2018-03-30 18:42:32 -07:00
|
|
|
import itertools as it
|
2018-12-24 12:37:51 -08:00
|
|
|
|
2018-03-30 18:42:32 -07:00
|
|
|
import numpy as np
|
|
|
|
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.constants import *
|
|
|
|
from manimlib.mobject.mobject import Mobject
|
|
|
|
from manimlib.mobject.types.vectorized_mobject import VGroup
|
|
|
|
from manimlib.mobject.types.vectorized_mobject import VMobject
|
|
|
|
from manimlib.utils.bezier import interpolate
|
|
|
|
from manimlib.utils.config_ops import digest_config
|
|
|
|
from manimlib.utils.config_ops import digest_locals
|
|
|
|
from manimlib.utils.paths import path_along_arc
|
|
|
|
from manimlib.utils.space_ops import angle_of_vector
|
|
|
|
from manimlib.utils.space_ops import center_of_mass
|
|
|
|
from manimlib.utils.space_ops import compass_directions
|
|
|
|
from manimlib.utils.space_ops import get_norm
|
|
|
|
from manimlib.utils.space_ops import rotate_vector
|
2015-10-27 21:00:50 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
class Arc(VMobject):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"radius": 1.0,
|
|
|
|
"start_angle": 0,
|
|
|
|
"num_anchors": 9,
|
|
|
|
"anchors_span_full_range": True,
|
2019-01-17 14:10:52 -08:00
|
|
|
"arc_center": ORIGIN,
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
def __init__(self, angle, **kwargs):
|
2018-01-19 17:30:39 -08:00
|
|
|
self.angle = angle
|
2016-04-17 00:31:38 -07:00
|
|
|
VMobject.__init__(self, **kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
|
|
|
def generate_points(self):
|
2018-01-17 15:12:39 -08:00
|
|
|
anchors = np.array([
|
2018-04-06 13:58:59 -07:00
|
|
|
np.cos(a) * RIGHT + np.sin(a) * UP
|
2016-07-12 15:16:20 -07:00
|
|
|
for a in np.linspace(
|
2018-04-06 13:58:59 -07:00
|
|
|
self.start_angle,
|
|
|
|
self.start_angle + self.angle,
|
2016-07-12 15:16:20 -07:00
|
|
|
self.num_anchors
|
2016-04-17 00:31:38 -07:00
|
|
|
)
|
2018-01-17 15:12:39 -08:00
|
|
|
])
|
2018-04-06 13:58:59 -07:00
|
|
|
# Figure out which control points will give the
|
|
|
|
# Appropriate tangent lines to the circle
|
|
|
|
d_theta = self.angle / (self.num_anchors - 1.0)
|
2018-01-17 15:12:39 -08:00
|
|
|
tangent_vectors = np.zeros(anchors.shape)
|
2019-02-05 11:02:15 -08:00
|
|
|
# Rotate all 90 degress, via (x, y) -> (-y, x)
|
2018-04-06 13:58:59 -07:00
|
|
|
tangent_vectors[:, 1] = anchors[:, 0]
|
|
|
|
tangent_vectors[:, 0] = -anchors[:, 1]
|
2019-02-05 11:02:15 -08:00
|
|
|
# Use tangent vectors to deduce anchors
|
2018-04-06 13:58:59 -07:00
|
|
|
handles1 = anchors[:-1] + (d_theta / 3) * tangent_vectors[:-1]
|
|
|
|
handles2 = anchors[1:] - (d_theta / 3) * tangent_vectors[1:]
|
2018-01-17 15:12:39 -08:00
|
|
|
self.set_anchors_and_handles(
|
2019-02-05 11:02:15 -08:00
|
|
|
anchors[:-1],
|
|
|
|
handles1, handles2,
|
|
|
|
anchors[1:],
|
2018-01-17 15:12:39 -08:00
|
|
|
)
|
2018-04-06 13:58:59 -07:00
|
|
|
self.scale(self.radius, about_point=ORIGIN)
|
2019-01-17 14:10:52 -08:00
|
|
|
self.shift(self.arc_center)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def add_tip(self, tip_length=0.25, at_start=False, at_end=True):
|
2018-01-23 10:53:24 -08:00
|
|
|
# clear out any old tips
|
|
|
|
for submob in self.submobjects:
|
2018-04-06 13:58:59 -07:00
|
|
|
if submob.mark_paths_closed:
|
2018-01-23 10:53:24 -08:00
|
|
|
self.remove(submob)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
# TODO, do this a better way
|
2018-01-22 16:30:07 -08:00
|
|
|
p1 = p2 = p3 = p4 = None
|
|
|
|
start_arrow = end_arrow = None
|
2018-02-21 17:11:53 -08:00
|
|
|
if at_end:
|
2018-01-23 10:53:24 -08:00
|
|
|
p1, p2 = self.points[-3:-1]
|
|
|
|
# self.points[-2:] did overshoot
|
2018-01-22 16:30:07 -08:00
|
|
|
start_arrow = Arrow(
|
2018-04-06 13:58:59 -07:00
|
|
|
p1, 2 * p2 - p1,
|
|
|
|
tip_length=tip_length,
|
|
|
|
max_tip_length_to_length_ratio=2.0
|
2018-01-22 16:30:07 -08:00
|
|
|
)
|
2018-04-06 13:58:59 -07:00
|
|
|
self.add(start_arrow.split()[-1]) # just the tip
|
2018-01-22 16:30:07 -08:00
|
|
|
|
2018-02-21 17:11:53 -08:00
|
|
|
if at_start:
|
2018-01-23 10:53:24 -08:00
|
|
|
p4, p3 = self.points[1:3]
|
|
|
|
# self.points[:2] did overshoot
|
2018-01-22 16:30:07 -08:00
|
|
|
end_arrow = Arrow(
|
2018-04-06 13:58:59 -07:00
|
|
|
p3, 2 * p4 - p3,
|
|
|
|
tip_length=tip_length,
|
|
|
|
max_tip_length_to_length_ratio=2.0
|
2018-01-22 16:30:07 -08:00
|
|
|
)
|
|
|
|
self.add(end_arrow.split()[-1])
|
|
|
|
|
2018-03-30 11:51:31 -07:00
|
|
|
self.set_color(self.get_color())
|
2016-08-03 16:51:16 -07:00
|
|
|
return self
|
|
|
|
|
2018-01-22 16:01:33 -08:00
|
|
|
def get_arc_center(self):
|
|
|
|
first_point = self.points[0]
|
2018-04-06 13:58:59 -07:00
|
|
|
radial_unit_vector = np.array(
|
|
|
|
[np.cos(self.start_angle), np.sin(self.start_angle), 0])
|
2018-01-22 16:01:33 -08:00
|
|
|
arc_center = first_point - self.radius * radial_unit_vector
|
|
|
|
return arc_center
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def move_arc_center_to(self, point):
|
2018-01-22 16:01:33 -08:00
|
|
|
v = point - self.get_arc_center()
|
|
|
|
self.shift(v)
|
|
|
|
return self
|
|
|
|
|
|
|
|
def stop_angle(self):
|
|
|
|
return self.start_angle + self.angle
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def set_bound_angles(self, start=0, stop=np.pi):
|
2018-01-23 10:53:24 -08:00
|
|
|
self.start_angle = start
|
|
|
|
self.angle = stop - start
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-01-23 10:53:24 -08:00
|
|
|
return self
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-02-23 18:36:54 +01:00
|
|
|
class ArcBetweenPoints(Arc):
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def __init__(self, start_point, end_point, angle=TAU / 4, **kwargs):
|
2018-02-23 18:36:54 +01:00
|
|
|
if angle == 0:
|
2018-02-23 19:27:59 +01:00
|
|
|
raise Exception("Arc with zero curve angle: use Line instead.")
|
2018-02-23 18:36:54 +01:00
|
|
|
|
|
|
|
midpoint = 0.5 * (start_point + end_point)
|
|
|
|
distance_vector = end_point - start_point
|
2018-04-06 13:58:59 -07:00
|
|
|
normal_vector = np.array([-distance_vector[1], distance_vector[0], 0])
|
2018-08-15 17:30:24 -07:00
|
|
|
distance = get_norm(normal_vector)
|
2018-02-23 18:36:54 +01:00
|
|
|
normal_vector /= distance
|
2018-02-23 19:27:59 +01:00
|
|
|
if angle < 0:
|
|
|
|
normal_vector *= -1
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
radius = distance / 2 / np.sin(0.5 * np.abs(angle))
|
|
|
|
length = distance / 2 / np.tan(0.5 * np.abs(angle))
|
|
|
|
arc_center = midpoint + length * normal_vector
|
2018-02-23 18:36:54 +01:00
|
|
|
w = start_point - arc_center
|
|
|
|
if w[0] != 0:
|
2018-04-06 13:58:59 -07:00
|
|
|
start_angle = np.arctan2(w[1], w[0])
|
2018-02-23 18:36:54 +01:00
|
|
|
else:
|
2018-04-06 13:58:59 -07:00
|
|
|
start_angle = np.pi / 2
|
2018-02-23 18:36:54 +01:00
|
|
|
|
|
|
|
Arc.__init__(self, angle,
|
2018-04-06 13:58:59 -07:00
|
|
|
radius=radius,
|
|
|
|
start_angle=start_angle,
|
|
|
|
**kwargs)
|
|
|
|
|
2018-02-23 18:36:54 +01:00
|
|
|
self.move_arc_center_to(arc_center)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-02-23 18:36:54 +01:00
|
|
|
class CurvedArrow(ArcBetweenPoints):
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def __init__(self, start_point, end_point, angle=TAU / 4, **kwargs):
|
2018-02-23 19:27:59 +01:00
|
|
|
# I know this is in reverse, but it works
|
|
|
|
if angle >= 0:
|
2018-04-06 13:58:59 -07:00
|
|
|
ArcBetweenPoints.__init__(
|
|
|
|
self, start_point, end_point, angle=angle, **kwargs)
|
|
|
|
self.add_tip(at_start=True, at_end=False)
|
2018-02-23 19:27:59 +01:00
|
|
|
else:
|
2018-04-06 13:58:59 -07:00
|
|
|
ArcBetweenPoints.__init__(
|
|
|
|
self, end_point, start_point, angle=-angle, **kwargs)
|
|
|
|
self.add_tip(at_start=False, at_end=True)
|
|
|
|
|
2018-02-23 18:36:54 +01:00
|
|
|
|
|
|
|
class CurvedDoubleArrow(ArcBetweenPoints):
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def __init__(self, start_point, end_point, angle=TAU / 4, **kwargs):
|
|
|
|
ArcBetweenPoints.__init__(
|
|
|
|
self, start_point, end_point, angle=angle, **kwargs)
|
|
|
|
self.add_tip(at_start=True, at_end=True)
|
|
|
|
|
2018-02-23 18:36:54 +01:00
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
class Circle(Arc):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"color": RED,
|
|
|
|
"close_new_points": True,
|
|
|
|
"anchors_span_full_range": False
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
def __init__(self, **kwargs):
|
2019-01-16 11:09:16 -08:00
|
|
|
Arc.__init__(self, TAU, **kwargs)
|
2015-09-28 16:25:18 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def surround(self, mobject, dim_to_match=0, stretch=False, buffer_factor=1.2):
|
2018-01-10 17:57:22 -08:00
|
|
|
# Ignores dim_to_match and stretch; result will always be a circle
|
|
|
|
# TODO: Perhaps create an ellipse class to handle singele-dimension stretching
|
2018-03-22 11:54:08 -07:00
|
|
|
|
|
|
|
# Something goes wrong here when surrounding lines?
|
|
|
|
# TODO: Figure out and fix
|
2018-01-10 17:57:22 -08:00
|
|
|
self.replace(mobject, dim_to_match, stretch)
|
2018-03-22 11:54:08 -07:00
|
|
|
|
2018-08-08 10:30:52 -07:00
|
|
|
self.set_width(
|
2018-04-06 13:58:59 -07:00
|
|
|
np.sqrt(mobject.get_width()**2 + mobject.get_height()**2))
|
2018-01-10 17:57:22 -08:00
|
|
|
self.scale(buffer_factor)
|
|
|
|
|
2019-01-17 14:10:52 -08:00
|
|
|
def get_point_from_angle(self, angle):
|
|
|
|
start_angle = angle_of_vector(
|
|
|
|
self.points[0] - self.get_center()
|
|
|
|
)
|
|
|
|
return self.point_from_proportion(
|
|
|
|
(angle - start_angle) / TAU
|
|
|
|
)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-07-20 13:37:12 -07:00
|
|
|
class Dot(Circle):
|
2016-04-17 00:31:38 -07:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"radius": 0.08,
|
|
|
|
"stroke_width": 0,
|
|
|
|
"fill_opacity": 1.0,
|
|
|
|
"color": WHITE
|
2016-04-17 00:31:38 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
|
|
|
def __init__(self, point=ORIGIN, **kwargs):
|
2016-04-17 19:29:27 -07:00
|
|
|
Circle.__init__(self, **kwargs)
|
|
|
|
self.shift(point)
|
|
|
|
self.init_colors()
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-01-27 19:09:59 +01:00
|
|
|
class Ellipse(VMobject):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"width": 2,
|
|
|
|
"height": 1
|
2018-01-27 19:09:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
def generate_points(self):
|
2018-04-06 13:58:59 -07:00
|
|
|
circle = Circle(radius=1)
|
2018-01-27 19:09:59 +01:00
|
|
|
circle = circle.stretch_to_fit_width(self.width)
|
|
|
|
circle = circle.stretch_to_fit_height(self.height)
|
|
|
|
self.points = circle.points
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-01-18 15:06:38 -08:00
|
|
|
class AnnularSector(VMobject):
|
2018-01-17 15:12:39 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"inner_radius": 1,
|
|
|
|
"outer_radius": 2,
|
|
|
|
"angle": TAU / 4,
|
|
|
|
"start_angle": 0,
|
|
|
|
"fill_opacity": 1,
|
|
|
|
"stroke_width": 0,
|
|
|
|
"color": WHITE,
|
|
|
|
"mark_paths_closed": True,
|
2018-01-17 15:12:39 -08:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-01-17 15:12:39 -08:00
|
|
|
def generate_points(self):
|
|
|
|
arc1 = Arc(
|
2018-04-06 13:58:59 -07:00
|
|
|
angle=self.angle,
|
|
|
|
start_angle=self.start_angle,
|
|
|
|
radius=self.inner_radius,
|
2018-01-17 15:12:39 -08:00
|
|
|
)
|
|
|
|
arc2 = Arc(
|
2018-04-06 13:58:59 -07:00
|
|
|
angle=-1 * self.angle,
|
|
|
|
start_angle=self.start_angle + self.angle,
|
|
|
|
radius=self.outer_radius,
|
2018-01-17 15:12:39 -08:00
|
|
|
)
|
|
|
|
a1_to_a2_points = np.array([
|
|
|
|
interpolate(arc1.points[-1], arc2.points[0], alpha)
|
|
|
|
for alpha in np.linspace(0, 1, 4)
|
|
|
|
])
|
|
|
|
a2_to_a1_points = np.array([
|
|
|
|
interpolate(arc2.points[-1], arc1.points[0], alpha)
|
|
|
|
for alpha in np.linspace(0, 1, 4)
|
|
|
|
])
|
|
|
|
self.points = np.array(arc1.points)
|
2019-02-05 11:02:15 -08:00
|
|
|
self.add_cubic_bezier_curve(*a1_to_a2_points[1:])
|
|
|
|
self.add_cubic_bezier_curve(*arc2.points[1:])
|
|
|
|
self.add_cubic_bezier_curve(*a2_to_a1_points[1:])
|
2018-01-17 15:12:39 -08:00
|
|
|
|
2018-01-19 17:30:39 -08:00
|
|
|
def get_arc_center(self):
|
|
|
|
first_point = self.points[0]
|
|
|
|
last_point = self.points[-2]
|
|
|
|
v = last_point - first_point
|
2018-08-15 17:30:24 -07:00
|
|
|
radial_unit_vector = v / get_norm(v)
|
2018-01-19 17:30:39 -08:00
|
|
|
arc_center = first_point - self.inner_radius * radial_unit_vector
|
|
|
|
return arc_center
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def move_arc_center_to(self, point):
|
2018-01-22 16:01:33 -08:00
|
|
|
v = point - self.get_arc_center()
|
|
|
|
self.shift(v)
|
|
|
|
return self
|
2018-01-19 17:30:39 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-01-18 15:06:38 -08:00
|
|
|
class Sector(AnnularSector):
|
2018-01-19 17:30:39 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"outer_radius": 1,
|
|
|
|
"inner_radius": 0
|
2018-01-19 17:30:39 -08:00
|
|
|
}
|
2018-01-18 15:06:38 -08:00
|
|
|
|
|
|
|
@property
|
|
|
|
def radius(self):
|
|
|
|
return self.outer_radius
|
|
|
|
|
|
|
|
@radius.setter
|
2018-04-06 13:58:59 -07:00
|
|
|
def radius(self, new_radius):
|
2018-01-18 15:06:38 -08:00
|
|
|
self.outer_radius = new_radius
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-01-18 15:06:38 -08:00
|
|
|
class Annulus(Circle):
|
|
|
|
CONFIG = {
|
|
|
|
"inner_radius": 1,
|
|
|
|
"outer_radius": 2,
|
2018-04-06 13:58:59 -07:00
|
|
|
"fill_opacity": 1,
|
|
|
|
"stroke_width": 0,
|
|
|
|
"color": WHITE,
|
|
|
|
"mark_paths_closed": False,
|
2018-01-18 15:06:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
def generate_points(self):
|
2018-01-25 23:23:43 +01:00
|
|
|
self.points = []
|
2018-01-18 15:06:38 -08:00
|
|
|
self.radius = self.outer_radius
|
2018-04-06 13:58:59 -07:00
|
|
|
outer_circle = Circle(radius=self.outer_radius)
|
2018-01-18 15:06:38 -08:00
|
|
|
inner_circle = Circle(radius=self.inner_radius)
|
|
|
|
inner_circle.flip()
|
2018-01-25 23:23:43 +01:00
|
|
|
self.points = outer_circle.points
|
2018-01-18 15:06:38 -08:00
|
|
|
self.add_subpath(inner_circle.points)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
class Line(VMobject):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"buff": 0,
|
|
|
|
"path_arc": None, # angle of arc specified here
|
|
|
|
"n_arc_anchors": 10, # Only used if path_arc is not None
|
2015-12-19 13:06:09 -08:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-09-28 16:25:18 -07:00
|
|
|
def __init__(self, start, end, **kwargs):
|
2015-12-19 13:06:09 -08:00
|
|
|
digest_config(self, kwargs)
|
2015-08-17 11:12:56 -07:00
|
|
|
self.set_start_and_end(start, end)
|
2016-04-17 00:31:38 -07:00
|
|
|
VMobject.__init__(self, **kwargs)
|
2015-06-10 22:00:35 -07:00
|
|
|
|
2017-04-26 16:21:38 -07:00
|
|
|
def generate_points(self):
|
|
|
|
if self.path_arc is None:
|
|
|
|
self.set_points_as_corners([self.start, self.end])
|
|
|
|
else:
|
|
|
|
path_func = path_along_arc(self.path_arc)
|
|
|
|
self.set_points_smoothly([
|
|
|
|
path_func(self.start, self.end, alpha)
|
|
|
|
for alpha in np.linspace(0, 1, self.n_arc_anchors)
|
|
|
|
])
|
2017-04-26 16:40:54 -07:00
|
|
|
self.account_for_buff()
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def set_path_arc(self, new_value):
|
2018-01-27 13:29:39 +01:00
|
|
|
self.path_arc = new_value
|
|
|
|
self.generate_points()
|
|
|
|
|
2017-04-26 16:40:54 -07:00
|
|
|
def account_for_buff(self):
|
2017-09-29 14:17:13 -07:00
|
|
|
length = self.get_arc_length()
|
2018-04-06 13:58:59 -07:00
|
|
|
if length < 2 * self.buff or self.buff == 0:
|
2017-04-26 16:40:54 -07:00
|
|
|
return
|
|
|
|
buff_proportion = self.buff / length
|
|
|
|
self.pointwise_become_partial(
|
|
|
|
self, buff_proportion, 1 - buff_proportion
|
|
|
|
)
|
2017-04-26 16:21:38 -07:00
|
|
|
|
2015-08-17 11:12:56 -07:00
|
|
|
def set_start_and_end(self, start, end):
|
2016-04-17 00:31:38 -07:00
|
|
|
start_to_end = self.pointify(end) - self.pointify(start)
|
2015-11-02 19:09:55 -08:00
|
|
|
vect = np.zeros(len(start_to_end))
|
2018-08-09 17:56:05 -07:00
|
|
|
longer_dim = np.argmax(list(map(abs, start_to_end)))
|
2015-11-02 19:09:55 -08:00
|
|
|
vect[longer_dim] = start_to_end[longer_dim]
|
2015-08-17 11:12:56 -07:00
|
|
|
self.start, self.end = [
|
2018-04-06 13:58:59 -07:00
|
|
|
arg.get_edge_center(unit * vect)
|
2015-08-17 11:12:56 -07:00
|
|
|
if isinstance(arg, Mobject)
|
|
|
|
else np.array(arg)
|
|
|
|
for arg, unit in zip([start, end], [1, -1])
|
|
|
|
]
|
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
def pointify(self, mob_or_point):
|
|
|
|
if isinstance(mob_or_point, Mobject):
|
|
|
|
return mob_or_point.get_center()
|
2016-04-20 19:24:54 -07:00
|
|
|
return np.array(mob_or_point)
|
2016-04-17 00:31:38 -07:00
|
|
|
|
2015-06-13 19:00:23 -07:00
|
|
|
def get_length(self):
|
2016-04-27 17:35:04 -07:00
|
|
|
start, end = self.get_start_and_end()
|
2018-08-15 17:30:24 -07:00
|
|
|
return get_norm(start - end)
|
2015-06-13 19:00:23 -07:00
|
|
|
|
2017-09-29 14:17:13 -07:00
|
|
|
def get_arc_length(self):
|
|
|
|
if self.path_arc:
|
|
|
|
anchors = self.get_anchors()
|
|
|
|
return sum([
|
2018-08-15 17:30:24 -07:00
|
|
|
get_norm(a2 - a1)
|
2017-09-29 14:17:13 -07:00
|
|
|
for a1, a2 in zip(anchors, anchors[1:])
|
|
|
|
])
|
|
|
|
else:
|
|
|
|
return self.get_length()
|
|
|
|
|
2016-03-21 19:30:09 -07:00
|
|
|
def get_start_and_end(self):
|
2016-04-27 17:35:04 -07:00
|
|
|
return self.get_start(), self.get_end()
|
|
|
|
|
2017-08-30 13:16:08 -07:00
|
|
|
def get_vector(self):
|
|
|
|
return self.get_end() - self.get_start()
|
|
|
|
|
2018-07-17 12:44:18 -07:00
|
|
|
def get_unit_vector(self):
|
|
|
|
vect = self.get_vector()
|
2018-08-15 17:30:24 -07:00
|
|
|
norm = get_norm(vect)
|
2018-07-17 12:44:18 -07:00
|
|
|
if norm == 0:
|
|
|
|
# TODO, is this the behavior I want?
|
|
|
|
return np.array(ORIGIN)
|
|
|
|
return vect / norm
|
|
|
|
|
2016-04-27 17:35:04 -07:00
|
|
|
def get_start(self):
|
2017-05-25 15:12:18 -07:00
|
|
|
return np.array(self.points[0])
|
2016-04-27 17:35:04 -07:00
|
|
|
|
|
|
|
def get_end(self):
|
2017-05-25 15:12:18 -07:00
|
|
|
return np.array(self.points[-1])
|
2016-03-21 19:30:09 -07:00
|
|
|
|
2015-06-13 19:00:23 -07:00
|
|
|
def get_slope(self):
|
2017-04-26 16:21:38 -07:00
|
|
|
start, end = self.get_start_and_end()
|
2015-06-13 19:00:23 -07:00
|
|
|
rise, run = [
|
2016-03-21 19:30:09 -07:00
|
|
|
float(end[i] - start[i])
|
2015-06-13 19:00:23 -07:00
|
|
|
for i in [1, 0]
|
|
|
|
]
|
2018-04-06 13:58:59 -07:00
|
|
|
return np.inf if run == 0 else rise / run
|
2016-03-21 19:30:09 -07:00
|
|
|
|
|
|
|
def get_angle(self):
|
|
|
|
start, end = self.get_start_and_end()
|
2018-04-06 13:58:59 -07:00
|
|
|
return angle_of_vector(end - start)
|
2015-06-13 19:00:23 -07:00
|
|
|
|
2019-02-03 12:10:55 -08:00
|
|
|
def set_angle(self, angle):
|
|
|
|
self.rotate(
|
|
|
|
angle - self.get_angle(),
|
|
|
|
about_point=self.get_start(),
|
|
|
|
)
|
2017-09-01 18:57:38 -07:00
|
|
|
|
2016-04-27 17:35:04 -07:00
|
|
|
def put_start_and_end_on(self, new_start, new_end):
|
2017-09-05 10:21:36 -07:00
|
|
|
self.start = new_start
|
|
|
|
self.end = new_end
|
2017-10-24 13:42:11 -07:00
|
|
|
self.buff = 0
|
2017-09-05 10:21:36 -07:00
|
|
|
self.generate_points()
|
|
|
|
return
|
|
|
|
|
2017-09-06 20:18:19 -07:00
|
|
|
def put_start_and_end_on_with_projection(self, new_start, new_end):
|
|
|
|
target_vect = np.array(new_end) - np.array(new_start)
|
|
|
|
curr_vect = self.get_vector()
|
2018-08-15 17:30:24 -07:00
|
|
|
curr_norm = get_norm(curr_vect)
|
2017-09-06 20:18:19 -07:00
|
|
|
if curr_norm == 0:
|
|
|
|
self.put_start_and_end_on(new_start, new_end)
|
|
|
|
return
|
2018-08-15 17:30:24 -07:00
|
|
|
target_norm = get_norm(target_vect)
|
2017-09-06 20:18:19 -07:00
|
|
|
if target_norm == 0:
|
|
|
|
epsilon = 0.001
|
2018-04-06 13:58:59 -07:00
|
|
|
self.scale(epsilon / curr_norm)
|
2017-09-06 20:18:19 -07:00
|
|
|
self.move_to(new_start)
|
|
|
|
return
|
|
|
|
unit_target = target_vect / target_norm
|
|
|
|
unit_curr = curr_vect / curr_norm
|
|
|
|
normal = np.cross(unit_target, unit_curr)
|
2018-08-15 17:30:24 -07:00
|
|
|
if get_norm(normal) == 0:
|
2017-09-06 20:18:19 -07:00
|
|
|
if unit_curr[0] == 0 and unit_curr[1] == 0:
|
|
|
|
normal = UP
|
|
|
|
else:
|
|
|
|
normal = OUT
|
|
|
|
angle_diff = np.arccos(
|
|
|
|
np.clip(np.dot(unit_target, unit_curr), -1, 1)
|
|
|
|
)
|
2018-04-06 13:58:59 -07:00
|
|
|
self.scale(target_norm / curr_norm)
|
2017-09-06 20:18:19 -07:00
|
|
|
self.rotate(-angle_diff, normal)
|
|
|
|
self.shift(new_start - self.get_start())
|
|
|
|
return self
|
2016-04-27 17:35:04 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-08-18 12:54:04 -07:00
|
|
|
class DashedLine(Line):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"dashed_segment_length": 0.05
|
2016-08-18 12:54:04 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-08-18 12:54:04 -07:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
self.init_kwargs = kwargs
|
|
|
|
Line.__init__(self, *args, **kwargs)
|
|
|
|
|
|
|
|
def generate_points(self):
|
2018-08-15 17:30:24 -07:00
|
|
|
length = get_norm(self.end - self.start)
|
2018-04-12 21:56:00 -07:00
|
|
|
if length == 0:
|
|
|
|
self.add(Line(self.start, self.end))
|
|
|
|
return self
|
2018-04-06 13:58:59 -07:00
|
|
|
num_interp_points = int(length / self.dashed_segment_length)
|
2018-04-16 14:17:10 -07:00
|
|
|
# Even number ensures that start and end points are hit
|
|
|
|
if num_interp_points % 2 == 1:
|
|
|
|
num_interp_points += 1
|
2016-08-18 12:54:04 -07:00
|
|
|
points = [
|
|
|
|
interpolate(self.start, self.end, alpha)
|
|
|
|
for alpha in np.linspace(0, 1, num_interp_points)
|
|
|
|
]
|
|
|
|
includes = it.cycle([True, False])
|
2017-09-06 20:18:19 -07:00
|
|
|
self.submobjects = [
|
|
|
|
Line(p1, p2, **self.init_kwargs)
|
|
|
|
for p1, p2, include in zip(points, points[1:], includes)
|
|
|
|
if include
|
|
|
|
]
|
|
|
|
self.put_start_and_end_on_with_projection(self.start, self.end)
|
2016-08-18 12:54:04 -07:00
|
|
|
return self
|
|
|
|
|
|
|
|
def get_start(self):
|
2018-01-13 15:51:04 -08:00
|
|
|
if len(self.points) > 0:
|
2017-02-27 15:56:22 -08:00
|
|
|
return self[0].points[0]
|
|
|
|
else:
|
|
|
|
return self.start
|
2016-08-18 12:54:04 -07:00
|
|
|
|
|
|
|
def get_end(self):
|
2017-02-27 15:56:22 -08:00
|
|
|
if len(self) > 0:
|
|
|
|
return self[-1].points[-1]
|
|
|
|
else:
|
|
|
|
return self.end
|
2016-08-18 12:54:04 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-11-29 17:29:31 -08:00
|
|
|
class Elbow(VMobject):
|
|
|
|
CONFIG = {
|
|
|
|
"width": 0.2,
|
|
|
|
"angle": 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
VMobject.__init__(self, **kwargs)
|
|
|
|
self.set_points_as_corners([UP, UP + RIGHT, RIGHT])
|
|
|
|
self.set_width(self.width, about_point=ORIGIN)
|
|
|
|
self.rotate(self.angle, about_point=ORIGIN)
|
|
|
|
|
|
|
|
|
2015-08-17 11:12:56 -07:00
|
|
|
class Arrow(Line):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"tip_length": 0.25,
|
|
|
|
"tip_width_to_length_ratio": 1,
|
|
|
|
"max_tip_length_to_length_ratio": 0.35,
|
|
|
|
"max_stem_width_to_tip_width_ratio": 0.3,
|
|
|
|
"buff": MED_SMALL_BUFF,
|
|
|
|
"preserve_tip_size_when_scaling": True,
|
|
|
|
"normal_vector": OUT,
|
|
|
|
"use_rectangular_stem": True,
|
|
|
|
"rectangular_stem_width": 0.05,
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-08-12 14:24:36 -07:00
|
|
|
def __init__(self, *args, **kwargs):
|
2018-08-09 17:56:05 -07:00
|
|
|
points = list(map(self.pointify, args))
|
2015-12-31 09:24:02 -08:00
|
|
|
if len(args) == 1:
|
2018-04-06 13:58:59 -07:00
|
|
|
args = (points[0] + UP + LEFT, points[0])
|
2015-08-12 14:24:36 -07:00
|
|
|
Line.__init__(self, *args, **kwargs)
|
2017-10-12 17:38:25 -07:00
|
|
|
self.init_tip()
|
2017-08-24 23:10:39 -07:00
|
|
|
if self.use_rectangular_stem and not hasattr(self, "rect"):
|
|
|
|
self.add_rectangular_stem()
|
2017-10-12 17:38:25 -07:00
|
|
|
self.init_colors()
|
|
|
|
|
|
|
|
def init_tip(self):
|
2018-03-31 16:32:34 -07:00
|
|
|
self.add_tip()
|
2015-08-12 14:24:36 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
def add_tip(self, add_at_end=True):
|
2017-05-05 11:19:10 -07:00
|
|
|
tip = VMobject(
|
2018-04-06 13:58:59 -07:00
|
|
|
close_new_points=True,
|
|
|
|
mark_paths_closed=True,
|
|
|
|
fill_color=self.color,
|
|
|
|
fill_opacity=1,
|
|
|
|
stroke_color=self.color,
|
|
|
|
stroke_width=0,
|
2017-05-05 11:19:10 -07:00
|
|
|
)
|
2018-03-31 18:05:02 -07:00
|
|
|
tip.add_at_end = add_at_end
|
2018-04-06 13:58:59 -07:00
|
|
|
self.set_tip_points(tip, add_at_end, preserve_normal=False)
|
2017-10-12 17:38:25 -07:00
|
|
|
self.add(tip)
|
2018-03-31 16:32:34 -07:00
|
|
|
if not hasattr(self, 'tip'):
|
2018-03-31 18:05:02 -07:00
|
|
|
self.tip = VGroup()
|
|
|
|
self.tip.match_style(tip)
|
|
|
|
self.tip.add(tip)
|
2017-10-12 17:38:25 -07:00
|
|
|
return tip
|
2017-05-05 11:19:10 -07:00
|
|
|
|
2017-08-24 23:10:39 -07:00
|
|
|
def add_rectangular_stem(self):
|
|
|
|
self.rect = Rectangle(
|
2018-04-06 13:58:59 -07:00
|
|
|
stroke_width=0,
|
|
|
|
fill_color=self.tip.get_fill_color(),
|
|
|
|
fill_opacity=self.tip.get_fill_opacity()
|
2017-08-24 23:10:39 -07:00
|
|
|
)
|
2017-08-24 23:17:26 -07:00
|
|
|
self.add_to_back(self.rect)
|
2018-04-06 13:58:59 -07:00
|
|
|
self.set_stroke(width=0)
|
2017-08-24 23:10:39 -07:00
|
|
|
self.set_rectangular_stem_points()
|
|
|
|
|
|
|
|
def set_rectangular_stem_points(self):
|
|
|
|
start, end = self.get_start_and_end()
|
2018-08-21 19:25:34 -07:00
|
|
|
tip_base_points = self.tip[0].get_anchors()[1:3]
|
2017-08-24 23:10:39 -07:00
|
|
|
tip_base = center_of_mass(tip_base_points)
|
|
|
|
tbp1, tbp2 = tip_base_points
|
|
|
|
perp_vect = tbp2 - tbp1
|
2018-08-15 17:30:24 -07:00
|
|
|
tip_base_width = get_norm(perp_vect)
|
2017-08-28 10:52:49 -07:00
|
|
|
if tip_base_width > 0:
|
|
|
|
perp_vect /= tip_base_width
|
2017-08-25 17:56:55 -07:00
|
|
|
width = min(
|
|
|
|
self.rectangular_stem_width,
|
2018-04-06 13:58:59 -07:00
|
|
|
self.max_stem_width_to_tip_width_ratio * tip_base_width,
|
2017-08-25 17:56:55 -07:00
|
|
|
)
|
2017-10-12 17:38:25 -07:00
|
|
|
if hasattr(self, "second_tip"):
|
|
|
|
start = center_of_mass(
|
|
|
|
self.second_tip.get_anchors()[1:]
|
|
|
|
)
|
2017-08-24 23:10:39 -07:00
|
|
|
self.rect.set_points_as_corners([
|
2018-04-06 13:58:59 -07:00
|
|
|
tip_base - perp_vect * width / 2,
|
2018-08-30 14:24:05 -07:00
|
|
|
start - perp_vect * width / 2,
|
|
|
|
start + perp_vect * width / 2,
|
|
|
|
tip_base + perp_vect * width / 2,
|
2017-08-24 23:10:39 -07:00
|
|
|
])
|
2018-05-15 13:54:04 -07:00
|
|
|
self.stem = self.rect # Alternate name
|
2017-08-24 23:10:39 -07:00
|
|
|
return self
|
|
|
|
|
2017-08-27 23:52:28 -07:00
|
|
|
def set_tip_points(
|
2018-03-31 16:32:34 -07:00
|
|
|
self, tip,
|
2018-04-06 13:58:59 -07:00
|
|
|
add_at_end=True,
|
|
|
|
tip_length=None,
|
|
|
|
preserve_normal=True,
|
|
|
|
):
|
2017-08-24 19:06:34 -07:00
|
|
|
if tip_length is None:
|
|
|
|
tip_length = self.tip_length
|
2017-08-27 23:52:28 -07:00
|
|
|
if preserve_normal:
|
|
|
|
normal_vector = self.get_normal_vector()
|
|
|
|
else:
|
|
|
|
normal_vector = self.normal_vector
|
2018-08-15 17:30:24 -07:00
|
|
|
line_length = get_norm(self.points[-1] - self.points[0])
|
2017-08-24 19:06:34 -07:00
|
|
|
tip_length = min(
|
2018-04-06 13:58:59 -07:00
|
|
|
tip_length, self.max_tip_length_to_length_ratio * line_length
|
2016-04-17 00:31:38 -07:00
|
|
|
)
|
2017-08-24 19:06:34 -07:00
|
|
|
|
|
|
|
indices = (-2, -1) if add_at_end else (1, 0)
|
|
|
|
pre_end_point, end_point = [
|
2017-10-27 12:59:38 -07:00
|
|
|
self.get_anchors()[index]
|
2017-08-24 19:06:34 -07:00
|
|
|
for index in indices
|
|
|
|
]
|
|
|
|
vect = end_point - pre_end_point
|
2017-08-27 23:52:28 -07:00
|
|
|
perp_vect = np.cross(vect, normal_vector)
|
2017-08-24 19:06:34 -07:00
|
|
|
for v in vect, perp_vect:
|
2018-08-15 17:30:24 -07:00
|
|
|
if get_norm(v) == 0:
|
2017-08-24 19:06:34 -07:00
|
|
|
v[0] = 1
|
2018-08-15 17:30:24 -07:00
|
|
|
v *= tip_length / get_norm(v)
|
2017-08-24 19:06:34 -07:00
|
|
|
ratio = self.tip_width_to_length_ratio
|
|
|
|
tip.set_points_as_corners([
|
2018-03-31 16:32:34 -07:00
|
|
|
end_point,
|
2018-04-06 13:58:59 -07:00
|
|
|
end_point - vect + perp_vect * ratio / 2,
|
|
|
|
end_point - vect - perp_vect * ratio / 2,
|
2017-08-24 19:06:34 -07:00
|
|
|
])
|
|
|
|
|
2017-05-05 11:19:10 -07:00
|
|
|
return self
|
2015-08-12 14:24:36 -07:00
|
|
|
|
2017-08-27 23:52:28 -07:00
|
|
|
def get_normal_vector(self):
|
2018-08-23 14:45:15 -07:00
|
|
|
p0, p1, p2 = self.tip[0].get_anchors()[:3]
|
2017-08-28 10:52:49 -07:00
|
|
|
result = np.cross(p2 - p1, p1 - p0)
|
2018-08-15 17:30:24 -07:00
|
|
|
norm = get_norm(result)
|
2017-08-27 23:52:28 -07:00
|
|
|
if norm == 0:
|
|
|
|
return self.normal_vector
|
|
|
|
else:
|
2018-04-06 13:58:59 -07:00
|
|
|
return result / norm
|
2017-08-27 23:52:28 -07:00
|
|
|
|
|
|
|
def reset_normal_vector(self):
|
|
|
|
self.normal_vector = self.get_normal_vector()
|
|
|
|
return self
|
|
|
|
|
2017-02-27 15:56:22 -08:00
|
|
|
def get_end(self):
|
|
|
|
if hasattr(self, "tip"):
|
2018-03-31 18:05:02 -07:00
|
|
|
return self.tip[0].get_anchors()[0]
|
2017-02-27 15:56:22 -08:00
|
|
|
else:
|
|
|
|
return Line.get_end(self)
|
|
|
|
|
2016-09-14 13:53:23 -07:00
|
|
|
def get_tip(self):
|
|
|
|
return self.tip
|
|
|
|
|
2017-08-24 19:06:34 -07:00
|
|
|
def put_start_and_end_on(self, *args, **kwargs):
|
|
|
|
Line.put_start_and_end_on(self, *args, **kwargs)
|
2018-04-06 13:58:59 -07:00
|
|
|
self.set_tip_points(self.tip[0], preserve_normal=False)
|
2017-08-24 23:10:39 -07:00
|
|
|
self.set_rectangular_stem_points()
|
2018-01-24 16:09:37 -08:00
|
|
|
return self
|
2017-08-24 19:06:34 -07:00
|
|
|
|
2017-03-14 15:50:16 -07:00
|
|
|
def scale(self, scale_factor, **kwargs):
|
|
|
|
Line.scale(self, scale_factor, **kwargs)
|
2017-08-24 19:06:34 -07:00
|
|
|
if self.preserve_tip_size_when_scaling:
|
2018-03-31 16:32:34 -07:00
|
|
|
for t in self.tip:
|
2018-03-31 18:05:02 -07:00
|
|
|
self.set_tip_points(t, add_at_end=t.add_at_end)
|
2017-10-27 15:12:11 -07:00
|
|
|
if self.use_rectangular_stem:
|
|
|
|
self.set_rectangular_stem_points()
|
2016-07-13 13:09:18 -07:00
|
|
|
return self
|
2016-04-27 17:35:04 -07:00
|
|
|
|
2018-01-24 16:09:37 -08:00
|
|
|
def copy(self):
|
|
|
|
return self.deepcopy()
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-03-17 23:54:28 -07:00
|
|
|
class Vector(Arrow):
|
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"color": YELLOW,
|
|
|
|
"buff": 0,
|
2016-03-17 23:54:28 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-04-27 17:35:04 -07:00
|
|
|
def __init__(self, direction, **kwargs):
|
2016-07-15 18:16:06 -07:00
|
|
|
if len(direction) == 2:
|
|
|
|
direction = np.append(np.array(direction), 0)
|
2016-04-27 17:35:04 -07:00
|
|
|
Arrow.__init__(self, ORIGIN, direction, **kwargs)
|
2016-03-17 23:54:28 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-04-23 23:36:05 -07:00
|
|
|
class DoubleArrow(Arrow):
|
2017-10-12 17:38:25 -07:00
|
|
|
def init_tip(self):
|
2018-04-01 10:51:54 -07:00
|
|
|
self.tip = VGroup()
|
|
|
|
for b in True, False:
|
2018-04-06 13:58:59 -07:00
|
|
|
t = self.add_tip(add_at_end=b)
|
2018-04-01 10:51:54 -07:00
|
|
|
t.add_at_end = b
|
|
|
|
self.tip.add(t)
|
|
|
|
self.tip.match_style(self.tip[0])
|
2016-07-12 10:34:35 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
class CubicBezier(VMobject):
|
|
|
|
def __init__(self, points, **kwargs):
|
|
|
|
VMobject.__init__(self, **kwargs)
|
|
|
|
self.set_points(points)
|
2015-10-20 21:55:46 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
class Polygon(VMobject):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"color": GREEN_D,
|
|
|
|
"mark_paths_closed": True,
|
|
|
|
"close_new_points": True,
|
2015-10-08 11:50:49 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-01-09 20:10:06 -08:00
|
|
|
def __init__(self, *vertices, **kwargs):
|
2016-04-17 00:31:38 -07:00
|
|
|
VMobject.__init__(self, **kwargs)
|
2019-02-05 15:24:51 -08:00
|
|
|
self.set_points_as_corners(
|
|
|
|
[*vertices, vertices[0]]
|
|
|
|
)
|
2015-10-09 19:53:38 -07:00
|
|
|
|
|
|
|
def get_vertices(self):
|
2019-02-05 15:24:51 -08:00
|
|
|
return self.get_start_anchors()
|
2015-12-19 13:06:09 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2017-01-16 11:43:59 -08:00
|
|
|
class RegularPolygon(Polygon):
|
2016-04-27 17:35:04 -07:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"start_angle": 0
|
2016-04-27 17:35:04 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
|
|
|
def __init__(self, n=3, **kwargs):
|
2016-04-27 17:35:04 -07:00
|
|
|
digest_config(self, kwargs, locals())
|
|
|
|
start_vect = rotate_vector(RIGHT, self.start_angle)
|
2017-01-16 11:43:59 -08:00
|
|
|
vertices = compass_directions(n, start_vect)
|
2016-04-27 17:35:04 -07:00
|
|
|
Polygon.__init__(self, *vertices, **kwargs)
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2019-02-05 15:24:51 -08:00
|
|
|
class Rectangle(Polygon):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"color": WHITE,
|
|
|
|
"height": 2.0,
|
|
|
|
"width": 4.0,
|
|
|
|
"mark_paths_closed": True,
|
|
|
|
"close_new_points": True,
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2019-02-05 15:24:51 -08:00
|
|
|
def __init__(self, **kwargs):
|
|
|
|
digest_config(self, kwargs)
|
|
|
|
x, y = self.width / 2., self.height / 2.
|
|
|
|
vertices = [
|
2018-04-06 13:58:59 -07:00
|
|
|
x * LEFT + y * UP,
|
|
|
|
x * RIGHT + y * UP,
|
|
|
|
x * RIGHT + y * DOWN,
|
|
|
|
x * LEFT + y * DOWN
|
2019-02-05 15:24:51 -08:00
|
|
|
]
|
|
|
|
Polygon.__init__(self, *vertices, **kwargs)
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-08-07 18:10:00 -07:00
|
|
|
|
|
|
|
class Square(Rectangle):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"side_length": 2.0,
|
2015-09-28 16:25:18 -07:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2015-09-28 16:25:18 -07:00
|
|
|
def __init__(self, **kwargs):
|
2016-03-19 17:19:02 -07:00
|
|
|
digest_config(self, kwargs)
|
2016-03-07 19:07:00 -08:00
|
|
|
Rectangle.__init__(
|
2018-04-06 13:58:59 -07:00
|
|
|
self,
|
|
|
|
height=self.side_length,
|
|
|
|
width=self.side_length,
|
2016-03-07 19:07:00 -08:00
|
|
|
**kwargs
|
|
|
|
)
|
2015-10-28 16:03:33 -07:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2018-04-12 23:19:09 +02:00
|
|
|
class RoundedRectangle(Rectangle):
|
|
|
|
CONFIG = {
|
2018-09-27 17:37:25 -07:00
|
|
|
"corner_radius": 0.5,
|
|
|
|
"close_new_points": True
|
2018-04-12 23:19:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
def generate_points(self):
|
|
|
|
y, x = self.height / 2., self.width / 2.
|
|
|
|
r = self.corner_radius
|
|
|
|
|
|
|
|
arc_ul = ArcBetweenPoints(x * LEFT + (y - r) * UP, (x - r) * LEFT + y * UP, angle = -TAU/4)
|
|
|
|
arc_ur = ArcBetweenPoints((x - r) * RIGHT + y * UP, x * RIGHT + (y - r) * UP, angle = -TAU/4)
|
|
|
|
arc_lr = ArcBetweenPoints(x * RIGHT + (y - r) * DOWN, (x - r) * RIGHT + y * DOWN, angle = -TAU/4)
|
|
|
|
arc_ll = ArcBetweenPoints(x * LEFT + (y - r) * DOWN, (x - r) * LEFT + y * DOWN, angle = TAU/4) # sic! bug in ArcBetweenPoints?
|
|
|
|
|
|
|
|
points = arc_ul.points
|
|
|
|
points = np.append(points,np.array([y * UP]), axis = 0)
|
|
|
|
points = np.append(points,np.array([y * UP]), axis = 0)
|
|
|
|
points = np.append(points,arc_ur.points, axis = 0)
|
|
|
|
points = np.append(points,np.array([x * RIGHT]), axis = 0)
|
|
|
|
points = np.append(points,np.array([x * RIGHT]), axis = 0)
|
|
|
|
points = np.append(points,arc_lr.points, axis = 0)
|
|
|
|
points = np.append(points,np.array([y * DOWN]), axis = 0)
|
|
|
|
points = np.append(points,np.array([y * DOWN]), axis = 0)
|
|
|
|
points = np.append(points,arc_ll.points[::-1], axis = 0) # sic! see comment above
|
|
|
|
points = np.append(points,np.array([x * LEFT]), axis = 0)
|
|
|
|
points = np.append(points,np.array([x * LEFT]), axis = 0)
|
|
|
|
points = np.append(points,np.array([x * LEFT + (y - r) * UP]), axis = 0)
|
|
|
|
|
|
|
|
points = points[::-1]
|
|
|
|
|
|
|
|
self.set_points(points)
|
|
|
|
|
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
class Grid(VMobject):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"height": 6.0,
|
|
|
|
"width": 6.0,
|
2016-01-26 10:23:05 -08:00
|
|
|
}
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-04-17 00:31:38 -07:00
|
|
|
def __init__(self, rows, columns, **kwargs):
|
|
|
|
digest_config(self, kwargs, locals())
|
|
|
|
VMobject.__init__(self, **kwargs)
|
|
|
|
|
2016-01-26 10:23:05 -08:00
|
|
|
def generate_points(self):
|
2016-04-17 00:31:38 -07:00
|
|
|
x_step = self.width / self.columns
|
|
|
|
y_step = self.height / self.rows
|
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
for x in np.arange(0, self.width + x_step, x_step):
|
2016-04-17 00:31:38 -07:00
|
|
|
self.add(Line(
|
2018-04-06 13:58:59 -07:00
|
|
|
[x - self.width / 2., -self.height / 2., 0],
|
|
|
|
[x - self.width / 2., self.height / 2., 0],
|
2016-04-17 00:31:38 -07:00
|
|
|
))
|
2018-04-06 13:58:59 -07:00
|
|
|
for y in np.arange(0, self.height + y_step, y_step):
|
2016-04-17 00:31:38 -07:00
|
|
|
self.add(Line(
|
2018-04-06 13:58:59 -07:00
|
|
|
[-self.width / 2., y - self.height / 2., 0],
|
|
|
|
[self.width / 2., y - self.height / 2., 0]
|
2016-04-17 00:31:38 -07:00
|
|
|
))
|