Changing the way VMobject handles its internal bezier curves so as to eliminate the need for sup_paths

This commit is contained in:
Grant Sanderson 2019-02-05 11:02:15 -08:00
parent eb0c63606d
commit eaf25ff34b
29 changed files with 432 additions and 278 deletions

View file

@ -206,11 +206,7 @@ class Camera(object):
else: else:
method = Mobject.get_family method = Mobject.get_family
return remove_list_redundancies(list( return remove_list_redundancies(list(
it.chain(*[ it.chain(*[method(m) for m in mobjects])
method(m)
for m in mobjects
if not (isinstance(m, VMobject) and m.is_subpath)
])
)) ))
def get_mobjects_to_display( def get_mobjects_to_display(
@ -324,32 +320,61 @@ class Camera(object):
self.display_vectorized(vmobject, ctx) self.display_vectorized(vmobject, ctx)
def display_vectorized(self, 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.set_cairo_context_path(ctx, vmobject)
self.apply_stroke(ctx, vmobject, background=True) self.apply_stroke(ctx, vmobject, background=True)
self.apply_fill(ctx, vmobject) self.apply_fill(ctx, vmobject)
self.apply_stroke(ctx, vmobject) self.apply_stroke(ctx, vmobject)
return self 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): 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() ctx.new_path()
for vmob in it.chain([vmobject], vmobject.get_subpath_mobjects()): for p0, p1, p2, p3 in quads:
points = self.transform_points_pre_display( if should_start_new_path(last_p3, p0):
vmob, vmob.points ctx.new_sub_path()
) ctx.move_to(*p0[:2])
if np.any(np.isnan(points)) or np.any(points == np.inf): ctx.curve_to(*p1[:2], *p2[:2], *p3[:2])
points = np.zeros((1, 3)) last_p3 = p3
ctx.new_sub_path() if vmobject.is_closed():
ctx.move_to(*points[0][:2]) ctx.close_path()
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()
return self return self
def set_cairo_context_color(self, ctx, rgbas, vmobject): def set_cairo_context_color(self, ctx, rgbas, vmobject):

View file

@ -12,7 +12,7 @@ from manimlib.utils.config_ops import digest_config
class MappingCamera(Camera): class MappingCamera(Camera):
CONFIG = { CONFIG = {
"mapping_func": lambda p: p, "mapping_func": lambda p: p,
"min_anchor_points": 50, "min_num_curves": 50,
"allow_object_intrusion": False "allow_object_intrusion": False
} }
@ -27,8 +27,8 @@ class MappingCamera(Camera):
mobject_copies = [mobject.copy() for mobject in mobjects] mobject_copies = [mobject.copy() for mobject in mobjects]
for mobject in mobject_copies: for mobject in mobject_copies:
if isinstance(mobject, VMobject) and \ if isinstance(mobject, VMobject) and \
0 < mobject.get_num_anchor_points() < self.min_anchor_points: 0 < mobject.get_num_curves() < self.min_num_curves:
mobject.insert_n_anchor_points(self.min_anchor_points) mobject.insert_n_curves(self.min_num_curves)
Camera.capture_mobjects( Camera.capture_mobjects(
self, mobject_copies, self, mobject_copies,
include_submobjects=False, include_submobjects=False,

View file

@ -324,12 +324,12 @@ class NumberPlane(VMobject):
arrow = Arrow(ORIGIN, point, **kwargs) arrow = Arrow(ORIGIN, point, **kwargs)
return arrow 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(): for mob in self.family_members_with_points():
num_anchors = mob.get_num_anchor_points() num_curves = mob.get_num_curves()
if num_inserted_anchor_points > num_anchors: if num_inserted_curves > num_curves:
mob.insert_n_anchor_points( mob.insert_n_curves(
num_inserted_anchor_points - num_anchors) num_inserted_curves - num_curves)
mob.make_smooth() mob.make_smooth()
return self return self

View file

@ -43,12 +43,16 @@ class Arc(VMobject):
# Appropriate tangent lines to the circle # Appropriate tangent lines to the circle
d_theta = self.angle / (self.num_anchors - 1.0) d_theta = self.angle / (self.num_anchors - 1.0)
tangent_vectors = np.zeros(anchors.shape) tangent_vectors = np.zeros(anchors.shape)
# Rotate all 90 degress, via (x, y) -> (-y, x)
tangent_vectors[:, 1] = anchors[:, 0] tangent_vectors[:, 1] = anchors[:, 0]
tangent_vectors[:, 0] = -anchors[:, 1] tangent_vectors[:, 0] = -anchors[:, 1]
# Use tangent vectors to deduce anchors
handles1 = anchors[:-1] + (d_theta / 3) * tangent_vectors[:-1] handles1 = anchors[:-1] + (d_theta / 3) * tangent_vectors[:-1]
handles2 = anchors[1:] - (d_theta / 3) * tangent_vectors[1:] handles2 = anchors[1:] - (d_theta / 3) * tangent_vectors[1:]
self.set_anchors_and_handles( self.set_anchors_and_handles(
anchors, handles1, handles2 anchors[:-1],
handles1, handles2,
anchors[1:],
) )
self.scale(self.radius, about_point=ORIGIN) self.scale(self.radius, about_point=ORIGIN)
self.shift(self.arc_center) self.shift(self.arc_center)
@ -250,9 +254,9 @@ class AnnularSector(VMobject):
for alpha in np.linspace(0, 1, 4) for alpha in np.linspace(0, 1, 4)
]) ])
self.points = np.array(arc1.points) self.points = np.array(arc1.points)
self.add_control_points(a1_to_a2_points[1:]) self.add_cubic_bezier_curve(*a1_to_a2_points[1:])
self.add_control_points(arc2.points[1:]) self.add_cubic_bezier_curve(*arc2.points[1:])
self.add_control_points(a2_to_a1_points[1:]) self.add_cubic_bezier_curve(*a2_to_a1_points[1:])
def get_arc_center(self): def get_arc_center(self):
first_point = self.points[0] first_point = self.points[0]

View file

@ -331,7 +331,7 @@ class VMobjectFromSVGPathstring(VMobject):
re.split(pattern, self.path_string)[1:] re.split(pattern, self.path_string)[1:]
)) ))
# Which mobject should new points be added to # Which mobject should new points be added to
self.growing_path = self self = self
for command, coord_string in pairs: for command, coord_string in pairs:
self.handle_command(command, coord_string) self.handle_command(command, coord_string)
# people treat y-coordinate differently # people treat y-coordinate differently
@ -342,28 +342,23 @@ class VMobjectFromSVGPathstring(VMobject):
command = command.upper() command = command.upper()
# new_points are the points that will be added to the curr_points # new_points are the points that will be added to the curr_points
# list. This variable may get modified in the conditionals below. # 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) 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: if isLower and len(points) > 0:
new_points += points[-1] 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": if command == "H":
new_points[0, 1] = points[-1, 1] new_points[0, 1] = points[-1, 1]
elif command == "V": elif command == "V":
@ -372,21 +367,27 @@ class VMobjectFromSVGPathstring(VMobject):
new_points[0, 0] += points[-1, 1] new_points[0, 0] += points[-1, 1]
new_points[0, 1] = new_points[0, 0] new_points[0, 1] = new_points[0, 0]
new_points[0, 0] = points[-1, 0] new_points[0, 0] = points[-1, 0]
new_points = new_points.repeat(3, axis=0) self.add_line_to(new_points[0])
elif command == "C": # curveto return
if command == "C": # curveto
pass # Yay! No action required pass # Yay! No action required
elif command in ["S", "T"]: # smooth curveto elif command in ["S", "T"]: # smooth curveto
handle1 = points[-1] + (points[-1] - points[-2]) self.add_smooth_curve_to(*new_points)
new_points = np.append([handle1], new_points, axis=0) # handle1 = points[-1] + (points[-1] - points[-2])
if command in ["Q", "T"]: # quadratic Bezier curve # new_points = np.append([handle1], new_points, axis=0)
return
elif command == "Q": # quadratic Bezier curve
# TODO, this is a suboptimal approximation # TODO, this is a suboptimal approximation
new_points = np.append([new_points[0]], new_points, axis=0) new_points = np.append([new_points[0]], new_points, axis=0)
elif command == "A": # elliptical Arc elif command == "A": # elliptical Arc
raise Exception("Not implemented") raise Exception("Not implemented")
elif command == "Z": # closepath elif command == "Z": # closepath
if not is_closed(points): if is_closed(points):
# Both handles and new anchor are the start return
new_points = points[[0, 0, 0]] # Both handles and new anchor are the start
# TODO, is this needed?
new_points = points[[0, 0, 0]]
# self.mark_paths_closed = True # self.mark_paths_closed = True
# Handle situations where there's multiple relative control points # 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] -= points[-1]
new_points[i:i + 3] += new_points[i - 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): def string_to_points(self, coord_string):
numbers = string_to_numbers(coord_string) numbers = string_to_numbers(coord_string)

View file

@ -1,4 +1,5 @@
import itertools as it import itertools as it
import sys
from colour import Color 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 bezier
from manimlib.utils.bezier import get_smooth_handle_points from manimlib.utils.bezier import get_smooth_handle_points
from manimlib.utils.bezier import interpolate 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.bezier import partial_bezier_points
from manimlib.utils.color import color_to_rgba from manimlib.utils.color import color_to_rgba
from manimlib.utils.iterables import make_even 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 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): class VMobject(Mobject):
CONFIG = { CONFIG = {
"fill_color": None, "fill_color": None,
@ -38,14 +48,16 @@ class VMobject(Mobject):
"sheen_direction": UL, "sheen_direction": UL,
# Indicates that it will not be displayed, but # Indicates that it will not be displayed, but
# that it should count in parent mobject's path # that it should count in parent mobject's path
"is_subpath": False,
"close_new_points": False, "close_new_points": False,
"mark_paths_closed": False,
"propagate_style_to_family": False, "propagate_style_to_family": False,
"pre_function_handle_to_anchor_scale_factor": 0.01, "pre_function_handle_to_anchor_scale_factor": 0.01,
"make_smooth_after_applying_functions": False, "make_smooth_after_applying_functions": False,
"background_image_file": None, "background_image_file": None,
"shade_in_3d": False, "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): def get_group_class(self):
@ -364,63 +376,136 @@ class VMobject(Mobject):
submob.z_index_group = self submob.z_index_group = self
return self return self
# Drawing # Points
def start_at(self, point): def set_points(self, points):
if len(self.points) == 0: self.points = np.array(points)
self.points = np.zeros((1, 3))
self.points[0] = point
return self return self
def add_control_points(self, control_points): def get_points(self):
assert(len(control_points) % 3 == 0) return np.array(self.points)
self.points = np.append(
self.points,
control_points,
axis=0
)
return self
def is_closed(self): def set_anchors_and_handles(self, anchors1, handles1, handles2, anchors2):
return is_closed(self.points) assert(len(anchors1) == len(handles1) == len(handles2) == len(anchors2))
nppcc = self.n_points_per_cubic_curve # 4
def set_anchors_and_handles(self, anchors, handles1, handles2): total_len = nppcc * len(anchors1)
assert(len(anchors) == len(handles1) + 1)
assert(len(anchors) == len(handles2) + 1)
total_len = 3 * (len(anchors) - 1) + 1
self.points = np.zeros((total_len, self.dim)) self.points = np.zeros((total_len, self.dim))
self.points[0] = anchors[0] arrays = [anchors1, handles1, handles2, anchors2]
arrays = [handles1, handles2, anchors[1:]]
for index, array in enumerate(arrays): for index, array in enumerate(arrays):
self.points[index + 1::3] = array self.points[index::nppcc] = array
return self.points return self
def set_points_as_corners(self, points): def clear_points(self):
if len(points) <= 1: self.points = np.zeros((0, self.dim))
return self
points = self.prepare_new_anchor_points(points) def append_points(self, new_points):
self.set_anchors_and_handles(points, *[ # TODO, check that number new points is a multiple of 4?
interpolate(points[:-1], points[1:], alpha) # or else that if len(self.points) % 4 == 1, then
for alpha in (1. / 3, 2. / 3) # 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 return self
def set_points_smoothly(self, points): def add_smooth_curve_to(self, *points):
if len(points) <= 1: """
return self If two points are passed in, the first is intepretted
points = self.prepare_new_anchor_points(points) as a handle, the second as an anchor
h1, h2 = get_smooth_handle_points(points) """
self.set_anchors_and_handles(points, h1, h2) 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 return self
def prepare_new_anchor_points(self, points): # TODO, remove
if not isinstance(points, np.ndarray): # def add_control_points(self, control_points):
points = np.array(points) # assert(len(control_points) % 3 == 0)
if self.close_new_points and not is_closed(points): # self.points = np.append(
points = np.append(points, [points[0]], axis=0) # 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 return points
def set_points(self, points): def set_points_as_corners(self, points):
self.points = np.array(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 return self
def set_anchor_points(self, points, mode="smooth"): def set_anchor_points(self, points, mode="smooth"):
@ -432,6 +517,7 @@ class VMobject(Mobject):
raise Exception("Unknown mode") raise Exception("Unknown mode")
return self return self
# TODO, this will not work!
def change_anchor_mode(self, mode): def change_anchor_mode(self, mode):
for submob in self.family_members_with_points(): for submob in self.family_members_with_points():
anchors = submob.get_anchors() anchors = submob.get_anchors()
@ -445,35 +531,18 @@ class VMobject(Mobject):
return self.change_anchor_mode("corners") return self.change_anchor_mode("corners")
def add_subpath(self, points): def add_subpath(self, points):
""" assert(len(points) % 4 == 0)
A VMobject is meant to represent self.points = np.append(self.points, points, axis=0)
a single "path", in the svg sense of the word. return self
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
def append_vectorized_mobject(self, vectorized_mobject): def append_vectorized_mobject(self, vectorized_mobject):
new_points = list(vectorized_mobject.points) 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): if self.has_new_path_started():
return [m for m in self.submobjects if hasattr(m, 'is_subpath') and m.is_subpath] # Remove last point, which is starting
# a new path
self.points = self.points[:-1]
self.append_points(new_points)
def apply_function(self, function): def apply_function(self, function):
factor = self.pre_function_handle_to_anchor_scale_factor factor = self.pre_function_handle_to_anchor_scale_factor
@ -495,95 +564,134 @@ class VMobject(Mobject):
again. again.
""" """
for submob in self.family_members_with_points(): for submob in self.family_members_with_points():
anchors, handles1, handles2 = submob.get_anchors_and_handles() a1, h1, h2, a2 = submob.get_anchors_and_handles()
# print len(anchors), len(handles1), len(handles2) a1_to_h1 = h1 - a1
a_to_h1 = handles1 - anchors[:-1] a2_to_h2 = h2 - a2
a_to_h2 = handles2 - anchors[1:] new_h1 = a1 + factor * a1_to_h1
handles1 = anchors[:-1] + factor * a_to_h1 new_h2 = a2 + factor * a2_to_h2
handles2 = anchors[1:] + factor * a_to_h2 submob.set_anchors_and_handles(a1, new_h1, new_h2, a2)
submob.set_anchors_and_handles(anchors, handles1, handles2) return self
#
def consider_points_equals(self, p0, p1):
return np.allclose(
p0, p1,
atol=self.tolerance_for_point_equality
)
# Information about line # Information about line
def component_curves(self): def get_all_cubic_bezier_point_tuples(self):
for n in range(self.get_num_anchor_points() - 1): points = self.get_points()
yield self.get_nth_curve(n) 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): def get_nth_curve_points(self, n):
return bezier(self.points[3 * n:3 * n + 4]) 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): def get_nth_curve_function(self, n):
return (len(self.points) - 1) // 3 + 1 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): def point_from_proportion(self, alpha):
num_cubics = self.get_num_anchor_points() - 1 num_cubics = self.get_num_curves()
interpoint_alpha = num_cubics * (alpha % (1. / num_cubics)) n, residue = integer_interpolate(0, num_cubics, alpha)
index = min(3 * int(alpha * num_cubics), 3 * num_cubics) curve = self.get_nth_curve_function(n)
cubic = bezier(self.points[index:index + 4]) return curve(residue)
return cubic(interpoint_alpha)
def get_anchors_and_handles(self): 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 [ return [
self.points[i::3] self.points[i::nppcc]
for i in range(3) 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): 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): def get_points_defining_boundary(self):
return np.array(list(it.chain(*[ 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 # Alignment
def align_points(self, vmobject): def align_points(self, vmobject):
# This will call back to align_points_with_larger
Mobject.align_points(self, vmobject) Mobject.align_points(self, vmobject)
self.align_rgbas(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 return self
def align_points_with_larger(self, larger_mobject): def align_points_with_larger(self, larger_mobject):
assert(isinstance(larger_mobject, VMobject)) assert(isinstance(larger_mobject, VMobject))
self.insert_n_anchor_points( lnc = larger_mobject.get_num_curves()
larger_mobject.get_num_anchor_points() - snc = self.get_num_curves()
self.get_num_anchor_points() self.insert_n_curves(lnc - snc)
)
return self return self
def insert_n_anchor_points(self, n): def insert_n_curves(self, n):
curr = self.get_num_anchor_points() new_path_point = None
if curr == 0: if self.has_new_path_started():
self.points = np.zeros((1, 3)) new_path_point = self.get_last_point()
n = n - 1 curr_curve_points = self.get_all_cubic_bezier_point_tuples()
if curr == 1: curr_num = len(curr_curve_points)
self.points = np.repeat(self.points, 3 * n + 1, axis=0) target_num = curr_num + n
return self # This is an array with values ranging from 0
points = np.array([self.points[0]]) # up to curr_num, with repeats such that
num_curves = curr - 1 # it's total length is target_num. For example,
# Curves in self are buckets, and we need to know # with curr_num = 10, target_num = 15, this would
# how many new anchor points to put into each one. # be [0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9]
# Each element of index_allocation is like a bucket, repeat_indices = (np.arange(target_num) * curr_num) // target_num
# and its value tells you the appropriate index of
# the smaller curve. # If the nth term of this list is k, it means
index_allocation = ( # that the nth curve of our path should be split
np.arange(curr + n - 1) * num_curves) // (curr + n - 1) # into k pieces. In the above example, this would
for index in range(num_curves): # be [2, 1, 2, 1, 2, 1, 2, 1, 2, 1]
curr_bezier_points = self.points[3 * index:3 * index + 4] split_factors = [
num_inter_curves = sum(index_allocation == index) sum(repeat_indices == i)
alphas = np.linspace(0, 1, num_inter_curves + 1) for i in range(curr_num)
# 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( self.clear_points()
curr_bezier_points, a, b 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( if new_path_point is not None:
points, new_points[1:], axis=0 self.append_points([new_path_point])
)
self.set_points(points)
return self return self
def align_rgbas(self, vmobject): def align_rgbas(self, vmobject):
@ -604,11 +712,6 @@ class VMobject(Mobject):
center = self.get_center() center = self.get_center()
return VectorizedPoint(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): def interpolate_color(self, mobject1, mobject2, alpha):
attrs = [ attrs = [
"fill_rgbas", "fill_rgbas",
@ -628,40 +731,40 @@ class VMobject(Mobject):
if alpha == 1.0: if alpha == 1.0:
setattr(self, attr, getattr(mobject2, attr)) setattr(self, attr, getattr(mobject2, attr))
def pointwise_become_partial(self, mobject, a, b): def pointwise_become_partial(self, vmobject, a, b):
assert(isinstance(mobject, VMobject)) assert(isinstance(vmobject, VMobject))
# Partial curve includes three portions: # Partial curve includes three portions:
# - A middle section, which matches the curve exactly # - A middle section, which matches the curve exactly
# - A start, which is some ending portion of an inner cubic # - A start, which is some ending portion of an inner cubic
# - An end, which is the starting portion of a later inner cubic # - An end, which is the starting portion of a later inner cubic
if a <= 0 and b >= 1: if a <= 0 and b >= 1:
self.set_points(mobject.points) self.set_points(vmobject.points)
self.mark_paths_closed = mobject.mark_paths_closed
return self return self
self.mark_paths_closed = False bezier_quads = vmobject.get_all_cubic_bezier_point_tuples()
num_cubics = mobject.get_num_anchor_points() - 1 num_cubics = len(bezier_quads)
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)
points[:4] = partial_bezier_points( lower_index, lower_residue = integer_interpolate(0, num_cubics, a)
points[:4], a_residue, 1 upper_index, upper_residue = integer_interpolate(0, num_cubics, b)
)
points[-4:] = partial_bezier_points( self.clear_points()
points[-4:], 0, b_residue self.append_points(partial_bezier_points(
) bezier_quads[lower_index], lower_residue, 1
self.set_points(points) ))
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 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): class VGroup(VMobject):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View file

@ -292,12 +292,13 @@ class CircularFractal(SelfSimilarFractal):
class JaggedCurvePiece(VMobject): class JaggedCurvePiece(VMobject):
def insert_n_anchor_points(self, n): def insert_n_curves(self, n):
if self.get_num_anchor_points() == 0: if self.get_num_curves() == 0:
self.points = np.zeros((1, 3)) self.set_points(np.zeros((1, 3)))
anchors = self.get_anchors() anchors = self.get_anchors()
indices = np.linspace(0, len(anchors) - 1, n + len(anchors)) \ indices = np.linspace(
.astype('int') 0, len(anchors) - 1, n + len(anchors)
).astype('int')
self.set_points_as_corners(anchors[indices]) self.set_points_as_corners(anchors[indices])

View file

@ -1,8 +1,7 @@
from scipy import linalg from scipy import linalg
import numpy as np import numpy as np
from manimlib.utils.simple_functions import choose_using_cache from manimlib.utils.simple_functions import choose
from manimlib.utils.space_ops import get_norm
CLOSED_THRESHOLD = 0.001 CLOSED_THRESHOLD = 0.001
@ -10,7 +9,7 @@ CLOSED_THRESHOLD = 0.001
def bezier(points): def bezier(points):
n = len(points) - 1 n = len(points) - 1
return lambda t: sum([ 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) for k, point in enumerate(points)
]) ])
@ -41,6 +40,25 @@ def interpolate(start, end, alpha):
return (1 - alpha) * start + alpha * end 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): def mid(start, end):
return (start + end) / 2.0 return (start + end) / 2.0
@ -134,4 +152,4 @@ def diag_to_matrix(l_and_u, diag):
def is_closed(points): def is_closed(points):
return get_norm(points[0] - points[-1]) < CLOSED_THRESHOLD return np.allclose(points[0], points[-1])

View file

@ -15,11 +15,13 @@ def choose_using_cache(n, r):
if n not in CHOOSE_CACHE: if n not in CHOOSE_CACHE:
CHOOSE_CACHE[n] = {} CHOOSE_CACHE[n] = {}
if r not in 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] 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: if n < r:
return 0 return 0
if r == 0: if r == 0:

View file

@ -1763,7 +1763,7 @@ class Initial2dFuncSceneWithoutMorphing(Initial2dFuncSceneBase):
# Alternative to the above, manually implementing split screen with a morphing animation # Alternative to the above, manually implementing split screen with a morphing animation
class Initial2dFuncSceneMorphing(Initial2dFuncSceneBase): class Initial2dFuncSceneMorphing(Initial2dFuncSceneBase):
CONFIG = { CONFIG = {
"num_needed_anchor_points" : 10, "num_needed_anchor_curves" : 10,
} }
def setup(self): def setup(self):
@ -1781,8 +1781,8 @@ class Initial2dFuncSceneMorphing(Initial2dFuncSceneBase):
def obj_draw(self, input_object): def obj_draw(self, input_object):
output_object = input_object.copy() output_object = input_object.copy()
if input_object.get_num_anchor_points() < self.num_needed_anchor_points: if input_object.get_num_curves() < self.num_needed_anchor_curves:
input_object.insert_n_anchor_points(self.num_needed_anchor_points) input_object.insert_n_curves(self.num_needed_anchor_curves)
output_object.apply_function(self.func) output_object.apply_function(self.func)
self.squash_onto_left(input_object) self.squash_onto_left(input_object)
self.squash_onto_right(output_object) self.squash_onto_right(output_object)

View file

@ -1606,7 +1606,7 @@ class DirectionOfA2DFunctionAlongABoundary(InputOutputScene):
input_plane.coords_to_point(2.5, -1.5), input_plane.coords_to_point(2.5, -1.5),
) )
rect.replace(line, stretch = True) rect.replace(line, stretch = True)
rect.insert_n_anchor_points(50) rect.insert_n_curves(50)
rect.match_background_image_file(colorings[0]) rect.match_background_image_file(colorings[0])
rect_image = rect.copy() rect_image = rect.copy()
@ -1815,7 +1815,7 @@ class ForeverNarrowingLoop(InputOutputScene):
# circle # circle
circle = Circle(color = WHITE, radius = self.circle_start_radius) circle = Circle(color = WHITE, radius = self.circle_start_radius)
circle.flip(axis = RIGHT) circle.flip(axis = RIGHT)
circle.insert_n_anchor_points(50) circle.insert_n_curves(50)
if self.start_around_target: if self.start_around_target:
circle.move_to(input_plane.coords_to_point(*self.target_coords)) circle.move_to(input_plane.coords_to_point(*self.target_coords))
else: else:
@ -2788,7 +2788,7 @@ class WindingNumbersInInputOutputContext(PathContainingZero):
in_loop = Circle() in_loop = Circle()
in_loop.flip(RIGHT) in_loop.flip(RIGHT)
# in_loop = Square(side_length = 2) # 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( in_loop.move_to(self.input_plane.coords_to_point(
*self.in_loop_center_coords *self.in_loop_center_coords
)) ))

View file

@ -45,7 +45,7 @@ class NumberlineTransformationScene(ZoomedScene):
"color": BLUE, "color": BLUE,
}, },
"output_line_config": {}, "output_line_config": {},
"num_inserted_number_line_anchors": 20, "num_inserted_number_line_curves": 20,
"default_delta_x": 0.1, "default_delta_x": 0.1,
"default_sample_dot_radius": 0.07, "default_sample_dot_radius": 0.07,
"default_sample_dot_colors": [RED, YELLOW], "default_sample_dot_colors": [RED, YELLOW],
@ -76,8 +76,8 @@ class NumberlineTransformationScene(ZoomedScene):
full_config = dict(self.number_line_config) full_config = dict(self.number_line_config)
full_config.update(added_config) full_config.update(added_config)
number_line = NumberLine(**full_config) number_line = NumberLine(**full_config)
number_line.main_line.insert_n_anchor_points( number_line.main_line.insert_n_curves(
self.num_inserted_number_line_anchors self.num_inserted_number_line_curves
) )
number_line.shift(zero_point - number_line.number_to_point(0)) number_line.shift(zero_point - number_line.number_to_point(0))
number_lines.add(number_line) number_lines.add(number_line)
@ -179,8 +179,8 @@ class NumberlineTransformationScene(ZoomedScene):
self.moving_input_line = input_line_copy self.moving_input_line = input_line_copy
input_line_copy.remove(input_line_copy.numbers) input_line_copy.remove(input_line_copy.numbers)
# input_line_copy.set_stroke(width=2) # input_line_copy.set_stroke(width=2)
input_line_copy.main_line.insert_n_anchor_points( input_line_copy.main_line.insert_n_curves(
self.num_inserted_number_line_anchors self.num_inserted_number_line_curves
) )
return AnimationGroup( return AnimationGroup(
self.get_mapping_animation( self.get_mapping_animation(
@ -311,7 +311,7 @@ class NumberlineTransformationScene(ZoomedScene):
# Add miniature number_line # Add miniature number_line
mini_line = self.mini_line = Line(frame.get_left(), frame.get_right()) mini_line = self.mini_line = Line(frame.get_left(), frame.get_right())
mini_line.scale(self.mini_line_scale_factor) 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.match_style(self.input_line.main_line)
mini_line_copy = mini_line.copy() mini_line_copy = mini_line.copy()
zcbr_group.add(mini_line_copy, mini_line) zcbr_group.add(mini_line_copy, mini_line)

View file

@ -1523,7 +1523,7 @@ class ShowLightInThreeDimensions(IntroduceScreen, ThreeDScene):
screens = VGroup( screens = VGroup(
Square(), Square(),
RegularPolygon(8), RegularPolygon(8),
Circle().insert_n_anchor_points(25), Circle().insert_n_curves(25),
) )
for screen in screens: for screen in screens:
screen.set_height(self.screen_height) screen.set_height(self.screen_height)
@ -3883,7 +3883,7 @@ class ThinkBackToHowAmazingThisIs(ThreeDScene):
self.number_line = number_line self.number_line = number_line
def show_giant_circle(self): 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) everything = VGroup(*self.mobjects)
circle = everything.copy() circle = everything.copy()
circle.move_to(ORIGIN) circle.move_to(ORIGIN)

View file

@ -2635,7 +2635,7 @@ class Test(Scene):
def construct(self): def construct(self):
randy = Randolph() randy = Randolph()
necklace = Necklace() necklace = Necklace()
necklace.insert_n_anchor_points(20) necklace.insert_n_curves(20)
# necklace.apply_function( # necklace.apply_function(
# lambda (x, y, z) : x*RIGHT + (y + 0.1*x**2)*UP # lambda (x, y, z) : x*RIGHT + (y + 0.1*x**2)*UP
# ) # )

View file

@ -843,7 +843,7 @@ class CylinderModel(Scene):
shift_val = 0.1 * LEFT + 0.2 * UP shift_val = 0.1 * LEFT + 0.2 * UP
scale_factor = get_norm(RIGHT - shift_val) scale_factor = get_norm(RIGHT - shift_val)
movers = VGroup(self.warped_grid, self.unit_circle) 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 = self.get_stream_lines()
stream_lines.scale(scale_factor) stream_lines.scale(scale_factor)
@ -4493,7 +4493,7 @@ class BroughtToYouBy(PiCreatureScene):
math.move_to(self.pi_creatures) math.move_to(self.pi_creatures)
spiral = Line(0.5 * RIGHT, 0.5 * RIGHT + 70 * UP) 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 from old_projects.zeta import zeta
spiral.apply_complex_function(zeta) spiral.apply_complex_function(zeta)
step = 0.1 step = 0.1

View file

@ -2964,7 +2964,7 @@ class ComplexExponentiationAdderHalf(
) )
line.set_color(YELLOW) line.set_color(YELLOW)
for submob in line: for submob in line:
submob.insert_n_anchor_points(10) submob.insert_n_curves(10)
submob.make_smooth() submob.make_smooth()
circle = VGroup( circle = VGroup(
Circle(), Circle(),
@ -3069,7 +3069,7 @@ class ComplexExponentiationMultiplierHalf(
line.set_color(YELLOW) line.set_color(YELLOW)
line.shift(FRAME_X_RADIUS*LEFT) line.shift(FRAME_X_RADIUS*LEFT)
for submob in line: for submob in line:
submob.insert_n_anchor_points(10) submob.insert_n_curves(10)
submob.make_smooth() submob.make_smooth()
circle = VGroup( circle = VGroup(
Circle(), Circle(),
@ -3110,7 +3110,7 @@ class ComplexExponentiationMultiplierHalf(
arc_line = Line(RIGHT, RIGHT+angle*UP) arc_line = Line(RIGHT, RIGHT+angle*UP)
brace = Brace(arc_line, RIGHT, buff = 0) brace = Brace(arc_line, RIGHT, buff = 0)
for submob in brace.family_members_with_points(): 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 = brace.copy()
curved_brace.shift(LEFT) curved_brace.shift(LEFT)
curved_brace.apply_complex_function( curved_brace.apply_complex_function(

View file

@ -162,7 +162,7 @@ class CircleScene(PiCreatureScene):
def get_unwrapped(self, ring, to_edge = LEFT, **kwargs): def get_unwrapped(self, ring, to_edge = LEFT, **kwargs):
R = ring.R R = ring.R
R_plus_dr = ring.R + ring.dR R_plus_dr = ring.R + ring.dR
n_anchors = ring.get_num_anchor_points() n_anchors = ring.get_num_curves()
result = VMobject() result = VMobject()
result.set_points_as_corners([ result.set_points_as_corners([
interpolate(np.pi*R_plus_dr*LEFT, np.pi*R_plus_dr*RIGHT, a) 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 ring.rect = rect
n_anchors = ring.get_num_anchor_points() n_anchors = ring.get_num_curves()
target = VMobject() target = VMobject()
target.set_points_as_corners([ target.set_points_as_corners([
interpolate(ORIGIN, DOWN, a) interpolate(ORIGIN, DOWN, a)

View file

@ -2218,7 +2218,7 @@ class IntroduceUnitCircleWithSine(GraphScene):
color = YELLOW, color = YELLOW,
) )
line.shift(FRAME_X_RADIUS*RIGHT/3).to_edge(UP) line.shift(FRAME_X_RADIUS*RIGHT/3).to_edge(UP)
line.insert_n_anchor_points(10) line.insert_n_curves(10)
line.make_smooth() line.make_smooth()
arc = Arc( arc = Arc(

View file

@ -1114,7 +1114,7 @@ class WhyPi(PiCreatureScene):
circum.set_color(circle.get_color()) circum.set_color(circle.get_color())
circum.scale(np.pi) circum.scale(np.pi)
circum.next_to(circle, DOWN, LARGE_BUFF) 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() circum.make_jagged()
pi = TexMobject("\\pi") pi = TexMobject("\\pi")
pi.next_to(circum, UP) pi.next_to(circum, UP)

View file

@ -156,7 +156,7 @@ class CircleScene(PiCreatureScene):
def get_unwrapped(self, ring, to_edge = LEFT, **kwargs): def get_unwrapped(self, ring, to_edge = LEFT, **kwargs):
R = ring.R R = ring.R
R_plus_dr = ring.R + ring.dR R_plus_dr = ring.R + ring.dR
n_anchors = ring.get_num_anchor_points() n_anchors = ring.get_num_curves()
result = VMobject() result = VMobject()
result.set_points_as_corners([ result.set_points_as_corners([
interpolate(np.pi*R_plus_dr*LEFT, np.pi*R_plus_dr*RIGHT, a) interpolate(np.pi*R_plus_dr*LEFT, np.pi*R_plus_dr*RIGHT, a)

View file

@ -500,7 +500,7 @@ class SneakyNonlinearTransformationExplained(SneakyNonlinearTransformation):
FRAME_Y_RADIUS*LEFT+FRAME_Y_RADIUS*DOWN, FRAME_Y_RADIUS*LEFT+FRAME_Y_RADIUS*DOWN,
FRAME_Y_RADIUS*RIGHT + FRAME_Y_RADIUS*UP 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.change_anchor_mode("smooth")
diag.set_color(YELLOW) diag.set_color(YELLOW)
self.play(ShowCreation(diag)) self.play(ShowCreation(diag))

View file

@ -2794,7 +2794,7 @@ class WriteComplexExponentialExpression(DrawFrequencyPlot):
for t in (get_t(), TAU) for t in (get_t(), TAU)
] ]
for mob in arc, circle: for mob in arc, circle:
mob.insert_n_anchor_points(20) mob.insert_n_curves(20)
mob.set_stroke(RED, 4) mob.set_stroke(RED, 4)
mob.apply_function( mob.apply_function(
lambda p : plane.number_to_point( lambda p : plane.number_to_point(

View file

@ -1442,8 +1442,8 @@ class DimensionOfQuadraticKoch(DimensionOfKoch):
for order in range(2, self.koch_curve_order+1): for order in range(2, self.koch_curve_order+1):
new_curve = self.get_curve(order) new_curve = self.get_curve(order)
new_curve.move_to(curve) new_curve.move_to(curve)
n_anchors = len(curve.get_anchors()) n_curve_parts = curve.get_num_curves()
curve.insert_n_anchor_points(6*n_anchors) curve.insert_n_curves(6 * n_curve_parts)
curve.make_jagged() curve.make_jagged()
self.play(Transform(curve, new_curve, run_time = 2)) self.play(Transform(curve, new_curve, run_time = 2))
self.wait() self.wait()

View file

@ -483,7 +483,7 @@ class EulerWrites628(Scene):
fill_opacity = 0.3, fill_opacity = 0.3,
fill_color = GREEN, 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.apply_function(lambda p : np.array([p[0], p[1] - 0.005*p[0]**2, p[2]]))
rect.rotate(0.012*TAU) rect.rotate(0.012*TAU)
rect.move_to(image) rect.move_to(image)

View file

@ -3206,7 +3206,7 @@ class SecondProof(SpecialThreeDScene):
ring.set_fill(color, opacity=1) ring.set_fill(color, opacity=1)
ring.set_stroke(color, width=0.5, opacity=1) ring.set_stroke(color, width=0.5, opacity=1)
for piece in ring: for piece in ring:
piece.insert_n_anchor_points(4) piece.insert_n_curves(4)
piece.on_sphere = True piece.on_sphere = True
piece.points = np.array([ piece.points = np.array([
*piece.points[3:-1], *piece.points[3:-1],

View file

@ -134,7 +134,7 @@ class Chaos(Eddy):
y * UP, y * UP + self.width * RIGHT, y * UP, y * UP + self.width * RIGHT,
stroke_width=1 stroke_width=1
) )
line.insert_n_anchor_points(self.n_midpoints) line.insert_n_curves(self.n_midpoints)
line.total_time = random.random() line.total_time = random.random()
delta_h = self.height / (self.n_lines - 1) delta_h = self.height / (self.n_lines - 1)
@ -876,7 +876,7 @@ class HighCurlFieldBreakingLayersLines(HighCurlFieldBreakingLayers):
def get_line(self): def get_line(self):
line = Line(LEFT, RIGHT) line = Line(LEFT, RIGHT)
line.insert_n_anchor_points(500) line.insert_n_curves(500)
line.set_width(5) line.set_width(5)
return line return line

View file

@ -2039,7 +2039,7 @@ class IntroduceDopplerRadar(Scene):
pulse_graph.underlying_function(x), pulse_graph.underlying_function(x),
echo_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 color = WHITE
) )
sum_graph.background_image_file = "blue_yellow_gradient" sum_graph.background_image_file = "blue_yellow_gradient"

View file

@ -984,7 +984,7 @@ class DeformToInterval(ClosedLoopScene):
interval.shift(2*DOWN) interval.shift(2*DOWN)
numbers = interval.get_number_mobjects(0, 1) numbers = interval.get_number_mobjects(0, 1)
line = Line(interval.get_left(), interval.get_right()) 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() line.make_smooth()
self.loop.scale(0.7) self.loop.scale(0.7)
@ -1058,7 +1058,7 @@ class RepresentPairInUnitSquare(ClosedLoopScene):
interval.shift(LEFT) interval.shift(LEFT)
numbers = interval.get_number_mobjects(0, 1) numbers = interval.get_number_mobjects(0, 1)
line = Line(interval.get_left(), interval.get_right()) 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() line.make_smooth()
vert_interval = interval.copy() vert_interval = interval.copy()
square = Square() square = Square()
@ -1270,7 +1270,7 @@ class EndpointsGluedTogether(ClosedLoopScene):
interval.shift(2*DOWN) interval.shift(2*DOWN)
numbers = interval.get_number_mobjects(0, 1) numbers = interval.get_number_mobjects(0, 1)
line = Line(interval.get_left(), interval.get_right()) 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() line.make_smooth()
self.loop.scale(0.7) self.loop.scale(0.7)

View file

@ -37,7 +37,7 @@ class ZetaTransformationScene(ComplexTransformationScene):
for line in mob.family_members_with_points(): for line in mob.family_members_with_points():
#Find point of line cloest to 1 on C #Find point of line cloest to 1 on C
if not isinstance(line, Line): if not isinstance(line, Line):
line.insert_n_anchor_points(self.min_added_anchors) line.insert_n_curves(self.min_added_anchors)
continue continue
p1 = line.get_start()+LEFT p1 = line.get_start()+LEFT
p2 = line.get_end()+LEFT p2 = line.get_end()+LEFT
@ -47,14 +47,14 @@ class ZetaTransformationScene(ComplexTransformationScene):
) )
#See how big this line will become #See how big this line will become
diameter = abs(zeta(complex(*closest_to_one[:2]))) 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), int(self.anchor_density*np.pi*diameter),
self.min_added_anchors, self.min_added_anchors,
self.max_added_anchors, self.max_added_anchors,
) )
num_anchors = line.get_num_anchor_points() num_curves = line.get_num_curves()
if num_anchors < target_num_anchors: if num_curves < target_num_curves:
line.insert_n_anchor_points(target_num_anchors-num_anchors) line.insert_n_curves(target_num_curves-num_curves)
line.make_smooth() line.make_smooth()
def add_extra_plane_lines_for_zeta(self, animate = False, **kwargs): def add_extra_plane_lines_for_zeta(self, animate = False, **kwargs):
@ -2279,7 +2279,7 @@ class IntroduceAnglePreservation(VisualizingSSquared):
color = YELLOW color = YELLOW
) )
arc.shift(intersection_point) arc.shift(intersection_point)
arc.insert_n_anchor_points(10) arc.insert_n_curves(10)
arc.generate_target() arc.generate_target()
input_z = complex(*arc.get_center()[:2]) input_z = complex(*arc.get_center()[:2])
scale_factor = abs(2*input_z) scale_factor = abs(2*input_z)