diff --git a/src/tools/coordinate.py b/src/tools/coordinate.py index dce56dc..1e1a2d0 100644 --- a/src/tools/coordinate.py +++ b/src/tools/coordinate.py @@ -2,7 +2,7 @@ from __future__ import annotations from enum import Enum from math import gcd, sqrt, inf, atan2, degrees, isclose from .math import round_half_up -from typing import Union, List +from typing import Union, List, Iterable from .tools import minmax @@ -379,6 +379,16 @@ class Shape: def __rand__(self, other): return self.intersection(other) + def __contains__(self, item: Coordinate) -> bool: + if not self.mode_3d: + return self.top_left.x <= item.x <= self.bottom_right.x and self.top_left.y <= item.y <= self.bottom_right.y + else: + return ( + self.top_left.x <= item.x <= self.bottom_right.x + and self.top_left.y <= item.y <= self.bottom_right.y + and self.top_left.z <= item.z <= self.bottom_right.z + ) + def __str__(self): return "%s(%s -> %s)" % ( self.__class__.__name__, @@ -394,9 +404,9 @@ class Shape: ) -class Square(Shape): +class Rectangle(Shape): def __init__(self, top_left, bottom_right): - super(Square, self).__init__(top_left, bottom_right) + super(Rectangle, self).__init__(top_left, bottom_right) self.mode_3d = False @@ -407,6 +417,7 @@ class Cube(Shape): super(Cube, self).__init__(top_left, bottom_right) +# FIXME: Line could probably also just be a subclass of Shape class Line: def __init__(self, start: Coordinate, end: Coordinate): if start[2] is not None or end[2] is not None: @@ -422,12 +433,6 @@ class Line: def connects_to(self, other: Line) -> bool: return self.start == other.start or self.start == other.end or self.end == other.start or self.end == other.end - def contains(self, point: Coordinate | tuple) -> bool: - return isclose( - self.start.getDistanceTo(self.end), - self.start.getDistanceTo(point) + self.end.getDistanceTo(point), - ) - def intersects(self, other: Line, strict: bool = True) -> bool: try: self.get_intersection(other, strict=strict) @@ -454,7 +459,7 @@ class Line: if not strict: return ret else: - if self.contains(ret) and other.contains(ret): + if ret in self and ret in other: return ret else: raise ValueError("intersection out of bounds") @@ -462,11 +467,79 @@ class Line: def __hash__(self): return hash((self.start, self.end)) + def __eq__(self, other: Line) -> bool: + return hash(self) == hash(other) + def __lt__(self, other: Line) -> bool: return self.start < other.start + def __contains__(self, point: Coordinate | tuple) -> bool: + return isclose( + self.start.getDistanceTo(self.end), + self.start.getDistanceTo(point) + self.end.getDistanceTo(point), + ) + + def __len__(self) -> int: + return int(self.start.getDistanceTo(self.end)) + def __str__(self): return f"Line({self.start} -> {self.end})" def __repr__(self): return str(self) + + +class Polygon: + def __init__(self, points: list[Coordinate]) -> None: + """points have to be in (counter)clockwise order, not repeating the first coordinate""" + if len(set(points)) != len(points): + raise ValueError("Polygon contains repeated points") + + self.points = points + self.lines = set() + for i in range(len(points) - 1): + self.lines.add(Line(points[i], points[i + 1])) + self.lines.add(Line(points[-1], points[0])) + + def get_circumference(self) -> float: + return sum(len(x) for x in self.lines) + + def get_area(self) -> float: + S = 0 + for i in range(len(self.points)): + S += ( + self.points[i].x * self.points[(i + 1) % len(self.points)].y + - self.points[(i + 1) % len(self.points)].x * self.points[i].y + ) + + return abs(S) / 2 + + def decompose(self) -> Iterable[Rectangle]: + points_left = list(self.points) + + def flip(point: Coordinate): + if point in points_left: + points_left.remove(point) + else: + points_left.append(point) + + while points_left: + pk, pl, pm = None, None, None + for c in sorted(points_left, key=lambda p: (p[1], p[0])): + if pk is None: + pk = c + continue + + if pl is None: + pl = c + continue + + if pk.x <= c.x < pl.x and pk.y < c.y: + pm = c + break + + flip(pk) + flip(pl) + flip(Coordinate(pk.x, pm.y)) + flip(Coordinate(pl.x, pm.y)) + yield Rectangle(pk, Coordinate(pl.x, pm.y))