lighthouse_protocol/utils/
rect.rs

1use std::{fmt::Debug, ops::{Add, Mul, Range, Sub}};
2
3use rand::{Rng, seq::IteratorRandom, thread_rng};
4
5use crate::{Vec2, LIGHTHOUSE_COLS};
6
7use super::{RemEuclid, 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
35impl<T> Rect<T> where T: Mul<Output = T> + Copy {
36    /// The rectangle's area.
37    pub fn area(self) -> T {
38        self.width() * self.height()
39    }
40}
41
42impl<T> Rect<T> where T: RemEuclid + Copy {
43    /// Wraps a value to the rectangle's bounds.
44    pub fn wrap(self, pos: Vec2<T>) -> Vec2<T> {
45        Vec2::new(
46            pos.x.rem_euclid(self.width()),
47            pos.y.rem_euclid(self.height()),
48        )
49    }
50}
51
52impl<T> Rect<T> where T: Zero + Eq + Copy {
53    /// Whether this rectangle is empty.
54    pub fn is_empty(self) -> bool {
55        self.size.x == T::ZERO && self.size.y == T::ZERO
56    }
57}
58
59impl<T> Rect<T> where T: Add<Output = T> + Copy {
60    /// The range of x values.
61    pub fn x_range(self) -> Range<T> {
62        self.origin.x..(self.origin.x + self.size.x)
63    }
64
65    /// The range of y values.
66    pub fn y_range(self) -> Range<T> {
67        self.origin.y..(self.origin.y + self.size.y)
68    }
69}
70
71impl<T> Rect<T> where T: Add<Output = T> + Copy, Range<T>: IteratorRandom<Item = T> {
72    /// Samples a random position within the rectangle with the given rng.
73    pub fn sample_random_with(self, rng: &mut impl Rng) -> Option<Vec2<T>> {
74        let x = self.x_range().choose(rng)?;
75        let y = self.y_range().choose(rng)?;
76        Some(Vec2::<T>::new(x, y))
77    }
78
79    /// Samples a random position within the rectangle.
80    pub fn sample_random(self) -> Option<Vec2<T>> {
81        self.sample_random_with(&mut thread_rng())
82    }
83}
84
85impl<T> Rect<T> where T: Add<Output = T> + Ord + Copy {
86    /// Checks whether the rectangle contains the given position.
87    pub fn contains(self, pos: Vec2<T>) -> bool {
88        pos.x >= self.origin.x && pos.x < self.origin.x + self.width()
89        && pos.y >= self.origin.y && pos.y < self.origin.y + self.height()
90    }
91}
92
93impl<T> Rect<T> where T: Add<Output = T> + Sub<Output = T> + TryInto<usize> + Ord + Copy, T::Error: Debug {
94    /// Converts a position to an index.
95    pub fn index_of(self, pos: Vec2<T>) -> usize {
96        debug_assert!(self.contains(pos));
97        let relative = pos - self.origin;
98        relative.y.try_into().unwrap() * LIGHTHOUSE_COLS + relative.x.try_into().unwrap()
99    }
100}