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
 | 
						|
 |