mirror of
https://github.com/3b1b/manim.git
synced 2025-08-01 08:54:38 +08:00
A few more Fourier circle animations
This commit is contained in:
@ -15,12 +15,21 @@ class FourierCirclesScene(Scene):
|
|||||||
"circle_style": {
|
"circle_style": {
|
||||||
"stroke_width": 2,
|
"stroke_width": 2,
|
||||||
},
|
},
|
||||||
|
"use_vectors": True,
|
||||||
"base_frequency": 1,
|
"base_frequency": 1,
|
||||||
"slow_factor": 0.25,
|
"slow_factor": 0.25,
|
||||||
"center_point": ORIGIN,
|
"center_point": ORIGIN,
|
||||||
"parametric_function_step_size": 0.001,
|
"parametric_function_step_size": 0.001,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
self.slow_factor_tracker = ValueTracker(
|
||||||
|
self.slow_factor
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_slow_factor(self):
|
||||||
|
return self.slow_factor_tracker.get_value()
|
||||||
|
|
||||||
#
|
#
|
||||||
def get_freqs(self):
|
def get_freqs(self):
|
||||||
n = self.n_circles
|
n = self.n_circles
|
||||||
@ -68,10 +77,16 @@ class FourierCirclesScene(Scene):
|
|||||||
color=color,
|
color=color,
|
||||||
**self.circle_style,
|
**self.circle_style,
|
||||||
)
|
)
|
||||||
circle.radial_line = Line(
|
if self.use_vectors:
|
||||||
|
LineClass = Arrow
|
||||||
|
else:
|
||||||
|
LineClass = Line
|
||||||
|
circle.radial_line = LineClass(
|
||||||
circle.get_center(),
|
circle.get_center(),
|
||||||
circle.get_start(),
|
circle.get_start(),
|
||||||
color=WHITE,
|
color=WHITE,
|
||||||
|
buff=0,
|
||||||
|
max_tip_length_to_length_ratio=0.1,
|
||||||
**self.circle_style,
|
**self.circle_style,
|
||||||
)
|
)
|
||||||
circle.add(circle.radial_line)
|
circle.add(circle.radial_line)
|
||||||
@ -85,7 +100,7 @@ class FourierCirclesScene(Scene):
|
|||||||
|
|
||||||
def update_circle(self, circle, dt):
|
def update_circle(self, circle, dt):
|
||||||
circle.rotate(
|
circle.rotate(
|
||||||
self.slow_factor * circle.freq * dt * TAU
|
self.get_slow_factor() * circle.freq * dt * TAU
|
||||||
)
|
)
|
||||||
circle.move_to(circle.center_func())
|
circle.move_to(circle.center_func())
|
||||||
return circle
|
return circle
|
||||||
@ -98,9 +113,11 @@ class FourierCirclesScene(Scene):
|
|||||||
|
|
||||||
def get_rotating_vector(self, circle):
|
def get_rotating_vector(self, circle):
|
||||||
vector = Vector(RIGHT, color=WHITE)
|
vector = Vector(RIGHT, color=WHITE)
|
||||||
vector.add_updater(lambda v: v.put_start_and_end_on(
|
vector.add_updater(lambda v, dt: v.put_start_and_end_on(
|
||||||
*circle.radial_line.get_start_and_end()
|
circle.get_center(),
|
||||||
|
circle.get_start(),
|
||||||
))
|
))
|
||||||
|
circle.vector = vector
|
||||||
return vector
|
return vector
|
||||||
|
|
||||||
def get_circle_end_path(self, circles, color=YELLOW):
|
def get_circle_end_path(self, circles, color=YELLOW):
|
||||||
@ -123,20 +140,20 @@ class FourierCirclesScene(Scene):
|
|||||||
return path
|
return path
|
||||||
|
|
||||||
# TODO, this should be a general animated mobect
|
# TODO, this should be a general animated mobect
|
||||||
def get_drawn_path(self, circles, **kwargs):
|
def get_drawn_path(self, circles, stroke_width=2, **kwargs):
|
||||||
path = self.get_circle_end_path(circles, **kwargs)
|
path = self.get_circle_end_path(circles, **kwargs)
|
||||||
broken_path = CurvesAsSubmobjects(path)
|
broken_path = CurvesAsSubmobjects(path)
|
||||||
broken_path.curr_time = 0
|
broken_path.curr_time = 0
|
||||||
|
|
||||||
def update_path(path, dt):
|
def update_path(path, dt):
|
||||||
alpha = path.curr_time * self.slow_factor
|
alpha = path.curr_time * self.get_slow_factor()
|
||||||
n_curves = len(path)
|
n_curves = len(path)
|
||||||
for a, sp in zip(np.linspace(0, 1, n_curves), path):
|
for a, sp in zip(np.linspace(0, 1, n_curves), path):
|
||||||
b = alpha - a
|
b = alpha - a
|
||||||
if b < 0:
|
if b < 0:
|
||||||
width = 0
|
width = 0
|
||||||
else:
|
else:
|
||||||
width = 2 * (1 - (b % 1))
|
width = stroke_width * (1 - (b % 1))
|
||||||
sp.set_stroke(YELLOW, width=width)
|
sp.set_stroke(YELLOW, width=width)
|
||||||
path.curr_time += dt
|
path.curr_time += dt
|
||||||
return path
|
return path
|
||||||
@ -168,7 +185,7 @@ class FourierCirclesScene(Scene):
|
|||||||
top_point = wave_copies.get_top()
|
top_point = wave_copies.get_top()
|
||||||
wave.creation = ShowCreation(
|
wave.creation = ShowCreation(
|
||||||
wave,
|
wave,
|
||||||
run_time=(1 / self.slow_factor),
|
run_time=(1 / self.get_slow_factor()),
|
||||||
rate_func=linear,
|
rate_func=linear,
|
||||||
)
|
)
|
||||||
cycle_animation(wave.creation)
|
cycle_animation(wave.creation)
|
||||||
@ -178,7 +195,7 @@ class FourierCirclesScene(Scene):
|
|||||||
|
|
||||||
def update_wave_copies(wcs):
|
def update_wave_copies(wcs):
|
||||||
index = int(
|
index = int(
|
||||||
wave.creation.total_time * self.slow_factor
|
wave.creation.total_time * self.get_slow_factor()
|
||||||
)
|
)
|
||||||
wcs[:index].match_style(wave)
|
wcs[:index].match_style(wave)
|
||||||
wcs[index:].set_stroke(width=0)
|
wcs[index:].set_stroke(width=0)
|
||||||
@ -290,7 +307,6 @@ class FourierOfPiSymbol(FourierCirclesScene):
|
|||||||
1 / np.sqrt(k),
|
1 / np.sqrt(k),
|
||||||
1,
|
1,
|
||||||
))
|
))
|
||||||
print(circle.freq, abs(circle.coefficient))
|
|
||||||
|
|
||||||
# approx_path = self.get_circle_end_path(circles)
|
# approx_path = self.get_circle_end_path(circles)
|
||||||
drawn_path = self.get_drawn_path(circles)
|
drawn_path = self.get_drawn_path(circles)
|
||||||
@ -323,36 +339,56 @@ class FourierOfTrebleClef(FourierOfPiSymbol):
|
|||||||
"n_circles": 100,
|
"n_circles": 100,
|
||||||
"run_time": 10,
|
"run_time": 10,
|
||||||
"start_drawn": True,
|
"start_drawn": True,
|
||||||
|
"file_name": "TrebleClef",
|
||||||
|
"height": 7.5,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_shape(self):
|
||||||
|
shape = SVGMobject(self.file_name)
|
||||||
|
return shape
|
||||||
|
|
||||||
def get_path(self):
|
def get_path(self):
|
||||||
path = SVGMobject("TrebleClef")
|
shape = self.get_shape()
|
||||||
path = path.family_members_with_points()[0]
|
path = shape.family_members_with_points()[0]
|
||||||
path.set_height(7.5)
|
path.set_height(self.height)
|
||||||
path.set_fill(opacity=0)
|
path.set_fill(opacity=0)
|
||||||
path.set_stroke(WHITE, 0)
|
path.set_stroke(WHITE, 0)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
class FourierOfEighthNote(FourierOfTrebleClef):
|
||||||
|
CONFIG = {
|
||||||
|
"file_name": "EighthNote"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FourierOfN(FourierOfTrebleClef):
|
||||||
|
CONFIG = {
|
||||||
|
"height": 6,
|
||||||
|
"n_circles": 200,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_shape(self):
|
||||||
|
return TexMobject("N")
|
||||||
|
|
||||||
|
|
||||||
class ExplainCircleAnimations(FourierCirclesScene):
|
class ExplainCircleAnimations(FourierCirclesScene):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
# "n_circles": 100,
|
"n_circles": 100,
|
||||||
"n_circles": 20,
|
|
||||||
"center_point": 2 * DOWN,
|
"center_point": 2 * DOWN,
|
||||||
"n_top_circles": 9,
|
"n_top_circles": 9,
|
||||||
# "slow_factor": 0.1,
|
|
||||||
"path_height": 3,
|
"path_height": 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
def construct(self):
|
def construct(self):
|
||||||
self.add_path()
|
self.add_path()
|
||||||
self.add_circles()
|
self.add_circles()
|
||||||
|
self.wait(8)
|
||||||
self.organize_circles_in_a_row()
|
self.organize_circles_in_a_row()
|
||||||
self.show_frequencies()
|
self.show_frequencies()
|
||||||
self.show_examples_for_frequencies()
|
self.show_examples_for_frequencies()
|
||||||
self.show_as_vectors()
|
self.show_as_vectors()
|
||||||
self.show_vector_sum()
|
self.show_vector_sum()
|
||||||
self.moons_of_moons_of_moons()
|
|
||||||
self.tweak_starting_vectors()
|
self.tweak_starting_vectors()
|
||||||
|
|
||||||
def add_path(self):
|
def add_path(self):
|
||||||
@ -367,11 +403,9 @@ class ExplainCircleAnimations(FourierCirclesScene):
|
|||||||
self.drawn_path = self.get_drawn_path(self.circles)
|
self.drawn_path = self.get_drawn_path(self.circles)
|
||||||
self.add(self.drawn_path)
|
self.add(self.drawn_path)
|
||||||
|
|
||||||
self.wait(8)
|
|
||||||
|
|
||||||
def organize_circles_in_a_row(self):
|
def organize_circles_in_a_row(self):
|
||||||
circles = self.circles
|
circles = self.circles
|
||||||
top_circles = circles[:self.n_top_circles].deepcopy()
|
top_circles = circles[:self.n_top_circles].copy()
|
||||||
|
|
||||||
center_trackers = VGroup()
|
center_trackers = VGroup()
|
||||||
for circle in top_circles:
|
for circle in top_circles:
|
||||||
@ -436,6 +470,9 @@ class ExplainCircleAnimations(FourierCirclesScene):
|
|||||||
)
|
)
|
||||||
self.wait(2)
|
self.wait(2)
|
||||||
|
|
||||||
|
self.freq_numbers = freq_numbers
|
||||||
|
self.freq_word = freq_word
|
||||||
|
|
||||||
def show_examples_for_frequencies(self):
|
def show_examples_for_frequencies(self):
|
||||||
top_circles = self.top_circles
|
top_circles = self.top_circles
|
||||||
c1, c2, c3 = [
|
c1, c2, c3 = [
|
||||||
@ -479,41 +516,179 @@ class ExplainCircleAnimations(FourierCirclesScene):
|
|||||||
])
|
])
|
||||||
)
|
)
|
||||||
self.wait(3)
|
self.wait(3)
|
||||||
|
self.play(FadeOut(self.freq_word))
|
||||||
|
|
||||||
def show_as_vectors(self):
|
def show_as_vectors(self):
|
||||||
top_circles = self.top_circles
|
top_circles = self.top_circles
|
||||||
top_vectors = self.get_rotating_vectors(top_circles)
|
top_vectors = self.get_rotating_vectors(top_circles)
|
||||||
|
top_vectors.set_color(RED)
|
||||||
|
lines = VGroup(*filter(
|
||||||
|
lambda sm: isinstance(sm, Line),
|
||||||
|
top_circles.family_members_with_points()
|
||||||
|
))
|
||||||
|
|
||||||
|
self.add(lines, top_circles)
|
||||||
self.play(
|
self.play(
|
||||||
top_circles.set_stroke, {"width": 0.5},
|
|
||||||
FadeIn(top_vectors),
|
FadeIn(top_vectors),
|
||||||
|
lines.fade, 1,
|
||||||
)
|
)
|
||||||
self.wait(3)
|
self.wait(3)
|
||||||
|
|
||||||
self.top_vectors = top_vectors
|
self.top_vectors = top_vectors
|
||||||
|
|
||||||
def show_vector_sum(self):
|
def show_vector_sum(self):
|
||||||
top_circles = self.top_circles
|
# trackers = self.center_trackers.deepcopy()
|
||||||
top_circles = self.top_circles
|
trackers = self.center_trackers.copy()
|
||||||
|
trackers.sort(
|
||||||
|
submob_func=lambda t: abs(t.circle.freq)
|
||||||
|
)
|
||||||
|
plane = self.plane = NumberPlane(
|
||||||
|
x_min=-3,
|
||||||
|
x_max=3,
|
||||||
|
y_min=-2,
|
||||||
|
y_max=2,
|
||||||
|
axis_config={
|
||||||
|
"stroke_color": LIGHT_GREY,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
plane.set_stroke(width=1)
|
||||||
|
plane.fade(0.5)
|
||||||
|
plane.move_to(self.center_point)
|
||||||
|
|
||||||
self.play(
|
self.play(
|
||||||
FadeOut(self.path),
|
# FadeOut(self.path),
|
||||||
|
FadeOut(self.drawn_path),
|
||||||
FadeOut(self.circles),
|
FadeOut(self.circles),
|
||||||
|
self.slow_factor_tracker.set_value, 0.05,
|
||||||
)
|
)
|
||||||
|
self.add(plane, self.path)
|
||||||
|
self.play(FadeIn(plane))
|
||||||
|
|
||||||
def moons_of_moons_of_moons(self):
|
new_circles = VGroup()
|
||||||
pass
|
new_vectors = VGroup()
|
||||||
|
last_tracker = None
|
||||||
|
for tracker in trackers:
|
||||||
|
if last_tracker:
|
||||||
|
tracker.new_location_func = last_tracker.circle.get_start
|
||||||
|
else:
|
||||||
|
tracker.new_location_func = lambda: self.center_point
|
||||||
|
|
||||||
|
original_circle = tracker.circle
|
||||||
|
original_vector = tracker.circle.vector
|
||||||
|
tracker.circle = original_circle.copy()
|
||||||
|
tracker.circle.center_func = tracker.get_location
|
||||||
|
tracker.vector = original_vector.copy()
|
||||||
|
tracker.vector.clear_updaters()
|
||||||
|
tracker.vector.circle = tracker.circle
|
||||||
|
tracker.vector.add_updater(lambda v: v.put_start_and_end_on(
|
||||||
|
v.circle.get_center(),
|
||||||
|
v.circle.get_start(),
|
||||||
|
))
|
||||||
|
new_circles.add(tracker.circle)
|
||||||
|
new_vectors.add(tracker.vector)
|
||||||
|
|
||||||
|
self.add(tracker, tracker.circle, tracker.vector)
|
||||||
|
start_point = tracker.get_location()
|
||||||
|
self.play(
|
||||||
|
UpdateFromAlphaFunc(
|
||||||
|
tracker, lambda t, a: t.move_to(
|
||||||
|
interpolate(
|
||||||
|
start_point,
|
||||||
|
tracker.new_location_func(),
|
||||||
|
a,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
run_time=2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tracker.add_updater(lambda t: t.move_to(
|
||||||
|
t.new_location_func()
|
||||||
|
))
|
||||||
|
self.wait(2)
|
||||||
|
last_tracker = tracker
|
||||||
|
|
||||||
|
self.wait(3)
|
||||||
|
|
||||||
|
self.clear()
|
||||||
|
self.slow_factor_tracker.set_value(0.1)
|
||||||
|
self.add(
|
||||||
|
self.top_circles,
|
||||||
|
self.top_vectors,
|
||||||
|
self.freq_numbers,
|
||||||
|
self.path,
|
||||||
|
)
|
||||||
|
self.add_circles()
|
||||||
|
for tc in self.top_circles:
|
||||||
|
for c in self.circles:
|
||||||
|
if c.freq == tc.freq:
|
||||||
|
tc.rotate(
|
||||||
|
angle_of_vector(c.get_start() - c.get_center()) -
|
||||||
|
angle_of_vector(tc.get_start() - tc.get_center())
|
||||||
|
)
|
||||||
|
self.wait(10)
|
||||||
|
|
||||||
def tweak_starting_vectors(self):
|
def tweak_starting_vectors(self):
|
||||||
pass
|
top_circles = self.top_circles
|
||||||
|
top_vectors = self.top_vectors
|
||||||
|
circles = self.circles
|
||||||
|
path = self.path
|
||||||
|
drawn_path = self.drawn_path
|
||||||
|
|
||||||
|
new_path = self.get_new_path()
|
||||||
|
new_coefs = self.get_coefficients_of_path(new_path)
|
||||||
|
new_circles = self.get_circles(coefficients=new_coefs)
|
||||||
|
|
||||||
|
new_top_circles = VGroup()
|
||||||
|
new_top_vectors = VGroup()
|
||||||
|
for top_circle in top_circles:
|
||||||
|
for circle in new_circles:
|
||||||
|
if circle.freq == top_circle.freq:
|
||||||
|
new_top_circle = circle.copy()
|
||||||
|
new_top_circle.center_func = top_circle.get_center
|
||||||
|
new_top_vector = self.get_rotating_vector(
|
||||||
|
new_top_circle
|
||||||
|
)
|
||||||
|
new_top_circles.add(new_top_circle)
|
||||||
|
new_top_vectors.add(new_top_vector)
|
||||||
|
|
||||||
|
self.play(
|
||||||
|
self.slow_factor_tracker.set_value, 0,
|
||||||
|
FadeOut(drawn_path)
|
||||||
|
)
|
||||||
|
self.wait()
|
||||||
|
self.play(
|
||||||
|
ReplacementTransform(top_circles, new_top_circles),
|
||||||
|
ReplacementTransform(top_vectors, new_top_vectors),
|
||||||
|
ReplacementTransform(circles, new_circles),
|
||||||
|
# ReplacementTransform(path, new_path),
|
||||||
|
FadeOut(path),
|
||||||
|
run_time=3,
|
||||||
|
)
|
||||||
|
new_drawn_path = self.get_drawn_path(
|
||||||
|
new_circles, stroke_width=4,
|
||||||
|
)
|
||||||
|
self.add(new_drawn_path)
|
||||||
|
self.slow_factor_tracker.set_value(0.1)
|
||||||
|
self.wait(20)
|
||||||
|
|
||||||
#
|
#
|
||||||
def get_path(self):
|
def configure_path(self, path):
|
||||||
tex = TexMobject("f")
|
|
||||||
path = tex.family_members_with_points()[0]
|
|
||||||
path.set_stroke(WHITE, 1)
|
path.set_stroke(WHITE, 1)
|
||||||
path.set_fill(opacity=0)
|
path.set_fill(BLACK, opacity=1)
|
||||||
path.set_height(self.path_height)
|
path.set_height(self.path_height)
|
||||||
path.move_to(self.center_point)
|
path.move_to(self.center_point)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
def get_path(self):
|
||||||
|
tex = TexMobject("f")
|
||||||
|
path = tex.family_members_with_points()[0]
|
||||||
|
self.configure_path(path)
|
||||||
|
return path
|
||||||
# return Square().set_height(3)
|
# return Square().set_height(3)
|
||||||
|
|
||||||
|
def get_new_path(self):
|
||||||
|
shape = SVGMobject("TrebleClef")
|
||||||
|
path = shape.family_members_with_points()[0]
|
||||||
|
self.configure_path(path)
|
||||||
|
path.scale(1.5, about_edge=DOWN)
|
||||||
|
return path
|
||||||
|
Reference in New Issue
Block a user