parry2d/query/point/
point_query.rs

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