use crate::{IRect, URect, Vec2};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
#[repr(C)]
#[derive(Default, Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Rect {
pub min: Vec2,
pub max: Vec2,
}
impl Rect {
pub const EMPTY: Self = Self {
max: Vec2::NEG_INFINITY,
min: Vec2::INFINITY,
};
#[inline]
pub fn new(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
Self::from_corners(Vec2::new(x0, y0), Vec2::new(x1, y1))
}
#[inline]
pub fn from_corners(p0: Vec2, p1: Vec2) -> Self {
Self {
min: p0.min(p1),
max: p0.max(p1),
}
}
#[inline]
pub fn from_center_size(origin: Vec2, size: Vec2) -> Self {
assert!(size.cmpge(Vec2::ZERO).all(), "Rect size must be positive");
let half_size = size / 2.;
Self::from_center_half_size(origin, half_size)
}
#[inline]
pub fn from_center_half_size(origin: Vec2, half_size: Vec2) -> Self {
assert!(
half_size.cmpge(Vec2::ZERO).all(),
"Rect half_size must be positive"
);
Self {
min: origin - half_size,
max: origin + half_size,
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.min.cmpge(self.max).any()
}
#[inline]
pub fn width(&self) -> f32 {
self.max.x - self.min.x
}
#[inline]
pub fn height(&self) -> f32 {
self.max.y - self.min.y
}
#[inline]
pub fn size(&self) -> Vec2 {
self.max - self.min
}
#[inline]
pub fn half_size(&self) -> Vec2 {
self.size() * 0.5
}
#[inline]
pub fn center(&self) -> Vec2 {
(self.min + self.max) * 0.5
}
#[inline]
pub fn contains(&self, point: Vec2) -> bool {
(point.cmpge(self.min) & point.cmple(self.max)).all()
}
#[inline]
pub fn union(&self, other: Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
#[inline]
pub fn union_point(&self, other: Vec2) -> Self {
Self {
min: self.min.min(other),
max: self.max.max(other),
}
}
#[inline]
pub fn intersect(&self, other: Self) -> Self {
let mut r = Self {
min: self.min.max(other.min),
max: self.max.min(other.max),
};
r.min = r.min.min(r.max);
r
}
#[inline]
pub fn inflate(&self, expansion: f32) -> Self {
let mut r = Self {
min: self.min - expansion,
max: self.max + expansion,
};
r.min = r.min.min(r.max);
r
}
pub fn normalize(&self, other: Self) -> Self {
let outer_size = other.size();
Self {
min: (self.min - other.min) / outer_size,
max: (self.max - other.min) / outer_size,
}
}
#[inline]
pub fn area(&self) -> f32 {
self.width() * self.height()
}
#[inline]
pub fn as_irect(&self) -> IRect {
IRect::from_corners(self.min.as_ivec2(), self.max.as_ivec2())
}
#[inline]
pub fn as_urect(&self) -> URect {
URect::from_corners(self.min.as_uvec2(), self.max.as_uvec2())
}
}
#[cfg(test)]
mod tests {
use crate::ops;
use super::*;
#[test]
fn well_formed() {
let r = Rect::from_center_size(Vec2::new(3., -5.), Vec2::new(8., 11.));
assert!(r.min.abs_diff_eq(Vec2::new(-1., -10.5), 1e-5));
assert!(r.max.abs_diff_eq(Vec2::new(7., 0.5), 1e-5));
assert!(r.center().abs_diff_eq(Vec2::new(3., -5.), 1e-5));
assert!(ops::abs(r.width() - 8.) <= 1e-5);
assert!(ops::abs(r.height() - 11.) <= 1e-5);
assert!(r.size().abs_diff_eq(Vec2::new(8., 11.), 1e-5));
assert!(r.half_size().abs_diff_eq(Vec2::new(4., 5.5), 1e-5));
assert!(r.contains(Vec2::new(3., -5.)));
assert!(r.contains(Vec2::new(-1., -10.5)));
assert!(r.contains(Vec2::new(-1., 0.5)));
assert!(r.contains(Vec2::new(7., -10.5)));
assert!(r.contains(Vec2::new(7., 0.5)));
assert!(!r.contains(Vec2::new(50., -5.)));
}
#[test]
fn rect_union() {
let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE);
let r2 = Rect {
min: Vec2::new(-0.8, 0.3),
max: Vec2::new(0.1, 0.7),
};
let u = r.union(r2);
assert!(u.min.abs_diff_eq(Vec2::new(-0.8, -0.5), 1e-5));
assert!(u.max.abs_diff_eq(Vec2::new(0.5, 0.7), 1e-5));
let r2 = Rect {
min: Vec2::new(-1.8, -0.5),
max: Vec2::new(-1.5, 0.3),
};
let u = r.union(r2);
assert!(u.min.abs_diff_eq(Vec2::new(-1.8, -0.5), 1e-5));
assert!(u.max.abs_diff_eq(Vec2::new(0.5, 0.5), 1e-5));
let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(0.5));
let u = r.union(r2);
assert!(u.min.abs_diff_eq(r.min, 1e-5));
assert!(u.max.abs_diff_eq(r.max, 1e-5));
let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(1.5));
let u = r.union(r2);
assert!(u.min.abs_diff_eq(r2.min, 1e-5));
assert!(u.max.abs_diff_eq(r2.max, 1e-5));
}
#[test]
fn rect_union_pt() {
let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE);
let v = Vec2::new(0.3, -0.2);
let u = r.union_point(v);
assert!(u.min.abs_diff_eq(r.min, 1e-5));
assert!(u.max.abs_diff_eq(r.max, 1e-5));
let v = Vec2::new(10., -3.);
let u = r.union_point(v);
assert!(u.min.abs_diff_eq(Vec2::new(-0.5, -3.), 1e-5));
assert!(u.max.abs_diff_eq(Vec2::new(10., 0.5), 1e-5));
}
#[test]
fn rect_intersect() {
let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE);
let r2 = Rect {
min: Vec2::new(-0.8, 0.3),
max: Vec2::new(0.1, 0.7),
};
let u = r.intersect(r2);
assert!(u.min.abs_diff_eq(Vec2::new(-0.5, 0.3), 1e-5));
assert!(u.max.abs_diff_eq(Vec2::new(0.1, 0.5), 1e-5));
let r2 = Rect {
min: Vec2::new(-1.8, -0.5),
max: Vec2::new(-1.5, 0.3),
};
let u = r.intersect(r2);
assert!(u.is_empty());
assert!(u.width() <= 1e-5);
let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(0.5));
let u = r.intersect(r2);
assert!(u.min.abs_diff_eq(r2.min, 1e-5));
assert!(u.max.abs_diff_eq(r2.max, 1e-5));
let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(1.5));
let u = r.intersect(r2);
assert!(u.min.abs_diff_eq(r.min, 1e-5));
assert!(u.max.abs_diff_eq(r.max, 1e-5));
}
#[test]
fn rect_inflate() {
let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE);
let r2 = r.inflate(0.3);
assert!(r2.min.abs_diff_eq(Vec2::new(-0.8, -0.8), 1e-5));
assert!(r2.max.abs_diff_eq(Vec2::new(0.8, 0.8), 1e-5));
}
}