A few more Fourier circle animations

This commit is contained in:
Grant Sanderson
2019-04-04 14:30:39 -07:00
parent 982d437267
commit ca70bf8e3f

View File

@ -15,12 +15,21 @@ class FourierCirclesScene(Scene):
"circle_style": {
"stroke_width": 2,
},
"use_vectors": True,
"base_frequency": 1,
"slow_factor": 0.25,
"center_point": ORIGIN,
"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):
n = self.n_circles
@ -68,10 +77,16 @@ class FourierCirclesScene(Scene):
color=color,
**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_start(),
color=WHITE,
buff=0,
max_tip_length_to_length_ratio=0.1,
**self.circle_style,
)
circle.add(circle.radial_line)
@ -85,7 +100,7 @@ class FourierCirclesScene(Scene):
def update_circle(self, circle, dt):
circle.rotate(
self.slow_factor * circle.freq * dt * TAU
self.get_slow_factor() * circle.freq * dt * TAU
)
circle.move_to(circle.center_func())
return circle
@ -98,9 +113,11 @@ class FourierCirclesScene(Scene):
def get_rotating_vector(self, circle):
vector = Vector(RIGHT, color=WHITE)
vector.add_updater(lambda v: v.put_start_and_end_on(
*circle.radial_line.get_start_and_end()
vector.add_updater(lambda v, dt: v.put_start_and_end_on(
circle.get_center(),
circle.get_start(),
))
circle.vector = vector
return vector
def get_circle_end_path(self, circles, color=YELLOW):
@ -123,20 +140,20 @@ class FourierCirclesScene(Scene):
return path
# 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)
broken_path = CurvesAsSubmobjects(path)
broken_path.curr_time = 0
def update_path(path, dt):
alpha = path.curr_time * self.slow_factor
alpha = path.curr_time * self.get_slow_factor()
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 = 2 * (1 - (b % 1))
width = stroke_width * (1 - (b % 1))
sp.set_stroke(YELLOW, width=width)
path.curr_time += dt
return path
@ -168,7 +185,7 @@ class FourierCirclesScene(Scene):
top_point = wave_copies.get_top()
wave.creation = ShowCreation(
wave,
run_time=(1 / self.slow_factor),
run_time=(1 / self.get_slow_factor()),
rate_func=linear,
)
cycle_animation(wave.creation)
@ -178,7 +195,7 @@ class FourierCirclesScene(Scene):
def update_wave_copies(wcs):
index = int(
wave.creation.total_time * self.slow_factor
wave.creation.total_time * self.get_slow_factor()
)
wcs[:index].match_style(wave)
wcs[index:].set_stroke(width=0)
@ -290,7 +307,6 @@ class FourierOfPiSymbol(FourierCirclesScene):
1 / np.sqrt(k),
1,
))
print(circle.freq, abs(circle.coefficient))
# approx_path = self.get_circle_end_path(circles)
drawn_path = self.get_drawn_path(circles)
@ -323,36 +339,56 @@ class FourierOfTrebleClef(FourierOfPiSymbol):
"n_circles": 100,
"run_time": 10,
"start_drawn": True,
"file_name": "TrebleClef",
"height": 7.5,
}
def get_shape(self):
shape = SVGMobject(self.file_name)
return shape
def get_path(self):
path = SVGMobject("TrebleClef")
path = path.family_members_with_points()[0]
path.set_height(7.5)
shape = self.get_shape()
path = shape.family_members_with_points()[0]
path.set_height(self.height)
path.set_fill(opacity=0)
path.set_stroke(WHITE, 0)
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):
CONFIG = {
# "n_circles": 100,
"n_circles": 20,
"n_circles": 100,
"center_point": 2 * DOWN,
"n_top_circles": 9,
# "slow_factor": 0.1,
"path_height": 3,
}
def construct(self):
self.add_path()
self.add_circles()
self.wait(8)
self.organize_circles_in_a_row()
self.show_frequencies()
self.show_examples_for_frequencies()
self.show_as_vectors()
self.show_vector_sum()
self.moons_of_moons_of_moons()
self.tweak_starting_vectors()
def add_path(self):
@ -367,11 +403,9 @@ class ExplainCircleAnimations(FourierCirclesScene):
self.drawn_path = self.get_drawn_path(self.circles)
self.add(self.drawn_path)
self.wait(8)
def organize_circles_in_a_row(self):
circles = self.circles
top_circles = circles[:self.n_top_circles].deepcopy()
top_circles = circles[:self.n_top_circles].copy()
center_trackers = VGroup()
for circle in top_circles:
@ -436,6 +470,9 @@ class ExplainCircleAnimations(FourierCirclesScene):
)
self.wait(2)
self.freq_numbers = freq_numbers
self.freq_word = freq_word
def show_examples_for_frequencies(self):
top_circles = self.top_circles
c1, c2, c3 = [
@ -479,41 +516,179 @@ class ExplainCircleAnimations(FourierCirclesScene):
])
)
self.wait(3)
self.play(FadeOut(self.freq_word))
def show_as_vectors(self):
top_circles = self.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(
top_circles.set_stroke, {"width": 0.5},
FadeIn(top_vectors),
lines.fade, 1,
)
self.wait(3)
self.top_vectors = top_vectors
def show_vector_sum(self):
top_circles = self.top_circles
top_circles = self.top_circles
# trackers = self.center_trackers.deepcopy()
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(
FadeOut(self.path),
# FadeOut(self.path),
FadeOut(self.drawn_path),
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):
pass
new_circles = VGroup()
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):
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):
tex = TexMobject("f")
path = tex.family_members_with_points()[0]
def configure_path(self, path):
path.set_stroke(WHITE, 1)
path.set_fill(opacity=0)
path.set_fill(BLACK, opacity=1)
path.set_height(self.path_height)
path.move_to(self.center_point)
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)
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