physics: refactor models and attributes with type safety

make invalid states unrepresentable!!!
This commit is contained in:
Quaternions 2024-08-09 14:16:10 -07:00
parent 5e45753756
commit d3f84c2dbd
3 changed files with 273 additions and 175 deletions

20
Cargo.lock generated
View File

@ -1900,9 +1900,9 @@ dependencies = [
[[package]] [[package]]
name = "strafesnet_bsp_loader" name = "strafesnet_bsp_loader"
version = "0.1.4" version = "0.1.5"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "b6b3e1324034abfd648e339580989f8f2c30ac2009296229349d88b8fcb4eedd" checksum = "35ee2c534efa039ad17ca41893ba1d75fafff014076353ac676c73fc808b9e44"
dependencies = [ dependencies = [
"glam", "glam",
"strafesnet_common", "strafesnet_common",
@ -1912,9 +1912,9 @@ dependencies = [
[[package]] [[package]]
name = "strafesnet_common" name = "strafesnet_common"
version = "0.3.0" version = "0.4.0"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "1077d45a0b064964906a57de765a5a2bfe47b41f2f807d13b18c70765e76d3dd" checksum = "ea4126f6fbf9aecf89c9e319290f0221d177dcaa8659b4b9e3d82acc37829f12"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"bitflags 2.6.0", "bitflags 2.6.0",
@ -1924,9 +1924,9 @@ dependencies = [
[[package]] [[package]]
name = "strafesnet_deferred_loader" name = "strafesnet_deferred_loader"
version = "0.3.2" version = "0.3.3"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "9d5ad437524fb201fd5be68f76c53dd831e81ccad4655e19e3d1ca201863b566" checksum = "596aba6d2747818781336ad95a1ee496e37f70052fd625a299fc7a555a6938d4"
dependencies = [ dependencies = [
"lazy-regex", "lazy-regex",
"strafesnet_common", "strafesnet_common",
@ -1935,9 +1935,9 @@ dependencies = [
[[package]] [[package]]
name = "strafesnet_rbx_loader" name = "strafesnet_rbx_loader"
version = "0.3.3" version = "0.3.4"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "3a910867e1f5ab2d9cc9c178973aee7fa029547e27465e47fea2eb99b860bb81" checksum = "6cd7fb0eca01ccd382067924e5fad15844f55a6bcc7c14c0e57a171298263a3e"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"glam", "glam",
@ -1952,9 +1952,9 @@ dependencies = [
[[package]] [[package]]
name = "strafesnet_snf" name = "strafesnet_snf"
version = "0.1.2" version = "0.1.3"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "d12fc351b2af5fd7a8183175d55ac43e21eb8fd1f76cc70cd4713c0d1a556c96" checksum = "a9ae481152d0389be29967e1d5f0377498df8ff9638175d56cd8e2c2e6982bfa"
dependencies = [ dependencies = [
"binrw 0.14.0", "binrw 0.14.0",
"id", "id",

View File

@ -23,7 +23,7 @@ id = { version = "0.1.0", registry = "strafesnet" }
parking_lot = "0.12.1" parking_lot = "0.12.1"
pollster = "0.3.0" pollster = "0.3.0"
strafesnet_bsp_loader = { version = "0.1.3", registry = "strafesnet", optional = true } 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_deferred_loader = { version = "0.3.1", features = ["legacy"], registry = "strafesnet", optional = true }
strafesnet_rbx_loader = { version = "0.3.2", 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 } strafesnet_snf = { version = "0.1.2", registry = "strafesnet", optional = true }

View File

@ -189,33 +189,69 @@ fn ladder_things(ladder_settings:&gameplay_style::LadderSettings,contact:&Contac
#[derive(Default)] #[derive(Default)]
struct PhysicsModels{ struct PhysicsModels{
meshes:HashMap<PhysicsMeshId,PhysicsMesh>, meshes:HashMap<PhysicsMeshId,PhysicsMesh>,
models:HashMap<PhysicsModelId,PhysicsModel>, contact_models:HashMap<ContactModelId,ContactModel>,
//separate models into Contacting and Intersecting? intersect_models:HashMap<IntersectModelId,IntersectModel>,
//wrap model id with ContactingModelId and IntersectingModelId contact_attributes:HashMap<ContactAttributesId,gameplay_attributes::ContactAttributes>,
//attributes can be split into contacting and intersecting (this also saves a bit of memory) intersect_attributes:HashMap<IntersectAttributesId,gameplay_attributes::IntersectAttributes>,
//can go even further and deduplicate General attributes separately, reconstructing it when queried
attributes:HashMap<PhysicsAttributesId,PhysicsCollisionAttributes>,
} }
impl PhysicsModels{ impl PhysicsModels{
fn clear(&mut self){ fn clear(&mut self){
self.meshes.clear(); self.meshes.clear();
self.models.clear(); self.contact_models.clear();
self.attributes.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{ 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( 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 &model.transform
) )
} }
fn model(&self,model_id:PhysicsModelId)->&PhysicsModel{ fn intersect_mesh(&self,intersect:&IntersectCollision)->TransformedMesh{
&self.models[&model_id] 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{ fn get_model_transform(&self,model_id:ModelId)->Option<&PhysicsMeshTransform>{
&self.attributes[&self.models[&model_id].attr_id] //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)] #[derive(Clone,Hash,Eq,PartialEq)]
enum PhysicsCollisionAttributes{ enum PhysicsCollisionAttributes{
Contact{//track whether you are contacting the object Contact(gameplay_attributes::ContactAttributes),
contacting:gameplay_attributes::ContactingAttributes, Intersect(gameplay_attributes::IntersectAttributes),
general:gameplay_attributes::GeneralAttributes,
},
Intersect{//track whether you are intersecting the object
intersecting:gameplay_attributes::IntersectingAttributes,
general:gameplay_attributes::GeneralAttributes,
},
} }
struct NonPhysicsError; struct NonPhysicsError;
impl TryFrom<&gameplay_attributes::CollisionAttributes> for PhysicsCollisionAttributes{ 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<Self,Self::Error>{ fn try_from(value:&gameplay_attributes::CollisionAttributes)->Result<Self,Self::Error>{
match value{ match value{
gameplay_attributes::CollisionAttributes::Decoration=>Err(NonPhysicsError), gameplay_attributes::CollisionAttributes::Decoration=>Err(NonPhysicsError),
gameplay_attributes::CollisionAttributes::Contact{contacting,general}=>Ok(Self::Contact{contacting:contacting.clone(),general:general.clone()}), gameplay_attributes::CollisionAttributes::Contact(attr)=>Ok(Self::Contact(attr.clone())),
gameplay_attributes::CollisionAttributes::Intersect{intersecting,general}=>Ok(Self::Intersect{intersecting:intersecting.clone(),general:general.clone()}), gameplay_attributes::CollisionAttributes::Intersect(attr)=>Ok(Self::Intersect(attr.clone())),
} }
} }
} }
#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)] #[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
struct PhysicsAttributesId(u32); struct ContactAttributesId(u32);
impl Into<CollisionAttributesId> for PhysicsAttributesId{ impl Into<CollisionAttributesId> for ContactAttributesId{
fn into(self)->CollisionAttributesId{ fn into(self)->CollisionAttributesId{
CollisionAttributesId::new(self.0) CollisionAttributesId::new(self.0)
} }
} }
impl From<CollisionAttributesId> for PhysicsAttributesId{ impl From<CollisionAttributesId> for ContactAttributesId{
fn from(value:CollisionAttributesId)->Self{ fn from(value:CollisionAttributesId)->Self{
Self::new(value.get()) Self::new(value.get())
} }
} }
#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
struct IntersectAttributesId(u32);
impl Into<CollisionAttributesId> for IntersectAttributesId{
fn into(self)->CollisionAttributesId{
CollisionAttributesId::new(self.0)
}
}
impl From<CollisionAttributesId> 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<ModelId> for ContactModelId{
fn into(self)->ModelId{
ModelId::new(self.get())
}
}
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
struct IntersectModelId(u32);
impl Into<ModelId> 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<ModelId> 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 //unique physics meshes indexed by this
#[derive(Debug,Default,Clone,Copy,Eq,Hash,PartialEq)] #[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
struct ConvexMeshId{ struct ConvexMeshId{
model_id:PhysicsModelId, model_id:PhysicsModelId,
submesh_id:PhysicsSubmeshId, submesh_id:PhysicsSubmeshId,
} }
#[derive(Debug,Default,Clone,Copy,Hash,id::Id,Eq,PartialEq)] struct ContactModel{
struct PhysicsModelId(u32);
impl Into<ModelId> for PhysicsModelId{
fn into(self)->ModelId{
ModelId::new(self.0)
}
}
impl From<ModelId> 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.
mesh_id:PhysicsMeshId, mesh_id:PhysicsMeshId,
//put these up on the Model (data normalization) attr_id:ContactAttributesId,
attr_id:PhysicsAttributesId, transform:PhysicsMeshTransform,
}
struct IntersectModel{
mesh_id:PhysicsMeshId,
attr_id:IntersectAttributesId,
transform:PhysicsMeshTransform, 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)] #[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
pub struct ContactCollision{ struct ContactCollision{
face_id:model_physics::MinkowskiFace, face_id:model_physics::MinkowskiFace,
convex_mesh_id:ConvexMeshId, model_id:ContactModelId,
submesh_id:PhysicsSubmeshId,
} }
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] #[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
pub struct IntersectCollision{ struct IntersectCollision{
convex_mesh_id:ConvexMeshId, model_id:IntersectModelId,
submesh_id:PhysicsSubmeshId,
} }
#[derive(Debug,Clone,Eq,Hash,PartialEq)] #[derive(Debug,Clone,Eq,Hash,PartialEq)]
pub enum Collision{ enum Collision{
Contact(ContactCollision), Contact(ContactCollision),
Intersect(IntersectCollision), Intersect(IntersectCollision),
} }
impl Collision{ impl Collision{
const fn convex_mesh_id(&self)->ConvexMeshId{ const fn new(convex_mesh_id:ConvexMeshId,face_id:model_physics::MinkowskiFace)->Self{
match self{ match convex_mesh_id.model_id{
&Collision::Contact(ContactCollision{convex_mesh_id,face_id:_}) PhysicsModelId::Contact(model_id)=>Collision::Contact(ContactCollision{model_id,submesh_id:convex_mesh_id.submesh_id,face_id}),
|&Collision::Intersect(IntersectCollision{convex_mesh_id})=>convex_mesh_id, PhysicsModelId::Intersect(model_id)=>Collision::Intersect(IntersectCollision{model_id,submesh_id:convex_mesh_id.submesh_id}),
}
}
const fn face_id(&self)->Option<model_physics::MinkowskiFace>{
match self{
&Collision::Contact(ContactCollision{convex_mesh_id:_,face_id})=>Some(face_id),
&Collision::Intersect(IntersectCollision{convex_mesh_id:_})=>None,
} }
} }
} }
@ -686,25 +731,13 @@ impl TouchingState{
} }
//add accelerators //add accelerators
for contact in &self.contacts{ for contact in &self.contacts{
match models.attr(contact.convex_mesh_id.model_id){ if let Some(accelerator)=&models.contact_attr(contact.model_id).general.accelerator{
PhysicsCollisionAttributes::Contact{contacting:_,general}=>{ a+=accelerator.acceleration;
match &general.accelerator{
Some(accelerator)=>a+=accelerator.acceleration,
None=>(),
}
},
_=>panic!("impossible touching state"),
} }
} }
for intersect in &self.intersects{ for intersect in &self.intersects{
match models.attr(intersect.convex_mesh_id.model_id){ if let Some(accelerator)=&models.intersect_attr(intersect.model_id).general.accelerator{
PhysicsCollisionAttributes::Intersect{intersecting:_,general}=>{ a+=accelerator.acceleration;
match &general.accelerator{
Some(accelerator)=>a+=accelerator.acceleration,
None=>(),
}
},
_=>panic!("impossible touching state"),
} }
} }
//TODO: add water //TODO: add water
@ -734,26 +767,26 @@ impl TouchingState{
let relative_body=VirtualBody::relative(&Body::default(),body).body(time); let relative_body=VirtualBody::relative(&Body::default(),body).body(time);
for contact in &self.contacts{ for contact in &self.contacts{
//detect face slide off //detect face slide off
let model_mesh=models.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()); 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)|{ collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(_face,time)|{
TimedInstruction{ TimedInstruction{
time, time,
instruction:PhysicsInternalInstruction::CollisionEnd( 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{ for intersect in &self.intersects{
//detect model collision in reverse //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()); 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)|{ collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(_face,time)|{
TimedInstruction{ TimedInstruction{
time, time,
instruction:PhysicsInternalInstruction::CollisionEnd( 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 /// use with caution, this is the only non-instruction way to mess with physics
pub fn generate_models(&mut self,map:&map::CompleteMap){ pub fn generate_models(&mut self,map:&map::CompleteMap){
self.state.clear(); self.state.clear();
self.data.modes=map.modes.clone(); let mut modes=map.modes.clone();
for mode in &mut self.data.modes.modes{ for mode in &mut modes.modes{
mode.denormalize_data(); 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::<CollisionAttributesId,PhysicsAttributesId>::new(); let mut physics_attr_id_from_model_attr_id=HashMap::<CollisionAttributesId,PhysicsAttributesId>::new();
let mut used_meshes=Vec::new(); let mut used_meshes=Vec::new();
let mut physics_mesh_id_from_model_mesh_id=HashMap::<MeshId,PhysicsMeshId>::new(); let mut physics_mesh_id_from_model_mesh_id=HashMap::<MeshId,PhysicsMeshId>::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(||{ //TODO: use .entry().or_insert_with(||{
let attr_id=if let Some(&attr_id)=physics_attr_id_from_model_attr_id.get(&model.attributes){ let attr_id=if let Some(&attr_id)=physics_attr_id_from_model_attr_id.get(&model.attributes){
attr_id attr_id
@ -1036,14 +1081,24 @@ impl PhysicsContext{
//check if it's real //check if it's real
match map.attributes.get(model.attributes.get() as usize).and_then(|m_attr|{ match map.attributes.get(model.attributes.get() as usize).and_then(|m_attr|{
PhysicsCollisionAttributes::try_from(m_attr).map_or(None,|p_attr|{ PhysicsCollisionAttributes::try_from(m_attr).map_or(None,|p_attr|{
let attr_id=PhysicsAttributesId::new(used_attributes.len() as u32); let attr_id=match p_attr{
used_attributes.push(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); physics_attr_id_from_model_attr_id.insert(model.attributes,attr_id);
Some(attr_id) Some(attr_id)
}) })
}){ }){
Some(attr_id)=>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){ 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, 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)))) let transform=PhysicsMeshTransform::new(model.transform);
}).collect(); match attr_id{
self.data.models.attributes=used_attributes.into_iter().enumerate().map(|(attr_id,attr)|(PhysicsAttributesId::new(attr_id as u32),attr)).collect(); PhysicsAttributesId::Contact(attr_id)=>{
self.data.models.meshes=used_meshes.into_iter().enumerate().map(|(mesh_id,mesh)|(PhysicsMeshId::new(mesh_id as u32),mesh)).collect(); contact_models.insert(ContactModelId::new(model_id as u32),ContactModel{
let convex_mesh_aabb_list=self.data.models.models.iter() mesh_id,
.flat_map(|(&model_id,model)|{ attr_id,
self.data.models.meshes[&model.mesh_id].submesh_views() transform,
});
},
PhysicsAttributesId::Intersect(attr_id)=>{
intersect_models.insert(IntersectModelId::new(model_id as u32),IntersectModel{
mesh_id,
attr_id,
transform,
});
},
}
}
let meshes:HashMap<PhysicsMeshId,PhysicsMesh>=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)|{ .enumerate().map(move|(submesh_id,view)|{
let mut aabb=aabb::Aabb::default(); 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(){ for v in transformed_mesh.verts(){
aabb.grow(v); aabb.grow(v);
} }
@ -1086,8 +1166,28 @@ impl PhysicsContext{
},aabb) },aabb)
}) })
}).collect(); }).collect();
self.data.bvh=bvh::generate_bvh(convex_mesh_aabb_list); let bvh=bvh::generate_bvh(convex_mesh_aabb_list);
println!("Physics Objects: {}",self.data.models.models.len()); 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 //tickless gaming
@ -1134,19 +1234,22 @@ impl PhysicsContext{
collector.collect(minkowski.predict_collision_in(relative_body,collector.time()) collector.collect(minkowski.predict_collision_in(relative_body,collector.time())
//temp (?) code to avoid collision loops //temp (?) code to avoid collision loops
.map_or(None,|(face,time)|if time<=state.time{None}else{Some((face,time))}) .map_or(None,|(face,time)|if time<=state.time{None}else{Some((face,time))})
.map(|(face,time)|{ .map(|(face,time)|
TimedInstruction{time,instruction:PhysicsInternalInstruction::CollisionStart(match data.models.attr(convex_mesh_id.model_id){ TimedInstruction{
PhysicsCollisionAttributes::Contact{contacting:_,general:_}=>Collision::Contact(ContactCollision{convex_mesh_id,face_id:face}), time,
PhysicsCollisionAttributes::Intersect{intersecting:_,general:_}=>Collision::Intersect(IntersectCollision{convex_mesh_id}), instruction:PhysicsInternalInstruction::CollisionStart(
})} Collision::new(convex_mesh_id,face)
})); )
}
)
);
}); });
collector.instruction() collector.instruction()
} }
fn contact_normal(models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{ 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()); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
minkowski.face_nd(contact.face_id).0 minkowski.face_nd(contact.face_id).0
} }
@ -1206,16 +1309,26 @@ fn teleport(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,sty
MoveState::Air 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<MoveState>{ 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<MoveState>{
let model=models.model(mode.get_spawn_model_id(stage_id)?.into()); let transform=models.get_model_transform(mode.get_spawn_model_id(stage_id)?)?;
let point=model.transform.vertex.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox.halfsize.y()+Planar64::ONE/16); 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)) Some(teleport(body,touching,models,style,hitbox_mesh,point))
} }
fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,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<MoveState>{ fn run_teleport_behaviour(
wormhole:&Option<gameplay_attributes::Wormhole>,
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<MoveState>{
//TODO: jump count and checkpoints are always reset on teleport. //TODO: jump count and checkpoints are always reset on teleport.
//Map makers are expected to use tools to prevent //Map makers are expected to use tools to prevent
//multi-boosting on JumpLimit boosters such as spawning into a SetVelocity //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 let Some(stage)=mode.get_stage(stage_element.stage_id()){
if mode_state.get_stage_id()<stage_element.stage_id(){ if mode_state.get_stage_id()<stage_element.stage_id(){
//checkpoint check //checkpoint check
@ -1253,19 +1366,19 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
gameplay_modes::StageElementBehaviour::Checkpoint=>{ gameplay_modes::StageElementBehaviour::Checkpoint=>{
//each of these checks if the model is actually a valid respective checkpoint object //each of these checks if the model is actually a valid respective checkpoint object
//accumulate sequential ordered checkpoints //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 //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{ match wormhole{
&Some(gameplay_attributes::Wormhole{destination_model})=>{ &Some(gameplay_attributes::Wormhole{destination_model})=>{
let origin_model=models.model(convex_mesh_id.model_id); let origin=models.get_model_transform(model_id)?;
let destination_model=models.model(destination_model.into()); let destination=models.get_model_transform(destination_model)?;
//ignore the transform for now //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, None=>None,
} }
@ -1274,8 +1387,7 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,models
fn collision_start_contact( fn collision_start_contact(
state:&mut PhysicsState, state:&mut PhysicsState,
data:&PhysicsData, data:&PhysicsData,
contacting:&gameplay_attributes::ContactingAttributes, attr:&gameplay_attributes::ContactAttributes,
general:&gameplay_attributes::GeneralAttributes,
contact:ContactCollision, contact:ContactCollision,
){ ){
let incident_velocity=state.body.velocity; let incident_velocity=state.body.velocity;
@ -1283,7 +1395,7 @@ fn collision_start_contact(
state.touching.insert(Collision::Contact(contact)); state.touching.insert(Collision::Contact(contact));
//clip v //clip v
set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,incident_velocity); 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::Surf)=>println!("I'm surfing!"),
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"), Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
&Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{ &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 //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()){ 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 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()){ 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 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); state.cull_velocity(data,jumped_velocity);
} }
} }
match &general.trajectory{ match &attr.general.trajectory{
Some(trajectory)=>{ Some(trajectory)=>{
match trajectory{ match trajectory{
gameplay_attributes::SetTrajectory::AirTime(_)=>todo!(), gameplay_attributes::SetTrajectory::AirTime(_)=>todo!(),
@ -1347,18 +1459,17 @@ fn collision_start_contact(
fn collision_start_intersect( fn collision_start_intersect(
state:&mut PhysicsState, state:&mut PhysicsState,
data:&PhysicsData, data:&PhysicsData,
intersecting:&gameplay_attributes::IntersectingAttributes, attr:&gameplay_attributes::IntersectAttributes,
general:&gameplay_attributes::GeneralAttributes,
intersect:IntersectCollision, intersect:IntersectCollision,
){ ){
//I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop //I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop
state.touching.insert(Collision::Intersect(intersect)); state.touching.insert(Collision::Intersect(intersect));
//insta booster! //insta booster!
if let Some(booster)=&general.booster{ if let Some(booster)=&attr.general.booster{
state.cull_velocity(data,booster.boost(state.body.velocity)); state.cull_velocity(data,booster.boost(state.body.velocity));
} }
if let Some(mode)=data.modes.get_mode(state.mode_state.get_mode_id()){ 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{ match zone{
Some(gameplay_modes::Zone::Start)=>{ Some(gameplay_modes::Zone::Start)=>{
println!("@@@@ Starting new run!"); println!("@@@@ Starting new run!");
@ -1373,15 +1484,14 @@ fn collision_start_intersect(
Some(gameplay_modes::Zone::Anticheat)=>state.run.flag(run::FlagReason::Anticheat), Some(gameplay_modes::Zone::Anticheat)=>state.run.flag(run::FlagReason::Anticheat),
None=>(), 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( fn collision_end_contact(
state:&mut PhysicsState, state:&mut PhysicsState,
data:&PhysicsData, data:&PhysicsData,
_contacting:&gameplay_attributes::ContactingAttributes, _attr:&gameplay_attributes::ContactAttributes,
_general:&gameplay_attributes::GeneralAttributes,
contact:ContactCollision, contact:ContactCollision,
){ ){
state.touching.remove(&Collision::Contact(contact));//remove contact before calling contact_constrain_acceleration 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( fn collision_end_intersect(
state:&mut PhysicsState, state:&mut PhysicsState,
data:&PhysicsData, data:&PhysicsData,
_intersecting:&gameplay_attributes::IntersectingAttributes, _attr:&gameplay_attributes::IntersectAttributes,
_general:&gameplay_attributes::GeneralAttributes,
intersect:IntersectCollision, intersect:IntersectCollision,
){ ){
state.touching.remove(&Collision::Intersect(intersect)); state.touching.remove(&Collision::Intersect(intersect));
if let Some(mode)=data.modes.get_mode(state.mode_state.get_mode_id()){ 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{ match zone{
Some(gameplay_modes::Zone::Start)=>{ Some(gameplay_modes::Zone::Start)=>{
match state.run.start(state.time){ 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); state.body.advance_time(state.time);
} }
match ins.instruction{ match ins.instruction{
PhysicsInternalInstruction::CollisionStart(collision)=>{ PhysicsInternalInstruction::CollisionStart(collision)=>match collision{
match (data.models.attr(collision.convex_mesh_id().model_id),&collision){ Collision::Contact(contact)=>collision_start_contact(state,data,data.models.contact_attr(contact.model_id),contact),
(PhysicsCollisionAttributes::Contact{contacting,general},&Collision::Contact(contact))=> Collision::Intersect(intersect)=>collision_start_intersect(state,data,data.models.intersect_attr(intersect.model_id),intersect),
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::CollisionEnd(collision)=>{ PhysicsInternalInstruction::CollisionEnd(collision)=>match collision{
match (data.models.attr(collision.convex_mesh_id().model_id),&collision){ Collision::Contact(contact)=>collision_end_contact(state,data,data.models.contact_attr(contact.model_id),contact),
(PhysicsCollisionAttributes::Contact{contacting,general},&Collision::Contact(contact))=> Collision::Intersect(intersect)=>collision_end_intersect(state,data,data.models.intersect_attr(intersect.model_id),intersect),
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::StrafeTick=>{ PhysicsInternalInstruction::StrafeTick=>{
//TODO make this less huge //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(walk_state)=state.move_state.get_walk_state(){
if let Some(jump_settings)=&state.style.jump{ 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 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){ let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref();
PhysicsCollisionAttributes::Contact{contacting:_,general}=>general.booster.as_ref(),
PhysicsCollisionAttributes::Intersect{..}=>None,
};
let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option); let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option);
state.cull_velocity(&data,jumped_velocity); state.cull_velocity(&data,jumped_velocity);
} }
@ -1571,10 +1667,12 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
}, },
PhysicsInputInstruction::Restart=>{ PhysicsInputInstruction::Restart=>{
//teleport to start zone //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: 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 //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); ).unwrap_or(Planar64Vec3::ZERO);
set_position(&mut state.body,&mut state.touching,spawn_point); set_position(&mut state.body,&mut state.touching,spawn_point);
set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,Planar64Vec3::ZERO); set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,Planar64Vec3::ZERO);