bracket_geometry/
rect.rs

1use crate::prelude::Point;
2use std::collections::HashSet;
3use std::convert::TryInto;
4use std::ops;
5
6/// Defines a two-dimensional rectangle.
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[derive(PartialEq, Eq, Copy, Clone, Debug)]
9pub struct Rect {
10    /// The X position of the first point (typically the left)
11    pub x1: i32,
12    /// The X position of the second point (typically the right)
13    pub x2: i32,
14    /// The Y position of the first point (typically the top)
15    pub y1: i32,
16    /// The Y position of the second point (typically the bottom)
17    pub y2: i32,
18}
19
20#[cfg(feature = "specs")]
21impl specs::prelude::Component for Rect {
22    type Storage = specs::prelude::VecStorage<Self>;
23}
24
25impl Default for Rect {
26    fn default() -> Rect {
27        Rect::zero()
28    }
29}
30
31impl Rect {
32    /// Create a new rectangle, specifying X/Y Width/Height
33    pub fn with_size<T>(x: T, y: T, w: T, h: T) -> Rect
34    where
35        T: TryInto<i32>,
36    {
37        let x_i32: i32 = x.try_into().ok().unwrap();
38        let y_i32: i32 = y.try_into().ok().unwrap();
39        Rect {
40            x1: x_i32,
41            y1: y_i32,
42            x2: x_i32 + w.try_into().ok().unwrap(),
43            y2: y_i32 + h.try_into().ok().unwrap(),
44        }
45    }
46
47    /// Create a new rectangle, specifying exact dimensions
48    pub fn with_exact<T>(x1: T, y1: T, x2: T, y2: T) -> Rect
49    where
50        T: TryInto<i32>,
51    {
52        Rect {
53            x1: x1.try_into().ok().unwrap(),
54            y1: y1.try_into().ok().unwrap(),
55            x2: x2.try_into().ok().unwrap(),
56            y2: y2.try_into().ok().unwrap(),
57        }
58    }
59
60    /// Creates a zero rectangle
61    pub fn zero() -> Rect {
62        Rect {
63            x1: 0,
64            y1: 0,
65            x2: 0,
66            y2: 0,
67        }
68    }
69
70    /// Returns true if this overlaps with other
71    #[must_use]
72    pub fn intersect(&self, other: &Rect) -> bool {
73        self.x1 <= other.x2 && self.x2 >= other.x1 && self.y1 <= other.y2 && self.y2 >= other.y1
74    }
75
76    /// Returns the center of the rectangle
77    #[must_use]
78    pub fn center(&self) -> Point {
79        Point::new((self.x1 + self.x2) / 2, (self.y1 + self.y2) / 2)
80    }
81
82    /// Returns true if a point is inside the rectangle
83    #[must_use]
84    pub fn point_in_rect(&self, point: Point) -> bool {
85        point.x >= self.x1 && point.x < self.x2 && point.y >= self.y1 && point.y < self.y2
86    }
87
88    /// Calls a function for each x/y point in the rectangle
89    pub fn for_each<F>(&self, mut f: F)
90    where
91        F: FnMut(Point),
92    {
93        for y in self.y1..self.y2 {
94            for x in self.x1..self.x2 {
95                f(Point::new(x, y));
96            }
97        }
98    }
99
100    /// Gets a set of all tiles in the rectangle
101    #[must_use]
102    pub fn point_set(&self) -> HashSet<Point> {
103        let mut result = HashSet::new();
104        for y in self.y1..self.y2 {
105            for x in self.x1..self.x2 {
106                result.insert(Point::new(x, y));
107            }
108        }
109        result
110    }
111
112    /// Returns the rectangle's width
113    #[must_use]
114    pub fn width(&self) -> i32 {
115        i32::abs(self.x2 - self.x1)
116    }
117
118    /// Returns the rectangle's height
119    #[must_use]
120    pub fn height(&self) -> i32 {
121        i32::abs(self.y2 - self.y1)
122    }
123}
124
125impl ops::Add<Rect> for Rect {
126    type Output = Rect;
127    fn add(mut self, rhs: Rect) -> Rect {
128        let w = self.width();
129        let h = self.height();
130        self.x1 += rhs.x1;
131        self.x2 = self.x1 + w;
132        self.y1 += rhs.y1;
133        self.y2 = self.y1 + h;
134        self
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use crate::prelude::{Point, Rect};
141
142    #[test]
143    fn test_dimensions() {
144        let rect = Rect::with_size(0, 0, 10, 10);
145        assert!(rect.width() == 10);
146        assert!(rect.height() == 10);
147    }
148
149    #[test]
150    fn test_add() {
151        let rect = Rect::with_size(0, 0, 10, 10) + Rect::with_size(1, 1, 1, 1);
152        assert!(rect.x1 == 1 && rect.y1 == 1);
153        assert!(rect.x2 == 11 && rect.y2 == 11);
154    }
155
156    #[test]
157    fn test_intersect() {
158        let r1 = Rect::with_size(0, 0, 10, 10);
159        let r2 = Rect::with_size(5, 5, 10, 10);
160        let r3 = Rect::with_size(100, 100, 5, 5);
161        assert!(r1.intersect(&r2));
162        assert!(!r1.intersect(&r3));
163    }
164
165    #[test]
166    fn test_center() {
167        let r1 = Rect::with_size(0, 0, 10, 10);
168        let center = r1.center();
169        assert!(center.x == 5 && center.y == 5);
170    }
171
172    #[test]
173    fn test_point_in_rect() {
174        let r1 = Rect::with_size(0, 0, 10, 10);
175        assert!(r1.point_in_rect(Point::new(5, 5)));
176        assert!(!r1.point_in_rect(Point::new(100, 100)));
177    }
178
179    #[test]
180    fn test_rect_set() {
181        let r1 = Rect::with_size(0, 0, 1, 1);
182        let points = r1.point_set();
183        assert!(points.contains(&Point::new(0, 0)));
184        assert!(!points.contains(&Point::new(1, 0)));
185        assert!(!points.contains(&Point::new(0, 1)));
186        assert!(!points.contains(&Point::new(1, 1)));
187    }
188
189    #[test]
190    fn test_rect_callback() {
191        use std::collections::HashSet;
192
193        let r1 = Rect::with_size(0, 0, 1, 1);
194        let mut points: HashSet<Point> = HashSet::new();
195        r1.for_each(|p| {
196            points.insert(p);
197        });
198        assert!(points.contains(&Point::new(0, 0)));
199        assert!(!points.contains(&Point::new(1, 0)));
200        assert!(!points.contains(&Point::new(0, 1)));
201        assert!(!points.contains(&Point::new(1, 1)));
202    }
203}