3b1b-manim/manimlib/mobject/geometry.py

776 lines
23 KiB
Python
Raw Normal View History

import itertools as it
2019-02-07 09:26:18 -08:00
import warnings
import numpy as np
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.space_ops import angle_of_vector
from manimlib.utils.space_ops import center_of_mass
from manimlib.utils.space_ops import compass_directions
2019-02-07 09:26:18 -08:00
from manimlib.utils.space_ops import line_intersection
from manimlib.utils.space_ops import get_norm
2019-02-07 10:59:04 -08:00
from manimlib.utils.space_ops import normalize
from manimlib.utils.space_ops import rotate_vector
2019-02-07 10:12:42 -08:00
DEFAULT_DOT_RADIUS = 0.08
2016-04-17 00:31:38 -07:00
class Arc(VMobject):
2016-02-27 16:32:53 -08:00
CONFIG = {
"radius": 1.0,
2019-02-07 09:26:18 -08:00
"num_components": 9,
"anchors_span_full_range": True,
"arc_center": ORIGIN,
}
2019-02-07 09:26:18 -08:00
def __init__(self, start_angle=0, angle=TAU / 4, **kwargs):
self.start_angle = start_angle
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):
2019-02-07 09:26:18 -08:00
self.set_pre_positioned_points()
self.scale(self.radius, about_point=ORIGIN)
self.shift(self.arc_center)
def set_pre_positioned_points(self):
2018-01-17 15:12:39 -08:00
anchors = np.array([
np.cos(a) * RIGHT + np.sin(a) * UP
for a in np.linspace(
self.start_angle,
self.start_angle + self.angle,
2019-02-07 09:26:18 -08:00
self.num_components,
2016-04-17 00:31:38 -07:00
)
2018-01-17 15:12:39 -08:00
])
# Figure out which control points will give the
# Appropriate tangent lines to the circle
2019-02-07 09:26:18 -08:00
d_theta = self.angle / (self.num_components - 1.0)
2018-01-17 15:12:39 -08:00
tangent_vectors = np.zeros(anchors.shape)
# Rotate all 90 degress, via (x, y) -> (-y, x)
tangent_vectors[:, 1] = anchors[:, 0]
tangent_vectors[:, 0] = -anchors[:, 1]
# Use tangent vectors to deduce anchors
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(
anchors[:-1],
handles1, handles2,
anchors[1:],
2018-01-17 15:12:39 -08:00
)
2018-01-22 16:30:07 -08:00
2019-02-07 09:26:18 -08:00
def add_tip(self, tip_length=0.25, at_start=False):
tip = self.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.move_to(ORIGIN, LEFT)
# Last two control points, defining both
# the end, and the tangency direction
if at_start:
2019-02-07 09:26:18 -08:00
end, handle = self.points[:2]
else:
handle, end = self.points[-2:]
tip.rotate(
angle_of_vector(handle - end),
about_point=ORIGIN
)
tip.shift(end)
self.add(tip)
return self
def get_arc_center(self):
2019-02-07 09:26:18 -08:00
"""
Looks at the normals to the first two
anchors, and finds their intersection points
"""
# First two anchors and handles
a1, h1, h2, a2 = self.points[:4]
# Tangent vectors
t1 = h1 - a1
t2 = h2 - a2
# Normals
n1 = rotate_vector(t1, TAU / 4)
n2 = rotate_vector(t2, TAU / 4)
try:
return line_intersection(
line1=(a1, a1 + n1),
line2=(a2, a2 + n2),
)
except Exception:
warnings.warn("Can't find Arc center, using ORIGIN instead")
return np.array(ORIGIN)
def move_arc_center_to(self, point):
2019-02-07 09:26:18 -08:00
self.shift(point - self.get_arc_center())
return self
def stop_angle(self):
2019-02-07 09:26:18 -08:00
return angle_of_vector(
self.points[-1] - self.get_arc_center()
) % TAU
2018-01-23 10:53:24 -08:00
class ArcBetweenPoints(Arc):
def __init__(self, start_point, end_point, angle=TAU / 4, **kwargs):
2019-02-07 09:26:18 -08:00
Arc.__init__(
self,
angle=angle,
2019-02-07 10:59:04 -08:00
**kwargs,
2019-02-07 09:26:18 -08:00
)
2019-02-07 10:59:04 -08:00
if angle == 0:
self.set_points_as_corners([LEFT, RIGHT])
self.put_start_and_end_on(start_point, end_point)
class CurvedArrow(ArcBetweenPoints):
2019-02-07 09:26:18 -08:00
def __init__(self, start_point, end_point, **kwargs):
ArcBetweenPoints.__init__(self, start_point, end_point, **kwargs)
self.add_tip()
2019-02-07 09:26:18 -08:00
class CurvedDoubleArrow(CurvedArrow):
def __init__(self, start_point, end_point, **kwargs):
CurvedArrow.__init__(
self, start_point, end_point, **kwargs
)
self.add_tip(at_start=True)
2016-04-17 00:31:38 -07:00
class Circle(Arc):
2016-02-27 16:32:53 -08:00
CONFIG = {
"color": RED,
"close_new_points": True,
"anchors_span_full_range": False
}
2016-04-17 00:31:38 -07:00
def __init__(self, **kwargs):
2019-02-07 09:26:18 -08:00
Arc.__init__(self, 0, TAU, **kwargs)
def surround(self, mobject, dim_to_match=0, stretch=False, buffer_factor=1.2):
# 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
self.replace(mobject, dim_to_match, stretch)
2018-03-22 11:54:08 -07:00
self.set_width(
2019-02-07 09:26:18 -08:00
np.sqrt(mobject.get_width()**2 + mobject.get_height()**2)
)
self.scale(buffer_factor)
2019-02-07 09:26:18 -08:00
def point_at_angle(self, angle):
start_angle = angle_of_vector(
self.points[0] - self.get_center()
)
return self.point_from_proportion(
(angle - start_angle) / TAU
)
class Dot(Circle):
2016-04-17 00:31:38 -07:00
CONFIG = {
2019-02-07 10:12:42 -08:00
"radius": DEFAULT_DOT_RADIUS,
"stroke_width": 0,
"fill_opacity": 1.0,
"color": WHITE
2016-04-17 00:31:38 -07:00
}
def __init__(self, point=ORIGIN, **kwargs):
2019-02-07 10:12:42 -08:00
Circle.__init__(self, arc_center=point, **kwargs)
2015-06-10 22:00:35 -07:00
2019-02-07 10:12:42 -08:00
class Ellipse(Circle):
2018-01-27 19:09:59 +01:00
CONFIG = {
"width": 2,
"height": 1
2018-01-27 19:09:59 +01:00
}
2019-02-07 10:12:42 -08:00
def __init__(self, **kwargs):
Circle.__init__(self, **kwargs)
self.set_width(width, stretch=True)
self.set_height(width, stretch=True)
2018-01-27 19:09:59 +01:00
2019-02-07 10:12:42 -08:00
class AnnularSector(Arc):
2018-01-17 15:12:39 -08:00
CONFIG = {
"inner_radius": 1,
"outer_radius": 2,
"angle": TAU / 4,
"start_angle": 0,
"fill_opacity": 1,
"stroke_width": 0,
"color": WHITE,
2018-01-17 15:12:39 -08:00
}
2018-01-17 15:12:39 -08:00
def generate_points(self):
2019-02-07 10:12:42 -08:00
inner_arc, outer_arc = [
Arc(
start_angle=self.start_angle,
angle=self.angle,
radius=radius,
arc_center=self.arc_center,
)
for radius in (self.inner_radius, self.outer_radius)
]
outer_arc.reverse_points()
self.append_points(inner_arc.points)
self.add_line_to(outer_arc.points[0])
self.append_points(outer_arc.points)
self.add_line_to(inner_arc.points[0])
2018-01-19 17:30:39 -08:00
2018-01-18 15:06:38 -08:00
class Sector(AnnularSector):
2018-01-19 17:30:39 -08:00
CONFIG = {
"outer_radius": 1,
"inner_radius": 0
2018-01-19 17:30:39 -08:00
}
2018-01-18 15:06:38 -08:00
2018-01-18 15:06:38 -08:00
class Annulus(Circle):
CONFIG = {
"inner_radius": 1,
"outer_radius": 2,
"fill_opacity": 1,
"stroke_width": 0,
"color": WHITE,
"mark_paths_closed": False,
2018-01-18 15:06:38 -08:00
}
def generate_points(self):
self.radius = self.outer_radius
outer_circle = Circle(radius=self.outer_radius)
2018-01-18 15:06:38 -08:00
inner_circle = Circle(radius=self.inner_radius)
2019-02-07 10:12:42 -08:00
inner_circle.reverse_points()
self.append_points(outer_circle.points)
self.append_points(inner_circle.points)
self.shift(self.arc_center)
2018-01-18 15:06:38 -08:00
2016-04-17 00:31:38 -07:00
class Line(VMobject):
2016-02-27 16:32:53 -08:00
CONFIG = {
"buff": 0,
"path_arc": None, # angle of arc specified here
}
def __init__(self, start, end, **kwargs):
digest_config(self, kwargs)
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):
2019-02-07 10:59:04 -08:00
if self.path_arc:
arc = ArcBetweenPoints(
self.start, self.end,
angle=self.path_arc
)
self.set_points(arc.points)
2017-04-26 16:21:38 -07:00
else:
2019-02-07 10:59:04 -08:00
self.set_points_as_corners([self.start, self.end])
self.account_for_buff()
def set_path_arc(self, new_value):
self.path_arc = new_value
self.generate_points()
def account_for_buff(self):
2019-02-07 10:59:04 -08:00
if self.buff == 0:
return
2017-09-29 14:17:13 -07:00
length = self.get_arc_length()
2019-02-07 10:59:04 -08:00
if length < 2 * self.buff:
return
buff_proportion = self.buff / length
self.pointwise_become_partial(
self, buff_proportion, 1 - buff_proportion
)
2017-04-26 16:21:38 -07:00
def set_start_and_end(self, start, end):
2019-02-07 10:59:04 -08:00
# If either start or end are Mobjects, this
# gives their centers
rough_start = self.pointify(start)
rough_end = self.pointify(end)
vect = normalize(rough_end - rough_start)
# Now that we know the direction between them,
# we can the appropriate boundary point from
# start and end, if they're mobjects
self.start = self.pointify(start, vect)
self.end = self.pointify(end, -vect)
def pointify(self, mob_or_point, direction=None):
2016-04-17 00:31:38 -07:00
if isinstance(mob_or_point, Mobject):
2019-02-07 10:59:04 -08:00
mob = mob_or_point
if direction is None:
return mob.get_center()
else:
return mob.get_boundary_point(direction)
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):
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:
2019-02-07 10:59:04 -08:00
points = np.array([
self.point_from_proportion(a)
for a in np.linspace(0, 1, 100)
2017-09-29 14:17:13 -07:00
])
2019-02-07 10:59:04 -08:00
diffs = points[1:] - points[:-1]
norms = np.apply_along_axis(get_norm, 1, diffs)
return np.sum(norms)
2017-09-29 14:17:13 -07:00
else:
return self.get_length()
2016-03-21 19:30:09 -07:00
def get_start_and_end(self):
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
def get_start(self):
return np.array(self.points[0])
def get_end(self):
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]
]
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()
return angle_of_vector(end - start)
2015-06-13 19:00:23 -07:00
def set_angle(self, angle):
self.rotate(
angle - self.get_angle(),
about_point=self.get_start(),
)
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
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
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)
)
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-08-18 12:54:04 -07:00
class DashedLine(Line):
CONFIG = {
"dashed_segment_length": 0.05
2016-08-18 12:54:04 -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
num_interp_points = int(length / self.dashed_segment_length)
# 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-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)
class Arrow(Line):
2016-02-27 16:32:53 -08:00
CONFIG = {
"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,
}
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:
args = (points[0] + UP + LEFT, points[0])
Line.__init__(self, *args, **kwargs)
self.init_tip()
if self.use_rectangular_stem and not hasattr(self, "rect"):
self.add_rectangular_stem()
self.init_colors()
def init_tip(self):
2018-03-31 16:32:34 -07:00
self.add_tip()
def add_tip(self, add_at_end=True):
2017-05-05 11:19:10 -07:00
tip = VMobject(
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
)
tip.add_at_end = add_at_end
self.set_tip_points(tip, add_at_end, preserve_normal=False)
self.add(tip)
2018-03-31 16:32:34 -07:00
if not hasattr(self, 'tip'):
self.tip = VGroup()
self.tip.match_style(tip)
self.tip.add(tip)
return tip
2017-05-05 11:19:10 -07:00
def add_rectangular_stem(self):
self.rect = Rectangle(
stroke_width=0,
fill_color=self.tip.get_fill_color(),
fill_opacity=self.tip.get_fill_opacity()
)
self.add_to_back(self.rect)
self.set_stroke(width=0)
self.set_rectangular_stem_points()
def set_rectangular_stem_points(self):
start, end = self.get_start_and_end()
tip_base_points = self.tip[0].get_anchors()[1:3]
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
width = min(
self.rectangular_stem_width,
self.max_stem_width_to_tip_width_ratio * tip_base_width,
)
if hasattr(self, "second_tip"):
start = center_of_mass(
self.second_tip.get_anchors()[1:]
)
self.rect.set_points_as_corners([
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,
])
self.stem = self.rect # Alternate name
return self
def set_tip_points(
2018-03-31 16:32:34 -07:00
self, tip,
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
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(
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
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,
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
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)
if norm == 0:
return self.normal_vector
else:
return result / norm
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"):
return self.tip[0].get_anchors()[0]
2017-02-27 15:56:22 -08:00
else:
return Line.get_end(self)
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)
self.set_tip_points(self.tip[0], preserve_normal=False)
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:
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()
return self
2018-01-24 16:09:37 -08:00
def copy(self):
return self.deepcopy()
2016-03-17 23:54:28 -07:00
class Vector(Arrow):
CONFIG = {
"color": YELLOW,
"buff": 0,
2016-03-17 23:54:28 -07:00
}
def __init__(self, direction, **kwargs):
if len(direction) == 2:
direction = np.append(np.array(direction), 0)
Arrow.__init__(self, ORIGIN, direction, **kwargs)
2016-03-17 23:54:28 -07:00
2016-04-23 23:36:05 -07:00
class DoubleArrow(Arrow):
def init_tip(self):
2018-04-01 10:51:54 -07:00
self.tip = VGroup()
for b in True, False:
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
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
2016-04-17 00:31:38 -07:00
class Polygon(VMobject):
2016-02-27 16:32:53 -08:00
CONFIG = {
"color": GREEN_D,
"mark_paths_closed": True,
"close_new_points": True,
2015-10-08 11:50:49 -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]]
)
def get_vertices(self):
2019-02-05 15:24:51 -08:00
return self.get_start_anchors()
2017-01-16 11:43:59 -08:00
class RegularPolygon(Polygon):
CONFIG = {
"start_angle": 0
}
2019-02-07 09:26:18 -08:00
def __init__(self, n=6, **kwargs):
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)
Polygon.__init__(self, *vertices, **kwargs)
2019-02-07 09:26:18 -08:00
class Triangle(RegularPolygon):
def __init__(self, **kwargs):
RegularPolygon.__init__(self, n=3, **kwargs)
2019-02-05 15:24:51 -08:00
class Rectangle(Polygon):
2016-02-27 16:32:53 -08:00
CONFIG = {
"color": WHITE,
"height": 2.0,
"width": 4.0,
"mark_paths_closed": True,
"close_new_points": True,
}
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 = [
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)
class Square(Rectangle):
2016-02-27 16:32:53 -08:00
CONFIG = {
"side_length": 2.0,
}
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__(
self,
height=self.side_length,
width=self.side_length,
2016-03-07 19:07:00 -08:00
**kwargs
)
class RoundedRectangle(Rectangle):
CONFIG = {
2018-09-27 17:37:25 -07:00
"corner_radius": 0.5,
"close_new_points": True
}
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 = {
"height": 6.0,
"width": 6.0,
2016-01-26 10:23:05 -08: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
for x in np.arange(0, self.width + x_step, x_step):
2016-04-17 00:31:38 -07:00
self.add(Line(
[x - self.width / 2., -self.height / 2., 0],
[x - self.width / 2., self.height / 2., 0],
2016-04-17 00:31:38 -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(
[-self.width / 2., y - self.height / 2., 0],
[self.width / 2., y - self.height / 2., 0]
2016-04-17 00:31:38 -07:00
))