lighthouse_protocol/utils/
rect.rs

1use std::{fmt::Debug, ops::{Add, Div, Mul, Range, Sub}};
2
3use rand::{Rng, seq::IteratorRandom, thread_rng};
4
5use crate::{Vec2, LIGHTHOUSE_COLS};
6
7use super::{RemEuclid, Unity, Zero};
8
9/// A rectangle on the integer grid.
10#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
11pub struct Rect<T> {
12    pub origin: Vec2<T>,
13    pub size: Vec2<T>,
14}
15
16impl<T> Rect<T> {
17    /// Creates a new rectangle.
18    pub const fn new(origin: Vec2<T>, size: Vec2<T>) -> Self {
19        Self { origin, size }
20    }
21}
22
23impl<T> Rect<T> where T: Copy {
24    /// The rectangle's width.
25    pub const fn width(self) -> T {
26        self.size.x
27    }
28
29    /// The rectangle's height.
30    pub const fn height(self) -> T {
31        self.size.y
32    }
33
34    /// Fetches the top-left corner of the rectangle, i.e. the origin.
35    pub const fn top_left(self) -> Vec2<T> {
36        self.origin
37    }
38}
39
40impl<T> Rect<T> where T: Add<Output = T> + Copy {
41    /// Fetches the bottom-right corner of the rectangle.
42    pub fn bottom_right(self) -> Vec2<T> {
43        self.origin + self.size
44    }
45}
46
47impl<T> Rect<T> where T: Add<Output = T> + Copy + Zero {
48    /// Fetches the bottom-left corner of the rectangle.
49    pub fn bottom_left(self) -> Vec2<T> {
50        self.origin + Vec2::new(T::ZERO, self.size.y)
51    }
52
53    /// Fetches the top-right corner of the rectangle.
54    pub fn top_right(self) -> Vec2<T> {
55        self.origin + Vec2::new(self.size.x, T::ZERO)
56    }
57}
58
59impl<T> Rect<T> where T: Add<Output = T> + Div<Output = T> + Copy + Zero + Unity {
60    /// The generic constant 2.
61    fn two() -> T {
62        T::ONE + T::ONE
63    }
64
65    /// Fetches the top-center position of the rectangle.
66    pub fn top_center(self) -> Vec2<T> {
67        self.origin + Vec2::new(self.size.x / Self::two(), T::ZERO)
68    }
69
70    /// Fetches the left-center position of the rectangle.
71    pub fn center_left(self) -> Vec2<T> {
72        self.origin + Vec2::new(T::ZERO, self.size.y / Self::two())
73    }
74
75    /// Fetches the right-center position of the rectangle.
76    pub fn center_right(self) -> Vec2<T> {
77        self.origin + Vec2::new(self.size.x, self.size.y / Self::two())
78    }
79
80    /// Fetches the bottom-center position of the rectangle.
81    pub fn bottom_center(self) -> Vec2<T> {
82        self.origin + Vec2::new(self.size.x / Self::two(), self.size.y)
83    }
84
85    /// Fetches the center position of the rectangle.
86    pub fn center(self) -> Vec2<T> {
87        self.origin + self.size.map(|c| c / Self::two())
88    }
89}
90
91impl<T> Rect<T> where T: Mul<Output = T> + Copy {
92    /// The rectangle's area.
93    pub fn area(self) -> T {
94        self.width() * self.height()
95    }
96}
97
98impl<T> Rect<T> where T: RemEuclid + Copy {
99    /// Wraps a value to the rectangle's bounds.
100    pub fn wrap(self, pos: Vec2<T>) -> Vec2<T> {
101        Vec2::new(
102            pos.x.rem_euclid(self.width()),
103            pos.y.rem_euclid(self.height()),
104        )
105    }
106}
107
108impl<T> Rect<T> where T: Zero + Eq + Copy {
109    /// Whether this rectangle is empty.
110    pub fn is_empty(self) -> bool {
111        self.size.x == T::ZERO && self.size.y == T::ZERO
112    }
113}
114
115impl<T> Rect<T> where T: Add<Output = T> + Copy {
116    /// The range of x values.
117    pub fn x_range(self) -> Range<T> {
118        self.origin.x..(self.origin.x + self.size.x)
119    }
120
121    /// The range of y values.
122    pub fn y_range(self) -> Range<T> {
123        self.origin.y..(self.origin.y + self.size.y)
124    }
125}
126
127impl<T> Rect<T> where T: Add<Output = T> + Copy, Range<T>: IteratorRandom<Item = T> {
128    /// Samples a random position within the rectangle with the given rng.
129    pub fn sample_random_with(self, rng: &mut impl Rng) -> Option<Vec2<T>> {
130        let x = self.x_range().choose(rng)?;
131        let y = self.y_range().choose(rng)?;
132        Some(Vec2::<T>::new(x, y))
133    }
134
135    /// Samples a random position within the rectangle.
136    pub fn sample_random(self) -> Option<Vec2<T>> {
137        self.sample_random_with(&mut thread_rng())
138    }
139}
140
141impl<T> Rect<T> where T: Add<Output = T> + Ord + Copy {
142    /// Checks whether the rectangle contains the given position.
143    pub fn contains(self, pos: Vec2<T>) -> bool {
144        pos.x >= self.origin.x && pos.x < self.origin.x + self.width()
145        && pos.y >= self.origin.y && pos.y < self.origin.y + self.height()
146    }
147
148    /// Checks whether the rectangle intersects the given rectangle.
149    pub fn intersects(self, other: Rect<T>) -> bool {
150        let s1 = self.top_left();
151        let e1 = self.bottom_right();
152        let s2 = other.top_left();
153        let e2 = other.bottom_right();
154        s2.x < e1.x && s1.x < e2.x &&
155        s2.y < e1.y && s1.y < e2.y
156    }
157}
158
159impl<T> Rect<T> where T: Add<Output = T> + Sub<Output = T> + TryInto<usize> + Ord + Copy, T::Error: Debug {
160    /// Converts a position to an index.
161    pub fn index_of(self, pos: Vec2<T>) -> usize {
162        debug_assert!(self.contains(pos));
163        let relative = pos - self.origin;
164        relative.y.try_into().unwrap() * LIGHTHOUSE_COLS + relative.x.try_into().unwrap()
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use std::ops::Sub;
171
172    use crate::Vec2;
173
174    use super::Rect;
175
176    #[test]
177    fn unit_points() {
178        let rect = Rect::new(Vec2::new(-1, -1), Vec2::new(2, 2));
179        assert_eq!(rect.top_left(), Vec2::new(-1, -1));
180        assert_eq!(rect.top_center(), Vec2::new(0, -1));
181        assert_eq!(rect.top_right(), Vec2::new(1, -1));
182
183        assert_eq!(rect.center_left(), Vec2::new(-1, 0));
184        assert_eq!(rect.center(), Vec2::new(0, 0));
185        assert_eq!(rect.center_right(), Vec2::new(1, 0));
186
187        assert_eq!(rect.bottom_left(), Vec2::new(-1, 1));
188        assert_eq!(rect.bottom_center(), Vec2::new(0, 1));
189        assert_eq!(rect.bottom_right(), Vec2::new(1, 1));
190    }
191
192    #[test]
193    fn intersections() {
194        assert!(rect(0, 0, 2, 2).intersects(rect(1, 1, 3, 3)));
195        assert!(rect(0, 0, 2, 2).intersects(rect(1, -1, 1, 3)));
196        assert!(!rect(0, -2, 1, 1).intersects(rect(1, -1, 1, 3)));
197        assert!(!rect(0, 0, 1, 1).intersects(rect(1, 0, 2, 1)));
198        assert!(rect(0, 0, 2, 1).intersects(rect(1, 0, 2, 1)));
199        assert!(!rect(0, 0, 2, 0).intersects(rect(1, 0, 2, 1)));
200    }
201
202    fn rect<T>(sx: T, sy: T, ex: T, ey: T) -> Rect<T> where T: Copy + Sub<Output = T> {
203        Rect::new(Vec2::new(sx, sy), Vec2::new(ex - sx, ey - sy))
204    }
205}