1#![allow(clippy::many_single_char_names)]
3
4use crate::algebra::{Matrix4, Point3, Vector3};
5use crate::math::aabb::AxisAlignedBoundingBox;
6use crate::math::{is_point_inside_triangle, plane::Plane, solve_quadratic};
7
8#[derive(Copy, Clone, Debug)]
9pub struct Ray {
10 pub origin: Vector3<f32>,
11 pub dir: Vector3<f32>,
12}
13
14impl Default for Ray {
15 #[inline]
16 fn default() -> Self {
17 Ray {
18 origin: Vector3::new(0.0, 0.0, 0.0),
19 dir: Vector3::new(0.0, 0.0, 1.0),
20 }
21 }
22}
23
24#[derive(Clone, Debug, Copy)]
26pub struct IntersectionResult {
27 pub min: f32,
28 pub max: f32,
29}
30
31impl IntersectionResult {
32 #[inline]
33 pub fn from_slice(roots: &[f32]) -> Self {
34 let mut min = f32::MAX;
35 let mut max = -f32::MAX;
36 for n in roots {
37 min = min.min(*n);
38 max = max.max(*n);
39 }
40 Self { min, max }
41 }
42
43 #[inline]
44 pub fn from_set(results: &[Option<IntersectionResult>]) -> Option<Self> {
45 let mut result = None;
46 for v in results {
47 match result {
48 None => result = *v,
49 Some(ref mut result) => {
50 if let Some(v) = v {
51 result.merge(v.min);
52 result.merge(v.max);
53 }
54 }
55 }
56 }
57 result
58 }
59
60 #[inline]
63 pub fn merge(&mut self, param: f32) {
64 if param < self.min {
65 self.min = param;
66 }
67 if param > self.max {
68 self.max = param;
69 }
70 }
71
72 #[inline]
73 pub fn merge_slice(&mut self, params: &[f32]) {
74 for param in params {
75 self.merge(*param)
76 }
77 }
78}
79
80pub enum CylinderKind {
81 Infinite,
82 Finite,
83 Capped,
84}
85
86impl Ray {
87 #[inline]
89 pub fn from_two_points(begin: Vector3<f32>, end: Vector3<f32>) -> Self {
90 Ray {
91 origin: begin,
92 dir: end - begin,
93 }
94 }
95
96 #[inline]
97 pub fn new(origin: Vector3<f32>, dir: Vector3<f32>) -> Self {
98 Self { origin, dir }
99 }
100
101 #[inline]
104 pub fn sphere_intersection_points(
105 &self,
106 position: &Vector3<f32>,
107 radius: f32,
108 ) -> Option<[Vector3<f32>; 2]> {
109 self.try_eval_points(self.sphere_intersection(position, radius))
110 }
111
112 #[inline]
113 pub fn sphere_intersection(
114 &self,
115 position: &Vector3<f32>,
116 radius: f32,
117 ) -> Option<IntersectionResult> {
118 let d = self.origin - *position;
119 let a = self.dir.dot(&self.dir);
120 let b = 2.0 * self.dir.dot(&d);
121 let c = d.dot(&d) - radius * radius;
122 solve_quadratic(a, b, c).map(|roots| IntersectionResult::from_slice(&roots))
123 }
124
125 #[inline]
127 pub fn is_intersect_sphere(&self, position: &Vector3<f32>, radius: f32) -> bool {
128 let d = self.origin - position;
129 let a = self.dir.dot(&self.dir);
130 let b = 2.0 * self.dir.dot(&d);
131 let c = d.dot(&d) - radius * radius;
132 let discriminant = b * b - 4.0 * a * c;
133 discriminant >= 0.0
134 }
135
136 #[inline]
138 pub fn project_point(&self, point: &Vector3<f32>) -> f32 {
139 (point - self.origin).dot(&self.dir) / self.dir.norm_squared()
140 }
141
142 #[inline]
144 pub fn get_point(&self, t: f32) -> Vector3<f32> {
145 self.origin + self.dir.scale(t)
146 }
147
148 #[inline]
149 pub fn box_intersection(
150 &self,
151 min: &Vector3<f32>,
152 max: &Vector3<f32>,
153 ) -> Option<IntersectionResult> {
154 let (mut tmin, mut tmax) = if self.dir.x >= 0.0 {
155 (
156 (min.x - self.origin.x) / self.dir.x,
157 (max.x - self.origin.x) / self.dir.x,
158 )
159 } else {
160 (
161 (max.x - self.origin.x) / self.dir.x,
162 (min.x - self.origin.x) / self.dir.x,
163 )
164 };
165
166 let (tymin, tymax) = if self.dir.y >= 0.0 {
167 (
168 (min.y - self.origin.y) / self.dir.y,
169 (max.y - self.origin.y) / self.dir.y,
170 )
171 } else {
172 (
173 (max.y - self.origin.y) / self.dir.y,
174 (min.y - self.origin.y) / self.dir.y,
175 )
176 };
177
178 if tmin > tymax || (tymin > tmax) {
179 return None;
180 }
181 if tymin > tmin {
182 tmin = tymin;
183 }
184 if tymax < tmax {
185 tmax = tymax;
186 }
187 let (tzmin, tzmax) = if self.dir.z >= 0.0 {
188 (
189 (min.z - self.origin.z) / self.dir.z,
190 (max.z - self.origin.z) / self.dir.z,
191 )
192 } else {
193 (
194 (max.z - self.origin.z) / self.dir.z,
195 (min.z - self.origin.z) / self.dir.z,
196 )
197 };
198
199 if (tmin > tzmax) || (tzmin > tmax) {
200 return None;
201 }
202 if tzmin > tmin {
203 tmin = tzmin;
204 }
205 if tzmax < tmax {
206 tmax = tzmax;
207 }
208 if tmin <= 1.0 && tmax >= 0.0 {
209 Some(IntersectionResult {
210 min: tmin,
211 max: tmax,
212 })
213 } else {
214 None
215 }
216 }
217
218 #[inline]
219 pub fn box_intersection_points(
220 &self,
221 min: &Vector3<f32>,
222 max: &Vector3<f32>,
223 ) -> Option<[Vector3<f32>; 2]> {
224 self.try_eval_points(self.box_intersection(min, max))
225 }
226
227 #[inline]
228 pub fn aabb_intersection(&self, aabb: &AxisAlignedBoundingBox) -> Option<IntersectionResult> {
229 self.box_intersection(&aabb.min, &aabb.max)
230 }
231
232 #[inline]
233 pub fn aabb_intersection_points(
234 &self,
235 aabb: &AxisAlignedBoundingBox,
236 ) -> Option<[Vector3<f32>; 2]> {
237 self.box_intersection_points(&aabb.min, &aabb.max)
238 }
239
240 #[inline]
243 pub fn plane_intersection(&self, plane: &Plane) -> f32 {
244 let u = -(self.origin.dot(&plane.normal) + plane.d);
245 let v = self.dir.dot(&plane.normal);
246 u / v
247 }
248
249 #[inline]
250 pub fn plane_intersection_point(&self, plane: &Plane) -> Option<Vector3<f32>> {
251 let t = self.plane_intersection(plane);
252 if !(0.0..=1.0).contains(&t) {
253 None
254 } else {
255 Some(self.get_point(t))
256 }
257 }
258
259 #[inline]
260 pub fn triangle_intersection(
261 &self,
262 vertices: &[Vector3<f32>; 3],
263 ) -> Option<(f32, Vector3<f32>)> {
264 let ba = vertices[1] - vertices[0];
265 let ca = vertices[2] - vertices[0];
266 let plane = Plane::from_normal_and_point(&ba.cross(&ca), &vertices[0])?;
267
268 let t = self.plane_intersection(&plane);
269 if (0.0..=1.0).contains(&t) {
270 let point = self.get_point(t);
271 if is_point_inside_triangle(&point, vertices) {
272 return Some((t, point));
273 }
274 }
275 None
276 }
277
278 #[inline]
279 pub fn triangle_intersection_point(
280 &self,
281 vertices: &[Vector3<f32>; 3],
282 ) -> Option<Vector3<f32>> {
283 let ba = vertices[1] - vertices[0];
284 let ca = vertices[2] - vertices[0];
285 let plane = Plane::from_normal_and_point(&ba.cross(&ca), &vertices[0])?;
286
287 if let Some(point) = self.plane_intersection_point(&plane) {
288 if is_point_inside_triangle(&point, vertices) {
289 return Some(point);
290 }
291 }
292 None
293 }
294
295 #[inline]
311 pub fn cylinder_intersection(
312 &self,
313 pa: &Vector3<f32>,
314 pb: &Vector3<f32>,
315 r: f32,
316 kind: CylinderKind,
317 ) -> Option<IntersectionResult> {
318 let va = (*pb - *pa)
319 .try_normalize(f32::EPSILON)
320 .unwrap_or_else(|| Vector3::new(0.0, 1.0, 0.0));
321 let vl = self.dir - va.scale(self.dir.dot(&va));
322 let dp = self.origin - *pa;
323 let dpva = dp - va.scale(dp.dot(&va));
324
325 let a = vl.norm_squared();
326 let b = 2.0 * vl.dot(&dpva);
327 let c = dpva.norm_squared() - r * r;
328
329 if let Some(cylinder_roots) = solve_quadratic(a, b, c) {
331 match kind {
332 CylinderKind::Infinite => Some(IntersectionResult::from_slice(&cylinder_roots)),
333 CylinderKind::Capped => {
334 let mut result = IntersectionResult::from_slice(&cylinder_roots);
335 for (cap_center, cap_normal) in [(pa, -va), (pb, va)].iter() {
337 let cap_plane =
338 Plane::from_normal_and_point(cap_normal, cap_center).unwrap();
339 let t = self.plane_intersection(&cap_plane);
340 if t > 0.0 {
341 let intersection = self.get_point(t);
342 if (*cap_center - intersection).norm_squared() <= r * r {
343 result.merge(t);
345 }
346 }
347 }
348 result.merge_slice(&cylinder_roots);
349 Some(result)
350 }
351 CylinderKind::Finite => {
352 let mut result = None;
355 for root in cylinder_roots.iter() {
356 let int_point = self.get_point(*root);
357 if (int_point - *pa).dot(&va) >= 0.0 && (*pb - int_point).dot(&va) >= 0.0 {
358 match &mut result {
359 None => {
360 result = Some(IntersectionResult {
361 min: *root,
362 max: *root,
363 })
364 }
365 Some(result) => result.merge(*root),
366 }
367 }
368 }
369 result
370 }
371 }
372 } else {
373 None
375 }
376 }
377
378 #[inline]
379 pub fn try_eval_points(&self, result: Option<IntersectionResult>) -> Option<[Vector3<f32>; 2]> {
380 match result {
381 None => None,
382 Some(result) => {
383 let a = if result.min >= 0.0 && result.min <= 1.0 {
384 Some(self.get_point(result.min))
385 } else {
386 None
387 };
388
389 let b = if result.max >= 0.0 && result.max <= 1.0 {
390 Some(self.get_point(result.max))
391 } else {
392 None
393 };
394
395 match a {
396 None => b.map(|b| [b, b]),
397 Some(a) => match b {
398 None => Some([a, a]),
399 Some(b) => Some([a, b]),
400 },
401 }
402 }
403 }
404 }
405
406 #[inline]
407 pub fn capsule_intersection(
408 &self,
409 pa: &Vector3<f32>,
410 pb: &Vector3<f32>,
411 radius: f32,
412 ) -> Option<[Vector3<f32>; 2]> {
413 let cylinder = self.cylinder_intersection(pa, pb, radius, CylinderKind::Finite);
416 let cap_a = self.sphere_intersection(pa, radius);
417 let cap_b = self.sphere_intersection(pb, radius);
418 self.try_eval_points(IntersectionResult::from_set(&[cylinder, cap_a, cap_b]))
419 }
420
421 #[must_use = "Method does not modify ray, instead it returns transformed copy"]
429 #[inline]
430 pub fn transform(&self, mat: Matrix4<f32>) -> Self {
431 Self {
432 origin: mat.transform_point(&Point3::from(self.origin)).coords,
433 dir: mat.transform_vector(&self.dir),
434 }
435 }
436}
437
438#[cfg(test)]
439mod test {
440 use crate::math::ray::Ray;
441 use crate::math::Vector3;
442
443 #[test]
444 fn intersection() {
445 let triangle = [
446 Vector3::new(0.0, 0.5, 0.0),
447 Vector3::new(-0.5, -0.5, 0.0),
448 Vector3::new(0.5, -0.5, 0.0),
449 ];
450 let ray = Ray::from_two_points(Vector3::new(0.0, 0.0, -2.0), Vector3::new(0.0, 0.0, -1.0));
451 assert!(ray.triangle_intersection_point(&triangle).is_none());
452 }
453}