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