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}