first commit
This commit is contained in:
0
helix/helpers/__init__.py
Normal file
0
helix/helpers/__init__.py
Normal file
152
helix/helpers/nodequadtree.py
Normal file
152
helix/helpers/nodequadtree.py
Normal file
@@ -0,0 +1,152 @@
|
||||
from math import floor, ceil
|
||||
|
||||
|
||||
# A utility class, a rectangle acting as boundaries of the quad tree
|
||||
class Bounds:
|
||||
def __init__(self, left, right, bottom, top):
|
||||
if top < bottom:
|
||||
top, bottom = bottom, top
|
||||
|
||||
if right < left:
|
||||
right, left = left, right
|
||||
|
||||
self.left = left
|
||||
self.right = right
|
||||
self.bottom = bottom
|
||||
self.top = top
|
||||
self.width = right - left
|
||||
self.height = top - bottom
|
||||
|
||||
def getWidth(self):
|
||||
return self.width
|
||||
|
||||
def getHeight(self):
|
||||
return self.height
|
||||
|
||||
def getLeft(self):
|
||||
return self.left
|
||||
|
||||
def getRight(self):
|
||||
return self.right
|
||||
|
||||
def getBottom(self):
|
||||
return self.bottom
|
||||
|
||||
def getTop(self):
|
||||
return self.top
|
||||
|
||||
|
||||
# A QuadTree implemented to specifically handle panel Nodes
|
||||
class NodeQuadTree():
|
||||
MAX_OBJECTS = 100
|
||||
MAX_LEVELS = 5
|
||||
|
||||
def __init__(self, level, bounds, variance):
|
||||
if level < 1:
|
||||
level = 1
|
||||
|
||||
self.level = level
|
||||
self.variance = variance
|
||||
|
||||
self.bounds = bounds
|
||||
if level == 1:
|
||||
self.bounds = Bounds(floor(bounds.getLeft() - self.variance),
|
||||
ceil(bounds.getRight() + self.variance),
|
||||
floor(bounds.getBottom() - self.variance),
|
||||
ceil(bounds.getTop() + self.variance))
|
||||
|
||||
self.nodeList = []
|
||||
self.quads = [None, None, None, None]
|
||||
|
||||
def clear(self):
|
||||
self.nodeList = []
|
||||
for quad in self.quads:
|
||||
if quad is not None:
|
||||
quad.clear()
|
||||
self.quads = [None, None, None, None]
|
||||
|
||||
def pointInside(self, point):
|
||||
if point.x - self.variance < self.bounds.getLeft():
|
||||
return False
|
||||
if point.x + self.variance > self.bounds.getRight():
|
||||
return False
|
||||
if point.y - self.variance < self.bounds.getBottom():
|
||||
return False
|
||||
if point.y + self.variance > self.bounds.getTop():
|
||||
return False
|
||||
return True
|
||||
|
||||
def split(self):
|
||||
left = self.bounds.getLeft()
|
||||
right = self.bounds.getRight()
|
||||
midLR = left + self.bounds.getWidth() / 2
|
||||
|
||||
bottom = self.bounds.getBottom()
|
||||
top = self.bounds.getTop()
|
||||
midBT = bottom + self.bounds.getHeight() / 2
|
||||
|
||||
self.quads[0] = NodeQuadTree(self.level + 1, Bounds(left, midLR, bottom, midBT), self.variance)
|
||||
self.quads[1] = NodeQuadTree(self.level + 1, Bounds(midLR, right, bottom, midBT), self.variance)
|
||||
self.quads[2] = NodeQuadTree(self.level + 1, Bounds(left, midLR, midBT, top), self.variance)
|
||||
self.quads[3] = NodeQuadTree(self.level + 1, Bounds(midLR, right, midBT, top), self.variance)
|
||||
|
||||
# Returns which child index the point belongs in, or -1 if it doesn't fit completely within any
|
||||
def getIndex(self, point):
|
||||
for i in range(4):
|
||||
if self.quads[i] is not None:
|
||||
if self.quads[i].pointInside(point):
|
||||
return i
|
||||
return -1
|
||||
|
||||
# insert the node into the QuadTree, or one of its children
|
||||
def insert(self, node):
|
||||
# add it to our children, if they exist and the point fits
|
||||
if self.quads[0] is not None:
|
||||
index = self.getIndex(node.coordinate)
|
||||
|
||||
if index != -1:
|
||||
self.quads[index].insert(node)
|
||||
return
|
||||
|
||||
# else, add it to self
|
||||
self.nodeList.append(node)
|
||||
|
||||
# too big? split into quads
|
||||
if (len(self.nodeList) > NodeQuadTree.MAX_OBJECTS) and \
|
||||
(self.level < NodeQuadTree.MAX_LEVELS) and \
|
||||
(self.quads[0] is None):
|
||||
|
||||
self.split()
|
||||
|
||||
toKeep = []
|
||||
for n in self.nodeList:
|
||||
index = self.getIndex(n.coordinate)
|
||||
|
||||
if index != -1:
|
||||
self.quads[index].insert(n)
|
||||
else:
|
||||
toKeep.append(n)
|
||||
|
||||
self.nodeList = toKeep
|
||||
|
||||
# Return a list of all possible nodes that can be near this point
|
||||
def retrieve(self, nearPoint):
|
||||
retNodes = list(self.nodeList)
|
||||
|
||||
if self.quads[0] is not None:
|
||||
index = self.getIndex(nearPoint)
|
||||
if index != -1:
|
||||
retNodes += self.quads[index].retrieve(nearPoint)
|
||||
else:
|
||||
for quad in self.quads:
|
||||
retNodes += quad.retrieve(nearPoint)
|
||||
|
||||
return retNodes
|
||||
|
||||
def report(self, outputArray):
|
||||
if outputArray is not None:
|
||||
outputArray.append(len(self.nodeList))
|
||||
|
||||
for quad in self.quads:
|
||||
if quad is not None:
|
||||
quad.report(outputArray)
|
||||
97
helix/helpers/polygon_helper.py
Normal file
97
helix/helpers/polygon_helper.py
Normal file
@@ -0,0 +1,97 @@
|
||||
def point_inside_polygon(x, y, points):
|
||||
n = len(points)
|
||||
inside = False
|
||||
|
||||
# this does a raytracing algorithm that I don't quite understand.
|
||||
# See http://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon/2922778#2922778
|
||||
# for an attempt at an explanation
|
||||
|
||||
p1x, p1y = points[0]
|
||||
for i in range(n + 1):
|
||||
p2x, p2y = points[i % n]
|
||||
if y > min(p1y, p2y):
|
||||
if y <= max(p1y, p2y):
|
||||
if x <= max(p1x, p2x):
|
||||
if p1y != p2y:
|
||||
xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
|
||||
if p1x == p2x or x <= xinters:
|
||||
inside = not inside
|
||||
p1x, p1y = p2x, p2y
|
||||
|
||||
return inside
|
||||
|
||||
def line_segments_intersect(first_line, second_line):
|
||||
# returns either None or the point where the line segments intersect
|
||||
x_0_0 = first_line[0][0]
|
||||
x_0_1 = first_line[1][0]
|
||||
y_0_0 = first_line[0][1]
|
||||
y_0_1 = first_line[1][1]
|
||||
|
||||
x_1_0 = second_line[0][0]
|
||||
x_1_1 = second_line[1][0]
|
||||
y_1_0 = second_line[0][1]
|
||||
y_1_1 = second_line[1][1]
|
||||
|
||||
if max(x_0_0, x_0_1) < min(x_1_0, x_1_1):
|
||||
return None
|
||||
|
||||
rise_0 = y_0_1 - y_0_0
|
||||
run_0 = x_0_1 - x_0_0
|
||||
if run_0 == 0:
|
||||
slope_0 = None
|
||||
y_intercept_0 = 0
|
||||
else:
|
||||
slope_0 = rise_0 / run_0
|
||||
y_intercept_0 = y_0_0 - (slope_0 * x_0_0)
|
||||
|
||||
rise_1 = y_1_1 - y_1_0
|
||||
run_1 = x_1_1 - x_1_0
|
||||
if run_1 == 0:
|
||||
slope_1 = None
|
||||
y_intercept_1 = 0
|
||||
else:
|
||||
slope_1 = rise_1 / run_1
|
||||
y_intercept_1 = y_1_0 - (slope_1 * x_1_0)
|
||||
|
||||
if slope_0 is not None and slope_1 is not None and abs(slope_0 - slope_1) < 1e-3:
|
||||
return None
|
||||
if slope_0 is None and slope_1 is None:
|
||||
return None
|
||||
if slope_0 is None:
|
||||
x_point = x_0_0
|
||||
slope = slope_1
|
||||
elif slope_1 is None:
|
||||
x_point = x_1_0
|
||||
slope = slope_0
|
||||
else:
|
||||
x_point = (y_intercept_1 - y_intercept_0) / (slope_0 - slope_1)
|
||||
slope = slope_0
|
||||
|
||||
if (x_point < max(min(x_0_0, x_0_1), min(x_1_0, x_1_1))) and (x_point > min(max(x_0_0, x_0_1), max(x_1_0, x_1_1))):
|
||||
return None
|
||||
|
||||
y_point = slope * x_point + y_intercept_1
|
||||
return x_point, y_point
|
||||
|
||||
|
||||
def clip_polygon(a, clip_region):
|
||||
points = []
|
||||
for x, y in a:
|
||||
points.append((x, y, point_inside_polygon(x, y, clip_region)))
|
||||
|
||||
idx = 0
|
||||
while idx < len(points):
|
||||
x0, y0, inside_0 = points[(idx-1) % len(points)]
|
||||
x1, y1, inside_1 = points[idx]
|
||||
|
||||
first_line = [(x0, y0), (x1, y1)]
|
||||
|
||||
if inside_0 and not inside_1: # intersected between then and now
|
||||
for index, vertex in enumerate(clip_region):
|
||||
second_line = [vertex, clip_region[(index + 1) % len(clip_region)]]
|
||||
intersection = line_segments_intersect(first_line, second_line)
|
||||
if intersection:
|
||||
points[idx] = (intersection[0], intersection[1], False)
|
||||
break
|
||||
idx += 1
|
||||
return [(x, y) for x, y, _ in points]
|
||||
Reference in New Issue
Block a user