graphics_shapes/
rect.rs

1use crate::general_math::rotate_points;
2use crate::new_hash_set;
3use crate::prelude::*;
4use crate::shape_box::ShapeBox;
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7use std::ops::Div;
8
9/// Rectangle
10///
11/// Must have flat edges, to rotate first convert to [Polygon] using [Rect::as_polygon()]
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
13#[derive(Debug, Clone, Eq, PartialEq, Hash)]
14pub struct Rect {
15    top_left: Coord,
16    bottom_right: Coord,
17}
18
19impl IntersectsContains for Rect {}
20
21impl Rect {
22    #[must_use]
23    pub fn new<P1: Into<Coord>, P2: Into<Coord>>(top_left: P1, bottom_right: P2) -> Self {
24        Self {
25            top_left: top_left.into(),
26            bottom_right: bottom_right.into(),
27        }
28    }
29
30    #[must_use]
31    pub fn new_with_size<P: Into<Coord>>(start: P, width: usize, height: usize) -> Self {
32        let top_left = start.into();
33        let bottom_right = Coord {
34            x: top_left.x + width as isize,
35            y: top_left.y + height as isize,
36        };
37        Self {
38            top_left,
39            bottom_right,
40        }
41    }
42}
43
44impl Rect {
45    #[must_use]
46    pub fn width(&self) -> usize {
47        (self.bottom_right.x - self.top_left.x).unsigned_abs()
48    }
49
50    #[must_use]
51    pub fn height(&self) -> usize {
52        (self.bottom_right.y - self.top_left.y).unsigned_abs()
53    }
54
55    #[inline]
56    #[must_use]
57    pub fn top_left(&self) -> Coord {
58        self.top_left
59    }
60
61    #[inline]
62    #[must_use]
63    pub fn bottom_right(&self) -> Coord {
64        self.bottom_right
65    }
66
67    #[inline]
68    #[must_use]
69    pub fn is_square(&self) -> bool {
70        let diff = self.bottom_right - self.top_left;
71        diff.x == diff.y
72    }
73}
74
75impl Shape for Rect {
76    fn from_points(points: &[Coord]) -> Self
77    where
78        Self: Sized,
79    {
80        Rect::new(points[0], points[1])
81    }
82
83    fn rebuild(&self, points: &[Coord]) -> Self
84    where
85        Self: Sized,
86    {
87        Rect::from_points(points)
88    }
89
90    fn contains(&self, point: Coord) -> bool {
91        (self.left()..=self.right()).contains(&point.x)
92            && (self.top()..=self.bottom()).contains(&point.y)
93    }
94
95    fn points(&self) -> Vec<Coord> {
96        vec![self.top_left, self.bottom_right]
97    }
98
99    fn rotate_around(&self, degrees: isize, point: Coord) -> Self
100    where
101        Self: Sized,
102    {
103        let degrees = (degrees as f32 / 90.0).round() as isize;
104        let points = rotate_points(point, &self.points(), degrees * 90);
105        Self::from_points(&points)
106    }
107
108    fn center(&self) -> Coord {
109        self.top_left.mid_point(self.bottom_right)
110    }
111
112    fn left(&self) -> isize {
113        self.top_left.x.min(self.bottom_right.x)
114    }
115
116    fn right(&self) -> isize {
117        self.top_left.x.max(self.bottom_right.x)
118    }
119
120    fn top(&self) -> isize {
121        self.top_left.y.min(self.bottom_right.y)
122    }
123
124    fn bottom(&self) -> isize {
125        self.top_left.y.max(self.bottom_right.y)
126    }
127
128    fn outline_pixels(&self) -> Vec<Coord> {
129        let mut output = new_hash_set();
130
131        let left = self.left();
132        let right = self.right();
133        let top = self.top();
134        let bottom = self.bottom();
135
136        for x in left..=right {
137            output.insert(coord!(x, top));
138            output.insert(coord!(x, bottom));
139        }
140        for y in top..=bottom {
141            output.insert(coord!(left, y));
142            output.insert(coord!(right, y));
143        }
144
145        output.into_iter().collect()
146    }
147
148    fn filled_pixels(&self) -> Vec<Coord> {
149        let mut output = new_hash_set();
150
151        let left = self.left();
152        let right = self.right();
153        let top = self.top();
154        let bottom = self.bottom();
155
156        for y in top..=bottom {
157            for x in left..=right {
158                output.insert(coord!(x, y));
159            }
160        }
161
162        output.into_iter().collect()
163    }
164
165    fn to_shape_box(&self) -> ShapeBox {
166        ShapeBox::Rect(self.clone())
167    }
168}
169
170impl Rect {
171    /// Create a circle around the center to the closest edge
172    #[must_use]
173    pub fn as_inner_circle(&self) -> Circle {
174        let radius = self.width().div(2).min(self.height().div(2));
175        Circle::new(self.center(), radius)
176    }
177
178    /// Create a circle around the center to the farthest edge
179    #[must_use]
180    pub fn as_outer_circle(&self) -> Circle {
181        let radius = self.width().div(2).max(self.height().div(2));
182        Circle::new(self.center(), radius)
183    }
184
185    /// Create two triangles (top left and bottom right right angles)
186    #[must_use]
187    pub fn as_triangles(&self) -> (Triangle, Triangle) {
188        let top_right = coord!(self.right(), self.top());
189        let bottom_left = coord!(self.left(), self.bottom());
190        (
191            Triangle::new(self.top_left(), top_right, bottom_left),
192            Triangle::new(self.bottom_right(), top_right, bottom_left),
193        )
194    }
195
196    /// Same shape but represented as four points/lines instead of two points
197    #[must_use]
198    pub fn as_polygon(&self) -> Polygon {
199        let top_right = coord!(self.right(), self.top());
200        let bottom_left = coord!(self.left(), self.bottom());
201        Polygon::new(&[self.top_left, top_right, self.bottom_right, bottom_left])
202    }
203
204    #[must_use]
205    pub fn as_outer_ellipse(&self) -> Ellipse {
206        Ellipse::new(self.center(), self.width(), self.height())
207    }
208
209    #[must_use]
210    pub fn as_lines(&self) -> [Line; 4] {
211        [
212            Line::new(self.top_left(), self.top_right()),
213            Line::new(self.top_right(), self.bottom_right()),
214            Line::new(self.bottom_right(), self.bottom_left()),
215            Line::new(self.bottom_left(), self.top_left()),
216        ]
217    }
218}
219
220#[cfg(test)]
221mod test {
222    use super::*;
223    use crate::test::check_points;
224
225    mod rotation {
226        use crate::rect::Rect;
227        use crate::Shape;
228
229        #[test]
230        fn rotate_square_around_bottom_right_corner_90_degrees_twice() {
231            let square = Rect::new((0, 0), (20, 20));
232            let rotated = square.rotate_around(90, coord!(20, 20));
233
234            assert_eq!(rotated.points(), coord_vec![(40, 0), (20, 20)]);
235
236            let rotated_again = rotated.rotate_around(90, coord!(20, 20));
237
238            assert_eq!(rotated_again.points(), coord_vec![(40, 40), (20, 20)])
239        }
240
241        #[test]
242        fn rotate_rect_around_center_90_degrees() {
243            let rect = Rect::new((0, 0), (40, 20));
244            let rotated = rect.rotate(90);
245
246            assert_eq!(rotated.points(), coord_vec![(30, -10), (10, 30)]);
247        }
248    }
249
250    #[test]
251    fn basic_outline() {
252        let rect = Rect::new((0, 0), (4, 4));
253        let points = rect.outline_pixels();
254        check_points(
255            &[
256                (0, 0),
257                (1, 0),
258                (2, 0),
259                (3, 0),
260                (4, 0),
261                (0, 1),
262                (4, 1),
263                (0, 2),
264                (4, 2),
265                (0, 3),
266                (4, 3),
267                (0, 4),
268                (1, 4),
269                (2, 4),
270                (3, 4),
271                (4, 4),
272            ],
273            &points,
274        );
275    }
276
277    #[test]
278    fn basic_filled() {
279        let rect = Rect::new((3, 2), (6, 4));
280        let points = rect.filled_pixels();
281        check_points(
282            &[
283                (3, 2),
284                (4, 2),
285                (5, 2),
286                (6, 2),
287                (3, 3),
288                (4, 3),
289                (5, 3),
290                (6, 3),
291                (3, 4),
292                (4, 4),
293                (5, 4),
294                (6, 4),
295            ],
296            &points,
297        );
298    }
299
300    #[test]
301    fn move_center() {
302        let rect = Rect::new((100, 100), (120, 120));
303        let moved = rect.move_center_to(coord!(50, 50));
304
305        assert_eq!(rect.center(), coord!(110, 110));
306        assert_eq!(moved.center(), coord!(50, 50));
307    }
308}