myelin_geometry/
aabb.rs

1use crate::{Intersects, Point};
2
3/// An axix-aligned bounding box
4///
5/// ```other
6/// ┼─────────────────────────────────────── x
7/// │
8/// │  Upper left → ┌─────────────┐
9/// │               │             │
10/// │               │             │
11/// │               └─────────────┘ ← Lower right
12/// │
13/// y
14/// ```
15#[derive(Debug, PartialEq, Copy, Clone)]
16pub struct Aabb {
17    /// The coordinates of the upper left corner of the box
18    pub upper_left: Point,
19    /// The coordinates of the lower right corner of the box
20    pub lower_right: Point,
21}
22
23impl Aabb {
24    /// Creates a new [`Aabb`] from two points.
25    ///
26    /// # Examples
27    ///
28    /// ## From tuples
29    /// ```
30    /// use myelin_geometry::Aabb;
31    ///
32    /// let area = Aabb::try_new((10.0, 0.0), (20.0, 10.0)).expect("Invalid aabb");
33    /// ```
34    ///
35    /// ## From points
36    /// ```
37    /// use myelin_geometry::{Aabb, Point};
38    ///
39    /// let area =
40    ///     Aabb::try_new(Point { x: 0.0, y: 10.0 }, Point { x: 20.0, y: 20.0 }).expect("Invalid aabb");
41    /// ```
42    ///
43    /// # Errors
44    ///
45    /// Returns an error when both points are the same.
46    ///
47    /// [`Aabb`]: ./struct.Aabb.html
48    pub fn try_new<P1, P2>(upper_left: P1, lower_right: P2) -> Result<Self, ()>
49    where
50        P1: Into<Point>,
51        P2: Into<Point>,
52    {
53        let upper_left = upper_left.into();
54        let lower_right = lower_right.into();
55
56        if upper_left.x >= lower_right.x || upper_left.y >= lower_right.y {
57            Err(())
58        } else {
59            Ok(Self {
60                upper_left,
61                lower_right,
62            })
63        }
64    }
65}
66
67impl Intersects for Aabb {
68    /// Returns wether the bounds of another `Aabb` are touching or
69    /// inside this `Aabb`.
70    /// ```other
71    /// ┼─────────────────────────────────────── x
72    /// │
73    /// │   ┌─────────────┐
74    /// │   │             │
75    /// │   │          ┌──│───────┐
76    /// │   └─────────────┘       │
77    /// │              └──────────┘
78    /// y
79    /// ```
80    fn intersects(&self, other: &Aabb) -> bool {
81        let x_overlaps =
82            self.upper_left.x <= other.lower_right.x && self.lower_right.x >= other.upper_left.x;
83        let y_overlaps =
84            self.upper_left.y <= other.lower_right.y && self.lower_right.y >= other.upper_left.y;
85
86        x_overlaps && y_overlaps
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn try_new_errors_for_equal_points() {
96        assert!(Aabb::try_new((10.0, 10.0), (10.0, 10.0)).is_err());
97    }
98
99    #[test]
100    fn try_new_errors_when_upper_left_is_larger_than_lower_right() {
101        assert!(Aabb::try_new((10.0, 10.0), (0.0, 0.0)).is_err());
102    }
103
104    #[test]
105    fn try_new_errors_when_upper_left_x_is_larger_than_lower_right_x() {
106        assert!(Aabb::try_new((10.0, 0.0), (0.0, 5.5)).is_err());
107    }
108
109    #[test]
110    fn try_new_errors_when_upper_left_y_is_larger_than_lower_right_y() {
111        assert!(Aabb::try_new((0.0, 10.0), (5.0, 0.0)).is_err());
112    }
113
114    #[test]
115    fn intersects_self() {
116        let aabb = Aabb::try_new((0.0, 0.0), (10.0, 10.0)).unwrap();
117        assert!(aabb.intersects(&aabb));
118    }
119
120    #[test]
121    fn intersects_contained() {
122        let bigger_aabb = Aabb::try_new((0.0, 0.0), (10.0, 10.0)).unwrap();
123        let smaller_aabb = Aabb::try_new((2.0, 2.0), (8.0, 8.0)).unwrap();
124        assert!(bigger_aabb.intersects(&smaller_aabb));
125        assert!(smaller_aabb.intersects(&bigger_aabb));
126    }
127
128    #[test]
129    fn intersects_touching() {
130        let left_aabb = Aabb::try_new((0.0, 0.0), (10.0, 10.0)).unwrap();
131        let right_aabb = Aabb::try_new((10.0, 0.0), (20.0, 10.0)).unwrap();
132        assert!(left_aabb.intersects(&right_aabb));
133        assert!(right_aabb.intersects(&left_aabb));
134    }
135
136    #[test]
137    fn intersects_diagonally_touching() {
138        let left_aabb = Aabb::try_new((0.0, 0.0), (10.0, 10.0)).unwrap();
139        let right_aabb = Aabb::try_new((10.0, 10.0), (20.0, 11.0)).unwrap();
140        assert!(left_aabb.intersects(&right_aabb));
141        assert!(right_aabb.intersects(&left_aabb));
142    }
143
144    #[test]
145    fn intersects_intersecting() {
146        let first_aabb = Aabb::try_new((0.0, 0.0), (10.0, 10.0)).unwrap();
147        let second_aabb = Aabb::try_new((8.0, 8.0), (20.0, 20.0)).unwrap();
148        assert!(first_aabb.intersects(&second_aabb));
149        assert!(second_aabb.intersects(&first_aabb));
150    }
151
152    #[test]
153    fn intersects_intersecting_when_negative() {
154        let first_aabb = Aabb::try_new((-10.0, -10.0), (-5.0, -5.0)).unwrap();
155        let second_aabb = Aabb::try_new((-6.0, -20.0), (-3.0, -3.0)).unwrap();
156        assert!(first_aabb.intersects(&second_aabb));
157        assert!(second_aabb.intersects(&first_aabb));
158    }
159
160    #[test]
161    fn intersects_intersecting_when_negative_and_positive() {
162        let first_aabb = Aabb::try_new((-5.0, -5.0), (5.0, 5.0)).unwrap();
163        let second_aabb = Aabb::try_new((-6.0, -20.0), (0.0, 2.0)).unwrap();
164        assert!(first_aabb.intersects(&second_aabb));
165        assert!(second_aabb.intersects(&first_aabb));
166    }
167
168    #[test]
169    fn does_not_intersect_when_appart() {
170        let first_aabb = Aabb::try_new((0.0, 0.0), (10.0, 10.0)).unwrap();
171        let second_aabb = Aabb::try_new((20.0, 0.0), (21.0, 20.0)).unwrap();
172        assert!(!first_aabb.intersects(&second_aabb));
173        assert!(!second_aabb.intersects(&first_aabb));
174    }
175}