1use crate::math::Real;
2use crate::query::{Ray, RayCast, RayIntersection};
3use crate::shape::{CompositeShapeRef, FeatureId, TriMesh};
4
5#[cfg(feature = "dim3")]
6pub use ray_cast_with_culling::RayCullingMode;
7
8impl RayCast for TriMesh {
9 #[inline]
10 fn cast_local_ray(&self, ray: &Ray, max_time_of_impact: Real, solid: bool) -> Option<Real> {
11 CompositeShapeRef(self)
12 .cast_local_ray(ray, max_time_of_impact, solid)
13 .map(|hit| hit.1)
14 }
15
16 #[inline]
17 fn cast_local_ray_and_get_normal(
18 &self,
19 ray: &Ray,
20 max_time_of_impact: Real,
21 solid: bool,
22 ) -> Option<RayIntersection> {
23 CompositeShapeRef(self)
24 .cast_local_ray_and_get_normal(ray, max_time_of_impact, solid)
25 .map(|(best, mut res)| {
26 if res.feature == FeatureId::Face(1) {
29 res.feature = FeatureId::Face(best + self.indices().len() as u32)
30 } else {
31 res.feature = FeatureId::Face(best);
32 }
33 res
34 })
35 }
36}
37
38#[cfg(feature = "dim3")]
40mod ray_cast_with_culling {
41 use crate::math::{Isometry, Real, Vector};
42 use crate::partitioning::Bvh;
43 use crate::query::details::NormalConstraints;
44 use crate::query::{Ray, RayIntersection};
45 use crate::shape::{
46 CompositeShape, CompositeShapeRef, FeatureId, Shape, TriMesh, Triangle, TypedCompositeShape,
47 };
48
49 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
51 pub enum RayCullingMode {
52 IgnoreBackfaces,
54 IgnoreFrontfaces,
56 }
57
58 impl RayCullingMode {
59 fn check(self, tri_normal: &Vector<Real>, ray_dir: &Vector<Real>) -> bool {
60 match self {
61 RayCullingMode::IgnoreBackfaces => tri_normal.dot(ray_dir) < 0.0,
62 RayCullingMode::IgnoreFrontfaces => tri_normal.dot(ray_dir) > 0.0,
63 }
64 }
65 }
66
67 struct TriMeshWithCulling<'a> {
70 trimesh: &'a TriMesh,
71 culling: RayCullingMode,
72 ray: &'a Ray,
73 }
74
75 impl CompositeShape for TriMeshWithCulling<'_> {
76 fn map_part_at(
77 &self,
78 shape_id: u32,
79 f: &mut dyn FnMut(Option<&Isometry<Real>>, &dyn Shape, Option<&dyn NormalConstraints>),
80 ) {
81 let _ = self.map_untyped_part_at(shape_id, f);
82 }
83
84 fn bvh(&self) -> &Bvh {
86 self.trimesh.bvh()
87 }
88 }
89
90 impl TypedCompositeShape for TriMeshWithCulling<'_> {
91 type PartShape = Triangle;
92 type PartNormalConstraints = ();
93
94 #[inline(always)]
95 fn map_typed_part_at<T>(
96 &self,
97 i: u32,
98 mut f: impl FnMut(
99 Option<&Isometry<Real>>,
100 &Self::PartShape,
101 Option<&Self::PartNormalConstraints>,
102 ) -> T,
103 ) -> Option<T> {
104 let tri = self.trimesh.triangle(i);
105 let tri_normal = tri.scaled_normal();
106
107 if self.culling.check(&tri_normal, &self.ray.dir) {
108 Some(f(None, &tri, None))
109 } else {
110 None
111 }
112 }
113
114 #[inline(always)]
115 fn map_untyped_part_at<T>(
116 &self,
117 i: u32,
118 mut f: impl FnMut(Option<&Isometry<Real>>, &dyn Shape, Option<&dyn NormalConstraints>) -> T,
119 ) -> Option<T> {
120 let tri = self.trimesh.triangle(i);
121 let tri_normal = tri.scaled_normal();
122
123 if self.culling.check(&tri_normal, &self.ray.dir) {
124 Some(f(None, &tri, None))
125 } else {
126 None
127 }
128 }
129 }
130
131 impl TriMesh {
132 #[inline]
139 pub fn cast_ray_with_culling(
140 &self,
141 m: &Isometry<Real>,
142 ray: &Ray,
143 max_time_of_impact: Real,
144 culling: RayCullingMode,
145 ) -> Option<RayIntersection> {
146 let ls_ray = ray.inverse_transform_by(m);
147 self.cast_local_ray_with_culling(&ls_ray, max_time_of_impact, culling)
148 .map(|inter| inter.transform_by(m))
149 }
150
151 pub fn cast_local_ray_with_culling(
156 &self,
157 ray: &Ray,
158 max_time_of_impact: Real,
159 culling: RayCullingMode,
160 ) -> Option<RayIntersection> {
161 let mesh_with_culling = TriMeshWithCulling {
162 trimesh: self,
163 culling,
164 ray,
165 };
166 CompositeShapeRef(&mesh_with_culling)
167 .cast_local_ray_and_get_normal(ray, max_time_of_impact, false)
168 .map(|(best, mut res)| {
169 if res.feature == FeatureId::Face(1) {
172 res.feature = FeatureId::Face(best + self.indices().len() as u32)
173 } else {
174 res.feature = FeatureId::Face(best);
175 }
176 res
177 })
178 }
179 }
180
181 #[cfg(test)]
182 mod test {
183 use crate::query::{Ray, RayCullingMode};
184 use crate::shape::TriMesh;
185 use nalgebra::{Point3, Vector3};
186
187 #[test]
188 fn cast_ray_on_trimesh_with_culling() {
189 let vertices = vec![
190 Point3::new(0.0, 0.0, 0.0),
191 Point3::new(1.0, 0.0, 0.0),
192 Point3::new(0.0, 1.0, 0.0),
193 ];
194 let indices = vec![[0, 1, 2]];
195 let ray_up = Ray::new(Point3::new(0.0, 0.0, -1.0), Vector3::new(0.0, 0.0, 1.0));
196 let ray_down = Ray::new(Point3::new(0.0, 0.0, 1.0), Vector3::new(0.0, 0.0, -1.0));
197
198 let mesh = TriMesh::new(vertices, indices).unwrap();
199 assert!(mesh
200 .cast_local_ray_with_culling(&ray_up, 1000.0, RayCullingMode::IgnoreFrontfaces)
201 .is_some());
202 assert!(mesh
203 .cast_local_ray_with_culling(&ray_down, 1000.0, RayCullingMode::IgnoreFrontfaces)
204 .is_none());
205
206 assert!(mesh
207 .cast_local_ray_with_culling(&ray_up, 1000.0, RayCullingMode::IgnoreBackfaces)
208 .is_none());
209 assert!(mesh
210 .cast_local_ray_with_culling(&ray_down, 1000.0, RayCullingMode::IgnoreBackfaces)
211 .is_some());
212 }
213 }
214}