forked from StrafesNET/strafe-project
Compare commits
35 Commits
roblox-bot
...
master
Author | SHA1 | Date | |
---|---|---|---|
0dc462a3b1 | |||
ca003edbc3 | |||
16abe23e97 | |||
67f8569178 | |||
121c9c5258 | |||
411b997b87 | |||
4d587b6c0b | |||
6ff74f08ab | |||
08f419f931 | |||
6066e82fd2 | |||
ca8035cdfc | |||
ff5d954cfb | |||
a967f31004 | |||
8ad5d28e51 | |||
ab05893813 | |||
2f7597146e | |||
004e0d3776 | |||
120d8197b7 | |||
36ba73a892 | |||
86cf7e74b1 | |||
24787fede5 | |||
3797408bc8 | |||
47c9b77b00 | |||
479e657251 | |||
63fbc94287 | |||
1318ae20ca | |||
851d9c935d | |||
d0a190861c | |||
4dca7fc369 | |||
62dfe23539 | |||
3991cb5064 | |||
1dc2556d85 | |||
4f21985290 | |||
ccce54c1a3 | |||
02bb2d797c |
@ -1,2 +1,6 @@
|
|||||||
[registries.strafesnet]
|
[registries.strafesnet]
|
||||||
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||||
|
|
||||||
|
[target.x86_64-unknown-linux-gnu]
|
||||||
|
linker = "clang"
|
||||||
|
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"]
|
||||||
|
72
Cargo.lock
generated
72
Cargo.lock
generated
@ -67,12 +67,6 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "android-tzdata"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_system_properties"
|
name = "android_system_properties"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@ -367,20 +361,6 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chrono"
|
|
||||||
version = "0.4.39"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
|
||||||
dependencies = [
|
|
||||||
"android-tzdata",
|
|
||||||
"iana-time-zone",
|
|
||||||
"js-sys",
|
|
||||||
"num-traits",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"windows-targets 0.52.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codespan-reporting"
|
name = "codespan-reporting"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@ -801,29 +781,6 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iana-time-zone"
|
|
||||||
version = "0.1.61"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
|
||||||
dependencies = [
|
|
||||||
"android_system_properties",
|
|
||||||
"core-foundation-sys",
|
|
||||||
"iana-time-zone-haiku",
|
|
||||||
"js-sys",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"windows-core 0.52.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iana-time-zone-haiku"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@ -2271,8 +2228,8 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
|||||||
name = "strafe-client"
|
name = "strafe-client"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"chrono",
|
|
||||||
"configparser",
|
"configparser",
|
||||||
"ddsfile",
|
"ddsfile",
|
||||||
"glam",
|
"glam",
|
||||||
@ -2283,11 +2240,7 @@ dependencies = [
|
|||||||
"strafesnet_common",
|
"strafesnet_common",
|
||||||
"strafesnet_deferred_loader",
|
"strafesnet_deferred_loader",
|
||||||
"strafesnet_rbx_loader",
|
"strafesnet_rbx_loader",
|
||||||
"strafesnet_roblox_bot_file",
|
|
||||||
"strafesnet_snf",
|
"strafesnet_snf",
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"web-sys",
|
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
@ -2340,16 +2293,6 @@ dependencies = [
|
|||||||
"strafesnet_common",
|
"strafesnet_common",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strafesnet_roblox_bot_file"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
|
||||||
checksum = "e9c0c75ba772a5c430083c73d352049b84dd218947473189552d2e10785087b6"
|
|
||||||
dependencies = [
|
|
||||||
"binrw",
|
|
||||||
"bitflags 2.6.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strafesnet_snf"
|
name = "strafesnet_snf"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -2949,7 +2892,7 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
"wgpu-types",
|
"wgpu-types",
|
||||||
"windows",
|
"windows",
|
||||||
"windows-core 0.58.0",
|
"windows-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2978,16 +2921,7 @@ version = "0.58.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core 0.58.0",
|
"windows-core",
|
||||||
"windows-targets 0.52.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-core"
|
|
||||||
version = "0.52.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -18,13 +18,8 @@ bitflags::bitflags!{
|
|||||||
const Use=1<<14;//Interact with object
|
const Use=1<<14;//Interact with object
|
||||||
const PrimaryAction=1<<15;//LBM/Shoot/Melee
|
const PrimaryAction=1<<15;//LBM/Shoot/Melee
|
||||||
const SecondaryAction=1<<16;//RMB/ADS/Block
|
const SecondaryAction=1<<16;//RMB/ADS/Block
|
||||||
}
|
|
||||||
}
|
const WASD=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).bits();
|
||||||
impl Controls{
|
const WASDQE=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown).bits();
|
||||||
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,5 +1,5 @@
|
|||||||
use crate::model;
|
use crate::model;
|
||||||
use crate::integer::{Time,Planar64,Planar64Vec3};
|
use crate::integer::{AbsoluteTime,Planar64,Planar64Vec3};
|
||||||
|
|
||||||
//you have this effect while in contact
|
//you have this effect while in contact
|
||||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||||
@ -31,7 +31,7 @@ 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
|
AirTime(AbsoluteTime),//increase airtime, invariant across mass and gravity changes
|
||||||
Height(Planar64),//increase height, invariant across mass and gravity changes
|
Height(Planar64),//increase height, invariant across mass and gravity changes
|
||||||
}
|
}
|
||||||
impl Booster{
|
impl Booster{
|
||||||
@ -57,13 +57,13 @@ pub enum TrajectoryChoice{
|
|||||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||||
pub enum SetTrajectory{
|
pub enum SetTrajectory{
|
||||||
//Speed-type SetTrajectory
|
//Speed-type SetTrajectory
|
||||||
AirTime(Time),//air time (relative to gravity direction) is invariant across mass and gravity changes
|
AirTime(AbsoluteTime),//air time (relative to gravity direction) is invariant across mass and gravity changes
|
||||||
Height(Planar64),//boost height (relative to gravity direction) is invariant across mass and gravity changes
|
Height(Planar64),//boost height (relative to gravity direction) is invariant across mass and gravity changes
|
||||||
DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions
|
DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions
|
||||||
//Velocity-type SetTrajectory
|
//Velocity-type SetTrajectory
|
||||||
TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time
|
TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time
|
||||||
target_point:Planar64Vec3,
|
target_point:Planar64Vec3,
|
||||||
time:Time,//short time = fast and direct, long time = launch high in the air, negative time = wrong way
|
time:AbsoluteTime,//short time = fast and direct, long time = launch high in the air, negative time = wrong way
|
||||||
},
|
},
|
||||||
TargetPointSpeed{//launch at a fixed speed and land at a target point
|
TargetPointSpeed{//launch at a fixed speed and land at a target point
|
||||||
target_point:Planar64Vec3,
|
target_point:Planar64Vec3,
|
||||||
|
@ -110,6 +110,7 @@ impl Stage{
|
|||||||
pub fn into_inner(self)->(HashMap<CheckpointId,ModelId>,HashSet<ModelId>){
|
pub fn into_inner(self)->(HashMap<CheckpointId,ModelId>,HashSet<ModelId>){
|
||||||
(self.ordered_checkpoints,self.unordered_checkpoints)
|
(self.ordered_checkpoints,self.unordered_checkpoints)
|
||||||
}
|
}
|
||||||
|
/// Returns true if the stage has no checkpoints.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn is_empty(&self)->bool{
|
pub const fn is_empty(&self)->bool{
|
||||||
self.is_complete(0,0)
|
self.is_complete(0,0)
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
|
const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
|
||||||
|
|
||||||
use crate::integer::{int,vec3::int as int3,Time,Ratio64,Planar64,Planar64Vec3};
|
use crate::integer::{int,vec3::int as int3,AbsoluteTime,Ratio64,Planar64,Planar64Vec3};
|
||||||
use crate::controls_bitflag::Controls;
|
use crate::controls_bitflag::Controls;
|
||||||
|
use crate::physics::Time as PhysicsTime;
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct StyleModifiers{
|
pub struct StyleModifiers{
|
||||||
@ -48,7 +49,7 @@ pub enum JumpCalculation{
|
|||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum JumpImpulse{
|
pub enum JumpImpulse{
|
||||||
Time(Time),//jump time is invariant across mass and gravity changes
|
Time(AbsoluteTime),//jump time is invariant across mass and gravity changes
|
||||||
Height(Planar64),//jump height is invariant across mass and gravity changes
|
Height(Planar64),//jump height is invariant across mass and gravity changes
|
||||||
Linear(Planar64),//jump velocity is invariant across mass and gravity changes
|
Linear(Planar64),//jump velocity is invariant across mass and gravity changes
|
||||||
Energy(Planar64),// :)
|
Energy(Planar64),// :)
|
||||||
@ -199,8 +200,8 @@ impl ControlsActivation{
|
|||||||
}
|
}
|
||||||
pub const fn full_3d()->Self{
|
pub const fn full_3d()->Self{
|
||||||
Self{
|
Self{
|
||||||
controls_mask:Controls::wasdqe(),
|
controls_mask:Controls::WASDQE,
|
||||||
controls_intersects:Controls::wasdqe(),
|
controls_intersects:Controls::WASDQE,
|
||||||
controls_contains:Controls::empty(),
|
controls_contains:Controls::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,8 +209,8 @@ impl ControlsActivation{
|
|||||||
//Normal
|
//Normal
|
||||||
pub const fn full_2d()->Self{
|
pub const fn full_2d()->Self{
|
||||||
Self{
|
Self{
|
||||||
controls_mask:Controls::wasd(),
|
controls_mask:Controls::WASD,
|
||||||
controls_intersects:Controls::wasd(),
|
controls_intersects:Controls::WASD,
|
||||||
controls_contains:Controls::empty(),
|
controls_contains:Controls::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,8 +273,8 @@ impl StrafeSettings{
|
|||||||
false=>None,
|
false=>None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn next_tick(&self,time:Time)->Time{
|
pub fn next_tick(&self,time:PhysicsTime)->PhysicsTime{
|
||||||
Time::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1))
|
PhysicsTime::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1))
|
||||||
}
|
}
|
||||||
pub const fn activates(&self,controls:Controls)->bool{
|
pub const fn activates(&self,controls:Controls)->bool{
|
||||||
self.enable.activates(controls)
|
self.enable.activates(controls)
|
||||||
@ -435,7 +436,7 @@ impl StyleModifiers{
|
|||||||
enable:ControlsActivation::full_2d(),
|
enable:ControlsActivation::full_2d(),
|
||||||
air_accel_limit:None,
|
air_accel_limit:None,
|
||||||
mv:int(3),
|
mv:int(3),
|
||||||
tick_rate:Ratio64::new(64,Time::ONE_SECOND.nanos() as u64).unwrap(),
|
tick_rate:Ratio64::new(64,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||||
}),
|
}),
|
||||||
jump:Some(JumpSettings{
|
jump:Some(JumpSettings{
|
||||||
impulse:JumpImpulse::Energy(int(512)),
|
impulse:JumpImpulse::Energy(int(512)),
|
||||||
@ -477,10 +478,10 @@ impl StyleModifiers{
|
|||||||
enable:ControlsActivation::full_2d(),
|
enable:ControlsActivation::full_2d(),
|
||||||
air_accel_limit:None,
|
air_accel_limit:None,
|
||||||
mv:int(27)/10,
|
mv:int(27)/10,
|
||||||
tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
|
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||||
}),
|
}),
|
||||||
jump:Some(JumpSettings{
|
jump:Some(JumpSettings{
|
||||||
impulse:JumpImpulse::Time(Time::from_micros(715_588)),
|
impulse:JumpImpulse::Time(AbsoluteTime::from_micros(715_588)),
|
||||||
calculation:JumpCalculation::Max,
|
calculation:JumpCalculation::Max,
|
||||||
limit_minimum:true,
|
limit_minimum:true,
|
||||||
}),
|
}),
|
||||||
@ -534,7 +535,7 @@ impl StyleModifiers{
|
|||||||
enable:ControlsActivation::full_2d(),
|
enable:ControlsActivation::full_2d(),
|
||||||
air_accel_limit:Some(Planar64::raw(150<<28)*100),
|
air_accel_limit:Some(Planar64::raw(150<<28)*100),
|
||||||
mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(),
|
mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(),
|
||||||
tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
|
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||||
}),
|
}),
|
||||||
jump:Some(JumpSettings{
|
jump:Some(JumpSettings{
|
||||||
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
|
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
|
||||||
@ -575,7 +576,7 @@ impl StyleModifiers{
|
|||||||
enable:ControlsActivation::full_2d(),
|
enable:ControlsActivation::full_2d(),
|
||||||
air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()),
|
air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()),
|
||||||
mv:(int(30)*VALVE_SCALE).fix_1(),
|
mv:(int(30)*VALVE_SCALE).fix_1(),
|
||||||
tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() as u64).unwrap(),
|
tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||||
}),
|
}),
|
||||||
jump:Some(JumpSettings{
|
jump:Some(JumpSettings{
|
||||||
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
|
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
|
||||||
|
@ -1,35 +1,57 @@
|
|||||||
use crate::integer::Time;
|
use crate::integer::Time;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TimedInstruction<I>{
|
pub struct TimedInstruction<I,T>{
|
||||||
pub time:Time,
|
pub time:Time<T>,
|
||||||
pub instruction:I,
|
pub instruction:I,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure all emitted instructions are processed before consuming external instructions
|
||||||
pub trait InstructionEmitter<I>{
|
pub trait InstructionEmitter<I>{
|
||||||
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<I>>;
|
type TimeInner;
|
||||||
|
fn next_instruction(&self,time_limit:Time<Self::TimeInner>)->Option<TimedInstruction<I,Self::TimeInner>>;
|
||||||
}
|
}
|
||||||
|
/// Apply an atomic state update
|
||||||
pub trait InstructionConsumer<I>{
|
pub trait InstructionConsumer<I>{
|
||||||
fn process_instruction(&mut self, instruction:TimedInstruction<I>);
|
type TimeInner;
|
||||||
|
fn process_instruction(&mut self,instruction:TimedInstruction<I,Self::TimeInner>);
|
||||||
}
|
}
|
||||||
|
/// If the object produces its own instructions, allow exhaustively feeding them back in
|
||||||
|
pub trait InstructionFeedback<I,T>:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>
|
||||||
|
where
|
||||||
|
Time<T>:Copy,
|
||||||
|
{
|
||||||
|
fn process_exhaustive(&mut self,time_limit:Time<T>){
|
||||||
|
while let Some(instruction)=self.next_instruction(time_limit){
|
||||||
|
self.process_instruction(instruction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<I,T,X> InstructionFeedback<I,T> for X
|
||||||
|
where
|
||||||
|
Time<T>:Copy,
|
||||||
|
X:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>,
|
||||||
|
{}
|
||||||
|
|
||||||
//PROPER PRIVATE FIELDS!!!
|
//PROPER PRIVATE FIELDS!!!
|
||||||
pub struct InstructionCollector<I>{
|
pub struct InstructionCollector<I,T>{
|
||||||
time:Time,
|
time:Time<T>,
|
||||||
instruction:Option<I>,
|
instruction:Option<I>,
|
||||||
}
|
}
|
||||||
impl<I> InstructionCollector<I>{
|
impl<I,T> InstructionCollector<I,T>
|
||||||
pub const fn new(time:Time)->Self{
|
where Time<T>:Copy+PartialOrd,
|
||||||
|
{
|
||||||
|
pub const fn new(time:Time<T>)->Self{
|
||||||
Self{
|
Self{
|
||||||
time,
|
time,
|
||||||
instruction:None
|
instruction:None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn time(&self)->Time{
|
pub const fn time(&self)->Time<T>{
|
||||||
self.time
|
self.time
|
||||||
}
|
}
|
||||||
pub fn collect(&mut self,instruction:Option<TimedInstruction<I>>){
|
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
|
||||||
match instruction{
|
match instruction{
|
||||||
Some(unwrap_instruction)=>{
|
Some(unwrap_instruction)=>{
|
||||||
if unwrap_instruction.time<self.time {
|
if unwrap_instruction.time<self.time {
|
||||||
@ -40,7 +62,7 @@ impl<I> InstructionCollector<I>{
|
|||||||
None=>(),
|
None=>(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn instruction(self)->Option<TimedInstruction<I>>{
|
pub fn instruction(self)->Option<TimedInstruction<I,T>>{
|
||||||
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
||||||
match self.instruction{
|
match self.instruction{
|
||||||
Some(instruction)=>Some(TimedInstruction{
|
Some(instruction)=>Some(TimedInstruction{
|
||||||
|
@ -2,19 +2,25 @@ pub use fixed_wide::fixed::{Fixed,Fix};
|
|||||||
pub use ratio_ops::ratio::{Ratio,Divide};
|
pub use ratio_ops::ratio::{Ratio,Divide};
|
||||||
|
|
||||||
//integer units
|
//integer units
|
||||||
|
|
||||||
|
/// specific example of a "default" time type
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
pub struct Time(i64);
|
pub enum TimeInner{}
|
||||||
impl Time{
|
pub type AbsoluteTime=Time<TimeInner>;
|
||||||
pub const MIN:Self=Self(i64::MIN);
|
|
||||||
pub const MAX:Self=Self(i64::MAX);
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
pub const ZERO:Self=Self(0);
|
pub struct Time<T>(i64,core::marker::PhantomData<T>);
|
||||||
pub const ONE_SECOND:Self=Self(1_000_000_000);
|
impl<T> Time<T>{
|
||||||
pub const ONE_MILLISECOND:Self=Self(1_000_000);
|
pub const MIN:Self=Self::raw(i64::MIN);
|
||||||
pub const ONE_MICROSECOND:Self=Self(1_000);
|
pub const MAX:Self=Self::raw(i64::MAX);
|
||||||
pub const ONE_NANOSECOND:Self=Self(1);
|
pub const ZERO:Self=Self::raw(0);
|
||||||
|
pub const ONE_SECOND:Self=Self::raw(1_000_000_000);
|
||||||
|
pub const ONE_MILLISECOND:Self=Self::raw(1_000_000);
|
||||||
|
pub const ONE_MICROSECOND:Self=Self::raw(1_000);
|
||||||
|
pub const ONE_NANOSECOND:Self=Self::raw(1);
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn raw(num:i64)->Self{
|
pub const fn raw(num:i64)->Self{
|
||||||
Self(num)
|
Self(num,core::marker::PhantomData)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn get(self)->i64{
|
pub const fn get(self)->i64{
|
||||||
@ -22,19 +28,19 @@ impl Time{
|
|||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn from_secs(num:i64)->Self{
|
pub const fn from_secs(num:i64)->Self{
|
||||||
Self(Self::ONE_SECOND.0*num)
|
Self::raw(Self::ONE_SECOND.0*num)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn from_millis(num:i64)->Self{
|
pub const fn from_millis(num:i64)->Self{
|
||||||
Self(Self::ONE_MILLISECOND.0*num)
|
Self::raw(Self::ONE_MILLISECOND.0*num)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn from_micros(num:i64)->Self{
|
pub const fn from_micros(num:i64)->Self{
|
||||||
Self(Self::ONE_MICROSECOND.0*num)
|
Self::raw(Self::ONE_MICROSECOND.0*num)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn from_nanos(num:i64)->Self{
|
pub const fn from_nanos(num:i64)->Self{
|
||||||
Self(Self::ONE_NANOSECOND.0*num)
|
Self::raw(Self::ONE_NANOSECOND.0*num)
|
||||||
}
|
}
|
||||||
//should I have checked subtraction? force all time variables to be positive?
|
//should I have checked subtraction? force all time variables to be positive?
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -45,14 +51,18 @@ impl Time{
|
|||||||
pub const fn to_ratio(self)->Ratio<Planar64,Planar64>{
|
pub const fn to_ratio(self)->Ratio<Planar64,Planar64>{
|
||||||
Ratio::new(Planar64::raw(self.0),Planar64::raw(1_000_000_000))
|
Ratio::new(Planar64::raw(self.0),Planar64::raw(1_000_000_000))
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
|
pub const fn coerce<U>(self)->Time<U>{
|
||||||
|
Time::raw(self.0)
|
||||||
}
|
}
|
||||||
impl From<Planar64> for Time{
|
}
|
||||||
|
impl<T> From<Planar64> for Time<T>{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(value:Planar64)->Self{
|
fn from(value:Planar64)->Self{
|
||||||
Time((value*Planar64::raw(1_000_000_000)).fix_1().to_raw())
|
Self::raw((value*Planar64::raw(1_000_000_000)).fix_1().to_raw())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<Num,Den,N1,T1> From<Ratio<Num,Den>> for Time
|
impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T>
|
||||||
where
|
where
|
||||||
Num:core::ops::Mul<Planar64,Output=N1>,
|
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||||
N1:Divide<Den,Output=T1>,
|
N1:Divide<Den,Output=T1>,
|
||||||
@ -60,34 +70,34 @@ impl<Num,Den,N1,T1> From<Ratio<Num,Den>> for Time
|
|||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(value:Ratio<Num,Den>)->Self{
|
fn from(value:Ratio<Num,Den>)->Self{
|
||||||
Time((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw())
|
Self::raw((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::fmt::Display for Time{
|
impl<T> std::fmt::Display for Time<T>{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
write!(f,"{}s+{:09}ns",self.0/Self::ONE_SECOND.0,self.0%Self::ONE_SECOND.0)
|
write!(f,"{}s+{:09}ns",self.0/Self::ONE_SECOND.0,self.0%Self::ONE_SECOND.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::default::Default for Time{
|
impl<T> std::default::Default for Time<T>{
|
||||||
fn default()->Self{
|
fn default()->Self{
|
||||||
Self(0)
|
Self::raw(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::ops::Neg for Time{
|
impl<T> std::ops::Neg for Time<T>{
|
||||||
type Output=Time;
|
type Output=Self;
|
||||||
#[inline]
|
#[inline]
|
||||||
fn neg(self)->Self::Output {
|
fn neg(self)->Self::Output {
|
||||||
Time(-self.0)
|
Self::raw(-self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
macro_rules! impl_time_additive_operator {
|
macro_rules! impl_time_additive_operator {
|
||||||
($trait:ty, $method:ident) => {
|
($trait:ty, $method:ident) => {
|
||||||
impl $trait for Time{
|
impl<T> $trait for Time<T>{
|
||||||
type Output=Time;
|
type Output=Self;
|
||||||
#[inline]
|
#[inline]
|
||||||
fn $method(self,rhs:Self)->Self::Output {
|
fn $method(self,rhs:Self)->Self::Output {
|
||||||
Time(self.0.$method(rhs.0))
|
Self::raw(self.0.$method(rhs.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -97,7 +107,7 @@ impl_time_additive_operator!(core::ops::Sub,sub);
|
|||||||
impl_time_additive_operator!(core::ops::Rem,rem);
|
impl_time_additive_operator!(core::ops::Rem,rem);
|
||||||
macro_rules! impl_time_additive_assign_operator {
|
macro_rules! impl_time_additive_assign_operator {
|
||||||
($trait:ty, $method:ident) => {
|
($trait:ty, $method:ident) => {
|
||||||
impl $trait for Time{
|
impl<T> $trait for Time<T>{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn $method(&mut self,rhs:Self){
|
fn $method(&mut self,rhs:Self){
|
||||||
self.0.$method(rhs.0)
|
self.0.$method(rhs.0)
|
||||||
@ -108,33 +118,37 @@ macro_rules! impl_time_additive_assign_operator {
|
|||||||
impl_time_additive_assign_operator!(core::ops::AddAssign,add_assign);
|
impl_time_additive_assign_operator!(core::ops::AddAssign,add_assign);
|
||||||
impl_time_additive_assign_operator!(core::ops::SubAssign,sub_assign);
|
impl_time_additive_assign_operator!(core::ops::SubAssign,sub_assign);
|
||||||
impl_time_additive_assign_operator!(core::ops::RemAssign,rem_assign);
|
impl_time_additive_assign_operator!(core::ops::RemAssign,rem_assign);
|
||||||
impl std::ops::Mul for Time{
|
impl<T> std::ops::Mul for Time<T>{
|
||||||
type Output=Ratio<fixed_wide::fixed::Fixed<2,64>,fixed_wide::fixed::Fixed<2,64>>;
|
type Output=Ratio<fixed_wide::fixed::Fixed<2,64>,fixed_wide::fixed::Fixed<2,64>>;
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mul(self,rhs:Self)->Self::Output{
|
fn mul(self,rhs:Self)->Self::Output{
|
||||||
Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2)))
|
Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::ops::Div<i64> for Time{
|
impl<T> std::ops::Div<i64> for Time<T>{
|
||||||
type Output=Time;
|
type Output=Self;
|
||||||
#[inline]
|
#[inline]
|
||||||
fn div(self,rhs:i64)->Self::Output{
|
fn div(self,rhs:i64)->Self::Output{
|
||||||
Time(self.0/rhs)
|
Self::raw(self.0/rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::ops::Mul<i64> for Time{
|
impl<T> std::ops::Mul<i64> for Time<T>{
|
||||||
type Output=Time;
|
type Output=Self;
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mul(self,rhs:i64)->Self::Output{
|
fn mul(self,rhs:i64)->Self::Output{
|
||||||
Time(self.0*rhs)
|
Self::raw(self.0*rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl core::ops::Mul<Time> for Planar64{
|
impl<T> core::ops::Mul<Time<T>> for Planar64{
|
||||||
type Output=Ratio<Fixed<2,64>,Planar64>;
|
type Output=Ratio<Fixed<2,64>,Planar64>;
|
||||||
fn mul(self,rhs:Time)->Self::Output{
|
fn mul(self,rhs:Time<T>)->Self::Output{
|
||||||
Ratio::new(self*Fixed::raw(rhs.0),Planar64::raw(1_000_000_000))
|
Ratio::new(self*Fixed::raw(rhs.0),Planar64::raw(1_000_000_000))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_time{
|
||||||
|
use super::*;
|
||||||
|
type Time=super::AbsoluteTime;
|
||||||
#[test]
|
#[test]
|
||||||
fn time_from_planar64(){
|
fn time_from_planar64(){
|
||||||
let a:Time=Planar64::from(1).into();
|
let a:Time=Planar64::from(1).into();
|
||||||
@ -156,6 +170,7 @@ fn time_times_planar64(){
|
|||||||
let b=Planar64::from(2);
|
let b=Planar64::from(2);
|
||||||
assert_eq!(b*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000*(1<<32))<<2,Fixed::<1,32>::raw_digit(1_000_000_000)));
|
assert_eq!(b*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000*(1<<32))<<2,Fixed::<1,32>::raw_digit(1_000_000_000)));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn gcd(mut a:u64,mut b:u64)->u64{
|
const fn gcd(mut a:u64,mut b:u64)->u64{
|
||||||
|
@ -7,6 +7,7 @@ pub mod mouse;
|
|||||||
pub mod timer;
|
pub mod timer;
|
||||||
pub mod integer;
|
pub mod integer;
|
||||||
pub mod physics;
|
pub mod physics;
|
||||||
|
pub mod session;
|
||||||
pub mod updatable;
|
pub mod updatable;
|
||||||
pub mod instruction;
|
pub mod instruction;
|
||||||
pub mod gameplay_attributes;
|
pub mod gameplay_attributes;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use crate::integer::Time;
|
use crate::integer::Time;
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct MouseState{
|
pub struct MouseState<T>{
|
||||||
pub pos:glam::IVec2,
|
pub pos:glam::IVec2,
|
||||||
pub time:Time,
|
pub time:Time<T>,
|
||||||
}
|
}
|
||||||
impl Default for MouseState{
|
impl<T> Default for MouseState<T>{
|
||||||
fn default()->Self{
|
fn default()->Self{
|
||||||
Self{
|
Self{
|
||||||
time:Time::ZERO,
|
time:Time::ZERO,
|
||||||
@ -13,8 +13,10 @@ impl Default for MouseState{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl MouseState{
|
impl<T> MouseState<T>
|
||||||
pub fn lerp(&self,target:&MouseState,time:Time)->glam::IVec2{
|
where Time<T>:Copy,
|
||||||
|
{
|
||||||
|
pub fn lerp(&self,target:&MouseState<T>,time:Time<T>)->glam::IVec2{
|
||||||
let m0=self.pos.as_i64vec2();
|
let m0=self.pos.as_i64vec2();
|
||||||
let m1=target.pos.as_i64vec2();
|
let m1=target.pos.as_i64vec2();
|
||||||
//these are deltas
|
//these are deltas
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
|
pub enum TimeInner{}
|
||||||
|
pub type Time=crate::integer::Time<TimeInner>;
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum Instruction{
|
pub enum Instruction{
|
||||||
ReplaceMouse(crate::mouse::MouseState,crate::mouse::MouseState),
|
ReplaceMouse(crate::mouse::MouseState<TimeInner>,crate::mouse::MouseState<TimeInner>),
|
||||||
SetNextMouse(crate::mouse::MouseState),
|
SetNextMouse(crate::mouse::MouseState<TimeInner>),
|
||||||
SetMoveRight(bool),
|
SetMoveRight(bool),
|
||||||
SetMoveUp(bool),
|
SetMoveUp(bool),
|
||||||
SetMoveBack(bool),
|
SetMoveBack(bool),
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
use crate::timer::{TimerFixed,Realtime,Paused,Unpaused};
|
use crate::timer::{TimerFixed,Realtime,Paused,Unpaused};
|
||||||
use crate::integer::Time;
|
|
||||||
|
use crate::physics::{TimeInner as PhysicsTimeInner,Time as PhysicsTime};
|
||||||
|
|
||||||
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
|
pub enum TimeInner{}
|
||||||
|
pub type Time=crate::integer::Time<TimeInner>;
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
pub enum FlagReason{
|
pub enum FlagReason{
|
||||||
@ -15,6 +20,11 @@ pub enum FlagReason{
|
|||||||
}
|
}
|
||||||
impl ToString for FlagReason{
|
impl ToString for FlagReason{
|
||||||
fn to_string(&self)->String{
|
fn to_string(&self)->String{
|
||||||
|
self.as_ref().to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<str> for FlagReason{
|
||||||
|
fn as_ref(&self)->&str{
|
||||||
match self{
|
match self{
|
||||||
FlagReason::Anticheat=>"Passed through anticheat zone.",
|
FlagReason::Anticheat=>"Passed through anticheat zone.",
|
||||||
FlagReason::StyleChange=>"Changed style.",
|
FlagReason::StyleChange=>"Changed style.",
|
||||||
@ -25,7 +35,7 @@ impl ToString for FlagReason{
|
|||||||
FlagReason::Timescale=>"Timescale 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::TimeTravel=>"Time travel is not allowed in this style.",
|
||||||
FlagReason::Teleport=>"Illegal teleport.",
|
FlagReason::Teleport=>"Illegal teleport.",
|
||||||
}.to_owned()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,8 +55,8 @@ impl std::error::Error for Error{}
|
|||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
enum RunState{
|
enum RunState{
|
||||||
Created,
|
Created,
|
||||||
Started{timer:TimerFixed<Realtime,Unpaused>},
|
Started{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Unpaused>},
|
||||||
Finished{timer:TimerFixed<Realtime,Paused>},
|
Finished{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Paused>},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
@ -62,14 +72,14 @@ impl Run{
|
|||||||
flagged:None,
|
flagged:None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn time(&self,time:Time)->Time{
|
pub fn time(&self,time:PhysicsTime)->Time{
|
||||||
match &self.state{
|
match &self.state{
|
||||||
RunState::Created=>Time::ZERO,
|
RunState::Created=>Time::ZERO,
|
||||||
RunState::Started{timer}=>timer.time(time),
|
RunState::Started{timer}=>timer.time(time),
|
||||||
RunState::Finished{timer}=>timer.time(time),
|
RunState::Finished{timer}=>timer.time(time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn start(&mut self,time:Time)->Result<(),Error>{
|
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
|
||||||
match &self.state{
|
match &self.state{
|
||||||
RunState::Created=>{
|
RunState::Created=>{
|
||||||
self.state=RunState::Started{
|
self.state=RunState::Started{
|
||||||
@ -81,7 +91,7 @@ impl Run{
|
|||||||
RunState::Finished{..}=>Err(Error::AlreadyFinished),
|
RunState::Finished{..}=>Err(Error::AlreadyFinished),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn finish(&mut self,time:Time)->Result<(),Error>{
|
pub fn finish(&mut self,time:PhysicsTime)->Result<(),Error>{
|
||||||
//this uses Copy
|
//this uses Copy
|
||||||
match &self.state{
|
match &self.state{
|
||||||
RunState::Created=>Err(Error::NotStarted),
|
RunState::Created=>Err(Error::NotStarted),
|
||||||
|
3
lib/common/src/session.rs
Normal file
3
lib/common/src/session.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
|
pub enum TimeInner{}
|
||||||
|
pub type Time=crate::integer::Time<TimeInner>;
|
@ -22,79 +22,106 @@ impl PauseState for Unpaused{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
|
enum Inner{}
|
||||||
|
type InnerTime=Time<Inner>;
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
pub struct Realtime{
|
pub struct Realtime<In,Out>{
|
||||||
offset:Time,
|
offset:InnerTime,
|
||||||
|
_in:core::marker::PhantomData<In>,
|
||||||
|
_out:core::marker::PhantomData<Out>,
|
||||||
|
}
|
||||||
|
impl<In,Out> Realtime<In,Out>{
|
||||||
|
pub const fn new(offset:InnerTime)->Self{
|
||||||
|
Self{
|
||||||
|
offset,
|
||||||
|
_in:core::marker::PhantomData,
|
||||||
|
_out:core::marker::PhantomData,
|
||||||
}
|
}
|
||||||
impl Realtime{
|
|
||||||
pub const fn new(offset:Time)->Self{
|
|
||||||
Self{offset}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
pub struct Scaled{
|
pub struct Scaled<In,Out>{
|
||||||
scale:Ratio64,
|
scale:Ratio64,
|
||||||
offset:Time,
|
offset:InnerTime,
|
||||||
|
_in:core::marker::PhantomData<In>,
|
||||||
|
_out:core::marker::PhantomData<Out>,
|
||||||
|
}
|
||||||
|
impl<In,Out> Scaled<In,Out>
|
||||||
|
where Time<In>:Copy,
|
||||||
|
{
|
||||||
|
pub const fn new(scale:Ratio64,offset:InnerTime)->Self{
|
||||||
|
Self{
|
||||||
|
scale,
|
||||||
|
offset,
|
||||||
|
_in:core::marker::PhantomData,
|
||||||
|
_out:core::marker::PhantomData,
|
||||||
}
|
}
|
||||||
impl Scaled{
|
|
||||||
pub const fn new(scale:Ratio64,offset:Time)->Self{
|
|
||||||
Self{scale,offset}
|
|
||||||
}
|
}
|
||||||
const fn with_scale(scale:Ratio64)->Self{
|
const fn with_scale(scale:Ratio64)->Self{
|
||||||
Self{scale,offset:Time::ZERO}
|
Self::new(scale,InnerTime::ZERO)
|
||||||
}
|
}
|
||||||
const fn scale(&self,time:Time)->Time{
|
const fn scale(&self,time:Time<In>)->InnerTime{
|
||||||
Time::raw(self.scale.mul_int(time.get()))
|
InnerTime::raw(self.scale.mul_int(time.get()))
|
||||||
}
|
}
|
||||||
const fn get_scale(&self)->Ratio64{
|
const fn get_scale(&self)->Ratio64{
|
||||||
self.scale
|
self.scale
|
||||||
}
|
}
|
||||||
fn set_scale(&mut self,time:Time,new_scale:Ratio64){
|
fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
|
||||||
let new_time=self.get_time(time);
|
let new_time=self.get_time(time);
|
||||||
self.scale=new_scale;
|
self.scale=new_scale;
|
||||||
self.set_time(time,new_time);
|
self.set_time(time,new_time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TimerState:Copy+std::fmt::Debug{
|
pub trait TimerState{
|
||||||
|
type In;
|
||||||
|
type Out;
|
||||||
fn identity()->Self;
|
fn identity()->Self;
|
||||||
fn get_time(&self,time:Time)->Time;
|
fn get_time(&self,time:Time<Self::In>)->Time<Self::Out>;
|
||||||
fn set_time(&mut self,time:Time,new_time:Time);
|
fn set_time(&mut self,time:Time<Self::In>,new_time:Time<Self::Out>);
|
||||||
fn get_offset(&self)->Time;
|
fn get_offset(&self)->InnerTime;
|
||||||
fn set_offset(&mut self,offset:Time);
|
fn set_offset(&mut self,offset:InnerTime);
|
||||||
}
|
}
|
||||||
impl TimerState for Realtime{
|
impl<In,Out> TimerState for Realtime<In,Out>{
|
||||||
|
type In=In;
|
||||||
|
type Out=Out;
|
||||||
fn identity()->Self{
|
fn identity()->Self{
|
||||||
Self{offset:Time::ZERO}
|
Self::new(InnerTime::ZERO)
|
||||||
}
|
}
|
||||||
fn get_time(&self,time:Time)->Time{
|
fn get_time(&self,time:Time<In>)->Time<Out>{
|
||||||
time+self.offset
|
time.coerce()+self.offset.coerce()
|
||||||
}
|
}
|
||||||
fn set_time(&mut self,time:Time,new_time:Time){
|
fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
|
||||||
self.offset=new_time-time;
|
self.offset=new_time.coerce()-time.coerce();
|
||||||
}
|
}
|
||||||
fn get_offset(&self)->Time{
|
fn get_offset(&self)->InnerTime{
|
||||||
self.offset
|
self.offset
|
||||||
}
|
}
|
||||||
fn set_offset(&mut self,offset:Time){
|
fn set_offset(&mut self,offset:InnerTime){
|
||||||
self.offset=offset;
|
self.offset=offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl TimerState for Scaled{
|
impl<In,Out> TimerState for Scaled<In,Out>
|
||||||
|
where Time<In>:Copy,
|
||||||
|
{
|
||||||
|
type In=In;
|
||||||
|
type Out=Out;
|
||||||
fn identity()->Self{
|
fn identity()->Self{
|
||||||
Self{scale:Ratio64::ONE,offset:Time::ZERO}
|
Self::new(Ratio64::ONE,InnerTime::ZERO)
|
||||||
}
|
}
|
||||||
fn get_time(&self,time:Time)->Time{
|
fn get_time(&self,time:Time<In>)->Time<Out>{
|
||||||
self.scale(time)+self.offset
|
(self.scale(time)+self.offset).coerce()
|
||||||
}
|
}
|
||||||
fn set_time(&mut self,time:Time,new_time:Time){
|
fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
|
||||||
self.offset=new_time-self.scale(time);
|
self.offset=new_time.coerce()-self.scale(time);
|
||||||
}
|
}
|
||||||
fn get_offset(&self)->Time{
|
fn get_offset(&self)->InnerTime{
|
||||||
self.offset
|
self.offset
|
||||||
}
|
}
|
||||||
fn set_offset(&mut self,offset:Time){
|
fn set_offset(&mut self,offset:InnerTime){
|
||||||
self.offset=offset;
|
self.offset=offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,8 +133,10 @@ pub struct TimerFixed<T:TimerState,P:PauseState>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
//scaled timer methods are generic across PauseState
|
//scaled timer methods are generic across PauseState
|
||||||
impl<P:PauseState> TimerFixed<Scaled,P>{
|
impl<P:PauseState,In,Out> TimerFixed<Scaled<In,Out>,P>
|
||||||
pub fn scaled(time:Time,new_time:Time,scale:Ratio64)->Self{
|
where Time<In>:Copy,
|
||||||
|
{
|
||||||
|
pub fn scaled(time:Time<In>,new_time:Time<Out>,scale:Ratio64)->Self{
|
||||||
let mut timer=Self{
|
let mut timer=Self{
|
||||||
state:Scaled::with_scale(scale),
|
state:Scaled::with_scale(scale),
|
||||||
_paused:P::new(),
|
_paused:P::new(),
|
||||||
@ -118,14 +147,16 @@ impl<P:PauseState> TimerFixed<Scaled,P>{
|
|||||||
pub const fn get_scale(&self)->Ratio64{
|
pub const fn get_scale(&self)->Ratio64{
|
||||||
self.state.get_scale()
|
self.state.get_scale()
|
||||||
}
|
}
|
||||||
pub fn set_scale(&mut self,time:Time,new_scale:Ratio64){
|
pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
|
||||||
self.state.set_scale(time,new_scale)
|
self.state.set_scale(time,new_scale)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//pause and unpause is generic across TimerState
|
//pause and unpause is generic across TimerState
|
||||||
impl<T:TimerState> TimerFixed<T,Paused>{
|
impl<T:TimerState> TimerFixed<T,Paused>
|
||||||
pub fn into_unpaused(self,time:Time)->TimerFixed<T,Unpaused>{
|
where Time<T::In>:Copy,
|
||||||
|
{
|
||||||
|
pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{
|
||||||
let new_time=self.time(time);
|
let new_time=self.time(time);
|
||||||
let mut timer=TimerFixed{
|
let mut timer=TimerFixed{
|
||||||
state:self.state,
|
state:self.state,
|
||||||
@ -135,8 +166,10 @@ impl<T:TimerState> TimerFixed<T,Paused>{
|
|||||||
timer
|
timer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T:TimerState> TimerFixed<T,Unpaused>{
|
impl<T:TimerState> TimerFixed<T,Unpaused>
|
||||||
pub fn into_paused(self,time:Time)->TimerFixed<T,Paused>{
|
where Time<T::In>:Copy,
|
||||||
|
{
|
||||||
|
pub fn into_paused(self,time:Time<T::In>)->TimerFixed<T,Paused>{
|
||||||
let new_time=self.time(time);
|
let new_time=self.time(time);
|
||||||
let mut timer=TimerFixed{
|
let mut timer=TimerFixed{
|
||||||
state:self.state,
|
state:self.state,
|
||||||
@ -149,7 +182,7 @@ impl<T:TimerState> TimerFixed<T,Unpaused>{
|
|||||||
|
|
||||||
//the new constructor and time queries are generic across both
|
//the new constructor and time queries are generic across both
|
||||||
impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
|
impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
|
||||||
pub fn new(time:Time,new_time:Time)->Self{
|
pub fn new(time:Time<T::In>,new_time:Time<T::Out>)->Self{
|
||||||
let mut timer=Self{
|
let mut timer=Self{
|
||||||
state:T::identity(),
|
state:T::identity(),
|
||||||
_paused:P::new(),
|
_paused:P::new(),
|
||||||
@ -166,15 +199,15 @@ impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
|
|||||||
pub fn into_state(self)->T{
|
pub fn into_state(self)->T{
|
||||||
self.state
|
self.state
|
||||||
}
|
}
|
||||||
pub fn time(&self,time:Time)->Time{
|
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
||||||
match P::IS_PAUSED{
|
match P::IS_PAUSED{
|
||||||
true=>self.state.get_offset(),
|
true=>self.state.get_offset().coerce(),
|
||||||
false=>self.state.get_time(time),
|
false=>self.state.get_time(time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_time(&mut self,time:Time,new_time:Time){
|
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
|
||||||
match P::IS_PAUSED{
|
match P::IS_PAUSED{
|
||||||
true=>self.state.set_offset(new_time),
|
true=>self.state.set_offset(new_time.coerce()),
|
||||||
false=>self.state.set_time(time,new_time),
|
false=>self.state.set_time(time,new_time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,7 +231,11 @@ pub enum Timer<T:TimerState>{
|
|||||||
Paused(TimerFixed<T,Paused>),
|
Paused(TimerFixed<T,Paused>),
|
||||||
Unpaused(TimerFixed<T,Unpaused>),
|
Unpaused(TimerFixed<T,Unpaused>),
|
||||||
}
|
}
|
||||||
impl<T:TimerState> Timer<T>{
|
impl<T:TimerState> Timer<T>
|
||||||
|
where
|
||||||
|
T:Copy,
|
||||||
|
Time<T::In>:Copy,
|
||||||
|
{
|
||||||
pub fn from_state(state:T,paused:bool)->Self{
|
pub fn from_state(state:T,paused:bool)->Self{
|
||||||
match paused{
|
match paused{
|
||||||
true=>Self::Paused(TimerFixed::from_state(state)),
|
true=>Self::Paused(TimerFixed::from_state(state)),
|
||||||
@ -211,32 +248,32 @@ impl<T:TimerState> Timer<T>{
|
|||||||
Self::Unpaused(timer)=>(timer.into_state(),false),
|
Self::Unpaused(timer)=>(timer.into_state(),false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn paused(time:Time,new_time:Time)->Self{
|
pub fn paused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
|
||||||
Self::Paused(TimerFixed::new(time,new_time))
|
Self::Paused(TimerFixed::new(time,new_time))
|
||||||
}
|
}
|
||||||
pub fn unpaused(time:Time,new_time:Time)->Self{
|
pub fn unpaused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
|
||||||
Self::Unpaused(TimerFixed::new(time,new_time))
|
Self::Unpaused(TimerFixed::new(time,new_time))
|
||||||
}
|
}
|
||||||
pub fn time(&self,time:Time)->Time{
|
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
||||||
match self{
|
match self{
|
||||||
Self::Paused(timer)=>timer.time(time),
|
Self::Paused(timer)=>timer.time(time),
|
||||||
Self::Unpaused(timer)=>timer.time(time),
|
Self::Unpaused(timer)=>timer.time(time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_time(&mut self,time:Time,new_time:Time){
|
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
|
||||||
match self{
|
match self{
|
||||||
Self::Paused(timer)=>timer.set_time(time,new_time),
|
Self::Paused(timer)=>timer.set_time(time,new_time),
|
||||||
Self::Unpaused(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>{
|
pub fn pause(&mut self,time:Time<T::In>)->Result<(),Error>{
|
||||||
*self=match *self{
|
*self=match *self{
|
||||||
Self::Paused(_)=>return Err(Error::AlreadyPaused),
|
Self::Paused(_)=>return Err(Error::AlreadyPaused),
|
||||||
Self::Unpaused(timer)=>Self::Paused(timer.into_paused(time)),
|
Self::Unpaused(timer)=>Self::Paused(timer.into_paused(time)),
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn unpause(&mut self,time:Time)->Result<(),Error>{
|
pub fn unpause(&mut self,time:Time<T::In>)->Result<(),Error>{
|
||||||
*self=match *self{
|
*self=match *self{
|
||||||
Self::Paused(timer)=>Self::Unpaused(timer.into_unpaused(time)),
|
Self::Paused(timer)=>Self::Unpaused(timer.into_unpaused(time)),
|
||||||
Self::Unpaused(_)=>return Err(Error::AlreadyUnpaused),
|
Self::Unpaused(_)=>return Err(Error::AlreadyUnpaused),
|
||||||
@ -249,7 +286,7 @@ impl<T:TimerState> Timer<T>{
|
|||||||
Self::Unpaused(_)=>false,
|
Self::Unpaused(_)=>false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_paused(&mut self,time:Time,paused:bool)->Result<(),Error>{
|
pub fn set_paused(&mut self,time:Time<T::In>,paused:bool)->Result<(),Error>{
|
||||||
match paused{
|
match paused{
|
||||||
true=>self.pause(time),
|
true=>self.pause(time),
|
||||||
false=>self.unpause(time),
|
false=>self.unpause(time),
|
||||||
@ -257,14 +294,16 @@ impl<T:TimerState> Timer<T>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//scaled timer methods are generic across PauseState
|
//scaled timer methods are generic across PauseState
|
||||||
impl Timer<Scaled>{
|
impl<In,Out> Timer<Scaled<In,Out>>
|
||||||
|
where Time<In>:Copy,
|
||||||
|
{
|
||||||
pub const fn get_scale(&self)->Ratio64{
|
pub const fn get_scale(&self)->Ratio64{
|
||||||
match self{
|
match self{
|
||||||
Self::Paused(timer)=>timer.get_scale(),
|
Self::Paused(timer)=>timer.get_scale(),
|
||||||
Self::Unpaused(timer)=>timer.get_scale(),
|
Self::Unpaused(timer)=>timer.get_scale(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_scale(&mut self,time:Time,new_scale:Ratio64){
|
pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
|
||||||
match self{
|
match self{
|
||||||
Self::Paused(timer)=>timer.set_scale(time,new_scale),
|
Self::Paused(timer)=>timer.set_scale(time,new_scale),
|
||||||
Self::Unpaused(timer)=>timer.set_scale(time,new_scale),
|
Self::Unpaused(timer)=>timer.set_scale(time,new_scale),
|
||||||
@ -280,10 +319,15 @@ mod test{
|
|||||||
Time::from_secs($s)
|
Time::from_secs($s)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
|
enum Parent{}
|
||||||
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
|
enum Calculated{}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_timerfixed_scaled(){
|
fn test_timerfixed_scaled(){
|
||||||
//create a paused timer that reads 0s
|
//create a paused timer that reads 0s
|
||||||
let timer=TimerFixed::<Scaled,Paused>::from_state(Scaled{scale:0.5f32.try_into().unwrap(),offset:sec!(0)});
|
let timer=TimerFixed::<Scaled<Parent,Calculated>,Paused>::from_state(Scaled::new(0.5f32.try_into().unwrap(),sec!(0)));
|
||||||
//the paused timer at 1 second should read 0s
|
//the paused timer at 1 second should read 0s
|
||||||
assert_eq!(timer.time(sec!(1)),sec!(0));
|
assert_eq!(timer.time(sec!(1)),sec!(0));
|
||||||
|
|
||||||
@ -300,7 +344,7 @@ mod test{
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_timer()->Result<(),Error>{
|
fn test_timer()->Result<(),Error>{
|
||||||
//create a paused timer that reads 0s
|
//create a paused timer that reads 0s
|
||||||
let mut timer=Timer::<Realtime>::paused(sec!(0),sec!(0));
|
let mut timer=Timer::<Realtime<Parent,Calculated>>::paused(sec!(0),sec!(0));
|
||||||
//the paused timer at 1 second should read 0s
|
//the paused timer at 1 second should read 0s
|
||||||
assert_eq!(timer.time(sec!(1)),sec!(0));
|
assert_eq!(timer.time(sec!(1)),sec!(0));
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use bnum::{BInt,cast::As};
|
use bnum::{BInt,cast::As};
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug,Default,Hash)]
|
#[derive(Clone,Copy,Debug,Default,Hash,PartialEq,PartialOrd,Ord)]
|
||||||
/// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled)
|
/// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled)
|
||||||
/// N is the number of u64s to use
|
/// N is the number of u64s to use
|
||||||
/// F is the number of fractional bits (always N*32 lol)
|
/// F is the number of fractional bits (always N*32 lol)
|
||||||
@ -87,12 +87,6 @@ impl_from!(
|
|||||||
i8,i16,i32,i64,i128,isize
|
i8,i16,i32,i64,i128,isize
|
||||||
);
|
);
|
||||||
|
|
||||||
impl<const N:usize,const F:usize> PartialEq for Fixed<N,F>{
|
|
||||||
#[inline]
|
|
||||||
fn eq(&self,other:&Self)->bool{
|
|
||||||
self.bits.eq(&other.bits)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<const N:usize,const F:usize,T> PartialEq<T> for Fixed<N,F>
|
impl<const N:usize,const F:usize,T> PartialEq<T> for Fixed<N,F>
|
||||||
where
|
where
|
||||||
T:Copy,
|
T:Copy,
|
||||||
@ -105,12 +99,6 @@ where
|
|||||||
}
|
}
|
||||||
impl<const N:usize,const F:usize> Eq for Fixed<N,F>{}
|
impl<const N:usize,const F:usize> Eq for Fixed<N,F>{}
|
||||||
|
|
||||||
impl<const N:usize,const F:usize> PartialOrd for Fixed<N,F>{
|
|
||||||
#[inline]
|
|
||||||
fn partial_cmp(&self,other:&Self)->Option<std::cmp::Ordering>{
|
|
||||||
self.bits.partial_cmp(&other.bits)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
|
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
|
||||||
where
|
where
|
||||||
T:Copy,
|
T:Copy,
|
||||||
@ -121,12 +109,6 @@ impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
|
|||||||
self.bits.partial_cmp(&other.into())
|
self.bits.partial_cmp(&other.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<const N:usize,const F:usize> Ord for Fixed<N,F>{
|
|
||||||
#[inline]
|
|
||||||
fn cmp(&self,other:&Self)->std::cmp::Ordering{
|
|
||||||
self.bits.cmp(&other.bits)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N:usize,const F:usize> std::ops::Neg for Fixed<N,F>{
|
impl<const N:usize,const F:usize> std::ops::Neg for Fixed<N,F>{
|
||||||
type Output=Self;
|
type Output=Self;
|
||||||
|
@ -33,7 +33,7 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
|||||||
// LMAO look at this function!
|
// LMAO look at this function!
|
||||||
pub fn dom_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result<T>)->mlua::Result<T>{
|
pub fn dom_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result<T>)->mlua::Result<T>{
|
||||||
let mut dom=lua.app_data_mut::<&'static mut WeakDom>().ok_or_else(||mlua::Error::runtime("DataModel missing"))?;
|
let mut dom=lua.app_data_mut::<&'static mut WeakDom>().ok_or_else(||mlua::Error::runtime("DataModel missing"))?;
|
||||||
f(&mut *dom)
|
f(*dom)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn coerce_float32(value:&mlua::Value)->Option<f32>{
|
fn coerce_float32(value:&mlua::Value)->Option<f32>{
|
||||||
|
@ -7,21 +7,29 @@ pub enum Error{
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
BLOCK_DEMO_HEADER:
|
BLOCK_DEMO_HEADER:
|
||||||
|
u32 num_maps
|
||||||
|
for map_id in 0..num_maps{
|
||||||
|
i64 simulation_time
|
||||||
u128 map_resource_id
|
u128 map_resource_id
|
||||||
u64 map_header_block_id
|
u64 map_header_block_id
|
||||||
|
}
|
||||||
u32 num_bots
|
u32 num_bots
|
||||||
for bot_id in 0..num_bots{
|
for bot_id in 0..num_bots{
|
||||||
|
i64 simulation_time
|
||||||
u128 bot_resource_id
|
u128 bot_resource_id
|
||||||
u64 bot_header_block_id
|
u64 bot_header_block_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//map loading timeline
|
||||||
|
|
||||||
//bot loading timeline
|
//bot loading timeline
|
||||||
how to do worldstate for deathrun!?
|
how to do worldstate for deathrun!?
|
||||||
|
- this is done in the client, there is no worldstate in the demo file
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub struct StreamableDemo<R:BinReaderExt>{
|
pub struct StreamableDemo<R:BinReaderExt>{
|
||||||
map:Box<crate::map::StreamableMap<R>>,
|
map:Vec<crate::map::StreamableMap<R>>,
|
||||||
bots:Vec<crate::bot::StreamableBot<R>>,
|
bots:Vec<crate::bot::StreamableBot<R>>,
|
||||||
}
|
}
|
||||||
impl<R:BinReaderExt> StreamableDemo<R>{
|
impl<R:BinReaderExt> StreamableDemo<R>{
|
||||||
|
2
lib/snf/src/session.rs
Normal file
2
lib/snf/src/session.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// a session is a recording of the client's inputs
|
||||||
|
// which should deterministically recreate a bot or whatever the client did
|
@ -15,8 +15,8 @@ source = ["dep:strafesnet_deferred_loader", "dep:strafesnet_bsp_loader"]
|
|||||||
roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"]
|
roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
arrayvec = "0.7.6"
|
||||||
bytemuck = { version = "1.13.1", features = ["derive"] }
|
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||||
chrono = "0.4.39"
|
|
||||||
configparser = "3.0.2"
|
configparser = "3.0.2"
|
||||||
ddsfile = "0.5.1"
|
ddsfile = "0.5.1"
|
||||||
glam = "0.29.0"
|
glam = "0.29.0"
|
||||||
@ -27,10 +27,6 @@ strafesnet_bsp_loader = { path = "../lib/bsp_loader", registry = "strafesnet", o
|
|||||||
strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
|
strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
|
||||||
strafesnet_deferred_loader = { path = "../lib/deferred_loader", features = ["legacy"], registry = "strafesnet", optional = true }
|
strafesnet_deferred_loader = { path = "../lib/deferred_loader", features = ["legacy"], registry = "strafesnet", optional = true }
|
||||||
strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet", optional = true }
|
strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet", optional = true }
|
||||||
strafesnet_roblox_bot_file = { version = "0.2.0", registry = "strafesnet" }
|
|
||||||
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true }
|
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true }
|
||||||
wasm-bindgen = "0.2.99"
|
|
||||||
wasm-bindgen-futures = "0.4.49"
|
|
||||||
web-sys = { version = "0.3.76", features = ["console"] }
|
|
||||||
wgpu = "23.0.1"
|
wgpu = "23.0.1"
|
||||||
winit = "0.30.7"
|
winit = "0.30.7"
|
||||||
|
160
strafe-client/src/body.rs
Normal file
160
strafe-client/src/body.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
use strafesnet_common::aabb;
|
||||||
|
use strafesnet_common::integer::{self,vec3,Time,Planar64,Planar64Vec3};
|
||||||
|
#[derive(Clone,Copy,Debug,Hash)]
|
||||||
|
pub struct Body<T>{
|
||||||
|
pub position:Planar64Vec3,//I64 where 2^32 = 1 u
|
||||||
|
pub velocity:Planar64Vec3,//I64 where 2^32 = 1 u/s
|
||||||
|
pub acceleration:Planar64Vec3,//I64 where 2^32 = 1 u/s/s
|
||||||
|
pub time:Time<T>,//nanoseconds x xxxxD!
|
||||||
|
}
|
||||||
|
impl<T> std::ops::Neg for Body<T>{
|
||||||
|
type Output=Self;
|
||||||
|
fn neg(self)->Self::Output{
|
||||||
|
Self{
|
||||||
|
position:self.position,
|
||||||
|
velocity:-self.velocity,
|
||||||
|
acceleration:self.acceleration,
|
||||||
|
time:-self.time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Body<T>
|
||||||
|
where Time<T>:Copy,
|
||||||
|
{
|
||||||
|
pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO);
|
||||||
|
pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time<T>)->Self{
|
||||||
|
Self{
|
||||||
|
position,
|
||||||
|
velocity,
|
||||||
|
acceleration,
|
||||||
|
time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
|
||||||
|
let dt=time-self.time;
|
||||||
|
self.position
|
||||||
|
+(self.velocity*dt).map(|elem|elem.divide().fix_1())
|
||||||
|
+self.acceleration.map(|elem|(dt*dt*elem/2).divide().fix_1())
|
||||||
|
}
|
||||||
|
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
|
||||||
|
let dt=time-self.time;
|
||||||
|
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().fix_1())
|
||||||
|
}
|
||||||
|
pub fn advance_time(&mut self,time:Time<T>){
|
||||||
|
self.position=self.extrapolated_position(time);
|
||||||
|
self.velocity=self.extrapolated_velocity(time);
|
||||||
|
self.time=time;
|
||||||
|
}
|
||||||
|
pub fn extrapolated_position_ratio_dt<Num,Den,N1,D1,N2,N3,D2,N4,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
|
||||||
|
where
|
||||||
|
// Why?
|
||||||
|
// All of this can be removed with const generics because the type can be specified as
|
||||||
|
// Ratio<Fixed<N,NF>,Fixed<D,DF>>
|
||||||
|
// which is known to implement all the necessary traits
|
||||||
|
Num:Copy,
|
||||||
|
Den:Copy+core::ops::Mul<i64,Output=D1>,
|
||||||
|
D1:Copy,
|
||||||
|
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||||
|
Planar64:core::ops::Mul<D1,Output=N2>,
|
||||||
|
N1:core::ops::Add<N2,Output=N3>,
|
||||||
|
Num:core::ops::Mul<N3,Output=N4>,
|
||||||
|
Den:core::ops::Mul<D1,Output=D2>,
|
||||||
|
D2:Copy,
|
||||||
|
Planar64:core::ops::Mul<D2,Output=N4>,
|
||||||
|
N4:integer::Divide<D2,Output=T1>,
|
||||||
|
T1:integer::Fix<Planar64>,
|
||||||
|
{
|
||||||
|
// a*dt^2/2 + v*dt + p
|
||||||
|
// (a*dt/2+v)*dt+p
|
||||||
|
(self.acceleration.map(|elem|dt*elem/2)+self.velocity).map(|elem|dt.mul_ratio(elem))
|
||||||
|
.map(|elem|elem.divide().fix())+self.position
|
||||||
|
}
|
||||||
|
pub fn extrapolated_velocity_ratio_dt<Num,Den,N1,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
|
||||||
|
where
|
||||||
|
Num:Copy,
|
||||||
|
Den:Copy,
|
||||||
|
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||||
|
Planar64:core::ops::Mul<Den,Output=N1>,
|
||||||
|
N1:integer::Divide<Den,Output=T1>,
|
||||||
|
T1:integer::Fix<Planar64>,
|
||||||
|
{
|
||||||
|
// a*dt + v
|
||||||
|
self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity
|
||||||
|
}
|
||||||
|
pub fn advance_time_ratio_dt(&mut self,dt:crate::model_physics::GigaTime){
|
||||||
|
self.position=self.extrapolated_position_ratio_dt(dt);
|
||||||
|
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
|
||||||
|
self.time+=dt.into();
|
||||||
|
}
|
||||||
|
pub fn infinity_dir(&self)->Option<Planar64Vec3>{
|
||||||
|
if self.velocity==vec3::ZERO{
|
||||||
|
if self.acceleration==vec3::ZERO{
|
||||||
|
None
|
||||||
|
}else{
|
||||||
|
Some(self.acceleration)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
Some(self.velocity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn grow_aabb(&self,aabb:&mut aabb::Aabb,t0:Time<T>,t1:Time<T>){
|
||||||
|
aabb.grow(self.extrapolated_position(t0));
|
||||||
|
aabb.grow(self.extrapolated_position(t1));
|
||||||
|
//v+a*t==0
|
||||||
|
//goober code
|
||||||
|
if !self.acceleration.x.is_zero(){
|
||||||
|
let t=-self.velocity.x/self.acceleration.x;
|
||||||
|
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
||||||
|
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self.acceleration.y.is_zero(){
|
||||||
|
let t=-self.velocity.y/self.acceleration.y;
|
||||||
|
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
||||||
|
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self.acceleration.z.is_zero(){
|
||||||
|
let t=-self.velocity.z/self.acceleration.z;
|
||||||
|
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
||||||
|
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
impl<T> std::fmt::Display for Body<T>{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"p({}) v({}) a({}) t({})",self.position,self.velocity,self.acceleration,self.time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VirtualBody<'a,T>{
|
||||||
|
body0:&'a Body<T>,
|
||||||
|
body1:&'a Body<T>,
|
||||||
|
}
|
||||||
|
impl<T> VirtualBody<'_,T>
|
||||||
|
where Time<T>:Copy,
|
||||||
|
{
|
||||||
|
pub const fn relative<'a>(body0:&'a Body<T>,body1:&'a Body<T>)->VirtualBody<'a,T>{
|
||||||
|
//(p0,v0,a0,t0)
|
||||||
|
//(p1,v1,a1,t1)
|
||||||
|
VirtualBody{
|
||||||
|
body0,
|
||||||
|
body1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
|
||||||
|
self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time)
|
||||||
|
}
|
||||||
|
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
|
||||||
|
self.body1.extrapolated_velocity(time)-self.body0.extrapolated_velocity(time)
|
||||||
|
}
|
||||||
|
pub fn acceleration(&self)->Planar64Vec3{
|
||||||
|
self.body1.acceleration-self.body0.acceleration
|
||||||
|
}
|
||||||
|
pub fn body(&self,time:Time<T>)->Body<T>{
|
||||||
|
Body::new(self.extrapolated_position(time),self.extrapolated_velocity(time),self.acceleration(),time)
|
||||||
|
}
|
||||||
|
}
|
@ -3,11 +3,11 @@ pub type INWorker<'a,Task>=CompatNWorker<'a,Task>;
|
|||||||
|
|
||||||
pub struct CompatNWorker<'a,Task>{
|
pub struct CompatNWorker<'a,Task>{
|
||||||
data:std::marker::PhantomData<Task>,
|
data:std::marker::PhantomData<Task>,
|
||||||
f:Box<dyn FnMut(Task)+'a>,
|
f:Box<dyn FnMut(Task)+Send+'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a,Task> CompatNWorker<'a,Task>{
|
impl<'a,Task> CompatNWorker<'a,Task>{
|
||||||
pub fn new(f:impl FnMut(Task)+'a)->CompatNWorker<'a,Task>{
|
pub fn new(f:impl FnMut(Task)+Send+'a)->CompatNWorker<'a,Task>{
|
||||||
Self{
|
Self{
|
||||||
data:std::marker::PhantomData,
|
data:std::marker::PhantomData,
|
||||||
f:Box::new(f),
|
f:Box::new(f),
|
||||||
|
@ -1,23 +1,34 @@
|
|||||||
use crate::physics::Body;
|
use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge};
|
||||||
use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge,MinkowskiMesh,MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert};
|
use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3};
|
||||||
use strafesnet_common::integer::{Time,Fixed,Ratio};
|
use crate::physics::{Time,Body};
|
||||||
|
|
||||||
#[derive(Debug)]
|
enum Transition<M:MeshQuery>{
|
||||||
enum Transition<F,E:DirectedEdge,V>{
|
|
||||||
Miss,
|
Miss,
|
||||||
Next(FEV<F,E,V>,GigaTime),
|
Next(FEV<M>,GigaTime),
|
||||||
Hit(F,GigaTime),
|
Hit(M::Face,GigaTime),
|
||||||
}
|
}
|
||||||
|
|
||||||
type MinkowskiFEV=FEV<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>;
|
pub enum CrawlResult<M:MeshQuery>{
|
||||||
type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>;
|
Miss(FEV<M>),
|
||||||
|
Hit(M::Face,GigaTime),
|
||||||
|
}
|
||||||
|
|
||||||
fn next_transition(fev:&MinkowskiFEV,body_time:GigaTime,mesh:&MinkowskiMesh,body:&Body,mut best_time:GigaTime)->MinkowskiTransition{
|
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||||
|
where
|
||||||
|
// This is hardcoded for MinkowskiMesh lol
|
||||||
|
M::Face:Copy,
|
||||||
|
M::Edge:Copy,
|
||||||
|
M::Vert:Copy,
|
||||||
|
F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>,
|
||||||
|
<F as core::ops::Mul<Fixed<1,32>>>::Output:core::iter::Sum,
|
||||||
|
<M as MeshQuery>::Offset:core::ops::Sub<<F as std::ops::Mul<Fixed<1,32>>>::Output>,
|
||||||
|
{
|
||||||
|
fn next_transition(&self,body_time:GigaTime,mesh:&M,body:&Body,mut best_time:GigaTime)->Transition<M>{
|
||||||
//conflicting derivative means it crosses in the wrong direction.
|
//conflicting derivative means it crosses in the wrong direction.
|
||||||
//if the transition time is equal to an already tested transition, do not replace the current best.
|
//if the transition time is equal to an already tested transition, do not replace the current best.
|
||||||
let mut best_transition=MinkowskiTransition::Miss;
|
let mut best_transition=Transition::Miss;
|
||||||
match fev{
|
match self{
|
||||||
&MinkowskiFEV::Face(face_id)=>{
|
&FEV::Face(face_id)=>{
|
||||||
//test own face collision time, ignoring roots with zero or conflicting derivative
|
//test own face collision time, ignoring roots with zero or conflicting derivative
|
||||||
//n=face.normal d=face.dot
|
//n=face.normal d=face.dot
|
||||||
//n.a t^2+n.v t+n.p-d==0
|
//n.a t^2+n.v t+n.p-d==0
|
||||||
@ -27,7 +38,7 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
|
|||||||
for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
best_time=dt;
|
best_time=dt;
|
||||||
best_transition=MinkowskiTransition::Hit(face_id,dt);
|
best_transition=Transition::Hit(face_id,dt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,14 +52,14 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
|
|||||||
for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
|
for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
|
||||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
best_time=dt;
|
best_time=dt;
|
||||||
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt);
|
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//if none:
|
//if none:
|
||||||
},
|
},
|
||||||
&MinkowskiFEV::Edge(edge_id)=>{
|
&FEV::Edge(edge_id)=>{
|
||||||
//test each face collision time, ignoring roots with zero or conflicting derivative
|
//test each face collision time, ignoring roots with zero or conflicting derivative
|
||||||
let edge_n=mesh.edge_n(edge_id);
|
let edge_n=mesh.edge_n(edge_id);
|
||||||
let edge_verts=mesh.edge_verts(edge_id);
|
let edge_verts=mesh.edge_verts(edge_id);
|
||||||
@ -61,7 +72,7 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
|
|||||||
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
|
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
|
||||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
best_time=dt;
|
best_time=dt;
|
||||||
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Face(edge_face_id),dt);
|
best_transition=Transition::Next(FEV::Face(edge_face_id),dt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,14 +85,14 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
|
|||||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
||||||
best_time=dt;
|
best_time=dt;
|
||||||
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Vert(vert_id),dt);
|
best_transition=Transition::Next(FEV::Vert(vert_id),dt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//if none:
|
//if none:
|
||||||
},
|
},
|
||||||
&MinkowskiFEV::Vert(vert_id)=>{
|
&FEV::Vert(vert_id)=>{
|
||||||
//test each edge collision time, ignoring roots with zero or conflicting derivative
|
//test each edge collision time, ignoring roots with zero or conflicting derivative
|
||||||
for &directed_edge_id in mesh.vert_edges(vert_id).iter(){
|
for &directed_edge_id in mesh.vert_edges(vert_id).iter(){
|
||||||
//edge is directed away from vertex, but we want the dot product to turn out negative
|
//edge is directed away from vertex, but we want the dot product to turn out negative
|
||||||
@ -90,7 +101,7 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
|
|||||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
||||||
best_time=dt;
|
best_time=dt;
|
||||||
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt);
|
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,12 +111,7 @@ type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,Minkowsk
|
|||||||
}
|
}
|
||||||
best_transition
|
best_transition
|
||||||
}
|
}
|
||||||
pub enum CrawlResult<F,E:DirectedEdge,V>{
|
pub fn crawl(mut self,mesh:&M,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult<M>{
|
||||||
Miss(FEV<F,E,V>),
|
|
||||||
Hit(F,GigaTime),
|
|
||||||
}
|
|
||||||
type MinkowskiCrawlResult=CrawlResult<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>;
|
|
||||||
pub fn crawl_fev(mut fev:MinkowskiFEV,mesh:&MinkowskiMesh,relative_body:&Body,start_time:Time,time_limit:Time)->MinkowskiCrawlResult{
|
|
||||||
let mut body_time={
|
let mut body_time={
|
||||||
let r=(start_time-relative_body.time).to_ratio();
|
let r=(start_time-relative_body.time).to_ratio();
|
||||||
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
||||||
@ -115,13 +121,14 @@ pub fn crawl_fev(mut fev:MinkowskiFEV,mesh:&MinkowskiMesh,relative_body:&Body,st
|
|||||||
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
||||||
};
|
};
|
||||||
for _ in 0..20{
|
for _ in 0..20{
|
||||||
match next_transition(&fev,body_time,mesh,relative_body,time_limit){
|
match self.next_transition(body_time,mesh,relative_body,time_limit){
|
||||||
Transition::Miss=>return CrawlResult::Miss(fev),
|
Transition::Miss=>return CrawlResult::Miss(self),
|
||||||
Transition::Next(next_fev,next_time)=>(fev,body_time)=(next_fev,next_time),
|
Transition::Next(next_fev,next_time)=>(self,body_time)=(next_fev,next_time),
|
||||||
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
|
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO: fix all bugs
|
//TODO: fix all bugs
|
||||||
//println!("Too many iterations! Using default behaviour instead of crashing...");
|
//println!("Too many iterations! Using default behaviour instead of crashing...");
|
||||||
CrawlResult::Miss(fev)
|
CrawlResult::Miss(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,13 +59,10 @@ impl std::fmt::Display for LoadError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for LoadError{}
|
impl std::error::Error for LoadError{}
|
||||||
|
|
||||||
pub fn load_file<P:AsRef<std::path::Path>>(path:P)->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
||||||
//blocking because it's simpler...
|
//blocking because it's simpler...
|
||||||
let file=std::fs::File::open(path).map_err(LoadError::File)?;
|
let file=std::fs::File::open(path).map_err(LoadError::File)?;
|
||||||
load(file)
|
match read(file).map_err(LoadError::ReadError)?{
|
||||||
}
|
|
||||||
pub fn load<R:Read+std::io::Seek>(reader:R)->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
|
||||||
match read(reader).map_err(LoadError::ReadError)?{
|
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
DataStructure::StrafesNET(map)=>Ok(map),
|
DataStructure::StrafesNET(map)=>Ok(map),
|
||||||
#[cfg(feature="roblox")]
|
#[cfg(feature="roblox")]
|
||||||
|
@ -98,12 +98,6 @@ impl std::default::Default for GraphicsCamera{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FrameState{
|
|
||||||
pub body:crate::physics::Body,
|
|
||||||
pub camera:crate::physics::PhysicsCamera,
|
|
||||||
pub time:integer::Time,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GraphicsState{
|
pub struct GraphicsState{
|
||||||
pipelines:GraphicsPipelines,
|
pipelines:GraphicsPipelines,
|
||||||
bind_groups:GraphicsBindGroups,
|
bind_groups:GraphicsBindGroups,
|
||||||
@ -882,7 +876,7 @@ impl GraphicsState{
|
|||||||
view:&wgpu::TextureView,
|
view:&wgpu::TextureView,
|
||||||
device:&wgpu::Device,
|
device:&wgpu::Device,
|
||||||
queue:&wgpu::Queue,
|
queue:&wgpu::Queue,
|
||||||
frame_state:FrameState,
|
frame_state:crate::physics_worker::FrameState,
|
||||||
){
|
){
|
||||||
//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input
|
//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
pub enum Instruction{
|
pub enum Instruction{
|
||||||
Render(crate::graphics::FrameState),
|
Render(crate::physics_worker::FrameState),
|
||||||
//UpdateModel(crate::graphics::GraphicsModelUpdate),
|
//UpdateModel(crate::graphics::GraphicsModelUpdate),
|
||||||
Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings),
|
Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings),
|
||||||
ChangeMap(strafesnet_common::map::CompleteMap),
|
ChangeMap(strafesnet_common::map::CompleteMap),
|
||||||
@ -21,7 +21,6 @@ pub fn new<'a>(
|
|||||||
device:wgpu::Device,
|
device:wgpu::Device,
|
||||||
queue:wgpu::Queue,
|
queue:wgpu::Queue,
|
||||||
)->crate::compat_worker::INWorker<'a,Instruction>{
|
)->crate::compat_worker::INWorker<'a,Instruction>{
|
||||||
let mut resize=None;
|
|
||||||
crate::compat_worker::INWorker::new(move |ins:Instruction|{
|
crate::compat_worker::INWorker::new(move |ins:Instruction|{
|
||||||
match ins{
|
match ins{
|
||||||
Instruction::ChangeMap(map)=>{
|
Instruction::ChangeMap(map)=>{
|
||||||
@ -29,18 +28,15 @@ pub fn new<'a>(
|
|||||||
graphics.generate_models(&device,&queue,&map);
|
graphics.generate_models(&device,&queue,&map);
|
||||||
},
|
},
|
||||||
Instruction::Resize(size,user_settings)=>{
|
Instruction::Resize(size,user_settings)=>{
|
||||||
resize=Some((size,user_settings));
|
|
||||||
}
|
|
||||||
Instruction::Render(frame_state)=>{
|
|
||||||
if let Some((size,user_settings))=resize.take(){
|
|
||||||
println!("Resizing to {:?}",size);
|
println!("Resizing to {:?}",size);
|
||||||
//let t0=std::time::Instant::now();
|
let t0=std::time::Instant::now();
|
||||||
config.width=size.width.clamp(1,2560/2);
|
config.width=size.width.max(1);
|
||||||
config.height=size.height.clamp(1,1440/2);
|
config.height=size.height.max(1);
|
||||||
surface.configure(&device,&config);
|
surface.configure(&device,&config);
|
||||||
graphics.resize(&device,&config,&user_settings);
|
graphics.resize(&device,&config,&user_settings);
|
||||||
//println!("Resize took {:?}",t0.elapsed());
|
println!("Resize took {:?}",t0.elapsed());
|
||||||
}
|
}
|
||||||
|
Instruction::Render(frame_state)=>{
|
||||||
//this has to go deeper somehow
|
//this has to go deeper somehow
|
||||||
let frame=match surface.get_current_texture(){
|
let frame=match surface.get_current_texture(){
|
||||||
Ok(frame)=>frame,
|
Ok(frame)=>frame,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
mod body;
|
||||||
mod file;
|
mod file;
|
||||||
mod setup;
|
mod setup;
|
||||||
mod window;
|
mod window;
|
||||||
@ -5,6 +6,7 @@ mod worker;
|
|||||||
mod physics;
|
mod physics;
|
||||||
mod graphics;
|
mod graphics;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
mod push_solve;
|
||||||
mod face_crawler;
|
mod face_crawler;
|
||||||
mod compat_worker;
|
mod compat_worker;
|
||||||
mod model_physics;
|
mod model_physics;
|
||||||
@ -12,10 +14,8 @@ mod model_graphics;
|
|||||||
mod physics_worker;
|
mod physics_worker;
|
||||||
mod graphics_worker;
|
mod graphics_worker;
|
||||||
|
|
||||||
|
const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
fn main(){
|
fn main(){
|
||||||
let title=format!("Strafe Client v{}",env!("CARGO_PKG_VERSION"));
|
setup::setup_and_start(TITLE);
|
||||||
#[cfg(target_arch="wasm32")]
|
|
||||||
wasm_bindgen_futures::spawn_local(setup::setup_and_start(title));
|
|
||||||
#[cfg(not(target_arch="wasm32"))]
|
|
||||||
pollster::block_on(setup::setup_and_start(title));
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@ use std::collections::{HashSet,HashMap};
|
|||||||
use strafesnet_common::integer::vec3::Vector3;
|
use strafesnet_common::integer::vec3::Vector3;
|
||||||
use strafesnet_common::model::{self,MeshId,PolygonIter};
|
use strafesnet_common::model::{self,MeshId,PolygonIter};
|
||||||
use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio};
|
use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio};
|
||||||
|
use strafesnet_common::physics::Time;
|
||||||
|
|
||||||
|
type Body=crate::body::Body<strafesnet_common::physics::TimeInner>;
|
||||||
|
|
||||||
pub trait UndirectedEdge{
|
pub trait UndirectedEdge{
|
||||||
type DirectedEdge:Copy+DirectedEdge;
|
type DirectedEdge:Copy+DirectedEdge;
|
||||||
@ -51,10 +54,10 @@ impl DirectedEdge for SubmeshDirectedEdgeId{
|
|||||||
|
|
||||||
//Vertex <-> Edge <-> Face -> Collide
|
//Vertex <-> Edge <-> Face -> Collide
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FEV<F,E:DirectedEdge,V>{
|
pub enum FEV<M:MeshQuery>{
|
||||||
Face(F),
|
Face(M::Face),
|
||||||
Edge(E::UndirectedEdge),
|
Edge(<M::Edge as DirectedEdge>::UndirectedEdge),
|
||||||
Vert(V),
|
Vert(M::Vert),
|
||||||
}
|
}
|
||||||
|
|
||||||
//use Unit32 #[repr(C)] for map files
|
//use Unit32 #[repr(C)] for map files
|
||||||
@ -64,25 +67,28 @@ struct Face{
|
|||||||
dot:Planar64,
|
dot:Planar64,
|
||||||
}
|
}
|
||||||
struct Vert(Planar64Vec3);
|
struct Vert(Planar64Vec3);
|
||||||
pub trait MeshQuery<FACE:Clone,EDGE:Clone+DirectedEdge,VERT:Clone>{
|
pub trait MeshQuery{
|
||||||
|
type Face:Clone;
|
||||||
|
type Edge:Clone+DirectedEdge;
|
||||||
|
type Vert:Clone;
|
||||||
// Vertex must be Planar64Vec3 because it represents an actual position
|
// Vertex must be Planar64Vec3 because it represents an actual position
|
||||||
type Normal;
|
type Normal;
|
||||||
type Offset;
|
type Offset;
|
||||||
fn edge_n(&self,edge_id:EDGE::UndirectedEdge)->Planar64Vec3{
|
fn edge_n(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Planar64Vec3{
|
||||||
let verts=self.edge_verts(edge_id);
|
let verts=self.edge_verts(edge_id);
|
||||||
self.vert(verts[1].clone())-self.vert(verts[0].clone())
|
self.vert(verts[1].clone())-self.vert(verts[0].clone())
|
||||||
}
|
}
|
||||||
fn directed_edge_n(&self,directed_edge_id:EDGE)->Planar64Vec3{
|
fn directed_edge_n(&self,directed_edge_id:Self::Edge)->Planar64Vec3{
|
||||||
let verts=self.edge_verts(directed_edge_id.as_undirected());
|
let verts=self.edge_verts(directed_edge_id.as_undirected());
|
||||||
(self.vert(verts[1].clone())-self.vert(verts[0].clone()))*((directed_edge_id.parity() as i64)*2-1)
|
(self.vert(verts[1].clone())-self.vert(verts[0].clone()))*((directed_edge_id.parity() as i64)*2-1)
|
||||||
}
|
}
|
||||||
fn vert(&self,vert_id:VERT)->Planar64Vec3;
|
fn vert(&self,vert_id:Self::Vert)->Planar64Vec3;
|
||||||
fn face_nd(&self,face_id:FACE)->(Self::Normal,Self::Offset);
|
fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset);
|
||||||
fn face_edges(&self,face_id:FACE)->Cow<Vec<EDGE>>;
|
fn face_edges(&self,face_id:Self::Face)->Cow<Vec<Self::Edge>>;
|
||||||
fn edge_faces(&self,edge_id:EDGE::UndirectedEdge)->Cow<[FACE;2]>;
|
fn edge_faces(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Face;2]>;
|
||||||
fn edge_verts(&self,edge_id:EDGE::UndirectedEdge)->Cow<[VERT;2]>;
|
fn edge_verts(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Vert;2]>;
|
||||||
fn vert_edges(&self,vert_id:VERT)->Cow<Vec<EDGE>>;
|
fn vert_edges(&self,vert_id:Self::Vert)->Cow<Vec<Self::Edge>>;
|
||||||
fn vert_faces(&self,vert_id:VERT)->Cow<Vec<FACE>>;
|
fn vert_faces(&self,vert_id:Self::Vert)->Cow<Vec<Self::Face>>;
|
||||||
}
|
}
|
||||||
struct FaceRefs{
|
struct FaceRefs{
|
||||||
edges:Vec<SubmeshDirectedEdgeId>,
|
edges:Vec<SubmeshDirectedEdgeId>,
|
||||||
@ -421,7 +427,10 @@ pub struct PhysicsMeshView<'a>{
|
|||||||
data:&'a PhysicsMeshData,
|
data:&'a PhysicsMeshData,
|
||||||
topology:&'a PhysicsMeshTopology,
|
topology:&'a PhysicsMeshTopology,
|
||||||
}
|
}
|
||||||
impl MeshQuery<SubmeshFaceId,SubmeshDirectedEdgeId,SubmeshVertId> for PhysicsMeshView<'_>{
|
impl MeshQuery for PhysicsMeshView<'_>{
|
||||||
|
type Face=SubmeshFaceId;
|
||||||
|
type Edge=SubmeshDirectedEdgeId;
|
||||||
|
type Vert=SubmeshVertId;
|
||||||
type Normal=Planar64Vec3;
|
type Normal=Planar64Vec3;
|
||||||
type Offset=Planar64;
|
type Offset=Planar64;
|
||||||
fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){
|
fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){
|
||||||
@ -495,7 +504,10 @@ impl TransformedMesh<'_>{
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl MeshQuery<SubmeshFaceId,SubmeshDirectedEdgeId,SubmeshVertId> for TransformedMesh<'_>{
|
impl MeshQuery for TransformedMesh<'_>{
|
||||||
|
type Face=SubmeshFaceId;
|
||||||
|
type Edge=SubmeshDirectedEdgeId;
|
||||||
|
type Vert=SubmeshVertId;
|
||||||
type Normal=Vector3<Fixed<3,96>>;
|
type Normal=Vector3<Fixed<3,96>>;
|
||||||
type Offset=Fixed<4,128>;
|
type Offset=Fixed<4,128>;
|
||||||
fn face_nd(&self,face_id:SubmeshFaceId)->(Self::Normal,Self::Offset){
|
fn face_nd(&self,face_id:SubmeshFaceId)->(Self::Normal,Self::Offset){
|
||||||
@ -669,13 +681,13 @@ impl MinkowskiMesh<'_>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// This function drops a vertex down to an edge or a face if the path from infinity did not cross any vertex-edge boundaries but the point is supposed to have already crossed a boundary down from a vertex
|
/// This function drops a vertex down to an edge or a face if the path from infinity did not cross any vertex-edge boundaries but the point is supposed to have already crossed a boundary down from a vertex
|
||||||
fn infinity_fev(&self,infinity_dir:Planar64Vec3,point:Planar64Vec3)->FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>{
|
fn infinity_fev(&self,infinity_dir:Planar64Vec3,point:Planar64Vec3)->FEV::<MinkowskiMesh>{
|
||||||
//start on any vertex
|
//start on any vertex
|
||||||
//cross uncrossable vertex-edge boundaries until you find the closest vertex or edge
|
//cross uncrossable vertex-edge boundaries until you find the closest vertex or edge
|
||||||
//cross edge-face boundary if it's uncrossable
|
//cross edge-face boundary if it's uncrossable
|
||||||
match self.crawl_boundaries(self.farthest_vert(infinity_dir),infinity_dir,point){
|
match self.crawl_boundaries(self.farthest_vert(infinity_dir),infinity_dir,point){
|
||||||
//if a vert is returned, it is the closest point to the infinity point
|
//if a vert is returned, it is the closest point to the infinity point
|
||||||
EV::Vert(vert_id)=>FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>::Vert(vert_id),
|
EV::Vert(vert_id)=>FEV::Vert(vert_id),
|
||||||
EV::Edge(edge_id)=>{
|
EV::Edge(edge_id)=>{
|
||||||
//cross to face if the boundary is not crossable and we are on the wrong side
|
//cross to face if the boundary is not crossable and we are on the wrong side
|
||||||
let edge_n=self.edge_n(edge_id);
|
let edge_n=self.edge_n(edge_id);
|
||||||
@ -693,14 +705,14 @@ impl MinkowskiMesh<'_>{
|
|||||||
//infinity_dir can always be treated as a velocity
|
//infinity_dir can always be treated as a velocity
|
||||||
if !boundary_d.is_positive()&&boundary_n.dot(infinity_dir).is_zero(){
|
if !boundary_d.is_positive()&&boundary_n.dot(infinity_dir).is_zero(){
|
||||||
//both faces cannot pass this condition, return early if one does.
|
//both faces cannot pass this condition, return early if one does.
|
||||||
return FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>::Face(face_id);
|
return FEV::Face(face_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>::Edge(edge_id)
|
FEV::Edge(edge_id)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn closest_fev_not_inside(&self,mut infinity_body:crate::physics::Body)->Option<FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>>{
|
fn closest_fev_not_inside(&self,mut infinity_body:Body)->Option<FEV<MinkowskiMesh>>{
|
||||||
infinity_body.infinity_dir().map_or(None,|dir|{
|
infinity_body.infinity_dir().map_or(None,|dir|{
|
||||||
let infinity_fev=self.infinity_fev(-dir,infinity_body.position);
|
let infinity_fev=self.infinity_fev(-dir,infinity_body.position);
|
||||||
//a line is simpler to solve than a parabola
|
//a line is simpler to solve than a parabola
|
||||||
@ -708,24 +720,24 @@ impl MinkowskiMesh<'_>{
|
|||||||
infinity_body.acceleration=vec3::ZERO;
|
infinity_body.acceleration=vec3::ZERO;
|
||||||
//crawl in from negative infinity along a tangent line to get the closest fev
|
//crawl in from negative infinity along a tangent line to get the closest fev
|
||||||
// TODO: change crawl_fev args to delta time? Optional values?
|
// TODO: change crawl_fev args to delta time? Optional values?
|
||||||
match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN/4,infinity_body.time){
|
match infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time){
|
||||||
crate::face_crawler::CrawlResult::Miss(fev)=>Some(fev),
|
crate::face_crawler::CrawlResult::Miss(fev)=>Some(fev),
|
||||||
crate::face_crawler::CrawlResult::Hit(_,_)=>None,
|
crate::face_crawler::CrawlResult::Hit(_,_)=>None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn predict_collision_in(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,GigaTime)>{
|
pub fn predict_collision_in(&self,relative_body:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{
|
||||||
self.closest_fev_not_inside(relative_body.clone()).map_or(None,|fev|{
|
self.closest_fev_not_inside(relative_body.clone()).map_or(None,|fev|{
|
||||||
//continue forwards along the body parabola
|
//continue forwards along the body parabola
|
||||||
match crate::face_crawler::crawl_fev(fev,self,relative_body,relative_body.time,time_limit){
|
match fev.crawl(self,relative_body,relative_body.time,time_limit){
|
||||||
crate::face_crawler::CrawlResult::Miss(_)=>None,
|
crate::face_crawler::CrawlResult::Miss(_)=>None,
|
||||||
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
|
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn predict_collision_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,GigaTime)>{
|
pub fn predict_collision_out(&self,relative_body:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{
|
||||||
//create an extrapolated body at time_limit
|
//create an extrapolated body at time_limit
|
||||||
let infinity_body=crate::physics::Body::new(
|
let infinity_body=Body::new(
|
||||||
relative_body.extrapolated_position(time_limit),
|
relative_body.extrapolated_position(time_limit),
|
||||||
-relative_body.extrapolated_velocity(time_limit),
|
-relative_body.extrapolated_velocity(time_limit),
|
||||||
relative_body.acceleration,
|
relative_body.acceleration,
|
||||||
@ -733,13 +745,13 @@ impl MinkowskiMesh<'_>{
|
|||||||
);
|
);
|
||||||
self.closest_fev_not_inside(infinity_body).map_or(None,|fev|{
|
self.closest_fev_not_inside(infinity_body).map_or(None,|fev|{
|
||||||
//continue backwards along the body parabola
|
//continue backwards along the body parabola
|
||||||
match crate::face_crawler::crawl_fev(fev,self,&-relative_body.clone(),-time_limit,-relative_body.time){
|
match fev.crawl(self,&-relative_body.clone(),-time_limit,-relative_body.time){
|
||||||
crate::face_crawler::CrawlResult::Miss(_)=>None,
|
crate::face_crawler::CrawlResult::Miss(_)=>None,
|
||||||
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,-time)),//no need to test -time<time_limit because of the first step
|
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,-time)),//no need to test -time<time_limit because of the first step
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn predict_collision_face_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{
|
pub fn predict_collision_face_out(&self,relative_body:&Body,time_limit:Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{
|
||||||
//no algorithm needed, there is only one state and two cases (Edge,None)
|
//no algorithm needed, there is only one state and two cases (Edge,None)
|
||||||
//determine when it passes an edge ("sliding off" case)
|
//determine when it passes an edge ("sliding off" case)
|
||||||
let mut best_time={
|
let mut best_time={
|
||||||
@ -766,15 +778,15 @@ impl MinkowskiMesh<'_>{
|
|||||||
}
|
}
|
||||||
best_edge.map(|e|(e.as_undirected(),best_time))
|
best_edge.map(|e|(e.as_undirected(),best_time))
|
||||||
}
|
}
|
||||||
fn infinity_in(&self,infinity_body:crate::physics::Body)->Option<(MinkowskiFace,GigaTime)>{
|
fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{
|
||||||
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
|
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
|
||||||
match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN/4,infinity_body.time){
|
match infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time){
|
||||||
crate::face_crawler::CrawlResult::Miss(_)=>None,
|
crate::face_crawler::CrawlResult::Miss(_)=>None,
|
||||||
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
|
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
|
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
|
||||||
let infinity_body=crate::physics::Body::new(point,vec3::Y,vec3::ZERO,integer::Time::ZERO);
|
let infinity_body=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO);
|
||||||
//movement must escape the mesh forwards and backwards in time,
|
//movement must escape the mesh forwards and backwards in time,
|
||||||
//otherwise the point is not inside the mesh
|
//otherwise the point is not inside the mesh
|
||||||
self.infinity_in(infinity_body)
|
self.infinity_in(infinity_body)
|
||||||
@ -784,9 +796,13 @@ impl MinkowskiMesh<'_>{
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl MeshQuery<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert> for MinkowskiMesh<'_>{
|
impl MeshQuery for MinkowskiMesh<'_>{
|
||||||
|
type Face=MinkowskiFace;
|
||||||
|
type Edge=MinkowskiDirectedEdge;
|
||||||
|
type Vert=MinkowskiVert;
|
||||||
type Normal=Vector3<Fixed<3,96>>;
|
type Normal=Vector3<Fixed<3,96>>;
|
||||||
type Offset=Fixed<4,128>;
|
type Offset=Fixed<4,128>;
|
||||||
|
// TODO: relative d
|
||||||
fn face_nd(&self,face_id:MinkowskiFace)->(Self::Normal,Self::Offset){
|
fn face_nd(&self,face_id:MinkowskiFace)->(Self::Normal,Self::Offset){
|
||||||
match face_id{
|
match face_id{
|
||||||
MinkowskiFace::VertFace(v0,f1)=>{
|
MinkowskiFace::VertFace(v0,f1)=>{
|
||||||
|
@ -5,15 +5,18 @@ use strafesnet_common::map;
|
|||||||
use strafesnet_common::run;
|
use strafesnet_common::run;
|
||||||
use strafesnet_common::aabb;
|
use strafesnet_common::aabb;
|
||||||
use strafesnet_common::model::{MeshId,ModelId};
|
use strafesnet_common::model::{MeshId,ModelId};
|
||||||
use strafesnet_common::mouse::MouseState;
|
|
||||||
use strafesnet_common::gameplay_attributes::{self,CollisionAttributesId};
|
use strafesnet_common::gameplay_attributes::{self,CollisionAttributesId};
|
||||||
use strafesnet_common::gameplay_modes::{self,StageId};
|
use strafesnet_common::gameplay_modes::{self,StageId};
|
||||||
use strafesnet_common::gameplay_style::{self,StyleModifiers};
|
use strafesnet_common::gameplay_style::{self,StyleModifiers};
|
||||||
use strafesnet_common::controls_bitflag::Controls;
|
use strafesnet_common::controls_bitflag::Controls;
|
||||||
use strafesnet_common::instruction::{self,InstructionEmitter,InstructionConsumer,TimedInstruction};
|
use strafesnet_common::instruction::{self,InstructionEmitter,InstructionConsumer,InstructionFeedback,TimedInstruction};
|
||||||
use strafesnet_common::integer::{self,vec3,mat3,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2};
|
use strafesnet_common::integer::{self,vec3,mat3,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2};
|
||||||
|
pub use strafesnet_common::physics::{Time,TimeInner};
|
||||||
use gameplay::ModeState;
|
use gameplay::ModeState;
|
||||||
|
|
||||||
|
pub type Body=crate::body::Body<TimeInner>;
|
||||||
|
type MouseState=strafesnet_common::mouse::MouseState<TimeInner>;
|
||||||
|
|
||||||
//external influence
|
//external influence
|
||||||
//this is how you influence the physics from outside
|
//this is how you influence the physics from outside
|
||||||
use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
|
use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
|
||||||
@ -21,7 +24,7 @@ use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
|
|||||||
//internal influence
|
//internal influence
|
||||||
//when the physics asks itself what happens next, this is how it's represented
|
//when the physics asks itself what happens next, this is how it's represented
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum PhysicsInternalInstruction{
|
pub enum PhysicsInternalInstruction{
|
||||||
CollisionStart(Collision,model_physics::GigaTime),
|
CollisionStart(Collision,model_physics::GigaTime),
|
||||||
CollisionEnd(Collision,model_physics::GigaTime),
|
CollisionEnd(Collision,model_physics::GigaTime),
|
||||||
StrafeTick,
|
StrafeTick,
|
||||||
@ -29,32 +32,13 @@ enum PhysicsInternalInstruction{
|
|||||||
// Water,
|
// Water,
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum PhysicsInstruction{
|
pub enum PhysicsInstruction{
|
||||||
Internal(PhysicsInternalInstruction),
|
Internal(PhysicsInternalInstruction),
|
||||||
//InputInstructions conditionally activate RefreshWalkTarget
|
//InputInstructions conditionally activate RefreshWalkTarget
|
||||||
//(by doing what SetWalkTargetVelocity used to do and then flagging it)
|
//(by doing what SetWalkTargetVelocity used to do and then flagging it)
|
||||||
Input(PhysicsInputInstruction),
|
Input(PhysicsInputInstruction),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug,Hash)]
|
|
||||||
pub struct Body{
|
|
||||||
pub position:Planar64Vec3,//I64 where 2^32 = 1 u
|
|
||||||
pub velocity:Planar64Vec3,//I64 where 2^32 = 1 u/s
|
|
||||||
pub acceleration:Planar64Vec3,//I64 where 2^32 = 1 u/s/s
|
|
||||||
pub time:Time,//nanoseconds x xxxxD!
|
|
||||||
}
|
|
||||||
impl std::ops::Neg for Body{
|
|
||||||
type Output=Self;
|
|
||||||
fn neg(self)->Self::Output{
|
|
||||||
Self{
|
|
||||||
position:self.position,
|
|
||||||
velocity:-self.velocity,
|
|
||||||
acceleration:self.acceleration,
|
|
||||||
time:-self.time,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone,Debug,Default)]
|
#[derive(Clone,Debug,Default)]
|
||||||
pub struct InputState{
|
pub struct InputState{
|
||||||
mouse:MouseState,
|
mouse:MouseState,
|
||||||
@ -235,11 +219,11 @@ impl PhysicsModels{
|
|||||||
}
|
}
|
||||||
fn get_model_transform(&self,model_id:ModelId)->Option<&PhysicsMeshTransform>{
|
fn get_model_transform(&self,model_id:ModelId)->Option<&PhysicsMeshTransform>{
|
||||||
//ModelId can possibly be a decoration
|
//ModelId can possibly be a decoration
|
||||||
self.contact_models.get(&ContactModelId::new(model_id.get())).map_or_else(
|
match self.contact_models.get(&ContactModelId::new(model_id.get())){
|
||||||
||self.intersect_models.get(&IntersectModelId::new(model_id.get()))
|
Some(model)=>Some(&model.transform),
|
||||||
|
None=>self.intersect_models.get(&IntersectModelId::new(model_id.get()))
|
||||||
.map(|model|&model.transform),
|
.map(|model|&model.transform),
|
||||||
|model|Some(&model.transform)
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
fn contact_model(&self,model_id:ContactModelId)->&ContactModel{
|
fn contact_model(&self,model_id:ContactModelId)->&ContactModel{
|
||||||
&self.contact_models[&model_id]
|
&self.contact_models[&model_id]
|
||||||
@ -273,15 +257,6 @@ pub struct PhysicsCamera{
|
|||||||
impl PhysicsCamera{
|
impl PhysicsCamera{
|
||||||
const ANGLE_PITCH_LOWER_LIMIT:Angle32=Angle32::NEG_FRAC_PI_2;
|
const ANGLE_PITCH_LOWER_LIMIT:Angle32=Angle32::NEG_FRAC_PI_2;
|
||||||
const ANGLE_PITCH_UPPER_LIMIT:Angle32=Angle32::FRAC_PI_2;
|
const ANGLE_PITCH_UPPER_LIMIT:Angle32=Angle32::FRAC_PI_2;
|
||||||
pub fn new(
|
|
||||||
sensitivity:Ratio64Vec2,
|
|
||||||
clamped_mouse_pos:glam::IVec2,
|
|
||||||
)->Self{
|
|
||||||
Self{
|
|
||||||
sensitivity,
|
|
||||||
clamped_mouse_pos,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn move_mouse(&mut self,mouse_delta:glam::IVec2){
|
pub fn move_mouse(&mut self,mouse_delta:glam::IVec2){
|
||||||
let mut unclamped_mouse_pos=self.clamped_mouse_pos+mouse_delta;
|
let mut unclamped_mouse_pos=self.clamped_mouse_pos+mouse_delta;
|
||||||
unclamped_mouse_pos.y=unclamped_mouse_pos.y.clamp(
|
unclamped_mouse_pos.y=unclamped_mouse_pos.y.clamp(
|
||||||
@ -335,6 +310,15 @@ impl std::default::Default for PhysicsCamera{
|
|||||||
}
|
}
|
||||||
mod gameplay{
|
mod gameplay{
|
||||||
use super::{gameplay_modes,HashSet,HashMap,ModelId};
|
use super::{gameplay_modes,HashSet,HashMap,ModelId};
|
||||||
|
pub enum JumpIncrementResult{
|
||||||
|
Allowed,
|
||||||
|
ExceededLimit,
|
||||||
|
}
|
||||||
|
impl JumpIncrementResult{
|
||||||
|
pub const fn is_allowed(self)->bool{
|
||||||
|
matches!(self,JumpIncrementResult::Allowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct ModeState{
|
pub struct ModeState{
|
||||||
mode_id:gameplay_modes::ModeId,
|
mode_id:gameplay_modes::ModeId,
|
||||||
@ -353,8 +337,14 @@ mod gameplay{
|
|||||||
pub const fn get_next_ordered_checkpoint_id(&self)->gameplay_modes::CheckpointId{
|
pub const fn get_next_ordered_checkpoint_id(&self)->gameplay_modes::CheckpointId{
|
||||||
self.next_ordered_checkpoint_id
|
self.next_ordered_checkpoint_id
|
||||||
}
|
}
|
||||||
pub fn get_jump_count(&self,model_id:ModelId)->Option<u32>{
|
fn increment_jump_count(&mut self,model_id:ModelId)->u32{
|
||||||
self.jump_counts.get(&model_id).copied()
|
*self.jump_counts.entry(model_id).and_modify(|c|*c+=1).or_insert(1)
|
||||||
|
}
|
||||||
|
pub fn try_increment_jump_count(&mut self,model_id:ModelId,jump_limit:Option<u8>)->JumpIncrementResult{
|
||||||
|
match jump_limit{
|
||||||
|
Some(jump_limit) if (jump_limit as u32)<self.increment_jump_count(model_id)=>JumpIncrementResult::ExceededLimit,
|
||||||
|
_=>JumpIncrementResult::Allowed,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub const fn ordered_checkpoint_count(&self)->u32{
|
pub const fn ordered_checkpoint_count(&self)->u32{
|
||||||
self.next_ordered_checkpoint_id.get()
|
self.next_ordered_checkpoint_id.get()
|
||||||
@ -568,7 +558,7 @@ impl MoveState{
|
|||||||
=>None,
|
=>None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
|
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<PhysicsInternalInstruction,TimeInner>>{
|
||||||
//check if you have a valid walk state and create an instruction
|
//check if you have a valid walk state and create an instruction
|
||||||
match self{
|
match self{
|
||||||
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{
|
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{
|
||||||
@ -735,8 +725,8 @@ impl Collision{
|
|||||||
}
|
}
|
||||||
#[derive(Clone,Debug,Default)]
|
#[derive(Clone,Debug,Default)]
|
||||||
struct TouchingState{
|
struct TouchingState{
|
||||||
contacts:HashSet::<ContactCollision>,
|
contacts:HashSet<ContactCollision>,
|
||||||
intersects:HashSet::<IntersectCollision>,
|
intersects:HashSet<IntersectCollision>,
|
||||||
}
|
}
|
||||||
impl TouchingState{
|
impl TouchingState{
|
||||||
fn clear(&mut self){
|
fn clear(&mut self){
|
||||||
@ -775,27 +765,29 @@ impl TouchingState{
|
|||||||
a
|
a
|
||||||
}
|
}
|
||||||
fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:&mut Planar64Vec3){
|
fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:&mut Planar64Vec3){
|
||||||
//TODO: trey push solve
|
let contacts=self.contacts.iter().map(|contact|{
|
||||||
for contact in &self.contacts{
|
|
||||||
let n=contact_normal(models,hitbox_mesh,contact);
|
let n=contact_normal(models,hitbox_mesh,contact);
|
||||||
let d=n.dot(*velocity);
|
crate::push_solve::Contact{
|
||||||
if d.is_negative(){
|
position:vec3::ZERO,
|
||||||
*velocity-=(n*d/n.length_squared()).divide().fix_1();
|
velocity:n,
|
||||||
}
|
normal:n,
|
||||||
}
|
}
|
||||||
|
}).collect();
|
||||||
|
*velocity=crate::push_solve::push_solve(&contacts,*velocity);
|
||||||
}
|
}
|
||||||
fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:&mut Planar64Vec3){
|
fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:&mut Planar64Vec3){
|
||||||
//TODO: trey push solve
|
let contacts=self.contacts.iter().map(|contact|{
|
||||||
for contact in &self.contacts{
|
|
||||||
let n=contact_normal(models,hitbox_mesh,contact);
|
let n=contact_normal(models,hitbox_mesh,contact);
|
||||||
let d=n.dot(*acceleration);
|
crate::push_solve::Contact{
|
||||||
if d.is_negative(){
|
position:vec3::ZERO,
|
||||||
*acceleration-=(n*d/n.length_squared()).divide().fix_1();
|
velocity:n,
|
||||||
|
normal:n,
|
||||||
}
|
}
|
||||||
|
}).collect();
|
||||||
|
*acceleration=crate::push_solve::push_solve(&contacts,*acceleration);
|
||||||
}
|
}
|
||||||
}
|
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<PhysicsInternalInstruction,TimeInner>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){
|
||||||
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<PhysicsInternalInstruction>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){
|
let relative_body=crate::body::VirtualBody::relative(&Body::ZERO,body).body(time);
|
||||||
let relative_body=VirtualBody::relative(&Body::ZERO,body).body(time);
|
|
||||||
for contact in &self.contacts{
|
for contact in &self.contacts{
|
||||||
//detect face slide off
|
//detect face slide off
|
||||||
let model_mesh=models.contact_mesh(contact);
|
let model_mesh=models.contact_mesh(contact);
|
||||||
@ -827,142 +819,6 @@ impl TouchingState{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Body{
|
|
||||||
pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO);
|
|
||||||
pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time)->Self{
|
|
||||||
Self{
|
|
||||||
position,
|
|
||||||
velocity,
|
|
||||||
acceleration,
|
|
||||||
time,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn extrapolated_position(&self,time:Time)->Planar64Vec3{
|
|
||||||
let dt=time-self.time;
|
|
||||||
self.position
|
|
||||||
+(self.velocity*dt).map(|elem|elem.divide().fix_1())
|
|
||||||
+self.acceleration.map(|elem|(dt*dt*elem/2).divide().fix_1())
|
|
||||||
}
|
|
||||||
pub fn extrapolated_velocity(&self,time:Time)->Planar64Vec3{
|
|
||||||
let dt=time-self.time;
|
|
||||||
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().fix_1())
|
|
||||||
}
|
|
||||||
pub fn advance_time(&mut self,time:Time){
|
|
||||||
self.position=self.extrapolated_position(time);
|
|
||||||
self.velocity=self.extrapolated_velocity(time);
|
|
||||||
self.time=time;
|
|
||||||
}
|
|
||||||
pub fn extrapolated_position_ratio_dt<Num,Den,N1,D1,N2,N3,D2,N4,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
|
|
||||||
where
|
|
||||||
// Why?
|
|
||||||
// All of this can be removed with const generics because the type can be specified as
|
|
||||||
// Ratio<Fixed<N,NF>,Fixed<D,DF>>
|
|
||||||
// which is known to implement all the necessary traits
|
|
||||||
Num:Copy,
|
|
||||||
Den:Copy+core::ops::Mul<i64,Output=D1>,
|
|
||||||
D1:Copy,
|
|
||||||
Num:core::ops::Mul<Planar64,Output=N1>,
|
|
||||||
Planar64:core::ops::Mul<D1,Output=N2>,
|
|
||||||
N1:core::ops::Add<N2,Output=N3>,
|
|
||||||
Num:core::ops::Mul<N3,Output=N4>,
|
|
||||||
Den:core::ops::Mul<D1,Output=D2>,
|
|
||||||
D2:Copy,
|
|
||||||
Planar64:core::ops::Mul<D2,Output=N4>,
|
|
||||||
N4:integer::Divide<D2,Output=T1>,
|
|
||||||
T1:integer::Fix<Planar64>,
|
|
||||||
{
|
|
||||||
// a*dt^2/2 + v*dt + p
|
|
||||||
// (a*dt/2+v)*dt+p
|
|
||||||
(self.acceleration.map(|elem|dt*elem/2)+self.velocity).map(|elem|dt.mul_ratio(elem))
|
|
||||||
.map(|elem|elem.divide().fix())+self.position
|
|
||||||
}
|
|
||||||
pub fn extrapolated_velocity_ratio_dt<Num,Den,N1,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
|
|
||||||
where
|
|
||||||
Num:Copy,
|
|
||||||
Den:Copy,
|
|
||||||
Num:core::ops::Mul<Planar64,Output=N1>,
|
|
||||||
Planar64:core::ops::Mul<Den,Output=N1>,
|
|
||||||
N1:integer::Divide<Den,Output=T1>,
|
|
||||||
T1:integer::Fix<Planar64>,
|
|
||||||
{
|
|
||||||
// a*dt + v
|
|
||||||
self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity
|
|
||||||
}
|
|
||||||
pub fn advance_time_ratio_dt(&mut self,dt:model_physics::GigaTime){
|
|
||||||
self.position=self.extrapolated_position_ratio_dt(dt);
|
|
||||||
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
|
|
||||||
self.time+=dt.into();
|
|
||||||
}
|
|
||||||
pub fn infinity_dir(&self)->Option<Planar64Vec3>{
|
|
||||||
if self.velocity==vec3::ZERO{
|
|
||||||
if self.acceleration==vec3::ZERO{
|
|
||||||
None
|
|
||||||
}else{
|
|
||||||
Some(self.acceleration)
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
Some(self.velocity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn grow_aabb(&self,aabb:&mut aabb::Aabb,t0:Time,t1:Time){
|
|
||||||
aabb.grow(self.extrapolated_position(t0));
|
|
||||||
aabb.grow(self.extrapolated_position(t1));
|
|
||||||
//v+a*t==0
|
|
||||||
//goober code
|
|
||||||
if !self.acceleration.x.is_zero(){
|
|
||||||
let t=-self.velocity.x/self.acceleration.x;
|
|
||||||
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
|
||||||
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !self.acceleration.y.is_zero(){
|
|
||||||
let t=-self.velocity.y/self.acceleration.y;
|
|
||||||
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
|
||||||
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !self.acceleration.z.is_zero(){
|
|
||||||
let t=-self.velocity.z/self.acceleration.z;
|
|
||||||
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
|
||||||
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for Body{
|
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
||||||
write!(f,"p({}) v({}) a({}) t({})",self.position,self.velocity,self.acceleration,self.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VirtualBody<'a>{
|
|
||||||
body0:&'a Body,
|
|
||||||
body1:&'a Body,
|
|
||||||
}
|
|
||||||
impl VirtualBody<'_>{
|
|
||||||
const fn relative<'a>(body0:&'a Body,body1:&'a Body)->VirtualBody<'a>{
|
|
||||||
//(p0,v0,a0,t0)
|
|
||||||
//(p1,v1,a1,t1)
|
|
||||||
VirtualBody{
|
|
||||||
body0,
|
|
||||||
body1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn extrapolated_position(&self,time:Time)->Planar64Vec3{
|
|
||||||
self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time)
|
|
||||||
}
|
|
||||||
fn extrapolated_velocity(&self,time:Time)->Planar64Vec3{
|
|
||||||
self.body1.extrapolated_velocity(time)-self.body0.extrapolated_velocity(time)
|
|
||||||
}
|
|
||||||
fn acceleration(&self)->Planar64Vec3{
|
|
||||||
self.body1.acceleration-self.body0.acceleration
|
|
||||||
}
|
|
||||||
fn body(&self,time:Time)->Body{
|
|
||||||
Body::new(self.extrapolated_position(time),self.extrapolated_velocity(time),self.acceleration(),time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct PhysicsState{
|
pub struct PhysicsState{
|
||||||
time:Time,
|
time:Time,
|
||||||
@ -974,7 +830,7 @@ pub struct PhysicsState{
|
|||||||
//input_state
|
//input_state
|
||||||
input_state:InputState,
|
input_state:InputState,
|
||||||
//style
|
//style
|
||||||
pub style:StyleModifiers,//mode style with custom style updates applied
|
style:StyleModifiers,//mode style with custom style updates applied
|
||||||
//gameplay_state
|
//gameplay_state
|
||||||
mode_state:ModeState,
|
mode_state:ModeState,
|
||||||
move_state:MoveState,
|
move_state:MoveState,
|
||||||
@ -1030,7 +886,7 @@ impl PhysicsState{
|
|||||||
new_state.camera.sensitivity=self.camera.sensitivity;
|
new_state.camera.sensitivity=self.camera.sensitivity;
|
||||||
*self=new_state;
|
*self=new_state;
|
||||||
}
|
}
|
||||||
fn next_move_instruction(&self)->Option<TimedInstruction<PhysicsInternalInstruction>>{
|
fn next_move_instruction(&self)->Option<TimedInstruction<PhysicsInternalInstruction,TimeInner>>{
|
||||||
self.move_state.next_move_instruction(&self.style.strafe,self.time)
|
self.move_state.next_move_instruction(&self.style.strafe,self.time)
|
||||||
}
|
}
|
||||||
fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){
|
fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){
|
||||||
@ -1076,18 +932,27 @@ impl PhysicsState{
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct PhysicsContext{
|
pub struct PhysicsContext{
|
||||||
pub state:PhysicsState,//this captures the entire state of the physics.
|
state:PhysicsState,//this captures the entire state of the physics.
|
||||||
data:PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state.
|
data:PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state.
|
||||||
}
|
}
|
||||||
//the physics consumes the generic PhysicsInstruction, but can only emit the more narrow PhysicsInternalInstruction
|
// the physics consumes both PhysicsInputInstruction and PhysicsInternalInstruction,
|
||||||
impl instruction::InstructionConsumer<PhysicsInstruction> for PhysicsContext{
|
// but can only emit PhysicsInternalInstruction
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInstruction>){
|
impl InstructionConsumer<PhysicsInternalInstruction> for PhysicsContext{
|
||||||
atomic_state_update(&mut self.state,&self.data,ins)
|
type TimeInner=TimeInner;
|
||||||
|
fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInternalInstruction,TimeInner>){
|
||||||
|
atomic_internal_instruction(&mut self.state,&self.data,ins)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl instruction::InstructionEmitter<PhysicsInternalInstruction> for PhysicsContext{
|
impl InstructionConsumer<PhysicsInputInstruction> for PhysicsContext{
|
||||||
//this little next instruction function can cache its return value and invalidate the cached value by watching the State.
|
type TimeInner=TimeInner;
|
||||||
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
|
fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInputInstruction,TimeInner>){
|
||||||
|
atomic_input_instruction(&mut self.state,&self.data,ins)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl InstructionEmitter<PhysicsInternalInstruction> for PhysicsContext{
|
||||||
|
type TimeInner=TimeInner;
|
||||||
|
//this little next instruction function could cache its return value and invalidate the cached value by watching the State.
|
||||||
|
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction,TimeInner>>{
|
||||||
next_instruction_internal(&self.state,&self.data,time_limit)
|
next_instruction_internal(&self.state,&self.data,time_limit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1244,29 +1109,14 @@ impl PhysicsContext{
|
|||||||
println!("Physics Objects: {}",model_count);
|
println!("Physics Objects: {}",model_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
//tickless gaming
|
pub fn run_input_instruction(&mut self,instruction:TimedInstruction<PhysicsInputInstruction,TimeInner>){
|
||||||
fn run_internal_exhaustive(&mut self,time_limit:Time){
|
self.process_exhaustive(instruction.time);
|
||||||
//prepare is ommitted - everything is done via instructions.
|
self.process_instruction(instruction);
|
||||||
while let Some(instruction)=self.next_instruction(time_limit){//collect
|
|
||||||
//process
|
|
||||||
self.process_instruction(TimedInstruction{
|
|
||||||
time:instruction.time,
|
|
||||||
instruction:PhysicsInstruction::Internal(instruction.instruction),
|
|
||||||
});
|
|
||||||
//write hash lol
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn run_input_instruction(&mut self,instruction:TimedInstruction<PhysicsInputInstruction>){
|
|
||||||
self.run_internal_exhaustive(instruction.time);
|
|
||||||
self.process_instruction(TimedInstruction{
|
|
||||||
time:instruction.time,
|
|
||||||
instruction:PhysicsInstruction::Input(instruction.instruction),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is the one who asks
|
//this is the one who asks
|
||||||
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
|
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction,TimeInner>>{
|
||||||
//JUST POLLING!!! NO MUTATION
|
//JUST POLLING!!! NO MUTATION
|
||||||
let mut collector = instruction::InstructionCollector::new(time_limit);
|
let mut collector = instruction::InstructionCollector::new(time_limit);
|
||||||
|
|
||||||
@ -1288,6 +1138,7 @@ impl PhysicsContext{
|
|||||||
collector.collect(minkowski.predict_collision_in(relative_body,collector.time())
|
collector.collect(minkowski.predict_collision_in(relative_body,collector.time())
|
||||||
//temp (?) code to avoid collision loops
|
//temp (?) code to avoid collision loops
|
||||||
.map_or(None,|(face,dt)|{
|
.map_or(None,|(face,dt)|{
|
||||||
|
// this must be rounded to avoid the infinite loop when hitting the start zone
|
||||||
let time=relative_body.time+dt.into();
|
let time=relative_body.time+dt.into();
|
||||||
if time<=state.time{None}else{Some((time,face,dt))}})
|
if time<=state.time{None}else{Some((time,face,dt))}})
|
||||||
.map(|(time,face,dt)|
|
.map(|(time,face,dt)|
|
||||||
@ -1450,7 +1301,7 @@ enum TeleportToSpawnError{
|
|||||||
NoModel,
|
NoModel,
|
||||||
}
|
}
|
||||||
fn teleport_to_spawn(
|
fn teleport_to_spawn(
|
||||||
stage:&gameplay_modes::Stage,
|
spawn_model_id:ModelId,
|
||||||
move_state:&mut MoveState,
|
move_state:&mut MoveState,
|
||||||
body:&mut Body,
|
body:&mut Body,
|
||||||
touching:&mut TouchingState,
|
touching:&mut TouchingState,
|
||||||
@ -1465,14 +1316,78 @@ fn teleport_to_spawn(
|
|||||||
input_state:&InputState,
|
input_state:&InputState,
|
||||||
time:Time,
|
time:Time,
|
||||||
)->Result<(),TeleportToSpawnError>{
|
)->Result<(),TeleportToSpawnError>{
|
||||||
|
//jump count and checkpoints are always reset on teleport_to_spawn.
|
||||||
|
//Map makers are expected to use tools to prevent
|
||||||
|
//multi-boosting on JumpLimit boosters such as spawning into a SetVelocity
|
||||||
|
mode_state.clear();
|
||||||
const EPSILON:Planar64=Planar64::raw((1<<32)/16);
|
const EPSILON:Planar64=Planar64::raw((1<<32)/16);
|
||||||
let transform=models.get_model_transform(stage.spawn()).ok_or(TeleportToSpawnError::NoModel)?;
|
let transform=models.get_model_transform(spawn_model_id).ok_or(TeleportToSpawnError::NoModel)?;
|
||||||
//TODO: transform.vertex.matrix3.col(1)+transform.vertex.translation
|
//TODO: transform.vertex.matrix3.col(1)+transform.vertex.translation
|
||||||
let point=transform.vertex.transform_point3(vec3::Y).fix_1()+Planar64Vec3::new([Planar64::ZERO,style.hitbox.halfsize.y+EPSILON,Planar64::ZERO]);
|
let point=transform.vertex.transform_point3(vec3::Y).fix_1()+Planar64Vec3::new([Planar64::ZERO,style.hitbox.halfsize.y+EPSILON,Planar64::ZERO]);
|
||||||
teleport(point,move_state,body,touching,run,mode_state,Some(mode),models,hitbox_mesh,bvh,style,camera,input_state,time);
|
teleport(point,move_state,body,touching,run,mode_state,Some(mode),models,hitbox_mesh,bvh,style,camera,input_state,time);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CheckpointCheckOutcome{
|
||||||
|
set_stage:Option<StageId>,
|
||||||
|
teleport_to_model:Option<ModelId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// stage_element.touch_result(mode,mode_state)
|
||||||
|
fn checkpoint_check(
|
||||||
|
mode_state:&ModeState,
|
||||||
|
stage_element:&gameplay_modes::StageElement,
|
||||||
|
mode:&gameplay_modes::Mode,
|
||||||
|
)->CheckpointCheckOutcome{
|
||||||
|
let current_stage_id=mode_state.get_stage_id();
|
||||||
|
let target_stage_id=stage_element.stage_id();
|
||||||
|
if current_stage_id<target_stage_id{
|
||||||
|
//checkpoint check
|
||||||
|
//check if current stage is complete
|
||||||
|
if let Some(current_stage)=mode.get_stage(current_stage_id){
|
||||||
|
if !current_stage.is_complete(mode_state.ordered_checkpoint_count(),mode_state.unordered_checkpoint_count()){
|
||||||
|
return CheckpointCheckOutcome{
|
||||||
|
set_stage:None,
|
||||||
|
teleport_to_model:Some(current_stage.spawn()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//check if all between stages have no checkpoints required to pass them
|
||||||
|
for stage_id in current_stage_id.get()+1..target_stage_id.get(){
|
||||||
|
let stage_id=StageId::new(stage_id);
|
||||||
|
//check if none of the between stages has checkpoints, if they do teleport back to that stage
|
||||||
|
match mode.get_stage(stage_id){
|
||||||
|
Some(stage)=>if !stage.is_empty(){
|
||||||
|
return CheckpointCheckOutcome{
|
||||||
|
set_stage:Some(stage_id),
|
||||||
|
teleport_to_model:Some(stage.spawn()),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
//no such stage! set to last existing stage
|
||||||
|
None=>return CheckpointCheckOutcome{
|
||||||
|
set_stage:Some(StageId::new(stage_id.get()-1)),
|
||||||
|
teleport_to_model:None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//notably you do not get teleported for touching ordered checkpoints in the wrong order within the same stage.
|
||||||
|
return CheckpointCheckOutcome{
|
||||||
|
set_stage:Some(target_stage_id),
|
||||||
|
teleport_to_model:None,
|
||||||
|
};
|
||||||
|
}else if stage_element.force(){
|
||||||
|
//forced stage_element will set the stage_id even if the stage has already been passed
|
||||||
|
return CheckpointCheckOutcome{
|
||||||
|
set_stage:Some(target_stage_id),
|
||||||
|
teleport_to_model:None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
CheckpointCheckOutcome{
|
||||||
|
set_stage:None,
|
||||||
|
teleport_to_model:None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn run_teleport_behaviour(
|
fn run_teleport_behaviour(
|
||||||
model_id:ModelId,
|
model_id:ModelId,
|
||||||
wormhole:Option<&gameplay_attributes::Wormhole>,
|
wormhole:Option<&gameplay_attributes::Wormhole>,
|
||||||
@ -1490,55 +1405,23 @@ fn run_teleport_behaviour(
|
|||||||
input_state:&InputState,
|
input_state:&InputState,
|
||||||
time:Time,
|
time:Time,
|
||||||
){
|
){
|
||||||
//TODO: jump count and checkpoints are always reset on teleport.
|
|
||||||
//Map makers are expected to use tools to prevent
|
|
||||||
//multi-boosting on JumpLimit boosters such as spawning into a SetVelocity
|
|
||||||
if let Some(mode)=mode{
|
if let Some(mode)=mode{
|
||||||
if let Some(stage_element)=mode.get_element(model_id){
|
if let Some(stage_element)=mode.get_element(model_id){
|
||||||
if let Some(stage)=mode.get_stage(stage_element.stage_id()){
|
if let Some(stage)=mode.get_stage(stage_element.stage_id()){
|
||||||
if mode_state.get_stage_id()<stage_element.stage_id(){
|
let CheckpointCheckOutcome{set_stage,teleport_to_model}=checkpoint_check(mode_state,stage_element,mode);
|
||||||
//checkpoint check
|
if let Some(stage_id)=set_stage{
|
||||||
//check if current stage is complete
|
|
||||||
if let Some(current_stage)=mode.get_stage(mode_state.get_stage_id()){
|
|
||||||
if !current_stage.is_complete(mode_state.ordered_checkpoint_count(),mode_state.unordered_checkpoint_count()){
|
|
||||||
//do the stage checkpoints have to be reset?
|
|
||||||
let _=teleport_to_spawn(current_stage,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//check if all between stages have no checkpoints required to pass them
|
|
||||||
let mut loop_unbroken=true;
|
|
||||||
for stage_id in mode_state.get_stage_id().get()+1..stage_element.stage_id().get(){
|
|
||||||
let stage_id=StageId::new(stage_id);
|
|
||||||
//check if none of the between stages has checkpoints, if they do teleport back to that stage
|
|
||||||
match mode.get_stage(stage_id){
|
|
||||||
Some(stage)=>if !stage.is_empty(){
|
|
||||||
mode_state.set_stage_id(stage_id);
|
mode_state.set_stage_id(stage_id);
|
||||||
let _=teleport_to_spawn(stage,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
}
|
||||||
|
if let Some(model_id)=teleport_to_model{
|
||||||
|
let _=teleport_to_spawn(model_id,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
||||||
return;
|
return;
|
||||||
},
|
|
||||||
None=>{
|
|
||||||
//no such stage! set to last existing stage and break loop
|
|
||||||
mode_state.set_stage_id(StageId::new(stage_id.get()-1));
|
|
||||||
loop_unbroken=false;
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//notably you do not get teleported for touching ordered checkpoints in the wrong order within the same stage.
|
|
||||||
if loop_unbroken{
|
|
||||||
mode_state.set_stage_id(stage_element.stage_id());
|
|
||||||
}
|
|
||||||
}else if stage_element.force(){
|
|
||||||
//forced stage_element will set the stage_id even if the stage has already been passed
|
|
||||||
mode_state.set_stage_id(stage_element.stage_id());
|
|
||||||
}
|
}
|
||||||
match stage_element.behaviour(){
|
match stage_element.behaviour(){
|
||||||
gameplay_modes::StageElementBehaviour::SpawnAt=>(),
|
gameplay_modes::StageElementBehaviour::SpawnAt=>(),
|
||||||
gameplay_modes::StageElementBehaviour::Trigger
|
gameplay_modes::StageElementBehaviour::Trigger
|
||||||
|gameplay_modes::StageElementBehaviour::Teleport=>if let Some(mode_state_stage)=mode.get_stage(mode_state.get_stage_id()){
|
|gameplay_modes::StageElementBehaviour::Teleport=>if let Some(mode_state_stage)=mode.get_stage(mode_state.get_stage_id()){
|
||||||
//I guess this is correct behaviour when trying to teleport to a non-existent spawn but it's still weird
|
//I guess this is correct behaviour when trying to teleport to a non-existent spawn but it's still weird
|
||||||
let _=teleport_to_spawn(mode_state_stage,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
let _=teleport_to_spawn(mode_state_stage.spawn(),move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
gameplay_modes::StageElementBehaviour::Platform=>(),
|
gameplay_modes::StageElementBehaviour::Platform=>(),
|
||||||
@ -1563,6 +1446,18 @@ fn run_teleport_behaviour(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn not_spawn_at(
|
||||||
|
mode:Option<&gameplay_modes::Mode>,
|
||||||
|
model_id:ModelId,
|
||||||
|
)->bool{
|
||||||
|
if let Some(mode)=mode{
|
||||||
|
if let Some(stage_element)=mode.get_element(model_id){
|
||||||
|
return stage_element.behaviour()!=gameplay_modes::StageElementBehaviour::SpawnAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn collision_start_contact(
|
fn collision_start_contact(
|
||||||
move_state:&mut MoveState,
|
move_state:&mut MoveState,
|
||||||
body:&mut Body,
|
body:&mut Body,
|
||||||
@ -1585,6 +1480,9 @@ fn collision_start_contact(
|
|||||||
touching.insert(Collision::Contact(contact));
|
touching.insert(Collision::Contact(contact));
|
||||||
//clip v
|
//clip v
|
||||||
set_velocity(body,touching,models,hitbox_mesh,incident_velocity);
|
set_velocity(body,touching,models,hitbox_mesh,incident_velocity);
|
||||||
|
let mut allow_jump=true;
|
||||||
|
let model_id=contact.model_id.into();
|
||||||
|
let mut allow_run_teleport_behaviour=not_spawn_at(mode,model_id);
|
||||||
match &attr.contacting.contact_behaviour{
|
match &attr.contacting.contact_behaviour{
|
||||||
Some(gameplay_attributes::ContactingBehaviour::Surf)=>println!("I'm surfing!"),
|
Some(gameplay_attributes::ContactingBehaviour::Surf)=>println!("I'm surfing!"),
|
||||||
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
|
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
|
||||||
@ -1605,9 +1503,10 @@ fn collision_start_contact(
|
|||||||
let walk_state=ContactMoveState::ladder(ladder_settings,body,gravity,target_velocity,contact);
|
let walk_state=ContactMoveState::ladder(ladder_settings,body,gravity,target_velocity,contact);
|
||||||
move_state.set_move_state(MoveState::Ladder(walk_state),body,touching,models,hitbox_mesh,style,camera,input_state);
|
move_state.set_move_state(MoveState::Ladder(walk_state),body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||||
},
|
},
|
||||||
Some(gameplay_attributes::ContactingBehaviour::NoJump)=>todo!("nyi"),
|
Some(gameplay_attributes::ContactingBehaviour::NoJump)=>allow_jump=false,
|
||||||
None=>if let Some(walk_settings)=&style.walk{
|
None=>if let Some(walk_settings)=&style.walk{
|
||||||
if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact),vec3::Y){
|
if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact),vec3::Y){
|
||||||
|
allow_run_teleport_behaviour=true;
|
||||||
//ground
|
//ground
|
||||||
let (gravity,target_velocity)=ground_things(walk_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state);
|
let (gravity,target_velocity)=ground_things(walk_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state);
|
||||||
let walk_state=ContactMoveState::ground(walk_settings,body,gravity,target_velocity,contact);
|
let walk_state=ContactMoveState::ground(walk_settings,body,gravity,target_velocity,contact);
|
||||||
@ -1616,14 +1515,32 @@ fn collision_start_contact(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
//I love making functions with 10 arguments to dodge the borrow checker
|
//I love making functions with 10 arguments to dodge the borrow checker
|
||||||
run_teleport_behaviour(contact.model_id.into(),attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
if allow_run_teleport_behaviour{
|
||||||
if style.get_control(Controls::Jump,input_state.controls){
|
run_teleport_behaviour(model_id,attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
||||||
|
}
|
||||||
|
if allow_jump&&style.get_control(Controls::Jump,input_state.controls){
|
||||||
if let (Some(jump_settings),Some(walk_state))=(&style.jump,move_state.get_walk_state()){
|
if let (Some(jump_settings),Some(walk_state))=(&style.jump,move_state.get_walk_state()){
|
||||||
|
let mut exceeded_jump_limit=false;
|
||||||
|
if let Some(mode)=mode{
|
||||||
|
if let Some(stage_element)=mode.get_element(model_id){
|
||||||
|
if !mode_state.try_increment_jump_count(model_id,stage_element.jump_limit()).is_allowed(){
|
||||||
|
exceeded_jump_limit=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if exceeded_jump_limit{
|
||||||
|
if let Some(mode)=mode{
|
||||||
|
if let Some(spawn_model_id)=mode.get_spawn_model_id(mode_state.get_stage_id()){
|
||||||
|
let _=teleport_to_spawn(spawn_model_id,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
let jump_dir=walk_state.jump_direction.direction(models,hitbox_mesh,&walk_state.contact);
|
let jump_dir=walk_state.jump_direction.direction(models,hitbox_mesh,&walk_state.contact);
|
||||||
let jumped_velocity=jump_settings.jumped_velocity(style,jump_dir,body.velocity,attr.general.booster.as_ref());
|
let jumped_velocity=jump_settings.jumped_velocity(style,jump_dir,body.velocity,attr.general.booster.as_ref());
|
||||||
move_state.cull_velocity(jumped_velocity,body,touching,models,hitbox_mesh,style,camera,input_state);
|
move_state.cull_velocity(jumped_velocity,body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
match &attr.general.trajectory{
|
match &attr.general.trajectory{
|
||||||
Some(trajectory)=>{
|
Some(trajectory)=>{
|
||||||
match trajectory{
|
match trajectory{
|
||||||
@ -1733,7 +1650,7 @@ fn collision_end_intersect(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInternalInstruction>){
|
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInternalInstruction,TimeInner>){
|
||||||
state.time=ins.time;
|
state.time=ins.time;
|
||||||
let (should_advance_body,goober_time)=match ins.instruction{
|
let (should_advance_body,goober_time)=match ins.instruction{
|
||||||
PhysicsInternalInstruction::CollisionStart(_,dt)
|
PhysicsInternalInstruction::CollisionStart(_,dt)
|
||||||
@ -1829,7 +1746,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInputInstruction>){
|
fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInputInstruction,TimeInner>){
|
||||||
state.time=ins.time;
|
state.time=ins.time;
|
||||||
let should_advance_body=match ins.instruction{
|
let should_advance_body=match ins.instruction{
|
||||||
//the body may as well be a quantum wave function
|
//the body may as well be a quantum wave function
|
||||||
@ -1923,7 +1840,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
if let Some(mode)=data.modes.get_mode(mode_id){
|
if let Some(mode)=data.modes.get_mode(mode_id){
|
||||||
if let Some(stage)=mode.get_stage(stage_id){
|
if let Some(stage)=mode.get_stage(stage_id){
|
||||||
let _=teleport_to_spawn(
|
let _=teleport_to_spawn(
|
||||||
stage,
|
stage.spawn(),
|
||||||
&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,
|
&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,
|
||||||
mode,
|
mode,
|
||||||
&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time
|
&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time
|
||||||
@ -1955,29 +1872,10 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInstruction>){
|
|
||||||
match &ins.instruction{
|
|
||||||
PhysicsInstruction::Input(PhysicsInputInstruction::Idle)
|
|
||||||
|PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_))
|
|
||||||
|PhysicsInstruction::Input(PhysicsInputInstruction::ReplaceMouse(_,_))
|
|
||||||
|PhysicsInstruction::Internal(PhysicsInternalInstruction::StrafeTick)
|
|
||||||
|PhysicsInstruction::Internal(PhysicsInternalInstruction::ReachWalkTargetVelocity)=>(),
|
|
||||||
_=>println!("{}|{:?}",ins.time,ins.instruction),
|
|
||||||
}
|
|
||||||
if ins.time<state.time{
|
|
||||||
println!("@@@@ Time travel warning! state.time={} ins.time={}\nInstruction={:?}",state.time,ins.time,ins.instruction);
|
|
||||||
}
|
|
||||||
//idle is special, it is specifically a no-op to get Internal events to catch up to real time
|
|
||||||
match ins.instruction{
|
|
||||||
PhysicsInstruction::Input(PhysicsInputInstruction::Idle)=>(),
|
|
||||||
PhysicsInstruction::Internal(instruction)=>atomic_internal_instruction(state,data,TimedInstruction{time:ins.time,instruction}),
|
|
||||||
PhysicsInstruction::Input(instruction)=>atomic_input_instruction(state,data,TimedInstruction{time:ins.time,instruction}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test{
|
mod test{
|
||||||
use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
|
use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
|
||||||
|
use crate::body::VirtualBody;
|
||||||
use super::*;
|
use super::*;
|
||||||
fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option<Time>){
|
fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option<Time>){
|
||||||
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::ZERO));
|
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::ZERO));
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
use strafesnet_common::mouse::MouseState;
|
use strafesnet_common::mouse::MouseState;
|
||||||
use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
|
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
||||||
use strafesnet_common::integer::Time;
|
use strafesnet_common::physics::{Time as PhysicsTime,TimeInner as PhysicsTimeInner,Instruction as PhysicsInputInstruction};
|
||||||
use strafesnet_common::instruction::TimedInstruction;
|
use strafesnet_common::instruction::TimedInstruction;
|
||||||
use strafesnet_common::timer::{Scaled,Timer,TimerState};
|
use strafesnet_common::timer::{Scaled,Timer,TimerState};
|
||||||
use mouse_interpolator::MouseInterpolator;
|
use mouse_interpolator::MouseInterpolator;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct FrameState{
|
||||||
|
pub body:crate::physics::Body,
|
||||||
|
pub camera:crate::physics::PhysicsCamera,
|
||||||
|
pub time:PhysicsTime,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum InputInstruction{
|
pub enum InputInstruction{
|
||||||
MoveMouse(glam::IVec2),
|
MoveMouse(glam::IVec2),
|
||||||
@ -36,11 +43,11 @@ pub struct MouseInterpolator{
|
|||||||
//"PlayerController"
|
//"PlayerController"
|
||||||
user_settings:crate::settings::UserSettings,
|
user_settings:crate::settings::UserSettings,
|
||||||
//"MouseInterpolator"
|
//"MouseInterpolator"
|
||||||
timeline:std::collections::VecDeque<TimedInstruction<PhysicsInputInstruction>>,
|
timeline:std::collections::VecDeque<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
||||||
last_mouse_time:Time,//this value is pre-transformed to simulation time
|
last_mouse_time:PhysicsTime,
|
||||||
mouse_blocking:bool,
|
mouse_blocking:bool,
|
||||||
//"Simulation"
|
//"Simulation"
|
||||||
timer:Timer<Scaled>,
|
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
||||||
physics:crate::physics::PhysicsContext,
|
physics:crate::physics::PhysicsContext,
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -58,7 +65,7 @@ impl MouseInterpolator{
|
|||||||
user_settings,
|
user_settings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn push_mouse_instruction(&mut self,ins:&TimedInstruction<Instruction>,m:glam::IVec2){
|
fn push_mouse_instruction(&mut self,ins:&TimedInstruction<Instruction,SessionTimeInner>,m:glam::IVec2){
|
||||||
if self.mouse_blocking{
|
if self.mouse_blocking{
|
||||||
//tell the game state which is living in the past about its future
|
//tell the game state which is living in the past about its future
|
||||||
self.timeline.push_front(TimedInstruction{
|
self.timeline.push_front(TimedInstruction{
|
||||||
@ -80,7 +87,7 @@ impl MouseInterpolator{
|
|||||||
}
|
}
|
||||||
self.last_mouse_time=self.timer.time(ins.time);
|
self.last_mouse_time=self.timer.time(ins.time);
|
||||||
}
|
}
|
||||||
fn push(&mut self,time:Time,phys_input:PhysicsInputInstruction){
|
fn push(&mut self,time:SessionTime,phys_input:PhysicsInputInstruction){
|
||||||
//This is always a non-mouse event
|
//This is always a non-mouse event
|
||||||
self.timeline.push_back(TimedInstruction{
|
self.timeline.push_back(TimedInstruction{
|
||||||
time:self.timer.time(time),
|
time:self.timer.time(time),
|
||||||
@ -89,7 +96,7 @@ impl MouseInterpolator{
|
|||||||
}
|
}
|
||||||
/// returns should_empty_queue
|
/// returns should_empty_queue
|
||||||
/// may or may not mutate internal state XD!
|
/// may or may not mutate internal state XD!
|
||||||
fn map_instruction(&mut self,ins:&TimedInstruction<Instruction>)->bool{
|
fn map_instruction(&mut self,ins:&TimedInstruction<Instruction,SessionTimeInner>)->bool{
|
||||||
let mut update_mouse_blocking=true;
|
let mut update_mouse_blocking=true;
|
||||||
match &ins.instruction{
|
match &ins.instruction{
|
||||||
Instruction::Input(input_instruction)=>match input_instruction{
|
Instruction::Input(input_instruction)=>match input_instruction{
|
||||||
@ -126,7 +133,7 @@ impl MouseInterpolator{
|
|||||||
Instruction::Render=>self.push(ins.time,PhysicsInputInstruction::Idle),
|
Instruction::Render=>self.push(ins.time,PhysicsInputInstruction::Idle),
|
||||||
&Instruction::SetPaused(paused)=>{
|
&Instruction::SetPaused(paused)=>{
|
||||||
if let Err(e)=self.timer.set_paused(ins.time,paused){
|
if let Err(e)=self.timer.set_paused(ins.time,paused){
|
||||||
println!("Cannot pause: {e}");
|
println!("Cannot SetPaused: {e}");
|
||||||
}
|
}
|
||||||
self.push(ins.time,PhysicsInputInstruction::Idle);
|
self.push(ins.time,PhysicsInputInstruction::Idle);
|
||||||
},
|
},
|
||||||
@ -140,7 +147,7 @@ impl MouseInterpolator{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// must check if self.mouse_blocking==true before calling!
|
/// must check if self.mouse_blocking==true before calling!
|
||||||
fn unblock_mouse(&mut self,time:Time){
|
fn unblock_mouse(&mut self,time:SessionTime){
|
||||||
//push an event to extrapolate no movement from
|
//push an event to extrapolate no movement from
|
||||||
self.timeline.push_front(TimedInstruction{
|
self.timeline.push_front(TimedInstruction{
|
||||||
time:self.last_mouse_time,
|
time:self.last_mouse_time,
|
||||||
@ -150,13 +157,13 @@ impl MouseInterpolator{
|
|||||||
//stop blocking. the mouse is not moving so the physics does not need to live in the past and wait for interpolation targets.
|
//stop blocking. the mouse is not moving so the physics does not need to live in the past and wait for interpolation targets.
|
||||||
self.mouse_blocking=false;
|
self.mouse_blocking=false;
|
||||||
}
|
}
|
||||||
fn update_mouse_blocking(&mut self,time:Time)->bool{
|
fn update_mouse_blocking(&mut self,time:SessionTime)->bool{
|
||||||
if self.mouse_blocking{
|
if self.mouse_blocking{
|
||||||
//assume the mouse has stopped moving after 10ms.
|
//assume the mouse has stopped moving after 10ms.
|
||||||
//shitty mice are 125Hz which is 8ms so this should cover that.
|
//shitty mice are 125Hz which is 8ms so this should cover that.
|
||||||
//setting this to 100us still doesn't print even though it's 10x lower than the polling rate,
|
//setting this to 100us still doesn't print even though it's 10x lower than the polling rate,
|
||||||
//so mouse events are probably not handled separately from drawing and fire right before it :(
|
//so mouse events are probably not handled separately from drawing and fire right before it :(
|
||||||
if Time::from_millis(10)<self.timer.time(time)-self.physics.get_next_mouse().time{
|
if PhysicsTime::from_millis(10)<self.timer.time(time)-self.physics.get_next_mouse().time{
|
||||||
self.unblock_mouse(time);
|
self.unblock_mouse(time);
|
||||||
true
|
true
|
||||||
}else{
|
}else{
|
||||||
@ -174,20 +181,20 @@ impl MouseInterpolator{
|
|||||||
self.physics.run_input_instruction(instruction);
|
self.physics.run_input_instruction(instruction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn handle_instruction(&mut self,ins:&TimedInstruction<Instruction>){
|
pub fn handle_instruction(&mut self,ins:&TimedInstruction<Instruction,SessionTimeInner>){
|
||||||
let should_empty_queue=self.map_instruction(ins);
|
let should_empty_queue=self.map_instruction(ins);
|
||||||
if should_empty_queue{
|
if should_empty_queue{
|
||||||
self.empty_queue();
|
self.empty_queue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_frame_state(&self,time:Time)->crate::graphics::FrameState{
|
pub fn get_frame_state(&self,time:SessionTime)->FrameState{
|
||||||
crate::graphics::FrameState{
|
FrameState{
|
||||||
body:self.physics.camera_body(),
|
body:self.physics.camera_body(),
|
||||||
camera:self.physics.camera(),
|
camera:self.physics.camera(),
|
||||||
time:self.timer.time(time),
|
time:self.timer.time(time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn change_map(&mut self,time:Time,map:&strafesnet_common::map::CompleteMap){
|
pub fn change_map(&mut self,time:SessionTime,map:&strafesnet_common::map::CompleteMap){
|
||||||
//dump any pending interpolation state
|
//dump any pending interpolation state
|
||||||
if self.mouse_blocking{
|
if self.mouse_blocking{
|
||||||
self.unblock_mouse(time);
|
self.unblock_mouse(time);
|
||||||
@ -199,7 +206,7 @@ impl MouseInterpolator{
|
|||||||
|
|
||||||
//use the standard input interface so the instructions are written out to bots
|
//use the standard input interface so the instructions are written out to bots
|
||||||
self.handle_instruction(&TimedInstruction{
|
self.handle_instruction(&TimedInstruction{
|
||||||
time:self.timer.time(time),
|
time,
|
||||||
instruction:Instruction::Input(InputInstruction::ResetAndSpawn(
|
instruction:Instruction::Input(InputInstruction::ResetAndSpawn(
|
||||||
strafesnet_common::gameplay_modes::ModeId::MAIN,
|
strafesnet_common::gameplay_modes::ModeId::MAIN,
|
||||||
strafesnet_common::gameplay_modes::StageId::FIRST,
|
strafesnet_common::gameplay_modes::StageId::FIRST,
|
||||||
@ -212,137 +219,16 @@ impl MouseInterpolator{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vector3_to_glam(v:&strafesnet_roblox_bot_file::v0::Vector3)->glam::Vec3{
|
|
||||||
glam::vec3(v.x,v.y,v.z)
|
|
||||||
}
|
|
||||||
fn f32_to_p64(f:f32)->strafesnet_common::integer::Planar64{
|
|
||||||
f.try_into().unwrap_or(strafesnet_common::integer::Planar64::ZERO)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PlayBacker{
|
|
||||||
//Instructions
|
|
||||||
timelines:strafesnet_roblox_bot_file::v0::Block,
|
|
||||||
//"Simulation"
|
|
||||||
event_id:usize,
|
|
||||||
offset:f64,
|
|
||||||
duration:f64,
|
|
||||||
timer:Timer<Scaled>,
|
|
||||||
physics:crate::physics::PhysicsContext,
|
|
||||||
}
|
|
||||||
impl PlayBacker{
|
|
||||||
pub fn new(
|
|
||||||
physics:crate::physics::PhysicsContext,
|
|
||||||
timelines:strafesnet_roblox_bot_file::v0::Block,
|
|
||||||
)->Self{
|
|
||||||
let first=timelines.output_events.first().unwrap();
|
|
||||||
let last=timelines.output_events.last().unwrap();
|
|
||||||
Self{
|
|
||||||
offset:first.time,
|
|
||||||
duration:last.time-first.time,
|
|
||||||
timelines,
|
|
||||||
event_id:0,
|
|
||||||
timer:Timer::from_state(Scaled::identity(),false),
|
|
||||||
physics,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn handle_instruction(&mut self,TimedInstruction{time,instruction}:&TimedInstruction<Instruction>){
|
|
||||||
//match the instruction so the playback is pausable :D
|
|
||||||
match instruction{
|
|
||||||
&Instruction::SetPaused(paused)=>{
|
|
||||||
let _=self.timer.set_paused(*time,paused);
|
|
||||||
},
|
|
||||||
_=>(),
|
|
||||||
}
|
|
||||||
let simulation_time=self.timer.time(*time);
|
|
||||||
let mut time_float=simulation_time.get() as f64/Time::ONE_SECOND.get() as f64+self.offset;
|
|
||||||
loop{
|
|
||||||
match self.timelines.output_events.get(self.event_id+1){
|
|
||||||
Some(next_event)=>{
|
|
||||||
if next_event.time<time_float{
|
|
||||||
self.event_id+=1;
|
|
||||||
}else{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None=>{
|
|
||||||
//reset playback
|
|
||||||
self.event_id=0;
|
|
||||||
self.offset-=self.duration;
|
|
||||||
time_float-=self.duration;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_frame_state(&self,time:Time)->crate::graphics::FrameState{
|
|
||||||
let time=self.timer.time(time);
|
|
||||||
let event0=&self.timelines.output_events[self.event_id];
|
|
||||||
let event1=&self.timelines.output_events[self.event_id+1];
|
|
||||||
let p0=vector3_to_glam(&event0.event.position);
|
|
||||||
let p1=vector3_to_glam(&event1.event.position);
|
|
||||||
let v0=vector3_to_glam(&event0.event.velocity);
|
|
||||||
let v1=vector3_to_glam(&event1.event.velocity);
|
|
||||||
let a0=vector3_to_glam(&event0.event.acceleration);
|
|
||||||
let a1=vector3_to_glam(&event1.event.acceleration);
|
|
||||||
let t0=event0.time;
|
|
||||||
let t1=event1.time;
|
|
||||||
let time_float=time.get() as f64/Time::ONE_SECOND.get() as f64;
|
|
||||||
let t=((time_float+self.offset-t0)/(t1-t0)) as f32;
|
|
||||||
let p=p0.lerp(p1,t).to_array().map(f32_to_p64);
|
|
||||||
let v=v0.lerp(v1,t).to_array().map(f32_to_p64);
|
|
||||||
let a=a0.lerp(a1,t).to_array().map(f32_to_p64);
|
|
||||||
|
|
||||||
//println!("position={:?}",p);
|
|
||||||
|
|
||||||
let angles0=vector3_to_glam(&event0.event.angles);
|
|
||||||
let angles1=vector3_to_glam(&event1.event.angles);
|
|
||||||
let angles=angles0.lerp(angles1,t);
|
|
||||||
// mask mantissa out and set it to minimum value
|
|
||||||
// let ax_epsilon=f32::from_bits(angles.x.to_bits()&!((1<<23)-1)|1);
|
|
||||||
// let ay_epsilon=f32::from_bits(angles.y.to_bits()&!((1<<23)-1)|1);
|
|
||||||
let body=crate::physics::Body{
|
|
||||||
time,
|
|
||||||
position:strafesnet_common::integer::Planar64Vec3::new(p)+self.physics.state.style.camera_offset,
|
|
||||||
velocity:strafesnet_common::integer::Planar64Vec3::new(v),
|
|
||||||
acceleration:strafesnet_common::integer::Planar64Vec3::new(a),
|
|
||||||
};
|
|
||||||
const FLOAT64_TO_ANGLE32_RADIANS:f64=((1i64<<31) as f64)/std::f64::consts::PI;
|
|
||||||
// xy is reversed in strafe client for some reason
|
|
||||||
let (ax,ay)=(
|
|
||||||
-angles.y as f64*FLOAT64_TO_ANGLE32_RADIANS,
|
|
||||||
-angles.x as f64*FLOAT64_TO_ANGLE32_RADIANS,
|
|
||||||
);
|
|
||||||
let camera=crate::physics::PhysicsCamera::new(
|
|
||||||
strafesnet_common::integer::Ratio64Vec2::new(1.0f32.try_into().unwrap(),1.0f32.try_into().unwrap()),
|
|
||||||
glam::ivec2(ax as i64 as i32,ay as i64 as i32)
|
|
||||||
);
|
|
||||||
crate::graphics::FrameState{
|
|
||||||
body,
|
|
||||||
camera,
|
|
||||||
time,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn user_settings(&self)->crate::settings::UserSettings{
|
|
||||||
//oof, settings ignored
|
|
||||||
crate::settings::UserSettings::default()
|
|
||||||
}
|
|
||||||
pub fn change_map(&mut self,time:Time,map:&strafesnet_common::map::CompleteMap){
|
|
||||||
self.physics.generate_models(&map);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new<'a>(
|
pub fn new<'a>(
|
||||||
mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>,
|
mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>,
|
||||||
user_settings:crate::settings::UserSettings,
|
user_settings:crate::settings::UserSettings,
|
||||||
)->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction>>{
|
)->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>{
|
||||||
let physics=crate::physics::PhysicsContext::default();
|
let physics=crate::physics::PhysicsContext::default();
|
||||||
//load bot
|
let mut interpolator=MouseInterpolator::new(
|
||||||
let data=include_bytes!("/home/quat/strafesnet/roblox_bot_file/files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d");
|
|
||||||
let mut bot_file=strafesnet_roblox_bot_file::v0::File::new(std::io::BufReader::new(std::io::Cursor::new(data))).unwrap();
|
|
||||||
let mut interpolator=PlayBacker::new(
|
|
||||||
physics,
|
physics,
|
||||||
bot_file.read_all().unwrap(),
|
user_settings
|
||||||
);
|
);
|
||||||
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction>|{
|
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTimeInner>|{
|
||||||
interpolator.handle_instruction(&ins);
|
interpolator.handle_instruction(&ins);
|
||||||
match ins.instruction{
|
match ins.instruction{
|
||||||
Instruction::Render=>{
|
Instruction::Render=>{
|
||||||
@ -350,7 +236,7 @@ pub fn new<'a>(
|
|||||||
graphics_worker.send(crate::graphics_worker::Instruction::Render(frame_state)).unwrap();
|
graphics_worker.send(crate::graphics_worker::Instruction::Render(frame_state)).unwrap();
|
||||||
},
|
},
|
||||||
Instruction::Resize(size)=>{
|
Instruction::Resize(size)=>{
|
||||||
graphics_worker.send(crate::graphics_worker::Instruction::Resize(size,interpolator.user_settings())).unwrap();
|
graphics_worker.send(crate::graphics_worker::Instruction::Resize(size,interpolator.user_settings().clone())).unwrap();
|
||||||
},
|
},
|
||||||
Instruction::ChangeMap(map)=>{
|
Instruction::ChangeMap(map)=>{
|
||||||
interpolator.change_map(ins.time,&map);
|
interpolator.change_map(ins.time,&map);
|
||||||
|
349
strafe-client/src/push_solve.rs
Normal file
349
strafe-client/src/push_solve.rs
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
use strafesnet_common::integer::{self,vec3::{self,Vector3},Fixed,Planar64,Planar64Vec3,Ratio};
|
||||||
|
|
||||||
|
// This algorithm is based on Lua code
|
||||||
|
// written by Trey Reynolds in 2021
|
||||||
|
|
||||||
|
// EPSILON=1/2^10
|
||||||
|
|
||||||
|
// A stack-allocated variable-size list that holds up to 4 elements
|
||||||
|
// Direct references are used instead of indices i0, i1, i2, i3
|
||||||
|
type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
|
||||||
|
|
||||||
|
// hack to allow comparing ratios to zero
|
||||||
|
const RATIO_ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON);
|
||||||
|
|
||||||
|
struct Ray{
|
||||||
|
origin:Planar64Vec3,
|
||||||
|
direction:Planar64Vec3,
|
||||||
|
}
|
||||||
|
impl Ray{
|
||||||
|
fn extrapolate<Num,Den,N1,T1>(&self,t:Ratio<Num,Den>)->Planar64Vec3
|
||||||
|
where
|
||||||
|
Num:Copy,
|
||||||
|
Den:Copy,
|
||||||
|
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||||
|
Planar64:core::ops::Mul<Den,Output=N1>,
|
||||||
|
N1:integer::Divide<Den,Output=T1>,
|
||||||
|
T1:integer::Fix<Planar64>,
|
||||||
|
{
|
||||||
|
self.origin+self.direction.map(|elem|(t*elem).divide().fix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about a contact restriction
|
||||||
|
pub struct Contact{
|
||||||
|
pub position:Planar64Vec3,
|
||||||
|
pub velocity:Planar64Vec3,
|
||||||
|
pub normal:Planar64Vec3,
|
||||||
|
}
|
||||||
|
impl Contact{
|
||||||
|
fn relative_to(&self,point:Planar64Vec3)->Self{
|
||||||
|
Self{
|
||||||
|
position:self.position-point,
|
||||||
|
velocity:self.velocity,
|
||||||
|
normal:self.normal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn relative_dot(&self,direction:Planar64Vec3)->Fixed<2,64>{
|
||||||
|
(direction-self.velocity).dot(self.normal)
|
||||||
|
}
|
||||||
|
/// Calculate the time of intersection. (previously get_touch_time)
|
||||||
|
fn solve(&self,ray:&Ray)->Ratio<Fixed<2,64>,Fixed<2,64>>{
|
||||||
|
(self.position-ray.origin).dot(self.normal)/(ray.direction-self.velocity).dot(self.normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//note that this is horrible with fixed point arithmetic
|
||||||
|
fn solve1(c0:&Contact)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<2,64>>>{
|
||||||
|
const EPSILON:Fixed<2,64>=Fixed::from_bits(Fixed::<2,64>::ONE.to_bits().shr(10));
|
||||||
|
let det=c0.normal.dot(c0.velocity);
|
||||||
|
if det.abs()<EPSILON{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let d0=c0.normal.dot(c0.position);
|
||||||
|
Some(c0.normal*d0/det)
|
||||||
|
}
|
||||||
|
fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<Fixed<5,160>>,Fixed<4,128>>>{
|
||||||
|
const EPSILON:Fixed<4,128>=Fixed::from_bits(Fixed::<4,128>::ONE.to_bits().shr(10));
|
||||||
|
let u0_u1=c0.velocity.cross(c1.velocity);
|
||||||
|
let n0_n1=c0.normal.cross(c1.normal);
|
||||||
|
let det=u0_u1.dot(n0_n1);
|
||||||
|
if det.abs()<EPSILON{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let d0=c0.normal.dot(c0.position);
|
||||||
|
let d1=c1.normal.dot(c1.position);
|
||||||
|
Some((c1.normal.cross(u0_u1)*d0+u0_u1.cross(c0.normal)*d1)/det)
|
||||||
|
}
|
||||||
|
fn solve3(c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ratio<Vector3<Fixed<4,128>>,Fixed<3,96>>>{
|
||||||
|
const EPSILON:Fixed<3,96>=Fixed::from_bits(Fixed::<3,96>::ONE.to_bits().shr(10));
|
||||||
|
let n0_n1=c0.normal.cross(c1.normal);
|
||||||
|
let det=c2.normal.dot(n0_n1);
|
||||||
|
if det.abs()<EPSILON{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let d0=c0.normal.dot(c0.position);
|
||||||
|
let d1=c1.normal.dot(c1.position);
|
||||||
|
let d2=c2.normal.dot(c2.position);
|
||||||
|
Some((c1.normal.cross(c2.normal)*d0+c2.normal.cross(c0.normal)*d1+c0.normal.cross(c1.normal)*d2)/det)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompose1(point:Planar64Vec3,u0:Planar64Vec3)->Option<[Ratio<Fixed<2,64>,Fixed<2,64>>;1]>{
|
||||||
|
let det=u0.dot(u0);
|
||||||
|
if det==Fixed::ZERO{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let s0=u0.dot(point)/det;
|
||||||
|
Some([s0])
|
||||||
|
}
|
||||||
|
fn decompose2(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3)->Option<[Ratio<Fixed<4,128>,Fixed<4,128>>;2]>{
|
||||||
|
let u0_u1=u0.cross(u1);
|
||||||
|
let det=u0_u1.dot(u0_u1);
|
||||||
|
if det==Fixed::ZERO{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let s0=u0_u1.dot(point.cross(u1))/det;
|
||||||
|
let s1=u0_u1.dot(u0.cross(point))/det;
|
||||||
|
Some([s0,s1])
|
||||||
|
}
|
||||||
|
fn decompose3(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3,u2:Planar64Vec3)->Option<[Ratio<Fixed<3,96>,Fixed<3,96>>;3]>{
|
||||||
|
let det=u0.cross(u1).dot(u2);
|
||||||
|
if det==Fixed::ZERO{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let s0=point.cross(u1).dot(u2)/det;
|
||||||
|
let s1=u0.cross(point).dot(u2)/det;
|
||||||
|
let s2=u0.cross(u1).dot(point)/det;
|
||||||
|
Some([s0,s1,s2])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_space_enclosed_2(
|
||||||
|
a:Planar64Vec3,
|
||||||
|
b:Planar64Vec3,
|
||||||
|
)->bool{
|
||||||
|
a.cross(b)==Vector3::new([Fixed::ZERO;3])
|
||||||
|
&&a.dot(b).is_negative()
|
||||||
|
}
|
||||||
|
fn is_space_enclosed_3(
|
||||||
|
a:Planar64Vec3,
|
||||||
|
b:Planar64Vec3,
|
||||||
|
c:Planar64Vec3
|
||||||
|
)->bool{
|
||||||
|
a.cross(b).dot(c)==Fixed::ZERO
|
||||||
|
&&{
|
||||||
|
let det_abac=a.cross(b).dot(a.cross(c));
|
||||||
|
let det_abbc=a.cross(b).dot(b.cross(c));
|
||||||
|
let det_acbc=a.cross(c).dot(b.cross(c));
|
||||||
|
return!( det_abac*det_abbc).is_positive()
|
||||||
|
&&!( det_abbc*det_acbc).is_positive()
|
||||||
|
&&!(-det_acbc*det_abac).is_positive()
|
||||||
|
||is_space_enclosed_2(a,b)
|
||||||
|
||is_space_enclosed_2(a,c)
|
||||||
|
||is_space_enclosed_2(b,c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn is_space_enclosed_4(
|
||||||
|
a:Planar64Vec3,
|
||||||
|
b:Planar64Vec3,
|
||||||
|
c:Planar64Vec3,
|
||||||
|
d:Planar64Vec3,
|
||||||
|
)->bool{
|
||||||
|
let det_abc=a.cross(b).dot(c);
|
||||||
|
let det_abd=a.cross(b).dot(d);
|
||||||
|
let det_acd=a.cross(c).dot(d);
|
||||||
|
let det_bcd=b.cross(c).dot(d);
|
||||||
|
return( det_abc*det_abd).is_negative()
|
||||||
|
&&(-det_abc*det_acd).is_negative()
|
||||||
|
&&( det_abd*det_acd).is_negative()
|
||||||
|
&&( det_abc*det_bcd).is_negative()
|
||||||
|
&&(-det_abd*det_bcd).is_negative()
|
||||||
|
&&( det_acd*det_bcd).is_negative()
|
||||||
|
||is_space_enclosed_3(a,b,c)
|
||||||
|
||is_space_enclosed_3(a,b,d)
|
||||||
|
||is_space_enclosed_3(a,c,d)
|
||||||
|
||is_space_enclosed_3(b,c,d)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn get_push_ray_0(point:Planar64Vec3)->Ray{
|
||||||
|
Ray{origin:point,direction:vec3::ZERO}
|
||||||
|
}
|
||||||
|
fn get_push_ray_1(point:Planar64Vec3,c0:&Contact)->Option<Ray>{
|
||||||
|
let direction=solve1(c0)?.divide().fix_1();
|
||||||
|
let [s0]=decompose1(direction,c0.velocity)?;
|
||||||
|
if s0.lt_ratio(RATIO_ZERO){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let origin=point+solve1(
|
||||||
|
&c0.relative_to(point),
|
||||||
|
)?.divide().fix_1();
|
||||||
|
Some(Ray{origin,direction})
|
||||||
|
}
|
||||||
|
fn get_push_ray_2(point:Planar64Vec3,c0:&Contact,c1:&Contact)->Option<Ray>{
|
||||||
|
let direction=solve2(c0,c1)?.divide().fix_1();
|
||||||
|
let [s0,s1]=decompose2(direction,c0.velocity,c1.velocity)?;
|
||||||
|
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let origin=point+solve2(
|
||||||
|
&c0.relative_to(point),
|
||||||
|
&c1.relative_to(point),
|
||||||
|
)?.divide().fix_1();
|
||||||
|
Some(Ray{origin,direction})
|
||||||
|
}
|
||||||
|
fn get_push_ray_3(point:Planar64Vec3,c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ray>{
|
||||||
|
let direction=solve3(c0,c1,c2)?.divide().fix_1();
|
||||||
|
let [s0,s1,s2]=decompose3(direction,c0.velocity,c1.velocity,c2.velocity)?;
|
||||||
|
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO)||s2.lt_ratio(RATIO_ZERO){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let origin=point+solve3(
|
||||||
|
&c0.relative_to(point),
|
||||||
|
&c1.relative_to(point),
|
||||||
|
&c2.relative_to(point),
|
||||||
|
)?.divide().fix_1();
|
||||||
|
Some(Ray{origin,direction})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn get_best_push_ray_and_conts_0<'a>(point:Planar64Vec3)->(Ray,Conts<'a>){
|
||||||
|
(get_push_ray_0(point),Conts::new_const())
|
||||||
|
}
|
||||||
|
fn get_best_push_ray_and_conts_1(point:Planar64Vec3,c0:&Contact)->Option<(Ray,Conts)>{
|
||||||
|
get_push_ray_1(point,c0)
|
||||||
|
.map(|ray|(ray,Conts::from_iter([c0])))
|
||||||
|
}
|
||||||
|
fn get_best_push_ray_and_conts_2<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact)->Option<(Ray,Conts<'a>)>{
|
||||||
|
if is_space_enclosed_2(c0.normal,c1.normal){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let Some(ray)=get_push_ray_2(point,c0,c1){
|
||||||
|
return Some((ray,Conts::from_iter([c0,c1])));
|
||||||
|
}
|
||||||
|
if let Some(ray)=get_push_ray_1(point,c0){
|
||||||
|
if !c1.relative_dot(ray.direction).is_negative(){
|
||||||
|
return Some((ray,Conts::from_iter([c0])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
fn get_best_push_ray_and_conts_3<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact,c2:&'a Contact)->Option<(Ray,Conts<'a>)>{
|
||||||
|
if is_space_enclosed_3(c0.normal,c1.normal,c2.normal){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let Some(ray)=get_push_ray_3(point,c0,c1,c2){
|
||||||
|
return Some((ray,Conts::from_iter([c0,c1,c2])));
|
||||||
|
}
|
||||||
|
if let Some(ray)=get_push_ray_2(point,c0,c1){
|
||||||
|
if !c2.relative_dot(ray.direction).is_negative(){
|
||||||
|
return Some((ray,Conts::from_iter([c0,c1])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ray)=get_push_ray_2(point,c0,c2){
|
||||||
|
if !c1.relative_dot(ray.direction).is_negative(){
|
||||||
|
return Some((ray,Conts::from_iter([c0,c2])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ray)=get_push_ray_1(point,c0){
|
||||||
|
if !c1.relative_dot(ray.direction).is_negative()
|
||||||
|
&&!c2.relative_dot(ray.direction).is_negative(){
|
||||||
|
return Some((ray,Conts::from_iter([c0])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
fn get_best_push_ray_and_conts_4<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact,c2:&'a Contact,c3:&'a Contact)->Option<(Ray,Conts<'a>)>{
|
||||||
|
if is_space_enclosed_4(c0.normal,c1.normal,c2.normal,c3.normal){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (ray012,conts012)=get_best_push_ray_and_conts_3(point,c0,c1,c2)?;
|
||||||
|
let (ray013,conts013)=get_best_push_ray_and_conts_3(point,c0,c1,c3)?;
|
||||||
|
let (ray023,conts023)=get_best_push_ray_and_conts_3(point,c0,c2,c3)?;
|
||||||
|
|
||||||
|
let err012=c3.relative_dot(ray012.direction);
|
||||||
|
let err013=c2.relative_dot(ray013.direction);
|
||||||
|
let err023=c1.relative_dot(ray023.direction);
|
||||||
|
|
||||||
|
let best_err=err012.max(err013).max(err023);
|
||||||
|
|
||||||
|
if best_err==err012{
|
||||||
|
return Some((ray012,conts012))
|
||||||
|
}else if best_err==err013{
|
||||||
|
return Some((ray013,conts013))
|
||||||
|
}else if best_err==err023{
|
||||||
|
return Some((ray023,conts023))
|
||||||
|
}
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_best_push_ray_and_conts<'a>(
|
||||||
|
point:Planar64Vec3,
|
||||||
|
conts:&[&'a Contact],
|
||||||
|
)->Option<(Ray,Conts<'a>)>{
|
||||||
|
match conts{
|
||||||
|
&[c0,c1,c2,c3]=>get_best_push_ray_and_conts_4(point,c0,c1,c2,c3),
|
||||||
|
&[c0,c1,c2]=>get_best_push_ray_and_conts_3(point,c0,c1,c2),
|
||||||
|
&[c0,c1]=>get_best_push_ray_and_conts_2(point,c0,c1),
|
||||||
|
&[c0]=>get_best_push_ray_and_conts_1(point,c0),
|
||||||
|
&[]=>Some(get_best_push_ray_and_conts_0(point)),
|
||||||
|
_=>unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_first_touch<'a>(contacts:&'a Vec<Contact>,ray:&Ray,conts:&Conts)->Option<(Ratio<Fixed<2,64>,Fixed<2,64>>,&'a Contact)>{
|
||||||
|
contacts.iter()
|
||||||
|
.filter(|&contact|
|
||||||
|
!conts.iter().any(|&c|std::ptr::eq(c,contact))
|
||||||
|
&&contact.relative_dot(ray.direction).is_negative()
|
||||||
|
)
|
||||||
|
.map(|contact|(contact.solve(ray),contact))
|
||||||
|
.min_by_key(|&(t,_)|t)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_solve(contacts:&Vec<Contact>,point:Planar64Vec3)->Planar64Vec3{
|
||||||
|
let (mut ray,mut conts)=get_best_push_ray_and_conts_0(point);
|
||||||
|
loop{
|
||||||
|
let (next_t,next_cont)=match get_first_touch(contacts,&ray,&conts){
|
||||||
|
Some((t,cont))=>(t,cont),
|
||||||
|
None=>return ray.origin,
|
||||||
|
};
|
||||||
|
|
||||||
|
if RATIO_ZERO.le_ratio(next_t){
|
||||||
|
return ray.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
//push_front
|
||||||
|
if conts.len()==conts.capacity(){
|
||||||
|
//this is a dead case, new_conts never has more than 3 elements
|
||||||
|
conts.rotate_right(1);
|
||||||
|
conts[0]=next_cont;
|
||||||
|
}else{
|
||||||
|
conts.push(next_cont);
|
||||||
|
conts.rotate_right(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let meet_point=ray.extrapolate(next_t);
|
||||||
|
match get_best_push_ray_and_conts(meet_point,conts.as_slice()){
|
||||||
|
Some((new_ray,new_conts))=>(ray,conts)=(new_ray,new_conts),
|
||||||
|
None=>return meet_point,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests{
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_push_solve(){
|
||||||
|
let contacts=vec![
|
||||||
|
Contact{
|
||||||
|
position:vec3::ZERO,
|
||||||
|
velocity:vec3::Y,
|
||||||
|
normal:vec3::Y,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
assert_eq!(
|
||||||
|
vec3::ZERO,
|
||||||
|
push_solve(&contacts,vec3::NEG_Y)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
use crate::window::WindowInstruction;
|
use crate::window::WindowInstruction;
|
||||||
use strafesnet_common::instruction::TimedInstruction;
|
use strafesnet_common::instruction::TimedInstruction;
|
||||||
use strafesnet_common::integer;
|
use strafesnet_common::integer;
|
||||||
|
use strafesnet_common::session::TimeInner as SessionTimeInner;
|
||||||
|
|
||||||
fn optional_features()->wgpu::Features{
|
fn optional_features()->wgpu::Features{
|
||||||
wgpu::Features::TEXTURE_COMPRESSION_ASTC
|
wgpu::Features::TEXTURE_COMPRESSION_ASTC
|
||||||
@ -32,16 +33,6 @@ fn create_window(title:&str,event_loop:&winit::event_loop::EventLoop<()>)->Resul
|
|||||||
use winit::platform::windows::WindowBuilderExtWindows;
|
use winit::platform::windows::WindowBuilderExtWindows;
|
||||||
builder=builder.with_no_redirection_bitmap(true);
|
builder=builder.with_no_redirection_bitmap(true);
|
||||||
}
|
}
|
||||||
#[cfg(target_arch="wasm32")]
|
|
||||||
{
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
use winit::platform::web::WindowAttributesExtWebSys;
|
|
||||||
let canvas=web_sys::window().unwrap()
|
|
||||||
.document().unwrap()
|
|
||||||
.get_element_by_id("canvas").unwrap()
|
|
||||||
.dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
|
|
||||||
attr=attr.with_canvas(Some(canvas));
|
|
||||||
}
|
|
||||||
event_loop.create_window(attr)
|
event_loop.create_window(attr)
|
||||||
}
|
}
|
||||||
fn create_instance()->SetupContextPartial1{
|
fn create_instance()->SetupContextPartial1{
|
||||||
@ -71,18 +62,36 @@ struct SetupContextPartial2<'a>{
|
|||||||
surface:wgpu::Surface<'a>,
|
surface:wgpu::Surface<'a>,
|
||||||
}
|
}
|
||||||
impl<'a> SetupContextPartial2<'a>{
|
impl<'a> SetupContextPartial2<'a>{
|
||||||
async fn pick_adapter(self)->SetupContextPartial3<'a>{
|
fn pick_adapter(self)->SetupContextPartial3<'a>{
|
||||||
let adapter;
|
let adapter;
|
||||||
|
|
||||||
//TODO: prefer adapter that implements optional features
|
//TODO: prefer adapter that implements optional features
|
||||||
//let optional_features=optional_features();
|
//let optional_features=optional_features();
|
||||||
let required_features=required_features();
|
let required_features=required_features();
|
||||||
|
|
||||||
let chosen_adapter=self.instance.request_adapter(&wgpu::RequestAdapterOptions{
|
//no helper function smh gotta write it myself
|
||||||
power_preference:wgpu::PowerPreference::HighPerformance,
|
let adapters=self.instance.enumerate_adapters(self.backends);
|
||||||
force_fallback_adapter:false,
|
|
||||||
compatible_surface:Some(&self.surface),
|
let mut chosen_adapter=None;
|
||||||
}).await;
|
let mut chosen_adapter_score=0;
|
||||||
|
for adapter in adapters {
|
||||||
|
if !adapter.is_surface_supported(&self.surface) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let score=match adapter.get_info().device_type{
|
||||||
|
wgpu::DeviceType::IntegratedGpu=>3,
|
||||||
|
wgpu::DeviceType::DiscreteGpu=>4,
|
||||||
|
wgpu::DeviceType::VirtualGpu=>2,
|
||||||
|
wgpu::DeviceType::Other|wgpu::DeviceType::Cpu=>1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let adapter_features=adapter.features();
|
||||||
|
if chosen_adapter_score<score&&adapter_features.contains(required_features) {
|
||||||
|
chosen_adapter_score=score;
|
||||||
|
chosen_adapter=Some(adapter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(maybe_chosen_adapter)=chosen_adapter{
|
if let Some(maybe_chosen_adapter)=chosen_adapter{
|
||||||
adapter=maybe_chosen_adapter;
|
adapter=maybe_chosen_adapter;
|
||||||
@ -121,7 +130,7 @@ struct SetupContextPartial3<'a>{
|
|||||||
adapter:wgpu::Adapter,
|
adapter:wgpu::Adapter,
|
||||||
}
|
}
|
||||||
impl<'a> SetupContextPartial3<'a>{
|
impl<'a> SetupContextPartial3<'a>{
|
||||||
async fn request_device(self)->SetupContextPartial4<'a>{
|
fn request_device(self)->SetupContextPartial4<'a>{
|
||||||
let optional_features=optional_features();
|
let optional_features=optional_features();
|
||||||
let required_features=required_features();
|
let required_features=required_features();
|
||||||
|
|
||||||
@ -129,7 +138,7 @@ impl<'a> SetupContextPartial3<'a>{
|
|||||||
let needed_limits=required_limits().using_resolution(self.adapter.limits());
|
let needed_limits=required_limits().using_resolution(self.adapter.limits());
|
||||||
|
|
||||||
let trace_dir=std::env::var("WGPU_TRACE");
|
let trace_dir=std::env::var("WGPU_TRACE");
|
||||||
let (device, queue)=self.adapter
|
let (device, queue)=pollster::block_on(self.adapter
|
||||||
.request_device(
|
.request_device(
|
||||||
&wgpu::DeviceDescriptor {
|
&wgpu::DeviceDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
@ -138,7 +147,7 @@ impl<'a> SetupContextPartial3<'a>{
|
|||||||
memory_hints:wgpu::MemoryHints::Performance,
|
memory_hints:wgpu::MemoryHints::Performance,
|
||||||
},
|
},
|
||||||
trace_dir.ok().as_ref().map(std::path::Path::new),
|
trace_dir.ok().as_ref().map(std::path::Path::new),
|
||||||
).await
|
))
|
||||||
.expect("Unable to find a suitable GPU adapter!");
|
.expect("Unable to find a suitable GPU adapter!");
|
||||||
|
|
||||||
SetupContextPartial4{
|
SetupContextPartial4{
|
||||||
@ -184,20 +193,20 @@ pub struct SetupContext<'a>{
|
|||||||
pub config:wgpu::SurfaceConfiguration,
|
pub config:wgpu::SurfaceConfiguration,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn setup_and_start(title:String){
|
pub fn setup_and_start(title:&str){
|
||||||
let event_loop=winit::event_loop::EventLoop::new().unwrap();
|
let event_loop=winit::event_loop::EventLoop::new().unwrap();
|
||||||
|
|
||||||
println!("Initializing the surface...");
|
println!("Initializing the surface...");
|
||||||
|
|
||||||
let partial_1=create_instance();
|
let partial_1=create_instance();
|
||||||
|
|
||||||
let window=create_window(title.as_str(),&event_loop).unwrap();
|
let window=create_window(title,&event_loop).unwrap();
|
||||||
|
|
||||||
let partial_2=partial_1.create_surface(&window).unwrap();
|
let partial_2=partial_1.create_surface(&window).unwrap();
|
||||||
|
|
||||||
let partial_3=partial_2.pick_adapter().await;
|
let partial_3=partial_2.pick_adapter();
|
||||||
|
|
||||||
let partial_4=partial_3.request_device().await;
|
let partial_4=partial_3.request_device();
|
||||||
|
|
||||||
let size=window.inner_size();
|
let size=window.inner_size();
|
||||||
|
|
||||||
@ -211,26 +220,26 @@ pub async fn setup_and_start(title:String){
|
|||||||
setup_context,
|
setup_context,
|
||||||
);
|
);
|
||||||
|
|
||||||
//if let Some(arg)=std::env::args().nth(1){
|
if let Some(arg)=std::env::args().nth(1){
|
||||||
//let path=std::path::PathBuf::from(arg);
|
let path=std::path::PathBuf::from(arg);
|
||||||
window_thread.send(TimedInstruction{
|
window_thread.send(TimedInstruction{
|
||||||
time:integer::Time::ZERO,
|
time:integer::Time::ZERO,
|
||||||
instruction:WindowInstruction::WindowEvent(winit::event::WindowEvent::DroppedFile("".into())),
|
instruction:WindowInstruction::WindowEvent(winit::event::WindowEvent::DroppedFile(path)),
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
//};
|
};
|
||||||
|
|
||||||
println!("Entering event loop...");
|
println!("Entering event loop...");
|
||||||
let root_time=chrono::Utc::now();
|
let root_time=std::time::Instant::now();
|
||||||
run_event_loop(event_loop,window_thread,root_time).unwrap();
|
run_event_loop(event_loop,window_thread,root_time).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_event_loop(
|
fn run_event_loop(
|
||||||
event_loop:winit::event_loop::EventLoop<()>,
|
event_loop:winit::event_loop::EventLoop<()>,
|
||||||
mut window_thread:crate::compat_worker::QNWorker<TimedInstruction<WindowInstruction>>,
|
mut window_thread:crate::compat_worker::QNWorker<TimedInstruction<WindowInstruction,SessionTimeInner>>,
|
||||||
root_time:chrono::DateTime<chrono::Utc>,
|
root_time:std::time::Instant
|
||||||
)->Result<(),winit::error::EventLoopError>{
|
)->Result<(),winit::error::EventLoopError>{
|
||||||
event_loop.run(move |event,elwt|{
|
event_loop.run(move |event,elwt|{
|
||||||
let time=integer::Time::from_nanos((chrono::Utc::now()-root_time).num_nanoseconds().unwrap());
|
let time=integer::Time::from_nanos(root_time.elapsed().as_nanos() as i64);
|
||||||
// *control_flow=if cfg!(feature="metal-auto-capture"){
|
// *control_flow=if cfg!(feature="metal-auto-capture"){
|
||||||
// winit::event_loop::ControlFlow::Exit
|
// winit::event_loop::ControlFlow::Exit
|
||||||
// }else{
|
// }else{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::physics_worker::InputInstruction;
|
use crate::physics_worker::InputInstruction;
|
||||||
use strafesnet_common::integer;
|
use strafesnet_common::integer;
|
||||||
use strafesnet_common::instruction::TimedInstruction;
|
use strafesnet_common::instruction::TimedInstruction;
|
||||||
|
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
||||||
|
|
||||||
pub enum WindowInstruction{
|
pub enum WindowInstruction{
|
||||||
Resize(winit::dpi::PhysicalSize<u32>),
|
Resize(winit::dpi::PhysicalSize<u32>),
|
||||||
@ -13,22 +14,20 @@ pub enum WindowInstruction{
|
|||||||
//holds thread handles to dispatch to
|
//holds thread handles to dispatch to
|
||||||
struct WindowContext<'a>{
|
struct WindowContext<'a>{
|
||||||
manual_mouse_lock:bool,
|
manual_mouse_lock:bool,
|
||||||
mouse:strafesnet_common::mouse::MouseState,//std::sync::Arc<std::sync::Mutex<>>
|
mouse:strafesnet_common::mouse::MouseState<SessionTimeInner>,//std::sync::Arc<std::sync::Mutex<>>
|
||||||
screen_size:glam::UVec2,
|
screen_size:glam::UVec2,
|
||||||
window:&'a winit::window::Window,
|
window:&'a winit::window::Window,
|
||||||
physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<crate::physics_worker::Instruction>>,
|
physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<crate::physics_worker::Instruction,SessionTimeInner>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowContext<'_>{
|
impl WindowContext<'_>{
|
||||||
fn get_middle_of_screen(&self)->winit::dpi::PhysicalPosition<u32>{
|
fn get_middle_of_screen(&self)->winit::dpi::PhysicalPosition<u32>{
|
||||||
winit::dpi::PhysicalPosition::new(self.screen_size.x/2,self.screen_size.y/2)
|
winit::dpi::PhysicalPosition::new(self.screen_size.x/2,self.screen_size.y/2)
|
||||||
}
|
}
|
||||||
fn window_event(&mut self,time:integer::Time,event:winit::event::WindowEvent){
|
fn window_event(&mut self,time:SessionTime,event:winit::event::WindowEvent){
|
||||||
match event{
|
match event{
|
||||||
winit::event::WindowEvent::DroppedFile(_path)=>{
|
winit::event::WindowEvent::DroppedFile(path)=>{
|
||||||
//match crate::file::load_file(path.as_path()){
|
match crate::file::load(path.as_path()){
|
||||||
let data=include_bytes!("/run/media/quat/Files/Documents/map-files/verify-scripts/maps/bhop_snfm/5692093612.snfm");
|
|
||||||
match crate::file::load(std::io::Cursor::new(data.as_slice())){
|
|
||||||
Ok(map)=>self.physics_thread.send(TimedInstruction{time,instruction:crate::physics_worker::Instruction::ChangeMap(map)}).unwrap(),
|
Ok(map)=>self.physics_thread.send(TimedInstruction{time,instruction:crate::physics_worker::Instruction::ChangeMap(map)}).unwrap(),
|
||||||
Err(e)=>println!("Failed to load map: {e}"),
|
Err(e)=>println!("Failed to load map: {e}"),
|
||||||
}
|
}
|
||||||
@ -124,7 +123,7 @@ impl WindowContext<'_>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device_event(&mut self,time:integer::Time,event: winit::event::DeviceEvent){
|
fn device_event(&mut self,time:SessionTime,event: winit::event::DeviceEvent){
|
||||||
match event{
|
match event{
|
||||||
winit::event::DeviceEvent::MouseMotion{
|
winit::event::DeviceEvent::MouseMotion{
|
||||||
delta,//these (f64,f64) are integers on my machine
|
delta,//these (f64,f64) are integers on my machine
|
||||||
@ -163,7 +162,7 @@ impl WindowContext<'_>{
|
|||||||
pub fn worker<'a>(
|
pub fn worker<'a>(
|
||||||
window:&'a winit::window::Window,
|
window:&'a winit::window::Window,
|
||||||
setup_context:crate::setup::SetupContext<'a>,
|
setup_context:crate::setup::SetupContext<'a>,
|
||||||
)->crate::compat_worker::QNWorker<'a,TimedInstruction<WindowInstruction>>{
|
)->crate::compat_worker::QNWorker<'a,TimedInstruction<WindowInstruction,SessionTimeInner>>{
|
||||||
// WindowContextSetup::new
|
// WindowContextSetup::new
|
||||||
let user_settings=crate::settings::read_user_settings();
|
let user_settings=crate::settings::read_user_settings();
|
||||||
|
|
||||||
@ -186,7 +185,7 @@ pub fn worker<'a>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
//WindowContextSetup::into_worker
|
//WindowContextSetup::into_worker
|
||||||
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<WindowInstruction>|{
|
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<WindowInstruction,SessionTimeInner>|{
|
||||||
match ins.instruction{
|
match ins.instruction{
|
||||||
WindowInstruction::RequestRedraw=>{
|
WindowInstruction::RequestRedraw=>{
|
||||||
window_context.window.request_redraw();
|
window_context.window.request_redraw();
|
||||||
|
@ -176,20 +176,20 @@ impl<'a,Task:Send+'a> INWorker<'a,Task>{
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test{
|
mod test{
|
||||||
use super::{thread,QRWorker};
|
use super::{thread,QRWorker};
|
||||||
use crate::physics;
|
type Body=crate::physics::Body;
|
||||||
use strafesnet_common::{integer,instruction};
|
use strafesnet_common::{integer,instruction};
|
||||||
#[test]//How to run this test with printing: cargo test --release -- --nocapture
|
#[test]//How to run this test with printing: cargo test --release -- --nocapture
|
||||||
fn test_worker() {
|
fn test_worker() {
|
||||||
// Create the worker thread
|
// Create the worker thread
|
||||||
let test_body=physics::Body::new(integer::vec3::ONE,integer::vec3::ONE,integer::vec3::ONE,integer::Time::ZERO);
|
let test_body=Body::new(integer::vec3::ONE,integer::vec3::ONE,integer::vec3::ONE,integer::Time::ZERO);
|
||||||
let worker=QRWorker::new(physics::Body::ZERO,
|
let worker=QRWorker::new(Body::ZERO,
|
||||||
|_|physics::Body::new(integer::vec3::ONE,integer::vec3::ONE,integer::vec3::ONE,integer::Time::ZERO)
|
|_|Body::new(integer::vec3::ONE,integer::vec3::ONE,integer::vec3::ONE,integer::Time::ZERO)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send tasks to the worker
|
// Send tasks to the worker
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
let task = instruction::TimedInstruction{
|
let task = instruction::TimedInstruction{
|
||||||
time:integer::Time::ZERO,
|
time:strafesnet_common::physics::Time::ZERO,
|
||||||
instruction:strafesnet_common::physics::Instruction::Idle,
|
instruction:strafesnet_common::physics::Instruction::Idle,
|
||||||
};
|
};
|
||||||
worker.send(task).unwrap();
|
worker.send(task).unwrap();
|
||||||
|
1
web-client/.gitignore
vendored
1
web-client/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/dist
|
|
@ -1,2 +0,0 @@
|
|||||||
[build]
|
|
||||||
target = "index.html"
|
|
@ -1,25 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1"/>
|
|
||||||
<title>Strafe Client</title>
|
|
||||||
|
|
||||||
<base data-trunk-public-url />
|
|
||||||
<style type="text/css">
|
|
||||||
body {
|
|
||||||
margin: 0px;
|
|
||||||
background: #fff;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.main-canvas {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<canvas class="main-canvas" id="canvas"></canvas>
|
|
||||||
<link data-trunk rel="rust" href="../strafe-client/Cargo.toml"/>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
Reference in New Issue
Block a user