#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Bounds {
pub min: [f64; 2],
pub max: [f64; 2],
}
impl Bounds {
#[must_use]
pub const fn new(min: [f64; 2], max: [f64; 2]) -> Self {
Self { min, max }
}
#[must_use]
pub const fn point(p: [f64; 2]) -> Self {
Self { min: p, max: p }
}
#[must_use]
pub fn area(&self) -> f64 {
(self.max[0] - self.min[0]) * (self.max[1] - self.min[1])
}
#[must_use]
pub fn half_perimeter(&self) -> f64 {
(self.max[0] - self.min[0]) + (self.max[1] - self.min[1])
}
#[must_use]
pub fn union(&self, other: &Bounds) -> Bounds {
Bounds {
min: [self.min[0].min(other.min[0]), self.min[1].min(other.min[1])],
max: [self.max[0].max(other.max[0]), self.max[1].max(other.max[1])],
}
}
#[must_use]
pub fn enlargement(&self, other: &Bounds) -> f64 {
self.union(other).area() - self.area()
}
#[must_use]
pub fn intersects(&self, other: &Bounds) -> bool {
self.min[0] <= other.max[0]
&& self.max[0] >= other.min[0]
&& self.min[1] <= other.max[1]
&& self.max[1] >= other.min[1]
}
#[must_use]
pub fn contains(&self, other: &Bounds) -> bool {
self.min[0] <= other.min[0]
&& self.max[0] >= other.max[0]
&& self.min[1] <= other.min[1]
&& self.max[1] >= other.max[1]
}
#[must_use]
#[inline]
pub fn covered_by(&self, other: &Bounds) -> bool {
other.contains(self)
}
#[must_use]
#[inline]
pub fn within(&self, other: &Bounds) -> bool {
self.min[0] < self.max[0] && self.min[1] < self.max[1] && self.covered_by(other)
}
#[must_use]
#[inline]
pub fn disjoint(&self, other: &Bounds) -> bool {
!self.intersects(other)
}
#[must_use]
#[inline]
pub fn overlaps(&self, other: &Bounds) -> bool {
self.min[0] < other.max[0]
&& self.max[0] > other.min[0]
&& self.min[1] < other.max[1]
&& self.max[1] > other.min[1]
&& !self.contains(other)
&& !other.contains(self)
}
#[must_use]
pub fn min_distance_to(&self, p: [f64; 2]) -> f64 {
geometry_coords::math::sqrt(self.comparable_min_distance_to(p))
}
#[must_use]
pub fn comparable_min_distance_to(&self, p: [f64; 2]) -> f64 {
let dx = clamp_gap(p[0], self.min[0], self.max[0]);
let dy = clamp_gap(p[1], self.min[1], self.max[1]);
dx * dx + dy * dy
}
#[must_use]
pub fn center(&self) -> [f64; 2] {
[
(self.min[0] + self.max[0]) * 0.5,
(self.min[1] + self.max[1]) * 0.5,
]
}
}
fn clamp_gap(v: f64, lo: f64, hi: f64) -> f64 {
if v < lo {
lo - v
} else if v > hi {
v - hi
} else {
0.0
}
}
#[must_use]
pub fn union_all(boxes: &[Bounds]) -> Bounds {
let mut acc = boxes[0];
for b in &boxes[1..] {
acc = acc.union(b);
}
acc
}
#[cfg(test)]
#[allow(
clippy::float_cmp,
reason = "box arithmetic on exact integer-valued literals"
)]
mod tests {
use super::{Bounds, union_all};
#[test]
fn area_and_perimeter() {
let b = Bounds::new([0.0, 0.0], [2.0, 3.0]);
assert_eq!(b.area(), 6.0);
assert_eq!(b.half_perimeter(), 5.0);
}
#[test]
fn union_and_enlargement() {
let a = Bounds::new([0.0, 0.0], [1.0, 1.0]);
let b = Bounds::new([2.0, 2.0], [3.0, 3.0]);
let u = a.union(&b);
assert_eq!(u, Bounds::new([0.0, 0.0], [3.0, 3.0]));
assert_eq!(a.enlargement(&b), 8.0);
}
#[test]
fn intersects_and_contains() {
let a = Bounds::new([0.0, 0.0], [4.0, 4.0]);
let inside = Bounds::new([1.0, 1.0], [2.0, 2.0]);
let overlap = Bounds::new([3.0, 3.0], [5.0, 5.0]);
let apart = Bounds::new([9.0, 9.0], [10.0, 10.0]);
assert!(a.contains(&inside));
assert!(a.intersects(&overlap));
assert!(!a.contains(&overlap));
assert!(!a.intersects(&apart));
}
#[test]
fn min_distance() {
let b = Bounds::new([0.0, 0.0], [2.0, 2.0]);
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);
}
#[test]
fn union_all_of_many() {
let boxes = [
Bounds::point([1.0, 1.0]),
Bounds::point([-2.0, 3.0]),
Bounds::point([4.0, -1.0]),
];
assert_eq!(union_all(&boxes), Bounds::new([-2.0, -1.0], [4.0, 3.0]));
}
}