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