diff --git a/src/model_physics.rs b/src/model_physics.rs index fdefd00..8ea1400 100644 --- a/src/model_physics.rs +++ b/src/model_physics.rs @@ -17,26 +17,31 @@ pub trait DirectedEdge{ } } -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -pub struct VertId(u32); -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -pub struct EdgeId(u32); -/// DirectedEdgeId refers to an EdgeId when undirected. -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -pub struct DirectedEdgeId(u32); -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -pub struct FaceId(u32); +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct MeshVertId(u32); +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct MeshFaceId(u32); -impl UndirectedEdge for EdgeId{ - type DirectedEdge=DirectedEdgeId; - fn as_directed(&self,parity:bool)->DirectedEdgeId{ - DirectedEdgeId(self.0|((parity as u32)<<(u32::BITS-1))) +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct SubmeshVertId(u32); +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct SubmeshEdgeId(u32); +/// DirectedEdgeId refers to an EdgeId when undirected. +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct SubmeshDirectedEdgeId(u32); +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct SubmeshFaceId(u32); + +impl UndirectedEdge for SubmeshEdgeId{ + type DirectedEdge=SubmeshDirectedEdgeId; + fn as_directed(&self,parity:bool)->SubmeshDirectedEdgeId{ + SubmeshDirectedEdgeId(self.0|((parity as u32)<<(u32::BITS-1))) } } -impl DirectedEdge for DirectedEdgeId{ - type UndirectedEdge=EdgeId; - fn as_undirected(&self)->EdgeId{ - EdgeId(self.0&!(1<<(u32::BITS-1))) +impl DirectedEdge for SubmeshDirectedEdgeId{ + type UndirectedEdge=SubmeshEdgeId; + fn as_undirected(&self)->SubmeshEdgeId{ + SubmeshEdgeId(self.0&!(1<<(u32::BITS-1))) } fn parity(&self)->bool{ self.0&(1<<(u32::BITS-1))!=0 @@ -74,16 +79,16 @@ pub trait MeshQuery{ fn vert_faces(&self,vert_id:VERT)->Cow>; } struct FaceRefs{ - edges:Vec, + edges:Vec, //verts:Vec, } struct EdgeRefs{ - faces:[FaceId;2],//left, right - verts:[VertId;2],//bottom, top + faces:[SubmeshFaceId;2],//left, right + verts:[SubmeshVertId;2],//bottom, top } struct VertRefs{ - faces:Vec, - edges:Vec, + faces:Vec, + edges:Vec, } struct PhysicsMeshData{ //this contains all real and virtual faces used in both the complete mesh and convex submeshes @@ -91,20 +96,24 @@ struct PhysicsMeshData{ //all remaining faces are virtual to operate internal logic of the face crawler //and cannot be part of a physics collision //virtual faces are only used in convex submeshes. - faces:Vec, - verts:Vec, + faces:Vec,//MeshFaceId indexes this list + verts:Vec,//MeshVertId indexes this list } struct PhysicsMeshTopology{ //mapping of local ids to PhysicsMeshData ids - faces:Vec, - verts:Vec, + faces:Vec,//SubmeshFaceId indexes this list + verts:Vec,//SubmeshVertId indexes this list //all ids here are local to this object face_topology:Vec, edge_topology:Vec, vert_topology:Vec, } +#[derive(id::Id)] +pub struct PhysicsMeshId(u32); +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct PhysicsSubmeshId(u32); pub struct PhysicsMesh{ - mesh_data:PhysicsMeshData, + data:PhysicsMeshData, //index 0 is the complete mesh. //index 1-2+ is convex submeshes. //Most objects in roblox maps are already convex, so the list length is 1 @@ -115,7 +124,7 @@ pub struct PhysicsMesh{ impl PhysicsMesh{ pub fn unit_cube()->Self{ //go go gadget debug print mesh - let mesh_data=PhysicsMeshData{ + let data=PhysicsMeshData{ faces:vec![ Face{normal:Planar64Vec3::raw( 4294967296, 0, 0),dot:Planar64::raw(4294967296)}, Face{normal:Planar64Vec3::raw( 0, 4294967296, 0),dot:Planar64::raw(4294967296)}, @@ -136,43 +145,43 @@ impl PhysicsMesh{ ] }; let mesh_topology=PhysicsMeshTopology{ - faces:(0..mesh_data.faces.len()).map(FaceId).collect(), - verts:(0..mesh_data.verts.len()).map(VertId).collect(), + faces:(0..data.faces.len() as u32).map(MeshFaceId::new).collect(), + verts:(0..data.verts.len() as u32).map(MeshVertId::new).collect(), face_topology:vec![ - FaceRefs{edges:vec![DirectedEdgeId(9223372036854775808),DirectedEdgeId(9223372036854775809),DirectedEdgeId(9223372036854775810),DirectedEdgeId(3)]}, - FaceRefs{edges:vec![DirectedEdgeId(9223372036854775812),DirectedEdgeId(9223372036854775813),DirectedEdgeId(6),DirectedEdgeId(1)]}, - FaceRefs{edges:vec![DirectedEdgeId(7),DirectedEdgeId(2),DirectedEdgeId(9223372036854775814),DirectedEdgeId(9223372036854775816)]}, - FaceRefs{edges:vec![DirectedEdgeId(8),DirectedEdgeId(5),DirectedEdgeId(9223372036854775817),DirectedEdgeId(10)]}, - FaceRefs{edges:vec![DirectedEdgeId(9223372036854775815),DirectedEdgeId(9223372036854775818),DirectedEdgeId(11),DirectedEdgeId(9223372036854775811)]}, - FaceRefs{edges:vec![DirectedEdgeId(4),DirectedEdgeId(0),DirectedEdgeId(9223372036854775819),DirectedEdgeId(9)]} + FaceRefs{edges:vec![SubmeshDirectedEdgeId(9223372036854775808),SubmeshDirectedEdgeId(9223372036854775809),SubmeshDirectedEdgeId(9223372036854775810),SubmeshDirectedEdgeId(3)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId(9223372036854775812),SubmeshDirectedEdgeId(9223372036854775813),SubmeshDirectedEdgeId(6),SubmeshDirectedEdgeId(1)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId(7),SubmeshDirectedEdgeId(2),SubmeshDirectedEdgeId(9223372036854775814),SubmeshDirectedEdgeId(9223372036854775816)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId(8),SubmeshDirectedEdgeId(5),SubmeshDirectedEdgeId(9223372036854775817),SubmeshDirectedEdgeId(10)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId(9223372036854775815),SubmeshDirectedEdgeId(9223372036854775818),SubmeshDirectedEdgeId(11),SubmeshDirectedEdgeId(9223372036854775811)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId(4),SubmeshDirectedEdgeId(0),SubmeshDirectedEdgeId(9223372036854775819),SubmeshDirectedEdgeId(9)]} ], edge_topology:vec![ - EdgeRefs{faces:[FaceId(0),FaceId(5)],verts:[VertId(0),VertId(1)]}, - EdgeRefs{faces:[FaceId(0),FaceId(1)],verts:[VertId(1),VertId(2)]}, - EdgeRefs{faces:[FaceId(0),FaceId(2)],verts:[VertId(2),VertId(3)]}, - EdgeRefs{faces:[FaceId(4),FaceId(0)],verts:[VertId(0),VertId(3)]}, - EdgeRefs{faces:[FaceId(1),FaceId(5)],verts:[VertId(1),VertId(4)]}, - EdgeRefs{faces:[FaceId(1),FaceId(3)],verts:[VertId(4),VertId(5)]}, - EdgeRefs{faces:[FaceId(2),FaceId(1)],verts:[VertId(2),VertId(5)]}, - EdgeRefs{faces:[FaceId(4),FaceId(2)],verts:[VertId(3),VertId(6)]}, - EdgeRefs{faces:[FaceId(2),FaceId(3)],verts:[VertId(5),VertId(6)]}, - EdgeRefs{faces:[FaceId(3),FaceId(5)],verts:[VertId(4),VertId(7)]}, - EdgeRefs{faces:[FaceId(4),FaceId(3)],verts:[VertId(6),VertId(7)]}, - EdgeRefs{faces:[FaceId(5),FaceId(4)],verts:[VertId(0),VertId(7)]} + EdgeRefs{faces:[SubmeshFaceId(0),SubmeshFaceId(5)],verts:[SubmeshVertId(0),SubmeshVertId(1)]}, + EdgeRefs{faces:[SubmeshFaceId(0),SubmeshFaceId(1)],verts:[SubmeshVertId(1),SubmeshVertId(2)]}, + EdgeRefs{faces:[SubmeshFaceId(0),SubmeshFaceId(2)],verts:[SubmeshVertId(2),SubmeshVertId(3)]}, + EdgeRefs{faces:[SubmeshFaceId(4),SubmeshFaceId(0)],verts:[SubmeshVertId(0),SubmeshVertId(3)]}, + EdgeRefs{faces:[SubmeshFaceId(1),SubmeshFaceId(5)],verts:[SubmeshVertId(1),SubmeshVertId(4)]}, + EdgeRefs{faces:[SubmeshFaceId(1),SubmeshFaceId(3)],verts:[SubmeshVertId(4),SubmeshVertId(5)]}, + EdgeRefs{faces:[SubmeshFaceId(2),SubmeshFaceId(1)],verts:[SubmeshVertId(2),SubmeshVertId(5)]}, + EdgeRefs{faces:[SubmeshFaceId(4),SubmeshFaceId(2)],verts:[SubmeshVertId(3),SubmeshVertId(6)]}, + EdgeRefs{faces:[SubmeshFaceId(2),SubmeshFaceId(3)],verts:[SubmeshVertId(5),SubmeshVertId(6)]}, + EdgeRefs{faces:[SubmeshFaceId(3),SubmeshFaceId(5)],verts:[SubmeshVertId(4),SubmeshVertId(7)]}, + EdgeRefs{faces:[SubmeshFaceId(4),SubmeshFaceId(3)],verts:[SubmeshVertId(6),SubmeshVertId(7)]}, + EdgeRefs{faces:[SubmeshFaceId(5),SubmeshFaceId(4)],verts:[SubmeshVertId(0),SubmeshVertId(7)]} ], vert_topology:vec![ - VertRefs{faces:vec![FaceId(0),FaceId(4),FaceId(5)],edges:vec![DirectedEdgeId(9223372036854775811),DirectedEdgeId(9223372036854775819),DirectedEdgeId(9223372036854775808)]}, - VertRefs{faces:vec![FaceId(0),FaceId(5),FaceId(1)],edges:vec![DirectedEdgeId(9223372036854775812),DirectedEdgeId(0),DirectedEdgeId(9223372036854775809)]}, - VertRefs{faces:vec![FaceId(0),FaceId(2),FaceId(1)],edges:vec![DirectedEdgeId(1),DirectedEdgeId(9223372036854775810),DirectedEdgeId(9223372036854775814)]}, - VertRefs{faces:vec![FaceId(0),FaceId(2),FaceId(4)],edges:vec![DirectedEdgeId(2),DirectedEdgeId(3),DirectedEdgeId(9223372036854775815)]}, - VertRefs{faces:vec![FaceId(3),FaceId(5),FaceId(1)],edges:vec![DirectedEdgeId(4),DirectedEdgeId(9223372036854775817),DirectedEdgeId(9223372036854775813)]}, - VertRefs{faces:vec![FaceId(2),FaceId(3),FaceId(1)],edges:vec![DirectedEdgeId(5),DirectedEdgeId(6),DirectedEdgeId(9223372036854775816)]}, - VertRefs{faces:vec![FaceId(2),FaceId(3),FaceId(4)],edges:vec![DirectedEdgeId(7),DirectedEdgeId(8),DirectedEdgeId(9223372036854775818)]}, - VertRefs{faces:vec![FaceId(4),FaceId(3),FaceId(5)],edges:vec![DirectedEdgeId(10),DirectedEdgeId(11),DirectedEdgeId(9)]} + VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(4),SubmeshFaceId(5)],edges:vec![SubmeshDirectedEdgeId(9223372036854775811),SubmeshDirectedEdgeId(9223372036854775819),SubmeshDirectedEdgeId(9223372036854775808)]}, + VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(5),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId(9223372036854775812),SubmeshDirectedEdgeId(0),SubmeshDirectedEdgeId(9223372036854775809)]}, + VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(2),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId(1),SubmeshDirectedEdgeId(9223372036854775810),SubmeshDirectedEdgeId(9223372036854775814)]}, + VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(2),SubmeshFaceId(4)],edges:vec![SubmeshDirectedEdgeId(2),SubmeshDirectedEdgeId(3),SubmeshDirectedEdgeId(9223372036854775815)]}, + VertRefs{faces:vec![SubmeshFaceId(3),SubmeshFaceId(5),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId(4),SubmeshDirectedEdgeId(9223372036854775817),SubmeshDirectedEdgeId(9223372036854775813)]}, + VertRefs{faces:vec![SubmeshFaceId(2),SubmeshFaceId(3),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId(5),SubmeshDirectedEdgeId(6),SubmeshDirectedEdgeId(9223372036854775816)]}, + VertRefs{faces:vec![SubmeshFaceId(2),SubmeshFaceId(3),SubmeshFaceId(4)],edges:vec![SubmeshDirectedEdgeId(7),SubmeshDirectedEdgeId(8),SubmeshDirectedEdgeId(9223372036854775818)]}, + VertRefs{faces:vec![SubmeshFaceId(4),SubmeshFaceId(3),SubmeshFaceId(5)],edges:vec![SubmeshDirectedEdgeId(10),SubmeshDirectedEdgeId(11),SubmeshDirectedEdgeId(9)]} ] }; Self{ - mesh_data, + data, submeshes:vec![mesh_topology], } } @@ -180,12 +189,12 @@ impl PhysicsMesh{ Self::unit_cube() } pub fn mesh_data(&self)->&PhysicsMeshData{ - &self.mesh_data + &self.data } pub fn complete_mesh(&self)->&PhysicsMeshTopology{ &self.submeshes[0] } - pub fn convex_submeshes(&self)->&[PhysicsMeshTopology]{ + pub fn submeshes(&self)->&[PhysicsMeshTopology]{ if self.submeshes.len()==1{ //the complete mesh is already a convex mesh &self.submeshes[0..0] @@ -193,18 +202,30 @@ impl PhysicsMesh{ &self.submeshes[1..] } } + pub fn submesh_view(&self,submesh_id:PhysicsSubmeshId)->PhysicsMeshView{ + PhysicsMeshView{ + data:&self.data, + topology:&self.submeshes()[submesh_id.get() as usize], + } + } + pub fn submesh_views(&self)->impl Iterator{ + self.submeshes().iter().map(|topology|PhysicsMeshView{ + data:&self.data, + topology, + }) + } } //mesh builder code #[derive(Default,Clone)] struct VertRefGuy{ - edges:std::collections::HashSet, - faces:std::collections::HashSet, + edges:std::collections::HashSet, + faces:std::collections::HashSet, } #[derive(Clone,Hash,Eq,PartialEq)] -struct EdgeRefVerts([VertId;2]); +struct EdgeRefVerts([SubmeshVertId;2]); impl EdgeRefVerts{ - fn new(v0:VertId,v1:VertId)->(Self,bool){ + fn new(v0:SubmeshVertId,v1:SubmeshVertId)->(Self,bool){ (if v0.0Self{ - Self([FaceId(0);2]) + Self([SubmeshFaceId(0);2]) } - fn push(&mut self,i:usize,face_id:FaceId){ + fn push(&mut self,i:usize,face_id:SubmeshFaceId){ self.0[i]=face_id; } } -struct FaceRefEdges(Vec); +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){ + fn push(&mut self,edge_ref_verts:EdgeRefVerts)->(&mut EdgeRefFaces,SubmeshEdgeId){ let edge_id=if let Some(&edge_id)=self.edge_id_from_guy.get(&edge_ref_verts){ edge_id }else{ @@ -237,7 +258,7 @@ impl EdgePool{ 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)) + (&mut unsafe{self.edge_guys.get_unchecked_mut(edge_id)}.1,SubmeshEdgeId::new(edge_id as u32)) } } impl From<&model::IndexedModel> for PhysicsMesh{ @@ -250,7 +271,7 @@ impl From<&model::IndexedModel> for PhysicsMesh{ let mut faces=Vec::new(); let mut face_ref_guys=Vec::new(); for group in &indexed_model.polygon_groups{for poly_vertices in group.polys(){ - let face_id=FaceId(face_i); + let face_id=SubmeshFaceId::new(face_i); //one face per poly let mut normal=Planar64Vec3::ZERO; let len=poly_vertices.len(); @@ -266,7 +287,7 @@ impl From<&model::IndexedModel> for PhysicsMesh{ (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_verts,is_sorted)=EdgeRefVerts::new(SubmeshVertId::new(vert0_id as u32),SubmeshVertId::new(vert1_id as u32)); 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 @@ -311,108 +332,129 @@ impl From<&model::IndexedModel> for PhysicsMesh{ } } +struct PhysicsMeshView<'a>{ + data:&'a PhysicsMeshData, + topology:&'a PhysicsMeshTopology, +} impl PhysicsMeshView<'_>{ pub fn verts<'a>(&'a self)->impl Iterator+'a{ - self.mesh_data.verts.iter().map(|Vert(pos)|*pos) + self.data.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) +impl MeshQuery for PhysicsMeshView<'_>{ + fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){ + let face_idx=self.topology.faces[face_id.get() as usize].get() as usize; + (self.data.faces[face_idx].normal,self.data.faces[face_idx].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 vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{ + let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize; + self.data.verts[vert_idx].0 } - fn face_edges(&self,face_id:FaceId)->Cow>{ - Cow::Borrowed(&self.face_topology[face_id.0].edges) + fn face_edges(&self,face_id:SubmeshFaceId)->Cow>{ + Cow::Borrowed(&self.topology.face_topology[face_id.get() as usize].edges) } - fn edge_faces(&self,edge_id:EdgeId)->Cow<[FaceId;2]>{ - Cow::Borrowed(&self.edge_topology[edge_id.0].faces) + fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{ + Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].faces) } - fn edge_verts(&self,edge_id:EdgeId)->Cow<[VertId;2]>{ - Cow::Borrowed(&self.edge_topology[edge_id.0].verts) + fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{ + Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].verts) } - fn vert_edges(&self,vert_id:VertId)->Cow>{ - Cow::Borrowed(&self.vert_topology[vert_id.0].edges) + fn vert_edges(&self,vert_id:SubmeshVertId)->Cow>{ + Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].edges) } - fn vert_faces(&self,vert_id:VertId)->Cow>{ - Cow::Borrowed(&self.vert_topology[vert_id.0].faces) + fn vert_faces(&self,vert_id:SubmeshVertId)->Cow>{ + Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].faces) } } -struct PhysicsMeshView<'a>{ - mesh_data:&'a PhysicsMeshData, - topology:&'a PhysicsMeshTopology, +pub struct PhysicsMeshTransform<'a>{ + vertex:&'a integer::Planar64Affine3, + normal:&'a integer::Planar64Mat3, + det:Planar64, } -struct PhysicsMeshTransform<'a>{ - transform:&'a integer::Planar64Affine3, - normal_transform:&'a integer::Planar64Mat3, - transform_det:Planar64, +impl PhysicsMeshTransform<'_>{ + pub fn new<'a>( + vertex:&'a integer::Planar64Affine3, + normal:&'a integer::Planar64Mat3, + det:Planar64 + )->PhysicsMeshTransform<'a>{ + PhysicsMeshTransform{ + vertex, + normal, + det, + } + } } pub struct TransformedMesh<'a>{ - mesh:PhysicsMeshView<'a>, + view:PhysicsMeshView<'a>, transform:PhysicsMeshTransform<'a>, } impl TransformedMesh<'_>{ pub fn new<'a>( mesh_data:&'a PhysicsMeshData, topology:&'a PhysicsMeshTopology, - transform:&'a integer::Planar64Affine3, - normal_transform:&'a integer::Planar64Mat3, - transform_det:Planar64, + vertex:&'a integer::Planar64Affine3, + normal:&'a integer::Planar64Mat3, + det:Planar64, )->TransformedMesh<'a>{ TransformedMesh{ - mesh_data, - topology, - transform, - normal_transform, - transform_det, + view:PhysicsMeshView{ + data: mesh_data, + topology, + }, + transform:PhysicsMeshTransform{ + vertex, + normal, + det, + } } } - fn farthest_vert(&self,dir:Planar64Vec3)->VertId{ + fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{ 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 mut best_vert=SubmeshVertId(0); + //this happens to be well-defined. there are no virtual virtices + for (i,vert_id) in self.view.topology.verts.iter().enumerate(){ + let vert=self.view.data.verts[vert_id.get() as usize]; + let p=self.transform.vertex.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=d+transformed_n.dot(self.transform.translation)/self.transform_det; - (transformed_n/self.transform_det,transformed_d) +impl MeshQuery for TransformedMesh<'_>{ + fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){ + let (n,d)=self.view.face_nd(face_id); + let transformed_n=*self.transform.normal*n; + let transformed_d=d+transformed_n.dot(self.transform.vertex.translation)/self.transform.det; + (transformed_n/self.transform.det,transformed_d) } - fn vert(&self,vert_id:VertId)->Planar64Vec3{ - self.transform.transform_point3(self.mesh.vert(vert_id)) + fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{ + self.transform.vertex.transform_point3(self.view.vert(vert_id)) } #[inline] - fn face_edges(&self,face_id:FaceId)->Cow>{ - self.mesh.face_edges(face_id) + fn face_edges(&self,face_id:SubmeshFaceId)->Cow>{ + self.view.face_edges(face_id) } #[inline] - fn edge_faces(&self,edge_id:EdgeId)->Cow<[FaceId;2]>{ - self.mesh.edge_faces(edge_id) + fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{ + self.view.edge_faces(edge_id) } #[inline] - fn edge_verts(&self,edge_id:EdgeId)->Cow<[VertId;2]>{ - self.mesh.edge_verts(edge_id) + fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{ + self.view.edge_verts(edge_id) } #[inline] - fn vert_edges(&self,vert_id:VertId)->Cow>{ - self.mesh.vert_edges(vert_id) + fn vert_edges(&self,vert_id:SubmeshVertId)->Cow>{ + self.view.vert_edges(vert_id) } #[inline] - fn vert_faces(&self,vert_id:VertId)->Cow>{ - self.mesh.vert_faces(vert_id) + fn vert_faces(&self,vert_id:SubmeshVertId)->Cow>{ + self.view.vert_faces(vert_id) } } @@ -422,12 +464,12 @@ impl MeshQuery for TransformedMesh<'_>{ //(vertex,face) #[derive(Clone,Copy)] pub enum MinkowskiVert{ - VertVert(VertId,VertId), + VertVert(SubmeshVertId,SubmeshVertId), } #[derive(Clone,Copy)] pub enum MinkowskiEdge{ - VertEdge(VertId,EdgeId), - EdgeVert(EdgeId,VertId), + VertEdge(SubmeshVertId,SubmeshEdgeId), + EdgeVert(SubmeshEdgeId,SubmeshVertId), //EdgeEdge when edges are parallel } impl UndirectedEdge for MinkowskiEdge{ @@ -441,8 +483,8 @@ impl UndirectedEdge for MinkowskiEdge{ } #[derive(Clone,Copy)] pub enum MinkowskiDirectedEdge{ - VertEdge(VertId,DirectedEdgeId), - EdgeVert(DirectedEdgeId,VertId), + VertEdge(SubmeshVertId,SubmeshDirectedEdgeId), + EdgeVert(SubmeshDirectedEdgeId,SubmeshVertId), //EdgeEdge when edges are parallel } impl DirectedEdge for MinkowskiDirectedEdge{ @@ -462,9 +504,9 @@ impl DirectedEdge for MinkowskiDirectedEdge{ } #[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] pub enum MinkowskiFace{ - VertFace(VertId,FaceId), - EdgeEdge(EdgeId,EdgeId,bool), - FaceVert(FaceId,VertId), + VertFace(SubmeshVertId,SubmeshFaceId), + EdgeEdge(SubmeshEdgeId,SubmeshEdgeId,bool), + FaceVert(SubmeshFaceId,SubmeshVertId), //EdgeFace //FaceEdge //FaceFace diff --git a/src/physics.rs b/src/physics.rs index 8ff0b0b..cedd694 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::collections::HashSet; -use crate::model_physics::{self,PhysicsMesh,TransformedMesh,MeshQuery}; +use crate::model_physics::{self,PhysicsMesh,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId}; use strafesnet_common::bvh; use strafesnet_common::map; use strafesnet_common::aabb; @@ -169,11 +169,10 @@ impl PhysicsModels{ } //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:PhysicsModelId)->TransformedMesh{ - let idx=model_id.get() as usize; - let convex_mesh_id=self.models[idx].convex_mesh_id; + fn mesh(&self,convex_mesh_id:ConvexMeshId)->TransformedMesh{ + let model_idx=convex_mesh_id.model_id.get() as usize; TransformedMesh::new( - &self.meshes[convex_mesh_id.mesh_id.get() as usize].groups[convex_mesh_id.group_id.get() as usize], + &self.meshes[model_idx].submesh_view(convex_mesh_id.submesh_id), &self.models[idx].transform, &self.models[idx].normal_transform, self.models[idx].transform_det, @@ -285,23 +284,19 @@ struct WorldState{} struct HitboxMesh{ halfsize:Planar64Vec3, mesh:PhysicsMesh, - transform:integer::Planar64Affine3, - normal_transform:Planar64Mat3, - transform_det:Planar64, + transform:PhysicsModelTransform, } impl HitboxMesh{ - fn new(mesh:PhysicsMesh,transform:integer::Planar64Affine3)->Self{ + fn new(mesh:PhysicsMesh,vertex_transform:integer::Planar64Affine3)->Self{ //calculate extents let mut aabb=aabb::Aabb::default(); - for vert in mesh.verts(){ - aabb.grow(transform.transform_point3(vert)); + for vert in mesh.complete_mesh_view().verts(){ + aabb.grow(vertex_transform.transform_point3(vert)); } Self{ halfsize:aabb.size()/2, mesh, - transform, - normal_transform:transform.matrix3.inverse_times_det().transpose(), - transform_det:transform.matrix3.determinant(), + transform:PhysicsModelTransform::new(vertex_transform) } } #[inline] @@ -482,15 +477,11 @@ impl TryFrom<&gameplay_attributes::CollisionAttributes> for PhysicsCollisionAttr #[derive(id::Id)] struct PhysicsAttributesId(u32); -//id assigned to deindexed IndexedPhysicsGroup -#[derive(id::Id)] -struct PhysicsMeshId(u32); -#[derive(id::Id)] -struct PhysicsGroupId(u32); //unique physics meshes indexed by this +#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] struct ConvexMeshId{ - model_id:PhysicsModelId,// 1:1 with IndexedModelId - group_id:PhysicsGroupId,// group in model + model_id:PhysicsModelId, + submesh_id:PhysicsSubmeshId, } #[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] struct PhysicsModelId(u32); @@ -504,37 +495,50 @@ impl From for PhysicsModelId{ Self::new(value.get()) } } +pub struct PhysicsModelTransform{ + vertex:integer::Planar64Affine3, + normal:integer::Planar64Mat3, + det:Planar64, +} +impl PhysicsModelTransform{ + pub const fn new(vertex_transform:integer::Planar64Affine3)->Self{ + Self{ + normal:vertex_transform.matrix3.inverse_times_det().transpose(), + det:vertex_transform.matrix3.determinant(), + vertex:vertex_transform, + } + } +} 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_id:PhysicsMeshId, //put these up on the Model (data normalization) attr_id:PhysicsAttributesId, - transform:integer::Planar64Affine3, - normal_transform:integer::Planar64Mat3, - transform_det:Planar64, + transform:PhysicsModelTransform, } impl PhysicsModel{ - pub fn new(mesh_id:PhysicsMeshId,attr_id:PhysicsAttributesId,transform:integer::Planar64Affine3)->Self{ + pub const fn new(mesh_id:PhysicsMeshId,attr_id:PhysicsAttributesId,transform:PhysicsModelTransform)->Self{ Self{ mesh_id, attr_id, transform, - normal_transform:transform.matrix3.inverse_times_det().transpose(), - transform_det:transform.matrix3.determinant(), } } + const fn transform(&self)->&PhysicsModelTransform{ + &self.transform + } } #[derive(Debug,Clone,Eq,Hash,PartialEq)] struct ContactCollision{ face_id:model_physics::MinkowskiFace, - model_id:PhysicsModelId,//using id to avoid lifetimes + convex_mesh_id:ConvexMeshId, } #[derive(Debug,Clone,Eq,Hash,PartialEq)] struct IntersectCollision{ - model_id:PhysicsModelId, + convex_mesh_id:ConvexMeshId, } #[derive(Debug,Clone,Eq,Hash,PartialEq)] enum Collision{ @@ -542,16 +546,16 @@ enum Collision{ Intersect(IntersectCollision), } impl Collision{ - fn model_id(&self)->PhysicsModelId{ + fn convex_mesh_id(&self)->ConvexMeshId{ match self{ - &Collision::Contact(ContactCollision{model_id,face_id:_}) - |&Collision::Intersect(IntersectCollision{model_id})=>model_id, + &Collision::Contact(ContactCollision{convex_mesh_id,face_id:_}) + |&Collision::Intersect(IntersectCollision{convex_mesh_id})=>convex_mesh_id, } } fn face_id(&self)->Option{ match self{ - &Collision::Contact(ContactCollision{model_id:_,face_id})=>Some(face_id), - &Collision::Intersect(IntersectCollision{model_id:_})=>None, + &Collision::Contact(ContactCollision{convex_mesh_id:_,face_id})=>Some(face_id), + &Collision::Intersect(IntersectCollision{convex_mesh_id:_})=>None, } } } @@ -584,7 +588,7 @@ impl TouchingState{ } //add accelerators for contact in &self.contacts{ - match models.attr(contact.model_id){ + match models.attr(contact.convex_mesh_id.model_id){ PhysicsCollisionAttributes::Contact{contacting,general}=>{ match &general.accelerator{ Some(accelerator)=>a+=accelerator.acceleration, @@ -595,7 +599,7 @@ impl TouchingState{ } } for intersect in &self.intersects{ - match models.attr(intersect.model_id){ + match models.attr(intersect.convex_mesh_id.model_id){ PhysicsCollisionAttributes::Intersect{intersecting,general}=>{ match &general.accelerator{ Some(accelerator)=>a+=accelerator.acceleration, @@ -636,7 +640,7 @@ impl TouchingState{ let mut move_state=MoveState::Air; let mut a=gravity; for contact in &self.contacts{ - match models.attr(contact.model_id){ + match models.attr(contact.convex_mesh_id.model_id){ PhysicsCollisionAttributes::Contact{contacting,general}=>{ let normal=contact_normal(models,hitbox_mesh,contact); match &contacting.contact_behaviour{ @@ -674,26 +678,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.model_id); - let minkowski=model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,hitbox_mesh); + let model_mesh=models.mesh(contact.convex_mesh_id); + 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:PhysicsInstruction::CollisionEnd( - Collision::Contact(ContactCollision{model_id:contact.model_id,face_id:contact.face_id}) + Collision::Contact(ContactCollision{convex_mesh_id:contact.convex_mesh_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=model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,hitbox_mesh); + let model_mesh=models.mesh(intersect.convex_mesh_id); + 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:PhysicsInstruction::CollisionEnd( - Collision::Intersect(IntersectCollision{model_id:intersect.model_id}) + Collision::Intersect(IntersectCollision{convex_mesh_id:intersect.convex_mesh_id}) ), } })); @@ -809,7 +813,7 @@ pub struct PhysicsState{ //random collection of contextual data that doesn't belong in PhysicsState pub struct PhysicsData{ //permanent map data - bvh:bvh::BvhNode, + bvh:bvh::BvhNode, modes:gameplay_modes::Modes, //transient map/environment data (open world may load/unload) models:PhysicsModels, @@ -939,33 +943,47 @@ impl PhysicsContext{ }); } - pub fn generate_models(&mut self,map:&map::Map){ + pub fn generate_models(&mut self,map:map::Map){ let mut starts=Vec::new(); let mut spawns=Vec::new(); let mut attr_hash=HashMap::new(); - for model in &map.models{ - let mesh_id=self.models.meshes.len(); + for (model_id,model) in map.models{ + let mesh_id=self.data.models.meshes.len(); let mut make_mesh=false; for model_instance in &model.instances{ 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()); + let attr_id=self.data.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; - self.models.push_model(model_physics); + self.data.models.push_model(model_physics); } } if make_mesh{ - self.models.push_mesh(PhysicsMesh::from(model)); + self.data.models.push_mesh(PhysicsMesh::from(model)); } } - self.bvh=bvh::generate_bvh(self.models.aabb_list(),|i|PhysicsModelId::new(i as u32)); - println!("Physics Objects: {}",self.models.models.len()); + let convex_mesh_aabb_list=self.data.models.models.iter() + .enumerate().flat_map(|(model_id,model)|{ + self.data.models.meshes[model.mesh_id.get() as usize].submesh_views() + .enumerate().map(|(submesh_id,view)|{ + let mut aabb=aabb::Aabb::default(); + for v in view.verts(){ + aabb.grow(v) + } + (ConvexMeshId{ + model_id:PhysicsModelId::new(model_id as u32), + submesh_id:PhysicsSubmeshId::new(submesh_id as u32), + },aabb) + }) + }).collect(); + self.data.bvh=bvh::generate_bvh_node(convex_mesh_aabb_list); + println!("Physics Objects: {}",self.data.models.models.len()); } //tickless gaming @@ -1020,17 +1038,17 @@ impl PhysicsContext{ //common body let relative_body=VirtualBody::relative(&Body::default(),&state.body).body(state.time); let hitbox_mesh=data.hitbox_mesh.transformed_mesh(); - data.bvh.the_tester(&aabb,&mut |id|{ + data.bvh.the_tester(&aabb,&mut |convex_mesh_id|{ //no checks are needed because of the time limits. - let model_mesh=data.models.mesh(id); - let minkowski=model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,&hitbox_mesh); + let model_mesh=data.models.mesh(convex_mesh_id); + let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh); 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:PhysicsInstruction::CollisionStart(match data.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}), + TimedInstruction{time,instruction:PhysicsInstruction::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}), })} })); }); @@ -1053,8 +1071,8 @@ fn jumped_velocity(models:&PhysicsModels,style:&StyleModifiers,hitbox_mesh:&Hitb } fn contact_normal(models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{ - let model_mesh=models.mesh(contact.model_id); - let minkowski=model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,&hitbox_mesh.transformed_mesh()); + let model_mesh=models.mesh(contact.convex_mesh_id); + let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); minkowski.face_nd(contact.face_id).0 } @@ -1116,15 +1134,15 @@ fn teleport(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,sty } 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.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox.halfsize.y()+Planar64::ONE/16); + let point=model.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,game:&mut ModeState,models:&PhysicsModels,mode:&gameplay_modes::Mode,style:&StyleModifiers,hitbox_mesh:&HitboxMesh,touching:&mut TouchingState,body:&mut Body,model_id:PhysicsModelId)->Option{ +fn run_teleport_behaviour(wormhole:&Option,game:&mut ModeState,models:&PhysicsModels,mode:&gameplay_modes::Mode,style:&StyleModifiers,hitbox_mesh:&HitboxMesh,touching:&mut TouchingState,body:&mut Body,convex_mesh_id:ConvexMeshId)->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(model_id.into()){ + if let Some(stage_element)=mode.get_element(convex_mesh_id.model_id.into()){ let stage=mode.get_stage(stage_element.stage_id)?; if stage_element.force||game.stage_id,game:& }, } if let Some(next_checkpoint)=stage.ordered_checkpoints.get(&game.next_ordered_checkpoint_id){ - if model_id==next_checkpoint{ + if convex_mesh_id==next_checkpoint{ //if you hit the next number in a sequence of ordered checkpoints //increment the current checkpoint id game.next_ordered_checkpoint_id=gameplay_modes::CheckpointId::new(game.next_ordered_checkpoint_id.get()+1); } } - if stage.unordered_checkpoints.contains(model_id.into()){ + if stage.unordered_checkpoints.contains(convex_mesh_id.model_id.into()){ //count model id in accumulated unordered checkpoints - game.unordered_checkpoints.insert(model_id.into()); + game.unordered_checkpoints.insert(convex_mesh_id.model_id.into()); } } match wormhole{ &Some(gameplay_attributes::Wormhole{destination_model})=>{ - let origin_model=models.model(model_id); + let origin_model=models.model(convex_mesh_id.model_id); let destination_model=models.model(destination_model.into()); //ignore the transform for now - Some(teleport(body,touching,models,style,hitbox_mesh,body.position-origin_model.transform.translation+destination_model.transform.translation)) + Some(teleport(body,touching,models,style,hitbox_mesh,body.position-origin_model.transform.vertex.translation+destination_model.transform.vertex.translation)) } None=>None, } @@ -1192,8 +1210,8 @@ fn run_teleport_behaviour(wormhole:&Option,game:& } match ins.instruction{ PhysicsInstruction::CollisionStart(c)=>{ - let model_id=c.model_id(); - match (data.models.attr(model_id),&c){ + let convex_mesh_id=c.convex_mesh_id(); + match (data.models.attr(convex_mesh_id.model_id),&c){ (PhysicsCollisionAttributes::Contact{contacting,general},Collision::Contact(contact))=>{ let mut v=state.body.velocity; let normal=contact_normal(&data.models,&data.hitbox_mesh,contact); @@ -1232,7 +1250,7 @@ fn run_teleport_behaviour(wormhole:&Option,game:& //check ground state.touching.insert(c); //I love making functions with 10 arguments to dodge the borrow checker - run_teleport_behaviour(&general.wormhole,&mut state.mode_state,&data.models,&data.modes.get_mode(state.mode_state.mode_id).unwrap(),&state.style,&data.hitbox_mesh,&mut state.touching,&mut state.body,model_id); + run_teleport_behaviour(&general.wormhole,&mut state.mode_state,&data.models,&data.modes.get_mode(state.mode_state.mode_id).unwrap(),&state.style,&data.hitbox_mesh,&mut state.touching,&mut state.body,convex_mesh_id); //flatten v state.touching.constrain_velocity(&data.models,&data.hitbox_mesh,&mut v); match &general.booster{ @@ -1276,13 +1294,13 @@ fn run_teleport_behaviour(wormhole:&Option,game:& (PhysicsCollisionAttributes::Intersect{intersecting: _,general},Collision::Intersect(intersect))=>{ //I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop state.touching.insert(c); - run_teleport_behaviour(&general.wormhole,&mut state.mode_state,&data.models,&data.modes.get_mode(state.mode_state.mode_id).unwrap(),&state.style,&data.hitbox_mesh,&mut state.touching,&mut state.body,model_id); + run_teleport_behaviour(&general.wormhole,&mut state.mode_state,&data.models,&data.modes.get_mode(state.mode_state.mode_id).unwrap(),&state.style,&data.hitbox_mesh,&mut state.touching,&mut state.body,convex_mesh_id); }, _=>panic!("invalid pair"), } }, PhysicsInstruction::CollisionEnd(c) => { - match data.models.attr(c.model_id()){ + match data.models.attr(c.convex_mesh_id().model_id){ PhysicsCollisionAttributes::Contact{contacting:_,general:_}=>{ state.touching.remove(&c);//remove contact before calling contact_constrain_acceleration //check ground @@ -1365,7 +1383,7 @@ fn run_teleport_behaviour(wormhole:&Option,game:& let spawn_point={ let mode=data.modes.get_mode(state.mode_state.mode_id).unwrap(); let stage=mode.get_stage(gameplay_modes::StageId::FIRST).unwrap(); - data.models.model(stage.spawn().into()).transform.translation + data.models.model(stage.spawn().into()).transform.vertex.translation }; set_position(&mut state.body,&mut state.touching,spawn_point); set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,Planar64Vec3::ZERO); @@ -1409,7 +1427,7 @@ fn test_collision_rotated(relative_body:Body,expected_collision_time:Option