diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index 10915c3df7fa7..f42a0b946d235 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -9,10 +9,10 @@ pub mod cubic_splines; mod ray; -mod rect; +mod rects; pub use ray::Ray; -pub use rect::Rect; +pub use rects::*; /// The `bevy_math` prelude. pub mod prelude { diff --git a/crates/bevy_math/src/rects/irect.rs b/crates/bevy_math/src/rects/irect.rs new file mode 100644 index 0000000000000..05498cea1554e --- /dev/null +++ b/crates/bevy_math/src/rects/irect.rs @@ -0,0 +1,458 @@ +use crate::{IVec2, Rect, URect}; + +/// A rectangle defined by two opposite corners. +/// +/// The rectangle is axis aligned, and defined by its minimum and maximum coordinates, +/// stored in `IRect::min` and `IRect::max`, respectively. The minimum/maximum invariant +/// must be upheld by the user when directly assigning the fields, otherwise some methods +/// produce invalid results. It is generally recommended to use one of the constructor +/// methods instead, which will ensure this invariant is met, unless you already have +/// the minimum and maximum corners. +#[repr(C)] +#[derive(Default, Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +pub struct IRect { + /// The minimum corner point of the rect. + pub min: IVec2, + /// The maximum corner point of the rect. + pub max: IVec2, +} + +impl IRect { + /// Create a new rectangle from two corner points. + /// + /// The two points do not need to be the minimum and/or maximum corners. + /// They only need to be two opposite corners. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::IRect; + /// let r = IRect::new(0, 4, 10, 6); // w=10 h=2 + /// let r = IRect::new(2, 3, 5, -1); // w=3 h=4 + /// ``` + #[inline] + pub fn new(x0: i32, y0: i32, x1: i32, y1: i32) -> Self { + Self::from_corners(IVec2::new(x0, y0), IVec2::new(x1, y1)) + } + + /// Create a new rectangle from two corner points. + /// + /// The two points do not need to be the minimum and/or maximum corners. + /// They only need to be two opposite corners. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{IRect, IVec2}; + /// // Unit rect from [0,0] to [1,1] + /// let r = IRect::from_corners(IVec2::ZERO, IVec2::ONE); // w=1 h=1 + /// // Same; the points do not need to be ordered + /// let r = IRect::from_corners(IVec2::ONE, IVec2::ZERO); // w=1 h=1 + /// ``` + #[inline] + pub fn from_corners(p0: IVec2, p1: IVec2) -> Self { + Self { + min: p0.min(p1), + max: p0.max(p1), + } + } + + /// Create a new rectangle from its center and size. + /// + /// # Rounding Behaviour + /// + /// If the size contains odd numbers they will be rounded down to the nearest whole number. + /// + /// # Panics + /// + /// This method panics if any of the components of the size is negative. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{IRect, IVec2}; + /// let r = IRect::from_center_size(IVec2::ZERO, IVec2::new(3, 2)); // w=2 h=2 + /// assert_eq!(r.min, IVec2::splat(-1)); + /// assert_eq!(r.max, IVec2::splat(1)); + /// ``` + #[inline] + pub fn from_center_size(origin: IVec2, size: IVec2) -> Self { + debug_assert!(size.cmpge(IVec2::ZERO).all(), "IRect size must be positive"); + let half_size = size / 2; + Self::from_center_half_size(origin, half_size) + } + + /// Create a new rectangle from its center and half-size. + /// + /// # Panics + /// + /// This method panics if any of the components of the half-size is negative. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{IRect, IVec2}; + /// let r = IRect::from_center_half_size(IVec2::ZERO, IVec2::ONE); // w=2 h=2 + /// assert_eq!(r.min, IVec2::splat(-1)); + /// assert_eq!(r.max, IVec2::splat(1)); + /// ``` + #[inline] + pub fn from_center_half_size(origin: IVec2, half_size: IVec2) -> Self { + assert!( + half_size.cmpge(IVec2::ZERO).all(), + "IRect half_size must be positive" + ); + Self { + min: origin - half_size, + max: origin + half_size, + } + } + + /// Check if the rectangle is empty. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{IRect, IVec2}; + /// let r = IRect::from_corners(IVec2::ZERO, IVec2::new(0, 1)); // w=0 h=1 + /// assert!(r.is_empty()); + /// ``` + #[inline] + pub fn is_empty(&self) -> bool { + self.min.cmpge(self.max).any() + } + + /// Rectangle width (max.x - min.x). + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::IRect; + /// let r = IRect::new(0, 0, 5, 1); // w=5 h=1 + /// assert_eq!(r.width(), 5); + /// ``` + #[inline] + pub fn width(&self) -> i32 { + self.max.x - self.min.x + } + + /// Rectangle height (max.y - min.y). + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::IRect; + /// let r = IRect::new(0, 0, 5, 1); // w=5 h=1 + /// assert_eq!(r.height(), 1); + /// ``` + #[inline] + pub fn height(&self) -> i32 { + self.max.y - self.min.y + } + + /// Rectangle size. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{IRect, IVec2}; + /// let r = IRect::new(0, 0, 5, 1); // w=5 h=1 + /// assert_eq!(r.size(), IVec2::new(5, 1)); + /// ``` + #[inline] + pub fn size(&self) -> IVec2 { + self.max - self.min + } + + /// Rectangle half-size. + /// + /// # Rounding Behaviour + /// + /// If the full size contains odd numbers they will be rounded down to the nearest whole number when calculating the half size. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{IRect, IVec2}; + /// let r = IRect::new(0, 0, 4, 3); // w=4 h=3 + /// assert_eq!(r.half_size(), IVec2::new(2, 1)); + /// ``` + #[inline] + pub fn half_size(&self) -> IVec2 { + self.size() / 2 + } + + /// The center point of the rectangle. + /// + /// # Rounding Behaviour + /// + /// If the (min + max) contains odd numbers they will be rounded down to the nearest whole number when calculating the center. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{IRect, IVec2}; + /// let r = IRect::new(0, 0, 5, 2); // w=5 h=2 + /// assert_eq!(r.center(), IVec2::new(2, 1)); + /// ``` + #[inline] + pub fn center(&self) -> IVec2 { + (self.min + self.max) / 2 + } + + /// Check if a point lies within this rectangle, inclusive of its edges. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::IRect; + /// let r = IRect::new(0, 0, 5, 1); // w=5 h=1 + /// assert!(r.contains(r.center())); + /// assert!(r.contains(r.min)); + /// assert!(r.contains(r.max)); + /// ``` + #[inline] + pub fn contains(&self, point: IVec2) -> bool { + (point.cmpge(self.min) & point.cmple(self.max)).all() + } + + /// Build a new rectangle formed of the union of this rectangle and another rectangle. + /// + /// The union is the smallest rectangle enclosing both rectangles. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{IRect, IVec2}; + /// let r1 = IRect::new(0, 0, 5, 1); // w=5 h=1 + /// let r2 = IRect::new(1, -1, 3, 3); // w=2 h=4 + /// let r = r1.union(r2); + /// assert_eq!(r.min, IVec2::new(0, -1)); + /// assert_eq!(r.max, IVec2::new(5, 3)); + /// ``` + #[inline] + pub fn union(&self, other: Self) -> Self { + Self { + min: self.min.min(other.min), + max: self.max.max(other.max), + } + } + + /// Build a new rectangle formed of the union of this rectangle and a point. + /// + /// The union is the smallest rectangle enclosing both the rectangle and the point. If the + /// point is already inside the rectangle, this method returns a copy of the rectangle. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{IRect, IVec2}; + /// let r = IRect::new(0, 0, 5, 1); // w=5 h=1 + /// let u = r.union_point(IVec2::new(3, 6)); + /// assert_eq!(u.min, IVec2::ZERO); + /// assert_eq!(u.max, IVec2::new(5, 6)); + /// ``` + #[inline] + pub fn union_point(&self, other: IVec2) -> Self { + Self { + min: self.min.min(other), + max: self.max.max(other), + } + } + + /// Build a new rectangle formed of the intersection of this rectangle and another rectangle. + /// + /// The intersection is the largest rectangle enclosed in both rectangles. If the intersection + /// is empty, this method returns an empty rectangle ([`IRect::is_empty()`] returns `true`), but + /// the actual values of [`IRect::min`] and [`IRect::max`] are implementation-dependent. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{IRect, IVec2}; + /// let r1 = IRect::new(0, 0, 5, 1); // w=5 h=1 + /// let r2 = IRect::new(1, -1, 3, 3); // w=2 h=4 + /// let r = r1.intersect(r2); + /// assert_eq!(r.min, IVec2::new(1, 0)); + /// assert_eq!(r.max, IVec2::new(3, 1)); + /// ``` + #[inline] + pub fn intersect(&self, other: Self) -> Self { + let mut r = Self { + min: self.min.max(other.min), + max: self.max.min(other.max), + }; + // Collapse min over max to enforce invariants and ensure e.g. width() or + // height() never return a negative value. + r.min = r.min.min(r.max); + r + } + + /// Create a new rectangle with a constant inset. + /// + /// The inset is the extra border on all sides. A positive inset produces a larger rectangle, + /// while a negative inset is allowed and produces a smaller rectangle. If the inset is negative + /// and its absolute value is larger than the rectangle half-size, the created rectangle is empty. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{IRect, IVec2}; + /// let r = IRect::new(0, 0, 5, 1); // w=5 h=1 + /// let r2 = r.inset(3); // w=11 h=7 + /// assert_eq!(r2.min, IVec2::splat(-3)); + /// assert_eq!(r2.max, IVec2::new(8, 4)); + /// + /// let r = IRect::new(0, -1, 4, 3); // w=4 h=4 + /// let r2 = r.inset(-1); // w=2 h=2 + /// assert_eq!(r2.min, IVec2::new(1, 0)); + /// assert_eq!(r2.max, IVec2::new(3, 2)); + /// ``` + #[inline] + pub fn inset(&self, inset: i32) -> Self { + let mut r = Self { + min: self.min - inset, + max: self.max + inset, + }; + // Collapse min over max to enforce invariants and ensure e.g. width() or + // height() never return a negative value. + r.min = r.min.min(r.max); + r + } + + /// Returns self as [`Rect`] (f32) + #[inline] + pub fn as_rect(&self) -> Rect { + Rect::from_corners(self.min.as_vec2(), self.max.as_vec2()) + } + + /// Returns self as [`URect`] (u32) + #[inline] + pub fn as_urect(&self) -> URect { + URect::from_corners(self.min.as_uvec2(), self.max.as_uvec2()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn well_formed() { + let r = IRect::from_center_size(IVec2::new(3, -5), IVec2::new(8, 12)); + + assert_eq!(r.min, IVec2::new(-1, -11)); + assert_eq!(r.max, IVec2::new(7, 1)); + + assert_eq!(r.center(), IVec2::new(3, -5)); + + assert_eq!(r.width().abs(), 8); + assert_eq!(r.height().abs(), 12); + assert_eq!(r.size(), IVec2::new(8, 12)); + assert_eq!(r.half_size(), IVec2::new(4, 6)); + + assert!(r.contains(IVec2::new(3, -5))); + assert!(r.contains(IVec2::new(-1, -10))); + assert!(r.contains(IVec2::new(-1, 0))); + assert!(r.contains(IVec2::new(7, -10))); + assert!(r.contains(IVec2::new(7, 0))); + assert!(!r.contains(IVec2::new(50, -5))); + } + + #[test] + fn rect_union() { + let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(4)); // [-2, -2] - [2, 2] + + // overlapping + let r2 = IRect { + min: IVec2::new(1, 1), + max: IVec2::new(3, 3), + }; + let u = r.union(r2); + assert_eq!(u.min, IVec2::new(-2, -2)); + assert_eq!(u.max, IVec2::new(3, 3)); + + // disjoint + let r2 = IRect { + min: IVec2::new(1, 4), + max: IVec2::new(4, 6), + }; + let u = r.union(r2); + assert_eq!(u.min, IVec2::new(-2, -2)); + assert_eq!(u.max, IVec2::new(4, 6)); + + // included + let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(2)); + let u = r.union(r2); + assert_eq!(u.min, r.min); + assert_eq!(u.max, r.max); + + // including + let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(6)); + let u = r.union(r2); + assert_eq!(u.min, r2.min); + assert_eq!(u.min, r2.min); + } + + #[test] + fn rect_union_pt() { + let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(4)); // [-2,-2] - [2,2] + + // inside + let v = IVec2::new(1, -1); + let u = r.union_point(v); + assert_eq!(u.min, r.min); + assert_eq!(u.max, r.max); + + // outside + let v = IVec2::new(10, -3); + let u = r.union_point(v); + assert_eq!(u.min, IVec2::new(-2, -3)); + assert_eq!(u.max, IVec2::new(10, 2)); + } + + #[test] + fn rect_intersect() { + let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(8)); // [-4,-4] - [4,4] + + // overlapping + let r2 = IRect { + min: IVec2::new(2, 2), + max: IVec2::new(6, 6), + }; + let u = r.intersect(r2); + assert_eq!(u.min, IVec2::new(2, 2)); + assert_eq!(u.max, IVec2::new(4, 4)); + + // disjoint + let r2 = IRect { + min: IVec2::new(-8, -2), + max: IVec2::new(-6, 2), + }; + let u = r.intersect(r2); + assert!(u.is_empty()); + assert_eq!(u.width(), 0); + + // included + let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(2)); + let u = r.intersect(r2); + assert_eq!(u.min, r2.min); + assert_eq!(u.max, r2.max); + + // including + let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(10)); + let u = r.intersect(r2); + assert_eq!(u.min, r.min); + assert_eq!(u.max, r.max); + } + + #[test] + fn rect_inset() { + let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(4)); // [-2,-2] - [2,2] + + let r2 = r.inset(2); + assert_eq!(r2.min, IVec2::new(-4, -4)); + assert_eq!(r2.max, IVec2::new(4, 4)); + } +} diff --git a/crates/bevy_math/src/rects/mod.rs b/crates/bevy_math/src/rects/mod.rs new file mode 100644 index 0000000000000..10b922576152a --- /dev/null +++ b/crates/bevy_math/src/rects/mod.rs @@ -0,0 +1,7 @@ +mod irect; +mod rect; +mod urect; + +pub use irect::IRect; +pub use rect::Rect; +pub use urect::URect; diff --git a/crates/bevy_math/src/rect.rs b/crates/bevy_math/src/rects/rect.rs similarity index 92% rename from crates/bevy_math/src/rect.rs rename to crates/bevy_math/src/rects/rect.rs index 080b0cd69d376..e06440f08cf49 100644 --- a/crates/bevy_math/src/rect.rs +++ b/crates/bevy_math/src/rects/rect.rs @@ -1,4 +1,4 @@ -use crate::Vec2; +use crate::{IRect, URect, Vec2}; /// A rectangle defined by two opposite corners. /// @@ -52,7 +52,7 @@ impl Rect { /// ``` #[inline] pub fn from_corners(p0: Vec2, p1: Vec2) -> Self { - Rect { + Self { min: p0.min(p1), max: p0.max(p1), } @@ -74,7 +74,7 @@ impl Rect { /// ``` #[inline] pub fn from_center_size(origin: Vec2, size: Vec2) -> Self { - assert!(size.cmpge(Vec2::ZERO).all()); + assert!(size.cmpge(Vec2::ZERO).all(), "Rect size must be positive"); let half_size = size / 2.; Self::from_center_half_size(origin, half_size) } @@ -95,7 +95,10 @@ impl Rect { /// ``` #[inline] pub fn from_center_half_size(origin: Vec2, half_size: Vec2) -> Self { - assert!(half_size.cmpge(Vec2::ZERO).all()); + assert!( + half_size.cmpge(Vec2::ZERO).all(), + "Rect half_size must be positive" + ); Self { min: origin - half_size, max: origin + half_size, @@ -217,8 +220,8 @@ impl Rect { /// assert!(r.max.abs_diff_eq(Vec2::new(5., 3.), 1e-5)); /// ``` #[inline] - pub fn union(&self, other: Rect) -> Rect { - Rect { + pub fn union(&self, other: Self) -> Self { + Self { min: self.min.min(other.min), max: self.max.max(other.max), } @@ -239,8 +242,8 @@ impl Rect { /// assert!(u.max.abs_diff_eq(Vec2::new(5., 6.), 1e-5)); /// ``` #[inline] - pub fn union_point(&self, other: Vec2) -> Rect { - Rect { + pub fn union_point(&self, other: Vec2) -> Self { + Self { min: self.min.min(other), max: self.max.max(other), } @@ -263,8 +266,8 @@ impl Rect { /// assert!(r.max.abs_diff_eq(Vec2::new(3., 1.), 1e-5)); /// ``` #[inline] - pub fn intersect(&self, other: Rect) -> Rect { - let mut r = Rect { + pub fn intersect(&self, other: Self) -> Self { + let mut r = Self { min: self.min.max(other.min), max: self.max.min(other.max), }; @@ -288,10 +291,15 @@ impl Rect { /// let r2 = r.inset(3.); // w=11 h=7 /// assert!(r2.min.abs_diff_eq(Vec2::splat(-3.), 1e-5)); /// assert!(r2.max.abs_diff_eq(Vec2::new(8., 4.), 1e-5)); + /// + /// let r = Rect::new(0., -1., 6., 7.); // w=6 h=8 + /// let r2 = r.inset(-2.); // w=11 h=7 + /// assert!(r2.min.abs_diff_eq(Vec2::new(2., 1.), 1e-5)); + /// assert!(r2.max.abs_diff_eq(Vec2::new(4., 5.), 1e-5)); /// ``` #[inline] - pub fn inset(&self, inset: f32) -> Rect { - let mut r = Rect { + pub fn inset(&self, inset: f32) -> Self { + let mut r = Self { min: self.min - inset, max: self.max + inset, }; @@ -300,6 +308,18 @@ impl Rect { r.min = r.min.min(r.max); r } + + /// Returns self as [`IRect`] (i32) + #[inline] + pub fn as_urect(&self) -> IRect { + IRect::from_corners(self.min.as_ivec2(), self.max.as_ivec2()) + } + + /// Returns self as [`URect`] (u32) + #[inline] + pub fn as_rect(&self) -> URect { + URect::from_corners(self.min.as_uvec2(), self.max.as_uvec2()) + } } #[cfg(test)] diff --git a/crates/bevy_math/src/rects/urect.rs b/crates/bevy_math/src/rects/urect.rs new file mode 100644 index 0000000000000..e68b5f161502f --- /dev/null +++ b/crates/bevy_math/src/rects/urect.rs @@ -0,0 +1,461 @@ +use crate::{IRect, Rect, UVec2}; + +/// A rectangle defined by two opposite corners. +/// +/// The rectangle is axis aligned, and defined by its minimum and maximum coordinates, +/// stored in `URect::min` and `URect::max`, respectively. The minimum/maximum invariant +/// must be upheld by the user when directly assigning the fields, otherwise some methods +/// produce invalid results. It is generally recommended to use one of the constructor +/// methods instead, which will ensure this invariant is met, unless you already have +/// the minimum and maximum corners. +#[repr(C)] +#[derive(Default, Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +pub struct URect { + /// The minimum corner point of the rect. + pub min: UVec2, + /// The maximum corner point of the rect. + pub max: UVec2, +} + +impl URect { + /// Create a new rectangle from two corner points. + /// + /// The two points do not need to be the minimum and/or maximum corners. + /// They only need to be two opposite corners. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::URect; + /// let r = URect::new(0, 4, 10, 6); // w=10 h=2 + /// let r = URect::new(2, 4, 5, 0); // w=3 h=4 + /// ``` + #[inline] + pub fn new(x0: u32, y0: u32, x1: u32, y1: u32) -> Self { + Self::from_corners(UVec2::new(x0, y0), UVec2::new(x1, y1)) + } + + /// Create a new rectangle from two corner points. + /// + /// The two points do not need to be the minimum and/or maximum corners. + /// They only need to be two opposite corners. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{URect, UVec2}; + /// // Unit rect from [0,0] to [1,1] + /// let r = URect::from_corners(UVec2::ZERO, UVec2::ONE); // w=1 h=1 + /// // Same; the points do not need to be ordered + /// let r = URect::from_corners(UVec2::ONE, UVec2::ZERO); // w=1 h=1 + /// ``` + #[inline] + pub fn from_corners(p0: UVec2, p1: UVec2) -> Self { + Self { + min: p0.min(p1), + max: p0.max(p1), + } + } + + /// Create a new rectangle from its center and size. + /// + /// # Rounding Behaviour + /// + /// If the size contains odd numbers they will be rounded down to the nearest whole number. + /// + /// # Panics + /// + /// This method panics if any of the components of the size is negative or if `origin - (size / 2)` results in any negatives. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{URect, UVec2}; + /// let r = URect::from_center_size(UVec2::ONE, UVec2::splat(2)); // w=2 h=2 + /// assert_eq!(r.min, UVec2::splat(0)); + /// assert_eq!(r.max, UVec2::splat(2)); + /// ``` + #[inline] + pub fn from_center_size(origin: UVec2, size: UVec2) -> Self { + assert!(origin.cmpge(size / 2).all(), "Origin must always be greater than or equal to (size / 2) otherwise the rectangle is undefined! Origin was {origin} and size was {size}"); + let half_size = size / 2; + Self::from_center_half_size(origin, half_size) + } + + /// Create a new rectangle from its center and half-size. + /// + /// # Panics + /// + /// This method panics if any of the components of the half-size is negative or if `origin - half_size` results in any negatives. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{URect, UVec2}; + /// let r = URect::from_center_half_size(UVec2::ONE, UVec2::ONE); // w=2 h=2 + /// assert_eq!(r.min, UVec2::splat(0)); + /// assert_eq!(r.max, UVec2::splat(2)); + /// ``` + #[inline] + pub fn from_center_half_size(origin: UVec2, half_size: UVec2) -> Self { + assert!(origin.cmpge(half_size).all(), "Origin must always be greater than or equal to half_size otherwise the rectangle is undefined! Origin was {origin} and half_size was {half_size}"); + Self { + min: origin - half_size, + max: origin + half_size, + } + } + + /// Check if the rectangle is empty. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{URect, UVec2}; + /// let r = URect::from_corners(UVec2::ZERO, UVec2::new(0, 1)); // w=0 h=1 + /// assert!(r.is_empty()); + /// ``` + #[inline] + pub fn is_empty(&self) -> bool { + self.min.cmpge(self.max).any() + } + + /// Rectangle width (max.x - min.x). + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::URect; + /// let r = URect::new(0, 0, 5, 1); // w=5 h=1 + /// assert_eq!(r.width(), 5); + /// ``` + #[inline] + pub fn width(&self) -> u32 { + self.max.x - self.min.x + } + + /// Rectangle height (max.y - min.y). + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::URect; + /// let r = URect::new(0, 0, 5, 1); // w=5 h=1 + /// assert_eq!(r.height(), 1); + /// ``` + #[inline] + pub fn height(&self) -> u32 { + self.max.y - self.min.y + } + + /// Rectangle size. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{URect, UVec2}; + /// let r = URect::new(0, 0, 5, 1); // w=5 h=1 + /// assert_eq!(r.size(), UVec2::new(5, 1)); + /// ``` + #[inline] + pub fn size(&self) -> UVec2 { + self.max - self.min + } + + /// Rectangle half-size. + /// + /// # Rounding Behaviour + /// + /// If the full size contains odd numbers they will be rounded down to the nearest whole number when calculating the half size. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{URect, UVec2}; + /// let r = URect::new(0, 0, 4, 2); // w=4 h=2 + /// assert_eq!(r.half_size(), UVec2::new(2, 1)); + /// ``` + #[inline] + pub fn half_size(&self) -> UVec2 { + self.size() / 2 + } + + /// The center point of the rectangle. + /// + /// # Rounding Behaviour + /// + /// If the (min + max) contains odd numbers they will be rounded down to the nearest whole number when calculating the center. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{URect, UVec2}; + /// let r = URect::new(0, 0, 4, 2); // w=4 h=2 + /// assert_eq!(r.center(), UVec2::new(2, 1)); + /// ``` + #[inline] + pub fn center(&self) -> UVec2 { + (self.min + self.max) / 2 + } + + /// Check if a point lies within this rectangle, inclusive of its edges. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::URect; + /// let r = URect::new(0, 0, 5, 1); // w=5 h=1 + /// assert!(r.contains(r.center())); + /// assert!(r.contains(r.min)); + /// assert!(r.contains(r.max)); + /// ``` + #[inline] + pub fn contains(&self, point: UVec2) -> bool { + (point.cmpge(self.min) & point.cmple(self.max)).all() + } + + /// Build a new rectangle formed of the union of this rectangle and another rectangle. + /// + /// The union is the smallest rectangle enclosing both rectangles. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{URect, UVec2}; + /// let r1 = URect::new(0, 0, 5, 1); // w=5 h=1 + /// let r2 = URect::new(1, 0, 3, 8); // w=2 h=4 + /// let r = r1.union(r2); + /// assert_eq!(r.min, UVec2::new(0, 0)); + /// assert_eq!(r.max, UVec2::new(5, 8)); + /// ``` + #[inline] + pub fn union(&self, other: Self) -> Self { + Self { + min: self.min.min(other.min), + max: self.max.max(other.max), + } + } + + /// Build a new rectangle formed of the union of this rectangle and a point. + /// + /// The union is the smallest rectangle enclosing both the rectangle and the point. If the + /// point is already inside the rectangle, this method returns a copy of the rectangle. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{URect, UVec2}; + /// let r = URect::new(0, 0, 5, 1); // w=5 h=1 + /// let u = r.union_point(UVec2::new(3, 6)); + /// assert_eq!(u.min, UVec2::ZERO); + /// assert_eq!(u.max, UVec2::new(5, 6)); + /// ``` + #[inline] + pub fn union_point(&self, other: UVec2) -> Self { + Self { + min: self.min.min(other), + max: self.max.max(other), + } + } + + /// Build a new rectangle formed of the intersection of this rectangle and another rectangle. + /// + /// The intersection is the largest rectangle enclosed in both rectangles. If the intersection + /// is empty, this method returns an empty rectangle ([`URect::is_empty()`] returns `true`), but + /// the actual values of [`URect::min`] and [`URect::max`] are implementation-dependent. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{URect, UVec2}; + /// let r1 = URect::new(0, 0, 2, 2); // w=2 h=2 + /// let r2 = URect::new(1, 1, 3, 3); // w=2 h=2 + /// let r = r1.intersect(r2); + /// assert_eq!(r.min, UVec2::new(1, 1)); + /// assert_eq!(r.max, UVec2::new(2, 2)); + /// ``` + #[inline] + pub fn intersect(&self, other: Self) -> Self { + let mut r = Self { + min: self.min.max(other.min), + max: self.max.min(other.max), + }; + // Collapse min over max to enforce invariants and ensure e.g. width() or + // height() never return a negative value. + r.min = r.min.min(r.max); + r + } + + /// Create a new rectangle with a constant inset. + /// + /// The inset is the extra border on all sides. A positive inset produces a larger rectangle, + /// while a negative inset is allowed and produces a smaller rectangle. If the inset is negative + /// and its absolute value is larger than the rectangle half-size, the created rectangle is empty. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_math::{URect, UVec2}; + /// let r = URect::new(4, 4, 6, 6); // w=2 h=2 + /// let r2 = r.inset(1); // w=4 h=4 + /// assert_eq!(r2.min, UVec2::splat(3)); + /// assert_eq!(r2.max, UVec2::splat(7)); + /// + /// let r = URect::new(4, 4, 8, 8); // w=4 h=4 + /// let r2 = r.inset(-1); // w=2 h=2 + /// assert_eq!(r2.min, UVec2::splat(5)); + /// assert_eq!(r2.max, UVec2::splat(7)); + /// ``` + #[inline] + pub fn inset(&self, inset: i32) -> Self { + let mut r = Self { + min: UVec2::new( + self.min.x.saturating_add_signed(-inset), + self.min.y.saturating_add_signed(-inset), + ), + max: UVec2::new( + self.max.x.saturating_add_signed(inset), + self.max.y.saturating_add_signed(inset), + ), + }; + // Collapse min over max to enforce invariants and ensure e.g. width() or + // height() never return a negative value. + r.min = r.min.min(r.max); + r + } + + /// Returns self as [`Rect`] (f32) + #[inline] + pub fn as_rect(&self) -> Rect { + Rect::from_corners(self.min.as_vec2(), self.max.as_vec2()) + } + + /// Returns self as [`IRect`] (i32) + #[inline] + pub fn as_urect(&self) -> IRect { + IRect::from_corners(self.min.as_ivec2(), self.max.as_ivec2()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn well_formed() { + let r = URect::from_center_size(UVec2::new(10, 16), UVec2::new(8, 12)); + + assert_eq!(r.min, UVec2::new(6, 10)); + assert_eq!(r.max, UVec2::new(14, 22)); + + assert_eq!(r.center(), UVec2::new(10, 16)); + + assert_eq!(r.width(), 8); + assert_eq!(r.height(), 12); + assert_eq!(r.size(), UVec2::new(8, 12)); + assert_eq!(r.half_size(), UVec2::new(4, 6)); + + assert!(r.contains(UVec2::new(7, 10))); + assert!(r.contains(UVec2::new(14, 10))); + assert!(r.contains(UVec2::new(10, 22))); + assert!(r.contains(UVec2::new(6, 22))); + assert!(r.contains(UVec2::new(14, 22))); + assert!(!r.contains(UVec2::new(50, 5))); + } + + #[test] + fn rect_union() { + let r = URect::from_center_size(UVec2::splat(4), UVec2::splat(4)); // [2, 2] - [6, 6] + + // overlapping + let r2 = URect { + min: UVec2::new(0, 0), + max: UVec2::new(3, 3), + }; + let u = r.union(r2); + assert_eq!(u.min, UVec2::new(0, 0)); + assert_eq!(u.max, UVec2::new(6, 6)); + + // disjoint + let r2 = URect { + min: UVec2::new(4, 7), + max: UVec2::new(8, 8), + }; + let u = r.union(r2); + assert_eq!(u.min, UVec2::new(2, 2)); + assert_eq!(u.max, UVec2::new(8, 8)); + + // included + let r2 = URect::from_center_size(UVec2::splat(4), UVec2::splat(2)); + let u = r.union(r2); + assert_eq!(u.min, r.min); + assert_eq!(u.max, r.max); + + // including + let r2 = URect::from_center_size(UVec2::splat(4), UVec2::splat(6)); + let u = r.union(r2); + assert_eq!(u.min, r2.min); + assert_eq!(u.min, r2.min); + } + + #[test] + fn rect_union_pt() { + let r = URect::from_center_size(UVec2::splat(4), UVec2::splat(4)); // [2, 2] - [6, 6] + + // inside + let v = UVec2::new(2, 5); + let u = r.union_point(v); + assert_eq!(u.min, r.min); + assert_eq!(u.max, r.max); + + // outside + let v = UVec2::new(10, 5); + let u = r.union_point(v); + assert_eq!(u.min, UVec2::new(2, 2)); + assert_eq!(u.max, UVec2::new(10, 6)); + } + + #[test] + fn rect_intersect() { + let r = URect::from_center_size(UVec2::splat(6), UVec2::splat(8)); // [2, 2] - [10, 10] + + // overlapping + let r2 = URect { + min: UVec2::new(8, 8), + max: UVec2::new(12, 12), + }; + let u = r.intersect(r2); + assert_eq!(u.min, UVec2::new(8, 8)); + assert_eq!(u.max, UVec2::new(10, 10)); + + // disjoint + let r2 = URect { + min: UVec2::new(12, 12), + max: UVec2::new(14, 18), + }; + let u = r.intersect(r2); + assert!(u.is_empty()); + assert_eq!(u.width(), 0); + + // included + let r2 = URect::from_center_size(UVec2::splat(6), UVec2::splat(2)); + let u = r.intersect(r2); + assert_eq!(u.min, r2.min); + assert_eq!(u.max, r2.max); + + // including + let r2 = URect::from_center_size(UVec2::splat(6), UVec2::splat(10)); + let u = r.intersect(r2); + assert_eq!(u.min, r.min); + assert_eq!(u.max, r.max); + } + + #[test] + fn rect_inset() { + let r = URect::from_center_size(UVec2::splat(6), UVec2::splat(6)); // [3, 3] - [9, 9] + + let r2 = r.inset(2); + assert_eq!(r2.min, UVec2::new(1, 1)); + assert_eq!(r2.max, UVec2::new(11, 11)); + } +}