1use core::cmp::Ordering;
4use nalgebra::{Point3, Unit, Vector3};
5
6use crate::rt::{Ray, Side};
7
8#[derive(Clone)]
10pub struct Cube {
11 pub mins: Point3<f64>,
13 pub maxs: Point3<f64>,
15}
16
17impl Cube {
18 #[inline]
20 #[must_use]
21 pub fn new(mins: Point3<f64>, maxs: Point3<f64>) -> Self {
22 debug_assert!(mins < maxs);
23
24 Self { mins, maxs }
25 }
26
27 #[inline]
29 #[must_use]
30 pub fn centre(&self) -> Point3<f64> {
31 nalgebra::center(&self.mins, &self.maxs)
32 }
33
34 #[inline]
36 #[must_use]
37 pub fn widths(&self) -> Vector3<f64> {
38 self.maxs - self.mins
39 }
40
41 #[inline]
43 #[must_use]
44 pub fn half_widths(&self) -> Vector3<f64> {
45 (self.maxs - self.mins) * 0.5
46 }
47
48 #[inline]
50 #[must_use]
51 pub fn area(&self) -> f64 {
52 let ws = self.widths();
53 2.0 * ws.z.mul_add(ws.x, ws.x.mul_add(ws.y, ws.y * ws.z))
54 }
55
56 #[inline]
58 #[must_use]
59 pub fn vol(&self) -> f64 {
60 let ws = self.widths();
61 ws.x * ws.y * ws.z
62 }
63
64 #[inline]
67 #[must_use]
68 pub fn contains(&self, p: &Point3<f64>) -> bool {
69 p >= &self.mins && p <= &self.maxs
70 }
71
72 #[inline]
75 pub fn shrink(&mut self, f: f64) {
76 debug_assert!(f > 0.0);
77 debug_assert!(f < 1.0);
78
79 let delta = self.half_widths() * f;
80
81 self.mins += delta;
82 self.maxs -= delta;
83 }
84
85 #[inline]
88 pub fn expand(&mut self, f: f64) {
89 debug_assert!(f > 0.0);
90
91 let delta = self.half_widths() * f;
92
93 self.mins -= delta;
94 self.maxs += delta;
95 }
96
97 #[inline]
99 #[must_use]
100 pub fn collides(&self, cube: &Self) -> bool {
101 self.mins <= cube.maxs && self.maxs >= cube.mins
102 }
103
104 #[inline]
106 #[must_use]
107 fn intersections(&self, ray: &Ray) -> (f64, f64) {
108 let t_0: Vec<_> = self
109 .mins
110 .iter()
111 .zip(ray.pos.iter().zip(ray.dir.iter()))
112 .map(|(m, (p, d))| (m - p) / d)
113 .collect();
114
115 let t_1: Vec<_> = self
116 .maxs
117 .iter()
118 .zip(ray.pos.iter().zip(ray.dir.iter()))
119 .map(|(m, (p, d))| (m - p) / d)
120 .collect();
121
122 let t_min = t_0
123 .iter()
124 .zip(t_1.iter())
125 .map(|(a, b)| a.min(*b))
126 .max_by(|a, b| {
127 if a < b {
128 Ordering::Less
129 } else {
130 Ordering::Greater
131 }
132 })
133 .expect("Failed to perform Ray-Cube intersection.");
134
135 let t_max = t_0
136 .iter()
137 .zip(t_1.iter())
138 .map(|(a, b)| a.max(*b))
139 .min_by(|a, b| {
140 if a < b {
141 Ordering::Less
142 } else {
143 Ordering::Greater
144 }
145 })
146 .expect("Failed to perform Ray-Cube intersection.");
147
148 (t_min, t_max)
149 }
150
151 #[inline]
153 #[must_use]
154 pub fn hit(&self, ray: &Ray) -> bool {
155 let (t_min, t_max) = self.intersections(ray);
156
157 !(t_max <= 0.0 || t_min > t_max)
158 }
159
160 #[inline]
162 #[must_use]
163 pub fn dist(&self, ray: &Ray) -> Option<f64> {
164 let (t_min, t_max) = self.intersections(ray);
165
166 if t_max <= 0.0 || t_min > t_max {
167 return None;
168 }
169
170 if t_min > 0.0 {
171 return Some(t_min);
172 }
173
174 Some(t_max)
175 }
176
177 #[inline]
179 #[must_use]
180 pub fn dist_side(&self, ray: &Ray) -> Option<(f64, Side)> {
181 if let Some(dist) = self.dist(ray) {
182 let hit = ray.pos + (dist * ray.dir.as_ref());
183 let relative = hit - self.centre();
184
185 let xy = relative.y / relative.x;
186 let zy = relative.z / relative.y;
187
188 let norm = Unit::new_normalize(if (-1.0..=1.0).contains(&xy) {
189 Vector3::new(1.0_f64.copysign(relative.x), 0.0, 0.0)
190 } else if (-1.0..=1.0).contains(&zy) {
191 Vector3::new(0.0, 1.0_f64.copysign(relative.y), 0.0)
192 } else {
193 Vector3::new(0.0, 0.0, 1.0_f64.copysign(relative.z))
194 });
195
196 return Some((dist, Side::new(&ray.dir, norm)));
197 }
198
199 None
200 }
201}