From d3f84c2dbddd9601cbdb9b0ec65830174a2c3729 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 9 Aug 2024 14:16:10 -0700 Subject: [PATCH] physics: refactor models and attributes with type safety make invalid states unrepresentable!!! --- Cargo.lock | 20 +-- Cargo.toml | 2 +- src/physics.rs | 426 ++++++++++++++++++++++++++++++------------------- 3 files changed, 273 insertions(+), 175 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb9d1263..4a1ed90b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1900,9 +1900,9 @@ dependencies = [ [[package]] name = "strafesnet_bsp_loader" -version = "0.1.4" +version = "0.1.5" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" -checksum = "b6b3e1324034abfd648e339580989f8f2c30ac2009296229349d88b8fcb4eedd" +checksum = "35ee2c534efa039ad17ca41893ba1d75fafff014076353ac676c73fc808b9e44" dependencies = [ "glam", "strafesnet_common", @@ -1912,9 +1912,9 @@ dependencies = [ [[package]] name = "strafesnet_common" -version = "0.3.0" +version = "0.4.0" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" -checksum = "1077d45a0b064964906a57de765a5a2bfe47b41f2f807d13b18c70765e76d3dd" +checksum = "ea4126f6fbf9aecf89c9e319290f0221d177dcaa8659b4b9e3d82acc37829f12" dependencies = [ "arrayvec", "bitflags 2.6.0", @@ -1924,9 +1924,9 @@ dependencies = [ [[package]] name = "strafesnet_deferred_loader" -version = "0.3.2" +version = "0.3.3" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" -checksum = "9d5ad437524fb201fd5be68f76c53dd831e81ccad4655e19e3d1ca201863b566" +checksum = "596aba6d2747818781336ad95a1ee496e37f70052fd625a299fc7a555a6938d4" dependencies = [ "lazy-regex", "strafesnet_common", @@ -1935,9 +1935,9 @@ dependencies = [ [[package]] name = "strafesnet_rbx_loader" -version = "0.3.3" +version = "0.3.4" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" -checksum = "3a910867e1f5ab2d9cc9c178973aee7fa029547e27465e47fea2eb99b860bb81" +checksum = "6cd7fb0eca01ccd382067924e5fad15844f55a6bcc7c14c0e57a171298263a3e" dependencies = [ "bytemuck", "glam", @@ -1952,9 +1952,9 @@ dependencies = [ [[package]] name = "strafesnet_snf" -version = "0.1.2" +version = "0.1.3" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" -checksum = "d12fc351b2af5fd7a8183175d55ac43e21eb8fd1f76cc70cd4713c0d1a556c96" +checksum = "a9ae481152d0389be29967e1d5f0377498df8ff9638175d56cd8e2c2e6982bfa" dependencies = [ "binrw 0.14.0", "id", diff --git a/Cargo.toml b/Cargo.toml index dd397687..b193cd17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ id = { version = "0.1.0", registry = "strafesnet" } parking_lot = "0.12.1" pollster = "0.3.0" strafesnet_bsp_loader = { version = "0.1.3", registry = "strafesnet", optional = true } -strafesnet_common = { version = "0.3.0", registry = "strafesnet" } +strafesnet_common = { version = "0.4.0", registry = "strafesnet" } strafesnet_deferred_loader = { version = "0.3.1", features = ["legacy"], registry = "strafesnet", optional = true } strafesnet_rbx_loader = { version = "0.3.2", registry = "strafesnet", optional = true } strafesnet_snf = { version = "0.1.2", registry = "strafesnet", optional = true } diff --git a/src/physics.rs b/src/physics.rs index b1c79c2b..9caf2318 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -189,33 +189,69 @@ fn ladder_things(ladder_settings:&gameplay_style::LadderSettings,contact:&Contac #[derive(Default)] struct PhysicsModels{ meshes:HashMap, - models:HashMap, - //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:HashMap, + contact_models:HashMap, + intersect_models:HashMap, + contact_attributes:HashMap, + intersect_attributes:HashMap, } impl PhysicsModels{ fn clear(&mut self){ self.meshes.clear(); - self.models.clear(); - self.attributes.clear(); + self.contact_models.clear(); + self.intersect_models.clear(); + self.contact_attributes.clear(); + self.intersect_attributes.clear(); } - //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,convex_mesh_id:ConvexMeshId)->TransformedMesh{ - let model=&self.models[&convex_mesh_id.model_id]; + let (mesh_id,transform)=match convex_mesh_id.model_id{ + PhysicsModelId::Contact(model_id)=>{ + let model=&self.contact_models[&model_id]; + (&model.mesh_id,&model.transform) + }, + PhysicsModelId::Intersect(model_id)=>{ + let model=&self.intersect_models[&model_id]; + (&model.mesh_id,&model.transform) + }, + }; TransformedMesh::new( - self.meshes[&model.mesh_id].submesh_view(convex_mesh_id.submesh_id), + self.meshes[mesh_id].submesh_view(convex_mesh_id.submesh_id), + transform + ) + } + //it's a bit weird to have three functions, but it's always going to be one of these + fn contact_mesh(&self,contact:&ContactCollision)->TransformedMesh{ + let model=&self.contact_models[&contact.model_id]; + TransformedMesh::new( + self.meshes[&model.mesh_id].submesh_view(contact.submesh_id), &model.transform ) } - fn model(&self,model_id:PhysicsModelId)->&PhysicsModel{ - &self.models[&model_id] + fn intersect_mesh(&self,intersect:&IntersectCollision)->TransformedMesh{ + let model=&self.intersect_models[&intersect.model_id]; + TransformedMesh::new( + self.meshes[&model.mesh_id].submesh_view(intersect.submesh_id), + &model.transform + ) } - fn attr(&self,model_id:PhysicsModelId)->&PhysicsCollisionAttributes{ - &self.attributes[&self.models[&model_id].attr_id] + fn get_model_transform(&self,model_id:ModelId)->Option<&PhysicsMeshTransform>{ + //ModelId can possibly be a decoration + self.contact_models.get(&ContactModelId::new(model_id.get())).map_or_else( + ||self.intersect_models.get(&IntersectModelId::new(model_id.get())) + .map(|model|&model.transform), + |model|Some(&model.transform) + ) + } + fn contact_model(&self,model_id:ContactModelId)->&ContactModel{ + &self.contact_models[&model_id] + } + fn intersect_model(&self,model_id:IntersectModelId)->&IntersectModel{ + &self.intersect_models[&model_id] + } + fn contact_attr(&self,model_id:ContactModelId)->&gameplay_attributes::ContactAttributes{ + &self.contact_attributes[&self.contact_models[&model_id].attr_id] + } + fn intersect_attr(&self,model_id:IntersectModelId)->&gameplay_attributes::IntersectAttributes{ + &self.intersect_attributes[&self.intersect_models[&model_id].attr_id] } } @@ -560,14 +596,8 @@ impl PhysicsOutputState{ #[derive(Clone,Hash,Eq,PartialEq)] enum PhysicsCollisionAttributes{ - Contact{//track whether you are contacting the object - contacting:gameplay_attributes::ContactingAttributes, - general:gameplay_attributes::GeneralAttributes, - }, - Intersect{//track whether you are intersecting the object - intersecting:gameplay_attributes::IntersectingAttributes, - general:gameplay_attributes::GeneralAttributes, - }, + Contact(gameplay_attributes::ContactAttributes), + Intersect(gameplay_attributes::IntersectAttributes), } struct NonPhysicsError; impl TryFrom<&gameplay_attributes::CollisionAttributes> for PhysicsCollisionAttributes{ @@ -575,85 +605,100 @@ impl TryFrom<&gameplay_attributes::CollisionAttributes> for PhysicsCollisionAttr fn try_from(value:&gameplay_attributes::CollisionAttributes)->Result{ match value{ gameplay_attributes::CollisionAttributes::Decoration=>Err(NonPhysicsError), - gameplay_attributes::CollisionAttributes::Contact{contacting,general}=>Ok(Self::Contact{contacting:contacting.clone(),general:general.clone()}), - gameplay_attributes::CollisionAttributes::Intersect{intersecting,general}=>Ok(Self::Intersect{intersecting:intersecting.clone(),general:general.clone()}), + gameplay_attributes::CollisionAttributes::Contact(attr)=>Ok(Self::Contact(attr.clone())), + gameplay_attributes::CollisionAttributes::Intersect(attr)=>Ok(Self::Intersect(attr.clone())), } } } #[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)] -struct PhysicsAttributesId(u32); -impl Into for PhysicsAttributesId{ +struct ContactAttributesId(u32); +impl Into for ContactAttributesId{ fn into(self)->CollisionAttributesId{ CollisionAttributesId::new(self.0) } } -impl From for PhysicsAttributesId{ +impl From for ContactAttributesId{ fn from(value:CollisionAttributesId)->Self{ Self::new(value.get()) } } +#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)] +struct IntersectAttributesId(u32); +impl Into for IntersectAttributesId{ + fn into(self)->CollisionAttributesId{ + CollisionAttributesId::new(self.0) + } +} +impl From for IntersectAttributesId{ + fn from(value:CollisionAttributesId)->Self{ + Self::new(value.get()) + } +} +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +struct ContactModelId(u32); +impl Into for ContactModelId{ + fn into(self)->ModelId{ + ModelId::new(self.get()) + } +} +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +struct IntersectModelId(u32); +impl Into for IntersectModelId{ + fn into(self)->ModelId{ + ModelId::new(self.get()) + } +} +#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] +enum PhysicsModelId{ + Contact(ContactModelId), + Intersect(IntersectModelId), +} +impl Into for PhysicsModelId{ + fn into(self)->ModelId{ + ModelId::new(match self{ + PhysicsModelId::Contact(model_id)=>model_id.get(), + PhysicsModelId::Intersect(model_id)=>model_id.get(), + }) + } +} //unique physics meshes indexed by this -#[derive(Debug,Default,Clone,Copy,Eq,Hash,PartialEq)] +#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] struct ConvexMeshId{ model_id:PhysicsModelId, submesh_id:PhysicsSubmeshId, } -#[derive(Debug,Default,Clone,Copy,Hash,id::Id,Eq,PartialEq)] -struct PhysicsModelId(u32); -impl Into for PhysicsModelId{ - fn into(self)->ModelId{ - ModelId::new(self.0) - } -} -impl From for PhysicsModelId{ - fn from(value:ModelId)->Self{ - Self::new(value.get()) - } -} -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. +struct ContactModel{ mesh_id:PhysicsMeshId, - //put these up on the Model (data normalization) - attr_id:PhysicsAttributesId, + attr_id:ContactAttributesId, + transform:PhysicsMeshTransform, +} +struct IntersectModel{ + mesh_id:PhysicsMeshId, + attr_id:IntersectAttributesId, transform:PhysicsMeshTransform, } -impl PhysicsModel{ - const fn new(mesh_id:PhysicsMeshId,attr_id:PhysicsAttributesId,transform:PhysicsMeshTransform)->Self{ - Self{ - mesh_id, - attr_id, - transform, - } - } -} - #[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] -pub struct ContactCollision{ +struct ContactCollision{ face_id:model_physics::MinkowskiFace, - convex_mesh_id:ConvexMeshId, + model_id:ContactModelId, + submesh_id:PhysicsSubmeshId, } #[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] -pub struct IntersectCollision{ - convex_mesh_id:ConvexMeshId, +struct IntersectCollision{ + model_id:IntersectModelId, + submesh_id:PhysicsSubmeshId, } #[derive(Debug,Clone,Eq,Hash,PartialEq)] -pub enum Collision{ +enum Collision{ Contact(ContactCollision), Intersect(IntersectCollision), } impl Collision{ - const fn convex_mesh_id(&self)->ConvexMeshId{ - match self{ - &Collision::Contact(ContactCollision{convex_mesh_id,face_id:_}) - |&Collision::Intersect(IntersectCollision{convex_mesh_id})=>convex_mesh_id, - } - } - const fn face_id(&self)->Option{ - match self{ - &Collision::Contact(ContactCollision{convex_mesh_id:_,face_id})=>Some(face_id), - &Collision::Intersect(IntersectCollision{convex_mesh_id:_})=>None, + const fn new(convex_mesh_id:ConvexMeshId,face_id:model_physics::MinkowskiFace)->Self{ + match convex_mesh_id.model_id{ + PhysicsModelId::Contact(model_id)=>Collision::Contact(ContactCollision{model_id,submesh_id:convex_mesh_id.submesh_id,face_id}), + PhysicsModelId::Intersect(model_id)=>Collision::Intersect(IntersectCollision{model_id,submesh_id:convex_mesh_id.submesh_id}), } } } @@ -686,25 +731,13 @@ impl TouchingState{ } //add accelerators for contact in &self.contacts{ - match models.attr(contact.convex_mesh_id.model_id){ - PhysicsCollisionAttributes::Contact{contacting:_,general}=>{ - match &general.accelerator{ - Some(accelerator)=>a+=accelerator.acceleration, - None=>(), - } - }, - _=>panic!("impossible touching state"), + if let Some(accelerator)=&models.contact_attr(contact.model_id).general.accelerator{ + a+=accelerator.acceleration; } } for intersect in &self.intersects{ - match models.attr(intersect.convex_mesh_id.model_id){ - PhysicsCollisionAttributes::Intersect{intersecting:_,general}=>{ - match &general.accelerator{ - Some(accelerator)=>a+=accelerator.acceleration, - None=>(), - } - }, - _=>panic!("impossible touching state"), + if let Some(accelerator)=&models.intersect_attr(intersect.model_id).general.accelerator{ + a+=accelerator.acceleration; } } //TODO: add water @@ -734,26 +767,26 @@ impl TouchingState{ 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.convex_mesh_id); + let model_mesh=models.contact_mesh(contact); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(_face,time)|{ TimedInstruction{ time, instruction:PhysicsInternalInstruction::CollisionEnd( - Collision::Contact(ContactCollision{convex_mesh_id:contact.convex_mesh_id,face_id:contact.face_id}) + Collision::Contact(*contact) ), } })); } for intersect in &self.intersects{ //detect model collision in reverse - let model_mesh=models.mesh(intersect.convex_mesh_id); + let model_mesh=models.intersect_mesh(intersect); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(_face,time)|{ TimedInstruction{ time, instruction:PhysicsInternalInstruction::CollisionEnd( - Collision::Intersect(IntersectCollision{convex_mesh_id:intersect.convex_mesh_id}) + Collision::Intersect(*intersect) ), } })); @@ -1020,15 +1053,27 @@ impl PhysicsContext{ /// use with caution, this is the only non-instruction way to mess with physics pub fn generate_models(&mut self,map:&map::CompleteMap){ self.state.clear(); - self.data.modes=map.modes.clone(); - for mode in &mut self.data.modes.modes{ + let mut modes=map.modes.clone(); + for mode in &mut modes.modes{ mode.denormalize_data(); } - let mut used_attributes=Vec::new(); + let mut used_contact_attributes=Vec::new(); + let mut used_intersect_attributes=Vec::new(); + + //temporary type for type safety lol + #[derive(Clone,Copy,Hash,Eq,PartialEq)] + enum PhysicsAttributesId{ + Contact(ContactAttributesId), + Intersect(IntersectAttributesId), + } + + let mut contact_models=HashMap::new(); + let mut intersect_models=HashMap::new(); + let mut physics_attr_id_from_model_attr_id=HashMap::::new(); let mut used_meshes=Vec::new(); let mut physics_mesh_id_from_model_mesh_id=HashMap::::new(); - self.data.models.models=map.models.iter().enumerate().filter_map(|(model_id,model)|{ + for (model_id,model) in map.models.iter().enumerate(){ //TODO: use .entry().or_insert_with(||{ let attr_id=if let Some(&attr_id)=physics_attr_id_from_model_attr_id.get(&model.attributes){ attr_id @@ -1036,14 +1081,24 @@ impl PhysicsContext{ //check if it's real match map.attributes.get(model.attributes.get() as usize).and_then(|m_attr|{ PhysicsCollisionAttributes::try_from(m_attr).map_or(None,|p_attr|{ - let attr_id=PhysicsAttributesId::new(used_attributes.len() as u32); - used_attributes.push(p_attr); + let attr_id=match p_attr{ + PhysicsCollisionAttributes::Contact(attr)=>{ + let attr_id=ContactAttributesId::new(used_contact_attributes.len() as u32); + used_contact_attributes.push(attr); + PhysicsAttributesId::Contact(attr_id) + }, + PhysicsCollisionAttributes::Intersect(attr)=>{ + let attr_id=IntersectAttributesId::new(used_intersect_attributes.len() as u32); + used_intersect_attributes.push(attr); + PhysicsAttributesId::Intersect(attr_id) + }, + }; physics_attr_id_from_model_attr_id.insert(model.attributes,attr_id); Some(attr_id) }) }){ Some(attr_id)=>attr_id, - None=>return None, + None=>continue, } }; let mesh_id=if let Some(&mesh_id)=physics_mesh_id_from_model_mesh_id.get(&model.mesh){ @@ -1064,19 +1119,44 @@ impl PhysicsContext{ } }){ Some(mesh_id)=>mesh_id, - None=>return None, + None=>continue, } }; - Some((PhysicsModelId::new(model_id as u32),PhysicsModel::new(mesh_id,attr_id,PhysicsMeshTransform::new(model.transform)))) - }).collect(); - self.data.models.attributes=used_attributes.into_iter().enumerate().map(|(attr_id,attr)|(PhysicsAttributesId::new(attr_id as u32),attr)).collect(); - self.data.models.meshes=used_meshes.into_iter().enumerate().map(|(mesh_id,mesh)|(PhysicsMeshId::new(mesh_id as u32),mesh)).collect(); - let convex_mesh_aabb_list=self.data.models.models.iter() - .flat_map(|(&model_id,model)|{ - self.data.models.meshes[&model.mesh_id].submesh_views() + let transform=PhysicsMeshTransform::new(model.transform); + match attr_id{ + PhysicsAttributesId::Contact(attr_id)=>{ + contact_models.insert(ContactModelId::new(model_id as u32),ContactModel{ + mesh_id, + attr_id, + transform, + }); + }, + PhysicsAttributesId::Intersect(attr_id)=>{ + intersect_models.insert(IntersectModelId::new(model_id as u32),IntersectModel{ + mesh_id, + attr_id, + transform, + }); + }, + } + } + let meshes:HashMap=used_meshes.into_iter() + .enumerate() + .map(|(mesh_id,mesh)| + (PhysicsMeshId::new(mesh_id as u32),mesh) + ).collect(); + let convex_mesh_aabb_list= + //map the two lists into a single type so they can be processed with one closure + contact_models.iter().map(|(&model_id,model)| + (PhysicsModelId::Contact(model_id),&model.mesh_id,&model.transform) + ).chain(intersect_models.iter().map(|(&model_id,model)| + (PhysicsModelId::Intersect(model_id),&model.mesh_id,&model.transform) + )) + .flat_map(|(model_id,mesh_id,transform)|{ + meshes[mesh_id].submesh_views() .enumerate().map(move|(submesh_id,view)|{ let mut aabb=aabb::Aabb::default(); - let transformed_mesh=TransformedMesh::new(view,&model.transform); + let transformed_mesh=TransformedMesh::new(view,transform); for v in transformed_mesh.verts(){ aabb.grow(v); } @@ -1086,8 +1166,28 @@ impl PhysicsContext{ },aabb) }) }).collect(); - self.data.bvh=bvh::generate_bvh(convex_mesh_aabb_list); - println!("Physics Objects: {}",self.data.models.models.len()); + let bvh=bvh::generate_bvh(convex_mesh_aabb_list); + let model_count=contact_models.len()+intersect_models.len(); + let models=PhysicsModels{ + meshes, + contact_models, + intersect_models, + contact_attributes:used_contact_attributes.into_iter() + .enumerate() + .map(|(attr_id,attr)| + (ContactAttributesId::new(attr_id as u32),attr) + ).collect(), + intersect_attributes:used_intersect_attributes.into_iter() + .enumerate() + .map(|(attr_id,attr)| + (IntersectAttributesId::new(attr_id as u32),attr) + ).collect(), + }; + self.data.bvh=bvh; + self.data.models=models; + self.data.modes=modes; + //hitbox_mesh is unchanged + println!("Physics Objects: {}",model_count); } //tickless gaming @@ -1134,19 +1234,22 @@ impl PhysicsContext{ collector.collect(minkowski.predict_collision_in(relative_body,collector.time()) //temp (?) code to avoid collision loops .map_or(None,|(face,time)|if time<=state.time{None}else{Some((face,time))}) - .map(|(face,time)|{ - TimedInstruction{time,instruction:PhysicsInternalInstruction::CollisionStart(match data.models.attr(convex_mesh_id.model_id){ - PhysicsCollisionAttributes::Contact{contacting:_,general:_}=>Collision::Contact(ContactCollision{convex_mesh_id,face_id:face}), - PhysicsCollisionAttributes::Intersect{intersecting:_,general:_}=>Collision::Intersect(IntersectCollision{convex_mesh_id}), - })} - })); + .map(|(face,time)| + TimedInstruction{ + time, + instruction:PhysicsInternalInstruction::CollisionStart( + Collision::new(convex_mesh_id,face) + ) + } + ) + ); }); collector.instruction() } fn contact_normal(models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{ - let model_mesh=models.mesh(contact.convex_mesh_id); + let model_mesh=models.contact_mesh(contact); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); minkowski.face_nd(contact.face_id).0 } @@ -1206,16 +1309,26 @@ fn teleport(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,sty MoveState::Air } fn teleport_to_spawn(body:&mut Body,touching:&mut TouchingState,style:&StyleModifiers,hitbox_mesh:&HitboxMesh,mode:&gameplay_modes::Mode,models:&PhysicsModels,stage_id:gameplay_modes::StageId)->Option{ - let model=models.model(mode.get_spawn_model_id(stage_id)?.into()); - let point=model.transform.vertex.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox.halfsize.y()+Planar64::ONE/16); + let transform=models.get_model_transform(mode.get_spawn_model_id(stage_id)?)?; + let point=transform.vertex.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox.halfsize.y()+Planar64::ONE/16); Some(teleport(body,touching,models,style,hitbox_mesh,point)) } -fn run_teleport_behaviour(wormhole:&Option,models:&PhysicsModels,mode:&gameplay_modes::Mode,style:&StyleModifiers,hitbox_mesh:&HitboxMesh,mode_state:&mut ModeState,touching:&mut TouchingState,body:&mut Body,convex_mesh_id:ConvexMeshId)->Option{ +fn run_teleport_behaviour( + wormhole:&Option, + models:&PhysicsModels, + mode:&gameplay_modes::Mode, + style:&StyleModifiers, + hitbox_mesh:&HitboxMesh, + mode_state:&mut ModeState, + touching:&mut TouchingState, + body:&mut Body, + model_id:ModelId, +)->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 - if let Some(stage_element)=mode.get_element(convex_mesh_id.model_id.into()){ + if let Some(stage_element)=mode.get_element(model_id){ if let Some(stage)=mode.get_stage(stage_element.stage_id()){ if mode_state.get_stage_id(),models gameplay_modes::StageElementBehaviour::Checkpoint=>{ //each of these checks if the model is actually a valid respective checkpoint object //accumulate sequential ordered checkpoints - mode_state.accumulate_ordered_checkpoint(&stage,convex_mesh_id.model_id.into()); + mode_state.accumulate_ordered_checkpoint(&stage,model_id); //insert model id in accumulated unordered checkpoints - mode_state.accumulate_unordered_checkpoint(&stage,convex_mesh_id.model_id.into()); + mode_state.accumulate_unordered_checkpoint(&stage,model_id); }, } } } match wormhole{ &Some(gameplay_attributes::Wormhole{destination_model})=>{ - let origin_model=models.model(convex_mesh_id.model_id); - let destination_model=models.model(destination_model.into()); + let origin=models.get_model_transform(model_id)?; + let destination=models.get_model_transform(destination_model)?; //ignore the transform for now - Some(teleport(body,touching,models,style,hitbox_mesh,body.position-origin_model.transform.vertex.translation+destination_model.transform.vertex.translation)) + Some(teleport(body,touching,models,style,hitbox_mesh,body.position-origin.vertex.translation+destination.vertex.translation)) } None=>None, } @@ -1274,8 +1387,7 @@ fn run_teleport_behaviour(wormhole:&Option,models fn collision_start_contact( state:&mut PhysicsState, data:&PhysicsData, - contacting:&gameplay_attributes::ContactingAttributes, - general:&gameplay_attributes::GeneralAttributes, + attr:&gameplay_attributes::ContactAttributes, contact:ContactCollision, ){ let incident_velocity=state.body.velocity; @@ -1283,7 +1395,7 @@ fn collision_start_contact( state.touching.insert(Collision::Contact(contact)); //clip v set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,incident_velocity); - match &contacting.contact_behaviour{ + match &attr.contacting.contact_behaviour{ Some(gameplay_attributes::ContactingBehaviour::Surf)=>println!("I'm surfing!"), Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"), &Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{ @@ -1315,16 +1427,16 @@ fn collision_start_contact( } //I love making functions with 10 arguments to dodge the borrow checker if let Some(mode)=data.modes.get_mode(state.mode_state.get_mode_id()){ - run_teleport_behaviour(&general.wormhole,&data.models,mode,&state.style,&data.hitbox_mesh,&mut state.mode_state,&mut state.touching,&mut state.body,contact.convex_mesh_id); + run_teleport_behaviour(&attr.general.wormhole,&data.models,mode,&state.style,&data.hitbox_mesh,&mut state.mode_state,&mut state.touching,&mut state.body,contact.model_id.into()); } if state.style.get_control(Controls::Jump,state.input_state.controls){ if let (Some(jump_settings),Some(walk_state))=(&state.style.jump,state.move_state.get_walk_state()){ let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact); - let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,general.booster.as_ref()); + let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,attr.general.booster.as_ref()); state.cull_velocity(data,jumped_velocity); } } - match &general.trajectory{ + match &attr.general.trajectory{ Some(trajectory)=>{ match trajectory{ gameplay_attributes::SetTrajectory::AirTime(_)=>todo!(), @@ -1347,18 +1459,17 @@ fn collision_start_contact( fn collision_start_intersect( state:&mut PhysicsState, data:&PhysicsData, - intersecting:&gameplay_attributes::IntersectingAttributes, - general:&gameplay_attributes::GeneralAttributes, + attr:&gameplay_attributes::IntersectAttributes, intersect:IntersectCollision, ){ //I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop state.touching.insert(Collision::Intersect(intersect)); //insta booster! - if let Some(booster)=&general.booster{ + if let Some(booster)=&attr.general.booster{ state.cull_velocity(data,booster.boost(state.body.velocity)); } if let Some(mode)=data.modes.get_mode(state.mode_state.get_mode_id()){ - let zone=mode.get_zone(intersect.convex_mesh_id.model_id.into()); + let zone=mode.get_zone(intersect.model_id.into()); match zone{ Some(gameplay_modes::Zone::Start)=>{ println!("@@@@ Starting new run!"); @@ -1373,15 +1484,14 @@ fn collision_start_intersect( Some(gameplay_modes::Zone::Anticheat)=>state.run.flag(run::FlagReason::Anticheat), None=>(), } - run_teleport_behaviour(&general.wormhole,&data.models,mode,&state.style,&data.hitbox_mesh,&mut state.mode_state,&mut state.touching,&mut state.body,intersect.convex_mesh_id); + run_teleport_behaviour(&attr.general.wormhole,&data.models,mode,&state.style,&data.hitbox_mesh,&mut state.mode_state,&mut state.touching,&mut state.body,intersect.model_id.into()); } } fn collision_end_contact( state:&mut PhysicsState, data:&PhysicsData, - _contacting:&gameplay_attributes::ContactingAttributes, - _general:&gameplay_attributes::GeneralAttributes, + _attr:&gameplay_attributes::ContactAttributes, contact:ContactCollision, ){ state.touching.remove(&Collision::Contact(contact));//remove contact before calling contact_constrain_acceleration @@ -1399,13 +1509,12 @@ fn collision_end_contact( fn collision_end_intersect( state:&mut PhysicsState, data:&PhysicsData, - _intersecting:&gameplay_attributes::IntersectingAttributes, - _general:&gameplay_attributes::GeneralAttributes, + _attr:&gameplay_attributes::IntersectAttributes, intersect:IntersectCollision, ){ state.touching.remove(&Collision::Intersect(intersect)); if let Some(mode)=data.modes.get_mode(state.mode_state.get_mode_id()){ - let zone=mode.get_zone(intersect.convex_mesh_id.model_id.into()); + let zone=mode.get_zone(intersect.model_id.into()); match zone{ Some(gameplay_modes::Zone::Start)=>{ match state.run.start(state.time){ @@ -1429,23 +1538,13 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim state.body.advance_time(state.time); } match ins.instruction{ - PhysicsInternalInstruction::CollisionStart(collision)=>{ - match (data.models.attr(collision.convex_mesh_id().model_id),&collision){ - (PhysicsCollisionAttributes::Contact{contacting,general},&Collision::Contact(contact))=> - collision_start_contact(state,data,contacting,general,contact), - (PhysicsCollisionAttributes::Intersect{intersecting,general},&Collision::Intersect(intersect))=> - collision_start_intersect(state,data,intersecting,general,intersect), - _=>panic!("invalid pair"), - } + PhysicsInternalInstruction::CollisionStart(collision)=>match collision{ + Collision::Contact(contact)=>collision_start_contact(state,data,data.models.contact_attr(contact.model_id),contact), + Collision::Intersect(intersect)=>collision_start_intersect(state,data,data.models.intersect_attr(intersect.model_id),intersect), }, - PhysicsInternalInstruction::CollisionEnd(collision)=>{ - match (data.models.attr(collision.convex_mesh_id().model_id),&collision){ - (PhysicsCollisionAttributes::Contact{contacting,general},&Collision::Contact(contact))=> - collision_end_contact(state,data,contacting,general,contact), - (PhysicsCollisionAttributes::Intersect{intersecting,general},&Collision::Intersect(intersect))=> - collision_end_intersect(state,data,intersecting,general,intersect), - _=>panic!("invalid pair"), - } + PhysicsInternalInstruction::CollisionEnd(collision)=>match collision{ + Collision::Contact(contact)=>collision_end_contact(state,data,data.models.contact_attr(contact.model_id),contact), + Collision::Intersect(intersect)=>collision_end_intersect(state,data,data.models.intersect_attr(intersect.model_id),intersect), }, PhysicsInternalInstruction::StrafeTick=>{ //TODO make this less huge @@ -1550,10 +1649,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI if let Some(walk_state)=state.move_state.get_walk_state(){ if let Some(jump_settings)=&state.style.jump{ let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact); - let booster_option=match data.models.attr(walk_state.contact.convex_mesh_id.model_id){ - PhysicsCollisionAttributes::Contact{contacting:_,general}=>general.booster.as_ref(), - PhysicsCollisionAttributes::Intersect{..}=>None, - }; + let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref(); let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option); state.cull_velocity(&data,jumped_velocity); } @@ -1571,10 +1667,12 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI }, PhysicsInputInstruction::Restart=>{ //teleport to start zone - let spawn_point=data.modes.get_mode(state.mode_state.get_mode_id()).map(|mode| + let spawn_point=data.modes.get_mode(state.mode_state.get_mode_id()).and_then(|mode| //TODO: spawn at the bottom of the start zone plus the hitbox size //TODO: set camera andles to face the same way as the start zone - data.models.model(mode.get_start().into()).transform.vertex.translation + data.models.get_model_transform(mode.get_start().into()).map(|transform| + transform.vertex.translation + ) ).unwrap_or(Planar64Vec3::ZERO); set_position(&mut state.body,&mut state.touching,spawn_point); set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,Planar64Vec3::ZERO);