Compare commits
12 Commits
master
...
physics-bu
Author | SHA1 | Date | |
---|---|---|---|
af86be029b | |||
7f20e73588 | |||
9308cc8a5c | |||
2f574b297f | |||
7c306da7b0 | |||
6e9d38604e | |||
a52d46b7cc | |||
9ecb494748 | |||
1f73a8b5c6 | |||
dbae80b1d2 | |||
8cc79304dc | |||
8e4792269d |
engine
integration-testing/src
lib
@ -11,6 +11,15 @@ 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,
|
||||
|
@ -2,6 +2,7 @@ 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),
|
||||
@ -27,17 +28,18 @@ impl<M:MeshQuery> CrawlResult<M>{
|
||||
}
|
||||
}
|
||||
|
||||
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>+std::fmt::Debug> 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>>,
|
||||
F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>+std::fmt::Debug+std::fmt::Display,
|
||||
<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;
|
||||
@ -75,6 +77,7 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||
//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);
|
||||
@ -84,6 +87,7 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||
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()){
|
||||
@ -138,7 +142,9 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||
Ratio::new(r.num.widen_4(),r.den.widen_4())
|
||||
};
|
||||
for _ in 0..20{
|
||||
match self.next_transition(body_time,mesh,relative_body,time_limit){
|
||||
let transition=self.next_transition(body_time,mesh,relative_body,time_limit);
|
||||
println!("transition={transition:?}");
|
||||
match transition{
|
||||
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),
|
||||
|
@ -68,16 +68,17 @@ pub enum FEV<M:MeshQuery>{
|
||||
}
|
||||
|
||||
//use Unit32 #[repr(C)] for map files
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
#[derive(Clone,Debug,Hash,Eq,PartialEq)]
|
||||
struct Face{
|
||||
normal:Planar64Vec3,
|
||||
dot:Planar64,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct Vert(Planar64Vec3);
|
||||
pub trait MeshQuery{
|
||||
type Face:Copy;
|
||||
type Edge:Copy+DirectedEdge;
|
||||
type Vert:Copy;
|
||||
type Face:Copy+std::fmt::Debug;
|
||||
type Edge:Copy+DirectedEdge+std::fmt::Debug;
|
||||
type Vert:Copy+std::fmt::Debug;
|
||||
// Vertex must be Planar64Vec3 because it represents an actual position
|
||||
type Normal;
|
||||
type Offset;
|
||||
@ -97,18 +98,22 @@ 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
|
||||
@ -118,6 +123,7 @@ 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
|
||||
@ -314,6 +320,9 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
|
||||
return Err(PhysicsMeshError::ZeroVertices);
|
||||
}
|
||||
let verts=mesh.unique_pos.iter().copied().map(Vert).collect();
|
||||
// TODO: do not hash faces to get face id
|
||||
// meshes can have multiple identical nd representations while still being distinct faces,
|
||||
// especially when the complete mesh is a non-convex mesh.
|
||||
//TODO: fix submeshes
|
||||
//flat map mesh.physics_groups[$1].groups.polys()[$2] as face_id
|
||||
//lower face_id points to upper face_id
|
||||
@ -422,6 +431,7 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PhysicsMeshView<'a>{
|
||||
data:&'a PhysicsMeshData,
|
||||
topology:&'a PhysicsMeshTopology,
|
||||
@ -458,6 +468,7 @@ impl MeshQuery for PhysicsMeshView<'_>{
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PhysicsMeshTransform{
|
||||
pub vertex:integer::Planar64Affine3,
|
||||
pub normal:integer::mat3::Matrix3<Fixed<2,64>>,
|
||||
@ -473,6 +484,7 @@ impl PhysicsMeshTransform{
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TransformedMesh<'a>{
|
||||
view:PhysicsMeshView<'a>,
|
||||
transform:&'a PhysicsMeshTransform,
|
||||
@ -598,6 +610,7 @@ pub enum MinkowskiFace{
|
||||
//FaceFace
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MinkowskiMesh<'a>{
|
||||
mesh0:TransformedMesh<'a>,
|
||||
mesh1:TransformedMesh<'a>,
|
||||
@ -742,7 +755,9 @@ 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()
|
||||
})
|
||||
@ -899,6 +914,7 @@ 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();
|
||||
@ -906,14 +922,18 @@ 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 best_d<dd{
|
||||
if !dd.den.is_zero()&&best_d<dd{
|
||||
println!("dd={dd:?}");
|
||||
best_d=dd;
|
||||
best_edge=Some(directed_edge_id1);
|
||||
}
|
||||
|
@ -857,6 +857,12 @@ 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,
|
||||
@ -1122,7 +1128,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);
|
||||
@ -1134,6 +1140,7 @@ 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());
|
||||
@ -1665,6 +1672,7 @@ 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(
|
||||
|
@ -9,10 +9,37 @@ 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{
|
||||
|
@ -1,5 +1,5 @@
|
||||
use strafesnet_common::integer::Planar64;
|
||||
use strafesnet_common::{model,integer};
|
||||
use strafesnet_common::integer::{self,Planar64,Planar64Vec3};
|
||||
use strafesnet_common::model::{self,VertexId};
|
||||
use strafesnet_common::integer::{vec3::Vector3,Fixed,Ratio};
|
||||
|
||||
use crate::{valve_transform_normal,valve_transform_dist};
|
||||
@ -138,7 +138,15 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
|
||||
loop{
|
||||
// push point onto vertices
|
||||
// problem: this may push a vertex that does not fit in the fixed point range and is thus meaningless
|
||||
face.push(intersection.divide().narrow_1().unwrap());
|
||||
//
|
||||
// physics bug 2 originates from vertices being imprecise?
|
||||
//
|
||||
// Mask off the most precise 16 bits so that
|
||||
// when face normals are calculated from
|
||||
// the remaining 16 fractional bits
|
||||
// they never exceed 32 bits of precision.
|
||||
const MASK:Planar64=Planar64::raw(!((1<<16)-1));
|
||||
face.push(intersection.divide().narrow_1().unwrap().map(|c|c&MASK));
|
||||
|
||||
// we looped back around to face1, we're done!
|
||||
if core::ptr::eq(face1,face2){
|
||||
@ -204,6 +212,33 @@ impl std::fmt::Display for BrushToMeshError{
|
||||
}
|
||||
impl core::error::Error for BrushToMeshError{}
|
||||
|
||||
fn subdivide_max_area(tris:&mut Vec<Vec<VertexId>>,cw_verts:&[(VertexId,Planar64Vec3)],i0:usize,i2:usize,id0:VertexId,id2:VertexId,v0:Planar64Vec3,v2:Planar64Vec3){
|
||||
if i0+1==i2{
|
||||
return;
|
||||
}
|
||||
let mut best_i1=i0+1;
|
||||
if i0+2<i2{
|
||||
let mut best_area={
|
||||
let (_,v1)=cw_verts[best_i1.rem_euclid(cw_verts.len())];
|
||||
(v2-v0).cross(v1-v0).length_squared()
|
||||
};
|
||||
for i1 in i0+2..=i2-1{
|
||||
let (_,v1)=cw_verts[i1.rem_euclid(cw_verts.len())];
|
||||
let area=(v2-v0).cross(v1-v0).length_squared();
|
||||
if best_area<area{
|
||||
best_i1=i1;
|
||||
best_area=area;
|
||||
}
|
||||
}
|
||||
}
|
||||
let i1=best_i1;
|
||||
let (id1,v1)=cw_verts[i1.rem_euclid(cw_verts.len())];
|
||||
// draw max area first
|
||||
tris.push(vec![id0,id1,id2]);
|
||||
subdivide_max_area(tris,cw_verts,i0,i1,id0,id1,v0,v1);
|
||||
subdivide_max_area(tris,cw_verts,i1,i2,id1,id2,v1,v2);
|
||||
}
|
||||
|
||||
pub fn faces_to_mesh(faces:Vec<Vec<integer::Planar64Vec3>>)->model::Mesh{
|
||||
// generate the mesh
|
||||
let mut mb=model::MeshBuilder::new();
|
||||
@ -212,16 +247,34 @@ pub fn faces_to_mesh(faces:Vec<Vec<integer::Planar64Vec3>>)->model::Mesh{
|
||||
// normals are ignored by physics
|
||||
let normal=mb.acquire_normal_id(integer::vec3::ZERO);
|
||||
|
||||
let polygon_list=faces.into_iter().map(|face|{
|
||||
face.into_iter().map(|pos|{
|
||||
let pos=mb.acquire_pos_id(pos);
|
||||
mb.acquire_vertex_id(model::IndexedVertex{
|
||||
let polygon_list=faces.into_iter().flat_map(|face|{
|
||||
let cw_verts=face.into_iter().map(|position|{
|
||||
let pos=mb.acquire_pos_id(position);
|
||||
(mb.acquire_vertex_id(model::IndexedVertex{
|
||||
pos,
|
||||
tex,
|
||||
normal,
|
||||
color,
|
||||
})
|
||||
}).collect()
|
||||
}),position)
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
// scan and select maximum area triangle O(n^3)
|
||||
let len=cw_verts.len();
|
||||
let cw_verts=cw_verts.as_slice();
|
||||
let ((i0,i1,i2),(v0,v1,v2))=cw_verts[..len-2].iter().enumerate().flat_map(|(i0,&(_,v0))|
|
||||
cw_verts[i0+1..len-1].iter().enumerate().flat_map(move|(i1,&(_,v1))|
|
||||
cw_verts[i0+i1+2..].iter().enumerate().map(move|(i2,&(_,v2))|((i0,i0+i1+1,i0+i1+i2+2),(v0,v1,v2)))
|
||||
)
|
||||
).max_by_key(|&(_,(v0,v1,v2))|(v2-v0).cross(v1-v0).length_squared()).unwrap();
|
||||
// scan and select more maximum area triangles n * O(n)
|
||||
let mut tris=Vec::with_capacity(len-2);
|
||||
// da big one
|
||||
let (id0,id1,id2)=(cw_verts[i0].0,cw_verts[i1].0,cw_verts[i2].0);
|
||||
tris.push(vec![id0,id1,id2]);
|
||||
subdivide_max_area(&mut tris,cw_verts,i0,i1,id0,id1,v0,v1);
|
||||
subdivide_max_area(&mut tris,cw_verts,i1,i2,id1,id2,v1,v2);
|
||||
subdivide_max_area(&mut tris,cw_verts,i2,i0+len,id2,id0,v2,v0);
|
||||
tris
|
||||
}).collect();
|
||||
|
||||
let polygon_groups=vec![model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))];
|
||||
|
@ -648,7 +648,7 @@ pub mod mat3{
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Default,Hash,Eq,PartialEq)]
|
||||
#[derive(Clone,Copy,Debug,Default,Hash,Eq,PartialEq)]
|
||||
pub struct Planar64Affine3{
|
||||
pub matrix3:Planar64Mat3,//includes scale above 1
|
||||
pub translation:Planar64Vec3,
|
||||
|
Loading…
x
Reference in New Issue
Block a user