parry2d/query/gjk/gjk.rs
1//! The Gilbert-Johnson-Keerthi distance algorithm.
2//!
3//! # What is GJK?
4//!
5//! The **Gilbert-Johnson-Keerthi (GJK)** algorithm is a fundamental geometric algorithm used
6//! to compute the distance between two convex shapes. It's one of the most important algorithms
7//! in collision detection and is used extensively in physics engines, robotics, and computer graphics.
8//!
9//! ## How GJK Works (Simplified)
10//!
11//! GJK works by operating on the **Minkowski difference** (also called Configuration Space Obstacle or CSO)
12//! of two shapes. Instead of directly comparing the shapes, GJK:
13//!
14//! 1. Constructs a simplex (triangle in 2D, tetrahedron in 3D) within the Minkowski difference
15//! 2. Iteratively refines this simplex to find the point closest to the origin
16//! 3. The distance from the origin to this closest point equals the distance between the shapes
17//!
18//! If the origin is **inside** the Minkowski difference, the shapes are intersecting.
19//! If the origin is **outside**, the distance to the closest point gives the separation distance.
20//!
21//! ## When is GJK Used?
22//!
23//! GJK is used whenever you need to:
24//! - **Check if two convex shapes intersect** (collision detection)
25//! - **Find the minimum distance** between two convex shapes
26//! - **Compute closest points** on two shapes
27//! - **Cast a shape along a direction** to find the time of impact (continuous collision detection)
28//!
29//! ## Key Advantages of GJK
30//!
31//! - Works with **any convex shape** that can provide a support function
32//! - Does **not require the full geometry** of the shapes (only support points)
33//! - Very **fast convergence** in most practical cases
34//! - Forms the basis for many collision detection systems
35//!
36//! ## Limitations
37//!
38//! - Only works with **convex shapes** (use convex decomposition for concave shapes)
39//! - When shapes are penetrating, GJK can only detect intersection but not penetration depth
40//! (use EPA - Expanding Polytope Algorithm - for penetration depth)
41//!
42//! # Main Functions in This Module
43//!
44//! - [`closest_points`] - The core GJK algorithm for finding distance and closest points
45//! - [`project_origin`] - Projects the origin onto a shape's boundary
46//! - [`cast_local_ray`] - Casts a ray against a shape (used for raycasting)
47//! - [`directional_distance`] - Computes how far a shape can move before touching another
48//!
49//! # Example
50//!
51//! See individual function documentation for usage examples.
52
53use na::{self, ComplexField, Unit};
54
55use crate::query::gjk::{CSOPoint, ConstantOrigin, VoronoiSimplex};
56use crate::shape::SupportMap;
57// use query::Proximity;
58use crate::math::{Isometry, Point, Real, Vector, DIM};
59use crate::query::{self, Ray};
60
61use num::{Bounded, Zero};
62
63/// Results of the GJK algorithm.
64///
65/// This enum represents the different outcomes when running the GJK algorithm to find
66/// the distance between two shapes. The result depends on whether the shapes are intersecting,
67/// how far apart they are, and what information was requested.
68///
69/// # Understanding the Results
70///
71/// - **Intersection**: The shapes are overlapping. The origin lies inside the Minkowski difference.
72/// - **ClosestPoints**: The exact closest points on both shapes were computed, along with the
73/// separation direction.
74/// - **Proximity**: The shapes are close but not intersecting. Only an approximate separation
75/// direction is provided (used when exact distance computation is not needed).
76/// - **NoIntersection**: The shapes are too far apart (beyond the specified `max_dist` threshold).
77///
78/// # Coordinate Spaces
79///
80/// All points and vectors in this result are expressed in the **local-space of the first shape**
81/// (the shape passed as `g1` to the GJK functions). This is important when working with
82/// transformed shapes.
83#[derive(Clone, Debug, PartialEq)]
84pub enum GJKResult {
85 /// The shapes are intersecting (overlapping).
86 ///
87 /// This means the origin is inside the Minkowski difference of the two shapes.
88 /// GJK cannot compute penetration depth; use the EPA (Expanding Polytope Algorithm)
89 /// for that purpose.
90 Intersection,
91
92 /// The closest points on both shapes were found.
93 ///
94 /// # Fields
95 ///
96 /// - First `Point`: The closest point on the first shape (in local-space of shape 1)
97 /// - Second `Point`: The closest point on the second shape (in local-space of shape 1)
98 /// - `Unit<Vector>`: The unit direction vector from shape 1 to shape 2 (separation axis)
99 ///
100 /// This variant is returned when `exact_dist` is `true` in the GJK algorithm and the
101 /// shapes are not intersecting.
102 ClosestPoints(Point<Real>, Point<Real>, Unit<Vector<Real>>),
103
104 /// The shapes are in proximity (close but not intersecting).
105 ///
106 /// # Fields
107 ///
108 /// - `Unit<Vector>`: An approximate separation axis (unit direction from shape 1 to shape 2)
109 ///
110 /// This variant is returned when `exact_dist` is `false` and the algorithm determines
111 /// the shapes are close but not intersecting. It's faster than computing exact closest
112 /// points when you only need to know if shapes are nearby.
113 Proximity(Unit<Vector<Real>>),
114
115 /// The shapes are too far apart.
116 ///
117 /// # Fields
118 ///
119 /// - `Unit<Vector>`: A separation axis (unit direction from shape 1 to shape 2)
120 ///
121 /// This variant is returned when the minimum distance between the shapes exceeds
122 /// the `max_dist` parameter passed to the GJK algorithm.
123 NoIntersection(Unit<Vector<Real>>),
124}
125
126/// The absolute tolerance used by the GJK algorithm.
127///
128/// This function returns the epsilon (tolerance) value that GJK uses to determine when
129/// it has converged to a solution. The tolerance affects:
130///
131/// - When two points are considered "close enough" to be the same
132/// - When the algorithm decides it has found the minimum distance
133/// - Numerical stability in edge cases
134///
135/// The returned value is 10 times the default machine epsilon for the current floating-point
136/// precision (f32 or f64). This provides a balance between accuracy and robustness.
137///
138/// # Returns
139///
140/// The absolute tolerance value (10 * DEFAULT_EPSILON)
141pub fn eps_tol() -> Real {
142 let _eps = crate::math::DEFAULT_EPSILON;
143 _eps * 10.0
144}
145
146/// Projects the origin onto the boundary of the given shape.
147///
148/// This function finds the point on the shape's surface that is closest to the origin (0, 0)
149/// in 2D or (0, 0, 0) in 3D. This is useful for distance queries and collision detection
150/// when you need to know the closest point on a shape.
151///
152/// # Important: Origin Must Be Outside
153///
154/// **The origin is assumed to be outside of the shape.** If the origin is inside the shape,
155/// this function returns `None`. For penetrating cases, use the EPA (Expanding Polytope Algorithm)
156/// instead.
157///
158/// # Parameters
159///
160/// - `m`: The position and orientation (isometry) of the shape in world space
161/// - `g`: The shape to project onto (must implement `SupportMap`)
162/// - `simplex`: A reusable simplex structure for the GJK algorithm. Initialize with
163/// `VoronoiSimplex::new()` before first use.
164///
165/// # Returns
166///
167/// - `Some(Point)`: The closest point on the shape's boundary, in the shape's **local space**
168/// - `None`: If the origin is inside the shape
169///
170/// # Example
171///
172/// ```rust
173/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
174/// use parry3d::shape::Ball;
175/// use parry3d::query::gjk::{project_origin, VoronoiSimplex};
176/// use parry3d::math::Isometry;
177///
178/// // Create a ball at position (5, 0, 0)
179/// let ball = Ball::new(1.0);
180/// let position = Isometry::translation(5.0, 0.0, 0.0);
181///
182/// // Project the origin onto the ball
183/// let mut simplex = VoronoiSimplex::new();
184/// if let Some(closest_point) = project_origin(&position, &ball, &mut simplex) {
185/// println!("Closest point on ball: {:?}", closest_point);
186/// // The point will be approximately (-1, 0, 0) in local space
187/// // which is the left side of the ball facing the origin
188/// }
189/// # }
190/// ```
191///
192/// # Performance Note
193///
194/// The `simplex` parameter can be reused across multiple calls to avoid allocations.
195/// This is particularly beneficial when performing many projection queries.
196pub fn project_origin<G: ?Sized + SupportMap>(
197 m: &Isometry<Real>,
198 g: &G,
199 simplex: &mut VoronoiSimplex,
200) -> Option<Point<Real>> {
201 match closest_points(
202 &m.inverse(),
203 g,
204 &ConstantOrigin,
205 Real::max_value(),
206 true,
207 simplex,
208 ) {
209 GJKResult::Intersection => None,
210 GJKResult::ClosestPoints(p, _, _) => Some(p),
211 _ => unreachable!(),
212 }
213}
214
215/*
216 * Separating Axis GJK
217 */
218/// Computes the closest points between two shapes using the GJK algorithm.
219///
220/// This is the **core function** of the GJK implementation in Parry. It can compute:
221/// - Whether two shapes are intersecting
222/// - The distance between two separated shapes
223/// - The closest points on both shapes
224/// - An approximate separation axis when exact distance isn't needed
225///
226/// # How It Works
227///
228/// The algorithm operates on the Minkowski difference (CSO) of the two shapes and iteratively
229/// builds a simplex that approximates the point closest to the origin. The algorithm terminates
230/// when:
231/// - The shapes are proven to intersect (origin is inside the CSO)
232/// - The minimum distance is found within the tolerance
233/// - The shapes are proven to be farther than `max_dist` apart
234///
235/// # Parameters
236///
237/// - `pos12`: The relative position of shape 2 with respect to shape 1. This is the isometry
238/// that transforms from shape 1's space to shape 2's space.
239/// - `g1`: The first shape (must implement `SupportMap`)
240/// - `g2`: The second shape (must implement `SupportMap`)
241/// - `max_dist`: Maximum distance to check. If shapes are farther than this, the algorithm
242/// returns `GJKResult::NoIntersection` early. Use `Real::max_value()` to disable this check.
243/// - `exact_dist`: Whether to compute exact closest points:
244/// - `true`: Computes exact distance and returns `GJKResult::ClosestPoints`
245/// - `false`: May return `GJKResult::Proximity` with only an approximate separation axis,
246/// which is faster when you only need to know if shapes are nearby
247/// - `simplex`: A reusable simplex structure. Initialize with `VoronoiSimplex::new()` before
248/// first use. Can be reused across calls for better performance.
249///
250/// # Returns
251///
252/// Returns a [`GJKResult`] which can be:
253/// - `Intersection`: The shapes are overlapping
254/// - `ClosestPoints(p1, p2, normal)`: The closest points on each shape (when `exact_dist` is true)
255/// - `Proximity(axis)`: An approximate separation axis (when `exact_dist` is false)
256/// - `NoIntersection(axis)`: The shapes are farther than `max_dist` apart
257///
258/// # Example: Basic Distance Query
259///
260/// ```rust
261/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
262/// use parry3d::shape::{Ball, Cuboid};
263/// use parry3d::query::gjk::{closest_points, VoronoiSimplex};
264/// use parry3d::math::{Isometry, Vector};
265///
266/// // Create two shapes
267/// let ball = Ball::new(1.0);
268/// let cuboid = Cuboid::new(Vector::new(1.0, 1.0, 1.0));
269///
270/// // Position them in space
271/// let pos1 = Isometry::translation(0.0, 0.0, 0.0);
272/// let pos2 = Isometry::translation(5.0, 0.0, 0.0);
273///
274/// // Compute relative position
275/// let pos12 = pos1.inv_mul(&pos2);
276///
277/// // Run GJK
278/// let mut simplex = VoronoiSimplex::new();
279/// let result = closest_points(
280/// &pos12,
281/// &ball,
282/// &cuboid,
283/// f32::MAX, // No distance limit
284/// true, // Compute exact distance
285/// &mut simplex,
286/// );
287///
288/// match result {
289/// parry3d::query::gjk::GJKResult::ClosestPoints(p1, p2, normal) => {
290/// println!("Closest point on ball: {:?}", p1);
291/// println!("Closest point on cuboid: {:?}", p2);
292/// println!("Separation direction: {:?}", normal);
293/// let distance = (p2 - p1).norm();
294/// println!("Distance: {}", distance);
295/// }
296/// parry3d::query::gjk::GJKResult::Intersection => {
297/// println!("Shapes are intersecting!");
298/// }
299/// _ => {}
300/// }
301/// # }
302/// ```
303///
304/// # Example: Fast Proximity Check
305///
306/// ```rust
307/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
308/// use parry3d::shape::Ball;
309/// use parry3d::query::gjk::{closest_points, VoronoiSimplex};
310/// use parry3d::math::Isometry;
311///
312/// let ball1 = Ball::new(1.0);
313/// let ball2 = Ball::new(1.0);
314/// let pos12 = Isometry::translation(3.0, 0.0, 0.0);
315///
316/// let mut simplex = VoronoiSimplex::new();
317/// let result = closest_points(
318/// &pos12,
319/// &ball1,
320/// &ball2,
321/// 5.0, // Only check up to distance 5.0
322/// false, // Don't compute exact distance
323/// &mut simplex,
324/// );
325///
326/// match result {
327/// parry3d::query::gjk::GJKResult::Proximity(_axis) => {
328/// println!("Shapes are close but not intersecting");
329/// }
330/// parry3d::query::gjk::GJKResult::Intersection => {
331/// println!("Shapes are intersecting");
332/// }
333/// parry3d::query::gjk::GJKResult::NoIntersection(_) => {
334/// println!("Shapes are too far apart (> 5.0 units)");
335/// }
336/// _ => {}
337/// }
338/// # }
339/// ```
340///
341/// # Performance Tips
342///
343/// 1. Reuse the `simplex` across multiple queries to avoid allocations
344/// 2. Set `exact_dist` to `false` when you only need proximity information
345/// 3. Use a reasonable `max_dist` to allow early termination
346/// 4. GJK converges fastest when shapes are well-separated
347///
348/// # Notes
349///
350/// - All returned points and vectors are in the local-space of shape 1
351/// - The algorithm typically converges in 5-10 iterations for well-separated shapes
352/// - Maximum iteration count is 100 to prevent infinite loops
353pub fn closest_points<G1, G2>(
354 pos12: &Isometry<Real>,
355 g1: &G1,
356 g2: &G2,
357 max_dist: Real,
358 exact_dist: bool,
359 simplex: &mut VoronoiSimplex,
360) -> GJKResult
361where
362 G1: ?Sized + SupportMap,
363 G2: ?Sized + SupportMap,
364{
365 let _eps = crate::math::DEFAULT_EPSILON;
366 let _eps_tol: Real = eps_tol();
367 let _eps_rel: Real = ComplexField::sqrt(_eps_tol);
368
369 // TODO: reset the simplex if it is empty?
370 let mut proj = simplex.project_origin_and_reduce();
371
372 let mut old_dir;
373
374 if let Some(proj_dir) = Unit::try_new(proj.coords, 0.0) {
375 old_dir = -proj_dir;
376 } else {
377 return GJKResult::Intersection;
378 }
379
380 let mut max_bound = Real::max_value();
381 let mut dir;
382 let mut niter = 0;
383
384 loop {
385 let old_max_bound = max_bound;
386
387 if let Some((new_dir, dist)) = Unit::try_new_and_get(-proj.coords, _eps_tol) {
388 dir = new_dir;
389 max_bound = dist;
390 } else {
391 // The origin is on the simplex.
392 return GJKResult::Intersection;
393 }
394
395 if max_bound >= old_max_bound {
396 if exact_dist {
397 let (p1, p2) = result(simplex, true);
398 return GJKResult::ClosestPoints(p1, p2, old_dir); // upper bounds inconsistencies
399 } else {
400 return GJKResult::Proximity(old_dir);
401 }
402 }
403
404 let cso_point = CSOPoint::from_shapes(pos12, g1, g2, &dir);
405 let min_bound = -dir.dot(&cso_point.point.coords);
406
407 assert!(min_bound.is_finite());
408
409 if min_bound > max_dist {
410 return GJKResult::NoIntersection(dir);
411 } else if !exact_dist && min_bound > 0.0 && max_bound <= max_dist {
412 return GJKResult::Proximity(old_dir);
413 } else if max_bound - min_bound <= _eps_rel * max_bound {
414 if exact_dist {
415 let (p1, p2) = result(simplex, false);
416 return GJKResult::ClosestPoints(p1, p2, dir); // the distance found has a good enough precision
417 } else {
418 return GJKResult::Proximity(dir);
419 }
420 }
421
422 if !simplex.add_point(cso_point) {
423 if exact_dist {
424 let (p1, p2) = result(simplex, false);
425 return GJKResult::ClosestPoints(p1, p2, dir);
426 } else {
427 return GJKResult::Proximity(dir);
428 }
429 }
430
431 old_dir = dir;
432 proj = simplex.project_origin_and_reduce();
433
434 if simplex.dimension() == DIM {
435 if min_bound >= _eps_tol {
436 if exact_dist {
437 let (p1, p2) = result(simplex, true);
438 return GJKResult::ClosestPoints(p1, p2, old_dir);
439 } else {
440 // NOTE: previous implementation used old_proj here.
441 return GJKResult::Proximity(old_dir);
442 }
443 } else {
444 return GJKResult::Intersection; // Point inside of the cso.
445 }
446 }
447 niter += 1;
448
449 if niter == 100 {
450 return GJKResult::NoIntersection(Vector::x_axis());
451 }
452 }
453}
454
455/// Casts a ray against a shape using the GJK algorithm.
456///
457/// This function performs raycasting by testing a ray against a shape to find if and where
458/// the ray intersects the shape. It uses a specialized version of GJK that works with rays.
459///
460/// # What is Raycasting?
461///
462/// Raycasting shoots a ray (infinite line starting from a point in a direction) and finds
463/// where it first hits a shape. This is essential for:
464/// - Mouse picking in 3D scenes
465/// - Line-of-sight checks
466/// - Projectile collision detection
467/// - Laser/scanner simulations
468///
469/// # Parameters
470///
471/// - `shape`: The shape to cast the ray against (must implement `SupportMap`)
472/// - `simplex`: A reusable simplex structure. Initialize with `VoronoiSimplex::new()`.
473/// - `ray`: The ray to cast, containing an origin point and direction vector
474/// - `max_time_of_impact`: Maximum distance along the ray to check. The ray will be treated
475/// as a line segment of length `max_time_of_impact * ray.dir.norm()`.
476///
477/// # Returns
478///
479/// - `Some((toi, normal))`: If the ray hits the shape
480/// - `toi`: Time of impact - multiply by `ray.dir.norm()` to get the actual distance
481/// - `normal`: Surface normal at the hit point
482/// - `None`: If the ray doesn't hit the shape within the maximum distance
483///
484/// # Example
485///
486/// ```rust
487/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
488/// use parry3d::shape::Ball;
489/// use parry3d::query::{Ray, gjk::{cast_local_ray, VoronoiSimplex}};
490/// use parry3d::math::{Point, Vector};
491///
492/// // Create a ball at the origin
493/// let ball = Ball::new(1.0);
494///
495/// // Create a ray starting at (0, 0, -5) pointing toward +Z
496/// let ray = Ray::new(
497/// Point::new(0.0, 0.0, -5.0),
498/// Vector::new(0.0, 0.0, 1.0)
499/// );
500///
501/// let mut simplex = VoronoiSimplex::new();
502/// if let Some((toi, normal)) = cast_local_ray(&ball, &mut simplex, &ray, f32::MAX) {
503/// let hit_point = ray.point_at(toi);
504/// println!("Ray hit at: {:?}", hit_point);
505/// println!("Surface normal: {:?}", normal);
506/// println!("Distance: {}", toi);
507/// } else {
508/// println!("Ray missed the shape");
509/// }
510/// # }
511/// ```
512///
513/// # Notes
514///
515/// - The ray is specified in the local-space of the shape
516/// - The returned normal points outward from the shape
517/// - For normalized ray directions, `toi` equals the distance to the hit point
518/// - This function is typically called by higher-level raycasting APIs
519pub fn cast_local_ray<G: ?Sized + SupportMap>(
520 shape: &G,
521 simplex: &mut VoronoiSimplex,
522 ray: &Ray,
523 max_time_of_impact: Real,
524) -> Option<(Real, Vector<Real>)> {
525 let g2 = ConstantOrigin;
526 minkowski_ray_cast(
527 &Isometry::identity(),
528 shape,
529 &g2,
530 ray,
531 max_time_of_impact,
532 simplex,
533 )
534}
535
536/// Computes how far a shape can move in a direction before touching another shape.
537///
538/// This function answers the question: "If I move shape 1 along this direction, how far
539/// can it travel before it touches shape 2?" This is useful for:
540/// - Continuous collision detection (CCD)
541/// - Movement planning and obstacle avoidance
542/// - Computing time-of-impact for moving objects
543/// - Safe navigation distances
544///
545/// # How It Works
546///
547/// The function casts shape 1 along the given direction vector and finds the first point
548/// where it would contact shape 2. It returns:
549/// - The distance that can be traveled
550/// - The contact normal at the point of first contact
551/// - The witness points (closest points) on both shapes at contact
552///
553/// # Parameters
554///
555/// - `pos12`: The relative position of shape 2 with respect to shape 1 (isometry from
556/// shape 1's space to shape 2's space)
557/// - `g1`: The first shape being moved (must implement `SupportMap`)
558/// - `g2`: The second shape (static target, must implement `SupportMap`)
559/// - `dir`: The direction vector to move shape 1 in (in local-space of shape 1)
560/// - `simplex`: A reusable simplex structure. Initialize with `VoronoiSimplex::new()`.
561///
562/// # Returns
563///
564/// - `Some((distance, normal, witness1, witness2))`: If contact would occur
565/// - `distance`: How far shape 1 can travel before touching shape 2
566/// - `normal`: The contact normal at the point of first contact
567/// - `witness1`: The contact point on shape 1 (in local-space of shape 1)
568/// - `witness2`: The contact point on shape 2 (in local-space of shape 1)
569/// - `None`: If no contact would occur (shapes don't intersect along this direction)
570///
571/// # Example
572///
573/// ```rust
574/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
575/// use parry3d::shape::Ball;
576/// use parry3d::query::gjk::{directional_distance, VoronoiSimplex};
577/// use parry3d::math::{Isometry, Vector};
578///
579/// // Two balls: one at origin, one at (10, 0, 0)
580/// let ball1 = Ball::new(1.0);
581/// let ball2 = Ball::new(1.0);
582/// let pos12 = Isometry::translation(10.0, 0.0, 0.0);
583///
584/// // Move ball1 toward ball2 along the +X axis
585/// let direction = Vector::new(1.0, 0.0, 0.0);
586///
587/// let mut simplex = VoronoiSimplex::new();
588/// if let Some((distance, normal, w1, w2)) = directional_distance(
589/// &pos12,
590/// &ball1,
591/// &ball2,
592/// &direction,
593/// &mut simplex
594/// ) {
595/// println!("Ball1 can move {} units before contact", distance);
596/// println!("Contact normal: {:?}", normal);
597/// println!("Contact point on ball1: {:?}", w1);
598/// println!("Contact point on ball2: {:?}", w2);
599/// // Expected: distance ≈ 8.0 (10.0 - 1.0 - 1.0)
600/// }
601/// # }
602/// ```
603///
604/// # Use Cases
605///
606/// **1. Continuous Collision Detection:**
607/// ```ignore
608/// let movement_dir = velocity * time_step;
609/// if let Some((toi, normal, _, _)) = directional_distance(...) {
610/// if toi < 1.0 {
611/// // Collision will occur during this timestep
612/// let collision_time = toi * time_step;
613/// }
614/// }
615/// ```
616///
617/// **2. Safe Movement Distance:**
618/// ```ignore
619/// let desired_movement = Vector::new(5.0, 0.0, 0.0);
620/// if let Some((max_safe_dist, _, _, _)) = directional_distance(...) {
621/// let actual_movement = desired_movement.normalize() * max_safe_dist.min(5.0);
622/// }
623/// ```
624///
625/// # Notes
626///
627/// - All inputs and outputs are in the local-space of shape 1
628/// - If the shapes are already intersecting, the returned distance is 0.0 and witness
629/// points are undefined (set to origin)
630/// - The direction vector does not need to be normalized
631/// - This function internally uses GJK raycasting on the Minkowski difference
632pub fn directional_distance<G1, G2>(
633 pos12: &Isometry<Real>,
634 g1: &G1,
635 g2: &G2,
636 dir: &Vector<Real>,
637 simplex: &mut VoronoiSimplex,
638) -> Option<(Real, Vector<Real>, Point<Real>, Point<Real>)>
639where
640 G1: ?Sized + SupportMap,
641 G2: ?Sized + SupportMap,
642{
643 let ray = Ray::new(Point::origin(), *dir);
644 minkowski_ray_cast(pos12, g1, g2, &ray, Real::max_value(), simplex).map(
645 |(time_of_impact, normal)| {
646 let witnesses = if !time_of_impact.is_zero() {
647 result(simplex, simplex.dimension() == DIM)
648 } else {
649 // If there is penetration, the witness points
650 // are undefined.
651 (Point::origin(), Point::origin())
652 };
653
654 (time_of_impact, normal, witnesses.0, witnesses.1)
655 },
656 )
657}
658
659// Ray-cast on the Minkowski Difference `g1 - pos12 * g2`.
660fn minkowski_ray_cast<G1, G2>(
661 pos12: &Isometry<Real>,
662 g1: &G1,
663 g2: &G2,
664 ray: &Ray,
665 max_time_of_impact: Real,
666 simplex: &mut VoronoiSimplex,
667) -> Option<(Real, Vector<Real>)>
668where
669 G1: ?Sized + SupportMap,
670 G2: ?Sized + SupportMap,
671{
672 let _eps = crate::math::DEFAULT_EPSILON;
673 let _eps_tol: Real = eps_tol();
674 let _eps_rel: Real = ComplexField::sqrt(_eps_tol);
675
676 let ray_length = ray.dir.norm();
677
678 if relative_eq!(ray_length, 0.0) {
679 return None;
680 }
681
682 let mut ltoi = 0.0;
683 let mut curr_ray = Ray::new(ray.origin, ray.dir / ray_length);
684 let dir = -curr_ray.dir;
685 let mut ldir = dir;
686
687 // Initialize the simplex.
688 let support_point = CSOPoint::from_shapes(pos12, g1, g2, &dir);
689 simplex.reset(support_point.translate(&-curr_ray.origin.coords));
690
691 // TODO: reset the simplex if it is empty?
692 let mut proj = simplex.project_origin_and_reduce();
693 let mut max_bound = Real::max_value();
694 let mut dir;
695 let mut niter = 0;
696 let mut last_chance = false;
697
698 loop {
699 let old_max_bound = max_bound;
700
701 if let Some((new_dir, dist)) = Unit::try_new_and_get(-proj.coords, _eps_tol) {
702 dir = new_dir;
703 max_bound = dist;
704 } else {
705 return Some((ltoi / ray_length, ldir));
706 }
707
708 let support_point = if max_bound >= old_max_bound {
709 // Upper bounds inconsistencies. Consider the projection as a valid support point.
710 last_chance = true;
711 CSOPoint::single_point(proj + curr_ray.origin.coords)
712 } else {
713 CSOPoint::from_shapes(pos12, g1, g2, &dir)
714 };
715
716 if last_chance && ltoi > 0.0 {
717 // last_chance && ltoi > 0.0 && (support_point.point - curr_ray.origin).dot(&ldir) >= 0.0 {
718 return Some((ltoi / ray_length, ldir));
719 }
720
721 // Clip the ray on the support halfspace (None <=> t < 0)
722 // The configurations are:
723 // dir.dot(curr_ray.dir) | t | Action
724 // −−−−−−−−−−−−−−−−−−−−-----+−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
725 // < 0 | < 0 | Continue.
726 // < 0 | > 0 | New lower bound, move the origin.
727 // > 0 | < 0 | Miss. No intersection.
728 // > 0 | > 0 | New higher bound.
729 match query::details::ray_toi_with_halfspace(&support_point.point, &dir, &curr_ray) {
730 Some(t) => {
731 if dir.dot(&curr_ray.dir) < 0.0 && t > 0.0 {
732 // new lower bound
733 ldir = *dir;
734 ltoi += t;
735
736 // NOTE: we divide by ray_length instead of doing max_time_of_impact * ray_length
737 // because the multiplication may cause an overflow if max_time_of_impact is set
738 // to Real::max_value() by users that want to have an infinite ray.
739 if ltoi / ray_length > max_time_of_impact {
740 return None;
741 }
742
743 let shift = curr_ray.dir * t;
744 curr_ray.origin += shift;
745 max_bound = Real::max_value();
746 simplex.modify_pnts(&|pt| pt.translate_mut(&-shift));
747 last_chance = false;
748 }
749 }
750 None => {
751 if dir.dot(&curr_ray.dir) > _eps_tol {
752 // miss
753 return None;
754 }
755 }
756 }
757
758 if last_chance {
759 return None;
760 }
761
762 let min_bound = -dir.dot(&(support_point.point.coords - curr_ray.origin.coords));
763
764 assert!(min_bound.is_finite());
765
766 if max_bound - min_bound <= _eps_rel * max_bound {
767 // This is needed when using fixed-points to avoid missing
768 // some castes.
769 // TODO: I feel like we should always return `Some` in
770 // this case, even with floating-point numbers. Though it
771 // has not been sufficiently tested with floats yet to be sure.
772 if cfg!(feature = "improved_fixed_point_support") {
773 return Some((ltoi / ray_length, ldir));
774 } else {
775 return None;
776 }
777 }
778
779 let _ = simplex.add_point(support_point.translate(&-curr_ray.origin.coords));
780 proj = simplex.project_origin_and_reduce();
781
782 if simplex.dimension() == DIM {
783 if min_bound >= _eps_tol {
784 return None;
785 } else {
786 return Some((ltoi / ray_length, ldir)); // Point inside of the cso.
787 }
788 }
789
790 niter += 1;
791 if niter == 100 {
792 return None;
793 }
794 }
795}
796
797fn result(simplex: &VoronoiSimplex, prev: bool) -> (Point<Real>, Point<Real>) {
798 let mut res = (Point::origin(), Point::origin());
799 if prev {
800 for i in 0..simplex.prev_dimension() + 1 {
801 let coord = simplex.prev_proj_coord(i);
802 let point = simplex.prev_point(i);
803 res.0 += point.orig1.coords * coord;
804 res.1 += point.orig2.coords * coord;
805 }
806
807 res
808 } else {
809 for i in 0..simplex.dimension() + 1 {
810 let coord = simplex.proj_coord(i);
811 let point = simplex.point(i);
812 res.0 += point.orig1.coords * coord;
813 res.1 += point.orig2.coords * coord;
814 }
815
816 res
817 }
818}