geometry/
bbox.rs

1//! Axis-aligned rectangular bounding boxes.
2
3use impl_trait_for_tuples::impl_for_tuples;
4
5use crate::{polygon::Polygon, rect::Rect, union::BoundingUnion};
6
7/// A geometric shape that has a bounding box.
8///
9/// # Examples
10///
11/// ```
12/// # use geometry::prelude::*;
13/// let rect = Rect::from_sides(0, 0, 100, 200);
14/// assert_eq!(rect.bbox(), Some(Rect::from_sides(0, 0, 100, 200)));
15/// let rect = Rect::from_xy(50, 70);
16/// assert_eq!(rect.bbox(), Some(Rect::from_sides(50, 70, 50, 70)));
17/// ```
18pub trait Bbox {
19    /// Computes the axis-aligned rectangular bounding box.
20    ///
21    /// If empty, this method should return `None`.
22    /// Note that poinst and zero-area rectangles are not empty:
23    /// these shapes contain a single point, and their bounding box
24    /// implementations will return `Some(_)`.
25    fn bbox(&self) -> Option<Rect>;
26
27    /// Computes the axis-aligned rectangular bounding box, panicking
28    /// if it is empty.
29    fn bbox_rect(&self) -> Rect {
30        self.bbox().unwrap()
31    }
32}
33
34impl<T> Bbox for &T
35where
36    T: Bbox,
37{
38    fn bbox(&self) -> Option<Rect> {
39        T::bbox(*self)
40    }
41}
42
43#[impl_for_tuples(64)]
44impl Bbox for TupleIdentifier {
45    #[allow(clippy::let_and_return)]
46    fn bbox(&self) -> Option<Rect> {
47        let mut bbox = None;
48        for_tuples!( #( bbox = bbox.bounding_union(&TupleIdentifier.bbox()); )* );
49        bbox
50    }
51}
52
53impl<T: Bbox> Bbox for Vec<T> {
54    fn bbox(&self) -> Option<Rect> {
55        let mut bbox = None;
56        for item in self {
57            bbox = bbox.bounding_union(&item.bbox());
58        }
59        bbox
60    }
61}
62
63impl Bbox for Option<Rect> {
64    fn bbox(&self) -> Option<Rect> {
65        *self
66    }
67}
68
69impl Bbox for Option<Polygon> {
70    fn bbox(&self) -> Option<Rect> {
71        match self {
72            Some(polygon) => Rect::from_sides_option(
73                polygon.left(),
74                polygon.bot(),
75                polygon.right(),
76                polygon.top(),
77            ),
78            None => None,
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use crate::{bbox::Bbox, point::Point, polygon::Polygon, rect::Rect};
86
87    #[test]
88    fn bbox_works_for_tuples() {
89        let tuple = (
90            Rect::from_sides(0, 0, 100, 200),
91            Rect::from_sides(-50, 20, 90, 250),
92        );
93        assert_eq!(tuple.bbox(), Some(Rect::from_sides(-50, 0, 100, 250)));
94    }
95
96    #[test]
97    fn bbox_works_for_vecs() {
98        let v = vec![
99            Rect::from_sides(0, 0, 100, 200),
100            Rect::from_sides(-50, 20, 90, 250),
101        ];
102        assert_eq!(v.bbox(), Some(Rect::from_sides(-50, 0, 100, 250)));
103    }
104
105    #[test]
106    fn bbox_works_for_polygon() {
107        let points = vec![
108            Point { x: -10, y: 25 },
109            Point { x: 0, y: 16 },
110            Point { x: 40, y: -20 },
111        ];
112        let polygon = Polygon::from_verts(points);
113        assert_eq!(polygon.bbox(), Some(Rect::from_sides(-10, -20, 40, 25)));
114    }
115
116    #[test]
117    fn bbox_works_for_diff_types() {
118        let points = vec![
119            Point { x: -10, y: 25 },
120            Point { x: 0, y: 16 },
121            Point { x: 40, y: -20 },
122        ];
123        let tuple: (Rect, Polygon) = (
124            Rect::from_sides(0, 0, 100, 200),
125            Polygon::from_verts(points),
126        );
127        assert_eq!(tuple.bbox(), Some(Rect::from_sides(-10, -20, 100, 200)));
128    }
129}