1use phys_geom::math::Real;
15use phys_geom::shape::Triangle;
16
17use crate::{Raycast, RaycastHitResult};
18
19impl Raycast for Triangle {
20 fn raycast(
21 &self,
22 local_ray: phys_geom::Ray,
23 max_distance: Real,
24 discard_inside_hit: bool,
25 ) -> Option<RaycastHitResult> {
26 let edge1 = self.b - self.a;
28 let edge2 = self.c - self.a;
29 let h = local_ray.direction.into_inner().cross(&edge2);
30 let a = edge1.dot(&h);
31
32 if a > -Real::EPSILON && a < Real::EPSILON {
34 return None;
35 }
36
37 if a < 0.0 {
39 return None;
40 }
41
42 let f = 1.0 / a;
43 let s = local_ray.origin - self.a;
44 let u = f * s.dot(&h);
45
46 if !(0.0..=1.0).contains(&u) {
48 return None;
49 }
50
51 let q = s.cross(&edge1);
52 let v = f * local_ray.direction.into_inner().dot(&q);
53
54 if v < 0.0 || u + v > 1.0 {
56 return None;
57 }
58
59 let t = f * edge2.dot(&q);
60
61 if t > Real::EPSILON {
63 if t > max_distance {
64 return None;
65 }
66
67 if t < Real::EPSILON * 100.0 {
69 if discard_inside_hit {
70 return None;
71 }
72 return Some(RaycastHitResult {
73 distance: 0.0,
74 normal: -self.normal(), });
76 }
77
78 return Some(RaycastHitResult {
79 distance: t,
80 normal: -self.normal(), });
82 }
83
84 None
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use approx::assert_relative_eq;
91 use phys_geom::math::*;
92
93 use super::*;
94
95 #[test]
96 fn test_raycast_triangle_basic() {
97 let triangle = Triangle::new(
99 Point3::new(-1.0, 0.0, 0.0),
100 Point3::new(1.0, 0.0, 0.0),
101 Point3::new(0.0, 1.0, 0.0),
102 );
103
104 let ray = phys_geom::Ray::new(
106 Point3::new(0.0, 0.5, 2.0),
107 UnitVec3::new_normalize(Vec3::new(0.0, 0.0, -1.0)),
108 );
109
110 let hit = triangle.raycast(ray, 10.0, false).unwrap();
111 assert_relative_eq!(hit.distance, 2.0);
112 assert_relative_eq!(
113 hit.normal,
114 UnitVec3::new_normalize(Vec3::new(0.0, 0.0, -1.0))
115 );
116 }
117
118 #[test]
119 fn test_raycast_triangle_from_below() {
120 let triangle = Triangle::new(
121 Point3::new(-1.0, 0.0, 0.0),
122 Point3::new(1.0, 0.0, 0.0),
123 Point3::new(0.0, 1.0, 0.0),
124 );
125
126 let ray = phys_geom::Ray::new(
128 Point3::new(0.0, 0.5, -2.0),
129 UnitVec3::new_normalize(Vec3::new(0.0, 0.0, 1.0)),
130 );
131
132 assert_eq!(triangle.raycast(ray, 10.0, false), None); }
134
135 #[test]
136 fn test_raycast_triangle_miss() {
137 let triangle = Triangle::new(
138 Point3::new(-1.0, 0.0, 0.0),
139 Point3::new(1.0, 0.0, 0.0),
140 Point3::new(0.0, 1.0, 0.0),
141 );
142
143 let ray = phys_geom::Ray::new(
145 Point3::new(2.0, 2.0, 2.0),
146 UnitVec3::new_normalize(Vec3::new(0.0, 0.0, -1.0)),
147 );
148
149 assert_eq!(triangle.raycast(ray, 10.0, false), None);
150 }
151
152 #[test]
153 fn test_raycast_triangle_parallel() {
154 let triangle = Triangle::new(
155 Point3::new(-1.0, 0.0, 0.0),
156 Point3::new(1.0, 0.0, 0.0),
157 Point3::new(0.0, 1.0, 0.0),
158 );
159
160 let ray = phys_geom::Ray::new(
162 Point3::new(0.0, 0.5, 2.0),
163 UnitVec3::new_normalize(Vec3::new(1.0, 0.0, 0.0)),
164 );
165
166 assert_eq!(triangle.raycast(ray, 10.0, false), None);
167 }
168
169 #[test]
170 fn test_raycast_triangle_max_distance() {
171 let triangle = Triangle::new(
172 Point3::new(-1.0, 0.0, 0.0),
173 Point3::new(1.0, 0.0, 0.0),
174 Point3::new(0.0, 1.0, 0.0),
175 );
176
177 let ray = phys_geom::Ray::new(
178 Point3::new(0.0, 0.5, 5.0),
179 UnitVec3::new_normalize(Vec3::new(0.0, 0.0, -1.0)),
180 );
181
182 assert_eq!(triangle.raycast(ray, 1.0, false), None);
184
185 let hit = triangle.raycast(ray, 10.0, false).unwrap();
187 assert_relative_eq!(hit.distance, 5.0);
188 }
189
190 #[test]
191 fn test_raycast_triangle_tilted() {
192 let triangle = Triangle::new(
194 Point3::new(0.0, 0.0, 0.0),
195 Point3::new(1.0, 0.0, 1.0),
196 Point3::new(0.0, 1.0, 1.0),
197 );
198
199 let ray = phys_geom::Ray::new(
201 Point3::new(0.2, 0.3, 2.0),
202 UnitVec3::new_normalize(Vec3::new(0.0, 0.0, -1.0)),
203 );
204
205 let hit = triangle.raycast(ray, 10.0, false).unwrap();
206 assert!(hit.distance > 0.0 && hit.distance < 2.0);
207
208 assert!(hit.normal.y > 0.0);
210 assert!(hit.normal.z < 0.0);
211 }
212
213 #[test]
214 fn test_raycast_triangle_edge_case() {
215 let triangle = Triangle::new(
216 Point3::new(0.0, 0.0, 0.0),
217 Point3::new(1.0, 0.0, 0.0),
218 Point3::new(0.0, 1.0, 0.0),
219 );
220
221 let ray = phys_geom::Ray::new(
223 Point3::new(0.0, 0.0, 1.0),
224 UnitVec3::new_normalize(Vec3::new(0.0, 0.0, -1.0)),
225 );
226
227 let hit = triangle.raycast(ray, 10.0, false).unwrap();
228 assert_relative_eq!(hit.distance, 1.0);
229 }
230}