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:
Jean Whitmore 2019-03-03 07:42:00 -06:00
parent 50da0b6aae
commit 61dbad5edd

View file

@ -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,