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}