Skip to main content

phyz_coupling/
boundary.rs

1//! Spatial bounding regions for coupling.
2
3use phyz_math::Vec3;
4
5/// Axis-aligned bounding box for overlap regions.
6#[derive(Clone, Debug)]
7pub struct BoundingBox {
8    pub min: Vec3,
9    pub max: Vec3,
10}
11
12impl BoundingBox {
13    /// Create a new bounding box.
14    pub fn new(min: Vec3, max: Vec3) -> Self {
15        Self { min, max }
16    }
17
18    /// Check if a point is inside the bounding box.
19    pub fn contains(&self, point: &Vec3) -> bool {
20        point.x >= self.min.x
21            && point.x <= self.max.x
22            && point.y >= self.min.y
23            && point.y <= self.max.y
24            && point.z >= self.min.z
25            && point.z <= self.max.z
26    }
27
28    /// Compute volume of the bounding box.
29    pub fn volume(&self) -> f64 {
30        let size = self.max - self.min;
31        size.x * size.y * size.z
32    }
33
34    /// Compute center of the bounding box.
35    pub fn center(&self) -> Vec3 {
36        (self.min + self.max) * 0.5
37    }
38
39    /// Expand the bounding box by a margin.
40    pub fn expand(&self, margin: f64) -> Self {
41        Self {
42            min: self.min - Vec3::new(margin, margin, margin),
43            max: self.max + Vec3::new(margin, margin, margin),
44        }
45    }
46
47    /// Check if two bounding boxes overlap.
48    pub fn overlaps(&self, other: &BoundingBox) -> bool {
49        self.min.x <= other.max.x
50            && self.max.x >= other.min.x
51            && self.min.y <= other.max.y
52            && self.max.y >= other.min.y
53            && self.min.z <= other.max.z
54            && self.max.z >= other.min.z
55    }
56
57    /// Compute intersection of two bounding boxes.
58    pub fn intersection(&self, other: &BoundingBox) -> Option<BoundingBox> {
59        if !self.overlaps(other) {
60            return None;
61        }
62
63        Some(BoundingBox {
64            min: Vec3::new(
65                self.min.x.max(other.min.x),
66                self.min.y.max(other.min.y),
67                self.min.z.max(other.min.z),
68            ),
69            max: Vec3::new(
70                self.max.x.min(other.max.x),
71                self.max.y.min(other.max.y),
72                self.max.z.min(other.max.z),
73            ),
74        })
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use approx::assert_relative_eq;
82
83    #[test]
84    fn test_contains() {
85        let bbox = BoundingBox::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
86
87        assert!(bbox.contains(&Vec3::new(0.0, 0.0, 0.0)));
88        assert!(bbox.contains(&Vec3::new(0.5, 0.5, 0.5)));
89        assert!(bbox.contains(&Vec3::new(-1.0, -1.0, -1.0)));
90        assert!(bbox.contains(&Vec3::new(1.0, 1.0, 1.0)));
91        assert!(!bbox.contains(&Vec3::new(2.0, 0.0, 0.0)));
92        assert!(!bbox.contains(&Vec3::new(0.0, 2.0, 0.0)));
93    }
94
95    #[test]
96    fn test_volume() {
97        let bbox = BoundingBox::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(2.0, 3.0, 4.0));
98        assert_relative_eq!(bbox.volume(), 24.0);
99    }
100
101    #[test]
102    fn test_center() {
103        let bbox = BoundingBox::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
104        let center = bbox.center();
105        assert_relative_eq!(center.x, 0.0);
106        assert_relative_eq!(center.y, 0.0);
107        assert_relative_eq!(center.z, 0.0);
108    }
109
110    #[test]
111    fn test_expand() {
112        let bbox = BoundingBox::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
113        let expanded = bbox.expand(0.5);
114
115        assert_relative_eq!(expanded.min.x, -1.5);
116        assert_relative_eq!(expanded.max.x, 1.5);
117    }
118
119    #[test]
120    fn test_overlaps() {
121        let bbox1 = BoundingBox::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(2.0, 2.0, 2.0));
122        let bbox2 = BoundingBox::new(Vec3::new(1.0, 1.0, 1.0), Vec3::new(3.0, 3.0, 3.0));
123        let bbox3 = BoundingBox::new(Vec3::new(3.0, 3.0, 3.0), Vec3::new(5.0, 5.0, 5.0));
124
125        assert!(bbox1.overlaps(&bbox2));
126        assert!(bbox2.overlaps(&bbox1));
127        assert!(!bbox1.overlaps(&bbox3));
128    }
129
130    #[test]
131    fn test_intersection() {
132        let bbox1 = BoundingBox::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(2.0, 2.0, 2.0));
133        let bbox2 = BoundingBox::new(Vec3::new(1.0, 1.0, 1.0), Vec3::new(3.0, 3.0, 3.0));
134
135        let intersection = bbox1.intersection(&bbox2).unwrap();
136        assert_relative_eq!(intersection.min.x, 1.0);
137        assert_relative_eq!(intersection.max.x, 2.0);
138
139        let bbox3 = BoundingBox::new(Vec3::new(3.0, 3.0, 3.0), Vec3::new(5.0, 5.0, 5.0));
140        assert!(bbox1.intersection(&bbox3).is_none());
141    }
142}