mirror of
https://github.com/3b1b/videos.git
synced 2025-08-31 21:58:59 +00:00
Finalize Fourier monster animations
This commit is contained in:
parent
2aec2f2c7b
commit
3dd96ca2f7
3 changed files with 108 additions and 80 deletions
|
@ -157,27 +157,31 @@ class FourierCirclesScene(Scene):
|
|||
def get_drawn_path_alpha(self):
|
||||
return self.get_vector_time()
|
||||
|
||||
def get_drawn_path(self, vectors, stroke_width=None, **kwargs):
|
||||
def get_drawn_path(self, vectors, stroke_width=None, fade_rate=0.2, **kwargs):
|
||||
if stroke_width is None:
|
||||
stroke_width = self.drawn_path_stroke_width
|
||||
path = self.get_vector_sum_path(vectors, **kwargs)
|
||||
broken_path = CurvesAsSubmobjects(path)
|
||||
path.set_stroke(self.drawn_path_color, stroke_width)
|
||||
self.add_path_fader(path, fade_rate)
|
||||
return path
|
||||
|
||||
def update_path(path, dt):
|
||||
def add_path_fader(self, path, fade_rate=0.2):
|
||||
stroke_width = np.max(path.get_stroke_width())
|
||||
stroke_opacity = np.max(path.get_stroke_opacity())
|
||||
|
||||
def update_path(path_, dt):
|
||||
alpha = self.get_vector_time()
|
||||
n_curves = len(path)
|
||||
for a, sp in zip(np.linspace(0, 1, n_curves), path):
|
||||
b = alpha - a
|
||||
if b < 0:
|
||||
width = 0
|
||||
else:
|
||||
width = stroke_width * (1 - (b % 1))
|
||||
sp.set_stroke(width=width)
|
||||
return path
|
||||
n = path_.get_num_points()
|
||||
fade_factors = (np.linspace(0, 1, n) - alpha) % 1
|
||||
fade_factors = fade_factors**fade_rate
|
||||
path_.set_stroke(
|
||||
width=stroke_width * fade_factors,
|
||||
opacity=stroke_opacity * fade_factors,
|
||||
)
|
||||
return path_
|
||||
|
||||
broken_path.set_color(self.drawn_path_color)
|
||||
broken_path.add_updater(update_path)
|
||||
return broken_path
|
||||
path.add_updater(update_path)
|
||||
return path
|
||||
|
||||
def get_y_component_wave(self,
|
||||
vectors,
|
||||
|
|
|
@ -3,16 +3,17 @@ from _2022.piano.fourier_animations import DecomposeAudioSegment
|
|||
from _2019.diffyq.part2.fourier_series import FourierCirclesScene
|
||||
|
||||
|
||||
class DecomposeRoar(DecomposeAudioSegment):
|
||||
class DecomposeRoar_SlowFPS(DecomposeAudioSegment):
|
||||
audio_file = "/Users/grant/Dropbox/3Blue1Brown/videos/2022/infinity/Roar.wav"
|
||||
graph_point = 0.428
|
||||
zoom_rect_dims = (0.2, 6.0)
|
||||
|
||||
def construct(self):
|
||||
self.add_full_waveform()
|
||||
self.add_full_waveform(run_time=2.5)
|
||||
self.zoom_in_on_segment(
|
||||
self.axes, self.graph,
|
||||
self.graph_point, self.zoom_rect_dims
|
||||
self.graph_point, self.zoom_rect_dims,
|
||||
run_time=3,
|
||||
)
|
||||
self.prepare_for_3d()
|
||||
self.show_all_waves()
|
||||
|
@ -55,7 +56,8 @@ class DecomposeRoar(DecomposeAudioSegment):
|
|||
x_range=(0, t_max),
|
||||
)
|
||||
wave.match_y(f_axes.c2p(freq, 0))
|
||||
wave.set_stroke(opacity=0.75)
|
||||
# wave.set_stroke(opacity=0.75)
|
||||
wave.set_stroke(opacity=1.0)
|
||||
wave.amp = amp
|
||||
wave.freq = freq
|
||||
wave.phase = phase
|
||||
|
@ -85,14 +87,14 @@ class DecomposeRoar(DecomposeAudioSegment):
|
|||
),
|
||||
lag_ratio=1 / len(sine_waves),
|
||||
),
|
||||
run_time=8
|
||||
run_time=4
|
||||
)
|
||||
self.wait(3)
|
||||
|
||||
|
||||
class CombineWavesToImage(FourierCirclesScene):
|
||||
class CombineWavesToImage_SlowFPS(FourierCirclesScene):
|
||||
CONFIG = {
|
||||
"peace_sign_height": 6.0,
|
||||
"drawing_height": 6.0,
|
||||
"n_shown_waves": 5,
|
||||
"n_vectors": 500,
|
||||
# "n_vectors": 50,
|
||||
|
@ -102,6 +104,7 @@ class CombineWavesToImage(FourierCirclesScene):
|
|||
# "parametric_function_step_size": 0.01,
|
||||
"drawn_path_color": YELLOW,
|
||||
"remove_background_waves": False,
|
||||
"drawing_file": "/Users/grant/Dropbox/3Blue1Brown/videos/2022/infinity/monster-holding-piece-sign-3.svg",
|
||||
}
|
||||
|
||||
def construct(self):
|
||||
|
@ -157,10 +160,10 @@ class CombineWavesToImage(FourierCirclesScene):
|
|||
))
|
||||
all_axes.arrange(DOWN, buff=1.0)
|
||||
|
||||
dots = VGroup(Tex("\\vdots", font_size=100), Tex(""))
|
||||
dots.arrange(RIGHT)
|
||||
dots = Tex("\\vdots", font_size=100)
|
||||
dots.next_to(all_axes, DOWN, buff=0.5)
|
||||
dots.match_x(all_axes[0].get_origin())
|
||||
dots.set_fill(WHITE)
|
||||
|
||||
frame = self.camera.frame
|
||||
group = Group(all_axes, dots)
|
||||
|
@ -216,10 +219,9 @@ class CombineWavesToImage(FourierCirclesScene):
|
|||
true_path = self.path
|
||||
fade_region = self.get_fade_region(true_path)
|
||||
|
||||
sf = self.get_slow_factor()
|
||||
rt = 5
|
||||
future_vt = self.get_vector_time() + sf * rt
|
||||
self.wait((1.0 - (future_vt % 1.0)) / sf)
|
||||
# sf = self.get_slow_factor()
|
||||
# future_vt = self.get_vector_time() + sf * rt
|
||||
|
||||
label = VGroup(Integer(100, edge_to_fix=DR), Text("terms"))
|
||||
label.arrange(RIGHT, buff=0.15)
|
||||
|
@ -227,7 +229,6 @@ class CombineWavesToImage(FourierCirclesScene):
|
|||
label[0].set_value(self.n_shown_waves)
|
||||
|
||||
# Animate entry
|
||||
drawn_path = self.get_drawn_path(self.vectors[:self.n_shown_waves + 1])
|
||||
terms = self.get_terms(self.n_shown_waves)
|
||||
self.play(
|
||||
FadeIn(fade_region, lag_ratio=0.1),
|
||||
|
@ -241,16 +242,21 @@ class CombineWavesToImage(FourierCirclesScene):
|
|||
), lag_ratio=0.1),
|
||||
FadeIn(label),
|
||||
Write(terms),
|
||||
# self.slow_factor_tracker.animate.set_value(0.1),
|
||||
run_time=rt,
|
||||
)
|
||||
self.vector_clock.set_value(0)
|
||||
|
||||
# Set up drawn paths
|
||||
self.add(drawn_path, terms)
|
||||
drawn_path = self.get_drawn_path(self.vectors[:self.n_shown_waves + 1])
|
||||
dot = self.add_draw_dot(self.vectors[:self.n_shown_waves + 1])
|
||||
self.add(drawn_path, terms, dot)
|
||||
anims = [
|
||||
UpdateFromAlphaFunc(drawn_path, lambda m, a: m.set_stroke(interpolate_color(BLACK, YELLOW, a))),
|
||||
UpdateFromAlphaFunc(dot, lambda m, a: m.set_opacity(a)),
|
||||
]
|
||||
if self.remove_background_waves:
|
||||
self.play(
|
||||
fade_region.animate.set_height(2 * FRAME_WIDTH).set_opacity(0.05),
|
||||
*anims,
|
||||
run_time=2
|
||||
)
|
||||
self.remove(
|
||||
|
@ -260,7 +266,7 @@ class CombineWavesToImage(FourierCirclesScene):
|
|||
fade_region,
|
||||
)
|
||||
else:
|
||||
self.wait(2)
|
||||
self.play(*anims, run_time=2)
|
||||
|
||||
# Now add on new vectors one at a time
|
||||
drawn_paths = dict()
|
||||
|
@ -280,8 +286,8 @@ class CombineWavesToImage(FourierCirclesScene):
|
|||
drawn_paths[n] = self.get_drawn_path(self.vectors[:n])
|
||||
if n not in all_terms:
|
||||
all_terms[n] = self.get_terms(n)
|
||||
group[0].become(drawn_paths[n])
|
||||
group[1].become(all_terms[n])
|
||||
group.replace_submobject(0, drawn_paths[n])
|
||||
group.replace_submobject(1, all_terms[n])
|
||||
group[2][0].set_value(n)
|
||||
group[3].set_submobjects(self.vectors[:n])
|
||||
group[4].set_submobjects(self.circles[:n])
|
||||
|
@ -289,11 +295,14 @@ class CombineWavesToImage(FourierCirclesScene):
|
|||
self.style_vectors(group[3])
|
||||
self.style_circles(group[4])
|
||||
|
||||
if n > 25:
|
||||
group[4].set_stroke(opacity=(1.0 / (n - 25))**0.2)
|
||||
|
||||
group.update()
|
||||
return group
|
||||
|
||||
self.remove(self.vectors, self.circles, drawn_path, terms)
|
||||
|
||||
self.remove(self.vectors, self.circles, drawn_path, terms, dot)
|
||||
self.add_draw_dot(drawing_group[3])
|
||||
self.play(UpdateFromAlphaFunc(
|
||||
drawing_group, update_drawing_group,
|
||||
run_time=15,
|
||||
|
@ -303,24 +312,30 @@ class CombineWavesToImage(FourierCirclesScene):
|
|||
inf = Tex("\\infty")
|
||||
inf.match_height(label[0])
|
||||
inf.move_to(label[0], RIGHT)
|
||||
inf.set_fill(WHITE)
|
||||
|
||||
drawn_path = drawing_group[0]
|
||||
# self.add_path_fader(true_path, fade_rate=1)
|
||||
true_path.set_stroke(width=1.0)
|
||||
|
||||
self.add(drawn_path, true_path, self.circles, self.vectors)
|
||||
self.play(
|
||||
FadeIn(true_path),
|
||||
VFadeOut(drawn_path),
|
||||
UpdateFromAlphaFunc(drawn_path, lambda m, a: m.set_stroke(interpolate_color(YELLOW, BLACK, a))),
|
||||
VFadeIn(true_path),
|
||||
ChangeDecimalToValue(label[0], 1000),
|
||||
VFadeOut(label[0]),
|
||||
FadeIn(inf),
|
||||
run_time=3,
|
||||
)
|
||||
self.remove(drawn_path)
|
||||
self.wait(12)
|
||||
|
||||
##
|
||||
|
||||
def get_drawing(self):
|
||||
# drawing = self.get_peace_sign()
|
||||
svg = SVGMobject("/Users/grant/Dropbox/3Blue1Brown/videos/2022/infinity/monster-holding-heart.svg")
|
||||
svg = SVGMobject(self.drawing_file)
|
||||
drawing = svg[1]
|
||||
drawing.set_height(self.peace_sign_height)
|
||||
drawing.set_height(self.drawing_height)
|
||||
drawing.set_stroke(self.drawn_path_color, 2)
|
||||
return drawing
|
||||
|
||||
|
@ -355,47 +370,55 @@ class CombineWavesToImage(FourierCirclesScene):
|
|||
for k, circle in zip(it.count(0), circles):
|
||||
circle.set_stroke(width=mcsw / (1 + k / 4), opacity=1)
|
||||
|
||||
if len(circles) > 25:
|
||||
circles.set_stroke(opacity=(1.0 / (len(circles) - 25))**0.2)
|
||||
|
||||
return circles
|
||||
|
||||
def get_fade_region(self, path, n_disks=100):
|
||||
disk = Circle(radius=path.get_width() / 2)
|
||||
disks = VGroup(*(
|
||||
disk.copy().scale(sf)
|
||||
for sf in np.linspace(1.0, 2.25, n_disks)
|
||||
))
|
||||
disks.set_stroke(width=0)
|
||||
disks.set_fill(BLACK, opacity=3.0 / n_disks)
|
||||
return disks
|
||||
def get_fade_region(self, path):
|
||||
dot = GlowDot()
|
||||
dot.set_radius(1.0 * path.get_width())
|
||||
dot.set_glow_factor(0.2)
|
||||
dot.set_color(BLACK, 0.95)
|
||||
return dot
|
||||
|
||||
def get_drawn_path(self, vectors):
|
||||
if len(vectors) == 0:
|
||||
nv = len(vectors)
|
||||
if nv == 0:
|
||||
return VGroup()
|
||||
return super().get_drawn_path(vectors, stroke_width=4).set_stroke(self.drawn_path_color)
|
||||
fade_rate = 10 * np.exp(0.01 * (5 - nv)) + 1.0
|
||||
path = super().get_drawn_path(vectors, stroke_width=4, fade_rate=fade_rate)
|
||||
path.set_stroke(self.drawn_path_color)
|
||||
return path
|
||||
|
||||
def get_terms(self, n_terms, max_terms=40, max_width=12):
|
||||
tex_str = ""
|
||||
def get_terms(self, n_terms, max_terms=100, max_width=12):
|
||||
ks = list(range(-int(np.floor(n_terms / 2)), int(np.ceil(n_terms / 2))))
|
||||
ks.sort(key=abs)
|
||||
ks = ks[:max_terms]
|
||||
ks.sort()
|
||||
font_size = max(180 / n_terms, 5)
|
||||
terms = VGroup()
|
||||
for k in ks:
|
||||
tex_str += "c_{k} e^{k \\cdot 2 \\pi i \\cdot t} +".replace("k", str(k))
|
||||
terms.add(MTex("c_{k} e^{k \\cdot 2 \\pi i \\cdot t} +".replace("k", str(k))))
|
||||
if n_terms <= max_terms:
|
||||
tex_str = tex_str[:-1] # Remove trailing plus
|
||||
terms[-1][-1].set_opacity(0)
|
||||
else:
|
||||
tex_str = "\\cdots + " + tex_str + "\\cdots"
|
||||
terms.add_to_back(MTex("\\cdots + "))
|
||||
terms.add(MTex("\\cdots"))
|
||||
|
||||
font_size = 180 / n_terms
|
||||
result = Tex(tex_str, font_size=font_size)
|
||||
result.set_max_width(max_width)
|
||||
result.next_to(self.path, UP, MED_SMALL_BUFF)
|
||||
terms.arrange(RIGHT, SMALL_BUFF)
|
||||
terms.scale(font_size / 48)
|
||||
terms.set_max_width(max_width)
|
||||
terms.next_to(self.path, UP, SMALL_BUFF)
|
||||
|
||||
max_light_terms = 15
|
||||
if n_terms > max_light_terms:
|
||||
result.set_fill(opacity=max_light_terms / (n_terms - max_light_terms))
|
||||
result.set_stroke(width=0)
|
||||
terms.set_fill(opacity=max_light_terms / (n_terms - max_light_terms))
|
||||
terms.set_stroke(width=0)
|
||||
|
||||
return result
|
||||
return terms
|
||||
|
||||
def add_draw_dot(self, vectors):
|
||||
dot = GlowDot()
|
||||
dot.add_updater(lambda d: d.move_to(
|
||||
vectors[-1].get_end() if len(vectors) > 0 else ORIGIN
|
||||
))
|
||||
self.add(dot)
|
||||
return dot
|
||||
|
|
|
@ -210,7 +210,7 @@ class DecomposeAudioSegment(Scene):
|
|||
self.break_down_into_fourier_components()
|
||||
self.back_to_full_signal()
|
||||
|
||||
def add_full_waveform(self):
|
||||
def add_full_waveform(self, run_time=5):
|
||||
axes, graph = self.get_signal_graph()
|
||||
|
||||
self.add(axes)
|
||||
|
@ -224,7 +224,7 @@ class DecomposeAudioSegment(Scene):
|
|||
time_width=0.1,
|
||||
rate_func=linear,
|
||||
),
|
||||
run_time=5,
|
||||
run_time=run_time,
|
||||
)
|
||||
|
||||
self.axes = axes
|
||||
|
@ -261,20 +261,21 @@ class DecomposeAudioSegment(Scene):
|
|||
zoom_rect.target.set_stroke(width=0)
|
||||
axes.target.set_stroke(opacity=0)
|
||||
|
||||
self.play(Write(zoom_rect))
|
||||
self.play(
|
||||
*map(MoveToTarget, movers),
|
||||
run_time=run_time
|
||||
)
|
||||
self.remove(graph, axes)
|
||||
self.wait()
|
||||
|
||||
# Swap axes
|
||||
new_axes = Axes((-2, 12), (-1, 1, 0.25), width=FRAME_WIDTH + 1)
|
||||
new_axes.shift(LEFT_SIDE + RIGHT - new_axes.get_origin())
|
||||
|
||||
if fade_in_new_axes:
|
||||
self.play(FadeIn(new_axes))
|
||||
self.play(Write(zoom_rect))
|
||||
self.play(
|
||||
*map(MoveToTarget, movers),
|
||||
FadeIn(new_axes),
|
||||
run_time=run_time,
|
||||
)
|
||||
self.remove(graph, axes)
|
||||
|
||||
# Swap axes
|
||||
|
||||
# if fade_in_new_axes:
|
||||
# self.play(FadeIn(new_axes))
|
||||
|
||||
self.original_graph = graph
|
||||
self.original_axes = axes
|
||||
|
|
Loading…
Add table
Reference in a new issue