parry2d/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}