parry3d/query/closest_points/
closest_points_shape_shape.rs

1use crate::math::{Isometry, Real};
2use crate::query::{ClosestPoints, DefaultQueryDispatcher, QueryDispatcher, Unsupported};
3use crate::shape::Shape;
4
5/// Computes the pair of closest points between two shapes.
6///
7/// This query finds the two points on the surfaces of the shapes that are closest
8/// to each other, up to a specified maximum distance. It's particularly useful for
9/// proximity detection, AI systems, and optimized spatial queries where you only
10/// care about nearby objects.
11///
12/// # Behavior
13///
14/// The function returns one of three possible results:
15///
16/// - **`Intersecting`**: Shapes are overlapping (penetrating or touching)
17/// - **`WithinMargin`**: Shapes are separated but the distance between them is ≤ `max_dist`
18/// - **`Disjoint`**: Shapes are separated by more than `max_dist`
19///
20/// When shapes are separated and within the margin, the returned points represent
21/// the exact locations on each shape's surface that are closest to each other.
22///
23/// # Maximum Distance Parameter
24///
25/// The `max_dist` parameter controls how far to search for closest points:
26///
27/// - **`max_dist = 0.0`**: Only returns points if shapes are touching or intersecting
28/// - **`max_dist > 0.0`**: Returns points if separation distance ≤ `max_dist`
29/// - **`max_dist = f32::MAX`**: Always computes closest points (no distance limit)
30///
31/// Using a finite `max_dist` can significantly improve performance by allowing early
32/// termination when shapes are far apart.
33///
34/// # Arguments
35///
36/// * `pos1` - Position and orientation of the first shape in world space
37/// * `g1` - The first shape (can be any shape implementing the `Shape` trait)
38/// * `pos2` - Position and orientation of the second shape in world space
39/// * `g2` - The second shape (can be any shape implementing the `Shape` trait)
40/// * `max_dist` - Maximum separation distance to search for closest points
41///
42/// # Returns
43///
44/// * `Ok(ClosestPoints::Intersecting)` - Shapes are overlapping
45/// * `Ok(ClosestPoints::WithinMargin(pt1, pt2))` - Closest points in world-space
46/// * `Ok(ClosestPoints::Disjoint)` - Shapes are further than `max_dist` apart
47/// * `Err(Unsupported)` - This shape pair combination is not supported
48///
49/// # Performance
50///
51/// Performance depends on shape types and the `max_dist` parameter:
52///
53/// - **Ball-Ball**: Very fast (analytical solution)
54/// - **Convex-Convex**: Moderate (GJK algorithm)
55/// - **Concave shapes**: Slower (requires BVH traversal)
56/// - **Finite `max_dist`**: Faster due to early termination when distance exceeds limit
57///
58/// # Example: Basic Usage
59///
60/// ```rust
61/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
62/// use parry3d::query::{closest_points, ClosestPoints};
63/// use parry3d::shape::Ball;
64/// use nalgebra::Isometry3;
65///
66/// let ball1 = Ball::new(1.0);
67/// let ball2 = Ball::new(1.0);
68///
69/// // Position balls 5 units apart
70/// let pos1 = Isometry3::translation(0.0, 0.0, 0.0);
71/// let pos2 = Isometry3::translation(5.0, 0.0, 0.0);
72///
73/// // Find closest points (unlimited distance)
74/// let result = closest_points(&pos1, &ball1, &pos2, &ball2, f32::MAX).unwrap();
75///
76/// if let ClosestPoints::WithinMargin(pt1, pt2) = result {
77///     // pt1 is at (1.0, 0.0, 0.0) - surface of ball1
78///     // pt2 is at (4.0, 0.0, 0.0) - surface of ball2
79///     let distance = (pt2 - pt1).norm();
80///     assert!((distance - 3.0).abs() < 1e-5); // 5.0 - 1.0 - 1.0 = 3.0
81/// }
82/// # }
83/// ```
84///
85/// # Example: Limited Search Distance
86///
87/// ```rust
88/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
89/// use parry3d::query::{closest_points, ClosestPoints};
90/// use parry3d::shape::Ball;
91/// use nalgebra::Isometry3;
92///
93/// let ball1 = Ball::new(1.0);
94/// let ball2 = Ball::new(1.0);
95///
96/// let pos1 = Isometry3::translation(0.0, 0.0, 0.0);
97/// let pos2 = Isometry3::translation(10.0, 0.0, 0.0);
98///
99/// // Only search within 5.0 units
100/// let result = closest_points(&pos1, &ball1, &pos2, &ball2, 5.0).unwrap();
101///
102/// // Shapes are 8.0 units apart (10.0 - 1.0 - 1.0), which exceeds max_dist
103/// assert_eq!(result, ClosestPoints::Disjoint);
104///
105/// // With larger search distance, we get the points
106/// let result2 = closest_points(&pos1, &ball1, &pos2, &ball2, 10.0).unwrap();
107/// assert!(matches!(result2, ClosestPoints::WithinMargin(_, _)));
108/// # }
109/// ```
110///
111/// # Example: Intersecting Shapes
112///
113/// ```rust
114/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
115/// use parry3d::query::{closest_points, ClosestPoints};
116/// use parry3d::shape::Cuboid;
117/// use nalgebra::{Isometry3, Vector3};
118///
119/// let box1 = Cuboid::new(Vector3::new(2.0, 2.0, 2.0));
120/// let box2 = Cuboid::new(Vector3::new(1.0, 1.0, 1.0));
121///
122/// // Position boxes so they overlap
123/// let pos1 = Isometry3::translation(0.0, 0.0, 0.0);
124/// let pos2 = Isometry3::translation(2.0, 0.0, 0.0);
125///
126/// let result = closest_points(&pos1, &box1, &pos2, &box2, 10.0).unwrap();
127///
128/// // When shapes intersect, closest points are undefined
129/// assert_eq!(result, ClosestPoints::Intersecting);
130/// # }
131/// ```
132///
133/// # Example: AI Proximity Detection
134///
135/// ```rust
136/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
137/// use parry3d::query::{closest_points, ClosestPoints};
138/// use parry3d::shape::Ball;
139/// use nalgebra::Isometry3;
140///
141/// // Enemy detection radius
142/// let detection_radius = 15.0;
143///
144/// let player = Ball::new(0.5);
145/// let enemy = Ball::new(0.5);
146///
147/// let player_pos = Isometry3::translation(0.0, 0.0, 0.0);
148/// let enemy_pos = Isometry3::translation(12.0, 0.0, 0.0);
149///
150/// let result = closest_points(
151///     &player_pos,
152///     &player,
153///     &enemy_pos,
154///     &enemy,
155///     detection_radius
156/// ).unwrap();
157///
158/// match result {
159///     ClosestPoints::WithinMargin(player_point, enemy_point) => {
160///         // Enemy detected! Calculate approach vector
161///         let approach_vector = player_point - enemy_point;
162///         println!("Enemy approaching from: {:?}", approach_vector.normalize());
163///     }
164///     ClosestPoints::Disjoint => {
165///         println!("Enemy out of detection range");
166///     }
167///     ClosestPoints::Intersecting => {
168///         println!("Enemy contact!");
169///     }
170/// }
171/// # }
172/// ```
173///
174/// # Example: Different Shape Types
175///
176/// ```rust
177/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
178/// use parry3d::query::{closest_points, ClosestPoints};
179/// use parry3d::shape::{Ball, Cuboid};
180/// use nalgebra::{Isometry3, Vector3};
181///
182/// let ball = Ball::new(2.0);
183/// let cuboid = Cuboid::new(Vector3::new(1.0, 1.0, 1.0));
184///
185/// let pos_ball = Isometry3::translation(5.0, 0.0, 0.0);
186/// let pos_cuboid = Isometry3::translation(0.0, 0.0, 0.0);
187///
188/// let result = closest_points(&pos_ball, &ball, &pos_cuboid, &cuboid, 10.0).unwrap();
189///
190/// if let ClosestPoints::WithinMargin(pt_ball, pt_cuboid) = result {
191///     // pt_ball is on the ball's surface
192///     // pt_cuboid is on the cuboid's surface
193///     println!("Closest point on ball: {:?}", pt_ball);
194///     println!("Closest point on cuboid: {:?}", pt_cuboid);
195///
196///     // Verify distance
197///     let separation = (pt_ball - pt_cuboid).norm();
198///     println!("Separation distance: {}", separation);
199/// }
200/// # }
201/// ```
202///
203/// # Comparison with Other Queries
204///
205/// Choose the right query for your use case:
206///
207/// | Query | Returns | Use When |
208/// |-------|---------|----------|
209/// | `closest_points` | Point locations | You need exact surface points |
210/// | [`distance`](crate::query::distance::distance()) | Distance value | You only need the distance |
211/// | [`contact`](crate::query::contact::contact()) | Contact info | Shapes are touching/penetrating |
212/// | [`intersection_test`](crate::query::intersection_test::intersection_test()) | Boolean | You only need yes/no overlap |
213///
214/// # See Also
215///
216/// - [`ClosestPoints`] - The return type with detailed documentation
217/// - [`distance`](crate::query::distance::distance()) - For just the distance value
218/// - [`contact`](crate::query::contact::contact()) - For penetration depth and contact normals
219/// - [`intersection_test`](crate::query::intersection_test::intersection_test()) - For boolean overlap test
220pub fn closest_points(
221    pos1: &Isometry<Real>,
222    g1: &dyn Shape,
223    pos2: &Isometry<Real>,
224    g2: &dyn Shape,
225    max_dist: Real,
226) -> Result<ClosestPoints, Unsupported> {
227    let pos12 = pos1.inv_mul(pos2);
228    DefaultQueryDispatcher
229        .closest_points(&pos12, g1, g2, max_dist)
230        .map(|res| res.transform_by(pos1, pos2))
231}