rust_rectangle_dividing/
axis_aligned_rectangle.rs

1use num_traits::{Float, Num, NumAssignOps, NumOps};
2
3use crate::area::Area;
4use crate::aspect_ratio::AspectRatio;
5use crate::axis::{Axis, SizeForAxis};
6use crate::component::Component;
7use crate::dividing::VerticalDividingHelper;
8use crate::point::{Edge, Point};
9use crate::rectangle::{Rectangle, RectangleSize};
10use crate::rotate::QuarterRotation;
11
12/// axis aligned starting at x, y and ending at x + width, y + height (left to right, top to bottom)
13#[derive(Debug, PartialEq, Clone)]
14pub struct AxisAlignedRectangle<T>
15where
16    T: Copy + Num + NumAssignOps + NumOps,
17{
18    pub point: Point<T>,
19    pub rectangle: Rectangle<T>,
20}
21
22impl<T> AxisAlignedRectangle<T>
23where
24    T: Copy + Num + NumAssignOps + NumOps + PartialOrd + Float,
25{
26    pub fn round(&self) -> Self {
27        let p1 = self.edge_left_top().round(Edge::RightBottom);
28        let p2 = self.edge_right_bottom().round(Edge::LeftTop);
29        let width = p2.x() - p1.x();
30        let height = p2.y() - p1.y();
31        let rect = Rectangle::new(width, height);
32        Self::new(&p1, &rect)
33    }
34}
35
36impl<T> SizeForAxis<T> for AxisAlignedRectangle<T>
37where
38    T: Copy + Num + NumAssignOps,
39{
40    fn size_for_axis(&self, axis: Axis) -> T {
41        self.rectangle.size_for_axis(axis)
42    }
43}
44
45/// A rectangle in 2D space with a width and height
46impl<T> RectangleSize<T> for AxisAlignedRectangle<T>
47where
48    T: Copy + Num + NumAssignOps + NumOps,
49{
50    fn width(&self) -> T {
51        self.rectangle.width()
52    }
53    fn height(&self) -> T {
54        self.rectangle.height()
55    }
56}
57
58impl<T> Component<T> for AxisAlignedRectangle<T>
59where
60    T: Copy + Num + NumAssignOps + NumOps,
61{
62    fn x(&self) -> T {
63        self.point.x()
64    }
65
66    fn y(&self) -> T {
67        self.point.y()
68    }
69}
70
71impl<T> AxisAlignedRectangle<T>
72where
73    T: Copy + Num + NumAssignOps + NumOps,
74{
75    /// A rectangle in 2D space constructor
76    pub fn new(point: &Point<T>, rectangle: &Rectangle<T>) -> Self {
77        Self {
78            point: *point,
79            rectangle: *rectangle,
80        }
81    }
82
83    pub fn rect(&self) -> Rectangle<T> {
84        self.rectangle
85    }
86
87    pub fn origin(&self) -> Point<T> {
88        self.point
89    }
90}
91
92impl<T> AxisAlignedRectangle<T>
93where
94    T: Copy + Num + NumAssignOps + NumOps + Float,
95{
96    pub fn from_two_point(p1: &Point<T>, p2: &Point<T>) -> Self {
97        let vec = *p1 - *p2;
98        let width = vec.x().abs();
99        let height = vec.y().abs();
100        let rect = Rectangle::new(width, height);
101
102        Self::new(p1, &rect)
103    }
104}
105
106impl<T> AspectRatio<T> for AxisAlignedRectangle<T>
107where
108    T: Copy + Num + NumAssignOps + NumOps,
109{
110    fn aspect_ratio(&self) -> T {
111        self.rectangle.aspect_ratio()
112    }
113}
114
115impl<T> AxisAlignedRectangle<T>
116where
117    T: Copy + Num + NumAssignOps + NumOps + PartialOrd,
118{
119    pub(crate) fn edge_left_top(&self) -> Point<T> {
120        self.point
121    }
122    pub(crate) fn edge_right_top(&self) -> Point<T> {
123        Point::new(self.point.x() + self.rectangle.width(), self.point.y())
124    }
125    pub(crate) fn edge_left_bottom(&self) -> Point<T> {
126        Point::new(self.point.x(), self.point.y() + self.rectangle.height())
127    }
128    pub(crate) fn edge_right_bottom(&self) -> Point<T> {
129        Point::new(
130            self.point.x() + self.rectangle.width(),
131            self.point.y() + self.rectangle.height(),
132        )
133    }
134
135    #[allow(dead_code)]
136    pub(crate) fn edges(&self) -> Vec<Point<T>> {
137        vec![
138            self.edge_left_top(),
139            self.edge_right_top(),
140            self.edge_right_bottom(),
141            self.edge_left_bottom(),
142        ]
143    }
144
145    #[allow(dead_code)]
146    pub(crate) fn includes(&self, p: &Point<T>) -> bool {
147        p.x() > self.point.x()
148            && p.x() < self.point.x() + self.rectangle.width()
149            && p.y() > self.point.y()
150            && p.y() < self.point.y() + self.rectangle.height()
151    }
152
153    #[allow(dead_code)]
154    pub(crate) fn includes_or_on_the_boundary(&self, p: &Point<T>) -> bool {
155        p.x() >= self.point.x()
156            && p.x() <= self.point.x() + self.rectangle.width()
157            && p.y() >= self.point.y()
158            && p.y() <= self.point.y() + self.rectangle.height()
159    }
160
161    #[allow(dead_code)]
162    pub(crate) fn overlaps(&self, other: &Self) -> bool {
163        // if any of the edges of the other rectangle are inside this rectangle, then they overlap
164        other.edges().iter().any(|p| self.includes(p))
165    }
166
167    #[allow(dead_code)]
168    pub(crate) fn enclodes(&self, other: &Self) -> bool {
169        // if all of the edges of the other rectangle are inside this rectangle, then they are enclosed
170        other
171            .edges()
172            .iter()
173            .all(|p| self.includes_or_on_the_boundary(p))
174    }
175}
176
177/// area of an axis aligned rectangle
178impl<T> Area<T> for AxisAlignedRectangle<T>
179where
180    T: Copy + Num + NumAssignOps,
181{
182    fn area(&self) -> T {
183        self.rectangle.area()
184    }
185}
186
187/// Rotate an axis aligned rectangle by 90 degrees
188impl<T> QuarterRotation for AxisAlignedRectangle<T>
189where
190    T: Copy + Num + NumAssignOps,
191{
192    fn rotate_clockwise(&self) -> Self {
193        Self::new(
194            &Point::new(self.y(), self.x()),
195            &Rectangle::new(self.height(), self.width()),
196        )
197    }
198}
199
200impl<T> VerticalDividingHelper<T> for AxisAlignedRectangle<T>
201where
202    T: Copy + Num + NumAssignOps + NumOps,
203{
204    /// dividing a rectangle into two rectangles (vertical)
205    fn divide_vertical_helper(&self, x: T) -> (AxisAlignedRectangle<T>, AxisAlignedRectangle<T>) {
206        (
207            Self::new(
208                &Point::new(self.x(), self.y()),
209                &Rectangle::new(x, self.height()),
210            ),
211            Self::new(
212                &Point::new(self.x() + x, self.y()),
213                &Rectangle::new(self.width() - x, self.height()),
214            ),
215        )
216    }
217}
218
219#[cfg(test)]
220mod tests {
221
222    use super::*;
223
224    #[test]
225    fn test_new() {
226        let point = Point::new(2, 3);
227        let rect = Rectangle::new(4, 5);
228        let result = AxisAlignedRectangle::new(&point, &rect);
229        assert_eq!(result.origin(), point);
230        assert_eq!(result.rect(), rect);
231        assert_eq!(result.x(), 2);
232        assert_eq!(result.y(), 3);
233        assert_eq!(result.width(), 4);
234        assert_eq!(result.height(), 5);
235    }
236
237    #[test]
238    fn test_rotate() {
239        let point = Point::new(2, 3);
240        let rect = Rectangle::new(4, 5);
241        let result = AxisAlignedRectangle::new(&point, &rect).rotate_clockwise();
242        assert_eq!(result.origin(), Point::new(3, 2));
243        assert_eq!(result.rect(), Rectangle::new(5, 4));
244    }
245
246    #[test]
247    fn test_area() {
248        let point = Point::new(2, 3);
249        let rect = Rectangle::new(4, 5);
250        let result = AxisAlignedRectangle::new(&point, &rect).area();
251        assert_eq!(result, 20);
252    }
253
254    #[test]
255    fn test_edges() {
256        let point = Point::new(2, 3);
257        let rect = Rectangle::new(4, 5);
258        let result = AxisAlignedRectangle::new(&point, &rect).edges();
259        assert_eq!(result.len(), 4);
260        assert_eq!(result[0], point);
261        assert_eq!(result[1], Point::new(6, 3));
262        assert_eq!(result[2], Point::new(6, 8));
263        assert_eq!(result[3], Point::new(2, 8));
264    }
265
266    #[test]
267    fn test_include() {
268        let point = Point::new(2, 3);
269        let rect = Rectangle::new(4, 5);
270        let a_rect = AxisAlignedRectangle::new(&point, &rect);
271        assert!(!a_rect.includes(&Point::new(1, 3)));
272        assert!(!a_rect.includes(&Point::new(7, 3)));
273        assert!(!a_rect.includes(&Point::new(6, 2)));
274        assert!(!a_rect.includes(&Point::new(6, 9)));
275    }
276
277    #[test]
278    fn test_overlaps() {
279        let point = Point::new(2, 3);
280        let rect = Rectangle::new(4, 5);
281        let a_rect = AxisAlignedRectangle::new(&point, &rect);
282        assert!(a_rect.overlaps(&AxisAlignedRectangle::new(
283            &Point::new(1, 2),
284            &Rectangle::new(4, 5)
285        )));
286        assert!(a_rect.overlaps(&AxisAlignedRectangle::new(
287            &Point::new(1, 4),
288            &Rectangle::new(4, 5)
289        )));
290        assert!(a_rect.overlaps(&AxisAlignedRectangle::new(
291            &Point::new(1, 5),
292            &Rectangle::new(4, 5)
293        )));
294        assert!(a_rect.overlaps(&AxisAlignedRectangle::new(
295            &Point::new(1, 6),
296            &Rectangle::new(4, 5)
297        )));
298    }
299}