Skip to main content

aabb_collision/
lib.rs

1//! Axis-Aligned Bounding Box (AABB) collision detection.
2
3use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub struct AABB {
7    pub min: [f64; 2],
8    pub max: [f64; 2],
9}
10
11impl AABB {
12    pub fn new(min: [f64; 2], max: [f64; 2]) -> Self {
13        Self { min, max }
14    }
15
16    pub fn from_center(center: [f64; 2], half_extents: [f64; 2]) -> Self {
17        Self {
18            min: [center[0] - half_extents[0], center[1] - half_extents[1]],
19            max: [center[0] + half_extents[0], center[1] + half_extents[1]],
20        }
21    }
22
23    pub fn center(&self) -> [f64; 2] {
24        [(self.min[0] + self.max[0]) / 2.0, (self.min[1] + self.max[1]) / 2.0]
25    }
26
27    pub fn half_extents(&self) -> [f64; 2] {
28        [(self.max[0] - self.min[0]) / 2.0, (self.max[1] - self.min[1]) / 2.0]
29    }
30
31    pub fn intersects(&self, other: &AABB) -> bool {
32        self.min[0] <= other.max[0]
33            && self.max[0] >= other.min[0]
34            && self.min[1] <= other.max[1]
35            && self.max[1] >= other.min[1]
36    }
37
38    pub fn contains_point(&self, p: [f64; 2]) -> bool {
39        p[0] >= self.min[0] && p[0] <= self.max[0] && p[1] >= self.min[1] && p[1] <= self.max[1]
40    }
41
42    pub fn contains(&self, other: &AABB) -> bool {
43        self.min[0] <= other.min[0]
44            && self.max[0] >= other.max[0]
45            && self.min[1] <= other.min[1]
46            && self.max[1] >= other.max[1]
47    }
48
49    /// Compute the overlapping region, if any.
50    pub fn overlap(&self, other: &AABB) -> Option<AABB> {
51        if !self.intersects(other) {
52            return None;
53        }
54        Some(AABB {
55            min: [self.min[0].max(other.min[0]), self.min[1].max(other.min[1])],
56            max: [self.max[0].min(other.max[0]), self.max[1].min(other.max[1])],
57        })
58    }
59
60    pub fn merged(&self, other: &AABB) -> AABB {
61        AABB {
62            min: [self.min[0].min(other.min[0]), self.min[1].min(other.min[1])],
63            max: [self.max[0].max(other.max[0]), self.max[1].max(other.max[1])],
64        }
65    }
66
67    pub fn area(&self) -> f64 {
68        (self.max[0] - self.min[0]) * (self.max[1] - self.min[1])
69    }
70}
71
72/// Simple broad-phase AABB collision checker using a sweep-and-prune approach.
73#[derive(Debug, Clone)]
74pub struct CollisionDetector {
75    boxes: Vec<(usize, AABB)>,
76}
77
78impl CollisionDetector {
79    pub fn new() -> Self {
80        Self { boxes: Vec::new() }
81    }
82
83    pub fn insert(&mut self, id: usize, aabb: AABB) {
84        self.boxes.push((id, aabb));
85    }
86
87    pub fn clear(&mut self) {
88        self.boxes.clear();
89    }
90
91    /// Find all pairs of overlapping AABBs.
92    pub fn find_collisions(&self) -> Vec<(usize, usize)> {
93        let mut pairs = Vec::new();
94        for i in 0..self.boxes.len() {
95            for j in (i + 1)..self.boxes.len() {
96                if self.boxes[i].1.intersects(&self.boxes[j].1) {
97                    let (a, b) = if self.boxes[i].0 < self.boxes[j].0 {
98                        (self.boxes[i].0, self.boxes[j].0)
99                    } else {
100                        (self.boxes[j].0, self.boxes[i].0)
101                    };
102                    pairs.push((a, b));
103                }
104            }
105        }
106        pairs.sort();
107        pairs.dedup();
108        pairs
109    }
110}
111
112impl Default for CollisionDetector {
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118/// 3D AABB variant
119#[derive(Debug, Clone, Copy, PartialEq)]
120pub struct AABB3 {
121    pub min: [f64; 3],
122    pub max: [f64; 3],
123}
124
125impl AABB3 {
126    pub fn new(min: [f64; 3], max: [f64; 3]) -> Self {
127        Self { min, max }
128    }
129
130    pub fn intersects(&self, other: &AABB3) -> bool {
131        self.min[0] <= other.max[0]
132            && self.max[0] >= other.min[0]
133            && self.min[1] <= other.max[1]
134            && self.max[1] >= other.min[1]
135            && self.min[2] <= other.max[2]
136            && self.max[2] >= other.min[2]
137    }
138
139    pub fn volume(&self) -> f64 {
140        (self.max[0] - self.min[0]) * (self.max[1] - self.min[1]) * (self.max[2] - self.min[2])
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_overlap() {
150        let a = AABB::new([0.0, 0.0], [2.0, 2.0]);
151        let b = AABB::new([1.0, 1.0], [3.0, 3.0]);
152        assert!(a.intersects(&b));
153        let overlap = a.overlap(&b).unwrap();
154        assert_eq!(overlap.min, [1.0, 1.0]);
155        assert_eq!(overlap.max, [2.0, 2.0]);
156    }
157
158    #[test]
159    fn test_no_overlap() {
160        let a = AABB::new([0.0, 0.0], [1.0, 1.0]);
161        let b = AABB::new([2.0, 2.0], [3.0, 3.0]);
162        assert!(!a.intersects(&b));
163    }
164
165    #[test]
166    fn test_collision_detector() {
167        let mut det = CollisionDetector::new();
168        det.insert(0, AABB::new([0.0, 0.0], [2.0, 2.0]));
169        det.insert(1, AABB::new([1.0, 1.0], [3.0, 3.0]));
170        det.insert(2, AABB::new([10.0, 10.0], [11.0, 11.0]));
171        let pairs = det.find_collisions();
172        assert_eq!(pairs, vec![(0, 1)]);
173    }
174}