Skip to content

Optimize VMobject.pointwise_become_partial() #3760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 65 additions & 34 deletions manim/mobject/types/vectorized_mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -1754,60 +1754,91 @@ def pointwise_become_partial(
a: float,
b: float,
) -> Self:
"""Given two bounds a and b, transforms the points of the self vmobject into the points of the vmobject
passed as parameter with respect to the bounds. Points here stand for control points of the bezier curves (anchors and handles)
"""Given a 2nd :class:`.VMobject` ``vmobject``, a lower bound ``a`` and
an upper bound ``b``, modify this :class:`.VMobject`'s points to
match the portion of the Bézier spline described by ``vmobject.points``
with the parameter ``t`` between ``a`` and ``b``.

Parameters
----------
vmobject
The vmobject that will serve as a model.
The :class:`.VMobject` that will serve as a model.
a
upper-bound.
The lower bound for ``t``.
b
lower-bound
The upper bound for ``t``

Returns
-------
:class:`VMobject`
``self``
:class:`.VMobject`
The :class:`.VMobject` itself, after the transformation.

Raises
------
TypeError
If ``vmobject`` is not an instance of :class:`VMobject`.
"""
assert isinstance(vmobject, VMobject)
if not isinstance(vmobject, VMobject):
raise TypeError(
f"Expected a VMobject, got value {vmobject} of type "
f"{type(vmobject).__name__}."
)
# Partial curve includes three portions:
# - A middle section, which matches the curve exactly
# - A start, which is some ending portion of an inner cubic
# - An end, which is the starting portion of a later inner cubic
# - A middle section, which matches the curve exactly.
# - A start, which is some ending portion of an inner cubic.
# - An end, which is the starting portion of a later inner cubic.
if a <= 0 and b >= 1:
self.set_points(vmobject.points)
return self
bezier_quads = vmobject.get_cubic_bezier_tuples()
num_cubics = len(bezier_quads)

# The following two lines will compute which bezier curves of the given mobject need to be processed.
# The residue basically indicates de proportion of the selected bezier curve that have to be selected.
# Ex : if lower_index is 3, and lower_residue is 0.4, then the algorithm will append to the points 0.4 of the third bezier curve
lower_index, lower_residue = integer_interpolate(0, num_cubics, a)
upper_index, upper_residue = integer_interpolate(0, num_cubics, b)

self.clear_points()
if num_cubics == 0:
num_curves = vmobject.get_num_curves()
if num_curves == 0:
self.clear_points()
return self

# The following two lines will compute which Bézier curves of the given Mobject must be processed.
# The residue indicates the proportion of the selected Bézier curve which must be selected.
#
# Example: if num_curves is 10, a is 0.34 and b is 0.78, then:
# - lower_index is 3 and lower_residue is 0.4, which means the algorithm will look at the 3rd Bézier
# and select its part which ranges from t=0.4 to t=1.
# - upper_index is 7 and upper_residue is 0.8, which means the algorithm will look at the 7th Bézier
# and select its part which ranges from t=0 to t=0.8.
lower_index, lower_residue = integer_interpolate(0, num_curves, a)
upper_index, upper_residue = integer_interpolate(0, num_curves, b)

nppc = self.n_points_per_curve
# If both indices coincide, get a part of a single Bézier curve.
if lower_index == upper_index:
self.append_points(
partial_bezier_points(
bezier_quads[lower_index],
lower_residue,
upper_residue,
),
# Look at the "lower_index"-th Bézier curve and select its part from
# t=lower_residue to t=upper_residue.
self.points = partial_bezier_points(
vmobject.points[nppc * lower_index : nppc * (lower_index + 1)],
lower_residue,
upper_residue,
)
else:
self.append_points(
partial_bezier_points(bezier_quads[lower_index], lower_residue, 1),
# Allocate space for (upper_index-lower_index+1) Bézier curves.
self.points = np.empty((nppc * (upper_index - lower_index + 1), self.dim))
# Look at the "lower_index"-th Bezier curve and select its part from
# t=lower_residue to t=1. This is the first curve in self.points.
self.points[:nppc] = partial_bezier_points(
vmobject.points[nppc * lower_index : nppc * (lower_index + 1)],
lower_residue,
1,
)
for quad in bezier_quads[lower_index + 1 : upper_index]:
self.append_points(quad)
self.append_points(
partial_bezier_points(bezier_quads[upper_index], 0, upper_residue),
# If there are more curves between the "lower_index"-th and the
# "upper_index"-th Béziers, add them all to self.points.
self.points[nppc:-nppc] = vmobject.points[
nppc * (lower_index + 1) : nppc * upper_index
]
# Look at the "upper_index"-th Bézier curve and select its part from
# t=0 to t=upper_residue. This is the last curve in self.points.
self.points[-nppc:] = partial_bezier_points(
vmobject.points[nppc * upper_index : nppc * (upper_index + 1)],
0,
upper_residue,
)

return self

def get_subcurve(self, a: float, b: float) -> Self:
Expand Down