Skip to content

Commit 938b8fc

Browse files
authored
Optimize VMobject methods which append to points (#3765)
1 parent 0a2fbbe commit 938b8fc

File tree

1 file changed

+98
-20
lines changed

1 file changed

+98
-20
lines changed

manim/mobject/types/vectorized_mobject.py

Lines changed: 98 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ def __init__(
159159
self.shade_in_3d: bool = shade_in_3d
160160
self.tolerance_for_point_equality: float = tolerance_for_point_equality
161161
self.n_points_per_cubic_curve: int = n_points_per_cubic_curve
162+
self._bezier_t_values: npt.NDArray[float] = np.linspace(
163+
0, 1, n_points_per_cubic_curve
164+
)
162165
self.cap_style: CapStyleType = cap_style
163166
super().__init__(**kwargs)
164167
self.submobjects: list[VMobject]
@@ -753,7 +756,7 @@ def set_anchors_and_handles(
753756
assert len(anchors1) == len(handles1) == len(handles2) == len(anchors2)
754757
nppcc = self.n_points_per_cubic_curve # 4
755758
total_len = nppcc * len(anchors1)
756-
self.points = np.zeros((total_len, self.dim))
759+
self.points = np.empty((total_len, self.dim))
757760
# the following will, from the four sets, dispatch them in points such that
758761
# self.points = [
759762
# anchors1[0], handles1[0], handles2[0], anchors1[0], anchors1[1],
@@ -765,23 +768,61 @@ def set_anchors_and_handles(
765768
return self
766769

767770
def clear_points(self) -> None:
771+
# TODO: shouldn't this return self instead of None?
768772
self.points = np.zeros((0, self.dim))
769773

770774
def append_points(self, new_points: Point3D_Array) -> Self:
775+
"""Append the given ``new_points`` to the end of
776+
:attr:`VMobject.points`.
777+
778+
Parameters
779+
----------
780+
new_points
781+
An array of 3D points to append.
782+
783+
Returns
784+
-------
785+
:class:`VMobject`
786+
The VMobject itself, after appending ``new_points``.
787+
"""
771788
# TODO, check that number new points is a multiple of 4?
772789
# or else that if len(self.points) % 4 == 1, then
773790
# len(new_points) % 4 == 3?
774-
self.points = np.append(self.points, new_points, axis=0)
791+
n = len(self.points)
792+
points = np.empty((n + len(new_points), self.dim))
793+
points[:n] = self.points
794+
points[n:] = new_points
795+
self.points = points
775796
return self
776797

777798
def start_new_path(self, point: Point3D) -> Self:
778-
if len(self.points) % 4 != 0:
799+
"""Append a ``point`` to the :attr:`VMobject.points`, which will be the
800+
beginning of a new Bézier curve in the path given by the points. If
801+
there's an unfinished curve at the end of :attr:`VMobject.points`,
802+
complete it by appending the last Bézier curve's start anchor as many
803+
times as needed.
804+
805+
Parameters
806+
----------
807+
point
808+
A 3D point to append to :attr:`VMobject.points`.
809+
810+
Returns
811+
-------
812+
:class:`VMobject`
813+
The VMobject itself, after appending ``point`` and starting a new
814+
curve.
815+
"""
816+
n_points = len(self.points)
817+
nppc = self.n_points_per_curve
818+
if n_points % nppc != 0:
779819
# close the open path by appending the last
780820
# start anchor sufficiently often
781821
last_anchor = self.get_start_anchors()[-1]
782-
for _ in range(4 - (len(self.points) % 4)):
783-
self.append_points([last_anchor])
784-
self.append_points([point])
822+
closure = [last_anchor] * (nppc - (n_points % nppc))
823+
self.append_points(closure + [point])
824+
else:
825+
self.append_points([point])
785826
return self
786827

787828
def add_cubic_bezier_curve(
@@ -863,7 +904,7 @@ def add_line_to(self, point: Point3D) -> Self:
863904
----------
864905
865906
point
866-
end of the straight line.
907+
The end of the straight line.
867908
868909
Returns
869910
-------
@@ -873,8 +914,8 @@ def add_line_to(self, point: Point3D) -> Self:
873914
nppcc = self.n_points_per_cubic_curve
874915
self.add_cubic_bezier_curve_to(
875916
*(
876-
interpolate(self.get_last_point(), point, a)
877-
for a in np.linspace(0, 1, nppcc)[1:]
917+
interpolate(self.get_last_point(), point, t)
918+
for t in self._bezier_t_values[1:]
878919
)
879920
)
880921
return self
@@ -939,15 +980,54 @@ def close_path(self) -> None:
939980
self.add_line_to(self.get_subpaths()[-1][0])
940981

941982
def add_points_as_corners(self, points: Iterable[Point3D]) -> Iterable[Point3D]:
942-
for point in points:
943-
self.add_line_to(point)
983+
"""Append multiple straight lines at the end of
984+
:attr:`VMobject.points`, which connect the given ``points`` in order
985+
starting from the end of the current path. These ``points`` would be
986+
therefore the corners of the new polyline appended to the path.
987+
988+
Parameters
989+
----------
990+
points
991+
An array of 3D points representing the corners of the polyline to
992+
append to :attr:`VMobject.points`.
993+
994+
Returns
995+
-------
996+
:class:`VMobject`
997+
The VMobject itself, after appending the straight lines to its
998+
path.
999+
"""
1000+
points = np.asarray(points).reshape(-1, self.dim)
1001+
if self.has_new_path_started():
1002+
# Pop the last point from self.points and
1003+
# add it to start_corners
1004+
start_corners = np.empty((len(points), self.dim))
1005+
start_corners[0] = self.points[-1]
1006+
start_corners[1:] = points[:-1]
1007+
end_corners = points
1008+
self.points = self.points[:-1]
1009+
else:
1010+
start_corners = points[:-1]
1011+
end_corners = points[1:]
1012+
1013+
nppcc = self.n_points_per_cubic_curve
1014+
new_points = np.empty((nppcc * start_corners.shape[0], self.dim))
1015+
new_points[::nppcc] = start_corners
1016+
new_points[nppcc - 1 :: nppcc] = end_corners
1017+
for i, t in enumerate(self._bezier_t_values):
1018+
new_points[i::nppcc] = interpolate(start_corners, end_corners, t)
1019+
1020+
self.append_points(new_points)
1021+
# TODO: shouldn't this method return self instead of points?
9441022
return points
9451023

9461024
def set_points_as_corners(self, points: Point3D_Array) -> Self:
947-
"""Given an array of points, set them as corner of the vmobject.
1025+
"""Given an array of points, set them as corners of the
1026+
:class:`VMobject`.
9481027
949-
To achieve that, this algorithm sets handles aligned with the anchors such that the resultant bezier curve will be the segment
950-
between the two anchors.
1028+
To achieve that, this algorithm sets handles aligned with the anchors
1029+
such that the resultant Bézier curve will be the segment between the
1030+
two anchors.
9511031
9521032
Parameters
9531033
----------
@@ -957,7 +1037,7 @@ def set_points_as_corners(self, points: Point3D_Array) -> Self:
9571037
Returns
9581038
-------
9591039
:class:`VMobject`
960-
``self``
1040+
The VMobject itself, after setting the new points as corners.
9611041
9621042
9631043
Examples
@@ -985,7 +1065,7 @@ def construct(self):
9851065
# This will set the handles aligned with the anchors.
9861066
# Id est, a bezier curve will be the segment from the two anchors such that the handles belongs to this segment.
9871067
self.set_anchors_and_handles(
988-
*(interpolate(points[:-1], points[1:], a) for a in np.linspace(0, 1, nppcc))
1068+
*(interpolate(points[:-1], points[1:], t) for t in self._bezier_t_values)
9891069
)
9901070
return self
9911071

@@ -1036,17 +1116,15 @@ def make_jagged(self) -> Self:
10361116

10371117
def add_subpath(self, points: Point3D_Array) -> Self:
10381118
assert len(points) % 4 == 0
1039-
self.points: Point3D_Array = np.append(self.points, points, axis=0)
1119+
self.append_points(points)
10401120
return self
10411121

10421122
def append_vectorized_mobject(self, vectorized_mobject: VMobject) -> None:
1043-
new_points = list(vectorized_mobject.points)
1044-
10451123
if self.has_new_path_started():
10461124
# Remove last point, which is starting
10471125
# a new path
10481126
self.points = self.points[:-1]
1049-
self.append_points(new_points)
1127+
self.append_points(vectorized_mobject.points)
10501128

10511129
def apply_function(self, function: MappingFunction) -> Self:
10521130
factor = self.pre_function_handle_to_anchor_scale_factor

0 commit comments

Comments
 (0)