three_d_asset/prelude/
aabb.rs

1use super::math::*;
2
3///
4/// A bounding box that aligns with the x, y and z axes.
5///
6#[derive(Debug, Copy, Clone)]
7pub struct AxisAlignedBoundingBox {
8    min: Vec3,
9    max: Vec3,
10}
11
12impl AxisAlignedBoundingBox {
13    /// An empty bounding box.
14    pub const EMPTY: Self = Self {
15        min: Vec3::new(std::f32::INFINITY, std::f32::INFINITY, std::f32::INFINITY),
16        max: Vec3::new(
17            std::f32::NEG_INFINITY,
18            std::f32::NEG_INFINITY,
19            std::f32::NEG_INFINITY,
20        ),
21    };
22
23    /// An infinitely large bounding box.
24    pub const INFINITE: Self = Self {
25        min: Vec3::new(
26            std::f32::NEG_INFINITY,
27            std::f32::NEG_INFINITY,
28            std::f32::NEG_INFINITY,
29        ),
30        max: Vec3::new(std::f32::INFINITY, std::f32::INFINITY, std::f32::INFINITY),
31    };
32
33    ///
34    /// Constructs a new bounding box and expands it such that all of the given positions are contained inside the bounding box.
35    ///
36    pub fn new_with_positions(positions: &[Vec3]) -> Self {
37        let mut aabb = Self::EMPTY;
38        aabb.expand(positions);
39        aabb
40    }
41
42    ///
43    /// Constructs a new bounding box and expands it such that all of the given positions transformed with the given transformation are contained inside the bounding box.
44    /// A position consisting of an x, y and z coordinate corresponds to three consecutive value in the positions array.
45    ///
46    pub fn new_with_transformed_positions(positions: &[Vec3], transformation: Mat4) -> Self {
47        let mut aabb = Self::EMPTY;
48        aabb.expand_with_transformation(positions, transformation);
49        aabb
50    }
51
52    ///
53    /// Returns true if the bounding box is empty (ie. constructed by [AxisAlignedBoundingBox::EMPTY]).
54    ///
55    pub fn is_empty(&self) -> bool {
56        self.max.x == f32::NEG_INFINITY
57    }
58
59    ///
60    /// Returns true if the bounding box is infinitely large (ie. constructed by [AxisAlignedBoundingBox::INFINITE]).
61    ///
62    pub fn is_infinite(&self) -> bool {
63        self.max.x == f32::INFINITY
64    }
65
66    ///
67    /// Get the minimum coordinate of the bounding box.
68    ///
69    pub fn min(&self) -> Vec3 {
70        self.min
71    }
72
73    ///
74    /// Get the maximum coordinate of the bounding box.
75    ///
76    pub fn max(&self) -> Vec3 {
77        self.max
78    }
79
80    ///
81    /// Get the center of the bounding box.
82    ///
83    pub fn center(&self) -> Vec3 {
84        if self.is_infinite() {
85            Vec3::new(0.0, 0.0, 0.0)
86        } else {
87            0.5 * self.max + 0.5 * self.min
88        }
89    }
90
91    ///
92    /// Get the size of the bounding box.
93    ///
94    pub fn size(&self) -> Vec3 {
95        self.max - self.min
96    }
97
98    /// Expands the bounding box to be at least the given size, keeping the center the same.
99    pub fn ensure_size(&mut self, min_size: Vec3) {
100        if !self.is_empty() && !self.is_infinite() {
101            let size = self.size();
102            if size.x < min_size.x {
103                let diff = min_size.x - size.x;
104                self.min.x -= 0.5 * diff;
105                self.max.x += 0.5 * diff;
106            }
107            if size.y < min_size.y {
108                let diff = min_size.y - size.y;
109                self.min.y -= 0.5 * diff;
110                self.max.y += 0.5 * diff;
111            }
112            if size.z < min_size.z {
113                let diff = min_size.z - size.z;
114                self.min.z -= 0.5 * diff;
115                self.max.z += 0.5 * diff;
116            }
117        }
118    }
119
120    /// Returns the intersection between this and the other given bounding box.
121    pub fn intersection(self, other: Self) -> Self {
122        let min_a = self.min();
123        let max_a = self.max();
124        let min_b = other.min();
125        let max_b = other.max();
126
127        if min_a.x >= max_b.x || min_a.y >= max_b.y || min_b.x >= max_a.x || min_b.y >= max_a.y {
128            return Self::EMPTY;
129        }
130
131        let min = vec3(
132            min_a.x.max(min_b.x),
133            min_a.y.max(min_b.y),
134            min_a.z.max(min_b.z),
135        );
136        let max = vec3(
137            max_a.x.min(max_b.x),
138            max_a.y.min(max_b.y),
139            max_a.z.min(max_b.z),
140        );
141
142        Self::new_with_positions(&[min, max])
143    }
144
145    ///
146    /// Expands the bounding box such that all of the given positions are contained inside the bounding box.
147    ///
148    pub fn expand(&mut self, positions: &[Vec3]) {
149        for p in positions {
150            self.min.x = self.min.x.min(p.x);
151            self.min.y = self.min.y.min(p.y);
152            self.min.z = self.min.z.min(p.z);
153
154            self.max.x = self.max.x.max(p.x);
155            self.max.y = self.max.y.max(p.y);
156            self.max.z = self.max.z.max(p.z);
157        }
158    }
159
160    ///
161    /// Expands the bounding box such that all of the given positions transformed with the given transformation are contained inside the bounding box.
162    ///
163    pub fn expand_with_transformation(&mut self, positions: &[Vec3], transformation: Mat4) {
164        self.expand(
165            &positions
166                .iter()
167                .map(|p| (transformation * p.extend(1.0)).truncate())
168                .collect::<Vec<_>>(),
169        )
170    }
171
172    ///
173    /// Expand the bounding box such that it also contains the given other bounding box.
174    ///
175    pub fn expand_with_aabb(&mut self, other: AxisAlignedBoundingBox) {
176        if self.is_empty() {
177            *self = other;
178        } else if !other.is_empty() {
179            self.expand(&[other.min(), other.max()]);
180        }
181    }
182
183    ///
184    /// Transforms the bounding box by the given transformation.
185    ///
186    pub fn transform(&mut self, transformation: Mat4) {
187        if !self.is_empty() && !self.is_infinite() {
188            *self = Self::new_with_transformed_positions(
189                &[
190                    self.min,
191                    vec3(self.max.x, self.min.y, self.min.z),
192                    vec3(self.min.x, self.max.y, self.min.z),
193                    vec3(self.min.x, self.min.y, self.max.z),
194                    vec3(self.min.x, self.max.y, self.max.z),
195                    vec3(self.max.x, self.min.y, self.max.z),
196                    vec3(self.max.x, self.max.y, self.min.z),
197                    self.max,
198                ],
199                transformation,
200            );
201        }
202    }
203
204    ///
205    /// Returns the bounding box transformed by the given transformation.
206    ///
207    pub fn transformed(mut self, transformation: Mat4) -> AxisAlignedBoundingBox {
208        self.transform(transformation);
209        self
210    }
211
212    /// Returns true if the given bounding box is fully inside this bounding box.
213    pub fn contains(&self, aabb: AxisAlignedBoundingBox) -> bool {
214        !self.is_empty()
215            && !aabb.is_empty()
216            && self.is_inside(aabb.min())
217            && self.is_inside(aabb.max())
218    }
219
220    /// Returns true if the given position is inside this bounding box.
221    pub fn is_inside(&self, position: Vec3) -> bool {
222        self.min.x <= position.x
223            && position.x <= self.max.x
224            && self.min.y <= position.y
225            && position.y <= self.max.y
226            && self.min.z <= position.z
227            && position.z <= self.max.z
228    }
229
230    ///
231    /// The distance from position to the point in this bounding box that is closest to position.
232    ///
233    pub fn distance(&self, position: Vec3) -> f32 {
234        let x = (self.min.x - position.x)
235            .max(position.x - self.max.x)
236            .max(0.0);
237        let y = (self.min.y - position.y)
238            .max(position.y - self.max.y)
239            .max(0.0);
240        let z = (self.min.z - position.z)
241            .max(position.z - self.max.z)
242            .max(0.0);
243        let d2 = x * x + y * y + z * z;
244        if d2 > 0.001 {
245            d2.sqrt()
246        } else {
247            d2
248        }
249    }
250
251    ///
252    /// The distance from position to the point in this bounding box that is furthest away from position.
253    ///
254    pub fn distance_max(&self, position: Vec3) -> f32 {
255        let x = (position.x - self.min.x)
256            .abs()
257            .max((self.max.x - position.x).abs());
258        let y = (position.y - self.min.y)
259            .abs()
260            .max((self.max.y - position.y).abs());
261        let z = (position.z - self.min.z)
262            .abs()
263            .max((self.max.z - position.z).abs());
264        let d2 = x * x + y * y + z * z;
265        if d2 > 0.001 {
266            d2.sqrt()
267        } else {
268            d2
269        }
270    }
271}