Skip to main content

flake/misc/
rect.rs

1use crate::prelude::{
2    Color, Vector2, draw_line, draw_rectangle_from_rect, draw_rectangle_lines_from_rect,
3    draw_rectangle_lines_rounded_from_rect, draw_rectangle_lines_rounded_from_rect_ex,
4    draw_rectangle_rotated, draw_rectangle_rounded_from_rect, draw_rectangle_rounded_from_rect_ex,
5    mouse_position_vec2,
6};
7use cgmath::vec2;
8
9/// # Rect
10/// Contains the position (x, y) and size (width, height) of a rectangle.
11/// Can be used for detecting AABB collisions or drawing rectangles.
12///
13/// ## Examples
14///
15/// ```
16/// let my_rect = Rect::new(0.0, 0.0, 50.0, 50.0);
17/// ```
18///
19/// Drawing a Rect to the screen:
20/// ```
21/// my_rect.draw(RED);
22/// // or
23/// draw_rectangle_from_rect(my_rect, RED);
24/// ```
25///
26/// Check collisions:
27/// ```
28/// if my_rect.contains_point(mouse_position_vec2()) {
29///     println!("Mouse is inside the rect")
30/// }
31///
32/// let aabb_intersection: Option<Rect> = my_rect.intersection(other_rect);
33/// ```
34#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd)]
35pub struct Rect {
36    pub x: f32,
37    pub y: f32,
38    pub w: f32,
39    pub h: f32,
40}
41
42impl Rect {
43    /// Makes a new `Rect` object with the x, y position and Width x Height
44    pub const fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
45        Self { x, y, w, h }
46    }
47
48    /// Build a Rect from two corner points (handles any order)
49    pub fn from_corners(a: Vector2, b: Vector2) -> Self {
50        let x = a.x.min(b.x);
51        let y = a.y.min(b.y);
52        Self::new(x, y, (a.x - b.x).abs(), (a.y - b.y).abs())
53    }
54
55    /// Build a Rect centered on `center` with the given size
56    pub fn from_center(center: Vector2, w: f32, h: f32) -> Self {
57        Self::new(center.x - w / 2.0, center.y - h / 2.0, w, h)
58    }
59
60    // --- Edges ---
61    pub const fn left(&self) -> f32 {
62        self.x
63    }
64    pub const fn right(&self) -> f32 {
65        self.x + self.w
66    }
67    pub const fn top(&self) -> f32 {
68        self.y
69    }
70    pub const fn bottom(&self) -> f32 {
71        self.y + self.h
72    }
73
74    // --- Points ---
75    pub const fn top_left(&self) -> Vector2 {
76        vec2(self.x, self.y)
77    }
78    pub const fn top_right(&self) -> Vector2 {
79        vec2(self.x + self.w, self.y)
80    }
81    pub const fn bottom_left(&self) -> Vector2 {
82        vec2(self.x, self.y + self.h)
83    }
84    pub const fn bottom_right(&self) -> Vector2 {
85        vec2(self.x + self.w, self.y + self.h)
86    }
87    pub const fn xy(&self) -> Vector2 {
88        vec2(self.x, self.y)
89    }
90    pub const fn wh(&self) -> Vector2 {
91        vec2(self.w, self.h)
92    }
93    pub const fn xw(&self) -> Vector2 {
94        vec2(self.x, self.w)
95    }
96    pub const fn xh(&self) -> Vector2 {
97        vec2(self.x, self.h)
98    }
99    pub const fn yw(&self) -> Vector2 {
100        vec2(self.y, self.w)
101    }
102    pub const fn yh(&self) -> Vector2 {
103        vec2(self.y, self.h)
104    }
105
106    pub fn corners(&self) -> [Vector2; 4] {
107        [
108            self.top_left(),
109            self.top_right(),
110            self.bottom_left(),
111            self.bottom_right(),
112        ]
113    }
114    pub fn center(&self) -> Vector2 {
115        vec2(self.x + self.w / 2.0, self.y + self.h / 2.0)
116    }
117
118    // --- Measurements ---
119    pub const fn area(&self) -> f32 {
120        self.w * self.h
121    }
122    pub const fn perimeter(&self) -> f32 {
123        2.0 * (self.w + self.h)
124    }
125    pub const fn size(&self) -> Vector2 {
126        vec2(self.w, self.h)
127    }
128    pub const fn position(&self) -> Vector2 {
129        vec2(self.x, self.y)
130    }
131
132    // --- Builders (non-mutating, return new Rect) ---
133    /// Get a new Rect with the specified x
134    pub const fn with_x(&self, x: f32) -> Self {
135        Self::new(x, self.y, self.w, self.h)
136    }
137    /// Get a new Rect with the specified y
138    pub const fn with_y(&self, y: f32) -> Self {
139        Self::new(self.x, y, self.w, self.h)
140    }
141    /// Get a new Rect with the specified w
142    pub const fn with_w(&self, w: f32) -> Self {
143        Self::new(self.x, self.y, w, self.h)
144    }
145    /// Get a new Rect with the specified h
146    pub const fn with_h(&self, h: f32) -> Self {
147        Self::new(self.x, self.y, self.w, h)
148    }
149
150    /// Get a new Rect with the specified position
151    pub fn with_position(&self, pos: Vector2) -> Self {
152        Self::new(pos.x, pos.y, self.w, self.h)
153    }
154    /// Get a new Rect with the specified position
155    pub fn with_position_xy(&self, x: f32, y: f32) -> Self {
156        Self::new(x, y, self.w, self.h)
157    }
158
159    /// Changes the position relatively by an offset Vector2.
160    pub fn change_position_by(&mut self, offset: Vector2) {
161        self.x += offset.x;
162        self.y += offset.y;
163    }
164
165    /// Changes the size relatively by an offset Vector2.
166    pub fn change_size_by(&mut self, offset: Vector2) {
167        self.w += offset.x;
168        self.h += offset.y;
169    }
170
171    /// Get a new Rect with the specified size
172    pub fn with_size(&self, size: Vector2) -> Self {
173        Self::new(self.x, self.y, size.x, size.y)
174    }
175
176    /// Get a moved Rect by an offset
177    pub fn translated(&self, offset: Vector2) -> Self {
178        Self::new(self.x + offset.x, self.y + offset.y, self.w, self.h)
179    }
180
181    /// Move the rect so its center is at `center`
182    pub fn centered_at(&self, center: Vector2) -> Self {
183        Self::from_center(center, self.w, self.h)
184    }
185
186    /// Grow/shrink the rect on all sides by `amount` (negative shrinks), keeping center fixed
187    pub fn inflated(&self, amount: f32) -> Self {
188        Self::new(
189            self.x - amount,
190            self.y - amount,
191            self.w + amount * 2.0,
192            self.h + amount * 2.0,
193        )
194    }
195
196    /// Scale width/height around the top-left corner (position unchanged)
197    pub const fn scaled(&self, factor: f32) -> Self {
198        Self::new(self.x, self.y, self.w * factor, self.h * factor)
199    }
200
201    /// Scale width/height around the rect's center
202    pub fn scaled_from_center(&self, factor: f32) -> Self {
203        let center = self.center();
204        Self::from_center(center, self.w * factor, self.h * factor)
205    }
206
207    /// Smallest rect that fully contains both `self` and `other`
208    pub fn union(&self, other: &Rect) -> Self {
209        let x1 = self.x.min(other.x);
210        let y1 = self.y.min(other.y);
211        let x2 = (self.x + self.w).max(other.x + other.w);
212        let y2 = (self.y + self.h).max(other.y + other.h);
213        Self::new(x1, y1, x2 - x1, y2 - y1)
214    }
215
216    /// Clamp a point to lie within this rect
217    pub fn clamp_point(&self, point: Vector2) -> Vector2 {
218        vec2(
219            point.x.clamp(self.left(), self.right()),
220            point.y.clamp(self.top(), self.bottom()),
221        )
222    }
223
224    /// Fix a rect with negative width/height into one with positive dimensions
225    pub fn normalized(&self) -> Self {
226        let (x, w) = if self.w < 0.0 {
227            (self.x + self.w, -self.w)
228        } else {
229            (self.x, self.w)
230        };
231        let (y, h) = if self.h < 0.0 {
232            (self.y + self.h, -self.h)
233        } else {
234            (self.y, self.h)
235        };
236        Self::new(x, y, w, h)
237    }
238
239    pub fn to_array(&self) -> [f32; 4] {
240        [self.x, self.y, self.w, self.h]
241    }
242
243    /// Linearly interpolate between two rects
244    pub fn lerp(self, other: Rect, t: f32) -> Self {
245        Self::new(
246            self.x + (other.x - self.x) * t,
247            self.y + (other.y - self.y) * t,
248            self.w + (other.w - self.w) * t,
249            self.h + (other.h - self.h) * t,
250        )
251    }
252
253    /// Test if point (x, y) lies inside/on the Rect
254    pub const fn contains_point(&self, x: f32, y: f32) -> bool {
255        x >= self.x && y >= self.y && x <= self.x + self.w && y <= self.y + self.h
256    }
257
258    /// Test if point (x, y) lies inside/on the Rect
259    pub const fn contains_point_fully(&self, x: f32, y: f32) -> bool {
260        x > self.x && y > self.y && x < self.x + self.w && y < self.y + self.h
261    }
262
263    /// Test if point (`Vector2`) lies inside/on the Rect
264    pub const fn contains_point_vec2(&self, point: Vector2) -> bool {
265        self.contains_point(point.x, point.y)
266    }
267
268    /// Test if point (`Vector2`) lies inside/on the Rect
269    pub const fn contains_point_fully_vec2(&self, point: Vector2) -> bool {
270        self.contains_point_fully(point.x, point.y)
271    }
272
273    /// Test if mouse lies inside/on the Rect
274    pub fn contains_mouse(&self) -> bool {
275        self.contains_point_vec2(mouse_position_vec2())
276    }
277
278    /// Test if mouse lies inside/on the Rect
279    pub fn contains_mouse_fully(&self) -> bool {
280        self.contains_point_fully_vec2(mouse_position_vec2())
281    }
282
283    /// AABB overlap test
284    pub const fn intersects(&self, other: &Rect) -> bool {
285        self.x < other.x + other.w
286            && self.x + self.w > other.x
287            && self.y < other.y + other.h
288            && self.y + self.h > other.y
289    }
290
291    /// Returns the overlapping region, or `None` if they don't intersect
292    pub const fn intersection(&self, other: Rect) -> Option<Rect> {
293        let x1 = self.x.max(other.x);
294        let y1 = self.y.max(other.y);
295        let x2 = (self.x + self.w).min(other.x + other.w);
296        let y2 = (self.y + self.h).min(other.y + other.h);
297
298        if x1 < x2 && y1 < y2 {
299            Some(Rect::new(x1, y1, x2 - x1, y2 - y1))
300        } else {
301            None
302        }
303    }
304
305    /// True if `self` fully contains `other`
306    pub const fn contains_rect_fully(&self, other: &Rect) -> bool {
307        self.x <= other.x
308            && self.y <= other.y
309            && self.x + self.w >= other.x + other.w
310            && self.y + self.h >= other.y + other.h
311    }
312
313    /// Updates the original rect with a new one and returns it as well
314    pub fn update(&mut self, new_rect: Rect) -> Rect {
315        *self = new_rect;
316        new_rect
317    }
318
319    /// Draw the Rect to the screen using a color
320    pub fn draw(&self, color: Color) {
321        draw_rectangle_from_rect(*self, color);
322    }
323
324    pub fn draw_rounded(&self, radius: f32, color: Color) {
325        draw_rectangle_rounded_from_rect(*self, radius, color);
326    }
327
328    pub fn draw_rounded_ex(
329        &self,
330        top_left: f32,
331        top_right: f32,
332        bottom_left: f32,
333        bottom_right: f32,
334        color: Color,
335    ) {
336        draw_rectangle_rounded_from_rect_ex(
337            *self,
338            top_left,
339            top_right,
340            bottom_left,
341            bottom_right,
342            color,
343        );
344    }
345
346    /// Draw the Rect's outline to the screen using a color and thickness
347    pub fn draw_lines(&self, thickness: f32, color: Color) {
348        draw_rectangle_lines_from_rect(*self, thickness, color);
349    }
350
351    pub fn draw_lines_rounded(&self, radius: f32, thickness: f32, color: Color) {
352        draw_rectangle_lines_rounded_from_rect(*self, radius, thickness, color);
353    }
354
355    pub fn draw_lines_rounded_ex(
356        &self,
357        top_left: f32,
358        top_right: f32,
359        bottom_left: f32,
360        bottom_right: f32,
361        thickness: f32,
362        color: Color,
363    ) {
364        draw_rectangle_lines_rounded_from_rect_ex(
365            *self,
366            top_left,
367            top_right,
368            bottom_left,
369            bottom_right,
370            thickness,
371            color,
372        );
373    }
374}
375
376impl Into<[f32; 4]> for Rect {
377    fn into(self) -> [f32; 4] {
378        self.to_array()
379    }
380}
381
382// -- OBB ----------------------------------------
383
384impl Rect {
385    /// Pushes `self` out of an axis-aligned `other`.
386    /// Returns the collision normal (direction `self` was pushed), or `None`.
387    pub fn resolve_collision(&mut self, other: Rect) -> Option<Vector2> {
388        let c = self.intersection(other)?;
389        let (sc, oc) = (self.center(), other.center());
390
391        if c.w < c.h {
392            let dir = if sc.x < oc.x { -1.0 } else { 1.0 };
393            self.x += c.w * dir;
394            Some(vec2(dir, 0.0))
395        } else {
396            let dir = if sc.y < oc.y { -1.0 } else { 1.0 };
397            self.y += c.h * dir;
398            Some(vec2(0.0, dir))
399        }
400    }
401
402    /// Pushes `self` out of a rotated `other` (SAT).
403    /// Returns the collision normal (direction `self` was pushed), or `None`.
404    pub fn resolve_collision_obb(
405        &mut self,
406        angle: f32,
407        other: &Rect,
408        other_angle: f32,
409    ) -> Option<Vector2> {
410        let mtv = self.intersection_obb(angle, other, other_angle)?;
411        let len = (mtv.x * mtv.x + mtv.y * mtv.y).sqrt();
412        if len <= f32::EPSILON {
413            return None;
414        }
415        self.change_position_by(mtv);
416        Some(mtv / len)
417    }
418
419    /// OBB vs OBB collision (SAT). Angles are in radians, each rect rotates around its own center.
420    /// Returns the minimum translation vector that pushes `self` out of `other`, or `None` if not colliding.
421    pub fn intersection_obb(&self, angle: f32, other: &Rect, other_angle: f32) -> Option<Vector2> {
422        let dot = |a: Vector2, b: Vector2| a.x * b.x + a.y * b.y;
423        let axes = |a: f32| {
424            let (s, c) = a.sin_cos();
425            [vec2(c, s), vec2(-s, c)]
426        };
427
428        let a_axes = axes(angle);
429        let b_axes = axes(other_angle);
430        let (ha, hb) = (self.size() * 0.5, other.size() * 0.5);
431        let d = other.center() - self.center();
432
433        let mut min_overlap = f32::MAX;
434        let mut mtv = vec2(0.0, 0.0);
435
436        for n in a_axes.iter().chain(b_axes.iter()) {
437            let n = *n;
438
439            // Projected half-extent of each box onto this axis
440            let ra = ha.x * dot(a_axes[0], n).abs() + ha.y * dot(a_axes[1], n).abs();
441            let rb = hb.x * dot(b_axes[0], n).abs() + hb.y * dot(b_axes[1], n).abs();
442
443            let dist = dot(d, n);
444            let overlap = ra + rb - dist.abs();
445
446            if overlap <= 0.0 {
447                return None; // separating axis found
448            }
449
450            if overlap < min_overlap {
451                min_overlap = overlap;
452                let sign = if dist > 0.0 { -1.0 } else { 1.0 };
453                mtv = n * (sign * overlap);
454            }
455        }
456
457        Some(mtv)
458    }
459
460    /// Bool-only OBB overlap test
461    pub fn intersects_obb(&self, angle: f32, other: &Rect, other_angle: f32) -> bool {
462        self.intersection_obb(angle, other, other_angle).is_some()
463    }
464
465    /// Corners of the rect rotated by `angle` (radians) around its center
466    pub fn corners_by_angle(&self, angle: f32) -> [Vector2; 4] {
467        let c = self.center();
468        let (s, co) = angle.sin_cos();
469        let (hw, hh) = (self.w / 2.0, self.h / 2.0);
470        [(-hw, -hh), (hw, -hh), (hw, hh), (-hw, hh)]
471            .map(|(x, y)| vec2(c.x + x * co - y * s, c.y + x * s + y * co))
472    }
473
474    /// Draw the Rect rotated by `angle` (radians) around its center
475    pub fn draw_rotated(&self, angle: f32, color: Color) {
476        let c = self.center();
477        draw_rectangle_rotated(c.x, c.y, self.w, self.h, angle, color);
478    }
479
480    /// Draw the rotated Rect's outline
481    pub fn draw_lines_rotated(&self, angle: f32, thickness: f32, color: Color) {
482        let c = self.corners_by_angle(angle);
483        for i in 0..4 {
484            let (a, b) = (c[i], c[(i + 1) % 4]);
485            draw_line(a.x, a.y, b.x, b.y, thickness, color);
486        }
487    }
488}