2018-03-30 18:19:23 -07:00
|
|
|
import numpy as np
|
2021-02-02 17:20:48 -08:00
|
|
|
import itertools as it
|
2020-02-21 10:57:10 -08:00
|
|
|
import math
|
2020-02-10 14:47:36 -08:00
|
|
|
from mapbox_earcut import triangulate_float32 as earcut
|
2018-03-31 15:11:35 -07:00
|
|
|
|
2020-06-04 11:29:36 -07:00
|
|
|
from manimlib.constants import RIGHT
|
2020-06-06 10:57:07 -07:00
|
|
|
from manimlib.constants import DOWN
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.constants import OUT
|
|
|
|
from manimlib.constants import PI
|
|
|
|
from manimlib.constants import TAU
|
|
|
|
from manimlib.utils.iterables import adjacent_pairs
|
2018-03-30 18:19:23 -07:00
|
|
|
|
2018-08-22 14:48:42 -07:00
|
|
|
|
2018-08-15 17:30:24 -07:00
|
|
|
def get_norm(vect):
|
|
|
|
return sum([x**2 for x in vect])**0.5
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-09-10 11:38:01 -07:00
|
|
|
# Quaternions
|
|
|
|
# TODO, implement quaternion type
|
|
|
|
|
|
|
|
|
2020-02-18 22:25:54 -08:00
|
|
|
def quaternion_mult(*quats):
|
|
|
|
if len(quats) == 0:
|
|
|
|
return [1, 0, 0, 0]
|
|
|
|
result = quats[0]
|
|
|
|
for next_quat in quats[1:]:
|
|
|
|
w1, x1, y1, z1 = result
|
|
|
|
w2, x2, y2, z2 = next_quat
|
|
|
|
result = [
|
|
|
|
w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2,
|
|
|
|
w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2,
|
|
|
|
w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2,
|
|
|
|
w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2,
|
|
|
|
]
|
|
|
|
return result
|
2018-09-10 11:38:01 -07:00
|
|
|
|
|
|
|
|
2020-06-09 20:40:36 -07:00
|
|
|
def quaternion_from_angle_axis(angle, axis, axis_normalized=False):
|
|
|
|
if not axis_normalized:
|
|
|
|
axis = normalize(axis)
|
|
|
|
return [math.cos(angle / 2), *(math.sin(angle / 2) * axis)]
|
2018-09-10 11:38:01 -07:00
|
|
|
|
|
|
|
|
2018-09-27 17:35:40 -07:00
|
|
|
def angle_axis_from_quaternion(quaternion):
|
|
|
|
axis = normalize(
|
|
|
|
quaternion[1:],
|
2020-02-18 22:25:54 -08:00
|
|
|
fall_back=[1, 0, 0]
|
2018-09-27 17:35:40 -07:00
|
|
|
)
|
|
|
|
angle = 2 * np.arccos(quaternion[0])
|
|
|
|
if angle > TAU / 2:
|
|
|
|
angle = TAU - angle
|
|
|
|
return angle, axis
|
|
|
|
|
|
|
|
|
2018-09-10 11:38:01 -07:00
|
|
|
def quaternion_conjugate(quaternion):
|
2020-02-18 22:25:54 -08:00
|
|
|
result = list(quaternion)
|
|
|
|
for i in range(1, len(result)):
|
|
|
|
result[i] *= -1
|
2018-09-10 11:38:01 -07:00
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def rotate_vector(vector, angle, axis=OUT):
|
2019-01-04 12:48:05 -08:00
|
|
|
if len(vector) == 2:
|
|
|
|
# Use complex numbers...because why not
|
|
|
|
z = complex(*vector) * np.exp(complex(0, angle))
|
2020-03-06 06:15:28 -08:00
|
|
|
result = [z.real, z.imag]
|
2019-01-04 12:48:05 -08:00
|
|
|
elif len(vector) == 3:
|
|
|
|
# Use quaternions...because why not
|
|
|
|
quat = quaternion_from_angle_axis(angle, axis)
|
|
|
|
quat_inv = quaternion_conjugate(quat)
|
2020-02-18 22:25:54 -08:00
|
|
|
product = quaternion_mult(quat, [0, *vector], quat_inv)
|
2020-03-06 06:15:28 -08:00
|
|
|
result = product[1:]
|
2019-01-04 12:48:05 -08:00
|
|
|
else:
|
|
|
|
raise Exception("vector must be of dimension 2 or 3")
|
2018-09-10 11:38:01 -07:00
|
|
|
|
2020-03-06 06:15:28 -08:00
|
|
|
if isinstance(vector, np.ndarray):
|
|
|
|
return np.array(result)
|
|
|
|
return result
|
|
|
|
|
2018-09-10 11:38:01 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def thick_diagonal(dim, thickness=2):
|
2018-03-30 18:19:23 -07:00
|
|
|
row_indices = np.arange(dim).repeat(dim).reshape((dim, dim))
|
|
|
|
col_indices = np.transpose(row_indices)
|
2018-04-06 13:58:59 -07:00
|
|
|
return (np.abs(row_indices - col_indices) < thickness).astype('uint8')
|
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
|
2020-06-01 16:21:18 -07:00
|
|
|
def rotation_matrix_transpose_from_quaternion(quat):
|
|
|
|
quat_inv = quaternion_conjugate(quat)
|
|
|
|
return [
|
|
|
|
quaternion_mult(quat, [0, *basis], quat_inv)[1:]
|
|
|
|
for basis in [
|
|
|
|
[1, 0, 0],
|
|
|
|
[0, 1, 0],
|
|
|
|
[0, 0, 1],
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def rotation_matrix_from_quaternion(quat):
|
|
|
|
return np.transpose(rotation_matrix_transpose_from_quaternion(quat))
|
|
|
|
|
|
|
|
|
2020-02-18 22:25:54 -08:00
|
|
|
def rotation_matrix_transpose(angle, axis):
|
|
|
|
if axis[0] == 0 and axis[1] == 0:
|
|
|
|
# axis = [0, 0, z] case is common enough it's worth
|
2020-02-20 15:51:26 -08:00
|
|
|
# having a shortcut
|
2020-02-18 22:25:54 -08:00
|
|
|
sgn = 1 if axis[2] > 0 else -1
|
2020-02-21 10:57:10 -08:00
|
|
|
cos_a = math.cos(angle)
|
|
|
|
sin_a = math.sin(angle) * sgn
|
2020-02-18 22:25:54 -08:00
|
|
|
return [
|
2020-02-20 15:51:26 -08:00
|
|
|
[cos_a, sin_a, 0],
|
|
|
|
[-sin_a, cos_a, 0],
|
2020-02-18 22:25:54 -08:00
|
|
|
[0, 0, 1],
|
|
|
|
]
|
|
|
|
quat = quaternion_from_angle_axis(angle, axis)
|
2020-06-01 16:21:18 -07:00
|
|
|
return rotation_matrix_transpose_from_quaternion(quat)
|
2020-02-18 22:25:54 -08:00
|
|
|
|
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def rotation_matrix(angle, axis):
|
|
|
|
"""
|
|
|
|
Rotation in R^3 about a specified axis of rotation.
|
|
|
|
"""
|
2020-02-18 22:25:54 -08:00
|
|
|
return np.transpose(rotation_matrix_transpose(angle, axis))
|
2018-03-30 18:19:23 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def rotation_about_z(angle):
|
|
|
|
return [
|
2020-02-21 10:57:10 -08:00
|
|
|
[math.cos(angle), -math.sin(angle), 0],
|
|
|
|
[math.sin(angle), math.cos(angle), 0],
|
2018-03-30 18:19:23 -07:00
|
|
|
[0, 0, 1]
|
|
|
|
]
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def z_to_vector(vector):
|
|
|
|
"""
|
2018-04-06 13:58:59 -07:00
|
|
|
Returns some matrix in SO(3) which takes the z-axis to the
|
2018-03-30 18:19:23 -07:00
|
|
|
(normalized) vector provided as an argument
|
|
|
|
"""
|
2020-06-05 13:21:35 -07:00
|
|
|
axis = cross(OUT, vector)
|
|
|
|
if get_norm(axis) == 0:
|
|
|
|
if vector[2] > 0:
|
|
|
|
return np.identity(3)
|
|
|
|
else:
|
|
|
|
return rotation_matrix(PI, RIGHT)
|
2020-06-03 17:10:33 -07:00
|
|
|
angle = np.arccos(np.dot(OUT, normalize(vector)))
|
2020-06-05 13:21:35 -07:00
|
|
|
return rotation_matrix(angle, axis=axis)
|
2018-03-30 18:19:23 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def angle_of_vector(vector):
|
|
|
|
"""
|
|
|
|
Returns polar coordinate theta when vector is project on xy plane
|
|
|
|
"""
|
|
|
|
return np.angle(complex(*vector[:2]))
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def angle_between_vectors(v1, v2):
|
|
|
|
"""
|
|
|
|
Returns the angle between two 3D vectors.
|
2019-02-07 21:38:03 -08:00
|
|
|
This angle will always be btw 0 and pi
|
2018-03-30 18:19:23 -07:00
|
|
|
"""
|
2020-02-11 19:48:50 -08:00
|
|
|
diff = (angle_of_vector(v2) - angle_of_vector(v1)) % TAU
|
|
|
|
return min(diff, TAU - diff)
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
|
|
|
|
def project_along_vector(point, vector):
|
|
|
|
matrix = np.identity(3) - np.outer(vector, vector)
|
|
|
|
return np.dot(point, matrix.T)
|
|
|
|
|
2018-08-15 16:23:29 -07:00
|
|
|
|
2018-08-29 00:09:57 -07:00
|
|
|
def normalize(vect, fall_back=None):
|
2018-08-15 16:23:29 -07:00
|
|
|
norm = get_norm(vect)
|
|
|
|
if norm > 0:
|
2018-08-30 14:24:57 -07:00
|
|
|
return np.array(vect) / norm
|
2020-02-21 10:57:10 -08:00
|
|
|
elif fall_back is not None:
|
|
|
|
return fall_back
|
2018-08-15 16:23:29 -07:00
|
|
|
else:
|
2020-02-21 10:57:10 -08:00
|
|
|
return np.zeros(len(vect))
|
2018-08-15 16:23:29 -07:00
|
|
|
|
|
|
|
|
2020-06-05 11:12:11 -07:00
|
|
|
def normalize_along_axis(array, axis, fall_back=None):
|
|
|
|
norms = np.sqrt((array * array).sum(axis))
|
|
|
|
norms[norms == 0] = 1
|
|
|
|
buffed_norms = np.repeat(norms, array.shape[axis]).reshape(array.shape)
|
|
|
|
array /= buffed_norms
|
|
|
|
return array
|
|
|
|
|
|
|
|
|
2018-08-15 16:23:29 -07:00
|
|
|
def cross(v1, v2):
|
|
|
|
return np.array([
|
|
|
|
v1[1] * v2[2] - v1[2] * v2[1],
|
|
|
|
v1[2] * v2[0] - v1[0] * v2[2],
|
|
|
|
v1[0] * v2[1] - v1[1] * v2[0]
|
|
|
|
])
|
|
|
|
|
|
|
|
|
2020-06-03 17:10:33 -07:00
|
|
|
def get_unit_normal(v1, v2, tol=1e-6):
|
|
|
|
v1 = normalize(v1)
|
|
|
|
v2 = normalize(v2)
|
|
|
|
cp = cross(v1, v2)
|
|
|
|
cp_norm = get_norm(cp)
|
|
|
|
if cp_norm < tol:
|
|
|
|
# Vectors align, so find a normal to them in the plane shared with the z-axis
|
|
|
|
new_cp = cross(cross(v1, OUT), v1)
|
|
|
|
new_cp_norm = get_norm(new_cp)
|
|
|
|
if new_cp_norm < tol:
|
2020-06-06 10:57:07 -07:00
|
|
|
return DOWN
|
2020-06-03 17:10:33 -07:00
|
|
|
return new_cp / new_cp_norm
|
|
|
|
return cp / cp_norm
|
2018-08-15 16:23:29 -07:00
|
|
|
|
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
###
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
|
|
|
def compass_directions(n=4, start_vect=RIGHT):
|
2018-10-05 17:19:48 -07:00
|
|
|
angle = TAU / n
|
2018-03-30 18:19:23 -07:00
|
|
|
return np.array([
|
2018-04-06 13:58:59 -07:00
|
|
|
rotate_vector(start_vect, k * angle)
|
2018-03-30 18:19:23 -07:00
|
|
|
for k in range(n)
|
|
|
|
])
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def complex_to_R3(complex_num):
|
|
|
|
return np.array((complex_num.real, complex_num.imag, 0))
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def R3_to_complex(point):
|
|
|
|
return complex(*point[:2])
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-05-30 12:02:25 -07:00
|
|
|
def complex_func_to_R3_func(complex_func):
|
|
|
|
return lambda p: complex_to_R3(complex_func(R3_to_complex(p)))
|
|
|
|
|
|
|
|
|
2018-03-30 18:19:23 -07:00
|
|
|
def center_of_mass(points):
|
|
|
|
points = [np.array(point).astype("float") for point in points]
|
|
|
|
return sum(points) / len(points)
|
2018-07-14 10:32:13 -07:00
|
|
|
|
|
|
|
|
2019-05-27 19:48:33 -07:00
|
|
|
def midpoint(point1, point2):
|
|
|
|
return center_of_mass([point1, point2])
|
|
|
|
|
|
|
|
|
2018-07-14 10:32:13 -07:00
|
|
|
def line_intersection(line1, line2):
|
|
|
|
"""
|
|
|
|
return intersection point of two lines,
|
|
|
|
each defined with a pair of vectors determining
|
|
|
|
the end points
|
|
|
|
"""
|
|
|
|
x_diff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0])
|
|
|
|
y_diff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1])
|
|
|
|
|
|
|
|
def det(a, b):
|
|
|
|
return a[0] * b[1] - a[1] * b[0]
|
|
|
|
|
|
|
|
div = det(x_diff, y_diff)
|
|
|
|
if div == 0:
|
|
|
|
raise Exception("Lines do not intersect")
|
|
|
|
d = (det(*line1), det(*line2))
|
|
|
|
x = det(d, x_diff) / div
|
|
|
|
y = det(d, y_diff) / div
|
|
|
|
return np.array([x, y, 0])
|
2018-08-28 09:45:12 -07:00
|
|
|
|
|
|
|
|
2020-02-04 15:24:16 -08:00
|
|
|
def find_intersection(p0, v0, p1, v1, threshold=1e-5):
|
|
|
|
"""
|
|
|
|
Return the intersection of a line passing through p0 in direction v0
|
|
|
|
with one passing through p1 in direction v1. (Or array of intersections
|
|
|
|
from arrays of such points/directions).
|
|
|
|
For 3d values, it returns the point on the ray p0 + v0 * t closest to the
|
|
|
|
ray p1 + v1 * t
|
|
|
|
"""
|
|
|
|
p0 = np.array(p0, ndmin=2)
|
|
|
|
v0 = np.array(v0, ndmin=2)
|
|
|
|
p1 = np.array(p1, ndmin=2)
|
|
|
|
v1 = np.array(v1, ndmin=2)
|
|
|
|
m, n = np.shape(p0)
|
|
|
|
assert(n in [2, 3])
|
|
|
|
|
|
|
|
numer = np.cross(v1, p1 - p0)
|
|
|
|
denom = np.cross(v1, v0)
|
|
|
|
if n == 3:
|
|
|
|
d = len(np.shape(numer))
|
|
|
|
new_numer = np.multiply(numer, numer).sum(d - 1)
|
|
|
|
new_denom = np.multiply(denom, numer).sum(d - 1)
|
|
|
|
numer, denom = new_numer, new_denom
|
|
|
|
|
|
|
|
denom[abs(denom) < threshold] = np.inf # So that ratio goes to 0 there
|
|
|
|
ratio = numer / denom
|
|
|
|
ratio = np.repeat(ratio, n).reshape((m, n))
|
|
|
|
return p0 + ratio * v0
|
|
|
|
|
|
|
|
|
2021-01-28 14:02:43 +05:30
|
|
|
def get_closest_point_on_line(a, b, p):
|
|
|
|
"""
|
|
|
|
It returns point x such that
|
|
|
|
x is on line ab and xp is perpendicular to ab.
|
|
|
|
If x lies beyond ab line, then it returns nearest edge(a or b).
|
|
|
|
"""
|
|
|
|
# x = b + t*(a-b) = t*a + (1-t)*b
|
|
|
|
t = np.dot(p - b, a - b) / np.dot(a - b, a - b)
|
|
|
|
if t < 0:
|
|
|
|
t = 0
|
|
|
|
if t > 1:
|
|
|
|
t = 1
|
|
|
|
return ((t * a) + ((1 - t) * b))
|
|
|
|
|
|
|
|
|
2018-08-28 09:45:12 -07:00
|
|
|
def get_winding_number(points):
|
|
|
|
total_angle = 0
|
|
|
|
for p1, p2 in adjacent_pairs(points):
|
|
|
|
d_angle = angle_of_vector(p2) - angle_of_vector(p1)
|
|
|
|
d_angle = ((d_angle + PI) % TAU) - PI
|
|
|
|
total_angle += d_angle
|
|
|
|
return total_angle / TAU
|
2020-02-10 14:47:36 -08:00
|
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
|
|
|
|
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):
|
2021-02-02 16:43:24 -08:00
|
|
|
return v[0] * v[0] + v[1] * v[1] + v[2] * v[2]
|
2020-02-10 14:47:36 -08:00
|
|
|
|
|
|
|
|
2020-02-11 19:48:50 -08:00
|
|
|
# TODO, fails for polygons drawn over themselves
|
2021-02-02 17:20:48 -08:00
|
|
|
def earclip_triangulation(verts, ring_ends):
|
2021-02-02 15:35:03 -08:00
|
|
|
"""
|
|
|
|
Returns a list of indices giving a triangulation
|
|
|
|
of a polygon, potentially with holes
|
|
|
|
|
2021-02-02 16:43:24 -08:00
|
|
|
- verts is a numpy array of points
|
2021-02-02 15:35:03 -08:00
|
|
|
|
2021-02-02 17:20:48 -08:00
|
|
|
- ring_ends is a list of indices indicating where
|
2021-02-02 15:35:03 -08:00
|
|
|
the ends of new paths are
|
|
|
|
"""
|
2021-02-02 17:20:48 -08:00
|
|
|
|
|
|
|
# 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)
|
|
|
|
]
|
2021-06-19 16:00:39 +08:00
|
|
|
# Points at the same position may cause problems
|
2021-06-18 14:43:09 +08:00
|
|
|
for i in rings:
|
|
|
|
verts[i[0]] += (verts[i[1]]-verts[i[0]])*5e-6
|
|
|
|
verts[i[-1]] += (verts[i[-2]]-verts[i[-1]])*5e-6
|
2021-02-02 17:20:48 -08:00
|
|
|
attached_rings = rings[:1]
|
|
|
|
detached_rings = rings[1:]
|
2020-02-10 14:47:36 -08:00
|
|
|
loop_connections = dict()
|
2021-02-02 17:20:48 -08:00
|
|
|
|
|
|
|
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)
|
|
|
|
]
|
|
|
|
|
2021-02-03 17:52:11 -08:00
|
|
|
# Closet point on the atttached rings to an estimated midpoint
|
2021-02-02 17:20:48 -08:00
|
|
|
# of the detached rings
|
2021-02-03 17:52:11 -08:00
|
|
|
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))
|
2021-02-02 17:20:48 -08:00
|
|
|
# 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]))
|
2021-02-03 17:52:11 -08:00
|
|
|
# Recalculate i based on new j
|
|
|
|
i = min(i_range, key=lambda i: norm_squared(verts[i] - verts[j]))
|
2021-02-02 17:20:48 -08:00
|
|
|
|
|
|
|
# Remember to connect the polygon at these points
|
2020-02-10 14:47:36 -08:00
|
|
|
loop_connections[i] = j
|
|
|
|
loop_connections[j] = i
|
2021-02-02 17:20:48 -08:00
|
|
|
|
|
|
|
# Move the ring which j belongs to from the
|
|
|
|
# attached list to the detached list
|
|
|
|
new_ring = next(filter(
|
2021-06-18 14:43:09 +08:00
|
|
|
lambda ring: ring[0] <= j <= ring[-1],
|
2021-02-02 17:20:48 -08:00
|
|
|
detached_rings
|
|
|
|
))
|
|
|
|
detached_rings.remove(new_ring)
|
|
|
|
attached_rings.append(new_ring)
|
2020-02-10 14:47:36 -08:00
|
|
|
|
|
|
|
# Setup linked list
|
|
|
|
after = []
|
2021-02-02 15:56:55 -08:00
|
|
|
end0 = 0
|
2021-02-02 17:20:48 -08:00
|
|
|
for end1 in ring_ends:
|
2021-02-02 15:56:55 -08:00
|
|
|
after.extend(range(end0 + 1, end1))
|
|
|
|
after.append(end0)
|
|
|
|
end0 = end1
|
2020-02-10 14:47:36 -08:00
|
|
|
|
|
|
|
# Find an ordering of indices walking around the polygon
|
|
|
|
indices = []
|
|
|
|
i = 0
|
2021-02-02 17:20:48 -08:00
|
|
|
for x in range(len(verts) + len(ring_ends) - 1):
|
2020-02-11 19:48:50 -08:00
|
|
|
# starting = False
|
2020-02-10 14:47:36 -08:00
|
|
|
if i in loop_connections:
|
|
|
|
j = loop_connections[i]
|
|
|
|
indices.extend([i, j])
|
|
|
|
i = after[j]
|
|
|
|
else:
|
|
|
|
indices.append(i)
|
|
|
|
i = after[i]
|
2020-02-11 19:48:50 -08:00
|
|
|
if i == 0:
|
|
|
|
break
|
2020-02-10 14:47:36 -08:00
|
|
|
|
|
|
|
meta_indices = earcut(verts[indices, :2], [len(indices)])
|
|
|
|
return [indices[mi] for mi in meta_indices]
|