Skip to main content

parry2d/query/point/
point_query.rs

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