Skip to main content

axiolid_core/
bounds.rs

1//! Axis-aligned bounds used by broad-phase algorithms.
2
3use crate::{Point3, Scalar, Vec3};
4
5/// Axis-aligned three-dimensional bounding box.
6#[derive(Debug, Clone, Copy, PartialEq)]
7#[cfg_attr(
8    feature = "serde",
9    derive(serde::Serialize, serde::Deserialize),
10    serde(deny_unknown_fields)
11)]
12pub struct Aabb {
13    /// Minimum corner.
14    pub min: Point3,
15    /// Maximum corner.
16    pub max: Point3,
17}
18
19impl Aabb {
20    /// An empty box that absorbs the first extended point.
21    pub const fn empty() -> Self {
22        Self {
23            min: Vec3::splat(Scalar::INFINITY),
24            max: Vec3::splat(Scalar::NEG_INFINITY),
25        }
26    }
27
28    /// Construct a non-empty box containing exactly one point.
29    #[inline]
30    pub const fn from_point(point: Point3) -> Self {
31        Self {
32            min: point,
33            max: point,
34        }
35    }
36
37    /// Grow to include a point.
38    #[inline]
39    pub fn extend(&mut self, point: Point3) {
40        self.min = self.min.min(point);
41        self.max = self.max.max(point);
42    }
43
44    /// Grow to include every point in another box.
45    #[inline]
46    pub fn union(&mut self, other: &Self) {
47        self.min = self.min.min(other.min);
48        self.max = self.max.max(other.max);
49    }
50
51    /// Whether both corners are finite coordinates.
52    #[inline]
53    pub fn is_finite(&self) -> bool {
54        self.min.is_finite() && self.max.is_finite()
55    }
56
57    /// Whether the boxes overlap. Touching counts as overlap.
58    #[inline]
59    pub fn intersects(&self, other: &Self) -> bool {
60        self.min.x <= other.max.x
61            && self.max.x >= other.min.x
62            && self.min.y <= other.max.y
63            && self.max.y >= other.min.y
64            && self.min.z <= other.max.z
65            && self.max.z >= other.min.z
66    }
67
68    /// Whether no point has been added.
69    #[inline]
70    pub fn is_empty(&self) -> bool {
71        self.min.x > self.max.x
72    }
73
74    /// Diagonal vector, or zero for an empty box.
75    pub fn diagonal(&self) -> Vec3 {
76        if self.is_empty() {
77            Vec3::ZERO
78        } else {
79            self.max - self.min
80        }
81    }
82
83    /// Centre point, or zero for an empty box.
84    #[inline]
85    pub fn center(&self) -> Point3 {
86        if self.is_empty() {
87            Vec3::ZERO
88        } else {
89            (self.min + self.max) * 0.5
90        }
91    }
92
93    /// Minimum Euclidean distance between box surfaces. Touching and overlap
94    /// have distance zero.
95    #[inline]
96    pub fn gap(&self, other: &Self) -> Scalar {
97        let separation = (other.min - self.max)
98            .max(self.min - other.max)
99            .max(Vec3::ZERO);
100        separation.length()
101    }
102}
103
104impl Default for Aabb {
105    fn default() -> Self {
106        Self::empty()
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn empty_box_absorbs_first_point() {
116        let mut bounds = Aabb::default();
117        assert!(bounds.is_empty());
118        bounds.extend(Vec3::new(1.0, 2.0, 3.0));
119        assert_eq!(bounds.min, bounds.max);
120    }
121
122    #[test]
123    fn disjoint_boxes_do_not_intersect() {
124        let mut left = Aabb::default();
125        left.extend(Vec3::ZERO);
126        left.extend(Vec3::ONE);
127        let mut right = Aabb::default();
128        right.extend(Vec3::splat(2.0));
129        right.extend(Vec3::splat(3.0));
130        assert!(!left.intersects(&right));
131    }
132}