parry2d/query/closest_points/
closest_points.rs

1use crate::math::{Isometry, Point, Real};
2
3use core::mem;
4
5/// Result of a closest points query between two shapes.
6///
7/// This enum represents the outcome of computing closest points between two shapes,
8/// taking into account a user-defined maximum search distance. It's useful for
9/// proximity-based gameplay mechanics, AI perception systems, and efficient spatial
10/// queries where you only care about nearby objects.
11///
12/// # Variants
13///
14/// The result depends on the relationship between the shapes and the `max_dist` parameter:
15///
16/// - **`Intersecting`**: Shapes are overlapping (penetrating or touching)
17/// - **`WithinMargin`**: Shapes are separated but within the search distance
18/// - **`Disjoint`**: Shapes are too far apart (beyond the search distance)
19///
20/// # Use Cases
21///
22/// - **AI perception**: Find nearest enemy within detection range
23/// - **Trigger zones**: Detect objects approaching a threshold distance
24/// - **LOD systems**: Compute detailed interactions only for nearby objects
25/// - **Physics optimization**: Skip expensive computations for distant pairs
26///
27/// # Example
28///
29/// ```rust
30/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
31/// use parry3d::query::{closest_points, ClosestPoints};
32/// use parry3d::shape::Ball;
33/// use nalgebra::Isometry3;
34///
35/// let ball1 = Ball::new(1.0);
36/// let ball2 = Ball::new(1.0);
37///
38/// let pos1 = Isometry3::translation(0.0, 0.0, 0.0);
39/// let pos2 = Isometry3::translation(5.0, 0.0, 0.0);
40///
41/// // Search for closest points within 10.0 units
42/// let result = closest_points(&pos1, &ball1, &pos2, &ball2, 10.0).unwrap();
43///
44/// match result {
45///     ClosestPoints::Intersecting => {
46///         println!("Shapes are overlapping!");
47///     }
48///     ClosestPoints::WithinMargin(pt1, pt2) => {
49///         println!("Closest point on shape1: {:?}", pt1);
50///         println!("Closest point on shape2: {:?}", pt2);
51///         let distance = (pt2 - pt1).norm();
52///         println!("Distance between shapes: {}", distance);
53///     }
54///     ClosestPoints::Disjoint => {
55///         println!("Shapes are more than 10.0 units apart");
56///     }
57/// }
58/// # }
59/// ```
60///
61/// # See Also
62///
63/// - [`closest_points`](crate::query::closest_points::closest_points()) - Main function to compute this result
64/// - [`distance`](crate::query::distance::distance()) - For just the distance value
65/// - [`contact`](crate::query::contact::contact()) - For detailed contact information when intersecting
66#[derive(Debug, PartialEq, Clone, Copy)]
67#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
68#[cfg_attr(
69    feature = "rkyv",
70    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize),
71    archive(check_bytes)
72)]
73pub enum ClosestPoints {
74    /// The two shapes are intersecting (overlapping or touching).
75    ///
76    /// When shapes intersect, their closest points are not well-defined, as there
77    /// are infinitely many contact points. Use [`contact`](crate::query::contact::contact())
78    /// instead to get penetration depth and contact normals.
79    ///
80    /// # Example
81    ///
82    /// ```
83    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
84    /// use parry3d::query::{closest_points, ClosestPoints};
85    /// use parry3d::shape::Ball;
86    /// use nalgebra::Isometry3;
87    ///
88    /// let ball1 = Ball::new(2.0);
89    /// let ball2 = Ball::new(2.0);
90    ///
91    /// // Overlapping balls (centers 3.0 apart, combined radii 4.0)
92    /// let pos1 = Isometry3::translation(0.0, 0.0, 0.0);
93    /// let pos2 = Isometry3::translation(3.0, 0.0, 0.0);
94    ///
95    /// let result = closest_points(&pos1, &ball1, &pos2, &ball2, 10.0).unwrap();
96    /// assert_eq!(result, ClosestPoints::Intersecting);
97    /// # }
98    /// ```
99    Intersecting,
100
101    /// The shapes are separated but within the specified maximum distance.
102    ///
103    /// Contains the two closest points in world-space coordinates:
104    /// - First point: Closest point on the surface of the first shape
105    /// - Second point: Closest point on the surface of the second shape
106    ///
107    /// The distance between the shapes equals the distance between these two points.
108    ///
109    /// # Example
110    ///
111    /// ```
112    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
113    /// use parry3d::query::{closest_points, ClosestPoints};
114    /// use parry3d::shape::Ball;
115    /// use nalgebra::Isometry3;
116    ///
117    /// let ball1 = Ball::new(1.0);
118    /// let ball2 = Ball::new(1.0);
119    ///
120    /// // Balls separated by 3.0 units (centers 5.0 apart, combined radii 2.0)
121    /// let pos1 = Isometry3::translation(0.0, 0.0, 0.0);
122    /// let pos2 = Isometry3::translation(5.0, 0.0, 0.0);
123    ///
124    /// let result = closest_points(&pos1, &ball1, &pos2, &ball2, 10.0).unwrap();
125    ///
126    /// if let ClosestPoints::WithinMargin(pt1, pt2) = result {
127    ///     // Points are on the surface, facing each other
128    ///     assert!((pt1.x - 1.0).abs() < 1e-5); // ball1 surface at x=1.0
129    ///     assert!((pt2.x - 4.0).abs() < 1e-5); // ball2 surface at x=4.0
130    ///
131    ///     // Distance between points
132    ///     let dist = (pt2 - pt1).norm();
133    ///     assert!((dist - 3.0).abs() < 1e-5);
134    /// } else {
135    ///     panic!("Expected WithinMargin");
136    /// }
137    /// # }
138    /// ```
139    WithinMargin(Point<Real>, Point<Real>),
140
141    /// The shapes are separated by more than the specified maximum distance.
142    ///
143    /// The actual distance between shapes is unknown (could be much larger than `max_dist`),
144    /// and no closest points are computed. This saves computation when you only care
145    /// about nearby objects.
146    ///
147    /// # Example
148    ///
149    /// ```
150    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
151    /// use parry3d::query::{closest_points, ClosestPoints};
152    /// use parry3d::shape::Ball;
153    /// use nalgebra::Isometry3;
154    ///
155    /// let ball1 = Ball::new(1.0);
156    /// let ball2 = Ball::new(1.0);
157    ///
158    /// // Balls separated by 8.0 units
159    /// let pos1 = Isometry3::translation(0.0, 0.0, 0.0);
160    /// let pos2 = Isometry3::translation(10.0, 0.0, 0.0);
161    ///
162    /// // Only search within 5.0 units
163    /// let result = closest_points(&pos1, &ball1, &pos2, &ball2, 5.0).unwrap();
164    /// assert_eq!(result, ClosestPoints::Disjoint);
165    ///
166    /// // With larger search distance, we get the points
167    /// let result2 = closest_points(&pos1, &ball1, &pos2, &ball2, 10.0).unwrap();
168    /// assert!(matches!(result2, ClosestPoints::WithinMargin(_, _)));
169    /// # }
170    /// ```
171    Disjoint,
172}
173
174impl ClosestPoints {
175    /// Swaps the two closest points in-place.
176    ///
177    /// This is useful when you need to reverse the perspective of the query result.
178    /// If the result is `WithinMargin`, the two points are swapped. For `Intersecting`
179    /// and `Disjoint`, this operation has no effect.
180    ///
181    /// # Example
182    ///
183    /// ```
184    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
185    /// use parry3d::query::{closest_points, ClosestPoints};
186    /// use parry3d::shape::Ball;
187    /// use nalgebra::Isometry3;
188    ///
189    /// let ball1 = Ball::new(1.0);
190    /// let ball2 = Ball::new(2.0);
191    ///
192    /// let pos1 = Isometry3::translation(0.0, 0.0, 0.0);
193    /// let pos2 = Isometry3::translation(5.0, 0.0, 0.0);
194    ///
195    /// let mut result = closest_points(&pos1, &ball1, &pos2, &ball2, 10.0).unwrap();
196    ///
197    /// if let ClosestPoints::WithinMargin(pt1_before, pt2_before) = result {
198    ///     result.flip();
199    ///     if let ClosestPoints::WithinMargin(pt1_after, pt2_after) = result {
200    ///         assert_eq!(pt1_before, pt2_after);
201    ///         assert_eq!(pt2_before, pt1_after);
202    ///     }
203    /// }
204    /// # }
205    /// ```
206    pub fn flip(&mut self) {
207        if let ClosestPoints::WithinMargin(ref mut p1, ref mut p2) = *self {
208            mem::swap(p1, p2)
209        }
210    }
211
212    /// Returns a copy with the two closest points swapped.
213    ///
214    /// This is the non-mutating version of [`flip`](Self::flip). It returns a new
215    /// `ClosestPoints` with swapped points if the result is `WithinMargin`, or returns
216    /// `self` unchanged for `Intersecting` and `Disjoint`.
217    ///
218    /// # Example
219    ///
220    /// ```
221    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
222    /// use parry3d::query::{closest_points, ClosestPoints};
223    /// use parry3d::shape::Ball;
224    /// use nalgebra::Isometry3;
225    ///
226    /// let ball1 = Ball::new(1.0);
227    /// let ball2 = Ball::new(2.0);
228    ///
229    /// let pos1 = Isometry3::translation(0.0, 0.0, 0.0);
230    /// let pos2 = Isometry3::translation(5.0, 0.0, 0.0);
231    ///
232    /// let result = closest_points(&pos1, &ball1, &pos2, &ball2, 10.0).unwrap();
233    /// let flipped = result.flipped();
234    ///
235    /// // Original is unchanged
236    /// if let ClosestPoints::WithinMargin(pt1_orig, pt2_orig) = result {
237    ///     if let ClosestPoints::WithinMargin(pt1_flip, pt2_flip) = flipped {
238    ///         assert_eq!(pt1_orig, pt2_flip);
239    ///         assert_eq!(pt2_orig, pt1_flip);
240    ///     }
241    /// }
242    /// # }
243    /// ```
244    #[must_use]
245    pub fn flipped(&self) -> Self {
246        if let ClosestPoints::WithinMargin(p1, p2) = *self {
247            ClosestPoints::WithinMargin(p2, p1)
248        } else {
249            *self
250        }
251    }
252
253    /// Transforms the closest points from local space to world space.
254    ///
255    /// Applies the isometry transformations to convert closest points from their
256    /// respective local coordinate frames to world-space coordinates. This is used
257    /// internally by the query system.
258    ///
259    /// - Point 1 is transformed by `pos1`
260    /// - Point 2 is transformed by `pos2`
261    /// - `Intersecting` and `Disjoint` variants are returned unchanged
262    ///
263    /// # Arguments
264    ///
265    /// * `pos1` - Transformation for the first shape
266    /// * `pos2` - Transformation for the second shape
267    ///
268    /// # Example
269    ///
270    /// ```
271    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
272    /// use parry3d::query::ClosestPoints;
273    /// use nalgebra::{Isometry3, Point3};
274    ///
275    /// // Points in local space
276    /// let local_result = ClosestPoints::WithinMargin(
277    ///     Point3::new(1.0, 0.0, 0.0),
278    ///     Point3::new(-1.0, 0.0, 0.0),
279    /// );
280    ///
281    /// // Transform to world space
282    /// let pos1 = Isometry3::translation(10.0, 0.0, 0.0);
283    /// let pos2 = Isometry3::translation(20.0, 0.0, 0.0);
284    ///
285    /// let world_result = local_result.transform_by(&pos1, &pos2);
286    ///
287    /// if let ClosestPoints::WithinMargin(pt1, pt2) = world_result {
288    ///     assert!((pt1.x - 11.0).abs() < 1e-5); // 10.0 + 1.0
289    ///     assert!((pt2.x - 19.0).abs() < 1e-5); // 20.0 + (-1.0)
290    /// }
291    /// # }
292    /// ```
293    #[must_use]
294    pub fn transform_by(self, pos1: &Isometry<Real>, pos2: &Isometry<Real>) -> Self {
295        if let ClosestPoints::WithinMargin(p1, p2) = self {
296            ClosestPoints::WithinMargin(pos1 * p1, pos2 * p2)
297        } else {
298            self
299        }
300    }
301}