Skip to main content

flake/misc/
rect.rs

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