@@ -344,22 +344,56 @@ impl Aabb {
344
344
345
345
/// Returns `true` if the given ray intersects with this AABB. Ray length is infinite.
346
346
///
347
- /// # Panics
347
+ /// Semantically equivalent to `self.intersects_ray(ray_from, ray_dir).is_some()`; might be microscopically faster.
348
+ ///
349
+ /// # Panics (Debug)
348
350
/// If `self.size` is negative.
349
351
#[ inline]
350
- pub fn intersects_ray ( self , from : Vector3 , dir : Vector3 ) -> bool {
352
+ pub fn intersects_ray ( self , ray_from : Vector3 , ray_dir : Vector3 ) -> bool {
353
+ let ( tnear, tfar) = self . compute_ray_tnear_tfar ( ray_from, ray_dir) ;
354
+
355
+ tnear <= tfar
356
+ }
357
+
358
+ /// Returns the point where the given (infinite) ray intersects with this AABB, or `None` if there is no intersection.
359
+ ///
360
+ /// # Panics (Debug)
361
+ /// If `self.size` is negative, or if `ray_dir` is zero. Note that this differs from Godot, which treats rays that degenerate to points as
362
+ /// intersecting if inside, and not if outside the AABB.
363
+ #[ inline]
364
+ pub fn intersect_ray ( self , ray_from : Vector3 , ray_dir : Vector3 ) -> Option < Vector3 > {
365
+ let ( tnear, tfar) = self . compute_ray_tnear_tfar ( ray_from, ray_dir) ;
366
+
367
+ if tnear <= tfar {
368
+ // if tnear < 0: the ray starts inside the box -> take other intersection point.
369
+ let t = if tnear < 0.0 { tfar } else { tnear } ;
370
+ Some ( ray_from + ray_dir * t)
371
+ } else {
372
+ None
373
+ }
374
+ }
375
+
376
+ // Credits: https://tavianator.com/2011/ray_box.html
377
+ fn compute_ray_tnear_tfar ( self , ray_from : Vector3 , ray_dir : Vector3 ) -> ( real , real ) {
351
378
self . assert_nonnegative ( ) ;
379
+ debug_assert ! (
380
+ ray_dir != Vector3 :: ZERO ,
381
+ "ray direction must not be zero; use contains_point() for point checks"
382
+ ) ;
352
383
353
- let tmin = ( self . position - from) / dir;
354
- let tmax = ( self . end ( ) - from) / dir;
384
+ // Note: leads to -inf/inf for each component that is 0. This should generally balance out, unless all are zero.
385
+ let recip_dir = ray_dir. recip ( ) ;
386
+
387
+ let tmin = ( self . position - ray_from) * recip_dir;
388
+ let tmax = ( self . end ( ) - ray_from) * recip_dir;
355
389
356
390
let t1 = tmin. coord_min ( tmax) ;
357
391
let t2 = tmin. coord_max ( tmax) ;
358
392
359
393
let tnear = t1. x . max ( t1. y ) . max ( t1. z ) ;
360
- let tfar = t2. y . min ( t2. x ) . min ( t2. z ) ;
394
+ let tfar = t2. x . min ( t2. y ) . min ( t2. z ) ;
361
395
362
- tnear <= tfar
396
+ ( tnear, tfar)
363
397
}
364
398
365
399
/// Returns `true` if the given ray intersects with this AABB. Segment length is finite.
@@ -571,6 +605,7 @@ mod test {
571
605
} ;
572
606
let from1 = Vector3 :: new ( 1.0 , 1.0 , -1.0 ) ;
573
607
let dir1 = Vector3 :: new ( 0.0 , 0.0 , 1.0 ) ;
608
+
574
609
assert ! ( aabb1. intersects_ray( from1, dir1) ) ;
575
610
576
611
// Test case 2: Ray misses the AABB
@@ -619,15 +654,159 @@ mod test {
619
654
assert ! ( aabb6. intersects_ray( from6, dir6) ) ;
620
655
}
621
656
657
+ #[ test] // Ported from Godot tests.
658
+ fn test_intersect_ray_2 ( ) {
659
+ let aabb = Aabb {
660
+ position : Vector3 :: new ( -1.5 , 2.0 , -2.5 ) ,
661
+ size : Vector3 :: new ( 4.0 , 5.0 , 6.0 ) ,
662
+ } ;
663
+
664
+ assert_eq ! (
665
+ aabb. intersect_ray( Vector3 :: new( -100.0 , 3.0 , 0.0 ) , Vector3 :: new( 1.0 , 0.0 , 0.0 ) ) ,
666
+ Some ( Vector3 :: new( -1.5 , 3.0 , 0.0 ) ) ,
667
+ "intersect_ray(), ray points directly at AABB -> Some"
668
+ ) ;
669
+
670
+ assert_eq ! (
671
+ aabb. intersect_ray( Vector3 :: new( 10.0 , 10.0 , 0.0 ) , Vector3 :: new( 0.0 , 1.0 , 0.0 ) ) ,
672
+ None ,
673
+ "intersect_ray(), ray parallel and outside the AABB -> None"
674
+ ) ;
675
+
676
+ assert_eq ! (
677
+ aabb. intersect_ray( Vector3 :: ONE , Vector3 :: new( 0.0 , 1.0 , 0.0 ) ) ,
678
+ Some ( Vector3 :: new( 1.0 , 2.0 , 1.0 ) ) ,
679
+ "intersect_ray(), ray originating inside the AABB -> Some"
680
+ ) ;
681
+
682
+ assert_eq ! (
683
+ aabb. intersect_ray( Vector3 :: new( -10.0 , 0.0 , 0.0 ) , Vector3 :: new( -1.0 , 0.0 , 0.0 ) ) ,
684
+ None ,
685
+ "intersect_ray(), ray points away from AABB -> None"
686
+ ) ;
687
+
688
+ assert_eq ! (
689
+ aabb. intersect_ray( Vector3 :: new( 0.0 , 0.0 , 0.0 ) , Vector3 :: ONE ) ,
690
+ Some ( Vector3 :: new( 2.0 , 2.0 , 2.0 ) ) ,
691
+ "intersect_ray(), ray along the AABB diagonal -> Some"
692
+ ) ;
693
+
694
+ assert_eq ! (
695
+ aabb. intersect_ray(
696
+ aabb. position + Vector3 :: splat( 0.0001 ) ,
697
+ Vector3 :: new( -1.0 , 0.0 , 0.0 )
698
+ ) ,
699
+ Some ( Vector3 :: new( -1.5 , 2.0001 , -2.4999 ) ) ,
700
+ "intersect_ray(), ray starting on the AABB's edge -> Some"
701
+ ) ;
702
+
703
+ assert_eq ! (
704
+ aabb. intersect_ray( Vector3 :: new( 0.0 , 0.0 , 0.0 ) , Vector3 :: new( 0.0 , 1.0 , 0.0 ) ) ,
705
+ Some ( Vector3 :: new( 0.0 , 2.0 , 0.0 ) ) ,
706
+ "intersect_ray(): ray has 2 axes parallel to AABB -> Some"
707
+ ) ;
708
+ }
709
+
710
+ #[ test] // Ported from Godot tests.
711
+ fn test_intersect_aabb ( ) {
712
+ let aabb_big = Aabb {
713
+ position : Vector3 :: new ( -1.5 , 2.0 , -2.5 ) ,
714
+ size : Vector3 :: new ( 4.0 , 5.0 , 6.0 ) ,
715
+ } ;
716
+
717
+ let aabb_small = Aabb {
718
+ position : Vector3 :: new ( -1.5 , 2.0 , -2.5 ) ,
719
+ size : Vector3 :: ONE ,
720
+ } ;
721
+ assert ! (
722
+ aabb_big. intersects( aabb_small) ,
723
+ "intersects() with fully contained AABB (touching the edge) should return true."
724
+ ) ;
725
+
726
+ let aabb_small = Aabb {
727
+ position : Vector3 :: new ( 0.5 , 1.5 , -2.0 ) ,
728
+ size : Vector3 :: ONE ,
729
+ } ;
730
+ assert ! (
731
+ aabb_big. intersects( aabb_small) ,
732
+ "intersects() with partially contained AABB (overflowing on Y axis) should return true."
733
+ ) ;
734
+
735
+ let aabb_small = Aabb {
736
+ position : Vector3 :: new ( 10.0 , -10.0 , -10.0 ) ,
737
+ size : Vector3 :: ONE ,
738
+ } ;
739
+ assert ! (
740
+ !aabb_big. intersects( aabb_small) ,
741
+ "intersects() with non-contained AABB should return false."
742
+ ) ;
743
+
744
+ let aabb_small = Aabb {
745
+ position : Vector3 :: new ( -1.5 , 2.0 , -2.5 ) ,
746
+ size : Vector3 :: ONE ,
747
+ } ;
748
+ let inter = aabb_big. intersection ( aabb_small) ;
749
+ assert ! (
750
+ inter. unwrap( ) . approx_eq( & aabb_small) ,
751
+ "intersection() with fully contained AABB should return the smaller AABB."
752
+ ) ;
753
+
754
+ let aabb_small = Aabb {
755
+ position : Vector3 :: new ( 0.5 , 1.5 , -2.0 ) ,
756
+ size : Vector3 :: ONE ,
757
+ } ;
758
+ let expected = Aabb {
759
+ position : Vector3 :: new ( 0.5 , 2.0 , -2.0 ) ,
760
+ size : Vector3 :: new ( 1.0 , 0.5 , 1.0 ) ,
761
+ } ;
762
+ let inter = aabb_big. intersection ( aabb_small) ;
763
+ assert ! (
764
+ inter. unwrap( ) . approx_eq( & expected) ,
765
+ "intersect() with partially contained AABB (overflowing on Y axis) should match expected."
766
+ ) ;
767
+
768
+ let aabb_small = Aabb {
769
+ position : Vector3 :: new ( 10.0 , -10.0 , -10.0 ) ,
770
+ size : Vector3 :: ONE ,
771
+ } ;
772
+ let inter = aabb_big. intersection ( aabb_small) ;
773
+ assert ! (
774
+ inter. is_none( ) ,
775
+ "intersect() with non-contained AABB should return None."
776
+ ) ;
777
+ }
778
+
779
+ #[ test]
780
+ #[ should_panic]
781
+ #[ cfg( debug_assertions) ]
782
+ fn test_intersect_ray_zero_dir_inside ( ) {
783
+ let aabb = Aabb {
784
+ position : Vector3 :: new ( -1.5 , 2.0 , -2.5 ) ,
785
+ size : Vector3 :: new ( 4.0 , 5.0 , 6.0 ) ,
786
+ } ;
787
+
788
+ aabb. intersect_ray ( Vector3 :: new ( -1.0 , 3.0 , -2.0 ) , Vector3 :: ZERO ) ;
789
+ }
790
+
791
+ #[ test]
792
+ #[ should_panic]
793
+ #[ cfg( debug_assertions) ]
794
+ fn test_intersect_ray_zero_dir_outside ( ) {
795
+ let aabb = Aabb {
796
+ position : Vector3 :: new ( -1.5 , 2.0 , -2.5 ) ,
797
+ size : Vector3 :: new ( 4.0 , 5.0 , 6.0 ) ,
798
+ } ;
799
+
800
+ aabb. intersect_ray ( Vector3 :: new ( -1000.0 , 3.0 , -2.0 ) , Vector3 :: ZERO ) ;
801
+ }
802
+
622
803
#[ test]
623
804
fn test_intersects_plane ( ) {
624
- // Create an AABB
625
805
let aabb = Aabb {
626
806
position : Vector3 :: new ( -1.0 , -1.0 , -1.0 ) ,
627
807
size : Vector3 :: new ( 2.0 , 2.0 , 2.0 ) ,
628
808
} ;
629
809
630
- // Define planes for testing
631
810
let plane_inside = Plane {
632
811
normal : Vector3 :: new ( 1.0 , 0.0 , 0.0 ) ,
633
812
d : 0.0 ,
@@ -655,6 +834,32 @@ mod test {
655
834
assert ! ( !aabb. intersects_plane( plane_parallel) ) ;
656
835
}
657
836
837
+ #[ test] // Ported from Godot tests.
838
+ fn test_intersects_plane_2 ( ) {
839
+ let aabb_big = Aabb {
840
+ position : Vector3 :: new ( -1.5 , 2.0 , -2.5 ) ,
841
+ size : Vector3 :: new ( 4.0 , 5.0 , 6.0 ) ,
842
+ } ;
843
+
844
+ let plane1 = Plane :: new ( Vector3 :: new ( 0.0 , 1.0 , 0.0 ) , 4.0 ) ;
845
+ assert ! (
846
+ aabb_big. intersects_plane( plane1) ,
847
+ "intersects_plane() should return true (plane near top)."
848
+ ) ;
849
+
850
+ let plane2 = Plane :: new ( Vector3 :: new ( 0.0 , -1.0 , 0.0 ) , -4.0 ) ;
851
+ assert ! (
852
+ aabb_big. intersects_plane( plane2) ,
853
+ "intersects_plane() should return true (plane near bottom)."
854
+ ) ;
855
+
856
+ let plane3 = Plane :: new ( Vector3 :: new ( 0.0 , 1.0 , 0.0 ) , 200.0 ) ;
857
+ assert ! (
858
+ !aabb_big. intersects_plane( plane3) ,
859
+ "intersects_plane() should return false (plane far away)."
860
+ ) ;
861
+ }
862
+
658
863
#[ test]
659
864
fn test_aabb_intersects_segment ( ) {
660
865
let aabb = Aabb {
@@ -672,4 +877,48 @@ mod test {
672
877
let to = Vector3 :: new ( -1.0 , 1.0 , 1.0 ) ;
673
878
assert ! ( !aabb. intersects_segment( from, to) ) ;
674
879
}
880
+
881
+ #[ test] // Ported from Godot tests.
882
+ fn test_intersects_segment_2 ( ) {
883
+ let aabb = Aabb {
884
+ position : Vector3 :: new ( -1.5 , 2.0 , -2.5 ) ,
885
+ size : Vector3 :: new ( 4.0 , 5.0 , 6.0 ) ,
886
+ } ;
887
+
888
+ // True cases.
889
+ assert ! (
890
+ aabb. intersects_segment( Vector3 :: new( 1.0 , 3.0 , 0.0 ) , Vector3 :: new( 0.0 , 3.0 , 0.0 ) ) ,
891
+ "intersects_segment(), segment fully inside -> true"
892
+ ) ;
893
+ assert ! (
894
+ aabb. intersects_segment( Vector3 :: new( 0.0 , 3.0 , 0.0 ) , Vector3 :: new( 0.0 , -300.0 , 0.0 ) ) ,
895
+ "intersects_segment(), segment crossing the box -> true"
896
+ ) ;
897
+ assert ! (
898
+ aabb. intersects_segment(
899
+ Vector3 :: new( -50.0 , 3.0 , -50.0 ) ,
900
+ Vector3 :: new( 50.0 , 3.0 , 50.0 )
901
+ ) ,
902
+ "intersects_segment(), diagonal crossing the box -> true"
903
+ ) ;
904
+
905
+ // False case.
906
+ assert ! (
907
+ !aabb. intersects_segment(
908
+ Vector3 :: new( -50.0 , 25.0 , -50.0 ) ,
909
+ Vector3 :: new( 50.0 , 25.0 , 50.0 )
910
+ ) ,
911
+ "intersects_segment(), segment above the box -> false"
912
+ ) ;
913
+
914
+ // Degenerate segments (points).
915
+ assert ! (
916
+ aabb. intersects_segment( Vector3 :: new( 0.0 , 3.0 , 0.0 ) , Vector3 :: new( 0.0 , 3.0 , 0.0 ) ) ,
917
+ "intersects_segment(), segment of length 0 *inside* the box -> true"
918
+ ) ;
919
+ assert ! (
920
+ !aabb. intersects_segment( Vector3 :: new( 0.0 , 300.0 , 0.0 ) , Vector3 :: new( 0.0 , 300.0 , 0.0 ) ) ,
921
+ "intersects_segment(), segment of length 0 *outside* the box -> false"
922
+ ) ;
923
+ }
675
924
}
0 commit comments