layout21raw/
bbox.rs

1//!
2//! # Rectangular Bounding Boxes and Associated Trait
3//!
4
5// Crates.io
6use serde::{Deserialize, Serialize};
7
8// Local imports
9use crate::{
10    geom::{Point, Shape},
11    Int, Rect,
12};
13
14/// # Axis-Aligned Rectangular Bounding Box
15///
16/// Points `p0` and `p1` represent opposite corners of a bounding rectangle.
17/// `p0` is always closest to negative-infinity, in both x and y,
18/// and `p1` is always closest to positive-infinity.
19///
20#[derive(Debug, Default, Copy, Clone, Deserialize, Serialize, PartialEq, Eq)]
21pub struct BoundBox {
22    pub p0: Point,
23    pub p1: Point,
24}
25impl BoundBox {
26    /// Create a new [BoundBox] from two [Point]s.
27    /// Callers are responsible for ensuring that p0.x <= p1.x, and p0.y <= p1.y.
28    fn new(p0: Point, p1: Point) -> Self {
29        Self { p0, p1 }
30    }
31    /// Create a new [BoundBox] from a single [Point].
32    /// The resultant [BoundBox] comprises solely the point, having zero area.
33    pub fn from_point(pt: &Point) -> Self {
34        Self {
35            p0: pt.clone(),
36            p1: pt.clone(),
37        }
38    }
39    /// Create a new [BoundBox] from two points
40    pub fn from_points(p0: &Point, p1: &Point) -> Self {
41        Self {
42            p0: Point::new(p0.x.min(p1.x), p0.y.min(p1.y)),
43            p1: Point::new(p0.x.max(p1.x), p0.y.max(p1.y)),
44        }
45    }
46    /// Create an empty, otherwise invalid [BoundBox]
47    pub fn empty() -> Self {
48        Self {
49            p0: Point::new(Int::MAX, Int::MAX),
50            p1: Point::new(Int::MIN, Int::MIN),
51        }
52    }
53    /// Boolean indication of whether a box is empty
54    pub fn is_empty(&self) -> bool {
55        self.p0.x > self.p1.x || self.p0.y > self.p1.y
56    }
57    /// Boolean indication of whether [Point] `pt` lies inside out box.
58    pub fn contains(&self, pt: &Point) -> bool {
59        self.p0.x <= pt.x && self.p1.x >= pt.x && self.p0.y <= pt.y && self.p1.y >= pt.y
60    }
61    /// Expand an existing [BoundBox] in all directions by `delta`
62    pub fn expand(&mut self, delta: Int) {
63        self.p0.x -= delta;
64        self.p0.y -= delta;
65        self.p1.x += delta;
66        self.p1.y += delta;
67    }
68    /// Get the box's size as an (x,y) tuple
69    pub fn size(&self) -> (Int, Int) {
70        (self.p1.x - self.p0.x, self.p1.y - self.p0.y)
71    }
72    /// Get the box's center
73    pub fn center(&self) -> Point {
74        Point::new((self.p0.x + self.p1.x) / 2, (self.p0.y + self.p1.y) / 2)
75    }
76}
77
78///
79/// # Bounding Box Trait
80///
81/// Methods for interacting with [BoundBox]s.
82/// Implementations for [Point]s, [Shape]s, and [BoundBox]s
83/// enable geometric transformations such as union and intersection.  
84///
85pub trait BoundBoxTrait {
86    /// Compute a rectangular bounding box around the implementing type.
87    fn bbox(&self) -> BoundBox;
88    /// Compute the intersection with rectangular bounding box `bbox`.
89    /// Creates and returns a new [BoundBox].
90    /// Default implementation is to return the intersection of `self.bbox()` and `bbox`.
91    fn intersection(&self, bbox: &BoundBox) -> BoundBox {
92        self.bbox().intersection(&bbox)
93    }
94    /// Compute the union with rectangular bounding box `bbox`.
95    /// Creates and returns a new [BoundBox].
96    /// Default implementation is to return the union of `self.bbox()` and `bbox`.
97    fn union(&self, bbox: &BoundBox) -> BoundBox {
98        self.bbox().union(&bbox)
99    }
100}
101impl BoundBoxTrait for BoundBox {
102    fn bbox(&self) -> BoundBox {
103        // We're great as we are, as a [BoundBox] already.
104        // Create a clone to adhere to our "new bbox" return-type.
105        self.clone()
106    }
107    fn intersection(&self, bbox: &BoundBox) -> BoundBox {
108        let pmin = Point::new(self.p0.x.max(bbox.p0.x), self.p0.y.max(bbox.p0.y));
109        let pmax = Point::new(self.p1.x.min(bbox.p1.x), self.p1.y.min(bbox.p1.y));
110        // Check for empty intersection, and return an empty box if so
111        if pmin.x > pmax.x || pmin.y > pmax.y {
112            return BoundBox::empty();
113        }
114        // Otherwise return the intersection
115        BoundBox::new(pmin, pmax)
116    }
117    fn union(&self, bbox: &BoundBox) -> BoundBox {
118        // Take the minimum and maximum of the two bounding boxes
119        BoundBox::new(
120            Point::new(self.p0.x.min(bbox.p0.x), self.p0.y.min(bbox.p0.y)),
121            Point::new(self.p1.x.max(bbox.p1.x), self.p1.y.max(bbox.p1.y)),
122        )
123    }
124}
125impl BoundBoxTrait for Point {
126    fn bbox(&self) -> BoundBox {
127        BoundBox::from_point(self)
128    }
129    fn intersection(&self, bbox: &BoundBox) -> BoundBox {
130        if !bbox.contains(self) {
131            return BoundBox::empty();
132        }
133        bbox.intersection(&BoundBox::from_point(self))
134    }
135    fn union(&self, bbox: &BoundBox) -> BoundBox {
136        BoundBox::new(
137            Point::new(self.x.min(bbox.p0.x), self.y.min(bbox.p0.y)),
138            Point::new(self.x.max(bbox.p1.x), self.y.max(bbox.p1.y)),
139        )
140    }
141}
142impl BoundBoxTrait for Shape {
143    fn bbox(&self) -> BoundBox {
144        // Dispatch based on shape-type, either two-Point or multi-Point form.
145        match self {
146            Shape::Rect(ref r) => BoundBox::from_points(&r.p0, &r.p1),
147            Shape::Polygon(ref p) => (&p.points).bbox(),
148            Shape::Path(ref p) => (&p.points).bbox(),
149        }
150    }
151}
152
153impl BoundBoxTrait for Rect {
154    fn bbox(&self) -> BoundBox {
155        BoundBox::from_points(&self.p0, &self.p1)
156    }
157}
158
159impl BoundBoxTrait for Vec<Point> {
160    fn bbox(&self) -> BoundBox {
161        // Take the union of all points in the vector
162        let mut bbox = BoundBox::empty();
163        for pt in self {
164            bbox = bbox.union(&pt.bbox());
165        }
166        bbox
167    }
168}