Skip to main content

mesh_graph/ops/
query.rs

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