diff --git a/src/tools/coordinate.py b/src/tools/coordinate.py index 1ebbc8e..dce56dc 100644 --- a/src/tools/coordinate.py +++ b/src/tools/coordinate.py @@ -3,6 +3,7 @@ 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 .tools import minmax class DistanceAlgorithm(Enum): @@ -49,14 +50,10 @@ class Coordinate(tuple): """ if algorithm == DistanceAlgorithm.EUCLIDEAN: if self[2] is None: - return sqrt( - abs(self[0] - target[0]) ** 2 + abs(self[1] - target[1]) ** 2 - ) + return sqrt(abs(self[0] - target[0]) ** 2 + abs(self[1] - target[1]) ** 2) else: return sqrt( - abs(self[0] - target[0]) ** 2 - + abs(self[1] - target[1]) ** 2 - + abs(self[2] - target[2]) ** 2 + abs(self[0] - target[0]) ** 2 + abs(self[1] - target[1]) ** 2 + abs(self[2] - target[2]) ** 2 ) elif algorithm == DistanceAlgorithm.CHEBYSHEV: if self[2] is None: @@ -72,11 +69,7 @@ class Coordinate(tuple): if self[2] is None: return abs(self[0] - target[0]) + abs(self[1] - target[1]) else: - return ( - abs(self[0] - target[0]) - + abs(self[1] - target[1]) - + abs(self[2] - target[2]) - ) + return abs(self[0] - target[0]) + abs(self[1] - target[1]) + abs(self[2] - target[2]) else: dist = [abs(self[0] - target[0]), abs(self[1] - target[1])] if self[2] is None: @@ -102,11 +95,7 @@ class Coordinate(tuple): if self[2] is None: return minX <= self[0] <= maxX and minY <= self[1] <= maxY else: - return ( - minX <= self[0] <= maxX - and minY <= self[1] <= maxY - and minZ <= self[2] <= maxZ - ) + return minX <= self[0] <= maxX and minY <= self[1] <= maxY and minZ <= self[2] <= maxZ def getCircle( self, @@ -126,11 +115,7 @@ class Coordinate(tuple): target = Coordinate(x, y) if not target.inBoundaries(minX, minY, maxX, maxY): continue - dist = round_half_up( - self.getDistanceTo( - target, algorithm=algorithm, includeDiagonals=False - ) - ) + dist = round_half_up(self.getDistanceTo(target, algorithm=algorithm, includeDiagonals=False)) if dist == radius: ret.append(target) @@ -141,11 +126,7 @@ class Coordinate(tuple): target = Coordinate(x, y) if not target.inBoundaries(minX, minY, maxX, maxY, minZ, maxZ): continue - dist = round_half_up( - self.getDistanceTo( - target, algorithm=algorithm, includeDiagonals=False - ) - ) + dist = round_half_up(self.getDistanceTo(target, algorithm=algorithm, includeDiagonals=False)) if dist == radius: ret.append(target) @@ -195,12 +176,7 @@ class Coordinate(tuple): yield self.__class__(self[0] + dx, self[1] + dy) else: if includeDiagonal: - nb_list = [ - (x, y, z) - for x in [-dist, 0, dist] - for y in [-dist, 0, dist] - for z in [-dist, 0, dist] - ] + nb_list = [(x, y, z) for x in [-dist, 0, dist] for y in [-dist, 0, dist] for z in [-dist, 0, dist]] nb_list.remove((0, 0, 0)) else: nb_list = [ @@ -213,11 +189,7 @@ class Coordinate(tuple): ] for dx, dy, dz in nb_list: - if ( - minX <= self[0] + dx <= maxX - and minY <= self[1] + dy <= maxY - and minZ <= self[2] + dz <= maxZ - ): + if minX <= self[0] + dx <= maxX and minY <= self[1] + dy <= maxY and minZ <= self[2] + dz <= maxZ: yield self.__class__(self[0] + dx, self[1] + dy, self[2] + dz) def getAngleTo(self, target: Coordinate | tuple, normalized: bool = False) -> float: @@ -246,19 +218,14 @@ class Coordinate(tuple): steps = gcd(diff[0], diff[1]) step_x = diff[0] // steps step_y = diff[1] // steps - return [ - self.__class__(self[0] + step_x * i, self[1] + step_y * i) - for i in range(steps + 1) - ] + return [self.__class__(self[0] + step_x * i, self[1] + step_y * i) for i in range(steps + 1)] else: steps = gcd(diff[0], diff[1], diff[2]) step_x = diff[0] // steps step_y = diff[1] // steps step_z = diff[2] // steps return [ - self.__class__( - self[0] + step_x * i, self[1] + step_y * i, self[2] + step_z * i - ) + self.__class__(self[0] + step_x * i, self[1] + step_y * i, self[2] + step_z * i) for i in range(steps + 1) ] @@ -281,17 +248,13 @@ class Coordinate(tuple): if self[2] is None: return self.__class__(self[0] + other[0], self[1] + other[1]) else: - return self.__class__( - self[0] + other[0], self[1] + other[1], self[2] + other[2] - ) + return self.__class__(self[0] + other[0], self[1] + other[1], self[2] + other[2]) def __sub__(self, other: Coordinate | tuple) -> Coordinate: if self[2] is None: return self.__class__(self[0] - other[0], self[1] - other[1]) else: - return self.__class__( - self[0] - other[0], self[1] - other[1], self[2] - other[2] - ) + return self.__class__(self[0] - other[0], self[1] - other[1], self[2] - other[2]) def __mul__(self, other: int | float) -> Coordinate: if self[2] is None: @@ -346,11 +309,7 @@ class Coordinate(tuple): step: int | float = 1, ) -> List[Coordinate]: if from_z is None or to_z is None: - return [ - cls(x, y) - for x in range(from_x, to_x + step, step) - for y in range(from_y, to_y + step, step) - ] + return [cls(x, y) for x in range(from_x, to_x + step, step) for y in range(from_y, to_y + step, step)] else: return [ cls(x, y, z) @@ -375,9 +334,7 @@ class Shape: def __len__(self): if not self.mode_3d: - return (self.bottom_right.x - self.top_left.x + 1) * ( - self.bottom_right.y - self.top_left.y + 1 - ) + return (self.bottom_right.x - self.top_left.x + 1) * (self.bottom_right.y - self.top_left.y + 1) else: return ( (self.bottom_right.x - self.top_left.x + 1) @@ -394,43 +351,23 @@ class Shape: if not self.mode_3d: intersect_top_left = Coordinate( - self.top_left.x - if self.top_left.x > other.top_left.x - else other.top_left.x, - self.top_left.y - if self.top_left.y > other.top_left.y - else other.top_left.y, + self.top_left.x if self.top_left.x > other.top_left.x else other.top_left.x, + self.top_left.y if self.top_left.y > other.top_left.y else other.top_left.y, ) intersect_bottom_right = Coordinate( - self.bottom_right.x - if self.bottom_right.x < other.bottom_right.x - else other.bottom_right.x, - self.bottom_right.y - if self.bottom_right.y < other.bottom_right.y - else other.bottom_right.y, + self.bottom_right.x if self.bottom_right.x < other.bottom_right.x else other.bottom_right.x, + self.bottom_right.y if self.bottom_right.y < other.bottom_right.y else other.bottom_right.y, ) else: intersect_top_left = Coordinate( - self.top_left.x - if self.top_left.x > other.top_left.x - else other.top_left.x, - self.top_left.y - if self.top_left.y > other.top_left.y - else other.top_left.y, - self.top_left.z - if self.top_left.z > other.top_left.z - else other.top_left.z, + self.top_left.x if self.top_left.x > other.top_left.x else other.top_left.x, + self.top_left.y if self.top_left.y > other.top_left.y else other.top_left.y, + self.top_left.z if self.top_left.z > other.top_left.z else other.top_left.z, ) intersect_bottom_right = Coordinate( - self.bottom_right.x - if self.bottom_right.x < other.bottom_right.x - else other.bottom_right.x, - self.bottom_right.y - if self.bottom_right.y < other.bottom_right.y - else other.bottom_right.y, - self.bottom_right.z - if self.bottom_right.z < other.bottom_right.z - else other.bottom_right.z, + self.bottom_right.x if self.bottom_right.x < other.bottom_right.x else other.bottom_right.x, + self.bottom_right.y if self.bottom_right.y < other.bottom_right.y else other.bottom_right.y, + self.bottom_right.z if self.bottom_right.z < other.bottom_right.z else other.bottom_right.z, ) if intersect_top_left <= intersect_bottom_right: @@ -474,8 +411,16 @@ class Line: def __init__(self, start: Coordinate, end: Coordinate): if start[2] is not None or end[2] is not None: raise NotImplementedError("3D Lines are hard(er)") - self.start = start - self.end = end + self.start, self.end = minmax(start, end) + + def is_horizontal(self) -> bool: + return self.start[1] == self.end[1] + + def is_vertical(self) -> bool: + return self.start[0] == self.end[0] + + 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( @@ -513,3 +458,15 @@ class Line: return ret else: raise ValueError("intersection out of bounds") + + def __hash__(self): + return hash((self.start, self.end)) + + def __lt__(self, other: Line) -> bool: + return self.start < other.start + + def __str__(self): + return f"Line({self.start} -> {self.end})" + + def __repr__(self): + return str(self)