Merge pull request #479 from 3b1b/ode

Ode
This commit is contained in:
Grant Sanderson 2019-03-20 21:29:25 -07:00 committed by GitHub
commit b2f973dce6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1799 additions and 330 deletions

View file

@ -2268,7 +2268,8 @@ class Thumbnail(TransformingAreasYCoord, MovingCameraScene):
ip = self.get_input_parallelogram(vect) ip = self.get_input_parallelogram(vect)
self.add_transformable_mobject(ip) self.add_transformable_mobject(ip)
self.apply_transposed_matrix([[2, -0.5], [1, 2]]) self.apply_transposed_matrix([[2, -0.5], [1, 2]])
# self.square.set_fill(YELLOW, 0.7) self.square.set_fill(opacity=0.7)
self.square.set_sheen(0.75, UR)
self.camera_frame.shift(UP) self.camera_frame.shift(UP)
words = TextMobject("Cramer's", "rule") words = TextMobject("Cramer's", "rule")

View file

@ -1,6 +1,22 @@
from active_projects.ode.part1.pendulum import * from active_projects.ode.part1.pendulum import *
from active_projects.ode.part1.staging import *
from active_projects.ode.part1.pi_scenes import *
OUTPUT_DIRECTORY = "ode/part1" OUTPUT_DIRECTORY = "ode/part1"
ALL_SCENE_CLASSES = [ ALL_SCENE_CLASSES = [
PendulumTest IntroducePendulum,
MultiplePendulumsOverlayed,
FormulasAreLies,
MediumAnglePendulum,
MediumHighAnglePendulum,
HighAnglePendulum,
LowAnglePendulum,
SomeOfYouWatching,
SmallAngleApproximationTex,
VeryLowAnglePendulum,
FollowThisThread,
StrogatzQuote,
# Something...
ShowGravityAcceleration,
BuildUpEquation,
] ]

View file

@ -1,4 +1,5 @@
from big_ol_pile_of_manim_imports import * from big_ol_pile_of_manim_imports import *
from active_projects.ode.part1.shared_constructs import *
class Pendulum(VGroup): class Pendulum(VGroup):
@ -19,9 +20,12 @@ class Pendulum(VGroup):
"weight_style": { "weight_style": {
"stroke_width": 0, "stroke_width": 0,
"fill_opacity": 1, "fill_opacity": 1,
"fill_color": DARK_GREY, "fill_color": GREY_BROWN,
"sheen_direction": UL, "sheen_direction": UL,
"sheen_factor": 0.5, "sheen_factor": 0.5,
"background_stroke_color": BLACK,
"background_stroke_width": 3,
"background_stroke_opacity": 0.5,
}, },
"dashed_line_config": { "dashed_line_config": {
"num_dashes": 25, "num_dashes": 25,
@ -36,7 +40,8 @@ class Pendulum(VGroup):
"velocity_vector_config": { "velocity_vector_config": {
"color": RED, "color": RED,
}, },
"n_steps_per_frame": 10, "theta_label_height": 0.25,
"n_steps_per_frame": 100,
} }
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -47,12 +52,15 @@ class Pendulum(VGroup):
self.rotating_group = VGroup(self.rod, self.weight) self.rotating_group = VGroup(self.rod, self.weight)
self.create_dashed_line() self.create_dashed_line()
self.create_angle_arc() self.create_angle_arc()
self.add_theta_label()
self.set_theta(self.initial_theta) self.set_theta(self.initial_theta)
self.update() self.update()
def create_fixed_point(self): def create_fixed_point(self):
self.fixed_point_tracker = VectorizedPoint(self.top_point) self.fixed_point_tracker = VectorizedPoint(self.top_point)
self.add(self.fixed_point_tracker)
return self
def create_rod(self): def create_rod(self):
rod = self.rod = Line(UP, DOWN) rod = self.rod = Line(UP, DOWN)
@ -87,11 +95,33 @@ class Pendulum(VGroup):
def add_velocity_vector(self): def add_velocity_vector(self):
def make_vector(): def make_vector():
vector = Vector(0.5 * self.get_omega() * RIGHT) omega = self.get_omega()
theta = self.get_theta()
vector = Vector(
0.5 * omega * RIGHT,
**self.velocity_vector_config,
)
vector.rotate(theta, about_point=ORIGIN)
vector.shift(self.rod.get_end())
return vector
self.velocity_vector = always_redraw(make_vector) self.velocity_vector = always_redraw(make_vector)
self.add(self.velocity_vector) self.add(self.velocity_vector)
return self return self
def add_theta_label(self):
label = self.theta_label = TexMobject("\\theta")
label.set_height(self.theta_label_height)
def update_label(l):
top = self.get_fixed_point()
arc_center = self.angle_arc.point_from_proportion(0.5)
vect = arc_center - top
vect = normalize(vect) * (1 + self.theta_label_height)
l.move_to(top + vect)
label.add_updater(update_label)
self.add(label)
# #
def get_theta(self): def get_theta(self):
theta = self.rod.get_angle() - self.dashed_line.get_angle() theta = self.rod.get_angle() - self.dashed_line.get_angle()
@ -141,11 +171,816 @@ class Pendulum(VGroup):
return self return self
class PendulumTest(Scene): class GravityVector(Vector):
def construct(self): CONFIG = {
pendulum = Pendulum( "color": YELLOW,
initial_theta=150 * DEGREES, "length_multiple": 1 / 9.8,
# TODO, continually update the length based
# on the pendulum's gravity?
}
def __init__(self, pendulum, **kwargs):
super().__init__(DOWN, **kwargs)
self.pendulum = pendulum
self.scale(self.length_multiple * pendulum.gravity)
self.attach_to_pendulum(pendulum)
def attach_to_pendulum(self, pendulum):
self.add_updater(lambda m: m.shift(
pendulum.weight.get_center() - self.get_start(),
))
def add_component_lines(self):
self.component_lines = always_redraw(self.create_component_lines)
self.add(self.component_lines)
def create_component_lines(self):
theta = self.pendulum.get_theta()
x_new = rotate(RIGHT, theta)
base = self.get_start()
tip = self.get_end()
vect = tip - base
corner = base + x_new * np.dot(vect, x_new)
kw = {"dash_length": 0.025}
return VGroup(
DashedLine(base, corner, **kw),
DashedLine(corner, tip, **kw),
) )
self.add(pendulum)
class ThetaValueDisplay(VGroup):
CONFIG = {
}
def __init__(self, **kwargs):
super().__init__(**kwargs)
class ThetaVsTAxes(Axes):
CONFIG = {
"x_min": 0,
"x_max": 8,
"y_min": -PI / 2,
"y_max": PI / 2,
"y_axis_config": {
"tick_frequency": PI / 8,
"unit_size": 1.5,
},
"number_line_config": {
"color": "#EEEEEE",
"stroke_width": 2,
"include_tip": False,
},
"graph_style": {
"stroke_color": GREEN,
"stroke_width": 3,
"fill_opacity": 0,
},
}
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.add_labels()
def add_axes(self):
self.axes = Axes(**self.axes_config)
self.add(self.axes)
def add_labels(self):
x_axis = self.get_x_axis()
y_axis = self.get_y_axis()
t_label = self.t_label = TexMobject("t")
t_label.next_to(x_axis.get_right(), UP, MED_SMALL_BUFF)
x_axis.label = t_label
x_axis.add(t_label)
theta_label = self.theta_label = TexMobject("\\theta(t)")
theta_label.next_to(y_axis.get_top(), UP, SMALL_BUFF)
y_axis.label = theta_label
y_axis.add(theta_label)
x_axis.add_numbers()
y_axis.add(self.get_y_axis_coordinates(y_axis))
def get_y_axis_coordinates(self, y_axis):
texs = [
# "\\pi \\over 4",
# "\\pi \\over 2",
# "3 \\pi \\over 4",
# "\\pi",
"\\pi / 4",
"\\pi / 2",
"3 \\pi / 4",
"\\pi",
]
values = np.arange(1, 5) * PI / 4
labels = VGroup()
for pos_tex, pos_value in zip(texs, values):
neg_tex = "-" + pos_tex
neg_value = -1 * pos_value
for tex, value in (pos_tex, pos_value), (neg_tex, neg_value):
if value > self.y_max or value < self.y_min:
continue
symbol = TexMobject(tex)
symbol.scale(0.5)
point = y_axis.number_to_point(value)
symbol.next_to(point, LEFT, MED_SMALL_BUFF)
labels.add(symbol)
return labels
def get_live_drawn_graph(self, pendulum,
t_max=None,
t_step=1.0 / 60,
**style):
style = merge_dicts_recursively(self.graph_style, style)
if t_max is None:
t_max = self.x_max
graph = VMobject()
graph.set_style(**style)
graph.all_coords = [(0, pendulum.get_theta())]
graph.time = 0
graph.time_of_last_addition = 0
def update_graph(graph, dt):
graph.time += dt
if graph.time > t_max:
graph.remove_updater(update_graph)
return
new_coords = (graph.time, pendulum.get_theta())
if graph.time - graph.time_of_last_addition >= t_step:
graph.all_coords.append(new_coords)
graph.time_of_last_addition = graph.time
points = [
self.coords_to_point(*coords)
for coords in [*graph.all_coords, new_coords]
]
graph.set_points_smoothly(points)
graph.add_updater(update_graph)
return graph
# Scenes
class IntroducePendulum(PiCreatureScene, MovingCameraScene):
CONFIG = {
"pendulum_config": {
"length": 3,
"top_point": 4 * RIGHT,
"weight_diameter": 0.35,
},
"theta_vs_t_axes_config": {
"y_max": PI / 4,
"y_min": -PI / 4,
"y_axis_config": {
"tick_frequency": PI / 16,
"unit_size": 2,
"tip_length": 0.3,
},
"number_line_config": {
"stroke_width": 2,
}
},
}
def setup(self):
MovingCameraScene.setup(self)
PiCreatureScene.setup(self)
def construct(self):
self.add_pendulum()
self.label_pi_creatures()
self.label_pendulum()
self.add_graph()
self.show_graph_period()
self.show_length_and_gravity()
self.tweak_length_and_gravity()
def create_pi_creatures(self):
randy = Randolph(color=BLUE_C)
morty = Mortimer(color=MAROON_E)
creatures = VGroup(randy, morty)
creatures.scale(0.5)
creatures.arrange(RIGHT, buff=2.5)
creatures.to_corner(DR)
return creatures
def add_pendulum(self):
pendulum = self.pendulum = Pendulum(**self.pendulum_config)
pendulum.start_swinging() pendulum.start_swinging()
frame = self.camera_frame
frame.save_state()
frame.scale(0.5)
frame.move_to(pendulum.dashed_line)
self.add(pendulum, frame)
def label_pi_creatures(self):
randy, morty = self.pi_creatures
randy_label = TextMobject("Physics\\\\", "student")
morty_label = TextMobject("Physics\\\\", "teacher")
labels = VGroup(randy_label, morty_label)
labels.scale(0.5)
randy_label.next_to(randy, UP, LARGE_BUFF)
morty_label.next_to(morty, UP, LARGE_BUFF)
for label, pi in zip(labels, self.pi_creatures):
label.arrow = Arrow(
label.get_bottom(), pi.eyes.get_top()
)
label.arrow.set_color(WHITE)
label.arrow.set_stroke(width=5)
morty.labels = VGroup(
morty_label,
morty_label.arrow,
)
self.play(
FadeInFromDown(randy_label),
GrowArrow(randy_label.arrow),
randy.change, "hooray",
)
self.play(
Animation(self.pendulum.fixed_point_tracker),
TransformFromCopy(randy_label[0], morty_label[0]),
FadeIn(morty_label[1]),
GrowArrow(morty_label.arrow),
morty.change, "raise_right_hand",
)
self.wait()
def label_pendulum(self):
pendulum = self.pendulum
randy, morty = self.pi_creatures
label = pendulum.theta_label
rect = SurroundingRectangle(label, buff=0.5 * SMALL_BUFF)
rect.add_updater(lambda r: r.move_to(label))
self.add(rect)
self.play(
ShowCreationThenFadeOut(rect),
ShowCreationThenDestruction(
label.copy().set_style(
fill_opacity=0,
stroke_color=PINK,
stroke_width=2,
)
),
randy.change, "pondering",
morty.change, "pondering",
)
self.wait()
def add_graph(self):
axes = self.axes = ThetaVsTAxes(**self.theta_vs_t_axes_config)
axes.y_axis.label.next_to(axes.y_axis, UP, buff=0)
axes.to_corner(UL)
self.play(
Restore(self.camera_frame),
DrawBorderThenFill(
axes,
rate_func=squish_rate_func(smooth, 0.5, 1),
lag_ratio=0.9,
),
Transform(
self.pendulum.theta_label.copy().clear_updaters(),
axes.y_axis.label.copy(),
remover=True,
rate_func=squish_rate_func(smooth, 0, 0.8),
),
run_time=3,
)
self.wait(2)
self.graph = axes.get_live_drawn_graph(self.pendulum)
self.add(self.graph)
self.wait(4)
def show_graph_period(self):
pendulum = self.pendulum
axes = self.axes
period = self.period = TAU * np.sqrt(
pendulum.length / pendulum.gravity
)
amplitude = pendulum.initial_theta
line = Line(
axes.coords_to_point(0, amplitude),
axes.coords_to_point(period, amplitude),
)
line.shift(SMALL_BUFF * RIGHT)
brace = Brace(line, UP, buff=SMALL_BUFF)
brace.add_to_back(brace.copy().set_style(BLACK, 10))
formula = TexMobject(
"\\sqrt{\\,", "2\\pi", "L", "/", "g", "}",
tex_to_color_map={
"L": BLUE,
"g": YELLOW,
}
)
formula.next_to(brace, UP, SMALL_BUFF)
self.period_formula = formula
self.period_brace = brace
self.play(
GrowFromCenter(brace),
FadeInFromDown(formula),
)
self.wait(2)
def show_length_and_gravity(self):
formula = self.period_formula
L = formula.get_part_by_tex("L")
g = formula.get_part_by_tex("g")
rod = self.pendulum.rod
new_rod = rod.copy()
new_rod.set_stroke(BLUE, 7)
new_rod.add_updater(lambda r: r.put_start_and_end_on(
*rod.get_start_and_end()
))
g_vect = GravityVector(
self.pendulum,
length_multiple=0.5 / 9.8,
)
self.play(ShowCreationThenDestructionAround(L))
dot = Dot(fill_opacity=0.25)
dot.move_to(L)
self.play(
ShowCreation(new_rod),
dot.move_to, new_rod,
dot.fade, 1,
)
self.remove(dot)
self.play(FadeOut(new_rod))
self.wait()
self.play(ShowCreationThenDestructionAround(g))
dot.move_to(g)
dot.set_fill(opacity=0.5)
self.play(
GrowArrow(g_vect),
dot.move_to, g_vect,
dot.fade, 1,
)
self.remove(dot)
self.wait(2)
self.gravity_vector = g_vect
def tweak_length_and_gravity(self):
pendulum = self.pendulum
axes = self.axes
graph = self.graph
brace = self.period_brace
formula = self.period_formula
g_vect = self.gravity_vector
randy, morty = self.pi_creatures
graph.clear_updaters()
period2 = self.period * np.sqrt(2)
period3 = self.period / np.sqrt(2)
amplitude = pendulum.initial_theta
graph2, graph3 = [
axes.get_graph(
lambda t: amplitude * np.cos(TAU * t / p),
color=RED,
)
for p in (period2, period3)
]
formula.add_updater(lambda m: m.next_to(
brace, UP, SMALL_BUFF
))
new_pendulum_config = dict(self.pendulum_config)
new_pendulum_config["length"] *= 2
new_pendulum_config["top_point"] += 3.5 * UP
# new_pendulum_config["initial_theta"] = pendulum.get_theta()
new_pendulum = Pendulum(**new_pendulum_config)
down_vectors = VGroup(*[
Vector(0.5 * DOWN)
for x in range(10 * 150)
])
down_vectors.arrange_in_grid(10, 150, buff=MED_SMALL_BUFF)
down_vectors.set_color_by_gradient(BLUE, RED)
# for vect in down_vectors:
# vect.shift(0.1 * np.random.random(3))
down_vectors.to_edge(RIGHT)
self.play(randy.change, "happy")
self.play(
ReplacementTransform(pendulum, new_pendulum),
morty.change, "horrified",
morty.shift, 3 * RIGHT,
morty.labels.shift, 3 * RIGHT,
)
self.remove(morty, morty.labels)
g_vect.attach_to_pendulum(new_pendulum)
new_pendulum.start_swinging()
self.play(
ReplacementTransform(graph, graph2),
brace.stretch, np.sqrt(2), 0, {"about_edge": LEFT},
)
self.add(g_vect)
self.wait(3)
new_pendulum.gravity *= 4
g_vect.scale(2)
self.play(
FadeOut(graph2),
LaggedStart(*[
GrowArrow(v, rate_func=there_and_back)
for v in down_vectors
], lag_ratio=0.0005, run_time=2, remover=True)
)
self.play(
FadeIn(graph3),
brace.stretch, 0.5, 0, {"about_edge": LEFT},
)
self.wait(6)
class MultiplePendulumsOverlayed(Scene):
CONFIG = {
"initial_thetas": [
150 * DEGREES,
90 * DEGREES,
60 * DEGREES,
30 * DEGREES,
10 * DEGREES,
],
"weight_colors": [
PINK, RED, GREEN, BLUE, GREY,
],
"pendulum_config": {
"top_point": ORIGIN,
"length": 3,
},
}
def construct(self):
pendulums = VGroup(*[
Pendulum(
initial_theta=theta,
weight_style={
"fill_color": wc,
"fill_opacity": 0.5,
},
**self.pendulum_config,
)
for theta, wc in zip(
self.initial_thetas,
self.weight_colors,
)
])
for pendulum in pendulums:
pendulum.start_swinging()
pendulum.remove(pendulum.theta_label)
randy = Randolph(color=BLUE_C)
randy.to_corner(DL)
randy.add_updater(lambda r: r.look_at(pendulums[0].weight))
axes = ThetaVsTAxes(
x_max=20,
y_axis_config={
"unit_size": 0.5,
"tip_length": 0.3,
},
)
axes.to_corner(UL)
graphs = VGroup(*[
axes.get_live_drawn_graph(
pendulum,
stroke_color=pendulum.weight.get_color(),
stroke_width=1,
)
for pendulum in pendulums
])
self.add(pendulums)
self.add(axes, *graphs)
self.play(randy.change, "sassy")
self.wait(2)
self.play(Blink(randy))
self.wait(5)
self.play(randy.change, "angry")
self.play(Blink(randy))
self.wait(10) self.wait(10)
class LowAnglePendulum(Scene):
CONFIG = {
"pendulum_config": {
"initial_theta": 20 * DEGREES,
"length": 2.0,
"damping": 0,
"top_point": ORIGIN,
},
"axes_config": {
"y_axis_config": {"unit_size": 0.75},
"x_axis_config": {
"unit_size": 0.5,
"numbers_to_show": range(2, 25, 2),
"number_scale_val": 0.5,
},
"x_max": 25,
"number_line_config": {
"tip_length": 0.3,
"stroke_width": 2,
}
},
"axes_corner": UL,
}
def construct(self):
pendulum = Pendulum(**self.pendulum_config)
axes = ThetaVsTAxes(**self.axes_config)
axes.center()
axes.to_corner(self.axes_corner, buff=LARGE_BUFF)
graph = axes.get_live_drawn_graph(pendulum)
L = pendulum.length
g = pendulum.gravity
theta0 = pendulum.initial_theta
prediction = axes.get_graph(
lambda t: theta0 * np.cos(t * np.sqrt(g / L))
)
dashed_prediction = DashedVMobject(prediction, num_dashes=300)
dashed_prediction.set_stroke(WHITE, 1)
prediction_formula = TexMobject(
"\\theta_0", "\\cos(\\sqrt{g / L} \\cdot t)"
)
prediction_formula.scale(0.75)
prediction_formula.next_to(
dashed_prediction, UP, SMALL_BUFF,
)
theta0 = prediction_formula.get_part_by_tex("\\theta_0")
theta0_brace = Brace(theta0, UP, buff=SMALL_BUFF)
theta0_brace.stretch(0.5, 1, about_edge=DOWN)
theta0_label = Integer(
pendulum.initial_theta * 180 / PI,
unit="^\\circ"
)
theta0_label.scale(0.75)
theta0_label.next_to(theta0_brace, UP, SMALL_BUFF)
group = VGroup(theta0_brace, theta0_label, prediction_formula)
group.shift_onto_screen(buff=MED_SMALL_BUFF)
self.add(axes, dashed_prediction, pendulum)
self.play(
ShowCreation(dashed_prediction, run_time=2),
FadeInFromDown(prediction_formula),
FadeInFromDown(theta0_brace),
FadeInFromDown(theta0_label),
)
self.play(
ShowCreationThenFadeAround(theta0_label),
ShowCreationThenFadeAround(pendulum.theta_label),
)
self.wait()
pendulum.start_swinging()
self.add(graph)
self.wait(30)
class ApproxWordsLowAnglePendulum(Scene):
def construct(self):
period = TexMobject(
"\\text{Period}", "\\approx",
"2\\pi \\sqrt{\\,{L} / {g}}",
**Lg_formula_config
)
checkmark = TexMobject("\\checkmark")
checkmark.set_color(GREEN)
checkmark.scale(2)
checkmark.next_to(period, RIGHT, MED_LARGE_BUFF)
self.add(period, checkmark)
class MediumAnglePendulum(LowAnglePendulum):
CONFIG = {
"pendulum_config": {
"initial_theta": 50 * DEGREES,
"n_steps_per_frame": 1000,
},
"axes_config": {
"y_axis_config": {"unit_size": 0.75},
"y_max": PI / 2,
"y_min": -PI / 2,
"number_line_config": {
"tip_length": 0.3,
"stroke_width": 2,
}
},
"pendulum_shift_vect": 1 * RIGHT,
}
class MediumHighAnglePendulum(MediumAnglePendulum):
CONFIG = {
"pendulum_config": {
"initial_theta": 90 * DEGREES,
"n_steps_per_frame": 1000,
},
}
class HighAnglePendulum(LowAnglePendulum):
CONFIG = {
"pendulum_config": {
"initial_theta": 175 * DEGREES,
"n_steps_per_frame": 1000,
"top_point": 1.5 * DOWN,
"length": 2,
},
"axes_config": {
"y_axis_config": {"unit_size": 0.5},
"y_max": PI,
"y_min": -PI,
"number_line_config": {
"tip_length": 0.3,
"stroke_width": 2,
}
},
"pendulum_shift_vect": 1 * RIGHT,
}
class VeryLowAnglePendulum(LowAnglePendulum):
CONFIG = {
"pendulum_config": {
"initial_theta": 10 * DEGREES,
"n_steps_per_frame": 1000,
"top_point": ORIGIN,
"length": 3,
},
"axes_config": {
"y_axis_config": {"unit_size": 2},
"y_max": PI / 4,
"y_min": -PI / 4,
"number_line_config": {
"tip_length": 0.3,
"stroke_width": 2,
}
},
"pendulum_shift_vect": 1 * RIGHT,
}
class BuildUpEquation(MovingCameraScene):
CONFIG = {
"pendulum_config": {
"length": 5,
"top_point": 3 * UP,
"initial_theta": 45 * DEGREES,
},
"g_vect_config": {
"length_multiple": 0.25,
},
"tan_line_color": BLUE,
"perp_line_color": PINK,
}
def construct(self):
self.add_pendulum()
self.show_constraint()
self.break_g_vect_into_components()
self.show_angle_geometry()
self.show_gsin_formula()
self.show_acceleration_at_different_angles()
self.ask_about_what_to_do()
self.show_velocity_and_position()
self.show_derivatives()
self.show_equation()
self.talk_about_sine_component()
self.add_air_resistance()
def add_pendulum(self):
self.pendulum = Pendulum(**self.pendulum_config)
self.add(self.pendulum)
def show_constraint(self):
pendulum = self.pendulum
weight = pendulum.weight
g_vect = self.g_vect = GravityVector(
pendulum, **self.g_vect_config,
)
g_word = self.g_word = TextMobject("Gravity")
g_word.rotate(-90 * DEGREES)
g_word.scale(0.75)
g_word.add_updater(lambda m: m.next_to(
g_vect, RIGHT, buff=-SMALL_BUFF,
))
theta_tracker = ValueTracker(pendulum.get_theta())
p = weight.get_center()
path = CubicBezier([p, p + 3 * DOWN, p + 3 * UP, p])
g_word.suspend_updating()
self.play(
GrowArrow(g_vect),
FadeInFrom(g_word, UP, lag_ratio=0.1),
)
g_word.resume_updating()
self.play(MoveAlongPath(weight, path, run_time=2))
self.wait()
pendulum.add_updater(lambda p: p.set_theta(
theta_tracker.get_value()
))
arcs = VGroup()
for u in [-1, 2, -1]:
d_theta = 40 * DEGREES * u
arc = Arc(
start_angle=pendulum.get_theta() - 90 * DEGREES,
angle=d_theta,
radius=pendulum.length,
arc_center=pendulum.get_fixed_point(),
stroke_width=2,
stroke_color=RED,
stroke_opacity=0.5,
)
self.play(
theta_tracker.increment_value, d_theta,
ShowCreation(arc)
)
arcs.add(arc)
pendulum.clear_updaters()
self.wait()
self.play(FadeOut(arc))
def break_g_vect_into_components(self):
g_vect = self.g_vect
g_vect.component_lines = always_redraw(
g_vect.create_component_lines
)
tan_line, perp_line = g_vect.component_lines
g_vect.tangent = always_redraw(lambda: Arrow(
tan_line.get_start(),
tan_line.get_end(),
buff=0,
color=self.tan_line_color,
))
g_vect.perp = always_redraw(lambda: Arrow(
perp_line.get_start(),
perp_line.get_end(),
buff=0,
color=self.perp_line_color,
))
self.play(
ShowCreation(g_vect.component_lines),
)
self.play(GrowArrow(g_vect.tangent))
self.wait()
self.play(GrowArrow(g_vect.perp))
self.wait()
def show_angle_geometry(self):
g_vect = self.g_vect
def show_gsin_formula(self):
pass
def show_acceleration_at_different_angles(self):
pass
def ask_about_what_to_do(self):
pass
def show_velocity_and_position(self):
pass
def show_derivatives(self):
pass
def show_equation(self):
pass
def talk_about_sine_component(self):
pass
def add_air_resistance(self):
pass
class NewSceneName(Scene):
def construct(self):
pass

View file

@ -0,0 +1,108 @@
from big_ol_pile_of_manim_imports import *
from active_projects.ode.part1.shared_constructs import *
class SomeOfYouWatching(TeacherStudentsScene):
CONFIG = {
"camera_config": {
"background_color": DARKER_GREY,
}
}
def construct(self):
screen = self.screen
screen.scale(1.25, about_edge=UL)
screen.set_fill(BLACK, 1)
self.add(screen)
self.teacher.change("raise_right_hand")
for student in self.students:
student.change("pondering", screen)
self.student_says(
"Well...yeah",
target_mode="tease"
)
self.wait(3)
class FormulasAreLies(PiCreatureScene):
def construct(self):
you = self.pi_creature
t2c = {
"{L}": BLUE,
"{g}": YELLOW,
"\\theta_0": WHITE,
"\\sqrt{\\,": WHITE,
}
kwargs = {"tex_to_color_map": t2c}
period_eq = TexMobject(
"\\text{Period} = 2\\pi \\sqrt{\\,{L} / {g}}",
**kwargs
)
theta_eq = TexMobject(
"\\theta(t) = \\theta_0 \\cos\\left("
"\\sqrt{\\,{L} / {g}} \\cdot t"
"\\right)",
**kwargs
)
equations = VGroup(theta_eq, period_eq)
equations.arrange(DOWN, buff=LARGE_BUFF)
for eq in period_eq, theta_eq:
i = eq.index_of_part_by_tex("\\sqrt")
eq.sqrt_part = eq[i:i + 4]
theta0 = theta_eq.get_part_by_tex("\\theta_0")
theta0_words = TextMobject("Starting angle")
theta0_words.next_to(theta0, UL)
theta0_words.shift(UP + 0.5 * RIGHT)
arrow = Arrow(
theta0_words.get_bottom(),
theta0,
color=WHITE,
tip_length=0.25,
)
bubble = SpeechBubble()
bubble.pin_to(you)
bubble.write("Lies!")
bubble.content.scale(2)
bubble.resize_to_content()
self.add(period_eq)
you.change("pondering", period_eq)
self.wait()
theta_eq.remove(*theta_eq.sqrt_part)
self.play(
TransformFromCopy(
period_eq.sqrt_part,
theta_eq.sqrt_part,
),
FadeIn(theta_eq)
)
theta_eq.add(*theta_eq.sqrt_part)
self.play(
FadeInFrom(theta0_words, LEFT),
GrowArrow(arrow),
)
self.wait()
self.play(you.change, "confused")
self.wait(0)
self.play(
you.change, "angry",
ShowCreation(bubble),
FadeInFromPoint(bubble.content, you.mouth),
equations.to_edge, LEFT,
FadeOut(arrow),
FadeOut(theta0_words),
)
self.wait()
def create_pi_creature(self):
return You().flip().to_corner(DR)
class NewSceneName(Scene):
def construct(self):
pass

View file

@ -0,0 +1,16 @@
from big_ol_pile_of_manim_imports import *
Lg_formula_config = {
"tex_to_color_map": {
"\\theta_0": WHITE,
"{L}": BLUE,
"{g}": YELLOW,
},
}
class You(PiCreature):
CONFIG = {
"color": BLUE_C,
}

View file

@ -0,0 +1,414 @@
from big_ol_pile_of_manim_imports import *
from active_projects.ode.part1.shared_constructs import *
def pendulum_vector_field(point, mu=0.1, g=9.8, L=3):
theta, omega = point[:2]
return np.array([
omega,
-np.sqrt(g / L) * np.sin(theta) - mu * omega,
0,
])
# Scenes
class VectorFieldTest(Scene):
def construct(self):
plane = NumberPlane(
# axis_config={"unit_size": 2}
)
mu_tracker = ValueTracker(1)
field = VectorField(
lambda p: pendulum_vector_field(
plane.point_to_coords(p),
mu=mu_tracker.get_value()
),
delta_x=0.5,
delta_y=0.5,
max_magnitude=4,
opacity=0.5,
# length_func=lambda norm: norm,
)
stream_lines = StreamLines(
field.func,
delta_x=0.5,
delta_y=0.5,
)
animated_stream_lines = AnimatedStreamLines(
stream_lines,
line_anim_class=ShowPassingFlashWithThinningStrokeWidth,
)
self.add(plane, field, animated_stream_lines)
self.wait(10)
class SmallAngleApproximationTex(Scene):
def construct(self):
approx = TexMobject(
"\\sin", "(", "\\theta", ") \\approx \\theta",
tex_to_color_map={"\\theta": RED},
arg_separator="",
)
implies = TexMobject("\\Downarrow")
period = TexMobject(
"\\text{Period}", "\\approx",
"2\\pi \\sqrt{\\,{L} / {g}}",
**Lg_formula_config,
)
group = VGroup(approx, implies, period)
group.arrange(DOWN)
approx_brace = Brace(approx, UP, buff=SMALL_BUFF)
approx_words = TextMobject(
"For small $\\theta$",
tex_to_color_map={"$\\theta$": RED},
)
approx_words.scale(0.75)
approx_words.next_to(approx_brace, UP, SMALL_BUFF)
self.add(approx, approx_brace, approx_words)
self.play(
Write(implies),
FadeInFrom(period, LEFT)
)
self.wait()
class FollowThisThread(Scene):
CONFIG = {
"screen_rect_style": {
"stroke_width": 2,
"stroke_color": WHITE,
"fill_opacity": 1,
"fill_color": DARKER_GREY,
}
}
def construct(self):
self.show_thumbnails()
self.show_words()
def show_thumbnails(self):
# TODO, replace each of these with a picture?
thumbnails = self.thumbnails = VGroup(
ScreenRectangle(**self.screen_rect_style),
ScreenRectangle(**self.screen_rect_style),
ScreenRectangle(**self.screen_rect_style),
ScreenRectangle(**self.screen_rect_style),
ScreenRectangle(**self.screen_rect_style),
)
n = len(thumbnails)
thumbnails.set_height(1.5)
line = self.line = CubicBezier([
[-5, 3, 0],
[3, 3, 0],
[-3, -3, 0],
[5, -3, 0],
])
for thumbnail, a in zip(thumbnails, np.linspace(0, 1, n)):
thumbnail.move_to(line.point_from_proportion(a))
self.play(
ShowCreation(
line,
rate_func=lambda t: np.clip(t * (n + 1) / n, 0, 1)
),
LaggedStart(*[
GrowFromCenter(
thumbnail,
rate_func=squish_rate_func(
smooth,
0, 0.7,
)
)
for thumbnail in thumbnails
], lag_ratio=1),
run_time=5
)
def show_words(self):
words = VGroup(
TextMobject("Generalize"),
TextMobject("Put in context"),
TextMobject("Modify"),
)
# words.arrange(DOWN, aligned_edge=LEFT, buff=LARGE_BUFF)
words.scale(1.5)
words.to_corner(UR)
words.add_to_back(VectorizedPoint(words.get_center()))
words.add(VectorizedPoint(words.get_center()))
diffEq = TextMobject("Differential\\\\equations")
diffEq.scale(1.5)
diffEq.to_corner(DL, buff=LARGE_BUFF)
for word1, word2 in zip(words, words[1:]):
self.play(
FadeInFromDown(word2),
FadeOutAndShift(word1, UP),
)
self.wait()
self.play(
ReplacementTransform(
VGroup(self.thumbnails).copy().fade(1),
diffEq,
lag_ratio=0.01,
)
)
self.wait()
class StrogatzQuote(Scene):
def construct(self):
law_words = "laws of physics"
language_words = "language of differential equations"
author = "-Steven Strogatz"
quote = TextMobject(
"""
\\Large
``Since Newton, mankind has come to realize
that the laws of physics are always expressed
in the language of differential equations.''\\\\
""" + author,
alignment="",
arg_separator=" ",
substrings_to_isolate=[law_words, language_words, author]
)
law_part = quote.get_part_by_tex(law_words)
language_part = quote.get_part_by_tex(language_words)
author_part = quote.get_part_by_tex(author)
quote.set_width(12)
quote.to_edge(UP)
quote[-2].shift(SMALL_BUFF * LEFT)
author_part.shift(RIGHT + 0.5 * DOWN)
author_part.scale(1.2, about_edge=UL)
movers = VGroup(*quote[:-1].family_members_with_points())
for mover in movers:
mover.save_state()
disc = Circle(radius=0.05)
disc.set_stroke(width=0)
disc.set_fill(BLACK, 0)
disc.move_to(mover)
mover.become(disc)
self.play(
FadeInFrom(author_part, LEFT),
LaggedStartMap(
# FadeInFromLarge,
# quote[:-1].family_members_with_points(),
Restore, movers,
lag_ratio=0.005,
run_time=2,
)
# FadeInFromDown(quote[:-1]),
# lag_ratio=0.01,
)
self.wait()
self.play(
Write(law_part.copy().set_color(YELLOW)),
run_time=1,
)
self.wait()
self.play(
Write(language_part.copy().set_color(BLUE)),
run_time=1.5,
)
self.wait(2)
class ShowGravityAcceleration(Scene):
def construct(self):
self.add_gravity_field()
self.add_title()
self.pulse_gravity_down()
self.show_trajectory()
self.combine_v_vects()
def add_gravity_field(self):
gravity_field = self.gravity_field = VectorField(
lambda p: DOWN,
# delta_x=2,
# delta_y=2,
)
gravity_field.set_opacity(0.5)
gravity_field.sort_submobjects(
lambda p: -p[1],
)
self.add(gravity_field)
def add_title(self):
title = self.title = TextMobject("Gravitational acceleration")
title.scale(1.5)
title.to_edge(UP)
g_eq = self.g_eq = TexMobject(
"{g}", "=", "-9.8", "\\frac{\\text{m/s}}{\\text{s}}",
**Lg_formula_config
)
g_eq.next_to(title, DOWN)
for mob in title, g_eq:
mob.add_background_rectangle_to_submobjects(
buff=0.05,
opacity=1,
)
self.add(title, g_eq)
def pulse_gravity_down(self):
field = self.gravity_field
self.play(LaggedStart(*[
ApplyFunction(
lambda v: v.set_opacity(1).scale(1.2),
vector,
rate_func=there_and_back,
)
for vector in field
]), run_time=2, lag_ratio=0.001)
self.add(self.title, self.g_eq)
def show_trajectory(self):
ball = Circle(
stroke_width=1,
stroke_color=WHITE,
fill_color=GREY,
fill_opacity=1,
sheen_factor=1,
sheen_direction=UL,
radius=0.25,
)
randy = Randolph(mode="pondering")
randy.eyes.set_stroke(BLACK, 0.5)
randy.match_height(ball)
randy.scale(0.75)
randy.move_to(ball)
ball.add(randy)
total_time = 6
p0 = 3 * DOWN + 5 * LEFT
v0 = 2.8 * UP + 1.5 * RIGHT
g = 0.9 * DOWN
graph = ParametricFunction(
lambda t: p0 + v0 * t + 0.5 * g * t**2,
t_min=0,
t_max=total_time,
)
# graph.center().to_edge(DOWN)
dashed_graph = DashedVMobject(graph, num_dashes=60)
dashed_graph.set_stroke(WHITE, 1)
ball.move_to(graph.get_start())
randy.add_updater(
lambda m, dt: m.rotate(dt).move_to(ball)
)
times = np.arange(0, total_time + 1)
velocity_graph = ParametricFunction(
lambda t: v0 + g * t,
t_min=0, t_max=total_time,
)
v_point = VectorizedPoint()
v_point.move_to(velocity_graph.get_start())
def get_v_vect():
result = Vector(
v_point.get_location(),
color=RED,
tip_length=0.2,
)
result.scale(0.5, about_point=result.get_start())
result.shift(ball.get_center())
result.set_stroke(width=2, family=False)
return result
v_vect = always_redraw(get_v_vect)
self.add(v_vect)
flash_rect = FullScreenRectangle(
stroke_width=0,
fill_color=WHITE,
fill_opacity=0.2,
)
flash = FadeOut(
flash_rect,
rate_func=squish_rate_func(smooth, 0, 0.1)
)
ball_copies = VGroup()
v_vect_copies = VGroup()
self.add(dashed_graph, ball)
for t1, t2 in zip(times, times[1:]):
v_vect_copy = v_vect.copy()
v_vect_copies.add(v_vect_copy)
self.add(v_vect_copy)
ball_copy = ball.copy()
ball_copy.clear_updaters()
ball_copies.add(ball_copy)
self.add(ball_copy)
dashed_graph.save_state()
kw = {
"rate_func": lambda alpha: interpolate(
t1 / total_time,
t2 / total_time,
alpha
)
}
self.play(
ShowCreation(dashed_graph, **kw),
MoveAlongPath(ball, graph, **kw),
MoveAlongPath(v_point, velocity_graph, **kw),
flash,
run_time=1,
)
dashed_graph.restore()
randy.clear_updaters()
self.wait()
self.v_vects = v_vect_copies
def combine_v_vects(self):
v_vects = self.v_vects.copy()
v_vects.generate_target()
new_center = 2 * DOWN + 2 * LEFT
for vect in v_vects.target:
vect.scale(1.5)
vect.set_stroke(width=2)
vect.shift(new_center - vect.get_start())
self.play(MoveToTarget(v_vects))
delta_vects = VGroup(*[
Arrow(
v1.get_end(),
v2.get_end(),
buff=0.01,
color=YELLOW,
).set_opacity(0.5)
for v1, v2 in zip(v_vects, v_vects[1:])
])
brace = Brace(Line(ORIGIN, UP), RIGHT)
braces = VGroup(*[
brace.copy().match_height(arrow).next_to(
arrow, RIGHT, buff=0.2 * SMALL_BUFF
)
for arrow in delta_vects
])
amounts = VGroup(*[
TextMobject("9.8 m/s").scale(0.5).next_to(
brace, RIGHT, SMALL_BUFF
)
for brace in braces
])
self.play(
FadeOut(self.gravity_field),
FadeIn(delta_vects, lag_ratio=0.1),
)
self.play(
LaggedStartMap(GrowFromCenter, braces),
LaggedStartMap(FadeInFrom, amounts, lambda m: (m, LEFT)),
)
self.wait()

View file

@ -55,6 +55,7 @@ from manimlib.mobject.types.point_cloud_mobject import *
from manimlib.mobject.types.vectorized_mobject import * from manimlib.mobject.types.vectorized_mobject import *
from manimlib.mobject.mobject_update_utils import * from manimlib.mobject.mobject_update_utils import *
from manimlib.mobject.value_tracker import * from manimlib.mobject.value_tracker import *
from manimlib.mobject.vector_field import *
from manimlib.for_3b1b_videos.common_scenes import * from manimlib.for_3b1b_videos.common_scenes import *
from manimlib.for_3b1b_videos.pi_creature import * from manimlib.for_3b1b_videos.pi_creature import *

View file

@ -97,6 +97,18 @@ class FadeOutAndShiftDown(FadeOutAndShift):
} }
class FadeInFromPoint(FadeIn):
def __init__(self, mobject, point, **kwargs):
self.point = point
super().__init__(mobject, **kwargs)
def create_starting_mobject(self):
start = super().create_starting_mobject()
start.scale(0)
start.move_to(self.point)
return start
class FadeInFromLarge(FadeIn): class FadeInFromLarge(FadeIn):
CONFIG = { CONFIG = {
"scale_factor": 2, "scale_factor": 2,

View file

@ -1,4 +1,3 @@
from manimlib.mobject.types.vectorized_mobject import VectorizedPoint
from manimlib.animation.transform import Transform from manimlib.animation.transform import Transform
# from manimlib.utils.paths import counterclockwise_path # from manimlib.utils.paths import counterclockwise_path
from manimlib.constants import PI from manimlib.constants import PI

View file

@ -1,5 +1,3 @@
from functools import reduce
import numpy as np import numpy as np
from manimlib.constants import * from manimlib.constants import *
@ -9,12 +7,8 @@ from manimlib.animation.composition import AnimationGroup
from manimlib.animation.composition import Succession from manimlib.animation.composition import Succession
from manimlib.animation.creation import ShowCreation from manimlib.animation.creation import ShowCreation
from manimlib.animation.creation import ShowPartial from manimlib.animation.creation import ShowPartial
from manimlib.animation.fading import FadeIn
from manimlib.animation.fading import FadeOut from manimlib.animation.fading import FadeOut
from manimlib.animation.transform import Transform from manimlib.animation.transform import Transform
from manimlib.animation.update import UpdateFromAlphaFunc
from manimlib.mobject.mobject_update_utils import always_redraw
from manimlib.mobject.mobject import Mobject
from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.geometry import Circle from manimlib.mobject.geometry import Circle
from manimlib.mobject.geometry import Dot from manimlib.mobject.geometry import Dot
@ -23,9 +17,6 @@ from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.geometry import Line from manimlib.mobject.geometry import Line
from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import interpolate
from manimlib.utils.config_ops import digest_config from manimlib.utils.config_ops import digest_config
from manimlib.utils.rate_functions import linear
from manimlib.utils.rate_functions import smooth
from manimlib.utils.rate_functions import squish_rate_func
from manimlib.utils.rate_functions import there_and_back from manimlib.utils.rate_functions import there_and_back
from manimlib.utils.rate_functions import wiggle from manimlib.utils.rate_functions import wiggle

View file

@ -1,5 +1,4 @@
from manimlib.animation.animation import Animation from manimlib.animation.animation import Animation
from manimlib.utils.config_ops import digest_config
from manimlib.utils.rate_functions import linear from manimlib.utils.rate_functions import linear

View file

@ -214,6 +214,8 @@ COLOR_MAP = {
"GREY": "#888888", "GREY": "#888888",
"DARK_GREY": "#444444", "DARK_GREY": "#444444",
"DARK_GRAY": "#444444", "DARK_GRAY": "#444444",
"DARKER_GREY": "#222222",
"DARKER_GRAY": "#222222",
"GREY_BROWN": "#736357", "GREY_BROWN": "#736357",
"PINK": "#D147BD", "PINK": "#D147BD",
"GREEN_SCREEN": "#00FF00", "GREEN_SCREEN": "#00FF00",

View file

@ -148,12 +148,12 @@ class PiCreatureScene(Scene):
self.pi_creature_thinks( self.pi_creature_thinks(
self.get_primary_pi_creature(), *content, **kwargs) self.get_primary_pi_creature(), *content, **kwargs)
def compile_play_args_to_animation_list(self, *args): def compile_play_args_to_animation_list(self, *args, **kwargs):
""" """
Add animations so that all pi creatures look at the Add animations so that all pi creatures look at the
first mobject being animated with each .play call first mobject being animated with each .play call
""" """
animations = Scene.compile_play_args_to_animation_list(self, *args) animations = Scene.compile_play_args_to_animation_list(self, *args, **kwargs)
if not self.any_pi_creatures_on_screen(): if not self.any_pi_creatures_on_screen():
return animations return animations
@ -211,21 +211,24 @@ class PiCreatureScene(Scene):
]) ])
return self return self
def wait(self, time=1, blink=True): def wait(self, time=1, blink=True, **kwargs):
if "stop_condition" in kwargs:
self.non_blink_wait(time, **kwargs)
return
while time >= 1: while time >= 1:
time_to_blink = self.total_wait_time % self.seconds_to_blink == 0 time_to_blink = self.total_wait_time % self.seconds_to_blink == 0
if blink and self.any_pi_creatures_on_screen() and time_to_blink: if blink and self.any_pi_creatures_on_screen() and time_to_blink:
self.blink() self.blink()
else: else:
self.non_blink_wait() self.non_blink_wait(**kwargs)
time -= 1 time -= 1
self.total_wait_time += 1 self.total_wait_time += 1
if time > 0: if time > 0:
self.non_blink_wait(time) self.non_blink_wait(time, **kwargs)
return self return self
def non_blink_wait(self, time=1): def non_blink_wait(self, time=1, **kwargs):
Scene.wait(self, time) Scene.wait(self, time, **kwargs)
return self return self
def change_mode(self, mode): def change_mode(self, mode):

View file

@ -542,7 +542,7 @@ class Arrow(Line):
"stroke_width": 6, "stroke_width": 6,
"buff": MED_SMALL_BUFF, "buff": MED_SMALL_BUFF,
"tip_width_to_length_ratio": 1, "tip_width_to_length_ratio": 1,
"max_tip_length_to_length_ratio": 0.2, "max_tip_length_to_length_ratio": 0.25,
"max_stroke_width_to_length_ratio": 6, "max_stroke_width_to_length_ratio": 6,
"preserve_tip_size_when_scaling": True, "preserve_tip_size_when_scaling": True,
"rectangular_stem_width": 0.05, "rectangular_stem_width": 0.05,
@ -560,15 +560,17 @@ class Arrow(Line):
has_tip = self.has_tip() has_tip = self.has_tip()
has_start_tip = self.has_start_tip() has_start_tip = self.has_start_tip()
if has_tip or has_start_tip: if has_tip or has_start_tip:
self.pop_tips() old_tips = self.pop_tips()
VMobject.scale(self, factor, **kwargs) VMobject.scale(self, factor, **kwargs)
self.set_stroke_width_from_length() self.set_stroke_width_from_length()
if has_tip: if has_tip:
self.add_tip() self.add_tip()
self.tip.match_style(old_tips[0])
if has_start_tip: if has_start_tip:
self.add_tip(at_start=True) self.add_tip(at_start=True)
self.start_tip.match_style(old_tips[1])
return self return self
def get_normal_vector(self): def get_normal_vector(self):
@ -588,10 +590,13 @@ class Arrow(Line):
def set_stroke_width_from_length(self): def set_stroke_width_from_length(self):
max_ratio = self.max_stroke_width_to_length_ratio max_ratio = self.max_stroke_width_to_length_ratio
self.set_stroke(width=min( self.set_stroke(
self.initial_stroke_width, width=min(
max_ratio * self.get_length(), self.initial_stroke_width,
)) max_ratio * self.get_length(),
),
family=False,
)
return self return self
# TODO, should this be the default for everything? # TODO, should this be the default for everything?

View file

@ -4,6 +4,7 @@ import itertools as it
import operator as op import operator as op
import os import os
import random import random
import sys
from colour import Color from colour import Color
import numpy as np import numpy as np

View file

@ -97,7 +97,7 @@ class NumberLine(Line):
def get_tick_numbers(self): def get_tick_numbers(self):
return np.arange( return np.arange(
self.leftmost_tick, self.leftmost_tick,
self.x_max - self.tick_frequency / 2, self.x_max + self.tick_frequency / 2,
self.tick_frequency self.tick_frequency
) )

View file

@ -446,7 +446,10 @@ class Bubble(SVGMobject):
return self.get_center() + factor * self.get_height() * UP return self.get_center() + factor * self.get_height() * UP
def move_tip_to(self, point): def move_tip_to(self, point):
VGroup(self, self.content).shift(point - self.get_tip()) mover = VGroup(self)
if self.content is not None:
mover.add(self.content)
mover.shift(point - self.get_tip())
return self return self
def flip(self): def flip(self):

View file

@ -202,6 +202,7 @@ class VMobject(Mobject):
) )
if background_image_file: if background_image_file:
self.color_using_background_image(background_image_file) self.color_using_background_image(background_image_file)
return self
def get_style(self): def get_style(self):
return { return {
@ -209,8 +210,10 @@ class VMobject(Mobject):
"fill_opacity": self.get_fill_opacities(), "fill_opacity": self.get_fill_opacities(),
"stroke_color": self.get_stroke_colors(), "stroke_color": self.get_stroke_colors(),
"stroke_width": self.get_stroke_width(), "stroke_width": self.get_stroke_width(),
"stroke_opacity": self.get_stroke_opacity(),
"background_stroke_color": self.get_stroke_colors(background=True), "background_stroke_color": self.get_stroke_colors(background=True),
"background_stroke_width": self.get_stroke_width(background=True), "background_stroke_width": self.get_stroke_width(background=True),
"background_stroke_opacity": self.get_stroke_opacity(background=True),
"sheen_factor": self.get_sheen_factor(), "sheen_factor": self.get_sheen_factor(),
"sheen_direction": self.get_sheen_direction(), "sheen_direction": self.get_sheen_direction(),
"background_image_file": self.get_background_image_file(), "background_image_file": self.get_background_image_file(),
@ -835,6 +838,8 @@ class VMobject(Mobject):
upper_index, upper_residue = integer_interpolate(0, num_cubics, b) upper_index, upper_residue = integer_interpolate(0, num_cubics, b)
self.clear_points() self.clear_points()
if num_cubics == 0:
return self
if lower_index == upper_index: if lower_index == upper_index:
self.append_points(partial_bezier_points( self.append_points(partial_bezier_points(
bezier_quads[lower_index], bezier_quads[lower_index],

View file

@ -0,0 +1,341 @@
import numpy as np
import os
import itertools as it
from PIL import Image
import random
from manimlib.constants import *
from manimlib.animation.composition import AnimationGroup
from manimlib.animation.indication import ShowPassingFlash
from manimlib.mobject.geometry import Vector
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.utils.bezier import inverse_interpolate
from manimlib.utils.bezier import interpolate
from manimlib.utils.color import color_to_rgb
from manimlib.utils.color import rgb_to_color
from manimlib.utils.config_ops import digest_config
from manimlib.utils.rate_functions import linear
from manimlib.utils.simple_functions import sigmoid
from manimlib.utils.space_ops import get_norm
# from manimlib.utils.space_ops import normalize
DEFAULT_SCALAR_FIELD_COLORS = [BLUE_E, GREEN, YELLOW, RED]
def get_colored_background_image(scalar_field_func,
number_to_rgb_func,
pixel_height=DEFAULT_PIXEL_HEIGHT,
pixel_width=DEFAULT_PIXEL_WIDTH):
ph = pixel_height
pw = pixel_width
fw = FRAME_WIDTH
fh = FRAME_HEIGHT
points_array = np.zeros((ph, pw, 3))
x_array = np.linspace(-fw / 2, fw / 2, pw)
x_array = x_array.reshape((1, len(x_array)))
x_array = x_array.repeat(ph, axis=0)
y_array = np.linspace(fh / 2, -fh / 2, ph)
y_array = y_array.reshape((len(y_array), 1))
y_array.repeat(pw, axis=1)
points_array[:, :, 0] = x_array
points_array[:, :, 1] = y_array
scalars = np.apply_along_axis(scalar_field_func, 2, points_array)
rgb_array = number_to_rgb_func(scalars.flatten()).reshape((ph, pw, 3))
return Image.fromarray((rgb_array * 255).astype('uint8'))
def get_rgb_gradient_function(min_value=0, max_value=1,
colors=[BLUE, RED],
flip_alphas=True, # Why?
):
rgbs = np.array(list(map(color_to_rgb, colors)))
def func(values):
alphas = inverse_interpolate(
min_value, max_value, np.array(values)
)
alphas = np.clip(alphas, 0, 1)
# if flip_alphas:
# alphas = 1 - alphas
scaled_alphas = alphas * (len(rgbs) - 1)
indices = scaled_alphas.astype(int)
next_indices = np.clip(indices + 1, 0, len(rgbs) - 1)
inter_alphas = scaled_alphas % 1
inter_alphas = inter_alphas.repeat(3).reshape((len(indices), 3))
result = interpolate(rgbs[indices], rgbs[next_indices], inter_alphas)
return result
return func
def get_color_field_image_file(scalar_func,
min_value=0, max_value=2,
colors=DEFAULT_SCALAR_FIELD_COLORS
):
# try_hash
np.random.seed(0)
sample_inputs = 5 * np.random.random(size=(10, 3)) - 10
sample_outputs = np.apply_along_axis(scalar_func, 1, sample_inputs)
func_hash = hash(
str(min_value) + str(max_value) + str(colors) + str(sample_outputs)
)
file_name = "%d.png" % func_hash
full_path = os.path.join(RASTER_IMAGE_DIR, file_name)
if not os.path.exists(full_path):
print("Rendering color field image " + str(func_hash))
rgb_gradient_func = get_rgb_gradient_function(
min_value=min_value,
max_value=max_value,
colors=colors
)
image = get_colored_background_image(scalar_func, rgb_gradient_func)
image.save(full_path)
return full_path
def move_along_vector_field(mobject, func):
mobject.add_updater(
lambda m, dt: m.shift(
func(m.get_center()) * dt
)
)
return mobject
def move_submobjects_along_vector_field(mobject, func):
def apply_nudge(mob, dt):
for submob in mob:
x, y = submob.get_center()[:2]
if abs(x) < FRAME_WIDTH and abs(y) < FRAME_HEIGHT:
submob.shift(func(submob.get_center()) * dt)
mobject.add_updater(apply_nudge)
return mobject
def move_points_along_vector_field(mobject, func):
def apply_nudge(self, dt):
self.mobject.apply_function(
lambda p: p + func(p) * dt
)
mobject.add_updater(apply_nudge)
return mobject
# Mobjects
class VectorField(VGroup):
CONFIG = {
"delta_x": 0.5,
"delta_y": 0.5,
"x_min": int(np.floor(-FRAME_WIDTH / 2)),
"x_max": int(np.ceil(FRAME_WIDTH / 2)),
"y_min": int(np.floor(-FRAME_HEIGHT / 2)),
"y_max": int(np.ceil(FRAME_HEIGHT / 2)),
"min_magnitude": 0,
"max_magnitude": 2,
"colors": DEFAULT_SCALAR_FIELD_COLORS,
# Takes in actual norm, spits out displayed norm
"length_func": lambda norm: 0.45 * sigmoid(norm),
"opacity": 1.0,
"vector_config": {},
}
def __init__(self, func, **kwargs):
VGroup.__init__(self, **kwargs)
self.func = func
self.rgb_gradient_function = get_rgb_gradient_function(
self.min_magnitude,
self.max_magnitude,
self.colors,
flip_alphas=False
)
x_range = np.arange(
self.x_min,
self.x_max + self.delta_x,
self.delta_x
)
y_range = np.arange(
self.y_min,
self.y_max + self.delta_y,
self.delta_y
)
for x, y in it.product(x_range, y_range):
point = x * RIGHT + y * UP
self.add(self.get_vector(point))
self.set_opacity(self.opacity)
def get_vector(self, point, **kwargs):
output = np.array(self.func(point))
norm = get_norm(output)
if norm == 0:
output *= 0
else:
output *= self.length_func(norm) / norm
vector_config = dict(self.vector_config)
vector_config.update(kwargs)
vect = Vector(output, **vector_config)
vect.shift(point)
fill_color = rgb_to_color(
self.rgb_gradient_function(np.array([norm]))[0]
)
vect.set_color(fill_color)
return vect
class StreamLines(VGroup):
CONFIG = {
# TODO, this is an awkward way to inherit
# defaults to a method.
"start_points_generator_config": {},
# Config for choosing start points
"x_min": -8,
"x_max": 8,
"y_min": -5,
"y_max": 5,
"delta_x": 0.5,
"delta_y": 0.5,
"n_repeats": 1,
"noise_factor": None,
# Config for drawing lines
"dt": 0.05,
"virtual_time": 3,
"n_anchors_per_line": 100,
"stroke_width": 1,
"stroke_color": WHITE,
"color_by_arc_length": True,
# Min and max arc lengths meant to define
# the color range, should color_by_arc_length be True
"min_arc_length": 0,
"max_arc_length": 12,
"color_by_magnitude": False,
# Min and max magnitudes meant to define
# the color range, should color_by_magnitude be True
"min_magnitude": 0.5,
"max_magnitude": 1.5,
"colors": DEFAULT_SCALAR_FIELD_COLORS,
"cutoff_norm": 15,
}
def __init__(self, func, **kwargs):
VGroup.__init__(self, **kwargs)
self.func = func
dt = self.dt
start_points = self.get_start_points(
**self.start_points_generator_config
)
for point in start_points:
points = [point]
for t in np.arange(0, self.virtual_time, dt):
last_point = points[-1]
points.append(last_point + dt * func(last_point))
if get_norm(last_point) > self.cutoff_norm:
break
line = VMobject()
step = max(1, int(len(points) / self.n_anchors_per_line))
line.set_points_smoothly(points[::step])
self.add(line)
self.set_stroke(self.stroke_color, self.stroke_width)
if self.color_by_arc_length:
len_to_rgb = get_rgb_gradient_function(
self.min_arc_length,
self.max_arc_length,
colors=self.colors,
)
for line in self:
arc_length = line.get_arc_length()
rgb = len_to_rgb([arc_length])[0]
color = rgb_to_color(rgb)
line.set_color(color)
elif self.color_by_magnitude:
image_file = get_color_field_image_file(
lambda p: get_norm(func(p)),
min_value=self.min_magnitude,
max_value=self.max_magnitude,
colors=self.colors,
)
self.color_using_background_image(image_file)
def get_start_points(self):
x_min = self.x_min
x_max = self.x_max
y_min = self.y_min
y_max = self.y_max
delta_x = self.delta_x
delta_y = self.delta_y
n_repeats = self.n_repeats
noise_factor = self.noise_factor
if noise_factor is None:
noise_factor = delta_y / 2
return np.array([
x * RIGHT + y * UP + noise_factor * np.random.random(3)
for n in range(n_repeats)
for x in np.arange(x_min, x_max + delta_x, delta_x)
for y in np.arange(y_min, y_max + delta_y, delta_y)
])
# TODO: Make it so that you can have a group of stream_lines
# varying in response to a changing vector field, and still
# animate the resulting flow
class ShowPassingFlashWithThinningStrokeWidth(AnimationGroup):
CONFIG = {
"n_segments": 10,
"time_width": 0.1,
"remover": True
}
def __init__(self, vmobject, **kwargs):
digest_config(self, kwargs)
max_stroke_width = vmobject.get_stroke_width()
max_time_width = kwargs.pop("time_width", self.time_width)
AnimationGroup.__init__(self, *[
ShowPassingFlash(
vmobject.deepcopy().set_stroke(width=stroke_width),
time_width=time_width,
**kwargs
)
for stroke_width, time_width in zip(
np.linspace(0, max_stroke_width, self.n_segments),
np.linspace(max_time_width, 0, self.n_segments)
)
])
# TODO, this is untested after turning it from a
# ContinualAnimation into a VGroup
class AnimatedStreamLines(VGroup):
CONFIG = {
"lag_range": 4,
"line_anim_class": ShowPassingFlash,
"line_anim_config": {
"run_time": 4,
"rate_func": linear,
"time_width": 0.3,
},
}
def __init__(self, stream_lines, **kwargs):
VGroup.__init__(self, **kwargs)
self.stream_lines = stream_lines
for line in stream_lines:
line.anim = self.line_anim_class(line, **self.line_anim_config)
line.anim.begin()
line.time = -self.lag_range * random.random()
self.add(line.anim.mobject)
self.add_updater(lambda m, dt: m.update(dt))
def update(self, dt):
stream_lines = self.stream_lines
for line in stream_lines:
line.time += dt
adjusted_time = max(line.time, 0) % line.anim.run_time
line.anim.update(adjusted_time / line.anim.run_time)

View file

@ -503,7 +503,7 @@ class Scene(Container):
self.update_mobjects(dt) self.update_mobjects(dt)
self.update_frame() self.update_frame()
self.add_frames(self.get_frame()) self.add_frames(self.get_frame())
if stop_condition and stop_condition(): if stop_condition is not None and stop_condition():
time_progression.close() time_progression.close()
break break
elif self.skip_animations: elif self.skip_animations:

View file

@ -287,6 +287,9 @@ class LinearTransformationScene(VectorScene):
}, },
"background_plane_kwargs": { "background_plane_kwargs": {
"color": GREY, "color": GREY,
"axis_config": {
"stroke_color": LIGHT_GREY,
},
"number_line_config": { "number_line_config": {
"color": GREY, "color": GREY,
}, },
@ -358,7 +361,7 @@ class LinearTransformationScene(VectorScene):
self.add_special_mobjects(self.moving_mobjects, mobject) self.add_special_mobjects(self.moving_mobjects, mobject)
def get_unit_square(self, color=YELLOW, opacity=0.3, stroke_width=3): def get_unit_square(self, color=YELLOW, opacity=0.3, stroke_width=3):
square = Rectangle( square = self.square = Rectangle(
color=color, color=color,
width=self.plane.get_x_unit_size(), width=self.plane.get_x_unit_size(),
height=self.plane.get_y_unit_size(), height=self.plane.get_y_unit_size(),

View file

@ -1,7 +1,6 @@
from big_ol_pile_of_manim_imports import * from big_ol_pile_of_manim_imports import *
DEFAULT_SCALAR_FIELD_COLORS = [BLUE_E, GREEN, YELLOW, RED]
# Quick note to anyone coming to this file with the # Quick note to anyone coming to this file with the
# intent of recreating animations from the video. Some # intent of recreating animations from the video. Some
@ -24,22 +23,6 @@ RABBIT_COLOR = "#C6D6EF"
# Helper functions # Helper functions
def get_flow_start_points(x_min=-8, x_max=8,
y_min=-5, y_max=5,
delta_x=0.5, delta_y=0.5,
n_repeats=1,
noise_factor=None
):
if noise_factor is None:
noise_factor = delta_y / 2
return np.array([
x * RIGHT + y * UP + noise_factor * np.random.random(3)
for n in range(n_repeats)
for x in np.arange(x_min, x_max + delta_x, delta_x)
for y in np.arange(y_min, y_max + delta_y, delta_y)
])
def joukowsky_map(z): def joukowsky_map(z):
if z == 0: if z == 0:
return 0 return 0
@ -99,77 +82,6 @@ def cylinder_flow_magnitude_field(point):
return get_norm(cylinder_flow_vector_field(point)) return get_norm(cylinder_flow_vector_field(point))
def get_colored_background_image(scalar_field_func,
number_to_rgb_func,
pixel_height=DEFAULT_PIXEL_HEIGHT,
pixel_width=DEFAULT_PIXEL_WIDTH,
):
ph = pixel_height
pw = pixel_width
fw = FRAME_WIDTH
fh = FRAME_HEIGHT
points_array = np.zeros((ph, pw, 3))
x_array = np.linspace(-fw / 2, fw / 2, pw)
x_array = x_array.reshape((1, len(x_array)))
x_array = x_array.repeat(ph, axis=0)
y_array = np.linspace(fh / 2, -fh / 2, ph)
y_array = y_array.reshape((len(y_array), 1))
y_array.repeat(pw, axis=1)
points_array[:, :, 0] = x_array
points_array[:, :, 1] = y_array
scalars = np.apply_along_axis(scalar_field_func, 2, points_array)
rgb_array = number_to_rgb_func(scalars.flatten()).reshape((ph, pw, 3))
return Image.fromarray((rgb_array * 255).astype('uint8'))
def get_rgb_gradient_function(min_value=0, max_value=1,
colors=[BLUE, RED],
flip_alphas=True, # Why?
):
rgbs = np.array(list(map(color_to_rgb, colors)))
def func(values):
alphas = inverse_interpolate(min_value, max_value, values)
alphas = np.clip(alphas, 0, 1)
# if flip_alphas:
# alphas = 1 - alphas
scaled_alphas = alphas * (len(rgbs) - 1)
indices = scaled_alphas.astype(int)
next_indices = np.clip(indices + 1, 0, len(rgbs) - 1)
inter_alphas = scaled_alphas % 1
inter_alphas = inter_alphas.repeat(3).reshape((len(indices), 3))
result = interpolate(rgbs[indices], rgbs[next_indices], inter_alphas)
return result
return func
def get_color_field_image_file(scalar_func,
min_value=0, max_value=2,
colors=DEFAULT_SCALAR_FIELD_COLORS
):
# try_hash
np.random.seed(0)
sample_inputs = 5 * np.random.random(size=(10, 3)) - 10
sample_outputs = np.apply_along_axis(scalar_func, 1, sample_inputs)
func_hash = hash(
str(min_value) + str(max_value) + str(colors) + str(sample_outputs)
)
file_name = "%d.png" % func_hash
full_path = os.path.join(RASTER_IMAGE_DIR, file_name)
if not os.path.exists(full_path):
print("Rendering color field image " + str(func_hash))
rgb_gradient_func = get_rgb_gradient_function(
min_value=min_value,
max_value=max_value,
colors=colors
)
image = get_colored_background_image(scalar_func, rgb_gradient_func)
image.save(full_path)
return full_path
def vec_tex(s): def vec_tex(s):
return "\\vec{\\textbf{%s}}" % s return "\\vec{\\textbf{%s}}" % s
@ -244,204 +156,6 @@ def preditor_prey_vector_field(point):
# Mobjects # Mobjects
class StreamLines(VGroup):
CONFIG = {
"start_points_generator": get_flow_start_points,
"start_points_generator_config": {},
"dt": 0.05,
"virtual_time": 3,
"n_anchors_per_line": 100,
"stroke_width": 1,
"stroke_color": WHITE,
"color_lines_by_magnitude": True,
"min_magnitude": 0.5,
"max_magnitude": 1.5,
"colors": DEFAULT_SCALAR_FIELD_COLORS,
"cutoff_norm": 15,
}
def __init__(self, func, **kwargs):
VGroup.__init__(self, **kwargs)
self.func = func
dt = self.dt
start_points = self.start_points_generator(
**self.start_points_generator_config
)
for point in start_points:
points = [point]
for t in np.arange(0, self.virtual_time, dt):
last_point = points[-1]
points.append(last_point + dt * func(last_point))
if get_norm(last_point) > self.cutoff_norm:
break
line = VMobject()
step = max(1, int(len(points) / self.n_anchors_per_line))
line.set_points_smoothly(points[::step])
self.add(line)
self.set_stroke(self.stroke_color, self.stroke_width)
if self.color_lines_by_magnitude:
image_file = get_color_field_image_file(
lambda p: get_norm(func(p)),
min_value=self.min_magnitude,
max_value=self.max_magnitude,
colors=self.colors,
)
self.color_using_background_image(image_file)
class VectorField(VGroup):
CONFIG = {
"delta_x": 0.5,
"delta_y": 0.5,
"x_min": int(np.floor(-FRAME_WIDTH / 2)),
"x_max": int(np.ceil(FRAME_WIDTH / 2)),
"y_min": int(np.floor(-FRAME_HEIGHT / 2)),
"y_max": int(np.ceil(FRAME_HEIGHT / 2)),
"min_magnitude": 0,
"max_magnitude": 2,
"colors": DEFAULT_SCALAR_FIELD_COLORS,
# Takes in actual norm, spits out displayed norm
"length_func": lambda norm: 0.5 * sigmoid(norm),
"stroke_color": BLACK,
"stroke_width": 0.5,
"fill_opacity": 1.0,
"vector_config": {},
}
def __init__(self, func, **kwargs):
VGroup.__init__(self, **kwargs)
self.func = func
self.rgb_gradient_function = get_rgb_gradient_function(
self.min_magnitude,
self.max_magnitude,
self.colors,
flip_alphas=False
)
for x in np.arange(self.x_min, self.x_max, self.delta_x):
for y in np.arange(self.y_min, self.y_max, self.delta_y):
point = x * RIGHT + y * UP
self.add(self.get_vector(point))
def get_vector(self, point, **kwargs):
output = np.array(self.func(point))
norm = get_norm(output)
if norm == 0:
output *= 0
else:
output *= self.length_func(norm) / norm
vector_config = dict(self.vector_config)
vector_config.update(kwargs)
vect = Vector(output, **vector_config)
vect.shift(point)
fill_color = rgb_to_color(
self.rgb_gradient_function(np.array([norm]))[0]
)
vect.set_color(fill_color)
vect.set_fill(opacity=self.fill_opacity)
vect.set_stroke(
self.stroke_color,
self.stroke_width
)
return vect
# Redefining what was once a ContinualAnimation class
# as a function
def VectorFieldFlow(mobject, func):
mobject.add_updater(
lambda m, dt: m.shift(
func(m.get_center()) * dt
)
)
return mobject
# Redefining what was once a ContinualAnimation class
# as a function
def VectorFieldSubmobjectFlow(mobject, func):
def apply_nudge(mob, dt):
for submob in mob:
x, y = submob.get_center()[:2]
if abs(x) < FRAME_WIDTH and abs(y) < FRAME_HEIGHT:
submob.shift(func(submob.get_center()) * dt)
mobject.add_updater(apply_nudge)
return mobject
# Redefining what was once a ContinualAnimation class
# as a function
def VectorFieldPointFlow(mobject, func):
def apply_nudge(self, dt):
self.mobject.apply_function(
lambda p: p + func(p) * dt
)
mobject.add_updater(apply_nudge)
return mobject
# TODO: Make it so that you can have a group of stream_lines
# varying in response to a changing vector field, and still
# animate the resulting flow
class ShowPassingFlashWithThinningStrokeWidth(AnimationGroup):
CONFIG = {
"n_segments": 10,
"time_width": 0.1,
"remover": True
}
def __init__(self, vmobject, **kwargs):
digest_config(self, kwargs)
max_stroke_width = vmobject.get_stroke_width()
max_time_width = kwargs.pop("time_width", self.time_width)
AnimationGroup.__init__(self, *[
ShowPassingFlash(
vmobject.deepcopy().set_stroke(width=stroke_width),
time_width=time_width,
**kwargs
)
for stroke_width, time_width in zip(
np.linspace(0, max_stroke_width, self.n_segments),
np.linspace(max_time_width, 0, self.n_segments)
)
])
# TODO, this is untested after turning it from a
# ContinualAnimation into a VGroup
class AnimatedStreamLines(VGroup):
CONFIG = {
"lag_range": 4,
"line_anim_class": ShowPassingFlash,
"line_anim_config": {
"run_time": 4,
"rate_func": linear,
"time_width": 0.3,
},
}
def __init__(self, stream_lines, **kwargs):
VGroup.__init__(self, **kwargs)
self.stream_lines = stream_lines
for line in stream_lines:
line.anim = self.line_anim_class(line, **self.line_anim_config)
line.time = -self.lag_range * random.random()
self.add(line.anim.mobject)
self.add_updater(lambda m, dt: m.update(dt))
def update(self, dt):
stream_lines = self.stream_lines
for line in stream_lines:
line.time += dt
adjusted_time = max(line.time, 0) % line.anim.run_time
line.anim.update(adjusted_time / line.anim.run_time)
# TODO, this is untested after turning it from a # TODO, this is untested after turning it from a
# ContinualAnimation into a VGroup # ContinualAnimation into a VGroup
class JigglingSubmobjects(VGroup): class JigglingSubmobjects(VGroup):
@ -3066,7 +2780,7 @@ class ShowTwoPopulations(Scene):
self.start_num_rabbits * RIGHT + self.start_num_rabbits * RIGHT +
self.start_num_foxes * UP self.start_num_foxes * UP
) )
self.add(VectorFieldFlow( self.add(move_along_vector_field(
phase_point, phase_point,
preditor_prey_vector_field, preditor_prey_vector_field,
)) ))
@ -3420,7 +3134,7 @@ class PhaseSpaceOfPopulationModel(ShowTwoPopulations, PiCreatureScene, MovingCam
dot_vector = new_dot_vector dot_vector = new_dot_vector
self.play(dot.move_to, dot_vector.get_end()) self.play(dot.move_to, dot_vector.get_end())
dot_movement = VectorFieldFlow( dot_movement = move_along_vector_field(
dot, lambda p: 0.3 * vector_field.func(p) dot, lambda p: 0.3 * vector_field.func(p)
) )
self.add(dot_movement) self.add(dot_movement)

View file

@ -1,7 +1,7 @@
from big_ol_pile_of_manim_imports import * from big_ol_pile_of_manim_imports import *
from old_projects.div_curl import PureAirfoilFlow from old_projects.div_curl import PureAirfoilFlow
from old_projects.div_curl import VectorFieldSubmobjectFlow from old_projects.div_curl import move_submobjects_along_vector_field
from old_projects.div_curl import VectorFieldPointFlow from old_projects.div_curl import move_points_along_vector_field
from old_projects.div_curl import four_swirls_function from old_projects.div_curl import four_swirls_function
from old_projects.lost_lecture import ShowWord from old_projects.lost_lecture import ShowWord
@ -836,7 +836,7 @@ class LaminarFlowLabel(Scene):
class HighCurlFieldBreakingLayers(Scene): class HighCurlFieldBreakingLayers(Scene):
CONFIG = { CONFIG = {
"flow_anim": VectorFieldSubmobjectFlow, "flow_anim": move_submobjects_along_vector_field,
} }
def construct(self): def construct(self):
@ -870,7 +870,7 @@ class HighCurlFieldBreakingLayers(Scene):
class HighCurlFieldBreakingLayersLines(HighCurlFieldBreakingLayers): class HighCurlFieldBreakingLayersLines(HighCurlFieldBreakingLayers):
CONFIG = { CONFIG = {
"flow_anim": VectorFieldPointFlow "flow_anim": move_points_along_vector_field
} }
def get_line(self): def get_line(self):