Compare commits
9 Commits
bug3-real
...
phys-new-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
d8804c0901
|
|||
|
865d1756b9
|
|||
|
3188f78744
|
|||
|
3e167af521
|
|||
|
8cf69933b1
|
|||
|
caef37dc36
|
|||
|
1832ce07ee
|
|||
|
5ac65ad845
|
|||
|
76696a3162
|
@@ -90,6 +90,16 @@ pub trait MeshQuery{
|
||||
let &[v0,v1]=self.edge_verts(directed_edge_id.as_undirected()).as_ref();
|
||||
(self.vert(v1)-self.vert(v0))*((directed_edge_id.parity() as i64)*2-1)
|
||||
}
|
||||
/// Returns an iterator over the vertices in the direction of the directed edges.
|
||||
/// Intended to be used to find adjacent vertices:
|
||||
/// `self.directed_verts(self.vert_edges(vert_id).as_ref())`
|
||||
/// TODO: rewrite this function as `adjacent_vertices`
|
||||
fn directed_verts(&self,edges:&[Self::Edge])->impl Iterator<Item=Self::Vert>{
|
||||
edges.iter().map(|e|{
|
||||
let edge_verts=self.edge_verts(e.as_undirected());
|
||||
edge_verts.as_ref()[e.parity() as usize]
|
||||
})
|
||||
}
|
||||
fn vert(&self,vert_id:Self::Vert)->Planar64Vec3;
|
||||
fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset);
|
||||
fn face_edges(&self,face_id:Self::Face)->impl AsRef<[Self::Edge]>;
|
||||
@@ -452,6 +462,8 @@ impl MeshQuery for PhysicsMeshView<'_>{
|
||||
let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize;
|
||||
self.data.verts[vert_idx].0
|
||||
}
|
||||
/// Directed edges going clockwise when looking in the direction of the face normal.
|
||||
/// (Edit this documentation if this is wrong!)
|
||||
fn face_edges(&self,face_id:SubmeshFaceId)->impl AsRef<[SubmeshDirectedEdgeId]>{
|
||||
self.topology.face_topology[face_id.get() as usize].edges.as_slice()
|
||||
}
|
||||
@@ -614,17 +626,6 @@ pub struct MinkowskiMesh<'a>{
|
||||
mesh1:TransformedMesh<'a>,
|
||||
}
|
||||
|
||||
//infinity fev algorithm state transition
|
||||
#[derive(Debug)]
|
||||
enum Transition{
|
||||
Done,//found closest vert, no edges are better
|
||||
Vert(MinkowskiVert),//transition to vert
|
||||
}
|
||||
enum EV{
|
||||
Vert(MinkowskiVert),
|
||||
Edge(MinkowskiEdge),
|
||||
}
|
||||
|
||||
pub type GigaTime=Ratio<Fixed<4,128>,Fixed<4,128>>;
|
||||
pub fn into_giga_time(time:Time,relative_to:Time)->GigaTime{
|
||||
let r=(time-relative_to).to_ratio();
|
||||
@@ -641,137 +642,195 @@ impl MinkowskiMesh<'_>{
|
||||
fn farthest_vert(&self,dir:Planar64Vec3)->MinkowskiVert{
|
||||
MinkowskiVert::VertVert(self.mesh0.farthest_vert(dir),self.mesh1.farthest_vert(-dir))
|
||||
}
|
||||
fn next_transition_vert(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->Transition{
|
||||
let mut best_transition=Transition::Done;
|
||||
for &directed_edge_id in self.vert_edges(vert_id).as_ref(){
|
||||
let edge_n=self.directed_edge_n(directed_edge_id);
|
||||
//is boundary uncrossable by a crawl from infinity
|
||||
let edge_verts=self.edge_verts(directed_edge_id.as_undirected());
|
||||
//select opposite vertex
|
||||
let test_vert_id=edge_verts.as_ref()[directed_edge_id.parity() as usize];
|
||||
//test if it's closer
|
||||
let diff=point-self.vert(test_vert_id);
|
||||
if edge_n.dot(infinity_dir).is_zero(){
|
||||
let distance_squared=diff.dot(diff);
|
||||
if distance_squared<*best_distance_squared{
|
||||
best_transition=Transition::Vert(test_vert_id);
|
||||
*best_distance_squared=distance_squared;
|
||||
}
|
||||
}
|
||||
}
|
||||
best_transition
|
||||
}
|
||||
fn final_ev(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{
|
||||
let mut best_transition=EV::Vert(vert_id);
|
||||
let diff=point-self.vert(vert_id);
|
||||
for &directed_edge_id in self.vert_edges(vert_id).as_ref(){
|
||||
let edge_n=self.directed_edge_n(directed_edge_id);
|
||||
//is boundary uncrossable by a crawl from infinity
|
||||
//check if time of collision is outside Time::MIN..Time::MAX
|
||||
if edge_n.dot(infinity_dir).is_zero(){
|
||||
let d=edge_n.dot(diff);
|
||||
//test the edge
|
||||
let edge_nn=edge_n.dot(edge_n);
|
||||
if !d.is_negative()&&d<=edge_nn{
|
||||
let distance_squared={
|
||||
let c=diff.cross(edge_n);
|
||||
//wrap for speed
|
||||
(c.dot(c)/edge_nn).divide().wrap_2()
|
||||
};
|
||||
if distance_squared<=*best_distance_squared{
|
||||
best_transition=EV::Edge(directed_edge_id.as_undirected());
|
||||
*best_distance_squared=distance_squared;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
best_transition
|
||||
}
|
||||
fn crawl_boundaries(&self,mut vert_id:MinkowskiVert,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{
|
||||
fn closest_fev_not_inside(&self,relative_position:Planar64Vec3)->Option<FEV<MinkowskiMesh<'_>>>{
|
||||
// Make a fast guess as to what the closest point will be.
|
||||
let MinkowskiVert::VertVert(mut v0,mut v1)=self.farthest_vert(relative_position);
|
||||
// TODO: alternate naive vertex searches to improve the robustness
|
||||
// in the "tall bipyramid" failure case
|
||||
let mut m0v=self.mesh0.vert(v0);
|
||||
let mut m1v=self.mesh1.vert(v1);
|
||||
let mut best_distance_squared={
|
||||
let diff=point-self.vert(vert_id);
|
||||
let diff=relative_position+m1v-m0v;
|
||||
diff.dot(diff)
|
||||
};
|
||||
let mut v0e=self.mesh0.vert_edges(v0);
|
||||
let mut v1e=self.mesh1.vert_edges(v1);
|
||||
let mut v0e_ref=v0e.as_ref();
|
||||
let mut v1e_ref=v1e.as_ref();
|
||||
// repeatedly check adjacent vertex permutations to see if they are closer
|
||||
loop{
|
||||
match self.next_transition_vert(vert_id,&mut best_distance_squared,infinity_dir,point){
|
||||
Transition::Done=>return self.final_ev(vert_id,&mut best_distance_squared,infinity_dir,point),
|
||||
Transition::Vert(new_vert_id)=>vert_id=new_vert_id,
|
||||
let mut best_v0=None;
|
||||
let mut best_v1=None;
|
||||
|
||||
// check vertices adjacent to v1 against v0
|
||||
for m1_test_vert in self.mesh1.directed_verts(v1e_ref){
|
||||
let m1v_test=self.mesh1.vert(m1_test_vert);
|
||||
let diff=relative_position+m1v_test-m0v;
|
||||
let d=diff.dot(diff);
|
||||
if d<best_distance_squared{
|
||||
best_distance_squared=d;
|
||||
best_v0=None;
|
||||
best_v1=Some(m1_test_vert);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// This function drops a vertex down to an edge or a face if the path from infinity did not cross any vertex-edge boundaries but the point is supposed to have already crossed a boundary down from a vertex
|
||||
fn infinity_fev(&self,infinity_dir:Planar64Vec3,point:Planar64Vec3)->FEV::<MinkowskiMesh<'_>>{
|
||||
//start on any vertex
|
||||
//cross uncrossable vertex-edge boundaries until you find the closest vertex or edge
|
||||
//cross edge-face boundary if it's uncrossable
|
||||
match self.crawl_boundaries(self.farthest_vert(infinity_dir),infinity_dir,point){
|
||||
//if a vert is returned, it is the closest point to the infinity point
|
||||
EV::Vert(vert_id)=>FEV::Vert(vert_id),
|
||||
EV::Edge(edge_id)=>{
|
||||
//cross to face if the boundary is not crossable and we are on the wrong side
|
||||
let edge_n=self.edge_n(edge_id);
|
||||
// point is multiplied by two because vert_sum sums two vertices.
|
||||
let delta_pos=point*2-{
|
||||
let &[v0,v1]=self.edge_verts(edge_id).as_ref();
|
||||
self.vert(v0)+self.vert(v1)
|
||||
};
|
||||
for (i,&face_id) in self.edge_faces(edge_id).as_ref().iter().enumerate(){
|
||||
let face_n=self.face_nd(face_id).0;
|
||||
//edge-face boundary nd, n facing out of the face towards the edge
|
||||
let boundary_n=face_n.cross(edge_n)*(i as i64*2-1);
|
||||
let boundary_d=boundary_n.dot(delta_pos);
|
||||
//check if time of collision is outside Time::MIN..Time::MAX
|
||||
//infinity_dir can always be treated as a velocity
|
||||
if !boundary_d.is_positive()&&boundary_n.dot(infinity_dir).is_zero(){
|
||||
//both faces cannot pass this condition, return early if one does.
|
||||
return FEV::Face(face_id);
|
||||
|
||||
// check vertices adjacent to v0 against v1
|
||||
for m0_test_vert in self.mesh0.directed_verts(v0e_ref){
|
||||
let m0v_test=self.mesh0.vert(m0_test_vert);
|
||||
let diff=relative_position+m1v-m0v_test;
|
||||
let d=diff.dot(diff);
|
||||
if d<best_distance_squared{
|
||||
best_distance_squared=d;
|
||||
best_v0=Some(m0_test_vert);
|
||||
best_v1=None;
|
||||
}
|
||||
}
|
||||
|
||||
// check permutations of adjacent vertices
|
||||
for m0_test_vert in self.mesh0.directed_verts(v0e_ref){
|
||||
let m0v_test=self.mesh0.vert(m0_test_vert);
|
||||
for m1_test_vert in self.mesh1.directed_verts(v1e_ref){
|
||||
let m1v_test=self.mesh1.vert(m1_test_vert);
|
||||
let diff=relative_position+m1v_test-m0v_test;
|
||||
let d=diff.dot(diff);
|
||||
if d<best_distance_squared{
|
||||
best_distance_squared=d;
|
||||
best_v0=Some(m0_test_vert);
|
||||
best_v1=Some(m1_test_vert);
|
||||
}
|
||||
}
|
||||
FEV::Edge(edge_id)
|
||||
},
|
||||
}
|
||||
}
|
||||
// TODO: fundamentally improve this algorithm.
|
||||
// All it needs to do is find the closest point on the mesh
|
||||
// and return the FEV which the point resides on.
|
||||
//
|
||||
// What it actually does is use the above functions to trace a ray in from infinity,
|
||||
// crawling the closest point along the mesh surface until the ray reaches
|
||||
// the starting point to discover the final FEV.
|
||||
//
|
||||
// The actual collision prediction probably does a single test
|
||||
// and then immediately returns with 0 FEV transitions on average,
|
||||
// because of the strict time_limit constraint.
|
||||
//
|
||||
// Most of the calculation time is just calculating the starting point
|
||||
// for the "actual" crawling algorithm below (predict_collision_{in|out}).
|
||||
fn closest_fev_not_inside(&self,mut infinity_body:Body,start_time:Bound<&Time>)->Option<FEV<MinkowskiMesh<'_>>>{
|
||||
infinity_body.infinity_dir().and_then(|dir|{
|
||||
let infinity_fev=self.infinity_fev(-dir,infinity_body.position);
|
||||
//a line is simpler to solve than a parabola
|
||||
infinity_body.velocity=dir;
|
||||
infinity_body.acceleration=vec3::ZERO;
|
||||
//crawl in from negative infinity along a tangent line to get the closest fev
|
||||
match infinity_fev.crawl(self,&infinity_body,Bound::Unbounded,start_time){
|
||||
// This is the expected case.
|
||||
// We expect to never hit the mesh while setting up for the real crawl
|
||||
// since the algorithm breaks down on the inside of the mesh.
|
||||
crate::face_crawler::CrawlResult::Miss(fev)=>Some(fev),
|
||||
// An exact hit is allowed, it has not crossed the boundary.
|
||||
crate::face_crawler::CrawlResult::Hit(face,ratio)=>match start_time{
|
||||
Bound::Included(_)=>ratio.num.is_zero().then(||FEV::Face(face)),
|
||||
// You are looking for collision events within a range that does not include the start_time.
|
||||
// The boundary is crossed at exactly start_time, so the range is not met.
|
||||
// Therefore, the correct return value is None.
|
||||
Bound::Excluded(_)=>unimplemented!(),
|
||||
// To infinity and beyond!
|
||||
Bound::Unbounded=>None,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// end condition
|
||||
let v0_changed=match best_v0{
|
||||
Some(new_v0)=>{
|
||||
v0=new_v0;
|
||||
m0v=self.mesh0.vert(v0);
|
||||
v0e=self.mesh0.vert_edges(v0);
|
||||
v0e_ref=v0e.as_ref();
|
||||
true
|
||||
},
|
||||
None=>false,
|
||||
};
|
||||
let v1_changed=match best_v1{
|
||||
Some(new_v1)=>{
|
||||
v1=new_v1;
|
||||
m1v=self.mesh1.vert(v1);
|
||||
v1e=self.mesh1.vert_edges(v1);
|
||||
v1e_ref=v1e.as_ref();
|
||||
true
|
||||
},
|
||||
None=>false,
|
||||
};
|
||||
// if neither vertex changes, we found the closest two vertices.
|
||||
if !(v0_changed&&v1_changed){
|
||||
break;
|
||||
}
|
||||
}
|
||||
// now you have the two closest vertices
|
||||
let mut best_fev=FEV::Vert(MinkowskiVert::VertVert(v0,v1));
|
||||
// ==== FEV::Edge ====
|
||||
// test VertEdges
|
||||
for &e1 in v1e_ref{
|
||||
let edge_verts=self.mesh1.edge_verts(e1.as_undirected());
|
||||
let &[ev0_id,ev1_id]=edge_verts.as_ref();
|
||||
let (ev0,ev1)=(self.mesh1.vert(ev0_id),self.mesh1.vert(ev1_id));
|
||||
let edge_n=ev1-ev0;
|
||||
// use relative coordinates to make including relative_position easier
|
||||
let diff=relative_position+m0v-ev0;
|
||||
let d=diff.dot(edge_n);
|
||||
// is test point between edge vertices
|
||||
let edge_nn=edge_n.dot(edge_n);
|
||||
if d.is_positive()&&d<edge_nn{
|
||||
let distance_squared={
|
||||
let c=diff.cross(edge_n);
|
||||
//wrap for speed
|
||||
(c.dot(c)/edge_nn).divide().wrap_2()
|
||||
};
|
||||
if distance_squared<best_distance_squared{
|
||||
best_distance_squared=distance_squared;
|
||||
best_fev=FEV::Edge(MinkowskiEdge::VertEdge(v0,e1.as_undirected()))
|
||||
}
|
||||
}
|
||||
}
|
||||
// test EdgeVerts
|
||||
for &e0 in v0e_ref{
|
||||
let edge_verts=self.mesh0.edge_verts(e0.as_undirected());
|
||||
let &[ev0_id,ev1_id]=edge_verts.as_ref();
|
||||
let (ev0,ev1)=(self.mesh0.vert(ev0_id),self.mesh0.vert(ev1_id));
|
||||
let edge_n=ev1-ev0;
|
||||
// use relative coordinates to make including relative_position easier
|
||||
let diff=m1v-relative_position-ev0;
|
||||
let d=diff.dot(edge_n);
|
||||
// is test point between edge vertices
|
||||
let edge_nn=edge_n.dot(edge_n);
|
||||
if d.is_positive()&&d<edge_nn{
|
||||
let distance_squared={
|
||||
let c=diff.cross(edge_n);
|
||||
//wrap for speed
|
||||
(c.dot(c)/edge_nn).divide().wrap_2()
|
||||
};
|
||||
if distance_squared<best_distance_squared{
|
||||
best_distance_squared=distance_squared;
|
||||
best_fev=FEV::Edge(MinkowskiEdge::EdgeVert(e0.as_undirected(),v1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==== FEV::Face ====
|
||||
// test VertFaces
|
||||
'outer: for &f1 in self.mesh1.vert_faces(v1).as_ref(){
|
||||
let (n,d)=self.mesh1.face_nd(f1);
|
||||
// Test the face's voronoi column
|
||||
for &e1 in self.mesh1.face_edges(f1).as_ref(){
|
||||
let edge_n=self.mesh1.directed_edge_n(e1);
|
||||
let boundary_n=n.cross(edge_n);
|
||||
let &[ev0_id,ev1_id]=self.mesh1.edge_verts(e1.as_undirected()).as_ref();
|
||||
let (ev0,ev1)=(self.mesh1.vert(ev0_id),self.mesh1.vert(ev1_id));
|
||||
let diff=(relative_position+m0v)*2-(ev0+ev1);
|
||||
if boundary_n.dot(diff).is_negative(){
|
||||
// The test point is outside the face's voronoi column.
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
// Calculate distance
|
||||
let d=n.dot(relative_position+m0v)-d;
|
||||
// Wrap for speed
|
||||
let distance_squared=(d*d).wrap_2();
|
||||
if distance_squared<best_distance_squared{
|
||||
best_distance_squared=distance_squared;
|
||||
best_fev=FEV::Face(MinkowskiFace::VertFace(v0,f1));
|
||||
}
|
||||
}
|
||||
// test FaceVerts
|
||||
'outer: for &f0 in self.mesh0.vert_faces(v0).as_ref(){
|
||||
let (n,d)=self.mesh0.face_nd(f0);
|
||||
// Test the face's voronoi column
|
||||
for &e0 in self.mesh0.face_edges(f0).as_ref(){
|
||||
let edge_n=self.mesh0.directed_edge_n(e0);
|
||||
let boundary_n=n.cross(edge_n);
|
||||
let &[ev0_id,ev1_id]=self.mesh0.edge_verts(e0.as_undirected()).as_ref();
|
||||
let (ev0,ev1)=(self.mesh0.vert(ev0_id),self.mesh0.vert(ev1_id));
|
||||
let diff=(m1v-relative_position)*2-(ev0+ev1);
|
||||
if boundary_n.dot(diff).is_negative(){
|
||||
// The test point is outside the face's voronoi column.
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
// Calculate distance
|
||||
let d=n.dot(relative_position+m0v)-d;
|
||||
// Wrap for speed
|
||||
let distance_squared=(d*d).wrap_2();
|
||||
if distance_squared<best_distance_squared{
|
||||
best_distance_squared=distance_squared;
|
||||
best_fev=FEV::Face(MinkowskiFace::FaceVert(f0,v1));
|
||||
}
|
||||
}
|
||||
// test EdgeEdges
|
||||
Some(best_fev)
|
||||
}
|
||||
pub fn predict_collision_in(&self,relative_body:&Body,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{
|
||||
self.closest_fev_not_inside(*relative_body,range.start_bound()).and_then(|fev|{
|
||||
self.closest_fev_not_inside(relative_body.position).and_then(|fev|{
|
||||
//continue forwards along the body parabola
|
||||
fev.crawl(self,relative_body,range.start_bound(),range.end_bound()).hit()
|
||||
})
|
||||
@@ -780,10 +839,9 @@ impl MinkowskiMesh<'_>{
|
||||
let (lower_bound,upper_bound)=(range.start_bound(),range.end_bound());
|
||||
// swap and negate bounds to do a time inversion
|
||||
let (lower_bound,upper_bound)=(upper_bound.map(|&t|-t),lower_bound.map(|&t|-t));
|
||||
let infinity_body=-relative_body;
|
||||
self.closest_fev_not_inside(infinity_body,lower_bound.as_ref()).and_then(|fev|{
|
||||
self.closest_fev_not_inside(relative_body.position).and_then(|fev|{
|
||||
//continue backwards along the body parabola
|
||||
fev.crawl(self,&infinity_body,lower_bound.as_ref(),upper_bound.as_ref()).hit()
|
||||
fev.crawl(self,&-relative_body,lower_bound.as_ref(),upper_bound.as_ref()).hit()
|
||||
//no need to test -time<time_limit because of the first step
|
||||
.map(|(face,time)|(face,-time))
|
||||
})
|
||||
@@ -816,20 +874,10 @@ impl MinkowskiMesh<'_>{
|
||||
}
|
||||
best_edge
|
||||
}
|
||||
fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{
|
||||
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
|
||||
// Bound::Included means that the surface of the mesh is included in the mesh
|
||||
infinity_fev.crawl(self,&infinity_body,Bound::Unbounded,Bound::Included(&infinity_body.time)).hit()
|
||||
}
|
||||
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
|
||||
let infinity_body=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO);
|
||||
//movement must escape the mesh forwards and backwards in time,
|
||||
//otherwise the point is not inside the mesh
|
||||
self.infinity_in(infinity_body)
|
||||
.is_some_and(|_|
|
||||
self.infinity_in(-infinity_body)
|
||||
.is_some()
|
||||
)
|
||||
// TODO
|
||||
println!("Unimplemented is_point_in_mesh called! {point}");
|
||||
false
|
||||
}
|
||||
}
|
||||
impl MeshQuery for MinkowskiMesh<'_>{
|
||||
|
||||
@@ -28,7 +28,6 @@ pub enum InternalInstruction{
|
||||
CollisionStart(Collision,model_physics::GigaTime),
|
||||
CollisionEnd(Collision,model_physics::GigaTime),
|
||||
StrafeTick,
|
||||
// TODO: add GigaTime to ReachWalkTargetVelocity
|
||||
ReachWalkTargetVelocity,
|
||||
// Water,
|
||||
}
|
||||
@@ -88,7 +87,7 @@ enum JumpDirection{
|
||||
impl JumpDirection{
|
||||
fn direction(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{
|
||||
match self{
|
||||
JumpDirection::FromContactNormal=>contact_normal(models,hitbox_mesh,&contact.convex_mesh_id,contact.face_id),
|
||||
JumpDirection::FromContactNormal=>contact_normal(models,hitbox_mesh,contact),
|
||||
&JumpDirection::Exactly(dir)=>dir,
|
||||
}
|
||||
}
|
||||
@@ -101,6 +100,7 @@ enum TransientAcceleration{
|
||||
time:Time,
|
||||
},
|
||||
//walk target will never be reached
|
||||
#[expect(dead_code)]
|
||||
Unreachable{
|
||||
acceleration:Planar64Vec3,
|
||||
}
|
||||
@@ -116,10 +116,6 @@ impl TransientAcceleration{
|
||||
fn with_target_diff(target_diff:Planar64Vec3,accel:Planar64,time:Time)->Self{
|
||||
if target_diff==vec3::ZERO{
|
||||
TransientAcceleration::Reached
|
||||
}else if accel==Planar64::ZERO{
|
||||
TransientAcceleration::Unreachable{
|
||||
acceleration:vec3::ZERO
|
||||
}
|
||||
}else{
|
||||
//normal friction acceleration is clippedAcceleration.dot(normal)*friction
|
||||
TransientAcceleration::Reachable{
|
||||
@@ -164,7 +160,7 @@ impl ContactMoveState{
|
||||
}
|
||||
}
|
||||
fn ground_things(walk_settings:&gameplay_style::WalkSettings,contact:&ContactCollision,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->(Planar64Vec3,Planar64Vec3){
|
||||
let normal=contact_normal(models,hitbox_mesh,&contact.convex_mesh_id,contact.face_id);
|
||||
let normal=contact_normal(models,hitbox_mesh,contact);
|
||||
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);
|
||||
@@ -172,7 +168,7 @@ fn ground_things(walk_settings:&gameplay_style::WalkSettings,contact:&ContactCol
|
||||
(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){
|
||||
let normal=contact_normal(models,hitbox_mesh,&contact.convex_mesh_id,contact.face_id);
|
||||
let normal=contact_normal(models,hitbox_mesh,contact);
|
||||
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);
|
||||
@@ -197,7 +193,7 @@ impl PhysicsModels{
|
||||
self.contact_attributes.clear();
|
||||
self.intersect_attributes.clear();
|
||||
}
|
||||
fn mesh(&self,convex_mesh_id:ConvexMeshId<PhysicsModelId>)->TransformedMesh<'_>{
|
||||
fn mesh(&self,convex_mesh_id:ConvexMeshId)->TransformedMesh<'_>{
|
||||
let (mesh_id,transform)=match convex_mesh_id.model_id{
|
||||
PhysicsModelId::Contact(model_id)=>{
|
||||
let model=&self.contact_models[&model_id];
|
||||
@@ -214,25 +210,25 @@ impl PhysicsModels{
|
||||
)
|
||||
}
|
||||
//it's a bit weird to have three functions, but it's always going to be one of these
|
||||
fn contact_mesh(&self,convex_mesh_id:&ConvexMeshId<ContactModelId>)->TransformedMesh<'_>{
|
||||
let model=&self.contact_models[&convex_mesh_id.model_id];
|
||||
fn contact_mesh(&self,contact:&ContactCollision)->TransformedMesh<'_>{
|
||||
let model=&self.contact_models[&contact.model_id];
|
||||
TransformedMesh::new(
|
||||
self.meshes[&model.mesh_id].submesh_view(convex_mesh_id.submesh_id),
|
||||
self.meshes[&model.mesh_id].submesh_view(contact.submesh_id),
|
||||
&model.transform
|
||||
)
|
||||
}
|
||||
fn intersect_mesh(&self,convex_mesh_id:&ConvexMeshId<IntersectModelId>)->TransformedMesh<'_>{
|
||||
let model=&self.intersect_models[&convex_mesh_id.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(convex_mesh_id.submesh_id),
|
||||
self.meshes[&model.mesh_id].submesh_view(intersect.submesh_id),
|
||||
&model.transform
|
||||
)
|
||||
}
|
||||
fn get_model_transform(&self,model_id:ModelId)->Option<&PhysicsMeshTransform>{
|
||||
//ModelId can possibly be a decoration
|
||||
match self.contact_models.get(&model_id.into()){
|
||||
match self.contact_models.get(&ContactModelId::new(model_id.get())){
|
||||
Some(model)=>Some(&model.transform),
|
||||
None=>self.intersect_models.get(&model_id.into())
|
||||
None=>self.intersect_models.get(&IntersectModelId::new(model_id.get()))
|
||||
.map(|model|&model.transform),
|
||||
}
|
||||
}
|
||||
@@ -615,7 +611,7 @@ impl MoveState{
|
||||
// TODO: unduplicate this code
|
||||
match self.get_walk_state(){
|
||||
// did you stop touching the thing you were walking on?
|
||||
Some(walk_state)=>if !touching.contacts.contains_key(&walk_state.contact.convex_mesh_id){
|
||||
Some(walk_state)=>if !touching.contacts.contains(&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
|
||||
@@ -646,9 +642,9 @@ impl TryFrom<&gameplay_attributes::CollisionAttributes> for PhysicsCollisionAttr
|
||||
}
|
||||
#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
|
||||
struct ContactAttributesId(u32);
|
||||
impl From<ContactAttributesId> for CollisionAttributesId{
|
||||
fn from(value:ContactAttributesId)->CollisionAttributesId{
|
||||
CollisionAttributesId::new(value.0)
|
||||
impl Into<CollisionAttributesId> for ContactAttributesId{
|
||||
fn into(self)->CollisionAttributesId{
|
||||
CollisionAttributesId::new(self.0)
|
||||
}
|
||||
}
|
||||
impl From<CollisionAttributesId> for ContactAttributesId{
|
||||
@@ -658,9 +654,9 @@ impl From<CollisionAttributesId> for ContactAttributesId{
|
||||
}
|
||||
#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
|
||||
struct IntersectAttributesId(u32);
|
||||
impl From<IntersectAttributesId> for CollisionAttributesId{
|
||||
fn from(value:IntersectAttributesId)->CollisionAttributesId{
|
||||
CollisionAttributesId::new(value.0)
|
||||
impl Into<CollisionAttributesId> for IntersectAttributesId{
|
||||
fn into(self)->CollisionAttributesId{
|
||||
CollisionAttributesId::new(self.0)
|
||||
}
|
||||
}
|
||||
impl From<CollisionAttributesId> for IntersectAttributesId{
|
||||
@@ -670,26 +666,16 @@ impl From<CollisionAttributesId> for IntersectAttributesId{
|
||||
}
|
||||
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
|
||||
struct ContactModelId(u32);
|
||||
impl From<ContactModelId> for ModelId{
|
||||
fn from(value:ContactModelId)->ModelId{
|
||||
ModelId::new(value.get())
|
||||
}
|
||||
}
|
||||
impl From<ModelId> for ContactModelId{
|
||||
fn from(other: ModelId)->Self{
|
||||
Self::new(other.get())
|
||||
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 From<IntersectModelId> for ModelId{
|
||||
fn from(value:IntersectModelId)->ModelId{
|
||||
ModelId::new(value.get())
|
||||
}
|
||||
}
|
||||
impl From<ModelId> for IntersectModelId{
|
||||
fn from(other: ModelId)->Self{
|
||||
Self::new(other.get())
|
||||
impl Into<ModelId> for IntersectModelId{
|
||||
fn into(self)->ModelId{
|
||||
ModelId::new(self.get())
|
||||
}
|
||||
}
|
||||
#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)]
|
||||
@@ -697,9 +683,9 @@ enum PhysicsModelId{
|
||||
Contact(ContactModelId),
|
||||
Intersect(IntersectModelId),
|
||||
}
|
||||
impl From<PhysicsModelId> for ModelId{
|
||||
fn from(value:PhysicsModelId)->ModelId{
|
||||
ModelId::new(match value{
|
||||
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(),
|
||||
})
|
||||
@@ -707,18 +693,10 @@ impl From<PhysicsModelId> for ModelId{
|
||||
}
|
||||
//unique physics meshes indexed by this
|
||||
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
|
||||
struct ConvexMeshId<Id>{
|
||||
model_id:Id,
|
||||
struct ConvexMeshId{
|
||||
model_id:PhysicsModelId,
|
||||
submesh_id:PhysicsSubmeshId,
|
||||
}
|
||||
impl<Id> ConvexMeshId<Id>{
|
||||
fn map<NewId>(self,model_id:NewId)->ConvexMeshId<NewId>{
|
||||
ConvexMeshId{
|
||||
model_id,
|
||||
submesh_id:self.submesh_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
struct ContactModel{
|
||||
mesh_id:PhysicsMeshId,
|
||||
attr_id:ContactAttributesId,
|
||||
@@ -732,12 +710,14 @@ struct IntersectModel{
|
||||
|
||||
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
|
||||
pub struct ContactCollision{
|
||||
convex_mesh_id:ConvexMeshId<ContactModelId>,
|
||||
face_id:model_physics::MinkowskiFace,
|
||||
model_id:ContactModelId,
|
||||
submesh_id:PhysicsSubmeshId,
|
||||
}
|
||||
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
|
||||
pub struct IntersectCollision{
|
||||
convex_mesh_id:ConvexMeshId<IntersectModelId>,
|
||||
model_id:IntersectModelId,
|
||||
submesh_id:PhysicsSubmeshId,
|
||||
}
|
||||
#[derive(Debug,Clone,Eq,Hash,PartialEq)]
|
||||
pub enum Collision{
|
||||
@@ -745,39 +725,33 @@ pub enum Collision{
|
||||
Intersect(IntersectCollision),
|
||||
}
|
||||
impl Collision{
|
||||
fn new(convex_mesh_id:ConvexMeshId<PhysicsModelId>,face_id:model_physics::MinkowskiFace)->Self{
|
||||
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{convex_mesh_id:convex_mesh_id.map(model_id),face_id}),
|
||||
PhysicsModelId::Intersect(model_id)=>Collision::Intersect(IntersectCollision{convex_mesh_id:convex_mesh_id.map(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}),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone,Debug,Default)]
|
||||
struct TouchingState{
|
||||
contacts:HashMap<ConvexMeshId<ContactModelId>,model_physics::MinkowskiFace>,
|
||||
intersects:HashSet<ConvexMeshId<IntersectModelId>>,
|
||||
contacts:HashSet<ContactCollision>,
|
||||
intersects:HashSet<IntersectCollision>,
|
||||
}
|
||||
impl TouchingState{
|
||||
fn clear(&mut self){
|
||||
self.contacts.clear();
|
||||
self.intersects.clear();
|
||||
}
|
||||
fn insert_contact(&mut self,contact:ContactCollision)->Option<model_physics::MinkowskiFace>{
|
||||
self.contacts.insert(contact.convex_mesh_id,contact.face_id)
|
||||
fn insert(&mut self,collision:Collision)->bool{
|
||||
match collision{
|
||||
Collision::Contact(collision)=>self.contacts.insert(collision),
|
||||
Collision::Intersect(collision)=>self.intersects.insert(collision),
|
||||
}
|
||||
}
|
||||
fn insert_intersect(&mut self,intersect:IntersectCollision)->bool{
|
||||
self.intersects.insert(intersect.convex_mesh_id)
|
||||
}
|
||||
fn remove_contact(&mut self,convex_mesh_id:&ConvexMeshId<ContactModelId>)->Option<model_physics::MinkowskiFace>{
|
||||
self.contacts.remove(convex_mesh_id)
|
||||
}
|
||||
fn remove_intersect(&mut self,convex_mesh_id:&ConvexMeshId<IntersectModelId>)->bool{
|
||||
self.intersects.remove(convex_mesh_id)
|
||||
}
|
||||
fn contains(&self,convex_mesh_id:&ConvexMeshId<PhysicsModelId>)->bool{
|
||||
match convex_mesh_id.model_id{
|
||||
PhysicsModelId::Contact(contact_model_id)=>self.contacts.contains_key(&convex_mesh_id.map(contact_model_id)),
|
||||
PhysicsModelId::Intersect(intersect_model_id)=>self.intersects.contains(&convex_mesh_id.map(intersect_model_id)),
|
||||
fn remove(&mut self,collision:&Collision)->bool{
|
||||
match collision{
|
||||
Collision::Contact(collision)=>self.contacts.remove(collision),
|
||||
Collision::Intersect(collision)=>self.intersects.remove(collision),
|
||||
}
|
||||
}
|
||||
fn base_acceleration(&self,models:&PhysicsModels,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->Planar64Vec3{
|
||||
@@ -786,7 +760,7 @@ impl TouchingState{
|
||||
a+=rocket_settings.acceleration(style.get_propulsion_control_dir(camera,input_state.controls));
|
||||
}
|
||||
//add accelerators
|
||||
for contact in self.contacts.keys(){
|
||||
for contact in &self.contacts{
|
||||
if let Some(accelerator)=&models.contact_attr(contact.model_id).general.accelerator{
|
||||
a+=accelerator.acceleration;
|
||||
}
|
||||
@@ -800,8 +774,8 @@ impl TouchingState{
|
||||
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)|{
|
||||
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
|
||||
let contacts:Vec<_>=self.contacts.iter().map(|contact|{
|
||||
let n=contact_normal(models,hitbox_mesh,contact);
|
||||
crate::push_solve::Contact{
|
||||
position:vec3::ZERO,
|
||||
velocity:n,
|
||||
@@ -811,8 +785,8 @@ impl TouchingState{
|
||||
crate::push_solve::push_solve(&contacts,velocity)
|
||||
}
|
||||
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);
|
||||
let contacts:Vec<_>=self.contacts.iter().map(|contact|{
|
||||
let n=contact_normal(models,hitbox_mesh,contact);
|
||||
crate::push_solve::Contact{
|
||||
position:vec3::ZERO,
|
||||
velocity:n,
|
||||
@@ -824,29 +798,29 @@ impl TouchingState{
|
||||
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,Time>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,start_time:Time){
|
||||
// let relative_body=body.relative_to(&Body::ZERO);
|
||||
let relative_body=body;
|
||||
for (convex_mesh_id,face_id) in &self.contacts{
|
||||
for contact in &self.contacts{
|
||||
//detect face slide off
|
||||
let model_mesh=models.contact_mesh(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,start_time..=collector.time(),*face_id).map(|(_face,time)|{
|
||||
collector.collect(minkowski.predict_collision_face_out(&relative_body,start_time..collector.time(),contact.face_id).map(|(_face,time)|{
|
||||
TimedInstruction{
|
||||
time:relative_body.time+time.into(),
|
||||
instruction:InternalInstruction::CollisionEnd(
|
||||
Collision::Contact(ContactCollision{face_id:*face_id,convex_mesh_id:*convex_mesh_id}),
|
||||
Collision::Contact(*contact),
|
||||
time
|
||||
),
|
||||
}
|
||||
}));
|
||||
}
|
||||
for convex_mesh_id in &self.intersects{
|
||||
for intersect in &self.intersects{
|
||||
//detect model collision in reverse
|
||||
let model_mesh=models.intersect_mesh(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,start_time..=collector.time()).map(|(_face,time)|{
|
||||
collector.collect(minkowski.predict_collision_out(&relative_body,start_time..collector.time()).map(|(_face,time)|{
|
||||
TimedInstruction{
|
||||
time:relative_body.time+time.into(),
|
||||
instruction:InternalInstruction::CollisionEnd(
|
||||
Collision::Intersect(IntersectCollision{convex_mesh_id:*convex_mesh_id}),
|
||||
Collision::Intersect(*intersect),
|
||||
time
|
||||
),
|
||||
}
|
||||
@@ -973,7 +947,7 @@ impl PhysicsState{
|
||||
// shared geometry for simulations
|
||||
pub struct PhysicsData{
|
||||
//permanent map data
|
||||
bvh:bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
|
||||
bvh:bvh::BvhNode<ConvexMeshId>,
|
||||
//transient map/environment data (open world loads/unloads parts of this data)
|
||||
models:PhysicsModels,
|
||||
//semi-transient data
|
||||
@@ -1059,16 +1033,14 @@ impl PhysicsData{
|
||||
let transform=PhysicsMeshTransform::new(model.transform);
|
||||
match attr_id{
|
||||
PhysicsAttributesId::Contact(attr_id)=>{
|
||||
let contact_model_id=ContactModelId::new(model_id as u32);
|
||||
contact_models.insert(contact_model_id,ContactModel{
|
||||
contact_models.insert(ContactModelId::new(model_id as u32),ContactModel{
|
||||
mesh_id,
|
||||
attr_id,
|
||||
transform,
|
||||
});
|
||||
},
|
||||
PhysicsAttributesId::Intersect(attr_id)=>{
|
||||
let intersect_model_id=IntersectModelId::new(model_id as u32);
|
||||
intersect_models.insert(intersect_model_id,IntersectModel{
|
||||
intersect_models.insert(IntersectModelId::new(model_id as u32),IntersectModel{
|
||||
mesh_id,
|
||||
attr_id,
|
||||
transform,
|
||||
@@ -1082,23 +1054,12 @@ impl PhysicsData{
|
||||
(PhysicsMeshId::new(mesh_id as u32),mesh)
|
||||
).collect();
|
||||
let convex_mesh_aabb_list=
|
||||
// use the map models iteration order to ensure that the
|
||||
// order that the models are passed into bvh::generate_bvh is consistent
|
||||
map.models.iter().enumerate().filter_map(|(model_id,model)|{
|
||||
match map.attributes.get(model.attributes.get() as usize){
|
||||
None|Some(gameplay_attributes::CollisionAttributes::Decoration)=>None,
|
||||
Some(gameplay_attributes::CollisionAttributes::Contact(_))=>{
|
||||
let model_id=ContactModelId::new(model_id as u32);
|
||||
let model=contact_models.get(&model_id)?;
|
||||
Some((PhysicsModelId::Contact(model_id),&model.mesh_id,&model.transform))
|
||||
},
|
||||
Some(gameplay_attributes::CollisionAttributes::Intersect(_))=>{
|
||||
let model_id=IntersectModelId::new(model_id as u32);
|
||||
let model=intersect_models.get(&model_id)?;
|
||||
Some((PhysicsModelId::Intersect(model_id),&model.mesh_id,&model.transform))
|
||||
},
|
||||
}
|
||||
})
|
||||
//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)|{
|
||||
@@ -1189,7 +1150,7 @@ impl<'a> PhysicsContext<'a>{
|
||||
//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);
|
||||
@@ -1200,19 +1161,21 @@ impl<'a> PhysicsContext<'a>{
|
||||
//relative to moving platforms
|
||||
//let relative_body=state.body.relative_to(&Body::ZERO);
|
||||
let relative_body=&state.body;
|
||||
data.bvh.sample_aabb(&aabb,&mut |convex_mesh_id|{
|
||||
if state.touching.contains(convex_mesh_id){
|
||||
return;
|
||||
}
|
||||
data.bvh.sample_aabb(&aabb,&mut |&convex_mesh_id|{
|
||||
//no checks are needed because of the time limits.
|
||||
let model_mesh=data.models.mesh(*convex_mesh_id);
|
||||
let model_mesh=data.models.mesh(convex_mesh_id);
|
||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh());
|
||||
collector.collect(minkowski.predict_collision_in(relative_body,state.time..=collector.time())
|
||||
.map(|(face,dt)|
|
||||
collector.collect(minkowski.predict_collision_in(relative_body,state.time..collector.time())
|
||||
//temp (?) code to avoid collision loops
|
||||
.and_then(|(face,dt)|{
|
||||
// this must be rounded to avoid the infinite loop when hitting the start zone
|
||||
let time=relative_body.time+dt.into();
|
||||
(state.time<time).then_some((time,face,dt))
|
||||
}).map(|(time,face,dt)|
|
||||
TimedInstruction{
|
||||
time:relative_body.time+dt.into(),
|
||||
time,
|
||||
instruction:InternalInstruction::CollisionStart(
|
||||
Collision::new(*convex_mesh_id,face),
|
||||
Collision::new(convex_mesh_id,face),
|
||||
dt
|
||||
)
|
||||
}
|
||||
@@ -1223,17 +1186,12 @@ impl<'a> PhysicsContext<'a>{
|
||||
}
|
||||
|
||||
|
||||
fn contact_normal(
|
||||
models:&PhysicsModels,
|
||||
hitbox_mesh:&HitboxMesh,
|
||||
convex_mesh_id:&ConvexMeshId<ContactModelId>,
|
||||
face_id:model_physics::MinkowskiFace,
|
||||
)->Planar64Vec3{
|
||||
let model_mesh=models.contact_mesh(convex_mesh_id);
|
||||
fn contact_normal(models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{
|
||||
let model_mesh=models.contact_mesh(contact);
|
||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
|
||||
// TODO: normalize to i64::MAX>>1
|
||||
// wrap for speed
|
||||
minkowski.face_nd(face_id).0.wrap_1()
|
||||
minkowski.face_nd(contact.face_id).0.wrap_1()
|
||||
}
|
||||
|
||||
fn recalculate_touching(
|
||||
@@ -1245,7 +1203,7 @@ fn recalculate_touching(
|
||||
mode:Option<&gameplay_modes::Mode>,
|
||||
models:&PhysicsModels,
|
||||
hitbox_mesh:&HitboxMesh,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId>,
|
||||
style:&StyleModifiers,
|
||||
camera:&PhysicsCamera,
|
||||
input_state:&InputState,
|
||||
@@ -1254,11 +1212,11 @@ fn recalculate_touching(
|
||||
//collision_end all existing contacts
|
||||
//I would have preferred while let Some(contact)=contacts.pop()
|
||||
//but there is no such method
|
||||
while let Some((&convex_mesh_id,_face_id))=touching.contacts.iter().next(){
|
||||
collision_end_contact(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,models.contact_attr(convex_mesh_id.model_id),&convex_mesh_id)
|
||||
while let Some(&contact)=touching.contacts.iter().next(){
|
||||
collision_end_contact(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,models.contact_attr(contact.model_id),contact)
|
||||
}
|
||||
while let Some(&convex_mesh_id)=touching.intersects.iter().next(){
|
||||
collision_end_intersect(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,mode,run,models.intersect_attr(convex_mesh_id.model_id),&convex_mesh_id,time);
|
||||
while let Some(&intersect)=touching.intersects.iter().next(){
|
||||
collision_end_intersect(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,mode,run,models.intersect_attr(intersect.model_id),intersect,time);
|
||||
}
|
||||
//find all models in the teleport region
|
||||
let mut aabb=aabb::Aabb::default();
|
||||
@@ -1280,7 +1238,8 @@ fn recalculate_touching(
|
||||
collision_start_intersect(move_state,body,mode_state,touching,mode,run,models,hitbox_mesh,bvh,style,camera,input_state,
|
||||
models.intersect_attr(model_id),
|
||||
IntersectCollision{
|
||||
convex_mesh_id:convex_mesh_id.map(model_id),
|
||||
model_id,
|
||||
submesh_id:convex_mesh_id.submesh_id,
|
||||
},
|
||||
time,
|
||||
),
|
||||
@@ -1298,7 +1257,7 @@ fn set_position(
|
||||
mode:Option<&gameplay_modes::Mode>,
|
||||
models:&PhysicsModels,
|
||||
hitbox_mesh:&HitboxMesh,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId>,
|
||||
style:&StyleModifiers,
|
||||
camera:&PhysicsCamera,
|
||||
input_state:&InputState,
|
||||
@@ -1314,8 +1273,8 @@ fn set_position(
|
||||
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);
|
||||
touching.contacts.retain(|contact|{
|
||||
let n=contact_normal(models,hitbox_mesh,contact);
|
||||
let r=n.dot(v).is_positive();
|
||||
if r{
|
||||
culled=true;
|
||||
@@ -1332,8 +1291,8 @@ fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hit
|
||||
fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a: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);
|
||||
touching.contacts.retain(|contact|{
|
||||
let n=contact_normal(models,hitbox_mesh,contact);
|
||||
let r=n.dot(a).is_positive();
|
||||
if r{
|
||||
culled=true;
|
||||
@@ -1357,7 +1316,7 @@ fn teleport(
|
||||
mode:Option<&gameplay_modes::Mode>,
|
||||
models:&PhysicsModels,
|
||||
hitbox_mesh:&HitboxMesh,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId>,
|
||||
style:&StyleModifiers,
|
||||
camera:&PhysicsCamera,
|
||||
input_state:&InputState,
|
||||
@@ -1379,7 +1338,7 @@ fn teleport_to_spawn(
|
||||
mode:&gameplay_modes::Mode,
|
||||
models:&PhysicsModels,
|
||||
hitbox_mesh:&HitboxMesh,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId>,
|
||||
style:&StyleModifiers,
|
||||
camera:&PhysicsCamera,
|
||||
input_state:&InputState,
|
||||
@@ -1468,7 +1427,7 @@ fn run_teleport_behaviour(
|
||||
mode_state:&mut ModeState,
|
||||
models:&PhysicsModels,
|
||||
hitbox_mesh:&HitboxMesh,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId>,
|
||||
style:&StyleModifiers,
|
||||
camera:&PhysicsCamera,
|
||||
input_state:&InputState,
|
||||
@@ -1515,7 +1474,7 @@ fn run_teleport_behaviour(
|
||||
}
|
||||
}
|
||||
|
||||
fn is_not_spawn_at(
|
||||
fn not_spawn_at(
|
||||
mode:Option<&gameplay_modes::Mode>,
|
||||
model_id:ModelId,
|
||||
)->bool{
|
||||
@@ -1536,7 +1495,7 @@ fn collision_start_contact(
|
||||
mode:Option<&gameplay_modes::Mode>,
|
||||
models:&PhysicsModels,
|
||||
hitbox_mesh:&HitboxMesh,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId>,
|
||||
style:&StyleModifiers,
|
||||
camera:&PhysicsCamera,
|
||||
input_state:&InputState,
|
||||
@@ -1546,12 +1505,12 @@ fn collision_start_contact(
|
||||
){
|
||||
let incident_velocity=body.velocity;
|
||||
//add to touching
|
||||
touching.insert_contact(contact);
|
||||
touching.insert(Collision::Contact(contact));
|
||||
//clip v
|
||||
set_velocity(body,touching,models,hitbox_mesh,incident_velocity);
|
||||
let mut allow_jump=true;
|
||||
let model_id=contact.convex_mesh_id.model_id.into();
|
||||
let mut allow_run_teleport_behaviour=is_not_spawn_at(mode,model_id);
|
||||
let model_id=contact.model_id.into();
|
||||
let mut allow_run_teleport_behaviour=not_spawn_at(mode,model_id);
|
||||
match &attr.contacting.contact_behaviour{
|
||||
Some(gameplay_attributes::ContactingBehaviour::Surf)=>(),
|
||||
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
|
||||
@@ -1574,7 +1533,7 @@ fn collision_start_contact(
|
||||
},
|
||||
Some(gameplay_attributes::ContactingBehaviour::NoJump)=>allow_jump=false,
|
||||
None=>if let Some(walk_settings)=&style.walk{
|
||||
if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact.convex_mesh_id,contact.face_id),vec3::Y){
|
||||
if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact),vec3::Y){
|
||||
allow_run_teleport_behaviour=true;
|
||||
//ground
|
||||
let (gravity,target_velocity)=ground_things(walk_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
@@ -1639,7 +1598,7 @@ fn collision_start_intersect(
|
||||
run:&mut run::Run,
|
||||
models:&PhysicsModels,
|
||||
hitbox_mesh:&HitboxMesh,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
|
||||
bvh:&bvh::BvhNode<ConvexMeshId>,
|
||||
style:&StyleModifiers,
|
||||
camera:&PhysicsCamera,
|
||||
input_state:&InputState,
|
||||
@@ -1648,13 +1607,13 @@ fn collision_start_intersect(
|
||||
time:Time,
|
||||
){
|
||||
//I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop
|
||||
touching.insert_intersect(intersect);
|
||||
touching.insert(Collision::Intersect(intersect));
|
||||
//insta booster!
|
||||
if let Some(booster)=&attr.general.booster{
|
||||
move_state.cull_velocity(booster.boost(body.velocity),body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
}
|
||||
if let Some(mode)=mode{
|
||||
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!");
|
||||
@@ -1671,7 +1630,7 @@ fn collision_start_intersect(
|
||||
}
|
||||
}
|
||||
move_state.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
run_teleport_behaviour(intersect.convex_mesh_id.model_id.into(),attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
||||
run_teleport_behaviour(intersect.model_id.into(),attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
||||
}
|
||||
|
||||
fn collision_end_contact(
|
||||
@@ -1684,17 +1643,15 @@ fn collision_end_contact(
|
||||
camera:&PhysicsCamera,
|
||||
input_state:&InputState,
|
||||
_attr:&gameplay_attributes::ContactAttributes,
|
||||
convex_mesh_id:&ConvexMeshId<ContactModelId>,
|
||||
contact:ContactCollision,
|
||||
){
|
||||
touching.remove_contact(convex_mesh_id);//remove contact before calling contact_constrain_acceleration
|
||||
touching.remove(&Collision::Contact(contact));//remove contact before calling contact_constrain_acceleration
|
||||
//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{
|
||||
Some(walk_state)=>if walk_state.contact==contact{
|
||||
move_state.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
}else{
|
||||
// stopped touching something else while walking
|
||||
@@ -1716,13 +1673,13 @@ fn collision_end_intersect(
|
||||
mode:Option<&gameplay_modes::Mode>,
|
||||
run:&mut run::Run,
|
||||
_attr:&gameplay_attributes::IntersectAttributes,
|
||||
convex_mesh_id:&ConvexMeshId<IntersectModelId>,
|
||||
intersect:IntersectCollision,
|
||||
time:Time,
|
||||
){
|
||||
touching.remove_intersect(convex_mesh_id);
|
||||
touching.remove(&Collision::Intersect(intersect));
|
||||
move_state.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||
if let Some(mode)=mode{
|
||||
let zone=mode.get_zone(convex_mesh_id.model_id.into());
|
||||
let zone=mode.get_zone(intersect.model_id.into());
|
||||
match zone{
|
||||
Some(gameplay_modes::Zone::Start)=>{
|
||||
match run.start(time){
|
||||
@@ -1736,14 +1693,17 @@ fn collision_end_intersect(
|
||||
}
|
||||
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<InternalInstruction,Time>){
|
||||
state.time=ins.time;
|
||||
match ins.instruction{
|
||||
// collisions advance the body precisely
|
||||
let (should_advance_body,goober_time)=match ins.instruction{
|
||||
InternalInstruction::CollisionStart(_,dt)
|
||||
|InternalInstruction::CollisionEnd(_,dt)=>state.body.advance_time_ratio_dt(dt),
|
||||
// this advances imprecisely
|
||||
InternalInstruction::ReachWalkTargetVelocity=>state.body.advance_time(state.time),
|
||||
// strafe tick decides for itself whether to advance the body.
|
||||
InternalInstruction::StrafeTick=>(),
|
||||
|InternalInstruction::CollisionEnd(_,dt)=>(true,Some(dt)),
|
||||
InternalInstruction::StrafeTick
|
||||
|InternalInstruction::ReachWalkTargetVelocity=>(true,None),
|
||||
};
|
||||
if should_advance_body{
|
||||
match goober_time{
|
||||
Some(dt)=>state.body.advance_time_ratio_dt(dt),
|
||||
None=>state.body.advance_time(state.time),
|
||||
}
|
||||
}
|
||||
match ins.instruction{
|
||||
InternalInstruction::CollisionStart(collision,_)=>{
|
||||
@@ -1753,7 +1713,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
||||
&mut state.move_state,&mut state.body,&mut state.mode_state,&mut state.touching,&mut state.run,
|
||||
mode,
|
||||
&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,
|
||||
data.models.contact_attr(contact.convex_mesh_id.model_id),
|
||||
data.models.contact_attr(contact.model_id),
|
||||
contact,
|
||||
state.time,
|
||||
),
|
||||
@@ -1761,7 +1721,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
||||
&mut state.move_state,&mut state.body,&mut state.mode_state,&mut state.touching,
|
||||
mode,
|
||||
&mut state.run,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,
|
||||
data.models.intersect_attr(intersect.convex_mesh_id.model_id),
|
||||
data.models.intersect_attr(intersect.model_id),
|
||||
intersect,
|
||||
state.time,
|
||||
),
|
||||
@@ -1770,15 +1730,15 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
||||
InternalInstruction::CollisionEnd(collision,_)=>match collision{
|
||||
Collision::Contact(contact)=>collision_end_contact(
|
||||
&mut state.move_state,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state,
|
||||
data.models.contact_attr(contact.convex_mesh_id.model_id),
|
||||
&contact.convex_mesh_id
|
||||
data.models.contact_attr(contact.model_id),
|
||||
contact
|
||||
),
|
||||
Collision::Intersect(intersect)=>collision_end_intersect(
|
||||
&mut state.move_state,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state,
|
||||
data.modes.get_mode(state.mode_state.get_mode_id()),
|
||||
&mut state.run,
|
||||
data.models.intersect_attr(intersect.convex_mesh_id.model_id),
|
||||
&intersect.convex_mesh_id,
|
||||
data.models.intersect_attr(intersect.model_id),
|
||||
intersect,
|
||||
state.time
|
||||
),
|
||||
},
|
||||
@@ -1790,8 +1750,6 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
||||
let masked_controls=strafe_settings.mask(controls);
|
||||
let control_dir=state.style.get_control_dir(masked_controls);
|
||||
if control_dir!=vec3::ZERO{
|
||||
// manually advance time
|
||||
state.body.advance_time(state.time);
|
||||
let camera_mat=state.camera.simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x);
|
||||
if let Some(ticked_velocity)=strafe_settings.tick_velocity(state.body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().wrap_1()){
|
||||
//this is wrong but will work ig
|
||||
@@ -1878,7 +1836,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=data.models.contact_attr(walk_state.contact.convex_mesh_id.model_id).general.booster.as_ref();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -106,9 +106,7 @@ fn bug_3(){
|
||||
.filter(|ins|!matches!(ins.instruction,InternalInstruction::StrafeTick));
|
||||
// touch side of part at 0,0,0
|
||||
assert_eq!(phys_iter.next().unwrap().time,Time::from_secs(1));
|
||||
// EXPECTED: touch top of part at 5,-5,0
|
||||
// OBSERVED: CollisionEnd part at 0,0,0; clip through part at 5,-5,0
|
||||
assert_eq!(phys_iter.next().unwrap().time,Time::from_secs(2));
|
||||
// touch top of part at 5,-5,0
|
||||
assert_eq!(phys_iter.next().unwrap().time,Time::from_secs(2));
|
||||
assert!(phys_iter.next().is_none());
|
||||
let body=physics.body();
|
||||
|
||||
@@ -101,6 +101,28 @@ impl_from!(
|
||||
i8,i16,i32,i64,i128,isize
|
||||
);
|
||||
|
||||
impl<const N:usize,const F:usize,T> PartialEq<T> for Fixed<N,F>
|
||||
where
|
||||
T:Copy,
|
||||
BInt::<N>:From<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self,&other:&T)->bool{
|
||||
self.bits.eq(&other.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
|
||||
where
|
||||
T:Copy,
|
||||
BInt::<N>:From<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn partial_cmp(&self,&other:&T)->Option<std::cmp::Ordering>{
|
||||
self.bits.partial_cmp(&other.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N:usize,const F:usize> std::ops::Neg for Fixed<N,F>{
|
||||
type Output=Self;
|
||||
#[inline]
|
||||
@@ -306,6 +328,16 @@ macro_rules! impl_additive_operator {
|
||||
self.$method(other)
|
||||
}
|
||||
}
|
||||
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||
where
|
||||
BInt::<N>:From<U>,
|
||||
{
|
||||
type Output = $output;
|
||||
#[inline]
|
||||
fn $method(self, other: U) -> Self::Output {
|
||||
Self::from_bits(self.bits.$method(BInt::<N>::from(other).shl(F as u32)))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! impl_additive_assign_operator {
|
||||
@@ -316,6 +348,15 @@ macro_rules! impl_additive_assign_operator {
|
||||
self.bits.$method(other.bits);
|
||||
}
|
||||
}
|
||||
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||
where
|
||||
BInt::<N>:From<U>,
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self, other: U) {
|
||||
self.bits.$method(BInt::<N>::from(other).shl(F as u32));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user