parry2d/query/point/point_query.rs
1use crate::math::{Pose, Real, Vector};
2use crate::shape::FeatureId;
3
4/// The result of projecting a point onto a shape.
5///
6/// Vector projection finds the closest point on a shape's surface to a given query point.
7/// This is fundamental for many geometric queries including distance calculation,
8/// collision detection, and surface sampling.
9///
10/// # Fields
11///
12/// - **is_inside**: Whether the query point is inside the shape
13/// - **point**: The closest point on the shape's surface
14///
15/// # Inside vs Outside
16///
17/// The `is_inside` flag indicates the query point's location relative to the shape:
18/// - `true`: Query point is inside the shape (projection is on boundary)
19/// - `false`: Query point is outside the shape (projection is nearest surface point)
20///
21/// # Solid Parameter
22///
23/// Most projection functions take a `solid` parameter:
24/// - `solid = true`: Shape is treated as solid (filled interior)
25/// - `solid = false`: Shape is treated as hollow (surface only)
26///
27/// This affects `is_inside` calculation for points in the interior.
28///
29/// # Example
30///
31/// ```rust
32/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
33/// use parry3d::query::PointQuery;
34/// use parry3d::shape::Ball;
35/// use parry3d::math::{Vector, Pose};
36///
37/// let ball = Ball::new(5.0);
38/// let ball_pos = Pose::translation(10.0, 0.0, 0.0);
39///
40/// // Project a point outside the ball
41/// let outside_point = Vector::ZERO;
42/// let proj = ball.project_point(&ball_pos, outside_point, true);
43///
44/// // Closest point on ball surface
45/// assert_eq!(proj.point, Vector::new(5.0, 0.0, 0.0));
46/// assert!(!proj.is_inside);
47///
48/// // Project a point inside the ball
49/// let inside_point = Vector::new(10.0, 0.0, 0.0); // At center
50/// let proj2 = ball.project_point(&ball_pos, inside_point, true);
51/// assert!(proj2.is_inside);
52/// # }
53/// ```
54#[derive(Copy, Clone, Debug, Default)]
55#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
56#[cfg_attr(
57 feature = "rkyv",
58 derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
59)]
60pub struct PointProjection {
61 /// Whether the query point was inside the shape.
62 ///
63 /// - `true`: Vector is in the interior (for solid shapes)
64 /// - `false`: Vector is outside the shape
65 pub is_inside: bool,
66
67 /// The closest point on the shape's surface to the query point.
68 ///
69 /// If `is_inside = true`, this is the nearest point on the boundary.
70 /// If `is_inside = false`, this is the nearest surface point.
71 pub point: Vector,
72}
73
74impl PointProjection {
75 /// Initializes a new `PointProjection`.
76 pub fn new(is_inside: bool, point: Vector) -> Self {
77 PointProjection { is_inside, point }
78 }
79
80 /// Transforms `self.point` by `pos`.
81 pub fn transform_by(&self, pos: &Pose) -> Self {
82 PointProjection {
83 is_inside: self.is_inside,
84 point: pos * self.point,
85 }
86 }
87
88 /// Returns `true` if `Self::is_inside` is `true` or if the distance between the projected point and `point` is smaller than `min_dist`.
89 pub fn is_inside_eps(&self, original_point: Vector, min_dist: Real) -> bool {
90 self.is_inside || (original_point - self.point).length_squared() < min_dist * min_dist
91 }
92}
93
94/// Trait for shapes that support point projection and containment queries.
95///
96/// This trait provides methods to:
97/// - Project points onto the shape's surface
98/// - Calculate distance from a point to the shape
99/// - Test if a point is inside the shape
100///
101/// All major shapes implement this trait, making it easy to perform point queries
102/// on any collision shape.
103///
104/// # Methods Overview
105///
106/// - **Projection**: `project_point()`, `project_local_point()`
107/// - **Distance**: `distance_to_point()`, `distance_to_local_point()`
108/// - **Containment**: `contains_point()`, `contains_local_point()`
109///
110/// # Local vs World Space
111///
112/// Methods with `local_` prefix work in the shape's local coordinate system:
113/// - **Local methods**: Vector must be in shape's coordinate frame
114/// - **World methods**: Vector in world space, shape transformation applied
115///
116/// # Solid Parameter
117///
118/// The `solid` parameter affects how interior points are handled:
119/// - `solid = true`: Shape is filled (has interior volume/area)
120/// - `solid = false`: Shape is hollow (surface only, no interior)
121///
122/// # Example
123///
124/// ```rust
125/// # #[cfg(all(feature = "dim3", feature = "f32"))] {
126/// use parry3d::query::PointQuery;
127/// use parry3d::shape::Cuboid;
128/// use parry3d::math::{Vector, Pose};
129///
130/// let cuboid = Cuboid::new(Vector::splat(1.0));
131/// let cuboid_pos = Pose::translation(5.0, 0.0, 0.0);
132///
133/// let query_point = Vector::ZERO;
134///
135/// // Project point onto cuboid surface
136/// let projection = cuboid.project_point(&cuboid_pos, query_point, true);
137/// println!("Closest point on cuboid: {:?}", projection.point);
138/// println!("Is inside: {}", projection.is_inside);
139///
140/// // Calculate distance to cuboid
141/// let distance = cuboid.distance_to_point(&cuboid_pos, query_point, true);
142/// println!("Distance: {}", distance);
143///
144/// // Test if point is inside cuboid
145/// let is_inside = cuboid.contains_point(&cuboid_pos, query_point);
146/// println!("Contains: {}", is_inside);
147/// # }
148/// ```
149pub trait PointQuery {
150 /// Projects a point onto the shape, with a maximum distance limit.
151 ///
152 /// Returns `None` if the projection would be further than `max_dist` from the query point.
153 /// This is useful for optimization when you only care about nearby projections.
154 ///
155 /// The point is in the shape's local coordinate system.
156 ///
157 /// # Arguments
158 ///
159 /// * `pt` - The point to project (in local space)
160 /// * `solid` - Whether to treat the shape as solid (filled)
161 /// * `max_dist` - Maximum distance to consider
162 ///
163 /// # Example
164 ///
165 /// ```
166 /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
167 /// use parry3d::query::PointQuery;
168 /// use parry3d::shape::Ball;
169 /// use parry3d::math::Vector;
170 ///
171 /// let ball = Ball::new(1.0);
172 /// let far_point = Vector::new(100.0, 0.0, 0.0);
173 ///
174 /// // Projection exists but is far away
175 /// assert!(ball.project_local_point(far_point, true).point.x > 0.0);
176 ///
177 /// // With max distance limit of 10, projection is rejected
178 /// assert!(ball.project_local_point_with_max_dist(far_point, true, 10.0).is_none());
179 ///
180 /// // Nearby point is accepted
181 /// let near_point = Vector::new(2.0, 0.0, 0.0);
182 /// assert!(ball.project_local_point_with_max_dist(near_point, true, 10.0).is_some());
183 /// # }
184 /// ```
185 fn project_local_point_with_max_dist(
186 &self,
187 pt: Vector,
188 solid: bool,
189 max_dist: Real,
190 ) -> Option<PointProjection> {
191 let proj = self.project_local_point(pt, solid);
192 if (proj.point - pt).length() > max_dist {
193 None
194 } else {
195 Some(proj)
196 }
197 }
198
199 /// Projects a point on `self` transformed by `m`, unless the projection lies further than the given max distance.
200 fn project_point_with_max_dist(
201 &self,
202 m: &Pose,
203 pt: Vector,
204 solid: bool,
205 max_dist: Real,
206 ) -> Option<PointProjection> {
207 self.project_local_point_with_max_dist(m.inverse_transform_point(pt), solid, max_dist)
208 .map(|proj| proj.transform_by(m))
209 }
210
211 /// Projects a point on `self`.
212 ///
213 /// The point is assumed to be expressed in the local-space of `self`.
214 fn project_local_point(&self, pt: Vector, solid: bool) -> PointProjection;
215
216 /// Projects a point on the boundary of `self` and returns the id of the
217 /// feature the point was projected on.
218 fn project_local_point_and_get_feature(&self, pt: Vector) -> (PointProjection, FeatureId);
219
220 /// Computes the minimal distance between a point and `self`.
221 fn distance_to_local_point(&self, pt: Vector, solid: bool) -> Real {
222 let proj = self.project_local_point(pt, solid);
223 let dist = (pt - proj.point).length();
224
225 if solid || !proj.is_inside {
226 dist
227 } else {
228 -dist
229 }
230 }
231
232 /// Tests if the given point is inside of `self`.
233 fn contains_local_point(&self, pt: Vector) -> bool {
234 self.project_local_point(pt, true).is_inside
235 }
236
237 /// Projects a point on `self` transformed by `m`.
238 fn project_point(&self, m: &Pose, pt: Vector, solid: bool) -> PointProjection {
239 self.project_local_point(m.inverse_transform_point(pt), solid)
240 .transform_by(m)
241 }
242
243 /// Computes the minimal distance between a point and `self` transformed by `m`.
244 #[inline]
245 fn distance_to_point(&self, m: &Pose, pt: Vector, solid: bool) -> Real {
246 self.distance_to_local_point(m.inverse_transform_point(pt), solid)
247 }
248
249 /// Projects a point on the boundary of `self` transformed by `m` and returns the id of the
250 /// feature the point was projected on.
251 fn project_point_and_get_feature(&self, m: &Pose, pt: Vector) -> (PointProjection, FeatureId) {
252 let res = self.project_local_point_and_get_feature(m.inverse_transform_point(pt));
253 (res.0.transform_by(m), res.1)
254 }
255
256 /// Tests if the given point is inside of `self` transformed by `m`.
257 #[inline]
258 fn contains_point(&self, m: &Pose, pt: Vector) -> bool {
259 self.contains_local_point(m.inverse_transform_point(pt))
260 }
261}
262
263/// Returns shape-specific info in addition to generic projection information
264///
265/// One requirement for the `PointQuery` trait is to be usable as a trait
266/// object. Unfortunately this precludes us from adding an associated type to it
267/// that might allow us to return shape-specific information in addition to the
268/// general information provided in `PointProjection`. This is where
269/// `PointQueryWithLocation` comes in. It forgoes the ability to be used as a trait
270/// object in exchange for being able to provide shape-specific projection
271/// information.
272///
273/// Any shapes that implement `PointQuery` but are able to provide extra
274/// information, can implement `PointQueryWithLocation` in addition and have their
275/// `PointQuery::project_point` implementation just call out to
276/// `PointQueryWithLocation::project_point_and_get_location`.
277pub trait PointQueryWithLocation {
278 /// Additional shape-specific projection information
279 ///
280 /// In addition to the generic projection information returned in
281 /// `PointProjection`, implementations might provide shape-specific
282 /// projection info. The type of this shape-specific information is defined
283 /// by this associated type.
284 type Location;
285
286 /// Projects a point on `self`.
287 fn project_local_point_and_get_location(
288 &self,
289 pt: Vector,
290 solid: bool,
291 ) -> (PointProjection, Self::Location);
292
293 /// Projects a point on `self` transformed by `m`.
294 fn project_point_and_get_location(
295 &self,
296 m: &Pose,
297 pt: Vector,
298 solid: bool,
299 ) -> (PointProjection, Self::Location) {
300 let res = self.project_local_point_and_get_location(m.inverse_transform_point(pt), solid);
301 (res.0.transform_by(m), res.1)
302 }
303
304 /// Projects a point on `self`, with a maximum projection distance.
305 fn project_local_point_and_get_location_with_max_dist(
306 &self,
307 pt: Vector,
308 solid: bool,
309 max_dist: Real,
310 ) -> Option<(PointProjection, Self::Location)> {
311 let (proj, location) = self.project_local_point_and_get_location(pt, solid);
312 if (proj.point - pt).length() > max_dist {
313 None
314 } else {
315 Some((proj, location))
316 }
317 }
318
319 /// Projects a point on `self` transformed by `m`, with a maximum projection distance.
320 fn project_point_and_get_location_with_max_dist(
321 &self,
322 m: &Pose,
323 pt: Vector,
324 solid: bool,
325 max_dist: Real,
326 ) -> Option<(PointProjection, Self::Location)> {
327 self.project_local_point_and_get_location_with_max_dist(
328 m.inverse_transform_point(pt),
329 solid,
330 max_dist,
331 )
332 .map(|res| (res.0.transform_by(m), res.1))
333 }
334}