Compare commits

...

12 Commits

5 changed files with 602 additions and 218 deletions

View File

@ -404,11 +404,12 @@ impl TryFrom<[f32;3]> for Unit32Vec3{
*/ */
///[-1.0,1.0] = [-2^32,2^32] ///[-1.0,1.0] = [-2^32,2^32]
#[derive(Clone,Copy,Hash,Eq,Ord,PartialEq,PartialOrd)] #[derive(Clone,Copy,Debug,Hash,Eq,Ord,PartialEq,PartialOrd)]
pub struct Planar64(i64); pub struct Planar64(i64);
impl Planar64{ impl Planar64{
pub const ZERO:Self=Self(0); pub const ZERO:Self=Self(0);
pub const ONE:Self=Self(1<<32); pub const ONE:Self=Self(1<<32);
pub const FRAC_1_SQRT2:Self=Self(3_037_000_500);
#[inline] #[inline]
pub const fn int(num:i32)->Self{ pub const fn int(num:i32)->Self{
Self(Self::ONE.0*num as i64) Self(Self::ONE.0*num as i64)
@ -531,7 +532,14 @@ impl std::ops::Mul<Planar64> for Planar64{
type Output=Planar64; type Output=Planar64;
#[inline] #[inline]
fn mul(self, rhs: Self) -> Self::Output { fn mul(self, rhs: Self) -> Self::Output {
Planar64((((self.0 as i128)*(rhs.0 as i128))>>32) as i64) Planar64(((self.0 as i128*rhs.0 as i128)>>32) as i64)
}
}
impl std::ops::Mul<Time> for Planar64{
type Output=Planar64;
#[inline]
fn mul(self,rhs:Time)->Self::Output{
Planar64(((self.0 as i128*rhs.0 as i128)/1_000_000_000) as i64)
} }
} }
impl std::ops::Div<i64> for Planar64{ impl std::ops::Div<i64> for Planar64{
@ -556,7 +564,7 @@ impl std::ops::Div<Planar64> for Planar64{
///[-1.0,1.0] = [-2^32,2^32] ///[-1.0,1.0] = [-2^32,2^32]
#[derive(Clone,Copy,Default,Hash,Eq,PartialEq)] #[derive(Clone,Copy,Debug,Default,Hash,Eq,PartialEq)]
pub struct Planar64Vec3(glam::I64Vec3); pub struct Planar64Vec3(glam::I64Vec3);
impl Planar64Vec3{ impl Planar64Vec3{
pub const ZERO:Self=Planar64Vec3(glam::I64Vec3::ZERO); pub const ZERO:Self=Planar64Vec3(glam::I64Vec3::ZERO);
@ -574,6 +582,10 @@ impl Planar64Vec3{
Self(glam::i64vec3((x as i64)<<32,(y as i64)<<32,(z as i64)<<32)) Self(glam::i64vec3((x as i64)<<32,(y as i64)<<32,(z as i64)<<32))
} }
#[inline] #[inline]
pub const fn raw(x:i64,y:i64,z:i64)->Self{
Self(glam::i64vec3(x,y,z))
}
#[inline]
pub fn x(&self)->Planar64{ pub fn x(&self)->Planar64{
Planar64(self.0.x) Planar64(self.0.x)
} }
@ -618,6 +630,14 @@ impl Planar64Vec3{
)>>32) as i64) )>>32) as i64)
} }
#[inline] #[inline]
/* pub fn cross(&self,rhs:Self)->Planar64Vec3{
Planar64Vec3(((
(self.0.x as i128)*(rhs.0.x as i128)+
(self.0.y as i128)*(rhs.0.y as i128)+
(self.0.z as i128)*(rhs.0.z as i128)
)>>32) as i64)
}*/
#[inline]
pub fn length(&self)->Planar64{ pub fn length(&self)->Planar64{
let radicand=(self.0.x as i128)*(self.0.x as i128)+(self.0.y as i128)*(self.0.y as i128)+(self.0.z as i128)*(self.0.z as i128); let radicand=(self.0.x as i128)*(self.0.x as i128)+(self.0.y as i128)*(self.0.y as i128)+(self.0.z as i128)*(self.0.z as i128);
Planar64(unsafe{(radicand as f64).sqrt().to_int_unchecked()}) Planar64(unsafe{(radicand as f64).sqrt().to_int_unchecked()})
@ -808,6 +828,23 @@ impl Planar64Mat3{
} }
} }
#[inline] #[inline]
pub fn from_rotation_yx(yaw:Angle32,pitch:Angle32)->Self{
let xtheta=yaw.0 as f64*ANGLE32_TO_FLOAT64_RADIANS;
let (xs,xc)=xtheta.sin_cos();
let (xc,xs)=(xc*PLANAR64_ONE_FLOAT64,xs*PLANAR64_ONE_FLOAT64);
let ytheta=pitch.0 as f64*ANGLE32_TO_FLOAT64_RADIANS;
let (ys,yc)=ytheta.sin_cos();
let (yc,ys)=(yc*PLANAR64_ONE_FLOAT64,ys*PLANAR64_ONE_FLOAT64);
//TODO: fix this rounding towards 0
let (xc,xs):(i64,i64)=(unsafe{xc.to_int_unchecked()},unsafe{xs.to_int_unchecked()});
let (yc,ys):(i64,i64)=(unsafe{yc.to_int_unchecked()},unsafe{ys.to_int_unchecked()});
Self::from_cols(
Planar64Vec3(glam::i64vec3(xc,0,-xs)),
Planar64Vec3(glam::i64vec3(((xs as i128*ys as i128)>>32) as i64,yc,((xc as i128*ys as i128)>>32) as i64)),
Planar64Vec3(glam::i64vec3(((xs as i128*yc as i128)>>32) as i64,-ys,((xc as i128*yc as i128)>>32) as i64)),
)
}
#[inline]
pub fn from_rotation_y(angle:Angle32)->Self{ pub fn from_rotation_y(angle:Angle32)->Self{
let theta=angle.0 as f64*ANGLE32_TO_FLOAT64_RADIANS; let theta=angle.0 as f64*ANGLE32_TO_FLOAT64_RADIANS;
let (s,c)=theta.sin_cos(); let (s,c)=theta.sin_cos();

View File

@ -49,9 +49,19 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,force_interse
let mut intersecting=crate::model::IntersectingAttributes::default(); let mut intersecting=crate::model::IntersectingAttributes::default();
let mut contacting=crate::model::ContactingAttributes::default(); let mut contacting=crate::model::ContactingAttributes::default();
let mut force_can_collide=can_collide; let mut force_can_collide=can_collide;
const GRAVITY:Planar64Vec3=Planar64Vec3::int(0,-100,0);
match name{ match name{
"Water"=>intersecting.water=Some(crate::model::IntersectingWater{density:Planar64::ONE,viscosity:Planar64::ONE/10,current:velocity}), "Water"=>{
"Accelerator"=>{force_can_collide=false;intersecting.accelerator=Some(crate::model::IntersectingAccelerator{acceleration:velocity})}, force_can_collide=false;
//TODO: read stupid CustomPhysicalProperties
intersecting.water=Some(crate::model::IntersectingWater{density:Planar64::ONE,viscosity:Planar64::ONE/10,current:velocity});
},
"Accelerator"=>{
//although the new game supports collidable accelerators, this is a roblox compatability map loader
force_can_collide=false;
general.accelerator=Some(crate::model::GameMechanicAccelerator{acceleration:velocity});
},
"SetVelocity"=>general.trajectory=Some(crate::model::GameMechanicSetTrajectory::Velocity(velocity)),
"MapFinish"=>{force_can_collide=false;general.zone=Some(crate::model::GameMechanicZone{mode_id:0,behaviour:crate::model::ZoneBehaviour::Finish})}, "MapFinish"=>{force_can_collide=false;general.zone=Some(crate::model::GameMechanicZone{mode_id:0,behaviour:crate::model::ZoneBehaviour::Finish})},
"MapAnticheat"=>{force_can_collide=false;general.zone=Some(crate::model::GameMechanicZone{mode_id:0,behaviour:crate::model::ZoneBehaviour::Anitcheat})}, "MapAnticheat"=>{force_can_collide=false;general.zone=Some(crate::model::GameMechanicZone{mode_id:0,behaviour:crate::model::ZoneBehaviour::Anitcheat})},
"Platform"=>general.teleport_behaviour=Some(crate::model::TeleportBehaviour::StageElement(crate::model::GameMechanicStageElement{ "Platform"=>general.teleport_behaviour=Some(crate::model::TeleportBehaviour::StageElement(crate::model::GameMechanicStageElement{
@ -72,12 +82,28 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,force_interse
}, },
behaviour:match &captures[2]{ behaviour:match &captures[2]{
"Spawn"|"SpawnAt"=>crate::model::StageElementBehaviour::SpawnAt, "Spawn"|"SpawnAt"=>crate::model::StageElementBehaviour::SpawnAt,
//cancollide false so you don't hit the side
//NOT a decoration
"Trigger"=>{force_can_collide=false;crate::model::StageElementBehaviour::Trigger}, "Trigger"=>{force_can_collide=false;crate::model::StageElementBehaviour::Trigger},
"Teleport"=>{force_can_collide=false;crate::model::StageElementBehaviour::Teleport}, "Teleport"=>{force_can_collide=false;crate::model::StageElementBehaviour::Teleport},
"Platform"=>crate::model::StageElementBehaviour::Platform, "Platform"=>crate::model::StageElementBehaviour::Platform,
_=>panic!("regex1[2] messed up bad"), _=>panic!("regex1[2] messed up bad"),
} }
})); }));
}else if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Jump)(\d+)$")
.captures(other){
general.teleport_behaviour=Some(crate::model::TeleportBehaviour::StageElement(crate::model::GameMechanicStageElement{
mode_id:0,
stage_id:0,
force:match captures.get(1){
Some(m)=>m.as_str()=="Force",
None=>false,
},
behaviour:match &captures[2]{
"Jump"=>crate::model::StageElementBehaviour::JumpLimit(captures[3].parse::<u32>().unwrap()),
_=>panic!("regex4[1] messed up bad"),
}
}));
}else if let Some(captures)=lazy_regex::regex!(r"^Bonus(Finish|Anticheat)(\d+)$") }else if let Some(captures)=lazy_regex::regex!(r"^Bonus(Finish|Anticheat)(\d+)$")
.captures(other){ .captures(other){
force_can_collide=false; force_can_collide=false;
@ -86,39 +112,46 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,force_interse
"Anticheat"=>general.zone=Some(crate::model::GameMechanicZone{mode_id:captures[2].parse::<u32>().unwrap(),behaviour:crate::model::ZoneBehaviour::Anitcheat}), "Anticheat"=>general.zone=Some(crate::model::GameMechanicZone{mode_id:captures[2].parse::<u32>().unwrap(),behaviour:crate::model::ZoneBehaviour::Anitcheat}),
_=>panic!("regex2[1] messed up bad"), _=>panic!("regex2[1] messed up bad"),
} }
} }else if let Some(captures)=lazy_regex::regex!(r"^(WormholeIn)(\d+)$")
}
}
//need some way to skip this
if velocity!=Planar64Vec3::ZERO{
general.booster=Some(crate::model::GameMechanicBooster{velocity});
}
match force_can_collide{
true=>{
match name{
"Bounce"=>contacting.elasticity=Some(u32::MAX),
"Surf"=>contacting.surf=Some(crate::model::ContactingSurf{}),
"Ladder"=>contacting.ladder=Some(crate::model::ContactingLadder{sticky:true}),
other=>{
if let Some(captures)=lazy_regex::regex!(r"^(Jump|WormholeIn)(\d+)$")
.captures(other){ .captures(other){
force_can_collide=false;
match &captures[1]{ match &captures[1]{
"Jump"=>general.jump_limit=Some(crate::model::GameMechanicJumpLimit{count:captures[2].parse::<u32>().unwrap()}),
"WormholeIn"=>general.teleport_behaviour=Some(crate::model::TeleportBehaviour::Wormhole(crate::model::GameMechanicWormhole{destination_model_id:captures[2].parse::<u32>().unwrap()})), "WormholeIn"=>general.teleport_behaviour=Some(crate::model::TeleportBehaviour::Wormhole(crate::model::GameMechanicWormhole{destination_model_id:captures[2].parse::<u32>().unwrap()})),
_=>panic!("regex3[1] messed up bad"), _=>panic!("regex3[1] messed up bad"),
} }
} }
} }
} }
//need some way to skip this
if velocity!=Planar64Vec3::ZERO{
//assume all vertical boosters are targetting a height
let vg=velocity.dot(GRAVITY);
if Planar64::ZERO<=vg{
//weird down booster
general.booster=Some(crate::model::GameMechanicBooster::Velocity(velocity));
}else{
println!("set attr");
let gg=GRAVITY.dot(GRAVITY);
let height=-vg*gg.sqrt().sqrt()*Planar64::FRAC_1_SQRT2/gg;//vi/sqrt(-2*a)=d
let v=velocity-GRAVITY*(vg/gg);
//if we are adding zero SO BE IT, the check to see if the vectors are parallel is too sensitive
general.booster=Some(crate::model::GameMechanicBooster::Velocity(v));
general.trajectory=Some(crate::model::GameMechanicSetTrajectory::Height(height));
}
}
match force_can_collide{
true=>{
match name{
"Bounce"=>contacting.contact_behaviour=Some(crate::model::ContactingBehaviour::Elastic(u32::MAX)),
"Surf"=>contacting.contact_behaviour=Some(crate::model::ContactingBehaviour::Surf),
"Ladder"=>contacting.contact_behaviour=Some(crate::model::ContactingBehaviour::Ladder(crate::model::ContactingLadder{sticky:true})),
_=>(),
}
crate::model::CollisionAttributes::Contact{contacting,general} crate::model::CollisionAttributes::Contact{contacting,general}
}, },
false=>if force_intersecting false=>if force_intersecting
||general.jump_limit.is_some() ||general.any()
||general.booster.is_some() ||intersecting.any()
||general.zone.is_some()
||general.teleport_behaviour.is_some()
||intersecting.water.is_some()
||intersecting.accelerator.is_some()
{ {
crate::model::CollisionAttributes::Intersect{intersecting,general} crate::model::CollisionAttributes::Intersect{intersecting,general}
}else{ }else{

View File

@ -931,7 +931,7 @@ impl framework::Example for GlobalState {
let screen_size=glam::uvec2(config.width,config.height); let screen_size=glam::uvec2(config.width,config.height);
let camera=GraphicsCamera::new(screen_size,user_settings.calculate_fov(1.0,&screen_size).as_vec2()); let camera=GraphicsCamera::new(screen_size,user_settings.calculate_fov(1.0,&screen_size).as_vec2());
let camera_uniforms = camera.to_uniform_data(physics.output().adjust_mouse(&physics.next_mouse)); let camera_uniforms = camera.to_uniform_data(physics.output().adjust_mouse(&physics::MouseState::default()));
let camera_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let camera_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Camera"), label: Some("Camera"),
contents: bytemuck::cast_slice(&camera_uniforms), contents: bytemuck::cast_slice(&camera_uniforms),
@ -1063,6 +1063,7 @@ impl framework::Example for GlobalState {
self.graphics.clear(); self.graphics.clear();
let mut physics=physics::PhysicsState::default(); let mut physics=physics::PhysicsState::default();
//physics.spawn()
physics.game.stage_id=0; physics.game.stage_id=0;
physics.spawn_point=spawn_point; physics.spawn_point=spawn_point;
physics.process_instruction(instruction::TimedInstruction{ physics.process_instruction(instruction::TimedInstruction{

View File

@ -1,4 +1,4 @@
use crate::integer::{Planar64,Planar64Vec3,Planar64Affine3}; use crate::integer::{Time,Planar64,Planar64Vec3,Planar64Affine3};
pub type TextureCoordinate=glam::Vec2; pub type TextureCoordinate=glam::Vec2;
pub type Color4=glam::Vec4; pub type Color4=glam::Vec4;
#[derive(Clone,Hash,PartialEq,Eq)] #[derive(Clone,Hash,PartialEq,Eq)]
@ -92,11 +92,15 @@ pub enum TempIndexedAttributes{
//you have this effect while in contact //you have this effect while in contact
#[derive(Clone)] #[derive(Clone)]
pub struct ContactingSurf{}
#[derive(Clone)]
pub struct ContactingLadder{ pub struct ContactingLadder{
pub sticky:bool pub sticky:bool
} }
#[derive(Clone)]
pub enum ContactingBehaviour{
Surf,
Ladder(ContactingLadder),
Elastic(u32),//[1/2^32,1] 0=None (elasticity+1)/2^32
}
//you have this effect while intersecting //you have this effect while intersecting
#[derive(Clone)] #[derive(Clone)]
pub struct IntersectingWater{ pub struct IntersectingWater{
@ -104,18 +108,37 @@ pub struct IntersectingWater{
pub density:Planar64, pub density:Planar64,
pub current:Planar64Vec3, pub current:Planar64Vec3,
} }
#[derive(Clone)]
pub struct IntersectingAccelerator{
pub acceleration:Planar64Vec3
}
//All models can be given these attributes //All models can be given these attributes
#[derive(Clone)] #[derive(Clone)]
pub struct GameMechanicJumpLimit{ pub struct GameMechanicAccelerator{
pub count:u32, pub acceleration:Planar64Vec3
} }
#[derive(Clone)] #[derive(Clone)]
pub struct GameMechanicBooster{ pub enum GameMechanicBooster{
pub velocity:Planar64Vec3, Affine(Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more
Velocity(Planar64Vec3),//straight up boost velocity adds to your current velocity
Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction
}
#[derive(Clone,Debug)]
pub enum TrajectoryChoice{
HighArcLongDuration,//underhand lob at target: less horizontal speed and more air time
LowArcShortDuration,//overhand throw at target: more horizontal speed and less air time
}
#[derive(Clone,Debug)]
pub enum GameMechanicSetTrajectory{
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
TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time
target_point:Planar64Vec3,
time:Time,//short time = fast and direct, long time = launch high in the air, negative time = wrong way
},
TrajectoryTargetPoint{//launch at a fixed speed and land at a target point
target_point:Planar64Vec3,
speed:Planar64,//if speed is too low this will fail to reach the target. The closest-passing trajectory will be chosen instead
trajectory_choice:TrajectoryChoice,
},
Velocity(Planar64Vec3),//SetVelocity
DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions
} }
#[derive(Clone)] #[derive(Clone)]
pub enum ZoneBehaviour{ pub enum ZoneBehaviour{
@ -130,10 +153,10 @@ pub struct GameMechanicZone{
pub behaviour:ZoneBehaviour, pub behaviour:ZoneBehaviour,
} }
// enum TrapCondition{ // enum TrapCondition{
// FasterThan(i64), // FasterThan(Planar64),
// SlowerThan(i64), // SlowerThan(Planar64),
// InRange(i64,i64), // InRange(Planar64,Planar64),
// OutsideRange(i64,i64), // OutsideRange(Planar64,Planar64),
// } // }
#[derive(Clone)] #[derive(Clone)]
pub enum StageElementBehaviour{ pub enum StageElementBehaviour{
@ -142,6 +165,7 @@ pub enum StageElementBehaviour{
Trigger, Trigger,
Teleport, Teleport,
Platform, Platform,
JumpLimit(u32),
//Speedtrap(TrapCondition),//Acts as a trigger with a speed condition //Speedtrap(TrapCondition),//Acts as a trigger with a speed condition
} }
#[derive(Clone)] #[derive(Clone)]
@ -164,24 +188,42 @@ pub enum TeleportBehaviour{
StageElement(GameMechanicStageElement), StageElement(GameMechanicStageElement),
Wormhole(GameMechanicWormhole), Wormhole(GameMechanicWormhole),
} }
//attributes listed in order of handling
#[derive(Default,Clone)] #[derive(Default,Clone)]
pub struct GameMechanicAttributes{ pub struct GameMechanicAttributes{
pub jump_limit:Option<GameMechanicJumpLimit>,
pub booster:Option<GameMechanicBooster>,
pub zone:Option<GameMechanicZone>, pub zone:Option<GameMechanicZone>,
pub booster:Option<GameMechanicBooster>,
pub trajectory:Option<GameMechanicSetTrajectory>,
pub teleport_behaviour:Option<TeleportBehaviour>, pub teleport_behaviour:Option<TeleportBehaviour>,
pub accelerator:Option<GameMechanicAccelerator>,
}
impl GameMechanicAttributes{
pub fn any(&self)->bool{
self.booster.is_some()
||self.trajectory.is_some()
||self.zone.is_some()
||self.teleport_behaviour.is_some()
||self.accelerator.is_some()
}
} }
#[derive(Default,Clone)] #[derive(Default,Clone)]
pub struct ContactingAttributes{ pub struct ContactingAttributes{
pub elasticity:Option<u32>,//[1/2^32,1] 0=None (elasticity+1)/2^32
//friction? //friction?
pub surf:Option<ContactingSurf>, pub contact_behaviour:Option<ContactingBehaviour>,
pub ladder:Option<ContactingLadder>, }
impl ContactingAttributes{
pub fn any(&self)->bool{
self.contact_behaviour.is_some()
}
} }
#[derive(Default,Clone)] #[derive(Default,Clone)]
pub struct IntersectingAttributes{ pub struct IntersectingAttributes{
pub water:Option<IntersectingWater>, pub water:Option<IntersectingWater>,
pub accelerator:Option<IntersectingAccelerator>, }
impl IntersectingAttributes{
pub fn any(&self)->bool{
self.water.is_some()
}
} }
//Spawn(u32) NO! spawns are indexed in the map header instead of marked with attibutes //Spawn(u32) NO! spawns are indexed in the map header instead of marked with attibutes
pub enum CollisionAttributes{ pub enum CollisionAttributes{

View File

@ -115,22 +115,60 @@ impl MouseState {
} }
} }
pub enum WalkEnum{ enum WalkEnum{
Reached, Reached,
Transient, Transient(WalkTarget),
}
struct WalkTarget{
velocity:Planar64Vec3,
time:Time,
}
struct WalkState{
normal:Planar64Vec3,
state:WalkEnum,
}
impl WalkEnum{
//args going crazy
//(walk_enum,body.acceleration)=with_target_velocity();
fn with_target_velocity(touching:&TouchingState,body:&Body,style:&StyleModifiers,models:&Vec<ModelPhysics>,mut velocity:Planar64Vec3,normal:&Planar64Vec3)->(WalkEnum,Planar64Vec3){
touching.constrain_velocity(models,&mut velocity);
let mut target_diff=velocity-body.velocity;
//remove normal component
target_diff-=normal.clone()*(normal.dot(target_diff)/normal.dot(normal.clone()));
if target_diff==Planar64Vec3::ZERO{
let mut a=Planar64Vec3::ZERO;
touching.constrain_acceleration(models,&mut a);
(WalkEnum::Reached,a)
}else{
//normal friction acceleration is clippedAcceleration.dot(normal)*friction
let diff_len=target_diff.length();
let friction=if diff_len<style.walk_speed{
style.static_friction
}else{
style.kinetic_friction
};
let accel=style.walk_accel.min(style.gravity.dot(Planar64Vec3::NEG_Y)*friction);
let time_delta=diff_len/accel;
let mut a=target_diff.with_length(accel);
touching.constrain_acceleration(models,&mut a);
(WalkEnum::Transient(WalkTarget{velocity,time:body.time+Time::from(time_delta)}),a)
}
} }
pub struct WalkState {
pub target_velocity: Planar64Vec3,
pub target_time: Time,
pub state: WalkEnum,
} }
impl WalkState{ impl WalkState{
pub fn new() -> Self { fn ground(touching:&TouchingState,body:&Body,style:&StyleModifiers,models:&Vec<ModelPhysics>,mut velocity:Planar64Vec3)->(Self,Planar64Vec3){
Self{ let (walk_enum,a)=WalkEnum::with_target_velocity(touching,body,style,models,velocity,&Planar64Vec3::Y);
target_velocity:Planar64Vec3::ZERO, (Self{
target_time:Time::ZERO, state:walk_enum,
state:WalkEnum::Reached, normal:Planar64Vec3::Y,
},a)
} }
fn ladder(touching:&TouchingState,body:&Body,style:&StyleModifiers,models:&Vec<ModelPhysics>,mut velocity:Planar64Vec3,normal:&Planar64Vec3)->(Self,Planar64Vec3){
let (walk_enum,a)=WalkEnum::with_target_velocity(touching,body,style,models,velocity,normal);
(Self{
state:walk_enum,
normal:normal.clone(),
},a)
} }
} }
@ -182,6 +220,14 @@ impl PhysicsCamera {
.clamp(self.angle_pitch_lower_limit,self.angle_pitch_upper_limit); .clamp(self.angle_pitch_lower_limit,self.angle_pitch_upper_limit);
return glam::vec2(ax.into(),ay.into()); return glam::vec2(ax.into(),ay.into());
} }
fn simulate_move_rotation(&self,mouse_pos:glam::IVec2)->Planar64Mat3{
let a=-self.sensitivity.mul_int((mouse_pos-self.mouse.pos+self.clamped_mouse_pos).as_i64vec2());
let ax=Angle32::wrap_from_i64(a.x);
let ay=Angle32::clamp_from_i64(a.y)
//clamp to actual vertical cam limit
.clamp(self.angle_pitch_lower_limit,self.angle_pitch_upper_limit);
Planar64Mat3::from_rotation_yx(ax,ay)
}
fn simulate_move_rotation_y(&self,mouse_pos_x:i32)->Planar64Mat3{ fn simulate_move_rotation_y(&self,mouse_pos_x:i32)->Planar64Mat3{
let ax=-self.sensitivity.x.mul_int((mouse_pos_x-self.mouse.pos.x+self.clamped_mouse_pos.x) as i64); let ax=-self.sensitivity.x.mul_int((mouse_pos_x-self.mouse.pos.x+self.clamped_mouse_pos.x) as i64);
Planar64Mat3::from_rotation_y(Angle32::wrap_from_i64(ax)) Planar64Mat3::from_rotation_y(Angle32::wrap_from_i64(ax))
@ -200,34 +246,48 @@ impl std::default::Default for GameMechanicsState{
} }
} }
pub struct WorldState{} struct WorldState{}
pub struct StyleModifiers{ enum JumpCalculation{
pub controls_mask:u32,//controls which are unable to be activated Capped,//roblox
pub controls_held:u32,//controls which must be active to be able to strafe Energy,//new
pub strafe_tick_rate:Ratio64, Linear,//source
pub jump_time:Time, }
pub mv:Planar64,
pub walkspeed:Planar64, enum JumpImpulse{
pub friction:Planar64, FromTime(Time),//jump time is invariant across mass and gravity changes
pub walk_accel:Planar64, FromHeight(Planar64),//jump height is invariant across mass and gravity changes
pub gravity:Planar64Vec3, FromDeltaV(Planar64),//jump velocity is invariant across mass and gravity changes
pub hitbox_halfsize:Planar64Vec3, FromEnergy(Planar64),// :)
}
//Jumping acts on dot(walks_state.normal,body.velocity)
//Capped means it increases the dot to the cap
//Energy means it adds energy
//Linear means it linearly adds on
struct StyleModifiers{
controls_mask:u32,//controls which are unable to be activated
controls_held:u32,//controls which must be active to be able to strafe
strafe_tick_rate:Ratio64,
jump_impulse:JumpImpulse,
jump_calculation:JumpCalculation,
static_friction:Planar64,
kinetic_friction:Planar64,
walk_speed:Planar64,
walk_accel:Planar64,
ladder_speed:Planar64,
ladder_accel:Planar64,
ladder_dot:Planar64,
swim_speed:Planar64,
mass:Planar64,
mv:Planar64,
air_accel_limit:Option<Planar64>,
gravity:Planar64Vec3,
hitbox_halfsize:Planar64Vec3,
} }
impl std::default::Default for StyleModifiers{ impl std::default::Default for StyleModifiers{
fn default() -> Self { fn default() -> Self {
Self{ Self::roblox_bhop()
controls_mask: !0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
controls_held: 0,
strafe_tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
jump_time: Time::from_nanos(715_588_000/2*100),//0.715588/2.0*100.0
gravity: Planar64Vec3::int(0,-100,0),
friction: Planar64::int(12)/10,
walk_accel: Planar64::int(90),
mv: Planar64::int(27)/10,
walkspeed: Planar64::int(18),
hitbox_halfsize: Planar64Vec3::int(2,5,2)/2,
}
} }
} }
impl StyleModifiers{ impl StyleModifiers{
@ -244,6 +304,121 @@ impl StyleModifiers{
const UP_DIR:Planar64Vec3=Planar64Vec3::Y; const UP_DIR:Planar64Vec3=Planar64Vec3::Y;
const FORWARD_DIR:Planar64Vec3=Planar64Vec3::NEG_Z; const FORWARD_DIR:Planar64Vec3=Planar64Vec3::NEG_Z;
fn new()->Self{
Self{
controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
controls_held:0,
strafe_tick_rate:Ratio64::new(128,Time::ONE_SECOND.nanos() as u64).unwrap(),
jump_impulse:JumpImpulse::FromEnergy(Planar64::int(512)),
jump_calculation:JumpCalculation::Energy,
gravity:Planar64Vec3::int(0,-80,0),
static_friction:Planar64::int(2),
kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static
mass:Planar64::int(1),
mv:Planar64::int(2),
air_accel_limit:None,
walk_speed:Planar64::int(16),
walk_accel:Planar64::int(80),
ladder_speed:Planar64::int(16),
ladder_accel:Planar64::int(160),
ladder_dot:(Planar64::int(1)/2).sqrt(),
swim_speed:Planar64::int(12),
hitbox_halfsize:Planar64Vec3::int(2,5,2)/2,
}
}
fn roblox_bhop()->Self{
Self{
controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
controls_held:0,
strafe_tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)),
jump_calculation:JumpCalculation::Capped,
gravity:Planar64Vec3::int(0,-100,0),
static_friction:Planar64::int(2),
kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static
mass:Planar64::int(1),
mv:Planar64::int(27)/10,
air_accel_limit:None,
walk_speed:Planar64::int(18),
walk_accel:Planar64::int(90),
ladder_speed:Planar64::int(18),
ladder_accel:Planar64::int(180),
ladder_dot:(Planar64::int(1)/2).sqrt(),
swim_speed:Planar64::int(12),
hitbox_halfsize:Planar64Vec3::int(2,5,2)/2,
}
}
fn roblox_surf()->Self{
Self{
controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
controls_held:0,
strafe_tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)),
jump_calculation:JumpCalculation::Capped,
gravity:Planar64Vec3::int(0,-50,0),
static_friction:Planar64::int(2),
kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static
mass:Planar64::int(1),
mv:Planar64::int(27)/10,
air_accel_limit:None,
walk_speed:Planar64::int(18),
walk_accel:Planar64::int(90),
ladder_speed:Planar64::int(18),
ladder_accel:Planar64::int(180),
ladder_dot:(Planar64::int(1)/2).sqrt(),
swim_speed:Planar64::int(12),
hitbox_halfsize:Planar64Vec3::int(2,5,2)/2,
}
}
fn source_bhop()->Self{
//camera_offset=vec3(0,64/16-73/16/2,0),
Self{
controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
controls_held:0,
strafe_tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
jump_impulse:JumpImpulse::FromHeight(Planar64::raw(52<<28)),
jump_calculation:JumpCalculation::Linear,
gravity:Planar64Vec3::raw(0,-800<<28,0),
static_friction:Planar64::int(2),//?
kinetic_friction:Planar64::int(3),//?
mass:Planar64::int(1),
mv:Planar64::raw(30<<28),
air_accel_limit:Some(Planar64::raw(150<<28)*66),
walk_speed:Planar64::int(18),//?
walk_accel:Planar64::int(90),//?
ladder_speed:Planar64::int(18),//?
ladder_accel:Planar64::int(180),//?
ladder_dot:(Planar64::int(1)/2).sqrt(),//?
swim_speed:Planar64::int(12),//?
hitbox_halfsize:Planar64Vec3::raw(33<<28,73<<28,33<<28)/2,
}
}
fn source_surf()->Self{
//camera_offset=vec3(0,64/16-73/16/2,0),
Self{
controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
controls_held:0,
strafe_tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() as u64).unwrap(),
jump_impulse:JumpImpulse::FromHeight(Planar64::raw(52<<28)),
jump_calculation:JumpCalculation::Linear,
gravity:Planar64Vec3::raw(0,-800<<28,0),
static_friction:Planar64::int(2),//?
kinetic_friction:Planar64::int(3),//?
mass:Planar64::int(1),
mv:Planar64::raw(30<<28),
air_accel_limit:Some(Planar64::raw(150<<28)*66),
walk_speed:Planar64::int(18),//?
walk_accel:Planar64::int(90),//?
ladder_speed:Planar64::int(18),//?
ladder_accel:Planar64::int(180),//?
ladder_dot:(Planar64::int(1)/2).sqrt(),//?
swim_speed:Planar64::int(12),//?
hitbox_halfsize:Planar64Vec3::raw(33<<28,73<<28,33<<28)/2,
}
}
fn get_control(&self,control:u32,controls:u32)->bool{ fn get_control(&self,control:u32,controls:u32)->bool{
controls&self.controls_mask&control==control controls&self.controls_mask&control==control
} }
@ -278,32 +453,70 @@ impl StyleModifiers{
return control_dir return control_dir
} }
fn get_jump_power(&self)->Planar64Vec3{ //fn get_jump_time(&self)->Planar64
Planar64Vec3::int(0,715588,0)/(2*1000000/100) //fn get_jump_height(&self)->Planar64
//fn get_jump_energy(&self)->Planar64
fn get_jump_deltav(&self)->Planar64{
match &self.jump_impulse{
&JumpImpulse::FromTime(time)=>self.gravity.length()*(time/2),
&JumpImpulse::FromHeight(height)=>(self.gravity.length()*height*2).sqrt(),
&JumpImpulse::FromDeltaV(deltav)=>deltav,
&JumpImpulse::FromEnergy(energy)=>(energy*2/self.mass).sqrt(),
} }
} }
fn get_walk_target_velocity(&self,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time)->Planar64Vec3{
let camera_mat=camera.simulate_move_rotation_y(camera.mouse.lerp(&next_mouse,time).x);
let control_dir=camera_mat*self.get_control_dir(controls);
control_dir*self.walk_speed
}
fn get_ladder_target_velocity(&self,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time)->Planar64Vec3{
let camera_mat=camera.simulate_move_rotation(camera.mouse.lerp(&next_mouse,time));
let control_dir=camera_mat*self.get_control_dir(controls);
// local m=sqrt(ControlDir.length_squared())
// local d=dot(Normal,ControlDir)/m
// if d<-LadderDot then
// ControlDir=Up*m
// d=dot(Normal,Up)
// elseif LadderDot<d then
// ControlDir=Up*-m
// d=-dot(Normal,Up)
// end
// return cross(cross(Normal,ControlDir),Normal)/sqrt(1-d*d)
control_dir*self.walk_speed
}
fn get_propulsion_target_velocity(&self,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time)->Planar64Vec3{
let camera_mat=camera.simulate_move_rotation(camera.mouse.lerp(&next_mouse,time));
let control_dir=camera_mat*self.get_control_dir(controls);
control_dir*self.walk_speed
}
}
enum MoveState{
Air,
Walk(WalkState),
Water,
Ladder(WalkState),
}
pub struct PhysicsState{ pub struct PhysicsState{
pub time:Time, pub time:Time,
pub body:Body, body:Body,
pub world:WorldState,//currently there is only one state the world can be in world:WorldState,//currently there is only one state the world can be in
pub game:GameMechanicsState, pub game:GameMechanicsState,
pub style:StyleModifiers, style:StyleModifiers,
pub contacts:std::collections::HashMap::<u32,RelativeCollision>, touching:TouchingState,
pub intersects:std::collections::HashMap::<u32,RelativeCollision>,
//pub intersections: Vec<ModelId>,
//camera must exist in state because wormholes modify the camera, also camera punch //camera must exist in state because wormholes modify the camera, also camera punch
pub camera:PhysicsCamera, camera:PhysicsCamera,
pub next_mouse:MouseState,//Where is the mouse headed next next_mouse:MouseState,//Where is the mouse headed next
pub controls:u32, controls:u32,
pub walk:WalkState, move_state:MoveState,
pub grounded:bool,
//all models //all models
pub models:Vec<ModelPhysics>, models:Vec<ModelPhysics>,
pub bvh:crate::bvh::BvhNode, bvh:crate::bvh::BvhNode,
pub modes:Vec<crate::model::ModeDescription>, modes:Vec<crate::model::ModeDescription>,
pub mode_from_mode_id:std::collections::HashMap::<u32,usize>, mode_from_mode_id:std::collections::HashMap::<u32,usize>,
//the spawn point is where you spawn when you load into the map. //the spawn point is where you spawn when you load into the map.
//This is not the same as Reset which teleports you to Spawn0 //This is not the same as Reset which teleports you to Spawn0
pub spawn_point:Planar64Vec3, pub spawn_point:Planar64Vec3,
@ -395,6 +608,55 @@ impl RelativeCollision {
} }
} }
struct TouchingState{
contacts:std::collections::HashMap::<u32,RelativeCollision>,
intersects:std::collections::HashMap::<u32,RelativeCollision>,
}
impl TouchingState{
fn clear(&mut self){
self.contacts.clear();
self.intersects.clear();
}
fn insert_contact(&mut self,model_id:u32,collision:RelativeCollision)->Option<RelativeCollision>{
self.contacts.insert(model_id,collision)
}
fn remove_contact(&mut self,model_id:u32)->Option<RelativeCollision>{
self.contacts.remove(&model_id)
}
fn insert_intersect(&mut self,model_id:u32,collision:RelativeCollision)->Option<RelativeCollision>{
self.intersects.insert(model_id,collision)
}
fn remove_intersect(&mut self,model_id:u32)->Option<RelativeCollision>{
self.intersects.remove(&model_id)
}
fn constrain_velocity(&self,models:&Vec<ModelPhysics>,velocity:&mut Planar64Vec3){
for (_,contact) in &self.contacts {
let n=contact.normal(models);
let d=velocity.dot(n);
if d<Planar64::ZERO{
(*velocity)-=n*(d/n.dot(n));
}
}
}
fn constrain_acceleration(&self,models:&Vec<ModelPhysics>,acceleration:&mut Planar64Vec3){
for (_,contact) in &self.contacts {
let n=contact.normal(models);
let d=acceleration.dot(n);
if d<Planar64::ZERO{
(*acceleration)-=n*(d/n.dot(n));
}
}
}
}
impl Default for TouchingState{
fn default() -> Self {
Self{
contacts: std::collections::HashMap::new(),
intersects: std::collections::HashMap::new(),
}
}
}
impl Body { impl Body {
pub fn with_pva(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3) -> Self { pub fn with_pva(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3) -> Self {
Self{ Self{
@ -431,12 +693,10 @@ impl Default for PhysicsState{
body: Body::with_pva(Planar64Vec3::int(0,50,0),Planar64Vec3::int(0,0,0),Planar64Vec3::int(0,-100,0)), body: Body::with_pva(Planar64Vec3::int(0,50,0),Planar64Vec3::int(0,0,0),Planar64Vec3::int(0,-100,0)),
time: Time::ZERO, time: Time::ZERO,
style:StyleModifiers::default(), style:StyleModifiers::default(),
grounded: false, touching:TouchingState::default(),
contacts: std::collections::HashMap::new(),
intersects: std::collections::HashMap::new(),
models: Vec::new(), models: Vec::new(),
bvh:crate::bvh::BvhNode::default(), bvh:crate::bvh::BvhNode::default(),
walk: WalkState::new(), move_state: MoveState::Air,
camera: PhysicsCamera::from_offset(Planar64Vec3::int(0,2,0)),//4.5-2.5=2 camera: PhysicsCamera::from_offset(Planar64Vec3::int(0,2,0)),//4.5-2.5=2
next_mouse: MouseState::default(), next_mouse: MouseState::default(),
controls: 0, controls: 0,
@ -452,8 +712,7 @@ impl PhysicsState {
pub fn clear(&mut self){ pub fn clear(&mut self){
self.models.clear(); self.models.clear();
self.modes.clear(); self.modes.clear();
self.contacts.clear(); self.touching.clear();
self.intersects.clear();
} }
pub fn into_worker(mut self)->crate::worker::CompatWorker<TimedInstruction<InputInstruction>,PhysicsOutputState,Box<dyn FnMut(TimedInstruction<InputInstruction>)->PhysicsOutputState>>{ pub fn into_worker(mut self)->crate::worker::CompatWorker<TimedInstruction<InputInstruction>,PhysicsOutputState,Box<dyn FnMut(TimedInstruction<InputInstruction>)->PhysicsOutputState>>{
@ -653,33 +912,19 @@ impl PhysicsState {
self.controls=if state{self.controls|control}else{self.controls&!control}; self.controls=if state{self.controls|control}else{self.controls&!control};
} }
fn jump(&mut self){ fn jump(&mut self){
self.grounded=false;//do I need this? match &self.move_state{
let mut v=self.body.velocity+self.style.get_jump_power(); MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>{
self.contact_constrain_velocity(&mut v); let mut v=self.body.velocity+walk_state.normal*self.style.get_jump_deltav();
self.touching.constrain_velocity(&self.models,&mut v);
self.body.velocity=v; self.body.velocity=v;
},
MoveState::Air|MoveState::Water=>(),
}
} }
fn contact_constrain_velocity(&self,velocity:&mut Planar64Vec3){
for (_,contact) in &self.contacts {
let n=contact.normal(&self.models);
let d=velocity.dot(n);
if d<Planar64::ZERO{
(*velocity)-=n*(d/n.dot(n));
}
}
}
fn contact_constrain_acceleration(&self,acceleration:&mut Planar64Vec3){
for (_,contact) in &self.contacts {
let n=contact.normal(&self.models);
let d=acceleration.dot(n);
if d<Planar64::ZERO{
(*acceleration)-=n*(d/n.dot(n));
}
}
}
fn next_strafe_instruction(&self) -> Option<TimedInstruction<PhysicsInstruction>> { fn next_strafe_instruction(&self) -> Option<TimedInstruction<PhysicsInstruction>> {
return Some(TimedInstruction{ return Some(TimedInstruction{
time:Time::from_nanos(self.style.strafe_tick_rate.rhs_div_int(self.style.strafe_tick_rate.mul_int(self.time.nanos())+1)), time:Time::from_nanos(self.style.strafe_tick_rate.rhs_div_int(self.style.strafe_tick_rate.mul_int(self.time.nanos()+1)+1)),
//only poll the physics if there is a before and after mouse event //only poll the physics if there is a before and after mouse event
instruction:PhysicsInstruction::StrafeTick instruction:PhysicsInstruction::StrafeTick
}); });
@ -717,44 +962,30 @@ impl PhysicsState {
// } // }
fn refresh_walk_target(&mut self){ fn refresh_walk_target(&mut self){
//calculate acceleration yada yada match &mut self.move_state{
if self.grounded{ MoveState::Air|MoveState::Water=>(),
let mut v=self.walk.target_velocity; MoveState::Walk(WalkState{normal,state})=>{
self.contact_constrain_velocity(&mut v); let n=normal;
let mut target_diff=v-self.body.velocity; (*state,self.body.acceleration)=WalkEnum::with_target_velocity(&self.touching,&self.body,&self.style,&self.models,self.style.get_walk_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time),&n);
//remove normal component },
target_diff-=Planar64Vec3::Y*target_diff.y(); MoveState::Ladder(WalkState{normal,state})=>{
if target_diff==Planar64Vec3::ZERO{ let n=normal;
let mut a=Planar64Vec3::ZERO; (*state,self.body.acceleration)=WalkEnum::with_target_velocity(&self.touching,&self.body,&self.style,&self.models,self.style.get_ladder_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time),&n);
self.contact_constrain_acceleration(&mut a); },
self.body.acceleration=a;
self.walk.state=WalkEnum::Reached;
}else{
//normal friction acceleration is clippedAcceleration.dot(normal)*friction
let accel=self.style.walk_accel.min(self.style.gravity.dot(Planar64Vec3::NEG_Y)*self.style.friction);
let time_delta=target_diff.length()/accel;
let mut a=target_diff.with_length(accel);
self.contact_constrain_acceleration(&mut a);
self.body.acceleration=a;
self.walk.target_time=self.body.time+Time::from(time_delta);
self.walk.state=WalkEnum::Transient;
}
}else{
self.walk.state=WalkEnum::Reached;//there is no walk target while not grounded
} }
} }
fn next_walk_instruction(&self) -> Option<TimedInstruction<PhysicsInstruction>> { fn next_move_instruction(&self)->Option<TimedInstruction<PhysicsInstruction>>{
//check if you have a valid walk state and create an instruction //check if you have a valid walk state and create an instruction
if self.grounded{ match &self.move_state{
match self.walk.state{ MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.state{
WalkEnum::Transient=>Some(TimedInstruction{ WalkEnum::Transient(walk_target)=>Some(TimedInstruction{
time:self.walk.target_time, time:walk_target.time,
instruction:PhysicsInstruction::ReachWalkTargetVelocity instruction:PhysicsInstruction::ReachWalkTargetVelocity
}), }),
WalkEnum::Reached=>None, WalkEnum::Reached=>None,
} }
}else{ MoveState::Air=>self.next_strafe_instruction(),
return None; MoveState::Water=>None,//TODO
} }
} }
fn mesh(&self) -> TreyMesh { fn mesh(&self) -> TreyMesh {
@ -1044,7 +1275,7 @@ impl crate::instruction::InstructionEmitter<PhysicsInstruction> for PhysicsState
//JUST POLLING!!! NO MUTATION //JUST POLLING!!! NO MUTATION
let mut collector = crate::instruction::InstructionCollector::new(time_limit); let mut collector = crate::instruction::InstructionCollector::new(time_limit);
//check for collision stop instructions with curent contacts //check for collision stop instructions with curent contacts
for (_,collision_data) in &self.contacts { for (_,collision_data) in &self.touching.contacts {
collector.collect(self.predict_collision_end(self.time,time_limit,collision_data)); collector.collect(self.predict_collision_end(self.time,time_limit,collision_data));
} }
// for collision_data in &self.intersects{ // for collision_data in &self.intersects{
@ -1056,17 +1287,11 @@ impl crate::instruction::InstructionEmitter<PhysicsInstruction> for PhysicsState
aabb.grow(self.body.extrapolated_position(time_limit)); aabb.grow(self.body.extrapolated_position(time_limit));
aabb.inflate(self.style.hitbox_halfsize); aabb.inflate(self.style.hitbox_halfsize);
self.bvh.the_tester(&aabb,&mut |id|{ self.bvh.the_tester(&aabb,&mut |id|{
if !(self.contacts.contains_key(&id)||self.intersects.contains_key(&id)){ if !(self.touching.contacts.contains_key(&id)||self.touching.intersects.contains_key(&id)){
collector.collect(self.predict_collision_start(self.time,time_limit,id)); collector.collect(self.predict_collision_start(self.time,time_limit,id));
} }
}); });
if self.grounded { collector.collect(self.next_move_instruction());
//walk maintenance
collector.collect(self.next_walk_instruction());
}else{
//check to see when the next strafe tick is
collector.collect(self.next_strafe_instruction());
}
collector.instruction() collector.instruction()
} }
} }
@ -1094,24 +1319,42 @@ impl crate::instruction::InstructionConsumer<PhysicsInstruction> for PhysicsStat
let model=c.model(&self.models).unwrap(); let model=c.model(&self.models).unwrap();
match &model.attributes{ match &model.attributes{
PhysicsCollisionAttributes::Contact{contacting,general}=>{ PhysicsCollisionAttributes::Contact{contacting,general}=>{
match &contacting.surf{ let mut v=self.body.velocity;
Some(surf)=>println!("I'm surfing!"), match &contacting.contact_behaviour{
Some(crate::model::ContactingBehaviour::Surf)=>println!("I'm surfing!"),
&Some(crate::model::ContactingBehaviour::Elastic(elasticity))=>{
let n=c.normal(&self.models);
let d=n.dot(v)*Planar64::raw(-1-elasticity as i64);
v-=n*(d/n.dot(n));
},
Some(crate::model::ContactingBehaviour::Ladder(contacting_ladder))=>{
if contacting_ladder.sticky{
//kill v
v=Planar64Vec3::ZERO;//model.velocity
}
//ladder walkstate
let (walk_state,a)=WalkState::ladder(&self.touching,&self.body,&self.style,&self.models,self.style.get_ladder_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time),&c.normal(&self.models));
self.move_state=MoveState::Ladder(walk_state);
self.body.acceleration=a;
}
None=>match &c.face { None=>match &c.face {
TreyMeshFace::Top => { TreyMeshFace::Top => {
//ground //ground
self.grounded=true; let (walk_state,a)=WalkState::ground(&self.touching,&self.body,&self.style,&self.models,self.style.get_walk_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time));
self.move_state=MoveState::Walk(walk_state);
self.body.acceleration=a;
}, },
_ => (), _ => (),
}, },
} }
//check ground //check ground
self.contacts.insert(c.model,c); self.touching.insert_contact(c.model,c);
match &general.teleport_behaviour{ match &general.teleport_behaviour{
Some(crate::model::TeleportBehaviour::StageElement(stage_element))=>{ Some(crate::model::TeleportBehaviour::StageElement(stage_element))=>{
if stage_element.force||self.game.stage_id<stage_element.stage_id{ if stage_element.force||self.game.stage_id<stage_element.stage_id{
self.game.stage_id=stage_element.stage_id; self.game.stage_id=stage_element.stage_id;
} }
match stage_element.behaviour{ match &stage_element.behaviour{
crate::model::StageElementBehaviour::SpawnAt=>(), crate::model::StageElementBehaviour::SpawnAt=>(),
crate::model::StageElementBehaviour::Trigger crate::model::StageElementBehaviour::Trigger
|crate::model::StageElementBehaviour::Teleport=>{ |crate::model::StageElementBehaviour::Teleport=>{
@ -1121,16 +1364,15 @@ impl crate::instruction::InstructionConsumer<PhysicsInstruction> for PhysicsStat
if let Some(model)=self.models.get(spawn as usize){ if let Some(model)=self.models.get(spawn as usize){
self.body.position=model.transform.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(self.style.hitbox_halfsize.y()+Planar64::ONE/16); self.body.position=model.transform.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(self.style.hitbox_halfsize.y()+Planar64::ONE/16);
//manual clear //for c in self.contacts{process_instruction(CollisionEnd(c))} //manual clear //for c in self.contacts{process_instruction(CollisionEnd(c))}
self.contacts.clear(); self.touching.clear();
self.intersects.clear();
self.body.acceleration=self.style.gravity; self.body.acceleration=self.style.gravity;
self.walk.state=WalkEnum::Reached; self.move_state=MoveState::Air;//TODO: calculate contacts and determine the actual state
self.grounded=false;
}else{println!("bad1");} }else{println!("bad1");}
}else{println!("bad2");} }else{println!("bad2");}
}else{println!("bad3");} }else{println!("bad3");}
}, },
crate::model::StageElementBehaviour::Platform=>(), crate::model::StageElementBehaviour::Platform=>(),
crate::model::StageElementBehaviour::JumpLimit(_)=>(),//TODO
} }
}, },
Some(crate::model::TeleportBehaviour::Wormhole(wormhole))=>{ Some(crate::model::TeleportBehaviour::Wormhole(wormhole))=>{
@ -1139,30 +1381,59 @@ impl crate::instruction::InstructionConsumer<PhysicsInstruction> for PhysicsStat
None=>(), None=>(),
} }
//flatten v //flatten v
let mut v=self.body.velocity; self.touching.constrain_velocity(&self.models,&mut v);
self.contact_constrain_velocity(&mut v);
match &general.booster{ match &general.booster{
Some(booster)=>{ Some(booster)=>{
v+=booster.velocity; match booster{
self.contact_constrain_velocity(&mut v); &crate::model::GameMechanicBooster::Affine(transform)=>v=transform.transform_point3(v),
&crate::model::GameMechanicBooster::Velocity(velocity)=>v+=velocity,
&crate::model::GameMechanicBooster::Energy{direction,energy}=>todo!(),
}
self.touching.constrain_velocity(&self.models,&mut v);
},
None=>(),
}
match &general.trajectory{
Some(trajectory)=>{
println!("??? {:?}",trajectory);
match trajectory{
&crate::model::GameMechanicSetTrajectory::Height(height)=>{
//vg=sqrt(-2*gg*height)
println!("height booster h={}",height);
let vg=v.dot(self.style.gravity);
let gg=self.style.gravity.dot(self.style.gravity);
let hb=(gg.sqrt()*height*2).sqrt()*gg.sqrt();
println!("hb={} vg={}",hb,vg);
let b=self.style.gravity*((-hb-vg)/gg);
println!("bopo {}",b);
v+=b;
},
&crate::model::GameMechanicSetTrajectory::Velocity(velocity)=>v=velocity,
crate::model::GameMechanicSetTrajectory::AirTime(_)
|crate::model::GameMechanicSetTrajectory::TargetPointTime{target_point:_,time:_}
|crate::model::GameMechanicSetTrajectory::TrajectoryTargetPoint{target_point:_,speed:_,trajectory_choice:_}
|crate::model::GameMechanicSetTrajectory::DotVelocity{direction:_,dot:_}
=>(),
}
self.touching.constrain_velocity(&self.models,&mut v);
}, },
None=>(), None=>(),
} }
self.body.velocity=v; self.body.velocity=v;
if self.grounded&&self.style.get_control(StyleModifiers::CONTROL_JUMP,self.controls){ if self.style.get_control(StyleModifiers::CONTROL_JUMP,self.controls){
self.jump(); self.jump();
} }
self.refresh_walk_target(); self.refresh_walk_target();
}, },
PhysicsCollisionAttributes::Intersect{intersecting,general}=>{ PhysicsCollisionAttributes::Intersect{intersecting,general}=>{
//I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop //I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop
self.intersects.insert(c.model,c); self.touching.insert_intersect(c.model,c);
match &general.teleport_behaviour{ match &general.teleport_behaviour{
Some(crate::model::TeleportBehaviour::StageElement(stage_element))=>{ Some(crate::model::TeleportBehaviour::StageElement(stage_element))=>{
if stage_element.force||self.game.stage_id<stage_element.stage_id{ if stage_element.force||self.game.stage_id<stage_element.stage_id{
self.game.stage_id=stage_element.stage_id; self.game.stage_id=stage_element.stage_id;
} }
match stage_element.behaviour{ match &stage_element.behaviour{
crate::model::StageElementBehaviour::SpawnAt=>(), crate::model::StageElementBehaviour::SpawnAt=>(),
crate::model::StageElementBehaviour::Trigger crate::model::StageElementBehaviour::Trigger
|crate::model::StageElementBehaviour::Teleport=>{ |crate::model::StageElementBehaviour::Teleport=>{
@ -1172,16 +1443,15 @@ impl crate::instruction::InstructionConsumer<PhysicsInstruction> for PhysicsStat
if let Some(model)=self.models.get(spawn as usize){ if let Some(model)=self.models.get(spawn as usize){
self.body.position=model.transform.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(self.style.hitbox_halfsize.y()+Planar64::ONE/16); self.body.position=model.transform.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(self.style.hitbox_halfsize.y()+Planar64::ONE/16);
//manual clear //for c in self.contacts{process_instruction(CollisionEnd(c))} //manual clear //for c in self.contacts{process_instruction(CollisionEnd(c))}
self.contacts.clear(); self.touching.clear();
self.intersects.clear();
self.body.acceleration=self.style.gravity; self.body.acceleration=self.style.gravity;
self.walk.state=WalkEnum::Reached; self.move_state=MoveState::Air;//TODO: calculate contacts and determine the actual state
self.grounded=false;
}else{println!("bad1");} }else{println!("bad1");}
}else{println!("bad2");} }else{println!("bad2");}
}else{println!("bad3");} }else{println!("bad3");}
}, },
crate::model::StageElementBehaviour::Platform=>(), crate::model::StageElementBehaviour::Platform=>(),
crate::model::StageElementBehaviour::JumpLimit(_)=>(),//TODO
} }
}, },
Some(crate::model::TeleportBehaviour::Wormhole(wormhole))=>{ Some(crate::model::TeleportBehaviour::Wormhole(wormhole))=>{
@ -1196,21 +1466,22 @@ impl crate::instruction::InstructionConsumer<PhysicsInstruction> for PhysicsStat
let model=c.model(&self.models).unwrap(); let model=c.model(&self.models).unwrap();
match &model.attributes{ match &model.attributes{
PhysicsCollisionAttributes::Contact{contacting,general}=>{ PhysicsCollisionAttributes::Contact{contacting,general}=>{
self.contacts.remove(&c.model);//remove contact before calling contact_constrain_acceleration self.touching.remove_contact(c.model);//remove contact before calling contact_constrain_acceleration
let mut a=self.style.gravity; let mut a=self.style.gravity;
self.contact_constrain_acceleration(&mut a); self.touching.constrain_acceleration(&self.models,&mut a);
self.body.acceleration=a; self.body.acceleration=a;
//check ground //check ground
//self.touching.get_move_state();
match &c.face { match &c.face {
TreyMeshFace::Top => { TreyMeshFace::Top => {
self.grounded=false; //TODO: make this more advanced checking contacts
self.move_state=MoveState::Air;
}, },
_ => (), _=>self.refresh_walk_target(),
} }
self.refresh_walk_target();
}, },
PhysicsCollisionAttributes::Intersect{intersecting,general}=>{ PhysicsCollisionAttributes::Intersect{intersecting,general}=>{
self.intersects.remove(&c.model); self.touching.remove_intersect(c.model);
}, },
} }
}, },
@ -1220,23 +1491,32 @@ impl crate::instruction::InstructionConsumer<PhysicsInstruction> for PhysicsStat
let d=self.body.velocity.dot(control_dir); let d=self.body.velocity.dot(control_dir);
if d<self.style.mv { if d<self.style.mv {
let mut v=self.body.velocity+control_dir*(self.style.mv-d); let mut v=self.body.velocity+control_dir*(self.style.mv-d);
self.contact_constrain_velocity(&mut v); self.touching.constrain_velocity(&self.models,&mut v);
self.body.velocity=v; self.body.velocity=v;
} }
} }
PhysicsInstruction::ReachWalkTargetVelocity => { PhysicsInstruction::ReachWalkTargetVelocity => {
match &mut self.move_state{
MoveState::Air|MoveState::Water=>(),
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>{
match &mut walk_state.state{
WalkEnum::Reached=>(),
WalkEnum::Transient(walk_target)=>{
//precisely set velocity //precisely set velocity
let mut a=Planar64Vec3::ZERO; let mut a=self.style.gravity;
self.contact_constrain_acceleration(&mut a); self.touching.constrain_acceleration(&self.models,&mut a);
self.body.acceleration=a; self.body.acceleration=a;
let mut v=self.walk.target_velocity; let mut v=walk_target.velocity;
self.contact_constrain_velocity(&mut v); self.touching.constrain_velocity(&self.models,&mut v);
self.body.velocity=v; self.body.velocity=v;
self.walk.state=WalkEnum::Reached; walk_state.state=WalkEnum::Reached;
},
}
}
}
}, },
PhysicsInstruction::Input(input_instruction) => { PhysicsInstruction::Input(input_instruction) => {
let mut refresh_walk_target=true; let mut refresh_walk_target=true;
let mut refresh_walk_target_velocity=true;
match input_instruction{ match input_instruction{
PhysicsInputInstruction::SetNextMouse(m) => { PhysicsInputInstruction::SetNextMouse(m) => {
self.camera.move_mouse(self.next_mouse.pos); self.camera.move_mouse(self.next_mouse.pos);
@ -1254,10 +1534,8 @@ impl crate::instruction::InstructionConsumer<PhysicsInstruction> for PhysicsStat
PhysicsInputInstruction::SetMoveDown(s) => self.set_control(StyleModifiers::CONTROL_MOVEDOWN,s), PhysicsInputInstruction::SetMoveDown(s) => self.set_control(StyleModifiers::CONTROL_MOVEDOWN,s),
PhysicsInputInstruction::SetJump(s) => { PhysicsInputInstruction::SetJump(s) => {
self.set_control(StyleModifiers::CONTROL_JUMP,s); self.set_control(StyleModifiers::CONTROL_JUMP,s);
if self.grounded{
self.jump(); self.jump();
} refresh_walk_target=false;
refresh_walk_target_velocity=false;
}, },
PhysicsInputInstruction::SetZoom(s) => { PhysicsInputInstruction::SetZoom(s) => {
self.set_control(StyleModifiers::CONTROL_ZOOM,s); self.set_control(StyleModifiers::CONTROL_ZOOM,s);
@ -1268,21 +1546,14 @@ impl crate::instruction::InstructionConsumer<PhysicsInstruction> for PhysicsStat
self.body.position=self.spawn_point; self.body.position=self.spawn_point;
self.body.velocity=Planar64Vec3::ZERO; self.body.velocity=Planar64Vec3::ZERO;
//manual clear //for c in self.contacts{process_instruction(CollisionEnd(c))} //manual clear //for c in self.contacts{process_instruction(CollisionEnd(c))}
self.contacts.clear(); self.touching.clear();
self.body.acceleration=self.style.gravity; self.body.acceleration=self.style.gravity;
self.walk.state=WalkEnum::Reached; self.move_state=MoveState::Air;
self.grounded=false;
refresh_walk_target=false; refresh_walk_target=false;
}, },
PhysicsInputInstruction::Idle => {refresh_walk_target=false;},//literally idle! PhysicsInputInstruction::Idle => {refresh_walk_target=false;},//literally idle!
} }
if refresh_walk_target{ if refresh_walk_target{
//calculate walk target velocity
if refresh_walk_target_velocity{
let camera_mat=self.camera.simulate_move_rotation_y(self.camera.mouse.lerp(&self.next_mouse,self.time).x);
let control_dir=camera_mat*self.style.get_control_dir(self.controls);
self.walk.target_velocity=control_dir*self.style.walkspeed;
}
self.refresh_walk_target(); self.refresh_walk_target();
} }
}, },