Skip to main content

yog_graphics/
rect.rs

1//! Axis-aligned rectangles.
2
3use yog_data::Schema;
4
5use crate::math::Vec2;
6
7/// A rectangle: its corner `x, y` (the one with the smallest coordinates)
8/// and its size.
9#[derive(Schema, Debug, Clone, Copy, PartialEq, Default)]
10pub struct Rect {
11    pub x: f32,
12    pub y: f32,
13    pub w: f32,
14    pub h: f32,
15}
16
17impl Rect {
18    pub const fn new(x: f32, y: f32, w: f32, h: f32) -> Rect {
19        Rect { x, y, w, h }
20    }
21
22    /// The rectangle from `x0, y0` to `x1, y1` (`x1 ≥ x0`, `y1 ≥ y0`).
23    pub const fn from_edges(x0: f32, y0: f32, x1: f32, y1: f32) -> Rect {
24        Rect {
25            x: x0,
26            y: y0,
27            w: x1 - x0,
28            h: y1 - y0,
29        }
30    }
31
32    /// The rectangle from corner `a` to corner `b`, in any order.
33    pub fn from_corners(a: Vec2, b: Vec2) -> Rect {
34        Rect {
35            x: a.x.min(b.x),
36            y: a.y.min(b.y),
37            w: (a.x - b.x).abs(),
38            h: (a.y - b.y).abs(),
39        }
40    }
41
42    pub fn right(&self) -> f32 {
43        self.x + self.w
44    }
45
46    pub fn bottom(&self) -> f32 {
47        self.y + self.h
48    }
49
50    pub fn center(&self) -> Vec2 {
51        Vec2::new(self.x + self.w / 2.0, self.y + self.h / 2.0)
52    }
53
54    pub fn contains(&self, p: Vec2) -> bool {
55        p.x >= self.x && p.y >= self.y && p.x < self.right() && p.y < self.bottom()
56    }
57
58    /// The rect shrunk by `pad` (top, right, bottom, left — `y` down), never
59    /// negative.
60    pub fn inset(&self, pad: [f32; 4]) -> Rect {
61        Rect {
62            x: self.x + pad[3],
63            y: self.y + pad[0],
64            w: (self.w - pad[1] - pad[3]).max(0.0),
65            h: (self.h - pad[0] - pad[2]).max(0.0),
66        }
67    }
68
69    /// Grown by `r` on every side.
70    pub fn outset(&self, r: f32) -> Rect {
71        Rect {
72            x: self.x - r,
73            y: self.y - r,
74            w: self.w + 2.0 * r,
75            h: self.h + 2.0 * r,
76        }
77    }
78
79    pub fn translate(&self, d: Vec2) -> Rect {
80        Rect {
81            x: self.x + d.x,
82            y: self.y + d.y,
83            ..*self
84        }
85    }
86
87    /// Every coordinate times `s`.
88    pub fn scale(&self, s: f32) -> Rect {
89        Rect::from_corners(
90            Vec2::new(self.x * s, self.y * s),
91            Vec2::new(self.right() * s, self.bottom() * s),
92        )
93    }
94
95    /// The overlap of two rects; `None` when they don't overlap.
96    pub fn intersect(&self, o: &Rect) -> Option<Rect> {
97        let (x0, y0) = (self.x.max(o.x), self.y.max(o.y));
98        let (x1, y1) = (self.right().min(o.right()), self.bottom().min(o.bottom()));
99        (x1 > x0 && y1 > y0).then_some(Rect {
100            x: x0,
101            y: y0,
102            w: x1 - x0,
103            h: y1 - y0,
104        })
105    }
106
107    pub fn union(&self, o: &Rect) -> Rect {
108        let (x, y) = (self.x.min(o.x), self.y.min(o.y));
109        Rect {
110            x,
111            y,
112            w: self.right().max(o.right()) - x,
113            h: self.bottom().max(o.bottom()) - y,
114        }
115    }
116
117    /// The smallest rectangle around `points`; `None` for none.
118    pub fn around(points: impl IntoIterator<Item = Vec2>) -> Option<Rect> {
119        let mut it = points.into_iter();
120        let first = it.next()?;
121        let (lo, hi) = it.fold((first, first), |(lo, hi), p| {
122            (
123                Vec2::new(lo.x.min(p.x), lo.y.min(p.y)),
124                Vec2::new(hi.x.max(p.x), hi.y.max(p.y)),
125            )
126        });
127        Some(Rect::from_corners(lo, hi))
128    }
129}