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}