This repository has no description
1use crate::{Point, Shape};
2use anyhow::{Error, Result, anyhow, format_err};
3#[cfg(feature = "web")]
4use wasm_bindgen::prelude::*;
5
6use super::Axis;
7
8#[cfg_attr(feature = "web", wasm_bindgen)]
9#[derive(Debug, Clone, Default, Copy)]
10pub struct Region {
11 pub start: Point,
12 pub end: Point,
13}
14
15impl Region {
16 /// iterates from left to right then top to bottom (in a "row-major" order)
17 pub fn iter(&self) -> RegionIterator {
18 self.into()
19 }
20
21 /// Iterates all points except the ones specified in the `except` region
22 pub fn except<'a>(
23 &self,
24 except: &'a Region,
25 ) -> impl Iterator<Item = Point> + use<'a> {
26 self.iter().filter(move |p| !except.contains(p))
27 }
28
29 pub fn iter_lower_triangle(&self) -> impl Iterator<Item = Point> {
30 self.iter().filter(|p| p.x() < p.y())
31 }
32
33 pub fn iter_upper_strict_triangle(&self) -> impl Iterator<Item = Point> {
34 self.iter().filter(|p| p.x() >= p.y())
35 }
36
37 /// Iterates all points outlining the region, in clockwise order starting from top-left
38 pub fn outline(&self) -> impl Iterator<Item = Point> {
39 self.top_edge()
40 .chain(self.right_edge().skip(1))
41 .chain(self.bottom_edge().rev().skip(1))
42 .chain(self.left_edge().rev().skip(1))
43 }
44
45 pub fn top_edge(&self) -> impl DoubleEndedIterator<Item = Point> {
46 (self.start.x()..=self.end.x())
47 .map(move |x| Point::Corner(x, self.start.y()))
48 }
49
50 pub fn bottom_edge(&self) -> impl DoubleEndedIterator<Item = Point> {
51 (self.start.x()..=self.end.x())
52 .map(move |x| Point::Corner(x, self.end.y()))
53 }
54
55 pub fn left_edge(&self) -> impl DoubleEndedIterator<Item = Point> {
56 (self.start.y()..=self.end.y())
57 .map(move |y| Point::Corner(self.start.x(), y))
58 }
59
60 pub fn right_edge(&self) -> impl DoubleEndedIterator<Item = Point> {
61 (self.start.y()..=self.end.y())
62 .map(move |y| Point::Corner(self.end.x(), y))
63 }
64
65 /// Corners of the region's outline
66 /// Does _not_ match .bottomright() etc., since
67 /// this method takes into account that the region is inclusive
68 /// topleft, topright, bottomright, bottomleft
69 pub fn corners(&self) -> [Point; 4] {
70 [
71 self.topleft(),
72 self.topright().translated(1, 0),
73 self.bottomright().translated(1, 1),
74 self.bottomleft().translated(0, 1),
75 ]
76 }
77
78 pub fn is_empty(&self) -> bool {
79 self.width() == 0 || self.height() == 0
80 }
81
82 pub fn ensure_nonempty(&self) -> Result<()> {
83 if self.is_empty() {
84 return Err(format_err!("Region {} is empty", self));
85 }
86
87 Ok(())
88 }
89
90 pub fn rectangle(&self) -> Shape {
91 Shape::Rectangle(self.start, self.end)
92 }
93
94 pub fn center_coords(&self, cell_size: usize) -> (f32, f32) {
95 let (x, y) = self.center().coords(cell_size);
96 let (h, w) = self.size(cell_size);
97
98 (x + (w / 2.0), y + (h / 2.0))
99 }
100}
101
102pub struct RegionIterator {
103 region: Region,
104 current: Point,
105}
106
107impl IntoIterator for Region {
108 type Item = Point;
109 type IntoIter = RegionIterator;
110
111 fn into_iter(self) -> Self::IntoIter {
112 self.iter()
113 }
114}
115
116impl Iterator for RegionIterator {
117 type Item = Point;
118
119 fn next(&mut self) -> Option<Self::Item> {
120 if self.current.x() > self.region.end.x() {
121 self.current
122 .set(self.region.start.x(), self.current.y() + 1);
123 }
124 if self.current.y() > self.region.end.y() {
125 return None;
126 }
127 let result = self.current;
128 self.current.set_x(self.current.x() + 1);
129 Some(result)
130 }
131}
132
133impl From<&Region> for RegionIterator {
134 fn from(region: &Region) -> Self {
135 Self {
136 region: *region,
137 current: region.start,
138 }
139 }
140}
141
142impl From<(&Point, &Point)> for Region {
143 fn from((s, e): (&Point, &Point)) -> Self {
144 Self { start: *s, end: *e }
145 }
146}
147
148impl From<(Point, Point)> for Region {
149 fn from((start, end): (Point, Point)) -> Self {
150 Self { start, end }
151 }
152}
153
154impl From<((usize, usize), (usize, usize))> for Region {
155 fn from((start, end): ((usize, usize), (usize, usize))) -> Self {
156 Region {
157 start: start.into(),
158 end: end.into(),
159 }
160 }
161}
162
163impl std::ops::Sub for Region {
164 type Output = (i32, i32);
165
166 fn sub(self, rhs: Self) -> Self::Output {
167 (
168 (self.start.x() as i32 - rhs.start.x() as i32),
169 (self.start.y() as i32 - rhs.start.y() as i32),
170 )
171 }
172}
173
174#[test]
175fn test_sub_and_transate_coherence() {
176 let a = Region::from_origin(Point::Corner(3, 3)).unwrap();
177 let mut b = a;
178 b.translate(2, 3);
179
180 assert_eq!(b - a, (2, 3));
181}
182
183impl Region {
184 pub fn new(
185 start: impl Into<Point>,
186 end: impl Into<Point>,
187 ) -> Result<Self, Error> {
188 let region = Self {
189 start: start.into(),
190 end: end.into(),
191 };
192 region.ensure_valid()
193 }
194
195 pub fn bottomleft(&self) -> Point {
196 Point::Corner(self.start.x(), self.end.y())
197 }
198
199 pub fn bottomright(&self) -> Point {
200 Point::Corner(self.end.x(), self.end.y())
201 }
202
203 pub fn topleft(&self) -> Point {
204 Point::Corner(self.start.x(), self.start.y())
205 }
206
207 pub fn topright(&self) -> Point {
208 Point::Corner(self.end.x(), self.start.y())
209 }
210
211 pub fn center(&self) -> Point {
212 Point::Center(
213 (self.start.x() + self.end.x()) / 2,
214 (self.start.y() + self.end.y()) / 2,
215 )
216 }
217
218 pub fn max<'a>(&'a self, other: &'a Region) -> &'a Region {
219 if self.within(other) { other } else { self }
220 }
221
222 pub fn merge<'a>(&'a self, other: &'a Region) -> Region {
223 Region {
224 start: Point::Corner(
225 self.start.x().min(other.start.x()),
226 self.start.y().min(other.start.y()),
227 ),
228 end: Point::Corner(
229 self.end.x().max(other.end.x()),
230 self.end.y().max(other.end.y()),
231 ),
232 }
233 }
234
235 pub fn from_origin(end: Point) -> Result<Self> {
236 Self::new((0, 0), end)
237 }
238
239 pub fn from_topleft(origin: Point, size: (usize, usize)) -> Result<Self> {
240 Self::new(
241 origin,
242 origin.translated_by(Point::from(size).translated(-1, -1)),
243 )
244 }
245
246 pub fn starting_from_topleft(&self, size: (usize, usize)) -> Result<Self> {
247 Self::from_topleft(self.start, size)
248 }
249
250 pub fn from_bottomleft(
251 origin: Point,
252 (w, h): (usize, usize),
253 ) -> Result<Self> {
254 Self::from_topleft(origin.translated(0, -(h as i32 - 1)), (w, h))
255 }
256
257 pub fn starting_from_bottomleft(&self, size: (usize, usize)) -> Result<Self> {
258 Self::from_bottomleft(self.bottomleft(), size)
259 }
260
261 pub fn from_bottomright(
262 origin: Point,
263 (w, h): (usize, usize),
264 ) -> Result<Self> {
265 Self::new(origin.translated(-(w as i32 - 1), -(h as i32 - 1)), origin)
266 }
267
268 pub fn starting_from_bottomright(
269 &self,
270 size: (usize, usize),
271 ) -> Result<Self> {
272 Self::from_bottomright(self.bottomright(), size)
273 }
274
275 pub fn from_topright(origin: Point, (w, h): (usize, usize)) -> Result<Self> {
276 Self::from_topleft(origin.translated(-(w as i32 - 1), 0), (w, h))
277 }
278
279 pub fn starting_from_topright(&self, size: (usize, usize)) -> Result<Self> {
280 Self::from_topright(self.topright(), size)
281 }
282
283 pub fn from_center_and_size(
284 center: Point,
285 (w, h): (usize, usize),
286 ) -> Result<Self> {
287 let (half_w, half_h) = (w / 2, h / 2);
288 Self::new(
289 (center.x() - half_w, center.y() - half_h),
290 (center.x() + half_w, center.y() + half_h),
291 )
292 }
293
294 // panics if the region is invalid
295 pub fn ensure_valid(self) -> Result<Self> {
296 if self.start.x() > self.end.x() || self.start.y() > self.end.y() {
297 return Err(format_err!(
298 "Invalid region: start ({:?}) > end ({:?})",
299 self.start,
300 self.end
301 ));
302 }
303
304 // check that no point's coordinate is too close to usize::MAX
305 if vec![self.start.x(), self.start.y(), self.end.x(), self.end.y()]
306 .iter()
307 .any(|&coord| coord >= usize::MAX - 10)
308 {
309 return Err(format_err!(
310 "Invalid region: coordinate very close to usize::MAX in region {:?}",
311 self
312 ));
313 }
314
315 Ok(self)
316 }
317
318 pub fn translate(&mut self, dx: i32, dy: i32) {
319 *self = self.translated(dx, dy);
320 }
321
322 pub fn translated(&self, dx: i32, dy: i32) -> Self {
323 Self {
324 start: (
325 (self.start.x() as i32 + dx).max(0) as usize,
326 (self.start.y() as i32 + dy).max(0) as usize,
327 )
328 .into(),
329 end: (
330 (self.end.x() as i32 + dx).max(0) as usize,
331 (self.end.y() as i32 + dy).max(0) as usize,
332 )
333 .into(),
334 }
335 }
336
337 pub fn translated_by(&self, point: Point) -> Self {
338 let (x, y) = point.xy::<i32>();
339 self.translated(x, y)
340 }
341
342 /// adds dx and dy to the end of the region (dx and dy are _not_ multiplicative but **additive** factors)
343 pub fn enlarged(&self, add_x: i32, add_y: i32) -> Self {
344 let resulting = Self {
345 start: self.start,
346 end: (
347 (self.end.x().saturating_add_signed(add_x as _)),
348 (self.end.y().saturating_add_signed(add_y as _)),
349 )
350 .into(),
351 };
352
353 resulting
354 .ensure_valid()
355 .map_err(|e| {
356 anyhow!(
357 "Invalid enlargement of ({add_x}, {add_y}) on {self:?}: {e:?}"
358 )
359 })
360 .unwrap()
361 }
362
363 /// resized is like enlarged, but transforms from the center, by first translating the region by (-dx, -dy)
364 pub fn resized(&self, add_x: i32, add_y: i32) -> Self {
365 self.translated(-add_x / 2, -add_y / 2)
366 .enlarged(add_x, add_y)
367 }
368
369 pub fn x_range(&self) -> std::ops::RangeInclusive<usize> {
370 self.start.x()..=self.end.x()
371 }
372 pub fn y_range(&self) -> std::ops::RangeInclusive<usize> {
373 self.start.y()..=self.end.y()
374 }
375
376 pub fn x_range_without_last(&self) -> std::ops::Range<usize> {
377 self.start.x()..self.end.x()
378 }
379
380 pub fn y_range_without_last(&self) -> std::ops::Range<usize> {
381 self.start.y()..self.end.y()
382 }
383
384 pub fn within(&self, other: &Region) -> bool {
385 self.start.x() >= other.start.x()
386 && self.start.y() >= other.start.y()
387 && self.end.x() <= other.end.x()
388 && self.end.y() <= other.end.y()
389 }
390
391 pub fn clamped(&self, within: &Region) -> Region {
392 Region {
393 start: (
394 self.start.x().max(within.start.x()),
395 self.start.y().max(within.start.y()),
396 )
397 .into(),
398 end: (
399 self.end.x().min(within.end.x()),
400 self.end.y().min(within.end.y()),
401 )
402 .into(),
403 }
404 }
405
406 pub fn width(&self) -> usize {
407 if self.end.x() < self.start.x() {
408 return 0;
409 }
410
411 self.end.x() - self.start.x() + 1
412 }
413
414 pub fn height(&self) -> usize {
415 let (sy, ey) = (self.start.y(), self.end.y());
416
417 if ey < sy {
418 return 0;
419 }
420
421 ey.checked_sub(sy)
422 .expect(&format!("{self:?} overflows when computing height"))
423 .checked_add(1)
424 .expect(&format!(
425 "{self:?} overflows when adjusting height computation"
426 ))
427 }
428
429 pub fn dimensions(&self) -> (usize, usize) {
430 (self.width(), self.height())
431 }
432
433 pub fn size(&self, cell_size: usize) -> (f32, f32) {
434 (
435 (self.width() * cell_size) as f32,
436 (self.height() * cell_size) as f32,
437 )
438 }
439
440 // goes from -width to width (inclusive on both ends)
441 pub fn mirrored_width_range(&self) -> std::ops::RangeInclusive<i32> {
442 let w = self.width() as i32;
443 -w..=w
444 }
445
446 pub fn mirrored_height_range(&self) -> std::ops::RangeInclusive<i32> {
447 let h = self.height() as i32;
448 -h..=h
449 }
450
451 pub fn split(&self, along: Axis) -> (Region, Region) {
452 match along {
453 Axis::Horizontal => (
454 Region {
455 start: self.start,
456 end: Point::Corner(self.end.x(), self.end.y() / 2),
457 },
458 Region {
459 start: Point::Corner(self.start.x(), self.end.y() / 2),
460 end: self.end,
461 },
462 ),
463 Axis::Vertical => (
464 Region {
465 start: self.start,
466 end: Point::Corner(self.end.x() / 2, self.end.y()),
467 },
468 Region {
469 start: Point::Corner(self.end.x() / 2, self.start.y()),
470 end: self.end,
471 },
472 ),
473 }
474 }
475}
476
477pub trait Containable<T> {
478 fn contains(&self, value: &T) -> bool;
479}
480
481impl Containable<Point> for Region {
482 fn contains(&self, value: &Point) -> bool {
483 self.x_range().contains(&value.x()) && self.y_range().contains(&value.y())
484 }
485}
486
487impl std::fmt::Display for Region {
488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489 write!(f, "[{},{}]", self.start, self.end)
490 }
491}