Update Files
This commit is contained in:
344
leenkx/blender/lnx/lightmapper/utility/rectpack/geometry.py
Normal file
344
leenkx/blender/lnx/lightmapper/utility/rectpack/geometry.py
Normal file
@ -0,0 +1,344 @@
|
||||
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
|
||||
|
Reference in New Issue
Block a user