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#[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 #[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 #[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 #[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 #[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}