Skip to main content

blockworx_geom/
rect.rs

1use std::fmt;
2use std::ops::{BitOr, BitOrAssign, Div, Mul};
3
4use crate::{Pos2, Rangef, Vec2, midpoint, pos2};
5
6/// An axis-aligned rectangle, `min` its left-top corner and `max` its
7/// right-bottom one.
8///
9/// A rect with `max < min` on either axis is *negative*: [`Rect::NOTHING`] is
10/// the deliberate case, the seed a bounding box accumulates from, and
11/// [`Rect::area`] and the containment tests are written to behave sensibly on
12/// it. There is no `Default`: a zero rect and an empty bounding box are
13/// different intentions, so the caller says which.
14#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
15pub struct Rect {
16    /// One corner, usually the left-top one.
17    pub min: Pos2,
18
19    /// The opposing corner, usually the right-bottom one.
20    pub max: Pos2,
21}
22
23impl Rect {
24    /// Contains every point.
25    pub const EVERYTHING: Self = Self {
26        min: pos2(-f32::INFINITY, -f32::INFINITY),
27        max: pos2(f32::INFINITY, f32::INFINITY),
28    };
29
30    /// The inverse of [`Self::EVERYTHING`], containing no point. The seed a
31    /// bounding box grows from: `NOTHING.union(r) == r`.
32    pub const NOTHING: Self = Self {
33        min: pos2(f32::INFINITY, f32::INFINITY),
34        max: pos2(-f32::INFINITY, -f32::INFINITY),
35    };
36
37    pub const ZERO: Self = Self {
38        min: Pos2::ZERO,
39        max: Pos2::ZERO,
40    };
41
42    #[inline]
43    pub const fn from_min_max(min: Pos2, max: Pos2) -> Self {
44        Self { min, max }
45    }
46
47    /// Left-top corner plus a size, stretching right-down.
48    #[inline]
49    pub fn from_min_size(min: Pos2, size: Vec2) -> Self {
50        Self {
51            min,
52            max: min + size,
53        }
54    }
55
56    #[inline]
57    pub fn from_center_size(center: Pos2, size: Vec2) -> Self {
58        Self {
59            min: center - size * 0.5,
60            max: center + size * 0.5,
61        }
62    }
63
64    /// The bounding rect of the two points, in either order.
65    #[inline]
66    pub fn from_two_pos(a: Pos2, b: Pos2) -> Self {
67        Self {
68            min: pos2(a.x.min(b.x), a.y.min(b.y)),
69            max: pos2(a.x.max(b.x), a.y.max(b.y)),
70        }
71    }
72
73    /// A zero-sized rect at a point.
74    #[inline]
75    pub const fn from_pos(point: Pos2) -> Self {
76        Self {
77            min: point,
78            max: point,
79        }
80    }
81
82    /// The bounding rect of the points; [`Self::NOTHING`] when there are none.
83    pub fn from_points(points: &[Pos2]) -> Self {
84        let mut rect = Self::NOTHING;
85        for &p in points {
86            rect.extend_with(p);
87        }
88        rect
89    }
90
91    #[inline]
92    pub fn from_x_y_ranges(x_range: Rangef, y_range: Rangef) -> Self {
93        Self {
94            min: pos2(x_range.min, y_range.min),
95            max: pos2(x_range.max, y_range.max),
96        }
97    }
98
99    /// Grow by `amnt` on all four sides, keeping the center.
100    #[must_use]
101    pub fn expand(self, amnt: f32) -> Self {
102        self.expand2(Vec2::splat(amnt))
103    }
104
105    /// Grow per-axis, keeping the center.
106    #[must_use]
107    pub fn expand2(self, amnt: Vec2) -> Self {
108        Self::from_min_max(self.min - amnt, self.max + amnt)
109    }
110
111    /// Shrink by `amnt` on all four sides, keeping the center.
112    #[must_use]
113    pub fn shrink(self, amnt: f32) -> Self {
114        self.shrink2(Vec2::splat(amnt))
115    }
116
117    /// Shrink per-axis, keeping the center.
118    #[must_use]
119    pub fn shrink2(self, amnt: Vec2) -> Self {
120        Self::from_min_max(self.min + amnt, self.max - amnt)
121    }
122
123    #[must_use]
124    #[inline]
125    pub fn translate(self, amnt: Vec2) -> Self {
126        Self::from_min_size(self.min + amnt, self.size())
127    }
128
129    #[must_use]
130    #[inline]
131    pub fn intersects(self, other: Self) -> bool {
132        self.min.x <= other.max.x
133            && other.min.x <= self.max.x
134            && self.min.y <= other.max.y
135            && other.min.y <= self.max.y
136    }
137
138    /// Keeps `min`.
139    pub fn set_width(&mut self, w: f32) {
140        self.max.x = self.min.x + w;
141    }
142
143    /// Keeps `min`.
144    pub fn set_height(&mut self, h: f32) {
145        self.max.y = self.min.y + h;
146    }
147
148    /// Keeps the size.
149    pub fn set_center(&mut self, center: Pos2) {
150        *self = self.translate(center - self.center());
151    }
152
153    #[must_use]
154    #[inline]
155    pub fn contains(&self, p: Pos2) -> bool {
156        self.min.x <= p.x && p.x <= self.max.x && self.min.y <= p.y && p.y <= self.max.y
157    }
158
159    #[must_use]
160    pub fn contains_rect(&self, other: Self) -> bool {
161        self.contains(other.min) && self.contains(other.max)
162    }
163
164    /// `p` clamped inside the rect.
165    #[must_use]
166    pub fn clamp(&self, p: Pos2) -> Pos2 {
167        p.clamp(self.min, self.max)
168    }
169
170    #[inline]
171    pub fn extend_with(&mut self, p: Pos2) {
172        self.min = self.min.min(p);
173        self.max = self.max.max(p);
174    }
175
176    /// The smallest rect containing both.
177    #[inline]
178    #[must_use]
179    pub fn union(self, other: Self) -> Self {
180        Self {
181            min: self.min.min(other.min),
182            max: self.max.max(other.max),
183        }
184    }
185
186    /// The area covered by both. Negative (see [`Rect`]) when they are disjoint.
187    #[inline]
188    #[must_use]
189    pub fn intersect(self, other: Self) -> Self {
190        Self {
191            min: self.min.max(other.min),
192            max: self.max.min(other.max),
193        }
194    }
195
196    #[inline]
197    pub fn center(&self) -> Pos2 {
198        Pos2 {
199            x: midpoint(self.min.x, self.max.x),
200            y: midpoint(self.min.y, self.max.y),
201        }
202    }
203
204    #[inline]
205    pub fn size(&self) -> Vec2 {
206        self.max - self.min
207    }
208
209    /// Can be negative.
210    #[inline]
211    pub fn width(&self) -> f32 {
212        self.max.x - self.min.x
213    }
214
215    /// Can be negative.
216    #[inline]
217    pub fn height(&self) -> f32 {
218        self.max.y - self.min.y
219    }
220
221    /// Width / height: below 1 portrait, above 1 landscape.
222    pub fn aspect_ratio(&self) -> f32 {
223        self.width() / self.height()
224    }
225
226    /// Zero rather than negative for a [negative](Rect) rect.
227    #[inline]
228    pub fn area(&self) -> f32 {
229        self.width().max(0.0) * self.height().max(0.0)
230    }
231
232    /// `width < 0 || height < 0`
233    #[inline]
234    pub fn is_negative(&self) -> bool {
235        self.max.x < self.min.x || self.max.y < self.min.y
236    }
237
238    /// `width > 0 && height > 0`
239    #[inline]
240    pub fn is_positive(&self) -> bool {
241        self.min.x < self.max.x && self.min.y < self.max.y
242    }
243
244    #[inline]
245    pub fn is_finite(&self) -> bool {
246        self.min.is_finite() && self.max.is_finite()
247    }
248
249    #[inline]
250    pub fn x_range(&self) -> Rangef {
251        Rangef::new(self.min.x, self.max.x)
252    }
253
254    #[inline]
255    pub fn y_range(&self) -> Rangef {
256        Rangef::new(self.min.y, self.max.y)
257    }
258}
259
260/// Named edges and corners, assuming the origin is towards left-top.
261impl Rect {
262    #[inline]
263    pub fn left(&self) -> f32 {
264        self.min.x
265    }
266
267    #[inline]
268    pub fn right(&self) -> f32 {
269        self.max.x
270    }
271
272    #[inline]
273    pub fn top(&self) -> f32 {
274        self.min.y
275    }
276
277    #[inline]
278    pub fn bottom(&self) -> f32 {
279        self.max.y
280    }
281
282    #[inline]
283    pub fn left_top(&self) -> Pos2 {
284        pos2(self.left(), self.top())
285    }
286
287    #[inline]
288    pub fn center_top(&self) -> Pos2 {
289        pos2(self.center().x, self.top())
290    }
291
292    #[inline]
293    pub fn right_top(&self) -> Pos2 {
294        pos2(self.right(), self.top())
295    }
296
297    #[inline]
298    pub fn left_center(&self) -> Pos2 {
299        pos2(self.left(), self.center().y)
300    }
301
302    #[inline]
303    pub fn right_center(&self) -> Pos2 {
304        pos2(self.right(), self.center().y)
305    }
306
307    #[inline]
308    pub fn left_bottom(&self) -> Pos2 {
309        pos2(self.left(), self.bottom())
310    }
311
312    #[inline]
313    pub fn center_bottom(&self) -> Pos2 {
314        pos2(self.center().x, self.bottom())
315    }
316
317    #[inline]
318    pub fn right_bottom(&self) -> Pos2 {
319        pos2(self.right(), self.bottom())
320    }
321}
322
323impl From<[Pos2; 2]> for Rect {
324    #[inline]
325    fn from([min, max]: [Pos2; 2]) -> Self {
326        Self { min, max }
327    }
328}
329
330impl Mul<f32> for Rect {
331    type Output = Self;
332
333    #[inline]
334    fn mul(self, factor: f32) -> Self {
335        Self {
336            min: self.min * factor,
337            max: self.max * factor,
338        }
339    }
340}
341
342impl Mul<Rect> for f32 {
343    type Output = Rect;
344
345    #[inline]
346    fn mul(self, r: Rect) -> Rect {
347        Rect {
348            min: self * r.min,
349            max: self * r.max,
350        }
351    }
352}
353
354impl Div<f32> for Rect {
355    type Output = Self;
356
357    #[inline]
358    fn div(self, factor: f32) -> Self {
359        Self {
360            min: self.min / factor,
361            max: self.max / factor,
362        }
363    }
364}
365
366impl BitOr for Rect {
367    type Output = Self;
368
369    #[inline]
370    fn bitor(self, other: Self) -> Self {
371        self.union(other)
372    }
373}
374
375impl BitOrAssign for Rect {
376    #[inline]
377    fn bitor_assign(&mut self, other: Self) {
378        *self = self.union(other);
379    }
380}
381
382impl fmt::Debug for Rect {
383    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384        if let Some(precision) = f.precision() {
385            write!(f, "[{1:.0$?} - {2:.0$?}]", precision, self.min, self.max)
386        } else {
387            write!(f, "[{:?} - {:?}]", self.min, self.max)
388        }
389    }
390}
391
392impl fmt::Display for Rect {
393    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394        write!(f, "[{} - {}]", self.min, self.max)
395    }
396}