parry3d_f64/shape/
support_map.rs

1//! Traits for support mapping based shapes.
2//!
3//! # What is a Support Map?
4//!
5//! A **support map** (or support function) is a fundamental concept in computational geometry that
6//! describes a convex shape by answering a simple question: "What point on the shape is furthest
7//! in a given direction?"
8//!
9//! More formally, for a convex shape `S` and a direction vector `d`, the support function returns:
10//!
11//! ```text
12//! support(S, d) = argmax { p · d : p ∈ S }
13//! ```
14//!
15//! Where `p · d` is the dot product between a point `p` on the shape and the direction `d`.
16//!
17//! ## Visual Intuition
18//!
19//! Imagine shining a light from infinity in direction `d` onto your shape. The support point
20//! is where the "shadow" boundary would be - the point that sticks out furthest in that direction.
21//!
22//! For example, for a circle centered at the origin with radius `r`:
23//! - If `d = (1, 0)` (pointing right), the support point is `(r, 0)` (rightmost point)
24//! - If `d = (0, 1)` (pointing up), the support point is `(0, r)` (topmost point)
25//! - If `d = (1, 1)` (diagonal), the support point is `(r/√2, r/√2)` (northeast point)
26//!
27//! ## Why Support Maps Matter for Collision Detection
28//!
29//! Support maps are the foundation of two powerful collision detection algorithms:
30//!
31//! ### 1. GJK Algorithm (Gilbert-Johnson-Keerthi)
32//!
33//! GJK is an iterative algorithm that determines:
34//! - Whether two convex shapes intersect
35//! - The distance between two separated shapes
36//! - The closest points between two shapes
37//!
38//! GJK works by computing the **Minkowski difference** of two shapes using only their support
39//! functions. It builds a simplex (a simple polytope) that converges toward the origin, allowing
40//! it to answer collision queries without ever explicitly computing the shapes' geometry.
41//!
42//! **Key advantage**: GJK only needs the support function - it never needs to know the actual
43//! vertices, faces, or internal structure of the shapes. This makes it incredibly flexible and
44//! efficient.
45//!
46//! ### 2. EPA Algorithm (Expanding Polytope Algorithm)
47//!
48//! EPA is used when two shapes are penetrating (overlapping). It computes:
49//! - The penetration depth (how much they overlap)
50//! - The penetration normal (the direction to separate them)
51//! - Contact points for physics simulation
52//!
53//! EPA starts with the final simplex from GJK and expands it into a polytope that approximates
54//! the Minkowski difference, converging toward the shallowest penetration.
55//!
56//! ## Why Support Maps are Efficient
57//!
58//! 1. **Simple to implement**: For most convex shapes, the support function is straightforward
59//! 2. **No geometry storage**: Implicit shapes (like spheres, capsules) don't need vertex data
60//! 3. **Transform-friendly**: Easy to handle rotations and translations
61//! 4. **Composable**: Can combine support functions for compound shapes
62//! 5. **Fast queries**: Often just a few dot products and comparisons
63//!
64//! ## Examples of Support Functions
65//!
66//! Here are some common shapes and their support functions:
67//!
68//! ### Sphere/Ball
69//! ```text
70//! support(sphere, d) = center + normalize(d) * radius
71//! ```
72//!
73//! ### Cuboid (Box)
74//! ```text
75//! support(box, d) = (sign(d.x) * half_width,
76//!                    sign(d.y) * half_height,
77//!                    sign(d.z) * half_depth)
78//! ```
79//!
80//! ### Convex Polygon/Polyhedron
81//! ```text
82//! support(poly, d) = vertex with maximum dot product with d
83//! ```
84//!
85//! ## Limitations
86//!
87//! Support maps only work for **convex** shapes. Concave shapes must be decomposed into
88//! convex parts or handled with different algorithms. This is why Parry provides composite
89//! shapes and specialized algorithms for triangle meshes.
90
91use crate::math::{Isometry, Point, Real, Vector};
92use na::Unit;
93
94/// Trait for convex shapes representable by a support mapping function.
95///
96/// A support map is a function that returns the point on a shape that is furthest in a given
97/// direction. This is the fundamental building block for collision detection algorithms like
98/// GJK (Gilbert-Johnson-Keerthi) and EPA (Expanding Polytope Algorithm).
99///
100/// # What You Need to Know
101///
102/// If you're implementing this trait for a custom shape, you only need to implement
103/// [`local_support_point`](SupportMap::local_support_point). The other methods have default
104/// implementations that handle transformations and normalized directions.
105///
106/// # Requirements
107///
108/// - The shape **must be convex**. Non-convex shapes will produce incorrect results.
109/// - The support function should return a point on the surface of the shape (or inside it,
110///   but surface points are preferred for better accuracy).
111/// - For a given direction `d`, the returned point `p` should maximize `p · d` (dot product).
112///
113/// # Examples
114///
115/// ## Using Support Maps for Distance Queries
116///
117/// ```rust
118/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
119/// use parry3d::shape::{Ball, Cuboid, SupportMap};
120/// use parry3d::math::{Point, Vector, Real};
121/// extern crate nalgebra as na;
122/// use parry3d::na::Vector3;
123///
124/// // Create a ball (sphere) with radius 1.0
125/// let ball = Ball::new(1.0);
126///
127/// // Get the support point in the direction (1, 0, 0) - pointing right
128/// let dir = Vector3::new(1.0, 0.0, 0.0);
129/// let support_point = ball.local_support_point(&dir);
130///
131/// // For a ball centered at origin, this should be approximately (1, 0, 0)
132/// assert!((support_point.x - 1.0).abs() < 1e-6);
133/// assert!(support_point.y.abs() < 1e-6);
134/// assert!(support_point.z.abs() < 1e-6);
135///
136/// // Try another direction - diagonal up and right
137/// let dir2 = Vector3::new(1.0, 1.0, 0.0);
138/// let support_point2 = ball.local_support_point(&dir2);
139///
140/// // The point should be on the surface of the ball (distance = radius)
141/// let distance = (support_point2.coords.norm() - 1.0).abs();
142/// assert!(distance < 1e-6);
143/// # }
144/// ```
145///
146/// ## Support Points on a Cuboid
147///
148/// ```rust
149/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
150/// use parry3d::shape::{Cuboid, SupportMap};
151/// extern crate nalgebra as na;
152/// use parry3d::na::Vector3;
153///
154/// // Create a cuboid (box) with half-extents 2x3x4
155/// let cuboid = Cuboid::new(Vector3::new(2.0, 3.0, 4.0));
156///
157/// // Support point in positive X direction should be at the right face
158/// let dir_x = Vector3::new(1.0, 0.0, 0.0);
159/// let support_x = cuboid.local_support_point(&dir_x);
160/// assert!((support_x.x - 2.0).abs() < 1e-6);
161///
162/// // Support point in negative Y direction should be at the bottom face
163/// let dir_neg_y = Vector3::new(0.0, -1.0, 0.0);
164/// let support_neg_y = cuboid.local_support_point(&dir_neg_y);
165/// assert!((support_neg_y.y + 3.0).abs() < 1e-6);
166///
167/// // Support point in diagonal direction should be at a corner
168/// let dir_diag = Vector3::new(1.0, 1.0, 1.0);
169/// let support_diag = cuboid.local_support_point(&dir_diag);
170/// assert!((support_diag.x - 2.0).abs() < 1e-6);
171/// assert!((support_diag.y - 3.0).abs() < 1e-6);
172/// assert!((support_diag.z - 4.0).abs() < 1e-6);
173/// # }
174/// ```
175///
176/// ## Implementing SupportMap for a Custom Shape
177///
178/// Here's how you might implement `SupportMap` for a simple custom shape:
179///
180/// ```rust
181/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
182/// # // Note: This example shows the concept but won't actually compile in doc tests
183/// # // since we can't implement traits for external types in doc tests.
184/// # // It's here for educational purposes.
185/// use parry3d::shape::SupportMap;
186/// use parry3d::math::{Point, Vector, Real};
187/// extern crate nalgebra as na;
188/// use parry3d::na::Vector3;
189///
190/// // A simple pill-shaped object aligned with the X axis
191/// struct SimplePill {
192///     half_length: Real,  // Half the length of the cylindrical part
193///     radius: Real,       // Radius of the spherical ends
194/// }
195///
196/// impl SupportMap for SimplePill {
197///     fn local_support_point(&self, dir: &Vector<Real>) -> Point<Real> {
198///         // Support point is on one of the spherical ends
199///         // Choose the end that's in the direction of dir.x
200///         let center_x = if dir.x >= 0.0 { self.half_length } else { -self.half_length };
201///
202///         // From that center, extend by radius in the direction of dir
203///         let dir_normalized = dir.normalize();
204///         Point::new(
205///             center_x + dir_normalized.x * self.radius,
206///             dir_normalized.y * self.radius,
207///             dir_normalized.z * self.radius,
208///         )
209///     }
210/// }
211/// # }
212/// ```
213pub trait SupportMap {
214    /// Evaluates the support function of this shape in local space.
215    ///
216    /// The support function returns the point on the shape's surface (or interior) that is
217    /// furthest in the given direction `dir`. More precisely, it finds the point `p` that
218    /// maximizes the dot product `p · dir`.
219    ///
220    /// # Parameters
221    ///
222    /// - `dir`: The direction vector to query. Does not need to be normalized (unit length),
223    ///   but the result may be more intuitive with normalized directions.
224    ///
225    /// # Returns
226    ///
227    /// A point on (or inside) the shape that is furthest in the direction `dir`.
228    ///
229    /// # Implementation Notes
230    ///
231    /// When implementing this method:
232    /// - The direction `dir` may have any length (including zero, though this is unusual)
233    /// - For better numerical stability, consider normalizing `dir` if needed
234    /// - The returned point should be in the shape's local coordinate system
235    /// - If `dir` is zero or very small, a reasonable point (like the center) should be returned
236    ///
237    /// # Examples
238    ///
239    /// ```
240    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
241    /// use parry3d::shape::{Ball, SupportMap};
242    /// extern crate nalgebra as na;
243    /// use parry3d::na::Vector3;
244    ///
245    /// let ball = Ball::new(2.5);
246    ///
247    /// // Support point pointing up (Z direction)
248    /// let up = Vector3::new(0.0, 0.0, 1.0);
249    /// let support_up = ball.local_support_point(&up);
250    ///
251    /// // Should be at the top of the ball
252    /// assert!((support_up.z - 2.5).abs() < 1e-6);
253    /// assert!(support_up.x.abs() < 1e-6);
254    /// assert!(support_up.y.abs() < 1e-6);
255    ///
256    /// // Support point pointing in negative X direction
257    /// let left = Vector3::new(-1.0, 0.0, 0.0);
258    /// let support_left = ball.local_support_point(&left);
259    ///
260    /// // Should be at the left side of the ball
261    /// assert!((support_left.x + 2.5).abs() < 1e-6);
262    /// # }
263    /// ```
264    ///
265    /// ## Why "local" support point?
266    ///
267    /// The "local" prefix means the point is in the shape's own coordinate system, before
268    /// any rotation or translation is applied. For transformed shapes, use
269    /// [`support_point`](SupportMap::support_point) instead.
270    fn local_support_point(&self, dir: &Vector<Real>) -> Point<Real>;
271
272    /// Same as [`local_support_point`](SupportMap::local_support_point) except that `dir` is
273    /// guaranteed to be normalized (unit length).
274    ///
275    /// This can be more efficient for some shapes that can take advantage of the normalized
276    /// direction vector. By default, this just forwards to `local_support_point`.
277    ///
278    /// # Parameters
279    ///
280    /// - `dir`: A unit-length direction vector (guaranteed by the type system)
281    ///
282    /// # Examples
283    ///
284    /// ```
285    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
286    /// use parry3d::shape::{Ball, SupportMap};
287    /// extern crate nalgebra as na;
288    /// use parry3d::na::{Vector3, Unit};
289    ///
290    /// let ball = Ball::new(1.5);
291    ///
292    /// // Create a normalized direction vector
293    /// let dir = Unit::new_normalize(Vector3::new(1.0, 1.0, 0.0));
294    ///
295    /// let support = ball.local_support_point_toward(&dir);
296    ///
297    /// // The support point should be on the sphere's surface
298    /// let distance_from_origin = support.coords.norm();
299    /// assert!((distance_from_origin - 1.5).abs() < 1e-6);
300    /// # }
301    /// ```
302    fn local_support_point_toward(&self, dir: &Unit<Vector<Real>>) -> Point<Real> {
303        self.local_support_point(dir.as_ref())
304    }
305
306    /// Evaluates the support function of this shape transformed by `transform`.
307    ///
308    /// This is the world-space version of [`local_support_point`](SupportMap::local_support_point).
309    /// It computes the support point for a shape that has been rotated and translated by the
310    /// given isometry (rigid transformation).
311    ///
312    /// # How it Works
313    ///
314    /// The algorithm:
315    /// 1. Transform the direction vector `dir` from world space to the shape's local space
316    /// 2. Compute the support point in local space
317    /// 3. Transform the support point from local space back to world space
318    ///
319    /// This is much more efficient than actually transforming the shape's geometry.
320    ///
321    /// # Parameters
322    ///
323    /// - `transform`: The rigid transformation (rotation + translation) applied to the shape
324    /// - `dir`: The direction vector in world space
325    ///
326    /// # Returns
327    ///
328    /// A point in world space that is the furthest point on the transformed shape in direction `dir`.
329    ///
330    /// # Examples
331    ///
332    /// ```
333    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
334    /// use parry3d::shape::{Ball, SupportMap};
335    /// use parry3d::math::Isometry;
336    /// extern crate nalgebra as na;
337    /// use parry3d::na::{Vector3, Translation3, UnitQuaternion};
338    ///
339    /// let ball = Ball::new(1.0);
340    ///
341    /// // Create a transformation: translate the ball to (10, 0, 0)
342    /// let transform = Isometry::translation(10.0, 0.0, 0.0);
343    ///
344    /// // Get support point in the positive X direction
345    /// let dir = Vector3::new(1.0, 0.0, 0.0);
346    /// let support = ball.support_point(&transform, &dir);
347    ///
348    /// // The support point should be at (11, 0, 0) - the rightmost point of the translated ball
349    /// assert!((support.x - 11.0).abs() < 1e-6);
350    /// assert!(support.y.abs() < 1e-6);
351    /// assert!(support.z.abs() < 1e-6);
352    /// # }
353    /// ```
354    ///
355    /// ## Example with Rotation
356    ///
357    /// ```
358    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
359    /// use parry3d::shape::{Cuboid, SupportMap};
360    /// use parry3d::math::Isometry;
361    /// extern crate nalgebra as na;
362    /// use parry3d::na::{Vector3, UnitQuaternion};
363    /// use std::f32::consts::PI;
364    ///
365    /// let cuboid = Cuboid::new(Vector3::new(2.0, 1.0, 1.0));
366    ///
367    /// // Rotate the cuboid 90 degrees around the Z axis
368    /// let rotation = UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.0);
369    /// let transform = Isometry::from_parts(Vector3::zeros().into(), rotation);
370    ///
371    /// // In world space, ask for support in the X direction
372    /// let dir = Vector3::new(1.0, 0.0, 0.0);
373    /// let support = cuboid.support_point(&transform, &dir);
374    ///
375    /// // After 90° rotation, the long axis (originally X) now points in Y direction
376    /// // So the support in X direction comes from the short axis
377    /// assert!(support.x.abs() <= 1.0 + 1e-5); // Should be around 1.0 (the short axis)
378    /// }
379    /// ```
380    fn support_point(&self, transform: &Isometry<Real>, dir: &Vector<Real>) -> Point<Real> {
381        let local_dir = transform.inverse_transform_vector(dir);
382        transform * self.local_support_point(&local_dir)
383    }
384
385    /// Same as [`support_point`](SupportMap::support_point) except that `dir` is guaranteed
386    /// to be normalized (unit length).
387    ///
388    /// This combines the benefits of both [`local_support_point_toward`](SupportMap::local_support_point_toward)
389    /// (normalized direction) and [`support_point`](SupportMap::support_point) (world-space transform).
390    ///
391    /// # Parameters
392    ///
393    /// - `transform`: The rigid transformation applied to the shape
394    /// - `dir`: A unit-length direction vector in world space
395    ///
396    /// # Examples
397    ///
398    /// ```
399    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
400    /// use parry3d::shape::{Ball, SupportMap};
401    /// use parry3d::math::Isometry;
402    /// extern crate nalgebra as na;
403    /// use parry3d::na::{Vector3, Unit};
404    ///
405    /// let ball = Ball::new(2.0);
406    ///
407    /// // Translate the ball
408    /// let transform = Isometry::translation(5.0, 3.0, -2.0);
409    ///
410    /// // Create a normalized direction
411    /// let dir = Unit::new_normalize(Vector3::new(1.0, 1.0, 1.0));
412    ///
413    /// let support = ball.support_point_toward(&transform, &dir);
414    ///
415    /// // The support point should be 2.0 units away from the center in the diagonal direction
416    /// let center = Vector3::new(5.0, 3.0, -2.0);
417    /// let offset = support.coords - center;
418    /// let distance = offset.norm();
419    /// assert!((distance - 2.0).abs() < 1e-6);
420    ///
421    /// // The offset should be parallel to the direction
422    /// let normalized_offset = offset.normalize();
423    /// assert!((normalized_offset.dot(&dir) - 1.0).abs() < 1e-6);
424    /// # }
425    /// ```
426    ///
427    /// ## Practical Use: GJK Algorithm
428    ///
429    /// This method is commonly used in the GJK algorithm, which needs to compute support points
430    /// for transformed shapes with normalized directions for better numerical stability:
431    ///
432    /// ```
433    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
434    /// use parry3d::shape::{Ball, Cuboid, SupportMap};
435    /// use parry3d::math::Isometry;
436    /// extern crate nalgebra as na;
437    /// use parry3d::na::{Vector3, Unit};
438    ///
439    /// // Two shapes at different positions
440    /// let ball = Ball::new(1.0);
441    /// let cuboid = Cuboid::new(Vector3::new(0.5, 0.5, 0.5));
442    ///
443    /// let ball_pos = Isometry::translation(0.0, 0.0, 0.0);
444    /// let cuboid_pos = Isometry::translation(3.0, 0.0, 0.0);
445    ///
446    /// // Direction from ball to cuboid
447    /// let dir = Unit::new_normalize(Vector3::new(1.0, 0.0, 0.0));
448    ///
449    /// // Get support points for the Minkowski difference (used in GJK)
450    /// let support_ball = ball.support_point_toward(&ball_pos, &dir);
451    /// let support_cuboid = cuboid.support_point_toward(&cuboid_pos, &-dir);
452    ///
453    /// // The Minkowski difference support point
454    /// let minkowski_support = support_ball - support_cuboid;
455    ///
456    /// println!("Support point for Minkowski difference: {:?}", minkowski_support);
457    /// }
458    /// ```
459    fn support_point_toward(
460        &self,
461        transform: &Isometry<Real>,
462        dir: &Unit<Vector<Real>>,
463    ) -> Point<Real> {
464        let local_dir = Unit::new_unchecked(transform.inverse_transform_vector(dir));
465        transform * self.local_support_point_toward(&local_dir)
466    }
467}