mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Merge branch 'master' into basel
This commit is contained in:
commit
ddf8a880ec
6 changed files with 356 additions and 186 deletions
|
@ -514,7 +514,6 @@ class FuncRotater(Animation):
|
||||||
angle_revs * 2 * np.pi,
|
angle_revs * 2 * np.pi,
|
||||||
)
|
)
|
||||||
self.mobject.set_color(color_func(angle_revs))
|
self.mobject.set_color(color_func(angle_revs))
|
||||||
# Will want to have arrow colors change to match direction as well
|
|
||||||
|
|
||||||
class TestRotater(Scene):
|
class TestRotater(Scene):
|
||||||
def construct(self):
|
def construct(self):
|
||||||
|
@ -543,6 +542,12 @@ class OdometerScene(Scene):
|
||||||
rate_func = None)
|
rate_func = None)
|
||||||
|
|
||||||
def point_to_rev((x, y)):
|
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)
|
return np.true_divide(np.arctan2(y, x), 2 * np.pi)
|
||||||
|
|
||||||
# Returns the value with the same fractional component as x, closest to m
|
# Returns the value with the same fractional component as x, closest to m
|
||||||
|
@ -578,16 +583,16 @@ class RectangleData():
|
||||||
self.rect = (x_interval, y_interval)
|
self.rect = (x_interval, y_interval)
|
||||||
|
|
||||||
def get_top_left(self):
|
def get_top_left(self):
|
||||||
return np.array((self.rect[0][0], self.rect[1][0]))
|
return np.array((self.rect[0][0], self.rect[1][1]))
|
||||||
|
|
||||||
def get_top_right(self):
|
def get_top_right(self):
|
||||||
return np.array((self.rect[0][1], self.rect[1][0]))
|
|
||||||
|
|
||||||
def get_bottom_right(self):
|
|
||||||
return np.array((self.rect[0][1], self.rect[1][1]))
|
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):
|
def get_bottom_left(self):
|
||||||
return np.array((self.rect[0][0], self.rect[1][1]))
|
return np.array((self.rect[0][0], self.rect[1][0]))
|
||||||
|
|
||||||
def get_top(self):
|
def get_top(self):
|
||||||
return (self.get_top_left(), self.get_top_right())
|
return (self.get_top_left(), self.get_top_right())
|
||||||
|
@ -611,22 +616,50 @@ class RectangleData():
|
||||||
elif dim == 1:
|
elif dim == 1:
|
||||||
return_data = [RectangleData(x_interval, new_interval) for new_interval in split_interval(y_interval)]
|
return_data = [RectangleData(x_interval, new_interval) for new_interval in split_interval(y_interval)]
|
||||||
else:
|
else:
|
||||||
print "Error!"
|
print "RectangleData.splits_on_dim passed illegitimate dimension!"
|
||||||
|
|
||||||
return tuple(return_data)
|
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):
|
def complex_to_pair(c):
|
||||||
return (c.real, c.imag)
|
return (c.real, c.imag)
|
||||||
|
|
||||||
class iterative_2d_test(Scene):
|
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):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"func" : lambda (x, y) : complex_to_pair(complex(x, y)**2 - complex(1, 2)**2),
|
"func" : plane_poly_with_roots((1, 2), (-1, 3)),
|
||||||
"initial_lower_x" : -5.1,
|
"initial_lower_x" : -5.1,
|
||||||
"initial_upper_x" : 5.1,
|
"initial_upper_x" : 5.1,
|
||||||
"initial_lower_y" : -3.1,
|
"initial_lower_y" : -3.1,
|
||||||
"initial_upper_y" : 3.1,
|
"initial_upper_y" : 3.1,
|
||||||
"num_iterations" : 20,
|
"num_iterations" : 5,
|
||||||
"num_checkpoints" : 10
|
"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):
|
def construct(self):
|
||||||
|
@ -634,8 +667,70 @@ class iterative_2d_test(Scene):
|
||||||
num_plane.fade()
|
num_plane.fade()
|
||||||
self.add(num_plane)
|
self.add(num_plane)
|
||||||
|
|
||||||
num_display = DecimalNumber(0, color = ORANGE)
|
rev_func = lambda p : point_to_rev(self.func(p))
|
||||||
num_display.move_to(UP + RIGHT)
|
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
|
||||||
|
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]
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
|
||||||
lower_x = self.initial_lower_x
|
lower_x = self.initial_lower_x
|
||||||
upper_x = self.initial_upper_x
|
upper_x = self.initial_upper_x
|
||||||
|
@ -647,80 +742,13 @@ class iterative_2d_test(Scene):
|
||||||
|
|
||||||
rect = RectangleData(x_interval, y_interval)
|
rect = RectangleData(x_interval, y_interval)
|
||||||
|
|
||||||
rev_func = lambda p : point_to_rev(self.func(p))
|
anim = Animate2dSolver(
|
||||||
|
cur_depth = 0,
|
||||||
|
rect = rect,
|
||||||
|
dim_to_split = 0,
|
||||||
|
)
|
||||||
|
|
||||||
dim_to_split = 0 # 0 for x, 1 for y
|
self.play(anim)
|
||||||
|
|
||||||
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 = "#FF0000")
|
|
||||||
self.play(
|
|
||||||
ShowCreation(line),
|
|
||||||
#ChangingDecimal(num_display, rebased_winder)
|
|
||||||
)
|
|
||||||
line.set_color("#00FF00")
|
|
||||||
return rebased_winder(1)
|
|
||||||
|
|
||||||
for i in range(self.num_iterations):
|
|
||||||
(explore_rect, alt_rect) = rect.splits_on_dim(dim_to_split)
|
|
||||||
|
|
||||||
top_wind = draw_line_return_wind(
|
|
||||||
explore_rect.get_top_left(),
|
|
||||||
explore_rect.get_top_right(),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
|
|
||||||
print(len(self.mobjects))
|
|
||||||
|
|
||||||
right_wind = draw_line_return_wind(
|
|
||||||
explore_rect.get_top_right(),
|
|
||||||
explore_rect.get_bottom_right(),
|
|
||||||
top_wind
|
|
||||||
)
|
|
||||||
|
|
||||||
print(len(self.mobjects))
|
|
||||||
|
|
||||||
bottom_wind = draw_line_return_wind(
|
|
||||||
explore_rect.get_bottom_right(),
|
|
||||||
explore_rect.get_bottom_left(),
|
|
||||||
right_wind
|
|
||||||
)
|
|
||||||
|
|
||||||
print(len(self.mobjects))
|
|
||||||
|
|
||||||
left_wind = draw_line_return_wind(
|
|
||||||
explore_rect.get_bottom_left(),
|
|
||||||
explore_rect.get_top_left(),
|
|
||||||
bottom_wind
|
|
||||||
)
|
|
||||||
|
|
||||||
print(len(self.mobjects))
|
|
||||||
|
|
||||||
total_wind = round(left_wind)
|
|
||||||
|
|
||||||
if total_wind == 0:
|
|
||||||
rect = alt_rect
|
|
||||||
else:
|
|
||||||
rect = explore_rect
|
|
||||||
|
|
||||||
dim_to_split = 1 - dim_to_split
|
|
||||||
|
|
||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
|
|
||||||
class EquationSolver2d(ZoomedScene):
|
|
||||||
#TODO
|
|
||||||
CONFIG = {
|
|
||||||
"func" : lambda p : p,
|
|
||||||
"target_input" : (0, 0),
|
|
||||||
"target_output" : (0, 0),
|
|
||||||
"initial_top_left_point" : (0, 0),
|
|
||||||
"initial_guess_dimensions" : (0, 0),
|
|
||||||
"num_iterations" : 10,
|
|
||||||
"iteration_at_which_to_start_zoom" : None
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -707,10 +707,7 @@ class FourierMachineScene(Scene):
|
||||||
for label in labels:
|
for label in labels:
|
||||||
label.scale(self.text_scale_val)
|
label.scale(self.text_scale_val)
|
||||||
time_label.next_to(time_axes.coords_to_point(3.5,0), DOWN)
|
time_label.next_to(time_axes.coords_to_point(3.5,0), DOWN)
|
||||||
intensity_label.next_to(
|
intensity_label.next_to(time_axes.y_axis.get_top(), RIGHT)
|
||||||
time_axes.y_axis.get_top(), RIGHT,
|
|
||||||
aligned_edge = UP,
|
|
||||||
)
|
|
||||||
time_axes.labels = labels
|
time_axes.labels = labels
|
||||||
time_axes.add(labels)
|
time_axes.add(labels)
|
||||||
time_axes.to_corner(UP+LEFT)
|
time_axes.to_corner(UP+LEFT)
|
||||||
|
@ -764,8 +761,10 @@ class FourierMachineScene(Scene):
|
||||||
graph = self.time_axes.get_graph(func, **config)
|
graph = self.time_axes.get_graph(func, **config)
|
||||||
return graph
|
return graph
|
||||||
|
|
||||||
def get_cosine_wave(self, freq = 1):
|
def get_cosine_wave(self, freq = 1, shift_val = 1, scale_val = 0.9):
|
||||||
return self.get_time_graph(lambda t : 1 + 0.5*np.cos(TAU*freq*t))
|
return self.get_time_graph(
|
||||||
|
lambda t : shift_val + scale_val*np.cos(TAU*freq*t)
|
||||||
|
)
|
||||||
|
|
||||||
def get_fourier_transform_graph(self, time_graph, **kwargs):
|
def get_fourier_transform_graph(self, time_graph, **kwargs):
|
||||||
if not hasattr(self, "frequency_axes"):
|
if not hasattr(self, "frequency_axes"):
|
||||||
|
@ -795,7 +794,6 @@ class FourierMachineScene(Scene):
|
||||||
)[0]
|
)[0]
|
||||||
return fourier_transform
|
return fourier_transform
|
||||||
|
|
||||||
|
|
||||||
def get_polarized_mobject(self, mobject, freq = 1.0):
|
def get_polarized_mobject(self, mobject, freq = 1.0):
|
||||||
if not hasattr(self, "circle_plane"):
|
if not hasattr(self, "circle_plane"):
|
||||||
self.get_circle_plane()
|
self.get_circle_plane()
|
||||||
|
@ -958,6 +956,8 @@ class WrapCosineGraphAroundCircle(FourierMachineScene):
|
||||||
for x in 1, 2
|
for x in 1, 2
|
||||||
])
|
])
|
||||||
words = self.get_bps_label()
|
words = self.get_bps_label()
|
||||||
|
words.save_state()
|
||||||
|
words.next_to(axes.coords_to_point(1.5, 0), DOWN, MED_LARGE_BUFF)
|
||||||
|
|
||||||
self.add(axes)
|
self.add(axes)
|
||||||
self.play(ShowCreation(graph, run_time = 2, rate_func = None))
|
self.play(ShowCreation(graph, run_time = 2, rate_func = None))
|
||||||
|
@ -967,7 +967,10 @@ class WrapCosineGraphAroundCircle(FourierMachineScene):
|
||||||
*map(ShowCreation, v_lines)
|
*map(ShowCreation, v_lines)
|
||||||
)
|
)
|
||||||
self.wait()
|
self.wait()
|
||||||
self.play(FadeOut(VGroup(braces, v_lines)))
|
self.play(
|
||||||
|
FadeOut(VGroup(braces, v_lines)),
|
||||||
|
words.restore,
|
||||||
|
)
|
||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
self.beats_per_second_label = words
|
self.beats_per_second_label = words
|
||||||
|
@ -1101,7 +1104,7 @@ class WrapCosineGraphAroundCircle(FourierMachineScene):
|
||||||
braces = VGroup(*self.get_peak_braces()[3:6])
|
braces = VGroup(*self.get_peak_braces()[3:6])
|
||||||
words = TextMobject("3 beats/second")
|
words = TextMobject("3 beats/second")
|
||||||
words.scale_to_fit_width(0.9*braces.get_width())
|
words.scale_to_fit_width(0.9*braces.get_width())
|
||||||
words.next_to(braces, UP, SMALL_BUFF)
|
words.move_to(braces, DOWN)
|
||||||
return words
|
return words
|
||||||
|
|
||||||
class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
|
class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
|
||||||
|
@ -1112,9 +1115,9 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
|
||||||
def construct(self):
|
def construct(self):
|
||||||
self.remove(self.pi_creature)
|
self.remove(self.pi_creature)
|
||||||
self.setup_graph()
|
self.setup_graph()
|
||||||
# self.indicate_weight_of_wire()
|
self.indicate_weight_of_wire()
|
||||||
self.show_center_of_mass_dot()
|
self.show_center_of_mass_dot()
|
||||||
# self.change_to_various_frequencies()
|
self.change_to_various_frequencies()
|
||||||
self.introduce_frequency_plot()
|
self.introduce_frequency_plot()
|
||||||
self.draw_full_frequency_plot()
|
self.draw_full_frequency_plot()
|
||||||
self.recap_objects_on_screen()
|
self.recap_objects_on_screen()
|
||||||
|
@ -1229,6 +1232,7 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
|
||||||
),
|
),
|
||||||
t_min = 0, t_max = TAU,
|
t_min = 0, t_max = TAU,
|
||||||
)
|
)
|
||||||
|
flower_path.move_to(self.center_of_mass_dot)
|
||||||
|
|
||||||
self.play(
|
self.play(
|
||||||
wps_label.move_to, self.circle_plane.get_top(),
|
wps_label.move_to, self.circle_plane.get_top(),
|
||||||
|
@ -1245,6 +1249,8 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
|
||||||
))
|
))
|
||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
|
self.x_coord_label = x_coord_label
|
||||||
|
|
||||||
def draw_full_frequency_plot(self):
|
def draw_full_frequency_plot(self):
|
||||||
graph = self.graph
|
graph = self.graph
|
||||||
fourier_graph = self.get_fourier_transform_graph(graph)
|
fourier_graph = self.get_fourier_transform_graph(graph)
|
||||||
|
@ -1255,51 +1261,129 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
|
||||||
stroke_width = 6,
|
stroke_width = 6,
|
||||||
color = fourier_graph.get_color()
|
color = fourier_graph.get_color()
|
||||||
)
|
)
|
||||||
dot = Dot(color = fourier_graph.get_color())
|
|
||||||
def update_dot(dot):
|
|
||||||
f = self.graph.polarized_mobject.frequency
|
|
||||||
dot.move_to(self.frequency_axes.input_to_graph_point(
|
|
||||||
f, fourier_graph
|
|
||||||
))
|
|
||||||
dot_update_anim = UpdateFromFunc(dot, update_dot)
|
|
||||||
|
|
||||||
self.change_frequency(0.0)
|
self.change_frequency(0.0)
|
||||||
dot_update_anim.update(0)
|
self.generate_fourier_dot_transform(fourier_graph)
|
||||||
self.wait()
|
self.wait()
|
||||||
self.play(ShowCreation(v_line))
|
self.play(ShowCreation(v_line))
|
||||||
self.play(GrowFromCenter(dot), FadeOut(v_line))
|
self.play(
|
||||||
|
GrowFromCenter(self.fourier_graph_dot),
|
||||||
|
FadeOut(v_line)
|
||||||
|
)
|
||||||
f_max = int(self.frequency_axes.x_max)
|
f_max = int(self.frequency_axes.x_max)
|
||||||
for freq in range(1, f_max+1):
|
for freq in [0.2, 1.5, 3.0, 4.0, 5.0]:
|
||||||
fourier_graph.restore()
|
fourier_graph.restore()
|
||||||
self.change_frequency(
|
self.change_frequency(
|
||||||
freq,
|
freq,
|
||||||
added_anims = [
|
added_anims = [ShowCreation(
|
||||||
ShowCreation(
|
fourier_graph,
|
||||||
fourier_graph,
|
rate_func = lambda t : interpolate(
|
||||||
rate_func = lambda t : interpolate(
|
(freq-1.)/f_max,
|
||||||
(freq-1.)/f_max,
|
float(freq)/f_max,
|
||||||
float(freq)/f_max,
|
smooth(t)
|
||||||
smooth(t)
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
dot_update_anim
|
)],
|
||||||
],
|
|
||||||
run_time = 5,
|
run_time = 5,
|
||||||
)
|
)
|
||||||
self.wait()
|
self.wait()
|
||||||
self.play(FadeOut(dot))
|
|
||||||
self.fourier_graph = fourier_graph
|
self.fourier_graph = fourier_graph
|
||||||
|
|
||||||
def recap_objects_on_screen(self):
|
def recap_objects_on_screen(self):
|
||||||
rect = FullScreenFadeRectangle()
|
rect = FullScreenFadeRectangle()
|
||||||
|
time_group = VGroup(
|
||||||
|
self.graph,
|
||||||
|
self.time_axes,
|
||||||
|
self.beats_per_second_label,
|
||||||
|
).copy()
|
||||||
|
circle_group = VGroup(
|
||||||
|
self.graph.polarized_mobject,
|
||||||
|
self.circle_plane,
|
||||||
|
self.winding_freq_label,
|
||||||
|
self.center_of_mass_label,
|
||||||
|
self.center_of_mass_dot,
|
||||||
|
).copy()
|
||||||
|
frequency_group = VGroup(
|
||||||
|
self.fourier_graph,
|
||||||
|
self.frequency_axes,
|
||||||
|
self.x_coord_label,
|
||||||
|
).copy()
|
||||||
|
groups = [time_group, circle_group, frequency_group]
|
||||||
|
|
||||||
self.play(FadeIn(rect))
|
self.play(FadeIn(rect))
|
||||||
|
self.wait()
|
||||||
|
for group in groups:
|
||||||
|
self.play(FadeIn(group))
|
||||||
|
self.play(ShowCreation(group[0]))
|
||||||
|
self.wait()
|
||||||
|
self.play(FadeOut(group))
|
||||||
|
self.wait()
|
||||||
|
self.play(FadeOut(rect))
|
||||||
|
|
||||||
def lower_graph(self):
|
def lower_graph(self):
|
||||||
pass
|
graph = self.graph
|
||||||
|
time_axes = self.time_axes
|
||||||
|
shift_vect = time_axes.coords_to_point(0, 1)
|
||||||
|
shift_vect -= time_axes.coords_to_point(0, 0)
|
||||||
|
fourier_graph = self.fourier_graph
|
||||||
|
new_graph = self.get_cosine_wave(
|
||||||
|
self.signal_frequency, shift_val = 0
|
||||||
|
)
|
||||||
|
new_fourier_graph = self.get_fourier_transform_graph(new_graph)
|
||||||
|
for mob in graph, time_axes, fourier_graph:
|
||||||
|
mob.save_state()
|
||||||
|
|
||||||
|
new_freq = 0.03
|
||||||
|
self.change_frequency(new_freq)
|
||||||
|
self.wait()
|
||||||
|
self.play(
|
||||||
|
time_axes.shift, shift_vect/2,
|
||||||
|
graph.shift, -shift_vect/2,
|
||||||
|
self.get_frequency_change_animation(
|
||||||
|
self.graph, new_freq
|
||||||
|
),
|
||||||
|
self.center_of_mass_dot_anim,
|
||||||
|
self.get_period_v_lines_update_anim(),
|
||||||
|
Transform(fourier_graph, new_fourier_graph),
|
||||||
|
self.fourier_graph_dot.move_to,
|
||||||
|
self.frequency_axes.coords_to_point(new_freq, 0),
|
||||||
|
run_time = 2
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
self.remove(self.fourier_graph_dot)
|
||||||
|
self.generate_fourier_dot_transform(new_fourier_graph)
|
||||||
|
self.change_frequency(3.0, run_time = 15, rate_func = None)
|
||||||
|
self.wait()
|
||||||
|
self.play(
|
||||||
|
graph.restore,
|
||||||
|
time_axes.restore,
|
||||||
|
self.get_frequency_change_animation(
|
||||||
|
self.graph, 3.0
|
||||||
|
),
|
||||||
|
self.center_of_mass_dot_anim,
|
||||||
|
self.get_period_v_lines_update_anim(),
|
||||||
|
fourier_graph.restore,
|
||||||
|
Animation(self.fourier_graph_dot),
|
||||||
|
run_time = 2
|
||||||
|
)
|
||||||
|
self.generate_fourier_dot_transform(self.fourier_graph)
|
||||||
|
self.wait()
|
||||||
|
self.play(FocusOn(self.fourier_graph_dot))
|
||||||
|
self.wait()
|
||||||
|
|
||||||
def label_as_almost_fourier(self):
|
def label_as_almost_fourier(self):
|
||||||
pass
|
x_coord_label = self.x_coord_label
|
||||||
|
almost_fourier_label = TextMobject(
|
||||||
|
"``Almost Fourier Transform''",
|
||||||
|
)
|
||||||
|
almost_fourier_label.move_to(x_coord_label, UP+LEFT)
|
||||||
|
x_coord_label.generate_target()
|
||||||
|
x_coord_label.target.next_to(almost_fourier_label, DOWN)
|
||||||
|
|
||||||
|
self.play(
|
||||||
|
MoveToTarget(x_coord_label),
|
||||||
|
Write(almost_fourier_label)
|
||||||
|
)
|
||||||
|
self.wait(2)
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
||||||
|
@ -1314,6 +1398,18 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
|
||||||
# result += self.circle_plane.get_center()
|
# result += self.circle_plane.get_center()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def generate_fourier_dot_transform(self, fourier_graph):
|
||||||
|
self.fourier_graph_dot = Dot(color = WHITE, radius = 0.05)
|
||||||
|
def update_dot(dot):
|
||||||
|
f = self.graph.polarized_mobject.frequency
|
||||||
|
dot.move_to(self.frequency_axes.input_to_graph_point(
|
||||||
|
f, fourier_graph
|
||||||
|
))
|
||||||
|
self.fourier_graph_dot_anim = UpdateFromFunc(
|
||||||
|
self.fourier_graph_dot, update_dot
|
||||||
|
)
|
||||||
|
self.fourier_graph_dot_anim.update(0)
|
||||||
|
|
||||||
def change_frequency(self, new_freq, **kwargs):
|
def change_frequency(self, new_freq, **kwargs):
|
||||||
kwargs["run_time"] = kwargs.get("run_time", 3)
|
kwargs["run_time"] = kwargs.get("run_time", 3)
|
||||||
added_anims = kwargs.get("added_anims", [])
|
added_anims = kwargs.get("added_anims", [])
|
||||||
|
@ -1329,14 +1425,40 @@ class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene):
|
||||||
self.get_period_v_lines_update_anim(),
|
self.get_period_v_lines_update_anim(),
|
||||||
]
|
]
|
||||||
anims += added_anims
|
anims += added_anims
|
||||||
#TODO, conditionals for center of mass
|
|
||||||
if hasattr(self, "center_of_mass_dot"):
|
if hasattr(self, "center_of_mass_dot"):
|
||||||
anims.append(self.center_of_mass_dot_anim)
|
anims.append(self.center_of_mass_dot_anim)
|
||||||
|
if hasattr(self, "fourier_graph_dot"):
|
||||||
|
anims.append(self.fourier_graph_dot_anim)
|
||||||
self.play(*anims, **kwargs)
|
self.play(*anims, **kwargs)
|
||||||
|
|
||||||
def create_pi_creature(self):
|
def create_pi_creature(self):
|
||||||
return Mortimer().to_corner(DOWN+RIGHT)
|
return Mortimer().to_corner(DOWN+RIGHT)
|
||||||
|
|
||||||
|
class StudentsHorrifiedAtScene(TeacherStudentsScene):
|
||||||
|
def construct(self):
|
||||||
|
self.change_student_modes(
|
||||||
|
*3*["horrified"],
|
||||||
|
look_at_arg = 2*UP + 3*LEFT
|
||||||
|
)
|
||||||
|
self.wait(4)
|
||||||
|
|
||||||
|
|
||||||
|
class ShowLinearity(DrawFrequencyPlot):
|
||||||
|
def construct(self):
|
||||||
|
self.show_lower_frequency_signal()
|
||||||
|
self.play_with_lower_frequency_signal()
|
||||||
|
self.point_out_fourier_spike()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -357,7 +357,7 @@ class Succession(Animation):
|
||||||
"""
|
"""
|
||||||
Each arg will either be an animation, or an animation class
|
Each arg will either be an animation, or an animation class
|
||||||
followed by its arguments (and potentially a dict for
|
followed by its arguments (and potentially a dict for
|
||||||
configuraiton).
|
configuration).
|
||||||
|
|
||||||
For example,
|
For example,
|
||||||
Succession(
|
Succession(
|
||||||
|
@ -415,27 +415,36 @@ class Succession(Animation):
|
||||||
#might very well mess with it.
|
#might very well mess with it.
|
||||||
self.original_run_time = run_time
|
self.original_run_time = run_time
|
||||||
|
|
||||||
|
# critical_alphas[i] is the start alpha of self.animations[i]
|
||||||
|
# critical_alphas[i + 1] is the end alpha of self.animations[i]
|
||||||
|
critical_times = np.concatenate(([0], np.cumsum(self.run_times)))
|
||||||
|
self.critical_alphas = map (lambda x : np.true_divide(x, run_time), critical_times)
|
||||||
|
|
||||||
mobject = Group(*[anim.mobject for anim in self.animations])
|
mobject = Group(*[anim.mobject for anim in self.animations])
|
||||||
Animation.__init__(self, mobject, run_time = run_time, **kwargs)
|
Animation.__init__(self, mobject, run_time = run_time, **kwargs)
|
||||||
|
|
||||||
|
def rewind_to_start(self):
|
||||||
|
for anim in reversed(self.animations):
|
||||||
|
anim.update(0)
|
||||||
|
|
||||||
def update_mobject(self, alpha):
|
def update_mobject(self, alpha):
|
||||||
if alpha >= 1.0:
|
self.rewind_to_start()
|
||||||
self.animations[-1].update(1)
|
|
||||||
return
|
for i in range(len(self.animations)):
|
||||||
run_times = self.run_times
|
sub_alpha = inverse_interpolate(
|
||||||
index = 0
|
self.critical_alphas[i],
|
||||||
time = alpha*self.original_run_time
|
self.critical_alphas[i + 1],
|
||||||
while sum(run_times[:index+1]) < time:
|
alpha
|
||||||
index += 1
|
)
|
||||||
if index > self.last_index:
|
if sub_alpha < 0:
|
||||||
self.animations[self.last_index].update(1)
|
return
|
||||||
self.animations[self.last_index].clean_up()
|
|
||||||
self.last_index = index
|
sub_alpha = clamp(0, 1, sub_alpha) # Could possibly adopt a non-clamping convention here
|
||||||
curr_anim = self.animations[index]
|
self.animations[i].update(sub_alpha)
|
||||||
sub_alpha = np.clip(
|
|
||||||
(time - sum(run_times[:index]))/run_times[index], 0, 1
|
def clean_up(self, *args, **kwargs):
|
||||||
)
|
for anim in self.animations:
|
||||||
curr_anim.update(sub_alpha)
|
anim.clean_up(*args, **kwargs)
|
||||||
|
|
||||||
class AnimationGroup(Animation):
|
class AnimationGroup(Animation):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
|
@ -452,23 +461,10 @@ class AnimationGroup(Animation):
|
||||||
for anim in self.sub_anims:
|
for anim in self.sub_anims:
|
||||||
anim.update(alpha)
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import inspect
|
||||||
import traceback
|
import traceback
|
||||||
import imp
|
import imp
|
||||||
import os
|
import os
|
||||||
|
import subprocess as sp
|
||||||
|
|
||||||
from helpers import *
|
from helpers import *
|
||||||
from scene import Scene
|
from scene import Scene
|
||||||
|
@ -58,7 +59,8 @@ def get_configuration():
|
||||||
("-s", "--show_last_frame"),
|
("-s", "--show_last_frame"),
|
||||||
("-l", "--low_quality"),
|
("-l", "--low_quality"),
|
||||||
("-m", "--medium_quality"),
|
("-m", "--medium_quality"),
|
||||||
("-f", "--save_pngs"),
|
("-g", "--save_pngs"),
|
||||||
|
("-f", "--show_file_in_finder"),
|
||||||
("-t", "--transparent"),
|
("-t", "--transparent"),
|
||||||
("-q", "--quiet"),
|
("-q", "--quiet"),
|
||||||
("-a", "--write_all")
|
("-a", "--write_all")
|
||||||
|
@ -74,11 +76,10 @@ def get_configuration():
|
||||||
config = {
|
config = {
|
||||||
"file" : args.file,
|
"file" : args.file,
|
||||||
"scene_name" : args.scene_name,
|
"scene_name" : args.scene_name,
|
||||||
"camera_config" : PRODUCTION_QUALITY_CAMERA_CONFIG, #TODO
|
"open_video_upon_completion" : args.preview,
|
||||||
"frame_duration" : PRODUCTION_QUALITY_FRAME_DURATION, #TODO
|
"show_file_in_finder" : args.show_file_in_finder,
|
||||||
"preview" : args.preview,
|
#By default, write to file
|
||||||
"write_to_movie" : args.write_to_movie,
|
"write_to_movie" : args.write_to_movie or not args.show_last_frame,
|
||||||
"save_frames" : args.preview, #Scenes only save frame when previewing
|
|
||||||
"show_last_frame" : args.show_last_frame,
|
"show_last_frame" : args.show_last_frame,
|
||||||
"save_pngs" : args.save_pngs,
|
"save_pngs" : args.save_pngs,
|
||||||
#If -t is passed in (for transparent), this will be RGBA
|
#If -t is passed in (for transparent), this will be RGBA
|
||||||
|
@ -88,7 +89,7 @@ def get_configuration():
|
||||||
"output_name" : args.output_name,
|
"output_name" : args.output_name,
|
||||||
"skip_to_animation_number" : args.skip_to_animation_number,
|
"skip_to_animation_number" : args.skip_to_animation_number,
|
||||||
}
|
}
|
||||||
if args.low_quality or args.preview:
|
if args.low_quality:
|
||||||
config["camera_config"] = LOW_QUALITY_CAMERA_CONFIG
|
config["camera_config"] = LOW_QUALITY_CAMERA_CONFIG
|
||||||
config["frame_duration"] = LOW_QUALITY_FRAME_DURATION
|
config["frame_duration"] = LOW_QUALITY_FRAME_DURATION
|
||||||
elif args.medium_quality:
|
elif args.medium_quality:
|
||||||
|
@ -102,10 +103,6 @@ def get_configuration():
|
||||||
if stan is not None:
|
if stan is not None:
|
||||||
config["skip_to_animation_number"] = int(stan)
|
config["skip_to_animation_number"] = int(stan)
|
||||||
|
|
||||||
#By default, write to file
|
|
||||||
actions = ["write_to_movie", "preview", "show_last_frame"]
|
|
||||||
if not any([config[key] for key in actions]):
|
|
||||||
config["write_to_movie"] = True
|
|
||||||
config["skip_animations"] = any([
|
config["skip_animations"] = any([
|
||||||
config["show_last_frame"] and not config["write_to_movie"],
|
config["show_last_frame"] and not config["write_to_movie"],
|
||||||
config["skip_to_animation_number"],
|
config["skip_to_animation_number"],
|
||||||
|
@ -117,12 +114,23 @@ def handle_scene(scene, **config):
|
||||||
curr_stdout = sys.stdout
|
curr_stdout = sys.stdout
|
||||||
sys.stdout = open(os.devnull, "w")
|
sys.stdout = open(os.devnull, "w")
|
||||||
|
|
||||||
if config["preview"]:
|
|
||||||
scene.preview()
|
|
||||||
if config["show_last_frame"]:
|
if config["show_last_frame"]:
|
||||||
if not config["write_all"]:
|
|
||||||
scene.show_frame()
|
|
||||||
scene.save_image(mode = config["saved_image_mode"])
|
scene.save_image(mode = config["saved_image_mode"])
|
||||||
|
open_file = any([
|
||||||
|
config["show_last_frame"],
|
||||||
|
config["open_video_upon_completion"],
|
||||||
|
config["show_file_in_finder"]
|
||||||
|
])
|
||||||
|
if open_file:
|
||||||
|
commands = ["open"]
|
||||||
|
if config["show_file_in_finder"]:
|
||||||
|
commands.append("-R")
|
||||||
|
#
|
||||||
|
if config["show_last_frame"]:
|
||||||
|
commands.append(scene.get_image_file_path())
|
||||||
|
else:
|
||||||
|
commands.append(scene.get_movie_file_path())
|
||||||
|
sp.call(commands)
|
||||||
|
|
||||||
if config["quiet"]:
|
if config["quiet"]:
|
||||||
sys.stdout.close()
|
sys.stdout.close()
|
||||||
|
@ -209,7 +217,6 @@ def main():
|
||||||
"frame_duration",
|
"frame_duration",
|
||||||
"skip_animations",
|
"skip_animations",
|
||||||
"write_to_movie",
|
"write_to_movie",
|
||||||
"save_frames",
|
|
||||||
"output_directory",
|
"output_directory",
|
||||||
"save_pngs",
|
"save_pngs",
|
||||||
"skip_to_animation_number",
|
"skip_to_animation_number",
|
||||||
|
|
|
@ -304,6 +304,12 @@ def digest_locals(obj, keys = None):
|
||||||
def interpolate(start, end, alpha):
|
def interpolate(start, end, alpha):
|
||||||
return (1-alpha)*start + alpha*end
|
return (1-alpha)*start + alpha*end
|
||||||
|
|
||||||
|
def mid(start, end):
|
||||||
|
return (start + end)/2.0
|
||||||
|
|
||||||
|
def inverse_interpolate(start, end, value):
|
||||||
|
return np.true_divide(value - start, end - start)
|
||||||
|
|
||||||
def clamp(lower, upper, val):
|
def clamp(lower, upper, val):
|
||||||
if val < lower:
|
if val < lower:
|
||||||
return lower
|
return lower
|
||||||
|
|
|
@ -33,6 +33,7 @@ class Scene(object):
|
||||||
"save_pngs" : False,
|
"save_pngs" : False,
|
||||||
"pngs_mode" : "RGBA",
|
"pngs_mode" : "RGBA",
|
||||||
"output_directory" : ANIMATIONS_DIR,
|
"output_directory" : ANIMATIONS_DIR,
|
||||||
|
"movie_file_extension" : ".mp4",
|
||||||
"name" : None,
|
"name" : None,
|
||||||
"always_continually_update" : False,
|
"always_continually_update" : False,
|
||||||
"random_seed" : 0,
|
"random_seed" : 0,
|
||||||
|
@ -313,7 +314,10 @@ class Scene(object):
|
||||||
|
|
||||||
def get_moving_mobjects(self, *animations):
|
def get_moving_mobjects(self, *animations):
|
||||||
moving_mobjects = list(it.chain(
|
moving_mobjects = list(it.chain(
|
||||||
[anim.mobject for anim in animations],
|
[
|
||||||
|
anim.mobject for anim in animations
|
||||||
|
if anim.mobject not in self.foreground_mobjects
|
||||||
|
],
|
||||||
[ca.mobject for ca in self.continual_animations],
|
[ca.mobject for ca in self.continual_animations],
|
||||||
self.foreground_mobjects,
|
self.foreground_mobjects,
|
||||||
))
|
))
|
||||||
|
@ -470,24 +474,31 @@ class Scene(object):
|
||||||
|
|
||||||
def preview(self):
|
def preview(self):
|
||||||
TkSceneRoot(self)
|
TkSceneRoot(self)
|
||||||
|
|
||||||
def save_image(self, name = None, mode = "RGB", dont_update = False):
|
def get_image_file_path(self, name = None, dont_update = False):
|
||||||
folder = "images"
|
folder = "images"
|
||||||
if dont_update:
|
if dont_update:
|
||||||
folder = str(self)
|
folder = str(self)
|
||||||
|
|
||||||
path = os.path.join(self.output_directory, folder)
|
path = os.path.join(self.output_directory, folder)
|
||||||
file_name = (name or str(self)) + ".png"
|
file_name = (name or str(self)) + ".png"
|
||||||
full_path = os.path.join(path, file_name)
|
return os.path.join(path, file_name)
|
||||||
if not os.path.exists(path):
|
|
||||||
os.makedirs(path)
|
def save_image(self, name = None, mode = "RGB", dont_update = False):
|
||||||
|
path = self.get_image_file_path(name, dont_update)
|
||||||
|
directory_path = os.path.dirname(path)
|
||||||
|
if not os.path.exists(directory_path):
|
||||||
|
os.makedirs(directory_path)
|
||||||
if not dont_update:
|
if not dont_update:
|
||||||
self.update_frame()
|
self.update_frame()
|
||||||
image = self.get_image()
|
image = self.get_image()
|
||||||
image = image.convert(mode)
|
image = image.convert(mode)
|
||||||
image.save(full_path)
|
image.save(path)
|
||||||
|
|
||||||
def get_movie_file_path(self, name, extension):
|
def get_movie_file_path(self, name = None, extension = None):
|
||||||
|
if extension is None:
|
||||||
|
extension = self.movie_file_extension
|
||||||
|
if name is None:
|
||||||
|
name = self.name
|
||||||
file_path = os.path.join(self.output_directory, name)
|
file_path = os.path.join(self.output_directory, name)
|
||||||
if not file_path.endswith(extension):
|
if not file_path.endswith(extension):
|
||||||
file_path += extension
|
file_path += extension
|
||||||
|
@ -497,8 +508,8 @@ class Scene(object):
|
||||||
|
|
||||||
def open_movie_pipe(self):
|
def open_movie_pipe(self):
|
||||||
name = str(self)
|
name = str(self)
|
||||||
file_path = self.get_movie_file_path(name, ".mp4")
|
file_path = self.get_movie_file_path(name)
|
||||||
temp_file_path = file_path.replace(".mp4", "Temp.mp4")
|
temp_file_path = file_path.replace(name, name + "Temp")
|
||||||
print("Writing to %s"%temp_file_path)
|
print("Writing to %s"%temp_file_path)
|
||||||
self.args_to_rename_file = (temp_file_path, file_path)
|
self.args_to_rename_file = (temp_file_path, file_path)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue