Compare commits

..

27 Commits

Author SHA1 Message Date
fccb13bc60 textures are loaded separately from this temporary structure 2024-02-13 02:06:44 -08:00
43f771fb0f updaate 2024-02-12 22:06:38 -08:00
d31dcd0b19 rename to SetTrajectory::is_absolute 2024-02-12 22:06:38 -08:00
f155dd6a50 hashable 2024-02-09 00:12:51 -08:00
43aa079d89 bvh reduce badness 2024-02-09 00:12:51 -08:00
2ddb546f73 compiles 2024-02-08 20:31:58 -08:00
41d1904f08 notes 2024-02-08 16:43:09 -08:00
03868552c2 physics requirements 2024-02-07 22:33:51 -08:00
a1fa2c2781 graphics rewrite 2024-02-07 19:46:14 -08:00
2ee041ff66 const 2024-02-07 19:45:51 -08:00
f8be75a3fc update id 2024-02-07 19:45:36 -08:00
ec59d251d2 private 2024-02-06 22:52:55 -08:00
05d91c3221 copy 2024-02-06 22:49:57 -08:00
cfbc486e12 aabb list is no longer flat 2024-02-06 22:44:38 -08:00
fc1e6581ec use id proc macro 2024-02-06 19:46:24 -08:00
7512c8676e add id dep 2024-02-06 19:41:44 -08:00
75a1829f53 aaa 2024-02-05 20:28:09 -08:00
6f0c579601 random 2024-02-05 19:50:27 -08:00
cbee156c2f make bvh generic 2024-02-05 19:50:16 -08:00
995ad255bc poly iter 2024-02-05 15:26:01 -08:00
78686b449d collapse this struct 2024-02-04 22:41:45 -08:00
f7b774c050 wip 2024-02-04 22:36:43 -08:00
5efdd3654a updates for rbx_loader 2024-02-04 20:54:52 -08:00
37fb390465 maybe do this 2024-02-03 16:31:11 -08:00
96d813a3a6 work 2024-02-03 16:04:59 -08:00
9291b77679 refine Updatable concept 2024-02-03 16:04:59 -08:00
fa97ab8c6d the work 2024-02-03 16:04:59 -08:00
19 changed files with 1013 additions and 1637 deletions

View File

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

83
Cargo.lock generated
View File

@ -2,112 +2,53 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bnum"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50202def95bf36cb7d1d7a7962cea1c36a3f8ad42425e5d2b71d7acb8041b5b8"
[[package]]
name = "fixed_wide"
version = "0.1.1"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "d9c2cf115b3785ede870fada07e8b1aeba3378345b4ca86fe3c772ecabc05c0f"
dependencies = [
"arrayvec",
"bnum",
"paste",
"ratio_ops",
]
[[package]] [[package]]
name = "glam" name = "glam"
version = "0.29.0" version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28091a37a5d09b555cb6628fd954da299b536433834f5b8e59eba78e0cbbf8a" checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
[[package]] [[package]]
name = "id" name = "id"
version = "0.1.0" version = "0.1.0"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" source = "git+https://git.itzana.me/Quaternions/id?rev=1f710976cc786c8853dab73d6e1cee53158deeb0#1f710976cc786c8853dab73d6e1cee53158deeb0"
checksum = "2337e7a6c273082b672e377e159d7a168fb51438461b7c4033c79a515dd7a25a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "linear_ops"
version = "0.1.0"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "b2e6977ac24f47086d8a7a2d4ae1c720e86dfdc8407cf5e34c18bfa01053c456"
dependencies = [
"fixed_wide",
"paste",
"ratio_ops",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.86" version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.37" version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "ratio_ops"
version = "0.1.0"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "01239195d6afe0509e7e3511b716c0540251dfe7ece0a9a5a27116afb766c42c"
[[package]] [[package]]
name = "strafesnet_common" name = "strafesnet_common"
version = "0.5.2" version = "0.1.0"
dependencies = [ dependencies = [
"arrayvec",
"bitflags",
"fixed_wide",
"glam", "glam",
"id", "id",
"linear_ops",
"ratio_ops",
] ]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.79" version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -116,6 +57,6 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.13" version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

View File

@ -1,19 +1,10 @@
[package] [package]
name = "strafesnet_common" name = "strafesnet_common"
version = "0.5.2" version = "0.1.0"
edition = "2021" edition = "2021"
repository = "https://git.itzana.me/StrafesNET/common"
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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
arrayvec = "0.7.4" glam = "0.25.0"
bitflags = "2.6.0" id = { git = "https://git.itzana.me/Quaternions/id", rev = "1f710976cc786c8853dab73d6e1cee53158deeb0" }
fixed_wide = { version = "0.1.1", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] }
linear_ops = { version = "0.1.0", registry = "strafesnet", features = ["deferred-division","named-fields"] }
ratio_ops = { version = "0.1.0", registry = "strafesnet" }
glam = "0.29.0"
id = { version = "0.1.0", registry = "strafesnet" }

View File

@ -1,4 +1,4 @@
use crate::integer::{vec3,Planar64Vec3}; use crate::integer::Planar64Vec3;
#[derive(Clone)] #[derive(Clone)]
pub struct Aabb{ pub struct Aabb{
@ -6,22 +6,13 @@ pub struct Aabb{
max:Planar64Vec3, max:Planar64Vec3,
} }
impl Default for Aabb{ impl Default for Aabb {
fn default()->Self{ fn default()->Self {
Self{min:vec3::MAX,max:vec3::MIN} Self{min:Planar64Vec3::MAX,max:Planar64Vec3::MIN}
} }
} }
impl Aabb{ 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){ pub fn grow(&mut self,point:Planar64Vec3){
self.min=self.min.min(point); self.min=self.min.min(point);
self.max=self.max.max(point); self.max=self.max.max(point);
@ -35,14 +26,13 @@ impl Aabb{
self.max+=hs; self.max+=hs;
} }
pub fn intersects(&self,aabb:&Aabb)->bool{ pub fn intersects(&self,aabb:&Aabb)->bool{
let bvec=self.min.lt(aabb.max)&aabb.min.lt(self.max); (self.min.cmplt(aabb.max)&aabb.min.cmplt(self.max)).all()
bvec.all()
} }
pub fn size(&self)->Planar64Vec3{ pub fn size(&self)->Planar64Vec3{
self.max-self.min self.max-self.min
} }
pub fn center(&self)->Planar64Vec3{ pub fn center(&self)->Planar64Vec3{
self.min+(self.max-self.min)>>1 self.min.midpoint(self.max)
} }
//probably use floats for area & volume because we don't care about precision //probably use floats for area & volume because we don't care about precision
// pub fn area_weight(&self)->f32{ // pub fn area_weight(&self)->f32{
@ -53,4 +43,4 @@ impl Aabb{
// let d=self.max-self.min; // let d=self.max-self.min;
// d.x*d.y*d.z // d.x*d.y*d.z
// } // }
} }

View File

@ -10,38 +10,26 @@ use crate::aabb::Aabb;
//sort the centerpoints on each axis (3 lists) //sort the centerpoints on each axis (3 lists)
//bv is put into octant based on whether it is upper or lower in each list //bv is put into octant based on whether it is upper or lower in each list
pub enum RecursiveContent<R,T>{ enum BvhNodeContent<T>{
Branch(Vec<R>), Branch(Vec<BvhNode<T>>),
Leaf(T), Leaf(T),
} }
impl<R,T> Default for RecursiveContent<R,T>{ impl<T> Default for BvhNodeContent<T>{
fn default()->Self{ fn default()->Self{
Self::Branch(Vec::new()) Self::Branch(Vec::new())
} }
} }
#[derive(Default)]
pub struct BvhNode<T>{ pub struct BvhNode<T>{
content:RecursiveContent<BvhNode<T>,T>, content:BvhNodeContent<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, aabb:Aabb,
} }
impl<T> BvhNode<T>{ impl<T:Copy+Eq+std::hash::Hash> BvhNode<T>{
pub fn the_tester<F:FnMut(&T)>(&self,aabb:&Aabb,f:&mut F){ pub fn the_tester<F:FnMut(T)>(&self,aabb:&Aabb,f:&mut F){
match &self.content{ match &self.content{
RecursiveContent::Leaf(model)=>f(model), &BvhNodeContent::Leaf(model)=>f(model),
RecursiveContent::Branch(children)=>for child in children{ BvhNodeContent::Branch(children)=>for child in children{
//this test could be moved outside the match statement //this test could be moved outside the match statement
//but that would test the root node aabb //but that would test the root node aabb
//you're probably not going to spend a lot of time outside the map, //you're probably not going to spend a lot of time outside the map,
@ -52,83 +40,38 @@ impl<T> BvhNode<T>{
}, },
} }
} }
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 fn generate_bvh<T:Copy+Eq+std::hash::Hash>(boxen:Vec<(T,Aabb)>)->BvhNode<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) generate_bvh_node(boxen,false)
} }
fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{ fn generate_bvh_node<T:Copy+Eq+std::hash::Hash>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
let n=boxen.len(); let n=boxen.len();
if force||n<20{ if force||n<20{
let mut aabb=Aabb::default(); let mut aabb=Aabb::default();
let nodes=boxen.into_iter().map(|b|{ let nodes=boxen.into_iter().map(|b|{
aabb.join(&b.1); aabb.join(&b.1);
BvhNode{ BvhNode{
content:RecursiveContent::Leaf(b.0), content:BvhNodeContent::Leaf(b.0),
aabb:b.1, aabb:b.1,
} }
}).collect(); }).collect();
BvhNode{ BvhNode{
content:RecursiveContent::Branch(nodes), content:BvhNodeContent::Branch(nodes),
aabb, aabb,
} }
}else{ }else{
let mut octant=std::collections::HashMap::with_capacity(n);//this ids which octant the boxen is put in
let mut sort_x=Vec::with_capacity(n); let mut sort_x=Vec::with_capacity(n);
let mut sort_y=Vec::with_capacity(n); let mut sort_y=Vec::with_capacity(n);
let mut sort_z=Vec::with_capacity(n); let mut sort_z=Vec::with_capacity(n);
for (i,(_,aabb)) in boxen.iter().enumerate(){ for (i,aabb) in boxen.iter(){
let center=aabb.center(); let center=aabb.center();
sort_x.push((i,center.x)); octant.insert(*i,0);
sort_y.push((i,center.y)); sort_x.push((*i,center.x()));
sort_z.push((i,center.z)); sort_y.push((*i,center.y()));
sort_z.push((*i,center.z()));
} }
sort_x.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1)); sort_x.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
sort_y.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1)); sort_y.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
@ -137,34 +80,26 @@ fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
let median_x=sort_x[h].1; let median_x=sort_x[h].1;
let median_y=sort_y[h].1; let median_y=sort_y[h].1;
let median_z=sort_z[h].1; let median_z=sort_z[h].1;
//locate a run of values equal to the median for (i,c) in sort_x{
//partition point gives the first index for which the predicate evaluates to false if median_x<c{
let first_index_eq_median_x=sort_x.partition_point(|&(_,x)|x<median_x); octant.insert(i,octant[&i]+1<<0);
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..]{ for (i,c) in sort_y{
octant[i]+=1<<1; if median_y<c{
octant.insert(i,octant[&i]+1<<1);
}
} }
for &(i,_) in &sort_z[partition_point_z..]{ for (i,c) in sort_z{
octant[i]+=1<<2; if median_z<c{
octant.insert(i,octant[&i]+1<<2);
}
} }
//generate lists for unique octant values //generate lists for unique octant values
let mut list_list=Vec::with_capacity(8); let mut list_list=Vec::with_capacity(8);
let mut octant_list=Vec::with_capacity(8); let mut octant_list=Vec::with_capacity(8);
for (i,(data,aabb)) in boxen.into_iter().enumerate(){ for (i,aabb) in boxen.into_iter(){
let octant_id=octant[i]; let octant_id=octant[&i];
let list_id=if let Some(list_id)=octant_list.iter().position(|&id|id==octant_id){ let list_id=if let Some(list_id)=octant_list.iter().position(|&id|id==octant_id){
list_id list_id
}else{ }else{
@ -173,14 +108,14 @@ fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
list_list.push(Vec::new()); list_list.push(Vec::new());
list_id list_id
}; };
list_list[list_id].push((data,aabb)); list_list[list_id].push((i,aabb));
} }
let mut aabb=Aabb::default(); let mut aabb=Aabb::default();
if list_list.len()==1{ if list_list.len()==1{
generate_bvh_node(list_list.remove(0),true) generate_bvh_node(list_list.remove(0),true)
}else{ }else{
BvhNode{ BvhNode{
content:RecursiveContent::Branch( content:BvhNodeContent::Branch(
list_list.into_iter().map(|b|{ list_list.into_iter().map(|b|{
let node=generate_bvh_node(b,false); let node=generate_bvh_node(b,false);
aabb.join(&node.aabb); aabb.join(&node.aabb);

View File

@ -1,30 +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
}
}
impl Controls{
pub const fn wasd()->Self{
Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight)
}
pub const fn wasdqe()->Self{
Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown)
}
}

View File

@ -31,23 +31,6 @@ pub enum Booster{
//Affine(crate::integer::Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more //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 Velocity(Planar64Vec3),//straight up boost velocity adds to your current velocity
Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction
AirTime(Time),//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)] #[derive(Clone,Hash,Eq,PartialEq)]
pub enum TrajectoryChoice{ pub enum TrajectoryChoice{
@ -151,24 +134,23 @@ impl IntersectingAttributes{
} }
#[derive(Clone,Copy,id::Id,Hash,Eq,PartialEq)] #[derive(Clone,Copy,id::Id,Hash,Eq,PartialEq)]
pub struct CollisionAttributesId(u32); 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)] #[derive(Clone,Hash,Eq,PartialEq)]
pub enum CollisionAttributes{ pub enum CollisionAttributes{
Decoration,//visual only Decoration,//visual only
Contact(ContactAttributes),//track whether you are contacting the object Contact{//track whether you are contacting the object
Intersect(IntersectAttributes),//track whether you are intersecting the object contacting:ContactingAttributes,
general:GeneralAttributes,
},
Intersect{//track whether you are intersecting the object
intersecting:IntersectingAttributes,
general:GeneralAttributes,
},
} }
impl CollisionAttributes{ impl std::default::Default for CollisionAttributes{
pub fn contact_default()->Self{ fn default()->Self{
Self::Contact(ContactAttributes::default()) Self::Contact{
contacting:ContactingAttributes::default(),
general:GeneralAttributes::default()
}
} }
} }

View File

@ -7,17 +7,15 @@ use crate::updatable::Updatable;
pub struct StageElement{ pub struct StageElement{
stage_id:StageId,//which stage spawn to send to stage_id:StageId,//which stage spawn to send to
force:bool,//allow setting to lower spawn id i.e. 7->3 force:bool,//allow setting to lower spawn id i.e. 7->3
behaviour:StageElementBehaviour, behaviour:StageElementBehaviour
jump_limit:Option<u8>,
} }
impl StageElement{ impl StageElement{
#[inline] #[inline]
pub const fn new(stage_id:StageId,force:bool,behaviour:StageElementBehaviour,jump_limit:Option<u8>)->Self{ pub const fn new(stage_id:StageId,force:bool,behaviour:StageElementBehaviour)->Self{
Self{ Self{
stage_id, stage_id,
force, force,
behaviour, behaviour,
jump_limit,
} }
} }
#[inline] #[inline]
@ -32,10 +30,6 @@ impl StageElement{
pub const fn behaviour(&self)->StageElementBehaviour{ pub const fn behaviour(&self)->StageElementBehaviour{
self.behaviour self.behaviour
} }
#[inline]
pub const fn jump_limit(&self)->Option<u8>{
self.jump_limit
}
} }
#[derive(Clone,Copy,Hash,Eq,PartialEq)] #[derive(Clone,Copy,Hash,Eq,PartialEq)]
@ -50,12 +44,12 @@ pub enum StageElementBehaviour{
Checkpoint,//this is a combined behaviour for Ordered & Unordered in case a model is used multiple times or for both. 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)] #[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
pub struct CheckpointId(u32); pub struct CheckpointId(u32);
impl CheckpointId{ impl CheckpointId{
pub const FIRST:Self=Self(0); pub const FIRST:Self=Self(0);
} }
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)] #[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)]
pub struct StageId(u32); pub struct StageId(u32);
impl StageId{ impl StageId{
pub const FIRST:Self=Self(0); pub const FIRST:Self=Self(0);
@ -71,22 +65,7 @@ pub struct Stage{
unordered_checkpoints:HashSet<ModelId>, unordered_checkpoints:HashSet<ModelId>,
} }
impl Stage{ impl Stage{
pub fn new( pub fn new(spawn:ModelId)->Self{
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{ Self{
spawn, spawn,
ordered_checkpoints_count:0, ordered_checkpoints_count:0,
@ -100,17 +79,6 @@ impl Stage{
self.spawn self.spawn
} }
#[inline] #[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)
}
#[inline]
pub const fn is_empty(&self)->bool{ pub const fn is_empty(&self)->bool{
self.is_complete(0,0) self.is_complete(0,0)
} }
@ -146,7 +114,7 @@ pub enum Zone{
Finish, Finish,
Anticheat, Anticheat,
} }
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)] #[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)]
pub struct ModeId(u32); pub struct ModeId(u32);
impl ModeId{ impl ModeId{
pub const MAIN:Self=Self(0); pub const MAIN:Self=Self(0);
@ -160,47 +128,19 @@ pub struct Mode{
stages:Vec<Stage>,//when you load the map you go to stages[0].spawn stages:Vec<Stage>,//when you load the map you go to stages[0].spawn
//mutually exlusive stage element behaviour //mutually exlusive stage element behaviour
elements:HashMap<ModelId,StageElement>, elements:HashMap<ModelId,StageElement>,
jump_limit:HashMap<ModelId,u32>,
} }
impl Mode{ impl Mode{
pub fn new( pub fn new(style:gameplay_style::StyleModifiers,start:ModelId)->Self{
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{ Self{
style, style,
start, start,
zones:HashMap::new(), zones:HashMap::new(),
stages:Vec::new(), stages:Vec::new(),
elements:HashMap::new(), elements:HashMap::new(),
jump_limit: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{ pub const fn get_start(&self)->ModelId{
self.start self.start
} }
@ -225,6 +165,9 @@ impl Mode{
pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{ pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{
self.elements.get(&model_id) self.elements.get(&model_id)
} }
pub fn get_jump_limit(&self,model_id:ModelId)->Option<u32>{
self.jump_limit.get(&model_id).copied()
}
//TODO: put this in the SNF //TODO: put this in the SNF
pub fn denormalize_data(&mut self){ pub fn denormalize_data(&mut self){
//expand and index normalized data //expand and index normalized data
@ -234,14 +177,12 @@ impl Mode{
stage_id:StageId(stage_id as u32), stage_id:StageId(stage_id as u32),
force:false, force:false,
behaviour:StageElementBehaviour::SpawnAt, behaviour:StageElementBehaviour::SpawnAt,
jump_limit:None,
}); });
for (_,&model) in &stage.ordered_checkpoints{ for (_,&model) in &stage.ordered_checkpoints{
self.elements.insert(model,StageElement{ self.elements.insert(model,StageElement{
stage_id:StageId(stage_id as u32), stage_id:StageId(stage_id as u32),
force:false, force:false,
behaviour:StageElementBehaviour::Checkpoint, behaviour:StageElementBehaviour::Checkpoint,
jump_limit:None,
}); });
} }
for &model in &stage.unordered_checkpoints{ for &model in &stage.unordered_checkpoints{
@ -249,7 +190,6 @@ impl Mode{
stage_id:StageId(stage_id as u32), stage_id:StageId(stage_id as u32),
force:false, force:false,
behaviour:StageElementBehaviour::Checkpoint, behaviour:StageElementBehaviour::Checkpoint,
jump_limit:None,
}); });
} }
} }
@ -262,6 +202,7 @@ pub struct ModeUpdate{
stages:HashMap<StageId,StageUpdate>, stages:HashMap<StageId,StageUpdate>,
//mutually exlusive stage element behaviour //mutually exlusive stage element behaviour
elements:HashMap<ModelId,StageElement>, elements:HashMap<ModelId,StageElement>,
jump_limit:HashMap<ModelId,u32>,
} }
impl Updatable<ModeUpdate> for Mode{ impl Updatable<ModeUpdate> for Mode{
fn update(&mut self,update:ModeUpdate){ fn update(&mut self,update:ModeUpdate){
@ -272,6 +213,7 @@ impl Updatable<ModeUpdate> for Mode{
} }
} }
self.elements.extend(update.elements); self.elements.extend(update.elements);
self.jump_limit.extend(update.jump_limit);
} }
} }
impl ModeUpdate{ impl ModeUpdate{
@ -290,6 +232,11 @@ impl ModeUpdate{
mu.elements.insert(model_id,element); mu.elements.insert(model_id,element);
mu mu
} }
pub fn jump_limit(model_id:ModelId,jump_limit:u32)->Self{
let mut mu=Self::default();
mu.jump_limit.insert(model_id,jump_limit);
mu
}
pub fn map_stage_element_ids<F:Fn(StageId)->StageId>(&mut self,f:F){ pub fn map_stage_element_ids<F:Fn(StageId)->StageId>(&mut self,f:F){
for (_,stage_element) in self.elements.iter_mut(){ for (_,stage_element) in self.elements.iter_mut(){
stage_element.stage_id=f(stage_element.stage_id); stage_element.stage_id=f(stage_element.stage_id);
@ -299,17 +246,14 @@ impl ModeUpdate{
#[derive(Default,Clone)] #[derive(Default,Clone)]
pub struct Modes{ pub struct Modes{
pub modes:Vec<Mode>, modes:Vec<Mode>,
} }
impl Modes{ impl Modes{
pub const fn new(modes:Vec<Mode>)->Self{ pub fn new(modes:Vec<Mode>)->Self{
Self{ Self{
modes, modes,
} }
} }
pub fn into_inner(self)->Vec<Mode>{
self.modes
}
pub fn push_mode(&mut self,mode:Mode){ pub fn push_mode(&mut self,mode:Mode){
self.modes.push(mode) self.modes.push(mode)
} }
@ -328,4 +272,4 @@ impl Updatable<ModesUpdate> for Modes{
} }
} }
} }
} }

View File

@ -1,398 +1,264 @@
const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16 const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
use crate::integer::{int,vec3::int as int3,Time,Ratio64,Planar64,Planar64Vec3}; use crate::integer::{Time,Ratio64,Planar64,Planar64Vec3};
use crate::controls_bitflag::Controls;
#[derive(Clone,Debug)] #[derive(Clone)]
pub struct StyleModifiers{ pub struct StyleModifiers{
//controls which are allowed to pass into gameplay (usually all) pub controls_used:u32,//controls which are allowed to pass into gameplay
pub controls_mask:Controls, pub controls_mask:u32,//controls which are masked from control state (e.g. jump in scroll style)
//controls which are masked from control state (e.g. !jump in scroll style)
pub controls_mask_state:Controls,
//strafing
pub strafe:Option<StrafeSettings>, pub strafe:Option<StrafeSettings>,
//player gets a controllable rocket force pub jump_impulse:JumpImpulse,
pub rocket:Option<PropulsionSettings>, pub jump_calculation:JumpCalculation,
//flying pub static_friction:Planar64,
//pub move_type:MoveType::Fly(FlySettings) pub kinetic_friction:Planar64,
//MoveType::Physics(PhysicsSettings) -> PhysicsSettings (strafe,rocket,jump,walk,ladder,swim,gravity) pub walk_speed:Planar64,
//jumping is allowed pub walk_accel:Planar64,
pub jump:Option<JumpSettings>, pub ladder_speed:Planar64,
//standing & walking is allowed pub ladder_accel:Planar64,
pub walk:Option<WalkSettings>, pub ladder_dot:Planar64,
//laddering is allowed pub swim_speed:Planar64,
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, pub mass:Planar64,
pub mv:Planar64,
pub surf_slope:Option<Planar64>,
pub rocket_force:Option<Planar64>,
pub gravity:Planar64Vec3,
pub hitbox:Hitbox,
pub camera_offset:Planar64Vec3,
} }
impl std::default::Default for StyleModifiers{ impl std::default::Default for StyleModifiers{
fn default()->Self{ fn default()->Self{
Self::roblox_bhop() Self::roblox_bhop()
} }
} }
impl StyleModifiers{
pub const CONTROL_MOVEFORWARD:u32=0b00000001;
pub const CONTROL_MOVEBACK:u32=0b00000010;
pub const CONTROL_MOVERIGHT:u32=0b00000100;
pub const CONTROL_MOVELEFT:u32=0b00001000;
pub const CONTROL_MOVEUP:u32=0b00010000;
pub const CONTROL_MOVEDOWN:u32=0b00100000;
pub const CONTROL_JUMP:u32=0b01000000;
pub const CONTROL_ZOOM:u32=0b10000000;
#[derive(Clone,Debug)] pub const RIGHT_DIR:Planar64Vec3=Planar64Vec3::X;
pub enum JumpCalculation{ pub const UP_DIR:Planar64Vec3=Planar64Vec3::Y;
Max,//Roblox: jumped_speed=max(velocity.boost(),velocity.jump()) pub const FORWARD_DIR:Planar64Vec3=Planar64Vec3::NEG_Z;
BoostThenJump,//jumped_speed=velocity.boost().jump()
JumpThenBoost,//jumped_speed=velocity.jump().boost() fn neo()->Self{
Self{
controls_used:!0,
controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
strafe:Some(StrafeSettings{
enable:EnableStrafe::Always,
air_accel_limit:None,
tick_rate:Ratio64::new(64,Time::ONE_SECOND.nanos() as u64).unwrap(),
}),
jump_impulse:JumpImpulse::FromEnergy(Planar64::int(512)),
jump_calculation:JumpCalculation::Energy,
gravity:Planar64Vec3::int(0,-80,0),
static_friction:Planar64::int(2),
kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static
mass:Planar64::int(1),
mv:Planar64::int(3),
rocket_force:None,
walk_speed:Planar64::int(16),
walk_accel:Planar64::int(80),
ladder_speed:Planar64::int(16),
ladder_accel:Planar64::int(160),
ladder_dot:(Planar64::int(1)/2).sqrt(),
swim_speed:Planar64::int(12),
surf_slope:Some(Planar64::raw(7)/8),
hitbox:Hitbox::roblox(),
camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2
}
}
pub fn roblox_bhop()->Self{
Self{
controls_used:!0,
controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
strafe:Some(StrafeSettings{
enable:EnableStrafe::Always,
air_accel_limit:None,
tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
}),
jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)),
jump_calculation:JumpCalculation::Capped,
gravity:Planar64Vec3::int(0,-100,0),
static_friction:Planar64::int(2),
kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static
mass:Planar64::int(1),
mv:Planar64::int(27)/10,
rocket_force:None,
walk_speed:Planar64::int(18),
walk_accel:Planar64::int(90),
ladder_speed:Planar64::int(18),
ladder_accel:Planar64::int(180),
ladder_dot:(Planar64::int(1)/2).sqrt(),
swim_speed:Planar64::int(12),
surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75
hitbox:Hitbox::roblox(),
camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2
}
}
fn roblox_surf()->Self{
Self{
controls_used:!0,
controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
strafe:Some(StrafeSettings{
enable:EnableStrafe::Always,
air_accel_limit:None,
tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
}),
jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)),
jump_calculation:JumpCalculation::Capped,
gravity:Planar64Vec3::int(0,-50,0),
static_friction:Planar64::int(2),
kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static
mass:Planar64::int(1),
mv:Planar64::int(27)/10,
rocket_force:None,
walk_speed:Planar64::int(18),
walk_accel:Planar64::int(90),
ladder_speed:Planar64::int(18),
ladder_accel:Planar64::int(180),
ladder_dot:(Planar64::int(1)/2).sqrt(),
swim_speed:Planar64::int(12),
surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75
hitbox:Hitbox::roblox(),
camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2
}
}
fn source_bhop()->Self{
Self{
controls_used:!0,
controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
strafe:Some(StrafeSettings{
enable:EnableStrafe::Always,
air_accel_limit:Some(Planar64::raw(150<<28)*100),
tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
}),
jump_impulse:JumpImpulse::FromHeight(Planar64::int(52)*VALVE_SCALE),
jump_calculation:JumpCalculation::Linear,
gravity:Planar64Vec3::int(0,-800,0)*VALVE_SCALE,
static_friction:Planar64::int(2),//?
kinetic_friction:Planar64::int(3),//?
mass:Planar64::int(1),
mv:Planar64::raw(30)*VALVE_SCALE,
rocket_force:None,
walk_speed:Planar64::int(18),//?
walk_accel:Planar64::int(90),//?
ladder_speed:Planar64::int(18),//?
ladder_accel:Planar64::int(180),//?
ladder_dot:(Planar64::int(1)/2).sqrt(),//?
swim_speed:Planar64::int(12),//?
surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75
hitbox:Hitbox::source(),
camera_offset:(Planar64Vec3::int(0,64,0)-Planar64Vec3::int(0,73,0)/2)*VALVE_SCALE,
}
}
fn source_surf()->Self{
Self{
controls_used:!0,
controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
strafe:Some(StrafeSettings{
enable:EnableStrafe::Always,
air_accel_limit:Some(Planar64::int(150)*66*VALVE_SCALE),
tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() as u64).unwrap(),
}),
jump_impulse:JumpImpulse::FromHeight(Planar64::int(52)*VALVE_SCALE),
jump_calculation:JumpCalculation::Linear,
gravity:Planar64Vec3::int(0,-800,0)*VALVE_SCALE,
static_friction:Planar64::int(2),//?
kinetic_friction:Planar64::int(3),//?
mass:Planar64::int(1),
mv:Planar64::int(30)*VALVE_SCALE,
rocket_force:None,
walk_speed:Planar64::int(18),//?
walk_accel:Planar64::int(90),//?
ladder_speed:Planar64::int(18),//?
ladder_accel:Planar64::int(180),//?
ladder_dot:(Planar64::int(1)/2).sqrt(),//?
swim_speed:Planar64::int(12),//?
surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75
hitbox:Hitbox::source(),
camera_offset:(Planar64Vec3::int(0,64,0)-Planar64Vec3::int(0,73,0)/2)*VALVE_SCALE,
}
}
fn roblox_rocket()->Self{
Self{
controls_used:!0,
controls_mask:!0,
strafe:None,
jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)),
jump_calculation:JumpCalculation::Capped,
gravity:Planar64Vec3::int(0,-100,0),
static_friction:Planar64::int(2),
kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static
mass:Planar64::int(1),
mv:Planar64::int(27)/10,
rocket_force:Some(Planar64::int(200)),
walk_speed:Planar64::int(18),
walk_accel:Planar64::int(90),
ladder_speed:Planar64::int(18),
ladder_accel:Planar64::int(180),
ladder_dot:(Planar64::int(1)/2).sqrt(),
swim_speed:Planar64::int(12),
surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75
hitbox:Hitbox::roblox(),
camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2
}
}
} }
#[derive(Clone,Debug)] #[derive(Clone)]
pub enum JumpCalculation{
Capped,//roblox
Energy,//new
Linear,//source
}
#[derive(Clone)]
pub enum JumpImpulse{ pub enum JumpImpulse{
Time(Time),//jump time is invariant across mass and gravity changes FromTime(Time),//jump time is invariant across mass and gravity changes
Height(Planar64),//jump height is invariant across mass and gravity changes FromHeight(Planar64),//jump height is invariant across mass and gravity changes
Linear(Planar64),//jump velocity is invariant across mass and gravity changes FromDeltaV(Planar64),//jump velocity is invariant across mass and gravity changes
Energy(Planar64),// :) FromEnergy(Planar64),// :)
} }
//Jumping acts on dot(walks_state.normal,body.velocity) //Jumping acts on dot(walks_state.normal,body.velocity)
//Capped means it increases the dot to the cap
//Energy means it adds energy //Energy means it adds energy
//Linear means it linearly adds on //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)] #[derive(Clone)]
pub struct JumpSettings{ pub enum EnableStrafe{
//information used to calculate jump power Always,
pub impulse:JumpImpulse, MaskAny(u32),//hsw, shsw
//information used to calculate jump behaviour MaskAll(u32),
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>), //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)] #[derive(Clone)]
pub struct StrafeSettings{ pub struct StrafeSettings{
pub enable:ControlsActivation, enable:EnableStrafe,
pub mv:Planar64, air_accel_limit:Option<Planar64>,
pub air_accel_limit:Option<Planar64>, tick_rate:Ratio64,
pub tick_rate:Ratio64,
} }
impl StrafeSettings{ 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:Time)->Time{ pub fn next_tick(&self,time:Time)->Time{
Time::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1)) Time::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1))
} }
pub const fn activates(&self,controls:Controls)->bool{ pub fn mask(&self,controls:u32)->bool{
self.enable.activates(controls) match self.enable{
} EnableStrafe::Always=>true,
pub const fn mask(&self,controls:Controls)->Controls{ EnableStrafe::MaskAny(mask)=>mask&controls!=0,
self.enable.mask(controls) EnableStrafe::MaskAll(mask)=>mask&controls==mask,
}
}
#[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)] #[derive(Clone)]
pub enum HitboxMesh{ pub enum HitboxMesh{
Box,//source Box,//source
Cylinder,//roblox Cylinder,//roblox
@ -402,210 +268,22 @@ pub enum HitboxMesh{
//DualCone, //DualCone,
} }
#[derive(Clone,Debug)] #[derive(Clone)]
pub struct Hitbox{ pub struct Hitbox{
pub halfsize:Planar64Vec3, pub halfsize:Planar64Vec3,
pub mesh:HitboxMesh, pub mesh:HitboxMesh,
} }
impl Hitbox{ impl Hitbox{
pub fn roblox()->Self{ fn roblox()->Self{
Self{ Self{
halfsize:int3(2,5,2)>>1, halfsize:Planar64Vec3::int(2,5,2)/2,
mesh:HitboxMesh::Cylinder, mesh:HitboxMesh::Cylinder,
} }
} }
pub fn source()->Self{ fn source()->Self{
Self{ Self{
halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).fix_1(), halfsize:Planar64Vec3::raw(33,73,33)/2*VALVE_SCALE,
mesh:HitboxMesh::Box, 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,Time::ONE_SECOND.nanos() 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,Time::ONE_SECOND.nanos() as u64).unwrap(),
}),
jump:Some(JumpSettings{
impulse:JumpImpulse::Time(Time::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,Time::ONE_SECOND.nanos() 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,Time::ONE_SECOND.nanos() 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

@ -19,14 +19,14 @@ pub struct InstructionCollector<I>{
instruction:Option<I>, instruction:Option<I>,
} }
impl<I> InstructionCollector<I>{ impl<I> InstructionCollector<I>{
pub const fn new(time:Time)->Self{ pub fn new(time:Time)->Self{
Self{ Self{
time, time,
instruction:None instruction:None
} }
} }
#[inline] #[inline]
pub const fn time(&self)->Time{ pub fn time(&self)->Time{
self.time self.time
} }
pub fn collect(&mut self,instruction:Option<TimedInstruction<I>>){ pub fn collect(&mut self,instruction:Option<TimedInstruction<I>>){
@ -50,4 +50,4 @@ impl<I> InstructionCollector<I>{
None=>None, None=>None,
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,11 @@
pub mod bvh; pub mod bvh;
pub mod map; pub mod map;
pub mod run;
pub mod aabb; pub mod aabb;
pub mod model; pub mod model;
pub mod mouse; pub mod zeroes;
pub mod timer;
pub mod integer; pub mod integer;
pub mod physics;
pub mod updatable; pub mod updatable;
pub mod instruction; pub mod instruction;
pub mod gameplay_attributes;
pub mod gameplay_modes; pub mod gameplay_modes;
pub mod gameplay_style; pub mod gameplay_style;
pub mod controls_bitflag; pub mod gameplay_attributes;

View File

@ -9,6 +9,5 @@ pub struct CompleteMap{
pub meshes:Vec<model::Mesh>, pub meshes:Vec<model::Mesh>,
pub models:Vec<model::Model>, pub models:Vec<model::Model>,
//RenderPattern //RenderPattern
pub textures:Vec<Vec<u8>>,
pub render_configs:Vec<model::RenderConfig>, pub render_configs:Vec<model::RenderConfig>,
} }

View File

@ -27,7 +27,6 @@ pub trait PolygonIter{
pub trait MapVertexId{ pub trait MapVertexId{
fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self; fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self;
} }
#[derive(Clone)]
pub struct PolygonList(Vec<IndexedVertexList>); pub struct PolygonList(Vec<IndexedVertexList>);
impl PolygonList{ impl PolygonList{
pub const fn new(list:Vec<IndexedVertexList>)->Self{ pub const fn new(list:Vec<IndexedVertexList>)->Self{
@ -55,7 +54,6 @@ impl MapVertexId for PolygonList{
// } // }
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)] #[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
pub struct PolygonGroupId(u32); pub struct PolygonGroupId(u32);
#[derive(Clone)]
pub enum PolygonGroup{ pub enum PolygonGroup{
PolygonList(PolygonList), PolygonList(PolygonList),
//TriangleStrip(TriangleStrip), //TriangleStrip(TriangleStrip),
@ -76,11 +74,11 @@ impl MapVertexId for PolygonGroup{
} }
} }
/// Ah yes, a group of things to render at the same time /// Ah yes, a group of things to render at the same time
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)] #[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
pub struct TextureId(u32); pub struct TextureId(u32);
#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)] #[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
pub struct RenderConfigId(u32); pub struct RenderConfigId(u32);
#[derive(Clone,Copy,Default)] #[derive(Default)]
pub struct RenderConfig{ pub struct RenderConfig{
pub texture:Option<TextureId>, pub texture:Option<TextureId>,
} }
@ -91,21 +89,19 @@ impl RenderConfig{
} }
} }
} }
#[derive(Clone)]
pub struct IndexedGraphicsGroup{ pub struct IndexedGraphicsGroup{
//Render pattern material/texture/shader/flat color //Render pattern material/texture/shader/flat color
pub render:RenderConfigId, pub render:RenderConfigId,
pub groups:Vec<PolygonGroupId>, pub groups:Vec<PolygonGroupId>,
} }
#[derive(Clone,Default)] #[derive(Default)]
pub struct IndexedPhysicsGroup{ pub struct IndexedPhysicsGroup{
//the polygons in this group are guaranteed to make a closed convex shape //the polygons in this group are guaranteed to make a closed convex shape
pub groups:Vec<PolygonGroupId>, pub groups:Vec<PolygonGroupId>,
} }
//This is a superset of PhysicsModel and GraphicsModel //This is a superset of PhysicsModel and GraphicsModel
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)] #[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
pub struct MeshId(u32); pub struct MeshId(u32);
#[derive(Clone)]
pub struct Mesh{ pub struct Mesh{
pub unique_pos:Vec<Planar64Vec3>,//Unit32Vec3 pub unique_pos:Vec<Planar64Vec3>,//Unit32Vec3
pub unique_normal:Vec<Planar64Vec3>,//Unit32Vec3 pub unique_normal:Vec<Planar64Vec3>,//Unit32Vec3

View File

@ -1,26 +0,0 @@
use crate::integer::Time;
#[derive(Clone,Debug)]
pub struct MouseState{
pub pos:glam::IVec2,
pub time:Time,
}
impl Default for MouseState{
fn default()->Self{
Self{
time:Time::ZERO,
pos:glam::IVec2::ZERO,
}
}
}
impl MouseState{
pub fn lerp(&self,target:&MouseState,time:Time)->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,27 +0,0 @@
#[derive(Clone,Debug)]
pub enum Instruction{
ReplaceMouse(crate::mouse::MouseState,crate::mouse::MouseState),
SetNextMouse(crate::mouse::MouseState),
SetMoveRight(bool),
SetMoveUp(bool),
SetMoveBack(bool),
SetMoveLeft(bool),
SetMoveDown(bool),
SetMoveForward(bool),
SetJump(bool),
SetZoom(bool),
/// Reset: fully replace the physics state.
/// This forgets all inputs and settings which need to be reapplied.
Reset,
/// Restart: Teleport to the start zone.
Restart,
/// Spawn: Teleport to a specific mode's spawn
/// Sets current mode & spawn
Spawn(crate::gameplay_modes::ModeId,crate::gameplay_modes::StageId),
Idle,
//Idle: there were no input events, but the simulation is safe to advance to this timestep
//for interpolation / networking / playback reasons, most playback heads will always want
//to be 1 instruction ahead to generate the next state for interpolation.
PracticeFly,
SetSensitivity(crate::integer::Ratio64Vec2),
}

View File

@ -1,103 +0,0 @@
use crate::timer::{TimerFixed,Realtime,Paused,Unpaused};
use crate::integer::Time;
#[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{
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.",
}.to_owned()
}
}
#[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,Unpaused>},
Finished{timer:TimerFixed<Realtime,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:Time)->Time{
match &self.state{
RunState::Created=>Time::ZERO,
RunState::Started{timer}=>timer.time(time),
RunState::Finished{timer}=>timer.time(time),
}
}
pub fn start(&mut self,time:Time)->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:Time)->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);
}
}
}

View File

@ -1,319 +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,Debug)]
pub struct Realtime{
offset:Time,
}
impl Realtime{
pub const fn new(offset:Time)->Self{
Self{offset}
}
}
#[derive(Clone,Copy,Debug)]
pub struct Scaled{
scale:Ratio64,
offset:Time,
}
impl Scaled{
pub const fn new(scale:Ratio64,offset:Time)->Self{
Self{scale,offset}
}
const fn with_scale(scale:Ratio64)->Self{
Self{scale,offset:Time::ZERO}
}
const fn scale(&self,time:Time)->Time{
Time::raw(self.scale.mul_int(time.get()))
}
const fn get_scale(&self)->Ratio64{
self.scale
}
fn set_scale(&mut self,time:Time,new_scale:Ratio64){
let new_time=self.get_time(time);
self.scale=new_scale;
self.set_time(time,new_time);
}
}
pub trait TimerState:Copy+std::fmt::Debug{
fn identity()->Self;
fn get_time(&self,time:Time)->Time;
fn set_time(&mut self,time:Time,new_time:Time);
fn get_offset(&self)->Time;
fn set_offset(&mut self,offset:Time);
}
impl TimerState for Realtime{
fn identity()->Self{
Self{offset:Time::ZERO}
}
fn get_time(&self,time:Time)->Time{
time+self.offset
}
fn set_time(&mut self,time:Time,new_time:Time){
self.offset=new_time-time;
}
fn get_offset(&self)->Time{
self.offset
}
fn set_offset(&mut self,offset:Time){
self.offset=offset;
}
}
impl TimerState for Scaled{
fn identity()->Self{
Self{scale:Ratio64::ONE,offset:Time::ZERO}
}
fn get_time(&self,time:Time)->Time{
self.scale(time)+self.offset
}
fn set_time(&mut self,time:Time,new_time:Time){
self.offset=new_time-self.scale(time);
}
fn get_offset(&self)->Time{
self.offset
}
fn set_offset(&mut self,offset:Time){
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> TimerFixed<Scaled,P>{
pub fn scaled(time:Time,new_time:Time,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,new_scale:Ratio64){
self.state.set_scale(time,new_scale)
}
}
//pause and unpause is generic across TimerState
impl<T:TimerState> TimerFixed<T,Paused>{
pub fn into_unpaused(self,time:Time)->TimerFixed<T,Unpaused>{
let new_time=self.time(time);
let mut timer=TimerFixed{
state:self.state,
_paused:Unpaused,
};
timer.set_time(time,new_time);
timer
}
}
impl<T:TimerState> TimerFixed<T,Unpaused>{
pub fn into_paused(self,time:Time)->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
}
}
//the new constructor and time queries are generic across both
impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
pub fn new(time:Time,new_time:Time)->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 time(&self,time:Time)->Time{
match P::IS_PAUSED{
true=>self.state.get_offset(),
false=>self.state.get_time(time),
}
}
pub fn set_time(&mut self,time:Time,new_time:Time){
match P::IS_PAUSED{
true=>self.state.set_offset(new_time),
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>{
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,new_time:Time)->Self{
Self::Paused(TimerFixed::new(time,new_time))
}
pub fn unpaused(time:Time,new_time:Time)->Self{
Self::Unpaused(TimerFixed::new(time,new_time))
}
pub fn time(&self,time:Time)->Time{
match self{
Self::Paused(timer)=>timer.time(time),
Self::Unpaused(timer)=>timer.time(time),
}
}
pub fn set_time(&mut self,time:Time,new_time:Time){
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)->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)->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,paused:bool)->Result<(),Error>{
match paused{
true=>self.pause(time),
false=>self.unpause(time),
}
}
}
//scaled timer methods are generic across PauseState
impl Timer<Scaled>{
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,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)
};
}
#[test]
fn test_timerfixed_scaled(){
//create a paused timer that reads 0s
let timer=TimerFixed::<Scaled,Paused>::from_state(Scaled{scale:0.5f32.try_into().unwrap(),offset:sec!(0)});
//the paused timer at 1 second should read 0s
assert_eq!(timer.time(sec!(1)),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!(20)),sec!(5));
}
#[test]
fn test_timer()->Result<(),Error>{
//create a paused timer that reads 0s
let mut timer=Timer::<Realtime>::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(())
}
}

40
src/zeroes.rs Normal file
View File

@ -0,0 +1,40 @@
//find roots of polynomials
use crate::integer::Planar64;
#[inline]
pub fn zeroes2(a0:Planar64,a1:Planar64,a2:Planar64) -> Vec<Planar64>{
if a2==Planar64::ZERO{
return zeroes1(a0, a1);
}
let radicand=a1.get() as i128*a1.get() as i128-a2.get() as i128*a0.get() as i128*4;
if 0<radicand {
//start with f64 sqrt
//failure case: 2^63 < sqrt(2^127)
let planar_radicand=Planar64::raw(unsafe{(radicand as f64).sqrt().to_int_unchecked()});
//TODO: one or two newtons
//sort roots ascending and avoid taking the difference of large numbers
match (Planar64::ZERO<a2,Planar64::ZERO<a1){
(true, true )=>vec![(-a1-planar_radicand)/(a2*2),(a0*2)/(-a1-planar_radicand)],
(true, false)=>vec![(a0*2)/(-a1+planar_radicand),(-a1+planar_radicand)/(a2*2)],
(false,true )=>vec![(a0*2)/(-a1-planar_radicand),(-a1-planar_radicand)/(a2*2)],
(false,false)=>vec![(-a1+planar_radicand)/(a2*2),(a0*2)/(-a1+planar_radicand)],
}
} else if radicand==0 {
return vec![a1/(a2*-2)];
} else {
return vec![];
}
}
#[inline]
pub fn zeroes1(a0:Planar64,a1:Planar64) -> Vec<Planar64> {
if a1==Planar64::ZERO{
return vec![];
}else{
let q=((-a0.get() as i128)<<32)/(a1.get() as i128);
if i64::MIN as i128<=q&&q<=i64::MAX as i128{
return vec![Planar64::raw(q as i64)];
}else{
return vec![];
}
}
}