Live convex hull
The smallest convex shape around the points, like a rubber band around nails. Drag the points. Convex hull shows how it is found.
pie.clear()
pie.scalar(0, out='gridOpacity')
import numpy as np
coords = np.random.default_rng(3).uniform(-4, 4, size=(14, 2)).round(2)
pts = [pie.point(x, y, fill='crimson') for x, y in coords]
def cross(o, a, b):
"""Above 0 if b is left of the line from o to a, below 0 if right, 0 if on it."""
return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])
def farther(o, a, b):
"""True if b is farther from o than a is."""
return np.hypot(b[0] - o[0], b[1] - o[1]) > np.hypot(a[0] - o[0], a[1] - o[1])
def wrap(ps):
"""The positions in ps of the hull's corners, counterclockwise from the leftmost point."""
start = min(range(len(ps)), key=lambda i: ps[i][0])
hull = [start]
for _ in range(len(ps)):
here = ps[hull[-1]]
best = (hull[-1] + 1) % len(ps)
for r in range(len(ps)):
turn = cross(here, ps[best], ps[r])
# r is right of the line to best, or on it but farther: r is the better next corner.
if turn < 0 or (turn == 0 and farther(here, ps[best], ps[r])):
best = r
if best == start:
break
hull.append(best)
return hull
def padded(hull, n):
"""`hull`, made n long by repeating its first corner, so the array keeps its size as points move."""
return hull + [hull[0]] * (n - len(hull))
def corners(*ps):
return np.array(ps)[padded(wrap(ps), len(ps))]
def next_corners(*ps):
"""The corner after each corner, so each corner and its next one make an edge."""
hull = wrap(ps)
return np.array(ps)[padded(hull[1:] + hull[:1], len(ps))]
def describe(*ps):
return f'{len(wrap(ps))} of {len(ps)} points are corners'
cs = pie.lift(corners, pie.Array[pie.Point])(*pts, hidden=True)
ns = pie.lift(next_corners, pie.Array[pie.Point])(*pts, hidden=True)
pie.triangle(cs[0], cs, ns, fill='rgba(13, 148, 136, 0.15)', stroke='none')
pie.line(cs, ns, stroke='teal')
pie.text(pie.lift(describe, str)(*pts), 0, 4.8, 14, stroke='#1c1917')
pie.text('Drag a point across the edge to add or drop a corner', 0, -4.8, 14, stroke='#1c1917')