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}