From 5cedf91709253e8d505e90825cd5ec6de1623440 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Thu, 30 Nov 2023 01:51:17 -0800 Subject: [PATCH] =?UTF-8?q?Face=20Crawler=E2=84=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/aabb.rs | 53 +- src/face_crawler.rs | 121 ++++ src/load_roblox.rs | 22 +- src/main.rs | 2 + src/model.rs | 118 ++-- src/model_physics.rs | 739 ++++++++++++++++++++- src/physics.rs | 1495 ++++++++++++++++++++++++++---------------- 7 files changed, 1869 insertions(+), 681 deletions(-) create mode 100644 src/face_crawler.rs diff --git a/src/aabb.rs b/src/aabb.rs index 14e78442..65e783f4 100644 --- a/src/aabb.rs +++ b/src/aabb.rs @@ -1,18 +1,9 @@ use crate::integer::Planar64Vec3; -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -pub enum AabbFace{ - Right,//+X - Top, - Back, - Left, - Bottom, - Front, -} #[derive(Clone)] pub struct Aabb{ - pub min:Planar64Vec3, - pub max:Planar64Vec3, + min:Planar64Vec3, + max:Planar64Vec3, } impl Default for Aabb { @@ -22,17 +13,6 @@ impl Default for Aabb { } impl Aabb{ - const VERTEX_DATA:[Planar64Vec3;8]=[ - Planar64Vec3::int( 1,-1,-1), - Planar64Vec3::int( 1, 1,-1), - Planar64Vec3::int( 1, 1, 1), - Planar64Vec3::int( 1,-1, 1), - Planar64Vec3::int(-1,-1, 1), - Planar64Vec3::int(-1, 1, 1), - Planar64Vec3::int(-1, 1,-1), - Planar64Vec3::int(-1,-1,-1), - ]; - pub fn grow(&mut self,point:Planar64Vec3){ self.min=self.min.min(point); self.max=self.max.max(point); @@ -48,34 +28,11 @@ impl Aabb{ pub fn intersects(&self,aabb:&Aabb)->bool{ (self.min.cmplt(aabb.max)&aabb.min.cmplt(self.max)).all() } - pub fn normal(face:AabbFace)->Planar64Vec3{ - match face { - AabbFace::Right=>Planar64Vec3::int(1,0,0), - AabbFace::Top=>Planar64Vec3::int(0,1,0), - AabbFace::Back=>Planar64Vec3::int(0,0,1), - AabbFace::Left=>Planar64Vec3::int(-1,0,0), - AabbFace::Bottom=>Planar64Vec3::int(0,-1,0), - AabbFace::Front=>Planar64Vec3::int(0,0,-1), - } + pub fn size(&self)->Planar64Vec3{ + self.max-self.min } - pub fn unit_vertices()->[Planar64Vec3;8] { - return Self::VERTEX_DATA; - } - // pub fn face(&self,face:AabbFace)->Aabb { - // let mut aabb=self.clone(); - // //in this implementation face = worldspace aabb face - // match face { - // AabbFace::Right => aabb.min.x=aabb.max.x, - // AabbFace::Top => aabb.min.y=aabb.max.y, - // AabbFace::Back => aabb.min.z=aabb.max.z, - // AabbFace::Left => aabb.max.x=aabb.min.x, - // AabbFace::Bottom => aabb.max.y=aabb.min.y, - // AabbFace::Front => aabb.max.z=aabb.min.z, - // } - // return aabb; - // } pub fn center(&self)->Planar64Vec3{ - return self.min.midpoint(self.max) + self.min.midpoint(self.max) } //probably use floats for area & volume because we don't care about precision // pub fn area_weight(&self)->f32{ diff --git a/src/face_crawler.rs b/src/face_crawler.rs new file mode 100644 index 00000000..86dee9c9 --- /dev/null +++ b/src/face_crawler.rs @@ -0,0 +1,121 @@ +use crate::physics::Body; +use crate::model_physics::{FEV,MeshQuery,DirectedEdge}; +use crate::integer::{Time,Planar64}; +use crate::zeroes::zeroes2; + +enum Transition{ + Miss, + Next(FEV,Time), + Hit(F,Time), +} + + fn next_transition(fev:&FEV,time:Time,mesh:&impl MeshQuery,body:&Body,time_limit:Time)->Transition{ + //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. + let mut best_time=time_limit; + let mut best_transtition=Transition::Miss; + match fev{ + &FEV::::Face(face_id)=>{ + //test own face collision time, ignoring roots with zero or conflicting derivative + //n=face.normal d=face.dot + //n.a t^2+n.v t+n.p-d==0 + let (n,d)=mesh.face_nd(face_id); + for t in zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ + let t=body.time+Time::from(t); + if time<=t&&t::Edge(directed_edge_id.as_undirected()),t); + break; + } + } + } + //if none: + }, + &FEV::::Edge(edge_id)=>{ + //test each face collision time, ignoring roots with zero or conflicting derivative + let edge_n=mesh.edge_n(edge_id); + let edge_verts=mesh.edge_verts(edge_id); + let vert_sum=mesh.vert(edge_verts[0])+mesh.vert(edge_verts[1]); + for (i,&edge_face_id) in mesh.edge_faces(edge_id).iter().enumerate(){ + let face_n=mesh.face_nd(edge_face_id).0; + //edge_n gets parity from the order of edge_faces + let n=face_n.cross(edge_n)*((i as i64)*2-1); + let d=n.dot(vert_sum); + //WARNING yada yada d *2 + for t in zeroes2((n.dot(body.position))*2-d,n.dot(body.velocity)*2,n.dot(body.acceleration)){ + let t=body.time+Time::from(t); + if time<=t&&t::Face(edge_face_id),t); + break; + } + } + } + //test each vertex collision time, ignoring roots with zero or conflicting derivative + for (i,&vert_id) in edge_verts.iter().enumerate(){ + //vertex normal gets parity from vert index + let n=edge_n*(1-2*(i as i64)); + let d=n.dot(mesh.vert(vert_id)); + for t in zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ + let t=body.time+Time::from(t); + if time<=t&&t::Vert(vert_id),t); + break; + } + } + } + //if none: + }, + &FEV::::Vert(vert_id)=>{ + //test each edge collision time, ignoring roots with zero or conflicting derivative + 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 + let n=-mesh.directed_edge_n(directed_edge_id); + let d=n.dot(mesh.vert(vert_id)); + for t in zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ + let t=body.time+Time::from(t); + if time<=t&&t::Edge(directed_edge_id.as_undirected()),t); + break; + } + } + } + //if none: + }, + } + best_transtition + } +pub enum CrawlResult{ + Miss(FEV), + Hit(F,Time), +} +pub fn crawl_fev(mut fev:FEV,mesh:&impl MeshQuery,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult{ + let mut time=start_time; + for _ in 0..20{ + match next_transition(&fev,time,mesh,relative_body,time_limit){ + Transition::Miss=>return CrawlResult::Miss(fev), + Transition::Next(next_fev,next_time)=>(fev,time)=(next_fev,next_time), + Transition::Hit(face,time)=>return CrawlResult::Hit(face,time), + } + } + //TODO: fix all bugs + println!("Too many iterations! Using default behaviour instead of crashing..."); + CrawlResult::Miss(fev) +} diff --git a/src/load_roblox.rs b/src/load_roblox.rs index 06f058c2..ec5aba02 100644 --- a/src/load_roblox.rs +++ b/src/load_roblox.rs @@ -45,14 +45,19 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,force_interse "Water"=>{ force_can_collide=false; //TODO: read stupid CustomPhysicalProperties - intersecting.water=Some(crate::model::IntersectingWater{density:Planar64::ONE,viscosity:Planar64::ONE/10,current:velocity}); + intersecting.water=Some(crate::model::IntersectingWater{density:Planar64::ONE,viscosity:Planar64::ONE/10,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}); }, - "UnorderedCheckpoint"=>general.checkpoint=Some(crate::model::GameMechanicCheckpoint::Unordered{mode_id:0}), + // "UnorderedCheckpoint"=>general.teleport_behaviour=Some(crate::model::TeleportBehaviour::StageElement(crate::model::GameMechanicStageElement{ + // mode_id:0, + // stage_id:0, + // force:false, + // behaviour:crate::model::StageElementBehaviour::Unordered + // })), "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})}, "MapAnticheat"=>{force_can_collide=false;general.zone=Some(crate::model::GameMechanicZone{mode_id:0,behaviour:crate::model::ZoneBehaviour::Anitcheat})}, @@ -111,13 +116,14 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,force_interse "WormholeIn"=>general.teleport_behaviour=Some(crate::model::TeleportBehaviour::Wormhole(crate::model::GameMechanicWormhole{destination_model_id:captures[2].parse::().unwrap()})), _=>panic!("regex3[1] messed up bad"), } - }else if let Some(captures)=lazy_regex::regex!(r"^(OrderedCheckpoint)(\d+)$") - .captures(other){ - match &captures[1]{ - "OrderedCheckpoint"=>general.checkpoint=Some(crate::model::GameMechanicCheckpoint::Ordered{mode_id:0,checkpoint_id:captures[2].parse::().unwrap()}), - _=>panic!("regex3[1] messed up bad"), - } } + // else if let Some(captures)=lazy_regex::regex!(r"^(OrderedCheckpoint)(\d+)$") + // .captures(other){ + // match &captures[1]{ + // "OrderedCheckpoint"=>general.checkpoint=Some(crate::model::GameMechanicCheckpoint::Ordered{mode_id:0,checkpoint_id:captures[2].parse::().unwrap()}), + // _=>panic!("regex3[1] messed up bad"), + // } + // } } } //need some way to skip this diff --git a/src/main.rs b/src/main.rs index 1220d33e..a942459d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,9 @@ mod settings; mod primitives; mod instruction; mod load_roblox; +mod face_crawler; mod compat_worker; +mod model_physics; mod model_graphics; mod physics_worker; mod graphics_worker; diff --git a/src/model.rs b/src/model.rs index 00ed3b0f..7ab29562 100644 --- a/src/model.rs +++ b/src/model.rs @@ -83,73 +83,78 @@ pub enum TempIndexedAttributes{ } //you have this effect while in contact -#[derive(Clone)] +#[derive(Clone,Hash,Eq,PartialEq)] pub struct ContactingLadder{ pub sticky:bool } -#[derive(Clone)] +#[derive(Clone,Hash,Eq,PartialEq)] pub enum ContactingBehaviour{ - Surf, - Ladder(ContactingLadder), - Elastic(u32),//[1/2^32,1] 0=None (elasticity+1)/2^32 + Surf, + Cling,//usable as a zipline, or other weird and wonderful things + Ladder(ContactingLadder), + Elastic(u32),//[1/2^32,1] 0=None (elasticity+1)/2^32 } //you have this effect while intersecting -#[derive(Clone)] +#[derive(Clone,Hash,Eq,PartialEq)] pub struct IntersectingWater{ pub viscosity:Planar64, pub density:Planar64, - pub current:Planar64Vec3, + pub velocity:Planar64Vec3, } //All models can be given these attributes -#[derive(Clone)] +#[derive(Clone,Hash,Eq,PartialEq)] pub struct GameMechanicAccelerator{ pub acceleration:Planar64Vec3 } -#[derive(Clone)] +#[derive(Clone,Hash,Eq,PartialEq)] pub enum GameMechanicBooster{ 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)] -pub enum GameMechanicCheckpoint{ - Ordered{ - mode_id:u32, - checkpoint_id:u32, - }, - Unordered{ - mode_id:u32, - }, -} -#[derive(Clone)] +#[derive(Clone,Hash,Eq,PartialEq)] 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)] +#[derive(Clone,Hash,Eq,PartialEq)] pub enum GameMechanicSetTrajectory{ + //Speed-type SetTrajectory 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 + DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions + //Velocity-type SetTrajectory 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 + TargetPointSpeed{//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)] +impl GameMechanicSetTrajectory{ + fn is_velocity(&self)->bool{ + match self{ + GameMechanicSetTrajectory::AirTime(_) + |GameMechanicSetTrajectory::Height(_) + |GameMechanicSetTrajectory::DotVelocity{direction:_,dot:_}=>false, + GameMechanicSetTrajectory::TargetPointTime{target_point:_,time:_} + |GameMechanicSetTrajectory::TargetPointSpeed{target_point:_,speed:_,trajectory_choice:_} + |GameMechanicSetTrajectory::Velocity(_)=>true, + } + } +} +#[derive(Clone,Hash,Eq,PartialEq)] pub enum ZoneBehaviour{ //Start is indexed //Checkpoints are indexed Finish, Anitcheat, } -#[derive(Clone)] +#[derive(Clone,Hash,Eq,PartialEq)] pub struct GameMechanicZone{ pub mode_id:u32, pub behaviour:ZoneBehaviour, @@ -160,31 +165,36 @@ pub struct GameMechanicZone{ // InRange(Planar64,Planar64), // OutsideRange(Planar64,Planar64), // } -#[derive(Clone)] +#[derive(Clone,Hash,Eq,PartialEq)] pub enum StageElementBehaviour{ - //Spawn,//The behaviour of stepping on a spawn setting the spawnid - SpawnAt, - Trigger, - Teleport, - Platform, - //Acts like a trigger if you haven't hit all the checkpoints. - Checkpoint{ - //if this is 2 you must have hit OrderedCheckpoint(0) OrderedCheckpoint(1) OrderedCheckpoint(2) to pass - ordered_checkpoint_id:Option, - //if this is 2 you must have hit at least 2 UnorderedCheckpoints to pass - unordered_checkpoint_count:u32, - }, - JumpLimit(u32), - //Speedtrap(TrapCondition),//Acts as a trigger with a speed condition + //Spawn,//The behaviour of stepping on a spawn setting the spawnid + SpawnAt,//must be standing on top to get effect. except cancollide false + Trigger, + Teleport, + Platform, + //Checkpoint acts like a trigger if you haven't hit all the checkpoints yet. + //Note that all stage elements act like this for the next stage. + Checkpoint, + //OrderedCheckpoint. You must pass through all of these in ascending order. + //If you hit them out of order it acts like a trigger. + //Do not support backtracking at all for now. + Ordered{ + checkpoint_id:u32, + }, + //UnorderedCheckpoint. You must pass through all of these in any order. + Unordered, + //If you get reset by a jump limit + JumpLimit(u32), + //Speedtrap(TrapCondition),//Acts as a trigger with a speed condition } -#[derive(Clone)] +#[derive(Clone,Hash,Eq,PartialEq)] pub struct GameMechanicStageElement{ pub mode_id:u32, pub stage_id:u32,//which spawn to send to pub force:bool,//allow setting to lower spawn id i.e. 7->3 pub behaviour:StageElementBehaviour } -#[derive(Clone)] +#[derive(Clone,Hash,Eq,PartialEq)] pub struct GameMechanicWormhole{ //destination does not need to be another wormhole //this defines a one way portal to a destination model transform @@ -192,17 +202,16 @@ pub struct GameMechanicWormhole{ pub destination_model_id:u32, //(position,angles)*=origin.transform.inverse()*destination.transform } -#[derive(Clone)] +#[derive(Clone,Hash,Eq,PartialEq)] pub enum TeleportBehaviour{ StageElement(GameMechanicStageElement), Wormhole(GameMechanicWormhole), } //attributes listed in order of handling -#[derive(Default,Clone)] +#[derive(Default,Clone,Hash,Eq,PartialEq)] pub struct GameMechanicAttributes{ pub zone:Option, pub booster:Option, - pub checkpoint:Option, pub trajectory:Option, pub teleport_behaviour:Option, pub accelerator:Option, @@ -211,13 +220,26 @@ impl GameMechanicAttributes{ pub fn any(&self)->bool{ self.zone.is_some() ||self.booster.is_some() - ||self.checkpoint.is_some() ||self.trajectory.is_some() ||self.teleport_behaviour.is_some() ||self.accelerator.is_some() } + pub fn is_wrcp(&self,current_mode_id:u32)->bool{ + self.trajectory.as_ref().map_or(false,|t|t.is_velocity()) + &&match &self.teleport_behaviour{ + Some(TeleportBehaviour::StageElement( + GameMechanicStageElement{ + mode_id, + stage_id:_, + force:true, + behaviour:StageElementBehaviour::Trigger|StageElementBehaviour::Teleport + } + ))=>current_mode_id==*mode_id, + _=>false, + } + } } -#[derive(Default,Clone)] +#[derive(Default,Clone,Hash,Eq,PartialEq)] pub struct ContactingAttributes{ //friction? pub contact_behaviour:Option, @@ -227,7 +249,7 @@ impl ContactingAttributes{ self.contact_behaviour.is_some() } } -#[derive(Default,Clone)] +#[derive(Default,Clone,Hash,Eq,PartialEq)] pub struct IntersectingAttributes{ pub water:Option, } diff --git a/src/model_physics.rs b/src/model_physics.rs index ab0c0141..ef08ce7d 100644 --- a/src/model_physics.rs +++ b/src/model_physics.rs @@ -1 +1,738 @@ -// \ No newline at end of file +use crate::integer::{Planar64,Planar64Vec3}; +use std::borrow::{Borrow,Cow}; + +#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] +pub struct VertId(usize); +#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] +pub struct EdgeId(usize); +pub trait UndirectedEdge{ + type DirectedEdge:Copy+DirectedEdge; + fn as_directed(&self,parity:bool)->Self::DirectedEdge; +} +impl UndirectedEdge for EdgeId{ + type DirectedEdge=DirectedEdgeId; + fn as_directed(&self,parity:bool)->DirectedEdgeId{ + DirectedEdgeId(self.0|((parity as usize)<<(usize::BITS-1))) + } +} +pub trait DirectedEdge{ + type UndirectedEdge:Copy+UndirectedEdge; + fn as_undirected(&self)->Self::UndirectedEdge; + fn parity(&self)->bool; + //this is stupid but may work fine + fn reverse(&self)-><::UndirectedEdge as UndirectedEdge>::DirectedEdge{ + self.as_undirected().as_directed(!self.parity()) + } +} +/// DirectedEdgeId refers to an EdgeId when undirected. +#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] +pub struct DirectedEdgeId(usize); +impl DirectedEdge for DirectedEdgeId{ + type UndirectedEdge=EdgeId; + fn as_undirected(&self)->EdgeId{ + EdgeId(self.0&!(1<<(usize::BITS-1))) + } + fn parity(&self)->bool{ + self.0&(1<<(usize::BITS-1))!=0 + } +} +#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] +pub struct FaceId(usize); + +//Vertex <-> Edge <-> Face -> Collide +pub enum FEV{ + Face(F), + Edge(E::UndirectedEdge), + Vert(V), +} + +//use Unit32 #[repr(C)] for map files +struct Face{ + normal:Planar64Vec3, + dot:Planar64, +} +struct Vert(Planar64Vec3); +pub trait MeshQuery{ + fn edge_n(&self,edge_id:EDGE::UndirectedEdge)->Planar64Vec3{ + let verts=self.edge_verts(edge_id); + self.vert(verts[1].clone())-self.vert(verts[0].clone()) + } + fn directed_edge_n(&self,directed_edge_id:EDGE)->Planar64Vec3{ + 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) + } + fn vert(&self,vert_id:VERT)->Planar64Vec3; + fn face_nd(&self,face_id:FACE)->(Planar64Vec3,Planar64); + fn face_edges(&self,face_id:FACE)->Cow>; + fn edge_faces(&self,edge_id:EDGE::UndirectedEdge)->Cow<[FACE;2]>; + fn edge_verts(&self,edge_id:EDGE::UndirectedEdge)->Cow<[VERT;2]>; + fn vert_edges(&self,vert_id:VERT)->Cow>; + fn vert_faces(&self,vert_id:VERT)->Cow>; +} +struct FaceRefs{ + edges:Vec, + //verts:Vec, +} +struct EdgeRefs{ + faces:[FaceId;2],//left, right + verts:[VertId;2],//bottom, top +} +struct VertRefs{ + faces:Vec, + edges:Vec, +} +pub struct PhysicsMesh{ + faces:Vec, + verts:Vec, + face_topology:Vec, + edge_topology:Vec, + vert_topology:Vec, +} + +#[derive(Default,Clone)] +struct VertRefGuy{ + edges:std::collections::HashSet, + faces:std::collections::HashSet, +} +#[derive(Clone,Hash,Eq,PartialEq)] +struct EdgeRefVerts([VertId;2]); +impl EdgeRefVerts{ + fn new(v0:VertId,v1:VertId)->(Self,bool){ + (if v0.0Self{ + Self([FaceId(0);2]) + } + fn push(&mut self,i:usize,face_id:FaceId){ + self.0[i]=face_id; + } +} +struct FaceRefEdges(Vec); +#[derive(Default)] +struct EdgePool{ + edge_guys:Vec<(EdgeRefVerts,EdgeRefFaces)>, + edge_id_from_guy:std::collections::HashMap, +} +impl EdgePool{ + fn push(&mut self,edge_ref_verts:EdgeRefVerts)->(&mut EdgeRefFaces,EdgeId){ + let edge_id=if let Some(&edge_id)=self.edge_id_from_guy.get(&edge_ref_verts){ + edge_id + }else{ + let edge_id=self.edge_guys.len(); + self.edge_guys.push((edge_ref_verts.clone(),EdgeRefFaces::new())); + self.edge_id_from_guy.insert(edge_ref_verts,edge_id); + edge_id + }; + (&mut unsafe{self.edge_guys.get_unchecked_mut(edge_id)}.1,EdgeId(edge_id)) + } +} +impl From<&crate::model::IndexedModel> for PhysicsMesh{ + fn from(indexed_model:&crate::model::IndexedModel)->Self{ + assert!(indexed_model.unique_pos.len()!=0,"Mesh cannot have 0 vertices"); + let verts=indexed_model.unique_pos.iter().map(|v|Vert(v.clone())).collect(); + let mut vert_ref_guys=vec![VertRefGuy::default();indexed_model.unique_pos.len()]; + let mut edge_pool=EdgePool::default(); + let mut face_i=0; + let mut faces=Vec::new(); + let mut face_ref_guys=Vec::new(); + for group in indexed_model.groups.iter(){for poly in group.polys.iter(){ + let face_id=FaceId(face_i); + //one face per poly + let mut normal=Planar64Vec3::ZERO; + let len=poly.vertices.len(); + let face_edges=poly.vertices.iter().enumerate().map(|(i,&vert_id)|{ + let vert0_id=indexed_model.unique_vertices[vert_id as usize].pos as usize; + let vert1_id=indexed_model.unique_vertices[poly.vertices[(i+1)%len] as usize].pos as usize; + //https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal (Newell's Method) + let v0=indexed_model.unique_pos[vert0_id]; + let v1=indexed_model.unique_pos[vert1_id]; + normal+=Planar64Vec3::new( + (v0.y()-v1.y())*(v0.z()+v1.z()), + (v0.z()-v1.z())*(v0.x()+v1.x()), + (v0.x()-v1.x())*(v0.y()+v1.y()), + ); + //get/create edge and push face into it + let (edge_ref_verts,is_sorted)=EdgeRefVerts::new(VertId(vert0_id),VertId(vert1_id)); + let (edge_ref_faces,edge_id)=edge_pool.push(edge_ref_verts); + //polygon vertices as assumed to be listed clockwise + //populate the edge face on the left or right depending on how the edge vertices got sorted + edge_ref_faces.push(!is_sorted as usize,face_id); + //index edges & face into vertices + { + let vert_ref_guy=unsafe{vert_ref_guys.get_unchecked_mut(vert0_id)}; + vert_ref_guy.edges.insert(edge_id.as_directed(is_sorted)); + vert_ref_guy.faces.insert(face_id); + unsafe{vert_ref_guys.get_unchecked_mut(vert1_id)}.edges.insert(edge_id.as_directed(!is_sorted)); + } + //return directed_edge_id + edge_id.as_directed(is_sorted) + }).collect(); + //choose precision loss randomly idk + normal=normal/len as i64; + let mut dot=Planar64::ZERO; + for &v in poly.vertices.iter(){ + dot+=normal.dot(indexed_model.unique_pos[indexed_model.unique_vertices[v as usize].pos as usize]); + } + faces.push(Face{normal,dot:dot/len as i64}); + face_ref_guys.push(FaceRefEdges(face_edges)); + face_i+=1; + }} + //conceivably faces, edges, and vertices exist now + Self{ + faces, + verts, + face_topology:face_ref_guys.into_iter().map(|face_ref_guy|{ + FaceRefs{edges:face_ref_guy.0} + }).collect(), + edge_topology:edge_pool.edge_guys.into_iter().map(|(edge_ref_verts,edge_ref_faces)| + EdgeRefs{faces:edge_ref_faces.0,verts:edge_ref_verts.0} + ).collect(), + vert_topology:vert_ref_guys.into_iter().map(|vert_ref_guy| + VertRefs{ + edges:vert_ref_guy.edges.into_iter().collect(), + faces:vert_ref_guy.faces.into_iter().collect(), + } + ).collect(), + } + } +} + +impl PhysicsMesh{ + pub fn verts<'a>(&'a self)->impl Iterator+'a{ + self.verts.iter().map(|Vert(pos)|*pos) + } +} +impl MeshQuery for PhysicsMesh{ + fn face_nd(&self,face_id:FaceId)->(Planar64Vec3,Planar64){ + (self.faces[face_id.0].normal,self.faces[face_id.0].dot) + } + //ideally I never calculate the vertex position, but I have to for the graphical meshes... + fn vert(&self,vert_id:VertId)->Planar64Vec3{ + self.verts[vert_id.0].0 + } + fn face_edges(&self,face_id:FaceId)->Cow>{ + Cow::Borrowed(&self.face_topology[face_id.0].edges) + } + fn edge_faces(&self,edge_id:EdgeId)->Cow<[FaceId;2]>{ + Cow::Borrowed(&self.edge_topology[edge_id.0].faces) + } + fn edge_verts(&self,edge_id:EdgeId)->Cow<[VertId;2]>{ + Cow::Borrowed(&self.edge_topology[edge_id.0].verts) + } + fn vert_edges(&self,vert_id:VertId)->Cow>{ + Cow::Borrowed(&self.vert_topology[vert_id.0].edges) + } + fn vert_faces(&self,vert_id:VertId)->Cow>{ + Cow::Borrowed(&self.vert_topology[vert_id.0].faces) + } +} + +pub struct TransformedMesh<'a>{ + mesh:&'a PhysicsMesh, + transform:&'a crate::integer::Planar64Affine3, + normal_transform:&'a crate::integer::Planar64Mat3, +} +impl TransformedMesh<'_>{ + pub fn new<'a>( + mesh:&'a PhysicsMesh, + transform:&'a crate::integer::Planar64Affine3, + normal_transform:&'a crate::integer::Planar64Mat3, + )->TransformedMesh<'a>{ + TransformedMesh{ + mesh, + transform, + normal_transform, + } + } + fn farthest_vert(&self,dir:Planar64Vec3)->VertId{ + let mut best_dot=Planar64::MIN; + let mut best_vert=VertId(0); + for (i,vert) in self.mesh.verts.iter().enumerate(){ + let p=self.transform.transform_point3(vert.0); + let d=dir.dot(p); + if best_dot for TransformedMesh<'_>{ + fn face_nd(&self,face_id:FaceId)->(Planar64Vec3,Planar64){ + let (n,d)=self.mesh.face_nd(face_id); + let transformed_n=*self.normal_transform*n; + let transformed_d=Planar64::raw(((transformed_n.dot128(self.transform.matrix3*n)<<32)/n.dot128(n)) as i64)*d+transformed_n.dot(self.transform.translation); + (transformed_n,transformed_d) + } + fn vert(&self,vert_id:VertId)->Planar64Vec3{ + self.transform.transform_point3(self.mesh.vert(vert_id)) + } + #[inline] + fn face_edges(&self,face_id:FaceId)->Cow>{ + self.mesh.face_edges(face_id) + } + #[inline] + fn edge_faces(&self,edge_id:EdgeId)->Cow<[FaceId;2]>{ + self.mesh.edge_faces(edge_id) + } + #[inline] + fn edge_verts(&self,edge_id:EdgeId)->Cow<[VertId;2]>{ + self.mesh.edge_verts(edge_id) + } + #[inline] + fn vert_edges(&self,vert_id:VertId)->Cow>{ + self.mesh.vert_edges(vert_id) + } + #[inline] + fn vert_faces(&self,vert_id:VertId)->Cow>{ + self.mesh.vert_faces(vert_id) + } +} + +//Note that a face on a minkowski mesh refers to a pair of fevs on the meshes it's summed from +//(face,vertex) +//(edge,edge) +//(vertex,face) +#[derive(Clone,Copy)] +pub enum MinkowskiVert{ + VertVert(VertId,VertId), +} +#[derive(Clone,Copy)] +pub enum MinkowskiEdge{ + VertEdge(VertId,EdgeId), + EdgeVert(EdgeId,VertId), + //EdgeEdge when edges are parallel +} +impl UndirectedEdge for MinkowskiEdge{ + type DirectedEdge=MinkowskiDirectedEdge; + fn as_directed(&self,parity:bool)->Self::DirectedEdge{ + match self{ + MinkowskiEdge::VertEdge(v0,e1)=>MinkowskiDirectedEdge::VertEdge(*v0,e1.as_directed(parity)), + MinkowskiEdge::EdgeVert(e0,v1)=>MinkowskiDirectedEdge::EdgeVert(e0.as_directed(parity),*v1), + } + } +} +#[derive(Clone,Copy)] +pub enum MinkowskiDirectedEdge{ + VertEdge(VertId,DirectedEdgeId), + EdgeVert(DirectedEdgeId,VertId), + //EdgeEdge when edges are parallel +} +impl DirectedEdge for MinkowskiDirectedEdge{ + type UndirectedEdge=MinkowskiEdge; + fn as_undirected(&self)->Self::UndirectedEdge{ + match self{ + MinkowskiDirectedEdge::VertEdge(v0,e1)=>MinkowskiEdge::VertEdge(*v0,e1.as_undirected()), + MinkowskiDirectedEdge::EdgeVert(e0,v1)=>MinkowskiEdge::EdgeVert(e0.as_undirected(),*v1), + } + } + fn parity(&self)->bool{ + match self{ + MinkowskiDirectedEdge::VertEdge(_,e) + |MinkowskiDirectedEdge::EdgeVert(e,_)=>e.parity(), + } + } +} +#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] +pub enum MinkowskiFace{ + VertFace(VertId,FaceId), + EdgeEdge(EdgeId,EdgeId,bool), + FaceVert(FaceId,VertId), + //EdgeFace + //FaceEdge + //FaceFace +} + +pub struct MinkowskiMesh<'a>{ + mesh0:&'a TransformedMesh<'a>, + mesh1:&'a TransformedMesh<'a>, +} + +//infinity fev algorithm state transition +enum Transition{ + Done,//found closest vert, no edges are better + Vert(MinkowskiVert),//transition to vert +} +enum EV{ + Vert(MinkowskiVert), + Edge(MinkowskiEdge), +} + +impl MinkowskiMesh<'_>{ + pub fn minkowski_sum<'a>(mesh0:&'a TransformedMesh,mesh1:&'a TransformedMesh)->MinkowskiMesh<'a>{ + MinkowskiMesh{ + mesh0, + mesh1, + } + } + fn farthest_vert(&self,dir:Planar64Vec3)->MinkowskiVert{ + MinkowskiVert::VertVert(self.mesh0.farthest_vert(dir),self.mesh1.farthest_vert(-dir)) + } + fn next_transition_vert(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Planar64,infinity_dir:Planar64Vec3,point:Planar64Vec3)->Transition{ + let mut best_transition=Transition::Done; + for &directed_edge_id in self.vert_edges(vert_id).iter(){ + let edge_n=self.directed_edge_n(directed_edge_id); + //is boundary uncrossable by a crawl from infinity + if infinity_dir.dot(edge_n)==Planar64::ZERO{ + let edge_verts=self.edge_verts(directed_edge_id.as_undirected()); + //select opposite vertex + let test_vert_id=edge_verts[directed_edge_id.parity() as usize]; + //test if it's closer + let diff=point-self.vert(test_vert_id); + let distance_squared=diff.dot(diff); + if distance_squared<*best_distance_squared{ + best_transition=Transition::Vert(test_vert_id); + *best_distance_squared=distance_squared; + } + } + } + best_transition + } + fn final_ev(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Planar64,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{ + let mut best_transition=EV::Vert(vert_id); + let diff=point-self.vert(vert_id); + for &directed_edge_id in self.vert_edges(vert_id).iter(){ + let edge_n=self.directed_edge_n(directed_edge_id); + //is boundary uncrossable by a crawl from infinity + if infinity_dir.dot(edge_n)==Planar64::ZERO{ + //test the edge + let d=diff.dot(edge_n); + let edge_nn=edge_n.dot(edge_n); + if Planar64::ZERO<=d&&d<=edge_nn{ + let distance_squared={ + let c=diff.cross(edge_n); + c.dot(c)/edge_nn + }; + if distance_squared<=*best_distance_squared{ + best_transition=EV::Edge(directed_edge_id.as_undirected()); + *best_distance_squared=distance_squared; + } + } + } + } + best_transition + } + fn crawl_boundaries(&self,mut vert_id:MinkowskiVert,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{ + let mut best_distance_squared={ + let diff=point-self.vert(vert_id); + diff.dot(diff) + }; + loop{ + match self.next_transition_vert(vert_id,&mut best_distance_squared,infinity_dir,point){ + Transition::Done=>return self.final_ev(vert_id,&mut best_distance_squared,infinity_dir,point), + Transition::Vert(new_vert_id)=>vert_id=new_vert_id, + } + } + } + /// 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::{ + //start on any vertex + //cross uncrossable vertex-edge boundaries until you find the closest vertex or edge + //cross edge-face boundary if it's uncrossable + 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 + EV::Vert(vert_id)=>FEV::::Vert(vert_id), + EV::Edge(edge_id)=>{ + //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 vert_sum={ + let &[v0,v1]=self.edge_verts(edge_id).borrow(); + self.vert(v0)+self.vert(v1) + }; + for (i,&face_id) in self.edge_faces(edge_id).iter().enumerate(){ + let face_n=self.face_nd(face_id).0; + //edge-face boundary nd, n facing out of the face towards the edge + let boundary_n=face_n.cross(edge_n)*(i as i64*2-1); + let boundary_d=boundary_n.dot(vert_sum); + // point.dot(boundary_n) is multiplied by two because vert_sum sums two vertices. + if infinity_dir.dot(boundary_n)==Planar64::ZERO&&point.dot(boundary_n)*2<=boundary_d{ + //both faces cannot pass this condition, return early if one does. + return FEV::::Face(face_id); + } + } + FEV::::Edge(edge_id) + }, + } + } + fn closest_fev_not_inside(&self,mut infinity_body:crate::physics::Body)->Option>{ + infinity_body.infinity_dir().map_or(None,|dir|{ + let infinity_fev=self.infinity_fev(-dir,infinity_body.position); + //a line is simpler to solve than a parabola + infinity_body.velocity=dir; + infinity_body.acceleration=Planar64Vec3::ZERO; + //crawl in from negative infinity along a tangent line to get the closest fev + match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,crate::integer::Time::MIN,infinity_body.time){ + crate::face_crawler::CrawlResult::Miss(fev)=>Some(fev), + crate::face_crawler::CrawlResult::Hit(_,_)=>None, + } + }) + } + pub fn predict_collision_in(&self,relative_body:&crate::physics::Body,time_limit:crate::integer::Time)->Option<(MinkowskiFace,crate::integer::Time)>{ + self.closest_fev_not_inside(relative_body.clone()).map_or(None,|fev|{ + //continue forwards along the body parabola + 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::Hit(face,time)=>Some((face,time)), + } + }) + } + pub fn predict_collision_out(&self,relative_body:&crate::physics::Body,time_limit:crate::integer::Time)->Option<(MinkowskiFace,crate::integer::Time)>{ + //create an extrapolated body at time_limit + let infinity_body=crate::physics::Body::new( + relative_body.extrapolated_position(time_limit), + -relative_body.extrapolated_velocity(time_limit), + relative_body.acceleration, + -time_limit, + ); + self.closest_fev_not_inside(infinity_body).map_or(None,|fev|{ + //continue backwards along the body parabola + 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::Hit(face,time)=>Some((face,-time)),//no need to test -timeOption<(MinkowskiEdge,crate::integer::Time)>{ + //no algorithm needed, there is only one state and two cases (Edge,None) + //determine when it passes an edge ("sliding off" case) + let mut best_time=time_limit; + let mut best_edge=None; + let face_n=self.face_nd(contact_face_id).0; + for &directed_edge_id in self.face_edges(contact_face_id).iter(){ + let edge_n=self.directed_edge_n(directed_edge_id); + //f x e points in + let n=face_n.cross(edge_n); + let verts=self.edge_verts(directed_edge_id.as_undirected()); + let d=n.dot(self.vert(verts[0])+self.vert(verts[1])); + //WARNING! d outside of *2 + for t in crate::zeroes::zeroes2((n.dot(relative_body.position))*2-d,n.dot(relative_body.velocity)*2,n.dot(relative_body.acceleration)){ + let t=relative_body.time+crate::integer::Time::from(t); + if relative_body.time for MinkowskiMesh<'_>{ + fn face_nd(&self,face_id:MinkowskiFace)->(Planar64Vec3,Planar64){ + match face_id{ + MinkowskiFace::VertFace(v0,f1)=>{ + let (n,d)=self.mesh1.face_nd(f1); + (-n,d-n.dot(self.mesh0.vert(v0))) + }, + MinkowskiFace::EdgeEdge(e0,e1,parity)=>{ + let edge0_n=self.mesh0.edge_n(e0); + let edge1_n=self.mesh1.edge_n(e1); + let &[e0v0,e0v1]=self.mesh0.edge_verts(e0).borrow(); + let &[e1v0,e1v1]=self.mesh1.edge_verts(e1).borrow(); + let n=edge0_n.cross(edge1_n); + let e0d=n.dot(self.mesh0.vert(e0v0)+self.mesh0.vert(e0v1)); + let e1d=n.dot(self.mesh1.vert(e1v0)+self.mesh1.vert(e1v1)); + (n*(parity as i64*4-2),(e0d-e1d)*(parity as i64*2-1)) + }, + MinkowskiFace::FaceVert(f0,v1)=>{ + let (n,d)=self.mesh0.face_nd(f0); + (n,d-n.dot(self.mesh1.vert(v1))) + }, + } + } + fn vert(&self,vert_id:MinkowskiVert)->Planar64Vec3{ + match vert_id{ + MinkowskiVert::VertVert(v0,v1)=>{ + self.mesh0.vert(v0)-self.mesh1.vert(v1) + }, + } + } + fn face_edges(&self,face_id:MinkowskiFace)->Cow>{ + match face_id{ + MinkowskiFace::VertFace(v0,f1)=>{ + Cow::Owned(self.mesh1.face_edges(f1).iter().map(|&edge_id1|{ + MinkowskiDirectedEdge::VertEdge(v0,edge_id1.reverse()) + }).collect()) + }, + MinkowskiFace::EdgeEdge(e0,e1,parity)=>{ + let e0v=self.mesh0.edge_verts(e0); + let e1v=self.mesh1.edge_verts(e1); + //could sort this if ordered edges are needed + //probably just need to reverse this list according to parity + Cow::Owned(vec![ + MinkowskiDirectedEdge::VertEdge(e0v[0],e1.as_directed(parity)), + MinkowskiDirectedEdge::EdgeVert(e0.as_directed(!parity),e1v[0]), + MinkowskiDirectedEdge::VertEdge(e0v[1],e1.as_directed(!parity)), + MinkowskiDirectedEdge::EdgeVert(e0.as_directed(parity),e1v[1]), + ]) + }, + MinkowskiFace::FaceVert(f0,v1)=>{ + Cow::Owned(self.mesh0.face_edges(f0).iter().map(|&edge_id0|{ + MinkowskiDirectedEdge::EdgeVert(edge_id0,v1) + }).collect()) + }, + } + } + fn edge_faces(&self,edge_id:MinkowskiEdge)->Cow<[MinkowskiFace;2]>{ + match edge_id{ + MinkowskiEdge::VertEdge(v0,e1)=>{ + //faces are listed backwards from the minkowski mesh + let v0e=self.mesh0.vert_edges(v0); + let &[e1f0,e1f1]=self.mesh1.edge_faces(e1).borrow(); + Cow::Owned([(e1f1,false),(e1f0,true)].map(|(edge_face_id1,face_parity)|{ + let mut best_edge=None; + let mut best_d=Planar64::ZERO; + let edge_face1_n=self.mesh1.face_nd(edge_face_id1).0; + let edge_face1_nn=edge_face1_n.dot(edge_face1_n); + for &directed_edge_id0 in v0e.iter(){ + let edge0_n=self.mesh0.directed_edge_n(directed_edge_id0); + //must be behind other face. + let d=edge_face1_n.dot(edge0_n); + if d{ + //tracking index with an external variable because .enumerate() is not available + let v1e=self.mesh1.vert_edges(v1); + let &[e0f0,e0f1]=self.mesh0.edge_faces(e0).borrow(); + Cow::Owned([(e0f0,true),(e0f1,false)].map(|(edge_face_id0,face_parity)|{ + let mut best_edge=None; + let mut best_d=Planar64::ZERO; + let edge_face0_n=self.mesh0.face_nd(edge_face_id0).0; + let edge_face0_nn=edge_face0_n.dot(edge_face0_n); + for &directed_edge_id1 in v1e.iter(){ + let edge1_n=self.mesh1.directed_edge_n(directed_edge_id1); + let d=edge_face0_n.dot(edge1_n); + if dCow<[MinkowskiVert;2]>{ + match edge_id{ + MinkowskiEdge::VertEdge(v0,e1)=>{ + Cow::Owned(self.mesh1.edge_verts(e1).map(|vert_id1|{ + MinkowskiVert::VertVert(v0,vert_id1) + })) + }, + MinkowskiEdge::EdgeVert(e0,v1)=>{ + Cow::Owned(self.mesh0.edge_verts(e0).map(|vert_id0|{ + MinkowskiVert::VertVert(vert_id0,v1) + })) + }, + } + } + fn vert_edges(&self,vert_id:MinkowskiVert)->Cow>{ + match vert_id{ + MinkowskiVert::VertVert(v0,v1)=>{ + let mut edges=Vec::new(); + //detect shared volume when the other mesh is mirrored along a test edge dir + let v0f=self.mesh0.vert_faces(v0); + let v1f=self.mesh1.vert_faces(v1); + let v0f_n:Vec=v0f.iter().map(|&face_id|self.mesh0.face_nd(face_id).0).collect(); + let v1f_n:Vec=v1f.iter().map(|&face_id|self.mesh1.face_nd(face_id).0).collect(); + let the_len=v0f.len()+v1f.len(); + for &directed_edge_id in self.mesh0.vert_edges(v0).iter(){ + let n=self.mesh0.directed_edge_n(directed_edge_id); + let nn=n.dot(n); + //make a set of faces + let mut face_normals=Vec::with_capacity(the_len); + //add mesh0 faces as-is + face_normals.clone_from(&v0f_n); + for face_n in &v1f_n{ + //add reflected mesh1 faces + face_normals.push(*face_n-n*(face_n.dot(n)*2/nn)); + } + if is_empty_volume(face_normals){ + edges.push(MinkowskiDirectedEdge::EdgeVert(directed_edge_id,v1)); + } + } + for &directed_edge_id in self.mesh1.vert_edges(v1).iter(){ + let n=self.mesh1.directed_edge_n(directed_edge_id); + let nn=n.dot(n); + let mut face_normals=Vec::with_capacity(the_len); + face_normals.clone_from(&v1f_n); + for face_n in &v0f_n{ + face_normals.push(*face_n-n*(face_n.dot(n)*2/nn)); + } + if is_empty_volume(face_normals){ + edges.push(MinkowskiDirectedEdge::VertEdge(v0,directed_edge_id)); + } + } + Cow::Owned(edges) + }, + } + } + fn vert_faces(&self,_vert_id:MinkowskiVert)->Cow>{ + unimplemented!() + } +} + +fn is_empty_volume(normals:Vec)->bool{ + let len=normals.len(); + for i in 0..len-1{ + for j in i+1..len{ + let n=normals[i].cross(normals[j]); + let mut d_comp=None; + for k in 0..len{ + if k!=i&&k!=j{ + let d=n.dot(normals[k]); + if let Some(comp)=&d_comp{ + if *comp*dSelf::Output{ + Self{ + position:self.position, + velocity:-self.velocity, + acceleration:self.acceleration, + time:-self.time, + } + } } //hey dumbass just use a delta @@ -70,6 +81,10 @@ impl MouseState { } } +enum JumpDirection{ + Exactly(Planar64Vec3), + FromContactNormal, +} enum WalkEnum{ Reached, Transient(WalkTarget), @@ -79,50 +94,49 @@ struct WalkTarget{ time:Time, } struct WalkState{ - normal:Planar64Vec3, + jump_direction:JumpDirection, + contact:ContactCollision, 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:&PhysicsModels,mut velocity:Planar64Vec3,normal:&Planar64Vec3)->(WalkEnum,Planar64Vec3){ - touching.constrain_velocity(models,&mut velocity); + fn with_target_velocity(body:&Body,style:&StyleModifiers,velocity:Planar64Vec3,normal:&Planar64Vec3,speed:Planar64,normal_accel:Planar64)->(WalkEnum,Planar64Vec3){ 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) + (WalkEnum::Reached,Planar64Vec3::ZERO) }else{ //normal friction acceleration is clippedAcceleration.dot(normal)*friction let diff_len=target_diff.length(); - let friction=if diff_len(Self,Planar64Vec3){ - let (walk_enum,a)=WalkEnum::with_target_velocity(touching,body,style,models,velocity,&Planar64Vec3::Y); + fn ground(body:&Body,style:&StyleModifiers,gravity:Planar64Vec3,velocity:Planar64Vec3,contact:ContactCollision,normal:&Planar64Vec3)->(Self,Planar64Vec3){ + let (walk_enum,a)=WalkEnum::with_target_velocity(body,style,velocity,&Planar64Vec3::Y,style.walk_speed,-normal.dot(gravity)); (Self{ state:walk_enum, - normal:Planar64Vec3::Y, + contact, + jump_direction:JumpDirection::Exactly(Planar64Vec3::Y), },a) } - fn ladder(touching:&TouchingState,body:&Body,style:&StyleModifiers,models:&PhysicsModels,velocity:Planar64Vec3,normal:&Planar64Vec3)->(Self,Planar64Vec3){ - let (walk_enum,a)=WalkEnum::with_target_velocity(touching,body,style,models,velocity,normal); + fn ladder(body:&Body,style:&StyleModifiers,gravity:Planar64Vec3,velocity:Planar64Vec3,contact:ContactCollision,normal:&Planar64Vec3)->(Self,Planar64Vec3){ + let (walk_enum,a)=WalkEnum::with_target_velocity(body,style,velocity,normal,style.ladder_speed,style.ladder_accel); (Self{ state:walk_enum, - normal:normal.clone(), + contact, + jump_direction:JumpDirection::FromContactNormal, },a) } } @@ -154,33 +168,63 @@ impl Default for Modes{ } } +#[derive(Default)] struct PhysicsModels{ + meshes:Vec, models:Vec, + //separate models into Contacting and Intersecting? + //wrap model id with ContactingModelId and IntersectingModelId + //attributes can be split into contacting and intersecting (this also saves a bit of memory) + //can go even further and deduplicate General attributes separately, reconstructing it when queried + attributes:Vec, model_id_from_wormhole_id:std::collections::HashMap::, } impl PhysicsModels{ fn clear(&mut self){ + self.meshes.clear(); self.models.clear(); + self.attributes.clear(); self.model_id_from_wormhole_id.clear(); } - fn get(&self,i:usize)->Option<&PhysicsModel>{ - self.models.get(i) + fn aabb_list(&self)->Vec{ + self.models.iter().map(|model|{ + let mut aabb=crate::aabb::Aabb::default(); + for pos in self.meshes[model.mesh_id].verts(){ + aabb.grow(model.transform.transform_point3(pos)); + } + aabb + }).collect() + } + //TODO: "statically" verify the maps don't refer to any nonexistant data, if they do delete the references. + //then I can make these getter functions unchecked. + fn mesh(&self,model_id:usize)->TransformedMesh{ + TransformedMesh::new( + &self.meshes[self.models[model_id].mesh_id], + &self.models[model_id].transform, + &self.models[model_id].normal_transform, + ) + } + fn model(&self,model_id:usize)->&PhysicsModel{ + &self.models[model_id] + } + fn attr(&self,model_id:usize)->&PhysicsCollisionAttributes{ + &self.attributes[self.models[model_id].attr_id] } fn get_wormhole_model(&self,wormhole_id:u32)->Option<&PhysicsModel>{ self.models.get(*self.model_id_from_wormhole_id.get(&wormhole_id)?) } - fn push(&mut self,model:PhysicsModel)->usize{ + fn push_mesh(&mut self,mesh:PhysicsMesh){ + self.meshes.push(mesh); + } + fn push_model(&mut self,model:PhysicsModel)->usize{ let model_id=self.models.len(); self.models.push(model); model_id } -} -impl Default for PhysicsModels{ - fn default() -> Self { - Self{ - models:Vec::new(), - model_id_from_wormhole_id:std::collections::HashMap::new(), - } + fn push_attr(&mut self,attr:PhysicsCollisionAttributes)->usize{ + let attr_id=self.attributes.len(); + self.attributes.push(attr); + attr_id } } @@ -294,6 +338,55 @@ struct StrafeSettings{ tick_rate:Ratio64, } +struct Hitbox{ + halfsize:Planar64Vec3, + mesh:PhysicsMesh, + transform:crate::integer::Planar64Affine3, + normal_transform:Planar64Mat3, +} +impl Hitbox{ + fn new(mesh:PhysicsMesh,transform:crate::integer::Planar64Affine3)->Self{ + //calculate extents + let normal_transform=transform.matrix3.inverse_times_det().transpose(); + let mut aabb=crate::aabb::Aabb::default(); + for vert in mesh.verts(){ + aabb.grow(transform.transform_point3(vert)); + } + Self{ + halfsize:aabb.size()/2, + mesh, + transform, + normal_transform, + } + } + fn from_mesh_scale(mesh:PhysicsMesh,scale:Planar64Vec3)->Self{ + Self{ + halfsize:scale, + mesh, + transform:crate::integer::Planar64Affine3::new(Planar64Mat3::from_diagonal(scale),Planar64Vec3::ZERO), + normal_transform:Planar64Mat3::from_diagonal(scale).inverse_times_det().transpose(), + } + } + fn from_mesh_scale_offset(mesh:PhysicsMesh,scale:Planar64Vec3,offset:Planar64Vec3)->Self{ + Self{ + halfsize:scale, + mesh, + transform:crate::integer::Planar64Affine3::new(Planar64Mat3::from_diagonal(scale),offset), + normal_transform:Planar64Mat3::from_diagonal(scale).inverse_times_det().transpose(), + } + } + fn roblox()->Self{ + Self::from_mesh_scale(PhysicsMesh::from(&crate::primitives::unit_cylinder()),Planar64Vec3::int(2,5,2)/2) + } + fn source()->Self{ + Self::from_mesh_scale(PhysicsMesh::from(&crate::primitives::unit_cube()),Planar64Vec3::raw(33<<28,73<<28,33<<28)/2) + } + #[inline] + fn transformed_mesh(&self)->TransformedMesh{ + TransformedMesh::new(&self.mesh,&self.transform,&self.normal_transform) + } +} + struct StyleModifiers{ controls_used:u32,//controls which are allowed to pass into gameplay controls_mask:u32,//controls which are masked from control state (e.g. jump in scroll style) @@ -310,9 +403,10 @@ struct StyleModifiers{ swim_speed:Planar64, mass:Planar64, mv:Planar64, + surf_slope:Option, rocket_force:Option, gravity:Planar64Vec3, - hitbox_halfsize:Planar64Vec3, + hitbox:Hitbox, camera_offset:Planar64Vec3, } impl std::default::Default for StyleModifiers{ @@ -334,14 +428,14 @@ impl StyleModifiers{ const UP_DIR:Planar64Vec3=Planar64Vec3::Y; const FORWARD_DIR:Planar64Vec3=Planar64Vec3::NEG_Z; - fn new()->Self{ + fn neo()->Self{ Self{ controls_used:!0, controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), strafe:Some(StrafeSettings{ enable:EnableStrafe::Always, air_accel_limit:None, - tick_rate:Ratio64::new(128,Time::ONE_SECOND.nanos() as u64).unwrap(), + tick_rate:Ratio64::new(64,Time::ONE_SECOND.nanos() as u64).unwrap(), }), jump_impulse:JumpImpulse::FromEnergy(Planar64::int(512)), jump_calculation:JumpCalculation::Energy, @@ -349,7 +443,7 @@ impl StyleModifiers{ 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), + mv:Planar64::int(3), rocket_force:None, walk_speed:Planar64::int(16), walk_accel:Planar64::int(80), @@ -357,7 +451,8 @@ impl StyleModifiers{ 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, + surf_slope:Some(Planar64::raw(7)/8), + hitbox:Hitbox::roblox(), camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 } } @@ -385,7 +480,8 @@ impl StyleModifiers{ 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, + surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 + hitbox:Hitbox::roblox(), camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 } } @@ -412,7 +508,8 @@ impl StyleModifiers{ 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, + surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 + hitbox:Hitbox::roblox(), camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 } } @@ -440,7 +537,8 @@ impl StyleModifiers{ 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, + surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 + hitbox:Hitbox::source(), camera_offset:Planar64Vec3::raw(0,(64<<28)-(73<<27),0), } } @@ -467,7 +565,8 @@ impl StyleModifiers{ 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, + surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 + hitbox:Hitbox::source(), camera_offset:Planar64Vec3::raw(0,(64<<28)-(73<<27),0), } } @@ -490,7 +589,8 @@ impl StyleModifiers{ 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, + surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 + hitbox:Hitbox::roblox(), camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 } } @@ -547,30 +647,67 @@ impl StyleModifiers{ } } - fn get_walk_target_velocity(&self,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time)->Planar64Vec3{ + fn get_walk_target_velocity(&self,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time,normal:&Planar64Vec3)->Planar64Vec3{ + let mut control_dir=self.get_control_dir(controls); + if control_dir==Planar64Vec3::ZERO{ + return control_dir; + } 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 + control_dir=camera_mat*control_dir; + let n=normal.length(); + let m=control_dir.length(); + let d=normal.dot(control_dir)/m; + if dPlanar64Vec3{ + fn get_ladder_target_velocity(&self,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time,normal:&Planar64Vec3)->Planar64Vec3{ + let mut control_dir=self.get_control_dir(controls); + if control_dir==Planar64Vec3::ZERO{ + return control_dir; + } 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 LadderDotPlanar64Vec3{ let camera_mat=camera.simulate_move_rotation(camera.mouse.lerp(&next_mouse,time)); camera_mat*self.get_control_dir(controls) } + #[inline] + fn mesh(&self)->TransformedMesh{ + self.hitbox.transformed_mesh() + } } enum MoveState{ @@ -592,7 +729,6 @@ pub struct PhysicsState{ pub next_mouse:MouseState,//Where is the mouse headed next controls:u32, move_state:MoveState, - //all models models:PhysicsModels, bvh:crate::bvh::BvhNode, @@ -613,10 +749,7 @@ impl PhysicsOutputState{ } } -//pretend to be using what we want to eventually do -type TreyMeshFace = crate::aabb::AabbFace; -type TreyMesh = crate::aabb::Aabb; - +#[derive(Clone,Hash,Eq,PartialEq)] enum PhysicsCollisionAttributes{ Contact{//track whether you are contacting the object contacting:crate::model::ContactingAttributes, @@ -627,124 +760,221 @@ enum PhysicsCollisionAttributes{ general:crate::model::GameMechanicAttributes, }, } +struct NonPhysicsError; +impl TryFrom<&crate::model::CollisionAttributes> for PhysicsCollisionAttributes{ + type Error=NonPhysicsError; + fn try_from(value:&crate::model::CollisionAttributes)->Result{ + match value{ + crate::model::CollisionAttributes::Decoration=>Err(NonPhysicsError), + crate::model::CollisionAttributes::Contact{contacting,general}=>Ok(Self::Contact{contacting:contacting.clone(),general:general.clone()}), + crate::model::CollisionAttributes::Intersect{intersecting,general}=>Ok(Self::Intersect{intersecting:intersecting.clone(),general:general.clone()}), + } + } +} -pub struct PhysicsModel { +pub struct PhysicsModel{ //A model is a thing that has a hitbox. can be represented by a list of TreyMesh-es //in this iteration, all it needs is extents. - mesh: TreyMesh, + mesh_id:usize, + attr_id:usize, transform:crate::integer::Planar64Affine3, - attributes:PhysicsCollisionAttributes, + normal_transform:crate::integer::Planar64Mat3, } -impl PhysicsModel { - fn from_model_transform_attributes(model:&crate::model::IndexedModel,transform:&crate::integer::Planar64Affine3,attributes:PhysicsCollisionAttributes)->Self{ - let mut aabb=TreyMesh::default(); - for indexed_vertex in &model.unique_vertices { - aabb.grow(transform.transform_point3(model.unique_pos[indexed_vertex.pos as usize])); - } +impl PhysicsModel{ + pub fn new(mesh_id:usize,attr_id:usize,transform:crate::integer::Planar64Affine3)->Self{ + let normal_transform=transform.matrix3.inverse_times_det().transpose(); Self{ - mesh:aabb, - attributes, - transform:transform.clone(), + mesh_id, + attr_id, + transform, + normal_transform, } } - pub fn from_model(model:&crate::model::IndexedModel,instance:&crate::model::ModelInstance) -> Option { - match &instance.attributes{ - crate::model::CollisionAttributes::Contact{contacting,general}=>Some(PhysicsModel::from_model_transform_attributes(model,&instance.transform,PhysicsCollisionAttributes::Contact{contacting:contacting.clone(),general:general.clone()})), - crate::model::CollisionAttributes::Intersect{intersecting,general}=>Some(PhysicsModel::from_model_transform_attributes(model,&instance.transform,PhysicsCollisionAttributes::Intersect{intersecting:intersecting.clone(),general:general.clone()})), - crate::model::CollisionAttributes::Decoration=>None, - } - } - pub fn unit_vertices(&self) -> [Planar64Vec3;8] { - TreyMesh::unit_vertices() - } - pub fn mesh(&self) -> &TreyMesh { - return &self.mesh; - } - // pub fn face_mesh(&self,face:TreyMeshFace)->TreyMesh{ - // self.mesh.face(face) - // } - pub fn face_normal(&self,face:TreyMeshFace) -> Planar64Vec3 { - TreyMesh::normal(face)//this is wrong for scale - } } -//need non-face (full model) variant for CanCollide false objects -//OR have a separate list from contacts for model intersection #[derive(Debug,Clone,Eq,Hash,PartialEq)] -pub struct RelativeCollision { - face:TreyMeshFace,//just an id - model:usize,//using id to avoid lifetimes +struct ContactCollision{ + face_id:crate::model_physics::MinkowskiFace, + model_id:usize,//using id to avoid lifetimes } - -impl RelativeCollision { - fn model<'a>(&self,models:&'a PhysicsModels)->Option<&'a PhysicsModel>{ - models.get(self.model) +#[derive(Debug,Clone,Eq,Hash,PartialEq)] +struct IntersectCollision{ + model_id:usize, +} +#[derive(Debug,Clone,Eq,Hash,PartialEq)] +enum Collision{ + Contact(ContactCollision), + Intersect(IntersectCollision), +} +impl Collision{ + fn model_id(&self)->usize{ + match self{ + &Collision::Contact(ContactCollision{model_id,face_id:_}) + |&Collision::Intersect(IntersectCollision{model_id})=>model_id, + } } - // pub fn mesh(&self,models:&Vec) -> TreyMesh { - // return self.model(models).unwrap().face_mesh(self.face).clone() - // } - fn normal(&self,models:&PhysicsModels) -> Planar64Vec3 { - return self.model(models).unwrap().face_normal(self.face) + fn face_id(&self)->Option{ + match self{ + &Collision::Contact(ContactCollision{model_id:_,face_id})=>Some(face_id), + &Collision::Intersect(IntersectCollision{model_id:_})=>None, + } } } - +#[derive(Default)] struct TouchingState{ - contacts:std::collections::HashMap::, - intersects:std::collections::HashMap::, + contacts:std::collections::HashSet::, + intersects:std::collections::HashSet::, } impl TouchingState{ fn clear(&mut self){ self.contacts.clear(); self.intersects.clear(); } - fn insert_contact(&mut self,model_id:usize,collision:RelativeCollision)->Option{ - self.contacts.insert(model_id,collision) + fn insert(&mut self,collision:Collision)->bool{ + match collision{ + Collision::Contact(collision)=>self.contacts.insert(collision), + Collision::Intersect(collision)=>self.intersects.insert(collision), + } } - fn remove_contact(&mut self,model_id:usize)->Option{ - self.contacts.remove(&model_id) + fn remove(&mut self,collision:&Collision)->bool{ + match collision{ + Collision::Contact(collision)=>self.contacts.remove(collision), + Collision::Intersect(collision)=>self.intersects.remove(collision), + } } - fn insert_intersect(&mut self,model_id:usize,collision:RelativeCollision)->Option{ - self.intersects.insert(model_id,collision) + fn base_acceleration(&self,models:&PhysicsModels,style:&StyleModifiers,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time)->Planar64Vec3{ + let mut a=style.gravity; + if let Some(rocket_force)=style.rocket_force{ + a+=style.get_propulsion_control_dir(camera,controls,next_mouse,time)*rocket_force; + } + //add accelerators + for contact in &self.contacts{ + match models.attr(contact.model_id){ + PhysicsCollisionAttributes::Contact{contacting,general}=>{ + match &general.accelerator{ + Some(accelerator)=>a+=accelerator.acceleration, + None=>(), + } + }, + _=>panic!("impossible touching state"), + } + } + for intersect in &self.intersects{ + match models.attr(intersect.model_id){ + PhysicsCollisionAttributes::Intersect{intersecting,general}=>{ + match &general.accelerator{ + Some(accelerator)=>a+=accelerator.acceleration, + None=>(), + } + }, + _=>panic!("impossible touching state"), + } + } + //add water../? + a } - fn remove_intersect(&mut self,model_id:usize)->Option{ - self.intersects.remove(&model_id) - } - fn constrain_velocity(&self,models:&PhysicsModels,velocity:&mut Planar64Vec3){ - for (_,contact) in &self.contacts { - let n=contact.normal(models); - let d=velocity.dot(n); - if d Self { - Self{ - contacts: std::collections::HashMap::new(), - intersects: std::collections::HashMap::new(), + fn get_move_state(&self,body:&Body,models:&PhysicsModels,style:&StyleModifiers,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time)->(MoveState,Planar64Vec3){ + //check current move conditions and use heuristics to determine + //which ladder to climb on, which ground to walk on, etc + //collect move state affecting objects from contacts (accelerator,water,ladder,ground) + let style_mesh=style.mesh(); + let gravity=self.base_acceleration(models,style,camera,controls,next_mouse,time); + let mut move_state=MoveState::Air; + let mut a=gravity; + for contact in &self.contacts{ + match models.attr(contact.model_id){ + PhysicsCollisionAttributes::Contact{contacting,general}=>{ + let normal=contact_normal(models,&style_mesh,contact); + match &contacting.contact_behaviour{ + Some(crate::model::ContactingBehaviour::Ladder(_))=>{ + //ladder walkstate + let mut target_velocity=style.get_ladder_target_velocity(camera,controls,next_mouse,time,&normal); + self.constrain_velocity(models,&style_mesh,&mut target_velocity); + let (walk_state,mut acceleration)=WalkState::ladder(body,style,gravity,target_velocity,contact.clone(),&normal); + move_state=MoveState::Ladder(walk_state); + self.constrain_acceleration(models,&style_mesh,&mut acceleration); + a=acceleration; + }, + None=>if style.surf_slope.map_or(true,|s|normal.walkable(s,Planar64Vec3::Y)){ + //check ground + let mut target_velocity=style.get_walk_target_velocity(camera,controls,next_mouse,time,&normal); + self.constrain_velocity(models,&style_mesh,&mut target_velocity); + let (walk_state,mut acceleration)=WalkState::ground(body,style,gravity,target_velocity,contact.clone(),&normal); + move_state=MoveState::Walk(walk_state); + self.constrain_acceleration(models,&style_mesh,&mut acceleration); + a=acceleration; + }, + _=>(), + } + }, + _=>panic!("impossible touching state"), + } + } + for intersect in &self.intersects{ + //water + } + self.constrain_acceleration(models,&style_mesh,&mut a); + (move_state,a) + } + fn predict_collision_end(&self,collector:&mut crate::instruction::InstructionCollector,models:&PhysicsModels,style_mesh:&TransformedMesh,body:&Body,time:Time){ + let relative_body=VirtualBody::relative(&Body::default(),body).body(time); + for contact in &self.contacts{ + //detect face slide off + let model_mesh=models.mesh(contact.model_id); + let minkowski=crate::model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,&style_mesh); + collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(face,time)|{ + TimedInstruction{ + time, + instruction:PhysicsInstruction::CollisionEnd( + Collision::Contact(ContactCollision{model_id:contact.model_id,face_id:contact.face_id}) + ), + } + })); + } + for intersect in &self.intersects{ + //detect model collision in reverse + let model_mesh=models.mesh(intersect.model_id); + let minkowski=crate::model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,&style_mesh); + collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(face,time)|{ + TimedInstruction{ + time, + instruction:PhysicsInstruction::CollisionEnd( + Collision::Intersect(IntersectCollision{model_id:intersect.model_id}) + ), + } + })); } } } -impl Body { - pub fn with_pva(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3) -> Self { +impl Body{ + pub fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time)->Self{ Self{ position, velocity, acceleration, - time:Time::ZERO, + time, } } pub fn extrapolated_position(&self,time:Time)->Planar64Vec3{ @@ -760,6 +990,42 @@ impl Body { self.velocity=self.extrapolated_velocity(time); self.time=time; } + pub fn infinity_dir(&self)->Option{ + if self.velocity==Planar64Vec3::ZERO{ + if self.acceleration==Planar64Vec3::ZERO{ + None + }else{ + Some(self.acceleration) + } + }else{ + Some(self.velocity) + } + } + pub fn grow_aabb(&self,aabb:&mut crate::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()!=Planar64::ZERO{ + let t=Time::from(-self.velocity.x()/self.acceleration.x()); + if t0)->std::fmt::Result{ @@ -767,11 +1033,38 @@ impl std::fmt::Display for Body{ } } +struct VirtualBody<'a>{ + body0:&'a Body, + body1:&'a Body, +} +impl VirtualBody<'_>{ + 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) + } +} + impl Default for PhysicsState{ fn default()->Self{ Self{ spawn_point:Planar64Vec3::int(0,50,0), - body:Body::with_pva(Planar64Vec3::int(0,50,0),Planar64Vec3::int(0,0,0),Planar64Vec3::int(0,-100,0)), + body:Body::new(Planar64Vec3::int(0,50,0),Planar64Vec3::int(0,0,0),Planar64Vec3::int(0,-100,0),Time::ZERO), time:Time::ZERO, style:StyleModifiers::default(), touching:TouchingState::default(), @@ -816,11 +1109,22 @@ impl PhysicsState { pub fn generate_models(&mut self,indexed_models:&crate::model::IndexedModelInstances){ let mut starts=Vec::new(); let mut spawns=Vec::new(); + let mut attr_hash=std::collections::HashMap::new(); for model in &indexed_models.models{ - //make aabb and run vertices to get realistic bounds + let mesh_id=self.models.meshes.len(); + let mut make_mesh=false; for model_instance in &model.instances{ - if let Some(model_physics)=PhysicsModel::from_model(model,model_instance){ - let model_id=self.models.push(model_physics); + if let Ok(physics_attributes)=PhysicsCollisionAttributes::try_from(&model_instance.attributes){ + let attr_id=if let Some(&attr_id)=attr_hash.get(&physics_attributes){ + attr_id + }else{ + let attr_id=self.models.push_attr(physics_attributes.clone()); + attr_hash.insert(physics_attributes,attr_id); + attr_id + }; + let model_physics=PhysicsModel::new(mesh_id,attr_id,model_instance.transform); + make_mesh=true; + let model_id=self.models.push_model(model_physics); for attr in &model_instance.temp_indexing{ match attr{ crate::model::TempIndexedAttributes::Start(s)=>starts.push((model_id,s.clone())), @@ -830,8 +1134,11 @@ impl PhysicsState { } } } + if make_mesh{ + self.models.push_mesh(PhysicsMesh::from(model)); + } } - self.bvh=crate::bvh::generate_bvh(self.models.models.iter().map(|m|m.mesh().clone()).collect()); + self.bvh=crate::bvh::generate_bvh(self.models.aabb_list()); //I don't wanna write structs for temporary structures //this code builds ModeDescriptions from the unsorted lists at the top of the function starts.sort_by_key(|tup|tup.1.mode_id); @@ -883,16 +1190,6 @@ impl PhysicsState { fn set_control(&mut self,control:u32,state:bool){ self.controls=if state{self.controls|control}else{self.controls&!control}; } - fn jump(&mut self){ - match &self.move_state{ - MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>{ - 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; - }, - MoveState::Air|MoveState::Water=>(), - } - } fn next_strafe_instruction(&self)->Option>{ self.style.strafe.as_ref().map(|strafe|{ @@ -935,20 +1232,29 @@ impl PhysicsState { // }); // } - fn refresh_walk_target(&mut self)->Option{ + fn refresh_walk_target(&mut self)->Planar64Vec3{ match &mut self.move_state{ - MoveState::Air|MoveState::Water=>None, - MoveState::Walk(WalkState{normal,state})=>{ - let n=normal; - let a; - (*state,a)=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); - Some(a) + MoveState::Air|MoveState::Water=>self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time), + MoveState::Walk(WalkState{state,contact,jump_direction:_})=>{ + let style_mesh=self.style.mesh(); + let n=contact_normal(&self.models,&style_mesh,contact); + let gravity=self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); + let mut a; + let mut v=self.style.get_walk_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time,&n); + self.touching.constrain_velocity(&self.models,&style_mesh,&mut v); + let normal_accel=-n.dot(gravity)/n.length(); + (*state,a)=WalkEnum::with_target_velocity(&self.body,&self.style,v,&n,self.style.walk_speed,normal_accel); + a }, - MoveState::Ladder(WalkState{normal,state})=>{ - let n=normal; - let a; - (*state,a)=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); - Some(a) + MoveState::Ladder(WalkState{state,contact,jump_direction:_})=>{ + let style_mesh=self.style.mesh(); + let n=contact_normal(&self.models,&style_mesh,contact); + let gravity=self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); + let mut a; + let mut v=self.style.get_ladder_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time,&n); + self.touching.constrain_velocity(&self.models,&style_mesh,&mut v); + (*state,a)=WalkEnum::with_target_velocity(&self.body,&self.style,v,&n,self.style.ladder_speed,self.style.ladder_accel); + a }, } } @@ -966,334 +1272,134 @@ impl PhysicsState { MoveState::Water=>None,//TODO } } - fn mesh(&self) -> TreyMesh { - let mut aabb=TreyMesh::default(); - for vertex in TreyMesh::unit_vertices(){ - aabb.grow(self.body.position+self.style.hitbox_halfsize*vertex); - } - aabb - } - fn predict_collision_end(&self,time:Time,time_limit:Time,collision_data:&RelativeCollision) -> Option> { - //must treat cancollide false objects differently: you may not exit through the same face you entered. - //RelativeCollsion must reference the full model instead of a particular face - //this is Ctrl+C Ctrl+V of predict_collision_start but with v=-v before the calc and t=-t after the calc - //find best t - let mut best_time=time_limit; - let mut exit_face:Option=None; - let mesh0=self.mesh(); - let mesh1=self.models.get(collision_data.model as usize).unwrap().mesh(); - let (v,a)=(-self.body.velocity,self.body.acceleration); - //collect x - match collision_data.face { - TreyMeshFace::Top|TreyMeshFace::Back|TreyMeshFace::Bottom|TreyMeshFace::Front=>{ - for t in zeroes2(mesh0.max.x()-mesh1.min.x(),v.x(),a.x()/2) { - //negative t = back in time - //must be moving towards surface to collide - //must beat the current soonest collision time - //must be moving towards surface - let t_time=self.body.time-Time::from(t); - if time<=t_time&&t_time{ - //generate event if v.x<0||a.x<0 - if -v.x(){ - //generate event if 0{ - for t in zeroes2(mesh0.max.y()-mesh1.min.y(),v.y(),a.y()/2) { - //negative t = back in time - //must be moving towards surface to collide - //must beat the current soonest collision time - //must be moving towards surface - let t_time=self.body.time-Time::from(t); - if time<=t_time&&t_time{ - //generate event if v.y<0||a.y<0 - if -v.y(){ - //generate event if 0{ - for t in zeroes2(mesh0.max.z()-mesh1.min.z(),v.z(),a.z()/2) { - //negative t = back in time - //must be moving towards surface to collide - //must beat the current soonest collision time - //must be moving towards surface - let t_time=self.body.time-Time::from(t); - if time<=t_time&&t_time{ - //generate event if v.z<0||a.z<0 - if -v.z(){ - //generate event if 0 Option> { - let mesh0=self.mesh(); - let mesh1=self.models.get(model_id).unwrap().mesh(); - let (p,v,a,body_time)=(self.body.position,self.body.velocity,self.body.acceleration,self.body.time); - //find best t - let mut best_time=time_limit; - let mut best_face:Option=None; - //collect x - for t in zeroes2(mesh0.max.x()-mesh1.min.x(),v.x(),a.x()/2) { - //must collide now or in the future - //must beat the current soonest collision time - //must be moving towards surface - let t_time=body_time+Time::from(t); - if time<=t_time&&t_time for PhysicsState { +impl crate::instruction::InstructionEmitter for PhysicsState{ //this little next instruction function can cache its return value and invalidate the cached value by watching the State. - fn next_instruction(&self,time_limit:Time) -> Option> { + fn next_instruction(&self,time_limit:Time)->Option>{ //JUST POLLING!!! NO MUTATION let mut collector = crate::instruction::InstructionCollector::new(time_limit); - //check for collision stop instructions with curent contacts - //TODO: make this into a touching.next_instruction(&mut collector) member function - for (_,collision_data) in &self.touching.contacts { - collector.collect(self.predict_collision_end(self.time,time_limit,collision_data)); - } - // for collision_data in &self.intersects{ - // collector.collect(self.predict_collision_end2(self.time,time_limit,collision_data)); - // } - //check for collision start instructions (against every part in the game with no optimization!!) - let mut aabb=crate::aabb::Aabb::default(); - aabb.grow(self.body.extrapolated_position(self.time)); - aabb.grow(self.body.extrapolated_position(time_limit)); - aabb.inflate(self.style.hitbox_halfsize); - self.bvh.the_tester(&aabb,&mut |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.next_move_instruction()); + + let style_mesh=self.style.mesh(); + //check for collision ends + self.touching.predict_collision_end(&mut collector,&self.models,&style_mesh,&self.body,self.time); + //check for collision starts + let mut aabb=crate::aabb::Aabb::default(); + self.body.grow_aabb(&mut aabb,self.time,collector.time()); + aabb.inflate(self.style.hitbox.halfsize); + //common body + let relative_body=VirtualBody::relative(&Body::default(),&self.body).body(self.time); + self.bvh.the_tester(&aabb,&mut |id|{ + //no checks are needed because of the time limits. + let model_mesh=self.models.mesh(id); + let minkowski=crate::model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,&style_mesh); + collector.collect(minkowski.predict_collision_in(&relative_body,collector.time()) + //temp (?) code to avoid collision loops + .map_or(None,|(face,time)|if time==self.time{None}else{Some((face,time))}) + .map(|(face,time)|{ + TimedInstruction{time,instruction:PhysicsInstruction::CollisionStart(match self.models.attr(id){ + PhysicsCollisionAttributes::Contact{contacting:_,general:_}=>Collision::Contact(ContactCollision{model_id:id,face_id:face}), + PhysicsCollisionAttributes::Intersect{intersecting:_,general:_}=>Collision::Intersect(IntersectCollision{model_id:id}), + })} + })); + }); collector.instruction() } } -fn teleport(body:&mut Body,touching:&mut TouchingState,style:&StyleModifiers,point:Planar64Vec3)->MoveState{ +fn get_walk_state(move_state:&MoveState)->Option<&WalkState>{ + match move_state{ + MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>Some(walk_state), + MoveState::Air|MoveState::Water=>None, + } +} + +fn jumped_velocity(models:&PhysicsModels,style:&StyleModifiers,walk_state:&WalkState,v:&mut Planar64Vec3){ + let jump_dir=match &walk_state.jump_direction{ + JumpDirection::FromContactNormal=>contact_normal(models,&style.mesh(),&walk_state.contact), + &JumpDirection::Exactly(dir)=>dir, + }; + *v=*v+jump_dir*(style.get_jump_deltav()/jump_dir.length()); +} + +fn contact_normal(models:&PhysicsModels,style_mesh:&TransformedMesh,contact:&ContactCollision)->Planar64Vec3{ + let model_mesh=models.mesh(contact.model_id); + let minkowski=crate::model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,style_mesh); + minkowski.face_nd(contact.face_id).0 +} + +fn set_position(body:&mut Body,touching:&mut TouchingState,point:Planar64Vec3)->Planar64Vec3{ + //test intersections at new position + //hovering above the surface 0 units is not intersecting. you will fall into it just fine body.position=point; //manual clear //for c in contacts{process_instruction(CollisionEnd(c))} touching.clear(); - body.acceleration=style.gravity; - MoveState::Air //TODO: calculate contacts and determine the actual state //touching.recalculate(body); + point } -fn teleport_to_spawn(body:&mut Body,touching:&mut TouchingState,style:&StyleModifiers,mode:&crate::model::ModeDescription,models:&PhysicsModels,stage_id:u32)->Option{ - let model=models.get(*mode.get_spawn_model_id(stage_id)? as usize)?; - let point=model.transform.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox_halfsize.y()+Planar64::ONE/16); - Some(teleport(body,touching,style,point)) +fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,style_mesh:&TransformedMesh,v:Planar64Vec3)->bool{ + //This is not correct but is better than what I have + let mut culled=false; + touching.contacts.retain(|contact|{ + let n=contact_normal(models,style_mesh,contact); + let r=n.dot(v)<=Planar64::ZERO; + if !r{ + culled=true; + println!("set_velocity_cull contact={:?}",contact); + } + r + }); + set_velocity(body,touching,models,style_mesh,v); + culled +} +fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,style_mesh:&TransformedMesh,mut v:Planar64Vec3)->Planar64Vec3{ + touching.constrain_velocity(models,style_mesh,&mut v); + body.velocity=v; + v +} +fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,style_mesh:&TransformedMesh,a:Planar64Vec3)->bool{ + //This is not correct but is better than what I have + let mut culled=false; + touching.contacts.retain(|contact|{ + let n=contact_normal(models,style_mesh,contact); + let r=n.dot(a)<=Planar64::ZERO; + if !r{ + culled=true; + println!("set_acceleration_cull contact={:?}",contact); + } + r + }); + set_acceleration(body,touching,models,style_mesh,a); + culled +} +fn set_acceleration(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,style_mesh:&TransformedMesh,mut a:Planar64Vec3)->Planar64Vec3{ + touching.constrain_acceleration(models,style_mesh,&mut a); + body.acceleration=a; + a } -fn run_teleport_behaviour(teleport_behaviour:&Option,game:&mut GameMechanicsState,models:&PhysicsModels,modes:&Modes,style:&StyleModifiers,touching:&mut TouchingState,body:&mut Body,model:&PhysicsModel)->Option{ +fn teleport(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,style:&StyleModifiers,point:Planar64Vec3)->MoveState{ + set_position(body,touching,point); + set_acceleration(body,touching,models,&style.mesh(),style.gravity); + MoveState::Air +} +fn teleport_to_spawn(body:&mut Body,touching:&mut TouchingState,style:&StyleModifiers,mode:&crate::model::ModeDescription,models:&PhysicsModels,stage_id:u32)->Option{ + let model=models.model(*mode.get_spawn_model_id(stage_id)? as usize); + let point=model.transform.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox.halfsize.y()+Planar64::ONE/16); + Some(teleport(body,touching,models,style,point)) +} + +fn run_teleport_behaviour(teleport_behaviour:&Option,game:&mut GameMechanicsState,models:&PhysicsModels,modes:&Modes,style:&StyleModifiers,touching:&mut TouchingState,body:&mut Body,model_id:usize)->Option{ + //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 match teleport_behaviour{ Some(crate::model::TeleportBehaviour::StageElement(stage_element))=>{ if stage_element.force||game.stage_idNone, + &crate::model::StageElementBehaviour::Checkpoint=>{ + // let mode=modes.get_mode(stage_element.mode_id)?; + // if mode.ordered_checkpoint_id.map_or(true,|id|id{ + if checkpoint_id{ + //count model id in accumulated unordered checkpoints + game.unordered_checkpoints.insert(model_id); + None + }, &crate::model::StageElementBehaviour::JumpLimit(jump_limit)=>{ //let count=game.jump_counts.get(&model.id); //TODO None }, - &crate::model::StageElementBehaviour::Checkpoint{ordered_checkpoint_id,unordered_checkpoint_count}=>{ - if (ordered_checkpoint_id.is_none()||ordered_checkpoint_id.is_some_and(|id|id{ - let origin_model=model; + let origin_model=models.model(model_id); let destination_model=models.get_wormhole_model(wormhole.destination_model_id)?; //ignore the transform for now - Some(teleport(body,touching,style,body.position-origin_model.transform.translation+destination_model.transform.translation)) + Some(teleport(body,touching,models,style,body.position-origin_model.transform.translation+destination_model.transform.translation)) } None=>None, } @@ -1333,15 +1459,15 @@ fn run_teleport_behaviour(teleport_behaviour:&Option for PhysicsState { fn process_instruction(&mut self, ins:TimedInstruction) { - match &ins.instruction { + match &ins.instruction{ PhysicsInstruction::Input(PhysicsInputInstruction::Idle) |PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_)) |PhysicsInstruction::Input(PhysicsInputInstruction::ReplaceMouse(_,_)) - |PhysicsInstruction::StrafeTick => (), + |PhysicsInstruction::StrafeTick=>(), _=>println!("{}|{:?}",ins.time,ins.instruction), } //selectively update body - match &ins.instruction { + match &ins.instruction{ PhysicsInstruction::Input(PhysicsInputInstruction::Idle)=>self.time=ins.time,//idle simply updates time PhysicsInstruction::Input(_) |PhysicsInstruction::ReachWalkTargetVelocity @@ -1349,121 +1475,125 @@ impl crate::instruction::InstructionConsumer for PhysicsStat |PhysicsInstruction::CollisionEnd(_) |PhysicsInstruction::StrafeTick=>self.advance_time(ins.time), } - match ins.instruction { - PhysicsInstruction::CollisionStart(c) => { - let model=c.model(&self.models).unwrap(); - match &model.attributes{ - PhysicsCollisionAttributes::Contact{contacting,general}=>{ + match ins.instruction{ + PhysicsInstruction::CollisionStart(c)=>{ + let style_mesh=self.style.mesh(); + let model_id=c.model_id(); + match (self.models.attr(model_id),&c){ + (PhysicsCollisionAttributes::Contact{contacting,general},Collision::Contact(contact))=>{ let mut v=self.body.velocity; + let normal=contact_normal(&self.models,&style_mesh,contact); match &contacting.contact_behaviour{ Some(crate::model::ContactingBehaviour::Surf)=>println!("I'm surfing!"), + Some(crate::model::ContactingBehaviour::Cling)=>println!("Unimplemented!"), &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)); + //velocity and normal are facing opposite directions so this is inherently negative. + let d=normal.dot(v)*(Planar64::ONE+Planar64::raw(elasticity as i64+1)); + v+=normal*(d/normal.dot(normal)); }, Some(crate::model::ContactingBehaviour::Ladder(contacting_ladder))=>{ if contacting_ladder.sticky{ //kill v + //actually you could do this with a booster attribute :thinking: 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)); + let gravity=self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); + let mut target_velocity=self.style.get_ladder_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time,&normal); + self.touching.constrain_velocity(&self.models,&style_mesh,&mut target_velocity); + let (walk_state,a)=WalkState::ladder(&self.body,&self.style,gravity,target_velocity,contact.clone(),&normal); self.move_state=MoveState::Ladder(walk_state); - self.body.acceleration=a; + set_acceleration(&mut self.body,&self.touching,&self.models,&style_mesh,a); } - None=>match &c.face { - TreyMeshFace::Top => { - //ground - 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; - }, - _ => (), + None=>if self.style.surf_slope.map_or(true,|s|contact_normal(&self.models,&style_mesh,contact).walkable(s,Planar64Vec3::Y)){ + //ground + let gravity=self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); + let mut target_velocity=self.style.get_walk_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time,&normal); + self.touching.constrain_velocity(&self.models,&style_mesh,&mut target_velocity); + let (walk_state,a)=WalkState::ground(&self.body,&self.style,gravity,target_velocity,contact.clone(),&normal); + self.move_state=MoveState::Walk(walk_state); + set_acceleration(&mut self.body,&self.touching,&self.models,&style_mesh,a); }, } //check ground - self.touching.insert_contact(c.model,c); + self.touching.insert(c); //I love making functions with 10 arguments to dodge the borrow checker - run_teleport_behaviour(&general.teleport_behaviour,&mut self.game,&self.models,&self.modes,&self.style,&mut self.touching,&mut self.body,model); + run_teleport_behaviour(&general.teleport_behaviour,&mut self.game,&self.models,&self.modes,&self.style,&mut self.touching,&mut self.body,model_id); //flatten v - self.touching.constrain_velocity(&self.models,&mut v); + self.touching.constrain_velocity(&self.models,&style_mesh,&mut v); match &general.booster{ Some(booster)=>{ + //DELETE THIS when boosters get converted to height machines match booster{ &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=>(), } + let calc_move=if self.style.get_control(StyleModifiers::CONTROL_JUMP,self.controls){ + if let Some(walk_state)=get_walk_state(&self.move_state){ + jumped_velocity(&self.models,&self.style,walk_state,&mut v); + set_velocity_cull(&mut self.body,&mut self.touching,&self.models,&style_mesh,v) + }else{false} + }else{false}; match &general.trajectory{ Some(trajectory)=>{ match trajectory{ crate::model::GameMechanicSetTrajectory::AirTime(_) => todo!(), crate::model::GameMechanicSetTrajectory::Height(_) => todo!(), crate::model::GameMechanicSetTrajectory::TargetPointTime { target_point: _, time: _ } => todo!(), - crate::model::GameMechanicSetTrajectory::TrajectoryTargetPoint { target_point: _, speed: _, trajectory_choice: _ } => todo!(), + crate::model::GameMechanicSetTrajectory::TargetPointSpeed { target_point: _, speed: _, trajectory_choice: _ } => todo!(), &crate::model::GameMechanicSetTrajectory::Velocity(velocity)=>v=velocity, crate::model::GameMechanicSetTrajectory::DotVelocity { direction: _, dot: _ } => todo!(), } - self.touching.constrain_velocity(&self.models,&mut v); }, None=>(), } - self.body.velocity=v; - if self.style.get_control(StyleModifiers::CONTROL_JUMP,self.controls){ - self.jump(); - } - if let Some(a)=self.refresh_walk_target(){ - self.body.acceleration=a; + set_velocity(&mut self.body,&self.touching,&self.models,&style_mesh,v); + //not sure if or is correct here + if calc_move||Planar64::ZERO{ + (PhysicsCollisionAttributes::Intersect{intersecting: _,general},Collision::Intersect(intersect))=>{ //I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop - self.touching.insert_intersect(c.model,c); - run_teleport_behaviour(&general.teleport_behaviour,&mut self.game,&self.models,&self.modes,&self.style,&mut self.touching,&mut self.body,model); + self.touching.insert(c); + run_teleport_behaviour(&general.teleport_behaviour,&mut self.game,&self.models,&self.modes,&self.style,&mut self.touching,&mut self.body,model_id); }, + _=>panic!("invalid pair"), } }, PhysicsInstruction::CollisionEnd(c) => { - let model=c.model(&self.models).unwrap(); - match &model.attributes{ - PhysicsCollisionAttributes::Contact{contacting: _,general: _}=>{ - self.touching.remove_contact(c.model);//remove contact before calling contact_constrain_acceleration - let mut a=self.style.gravity; - if let Some(rocket_force)=self.style.rocket_force{ - a+=self.style.get_propulsion_control_dir(&self.camera,self.controls,&self.next_mouse,self.time)*rocket_force; - } - self.touching.constrain_acceleration(&self.models,&mut a); - self.body.acceleration=a; + match self.models.attr(c.model_id()){ + PhysicsCollisionAttributes::Contact{contacting:_,general:_}=>{ + self.touching.remove(&c);//remove contact before calling contact_constrain_acceleration //check ground - //self.touching.get_move_state(); - match &c.face { - TreyMeshFace::Top => { - //TODO: make this more advanced checking contacts - self.move_state=MoveState::Air; - }, - _=>if let Some(a)=self.refresh_walk_target(){ - self.body.acceleration=a; - }, - } + (self.move_state,self.body.acceleration)=self.touching.get_move_state(&self.body,&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); }, - PhysicsCollisionAttributes::Intersect{intersecting: _,general: _}=>{ - self.touching.remove_intersect(c.model); + PhysicsCollisionAttributes::Intersect{intersecting:_,general:_}=>{ + self.touching.remove(&c); }, } }, PhysicsInstruction::StrafeTick => { - 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); - let d=self.body.velocity.dot(control_dir); - if d { @@ -1473,13 +1603,12 @@ impl crate::instruction::InstructionConsumer for PhysicsStat match &mut walk_state.state{ WalkEnum::Reached=>(), WalkEnum::Transient(walk_target)=>{ + let style_mesh=self.style.mesh(); //precisely set velocity - let mut a=self.style.gravity; - self.touching.constrain_acceleration(&self.models,&mut a); - self.body.acceleration=a; - let mut v=walk_target.velocity; - self.touching.constrain_velocity(&self.models,&mut v); - self.body.velocity=v; + let a=Planar64Vec3::ZERO;//ignore gravity for now. + set_acceleration(&mut self.body,&self.touching,&self.models,&style_mesh,a); + let v=walk_target.velocity; + set_velocity(&mut self.body,&self.touching,&self.models,&style_mesh,v); walk_state.state=WalkEnum::Reached; }, } @@ -1505,7 +1634,13 @@ impl crate::instruction::InstructionConsumer for PhysicsStat PhysicsInputInstruction::SetMoveDown(s) => self.set_control(StyleModifiers::CONTROL_MOVEDOWN,s), PhysicsInputInstruction::SetJump(s) => { self.set_control(StyleModifiers::CONTROL_JUMP,s); - self.jump(); + if let Some(walk_state)=get_walk_state(&self.move_state){ + let mut v=self.body.velocity; + jumped_velocity(&self.models,&self.style,walk_state,&mut v); + if set_velocity_cull(&mut self.body,&mut self.touching,&self.models,&self.style.mesh(),v){ + (self.move_state,self.body.acceleration)=self.touching.get_move_state(&self.body,&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); + } + } refresh_walk_target=false; }, PhysicsInputInstruction::SetZoom(s) => { @@ -1513,28 +1648,236 @@ impl crate::instruction::InstructionConsumer for PhysicsStat refresh_walk_target=false; }, PhysicsInputInstruction::Reset => { - //temp - self.body.position=self.spawn_point; - self.body.velocity=Planar64Vec3::ZERO; - //manual clear //for c in self.contacts{process_instruction(CollisionEnd(c))} - self.touching.clear(); - self.body.acceleration=self.style.gravity; - self.move_state=MoveState::Air; + //it matters which of these runs first, but I have not thought it through yet as it doesn't matter yet + set_position(&mut self.body,&mut self.touching,self.spawn_point); + set_velocity(&mut self.body,&self.touching,&self.models,&self.style.mesh(),Planar64Vec3::ZERO); + (self.move_state,self.body.acceleration)=self.touching.get_move_state(&self.body,&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); refresh_walk_target=false; }, PhysicsInputInstruction::Idle => {refresh_walk_target=false;},//literally idle! } if refresh_walk_target{ - if let Some(a)=self.refresh_walk_target(){ - self.body.acceleration=a; - }else if let Some(rocket_force)=self.style.rocket_force{ - let mut a=self.style.gravity; - a+=self.style.get_propulsion_control_dir(&self.camera,self.controls,&self.next_mouse,self.time)*rocket_force; - self.touching.constrain_acceleration(&self.models,&mut a); - self.body.acceleration=a; + let a=self.refresh_walk_target(); + if set_acceleration_cull(&mut self.body,&mut self.touching,&self.models,&self.style.mesh(),a){ + (self.move_state,self.body.acceleration)=self.touching.get_move_state(&self.body,&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); } } }, } } } + +#[allow(dead_code)] +fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option