1use glam::{Quat, Vec3, Vec3A};
2
3#[derive(Debug, Clone, Copy)]
4pub struct Ray {
5 pub origin: Vec3A,
6 pub direction: Vec3A, }
8
9impl Ray {
10 #[inline]
11 pub fn new(origin: impl Into<Vec3A>, direction: impl Into<Vec3A>) -> Self {
12 let dir = direction.into().normalize();
13 debug_assert!(dir.is_finite(), "Ray direction must be non-zero");
14 Self {
15 origin: origin.into(),
16 direction: dir,
17 }
18 }
19
20 #[inline]
22 pub fn at(self, t: f32) -> Vec3A {
23 self.origin + self.direction * t
24 }
25
26 #[inline]
29 pub fn intersect_bounds(self, min: Vec3A, max: Vec3A) -> Option<f32> {
30 let inv_dir = self.direction.recip();
31
32 let t0 = (min - self.origin) * inv_dir;
33 let t1 = (max - self.origin) * inv_dir;
34
35 let tmin_vec = t0.min(t1);
36 let tmax_vec = t0.max(t1);
37
38 let tmin = tmin_vec.x.max(tmin_vec.y).max(tmin_vec.z);
39 let tmax = tmax_vec.x.min(tmax_vec.y).min(tmax_vec.z);
40
41 if tmin <= tmax && tmax > 0.0 {
42 Some(if tmin > 0.0 { tmin } else { tmax })
43 } else {
44 None
45 }
46 }
47
48 #[inline]
50 pub fn intersect_aabb(self, aabb: crate::aabb::Aabb) -> Option<f32> {
51 self.intersect_bounds(aabb.min, aabb.max)
52 }
53
54 #[inline]
57 pub fn intersect_triangle(
58 self,
59 v0: impl Into<Vec3A>,
60 v1: impl Into<Vec3A>,
61 v2: impl Into<Vec3A>,
62 ) -> Option<f32> {
63 let v0 = v0.into();
64 let v1 = v1.into();
65 let v2 = v2.into();
66 let edge1 = v1 - v0;
67 let edge2 = v2 - v0;
68
69 let h = self.direction.cross(edge2);
70 let a = edge1.dot(h);
71
72 if a.abs() < 1e-8 {
74 return None;
75 }
76
77 let f = 1.0 / a;
78 let s = self.origin - v0;
79 let u = f * s.dot(h);
80
81 if !(0.0..=1.0).contains(&u) {
82 return None;
83 }
84
85 let q = s.cross(edge1);
86 let v = f * self.direction.dot(q);
87
88 if v < 0.0 || u + v > 1.0 {
89 return None;
90 }
91
92 let t = f * edge2.dot(q);
93
94 if t > 1e-8 {
95 Some(t)
96 } else {
97 None
98 }
99 }
100
101 #[inline]
104 pub fn intersect_obb(
105 self,
106 center: impl Into<Vec3A>,
107 half_extents: impl Into<Vec3A>,
108 rotation: Quat,
109 ) -> Option<f32> {
110 let c = center.into();
111 let he = half_extents.into();
112 let inv_rot = rotation.inverse();
113
114 let local_origin = Vec3A::from(inv_rot * Vec3::from(self.origin - c));
117 let local_direction = Vec3A::from(inv_rot * Vec3::from(self.direction));
118
119 let local_ray = Ray {
122 origin: local_origin,
123 direction: local_direction,
124 };
125
126 local_ray.intersect_bounds(-he, he)
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn test_ray_intersect_aabb_hit() {
137 let ray = Ray::new(Vec3::new(0.0, 0.0, -5.0), Vec3::new(0.0, 0.0, 1.0));
138 let aabb = crate::aabb::Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
139
140 let t = ray.intersect_aabb(aabb);
141 assert!(t.is_some());
142 assert!((t.unwrap() - 4.0).abs() < 1e-5); }
144
145 #[test]
146 fn test_ray_intersect_aabb_miss() {
147 let ray = Ray::new(Vec3::new(0.0, 5.0, -5.0), Vec3::new(0.0, 0.0, 1.0));
148 let aabb = crate::aabb::Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
149
150 let t = ray.intersect_aabb(aabb);
151 assert!(t.is_none());
152 }
153
154 #[test]
155 fn test_ray_intersect_aabb_inside() {
156 let ray = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 1.0));
157 let aabb = crate::aabb::Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
158
159 let t = ray.intersect_aabb(aabb);
160 assert!(t.is_some());
161 assert!((t.unwrap() - 1.0).abs() < 1e-5); }
163
164 #[test]
165 fn test_ray_intersect_aabb_behind() {
166 let ray = Ray::new(Vec3::new(0.0, 0.0, 5.0), Vec3::new(0.0, 0.0, 1.0));
167 let aabb = crate::aabb::Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
168
169 let t = ray.intersect_aabb(aabb);
170 assert!(t.is_none()); }
172
173 #[test]
174 fn test_ray_intersect_obb() {
175 let origin = Vec3::new(0.0, 0.0, -5.0);
176 let direction = Vec3::new(0.0, 0.0, 1.0);
177 let ray = Ray::new(origin, direction);
178
179 let obb_center = Vec3::new(0.0, 0.0, 0.0);
180 let obb_extents = Vec3::new(1.0, 1.0, 1.0);
181
182 let rot = Quat::from_rotation_y(std::f32::consts::FRAC_PI_4);
184
185 let t = ray.intersect_obb(obb_center, obb_extents, rot);
186 assert!(t.is_some());
187
188 assert!((t.unwrap() - (5.0 - std::f32::consts::SQRT_2)).abs() < 1e-4);
192 }
193
194 #[test]
195 fn test_ray_parallel_hit() {
196 let ray = Ray::new(Vec3::new(0.0, -5.0, 0.0), Vec3::Y);
198 let aabb = crate::aabb::Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
199
200 let t = ray.intersect_aabb(aabb);
202 assert!(t.is_some());
203 assert!((t.unwrap() - 4.0).abs() < 1e-5);
204 }
205
206 #[test]
207 fn test_ray_parallel_miss() {
208 let ray_miss = Ray::new(Vec3::new(5.0, -5.0, 0.0), Vec3::Y);
210 let aabb = crate::aabb::Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
211
212 let t_miss = ray_miss.intersect_aabb(aabb);
213 assert!(t_miss.is_none());
214 }
215}