mirror of
https://github.com/3b1b/manim.git
synced 2025-08-31 16:08:54 +00:00
Changing the way VMobject handles its internal bezier curves so as to eliminate the need for sup_paths
This commit is contained in:
parent
eb0c63606d
commit
eaf25ff34b
29 changed files with 432 additions and 278 deletions
|
@ -206,11 +206,7 @@ class Camera(object):
|
|||
else:
|
||||
method = Mobject.get_family
|
||||
return remove_list_redundancies(list(
|
||||
it.chain(*[
|
||||
method(m)
|
||||
for m in mobjects
|
||||
if not (isinstance(m, VMobject) and m.is_subpath)
|
||||
])
|
||||
it.chain(*[method(m) for m in mobjects])
|
||||
))
|
||||
|
||||
def get_mobjects_to_display(
|
||||
|
@ -324,32 +320,61 @@ class Camera(object):
|
|||
self.display_vectorized(vmobject, ctx)
|
||||
|
||||
def display_vectorized(self, vmobject, ctx):
|
||||
if vmobject.is_subpath:
|
||||
# Subpath vectorized mobjects are taken care
|
||||
# of by their parent
|
||||
return
|
||||
self.set_cairo_context_path(ctx, vmobject)
|
||||
self.apply_stroke(ctx, vmobject, background=True)
|
||||
self.apply_fill(ctx, vmobject)
|
||||
self.apply_stroke(ctx, vmobject)
|
||||
return self
|
||||
|
||||
# def old_set_cairo_context_path(self, ctx, vmobject):
|
||||
# ctx.new_path()
|
||||
# for vmob in it.chain([vmobject], vmobject.get_subpath_mobjects()):
|
||||
# points = self.transform_points_pre_display(
|
||||
# vmob, vmob.points
|
||||
# )
|
||||
# if np.any(np.isnan(points)) or np.any(points == np.inf):
|
||||
# # TODO, print some kind of warning about
|
||||
# # mobject having invalid points?
|
||||
# points = np.zeros((1, 3))
|
||||
# ctx.new_sub_path()
|
||||
# ctx.move_to(*points[0][:2])
|
||||
# for p0, p1, p2 in zip(points[1::3], points[2::3], points[3::3]):
|
||||
# ctx.curve_to(*p0[:2], *p1[:2], *p2[:2])
|
||||
# if vmob.is_closed():
|
||||
# ctx.close_path()
|
||||
# return self
|
||||
|
||||
def set_cairo_context_path(self, ctx, vmobject):
|
||||
# self.old_set_cairo_context_path(ctx, vmobject)
|
||||
# return
|
||||
|
||||
points = vmobject.get_points()
|
||||
if len(points) == 0:
|
||||
return
|
||||
elif np.any(np.isnan(points)) or np.any(points == np.inf):
|
||||
# TODO, print some kind of warning about
|
||||
# mobject having invalid points?
|
||||
points = np.zeros((1, 3))
|
||||
|
||||
def should_start_new_path(last_p3, p0):
|
||||
if last_p3 is None:
|
||||
return True
|
||||
else:
|
||||
return not vmobject.consider_points_equals(
|
||||
last_p3, p0
|
||||
)
|
||||
|
||||
last_p3 = None
|
||||
quads = vmobject.get_all_cubic_bezier_point_tuples()
|
||||
ctx.new_path()
|
||||
for vmob in it.chain([vmobject], vmobject.get_subpath_mobjects()):
|
||||
points = self.transform_points_pre_display(
|
||||
vmob, vmob.points
|
||||
)
|
||||
if np.any(np.isnan(points)) or np.any(points == np.inf):
|
||||
points = np.zeros((1, 3))
|
||||
ctx.new_sub_path()
|
||||
ctx.move_to(*points[0][:2])
|
||||
for triplet in zip(points[1::3], points[2::3], points[3::3]):
|
||||
ctx.curve_to(*it.chain(*[
|
||||
point[:2] for point in triplet
|
||||
]))
|
||||
if vmob.is_closed():
|
||||
ctx.close_path()
|
||||
for p0, p1, p2, p3 in quads:
|
||||
if should_start_new_path(last_p3, p0):
|
||||
ctx.new_sub_path()
|
||||
ctx.move_to(*p0[:2])
|
||||
ctx.curve_to(*p1[:2], *p2[:2], *p3[:2])
|
||||
last_p3 = p3
|
||||
if vmobject.is_closed():
|
||||
ctx.close_path()
|
||||
return self
|
||||
|
||||
def set_cairo_context_color(self, ctx, rgbas, vmobject):
|
||||
|
|
|
@ -12,7 +12,7 @@ from manimlib.utils.config_ops import digest_config
|
|||
class MappingCamera(Camera):
|
||||
CONFIG = {
|
||||
"mapping_func": lambda p: p,
|
||||
"min_anchor_points": 50,
|
||||
"min_num_curves": 50,
|
||||
"allow_object_intrusion": False
|
||||
}
|
||||
|
||||
|
@ -27,8 +27,8 @@ class MappingCamera(Camera):
|
|||
mobject_copies = [mobject.copy() for mobject in mobjects]
|
||||
for mobject in mobject_copies:
|
||||
if isinstance(mobject, VMobject) and \
|
||||
0 < mobject.get_num_anchor_points() < self.min_anchor_points:
|
||||
mobject.insert_n_anchor_points(self.min_anchor_points)
|
||||
0 < mobject.get_num_curves() < self.min_num_curves:
|
||||
mobject.insert_n_curves(self.min_num_curves)
|
||||
Camera.capture_mobjects(
|
||||
self, mobject_copies,
|
||||
include_submobjects=False,
|
||||
|
|
|
@ -324,12 +324,12 @@ class NumberPlane(VMobject):
|
|||
arrow = Arrow(ORIGIN, point, **kwargs)
|
||||
return arrow
|
||||
|
||||
def prepare_for_nonlinear_transform(self, num_inserted_anchor_points=50):
|
||||
def prepare_for_nonlinear_transform(self, num_inserted_curves=50):
|
||||
for mob in self.family_members_with_points():
|
||||
num_anchors = mob.get_num_anchor_points()
|
||||
if num_inserted_anchor_points > num_anchors:
|
||||
mob.insert_n_anchor_points(
|
||||
num_inserted_anchor_points - num_anchors)
|
||||
num_curves = mob.get_num_curves()
|
||||
if num_inserted_curves > num_curves:
|
||||
mob.insert_n_curves(
|
||||
num_inserted_curves - num_curves)
|
||||
mob.make_smooth()
|
||||
return self
|
||||
|
||||
|
|
|
@ -43,12 +43,16 @@ class Arc(VMobject):
|
|||
# Appropriate tangent lines to the circle
|
||||
d_theta = self.angle / (self.num_anchors - 1.0)
|
||||
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:]
|
||||
self.set_anchors_and_handles(
|
||||
anchors, handles1, handles2
|
||||
anchors[:-1],
|
||||
handles1, handles2,
|
||||
anchors[1:],
|
||||
)
|
||||
self.scale(self.radius, about_point=ORIGIN)
|
||||
self.shift(self.arc_center)
|
||||
|
@ -250,9 +254,9 @@ class AnnularSector(VMobject):
|
|||
for alpha in np.linspace(0, 1, 4)
|
||||
])
|
||||
self.points = np.array(arc1.points)
|
||||
self.add_control_points(a1_to_a2_points[1:])
|
||||
self.add_control_points(arc2.points[1:])
|
||||
self.add_control_points(a2_to_a1_points[1:])
|
||||
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:])
|
||||
|
||||
def get_arc_center(self):
|
||||
first_point = self.points[0]
|
||||
|
|
|
@ -331,7 +331,7 @@ class VMobjectFromSVGPathstring(VMobject):
|
|||
re.split(pattern, self.path_string)[1:]
|
||||
))
|
||||
# Which mobject should new points be added to
|
||||
self.growing_path = self
|
||||
self = self
|
||||
for command, coord_string in pairs:
|
||||
self.handle_command(command, coord_string)
|
||||
# people treat y-coordinate differently
|
||||
|
@ -342,28 +342,23 @@ class VMobjectFromSVGPathstring(VMobject):
|
|||
command = command.upper()
|
||||
# new_points are the points that will be added to the curr_points
|
||||
# list. This variable may get modified in the conditionals below.
|
||||
points = self.growing_path.points
|
||||
points = self.points
|
||||
new_points = self.string_to_points(coord_string)
|
||||
|
||||
if command == "M": # moveto
|
||||
if isLower and len(points) > 0:
|
||||
new_points[0] += points[-1]
|
||||
if len(points) > 0:
|
||||
self.growing_path = self.add_subpath(new_points[:1])
|
||||
else:
|
||||
self.growing_path.start_at(new_points[0])
|
||||
|
||||
if len(new_points) <= 1:
|
||||
return
|
||||
|
||||
points = self.growing_path.points
|
||||
new_points = new_points[1:]
|
||||
command = "L"
|
||||
|
||||
if isLower and len(points) > 0:
|
||||
new_points += points[-1]
|
||||
|
||||
if command in ["L", "H", "V"]: # lineto
|
||||
if command == "M": # moveto
|
||||
self.start_new_path(new_points[0])
|
||||
|
||||
if len(new_points) <= 1:
|
||||
return
|
||||
|
||||
# Huh? When does this come up?
|
||||
points = self.points
|
||||
new_points = new_points[1:]
|
||||
command = "L"
|
||||
elif command in ["L", "H", "V"]: # lineto
|
||||
if command == "H":
|
||||
new_points[0, 1] = points[-1, 1]
|
||||
elif command == "V":
|
||||
|
@ -372,21 +367,27 @@ class VMobjectFromSVGPathstring(VMobject):
|
|||
new_points[0, 0] += points[-1, 1]
|
||||
new_points[0, 1] = new_points[0, 0]
|
||||
new_points[0, 0] = points[-1, 0]
|
||||
new_points = new_points.repeat(3, axis=0)
|
||||
elif command == "C": # curveto
|
||||
self.add_line_to(new_points[0])
|
||||
return
|
||||
|
||||
if command == "C": # curveto
|
||||
pass # Yay! No action required
|
||||
elif command in ["S", "T"]: # smooth curveto
|
||||
handle1 = points[-1] + (points[-1] - points[-2])
|
||||
new_points = np.append([handle1], new_points, axis=0)
|
||||
if command in ["Q", "T"]: # quadratic Bezier curve
|
||||
self.add_smooth_curve_to(*new_points)
|
||||
# handle1 = points[-1] + (points[-1] - points[-2])
|
||||
# new_points = np.append([handle1], new_points, axis=0)
|
||||
return
|
||||
elif command == "Q": # quadratic Bezier curve
|
||||
# TODO, this is a suboptimal approximation
|
||||
new_points = np.append([new_points[0]], new_points, axis=0)
|
||||
elif command == "A": # elliptical Arc
|
||||
raise Exception("Not implemented")
|
||||
elif command == "Z": # closepath
|
||||
if not is_closed(points):
|
||||
# Both handles and new anchor are the start
|
||||
new_points = points[[0, 0, 0]]
|
||||
if is_closed(points):
|
||||
return
|
||||
# Both handles and new anchor are the start
|
||||
# TODO, is this needed?
|
||||
new_points = points[[0, 0, 0]]
|
||||
# self.mark_paths_closed = True
|
||||
|
||||
# Handle situations where there's multiple relative control points
|
||||
|
@ -395,7 +396,7 @@ class VMobjectFromSVGPathstring(VMobject):
|
|||
new_points[i:i + 3] -= points[-1]
|
||||
new_points[i:i + 3] += new_points[i - 1]
|
||||
|
||||
self.growing_path.add_control_points(new_points)
|
||||
self.add_cubic_bezier_curve_to(*new_points)
|
||||
|
||||
def string_to_points(self, coord_string):
|
||||
numbers = string_to_numbers(coord_string)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import itertools as it
|
||||
import sys
|
||||
|
||||
from colour import Color
|
||||
|
||||
|
@ -8,7 +9,7 @@ from manimlib.mobject.three_d_utils import get_3d_vmob_gradient_start_and_end_po
|
|||
from manimlib.utils.bezier import bezier
|
||||
from manimlib.utils.bezier import get_smooth_handle_points
|
||||
from manimlib.utils.bezier import interpolate
|
||||
from manimlib.utils.bezier import is_closed
|
||||
from manimlib.utils.bezier import integer_interpolate
|
||||
from manimlib.utils.bezier import partial_bezier_points
|
||||
from manimlib.utils.color import color_to_rgba
|
||||
from manimlib.utils.iterables import make_even
|
||||
|
@ -17,6 +18,15 @@ from manimlib.utils.iterables import tuplify
|
|||
from manimlib.utils.simple_functions import clip_in_place
|
||||
|
||||
|
||||
# TODO
|
||||
# - Change cubic curve groups to have 4 points instead of 3
|
||||
# - Change sub_path idea accordingly
|
||||
# - No more mark_paths_closed, instead have the camera test
|
||||
# if last point in close to first point
|
||||
# - Think about length of self.points. Always 0 or 1 mod 4?
|
||||
# That's kind of weird.
|
||||
|
||||
|
||||
class VMobject(Mobject):
|
||||
CONFIG = {
|
||||
"fill_color": None,
|
||||
|
@ -38,14 +48,16 @@ class VMobject(Mobject):
|
|||
"sheen_direction": UL,
|
||||
# Indicates that it will not be displayed, but
|
||||
# that it should count in parent mobject's path
|
||||
"is_subpath": False,
|
||||
"close_new_points": False,
|
||||
"mark_paths_closed": False,
|
||||
"propagate_style_to_family": False,
|
||||
"pre_function_handle_to_anchor_scale_factor": 0.01,
|
||||
"make_smooth_after_applying_functions": False,
|
||||
"background_image_file": None,
|
||||
"shade_in_3d": False,
|
||||
# This is within a pixel
|
||||
# TODO, what if you're rather zoomed in?
|
||||
"tolerance_for_point_equality": 1e-6,
|
||||
"n_points_per_cubic_curve": 4,
|
||||
}
|
||||
|
||||
def get_group_class(self):
|
||||
|
@ -364,63 +376,136 @@ class VMobject(Mobject):
|
|||
submob.z_index_group = self
|
||||
return self
|
||||
|
||||
# Drawing
|
||||
def start_at(self, point):
|
||||
if len(self.points) == 0:
|
||||
self.points = np.zeros((1, 3))
|
||||
self.points[0] = point
|
||||
# Points
|
||||
def set_points(self, points):
|
||||
self.points = np.array(points)
|
||||
return self
|
||||
|
||||
def add_control_points(self, control_points):
|
||||
assert(len(control_points) % 3 == 0)
|
||||
self.points = np.append(
|
||||
self.points,
|
||||
control_points,
|
||||
axis=0
|
||||
)
|
||||
return self
|
||||
def get_points(self):
|
||||
return np.array(self.points)
|
||||
|
||||
def is_closed(self):
|
||||
return is_closed(self.points)
|
||||
|
||||
def set_anchors_and_handles(self, anchors, handles1, handles2):
|
||||
assert(len(anchors) == len(handles1) + 1)
|
||||
assert(len(anchors) == len(handles2) + 1)
|
||||
total_len = 3 * (len(anchors) - 1) + 1
|
||||
def set_anchors_and_handles(self, anchors1, handles1, handles2, anchors2):
|
||||
assert(len(anchors1) == len(handles1) == len(handles2) == len(anchors2))
|
||||
nppcc = self.n_points_per_cubic_curve # 4
|
||||
total_len = nppcc * len(anchors1)
|
||||
self.points = np.zeros((total_len, self.dim))
|
||||
self.points[0] = anchors[0]
|
||||
arrays = [handles1, handles2, anchors[1:]]
|
||||
arrays = [anchors1, handles1, handles2, anchors2]
|
||||
for index, array in enumerate(arrays):
|
||||
self.points[index + 1::3] = array
|
||||
return self.points
|
||||
self.points[index::nppcc] = array
|
||||
return self
|
||||
|
||||
def set_points_as_corners(self, points):
|
||||
if len(points) <= 1:
|
||||
return self
|
||||
points = self.prepare_new_anchor_points(points)
|
||||
self.set_anchors_and_handles(points, *[
|
||||
interpolate(points[:-1], points[1:], alpha)
|
||||
for alpha in (1. / 3, 2. / 3)
|
||||
def clear_points(self):
|
||||
self.points = np.zeros((0, self.dim))
|
||||
|
||||
def append_points(self, new_points):
|
||||
# TODO, check that number new points is a multiple of 4?
|
||||
# or else that if len(self.points) % 4 == 1, then
|
||||
# len(new_points) % 4 == 3?
|
||||
self.points = np.append(self.points, new_points, axis=0)
|
||||
return self
|
||||
|
||||
def start_new_path(self, point):
|
||||
# TODO, make sure that len(self.points) % 4 == 0?
|
||||
self.append_points([point])
|
||||
return self
|
||||
|
||||
def add_cubic_bezier_curve(self, anchor1, handle1, handle2, anchor2):
|
||||
# TODO, check the len(self.points) % 4 == 0?
|
||||
self.append_points([anchor1, handle1, handle2, anchor2])
|
||||
|
||||
def add_cubic_bezier_curve_to(self, handle1, handle2, anchor):
|
||||
"""
|
||||
Add cubic bezier curve to the path.
|
||||
"""
|
||||
self.throw_error_if_no_points()
|
||||
new_points = [handle1, handle2, anchor]
|
||||
if self.has_new_path_started():
|
||||
self.append_points(new_points)
|
||||
else:
|
||||
self.append_points([self.get_last_point()] + new_points)
|
||||
|
||||
def add_line_to(self, point):
|
||||
nppcc = self.n_points_per_cubic_curve
|
||||
self.add_cubic_bezier_curve_to(*[
|
||||
interpolate(self.get_last_point(), point, a)
|
||||
for a in np.linspace(0, 1, nppcc)[1:]
|
||||
])
|
||||
return self
|
||||
|
||||
def set_points_smoothly(self, points):
|
||||
if len(points) <= 1:
|
||||
return self
|
||||
points = self.prepare_new_anchor_points(points)
|
||||
h1, h2 = get_smooth_handle_points(points)
|
||||
self.set_anchors_and_handles(points, h1, h2)
|
||||
def add_smooth_curve_to(self, *points):
|
||||
"""
|
||||
If two points are passed in, the first is intepretted
|
||||
as a handle, the second as an anchor
|
||||
"""
|
||||
if len(points) == 1:
|
||||
handle2 = None
|
||||
new_anchor = points[0]
|
||||
elif len(points) == 2:
|
||||
handle2, new_anchor = points
|
||||
else:
|
||||
name = sys._getframe(0).f_code.co_name
|
||||
raise Exception("Only call {} with 1 or 2 points".format(name))
|
||||
|
||||
if self.has_new_path_started():
|
||||
self.add_line_to(new_anchor)
|
||||
else:
|
||||
self.throw_error_if_no_points()
|
||||
last_h2, last_a2 = self.points[-2:]
|
||||
last_tangent = (last_a2 - last_h2)
|
||||
handle1 = last_a2 + last_tangent
|
||||
if handle2 is None:
|
||||
to_anchor_vect = new_anchor - last_a2
|
||||
new_tangent = rotate_vector(
|
||||
last_tangent, PI, axis=to_anchor_vect
|
||||
)
|
||||
handle2 = new_anchor - new_tangent
|
||||
self.append_points([
|
||||
last_a2, handle1, handle2, new_anchor
|
||||
])
|
||||
return self
|
||||
|
||||
def prepare_new_anchor_points(self, points):
|
||||
if not isinstance(points, np.ndarray):
|
||||
points = np.array(points)
|
||||
if self.close_new_points and not is_closed(points):
|
||||
points = np.append(points, [points[0]], axis=0)
|
||||
# TODO, remove
|
||||
# def add_control_points(self, control_points):
|
||||
# assert(len(control_points) % 3 == 0)
|
||||
# self.points = np.append(
|
||||
# self.points,
|
||||
# control_points,
|
||||
# axis=0
|
||||
# )
|
||||
# return self
|
||||
|
||||
def has_new_path_started(self):
|
||||
nppcc = self.n_points_per_cubic_curve # 4
|
||||
return len(self.points) % nppcc == 1
|
||||
|
||||
def get_last_point(self):
|
||||
return self.points[-1]
|
||||
|
||||
def is_closed(self):
|
||||
return self.consider_points_equals(
|
||||
self.points[0], self.points[-1]
|
||||
)
|
||||
|
||||
def has_no_points(self):
|
||||
return len(self.points) == 0
|
||||
|
||||
def add_points_as_corners(self, points):
|
||||
for point in points:
|
||||
self.add_line_to(point)
|
||||
return points
|
||||
|
||||
def set_points(self, points):
|
||||
self.points = np.array(points)
|
||||
def set_points_as_corners(self, points):
|
||||
self.clear_points()
|
||||
self.start_new_path(points[0])
|
||||
self.add_points_as_corners(points[1:])
|
||||
return self
|
||||
|
||||
def set_points_smoothly(self, points):
|
||||
assert(len(points) > 1)
|
||||
h1, h2 = get_smooth_handle_points(points)
|
||||
self.set_anchors_and_handles(
|
||||
points[:-1], h1, h2, points[1:]
|
||||
)
|
||||
return self
|
||||
|
||||
def set_anchor_points(self, points, mode="smooth"):
|
||||
|
@ -432,6 +517,7 @@ class VMobject(Mobject):
|
|||
raise Exception("Unknown mode")
|
||||
return self
|
||||
|
||||
# TODO, this will not work!
|
||||
def change_anchor_mode(self, mode):
|
||||
for submob in self.family_members_with_points():
|
||||
anchors = submob.get_anchors()
|
||||
|
@ -445,35 +531,18 @@ class VMobject(Mobject):
|
|||
return self.change_anchor_mode("corners")
|
||||
|
||||
def add_subpath(self, points):
|
||||
"""
|
||||
A VMobject is meant to represent
|
||||
a single "path", in the svg sense of the word.
|
||||
However, one such path may really consist of separate
|
||||
continuous components if there is a move_to command.
|
||||
These other portions of the path will be treated as submobjects,
|
||||
but will be tracked in a separate special list for when
|
||||
it comes time to display.
|
||||
"""
|
||||
subpath_mobject = self.copy() # Really helps to be of the same class
|
||||
subpath_mobject.submobjects = []
|
||||
subpath_mobject.is_subpath = True
|
||||
subpath_mobject.set_points(points)
|
||||
self.add(subpath_mobject)
|
||||
return subpath_mobject
|
||||
assert(len(points) % 4 == 0)
|
||||
self.points = np.append(self.points, points, axis=0)
|
||||
return self
|
||||
|
||||
def append_vectorized_mobject(self, vectorized_mobject):
|
||||
new_points = list(vectorized_mobject.points)
|
||||
if len(new_points) == 0:
|
||||
return
|
||||
if self.get_num_points() == 0:
|
||||
self.start_at(new_points[0])
|
||||
self.add_control_points(new_points[1:])
|
||||
else:
|
||||
self.add_control_points(2 * [new_points[0]] + new_points)
|
||||
return self
|
||||
|
||||
def get_subpath_mobjects(self):
|
||||
return [m for m in self.submobjects if hasattr(m, 'is_subpath') and m.is_subpath]
|
||||
if self.has_new_path_started():
|
||||
# Remove last point, which is starting
|
||||
# a new path
|
||||
self.points = self.points[:-1]
|
||||
self.append_points(new_points)
|
||||
|
||||
def apply_function(self, function):
|
||||
factor = self.pre_function_handle_to_anchor_scale_factor
|
||||
|
@ -495,95 +564,134 @@ class VMobject(Mobject):
|
|||
again.
|
||||
"""
|
||||
for submob in self.family_members_with_points():
|
||||
anchors, handles1, handles2 = submob.get_anchors_and_handles()
|
||||
# print len(anchors), len(handles1), len(handles2)
|
||||
a_to_h1 = handles1 - anchors[:-1]
|
||||
a_to_h2 = handles2 - anchors[1:]
|
||||
handles1 = anchors[:-1] + factor * a_to_h1
|
||||
handles2 = anchors[1:] + factor * a_to_h2
|
||||
submob.set_anchors_and_handles(anchors, handles1, handles2)
|
||||
a1, h1, h2, a2 = submob.get_anchors_and_handles()
|
||||
a1_to_h1 = h1 - a1
|
||||
a2_to_h2 = h2 - a2
|
||||
new_h1 = a1 + factor * a1_to_h1
|
||||
new_h2 = a2 + factor * a2_to_h2
|
||||
submob.set_anchors_and_handles(a1, new_h1, new_h2, a2)
|
||||
return self
|
||||
|
||||
#
|
||||
def consider_points_equals(self, p0, p1):
|
||||
return np.allclose(
|
||||
p0, p1,
|
||||
atol=self.tolerance_for_point_equality
|
||||
)
|
||||
|
||||
# Information about line
|
||||
|
||||
def component_curves(self):
|
||||
for n in range(self.get_num_anchor_points() - 1):
|
||||
yield self.get_nth_curve(n)
|
||||
def get_all_cubic_bezier_point_tuples(self):
|
||||
points = self.get_points()
|
||||
if self.has_new_path_started():
|
||||
points = points[:-1]
|
||||
# TODO, Throw error if len(points) is > 1 and
|
||||
# not divisible by 4 (i.e. n_points_per_cubic_curve)
|
||||
nppcc = self.n_points_per_cubic_curve
|
||||
return np.array([
|
||||
points[i:i + nppcc]
|
||||
for i in range(0, len(points), nppcc)
|
||||
])
|
||||
|
||||
def get_nth_curve(self, n):
|
||||
return bezier(self.points[3 * n:3 * n + 4])
|
||||
def get_nth_curve_points(self, n):
|
||||
assert(n < self.get_num_curves())
|
||||
nppcc = self.n_points_per_cubic_curve
|
||||
return self.points[nppcc * n:nppcc * (n + 1)]
|
||||
|
||||
def get_num_anchor_points(self):
|
||||
return (len(self.points) - 1) // 3 + 1
|
||||
def get_nth_curve_function(self, n):
|
||||
return bezier(self.get_nth_curve_points(n))
|
||||
|
||||
def get_num_curves(self):
|
||||
nppcc = self.n_points_per_cubic_curve
|
||||
return len(self.points) // nppcc
|
||||
|
||||
def point_from_proportion(self, alpha):
|
||||
num_cubics = self.get_num_anchor_points() - 1
|
||||
interpoint_alpha = num_cubics * (alpha % (1. / num_cubics))
|
||||
index = min(3 * int(alpha * num_cubics), 3 * num_cubics)
|
||||
cubic = bezier(self.points[index:index + 4])
|
||||
return cubic(interpoint_alpha)
|
||||
num_cubics = self.get_num_curves()
|
||||
n, residue = integer_interpolate(0, num_cubics, alpha)
|
||||
curve = self.get_nth_curve_function(n)
|
||||
return curve(residue)
|
||||
|
||||
def get_anchors_and_handles(self):
|
||||
"""
|
||||
returns anchors1, handles1, handles2, anchors2,
|
||||
where (anchors1[i], handles1[i], handles2[i], anchors2[i])
|
||||
will be four points defining a cubic bezier curve
|
||||
for any i in range(0, len(anchors1))
|
||||
"""
|
||||
nppcc = self.n_points_per_cubic_curve
|
||||
return [
|
||||
self.points[i::3]
|
||||
for i in range(3)
|
||||
self.points[i::nppcc]
|
||||
for i in range(nppcc)
|
||||
]
|
||||
|
||||
def get_start_anchors(self):
|
||||
return self.points[0::self.n_points_per_cubic_curve]
|
||||
|
||||
def get_end_anchors(self):
|
||||
nppcc = self.n_points_per_cubic_curve
|
||||
return self.points[nppcc - 1::nppcc]
|
||||
|
||||
def get_anchors(self):
|
||||
return self.points[::3]
|
||||
return np.array(list(it.chain(*zip(
|
||||
self.get_start_anchors(),
|
||||
self.get_end_anchors(),
|
||||
))))
|
||||
|
||||
def get_points_defining_boundary(self):
|
||||
return np.array(list(it.chain(*[
|
||||
sm.get_anchors() for sm in self.get_family()
|
||||
sm.get_anchors()
|
||||
for sm in self.get_family()
|
||||
])))
|
||||
|
||||
# Alignment
|
||||
def align_points(self, vmobject):
|
||||
# This will call back to align_points_with_larger
|
||||
Mobject.align_points(self, vmobject)
|
||||
self.align_rgbas(vmobject)
|
||||
is_subpath = self.is_subpath or vmobject.is_subpath
|
||||
self.is_subpath = vmobject.is_subpath = is_subpath
|
||||
mark_closed = self.mark_paths_closed and vmobject.mark_paths_closed
|
||||
self.mark_paths_closed = vmobject.mark_paths_closed = mark_closed
|
||||
return self
|
||||
|
||||
def align_points_with_larger(self, larger_mobject):
|
||||
assert(isinstance(larger_mobject, VMobject))
|
||||
self.insert_n_anchor_points(
|
||||
larger_mobject.get_num_anchor_points() -
|
||||
self.get_num_anchor_points()
|
||||
)
|
||||
lnc = larger_mobject.get_num_curves()
|
||||
snc = self.get_num_curves()
|
||||
self.insert_n_curves(lnc - snc)
|
||||
return self
|
||||
|
||||
def insert_n_anchor_points(self, n):
|
||||
curr = self.get_num_anchor_points()
|
||||
if curr == 0:
|
||||
self.points = np.zeros((1, 3))
|
||||
n = n - 1
|
||||
if curr == 1:
|
||||
self.points = np.repeat(self.points, 3 * n + 1, axis=0)
|
||||
return self
|
||||
points = np.array([self.points[0]])
|
||||
num_curves = curr - 1
|
||||
# Curves in self are buckets, and we need to know
|
||||
# how many new anchor points to put into each one.
|
||||
# Each element of index_allocation is like a bucket,
|
||||
# and its value tells you the appropriate index of
|
||||
# the smaller curve.
|
||||
index_allocation = (
|
||||
np.arange(curr + n - 1) * num_curves) // (curr + n - 1)
|
||||
for index in range(num_curves):
|
||||
curr_bezier_points = self.points[3 * index:3 * index + 4]
|
||||
num_inter_curves = sum(index_allocation == index)
|
||||
alphas = np.linspace(0, 1, num_inter_curves + 1)
|
||||
# alphas = np.arange(0, num_inter_curves+1)/float(num_inter_curves)
|
||||
for a, b in zip(alphas, alphas[1:]):
|
||||
new_points = partial_bezier_points(
|
||||
curr_bezier_points, a, b
|
||||
def insert_n_curves(self, n):
|
||||
new_path_point = None
|
||||
if self.has_new_path_started():
|
||||
new_path_point = self.get_last_point()
|
||||
curr_curve_points = self.get_all_cubic_bezier_point_tuples()
|
||||
curr_num = len(curr_curve_points)
|
||||
target_num = curr_num + n
|
||||
# This is an array with values ranging from 0
|
||||
# up to curr_num, with repeats such that
|
||||
# it's total length is target_num. For example,
|
||||
# with curr_num = 10, target_num = 15, this would
|
||||
# be [0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9]
|
||||
repeat_indices = (np.arange(target_num) * curr_num) // target_num
|
||||
|
||||
# If the nth term of this list is k, it means
|
||||
# that the nth curve of our path should be split
|
||||
# into k pieces. In the above example, this would
|
||||
# be [2, 1, 2, 1, 2, 1, 2, 1, 2, 1]
|
||||
split_factors = [
|
||||
sum(repeat_indices == i)
|
||||
for i in range(curr_num)
|
||||
]
|
||||
|
||||
self.clear_points()
|
||||
for points, sf in zip(curr_curve_points, split_factors):
|
||||
# What was once a single cubic curve defined
|
||||
# by "points" will now be broken into sf
|
||||
# smaller cubic curves
|
||||
alphas = np.linspace(0, 1, sf + 1)
|
||||
for a1, a2 in zip(alphas, alphas[1:]):
|
||||
self.append_points(
|
||||
partial_bezier_points(points, a1, a2)
|
||||
)
|
||||
points = np.append(
|
||||
points, new_points[1:], axis=0
|
||||
)
|
||||
self.set_points(points)
|
||||
if new_path_point is not None:
|
||||
self.append_points([new_path_point])
|
||||
return self
|
||||
|
||||
def align_rgbas(self, vmobject):
|
||||
|
@ -604,11 +712,6 @@ class VMobject(Mobject):
|
|||
center = self.get_center()
|
||||
return VectorizedPoint(center)
|
||||
|
||||
def repeat_submobject(self, submobject):
|
||||
if submobject.is_subpath:
|
||||
return VectorizedPoint(submobject.points[0])
|
||||
return submobject.copy()
|
||||
|
||||
def interpolate_color(self, mobject1, mobject2, alpha):
|
||||
attrs = [
|
||||
"fill_rgbas",
|
||||
|
@ -628,40 +731,40 @@ class VMobject(Mobject):
|
|||
if alpha == 1.0:
|
||||
setattr(self, attr, getattr(mobject2, attr))
|
||||
|
||||
def pointwise_become_partial(self, mobject, a, b):
|
||||
assert(isinstance(mobject, VMobject))
|
||||
def pointwise_become_partial(self, vmobject, a, b):
|
||||
assert(isinstance(vmobject, VMobject))
|
||||
# Partial curve includes three portions:
|
||||
# - A middle section, which matches the curve exactly
|
||||
# - A start, which is some ending portion of an inner cubic
|
||||
# - An end, which is the starting portion of a later inner cubic
|
||||
if a <= 0 and b >= 1:
|
||||
self.set_points(mobject.points)
|
||||
self.mark_paths_closed = mobject.mark_paths_closed
|
||||
self.set_points(vmobject.points)
|
||||
return self
|
||||
self.mark_paths_closed = False
|
||||
num_cubics = mobject.get_num_anchor_points() - 1
|
||||
lower_index = int(a * num_cubics)
|
||||
upper_index = int(b * num_cubics)
|
||||
points = np.array(
|
||||
mobject.points[3 * lower_index:3 * upper_index + 4]
|
||||
)
|
||||
if len(points) > 1:
|
||||
a_residue = (num_cubics * a) % 1
|
||||
b_residue = (num_cubics * b) % 1
|
||||
if b == 1:
|
||||
b_residue = 1
|
||||
elif lower_index == upper_index:
|
||||
b_residue = (b_residue - a_residue) / (1 - a_residue)
|
||||
bezier_quads = vmobject.get_all_cubic_bezier_point_tuples()
|
||||
num_cubics = len(bezier_quads)
|
||||
|
||||
points[:4] = partial_bezier_points(
|
||||
points[:4], a_residue, 1
|
||||
)
|
||||
points[-4:] = partial_bezier_points(
|
||||
points[-4:], 0, b_residue
|
||||
)
|
||||
self.set_points(points)
|
||||
lower_index, lower_residue = integer_interpolate(0, num_cubics, a)
|
||||
upper_index, upper_residue = integer_interpolate(0, num_cubics, b)
|
||||
|
||||
self.clear_points()
|
||||
self.append_points(partial_bezier_points(
|
||||
bezier_quads[lower_index], lower_residue, 1
|
||||
))
|
||||
for quad in bezier_quads[lower_index + 1:upper_index]:
|
||||
self.append_points(quad)
|
||||
self.append_points(partial_bezier_points(
|
||||
bezier_quads[upper_index], 0, upper_residue
|
||||
))
|
||||
return self
|
||||
|
||||
# Errors
|
||||
def throw_error_if_no_points(self):
|
||||
if self.has_no_points():
|
||||
message = "Cannot call VMobject.{}" +\
|
||||
"for a VMobject with no points"
|
||||
caller_name = sys._getframe(1).f_code.co_name
|
||||
raise Exception(message.format(caller_name))
|
||||
|
||||
|
||||
class VGroup(VMobject):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -292,12 +292,13 @@ class CircularFractal(SelfSimilarFractal):
|
|||
|
||||
|
||||
class JaggedCurvePiece(VMobject):
|
||||
def insert_n_anchor_points(self, n):
|
||||
if self.get_num_anchor_points() == 0:
|
||||
self.points = np.zeros((1, 3))
|
||||
def insert_n_curves(self, n):
|
||||
if self.get_num_curves() == 0:
|
||||
self.set_points(np.zeros((1, 3)))
|
||||
anchors = self.get_anchors()
|
||||
indices = np.linspace(0, len(anchors) - 1, n + len(anchors)) \
|
||||
.astype('int')
|
||||
indices = np.linspace(
|
||||
0, len(anchors) - 1, n + len(anchors)
|
||||
).astype('int')
|
||||
self.set_points_as_corners(anchors[indices])
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
from scipy import linalg
|
||||
import numpy as np
|
||||
|
||||
from manimlib.utils.simple_functions import choose_using_cache
|
||||
from manimlib.utils.space_ops import get_norm
|
||||
from manimlib.utils.simple_functions import choose
|
||||
|
||||
CLOSED_THRESHOLD = 0.001
|
||||
|
||||
|
@ -10,7 +9,7 @@ CLOSED_THRESHOLD = 0.001
|
|||
def bezier(points):
|
||||
n = len(points) - 1
|
||||
return lambda t: sum([
|
||||
((1 - t)**(n - k)) * (t**k) * choose_using_cache(n, k) * point
|
||||
((1 - t)**(n - k)) * (t**k) * choose(n, k) * point
|
||||
for k, point in enumerate(points)
|
||||
])
|
||||
|
||||
|
@ -41,6 +40,25 @@ def interpolate(start, end, alpha):
|
|||
return (1 - alpha) * start + alpha * end
|
||||
|
||||
|
||||
def integer_interpolate(start, end, alpha):
|
||||
"""
|
||||
alpha is a float between 0 and 1. This returns
|
||||
an integer between start and end (inclusive) representing
|
||||
appropriate interpolation between them, along with a
|
||||
"residue" representing a new proportion between the
|
||||
returned integer and the next one of the
|
||||
list.
|
||||
|
||||
For example, if start=0, end=10, alpha=0.46, This
|
||||
would return (4, 0.6).
|
||||
"""
|
||||
if alpha >= 1:
|
||||
return (end - 1, 1.0)
|
||||
value = int(interpolate(start, end, alpha))
|
||||
residue = ((end - start) * alpha) % 1
|
||||
return (value, residue)
|
||||
|
||||
|
||||
def mid(start, end):
|
||||
return (start + end) / 2.0
|
||||
|
||||
|
@ -134,4 +152,4 @@ def diag_to_matrix(l_and_u, diag):
|
|||
|
||||
|
||||
def is_closed(points):
|
||||
return get_norm(points[0] - points[-1]) < CLOSED_THRESHOLD
|
||||
return np.allclose(points[0], points[-1])
|
||||
|
|
|
@ -15,11 +15,13 @@ def choose_using_cache(n, r):
|
|||
if n not in CHOOSE_CACHE:
|
||||
CHOOSE_CACHE[n] = {}
|
||||
if r not in CHOOSE_CACHE[n]:
|
||||
CHOOSE_CACHE[n][r] = choose(n, r)
|
||||
CHOOSE_CACHE[n][r] = choose(n, r, use_cache=False)
|
||||
return CHOOSE_CACHE[n][r]
|
||||
|
||||
|
||||
def choose(n, r):
|
||||
def choose(n, r, use_cache=True):
|
||||
if use_cache:
|
||||
return choose_using_cache(n, r)
|
||||
if n < r:
|
||||
return 0
|
||||
if r == 0:
|
||||
|
|
|
@ -1763,7 +1763,7 @@ class Initial2dFuncSceneWithoutMorphing(Initial2dFuncSceneBase):
|
|||
# Alternative to the above, manually implementing split screen with a morphing animation
|
||||
class Initial2dFuncSceneMorphing(Initial2dFuncSceneBase):
|
||||
CONFIG = {
|
||||
"num_needed_anchor_points" : 10,
|
||||
"num_needed_anchor_curves" : 10,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
|
@ -1781,8 +1781,8 @@ class Initial2dFuncSceneMorphing(Initial2dFuncSceneBase):
|
|||
|
||||
def obj_draw(self, input_object):
|
||||
output_object = input_object.copy()
|
||||
if input_object.get_num_anchor_points() < self.num_needed_anchor_points:
|
||||
input_object.insert_n_anchor_points(self.num_needed_anchor_points)
|
||||
if input_object.get_num_curves() < self.num_needed_anchor_curves:
|
||||
input_object.insert_n_curves(self.num_needed_anchor_curves)
|
||||
output_object.apply_function(self.func)
|
||||
self.squash_onto_left(input_object)
|
||||
self.squash_onto_right(output_object)
|
||||
|
|
|
@ -1606,7 +1606,7 @@ class DirectionOfA2DFunctionAlongABoundary(InputOutputScene):
|
|||
input_plane.coords_to_point(2.5, -1.5),
|
||||
)
|
||||
rect.replace(line, stretch = True)
|
||||
rect.insert_n_anchor_points(50)
|
||||
rect.insert_n_curves(50)
|
||||
rect.match_background_image_file(colorings[0])
|
||||
|
||||
rect_image = rect.copy()
|
||||
|
@ -1815,7 +1815,7 @@ class ForeverNarrowingLoop(InputOutputScene):
|
|||
# circle
|
||||
circle = Circle(color = WHITE, radius = self.circle_start_radius)
|
||||
circle.flip(axis = RIGHT)
|
||||
circle.insert_n_anchor_points(50)
|
||||
circle.insert_n_curves(50)
|
||||
if self.start_around_target:
|
||||
circle.move_to(input_plane.coords_to_point(*self.target_coords))
|
||||
else:
|
||||
|
@ -2788,7 +2788,7 @@ class WindingNumbersInInputOutputContext(PathContainingZero):
|
|||
in_loop = Circle()
|
||||
in_loop.flip(RIGHT)
|
||||
# in_loop = Square(side_length = 2)
|
||||
in_loop.insert_n_anchor_points(100)
|
||||
in_loop.insert_n_curves(100)
|
||||
in_loop.move_to(self.input_plane.coords_to_point(
|
||||
*self.in_loop_center_coords
|
||||
))
|
||||
|
|
|
@ -45,7 +45,7 @@ class NumberlineTransformationScene(ZoomedScene):
|
|||
"color": BLUE,
|
||||
},
|
||||
"output_line_config": {},
|
||||
"num_inserted_number_line_anchors": 20,
|
||||
"num_inserted_number_line_curves": 20,
|
||||
"default_delta_x": 0.1,
|
||||
"default_sample_dot_radius": 0.07,
|
||||
"default_sample_dot_colors": [RED, YELLOW],
|
||||
|
@ -76,8 +76,8 @@ class NumberlineTransformationScene(ZoomedScene):
|
|||
full_config = dict(self.number_line_config)
|
||||
full_config.update(added_config)
|
||||
number_line = NumberLine(**full_config)
|
||||
number_line.main_line.insert_n_anchor_points(
|
||||
self.num_inserted_number_line_anchors
|
||||
number_line.main_line.insert_n_curves(
|
||||
self.num_inserted_number_line_curves
|
||||
)
|
||||
number_line.shift(zero_point - number_line.number_to_point(0))
|
||||
number_lines.add(number_line)
|
||||
|
@ -179,8 +179,8 @@ class NumberlineTransformationScene(ZoomedScene):
|
|||
self.moving_input_line = input_line_copy
|
||||
input_line_copy.remove(input_line_copy.numbers)
|
||||
# input_line_copy.set_stroke(width=2)
|
||||
input_line_copy.main_line.insert_n_anchor_points(
|
||||
self.num_inserted_number_line_anchors
|
||||
input_line_copy.main_line.insert_n_curves(
|
||||
self.num_inserted_number_line_curves
|
||||
)
|
||||
return AnimationGroup(
|
||||
self.get_mapping_animation(
|
||||
|
@ -311,7 +311,7 @@ class NumberlineTransformationScene(ZoomedScene):
|
|||
# Add miniature number_line
|
||||
mini_line = self.mini_line = Line(frame.get_left(), frame.get_right())
|
||||
mini_line.scale(self.mini_line_scale_factor)
|
||||
mini_line.insert_n_anchor_points(self.num_inserted_number_line_anchors)
|
||||
mini_line.insert_n_curves(self.num_inserted_number_line_curves)
|
||||
mini_line.match_style(self.input_line.main_line)
|
||||
mini_line_copy = mini_line.copy()
|
||||
zcbr_group.add(mini_line_copy, mini_line)
|
||||
|
|
|
@ -1523,7 +1523,7 @@ class ShowLightInThreeDimensions(IntroduceScreen, ThreeDScene):
|
|||
screens = VGroup(
|
||||
Square(),
|
||||
RegularPolygon(8),
|
||||
Circle().insert_n_anchor_points(25),
|
||||
Circle().insert_n_curves(25),
|
||||
)
|
||||
for screen in screens:
|
||||
screen.set_height(self.screen_height)
|
||||
|
@ -3883,7 +3883,7 @@ class ThinkBackToHowAmazingThisIs(ThreeDScene):
|
|||
self.number_line = number_line
|
||||
|
||||
def show_giant_circle(self):
|
||||
self.number_line.main_line.insert_n_anchor_points(10000)
|
||||
self.number_line.main_line.insert_n_curves(10000)
|
||||
everything = VGroup(*self.mobjects)
|
||||
circle = everything.copy()
|
||||
circle.move_to(ORIGIN)
|
||||
|
|
|
@ -2635,7 +2635,7 @@ class Test(Scene):
|
|||
def construct(self):
|
||||
randy = Randolph()
|
||||
necklace = Necklace()
|
||||
necklace.insert_n_anchor_points(20)
|
||||
necklace.insert_n_curves(20)
|
||||
# necklace.apply_function(
|
||||
# lambda (x, y, z) : x*RIGHT + (y + 0.1*x**2)*UP
|
||||
# )
|
||||
|
|
|
@ -843,7 +843,7 @@ class CylinderModel(Scene):
|
|||
shift_val = 0.1 * LEFT + 0.2 * UP
|
||||
scale_factor = get_norm(RIGHT - shift_val)
|
||||
movers = VGroup(self.warped_grid, self.unit_circle)
|
||||
self.unit_circle.insert_n_anchor_points(50)
|
||||
self.unit_circle.insert_n_curves(50)
|
||||
|
||||
stream_lines = self.get_stream_lines()
|
||||
stream_lines.scale(scale_factor)
|
||||
|
@ -4493,7 +4493,7 @@ class BroughtToYouBy(PiCreatureScene):
|
|||
math.move_to(self.pi_creatures)
|
||||
|
||||
spiral = Line(0.5 * RIGHT, 0.5 * RIGHT + 70 * UP)
|
||||
spiral.insert_n_anchor_points(1000)
|
||||
spiral.insert_n_curves(1000)
|
||||
from old_projects.zeta import zeta
|
||||
spiral.apply_complex_function(zeta)
|
||||
step = 0.1
|
||||
|
|
|
@ -2964,7 +2964,7 @@ class ComplexExponentiationAdderHalf(
|
|||
)
|
||||
line.set_color(YELLOW)
|
||||
for submob in line:
|
||||
submob.insert_n_anchor_points(10)
|
||||
submob.insert_n_curves(10)
|
||||
submob.make_smooth()
|
||||
circle = VGroup(
|
||||
Circle(),
|
||||
|
@ -3069,7 +3069,7 @@ class ComplexExponentiationMultiplierHalf(
|
|||
line.set_color(YELLOW)
|
||||
line.shift(FRAME_X_RADIUS*LEFT)
|
||||
for submob in line:
|
||||
submob.insert_n_anchor_points(10)
|
||||
submob.insert_n_curves(10)
|
||||
submob.make_smooth()
|
||||
circle = VGroup(
|
||||
Circle(),
|
||||
|
@ -3110,7 +3110,7 @@ class ComplexExponentiationMultiplierHalf(
|
|||
arc_line = Line(RIGHT, RIGHT+angle*UP)
|
||||
brace = Brace(arc_line, RIGHT, buff = 0)
|
||||
for submob in brace.family_members_with_points():
|
||||
submob.insert_n_anchor_points(10)
|
||||
submob.insert_n_curves(10)
|
||||
curved_brace = brace.copy()
|
||||
curved_brace.shift(LEFT)
|
||||
curved_brace.apply_complex_function(
|
||||
|
|
|
@ -162,7 +162,7 @@ class CircleScene(PiCreatureScene):
|
|||
def get_unwrapped(self, ring, to_edge = LEFT, **kwargs):
|
||||
R = ring.R
|
||||
R_plus_dr = ring.R + ring.dR
|
||||
n_anchors = ring.get_num_anchor_points()
|
||||
n_anchors = ring.get_num_curves()
|
||||
result = VMobject()
|
||||
result.set_points_as_corners([
|
||||
interpolate(np.pi*R_plus_dr*LEFT, np.pi*R_plus_dr*RIGHT, a)
|
||||
|
@ -1115,7 +1115,7 @@ class GraphRectangles(CircleScene, GraphScene):
|
|||
|
||||
ring.rect = rect
|
||||
|
||||
n_anchors = ring.get_num_anchor_points()
|
||||
n_anchors = ring.get_num_curves()
|
||||
target = VMobject()
|
||||
target.set_points_as_corners([
|
||||
interpolate(ORIGIN, DOWN, a)
|
||||
|
|
|
@ -2218,7 +2218,7 @@ class IntroduceUnitCircleWithSine(GraphScene):
|
|||
color = YELLOW,
|
||||
)
|
||||
line.shift(FRAME_X_RADIUS*RIGHT/3).to_edge(UP)
|
||||
line.insert_n_anchor_points(10)
|
||||
line.insert_n_curves(10)
|
||||
line.make_smooth()
|
||||
|
||||
arc = Arc(
|
||||
|
|
|
@ -1114,7 +1114,7 @@ class WhyPi(PiCreatureScene):
|
|||
circum.set_color(circle.get_color())
|
||||
circum.scale(np.pi)
|
||||
circum.next_to(circle, DOWN, LARGE_BUFF)
|
||||
circum.insert_n_anchor_points(circle.get_num_anchor_points()-2)
|
||||
circum.insert_n_curves(circle.get_num_curves()-2)
|
||||
circum.make_jagged()
|
||||
pi = TexMobject("\\pi")
|
||||
pi.next_to(circum, UP)
|
||||
|
|
|
@ -156,7 +156,7 @@ class CircleScene(PiCreatureScene):
|
|||
def get_unwrapped(self, ring, to_edge = LEFT, **kwargs):
|
||||
R = ring.R
|
||||
R_plus_dr = ring.R + ring.dR
|
||||
n_anchors = ring.get_num_anchor_points()
|
||||
n_anchors = ring.get_num_curves()
|
||||
result = VMobject()
|
||||
result.set_points_as_corners([
|
||||
interpolate(np.pi*R_plus_dr*LEFT, np.pi*R_plus_dr*RIGHT, a)
|
||||
|
|
|
@ -500,7 +500,7 @@ class SneakyNonlinearTransformationExplained(SneakyNonlinearTransformation):
|
|||
FRAME_Y_RADIUS*LEFT+FRAME_Y_RADIUS*DOWN,
|
||||
FRAME_Y_RADIUS*RIGHT + FRAME_Y_RADIUS*UP
|
||||
)
|
||||
diag.insert_n_anchor_points(20)
|
||||
diag.insert_n_curves(20)
|
||||
diag.change_anchor_mode("smooth")
|
||||
diag.set_color(YELLOW)
|
||||
self.play(ShowCreation(diag))
|
||||
|
|
|
@ -2794,7 +2794,7 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot):
|
|||
for t in (get_t(), TAU)
|
||||
]
|
||||
for mob in arc, circle:
|
||||
mob.insert_n_anchor_points(20)
|
||||
mob.insert_n_curves(20)
|
||||
mob.set_stroke(RED, 4)
|
||||
mob.apply_function(
|
||||
lambda p : plane.number_to_point(
|
||||
|
|
|
@ -1442,8 +1442,8 @@ class DimensionOfQuadraticKoch(DimensionOfKoch):
|
|||
for order in range(2, self.koch_curve_order+1):
|
||||
new_curve = self.get_curve(order)
|
||||
new_curve.move_to(curve)
|
||||
n_anchors = len(curve.get_anchors())
|
||||
curve.insert_n_anchor_points(6*n_anchors)
|
||||
n_curve_parts = curve.get_num_curves()
|
||||
curve.insert_n_curves(6 * n_curve_parts)
|
||||
curve.make_jagged()
|
||||
self.play(Transform(curve, new_curve, run_time = 2))
|
||||
self.wait()
|
||||
|
|
|
@ -483,7 +483,7 @@ class EulerWrites628(Scene):
|
|||
fill_opacity = 0.3,
|
||||
fill_color = GREEN,
|
||||
)
|
||||
rect.insert_n_anchor_points(20)
|
||||
rect.insert_n_curves(20)
|
||||
rect.apply_function(lambda p : np.array([p[0], p[1] - 0.005*p[0]**2, p[2]]))
|
||||
rect.rotate(0.012*TAU)
|
||||
rect.move_to(image)
|
||||
|
|
|
@ -3206,7 +3206,7 @@ class SecondProof(SpecialThreeDScene):
|
|||
ring.set_fill(color, opacity=1)
|
||||
ring.set_stroke(color, width=0.5, opacity=1)
|
||||
for piece in ring:
|
||||
piece.insert_n_anchor_points(4)
|
||||
piece.insert_n_curves(4)
|
||||
piece.on_sphere = True
|
||||
piece.points = np.array([
|
||||
*piece.points[3:-1],
|
||||
|
|
|
@ -134,7 +134,7 @@ class Chaos(Eddy):
|
|||
y * UP, y * UP + self.width * RIGHT,
|
||||
stroke_width=1
|
||||
)
|
||||
line.insert_n_anchor_points(self.n_midpoints)
|
||||
line.insert_n_curves(self.n_midpoints)
|
||||
line.total_time = random.random()
|
||||
delta_h = self.height / (self.n_lines - 1)
|
||||
|
||||
|
@ -876,7 +876,7 @@ class HighCurlFieldBreakingLayersLines(HighCurlFieldBreakingLayers):
|
|||
|
||||
def get_line(self):
|
||||
line = Line(LEFT, RIGHT)
|
||||
line.insert_n_anchor_points(500)
|
||||
line.insert_n_curves(500)
|
||||
line.set_width(5)
|
||||
return line
|
||||
|
||||
|
|
|
@ -2039,7 +2039,7 @@ class IntroduceDopplerRadar(Scene):
|
|||
pulse_graph.underlying_function(x),
|
||||
echo_graph.underlying_function(x),
|
||||
]),
|
||||
num_graph_points = echo_graph.get_num_anchor_points(),
|
||||
num_graph_points = echo_graph.get_num_curves(),
|
||||
color = WHITE
|
||||
)
|
||||
sum_graph.background_image_file = "blue_yellow_gradient"
|
||||
|
|
|
@ -984,7 +984,7 @@ class DeformToInterval(ClosedLoopScene):
|
|||
interval.shift(2*DOWN)
|
||||
numbers = interval.get_number_mobjects(0, 1)
|
||||
line = Line(interval.get_left(), interval.get_right())
|
||||
line.insert_n_anchor_points(self.loop.get_num_anchor_points())
|
||||
line.insert_n_curves(self.loop.get_num_curves())
|
||||
line.make_smooth()
|
||||
|
||||
self.loop.scale(0.7)
|
||||
|
@ -1058,7 +1058,7 @@ class RepresentPairInUnitSquare(ClosedLoopScene):
|
|||
interval.shift(LEFT)
|
||||
numbers = interval.get_number_mobjects(0, 1)
|
||||
line = Line(interval.get_left(), interval.get_right())
|
||||
line.insert_n_anchor_points(self.loop.get_num_anchor_points())
|
||||
line.insert_n_curves(self.loop.get_num_curves())
|
||||
line.make_smooth()
|
||||
vert_interval = interval.copy()
|
||||
square = Square()
|
||||
|
@ -1270,7 +1270,7 @@ class EndpointsGluedTogether(ClosedLoopScene):
|
|||
interval.shift(2*DOWN)
|
||||
numbers = interval.get_number_mobjects(0, 1)
|
||||
line = Line(interval.get_left(), interval.get_right())
|
||||
line.insert_n_anchor_points(self.loop.get_num_anchor_points())
|
||||
line.insert_n_curves(self.loop.get_num_curves())
|
||||
line.make_smooth()
|
||||
|
||||
self.loop.scale(0.7)
|
||||
|
|
|
@ -37,7 +37,7 @@ class ZetaTransformationScene(ComplexTransformationScene):
|
|||
for line in mob.family_members_with_points():
|
||||
#Find point of line cloest to 1 on C
|
||||
if not isinstance(line, Line):
|
||||
line.insert_n_anchor_points(self.min_added_anchors)
|
||||
line.insert_n_curves(self.min_added_anchors)
|
||||
continue
|
||||
p1 = line.get_start()+LEFT
|
||||
p2 = line.get_end()+LEFT
|
||||
|
@ -47,14 +47,14 @@ class ZetaTransformationScene(ComplexTransformationScene):
|
|||
)
|
||||
#See how big this line will become
|
||||
diameter = abs(zeta(complex(*closest_to_one[:2])))
|
||||
target_num_anchors = np.clip(
|
||||
target_num_curves = np.clip(
|
||||
int(self.anchor_density*np.pi*diameter),
|
||||
self.min_added_anchors,
|
||||
self.max_added_anchors,
|
||||
)
|
||||
num_anchors = line.get_num_anchor_points()
|
||||
if num_anchors < target_num_anchors:
|
||||
line.insert_n_anchor_points(target_num_anchors-num_anchors)
|
||||
num_curves = line.get_num_curves()
|
||||
if num_curves < target_num_curves:
|
||||
line.insert_n_curves(target_num_curves-num_curves)
|
||||
line.make_smooth()
|
||||
|
||||
def add_extra_plane_lines_for_zeta(self, animate = False, **kwargs):
|
||||
|
@ -2279,7 +2279,7 @@ class IntroduceAnglePreservation(VisualizingSSquared):
|
|||
color = YELLOW
|
||||
)
|
||||
arc.shift(intersection_point)
|
||||
arc.insert_n_anchor_points(10)
|
||||
arc.insert_n_curves(10)
|
||||
arc.generate_target()
|
||||
input_z = complex(*arc.get_center()[:2])
|
||||
scale_factor = abs(2*input_z)
|
||||
|
|
Loading…
Add table
Reference in a new issue