mirror of
https://github.com/3b1b/manim.git
synced 2025-11-14 22:57:44 +00:00
Merge branch 'master' into lighthouse
This commit is contained in:
commit
ad2b1c3933
11 changed files with 1824 additions and 417 deletions
|
|
@ -128,7 +128,7 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
|
|||
|
||||
downBrace, upBrace = yBraces = TexMobject("||")
|
||||
yBraces.stretch(2, 0)
|
||||
yBraces.rotate(np.pi/2)
|
||||
yBraces.rotate(TAU/4)
|
||||
|
||||
lowerX = self.initial_lower_x
|
||||
lowerY = self.func(lowerX)
|
||||
|
|
@ -256,28 +256,567 @@ class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
|
|||
self.drawGraph()
|
||||
self.solveEquation()
|
||||
|
||||
class FirstSqrtScene(EquationSolver1d):
|
||||
|
||||
# TODO: Perhaps have bullets (pulses) fade out and in at ends of line, instead of jarringly
|
||||
# popping out and in?
|
||||
#
|
||||
# TODO: Perhaps have bullets change color corresponding to a function of their coordinates?
|
||||
# This could involve some merging of functoinality with PiWalker
|
||||
class LinePulser(ContinualAnimation):
|
||||
def __init__(self, line, bullet_template, num_bullets, pulse_time, color_func = None, **kwargs):
|
||||
self.line = line
|
||||
self.num_bullets = num_bullets
|
||||
self.pulse_time = pulse_time
|
||||
self.bullets = [bullet_template.copy() for i in range(num_bullets)]
|
||||
self.color_func = color_func
|
||||
ContinualAnimation.__init__(self, VGroup(line, VGroup(*self.bullets)), **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
alpha = self.external_time % self.pulse_time
|
||||
start = self.line.get_start()
|
||||
end = self.line.get_end()
|
||||
for i in range(self.num_bullets):
|
||||
position = interpolate(start, end,
|
||||
np.true_divide((i + alpha),(self.num_bullets)))
|
||||
self.bullets[i].move_to(position)
|
||||
if self.color_func:
|
||||
self.bullets.set_color(self.color_func(position))
|
||||
|
||||
|
||||
def color_func(alpha):
|
||||
alpha = alpha % 1
|
||||
colors = ["#FF0000", ORANGE, YELLOW, "#00FF00", "#0000FF", "#FF00FF"]
|
||||
num_colors = len(colors)
|
||||
beta = (alpha % (1.0/num_colors)) * num_colors
|
||||
start_index = int(np.floor(num_colors * alpha)) % num_colors
|
||||
end_index = (start_index + 1) % num_colors
|
||||
|
||||
return interpolate_color(colors[start_index], colors[end_index], beta)
|
||||
|
||||
class ArrowCircleTest(Scene):
|
||||
def construct(self):
|
||||
circle_radius = 3
|
||||
circle = Circle(radius = circle_radius, color = WHITE)
|
||||
self.add(circle)
|
||||
|
||||
base_arrow = Arrow(circle_radius * 0.7 * RIGHT, circle_radius * 1.3 * RIGHT)
|
||||
|
||||
def rev_rotate(x, revs):
|
||||
x.rotate(revs * TAU)
|
||||
x.set_color(color_func(revs))
|
||||
return x
|
||||
|
||||
num_arrows = 8 * 3
|
||||
arrows = [rev_rotate(base_arrow.copy(), (np.true_divide(i, num_arrows))) for i in range(num_arrows)]
|
||||
arrows_vgroup = VGroup(*arrows)
|
||||
|
||||
self.play(ShowCreation(arrows_vgroup), run_time = 2.5, rate_func = None)
|
||||
|
||||
self.wait()
|
||||
|
||||
class FuncRotater(Animation):
|
||||
CONFIG = {
|
||||
"x_min" : 0,
|
||||
"x_max" : 2.5,
|
||||
"y_min" : 0,
|
||||
"y_max" : 2.5**2,
|
||||
"graph_origin" : 2*DOWN + 5 * LEFT,
|
||||
"x_axis_width" : 12,
|
||||
"zoom_factor" : 3,
|
||||
"zoomed_canvas_center" : 2.25 * UP + 1.75 * LEFT,
|
||||
"func" : lambda x : x**2,
|
||||
"targetX" : np.sqrt(2),
|
||||
"targetY" : 2,
|
||||
"initial_lower_x" : 1,
|
||||
"initial_upper_x" : 2,
|
||||
"num_iterations" : 10,
|
||||
"iteration_at_which_to_start_zoom" : 3,
|
||||
"graph_label" : "y = x^2",
|
||||
"show_target_line" : True,
|
||||
"rotate_func" : lambda x : x # Func from alpha to revolutions
|
||||
}
|
||||
|
||||
# Perhaps abstract this out into an "Animation updating from original object" class
|
||||
def update_submobject(self, submobject, starting_submobject, alpha):
|
||||
submobject.points = np.array(starting_submobject.points)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
Animation.update_mobject(self, alpha)
|
||||
angle_revs = self.rotate_func(alpha)
|
||||
# We do a clockwise rotation
|
||||
self.mobject.rotate(
|
||||
-angle_revs * TAU,
|
||||
about_point = ORIGIN
|
||||
)
|
||||
self.mobject.set_color(color_func(angle_revs))
|
||||
|
||||
class TestRotater(Scene):
|
||||
def construct(self):
|
||||
test_line = Line(ORIGIN, RIGHT)
|
||||
self.play(FuncRotater(
|
||||
test_line,
|
||||
rotate_func = lambda x : x % 0.25,
|
||||
run_time = 10))
|
||||
|
||||
# TODO: Be careful about clockwise vs. counterclockwise convention throughout!
|
||||
# Make sure this is correct everywhere in resulting video.
|
||||
class OdometerScene(Scene):
|
||||
CONFIG = {
|
||||
"rotate_func" : lambda x : np.sin(x * TAU),
|
||||
"run_time" : 5,
|
||||
"dashed_line_angle" : None,
|
||||
"biased_display_start" : None
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
radius = 1.3
|
||||
circle = Circle(center = ORIGIN, radius = radius)
|
||||
self.add(circle)
|
||||
|
||||
if self.dashed_line_angle:
|
||||
dashed_line = DashedLine(ORIGIN, radius * RIGHT)
|
||||
# Clockwise rotation
|
||||
dashed_line.rotate(-self.dashed_line_angle * TAU, about_point = ORIGIN)
|
||||
self.add(dashed_line)
|
||||
|
||||
num_display = DecimalNumber(0)
|
||||
num_display.move_to(2 * DOWN)
|
||||
|
||||
display_val_bias = 0
|
||||
if self.biased_display_start != None:
|
||||
display_val_bias = self.biased_display_start - self.rotate_func(0)
|
||||
display_func = lambda alpha : self.rotate_func(alpha) + display_val_bias
|
||||
|
||||
base_arrow = Arrow(ORIGIN, RIGHT, buff = 0)
|
||||
|
||||
self.play(
|
||||
FuncRotater(base_arrow, rotate_func = self.rotate_func),
|
||||
ChangingDecimal(num_display, display_func),
|
||||
run_time = self.run_time,
|
||||
rate_func = None)
|
||||
|
||||
def point_to_rev((x, y)):
|
||||
# Warning: np.arctan2 would happily discontinuously returns the value 0 for (0, 0), due to
|
||||
# design choices in the underlying atan2 library call, but for our purposes, this is
|
||||
# illegitimate, and all winding number calculations must be set up to avoid this
|
||||
if (x, y) == (0, 0):
|
||||
print "Error! Angle of (0, 0) computed!"
|
||||
return None
|
||||
return np.true_divide(np.arctan2(y, x), TAU)
|
||||
|
||||
# Returns the value with the same fractional component as x, closest to m
|
||||
def resit_near(x, m):
|
||||
frac_diff = (x - m) % 1
|
||||
if frac_diff > 0.5:
|
||||
frac_diff -= 1
|
||||
return m + frac_diff
|
||||
|
||||
# TODO?: Perhaps use modulus of (uniform) continuity instead of num_checkpoints, calculating
|
||||
# latter as needed from former?
|
||||
def make_alpha_winder(func, start, end, num_checkpoints):
|
||||
check_points = [None for i in range(num_checkpoints)]
|
||||
check_points[0] = func(start)
|
||||
step_size = np.true_divide(end - start, num_checkpoints)
|
||||
for i in range(num_checkpoints - 1):
|
||||
check_points[i + 1] = \
|
||||
resit_near(
|
||||
func(start + (i + 1) * step_size),
|
||||
check_points[i])
|
||||
def return_func(alpha):
|
||||
index = clamp(0, num_checkpoints - 1, int(alpha * num_checkpoints))
|
||||
x = interpolate(start, end, alpha)
|
||||
return resit_near(func(x), check_points[index])
|
||||
return return_func
|
||||
|
||||
def split_interval((a, b)):
|
||||
mid = (a + b)/2.0
|
||||
return ((a, mid), (mid, b))
|
||||
|
||||
class RectangleData():
|
||||
def __init__(self, x_interval, y_interval):
|
||||
self.rect = (x_interval, y_interval)
|
||||
|
||||
def get_top_left(self):
|
||||
return np.array((self.rect[0][0], self.rect[1][1]))
|
||||
|
||||
def get_top_right(self):
|
||||
return np.array((self.rect[0][1], self.rect[1][1]))
|
||||
|
||||
def get_bottom_right(self):
|
||||
return np.array((self.rect[0][1], self.rect[1][0]))
|
||||
|
||||
def get_bottom_left(self):
|
||||
return np.array((self.rect[0][0], self.rect[1][0]))
|
||||
|
||||
def get_top(self):
|
||||
return (self.get_top_left(), self.get_top_right())
|
||||
|
||||
def get_right(self):
|
||||
return (self.get_top_right(), self.get_bottom_right())
|
||||
|
||||
def get_bottom(self):
|
||||
return (self.get_bottom_right(), self.get_bottom_left())
|
||||
|
||||
def get_left(self):
|
||||
return (self.get_bottom_left(), self.get_top_left())
|
||||
|
||||
def splits_on_dim(self, dim):
|
||||
x_interval = self.rect[0]
|
||||
y_interval = self.rect[1]
|
||||
|
||||
# TODO: Can refactor the following; will do later
|
||||
if dim == 0:
|
||||
return_data = [RectangleData(new_interval, y_interval) for new_interval in split_interval(x_interval)]
|
||||
elif dim == 1:
|
||||
return_data = [RectangleData(x_interval, new_interval) for new_interval in split_interval(y_interval)]
|
||||
else:
|
||||
print "RectangleData.splits_on_dim passed illegitimate dimension!"
|
||||
|
||||
return tuple(return_data)
|
||||
|
||||
def split_line_on_dim(self, dim):
|
||||
x_interval = self.rect[0]
|
||||
y_interval = self.rect[1]
|
||||
|
||||
if dim == 0:
|
||||
sides = (self.get_top(), self.get_bottom())
|
||||
elif dim == 1:
|
||||
sides = (self.get_left(), self.get_right())
|
||||
else:
|
||||
print "RectangleData.split_line_on_dim passed illegitimate dimension!"
|
||||
|
||||
return tuple([mid(x, y) for (x, y) in sides])
|
||||
|
||||
def complex_to_pair(c):
|
||||
return (c.real, c.imag)
|
||||
|
||||
def plane_poly_with_roots(*points):
|
||||
def f((x, y)):
|
||||
return complex_to_pair(np.prod([complex(x, y) - complex(a,b) for (a,b) in points]))
|
||||
return f
|
||||
|
||||
def plane_func_from_complex_func(f):
|
||||
return lambda (x, y) : complex_to_pair(f(complex(x,y)))
|
||||
|
||||
def point_func_from_complex_func(f):
|
||||
return lambda (x, y, z): complex_to_R3(f(complex(x, y)))
|
||||
|
||||
empty_animation = Animation(Mobject())
|
||||
def EmptyAnimation():
|
||||
return empty_animation
|
||||
|
||||
class WalkerAnimation(Animation):
|
||||
CONFIG = {
|
||||
"walk_func" : None, # Must be initialized to use
|
||||
"remover" : True,
|
||||
"rate_func" : None,
|
||||
"coords_to_point" : None
|
||||
}
|
||||
|
||||
def __init__(self, walk_func, rev_func, coords_to_point, **kwargs):
|
||||
self.walk_func = walk_func
|
||||
self.rev_func = rev_func
|
||||
self.coords_to_point = coords_to_point
|
||||
self.compound_walker = VGroup()
|
||||
self.compound_walker.walker = PiCreature(color = RED)
|
||||
self.compound_walker.walker.scale(0.35)
|
||||
self.compound_walker.arrow = Arrow(ORIGIN, RIGHT) #, buff = 0)
|
||||
self.compound_walker.digest_mobject_attrs()
|
||||
Animation.__init__(self, self.compound_walker, **kwargs)
|
||||
|
||||
# Perhaps abstract this out into an "Animation updating from original object" class
|
||||
def update_submobject(self, submobject, starting_submobject, alpha):
|
||||
submobject.points = np.array(starting_submobject.points)
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
Animation.update_mobject(self, alpha)
|
||||
cur_x, cur_y = cur_coords = self.walk_func(alpha)
|
||||
self.mobject.walker.move_to(self.coords_to_point(cur_x, cur_y))
|
||||
rev = self.rev_func(cur_coords)
|
||||
self.mobject.walker.set_color(color_func(rev))
|
||||
self.mobject.arrow.set_color(color_func(rev))
|
||||
self.mobject.arrow.rotate(
|
||||
rev * TAU,
|
||||
about_point = ORIGIN #self.mobject.arrow.get_start()
|
||||
)
|
||||
|
||||
def LinearWalker(start_coords, end_coords, coords_to_point, rev_func, **kwargs):
|
||||
walk_func = lambda alpha : interpolate(start_coords, end_coords, alpha)
|
||||
return WalkerAnimation(
|
||||
walk_func = walk_func,
|
||||
coords_to_point = coords_to_point,
|
||||
rev_func = rev_func,
|
||||
**kwargs)
|
||||
|
||||
class PiWalker(Scene):
|
||||
CONFIG = {
|
||||
"func" : plane_func_from_complex_func(lambda c : c**2),
|
||||
"walk_coords" : [],
|
||||
"step_run_time" : 1
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
|
||||
rev_func = lambda p : point_to_rev(self.func(p))
|
||||
|
||||
num_plane = NumberPlane()
|
||||
num_plane.fade()
|
||||
self.add(num_plane)
|
||||
|
||||
walk_coords = self.walk_coords
|
||||
for i in range(len(walk_coords)):
|
||||
start_x, start_y = start_coords = walk_coords[i]
|
||||
start_point = num_plane.coords_to_point(start_x, start_y)
|
||||
end_x, end_y = end_coords = walk_coords[(i + 1) % len(walk_coords)]
|
||||
end_point = num_plane.coords_to_point(end_x, end_y)
|
||||
self.play(
|
||||
LinearWalker(
|
||||
start_coords = start_coords,
|
||||
end_coords = end_coords,
|
||||
coords_to_point = num_plane.coords_to_point,
|
||||
rev_func = rev_func,
|
||||
remover = (i < len(walk_coords) - 1)
|
||||
),
|
||||
ShowCreation(Line(start_point, end_point)),
|
||||
run_time = self.step_run_time)
|
||||
|
||||
|
||||
self.wait()
|
||||
|
||||
class PiWalkerRect(PiWalker):
|
||||
CONFIG = {
|
||||
"start_x" : -1,
|
||||
"start_y" : 1,
|
||||
"walk_width" : 2,
|
||||
"walk_height" : 2,
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
TL = np.array((self.start_x, self.start_y))
|
||||
TR = TL + (self.walk_width, 0)
|
||||
BR = TR + (0, -self.walk_height)
|
||||
BL = BR + (-self.walk_width, 0)
|
||||
self.walk_coords = [TL, TR, BR, BL]
|
||||
PiWalker.setup(self)
|
||||
|
||||
class PiWalkerCircle(PiWalker):
|
||||
CONFIG = {
|
||||
"radius" : 1,
|
||||
"num_steps" : 100,
|
||||
"step_run_time" : 0.01
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
r = self.radius
|
||||
N = self.num_steps
|
||||
self.walk_coords = [r * np.array((np.cos(i * TAU/N), np.sin(i * TAU/N))) for i in range(N)]
|
||||
PiWalker.setup(self)
|
||||
|
||||
# TODO: Perhaps restructure this to avoid using AnimationGroup, and instead
|
||||
# use lists of animations or lists or other such data, to be merged and processed into parallel
|
||||
# animations later
|
||||
class EquationSolver2d(Scene):
|
||||
CONFIG = {
|
||||
"func" : plane_poly_with_roots((1, 2), (-1, 3)),
|
||||
"initial_lower_x" : -5.1,
|
||||
"initial_upper_x" : 5.1,
|
||||
"initial_lower_y" : -3.1,
|
||||
"initial_upper_y" : 3.1,
|
||||
"num_iterations" : 5,
|
||||
"num_checkpoints" : 10,
|
||||
# TODO: Consider adding a "find_all_roots" flag, which could be turned off
|
||||
# to only explore one of the two candidate subrectangles when both are viable
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
num_plane = NumberPlane()
|
||||
num_plane.fade()
|
||||
self.add(num_plane)
|
||||
|
||||
rev_func = lambda p : point_to_rev(self.func(p))
|
||||
|
||||
def Animate2dSolver(cur_depth, rect, dim_to_split):
|
||||
if cur_depth >= self.num_iterations:
|
||||
return EmptyAnimation()
|
||||
|
||||
def draw_line_return_wind(start, end, start_wind):
|
||||
alpha_winder = make_alpha_winder(rev_func, start, end, self.num_checkpoints)
|
||||
a0 = alpha_winder(0)
|
||||
rebased_winder = lambda alpha: alpha_winder(alpha) - a0 + start_wind
|
||||
line = Line(num_plane.coords_to_point(*start), num_plane.coords_to_point(*end),
|
||||
stroke_width = 5,
|
||||
color = RED)
|
||||
thin_line = line.copy()
|
||||
thin_line.set_stroke(width = 1)
|
||||
walker_anim = LinearWalker(
|
||||
start_coords = start,
|
||||
end_coords = end,
|
||||
coords_to_point = num_plane.coords_to_point,
|
||||
rev_func = rev_func,
|
||||
remover = True
|
||||
)
|
||||
line_draw_anim = AnimationGroup(ShowCreation(line, rate_func = None), walker_anim,
|
||||
run_time = 2)
|
||||
anim = Succession(
|
||||
line_draw_anim,
|
||||
Transform, line, thin_line
|
||||
)
|
||||
return (anim, rebased_winder(1))
|
||||
|
||||
wind_so_far = 0
|
||||
anim = EmptyAnimation()
|
||||
sides = [
|
||||
rect.get_top(),
|
||||
rect.get_right(),
|
||||
rect.get_bottom(),
|
||||
rect.get_left()
|
||||
]
|
||||
for (start, end) in sides:
|
||||
(next_anim, wind_so_far) = draw_line_return_wind(start, end, wind_so_far)
|
||||
anim = Succession(anim, next_anim)
|
||||
|
||||
total_wind = round(wind_so_far)
|
||||
|
||||
if total_wind == 0:
|
||||
coords = [
|
||||
rect.get_top_left(),
|
||||
rect.get_top_right(),
|
||||
rect.get_bottom_right(),
|
||||
rect.get_bottom_left()
|
||||
]
|
||||
points = [num_plane.coords_to_point(x, y) for (x, y) in coords]
|
||||
# TODO: Maybe use diagonal lines or something to fill in rectangles indicating
|
||||
# their "Nothing here" status?
|
||||
fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = RED)
|
||||
return Succession(anim, FadeIn(fill_rect))
|
||||
else:
|
||||
(sub_rect1, sub_rect2) = rect.splits_on_dim(dim_to_split)
|
||||
sub_rects = [sub_rect1, sub_rect2]
|
||||
sub_anims = [
|
||||
Animate2dSolver(
|
||||
cur_depth = cur_depth + 1,
|
||||
rect = sub_rect,
|
||||
dim_to_split = 1 - dim_to_split
|
||||
)
|
||||
for sub_rect in sub_rects
|
||||
]
|
||||
mid_line_coords = rect.split_line_on_dim(dim_to_split)
|
||||
mid_line_points = [num_plane.coords_to_point(x, y) for (x, y) in mid_line_coords]
|
||||
mid_line = DashedLine(*mid_line_points)
|
||||
return Succession(anim,
|
||||
ShowCreation(mid_line),
|
||||
# FadeOut(mid_line), # TODO: Can change timing so this fades out at just the time it would be overdrawn
|
||||
AnimationGroup(*sub_anims)
|
||||
)
|
||||
|
||||
lower_x = self.initial_lower_x
|
||||
upper_x = self.initial_upper_x
|
||||
lower_y = self.initial_lower_y
|
||||
upper_y = self.initial_upper_y
|
||||
|
||||
x_interval = (lower_x, upper_x)
|
||||
y_interval = (lower_y, upper_y)
|
||||
|
||||
rect = RectangleData(x_interval, y_interval)
|
||||
|
||||
anim = Animate2dSolver(
|
||||
cur_depth = 0,
|
||||
rect = rect,
|
||||
dim_to_split = 0,
|
||||
)
|
||||
|
||||
self.play(anim)
|
||||
|
||||
self.wait()
|
||||
|
||||
#############
|
||||
# Above are mostly general tools; here, we list, in order, finished or near-finished scenes
|
||||
|
||||
class FirstSqrtScene(EquationSolver1d):
|
||||
CONFIG = {
|
||||
"x_min" : 0,
|
||||
"x_max" : 2.5,
|
||||
"y_min" : 0,
|
||||
"y_max" : 2.5**2,
|
||||
"graph_origin" : 2*DOWN + 5 * LEFT,
|
||||
"x_axis_width" : 12,
|
||||
"zoom_factor" : 3,
|
||||
"zoomed_canvas_center" : 2.25 * UP + 1.75 * LEFT,
|
||||
"func" : lambda x : x**2,
|
||||
"targetX" : np.sqrt(2),
|
||||
"targetY" : 2,
|
||||
"initial_lower_x" : 1,
|
||||
"initial_upper_x" : 2,
|
||||
"num_iterations" : 10,
|
||||
"iteration_at_which_to_start_zoom" : 3,
|
||||
"graph_label" : "y = x^2",
|
||||
"show_target_line" : True,
|
||||
}
|
||||
|
||||
# TODO: Pi creatures intrigued
|
||||
|
||||
class ComplexPlaneIs2d(Scene):
|
||||
def construct(self):
|
||||
com_plane = ComplexPlane()
|
||||
self.add(com_plane)
|
||||
# TODO: Add labels to axes, specific complex points
|
||||
self.wait()
|
||||
|
||||
class NumberLineScene(Scene):
|
||||
def construct(self):
|
||||
num_line = NumberLine()
|
||||
self.add(num_line)
|
||||
# TODO: Add labels, arrows, specific points
|
||||
self.wait()
|
||||
|
||||
border_color = PURPLE_E
|
||||
inner_color = RED
|
||||
stroke_width = 10
|
||||
|
||||
left_point = num_line.number_to_point(-1)
|
||||
right_point = num_line.number_to_point(1)
|
||||
interval_1d = Line(left_point, right_point,
|
||||
stroke_color = inner_color, stroke_width = stroke_width)
|
||||
left_dot = Dot(left_point, stroke_width = stroke_width, color = border_color)
|
||||
right_dot = Dot(right_point, stroke_width = stroke_width, color = border_color)
|
||||
endpoints_1d = VGroup(left_dot, right_dot)
|
||||
full_1d = VGroup(interval_1d, endpoints_1d)
|
||||
self.play(ShowCreation(full_1d))
|
||||
self.wait()
|
||||
|
||||
num_plane = NumberPlane()
|
||||
|
||||
random_points = [UP + LEFT, UP + RIGHT, DOWN + RIGHT, DOWN + LEFT]
|
||||
|
||||
border_2d = Polygon(
|
||||
*random_points,
|
||||
stroke_color = border_color,
|
||||
stroke_width = stroke_width)
|
||||
|
||||
filling_2d = Polygon(
|
||||
*random_points,
|
||||
fill_color = inner_color,
|
||||
fill_opacity = 0.8,
|
||||
stroke_width = stroke_width)
|
||||
full_2d = VGroup(filling_2d, border_2d)
|
||||
|
||||
self.play(
|
||||
FadeOut(num_line),
|
||||
FadeIn(num_plane),
|
||||
ReplacementTransform(full_1d, full_2d))
|
||||
|
||||
self.wait()
|
||||
|
||||
class Initial2dFuncScene(Scene):
|
||||
|
||||
def setup(self):
|
||||
left_camera = Camera(**self.camera_config)
|
||||
right_camera = MappingCamera(
|
||||
mapping_func = point_func_from_complex_func(lambda c : np.exp(c)),
|
||||
**self.camera_config)
|
||||
split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config)
|
||||
self.camera = split_screen_camera
|
||||
|
||||
def construct(self):
|
||||
num_plane = NumberPlane()
|
||||
num_plane.fade()
|
||||
self.add(num_plane)
|
||||
points = [LEFT + DOWN, RIGHT + DOWN, LEFT + UP, RIGHT + UP]
|
||||
for i in range(len(points) - 1):
|
||||
line = Line(points[i], points[i + 1], color = RED)
|
||||
self.play(ShowCreation(line))
|
||||
|
||||
# TODO: Illustrations for introducing domain coloring
|
||||
|
||||
# TODO: Bunch of Pi walker scenes
|
||||
|
||||
# TODO: An odometer scene when introducing winding numbers
|
||||
|
||||
class SecondSqrtScene(FirstSqrtScene, ReconfigurableScene):
|
||||
# TODO: Don't bother with ReconfigurableScene; just use new config from start
|
||||
|
||||
def setup(self):
|
||||
FirstSqrtScene.setup(self)
|
||||
|
|
@ -298,26 +837,6 @@ class SecondSqrtScene(FirstSqrtScene, ReconfigurableScene):
|
|||
graph_origin = newOrigin)
|
||||
self.solveEquation()
|
||||
|
||||
# TODO: Perhaps have bullets (pulses) fade out and in at ends of line, instead of jarringly
|
||||
# popping out and in?
|
||||
#
|
||||
# TODO: Perhaps have bullets change color corresponding to a function of their coordinates?
|
||||
class LinePulser(ContinualAnimation):
|
||||
def __init__(self, line, bullet_template, num_bullets, pulse_time, **kwargs):
|
||||
self.line = line
|
||||
self.num_bullets = num_bullets
|
||||
self.pulse_time = pulse_time
|
||||
self.bullets = [bullet_template.copy() for i in range(num_bullets)]
|
||||
ContinualAnimation.__init__(self, VGroup(line, VGroup(*self.bullets)), **kwargs)
|
||||
|
||||
def update_mobject(self, dt):
|
||||
alpha = self.external_time % self.pulse_time
|
||||
start = self.line.get_start()
|
||||
end = self.line.get_end()
|
||||
for i in range(self.num_bullets):
|
||||
self.bullets[i].move_to(interpolate(start, end,
|
||||
np.true_divide((i + alpha),(self.num_bullets))))
|
||||
|
||||
class LoopSplitScene(Scene):
|
||||
|
||||
def PulsedLine(self, start, end, bullet_template, num_bullets = 4, pulse_time = 1, **kwargs):
|
||||
|
|
@ -327,6 +846,8 @@ class LoopSplitScene(Scene):
|
|||
|
||||
def construct(self):
|
||||
num_plane = NumberPlane(color = LIGHT_GREY, stroke_width = 1)
|
||||
|
||||
# We actually don't want to highlight
|
||||
num_plane.axes.set_stroke(color = WHITE, width = 2)
|
||||
num_plane.fade()
|
||||
self.add(num_plane)
|
||||
|
|
@ -427,6 +948,7 @@ class LoopSplitScene(Scene):
|
|||
|
||||
self.wait()
|
||||
|
||||
# Is there a way to abstract this into a general process to derive a new mapped scene from an old scene?
|
||||
class LoopSplitSceneMapped(LoopSplitScene):
|
||||
|
||||
def setup(self):
|
||||
|
|
@ -437,318 +959,67 @@ class LoopSplitSceneMapped(LoopSplitScene):
|
|||
split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config)
|
||||
self.camera = split_screen_camera
|
||||
|
||||
class NumberLineScene(Scene):
|
||||
def construct(self):
|
||||
num_line = NumberLine()
|
||||
self.add(num_line)
|
||||
self.wait()
|
||||
|
||||
interval_1d = Line(num_line.number_to_point(-1), num_line.number_to_point(1),
|
||||
stroke_color = RED, stroke_width = 10)
|
||||
self.play(ShowCreation(interval_1d))
|
||||
self.wait()
|
||||
|
||||
num_plane = NumberPlane()
|
||||
|
||||
random_points = [UP + LEFT, 2 * UP, RIGHT, DOWN, DOWN + RIGHT, LEFT]
|
||||
|
||||
interval_2d = Polygon(
|
||||
*random_points,
|
||||
stroke_color = RED,
|
||||
stroke_width = 10)
|
||||
# TODO: Turn this into a more complicated, curvy loop?
|
||||
# TODO: Illustrate borders and filled interiors with a particular color
|
||||
# on both 1d and 2d region?
|
||||
|
||||
self.play(
|
||||
FadeOut(num_line),
|
||||
FadeIn(num_plane),
|
||||
ReplacementTransform(interval_1d, interval_2d))
|
||||
|
||||
self.wait()
|
||||
|
||||
def color_func(alpha):
|
||||
alpha = alpha % 1
|
||||
colors = ["#FF0000", ORANGE, YELLOW, "#00FF00", "#0000FF", "#FF00FF"]
|
||||
num_colors = len(colors)
|
||||
beta = (alpha % (1.0/num_colors)) * num_colors
|
||||
start_index = int(np.floor(num_colors * alpha)) % num_colors
|
||||
end_index = (start_index + 1) % num_colors
|
||||
|
||||
return interpolate_color(colors[start_index], colors[end_index], beta)
|
||||
|
||||
class ArrowCircleTest(Scene):
|
||||
def construct(self):
|
||||
circle_radius = 3
|
||||
circle = Circle(radius = circle_radius, color = WHITE)
|
||||
self.add(circle)
|
||||
|
||||
base_arrow = Arrow(circle_radius * 0.7 * RIGHT, circle_radius * 1.3 * RIGHT)
|
||||
|
||||
def rev_rotate(x, revs):
|
||||
x.rotate(revs * 2 * np.pi)
|
||||
x.set_color(color_func(revs))
|
||||
return x
|
||||
|
||||
num_arrows = 8 * 3
|
||||
arrows = [rev_rotate(base_arrow.copy(), (np.true_divide(i, num_arrows))) for i in range(num_arrows)]
|
||||
arrows_vgroup = VGroup(*arrows)
|
||||
|
||||
self.play(ShowCreation(arrows_vgroup), run_time = 2.5, rate_func = None)
|
||||
|
||||
self.wait()
|
||||
|
||||
class FuncRotater(Animation):
|
||||
# TODO: Perhaps do extra illustration of zooming out and winding around a large circle,
|
||||
# to illustrate relation between degree and large-scale winding number
|
||||
class FundThmAlg(EquationSolver2d):
|
||||
CONFIG = {
|
||||
"rotate_func" : lambda x : x # Func from alpha to revolutions
|
||||
"func" : plane_poly_with_roots((1, 2), (-1, 3), (-1, 3)),
|
||||
"num_iterations" : 1,
|
||||
}
|
||||
|
||||
# Perhaps abstract this out into an "Animation from base object" class
|
||||
def update_submobject(self, submobject, starting_submobject, alpha):
|
||||
submobject.points = np.array(starting_submobject.points)
|
||||
# TODO: Borsuk-Ulam visuals
|
||||
# Note: May want to do an ordinary square scene, then mapping func it into a circle
|
||||
# class BorsukUlamScene(PiWalker):
|
||||
|
||||
def update_mobject(self, alpha):
|
||||
Animation.update_mobject(self, alpha)
|
||||
angle_revs = self.rotate_func(alpha)
|
||||
self.mobject.rotate(
|
||||
angle_revs * 2 * np.pi,
|
||||
)
|
||||
self.mobject.set_color(color_func(angle_revs))
|
||||
# 3-way scene of "Good enough"-illustrating odometers; to be composed in Premiere
|
||||
left_func = lambda x : x**2 - x + 1
|
||||
diff_func = lambda x : np.cos(1.4 * (x - 0.1) * (np.log(x + 0.1) - 0.3) * TAU)/2.1
|
||||
|
||||
class TestRotater(Scene):
|
||||
def construct(self):
|
||||
test_line = Line(ORIGIN, RIGHT)
|
||||
self.play(FuncRotater(
|
||||
test_line,
|
||||
rotate_func = lambda x : x % 0.25,
|
||||
run_time = 10))
|
||||
|
||||
class OdometerScene(Scene):
|
||||
class LeftOdometer(OdometerScene):
|
||||
CONFIG = {
|
||||
"rotate_func" : lambda x : np.sin(x * 2 * np.pi),
|
||||
"run_time" : 5
|
||||
"rotate_func" : left_func,
|
||||
"biased_display_start" : 0
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
base_arrow = Arrow(ORIGIN, RIGHT)
|
||||
circle = Circle(center = ORIGIN, radius = 1.3)
|
||||
self.add(circle)
|
||||
num_display = DecimalNumber(0)
|
||||
num_display.move_to(2 * DOWN)
|
||||
self.play(
|
||||
FuncRotater(base_arrow, rotate_func = self.rotate_func),
|
||||
ChangingDecimal(num_display, self.rotate_func),
|
||||
run_time = self.run_time,
|
||||
rate_func = None)
|
||||
|
||||
def point_to_rev((x, y)):
|
||||
# Warning: np.arctan2 would happily discontinuously returns the value 0 for (0, 0), due to
|
||||
# design choices in the underlying atan2 library call, but for our purposes, this is
|
||||
# illegitimate, and all winding number calculations must be set up to avoid this
|
||||
if (x, y) == (0, 0):
|
||||
print "Error! Angle of (0, 0) computed!"
|
||||
return None
|
||||
return np.true_divide(np.arctan2(y, x), 2 * np.pi)
|
||||
|
||||
# Returns the value with the same fractional component as x, closest to m
|
||||
def resit_near(x, m):
|
||||
frac_diff = (x - m) % 1
|
||||
if frac_diff > 0.5:
|
||||
frac_diff -= 1
|
||||
return m + frac_diff
|
||||
|
||||
# TODO?: Perhaps use modulus of (uniform) continuity instead of num_checkpoints, calculating
|
||||
# latter as needed from former?
|
||||
def make_alpha_winder(func, start, end, num_checkpoints):
|
||||
check_points = [None for i in range(num_checkpoints)]
|
||||
check_points[0] = func(start)
|
||||
step_size = np.true_divide(end - start, num_checkpoints)
|
||||
for i in range(num_checkpoints - 1):
|
||||
check_points[i + 1] = \
|
||||
resit_near(
|
||||
func(start + (i + 1) * step_size),
|
||||
check_points[i])
|
||||
def return_func(alpha):
|
||||
index = clamp(0, num_checkpoints - 1, int(alpha * num_checkpoints))
|
||||
x = interpolate(start, end, alpha)
|
||||
return resit_near(func(x), check_points[index])
|
||||
return return_func
|
||||
|
||||
def split_interval((a, b)):
|
||||
mid = (a + b)/2.0
|
||||
return ((a, mid), (mid, b))
|
||||
|
||||
class RectangleData():
|
||||
def __init__(self, x_interval, y_interval):
|
||||
self.rect = (x_interval, y_interval)
|
||||
|
||||
def get_top_left(self):
|
||||
return np.array((self.rect[0][0], self.rect[1][1]))
|
||||
|
||||
def get_top_right(self):
|
||||
return np.array((self.rect[0][1], self.rect[1][1]))
|
||||
|
||||
def get_bottom_right(self):
|
||||
return np.array((self.rect[0][1], self.rect[1][0]))
|
||||
|
||||
def get_bottom_left(self):
|
||||
return np.array((self.rect[0][0], self.rect[1][0]))
|
||||
|
||||
def get_top(self):
|
||||
return (self.get_top_left(), self.get_top_right())
|
||||
|
||||
def get_right(self):
|
||||
return (self.get_top_right(), self.get_bottom_right())
|
||||
|
||||
def get_bottom(self):
|
||||
return (self.get_bottom_right(), self.get_bottom_left())
|
||||
|
||||
def get_left(self):
|
||||
return (self.get_bottom_left(), self.get_top_left())
|
||||
|
||||
def splits_on_dim(self, dim):
|
||||
x_interval = self.rect[0]
|
||||
y_interval = self.rect[1]
|
||||
|
||||
# TODO: Can refactor the following; will do later
|
||||
if dim == 0:
|
||||
return_data = [RectangleData(new_interval, y_interval) for new_interval in split_interval(x_interval)]
|
||||
elif dim == 1:
|
||||
return_data = [RectangleData(x_interval, new_interval) for new_interval in split_interval(y_interval)]
|
||||
else:
|
||||
print "RectangleData.splits_on_dim passed illegitimate dimension!"
|
||||
|
||||
return tuple(return_data)
|
||||
|
||||
def split_line_on_dim(self, dim):
|
||||
x_interval = self.rect[0]
|
||||
y_interval = self.rect[1]
|
||||
|
||||
if dim == 0:
|
||||
sides = (self.get_top(), self.get_bottom())
|
||||
elif dim == 1:
|
||||
sides = (self.get_left(), self.get_right())
|
||||
|
||||
return tuple([mid(x, y) for (x, y) in sides])
|
||||
|
||||
def complex_to_pair(c):
|
||||
return (c.real, c.imag)
|
||||
|
||||
def plane_poly_with_roots(*points):
|
||||
def f((x, y)):
|
||||
return complex_to_pair(np.prod([complex(x, y) - complex(a,b) for (a,b) in points]))
|
||||
return f
|
||||
|
||||
def plane_func_from_complex_func(f):
|
||||
return lambda (x, y) : complex_to_pair(f(complex(x,y)))
|
||||
|
||||
empty_animation = Animation(Mobject())
|
||||
def EmptyAnimation():
|
||||
return empty_animation
|
||||
|
||||
# TODO: Perhaps restructure this to avoid using AnimationGroup/UnsyncedParallels, and instead
|
||||
# use lists of animations or lists or other such data, to be merged and processed into parallel
|
||||
# animations later
|
||||
class EquationSolver2d(Scene):
|
||||
class RightOdometer(OdometerScene):
|
||||
CONFIG = {
|
||||
"func" : plane_poly_with_roots((1, 2), (-1, 3)),
|
||||
"initial_lower_x" : -5.1,
|
||||
"initial_upper_x" : 5.1,
|
||||
"initial_lower_y" : -3.1,
|
||||
"initial_upper_y" : 3.1,
|
||||
"num_iterations" : 5,
|
||||
"num_checkpoints" : 10,
|
||||
# TODO: Consider adding a "find_all_roots" flag, which could be turned off
|
||||
# to only explore one of the two candidate subrectangles when both are viable
|
||||
"rotate_func" : lambda x : left_func(x) + diff_func(x),
|
||||
"biased_display_start" : 0
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
num_plane = NumberPlane()
|
||||
num_plane.fade()
|
||||
self.add(num_plane)
|
||||
class DiffOdometer(OdometerScene):
|
||||
CONFIG = {
|
||||
"rotate_func" : diff_func,
|
||||
"dashed_line_angle" : 0.5,
|
||||
"biased_display_start" : 0
|
||||
}
|
||||
|
||||
rev_func = lambda p : point_to_rev(self.func(p))
|
||||
# TODO: Brouwer's fixed point theorem visuals
|
||||
|
||||
def Animate2dSolver(cur_depth, rect, dim_to_split):
|
||||
if cur_depth >= self.num_iterations:
|
||||
return EmptyAnimation()
|
||||
# TODO: Pi creatures wide-eyed in amazement
|
||||
|
||||
def draw_line_return_wind(start, end, start_wind):
|
||||
alpha_winder = make_alpha_winder(rev_func, start, end, self.num_checkpoints)
|
||||
a0 = alpha_winder(0)
|
||||
rebased_winder = lambda alpha: alpha_winder(alpha) - a0 + start_wind
|
||||
line = Line(num_plane.coords_to_point(*start), num_plane.coords_to_point(*end),
|
||||
stroke_width = 5,
|
||||
color = RED)
|
||||
thin_line = line.copy()
|
||||
thin_line.set_stroke(width = 1)
|
||||
anim = Succession(
|
||||
ShowCreation, line,
|
||||
Transform, line, thin_line
|
||||
)
|
||||
return (anim, rebased_winder(1))
|
||||
#################
|
||||
|
||||
wind_so_far = 0
|
||||
anim = EmptyAnimation()
|
||||
sides = [
|
||||
rect.get_top(),
|
||||
rect.get_right(),
|
||||
rect.get_bottom(),
|
||||
rect.get_left()
|
||||
]
|
||||
for (start, end) in sides:
|
||||
(next_anim, wind_so_far) = draw_line_return_wind(start, end, wind_so_far)
|
||||
anim = Succession(anim, next_anim)
|
||||
# TODOs, from easiest to hardest:
|
||||
|
||||
total_wind = round(wind_so_far)
|
||||
# Minor fiddling with little things in each animation; placements, colors, timing
|
||||
|
||||
if total_wind == 0:
|
||||
coords = [
|
||||
rect.get_top_left(),
|
||||
rect.get_top_right(),
|
||||
rect.get_bottom_right(),
|
||||
rect.get_bottom_left()
|
||||
]
|
||||
points = [num_plane.coords_to_point(x, y) for (x, y) in coords]
|
||||
fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = RED)
|
||||
return Succession(anim, FadeIn(fill_rect))
|
||||
else:
|
||||
(sub_rect1, sub_rect2) = rect.splits_on_dim(dim_to_split)
|
||||
sub_rects = [sub_rect1, sub_rect2]
|
||||
sub_anims = [
|
||||
Animate2dSolver(
|
||||
cur_depth = cur_depth + 1,
|
||||
rect = sub_rect,
|
||||
dim_to_split = 1 - dim_to_split
|
||||
)
|
||||
for sub_rect in sub_rects
|
||||
]
|
||||
mid_line_coords = rect.split_line_on_dim(dim_to_split)
|
||||
mid_line_points = [num_plane.coords_to_point(x, y) for (x, y) in mid_line_coords]
|
||||
mid_line = DashedLine(*mid_line_points)
|
||||
return Succession(anim,
|
||||
ShowCreation(mid_line),
|
||||
FadeOut(mid_line),
|
||||
UnsyncedParallel(*sub_anims)
|
||||
)
|
||||
# Odometer/swinging arrows stuff
|
||||
|
||||
lower_x = self.initial_lower_x
|
||||
upper_x = self.initial_upper_x
|
||||
lower_y = self.initial_lower_y
|
||||
upper_y = self.initial_upper_y
|
||||
# Writing new Pi creature walker scenes off of general template
|
||||
|
||||
x_interval = (lower_x, upper_x)
|
||||
y_interval = (lower_y, upper_y)
|
||||
# Split screen illustration of 2d function (before domain coloring)
|
||||
|
||||
rect = RectangleData(x_interval, y_interval)
|
||||
# Generalizing Pi color walker stuff/making bullets on pulsing lines change colors dynamically according to function traced out
|
||||
|
||||
anim = Animate2dSolver(
|
||||
cur_depth = 0,
|
||||
rect = rect,
|
||||
dim_to_split = 0,
|
||||
)
|
||||
# ----
|
||||
|
||||
self.play(anim)
|
||||
# Pi creature emotion stuff
|
||||
|
||||
self.wait()
|
||||
# BFT visuals
|
||||
|
||||
# Borsuk-Ulam visuals
|
||||
|
||||
# Domain coloring
|
||||
|
||||
# FIN
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -460,11 +460,3 @@ class AnimationGroup(Animation):
|
|||
def update_mobject(self, alpha):
|
||||
for anim in self.sub_anims:
|
||||
anim.update(alpha)
|
||||
|
||||
# Parallel animations where shorter animations are not stretched out to match the longest
|
||||
class UnsyncedParallel(AnimationGroup):
|
||||
def __init__(self, *sub_anims, **kwargs):
|
||||
digest_config(self, kwargs, locals())
|
||||
self.run_time = max([a.run_time for a in sub_anims])
|
||||
everything = Mobject(*[a.mobject for a in sub_anims])
|
||||
Animation.__init__(self, everything, **kwargs)
|
||||
1
camera/__init__.py
Normal file
1
camera/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from camera import *
|
||||
|
|
@ -454,14 +454,14 @@ class Mobject(object):
|
|||
**kwargs
|
||||
)
|
||||
|
||||
def match_width(self, mobject):
|
||||
return self.match_dim(mobject, 0)
|
||||
def match_width(self, mobject, **kwargs):
|
||||
return self.match_dim(mobject, 0, **kwargs)
|
||||
|
||||
def match_height(self, mobject):
|
||||
return self.match_dim(mobject, 1)
|
||||
def match_height(self, mobject, **kwargs):
|
||||
return self.match_dim(mobject, 1, **kwargs)
|
||||
|
||||
def match_depth(self, mobject):
|
||||
return self.match_dim(mobject, 2)
|
||||
def match_depth(self, mobject, **kwargs):
|
||||
return self.match_dim(mobject, 2, **kwargs)
|
||||
|
||||
## Color functions
|
||||
|
||||
|
|
|
|||
|
|
@ -100,12 +100,12 @@ class VMobject(Mobject):
|
|||
#match styles accordingly
|
||||
submobs1, submobs2 = self.submobjects, vmobject.submobjects
|
||||
if len(submobs1) == 0:
|
||||
return
|
||||
return self
|
||||
elif len(submobs2) == 0:
|
||||
submobs2 = [vmobject]
|
||||
for sm1, sm2 in zip(*make_even(submobs1, submobs2)):
|
||||
sm1.match_style(sm2)
|
||||
return
|
||||
return self
|
||||
|
||||
def fade(self, darkness = 0.5):
|
||||
for submob in self.submobject_family():
|
||||
|
|
@ -353,7 +353,8 @@ class VMobject(Mobject):
|
|||
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.arange(0, num_inter_curves+1)/float(num_inter_curves)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ def get_sorted_scene_names(module_name):
|
|||
for line_no in sorted(line_to_scene.keys())
|
||||
]
|
||||
|
||||
|
||||
|
||||
def stage_animaions(module_name):
|
||||
def stage_animations(module_name):
|
||||
scene_names = get_sorted_scene_names(module_name)
|
||||
animation_dir = os.path.join(
|
||||
ANIMATIONS_DIR, module_name.replace(".py", "")
|
||||
|
|
@ -32,19 +30,27 @@ def stage_animaions(module_name):
|
|||
sorted_files.append(
|
||||
os.path.join(animation_dir, clip)
|
||||
)
|
||||
for f in os.listdir(STAGED_SCENES_DIR):
|
||||
os.remove(os.path.join(STAGED_SCENES_DIR, f))
|
||||
for f, count in zip(sorted_files, it.count()):
|
||||
staged_scenes_dir = os.path.join(animation_dir, "staged_scenes")
|
||||
count = 0
|
||||
while True:
|
||||
staged_scenes_dir = os.path.join(
|
||||
animation_dir, "staged_scenes_%d"%count
|
||||
)
|
||||
if not os.path.exists(staged_scenes_dir):
|
||||
os.makedirs(staged_scenes_dir)
|
||||
break
|
||||
#Otherwise, keep trying new names until
|
||||
#there is a free one
|
||||
count += 1
|
||||
for count, f in enumerate(sorted_files):
|
||||
symlink_name = os.path.join(
|
||||
STAGED_SCENES_DIR,
|
||||
staged_scenes_dir,
|
||||
"Scene_%03d"%count + f.split(os.sep)[-1]
|
||||
)
|
||||
os.symlink(f, symlink_name)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
raise Exception("No module given.")
|
||||
module_name = sys.argv[1]
|
||||
stage_animaions(module_name)
|
||||
stage_animations(module_name)
|
||||
|
|
@ -443,7 +443,9 @@ class PiCreatureScene(Scene):
|
|||
added_anims = kwargs.pop("added_anims", [])
|
||||
|
||||
anims = []
|
||||
on_screen_mobjects = self.get_mobjects()
|
||||
on_screen_mobjects = self.camera.extract_mobject_family_members(
|
||||
self.get_mobjects()
|
||||
)
|
||||
def has_bubble(pi):
|
||||
return hasattr(pi, "bubble") and \
|
||||
pi.bubble is not None and \
|
||||
|
|
@ -478,7 +480,7 @@ class PiCreatureScene(Scene):
|
|||
]
|
||||
anims += added_anims
|
||||
|
||||
self.play(*anims)
|
||||
self.play(*anims, **kwargs)
|
||||
|
||||
def pi_creature_says(self, *args, **kwargs):
|
||||
self.introduce_bubble(
|
||||
|
|
|
|||
|
|
@ -97,9 +97,6 @@ class Arc(VMobject):
|
|||
return self
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Circle(Arc):
|
||||
CONFIG = {
|
||||
"color" : RED,
|
||||
|
|
@ -356,6 +353,17 @@ class Line(VMobject):
|
|||
self.shift(new_start - self.get_start())
|
||||
return self
|
||||
|
||||
def insert_n_anchor_points(self, n):
|
||||
if not self.path_arc:
|
||||
n_anchors = self.get_num_anchor_points()
|
||||
new_num_points = 3*(n_anchors + n)+1
|
||||
self.points = np.array([
|
||||
self.point_from_proportion(alpha)
|
||||
for alpha in np.linspace(0, 1, new_num_points)
|
||||
])
|
||||
else:
|
||||
VMobject.insert_n_anchor_points(self, n)
|
||||
|
||||
class DashedLine(Line):
|
||||
CONFIG = {
|
||||
"dashed_segment_length" : 0.05
|
||||
|
|
@ -531,6 +539,7 @@ class Arrow(Line):
|
|||
Line.put_start_and_end_on(self, *args, **kwargs)
|
||||
self.set_tip_points(self.tip, preserve_normal = False)
|
||||
self.set_rectangular_stem_points()
|
||||
return self
|
||||
|
||||
def scale(self, scale_factor, **kwargs):
|
||||
Line.scale(self, scale_factor, **kwargs)
|
||||
|
|
@ -540,6 +549,9 @@ class Arrow(Line):
|
|||
self.set_rectangular_stem_points()
|
||||
return self
|
||||
|
||||
def copy(self):
|
||||
return self.deepcopy()
|
||||
|
||||
class Vector(Arrow):
|
||||
CONFIG = {
|
||||
"color" : YELLOW,
|
||||
|
|
|
|||
|
|
@ -200,14 +200,21 @@ class Axes(VGroup):
|
|||
self.y_axis.point_to_number(point),
|
||||
)
|
||||
|
||||
def get_graph(self, function, num_graph_points = None, **kwargs):
|
||||
def get_graph(
|
||||
self, function, num_graph_points = None,
|
||||
x_min = None,
|
||||
x_max = None,
|
||||
**kwargs
|
||||
):
|
||||
kwargs["fill_opacity"] = kwargs.get("fill_opacity", 0)
|
||||
kwargs["num_anchor_points"] = \
|
||||
num_graph_points or self.default_num_graph_points
|
||||
x_min = x_min or self.x_min
|
||||
x_max = x_max or self.x_max
|
||||
graph = ParametricFunction(
|
||||
lambda t : self.coords_to_point(t, function(t)),
|
||||
t_min = self.x_min,
|
||||
t_max = self.x_max,
|
||||
t_min = x_min,
|
||||
t_max = x_max,
|
||||
**kwargs
|
||||
)
|
||||
graph.underlying_function = function
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue