Skip to content

Commit 7b841c2

Browse files
authored
Created and optimized Bézier splitting functions such as partial_bezier_points() in manim.utils.bezier (#3766)
* Optimized manim.utils.partial_bezier_points() * Added split_bezier, subdivide_bezier and bezier_remap, and tests * Use bezier_remap() in VMobject and OpenGLVMobject() * Note that partial_bezier_points is similar to calling split_bezier twice
1 parent b415604 commit 7b841c2

File tree

6 files changed

+1270
-184
lines changed

6 files changed

+1270
-184
lines changed

manim/mobject/opengl/opengl_vectorized_mobject.py

Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
from manim.renderer.shader_wrapper import ShaderWrapper
1616
from manim.utils.bezier import (
1717
bezier,
18+
bezier_remap,
1819
get_quadratic_approximation_of_cubic,
1920
get_smooth_cubic_bezier_handle_points,
2021
integer_interpolate,
2122
interpolate,
22-
partial_quadratic_bezier_points,
23+
partial_bezier_points,
2324
proportions_along_bezier_curve_for_point,
24-
quadratic_bezier_remap,
2525
)
2626
from manim.utils.color import BLACK, WHITE, ManimColor, ParsableManimColor
2727
from manim.utils.config_ops import _Data
@@ -555,7 +555,7 @@ def subdivide_sharp_curves(self, angle_threshold=30 * DEGREES, recurse=True):
555555
alphas = np.linspace(0, 1, n + 1)
556556
new_points.extend(
557557
[
558-
partial_quadratic_bezier_points(tup, a1, a2)
558+
partial_bezier_points(tup, a1, a2)
559559
for a1, a2 in zip(alphas, alphas[1:])
560560
],
561561
)
@@ -1275,33 +1275,12 @@ def insert_n_curves_to_point_list(self, n: int, points: np.ndarray) -> np.ndarra
12751275
if len(points) == 1:
12761276
return np.repeat(points, nppc * n, 0)
12771277

1278-
bezier_groups = self.get_bezier_tuples_from_points(points)
1279-
norms = np.array([np.linalg.norm(bg[nppc - 1] - bg[0]) for bg in bezier_groups])
1280-
total_norm = sum(norms)
1281-
# Calculate insertions per curve (ipc)
1282-
if total_norm < 1e-6:
1283-
ipc = [n] + [0] * (len(bezier_groups) - 1)
1284-
else:
1285-
ipc = np.round(n * norms / sum(norms)).astype(int)
1286-
1287-
diff = n - sum(ipc)
1288-
for _ in range(diff):
1289-
ipc[np.argmin(ipc)] += 1
1290-
for _ in range(-diff):
1291-
ipc[np.argmax(ipc)] -= 1
1292-
1293-
new_length = sum(x + 1 for x in ipc)
1294-
new_points = np.empty((new_length, nppc, 3))
1295-
i = 0
1296-
for group, n_inserts in zip(bezier_groups, ipc):
1297-
# What was once a single quadratic curve defined
1298-
# by "group" will now be broken into n_inserts + 1
1299-
# smaller quadratic curves
1300-
alphas = np.linspace(0, 1, n_inserts + 2)
1301-
for a1, a2 in zip(alphas, alphas[1:]):
1302-
new_points[i] = partial_quadratic_bezier_points(group, a1, a2)
1303-
i = i + 1
1304-
return np.vstack(new_points)
1278+
bezier_tuples = self.get_bezier_tuples_from_points(points)
1279+
current_number_of_curves = len(bezier_tuples)
1280+
new_number_of_curves = current_number_of_curves + n
1281+
new_bezier_tuples = bezier_remap(bezier_tuples, new_number_of_curves)
1282+
new_points = new_bezier_tuples.reshape(-1, 3)
1283+
return new_points
13051284

13061285
def interpolate(self, mobject1, mobject2, alpha, *args, **kwargs):
13071286
super().interpolate(mobject1, mobject2, alpha, *args, **kwargs)
@@ -1354,32 +1333,26 @@ def pointwise_become_partial(
13541333
return self
13551334
if lower_index == upper_index:
13561335
self.append_points(
1357-
partial_quadratic_bezier_points(
1336+
partial_bezier_points(
13581337
bezier_triplets[lower_index],
13591338
lower_residue,
13601339
upper_residue,
13611340
),
13621341
)
13631342
else:
13641343
self.append_points(
1365-
partial_quadratic_bezier_points(
1366-
bezier_triplets[lower_index], lower_residue, 1
1367-
),
1344+
partial_bezier_points(bezier_triplets[lower_index], lower_residue, 1),
13681345
)
13691346
inner_points = bezier_triplets[lower_index + 1 : upper_index]
13701347
if len(inner_points) > 0:
13711348
if remap:
1372-
new_triplets = quadratic_bezier_remap(
1373-
inner_points, num_quadratics - 2
1374-
)
1349+
new_triplets = bezier_remap(inner_points, num_quadratics - 2)
13751350
else:
13761351
new_triplets = bezier_triplets
13771352

13781353
self.append_points(np.asarray(new_triplets).reshape(-1, 3))
13791354
self.append_points(
1380-
partial_quadratic_bezier_points(
1381-
bezier_triplets[upper_index], 0, upper_residue
1382-
),
1355+
partial_bezier_points(bezier_triplets[upper_index], 0, upper_residue),
13831356
)
13841357
return self
13851358

manim/mobject/types/vectorized_mobject.py

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
)
3131
from manim.utils.bezier import (
3232
bezier,
33+
bezier_remap,
3334
get_smooth_handle_points,
3435
integer_interpolate,
3536
interpolate,
@@ -1693,40 +1694,11 @@ def insert_n_curves_to_point_list(
16931694
if len(points) == 1:
16941695
nppcc = self.n_points_per_cubic_curve
16951696
return np.repeat(points, nppcc * n, 0)
1696-
bezier_quads = self.get_cubic_bezier_tuples_from_points(points)
1697-
curr_num = len(bezier_quads)
1698-
target_num = curr_num + n
1699-
# This is an array with values ranging from 0
1700-
# up to curr_num, with repeats such that
1701-
# it's total length is target_num. For example,
1702-
# with curr_num = 10, target_num = 15, this would
1703-
# be [0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9]
1704-
repeat_indices = (np.arange(target_num, dtype="i") * curr_num) // target_num
1705-
1706-
# If the nth term of this list is k, it means
1707-
# that the nth curve of our path should be split
1708-
# into k pieces.
1709-
# In the above example our array had the following elements
1710-
# [0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9]
1711-
# We have two 0s, one 1, two 2s and so on.
1712-
# The split factors array would hence be:
1713-
# [2, 1, 2, 1, 2, 1, 2, 1, 2, 1]
1714-
split_factors = np.zeros(curr_num, dtype="i")
1715-
for val in repeat_indices:
1716-
split_factors[val] += 1
1717-
1718-
new_points = np.zeros((0, self.dim))
1719-
for quad, sf in zip(bezier_quads, split_factors):
1720-
# What was once a single cubic curve defined
1721-
# by "quad" will now be broken into sf
1722-
# smaller cubic curves
1723-
alphas = np.linspace(0, 1, sf + 1)
1724-
for a1, a2 in zip(alphas, alphas[1:]):
1725-
new_points = np.append(
1726-
new_points,
1727-
partial_bezier_points(quad, a1, a2),
1728-
axis=0,
1729-
)
1697+
bezier_tuples = self.get_cubic_bezier_tuples_from_points(points)
1698+
current_number_of_curves = len(bezier_tuples)
1699+
new_number_of_curves = current_number_of_curves + n
1700+
new_bezier_tuples = bezier_remap(bezier_tuples, new_number_of_curves)
1701+
new_points = new_bezier_tuples.reshape(-1, 3)
17301702
return new_points
17311703

17321704
def align_rgbas(self, vmobject: VMobject) -> Self:

0 commit comments

Comments
 (0)