1#[derive(Debug, Clone, Copy, PartialEq)]
17pub struct Bounds {
18 pub min: [f64; 2],
20 pub max: [f64; 2],
22}
23
24impl Bounds {
25 #[must_use]
27 pub const fn new(min: [f64; 2], max: [f64; 2]) -> Self {
28 Self { min, max }
29 }
30
31 #[must_use]
33 pub const fn point(p: [f64; 2]) -> Self {
34 Self { min: p, max: p }
35 }
36
37 #[must_use]
39 pub fn area(&self) -> f64 {
40 (self.max[0] - self.min[0]) * (self.max[1] - self.min[1])
41 }
42
43 #[must_use]
45 pub fn half_perimeter(&self) -> f64 {
46 (self.max[0] - self.min[0]) + (self.max[1] - self.min[1])
47 }
48
49 #[must_use]
51 pub fn union(&self, other: &Bounds) -> Bounds {
52 Bounds {
53 min: [self.min[0].min(other.min[0]), self.min[1].min(other.min[1])],
54 max: [self.max[0].max(other.max[0]), self.max[1].max(other.max[1])],
55 }
56 }
57
58 #[must_use]
61 pub fn enlargement(&self, other: &Bounds) -> f64 {
62 self.union(other).area() - self.area()
63 }
64
65 #[must_use]
68 pub fn intersects(&self, other: &Bounds) -> bool {
69 self.min[0] <= other.max[0]
70 && self.max[0] >= other.min[0]
71 && self.min[1] <= other.max[1]
72 && self.max[1] >= other.min[1]
73 }
74
75 #[must_use]
77 pub fn contains(&self, other: &Bounds) -> bool {
78 self.min[0] <= other.min[0]
79 && self.max[0] >= other.max[0]
80 && self.min[1] <= other.min[1]
81 && self.max[1] >= other.max[1]
82 }
83
84 #[must_use]
87 pub fn min_distance_to(&self, p: [f64; 2]) -> f64 {
88 self.comparable_min_distance_to(p).sqrt()
89 }
90
91 #[must_use]
96 pub fn comparable_min_distance_to(&self, p: [f64; 2]) -> f64 {
97 let dx = clamp_gap(p[0], self.min[0], self.max[0]);
98 let dy = clamp_gap(p[1], self.min[1], self.max[1]);
99 dx * dx + dy * dy
100 }
101
102 #[must_use]
104 pub fn center(&self) -> [f64; 2] {
105 [
106 (self.min[0] + self.max[0]) * 0.5,
107 (self.min[1] + self.max[1]) * 0.5,
108 ]
109 }
110}
111
112fn clamp_gap(v: f64, lo: f64, hi: f64) -> f64 {
114 if v < lo {
115 lo - v
116 } else if v > hi {
117 v - hi
118 } else {
119 0.0
120 }
121}
122
123#[must_use]
130pub fn union_all(boxes: &[Bounds]) -> Bounds {
131 let mut acc = boxes[0];
132 for b in &boxes[1..] {
133 acc = acc.union(b);
134 }
135 acc
136}
137
138#[cfg(test)]
139#[allow(
140 clippy::float_cmp,
141 reason = "box arithmetic on exact integer-valued literals"
142)]
143mod tests {
144 use super::{Bounds, union_all};
145
146 #[test]
147 fn area_and_perimeter() {
148 let b = Bounds::new([0.0, 0.0], [2.0, 3.0]);
149 assert_eq!(b.area(), 6.0);
150 assert_eq!(b.half_perimeter(), 5.0);
151 }
152
153 #[test]
154 fn union_and_enlargement() {
155 let a = Bounds::new([0.0, 0.0], [1.0, 1.0]);
156 let b = Bounds::new([2.0, 2.0], [3.0, 3.0]);
157 let u = a.union(&b);
158 assert_eq!(u, Bounds::new([0.0, 0.0], [3.0, 3.0]));
159 assert_eq!(a.enlargement(&b), 8.0);
161 }
162
163 #[test]
164 fn intersects_and_contains() {
165 let a = Bounds::new([0.0, 0.0], [4.0, 4.0]);
166 let inside = Bounds::new([1.0, 1.0], [2.0, 2.0]);
167 let overlap = Bounds::new([3.0, 3.0], [5.0, 5.0]);
168 let apart = Bounds::new([9.0, 9.0], [10.0, 10.0]);
169 assert!(a.contains(&inside));
170 assert!(a.intersects(&overlap));
171 assert!(!a.contains(&overlap));
172 assert!(!a.intersects(&apart));
173 }
174
175 #[test]
176 fn min_distance() {
177 let b = Bounds::new([0.0, 0.0], [2.0, 2.0]);
178 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);
182 }
183
184 #[test]
185 fn union_all_of_many() {
186 let boxes = [
187 Bounds::point([1.0, 1.0]),
188 Bounds::point([-2.0, 3.0]),
189 Bounds::point([4.0, -1.0]),
190 ];
191 assert_eq!(union_all(&boxes), Bounds::new([-2.0, -1.0], [4.0, 3.0]));
192 }
193}