rg3d_core/math/
aabb.rs

1use crate::{
2    algebra::{Matrix4, Vector3},
3    math::Matrix4Ext,
4    visitor::{Visit, VisitResult, Visitor},
5};
6
7#[derive(Copy, Clone, Debug)]
8pub struct AxisAlignedBoundingBox {
9    pub min: Vector3<f32>,
10    pub max: Vector3<f32>,
11}
12
13impl Default for AxisAlignedBoundingBox {
14    #[inline]
15    fn default() -> Self {
16        Self {
17            min: Vector3::new(f32::MAX, f32::MAX, f32::MAX),
18            max: Vector3::new(-f32::MAX, -f32::MAX, -f32::MAX),
19        }
20    }
21}
22
23impl AxisAlignedBoundingBox {
24    #[inline]
25    pub const fn unit() -> Self {
26        Self::from_min_max(Vector3::new(-0.5, -0.5, -0.5), Vector3::new(0.5, 0.5, 0.5))
27    }
28
29    #[inline]
30    pub fn from_radius(radius: f32) -> Self {
31        Self {
32            min: Vector3::new(-radius, -radius, -radius),
33            max: Vector3::new(radius, radius, radius),
34        }
35    }
36
37    #[inline]
38    pub const fn from_min_max(min: Vector3<f32>, max: Vector3<f32>) -> Self {
39        Self { min, max }
40    }
41
42    #[inline]
43    pub fn from_points(points: &[Vector3<f32>]) -> Self {
44        let mut aabb = AxisAlignedBoundingBox::default();
45        for pt in points {
46            aabb.add_point(*pt);
47        }
48        aabb
49    }
50
51    #[inline]
52    pub fn add_point(&mut self, a: Vector3<f32>) {
53        if a.x < self.min.x {
54            self.min.x = a.x;
55        }
56        if a.y < self.min.y {
57            self.min.y = a.y;
58        }
59        if a.z < self.min.z {
60            self.min.z = a.z;
61        }
62
63        if a.x > self.max.x {
64            self.max.x = a.x;
65        }
66        if a.y > self.max.y {
67            self.max.y = a.y;
68        }
69        if a.z > self.max.z {
70            self.max.z = a.z;
71        }
72    }
73
74    #[inline]
75    pub fn inflate(&mut self, delta: Vector3<f32>) {
76        self.min -= delta.scale(0.5);
77        self.max += delta.scale(0.5);
78    }
79
80    #[inline]
81    pub fn add_box(&mut self, other: Self) {
82        self.add_point(other.min);
83        self.add_point(other.max);
84    }
85
86    #[inline]
87    pub fn corners(&self) -> [Vector3<f32>; 8] {
88        [
89            Vector3::new(self.min.x, self.min.y, self.min.z),
90            Vector3::new(self.min.x, self.min.y, self.max.z),
91            Vector3::new(self.max.x, self.min.y, self.max.z),
92            Vector3::new(self.max.x, self.min.y, self.min.z),
93            Vector3::new(self.min.x, self.max.y, self.min.z),
94            Vector3::new(self.min.x, self.max.y, self.max.z),
95            Vector3::new(self.max.x, self.max.y, self.max.z),
96            Vector3::new(self.max.x, self.max.y, self.min.z),
97        ]
98    }
99
100    #[inline]
101    pub fn offset(&mut self, v: Vector3<f32>) {
102        self.min += v;
103        self.max += v;
104    }
105
106    #[inline]
107    pub fn center(&self) -> Vector3<f32> {
108        (self.max + self.min).scale(0.5)
109    }
110
111    #[inline]
112    pub fn half_extents(&self) -> Vector3<f32> {
113        (self.max - self.min).scale(0.5)
114    }
115
116    #[inline]
117    pub fn invalidate(&mut self) {
118        *self = Default::default();
119    }
120
121    #[inline]
122    pub fn is_contains_point(&self, point: Vector3<f32>) -> bool {
123        point.x >= self.min.x
124            && point.x <= self.max.x
125            && point.y >= self.min.y
126            && point.y <= self.max.y
127            && point.z >= self.min.z
128            && point.z <= self.max.z
129    }
130
131    #[inline]
132    pub fn is_intersects_sphere(&self, position: Vector3<f32>, radius: f32) -> bool {
133        let r2 = radius.powi(2);
134        let mut dmin = 0.0;
135
136        if position.x < self.min.x {
137            dmin += (position.x - self.min.x).powi(2);
138        } else if position.x > self.max.x {
139            dmin += (position.x - self.max.x).powi(2);
140        }
141
142        if position.y < self.min.y {
143            dmin += (position.y - self.min.y).powi(2);
144        } else if position.y > self.max.y {
145            dmin += (position.y - self.max.y).powi(2);
146        }
147
148        if position.z < self.min.z {
149            dmin += (position.z - self.min.z).powi(2);
150        } else if position.z > self.max.z {
151            dmin += (position.z - self.max.z).powi(2);
152        }
153
154        dmin <= r2
155            || ((position.x >= self.min.x)
156                && (position.x <= self.max.x)
157                && (position.y >= self.min.y)
158                && (position.y <= self.max.y)
159                && (position.z >= self.min.z)
160                && (position.z <= self.max.z))
161    }
162
163    #[inline]
164    pub fn intersect_aabb(&self, other: &Self) -> bool {
165        let self_center = self.center();
166        let self_half_extents = self.half_extents();
167
168        let other_half_extents = other.half_extents();
169        let other_center = other.center();
170
171        if (self_center.x - other_center.x).abs() > (self_half_extents.x + other_half_extents.x) {
172            return false;
173        }
174
175        if (self_center.y - other_center.y).abs() > (self_half_extents.y + other_half_extents.y) {
176            return false;
177        }
178
179        if (self_center.z - other_center.z).abs() > (self_half_extents.z + other_half_extents.z) {
180            return false;
181        }
182
183        true
184    }
185
186    /// Transforms axis-aligned bounding box using given affine transformation matrix.
187    ///
188    /// # References
189    ///
190    /// Transforming Axis-Aligned Bounding Boxes by Jim Arvo, "Graphics Gems", Academic Press, 1990
191    #[inline]
192    #[must_use]
193    pub fn transform(&self, m: &Matrix4<f32>) -> AxisAlignedBoundingBox {
194        let basis = m.basis();
195
196        let mut transformed = Self {
197            min: m.position(),
198            max: m.position(),
199        };
200
201        for i in 0..3 {
202            for j in 0..3 {
203                let a = basis[(i, j)] * self.min[j];
204                let b = basis[(i, j)] * self.max[j];
205                if a < b {
206                    transformed.min[i] += a;
207                    transformed.max[i] += b;
208                } else {
209                    transformed.min[i] += b;
210                    transformed.max[i] += a;
211                }
212            }
213        }
214
215        transformed
216    }
217
218    #[inline]
219    pub fn split(&self) -> [AxisAlignedBoundingBox; 8] {
220        let center = self.center();
221        let min = &self.min;
222        let max = &self.max;
223        [
224            AxisAlignedBoundingBox::from_min_max(
225                Vector3::new(min.x, min.y, min.z),
226                Vector3::new(center.x, center.y, center.z),
227            ),
228            AxisAlignedBoundingBox::from_min_max(
229                Vector3::new(center.x, min.y, min.z),
230                Vector3::new(max.x, center.y, center.z),
231            ),
232            AxisAlignedBoundingBox::from_min_max(
233                Vector3::new(min.x, min.y, center.z),
234                Vector3::new(center.x, center.y, max.z),
235            ),
236            AxisAlignedBoundingBox::from_min_max(
237                Vector3::new(center.x, min.y, center.z),
238                Vector3::new(max.x, center.y, max.z),
239            ),
240            AxisAlignedBoundingBox::from_min_max(
241                Vector3::new(min.x, center.y, min.z),
242                Vector3::new(center.x, max.y, center.z),
243            ),
244            AxisAlignedBoundingBox::from_min_max(
245                Vector3::new(center.x, center.y, min.z),
246                Vector3::new(max.x, max.y, center.z),
247            ),
248            AxisAlignedBoundingBox::from_min_max(
249                Vector3::new(min.x, center.y, center.z),
250                Vector3::new(center.x, max.y, max.z),
251            ),
252            AxisAlignedBoundingBox::from_min_max(
253                Vector3::new(center.x, center.y, center.z),
254                Vector3::new(max.x, max.y, max.z),
255            ),
256        ]
257    }
258}
259
260impl Visit for AxisAlignedBoundingBox {
261    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
262        visitor.enter_region(name)?;
263
264        self.min.visit("Min", visitor)?;
265        self.max.visit("Max", visitor)?;
266
267        visitor.leave_region()
268    }
269}
270
271#[cfg(test)]
272mod test {
273    use crate::algebra::{Matrix4, Vector3};
274    use crate::math::aabb::AxisAlignedBoundingBox;
275
276    #[test]
277    fn test_aabb_transform() {
278        let aabb = AxisAlignedBoundingBox {
279            min: Vector3::new(0.0, 0.0, 0.0),
280            max: Vector3::new(1.0, 1.0, 1.0),
281        };
282
283        let transform = Matrix4::new_translation(&Vector3::new(1.0, 1.0, 1.0))
284            * Matrix4::new_nonuniform_scaling(&Vector3::new(2.0, 2.0, 2.0));
285
286        let transformed_aabb = aabb.transform(&transform);
287
288        assert_eq!(transformed_aabb.min, Vector3::new(1.0, 1.0, 1.0));
289        assert_eq!(transformed_aabb.max, Vector3::new(3.0, 3.0, 3.0));
290    }
291}