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