412 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			412 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								from __future__ import annotations
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import itertools
							 | 
						||
| 
								 | 
							
								import math
							 | 
						||
| 
								 | 
							
								import random
							 | 
						||
| 
								 | 
							
								from typing import TYPE_CHECKING, Iterable, List, Set, Tuple, Union
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from s2clientprotocol import common_pb2 as common_pb
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if TYPE_CHECKING:
							 | 
						||
| 
								 | 
							
								    from .unit import Unit
							 | 
						||
| 
								 | 
							
								    from .units import Units
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								EPSILON = 10**-8
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _sign(num):
							 | 
						||
| 
								 | 
							
								    return math.copysign(1, num)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Pointlike(tuple):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def position(self) -> Pointlike:
							 | 
						||
| 
								 | 
							
								        return self
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def distance_to(self, target: Union[Unit, Point2]) -> float:
							 | 
						||
| 
								 | 
							
								        """Calculate a single distance from a point or unit to another point or unit
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param target:"""
							 | 
						||
| 
								 | 
							
								        p = target.position
							 | 
						||
| 
								 | 
							
								        return math.hypot(self[0] - p[0], self[1] - p[1])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def distance_to_point2(self, p: Union[Point2, Tuple[float, float]]) -> float:
							 | 
						||
| 
								 | 
							
								        """Same as the function above, but should be a bit faster because of the dropped asserts
							 | 
						||
| 
								 | 
							
								        and conversion.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param p:"""
							 | 
						||
| 
								 | 
							
								        return math.hypot(self[0] - p[0], self[1] - p[1])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _distance_squared(self, p2: Point2) -> float:
							 | 
						||
| 
								 | 
							
								        """Function used to not take the square root as the distances will stay proportionally the same.
							 | 
						||
| 
								 | 
							
								        This is to speed up the sorting process.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param p2:"""
							 | 
						||
| 
								 | 
							
								        return (self[0] - p2[0])**2 + (self[1] - p2[1])**2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def sort_by_distance(self, ps: Union[Units, Iterable[Point2]]) -> List[Point2]:
							 | 
						||
| 
								 | 
							
								        """This returns the target points sorted as list.
							 | 
						||
| 
								 | 
							
								        You should not pass a set or dict since those are not sortable.
							 | 
						||
| 
								 | 
							
								        If you want to sort your units towards a point, use 'units.sorted_by_distance_to(point)' instead.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param ps:"""
							 | 
						||
| 
								 | 
							
								        return sorted(ps, key=lambda p: self.distance_to_point2(p.position))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def closest(self, ps: Union[Units, Iterable[Point2]]) -> Union[Unit, Point2]:
							 | 
						||
| 
								 | 
							
								        """This function assumes the 2d distance is meant
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param ps:"""
							 | 
						||
| 
								 | 
							
								        assert ps, "ps is empty"
							 | 
						||
| 
								 | 
							
								        # pylint: disable=W0108
							 | 
						||
| 
								 | 
							
								        return min(ps, key=lambda p: self.distance_to(p))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def distance_to_closest(self, ps: Union[Units, Iterable[Point2]]) -> float:
							 | 
						||
| 
								 | 
							
								        """This function assumes the 2d distance is meant
							 | 
						||
| 
								 | 
							
								        :param ps:"""
							 | 
						||
| 
								 | 
							
								        assert ps, "ps is empty"
							 | 
						||
| 
								 | 
							
								        closest_distance = math.inf
							 | 
						||
| 
								 | 
							
								        for p2 in ps:
							 | 
						||
| 
								 | 
							
								            p2 = p2.position
							 | 
						||
| 
								 | 
							
								            distance = self.distance_to(p2)
							 | 
						||
| 
								 | 
							
								            if distance <= closest_distance:
							 | 
						||
| 
								 | 
							
								                closest_distance = distance
							 | 
						||
| 
								 | 
							
								        return closest_distance
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def furthest(self, ps: Union[Units, Iterable[Point2]]) -> Union[Unit, Pointlike]:
							 | 
						||
| 
								 | 
							
								        """This function assumes the 2d distance is meant
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param ps: Units object, or iterable of Unit or Point2"""
							 | 
						||
| 
								 | 
							
								        assert ps, "ps is empty"
							 | 
						||
| 
								 | 
							
								        # pylint: disable=W0108
							 | 
						||
| 
								 | 
							
								        return max(ps, key=lambda p: self.distance_to(p))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def distance_to_furthest(self, ps: Union[Units, Iterable[Point2]]) -> float:
							 | 
						||
| 
								 | 
							
								        """This function assumes the 2d distance is meant
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param ps:"""
							 | 
						||
| 
								 | 
							
								        assert ps, "ps is empty"
							 | 
						||
| 
								 | 
							
								        furthest_distance = -math.inf
							 | 
						||
| 
								 | 
							
								        for p2 in ps:
							 | 
						||
| 
								 | 
							
								            p2 = p2.position
							 | 
						||
| 
								 | 
							
								            distance = self.distance_to(p2)
							 | 
						||
| 
								 | 
							
								            if distance >= furthest_distance:
							 | 
						||
| 
								 | 
							
								                furthest_distance = distance
							 | 
						||
| 
								 | 
							
								        return furthest_distance
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def offset(self, p) -> Pointlike:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param p:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self.__class__(a + b for a, b in itertools.zip_longest(self, p[:len(self)], fillvalue=0))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def unit_axes_towards(self, p):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param p:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self.__class__(_sign(b - a) for a, b in itertools.zip_longest(self, p[:len(self)], fillvalue=0))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def towards(self, p: Union[Unit, Pointlike], distance: Union[int, float] = 1, limit: bool = False) -> Pointlike:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param p:
							 | 
						||
| 
								 | 
							
								        :param distance:
							 | 
						||
| 
								 | 
							
								        :param limit:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        p = p.position
							 | 
						||
| 
								 | 
							
								        # assert self != p, f"self is {self}, p is {p}"
							 | 
						||
| 
								 | 
							
								        # TODO test and fix this if statement
							 | 
						||
| 
								 | 
							
								        if self == p:
							 | 
						||
| 
								 | 
							
								            return self
							 | 
						||
| 
								 | 
							
								        # end of test
							 | 
						||
| 
								 | 
							
								        d = self.distance_to(p)
							 | 
						||
| 
								 | 
							
								        if limit:
							 | 
						||
| 
								 | 
							
								            distance = min(d, distance)
							 | 
						||
| 
								 | 
							
								        return self.__class__(
							 | 
						||
| 
								 | 
							
								            a + (b - a) / d * distance for a, b in itertools.zip_longest(self, p[:len(self)], fillvalue=0)
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __eq__(self, other):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            return all(abs(a - b) <= EPSILON for a, b in itertools.zip_longest(self, other, fillvalue=0))
							 | 
						||
| 
								 | 
							
								        except TypeError:
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __hash__(self):
							 | 
						||
| 
								 | 
							
								        return hash(tuple(self))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# pylint: disable=R0904
							 | 
						||
| 
								 | 
							
								class Point2(Pointlike):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def from_proto(cls, data) -> Point2:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        :param data:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return cls((data.x, data.y))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def as_Point2D(self) -> common_pb.Point2D:
							 | 
						||
| 
								 | 
							
								        return common_pb.Point2D(x=self.x, y=self.y)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def as_PointI(self) -> common_pb.PointI:
							 | 
						||
| 
								 | 
							
								        """Represents points on the minimap. Values must be between 0 and 64."""
							 | 
						||
| 
								 | 
							
								        return common_pb.PointI(x=self.x, y=self.y)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def rounded(self) -> Point2:
							 | 
						||
| 
								 | 
							
								        return Point2((math.floor(self[0]), math.floor(self[1])))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def length(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ This property exists in case Point2 is used as a vector. """
							 | 
						||
| 
								 | 
							
								        return math.hypot(self[0], self[1])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def normalized(self) -> Point2:
							 | 
						||
| 
								 | 
							
								        """ This property exists in case Point2 is used as a vector. """
							 | 
						||
| 
								 | 
							
								        length = self.length
							 | 
						||
| 
								 | 
							
								        # Cannot normalize if length is zero
							 | 
						||
| 
								 | 
							
								        assert length
							 | 
						||
| 
								 | 
							
								        return self.__class__((self[0] / length, self[1] / length))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def x(self) -> float:
							 | 
						||
| 
								 | 
							
								        return self[0]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def y(self) -> float:
							 | 
						||
| 
								 | 
							
								        return self[1]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def to2(self) -> Point2:
							 | 
						||
| 
								 | 
							
								        return Point2(self[:2])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def to3(self) -> Point3:
							 | 
						||
| 
								 | 
							
								        return Point3((*self, 0))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def round(self, decimals: int) -> Point2:
							 | 
						||
| 
								 | 
							
								        """Rounds each number in the tuple to the amount of given decimals."""
							 | 
						||
| 
								 | 
							
								        return Point2((round(self[0], decimals), round(self[1], decimals)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def offset(self, p: Point2) -> Point2:
							 | 
						||
| 
								 | 
							
								        return Point2((self[0] + p[0], self[1] + p[1]))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def random_on_distance(self, distance) -> Point2:
							 | 
						||
| 
								 | 
							
								        if isinstance(distance, (tuple, list)):  # interval
							 | 
						||
| 
								 | 
							
								            distance = distance[0] + random.random() * (distance[1] - distance[0])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        assert distance > 0, "Distance is not greater than 0"
							 | 
						||
| 
								 | 
							
								        angle = random.random() * 2 * math.pi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        dx, dy = math.cos(angle), math.sin(angle)
							 | 
						||
| 
								 | 
							
								        return Point2((self.x + dx * distance, self.y + dy * distance))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def towards_with_random_angle(
							 | 
						||
| 
								 | 
							
								        self,
							 | 
						||
| 
								 | 
							
								        p: Union[Point2, Point3],
							 | 
						||
| 
								 | 
							
								        distance: Union[int, float] = 1,
							 | 
						||
| 
								 | 
							
								        max_difference: Union[int, float] = (math.pi / 4),
							 | 
						||
| 
								 | 
							
								    ) -> Point2:
							 | 
						||
| 
								 | 
							
								        tx, ty = self.to2.towards(p.to2, 1)
							 | 
						||
| 
								 | 
							
								        angle = math.atan2(ty - self.y, tx - self.x)
							 | 
						||
| 
								 | 
							
								        angle = (angle - max_difference) + max_difference * 2 * random.random()
							 | 
						||
| 
								 | 
							
								        return Point2((self.x + math.cos(angle) * distance, self.y + math.sin(angle) * distance))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def circle_intersection(self, p: Point2, r: Union[int, float]) -> Set[Point2]:
							 | 
						||
| 
								 | 
							
								        """self is point1, p is point2, r is the radius for circles originating in both points
							 | 
						||
| 
								 | 
							
								        Used in ramp finding
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param p:
							 | 
						||
| 
								 | 
							
								        :param r:"""
							 | 
						||
| 
								 | 
							
								        assert self != p, "self is equal to p"
							 | 
						||
| 
								 | 
							
								        distanceBetweenPoints = self.distance_to(p)
							 | 
						||
| 
								 | 
							
								        assert r >= distanceBetweenPoints / 2
							 | 
						||
| 
								 | 
							
								        # remaining distance from center towards the intersection, using pythagoras
							 | 
						||
| 
								 | 
							
								        remainingDistanceFromCenter = (r**2 - (distanceBetweenPoints / 2)**2)**0.5
							 | 
						||
| 
								 | 
							
								        # center of both points
							 | 
						||
| 
								 | 
							
								        offsetToCenter = Point2(((p.x - self.x) / 2, (p.y - self.y) / 2))
							 | 
						||
| 
								 | 
							
								        center = self.offset(offsetToCenter)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # stretch offset vector in the ratio of remaining distance from center to intersection
							 | 
						||
| 
								 | 
							
								        vectorStretchFactor = remainingDistanceFromCenter / (distanceBetweenPoints / 2)
							 | 
						||
| 
								 | 
							
								        v = offsetToCenter
							 | 
						||
| 
								 | 
							
								        offsetToCenterStretched = Point2((v.x * vectorStretchFactor, v.y * vectorStretchFactor))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # rotate vector by 90° and -90°
							 | 
						||
| 
								 | 
							
								        vectorRotated1 = Point2((offsetToCenterStretched.y, -offsetToCenterStretched.x))
							 | 
						||
| 
								 | 
							
								        vectorRotated2 = Point2((-offsetToCenterStretched.y, offsetToCenterStretched.x))
							 | 
						||
| 
								 | 
							
								        intersect1 = center.offset(vectorRotated1)
							 | 
						||
| 
								 | 
							
								        intersect2 = center.offset(vectorRotated2)
							 | 
						||
| 
								 | 
							
								        return {intersect1, intersect2}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def neighbors4(self) -> set:
							 | 
						||
| 
								 | 
							
								        return {
							 | 
						||
| 
								 | 
							
								            Point2((self.x - 1, self.y)),
							 | 
						||
| 
								 | 
							
								            Point2((self.x + 1, self.y)),
							 | 
						||
| 
								 | 
							
								            Point2((self.x, self.y - 1)),
							 | 
						||
| 
								 | 
							
								            Point2((self.x, self.y + 1)),
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def neighbors8(self) -> set:
							 | 
						||
| 
								 | 
							
								        return self.neighbors4 | {
							 | 
						||
| 
								 | 
							
								            Point2((self.x - 1, self.y - 1)),
							 | 
						||
| 
								 | 
							
								            Point2((self.x - 1, self.y + 1)),
							 | 
						||
| 
								 | 
							
								            Point2((self.x + 1, self.y - 1)),
							 | 
						||
| 
								 | 
							
								            Point2((self.x + 1, self.y + 1)),
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def negative_offset(self, other: Point2) -> Point2:
							 | 
						||
| 
								 | 
							
								        return self.__class__((self[0] - other[0], self[1] - other[1]))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __add__(self, other: Point2) -> Point2:
							 | 
						||
| 
								 | 
							
								        return self.offset(other)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __sub__(self, other: Point2) -> Point2:
							 | 
						||
| 
								 | 
							
								        return self.negative_offset(other)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __neg__(self) -> Point2:
							 | 
						||
| 
								 | 
							
								        return self.__class__(-a for a in self)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __abs__(self) -> float:
							 | 
						||
| 
								 | 
							
								        return math.hypot(self.x, self.y)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __bool__(self) -> bool:
							 | 
						||
| 
								 | 
							
								        if self.x != 0 or self.y != 0:
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								        return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __mul__(self, other: Union[int, float, Point2]) -> Point2:
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            return self.__class__((self.x * other.x, self.y * other.y))
							 | 
						||
| 
								 | 
							
								        except AttributeError:
							 | 
						||
| 
								 | 
							
								            return self.__class__((self.x * other, self.y * other))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __rmul__(self, other: Union[int, float, Point2]) -> Point2:
							 | 
						||
| 
								 | 
							
								        return self.__mul__(other)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __truediv__(self, other: Union[int, float, Point2]) -> Point2:
							 | 
						||
| 
								 | 
							
								        if isinstance(other, self.__class__):
							 | 
						||
| 
								 | 
							
								            return self.__class__((self.x / other.x, self.y / other.y))
							 | 
						||
| 
								 | 
							
								        return self.__class__((self.x / other, self.y / other))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def is_same_as(self, other: Point2, dist=0.001) -> bool:
							 | 
						||
| 
								 | 
							
								        return self.distance_to_point2(other) <= dist
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def direction_vector(self, other: Point2) -> Point2:
							 | 
						||
| 
								 | 
							
								        """ Converts a vector to a direction that can face vertically, horizontally or diagonal or be zero, e.g. (0, 0), (1, -1), (1, 0) """
							 | 
						||
| 
								 | 
							
								        return self.__class__((_sign(other.x - self.x), _sign(other.y - self.y)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def manhattan_distance(self, other: Point2) -> float:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        :param other:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return abs(other.x - self.x) + abs(other.y - self.y)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @staticmethod
							 | 
						||
| 
								 | 
							
								    def center(points: List[Point2]) -> Point2:
							 | 
						||
| 
								 | 
							
								        """Returns the central point for points in list
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :param points:"""
							 | 
						||
| 
								 | 
							
								        s = Point2((0, 0))
							 | 
						||
| 
								 | 
							
								        for p in points:
							 | 
						||
| 
								 | 
							
								            s += p
							 | 
						||
| 
								 | 
							
								        return s / len(points)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Point3(Point2):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def from_proto(cls, data) -> Point3:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        :param data:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return cls((data.x, data.y, data.z))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def as_Point(self) -> common_pb.Point:
							 | 
						||
| 
								 | 
							
								        return common_pb.Point(x=self.x, y=self.y, z=self.z)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def rounded(self) -> Point3:
							 | 
						||
| 
								 | 
							
								        return Point3((math.floor(self[0]), math.floor(self[1]), math.floor(self[2])))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def z(self) -> float:
							 | 
						||
| 
								 | 
							
								        return self[2]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def to3(self) -> Point3:
							 | 
						||
| 
								 | 
							
								        return Point3(self)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __add__(self, other: Union[Point2, Point3]) -> Point3:
							 | 
						||
| 
								 | 
							
								        if not isinstance(other, Point3) and isinstance(other, Point2):
							 | 
						||
| 
								 | 
							
								            return Point3((self.x + other.x, self.y + other.y, self.z))
							 | 
						||
| 
								 | 
							
								        return Point3((self.x + other.x, self.y + other.y, self.z + other.z))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Size(Point2):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def width(self) -> float:
							 | 
						||
| 
								 | 
							
								        return self[0]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def height(self) -> float:
							 | 
						||
| 
								 | 
							
								        return self[1]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Rect(tuple):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def from_proto(cls, data):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        :param data:
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        assert data.p0.x < data.p1.x and data.p0.y < data.p1.y
							 | 
						||
| 
								 | 
							
								        return cls((data.p0.x, data.p0.y, data.p1.x - data.p0.x, data.p1.y - data.p0.y))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def x(self) -> float:
							 | 
						||
| 
								 | 
							
								        return self[0]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def y(self) -> float:
							 | 
						||
| 
								 | 
							
								        return self[1]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def width(self) -> float:
							 | 
						||
| 
								 | 
							
								        return self[2]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def height(self) -> float:
							 | 
						||
| 
								 | 
							
								        return self[3]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def right(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the x-coordinate of the rectangle of its right side. """
							 | 
						||
| 
								 | 
							
								        return self.x + self.width
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def top(self) -> float:
							 | 
						||
| 
								 | 
							
								        """ Returns the y-coordinate of the rectangle of its top side. """
							 | 
						||
| 
								 | 
							
								        return self.y + self.height
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def size(self) -> Size:
							 | 
						||
| 
								 | 
							
								        return Size((self[2], self[3]))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def center(self) -> Point2:
							 | 
						||
| 
								 | 
							
								        return Point2((self.x + self.width / 2, self.y + self.height / 2))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def offset(self, p):
							 | 
						||
| 
								 | 
							
								        return self.__class__((self[0] + p[0], self[1] + p[1], self[2], self[3]))
							 |