
# Objective Now that #13432 has been merged, it's important we update our reflected types to properly opt into this feature. If we do not, then this could cause issues for users downstream who want to make use of reflection-based cloning. ## Solution This PR is broken into 4 commits: 1. Add `#[reflect(Clone)]` on all types marked `#[reflect(opaque)]` that are also `Clone`. This is mandatory as these types would otherwise cause the cloning operation to fail for any type that contains it at any depth. 2. Update the reflection example to suggest adding `#[reflect(Clone)]` on opaque types. 3. Add `#[reflect(clone)]` attributes on all fields marked `#[reflect(ignore)]` that are also `Clone`. This prevents the ignored field from causing the cloning operation to fail. Note that some of the types that contain these fields are also `Clone`, and thus can be marked `#[reflect(Clone)]`. This makes the `#[reflect(clone)]` attribute redundant. However, I think it's safer to keep it marked in the case that the `Clone` impl/derive is ever removed. I'm open to removing them, though, if people disagree. 4. Finally, I added `#[reflect(Clone)]` on all types that are also `Clone`. While not strictly necessary, it enables us to reduce the generated output since we can just call `Clone::clone` directly instead of calling `PartialReflect::reflect_clone` on each variant/field. It also means we benefit from any optimizations or customizations made in the `Clone` impl, including directly dereferencing `Copy` values and increasing reference counters. Along with that change I also took the liberty of adding any missing registrations that I saw could be applied to the type as well, such as `Default`, `PartialEq`, and `Hash`. There were hundreds of these to edit, though, so it's possible I missed quite a few. That last commit is **_massive_**. There were nearly 700 types to update. So it's recommended to review the first three before moving onto that last one. Additionally, I can break the last commit off into its own PR or into smaller PRs, but I figured this would be the easiest way of doing it (and in a timely manner since I unfortunately don't have as much time as I used to for code contributions). ## Testing You can test locally with a `cargo check`: ``` cargo check --workspace --all-features ```
496 lines
16 KiB
Rust
496 lines
16 KiB
Rust
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};
|
|
|
|
/// A rectangle defined by two opposite corners.
|
|
///
|
|
/// The rectangle is axis aligned, and defined by its minimum and maximum coordinates,
|
|
/// stored in `Rect::min` and `Rect::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))]
|
|
#[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 {
|
|
/// The minimum corner point of the rect.
|
|
pub min: Vec2,
|
|
/// The maximum corner point of the rect.
|
|
pub max: Vec2,
|
|
}
|
|
|
|
impl Rect {
|
|
/// An empty `Rect`, represented by maximum and minimum corner points
|
|
/// at `Vec2::NEG_INFINITY` and `Vec2::INFINITY`, respectively.
|
|
/// This is so the `Rect` has a infinitely negative size.
|
|
/// This is useful, because when taking a union B of a non-empty `Rect` A and
|
|
/// this empty `Rect`, B will simply equal A.
|
|
pub const EMPTY: Self = Self {
|
|
max: Vec2::NEG_INFINITY,
|
|
min: Vec2::INFINITY,
|
|
};
|
|
/// 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
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::Rect;
|
|
/// let r = Rect::new(0., 4., 10., 6.); // w=10 h=2
|
|
/// let r = Rect::new(2., 3., 5., -1.); // w=3 h=4
|
|
/// ```
|
|
#[inline]
|
|
pub fn new(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
|
|
Self::from_corners(Vec2::new(x0, y0), Vec2::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
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::{Rect, Vec2};
|
|
/// // Unit rect from [0,0] to [1,1]
|
|
/// let r = Rect::from_corners(Vec2::ZERO, Vec2::ONE); // w=1 h=1
|
|
/// // Same; the points do not need to be ordered
|
|
/// let r = Rect::from_corners(Vec2::ONE, Vec2::ZERO); // w=1 h=1
|
|
/// ```
|
|
#[inline]
|
|
pub fn from_corners(p0: Vec2, p1: Vec2) -> Self {
|
|
Self {
|
|
min: p0.min(p1),
|
|
max: p0.max(p1),
|
|
}
|
|
}
|
|
|
|
/// Create a new rectangle from its center and size.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// This method panics if any of the components of the size is negative.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::{Rect, Vec2};
|
|
/// let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // w=1 h=1
|
|
/// assert!(r.min.abs_diff_eq(Vec2::splat(-0.5), 1e-5));
|
|
/// assert!(r.max.abs_diff_eq(Vec2::splat(0.5), 1e-5));
|
|
/// ```
|
|
#[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)
|
|
}
|
|
|
|
/// 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
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::{Rect, Vec2};
|
|
/// let r = Rect::from_center_half_size(Vec2::ZERO, Vec2::ONE); // w=2 h=2
|
|
/// assert!(r.min.abs_diff_eq(Vec2::splat(-1.), 1e-5));
|
|
/// assert!(r.max.abs_diff_eq(Vec2::splat(1.), 1e-5));
|
|
/// ```
|
|
#[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,
|
|
}
|
|
}
|
|
|
|
/// Check if the rectangle is empty.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::{Rect, Vec2};
|
|
/// let r = Rect::from_corners(Vec2::ZERO, Vec2::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
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::Rect;
|
|
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
|
|
/// assert!((r.width() - 5.).abs() <= 1e-5);
|
|
/// ```
|
|
#[inline]
|
|
pub fn width(&self) -> f32 {
|
|
self.max.x - self.min.x
|
|
}
|
|
|
|
/// Rectangle height (max.y - min.y).
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::Rect;
|
|
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
|
|
/// assert!((r.height() - 1.).abs() <= 1e-5);
|
|
/// ```
|
|
#[inline]
|
|
pub fn height(&self) -> f32 {
|
|
self.max.y - self.min.y
|
|
}
|
|
|
|
/// Rectangle size.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::{Rect, Vec2};
|
|
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
|
|
/// assert!(r.size().abs_diff_eq(Vec2::new(5., 1.), 1e-5));
|
|
/// ```
|
|
#[inline]
|
|
pub fn size(&self) -> Vec2 {
|
|
self.max - self.min
|
|
}
|
|
|
|
/// Rectangle half-size.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::{Rect, Vec2};
|
|
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
|
|
/// assert!(r.half_size().abs_diff_eq(Vec2::new(2.5, 0.5), 1e-5));
|
|
/// ```
|
|
#[inline]
|
|
pub fn half_size(&self) -> Vec2 {
|
|
self.size() * 0.5
|
|
}
|
|
|
|
/// The center point of the rectangle.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::{Rect, Vec2};
|
|
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
|
|
/// assert!(r.center().abs_diff_eq(Vec2::new(2.5, 0.5), 1e-5));
|
|
/// ```
|
|
#[inline]
|
|
pub fn center(&self) -> Vec2 {
|
|
(self.min + self.max) * 0.5
|
|
}
|
|
|
|
/// Check if a point lies within this rectangle, inclusive of its edges.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::Rect;
|
|
/// let r = Rect::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: Vec2) -> 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
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::{Rect, Vec2};
|
|
/// let r1 = Rect::new(0., 0., 5., 1.); // w=5 h=1
|
|
/// let r2 = Rect::new(1., -1., 3., 3.); // w=2 h=4
|
|
/// let r = r1.union(r2);
|
|
/// assert!(r.min.abs_diff_eq(Vec2::new(0., -1.), 1e-5));
|
|
/// assert!(r.max.abs_diff_eq(Vec2::new(5., 3.), 1e-5));
|
|
/// ```
|
|
#[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
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::{Rect, Vec2};
|
|
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
|
|
/// let u = r.union_point(Vec2::new(3., 6.));
|
|
/// assert!(u.min.abs_diff_eq(Vec2::ZERO, 1e-5));
|
|
/// assert!(u.max.abs_diff_eq(Vec2::new(5., 6.), 1e-5));
|
|
/// ```
|
|
#[inline]
|
|
pub fn union_point(&self, other: Vec2) -> 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 ([`Rect::is_empty()`] returns `true`), but
|
|
/// the actual values of [`Rect::min`] and [`Rect::max`] are implementation-dependent.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::{Rect, Vec2};
|
|
/// let r1 = Rect::new(0., 0., 5., 1.); // w=5 h=1
|
|
/// let r2 = Rect::new(1., -1., 3., 3.); // w=2 h=4
|
|
/// let r = r1.intersect(r2);
|
|
/// assert!(r.min.abs_diff_eq(Vec2::new(1., 0.), 1e-5));
|
|
/// assert!(r.max.abs_diff_eq(Vec2::new(3., 1.), 1e-5));
|
|
/// ```
|
|
#[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 by expanding it evenly on all sides.
|
|
///
|
|
/// A positive expansion value produces a larger rectangle,
|
|
/// while a negative expansion value produces a smaller rectangle.
|
|
/// If this would result in zero or negative width or height, [`Rect::EMPTY`] is returned instead.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::{Rect, Vec2};
|
|
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
|
|
/// let r2 = r.inflate(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.inflate(-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 inflate(&self, expansion: f32) -> Self {
|
|
let mut r = Self {
|
|
min: self.min - expansion,
|
|
max: self.max + expansion,
|
|
};
|
|
// 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
|
|
}
|
|
|
|
/// Build a new rectangle from this one with its coordinates expressed
|
|
/// relative to `other` in a normalized ([0..1] x [0..1]) coordinate system.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// # use bevy_math::{Rect, Vec2};
|
|
/// let r = Rect::new(2., 3., 4., 6.);
|
|
/// let s = Rect::new(0., 0., 10., 10.);
|
|
/// let n = r.normalize(s);
|
|
///
|
|
/// assert_eq!(n.min.x, 0.2);
|
|
/// assert_eq!(n.min.y, 0.3);
|
|
/// assert_eq!(n.max.x, 0.4);
|
|
/// assert_eq!(n.max.y, 0.6);
|
|
/// ```
|
|
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,
|
|
}
|
|
}
|
|
|
|
/// Returns self as [`IRect`] (i32)
|
|
#[inline]
|
|
pub fn as_irect(&self) -> IRect {
|
|
IRect::from_corners(self.min.as_ivec2(), self.max.as_ivec2())
|
|
}
|
|
|
|
/// 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 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); // [-0.5,-0.5] - [0.5,0.5]
|
|
|
|
// overlapping
|
|
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));
|
|
|
|
// disjoint
|
|
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));
|
|
|
|
// included
|
|
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));
|
|
|
|
// including
|
|
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); // [-0.5,-0.5] - [0.5,0.5]
|
|
|
|
// inside
|
|
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));
|
|
|
|
// outside
|
|
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); // [-0.5,-0.5] - [0.5,0.5]
|
|
|
|
// overlapping
|
|
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));
|
|
|
|
// disjoint
|
|
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);
|
|
|
|
// included
|
|
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));
|
|
|
|
// including
|
|
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); // [-0.5,-0.5] - [0.5,0.5]
|
|
|
|
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));
|
|
}
|
|
}
|