This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

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}