Skip to main content

linear_sim/collision/proximity/
gjk.rs

1use sorted_vec::partial::ReverseSortedVec;
2
3use crate::object;
4use crate::math::*;
5
6use super::Proximity;
7
8#[derive(Clone, Copy, Debug, PartialEq)]
9struct SimplexMinkowski {
10  pub points  : [PointMinkowski; 4],
11  pub npoints : u8
12}
13
14/// Represents a point in the Minkowski difference A - B of shapes A and B
15#[derive(Clone, Copy, Debug, Default, PartialEq)]
16struct PointMinkowski {
17  pub point_a : Point3 <f64>,
18  pub point_b : Point3 <f64>
19  // TODO: benchmark if it's more performant to pre-compute point
20  //pub point  : Point3 <f64>
21}
22
23impl SimplexMinkowski {
24  const fn push (&mut self, point : PointMinkowski) {
25    self.points[self.npoints as usize] = point;
26    self.npoints += 1;
27  }
28}
29
30impl PointMinkowski {
31  /// Point A - point B
32  fn point (self) -> Point3 <f64> {
33    Point3 (self.point_a - self.point_b)
34  }
35
36  fn support <A, B> (direction : NonZero3 <f64>, object_a : &A, object_b : &B)
37    -> (Self, f64)
38  where A : object::Bounded, B : object::Bounded {
39    let (point_a, _) = object_a.to_primitive().support (direction);
40    let (point_b, _) = object_b.to_primitive().support (-direction);
41    let minkowski = PointMinkowski { point_a, point_b };
42    (minkowski, minkowski.point().0.dot (*direction))
43  }
44}
45
46impl Proximity {
47  /// Return the proximity query using GJK algorithm.
48  pub fn query_gjk <A, B> (object_a : &A, object_b : &B) -> Self where
49    A : object::Bounded, B : object::Bounded
50  {
51    // query_gjk function outline:
52    // 1. main loop: gjk search for simplex containing the origin in minkowski hull
53    // 2. if contained (overlapping):
54    //      compute the penetration depth
55    //    else (not overlapping):
56    //      determine nearest features and calculate distance
57    const TOLERANCE         : f64 = 0.000_000_000_01;
58    const TOLERANCE_SQUARED : f64 = TOLERANCE * TOLERANCE;
59    // initial search direction
60    let mut search_dir = NonZero3::unchecked (vector3 (1.0, 1.0, 1.0));
61    let (support, _) = PointMinkowski::support (search_dir, object_a, object_b);
62    let mut near = support.point();
63    let mut distance_squared = near.0.magnitude_squared();
64    let mut lowest_distance_sq = distance_squared;
65    let mut simplex = SimplexMinkowski {
66      points:  [support,
67        PointMinkowski::default(), PointMinkowski::default(), PointMinkowski::default()],
68      npoints: 1
69    };
70    // new search direction towards the origin
71    // TODO: is this safe ?
72    search_dir = -NonZero3::unchecked (support.point().0);
73    let mut lowest_unique_count = 0;
74    let mut last_distance_sq = f64::MAX;
75    let noprogress_limit = 3;
76    let mut noprogress_count = 0;
77    let mut unique_count = 0;
78    loop {
79      let (support, _) = PointMinkowski::support (search_dir, object_a, object_b);
80      // vector from last nearest point to new support point
81      let vnew = support.point() - near;
82      let dot_new = vnew.dot (*search_dir);
83      if dot_new <= TOLERANCE {
84        // no intersection
85        break
86      }
87      // add new support point and check for overlap
88      simplex.push (support);
89      let mut overlap = false;
90      if simplex.npoints == 4 {
91        // check for tetrahedron containment, drop point to create face if no containment
92        let [a, b, c, d] = simplex.points.map (PointMinkowski::point);
93        // check to see if origin is contained
94        let ab = b - a;
95        let ac = c - a;
96        let ad = d - a;
97        let da = a - d;
98        let db = b - d;
99        let dc = c - d;
100        let ab_x_ac = ab.cross (ac);
101        let ab_x_ac_dot_ad = ab_x_ac.dot (ad);
102        let nd = -d.0;
103        let dab;
104        let dac;
105        #[expect(clippy::useless_let_if_seq)]
106        let dbc;
107        if ab_x_ac_dot_ad > 0.0 {
108          // winding ab x ac
109          dab = db.cross (da);
110          dac = da.cross (dc);
111          dbc = dc.cross (db);
112        } else {
113          // winding ac x ab
114          dab = da.cross (db);
115          dac = dc.cross (da);
116          dbc = db.cross (dc);
117        }
118        // containment checks
119        let check1 = dab.dot (nd) > 0.000000001;
120        let check2 = dac.dot (nd) > 0.000000001;
121        let check3 = dbc.dot (nd) > 0.000000001;
122        if check1 && check2 && check3 {
123          overlap = true
124        } else {
125          // otherwise intersect the base perpendicular (near) with the new faces
126          let line = geometry::Segment3::noisy (Point3::<f64>::origin(), near).into();
127          // face 1 = dab
128          if let Some (triangle) = geometry::Triangle3::new (d, a, b)
129            && geometry::intersect::line_triangle (line, triangle).is_some()
130          {
131            // drop c
132            simplex.points[2] = simplex.points[3];
133          } else {
134            // face 2 = dac
135            if let Some (triangle) = geometry::Triangle3::new (d, a, c)
136              && geometry::intersect::line_triangle (line, triangle).is_some()
137            {
138              // drop b
139              simplex.points[1] = simplex.points[3];
140            } else {
141              // face 3 = dbc
142              if let Some (triangle) = geometry::Triangle3::new (d, b, c)
143                && geometry::intersect::line_triangle (line, triangle).is_some()
144              {
145                // drop a
146                simplex.points[0] = simplex.points[3];
147              } else {
148                unreachable!("should have intersected new simplex")
149              }
150            }
151          }
152          simplex.npoints = 3;
153        }
154      }
155      if !overlap {
156        // get new search direction
157        match simplex.npoints {
158          1 => {
159            near = simplex.points[0].point();
160            distance_squared = near.0.magnitude_squared();
161            if distance_squared < TOLERANCE_SQUARED {
162              overlap = true
163            } else {
164              // new search direction assigned to inverse of this point, i.e. towards origin
165              search_dir = NonZero3::new (-near.0).unwrap()
166            }
167          }
168          2 => {
169            let t;
170            (t, near) = geometry::Segment3::unchecked (
171              simplex.points[0].point(), simplex.points[1].point()
172            ).nearest_point (Point3::origin());
173            distance_squared = near.0.magnitude_squared();
174            if distance_squared < TOLERANCE_SQUARED {
175              overlap = true
176            } else if *t == 1.0 {
177              // point 1 becomes the new simplex, new direction point 1 towards origin
178              simplex.points[0] = simplex.points[1];
179              simplex.npoints -= 1;
180              search_dir = NonZero3::unchecked (-simplex.points[0].point().0)
181            } else {
182              // new direction edge normal towards origin which is equal to -near
183              search_dir = NonZero3::unchecked (-near.0)
184            }
185          }
186          3 => {
187            let (s, t);
188            ([s, t], near) = geometry::Triangle3::unchecked (
189              simplex.points[0].point(),
190              simplex.points[1].point(),
191              simplex.points[2].point()
192            ).nearest_point (Point3::origin());
193            distance_squared = near.0.magnitude_squared();
194            if distance_squared < TOLERANCE_SQUARED {
195              overlap = true
196            } else {
197              // new search direction
198              search_dir = NonZero3::unchecked (-near.0);
199              if *s == 0.0 && *t == 0.0 {
200                // point A, search direction -A
201                simplex.npoints = 1;
202              } else if *s == 1.0 && *t == 0.0 {
203                // point B, search direction -B
204                simplex.points[0] = simplex.points[1];
205                simplex.npoints = 1;
206              } else if *s == 0.0 && *t == 1.0 {
207                // point C, search direction -C
208                simplex.points[0] = simplex.points[2];
209                simplex.npoints = 1;
210              } else if (*s + *t - 1.0).abs() < TOLERANCE {
211                // edge BC, search direction BC x BO x BC
212                simplex.points[0] = simplex.points[1];
213                simplex.points[1] = simplex.points[2];
214                simplex.npoints = 2;
215              } else if *s < TOLERANCE {
216                // edge AC, search direction AC x AO x AC
217                simplex.points[1] = simplex.points[2];
218                simplex.npoints = 2;
219              } else if *t < TOLERANCE {
220                // edge AB, search direction AB x AO x AB
221                simplex.npoints = 2;
222              } else {
223                // triangle ABC, direction face normal to origin
224                debug_assert!(*s > 0.0);
225                debug_assert!(*s < 1.0);
226                debug_assert!(*t > 0.0);
227                debug_assert!(*t < 1.0);
228                debug_assert!(*s + *t < 1.0);
229              }
230            }
231          }
232          _ => unreachable!()
233        }
234      }
235      if overlap {
236        // compute penetration
237        /// Represents distance from origin to a face in the polytope
238        #[derive(PartialEq)]
239        struct PolytopeFace {
240          /// Distance squared from origin
241          pub distance_squared : NonNegative <f64>,
242          /// Distance vector from origin
243          pub distance_vector  : Vector3 <f64>,
244          pub nearest_a        : Point3 <f64>,
245          pub nearest_b        : Point3 <f64>,
246          pub points           : [PointMinkowski; 3],
247          pub normal           : NonZero3 <f64>,
248          pub exterior         : bool
249        }
250        impl PartialOrd for PolytopeFace {
251          fn partial_cmp (&self, other : &Self) -> Option <std::cmp::Ordering> {
252            self.distance_squared.partial_cmp (&other.distance_squared)
253          }
254        }
255        let mut node_list = ReverseSortedVec::<PolytopeFace>::new();
256        let add_face = |
257          node_list : &mut ReverseSortedVec <PolytopeFace>,
258          p1 : PointMinkowski, p2 : PointMinkowski, p3 : PointMinkowski
259        | {
260          let triangle = geometry::Triangle3::noisy (p1.point(), p2.point(), p3.point());
261          let ([s, t], nearest) = triangle.nearest_point (Point3::origin());
262          let nearest_a = p1.point_a +
263            (p2.point_a - p1.point_a) * *s + (p3.point_a - p1.point_a) * *t;
264          let nearest_b = p1.point_b +
265            (p2.point_b - p1.point_b) * *s + (p3.point_b - p1.point_b) * *t;
266          let normal = {
267            let mut normal = triangle.normal();
268            if normal.dot (triangle.point_a().0) < 0.0 {
269              normal = -normal
270            }
271            normal
272          };
273          let node = PolytopeFace {
274            distance_squared: nearest.0.norm_squared(),
275            distance_vector:  -nearest.0,
276            points:           [p1, p2, p3],
277            exterior:         false,
278            nearest_a,
279            nearest_b,
280            normal
281          };
282          node_list.insert (node);
283        };
284        let a  = simplex.points[0];
285        let b  = simplex.points[1];
286        let c  = simplex.points[2];
287        let d  = simplex.points[3];
288        match simplex.npoints {
289          3 => {
290            // add support points in direction of face normals
291            let e1 = simplex.points[1].point() - simplex.points[0].point();
292            let e2 = simplex.points[2].point() - simplex.points[0].point();
293            let n1 = NonZero3::noisy (e1.cross (e2));
294            let n2 = -n1;
295            // p1
296            let (p1, dot) = PointMinkowski::support (n1, object_a, object_b);
297            if dot <= TOLERANCE {
298              let half_axis = *n1 * TOLERANCE * 0.5;
299              return Proximity {
300                distance:  -TOLERANCE.sqrt(),
301                normal:    n1.normalize(),
302                midpoint:  p1.point_a + half_axis,
303                half_axis
304              }
305            }
306            add_face (&mut node_list, a, b, p1);
307            add_face (&mut node_list, a, c, p1);
308            add_face (&mut node_list, b, c, p1);
309            // p2
310            let (p2, dot) = PointMinkowski::support (n2, object_a, object_b);
311            if dot <= TOLERANCE {
312              let half_axis = *n2 * TOLERANCE * 0.5;
313              return Proximity {
314                distance:  -TOLERANCE.sqrt(),
315                normal:    n2.normalize(),
316                midpoint:  p2.point_a + half_axis,
317                half_axis
318              }
319            }
320            add_face (&mut node_list, a, b, p2);
321            add_face (&mut node_list, a, c, p2);
322            add_face (&mut node_list, b, c, p2);
323          }
324          4 => {
325            add_face (&mut node_list, a, b, c);
326            add_face (&mut node_list, a, b, d);
327            add_face (&mut node_list, a, c, d);
328            add_face (&mut node_list, b, c, d);
329          }
330          _ => unreachable!()
331        }
332        // expanding polytope search loop
333        // TODO: for improved efficiency it may be desirable to fix winding for all
334        // faces in a CW or CCW direction so that shared edges are always in opposite
335        // directions and therefore only one edge direction needs to be checked when
336        // adding to the edge array when creating a hole for the new support point
337        let mut edges = vec![];
338        let mut edge_in_list = vec![];
339        let mut last_iter_distance = f64::MIN;
340        let exterior_node_proximity = |node : PolytopeFace| {
341          let half_axis = node.distance_vector * 0.5;
342          Proximity {
343            half_axis,
344            distance:  -*node.distance_squared.sqrt(),
345            normal:    half_axis.normalize(), // TODO: verify correct ?
346            midpoint:  node.nearest_a + half_axis
347          }
348        };
349        let mut iter = 0;
350        let noprogress_limit = 3;
351        let mut noprogress_count = 0;
352        loop {
353          let mut node = node_list.pop().unwrap();
354          if approx::relative_eq!(*node.distance_squared, last_iter_distance,
355            epsilon = 0.000000001
356          ) {
357            // no progress
358            noprogress_count += 1;
359            if noprogress_count == noprogress_limit {
360              log::trace!("expanding polytope search no progress after {iter} iterations");
361              return exterior_node_proximity (node)
362            }
363          } else {
364            noprogress_count = 0;
365          }
366          last_iter_distance = *node.distance_squared;
367          // calculate support point for face normal
368          let (pa, _) = PointMinkowski::support (node.normal, object_a, object_b);
369          if node.points.contains (&pa) {
370            // support point already in face
371            node.exterior = true;
372          } else {
373            // exterior face check
374            let bpa = pa.point() - node.points[0].point();
375            // TODO: is it necessary to normalize here?
376            let normal_unit = node.normal.normalize();
377            if normal_unit.dot (bpa) < TOLERANCE {
378              node.exterior = true;
379            }
380          }
381          if node.exterior {
382            // exit if face is exterior
383            return exterior_node_proximity (node)
384          } else {
385            // face is not exterior:
386            // insert node edges into edge list
387            edges.clear();
388            edge_in_list.clear();
389            edges.extend ([
390              [node.points[0], node.points[1]],
391              [node.points[0], node.points[2]],
392              [node.points[1], node.points[2]]
393            ]);
394            edge_in_list.extend ([true, true, true]);
395            // remove all faces visible to new support point and fill holes with new
396            // faces
397            node_list.retain (|node|{
398              let p0_pa = pa.point() - node.points[0].point();
399              if TOLERANCE < node.normal.dot (p0_pa) {
400                // pa visible: add edges of this face to edge array
401                let mut add_edge = |i : usize, j : usize| {
402                  let mut found = false;
403                  // filter: skip excluded edges
404                  for (k, edge) in edges.iter().enumerate()
405                    .filter (|(k, _)| edge_in_list[*k])
406                  {
407                    if node.points[i].point() == edge[0].point()
408                      && node.points[j].point() == edge[1].point()
409                      || node.points[i].point() == edge[1].point()
410                      && node.points[j].point() == edge[0].point()
411                    {
412                      found = true;
413                      edge_in_list[k] = false;  // exclude shared edge
414                      break
415                    }
416                  }
417                  if !found {
418                    // insert
419                    edge_in_list.push (true);
420                    edges.push ([node.points[i], node.points[j]]);
421                  }
422                };
423                add_edge (0, 1); // edge0: 0->1
424                add_edge (0, 2); // edge1: 0->2
425                add_edge (1, 2); // edge2: 1->2
426                false // remove
427              } else {
428                true  // retain
429              }
430            });
431            // add new faces connected to support point and "hole" edges in edge list
432            // filter: skip excluded edges
433            for (_, edge) in edges.iter().enumerate()
434              .filter (|(i, _)| edge_in_list[*i])
435            {
436              if geometry::Triangle3::new (pa.point(), edge[0].point(), edge[1].point())
437                .is_none()
438              {
439                // skip faces where points are colinear
440                continue
441              }
442              add_face (&mut node_list, pa, edge[0], edge[1]);
443            }
444          }
445          iter += 1;
446        }
447      } // end penetration
448      // count unique points
449      if simplex.points[0].point_a == simplex.points[1].point_a
450        && simplex.points[1].point_a == simplex.points[2].point_a
451      {
452        unique_count += 1;
453      } else if simplex.points[0].point_a == simplex.points[1].point_a
454        || simplex.points[0].point_a == simplex.points[2].point_a
455        || simplex.points[1].point_a == simplex.points[2].point_a
456      {
457        unique_count += 2;
458      } else {
459        debug_assert!(
460          simplex.points[0].point_a != simplex.points[1].point_a &&
461          simplex.points[0].point_a != simplex.points[2].point_a &&
462          simplex.points[1].point_a != simplex.points[2].point_a);
463        unique_count += 3;
464      }
465      if simplex.points[0].point_b == simplex.points[1].point_b
466        && simplex.points[1].point_b == simplex.points[2].point_b
467      {
468        unique_count += 1;
469      } else if simplex.points[0].point_b == simplex.points[1].point_b
470        || simplex.points[0].point_b == simplex.points[2].point_b
471        || simplex.points[1].point_b == simplex.points[2].point_b
472      {
473        unique_count += 2;
474      } else {
475        debug_assert!(
476          simplex.points[0].point_b != simplex.points[1].point_b &&
477          simplex.points[0].point_b != simplex.points[2].point_b &&
478          simplex.points[1].point_b != simplex.points[2].point_b);
479        unique_count += 3;
480      }
481      // new lowest
482      if distance_squared < lowest_distance_sq ||
483        distance_squared == lowest_distance_sq && unique_count < lowest_unique_count
484      {
485        lowest_distance_sq = distance_squared;
486        lowest_unique_count = unique_count;
487      } else if distance_squared >= last_distance_sq {
488        if noprogress_count < noprogress_limit {
489          noprogress_count += 1;
490        } else {
491          // no progress limit reached: break
492          break
493        }
494      }
495      last_distance_sq = distance_squared;
496    }
497    // distance squared to nearest feature
498    // check duplicate vertices
499    let mut dupe_a_01 = false;
500    let mut dupe_a_02 = false;
501    let mut dupe_a_12 = false;
502    let mut dupe_b_01 = false;
503    let mut dupe_b_02 = false;
504    let mut dupe_b_12 = false;
505    if simplex.npoints >= 2 {
506      dupe_a_01 = simplex.points[0].point_a == simplex.points[1].point_a;
507      dupe_b_01 = simplex.points[0].point_b == simplex.points[1].point_b;
508    }
509    if simplex.npoints >= 3 {
510      dupe_a_02 = simplex.points[0].point_a == simplex.points[2].point_a;
511      dupe_a_12 = simplex.points[1].point_a == simplex.points[2].point_a;
512      dupe_b_02 = simplex.points[0].point_b == simplex.points[2].point_b;
513      dupe_b_12 = simplex.points[1].point_b == simplex.points[2].point_b;
514    }
515    // fix some degenerate cases
516    // simplex.points[0].point() == simplex.points[1].point()
517    debug_assert!(!(simplex.npoints == 2 && unique_count == 2),
518      "only one unique point: not possible");
519    // calculate final distance and nearest points
520    match simplex.npoints {
521      1 => {
522        // point simplex
523        let nearest_a = simplex.points[0].point_a;
524        let nearest_b = simplex.points[0].point_b;
525        let a_to_b = nearest_b - nearest_a;
526        let half_axis = a_to_b * 0.5;
527        let midpoint = nearest_a + half_axis;
528        let normal = if !a_to_b.is_approx_zero() {
529          Unit3::normalize (-a_to_b)
530        } else {
531          // points are touching: use object centers
532          Unit3::normalize (object_a.position().0 - object_b.position().0)
533        };
534        let distance = a_to_b.magnitude();
535        Proximity { distance, half_axis, midpoint, normal }
536      }
537      2 => {
538        // line simplex
539        let point0_a = simplex.points[0].point_a;
540        let point1_a = simplex.points[1].point_a;
541        let point0_b = simplex.points[0].point_b;
542        let point1_b = simplex.points[1].point_b;
543        if !dupe_a_01 && !dupe_b_01 {
544          // edge vs edge
545          let edge_a = geometry::Segment3::noisy (point0_a, point1_a);
546          let edge_b = geometry::Segment3::noisy (point0_b, point1_b);
547          Proximity::query_segment_segment (&edge_a, &edge_b)
548        } else {
549          // point vs edge
550          if dupe_a_01 {
551            // point in A vs edge in B
552            debug_assert!(!dupe_b_01);
553            debug_assert_eq!(point0_a, point1_a);
554            let point_a = point0_a;
555            let edge_b  = geometry::Segment3::noisy (point0_b, point1_b);
556            // flip direction so proximity query points from A -> B
557            Proximity::query_segment_point (&edge_b, &point_a).flip()
558          } else {
559            // point in B vs edge in A
560            debug_assert!(dupe_b_01);
561            let point_b = point0_b;
562            let edge_a  = geometry::Segment3::noisy (point0_a, point1_a);
563            Proximity::query_segment_point (&edge_a, &point_b)
564          }
565        }
566      }
567      3 => {
568        // triangle simplex
569        let is_edge = |
570          p0 : &mut Point3 <f64>, p1 : &mut Point3 <f64>, p2 : &Point3 <f64>
571        | {
572          let p01 = *p1 - *p0;
573          let p02 = *p2 - *p0;
574          let p01_cross_p02 = p01.cross (p02);
575          if p01_cross_p02.is_approx_zero() {
576            // points are colinear
577            let p12 = *p2 - *p1;
578            let dot_p01 = p01.magnitude_squared();
579            let dot_p02 = p02.magnitude_squared();
580            let dot_p12 = p12.magnitude_squared();
581            // swap so that longest edge is always p0->p1
582            if dot_p01 > dot_p02 && dot_p01 > dot_p12 {
583              // do nothing: already p0->p1
584            } else if dot_p02 > dot_p01 && dot_p02 > dot_p12 {
585              *p1 = *p2;
586            } else {
587              debug_assert!(dot_p12 > dot_p01 && dot_p12 > dot_p02);
588              *p0 = *p1;
589              *p1 = *p2;
590            }
591            true
592          } else {
593            false
594          }
595        };
596        // point vs point not possible
597        debug_assert!(!((dupe_a_01 && dupe_a_12) && (dupe_b_01 && dupe_b_12)));
598        if (!dupe_a_01 && !dupe_a_02 && !dupe_a_12) &&
599           (!dupe_b_01 && !dupe_b_02 && !dupe_b_12)
600        {
601          // face vs face
602          let mut a0 = simplex.points[0].point_a;
603          let mut a1 = simplex.points[1].point_a;
604          let a2 = simplex.points[2].point_a;
605          let mut b0 = simplex.points[0].point_b;
606          let mut b1 = simplex.points[1].point_b;
607          let b2 = simplex.points[2].point_b;
608          let edge_a = is_edge (&mut a0, &mut a1, &a2);
609          let edge_b = is_edge (&mut b0, &mut b1, &b2);
610          if edge_a && edge_b {
611            // edge vs edge
612            let edge_a = geometry::Segment3::noisy (a0, a1);
613            let edge_b = geometry::Segment3::noisy (b0, b1);
614            Proximity::query_segment_segment (&edge_a, &edge_b)
615          } else if edge_a && !edge_b {
616            // edge vs face
617            let edge_a = geometry::Segment3::noisy (a0, a1);
618            let triangle_b = geometry::Triangle3::noisy (b0, b1, b2);
619            // flip direction so proximity query points from A -> B
620            Proximity::query_triangle_segment (&triangle_b, &edge_a).flip()
621          } else if !edge_a && edge_b {
622            // face vs edge
623            let edge_b = geometry::Segment3::noisy (b0, b1);
624            let triangle_a = geometry::Triangle3::noisy (a0, a1, a2);
625            Proximity::query_triangle_segment (&triangle_a, &edge_b)
626          } else {
627            debug_assert!(!edge_a);
628            debug_assert!(!edge_b);
629            // face vs face
630            let triangle_a = geometry::Triangle3::noisy (a0, a1, a2);
631            let triangle_b = geometry::Triangle3::noisy (b0, b1, b2);
632            Proximity::query_triangle_triangle (&triangle_a, &triangle_b)
633          }
634        } else if (dupe_a_01 != dupe_a_02 || dupe_a_01 != dupe_a_12)
635          && (!dupe_b_01 && !dupe_b_02 && !dupe_b_12)
636        {
637          // edge vs face
638          // need to look at distance_sq between edge and face in objects: there is
639          // probably not a general way to get the contact points from the simplex as
640          // the 3-simplex doesn't fully determine the intersection of an edge+face this
641          // code makes use of a segment/triangle distance_sq function from eberly
642          // geometrictools which is probably slower than the other (simpler) cases
643          let a0 = simplex.points[0].point_a;
644          let mut a1 = simplex.points[1].point_a;
645          let mut b0 = simplex.points[0].point_b;
646          let mut b1 = simplex.points[1].point_b;
647          let b2 = simplex.points[2].point_b;
648          if dupe_a_01 || dupe_a_12 {
649            a1 = simplex.points[2].point_a;
650          }
651          let edge_b = is_edge (&mut b0, &mut b1, &b2);
652          let edge_a = geometry::Segment3::noisy (a0, a1);
653          if edge_b {
654            // edge vs edge
655            let edge_b = geometry::Segment3::noisy (b0, b1);
656            Proximity::query_segment_segment (&edge_a, &edge_b)
657          } else {
658            // edge vs triangle
659            let triangle_b = geometry::Triangle3::noisy (b0, b1, b2);
660            // flip direction so proximity query points from A -> B
661            Proximity::query_triangle_segment (&triangle_b, &edge_a).flip()
662          }
663        } else if (dupe_b_01 != dupe_b_02 || dupe_b_01 != dupe_b_12)
664          && (!dupe_a_01 && !dupe_a_02 && !dupe_a_12)
665        {
666          // face vs edge
667          let mut a0 = simplex.points[0].point_a;
668          let mut a1 = simplex.points[1].point_a;
669          let a2 = simplex.points[2].point_a;
670          let b0 = simplex.points[0].point_b;
671          let b1 = if dupe_b_01 || dupe_b_12 {
672            simplex.points[2].point_b
673          } else {
674            simplex.points[1].point_b
675          };
676          let edge_a = is_edge (&mut a0, &mut a1, &a2);
677          if edge_a {
678            // edge vs edge
679            let edge_a = geometry::Segment3::noisy (a0, a1);
680            let edge_b = geometry::Segment3::noisy (b0, b1);
681            Proximity::query_segment_segment (&edge_a, &edge_b)
682          } else {
683            // triangle vs edge
684            let triangle_a = geometry::Triangle3::noisy (a0, a1, a2);
685            let edge_b = geometry::Segment3::noisy (b0, b1);
686            Proximity::query_triangle_segment (&triangle_a, &edge_b)
687          }
688        } else {
689          if (dupe_a_01 != dupe_a_02 || dupe_a_01 != dupe_a_12)
690            && !(dupe_a_01 && dupe_a_02 && dupe_a_12)  // XOR of 3 values
691          {
692            // edge vs edge
693            // B also has exactly one duplicate pair
694            debug_assert!(dupe_b_01 != dupe_b_02 || dupe_b_01 != dupe_b_12);
695            debug_assert!(!(dupe_b_01 && dupe_b_02 && dupe_b_12));
696            debug_assert!(!(dupe_a_01 && dupe_b_01));
697            debug_assert!(!(dupe_a_02 && dupe_b_02));
698            debug_assert!(!(dupe_a_12 && dupe_b_12));
699            // before making the distance call, the base point of the triangle simplex
700            // has to be the intersection of the 'duplicate' points in each object and
701            // the edge 01 of the simplex triangle should be the edge corresponding to
702            // the edge in A, and likewise edge 02 for B
703            if (dupe_a_01 != dupe_a_02) && (dupe_b_01 != dupe_b_02) {
704              // 0 -> 0
705              if dupe_a_02 {
706                // 1 -> 1
707                // 2 -> 2
708                debug_assert!(dupe_b_01);
709                // no action needed
710              } else {
711                // 1 -> 2
712                // 2 -> 1
713                debug_assert!(dupe_a_01);
714                debug_assert!(dupe_b_02);
715                simplex.points.swap(1, 2);
716              }
717            } else if (dupe_a_01 != dupe_a_12) && (dupe_b_01 != dupe_b_12) {
718              // 0 -> 1
719              let temp = simplex.points[0];
720              simplex.points[0] = simplex.points[1];
721              if dupe_a_01 {
722                // 1 -> 2
723                // 2 -> 0
724                debug_assert!(dupe_b_12);
725                simplex.points[1] = simplex.points[2];
726                simplex.points[2] = temp;
727              } else {
728                // 1 -> 0
729                // 2 -> 2
730                debug_assert!(dupe_a_12);
731                debug_assert!(dupe_b_01);
732                simplex.points[1] = temp;
733              }
734            } else {
735              // 0 -> 2
736              debug_assert!(dupe_a_02 != dupe_a_12);
737              debug_assert!(dupe_b_02 != dupe_b_12);
738              let temp = simplex.points[0];
739              simplex.points[0] = simplex.points[2];
740              if dupe_a_02 {
741                // 1 -> 1
742                // 2 -> 0
743                debug_assert!(dupe_b_12);
744                simplex.points[2] = temp;
745              } else {
746                // 1 -> 0
747                // 2 -> 1
748                debug_assert!(dupe_a_12);
749                debug_assert!(dupe_b_02);
750                simplex.points[2] = simplex.points[1];
751                simplex.points[1] = temp;
752              }
753            }
754            let a0 = simplex.points[0].point_a;
755            let a1 = simplex.points[1].point_a;
756            let b0 = simplex.points[0].point_b;
757            // somewhat confusing, but edge B is always along simplex points 0->2
758            let b2 = simplex.points[2].point_b;
759            let edge_a = geometry::Segment3::noisy (a0, a1);
760            let edge_b = geometry::Segment3::noisy (b0, b2);
761            Proximity::query_segment_segment (&edge_a, &edge_b)
762          } else {
763            // point vs face
764            // XOR: point in one of the objects but not both
765            debug_assert!((dupe_a_01 && dupe_a_12) != (dupe_b_01 && dupe_b_12));
766            if dupe_a_01 && dupe_a_12 {
767              // point in A, face in B
768              debug_assert!(dupe_a_02); // implied
769              let point_a = simplex.points[0].point_a;
770              let b0 = simplex.points[0].point_b;
771              let b1 = simplex.points[1].point_b;
772              let b2 = simplex.points[2].point_b;
773              let triangle_b = geometry::Triangle3::noisy (b0, b1, b2);
774              // flip direction so proximity query points from A -> B
775              Proximity::query_triangle_point (&triangle_b, &point_a).flip()
776            } else {
777              // point in B, face in A
778              debug_assert!(dupe_b_01 && dupe_b_12);
779              debug_assert!(dupe_b_02); // implied
780              let point_b = simplex.points[0].point_b;
781              let a0 = simplex.points[0].point_a;
782              let a1 = simplex.points[1].point_a;
783              let a2 = simplex.points[2].point_a;
784              let triangle_a = geometry::Triangle3::noisy (a0, a1, a2);
785              Proximity::query_triangle_point (&triangle_a, &point_b)
786            }
787          }
788        }
789      }
790      _ => unreachable!()
791    }
792  }
793}
794
795#[cfg(test)]
796mod tests {
797  use approx;
798  use gl_utils::{mesh, Mesh};
799  use rand;
800  use rand_distr;
801  use rand_xorshift::XorShiftRng;
802  use crate::math::geometry::*;
803  use crate::component;
804  use super::*;
805
806  #[test]
807  fn query_gjk() {
808    use rand::SeedableRng;
809    use rand_distr::Distribution;
810
811    let tetrahedron = |p1 : [f64; 3], p2 : [f64; 3], p3 : [f64; 3], p4 : [f64; 3]|
812      -> (component::Position, component::Bound)
813    {
814      ( component::Position::origin(),
815        Hull3::from_points_with_mesh (
816          &simplex3::Tetrahedron::<f64>::noisy (
817            p1.into(), p2.into(), p3.into(), p4.into()
818          ).points()
819        ).unwrap().into()
820      )
821    };
822    let a = tetrahedron (
823      [-2.0,  2.0, -1.0],
824      [ 2.0,  2.0, -1.0],
825      [ 0.0, -2.0, -1.0],
826      [ 0.0,  0.0,  2.0]);
827    let b = tetrahedron (
828      [-2.0,  2.0,  3.5],
829      [ 2.0,  2.0,  3.5],
830      [ 0.0, -2.0,  3.5],
831      [ 0.0,  0.0,  6.5]);
832    let proximity = Proximity::query_gjk (&a, &b);
833    assert_eq!(proximity.distance, 1.5);
834    assert_eq!(proximity.half_axis, [0.0, 0.0, 0.75].into());
835    assert_eq!(proximity.midpoint, [0.0, 0.0, 2.75].into());
836    assert_eq!(proximity.normal, -Unit3::axis_z());
837    let a = tetrahedron (
838      [-2.0,  2.0, -1.0],
839      [ 2.0,  2.0, -1.0],
840      [ 0.0, -2.0, -1.0],
841      [ 0.0,  0.0,  2.0]);
842    let b = tetrahedron (
843      [-2.0,  2.0,  1.75],
844      [ 2.0,  2.0,  1.75],
845      [ 0.0, -2.0,  1.75],
846      [ 0.0,  0.0,  4.75]);
847    let proximity = Proximity::query_gjk (&a, &b);
848    assert_eq!(proximity.distance, -0.25);
849    assert_eq!(proximity.half_axis, [0.0, 0.0, -0.125].into());
850    assert_eq!(proximity.midpoint, [0.0, 0.0, 1.875].into());
851    assert_eq!(proximity.normal, -Unit3::axis_z());
852    let a = tetrahedron (
853      [ 0.0,  0.0,  3.5],
854      [-2.0,  2.0,  0.5],
855      [ 2.0,  2.0,  0.5],
856      [ 0.0, -2.0,  0.5]);
857    let b = tetrahedron (
858      [-2.0,  2.0,  1.0],
859      [ 2.0,  2.0,  1.0],
860      [ 0.0, -2.0,  1.0],
861      [ 0.0,  0.0, -2.0]);
862    let proximity = Proximity::query_gjk (&a, &b);
863    assert_eq!(proximity.distance, -0.5);
864    approx::assert_abs_diff_eq!(proximity.half_axis, [0.0, 0.0, 0.25].into());
865    assert_eq!(proximity.midpoint, [0.0, 2.0/3.0, 0.75].into());
866    approx::assert_abs_diff_eq!(*proximity.normal, *Unit3::axis_z(),
867      epsilon = f64::EPSILON * 4.0);
868    let hull = |points : &[Point3 <f64>]| -> (component::Position, component::Bound) {
869      ( component::Position::origin(),
870        Hull3::from_points_with_mesh (points).unwrap().into()
871      )
872    };
873    let a = hull (&[
874      [ 0.0,  0.0,  3.5],
875      [ 0.0,  0.0,  0.5],
876      [-2.0,  2.0,  0.5],
877      [ 2.0,  2.0,  0.5],
878      [ 0.0, -2.0,  0.5]].map (Into::into));
879    let b = hull (&[
880      [-2.0,  2.0,  1.0],
881      [ 2.0,  2.0,  1.0],
882      [ 0.0, -2.0,  1.0],
883      [ 0.0,  0.0,  1.0],
884      [ 0.0,  0.0, -2.0]].map (Into::into));
885    let proximity = Proximity::query_gjk (&a, &b);
886    assert_eq!(proximity.distance, -0.5);
887    approx::assert_abs_diff_eq!(proximity.half_axis, [0.0, 0.0, 0.25].into());
888    assert_eq!(proximity.midpoint, [0.0, 2.0/3.0, 0.75].into());
889    approx::assert_abs_diff_eq!(*proximity.normal, *Unit3::axis_z(),
890      epsilon = f64::EPSILON * 4.0);
891
892    {
893      const NPOINTS : usize = 4;
894      let mut rng    = XorShiftRng::seed_from_u64 (0);
895      let cauchy     = rand_distr::Cauchy::new (0.0, 1.0).unwrap();
896      let randf      = |rng : &mut XorShiftRng| cauchy.sample (rng);
897      let aabb = Aabb3::with_minmax (
898        [-5.0, -5.0, -5.0].into(),
899        [ 5.0,  5.0,  5.0].into()).unwrap();
900      let rand_point = |rng : &mut XorShiftRng|
901        aabb.clamp (point3 (randf (rng), randf (rng), randf (rng)));
902      let object = |hull : shape::Hull <f64>|
903        (component::Position::origin(), component::Bound::from (hull));
904      #[expect(unused_variables)]
905      let write_gltf = |
906        (hull, mesh) : &shape::Hull <f64>,
907        filename     : &str
908      | {
909        let mut m = mesh::Triangles3dBuilder::empty();
910        for triangle in mesh.triangles().values() {
911          let triangle = hull.triangle (triangle);
912          m.push_face (triangle.points());
913        }
914        Mesh::from (m.build()).write_gltf (filename);
915      };
916      for _i in 0..1000 {
917        //println!("ITER: {i}");
918        let a = {
919          let points = std::iter::repeat_with (|| rand_point (&mut rng)).take (NPOINTS)
920            .collect::<Vec<_>>();
921          let hull = Hull3::from_points_with_mesh (&points).unwrap();
922          //write_gltf (&hull, mesh, "hull_a.gltf");
923          object (hull)
924        };
925        let b = {
926          let points = std::iter::repeat_with (|| rand_point (&mut rng)).take (NPOINTS)
927            .collect::<Vec<_>>();
928          let hull = Hull3::from_points_with_mesh (&points).unwrap();
929          //write_gltf (&hull, mesh, "hull_b.gltf");
930          object (hull)
931        };
932        //show!(a);
933        //show!(b);
934        let _proximity = Proximity::query_gjk (&a, &b);
935      }
936    }
937  }
938}