1use crate::{Aabb, EPSILON, Sphere, Vec2, Vec3, sqrt};
2
3#[derive(Clone, Copy, Debug, PartialEq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct Ray3 {
7 pub origin: Vec3,
9 pub direction: Vec3,
11}
12
13impl Ray3 {
14 #[inline]
16 pub fn new(origin: Vec3, direction: Vec3) -> Self {
17 Self {
18 origin,
19 direction: direction.normalize(),
20 }
21 }
22
23 #[inline]
25 pub fn at(self, t: f32) -> Vec3 {
26 self.origin + self.direction * t
27 }
28
29 pub fn intersect_aabb(self, aabb: Aabb) -> Option<f32> {
31 let mut t_min = 0.0_f32;
32 let mut t_max = f32::INFINITY;
33
34 for axis in 0..3 {
35 let origin = self.origin[axis];
36 let direction = self.direction[axis];
37 let min = aabb.min[axis];
38 let max = aabb.max[axis];
39
40 if direction.abs() <= EPSILON {
41 if origin < min || origin > max {
42 return None;
43 }
44 continue;
45 }
46
47 let inv = 1.0 / direction;
48 let mut t1 = (min - origin) * inv;
49 let mut t2 = (max - origin) * inv;
50 if t1 > t2 {
51 core::mem::swap(&mut t1, &mut t2);
52 }
53 t_min = t_min.max(t1);
54 t_max = t_max.min(t2);
55 if t_min > t_max {
56 return None;
57 }
58 }
59
60 Some(t_min)
61 }
62
63 pub fn intersect_sphere(self, center: Vec3, radius: f32) -> Option<f32> {
65 self.intersect_bounding_sphere(Sphere::new(center, radius))
66 }
67
68 pub fn intersect_bounding_sphere(self, sphere: Sphere) -> Option<f32> {
70 let oc = self.origin - sphere.center;
71 let a = self.direction.dot(self.direction);
72 let b = 2.0 * oc.dot(self.direction);
73 let c = oc.dot(oc) - sphere.radius * sphere.radius;
74 let discriminant = b * b - 4.0 * a * c;
75 if discriminant < 0.0 || a.abs() <= EPSILON {
76 return None;
77 }
78 let root = sqrt(discriminant);
79 let t0 = (-b - root) / (2.0 * a);
80 let t1 = (-b + root) / (2.0 * a);
81 if t0 >= 0.0 {
82 Some(t0)
83 } else if t1 >= 0.0 {
84 Some(t1)
85 } else {
86 None
87 }
88 }
89
90 pub fn intersect_triangle(self, a: Vec3, b: Vec3, c: Vec3) -> Option<(f32, Vec2)> {
95 let edge1 = b - a;
96 let edge2 = c - a;
97 let pvec = self.direction.cross(edge2);
98 let det = edge1.dot(pvec);
99 if det.abs() <= EPSILON {
100 return None;
101 }
102
103 let inv_det = 1.0 / det;
104 let tvec = self.origin - a;
105 let u = tvec.dot(pvec) * inv_det;
106 if !(0.0..=1.0).contains(&u) {
107 return None;
108 }
109
110 let qvec = tvec.cross(edge1);
111 let v = self.direction.dot(qvec) * inv_det;
112 if v < 0.0 || u + v > 1.0 {
113 return None;
114 }
115
116 let t = edge2.dot(qvec) * inv_det;
117 if t >= 0.0 {
118 Some((t, Vec2::new(u, v)))
119 } else {
120 None
121 }
122 }
123}
124
125impl Default for Ray3 {
126 #[inline]
127 fn default() -> Self {
128 Self::new(Vec3::ZERO, Vec3::NEG_Z)
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use crate::assert_close;
136
137 #[test]
138 fn ray_intersects_aabb() {
139 let ray = Ray3::new(Vec3::new(0.0, 0.0, 5.0), Vec3::NEG_Z);
140 let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
141 assert_close(ray.intersect_aabb(aabb).unwrap(), 4.0);
142 }
143
144 #[test]
145 fn ray_intersects_sphere() {
146 let ray = Ray3::new(Vec3::new(0.0, 0.0, 5.0), Vec3::NEG_Z);
147 assert_close(ray.intersect_sphere(Vec3::ZERO, 1.0).unwrap(), 4.0);
148 }
149
150 #[test]
151 fn ray_intersects_triangle_with_barycentric_coordinates() {
152 let ray = Ray3::new(Vec3::new(0.25, 0.25, 1.0), Vec3::NEG_Z);
153 let (t, uv) = ray
154 .intersect_triangle(Vec3::ZERO, Vec3::X, Vec3::Y)
155 .unwrap();
156 assert_close(t, 1.0);
157 assert_close(uv.x, 0.25);
158 assert_close(uv.y, 0.25);
159 }
160}