Compare commits

..

41 Commits

Author SHA1 Message Date
a139b5a409 update deps (aggressive) 2024-07-22 12:58:36 -07:00
1b71bda9c7 wgpu 22.0.0 2024-07-22 12:33:39 -07:00
5f1b162775 use strafesnet registry 2024-03-30 13:39:06 -07:00
ed27ee060b pretty polygon fanning from vbsp code 2024-03-18 21:08:39 -07:00
5c107c1e5f print texture load error 2024-03-18 20:50:39 -07:00
3834194156 update deps 2024-03-13 13:57:21 -07:00
d0432188b5 include meshes symbolic link 2024-03-13 11:48:34 -07:00
9ff946c325 implement roblox mesh loading 2024-03-13 11:48:34 -07:00
63b2b1676c update deps 2024-03-13 11:48:34 -07:00
b7fc7d2a12 update deps 2024-03-02 13:19:01 -08:00
f273bbf4a9 update strafesnet deps 2024-03-02 13:18:56 -08:00
cc19917025 note 2024-02-22 02:17:20 -08:00
cc58f23512 the bug 2024-02-22 01:30:17 -08:00
b6e7ce4e25 fixup double references 2024-02-22 01:29:29 -08:00
b0d8f2e09a not-working 2024-02-22 00:57:09 -08:00
9b4b09798b notes about fixes 2024-02-21 22:58:40 -08:00
5a55aefb4f unused argument 2024-02-21 22:28:33 -08:00
8f606b7cfc ad-hoc fixups 2024-02-21 22:07:40 -08:00
fad7ed393b implicitly apply_to_body in apply_enum 2024-02-21 22:07:21 -08:00
d2a864e2c1 rename MoveState::changed to apply_enum 2024-02-21 22:07:05 -08:00
f027594ab4 before and after not needed currently 2024-02-21 05:24:12 -08:00
0026b92a72 hide hints 2024-02-21 05:20:40 -08:00
4dc706b783 notes 2024-02-21 05:20:40 -08:00
9bf3f55191 it works?? 2024-02-21 05:17:13 -08:00
eb34cce746 flyin 2024-02-21 04:12:08 -08:00
623a2d2a4f be pedantic about public private 2024-02-21 04:11:31 -08:00
aadcca91ea Debug 2024-02-21 03:23:50 -08:00
04c70ba0bc more private 2024-02-21 02:52:20 -08:00
865c086f14 actually implement this function 2024-02-21 02:48:49 -08:00
619049c11f unneeded 2024-02-21 02:48:41 -08:00
2acefe9a1a want water here 2024-02-21 02:41:19 -08:00
2496d71afa compiler 2024-02-21 02:32:00 -08:00
8ffbca204b wishful thinking 2024-02-21 02:31:50 -08:00
05e2f67e36 don't write duplicate code 2024-02-21 02:17:13 -08:00
b955407b06 delete get_move_state function 2024-02-21 01:32:29 -08:00
fb47b09925 wip 2024-02-21 01:32:03 -08:00
dbd08729d7 wip 2024-02-21 00:31:44 -08:00
c895a66ad6 wip 2024-02-20 22:27:31 -08:00
efdb4c97be edit jumped_velocity garbage 2024-02-18 01:52:29 -08:00
9396623f0c wip - notably remove camera interpolation for walking and rocket 2024-02-18 01:52:29 -08:00
c55156bb92 wip 2024-02-18 01:52:29 -08:00
201 changed files with 3084 additions and 20333 deletions

View File

@ -1,2 +1,2 @@
[registries.strafesnet]
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target
/textures

1
CONTRIBUTING.md Normal file
View File

@ -0,0 +1 @@
By contributing code to the [StrafesNET project](https://git.itzana.me/StrafesNET/strafe-client), you agree to license your contribution under the [License](LICENSE).

1592
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,30 @@
[workspace]
members = [
"engine/graphics",
"engine/physics",
"engine/session",
"engine/settings",
"integration-testing",
"lib/bsp_loader",
"lib/common",
"lib/deferred_loader",
"lib/fixed_wide",
"lib/linear_ops",
"lib/ratio_ops",
"lib/rbx_loader",
"lib/roblox_emulator",
"lib/snf",
"strafe-client",
]
resolver = "2"
[package]
name = "strafe-client"
version = "0.9.5"
edition = "2021"
repository = "https://git.itzana.me/StrafesNET/strafe-client"
license = "Custom"
description = "StrafesNET game client for bhop and surf."
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[profile.release]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bytemuck = { version = "1.13.1", features = ["derive"] }
configparser = "3.0.2"
ddsfile = "0.5.1"
glam = "0.25.0"
id = { version = "0.1.0", registry = "strafesnet" }
parking_lot = "0.12.1"
pollster = "0.3.0"
strafesnet_bsp_loader = { version = "0.1.1", registry = "strafesnet" }
strafesnet_common = { version = "0.1.2", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.3.0", features = ["legacy"], registry = "strafesnet" }
strafesnet_rbx_loader = { version = "0.3.0", registry = "strafesnet" }
wgpu = "22.0.0"
winit = "0.30.4"
#[profile.release]
#lto = true
strip = true
codegen-units = 1
#strip = true
#codegen-units = 1

View File

@ -1,16 +1,10 @@
<img align="right" width="25%" src="logo.png">
<img align="right" width="25%" src="strafe.png">
# Strafe Project
Monorepo for working on projects related to strafe client.
## Try it out
See [releases](https://git.itzana.me/StrafesNET/strafe-project/releases) for downloads.
# Strafe Client
In development client for jumping on squares (and riding on triangles)
## How to build and run
1. Have rust and git installed
2. `git clone https://git.itzana.me/StrafesNET/strafe-project`
3. `cd strafe-project`
4. `cargo run --release --bin strafe-client`
## Licenses
Each project has its own license. Most crates are MIT/Apache but notably the Strafe Client and engine crates have a sole proprietor license.
2. `git clone https://git.itzana.me/StrafesNET/strafe-client`
3. `cd strafe-client`
4. `cargo run --release`

View File

@ -1,14 +0,0 @@
[package]
name = "strafesnet_graphics"
version = "0.1.0"
edition = "2021"
[dependencies]
bytemuck = { version = "1.13.1", features = ["derive"] }
ddsfile = "0.5.1"
glam = "0.29.0"
id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
strafesnet_session = { path = "../session", registry = "strafesnet" }
strafesnet_settings = { path = "../settings", registry = "strafesnet" }
wgpu = "24.0.0"

View File

@ -1,2 +0,0 @@
pub mod model;
pub mod graphics;

View File

@ -1,10 +0,0 @@
[package]
name = "strafesnet_physics"
version = "0.1.0"
edition = "2021"
[dependencies]
arrayvec = "0.7.6"
glam = "0.29.0"
id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }

View File

@ -1,8 +0,0 @@
/*******************************************************
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
*
* This file is part of the StrafesNET bhop/surf client.
*
* StrafesNET can not be copied and/or distributed
* without the express permission of Rhys Lloyd
*******************************************************/

View File

@ -1,160 +0,0 @@
use strafesnet_common::aabb;
use strafesnet_common::integer::{self,vec3,Time,Planar64,Planar64Vec3};
#[derive(Clone,Copy,Debug,Hash)]
pub struct Body<T>{
pub position:Planar64Vec3,//I64 where 2^32 = 1 u
pub velocity:Planar64Vec3,//I64 where 2^32 = 1 u/s
pub acceleration:Planar64Vec3,//I64 where 2^32 = 1 u/s/s
pub time:Time<T>,//nanoseconds x xxxxD!
}
impl<T> std::ops::Neg for Body<T>{
type Output=Self;
fn neg(self)->Self::Output{
Self{
position:self.position,
velocity:-self.velocity,
acceleration:self.acceleration,
time:-self.time,
}
}
}
impl<T> Body<T>
where Time<T>:Copy,
{
pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO);
pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time<T>)->Self{
Self{
position,
velocity,
acceleration,
time,
}
}
pub const fn relative_to<'a>(&'a self,body0:&'a Body<T>)->VirtualBody<'a,T>{
//(p0,v0,a0,t0)
//(p1,v1,a1,t1)
VirtualBody{
body0,
body1:self,
}
}
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
let dt=time-self.time;
self.position
+(self.velocity*dt).map(|elem|elem.divide().fix_1())
+self.acceleration.map(|elem|(dt*dt*elem/2).divide().fix_1())
}
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
let dt=time-self.time;
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().fix_1())
}
pub fn advance_time(&mut self,time:Time<T>){
self.position=self.extrapolated_position(time);
self.velocity=self.extrapolated_velocity(time);
self.time=time;
}
pub fn extrapolated_position_ratio_dt<Num,Den,N1,D1,N2,N3,D2,N4,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
where
// Why?
// All of this can be removed with const generics because the type can be specified as
// Ratio<Fixed<N,NF>,Fixed<D,DF>>
// which is known to implement all the necessary traits
Num:Copy,
Den:Copy+core::ops::Mul<i64,Output=D1>,
D1:Copy,
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<D1,Output=N2>,
N1:core::ops::Add<N2,Output=N3>,
Num:core::ops::Mul<N3,Output=N4>,
Den:core::ops::Mul<D1,Output=D2>,
D2:Copy,
Planar64:core::ops::Mul<D2,Output=N4>,
N4:integer::Divide<D2,Output=T1>,
T1:integer::Fix<Planar64>,
{
// a*dt^2/2 + v*dt + p
// (a*dt/2+v)*dt+p
(self.acceleration.map(|elem|dt*elem/2)+self.velocity).map(|elem|dt.mul_ratio(elem))
.map(|elem|elem.divide().fix())+self.position
}
pub fn extrapolated_velocity_ratio_dt<Num,Den,N1,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
where
Num:Copy,
Den:Copy,
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<Den,Output=N1>,
N1:integer::Divide<Den,Output=T1>,
T1:integer::Fix<Planar64>,
{
// a*dt + v
self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity
}
pub fn advance_time_ratio_dt(&mut self,dt:crate::model::GigaTime){
self.position=self.extrapolated_position_ratio_dt(dt);
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
self.time+=dt.into();
}
pub fn infinity_dir(&self)->Option<Planar64Vec3>{
if self.velocity==vec3::ZERO{
if self.acceleration==vec3::ZERO{
None
}else{
Some(self.acceleration)
}
}else{
Some(self.velocity)
}
}
pub fn grow_aabb(&self,aabb:&mut aabb::Aabb,t0:Time<T>,t1:Time<T>){
aabb.grow(self.extrapolated_position(t0));
aabb.grow(self.extrapolated_position(t1));
//v+a*t==0
//goober code
if !self.acceleration.x.is_zero(){
let t=-self.velocity.x/self.acceleration.x;
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
aabb.grow(self.extrapolated_position_ratio_dt(t));
}
}
if !self.acceleration.y.is_zero(){
let t=-self.velocity.y/self.acceleration.y;
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
aabb.grow(self.extrapolated_position_ratio_dt(t));
}
}
if !self.acceleration.z.is_zero(){
let t=-self.velocity.z/self.acceleration.z;
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
aabb.grow(self.extrapolated_position_ratio_dt(t));
}
}
}
}
impl<T> std::fmt::Display for Body<T>{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"p({}) v({}) a({}) t({})",self.position,self.velocity,self.acceleration,self.time)
}
}
pub struct VirtualBody<'a,T>{
body0:&'a Body<T>,
body1:&'a Body<T>,
}
impl<T> VirtualBody<'_,T>
where Time<T>:Copy,
{
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time)
}
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
self.body1.extrapolated_velocity(time)-self.body0.extrapolated_velocity(time)
}
pub fn acceleration(&self)->Planar64Vec3{
self.body1.acceleration-self.body0.acceleration
}
pub fn body(&self,time:Time<T>)->Body<T>{
Body::new(self.extrapolated_position(time),self.extrapolated_velocity(time),self.acceleration(),time)
}
}

View File

@ -1,148 +0,0 @@
use crate::model::{GigaTime,FEV,MeshQuery,DirectedEdge};
use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3};
use crate::physics::{Time,Body};
enum Transition<M:MeshQuery>{
Miss,
Next(FEV<M>,GigaTime),
Hit(M::Face,GigaTime),
}
pub enum CrawlResult<M:MeshQuery>{
Miss(FEV<M>),
Hit(M::Face,GigaTime),
}
impl<M:MeshQuery> CrawlResult<M>{
pub fn hit(self)->Option<(M::Face,GigaTime)>{
match self{
CrawlResult::Miss(_)=>None,
CrawlResult::Hit(face,time)=>Some((face,time)),
}
}
pub fn miss(self)->Option<FEV<M>>{
match self{
CrawlResult::Miss(fev)=>Some(fev),
CrawlResult::Hit(_,_)=>None,
}
}
}
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
where
// This is hardcoded for MinkowskiMesh lol
M::Face:Copy,
M::Edge:Copy,
M::Vert:Copy,
F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>,
<F as core::ops::Mul<Fixed<1,32>>>::Output:core::iter::Sum,
<M as MeshQuery>::Offset:core::ops::Sub<<F as std::ops::Mul<Fixed<1,32>>>::Output>,
{
fn next_transition(&self,body_time:GigaTime,mesh:&M,body:&Body,mut best_time:GigaTime)->Transition<M>{
//conflicting derivative means it crosses in the wrong direction.
//if the transition time is equal to an already tested transition, do not replace the current best.
let mut best_transition=Transition::Miss;
match self{
&FEV::Face(face_id)=>{
//test own face collision time, ignoring roots with zero or conflicting derivative
//n=face.normal d=face.dot
//n.a t^2+n.v t+n.p-d==0
let (n,d)=mesh.face_nd(face_id);
//TODO: use higher precision d value?
//use the mesh transform translation instead of baking it into the d value.
for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt;
best_transition=Transition::Hit(face_id,dt);
break;
}
}
//test each edge collision time, ignoring roots with zero or conflicting derivative
for &directed_edge_id in mesh.face_edges(face_id).iter(){
let edge_n=mesh.directed_edge_n(directed_edge_id);
let n=n.cross(edge_n);
let verts=mesh.edge_verts(directed_edge_id.as_undirected());
//WARNING: d is moved out of the *2 block because of adding two vertices!
//WARNING: precision is swept under the rug!
for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt;
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
break;
}
}
}
//if none:
},
&FEV::Edge(edge_id)=>{
//test each face collision time, ignoring roots with zero or conflicting derivative
let edge_n=mesh.edge_n(edge_id);
let edge_verts=mesh.edge_verts(edge_id);
let delta_pos=body.position*2-(mesh.vert(edge_verts[0])+mesh.vert(edge_verts[1]));
for (i,&edge_face_id) in mesh.edge_faces(edge_id).iter().enumerate(){
let face_n=mesh.face_nd(edge_face_id).0;
//edge_n gets parity from the order of edge_faces
let n=face_n.cross(edge_n)*((i as i64)*2-1);
//WARNING yada yada d *2
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt;
best_transition=Transition::Next(FEV::Face(edge_face_id),dt);
break;
}
}
}
//test each vertex collision time, ignoring roots with zero or conflicting derivative
for (i,&vert_id) in edge_verts.iter().enumerate(){
//vertex normal gets parity from vert index
let n=edge_n*(1-2*(i as i64));
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
best_time=dt;
best_transition=Transition::Next(FEV::Vert(vert_id),dt);
break;
}
}
}
//if none:
},
&FEV::Vert(vert_id)=>{
//test each edge collision time, ignoring roots with zero or conflicting derivative
for &directed_edge_id in mesh.vert_edges(vert_id).iter(){
//edge is directed away from vertex, but we want the dot product to turn out negative
let n=-mesh.directed_edge_n(directed_edge_id);
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
best_time=dt;
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
break;
}
}
}
//if none:
},
}
best_transition
}
pub fn crawl(mut self,mesh:&M,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult<M>{
let mut body_time={
let r=(start_time-relative_body.time).to_ratio();
Ratio::new(r.num.fix_4(),r.den.fix_4())
};
let time_limit={
let r=(time_limit-relative_body.time).to_ratio();
Ratio::new(r.num.fix_4(),r.den.fix_4())
};
for _ in 0..20{
match self.next_transition(body_time,mesh,relative_body,time_limit){
Transition::Miss=>return CrawlResult::Miss(self),
Transition::Next(next_fev,next_time)=>(self,body_time)=(next_fev,next_time),
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
}
}
//TODO: fix all bugs
//println!("Too many iterations! Using default behaviour instead of crashing...");
CrawlResult::Miss(self)
}
}

View File

@ -1,45 +0,0 @@
mod body;
mod push_solve;
mod face_crawler;
mod model;
pub mod physics;
// Physics bug fixes can easily desync all bots.
//
// When replaying a bot, use the exact physics version which it was recorded with.
//
// When validating a new bot, ignore the version and use the latest version,
// and overwrite the version in the file.
//
// Compatible physics versions should be determined
// empirically at development time via leaderboard resimulation.
//
// Compatible physics versions should result in an identical leaderboard state,
// or the only bots which fail are ones exploiting a surgically patched bug.
#[derive(Clone,Copy,Hash,Debug,id::Id,Eq,PartialEq,Ord,PartialOrd)]
pub struct PhysicsVersion(u32);
pub const VERSION:PhysicsVersion=PhysicsVersion(0);
const LATEST_COMPATIBLE_VERSION:[u32;1+VERSION.0 as usize]=const{
let compat=[0];
let mut input_version=0;
while input_version<compat.len(){
// compatible version must be greater than or equal to the input version
assert!(input_version as u32<=compat[input_version]);
// compatible version must be a version that exists
assert!(compat[input_version]<=VERSION.0);
input_version+=1;
}
compat
};
pub enum PhysicsVersionError{
UnknownPhysicsVersion,
}
pub const fn get_latest_compatible_version(PhysicsVersion(version):PhysicsVersion)->Result<PhysicsVersion,PhysicsVersionError>{
if (version as usize)<LATEST_COMPATIBLE_VERSION.len(){
Ok(PhysicsVersion(LATEST_COMPATIBLE_VERSION[version as usize]))
}else{
Err(PhysicsVersionError::UnknownPhysicsVersion)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,349 +0,0 @@
use strafesnet_common::integer::{self,vec3::{self,Vector3},Fixed,Planar64,Planar64Vec3,Ratio};
// This algorithm is based on Lua code
// written by Trey Reynolds in 2021
// EPSILON=1/2^10
// A stack-allocated variable-size list that holds up to 4 elements
// Direct references are used instead of indices i0, i1, i2, i3
type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
// hack to allow comparing ratios to zero
const RATIO_ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON);
struct Ray{
origin:Planar64Vec3,
direction:Planar64Vec3,
}
impl Ray{
fn extrapolate<Num,Den,N1,T1>(&self,t:Ratio<Num,Den>)->Planar64Vec3
where
Num:Copy,
Den:Copy,
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<Den,Output=N1>,
N1:integer::Divide<Den,Output=T1>,
T1:integer::Fix<Planar64>,
{
self.origin+self.direction.map(|elem|(t*elem).divide().fix())
}
}
/// Information about a contact restriction
pub struct Contact{
pub position:Planar64Vec3,
pub velocity:Planar64Vec3,
pub normal:Planar64Vec3,
}
impl Contact{
fn relative_to(&self,point:Planar64Vec3)->Self{
Self{
position:self.position-point,
velocity:self.velocity,
normal:self.normal,
}
}
fn relative_dot(&self,direction:Planar64Vec3)->Fixed<2,64>{
(direction-self.velocity).dot(self.normal)
}
/// Calculate the time of intersection. (previously get_touch_time)
fn solve(&self,ray:&Ray)->Ratio<Fixed<2,64>,Fixed<2,64>>{
(self.position-ray.origin).dot(self.normal)/(ray.direction-self.velocity).dot(self.normal)
}
}
//note that this is horrible with fixed point arithmetic
fn solve1(c0:&Contact)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<2,64>>>{
const EPSILON:Fixed<2,64>=Fixed::from_bits(Fixed::<2,64>::ONE.to_bits().shr(10));
let det=c0.normal.dot(c0.velocity);
if det.abs()<EPSILON{
return None;
}
let d0=c0.normal.dot(c0.position);
Some(c0.normal*d0/det)
}
fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<Fixed<5,160>>,Fixed<4,128>>>{
const EPSILON:Fixed<4,128>=Fixed::from_bits(Fixed::<4,128>::ONE.to_bits().shr(10));
let u0_u1=c0.velocity.cross(c1.velocity);
let n0_n1=c0.normal.cross(c1.normal);
let det=u0_u1.dot(n0_n1);
if det.abs()<EPSILON{
return None;
}
let d0=c0.normal.dot(c0.position);
let d1=c1.normal.dot(c1.position);
Some((c1.normal.cross(u0_u1)*d0+u0_u1.cross(c0.normal)*d1)/det)
}
fn solve3(c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ratio<Vector3<Fixed<4,128>>,Fixed<3,96>>>{
const EPSILON:Fixed<3,96>=Fixed::from_bits(Fixed::<3,96>::ONE.to_bits().shr(10));
let n0_n1=c0.normal.cross(c1.normal);
let det=c2.normal.dot(n0_n1);
if det.abs()<EPSILON{
return None;
}
let d0=c0.normal.dot(c0.position);
let d1=c1.normal.dot(c1.position);
let d2=c2.normal.dot(c2.position);
Some((c1.normal.cross(c2.normal)*d0+c2.normal.cross(c0.normal)*d1+c0.normal.cross(c1.normal)*d2)/det)
}
fn decompose1(point:Planar64Vec3,u0:Planar64Vec3)->Option<[Ratio<Fixed<2,64>,Fixed<2,64>>;1]>{
let det=u0.dot(u0);
if det==Fixed::ZERO{
return None;
}
let s0=u0.dot(point)/det;
Some([s0])
}
fn decompose2(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3)->Option<[Ratio<Fixed<4,128>,Fixed<4,128>>;2]>{
let u0_u1=u0.cross(u1);
let det=u0_u1.dot(u0_u1);
if det==Fixed::ZERO{
return None;
}
let s0=u0_u1.dot(point.cross(u1))/det;
let s1=u0_u1.dot(u0.cross(point))/det;
Some([s0,s1])
}
fn decompose3(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3,u2:Planar64Vec3)->Option<[Ratio<Fixed<3,96>,Fixed<3,96>>;3]>{
let det=u0.cross(u1).dot(u2);
if det==Fixed::ZERO{
return None;
}
let s0=point.cross(u1).dot(u2)/det;
let s1=u0.cross(point).dot(u2)/det;
let s2=u0.cross(u1).dot(point)/det;
Some([s0,s1,s2])
}
fn is_space_enclosed_2(
a:Planar64Vec3,
b:Planar64Vec3,
)->bool{
a.cross(b)==Vector3::new([Fixed::ZERO;3])
&&a.dot(b).is_negative()
}
fn is_space_enclosed_3(
a:Planar64Vec3,
b:Planar64Vec3,
c:Planar64Vec3
)->bool{
a.cross(b).dot(c)==Fixed::ZERO
&&{
let det_abac=a.cross(b).dot(a.cross(c));
let det_abbc=a.cross(b).dot(b.cross(c));
let det_acbc=a.cross(c).dot(b.cross(c));
return!( det_abac*det_abbc).is_positive()
&&!( det_abbc*det_acbc).is_positive()
&&!(-det_acbc*det_abac).is_positive()
||is_space_enclosed_2(a,b)
||is_space_enclosed_2(a,c)
||is_space_enclosed_2(b,c)
}
}
fn is_space_enclosed_4(
a:Planar64Vec3,
b:Planar64Vec3,
c:Planar64Vec3,
d:Planar64Vec3,
)->bool{
let det_abc=a.cross(b).dot(c);
let det_abd=a.cross(b).dot(d);
let det_acd=a.cross(c).dot(d);
let det_bcd=b.cross(c).dot(d);
return( det_abc*det_abd).is_negative()
&&(-det_abc*det_acd).is_negative()
&&( det_abd*det_acd).is_negative()
&&( det_abc*det_bcd).is_negative()
&&(-det_abd*det_bcd).is_negative()
&&( det_acd*det_bcd).is_negative()
||is_space_enclosed_3(a,b,c)
||is_space_enclosed_3(a,b,d)
||is_space_enclosed_3(a,c,d)
||is_space_enclosed_3(b,c,d)
}
const fn get_push_ray_0(point:Planar64Vec3)->Ray{
Ray{origin:point,direction:vec3::ZERO}
}
fn get_push_ray_1(point:Planar64Vec3,c0:&Contact)->Option<Ray>{
let direction=solve1(c0)?.divide().fix_1();
let [s0]=decompose1(direction,c0.velocity)?;
if s0.lt_ratio(RATIO_ZERO){
return None;
}
let origin=point+solve1(
&c0.relative_to(point),
)?.divide().fix_1();
Some(Ray{origin,direction})
}
fn get_push_ray_2(point:Planar64Vec3,c0:&Contact,c1:&Contact)->Option<Ray>{
let direction=solve2(c0,c1)?.divide().fix_1();
let [s0,s1]=decompose2(direction,c0.velocity,c1.velocity)?;
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO){
return None;
}
let origin=point+solve2(
&c0.relative_to(point),
&c1.relative_to(point),
)?.divide().fix_1();
Some(Ray{origin,direction})
}
fn get_push_ray_3(point:Planar64Vec3,c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ray>{
let direction=solve3(c0,c1,c2)?.divide().fix_1();
let [s0,s1,s2]=decompose3(direction,c0.velocity,c1.velocity,c2.velocity)?;
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO)||s2.lt_ratio(RATIO_ZERO){
return None;
}
let origin=point+solve3(
&c0.relative_to(point),
&c1.relative_to(point),
&c2.relative_to(point),
)?.divide().fix_1();
Some(Ray{origin,direction})
}
const fn get_best_push_ray_and_conts_0<'a>(point:Planar64Vec3)->(Ray,Conts<'a>){
(get_push_ray_0(point),Conts::new_const())
}
fn get_best_push_ray_and_conts_1(point:Planar64Vec3,c0:&Contact)->Option<(Ray,Conts)>{
get_push_ray_1(point,c0)
.map(|ray|(ray,Conts::from_iter([c0])))
}
fn get_best_push_ray_and_conts_2<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact)->Option<(Ray,Conts<'a>)>{
if is_space_enclosed_2(c0.normal,c1.normal){
return None;
}
if let Some(ray)=get_push_ray_2(point,c0,c1){
return Some((ray,Conts::from_iter([c0,c1])));
}
if let Some(ray)=get_push_ray_1(point,c0){
if !c1.relative_dot(ray.direction).is_negative(){
return Some((ray,Conts::from_iter([c0])));
}
}
return None;
}
fn get_best_push_ray_and_conts_3<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact,c2:&'a Contact)->Option<(Ray,Conts<'a>)>{
if is_space_enclosed_3(c0.normal,c1.normal,c2.normal){
return None;
}
if let Some(ray)=get_push_ray_3(point,c0,c1,c2){
return Some((ray,Conts::from_iter([c0,c1,c2])));
}
if let Some(ray)=get_push_ray_2(point,c0,c1){
if !c2.relative_dot(ray.direction).is_negative(){
return Some((ray,Conts::from_iter([c0,c1])));
}
}
if let Some(ray)=get_push_ray_2(point,c0,c2){
if !c1.relative_dot(ray.direction).is_negative(){
return Some((ray,Conts::from_iter([c0,c2])));
}
}
if let Some(ray)=get_push_ray_1(point,c0){
if !c1.relative_dot(ray.direction).is_negative()
&&!c2.relative_dot(ray.direction).is_negative(){
return Some((ray,Conts::from_iter([c0])));
}
}
return None;
}
fn get_best_push_ray_and_conts_4<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact,c2:&'a Contact,c3:&'a Contact)->Option<(Ray,Conts<'a>)>{
if is_space_enclosed_4(c0.normal,c1.normal,c2.normal,c3.normal){
return None;
}
let (ray012,conts012)=get_best_push_ray_and_conts_3(point,c0,c1,c2)?;
let (ray013,conts013)=get_best_push_ray_and_conts_3(point,c0,c1,c3)?;
let (ray023,conts023)=get_best_push_ray_and_conts_3(point,c0,c2,c3)?;
let err012=c3.relative_dot(ray012.direction);
let err013=c2.relative_dot(ray013.direction);
let err023=c1.relative_dot(ray023.direction);
let best_err=err012.max(err013).max(err023);
if best_err==err012{
return Some((ray012,conts012))
}else if best_err==err013{
return Some((ray013,conts013))
}else if best_err==err023{
return Some((ray023,conts023))
}
unreachable!()
}
fn get_best_push_ray_and_conts<'a>(
point:Planar64Vec3,
conts:&[&'a Contact],
)->Option<(Ray,Conts<'a>)>{
match conts{
&[c0,c1,c2,c3]=>get_best_push_ray_and_conts_4(point,c0,c1,c2,c3),
&[c0,c1,c2]=>get_best_push_ray_and_conts_3(point,c0,c1,c2),
&[c0,c1]=>get_best_push_ray_and_conts_2(point,c0,c1),
&[c0]=>get_best_push_ray_and_conts_1(point,c0),
&[]=>Some(get_best_push_ray_and_conts_0(point)),
_=>unreachable!(),
}
}
fn get_first_touch<'a>(contacts:&'a [Contact],ray:&Ray,conts:&Conts)->Option<(Ratio<Fixed<2,64>,Fixed<2,64>>,&'a Contact)>{
contacts.iter()
.filter(|&contact|
!conts.iter().any(|&c|std::ptr::eq(c,contact))
&&contact.relative_dot(ray.direction).is_negative()
)
.map(|contact|(contact.solve(ray),contact))
.min_by_key(|&(t,_)|t)
}
pub fn push_solve(contacts:&[Contact],point:Planar64Vec3)->Planar64Vec3{
let (mut ray,mut conts)=get_best_push_ray_and_conts_0(point);
loop{
let (next_t,next_cont)=match get_first_touch(contacts,&ray,&conts){
Some((t,cont))=>(t,cont),
None=>return ray.origin,
};
if RATIO_ZERO.le_ratio(next_t){
return ray.origin;
}
//push_front
if conts.len()==conts.capacity(){
//this is a dead case, new_conts never has more than 3 elements
conts.rotate_right(1);
conts[0]=next_cont;
}else{
conts.push(next_cont);
conts.rotate_right(1);
}
let meet_point=ray.extrapolate(next_t);
match get_best_push_ray_and_conts(meet_point,conts.as_slice()){
Some((new_ray,new_conts))=>(ray,conts)=(new_ray,new_conts),
None=>return meet_point,
}
}
}
#[cfg(test)]
mod tests{
use super::*;
#[test]
fn test_push_solve(){
let contacts=vec![
Contact{
position:vec3::ZERO,
velocity:vec3::Y,
normal:vec3::Y,
}
];
assert_eq!(
vec3::ZERO,
push_solve(&contacts,vec3::NEG_Y)
);
}
}

View File

@ -1,12 +0,0 @@
[package]
name = "strafesnet_session"
version = "0.1.0"
edition = "2021"
[dependencies]
glam = "0.29.0"
replace_with = "0.1.7"
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
strafesnet_physics = { path = "../physics", registry = "strafesnet" }
strafesnet_settings = { path = "../settings", registry = "strafesnet" }
strafesnet_snf = { path = "../../lib/snf", registry = "strafesnet" }

View File

@ -1,8 +0,0 @@
/*******************************************************
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
*
* This file is part of the StrafesNET bhop/surf client.
*
* StrafesNET can not be copied and/or distributed
* without the express permission of Rhys Lloyd
*******************************************************/

View File

@ -1,2 +0,0 @@
mod mouse_interpolator;
pub mod session;

View File

@ -1,281 +0,0 @@
use strafesnet_common::mouse::MouseState;
use strafesnet_common::physics::{
MouseInstruction,SetControlInstruction,ModeInstruction,MiscInstruction,
Instruction as PhysicsInstruction,
TimeInner as PhysicsTimeInner,
Time as PhysicsTime,
};
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction};
type TimedSelfInstruction=TimedInstruction<Instruction,PhysicsTimeInner>;
type DoubleTimedSelfInstruction=TimedInstruction<TimedSelfInstruction,SessionTimeInner>;
type TimedPhysicsInstruction=TimedInstruction<PhysicsInstruction,PhysicsTimeInner>;
const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10);
/// To be fed into MouseInterpolator
#[derive(Clone,Debug)]
pub(crate) enum Instruction{
MoveMouse(glam::IVec2),
SetControl(SetControlInstruction),
Mode(ModeInstruction),
Misc(MiscInstruction),
Idle,
}
#[derive(Clone,Debug)]
enum UnbufferedInstruction{
MoveMouse(glam::IVec2),
NonMouse(NonMouseInstruction),
}
#[derive(Clone,Debug)]
enum BufferedInstruction{
Mouse(MouseInstruction),
NonMouse(NonMouseInstruction),
}
#[derive(Clone,Debug)]
pub(crate) enum NonMouseInstruction{
SetControl(SetControlInstruction),
Mode(ModeInstruction),
Misc(MiscInstruction),
Idle,
}
impl From<Instruction> for UnbufferedInstruction{
#[inline]
fn from(value:Instruction)->Self{
match value{
Instruction::MoveMouse(mouse_instruction)=>UnbufferedInstruction::MoveMouse(mouse_instruction),
Instruction::SetControl(set_control_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::SetControl(set_control_instruction)),
Instruction::Mode(mode_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Mode(mode_instruction)),
Instruction::Misc(misc_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Misc(misc_instruction)),
Instruction::Idle=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Idle),
}
}
}
impl From<BufferedInstruction> for PhysicsInstruction{
#[inline]
fn from(value:BufferedInstruction)->Self{
match value{
BufferedInstruction::Mouse(mouse_instruction)=>PhysicsInstruction::Mouse(mouse_instruction),
BufferedInstruction::NonMouse(non_mouse_instruction)=>match non_mouse_instruction{
NonMouseInstruction::SetControl(set_control_instruction)=>PhysicsInstruction::SetControl(set_control_instruction),
NonMouseInstruction::Mode(mode_instruction)=>PhysicsInstruction::Mode(mode_instruction),
NonMouseInstruction::Misc(misc_instruction)=>PhysicsInstruction::Misc(misc_instruction),
NonMouseInstruction::Idle=>PhysicsInstruction::Idle,
},
}
}
}
pub(crate) enum StepInstruction{
Pop,
Timeout,
}
#[derive(Clone,Debug)]
enum BufferState{
Unbuffered,
Initializing(SessionTime,MouseState<PhysicsTimeInner>),
Buffered(SessionTime,MouseState<PhysicsTimeInner>),
}
pub struct MouseInterpolator{
buffer_state:BufferState,
// double timestamped timeline?
buffer:std::collections::VecDeque<TimedPhysicsInstruction>,
output:std::collections::VecDeque<TimedPhysicsInstruction>,
}
// Maybe MouseInterpolator manipulation is better expressed using impls
// and called from Instruction trait impls in session
impl InstructionConsumer<TimedSelfInstruction> for MouseInterpolator{
type TimeInner=SessionTimeInner;
fn process_instruction(&mut self,ins:DoubleTimedSelfInstruction){
self.push_unbuffered_input(ins.time,ins.instruction.time,ins.instruction.instruction.into())
}
}
impl InstructionEmitter<StepInstruction> for MouseInterpolator{
type TimeInner=SessionTimeInner;
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
self.buffered_instruction_with_timeout(time_limit)
}
}
impl MouseInterpolator{
pub fn new()->MouseInterpolator{
MouseInterpolator{
buffer_state:BufferState::Unbuffered,
buffer:std::collections::VecDeque::new(),
output:std::collections::VecDeque::new(),
}
}
fn push_mouse_and_flush_buffer(&mut self,ins:TimedInstruction<MouseInstruction,PhysicsTimeInner>){
self.buffer.push_front(TimedInstruction{
time:ins.time,
instruction:BufferedInstruction::Mouse(ins.instruction).into(),
});
// flush buffer to output
if self.output.len()==0{
// swap buffers
core::mem::swap(&mut self.buffer,&mut self.output);
}else{
// append buffer contents to output
self.output.append(&mut self.buffer);
}
}
fn get_mouse_timedout_at(&self,time_limit:SessionTime)->Option<SessionTime>{
match &self.buffer_state{
BufferState::Unbuffered=>None,
BufferState::Initializing(time,_mouse_state)
|BufferState::Buffered(time,_mouse_state)=>{
let timeout=*time+MOUSE_TIMEOUT;
(timeout<time_limit).then_some(timeout)
}
}
}
fn timeout_mouse(&mut self,timeout_time:PhysicsTime){
// the state always changes to unbuffered
let buffer_state=core::mem::replace(&mut self.buffer_state,BufferState::Unbuffered);
match buffer_state{
BufferState::Unbuffered=>(),
BufferState::Initializing(_time,mouse_state)=>{
// only a single mouse move was sent in 10ms, this is very much an edge case!
self.push_mouse_and_flush_buffer(TimedInstruction{
time:mouse_state.time,
instruction:MouseInstruction::ReplaceMouse{
m1:MouseState{pos:mouse_state.pos,time:timeout_time},
m0:mouse_state,
},
});
}
BufferState::Buffered(_time,mouse_state)=>{
// duplicate the currently buffered mouse state but at a later (future, from the physics perspective) time
self.push_mouse_and_flush_buffer(TimedInstruction{
time:mouse_state.time,
instruction:MouseInstruction::SetNextMouse(MouseState{pos:mouse_state.pos,time:timeout_time}),
});
},
}
}
fn push_unbuffered_input(&mut self,session_time:SessionTime,physics_time:PhysicsTime,ins:UnbufferedInstruction){
// new input
// if there is zero instruction buffered, it means the mouse is not moving
// case 1: unbuffered
// no mouse event is buffered
// - ins is mouse event? change to buffered
// - ins other -> write to timeline
// case 2: buffered
// a mouse event is buffered, and exists within the last 10ms
// case 3: stop
// a mouse event is buffered, but no mouse events have transpired within 10ms
// replace_with allows the enum variant to safely be replaced
// from behind a mutable reference, but a panic in the closure means that
// the entire program terminates rather than completing an unwind.
let (ins_mouse,ins_other)=replace_with::replace_with_or_abort_and_return(&mut self.buffer_state,|buffer_state|{
match ins{
UnbufferedInstruction::MoveMouse(pos)=>{
let next_mouse_state=MouseState{pos,time:physics_time};
match buffer_state{
BufferState::Unbuffered=>{
((None,None),BufferState::Initializing(session_time,next_mouse_state))
},
BufferState::Initializing(_time,mouse_state)=>{
let ins_mouse=TimedInstruction{
time:mouse_state.time,
instruction:MouseInstruction::ReplaceMouse{
m0:mouse_state,
m1:next_mouse_state.clone(),
},
};
((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state))
},
BufferState::Buffered(_time,mouse_state)=>{
let ins_mouse=TimedInstruction{
time:mouse_state.time,
instruction:MouseInstruction::SetNextMouse(next_mouse_state.clone()),
};
((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state))
},
}
},
UnbufferedInstruction::NonMouse(other_instruction)=>((None,Some(TimedInstruction{
time:physics_time,
instruction:other_instruction,
})),buffer_state),
}
});
if let Some(ins)=ins_mouse{
self.push_mouse_and_flush_buffer(ins);
}
if let Some(ins)=ins_other{
let instruction=TimedInstruction{
time:ins.time,
instruction:BufferedInstruction::NonMouse(ins.instruction).into(),
};
if matches!(self.buffer_state,BufferState::Unbuffered){
self.output.push_back(instruction);
}else{
self.buffer.push_back(instruction);
}
}
}
fn buffered_instruction_with_timeout(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,SessionTimeInner>>{
match self.get_mouse_timedout_at(time_limit){
Some(timeout)=>Some(TimedInstruction{
time:timeout,
instruction:StepInstruction::Timeout,
}),
None=>(self.output.len()!=0).then_some(TimedInstruction{
// this timestamp should not matter
time:time_limit,
instruction:StepInstruction::Pop,
}),
}
}
pub fn pop_buffered_instruction(&mut self,ins:TimedInstruction<StepInstruction,PhysicsTimeInner>)->Option<TimedPhysicsInstruction>{
match ins.instruction{
StepInstruction::Pop=>(),
StepInstruction::Timeout=>self.timeout_mouse(ins.time),
}
self.output.pop_front()
}
}
#[cfg(test)]
mod test{
use super::*;
#[test]
fn test(){
let mut interpolator=MouseInterpolator::new();
let timer=strafesnet_common::timer::Timer::<strafesnet_common::timer::Scaled<SessionTimeInner,PhysicsTimeInner>>::unpaused(SessionTime::ZERO,PhysicsTime::from_secs(1000));
macro_rules! push{
($time:expr,$ins:expr)=>{
println!("in={:?}",$ins);
interpolator.push_unbuffered_input(
$time,
timer.time($time),
$ins,
);
while let Some(ins)=interpolator.buffered_instruction_with_timeout($time){
let ins_retimed=TimedInstruction{
time:timer.time(ins.time),
instruction:ins.instruction,
};
let out=interpolator.pop_buffered_instruction(ins_retimed);
println!("out={out:?}");
}
};
}
// test each buffer_state transition
let mut t=SessionTime::ZERO;
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
t+=SessionTime::from_millis(5);
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
t+=SessionTime::from_millis(5);
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
t+=SessionTime::from_millis(1);
}
}

View File

@ -1,443 +0,0 @@
use std::collections::HashMap;
use strafesnet_common::gameplay_modes::{ModeId,StageId};
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction};
// session represents the non-hardware state of the client.
// Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state.
use strafesnet_common::physics::{
ModeInstruction,MiscInstruction,
Instruction as PhysicsInputInstruction,
TimeInner as PhysicsTimeInner,
Time as PhysicsTime
};
use strafesnet_common::timer::{Scaled,Timer};
use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime};
use strafesnet_settings::directories::Directories;
use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction};
use strafesnet_physics::physics::{self,PhysicsContext,PhysicsData};
use strafesnet_settings::settings::UserSettings;
pub enum Instruction<'a>{
Input(SessionInputInstruction),
Control(SessionControlInstruction),
Playback(SessionPlaybackInstruction),
ChangeMap(&'a strafesnet_common::map::CompleteMap),
LoadReplay(strafesnet_snf::bot::Segment),
Idle,
}
pub enum SessionInputInstruction{
Mouse(glam::IVec2),
SetControl(strafesnet_common::physics::SetControlInstruction),
Mode(ImplicitModeInstruction),
Misc(strafesnet_common::physics::MiscInstruction),
}
/// Implicit mode instruction are fed separately to session.
/// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction
#[derive(Clone,Debug)]
pub enum ImplicitModeInstruction{
ResetAndRestart,
ResetAndSpawn(ModeId,StageId),
}
pub enum SessionControlInstruction{
SetPaused(bool),
// copy the current session simulation recording into a replay and view it
CopyRecordingIntoReplayAndSpectate,
StopSpectate,
SaveReplay,
LoadIntoReplayState,
}
pub enum SessionPlaybackInstruction{
SkipForward,
SkipBack,
TogglePaused,
DecreaseTimescale,
IncreaseTimescale,
}
pub struct FrameState{
pub body:physics::Body,
pub camera:physics::PhysicsCamera,
pub time:PhysicsTime,
}
pub struct Simulation{
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
physics:physics::PhysicsState,
}
impl Simulation{
pub const fn new(
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
physics:physics::PhysicsState,
)->Self{
Self{
timer,
physics,
}
}
pub fn get_frame_state(&self,time:SessionTime)->FrameState{
FrameState{
body:self.physics.camera_body(),
camera:self.physics.camera(),
time:self.timer.time(time),
}
}
}
#[derive(Default)]
pub struct Recording{
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
}
impl Recording{
pub fn new(
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
)->Self{
Self{instructions}
}
fn clear(&mut self){
self.instructions.clear();
}
}
pub struct Replay{
next_instruction_id:usize,
recording:Recording,
simulation:Simulation,
}
impl Replay{
pub const fn new(
recording:Recording,
simulation:Simulation,
)->Self{
Self{
next_instruction_id:0,
recording,
simulation,
}
}
pub fn advance(&mut self,physics_data:&PhysicsData,time_limit:SessionTime){
let mut time=self.simulation.timer.time(time_limit);
loop{
if let Some(ins)=self.recording.instructions.get(self.next_instruction_id){
if ins.time<time{
PhysicsContext::run_input_instruction(&mut self.simulation.physics,physics_data,ins.clone());
self.next_instruction_id+=1;
}else{
break;
}
}else{
// loop playback
self.next_instruction_id=0;
// No need to reset physics because the very first instruction is 'Reset'
let new_time=self.recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
self.simulation.timer.set_time(time_limit,new_time);
time=new_time;
}
}
}
}
#[derive(Clone,Copy,Hash,PartialEq,Eq)]
struct BotId(u32);
//#[derive(Clone,Copy,Hash,PartialEq,Eq)]
//struct PlayerId(u32);
enum ViewState{
Play,
//Spectate(PlayerId),
Replay(BotId),
}
pub struct Session{
directories:Directories,
user_settings:UserSettings,
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
view_state:ViewState,
//gui:GuiState
geometry_shared:physics::PhysicsData,
simulation:Simulation,
// below fields not included in lite session
recording:Recording,
//players:HashMap<PlayerId,Simulation>,
replays:HashMap<BotId,Replay>,
}
impl Session{
pub fn new(
user_settings:UserSettings,
directories:Directories,
simulation:Simulation,
)->Self{
Self{
user_settings,
directories,
mouse_interpolator:MouseInterpolator::new(),
geometry_shared:Default::default(),
simulation,
view_state:ViewState::Play,
recording:Default::default(),
replays:HashMap::new(),
}
}
fn clear_recording(&mut self){
self.recording.clear();
}
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
self.simulation.physics.clear();
self.geometry_shared.generate_models(map);
}
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
match &self.view_state{
ViewState::Play=>Some(self.simulation.get_frame_state(time)),
ViewState::Replay(bot_id)=>self.replays.get(bot_id).map(|replay|
replay.simulation.get_frame_state(time)
),
}
}
pub fn user_settings(&self)->&UserSettings{
&self.user_settings
}
}
// mouseinterpolator consumes RawInputInstruction
// mouseinterpolator emits PhysicsInputInstruction
// mouseinterpolator consumes DoStep to move on to the next emitted instruction
// Session comsumes SessionInstruction -> forwards RawInputInstruction to mouseinterpolator
// Session consumes DoStep -> forwards DoStep to mouseinterpolator
// Session emits DoStep
impl InstructionConsumer<Instruction<'_>> for Session{
type TimeInner=SessionTimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::TimeInner>){
// repetitive procedure macro
macro_rules! run_mouse_interpolator_instruction{
($instruction:expr)=>{
self.mouse_interpolator.process_instruction(TimedInstruction{
time:ins.time,
instruction:TimedInstruction{
time:self.simulation.timer.time(ins.time),
instruction:$instruction,
},
});
};
}
// process any timeouts that occured since the last instruction
self.process_exhaustive(ins.time);
match ins.instruction{
// send it down to MouseInterpolator with two timestamps, SessionTime and PhysicsTime
Instruction::Input(SessionInputInstruction::Mouse(pos))=>{
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::MoveMouse(pos));
},
Instruction::Input(SessionInputInstruction::SetControl(set_control_instruction))=>{
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::SetControl(set_control_instruction));
},
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndRestart))=>{
self.clear_recording();
let mode_id=self.simulation.physics.mode();
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Restart(mode_id)));
},
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndSpawn(mode_id,spawn_id)))=>{
self.clear_recording();
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Spawn(mode_id,spawn_id)));
},
Instruction::Input(SessionInputInstruction::Misc(misc_instruction))=>{
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(misc_instruction));
},
Instruction::Control(SessionControlInstruction::SetPaused(paused))=>{
// don't flush the buffered instructions in the mouse interpolator
// until the mouse is confirmed to be not moving at a later time
// what if they pause for 5ms lmao
_=self.simulation.timer.set_paused(ins.time,paused);
},
Instruction::Control(SessionControlInstruction::CopyRecordingIntoReplayAndSpectate)=> if let ViewState::Play=self.view_state{
// Bind: B
// pause simulation
_=self.simulation.timer.set_paused(ins.time,true);
// create recording
let mut recording=Recording::default();
recording.instructions.extend(self.recording.instructions.iter().cloned());
// create timer starting at first instruction (or zero if the list is empty)
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
let timer=Timer::unpaused(ins.time,new_time);
// create default physics state
let simulation=Simulation::new(timer,Default::default());
// invent a new bot id and insert the replay
let bot_id=BotId(self.replays.len() as u32);
self.replays.insert(bot_id,Replay::new(
recording,
simulation,
));
// begin spectate
self.view_state=ViewState::Replay(bot_id);
},
Instruction::Control(SessionControlInstruction::StopSpectate)=>{
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
// delete the bot, otherwise it's inaccessible and wastes CPU
match view_state{
ViewState::Play=>(),
ViewState::Replay(bot_id)=>{
self.replays.remove(&bot_id);
},
}
_=self.simulation.timer.set_paused(ins.time,false);
},
Instruction::Control(SessionControlInstruction::SaveReplay)=>{
// Bind: N
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
match view_state{
ViewState::Play=>(),
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
let mut replays_path=self.directories.replays.clone();
let file_name=format!("{}.snfb",ins.time);
std::thread::spawn(move ||{
std::fs::create_dir_all(replays_path.as_path()).unwrap();
replays_path.push(file_name);
let file=std::fs::File::create(replays_path).unwrap();
strafesnet_snf::bot::write_bot(
std::io::BufWriter::new(file),
strafesnet_physics::VERSION.get(),
replay.recording.instructions
).unwrap();
println!("Finished writing bot file!");
});
},
}
_=self.simulation.timer.set_paused(ins.time,false);
},
Instruction::Control(SessionControlInstruction::LoadIntoReplayState)=>{
// Bind: J
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
match view_state{
ViewState::Play=>(),
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
self.recording.instructions=replay.recording.instructions.into_iter().take(replay.next_instruction_id).collect();
self.simulation=replay.simulation;
},
}
// don't unpause -- use the replay timer state whether it is pasued or unpaused
},
Instruction::Playback(SessionPlaybackInstruction::IncreaseTimescale)=>{
match &self.view_state{
ViewState::Play=>{
// allow simulation timescale for fun
let scale=self.simulation.timer.get_scale();
self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap());
},
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
let scale=replay.simulation.timer.get_scale();
replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap());
},
}
},
Instruction::Playback(SessionPlaybackInstruction::DecreaseTimescale)=>{
match &self.view_state{
ViewState::Play=>{
// allow simulation timescale for fun
let scale=self.simulation.timer.get_scale();
self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap());
},
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
let scale=replay.simulation.timer.get_scale();
replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap());
},
}
},
Instruction::Playback(SessionPlaybackInstruction::SkipForward)=>{
match &self.view_state{
ViewState::Play=>(),
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
replay.simulation.timer.set_time(ins.time,time);
},
}
},
Instruction::Playback(SessionPlaybackInstruction::SkipBack)=>{
match &self.view_state{
ViewState::Play=>(),
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
replay.simulation.timer.set_time(ins.time,time);
// resimulate the entire playback lol
replay.next_instruction_id=0;
},
}
},
Instruction::Playback(SessionPlaybackInstruction::TogglePaused)=>{
match &self.view_state{
ViewState::Play=>(),
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
_=replay.simulation.timer.set_paused(ins.time,!replay.simulation.timer.is_paused());
},
}
}
Instruction::ChangeMap(complete_map)=>{
self.clear_recording();
self.change_map(complete_map);
},
Instruction::LoadReplay(bot)=>{
// pause simulation
_=self.simulation.timer.set_paused(ins.time,true);
// create recording
let recording=Recording::new(bot.instructions);
// create timer starting at first instruction (or zero if the list is empty)
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
let timer=Timer::unpaused(ins.time,new_time);
// create default physics state
let simulation=Simulation::new(timer,Default::default());
// invent a new bot id and insert the replay
let bot_id=BotId(self.replays.len() as u32);
self.replays.insert(bot_id,Replay::new(
recording,
simulation,
));
// begin spectate
self.view_state=ViewState::Replay(bot_id);
},
Instruction::Idle=>{
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Idle);
// this just refreshes the replays
for replay in self.replays.values_mut(){
// TODO: filter idles from recording, inject new idles in real time
replay.advance(&self.geometry_shared,ins.time);
}
}
};
// process all emitted output instructions
self.process_exhaustive(ins.time);
}
}
impl InstructionConsumer<StepInstruction> for Session{
type TimeInner=SessionTimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::TimeInner>){
let time=self.simulation.timer.time(ins.time);
if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins.set_time(time)){
//record
self.recording.instructions.push(instruction.clone());
PhysicsContext::run_input_instruction(&mut self.simulation.physics,&self.geometry_shared,instruction);
}
}
}
impl InstructionEmitter<StepInstruction> for Session{
type TimeInner=SessionTimeInner;
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
self.mouse_interpolator.next_instruction(time_limit)
}
}

View File

@ -1,10 +0,0 @@
[package]
name = "strafesnet_settings"
version = "0.1.0"
edition = "2021"
[dependencies]
configparser = "3.0.2"
directories = "6.0.0"
glam = "0.29.0"
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }

View File

@ -1,8 +0,0 @@
/*******************************************************
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
*
* This file is part of the StrafesNET bhop/surf client.
*
* StrafesNET can not be copied and/or distributed
* without the express permission of Rhys Lloyd
*******************************************************/

View File

@ -1,32 +0,0 @@
use std::path::PathBuf;
use crate::settings::{UserSettings,load_user_settings};
pub struct Directories{
pub settings:PathBuf,
pub maps:PathBuf,
pub replays:PathBuf,
}
impl Directories{
pub fn settings(&self)->UserSettings{
load_user_settings(&self.settings)
}
pub fn user()->Option<Self>{
let dirs=directories::ProjectDirs::from("net.strafes","StrafesNET","Strafe Client")?;
Some(Self{
settings:dirs.config_dir().join("settings.conf"),
maps:dirs.cache_dir().join("maps"),
// separate directory for remote downloaded replays (cache)
// bots:dirs.cache_dir().join("bots"),
replays:dirs.data_local_dir().join("replays"),
})
}
pub fn portable()->Result<Self,std::io::Error>{
let current_dir=std::env::current_dir()?;
Ok(Self{
settings:current_dir.join("settings.conf"),
maps:current_dir.join("maps"),
replays:current_dir.join("replays"),
})
}
}

View File

@ -1,2 +0,0 @@
pub mod settings;
pub mod directories;

View File

@ -1,9 +0,0 @@
[package]
name = "integration-testing"
version = "0.1.0"
edition = "2021"
[dependencies]
strafesnet_common = { version = "0.5.2", path = "../lib/common", registry = "strafesnet" }
strafesnet_physics = { version = "0.1.0", path = "../engine/physics", registry = "strafesnet" }
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet" }

View File

@ -1,221 +0,0 @@
use std::{io::{Cursor,Read},path::Path};
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
fn main(){
test_determinism().unwrap();
}
#[allow(unused)]
#[derive(Debug)]
enum ReplayError{
IO(std::io::Error),
SNF(strafesnet_snf::Error),
SNFM(strafesnet_snf::map::Error),
SNFB(strafesnet_snf::bot::Error),
}
impl From<std::io::Error> for ReplayError{
fn from(value:std::io::Error)->Self{
Self::IO(value)
}
}
impl From<strafesnet_snf::Error> for ReplayError{
fn from(value:strafesnet_snf::Error)->Self{
Self::SNF(value)
}
}
impl From<strafesnet_snf::map::Error> for ReplayError{
fn from(value:strafesnet_snf::map::Error)->Self{
Self::SNFM(value)
}
}
impl From<strafesnet_snf::bot::Error> for ReplayError{
fn from(value:strafesnet_snf::bot::Error)->Self{
Self::SNFB(value)
}
}
fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{
let mut file=std::fs::File::open(path)?;
let mut data=Vec::new();
file.read_to_end(&mut data)?;
Ok(Cursor::new(data))
}
fn run_replay()->Result<(),ReplayError>{
println!("loading map file..");
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
println!("loading bot file..");
let data=read_entire_file("../tools/replays/535s+159764769ns.snfb")?;
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
// create recording
let mut physics_data=PhysicsData::default();
println!("generating models..");
physics_data.generate_models(&map);
println!("simulating...");
let mut physics=PhysicsState::default();
for ins in bot.instructions{
PhysicsContext::run_input_instruction(&mut physics,&physics_data,ins);
}
match physics.get_finish_time(){
Some(time)=>println!("finish time:{}",time),
None=>println!("simulation did not end in finished state"),
}
Ok(())
}
enum DeterminismResult{
Deterministic,
NonDeterministic,
}
fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsData)->DeterminismResult{
// create default physics state
let mut physics_deterministic=PhysicsState::default();
// create a second physics state
let mut physics_filtered=PhysicsState::default();
// invent a new bot id and insert the replay
println!("simulating...");
let mut non_idle_count=0;
for (i,ins) in bot.instructions.into_iter().enumerate(){
let state_deterministic=physics_deterministic.clone();
let state_filtered=physics_filtered.clone();
PhysicsContext::run_input_instruction(&mut physics_deterministic,&physics_data,ins.clone());
match ins{
strafesnet_common::instruction::TimedInstruction{instruction:strafesnet_common::physics::Instruction::Idle,..}=>(),
other=>{
non_idle_count+=1;
// run
PhysicsContext::run_input_instruction(&mut physics_filtered,&physics_data,other.clone());
// check if position matches
let b0=physics_deterministic.camera_body();
let b1=physics_filtered.camera_body();
if b0.position!=b1.position{
println!("desync at instruction #{}",i);
println!("non idle instructions completed={non_idle_count}");
println!("instruction #{i}={:?}",other);
println!("deterministic state0:\n{state_deterministic:?}");
println!("filtered state0:\n{state_filtered:?}");
println!("deterministic state1:\n{:?}",physics_deterministic);
println!("filtered state1:\n{:?}",physics_filtered);
return DeterminismResult::NonDeterministic;
}
},
}
}
match physics_deterministic.get_finish_time(){
Some(time)=>println!("[with idle] finish time:{}",time),
None=>println!("[with idle] simulation did not end in finished state"),
}
match physics_filtered.get_finish_time(){
Some(time)=>println!("[filtered] finish time:{}",time),
None=>println!("[filtered] simulation did not end in finished state"),
}
DeterminismResult::Deterministic
}
type ThreadResult=Result<Option<DeterminismResult>,ReplayError>;
fn read_and_run(file_path:std::path::PathBuf,physics_data:&PhysicsData)->ThreadResult{
let data=read_entire_file(file_path.as_path())?;
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
println!("Running {:?}",file_path.file_stem());
Ok(Some(segment_determinism(bot,physics_data)))
}
fn do_thread<'a>(s:&'a std::thread::Scope<'a,'_>,file_path:std::path::PathBuf,send:std::sync::mpsc::Sender<ThreadResult>,physics_data:&'a PhysicsData){
s.spawn(move ||{
let result=read_and_run(file_path,physics_data);
// send when thread is complete
send.send(result).unwrap();
});
}
fn get_file_path(dir_entry:std::fs::DirEntry)->Result<Option<std::path::PathBuf>,std::io::Error>{
Ok(dir_entry.file_type()?.is_file().then_some(
dir_entry.path()
))
}
fn test_determinism()->Result<(),ReplayError>{
let thread_limit=std::thread::available_parallelism()?.get();
println!("loading map file..");
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
let mut physics_data=PhysicsData::default();
println!("generating models..");
physics_data.generate_models(&map);
let (send,recv)=std::sync::mpsc::channel();
let mut read_dir=std::fs::read_dir("../tools/replays")?;
// promise that &physics_data will outlive the spawned threads
let thread_results=std::thread::scope(|s|{
let mut thread_results=Vec::new();
// spawn threads
println!("spawning up to {thread_limit} threads...");
let mut active_thread_count=0;
while active_thread_count<thread_limit{
if let Some(dir_entry_result)=read_dir.next(){
if let Some(file_path)=get_file_path(dir_entry_result?)?{
active_thread_count+=1;
do_thread(s,file_path,send.clone(),&physics_data);
}
}else{
break;
}
}
// spawn another thread every time a message is received from the channel
println!("riding parallelism wave...");
while let Some(dir_entry_result)=read_dir.next(){
if let Some(file_path)=get_file_path(dir_entry_result?)?{
// wait for a thread to complete
thread_results.push(recv.recv().unwrap());
do_thread(s,file_path,send.clone(),&physics_data);
}
}
// wait for remaining threads to complete
println!("waiting for all threads to complete...");
for _ in 0..active_thread_count{
thread_results.push(recv.recv().unwrap());
}
println!("done.");
Ok::<_,ReplayError>(thread_results)
})?;
// tally results
#[derive(Default)]
struct Totals{
deterministic:u32,
nondeterministic:u32,
invalid:u32,
error:u32,
}
let Totals{deterministic,nondeterministic,invalid,error}=thread_results.into_iter().fold(Totals::default(),|mut totals,result|{
match result{
Ok(Some(DeterminismResult::Deterministic))=>totals.deterministic+=1,
Ok(Some(DeterminismResult::NonDeterministic))=>totals.nondeterministic+=1,
Ok(None)=>totals.invalid+=1,
Err(_)=>totals.error+=1,
}
totals
});
println!("deterministic={deterministic}");
println!("nondeterministic={nondeterministic}");
println!("invalid={invalid}");
println!("error={error}");
assert!(nondeterministic==0);
assert!(invalid==0);
assert!(error==0);
Ok(())
}

View File

@ -1,19 +0,0 @@
Vectors: Fixed Size, Fixed Point, Wide
======================================
## These exist separately in the Rust ecosystem, but not together.
#### License
<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>
<br>
<sub>
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
</sub>

View File

@ -1 +0,0 @@
/target

View File

@ -1,16 +0,0 @@
[package]
name = "strafesnet_bsp_loader"
version = "0.2.2"
edition = "2021"
repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0"
description = "Convert Valve BSP files to StrafesNET data structures."
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
glam = "0.29.0"
strafesnet_common = { path = "../common", registry = "strafesnet" }
vbsp = "0.6.0"
vmdl = "0.2.0"

View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -1,23 +0,0 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -1,19 +0,0 @@
StrafesNET BSP Loader
=====================
## Convert Valve BSP files into StrafesNET data structures
#### License
<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>
<br>
<sub>
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
</sub>

View File

@ -1,333 +0,0 @@
use strafesnet_common::{map,model,integer,gameplay_attributes};
const VALVE_SCALE:f32=1.0/16.0;
fn valve_transform([x,y,z]:[f32;3])->integer::Planar64Vec3{
integer::vec3::try_from_f32_array([x*VALVE_SCALE,z*VALVE_SCALE,-y*VALVE_SCALE]).unwrap()
}
pub fn convert_bsp<AcquireRenderConfigId,AcquireMeshId>(
bsp:&vbsp::Bsp,
mut acquire_render_config_id:AcquireRenderConfigId,
mut acquire_mesh_id:AcquireMeshId
)->PartialMap1
where
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
AcquireMeshId:FnMut(&str)->model::MeshId,
{
//figure out real attributes later
let mut unique_attributes=Vec::new();
unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration);
const TEMP_TOUCH_ME_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0);
let mut prop_mesh_count=0;
//declare all prop models to Loader
let prop_models=bsp.static_props().map(|prop|{
//get or create mesh_id
let mesh_id=acquire_mesh_id(prop.model());
//not the most failsafe code but this is just for the map tool lmao
if prop_mesh_count==mesh_id.get(){
prop_mesh_count+=1;
};
let placement=prop.as_prop_placement();
model::Model{
mesh:mesh_id,
attributes:TEMP_TOUCH_ME_ATTRIBUTE,
transform:integer::Planar64Affine3::new(
integer::mat3::try_from_f32_array_2d((
glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale))
//TODO: figure this out
*glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into()))
).to_cols_array_2d()).unwrap(),
valve_transform(placement.origin.into()),
),
color:glam::Vec4::ONE,
}
}).collect();
//TODO: make the main map one single mesh with a bunch of different physics groups and graphics groups
//the generated MeshIds in here will collide with the Loader Mesh Ids
//but I can't think of a good workaround other than just remapping one later.
let world_meshes:Vec<model::Mesh>=bsp.models().map(|world_model|{
//non-deduplicated
let mut spam_pos=Vec::new();
let mut spam_tex=Vec::new();
let mut spam_normal=Vec::new();
let mut spam_vertices=Vec::new();
let mut graphics_groups=Vec::new();
let mut physics_group=model::IndexedPhysicsGroup::default();
let polygon_groups=world_model.faces().enumerate().map(|(polygon_group_id,face)|{
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
let face_texture=face.texture();
let face_texture_data=face_texture.texture_data();
//this would be better as a 4x2 matrix
let texture_transform_u=glam::Vec4::from_array(face_texture.texture_transforms_u)/(face_texture_data.width as f32);
let texture_transform_v=glam::Vec4::from_array(face_texture.texture_transforms_v)/(face_texture_data.height as f32);
//this automatically figures out what the texture is trying to do and creates
//a render config for it, and then returns the id to that render config
let render_id=acquire_render_config_id(Some(face_texture_data.name()));
//normal
let normal=face.normal();
let normal_idx=spam_normal.len() as u32;
spam_normal.push(valve_transform(normal.into()));
let mut polygon_iter=face.vertex_positions().map(|vertex_position|{
//world_model.origin seems to always be 0,0,0
let vertex_xyz=(world_model.origin+vertex_position).into();
let pos_idx=spam_pos.len();
spam_pos.push(valve_transform(vertex_xyz));
//calculate texture coordinates
let pos=glam::Vec3::from_array(vertex_xyz).extend(1.0);
let tex=glam::vec2(texture_transform_u.dot(pos),texture_transform_v.dot(pos));
let tex_idx=spam_tex.len() as u32;
spam_tex.push(tex);
let vertex_id=model::VertexId::new(spam_vertices.len() as u32);
spam_vertices.push(model::IndexedVertex{
pos:model::PositionId::new(pos_idx as u32),
tex:model::TextureCoordinateId::new(tex_idx as u32),
normal:model::NormalId::new(normal_idx),
color:model::ColorId::new(0),
});
vertex_id
});
let polygon_list=std::iter::from_fn(move||{
match (polygon_iter.next(),polygon_iter.next(),polygon_iter.next()){
(Some(v1),Some(v2),Some(v3))=>Some(vec![v1,v2,v3]),
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
_=>None,
}
}).collect();
if face.is_visible(){
//TODO: deduplicate graphics groups by render id
graphics_groups.push(model::IndexedGraphicsGroup{
render:render_id,
groups:vec![polygon_group_id],
})
}
physics_group.groups.push(polygon_group_id);
model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
}).collect();
model::Mesh{
unique_pos:spam_pos,
unique_tex:spam_tex,
unique_normal:spam_normal,
unique_color:vec![glam::Vec4::ONE],
unique_vertices:spam_vertices,
polygon_groups,
graphics_groups,
physics_groups:vec![physics_group],
}
}).collect();
let world_models:Vec<model::Model>=
//one instance of the main world mesh
std::iter::once((
//world_model
model::MeshId::new(0),
//model_origin
vbsp::Vector::from([0.0,0.0,0.0]),
//model_color
vbsp::Color{r:255,g:255,b:255},
)).chain(
//entities sprinkle instances of the other meshes around
bsp.entities.iter()
.flat_map(|ent|ent.parse())//ignore entity parsing errors
.filter_map(|ent|match ent{
vbsp::Entity::Brush(brush)=>Some(brush),
vbsp::Entity::BrushIllusionary(brush)=>Some(brush),
vbsp::Entity::BrushWall(brush)=>Some(brush),
vbsp::Entity::BrushWallToggle(brush)=>Some(brush),
_=>None,
}).flat_map(|brush|
//The first character of brush.model is '*'
brush.model[1..].parse().map(|mesh_id|//ignore parse int errors
(model::MeshId::new(mesh_id),brush.origin,brush.color)
)
)
).map(|(mesh_id,model_origin,vbsp::Color{r,g,b})|{
model::Model{
mesh:mesh_id,
attributes:TEMP_TOUCH_ME_ATTRIBUTE,
transform:integer::Planar64Affine3::new(
integer::mat3::identity(),
valve_transform(model_origin.into())
),
color:(glam::Vec3::from_array([r as f32,g as f32,b as f32])/255.0).extend(1.0),
}
}).collect();
PartialMap1{
attributes:unique_attributes,
world_meshes,
prop_models,
world_models,
modes:strafesnet_common::gameplay_modes::Modes::new(Vec::new()),
}
}
//partially constructed map types
pub struct PartialMap1{
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
prop_models:Vec<model::Model>,
world_meshes:Vec<model::Mesh>,
world_models:Vec<model::Model>,
modes:strafesnet_common::gameplay_modes::Modes,
}
impl PartialMap1{
pub fn add_prop_meshes<AcquireRenderConfigId>(
self,
prop_meshes:impl IntoIterator<Item=(model::MeshId,crate::data::ModelData)>,
mut acquire_render_config_id:AcquireRenderConfigId,
)->PartialMap2
where
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
{
PartialMap2{
attributes:self.attributes,
prop_meshes:prop_meshes.into_iter().filter_map(|(mesh_id,model_data)|
//this will generate new render ids and texture ids
match convert_mesh(model_data,&mut acquire_render_config_id){
Ok(mesh)=>Some((mesh_id,mesh)),
Err(e)=>{
println!("error converting mesh: {e}");
None
}
}
).collect(),
prop_models:self.prop_models,
world_meshes:self.world_meshes,
world_models:self.world_models,
modes:self.modes,
}
}
}
pub struct PartialMap2{
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
prop_meshes:Vec<(model::MeshId,model::Mesh)>,
prop_models:Vec<model::Model>,
world_meshes:Vec<model::Mesh>,
world_models:Vec<model::Model>,
modes:strafesnet_common::gameplay_modes::Modes,
}
impl PartialMap2{
pub fn add_render_configs_and_textures(
mut self,
render_configs:impl IntoIterator<Item=(model::RenderConfigId,model::RenderConfig)>,
textures:impl IntoIterator<Item=(model::TextureId,Vec<u8>)>,
)->map::CompleteMap{
//merge mesh and model lists, flatten and remap all ids
let mesh_id_offset=self.world_meshes.len();
println!("prop_meshes.len()={}",self.prop_meshes.len());
let (mut prop_meshes,prop_mesh_id_map):(Vec<model::Mesh>,std::collections::HashMap<model::MeshId,model::MeshId>)
=self.prop_meshes.into_iter().enumerate().map(|(new_mesh_id,(old_mesh_id,mesh))|{
(mesh,(old_mesh_id,model::MeshId::new((mesh_id_offset+new_mesh_id) as u32)))
}).unzip();
self.world_meshes.append(&mut prop_meshes);
//there is no modes or runtime behaviour with references to the model ids currently
//so just relentlessly cull them if the mesh is missing
self.world_models.extend(self.prop_models.into_iter().filter_map(|mut model|
prop_mesh_id_map.get(&model.mesh).map(|&new_mesh_id|{
model.mesh=new_mesh_id;
model
})
));
//let mut models=Vec::new();
let (textures,texture_id_map):(Vec<Vec<u8>>,std::collections::HashMap<model::TextureId,model::TextureId>)
=textures.into_iter()
//.filter_map(f) cull unused textures
.enumerate().map(|(new_texture_id,(old_texture_id,texture))|{
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
}).unzip();
let render_configs=render_configs.into_iter().map(|(render_config_id,mut render_config)|{
//this may generate duplicate no-texture render configs but idc
render_config.texture=render_config.texture.and_then(|texture_id|
texture_id_map.get(&texture_id).copied()
);
render_config
}).collect();
map::CompleteMap{
modes:self.modes,
attributes:self.attributes,
meshes:self.world_meshes,
models:self.world_models,
textures,
render_configs,
}
}
}
fn convert_mesh<AcquireRenderConfigId>(
model_data:crate::data::ModelData,
acquire_render_config_id:&mut AcquireRenderConfigId,
)->Result<model::Mesh,vmdl::ModelError>
where
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
{
let model=model_data.read_model()?;
let texture_paths=model.texture_directories();
if texture_paths.len()!=1{
println!("WARNING: multiple texture paths");
}
let skin=model.skin_tables().nth(0).unwrap();
let mut spam_pos=Vec::with_capacity(model.vertices().len());
let mut spam_normal=Vec::with_capacity(model.vertices().len());
let mut spam_tex=Vec::with_capacity(model.vertices().len());
let mut spam_vertices=Vec::with_capacity(model.vertices().len());
for (i,vertex) in model.vertices().iter().enumerate(){
spam_pos.push(valve_transform(vertex.position.into()));
spam_normal.push(valve_transform(vertex.normal.into()));
spam_tex.push(glam::Vec2::from_array(vertex.texture_coordinates));
spam_vertices.push(model::IndexedVertex{
pos:model::PositionId::new(i as u32),
tex:model::TextureCoordinateId::new(i as u32),
normal:model::NormalId::new(i as u32),
color:model::ColorId::new(0),
});
}
let mut graphics_groups=Vec::new();
let mut physics_groups=Vec::new();
let polygon_groups=model.meshes().enumerate().map(|(polygon_group_id,mesh)|{
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
let render_id=if let (Some(texture_path),Some(texture_name))=(texture_paths.get(0),skin.texture(mesh.material_index())){
let mut path=std::path::PathBuf::from(texture_path.as_str());
path.push(texture_name);
acquire_render_config_id(path.as_os_str().to_str())
}else{
acquire_render_config_id(None)
};
graphics_groups.push(model::IndexedGraphicsGroup{
render:render_id,
groups:vec![polygon_group_id],
});
physics_groups.push(model::IndexedPhysicsGroup{
groups:vec![polygon_group_id],
});
model::PolygonGroup::PolygonList(model::PolygonList::new(
//looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function
mesh.vertex_strip_indices().flat_map(|mut strip|
std::iter::from_fn(move||{
match (strip.next(),strip.next(),strip.next()){
(Some(v1),Some(v2),Some(v3))=>Some([v1,v2,v3].map(|vertex_id|model::VertexId::new(vertex_id as u32)).to_vec()),
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
_=>None,
}
})
).collect()
))
}).collect();
Ok(model::Mesh{
unique_pos:spam_pos,
unique_normal:spam_normal,
unique_tex:spam_tex,
unique_color:vec![glam::Vec4::ONE],
unique_vertices:spam_vertices,
polygon_groups,
graphics_groups,
physics_groups,
})
}

View File

@ -1,60 +0,0 @@
pub struct Bsp(vbsp::Bsp);
impl Bsp{
pub const fn new(value:vbsp::Bsp)->Self{
Self(value)
}
}
impl AsRef<vbsp::Bsp> for Bsp{
fn as_ref(&self)->&vbsp::Bsp{
&self.0
}
}
pub struct MdlData(Vec<u8>);
impl MdlData{
pub const fn new(value:Vec<u8>)->Self{
Self(value)
}
}
impl AsRef<[u8]> for MdlData{
fn as_ref(&self)->&[u8]{
self.0.as_ref()
}
}
pub struct VtxData(Vec<u8>);
impl VtxData{
pub const fn new(value:Vec<u8>)->Self{
Self(value)
}
}
impl AsRef<[u8]> for VtxData{
fn as_ref(&self)->&[u8]{
self.0.as_ref()
}
}
pub struct VvdData(Vec<u8>);
impl VvdData{
pub const fn new(value:Vec<u8>)->Self{
Self(value)
}
}
impl AsRef<[u8]> for VvdData{
fn as_ref(&self)->&[u8]{
self.0.as_ref()
}
}
pub struct ModelData{
pub mdl:MdlData,
pub vtx:VtxData,
pub vvd:VvdData,
}
impl ModelData{
pub fn read_model(&self)->Result<vmdl::Model,vmdl::ModelError>{
Ok(vmdl::Model::from_parts(
vmdl::mdl::Mdl::read(self.mdl.as_ref())?,
vmdl::vtx::Vtx::read(self.vtx.as_ref())?,
vmdl::vvd::Vvd::read(self.vvd.as_ref())?,
))
}
}

View File

@ -1,37 +0,0 @@
mod bsp;
pub mod data;
pub use data::Bsp;
#[derive(Debug)]
pub enum ReadError{
Bsp(vbsp::BspError),
Io(std::io::Error),
}
impl std::fmt::Display for ReadError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ReadError{}
pub fn read<R:std::io::Read>(mut input:R)->Result<Bsp,ReadError>{
let mut s=Vec::new();
//TODO: mmap
input.read_to_end(&mut s).map_err(ReadError::Io)?;
vbsp::Bsp::read(s.as_slice()).map(Bsp::new).map_err(ReadError::Bsp)
}
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
bsp:&Bsp,
acquire_render_config_id:AcquireRenderConfigId,
acquire_mesh_id:AcquireMeshId
)->bsp::PartialMap1
where
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
{
bsp::convert_bsp(bsp.as_ref(),acquire_render_config_id,acquire_mesh_id)
}

View File

@ -1 +0,0 @@
/target

View File

@ -1,19 +0,0 @@
[package]
name = "strafesnet_common"
version = "0.5.2"
edition = "2021"
repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0"
description = "Common types and helpers for Strafe Client associated projects."
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
arrayvec = "0.7.4"
bitflags = "2.6.0"
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] }
linear_ops = { path = "../linear_ops", registry = "strafesnet", features = ["deferred-division","named-fields"] }
ratio_ops = { path = "../ratio_ops", registry = "strafesnet" }
glam = "0.29.0"
id = { version = "0.1.0", registry = "strafesnet" }

View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -1,23 +0,0 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -1,19 +0,0 @@
StrafesNET Common Library
=========================
## Common types used in the StrafesNET ecosystem
#### License
<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>
<br>
<sub>
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
</sub>

View File

@ -1,56 +0,0 @@
use crate::integer::{vec3,Planar64Vec3};
#[derive(Clone)]
pub struct Aabb{
min:Planar64Vec3,
max:Planar64Vec3,
}
impl Default for Aabb{
fn default()->Self{
Self{min:vec3::MAX,max:vec3::MIN}
}
}
impl Aabb{
pub const fn new(min:Planar64Vec3,max:Planar64Vec3)->Self{
Self{min,max}
}
pub const fn max(&self)->Planar64Vec3{
self.max
}
pub const fn min(&self)->Planar64Vec3{
self.min
}
pub fn grow(&mut self,point:Planar64Vec3){
self.min=self.min.min(point);
self.max=self.max.max(point);
}
pub fn join(&mut self,aabb:&Aabb){
self.min=self.min.min(aabb.min);
self.max=self.max.max(aabb.max);
}
pub fn inflate(&mut self,hs:Planar64Vec3){
self.min-=hs;
self.max+=hs;
}
pub fn intersects(&self,aabb:&Aabb)->bool{
let bvec=self.min.lt(aabb.max)&aabb.min.lt(self.max);
bvec.all()
}
pub fn size(&self)->Planar64Vec3{
self.max-self.min
}
pub fn center(&self)->Planar64Vec3{
self.min+(self.max-self.min)>>1
}
//probably use floats for area & volume because we don't care about precision
// pub fn area_weight(&self)->f32{
// let d=self.max-self.min;
// d.x*d.y+d.y*d.z+d.z*d.x
// }
// pub fn volume(&self)->f32{
// let d=self.max-self.min;
// d.x*d.y*d.z
// }
}

View File

@ -1,194 +0,0 @@
use crate::aabb::Aabb;
//da algaritum
//lista boxens
//sort by {minx,maxx,miny,maxy,minz,maxz} (6 lists)
//find the sets that minimizes the sum of surface areas
//splitting is done when the minimum split sum of surface areas is larger than the node's own surface area
//start with bisection into octrees because a bad bvh is still 1000x better than no bvh
//sort the centerpoints on each axis (3 lists)
//bv is put into octant based on whether it is upper or lower in each list
pub enum RecursiveContent<R,T>{
Branch(Vec<R>),
Leaf(T),
}
impl<R,T> Default for RecursiveContent<R,T>{
fn default()->Self{
Self::Branch(Vec::new())
}
}
pub struct BvhNode<T>{
content:RecursiveContent<BvhNode<T>,T>,
aabb:Aabb,
}
impl<T> Default for BvhNode<T>{
fn default()->Self{
Self{
content:Default::default(),
aabb:Aabb::default(),
}
}
}
pub struct BvhWeightNode<W,T>{
content:RecursiveContent<BvhWeightNode<W,T>,T>,
weight:W,
aabb:Aabb,
}
impl<T> BvhNode<T>{
pub fn the_tester<F:FnMut(&T)>(&self,aabb:&Aabb,f:&mut F){
match &self.content{
RecursiveContent::Leaf(model)=>f(model),
RecursiveContent::Branch(children)=>for child in children{
//this test could be moved outside the match statement
//but that would test the root node aabb
//you're probably not going to spend a lot of time outside the map,
//so the test is extra work for nothing
if aabb.intersects(&child.aabb){
child.the_tester(aabb,f);
}
},
}
}
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
match self.content{
RecursiveContent::Leaf(model)=>f(model),
RecursiveContent::Branch(children)=>for child in children{
child.into_visitor(f)
},
}
}
pub fn weigh_contents<W:Copy+std::iter::Sum<W>,F:Fn(&T)->W>(self,f:&F)->BvhWeightNode<W,T>{
match self.content{
RecursiveContent::Leaf(model)=>BvhWeightNode{
weight:f(&model),
content:RecursiveContent::Leaf(model),
aabb:self.aabb,
},
RecursiveContent::Branch(children)=>{
let branch:Vec<BvhWeightNode<W,T>>=children.into_iter().map(|child|
child.weigh_contents(f)
).collect();
BvhWeightNode{
weight:branch.iter().map(|node|node.weight).sum(),
content:RecursiveContent::Branch(branch),
aabb:self.aabb,
}
},
}
}
}
impl <W,T> BvhWeightNode<W,T>{
pub const fn weight(&self)->&W{
&self.weight
}
pub const fn aabb(&self)->&Aabb{
&self.aabb
}
pub fn into_content(self)->RecursiveContent<BvhWeightNode<W,T>,T>{
self.content
}
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
match self.content{
RecursiveContent::Leaf(model)=>f(model),
RecursiveContent::Branch(children)=>for child in children{
child.into_visitor(f)
},
}
}
}
pub fn generate_bvh<T>(boxen:Vec<(T,Aabb)>)->BvhNode<T>{
generate_bvh_node(boxen,false)
}
fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
let n=boxen.len();
if force||n<20{
let mut aabb=Aabb::default();
let nodes=boxen.into_iter().map(|b|{
aabb.join(&b.1);
BvhNode{
content:RecursiveContent::Leaf(b.0),
aabb:b.1,
}
}).collect();
BvhNode{
content:RecursiveContent::Branch(nodes),
aabb,
}
}else{
let mut sort_x=Vec::with_capacity(n);
let mut sort_y=Vec::with_capacity(n);
let mut sort_z=Vec::with_capacity(n);
for (i,(_,aabb)) in boxen.iter().enumerate(){
let center=aabb.center();
sort_x.push((i,center.x));
sort_y.push((i,center.y));
sort_z.push((i,center.z));
}
sort_x.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
sort_y.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
sort_z.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
let h=n/2;
let median_x=sort_x[h].1;
let median_y=sort_y[h].1;
let median_z=sort_z[h].1;
//locate a run of values equal to the median
//partition point gives the first index for which the predicate evaluates to false
let first_index_eq_median_x=sort_x.partition_point(|&(_,x)|x<median_x);
let first_index_eq_median_y=sort_y.partition_point(|&(_,y)|y<median_y);
let first_index_eq_median_z=sort_z.partition_point(|&(_,z)|z<median_z);
let first_index_gt_median_x=sort_x.partition_point(|&(_,x)|x<=median_x);
let first_index_gt_median_y=sort_y.partition_point(|&(_,y)|y<=median_y);
let first_index_gt_median_z=sort_z.partition_point(|&(_,z)|z<=median_z);
//pick which side median value copies go into such that both sides are as balanced as possible based on distance from n/2
let partition_point_x=if n.abs_diff(2*first_index_eq_median_x)<n.abs_diff(2*first_index_gt_median_x){first_index_eq_median_x}else{first_index_gt_median_x};
let partition_point_y=if n.abs_diff(2*first_index_eq_median_y)<n.abs_diff(2*first_index_gt_median_y){first_index_eq_median_y}else{first_index_gt_median_y};
let partition_point_z=if n.abs_diff(2*first_index_eq_median_z)<n.abs_diff(2*first_index_gt_median_z){first_index_eq_median_z}else{first_index_gt_median_z};
//this ids which octant the boxen is put in
let mut octant=vec![0;n];
for &(i,_) in &sort_x[partition_point_x..]{
octant[i]+=1<<0;
}
for &(i,_) in &sort_y[partition_point_y..]{
octant[i]+=1<<1;
}
for &(i,_) in &sort_z[partition_point_z..]{
octant[i]+=1<<2;
}
//generate lists for unique octant values
let mut list_list=Vec::with_capacity(8);
let mut octant_list=Vec::with_capacity(8);
for (i,(data,aabb)) in boxen.into_iter().enumerate(){
let octant_id=octant[i];
let list_id=if let Some(list_id)=octant_list.iter().position(|&id|id==octant_id){
list_id
}else{
let list_id=list_list.len();
octant_list.push(octant_id);
list_list.push(Vec::new());
list_id
};
list_list[list_id].push((data,aabb));
}
let mut aabb=Aabb::default();
if list_list.len()==1{
generate_bvh_node(list_list.remove(0),true)
}else{
BvhNode{
content:RecursiveContent::Branch(
list_list.into_iter().map(|b|{
let node=generate_bvh_node(b,false);
aabb.join(&node.aabb);
node
}).collect()
),
aabb,
}
}
}
}

View File

@ -1,25 +0,0 @@
bitflags::bitflags!{
#[derive(Clone,Copy,Debug,Default)]
pub struct Controls:u32{
const MoveForward=1<<0;
const MoveLeft=1<<1;
const MoveBackward=1<<2;
const MoveRight=1<<3;
const MoveUp=1<<4;
const MoveDown=1<<5;
const LookUp=1<<6;
const LookLeft=1<<7;
const LookDown=1<<8;
const LookRight=1<<9;
const Jump=1<<10;
const Crouch=1<<11;
const Sprint=1<<12;
const Zoom=1<<13;
const Use=1<<14;//Interact with object
const PrimaryAction=1<<15;//LBM/Shoot/Melee
const SecondaryAction=1<<16;//RMB/ADS/Block
const WASD=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).bits();
const WASDQE=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown).bits();
}
}

View File

@ -1,174 +0,0 @@
use crate::model;
use crate::integer::{AbsoluteTime,Planar64,Planar64Vec3};
//you have this effect while in contact
#[derive(Clone,Hash,Eq,PartialEq)]
pub struct ContactingLadder{
pub sticky:bool
}
#[derive(Clone,Hash,Eq,PartialEq)]
pub enum ContactingBehaviour{
Surf,
Ladder(ContactingLadder),
NoJump,
Cling,//usable as a zipline, or other weird and wonderful things
Elastic(u32),//[1/2^32,1] 0=None (elasticity+1)/2^32
}
//you have this effect while intersecting
#[derive(Clone,Hash,Eq,PartialEq)]
pub struct IntersectingWater{
pub viscosity:Planar64,
pub density:Planar64,
pub velocity:Planar64Vec3,
}
//All models can be given these attributes
#[derive(Clone,Hash,Eq,PartialEq)]
pub struct Accelerator{
pub acceleration:Planar64Vec3
}
#[derive(Clone,Hash,Eq,PartialEq)]
pub enum Booster{
//Affine(crate::integer::Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more
Velocity(Planar64Vec3),//straight up boost velocity adds to your current velocity
Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction
AirTime(AbsoluteTime),//increase airtime, invariant across mass and gravity changes
Height(Planar64),//increase height, invariant across mass and gravity changes
}
impl Booster{
pub fn boost(&self,velocity:Planar64Vec3)->Planar64Vec3{
match self{
&Booster::Velocity(boost_velocity)=>velocity+boost_velocity,
&Booster::Energy{..}=>{
todo!()
//let d=direction.dot(velocity);
//TODO: think about negative
//velocity+direction.with_length((d*d+energy).sqrt()-d)
},
Booster::AirTime(_)=>todo!(),
Booster::Height(_)=>todo!(),
}
}
}
#[derive(Clone,Hash,Eq,PartialEq)]
pub enum TrajectoryChoice{
HighArcLongDuration,//underhand lob at target: less horizontal speed and more air time
LowArcShortDuration,//overhand throw at target: more horizontal speed and less air time
}
#[derive(Clone,Hash,Eq,PartialEq)]
pub enum SetTrajectory{
//Speed-type SetTrajectory
AirTime(AbsoluteTime),//air time (relative to gravity direction) is invariant across mass and gravity changes
Height(Planar64),//boost height (relative to gravity direction) is invariant across mass and gravity changes
DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions
//Velocity-type SetTrajectory
TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time
target_point:Planar64Vec3,
time:AbsoluteTime,//short time = fast and direct, long time = launch high in the air, negative time = wrong way
},
TargetPointSpeed{//launch at a fixed speed and land at a target point
target_point:Planar64Vec3,
speed:Planar64,//if speed is too low this will fail to reach the target. The closest-passing trajectory will be chosen instead
trajectory_choice:TrajectoryChoice,
},
Velocity(Planar64Vec3),//SetVelocity
}
impl SetTrajectory{
pub const fn is_absolute(&self)->bool{
match self{
SetTrajectory::AirTime(_)
|SetTrajectory::Height(_)
|SetTrajectory::DotVelocity{direction:_,dot:_}=>false,
SetTrajectory::TargetPointTime{target_point:_,time:_}
|SetTrajectory::TargetPointSpeed{target_point:_,speed:_,trajectory_choice:_}
|SetTrajectory::Velocity(_)=>true,
}
}
}
// enum TrapCondition{
// FasterThan(Planar64),
// SlowerThan(Planar64),
// InRange(Planar64,Planar64),
// OutsideRange(Planar64,Planar64),
// }
#[derive(Clone,Hash,Eq,PartialEq)]
pub struct Wormhole{
//destination does not need to be another wormhole
//this defines a one way portal to a destination model transform
//two of these can create a two way wormhole
pub destination_model:model::ModelId,
//(position,angles)*=origin.transform.inverse()*destination.transform
}
//attributes listed in order of handling
#[derive(Default,Clone,Hash,Eq,PartialEq)]
pub struct GeneralAttributes{
pub booster:Option<Booster>,
pub trajectory:Option<SetTrajectory>,
pub wormhole:Option<Wormhole>,
pub accelerator:Option<Accelerator>,
}
impl GeneralAttributes{
pub const fn any(&self)->bool{
self.booster.is_some()
||self.trajectory.is_some()
||self.wormhole.is_some()
||self.accelerator.is_some()
}
pub fn is_wrcp(&self)->bool{
self.trajectory.as_ref().map_or(false,|t|t.is_absolute())
/*
&&match &self.teleport_behaviour{
Some(TeleportBehaviour::StageElement(
StageElement{
mode_id,
stage_id:_,
force:true,
behaviour:StageElementBehaviour::Trigger|StageElementBehaviour::Teleport
}
))=>current_mode_id==*mode_id,
_=>false,
}
*/
}
}
#[derive(Default,Clone,Hash,Eq,PartialEq)]
pub struct ContactingAttributes{
//friction?
pub contact_behaviour:Option<ContactingBehaviour>,
}
impl ContactingAttributes{
pub const fn any(&self)->bool{
self.contact_behaviour.is_some()
}
}
#[derive(Default,Clone,Hash,Eq,PartialEq)]
pub struct IntersectingAttributes{
pub water:Option<IntersectingWater>,
}
impl IntersectingAttributes{
pub const fn any(&self)->bool{
self.water.is_some()
}
}
#[derive(Clone,Copy,id::Id,Hash,Eq,PartialEq)]
pub struct CollisionAttributesId(u32);
#[derive(Clone,Default,Hash,Eq,PartialEq)]
pub struct ContactAttributes{
pub contacting:ContactingAttributes,
pub general:GeneralAttributes,
}
#[derive(Clone,Default,Hash,Eq,PartialEq)]
pub struct IntersectAttributes{
pub intersecting:IntersectingAttributes,
pub general:GeneralAttributes,
}
#[derive(Clone,Hash,Eq,PartialEq)]
pub enum CollisionAttributes{
Decoration,//visual only
Contact(ContactAttributes),//track whether you are contacting the object
Intersect(IntersectAttributes),//track whether you are intersecting the object
}
impl CollisionAttributes{
pub fn contact_default()->Self{
Self::Contact(ContactAttributes::default())
}
}

View File

@ -1,332 +0,0 @@
use std::collections::{HashSet,HashMap};
use crate::model::ModelId;
use crate::gameplay_style;
use crate::updatable::Updatable;
#[derive(Clone)]
pub struct StageElement{
stage_id:StageId,//which stage spawn to send to
force:bool,//allow setting to lower spawn id i.e. 7->3
behaviour:StageElementBehaviour,
jump_limit:Option<u8>,
}
impl StageElement{
#[inline]
pub const fn new(stage_id:StageId,force:bool,behaviour:StageElementBehaviour,jump_limit:Option<u8>)->Self{
Self{
stage_id,
force,
behaviour,
jump_limit,
}
}
#[inline]
pub const fn stage_id(&self)->StageId{
self.stage_id
}
#[inline]
pub const fn force(&self)->bool{
self.force
}
#[inline]
pub const fn behaviour(&self)->StageElementBehaviour{
self.behaviour
}
#[inline]
pub const fn jump_limit(&self)->Option<u8>{
self.jump_limit
}
}
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
pub enum StageElementBehaviour{
SpawnAt,//must be standing on top to get effect. except cancollide false
Trigger,
Teleport,
Platform,
//Check(point) acts like a trigger if you haven't hit all the checkpoints on previous stages yet.
//Note that all stage elements act like this, this is just the isolated behaviour.
Check,
Checkpoint,//this is a combined behaviour for Ordered & Unordered in case a model is used multiple times or for both.
}
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)]
pub struct CheckpointId(u32);
impl CheckpointId{
pub const FIRST:Self=Self(0);
}
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)]
pub struct StageId(u32);
impl StageId{
pub const FIRST:Self=Self(0);
}
#[derive(Clone)]
pub struct Stage{
spawn:ModelId,
//open world support lol
ordered_checkpoints_count:u32,
unordered_checkpoints_count:u32,
//currently loaded checkpoint models
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
unordered_checkpoints:HashSet<ModelId>,
}
impl Stage{
pub fn new(
spawn:ModelId,
ordered_checkpoints_count:u32,
unordered_checkpoints_count:u32,
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
unordered_checkpoints:HashSet<ModelId>,
)->Self{
Self{
spawn,
ordered_checkpoints_count,
unordered_checkpoints_count,
ordered_checkpoints,
unordered_checkpoints,
}
}
pub fn empty(spawn:ModelId)->Self{
Self{
spawn,
ordered_checkpoints_count:0,
unordered_checkpoints_count:0,
ordered_checkpoints:HashMap::new(),
unordered_checkpoints:HashSet::new(),
}
}
#[inline]
pub const fn spawn(&self)->ModelId{
self.spawn
}
#[inline]
pub const fn ordered_checkpoints_count(&self)->u32{
self.ordered_checkpoints_count
}
#[inline]
pub const fn unordered_checkpoints_count(&self)->u32{
self.unordered_checkpoints_count
}
pub fn into_inner(self)->(HashMap<CheckpointId,ModelId>,HashSet<ModelId>){
(self.ordered_checkpoints,self.unordered_checkpoints)
}
/// Returns true if the stage has no checkpoints.
#[inline]
pub const fn is_empty(&self)->bool{
self.is_complete(0,0)
}
#[inline]
pub const fn is_complete(&self,ordered_checkpoints_count:u32,unordered_checkpoints_count:u32)->bool{
self.ordered_checkpoints_count==ordered_checkpoints_count&&self.unordered_checkpoints_count==unordered_checkpoints_count
}
#[inline]
pub fn is_next_ordered_checkpoint(&self,next_ordered_checkpoint_id:CheckpointId,model_id:ModelId)->bool{
self.ordered_checkpoints.get(&next_ordered_checkpoint_id).is_some_and(|&next_checkpoint|model_id==next_checkpoint)
}
#[inline]
pub fn is_unordered_checkpoint(&self,model_id:ModelId)->bool{
self.unordered_checkpoints.contains(&model_id)
}
}
#[derive(Default)]
pub struct StageUpdate{
//other behaviour models of this stage can have
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
unordered_checkpoints:HashSet<ModelId>,
}
impl Updatable<StageUpdate> for Stage{
fn update(&mut self,update:StageUpdate){
self.ordered_checkpoints.extend(update.ordered_checkpoints);
self.unordered_checkpoints.extend(update.unordered_checkpoints);
}
}
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
pub enum Zone{
Start,
Finish,
Anticheat,
}
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)]
pub struct ModeId(u32);
impl ModeId{
pub const MAIN:Self=Self(0);
pub const BONUS:Self=Self(1);
}
#[derive(Clone)]
pub struct Mode{
style:gameplay_style::StyleModifiers,
start:ModelId,//when you press reset you go here
zones:HashMap<ModelId,Zone>,
stages:Vec<Stage>,//when you load the map you go to stages[0].spawn
//mutually exlusive stage element behaviour
elements:HashMap<ModelId,StageElement>,
}
impl Mode{
pub fn new(
style:gameplay_style::StyleModifiers,
start:ModelId,
zones:HashMap<ModelId,Zone>,
stages:Vec<Stage>,
elements:HashMap<ModelId,StageElement>,
)->Self{
Self{
style,
start,
zones,
stages,
elements,
}
}
pub fn empty(style:gameplay_style::StyleModifiers,start:ModelId)->Self{
Self{
style,
start,
zones:HashMap::new(),
stages:Vec::new(),
elements:HashMap::new(),
}
}
pub fn into_inner(self)->(
gameplay_style::StyleModifiers,
ModelId,
HashMap<ModelId,Zone>,
Vec<Stage>,
HashMap<ModelId,StageElement>,
){
(
self.style,
self.start,
self.zones,
self.stages,
self.elements,
)
}
pub const fn get_start(&self)->ModelId{
self.start
}
pub const fn get_style(&self)->&gameplay_style::StyleModifiers{
&self.style
}
pub fn push_stage(&mut self,stage:Stage){
self.stages.push(stage)
}
pub fn get_stage_mut(&mut self,stage:StageId)->Option<&mut Stage>{
self.stages.get_mut(stage.0 as usize)
}
pub fn get_spawn_model_id(&self,stage:StageId)->Option<ModelId>{
self.stages.get(stage.0 as usize).map(|s|s.spawn)
}
pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{
self.zones.get(&model_id)
}
pub fn get_stage(&self,stage_id:StageId)->Option<&Stage>{
self.stages.get(stage_id.0 as usize)
}
pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{
self.elements.get(&model_id)
}
//TODO: put this in the SNF
pub fn denormalize_data(&mut self){
//expand and index normalized data
self.zones.insert(self.start,Zone::Start);
for (stage_id,stage) in self.stages.iter().enumerate(){
self.elements.insert(stage.spawn,StageElement{
stage_id:StageId(stage_id as u32),
force:false,
behaviour:StageElementBehaviour::SpawnAt,
jump_limit:None,
});
for (_,&model) in &stage.ordered_checkpoints{
self.elements.insert(model,StageElement{
stage_id:StageId(stage_id as u32),
force:false,
behaviour:StageElementBehaviour::Checkpoint,
jump_limit:None,
});
}
for &model in &stage.unordered_checkpoints{
self.elements.insert(model,StageElement{
stage_id:StageId(stage_id as u32),
force:false,
behaviour:StageElementBehaviour::Checkpoint,
jump_limit:None,
});
}
}
}
}
//this would be nice as a macro
#[derive(Default)]
pub struct ModeUpdate{
zones:HashMap<ModelId,Zone>,
stages:HashMap<StageId,StageUpdate>,
//mutually exlusive stage element behaviour
elements:HashMap<ModelId,StageElement>,
}
impl Updatable<ModeUpdate> for Mode{
fn update(&mut self,update:ModeUpdate){
self.zones.extend(update.zones);
for (stage,stage_update) in update.stages{
if let Some(stage)=self.stages.get_mut(stage.0 as usize){
stage.update(stage_update);
}
}
self.elements.extend(update.elements);
}
}
impl ModeUpdate{
pub fn zone(model_id:ModelId,zone:Zone)->Self{
let mut mu=Self::default();
mu.zones.insert(model_id,zone);
mu
}
pub fn stage(stage_id:StageId,stage_update:StageUpdate)->Self{
let mut mu=Self::default();
mu.stages.insert(stage_id,stage_update);
mu
}
pub fn element(model_id:ModelId,element:StageElement)->Self{
let mut mu=Self::default();
mu.elements.insert(model_id,element);
mu
}
pub fn map_stage_element_ids<F:Fn(StageId)->StageId>(&mut self,f:F){
for (_,stage_element) in self.elements.iter_mut(){
stage_element.stage_id=f(stage_element.stage_id);
}
}
}
#[derive(Default,Clone)]
pub struct Modes{
pub modes:Vec<Mode>,
}
impl Modes{
pub const fn new(modes:Vec<Mode>)->Self{
Self{
modes,
}
}
pub fn into_inner(self)->Vec<Mode>{
self.modes
}
pub fn push_mode(&mut self,mode:Mode){
self.modes.push(mode)
}
pub fn get_mode(&self,mode:ModeId)->Option<&Mode>{
self.modes.get(mode.0 as usize)
}
}
pub struct ModesUpdate{
modes:HashMap<ModeId,ModeUpdate>,
}
impl Updatable<ModesUpdate> for Modes{
fn update(&mut self,update:ModesUpdate){
for (mode,mode_update) in update.modes{
if let Some(mode)=self.modes.get_mut(mode.0 as usize){
mode.update(mode_update);
}
}
}
}

View File

@ -1,612 +0,0 @@
const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
use crate::integer::{int,vec3::int as int3,AbsoluteTime,Ratio64,Planar64,Planar64Vec3};
use crate::controls_bitflag::Controls;
use crate::physics::Time as PhysicsTime;
#[derive(Clone,Debug)]
pub struct StyleModifiers{
//controls which are allowed to pass into gameplay (usually all)
pub controls_mask:Controls,
//controls which are masked from control state (e.g. !jump in scroll style)
pub controls_mask_state:Controls,
//strafing
pub strafe:Option<StrafeSettings>,
//player gets a controllable rocket force
pub rocket:Option<PropulsionSettings>,
//flying
//pub move_type:MoveType::Fly(FlySettings)
//MoveType::Physics(PhysicsSettings) -> PhysicsSettings (strafe,rocket,jump,walk,ladder,swim,gravity)
//jumping is allowed
pub jump:Option<JumpSettings>,
//standing & walking is allowed
pub walk:Option<WalkSettings>,
//laddering is allowed
pub ladder:Option<LadderSettings>,
//water propulsion
pub swim:Option<PropulsionSettings>,
//maximum slope before sloped surfaces become frictionless
pub gravity:Planar64Vec3,
//hitbox
pub hitbox:Hitbox,
//camera location relative to the center (0,0,0) of the hitbox
pub camera_offset:Planar64Vec3,
//unused
pub mass:Planar64,
}
impl std::default::Default for StyleModifiers{
fn default()->Self{
Self::roblox_bhop()
}
}
#[derive(Clone,Debug)]
pub enum JumpCalculation{
Max,//Roblox: jumped_speed=max(velocity.boost(),velocity.jump())
BoostThenJump,//jumped_speed=velocity.boost().jump()
JumpThenBoost,//jumped_speed=velocity.jump().boost()
}
#[derive(Clone,Debug)]
pub enum JumpImpulse{
Time(AbsoluteTime),//jump time is invariant across mass and gravity changes
Height(Planar64),//jump height is invariant across mass and gravity changes
Linear(Planar64),//jump velocity is invariant across mass and gravity changes
Energy(Planar64),// :)
}
//Jumping acts on dot(walks_state.normal,body.velocity)
//Energy means it adds energy
//Linear means it linearly adds on
impl JumpImpulse{
pub fn jump(
&self,
velocity:Planar64Vec3,
jump_dir:Planar64Vec3,
gravity:&Planar64Vec3,
mass:Planar64,
)->Planar64Vec3{
match self{
&JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().fix_1()),
&JumpImpulse::Height(height)=>{
//height==-v.y*v.y/(2*g.y);
//use energy to determine max height
let gg=gravity.length_squared();
let g=gg.sqrt().fix_1();
let v_g=gravity.dot(velocity);
//do it backwards
let radicand=v_g*v_g+(g*height*2).fix_4();
velocity-(*gravity*(radicand.sqrt().fix_2()+v_g)/gg).divide().fix_1()
},
&JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().fix_1(),
&JumpImpulse::Energy(energy)=>{
//calculate energy
//let e=gravity.dot(velocity);
//add
//you get the idea
todo!()
},
}
}
//TODO: remove this and implement JumpCalculation properly
pub fn get_jump_deltav(&self,gravity:&Planar64Vec3,mass:Planar64)->Planar64{
//gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction
match self{
&JumpImpulse::Time(time)=>(gravity.length().fix_1()*time/2).divide().fix_1(),
&JumpImpulse::Height(height)=>(gravity.length()*height*2).sqrt().fix_1(),
&JumpImpulse::Linear(deltav)=>deltav,
&JumpImpulse::Energy(energy)=>(energy.sqrt()*2/mass.sqrt()).divide().fix_1(),
}
}
}
#[derive(Clone,Debug)]
pub struct JumpSettings{
//information used to calculate jump power
pub impulse:JumpImpulse,
//information used to calculate jump behaviour
pub calculation:JumpCalculation,
//limit the minimum jump power when combined with downwards momentum
//This is true in both roblox and source
pub limit_minimum:bool,
}
impl JumpSettings{
pub fn jumped_velocity(
&self,
style:&StyleModifiers,
jump_dir:Planar64Vec3,
rel_velocity:Planar64Vec3,
booster:Option<&crate::gameplay_attributes::Booster>,
)->Planar64Vec3{
let jump_speed=self.impulse.get_jump_deltav(&style.gravity,style.mass);
match (self.limit_minimum,&self.calculation){
(true,JumpCalculation::Max)=>{
//the roblox calculation
let boost_vel=match booster{
Some(booster)=>booster.boost(rel_velocity),
None=>rel_velocity,
};
let j=boost_vel.dot(jump_dir);
let js=jump_speed.fix_2();
if j<js{
//weak booster: just do a regular jump
boost_vel+jump_dir.with_length(js-j).divide().fix_1()
}else{
//activate booster normally, jump does nothing
boost_vel
}
},
(true,_)=>{
//the source calculation (?)
let boost_vel=match booster{
Some(booster)=>booster.boost(rel_velocity),
None=>rel_velocity,
};
let j=boost_vel.dot(jump_dir);
let js=jump_speed.fix_2();
if j<js{
//speed in direction of jump cannot be lower than amount
boost_vel+jump_dir.with_length(js-j).divide().fix_1()
}else{
//boost and jump add together
boost_vel+jump_dir.with_length(js).divide().fix_1()
}
}
(false,JumpCalculation::Max)=>{
//??? calculation
//max(boost_vel,jump_vel)
let boost_vel=match booster{
Some(booster)=>booster.boost(rel_velocity),
None=>rel_velocity,
};
let boost_dot=boost_vel.dot(jump_dir);
let js=jump_speed.fix_2();
if boost_dot<js{
//weak boost is extended to jump speed
boost_vel+jump_dir.with_length(js-boost_dot).divide().fix_1()
}else{
//activate booster normally, jump does nothing
boost_vel
}
},
//the strafe client calculation
(false,_)=>{
let boost_vel=match booster{
Some(booster)=>booster.boost(rel_velocity),
None=>rel_velocity,
};
boost_vel+jump_dir.with_length(jump_speed).divide().fix_1()
},
}
}
}
#[derive(Clone,Debug)]
pub struct ControlsActivation{
//allowed keys
pub controls_mask:Controls,
//allow strafing only if any of the masked controls are held, eg W|S for shsw
pub controls_intersects:Controls,
//allow strafing only if all of the masked controls are held, eg W for hsw, w-only
pub controls_contains:Controls,
//Function(Box<dyn Fn(u32)->bool>),
}
impl ControlsActivation{
pub const fn mask(&self,controls:Controls)->Controls{
controls.intersection(self.controls_mask)
}
pub const fn activates(&self,controls:Controls)->bool{
(self.controls_intersects.is_empty()||controls.intersects(self.controls_intersects))
&&controls.contains(self.controls_contains)
}
pub const fn full_3d()->Self{
Self{
controls_mask:Controls::WASDQE,
controls_intersects:Controls::WASDQE,
controls_contains:Controls::empty(),
}
}
//classical styles
//Normal
pub const fn full_2d()->Self{
Self{
controls_mask:Controls::WASD,
controls_intersects:Controls::WASD,
controls_contains:Controls::empty(),
}
}
//Sideways
pub const fn sideways()->Self{
Self{
controls_mask:Controls::MoveForward.union(Controls::MoveBackward),
controls_intersects:Controls::MoveForward.union(Controls::MoveBackward),
controls_contains:Controls::empty(),
}
}
//Half-Sideways
pub const fn half_sideways()->Self{
Self{
controls_mask:Controls::MoveForward.union(Controls::MoveLeft).union(Controls::MoveRight),
controls_intersects:Controls::MoveLeft.union(Controls::MoveRight),
controls_contains:Controls::MoveForward,
}
}
//Surf Half-Sideways
pub const fn surf_half_sideways()->Self{
Self{
controls_mask:Controls::MoveForward.union(Controls::MoveBackward).union(Controls::MoveLeft).union(Controls::MoveRight),
controls_intersects:Controls::MoveForward.union(Controls::MoveBackward),
controls_contains:Controls::empty(),
}
}
//W-Only
pub const fn w_only()->Self{
Self{
controls_mask:Controls::MoveForward,
controls_intersects:Controls::empty(),
controls_contains:Controls::MoveForward,
}
}
//A-Only
pub const fn a_only()->Self{
Self{
controls_mask:Controls::MoveLeft,
controls_intersects:Controls::empty(),
controls_contains:Controls::MoveLeft,
}
}
//Backwards
}
#[derive(Clone,Debug)]
pub struct StrafeSettings{
pub enable:ControlsActivation,
pub mv:Planar64,
pub air_accel_limit:Option<Planar64>,
pub tick_rate:Ratio64,
}
impl StrafeSettings{
pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option<Planar64Vec3>{
let d=velocity.dot(control_dir);
let mv=self.mv.fix_2();
match d<mv{
true=>Some(velocity+(control_dir*self.air_accel_limit.map_or(mv-d,|limit|limit.fix_2().min(mv-d))).fix_1()),
false=>None,
}
}
pub fn next_tick(&self,time:PhysicsTime)->PhysicsTime{
PhysicsTime::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1))
}
pub const fn activates(&self,controls:Controls)->bool{
self.enable.activates(controls)
}
pub const fn mask(&self,controls:Controls)->Controls{
self.enable.mask(controls)
}
}
#[derive(Clone,Debug)]
pub struct PropulsionSettings{
pub magnitude:Planar64,
}
impl PropulsionSettings{
pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{
(control_dir*self.magnitude).fix_1()
}
}
#[derive(Clone,Debug)]
pub struct AccelerateSettings{
pub accel:Planar64,
pub topspeed:Planar64,
}
#[derive(Clone,Debug)]
pub struct WalkSettings{
pub accelerate:AccelerateSettings,
pub static_friction:Planar64,
pub kinetic_friction:Planar64,
//if a surf slope angle does not exist, then everything is slippery and walking is impossible
pub surf_dot:Planar64,//surf_dot<n.dot(up)/n.length()
}
impl WalkSettings{
pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
//TODO: fallible walk accel
let diff_len=target_diff.length().fix_1();
let friction=if diff_len<self.accelerate.topspeed{
self.static_friction
}else{
self.kinetic_friction
};
self.accelerate.accel.min((-gravity.y*friction).fix_1())
}
pub fn get_walk_target_velocity(&self,control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
if control_dir==crate::integer::vec3::ZERO{
return control_dir;
}
let nn=normal.length_squared();
let mm=control_dir.length_squared();
let nnmm=nn*mm;
let d=normal.dot(control_dir);
let dd=d*d;
if dd<nnmm{
let cr=normal.cross(control_dir);
if cr==crate::integer::vec3::ZERO_2{
crate::integer::vec3::ZERO
}else{
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().fix_1()
}
}else{
crate::integer::vec3::ZERO
}
}
pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{
//normal is not guaranteed to be unit length
let ny=normal.dot(up);
let h=normal.length().fix_1();
//remember this is a normal vector
ny.is_positive()&&h*self.surf_dot<ny
}
}
#[derive(Clone,Debug)]
pub struct LadderSettings{
pub accelerate:AccelerateSettings,
//how close to pushing directly into/out of the ladder normal
//does your input need to be to redirect straight up/down the ladder
pub dot:Planar64,
}
impl LadderSettings{
pub const fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
//TODO: fallible ladder accel
self.accelerate.accel
}
pub fn get_ladder_target_velocity(&self,mut control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
if control_dir==crate::integer::vec3::ZERO{
return control_dir;
}
let nn=normal.length_squared();
let mm=control_dir.length_squared();
let nnmm=nn*mm;
let d=normal.dot(control_dir);
let mut dd=d*d;
if (self.dot*self.dot*nnmm).fix_4()<dd{
if d.is_negative(){
control_dir=Planar64Vec3::new([Planar64::ZERO,mm.fix_1(),Planar64::ZERO]);
}else{
control_dir=Planar64Vec3::new([Planar64::ZERO,-mm.fix_1(),Planar64::ZERO]);
}
dd=(normal.y*normal.y).fix_4();
}
//n=d if you are standing on top of a ladder and press E.
//two fixes:
//- ladder movement is not allowed on walkable surfaces
//- fix the underlying issue
if dd<nnmm{
let cr=normal.cross(control_dir);
if cr==crate::integer::vec3::ZERO_2{
crate::integer::vec3::ZERO
}else{
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().fix_1()
}
}else{
crate::integer::vec3::ZERO
}
}
}
#[derive(Clone,Debug)]
pub enum HitboxMesh{
Box,//source
Cylinder,//roblox
//Sphere,//roblox old physics
//Point,
//Line,
//DualCone,
}
#[derive(Clone,Debug)]
pub struct Hitbox{
pub halfsize:Planar64Vec3,
pub mesh:HitboxMesh,
}
impl Hitbox{
pub fn roblox()->Self{
Self{
halfsize:int3(2,5,2)>>1,
mesh:HitboxMesh::Cylinder,
}
}
pub fn source()->Self{
Self{
halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).fix_1(),
mesh:HitboxMesh::Box,
}
}
}
impl StyleModifiers{
pub const RIGHT_DIR:Planar64Vec3=crate::integer::vec3::X;
pub const UP_DIR:Planar64Vec3=crate::integer::vec3::Y;
pub const FORWARD_DIR:Planar64Vec3=crate::integer::vec3::NEG_Z;
pub fn neo()->Self{
Self{
controls_mask:Controls::all(),
controls_mask_state:Controls::all(),
strafe:Some(StrafeSettings{
enable:ControlsActivation::full_2d(),
air_accel_limit:None,
mv:int(3),
tick_rate:Ratio64::new(64,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}),
jump:Some(JumpSettings{
impulse:JumpImpulse::Energy(int(512)),
calculation:JumpCalculation::JumpThenBoost,
limit_minimum:false,
}),
gravity:int3(0,-80,0),
mass:int(1),
rocket:None,
walk:Some(WalkSettings{
accelerate:AccelerateSettings{
topspeed:int(16),
accel:int(80),
},
static_friction:int(2),
kinetic_friction:int(3),//unrealistic: kinetic friction is typically lower than static
surf_dot:int(3)/4,
}),
ladder:Some(LadderSettings{
accelerate:AccelerateSettings{
topspeed:int(16),
accel:int(160),
},
dot:(int(1)/2).sqrt(),
}),
swim:Some(PropulsionSettings{
magnitude:int(12),
}),
hitbox:Hitbox::roblox(),
camera_offset:int3(0,2,0),//4.5-2.5=2
}
}
pub fn roblox_bhop()->Self{
Self{
controls_mask:Controls::all(),
controls_mask_state:Controls::all(),
strafe:Some(StrafeSettings{
enable:ControlsActivation::full_2d(),
air_accel_limit:None,
mv:int(27)/10,
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}),
jump:Some(JumpSettings{
impulse:JumpImpulse::Time(AbsoluteTime::from_micros(715_588)),
calculation:JumpCalculation::Max,
limit_minimum:true,
}),
gravity:int3(0,-100,0),
mass:int(1),
rocket:None,
walk:Some(WalkSettings{
accelerate:AccelerateSettings{
topspeed:int(18),
accel:int(90),
},
static_friction:int(2),
kinetic_friction:int(3),//unrealistic: kinetic friction is typically lower than static
surf_dot:int(3)/4,// normal.y=0.75
}),
ladder:Some(LadderSettings{
accelerate:AccelerateSettings{
topspeed:int(18),
accel:int(180),
},
dot:(int(1)/2).sqrt(),
}),
swim:Some(PropulsionSettings{
magnitude:int(12),
}),
hitbox:Hitbox::roblox(),
camera_offset:int3(0,2,0),//4.5-2.5=2
}
}
pub fn roblox_surf()->Self{
Self{
gravity:int3(0,-50,0),
..Self::roblox_bhop()
}
}
pub fn roblox_rocket()->Self{
Self{
strafe:None,
rocket:Some(PropulsionSettings{
magnitude:int(200),
}),
..Self::roblox_bhop()
}
}
pub fn source_bhop()->Self{
Self{
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown,
controls_mask_state:Controls::all(),
strafe:Some(StrafeSettings{
enable:ControlsActivation::full_2d(),
air_accel_limit:Some(Planar64::raw(150<<28)*100),
mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(),
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}),
jump:Some(JumpSettings{
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
calculation:JumpCalculation::JumpThenBoost,
limit_minimum:true,
}),
gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(),
mass:int(1),
rocket:None,
walk:Some(WalkSettings{
accelerate:AccelerateSettings{
topspeed:int(18),//?
accel:int(90),//?
},
static_friction:int(2),//?
kinetic_friction:int(3),//?
surf_dot:int(3)/4,// normal.y=0.75
}),
ladder:Some(LadderSettings{
accelerate:AccelerateSettings{
topspeed:int(18),//?
accel:int(180),//?
},
dot:(int(1)/2).sqrt(),//?
}),
swim:Some(PropulsionSettings{
magnitude:int(12),//?
}),
hitbox:Hitbox::source(),
camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(),
}
}
pub fn source_surf()->Self{
Self{
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown,
controls_mask_state:Controls::all(),
strafe:Some(StrafeSettings{
enable:ControlsActivation::full_2d(),
air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()),
mv:(int(30)*VALVE_SCALE).fix_1(),
tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}),
jump:Some(JumpSettings{
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
calculation:JumpCalculation::JumpThenBoost,
limit_minimum:true,
}),
gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(),
mass:int(1),
rocket:None,
walk:Some(WalkSettings{
accelerate:AccelerateSettings{
topspeed:int(18),//?
accel:int(90),//?
},
static_friction:int(2),//?
kinetic_friction:int(3),//?
surf_dot:int(3)/4,// normal.y=0.75
}),
ladder:Some(LadderSettings{
accelerate:AccelerateSettings{
topspeed:int(18),//?
accel:int(180),//?
},
dot:(int(1)/2).sqrt(),//?
}),
swim:Some(PropulsionSettings{
magnitude:int(12),//?
}),
hitbox:Hitbox::source(),
camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(),
}
}
}

View File

@ -1,82 +0,0 @@
use crate::integer::Time;
#[derive(Clone,Debug)]
pub struct TimedInstruction<I,T>{
pub time:Time<T>,
pub instruction:I,
}
impl<I,T> TimedInstruction<I,T>{
#[inline]
pub fn set_time<TimeInner>(self,new_time:Time<TimeInner>)->TimedInstruction<I,TimeInner>{
TimedInstruction{
time:new_time,
instruction:self.instruction,
}
}
}
/// Ensure all emitted instructions are processed before consuming external instructions
pub trait InstructionEmitter<I>{
type TimeInner;
fn next_instruction(&self,time_limit:Time<Self::TimeInner>)->Option<TimedInstruction<I,Self::TimeInner>>;
}
/// Apply an atomic state update
pub trait InstructionConsumer<I>{
type TimeInner;
fn process_instruction(&mut self,instruction:TimedInstruction<I,Self::TimeInner>);
}
/// If the object produces its own instructions, allow exhaustively feeding them back in
pub trait InstructionFeedback<I,T>:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>
where
Time<T>:Copy,
{
#[inline]
fn process_exhaustive(&mut self,time_limit:Time<T>){
while let Some(instruction)=self.next_instruction(time_limit){
self.process_instruction(instruction);
}
}
}
impl<I,T,X> InstructionFeedback<I,T> for X
where
Time<T>:Copy,
X:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>,
{}
//PROPER PRIVATE FIELDS!!!
pub struct InstructionCollector<I,T>{
time:Time<T>,
instruction:Option<I>,
}
impl<I,T> InstructionCollector<I,T>
where Time<T>:Copy+PartialOrd,
{
#[inline]
pub const fn new(time:Time<T>)->Self{
Self{
time,
instruction:None
}
}
#[inline]
pub const fn time(&self)->Time<T>{
self.time
}
#[inline]
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
if let Some(ins)=instruction{
if ins.time<self.time{
self.time=ins.time;
self.instruction=Some(ins.instruction);
}
}
}
#[inline]
pub fn take(self)->Option<TimedInstruction<I,T>>{
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
self.instruction.map(|instruction|TimedInstruction{
time:self.time,
instruction
})
}
}

View File

@ -1,680 +0,0 @@
pub use fixed_wide::fixed::{Fixed,Fix};
pub use ratio_ops::ratio::{Ratio,Divide};
//integer units
/// specific example of a "default" time type
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
pub enum TimeInner{}
pub type AbsoluteTime=Time<TimeInner>;
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
pub struct Time<T>(i64,core::marker::PhantomData<T>);
impl<T> Time<T>{
pub const MIN:Self=Self::raw(i64::MIN);
pub const MAX:Self=Self::raw(i64::MAX);
pub const ZERO:Self=Self::raw(0);
pub const EPSILON:Self=Self::raw(1);
pub const ONE_SECOND:Self=Self::raw(1_000_000_000);
pub const ONE_MILLISECOND:Self=Self::raw(1_000_000);
pub const ONE_MICROSECOND:Self=Self::raw(1_000);
pub const ONE_NANOSECOND:Self=Self::raw(1);
#[inline]
pub const fn raw(num:i64)->Self{
Self(num,core::marker::PhantomData)
}
#[inline]
pub const fn get(self)->i64{
self.0
}
#[inline]
pub const fn from_secs(num:i64)->Self{
Self::raw(Self::ONE_SECOND.0*num)
}
#[inline]
pub const fn from_millis(num:i64)->Self{
Self::raw(Self::ONE_MILLISECOND.0*num)
}
#[inline]
pub const fn from_micros(num:i64)->Self{
Self::raw(Self::ONE_MICROSECOND.0*num)
}
#[inline]
pub const fn from_nanos(num:i64)->Self{
Self::raw(Self::ONE_NANOSECOND.0*num)
}
//should I have checked subtraction? force all time variables to be positive?
#[inline]
pub const fn nanos(self)->i64{
self.0
}
#[inline]
pub const fn to_ratio(self)->Ratio<Planar64,Planar64>{
Ratio::new(Planar64::raw(self.0),Planar64::raw(1_000_000_000))
}
#[inline]
pub const fn coerce<U>(self)->Time<U>{
Time::raw(self.0)
}
}
impl<T> From<Planar64> for Time<T>{
#[inline]
fn from(value:Planar64)->Self{
Self::raw((value*Planar64::raw(1_000_000_000)).fix_1().to_raw())
}
}
impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T>
where
Num:core::ops::Mul<Planar64,Output=N1>,
N1:Divide<Den,Output=T1>,
T1:Fix<Planar64>,
{
#[inline]
fn from(value:Ratio<Num,Den>)->Self{
Self::raw((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw())
}
}
impl<T> std::fmt::Display for Time<T>{
#[inline]
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{}s+{:09}ns",self.0/Self::ONE_SECOND.0,self.0%Self::ONE_SECOND.0)
}
}
impl<T> std::default::Default for Time<T>{
fn default()->Self{
Self::raw(0)
}
}
impl<T> std::ops::Neg for Time<T>{
type Output=Self;
#[inline]
fn neg(self)->Self::Output {
Self::raw(-self.0)
}
}
macro_rules! impl_time_additive_operator {
($trait:ty, $method:ident) => {
impl<T> $trait for Time<T>{
type Output=Self;
#[inline]
fn $method(self,rhs:Self)->Self::Output {
Self::raw(self.0.$method(rhs.0))
}
}
};
}
impl_time_additive_operator!(core::ops::Add,add);
impl_time_additive_operator!(core::ops::Sub,sub);
impl_time_additive_operator!(core::ops::Rem,rem);
macro_rules! impl_time_additive_assign_operator {
($trait:ty, $method:ident) => {
impl<T> $trait for Time<T>{
#[inline]
fn $method(&mut self,rhs:Self){
self.0.$method(rhs.0)
}
}
};
}
impl_time_additive_assign_operator!(core::ops::AddAssign,add_assign);
impl_time_additive_assign_operator!(core::ops::SubAssign,sub_assign);
impl_time_additive_assign_operator!(core::ops::RemAssign,rem_assign);
impl<T> std::ops::Mul for Time<T>{
type Output=Ratio<fixed_wide::fixed::Fixed<2,64>,fixed_wide::fixed::Fixed<2,64>>;
#[inline]
fn mul(self,rhs:Self)->Self::Output{
Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2)))
}
}
impl<T> std::ops::Div<i64> for Time<T>{
type Output=Self;
#[inline]
fn div(self,rhs:i64)->Self::Output{
Self::raw(self.0/rhs)
}
}
impl<T> std::ops::Mul<i64> for Time<T>{
type Output=Self;
#[inline]
fn mul(self,rhs:i64)->Self::Output{
Self::raw(self.0*rhs)
}
}
impl<T> core::ops::Mul<Time<T>> for Planar64{
type Output=Ratio<Fixed<2,64>,Planar64>;
fn mul(self,rhs:Time<T>)->Self::Output{
Ratio::new(self*Fixed::raw(rhs.0),Planar64::raw(1_000_000_000))
}
}
#[cfg(test)]
mod test_time{
use super::*;
type Time=super::AbsoluteTime;
#[test]
fn time_from_planar64(){
let a:Time=Planar64::from(1).into();
assert_eq!(a,Time::ONE_SECOND);
}
#[test]
fn time_from_ratio(){
let a:Time=Ratio::new(Planar64::from(1),Planar64::from(1)).into();
assert_eq!(a,Time::ONE_SECOND);
}
#[test]
fn time_squared(){
let a=Time::from_secs(2);
assert_eq!(a*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000i64.pow(2))*4,Fixed::<2,64>::raw_digit(1_000_000_000i64.pow(2))));
}
#[test]
fn time_times_planar64(){
let a=Time::from_secs(2);
let b=Planar64::from(2);
assert_eq!(b*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000*(1<<32))<<2,Fixed::<1,32>::raw_digit(1_000_000_000)));
}
}
#[inline]
const fn gcd(mut a:u64,mut b:u64)->u64{
while b!=0{
(a,b)=(b,a.rem_euclid(b));
};
a
}
#[derive(Clone,Copy,Debug,Hash)]
pub struct Ratio64{
num:i64,
den:u64,
}
impl Ratio64{
pub const ZERO:Self=Ratio64{num:0,den:1};
pub const ONE:Self=Ratio64{num:1,den:1};
#[inline]
pub const fn new(num:i64,den:u64)->Option<Ratio64>{
if den==0{
None
}else{
let d=gcd(num.unsigned_abs(),den);
Some(Self{num:num/(d as i64),den:den/d})
}
}
#[inline]
pub const fn num(self)->i64{
self.num
}
#[inline]
pub const fn den(self)->u64{
self.den
}
#[inline]
pub const fn mul_int(&self,rhs:i64)->i64{
rhs*self.num/(self.den as i64)
}
#[inline]
pub const fn rhs_div_int(&self,rhs:i64)->i64{
rhs*(self.den as i64)/self.num
}
#[inline]
pub const fn mul_ref(&self,rhs:&Ratio64)->Ratio64{
let (num,den)=(self.num*rhs.num,self.den*rhs.den);
let d=gcd(num.unsigned_abs(),den);
Self{
num:num/(d as i64),
den:den/d,
}
}
}
//from num_traits crate
#[inline]
fn integer_decode_f32(f: f32) -> (u64, i16, i8) {
let bits: u32 = f.to_bits();
let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 };
let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;
let mantissa = if exponent == 0 {
(bits & 0x7fffff) << 1
} else {
(bits & 0x7fffff) | 0x800000
};
// Exponent bias + mantissa shift
exponent -= 127 + 23;
(mantissa as u64, exponent, sign)
}
#[inline]
fn integer_decode_f64(f: f64) -> (u64, i16, i8) {
let bits: u64 = f.to_bits();
let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 };
let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
let mantissa = if exponent == 0 {
(bits & 0xfffffffffffff) << 1
} else {
(bits & 0xfffffffffffff) | 0x10000000000000
};
// Exponent bias + mantissa shift
exponent -= 1023 + 52;
(mantissa, exponent, sign)
}
#[derive(Debug)]
pub enum Ratio64TryFromFloatError{
Nan,
Infinite,
Subnormal,
HighlyNegativeExponent(i16),
HighlyPositiveExponent(i16),
}
const MAX_DENOMINATOR:u128=u64::MAX as u128;
#[inline]
fn ratio64_from_mes((m,e,s):(u64,i16,i8))->Result<Ratio64,Ratio64TryFromFloatError>{
if e< -127{
//this can also just be zero
Err(Ratio64TryFromFloatError::HighlyNegativeExponent(e))
}else if e< -63{
//approximate input ratio within denominator limit
let mut target_num=m as u128;
let mut target_den=1u128<<-e;
let mut num=1;
let mut den=0;
let mut prev_num=0;
let mut prev_den=1;
while target_den!=0{
let whole=target_num/target_den;
(target_num,target_den)=(target_den,target_num-whole*target_den);
let new_num=whole*num+prev_num;
let new_den=whole*den+prev_den;
if MAX_DENOMINATOR<new_den{
break;
}else{
(prev_num,prev_den)=(num,den);
(num,den)=(new_num,new_den);
}
}
Ok(Ratio64::new(num as i64,den as u64).unwrap())
}else if e<0{
Ok(Ratio64::new((m as i64)*(s as i64),1<<-e).unwrap())
}else if (64-m.leading_zeros() as i16)+e<64{
Ok(Ratio64::new((m as i64)*(s as i64)*(1<<e),1).unwrap())
}else{
Err(Ratio64TryFromFloatError::HighlyPositiveExponent(e))
}
}
impl TryFrom<f32> for Ratio64{
type Error=Ratio64TryFromFloatError;
#[inline]
fn try_from(value:f32)->Result<Self,Self::Error>{
match value.classify(){
std::num::FpCategory::Nan=>Err(Self::Error::Nan),
std::num::FpCategory::Infinite=>Err(Self::Error::Infinite),
std::num::FpCategory::Zero=>Ok(Self::ZERO),
std::num::FpCategory::Subnormal
|std::num::FpCategory::Normal=>ratio64_from_mes(integer_decode_f32(value)),
}
}
}
impl TryFrom<f64> for Ratio64{
type Error=Ratio64TryFromFloatError;
#[inline]
fn try_from(value:f64)->Result<Self,Self::Error>{
match value.classify(){
std::num::FpCategory::Nan=>Err(Self::Error::Nan),
std::num::FpCategory::Infinite=>Err(Self::Error::Infinite),
std::num::FpCategory::Zero=>Ok(Self::ZERO),
std::num::FpCategory::Subnormal
|std::num::FpCategory::Normal=>ratio64_from_mes(integer_decode_f64(value)),
}
}
}
impl std::ops::Mul<Ratio64> for Ratio64{
type Output=Ratio64;
#[inline]
fn mul(self,rhs:Ratio64)->Self::Output{
let (num,den)=(self.num*rhs.num,self.den*rhs.den);
let d=gcd(num.unsigned_abs(),den);
Self{
num:num/(d as i64),
den:den/d,
}
}
}
impl std::ops::Mul<i64> for Ratio64{
type Output=Ratio64;
#[inline]
fn mul(self,rhs:i64)->Self::Output {
Self{
num:self.num*rhs,
den:self.den,
}
}
}
impl std::ops::Div<u64> for Ratio64{
type Output=Ratio64;
#[inline]
fn div(self,rhs:u64)->Self::Output {
Self{
num:self.num,
den:self.den*rhs,
}
}
}
#[derive(Clone,Copy,Debug,Hash)]
pub struct Ratio64Vec2{
pub x:Ratio64,
pub y:Ratio64,
}
impl Ratio64Vec2{
pub const ONE:Self=Self{x:Ratio64::ONE,y:Ratio64::ONE};
#[inline]
pub const fn new(x:Ratio64,y:Ratio64)->Self{
Self{x,y}
}
#[inline]
pub const fn mul_int(&self,rhs:glam::I64Vec2)->glam::I64Vec2{
glam::i64vec2(
self.x.mul_int(rhs.x),
self.y.mul_int(rhs.y),
)
}
}
impl std::ops::Mul<i64> for Ratio64Vec2{
type Output=Ratio64Vec2;
#[inline]
fn mul(self,rhs:i64)->Self::Output {
Self{
x:self.x*rhs,
y:self.y*rhs,
}
}
}
///[-pi,pi) = [-2^31,2^31-1]
#[derive(Clone,Copy,Hash)]
pub struct Angle32(i32);
impl Angle32{
const ANGLE32_TO_FLOAT64_RADIANS:f64=std::f64::consts::PI/((1i64<<31) as f64);
pub const FRAC_PI_2:Self=Self(1<<30);
pub const NEG_FRAC_PI_2:Self=Self(-1<<30);
pub const PI:Self=Self(-1<<31);
#[inline]
pub const fn wrap_from_i64(theta:i64)->Self{
//take lower bits
//note: this was checked on compiler explorer and compiles to 1 instruction!
Self(i32::from_ne_bytes(((theta&((1<<32)-1)) as u32).to_ne_bytes()))
}
#[inline]
pub fn clamp_from_i64(theta:i64)->Self{
//the assembly is a bit confusing for this, I thought it was checking the same thing twice
//but it's just checking and then overwriting the value for both upper and lower bounds.
Self(theta.clamp(i32::MIN as i64,i32::MAX as i64) as i32)
}
#[inline]
pub const fn get(&self)->i32{
self.0
}
/// Clamps the value towards the midpoint of the range.
/// Note that theta_min can be larger than theta_max and it will wrap clamp the other way around
#[inline]
pub fn clamp(&self,theta_min:Self,theta_max:Self)->Self{
//((max-min as u32)/2 as i32)+min
let midpoint=((
(theta_max.0 as u32)
.wrapping_sub(theta_min.0 as u32)
/2
) as i32)//(u32::MAX/2) as i32 ALWAYS works
.wrapping_add(theta_min.0);
//(theta-mid).clamp(max-mid,min-mid)+mid
Self(
self.0.wrapping_sub(midpoint)
.max(theta_min.0.wrapping_sub(midpoint))
.min(theta_max.0.wrapping_sub(midpoint))
.wrapping_add(midpoint)
)
}
#[inline]
pub fn cos_sin(&self)->(Planar64,Planar64){
/*
//cordic
let a=self.0 as u32;
//initialize based on the quadrant
let (mut x,mut y)=match (a&(1<<31)!=0,a&(1<<30)!=0){
(false,false)=>( 1i64<<32, 0i64 ),//TR
(false,true )=>( 0i64 , 1i64<<32),//TL
(true ,false)=>(-1i64<<32, 0i64 ),//BL
(true ,true )=>( 0i64 ,-1i64<<32),//BR
};
println!("x={} y={}",Planar64::raw(x),Planar64::raw(y));
for i in 0..30{
if a&(1<<(29-i))!=0{
(x,y)=(x-(y>>i),y+(x>>i));
}
println!("i={i} t={} x={} y={}",(a&(1<<(29-i))!=0) as u8,Planar64::raw(x),Planar64::raw(y));
}
//don't forget the gain
(Planar64::raw(x),Planar64::raw(y))
*/
let (s,c)=(self.0 as f64*Self::ANGLE32_TO_FLOAT64_RADIANS).sin_cos();
(Planar64::raw((c*((1u64<<32) as f64)) as i64),Planar64::raw((s*((1u64<<32) as f64)) as i64))
}
}
impl Into<f32> for Angle32{
#[inline]
fn into(self)->f32{
(self.0 as f64*Self::ANGLE32_TO_FLOAT64_RADIANS) as f32
}
}
impl std::ops::Neg for Angle32{
type Output=Angle32;
#[inline]
fn neg(self)->Self::Output{
Angle32(self.0.wrapping_neg())
}
}
impl std::ops::Add<Angle32> for Angle32{
type Output=Angle32;
#[inline]
fn add(self,rhs:Self)->Self::Output {
Angle32(self.0.wrapping_add(rhs.0))
}
}
impl std::ops::Sub<Angle32> for Angle32{
type Output=Angle32;
#[inline]
fn sub(self,rhs:Self)->Self::Output {
Angle32(self.0.wrapping_sub(rhs.0))
}
}
impl std::ops::Mul<i32> for Angle32{
type Output=Angle32;
#[inline]
fn mul(self,rhs:i32)->Self::Output {
Angle32(self.0.wrapping_mul(rhs))
}
}
impl std::ops::Mul<Angle32> for Angle32{
type Output=Angle32;
#[inline]
fn mul(self,rhs:Self)->Self::Output {
Angle32(self.0.wrapping_mul(rhs.0))
}
}
#[test]
fn angle_sin_cos(){
fn close_enough(lhs:Planar64,rhs:Planar64)->bool{
(lhs-rhs).abs()<Planar64::EPSILON*4
}
fn test_angle(f:f64){
let a=Angle32((f/Angle32::ANGLE32_TO_FLOAT64_RADIANS) as i32);
println!("a={:#034b}",a.0);
let (c,s)=a.cos_sin();
let h=(s*s+c*c).sqrt();
println!("cordic s={} c={}",(s/h).divide(),(c/h).divide());
let (fs,fc)=f.sin_cos();
println!("float s={} c={}",fs,fc);
assert!(close_enough((c/h).divide().fix_1(),Planar64::raw((fc*((1u64<<32) as f64)) as i64)));
assert!(close_enough((s/h).divide().fix_1(),Planar64::raw((fs*((1u64<<32) as f64)) as i64)));
}
test_angle(1.0);
test_angle(std::f64::consts::PI/4.0);
test_angle(std::f64::consts::PI/8.0);
}
/* Unit type unused for now, may revive it for map files
///[-1.0,1.0] = [-2^30,2^30]
pub struct Unit32(i32);
impl Unit32{
#[inline]
pub fn as_planar64(&self) -> Planar64{
Planar64(4*(self.0 as i64))
}
}
const UNIT32_ONE_FLOAT64=((1<<30) as f64);
///[-1.0,1.0] = [-2^30,2^30]
pub struct Unit32Vec3(glam::IVec3);
impl TryFrom<[f32;3]> for Unit32Vec3{
type Error=Unit32TryFromFloatError;
fn try_from(value:[f32;3])->Result<Self,Self::Error>{
Ok(Self(glam::ivec3(
Unit32::try_from(Planar64::try_from(value[0])?)?.0,
Unit32::try_from(Planar64::try_from(value[1])?)?.0,
Unit32::try_from(Planar64::try_from(value[2])?)?.0,
)))
}
}
*/
pub type Planar64TryFromFloatError=fixed_wide::fixed::FixedFromFloatError;
pub type Planar64=fixed_wide::types::I32F32;
pub type Planar64Vec3=linear_ops::types::Vector3<Planar64>;
pub type Planar64Mat3=linear_ops::types::Matrix3<Planar64>;
pub mod vec3{
use super::*;
pub use linear_ops::types::Vector3;
pub const MIN:Planar64Vec3=Planar64Vec3::new([Planar64::MIN;3]);
pub const MAX:Planar64Vec3=Planar64Vec3::new([Planar64::MAX;3]);
pub const ZERO:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO;3]);
pub const ZERO_2:linear_ops::types::Vector3<Fixed::<2,64>>=linear_ops::types::Vector3::new([Fixed::<2,64>::ZERO;3]);
pub const X:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ZERO,Planar64::ZERO]);
pub const Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ONE,Planar64::ZERO]);
pub const Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::ONE]);
pub const ONE:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ONE,Planar64::ONE]);
pub const NEG_X:Planar64Vec3=Planar64Vec3::new([Planar64::NEG_ONE,Planar64::ZERO,Planar64::ZERO]);
pub const NEG_Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::NEG_ONE,Planar64::ZERO]);
pub const NEG_Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::NEG_ONE]);
pub const NEG_ONE:Planar64Vec3=Planar64Vec3::new([Planar64::NEG_ONE,Planar64::NEG_ONE,Planar64::NEG_ONE]);
#[inline]
pub const fn int(x:i32,y:i32,z:i32)->Planar64Vec3{
Planar64Vec3::new([Planar64::raw((x as i64)<<32),Planar64::raw((y as i64)<<32),Planar64::raw((z as i64)<<32)])
}
#[inline]
pub fn raw_array(array:[i64;3])->Planar64Vec3{
Planar64Vec3::new(array.map(Planar64::raw))
}
#[inline]
pub fn raw_xyz(x:i64,y:i64,z:i64)->Planar64Vec3{
Planar64Vec3::new([Planar64::raw(x),Planar64::raw(y),Planar64::raw(z)])
}
#[inline]
pub fn try_from_f32_array([x,y,z]:[f32;3])->Result<Planar64Vec3,Planar64TryFromFloatError>{
Ok(Planar64Vec3::new([
try_from_f32(x)?,
try_from_f32(y)?,
try_from_f32(z)?,
]))
}
}
#[inline]
pub fn int(value:i32)->Planar64{
Planar64::from(value)
}
#[inline]
pub fn try_from_f32(value:f32)->Result<Planar64,Planar64TryFromFloatError>{
let result:Result<Planar64,_>=value.try_into();
match result{
Ok(ok)=>Ok(ok),
Err(e)=>e.underflow_to_zero(),
}
}
pub mod mat3{
use super::*;
pub use linear_ops::types::Matrix3;
#[inline]
pub const fn identity()->Planar64Mat3{
Planar64Mat3::new([
[Planar64::ONE,Planar64::ZERO,Planar64::ZERO],
[Planar64::ZERO,Planar64::ONE,Planar64::ZERO],
[Planar64::ZERO,Planar64::ZERO,Planar64::ONE],
])
}
#[inline]
pub fn from_diagonal(diag:Planar64Vec3)->Planar64Mat3{
Planar64Mat3::new([
[diag.x,Planar64::ZERO,Planar64::ZERO],
[Planar64::ZERO,diag.y,Planar64::ZERO],
[Planar64::ZERO,Planar64::ZERO,diag.z],
])
}
#[inline]
pub fn from_rotation_yx(x:Angle32,y:Angle32)->Planar64Mat3{
let (xc,xs)=x.cos_sin();
let (yc,ys)=y.cos_sin();
Planar64Mat3::from_cols([
Planar64Vec3::new([xc,Planar64::ZERO,-xs]),
Planar64Vec3::new([(xs*ys).fix_1(),yc,(xc*ys).fix_1()]),
Planar64Vec3::new([(xs*yc).fix_1(),-ys,(xc*yc).fix_1()]),
])
}
#[inline]
pub fn from_rotation_y(y:Angle32)->Planar64Mat3{
let (c,s)=y.cos_sin();
Planar64Mat3::from_cols([
Planar64Vec3::new([c,Planar64::ZERO,-s]),
vec3::Y,
Planar64Vec3::new([s,Planar64::ZERO,c]),
])
}
#[inline]
pub fn try_from_f32_array_2d([x_axis,y_axis,z_axis]:[[f32;3];3])->Result<Planar64Mat3,Planar64TryFromFloatError>{
Ok(Planar64Mat3::new([
vec3::try_from_f32_array(x_axis)?.to_array(),
vec3::try_from_f32_array(y_axis)?.to_array(),
vec3::try_from_f32_array(z_axis)?.to_array(),
]))
}
}
#[derive(Clone,Copy,Default,Hash,Eq,PartialEq)]
pub struct Planar64Affine3{
pub matrix3:Planar64Mat3,//includes scale above 1
pub translation:Planar64Vec3,
}
impl Planar64Affine3{
#[inline]
pub const fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{
Self{matrix3,translation}
}
#[inline]
pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3<Fixed<2,64>>{
self.translation.fix_2()+self.matrix3*point
}
}
impl Into<glam::Mat4> for Planar64Affine3{
#[inline]
fn into(self)->glam::Mat4{
let matrix3=self.matrix3.to_array().map(|row|row.map(Into::<f32>::into));
let translation=self.translation.to_array().map(Into::<f32>::into);
glam::Mat4::from_cols_array(&[
matrix3[0][0],matrix3[0][1],matrix3[0][2],0.0,
matrix3[1][0],matrix3[1][1],matrix3[1][2],0.0,
matrix3[2][0],matrix3[2][1],matrix3[2][2],0.0,
translation[0],translation[1],translation[2],1.0
])
}
}
#[test]
fn test_sqrt(){
let r=int(400);
assert_eq!(r,Planar64::raw(1717986918400));
let s=r.sqrt();
assert_eq!(s,Planar64::raw(85899345920));
}

View File

@ -1,16 +0,0 @@
pub mod bvh;
pub mod map;
pub mod run;
pub mod aabb;
pub mod model;
pub mod mouse;
pub mod timer;
pub mod integer;
pub mod physics;
pub mod session;
pub mod updatable;
pub mod instruction;
pub mod gameplay_attributes;
pub mod gameplay_modes;
pub mod gameplay_style;
pub mod controls_bitflag;

View File

@ -1,14 +0,0 @@
use crate::model;
use crate::gameplay_modes;
use crate::gameplay_attributes;
//this is a temporary struct to try to get the code running again
//TODO: use snf::map::Region to update the data in physics and graphics instead of this
pub struct CompleteMap{
pub modes:gameplay_modes::Modes,
pub attributes:Vec<gameplay_attributes::CollisionAttributes>,
pub meshes:Vec<model::Mesh>,
pub models:Vec<model::Model>,
//RenderPattern
pub textures:Vec<Vec<u8>>,
pub render_configs:Vec<model::RenderConfig>,
}

View File

@ -1,133 +0,0 @@
use crate::integer::{Planar64Vec3,Planar64Affine3};
use crate::gameplay_attributes;
pub type TextureCoordinate=glam::Vec2;
pub type Color4=glam::Vec4;
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
pub struct PositionId(u32);
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
pub struct TextureCoordinateId(u32);
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
pub struct NormalId(u32);
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
pub struct ColorId(u32);
#[derive(Clone,Hash,PartialEq,Eq)]
pub struct IndexedVertex{
pub pos:PositionId,
pub tex:TextureCoordinateId,
pub normal:NormalId,
pub color:ColorId,
}
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
pub struct VertexId(u32);
pub type IndexedVertexList=Vec<VertexId>;
pub trait PolygonIter{
fn polys(&self)->impl Iterator<Item=&[VertexId]>;
}
pub trait MapVertexId{
fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self;
}
#[derive(Clone)]
pub struct PolygonList(Vec<IndexedVertexList>);
impl PolygonList{
pub const fn new(list:Vec<IndexedVertexList>)->Self{
Self(list)
}
pub fn extend<T:IntoIterator<Item=IndexedVertexList>>(&mut self,iter:T){
self.0.extend(iter);
}
}
impl PolygonIter for PolygonList{
fn polys(&self)->impl Iterator<Item=&[VertexId]>{
self.0.iter().map(|poly|poly.as_slice())
}
}
impl MapVertexId for PolygonList{
fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self{
Self(self.0.into_iter().map(|ivl|ivl.into_iter().map(&f).collect()).collect())
}
}
// pub struct TriangleStrip(IndexedVertexList);
// impl PolygonIter for TriangleStrip{
// fn polys(&self)->impl Iterator<Item=&[VertexId]>{
// self.0.vertices.windows(3).enumerate().map(|(i,s)|if i&0!=0{return s.iter().rev()}else{return s.iter()})
// }
// }
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
pub struct PolygonGroupId(u32);
#[derive(Clone)]
pub enum PolygonGroup{
PolygonList(PolygonList),
//TriangleStrip(TriangleStrip),
}
impl PolygonIter for PolygonGroup{
fn polys(&self)->impl Iterator<Item=&[VertexId]>{
match self{
PolygonGroup::PolygonList(list)=>list.polys(),
//PolygonGroup::TriangleStrip(strip)=>strip.polys(),
}
}
}
impl MapVertexId for PolygonGroup{
fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self{
match self{
PolygonGroup::PolygonList(polys)=>Self::PolygonList(polys.map_vertex_id(f)),
}
}
}
/// Ah yes, a group of things to render at the same time
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)]
pub struct TextureId(u32);
#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
pub struct RenderConfigId(u32);
#[derive(Clone,Copy,Default)]
pub struct RenderConfig{
pub texture:Option<TextureId>,
}
impl RenderConfig{
pub const fn texture(texture:TextureId)->Self{
Self{
texture:Some(texture),
}
}
}
#[derive(Clone)]
pub struct IndexedGraphicsGroup{
//Render pattern material/texture/shader/flat color
pub render:RenderConfigId,
pub groups:Vec<PolygonGroupId>,
}
#[derive(Clone,Default)]
pub struct IndexedPhysicsGroup{
//the polygons in this group are guaranteed to make a closed convex shape
pub groups:Vec<PolygonGroupId>,
}
//This is a superset of PhysicsModel and GraphicsModel
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)]
pub struct MeshId(u32);
#[derive(Clone)]
pub struct Mesh{
pub unique_pos:Vec<Planar64Vec3>,//Unit32Vec3
pub unique_normal:Vec<Planar64Vec3>,//Unit32Vec3
pub unique_tex:Vec<TextureCoordinate>,
pub unique_color:Vec<Color4>,
pub unique_vertices:Vec<IndexedVertex>,
//polygon groups are constant texture AND convexity slices
//note that this may need to be changed to be a list of individual faces
//for submeshes to work since face ids need to be consistent across submeshes
//so face == polygon_groups[face_id]
pub polygon_groups:Vec<PolygonGroup>,
//graphics indexed (by texture)
pub graphics_groups:Vec<IndexedGraphicsGroup>,
//physics indexed (by convexity)
pub physics_groups:Vec<IndexedPhysicsGroup>,
}
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
pub struct ModelId(u32);
pub struct Model{
pub mesh:MeshId,
pub attributes:gameplay_attributes::CollisionAttributesId,
pub color:Color4,//transparency is in here
pub transform:Planar64Affine3,
}

View File

@ -1,28 +0,0 @@
use crate::integer::Time;
#[derive(Clone,Debug)]
pub struct MouseState<T>{
pub pos:glam::IVec2,
pub time:Time<T>,
}
impl<T> Default for MouseState<T>{
fn default()->Self{
Self{
time:Time::ZERO,
pos:glam::IVec2::ZERO,
}
}
}
impl<T> MouseState<T>
where Time<T>:Copy,
{
pub fn lerp(&self,target:&MouseState<T>,time:Time<T>)->glam::IVec2{
let m0=self.pos.as_i64vec2();
let m1=target.pos.as_i64vec2();
//these are deltas
let t1t=(target.time-time).nanos();
let tt0=(time-self.time).nanos();
let dt=(target.time-self.time).nanos();
((m0*t1t+m1*tt0)/dt).as_ivec2()
}
}

View File

@ -1,56 +0,0 @@
use crate::mouse::MouseState;
use crate::gameplay_modes::{ModeId,StageId};
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
pub enum TimeInner{}
pub type Time=crate::integer::Time<TimeInner>;
#[derive(Clone,Debug)]
pub enum Instruction{
Mouse(MouseInstruction),
SetControl(SetControlInstruction),
Mode(ModeInstruction),
Misc(MiscInstruction),
/// Idle: there were no input events, but the simulation is safe to advance to this timestep
Idle,
}
impl Instruction{
pub const IDLE:Self=Self::Idle;
}
#[derive(Clone,Debug)]
pub enum MouseInstruction{
/// Replace the entire interpolation state to avoid dividing by zero when replacing twice
ReplaceMouse{
m0:MouseState<TimeInner>,
m1:MouseState<TimeInner>,
},
SetNextMouse(MouseState<TimeInner>),
}
#[derive(Clone,Debug)]
pub enum SetControlInstruction{
SetMoveRight(bool),
SetMoveUp(bool),
SetMoveBack(bool),
SetMoveLeft(bool),
SetMoveDown(bool),
SetMoveForward(bool),
SetJump(bool),
SetZoom(bool),
}
#[derive(Clone,Debug)]
pub enum ModeInstruction{
/// Reset: fully replace the physics state.
/// This forgets all inputs and settings which need to be reapplied.
Reset,
/// Restart: Teleport to the start zone.
/// This runs when you press R or teleport to a bonus
Restart(ModeId),
/// Spawn: Teleport to a specific mode's spawn
/// This runs when the map loads to put you at the map lobby
Spawn(ModeId,StageId),
}
#[derive(Clone,Debug)]
pub enum MiscInstruction{
PracticeFly,
SetSensitivity(crate::integer::Ratio64Vec2),
}

View File

@ -1,119 +0,0 @@
use crate::timer::{TimerFixed,Realtime,Paused,Unpaused};
use crate::physics::{TimeInner as PhysicsTimeInner,Time as PhysicsTime};
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
pub enum TimeInner{}
pub type Time=crate::integer::Time<TimeInner>;
#[derive(Clone,Copy,Debug)]
pub enum FlagReason{
Anticheat,
StyleChange,
Clock,
Pause,
Flying,
Gravity,
Timescale,
TimeTravel,
Teleport,
}
impl ToString for FlagReason{
fn to_string(&self)->String{
self.as_ref().to_owned()
}
}
impl AsRef<str> for FlagReason{
fn as_ref(&self)->&str{
match self{
FlagReason::Anticheat=>"Passed through anticheat zone.",
FlagReason::StyleChange=>"Changed style.",
FlagReason::Clock=>"Incorrect clock. (This can be caused by internet hiccups)",
FlagReason::Pause=>"Pausing is not allowed in this style.",
FlagReason::Flying=>"Flying is not allowed in this style.",
FlagReason::Gravity=>"Gravity modification is not allowed in this style.",
FlagReason::Timescale=>"Timescale is not allowed in this style.",
FlagReason::TimeTravel=>"Time travel is not allowed in this style.",
FlagReason::Teleport=>"Illegal teleport.",
}
}
}
#[derive(Debug)]
pub enum Error{
NotStarted,
AlreadyStarted,
AlreadyFinished,
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for Error{}
#[derive(Clone,Copy,Debug)]
enum RunState{
Created,
Started{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Unpaused>},
Finished{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Paused>},
}
#[derive(Clone,Copy,Debug)]
pub struct Run{
state:RunState,
flagged:Option<FlagReason>,
}
impl Run{
pub fn new()->Self{
Self{
state:RunState::Created,
flagged:None,
}
}
pub fn time(&self,time:PhysicsTime)->Time{
match &self.state{
RunState::Created=>Time::ZERO,
RunState::Started{timer}=>timer.time(time),
RunState::Finished{timer}=>timer.time(),
}
}
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
match &self.state{
RunState::Created=>{
self.state=RunState::Started{
timer:TimerFixed::new(time,Time::ZERO),
};
Ok(())
},
RunState::Started{..}=>Err(Error::AlreadyStarted),
RunState::Finished{..}=>Err(Error::AlreadyFinished),
}
}
pub fn finish(&mut self,time:PhysicsTime)->Result<(),Error>{
//this uses Copy
match &self.state{
RunState::Created=>Err(Error::NotStarted),
RunState::Started{timer}=>{
self.state=RunState::Finished{
timer:timer.into_paused(time),
};
Ok(())
},
RunState::Finished{..}=>Err(Error::AlreadyFinished),
}
}
pub fn flag(&mut self,flag_reason:FlagReason){
//don't replace the first reason the run was flagged
if self.flagged.is_none(){
self.flagged=Some(flag_reason);
}
}
pub fn get_finish_time(&self)->Option<Time>{
match &self.state{
RunState::Finished{timer}=>Some(timer.time()),
_=>None,
}
}
}

View File

@ -1,3 +0,0 @@
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
pub enum TimeInner{}
pub type Time=crate::integer::Time<TimeInner>;

View File

@ -1,363 +0,0 @@
use crate::integer::{Time,Ratio64};
#[derive(Clone,Copy,Debug)]
pub struct Paused;
#[derive(Clone,Copy,Debug)]
pub struct Unpaused;
pub trait PauseState:Copy+std::fmt::Debug{
const IS_PAUSED:bool;
fn new()->Self;
}
impl PauseState for Paused{
const IS_PAUSED:bool=true;
fn new()->Self{
Self
}
}
impl PauseState for Unpaused{
const IS_PAUSED:bool=false;
fn new()->Self{
Self
}
}
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
enum Inner{}
type InnerTime=Time<Inner>;
#[derive(Clone,Copy,Debug)]
pub struct Realtime<In,Out>{
offset:InnerTime,
_in:core::marker::PhantomData<In>,
_out:core::marker::PhantomData<Out>,
}
impl<In,Out> Realtime<In,Out>{
pub const fn new(offset:InnerTime)->Self{
Self{
offset,
_in:core::marker::PhantomData,
_out:core::marker::PhantomData,
}
}
}
#[derive(Clone,Copy,Debug)]
pub struct Scaled<In,Out>{
scale:Ratio64,
offset:InnerTime,
_in:core::marker::PhantomData<In>,
_out:core::marker::PhantomData<Out>,
}
impl<In,Out> Scaled<In,Out>
where Time<In>:Copy,
{
pub const fn new(scale:Ratio64,offset:InnerTime)->Self{
Self{
scale,
offset,
_in:core::marker::PhantomData,
_out:core::marker::PhantomData,
}
}
const fn with_scale(scale:Ratio64)->Self{
Self::new(scale,InnerTime::ZERO)
}
const fn scale(&self,time:Time<In>)->InnerTime{
InnerTime::raw(self.scale.mul_int(time.get()))
}
const fn get_scale(&self)->Ratio64{
self.scale
}
fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
let new_time=self.get_time(time);
self.scale=new_scale;
self.set_time(time,new_time);
}
}
pub trait TimerState{
type In;
type Out;
fn identity()->Self;
fn get_time(&self,time:Time<Self::In>)->Time<Self::Out>;
fn set_time(&mut self,time:Time<Self::In>,new_time:Time<Self::Out>);
fn get_offset(&self)->InnerTime;
fn set_offset(&mut self,offset:InnerTime);
}
impl<In,Out> TimerState for Realtime<In,Out>{
type In=In;
type Out=Out;
fn identity()->Self{
Self::new(InnerTime::ZERO)
}
fn get_time(&self,time:Time<In>)->Time<Out>{
time.coerce()+self.offset.coerce()
}
fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
self.offset=new_time.coerce()-time.coerce();
}
fn get_offset(&self)->InnerTime{
self.offset
}
fn set_offset(&mut self,offset:InnerTime){
self.offset=offset;
}
}
impl<In,Out> TimerState for Scaled<In,Out>
where Time<In>:Copy,
{
type In=In;
type Out=Out;
fn identity()->Self{
Self::new(Ratio64::ONE,InnerTime::ZERO)
}
fn get_time(&self,time:Time<In>)->Time<Out>{
(self.scale(time)+self.offset).coerce()
}
fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
self.offset=new_time.coerce()-self.scale(time);
}
fn get_offset(&self)->InnerTime{
self.offset
}
fn set_offset(&mut self,offset:InnerTime){
self.offset=offset;
}
}
#[derive(Clone,Copy,Debug)]
pub struct TimerFixed<T:TimerState,P:PauseState>{
state:T,
_paused:P,
}
//scaled timer methods are generic across PauseState
impl<P:PauseState,In,Out> TimerFixed<Scaled<In,Out>,P>
where Time<In>:Copy,
{
pub fn scaled(time:Time<In>,new_time:Time<Out>,scale:Ratio64)->Self{
let mut timer=Self{
state:Scaled::with_scale(scale),
_paused:P::new(),
};
timer.set_time(time,new_time);
timer
}
pub const fn get_scale(&self)->Ratio64{
self.state.get_scale()
}
pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
self.state.set_scale(time,new_scale)
}
}
//pause and unpause is generic across TimerState
impl<T:TimerState> TimerFixed<T,Paused>
where Time<T::In>:Copy,
{
pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{
let new_time=self.time();
let mut timer=TimerFixed{
state:self.state,
_paused:Unpaused,
};
timer.set_time(time,new_time);
timer
}
pub fn time(&self)->Time<T::Out>{
self.state.get_offset().coerce()
}
}
impl<T:TimerState> TimerFixed<T,Unpaused>
where Time<T::In>:Copy,
{
pub fn into_paused(self,time:Time<T::In>)->TimerFixed<T,Paused>{
let new_time=self.time(time);
let mut timer=TimerFixed{
state:self.state,
_paused:Paused,
};
timer.set_time(time,new_time);
timer
}
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
self.state.get_time(time)
}
}
//the new constructor and time queries are generic across both
impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
pub fn new(time:Time<T::In>,new_time:Time<T::Out>)->Self{
let mut timer=Self{
state:T::identity(),
_paused:P::new(),
};
timer.set_time(time,new_time);
timer
}
pub fn from_state(state:T)->Self{
Self{
state,
_paused:P::new(),
}
}
pub fn into_state(self)->T{
self.state
}
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
match P::IS_PAUSED{
true=>self.state.set_offset(new_time.coerce()),
false=>self.state.set_time(time,new_time),
}
}
}
#[derive(Debug)]
pub enum Error{
AlreadyPaused,
AlreadyUnpaused,
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for Error{}
//wrapper type which holds type state internally
#[derive(Clone,Debug)]
pub enum Timer<T:TimerState>{
Paused(TimerFixed<T,Paused>),
Unpaused(TimerFixed<T,Unpaused>),
}
impl<T:TimerState> Timer<T>
where
T:Copy,
Time<T::In>:Copy,
{
pub fn from_state(state:T,paused:bool)->Self{
match paused{
true=>Self::Paused(TimerFixed::from_state(state)),
false=>Self::Unpaused(TimerFixed::from_state(state)),
}
}
pub fn into_state(self)->(T,bool){
match self{
Self::Paused(timer)=>(timer.into_state(),true),
Self::Unpaused(timer)=>(timer.into_state(),false),
}
}
pub fn paused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
Self::Paused(TimerFixed::new(time,new_time))
}
pub fn unpaused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
Self::Unpaused(TimerFixed::new(time,new_time))
}
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
match self{
Self::Paused(timer)=>timer.time(),
Self::Unpaused(timer)=>timer.time(time),
}
}
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
match self{
Self::Paused(timer)=>timer.set_time(time,new_time),
Self::Unpaused(timer)=>timer.set_time(time,new_time),
}
}
pub fn pause(&mut self,time:Time<T::In>)->Result<(),Error>{
*self=match *self{
Self::Paused(_)=>return Err(Error::AlreadyPaused),
Self::Unpaused(timer)=>Self::Paused(timer.into_paused(time)),
};
Ok(())
}
pub fn unpause(&mut self,time:Time<T::In>)->Result<(),Error>{
*self=match *self{
Self::Paused(timer)=>Self::Unpaused(timer.into_unpaused(time)),
Self::Unpaused(_)=>return Err(Error::AlreadyUnpaused),
};
Ok(())
}
pub fn is_paused(&self)->bool{
match self{
Self::Paused(_)=>true,
Self::Unpaused(_)=>false,
}
}
pub fn set_paused(&mut self,time:Time<T::In>,paused:bool)->Result<(),Error>{
match paused{
true=>self.pause(time),
false=>self.unpause(time),
}
}
}
//scaled timer methods are generic across PauseState
impl<In,Out> Timer<Scaled<In,Out>>
where Time<In>:Copy,
{
pub const fn get_scale(&self)->Ratio64{
match self{
Self::Paused(timer)=>timer.get_scale(),
Self::Unpaused(timer)=>timer.get_scale(),
}
}
pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
match self{
Self::Paused(timer)=>timer.set_scale(time,new_scale),
Self::Unpaused(timer)=>timer.set_scale(time,new_scale),
}
}
}
#[cfg(test)]
mod test{
use super::*;
macro_rules! sec {
($s: expr) => {
Time::from_secs($s)
};
}
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
enum Parent{}
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
enum Calculated{}
#[test]
fn test_timerfixed_scaled(){
//create a paused timer that reads 0s
let timer=TimerFixed::<Scaled<Parent,Calculated>,Paused>::from_state(Scaled::new(0.5f32.try_into().unwrap(),sec!(0)));
//the paused timer at 1 second should read 0s
assert_eq!(timer.time(),sec!(0));
//unpause it after one second
let timer=timer.into_unpaused(sec!(1));
//the timer at 6 seconds should read 2.5s
assert_eq!(timer.time(sec!(6)),Time::from_millis(2500));
//pause the timer after 11 seconds
let timer=timer.into_paused(sec!(11));
//the paused timer at 20 seconds should read 5s
assert_eq!(timer.time(),sec!(5));
}
#[test]
fn test_timer()->Result<(),Error>{
//create a paused timer that reads 0s
let mut timer=Timer::<Realtime<Parent,Calculated>>::paused(sec!(0),sec!(0));
//the paused timer at 1 second should read 0s
assert_eq!(timer.time(sec!(1)),sec!(0));
//unpause it after one second
timer.unpause(sec!(1))?;
//the timer at 6 seconds should read 5s
assert_eq!(timer.time(sec!(6)),sec!(5));
//pause the timer after 11 seconds
timer.pause(sec!(11))?;
//the paused timer at 20 seconds should read 10s
assert_eq!(timer.time(sec!(20)),sec!(10));
Ok(())
}
}

View File

@ -1,56 +0,0 @@
pub trait Updatable<Updater>{
fn update(&mut self,update:Updater);
}
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
struct InnerId(u32);
#[derive(Clone)]
struct Inner{
id:InnerId,
enabled:bool,
}
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
struct OuterId(u32);
struct Outer{
id:OuterId,
inners:std::collections::HashMap<InnerId,Inner>,
}
enum Update<I,U>{
Insert(I),
Update(U),
Remove
}
struct InnerUpdate{
//#[updatable(Update)]
enabled:Option<bool>,
}
struct OuterUpdate{
//#[updatable(Insert,Update,Remove)]
inners:std::collections::HashMap<InnerId,Update<Inner,InnerUpdate>>,
//#[updatable(Update)]
//inners:std::collections::HashMap<InnerId,InnerUpdate>,
}
impl Updatable<InnerUpdate> for Inner{
fn update(&mut self,update:InnerUpdate){
if let Some(enabled)=update.enabled{
self.enabled=enabled;
}
}
}
impl Updatable<OuterUpdate> for Outer{
fn update(&mut self,update:OuterUpdate){
for (id,up) in update.inners{
match up{
Update::Insert(new_inner)=>self.inners.insert(id,new_inner),
Update::Update(inner_update)=>self.inners.get_mut(&id).map(|inner|{
let old=inner.clone();
inner.update(inner_update);
old
}),
Update::Remove=>self.inners.remove(&id),
};
}
}
}
//*/

View File

@ -1 +0,0 @@
/target

View File

@ -1,21 +0,0 @@
[package]
name = "strafesnet_deferred_loader"
version = "0.4.1"
edition = "2021"
repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0"
description = "Acquire IDs for objects before loading them in bulk."
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["legacy"]
legacy = ["dep:url","dep:vbsp"]
#roblox = ["dep:lazy-regex"]
#source = ["dep:vbsp"]
[dependencies]
strafesnet_common = { path = "../common", registry = "strafesnet" }
url = { version = "2.5.2", optional = true }
vbsp = { version = "0.6.0", optional = true }

View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -1,23 +0,0 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -1,19 +0,0 @@
Texture Loader
==============
## Texture loader, designed to be used in conjunction with rbx_loader, bsp_loader or Strafe Client
#### License
<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>
<br>
<sub>
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
</sub>

View File

@ -1,34 +0,0 @@
#[cfg(feature="legacy")]
mod roblox_legacy;
#[cfg(feature="legacy")]
mod source_legacy;
#[cfg(feature="roblox")]
mod roblox;
#[cfg(feature="source")]
mod source;
#[cfg(any(feature="roblox",feature="legacy"))]
pub mod rbxassetid;
pub mod texture;
#[cfg(any(feature="source",feature="legacy"))]
pub mod valve_mesh;
#[cfg(any(feature="roblox",feature="legacy"))]
pub mod roblox_mesh;
#[cfg(feature="legacy")]
pub fn roblox_legacy()->roblox_legacy::Loader{
roblox_legacy::Loader::new()
}
#[cfg(feature="legacy")]
pub fn source_legacy()->source_legacy::Loader{
source_legacy::Loader::new()
}
#[cfg(feature="roblox")]
pub fn roblox()->roblox::Loader{
roblox::Loader::new()
}
#[cfg(feature="source")]
pub fn source()->source::Loader{
source::Loader::new()
}

View File

@ -1,48 +0,0 @@
#[derive(Hash,Eq,PartialEq)]
pub struct RobloxAssetId(pub u64);
#[derive(Debug)]
#[allow(dead_code)]
pub struct StringWithError{
string:String,
error:RobloxAssetIdParseErr,
}
impl std::fmt::Display for StringWithError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for StringWithError{}
impl StringWithError{
const fn new(
string:String,
error:RobloxAssetIdParseErr,
)->Self{
Self{string,error}
}
}
#[derive(Debug)]
pub enum RobloxAssetIdParseErr{
Url(url::ParseError),
UnknownScheme,
ParseInt(std::num::ParseIntError),
MissingAssetId,
}
impl std::str::FromStr for RobloxAssetId{
type Err=StringWithError;
fn from_str(s:&str)->Result<Self,Self::Err>{
let url=url::Url::parse(s).map_err(|e|StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::Url(e)))?;
let parsed_asset_id=match url.scheme(){
"rbxassetid"=>url.domain().ok_or_else(||StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::MissingAssetId))?.parse(),
"http"|"https"=>{
let (_,asset_id)=url.query_pairs()
.find(|(id,_)|match id.as_ref(){
"ID"|"id"|"Id"|"iD"=>true,
_=>false,
}).ok_or_else(||StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::MissingAssetId))?;
asset_id.parse()
},
_=>Err(StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::UnknownScheme))?,
};
Ok(Self(parsed_asset_id.map_err(|e|StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::ParseInt(e)))?))
}
}

View File

@ -1,112 +0,0 @@
use std::io::Read;
use std::collections::HashMap;
use crate::roblox_mesh;
use crate::texture::{RenderConfigs,Texture};
use strafesnet_common::model::{MeshId,RenderConfig,RenderConfigId,TextureId};
use crate::rbxassetid::RobloxAssetId;
#[derive(Default)]
pub struct RenderConfigLoader{
texture_count:u32,
render_configs:Vec<RenderConfig>,
render_config_id_from_asset_id:HashMap<Option<RobloxAssetId>,RenderConfigId>,
}
impl RenderConfigLoader{
pub fn acquire_render_config_id(&mut self,name:Option<&str>)->RenderConfigId{
let render_id=RenderConfigId::new(self.render_config_id_from_asset_id.len() as u32);
let index=name.and_then(|name|{
match name.parse::<RobloxAssetId>(){
Ok(asset_id)=>Some(asset_id),
Err(e)=>{
println!("Failed to parse AssetId: {e}");
None
},
}
});
*self.render_config_id_from_asset_id.entry(index).or_insert_with(||{
//create the render config.
let render_config=if name.is_some(){
let render_config=RenderConfig::texture(TextureId::new(self.texture_count));
self.texture_count+=1;
render_config
}else{
RenderConfig::default()
};
self.render_configs.push(render_config);
render_id
})
}
}
#[derive(Default)]
pub struct MeshLoader{
mesh_id_from_asset_id:HashMap<Option<RobloxAssetId>,MeshId>,
}
impl MeshLoader{
pub fn acquire_mesh_id(&mut self,name:&str)->MeshId{
let mesh_id=MeshId::new(self.mesh_id_from_asset_id.len() as u32);
let index=match name.parse::<RobloxAssetId>(){
Ok(asset_id)=>Some(asset_id),
Err(e)=>{
println!("Failed to parse AssetId: {e}");
None
},
};
*self.mesh_id_from_asset_id.entry(index).or_insert(mesh_id)
}
pub fn load_meshes(&mut self)->Result<roblox_mesh::Meshes,std::io::Error>{
let mut mesh_data=vec![None;self.mesh_id_from_asset_id.len()];
for (asset_id_option,mesh_id) in &self.mesh_id_from_asset_id{
if let Some(asset_id)=asset_id_option{
if let Ok(mut file)=std::fs::File::open(format!("meshes/{}",asset_id.0)){
//TODO: parallel
let mut data=Vec::<u8>::new();
file.read_to_end(&mut data)?;
mesh_data[mesh_id.get() as usize]=Some(roblox_mesh::RobloxMeshData::new(data));
}else{
println!("[roblox_legacy] no mesh name={}",asset_id.0);
}
}
}
Ok(roblox_mesh::Meshes::new(mesh_data))
}
}
pub struct Loader{
render_config_loader:RenderConfigLoader,
mesh_loader:MeshLoader,
}
impl Loader{
pub fn new()->Self{
Self{
render_config_loader:RenderConfigLoader::default(),
mesh_loader:MeshLoader::default(),
}
}
pub fn get_inner_mut(&mut self)->(&mut RenderConfigLoader,&mut MeshLoader){
(&mut self.render_config_loader,&mut self.mesh_loader)
}
pub fn into_render_configs(mut self)->Result<RenderConfigs,std::io::Error>{
let mut sorted_textures=vec![None;self.render_config_loader.texture_count as usize];
for (asset_id_option,render_config_id) in self.render_config_loader.render_config_id_from_asset_id{
let render_config=self.render_config_loader.render_configs.get_mut(render_config_id.get() as usize).unwrap();
if let (Some(asset_id),Some(texture_id))=(asset_id_option,render_config.texture){
if let Ok(mut file)=std::fs::File::open(format!("textures/{}.dds",asset_id.0)){
//TODO: parallel
let mut data=Vec::<u8>::new();
file.read_to_end(&mut data)?;
sorted_textures[texture_id.get() as usize]=Some(Texture::ImageDDS(data));
}else{
//texture failed to load
render_config.texture=None;
}
}
}
Ok(RenderConfigs::new(
sorted_textures,
self.render_config_loader.render_configs,
))
}
}

View File

@ -1,30 +0,0 @@
use strafesnet_common::model::MeshId;
#[derive(Clone)]
pub struct RobloxMeshData(Vec<u8>);
impl RobloxMeshData{
pub(crate) fn new(data:Vec<u8>)->Self{
Self(data)
}
pub fn get(self)->Vec<u8>{
self.0
}
}
pub struct Meshes{
meshes:Vec<Option<RobloxMeshData>>,
}
impl Meshes{
pub(crate) const fn new(meshes:Vec<Option<RobloxMeshData>>)->Self{
Self{
meshes,
}
}
pub fn get_texture(&self,texture_id:MeshId)->Option<&RobloxMeshData>{
self.meshes.get(texture_id.get() as usize)?.as_ref()
}
pub fn into_iter(self)->impl Iterator<Item=(MeshId,RobloxMeshData)>{
self.meshes.into_iter().enumerate().filter_map(|(mesh_id,maybe_mesh)|
maybe_mesh.map(|mesh|(MeshId::new(mesh_id as u32),mesh))
)
}
}

View File

@ -1,102 +0,0 @@
use std::io::Read;
use std::collections::HashMap;
use crate::valve_mesh;
use crate::texture::{Texture,RenderConfigs};
use strafesnet_common::model::{MeshId,TextureId,RenderConfig,RenderConfigId};
pub struct RenderConfigLoader{
texture_count:u32,
render_configs:Vec<RenderConfig>,
texture_paths:HashMap<Option<Box<str>>,RenderConfigId>,
}
impl RenderConfigLoader{
pub fn acquire_render_config_id(&mut self,name:Option<&str>)->RenderConfigId{
let render_id=RenderConfigId::new(self.texture_paths.len() as u32);
*self.texture_paths.entry(name.map(Into::into)).or_insert_with(||{
//create the render config.
let render_config=if name.is_some(){
let render_config=RenderConfig::texture(TextureId::new(self.texture_count));
self.texture_count+=1;
render_config
}else{
RenderConfig::default()
};
self.render_configs.push(render_config);
render_id
})
}
}
pub struct MeshLoader{
mesh_paths:HashMap<Box<str>,MeshId>,
}
impl MeshLoader{
pub fn acquire_mesh_id(&mut self,name:&str)->MeshId{
let mesh_id=MeshId::new(self.mesh_paths.len() as u32);
*self.mesh_paths.entry(name.into()).or_insert(mesh_id)
}
//load_meshes should look like load_textures
pub fn load_meshes(&mut self,bsp:&vbsp::Bsp)->valve_mesh::Meshes{
let mut mesh_data=vec![None;self.mesh_paths.len()];
for (mesh_path,mesh_id) in &self.mesh_paths{
let mesh_path_lower=mesh_path.to_lowercase();
//.mdl, .vvd, .dx90.vtx
let path=std::path::PathBuf::from(mesh_path_lower.as_str());
let mut vvd_path=path.clone();
let mut vtx_path=path.clone();
vvd_path.set_extension("vvd");
vtx_path.set_extension("dx90.vtx");
match (bsp.pack.get(mesh_path_lower.as_str()),bsp.pack.get(vvd_path.as_os_str().to_str().unwrap()),bsp.pack.get(vtx_path.as_os_str().to_str().unwrap())){
(Ok(Some(mdl_file)),Ok(Some(vvd_file)),Ok(Some(vtx_file)))=>{
mesh_data[mesh_id.get() as usize]=Some(valve_mesh::ModelData{
mdl:valve_mesh::MdlData::new(mdl_file),
vtx:valve_mesh::VtxData::new(vtx_file),
vvd:valve_mesh::VvdData::new(vvd_file),
});
},
_=>println!("no model name={}",mesh_path),
}
}
valve_mesh::Meshes::new(mesh_data)
}
}
pub struct Loader{
render_config_loader:RenderConfigLoader,
mesh_loader:MeshLoader,
}
impl Loader{
pub fn new()->Self{
Self{
render_config_loader:RenderConfigLoader{
texture_count:0,
texture_paths:HashMap::new(),
render_configs:Vec::new(),
},
mesh_loader:MeshLoader{mesh_paths:HashMap::new()},
}
}
pub fn get_inner_mut(&mut self)->(&mut RenderConfigLoader,&mut MeshLoader){
(&mut self.render_config_loader,&mut self.mesh_loader)
}
pub fn into_render_configs(mut self)->Result<RenderConfigs,std::io::Error>{
let mut sorted_textures=vec![None;self.render_config_loader.texture_count as usize];
for (texture_path,render_config_id) in self.render_config_loader.texture_paths{
let render_config=self.render_config_loader.render_configs.get_mut(render_config_id.get() as usize).unwrap();
if let (Some(texture_path),Some(texture_id))=(texture_path,render_config.texture){
if let Ok(mut file)=std::fs::File::open(format!("textures/{}.dds",texture_path)){
//TODO: parallel
let mut data=Vec::<u8>::new();
file.read_to_end(&mut data)?;
sorted_textures[texture_id.get() as usize]=Some(Texture::ImageDDS(data));
}else{
//texture failed to load
render_config.texture=None;
}
}
}
Ok(RenderConfigs::new(
sorted_textures,
self.render_config_loader.render_configs,
))
}
}

View File

@ -1,39 +0,0 @@
use strafesnet_common::model::{TextureId,RenderConfigId,RenderConfig};
#[derive(Clone)]
pub enum Texture{
ImageDDS(Vec<u8>),
}
impl AsRef<[u8]> for Texture{
fn as_ref(&self)->&[u8]{
match self{
Texture::ImageDDS(data)=>data.as_ref(),
}
}
}
pub struct RenderConfigs{
textures:Vec<Option<Texture>>,
render_configs:Vec<RenderConfig>,
}
impl RenderConfigs{
pub(crate) const fn new(textures:Vec<Option<Texture>>,render_configs:Vec<RenderConfig>)->Self{
Self{
textures,
render_configs,
}
}
pub fn consume(self)->(
impl Iterator<Item=(TextureId,Texture)>,
impl Iterator<Item=(RenderConfigId,RenderConfig)>
){
(
self.textures.into_iter().enumerate().filter_map(|(texture_id,maybe_texture)|
maybe_texture.map(|texture|(TextureId::new(texture_id as u32),texture))
),
self.render_configs.into_iter().enumerate().map(|(render_id,render)|
(RenderConfigId::new(render_id as u32),render)
),
)
}
}

View File

@ -1,60 +0,0 @@
use strafesnet_common::model::MeshId;
//duplicate this code for now
#[derive(Clone)]
pub struct MdlData(Vec<u8>);
impl MdlData{
pub const fn new(value:Vec<u8>)->Self{
Self(value)
}
pub fn get(self)->Vec<u8>{
self.0
}
}
#[derive(Clone)]
pub struct VtxData(Vec<u8>);
impl VtxData{
pub const fn new(value:Vec<u8>)->Self{
Self(value)
}
pub fn get(self)->Vec<u8>{
self.0
}
}
#[derive(Clone)]
pub struct VvdData(Vec<u8>);
impl VvdData{
pub const fn new(value:Vec<u8>)->Self{
Self(value)
}
pub fn get(self)->Vec<u8>{
self.0
}
}
#[derive(Clone)]
pub struct ModelData{
pub mdl:MdlData,
pub vtx:VtxData,
pub vvd:VvdData,
}
//meshes is more prone to failure
pub struct Meshes{
meshes:Vec<Option<ModelData>>,
}
impl Meshes{
pub(crate) const fn new(meshes:Vec<Option<ModelData>>)->Self{
Self{
meshes,
}
}
pub fn get_texture(&self,texture_id:MeshId)->Option<&ModelData>{
self.meshes.get(texture_id.get() as usize)?.as_ref()
}
pub fn into_iter(self)->impl Iterator<Item=(MeshId,ModelData)>{
self.meshes.into_iter().enumerate().filter_map(|(mesh_id,maybe_mesh)|
maybe_mesh.map(|mesh|(MeshId::new(mesh_id as u32),mesh))
)
}
}

View File

@ -1 +0,0 @@
/target

View File

@ -1,20 +0,0 @@
[package]
name = "fixed_wide"
version = "0.1.1"
edition = "2021"
repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0"
description = "Fixed point numbers with optional widening Mul operator."
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[features]
default=[]
deferred-division=["dep:ratio_ops"]
wide-mul=[]
zeroes=["dep:arrayvec"]
[dependencies]
bnum = "0.12.0"
arrayvec = { version = "0.7.6", optional = true }
paste = "1.0.15"
ratio_ops = { path = "../ratio_ops", registry = "strafesnet", optional = true }

View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -1,23 +0,0 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -1,829 +0,0 @@
use bnum::{BInt,cast::As};
#[derive(Clone,Copy,Debug,Default,Hash,PartialEq,Eq,PartialOrd,Ord)]
/// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled)
/// N is the number of u64s to use
/// F is the number of fractional bits (always N*32 lol)
pub struct Fixed<const N:usize,const F:usize>{
pub(crate)bits:BInt<{N}>,
}
impl<const N:usize,const F:usize> Fixed<N,F>{
pub const MAX:Self=Self::from_bits(BInt::<N>::MAX);
pub const MIN:Self=Self::from_bits(BInt::<N>::MIN);
pub const ZERO:Self=Self::from_bits(BInt::<N>::ZERO);
pub const EPSILON:Self=Self::from_bits(BInt::<N>::ONE);
pub const NEG_EPSILON:Self=Self::from_bits(BInt::<N>::NEG_ONE);
pub const ONE:Self=Self::from_bits(BInt::<N>::ONE.shl(F as u32));
pub const TWO:Self=Self::from_bits(BInt::<N>::TWO.shl(F as u32));
pub const HALF:Self=Self::from_bits(BInt::<N>::ONE.shl(F as u32-1));
pub const NEG_ONE:Self=Self::from_bits(BInt::<N>::NEG_ONE.shl(F as u32));
pub const NEG_TWO:Self=Self::from_bits(BInt::<N>::NEG_TWO.shl(F as u32));
pub const NEG_HALF:Self=Self::from_bits(BInt::<N>::NEG_ONE.shl(F as u32-1));
}
impl<const N:usize,const F:usize> Fixed<N,F>{
#[inline]
pub const fn from_bits(bits:BInt::<N>)->Self{
Self{
bits,
}
}
#[inline]
pub const fn to_bits(self)->BInt<N>{
self.bits
}
#[inline]
pub const fn raw_digit(value:i64)->Self{
let mut digits=[0u64;N];
digits[0]=value.abs() as u64;
//sign bit
digits[N-1]|=(value&i64::MIN) as u64;
Self::from_bits(BInt::from_bits(bnum::BUint::from_digits(digits)))
}
#[inline]
pub const fn is_zero(self)->bool{
self.bits.is_zero()
}
#[inline]
pub const fn is_negative(self)->bool{
self.bits.is_negative()
}
#[inline]
pub const fn is_positive(self)->bool{
self.bits.is_positive()
}
#[inline]
pub const fn abs(self)->Self{
Self::from_bits(self.bits.abs())
}
}
impl<const F:usize> Fixed<1,F>{
/// My old code called this function everywhere so let's provide it
#[inline]
pub const fn raw(value:i64)->Self{
Self::from_bits(BInt::from_bits(bnum::BUint::from_digit(value as u64)))
}
#[inline]
pub const fn to_raw(self)->i64{
let &[digit]=self.to_bits().to_bits().digits();
digit as i64
}
}
macro_rules! impl_from {
($($from:ty),*)=>{
$(
impl<const N:usize,const F:usize> From<$from> for Fixed<N,F>{
#[inline]
fn from(value:$from)->Self{
Self::from_bits(BInt::<{N}>::from(value)<<F as u32)
}
}
)*
};
}
impl_from!(
u8,u16,u32,u64,u128,usize,
i8,i16,i32,i64,i128,isize
);
impl<const N:usize,const F:usize,T> PartialEq<T> for Fixed<N,F>
where
T:Copy,
BInt::<N>:From<T>,
{
#[inline]
fn eq(&self,&other:&T)->bool{
self.bits.eq(&other.into())
}
}
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
where
T:Copy,
BInt::<N>:From<T>,
{
#[inline]
fn partial_cmp(&self,&other:&T)->Option<std::cmp::Ordering>{
self.bits.partial_cmp(&other.into())
}
}
impl<const N:usize,const F:usize> std::ops::Neg for Fixed<N,F>{
type Output=Self;
#[inline]
fn neg(self)->Self{
Self::from_bits(self.bits.neg())
}
}
impl<const N:usize,const F:usize> std::iter::Sum for Fixed<N,F>{
#[inline]
fn sum<I:Iterator<Item=Self>>(iter:I)->Self{
let mut sum=Self::ZERO;
for elem in iter{
sum+=elem;
}
sum
}
}
const fn signed_shift(lhs:u64,rhs:i32)->u64{
if rhs.is_negative(){
lhs>>-rhs
}else{
lhs<<rhs
}
}
macro_rules! impl_into_float {
( $output: ty, $unsigned:ty, $exponent_bits:expr, $mantissa_bits:expr ) => {
impl<const N:usize,const F:usize> Into<$output> for Fixed<N,F>{
#[inline]
fn into(self)->$output{
const DIGIT_SHIFT:u32=6;//Log2[64]
// SBBB BBBB
// 1001 1110 0000 0000
let sign=if self.bits.is_negative(){(1 as $unsigned)<<(<$unsigned>::BITS-1)}else{0};
let unsigned=self.bits.unsigned_abs();
let most_significant_bit=unsigned.bits();
let exp=if unsigned.is_zero(){
0
}else{
let msb=most_significant_bit as $unsigned;
let _127=((1 as $unsigned)<<($exponent_bits-1))-1;
let msb_offset=msb+_127-1-F as $unsigned;
msb_offset<<($mantissa_bits-1)
};
let digits=unsigned.digits();
let digit_index=most_significant_bit.saturating_sub(1)>>DIGIT_SHIFT;
let digit=digits[digit_index as usize];
//How many bits does the mantissa take from this digit
let take_bits=most_significant_bit-(digit_index<<DIGIT_SHIFT);
let rest_of_mantissa=$mantissa_bits as i32-(take_bits as i32);
let mut unmasked_mant=signed_shift(digit,rest_of_mantissa) as $unsigned;
if 0<rest_of_mantissa&&digit_index!=0{
//take the next digit down and shove some of its bits onto the bottom of the mantissa
let digit=digits[digit_index as usize-1];
let take_bits=most_significant_bit-((digit_index-1)<<DIGIT_SHIFT);
let rest_of_mantissa=$mantissa_bits as i32-(take_bits as i32);
let unmasked_mant2=signed_shift(digit,rest_of_mantissa) as $unsigned;
unmasked_mant|=unmasked_mant2;
}
let mant=unmasked_mant&((1 as $unsigned)<<($mantissa_bits-1))-1;
let bits=sign|exp|mant;
<$output>::from_bits(bits)
}
}
}
}
impl_into_float!(f32,u32,8,24);
impl_into_float!(f64,u64,11,53);
#[inline]
fn integer_decode_f32(f: f32) -> (u64, i16, bool) {
let bits: u32 = f.to_bits();
let sign: bool = bits & (1<<31) != 0;
let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;
let mantissa = if exponent == 0 {
(bits & 0x7fffff) << 1
} else {
(bits & 0x7fffff) | 0x800000
};
// Exponent bias + mantissa shift
exponent -= 127 + 23;
(mantissa as u64, exponent, sign)
}
#[inline]
fn integer_decode_f64(f: f64) -> (u64, i16, bool) {
let bits: u64 = f.to_bits();
let sign: bool = bits & (1u64<<63) != 0;
let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
let mantissa = if exponent == 0 {
(bits & 0xfffffffffffff) << 1
} else {
(bits & 0xfffffffffffff) | 0x10000000000000
};
// Exponent bias + mantissa shift
exponent -= 1023 + 52;
(mantissa, exponent, sign)
}
#[derive(Debug,Eq,PartialEq)]
pub enum FixedFromFloatError{
Nan,
Infinite,
Overflow,
Underflow,
}
impl FixedFromFloatError{
pub fn underflow_to_zero<const N:usize,const F:usize>(self)->Result<Fixed<N,F>,Self>{
match self{
FixedFromFloatError::Underflow=>Ok(Fixed::ZERO),
_=>Err(self),
}
}
}
macro_rules! impl_from_float {
( $decode:ident, $input: ty, $mantissa_bits:expr ) => {
impl<const N:usize,const F:usize> TryFrom<$input> for Fixed<N,F>{
type Error=FixedFromFloatError;
#[inline]
fn try_from(value:$input)->Result<Self,Self::Error>{
const DIGIT_SHIFT:u32=6;
match value.classify(){
std::num::FpCategory::Nan=>Err(FixedFromFloatError::Nan),
std::num::FpCategory::Infinite=>Err(FixedFromFloatError::Infinite),
std::num::FpCategory::Zero=>Ok(Self::ZERO),
std::num::FpCategory::Subnormal
|std::num::FpCategory::Normal
=>{
let (m,e,s)=$decode(value);
let mut digits=[0u64;N];
let most_significant_bit=e as i32+$mantissa_bits as i32+F as i32;
if most_significant_bit<0{
return Err(FixedFromFloatError::Underflow);
}
let digit_index=most_significant_bit>>DIGIT_SHIFT;
let digit=digits.get_mut(digit_index as usize).ok_or(FixedFromFloatError::Overflow)?;
let take_bits=most_significant_bit-(digit_index<<DIGIT_SHIFT);
let rest_of_mantissa=-($mantissa_bits as i32-(take_bits as i32));
*digit=signed_shift(m,rest_of_mantissa);
if rest_of_mantissa<0&&digit_index!=0{
//we don't care if some float bits are partially truncated
if let Some(digit)=digits.get_mut((digit_index-1) as usize){
let take_bits=most_significant_bit-((digit_index-1)<<DIGIT_SHIFT);
let rest_of_mantissa=-($mantissa_bits as i32-(take_bits as i32));
*digit=signed_shift(m,rest_of_mantissa);
}
}
let bits=BInt::from_bits(bnum::BUint::from_digits(digits));
Ok(if s{
Self::from_bits(bits.overflowing_neg().0)
}else{
Self::from_bits(bits)
})
},
}
}
}
}
}
impl_from_float!(integer_decode_f32,f32,24);
impl_from_float!(integer_decode_f64,f64,53);
impl<const N:usize,const F:usize> core::fmt::Display for Fixed<N,F>{
#[inline]
fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{
let float:f32=(*self).into();
core::write!(f,"{:.3}",float)
}
}
macro_rules! impl_additive_operator {
( $struct: ident, $trait: ident, $method: ident, $output: ty ) => {
impl<const N:usize,const F:usize> $struct<N,F>{
#[inline]
pub const fn $method(self, other: Self) -> Self {
Self::from_bits(self.bits.$method(other.bits))
}
}
impl<const N:usize,const F:usize> core::ops::$trait for $struct<N,F>{
type Output = $output;
#[inline]
fn $method(self, other: Self) -> Self::Output {
self.$method(other)
}
}
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
where
BInt::<N>:From<U>,
{
type Output = $output;
#[inline]
fn $method(self, other: U) -> Self::Output {
Self::from_bits(self.bits.$method(BInt::<N>::from(other).shl(F as u32)))
}
}
};
}
macro_rules! impl_additive_assign_operator {
( $struct: ident, $trait: ident, $method: ident ) => {
impl<const N:usize,const F:usize> core::ops::$trait for $struct<N,F>{
#[inline]
fn $method(&mut self, other: Self) {
self.bits.$method(other.bits);
}
}
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
where
BInt::<N>:From<U>,
{
#[inline]
fn $method(&mut self, other: U) {
self.bits.$method(BInt::<N>::from(other).shl(F as u32));
}
}
};
}
// Impl arithmetic pperators
impl_additive_assign_operator!( Fixed, AddAssign, add_assign );
impl_additive_operator!( Fixed, Add, add, Self );
impl_additive_assign_operator!( Fixed, SubAssign, sub_assign );
impl_additive_operator!( Fixed, Sub, sub, Self );
impl_additive_assign_operator!( Fixed, RemAssign, rem_assign );
impl_additive_operator!( Fixed, Rem, rem, Self );
// Impl bitwise operators
impl_additive_assign_operator!( Fixed, BitAndAssign, bitand_assign );
impl_additive_operator!( Fixed, BitAnd, bitand, Self );
impl_additive_assign_operator!( Fixed, BitOrAssign, bitor_assign );
impl_additive_operator!( Fixed, BitOr, bitor, Self );
impl_additive_assign_operator!( Fixed, BitXorAssign, bitxor_assign );
impl_additive_operator!( Fixed, BitXor, bitxor, Self );
// non-wide operators. The result is the same width as the inputs.
// This macro is not used in the default configuration.
#[allow(unused_macros)]
macro_rules! impl_multiplicative_operator_not_const_generic {
( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
impl<const F:usize> core::ops::$trait for $struct<$width,F>{
type Output = $output;
#[inline]
fn $method(self, other: Self) -> Self::Output {
paste::item!{
self.[<fixed_ $method>](other)
}
}
}
};
}
macro_rules! impl_multiplicative_assign_operator_not_const_generic {
( ($struct: ident, $trait: ident, $method: ident, $non_assign_method: ident ), $width:expr ) => {
impl<const F:usize> core::ops::$trait for $struct<$width,F>{
#[inline]
fn $method(&mut self, other: Self) {
paste::item!{
*self=self.[<fixed_ $non_assign_method>](other);
}
}
}
};
}
macro_rules! impl_multiply_operator_not_const_generic {
( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
impl<const F:usize> $struct<$width,F>{
paste::item!{
#[inline]
pub fn [<fixed_ $method>](self, rhs: Self) -> Self {
let (low,high)=self.bits.unsigned_abs().widening_mul(rhs.bits.unsigned_abs());
let out:BInt::<{$width*2}>=unsafe{core::mem::transmute([low,high])};
if self.is_negative()==rhs.is_negative(){
Self::from_bits(out.shr(F as u32).as_())
}else{
-Self::from_bits(out.shr(F as u32).as_())
}
}
}
}
#[cfg(not(feature="wide-mul"))]
impl_multiplicative_operator_not_const_generic!(($struct, $trait, $method, $output ), $width);
#[cfg(feature="deferred-division")]
impl ratio_ops::ratio::Divide<i64> for Fixed<$width,{$width*32}>{
type Output=Self;
#[inline]
fn divide(self, other: i64)->Self::Output{
Self::from_bits(self.bits.div_euclid(BInt::from(other)))
}
}
}
}
macro_rules! impl_divide_operator_not_const_generic {
( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
impl<const F:usize> $struct<$width,F>{
paste::item!{
#[inline]
pub fn [<fixed_ $method>](self,other:Self)->Self{
//this only needs to be $width+F as u32/64+1 but MUH CONST GENERICS!!!!!
let lhs=self.bits.as_::<BInt::<{$width*2}>>().shl(F as u32);
let rhs=other.bits.as_::<BInt::<{$width*2}>>();
Self::from_bits(lhs.div_euclid(rhs).as_())
}
}
}
#[cfg(all(not(feature="wide-mul"),not(feature="deferred-division")))]
impl_multiplicative_operator_not_const_generic!(($struct, $trait, $method, $output ), $width);
#[cfg(all(not(feature="wide-mul"),feature="deferred-division"))]
impl<const F:usize> ratio_ops::ratio::Divide for $struct<$width,F>{
type Output = $output;
#[inline]
fn divide(self, other: Self) -> Self::Output {
paste::item!{
self.[<fixed_ $method>](other)
}
}
}
};
}
macro_rules! impl_multiplicative_operator {
( $struct: ident, $trait: ident, $method: ident, $inner_method: ident, $output: ty ) => {
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
where
BInt::<N>:From<U>+core::ops::$trait,
{
type Output = $output;
#[inline]
fn $method(self,other:U)->Self::Output{
Self::from_bits(self.bits.$inner_method(BInt::<N>::from(other)))
}
}
};
}
macro_rules! impl_multiplicative_assign_operator {
( $struct: ident, $trait: ident, $method: ident, $not_assign_method: ident ) => {
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
where
BInt::<N>:From<U>+core::ops::$trait,
{
#[inline]
fn $method(&mut self,other:U){
self.bits=self.bits.$not_assign_method(BInt::<N>::from(other));
}
}
};
}
macro_rules! macro_repeated{
(
$macro:ident,
$any:tt,
$($repeated:tt),*
)=>{
$(
$macro!($any, $repeated);
)*
};
}
macro_rules! macro_16 {
( $macro: ident, $any:tt ) => {
macro_repeated!($macro,$any,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);
}
}
macro_16!( impl_multiplicative_assign_operator_not_const_generic, (Fixed, MulAssign, mul_assign, mul) );
macro_16!( impl_multiply_operator_not_const_generic, (Fixed, Mul, mul, Self) );
macro_16!( impl_multiplicative_assign_operator_not_const_generic, (Fixed, DivAssign, div_assign, div) );
macro_16!( impl_divide_operator_not_const_generic, (Fixed, Div, div, Self) );
impl_multiplicative_assign_operator!( Fixed, MulAssign, mul_assign, mul );
impl_multiplicative_operator!( Fixed, Mul, mul, mul, Self );
impl_multiplicative_assign_operator!( Fixed, DivAssign, div_assign, div_euclid );
impl_multiplicative_operator!( Fixed, Div, div, div_euclid, Self );
#[cfg(feature="deferred-division")]
impl<const LHS_N:usize,const LHS_F:usize,const RHS_N:usize,const RHS_F:usize> core::ops::Div<Fixed<RHS_N,RHS_F>> for Fixed<LHS_N,LHS_F>{
type Output=ratio_ops::ratio::Ratio<Fixed<LHS_N,LHS_F>,Fixed<RHS_N,RHS_F>>;
#[inline]
fn div(self, other: Fixed<RHS_N,RHS_F>)->Self::Output{
ratio_ops::ratio::Ratio::new(self,other)
}
}
#[cfg(feature="deferred-division")]
impl<const N:usize,const F:usize> ratio_ops::ratio::Parity for Fixed<N,F>{
fn parity(&self)->bool{
self.is_negative()
}
}
macro_rules! impl_shift_operator {
( $struct: ident, $trait: ident, $method: ident, $output: ty ) => {
impl<const N:usize,const F:usize> core::ops::$trait<u32> for $struct<N,F>{
type Output = $output;
#[inline]
fn $method(self, other: u32) -> Self::Output {
Self::from_bits(self.bits.$method(other))
}
}
};
}
macro_rules! impl_shift_assign_operator {
( $struct: ident, $trait: ident, $method: ident ) => {
impl<const N:usize,const F:usize> core::ops::$trait<u32> for $struct<N,F>{
#[inline]
fn $method(&mut self, other: u32) {
self.bits.$method(other);
}
}
};
}
impl_shift_assign_operator!( Fixed, ShlAssign, shl_assign );
impl_shift_operator!( Fixed, Shl, shl, Self );
impl_shift_assign_operator!( Fixed, ShrAssign, shr_assign );
impl_shift_operator!( Fixed, Shr, shr, Self );
// wide operators. The result width is the sum of the input widths, i.e. none of the multiplication
#[allow(unused_macros)]
macro_rules! impl_wide_operators{
($lhs:expr,$rhs:expr)=>{
impl core::ops::Mul<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>;
#[inline]
fn mul(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{
paste::item!{
self.[<wide_mul_ $lhs _ $rhs>](other)
}
}
}
#[cfg(not(feature="deferred-division"))]
impl core::ops::Div<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>;
#[inline]
fn div(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{
paste::item!{
self.[<wide_div_ $lhs _ $rhs>](other)
}
}
}
#[cfg(feature="deferred-division")]
impl ratio_ops::ratio::Divide<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>;
#[inline]
fn divide(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{
paste::item!{
self.[<wide_div_ $lhs _ $rhs>](other)
}
}
}
}
}
// WIDE MUL: multiply into a wider type
// let a = I32F32::ONE;
// let b:I64F64 = a.wide_mul(a);
macro_rules! impl_wide_not_const_generic{
(
(),
($lhs:expr,$rhs:expr)
)=>{
impl Fixed<$lhs,{$lhs*32}>
{
paste::item!{
#[inline]
pub fn [<wide_mul_ $lhs _ $rhs>](self,rhs:Fixed<$rhs,{$rhs*32}>)->Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>{
let lhs=self.bits.as_::<BInt<{$lhs+$rhs}>>();
let rhs=rhs.bits.as_::<BInt<{$lhs+$rhs}>>();
Fixed::from_bits(lhs*rhs)
}
/// This operation cannot represent the fraction exactly,
/// but it shapes the output to have precision for the
/// largest and smallest possible fractions.
#[inline]
pub fn [<wide_div_ $lhs _ $rhs>](self,rhs:Fixed<$rhs,{$rhs*32}>)->Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>{
// (lhs/2^LHS_FRAC)/(rhs/2^RHS_FRAC)
let lhs=self.bits.as_::<BInt<{$lhs+$rhs}>>().shl($rhs*64);
let rhs=rhs.bits.as_::<BInt<{$lhs+$rhs}>>();
Fixed::from_bits(lhs/rhs)
}
}
}
#[cfg(feature="wide-mul")]
impl_wide_operators!($lhs,$rhs);
};
}
macro_rules! impl_wide_same_size_not_const_generic{
(
(),
$width:expr
)=>{
impl Fixed<$width,{$width*32}>
{
paste::item!{
#[inline]
pub fn [<wide_mul_ $width _ $width>](self,rhs:Fixed<$width,{$width*32}>)->Fixed<{$width*2},{$width*2*32}>{
let (low,high)=self.bits.unsigned_abs().widening_mul(rhs.bits.unsigned_abs());
let out:BInt::<{$width*2}>=unsafe{core::mem::transmute([low,high])};
if self.is_negative()==rhs.is_negative(){
Fixed::from_bits(out)
}else{
// Normal neg is the cheapest negation operation
// And the inputs cannot reach the point where it matters
Fixed::from_bits(out.neg())
}
}
/// This operation cannot represent the fraction exactly,
/// but it shapes the output to have precision for the
/// largest and smallest possible fractions.
#[inline]
pub fn [<wide_div_ $width _ $width>](self,rhs:Fixed<$width,{$width*32}>)->Fixed<{$width*2},{$width*2*32}>{
// (lhs/2^LHS_FRAC)/(rhs/2^RHS_FRAC)
let lhs=self.bits.as_::<BInt<{$width*2}>>().shl($width*64);
let rhs=rhs.bits.as_::<BInt<{$width*2}>>();
Fixed::from_bits(lhs/rhs)
}
}
}
#[cfg(feature="wide-mul")]
impl_wide_operators!($width,$width);
};
}
//const generics sidestepped wahoo
macro_repeated!(
impl_wide_not_const_generic,(),
(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),
(1,2), (3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),
(1,3),(2,3), (4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),
(1,4),(2,4),(3,4), (5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),
(1,5),(2,5),(3,5),(4,5), (6,5),(7,5),(8,5),(9,5),(10,5),(11,5),
(1,6),(2,6),(3,6),(4,6),(5,6), (7,6),(8,6),(9,6),(10,6),
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7), (8,7),(9,7),
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8), (9,8),
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),
(1,11),(2,11),(3,11),(4,11),(5,11),
(1,12),(2,12),(3,12),(4,12),
(1,13),(2,13),(3,13),
(1,14),(2,14),
(1,15)
);
macro_repeated!(
impl_wide_same_size_not_const_generic,(),
1,2,3,4,5,6,7,8
);
pub trait Fix<Out>{
fn fix(self)->Out;
}
macro_rules! impl_fix_rhs_lt_lhs_not_const_generic{
(
(),
($lhs:expr,$rhs:expr)
)=>{
impl Fixed<$lhs,{$lhs*32}>
{
paste::item!{
#[inline]
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits.shr(($lhs-$rhs)*32)))
}
}
}
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
paste::item!{
self.[<fix_ $rhs>]()
}
}
}
}
}
macro_rules! impl_fix_lhs_lt_rhs_not_const_generic{
(
(),
($lhs:expr,$rhs:expr)
)=>{
impl Fixed<$lhs,{$lhs*32}>
{
paste::item!{
#[inline]
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits).shl(($rhs-$lhs)*32))
}
}
}
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
paste::item!{
self.[<fix_ $rhs>]()
}
}
}
}
}
macro_rules! impl_fix_lhs_eq_rhs_not_const_generic{
(
(),
($lhs:expr,$rhs:expr)
)=>{
impl Fixed<$lhs,{$lhs*32}>
{
paste::item!{
#[inline]
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
self
}
}
}
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
paste::item!{
self.[<fix_ $rhs>]()
}
}
}
}
}
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
macro_repeated!(
impl_fix_rhs_lt_lhs_not_const_generic,(),
(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),(17,1),
(3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2),
(4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3),
(5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4),
(6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5),
(7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6),
(8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),
(9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8),
(10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),
(11,10),(12,10),(13,10),(14,10),(15,10),(16,10),
(12,11),(13,11),(14,11),(15,11),(16,11),
(13,12),(14,12),(15,12),(16,12),
(14,13),(15,13),(16,13),
(15,14),(16,14),
(16,15)
);
macro_repeated!(
impl_fix_lhs_lt_rhs_not_const_generic,(),
(1,2),
(1,3),(2,3),
(1,4),(2,4),(3,4),
(1,5),(2,5),(3,5),(4,5),
(1,6),(2,6),(3,6),(4,6),(5,6),
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7),
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),
(1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),
(1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,12),
(1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),
(1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14),
(1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15),
(1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16)
);
macro_repeated!(
impl_fix_lhs_eq_rhs_not_const_generic,(),
(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10),(11,11),(12,12),(13,13),(14,14),(15,15),(16,16)
);
macro_rules! impl_not_const_generic{
($n:expr,$_2n:expr)=>{
impl Fixed<$n,{$n*32}>{
paste::item!{
#[inline]
pub fn sqrt_unchecked(self)->Self{
//1<<max_shift must be the minimum power of two which when squared is greater than self
//calculating max_shift:
//1. count "used" bits to the left of the decimal, not including the sign bit (so -1)
//2. divide by 2 via >>1 (sqrt-ish)
//3. add on fractional offset
//Voila
let used_bits=self.bits.bits() as i32-1-($n*32) as i32;
let max_shift=((used_bits>>1)+($n*32) as i32) as u32;
let mut result=Self::ZERO;
//resize self to match the wide mul output
let wide_self=self.[<fix_ $_2n>]();
//descend down the bits and check if flipping each bit would push the square over the input value
for shift in (0..=max_shift).rev(){
let new_result={
let mut bits=result.to_bits().to_bits();
bits.set_bit(shift,true);
Self::from_bits(BInt::from_bits(bits))
};
if new_result.[<wide_mul_ $n _ $n>](new_result)<=wide_self{
result=new_result;
}
}
result
}
}
#[inline]
pub fn sqrt(self)->Self{
if self<Self::ZERO{
panic!("Square root less than zero")
}else{
self.sqrt_unchecked()
}
}
#[inline]
pub fn sqrt_checked(self)->Option<Self>{
if self<Self::ZERO{
None
}else{
Some(self.sqrt_unchecked())
}
}
}
}
}
impl_not_const_generic!(1,2);
impl_not_const_generic!(2,4);
impl_not_const_generic!(3,6);
impl_not_const_generic!(4,8);
impl_not_const_generic!(5,10);
impl_not_const_generic!(6,12);
impl_not_const_generic!(7,14);
impl_not_const_generic!(8,16);

View File

@ -1,8 +0,0 @@
pub mod fixed;
pub mod types;
#[cfg(feature="zeroes")]
pub mod zeroes;
#[cfg(test)]
mod tests;

View File

@ -1,218 +0,0 @@
use crate::types::I32F32;
use crate::types::I256F256;
#[test]
fn you_can_add_numbers(){
let a=I256F256::from((3i128*2).pow(4));
assert_eq!(a+a,I256F256::from((3i128*2).pow(4)*2));
}
#[test]
fn to_f32(){
let a=I256F256::from(1)>>2;
let f:f32=a.into();
assert_eq!(f,0.25f32);
let f:f32=(-a).into();
assert_eq!(f,-0.25f32);
let a=I256F256::from(0);
let f:f32=(-a).into();
assert_eq!(f,0f32);
let a=I256F256::from(237946589723468975i64)<<16;
let f:f32=a.into();
assert_eq!(f,237946589723468975f32*2.0f32.powi(16));
}
#[test]
fn to_f64(){
let a=I256F256::from(1)>>2;
let f:f64=a.into();
assert_eq!(f,0.25f64);
let f:f64=(-a).into();
assert_eq!(f,-0.25f64);
let a=I256F256::from(0);
let f:f64=(-a).into();
assert_eq!(f,0f64);
let a=I256F256::from(237946589723468975i64)<<16;
let f:f64=a.into();
assert_eq!(f,237946589723468975f64*2.0f64.powi(16));
}
#[test]
fn from_f32(){
let a=I256F256::from(1)>>2;
let b:Result<I256F256,_>=0.25f32.try_into();
assert_eq!(b,Ok(a));
let a=I256F256::from(-1)>>2;
let b:Result<I256F256,_>=(-0.25f32).try_into();
assert_eq!(b,Ok(a));
let a=I256F256::from(0);
let b:Result<I256F256,_>=0.try_into();
assert_eq!(b,Ok(a));
let a=I256F256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
let b:Result<I256F256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f32*2.0f32.powi(16)).try_into();
assert_eq!(b,Ok(a));
//I32F32::MAX into f32 is truncated into this value
let a=I32F32::raw(0b111111111111111111111111000000000000000000000000000000000000000i64);
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MAX).try_into();
assert_eq!(b,Ok(a));
//I32F32::MIN hits a special case since it's not representable as a positive signed integer
//TODO: don't return an overflow because this is technically possible
let a=I32F32::MIN;
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MIN).try_into();
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
//16 is within the 24 bits of float precision
let b:Result<I32F32,_>=Into::<f32>::into(-I32F32::MIN.fix_2()).try_into();
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
let b:Result<I32F32,_>=f32::MIN_POSITIVE.try_into();
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Underflow));
//test many cases
for i in 0..64{
let a=crate::fixed::Fixed::<2,64>::raw_digit(0b111111111111111111111111000000000000000000000000000000000000000i64)<<i;
let f:f32=a.into();
let b:Result<crate::fixed::Fixed<2,64>,_>=f.try_into();
assert_eq!(b,Ok(a));
}
}
#[test]
fn from_f64(){
let a=I256F256::from(1)>>2;
let b:Result<I256F256,_>=0.25f64.try_into();
assert_eq!(b,Ok(a));
let a=I256F256::from(-1)>>2;
let b:Result<I256F256,_>=(-0.25f64).try_into();
assert_eq!(b,Ok(a));
let a=I256F256::from(0);
let b:Result<I256F256,_>=0.try_into();
assert_eq!(b,Ok(a));
let a=I256F256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
let b:Result<I256F256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f64*2.0f64.powi(16)).try_into();
assert_eq!(b,Ok(a));
}
#[test]
fn you_can_shr_numbers(){
let a=I32F32::from(4);
assert_eq!(a>>1,I32F32::from(2));
}
#[test]
fn test_wide_mul(){
let a=I32F32::ONE;
let aa=a.wide_mul_1_1(a);
assert_eq!(aa,crate::types::I64F64::ONE);
}
#[test]
fn test_wide_div(){
let a=I32F32::ONE*4;
let b=I32F32::ONE*2;
let wide_a=a.wide_mul_1_1(I32F32::ONE);
let wide_b=b.wide_mul_1_1(I32F32::ONE);
let ab=a.wide_div_1_1(b);
assert_eq!(ab,crate::types::I64F64::ONE*2);
let wab=wide_a.wide_div_2_1(b);
assert_eq!(wab,crate::fixed::Fixed::<3,96>::ONE*2);
let awb=a.wide_div_1_2(wide_b);
assert_eq!(awb,crate::fixed::Fixed::<3,96>::ONE*2);
}
#[test]
fn test_wide_mul_repeated() {
let a=I32F32::from(2);
let b=I32F32::from(3);
let w1=a.wide_mul_1_1(b);
let w2=w1.wide_mul_2_2(w1);
let w3=w2.wide_mul_4_4(w2);
assert_eq!(w3,I256F256::from((3i128*2).pow(4)));
}
#[test]
fn test_bint(){
let a=I32F32::ONE;
assert_eq!(a*2,I32F32::from(2));
}
#[test]
fn test_fix(){
assert_eq!(I32F32::ONE.fix_8(),I256F256::ONE);
assert_eq!(I32F32::ONE,I256F256::ONE.fix_1());
assert_eq!(I32F32::NEG_ONE.fix_8(),I256F256::NEG_ONE);
assert_eq!(I32F32::NEG_ONE,I256F256::NEG_ONE.fix_1());
}
#[test]
fn test_sqrt(){
let a=I32F32::ONE*4;
assert_eq!(a.sqrt(),I32F32::from(2));
}
#[test]
fn test_sqrt_zero(){
let a=I32F32::ZERO;
assert_eq!(a.sqrt(),I32F32::ZERO);
}
#[test]
fn test_sqrt_low(){
let a=I32F32::HALF;
let b=a.fixed_mul(a);
assert_eq!(b.sqrt(),a);
}
fn find_equiv_sqrt_via_f64(n:I32F32)->I32F32{
//GIMME THEM BITS BOY
let &[bits]=n.to_bits().to_bits().digits();
let ibits=bits as i64;
let f=(ibits as f64)/((1u64<<32) as f64);
let f_ans=f.sqrt();
let i=(f_ans*((1u64<<32) as f64)) as i64;
let r=I32F32::from_bits(bnum::BInt::<1>::from(i));
//mimic the behaviour of the algorithm,
//return the result if it truncates to the exact answer
if (r+I32F32::EPSILON).wide_mul_1_1(r+I32F32::EPSILON)==n.wide_mul_1_1(I32F32::ONE){
return r+I32F32::EPSILON;
}
if (r-I32F32::EPSILON).wide_mul_1_1(r-I32F32::EPSILON)==n.wide_mul_1_1(I32F32::ONE){
return r-I32F32::EPSILON;
}
return r;
}
fn test_exact(n:I32F32){
assert_eq!(n.sqrt(),find_equiv_sqrt_via_f64(n));
}
#[test]
fn test_sqrt_exact(){
//43
for i in 0..((i64::MAX as f32).ln() as u32){
let n=I32F32::from_bits(bnum::BInt::<1>::from((i as f32).exp() as i64));
test_exact(n);
}
}
#[test]
fn test_sqrt_max(){
let a=I32F32::MAX;
test_exact(a);
}
#[test]
#[cfg(all(feature="zeroes",not(feature="deferred-division")))]
fn test_zeroes_normal(){
// (x-1)*(x+1)
// x^2-1
let zeroes=I32F32::zeroes2(I32F32::NEG_ONE,I32F32::ZERO,I32F32::ONE);
assert_eq!(zeroes,arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE,I32F32::ONE]));
let zeroes=I32F32::zeroes2(I32F32::NEG_ONE*3,I32F32::ONE*2,I32F32::ONE);
assert_eq!(zeroes,arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE*3,I32F32::ONE]));
}
#[test]
#[cfg(all(feature="zeroes",feature="deferred-division"))]
fn test_zeroes_deferred_division(){
// (x-1)*(x+1)
// x^2-1
let zeroes=I32F32::zeroes2(I32F32::NEG_ONE,I32F32::ZERO,I32F32::ONE);
assert_eq!(
zeroes,
arrayvec::ArrayVec::from_iter([
ratio_ops::ratio::Ratio::new(I32F32::ONE*2,I32F32::NEG_ONE*2),
ratio_ops::ratio::Ratio::new(I32F32::ONE*2,I32F32::ONE*2),
])
);
}

View File

@ -1,4 +0,0 @@
pub type I32F32=crate::fixed::Fixed<1,32>;
pub type I64F64=crate::fixed::Fixed<2,64>;
pub type I128F128=crate::fixed::Fixed<4,128>;
pub type I256F256=crate::fixed::Fixed<8,256>;

View File

@ -1,53 +0,0 @@
use crate::fixed::Fixed;
use arrayvec::ArrayVec;
use std::cmp::Ordering;
macro_rules! impl_zeroes{
($n:expr)=>{
impl Fixed<$n,{$n*32}>{
#[inline]
pub fn zeroes2(a0:Self,a1:Self,a2:Self)->ArrayVec<<Self as core::ops::Div>::Output,2>{
let a2pos=match a2.cmp(&Self::ZERO){
Ordering::Greater=>true,
Ordering::Equal=>return ArrayVec::from_iter(Self::zeroes1(a0,a1).into_iter()),
Ordering::Less=>false,
};
let radicand=a1*a1-a2*a0*4;
match radicand.cmp(&<Self as core::ops::Mul>::Output::ZERO){
Ordering::Greater=>{
paste::item!{
let planar_radicand=radicand.sqrt().[<fix_ $n>]();
}
//sort roots ascending and avoid taking the difference of large numbers
let zeroes=match (a2pos,Self::ZERO<a1){
(true, true )=>[(-a1-planar_radicand)/(a2*2),(a0*2)/(-a1-planar_radicand)],
(true, false)=>[(a0*2)/(-a1+planar_radicand),(-a1+planar_radicand)/(a2*2)],
(false,true )=>[(a0*2)/(-a1-planar_radicand),(-a1-planar_radicand)/(a2*2)],
(false,false)=>[(-a1+planar_radicand)/(a2*2),(a0*2)/(-a1+planar_radicand)],
};
ArrayVec::from_iter(zeroes)
},
Ordering::Equal=>ArrayVec::from_iter([(a1)/(a2*-2)]),
Ordering::Less=>ArrayVec::new_const(),
}
}
#[inline]
pub fn zeroes1(a0:Self,a1:Self)->ArrayVec<<Self as core::ops::Div>::Output,1>{
if a1==Self::ZERO{
ArrayVec::new_const()
}else{
ArrayVec::from_iter([(-a0)/(a1)])
}
}
}
};
}
impl_zeroes!(1);
impl_zeroes!(2);
impl_zeroes!(3);
impl_zeroes!(4);
//sqrt doubles twice!
//impl_zeroes!(5);
//impl_zeroes!(6);
//impl_zeroes!(7);
//impl_zeroes!(8);

View File

@ -1 +0,0 @@
/target

View File

@ -1,22 +0,0 @@
[package]
name = "linear_ops"
version = "0.1.0"
edition = "2021"
repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0"
description = "Vector/Matrix operations using trait bounds."
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[features]
default=["named-fields","fixed-wide"]
named-fields=[]
fixed-wide=["dep:fixed_wide","dep:paste"]
deferred-division=["dep:ratio_ops"]
[dependencies]
ratio_ops = { path = "../ratio_ops", registry = "strafesnet", optional = true }
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", optional = true }
paste = { version = "1.0.15", optional = true }
[dev-dependencies]
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", features = ["wide-mul"] }

View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -1,23 +0,0 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -1,10 +0,0 @@
mod macros;
pub mod types;
pub mod vector;
pub mod matrix;
#[cfg(feature="named-fields")]
mod named;
#[cfg(test)]
mod tests;

View File

@ -1 +0,0 @@

View File

@ -1,79 +0,0 @@
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_fixed_wide_vector_not_const_generic {
(
(),
$n:expr
) => {
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$n,{$n*32}>>{
#[inline]
pub fn length(self)-><fixed_wide::fixed::Fixed::<$n,{$n*32}> as core::ops::Mul>::Output{
self.length_squared().sqrt_unchecked()
}
#[inline]
pub fn with_length<U,V>(self,length:U)-><Vector<N,V> as core::ops::Div<<fixed_wide::fixed::Fixed::<$n,{$n*32}> as core::ops::Mul>::Output>>::Output
where
fixed_wide::fixed::Fixed<$n,{$n*32}>:core::ops::Mul<U,Output=V>,
U:Copy,
V:core::ops::Div<<fixed_wide::fixed::Fixed::<$n,{$n*32}> as core::ops::Mul>::Output>,
{
self*length/self.length()
}
}
};
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! macro_4 {
( $macro: ident, $any:tt ) => {
$crate::macro_repeated!($macro,$any,1,2,3,4);
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_fixed_wide_vector {
() => {
$crate::macro_4!(impl_fixed_wide_vector_not_const_generic,());
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
$crate::macro_repeated!(
impl_fix_not_const_generic,(),
(1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),
(1,2),(2,2),(3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2),
(1,3),(2,3),(3,3),(4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3),
(1,4),(2,4),(3,4),(4,4),(5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4),
(1,5),(2,5),(3,5),(4,5),(5,5),(6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5),
(1,6),(2,6),(3,6),(4,6),(5,6),(6,6),(7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6),
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7),(7,7),(8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),(8,8),(9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8),
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),(9,9),(10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),(10,10),(11,10),(12,10),(13,10),(14,10),(15,10),(16,10),
(1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),(11,11),(12,11),(13,11),(14,11),(15,11),(16,11),
(1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,12),(12,12),(13,12),(14,12),(15,12),(16,12),
(1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),(13,13),(14,13),(15,13),(16,13),
(1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14),(14,14),(15,14),(16,14),
(1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15),(15,15),(16,15),
(1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16),(16,16)
);
};
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_fix_not_const_generic{
(
(),
($lhs:expr,$rhs:expr)
)=>{
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$lhs,{$lhs*32}>>
{
paste::item!{
#[inline]
pub fn [<fix_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
self.map(|t|t.[<fix_ $rhs>]())
}
}
}
}
}

View File

@ -1,272 +0,0 @@
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_matrix {
() => {
impl<const X:usize,const Y:usize,T> Matrix<X,Y,T>{
#[inline(always)]
pub const fn new(array:[[T;Y];X])->Self{
Self{array}
}
#[inline(always)]
pub fn to_array(self)->[[T;Y];X]{
self.array
}
#[inline]
pub fn from_cols(cols:[Vector<Y,T>;X])->Self
{
Matrix::new(
cols.map(|col|col.array),
)
}
#[inline]
pub fn map<F,U>(self,f:F)->Matrix<X,Y,U>
where
F:Fn(T)->U
{
Matrix::new(
self.array.map(|inner|inner.map(&f)),
)
}
#[inline]
pub fn transpose(self)->Matrix<Y,X,T>{
//how did I think of this
let mut array_of_iterators=self.array.map(|axis|axis.into_iter());
Matrix::new(
core::array::from_fn(|_|
array_of_iterators.each_mut().map(|iter|
iter.next().unwrap()
)
)
)
}
#[inline]
// old (list of rows) MatY<VecX>.MatX<VecZ> = MatY<VecZ>
// new (list of columns) MatX<VecY>.MatZ<VecX> = MatZ<VecY>
pub fn dot<const Z:usize,U,V>(self,rhs:Matrix<Z,X,U>)->Matrix<Z,Y,V>
where
T:core::ops::Mul<U,Output=V>+Copy,
V:core::iter::Sum,
U:Copy,
{
let mut array_of_iterators=self.array.map(|axis|axis.into_iter().cycle());
Matrix{
array:rhs.array.map(|rhs_axis|
core::array::from_fn(|_|
array_of_iterators
.iter_mut()
.zip(rhs_axis.iter())
.map(|(lhs_iter,&rhs_value)|
lhs_iter.next().unwrap()*rhs_value
).sum()
)
)
}
}
#[inline]
// MatX<VecY>.VecY = VecX
pub fn transform_vector<U,V>(self,rhs:Vector<X,U>)->Vector<Y,V>
where
T:core::ops::Mul<U,Output=V>,
V:core::iter::Sum,
U:Copy,
{
let mut array_of_iterators=self.array.map(|axis|axis.into_iter());
Vector::new(
core::array::from_fn(|_|
array_of_iterators
.iter_mut()
.zip(rhs.array.iter())
.map(|(lhs_iter,&rhs_value)|
lhs_iter.next().unwrap()*rhs_value
).sum()
)
)
}
}
impl<const X:usize,const Y:usize,T> Matrix<X,Y,T>
where
T:Copy
{
#[inline(always)]
pub const fn from_value(value:T)->Self{
Self::new([[value;Y];X])
}
}
impl<const X:usize,const Y:usize,T:Default> Default for Matrix<X,Y,T>{
#[inline]
fn default()->Self{
Self::new(
core::array::from_fn(|_|core::array::from_fn(|_|Default::default()))
)
}
}
impl<const X:usize,const Y:usize,T:core::fmt::Display> core::fmt::Display for Matrix<X,Y,T>{
#[inline]
fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{
for col in &self.array[0..X]{
core::write!(f,"\n")?;
for elem in &col[0..Y-1]{
core::write!(f,"{}, ",elem)?;
}
// assume we will be using matrices of size 1x1 or greater
core::write!(f,"{}",col.last().unwrap())?;
}
Ok(())
}
}
impl<const X:usize,const Y:usize,const Z:usize,T,U,V> core::ops::Mul<Matrix<Z,X,U>> for Matrix<X,Y,T>
where
T:core::ops::Mul<U,Output=V>+Copy,
V:core::iter::Sum,
U:Copy,
{
type Output=Matrix<Z,Y,V>;
#[inline]
fn mul(self,rhs:Matrix<Z,X,U>)->Self::Output{
self.dot(rhs)
}
}
impl<const X:usize,const Y:usize,T,U,V> core::ops::Mul<Vector<X,U>> for Matrix<X,Y,T>
where
T:core::ops::Mul<U,Output=V>,
V:core::iter::Sum,
U:Copy,
{
type Output=Vector<Y,V>;
#[inline]
fn mul(self,rhs:Vector<X,U>)->Self::Output{
self.transform_vector(rhs)
}
}
#[cfg(feature="deferred-division")]
$crate::impl_matrix_deferred_division!();
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_matrix_deferred_division {
() => {
impl<const X:usize,const Y:usize,T:ratio_ops::ratio::Divide<U,Output=V>,U:Copy,V> ratio_ops::ratio::Divide<U> for Matrix<X,Y,T>{
type Output=Matrix<X,Y,V>;
#[inline]
fn divide(self,rhs:U)->Self::Output{
self.map(|t|t.divide(rhs))
}
}
impl<const X:usize,const Y:usize,T,U> core::ops::Div<U> for Matrix<X,Y,T>{
type Output=ratio_ops::ratio::Ratio<Matrix<X,Y,T>,U>;
#[inline]
fn div(self,rhs:U)->Self::Output{
ratio_ops::ratio::Ratio::new(self,rhs)
}
}
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_matrix_extend {
( $x: expr, $y: expr ) => {
impl<T> Matrix<$x,$y,T>{
#[inline]
pub fn extend_column(self,value:Vector<$y,T>)->Matrix<{$x+1},$y,T>{
let mut iter=self.array.into_iter().chain(core::iter::once(value.array));
Matrix::new(
core::array::from_fn(|_|iter.next().unwrap()),
)
}
#[inline]
pub fn extend_row(self,value:Vector<$x,T>)->Matrix<$x,{$y+1},T>{
let mut iter_rows=value.array.into_iter();
Matrix::new(
self.array.map(|axis|{
let mut elements_iter=axis.into_iter().chain(core::iter::once(iter_rows.next().unwrap()));
core::array::from_fn(|_|elements_iter.next().unwrap())
})
)
}
}
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_matrix_named_fields_shape {
(
($struct_outer:ident, $size_outer: expr),
($size_inner: expr)
) => {
impl<T> core::ops::Deref for Matrix<$size_outer,$size_inner,T>{
type Target=$struct_outer<Vector<$size_inner,T>>;
#[inline]
fn deref(&self)->&Self::Target{
unsafe{core::mem::transmute(&self.array)}
}
}
impl<T> core::ops::DerefMut for Matrix<$size_outer,$size_inner,T>{
#[inline]
fn deref_mut(&mut self)->&mut Self::Target{
unsafe{core::mem::transmute(&mut self.array)}
}
}
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_matrix_named_fields_shape_shim {
(
($($vector_info:tt),+),
$matrix_info:tt
) => {
$crate::macro_repeated!(impl_matrix_named_fields_shape,$matrix_info,$($vector_info),+);
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_matrix_named_fields {
(
($($matrix_info:tt),+),
$vector_infos:tt
) => {
$crate::macro_repeated!(impl_matrix_named_fields_shape_shim,$vector_infos,$($matrix_info),+);
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_matrix_3x3 {
()=>{
impl<T,T2,T3> Matrix<3,3,T>
where
//cross
T:core::ops::Mul<T,Output=T2>+Copy,
T2:core::ops::Sub,
//dot
T:core::ops::Mul<<T2 as core::ops::Sub>::Output,Output=T3>,
T3:core::iter::Sum,
{
pub fn det(self)->T3{
self.x_axis.dot(self.y_axis.cross(self.z_axis))
}
}
impl<T,T2> Matrix<3,3,T>
where
T:core::ops::Mul<T,Output=T2>+Copy,
T2:core::ops::Sub,
{
pub fn adjugate(self)->Matrix<3,3,<T2 as core::ops::Sub>::Output>{
Matrix::new([
[self.y_axis.y*self.z_axis.z-self.y_axis.z*self.z_axis.y,self.x_axis.z*self.z_axis.y-self.x_axis.y*self.z_axis.z,self.x_axis.y*self.y_axis.z-self.x_axis.z*self.y_axis.y],
[self.y_axis.z*self.z_axis.x-self.y_axis.x*self.z_axis.z,self.x_axis.x*self.z_axis.z-self.x_axis.z*self.z_axis.x,self.x_axis.z*self.y_axis.x-self.x_axis.x*self.y_axis.z],
[self.y_axis.x*self.z_axis.y-self.y_axis.y*self.z_axis.x,self.x_axis.y*self.z_axis.x-self.x_axis.x*self.z_axis.y,self.x_axis.x*self.y_axis.y-self.x_axis.y*self.y_axis.x],
])
}
}
}
}

View File

@ -1,20 +0,0 @@
pub mod common;
pub mod vector;
pub mod matrix;
#[cfg(feature="fixed-wide")]
pub mod fixed_wide;
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! macro_repeated{
(
$macro:ident,
$any:tt,
$($repeated:tt),*
)=>{
$(
$crate::$macro!($any, $repeated);
)*
};
}

View File

@ -1,357 +0,0 @@
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_vector {
() => {
impl<const N:usize,T> Vector<N,T>{
#[inline(always)]
pub const fn new(array:[T;N])->Self{
Self{array}
}
#[inline(always)]
pub fn to_array(self)->[T;N]{
self.array
}
#[inline]
pub fn map<F,U>(self,f:F)->Vector<N,U>
where
F:Fn(T)->U
{
Vector::new(
self.array.map(f)
)
}
#[inline]
pub fn map_zip<F,U,V>(self,other:Vector<N,U>,f:F)->Vector<N,V>
where
F:Fn((T,U))->V,
{
let mut iter=self.array.into_iter().zip(other.array);
Vector::new(
core::array::from_fn(|_|f(iter.next().unwrap())),
)
}
}
impl<const N:usize,T:Copy> Vector<N,T>{
#[inline(always)]
pub const fn from_value(value:T)->Self{
Self::new([value;N])
}
}
impl<const N:usize,T:Default> Default for Vector<N,T>{
#[inline]
fn default()->Self{
Self::new(
core::array::from_fn(|_|Default::default())
)
}
}
impl<const N:usize,T:core::fmt::Display> core::fmt::Display for Vector<N,T>{
#[inline]
fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{
for elem in &self.array[0..N-1]{
core::write!(f,"{}, ",elem)?;
}
// assume we will be using vectors of length 1 or greater
core::write!(f,"{}",self.array.last().unwrap())
}
}
impl<const N:usize,T:Ord> Vector<N,T>{
#[inline]
pub fn min(self,rhs:Self)->Self{
self.map_zip(rhs,|(a,b)|a.min(b))
}
#[inline]
pub fn max(self,rhs:Self)->Self{
self.map_zip(rhs,|(a,b)|a.max(b))
}
#[inline]
pub fn cmp(self,rhs:Self)->Vector<N,core::cmp::Ordering>{
self.map_zip(rhs,|(a,b)|a.cmp(&b))
}
#[inline]
pub fn lt(self,rhs:Self)->Vector<N,bool>{
self.map_zip(rhs,|(a,b)|a.lt(&b))
}
#[inline]
pub fn gt(self,rhs:Self)->Vector<N,bool>{
self.map_zip(rhs,|(a,b)|a.gt(&b))
}
#[inline]
pub fn ge(self,rhs:Self)->Vector<N,bool>{
self.map_zip(rhs,|(a,b)|a.ge(&b))
}
#[inline]
pub fn le(self,rhs:Self)->Vector<N,bool>{
self.map_zip(rhs,|(a,b)|a.le(&b))
}
}
impl<const N:usize> Vector<N,bool>{
#[inline]
pub fn all(&self)->bool{
self.array==[true;N]
}
#[inline]
pub fn any(&self)->bool{
self.array!=[false;N]
}
}
impl<const N:usize,T:core::ops::Neg<Output=V>,V> core::ops::Neg for Vector<N,T>{
type Output=Vector<N,V>;
#[inline]
fn neg(self)->Self::Output{
Vector::new(
self.array.map(|t|-t)
)
}
}
impl<const N:usize,T> Vector<N,T>
{
#[inline]
pub fn dot<U,V>(self,rhs:Vector<N,U>)->V
where
T:core::ops::Mul<U,Output=V>,
V:core::iter::Sum,
{
self.array.into_iter().zip(rhs.array).map(|(a,b)|a*b).sum()
}
}
impl<const N:usize,T,V> Vector<N,T>
where
T:core::ops::Mul<Output=V>+Copy,
V:core::iter::Sum,
{
#[inline]
pub fn length_squared(self)->V{
self.array.into_iter().map(|t|t*t).sum()
}
}
// Impl arithmetic operators
$crate::impl_vector_assign_operator!(AddAssign, add_assign );
$crate::impl_vector_operator!(Add, add );
$crate::impl_vector_assign_operator!(SubAssign, sub_assign );
$crate::impl_vector_operator!(Sub, sub );
$crate::impl_vector_assign_operator!(RemAssign, rem_assign );
$crate::impl_vector_operator!(Rem, rem );
// mul and div are special, usually you multiply by a scalar
// and implementing both vec*vec and vec*scalar is conflicting implementations Q_Q
$crate::impl_vector_assign_operator_scalar!(MulAssign, mul_assign );
$crate::impl_vector_operator_scalar!(Mul, mul );
$crate::impl_vector_assign_operator_scalar!(DivAssign, div_assign );
#[cfg(not(feature="deferred-division"))]
$crate::impl_vector_operator_scalar!(Div, div );
#[cfg(feature="deferred-division")]
$crate::impl_vector_deferred_division!();
// Impl bitwise operators
$crate::impl_vector_assign_operator!(BitAndAssign, bitand_assign );
$crate::impl_vector_operator!(BitAnd, bitand );
$crate::impl_vector_assign_operator!(BitOrAssign, bitor_assign );
$crate::impl_vector_operator!(BitOr, bitor );
$crate::impl_vector_assign_operator!(BitXorAssign, bitxor_assign );
$crate::impl_vector_operator!(BitXor, bitxor );
// Impl shift operators
$crate::impl_vector_shift_assign_operator!(ShlAssign, shl_assign);
$crate::impl_vector_shift_operator!(Shl, shl);
$crate::impl_vector_shift_assign_operator!(ShrAssign, shr_assign);
$crate::impl_vector_shift_operator!(Shr, shr);
// dedicated methods for this type
#[cfg(feature="fixed-wide")]
$crate::impl_fixed_wide_vector!();
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_vector_deferred_division {
() => {
impl<const N:usize,T:ratio_ops::ratio::Divide<U,Output=V>,U:Copy,V> ratio_ops::ratio::Divide<U> for Vector<N,T>{
type Output=Vector<N,V>;
#[inline]
fn divide(self,rhs:U)->Self::Output{
self.map(|t|t.divide(rhs))
}
}
impl<const N:usize,T,U> core::ops::Div<U> for Vector<N,T>{
type Output=ratio_ops::ratio::Ratio<Vector<N,T>,U>;
#[inline]
fn div(self,rhs:U)->Self::Output{
ratio_ops::ratio::Ratio::new(self,rhs)
}
}
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_vector_operator_scalar {
($trait: ident, $method: ident ) => {
impl<const N:usize,T:core::ops::$trait<U,Output=V>,U:Copy,V> core::ops::$trait<U> for Vector<N,T>{
type Output=Vector<N,V>;
#[inline]
fn $method(self,rhs:U)->Self::Output{
self.map(|t|t.$method(rhs))
}
}
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_vector_operator {
($trait: ident, $method: ident ) => {
impl<const N:usize,T:core::ops::$trait<U,Output=V>,U,V> core::ops::$trait<Vector<N,U>> for Vector<N,T>{
type Output=Vector<N,V>;
#[inline]
fn $method(self,rhs:Vector<N,U>)->Self::Output{
self.map_zip(rhs,|(a,b)|a.$method(b))
}
}
impl<const N:usize,T:core::ops::$trait<i64,Output=T>> core::ops::$trait<i64> for Vector<N,T>{
type Output=Self;
#[inline]
fn $method(self,rhs:i64)->Self::Output{
self.map(|t|t.$method(rhs))
}
}
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_vector_assign_operator_scalar {
($trait: ident, $method: ident ) => {
impl<const N:usize,T:core::ops::$trait<U>,U:Copy> core::ops::$trait<U> for Vector<N,T>{
#[inline]
fn $method(&mut self,rhs:U){
self.array.iter_mut()
.for_each(|t|t.$method(rhs))
}
}
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_vector_assign_operator {
($trait: ident, $method: ident ) => {
impl<const N:usize,T:core::ops::$trait<U>,U> core::ops::$trait<Vector<N,U>> for Vector<N,T>{
#[inline]
fn $method(&mut self,rhs:Vector<N,U>){
self.array.iter_mut().zip(rhs.array)
.for_each(|(a,b)|a.$method(b))
}
}
impl<const N:usize,T:core::ops::$trait<i64>> core::ops::$trait<i64> for Vector<N,T>{
#[inline]
fn $method(&mut self,rhs:i64){
self.array.iter_mut()
.for_each(|t|t.$method(rhs))
}
}
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_vector_shift_operator {
($trait: ident, $method: ident ) => {
impl<const N:usize,T:core::ops::$trait<U,Output=V>,U,V> core::ops::$trait<Vector<N,U>> for Vector<N,T>{
type Output=Vector<N,V>;
#[inline]
fn $method(self,rhs:Vector<N,U>)->Self::Output{
self.map_zip(rhs,|(a,b)|a.$method(b))
}
}
impl<const N:usize,T:core::ops::$trait<u32,Output=V>,V> core::ops::$trait<u32> for Vector<N,T>{
type Output=Vector<N,V>;
#[inline]
fn $method(self,rhs:u32)->Self::Output{
self.map(|t|t.$method(rhs))
}
}
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_vector_shift_assign_operator {
($trait: ident, $method: ident ) => {
impl<const N:usize,T:core::ops::$trait<U>,U> core::ops::$trait<Vector<N,U>> for Vector<N,T>{
#[inline]
fn $method(&mut self,rhs:Vector<N,U>){
self.array.iter_mut().zip(rhs.array)
.for_each(|(a,b)|a.$method(b))
}
}
impl<const N:usize,T:core::ops::$trait<u32>> core::ops::$trait<u32> for Vector<N,T>{
#[inline]
fn $method(&mut self,rhs:u32){
self.array.iter_mut()
.for_each(|t|t.$method(rhs))
}
}
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_vector_extend {
( $size: expr ) => {
impl<T> Vector<$size,T>{
#[inline]
pub fn extend(self,value:T)->Vector<{$size+1},T>{
let mut iter=self.array.into_iter().chain(core::iter::once(value));
Vector::new(
core::array::from_fn(|_|iter.next().unwrap()),
)
}
}
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_vector_named_fields {
( $struct:ident, $size: expr ) => {
impl<T> core::ops::Deref for Vector<$size,T>{
type Target=$struct<T>;
#[inline]
fn deref(&self)->&Self::Target{
unsafe{core::mem::transmute(&self.array)}
}
}
impl<T> core::ops::DerefMut for Vector<$size,T>{
#[inline]
fn deref_mut(&mut self)->&mut Self::Target{
unsafe{core::mem::transmute(&mut self.array)}
}
}
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_vector_3 {
()=>{
impl<T> Vector<3,T>
{
#[inline]
pub fn cross<U,V>(self,rhs:Vector<3,U>)->Vector<3,<V as core::ops::Sub>::Output>
where
T:core::ops::Mul<U,Output=V>+Copy,
U:Copy,
V:core::ops::Sub,
{
Vector::new([
self.y*rhs.z-self.z*rhs.y,
self.z*rhs.x-self.x*rhs.z,
self.x*rhs.y-self.y*rhs.x,
])
}
}
}
}

View File

@ -1,17 +0,0 @@
use crate::vector::Vector;
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
pub struct Matrix<const X:usize,const Y:usize,T>{
pub(crate) array:[[T;Y];X],
}
crate::impl_matrix!();
crate::impl_matrix_extend!(2,2);
crate::impl_matrix_extend!(2,3);
crate::impl_matrix_extend!(3,2);
crate::impl_matrix_extend!(3,3);
//Special case 3x3 matrix operations because I cba to write macros for the arbitrary cases
#[cfg(feature="named-fields")]
crate::impl_matrix_3x3!();

View File

@ -1,59 +0,0 @@
use crate::vector::Vector;
use crate::matrix::Matrix;
#[repr(C)]
pub struct Vector2<T> {
pub x: T,
pub y: T,
}
#[repr(C)]
pub struct Vector3<T> {
pub x: T,
pub y: T,
pub z: T,
}
#[repr(C)]
pub struct Vector4<T> {
pub x: T,
pub y: T,
pub z: T,
pub w: T,
}
crate::impl_vector_named_fields!(Vector2, 2);
crate::impl_vector_named_fields!(Vector3, 3);
crate::impl_vector_named_fields!(Vector4, 4);
#[repr(C)]
pub struct Matrix2<T> {
pub x_axis: T,
pub y_axis: T,
}
#[repr(C)]
pub struct Matrix3<T> {
pub x_axis: T,
pub y_axis: T,
pub z_axis: T,
}
#[repr(C)]
pub struct Matrix4<T> {
pub x_axis: T,
pub y_axis: T,
pub z_axis: T,
pub w_axis: T,
}
crate::impl_matrix_named_fields!(
//outer struct
(
(Matrix2, 2),
(Matrix3, 3),
(Matrix4, 4)
),
//inner struct
(
(2),
(3),
(4)
)
);

View File

@ -1,96 +0,0 @@
use crate::types::{Matrix3,Matrix3x2,Matrix3x4,Matrix4x2,Vector3};
type Planar64=fixed_wide::types::I32F32;
type Planar64Wide1=fixed_wide::types::I64F64;
//type Planar64Wide2=fixed_wide::types::I128F128;
type Planar64Wide3=fixed_wide::types::I256F256;
#[test]
fn wide_vec3(){
let v=Vector3::from_value(Planar64::from(3));
let v1=v*v.x;
let v2=v1*v1.y;
let v3=v2*v2.z;
assert_eq!(v3.array,Vector3::from_value(Planar64Wide3::from(3i128.pow(8))).array);
}
#[test]
fn wide_vec3_dot(){
let v=Vector3::from_value(Planar64::from(3));
let v1=v*v.x;
let v2=v1*v1.y;
let v3=v2.dot(v2);
assert_eq!(v3,Planar64Wide3::from(3i128.pow(8)*3));
}
#[test]
fn wide_vec3_length_squared(){
let v=Vector3::from_value(Planar64::from(3));
let v1=v*v.x;
let v2=v1*v1.y;
let v3=v2.length_squared();
assert_eq!(v3,Planar64Wide3::from(3i128.pow(8)*3));
}
#[test]
fn wide_matrix_dot(){
let lhs=Matrix3x4::new([
[Planar64::from(1),Planar64::from(2),Planar64::from(3),Planar64::from(4)],
[Planar64::from(5),Planar64::from(6),Planar64::from(7),Planar64::from(8)],
[Planar64::from(9),Planar64::from(10),Planar64::from(11),Planar64::from(12)],
]).transpose();
let rhs=Matrix4x2::new([
[Planar64::from(1),Planar64::from(2)],
[Planar64::from(3),Planar64::from(4)],
[Planar64::from(5),Planar64::from(6)],
[Planar64::from(7),Planar64::from(8)],
]).transpose();
// Mat3<Vec4>.dot(Mat4<Vec2>) -> Mat3<Vec2>
let m_dot=lhs*rhs;
//In[1]:= {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} . {{1, 2}, {3, 4}, {5, 6}, {7, 8}}
//Out[1]= {{50, 60}, {114, 140}, {178, 220}}
assert_eq!(
m_dot.array,
Matrix3x2::new([
[Planar64Wide1::from(50),Planar64Wide1::from(60)],
[Planar64Wide1::from(114),Planar64Wide1::from(140)],
[Planar64Wide1::from(178),Planar64Wide1::from(220)],
]).transpose().array
);
}
#[test]
#[cfg(feature="named-fields")]
fn wide_matrix_det(){
let m=Matrix3::new([
[Planar64::from(1),Planar64::from(2),Planar64::from(3)],
[Planar64::from(4),Planar64::from(5),Planar64::from(7)],
[Planar64::from(6),Planar64::from(8),Planar64::from(9)],
]);
// In[2]:= Det[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}]
// Out[2]= 7
assert_eq!(m.det(),fixed_wide::fixed::Fixed::<3,96>::from(7));
}
#[test]
#[cfg(feature="named-fields")]
fn wide_matrix_adjugate(){
let m=Matrix3::new([
[Planar64::from(1),Planar64::from(2),Planar64::from(3)],
[Planar64::from(4),Planar64::from(5),Planar64::from(7)],
[Planar64::from(6),Planar64::from(8),Planar64::from(9)],
]);
// In[6]:= Adjugate[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}]
// Out[6]= {{-11, 6, -1}, {6, -9, 5}, {2, 4, -3}}
assert_eq!(
m.adjugate().array,
Matrix3::new([
[Planar64Wide1::from(-11),Planar64Wide1::from(6),Planar64Wide1::from(-1)],
[Planar64Wide1::from(6),Planar64Wide1::from(-9),Planar64Wide1::from(5)],
[Planar64Wide1::from(2),Planar64Wide1::from(4),Planar64Wide1::from(-3)],
]).array
);
}

View File

@ -1,6 +0,0 @@
mod tests;
#[cfg(feature="named-fields")]
mod named;
mod fixed_wide;

View File

@ -1,30 +0,0 @@
use crate::types::{Vector3,Matrix3};
#[test]
fn test_vector(){
let mut v=Vector3::new([1,2,3]);
assert_eq!(v.x,1);
assert_eq!(v.y,2);
assert_eq!(v.z,3);
v.x=5;
assert_eq!(v.x,5);
v.y*=v.x;
assert_eq!(v.y,10);
}
#[test]
fn test_matrix(){
let mut v=Matrix3::from_value(2);
assert_eq!(v.x_axis.x,2);
assert_eq!(v.y_axis.y,2);
assert_eq!(v.z_axis.z,2);
v.x_axis.x=5;
assert_eq!(v.x_axis.x,5);
v.y_axis.z*=v.x_axis.x;
assert_eq!(v.y_axis.z,10);
}

Some files were not shown because too many files have changed in this diff Show More