From 3fdf6f7f9b72931f1929c7d7f38ac7274ea19d5f Mon Sep 17 00:00:00 2001 From: salt-die <53280662+salt-die@users.noreply.github.com> Date: Mon, 28 Aug 2023 16:35:14 -0500 Subject: [PATCH] `Region._merge_regions` and `Region._coalesce` changed to methods. --- rects/region.py | 77 ++++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/rects/region.py b/rects/region.py index 1e1e380..6e03189 100644 --- a/rects/region.py +++ b/rects/region.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from operator import or_, and_, xor from typing import Callable, Iterator @@ -49,6 +49,9 @@ class Rect: @dataclass(slots=True) class Band: + """ + A row of mutually exclusive rects. + """ y1: int y2: int walls: list[int] @@ -61,15 +64,15 @@ def __post_init__(self): @dataclass(slots=True) class Region: """ - Collection of mutually exclusive bands. + Collection of mutually exclusive bands of rects. """ - bands: list[Band] + bands: list[Band] = field(default_factory=[]) - @staticmethod - def _coalesce(bands: list[Band]): + def _coalesce(self): """ Join contiguous bands with the same walls to reduce rects. """ + bands = self.bands i = 0 while i < len(bands) - 1: a, b = bands[i], bands[i + 1] @@ -83,14 +86,13 @@ def _coalesce(bands: list[Band]): else: i += 1 - @classmethod - def _merge_regions(cls, op: BoolOp, a: "Region", b: "Region") -> "Region": + def _merge_regions(self, other: "Region", op: BoolOp) -> "Region": bands = [] i = j = 0 scanline = -float("inf") - while i < len(a.bands) and j < len(b.bands): - r, s = a.bands[i], b.bands[j] + while i < len(self.bands) and j < len(other.bands): + r, s = self.bands[i], other.bands[j] if r.y1 <= s.y1: if scanline < r.y1: scanline = r.y1 @@ -202,41 +204,35 @@ def _merge_regions(cls, op: BoolOp, a: "Region", b: "Region") -> "Region": j += 1 i += 1 - while i < len(a.bands): - r = a.bands[i] + while i < len(self.bands): + r = self.bands[i] if scanline < r.y1: scanline = r.y1 bands.append(Band(scanline, r.y2, _merge(op, r.walls, _NO_WALLS))) i += 1 - while j < len(b.bands): - s = b.bands[j] + while j < len(other.bands): + s = other.bands[j] if scanline < s.y1: scanline = s.y1 bands.append(Band(scanline, s.y2, _merge(op, _NO_WALLS, s.walls))) j += 1 - cls._coalesce(bands) - return cls(bands) - - @classmethod - def from_rect(cls, rect: Rect) -> "Region": - """ - Make a region from a `Rect`. - """ - return cls([Band(rect.y, rect.y + rect.height, [rect.x, rect.x + rect.width])]) + region = Region(bands=bands) + region._coalesce() + return region def __and__(self, other: "Region") -> "Region": - return Region._merge_regions(and_, self, other) + return self._merge_regions(other, and_) def __add__(self, other: "Region") -> "Region": - return Region._merge_regions(or_, self, other) + return self._merge_regions(other, or_) def __sub__(self, other: "Region") -> "Region": - return Region._merge_regions(sub, self, other) + return self._merge_regions(other, sub) def __xor__(self, other: "Region") -> "Region": - return Region._merge_regions(xor, self, other) + return self._merge_regions(other, xor) def rects(self) -> Iterator[Rect]: """ @@ -249,6 +245,25 @@ def rects(self) -> Iterator[Rect]: yield Rect(band.y1, band.walls[i], height, band.walls[i + 1] - band.walls[i]) i += 2 + @property + def bbox(self) -> Rect | None: + """ + Bounding box of region or None if region is empty. + """ + if not self.bands: + return + + y = self.bands[0].y1 + x = min(band.walls[0] for band in self.bands) + height = self.bands[-1].y2 - y + width = max(band.walls[-1] for band in self.bands) - x + + return Rect(y, x, height, width) + + @classmethod + def from_rect(cls, rect: Rect) -> "Region": + return cls([Band(rect.y, rect.y + rect.height, [rect.x, rect.x + rect.width])]) + if __name__ == "__main__": r = Region.from_rect(Rect(0, 0, 60, 60)) @@ -257,9 +272,13 @@ def rects(self) -> Iterator[Rect]: assert r == r - s + s - t + t - print(r - s - t) - for rect in (r - s - t).rects(): - print(rect) + v = r - s - t + + print("v:", v) + print("v.bbox:", v.bbox) + + for i, rect in enumerate(v.rects()): + print(f"rect {i} of v:", rect) try: Band(5, 3, [1, 2, 3])