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}