mesh_graph/ops/
query.rs

1use itertools::Itertools;
2use parry3d::{
3    math::{Isometry, Point, Vector},
4    partitioning::Bvh,
5    query::{
6        PointProjection, PointQuery, PointQueryWithLocation, Ray, RayCast, RayIntersection,
7        details::NormalConstraints,
8    },
9    shape::{CompositeShape, CompositeShapeRef, FeatureId, Shape, Triangle, TypedCompositeShape},
10};
11use tracing::instrument;
12
13use crate::{Face, MeshGraph, error_none, utils::unwrap_or_return};
14
15impl PointQuery for MeshGraph {
16    #[inline]
17    #[instrument(skip(self))]
18    fn project_local_point(&self, point: &Point<f32>, solid: bool) -> PointProjection {
19        self.project_local_point_and_get_location(point, solid).0
20    }
21
22    fn project_local_point_and_get_feature(
23        &self,
24        _point: &Point<f32>,
25    ) -> (PointProjection, FeatureId) {
26        unimplemented!("Not available")
27    }
28}
29
30impl PointQueryWithLocation for MeshGraph {
31    type Location = Face;
32
33    #[inline]
34    #[instrument(skip(self))]
35    fn project_local_point_and_get_location(
36        &self,
37        point: &Point<f32>,
38        solid: bool,
39    ) -> (PointProjection, Self::Location) {
40        self.project_local_point_and_get_location_with_max_dist(point, solid, f32::MAX)
41            .unwrap()
42    }
43
44    /// Projects a point on `self`, with a maximum projection distance.
45    #[instrument(skip(self))]
46    fn project_local_point_and_get_location_with_max_dist(
47        &self,
48        point: &Point<f32>,
49        solid: bool,
50        max_dist: f32,
51    ) -> Option<(PointProjection, Self::Location)> {
52        let (shape_id, (mut proj, _)) =
53            CompositeShapeRef(self).project_local_point_and_get_location(point, max_dist, solid)?;
54
55        // TODO : this could be more precise by interpolating the normal depending on the hit location
56
57        let face_id = self
58            .index_to_face_id
59            .get(&shape_id)
60            .or_else(error_none!("Face not found"))?;
61        let face = self
62            .faces
63            .get(*face_id)
64            .or_else(error_none!("Face not found"))?;
65
66        if let Some(vertex_normals) = self.vertex_normals.as_ref() {
67            let he = self
68                .halfedges
69                .get(face.halfedge)
70                .or_else(error_none!("Halfedge not found"))?;
71            let pseudo_normal = vertex_normals
72                .get(he.end_vertex)
73                .or_else(error_none!("Vertex normal not found"))?;
74
75            let dpt = point - proj.point;
76            proj.is_inside = dpt.dot(&Vector::new(
77                pseudo_normal.x,
78                pseudo_normal.y,
79                pseudo_normal.z,
80            )) <= 0.0;
81        }
82
83        Some((proj, *face))
84    }
85}
86
87impl RayCast for MeshGraph {
88    #[inline]
89    #[instrument(skip(self))]
90    fn cast_local_ray(&self, ray: &Ray, max_time_of_impact: f32, solid: bool) -> Option<f32> {
91        CompositeShapeRef(self)
92            .cast_local_ray(ray, max_time_of_impact, solid)
93            .map(|hit| hit.1)
94    }
95
96    #[inline]
97    #[instrument(skip(self))]
98    fn cast_local_ray_and_get_normal(
99        &self,
100        ray: &Ray,
101        max_time_of_impact: f32,
102        solid: bool,
103    ) -> Option<RayIntersection> {
104        CompositeShapeRef(self)
105            .cast_local_ray_and_get_normal(ray, max_time_of_impact, solid)
106            .map(|(_, res)| res)
107    }
108}
109
110impl CompositeShape for MeshGraph {
111    #[instrument(skip(self, f))]
112    fn map_part_at(
113        &self,
114        shape_id: u32,
115        f: &mut dyn FnMut(Option<&Isometry<f32>>, &dyn Shape, Option<&dyn NormalConstraints>),
116    ) {
117        let tri = self.triangle(shape_id);
118        let normal_constraints = Default::default(); // self.triangle_normal_constraints(face_id);
119        f(None, &tri, normal_constraints)
120    }
121
122    fn bvh(&self) -> &Bvh {
123        &self.bvh
124    }
125}
126
127impl TypedCompositeShape for MeshGraph {
128    type PartShape = Triangle;
129    type PartNormalConstraints = ();
130
131    #[instrument(skip(self, f))]
132    fn map_typed_part_at<T>(
133        &self,
134        shape_id: u32,
135        mut f: impl FnMut(
136            Option<&Isometry<f32>>,
137            &Self::PartShape,
138            Option<&Self::PartNormalConstraints>,
139        ) -> T,
140    ) -> Option<T> {
141        let tri = self.triangle(shape_id);
142        let pseudo_normals = None; // self.triangle_normal_constraints(face_id);
143        Some(f(None, &tri, pseudo_normals.as_ref()))
144    }
145
146    #[instrument(skip(self, f))]
147    fn map_untyped_part_at<T>(
148        &self,
149        shape_id: u32,
150        mut f: impl FnMut(Option<&Isometry<f32>>, &dyn Shape, Option<&dyn NormalConstraints>) -> T,
151    ) -> Option<T> {
152        let tri = self.triangle(shape_id);
153        let pseudo_normals = Default::default(); // self.triangle_normal_constraints(face_id);
154        Some(f(None, &tri, pseudo_normals))
155    }
156}
157
158impl MeshGraph {
159    #[instrument(skip(self))]
160    pub fn triangle(&self, shape_id: u32) -> Triangle {
161        let face_id = unwrap_or_return!(
162            self.index_to_face_id.get(&shape_id),
163            "Index not found",
164            Triangle::default()
165        );
166
167        let face = unwrap_or_return!(
168            self.faces.get(*face_id),
169            "Face not found",
170            Triangle::default()
171        );
172
173        let pos = face
174            .vertices(self)
175            .filter_map(|v_id| {
176                self.positions
177                    .get(v_id)
178                    .or_else(error_none!("Position not found"))
179            })
180            .collect_vec();
181
182        if pos.len() < 3 {
183            return Triangle::default();
184        }
185
186        Triangle::new(
187            Point::new(pos[0].x, pos[0].y, pos[0].z),
188            Point::new(pos[1].x, pos[1].y, pos[1].z),
189            Point::new(pos[2].x, pos[2].y, pos[2].z),
190        )
191    }
192
193    // TODO : is this necessary?
194    // pub fn triangle_normal_constraints(&self, face_id: FaceId) -> Option<TrianglePseudoNormals> {
195    //     if let Some(vertex_normals) = &self.vertex_normals {
196    //         let triangle = self.triangle(face_id);
197    //         let pseudo_normals = self.pseudo_normals.as_ref()?;
198    //         let edges_pseudo_normals = pseudo_normals.edges_pseudo_normal[i as usize];
199
200    //         // TODO: could the pseudo-normal be pre-normalized instead of having to renormalize
201    //         //       every time we need them?
202    //         Some(TrianglePseudoNormals {
203    //             face: triangle.normal()?,
204    //             edges: [
205    //                 Unit::try_new(edges_pseudo_normals[0], 1.0e-6)?,
206    //                 Unit::try_new(edges_pseudo_normals[1], 1.0e-6)?,
207    //                 Unit::try_new(edges_pseudo_normals[2], 1.0e-6)?,
208    //             ],
209    //         })
210    //     } else {
211    //         None
212    //     }
213    // }
214}