parry3d/query/epa/
epa3.rs

1//! Three-dimensional penetration depth queries using the Expanding Polytope Algorithm.
2//!
3//! This module provides the 3D-specific implementation of EPA, which works with
4//! polyhedra (triangular faces) rather than polygons (edges) as in the 2D version.
5
6use crate::math::{Isometry, Point, Real, Vector};
7use crate::query::gjk::{self, CSOPoint, ConstantOrigin, VoronoiSimplex};
8use crate::query::PointQueryWithLocation;
9use crate::shape::{SupportMap, Triangle, TrianglePointLocation};
10use crate::utils;
11use alloc::collections::BinaryHeap;
12use alloc::vec::Vec;
13use core::cmp::Ordering;
14use na::{self, Unit};
15use num::Bounded;
16
17#[derive(Copy, Clone, PartialEq)]
18struct FaceId {
19    id: usize,
20    neg_dist: Real,
21}
22
23impl FaceId {
24    fn new(id: usize, neg_dist: Real) -> Option<Self> {
25        if neg_dist > gjk::eps_tol() {
26            None
27        } else {
28            Some(FaceId { id, neg_dist })
29        }
30    }
31}
32
33impl Eq for FaceId {}
34
35impl PartialOrd for FaceId {
36    #[inline]
37    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
38        Some(self.cmp(other))
39    }
40}
41
42impl Ord for FaceId {
43    #[inline]
44    fn cmp(&self, other: &Self) -> Ordering {
45        if self.neg_dist < other.neg_dist {
46            Ordering::Less
47        } else if self.neg_dist > other.neg_dist {
48            Ordering::Greater
49        } else {
50            Ordering::Equal
51        }
52    }
53}
54
55#[derive(Clone, Debug)]
56struct Face {
57    pts: [usize; 3],
58    adj: [usize; 3],
59    normal: Unit<Vector<Real>>,
60    bcoords: [Real; 3],
61    deleted: bool,
62}
63
64impl Face {
65    pub fn new_with_proj(
66        vertices: &[CSOPoint],
67        bcoords: [Real; 3],
68        pts: [usize; 3],
69        adj: [usize; 3],
70    ) -> Self {
71        let normal;
72
73        if let Some(n) = utils::ccw_face_normal([
74            &vertices[pts[0]].point,
75            &vertices[pts[1]].point,
76            &vertices[pts[2]].point,
77        ]) {
78            normal = n;
79        } else {
80            // This is a bit of a hack for degenerate faces.
81            // TODO: It will work OK with our current code, though
82            // we should do this in another way to avoid any risk
83            // of misusing the face normal in the future.
84            normal = Unit::new_unchecked(na::zero());
85        }
86
87        Face {
88            pts,
89            bcoords,
90            adj,
91            normal,
92            deleted: false,
93        }
94    }
95
96    pub fn new(vertices: &[CSOPoint], pts: [usize; 3], adj: [usize; 3]) -> (Self, bool) {
97        let tri = Triangle::new(
98            vertices[pts[0]].point,
99            vertices[pts[1]].point,
100            vertices[pts[2]].point,
101        );
102        let (proj, loc) = tri.project_local_point_and_get_location(&Point::<Real>::origin(), true);
103
104        match loc {
105            TrianglePointLocation::OnVertex(_) | TrianglePointLocation::OnEdge(_, _) => {
106                let eps_tol = crate::math::DEFAULT_EPSILON * 100.0; // Same as in closest_points
107                (
108                    // barycentric_coordinates is guaranteed to work in OnVertex and OnEdge locations
109                    Self::new_with_proj(vertices, loc.barycentric_coordinates().unwrap(), pts, adj),
110                    proj.is_inside_eps(&Point::<Real>::origin(), eps_tol),
111                )
112            }
113            TrianglePointLocation::OnFace(_, bcoords) => {
114                (Self::new_with_proj(vertices, bcoords, pts, adj), true)
115            }
116            _ => (Self::new_with_proj(vertices, [0.0; 3], pts, adj), false),
117        }
118    }
119
120    pub fn closest_points(&self, vertices: &[CSOPoint]) -> (Point<Real>, Point<Real>) {
121        (
122            vertices[self.pts[0]].orig1 * self.bcoords[0]
123                + vertices[self.pts[1]].orig1.coords * self.bcoords[1]
124                + vertices[self.pts[2]].orig1.coords * self.bcoords[2],
125            vertices[self.pts[0]].orig2 * self.bcoords[0]
126                + vertices[self.pts[1]].orig2.coords * self.bcoords[1]
127                + vertices[self.pts[2]].orig2.coords * self.bcoords[2],
128        )
129    }
130
131    pub fn contains_point(&self, id: usize) -> bool {
132        self.pts[0] == id || self.pts[1] == id || self.pts[2] == id
133    }
134
135    pub fn next_ccw_pt_id(&self, id: usize) -> usize {
136        if self.pts[0] == id {
137            1
138        } else if self.pts[1] == id {
139            2
140        } else {
141            if self.pts[2] != id {
142                log::debug!(
143                    "Hit unexpected state in EPA: found index {}, expected: {}.",
144                    self.pts[2],
145                    id
146                );
147            }
148
149            0
150        }
151    }
152
153    pub fn can_be_seen_by(&self, vertices: &[CSOPoint], point: usize, opp_pt_id: usize) -> bool {
154        let p0 = &vertices[self.pts[opp_pt_id]].point;
155        let p1 = &vertices[self.pts[(opp_pt_id + 1) % 3]].point;
156        let p2 = &vertices[self.pts[(opp_pt_id + 2) % 3]].point;
157        let pt = &vertices[point].point;
158
159        // NOTE: it is important that we return true for the case where
160        // the dot product is zero. This is because degenerate faces will
161        // have a zero normal, causing the dot product to be zero.
162        // So return true for these case will let us skip the triangle
163        // during silhouette computation.
164        (*pt - *p0).dot(&self.normal) >= -gjk::eps_tol()
165            || Triangle::new(*p1, *p2, *pt).is_affinely_dependent()
166    }
167}
168
169struct SilhouetteEdge {
170    face_id: usize,
171    opp_pt_id: usize,
172}
173
174impl SilhouetteEdge {
175    pub fn new(face_id: usize, opp_pt_id: usize) -> Self {
176        SilhouetteEdge { face_id, opp_pt_id }
177    }
178}
179
180/// The Expanding Polytope Algorithm in 3D.
181///
182/// This structure computes the penetration depth between two shapes when they are overlapping.
183/// It's used after GJK (Gilbert-Johnson-Keerthi) determines that two shapes are penetrating.
184///
185/// # What does EPA do?
186///
187/// EPA finds:
188/// - The **penetration depth**: How far the shapes are overlapping
189/// - The **contact normal**: The direction to separate the shapes
190/// - The **contact points**: Where the shapes are touching on each surface
191///
192/// # How it works in 3D
193///
194/// In 3D, EPA maintains a convex polyhedron (made of triangular faces) in the Minkowski
195/// difference space (CSO - Configuration Space Obstacle) that encloses the origin. It iteratively:
196///
197/// 1. Finds the triangular face closest to the origin
198/// 2. Expands the polyhedron by adding a new support point in the direction of that face's normal
199/// 3. Removes faces that can be "seen" from the new point (they're now inside)
200/// 4. Creates new faces connecting the boundary edges (silhouette) to the new point
201/// 5. Repeats until the polyhedron cannot expand further (convergence)
202///
203/// The final closest face provides the penetration depth (distance to origin) and contact normal.
204///
205/// # Example
206///
207/// ```
208/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
209/// use parry3d::query::epa::EPA;
210/// use parry3d::query::gjk::VoronoiSimplex;
211/// use parry3d::shape::Ball;
212/// use parry3d::na::Isometry3;
213///
214/// let ball1 = Ball::new(1.0);
215/// let ball2 = Ball::new(1.0);
216/// let pos12 = Isometry3::translation(1.5, 0.0, 0.0); // Overlapping spheres
217///
218/// // After GJK determines penetration and fills a simplex:
219/// let mut epa = EPA::new();
220/// let simplex = VoronoiSimplex::new(); // Would be filled by GJK
221///
222/// // EPA computes the contact details
223/// // if let Some((pt1, pt2, normal)) = epa.closest_points(&pos12, &ball1, &ball2, &simplex) {
224/// //     println!("Penetration depth: {}", (pt2 - pt1).norm());
225/// //     println!("Contact normal: {}", normal);
226/// // }
227/// # }
228/// ```
229///
230/// # Reusability
231///
232/// The `EPA` structure can be reused across multiple queries to avoid allocations.
233/// Internal buffers are cleared and reused in each call to [`closest_points`](EPA::closest_points).
234///
235/// # Convergence and Failure Cases
236///
237/// EPA may return `None` in these situations:
238/// - The shapes are not actually penetrating (GJK should be used instead)
239/// - Degenerate or nearly-degenerate geometry causes numerical instability
240/// - The initial simplex from GJK is invalid
241/// - The algorithm fails to converge after 100 iterations
242/// - Silhouette extraction fails (topology issues)
243///
244/// When `None` is returned, the shapes may be touching at a single point, edge, or face,
245/// or there may be numerical precision issues with the input geometry.
246///
247/// # Complexity
248///
249/// The 3D EPA implementation is more complex than 2D because:
250/// - It maintains a 3D mesh topology with face adjacency information
251/// - It needs to compute silhouettes (visible edges from a point)
252/// - It handles more degenerate cases (coplanar faces, edge cases)
253#[derive(Default)]
254pub struct EPA {
255    vertices: Vec<CSOPoint>,
256    faces: Vec<Face>,
257    silhouette: Vec<SilhouetteEdge>,
258    heap: BinaryHeap<FaceId>,
259}
260
261impl EPA {
262    /// Creates a new instance of the 3D Expanding Polytope Algorithm.
263    ///
264    /// This allocates internal data structures (vertices, faces, silhouette buffer, and a priority heap).
265    /// The same `EPA` instance can be reused for multiple queries to avoid repeated allocations.
266    ///
267    /// # Example
268    ///
269    /// ```
270    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
271    /// use parry3d::query::epa::EPA;
272    ///
273    /// let mut epa = EPA::new();
274    /// // Use epa for multiple queries...
275    /// # }
276    /// ```
277    pub fn new() -> Self {
278        Self::default()
279    }
280
281    fn reset(&mut self) {
282        self.vertices.clear();
283        self.faces.clear();
284        self.heap.clear();
285        self.silhouette.clear();
286    }
287
288    /// Projects the origin onto the boundary of the given shape.
289    ///
290    /// This is a specialized version of [`closest_points`](EPA::closest_points) for projecting
291    /// a point (the origin) onto a single shape's surface.
292    ///
293    /// # Parameters
294    ///
295    /// - `m`: The position and orientation of the shape in world space
296    /// - `g`: The shape to project onto (must implement [`SupportMap`])
297    /// - `simplex`: A Voronoi simplex from GJK that encloses the origin (indicating the origin
298    ///   is inside the shape)
299    ///
300    /// # Returns
301    ///
302    /// - `Some(point)`: The closest point on the shape's boundary to the origin, in the local
303    ///   space of `g`
304    /// - `None`: If the origin is not inside the shape, or if EPA failed to converge
305    ///
306    /// # Prerequisites
307    ///
308    /// The origin **must be inside** the shape. If it's outside, use GJK instead.
309    /// Typically, you would:
310    /// 1. Run GJK to detect if a point is inside a shape
311    /// 2. If inside, use this method to find the closest boundary point
312    ///
313    /// # Example
314    ///
315    /// ```
316    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
317    /// use parry3d::query::epa::EPA;
318    /// use parry3d::query::gjk::VoronoiSimplex;
319    /// use parry3d::shape::Ball;
320    /// use parry3d::na::Isometry3;
321    ///
322    /// let ball = Ball::new(2.0);
323    /// let pos = Isometry3::<f32>::identity();
324    ///
325    /// // Assume GJK determined the origin is inside and filled simplex
326    /// let simplex = VoronoiSimplex::new();
327    /// let mut epa = EPA::new();
328    ///
329    /// // Find the closest point on the ball's surface to the origin
330    /// // if let Some(surface_point) = epa.project_origin(&pos, &ball, &simplex) {
331    /// //     println!("Closest surface point: {:?}", surface_point);
332    /// // }
333    /// # }
334    /// ```
335    pub fn project_origin<G: ?Sized + SupportMap>(
336        &mut self,
337        m: &Isometry<Real>,
338        g: &G,
339        simplex: &VoronoiSimplex,
340    ) -> Option<Point<Real>> {
341        self.closest_points(&m.inverse(), g, &ConstantOrigin, simplex)
342            .map(|(p, _, _)| p)
343    }
344
345    /// Computes the closest points between two penetrating shapes and their contact normal.
346    ///
347    /// This is the main EPA method that computes detailed contact information for overlapping shapes.
348    /// It should be called after GJK determines that two shapes are penetrating.
349    ///
350    /// # Parameters
351    ///
352    /// - `pos12`: The relative position/orientation from `g2`'s frame to `g1`'s frame
353    ///   (typically computed as `pos1.inverse() * pos2`)
354    /// - `g1`: The first shape (must implement [`SupportMap`])
355    /// - `g2`: The second shape (must implement [`SupportMap`])
356    /// - `simplex`: A Voronoi simplex from GJK that encloses the origin, indicating penetration
357    ///
358    /// # Returns
359    ///
360    /// Returns `Some((point1, point2, normal))` where:
361    /// - `point1`: Contact point on shape `g1` in `g1`'s local frame
362    /// - `point2`: Contact point on shape `g2` in `g2`'s local frame
363    /// - `normal`: Contact normal pointing from `g2` toward `g1`, normalized
364    ///
365    /// The **penetration depth** can be computed as `(point1 - point2).norm()` after transforming
366    /// both points to the same coordinate frame.
367    ///
368    /// Returns `None` if:
369    /// - The shapes are not actually penetrating
370    /// - EPA fails to converge (degenerate geometry, numerical issues)
371    /// - The simplex is invalid or empty
372    /// - The algorithm reaches the maximum iteration limit (100 iterations)
373    /// - Silhouette extraction fails (indicates topology corruption)
374    ///
375    /// # Prerequisites
376    ///
377    /// The shapes **must be penetrating**. The typical workflow is:
378    /// 1. Run GJK to check if shapes intersect
379    /// 2. If GJK detects penetration and returns a simplex enclosing the origin
380    /// 3. Use EPA with that simplex to compute detailed contact information
381    ///
382    /// # Example
383    ///
384    /// ```
385    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
386    /// use parry3d::query::epa::EPA;
387    /// use parry3d::query::gjk::{GJKResult, VoronoiSimplex};
388    /// use parry3d::shape::Ball;
389    /// use parry3d::na::Isometry3;
390    ///
391    /// let ball1 = Ball::new(1.0);
392    /// let ball2 = Ball::new(1.0);
393    ///
394    /// let pos1 = Isometry3::identity();
395    /// let pos2 = Isometry3::translation(1.5, 0.0, 0.0); // Overlapping
396    /// let pos12 = pos1.inverse() * pos2;
397    ///
398    /// // After GJK detects penetration:
399    /// let mut simplex = VoronoiSimplex::new();
400    /// // ... simplex would be filled by GJK ...
401    ///
402    /// let mut epa = EPA::new();
403    /// // if let Some((pt1, pt2, normal)) = epa.closest_points(&pos12, &ball1, &ball2, &simplex) {
404    /// //     println!("Contact on shape 1: {:?}", pt1);
405    /// //     println!("Contact on shape 2: {:?}", pt2);
406    /// //     println!("Contact normal: {}", normal);
407    /// //     println!("Penetration depth: ~0.5");
408    /// // }
409    /// # }
410    /// ```
411    ///
412    /// # Technical Details
413    ///
414    /// The algorithm works in the **Minkowski difference space** (also called the Configuration
415    /// Space Obstacle or CSO), where the difference between support points of the two shapes
416    /// forms a new shape. When shapes penetrate, this CSO contains the origin.
417    ///
418    /// EPA iteratively expands a convex polyhedron (in 3D) that surrounds the origin. At each
419    /// iteration:
420    /// 1. It finds the triangular face closest to the origin
421    /// 2. Computes a support point in that face's normal direction
422    /// 3. Determines which existing faces are visible from the new point (the **silhouette**)
423    /// 4. Removes visible faces and creates new ones connecting the silhouette boundary to the new point
424    ///
425    /// This process maintains a valid convex hull that progressively tightens around the
426    /// origin until convergence, at which point the closest face defines the penetration
427    /// depth and contact normal.
428    pub fn closest_points<G1, G2>(
429        &mut self,
430        pos12: &Isometry<Real>,
431        g1: &G1,
432        g2: &G2,
433        simplex: &VoronoiSimplex,
434    ) -> Option<(Point<Real>, Point<Real>, Unit<Vector<Real>>)>
435    where
436        G1: ?Sized + SupportMap,
437        G2: ?Sized + SupportMap,
438    {
439        let _eps = crate::math::DEFAULT_EPSILON;
440        let _eps_tol = _eps * 100.0;
441
442        self.reset();
443
444        /*
445         * Initialization.
446         */
447        for i in 0..simplex.dimension() + 1 {
448            self.vertices.push(*simplex.point(i));
449        }
450
451        if simplex.dimension() == 0 {
452            let mut n: Vector<Real> = na::zero();
453            n[1] = 1.0;
454            return Some((Point::origin(), Point::origin(), Unit::new_unchecked(n)));
455        } else if simplex.dimension() == 3 {
456            let dp1 = self.vertices[1] - self.vertices[0];
457            let dp2 = self.vertices[2] - self.vertices[0];
458            let dp3 = self.vertices[3] - self.vertices[0];
459
460            if dp1.cross(&dp2).dot(&dp3) > 0.0 {
461                self.vertices.swap(1, 2)
462            }
463
464            let pts1 = [0, 1, 2];
465            let pts2 = [1, 3, 2];
466            let pts3 = [0, 2, 3];
467            let pts4 = [0, 3, 1];
468
469            let adj1 = [3, 1, 2];
470            let adj2 = [3, 2, 0];
471            let adj3 = [0, 1, 3];
472            let adj4 = [2, 1, 0];
473
474            let (face1, proj_inside1) = Face::new(&self.vertices, pts1, adj1);
475            let (face2, proj_inside2) = Face::new(&self.vertices, pts2, adj2);
476            let (face3, proj_inside3) = Face::new(&self.vertices, pts3, adj3);
477            let (face4, proj_inside4) = Face::new(&self.vertices, pts4, adj4);
478
479            self.faces.push(face1);
480            self.faces.push(face2);
481            self.faces.push(face3);
482            self.faces.push(face4);
483
484            if proj_inside1 {
485                let dist1 = self.faces[0].normal.dot(&self.vertices[0].point.coords);
486                self.heap.push(FaceId::new(0, -dist1)?);
487            }
488
489            if proj_inside2 {
490                let dist2 = self.faces[1].normal.dot(&self.vertices[1].point.coords);
491                self.heap.push(FaceId::new(1, -dist2)?);
492            }
493
494            if proj_inside3 {
495                let dist3 = self.faces[2].normal.dot(&self.vertices[2].point.coords);
496                self.heap.push(FaceId::new(2, -dist3)?);
497            }
498
499            if proj_inside4 {
500                let dist4 = self.faces[3].normal.dot(&self.vertices[3].point.coords);
501                self.heap.push(FaceId::new(3, -dist4)?);
502            }
503
504            if !(proj_inside1 || proj_inside2 || proj_inside3 || proj_inside4) {
505                // Related issues:
506                // https://github.com/dimforge/parry/issues/253
507                // https://github.com/dimforge/parry/issues/246
508                log::debug!("Hit unexpected state in EPA: failed to project the origin on the initial simplex.");
509                return None;
510            }
511        } else {
512            if simplex.dimension() == 1 {
513                let dpt = self.vertices[1] - self.vertices[0];
514
515                Vector::orthonormal_subspace_basis(&[dpt], |dir| {
516                    // XXX: dir should already be unit on nalgebra!
517                    let dir = Unit::new_unchecked(*dir);
518                    self.vertices
519                        .push(CSOPoint::from_shapes(pos12, g1, g2, &dir));
520                    false
521                });
522            }
523
524            let pts1 = [0, 1, 2];
525            let pts2 = [0, 2, 1];
526
527            let adj1 = [1, 1, 1];
528            let adj2 = [0, 0, 0];
529
530            let (face1, _) = Face::new(&self.vertices, pts1, adj1);
531            let (face2, _) = Face::new(&self.vertices, pts2, adj2);
532            self.faces.push(face1);
533            self.faces.push(face2);
534
535            self.heap.push(FaceId::new(0, 0.0)?);
536            self.heap.push(FaceId::new(1, 0.0)?);
537        }
538
539        let mut niter = 0;
540        let mut max_dist = Real::max_value();
541        let mut best_face_id = *self.heap.peek()?;
542        let mut old_dist = 0.0;
543
544        /*
545         * Run the expansion.
546         */
547        while let Some(face_id) = self.heap.pop() {
548            // Create new faces.
549            let face = self.faces[face_id.id].clone();
550
551            if face.deleted {
552                continue;
553            }
554
555            let cso_point = CSOPoint::from_shapes(pos12, g1, g2, &face.normal);
556            let support_point_id = self.vertices.len();
557            self.vertices.push(cso_point);
558
559            let candidate_max_dist = cso_point.point.coords.dot(&face.normal);
560
561            if candidate_max_dist < max_dist {
562                best_face_id = face_id;
563                max_dist = candidate_max_dist;
564            }
565
566            let curr_dist = -face_id.neg_dist;
567
568            if max_dist - curr_dist < _eps_tol ||
569                // Accept the intersection as the algorithm is stuck and no new points will be found
570                // This happens because of numerical stability issue
571                ((curr_dist - old_dist).abs() < _eps && candidate_max_dist < max_dist)
572            {
573                let best_face = &self.faces[best_face_id.id];
574                let points = best_face.closest_points(&self.vertices);
575                return Some((points.0, points.1, best_face.normal));
576            }
577
578            old_dist = curr_dist;
579
580            self.faces[face_id.id].deleted = true;
581
582            let adj_opp_pt_id1 = self.faces[face.adj[0]].next_ccw_pt_id(face.pts[0]);
583            let adj_opp_pt_id2 = self.faces[face.adj[1]].next_ccw_pt_id(face.pts[1]);
584            let adj_opp_pt_id3 = self.faces[face.adj[2]].next_ccw_pt_id(face.pts[2]);
585
586            self.compute_silhouette(support_point_id, face.adj[0], adj_opp_pt_id1);
587            self.compute_silhouette(support_point_id, face.adj[1], adj_opp_pt_id2);
588            self.compute_silhouette(support_point_id, face.adj[2], adj_opp_pt_id3);
589
590            let first_new_face_id = self.faces.len();
591
592            if self.silhouette.is_empty() {
593                // TODO: Something went very wrong because we failed to extract a silhouette…
594                return None;
595            }
596
597            for edge in &self.silhouette {
598                if !self.faces[edge.face_id].deleted {
599                    let new_face_id = self.faces.len();
600
601                    let face_adj = &mut self.faces[edge.face_id];
602                    let pt_id1 = face_adj.pts[(edge.opp_pt_id + 2) % 3];
603                    let pt_id2 = face_adj.pts[(edge.opp_pt_id + 1) % 3];
604
605                    let pts = [pt_id1, pt_id2, support_point_id];
606                    let adj = [edge.face_id, new_face_id + 1, new_face_id - 1];
607                    let new_face = Face::new(&self.vertices, pts, adj);
608
609                    face_adj.adj[(edge.opp_pt_id + 1) % 3] = new_face_id;
610
611                    self.faces.push(new_face.0);
612
613                    if new_face.1 {
614                        let pt = self.vertices[self.faces[new_face_id].pts[0]].point.coords;
615                        let dist = self.faces[new_face_id].normal.dot(&pt);
616                        if dist < curr_dist {
617                            // TODO: if we reach this point, there were issues due to
618                            // numerical errors.
619                            let points = face.closest_points(&self.vertices);
620                            return Some((points.0, points.1, face.normal));
621                        }
622
623                        self.heap.push(FaceId::new(new_face_id, -dist)?);
624                    }
625                }
626            }
627
628            if first_new_face_id == self.faces.len() {
629                // Something went very wrong because all the edges
630                // from the silhouette belonged to deleted faces.
631                return None;
632            }
633
634            self.faces[first_new_face_id].adj[2] = self.faces.len() - 1;
635            self.faces.last_mut().unwrap().adj[1] = first_new_face_id;
636
637            self.silhouette.clear();
638            // self.check_topology(); // NOTE: for debugging only.
639
640            niter += 1;
641            if niter > 100 {
642                // if we reached this point, our algorithm didn't converge to what precision we wanted.
643                // still return an intersection point, as it's probably close enough.
644                break;
645            }
646        }
647
648        let best_face = &self.faces[best_face_id.id];
649        let points = best_face.closest_points(&self.vertices);
650        Some((points.0, points.1, best_face.normal))
651    }
652
653    fn compute_silhouette(&mut self, point: usize, id: usize, opp_pt_id: usize) {
654        if !self.faces[id].deleted {
655            if !self.faces[id].can_be_seen_by(&self.vertices, point, opp_pt_id) {
656                self.silhouette.push(SilhouetteEdge::new(id, opp_pt_id));
657            } else {
658                self.faces[id].deleted = true;
659
660                let adj_pt_id1 = (opp_pt_id + 2) % 3;
661                let adj_pt_id2 = opp_pt_id;
662
663                let adj1 = self.faces[id].adj[adj_pt_id1];
664                let adj2 = self.faces[id].adj[adj_pt_id2];
665
666                let adj_opp_pt_id1 =
667                    self.faces[adj1].next_ccw_pt_id(self.faces[id].pts[adj_pt_id1]);
668                let adj_opp_pt_id2 =
669                    self.faces[adj2].next_ccw_pt_id(self.faces[id].pts[adj_pt_id2]);
670
671                self.compute_silhouette(point, adj1, adj_opp_pt_id1);
672                self.compute_silhouette(point, adj2, adj_opp_pt_id2);
673            }
674        }
675    }
676
677    #[allow(dead_code)]
678    #[cfg(feature = "std")]
679    fn print_silhouette(&self) {
680        use std::{print, println};
681
682        print!("Silhouette points: ");
683        for i in 0..self.silhouette.len() {
684            let edge = &self.silhouette[i];
685            let face = &self.faces[edge.face_id];
686
687            if !face.deleted {
688                print!(
689                    "({}, {}) ",
690                    face.pts[(edge.opp_pt_id + 2) % 3],
691                    face.pts[(edge.opp_pt_id + 1) % 3]
692                );
693            }
694        }
695        println!();
696    }
697
698    #[allow(dead_code)]
699    fn check_topology(&self) {
700        for i in 0..self.faces.len() {
701            let face = &self.faces[i];
702            if face.deleted {
703                continue;
704            }
705
706            // println!("checking {}-th face.", i);
707            let adj1 = &self.faces[face.adj[0]];
708            let adj2 = &self.faces[face.adj[1]];
709            let adj3 = &self.faces[face.adj[2]];
710
711            assert!(!adj1.deleted);
712            assert!(!adj2.deleted);
713            assert!(!adj3.deleted);
714
715            assert!(face.pts[0] != face.pts[1]);
716            assert!(face.pts[0] != face.pts[2]);
717            assert!(face.pts[1] != face.pts[2]);
718
719            assert!(adj1.contains_point(face.pts[0]));
720            assert!(adj1.contains_point(face.pts[1]));
721
722            assert!(adj2.contains_point(face.pts[1]));
723            assert!(adj2.contains_point(face.pts[2]));
724
725            assert!(adj3.contains_point(face.pts[2]));
726            assert!(adj3.contains_point(face.pts[0]));
727
728            let opp_pt_id1 = adj1.next_ccw_pt_id(face.pts[0]);
729            let opp_pt_id2 = adj2.next_ccw_pt_id(face.pts[1]);
730            let opp_pt_id3 = adj3.next_ccw_pt_id(face.pts[2]);
731
732            assert!(!face.contains_point(adj1.pts[opp_pt_id1]));
733            assert!(!face.contains_point(adj2.pts[opp_pt_id2]));
734            assert!(!face.contains_point(adj3.pts[opp_pt_id3]));
735
736            assert!(adj1.adj[(opp_pt_id1 + 1) % 3] == i);
737            assert!(adj2.adj[(opp_pt_id2 + 1) % 3] == i);
738            assert!(adj3.adj[(opp_pt_id3 + 1) % 3] == i);
739        }
740    }
741}