bevy_math/rects/
irect.rs

1use crate::{IVec2, Rect, URect};
2
3#[cfg(feature = "bevy_reflect")]
4use bevy_reflect::{std_traits::ReflectDefault, Reflect};
5#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
6use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
7
8/// A rectangle defined by two opposite corners.
9///
10/// The rectangle is axis aligned, and defined by its minimum and maximum coordinates,
11/// stored in `IRect::min` and `IRect::max`, respectively. The minimum/maximum invariant
12/// must be upheld by the user when directly assigning the fields, otherwise some methods
13/// produce invalid results. It is generally recommended to use one of the constructor
14/// methods instead, which will ensure this invariant is met, unless you already have
15/// the minimum and maximum corners.
16#[repr(C)]
17#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
18#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
19#[cfg_attr(
20    feature = "bevy_reflect",
21    derive(Reflect),
22    reflect(Debug, PartialEq, Hash, Default)
23)]
24#[cfg_attr(
25    all(feature = "serialize", feature = "bevy_reflect"),
26    reflect(Serialize, Deserialize)
27)]
28pub struct IRect {
29    /// The minimum corner point of the rect.
30    pub min: IVec2,
31    /// The maximum corner point of the rect.
32    pub max: IVec2,
33}
34
35impl IRect {
36    /// An empty `IRect`, represented by maximum and minimum corner points
37    /// with `max == IVec2::MIN` and `min == IVec2::MAX`, so the
38    /// rect has an extremely large negative size.
39    /// This is useful, because when taking a union B of a non-empty `IRect` A and
40    /// this empty `IRect`, B will simply equal A.
41    pub const EMPTY: Self = Self {
42        max: IVec2::MIN,
43        min: IVec2::MAX,
44    };
45    /// Create a new rectangle from two corner points.
46    ///
47    /// The two points do not need to be the minimum and/or maximum corners.
48    /// They only need to be two opposite corners.
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// # use bevy_math::IRect;
54    /// let r = IRect::new(0, 4, 10, 6); // w=10 h=2
55    /// let r = IRect::new(2, 3, 5, -1); // w=3 h=4
56    /// ```
57    #[inline]
58    pub fn new(x0: i32, y0: i32, x1: i32, y1: i32) -> Self {
59        Self::from_corners(IVec2::new(x0, y0), IVec2::new(x1, y1))
60    }
61
62    /// Create a new rectangle from two corner points.
63    ///
64    /// The two points do not need to be the minimum and/or maximum corners.
65    /// They only need to be two opposite corners.
66    ///
67    /// # Examples
68    ///
69    /// ```
70    /// # use bevy_math::{IRect, IVec2};
71    /// // Unit rect from [0,0] to [1,1]
72    /// let r = IRect::from_corners(IVec2::ZERO, IVec2::ONE); // w=1 h=1
73    /// // Same; the points do not need to be ordered
74    /// let r = IRect::from_corners(IVec2::ONE, IVec2::ZERO); // w=1 h=1
75    /// ```
76    #[inline]
77    pub fn from_corners(p0: IVec2, p1: IVec2) -> Self {
78        Self {
79            min: p0.min(p1),
80            max: p0.max(p1),
81        }
82    }
83
84    /// Create a new rectangle from its center and size.
85    ///
86    /// # Rounding Behavior
87    ///
88    /// If the size contains odd numbers they will be rounded down to the nearest whole number.
89    ///
90    /// # Panics
91    ///
92    /// This method panics if any of the components of the size is negative.
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// # use bevy_math::{IRect, IVec2};
98    /// let r = IRect::from_center_size(IVec2::ZERO, IVec2::new(3, 2)); // w=2 h=2
99    /// assert_eq!(r.min, IVec2::splat(-1));
100    /// assert_eq!(r.max, IVec2::splat(1));
101    /// ```
102    #[inline]
103    pub fn from_center_size(origin: IVec2, size: IVec2) -> Self {
104        debug_assert!(size.cmpge(IVec2::ZERO).all(), "IRect size must be positive");
105        let half_size = size / 2;
106        Self::from_center_half_size(origin, half_size)
107    }
108
109    /// Create a new rectangle from its center and half-size.
110    ///
111    /// # Panics
112    ///
113    /// This method panics if any of the components of the half-size is negative.
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// # use bevy_math::{IRect, IVec2};
119    /// let r = IRect::from_center_half_size(IVec2::ZERO, IVec2::ONE); // w=2 h=2
120    /// assert_eq!(r.min, IVec2::splat(-1));
121    /// assert_eq!(r.max, IVec2::splat(1));
122    /// ```
123    #[inline]
124    pub fn from_center_half_size(origin: IVec2, half_size: IVec2) -> Self {
125        assert!(
126            half_size.cmpge(IVec2::ZERO).all(),
127            "IRect half_size must be positive"
128        );
129        Self {
130            min: origin - half_size,
131            max: origin + half_size,
132        }
133    }
134
135    /// Check if the rectangle is empty.
136    ///
137    /// # Examples
138    ///
139    /// ```
140    /// # use bevy_math::{IRect, IVec2};
141    /// let r = IRect::from_corners(IVec2::ZERO, IVec2::new(0, 1)); // w=0 h=1
142    /// assert!(r.is_empty());
143    /// ```
144    #[inline]
145    pub fn is_empty(&self) -> bool {
146        self.min.cmpge(self.max).any()
147    }
148
149    /// Rectangle width (max.x - min.x).
150    ///
151    /// # Examples
152    ///
153    /// ```
154    /// # use bevy_math::IRect;
155    /// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
156    /// assert_eq!(r.width(), 5);
157    /// ```
158    #[inline]
159    pub fn width(&self) -> i32 {
160        self.max.x - self.min.x
161    }
162
163    /// Rectangle height (max.y - min.y).
164    ///
165    /// # Examples
166    ///
167    /// ```
168    /// # use bevy_math::IRect;
169    /// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
170    /// assert_eq!(r.height(), 1);
171    /// ```
172    #[inline]
173    pub fn height(&self) -> i32 {
174        self.max.y - self.min.y
175    }
176
177    /// Rectangle size.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// # use bevy_math::{IRect, IVec2};
183    /// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
184    /// assert_eq!(r.size(), IVec2::new(5, 1));
185    /// ```
186    #[inline]
187    pub fn size(&self) -> IVec2 {
188        self.max - self.min
189    }
190
191    /// Rectangle half-size.
192    ///
193    /// # Rounding Behavior
194    ///
195    /// If the full size contains odd numbers they will be rounded down to the nearest whole number when calculating the half size.
196    ///
197    /// # Examples
198    ///
199    /// ```
200    /// # use bevy_math::{IRect, IVec2};
201    /// let r = IRect::new(0, 0, 4, 3); // w=4 h=3
202    /// assert_eq!(r.half_size(), IVec2::new(2, 1));
203    /// ```
204    #[inline]
205    pub fn half_size(&self) -> IVec2 {
206        self.size() / 2
207    }
208
209    /// The center point of the rectangle.
210    ///
211    /// # Rounding Behavior
212    ///
213    /// If the (min + max) contains odd numbers they will be rounded down to the nearest whole number when calculating the center.
214    ///
215    /// # Examples
216    ///
217    /// ```
218    /// # use bevy_math::{IRect, IVec2};
219    /// let r = IRect::new(0, 0, 5, 2); // w=5 h=2
220    /// assert_eq!(r.center(), IVec2::new(2, 1));
221    /// ```
222    #[inline]
223    pub fn center(&self) -> IVec2 {
224        (self.min + self.max) / 2
225    }
226
227    /// Check if a point lies within this rectangle, inclusive of its edges.
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// # use bevy_math::IRect;
233    /// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
234    /// assert!(r.contains(r.center()));
235    /// assert!(r.contains(r.min));
236    /// assert!(r.contains(r.max));
237    /// ```
238    #[inline]
239    pub fn contains(&self, point: IVec2) -> bool {
240        (point.cmpge(self.min) & point.cmple(self.max)).all()
241    }
242
243    /// Build a new rectangle formed of the union of this rectangle and another rectangle.
244    ///
245    /// The union is the smallest rectangle enclosing both rectangles.
246    ///
247    /// # Examples
248    ///
249    /// ```
250    /// # use bevy_math::{IRect, IVec2};
251    /// let r1 = IRect::new(0, 0, 5, 1); // w=5 h=1
252    /// let r2 = IRect::new(1, -1, 3, 3); // w=2 h=4
253    /// let r = r1.union(r2);
254    /// assert_eq!(r.min, IVec2::new(0, -1));
255    /// assert_eq!(r.max, IVec2::new(5, 3));
256    /// ```
257    #[inline]
258    pub fn union(&self, other: Self) -> Self {
259        Self {
260            min: self.min.min(other.min),
261            max: self.max.max(other.max),
262        }
263    }
264
265    /// Build a new rectangle formed of the union of this rectangle and a point.
266    ///
267    /// The union is the smallest rectangle enclosing both the rectangle and the point. If the
268    /// point is already inside the rectangle, this method returns a copy of the rectangle.
269    ///
270    /// # Examples
271    ///
272    /// ```
273    /// # use bevy_math::{IRect, IVec2};
274    /// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
275    /// let u = r.union_point(IVec2::new(3, 6));
276    /// assert_eq!(u.min, IVec2::ZERO);
277    /// assert_eq!(u.max, IVec2::new(5, 6));
278    /// ```
279    #[inline]
280    pub fn union_point(&self, other: IVec2) -> Self {
281        Self {
282            min: self.min.min(other),
283            max: self.max.max(other),
284        }
285    }
286
287    /// Build a new rectangle formed of the intersection of this rectangle and another rectangle.
288    ///
289    /// The intersection is the largest rectangle enclosed in both rectangles. If the intersection
290    /// is empty, this method returns an empty rectangle ([`IRect::is_empty()`] returns `true`), but
291    /// the actual values of [`IRect::min`] and [`IRect::max`] are implementation-dependent.
292    ///
293    /// # Examples
294    ///
295    /// ```
296    /// # use bevy_math::{IRect, IVec2};
297    /// let r1 = IRect::new(0, 0, 5, 1); // w=5 h=1
298    /// let r2 = IRect::new(1, -1, 3, 3); // w=2 h=4
299    /// let r = r1.intersect(r2);
300    /// assert_eq!(r.min, IVec2::new(1, 0));
301    /// assert_eq!(r.max, IVec2::new(3, 1));
302    /// ```
303    #[inline]
304    pub fn intersect(&self, other: Self) -> Self {
305        let mut r = Self {
306            min: self.min.max(other.min),
307            max: self.max.min(other.max),
308        };
309        // Collapse min over max to enforce invariants and ensure e.g. width() or
310        // height() never return a negative value.
311        r.min = r.min.min(r.max);
312        r
313    }
314
315    /// Create a new rectangle by expanding it evenly on all sides.
316    ///
317    /// A positive expansion value produces a larger rectangle,
318    /// while a negative expansion value produces a smaller rectangle.
319    /// If this would result in zero or negative width or height, [`IRect::EMPTY`] is returned instead.
320    ///
321    /// # Examples
322    ///
323    /// ```
324    /// # use bevy_math::{IRect, IVec2};
325    /// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
326    /// let r2 = r.inflate(3); // w=11 h=7
327    /// assert_eq!(r2.min, IVec2::splat(-3));
328    /// assert_eq!(r2.max, IVec2::new(8, 4));
329    ///
330    /// let r = IRect::new(0, -1, 4, 3); // w=4 h=4
331    /// let r2 = r.inflate(-1); // w=2 h=2
332    /// assert_eq!(r2.min, IVec2::new(1, 0));
333    /// assert_eq!(r2.max, IVec2::new(3, 2));
334    /// ```
335    #[inline]
336    pub fn inflate(&self, expansion: i32) -> Self {
337        let mut r = Self {
338            min: self.min - expansion,
339            max: self.max + expansion,
340        };
341        // Collapse min over max to enforce invariants and ensure e.g. width() or
342        // height() never return a negative value.
343        r.min = r.min.min(r.max);
344        r
345    }
346
347    /// Returns self as [`Rect`] (f32)
348    #[inline]
349    pub fn as_rect(&self) -> Rect {
350        Rect::from_corners(self.min.as_vec2(), self.max.as_vec2())
351    }
352
353    /// Returns self as [`URect`] (u32)
354    #[inline]
355    pub fn as_urect(&self) -> URect {
356        URect::from_corners(self.min.as_uvec2(), self.max.as_uvec2())
357    }
358}
359
360#[cfg(test)]
361mod tests {
362    use super::*;
363
364    #[test]
365    fn well_formed() {
366        let r = IRect::from_center_size(IVec2::new(3, -5), IVec2::new(8, 12));
367
368        assert_eq!(r.min, IVec2::new(-1, -11));
369        assert_eq!(r.max, IVec2::new(7, 1));
370
371        assert_eq!(r.center(), IVec2::new(3, -5));
372
373        assert_eq!(r.width().abs(), 8);
374        assert_eq!(r.height().abs(), 12);
375        assert_eq!(r.size(), IVec2::new(8, 12));
376        assert_eq!(r.half_size(), IVec2::new(4, 6));
377
378        assert!(r.contains(IVec2::new(3, -5)));
379        assert!(r.contains(IVec2::new(-1, -10)));
380        assert!(r.contains(IVec2::new(-1, 0)));
381        assert!(r.contains(IVec2::new(7, -10)));
382        assert!(r.contains(IVec2::new(7, 0)));
383        assert!(!r.contains(IVec2::new(50, -5)));
384    }
385
386    #[test]
387    fn rect_union() {
388        let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(4)); // [-2, -2] - [2, 2]
389
390        // overlapping
391        let r2 = IRect {
392            min: IVec2::new(1, 1),
393            max: IVec2::new(3, 3),
394        };
395        let u = r.union(r2);
396        assert_eq!(u.min, IVec2::new(-2, -2));
397        assert_eq!(u.max, IVec2::new(3, 3));
398
399        // disjoint
400        let r2 = IRect {
401            min: IVec2::new(1, 4),
402            max: IVec2::new(4, 6),
403        };
404        let u = r.union(r2);
405        assert_eq!(u.min, IVec2::new(-2, -2));
406        assert_eq!(u.max, IVec2::new(4, 6));
407
408        // included
409        let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(2));
410        let u = r.union(r2);
411        assert_eq!(u.min, r.min);
412        assert_eq!(u.max, r.max);
413
414        // including
415        let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(6));
416        let u = r.union(r2);
417        assert_eq!(u.min, r2.min);
418        assert_eq!(u.min, r2.min);
419    }
420
421    #[test]
422    fn rect_union_pt() {
423        let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(4)); // [-2,-2] - [2,2]
424
425        // inside
426        let v = IVec2::new(1, -1);
427        let u = r.union_point(v);
428        assert_eq!(u.min, r.min);
429        assert_eq!(u.max, r.max);
430
431        // outside
432        let v = IVec2::new(10, -3);
433        let u = r.union_point(v);
434        assert_eq!(u.min, IVec2::new(-2, -3));
435        assert_eq!(u.max, IVec2::new(10, 2));
436    }
437
438    #[test]
439    fn rect_intersect() {
440        let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(8)); // [-4,-4] - [4,4]
441
442        // overlapping
443        let r2 = IRect {
444            min: IVec2::new(2, 2),
445            max: IVec2::new(6, 6),
446        };
447        let u = r.intersect(r2);
448        assert_eq!(u.min, IVec2::new(2, 2));
449        assert_eq!(u.max, IVec2::new(4, 4));
450
451        // disjoint
452        let r2 = IRect {
453            min: IVec2::new(-8, -2),
454            max: IVec2::new(-6, 2),
455        };
456        let u = r.intersect(r2);
457        assert!(u.is_empty());
458        assert_eq!(u.width(), 0);
459
460        // included
461        let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(2));
462        let u = r.intersect(r2);
463        assert_eq!(u.min, r2.min);
464        assert_eq!(u.max, r2.max);
465
466        // including
467        let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(10));
468        let u = r.intersect(r2);
469        assert_eq!(u.min, r.min);
470        assert_eq!(u.max, r.max);
471    }
472
473    #[test]
474    fn rect_inflate() {
475        let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(4)); // [-2,-2] - [2,2]
476
477        let r2 = r.inflate(2);
478        assert_eq!(r2.min, IVec2::new(-4, -4));
479        assert_eq!(r2.max, IVec2::new(4, 4));
480    }
481}