Skip to main content

parry2d_f64/query/point/
point_composite_shape.rs

1#![allow(unused_parens)] // Needed by the macro.
2
3use crate::math::{Real, Vector};
4use crate::partitioning::BvhNode;
5use crate::query::{PointProjection, PointQuery, PointQueryWithLocation};
6use crate::shape::{
7    CompositeShapeRef, FeatureId, SegmentPointLocation, TriMesh, TrianglePointLocation,
8    TypedCompositeShape,
9};
10
11use crate::shape::{Compound, Polyline};
12
13impl<S: TypedCompositeShape> CompositeShapeRef<'_, S> {
14    /// Project a point on this composite shape.
15    ///
16    /// Returns the projected point as well as the index of the sub-shape of `self` that was hit.
17    /// The third tuple element contains some shape-specific information about the projected point.
18    #[inline]
19    pub fn project_local_point_and_get_location(
20        &self,
21        point: Vector,
22        max_dist: Real,
23        solid: bool,
24    ) -> Option<(
25        u32,
26        (
27            PointProjection,
28            <S::PartShape as PointQueryWithLocation>::Location,
29        ),
30    )>
31    where
32        S::PartShape: PointQueryWithLocation,
33    {
34        self.0
35            .bvh()
36            .find_best(
37                max_dist,
38                |node: &BvhNode, _best_so_far| node.aabb().distance_to_local_point(point, true),
39                |primitive, _best_so_far| {
40                    let proj = self.0.map_typed_part_at(primitive, |pose, shape, _| {
41                        if let Some(pose) = pose {
42                            shape.project_point_and_get_location(pose, point, solid)
43                        } else {
44                            shape.project_local_point_and_get_location(point, solid)
45                        }
46                    })?;
47                    let cost = (proj.0.point - point).length();
48                    Some((cost, proj))
49                },
50            )
51            .map(|(best_id, (_, (proj, location)))| (best_id, (proj, location)))
52    }
53
54    /// Project a point on this composite shape.
55    ///
56    /// Returns the projected point as well as the index of the sub-shape of `self` that was hit.
57    /// If `solid` is `false` then the point will be projected to the closest boundary of `self` even
58    /// if it is contained by one of its sub-shapes.
59    pub fn project_local_point(
60        &self,
61        point: Vector,
62        max_dist: Real,
63        solid: bool,
64    ) -> Option<(u32, PointProjection)> {
65        let (best_id, (_, proj)) = self.0.bvh().find_best(
66            max_dist,
67            |node: &BvhNode, _best_so_far| node.aabb().distance_to_local_point(point, true),
68            |primitive, _best_so_far| {
69                let proj = self.0.map_typed_part_at(primitive, |pose, shape, _| {
70                    if let Some(pose) = pose {
71                        shape.project_point(pose, point, solid)
72                    } else {
73                        shape.project_local_point(point, solid)
74                    }
75                })?;
76                let dist = (proj.point - point).length();
77                Some((dist, proj))
78            },
79        )?;
80        Some((best_id, proj))
81    }
82
83    /// Project a point on this composite shape.
84    ///
85    /// Returns the projected point as well as the index of the sub-shape of `self` that was hit.
86    /// The third tuple element contains some shape-specific information about the shape feature
87    /// hit by the projection.
88    #[inline]
89    pub fn project_local_point_and_get_feature(
90        &self,
91        point: Vector,
92        max_dist: Real,
93    ) -> Option<(u32, (PointProjection, FeatureId))> {
94        let (best_id, (_, (proj, feature_id))) = self.0.bvh().find_best(
95            max_dist,
96            |node: &BvhNode, _best_so_far| node.aabb().distance_to_local_point(point, true),
97            |primitive, _best_so_far| {
98                let proj = self.0.map_typed_part_at(primitive, |pose, shape, _| {
99                    if let Some(pose) = pose {
100                        shape.project_point_and_get_feature(pose, point)
101                    } else {
102                        shape.project_local_point_and_get_feature(point)
103                    }
104                })?;
105                let cost = (proj.0.point - point).length();
106                Some((cost, proj))
107            },
108        )?;
109        Some((best_id, (proj, feature_id)))
110    }
111
112    // TODO: implement distance_to_point too?
113
114    /// Returns the index of any sub-shape of `self` that contains the given point.
115    #[inline]
116    pub fn contains_local_point(&self, point: Vector) -> Option<u32> {
117        self.0
118            .bvh()
119            .leaves(|node: &BvhNode| node.aabb().contains_local_point(point))
120            .find(|leaf_id| {
121                self.0
122                    .map_typed_part_at(*leaf_id, |pose, shape, _| {
123                        if let Some(pose) = pose {
124                            shape.contains_point(pose, point)
125                        } else {
126                            shape.contains_local_point(point)
127                        }
128                    })
129                    .unwrap_or(false)
130            })
131    }
132}
133
134impl PointQuery for Polyline {
135    #[inline]
136    fn project_local_point(&self, point: Vector, solid: bool) -> PointProjection {
137        self.project_local_point_and_get_location(point, solid).0
138    }
139
140    #[inline]
141    #[allow(unused_mut)] // Because we need mut in 2D but not in 3D.
142    fn project_local_point_and_get_feature(&self, point: Vector) -> (PointProjection, FeatureId) {
143        // Every comparison involving a NaN is false, so the traversal finds no candidate
144        // at all when `point` (or `self`) isn’t finite. Report `point` itself rather than
145        // an arbitrary projection onto whichever part we happened to pick.
146        let Some((seg_id, (mut proj, feature))) =
147            CompositeShapeRef(self).project_local_point_and_get_feature(point, Real::MAX)
148        else {
149            return (PointProjection::new(false, point), FeatureId::Unknown);
150        };
151
152        // A point behind the outward pseudo-normal is inside.
153        #[cfg(feature = "dim2")]
154        if let Some(constraints) = self.segment_normal_constraints(seg_id) {
155            let pseudo_normal = match feature {
156                FeatureId::Vertex(i) => constraints.edges[i as usize],
157                _ => constraints.face,
158            };
159            proj.is_inside = (point - proj.point).dot(pseudo_normal) <= 0.0;
160        }
161
162        let polyline_feature = self.segment_feature_to_polyline_feature(seg_id, feature);
163        (proj, polyline_feature)
164    }
165
166    // TODO: implement distance_to_point too?
167
168    #[inline]
169    fn contains_local_point(&self, point: Vector) -> bool {
170        // An oriented polyline has a solid interior; reuse the projection's inside test.
171        #[cfg(feature = "dim2")]
172        if self.flags().contains(crate::shape::PolylineFlags::ORIENTED) {
173            return self
174                .project_local_point_and_get_location(point, true)
175                .0
176                .is_inside;
177        }
178
179        CompositeShapeRef(self)
180            .contains_local_point(point)
181            .is_some()
182    }
183}
184
185impl PointQuery for TriMesh {
186    #[inline]
187    fn project_local_point(&self, point: Vector, solid: bool) -> PointProjection {
188        CompositeShapeRef(self)
189            .project_local_point(point, Real::MAX, solid)
190            .map(|(_, proj)| proj)
191            // No candidate: `point` (or `self`) isn’t finite. See
192            // `Polyline::project_local_point_and_get_feature`.
193            .unwrap_or(PointProjection::new(false, point))
194    }
195
196    #[inline]
197    fn project_local_point_and_get_feature(&self, point: Vector) -> (PointProjection, FeatureId) {
198        #[cfg(feature = "dim3")]
199        if self.pseudo_normals().is_some() {
200            // If we can, in 3D, take the pseudo-normals into account.
201            let (proj, (id, _feature)) = self.project_local_point_and_get_location(point, false);
202            let feature_id = FeatureId::Face(id);
203            return (proj, feature_id);
204        }
205
206        let solid = cfg!(feature = "dim2");
207        // No candidate: `point` (or `self`) isn’t finite. See
208        // `Polyline::project_local_point_and_get_feature`.
209        let Some((tri_id, proj)) =
210            CompositeShapeRef(self).project_local_point(point, Real::MAX, solid)
211        else {
212            return (PointProjection::new(false, point), FeatureId::Unknown);
213        };
214        (proj, FeatureId::Face(tri_id))
215    }
216
217    // TODO: implement distance_to_point too?
218
219    #[inline]
220    fn contains_local_point(&self, point: Vector) -> bool {
221        #[cfg(feature = "dim3")]
222        if self.pseudo_normals.is_some() {
223            // If we can, in 3D, take the pseudo-normals into account.
224            return self
225                .project_local_point_and_get_location(point, true)
226                .0
227                .is_inside;
228        }
229
230        CompositeShapeRef(self)
231            .contains_local_point(point)
232            .is_some()
233    }
234
235    /// Projects a point on `self` transformed by `m`, unless the projection lies further than the given max distance.
236    fn project_local_point_with_max_dist(
237        &self,
238        pt: Vector,
239        solid: bool,
240        max_dist: Real,
241    ) -> Option<PointProjection> {
242        self.project_local_point_and_get_location_with_max_dist(pt, solid, max_dist)
243            .map(|proj| proj.0)
244    }
245}
246
247impl PointQuery for Compound {
248    #[inline]
249    fn project_local_point(&self, point: Vector, solid: bool) -> PointProjection {
250        CompositeShapeRef(self)
251            .project_local_point(point, Real::MAX, solid)
252            .map(|(_, proj)| proj)
253            // No candidate: `point` (or `self`) isn’t finite. See
254            // `Polyline::project_local_point_and_get_feature`.
255            .unwrap_or(PointProjection::new(false, point))
256    }
257
258    #[inline]
259    fn project_local_point_and_get_feature(&self, point: Vector) -> (PointProjection, FeatureId) {
260        (
261            CompositeShapeRef(self)
262                .project_local_point_and_get_feature(point, Real::MAX)
263                .map(|(_, (proj, _))| proj)
264                // No candidate: `point` (or `self`) isn’t finite. See
265                // `Polyline::project_local_point_and_get_feature`.
266                .unwrap_or(PointProjection::new(false, point)),
267            FeatureId::Unknown,
268        )
269    }
270
271    #[inline]
272    fn contains_local_point(&self, point: Vector) -> bool {
273        CompositeShapeRef(self)
274            .contains_local_point(point)
275            .is_some()
276    }
277}
278
279impl PointQueryWithLocation for Polyline {
280    type Location = (u32, SegmentPointLocation);
281
282    #[inline]
283    fn project_local_point_and_get_location(
284        &self,
285        point: Vector,
286        solid: bool,
287    ) -> (PointProjection, Self::Location) {
288        self.project_local_point_and_get_location_with_max_dist(point, solid, Real::MAX)
289            // No candidate: `point` (or `self`) isn’t finite. See
290            // `Polyline::project_local_point_and_get_feature`.
291            .unwrap_or((
292                PointProjection::new(false, point),
293                (0, SegmentPointLocation::OnVertex(0)),
294            ))
295    }
296
297    /// Projects a point on `self`, with a maximum projection distance.
298    fn project_local_point_and_get_location_with_max_dist(
299        &self,
300        point: Vector,
301        solid: bool,
302        max_dist: Real,
303    ) -> Option<(PointProjection, Self::Location)> {
304        #[allow(unused_mut)] // Because we need mut in 2D but not in 3D.
305        if let Some((seg_id, (mut proj, loc))) =
306            CompositeShapeRef(self).project_local_point_and_get_location(point, max_dist, solid)
307        {
308            // A point behind the outward pseudo-normal is inside.
309            #[cfg(feature = "dim2")]
310            if let Some(constraints) = self.segment_normal_constraints(seg_id) {
311                let pseudo_normal = match loc {
312                    SegmentPointLocation::OnVertex(i) => constraints.edges[i as usize],
313                    SegmentPointLocation::OnEdge(_) => constraints.face,
314                };
315                proj.is_inside = (point - proj.point).dot(pseudo_normal) <= 0.0;
316
317                if proj.is_inside && solid {
318                    proj.point = point;
319                }
320            }
321
322            Some((proj, (seg_id, loc)))
323        } else {
324            None
325        }
326    }
327}
328
329impl PointQueryWithLocation for TriMesh {
330    type Location = (u32, TrianglePointLocation);
331
332    #[inline]
333    #[allow(unused_mut)] // Because we need mut in 3D but not in 2D.
334    fn project_local_point_and_get_location(
335        &self,
336        point: Vector,
337        solid: bool,
338    ) -> (PointProjection, Self::Location) {
339        self.project_local_point_and_get_location_with_max_dist(point, solid, Real::MAX)
340            // No candidate: `point` (or `self`) isn’t finite. See
341            // `Polyline::project_local_point_and_get_feature`.
342            .unwrap_or((
343                PointProjection::new(false, point),
344                (0, TrianglePointLocation::OnVertex(0)),
345            ))
346    }
347
348    /// Projects a point on `self`, with a maximum projection distance.
349    fn project_local_point_and_get_location_with_max_dist(
350        &self,
351        point: Vector,
352        solid: bool,
353        max_dist: Real,
354    ) -> Option<(PointProjection, Self::Location)> {
355        #[allow(unused_mut)] // mut is needed in 3D.
356        if let Some((part_id, (mut proj, location))) =
357            CompositeShapeRef(self).project_local_point_and_get_location(point, max_dist, solid)
358        {
359            #[cfg(feature = "dim3")]
360            if let Some(pseudo_normals) = self.pseudo_normals_if_oriented() {
361                let pseudo_normal = match location {
362                    TrianglePointLocation::OnFace(..) | TrianglePointLocation::OnSolid => {
363                        Some(self.triangle(part_id).scaled_normal())
364                    }
365                    TrianglePointLocation::OnEdge(i, _) => pseudo_normals
366                        .edges_pseudo_normal
367                        .get(part_id as usize)
368                        .map(|pn| pn[i as usize]),
369                    TrianglePointLocation::OnVertex(i) => {
370                        let idx = self.indices()[part_id as usize];
371                        pseudo_normals
372                            .vertices_pseudo_normal
373                            .get(idx[i as usize] as usize)
374                            .copied()
375                    }
376                };
377
378                if let Some(pseudo_normal) = pseudo_normal {
379                    let dpt = point - proj.point;
380                    proj.is_inside = dpt.dot(pseudo_normal) <= 0.0;
381                }
382            }
383
384            Some((proj, (part_id, location)))
385        } else {
386            None
387        }
388    }
389}