parry3d_f64/query/point/
point_composite_shape.rs

1#![allow(unused_parens)] // Needed by the macro.
2
3use crate::bounding_volume::SimdAabb;
4use crate::math::{Point, Real, SimdReal, SIMD_WIDTH};
5use crate::partitioning::{SimdBestFirstVisitStatus, SimdBestFirstVisitor};
6use crate::query::visitors::CompositePointContainmentTest;
7use crate::query::{PointProjection, PointQuery, PointQueryWithLocation};
8use crate::shape::{
9    FeatureId, SegmentPointLocation, TriMesh, TrianglePointLocation, TypedSimdCompositeShape,
10};
11use na;
12use simba::simd::{SimdBool as _, SimdPartialOrd, SimdValue};
13
14use crate::shape::{Compound, Polyline};
15
16impl PointQuery for Polyline {
17    #[inline]
18    fn project_local_point(&self, point: &Point<Real>, solid: bool) -> PointProjection {
19        self.project_local_point_and_get_location(point, solid).0
20    }
21
22    #[inline]
23    fn project_local_point_and_get_feature(
24        &self,
25        point: &Point<Real>,
26    ) -> (PointProjection, FeatureId) {
27        let mut visitor =
28            PointCompositeShapeProjWithFeatureBestFirstVisitor::new(self, point, false);
29        let (proj, (id, feature)) = self.qbvh().traverse_best_first(&mut visitor).unwrap().1;
30        let polyline_feature = self.segment_feature_to_polyline_feature(id, feature);
31
32        (proj, polyline_feature)
33    }
34
35    // TODO: implement distance_to_point too?
36
37    #[inline]
38    fn contains_local_point(&self, point: &Point<Real>) -> bool {
39        let mut visitor = CompositePointContainmentTest::new(self, point);
40        let _ = self.qbvh().traverse_depth_first(&mut visitor);
41        visitor.found
42    }
43}
44
45impl PointQuery for TriMesh {
46    #[inline]
47    fn project_local_point(&self, point: &Point<Real>, solid: bool) -> PointProjection {
48        self.project_local_point_and_get_location(point, solid).0
49    }
50
51    #[inline]
52    fn project_local_point_and_get_feature(
53        &self,
54        point: &Point<Real>,
55    ) -> (PointProjection, FeatureId) {
56        #[cfg(feature = "dim3")]
57        if self.pseudo_normals().is_some() {
58            // If we can, in 3D, take the pseudo-normals into account.
59            let (proj, (id, _feature)) = self.project_local_point_and_get_location(point, false);
60            let feature_id = FeatureId::Face(id);
61            return (proj, feature_id);
62        }
63
64        let solid = cfg!(feature = "dim2");
65
66        let mut visitor =
67            PointCompositeShapeProjWithFeatureBestFirstVisitor::new(self, point, solid);
68        let (proj, (id, _feature)) = self.qbvh().traverse_best_first(&mut visitor).unwrap().1;
69        let feature_id = FeatureId::Face(id);
70        (proj, feature_id)
71    }
72
73    // TODO: implement distance_to_point too?
74
75    #[inline]
76    fn contains_local_point(&self, point: &Point<Real>) -> bool {
77        #[cfg(feature = "dim3")]
78        if self.pseudo_normals.is_some() {
79            // If we can, in 3D, take the pseudo-normals into account.
80            return self
81                .project_local_point_and_get_location(point, true)
82                .0
83                .is_inside;
84        }
85
86        let mut visitor = CompositePointContainmentTest::new(self, point);
87        let _ = self.qbvh().traverse_depth_first(&mut visitor);
88        visitor.found
89    }
90
91    /// Projects a point on `self` transformed by `m`, unless the projection lies further than the given max distance.
92    fn project_local_point_with_max_dist(
93        &self,
94        pt: &Point<Real>,
95        solid: bool,
96        max_dist: Real,
97    ) -> Option<PointProjection> {
98        self.project_local_point_and_get_location_with_max_dist(pt, solid, max_dist)
99            .map(|proj| proj.0)
100    }
101}
102
103impl PointQuery for Compound {
104    #[inline]
105    fn project_local_point(&self, point: &Point<Real>, solid: bool) -> PointProjection {
106        let mut visitor = PointCompositeShapeProjBestFirstVisitor::new(self, point, solid);
107        self.qbvh().traverse_best_first(&mut visitor).unwrap().1 .0
108    }
109
110    #[inline]
111    fn project_local_point_and_get_feature(
112        &self,
113        point: &Point<Real>,
114    ) -> (PointProjection, FeatureId) {
115        (self.project_local_point(point, false), FeatureId::Unknown)
116    }
117
118    #[inline]
119    fn contains_local_point(&self, point: &Point<Real>) -> bool {
120        let mut visitor = CompositePointContainmentTest::new(self, point);
121        let _ = self.qbvh().traverse_depth_first(&mut visitor);
122        visitor.found
123    }
124}
125
126impl PointQueryWithLocation for Polyline {
127    type Location = (u32, SegmentPointLocation);
128
129    #[inline]
130    fn project_local_point_and_get_location(
131        &self,
132        point: &Point<Real>,
133        solid: bool,
134    ) -> (PointProjection, Self::Location) {
135        let mut visitor =
136            PointCompositeShapeProjWithLocationBestFirstVisitor::new(self, point, solid);
137        self.qbvh().traverse_best_first(&mut visitor).unwrap().1
138    }
139}
140
141impl PointQueryWithLocation for TriMesh {
142    type Location = (u32, TrianglePointLocation);
143
144    #[inline]
145    #[allow(unused_mut)] // Because we need mut in 3D but not in 2D.
146    fn project_local_point_and_get_location(
147        &self,
148        point: &Point<Real>,
149        solid: bool,
150    ) -> (PointProjection, Self::Location) {
151        self.project_local_point_and_get_location_with_max_dist(point, solid, Real::MAX)
152            .unwrap()
153    }
154
155    /// Projects a point on `self`, with a maximum projection distance.
156    fn project_local_point_and_get_location_with_max_dist(
157        &self,
158        point: &Point<Real>,
159        solid: bool,
160        max_dist: Real,
161    ) -> Option<(PointProjection, Self::Location)> {
162        let mut visitor =
163            PointCompositeShapeProjWithLocationBestFirstVisitor::new(self, point, solid);
164
165        #[allow(unused_mut)] // mut is needed in 3D.
166        if let Some((_, (mut proj, (part_id, location)))) =
167            self.qbvh()
168                .traverse_best_first_node(&mut visitor, 0, max_dist)
169        {
170            #[cfg(feature = "dim3")]
171            if let Some(pseudo_normals) = self.pseudo_normals_if_oriented() {
172                let pseudo_normal = match location {
173                    TrianglePointLocation::OnFace(..) | TrianglePointLocation::OnSolid => {
174                        Some(self.triangle(part_id).scaled_normal())
175                    }
176                    TrianglePointLocation::OnEdge(i, _) => pseudo_normals
177                        .edges_pseudo_normal
178                        .get(part_id as usize)
179                        .map(|pn| pn[i as usize]),
180                    TrianglePointLocation::OnVertex(i) => {
181                        let idx = self.indices()[part_id as usize];
182                        pseudo_normals
183                            .vertices_pseudo_normal
184                            .get(idx[i as usize] as usize)
185                            .copied()
186                    }
187                };
188
189                if let Some(pseudo_normal) = pseudo_normal {
190                    let dpt = point - proj.point;
191                    proj.is_inside = dpt.dot(&pseudo_normal) <= 0.0;
192                }
193            }
194
195            Some((proj, (part_id, location)))
196        } else {
197            None
198        }
199    }
200}
201
202/*
203 * Visitors
204 */
205macro_rules! gen_visitor(
206    ($Visitor: ident, $project_local_point: ident, $project_point: ident $(, $Location: ty, $extra_info: ident)* $(| $args: ident)* $(where $PartShapeBound: ident)*) => {
207        /// A visitor for the projection of a point on a composite shape.
208        pub struct $Visitor<'a, S> {
209            shape: &'a S,
210            point: &'a Point<Real>,
211            simd_point: Point<SimdReal>,
212            solid: bool,
213        }
214
215        impl<'a, S> $Visitor<'a, S> {
216            /// Initialize a visitor for the projection of a point on a composite shape.
217            pub fn new(shape: &'a S, point: &'a Point<Real>, solid: bool) -> Self {
218                Self {
219                    shape,
220                    point,
221                    simd_point: Point::splat(*point),
222                    solid,
223                }
224            }
225        }
226
227        impl<'a, S> SimdBestFirstVisitor<S::PartId, SimdAabb> for $Visitor<'a, S>
228        where S: TypedSimdCompositeShape
229              $(, $Location: Copy)*
230              $(, S::PartShape: $PartShapeBound)* {
231            type Result = (PointProjection, (S::PartId $(, $Location)*));
232
233            #[inline]
234            fn visit(
235                &mut self,
236                best: Real,
237                aabb: &SimdAabb,
238                data: Option<[Option<&S::PartId>; SIMD_WIDTH]>,
239            ) -> SimdBestFirstVisitStatus<Self::Result> {
240                let dist = aabb.distance_to_local_point(&self.simd_point);
241                let mask = dist.simd_lt(SimdReal::splat(best));
242
243                if let Some(data) = data {
244                    let mut weights = [0.0; SIMD_WIDTH];
245                    let mut results = [None; SIMD_WIDTH];
246                    let bitmask = mask.bitmask();
247
248                    for ii in 0..SIMD_WIDTH {
249                        if (bitmask & (1 << ii)) != 0 && data[ii].is_some() {
250                            let mut is_inside = false;
251                            let subshape_id = *data[ii].unwrap();
252                            self.shape.map_typed_part_at(subshape_id, |part_pos, part_shape, _| {
253                                let (proj $(, $extra_info)*) = if let Some(part_pos) = part_pos {
254                                    part_shape.$project_point(
255                                        part_pos,
256                                        self.point
257                                        $(, self.$args)*
258                                    )
259                                } else {
260                                    part_shape.$project_local_point(
261                                        self.point
262                                        $(, self.$args)*
263                                    )
264                                };
265
266                                is_inside = proj.is_inside;
267                                weights[ii] = na::distance(self.point, &proj.point);
268                                results[ii] = Some((proj, (subshape_id $(, $extra_info)*)));
269                            });
270
271                            if self.solid && is_inside {
272                                return SimdBestFirstVisitStatus::ExitEarly(results[ii]);
273                            }
274                        }
275                    }
276
277                    SimdBestFirstVisitStatus::MaybeContinue {
278                        weights: SimdReal::from(weights),
279                        mask,
280                        results,
281                    }
282                } else {
283                    SimdBestFirstVisitStatus::MaybeContinue {
284                        weights: dist,
285                        mask,
286                        results: [None; SIMD_WIDTH],
287                    }
288                }
289            }
290        }
291    }
292);
293
294gen_visitor!(
295    PointCompositeShapeProjBestFirstVisitor,
296    project_local_point,
297    project_point | solid
298);
299gen_visitor!(
300    PointCompositeShapeProjWithLocationBestFirstVisitor,
301    project_local_point_and_get_location,
302    project_point_and_get_location,
303    <S::PartShape as PointQueryWithLocation>::Location,
304    extra_info | solid
305    where PointQueryWithLocation
306    where Copy
307);
308gen_visitor!(
309    PointCompositeShapeProjWithFeatureBestFirstVisitor,
310    project_local_point_and_get_feature,
311    project_point_and_get_feature,
312    FeatureId,
313    extra_info
314);