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.simple_functions import fdiv
|
||||||
from manimlib.utils.space_ops import angle_of_vector
|
from manimlib.utils.space_ops import angle_of_vector
|
||||||
from manimlib.utils.space_ops import angle_between_vectors
|
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 compass_directions
|
||||||
from manimlib.utils.space_ops import line_intersection
|
from manimlib.utils.space_ops import line_intersection
|
||||||
from manimlib.utils.space_ops import get_norm
|
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_DOT_RADIUS = 0.08
|
||||||
|
DEFAULT_SMALL_DOT_RADIUS = 0.04
|
||||||
DEFAULT_DASH_LENGTH = 0.05
|
DEFAULT_DASH_LENGTH = 0.05
|
||||||
|
DEFAULT_ARROW_TIP_LENGTH = 0.35
|
||||||
|
|
||||||
|
|
||||||
class TipableVMobject(VMobject):
|
class TipableVMobject(VMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"tip_length": 0.3,
|
"tip_length": DEFAULT_ARROW_TIP_LENGTH,
|
||||||
# TODO
|
# TODO
|
||||||
"normal_vector": OUT,
|
"normal_vector": OUT,
|
||||||
|
"tip_style": {
|
||||||
|
"fill_opacity": 1,
|
||||||
|
"stroke_width": 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
Meant simply for shard functionality between
|
Meant simply for shard functionality between
|
||||||
|
@ -36,61 +41,120 @@ class TipableVMobject(VMobject):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def add_tip(self, tip_length=None, at_start=False):
|
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)
|
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
|
# Last two control points, defining both
|
||||||
# the end, and the tangency direction
|
# the end, and the tangency direction
|
||||||
if at_start:
|
if at_start:
|
||||||
anchor = self.get_start()
|
anchor = self.get_start()
|
||||||
handle = self.get_first_handle()
|
handle = self.get_first_handle()
|
||||||
self.start_tip = tip
|
|
||||||
else:
|
else:
|
||||||
handle = self.get_last_handle()
|
handle = self.get_last_handle()
|
||||||
anchor = self.get_end()
|
anchor = self.get_end()
|
||||||
self.tip = tip
|
tip.rotate(
|
||||||
tip.rotate(angle_of_vector(handle - anchor))
|
angle_of_vector(handle - anchor) -
|
||||||
tip.shift(anchor - tip.get_start())
|
PI - tip.get_angle()
|
||||||
|
)
|
||||||
self.reset_endpoints_based_on_tip(tip, at_start)
|
tip.shift(anchor - tip.get_tip_point())
|
||||||
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)
|
|
||||||
return tip
|
return tip
|
||||||
|
|
||||||
def reset_endpoints_based_on_tip(self, tip, at_start):
|
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:
|
if at_start:
|
||||||
self.put_start_and_end_on(
|
self.put_start_and_end_on(
|
||||||
tip_base, self.get_end()
|
tip.get_base(), self.get_end()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.put_start_and_end_on(
|
self.put_start_and_end_on(
|
||||||
self.get_start(), tip_base,
|
self.get_start(), tip.get_base(),
|
||||||
)
|
)
|
||||||
return self
|
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):
|
def get_first_handle(self):
|
||||||
return self.points[1]
|
return self.points[1]
|
||||||
|
|
||||||
def get_last_handle(self):
|
def get_last_handle(self):
|
||||||
return self.points[-2]
|
return self.points[-2]
|
||||||
|
|
||||||
# def get_end(self):
|
def get_end(self):
|
||||||
# if hasattr(self, "tip"):
|
if self.has_tip():
|
||||||
# return self.tip[0].get_anchors()[0]
|
return self.tip.get_start()
|
||||||
# else:
|
else:
|
||||||
# return Line.get_end(self)
|
return VMobject.get_end(self)
|
||||||
|
|
||||||
# def get_start(self):
|
def get_start(self):
|
||||||
# if hasattr(self, "tip"):
|
if self.has_start_tip():
|
||||||
# pass
|
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):
|
class Arc(TipableVMobject):
|
||||||
|
@ -238,6 +302,12 @@ class Dot(Circle):
|
||||||
Circle.__init__(self, arc_center=point, **kwargs)
|
Circle.__init__(self, arc_center=point, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class SmallDot(Dot):
|
||||||
|
CONFIG = {
|
||||||
|
"radius": DEFAULT_SMALL_DOT_RADIUS,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Ellipse(Circle):
|
class Ellipse(Circle):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"width": 2,
|
"width": 2,
|
||||||
|
@ -313,7 +383,7 @@ class Line(TipableVMobject):
|
||||||
|
|
||||||
def __init__(self, start, end, **kwargs):
|
def __init__(self, start, end, **kwargs):
|
||||||
digest_config(self, kwargs)
|
digest_config(self, kwargs)
|
||||||
self.set_start_and_end(start, end)
|
self.set_start_and_end_attrs(start, end)
|
||||||
VMobject.__init__(self, **kwargs)
|
VMobject.__init__(self, **kwargs)
|
||||||
|
|
||||||
def generate_points(self):
|
def generate_points(self):
|
||||||
|
@ -348,7 +418,7 @@ class Line(TipableVMobject):
|
||||||
)
|
)
|
||||||
return self
|
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
|
# If either start or end are Mobjects, this
|
||||||
# gives their centers
|
# gives their centers
|
||||||
rough_start = self.pointify(start)
|
rough_start = self.pointify(start)
|
||||||
|
@ -472,105 +542,61 @@ class Arrow(Line):
|
||||||
"stroke_width": 6,
|
"stroke_width": 6,
|
||||||
"buff": MED_SMALL_BUFF,
|
"buff": MED_SMALL_BUFF,
|
||||||
"tip_width_to_length_ratio": 1,
|
"tip_width_to_length_ratio": 1,
|
||||||
"max_tip_length_to_length_ratio": 0.35,
|
"max_tip_length_to_length_ratio": 0.2,
|
||||||
"max_stem_width_to_tip_width_ratio": 0.3,
|
"max_stroke_width_to_length_ratio": 6,
|
||||||
"preserve_tip_size_when_scaling": True,
|
"preserve_tip_size_when_scaling": True,
|
||||||
"rectangular_stem_width": 0.05,
|
"rectangular_stem_width": 0.05,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
Line.__init__(self, *args, **kwargs)
|
Line.__init__(self, *args, **kwargs)
|
||||||
self.add_tip(tip_length=self.tip_length)
|
# TODO, should this be affected when
|
||||||
# self.init_colors()
|
# Arrow.set_stroke is called?
|
||||||
|
self.initial_stroke_width = self.stroke_width
|
||||||
def init_tip(self):
|
|
||||||
self.add_tip()
|
self.add_tip()
|
||||||
|
self.set_stroke_width_from_length()
|
||||||
|
|
||||||
# def add_tip(self, add_at_end=True):
|
def scale(self, factor, **kwargs):
|
||||||
# tip = VMobject(
|
has_tip = self.has_tip()
|
||||||
# close_new_points=True,
|
has_start_tip = self.has_start_tip()
|
||||||
# mark_paths_closed=True,
|
if has_tip or has_start_tip:
|
||||||
# fill_color=self.color,
|
self.pop_tips()
|
||||||
# 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 set_tip_points(
|
VMobject.scale(self, factor, **kwargs)
|
||||||
self, tip,
|
self.set_stroke_width_from_length()
|
||||||
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,
|
|
||||||
])
|
|
||||||
|
|
||||||
|
if has_tip:
|
||||||
|
self.add_tip()
|
||||||
|
if has_start_tip:
|
||||||
|
self.add_tip(at_start=True)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_normal_vector(self):
|
def get_normal_vector(self):
|
||||||
p0, p1, p2 = self.tip[0].get_anchors()[:3]
|
p0, p1, p2 = self.tip.get_start_anchors()[:3]
|
||||||
result = np.cross(p2 - p1, p1 - p0)
|
return normalize(np.cross(p2 - p1, p1 - p0))
|
||||||
norm = get_norm(result)
|
|
||||||
if norm == 0:
|
|
||||||
return self.normal_vector
|
|
||||||
else:
|
|
||||||
return result / norm
|
|
||||||
|
|
||||||
def reset_normal_vector(self):
|
def reset_normal_vector(self):
|
||||||
self.normal_vector = self.get_normal_vector()
|
self.normal_vector = self.get_normal_vector()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_tip(self):
|
def get_default_tip_length(self):
|
||||||
return self.tip
|
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):
|
def set_stroke_width_from_length(self):
|
||||||
# Line.put_start_and_end_on(self, *args, **kwargs)
|
max_ratio = self.max_stroke_width_to_length_ratio
|
||||||
# self.set_tip_points(self.tip[0], preserve_normal=False)
|
self.set_stroke(width=min(
|
||||||
# return self
|
self.initial_stroke_width,
|
||||||
|
max_ratio * self.get_length(),
|
||||||
|
))
|
||||||
|
return self
|
||||||
|
|
||||||
# def scale(self, scale_factor, **kwargs):
|
# TODO, should this be the default for everything?
|
||||||
# Line.scale(self, scale_factor, **kwargs)
|
def copy(self):
|
||||||
# if self.preserve_tip_size_when_scaling:
|
return self.deepcopy()
|
||||||
# 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()
|
|
||||||
|
|
||||||
|
|
||||||
class Vector(Arrow):
|
class Vector(Arrow):
|
||||||
|
@ -670,6 +696,35 @@ class Triangle(RegularPolygon):
|
||||||
RegularPolygon.__init__(self, n=3, **kwargs)
|
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):
|
class Rectangle(Polygon):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"color": WHITE,
|
"color": WHITE,
|
||||||
|
|
Loading…
Add table
Reference in a new issue