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}