bevy_math/rects/rect.rs
1use crate::{IRect, URect, Vec2};
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 `Rect::min` and `Rect::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)]
18#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
19#[cfg_attr(
20 feature = "bevy_reflect",
21 derive(Reflect),
22 reflect(Debug, PartialEq, Default, Clone)
23)]
24#[cfg_attr(
25 all(feature = "serialize", feature = "bevy_reflect"),
26 reflect(Serialize, Deserialize)
27)]
28pub struct Rect {
29 /// The minimum corner point of the rect.
30 pub min: Vec2,
31 /// The maximum corner point of the rect.
32 pub max: Vec2,
33}
34
35impl Rect {
36 /// An empty `Rect`, represented by maximum and minimum corner points
37 /// at `Vec2::NEG_INFINITY` and `Vec2::INFINITY`, respectively.
38 /// This is so the `Rect` has a infinitely negative size.
39 /// This is useful, because when taking a union B of a non-empty `Rect` A and
40 /// this empty `Rect`, B will simply equal A.
41 pub const EMPTY: Self = Self {
42 max: Vec2::NEG_INFINITY,
43 min: Vec2::INFINITY,
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::Rect;
54 /// let r = Rect::new(0., 4., 10., 6.); // w=10 h=2
55 /// let r = Rect::new(2., 3., 5., -1.); // w=3 h=4
56 /// ```
57 #[inline]
58 pub const fn new(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
59 Self::from_corners(Vec2::new(x0, y0), Vec2::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::{Rect, Vec2};
71 /// // Unit rect from [0,0] to [1,1]
72 /// let r = Rect::from_corners(Vec2::ZERO, Vec2::ONE); // w=1 h=1
73 /// // Same; the points do not need to be ordered
74 /// let r = Rect::from_corners(Vec2::ONE, Vec2::ZERO); // w=1 h=1
75 /// ```
76 #[inline]
77 pub const fn from_corners(p0: Vec2, p1: Vec2) -> Self {
78 Self {
79 min: Vec2::new(p0.x.min(p1.x), p0.y.min(p1.y)),
80 max: Vec2::new(p0.x.max(p1.x), p0.y.max(p1.y)),
81 }
82 }
83
84 /// Create a new rectangle from its center and size.
85 ///
86 /// # Panics
87 ///
88 /// This method panics if any of the components of the size is negative.
89 ///
90 /// # Examples
91 ///
92 /// ```
93 /// # use bevy_math::{Rect, Vec2};
94 /// let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // w=1 h=1
95 /// assert!(r.min.abs_diff_eq(Vec2::splat(-0.5), 1e-5));
96 /// assert!(r.max.abs_diff_eq(Vec2::splat(0.5), 1e-5));
97 /// ```
98 #[inline]
99 pub const fn from_center_size(origin: Vec2, size: Vec2) -> Self {
100 assert!(0. <= size.x && 0. <= size.y, "Rect size must be positive");
101 Self::from_center_half_size(origin, Vec2::new(0.5 * size.x, 0.5 * size.y))
102 }
103
104 /// Create a new rectangle from its center and half-size.
105 ///
106 /// # Panics
107 ///
108 /// This method panics if any of the components of the half-size is negative.
109 ///
110 /// # Examples
111 ///
112 /// ```
113 /// # use bevy_math::{Rect, Vec2};
114 /// let r = Rect::from_center_half_size(Vec2::ZERO, Vec2::ONE); // w=2 h=2
115 /// assert!(r.min.abs_diff_eq(Vec2::splat(-1.), 1e-5));
116 /// assert!(r.max.abs_diff_eq(Vec2::splat(1.), 1e-5));
117 /// ```
118 #[inline]
119 pub const fn from_center_half_size(origin: Vec2, half_size: Vec2) -> Self {
120 assert!(
121 0. <= half_size.x && 0. <= half_size.y,
122 "Rect half_size must be positive"
123 );
124 Self {
125 min: Vec2::new(origin.x - half_size.x, origin.y - half_size.y),
126 max: Vec2::new(origin.x + half_size.x, origin.y + half_size.y),
127 }
128 }
129
130 /// Check if the rectangle is empty.
131 ///
132 /// # Examples
133 ///
134 /// ```
135 /// # use bevy_math::{Rect, Vec2};
136 /// let r = Rect::from_corners(Vec2::ZERO, Vec2::new(0., 1.)); // w=0 h=1
137 /// assert!(r.is_empty());
138 /// ```
139 #[inline]
140 pub const fn is_empty(&self) -> bool {
141 self.min.x >= self.max.x || self.min.y >= self.max.y
142 }
143
144 /// Rectangle width (max.x - min.x).
145 ///
146 /// # Examples
147 ///
148 /// ```
149 /// # use bevy_math::Rect;
150 /// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
151 /// assert!((r.width() - 5.).abs() <= 1e-5);
152 /// ```
153 #[inline]
154 pub const fn width(&self) -> f32 {
155 self.max.x - self.min.x
156 }
157
158 /// Rectangle height (max.y - min.y).
159 ///
160 /// # Examples
161 ///
162 /// ```
163 /// # use bevy_math::Rect;
164 /// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
165 /// assert!((r.height() - 1.).abs() <= 1e-5);
166 /// ```
167 #[inline]
168 pub const fn height(&self) -> f32 {
169 self.max.y - self.min.y
170 }
171
172 /// Rectangle size.
173 ///
174 /// # Examples
175 ///
176 /// ```
177 /// # use bevy_math::{Rect, Vec2};
178 /// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
179 /// assert!(r.size().abs_diff_eq(Vec2::new(5., 1.), 1e-5));
180 /// ```
181 #[inline]
182 pub const fn size(&self) -> Vec2 {
183 Vec2::new(self.max.x - self.min.x, self.max.y - self.min.y)
184 }
185
186 /// Rectangle half-size.
187 ///
188 /// # Examples
189 ///
190 /// ```
191 /// # use bevy_math::{Rect, Vec2};
192 /// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
193 /// assert!(r.half_size().abs_diff_eq(Vec2::new(2.5, 0.5), 1e-5));
194 /// ```
195 #[inline]
196 pub const fn half_size(&self) -> Vec2 {
197 let size = self.size();
198 Vec2::new(0.5 * size.x, 0.5 * size.y)
199 }
200
201 /// The center point of the rectangle.
202 ///
203 /// # Examples
204 ///
205 /// ```
206 /// # use bevy_math::{Rect, Vec2};
207 /// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
208 /// assert!(r.center().abs_diff_eq(Vec2::new(2.5, 0.5), 1e-5));
209 /// ```
210 #[inline]
211 pub const fn center(&self) -> Vec2 {
212 Vec2::new(
213 0.5 * (self.min.x + self.max.x),
214 0.5 * (self.min.y + self.max.y),
215 )
216 }
217
218 /// Returns the rectangle translated by the given offset.
219 ///
220 /// # Examples
221 ///
222 /// ```
223 /// # use bevy_math::{Rect, Vec2};
224 /// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
225 /// let r2 = r.translate(Vec2::new(2., -3.));
226 /// assert!(r2.min.abs_diff_eq(Vec2::new(2., -3.), 1e-5));
227 /// assert!(r2.max.abs_diff_eq(Vec2::new(7., -2.), 1e-5));
228 /// ```
229 #[inline]
230 pub const fn translate(&self, offset: Vec2) -> Self {
231 Self {
232 min: Vec2::new(self.min.x + offset.x, self.min.y + offset.y),
233 max: Vec2::new(self.max.x + offset.x, self.max.y + offset.y),
234 }
235 }
236
237 /// Check if a point lies within this rectangle, inclusive of its edges.
238 ///
239 /// # Examples
240 ///
241 /// ```
242 /// # use bevy_math::Rect;
243 /// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
244 /// assert!(r.contains(r.center()));
245 /// assert!(r.contains(r.min));
246 /// assert!(r.contains(r.max));
247 /// ```
248 #[inline]
249 pub const fn contains(&self, point: Vec2) -> bool {
250 self.min.x <= point.x
251 && point.x <= self.max.x
252 && self.min.y <= point.y
253 && point.y <= self.max.y
254 }
255
256 /// Clamps a point to lie within this rectangle.
257 ///
258 /// # Examples
259 ///
260 /// ```
261 /// # use bevy_math::{Rect, Vec2};
262 /// let r = Rect::new(0., 0., 4., 5.);
263 /// assert_eq!(r.clamp_point(Vec2::new(-1., 6.)), Vec2::new(0., 5.));
264 /// assert_eq!(r.clamp_point(Vec2::ONE), Vec2::ONE);
265 /// ```
266 #[inline]
267 pub fn clamp_point(&self, point: Vec2) -> Vec2 {
268 point.clamp(self.min, self.max)
269 }
270
271 /// Build a new rectangle formed of the union of this rectangle and another rectangle.
272 ///
273 /// The union is the smallest rectangle enclosing both rectangles.
274 ///
275 /// # Examples
276 ///
277 /// ```
278 /// # use bevy_math::{Rect, Vec2};
279 /// let r1 = Rect::new(0., 0., 5., 1.); // w=5 h=1
280 /// let r2 = Rect::new(1., -1., 3., 3.); // w=2 h=4
281 /// let r = r1.union(r2);
282 /// assert!(r.min.abs_diff_eq(Vec2::new(0., -1.), 1e-5));
283 /// assert!(r.max.abs_diff_eq(Vec2::new(5., 3.), 1e-5));
284 /// ```
285 #[inline]
286 pub const fn union(&self, other: Self) -> Self {
287 let min_x = self.min.x.min(other.min.x);
288 let min_y = self.min.y.min(other.min.y);
289 let max_x = self.max.x.max(other.max.x);
290 let max_y = self.max.y.max(other.max.y);
291
292 Self {
293 min: Vec2::new(min_x, min_y),
294 max: Vec2::new(max_x, max_y),
295 }
296 }
297
298 /// Build a new rectangle formed of the union of this rectangle and a point.
299 ///
300 /// The union is the smallest rectangle enclosing both the rectangle and the point. If the
301 /// point is already inside the rectangle, this method returns a copy of the rectangle.
302 ///
303 /// # Examples
304 ///
305 /// ```
306 /// # use bevy_math::{Rect, Vec2};
307 /// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
308 /// let u = r.union_point(Vec2::new(3., 6.));
309 /// assert!(u.min.abs_diff_eq(Vec2::ZERO, 1e-5));
310 /// assert!(u.max.abs_diff_eq(Vec2::new(5., 6.), 1e-5));
311 /// ```
312 #[inline]
313 pub const fn union_point(&self, other: Vec2) -> Self {
314 let min_x = self.min.x.min(other.x);
315 let min_y = self.min.y.min(other.y);
316 let max_x = self.max.x.max(other.x);
317 let max_y = self.max.y.max(other.y);
318
319 Self {
320 min: Vec2::new(min_x, min_y),
321 max: Vec2::new(max_x, max_y),
322 }
323 }
324
325 /// Build a new rectangle formed of the intersection of this rectangle and another rectangle.
326 ///
327 /// The intersection is the largest rectangle enclosed in both rectangles. If the intersection
328 /// is empty, this method returns an empty rectangle ([`Rect::is_empty()`] returns `true`), but
329 /// the actual values of [`Rect::min`] and [`Rect::max`] are implementation-dependent.
330 ///
331 /// # Examples
332 ///
333 /// ```
334 /// # use bevy_math::{Rect, Vec2};
335 /// let r1 = Rect::new(0., 0., 5., 1.); // w=5 h=1
336 /// let r2 = Rect::new(1., -1., 3., 3.); // w=2 h=4
337 /// let r = r1.intersect(r2);
338 /// assert!(r.min.abs_diff_eq(Vec2::new(1., 0.), 1e-5));
339 /// assert!(r.max.abs_diff_eq(Vec2::new(3., 1.), 1e-5));
340 /// ```
341 #[inline]
342 pub fn intersect(&self, other: Self) -> Self {
343 let min_x = self.min.x.max(other.min.x);
344 let min_y = self.min.y.max(other.min.y);
345 let max_x = self.max.x.min(other.max.x);
346 let max_y = self.max.y.min(other.max.y);
347 // Collapse min over max to enforce invariants and ensure e.g. width() or
348 // height() never return a negative value.
349 let collapsed_min_x = min_x.min(max_x);
350 let collapsed_min_y = min_y.min(max_y);
351 Self {
352 min: Vec2::new(collapsed_min_x, collapsed_min_y),
353 max: Vec2::new(max_x, max_y),
354 }
355 }
356
357 /// Create a new rectangle by expanding it evenly on all sides.
358 ///
359 /// A positive expansion value produces a larger rectangle,
360 /// while a negative expansion value produces a smaller rectangle.
361 /// If this would result in zero or negative width or height, [`Rect::EMPTY`] is returned instead.
362 ///
363 /// # Examples
364 ///
365 /// ```
366 /// # use bevy_math::{Rect, Vec2};
367 /// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
368 /// let r2 = r.inflate(3.); // w=11 h=7
369 /// assert!(r2.min.abs_diff_eq(Vec2::splat(-3.), 1e-5));
370 /// assert!(r2.max.abs_diff_eq(Vec2::new(8., 4.), 1e-5));
371 ///
372 /// let r = Rect::new(0., -1., 6., 7.); // w=6 h=8
373 /// let r2 = r.inflate(-2.); // w=11 h=7
374 /// assert!(r2.min.abs_diff_eq(Vec2::new(2., 1.), 1e-5));
375 /// assert!(r2.max.abs_diff_eq(Vec2::new(4., 5.), 1e-5));
376 /// ```
377 #[inline]
378 pub const fn inflate(&self, expansion: f32) -> Self {
379 let min_x = self.min.x - expansion;
380 let min_y = self.min.y - expansion;
381 let max_x = self.max.x + expansion;
382 let max_y = self.max.y + expansion;
383 // Collapse min over max to enforce invariants and ensure e.g. width() or
384 // height() never return a negative value.
385 let collapsed_min_x = min_x.min(max_x);
386 let collapsed_min_y = min_y.min(max_y);
387 Self {
388 min: Vec2::new(collapsed_min_x, collapsed_min_y),
389 max: Vec2::new(max_x, max_y),
390 }
391 }
392
393 /// Build a new rectangle from this one with its coordinates expressed
394 /// relative to `other` in a normalized ([0..1] x [0..1]) coordinate system.
395 ///
396 /// # Examples
397 ///
398 /// ```
399 /// # use bevy_math::{Rect, Vec2};
400 /// let r = Rect::new(2., 3., 4., 6.);
401 /// let s = Rect::new(0., 0., 10., 10.);
402 /// let n = r.normalize(s);
403 ///
404 /// assert_eq!(n.min.x, 0.2);
405 /// assert_eq!(n.min.y, 0.3);
406 /// assert_eq!(n.max.x, 0.4);
407 /// assert_eq!(n.max.y, 0.6);
408 /// ```
409 pub const fn normalize(&self, other: Self) -> Self {
410 let outer_size = other.size();
411 let min_x = (self.min.x - other.min.x) / outer_size.x;
412 let min_y = (self.min.y - other.min.y) / outer_size.y;
413 let max_x = (self.max.x - other.min.x) / outer_size.x;
414 let max_y = (self.max.y - other.min.y) / outer_size.y;
415
416 Self {
417 min: Vec2::new(min_x, min_y),
418 max: Vec2::new(max_x, max_y),
419 }
420 }
421
422 /// Return the area of this rectangle.
423 ///
424 /// # Examples
425 ///
426 /// ```
427 /// # use bevy_math::Rect;
428 /// let r = Rect::new(0., 0., 10., 10.); // w=10 h=10
429 /// assert_eq!(r.area(), 100.0);
430 /// ```
431 #[inline]
432 pub const fn area(&self) -> f32 {
433 self.width() * self.height()
434 }
435
436 /// Returns self as [`IRect`] (i32)
437 #[inline]
438 pub fn as_irect(&self) -> IRect {
439 IRect::from_corners(self.min.as_ivec2(), self.max.as_ivec2())
440 }
441
442 /// Returns self as [`URect`] (u32)
443 #[inline]
444 pub fn as_urect(&self) -> URect {
445 URect::from_corners(self.min.as_uvec2(), self.max.as_uvec2())
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use crate::ops;
452
453 use super::*;
454
455 #[test]
456 fn well_formed() {
457 let r = Rect::from_center_size(Vec2::new(3., -5.), Vec2::new(8., 11.));
458
459 assert!(r.min.abs_diff_eq(Vec2::new(-1., -10.5), 1e-5));
460 assert!(r.max.abs_diff_eq(Vec2::new(7., 0.5), 1e-5));
461
462 assert!(r.center().abs_diff_eq(Vec2::new(3., -5.), 1e-5));
463
464 assert!(ops::abs(r.width() - 8.) <= 1e-5);
465 assert!(ops::abs(r.height() - 11.) <= 1e-5);
466 assert!(r.size().abs_diff_eq(Vec2::new(8., 11.), 1e-5));
467 assert!(r.half_size().abs_diff_eq(Vec2::new(4., 5.5), 1e-5));
468
469 assert!(r.contains(Vec2::new(3., -5.)));
470 assert!(r.contains(Vec2::new(-1., -10.5)));
471 assert!(r.contains(Vec2::new(-1., 0.5)));
472 assert!(r.contains(Vec2::new(7., -10.5)));
473 assert!(r.contains(Vec2::new(7., 0.5)));
474 assert!(!r.contains(Vec2::new(50., -5.)));
475 }
476
477 #[test]
478 fn rect_union() {
479 let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // [-0.5,-0.5] - [0.5,0.5]
480
481 // overlapping
482 let r2 = Rect {
483 min: Vec2::new(-0.8, 0.3),
484 max: Vec2::new(0.1, 0.7),
485 };
486 let u = r.union(r2);
487 assert!(u.min.abs_diff_eq(Vec2::new(-0.8, -0.5), 1e-5));
488 assert!(u.max.abs_diff_eq(Vec2::new(0.5, 0.7), 1e-5));
489
490 // disjoint
491 let r2 = Rect {
492 min: Vec2::new(-1.8, -0.5),
493 max: Vec2::new(-1.5, 0.3),
494 };
495 let u = r.union(r2);
496 assert!(u.min.abs_diff_eq(Vec2::new(-1.8, -0.5), 1e-5));
497 assert!(u.max.abs_diff_eq(Vec2::new(0.5, 0.5), 1e-5));
498
499 // included
500 let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(0.5));
501 let u = r.union(r2);
502 assert!(u.min.abs_diff_eq(r.min, 1e-5));
503 assert!(u.max.abs_diff_eq(r.max, 1e-5));
504
505 // including
506 let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(1.5));
507 let u = r.union(r2);
508 assert!(u.min.abs_diff_eq(r2.min, 1e-5));
509 assert!(u.max.abs_diff_eq(r2.max, 1e-5));
510 }
511
512 #[test]
513 fn rect_union_pt() {
514 let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // [-0.5,-0.5] - [0.5,0.5]
515
516 // inside
517 let v = Vec2::new(0.3, -0.2);
518 let u = r.union_point(v);
519 assert!(u.min.abs_diff_eq(r.min, 1e-5));
520 assert!(u.max.abs_diff_eq(r.max, 1e-5));
521
522 // outside
523 let v = Vec2::new(10., -3.);
524 let u = r.union_point(v);
525 assert!(u.min.abs_diff_eq(Vec2::new(-0.5, -3.), 1e-5));
526 assert!(u.max.abs_diff_eq(Vec2::new(10., 0.5), 1e-5));
527 }
528
529 #[test]
530 fn rect_intersect() {
531 let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // [-0.5,-0.5] - [0.5,0.5]
532
533 // overlapping
534 let r2 = Rect {
535 min: Vec2::new(-0.8, 0.3),
536 max: Vec2::new(0.1, 0.7),
537 };
538 let u = r.intersect(r2);
539 assert!(u.min.abs_diff_eq(Vec2::new(-0.5, 0.3), 1e-5));
540 assert!(u.max.abs_diff_eq(Vec2::new(0.1, 0.5), 1e-5));
541
542 // disjoint
543 let r2 = Rect {
544 min: Vec2::new(-1.8, -0.5),
545 max: Vec2::new(-1.5, 0.3),
546 };
547 let u = r.intersect(r2);
548 assert!(u.is_empty());
549 assert!(u.width() <= 1e-5);
550
551 // included
552 let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(0.5));
553 let u = r.intersect(r2);
554 assert!(u.min.abs_diff_eq(r2.min, 1e-5));
555 assert!(u.max.abs_diff_eq(r2.max, 1e-5));
556
557 // including
558 let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(1.5));
559 let u = r.intersect(r2);
560 assert!(u.min.abs_diff_eq(r.min, 1e-5));
561 assert!(u.max.abs_diff_eq(r.max, 1e-5));
562 }
563
564 #[test]
565 fn rect_inflate() {
566 let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // [-0.5,-0.5] - [0.5,0.5]
567
568 let r2 = r.inflate(0.3);
569 assert!(r2.min.abs_diff_eq(Vec2::new(-0.8, -0.8), 1e-5));
570 assert!(r2.max.abs_diff_eq(Vec2::new(0.8, 0.8), 1e-5));
571 }
572
573 #[test]
574 fn rect_translate() {
575 let r = Rect::new(0., 1., 4., 3.);
576 let r2 = r.translate(Vec2::new(2., -5.));
577
578 assert!(r2.min.abs_diff_eq(Vec2::new(2., -4.), 1e-5));
579 assert!(r2.max.abs_diff_eq(Vec2::new(6., -2.), 1e-5));
580 assert!(r2.size().abs_diff_eq(r.size(), 1e-5));
581 }
582
583 #[test]
584 fn rect_clamp_point() {
585 let r = Rect::new(0., 1., 4., 3.);
586
587 assert_eq!(r.clamp_point(Vec2::new(2., 2.)), Vec2::new(2., 2.));
588 assert_eq!(r.clamp_point(Vec2::new(-1., 5.)), Vec2::new(0., 3.));
589 }
590}