Compare commits
55 Commits
spawn-beha
...
master
Author | SHA1 | Date | |
---|---|---|---|
81f6a405cf | |||
522f9f6668 | |||
d02df2f83e | |||
ed78807a9f | |||
b419f2a321 | |||
92e333fcbe | |||
79011171cb | |||
cd58e20fc2 | |||
fbdabf449a | |||
0a95f492ba | |||
27dba8a90d | |||
2b8bb0b705 | |||
88b87b1859 | |||
aa79d9a54e | |||
d95549070b | |||
af1f1aad14 | |||
8012a459bf | |||
22216846e3 | |||
eebde9b54a | |||
7d1058164b | |||
415be69ba8 | |||
62e9397c98 | |||
9563290454 | |||
a2d9e97467 | |||
62a6af9261 | |||
a819f93fa9 | |||
0375844f4f | |||
0dd41ae2f1 | |||
fed810334b | |||
0ff4de3d38 | |||
0c0365746e | |||
0c4a34c9cb | |||
1318fd2856 | |||
6ce2e64961 | |||
1401c7ad13 | |||
7b826e11be | |||
f337c71c5b | |||
ba00fed9b8 | |||
7e91c85822 | |||
33aa8d5d9c | |||
6cd5234c91 | |||
0215df9f96 | |||
582009baed | |||
0e4489b360 | |||
324e0eb69b | |||
a9f3e61f2b | |||
db142d125b | |||
ef922135e6 | |||
9aec0e1b52 | |||
b065f83faf | |||
39b202176f | |||
093a54c527 | |||
45dc9e4bd3 | |||
f2635be0fb | |||
47cdea0c8a |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[registries.strafesnet]
|
||||||
|
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
113
Cargo.lock
generated
113
Cargo.lock
generated
@ -3,14 +3,119 @@
|
|||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glam"
|
name = "arrayvec"
|
||||||
version = "0.25.0"
|
version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
|
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]]
|
||||||
|
name = "glam"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c28091a37a5d09b555cb6628fd954da299b536433834f5b8e59eba78e0cbbf8a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "id"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||||
|
checksum = "2337e7a6c273082b672e377e159d7a168fb51438461b7c4033c79a515dd7a25a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"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]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.86"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||||
|
dependencies = [
|
||||||
|
"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.1.0"
|
version = "0.5.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
"bitflags",
|
||||||
|
"fixed_wide",
|
||||||
"glam",
|
"glam",
|
||||||
|
"id",
|
||||||
|
"linear_ops",
|
||||||
|
"ratio_ops",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||||
|
14
Cargo.toml
14
Cargo.toml
@ -1,9 +1,19 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafesnet_common"
|
name = "strafesnet_common"
|
||||||
version = "0.1.0"
|
version = "0.5.2"
|
||||||
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]
|
||||||
glam = "0.25.0"
|
arrayvec = "0.7.4"
|
||||||
|
bitflags = "2.6.0"
|
||||||
|
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" }
|
||||||
|
24
src/aabb.rs
24
src/aabb.rs
@ -1,4 +1,4 @@
|
|||||||
use crate::integer::Planar64Vec3;
|
use crate::integer::{vec3,Planar64Vec3};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Aabb{
|
pub struct Aabb{
|
||||||
@ -6,13 +6,22 @@ pub struct Aabb{
|
|||||||
max:Planar64Vec3,
|
max:Planar64Vec3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Aabb {
|
impl Default for Aabb{
|
||||||
fn default()->Self {
|
fn default()->Self{
|
||||||
Self{min:Planar64Vec3::MAX,max:Planar64Vec3::MIN}
|
Self{min:vec3::MAX,max:vec3::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);
|
||||||
@ -26,13 +35,14 @@ impl Aabb{
|
|||||||
self.max+=hs;
|
self.max+=hs;
|
||||||
}
|
}
|
||||||
pub fn intersects(&self,aabb:&Aabb)->bool{
|
pub fn intersects(&self,aabb:&Aabb)->bool{
|
||||||
(self.min.cmplt(aabb.max)&aabb.min.cmplt(self.max)).all()
|
let bvec=self.min.lt(aabb.max)&aabb.min.lt(self.max);
|
||||||
|
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.midpoint(self.max)
|
self.min+(self.max-self.min)>>1
|
||||||
}
|
}
|
||||||
//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{
|
||||||
@ -43,4 +53,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
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
163
src/bvh.rs
163
src/bvh.rs
@ -9,26 +9,39 @@ use crate::aabb::Aabb;
|
|||||||
//start with bisection into octrees because a bad bvh is still 1000x better than no bvh
|
//start with bisection into octrees because a bad bvh is still 1000x better than no bvh
|
||||||
//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
|
||||||
enum BvhNodeContent{
|
|
||||||
Branch(Vec<BvhNode>),
|
pub enum RecursiveContent<R,T>{
|
||||||
Leaf(usize),
|
Branch(Vec<R>),
|
||||||
|
Leaf(T),
|
||||||
}
|
}
|
||||||
impl Default for BvhNodeContent{
|
impl<R,T> Default for RecursiveContent<R,T>{
|
||||||
fn default()->Self{
|
fn default()->Self{
|
||||||
Self::Branch(Vec::new())
|
Self::Branch(Vec::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Default)]
|
pub struct BvhNode<T>{
|
||||||
pub struct BvhNode{
|
content:RecursiveContent<BvhNode<T>,T>,
|
||||||
content:BvhNodeContent,
|
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 BvhNode{
|
impl<T> BvhNode<T>{
|
||||||
pub fn the_tester<F:FnMut(usize)>(&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{
|
||||||
&BvhNodeContent::Leaf(model)=>f(model),
|
RecursiveContent::Leaf(model)=>f(model),
|
||||||
BvhNodeContent::Branch(children)=>for child in children{
|
RecursiveContent::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,
|
||||||
@ -39,38 +52,83 @@ impl BvhNode{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_bvh(boxen:Vec<Aabb>)->BvhNode{
|
impl <W,T> BvhWeightNode<W,T>{
|
||||||
generate_bvh_node(boxen.into_iter().enumerate().collect())
|
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)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_bvh_node(boxen:Vec<(usize,Aabb)>)->BvhNode{
|
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();
|
let n=boxen.len();
|
||||||
if 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:BvhNodeContent::Leaf(b.0),
|
content:RecursiveContent::Leaf(b.0),
|
||||||
aabb:b.1,
|
aabb:b.1,
|
||||||
}
|
}
|
||||||
}).collect();
|
}).collect();
|
||||||
BvhNode{
|
BvhNode{
|
||||||
content:BvhNodeContent::Branch(nodes),
|
content:RecursiveContent::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(){
|
for (i,(_,aabb)) in boxen.iter().enumerate(){
|
||||||
let center=aabb.center();
|
let center=aabb.center();
|
||||||
octant.insert(*i,0);
|
sort_x.push((i,center.x));
|
||||||
sort_x.push((*i,center.x()));
|
sort_y.push((i,center.y));
|
||||||
sort_y.push((*i,center.y()));
|
sort_z.push((i,center.z));
|
||||||
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));
|
||||||
@ -79,26 +137,34 @@ fn generate_bvh_node(boxen:Vec<(usize,Aabb)>)->BvhNode{
|
|||||||
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;
|
||||||
for (i,c) in sort_x{
|
//locate a run of values equal to the median
|
||||||
if median_x<c{
|
//partition point gives the first index for which the predicate evaluates to false
|
||||||
octant.insert(i,octant[&i]+1<<0);
|
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,c) in sort_y{
|
for &(i,_) in &sort_y[partition_point_y..]{
|
||||||
if median_y<c{
|
octant[i]+=1<<1;
|
||||||
octant.insert(i,octant[&i]+1<<1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (i,c) in sort_z{
|
for &(i,_) in &sort_z[partition_point_z..]{
|
||||||
if median_z<c{
|
octant[i]+=1<<2;
|
||||||
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,aabb) in boxen.into_iter(){
|
for (i,(data,aabb)) in boxen.into_iter().enumerate(){
|
||||||
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{
|
||||||
@ -107,17 +173,22 @@ fn generate_bvh_node(boxen:Vec<(usize,Aabb)>)->BvhNode{
|
|||||||
list_list.push(Vec::new());
|
list_list.push(Vec::new());
|
||||||
list_id
|
list_id
|
||||||
};
|
};
|
||||||
list_list[list_id].push((i,aabb));
|
list_list[list_id].push((data,aabb));
|
||||||
}
|
}
|
||||||
let mut aabb=Aabb::default();
|
let mut aabb=Aabb::default();
|
||||||
let children=list_list.into_iter().map(|b|{
|
if list_list.len()==1{
|
||||||
let node=generate_bvh_node(b);
|
generate_bvh_node(list_list.remove(0),true)
|
||||||
aabb.join(&node.aabb);
|
}else{
|
||||||
node
|
BvhNode{
|
||||||
}).collect();
|
content:RecursiveContent::Branch(
|
||||||
BvhNode{
|
list_list.into_iter().map(|b|{
|
||||||
content:BvhNodeContent::Branch(children),
|
let node=generate_bvh_node(b,false);
|
||||||
aabb,
|
aabb.join(&node.aabb);
|
||||||
|
node
|
||||||
|
}).collect()
|
||||||
|
),
|
||||||
|
aabb,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
30
src/controls_bitflag.rs
Normal file
30
src/controls_bitflag.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::model;
|
||||||
use crate::integer::{Time,Planar64,Planar64Vec3};
|
use crate::integer::{Time,Planar64,Planar64Vec3};
|
||||||
|
|
||||||
//you have this effect while in contact
|
//you have this effect while in contact
|
||||||
@ -8,8 +9,9 @@ pub struct ContactingLadder{
|
|||||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||||
pub enum ContactingBehaviour{
|
pub enum ContactingBehaviour{
|
||||||
Surf,
|
Surf,
|
||||||
Cling,//usable as a zipline, or other weird and wonderful things
|
|
||||||
Ladder(ContactingLadder),
|
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
|
Elastic(u32),//[1/2^32,1] 0=None (elasticity+1)/2^32
|
||||||
}
|
}
|
||||||
//you have this effect while intersecting
|
//you have this effect while intersecting
|
||||||
@ -29,6 +31,23 @@ 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{
|
||||||
@ -54,7 +73,7 @@ pub enum SetTrajectory{
|
|||||||
Velocity(Planar64Vec3),//SetVelocity
|
Velocity(Planar64Vec3),//SetVelocity
|
||||||
}
|
}
|
||||||
impl SetTrajectory{
|
impl SetTrajectory{
|
||||||
fn is_velocity(&self)->bool{
|
pub const fn is_absolute(&self)->bool{
|
||||||
match self{
|
match self{
|
||||||
SetTrajectory::AirTime(_)
|
SetTrajectory::AirTime(_)
|
||||||
|SetTrajectory::Height(_)
|
|SetTrajectory::Height(_)
|
||||||
@ -76,7 +95,7 @@ pub struct Wormhole{
|
|||||||
//destination does not need to be another wormhole
|
//destination does not need to be another wormhole
|
||||||
//this defines a one way portal to a destination model transform
|
//this defines a one way portal to a destination model transform
|
||||||
//two of these can create a two way wormhole
|
//two of these can create a two way wormhole
|
||||||
pub destination_model_id:u32,
|
pub destination_model:model::ModelId,
|
||||||
//(position,angles)*=origin.transform.inverse()*destination.transform
|
//(position,angles)*=origin.transform.inverse()*destination.transform
|
||||||
}
|
}
|
||||||
//attributes listed in order of handling
|
//attributes listed in order of handling
|
||||||
@ -88,14 +107,14 @@ pub struct GeneralAttributes{
|
|||||||
pub accelerator:Option<Accelerator>,
|
pub accelerator:Option<Accelerator>,
|
||||||
}
|
}
|
||||||
impl GeneralAttributes{
|
impl GeneralAttributes{
|
||||||
pub fn any(&self)->bool{
|
pub const fn any(&self)->bool{
|
||||||
self.booster.is_some()
|
self.booster.is_some()
|
||||||
||self.trajectory.is_some()
|
||self.trajectory.is_some()
|
||||||
||self.wormhole.is_some()
|
||self.wormhole.is_some()
|
||||||
||self.accelerator.is_some()
|
||self.accelerator.is_some()
|
||||||
}
|
}
|
||||||
pub fn is_wrcp(&self)->bool{
|
pub fn is_wrcp(&self)->bool{
|
||||||
self.trajectory.as_ref().map_or(false,|t|t.is_velocity())
|
self.trajectory.as_ref().map_or(false,|t|t.is_absolute())
|
||||||
/*
|
/*
|
||||||
&&match &self.teleport_behaviour{
|
&&match &self.teleport_behaviour{
|
||||||
Some(TeleportBehaviour::StageElement(
|
Some(TeleportBehaviour::StageElement(
|
||||||
@ -117,7 +136,7 @@ pub struct ContactingAttributes{
|
|||||||
pub contact_behaviour:Option<ContactingBehaviour>,
|
pub contact_behaviour:Option<ContactingBehaviour>,
|
||||||
}
|
}
|
||||||
impl ContactingAttributes{
|
impl ContactingAttributes{
|
||||||
pub fn any(&self)->bool{
|
pub const fn any(&self)->bool{
|
||||||
self.contact_behaviour.is_some()
|
self.contact_behaviour.is_some()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,27 +145,30 @@ pub struct IntersectingAttributes{
|
|||||||
pub water:Option<IntersectingWater>,
|
pub water:Option<IntersectingWater>,
|
||||||
}
|
}
|
||||||
impl IntersectingAttributes{
|
impl IntersectingAttributes{
|
||||||
pub fn any(&self)->bool{
|
pub const fn any(&self)->bool{
|
||||||
self.water.is_some()
|
self.water.is_some()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[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)]
|
||||||
pub enum CollisionAttributes{
|
pub enum CollisionAttributes{
|
||||||
Decoration,//visual only
|
Decoration,//visual only
|
||||||
Contact{//track whether you are contacting the object
|
Contact(ContactAttributes),//track whether you are contacting the object
|
||||||
contacting:ContactingAttributes,
|
Intersect(IntersectAttributes),//track whether you are intersecting the object
|
||||||
general:GeneralAttributes,
|
|
||||||
},
|
|
||||||
Intersect{//track whether you are intersecting the object
|
|
||||||
intersecting:IntersectingAttributes,
|
|
||||||
general:GeneralAttributes,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
impl std::default::Default for CollisionAttributes{
|
impl CollisionAttributes{
|
||||||
fn default()->Self{
|
pub fn contact_default()->Self{
|
||||||
Self::Contact{
|
Self::Contact(ContactAttributes::default())
|
||||||
contacting:ContactingAttributes::default(),
|
|
||||||
general:GeneralAttributes::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,44 @@
|
|||||||
use std::collections::{HashSet,HashMap};
|
use std::collections::{HashSet,HashMap};
|
||||||
use crate::model::ModelId;
|
use crate::model::ModelId;
|
||||||
use crate::gameplay_style;
|
use crate::gameplay_style;
|
||||||
|
use crate::updatable::Updatable;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct StageElement{
|
pub struct StageElement{
|
||||||
stage: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{
|
||||||
|
#[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,Hash,Eq,PartialEq)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
||||||
pub enum StageElementBehaviour{
|
pub enum StageElementBehaviour{
|
||||||
SpawnAt,//must be standing on top to get effect. except cancollide false
|
SpawnAt,//must be standing on top to get effect. except cancollide false
|
||||||
Trigger,
|
Trigger,
|
||||||
@ -20,71 +50,282 @@ 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)]
|
||||||
|
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);
|
pub struct StageId(u32);
|
||||||
|
impl StageId{
|
||||||
|
pub const FIRST:Self=Self(0);
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Stage{
|
pub struct Stage{
|
||||||
spawn:ModelId,
|
spawn:ModelId,
|
||||||
//other behaviour models of this stage can have
|
//open world support lol
|
||||||
ordered_checkpoints:Vec<ModelId>,
|
ordered_checkpoints_count:u32,
|
||||||
|
unordered_checkpoints_count:u32,
|
||||||
|
//currently loaded checkpoint models
|
||||||
|
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
|
||||||
unordered_checkpoints:HashSet<ModelId>,
|
unordered_checkpoints:HashSet<ModelId>,
|
||||||
}
|
}
|
||||||
|
impl Stage{
|
||||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
pub fn new(
|
||||||
pub enum ZoneBehaviour{
|
spawn:ModelId,
|
||||||
Finish,
|
ordered_checkpoints_count:u32,
|
||||||
Anitcheat,
|
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)
|
||||||
|
}
|
||||||
|
#[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);
|
pub struct ModeId(u32);
|
||||||
|
impl ModeId{
|
||||||
|
pub const MAIN:Self=Self(0);
|
||||||
|
pub const BONUS:Self=Self(1);
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Mode{
|
pub struct Mode{
|
||||||
style:gameplay_style::StyleModifiers,
|
style:gameplay_style::StyleModifiers,
|
||||||
start:ModelId,
|
start:ModelId,//when you press reset you go here
|
||||||
zones:HashMap<ModelId,ZoneBehaviour>,
|
zones:HashMap<ModelId,Zone>,
|
||||||
stages:Vec<Stage>,
|
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(
|
||||||
|
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>{
|
pub fn get_spawn_model_id(&self,stage:StageId)->Option<ModelId>{
|
||||||
self.stages.get(stage.0 as usize).map(|s|s.spawn)
|
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){
|
pub fn denormalize_data(&mut self){
|
||||||
//expand and index normalized data
|
//expand and index normalized data
|
||||||
|
self.zones.insert(self.start,Zone::Start);
|
||||||
for (stage_id,stage) in self.stages.iter().enumerate(){
|
for (stage_id,stage) in self.stages.iter().enumerate(){
|
||||||
self.elements.insert(stage.spawn,StageElement{
|
self.elements.insert(stage.spawn,StageElement{
|
||||||
stage: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_id in &stage.ordered_checkpoints{
|
for (_,&model) in &stage.ordered_checkpoints{
|
||||||
self.elements.insert(model_id,StageElement{
|
self.elements.insert(model,StageElement{
|
||||||
stage: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_id in &stage.unordered_checkpoints{
|
for &model in &stage.unordered_checkpoints{
|
||||||
self.elements.insert(model_id,StageElement{
|
self.elements.insert(model,StageElement{
|
||||||
stage: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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//this would be nice as a macro
|
||||||
#[derive(Default)]
|
#[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 struct Modes{
|
||||||
modes:Vec<Mode>,
|
pub modes:Vec<Mode>,
|
||||||
}
|
}
|
||||||
impl Modes{
|
impl Modes{
|
||||||
pub fn clear(&mut self){
|
pub const fn new(modes:Vec<Mode>)->Self{
|
||||||
self.modes.clear();
|
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>{
|
pub fn get_mode(&self,mode:ModeId)->Option<&Mode>{
|
||||||
self.modes.get(mode.0 as usize)
|
self.modes.get(mode.0 as usize)
|
||||||
}
|
}
|
||||||
pub fn insert(&mut self,mode:Mode){
|
}
|
||||||
self.modes.push(mode);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,247 +1,399 @@
|
|||||||
const VALVE_SCALE:i64=16;
|
const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
|
||||||
|
|
||||||
use crate::integer::{Time,Ratio64,Planar64,Planar64Vec3};
|
use crate::integer::{int,vec3::int as int3,Time,Ratio64,Planar64,Planar64Vec3};
|
||||||
|
use crate::controls_bitflag::Controls;
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
pub struct StyleModifiers{
|
pub struct StyleModifiers{
|
||||||
controls_used:u32,//controls which are allowed to pass into gameplay
|
//controls which are allowed to pass into gameplay (usually all)
|
||||||
controls_mask:u32,//controls which are masked from control state (e.g. jump in scroll style)
|
pub controls_mask:Controls,
|
||||||
strafe:Option<StrafeSettings>,
|
//controls which are masked from control state (e.g. !jump in scroll style)
|
||||||
jump_impulse:JumpImpulse,
|
pub controls_mask_state:Controls,
|
||||||
jump_calculation:JumpCalculation,
|
//strafing
|
||||||
static_friction:Planar64,
|
pub strafe:Option<StrafeSettings>,
|
||||||
kinetic_friction:Planar64,
|
//player gets a controllable rocket force
|
||||||
walk_speed:Planar64,
|
pub rocket:Option<PropulsionSettings>,
|
||||||
walk_accel:Planar64,
|
//flying
|
||||||
ladder_speed:Planar64,
|
//pub move_type:MoveType::Fly(FlySettings)
|
||||||
ladder_accel:Planar64,
|
//MoveType::Physics(PhysicsSettings) -> PhysicsSettings (strafe,rocket,jump,walk,ladder,swim,gravity)
|
||||||
ladder_dot:Planar64,
|
//jumping is allowed
|
||||||
swim_speed:Planar64,
|
pub jump:Option<JumpSettings>,
|
||||||
mass:Planar64,
|
//standing & walking is allowed
|
||||||
mv:Planar64,
|
pub walk:Option<WalkSettings>,
|
||||||
surf_slope:Option<Planar64>,
|
//laddering is allowed
|
||||||
rocket_force:Option<Planar64>,
|
pub ladder:Option<LadderSettings>,
|
||||||
gravity:Planar64Vec3,
|
//water propulsion
|
||||||
hitbox:Hitbox,
|
pub swim:Option<PropulsionSettings>,
|
||||||
camera_offset:Planar64Vec3,
|
//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{
|
impl std::default::Default for StyleModifiers{
|
||||||
fn default()->Self{
|
fn default()->Self{
|
||||||
Self::roblox_bhop()
|
Self::roblox_bhop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl StyleModifiers{
|
|
||||||
const CONTROL_MOVEFORWARD:u32=0b00000001;
|
|
||||||
const CONTROL_MOVEBACK:u32=0b00000010;
|
|
||||||
const CONTROL_MOVERIGHT:u32=0b00000100;
|
|
||||||
const CONTROL_MOVELEFT:u32=0b00001000;
|
|
||||||
const CONTROL_MOVEUP:u32=0b00010000;
|
|
||||||
const CONTROL_MOVEDOWN:u32=0b00100000;
|
|
||||||
const CONTROL_JUMP:u32=0b01000000;
|
|
||||||
const CONTROL_ZOOM:u32=0b10000000;
|
|
||||||
|
|
||||||
const RIGHT_DIR:Planar64Vec3=Planar64Vec3::X;
|
#[derive(Clone,Debug)]
|
||||||
const UP_DIR:Planar64Vec3=Planar64Vec3::Y;
|
pub enum JumpCalculation{
|
||||||
const FORWARD_DIR:Planar64Vec3=Planar64Vec3::NEG_Z;
|
Max,//Roblox: jumped_speed=max(velocity.boost(),velocity.jump())
|
||||||
|
BoostThenJump,//jumped_speed=velocity.boost().jump()
|
||||||
fn neo()->Self{
|
JumpThenBoost,//jumped_speed=velocity.jump().boost()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum JumpCalculation{
|
#[derive(Clone,Debug)]
|
||||||
Capped,//roblox
|
pub enum JumpImpulse{
|
||||||
Energy,//new
|
Time(Time),//jump time is invariant across mass and gravity changes
|
||||||
Linear,//source
|
Height(Planar64),//jump height is invariant across mass and gravity changes
|
||||||
}
|
Linear(Planar64),//jump velocity is invariant across mass and gravity changes
|
||||||
|
Energy(Planar64),// :)
|
||||||
enum JumpImpulse{
|
|
||||||
FromTime(Time),//jump time is invariant across mass and gravity changes
|
|
||||||
FromHeight(Planar64),//jump height is invariant across mass and gravity changes
|
|
||||||
FromDeltaV(Planar64),//jump velocity is invariant across mass and gravity changes
|
|
||||||
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum EnableStrafe{
|
#[derive(Clone,Debug)]
|
||||||
Always,
|
pub struct JumpSettings{
|
||||||
MaskAny(u32),//hsw, shsw
|
//information used to calculate jump power
|
||||||
MaskAll(u32),
|
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>),
|
//Function(Box<dyn Fn(u32)->bool>),
|
||||||
}
|
}
|
||||||
|
impl ControlsActivation{
|
||||||
struct StrafeSettings{
|
pub const fn mask(&self,controls:Controls)->Controls{
|
||||||
enable:EnableStrafe,
|
controls.intersection(self.controls_mask)
|
||||||
air_accel_limit:Option<Planar64>,
|
}
|
||||||
tick_rate:Ratio64,
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
enum HitboxMesh{
|
#[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:Time)->Time{
|
||||||
|
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{
|
||||||
|
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
|
Box,//source
|
||||||
Cylinder,//roblox
|
Cylinder,//roblox
|
||||||
//Sphere,//roblox old physics
|
//Sphere,//roblox old physics
|
||||||
@ -250,21 +402,210 @@ enum HitboxMesh{
|
|||||||
//DualCone,
|
//DualCone,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Hitbox{
|
#[derive(Clone,Debug)]
|
||||||
halfsize:Planar64Vec3,
|
pub struct Hitbox{
|
||||||
mesh:HitboxMesh,
|
pub halfsize:Planar64Vec3,
|
||||||
|
pub mesh:HitboxMesh,
|
||||||
}
|
}
|
||||||
impl Hitbox{
|
impl Hitbox{
|
||||||
fn roblox()->Self{
|
pub fn roblox()->Self{
|
||||||
Self{
|
Self{
|
||||||
halfsize:Planar64Vec3::int(2,5,2)/2,
|
halfsize:int3(2,5,2)>>1,
|
||||||
mesh:HitboxMesh::Cylinder,
|
mesh:HitboxMesh::Cylinder,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn source()->Self{
|
pub fn source()->Self{
|
||||||
Self{
|
Self{
|
||||||
halfsize:Planar64Vec3::raw(33,73,33)/2/VALVE_SCALE,
|
halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).fix_1(),
|
||||||
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,14 +19,14 @@ pub struct InstructionCollector<I>{
|
|||||||
instruction:Option<I>,
|
instruction:Option<I>,
|
||||||
}
|
}
|
||||||
impl<I> InstructionCollector<I>{
|
impl<I> InstructionCollector<I>{
|
||||||
pub fn new(time:Time)->Self{
|
pub const fn new(time:Time)->Self{
|
||||||
Self{
|
Self{
|
||||||
time,
|
time,
|
||||||
instruction:None
|
instruction:None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn time(&self)->Time{
|
pub const 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
888
src/integer.rs
888
src/integer.rs
File diff suppressed because it is too large
Load Diff
10
src/lib.rs
10
src/lib.rs
@ -1,9 +1,15 @@
|
|||||||
pub mod bvh;
|
pub mod bvh;
|
||||||
|
pub mod map;
|
||||||
|
pub mod run;
|
||||||
pub mod aabb;
|
pub mod aabb;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod zeroes;
|
pub mod mouse;
|
||||||
|
pub mod timer;
|
||||||
pub mod integer;
|
pub mod integer;
|
||||||
|
pub mod physics;
|
||||||
|
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 gameplay_attributes;
|
pub mod controls_bitflag;
|
||||||
|
14
src/map.rs
Normal file
14
src/map.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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>,
|
||||||
|
}
|
121
src/model.rs
121
src/model.rs
@ -3,51 +3,130 @@ use crate::gameplay_attributes;
|
|||||||
|
|
||||||
pub type TextureCoordinate=glam::Vec2;
|
pub type TextureCoordinate=glam::Vec2;
|
||||||
pub type Color4=glam::Vec4;
|
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)]
|
#[derive(Clone,Hash,PartialEq,Eq)]
|
||||||
pub struct IndexedVertex{
|
pub struct IndexedVertex{
|
||||||
pub pos:u32,
|
pub pos:PositionId,
|
||||||
pub tex:u32,
|
pub tex:TextureCoordinateId,
|
||||||
pub normal:u32,
|
pub normal:NormalId,
|
||||||
pub color:u32,
|
pub color:ColorId,
|
||||||
}
|
}
|
||||||
|
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
|
||||||
pub struct VertexId(u32);
|
pub struct VertexId(u32);
|
||||||
pub struct IndexedVertexList{
|
pub type IndexedVertexList=Vec<VertexId>;
|
||||||
pub vertices:Vec<VertexId>,
|
pub trait PolygonIter{
|
||||||
|
fn polys(&self)->impl Iterator<Item=&[VertexId]>;
|
||||||
}
|
}
|
||||||
pub struct GroupId(u32);
|
pub trait MapVertexId{
|
||||||
pub enum IndexedGroup{
|
fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self;
|
||||||
PolygonList(Vec<IndexedVertexList>),
|
|
||||||
//TriangleStrip(Vec<IndexedVertexList>),
|
|
||||||
}
|
}
|
||||||
pub struct RenderId(u32);
|
#[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{
|
pub struct IndexedGraphicsGroup{
|
||||||
//Render pattern material/texture/shader/flat color
|
//Render pattern material/texture/shader/flat color
|
||||||
pub render:RenderId,
|
pub render:RenderConfigId,
|
||||||
pub groups:Vec<GroupId>,
|
pub groups:Vec<PolygonGroupId>,
|
||||||
}
|
}
|
||||||
|
#[derive(Clone,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<GroupId>,
|
pub groups:Vec<PolygonGroupId>,
|
||||||
}
|
}
|
||||||
//This is a superset of PhysicsModel and GraphicsModel
|
//This is a superset of PhysicsModel and GraphicsModel
|
||||||
pub struct IndexedModel{
|
#[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_pos:Vec<Planar64Vec3>,//Unit32Vec3
|
||||||
pub unique_normal:Vec<Planar64Vec3>,//Unit32Vec3
|
pub unique_normal:Vec<Planar64Vec3>,//Unit32Vec3
|
||||||
pub unique_tex:Vec<TextureCoordinate>,
|
pub unique_tex:Vec<TextureCoordinate>,
|
||||||
pub unique_color:Vec<Color4>,
|
pub unique_color:Vec<Color4>,
|
||||||
pub unique_vertices:Vec<IndexedVertex>,
|
pub unique_vertices:Vec<IndexedVertex>,
|
||||||
//groups are constant texture AND convexity slices
|
//polygon groups are constant texture AND convexity slices
|
||||||
pub groups:Vec<IndexedGroup>,
|
//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)
|
//graphics indexed (by texture)
|
||||||
pub graphics_sets:Vec<IndexedGraphicsGroup>,
|
pub graphics_groups:Vec<IndexedGraphicsGroup>,
|
||||||
//physics indexed (by convexity)
|
//physics indexed (by convexity)
|
||||||
pub physics_sets:Vec<IndexedPhysicsGroup>,
|
pub physics_groups:Vec<IndexedPhysicsGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
|
||||||
pub struct ModelId(u32);
|
pub struct ModelId(u32);
|
||||||
pub struct Model{
|
pub struct Model{
|
||||||
pub model:ModelId,
|
pub mesh:MeshId,
|
||||||
pub attributes:gameplay_attributes::CollisionAttributesId,
|
pub attributes:gameplay_attributes::CollisionAttributesId,
|
||||||
pub color:Color4,//transparency is in here
|
pub color:Color4,//transparency is in here
|
||||||
pub transform:Planar64Affine3,
|
pub transform:Planar64Affine3,
|
||||||
|
26
src/mouse.rs
Normal file
26
src/mouse.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
27
src/physics.rs
Normal file
27
src/physics.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#[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),
|
||||||
|
}
|
103
src/run.rs
Normal file
103
src/run.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
319
src/timer.rs
Normal file
319
src/timer.rs
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
56
src/updatable.rs
Normal file
56
src/updatable.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//*/
|
@ -1,40 +0,0 @@
|
|||||||
//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![];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user