expand coordinate.Line functionality

This commit is contained in:
Stefan Harmuth 2023-12-28 06:07:39 +01:00
parent f7d1fde5b7
commit 8dea09c30f

View File

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