1use phys_geom::math::{Real, *};
16use phys_geom::shape::Cuboid;
17use phys_geom::ComputeAabb3;
18
19use crate::{Raycast, RaycastHitResult};
20
21impl Raycast for Cuboid {
22 fn raycast(
23 &self,
24 local_ray: phys_geom::Ray,
25 max_distance: Real,
26 discard_inside_hit: bool,
27 ) -> Option<RaycastHitResult> {
28 let one_over_direction = local_ray.one_over_direction();
29 let aabb = self.compute_aabb();
30
31 if let Some((entry, max_entry)) = aabb.raycast_by_one_over_direction_impl(
32 local_ray.origin,
33 one_over_direction,
34 max_distance,
35 ) {
36 if max_entry <= 0.0 {
37 if discard_inside_hit {
38 return None;
39 }
40 return Some(RaycastHitResult {
41 distance: 0.0,
42 normal: -local_ray.direction,
43 });
44 }
45
46 let mut normal: UnitVec3;
47 let inverse;
48
49 #[allow(clippy::float_cmp)]
50 if entry.x == max_entry {
52 normal = Vec3::x_axis();
53 inverse = local_ray.direction.x > 0.0;
54 } else if entry.y == max_entry {
55 normal = Vec3::y_axis();
56 inverse = local_ray.direction.y > 0.0;
57 } else {
58 normal = Vec3::z_axis();
59 inverse = local_ray.direction.z > 0.0;
60 }
61
62 if inverse {
63 normal = -normal;
64 }
65
66 Some(RaycastHitResult {
67 distance: max_entry,
68 normal,
69 })
70 } else {
71 None
72 }
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use phys_geom::math::*;
79 use phys_geom::Ray;
80
81 use super::*;
82 #[test]
83 fn test_raycast() {
84 let cuboid = Cuboid::new(Vec3::new(1.0, 1.0, 1.0) * 2.0);
85 let ray_z = Ray::new_with_vec3(Point3::new(0.0, 0.0, 2.0), Vec3::new(0.0, 0.0, -1.0));
86 assert_eq!(
87 cuboid.raycast(ray_z, 5.0, false),
88 Some(RaycastHitResult {
89 distance: 1.0,
90 normal: UnitVec3::new_normalize(Vec3::new(0.0, 0.0, 1.0)),
91 })
92 );
93
94 assert_eq!(cuboid.raycast(ray_z, 0.5, false), None);
95
96 assert_eq!(
97 cuboid.raycast(
98 Ray::new_with_vec3(Point3::new(2.0, 0.0, 0.0), Vec3::new(-1.0, 0.0, 0.0)),
99 5.0,
100 false
101 ),
102 Some(RaycastHitResult {
103 distance: 1.0,
104 normal: UnitVec3::new_normalize(Vec3::new(1.0, 0.0, 0.0)),
105 })
106 );
107
108 assert_eq!(
109 cuboid.raycast(
110 Ray::new_with_vec3(Point3::new(0.0, 2.0, 0.0), Vec3::new(0.0, -1.0, 0.0)),
111 5.0,
112 false
113 ),
114 Some(RaycastHitResult {
115 distance: 1.0,
116 normal: Vec3::y_axis(),
117 })
118 );
119
120 assert_eq!(
121 cuboid.raycast(
122 Ray::new_with_vec3(Point3::new(-2.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)),
123 5.0,
124 false
125 ),
126 Some(RaycastHitResult {
127 distance: 1.0,
128 normal: -Vec3::x_axis(),
129 })
130 );
131
132 assert_eq!(
133 cuboid.raycast(
134 Ray::new_with_vec3(Point3::new(0.0, -2.0, 0.0), Vec3::new(0.0, 1.0, 0.0)),
135 5.0,
136 false
137 ),
138 Some(RaycastHitResult {
139 distance: 1.0,
140 normal: -Vec3::y_axis(),
141 })
142 );
143
144 assert_eq!(
145 cuboid.raycast(
146 Ray::new_with_vec3(Point3::new(0.0, 0.0, -2.0), Vec3::new(0.0, 0.0, 1.0)),
147 5.0,
148 false
149 ),
150 Some(RaycastHitResult {
151 distance: 1.0,
152 normal: -Vec3::z_axis(),
153 })
154 );
155
156 {
157 let ray = Ray::new_with_vec3(Point3::new(0.0, 0.0, 2.0), Vec3::new(3.0, 0.0, -2.0));
158 assert_eq!(cuboid.raycast(ray, 10.0, false), None);
159 }
160 }
161
162 #[test]
163 fn test_raycast_surface() {
164 let cuboid = Cuboid::new(Vec3::new(1.0, 1.0, 1.0) * 2.0);
165 let ray = Ray::new_with_vec3(Point3::new(0.0, 0.0, 1.0), Vec3::new(0.0, 0.0, 1.0));
166 assert_eq!(cuboid.raycast(ray, 10.0, true), None);
167 assert_eq!(
168 cuboid.raycast(ray, 10.0, false),
169 Some(RaycastHitResult {
170 distance: 0.0,
171 normal: -ray.direction,
172 })
173 );
174 }
175
176 #[test]
178 fn test_raycast_inner() {
179 let cuboid = Cuboid::new(Vec3::new(1.0, 1.0, 1.0) * 2.0);
180 let ray = Ray::new_with_vec3(Point3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, -1.0));
181 assert_eq!(
182 cuboid.raycast(ray, 5.0, false),
183 Some(RaycastHitResult {
184 distance: 0.0,
185 normal: -ray.direction,
186 })
187 );
188
189 assert_eq!(cuboid.raycast(ray, 5.0, true), None);
190 }
191}