mirror of
https://github.com/3b1b/manim.git
synced 2025-07-28 04:23:16 +08:00
Fix triangulation issue for polygons with many holes
This commit is contained in:
@ -23,35 +23,6 @@ from manimlib.utils.directories import get_mobject_data_dir
|
|||||||
from manimlib.utils.images import get_full_vector_image_path
|
from manimlib.utils.images import get_full_vector_image_path
|
||||||
|
|
||||||
|
|
||||||
def check_and_fix_percent_bug(sym):
|
|
||||||
# This is an ugly patch addressing something which should be
|
|
||||||
# addressed at a deeper level.
|
|
||||||
# The svg path for percent symbols have a known bug, so this
|
|
||||||
# checks if the symbol is (probably) a percentage sign, and
|
|
||||||
# splits it so that it's displayed properly.
|
|
||||||
if len(sym.get_points()) not in [315, 324, 372, 468, 483] or len(sym.get_subpaths()) != 4:
|
|
||||||
return
|
|
||||||
|
|
||||||
sym = sym.family_members_with_points()[0]
|
|
||||||
new_sym = VMobject()
|
|
||||||
path_lengths = [len(path) for path in sym.get_subpaths()]
|
|
||||||
sym_points = sym.get_points()
|
|
||||||
if len(sym_points) in [315, 324, 372]:
|
|
||||||
n = sum(path_lengths[:2])
|
|
||||||
p1 = sym_points[:n]
|
|
||||||
p2 = sym_points[n:]
|
|
||||||
elif len(sym_points) in [468, 483]:
|
|
||||||
p1 = np.vstack([
|
|
||||||
sym_points[:path_lengths[0]],
|
|
||||||
sym_points[-path_lengths[3]:]
|
|
||||||
])
|
|
||||||
p2 = sym_points[path_lengths[0]:sum(path_lengths[:3])]
|
|
||||||
sym.set_points(p1)
|
|
||||||
new_sym.set_points(p2)
|
|
||||||
sym.add(new_sym)
|
|
||||||
sym.refresh_triangulation()
|
|
||||||
|
|
||||||
|
|
||||||
def string_to_numbers(num_string):
|
def string_to_numbers(num_string):
|
||||||
num_string = num_string.replace("-", ",-")
|
num_string = num_string.replace("-", ",-")
|
||||||
num_string = num_string.replace("e,-", "e-")
|
num_string = num_string.replace("e,-", "e-")
|
||||||
@ -379,7 +350,6 @@ class VMobjectFromSVGPathstring(VMobject):
|
|||||||
self.stretch(-1, 1, about_point=ORIGIN)
|
self.stretch(-1, 1, about_point=ORIGIN)
|
||||||
# Save to a file for future use
|
# Save to a file for future use
|
||||||
np.save(points_filepath, self.get_points())
|
np.save(points_filepath, self.get_points())
|
||||||
check_and_fix_percent_bug(self)
|
|
||||||
|
|
||||||
def get_commands_and_coord_strings(self):
|
def get_commands_and_coord_strings(self):
|
||||||
all_commands = list(self.get_command_to_function_map().keys())
|
all_commands = list(self.get_command_to_function_map().keys())
|
||||||
|
@ -349,29 +349,41 @@ def norm_squared(v):
|
|||||||
|
|
||||||
# TODO, fails for polygons drawn over themselves
|
# TODO, fails for polygons drawn over themselves
|
||||||
def earclip_triangulation(verts, rings):
|
def earclip_triangulation(verts, rings):
|
||||||
|
"""
|
||||||
|
Returns a list of indices giving a triangulation
|
||||||
|
of a polygon, potentially with holes
|
||||||
|
|
||||||
|
- verts is an NxM numpy array of points with M > 2
|
||||||
|
|
||||||
|
- rings is a list of indices indicating where
|
||||||
|
the ends of new paths are
|
||||||
|
"""
|
||||||
n = len(verts)
|
n = len(verts)
|
||||||
# Establish where loop indices should be connected
|
# Establish where loop indices should be connected
|
||||||
loop_connections = dict()
|
loop_connections = dict()
|
||||||
for e0, e1 in zip(rings, rings[1:]):
|
# for e0, e1 in zip(rings, rings[1:]):
|
||||||
temp_i = e0
|
e0 = rings[0]
|
||||||
# Find closet point in the first ring (j) to
|
for e1 in rings[1:]:
|
||||||
# the first index of this ring (i)
|
# Find closet pair of points with the first
|
||||||
norms = np.array([
|
# coming from the current ring, and the second
|
||||||
[j, norm_squared(verts[temp_i] - verts[j])]
|
# coming from the next ring
|
||||||
for j in range(0, rings[0])
|
index_pairs = [
|
||||||
if j not in loop_connections
|
(i, j)
|
||||||
])
|
for i in range(0, e0)
|
||||||
j = int(norms[norms[:, 1].argmin()][0])
|
for j in range(e0, e1)
|
||||||
# Find i closest to this j
|
|
||||||
norms = np.array([
|
|
||||||
[i, norm_squared(verts[i] - verts[j])]
|
|
||||||
for i in range(e0, e1)
|
|
||||||
if i not in loop_connections
|
if i not in loop_connections
|
||||||
])
|
if j not in loop_connections
|
||||||
i = int(norms[norms[:, 1].argmin()][0])
|
]
|
||||||
|
i, j = index_pairs[np.argmin([
|
||||||
|
norm_squared(verts[i] - verts[j])
|
||||||
|
for i, j in index_pairs
|
||||||
|
])]
|
||||||
|
|
||||||
|
# Connect the polygon at these points so that
|
||||||
|
# it's treated as a single highly-convex ring
|
||||||
loop_connections[i] = j
|
loop_connections[i] = j
|
||||||
loop_connections[j] = i
|
loop_connections[j] = i
|
||||||
|
e0 = e1
|
||||||
|
|
||||||
# Setup linked list
|
# Setup linked list
|
||||||
after = []
|
after = []
|
||||||
@ -397,87 +409,3 @@ def earclip_triangulation(verts, rings):
|
|||||||
|
|
||||||
meta_indices = earcut(verts[indices, :2], [len(indices)])
|
meta_indices = earcut(verts[indices, :2], [len(indices)])
|
||||||
return [indices[mi] for mi in meta_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
|
|
||||||
|
Reference in New Issue
Block a user