Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
a92526407c
|
|||
|
47239b10a8
|
|||
|
f5588989e9
|
|||
|
979d46e42a
|
|||
|
f858fa86e6
|
|||
|
8e6d598ea3
|
|||
|
78c0cab05a
|
|||
|
dd972c91ee
|
|||
|
8a897ca377
|
|||
|
65f29fd395
|
|||
|
a673d62ffd
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3997,6 +3997,7 @@ dependencies = [
|
||||
"arrayvec",
|
||||
"glam",
|
||||
"id",
|
||||
"mlua",
|
||||
"strafesnet_common",
|
||||
]
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ 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]
|
||||
|
||||
@@ -5,6 +5,7 @@ mod minkowski;
|
||||
mod model;
|
||||
mod push_solve;
|
||||
mod minimum_difference;
|
||||
mod minimum_difference_lua;
|
||||
|
||||
pub mod physics;
|
||||
|
||||
|
||||
@@ -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)]
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
enum Simplex1_3<Vert>{
|
||||
Simplex1(Simplex<1,Vert>),
|
||||
Simplex2(Simplex<2,Vert>),
|
||||
@@ -139,12 +139,15 @@ 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;
|
||||
@@ -171,6 +174,7 @@ fn reduce2<M:MeshQuery<Position=Planar64Vec3>>(
|
||||
where
|
||||
M::Vert:Copy
|
||||
{
|
||||
println!("reduce2");
|
||||
// --debug.profilebegin("reduceSimplex1")
|
||||
// local a = a1 - a0
|
||||
// local b = b1 - b0
|
||||
@@ -231,6 +235,7 @@ fn reduce3<M:MeshQuery<Position=Planar64Vec3>>(
|
||||
where
|
||||
M::Vert:Copy
|
||||
{
|
||||
println!("reduce3");
|
||||
// --debug.profilebegin("reduceSimplex2")
|
||||
// local a = a1 - a0
|
||||
// local b = b1 - b0
|
||||
@@ -343,6 +348,7 @@ fn reduce4<M:MeshQuery<Position=Planar64Vec3>>(
|
||||
where
|
||||
M::Vert:Copy
|
||||
{
|
||||
println!("reduce4");
|
||||
// --debug.profilebegin("reduceSimplex3")
|
||||
// local a = a1 - a0
|
||||
// local b = b1 - b0
|
||||
@@ -734,6 +740,7 @@ 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
|
||||
@@ -743,6 +750,8 @@ 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)
|
||||
@@ -751,11 +760,21 @@ 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;
|
||||
}
|
||||
@@ -766,7 +785,11 @@ pub fn closest_fev_not_inside<'a>(mesh:&MinkowskiMesh<'a>,point:Planar64Vec3)->O
|
||||
Simplex1_3::Simplex2([v0,v1])=>{
|
||||
// invert
|
||||
let (v0,v1)=(-v0,-v1);
|
||||
crawl_to_closest_ev(mesh,[v0,v1],point).into()
|
||||
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()
|
||||
},
|
||||
Simplex1_3::Simplex3([v0,v1,v2])=>{
|
||||
// invert
|
||||
@@ -774,7 +797,11 @@ pub fn closest_fev_not_inside<'a>(mesh:&MinkowskiMesh<'a>,point:Planar64Vec3)->O
|
||||
// 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
|
||||
crawl_to_closest_fev(mesh,[v0,v1,v2],point)
|
||||
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
|
||||
},
|
||||
})
|
||||
},
|
||||
@@ -832,6 +859,7 @@ 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);
|
||||
@@ -840,6 +868,8 @@ 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
|
||||
@@ -847,7 +877,11 @@ 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
|
||||
if ENABLE_FAST_FAIL&&direction.dot(next_pos+point).is_negative(){
|
||||
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");
|
||||
return on_fast_fail();
|
||||
}
|
||||
|
||||
@@ -856,8 +890,11 @@ 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
|
||||
if !direction.dot(next_pos-last_pos).is_positive()
|
||||
||simplex_big.det_is_zero(mesh){
|
||||
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");
|
||||
// Found enough information to compute the exact closest point.
|
||||
// local norm = direction.unit
|
||||
// local dist = a:Dot(norm)
|
||||
@@ -870,6 +907,7 @@ 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);
|
||||
|
||||
174
engine/physics/src/minimum_difference_lua.rs
Normal file
174
engine/physics/src/minimum_difference_lua.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
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})
|
||||
}
|
||||
@@ -80,8 +80,8 @@ pub enum MinkowskiFace{
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MinkowskiMesh<'a>{
|
||||
mesh0:TransformedMesh<'a>,
|
||||
mesh1:TransformedMesh<'a>,
|
||||
pub mesh0:TransformedMesh<'a>,
|
||||
pub mesh1:TransformedMesh<'a>,
|
||||
}
|
||||
|
||||
// TODO: remove this
|
||||
@@ -99,6 +99,9 @@ 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),
|
||||
|
||||
@@ -478,7 +478,7 @@ impl PhysicsMeshTransform{
|
||||
#[derive(Debug,Clone,Copy)]
|
||||
pub struct TransformedMesh<'a>{
|
||||
view:PhysicsMeshView<'a>,
|
||||
transform:&'a PhysicsMeshTransform,
|
||||
pub transform:&'a PhysicsMeshTransform,
|
||||
}
|
||||
impl TransformedMesh<'_>{
|
||||
pub const fn new<'a>(
|
||||
|
||||
@@ -174,8 +174,7 @@ 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 contacts=touching.contacts(models,hitbox_mesh);
|
||||
let target_velocity_clipped=touching.constrain_velocity(&contacts,target_velocity).velocity;
|
||||
let target_velocity_clipped=touching.constrain_velocity(models,hitbox_mesh,target_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){
|
||||
@@ -183,8 +182,7 @@ 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 contacts=touching.contacts(models,hitbox_mesh);
|
||||
let target_velocity_clipped=touching.constrain_velocity(&contacts,target_velocity).velocity;
|
||||
let target_velocity_clipped=touching.constrain_velocity(models,hitbox_mesh,target_velocity);
|
||||
(gravity,target_velocity_clipped)
|
||||
}
|
||||
|
||||
@@ -522,8 +520,7 @@ impl MoveState{
|
||||
// calculate base acceleration
|
||||
let base_acceleration=touching.base_acceleration(models,style,camera,input_state);
|
||||
// constrain_acceleration clips according to contacts
|
||||
let contacts=touching.contacts(models,hitbox_mesh);
|
||||
touching.constrain_acceleration(&contacts,base_acceleration).acceleration
|
||||
touching.constrain_acceleration(models,hitbox_mesh,base_acceleration)
|
||||
},
|
||||
MoveState::Walk(walk_state)
|
||||
|MoveState::Ladder(walk_state)
|
||||
@@ -608,26 +605,25 @@ 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){
|
||||
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),
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -756,18 +752,6 @@ 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
|
||||
@@ -823,8 +807,8 @@ impl TouchingState{
|
||||
//TODO: add water
|
||||
a
|
||||
}
|
||||
fn contacts<'a>(&'a self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh)->Contacts<'a>{
|
||||
let contacts=self.contacts.iter().map(|(convex_mesh_id,face_id)|{
|
||||
fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity: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(),
|
||||
@@ -832,24 +816,18 @@ impl TouchingState{
|
||||
normal:n,
|
||||
}
|
||||
}).collect();
|
||||
Contacts{
|
||||
contacts,
|
||||
lifetime:core::marker::PhantomData,
|
||||
}
|
||||
crate::push_solve::push_solve(&contacts,velocity).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 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 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);
|
||||
@@ -965,6 +943,9 @@ 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);
|
||||
}
|
||||
@@ -1022,6 +1003,12 @@ 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();
|
||||
@@ -1348,50 +1335,22 @@ 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{
|
||||
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)
|
||||
});
|
||||
// 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
|
||||
//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;
|
||||
}
|
||||
is_contact_removed=true;
|
||||
false
|
||||
!r
|
||||
});
|
||||
|
||||
body.velocity=velocity;
|
||||
|
||||
is_contact_removed
|
||||
set_velocity(body,touching,models,hitbox_mesh,v);
|
||||
culled
|
||||
}
|
||||
fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3){
|
||||
let contacts=touching.contacts(models,hitbox_mesh);
|
||||
body.velocity=touching.constrain_velocity(&contacts,v).velocity;
|
||||
body.velocity=touching.constrain_velocity(models,hitbox_mesh,v);
|
||||
}
|
||||
|
||||
fn teleport(
|
||||
@@ -1750,7 +1709,20 @@ fn collision_end_contact(
|
||||
//check ground
|
||||
//TODO do better
|
||||
//this is inner code from move_state.cull_velocity
|
||||
move_state.touching_changed(body,touching,models,hitbox_mesh,style,camera,input_state,|contact|contact.convex_mesh_id==*convex_mesh_id);
|
||||
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),
|
||||
}
|
||||
}
|
||||
fn collision_end_intersect(
|
||||
move_state:&mut MoveState,
|
||||
@@ -1847,7 +1819,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.move_state.cull_velocity(ticked_velocity,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state);
|
||||
state.cull_velocity(data,ticked_velocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1931,7 +1903,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.move_state.cull_velocity(jumped_velocity,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state);
|
||||
state.cull_velocity(data,jumped_velocity);
|
||||
}
|
||||
}
|
||||
b_refresh_walk_target=false;
|
||||
@@ -2004,7 +1976,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.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);
|
||||
state.cull_velocity(data,state.body.velocity);
|
||||
//also check if accelerating away from surface
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
pub type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
|
||||
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);
|
||||
|
||||
@@ -76,3 +76,21 @@ 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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user