cogs_gamedev/grids/
rectangles.rs

1use super::ICoord;
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6/// A rectangle with integer values.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9pub struct IRect {
10    pub left: isize,
11    pub top: isize,
12    pub width: usize,
13    pub height: usize,
14}
15
16impl IRect {
17    pub fn new(left: isize, top: isize, width: usize, height: usize) -> Self {
18        Self {
19            left,
20            top,
21            width,
22            height,
23        }
24    }
25
26    /// Return a new rectangle centered at the given position with the given w/h
27    pub fn centered(center: ICoord, width: usize, height: usize) -> Self {
28        let top = center.y - height as isize / 2;
29        let left = center.x - width as isize / 2;
30        Self::new(left, top, width, height)
31    }
32
33    /// Does this rect contain the pos?
34    ///
35    /// Points on the boundary count.
36    pub fn contains(&self, pos: ICoord) -> bool {
37        self.top <= pos.y && self.bottom() >= pos.y && self.left <= pos.x && self.right() >= pos.x
38    }
39
40    pub fn area(&self) -> usize {
41        self.width * self.height
42    }
43
44    /// Iterator through all the positions in the rect.
45    /// It goes in reading order: left-to-right, top-to-bottom.
46    ///
47    /// (Sorry hebrew speakers)
48    pub fn contained_coords(&self) -> RectIter {
49        RectIter::new(*self)
50    }
51
52    pub fn right(&self) -> isize {
53        self.left + self.width as isize - 1
54    }
55
56    pub fn bottom(&self) -> isize {
57        self.top + self.height as isize - 1
58    }
59
60    pub fn shifted(self, by: ICoord) -> IRect {
61        IRect {
62            top: self.top + by.y,
63            left: self.left + by.x,
64            ..self
65        }
66    }
67}
68
69impl std::ops::Add<ICoord> for IRect {
70    type Output = IRect;
71    fn add(self, rhs: ICoord) -> Self::Output {
72        self.shifted(rhs)
73    }
74}
75
76pub struct RectIter {
77    rect: IRect,
78    cursor: ICoord,
79    exhausted: bool,
80}
81
82impl RectIter {
83    pub fn new(rect: IRect) -> Self {
84        Self {
85            rect,
86            cursor: ICoord::new(rect.left, rect.top),
87            // if the rect is just a line, don't output anything.
88            exhausted: rect.width == 0 || rect.height == 0,
89        }
90    }
91}
92
93impl Iterator for RectIter {
94    type Item = ICoord;
95    fn next(&mut self) -> Option<Self::Item> {
96        if !self.exhausted {
97            let out = self.cursor;
98
99            self.cursor.x += 1;
100            if !self.rect.contains(self.cursor) {
101                self.cursor.x = self.rect.left;
102                self.cursor.y += 1;
103                if !self.rect.contains(self.cursor) {
104                    self.exhausted = true;
105                    // And return this one final point
106                    // next go-around we return None
107                }
108            }
109
110            Some(out)
111        } else {
112            None
113        }
114    }
115}