1use crate::math::Real;
2use crate::query::{Ray, RayCast, RayIntersection};
3use crate::shape::{CompositeShapeRef, 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(|(triangle_id, hit)| hit.with_subshape(triangle_id))
26 }
27}
28
29#[cfg(feature = "dim3")]
31mod ray_cast_with_culling {
32 use crate::math::{Pose, Real, Vector};
33 use crate::partitioning::Bvh;
34 use crate::query::details::NormalConstraints;
35 use crate::query::{Ray, RayIntersection};
36 use crate::shape::{
37 CompositeShape, CompositeShapeRef, Shape, TriMesh, Triangle, TypedCompositeShape,
38 };
39
40 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
42 pub enum RayCullingMode {
43 IgnoreBackfaces,
45 IgnoreFrontfaces,
47 }
48
49 impl RayCullingMode {
50 fn check(self, tri_normal: Vector, ray_dir: Vector) -> bool {
51 match self {
52 RayCullingMode::IgnoreBackfaces => tri_normal.dot(ray_dir) < 0.0,
53 RayCullingMode::IgnoreFrontfaces => tri_normal.dot(ray_dir) > 0.0,
54 }
55 }
56 }
57
58 struct TriMeshWithCulling<'a> {
61 trimesh: &'a TriMesh,
62 culling: RayCullingMode,
63 ray: &'a Ray,
64 }
65
66 impl CompositeShape for TriMeshWithCulling<'_> {
67 fn map_part_at(
68 &self,
69 shape_id: u32,
70 f: &mut dyn FnMut(Option<&Pose>, &dyn Shape, Option<&dyn NormalConstraints>),
71 ) {
72 let _ = self.map_untyped_part_at(shape_id, f);
73 }
74
75 fn bvh(&self) -> &Bvh {
77 self.trimesh.bvh()
78 }
79 }
80
81 impl TypedCompositeShape for TriMeshWithCulling<'_> {
82 type PartShape = Triangle;
83 type PartNormalConstraints = ();
84
85 #[inline(always)]
86 fn map_typed_part_at<T>(
87 &self,
88 i: u32,
89 mut f: impl FnMut(
90 Option<&Pose>,
91 &Self::PartShape,
92 Option<&Self::PartNormalConstraints>,
93 ) -> T,
94 ) -> Option<T> {
95 let tri = self.trimesh.triangle(i);
96 let tri_normal = tri.scaled_normal();
97
98 if self.culling.check(tri_normal, self.ray.dir) {
99 Some(f(None, &tri, None))
100 } else {
101 None
102 }
103 }
104
105 #[inline(always)]
106 fn map_untyped_part_at<T>(
107 &self,
108 i: u32,
109 mut f: impl FnMut(Option<&Pose>, &dyn Shape, Option<&dyn NormalConstraints>) -> T,
110 ) -> Option<T> {
111 let tri = self.trimesh.triangle(i);
112 let tri_normal = tri.scaled_normal();
113
114 if self.culling.check(tri_normal, self.ray.dir) {
115 Some(f(None, &tri, None))
116 } else {
117 None
118 }
119 }
120 }
121
122 impl TriMesh {
123 #[inline]
130 pub fn cast_ray_with_culling(
131 &self,
132 m: &Pose,
133 ray: &Ray,
134 max_time_of_impact: Real,
135 culling: RayCullingMode,
136 ) -> Option<RayIntersection> {
137 let ls_ray = ray.inverse_transform_by(m);
138 self.cast_local_ray_with_culling(&ls_ray, max_time_of_impact, culling)
139 .map(|inter| inter.transform_by(m))
140 }
141
142 pub fn cast_local_ray_with_culling(
147 &self,
148 ray: &Ray,
149 max_time_of_impact: Real,
150 culling: RayCullingMode,
151 ) -> Option<RayIntersection> {
152 let mesh_with_culling = TriMeshWithCulling {
153 trimesh: self,
154 culling,
155 ray,
156 };
157 CompositeShapeRef(&mesh_with_culling)
158 .cast_local_ray_and_get_normal(ray, max_time_of_impact, false)
159 .map(|(triangle_id, hit)| hit.with_subshape(triangle_id))
160 }
161 }
162
163 #[cfg(test)]
164 mod test {
165 use crate::math::Vector;
166 use crate::query::{Ray, RayCullingMode};
167 use crate::shape::TriMesh;
168
169 #[test]
170 fn cast_ray_on_trimesh_with_culling() {
171 let vertices = vec![
172 Vector::ZERO,
173 Vector::new(1.0, 0.0, 0.0),
174 Vector::new(0.0, 1.0, 0.0),
175 ];
176 let indices = vec![[0, 1, 2]];
177 let ray_up = Ray::new(Vector::new(0.0, 0.0, -1.0), Vector::new(0.0, 0.0, 1.0));
178 let ray_down = Ray::new(Vector::new(0.0, 0.0, 1.0), Vector::new(0.0, 0.0, -1.0));
179
180 let mesh = TriMesh::new(vertices, indices).unwrap();
181 assert!(mesh
182 .cast_local_ray_with_culling(&ray_up, 1000.0, RayCullingMode::IgnoreFrontfaces)
183 .is_some());
184 assert!(mesh
185 .cast_local_ray_with_culling(&ray_down, 1000.0, RayCullingMode::IgnoreFrontfaces)
186 .is_none());
187
188 assert!(mesh
189 .cast_local_ray_with_culling(&ray_up, 1000.0, RayCullingMode::IgnoreBackfaces)
190 .is_none());
191 assert!(mesh
192 .cast_local_ray_with_culling(&ray_down, 1000.0, RayCullingMode::IgnoreBackfaces)
193 .is_some());
194 }
195 }
196}