Skip to content
Open
Show file tree
Hide file tree
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
44 changes: 44 additions & 0 deletions manim/mobject/geometry/line.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from manim.mobject.opengl.opengl_compatibility import ConvertToOpenGL
from manim.mobject.opengl.opengl_mobject import OpenGLMobject
from manim.mobject.types.vectorized_mobject import DashedVMobject, VGroup, VMobject
from manim.utils.bezier import partial_bezier_points
from manim.utils.color import WHITE
from manim.utils.space_ops import angle_of_vector, line_intersection, normalize

Expand Down Expand Up @@ -234,6 +235,49 @@ def construct(self):
self.generate_points()
return super().put_start_and_end_on(start, end)

def _trim_path_with_tip_base(
self,
point: Point3DLike,
at_start: bool,
) -> bool:
if self.path_arc != 0 or self.get_num_curves() <= 1:
return False

curve_index = 0 if at_start else self.get_num_curves() - 1
if curve_index < 0:
return False

curve_points = self.get_nth_curve_points(curve_index)
start_anchor = curve_points[0]
end_anchor = curve_points[-1]
segment = end_anchor - start_anchor
segment_length_sq = float(np.dot(segment, segment))
if segment_length_sq == 0:
return False

residue = float(
np.clip(
np.dot(np.asarray(point) - start_anchor, segment) / segment_length_sq,
0,
1,
)
)
nppc = self.n_points_per_curve
if at_start:
self.points[:nppc] = partial_bezier_points(curve_points, residue, 1)
else:
self.points[-nppc:] = partial_bezier_points(curve_points, 0, residue)
return True

def reset_endpoints_based_on_tip(self, tip: ArrowTip, at_start: bool) -> Self:
if self.get_length() == 0:
return self

if self._trim_path_with_tip_base(tip.base, at_start):
return self

return super().reset_endpoints_based_on_tip(tip, at_start)

def get_vector(self) -> Vector3D:
return self.get_end() - self.get_start()

Expand Down
40 changes: 40 additions & 0 deletions manim/mobject/opengl/opengl_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
Vector3D,
Vector3DLike,
)
from manim.utils.bezier import partial_bezier_points
from manim.utils.color import *
from manim.utils.iterables import adjacent_n_tuples, adjacent_pairs
from manim.utils.simple_functions import clip
Expand Down Expand Up @@ -553,6 +554,45 @@ def put_start_and_end_on(self, start: Point3DLike, end: Point3DLike) -> Self:
self.set_points_by_ends(start, end, self.path_arc)
return super().put_start_and_end_on(start, end)

def _trim_path_with_tip_base(self, point: Point3DLike, at_start: bool) -> bool:
if self.path_arc != 0 or self.get_num_curves() <= 1:
return False

curve_index = 0 if at_start else self.get_num_curves() - 1
if curve_index < 0:
return False

curve_points = self.get_nth_curve_points(curve_index)
start_anchor = curve_points[0]
end_anchor = curve_points[-1]
segment = end_anchor - start_anchor
segment_length_sq = float(np.dot(segment, segment))
if segment_length_sq == 0:
return False

residue = float(
np.clip(
np.dot(np.asarray(point) - start_anchor, segment) / segment_length_sq,
0,
1,
)
)
nppc = self.n_points_per_curve
if at_start:
self.points[:nppc] = partial_bezier_points(curve_points, residue, 1)
else:
self.points[-nppc:] = partial_bezier_points(curve_points, 0, residue)
return True

def reset_endpoints_based_on_tip(self, tip: OpenGLArrowTip, at_start: bool) -> Self:
if self.get_length() == 0:
return self

if self._trim_path_with_tip_base(tip.get_base(), at_start):
return self

return super().reset_endpoints_based_on_tip(tip, at_start)

def get_vector(self) -> Vector3D:
return self.get_end() - self.get_start()

Expand Down
44 changes: 44 additions & 0 deletions tests/module/mobject/geometry/test_unit_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,50 @@ def test_line_with_buff_and_path_arc():
np.testing.assert_allclose(line.points, expected_points)


def test_add_tip_preserves_polyline_corner():
line = Line()
line.set_points_as_corners(
[
np.array([0.0, 0.0, 0.0]),
np.array([0.0, 2.0, 0.0]),
np.array([3.0, 2.0, 0.0]),
]
)
original_first_curve = line.points[:4].copy()

line.add_tip(tip_length=0.5)

np.testing.assert_allclose(line.points[:4], original_first_curve)
np.testing.assert_allclose(line.points[3], np.array([0.0, 2.0, 0.0]))
np.testing.assert_allclose(line.points[4], np.array([0.0, 2.0, 0.0]))
np.testing.assert_allclose(line.points[-1], line.tip.base)
np.testing.assert_allclose(line.tip.base, np.array([2.5, 2.0, 0.0]))


def test_add_start_tip_preserves_polyline_corner():
line = Line()
line.set_points_as_corners(
[
np.array([0.0, 0.0, 0.0]),
np.array([0.0, 2.0, 0.0]),
np.array([3.0, 2.0, 0.0]),
]
)
original_last_curve = line.points[-4:].copy()

line.add_tip(at_start=True, tip_length=0.5)

np.testing.assert_allclose(line.points[-4:], original_last_curve)
np.testing.assert_allclose(line.points[3], np.array([0.0, 2.0, 0.0]))
np.testing.assert_allclose(line.points[4], np.array([0.0, 2.0, 0.0]))
np.testing.assert_allclose(line.points[0], line.start_tip.base, atol=1e-12)
np.testing.assert_allclose(
line.start_tip.base,
np.array([0.0, 0.5, 0.0]),
atol=1e-12,
)


def test_Circle_point_at_angle():
from manim import TAU

Expand Down
Loading