mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Revert "Reverted Cleaned Up Arrow Implementation (commit d489b5df5c77...)"
Undoing my revert of another commit because I found a fix.
This reverts commit cd29ff6e0d
.
This commit is contained in:
parent
50da0b6aae
commit
61dbad5edd
1 changed files with 166 additions and 111 deletions
|
@ -12,7 +12,6 @@ from manimlib.utils.iterables import adjacent_pairs
|
|||
from manimlib.utils.simple_functions import fdiv
|
||||
from manimlib.utils.space_ops import angle_of_vector
|
||||
from manimlib.utils.space_ops import angle_between_vectors
|
||||
from manimlib.utils.space_ops import center_of_mass
|
||||
from manimlib.utils.space_ops import compass_directions
|
||||
from manimlib.utils.space_ops import line_intersection
|
||||
from manimlib.utils.space_ops import get_norm
|
||||
|
@ -21,14 +20,20 @@ from manimlib.utils.space_ops import rotate_vector
|
|||
|
||||
|
||||
DEFAULT_DOT_RADIUS = 0.08
|
||||
DEFAULT_SMALL_DOT_RADIUS = 0.04
|
||||
DEFAULT_DASH_LENGTH = 0.05
|
||||
DEFAULT_ARROW_TIP_LENGTH = 0.35
|
||||
|
||||
|
||||
class TipableVMobject(VMobject):
|
||||
CONFIG = {
|
||||
"tip_length": 0.3,
|
||||
"tip_length": DEFAULT_ARROW_TIP_LENGTH,
|
||||
# TODO
|
||||
"normal_vector": OUT,
|
||||
"tip_style": {
|
||||
"fill_opacity": 1,
|
||||
"stroke_width": 0,
|
||||
}
|
||||
}
|
||||
"""
|
||||
Meant simply for shard functionality between
|
||||
|
@ -36,61 +41,120 @@ class TipableVMobject(VMobject):
|
|||
"""
|
||||
|
||||
def add_tip(self, tip_length=None, at_start=False):
|
||||
tip = self.create_tip(tip_length, at_start)
|
||||
self.reset_endpoints_based_on_tip(tip, at_start)
|
||||
self.asign_tip_attr(tip, at_start)
|
||||
self.add(tip)
|
||||
return self
|
||||
|
||||
def create_tip(self, tip_length=None, at_start=False):
|
||||
tip = self.get_unpositioned_tip(tip_length)
|
||||
self.position_tip(tip, at_start)
|
||||
return tip
|
||||
|
||||
def get_unpositioned_tip(self, tip_length=None):
|
||||
if tip_length is None:
|
||||
tip_length = self.get_default_tip_length()
|
||||
color = self.get_color()
|
||||
style = {
|
||||
"fill_color": color,
|
||||
"stroke_color": color
|
||||
}
|
||||
style.update(self.tip_style)
|
||||
tip = ArrowTip(length=tip_length, **style)
|
||||
return tip
|
||||
|
||||
def position_tip(self, tip, at_start=False):
|
||||
# Last two control points, defining both
|
||||
# the end, and the tangency direction
|
||||
if at_start:
|
||||
anchor = self.get_start()
|
||||
handle = self.get_first_handle()
|
||||
self.start_tip = tip
|
||||
else:
|
||||
handle = self.get_last_handle()
|
||||
anchor = self.get_end()
|
||||
self.tip = tip
|
||||
tip.rotate(angle_of_vector(handle - anchor))
|
||||
tip.shift(anchor - tip.get_start())
|
||||
|
||||
self.reset_endpoints_based_on_tip(tip, at_start)
|
||||
self.add(tip)
|
||||
return self
|
||||
|
||||
def get_unpositioned_tip(self, tip_length=None):
|
||||
if tip_length is None:
|
||||
tip_length = self.tip_length
|
||||
tip = Triangle(start_angle=PI)
|
||||
tip.match_style(self)
|
||||
tip.set_fill(self.get_stroke_color(), opacity=1)
|
||||
tip.set_height(tip_length)
|
||||
tip.set_width(tip_length, stretch=True)
|
||||
tip.rotate(
|
||||
angle_of_vector(handle - anchor) -
|
||||
PI - tip.get_angle()
|
||||
)
|
||||
tip.shift(anchor - tip.get_tip_point())
|
||||
return tip
|
||||
|
||||
def reset_endpoints_based_on_tip(self, tip, at_start):
|
||||
tip_base = tip.point_from_proportion(0.5)
|
||||
if self.get_length() == 0:
|
||||
# Zero length, put_start_and_end_on wouldn't
|
||||
# work
|
||||
return self
|
||||
if at_start:
|
||||
self.put_start_and_end_on(
|
||||
tip_base, self.get_end()
|
||||
tip.get_base(), self.get_end()
|
||||
)
|
||||
else:
|
||||
self.put_start_and_end_on(
|
||||
self.get_start(), tip_base,
|
||||
self.get_start(), tip.get_base(),
|
||||
)
|
||||
return self
|
||||
|
||||
def asign_tip_attr(self, tip, at_start):
|
||||
if at_start:
|
||||
self.start_tip = tip
|
||||
else:
|
||||
self.tip = tip
|
||||
return self
|
||||
|
||||
def get_tips(self):
|
||||
result = VGroup()
|
||||
if hasattr(self, "tip"):
|
||||
result.add(self.tip)
|
||||
if hasattr(self, "start_tip"):
|
||||
result.add(self.start_tip)
|
||||
return result
|
||||
|
||||
def get_tip(self):
|
||||
tips = self.get_tips()
|
||||
if len(tips) == 0:
|
||||
raise Exception("tip not found")
|
||||
else:
|
||||
return tips[0]
|
||||
|
||||
def get_default_tip_length(self):
|
||||
return self.tip_length
|
||||
|
||||
def get_first_handle(self):
|
||||
return self.points[1]
|
||||
|
||||
def get_last_handle(self):
|
||||
return self.points[-2]
|
||||
|
||||
# def get_end(self):
|
||||
# if hasattr(self, "tip"):
|
||||
# return self.tip[0].get_anchors()[0]
|
||||
# else:
|
||||
# return Line.get_end(self)
|
||||
def get_end(self):
|
||||
if self.has_tip():
|
||||
return self.tip.get_start()
|
||||
else:
|
||||
return VMobject.get_end(self)
|
||||
|
||||
# def get_start(self):
|
||||
# if hasattr(self, "tip"):
|
||||
# pass
|
||||
def get_start(self):
|
||||
if self.has_start_tip():
|
||||
return self.start_tip.get_start()
|
||||
else:
|
||||
return VMobject.get_start(self)
|
||||
|
||||
def has_tip(self):
|
||||
return hasattr(self, "tip") and self.tip in self
|
||||
|
||||
def has_start_tip(self):
|
||||
return hasattr(self, "start_tip") and self.start_tip in self
|
||||
|
||||
def pop_tips(self):
|
||||
start, end = self.get_start_and_end()
|
||||
result = VGroup()
|
||||
if self.has_tip():
|
||||
result.add(self.tip)
|
||||
self.remove(self.tip)
|
||||
if self.has_start_tip():
|
||||
result.add(self.start_tip)
|
||||
self.remove(self.start_tip)
|
||||
self.put_start_and_end_on(start, end)
|
||||
return result
|
||||
|
||||
|
||||
class Arc(TipableVMobject):
|
||||
|
@ -238,6 +302,12 @@ class Dot(Circle):
|
|||
Circle.__init__(self, arc_center=point, **kwargs)
|
||||
|
||||
|
||||
class SmallDot(Dot):
|
||||
CONFIG = {
|
||||
"radius": DEFAULT_SMALL_DOT_RADIUS,
|
||||
}
|
||||
|
||||
|
||||
class Ellipse(Circle):
|
||||
CONFIG = {
|
||||
"width": 2,
|
||||
|
@ -313,7 +383,7 @@ class Line(TipableVMobject):
|
|||
|
||||
def __init__(self, start, end, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
self.set_start_and_end(start, end)
|
||||
self.set_start_and_end_attrs(start, end)
|
||||
VMobject.__init__(self, **kwargs)
|
||||
|
||||
def generate_points(self):
|
||||
|
@ -348,7 +418,7 @@ class Line(TipableVMobject):
|
|||
)
|
||||
return self
|
||||
|
||||
def set_start_and_end(self, start, end):
|
||||
def set_start_and_end_attrs(self, start, end):
|
||||
# If either start or end are Mobjects, this
|
||||
# gives their centers
|
||||
rough_start = self.pointify(start)
|
||||
|
@ -472,105 +542,61 @@ class Arrow(Line):
|
|||
"stroke_width": 6,
|
||||
"buff": MED_SMALL_BUFF,
|
||||
"tip_width_to_length_ratio": 1,
|
||||
"max_tip_length_to_length_ratio": 0.35,
|
||||
"max_stem_width_to_tip_width_ratio": 0.3,
|
||||
"max_tip_length_to_length_ratio": 0.2,
|
||||
"max_stroke_width_to_length_ratio": 6,
|
||||
"preserve_tip_size_when_scaling": True,
|
||||
"rectangular_stem_width": 0.05,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
Line.__init__(self, *args, **kwargs)
|
||||
self.add_tip(tip_length=self.tip_length)
|
||||
# self.init_colors()
|
||||
|
||||
def init_tip(self):
|
||||
# TODO, should this be affected when
|
||||
# Arrow.set_stroke is called?
|
||||
self.initial_stroke_width = self.stroke_width
|
||||
self.add_tip()
|
||||
self.set_stroke_width_from_length()
|
||||
|
||||
# def add_tip(self, add_at_end=True):
|
||||
# tip = VMobject(
|
||||
# close_new_points=True,
|
||||
# mark_paths_closed=True,
|
||||
# fill_color=self.color,
|
||||
# fill_opacity=1,
|
||||
# stroke_color=self.color,
|
||||
# stroke_width=0,
|
||||
# )
|
||||
# tip.add_at_end = add_at_end
|
||||
# self.set_tip_points(tip, add_at_end, preserve_normal=False)
|
||||
# self.add(tip)
|
||||
# if not hasattr(self, 'tip'):
|
||||
# self.tip = VGroup()
|
||||
# self.tip.match_style(tip)
|
||||
# self.tip.add(tip)
|
||||
# return tip
|
||||
def scale(self, factor, **kwargs):
|
||||
has_tip = self.has_tip()
|
||||
has_start_tip = self.has_start_tip()
|
||||
if has_tip or has_start_tip:
|
||||
self.pop_tips()
|
||||
|
||||
def set_tip_points(
|
||||
self, tip,
|
||||
add_at_end=True,
|
||||
tip_length=None,
|
||||
preserve_normal=True,
|
||||
):
|
||||
if tip_length is None:
|
||||
tip_length = self.tip_length
|
||||
if preserve_normal:
|
||||
normal_vector = self.get_normal_vector()
|
||||
else:
|
||||
normal_vector = self.normal_vector
|
||||
line_length = get_norm(self.points[-1] - self.points[0])
|
||||
tip_length = min(
|
||||
tip_length, self.max_tip_length_to_length_ratio * line_length
|
||||
)
|
||||
|
||||
indices = (-2, -1) if add_at_end else (1, 0)
|
||||
pre_end_point, end_point = [
|
||||
self.get_anchors()[index]
|
||||
for index in indices
|
||||
]
|
||||
vect = end_point - pre_end_point
|
||||
perp_vect = np.cross(vect, normal_vector)
|
||||
for v in vect, perp_vect:
|
||||
if get_norm(v) == 0:
|
||||
v[0] = 1
|
||||
v *= tip_length / get_norm(v)
|
||||
ratio = self.tip_width_to_length_ratio
|
||||
tip.set_points_as_corners([
|
||||
end_point,
|
||||
end_point - vect + perp_vect * ratio / 2,
|
||||
end_point - vect - perp_vect * ratio / 2,
|
||||
])
|
||||
VMobject.scale(self, factor, **kwargs)
|
||||
self.set_stroke_width_from_length()
|
||||
|
||||
if has_tip:
|
||||
self.add_tip()
|
||||
if has_start_tip:
|
||||
self.add_tip(at_start=True)
|
||||
return self
|
||||
|
||||
def get_normal_vector(self):
|
||||
p0, p1, p2 = self.tip[0].get_anchors()[:3]
|
||||
result = np.cross(p2 - p1, p1 - p0)
|
||||
norm = get_norm(result)
|
||||
if norm == 0:
|
||||
return self.normal_vector
|
||||
else:
|
||||
return result / norm
|
||||
p0, p1, p2 = self.tip.get_start_anchors()[:3]
|
||||
return normalize(np.cross(p2 - p1, p1 - p0))
|
||||
|
||||
def reset_normal_vector(self):
|
||||
self.normal_vector = self.get_normal_vector()
|
||||
return self
|
||||
|
||||
def get_tip(self):
|
||||
return self.tip
|
||||
def get_default_tip_length(self):
|
||||
max_ratio = self.max_tip_length_to_length_ratio
|
||||
return min(
|
||||
self.tip_length,
|
||||
max_ratio * self.get_length(),
|
||||
)
|
||||
|
||||
# def put_start_and_end_on(self, *args, **kwargs):
|
||||
# Line.put_start_and_end_on(self, *args, **kwargs)
|
||||
# self.set_tip_points(self.tip[0], preserve_normal=False)
|
||||
# return self
|
||||
def set_stroke_width_from_length(self):
|
||||
max_ratio = self.max_stroke_width_to_length_ratio
|
||||
self.set_stroke(width=min(
|
||||
self.initial_stroke_width,
|
||||
max_ratio * self.get_length(),
|
||||
))
|
||||
return self
|
||||
|
||||
# def scale(self, scale_factor, **kwargs):
|
||||
# Line.scale(self, scale_factor, **kwargs)
|
||||
# if self.preserve_tip_size_when_scaling:
|
||||
# for t in self.tip:
|
||||
# self.set_tip_points(t, add_at_end=t.add_at_end)
|
||||
# return self
|
||||
|
||||
# def copy(self):
|
||||
# return self.deepcopy()
|
||||
# TODO, should this be the default for everything?
|
||||
def copy(self):
|
||||
return self.deepcopy()
|
||||
|
||||
|
||||
class Vector(Arrow):
|
||||
|
@ -670,6 +696,35 @@ class Triangle(RegularPolygon):
|
|||
RegularPolygon.__init__(self, n=3, **kwargs)
|
||||
|
||||
|
||||
class ArrowTip(Triangle):
|
||||
CONFIG = {
|
||||
"fill_opacity": 1,
|
||||
"stroke_width": 0,
|
||||
"length": DEFAULT_ARROW_TIP_LENGTH,
|
||||
"start_angle": PI,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
Triangle.__init__(self, **kwargs)
|
||||
self.set_width(self.length)
|
||||
self.set_height(self.length, stretch=True)
|
||||
|
||||
def get_base(self):
|
||||
return self.point_from_proportion(0.5)
|
||||
|
||||
def get_tip_point(self):
|
||||
return self.points[0]
|
||||
|
||||
def get_vector(self):
|
||||
return self.get_tip_point() - self.get_base()
|
||||
|
||||
def get_angle(self):
|
||||
return angle_of_vector(self.get_vector())
|
||||
|
||||
def get_length(self):
|
||||
return get_norm(self.get_vector())
|
||||
|
||||
|
||||
class Rectangle(Polygon):
|
||||
CONFIG = {
|
||||
"color": WHITE,
|
||||
|
|
Loading…
Add table
Reference in a new issue