Compare commits

..

3 Commits

Author SHA1 Message Date
8c88c058b6 use push planes 2026-03-25 08:20:41 -07:00
dd4fdd34f7 add touching_changed function 2026-03-25 08:20:41 -07:00
f9c808ccb8 set up borrow checker hand holding 2026-03-25 08:20:41 -07:00
10 changed files with 107 additions and 315 deletions

1
Cargo.lock generated
View File

@@ -3997,7 +3997,6 @@ dependencies = [
"arrayvec",
"glam",
"id",
"mlua",
"strafesnet_common",
]

View File

@@ -7,7 +7,6 @@ edition = "2024"
arrayvec = "0.7.6"
glam.workspace = true
id = { version = "0.1.0", registry = "strafesnet" }
mlua = { version = "0.11.5", features = ["luau"] }
strafesnet_common.workspace = true
[lints]

View File

@@ -5,7 +5,6 @@ mod minkowski;
mod model;
mod push_solve;
mod minimum_difference;
mod minimum_difference_lua;
pub mod physics;

View File

@@ -11,7 +11,7 @@ use crate::minkowski::{MinkowskiMesh,MinkowskiVert};
// written by Trey Reynolds in 2021
type Simplex<const N:usize,Vert>=[Vert;N];
#[derive(Clone,Copy,Debug)]
#[derive(Clone,Copy)]
enum Simplex1_3<Vert>{
Simplex1(Simplex<1,Vert>),
Simplex2(Simplex<2,Vert>),
@@ -139,15 +139,12 @@ fn reduce1<M:MeshQuery<Position=Planar64Vec3>>(
)->Reduced<M::Vert>
where M::Vert:Copy,
{
println!("reduce1");
// --debug.profilebegin("reduceSimplex0")
// local a = a1 - a0
let p0=mesh.vert(v0);
println!("p0={p0}");
// local p = -a
let p=-(p0+point);
println!("p={p}");
// local direction = p
let mut dir=p;
@@ -174,7 +171,6 @@ fn reduce2<M:MeshQuery<Position=Planar64Vec3>>(
where
M::Vert:Copy
{
println!("reduce2");
// --debug.profilebegin("reduceSimplex1")
// local a = a1 - a0
// local b = b1 - b0
@@ -235,7 +231,6 @@ fn reduce3<M:MeshQuery<Position=Planar64Vec3>>(
where
M::Vert:Copy
{
println!("reduce3");
// --debug.profilebegin("reduceSimplex2")
// local a = a1 - a0
// local b = b1 - b0
@@ -348,7 +343,6 @@ fn reduce4<M:MeshQuery<Position=Planar64Vec3>>(
where
M::Vert:Copy
{
println!("reduce4");
// --debug.profilebegin("reduceSimplex3")
// local a = a1 - a0
// local b = b1 - b0
@@ -740,7 +734,6 @@ fn crawl_to_closest_fev<'a>(mesh:&MinkowskiMesh<'a>,simplex:Simplex<3,MinkowskiV
//if test point is behind face, the face is invalid
// TODO: find out why I thought of this backwards
if !(face_n.dot(point)-d).is_positive(){
println!("behind");
continue;
}
//edge-face boundary nd, n facing out of the face towards the edge
@@ -750,8 +743,6 @@ fn crawl_to_closest_fev<'a>(mesh:&MinkowskiMesh<'a>,simplex:Simplex<3,MinkowskiV
if !boundary_d.is_positive(){
//both faces cannot pass this condition, return early if one does.
return FEV::Face(face_id);
}else{
println!("boundary_d is positive");
}
}
FEV::Edge(edge_id)
@@ -760,21 +751,11 @@ fn crawl_to_closest_fev<'a>(mesh:&MinkowskiMesh<'a>,simplex:Simplex<3,MinkowskiV
}
pub fn closest_fev_not_inside<'a>(mesh:&MinkowskiMesh<'a>,point:Planar64Vec3)->Option<FEV<MinkowskiMesh<'a>>>{
println!("=== LUA ===");
let (hits,_details)=crate::minimum_difference_lua::minimum_difference_details(mesh,point).unwrap();
println!("=== RUST ===");
let closest_fev_not_inside=closest_fev_not_inside_inner(mesh,point);
assert_eq!(hits,closest_fev_not_inside.is_none(),"algorithms disagree");
closest_fev_not_inside
}
pub fn closest_fev_not_inside_inner<'a>(mesh:&MinkowskiMesh<'a>,point:Planar64Vec3)->Option<FEV<MinkowskiMesh<'a>>>{
const ENABLE_FAST_FAIL:bool=false;
// TODO: remove mesh negation
minimum_difference::<ENABLE_FAST_FAIL,_,_>(&-mesh,point,
// on_exact
|is_intersecting,simplex|{
println!("on_exact simplex={simplex:?}");
if is_intersecting{
return None;
}
@@ -785,11 +766,7 @@ pub fn closest_fev_not_inside_inner<'a>(mesh:&MinkowskiMesh<'a>,point:Planar64Ve
Simplex1_3::Simplex2([v0,v1])=>{
// invert
let (v0,v1)=(-v0,-v1);
let ev=crawl_to_closest_ev(mesh,[v0,v1],point);
if !matches!(ev,EV::Edge(_)){
println!("I can't believe it's not an edge!");
}
ev.into()
crawl_to_closest_ev(mesh,[v0,v1],point).into()
},
Simplex1_3::Simplex3([v0,v1,v2])=>{
// invert
@@ -797,11 +774,7 @@ pub fn closest_fev_not_inside_inner<'a>(mesh:&MinkowskiMesh<'a>,point:Planar64Ve
// Shimmy to the side until you find a face that contains the closest point
// it's ALWAYS representable as a face, but this algorithm may
// return E or V in edge cases but I don't think that will break the face crawler
let fev=crawl_to_closest_fev(mesh,[v0,v1,v2],point);
if !matches!(fev,FEV::Face(_)){
println!("I can't believe it's not a face!");
}
fev
crawl_to_closest_fev(mesh,[v0,v1,v2],point)
},
})
},
@@ -859,7 +832,6 @@ fn minimum_difference<const ENABLE_FAST_FAIL:bool,T,M:MeshQuery<Position=Planar6
if initial_axis==vec3::zero(){
initial_axis=choose_any_direction();
}
println!("initial_axis={initial_axis}");
let last_point=mesh.farthest_vert(-initial_axis);
// this represents the 'a' value in the commented code
let mut last_pos=mesh.vert(last_point);
@@ -868,8 +840,6 @@ fn minimum_difference<const ENABLE_FAST_FAIL:bool,T,M:MeshQuery<Position=Planar6
// exitRadius = testIntersection and 0 or exitRadius or 1/0
// for _ = 1, 100 do
loop{
println!("direction={direction}");
// new_point_p = queryP(-direction)
// new_point_q = queryQ(direction)
// local next_point = new_point_q - new_point_p
@@ -877,11 +847,7 @@ fn minimum_difference<const ENABLE_FAST_FAIL:bool,T,M:MeshQuery<Position=Planar6
let next_pos=mesh.vert(next_point);
// if -direction:Dot(next_point) > (exitRadius + radiusP + radiusQ)*direction.magnitude then
let d=direction.dot(next_pos+point);
let fast_fail=d.is_negative();
println!("ENABLE_FAST_FAIL={ENABLE_FAST_FAIL} fast_fail={fast_fail} next_point={} dot={d}",next_pos+point);
if ENABLE_FAST_FAIL&&fast_fail{
println!("on_fast_fail");
if ENABLE_FAST_FAIL&&direction.dot(next_pos+point).is_negative(){
return on_fast_fail();
}
@@ -890,11 +856,8 @@ fn minimum_difference<const ENABLE_FAST_FAIL:bool,T,M:MeshQuery<Position=Planar6
// if
// direction:Dot(next_point - a) <= 0 or
// absDet(next_point, a, b, c) < 1e-6
let d1=direction.dot(next_pos-last_pos);
let cond2=simplex_big.det_is_zero(mesh);
println!("d1={d1:?} cond2={cond2}");
if !d1.is_positive()||cond2{
println!("on_exact");
if !direction.dot(next_pos-last_pos).is_positive()
||simplex_big.det_is_zero(mesh){
// Found enough information to compute the exact closest point.
// local norm = direction.unit
// local dist = a:Dot(norm)
@@ -907,7 +870,6 @@ fn minimum_difference<const ENABLE_FAST_FAIL:bool,T,M:MeshQuery<Position=Planar6
match simplex_big.reduce(mesh,point){
// if a and b and c and d then
Reduce::Escape(simplex)=>{
println!("on_escape");
// Enough information to conclude that the meshes are intersecting.
// Topology information is computed if needed.
return on_escape(simplex);

View File

@@ -1,174 +0,0 @@
use mlua::{Lua,FromLuaMulti,IntoLuaMulti,Function,Result as LuaResult,Vector};
use strafesnet_common::integer::{Planar64,Planar64Vec3,FixedFromFloatError};
use crate::mesh_query::MeshQuery;
use crate::minkowski::MinkowskiMesh;
pub fn contains_point(
mesh:&MinkowskiMesh,
point:Planar64Vec3,
)->LuaResult<bool>{
Ok(minimum_difference(mesh,point,true)?.hits)
}
pub fn minimum_difference_details(
mesh:&MinkowskiMesh,
point:Planar64Vec3,
)->LuaResult<(bool,Option<Details>)>{
let md=minimum_difference(mesh,point,false)?;
Ok((md.hits,md.details))
}
fn p64v3(v:Vector)->Result<Planar64Vec3,FixedFromFloatError>{
Ok(Planar64Vec3::new([
v.x().try_into()?,
v.y().try_into()?,
v.z().try_into()?,
]))
}
fn vec(v:Planar64Vec3)->Vector{
Vector::new(v.x.into(),v.y.into(),v.z.into())
}
struct MinimumDifference{
hits:bool,
details:Option<Details>
}
pub struct Details{
pub distance:Planar64,
pub p_pos:Planar64Vec3,
pub p_norm:Planar64Vec3,
pub q_pos:Planar64Vec3,
pub q_norm:Planar64Vec3,
}
impl FromLuaMulti for MinimumDifference{
fn from_lua_multi(mut values:mlua::MultiValue,_lua:&Lua)->LuaResult<Self>{
match values.make_contiguous(){
&mut [
mlua::Value::Boolean(hits),
mlua::Value::Nil,
mlua::Value::Nil,
mlua::Value::Nil,
mlua::Value::Nil,
mlua::Value::Nil,
]=>Ok(Self{hits,details:None}),
&mut [
mlua::Value::Boolean(hits),
mlua::Value::Number(distance),
mlua::Value::Vector(p_pos),
mlua::Value::Vector(p_norm),
mlua::Value::Vector(q_pos),
mlua::Value::Vector(q_norm),
]=>Ok(Self{
hits,
details:Some(Details{
distance:distance.try_into().unwrap(),
p_pos:p64v3(p_pos).unwrap(),
p_norm:p64v3(p_norm).unwrap(),
q_pos:p64v3(q_pos).unwrap(),
q_norm:p64v3(q_norm).unwrap(),
}),
}),
&mut [
mlua::Value::Boolean(hits),
mlua::Value::Integer(distance),
mlua::Value::Vector(p_pos),
mlua::Value::Vector(p_norm),
mlua::Value::Vector(q_pos),
mlua::Value::Vector(q_norm),
]=>Ok(Self{
hits,
details:Some(Details{
distance:distance.into(),
p_pos:p64v3(p_pos).unwrap(),
p_norm:p64v3(p_norm).unwrap(),
q_pos:p64v3(q_pos).unwrap(),
q_norm:p64v3(q_norm).unwrap(),
}),
}),
values=>Err(mlua::Error::runtime(format!("Invalid return values: {values:?}"))),
}
}
}
struct Args{
query_p:Function,
radius_p:f64,
query_q:Function,
radius_q:f64,
test_intersection:bool,
}
impl Args{
fn new(
lua:&Lua,
mesh:&'static MinkowskiMesh<'static>,
point:Planar64Vec3,
test_intersection:bool,
)->LuaResult<Self>{
let radius_p=0.0;
let radius_q=0.0;
// Query the farthest point on the mesh in the given direction.
let query_p=lua.create_function(move|_,dir:Option<Vector>|{
let Some(dir)=dir else{
return Ok(vec(mesh.mesh0.hint_point()));
};
let dir=p64v3(dir).unwrap();
let vert_id=mesh.mesh0.farthest_vert(dir);
let dir=mesh.mesh0.vert(vert_id);
Ok(vec(dir))
})?;
// query_q is different since it includes the test point offset.
let query_q=lua.create_function(move|_,dir:Option<Vector>|{
let Some(dir)=dir else{
return Ok(vec(mesh.mesh1.hint_point()+point));
};
let dir=p64v3(dir).unwrap();
let vert_id=mesh.mesh1.farthest_vert(dir);
let dir=mesh.mesh1.vert(vert_id)+point;
Ok(vec(dir))
})?;
Ok(Args{
query_p,
radius_p,
query_q,
radius_q,
test_intersection,
})
}
}
impl IntoLuaMulti for Args{
fn into_lua_multi(self,lua:&Lua)->LuaResult<mlua::MultiValue>{
use mlua::IntoLua;
Ok(mlua::MultiValue::from_vec(vec![
self.query_p.into_lua(lua)?,
self.radius_p.into_lua(lua)?,
self.query_q.into_lua(lua)?,
self.radius_q.into_lua(lua)?,
mlua::Value::Nil,
self.test_intersection.into_lua(lua)?,
]))
}
}
fn minimum_difference(
mesh:&MinkowskiMesh,
point:Planar64Vec3,
test_intersection:bool,
)->LuaResult<MinimumDifference>{
let ctx=init_lua()?;
// SAFETY: mesh lifetime must outlive args usages
let mesh=unsafe{core::mem::transmute(mesh)};
let args=Args::new(&ctx.lua,mesh,point,test_intersection)?;
ctx.f.call(args)
}
struct Ctx{
lua:Lua,
f:Function,
}
fn init_lua()->LuaResult<Ctx>{
static SOURCE:std::sync::LazyLock<String>=std::sync::LazyLock::new(||std::fs::read_to_string("../../Trey-MinimumDifference.lua").unwrap());
let lua=Lua::new();
lua.sandbox(true)?;
let lib_f=lua.load(SOURCE.as_str()).set_name("Trey-MinimumDifference").into_function()?;
let lib:mlua::Table=lib_f.call(())?;
let f=lib.raw_get("difference")?;
Ok(Ctx{lua,f})
}

View File

@@ -80,8 +80,8 @@ pub enum MinkowskiFace{
#[derive(Debug)]
pub struct MinkowskiMesh<'a>{
pub mesh0:TransformedMesh<'a>,
pub mesh1:TransformedMesh<'a>,
mesh0:TransformedMesh<'a>,
mesh1:TransformedMesh<'a>,
}
// TODO: remove this
@@ -99,9 +99,6 @@ impl MinkowskiMesh<'_>{
mesh1,
}
}
pub fn closest_point(&self,point:Planar64Vec3)->Option<crate::mesh_query::FEV<Self>>{
crate::minimum_difference::closest_fev_not_inside(self,point)
}
pub fn predict_collision_in(&self,trajectory:&Trajectory,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{
let start_position=match range.start_bound(){
Bound::Included(time)=>trajectory.extrapolated_position(*time),

View File

@@ -478,7 +478,7 @@ impl PhysicsMeshTransform{
#[derive(Debug,Clone,Copy)]
pub struct TransformedMesh<'a>{
view:PhysicsMeshView<'a>,
pub transform:&'a PhysicsMeshTransform,
transform:&'a PhysicsMeshTransform,
}
impl TransformedMesh<'_>{
pub const fn new<'a>(

View File

@@ -174,7 +174,8 @@ fn ground_things(walk_settings:&gameplay_style::WalkSettings,contact:&ContactCol
let gravity=touching.base_acceleration(models,style,camera,input_state);
let control_dir=style.get_y_control_dir(camera,input_state.controls);
let target_velocity=walk_settings.get_walk_target_velocity(control_dir,normal);
let target_velocity_clipped=touching.constrain_velocity(models,hitbox_mesh,target_velocity);
let contacts=touching.contacts(models,hitbox_mesh);
let target_velocity_clipped=touching.constrain_velocity(&contacts,target_velocity).velocity;
(gravity,target_velocity_clipped)
}
fn ladder_things(ladder_settings:&gameplay_style::LadderSettings,contact:&ContactCollision,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->(Planar64Vec3,Planar64Vec3){
@@ -182,7 +183,8 @@ fn ladder_things(ladder_settings:&gameplay_style::LadderSettings,contact:&Contac
let gravity=touching.base_acceleration(models,style,camera,input_state);
let control_dir=style.get_y_control_dir(camera,input_state.controls);
let target_velocity=ladder_settings.get_ladder_target_velocity(control_dir,normal);
let target_velocity_clipped=touching.constrain_velocity(models,hitbox_mesh,target_velocity);
let contacts=touching.contacts(models,hitbox_mesh);
let target_velocity_clipped=touching.constrain_velocity(&contacts,target_velocity).velocity;
(gravity,target_velocity_clipped)
}
@@ -520,7 +522,8 @@ impl MoveState{
// calculate base acceleration
let base_acceleration=touching.base_acceleration(models,style,camera,input_state);
// constrain_acceleration clips according to contacts
touching.constrain_acceleration(models,hitbox_mesh,base_acceleration)
let contacts=touching.contacts(models,hitbox_mesh);
touching.constrain_acceleration(&contacts,base_acceleration).acceleration
},
MoveState::Walk(walk_state)
|MoveState::Ladder(walk_state)
@@ -605,25 +608,26 @@ impl MoveState{
*self=move_state;
//this function call reads the above state that was just set
self.update_walk_target(body,touching,models,hitbox_mesh,style,camera,input_state);
// Never used? make body immutable
self.update_fly_velocity(body,touching,models,hitbox_mesh,style,camera,input_state);
}
fn cull_velocity(&mut self,velocity:Planar64Vec3,body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){
//TODO: be more precise about contacts
if set_velocity_cull(body,touching,models,hitbox_mesh,velocity){
// TODO do better
// TODO: unduplicate this code
match self.get_walk_state(){
// did you stop touching the thing you were walking on?
Some(walk_state)=>if !touching.contains_contact(&walk_state.contact.convex_mesh_id){
self.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state);
}else{
// stopped touching something else while walking
self.update_walk_target(body,touching,models,hitbox_mesh,style,camera,input_state);
self.update_fly_velocity(body,touching,models,hitbox_mesh,style,camera,input_state);
},
// not walking, but stopped touching something
None=>self.update_fly_velocity(body,touching,models,hitbox_mesh,style,camera,input_state),
}
self.touching_changed(body,touching,models,hitbox_mesh,style,camera,input_state,|contact|!touching.contains_contact(&contact.convex_mesh_id));
}
}
fn touching_changed(&mut self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState,is_contact_removed:impl Fn(&ContactCollision)->bool){
match self.get_walk_state(){
// did you stop touching the thing you were walking on?
Some(walk_state)=>if is_contact_removed(&walk_state.contact){
self.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state);
}else{
// stopped touching something else while walking
self.update_walk_target(body,touching,models,hitbox_mesh,style,camera,input_state);
self.update_fly_velocity(body,touching,models,hitbox_mesh,style,camera,input_state);
},
// not walking, but stopped touching something
None=>self.update_fly_velocity(body,touching,models,hitbox_mesh,style,camera,input_state),
}
}
}
@@ -752,6 +756,18 @@ impl Collision{
}
}
}
struct Contacts<'a>{
contacts:Vec<crate::push_solve::Contact>,
lifetime:core::marker::PhantomData<&'a ()>,
}
struct ConstrainedVelocity<'a>{
velocity:Planar64Vec3,
constraints:crate::push_solve::Conts<'a>,
}
struct ConstrainedAcceleration<'a>{
acceleration:Planar64Vec3,
constraints:crate::push_solve::Conts<'a>,
}
#[derive(Clone,Debug,Default)]
struct TouchingState{
// This is kind of jank, it's a ContactCollision
@@ -807,8 +823,8 @@ impl TouchingState{
//TODO: add water
a
}
fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:Planar64Vec3)->Planar64Vec3{
let contacts:Vec<_>=self.contacts.iter().map(|(convex_mesh_id,face_id)|{
fn contacts<'a>(&'a self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh)->Contacts<'a>{
let contacts=self.contacts.iter().map(|(convex_mesh_id,face_id)|{
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
crate::push_solve::Contact{
position:vec3::zero(),
@@ -816,18 +832,24 @@ impl TouchingState{
normal:n,
}
}).collect();
crate::push_solve::push_solve(&contacts,velocity).0
Contacts{
contacts,
lifetime:core::marker::PhantomData,
}
}
fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:Planar64Vec3)->Planar64Vec3{
let contacts:Vec<_>=self.contacts.iter().map(|(convex_mesh_id,face_id)|{
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
crate::push_solve::Contact{
position:vec3::zero(),
velocity:n,
normal:n,
}
}).collect();
crate::push_solve::push_solve(&contacts,acceleration).0
fn constrain_velocity<'a>(&self,contacts:&'a Contacts<'_>,velocity:Planar64Vec3)->ConstrainedVelocity<'a>{
let (velocity,constraints)=crate::push_solve::push_solve(&contacts.contacts,velocity);
ConstrainedVelocity{
velocity,
constraints
}
}
fn constrain_acceleration<'a>(&self,contacts:&'a Contacts<'_>,acceleration:Planar64Vec3)->ConstrainedAcceleration<'a>{
let (acceleration,constraints)=crate::push_solve::push_solve(&contacts.contacts,acceleration);
ConstrainedAcceleration{
acceleration,
constraints
}
}
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,Time>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,trajectory:&Trajectory,start_time:Time){
// let relative_body=body.relative_to(&Body::ZERO);
@@ -943,9 +965,6 @@ impl PhysicsState{
fn next_move_instruction(&self)->Option<TimedInstruction<InternalInstruction,Time>>{
self.move_state.next_move_instruction(&self.style.strafe,self.time)
}
fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){
self.move_state.cull_velocity(velocity,&mut self.body,&mut self.touching,&data.models,&data.hitbox_mesh,&self.style,&self.camera,&self.input_state);
}
fn set_move_state(&mut self,data:&PhysicsData,move_state:MoveState){
self.move_state.set_move_state(move_state,&mut self.body,&self.touching,&data.models,&data.hitbox_mesh,&self.style,&self.camera,&self.input_state);
}
@@ -1003,12 +1022,6 @@ impl PhysicsData{
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
}
}
pub fn closest_point(&self,mesh_id:u32,point:Planar64Vec3)->Option<crate::mesh_query::FEV<MinkowskiMesh<'_>>>{
let model_mesh=self.models.mesh(ConvexMeshId{model_id:PhysicsModelId::Contact(ContactModelId(mesh_id)),submesh_id:PhysicsSubmeshId::new(0)});
println!("transform={:?}",model_mesh.transform.vertex.matrix3);
let minkowski=MinkowskiMesh::minkowski_sum(model_mesh,self.hitbox_mesh.transformed_mesh());
minkowski.closest_point(point)
}
pub fn new(map:&map::CompleteMap)->Self{
let modes=map.modes.clone().denormalize();
let mut used_contact_attributes=Vec::new();
@@ -1335,22 +1348,50 @@ fn set_position(
recalculate_touching(move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time);
point
}
/// Returns true when a contact was removed
fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3)->bool{
//This is not correct but is better than what I have
let mut culled=false;
touching.contacts.retain(|convex_mesh_id,face_id|{
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
let r=n.dot(v).is_positive();
if r{
culled=true;
}
!r
let contacts=touching.contacts(models,hitbox_mesh);
let ConstrainedVelocity{velocity,mut constraints}=touching.constrain_velocity(&contacts,v);
// prep list for drain
constraints.sort_by_key(|&cont|{
let cont_ptr:*const crate::push_solve::Contact=cont;
contacts.contacts.len()-(cont_ptr as usize-contacts.contacts.as_ptr() as usize)
});
set_velocity(body,touching,models,hitbox_mesh,v);
culled
// create a list of indices to retain
let mut indices:arrayvec::ArrayVec<_,4>=constraints.iter().map(|&cont|{
let cont_ptr:*const crate::push_solve::Contact=cont;
cont_ptr as usize-contacts.contacts.as_ptr() as usize
}).collect();
drop(constraints);
let mut is_contact_removed=false;
// Delete contacts which do not constrain the velocity
let mut i=0;
touching.contacts.retain(|_,_|{
if let Some(&next_i)=indices.last(){
let is_active=i==next_i;
if is_active{
indices.pop();
}else{
is_contact_removed=true;
}
i+=1;
return is_active
}
is_contact_removed=true;
false
});
body.velocity=velocity;
is_contact_removed
}
fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3){
body.velocity=touching.constrain_velocity(models,hitbox_mesh,v);
let contacts=touching.contacts(models,hitbox_mesh);
body.velocity=touching.constrain_velocity(&contacts,v).velocity;
}
fn teleport(
@@ -1709,20 +1750,7 @@ fn collision_end_contact(
//check ground
//TODO do better
//this is inner code from move_state.cull_velocity
match move_state.get_walk_state(){
// did you stop touching the thing you were walking on?
// This does not check the face! Is that a bad thing? It should be
// impossible to stop touching a different face than you started touching...
Some(walk_state)=>if &walk_state.contact.convex_mesh_id==convex_mesh_id{
move_state.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state);
}else{
// stopped touching something else while walking
move_state.update_walk_target(body,touching,models,hitbox_mesh,style,camera,input_state);
move_state.update_fly_velocity(body,touching,models,hitbox_mesh,style,camera,input_state);
},
// not walking, but stopped touching something
None=>move_state.update_fly_velocity(body,touching,models,hitbox_mesh,style,camera,input_state),
}
move_state.touching_changed(body,touching,models,hitbox_mesh,style,camera,input_state,|contact|contact.convex_mesh_id==*convex_mesh_id);
}
fn collision_end_intersect(
move_state:&mut MoveState,
@@ -1819,7 +1847,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
state.body=extrapolated_body;
//this is wrong but will work ig
//need to note which push planes activate in push solve and keep those
state.cull_velocity(data,ticked_velocity);
state.move_state.cull_velocity(ticked_velocity,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state);
}
}
}
@@ -1903,7 +1931,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact);
let booster_option=data.models.contact_attr(walk_state.contact.convex_mesh_id.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);
state.move_state.cull_velocity(jumped_velocity,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state);
}
}
b_refresh_walk_target=false;
@@ -1976,7 +2004,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
if b_refresh_walk_target{
state.move_state.update_walk_target(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state);
state.move_state.update_fly_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state);
state.cull_velocity(data,state.body.velocity);
state.move_state.cull_velocity(state.body.velocity,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state);
//also check if accelerating away from surface
}
}

View File

@@ -10,7 +10,7 @@ use strafesnet_common::ray::Ray;
// A stack-allocated variable-size list that holds up to 4 elements
// Direct references are used instead of indices i0, i1, i2, i3
type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
pub type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
// hack to allow comparing ratios to zero
const RATIO_ZERO:Ratio<F64_32,F64_32>=Ratio::new(Fixed::ZERO,Fixed::EPSILON);

View File

@@ -76,21 +76,3 @@ fn physics_bug_3()->Result<(),ReplayError>{
Ok(())
}
#[test]
fn physics_bug_26()->Result<(),ReplayError>{
println!("loading map file..");
let data=read_entire_file("../tools/bhop_maps/5692124338.snfm")?;
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
// create recording
println!("generating models..");
let physics_data=PhysicsData::new(&map);
println!("reproducing bug...");
//teleport to bug
let fev=physics_data.closest_point(1020,strafesnet_common::integer::vec3::try_from_f32_array([76.889,363.188,-309.263]).unwrap()).unwrap();
println!("{fev:?}");
Ok(())
}