Пример #1
0
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]
Пример #2
0
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]
Пример #3
0
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]
Пример #4
0
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