galileo_types/cartesian/
rect.rs

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/// Rectangle in 2d cartesian coordinate space.
10#[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    /// Creates a new rectangle.
23    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    /// X min.
44    pub fn x_min(&self) -> N {
45        self.x_min
46    }
47
48    /// X max.
49    pub fn x_max(&self) -> N {
50        self.x_max
51    }
52
53    /// Y min.
54    pub fn y_min(&self) -> N {
55        self.y_min
56    }
57
58    /// Y max.
59    pub fn y_max(&self) -> N {
60        self.y_max
61    }
62
63    /// Width of the rectangle. Guaranteed to be non-negative.
64    pub fn width(&self) -> N {
65        self.x_max - self.x_min
66    }
67
68    /// Height of the rectangle. Guaranteed to be non-negative.
69    pub fn height(&self) -> N {
70        self.y_max - self.y_min
71    }
72
73    /// Half of the width of the rectangle. Guaranteed to be non-negative.
74    pub fn half_width(&self) -> N {
75        self.width() / N::from_f64(2.0).expect("const conversion")
76    }
77
78    /// Half of the height of the rectangle. Guaranteed to be non-negative.
79    pub fn half_height(&self) -> N {
80        self.height() / N::from_f64(2.0).expect("const conversion")
81    }
82
83    /// Converts the rectangle into a closed contour of points.
84    pub fn into_contour(self) -> ClosedContour<Point2<N>> {
85        ClosedContour::new(Vec::from(self.into_quadrangle()))
86    }
87
88    /// Moves the boundaries of the rectangle by `amount` inside (outside, if the `amount` is negative). If the
89    /// width or height of the resulting rectangle are negative, they are set to 0.
90    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    /// Adds the given amount to the coordinates of the rectangle.
111    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    /// Creates a new rectangle with the boundaries of this and other one.
121    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    /// Creates a zero-area rectangle from the point.
147    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    /// Returns a minimum rectangle that contains all the points in the iterator.
157    ///
158    /// Returns `None` if the iterator is empty.
159    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    /// Returns `true` if the point is inside (or on a side) of the rectagle.
193    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    /// Changes the width and height of the rectangle by the factor of `factor`, keeping the center of the rectangle
201    /// at the same place.
202    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    /// Returns a new rectangle, boundaries of which are inside of boundaries of this and the `other` rectangles.
217    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    /// Returns the center point of the rectangle.
243    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    /// Returns a set of 4 points - corners of the rectangle.
251    ///
252    /// The order of points is:
253    /// 1. Left bottom
254    /// 2. Left top
255    /// 3. Right top
256    /// 4. Right bottom
257    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    /// Returns true if two rectangle have at least one common point.
267    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}