Skip to main content

thedes_geometry/
rect.rs

1use std::{
2    fmt,
3    ops::{Add, Div, Mul, Rem, Sub},
4};
5
6use num::{
7    CheckedAdd,
8    CheckedDiv,
9    CheckedMul,
10    CheckedSub,
11    One,
12    Zero,
13    traits::CheckedRem,
14};
15use rand::{
16    Rng,
17    distr::{Distribution, uniform::SampleUniform},
18};
19use serde::{Deserialize, Serialize};
20use thiserror::Error;
21
22use crate::{
23    coords::CoordPair,
24    orientation::{Direction, DirectionVec},
25};
26
27#[derive(Debug, Error)]
28pub enum InvalidRectDistr {
29    #[error("Rectangle distribution is empty")]
30    Empty,
31}
32
33#[derive(Debug, Error)]
34#[error("Invalid point {point} for rectangle {rect}")]
35pub struct InvalidPoint<C>
36where
37    C: fmt::Display,
38{
39    pub point: CoordPair<C>,
40    pub rect: Rect<C>,
41}
42
43#[derive(Debug, Error)]
44#[error("Invalid area {area} for partition of rectangle {rect}")]
45pub struct InvalidArea<C>
46where
47    C: fmt::Display,
48{
49    pub area: C,
50    pub rect: Rect<C>,
51}
52
53#[derive(Debug, Error)]
54pub enum HorzAreaError<C>
55where
56    C: fmt::Display,
57{
58    #[error("Point outside of rectangle defines no internal area")]
59    InvalidRectPoint(
60        #[from]
61        #[source]
62        InvalidPoint<C>,
63    ),
64    #[error("Arithmetic overflow computing area for rectangle of size {size}")]
65    Overflow { size: CoordPair<C> },
66}
67
68#[derive(
69    Debug,
70    Clone,
71    Copy,
72    PartialEq,
73    Eq,
74    PartialOrd,
75    Ord,
76    Hash,
77    Default,
78    Serialize,
79    Deserialize,
80)]
81pub struct Rect<C> {
82    pub top_left: CoordPair<C>,
83    pub size: CoordPair<C>,
84}
85
86impl<C> Rect<C> {
87    pub fn as_ref(&self) -> Rect<&C> {
88        Rect { top_left: self.top_left.as_ref(), size: self.size.as_ref() }
89    }
90
91    pub fn as_mut(&mut self) -> Rect<&mut C> {
92        Rect { top_left: self.top_left.as_mut(), size: self.size.as_mut() }
93    }
94
95    pub fn map<F, C0>(self, mut mapper: F) -> Rect<C0>
96    where
97        F: FnMut(C) -> C0,
98    {
99        Rect {
100            top_left: self.top_left.map(&mut mapper),
101            size: self.size.map(mapper),
102        }
103    }
104
105    pub fn try_map<F, C0, E>(self, mut mapper: F) -> Result<Rect<C0>, E>
106    where
107        F: FnMut(C) -> Result<C0, E>,
108    {
109        Ok(Rect {
110            top_left: self.top_left.try_map(&mut mapper)?,
111            size: self.size.try_map(mapper)?,
112        })
113    }
114
115    pub fn bottom_right<D>(self) -> CoordPair<D>
116    where
117        C: Add<Output = D>,
118    {
119        self.top_left + self.size
120    }
121
122    pub fn checked_bottom_right(&self) -> Option<CoordPair<C>>
123    where
124        C: CheckedAdd,
125    {
126        self.top_left.checked_add(&self.size)
127    }
128
129    pub fn contains_point(self, point: CoordPair<C>) -> bool
130    where
131        C: Sub<Output = C> + PartialOrd,
132    {
133        self.top_left
134            .zip3(self.size, point)
135            .all(|(start, size, coord)| coord >= start && coord - start < size)
136    }
137
138    pub fn checked_horz_area_down_to(
139        self,
140        point: CoordPair<C>,
141    ) -> Result<C, HorzAreaError<C>>
142    where
143        C: Sub<Output = C> + CheckedAdd + CheckedMul,
144        C: fmt::Display + PartialOrd + Clone,
145    {
146        if self.clone().contains_point(point.clone()) {
147            let from_origin = point - self.top_left;
148            self.size
149                .x
150                .checked_mul(&from_origin.y)
151                .and_then(|scaled| scaled.checked_add(&from_origin.x))
152                .ok_or(HorzAreaError::Overflow { size: self.size })
153        } else {
154            Err(InvalidPoint { point, rect: self })?
155        }
156    }
157
158    pub fn horz_area_down_to(self, point: CoordPair<C>) -> C
159    where
160        C: Sub<Output = C> + Mul<Output = C> + Add<Output = C>,
161        C: fmt::Display + PartialOrd + Clone,
162    {
163        let from_origin = point - self.top_left;
164        self.size.x * from_origin.y + from_origin.x
165    }
166
167    pub fn checked_bot_right_of_horz_area(
168        &self,
169        area: &C,
170    ) -> Result<CoordPair<C>, InvalidArea<C>>
171    where
172        C: CheckedAdd + CheckedDiv + CheckedRem + Clone,
173        C: fmt::Display,
174    {
175        let optional_coords = CoordPair {
176            x: area.checked_rem(&self.size.x),
177            y: area.checked_div(&self.size.x),
178        };
179
180        optional_coords
181            .transpose()
182            .and_then(|from_origin| self.top_left.checked_add(&from_origin))
183            .ok_or(InvalidArea { area: area.clone(), rect: self.clone() })
184    }
185
186    pub fn bot_right_of_horz_area(self, area: C) -> CoordPair<C>
187    where
188        C: Add<Output = C> + Div<Output = C> + Rem<Output = C> + Clone,
189    {
190        let x = area.clone() % self.size.x;
191        let y = area / self.size.y;
192        let from_origin = CoordPair { x, y };
193        self.top_left + from_origin
194    }
195
196    pub fn total_area<D>(self) -> D
197    where
198        C: Mul<Output = D>,
199    {
200        self.size.x * self.size.y
201    }
202
203    pub fn checked_total_area(&self) -> Option<C>
204    where
205        C: CheckedMul,
206    {
207        self.as_ref().checked_total_area_by_ref()
208    }
209
210    pub fn checked_move_point_unit(
211        self,
212        point: CoordPair<C>,
213        direction: Direction,
214    ) -> Result<CoordPair<C>, InvalidPoint<C>>
215    where
216        C: Add<Output = C> + Sub<Output = C> + CheckedAdd + CheckedSub + One,
217        C: Clone + PartialOrd + fmt::Display,
218    {
219        self.checked_move_point_by(
220            point,
221            DirectionVec::unit(direction).as_ref(),
222        )
223    }
224
225    pub fn checked_move_point_by(
226        self,
227        point: CoordPair<C>,
228        vector: DirectionVec<&C>,
229    ) -> Result<CoordPair<C>, InvalidPoint<C>>
230    where
231        C: Add<Output = C> + Sub<Output = C> + CheckedAdd + CheckedSub,
232        C: Clone + PartialOrd + fmt::Display,
233    {
234        if let Some(output) = point
235            .checked_move_by(vector)
236            .filter(|output| self.clone().contains_point(output.clone()))
237        {
238            Ok(output)
239        } else {
240            Err(InvalidPoint { point, rect: self })
241        }
242    }
243}
244
245impl<'a, C> Rect<&'a C> {
246    pub fn copied(self) -> Rect<C>
247    where
248        C: Copy,
249    {
250        Rect { top_left: self.top_left.copied(), size: self.size.copied() }
251    }
252
253    pub fn cloned(self) -> Rect<C>
254    where
255        C: Clone,
256    {
257        Rect { top_left: self.top_left.cloned(), size: self.size.cloned() }
258    }
259
260    pub fn checked_bottom_right_by_ref(self) -> Option<CoordPair<C>>
261    where
262        C: CheckedAdd,
263    {
264        self.top_left.checked_add_by_ref(self.size)
265    }
266
267    pub fn checked_total_area_by_ref(self) -> Option<C>
268    where
269        C: CheckedMul,
270    {
271        self.size.x.checked_mul(self.size.y)
272    }
273}
274
275impl<'a, C> Rect<&'a mut C> {
276    pub fn copied(self) -> Rect<C>
277    where
278        C: Copy,
279    {
280        Rect { top_left: self.top_left.copied(), size: self.size.copied() }
281    }
282
283    pub fn cloned(self) -> Rect<C>
284    where
285        C: Clone,
286    {
287        Rect { top_left: self.top_left.cloned(), size: self.size.cloned() }
288    }
289
290    pub fn share(self) -> Rect<&'a C> {
291        Rect { top_left: self.top_left.share(), size: self.size.share() }
292    }
293}
294
295impl<C> fmt::Display for Rect<C>
296where
297    C: fmt::Display,
298{
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        write!(f, "{}[{}]", self.top_left, self.size)
301    }
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
305pub struct UniformRectDistr<C> {
306    rect: Rect<C>,
307}
308
309impl<C> UniformRectDistr<C>
310where
311    C: Zero,
312{
313    pub fn new(rect: Rect<C>) -> Result<Self, InvalidRectDistr> {
314        if rect.size.as_ref().any(|elem| elem.is_zero()) {
315            Err(InvalidRectDistr::Empty)?
316        }
317        Ok(Self { rect })
318    }
319}
320
321impl<C> Distribution<CoordPair<C>> for UniformRectDistr<C>
322where
323    C: SampleUniform + Clone + Add<Output = C> + PartialOrd,
324{
325    fn sample<R>(&self, rng: &mut R) -> CoordPair<C>
326    where
327        R: Rng + ?Sized,
328    {
329        self.rect
330            .top_left
331            .clone()
332            .zip2_with(self.rect.clone().bottom_right(), |min, max| {
333                rng.random_range(min .. max)
334            })
335    }
336}