rect_lib/lib.rs
1use core::cmp::Reverse;
2use num::{Num, One};
3
4// re-export the num crate
5pub use num;
6
7// basic rectangle
8mod basic_rectangle;
9pub use basic_rectangle::BasicRectangle;
10
11/// A trait containing methods for rectangle like data structures which implement `Sized` & `Copy`.
12///
13/// This trait treats all edges (left, right, top, & bottom) as inclusive.
14///
15/// # Example
16/// ```
17/// use rect_lib::Rectangle;
18///
19/// #[derive(Clone, Copy)]
20/// pub struct BasicRectangle {
21/// x: i32,
22/// y: i32,
23/// width: i32,
24/// height: i32,
25/// }
26///
27/// impl Rectangle for BasicRectangle {
28/// type Unit = i32;
29///
30/// fn left(&self) -> i32 {
31/// self.x
32/// }
33///
34/// fn right(&self) -> i32 {
35/// self.x + self.width - 1
36/// }
37///
38/// fn top(&self) -> i32 {
39/// self.y
40/// }
41///
42/// fn bottom(&self) -> i32 {
43/// self.y - self.height + 1
44/// }
45///
46/// fn new_from_sides(left: i32, right: i32, top: i32, bottom: i32) -> Self {
47/// Self {
48/// x: left,
49/// y: top,
50/// width: right - left + 1,
51/// height: top - bottom + 1,
52/// }
53/// }
54/// }
55/// ```
56pub trait Rectangle
57where
58 Self: Sized + Copy,
59{
60 // - Required implementations.
61
62 /// The unit type used for the rectangle.
63 type Unit: Num + One + Copy + PartialEq + PartialOrd + Ord;
64
65 /// The left most point of the rectangle.
66 ///
67 /// # Example
68 /// ```
69 /// use rect_lib::{BasicRectangle, Rectangle};
70 ///
71 /// let rect = BasicRectangle::new_from_sides(0, 1, 2, 3);
72 /// assert_eq!(rect.left(), 0);
73 /// ````
74 fn left(&self) -> Self::Unit;
75
76 /// The right most point of the rectangle.
77 ///
78 /// # Example
79 /// ```
80 /// use rect_lib::{BasicRectangle, Rectangle};
81 ///
82 /// let rect = BasicRectangle::new_from_sides(0, 1, 2, 3);
83 /// assert_eq!(rect.right(), 1);
84 /// ```
85 fn right(&self) -> Self::Unit;
86
87 /// The top most point of the rectangle.
88 ///
89 /// # Example
90 /// ```
91 /// use rect_lib::{BasicRectangle, Rectangle};
92 ///
93 /// let rect = BasicRectangle::new_from_sides(0, 1, 2, 3);
94 /// assert_eq!(rect.top(), 2);
95 /// ```
96 fn top(&self) -> Self::Unit;
97
98 /// The bottom most point of the rectangle.
99 ///
100 /// # Example
101 /// ```
102 /// use rect_lib::{BasicRectangle, Rectangle};
103 ///
104 /// let rect = BasicRectangle::new_from_sides(0, 1, 2, 3);
105 /// assert_eq!(rect.bottom(), 3);
106 /// ```
107 fn bottom(&self) -> Self::Unit;
108
109 /// Creates a new rectangle from the given sides.
110 /// The sides are inclusive.
111 ///
112 /// # Example
113 /// ```
114 /// use rect_lib::{BasicRectangle, Rectangle};
115 ///
116 /// let rect = BasicRectangle::new_from_sides(0, 1, 2, 3);
117 /// ```
118 fn new_from_sides(
119 left: Self::Unit,
120 right: Self::Unit,
121 top: Self::Unit,
122 bottom: Self::Unit,
123 ) -> Self;
124
125 // - Default implementations.
126
127 /// The width of the rectangle.
128 /// This is calculated as `right - left`.
129 ///
130 /// # Example
131 /// ```
132 /// use rect_lib::{BasicRectangle, Rectangle};
133 ///
134 /// let rect = BasicRectangle::new_from_sides(0, 1, 1, 0);
135 /// assert_eq!(rect.width(), 1);
136 /// ```
137 fn width(&self) -> Self::Unit {
138 self.right() - self.left()
139 }
140
141 /// The height of the rectangle.
142 /// This is calculated as `top - bottom`.
143 ///
144 /// # Example
145 /// ```
146 /// use rect_lib::{BasicRectangle, Rectangle};
147 ///
148 /// let rect = BasicRectangle::new_from_sides(0, 1, 1, 0);
149 /// assert_eq!(rect.height(), 1);
150 /// ```
151 fn height(&self) -> Self::Unit {
152 self.top() - self.bottom()
153 }
154
155 /// Translates the rectangle by the given amount.
156 /// This is done by adding the given amount to the x and y coordinates.
157 ///
158 /// # Example
159 /// ```
160 /// use rect_lib::{BasicRectangle, Rectangle};
161 ///
162 /// let rect = BasicRectangle::new_from_sides(0, 1, 1, 0);
163 /// let translated = rect.translate(1, 1);
164 /// assert_eq!(translated, BasicRectangle::new_from_sides(1, 2, 2, 1));
165 /// ```
166 fn translate(&self, x: Self::Unit, y: Self::Unit) -> Self {
167 Self::new_from_sides(
168 self.left() + x,
169 self.right() + x,
170 self.top() + y,
171 self.bottom() + y,
172 )
173 }
174
175 /// The perimeter of the rectangle.
176 /// This is calculated as `(width + height) * 2`.
177 ///
178 /// # Example
179 /// ```
180 /// use rect_lib::{BasicRectangle, Rectangle};
181 ///
182 /// let rect = BasicRectangle::new_from_sides(0, 1, 1, 0);
183 /// assert_eq!(rect.perimeter(), 4);
184 /// ```
185 fn perimeter(&self) -> Self::Unit {
186 (self.width() + self.height()) * (Self::Unit::one() + Self::Unit::one())
187 }
188
189 /// The area of the rectangle.
190 /// This is calculated as `width * height`.
191 ///
192 /// # Example
193 /// ```
194 /// use rect_lib::{BasicRectangle, Rectangle};
195 ///
196 /// let rect = BasicRectangle::new_from_sides(0, 1, 1, 0);
197 /// assert_eq!(rect.area(), 1);
198 /// ```
199 fn area(&self) -> Self::Unit {
200 // This function is so cute for some reason
201 self.width() * self.height() // :3
202 }
203
204 /// Checks if the rectangle contains the given point.
205 ///
206 /// # Example
207 /// ```
208 /// use rect_lib::{BasicRectangle, Rectangle};
209 ///
210 /// let rect = BasicRectangle::new_from_sides(0, 1, 1, 0);
211 /// assert!(rect.contains_point(0, 1));
212 /// assert!(!rect.contains_point(0, 2));
213 /// ```
214 fn contains_point(&self, x: Self::Unit, y: Self::Unit) -> bool {
215 x >= self.left() && x <= self.right() && y <= self.top() && y >= self.bottom()
216 }
217
218 /// Checks if one rectangle contains another.
219 ///
220 /// # Example
221 /// ```
222 /// use rect_lib::{BasicRectangle, Rectangle};
223 ///
224 /// let rect = BasicRectangle::new_from_sides(0, 2, 2, 0);
225 /// let other = BasicRectangle::new_from_sides(0, 1, 1, 0);
226 /// assert!(rect.contains_rectangle(&other));
227 /// assert!(!other.contains_rectangle(&rect));
228 /// ```
229 fn contains_rectangle(&self, other: &impl Rectangle<Unit = Self::Unit>) -> bool {
230 self.left() <= other.left()
231 && self.right() >= other.right()
232 && self.top() >= other.top()
233 && self.bottom() <= other.bottom()
234 }
235
236 /// Checks if one rectangle overlaps with another.
237 ///
238 /// # Example
239 /// ```
240 /// use rect_lib::{BasicRectangle, Rectangle};
241 ///
242 /// let rect = BasicRectangle::new_from_sides(0, 2, 2, 0);
243 /// assert!(rect.overlaps(&BasicRectangle::new_from_sides(1, 3, 3, 1)));
244 /// assert!(!rect.overlaps(&BasicRectangle::new_from_sides(3, 4, 4, 3)));
245 /// ```
246 fn overlaps(&self, other: &impl Rectangle<Unit = Self::Unit>) -> bool {
247 self.left() <= other.right()
248 && self.right() >= other.left()
249 && self.top() >= other.bottom()
250 && self.bottom() <= other.top()
251 }
252
253 /// Returns the intersection of two rectangles.
254 /// If the rectangles do not intersect, `None` is returned.
255 ///
256 /// # Example
257 /// ```
258 /// use rect_lib::{BasicRectangle, Rectangle};
259 ///
260 /// let rect = BasicRectangle::new_from_sides(0, 2, 2, 0);
261 /// let intersection = rect.intersection(&BasicRectangle::new_from_sides(1, 3, 3, 1));
262 /// assert_eq!(intersection, Some(BasicRectangle::new_from_sides(1, 2, 2, 1)));
263 ///
264 /// let no_intersection = rect.intersection(&BasicRectangle::new_from_sides(3, 4, 4, 3));
265 /// assert_eq!(no_intersection, None);
266 /// ```
267 fn intersection(&self, other: &impl Rectangle<Unit = Self::Unit>) -> Option<Self> {
268 let left = self.left().max(other.left());
269 let right = self.right().min(other.right());
270 let top = self.top().min(other.top());
271 let bottom = self.bottom().max(other.bottom());
272
273 if left <= right && bottom <= top {
274 Some(Self::new_from_sides(left, right, top, bottom))
275 } else {
276 None
277 }
278 }
279
280 /// This algorithm identifies all unique unobstructed sub-rectangles within a given rectangle by comparing it against a list of obstructions.
281 ///
282 /// # Example
283 /// ```
284 /// use rect_lib::{BasicRectangle, Rectangle};
285 ///
286 /// let rect = BasicRectangle::new_from_sides(0, 5, 5, 0);
287 /// let obstruction = BasicRectangle::new_from_sides(0, 2, 5, 1);
288 /// let subrects = rect.unobstructed_subrectangles(&vec![&obstruction]);
289 ///
290 /// assert_eq!(subrects.len(), 2);
291 /// assert!(subrects.iter().all(|r| [
292 /// BasicRectangle::new_from_sides(0, 5, 0, 0),
293 /// BasicRectangle::new_from_sides(3, 5, 5, 0)
294 /// ].contains(r)));
295 /// ```
296 fn unobstructed_subrectangles(
297 &self,
298 obstructions: &[&impl Rectangle<Unit = Self::Unit>],
299 ) -> Vec<Self> {
300 /// A rectangle that has not been obstructed yet
301 #[derive(Clone)]
302 struct UnfinishedRect<T: Rectangle> {
303 left: T::Unit,
304 top: T::Unit,
305 bottom: T::Unit,
306 }
307 /// A gap between two obstructions
308 struct Gap<T: Rectangle> {
309 top: T::Unit,
310 bottom: T::Unit,
311 }
312 /// A line we need to check for gaps
313 struct Line<T: Rectangle> {
314 x: T::Unit,
315 opens: bool,
316 }
317
318 let mut obstructions = obstructions.to_vec();
319 // sort the obstructions by top position
320 obstructions.sort_unstable_by(
321 // descending order
322 |rect_a, rect_b| {
323 rect_b.top().cmp(&rect_a.top()) // by the first point on each
324 },
325 );
326
327 // Section 1: collect all lines that need to be checked for gaps
328 let mut lines: Vec<Line<Self>> = vec![Line {
329 x: self.left(),
330 opens: true,
331 }];
332
333 for rect in &obstructions {
334 // gaps might close on the left of each obstruction
335 lines.push(Line {
336 x: rect.left(),
337 opens: false,
338 });
339
340 // gaps might open just after the right of each obstruction
341 lines.push(Line {
342 x: rect.right() + Self::Unit::one(),
343 opens: true,
344 });
345 }
346
347 // order from left to right
348 lines.sort_unstable_by_key(|line| line.x);
349 lines.dedup_by_key(|line| line.x);
350
351 // filter out lines that are outside the rectangle
352 let lines = lines
353 .into_iter()
354 .filter(|line| self.left() <= line.x && line.x <= self.right());
355
356 // this is the list we will return
357 let mut unique_rectangles: Vec<Self> = Vec::new();
358
359 // this will store active rectangles as we sweep from line to line
360 let mut active_rectangles: Vec<UnfinishedRect<Self>> = Vec::new();
361
362 for line in lines {
363 // Section 2: collect all gaps between obstructions
364 let mut gaps: Vec<Gap<Self>> = Vec::new();
365
366 // think of each obstruction as a shingle on a roof
367 // if the bottom of one shingle is above the top of the next there is a gap between them
368 let mut last_rectange_bottom: Self::Unit = self.top();
369
370 // filter out obstructions that don't intersect the current line
371 for obstruction in obstructions
372 .iter()
373 .filter(|rect| rect.left() <= line.x && line.x <= rect.right())
374 {
375 if last_rectange_bottom > obstruction.top() {
376 gaps.push(Gap {
377 top: last_rectange_bottom,
378 bottom: obstruction.top() + Self::Unit::one(), // the top is inclusive so +1
379 });
380 }
381
382 // if a later shingle starts in the same place we could get a fake gap
383 // so we avoid that by getting the lowest point
384 last_rectange_bottom =
385 last_rectange_bottom.min(obstruction.bottom() - Self::Unit::one());
386 }
387
388 // check if there is a gap between the bottom of the last shingle and the end of the roof
389 // the bottom is inclusive so >=
390 if last_rectange_bottom >= self.bottom() {
391 gaps.push(Gap {
392 top: last_rectange_bottom,
393 bottom: self.bottom(),
394 });
395 }
396 // alright, we have all the gaps
397
398 active_rectangles.sort_unstable_by_key(|rect| Reverse(rect.left));
399
400 // Section 3: if the current line opens we create new rectangles
401 if line.opens {
402 // try to create a new rect for each gap
403 for gap in gaps {
404 // make sure its unique
405 if !active_rectangles
406 .iter()
407 .any(|rect| gap.top == rect.top && gap.bottom == rect.bottom)
408 {
409 active_rectangles.push(UnfinishedRect {
410 left: line.x,
411 top: gap.top,
412 bottom: gap.bottom,
413 });
414 }
415 }
416
417 // on to the next line
418 continue;
419 }
420
421 // Section 3 & 1/2: if the current line closes we finish rectangles
422 let mut new_active_rectangles: Vec<UnfinishedRect<Self>> = Vec::new();
423
424 active_rectangles = active_rectangles
425 .iter()
426 .filter(|rect| {
427 // if the current rect fits within a gap we can keep it
428 for gap in gaps.iter() {
429 if gap.top >= rect.top && rect.bottom >= gap.bottom {
430 // on to the next active rect
431 return true;
432 }
433 }
434
435 // if it is obstructed we can close it
436 unique_rectangles.push(Self::new_from_sides(
437 rect.left, // left
438 line.x - Self::Unit::one(), // right
439 rect.top, // top
440 rect.bottom, // bottom
441 ));
442
443 // check if there are any gaps within the current rect
444 for gap in gaps
445 .iter()
446 .filter(|gap| gap.top <= rect.top || rect.bottom <= gap.bottom)
447 {
448 let top_limit = rect.top.min(gap.top);
449 let bottom_limit = rect.bottom.max(gap.bottom);
450
451 // make sure its unique
452 if !active_rectangles
453 .iter()
454 .chain(new_active_rectangles.iter())
455 .any(|rect| top_limit == rect.top && bottom_limit == rect.bottom)
456 {
457 new_active_rectangles.push(UnfinishedRect {
458 left: rect.left,
459 top: top_limit,
460 bottom: bottom_limit,
461 });
462 }
463 }
464
465 // make sure to remove it from active
466 false
467 })
468 .cloned()
469 .collect();
470
471 // add any new sub rectangles
472 active_rectangles.append(&mut new_active_rectangles);
473 }
474
475 // Section 4: now that we have checked all lines we can close any remaining rectangles
476 for rect in active_rectangles {
477 unique_rectangles.push(Self::new_from_sides(
478 rect.left,
479 self.right(),
480 rect.top,
481 rect.bottom,
482 ));
483 }
484
485 // Quod Erat Demonstrandum
486 unique_rectangles
487 }
488}