345 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			345 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								from math import sqrt
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Point(object):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    __slots__ = ('x', 'y')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, x, y):
							 | 
						||
| 
								 | 
							
								        self.x = x
							 | 
						||
| 
								 | 
							
								        self.y = y
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __eq__(self, other):
							 | 
						||
| 
								 | 
							
								        return (self.x == other.x and self.y == other.y)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __repr__(self):
							 | 
						||
| 
								 | 
							
								        return "P({}, {})".format(self.x, self.y)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def distance(self, point):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Calculate distance to another point
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return sqrt((self.x-point.x)**2+(self.y-point.y)**2)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def distance_squared(self, point):
							 | 
						||
| 
								 | 
							
								        return (self.x-point.x)**2+(self.y-point.y)**2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Segment(object):
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    __slots__ = ('start', 'end')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, start, end):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            start (Point): Segment start point
							 | 
						||
| 
								 | 
							
								            end (Point): Segment end point
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        assert(isinstance(start, Point) and isinstance(end, Point))
							 | 
						||
| 
								 | 
							
								        self.start = start
							 | 
						||
| 
								 | 
							
								        self.end = end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __eq__(self, other):
							 | 
						||
| 
								 | 
							
								        if not isinstance(other, self.__class__):
							 | 
						||
| 
								 | 
							
								            None
							 | 
						||
| 
								 | 
							
								        return self.start==other.start and self.end==other.end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __repr__(self):
							 | 
						||
| 
								 | 
							
								        return "S({}, {})".format(self.start, self.end)
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def length_squared(self):
							 | 
						||
| 
								 | 
							
								        """Faster than length and useful for some comparisons"""
							 | 
						||
| 
								 | 
							
								        return self.start.distance_squared(self.end)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def length(self):
							 | 
						||
| 
								 | 
							
								        return self.start.distance(self.end)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def top(self):
							 | 
						||
| 
								 | 
							
								        return max(self.start.y, self.end.y)
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def bottom(self):
							 | 
						||
| 
								 | 
							
								        return min(self.start.y, self.end.y)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def right(self):
							 | 
						||
| 
								 | 
							
								        return max(self.start.x, self.end.x)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def left(self):
							 | 
						||
| 
								 | 
							
								        return min(self.start.x, self.end.x)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class HSegment(Segment):
							 | 
						||
| 
								 | 
							
								    """Horizontal Segment""" 
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, start, length):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Create an Horizontal segment given its left most end point and its
							 | 
						||
| 
								 | 
							
								        length.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            - start (Point): Starting Point
							 | 
						||
| 
								 | 
							
								            - length (number): segment length
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        assert(isinstance(start, Point) and not isinstance(length, Point))
							 | 
						||
| 
								 | 
							
								        super(HSegment, self).__init__(start, Point(start.x+length, start.y))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def length(self):
							 | 
						||
| 
								 | 
							
								        return self.end.x-self.start.x
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class VSegment(Segment):
							 | 
						||
| 
								 | 
							
								    """Vertical Segment"""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, start, length):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Create a Vertical segment given its bottom most end point and its
							 | 
						||
| 
								 | 
							
								        length.
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            - start (Point): Starting Point
							 | 
						||
| 
								 | 
							
								            - length (number): segment length
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        assert(isinstance(start, Point) and not isinstance(length, Point))
							 | 
						||
| 
								 | 
							
								        super(VSegment, self).__init__(start, Point(start.x, start.y+length))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def length(self):
							 | 
						||
| 
								 | 
							
								        return self.end.y-self.start.y
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Rectangle(object):
							 | 
						||
| 
								 | 
							
								    """Basic rectangle primitive class.
							 | 
						||
| 
								 | 
							
								    x, y-> Lower right corner coordinates
							 | 
						||
| 
								 | 
							
								    width - 
							 | 
						||
| 
								 | 
							
								    height - 
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    __slots__ = ('width', 'height', 'x', 'y', 'rid')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, x, y, width, height, rid = None):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Args:
							 | 
						||
| 
								 | 
							
								            x (int, float):
							 | 
						||
| 
								 | 
							
								            y (int, float):
							 | 
						||
| 
								 | 
							
								            width (int, float):
							 | 
						||
| 
								 | 
							
								            height (int, float):
							 | 
						||
| 
								 | 
							
								            rid (int):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        assert(height >=0 and width >=0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.width = width
							 | 
						||
| 
								 | 
							
								        self.height = height
							 | 
						||
| 
								 | 
							
								        self.x = x
							 | 
						||
| 
								 | 
							
								        self.y = y
							 | 
						||
| 
								 | 
							
								        self.rid = rid
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def bottom(self):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Rectangle bottom edge y coordinate
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self.y
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def top(self):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Rectangle top edge y coordiante
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self.y+self.height
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def left(self):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Rectangle left ednge x coordinate
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self.x
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def right(self):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Rectangle right edge x coordinate
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self.x+self.width
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def corner_top_l(self):
							 | 
						||
| 
								 | 
							
								        return Point(self.left, self.top)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def corner_top_r(self):
							 | 
						||
| 
								 | 
							
								        return Point(self.right, self.top)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def corner_bot_r(self):
							 | 
						||
| 
								 | 
							
								        return Point(self.right, self.bottom)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @property
							 | 
						||
| 
								 | 
							
								    def corner_bot_l(self):
							 | 
						||
| 
								 | 
							
								        return Point(self.left, self.bottom)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __lt__(self, other):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Compare rectangles by area (used for sorting)
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self.area() < other.area()
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    def __eq__(self, other):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Equal rectangles have same area.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if not isinstance(other, self.__class__):
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return (self.width == other.width and \
							 | 
						||
| 
								 | 
							
								                self.height == other.height and \
							 | 
						||
| 
								 | 
							
								                self.x == other.x and \
							 | 
						||
| 
								 | 
							
								                self.y == other.y)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __hash__(self):
							 | 
						||
| 
								 | 
							
								        return hash((self.x, self.y, self.width, self.height))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __iter__(self):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Iterate through rectangle corners
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        yield self.corner_top_l
							 | 
						||
| 
								 | 
							
								        yield self.corner_top_r
							 | 
						||
| 
								 | 
							
								        yield self.corner_bot_r
							 | 
						||
| 
								 | 
							
								        yield self.corner_bot_l
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __repr__(self):
							 | 
						||
| 
								 | 
							
								        return "R({}, {}, {}, {})".format(self.x, self.y, self.width, self.height)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def area(self):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Rectangle area
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return self.width * self.height
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def move(self, x, y):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Move Rectangle to x,y coordinates
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            x (int, float): X coordinate
							 | 
						||
| 
								 | 
							
								            y (int, float): Y coordinate
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        self.x = x
							 | 
						||
| 
								 | 
							
								        self.y = y
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def contains(self, rect):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Tests if another rectangle is contained by this one
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            rect (Rectangle): The other rectangle
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            bool: True if it is container, False otherwise
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        return (rect.y >= self.y and \
							 | 
						||
| 
								 | 
							
								                rect.x >= self.x and \
							 | 
						||
| 
								 | 
							
								                rect.y+rect.height <= self.y+self.height and \
							 | 
						||
| 
								 | 
							
								                rect.x+rect.width  <= self.x+self.width)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def intersects(self, rect, edges=False):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Detect intersections between this and another Rectangle.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Parameters:
							 | 
						||
| 
								 | 
							
								            rect (Rectangle): The other rectangle.
							 | 
						||
| 
								 | 
							
								            edges (bool): True to consider rectangles touching by their
							 | 
						||
| 
								 | 
							
								                edges or corners to be intersecting.
							 | 
						||
| 
								 | 
							
								                (Should have been named include_touching)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            bool: True if the rectangles intersect, False otherwise
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if edges:
							 | 
						||
| 
								 | 
							
								            if (self.bottom > rect.top or self.top < rect.bottom or\
							 | 
						||
| 
								 | 
							
								                self.left > rect.right or self.right < rect.left):
							 | 
						||
| 
								 | 
							
								                return False
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            if (self.bottom >= rect.top or self.top <= rect.bottom or
							 | 
						||
| 
								 | 
							
								                self.left >= rect.right or self.right <= rect.left):
							 | 
						||
| 
								 | 
							
								                return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def intersection(self, rect, edges=False):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Returns the rectangle resulting of the intersection between this and another 
							 | 
						||
| 
								 | 
							
								        rectangle. If the rectangles are only touching by their edges, and the 
							 | 
						||
| 
								 | 
							
								        argument 'edges' is True the rectangle returned will have an area of 0.
							 | 
						||
| 
								 | 
							
								        Returns None if there is no intersection.
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								             rect (Rectangle): The other rectangle.
							 | 
						||
| 
								 | 
							
								             edges (bool): If True Rectangles touching by their edges are 
							 | 
						||
| 
								 | 
							
								                considered to be intersection. In this case a rectangle of 
							 | 
						||
| 
								 | 
							
								                0 height or/and width will be returned.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            Rectangle: Intersection.
							 | 
						||
| 
								 | 
							
								            None: There was no intersection.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if not self.intersects(rect, edges=edges):
							 | 
						||
| 
								 | 
							
								            return None
							 | 
						||
| 
								 | 
							
								        
							 | 
						||
| 
								 | 
							
								        bottom = max(self.bottom, rect.bottom)
							 | 
						||
| 
								 | 
							
								        left = max(self.left, rect.left)
							 | 
						||
| 
								 | 
							
								        top = min(self.top, rect.top)
							 | 
						||
| 
								 | 
							
								        right = min(self.right, rect.right)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return Rectangle(left, bottom, right-left, top-bottom)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def join(self, other):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        Try to join a rectangle to this one, if the result is also a rectangle 
							 | 
						||
| 
								 | 
							
								        and the operation is successful and this rectangle is modified to the union.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Arguments:
							 | 
						||
| 
								 | 
							
								            other (Rectangle): Rectangle to join
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Returns:
							 | 
						||
| 
								 | 
							
								            bool: True when successfully joined, False otherwise
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if self.contains(other):
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if other.contains(self):
							 | 
						||
| 
								 | 
							
								            self.x = other.x
							 | 
						||
| 
								 | 
							
								            self.y = other.y
							 | 
						||
| 
								 | 
							
								            self.width = other.width
							 | 
						||
| 
								 | 
							
								            self.height = other.height
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if not self.intersects(other, edges=True):
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Other rectangle is Up/Down from this
							 | 
						||
| 
								 | 
							
								        if  self.left == other.left and self.width == other.width:
							 | 
						||
| 
								 | 
							
								            y_min = min(self.bottom, other.bottom)
							 | 
						||
| 
								 | 
							
								            y_max = max(self.top, other.top)  
							 | 
						||
| 
								 | 
							
								            self.y = y_min
							 | 
						||
| 
								 | 
							
								            self.height = y_max-y_min
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Other rectangle is Right/Left from this
							 | 
						||
| 
								 | 
							
								        if  self.bottom == other.bottom and self.height == other.height:
							 | 
						||
| 
								 | 
							
								            x_min = min(self.left, other.left)
							 | 
						||
| 
								 | 
							
								            x_max = max(self.right, other.right)
							 | 
						||
| 
								 | 
							
								            self.x = x_min
							 | 
						||
| 
								 | 
							
								            self.width = x_max-x_min
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return False
							 | 
						||
| 
								 | 
							
								
							 |