arctk/geom/
cube.rs

1//! Axis-aligned cuboid.
2
3use core::cmp::Ordering;
4use nalgebra::{Point3, Unit, Vector3};
5
6use crate::rt::{Ray, Side};
7
8/// Cuboid oriented along the Cartesian axes.
9#[derive(Clone)]
10pub struct Cube {
11    /// Minimum bound.
12    pub mins: Point3<f64>,
13    /// Maximum bound.
14    pub maxs: Point3<f64>,
15}
16
17impl Cube {
18    /// Construct a new instance.
19    #[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    /// Calculate the centre position.
28    #[inline]
29    #[must_use]
30    pub fn centre(&self) -> Point3<f64> {
31        nalgebra::center(&self.mins, &self.maxs)
32    }
33
34    /// Calculate the widths.
35    #[inline]
36    #[must_use]
37    pub fn widths(&self) -> Vector3<f64> {
38        self.maxs - self.mins
39    }
40
41    /// Calculate the half-widths.
42    #[inline]
43    #[must_use]
44    pub fn half_widths(&self) -> Vector3<f64> {
45        (self.maxs - self.mins) * 0.5
46    }
47
48    /// Calculate the surface area.
49    #[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    /// Calculate the volume.
57    #[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    /// Determine if the given point if contained with the volume.
65    /// Points lying on the surface are contained contained.
66    #[inline]
67    #[must_use]
68    pub fn contains(&self, p: &Point3<f64>) -> bool {
69        p >= &self.mins && p <= &self.maxs
70    }
71
72    /// Shrink by a fraction of the side lengths.
73    /// Central position is maintained.
74    #[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    /// Expand by a fraction of the side lengths.
86    /// Central position is maintained.
87    #[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    /// Check for an intersection with another Cube.
98    #[inline]
99    #[must_use]
100    pub fn collides(&self, cube: &Self) -> bool {
101        self.mins <= cube.maxs && self.maxs >= cube.mins
102    }
103
104    /// Determine the distances from a given Ray to the axial planes of a Cube's minimum and maximal bounds.
105    #[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    /// Determine if a Ray-Cube intersection occurs.
152    #[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    /// Determine the distance to a Ray-Cube intersection.
161    #[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    /// Determine the distance and facing side of a Ray-Cube intersection.
178    #[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}