1use nalgebra::Scalar;
2use num_traits::{FromPrimitive, Num};
3use serde::{Deserialize, Serialize};
4
5use super::{Point2, Vector2};
6use crate::cartesian::CartesianPoint2d;
7use crate::impls::ClosedContour;
8
9#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd, Hash, Deserialize, Serialize)]
11pub struct Rect<N = f64> {
12 x_min: N,
13 y_min: N,
14 x_max: N,
15 y_max: N,
16}
17
18impl<N> Rect<N>
19where
20 N: Num + Copy + PartialOrd + Scalar + FromPrimitive,
21{
22 pub fn new(x_min: N, y_min: N, x_max: N, y_max: N) -> Self {
24 let (x_min, x_max) = if x_min > x_max {
25 (x_max, x_min)
26 } else {
27 (x_min, x_max)
28 };
29 let (y_min, y_max) = if y_min > y_max {
30 (y_max, y_min)
31 } else {
32 (y_min, y_max)
33 };
34
35 Self {
36 x_min,
37 y_min,
38 x_max,
39 y_max,
40 }
41 }
42
43 pub fn x_min(&self) -> N {
45 self.x_min
46 }
47
48 pub fn x_max(&self) -> N {
50 self.x_max
51 }
52
53 pub fn y_min(&self) -> N {
55 self.y_min
56 }
57
58 pub fn y_max(&self) -> N {
60 self.y_max
61 }
62
63 pub fn width(&self) -> N {
65 self.x_max - self.x_min
66 }
67
68 pub fn height(&self) -> N {
70 self.y_max - self.y_min
71 }
72
73 pub fn half_width(&self) -> N {
75 self.width() / N::from_f64(2.0).expect("const conversion")
76 }
77
78 pub fn half_height(&self) -> N {
80 self.height() / N::from_f64(2.0).expect("const conversion")
81 }
82
83 pub fn into_contour(self) -> ClosedContour<Point2<N>> {
85 ClosedContour::new(Vec::from(self.into_quadrangle()))
86 }
87
88 pub fn shrink(&self, amount: N) -> Self {
91 let amount_x = if amount <= self.half_width() {
92 amount
93 } else {
94 self.half_width()
95 };
96 let amount_y = if amount <= self.half_height() {
97 amount
98 } else {
99 self.half_height()
100 };
101
102 Self {
103 x_min: self.x_min + amount_x,
104 x_max: self.x_max - amount_x,
105 y_min: self.y_min + amount_y,
106 y_max: self.y_max - amount_y,
107 }
108 }
109
110 pub fn shift(&self, dx: N, dy: N) -> Self {
112 Self {
113 x_min: self.x_min + dx,
114 x_max: self.x_max + dx,
115 y_min: self.y_min + dy,
116 y_max: self.y_max + dy,
117 }
118 }
119
120 pub fn merge(&self, other: Self) -> Self {
122 Self {
123 x_min: if self.x_min < other.x_min {
124 self.x_min
125 } else {
126 other.x_min
127 },
128 y_min: if self.y_min < other.y_min {
129 self.y_min
130 } else {
131 other.y_min
132 },
133 x_max: if self.x_max > other.x_max {
134 self.x_max
135 } else {
136 other.x_max
137 },
138 y_max: if self.y_max > other.y_max {
139 self.y_max
140 } else {
141 other.y_max
142 },
143 }
144 }
145
146 pub fn from_point(p: &impl CartesianPoint2d<Num = N>) -> Self {
148 Self {
149 x_min: p.x(),
150 x_max: p.x(),
151 y_min: p.y(),
152 y_max: p.y(),
153 }
154 }
155
156 pub fn from_points<'a, P: CartesianPoint2d<Num = N> + 'a>(
160 points: impl IntoIterator<Item = P>,
161 ) -> Option<Self> {
162 let mut iterator = points.into_iter();
163 let first = iterator.next()?;
164 let mut x_min = first.x();
165 let mut y_min = first.y();
166 let mut x_max = first.x();
167 let mut y_max = first.y();
168
169 for p in iterator {
170 if x_min > p.x() {
171 x_min = p.x();
172 }
173 if y_min > p.y() {
174 y_min = p.y();
175 }
176 if x_max < p.x() {
177 x_max = p.x();
178 }
179 if y_max < p.y() {
180 y_max = p.y();
181 }
182 }
183
184 Some(Self {
185 x_min,
186 y_min,
187 x_max,
188 y_max,
189 })
190 }
191
192 pub fn contains(&self, point: &impl CartesianPoint2d<Num = N>) -> bool {
194 self.x_min <= point.x()
195 && self.x_max >= point.x()
196 && self.y_min <= point.y()
197 && self.y_max >= point.y()
198 }
199
200 pub fn magnify(&self, factor: N) -> Self {
203 let two = N::from_f64(2.0).expect("const conversion failed");
204 let cx = (self.x_min + self.x_max) / two;
205 let cy = (self.y_min + self.y_max) / two;
206 let half_width = self.width() / two * factor;
207 let half_height = self.height() / two * factor;
208 Self {
209 x_min: cx - half_width,
210 x_max: cx + half_width,
211 y_min: cy - half_height,
212 y_max: cy + half_height,
213 }
214 }
215
216 pub fn limit(&self, other: Self) -> Self {
218 Self {
219 x_min: if self.x_min > other.x_min {
220 self.x_min
221 } else {
222 other.x_min
223 },
224 y_min: if self.y_min > other.y_min {
225 self.y_min
226 } else {
227 other.y_min
228 },
229 x_max: if self.x_max < other.x_max {
230 self.x_max
231 } else {
232 other.x_max
233 },
234 y_max: if self.y_max < other.y_max {
235 self.y_max
236 } else {
237 other.y_max
238 },
239 }
240 }
241
242 pub fn center(&self) -> Point2<N> {
244 Point2::new(
245 (self.x_min + self.x_max) / N::from_f64(2.0).expect("const conversion failed"),
246 (self.y_min + self.y_max) / N::from_f64(2.0).expect("const conversion failed"),
247 )
248 }
249
250 pub fn into_quadrangle(self) -> [Point2<N>; 4] {
258 [
259 Point2::new(self.x_min, self.y_min),
260 Point2::new(self.x_min, self.y_max),
261 Point2::new(self.x_max, self.y_max),
262 Point2::new(self.x_max, self.y_min),
263 ]
264 }
265
266 pub fn intersects(&self, other: Rect<N>) -> bool {
268 self.x_max >= other.x_min
269 && self.x_min <= other.x_max
270 && self.y_max >= other.y_min
271 && self.y_min <= other.y_max
272 }
273}
274
275impl<N: Num + Copy + PartialOrd + Scalar + FromPrimitive> FromIterator<Rect<N>>
276 for Option<Rect<N>>
277{
278 fn from_iter<T: IntoIterator<Item = Rect<N>>>(iter: T) -> Self {
279 let mut iter = iter.into_iter();
280 let mut prev = iter.next()?;
281 for next in iter {
282 prev = prev.merge(next);
283 }
284
285 Some(prev)
286 }
287}
288
289impl<N> std::ops::Add<Vector2<N>> for Rect<N>
290where
291 N: Num + Copy + PartialOrd + Scalar + FromPrimitive,
292{
293 type Output = Rect<N>;
294
295 fn add(self, rhs: Vector2<N>) -> Self::Output {
296 Self {
297 x_min: self.x_min + rhs.dx(),
298 y_min: self.y_min + rhs.dy(),
299 x_max: self.x_max + rhs.dx(),
300 y_max: self.y_max + rhs.dy(),
301 }
302 }
303}
304
305impl<N> std::ops::Sub<Vector2<N>> for Rect<N>
306where
307 N: Num + Copy + PartialOrd + Scalar + FromPrimitive,
308{
309 type Output = Rect<N>;
310
311 fn sub(self, rhs: Vector2<N>) -> Self::Output {
312 Self {
313 x_min: self.x_min - rhs.dx(),
314 y_min: self.y_min - rhs.dy(),
315 x_max: self.x_max - rhs.dx(),
316 y_max: self.y_max - rhs.dy(),
317 }
318 }
319}