1use crate::collider::{Capsule, Sphere, AABB};
13use crate::math::{Fix128, Vec3Fix};
14
15#[cfg(not(feature = "std"))]
16use alloc::vec::Vec;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub struct Ray {
21 pub origin: Vec3Fix,
23 pub direction: Vec3Fix,
25}
26
27const RAY_PARALLEL_EPSILON: Fix128 = Fix128 {
29 hi: 0,
30 lo: 0x0000000100000000,
31};
32
33impl Ray {
34 #[inline]
36 #[must_use]
37 pub fn new(origin: Vec3Fix, direction: Vec3Fix) -> Self {
38 let len = direction.length();
39 Self {
40 origin,
41 direction: if len.is_zero() {
42 Vec3Fix::UNIT_X
43 } else {
44 direction / len
45 },
46 }
47 }
48
49 #[inline]
51 #[must_use]
52 pub fn at(&self, t: Fix128) -> Vec3Fix {
53 self.origin + self.direction * t
54 }
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub struct RayHit {
60 pub t: Fix128,
62 pub point: Vec3Fix,
64 pub normal: Vec3Fix,
66 pub body_index: usize,
68}
69
70#[inline]
74#[must_use]
75pub fn ray_sphere(ray: &Ray, sphere: &Sphere, max_t: Fix128) -> Option<RayHit> {
76 let oc = ray.origin - sphere.center;
77 let b = oc.dot(ray.direction);
78 let c = oc.dot(oc) - sphere.radius * sphere.radius;
79 let discriminant = b * b - c;
80
81 if discriminant < Fix128::ZERO {
82 return None;
83 }
84
85 let sqrt_d = discriminant.sqrt();
86
87 let t = -b - sqrt_d;
89 if t >= Fix128::ZERO && t <= max_t {
90 let point = ray.at(t);
91 let normal = (point - sphere.center).normalize();
92 return Some(RayHit {
93 t,
94 point,
95 normal,
96 body_index: 0,
97 });
98 }
99
100 let t = -b + sqrt_d;
102 if t >= Fix128::ZERO && t <= max_t {
103 let point = ray.at(t);
104 let normal = (point - sphere.center).normalize();
105 return Some(RayHit {
106 t,
107 point,
108 normal,
109 body_index: 0,
110 });
111 }
112
113 None
114}
115
116#[inline]
136#[must_use]
137pub(crate) fn sweep_ray_sphere(ray: &Ray, sphere: &Sphere, max_t: Fix128) -> Option<RayHit> {
138 let oc = ray.origin - sphere.center;
139 let c = oc.dot(oc) - sphere.radius * sphere.radius;
140 if c > Fix128::ZERO {
141 return ray_sphere(ray, sphere, max_t);
144 }
145 let normal = if oc.length_squared().is_zero() {
149 -ray.direction
150 } else {
151 if oc.dot(ray.direction) >= Fix128::ZERO {
152 return None;
153 }
154 oc.normalize()
155 };
156 Some(RayHit {
157 t: Fix128::ZERO,
158 point: ray.origin,
159 normal,
160 body_index: 0,
161 })
162}
163
164#[inline]
168#[must_use]
169pub fn ray_aabb(ray: &Ray, aabb: &AABB, max_t: Fix128) -> Option<RayHit> {
170 let (t_min, t_max) = ray_aabb_interval(ray, aabb, max_t)?;
171
172 if t_min > max_t || t_max < Fix128::ZERO {
173 return None;
174 }
175
176 let t = if t_min >= Fix128::ZERO { t_min } else { t_max };
177 if t > max_t {
178 return None;
179 }
180
181 let point = ray.at(t);
182
183 let normal = aabb_hit_normal(point, aabb);
185
186 Some(RayHit {
187 t,
188 point,
189 normal,
190 body_index: 0,
191 })
192}
193
194#[inline]
200fn ray_aabb_interval(ray: &Ray, aabb: &AABB, max_t: Fix128) -> Option<(Fix128, Fix128)> {
201 let mut t_min = -max_t;
202 let mut t_max = max_t;
203
204 if !ray.direction.x.is_zero() {
206 let inv_d = Fix128::ONE / ray.direction.x;
207 let mut t0 = (aabb.min.x - ray.origin.x) * inv_d;
208 let mut t1 = (aabb.max.x - ray.origin.x) * inv_d;
209 if t0 > t1 {
210 core::mem::swap(&mut t0, &mut t1);
211 }
212 t_min = if t0 > t_min { t0 } else { t_min };
213 t_max = if t1 < t_max { t1 } else { t_max };
214 if t_min > t_max {
215 return None;
216 }
217 } else if ray.origin.x < aabb.min.x || ray.origin.x > aabb.max.x {
218 return None;
219 }
220
221 if !ray.direction.y.is_zero() {
223 let inv_d = Fix128::ONE / ray.direction.y;
224 let mut t0 = (aabb.min.y - ray.origin.y) * inv_d;
225 let mut t1 = (aabb.max.y - ray.origin.y) * inv_d;
226 if t0 > t1 {
227 core::mem::swap(&mut t0, &mut t1);
228 }
229 t_min = if t0 > t_min { t0 } else { t_min };
230 t_max = if t1 < t_max { t1 } else { t_max };
231 if t_min > t_max {
232 return None;
233 }
234 } else if ray.origin.y < aabb.min.y || ray.origin.y > aabb.max.y {
235 return None;
236 }
237
238 if !ray.direction.z.is_zero() {
240 let inv_d = Fix128::ONE / ray.direction.z;
241 let mut t0 = (aabb.min.z - ray.origin.z) * inv_d;
242 let mut t1 = (aabb.max.z - ray.origin.z) * inv_d;
243 if t0 > t1 {
244 core::mem::swap(&mut t0, &mut t1);
245 }
246 t_min = if t0 > t_min { t0 } else { t_min };
247 t_max = if t1 < t_max { t1 } else { t_max };
248 if t_min > t_max {
249 return None;
250 }
251 } else if ray.origin.z < aabb.min.z || ray.origin.z > aabb.max.z {
252 return None;
253 }
254
255 Some((t_min, t_max))
256}
257
258fn aabb_hit_normal(point: Vec3Fix, aabb: &AABB) -> Vec3Fix {
260 let center = Vec3Fix::new(
261 (aabb.min.x + aabb.max.x).half(),
262 (aabb.min.y + aabb.max.y).half(),
263 (aabb.min.z + aabb.max.z).half(),
264 );
265 let half = Vec3Fix::new(
266 (aabb.max.x - aabb.min.x).half(),
267 (aabb.max.y - aabb.min.y).half(),
268 (aabb.max.z - aabb.min.z).half(),
269 );
270 let local = point - center;
271
272 let dx = (local.x.abs() - half.x).abs();
274 let dy = (local.y.abs() - half.y).abs();
275 let dz = (local.z.abs() - half.z).abs();
276
277 if dx < dy && dx < dz {
278 Vec3Fix::new(
279 if local.x >= Fix128::ZERO {
280 Fix128::ONE
281 } else {
282 Fix128::NEG_ONE
283 },
284 Fix128::ZERO,
285 Fix128::ZERO,
286 )
287 } else if dy < dz {
288 Vec3Fix::new(
289 Fix128::ZERO,
290 if local.y >= Fix128::ZERO {
291 Fix128::ONE
292 } else {
293 Fix128::NEG_ONE
294 },
295 Fix128::ZERO,
296 )
297 } else {
298 Vec3Fix::new(
299 Fix128::ZERO,
300 Fix128::ZERO,
301 if local.z >= Fix128::ZERO {
302 Fix128::ONE
303 } else {
304 Fix128::NEG_ONE
305 },
306 )
307 }
308}
309
310#[must_use]
312pub fn ray_capsule(ray: &Ray, capsule: &Capsule, max_t: Fix128) -> Option<RayHit> {
313 let ab = capsule.b - capsule.a;
315 let ao = ray.origin - capsule.a;
316
317 let ab_dot_ab = ab.dot(ab);
319 if ab_dot_ab.is_zero() {
320 let sphere = Sphere::new(capsule.a, capsule.radius);
322 return ray_sphere(ray, &sphere, max_t);
323 }
324
325 let ab_dot_d = ab.dot(ray.direction);
326 let ab_dot_ao = ab.dot(ao);
327
328 let d_perp = ray.direction - ab * (ab_dot_d / ab_dot_ab);
329 let ao_perp = ao - ab * (ab_dot_ao / ab_dot_ab);
330
331 let a_coeff = d_perp.dot(d_perp);
332 let b_coeff = d_perp.dot(ao_perp).double();
333 let c_coeff = ao_perp.dot(ao_perp) - capsule.radius * capsule.radius;
334
335 let discriminant = b_coeff * b_coeff - Fix128::from_int(4) * a_coeff * c_coeff;
336 if discriminant < Fix128::ZERO {
337 let sphere_a = Sphere::new(capsule.a, capsule.radius);
339 let sphere_b = Sphere::new(capsule.b, capsule.radius);
340 let hit_a = ray_sphere(ray, &sphere_a, max_t);
341 let hit_b = ray_sphere(ray, &sphere_b, max_t);
342 return closer_hit(hit_a, hit_b);
343 }
344
345 let sqrt_d = discriminant.sqrt();
346 let two_a = a_coeff.double();
347 if two_a.is_zero() {
348 let sphere_a = Sphere::new(capsule.a, capsule.radius);
351 let sphere_b = Sphere::new(capsule.b, capsule.radius);
352 return closer_hit(
353 ray_sphere(ray, &sphere_a, max_t),
354 ray_sphere(ray, &sphere_b, max_t),
355 );
356 }
357
358 let t = (-b_coeff - sqrt_d) / two_a;
359 if t >= Fix128::ZERO && t <= max_t {
360 let point = ray.at(t);
361 let ap = point - capsule.a;
363 let proj = ap.dot(ab) / ab_dot_ab;
364 if proj >= Fix128::ZERO && proj <= Fix128::ONE {
365 let center_on_axis = capsule.a + ab * proj;
366 let normal = (point - center_on_axis).normalize();
367 return Some(RayHit {
368 t,
369 point,
370 normal,
371 body_index: 0,
372 });
373 }
374 }
375
376 let sphere_a = Sphere::new(capsule.a, capsule.radius);
378 let sphere_b = Sphere::new(capsule.b, capsule.radius);
379 let hit_a = ray_sphere(ray, &sphere_a, max_t);
380 let hit_b = ray_sphere(ray, &sphere_b, max_t);
381 closer_hit(hit_a, hit_b)
382}
383
384#[inline]
386fn closer_hit(a: Option<RayHit>, b: Option<RayHit>) -> Option<RayHit> {
387 match (a, b) {
388 (Some(ha), Some(hb)) => {
389 if ha.t < hb.t {
390 Some(ha)
391 } else {
392 Some(hb)
393 }
394 }
395 (Some(h), None) | (None, Some(h)) => Some(h),
396 (None, None) => None,
397 }
398}
399
400#[must_use]
404pub fn ray_plane(
405 ray: &Ray,
406 plane_normal: Vec3Fix,
407 plane_offset: Fix128,
408 max_t: Fix128,
409) -> Option<RayHit> {
410 let denom = ray.direction.dot(plane_normal);
411
412 if denom.abs() < RAY_PARALLEL_EPSILON {
413 return None; }
415
416 let t = (plane_offset - ray.origin.dot(plane_normal)) / denom;
417
418 if t >= Fix128::ZERO && t <= max_t {
419 let point = ray.at(t);
420 let normal = if denom < Fix128::ZERO {
421 plane_normal
422 } else {
423 -plane_normal
424 };
425 Some(RayHit {
426 t,
427 point,
428 normal,
429 body_index: 0,
430 })
431 } else {
432 None
433 }
434}
435
436#[must_use]
439pub fn raycast_aabbs(ray: &Ray, aabbs: &[(AABB, usize)], max_t: Fix128) -> Option<RayHit> {
440 let mut closest: Option<RayHit> = None;
441 let mut best_t = max_t;
442
443 for &(ref aabb, body_idx) in aabbs {
444 if let Some(mut hit) = ray_aabb(ray, aabb, best_t) {
445 if closest.is_some() && hit.t >= best_t {
447 continue;
448 }
449 hit.body_index = body_idx;
450 best_t = hit.t;
451 closest = Some(hit);
452 }
453 }
454
455 closest
456}
457
458#[must_use]
462pub fn raycast_spheres(ray: &Ray, spheres: &[(Sphere, usize)], max_t: Fix128) -> Option<RayHit> {
463 let mut closest: Option<RayHit> = None;
464 let mut best_t = max_t;
465
466 for &(ref sphere, body_idx) in spheres {
467 if let Some(mut hit) = ray_sphere(ray, sphere, best_t) {
468 if closest.is_some() && hit.t >= best_t {
470 continue;
471 }
472 hit.body_index = body_idx;
473 best_t = hit.t;
474 closest = Some(hit);
475 }
476 }
477
478 closest
479}
480
481#[must_use]
483pub fn raycast_all_spheres(ray: &Ray, spheres: &[(Sphere, usize)], max_t: Fix128) -> Vec<RayHit> {
484 let mut hits = Vec::new();
485
486 for &(ref sphere, body_idx) in spheres {
487 if let Some(mut hit) = ray_sphere(ray, sphere, max_t) {
488 hit.body_index = body_idx;
489 hits.push(hit);
490 }
491 }
492
493 hits.sort_by_key(|a| a.t);
495 hits
496}
497
498#[must_use]
500pub fn raycast_all_aabbs(ray: &Ray, aabbs: &[(AABB, usize)], max_t: Fix128) -> Vec<RayHit> {
501 let mut hits = Vec::new();
502
503 for &(ref aabb, body_idx) in aabbs {
504 if let Some(mut hit) = ray_aabb(ray, aabb, max_t) {
505 hit.body_index = body_idx;
506 hits.push(hit);
507 }
508 }
509
510 hits.sort_by_key(|a| a.t);
511 hits
512}
513
514#[inline]
516#[must_use]
517pub fn raycast_any_spheres(ray: &Ray, spheres: &[(Sphere, usize)], max_t: Fix128) -> bool {
518 for (sphere, _) in spheres {
519 if ray_sphere(ray, sphere, max_t).is_some() {
520 return true;
521 }
522 }
523 false
524}
525
526#[inline]
528#[must_use]
529pub fn raycast_any_aabbs(ray: &Ray, aabbs: &[(AABB, usize)], max_t: Fix128) -> bool {
530 for (aabb, _) in aabbs {
531 if ray_aabb(ray, aabb, max_t).is_some() {
532 return true;
533 }
534 }
535 false
536}
537
538#[must_use]
548pub fn sweep_sphere(
549 sphere: &Sphere,
550 direction: Vec3Fix,
551 target_sphere: &Sphere,
552 max_t: Fix128,
553) -> Option<RayHit> {
554 let expanded = Sphere::new(target_sphere.center, target_sphere.radius + sphere.radius);
556 let ray = Ray::new(sphere.center, direction);
557 sweep_ray_sphere(&ray, &expanded, max_t)
558}
559
560#[cfg(all(test, feature = "std"))]
561mod tests {
562 use super::*;
563
564 #[test]
565 fn test_ray_sphere_hit() {
566 let ray = Ray::new(Vec3Fix::from_int(-5, 0, 0), Vec3Fix::UNIT_X);
567 let sphere = Sphere::new(Vec3Fix::ZERO, Fix128::ONE);
568
569 let hit = ray_sphere(&ray, &sphere, Fix128::from_int(100)).unwrap();
570 let expected_t = Fix128::from_int(4); let error = (hit.t - expected_t).abs();
573 assert!(error < Fix128::ONE, "t should be ~4, got {:?}", hit.t);
574 }
575
576 #[test]
577 fn test_ray_sphere_miss() {
578 let ray = Ray::new(Vec3Fix::from_int(-5, 5, 0), Vec3Fix::UNIT_X);
579 let sphere = Sphere::new(Vec3Fix::ZERO, Fix128::ONE);
580
581 assert!(ray_sphere(&ray, &sphere, Fix128::from_int(100)).is_none());
582 }
583
584 #[test]
585 fn test_ray_aabb_hit() {
586 let ray = Ray::new(Vec3Fix::from_int(-5, 0, 0), Vec3Fix::UNIT_X);
587 let aabb = AABB::new(Vec3Fix::from_int(-1, -1, -1), Vec3Fix::from_int(1, 1, 1));
588
589 let hit = ray_aabb(&ray, &aabb, Fix128::from_int(100)).unwrap();
590 let expected_t = Fix128::from_int(4); let error = (hit.t - expected_t).abs();
592 assert!(error < Fix128::ONE, "t should be ~4");
593 }
594
595 #[test]
596 fn test_ray_aabb_miss() {
597 let ray = Ray::new(Vec3Fix::from_int(-5, 5, 0), Vec3Fix::UNIT_X);
598 let aabb = AABB::new(Vec3Fix::from_int(-1, -1, -1), Vec3Fix::from_int(1, 1, 1));
599
600 assert!(ray_aabb(&ray, &aabb, Fix128::from_int(100)).is_none());
601 }
602
603 #[test]
604 fn test_ray_plane() {
605 let ray = Ray::new(
606 Vec3Fix::from_int(0, 10, 0),
607 Vec3Fix::new(Fix128::ZERO, Fix128::NEG_ONE, Fix128::ZERO), );
609
610 let hit = ray_plane(&ray, Vec3Fix::UNIT_Y, Fix128::ZERO, Fix128::from_int(100)).unwrap();
611 let expected_t = Fix128::from_int(10);
612 let error = (hit.t - expected_t).abs();
613 assert!(error < Fix128::ONE, "Should hit ground plane at t=10");
614 }
615
616 #[test]
617 fn test_sweep_sphere() {
618 let moving = Sphere::new(Vec3Fix::from_int(-5, 0, 0), Fix128::ONE);
619 let target = Sphere::new(Vec3Fix::ZERO, Fix128::ONE);
620
621 let hit = sweep_sphere(&moving, Vec3Fix::UNIT_X, &target, Fix128::from_int(100));
622 assert!(hit.is_some(), "Swept sphere should hit target");
623 let t = hit.unwrap().t;
624 let error = (t - Fix128::from_int(3)).abs();
626 assert!(error < Fix128::ONE, "Should hit at approximately t=3");
627 }
628
629 #[test]
630 fn test_raycast_all_spheres() {
631 let spheres = vec![
632 (Sphere::new(Vec3Fix::from_int(3, 0, 0), Fix128::ONE), 0),
633 (Sphere::new(Vec3Fix::from_int(6, 0, 0), Fix128::ONE), 1),
634 (Sphere::new(Vec3Fix::from_int(9, 0, 0), Fix128::ONE), 2),
635 ];
636 let ray = Ray::new(Vec3Fix::from_int(-5, 0, 0), Vec3Fix::UNIT_X);
637 let hits = raycast_all_spheres(&ray, &spheres, Fix128::from_int(100));
638 assert_eq!(hits.len(), 3, "Should hit all 3 spheres");
639 assert_eq!(hits[0].body_index, 0);
641 assert_eq!(hits[1].body_index, 1);
642 assert_eq!(hits[2].body_index, 2);
643 }
644
645 #[test]
646 fn test_raycast_any_spheres() {
647 let spheres = vec![(Sphere::new(Vec3Fix::from_int(3, 0, 0), Fix128::ONE), 0)];
648 let ray = Ray::new(Vec3Fix::from_int(-5, 0, 0), Vec3Fix::UNIT_X);
649 assert!(raycast_any_spheres(&ray, &spheres, Fix128::from_int(100)));
650
651 let miss_ray = Ray::new(Vec3Fix::from_int(-5, 10, 0), Vec3Fix::UNIT_X);
652 assert!(!raycast_any_spheres(
653 &miss_ray,
654 &spheres,
655 Fix128::from_int(100)
656 ));
657 }
658
659 #[test]
660 fn test_raycast_all_aabbs() {
661 let aabbs = vec![
662 (
663 AABB::new(Vec3Fix::from_int(2, -1, -1), Vec3Fix::from_int(4, 1, 1)),
664 0,
665 ),
666 (
667 AABB::new(Vec3Fix::from_int(6, -1, -1), Vec3Fix::from_int(8, 1, 1)),
668 1,
669 ),
670 ];
671 let ray = Ray::new(Vec3Fix::from_int(-5, 0, 0), Vec3Fix::UNIT_X);
672 let hits = raycast_all_aabbs(&ray, &aabbs, Fix128::from_int(100));
673 assert_eq!(hits.len(), 2, "Should hit both AABBs");
674 assert_eq!(hits[0].body_index, 0);
675 assert_eq!(hits[1].body_index, 1);
676 }
677
678 #[test]
679 fn test_ray_capsule() {
680 let ray = Ray::new(Vec3Fix::from_int(-5, 0, 0), Vec3Fix::UNIT_X);
681 let capsule = Capsule::new(
682 Vec3Fix::from_int(0, -2, 0),
683 Vec3Fix::from_int(0, 2, 0),
684 Fix128::ONE,
685 );
686
687 let hit = ray_capsule(&ray, &capsule, Fix128::from_int(100));
688 assert!(hit.is_some(), "Ray should hit capsule");
689 }
690
691 #[test]
692 fn raycast_spheres_returns_nearest_hit_regardless_of_list_order() {
693 let ray = Ray::new(Vec3Fix::from_int(-5, 0, 0), Vec3Fix::UNIT_X);
700 let far = (
701 Sphere::new(Vec3Fix::from_int(10, 0, 0), Fix128::from_int(2)),
702 7usize,
703 );
704 let off = (Sphere::new(Vec3Fix::from_int(0, 5, 0), Fix128::ONE), 3usize);
705 let mid = (Sphere::new(Vec3Fix::from_int(0, 0, 0), Fix128::ONE), 1usize);
706 let near = (
707 Sphere::new(Vec3Fix::from_int(-3, 0, 0), Fix128::ONE),
708 9usize,
709 );
710 let behind = (
711 Sphere::new(Vec3Fix::from_int(-20, 0, 0), Fix128::ONE),
712 4usize,
713 );
714 let spheres = [far, off, mid, near, behind];
715 let max_t = Fix128::from_int(100);
716
717 let hit = raycast_spheres(&ray, &spheres, max_t);
718 assert_eq!(
719 hit,
720 Some(RayHit {
721 t: Fix128::ONE,
722 point: Vec3Fix::from_int(-4, 0, 0),
723 normal: Vec3Fix::from_int(-1, 0, 0),
724 body_index: 9,
725 })
726 );
727 let mut reversed = spheres;
729 reversed.reverse();
730 assert_eq!(raycast_spheres(&ray, &reversed, max_t), hit);
731 let all = raycast_all_spheres(&ray, &spheres, max_t);
733 assert_eq!(all.first().copied(), hit);
734 assert_eq!(
735 all.iter().map(|h| h.body_index).collect::<Vec<_>>(),
736 vec![9, 1, 7]
737 );
738
739 let without_near = [far, off, mid, behind];
741 let second = raycast_spheres(&ray, &without_near, max_t);
742 assert_eq!(
743 second.map(|h| (h.t, h.body_index)),
744 Some((Fix128::from_int(4), 1))
745 );
746 assert_eq!(second.map(|h| h.point), Some(Vec3Fix::from_int(-1, 0, 0)));
747 assert_eq!(second.map(|h| h.normal), Some(Vec3Fix::from_int(-1, 0, 0)));
748
749 assert!(raycast_spheres(&ray, &spheres, Fix128::from_ratio(1, 2)).is_none());
751 assert_eq!(
752 raycast_spheres(&ray, &spheres, Fix128::from_int(3)).map(|h| h.body_index),
753 Some(9)
754 );
755 assert!(raycast_spheres(&ray, &[off, behind], max_t).is_none());
757 assert!(raycast_spheres(&ray, &[], max_t).is_none());
758 }
759
760 #[test]
761 fn raycast_aabbs_returns_nearest_hit_regardless_of_list_order() {
762 let ray = Ray::new(Vec3Fix::from_int(-5, 0, 0), Vec3Fix::UNIT_X);
769 let bx = |x0: i64, x1: i64, y0: i64, y1: i64| {
770 AABB::new(Vec3Fix::from_int(x0, y0, -1), Vec3Fix::from_int(x1, y1, 1))
771 };
772 let a = (bx(2, 4, -1, 1), 0usize);
773 let b = (bx(6, 8, -1, 1), 1usize);
774 let off = (bx(2, 4, 3, 5), 2usize);
775 let near = (bx(-1, 1, -1, 1), 5usize);
776 let behind = (bx(-9, -7, -1, 1), 8usize);
777 let aabbs = [a, b, off, near, behind];
778 let max_t = Fix128::from_int(100);
779
780 let hit = raycast_aabbs(&ray, &aabbs, max_t);
781 assert_eq!(
782 hit,
783 Some(RayHit {
784 t: Fix128::from_int(4),
785 point: Vec3Fix::from_int(-1, 0, 0),
786 normal: Vec3Fix::from_int(-1, 0, 0),
787 body_index: 5,
788 })
789 );
790 let mut reversed = aabbs;
791 reversed.reverse();
792 assert_eq!(raycast_aabbs(&ray, &reversed, max_t), hit);
793 let all = raycast_all_aabbs(&ray, &aabbs, max_t);
794 assert_eq!(all.first().copied(), hit);
795 assert_eq!(
796 all.iter().map(|h| h.body_index).collect::<Vec<_>>(),
797 vec![5, 0, 1]
798 );
799
800 let second = raycast_aabbs(&ray, &[a, b, off, behind], max_t);
802 assert_eq!(
803 second.map(|h| (h.t, h.body_index)),
804 Some((Fix128::from_int(7), 0))
805 );
806 let inside = (bx(-6, -4, -1, 1), 6usize);
808 let exit = raycast_aabbs(&ray, &[inside], max_t);
809 assert_eq!(
810 exit.map(|h| (h.t, h.body_index, h.normal)),
811 Some((Fix128::ONE, 6, Vec3Fix::from_int(1, 0, 0)))
812 );
813 assert!(raycast_aabbs(&ray, &aabbs, Fix128::from_int(3)).is_none());
815 assert_eq!(
816 raycast_aabbs(&ray, &aabbs, Fix128::from_int(5)).map(|h| h.body_index),
817 Some(5)
818 );
819 assert!(raycast_aabbs(&ray, &[off, behind], max_t).is_none());
820 assert!(raycast_aabbs(&ray, &[], max_t).is_none());
821 }
822}