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]
24 pub fn from_ndc(ndc: glam::Vec2, view_proj_inv: glam::Mat4) -> Self {
25 let near_ndc = glam::Vec4::new(ndc.x, ndc.y, 0.0, 1.0);
27 let far_ndc = glam::Vec4::new(ndc.x, ndc.y, 1.0, 1.0);
28
29 let mut near_world = view_proj_inv * near_ndc;
30 debug_assert!(near_world.w.abs() > 1e-10, "Ray::from_ndc: degenerate near w (singular VP inverse?)");
31 near_world /= near_world.w;
32
33 let mut far_world = view_proj_inv * far_ndc;
34 debug_assert!(far_world.w.abs() > 1e-10, "Ray::from_ndc: degenerate far w (singular VP inverse?)");
35 far_world /= far_world.w;
36
37 let origin = near_world.truncate();
38 let direction = (far_world.truncate() - origin).normalize();
39
40 Self::new(origin, direction)
41 }
42
43 #[inline]
45 pub fn at(self, t: f32) -> Vec3A {
46 self.origin + self.direction * t
47 }
48
49 #[inline]
52 pub fn intersect_bounds(self, min: Vec3A, max: Vec3A) -> Option<f32> {
53 let inv_dir = self.direction.recip();
54
55 let t0 = (min - self.origin) * inv_dir;
56 let t1 = (max - self.origin) * inv_dir;
57
58 let tmin_vec = t0.min(t1);
59 let tmax_vec = t0.max(t1);
60
61 let tmin = tmin_vec.x.max(tmin_vec.y).max(tmin_vec.z);
62 let tmax = tmax_vec.x.min(tmax_vec.y).min(tmax_vec.z);
63
64 if tmin <= tmax && tmax > 0.0 {
65 Some(if tmin > 0.0 { tmin } else { tmax })
66 } else {
67 None
68 }
69 }
70
71 #[inline]
73 pub fn intersect_aabb(self, aabb: crate::aabb::Aabb) -> Option<f32> {
74 self.intersect_bounds(aabb.min, aabb.max)
75 }
76
77 #[inline]
80 pub fn intersect_triangle(
81 self,
82 v0: impl Into<Vec3A>,
83 v1: impl Into<Vec3A>,
84 v2: impl Into<Vec3A>,
85 ) -> Option<f32> {
86 let v0 = v0.into();
87 let v1 = v1.into();
88 let v2 = v2.into();
89 let edge1 = v1 - v0;
90 let edge2 = v2 - v0;
91
92 let h = self.direction.cross(edge2);
93 let a = edge1.dot(h);
94
95 if a.abs() < 1e-8 {
97 return None;
98 }
99
100 let f = 1.0 / a;
101 let s = self.origin - v0;
102 let u = f * s.dot(h);
103
104 if !(0.0..=1.0).contains(&u) {
105 return None;
106 }
107
108 let q = s.cross(edge1);
109 let v = f * self.direction.dot(q);
110
111 if v < 0.0 || u + v > 1.0 {
112 return None;
113 }
114
115 let t = f * edge2.dot(q);
116
117 if t > 1e-8 {
118 Some(t)
119 } else {
120 None
121 }
122 }
123
124 #[inline]
127 pub fn intersect_obb(
128 self,
129 center: impl Into<Vec3A>,
130 half_extents: impl Into<Vec3A>,
131 rotation: Quat,
132 ) -> Option<f32> {
133 let c = center.into();
134 let he = half_extents.into();
135 let inv_rot = rotation.inverse();
136
137 let local_origin = Vec3A::from(inv_rot * Vec3::from(self.origin - c));
140 let local_direction = Vec3A::from(inv_rot * Vec3::from(self.direction));
141
142 let local_ray = Ray {
145 origin: local_origin,
146 direction: local_direction,
147 };
148
149 local_ray.intersect_bounds(-he, he)
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_ray_intersect_aabb_hit() {
160 let ray = Ray::new(Vec3::new(0.0, 0.0, -5.0), Vec3::new(0.0, 0.0, 1.0));
161 let aabb = crate::aabb::Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
162
163 let t = ray.intersect_aabb(aabb);
164 assert!(t.is_some());
165 assert!((t.unwrap() - 4.0).abs() < 1e-5); }
167
168 #[test]
169 fn test_ray_intersect_aabb_miss() {
170 let ray = Ray::new(Vec3::new(0.0, 5.0, -5.0), Vec3::new(0.0, 0.0, 1.0));
171 let aabb = crate::aabb::Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
172
173 let t = ray.intersect_aabb(aabb);
174 assert!(t.is_none());
175 }
176
177 #[test]
178 fn test_ray_intersect_aabb_inside() {
179 let ray = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 1.0));
180 let aabb = crate::aabb::Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
181
182 let t = ray.intersect_aabb(aabb);
183 assert!(t.is_some());
184 assert!((t.unwrap() - 1.0).abs() < 1e-5); }
186
187 #[test]
188 fn test_ray_intersect_aabb_behind() {
189 let ray = Ray::new(Vec3::new(0.0, 0.0, 5.0), Vec3::new(0.0, 0.0, 1.0));
190 let aabb = crate::aabb::Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
191
192 let t = ray.intersect_aabb(aabb);
193 assert!(t.is_none()); }
195
196 #[test]
197 fn test_ray_intersect_obb() {
198 let origin = Vec3::new(0.0, 0.0, -5.0);
199 let direction = Vec3::new(0.0, 0.0, 1.0);
200 let ray = Ray::new(origin, direction);
201
202 let obb_center = Vec3::new(0.0, 0.0, 0.0);
203 let obb_extents = Vec3::new(1.0, 1.0, 1.0);
204
205 let rot = Quat::from_rotation_y(std::f32::consts::FRAC_PI_4);
207
208 let t = ray.intersect_obb(obb_center, obb_extents, rot);
209 assert!(t.is_some());
210
211 assert!((t.unwrap() - (5.0 - std::f32::consts::SQRT_2)).abs() < 1e-4);
215 }
216
217 #[test]
218 fn test_ray_parallel_hit() {
219 let ray = Ray::new(Vec3::new(0.0, -5.0, 0.0), Vec3::Y);
221 let aabb = crate::aabb::Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
222
223 let t = ray.intersect_aabb(aabb);
225 assert!(t.is_some());
226 assert!((t.unwrap() - 4.0).abs() < 1e-5);
227 }
228
229 #[test]
230 fn test_ray_parallel_miss() {
231 let ray_miss = Ray::new(Vec3::new(5.0, -5.0, 0.0), Vec3::Y);
233 let aabb = crate::aabb::Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
234
235 let t_miss = ray_miss.intersect_aabb(aabb);
236 assert!(t_miss.is_none());
237 }
238 #[test]
239 fn test_ray_from_ndc() {
240 let view = glam::Mat4::look_at_rh(
241 Vec3::new(0.0, 0.0, 10.0), Vec3::ZERO, Vec3::Y, );
245 let proj = glam::Mat4::perspective_rh(std::f32::consts::FRAC_PI_2, 1.0, 0.1, 100.0);
246 let view_proj_inv = (proj * view).inverse();
247
248 let ray_center = Ray::from_ndc(glam::Vec2::new(0.0, 0.0), view_proj_inv);
250
251 assert!((ray_center.direction.z - (-1.0)).abs() < 1e-5);
254 assert!(ray_center.direction.x.abs() < 1e-5);
255 assert!(ray_center.direction.y.abs() < 1e-5);
256 }
257}