11import numpy as np
2+ import math
3+ import numbers
24
35from manimlib .constants import *
46from 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
580584class 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