Skip to content

Optimize VMobject methods which append to points #3765

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 3 commits into from
May 23, 2024
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
118 changes: 98 additions & 20 deletions manim/mobject/types/vectorized_mobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ def __init__(
self.shade_in_3d: bool = shade_in_3d
self.tolerance_for_point_equality: float = tolerance_for_point_equality
self.n_points_per_cubic_curve: int = n_points_per_cubic_curve
self._bezier_t_values: npt.NDArray[float] = np.linspace(
0, 1, n_points_per_cubic_curve
)
self.cap_style: CapStyleType = cap_style
super().__init__(**kwargs)
self.submobjects: list[VMobject]
Expand Down Expand Up @@ -753,7 +756,7 @@ def set_anchors_and_handles(
assert len(anchors1) == len(handles1) == len(handles2) == len(anchors2)
nppcc = self.n_points_per_cubic_curve # 4
total_len = nppcc * len(anchors1)
self.points = np.zeros((total_len, self.dim))
self.points = np.empty((total_len, self.dim))
# the following will, from the four sets, dispatch them in points such that
# self.points = [
# anchors1[0], handles1[0], handles2[0], anchors1[0], anchors1[1],
Expand All @@ -765,23 +768,61 @@ def set_anchors_and_handles(
return self

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

def append_points(self, new_points: Point3D_Array) -> Self:
"""Append the given ``new_points`` to the end of
:attr:`VMobject.points`.

Parameters
----------
new_points
An array of 3D points to append.

Returns
-------
:class:`VMobject`
The VMobject itself, after appending ``new_points``.
"""
# TODO, check that number new points is a multiple of 4?
# or else that if len(self.points) % 4 == 1, then
# len(new_points) % 4 == 3?
self.points = np.append(self.points, new_points, axis=0)
n = len(self.points)
points = np.empty((n + len(new_points), self.dim))
points[:n] = self.points
points[n:] = new_points
self.points = points
return self

def start_new_path(self, point: Point3D) -> Self:
if len(self.points) % 4 != 0:
"""Append a ``point`` to the :attr:`VMobject.points`, which will be the
beginning of a new Bézier curve in the path given by the points. If
there's an unfinished curve at the end of :attr:`VMobject.points`,
complete it by appending the last Bézier curve's start anchor as many
times as needed.

Parameters
----------
point
A 3D point to append to :attr:`VMobject.points`.

Returns
-------
:class:`VMobject`
The VMobject itself, after appending ``point`` and starting a new
curve.
"""
n_points = len(self.points)
nppc = self.n_points_per_curve
if n_points % nppc != 0:
# close the open path by appending the last
# start anchor sufficiently often
last_anchor = self.get_start_anchors()[-1]
for _ in range(4 - (len(self.points) % 4)):
self.append_points([last_anchor])
self.append_points([point])
closure = [last_anchor] * (nppc - (n_points % nppc))
self.append_points(closure + [point])
else:
self.append_points([point])
return self

def add_cubic_bezier_curve(
Expand Down Expand Up @@ -863,7 +904,7 @@ def add_line_to(self, point: Point3D) -> Self:
----------

point
end of the straight line.
The end of the straight line.

Returns
-------
Expand All @@ -873,8 +914,8 @@ def add_line_to(self, point: Point3D) -> Self:
nppcc = self.n_points_per_cubic_curve
self.add_cubic_bezier_curve_to(
*(
interpolate(self.get_last_point(), point, a)
for a in np.linspace(0, 1, nppcc)[1:]
interpolate(self.get_last_point(), point, t)
for t in self._bezier_t_values[1:]
)
)
return self
Expand Down Expand Up @@ -939,15 +980,54 @@ def close_path(self) -> None:
self.add_line_to(self.get_subpaths()[-1][0])

def add_points_as_corners(self, points: Iterable[Point3D]) -> Iterable[Point3D]:
for point in points:
self.add_line_to(point)
"""Append multiple straight lines at the end of
:attr:`VMobject.points`, which connect the given ``points`` in order
starting from the end of the current path. These ``points`` would be
therefore the corners of the new polyline appended to the path.

Parameters
----------
points
An array of 3D points representing the corners of the polyline to
append to :attr:`VMobject.points`.

Returns
-------
:class:`VMobject`
The VMobject itself, after appending the straight lines to its
path.
"""
points = np.asarray(points).reshape(-1, self.dim)
if self.has_new_path_started():
# Pop the last point from self.points and
# add it to start_corners
start_corners = np.empty((len(points), self.dim))
start_corners[0] = self.points[-1]
start_corners[1:] = points[:-1]
end_corners = points
self.points = self.points[:-1]
else:
start_corners = points[:-1]
end_corners = points[1:]

nppcc = self.n_points_per_cubic_curve
new_points = np.empty((nppcc * start_corners.shape[0], self.dim))
new_points[::nppcc] = start_corners
new_points[nppcc - 1 :: nppcc] = end_corners
for i, t in enumerate(self._bezier_t_values):
new_points[i::nppcc] = interpolate(start_corners, end_corners, t)

self.append_points(new_points)
# TODO: shouldn't this method return self instead of points?
return points

def set_points_as_corners(self, points: Point3D_Array) -> Self:
"""Given an array of points, set them as corner of the vmobject.
"""Given an array of points, set them as corners of the
:class:`VMobject`.

To achieve that, this algorithm sets handles aligned with the anchors such that the resultant bezier curve will be the segment
between the two anchors.
To achieve that, this algorithm sets handles aligned with the anchors
such that the resultant Bézier curve will be the segment between the
two anchors.

Parameters
----------
Expand All @@ -957,7 +1037,7 @@ def set_points_as_corners(self, points: Point3D_Array) -> Self:
Returns
-------
:class:`VMobject`
``self``
The VMobject itself, after setting the new points as corners.


Examples
Expand Down Expand Up @@ -985,7 +1065,7 @@ def construct(self):
# This will set the handles aligned with the anchors.
# Id est, a bezier curve will be the segment from the two anchors such that the handles belongs to this segment.
self.set_anchors_and_handles(
*(interpolate(points[:-1], points[1:], a) for a in np.linspace(0, 1, nppcc))
*(interpolate(points[:-1], points[1:], t) for t in self._bezier_t_values)
)
return self

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

def add_subpath(self, points: Point3D_Array) -> Self:
assert len(points) % 4 == 0
self.points: Point3D_Array = np.append(self.points, points, axis=0)
self.append_points(points)
return self

def append_vectorized_mobject(self, vectorized_mobject: VMobject) -> None:
new_points = list(vectorized_mobject.points)

if self.has_new_path_started():
# Remove last point, which is starting
# a new path
self.points = self.points[:-1]
self.append_points(new_points)
self.append_points(vectorized_mobject.points)

def apply_function(self, function: MappingFunction) -> Self:
factor = self.pre_function_handle_to_anchor_scale_factor
Expand Down
Loading