diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index 50ac24b7a9..86b5cd1386 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -1830,60 +1830,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) + num_curves = vmobject.get_num_curves() + if num_curves == 0: + self.clear_points() + return self - # 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) + # 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) - self.clear_points() - if num_cubics == 0: - return self + 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: