1#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
17#[derive(Debug, Clone, Copy, PartialEq)]
18pub struct Bounds {
19 pub min: [f64; 2],
21 pub max: [f64; 2],
23}
24
25impl Bounds {
26 #[must_use]
28 pub const fn new(min: [f64; 2], max: [f64; 2]) -> Self {
29 Self { min, max }
30 }
31
32 #[must_use]
34 pub const fn point(p: [f64; 2]) -> Self {
35 Self { min: p, max: p }
36 }
37
38 #[must_use]
40 pub fn area(&self) -> f64 {
41 (self.max[0] - self.min[0]) * (self.max[1] - self.min[1])
42 }
43
44 #[must_use]
46 pub fn half_perimeter(&self) -> f64 {
47 (self.max[0] - self.min[0]) + (self.max[1] - self.min[1])
48 }
49
50 #[must_use]
52 pub fn union(&self, other: &Bounds) -> Bounds {
53 Bounds {
54 min: [self.min[0].min(other.min[0]), self.min[1].min(other.min[1])],
55 max: [self.max[0].max(other.max[0]), self.max[1].max(other.max[1])],
56 }
57 }
58
59 #[must_use]
62 pub fn enlargement(&self, other: &Bounds) -> f64 {
63 self.union(other).area() - self.area()
64 }
65
66 #[must_use]
69 pub fn intersects(&self, other: &Bounds) -> bool {
70 self.min[0] <= other.max[0]
71 && self.max[0] >= other.min[0]
72 && self.min[1] <= other.max[1]
73 && self.max[1] >= other.min[1]
74 }
75
76 #[must_use]
78 pub fn contains(&self, other: &Bounds) -> bool {
79 self.min[0] <= other.min[0]
80 && self.max[0] >= other.max[0]
81 && self.min[1] <= other.min[1]
82 && self.max[1] >= other.max[1]
83 }
84
85 #[must_use]
90 #[inline]
91 pub fn covered_by(&self, other: &Bounds) -> bool {
92 other.contains(self)
93 }
94
95 #[must_use]
103 #[inline]
104 pub fn within(&self, other: &Bounds) -> bool {
105 self.min[0] < self.max[0] && self.min[1] < self.max[1] && self.covered_by(other)
106 }
107
108 #[must_use]
110 #[inline]
111 pub fn disjoint(&self, other: &Bounds) -> bool {
112 !self.intersects(other)
113 }
114
115 #[must_use]
120 #[inline]
121 pub fn overlaps(&self, other: &Bounds) -> bool {
122 self.min[0] < other.max[0]
123 && self.max[0] > other.min[0]
124 && self.min[1] < other.max[1]
125 && self.max[1] > other.min[1]
126 && !self.contains(other)
127 && !other.contains(self)
128 }
129
130 #[must_use]
133 pub fn min_distance_to(&self, p: [f64; 2]) -> f64 {
134 geometry_coords::math::sqrt(self.comparable_min_distance_to(p))
135 }
136
137 #[must_use]
142 pub fn comparable_min_distance_to(&self, p: [f64; 2]) -> f64 {
143 let dx = clamp_gap(p[0], self.min[0], self.max[0]);
144 let dy = clamp_gap(p[1], self.min[1], self.max[1]);
145 dx * dx + dy * dy
146 }
147
148 #[must_use]
150 pub fn center(&self) -> [f64; 2] {
151 [
152 (self.min[0] + self.max[0]) * 0.5,
153 (self.min[1] + self.max[1]) * 0.5,
154 ]
155 }
156}
157
158fn clamp_gap(v: f64, lo: f64, hi: f64) -> f64 {
160 if v < lo {
161 lo - v
162 } else if v > hi {
163 v - hi
164 } else {
165 0.0
166 }
167}
168
169#[must_use]
176pub fn union_all(boxes: &[Bounds]) -> Bounds {
177 let mut acc = boxes[0];
178 for b in &boxes[1..] {
179 acc = acc.union(b);
180 }
181 acc
182}
183
184#[cfg(test)]
185#[allow(
186 clippy::float_cmp,
187 reason = "box arithmetic on exact integer-valued literals"
188)]
189mod tests {
190 use super::{Bounds, union_all};
191
192 #[test]
193 fn area_and_perimeter() {
194 let b = Bounds::new([0.0, 0.0], [2.0, 3.0]);
195 assert_eq!(b.area(), 6.0);
196 assert_eq!(b.half_perimeter(), 5.0);
197 }
198
199 #[test]
200 fn union_and_enlargement() {
201 let a = Bounds::new([0.0, 0.0], [1.0, 1.0]);
202 let b = Bounds::new([2.0, 2.0], [3.0, 3.0]);
203 let u = a.union(&b);
204 assert_eq!(u, Bounds::new([0.0, 0.0], [3.0, 3.0]));
205 assert_eq!(a.enlargement(&b), 8.0);
207 }
208
209 #[test]
210 fn intersects_and_contains() {
211 let a = Bounds::new([0.0, 0.0], [4.0, 4.0]);
212 let inside = Bounds::new([1.0, 1.0], [2.0, 2.0]);
213 let overlap = Bounds::new([3.0, 3.0], [5.0, 5.0]);
214 let apart = Bounds::new([9.0, 9.0], [10.0, 10.0]);
215 assert!(a.contains(&inside));
216 assert!(a.intersects(&overlap));
217 assert!(!a.contains(&overlap));
218 assert!(!a.intersects(&apart));
219 }
220
221 #[test]
222 fn min_distance() {
223 let b = Bounds::new([0.0, 0.0], [2.0, 2.0]);
224 assert_eq!(b.min_distance_to([1.0, 1.0]), 0.0); assert_eq!(b.min_distance_to([5.0, 1.0]), 3.0); assert_eq!(b.min_distance_to([5.0, 6.0]), 5.0); assert_eq!(b.comparable_min_distance_to([5.0, 6.0]), 25.0);
228 }
229
230 #[test]
231 fn union_all_of_many() {
232 let boxes = [
233 Bounds::point([1.0, 1.0]),
234 Bounds::point([-2.0, 3.0]),
235 Bounds::point([4.0, -1.0]),
236 ];
237 assert_eq!(union_all(&boxes), Bounds::new([-2.0, -1.0], [4.0, 3.0]));
238 }
239}