use crate::{coord, polygon, Coord, CoordFloat, CoordNum, Line, Polygon};
#[derive(Eq, PartialEq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Rect<T: CoordNum = f64> {
min: Coord<T>,
max: Coord<T>,
}
impl<T: CoordNum> Rect<T> {
pub fn new<C>(c1: C, c2: C) -> Self
where
C: Into<Coord<T>>,
{
let c1 = c1.into();
let c2 = c2.into();
let (min_x, max_x) = if c1.x < c2.x {
(c1.x, c2.x)
} else {
(c2.x, c1.x)
};
let (min_y, max_y) = if c1.y < c2.y {
(c1.y, c2.y)
} else {
(c2.y, c1.y)
};
Self {
min: coord! { x: min_x, y: min_y },
max: coord! { x: max_x, y: max_y },
}
}
#[deprecated(
since = "0.6.2",
note = "Use `Rect::new` instead, since `Rect::try_new` will never Error"
)]
#[allow(deprecated)]
pub fn try_new<C>(c1: C, c2: C) -> Result<Rect<T>, InvalidRectCoordinatesError>
where
C: Into<Coord<T>>,
{
Ok(Rect::new(c1, c2))
}
pub fn min(self) -> Coord<T> {
self.min
}
pub fn set_min<C>(&mut self, min: C)
where
C: Into<Coord<T>>,
{
self.min = min.into();
self.assert_valid_bounds();
}
pub fn max(self) -> Coord<T> {
self.max
}
pub fn set_max<C>(&mut self, max: C)
where
C: Into<Coord<T>>,
{
self.max = max.into();
self.assert_valid_bounds();
}
pub fn width(self) -> T {
self.max().x - self.min().x
}
pub fn height(self) -> T {
self.max().y - self.min().y
}
pub fn to_polygon(self) -> Polygon<T> {
polygon![
(x: self.max.x, y: self.min.y),
(x: self.max.x, y: self.max.y),
(x: self.min.x, y: self.max.y),
(x: self.min.x, y: self.min.y),
(x: self.max.x, y: self.min.y),
]
}
pub fn to_lines(&self) -> [Line<T>; 4] {
[
Line::new(
coord! {
x: self.max.x,
y: self.min.y,
},
coord! {
x: self.max.x,
y: self.max.y,
},
),
Line::new(
coord! {
x: self.max.x,
y: self.max.y,
},
coord! {
x: self.min.x,
y: self.max.y,
},
),
Line::new(
coord! {
x: self.min.x,
y: self.max.y,
},
coord! {
x: self.min.x,
y: self.min.y,
},
),
Line::new(
coord! {
x: self.min.x,
y: self.min.y,
},
coord! {
x: self.max.x,
y: self.min.y,
},
),
]
}
pub fn split_x(self) -> [Rect<T>; 2] {
let two = T::one() + T::one();
let mid_x = self.min().x + self.width() / two;
[
Rect::new(self.min(), coord! { x: mid_x, y: self.max().y }),
Rect::new(coord! { x: mid_x, y: self.min().y }, self.max()),
]
}
pub fn split_y(self) -> [Rect<T>; 2] {
let two = T::one() + T::one();
let mid_y = self.min().y + self.height() / two;
[
Rect::new(self.min(), coord! { x: self.max().x, y: mid_y }),
Rect::new(coord! { x: self.min().x, y: mid_y }, self.max()),
]
}
fn assert_valid_bounds(&self) {
if !self.has_valid_bounds() {
panic!("{}", RECT_INVALID_BOUNDS_ERROR);
}
}
fn has_valid_bounds(&self) -> bool {
self.min.x <= self.max.x && self.min.y <= self.max.y
}
}
impl<T: CoordFloat> Rect<T> {
pub fn center(self) -> Coord<T> {
let two = T::one() + T::one();
coord! {
x: (self.max.x + self.min.x) / two,
y: (self.max.y + self.min.y) / two,
}
}
}
static RECT_INVALID_BOUNDS_ERROR: &str = "Failed to create Rect: 'min' coordinate's x/y value must be smaller or equal to the 'max' x/y value";
#[cfg(any(feature = "approx", test))]
mod approx_integration {
use super::*;
use approx::{AbsDiffEq, RelativeEq, UlpsEq};
impl<T> RelativeEq for Rect<T>
where
T: CoordNum + RelativeEq<Epsilon = T>,
{
#[inline]
fn default_max_relative() -> Self::Epsilon {
T::default_max_relative()
}
#[inline]
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
if !self.min.relative_eq(&other.min, epsilon, max_relative) {
return false;
}
if !self.max.relative_eq(&other.max, epsilon, max_relative) {
return false;
}
true
}
}
impl<T> AbsDiffEq for Rect<T>
where
T: CoordNum + AbsDiffEq<Epsilon = T>,
{
type Epsilon = T;
#[inline]
fn default_epsilon() -> Self::Epsilon {
T::default_epsilon()
}
#[inline]
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
if !self.min.abs_diff_eq(&other.min, epsilon) {
return false;
}
if !self.max.abs_diff_eq(&other.max, epsilon) {
return false;
}
true
}
}
impl<T> UlpsEq for Rect<T>
where
T: CoordNum + UlpsEq<Epsilon = T>,
{
fn default_max_ulps() -> u32 {
T::default_max_ulps()
}
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
if !self.min.ulps_eq(&other.min, epsilon, max_ulps) {
return false;
}
if !self.max.ulps_eq(&other.max, epsilon, max_ulps) {
return false;
}
true
}
}
}
#[deprecated(
since = "0.6.2",
note = "Use `Rect::new` instead, since `Rect::try_new` will never Error"
)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct InvalidRectCoordinatesError;
#[cfg(feature = "std")]
#[allow(deprecated)]
impl std::error::Error for InvalidRectCoordinatesError {}
#[allow(deprecated)]
impl core::fmt::Display for InvalidRectCoordinatesError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{RECT_INVALID_BOUNDS_ERROR}")
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::coord;
#[test]
fn rect() {
let rect = Rect::new((10, 10), (20, 20));
assert_eq!(rect.min, coord! { x: 10, y: 10 });
assert_eq!(rect.max, coord! { x: 20, y: 20 });
let rect = Rect::new((20, 20), (10, 10));
assert_eq!(rect.min, coord! { x: 10, y: 10 });
assert_eq!(rect.max, coord! { x: 20, y: 20 });
let rect = Rect::new((10, 20), (20, 10));
assert_eq!(rect.min, coord! { x: 10, y: 10 });
assert_eq!(rect.max, coord! { x: 20, y: 20 });
}
#[test]
fn rect_width() {
let rect = Rect::new((10, 10), (20, 20));
assert_eq!(rect.width(), 10);
}
#[test]
fn rect_height() {
let rect = Rect::new((10., 10.), (20., 20.));
assert_relative_eq!(rect.height(), 10.);
}
#[test]
fn rect_center() {
assert_relative_eq!(
Rect::new((0., 10.), (10., 90.)).center(),
Coord::from((5., 50.))
);
assert_relative_eq!(
Rect::new((-42., -42.), (42., 42.)).center(),
Coord::from((0., 0.))
);
assert_relative_eq!(
Rect::new((0., 0.), (0., 0.)).center(),
Coord::from((0., 0.))
);
}
}