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}