|
|
|
|
@@ -645,6 +645,17 @@ pub struct MinkowskiMesh<'a>{
|
|
|
|
|
pub 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();
|
|
|
|
|
@@ -666,8 +677,128 @@ impl MinkowskiMesh<'_>{
|
|
|
|
|
mesh1,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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{
|
|
|
|
|
let mut best_distance_squared={
|
|
|
|
|
let diff=point-self.vert(vert_id);
|
|
|
|
|
diff.dot(diff)
|
|
|
|
|
};
|
|
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/// 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
infinity_fev.crawl(self,&infinity_body,Bound::Unbounded,start_time).miss()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
pub fn predict_collision_in(&self,relative_body:&Body,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{
|
|
|
|
|
let old_fev=self.closest_fev_not_inside(*relative_body,range.start_bound());
|
|
|
|
|
let fev=crate::minimum_difference::closest_fev_not_inside(self,relative_body.position)?;
|
|
|
|
|
println!("old_fev={old_fev:?}");
|
|
|
|
|
println!("fev={fev:?}");
|
|
|
|
|
//continue forwards along the body parabola
|
|
|
|
|
fev.crawl(self,relative_body,range.start_bound(),range.end_bound()).hit()
|
|
|
|
|
}
|
|
|
|
|
|