Compare commits

..

4 Commits

Author SHA1 Message Date
2a03987d89 fixme 2025-03-14 15:24:53 -07:00
57a4cadaf3 bsp_loader: max area triangulation 2025-03-14 15:24:53 -07:00
881bc60ab3 bsp_loader: truncate vertex precision to 16 bits
The physics algorithm expects vertices to align exactly with faces.  Since the face normal is calculated via the cross product of vertex positions, this allows the face normals to be exact with respect to the vertex positions.
2025-03-14 15:24:53 -07:00
0fb0230cb1 tools: create dev profile 2025-03-14 15:24:47 -07:00
8 changed files with 15 additions and 77 deletions

View File

@@ -24,3 +24,7 @@ resolver = "2"
#lto = true
strip = true
codegen-units = 1
[profile.dev]
strip = false
opt-level = 3

View File

@@ -11,15 +11,6 @@ pub fn required_limits()->wgpu::Limits{
wgpu::Limits::default()
}
// physics debug view:
// render two meshes adjacent according to Minkowski FEV
// render additional mesh at path point
// render path parabola
// highlight meshes according to Minkowski FEV
//
// debug process:
// press a button to step physics by one transition
struct Indices{
count:u32,
buf:wgpu::Buffer,

View File

@@ -2,7 +2,6 @@ use crate::model::{GigaTime,FEV,MeshQuery,DirectedEdge};
use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3};
use crate::physics::{Time,Body};
#[derive(Debug)]
enum Transition<M:MeshQuery>{
Miss,
Next(FEV<M>,GigaTime),
@@ -28,18 +27,17 @@ impl<M:MeshQuery> CrawlResult<M>{
}
}
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>+std::fmt::Debug> FEV<M>
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
where
// This is hardcoded for MinkowskiMesh lol
M::Face:Copy,
M::Edge:Copy,
M::Vert:Copy,
F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>+std::fmt::Debug+std::fmt::Display,
F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>,
<F as core::ops::Mul<Fixed<1,32>>>::Output:core::iter::Sum,
<M as MeshQuery>::Offset:core::ops::Sub<<F as std::ops::Mul<Fixed<1,32>>>::Output>,
{
fn next_transition(&self,body_time:GigaTime,mesh:&M,body:&Body,mut best_time:GigaTime)->Transition<M>{
println!("next_transition fev={self:?}");
//conflicting derivative means it crosses in the wrong direction.
//if the transition time is equal to an already tested transition, do not replace the current best.
let mut best_transition=Transition::Miss;
@@ -77,7 +75,6 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>+std::fmt::Debug>
//if none:
},
&FEV::Edge(edge_id)=>{
println!("test edge={edge_id:?}");
//test each face collision time, ignoring roots with zero or conflicting derivative
let edge_n=mesh.edge_n(edge_id);
let edge_verts=mesh.edge_verts(edge_id);
@@ -87,7 +84,6 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>+std::fmt::Debug>
let face_n=mesh.face_nd(edge_face_id).0;
//edge_n gets parity from the order of edge_faces
let n=face_n.cross(edge_n)*((i as i64)*2-1);
println!("edge_face={edge_face_id:?} face_n={face_n} n={n}");
//WARNING yada yada d *2
//wrap for speed
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).wrap_4(),n.dot(body.velocity).wrap_4()*2,n.dot(body.acceleration).wrap_4()){
@@ -142,9 +138,7 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>+std::fmt::Debug>
Ratio::new(r.num.widen_4(),r.den.widen_4())
};
for _ in 0..20{
let transition=self.next_transition(body_time,mesh,relative_body,time_limit);
println!("transition={transition:?}");
match transition{
match self.next_transition(body_time,mesh,relative_body,time_limit){
Transition::Miss=>return CrawlResult::Miss(self),
Transition::Next(next_fev,next_time)=>(self,body_time)=(next_fev,next_time),
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),

View File

@@ -68,17 +68,16 @@ pub enum FEV<M:MeshQuery>{
}
//use Unit32 #[repr(C)] for map files
#[derive(Clone,Debug,Hash,Eq,PartialEq)]
#[derive(Clone,Hash,Eq,PartialEq)]
struct Face{
normal:Planar64Vec3,
dot:Planar64,
}
#[derive(Debug)]
struct Vert(Planar64Vec3);
pub trait MeshQuery{
type Face:Copy+std::fmt::Debug;
type Edge:Copy+DirectedEdge+std::fmt::Debug;
type Vert:Copy+std::fmt::Debug;
type Face:Copy;
type Edge:Copy+DirectedEdge;
type Vert:Copy;
// Vertex must be Planar64Vec3 because it represents an actual position
type Normal;
type Offset;
@@ -98,22 +97,18 @@ pub trait MeshQuery{
fn vert_edges(&self,vert_id:Self::Vert)->impl AsRef<[Self::Edge]>;
fn vert_faces(&self,vert_id:Self::Vert)->impl AsRef<[Self::Face]>;
}
#[derive(Debug)]
struct FaceRefs{
edges:Vec<SubmeshDirectedEdgeId>,
//verts:Vec<VertId>,
}
#[derive(Debug)]
struct EdgeRefs{
faces:[SubmeshFaceId;2],//left, right
verts:[SubmeshVertId;2],//bottom, top
}
#[derive(Debug)]
struct VertRefs{
faces:Vec<SubmeshFaceId>,
edges:Vec<SubmeshDirectedEdgeId>,
}
#[derive(Debug)]
pub struct PhysicsMeshData{
//this contains all real and virtual faces used in both the complete mesh and convex submeshes
//faces are sorted such that all faces that belong to the complete mesh appear first, and then
@@ -123,7 +118,6 @@ pub struct PhysicsMeshData{
faces:Vec<Face>,//MeshFaceId indexes this list
verts:Vec<Vert>,//MeshVertId indexes this list
}
#[derive(Debug)]
pub struct PhysicsMeshTopology{
//mapping of local ids to PhysicsMeshData ids
faces:Vec<MeshFaceId>,//SubmeshFaceId indexes this list
@@ -431,7 +425,6 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
}
}
#[derive(Debug)]
pub struct PhysicsMeshView<'a>{
data:&'a PhysicsMeshData,
topology:&'a PhysicsMeshTopology,
@@ -468,7 +461,6 @@ impl MeshQuery for PhysicsMeshView<'_>{
}
}
#[derive(Debug)]
pub struct PhysicsMeshTransform{
pub vertex:integer::Planar64Affine3,
pub normal:integer::mat3::Matrix3<Fixed<2,64>>,
@@ -484,7 +476,6 @@ impl PhysicsMeshTransform{
}
}
#[derive(Debug)]
pub struct TransformedMesh<'a>{
view:PhysicsMeshView<'a>,
transform:&'a PhysicsMeshTransform,
@@ -610,7 +601,6 @@ pub enum MinkowskiFace{
//FaceFace
}
#[derive(Debug)]
pub struct MinkowskiMesh<'a>{
mesh0:TransformedMesh<'a>,
mesh1:TransformedMesh<'a>,
@@ -755,9 +745,7 @@ impl MinkowskiMesh<'_>{
})
}
pub fn predict_collision_in(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>)->Option<(MinkowskiFace,GigaTime)>{
println!("=== physics setup ===");
self.closest_fev_not_inside(relative_body.clone(),start_time).and_then(|fev|{
println!("=== physics crawl ===");
//continue forwards along the body parabola
fev.crawl(self,relative_body,start_time,time_limit).hit()
})
@@ -914,7 +902,6 @@ impl MeshQuery for MinkowskiMesh<'_>{
}))
},
MinkowskiEdge::EdgeVert(e0,v1)=>{
println!("MinkowskiEdge::EdgeVert({e0:?},{v1:?})");
//tracking index with an external variable because .enumerate() is not available
let v1e=self.mesh1.vert_edges(v1);
let &[e0f0,e0f1]=self.mesh0.edge_faces(e0).as_ref();
@@ -922,18 +909,14 @@ impl MeshQuery for MinkowskiMesh<'_>{
let mut best_edge=None;
let mut best_d:Ratio<Fixed<8,256>,Fixed<8,256>>=Ratio::new(Fixed::ZERO,Fixed::ONE);
let edge_face0_n=self.mesh0.face_nd(edge_face_id0).0;
println!("edge_face0_n={edge_face0_n}");
let edge_face0_nn=edge_face0_n.dot(edge_face0_n);
for &directed_edge_id1 in v1e.as_ref(){
let edge1_n=self.mesh1.directed_edge_n(directed_edge_id1);
println!("edge1_n={edge1_n}");
let d=edge_face0_n.dot(edge1_n);
println!("d={d} {d:?}");
if d.is_negative(){
let edge1_nn=edge1_n.dot(edge1_n);
let dd=(d*d)/(edge_face0_nn*edge1_nn);
if !dd.den.is_zero()&&best_d<dd{
println!("dd={dd:?}");
if best_d<dd{
best_d=dd;
best_edge=Some(directed_edge_id1);
}

View File

@@ -857,12 +857,6 @@ impl Default for PhysicsState{
}
impl PhysicsState{
pub fn new_at_position(position:Planar64Vec3)->Self{
Self{
body:Body::new(position,vec3::int(0,0,0),vec3::int(0,-100,0),Time::ZERO),
..Self::default()
}
}
pub fn camera_body(&self)->Body{
Body{
position:self.body.position+self.style.camera_offset,
@@ -1128,7 +1122,7 @@ impl PhysicsData{
//JUST POLLING!!! NO MUTATION
let mut collector=instruction::InstructionCollector::new(time_limit);
// collector.collect(state.next_move_instruction());
collector.collect(state.next_move_instruction());
//check for collision ends
state.touching.predict_collision_end(&mut collector,&data.models,&data.hitbox_mesh,&state.body,state.time);
@@ -1140,7 +1134,6 @@ impl PhysicsData{
//let relative_body=state.body.relative_to(&Body::ZERO);
let relative_body=&state.body;
data.bvh.sample_aabb(&aabb,&mut |&convex_mesh_id|{
println!("sample model={:?}",convex_mesh_id.model_id);
//no checks are needed because of the time limits.
let model_mesh=data.models.mesh(convex_mesh_id);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh());
@@ -1672,7 +1665,6 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
}
match ins.instruction{
InternalInstruction::CollisionStart(collision,_)=>{
println!("yeaahhh!");
let mode=data.modes.get_mode(state.mode_state.get_mode_id());
match collision{
Collision::Contact(contact)=>collision_start_contact(

View File

@@ -9,37 +9,10 @@ fn main(){
match arg.as_deref(){
Some("determinism")|None=>test_determinism().unwrap(),
Some("replay")=>run_replay().unwrap(),
Some("bug2")=>physics_bug_2().unwrap(),
_=>println!("invalid argument"),
}
}
fn physics_bug_2()->Result<(),ReplayError>{
println!("loading map file..");
let data=read_entire_file("bhop_monster_jam.snfm")?;
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
// create recording
let mut physics_data=PhysicsData::default();
println!("generating models..");
physics_data.generate_models(&map);
println!("simulating...");
//teleport to bug
// body pos = Vector { array: [Fixed { bits: 554895163352 }, Fixed { bits: 1485633089990 }, Fixed { bits: 1279601007173 }] }
// after the fix it's still happening, possibly for a different reason, new position to evince:
// body pos = Vector { array: [Fixed { bits: 555690659654 }, Fixed { bits: 1490485868773 }, Fixed { bits: 1277783839382 }] }
let mut physics=PhysicsState::new_at_position(strafesnet_common::integer::vec3::raw_xyz(555690659654,1490485868773,1277783839382));
// wait one second to activate the bug
// hit=Some(ModelId(2262))
PhysicsContext::run_input_instruction(&mut physics,&physics_data,strafesnet_common::instruction::TimedInstruction{
time:strafesnet_common::integer::Time::from_millis(500),
instruction:strafesnet_common::physics::Instruction::Idle,
});
Ok(())
}
#[allow(unused)]
#[derive(Debug)]
enum ReplayError{

View File

@@ -648,7 +648,7 @@ pub mod mat3{
}
}
#[derive(Clone,Copy,Debug,Default,Hash,Eq,PartialEq)]
#[derive(Clone,Copy,Default,Hash,Eq,PartialEq)]
pub struct Planar64Affine3{
pub matrix3:Planar64Mat3,//includes scale above 1
pub translation:Planar64Vec3,

1
tools/dev Executable file
View File

@@ -0,0 +1 @@
RUST_BACKTRACE=1 mangohud ../target/debug/strafe-client "$@"