Compare commits

..

4 Commits

Author SHA1 Message Date
89d51c60f4 implement looping 2025-01-03 20:16:23 -08:00
d04a5e4625 working playback 2025-01-03 16:34:36 -08:00
904338d7c6 add strafesnet_roblox_bot_file dep 2025-01-03 16:34:36 -08:00
40ad61fed7 bot player 2025-01-03 16:33:36 -08:00
28 changed files with 692 additions and 1129 deletions

12
Cargo.lock generated
View File

@ -2228,7 +2228,6 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
name = "strafe-client" name = "strafe-client"
version = "0.10.5" version = "0.10.5"
dependencies = [ dependencies = [
"arrayvec",
"bytemuck", "bytemuck",
"configparser", "configparser",
"ddsfile", "ddsfile",
@ -2240,6 +2239,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",
"wgpu", "wgpu",
"winit", "winit",
@ -2293,6 +2293,16 @@ 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"

View File

@ -18,8 +18,13 @@ 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(); }
const WASDQE=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown).bits(); impl Controls{
pub const fn wasd()->Self{
Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight)
}
pub const fn wasdqe()->Self{
Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown)
} }
} }

View File

@ -1,5 +1,5 @@
use crate::model; use crate::model;
use crate::integer::{AbsoluteTime,Planar64,Planar64Vec3}; use crate::integer::{Time,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(AbsoluteTime),//increase airtime, invariant across mass and gravity changes AirTime(Time),//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(AbsoluteTime),//air time (relative to gravity direction) is invariant across mass and gravity changes AirTime(Time),//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:AbsoluteTime,//short time = fast and direct, long time = launch high in the air, negative time = wrong way time:Time,//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,

View File

@ -110,7 +110,6 @@ 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)

View File

@ -1,8 +1,7 @@
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,AbsoluteTime,Ratio64,Planar64,Planar64Vec3}; use crate::integer::{int,vec3::int as int3,Time,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{
@ -49,7 +48,7 @@ pub enum JumpCalculation{
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub enum JumpImpulse{ pub enum JumpImpulse{
Time(AbsoluteTime),//jump time is invariant across mass and gravity changes Time(Time),//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),// :)
@ -200,8 +199,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(),
} }
} }
@ -209,8 +208,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(),
} }
} }
@ -273,8 +272,8 @@ impl StrafeSettings{
false=>None, false=>None,
} }
} }
pub fn next_tick(&self,time:PhysicsTime)->PhysicsTime{ pub fn next_tick(&self,time:Time)->Time{
PhysicsTime::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1)) Time::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1))
} }
pub const fn activates(&self,controls:Controls)->bool{ pub const fn activates(&self,controls:Controls)->bool{
self.enable.activates(controls) self.enable.activates(controls)
@ -436,7 +435,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,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), tick_rate:Ratio64::new(64,Time::ONE_SECOND.nanos() as u64).unwrap(),
}), }),
jump:Some(JumpSettings{ jump:Some(JumpSettings{
impulse:JumpImpulse::Energy(int(512)), impulse:JumpImpulse::Energy(int(512)),
@ -478,10 +477,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,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
}), }),
jump:Some(JumpSettings{ jump:Some(JumpSettings{
impulse:JumpImpulse::Time(AbsoluteTime::from_micros(715_588)), impulse:JumpImpulse::Time(Time::from_micros(715_588)),
calculation:JumpCalculation::Max, calculation:JumpCalculation::Max,
limit_minimum:true, limit_minimum:true,
}), }),
@ -535,7 +534,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,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() 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()),
@ -576,7 +575,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,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() 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()),

View File

@ -1,41 +1,35 @@
use crate::integer::Time; use crate::integer::Time;
#[derive(Debug)] #[derive(Debug)]
pub struct TimedInstruction<I,T>{ pub struct TimedInstruction<I>{
pub time:Time<T>, pub time:Time,
pub instruction:I, pub instruction:I,
} }
pub trait InstructionEmitter{ pub trait InstructionEmitter<I>{
type Instruction; fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<I>>;
type TimeInner;
fn next_instruction(&self,time_limit:Time<Self::TimeInner>)->Option<TimedInstruction<Self::Instruction,Self::TimeInner>>;
} }
pub trait InstructionConsumer{ pub trait InstructionConsumer<I>{
type Instruction; fn process_instruction(&mut self, instruction:TimedInstruction<I>);
type TimeInner;
fn process_instruction(&mut self, instruction:TimedInstruction<Self::Instruction,Self::TimeInner>);
} }
//PROPER PRIVATE FIELDS!!! //PROPER PRIVATE FIELDS!!!
pub struct InstructionCollector<I,T>{ pub struct InstructionCollector<I>{
time:Time<T>, time:Time,
instruction:Option<I>, instruction:Option<I>,
} }
impl<I,T> InstructionCollector<I,T> impl<I> InstructionCollector<I>{
where Time<T>:Copy+PartialOrd, pub const fn new(time:Time)->Self{
{
pub const fn new(time:Time<T>)->Self{
Self{ Self{
time, time,
instruction:None instruction:None
} }
} }
#[inline] #[inline]
pub const fn time(&self)->Time<T>{ pub const fn time(&self)->Time{
self.time self.time
} }
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){ pub fn collect(&mut self,instruction:Option<TimedInstruction<I>>){
match instruction{ match instruction{
Some(unwrap_instruction)=>{ Some(unwrap_instruction)=>{
if unwrap_instruction.time<self.time { if unwrap_instruction.time<self.time {
@ -46,7 +40,7 @@ impl<I,T> InstructionCollector<I,T>
None=>(), None=>(),
} }
} }
pub fn instruction(self)->Option<TimedInstruction<I,T>>{ pub fn instruction(self)->Option<TimedInstruction<I>>{
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR //STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
match self.instruction{ match self.instruction{
Some(instruction)=>Some(TimedInstruction{ Some(instruction)=>Some(TimedInstruction{

View File

@ -2,25 +2,19 @@ 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 enum TimeInner{} pub struct Time(i64);
pub type AbsoluteTime=Time<TimeInner>; impl Time{
pub const MIN:Self=Self(i64::MIN);
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] pub const MAX:Self=Self(i64::MAX);
pub struct Time<T>(i64,core::marker::PhantomData<T>); pub const ZERO:Self=Self(0);
impl<T> Time<T>{ pub const ONE_SECOND:Self=Self(1_000_000_000);
pub const MIN:Self=Self::raw(i64::MIN); pub const ONE_MILLISECOND:Self=Self(1_000_000);
pub const MAX:Self=Self::raw(i64::MAX); pub const ONE_MICROSECOND:Self=Self(1_000);
pub const ZERO:Self=Self::raw(0); pub const ONE_NANOSECOND:Self=Self(1);
pub const ONE_SECOND:Self=Self::raw(1_000_000_000);
pub const ONE_MILLISECOND:Self=Self::raw(1_000_000);
pub const ONE_MICROSECOND:Self=Self::raw(1_000);
pub const ONE_NANOSECOND:Self=Self::raw(1);
#[inline] #[inline]
pub const fn raw(num:i64)->Self{ pub const fn raw(num:i64)->Self{
Self(num,core::marker::PhantomData) Self(num)
} }
#[inline] #[inline]
pub const fn get(self)->i64{ pub const fn get(self)->i64{
@ -28,19 +22,19 @@ impl<T> Time<T>{
} }
#[inline] #[inline]
pub const fn from_secs(num:i64)->Self{ pub const fn from_secs(num:i64)->Self{
Self::raw(Self::ONE_SECOND.0*num) Self(Self::ONE_SECOND.0*num)
} }
#[inline] #[inline]
pub const fn from_millis(num:i64)->Self{ pub const fn from_millis(num:i64)->Self{
Self::raw(Self::ONE_MILLISECOND.0*num) Self(Self::ONE_MILLISECOND.0*num)
} }
#[inline] #[inline]
pub const fn from_micros(num:i64)->Self{ pub const fn from_micros(num:i64)->Self{
Self::raw(Self::ONE_MICROSECOND.0*num) Self(Self::ONE_MICROSECOND.0*num)
} }
#[inline] #[inline]
pub const fn from_nanos(num:i64)->Self{ pub const fn from_nanos(num:i64)->Self{
Self::raw(Self::ONE_NANOSECOND.0*num) Self(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]
@ -51,18 +45,14 @@ impl<T> Time<T>{
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{
Self::raw((value*Planar64::raw(1_000_000_000)).fix_1().to_raw()) Time((value*Planar64::raw(1_000_000_000)).fix_1().to_raw())
} }
} }
impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T> impl<Num,Den,N1,T1> From<Ratio<Num,Den>> for Time
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>,
@ -70,34 +60,34 @@ impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T>
{ {
#[inline] #[inline]
fn from(value:Ratio<Num,Den>)->Self{ fn from(value:Ratio<Num,Den>)->Self{
Self::raw((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw()) Time((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw())
} }
} }
impl<T> std::fmt::Display for Time<T>{ impl std::fmt::Display for Time{
#[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<T> std::default::Default for Time<T>{ impl std::default::Default for Time{
fn default()->Self{ fn default()->Self{
Self::raw(0) Self(0)
} }
} }
impl<T> std::ops::Neg for Time<T>{ impl std::ops::Neg for Time{
type Output=Self; type Output=Time;
#[inline] #[inline]
fn neg(self)->Self::Output { fn neg(self)->Self::Output {
Self::raw(-self.0) Time(-self.0)
} }
} }
macro_rules! impl_time_additive_operator { macro_rules! impl_time_additive_operator {
($trait:ty, $method:ident) => { ($trait:ty, $method:ident) => {
impl<T> $trait for Time<T>{ impl $trait for Time{
type Output=Self; type Output=Time;
#[inline] #[inline]
fn $method(self,rhs:Self)->Self::Output { fn $method(self,rhs:Self)->Self::Output {
Self::raw(self.0.$method(rhs.0)) Time(self.0.$method(rhs.0))
} }
} }
}; };
@ -107,7 +97,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<T> $trait for Time<T>{ impl $trait for Time{
#[inline] #[inline]
fn $method(&mut self,rhs:Self){ fn $method(&mut self,rhs:Self){
self.0.$method(rhs.0) self.0.$method(rhs.0)
@ -118,37 +108,33 @@ 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<T> std::ops::Mul for Time<T>{ impl std::ops::Mul for Time{
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<T> std::ops::Div<i64> for Time<T>{ impl std::ops::Div<i64> for Time{
type Output=Self; type Output=Time;
#[inline] #[inline]
fn div(self,rhs:i64)->Self::Output{ fn div(self,rhs:i64)->Self::Output{
Self::raw(self.0/rhs) Time(self.0/rhs)
} }
} }
impl<T> std::ops::Mul<i64> for Time<T>{ impl std::ops::Mul<i64> for Time{
type Output=Self; type Output=Time;
#[inline] #[inline]
fn mul(self,rhs:i64)->Self::Output{ fn mul(self,rhs:i64)->Self::Output{
Self::raw(self.0*rhs) Time(self.0*rhs)
} }
} }
impl<T> core::ops::Mul<Time<T>> for Planar64{ impl core::ops::Mul<Time> for Planar64{
type Output=Ratio<Fixed<2,64>,Planar64>; type Output=Ratio<Fixed<2,64>,Planar64>;
fn mul(self,rhs:Time<T>)->Self::Output{ fn mul(self,rhs:Time)->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();
@ -170,7 +156,6 @@ mod test_time{
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{

View File

@ -7,7 +7,6 @@ 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;

View File

@ -1,11 +1,11 @@
use crate::integer::Time; use crate::integer::Time;
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub struct MouseState<T>{ pub struct MouseState{
pub pos:glam::IVec2, pub pos:glam::IVec2,
pub time:Time<T>, pub time:Time,
} }
impl<T> Default for MouseState<T>{ impl Default for MouseState{
fn default()->Self{ fn default()->Self{
Self{ Self{
time:Time::ZERO, time:Time::ZERO,
@ -13,10 +13,8 @@ impl<T> Default for MouseState<T>{
} }
} }
} }
impl<T> MouseState<T> impl MouseState{
where Time<T>:Copy, pub fn lerp(&self,target:&MouseState,time:Time)->glam::IVec2{
{
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

View File

@ -1,11 +1,7 @@
#[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<TimeInner>,crate::mouse::MouseState<TimeInner>), ReplaceMouse(crate::mouse::MouseState,crate::mouse::MouseState),
SetNextMouse(crate::mouse::MouseState<TimeInner>), SetNextMouse(crate::mouse::MouseState),
SetMoveRight(bool), SetMoveRight(bool),
SetMoveUp(bool), SetMoveUp(bool),
SetMoveBack(bool), SetMoveBack(bool),

View File

@ -1,10 +1,5 @@
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{
@ -20,11 +15,6 @@ 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.",
@ -35,7 +25,7 @@ impl AsRef<str> 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()
} }
} }
@ -55,8 +45,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<PhysicsTimeInner,TimeInner>,Unpaused>}, Started{timer:TimerFixed<Realtime,Unpaused>},
Finished{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Paused>}, Finished{timer:TimerFixed<Realtime,Paused>},
} }
#[derive(Clone,Copy,Debug)] #[derive(Clone,Copy,Debug)]
@ -72,14 +62,14 @@ impl Run{
flagged:None, flagged:None,
} }
} }
pub fn time(&self,time:PhysicsTime)->Time{ pub fn time(&self,time:Time)->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:PhysicsTime)->Result<(),Error>{ pub fn start(&mut self,time:Time)->Result<(),Error>{
match &self.state{ match &self.state{
RunState::Created=>{ RunState::Created=>{
self.state=RunState::Started{ self.state=RunState::Started{
@ -91,7 +81,7 @@ impl Run{
RunState::Finished{..}=>Err(Error::AlreadyFinished), RunState::Finished{..}=>Err(Error::AlreadyFinished),
} }
} }
pub fn finish(&mut self,time:PhysicsTime)->Result<(),Error>{ pub fn finish(&mut self,time:Time)->Result<(),Error>{
//this uses Copy //this uses Copy
match &self.state{ match &self.state{
RunState::Created=>Err(Error::NotStarted), RunState::Created=>Err(Error::NotStarted),

View File

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

View File

@ -22,106 +22,79 @@ 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<In,Out>{ pub struct Realtime{
offset:InnerTime, offset:Time,
_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<In,Out>{ pub struct Scaled{
scale:Ratio64, scale:Ratio64,
offset:InnerTime, offset:Time,
_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::new(scale,InnerTime::ZERO) Self{scale,offset:Time::ZERO}
} }
const fn scale(&self,time:Time<In>)->InnerTime{ const fn scale(&self,time:Time)->Time{
InnerTime::raw(self.scale.mul_int(time.get())) Time::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<In>,new_scale:Ratio64){ fn set_scale(&mut self,time:Time,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{ pub trait TimerState:Copy+std::fmt::Debug{
type In;
type Out;
fn identity()->Self; fn identity()->Self;
fn get_time(&self,time:Time<Self::In>)->Time<Self::Out>; fn get_time(&self,time:Time)->Time;
fn set_time(&mut self,time:Time<Self::In>,new_time:Time<Self::Out>); fn set_time(&mut self,time:Time,new_time:Time);
fn get_offset(&self)->InnerTime; fn get_offset(&self)->Time;
fn set_offset(&mut self,offset:InnerTime); fn set_offset(&mut self,offset:Time);
} }
impl<In,Out> TimerState for Realtime<In,Out>{ impl TimerState for Realtime{
type In=In;
type Out=Out;
fn identity()->Self{ fn identity()->Self{
Self::new(InnerTime::ZERO) Self{offset:Time::ZERO}
} }
fn get_time(&self,time:Time<In>)->Time<Out>{ fn get_time(&self,time:Time)->Time{
time.coerce()+self.offset.coerce() time+self.offset
} }
fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){ fn set_time(&mut self,time:Time,new_time:Time){
self.offset=new_time.coerce()-time.coerce(); self.offset=new_time-time;
} }
fn get_offset(&self)->InnerTime{ fn get_offset(&self)->Time{
self.offset self.offset
} }
fn set_offset(&mut self,offset:InnerTime){ fn set_offset(&mut self,offset:Time){
self.offset=offset; self.offset=offset;
} }
} }
impl<In,Out> TimerState for Scaled<In,Out> impl TimerState for Scaled{
where Time<In>:Copy,
{
type In=In;
type Out=Out;
fn identity()->Self{ fn identity()->Self{
Self::new(Ratio64::ONE,InnerTime::ZERO) Self{scale:Ratio64::ONE,offset:Time::ZERO}
} }
fn get_time(&self,time:Time<In>)->Time<Out>{ fn get_time(&self,time:Time)->Time{
(self.scale(time)+self.offset).coerce() self.scale(time)+self.offset
} }
fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){ fn set_time(&mut self,time:Time,new_time:Time){
self.offset=new_time.coerce()-self.scale(time); self.offset=new_time-self.scale(time);
} }
fn get_offset(&self)->InnerTime{ fn get_offset(&self)->Time{
self.offset self.offset
} }
fn set_offset(&mut self,offset:InnerTime){ fn set_offset(&mut self,offset:Time){
self.offset=offset; self.offset=offset;
} }
} }
@ -133,10 +106,8 @@ pub struct TimerFixed<T:TimerState,P:PauseState>{
} }
//scaled timer methods are generic across PauseState //scaled timer methods are generic across PauseState
impl<P:PauseState,In,Out> TimerFixed<Scaled<In,Out>,P> impl<P:PauseState> TimerFixed<Scaled,P>{
where Time<In>:Copy, pub fn scaled(time:Time,new_time:Time,scale:Ratio64)->Self{
{
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(),
@ -147,16 +118,14 @@ impl<P:PauseState,In,Out> TimerFixed<Scaled<In,Out>,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<In>,new_scale:Ratio64){ pub fn set_scale(&mut self,time:Time,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>{
where Time<T::In>:Copy, pub fn into_unpaused(self,time:Time)->TimerFixed<T,Unpaused>{
{
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,
@ -166,10 +135,8 @@ impl<T:TimerState> TimerFixed<T,Paused>
timer timer
} }
} }
impl<T:TimerState> TimerFixed<T,Unpaused> impl<T:TimerState> TimerFixed<T,Unpaused>{
where Time<T::In>:Copy, pub fn into_paused(self,time:Time)->TimerFixed<T,Paused>{
{
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,
@ -182,7 +149,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<T::In>,new_time:Time<T::Out>)->Self{ pub fn new(time:Time,new_time:Time)->Self{
let mut timer=Self{ let mut timer=Self{
state:T::identity(), state:T::identity(),
_paused:P::new(), _paused:P::new(),
@ -199,15 +166,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<T::In>)->Time<T::Out>{ pub fn time(&self,time:Time)->Time{
match P::IS_PAUSED{ match P::IS_PAUSED{
true=>self.state.get_offset().coerce(), true=>self.state.get_offset(),
false=>self.state.get_time(time), false=>self.state.get_time(time),
} }
} }
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){ pub fn set_time(&mut self,time:Time,new_time:Time){
match P::IS_PAUSED{ match P::IS_PAUSED{
true=>self.state.set_offset(new_time.coerce()), true=>self.state.set_offset(new_time),
false=>self.state.set_time(time,new_time), false=>self.state.set_time(time,new_time),
} }
} }
@ -231,11 +198,7 @@ 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)),
@ -248,32 +211,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<T::In>,new_time:Time<T::Out>)->Self{ pub fn paused(time:Time,new_time:Time)->Self{
Self::Paused(TimerFixed::new(time,new_time)) Self::Paused(TimerFixed::new(time,new_time))
} }
pub fn unpaused(time:Time<T::In>,new_time:Time<T::Out>)->Self{ pub fn unpaused(time:Time,new_time:Time)->Self{
Self::Unpaused(TimerFixed::new(time,new_time)) Self::Unpaused(TimerFixed::new(time,new_time))
} }
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{ pub fn time(&self,time:Time)->Time{
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<T::In>,new_time:Time<T::Out>){ pub fn set_time(&mut self,time:Time,new_time:Time){
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<T::In>)->Result<(),Error>{ pub fn pause(&mut self,time:Time)->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<T::In>)->Result<(),Error>{ pub fn unpause(&mut self,time:Time)->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),
@ -286,7 +249,7 @@ impl<T:TimerState> Timer<T>
Self::Unpaused(_)=>false, Self::Unpaused(_)=>false,
} }
} }
pub fn set_paused(&mut self,time:Time<T::In>,paused:bool)->Result<(),Error>{ pub fn set_paused(&mut self,time:Time,paused:bool)->Result<(),Error>{
match paused{ match paused{
true=>self.pause(time), true=>self.pause(time),
false=>self.unpause(time), false=>self.unpause(time),
@ -294,16 +257,14 @@ impl<T:TimerState> Timer<T>
} }
} }
//scaled timer methods are generic across PauseState //scaled timer methods are generic across PauseState
impl<In,Out> Timer<Scaled<In,Out>> impl Timer<Scaled>{
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<In>,new_scale:Ratio64){ pub fn set_scale(&mut self,time:Time,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),
@ -319,15 +280,10 @@ 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<Parent,Calculated>,Paused>::from_state(Scaled::new(0.5f32.try_into().unwrap(),sec!(0))); let timer=TimerFixed::<Scaled,Paused>::from_state(Scaled{scale:0.5f32.try_into().unwrap(),offset:sec!(0)});
//the paused timer at 1 second should read 0s //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));
@ -344,7 +300,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<Parent,Calculated>>::paused(sec!(0),sec!(0)); let mut timer=Timer::<Realtime>::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));

View File

@ -7,29 +7,21 @@ 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:Vec<crate::map::StreamableMap<R>>, map:Box<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>{

View File

@ -1,2 +0,0 @@
// a session is a recording of the client's inputs
// which should deterministically recreate a bot or whatever the client did

View File

@ -15,7 +15,6 @@ 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"] }
configparser = "3.0.2" configparser = "3.0.2"
ddsfile = "0.5.1" ddsfile = "0.5.1"
@ -27,6 +26,7 @@ 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 }
wgpu = "23.0.1" wgpu = "23.0.1"
winit = "0.30.7" winit = "0.30.7"

View File

@ -1,160 +0,0 @@
use strafesnet_common::aabb;
use strafesnet_common::integer::{self,vec3,Time,Planar64,Planar64Vec3};
#[derive(Clone,Copy,Debug,Hash)]
pub struct Body<T>{
pub position:Planar64Vec3,//I64 where 2^32 = 1 u
pub velocity:Planar64Vec3,//I64 where 2^32 = 1 u/s
pub acceleration:Planar64Vec3,//I64 where 2^32 = 1 u/s/s
pub time:Time<T>,//nanoseconds x xxxxD!
}
impl<T> std::ops::Neg for Body<T>{
type Output=Self;
fn neg(self)->Self::Output{
Self{
position:self.position,
velocity:-self.velocity,
acceleration:self.acceleration,
time:-self.time,
}
}
}
impl<T> Body<T>
where Time<T>:Copy,
{
pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO);
pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time<T>)->Self{
Self{
position,
velocity,
acceleration,
time,
}
}
pub 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)
}
}

View File

@ -1,34 +1,23 @@
use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge}; use crate::physics::Body;
use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3}; use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge,MinkowskiMesh,MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert};
use crate::physics::{Time,Body}; use strafesnet_common::integer::{Time,Fixed,Ratio};
enum Transition<M:MeshQuery>{ #[derive(Debug)]
enum Transition<F,E:DirectedEdge,V>{
Miss, Miss,
Next(FEV<M>,GigaTime), Next(FEV<F,E,V>,GigaTime),
Hit(M::Face,GigaTime), Hit(F,GigaTime),
} }
pub enum CrawlResult<M:MeshQuery>{ type MinkowskiFEV=FEV<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>;
Miss(FEV<M>), type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>;
Hit(M::Face,GigaTime),
}
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M> fn next_transition(fev:&MinkowskiFEV,body_time:GigaTime,mesh:&MinkowskiMesh,body:&Body,mut best_time:GigaTime)->MinkowskiTransition{
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=Transition::Miss; let mut best_transition=MinkowskiTransition::Miss;
match self{ match fev{
&FEV::Face(face_id)=>{ &MinkowskiFEV::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
@ -38,7 +27,7 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
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=Transition::Hit(face_id,dt); best_transition=MinkowskiTransition::Hit(face_id,dt);
break; break;
} }
} }
@ -52,14 +41,14 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
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=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt); best_transition=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt);
break; break;
} }
} }
} }
//if none: //if none:
}, },
&FEV::Edge(edge_id)=>{ &MinkowskiFEV::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);
@ -72,7 +61,7 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
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=Transition::Next(FEV::Face(edge_face_id),dt); best_transition=MinkowskiTransition::Next(MinkowskiFEV::Face(edge_face_id),dt);
break; break;
} }
} }
@ -85,14 +74,14 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
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=Transition::Next(FEV::Vert(vert_id),dt); best_transition=MinkowskiTransition::Next(MinkowskiFEV::Vert(vert_id),dt);
break; break;
} }
} }
} }
//if none: //if none:
}, },
&FEV::Vert(vert_id)=>{ &MinkowskiFEV::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
@ -101,7 +90,7 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
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=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt); best_transition=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt);
break; break;
} }
} }
@ -111,7 +100,12 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
} }
best_transition best_transition
} }
pub fn crawl(mut self,mesh:&M,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult<M>{ pub enum CrawlResult<F,E:DirectedEdge,V>{
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())
@ -121,14 +115,13 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
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 self.next_transition(body_time,mesh,relative_body,time_limit){ match next_transition(&fev,body_time,mesh,relative_body,time_limit){
Transition::Miss=>return CrawlResult::Miss(self), Transition::Miss=>return CrawlResult::Miss(fev),
Transition::Next(next_fev,next_time)=>(self,body_time)=(next_fev,next_time), Transition::Next(next_fev,next_time)=>(fev,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(self) CrawlResult::Miss(fev)
}
} }

View File

@ -98,6 +98,12 @@ 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,
@ -876,7 +882,7 @@ impl GraphicsState{
view:&wgpu::TextureView, view:&wgpu::TextureView,
device:&wgpu::Device, device:&wgpu::Device,
queue:&wgpu::Queue, queue:&wgpu::Queue,
frame_state:crate::physics_worker::FrameState, frame_state: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

View File

@ -1,5 +1,5 @@
pub enum Instruction{ pub enum Instruction{
Render(crate::physics_worker::FrameState), Render(crate::graphics::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),

View File

@ -1,4 +1,3 @@
mod body;
mod file; mod file;
mod setup; mod setup;
mod window; mod window;
@ -6,7 +5,6 @@ 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;
@ -14,8 +12,6 @@ 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(){
setup::setup_and_start(TITLE); setup::setup_and_start(format!("Strafe Client v{}",env!("CARGO_PKG_VERSION")));
} }

View File

@ -3,9 +3,6 @@ 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;
@ -54,10 +51,10 @@ impl DirectedEdge for SubmeshDirectedEdgeId{
//Vertex <-> Edge <-> Face -> Collide //Vertex <-> Edge <-> Face -> Collide
#[derive(Debug)] #[derive(Debug)]
pub enum FEV<M:MeshQuery>{ pub enum FEV<F,E:DirectedEdge,V>{
Face(M::Face), Face(F),
Edge(<M::Edge as DirectedEdge>::UndirectedEdge), Edge(E::UndirectedEdge),
Vert(M::Vert), Vert(V),
} }
//use Unit32 #[repr(C)] for map files //use Unit32 #[repr(C)] for map files
@ -67,28 +64,25 @@ struct Face{
dot:Planar64, dot:Planar64,
} }
struct Vert(Planar64Vec3); struct Vert(Planar64Vec3);
pub trait MeshQuery{ pub trait MeshQuery<FACE:Clone,EDGE:Clone+DirectedEdge,VERT:Clone>{
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:<Self::Edge as DirectedEdge>::UndirectedEdge)->Planar64Vec3{ fn edge_n(&self,edge_id:EDGE::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:Self::Edge)->Planar64Vec3{ fn directed_edge_n(&self,directed_edge_id: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:Self::Vert)->Planar64Vec3; fn vert(&self,vert_id:VERT)->Planar64Vec3;
fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset); fn face_nd(&self,face_id:FACE)->(Self::Normal,Self::Offset);
fn face_edges(&self,face_id:Self::Face)->Cow<Vec<Self::Edge>>; fn face_edges(&self,face_id:FACE)->Cow<Vec<EDGE>>;
fn edge_faces(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Face;2]>; fn edge_faces(&self,edge_id:EDGE::UndirectedEdge)->Cow<[FACE;2]>;
fn edge_verts(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Vert;2]>; fn edge_verts(&self,edge_id:EDGE::UndirectedEdge)->Cow<[VERT;2]>;
fn vert_edges(&self,vert_id:Self::Vert)->Cow<Vec<Self::Edge>>; fn vert_edges(&self,vert_id:VERT)->Cow<Vec<EDGE>>;
fn vert_faces(&self,vert_id:Self::Vert)->Cow<Vec<Self::Face>>; fn vert_faces(&self,vert_id:VERT)->Cow<Vec<FACE>>;
} }
struct FaceRefs{ struct FaceRefs{
edges:Vec<SubmeshDirectedEdgeId>, edges:Vec<SubmeshDirectedEdgeId>,
@ -427,10 +421,7 @@ pub struct PhysicsMeshView<'a>{
data:&'a PhysicsMeshData, data:&'a PhysicsMeshData,
topology:&'a PhysicsMeshTopology, topology:&'a PhysicsMeshTopology,
} }
impl MeshQuery for PhysicsMeshView<'_>{ impl MeshQuery<SubmeshFaceId,SubmeshDirectedEdgeId,SubmeshVertId> 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){
@ -504,10 +495,7 @@ impl TransformedMesh<'_>{
) )
} }
} }
impl MeshQuery for TransformedMesh<'_>{ impl MeshQuery<SubmeshFaceId,SubmeshDirectedEdgeId,SubmeshVertId> 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){
@ -681,13 +669,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::<MinkowskiMesh>{ fn infinity_fev(&self,infinity_dir:Planar64Vec3,point:Planar64Vec3)->FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>{
//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::Vert(vert_id), EV::Vert(vert_id)=>FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>::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);
@ -705,14 +693,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::Face(face_id); return FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>::Face(face_id);
} }
} }
FEV::Edge(edge_id) FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>::Edge(edge_id)
}, },
} }
} }
fn closest_fev_not_inside(&self,mut infinity_body:Body)->Option<FEV<MinkowskiMesh>>{ fn closest_fev_not_inside(&self,mut infinity_body:crate::physics::Body)->Option<FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>>{
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
@ -720,24 +708,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 infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time){ match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::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:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{ pub fn predict_collision_in(&self,relative_body:&crate::physics::Body,time_limit:integer::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 fev.crawl(self,relative_body,relative_body.time,time_limit){ match crate::face_crawler::crawl_fev(fev,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:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{ pub fn predict_collision_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,GigaTime)>{
//create an extrapolated body at time_limit //create an extrapolated body at time_limit
let infinity_body=Body::new( let infinity_body=crate::physics::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,
@ -745,13 +733,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 fev.crawl(self,&-relative_body.clone(),-time_limit,-relative_body.time){ match crate::face_crawler::crawl_fev(fev,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:&Body,time_limit:Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{ pub fn predict_collision_face_out(&self,relative_body:&crate::physics::Body,time_limit:integer::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={
@ -778,15 +766,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:Body)->Option<(MinkowskiFace,GigaTime)>{ fn infinity_in(&self,infinity_body:crate::physics::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 infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time){ match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::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=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO); let infinity_body=crate::physics::Body::new(point,vec3::Y,vec3::ZERO,integer::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)
@ -796,13 +784,9 @@ impl MinkowskiMesh<'_>{
) )
} }
} }
impl MeshQuery for MinkowskiMesh<'_>{ impl MeshQuery<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert> 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)=>{

View File

@ -5,18 +5,15 @@ 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,TimedInstruction};
use strafesnet_common::integer::{self,vec3,mat3,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2}; use strafesnet_common::integer::{self,vec3,mat3,Time,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;
@ -24,7 +21,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)]
pub enum PhysicsInternalInstruction{ enum PhysicsInternalInstruction{
CollisionStart(Collision,model_physics::GigaTime), CollisionStart(Collision,model_physics::GigaTime),
CollisionEnd(Collision,model_physics::GigaTime), CollisionEnd(Collision,model_physics::GigaTime),
StrafeTick, StrafeTick,
@ -32,13 +29,32 @@ pub enum PhysicsInternalInstruction{
// Water, // Water,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum PhysicsInstruction{ 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,
@ -219,11 +235,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
match self.contact_models.get(&ContactModelId::new(model_id.get())){ self.contact_models.get(&ContactModelId::new(model_id.get())).map_or_else(
Some(model)=>Some(&model.transform), ||self.intersect_models.get(&IntersectModelId::new(model_id.get()))
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]
@ -257,6 +273,15 @@ 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(
@ -310,15 +335,6 @@ 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,
@ -337,14 +353,8 @@ 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
} }
fn increment_jump_count(&mut self,model_id:ModelId)->u32{ pub fn get_jump_count(&self,model_id:ModelId)->Option<u32>{
*self.jump_counts.entry(model_id).and_modify(|c|*c+=1).or_insert(1) self.jump_counts.get(&model_id).copied()
}
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()
@ -558,7 +568,7 @@ impl MoveState{
=>None, =>None,
} }
} }
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<PhysicsInternalInstruction,TimeInner>>{ fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
//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{
@ -725,8 +735,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){
@ -765,35 +775,27 @@ 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){
let contacts=self.contacts.iter().map(|contact|{ //TODO: trey push solve
for contact in &self.contacts{
let n=contact_normal(models,hitbox_mesh,contact); let n=contact_normal(models,hitbox_mesh,contact);
crate::push_solve::Contact{ let d=n.dot(*velocity);
position:vec3::ZERO, if d.is_negative(){
velocity:n, *velocity-=(n*d/n.length_squared()).divide().fix_1();
normal:n,
} }
}).collect();
match crate::push_solve::push_solve(&contacts,*velocity){
Some(new_velocity)=>*velocity=new_velocity,
None=>println!("Algorithm silently failing :)"),
} }
} }
fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:&mut Planar64Vec3){ fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:&mut Planar64Vec3){
let contacts=self.contacts.iter().map(|contact|{ //TODO: trey push solve
for contact in &self.contacts{
let n=contact_normal(models,hitbox_mesh,contact); let n=contact_normal(models,hitbox_mesh,contact);
crate::push_solve::Contact{ let d=n.dot(*acceleration);
position:vec3::ZERO, if d.is_negative(){
velocity:n, *acceleration-=(n*d/n.length_squared()).divide().fix_1();
normal:n,
}
}).collect();
match crate::push_solve::push_solve(&contacts,*acceleration){
Some(new_acceleration)=>*acceleration=new_acceleration,
None=>println!("Algorithm silently failing :)"),
} }
} }
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<PhysicsInternalInstruction,TimeInner>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){ }
let relative_body=crate::body::VirtualBody::relative(&Body::ZERO,body).body(time); fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<PhysicsInternalInstruction>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time: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);
@ -825,6 +827,142 @@ 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,
@ -892,7 +1030,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,TimeInner>>{ fn next_move_instruction(&self)->Option<TimedInstruction<PhysicsInternalInstruction>>{
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){
@ -942,18 +1080,14 @@ pub struct PhysicsContext{
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 the generic PhysicsInstruction, but can only emit the more narrow PhysicsInternalInstruction
impl instruction::InstructionConsumer for PhysicsContext{ impl instruction::InstructionConsumer<PhysicsInstruction> for PhysicsContext{
type Instruction=PhysicsInstruction; fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInstruction>){
type TimeInner=TimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<PhysicsInstruction,TimeInner>){
atomic_state_update(&mut self.state,&self.data,ins) atomic_state_update(&mut self.state,&self.data,ins)
} }
} }
impl instruction::InstructionEmitter for PhysicsContext{ impl instruction::InstructionEmitter<PhysicsInternalInstruction> for PhysicsContext{
type Instruction=PhysicsInternalInstruction; //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>>{
//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)
} }
} }
@ -1122,7 +1256,7 @@ impl PhysicsContext{
//write hash lol //write hash lol
} }
} }
pub fn run_input_instruction(&mut self,instruction:TimedInstruction<PhysicsInputInstruction,TimeInner>){ pub fn run_input_instruction(&mut self,instruction:TimedInstruction<PhysicsInputInstruction>){
self.run_internal_exhaustive(instruction.time); self.run_internal_exhaustive(instruction.time);
self.process_instruction(TimedInstruction{ self.process_instruction(TimedInstruction{
time:instruction.time, time:instruction.time,
@ -1132,7 +1266,7 @@ impl PhysicsContext{
} }
//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,TimeInner>>{ fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<PhysicsInternalInstruction>>{
//JUST POLLING!!! NO MUTATION //JUST POLLING!!! NO MUTATION
let mut collector = instruction::InstructionCollector::new(time_limit); let mut collector = instruction::InstructionCollector::new(time_limit);
@ -1316,7 +1450,7 @@ enum TeleportToSpawnError{
NoModel, NoModel,
} }
fn teleport_to_spawn( fn teleport_to_spawn(
spawn_model_id:ModelId, stage:&gameplay_modes::Stage,
move_state:&mut MoveState, move_state:&mut MoveState,
body:&mut Body, body:&mut Body,
touching:&mut TouchingState, touching:&mut TouchingState,
@ -1331,78 +1465,14 @@ 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(spawn_model_id).ok_or(TeleportToSpawnError::NoModel)?; let transform=models.get_model_transform(stage.spawn()).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>,
@ -1420,23 +1490,55 @@ 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()){
let CheckpointCheckOutcome{set_stage,teleport_to_model}=checkpoint_check(mode_state,stage_element,mode); if mode_state.get_stage_id()<stage_element.stage_id(){
if let Some(stage_id)=set_stage{ //checkpoint check
mode_state.set_stage_id(stage_id); //check if current stage is complete
} if let Some(current_stage)=mode.get_stage(mode_state.get_stage_id()){
if let Some(model_id)=teleport_to_model{ if !current_stage.is_complete(mode_state.ordered_checkpoint_count(),mode_state.unordered_checkpoint_count()){
let _=teleport_to_spawn(model_id,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time); //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; 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);
let _=teleport_to_spawn(stage,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
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.spawn(),move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time); let _=teleport_to_spawn(mode_state_stage,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=>(),
@ -1461,18 +1563,6 @@ 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,
@ -1495,9 +1585,6 @@ 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!"),
@ -1518,10 +1605,9 @@ 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)=>allow_jump=false, Some(gameplay_attributes::ContactingBehaviour::NoJump)=>todo!("nyi"),
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);
@ -1530,32 +1616,14 @@ 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
if allow_run_teleport_behaviour{ 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);
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 style.get_control(Controls::Jump,input_state.controls){
}
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{
@ -1665,7 +1733,7 @@ fn collision_end_intersect(
} }
} }
} }
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInternalInstruction,TimeInner>){ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInternalInstruction>){
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)
@ -1761,7 +1829,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
} }
} }
fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInputInstruction,TimeInner>){ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInputInstruction>){
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
@ -1855,7 +1923,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.spawn(), stage,
&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
@ -1887,7 +1955,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
} }
} }
fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInstruction,TimeInner>){ fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<PhysicsInstruction>){
match &ins.instruction{ match &ins.instruction{
PhysicsInstruction::Input(PhysicsInputInstruction::Idle) PhysicsInstruction::Input(PhysicsInputInstruction::Idle)
|PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_)) |PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_))
@ -1910,7 +1978,6 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
#[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));

View File

@ -1,17 +1,10 @@
use strafesnet_common::mouse::MouseState; use strafesnet_common::mouse::MouseState;
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner}; use strafesnet_common::physics::Instruction as PhysicsInputInstruction;
use strafesnet_common::physics::{Time as PhysicsTime,TimeInner as PhysicsTimeInner,Instruction as PhysicsInputInstruction}; use strafesnet_common::integer::Time;
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),
@ -43,11 +36,11 @@ pub struct MouseInterpolator{
//"PlayerController" //"PlayerController"
user_settings:crate::settings::UserSettings, user_settings:crate::settings::UserSettings,
//"MouseInterpolator" //"MouseInterpolator"
timeline:std::collections::VecDeque<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>, timeline:std::collections::VecDeque<TimedInstruction<PhysicsInputInstruction>>,
last_mouse_time:PhysicsTime, last_mouse_time:Time,//this value is pre-transformed to simulation time
mouse_blocking:bool, mouse_blocking:bool,
//"Simulation" //"Simulation"
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>, timer:Timer<Scaled>,
physics:crate::physics::PhysicsContext, physics:crate::physics::PhysicsContext,
} }
@ -65,7 +58,7 @@ impl MouseInterpolator{
user_settings, user_settings,
} }
} }
fn push_mouse_instruction(&mut self,ins:&TimedInstruction<Instruction,SessionTimeInner>,m:glam::IVec2){ fn push_mouse_instruction(&mut self,ins:&TimedInstruction<Instruction>,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{
@ -87,7 +80,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:SessionTime,phys_input:PhysicsInputInstruction){ fn push(&mut self,time:Time,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),
@ -96,7 +89,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,SessionTimeInner>)->bool{ fn map_instruction(&mut self,ins:&TimedInstruction<Instruction>)->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{
@ -133,7 +126,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 SetPaused: {e}"); println!("Cannot pause: {e}");
} }
self.push(ins.time,PhysicsInputInstruction::Idle); self.push(ins.time,PhysicsInputInstruction::Idle);
}, },
@ -147,7 +140,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:SessionTime){ fn unblock_mouse(&mut self,time:Time){
//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,
@ -157,13 +150,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:SessionTime)->bool{ fn update_mouse_blocking(&mut self,time:Time)->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 PhysicsTime::from_millis(10)<self.timer.time(time)-self.physics.get_next_mouse().time{ if Time::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{
@ -181,20 +174,20 @@ impl MouseInterpolator{
self.physics.run_input_instruction(instruction); self.physics.run_input_instruction(instruction);
} }
} }
pub fn handle_instruction(&mut self,ins:&TimedInstruction<Instruction,SessionTimeInner>){ pub fn handle_instruction(&mut self,ins:&TimedInstruction<Instruction>){
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:SessionTime)->FrameState{ pub fn get_frame_state(&self,time:Time)->crate::graphics::FrameState{
FrameState{ crate::graphics::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:SessionTime,map:&strafesnet_common::map::CompleteMap){ pub fn change_map(&mut self,time:Time,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);
@ -206,7 +199,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, time:self.timer.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,
@ -219,16 +212,137 @@ 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),
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,SessionTimeInner>>{ )->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction>>{
let physics=crate::physics::PhysicsContext::default(); let physics=crate::physics::PhysicsContext::default();
let mut interpolator=MouseInterpolator::new( //load bot
let file=std::fs::File::open("/home/quat/strafesnet/roblox_bot_file/files/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d").unwrap();
let mut bot_file=strafesnet_roblox_bot_file::v0::File::new(std::io::BufReader::new(file)).unwrap();
let mut interpolator=PlayBacker::new(
physics, physics,
user_settings bot_file.read_all().unwrap(),
); );
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTimeInner>|{ crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction>|{
interpolator.handle_instruction(&ins); interpolator.handle_instruction(&ins);
match ins.instruction{ match ins.instruction{
Instruction::Render=>{ Instruction::Render=>{
@ -236,7 +350,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().clone())).unwrap(); graphics_worker.send(crate::graphics_worker::Instruction::Resize(size,interpolator.user_settings())).unwrap();
}, },
Instruction::ChangeMap(map)=>{ Instruction::ChangeMap(map)=>{
interpolator.change_map(ins.time,&map); interpolator.change_map(ins.time,&map);

View File

@ -1,353 +0,0 @@
use strafesnet_common::integer::{self,vec3::{self,Vector3},Fixed,Planar64,Planar64Vec3,Ratio};
// This algorithm is based on Lua code
// written by Trey Reynolds in 2021
// EPSILON=1/2^10
// A stack-allocated variable-size list that holds up to 4 elements
// Direct references are used instead of indices i0, i1, i2, i3
type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
// hack to allow comparing ratios to zero
const RATIO_ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON);
struct Ray{
origin:Planar64Vec3,
direction:Planar64Vec3,
}
impl Ray{
fn extrapolate<Num,Den,N1,T1>(&self,t:Ratio<Num,Den>)->Planar64Vec3
where
Num:Copy,
Den:Copy,
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<Den,Output=N1>,
N1:integer::Divide<Den,Output=T1>,
T1:integer::Fix<Planar64>,
{
self.origin+self.direction.map(|elem|(t*elem).divide().fix())
}
}
/// Information about a contact restriction
pub struct Contact{
pub position:Planar64Vec3,
pub velocity:Planar64Vec3,
pub normal:Planar64Vec3,
}
impl Contact{
fn relative_to(&self,point:Planar64Vec3)->Self{
Self{
position:self.position-point,
velocity:self.velocity,
normal:self.normal,
}
}
fn relative_dot(&self,direction:Planar64Vec3)->Fixed<2,64>{
(direction-self.velocity).dot(self.normal)
}
/// Calculate the time of intersection. (previously get_touch_time)
fn solve(&self,ray:&Ray)->Ratio<Fixed<2,64>,Fixed<2,64>>{
(self.position-ray.origin).dot(self.normal)/(ray.direction-self.velocity).dot(self.normal)
}
}
//note that this is horrible with fixed point arithmetic
fn solve1(c0:&Contact)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<2,64>>>{
const EPSILON:Fixed<2,64>=Fixed::from_bits(Fixed::<2,64>::ONE.to_bits().shr(10));
let det=c0.normal.dot(c0.velocity);
if det.abs()<EPSILON{
return None;
}
let d0=c0.normal.dot(c0.position);
Some(c0.normal*d0/det)
}
fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<Fixed<5,160>>,Fixed<4,128>>>{
const EPSILON:Fixed<4,128>=Fixed::from_bits(Fixed::<4,128>::ONE.to_bits().shr(10));
let u0_u1=c0.velocity.cross(c1.velocity);
let n0_n1=c0.normal.cross(c1.normal);
let det=u0_u1.dot(n0_n1);
if det.abs()<EPSILON{
return None;
}
let d0=c0.normal.dot(c0.position);
let d1=c1.normal.dot(c1.position);
Some((c1.normal.cross(u0_u1)*d0+u0_u1.cross(c0.normal)*d1)/det)
}
fn solve3(c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ratio<Vector3<Fixed<4,128>>,Fixed<3,96>>>{
const EPSILON:Fixed<3,96>=Fixed::from_bits(Fixed::<3,96>::ONE.to_bits().shr(10));
let n0_n1=c0.normal.cross(c1.normal);
let det=c2.normal.dot(n0_n1);
if det.abs()<EPSILON{
return None;
}
let d0=c0.normal.dot(c0.position);
let d1=c1.normal.dot(c1.position);
let d2=c2.normal.dot(c2.position);
Some((c1.normal.cross(c2.normal)*d0+c2.normal.cross(c0.normal)*d1+c0.normal.cross(c1.normal)*d2)/det)
}
fn decompose1(point:Planar64Vec3,u0:Planar64Vec3)->Option<[Ratio<Fixed<2,64>,Fixed<2,64>>;1]>{
let det=u0.dot(u0);
if det==Fixed::ZERO{
return None;
}
let s0=u0.dot(point)/det;
Some([s0])
}
fn decompose2(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3)->Option<[Ratio<Fixed<4,128>,Fixed<4,128>>;2]>{
let u0_u1=u0.cross(u1);
let det=u0_u1.dot(u0_u1);
if det==Fixed::ZERO{
return None;
}
let s0=u0_u1.dot(point.cross(u1))/det;
let s1=u0_u1.dot(u0.cross(point))/det;
Some([s0,s1])
}
fn decompose3(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3,u2:Planar64Vec3)->Option<[Ratio<Fixed<3,96>,Fixed<3,96>>;3]>{
let det=u0.cross(u1).dot(u2);
if det==Fixed::ZERO{
return None;
}
let s0=point.cross(u1).dot(u2)/det;
let s1=u0.cross(point).dot(u2)/det;
let s2=u0.cross(u1).dot(point)/det;
Some([s0,s1,s2])
}
fn is_space_enclosed_2(
a:Planar64Vec3,
b:Planar64Vec3,
)->bool{
a.cross(b)==Vector3::new([Fixed::ZERO;3])
&&a.dot(b).is_negative()
}
fn is_space_enclosed_3(
a:Planar64Vec3,
b:Planar64Vec3,
c:Planar64Vec3
)->bool{
a.cross(b).dot(c)==Fixed::ZERO
&&{
let det_abac=a.cross(b).dot(a.cross(c));
let det_abbc=a.cross(b).dot(b.cross(c));
let det_acbc=a.cross(c).dot(b.cross(c));
return!( det_abac*det_abbc).is_positive()
&&!( det_abbc*det_acbc).is_positive()
&&!(-det_acbc*det_abac).is_positive()
||is_space_enclosed_2(a,b)
||is_space_enclosed_2(a,c)
||is_space_enclosed_2(b,c)
}
}
fn is_space_enclosed_4(
a:Planar64Vec3,
b:Planar64Vec3,
c:Planar64Vec3,
d:Planar64Vec3,
)->bool{
let det_abc=a.cross(b).dot(c);
let det_abd=a.cross(b).dot(d);
let det_acd=a.cross(c).dot(d);
let det_bcd=b.cross(c).dot(d);
return( det_abc*det_abd).is_negative()
&&(-det_abc*det_acd).is_negative()
&&( det_abd*det_acd).is_negative()
&&( det_abc*det_bcd).is_negative()
&&(-det_abd*det_bcd).is_negative()
&&( det_acd*det_bcd).is_negative()
||is_space_enclosed_3(a,b,c)
||is_space_enclosed_3(a,b,d)
||is_space_enclosed_3(a,c,d)
||is_space_enclosed_3(b,c,d)
}
const fn get_push_ray_0(point:Planar64Vec3)->Option<Ray>{
Some(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)->Option<(Ray,Conts<'a>)>{
match get_push_ray_0(point){
Some(ray)=>Some((ray,Conts::new_const())),
None=>None,
}
}
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:Conts<'a>,
)->Option<(Ray,Conts<'a>)>{
match conts.as_slice(){
&[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),
&[]=>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)->Option<Planar64Vec3>{
const ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON);
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,conts))=>(t,conts),
None=>return Some(ray.origin),
};
if ZERO.le_ratio(next_t){
return Some(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){
Some((new_ray,new_conts))=>(ray,conts)=(new_ray,new_conts),
None=>return Some(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!(
Some(vec3::ZERO),
push_solve(&contacts,vec3::NEG_Y)
);
}
}

View File

@ -1,7 +1,6 @@
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
@ -193,14 +192,14 @@ pub struct SetupContext<'a>{
pub config:wgpu::SurfaceConfiguration, pub config:wgpu::SurfaceConfiguration,
} }
pub fn setup_and_start(title:&str){ pub fn setup_and_start(title:String){
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,&event_loop).unwrap(); let window=create_window(title.as_str(),&event_loop).unwrap();
let partial_2=partial_1.create_surface(&window).unwrap(); let partial_2=partial_1.create_surface(&window).unwrap();
@ -235,7 +234,7 @@ pub fn setup_and_start(title:&str){
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,SessionTimeInner>>, mut window_thread:crate::compat_worker::QNWorker<TimedInstruction<WindowInstruction>>,
root_time:std::time::Instant 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|{

View File

@ -1,7 +1,6 @@
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>),
@ -14,17 +13,17 @@ 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<SessionTimeInner>,//std::sync::Arc<std::sync::Mutex<>> mouse:strafesnet_common::mouse::MouseState,//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,SessionTimeInner>>, physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<crate::physics_worker::Instruction>>,
} }
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:SessionTime,event:winit::event::WindowEvent){ fn window_event(&mut self,time:integer::Time,event:winit::event::WindowEvent){
match event{ match event{
winit::event::WindowEvent::DroppedFile(path)=>{ winit::event::WindowEvent::DroppedFile(path)=>{
match crate::file::load(path.as_path()){ match crate::file::load(path.as_path()){
@ -123,7 +122,7 @@ impl WindowContext<'_>{
} }
} }
fn device_event(&mut self,time:SessionTime,event: winit::event::DeviceEvent){ fn device_event(&mut self,time:integer::Time,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
@ -162,7 +161,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,SessionTimeInner>>{ )->crate::compat_worker::QNWorker<'a,TimedInstruction<WindowInstruction>>{
// WindowContextSetup::new // WindowContextSetup::new
let user_settings=crate::settings::read_user_settings(); let user_settings=crate::settings::read_user_settings();
@ -185,7 +184,7 @@ pub fn worker<'a>(
}; };
//WindowContextSetup::into_worker //WindowContextSetup::into_worker
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<WindowInstruction,SessionTimeInner>|{ crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<WindowInstruction>|{
match ins.instruction{ match ins.instruction{
WindowInstruction::RequestRedraw=>{ WindowInstruction::RequestRedraw=>{
window_context.window.request_redraw(); window_context.window.request_redraw();

View File

@ -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};
type Body=crate::physics::Body; use crate::physics;
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=Body::new(integer::vec3::ONE,integer::vec3::ONE,integer::vec3::ONE,integer::Time::ZERO); let test_body=physics::Body::new(integer::vec3::ONE,integer::vec3::ONE,integer::vec3::ONE,integer::Time::ZERO);
let worker=QRWorker::new(Body::ZERO, let worker=QRWorker::new(physics::Body::ZERO,
|_|Body::new(integer::vec3::ONE,integer::vec3::ONE,integer::vec3::ONE,integer::Time::ZERO) |_|physics::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:strafesnet_common::physics::Time::ZERO, time:integer::Time::ZERO,
instruction:strafesnet_common::physics::Instruction::Idle, instruction:strafesnet_common::physics::Instruction::Idle,
}; };
worker.send(task).unwrap(); worker.send(task).unwrap();