#![doc = include_str!("../docs/main.md")]
#![deny(missing_docs)]
#[cfg(feature = "approx")]
use approx::ulps_eq;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BoundingBox {
xmin: f64,
xmax: f64,
ymin: f64,
ymax: f64,
}
impl BoundingBox {
pub fn new(xmin: f64, xmax: f64, ymin: f64, ymax: f64) -> Self {
return Self::try_new(xmin, xmax, ymin, ymax)
.expect("one of the conditions xmin <= xmax and ymin <= ymax is not fulfilled");
}
pub fn try_new(xmin: f64, xmax: f64, ymin: f64, ymax: f64) -> Option<Self> {
if xmin > xmax || ymin > ymax {
return None;
}
return Some(BoundingBox {
xmin,
xmax,
ymin,
ymax,
});
}
pub fn xmin(&self) -> f64 {
return self.xmin;
}
pub fn xmax(&self) -> f64 {
return self.xmax;
}
pub fn ymin(&self) -> f64 {
return self.ymin;
}
pub fn ymax(&self) -> f64 {
return self.ymax;
}
pub fn try_set_xmin(&mut self, val: f64) -> bool {
if val > self.xmax {
return false;
} else {
self.xmin = val;
return true;
}
}
pub fn try_set_xmax(&mut self, val: f64) -> bool {
if val < self.xmin {
return false;
} else {
self.xmax = val;
return true;
}
}
pub fn try_set_ymin(&mut self, val: f64) -> bool {
if val > self.ymax {
return false;
} else {
self.ymin = val;
return true;
}
}
pub fn try_set_ymax(&mut self, val: f64) -> bool {
if val < self.ymin {
return false;
} else {
self.ymax = val;
return true;
}
}
pub fn from_points<'a, T: Into<[f64; 2]>, I: Iterator<Item = T>>(mut verts: I) -> Option<Self> {
match verts.next() {
Some(pt) => {
let pt: [f64; 2] = pt.into();
let mut xmin = pt[0];
let mut xmax = pt[0];
let mut ymin = pt[1];
let mut ymax = pt[1];
for vert in verts {
let pt: [f64; 2] = vert.into();
if pt[0] > xmax {
xmax = pt[0]
}
if pt[0] < xmin {
xmin = pt[0]
}
if pt[1] > ymax {
ymax = pt[1]
}
if pt[1] < ymin {
ymin = pt[1]
}
}
return Some(BoundingBox::new(xmin, xmax, ymin, ymax));
}
None => return None,
}
}
pub fn from_bounded_entities<T: Into<BoundingBox>, I: Iterator<Item = T>>(
mut entities: I,
) -> Option<Self> {
let first_bb: BoundingBox = entities.next()?.into();
let bb = entities.fold(first_bb, |acc, drawable| drawable.into().union(&acc));
return Some(bb);
}
pub fn union(&self, other: &BoundingBox) -> BoundingBox {
let xmin: f64;
let xmax: f64;
let ymin: f64;
let ymax: f64;
if self.xmin > other.xmin {
xmin = other.xmin;
} else {
xmin = self.xmin;
}
if self.xmax > other.xmax {
xmax = self.xmax;
} else {
xmax = other.xmax;
}
if self.ymin > other.ymin {
ymin = other.ymin;
} else {
ymin = self.ymin;
}
if self.ymax > other.ymax {
ymax = self.ymax;
} else {
ymax = other.ymax;
}
return BoundingBox {
xmin,
xmax,
ymin,
ymax,
};
}
pub fn contains_point<T: Into<[f64; 2]>>(&self, point: T) -> bool {
let point: [f64; 2] = point.into();
return (self.xmin < point[0] || self.xmin == point[0])
&& (self.ymin < point[1] || self.ymin == point[1])
&& (self.xmax > point[0] || self.xmax == point[0])
&& (self.ymax > point[1] || self.ymax == point[1]);
}
#[cfg(feature = "approx")]
pub fn approx_contains_point<T: Into<[f64; 2]>>(
&self,
point: T,
epsilon: f64,
max_ulps: u32,
) -> bool {
let point: [f64; 2] = point.into();
return (self.xmin < point[0]
|| ulps_eq!(self.xmin, point[0], epsilon = epsilon, max_ulps = max_ulps))
&& (self.ymin < point[1]
|| ulps_eq!(self.ymin, point[1], epsilon = epsilon, max_ulps = max_ulps))
&& (self.xmax > point[0]
|| ulps_eq!(self.xmax, point[0], epsilon = epsilon, max_ulps = max_ulps))
&& (self.ymax > point[1]
|| ulps_eq!(self.ymax, point[1], epsilon = epsilon, max_ulps = max_ulps));
}
pub fn contains(&self, other: &Self) -> bool {
return self.xmin <= other.xmin
&& self.ymin <= other.ymin
&& self.xmax >= other.xmax
&& self.ymax >= other.ymax;
}
#[cfg(feature = "approx")]
pub fn approx_contains(&self, other: &Self, epsilon: f64, max_ulps: u32) -> bool {
return (self.xmin < other.xmin
|| ulps_eq!(
self.xmin,
other.xmin,
epsilon = epsilon,
max_ulps = max_ulps
))
&& (self.ymin < other.ymin
|| ulps_eq!(
self.ymin,
other.ymin,
epsilon = epsilon,
max_ulps = max_ulps
))
&& (self.xmax > other.xmax
|| ulps_eq!(
self.xmax,
other.xmax,
epsilon = epsilon,
max_ulps = max_ulps
))
&& (self.ymax > other.ymax
|| ulps_eq!(
self.ymax,
other.ymax,
epsilon = epsilon,
max_ulps = max_ulps
));
}
#[cfg(feature = "approx")]
pub fn approx_eq(&self, other: &Self, epsilon: f64, max_ulps: u32) -> bool {
return ulps_eq!(
self.xmin(),
other.xmin(),
epsilon = epsilon,
max_ulps = max_ulps
) && ulps_eq!(
self.xmax(),
other.xmax(),
epsilon = epsilon,
max_ulps = max_ulps
) && ulps_eq!(
self.ymin(),
other.ymin(),
epsilon = epsilon,
max_ulps = max_ulps
) && ulps_eq!(
self.ymax(),
other.ymax(),
epsilon = epsilon,
max_ulps = max_ulps
);
}
pub fn intersects(&self, other: &Self) -> bool {
return self.xmin() < other.xmax()
&& other.xmin() < self.xmax()
&& self.ymin() < other.ymax()
&& other.ymin() < self.ymax();
}
pub fn touches(&self, other: &Self) -> bool {
if self.intersects(&other) {
return false;
} else {
return self.xmin() == other.xmax()
|| self.xmax() == other.xmin()
|| self.ymin() == other.ymax()
|| self.ymax() == other.ymin();
}
}
#[cfg(feature = "approx")]
pub fn approx_touches(&self, other: &Self, epsilon: f64, max_ulps: u32) -> bool {
if self.intersects(&other) {
return false;
} else {
return ulps_eq!(
self.xmin(),
other.xmax(),
epsilon = epsilon,
max_ulps = max_ulps
) || ulps_eq!(
self.xmax(),
other.xmin(),
epsilon = epsilon,
max_ulps = max_ulps
) || ulps_eq!(
self.ymin(),
other.ymax(),
epsilon = epsilon,
max_ulps = max_ulps
) || ulps_eq!(
self.ymax(),
other.ymin(),
epsilon = epsilon,
max_ulps = max_ulps
);
}
}
pub fn width(&self) -> f64 {
return self.xmax - self.xmin;
}
pub fn height(&self) -> f64 {
return self.ymax - self.ymin;
}
pub fn center(&self) -> [f64; 2] {
let x = 0.5 * (self.xmax + self.xmin);
let y = 0.5 * (self.ymax + self.ymin);
return [x, y];
}
pub fn translate<T: Into<[f64; 2]>>(&mut self, shift: T) -> () {
let shift: [f64; 2] = shift.into();
self.xmin += shift[0];
self.xmax += shift[0];
self.ymin += shift[1];
self.ymax += shift[1];
}
pub fn scale(&mut self, factor: f64) -> () {
let dw = 0.5 * (factor - 1.0) * self.width();
let dh = 0.5 * (factor - 1.0) * self.height();
self.xmin = self.xmin - dw;
self.xmax = self.xmax + dw;
self.ymin = self.ymin - dh;
self.ymax = self.ymax + dh;
}
pub fn remove_singular_dimensions(&mut self, add_to_extr: f64) {
if self.width() == 0.0 {
self.xmin -= add_to_extr;
self.xmax += add_to_extr;
}
if self.height() == 0.0 {
self.ymin -= add_to_extr;
self.ymax += add_to_extr;
}
}
pub fn is_finite(&self) -> bool {
return self.xmin.is_finite()
&& self.xmax.is_finite()
&& self.ymin.is_finite()
&& self.ymax.is_finite();
}
}
impl From<[f64; 2]> for BoundingBox {
fn from(v: [f64; 2]) -> Self {
return (&v).into();
}
}
impl From<&'_ [f64; 2]> for BoundingBox {
fn from(v: &'_ [f64; 2]) -> Self {
return BoundingBox::new(v[0], v[0], v[1], v[1]);
}
}
pub trait ToBoundingBox {
fn bounding_box(&self) -> BoundingBox;
}
impl<T> ToBoundingBox for T
where
for<'a> &'a T: Into<BoundingBox>,
{
fn bounding_box(&self) -> BoundingBox {
self.into()
}
}