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]]
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",

View File

@ -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 }

View File

@ -189,33 +189,69 @@ fn ladder_things(ladder_settings:&gameplay_style::LadderSettings,contact:&Contac
#[derive(Default)]
struct PhysicsModels{
meshes:HashMap<PhysicsMeshId,PhysicsMesh>,
models:HashMap<PhysicsModelId,PhysicsModel>,
//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<PhysicsAttributesId,PhysicsCollisionAttributes>,
contact_models:HashMap<ContactModelId,ContactModel>,
intersect_models:HashMap<IntersectModelId,IntersectModel>,
contact_attributes:HashMap<ContactAttributesId,gameplay_attributes::ContactAttributes>,
intersect_attributes:HashMap<IntersectAttributesId,gameplay_attributes::IntersectAttributes>,
}
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<Self,Self::Error>{
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<CollisionAttributesId> for PhysicsAttributesId{
struct ContactAttributesId(u32);
impl Into<CollisionAttributesId> for ContactAttributesId{
fn into(self)->CollisionAttributesId{
CollisionAttributesId::new(self.0)
}
}
impl From<CollisionAttributesId> for PhysicsAttributesId{
impl From<CollisionAttributesId> for ContactAttributesId{
fn from(value:CollisionAttributesId)->Self{
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
#[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<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.
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<model_physics::MinkowskiFace>{
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::<CollisionAttributesId,PhysicsAttributesId>::new();
let mut used_meshes=Vec::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(||{
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<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)|{
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<MoveState>{
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<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.
//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()<stage_element.stage_id(){
//checkpoint check
@ -1253,19 +1366,19 @@ fn run_teleport_behaviour(wormhole:&Option<gameplay_attributes::Wormhole>,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<gameplay_attributes::Wormhole>,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);