mirror of
https://github.com/3b1b/manim.git
synced 2025-11-14 11:17:43 +00:00
Added wrapper around mapbox triangulation to make it work for polygons with holes
This commit is contained in:
parent
b243c522b1
commit
7ce75bdb52
1 changed files with 166 additions and 0 deletions
|
|
@ -1,6 +1,8 @@
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import itertools as it
|
||||||
|
from mapbox_earcut import triangulate_float32 as earcut
|
||||||
|
|
||||||
from manimlib.constants import OUT
|
from manimlib.constants import OUT
|
||||||
from manimlib.constants import PI
|
from manimlib.constants import PI
|
||||||
|
|
@ -267,3 +269,167 @@ def get_winding_number(points):
|
||||||
d_angle = ((d_angle + PI) % TAU) - PI
|
d_angle = ((d_angle + PI) % TAU) - PI
|
||||||
total_angle += d_angle
|
total_angle += d_angle
|
||||||
return total_angle / TAU
|
return total_angle / TAU
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
|
||||||
|
def cross2d(a, b):
|
||||||
|
if len(a.shape) == 2:
|
||||||
|
return a[:, 0] * b[:, 1] - a[:, 1] * b[:, 0]
|
||||||
|
else:
|
||||||
|
return a[0] * b[1] - b[0] * a[1]
|
||||||
|
|
||||||
|
|
||||||
|
def tri_area(a, b, c):
|
||||||
|
return 0.5 * abs(
|
||||||
|
a[0] * (b[1] - c[1]) +
|
||||||
|
b[0] * (c[1] - a[1]) +
|
||||||
|
c[0] * (a[1] - b[1])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_inside_triangle(p, a, b, c):
|
||||||
|
"""
|
||||||
|
Test if point p is inside triangle abc
|
||||||
|
"""
|
||||||
|
crosses = np.array([
|
||||||
|
cross2d(p - a, b - p),
|
||||||
|
cross2d(p - b, c - p),
|
||||||
|
cross2d(p - c, a - p),
|
||||||
|
])
|
||||||
|
return np.all(crosses > 0) or np.all(crosses < 0)
|
||||||
|
|
||||||
|
|
||||||
|
def norm_squared(v):
|
||||||
|
return sum(v * v)
|
||||||
|
|
||||||
|
|
||||||
|
def earclip_triangulation(verts, rings):
|
||||||
|
n = len(verts)
|
||||||
|
# Establish where loop indices should be connected
|
||||||
|
loop_connections = dict()
|
||||||
|
for e0, e1 in zip(rings, rings[1:]):
|
||||||
|
temp_i = e0
|
||||||
|
# Find j closest to temp_i
|
||||||
|
norms = np.array([
|
||||||
|
[j, norm_squared(verts[temp_i] - verts[j])]
|
||||||
|
for j in it.chain(range(0, e0), range(e1, n))
|
||||||
|
])
|
||||||
|
j = int(norms[np.argmin(norms[:, 1])][0])
|
||||||
|
# Find i closest to this j
|
||||||
|
norms = np.array([
|
||||||
|
[i, norm_squared(verts[i] - verts[j])]
|
||||||
|
for i in range(e0, e1)
|
||||||
|
])
|
||||||
|
i = int(norms[np.argmin(norms[:, 1])][0])
|
||||||
|
|
||||||
|
loop_connections[i] = j
|
||||||
|
loop_connections[j] = i
|
||||||
|
|
||||||
|
# Setup linked list
|
||||||
|
after = []
|
||||||
|
e0 = 0
|
||||||
|
for e1 in rings:
|
||||||
|
after.extend([*range(e0 + 1, e1), e0])
|
||||||
|
e0 = e1
|
||||||
|
|
||||||
|
# Find an ordering of indices walking around the polygon
|
||||||
|
indices = []
|
||||||
|
i = 0
|
||||||
|
starting = True
|
||||||
|
while (i != 0 or starting):
|
||||||
|
starting = False
|
||||||
|
if i in loop_connections:
|
||||||
|
j = loop_connections[i]
|
||||||
|
indices.extend([i, j])
|
||||||
|
i = after[j]
|
||||||
|
else:
|
||||||
|
indices.append(i)
|
||||||
|
i = after[i]
|
||||||
|
|
||||||
|
meta_indices = earcut(verts[indices, :2], [len(indices)])
|
||||||
|
return [indices[mi] for mi in meta_indices]
|
||||||
|
|
||||||
|
|
||||||
|
def old_earclip_triangulation(verts, rings, orientation):
|
||||||
|
n = len(verts)
|
||||||
|
assert(n in rings)
|
||||||
|
result = []
|
||||||
|
|
||||||
|
# Establish where loop indices should be connected
|
||||||
|
loop_connections = dict()
|
||||||
|
e0 = 0
|
||||||
|
for e1 in rings:
|
||||||
|
norms = np.array([
|
||||||
|
[i, j, get_norm(verts[i] - verts[j])]
|
||||||
|
for i in range(e0, e1)
|
||||||
|
for j in it.chain(range(0, e0), range(e1, n))
|
||||||
|
])
|
||||||
|
if len(norms) == 0:
|
||||||
|
continue
|
||||||
|
i, j = norms[np.argmin(norms[:, 2])][:2].astype(int)
|
||||||
|
loop_connections[i] = j
|
||||||
|
loop_connections[j] = i
|
||||||
|
e0 = e1
|
||||||
|
|
||||||
|
# Setup bidirectional linked list
|
||||||
|
before = []
|
||||||
|
after = []
|
||||||
|
e0 = 0
|
||||||
|
for e1 in rings:
|
||||||
|
after += [*range(e0 + 1, e1), e0]
|
||||||
|
before += [e1 - 1, *range(e0, e1 - 1)]
|
||||||
|
e0 = e1
|
||||||
|
|
||||||
|
# Initialize edge triangles
|
||||||
|
edge_tris = []
|
||||||
|
i = 0
|
||||||
|
starting = True
|
||||||
|
while (i != 0 or starting):
|
||||||
|
starting = False
|
||||||
|
if i in loop_connections:
|
||||||
|
j = loop_connections[i]
|
||||||
|
edge_tris.append([before[i], i, j])
|
||||||
|
edge_tris.append([i, j, after[j]])
|
||||||
|
i = after[j]
|
||||||
|
else:
|
||||||
|
edge_tris.append([before[i], i, after[i]])
|
||||||
|
i = after[i]
|
||||||
|
|
||||||
|
# Set up a test for whether or not three indices
|
||||||
|
# form an ear of the polygon, meaning a convex corner
|
||||||
|
# which doesn't contain any other vertices
|
||||||
|
indices = list(range(n))
|
||||||
|
|
||||||
|
def is_ear(*tri_indices):
|
||||||
|
tri = [verts[i] for i in tri_indices]
|
||||||
|
v1 = tri[1] - tri[0]
|
||||||
|
v2 = tri[2] - tri[1]
|
||||||
|
cross = v1[0] * v2[1] - v2[0] * v1[1]
|
||||||
|
if orientation * cross < 0:
|
||||||
|
return False
|
||||||
|
for j in indices:
|
||||||
|
if j in tri_indices:
|
||||||
|
continue
|
||||||
|
elif is_inside_triangle(verts[j], *tri):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Loop through and clip off all the ears
|
||||||
|
n_failures = 0
|
||||||
|
i = 0
|
||||||
|
while n_failures < len(edge_tris):
|
||||||
|
n = len(edge_tris)
|
||||||
|
edge_tri = edge_tris[i % n]
|
||||||
|
if is_ear(*edge_tri):
|
||||||
|
result.extend(edge_tri)
|
||||||
|
edge_tris[(i - 1) % n][2] = edge_tri[2]
|
||||||
|
edge_tris[(i + 1) % n][0] = edge_tri[0]
|
||||||
|
if edge_tri[1] in indices:
|
||||||
|
indices.remove(edge_tri[1])
|
||||||
|
edge_tris.remove(edge_tri)
|
||||||
|
n_failures = 0
|
||||||
|
else:
|
||||||
|
n_failures += 1
|
||||||
|
i += 1
|
||||||
|
return result
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue