Skip to content

Commit 8647a64

Browse files
committed
Reimplement arrow to be stroke, not fill (which will break some past scenes)
1 parent 3bb8f3f commit 8647a64

File tree

1 file changed

+99
-22
lines changed

1 file changed

+99
-22
lines changed

manimlib/mobject/geometry.py

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import numpy as np
2+
import math
3+
import numbers
24

35
from manimlib.constants import *
46
from manimlib.mobject.mobject import Mobject
@@ -404,32 +406,32 @@ def init_points(self):
404406
self.set_points_by_ends(self.start, self.end, self.buff, self.path_arc)
405407

406408
def set_points_by_ends(self, start, end, buff=0, path_arc=0):
409+
vect = end - start
410+
dist = get_norm(vect)
411+
if np.isclose(dist, 0):
412+
self.set_points_as_corners([start, end])
413+
return self
407414
if path_arc:
408-
self.set_points(Arc.create_quadratic_bezier_points(path_arc))
409-
self.put_start_and_end_on(start, end)
415+
radius = (dist / 2) / math.sin(path_arc / 2)
416+
alpha = (PI - path_arc) / 2
417+
center = start + radius * normalize(rotate_vector(end - start, alpha))
418+
419+
raw_arc_points = Arc.create_quadratic_bezier_points(
420+
angle=path_arc - 2 * buff / radius,
421+
start_angle=angle_of_vector(start - center) + buff / radius,
422+
)
423+
self.set_points(center + radius * raw_arc_points)
410424
else:
425+
if buff > 0 and dist > 0:
426+
start = start + vect * (buff / dist)
427+
end = end - vect * (buff / dist)
411428
self.set_points_as_corners([start, end])
412-
self.account_for_buff(self.buff)
429+
return self
413430

414431
def set_path_arc(self, new_value):
415432
self.path_arc = new_value
416433
self.init_points()
417434

418-
def account_for_buff(self, buff):
419-
if buff == 0:
420-
return
421-
#
422-
if self.path_arc == 0:
423-
length = self.get_length()
424-
else:
425-
length = self.get_arc_length()
426-
#
427-
if length < 2 * buff:
428-
return
429-
buff_prop = buff / length
430-
self.pointwise_become_partial(self, buff_prop, 1 - buff_prop)
431-
return self
432-
433435
def set_start_and_end_attrs(self, start, end):
434436
# If either start or end are Mobjects, this
435437
# gives their centers
@@ -439,8 +441,8 @@ def set_start_and_end_attrs(self, start, end):
439441
# Now that we know the direction between them,
440442
# we can find the appropriate boundary point from
441443
# start and end, if they're mobjects
442-
self.start = self.pointify(start, vect) + self.buff * vect
443-
self.end = self.pointify(end, -vect) - self.buff * vect
444+
self.start = self.pointify(start, vect)
445+
self.end = self.pointify(end, -vect)
444446

445447
def pointify(self, mob_or_point, direction=None):
446448
"""
@@ -461,8 +463,10 @@ def pointify(self, mob_or_point, direction=None):
461463

462464
def put_start_and_end_on(self, start, end):
463465
curr_start, curr_end = self.get_start_and_end()
464-
if (curr_start == curr_end).all():
465-
self.set_points_by_ends(start, end, self.path_arc)
466+
if np.isclose(curr_start, curr_end).all():
467+
# Handle null lines more gracefully
468+
self.set_points_by_ends(start, end, buff=0, path_arc=self.path_arc)
469+
return self
466470
return super().put_start_and_end_on(start, end)
467471

468472
def get_vector(self):
@@ -578,6 +582,79 @@ def __init__(self, **kwargs):
578582

579583

580584
class Arrow(Line):
585+
CONFIG = {
586+
"stroke_color": GREY_A,
587+
"stroke_width": 10,
588+
"tip_width_ratio": 4,
589+
"width_to_tip_len": 0.0075,
590+
"max_tip_length_to_length_ratio": 0.3,
591+
"max_width_to_length_ratio": 10,
592+
}
593+
594+
def set_points_by_ends(self, start, end, buff=0, path_arc=0):
595+
super().set_points_by_ends(start, end, buff, path_arc)
596+
self.insert_tip_anchor()
597+
return self
598+
599+
def init_colors(self):
600+
super().init_colors()
601+
self.create_tip_with_stroke_width()
602+
603+
def get_arc_length(self):
604+
# Push up into Line?
605+
arc_len = get_norm(self.get_vector())
606+
if self.path_arc > 0:
607+
arc_len *= self.path_arc / (2 * math.sin(self.path_arc / 2))
608+
return arc_len
609+
610+
def insert_tip_anchor(self):
611+
prev_end = self.get_end()
612+
arc_len = self.get_arc_length()
613+
tip_len = self.get_stroke_width() * self.width_to_tip_len * self.tip_width_ratio
614+
if tip_len > self.max_tip_length_to_length_ratio * arc_len:
615+
alpha = self.max_tip_length_to_length_ratio
616+
else:
617+
alpha = tip_len / arc_len
618+
self.pointwise_become_partial(self, 0, 1 - alpha)
619+
self.add_line_to(prev_end)
620+
return self
621+
622+
def create_tip_with_stroke_width(self):
623+
width = min(
624+
self.max_stroke_width,
625+
self.max_width_to_length_ratio * self.get_length(),
626+
)
627+
widths_array = np.full(self.get_num_points(), width)
628+
nppc = self.n_points_per_curve
629+
if len(widths_array) > nppc:
630+
widths_array[-nppc:] = [
631+
a * self.tip_width_ratio * width
632+
for a in np.linspace(1, 0, nppc)
633+
]
634+
self.set_stroke(width=widths_array)
635+
return self
636+
637+
def reset_tip(self):
638+
self.set_points_by_ends(
639+
self.get_start(),
640+
self.get_end(),
641+
path_arc=self.path_arc,
642+
)
643+
self.create_tip_with_stroke_width()
644+
return self
645+
646+
def set_stroke(self, color=None, width=None, *args, **kwargs):
647+
super().set_stroke(color=color, width=width, *args, **kwargs)
648+
if isinstance(width, numbers.Number):
649+
self.max_stroke_width = width
650+
self.reset_tip()
651+
return self
652+
653+
def _handle_scale_side_effects(self, scale_factor):
654+
return self.reset_tip()
655+
656+
657+
class FillArrow(Line):
581658
CONFIG = {
582659
"fill_color": GREY_A,
583660
"fill_opacity": 1,

0 commit comments

Comments
 (0)