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}