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 closet point in the first ring (j) to # the first index of this ring (i) norms = np.array([ [j, norm_squared(verts[temp_i] - verts[j])] for j in range(0, rings[0]) if j not in loop_connections ]) j = int(norms[norms[:, 1].argmin()][0]) # 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 ]) i = int(norms[norms[:, 1].argmin()][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 for x in range(n + len(rings) - 1): # 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] if i == 0: break meta_indices = earcut(verts[indices, :2], [len(indices)]) return [indices[mi] for mi in meta_indices]
def earclip_triangulation(verts, ring_ends): """ Returns a list of indices giving a triangulation of a polygon, potentially with holes - verts is a numpy array of points - ring_ends is a list of indices indicating where the ends of new paths are """ # First, connect all the rings so that the polygon # with holes is instead treated as a (very convex) # polygon with one edge. Do this by drawing connections # between rings close to each other rings = [list(range(e0, e1)) for e0, e1 in zip([0, *ring_ends], ring_ends)] attached_rings = rings[:1] detached_rings = rings[1:] loop_connections = {} while detached_rings: i_range, j_range = [ list( filter( # Ignore indices that are already being # used to draw some connection lambda i: i not in loop_connections, it.chain(*ring_group), ) ) for ring_group in (attached_rings, detached_rings) ] # Closet point on the atttached rings to an estimated midpoint # of the detached rings tmp_j_vert = midpoint(verts[j_range[0]], verts[j_range[len(j_range) // 2]]) i = min(i_range, key=lambda i: norm_squared(verts[i] - tmp_j_vert)) # Closet point of the detached rings to the aforementioned # point of the attached rings j = min(j_range, key=lambda j: norm_squared(verts[i] - verts[j])) # Recalculate i based on new j i = min(i_range, key=lambda i: norm_squared(verts[i] - verts[j])) # Remember to connect the polygon at these points loop_connections[i] = j loop_connections[j] = i # Move the ring which j belongs to from the # attached list to the detached list new_ring = next(filter(lambda ring: ring[0] <= j < ring[-1], detached_rings)) detached_rings.remove(new_ring) attached_rings.append(new_ring) # Setup linked list after = [] end0 = 0 for end1 in ring_ends: after.extend(range(end0 + 1, end1)) after.append(end0) end0 = end1 # Find an ordering of indices walking around the polygon indices = [] i = 0 for _ in range(len(verts) + len(ring_ends) - 1): # 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] if i == 0: break meta_indices = earcut(verts[indices, :2], [len(indices)]) return [indices[mi] for mi in meta_indices]
def earclip_triangulation(verts, rings): """ Returns a list of indices giving a triangulation of a polygon, potentially with holes - verts is a numpy array of points - rings is a list of indices indicating where the ends of new paths are """ n = len(verts) # Establish where loop indices should be connected loop_connections = dict() end0 = rings[0] for end1 in rings[1:]: # Find a close pair of points connecting the current # ring to the next ring # Ignore indices already used for prior connections i_range = list( filter(lambda i: i not in loop_connections, range(0, end0))) j_range = list(range(end0, end1)) # Closet point on the first ring to the first point # of the second ring i = i_range[np.argmin( [norm_squared(verts[i] - verts[end0]) for i in i_range])] # Closet point of the second ring to the aforementioned # point of the first ring j = j_range[np.argmin( [norm_squared(verts[i] - verts[j]) for j in j_range])] # Connect the polygon at these points so that # it's treated as a single highly-convex ring loop_connections[i] = j loop_connections[j] = i end0 = end1 # Setup linked list after = [] end0 = 0 for end1 in rings: after.extend(range(end0 + 1, end1)) after.append(end0) end0 = end1 # Find an ordering of indices walking around the polygon indices = [] i = 0 for x in range(n + len(rings) - 1): # 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] if i == 0: break meta_indices = earcut(verts[indices, :2], [len(indices)]) return [indices[mi] for mi in meta_indices]
def earclip_triangulation(verts: np.ndarray, ring_ends: list[int]) -> list: """ Returns a list of indices giving a triangulation of a polygon, potentially with holes - verts is a numpy array of points - ring_ends is a list of indices indicating where the ends of new paths are """ rings = [list(range(e0, e1)) for e0, e1 in zip([0, *ring_ends], ring_ends)] def is_in(point, ring_id): return abs( abs(get_winding_number([i - point for i in verts[rings[ring_id]]])) - 1) < 1e-5 def ring_area(ring_id): ring = rings[ring_id] s = 0 for i, j in zip(ring[1:], ring): s += cross2d(verts[i], verts[j]) return abs(s) / 2 # Points at the same position may cause problems for i in rings: verts[i[0]] += (verts[i[1]] - verts[i[0]]) * 1e-6 verts[i[-1]] += (verts[i[-2]] - verts[i[-1]]) * 1e-6 # First, we should know which rings are directly contained in it for each ring right = [max(verts[rings[i], 0]) for i in range(len(rings))] left = [min(verts[rings[i], 0]) for i in range(len(rings))] top = [max(verts[rings[i], 1]) for i in range(len(rings))] bottom = [min(verts[rings[i], 1]) for i in range(len(rings))] area = [ring_area(i) for i in range(len(rings))] # The larger ring must be outside rings_sorted = list(range(len(rings))) rings_sorted.sort(key=lambda x: area[x], reverse=True) def is_in_fast(ring_a, ring_b): # Whether a is in b return reduce( op.and_, (left[ring_b] <= left[ring_a] <= right[ring_a] <= right[ring_b], bottom[ring_b] <= bottom[ring_a] <= top[ring_a] <= top[ring_b], is_in(verts[rings[ring_a][0]], ring_b))) chilren = [[] for i in rings] ringenum = ProgressDisplay( enumerate(rings_sorted), total=len(rings), leave=False, ascii=True if platform.system() == 'Windows' else None, dynamic_ncols=True, desc="SVG Triangulation", delay=3, ) for idx, i in ringenum: for j in rings_sorted[:idx][::-1]: if is_in_fast(i, j): chilren[j].append(i) break res = [] # Then, we can use earcut for each part used = [False] * len(rings) for i in rings_sorted: if used[i]: continue v = rings[i] ring_ends = [len(v)] for j in chilren[i]: used[j] = True v += rings[j] ring_ends.append(len(v)) res += [v[i] for i in earcut(verts[v, :2], ring_ends)] return res