Skip to content

Instantly share code, notes, and snippets.

@jkulesza
Forked from tixxit/chan.py
Created October 11, 2016 20:01
Show Gist options
  • Save jkulesza/aa56573ec05aa505ad0315beac2d8125 to your computer and use it in GitHub Desktop.
Save jkulesza/aa56573ec05aa505ad0315beac2d8125 to your computer and use it in GitHub Desktop.
# Chan's Convex Hull O(n log h) - Tom Switzer <[email protected]>
TURN_LEFT, TURN_RIGHT, TURN_NONE = (1, -1, 0)
def turn(p, q, r):
"""Returns -1, 0, 1 if p,q,r forms a right, straight, or left turn."""
return cmp((q[0] - p[0])*(r[1] - p[1]) - (r[0] - p[0])*(q[1] - p[1]), 0)
def _keep_left(hull, r):
while len(hull) > 1 and turn(hull[-2], hull[-1], r) != TURN_LEFT:
hull.pop()
return (not len(hull) or hull[-1] != r) and hull.append(r) or hull
def _graham_scan(points):
"""Returns points on convex hull of an array of points in CCW order."""
points.sort()
lh = reduce(_keep_left, points, [])
uh = reduce(_keep_left, reversed(points), [])
return lh.extend(uh[i] for i in xrange(1, len(uh) - 1)) or lh
def _rtangent(hull, p):
"""Return the index of the point in hull that the right tangent line from p
to hull touches.
"""
l, r = 0, len(hull)
l_prev = turn(p, hull[0], hull[-1])
l_next = turn(p, hull[0], hull[(l + 1) % r])
while l < r:
c = (l + r) / 2
c_prev = turn(p, hull[c], hull[(c - 1) % len(hull)])
c_next = turn(p, hull[c], hull[(c + 1) % len(hull)])
c_side = turn(p, hull[l], hull[c])
if c_prev != TURN_RIGHT and c_next != TURN_RIGHT:
return c
elif c_side == TURN_LEFT and (l_next == TURN_RIGHT or
l_prev == l_next) or \
c_side == TURN_RIGHT and c_prev == TURN_RIGHT:
r = c # Tangent touches left chain
else:
l = c + 1 # Tangent touches right chain
l_prev = -c_next # Switch sides
l_next = turn(p, hull[l], hull[(l + 1) % len(hull)])
return l
def _min_hull_pt_pair(hulls):
"""Returns the hull, point index pair that is minimal."""
h, p = 0, 0
for i in xrange(len(hulls)):
j = min(xrange(len(hulls[i])), key=lambda j: hulls[i][j])
if hulls[i][j] < hulls[h][p]:
h, p = i, j
return (h, p)
def _next_hull_pt_pair(hulls, pair):
"""
Returns the (hull, point) index pair of the next point in the convex
hull.
"""
p = hulls[pair[0]][pair[1]]
next = (pair[0], (pair[1] + 1) % len(hulls[pair[0]]))
for h in (i for i in xrange(len(hulls)) if i != pair[0]):
s = _rtangent(hulls[h], p)
q, r = hulls[next[0]][next[1]], hulls[h][s]
t = turn(p, q, r)
if t == TURN_RIGHT or t == TURN_NONE and _dist(p, r) > _dist(p, q):
next = (h, s)
return next
def convex_hull(pts):
"""Returns the points on the convex hull of pts in CCW order."""
for m in (1 << (1 << t) for t in xrange(len(pts))):
hulls = [_graham_scan(pts[i:i + m]) for i in xrange(0, len(pts), m)]
hull = [_min_hull_pt_pair(hulls)]
for throw_away in xrange(m):
p = _next_hull_pt_pair(hulls, hull[-1])
if p == hull[0]:
return [hulls[h][i] for h, i in hull]
hull.append(p)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment