parry3d/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::{Pose, Vector};
92
93/// Trait for convex shapes representable by a support mapping function.
94///
95/// A support map is a function that returns the point on a shape that is furthest in a given
96/// direction. This is the fundamental building block for collision detection algorithms like
97/// GJK (Gilbert-Johnson-Keerthi) and EPA (Expanding Polytope Algorithm).
98///
99/// # What You Need to Know
100///
101/// If you're implementing this trait for a custom shape, you only need to implement
102/// [`local_support_point`](SupportMap::local_support_point). The other methods have default
103/// implementations that handle transformations and normalized directions.
104///
105/// # Requirements
106///
107/// - The shape **must be convex**. Non-convex shapes will produce incorrect results.
108/// - The support function should return a point on the surface of the shape (or inside it,
109///   but surface points are preferred for better accuracy).
110/// - For a given direction `d`, the returned point `p` should maximize `p · d` (dot product).
111///
112/// # Examples
113///
114/// ## Using Support Maps for Distance Queries
115///
116/// ```rust
117/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
118/// use parry3d::shape::{Ball, Cuboid, SupportMap};
119/// use parry3d::math::{Vector, Real};
120///
121/// // Create a ball (sphere) with radius 1.0
122/// let ball = Ball::new(1.0);
123///
124/// // Get the support point in the direction (1, 0, 0) - pointing right
125/// let dir = Vector::new(1.0, 0.0, 0.0);
126/// let support_point = ball.local_support_point(dir);
127///
128/// // For a ball centered at origin, this should be approximately (1, 0, 0)
129/// assert!((support_point.x - 1.0).abs() < 1e-6);
130/// assert!(support_point.y.abs() < 1e-6);
131/// assert!(support_point.z.abs() < 1e-6);
132///
133/// // Try another direction - diagonal up and right
134/// let dir2 = Vector::new(1.0, 1.0, 0.0);
135/// let support_point2 = ball.local_support_point(dir2);
136///
137/// // The point should be on the surface of the ball (distance = radius)
138/// let distance = (support_point2.length() - 1.0).abs();
139/// assert!(distance < 1e-6);
140/// # }
141/// ```
142///
143/// ## Support Vectors on a Cuboid
144///
145/// ```rust
146/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
147/// use parry3d::shape::{Cuboid, SupportMap};
148/// use parry3d::math::Vector;
149///
150/// // Create a cuboid (box) with half-extents 2x3x4
151/// let cuboid = Cuboid::new(Vector::new(2.0, 3.0, 4.0));
152///
153/// // Support point in positive X direction should be at the right face
154/// let dir_x = Vector::new(1.0, 0.0, 0.0);
155/// let support_x = cuboid.local_support_point(dir_x);
156/// assert!((support_x.x - 2.0).abs() < 1e-6);
157///
158/// // Support point in negative Y direction should be at the bottom face
159/// let dir_neg_y = Vector::new(0.0, -1.0, 0.0);
160/// let support_neg_y = cuboid.local_support_point(dir_neg_y);
161/// assert!((support_neg_y.y + 3.0).abs() < 1e-6);
162///
163/// // Support point in diagonal direction should be at a corner
164/// let dir_diag = Vector::new(1.0, 1.0, 1.0);
165/// let support_diag = cuboid.local_support_point(dir_diag);
166/// assert!((support_diag.x - 2.0).abs() < 1e-6);
167/// assert!((support_diag.y - 3.0).abs() < 1e-6);
168/// assert!((support_diag.z - 4.0).abs() < 1e-6);
169/// # }
170/// ```
171///
172/// ## Implementing SupportMap for a Custom Shape
173///
174/// Here's how you might implement `SupportMap` for a simple custom shape:
175///
176/// ```rust
177/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
178/// # // Note: This example shows the concept but won't actually compile in doc tests
179/// # // since we can't implement traits for external types in doc tests.
180/// # // It's here for educational purposes.
181/// use parry3d::shape::SupportMap;
182/// use parry3d::math::{Vector, Real};
183///
184/// // A simple pill-shaped object aligned with the X axis
185/// struct SimplePill {
186///     half_length: Real,  // Half the length of the cylindrical part
187///     radius: Real,       // Radius of the spherical ends
188/// }
189///
190/// impl SupportMap for SimplePill {
191///     fn local_support_point(&self, dir: Vector) -> Vector {
192///         // Support point is on one of the spherical ends
193///         // Choose the end that's in the direction of dir.x
194///         let center_x = if dir.x >= 0.0 { self.half_length } else { -self.half_length };
195///
196///         // From that center, extend by radius in the direction of dir
197///         let dir_normalized = dir.normalize();
198///         Vector::new(
199///             center_x + dir_normalized.x * self.radius,
200///             dir_normalized.y * self.radius,
201///             dir_normalized.z * self.radius,
202///         )
203///     }
204/// }
205/// # }
206/// ```
207pub trait SupportMap {
208    /// Evaluates the support function of this shape in local space.
209    ///
210    /// The support function returns the point on the shape's surface (or interior) that is
211    /// furthest in the given direction `dir`. More precisely, it finds the point `p` that
212    /// maximizes the dot product `p · dir`.
213    ///
214    /// # Parameters
215    ///
216    /// - `dir`: The direction vector to query. Does not need to be normalized (unit length),
217    ///   but the result may be more intuitive with normalized directions.
218    ///
219    /// # Returns
220    ///
221    /// A point on (or inside) the shape that is furthest in the direction `dir`.
222    ///
223    /// # Implementation Notes
224    ///
225    /// When implementing this method:
226    /// - The direction `dir` may have any length (including zero, though this is unusual)
227    /// - For better numerical stability, consider normalizing `dir` if needed
228    /// - The returned point should be in the shape's local coordinate system
229    /// - If `dir` is zero or very small, a reasonable point (like the center) should be returned
230    ///
231    /// # Examples
232    ///
233    /// ```
234    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
235    /// use parry3d::shape::{Ball, SupportMap};
236    /// use parry3d::math::Vector;
237    ///
238    /// let ball = Ball::new(2.5);
239    ///
240    /// // Support point pointing up (Z direction)
241    /// let up = Vector::new(0.0, 0.0, 1.0);
242    /// let support_up = ball.local_support_point(up);
243    ///
244    /// // Should be at the top of the ball
245    /// assert!((support_up.z - 2.5).abs() < 1e-6);
246    /// assert!(support_up.x.abs() < 1e-6);
247    /// assert!(support_up.y.abs() < 1e-6);
248    ///
249    /// // Support point pointing in negative X direction
250    /// let left = Vector::new(-1.0, 0.0, 0.0);
251    /// let support_left = ball.local_support_point(left);
252    ///
253    /// // Should be at the left side of the ball
254    /// assert!((support_left.x + 2.5).abs() < 1e-6);
255    /// # }
256    /// ```
257    ///
258    /// ## Why "local" support point?
259    ///
260    /// The "local" prefix means the point is in the shape's own coordinate system, before
261    /// any rotation or translation is applied. For transformed shapes, use
262    /// [`support_point`](SupportMap::support_point) instead.
263    fn local_support_point(&self, dir: Vector) -> Vector;
264
265    /// Same as [`local_support_point`](SupportMap::local_support_point) except that `dir` is
266    /// guaranteed to be normalized (unit length).
267    ///
268    /// This can be more efficient for some shapes that can take advantage of the normalized
269    /// direction vector. By default, this just forwards to `local_support_point`.
270    ///
271    /// # Parameters
272    ///
273    /// - `dir`: A unit-length direction vector (guaranteed by the type system)
274    ///
275    /// # Examples
276    ///
277    /// ```
278    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
279    /// use parry3d::shape::{Ball, SupportMap};
280    /// use parry3d::math::Vector;
281    ///
282    /// let ball = Ball::new(1.5);
283    ///
284    /// // Create a normalized direction vector
285    /// let dir = Vector::new(1.0, 1.0, 0.0).normalize();
286    ///
287    /// let support = ball.local_support_point_toward(dir);
288    ///
289    /// // The support point should be on the sphere's surface
290    /// let distance_from_origin = support.length();
291    /// assert!((distance_from_origin - 1.5).abs() < 1e-6);
292    /// # }
293    /// ```
294    fn local_support_point_toward(&self, dir: Vector) -> Vector {
295        self.local_support_point(dir)
296    }
297
298    /// Evaluates the support function of this shape transformed by `transform`.
299    ///
300    /// This is the world-space version of [`local_support_point`](SupportMap::local_support_point).
301    /// It computes the support point for a shape that has been rotated and translated by the
302    /// given isometry (rigid transformation).
303    ///
304    /// # How it Works
305    ///
306    /// The algorithm:
307    /// 1. Transform the direction vector `dir` from world space to the shape's local space
308    /// 2. Compute the support point in local space
309    /// 3. Transform the support point from local space back to world space
310    ///
311    /// This is much more efficient than actually transforming the shape's geometry.
312    ///
313    /// # Parameters
314    ///
315    /// - `transform`: The rigid transformation (rotation + translation) applied to the shape
316    /// - `dir`: The direction vector in world space
317    ///
318    /// # Returns
319    ///
320    /// A point in world space that is the furthest point on the transformed shape in direction `dir`.
321    ///
322    /// # Examples
323    ///
324    /// ```
325    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
326    /// use parry3d::shape::{Ball, SupportMap};
327    /// use parry3d::math::{Pose, Vector};
328    ///
329    /// let ball = Ball::new(1.0);
330    ///
331    /// // Create a transformation: translate the ball to (10, 0, 0)
332    /// let transform = Pose::translation(10.0, 0.0, 0.0);
333    ///
334    /// // Get support point in the positive X direction
335    /// let dir = Vector::new(1.0, 0.0, 0.0);
336    /// let support = ball.support_point(&transform, dir);
337    ///
338    /// // The support point should be at (11, 0, 0) - the rightmost point of the translated ball
339    /// assert!((support.x - 11.0).abs() < 1e-6);
340    /// assert!(support.y.abs() < 1e-6);
341    /// assert!(support.z.abs() < 1e-6);
342    /// # }
343    /// ```
344    ///
345    /// ## Example with Rotation
346    ///
347    /// ```
348    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
349    /// use parry3d::shape::{Cuboid, SupportMap};
350    /// use parry3d::math::{Pose, Rotation, Vector};
351    /// use core::f32::consts::PI;
352    ///
353    /// let cuboid = Cuboid::new(Vector::new(2.0, 1.0, 1.0));
354    ///
355    /// // Rotate the cuboid 90 degrees around the Z axis
356    /// let rotation = Rotation::from_axis_angle(Vector::Z, PI / 2.0);
357    /// let transform = Pose::from_parts(Vector::ZERO, rotation);
358    ///
359    /// // In world space, ask for support in the X direction
360    /// let dir = Vector::new(1.0, 0.0, 0.0);
361    /// let support = cuboid.support_point(&transform, dir);
362    ///
363    /// // After 90° rotation, the long axis (originally X) now points in Y direction
364    /// // So the support in X direction comes from the short axis
365    /// assert!(support.x.abs() <= 1.0 + 1e-5); // Should be around 1.0 (the short axis)
366    /// # }
367    /// ```
368    fn support_point(&self, transform: &Pose, dir: Vector) -> Vector {
369        let local_dir = transform.rotation.inverse() * dir;
370        transform * self.local_support_point(local_dir)
371    }
372
373    /// Same as [`support_point`](SupportMap::support_point) except that `dir` is guaranteed
374    /// to be normalized (unit length).
375    ///
376    /// This combines the benefits of both [`local_support_point_toward`](SupportMap::local_support_point_toward)
377    /// (normalized direction) and [`support_point`](SupportMap::support_point) (world-space transform).
378    ///
379    /// # Parameters
380    ///
381    /// - `transform`: The rigid transformation applied to the shape
382    /// - `dir`: A unit-length direction vector in world space
383    ///
384    /// # Examples
385    ///
386    /// ```
387    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
388    /// use parry3d::shape::{Ball, SupportMap};
389    /// use parry3d::math::{Pose, Vector};
390    ///
391    /// let ball = Ball::new(2.0);
392    ///
393    /// // Translate the ball
394    /// let transform = Pose::translation(5.0, 3.0, -2.0);
395    ///
396    /// // Create a normalized direction
397    /// let dir = Vector::new(1.0, 1.0, 1.0).normalize();
398    ///
399    /// let support = ball.support_point_toward(&transform, dir);
400    ///
401    /// // The support point should be 2.0 units away from the center in the diagonal direction
402    /// let center = Vector::new(5.0, 3.0, -2.0);
403    /// let offset = support - center;
404    /// let distance = offset.length();
405    /// assert!((distance - 2.0).abs() < 1e-6);
406    ///
407    /// // The offset should be parallel to the direction
408    /// let normalized_offset = offset.normalize();
409    /// assert!((normalized_offset.dot(dir) - 1.0).abs() < 1e-6);
410    /// # }
411    /// ```
412    ///
413    /// ## Practical Use: GJK Algorithm
414    ///
415    /// This method is commonly used in the GJK algorithm, which needs to compute support points
416    /// for transformed shapes with normalized directions for better numerical stability:
417    ///
418    /// ```
419    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
420    /// use parry3d::shape::{Ball, Cuboid, SupportMap};
421    /// use parry3d::math::{Pose, Vector};
422    ///
423    /// // Two shapes at different positions
424    /// let ball = Ball::new(1.0);
425    /// let cuboid = Cuboid::new(Vector::new(0.5, 0.5, 0.5));
426    ///
427    /// let ball_pos = Pose::translation(0.0, 0.0, 0.0);
428    /// let cuboid_pos = Pose::translation(3.0, 0.0, 0.0);
429    ///
430    /// // Direction from ball to cuboid
431    /// let dir = Vector::new(1.0, 0.0, 0.0).normalize();
432    ///
433    /// // Get support points for the Minkowski difference (used in GJK)
434    /// let support_ball = ball.support_point_toward(&ball_pos, dir);
435    /// let support_cuboid = cuboid.support_point_toward(&cuboid_pos, -dir);
436    ///
437    /// // The Minkowski difference support point
438    /// let minkowski_support = support_ball - support_cuboid;
439    ///
440    /// println!("Support point for Minkowski difference: {:?}", minkowski_support);
441    /// # }
442    /// ```
443    fn support_point_toward(&self, transform: &Pose, dir: Vector) -> Vector {
444        let local_dir = transform.rotation.inverse() * dir;
445        transform * self.local_support_point_toward(local_dir)
446    }
447}