parry2d/query/closest_points/
closest_points.rs

1use crate::math::{Pose, Vector};
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 parry3d::math::Pose;
34///
35/// let ball1 = Ball::new(1.0);
36/// let ball2 = Ball::new(1.0);
37///
38/// let pos1 = Pose::translation(0.0, 0.0, 0.0);
39/// let pos2 = Pose::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).length();
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)]
72pub enum ClosestPoints {
73    /// The two shapes are intersecting (overlapping or touching).
74    ///
75    /// When shapes intersect, their closest points are not well-defined, as there
76    /// are infinitely many contact points. Use [`contact`](crate::query::contact::contact())
77    /// instead to get penetration depth and contact normals.
78    ///
79    /// # Example
80    ///
81    /// ```
82    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
83    /// use parry3d::query::{closest_points, ClosestPoints};
84    /// use parry3d::shape::Ball;
85    /// use parry3d::math::Pose;
86    ///
87    /// let ball1 = Ball::new(2.0);
88    /// let ball2 = Ball::new(2.0);
89    ///
90    /// // Overlapping balls (centers 3.0 apart, combined radii 4.0)
91    /// let pos1 = Pose::translation(0.0, 0.0, 0.0);
92    /// let pos2 = Pose::translation(3.0, 0.0, 0.0);
93    ///
94    /// let result = closest_points(&pos1, &ball1, &pos2, &ball2, 10.0).unwrap();
95    /// assert_eq!(result, ClosestPoints::Intersecting);
96    /// # }
97    /// ```
98    Intersecting,
99
100    /// The shapes are separated but within the specified maximum distance.
101    ///
102    /// Contains the two closest points in world-space coordinates:
103    /// - First point: Closest point on the surface of the first shape
104    /// - Second point: Closest point on the surface of the second shape
105    ///
106    /// The distance between the shapes equals the distance between these two points.
107    ///
108    /// # Example
109    ///
110    /// ```
111    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
112    /// use parry3d::query::{closest_points, ClosestPoints};
113    /// use parry3d::shape::Ball;
114    /// use parry3d::math::Pose;
115    ///
116    /// let ball1 = Ball::new(1.0);
117    /// let ball2 = Ball::new(1.0);
118    ///
119    /// // Balls separated by 3.0 units (centers 5.0 apart, combined radii 2.0)
120    /// let pos1 = Pose::translation(0.0, 0.0, 0.0);
121    /// let pos2 = Pose::translation(5.0, 0.0, 0.0);
122    ///
123    /// let result = closest_points(&pos1, &ball1, &pos2, &ball2, 10.0).unwrap();
124    ///
125    /// if let ClosestPoints::WithinMargin(pt1, pt2) = result {
126    ///     // Vectors are on the surface, facing each other
127    ///     assert!((pt1.x - 1.0).abs() < 1e-5); // ball1 surface at x=1.0
128    ///     assert!((pt2.x - 4.0).abs() < 1e-5); // ball2 surface at x=4.0
129    ///
130    ///     // Distance between points
131    ///     let dist = (pt2 - pt1).length();
132    ///     assert!((dist - 3.0).abs() < 1e-5);
133    /// } else {
134    ///     panic!("Expected WithinMargin");
135    /// }
136    /// # }
137    /// ```
138    WithinMargin(Vector, Vector),
139
140    /// The shapes are separated by more than the specified maximum distance.
141    ///
142    /// The actual distance between shapes is unknown (could be much larger than `max_dist`),
143    /// and no closest points are computed. This saves computation when you only care
144    /// about nearby objects.
145    ///
146    /// # Example
147    ///
148    /// ```
149    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
150    /// use parry3d::query::{closest_points, ClosestPoints};
151    /// use parry3d::shape::Ball;
152    /// use parry3d::math::Pose;
153    ///
154    /// let ball1 = Ball::new(1.0);
155    /// let ball2 = Ball::new(1.0);
156    ///
157    /// // Balls separated by 8.0 units
158    /// let pos1 = Pose::translation(0.0, 0.0, 0.0);
159    /// let pos2 = Pose::translation(10.0, 0.0, 0.0);
160    ///
161    /// // Only search within 5.0 units
162    /// let result = closest_points(&pos1, &ball1, &pos2, &ball2, 5.0).unwrap();
163    /// assert_eq!(result, ClosestPoints::Disjoint);
164    ///
165    /// // With larger search distance, we get the points
166    /// let result2 = closest_points(&pos1, &ball1, &pos2, &ball2, 10.0).unwrap();
167    /// assert!(matches!(result2, ClosestPoints::WithinMargin(_, _)));
168    /// # }
169    /// ```
170    Disjoint,
171}
172
173impl ClosestPoints {
174    /// Swaps the two closest points in-place.
175    ///
176    /// This is useful when you need to reverse the perspective of the query result.
177    /// If the result is `WithinMargin`, the two points are swapped. For `Intersecting`
178    /// and `Disjoint`, this operation has no effect.
179    ///
180    /// # Example
181    ///
182    /// ```
183    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
184    /// use parry3d::query::{closest_points, ClosestPoints};
185    /// use parry3d::shape::Ball;
186    /// use parry3d::math::Pose;
187    ///
188    /// let ball1 = Ball::new(1.0);
189    /// let ball2 = Ball::new(2.0);
190    ///
191    /// let pos1 = Pose::translation(0.0, 0.0, 0.0);
192    /// let pos2 = Pose::translation(5.0, 0.0, 0.0);
193    ///
194    /// let mut result = closest_points(&pos1, &ball1, &pos2, &ball2, 10.0).unwrap();
195    ///
196    /// if let ClosestPoints::WithinMargin(pt1_before, pt2_before) = result {
197    ///     result.flip();
198    ///     if let ClosestPoints::WithinMargin(pt1_after, pt2_after) = result {
199    ///         assert_eq!(pt1_before, pt2_after);
200    ///         assert_eq!(pt2_before, pt1_after);
201    ///     }
202    /// }
203    /// # }
204    /// ```
205    pub fn flip(&mut self) {
206        if let ClosestPoints::WithinMargin(ref mut p1, ref mut p2) = *self {
207            mem::swap(p1, p2)
208        }
209    }
210
211    /// Returns a copy with the two closest points swapped.
212    ///
213    /// This is the non-mutating version of [`flip`](Self::flip). It returns a new
214    /// `ClosestPoints` with swapped points if the result is `WithinMargin`, or returns
215    /// `self` unchanged for `Intersecting` and `Disjoint`.
216    ///
217    /// # Example
218    ///
219    /// ```
220    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
221    /// use parry3d::query::{closest_points, ClosestPoints};
222    /// use parry3d::shape::Ball;
223    /// use parry3d::math::Pose;
224    ///
225    /// let ball1 = Ball::new(1.0);
226    /// let ball2 = Ball::new(2.0);
227    ///
228    /// let pos1 = Pose::translation(0.0, 0.0, 0.0);
229    /// let pos2 = Pose::translation(5.0, 0.0, 0.0);
230    ///
231    /// let result = closest_points(&pos1, &ball1, &pos2, &ball2, 10.0).unwrap();
232    /// let flipped = result.flipped();
233    ///
234    /// // Original is unchanged
235    /// if let ClosestPoints::WithinMargin(pt1_orig, pt2_orig) = result {
236    ///     if let ClosestPoints::WithinMargin(pt1_flip, pt2_flip) = flipped {
237    ///         assert_eq!(pt1_orig, pt2_flip);
238    ///         assert_eq!(pt2_orig, pt1_flip);
239    ///     }
240    /// }
241    /// # }
242    /// ```
243    #[must_use]
244    pub fn flipped(&self) -> Self {
245        if let ClosestPoints::WithinMargin(p1, p2) = *self {
246            ClosestPoints::WithinMargin(p2, p1)
247        } else {
248            *self
249        }
250    }
251
252    /// Transforms the closest points from local space to world space.
253    ///
254    /// Applies the isometry transformations to convert closest points from their
255    /// respective local coordinate frames to world-space coordinates. This is used
256    /// internally by the query system.
257    ///
258    /// - Vector 1 is transformed by `pos1`
259    /// - Vector 2 is transformed by `pos2`
260    /// - `Intersecting` and `Disjoint` variants are returned unchanged
261    ///
262    /// # Arguments
263    ///
264    /// * `pos1` - Transformation for the first shape
265    /// * `pos2` - Transformation for the second shape
266    ///
267    /// # Example
268    ///
269    /// ```
270    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
271    /// use parry3d::query::ClosestPoints;
272    /// use parry3d::math::{Pose, Vector};
273    ///
274    /// // Vectors in local space
275    /// let local_result = ClosestPoints::WithinMargin(
276    ///     Vector::new(1.0, 0.0, 0.0),
277    ///     Vector::new(-1.0, 0.0, 0.0),
278    /// );
279    ///
280    /// // Transform to world space
281    /// let pos1 = Pose::translation(10.0, 0.0, 0.0);
282    /// let pos2 = Pose::translation(20.0, 0.0, 0.0);
283    ///
284    /// let world_result = local_result.transform_by(&pos1, &pos2);
285    ///
286    /// if let ClosestPoints::WithinMargin(pt1, pt2) = world_result {
287    ///     assert!((pt1.x - 11.0).abs() < 1e-5); // 10.0 + 1.0
288    ///     assert!((pt2.x - 19.0).abs() < 1e-5); // 20.0 + (-1.0)
289    /// }
290    /// # }
291    /// ```
292    #[must_use]
293    pub fn transform_by(self, pos1: &Pose, pos2: &Pose) -> Self {
294        if let ClosestPoints::WithinMargin(p1, p2) = self {
295            ClosestPoints::WithinMargin(pos1 * p1, pos2 * p2)
296        } else {
297            self
298        }
299    }
300}