word_cloud/geometry/
rectangle.rs

1use rand::Rng;
2use std::fmt::{Debug, Display, Result};
3
4pub type Vector = (u32, u32);
5
6#[derive(PartialEq)]
7pub struct Rectangle {
8    pos: Vector,
9    size: Vector,
10}
11
12impl Debug for Rectangle {
13    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
14        write!(
15            f,
16            "Rectangle[{}, {}] x [{}, {}]",
17            self.pos.0, self.pos.1, self.size.0, self.size.1
18        )
19    }
20}
21
22impl Display for Rectangle {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
24        Debug::fmt(&self, f)
25    }
26}
27
28impl Clone for Rectangle {
29    fn clone(&self) -> Self {
30        Self {
31            pos: self.pos,
32            size: self.size,
33        }
34    }
35}
36
37impl Rectangle {
38    pub fn new(x: u32, y: u32, width: u32, height: u32) -> Rectangle {
39        Rectangle {
40            pos: (x, y),
41            size: (width, height),
42        }
43    }
44
45    pub fn random(minmax_width: Vector, minmax_height: Vector) -> Option<Rectangle> {
46        if (minmax_width.0 >= minmax_width.1) || (minmax_height.0 >= minmax_height.1) {
47            return None;
48        }
49
50        let mut generator = rand::thread_rng();
51
52        Some(Rectangle {
53            pos: (0, 0),
54            size: (
55                generator.gen_range(minmax_width.0..=minmax_width.1),
56                generator.gen_range(minmax_height.0..=minmax_height.1),
57            ),
58        })
59    }
60
61    pub fn from_points(x1: u32, y1: u32, x2: u32, y2: u32) -> Option<Rectangle> {
62        if (x2 <= x1) || (y2 <= y1) {
63            return None;
64        }
65
66        Some(Rectangle {
67            pos: (x1, y1),
68            size: (x2 - x1, y2 - y1),
69        })
70    }
71
72    pub fn from_center(cx: u32, cy: u32, width: u32, height: u32) -> Option<Rectangle> {
73        let half_size = (width / 2, height / 2);
74
75        if ((cx as isize - half_size.0 as isize) < 0) || ((cy as isize - half_size.1 as isize) < 0)
76        {
77            return None;
78        }
79
80        let pos = (cx - half_size.0, cy - half_size.1);
81
82        Some(Rectangle {
83            pos,
84            size: (width, height),
85        })
86    }
87
88    pub fn pos(&self) -> Vector {
89        self.pos
90    }
91
92    pub fn size(&self) -> Vector {
93        self.size
94    }
95
96    pub fn top_left(&self) -> Vector {
97        self.pos
98    }
99
100    pub fn bottom_right(&self) -> Vector {
101        (self.pos.0 + self.size.0, self.pos.1 + self.size.1)
102    }
103
104    pub fn x1(&self) -> u32 {
105        self.pos.0
106    }
107
108    pub fn y1(&self) -> u32 {
109        self.pos.1
110    }
111
112    pub fn x2(&self) -> u32 {
113        self.bottom_right().0
114    }
115
116    pub fn y2(&self) -> u32 {
117        self.bottom_right().1
118    }
119
120    pub fn center(&self) -> Vector {
121        (
122            self.pos.0 + (self.size.0 / 2),
123            self.pos.1 + (self.size.1 / 2),
124        )
125    }
126
127    pub fn contains(&self, other: &Rectangle) -> bool {
128        (self.x1() <= other.x1())
129            && (self.x2() >= other.x1())
130            && (self.x1() <= other.x2())
131            && (self.x2() >= other.x2())
132            && (self.y1() <= other.y1())
133            && (self.y2() >= other.y1())
134            && (self.y1() <= other.y2())
135            && (self.y2() >= other.y2())
136    }
137
138    pub fn collides(&self, other: &Rectangle) -> bool {
139        (self.x1() <= other.x2())
140            && (other.x1() <= self.x2())
141            && (self.y1() <= other.y2())
142            && (other.y1() <= self.y2())
143    }
144
145    pub fn collides_with_point(&self, other: Vector) -> bool {
146        (self.x1() <= other.0)
147            && (other.0 <= self.x2())
148            && (self.y1() <= other.1)
149            && (other.1 <= self.y2())
150    }
151
152    pub fn grow(&self, size: isize) -> Option<Rectangle> {
153        let size = (self.size.0 as isize + size, self.size.1 as isize + size);
154
155        if (size.0 <= 0) || (size.1 <= 0) {
156            return None;
157        }
158
159        Some(Rectangle {
160            pos: self.pos,
161            size: (size.0 as u32, size.1 as u32),
162        })
163    }
164
165    pub fn displace(&self, x_offset: isize, y_offset: isize) -> Option<Rectangle> {
166        let pos = (
167            self.pos.0 as isize + x_offset,
168            self.pos.1 as isize + y_offset,
169        );
170
171        if (pos.0 < 0) || (pos.1 < 0) {
172            return None;
173        }
174
175        Some(Rectangle {
176            pos: (pos.0 as u32, pos.1 as u32),
177            size: (self.size.0, self.size.1),
178        })
179    }
180}
181
182#[cfg(test)]
183mod test {
184    use super::*;
185
186    #[test]
187    fn new() {
188        let rect = Rectangle::new(10, 20, 30, 40);
189
190        assert_eq!((10, 20), rect.pos());
191        assert_eq!((30, 40), rect.size());
192        assert_eq!((10, 20), rect.top_left());
193        assert_eq!((40, 60), rect.bottom_right());
194        assert_eq!((25, 40), rect.center());
195
196        assert_eq!(10, rect.x1());
197        assert_eq!(20, rect.y1());
198        assert_eq!(40, rect.x2());
199        assert_eq!(60, rect.y2());
200    }
201
202    #[test]
203    fn random() {
204        let r = Rectangle::random((10, 20), (2, 5)).unwrap();
205
206        assert_eq!(0, r.pos().0);
207        assert_eq!(0, r.pos().1);
208        assert!((r.size().0 >= 10) && (r.size().0 <= 20));
209        assert!((r.size().1 >= 2) && (r.size().1 <= 5));
210    }
211
212    #[test]
213    fn from_points() {
214        let rect = Rectangle::from_points(10, 20, 30, 40).unwrap();
215
216        assert_eq!((10, 20), rect.pos());
217        assert_eq!((20, 20), rect.size());
218        assert_eq!((10, 20), rect.top_left());
219        assert_eq!((30, 40), rect.bottom_right());
220        assert_eq!((20, 30), rect.center());
221
222        assert_eq!(10, rect.x1());
223        assert_eq!(20, rect.y1());
224        assert_eq!(30, rect.x2());
225        assert_eq!(40, rect.y2());
226    }
227
228    #[test]
229    fn from_center() {
230        let rect = Rectangle::from_center(5, 5, 10, 10).unwrap();
231
232        assert_eq!(0, rect.x1());
233        assert_eq!(0, rect.y1());
234        assert_eq!(10, rect.size().0);
235        assert_eq!(10, rect.size().1);
236
237        let rect = Rectangle::from_center(0, 0, 5, 5);
238        assert_eq!(None, rect);
239    }
240
241    #[test]
242    fn from_point_with_negative_size() {
243        let r = Rectangle::from_points(10, 10, 0, 0);
244        assert_eq!(None, r);
245    }
246
247    #[test]
248    fn from_point_with_empty_rectangle() {
249        let r = Rectangle::from_points(10, 10, 10, 10);
250        assert_eq!(None, r);
251    }
252
253    #[test]
254    fn collides_with_no_collision() {
255        let r1 = Rectangle::from_points(0, 0, 10, 12).unwrap();
256        let r2 = Rectangle::from_points(25, 25, 30, 35).unwrap();
257
258        assert!(!r1.collides(&r2));
259        assert!(!r2.collides(&r1));
260
261        let r1 = r1.grow(5).unwrap();
262
263        assert!(!r1.collides(&r2));
264        assert!(!r2.collides(&r1));
265    }
266
267    #[test]
268    fn collides_with_collisions() {
269        let r1 = Rectangle::from_points(0, 0, 10, 10).unwrap();
270        let r2 = Rectangle::from_points(7, 9, 30, 35).unwrap();
271
272        assert!(r1.collides(&r2));
273        assert!(r2.collides(&r1));
274
275        let r1 = r1.grow(5).unwrap();
276
277        assert!(r1.collides(&r2));
278        assert!(r2.collides(&r1));
279    }
280
281    #[test]
282    fn collides_with_touching() {
283        let r1 = Rectangle::from_points(0, 0, 10, 10).unwrap();
284        let r2 = Rectangle::from_points(10, 10, 20, 20).unwrap();
285
286        assert!(r1.collides(&r2));
287        assert!(r2.collides(&r1));
288    }
289
290    #[test]
291    fn grow() {
292        let r = Rectangle::new(10, 15, 20, 10);
293
294        let new_rect = r.grow(5).unwrap();
295
296        assert_eq!(25, new_rect.size().0);
297        assert_eq!(15, new_rect.size().1);
298
299        let new_rect = new_rect.grow(-10).unwrap();
300
301        assert_eq!(15, new_rect.size().0);
302        assert_eq!(5, new_rect.size().1);
303    }
304
305    #[test]
306    fn grow_negative() {
307        let r = Rectangle::new(10, 15, 20, 10);
308
309        let new_rect = r.grow(-10);
310
311        assert_eq!(None, new_rect);
312    }
313
314    #[test]
315    fn displace() {
316        let r = Rectangle::new(10, 15, 20, 10);
317
318        let new_rect = r.displace(5, 15).unwrap();
319
320        assert_eq!(15, new_rect.pos().0);
321        assert_eq!(30, new_rect.pos().1);
322    }
323
324    #[test]
325    fn displace_outside() {
326        let r = Rectangle::new(10, 15, 20, 10);
327
328        let new_rect = r.displace(-15, -15);
329
330        assert_eq!(None, new_rect);
331    }
332}