mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
1619 lines
47 KiB
Python
1619 lines
47 KiB
Python
from big_ol_pile_of_manim_imports import *
|
|
import subprocess
|
|
from pydub import AudioSegment
|
|
|
|
|
|
class Block(Square):
|
|
CONFIG = {
|
|
"mass": 1,
|
|
"velocity": 0,
|
|
"width": None,
|
|
"label_text": None,
|
|
"label_scale_value": 0.8,
|
|
"fill_opacity": 1,
|
|
"stroke_width": 3,
|
|
"stroke_color": WHITE,
|
|
"fill_color": None,
|
|
"sheen_direction": UL,
|
|
"sheen": 0.5,
|
|
"sheen_direction": UL,
|
|
}
|
|
|
|
def __init__(self, **kwargs):
|
|
digest_config(self, kwargs)
|
|
if self.width is None:
|
|
self.width = self.mass_to_width(self.mass)
|
|
if self.fill_color is None:
|
|
self.fill_color = self.mass_to_color(self.mass)
|
|
if self.label_text is None:
|
|
self.label_text = self.mass_to_label_text(self.mass)
|
|
if "width" in kwargs:
|
|
kwargs.pop("width")
|
|
Square.__init__(self, side_length=self.width, **kwargs)
|
|
self.label = self.get_label()
|
|
self.add(self.label)
|
|
|
|
def get_label(self):
|
|
label = TextMobject(self.label_text)
|
|
label.scale(self.label_scale_value)
|
|
label.next_to(self, UP, SMALL_BUFF)
|
|
return label
|
|
|
|
def get_points_defining_boundary(self):
|
|
return self.points
|
|
|
|
def mass_to_color(self, mass):
|
|
colors = [
|
|
LIGHT_GREY,
|
|
BLUE_B,
|
|
BLUE_D,
|
|
BLUE_E,
|
|
BLUE_E,
|
|
DARK_GREY,
|
|
DARK_GREY,
|
|
BLACK,
|
|
]
|
|
index = min(int(np.log10(mass)), len(colors) - 1)
|
|
return colors[index]
|
|
|
|
def mass_to_width(self, mass):
|
|
return 1 + 0.25 * np.log10(mass)
|
|
|
|
def mass_to_label_text(self, mass):
|
|
return "{:,}\\,kg".format(int(mass))
|
|
|
|
|
|
class SlidingBlocks(VGroup):
|
|
CONFIG = {
|
|
"block1_config": {
|
|
"distance": 7,
|
|
"mass": 1e6,
|
|
"velocity": -2,
|
|
},
|
|
"block2_config": {
|
|
"distance": 3,
|
|
"mass": 1,
|
|
"velocity": 0,
|
|
},
|
|
"collect_clack_data": True,
|
|
}
|
|
|
|
def __init__(self, surrounding_scene, **kwargs):
|
|
VGroup.__init__(self, **kwargs)
|
|
self.surrounding_scene = surrounding_scene
|
|
self.floor = surrounding_scene.floor
|
|
self.wall = surrounding_scene.wall
|
|
|
|
self.block1 = self.get_block(**self.block1_config)
|
|
self.block2 = self.get_block(**self.block2_config)
|
|
self.mass_ratio = self.block2.mass / self.block1.mass
|
|
self.phase_space_point_tracker = self.get_phase_space_point_tracker()
|
|
self.add(
|
|
self.block1, self.block2,
|
|
self.phase_space_point_tracker,
|
|
)
|
|
self.add_updater(self.__class__.update_positions)
|
|
|
|
if self.collect_clack_data:
|
|
self.clack_data = self.get_clack_data()
|
|
|
|
def get_block(self, distance, **kwargs):
|
|
block = Block(**kwargs)
|
|
block.move_to(
|
|
self.floor.get_top()[1] * UP +
|
|
(self.wall.get_right()[0] + distance) * RIGHT,
|
|
DL,
|
|
)
|
|
return block
|
|
|
|
def get_phase_space_point_tracker(self):
|
|
block1, block2 = self.block1, self.block2
|
|
w2 = block2.get_width()
|
|
s1 = block1.get_left()[0] - self.wall.get_right()[0] - w2
|
|
s2 = block2.get_right()[0] - self.wall.get_right()[0] - w2
|
|
result = VectorizedPoint([
|
|
s1 * np.sqrt(block1.mass),
|
|
s2 * np.sqrt(block2.mass),
|
|
0
|
|
])
|
|
|
|
result.velocity = np.array([
|
|
np.sqrt(block1.mass) * block1.velocity,
|
|
np.sqrt(block2.mass) * block2.velocity,
|
|
0
|
|
])
|
|
return result
|
|
|
|
def update_positions(self, dt):
|
|
self.phase_space_point_tracker.shift(
|
|
self.phase_space_point_tracker.velocity * dt
|
|
)
|
|
self.update_blocks_from_phase_space_point_tracker()
|
|
|
|
def update_blocks_from_phase_space_point_tracker(self):
|
|
block1, block2 = self.block1, self.block2
|
|
ps_point = self.phase_space_point_tracker.get_location()
|
|
|
|
theta = np.arctan(np.sqrt(self.mass_ratio))
|
|
ps_point_angle = angle_of_vector(ps_point)
|
|
n_clacks = int(ps_point_angle / theta)
|
|
reflected_point = rotate_vector(
|
|
ps_point,
|
|
-2 * np.ceil(n_clacks / 2) * theta
|
|
)
|
|
reflected_point = np.abs(reflected_point)
|
|
|
|
shadow_wall_x = self.wall.get_right()[0] + block2.get_width()
|
|
floor_y = self.floor.get_top()[1]
|
|
s1 = reflected_point[0] / np.sqrt(block1.mass)
|
|
s2 = reflected_point[1] / np.sqrt(block2.mass)
|
|
block1.move_to(
|
|
(shadow_wall_x + s1) * RIGHT +
|
|
floor_y * UP,
|
|
DL,
|
|
)
|
|
block2.move_to(
|
|
(shadow_wall_x + s2) * RIGHT +
|
|
floor_y * UP,
|
|
DR,
|
|
)
|
|
|
|
self.surrounding_scene.update_num_clacks(n_clacks)
|
|
|
|
def get_clack_data(self):
|
|
ps_point = self.phase_space_point_tracker.get_location()
|
|
ps_velocity = self.phase_space_point_tracker.velocity
|
|
if ps_velocity[1] != 0:
|
|
raise Exception(
|
|
"Haven't implemented anything to gather clack "
|
|
"data from a start state with block2 moving"
|
|
)
|
|
y = ps_point[1]
|
|
theta = np.arctan(np.sqrt(self.mass_ratio))
|
|
|
|
clack_data = []
|
|
for k in range(1, int(PI / theta) + 1):
|
|
clack_ps_point = np.array([
|
|
y / np.tan(k * theta),
|
|
y,
|
|
0
|
|
])
|
|
time = get_norm(ps_point - clack_ps_point) / get_norm(ps_velocity)
|
|
reflected_point = rotate_vector(
|
|
clack_ps_point,
|
|
-2 * np.ceil((k - 1) / 2) * theta
|
|
)
|
|
block2 = self.block2
|
|
s2 = reflected_point[1] / np.sqrt(block2.mass)
|
|
location = np.array([
|
|
self.wall.get_right()[0] + s2,
|
|
block2.get_center()[1],
|
|
0
|
|
])
|
|
if k % 2 == 1:
|
|
location += block2.get_width() * RIGHT
|
|
clack_data.append((location, time))
|
|
return clack_data
|
|
|
|
|
|
class ClackFlashes(ContinualAnimation):
|
|
CONFIG = {
|
|
"flash_config": {
|
|
"run_time": 0.5,
|
|
"line_length": 0.1,
|
|
"flash_radius": 0.2,
|
|
},
|
|
"start_up_time": 0,
|
|
"min_time_between_flashes": 1 / 30,
|
|
}
|
|
|
|
def __init__(self, clack_data, **kwargs):
|
|
digest_config(self, kwargs)
|
|
self.flashes = []
|
|
group = Group()
|
|
last_time = 0
|
|
for location, time in clack_data:
|
|
if (time - last_time) < self.min_time_between_flashes:
|
|
continue
|
|
last_time = time
|
|
flash = Flash(location, **self.flash_config)
|
|
flash.start_time = time
|
|
flash.end_time = time + flash.run_time
|
|
self.flashes.append(flash)
|
|
ContinualAnimation.__init__(self, group, **kwargs)
|
|
|
|
def update_mobject(self, dt):
|
|
total_time = self.external_time
|
|
for flash in self.flashes:
|
|
if flash.start_time < total_time < flash.end_time:
|
|
if flash.mobject not in self.mobject:
|
|
self.mobject.add(flash.mobject)
|
|
flash.update(
|
|
(total_time - flash.start_time) / flash.run_time
|
|
)
|
|
else:
|
|
if flash.mobject in self.mobject:
|
|
self.mobject.remove(flash.mobject)
|
|
|
|
|
|
class Wall(Line):
|
|
CONFIG = {
|
|
"tick_spacing": 0.5,
|
|
"tick_length": 0.25,
|
|
"tick_style": {
|
|
"stroke_width": 1,
|
|
"stroke_color": WHITE,
|
|
},
|
|
}
|
|
|
|
def __init__(self, height, **kwargs):
|
|
Line.__init__(self, ORIGIN, height * UP, **kwargs)
|
|
self.height = height
|
|
self.ticks = self.get_ticks()
|
|
self.add(self.ticks)
|
|
|
|
def get_ticks(self):
|
|
n_lines = int(self.height / self.tick_spacing)
|
|
lines = VGroup(*[
|
|
Line(ORIGIN, self.tick_length * UR).shift(n * self.tick_spacing * UP)
|
|
for n in range(n_lines)
|
|
])
|
|
lines.set_style(**self.tick_style)
|
|
lines.move_to(self, DR)
|
|
return lines
|
|
|
|
|
|
class BlocksAndWallScene(Scene):
|
|
CONFIG = {
|
|
"include_sound": True,
|
|
"count_clacks": True,
|
|
"counter_group_shift_vect": LEFT,
|
|
"sliding_blocks_config": {},
|
|
"floor_y": -2,
|
|
"wall_x": -6,
|
|
"n_wall_ticks": 15,
|
|
"counter_label": "\\# Collisions: ",
|
|
"collision_sound": "clack.wav",
|
|
"show_flash_animations": True,
|
|
"min_time_between_sounds": 0.004,
|
|
}
|
|
|
|
def setup(self):
|
|
self.track_time()
|
|
self.add_floor_and_wall()
|
|
self.add_blocks()
|
|
if self.show_flash_animations:
|
|
self.add_flash_animations()
|
|
|
|
if self.count_clacks:
|
|
self.add_counter()
|
|
|
|
def add_floor_and_wall(self):
|
|
self.floor = self.get_floor()
|
|
self.wall = self.get_wall()
|
|
self.add(self.floor, self.wall)
|
|
|
|
def add_blocks(self):
|
|
self.blocks = SlidingBlocks(self, **self.sliding_blocks_config)
|
|
if hasattr(self.blocks, "clack_data"):
|
|
self.clack_data = self.blocks.clack_data
|
|
self.add(self.blocks)
|
|
|
|
def add_flash_animations(self):
|
|
self.clack_flashes = ClackFlashes(self.clack_data)
|
|
self.add(self.clack_flashes)
|
|
|
|
def track_time(self):
|
|
time_tracker = ValueTracker()
|
|
time_tracker.add_updater(lambda m, dt: m.increment_value(dt))
|
|
self.add(time_tracker)
|
|
self.get_time = time_tracker.get_value
|
|
|
|
def add_counter(self):
|
|
self.n_clacks = 0
|
|
counter_label = TextMobject(self.counter_label)
|
|
counter_mob = Integer(self.n_clacks)
|
|
counter_mob.next_to(
|
|
counter_label[-1], RIGHT,
|
|
aligned_edge=DOWN,
|
|
)
|
|
counter_group = VGroup(
|
|
counter_label,
|
|
counter_mob,
|
|
)
|
|
counter_group.to_corner(UR)
|
|
counter_group.shift(self.counter_group_shift_vect)
|
|
self.add(counter_group)
|
|
|
|
self.counter_mob = counter_mob
|
|
|
|
def get_wall(self):
|
|
height = (FRAME_HEIGHT / 2) - self.floor_y
|
|
wall = Wall(height=height)
|
|
wall.shift(self.wall_x * RIGHT)
|
|
wall.to_edge(UP, buff=0)
|
|
return wall
|
|
|
|
def get_floor(self):
|
|
floor = Line(self.wall_x * RIGHT, FRAME_WIDTH * RIGHT / 2)
|
|
floor.shift(self.floor_y * UP)
|
|
return floor
|
|
|
|
def update_num_clacks(self, n_clacks):
|
|
if hasattr(self, "n_clacks"):
|
|
if n_clacks == self.n_clacks:
|
|
return
|
|
self.counter_mob.set_value(n_clacks)
|
|
|
|
def create_sound_file(self, clack_data):
|
|
clack_file = os.path.join(SOUND_DIR, self.collision_sound)
|
|
output_file = self.get_movie_file_path(extension='.wav')
|
|
times = [
|
|
time
|
|
for location, time in clack_data
|
|
if time < 300 # In case of any extremes
|
|
]
|
|
|
|
clack = AudioSegment.from_wav(clack_file)
|
|
total_time = max(times) + 1
|
|
clacks = AudioSegment.silent(int(1000 * total_time))
|
|
last_position = 0
|
|
min_diff = int(1000 * self.min_time_between_sounds)
|
|
for time in times:
|
|
position = int(1000 * time)
|
|
d_position = position - last_position
|
|
if d_position < min_diff:
|
|
continue
|
|
if time > self.get_time():
|
|
break
|
|
last_position = position
|
|
clacks = clacks.fade(-50, start=position, end=position + 10)
|
|
clacks = clacks.overlay(
|
|
clack,
|
|
position=position
|
|
)
|
|
clacks.export(output_file, format="wav")
|
|
return output_file
|
|
|
|
def combine_movie_files(self):
|
|
Scene.combine_movie_files(self)
|
|
if self.include_sound:
|
|
sound_file_path = self.create_sound_file(self.clack_data)
|
|
movie_path = self.get_movie_file_path()
|
|
temp_path = self.get_movie_file_path(str(self) + "TempSound")
|
|
commands = [
|
|
"ffmpeg",
|
|
"-i", movie_path,
|
|
"-i", sound_file_path,
|
|
"-c:v", "copy", "-c:a", "aac",
|
|
'-loglevel', 'error',
|
|
"-strict", "experimental",
|
|
temp_path,
|
|
]
|
|
subprocess.call(commands)
|
|
subprocess.call(["rm", sound_file_path])
|
|
subprocess.call(["mv", temp_path, movie_path])
|
|
|
|
# Animated scenes
|
|
|
|
|
|
class NameIntro(Scene):
|
|
def construct(self):
|
|
name = TextMobject("3Blue", "1Brown", arg_separator="")
|
|
blue, brown = name
|
|
name.scale(2.5)
|
|
for part in name:
|
|
part.save_state()
|
|
brown.to_edge(RIGHT, buff=0)
|
|
flash_time = 0.75
|
|
|
|
self.add(blue, brown)
|
|
self.play(
|
|
VFadeIn(blue),
|
|
VFadeIn(brown),
|
|
Restore(brown, rate_func=None),
|
|
)
|
|
self.play(
|
|
Flash(blue.get_right(), run_time=flash_time),
|
|
ApplyMethod(
|
|
blue.to_edge, LEFT, {"buff": 0},
|
|
rate_func=None,
|
|
),
|
|
)
|
|
self.play(
|
|
Flash(blue.get_left(), run_time=flash_time),
|
|
Restore(blue, rate_func=None),
|
|
)
|
|
self.play(
|
|
Flash(blue.get_right(), run_time=flash_time),
|
|
ApplyMethod(
|
|
brown.to_edge, RIGHT, {"buff": 0},
|
|
rate_func=None,
|
|
)
|
|
)
|
|
|
|
|
|
class MathAndPhysicsConspiring(Scene):
|
|
def construct(self):
|
|
v_line = Line(DOWN, UP).scale(FRAME_HEIGHT)
|
|
v_line.save_state()
|
|
v_line.fade(1)
|
|
v_line.scale(0)
|
|
math_title = TextMobject("Math")
|
|
math_title.set_color(BLUE)
|
|
physics_title = TextMobject("Physics")
|
|
physics_title.set_color(YELLOW)
|
|
for title, vect in (math_title, LEFT), (physics_title, RIGHT):
|
|
title.scale(2)
|
|
title.shift(vect * FRAME_WIDTH / 4)
|
|
title.to_edge(UP)
|
|
|
|
math_stuffs = VGroup(
|
|
TexMobject("\\pi = {:.16}\\dots".format(PI)),
|
|
self.get_tangent_image(),
|
|
)
|
|
math_stuffs.arrange_submobjects(DOWN, buff=MED_LARGE_BUFF)
|
|
math_stuffs.next_to(math_title, DOWN, LARGE_BUFF)
|
|
to_fade = VGroup(math_title, *math_stuffs, physics_title)
|
|
|
|
self.play(
|
|
LaggedStart(
|
|
FadeInFromDown, to_fade,
|
|
lag_ratio=0.7,
|
|
run_time=3,
|
|
),
|
|
Restore(v_line, run_time=2, path_arc=PI / 2),
|
|
)
|
|
self.wait()
|
|
|
|
def get_tangent_image(self):
|
|
axes = Axes(
|
|
x_min=-1.5,
|
|
x_max=1.5,
|
|
y_min=-1.5,
|
|
y_max=1.5,
|
|
)
|
|
circle = Circle()
|
|
circle.set_color(WHITE)
|
|
theta = 30 * DEGREES
|
|
arc = Arc(angle=theta, radius=0.4)
|
|
theta_label = TexMobject("\\theta")
|
|
theta_label.scale(0.5)
|
|
theta_label.next_to(arc.get_center(), RIGHT, buff=SMALL_BUFF)
|
|
theta_label.shift(0.025 * UL)
|
|
line = Line(ORIGIN, rotate_vector(RIGHT, theta))
|
|
line.set_color(WHITE)
|
|
one = TexMobject("1").scale(0.5)
|
|
one.next_to(line.point_from_proportion(0.7), UL, 0.5 * SMALL_BUFF)
|
|
tan_line = Line(
|
|
line.get_end(),
|
|
(1.0 / np.cos(theta)) * RIGHT
|
|
)
|
|
tan_line.set_color(RED)
|
|
tan_text = TexMobject("\\tan(\\theta)")
|
|
tan_text.rotate(tan_line.get_angle())
|
|
tan_text.scale(0.5)
|
|
tan_text.move_to(tan_line)
|
|
tan_text.match_color(tan_line)
|
|
tan_text.shift(0.2 * normalize(line.get_vector()))
|
|
|
|
result = VGroup(
|
|
axes, circle,
|
|
line, one,
|
|
arc, theta_label,
|
|
tan_line, tan_text,
|
|
)
|
|
result.set_height(4)
|
|
return result
|
|
|
|
|
|
class LightBouncing(MovingCameraScene):
|
|
CONFIG = {
|
|
"theta": np.arctan(0.15),
|
|
"show_fanning": False,
|
|
"mirror_shift_vect": 5 * LEFT,
|
|
"mirror_length": 10,
|
|
"beam_start_x": 12,
|
|
"beam_height": 1,
|
|
}
|
|
|
|
def construct(self):
|
|
theta = self.theta
|
|
h_line = Line(ORIGIN, self.mirror_length * RIGHT)
|
|
d_line = h_line.copy().rotate(theta, about_point=ORIGIN)
|
|
mirrors = VGroup(h_line, d_line)
|
|
self.add(mirrors)
|
|
|
|
beam_height = self.beam_height
|
|
start_point = self.beam_start_x * RIGHT + beam_height * UP
|
|
points = [start_point] + [
|
|
np.array([
|
|
(beam_height / np.tan(k * theta)),
|
|
beam_height,
|
|
0,
|
|
])
|
|
for k in range(1, int(PI / theta))
|
|
] + [rotate(start_point, PI, UP)]
|
|
reflected_points = []
|
|
for k, point in enumerate(points):
|
|
reflected_point = rotate_vector(point, -2 * (k // 2) * theta)
|
|
reflected_point[1] = abs(reflected_point[1])
|
|
reflected_points.append(reflected_point)
|
|
beam = VMobject()
|
|
beam.set_points_as_corners(reflected_points)
|
|
beam.set_stroke(YELLOW, 2)
|
|
|
|
anims = [self.get_beam_anim(beam)]
|
|
|
|
if self.show_fanning:
|
|
for k in range(2, int(PI / theta) + 1):
|
|
line = h_line.copy()
|
|
line.set_stroke(WHITE, 1)
|
|
line.rotate(k * theta, about_point=ORIGIN)
|
|
self.add(line)
|
|
straight_beam = VMobject()
|
|
straight_beam.set_points_as_corners(points)
|
|
straight_beam.set_stroke(YELLOW, 2)
|
|
anims.append(self.get_beam_anim(straight_beam))
|
|
|
|
self.camera_frame.shift(-self.mirror_shift_vect)
|
|
self.play(*anims)
|
|
self.wait()
|
|
|
|
def get_beam_anim(self, beam):
|
|
dot = Dot()
|
|
dot.scale(0.5)
|
|
dot.match_color(beam)
|
|
return AnimationGroup(
|
|
ShowPassingFlash(
|
|
beam,
|
|
run_time=5,
|
|
rate_func=lambda t: smooth(t, 5),
|
|
time_width=0.05,
|
|
),
|
|
UpdateFromFunc(
|
|
dot,
|
|
lambda m: m.move_to(beam.points[-1])
|
|
),
|
|
)
|
|
|
|
|
|
class BlocksAndWallExample(BlocksAndWallScene):
|
|
CONFIG = {
|
|
"sliding_blocks_config": {
|
|
"block1_config": {
|
|
"mass": 1e0,
|
|
"velocity": -2,
|
|
}
|
|
},
|
|
"wait_time": 15,
|
|
}
|
|
|
|
def construct(self):
|
|
self.wait(self.wait_time)
|
|
|
|
|
|
class BlocksAndWallExampleMass1e1(BlocksAndWallExample):
|
|
CONFIG = {
|
|
"sliding_blocks_config": {
|
|
"block1_config": {
|
|
"mass": 1e1,
|
|
"velocity": -1.5,
|
|
}
|
|
},
|
|
"wait_time": 20,
|
|
}
|
|
|
|
|
|
class TwoBlocksLabel(Scene):
|
|
def construct(self):
|
|
label = TextMobject("Two sliding \\\\ blocks")
|
|
label.to_edge(UP)
|
|
arrows = VGroup(*[
|
|
Arrow(label.get_bottom(), point)
|
|
for point in [RIGHT, LEFT]
|
|
])
|
|
arrows.set_color(RED)
|
|
self.play(
|
|
Write(label),
|
|
LaggedStart(GrowArrow, arrows, lag_ratio=0.7),
|
|
run_time=1
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class WallLabel(Scene):
|
|
def construct(self):
|
|
wall = Line(TOP, 2 * DOWN)
|
|
wall.set_stroke(YELLOW, 10)
|
|
word = TextMobject("Wall")
|
|
word.rotate(-90 * DEGREES)
|
|
word.next_to(wall, RIGHT, MED_SMALL_BUFF)
|
|
self.play(
|
|
Write(word),
|
|
ShowPassingFlash(wall)
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class CowToSphere(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
|
|
class NoFrictionLabel(Scene):
|
|
def construct(self):
|
|
words = TextMobject("Frictionless")
|
|
words.shift(2 * RIGHT)
|
|
words.add_updater(
|
|
lambda m, dt: m.shift(dt * LEFT)
|
|
)
|
|
|
|
self.play(VFadeIn(words))
|
|
self.wait(2)
|
|
self.play(VFadeOut(words))
|
|
|
|
|
|
class Mass1e1WithElasticLabel(BlocksAndWallExampleMass1e1):
|
|
def add_flash_animations(self):
|
|
super().add_flash_animations()
|
|
flashes = self.clack_flashes
|
|
label = TextMobject(
|
|
"Purely elastic collisions\\\\"
|
|
"(no energy lost)"
|
|
)
|
|
label.set_color(YELLOW)
|
|
label.move_to(2 * LEFT + 2 * UP)
|
|
self.add(label)
|
|
self.add(*[
|
|
self.get_arrow(label, flashes, flash)
|
|
for flash in flashes.flashes
|
|
])
|
|
|
|
def get_arrow(self, label, clack_flashes, flash):
|
|
arrow = Arrow(
|
|
label.get_bottom(),
|
|
flash.mobject.get_center() + 0.0 * UP,
|
|
)
|
|
arrow.set_fill(YELLOW)
|
|
arrow.set_stroke(BLACK, 1, background=True)
|
|
arrow.original_length = arrow.get_length()
|
|
|
|
def set_opacity(arrow):
|
|
time = self.get_time()
|
|
from_start = time - flash.start_time
|
|
if from_start < 0:
|
|
opacity = 0
|
|
else:
|
|
opacity = smooth(1 - 2 * from_start)
|
|
arrow.set_fill(opacity=opacity)
|
|
arrow.set_stroke(opacity=opacity, background=True)
|
|
# if opacity > 0:
|
|
# arrow.scale(
|
|
# opacity * arrow.original_length / arrow.get_length(),
|
|
# about_point=arrow.get_end()
|
|
# )
|
|
|
|
arrow.add_updater(set_opacity)
|
|
return arrow
|
|
|
|
|
|
class AskAboutSoundlessness(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.student_says(
|
|
"No sound,\\\\right?"
|
|
)
|
|
self.play(self.teacher.change, "guilty")
|
|
self.wait(2)
|
|
self.teacher_says(
|
|
"Focus on \\\\ collisions",
|
|
target_mode="speaking",
|
|
added_anims=[
|
|
self.get_student_changes("pondering", "confused", "thinking")
|
|
]
|
|
)
|
|
self.look_at(self.screen)
|
|
self.wait(3)
|
|
|
|
|
|
class ShowCreationRect(Scene):
|
|
def construct(self):
|
|
rect = SurroundingRectangle(TextMobject("\\# Collisions: 3"))
|
|
self.play(ShowCreation(rect))
|
|
self.play(FadeOut(rect))
|
|
self.wait()
|
|
|
|
|
|
class BlocksAndWallExampleSameMass(BlocksAndWallExample):
|
|
pass
|
|
|
|
|
|
class ShowLeftArrow(Scene):
|
|
def construct(self):
|
|
arrow = Vector(2 * LEFT, color=RED)
|
|
self.play(GrowArrow(arrow))
|
|
self.wait()
|
|
self.play(FadeOut(arrow))
|
|
|
|
|
|
class AskWhatWillHappen(PiCreatureScene):
|
|
def construct(self):
|
|
morty = self.pi_creature
|
|
morty.set_color(GREY_BROWN)
|
|
|
|
self.pi_creature_says(
|
|
"What will\\\\"
|
|
"happen?",
|
|
target_mode="maybe",
|
|
look_at_arg=4 * DR,
|
|
)
|
|
self.wait(3)
|
|
|
|
|
|
class BlocksAndWallExampleMass1e2(BlocksAndWallExample):
|
|
CONFIG = {
|
|
"sliding_blocks_config": {
|
|
"block1_config": {
|
|
"mass": 1e2,
|
|
"velocity": -0.6,
|
|
}
|
|
},
|
|
"wait_time": 25,
|
|
}
|
|
|
|
|
|
class BlocksAndWallExampleMass1e4(BlocksAndWallExample):
|
|
CONFIG = {
|
|
"sliding_blocks_config": {
|
|
"block1_config": {
|
|
"mass": 1e4,
|
|
"velocity": -1.5,
|
|
},
|
|
},
|
|
"wait_time": 25,
|
|
}
|
|
|
|
|
|
class BlocksAndWallExampleMass1e4SlowMo(BlocksAndWallExample):
|
|
CONFIG = {
|
|
"sliding_blocks_config": {
|
|
"block1_config": {
|
|
"mass": 1e4,
|
|
"velocity": -0.1,
|
|
"distance": 4.1
|
|
},
|
|
},
|
|
"wait_time": 50,
|
|
"collision_sound": "slow_clack.wav",
|
|
}
|
|
|
|
|
|
class SlowMotionLabel(Scene):
|
|
def construct(self):
|
|
words = TextMobject("Slow motion replay")
|
|
words.scale(2).to_edge(UP)
|
|
self.play(Write(words))
|
|
self.wait()
|
|
|
|
|
|
class BlocksAndWallExampleMass1e6(BlocksAndWallExample):
|
|
CONFIG = {
|
|
"sliding_blocks_config": {
|
|
"block1_config": {
|
|
"mass": 1e6,
|
|
"velocity": -1,
|
|
},
|
|
},
|
|
"wait_time": 20,
|
|
}
|
|
|
|
|
|
class BlocksAndWallExampleMass1e6SlowMo(BlocksAndWallExample):
|
|
CONFIG = {
|
|
"sliding_blocks_config": {
|
|
"block1_config": {
|
|
"mass": 1e6,
|
|
"velocity": -0.1,
|
|
"distance": 4.1
|
|
},
|
|
},
|
|
"wait_time": 60,
|
|
"collision_sound": "slow_clack.wav",
|
|
}
|
|
|
|
|
|
class BlocksAndWallExampleMass1e8(BlocksAndWallExample):
|
|
CONFIG = {
|
|
"sliding_blocks_config": {
|
|
"block1_config": {
|
|
"mass": 1e8,
|
|
"velocity": -1,
|
|
},
|
|
},
|
|
"wait_time": 25,
|
|
}
|
|
|
|
|
|
class BlocksAndWallExampleMass1e10(BlocksAndWallExample):
|
|
CONFIG = {
|
|
"sliding_blocks_config": {
|
|
"block1_config": {
|
|
"mass": 1e10,
|
|
"velocity": -1,
|
|
},
|
|
},
|
|
"wait_time": 25,
|
|
}
|
|
|
|
|
|
class DigitsOfPi(Scene):
|
|
CONFIG = {"n_digits": 9}
|
|
|
|
def construct(self):
|
|
nd = self.n_digits
|
|
pow10 = int(10**nd)
|
|
rounded_pi = int(pow10 * PI) / pow10
|
|
equation = TexMobject(
|
|
("\\pi = {:." + str(nd) + "f}...").format(rounded_pi)
|
|
)
|
|
equation.set_color(YELLOW)
|
|
pi_creature = Randolph(color=YELLOW)
|
|
pi_creature.match_width(equation[0])
|
|
pi_creature.scale(1.4)
|
|
pi_creature.move_to(equation[0], DOWN)
|
|
self.add(pi_creature, equation[1])
|
|
self.play(ShowIncreasingSubsets(
|
|
equation[2:],
|
|
rate_func=None,
|
|
run_time=1,
|
|
))
|
|
self.play(Blink(pi_creature))
|
|
self.wait()
|
|
|
|
|
|
class GalperinPaperScroll(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
|
|
class PiComputingAlgorithmsAxes(Scene):
|
|
def construct(self):
|
|
self.setup_axes()
|
|
self.add_methods()
|
|
|
|
def setup_axes(self):
|
|
axes = Axes(
|
|
x_min=0,
|
|
y_min=0,
|
|
x_max=9,
|
|
y_max=5,
|
|
number_line_config={
|
|
"tick_frequency": 100,
|
|
"numbers_with_elongated_ticks": [],
|
|
}
|
|
)
|
|
|
|
y_label = TextMobject("Efficiency")
|
|
y_label.rotate(90 * DEGREES)
|
|
y_label.next_to(axes.y_axis, LEFT, SMALL_BUFF)
|
|
x_label = TextMobject("Elegance")
|
|
x_label.next_to(axes.x_axis, DOWN, SMALL_BUFF)
|
|
axes.add(y_label, x_label)
|
|
axes.center().to_edge(DOWN)
|
|
self.axes = axes
|
|
|
|
title = TextMobject("Algorithms for computing $\\pi$")
|
|
title.scale(1.5)
|
|
title.to_edge(UP, buff=MED_SMALL_BUFF)
|
|
|
|
self.add(title, axes)
|
|
|
|
def add_methods(self):
|
|
method_location_list = [
|
|
(self.get_machin_like_formula(), (1, 4.5)),
|
|
(self.get_viete(), (3, 3.5)),
|
|
(self.get_measuring_tape(), (0.5, 1)),
|
|
(self.get_monte_carlo(), (2, 0.2)),
|
|
(self.get_basel(), (6, 1)),
|
|
(self.get_blocks_image(), (8, 0.1)),
|
|
]
|
|
|
|
algorithms = VGroup()
|
|
for method, location in method_location_list:
|
|
cross = TexMobject("\\times")
|
|
cross.set_color(RED)
|
|
cross.move_to(self.axes.coords_to_point(*location))
|
|
method.next_to(cross, UP, SMALL_BUFF)
|
|
method.align_to(cross, LEFT)
|
|
method.shift_onto_screen()
|
|
algorithms.add(VGroup(method, cross))
|
|
|
|
self.play(LaggedStart(
|
|
FadeInFromDown, algorithms,
|
|
run_time=4,
|
|
lag_ratio=0.4,
|
|
))
|
|
self.wait()
|
|
self.play(ShowCreationThenFadeAround(algorithms[-1][0]))
|
|
|
|
def get_machin_like_formula(self):
|
|
formula = TexMobject(
|
|
"\\frac{\\pi}{4} = "
|
|
"12\\arctan\\left(\\frac{1}{49}\\right) + "
|
|
"32\\arctan\\left(\\frac{1}{57}\\right) - "
|
|
"5\\arctan\\left(\\frac{1}{239}\\right) + "
|
|
"12\\arctan\\left(\\frac{1}{110{,}443}\\right)"
|
|
)
|
|
formula.scale(0.5)
|
|
return formula
|
|
|
|
def get_viete(self):
|
|
formula = TexMobject(
|
|
"\\frac{2}{\\pi} = "
|
|
"\\frac{\\sqrt{2}}{2} \\cdot"
|
|
"\\frac{\\sqrt{2 + \\sqrt{2}}}{2} \\cdot"
|
|
"\\frac{\\sqrt{2 + \\sqrt{2 + \\sqrt{2}}}}{2} \\cdots"
|
|
)
|
|
formula.scale(0.5)
|
|
return formula
|
|
|
|
def get_measuring_tape(self):
|
|
return TextMobject("Measuring tape").scale(0.75)
|
|
|
|
def get_monte_carlo(self):
|
|
return TextMobject("Monte Carlo").scale(0.75)
|
|
|
|
def get_basel(self):
|
|
formula = TexMobject(
|
|
"\\frac{\\pi^2}{6} = "
|
|
"\\sum_{n=1}^\\infty \\frac{1}{n^2}"
|
|
)
|
|
formula.scale(0.5)
|
|
return formula
|
|
|
|
def get_blocks_image(self):
|
|
scene = BlocksAndWallScene(
|
|
write_to_movie=False,
|
|
skip_animations=True,
|
|
count_clacks=False,
|
|
floor_y=1,
|
|
wall_x=0,
|
|
n_wall_ticks=6,
|
|
sliding_blocks_config={
|
|
"block1_config": {
|
|
"mass": 1e2,
|
|
"velocity": -0.01,
|
|
"distance": 3.5
|
|
},
|
|
"block2_config": {
|
|
"distance": 1,
|
|
"velocity": 0,
|
|
},
|
|
}
|
|
)
|
|
group = VGroup(
|
|
scene.wall, scene.floor,
|
|
scene.blocks.block1,
|
|
scene.blocks.block2,
|
|
)
|
|
group.set_width(3)
|
|
return group
|
|
|
|
|
|
class StepsOfTheAlgorithm(TeacherStudentsScene):
|
|
def construct(self):
|
|
steps = self.get_steps()
|
|
steps.arrange_submobjects(
|
|
DOWN,
|
|
buff=MED_LARGE_BUFF,
|
|
aligned_edge=LEFT,
|
|
)
|
|
steps.to_corner(UL)
|
|
steps.scale(0.8)
|
|
|
|
for step in steps:
|
|
self.play(
|
|
FadeInFromDown(step[0]),
|
|
self.teacher.change, "raise_right_hand"
|
|
)
|
|
self.play(
|
|
Write(step[1], run_time=2),
|
|
self.get_student_changes(
|
|
*["pondering"] * 3,
|
|
look_at_arg=step,
|
|
)
|
|
)
|
|
self.wait()
|
|
self.change_student_modes(
|
|
"sassy", "erm", "confused",
|
|
look_at_arg=steps,
|
|
added_anims=[self.teacher.change, "happy"]
|
|
)
|
|
self.wait(3)
|
|
|
|
def get_steps(self):
|
|
return VGroup(
|
|
TextMobject("Step 1:", "Implement a physics engine"),
|
|
TextMobject(
|
|
"Step 2:",
|
|
"Choose the number of digits, $d$,\\\\"
|
|
"of $\\pi$ that you want to compute"
|
|
),
|
|
TextMobject(
|
|
"Step 3:",
|
|
"Set one mass to $100^{d - 1}$,\\\\"
|
|
"the other to $1$"
|
|
),
|
|
TextMobject("Step 4:", "Count collisions"),
|
|
)
|
|
|
|
|
|
class StepsOfTheAlgorithmJustTitles(StepsOfTheAlgorithm):
|
|
def construct(self):
|
|
self.remove(*self.pi_creatures)
|
|
titles = self.get_steps()
|
|
for title in titles:
|
|
title.scale(1.5)
|
|
title.to_edge(UP)
|
|
|
|
last_title = VectorizedPoint()
|
|
for title in titles:
|
|
self.play(
|
|
FadeInFromDown(title),
|
|
FadeOutAndShift(last_title, UP),
|
|
)
|
|
self.wait()
|
|
last_title = title
|
|
|
|
|
|
class BlocksAndWallExampleToShowWithSteps(BlocksAndWallExample):
|
|
CONFIG = {
|
|
"sliding_blocks_config": {
|
|
"block1_config": {
|
|
"mass": 1e22,
|
|
"velocity": -1,
|
|
"label_text": "$100^{(12 - 1)}$\\,kg",
|
|
"width": 2,
|
|
},
|
|
"collect_clack_data": False,
|
|
},
|
|
"wait_time": 25,
|
|
"counter_group_shift_vect": 5 * LEFT,
|
|
"count_clacks": True,
|
|
"include_sound": False,
|
|
"show_flash_animations": False,
|
|
}
|
|
|
|
|
|
class CompareToGalacticMass(Scene):
|
|
def construct(self):
|
|
self.add_digits_of_pi()
|
|
self.show_mass()
|
|
self.show_galactic_black_holes()
|
|
self.show_total_count()
|
|
|
|
def add_digits_of_pi(self):
|
|
# 20 digits of pi
|
|
digits = TexMobject("3.1415926535897932384...")
|
|
digits.set_width(FRAME_WIDTH - 3)
|
|
digits.to_edge(UP)
|
|
|
|
highlighted_digits = VGroup(*[
|
|
d.copy().set_background_stroke(color=BLUE, width=5)
|
|
for d in [digits[0], *digits[2:-3]]
|
|
])
|
|
counter = Integer(0)
|
|
counter.scale(1.5)
|
|
counter.set_color(BLUE)
|
|
brace = VMobject()
|
|
self.add(counter, brace)
|
|
|
|
for k in range(len(highlighted_digits)):
|
|
if k == 0:
|
|
self.add(digits[0])
|
|
else:
|
|
self.remove(highlighted_digits[k - 1])
|
|
self.add(digits[k + 1])
|
|
self.add(highlighted_digits[k])
|
|
counter.increment_value()
|
|
brace.become(Brace(highlighted_digits[:k + 1], DOWN))
|
|
counter.next_to(brace, DOWN)
|
|
self.wait(0.1)
|
|
self.add(digits)
|
|
self.remove(*highlighted_digits)
|
|
|
|
digits_word = TextMobject("digits")
|
|
digits_word.scale(1.5)
|
|
digits_word.match_color(counter)
|
|
counter.generate_target()
|
|
group = VGroup(counter.target, digits_word)
|
|
group.arrange_submobjects(
|
|
RIGHT,
|
|
index_of_submobject_to_align=0,
|
|
aligned_edge=DOWN,
|
|
buff=0.7,
|
|
)
|
|
group.next_to(brace, DOWN)
|
|
self.play(
|
|
MoveToTarget(counter),
|
|
FadeInFrom(digits_word, LEFT),
|
|
)
|
|
self.wait()
|
|
|
|
self.pi_digits_group = VGroup(
|
|
digits, brace, counter, digits_word
|
|
)
|
|
|
|
def show_mass(self):
|
|
bw_scene = BlocksAndWallExample(
|
|
write_to_movie=False,
|
|
skip_animations=True,
|
|
count_clacks=False,
|
|
show_flash_animations=False,
|
|
floor_y=0,
|
|
wall_x=-2,
|
|
n_wall_ticks=8,
|
|
sliding_blocks_config={
|
|
"block1_config": {
|
|
"mass": 1e6,
|
|
"velocity": -0.01,
|
|
"distance": 4.5,
|
|
"label_text": "$100^{(20 - 1)}$ kg",
|
|
"fill_color": BLACK,
|
|
},
|
|
"block2_config": {
|
|
"distance": 1,
|
|
"velocity": 0,
|
|
},
|
|
}
|
|
)
|
|
block1 = bw_scene.blocks.block1
|
|
block2 = bw_scene.blocks.block2
|
|
group = VGroup(
|
|
bw_scene.wall, bw_scene.floor,
|
|
block1, block2
|
|
)
|
|
group.center()
|
|
group.to_edge(DOWN)
|
|
|
|
arrow = Vector(2 * LEFT, color=RED)
|
|
arrow.shift(block1.get_center())
|
|
group.add(arrow)
|
|
|
|
brace = Brace(block1.label[:-2], UP, buff=SMALL_BUFF)
|
|
number_words = TextMobject(
|
|
"100", *["billion"] * 4,
|
|
)
|
|
number_words.next_to(brace, UP, buff=SMALL_BUFF)
|
|
VGroup(brace, number_words).set_color(YELLOW)
|
|
|
|
self.play(Write(group))
|
|
self.wait()
|
|
last_word = number_words[0].copy()
|
|
last_word.next_to(brace, UP, SMALL_BUFF)
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
FadeInFromDown(last_word),
|
|
)
|
|
for k in range(1, len(number_words) + 1):
|
|
self.remove(last_word)
|
|
last_word = number_words[:k].copy()
|
|
last_word.next_to(brace, UP, SMALL_BUFF)
|
|
self.add(last_word)
|
|
self.wait(0.4)
|
|
self.wait()
|
|
self.remove(last_word)
|
|
self.add(number_words)
|
|
group.add(brace, number_words)
|
|
self.play(group.to_corner, DL)
|
|
|
|
self.block1 = block1
|
|
self.block2 = block2
|
|
self.block_setup_group = group
|
|
|
|
def show_galactic_black_holes(self):
|
|
black_hole = SVGMobject(file_name="black_hole")
|
|
black_hole.set_color(BLACK)
|
|
black_hole.set_sheen(0.2, UL)
|
|
black_hole.set_height(1)
|
|
black_holes = VGroup(*[
|
|
black_hole.copy() for k in range(10)
|
|
])
|
|
black_holes.arrange_submobjects_in_grid(5, 2)
|
|
black_holes.to_corner(DR)
|
|
random.shuffle(black_holes.submobjects)
|
|
for bh in black_holes:
|
|
bh.save_state()
|
|
bh.scale(3)
|
|
bh.set_fill(DARK_GREY, 0)
|
|
|
|
equals = TexMobject("=")
|
|
equals.scale(2)
|
|
equals.next_to(self.block1, RIGHT)
|
|
|
|
words = TextMobject("10x Sgr A$^*$ \\\\ supermassive \\\\ black hole")
|
|
words.next_to(equals, RIGHT)
|
|
self.add(words)
|
|
|
|
self.play(
|
|
Write(equals),
|
|
Write(words),
|
|
LaggedStart(
|
|
Restore, black_holes,
|
|
run_time=3
|
|
)
|
|
)
|
|
self.wait()
|
|
|
|
self.black_hole_words = VGroup(equals, words)
|
|
self.black_holes = black_holes
|
|
|
|
def show_total_count(self):
|
|
digits = self.pi_digits_group[0]
|
|
to_fade = self.pi_digits_group[1:]
|
|
tex_string = "{:,}".format(31415926535897932384)
|
|
number = TexMobject(tex_string)
|
|
number.scale(1.5)
|
|
number.to_edge(UP)
|
|
|
|
commas = VGroup(*[
|
|
mob
|
|
for c, mob in zip(tex_string, number)
|
|
if c is ","
|
|
])
|
|
dots = VGroup(*[
|
|
mob
|
|
for c, mob in zip(digits.get_tex_string(), digits)
|
|
if c is "."
|
|
])
|
|
|
|
self.play(FadeOut(to_fade))
|
|
self.play(
|
|
ReplacementTransform(
|
|
VGroup(*filter(lambda m: m not in dots, digits)),
|
|
VGroup(*filter(lambda m: m not in commas, number)),
|
|
),
|
|
ReplacementTransform(
|
|
dots, commas,
|
|
submobject_mode="lagged_start",
|
|
run_time=2
|
|
)
|
|
)
|
|
|
|
group0 = number[:2].copy()
|
|
group1 = number[3:3 + 9 + 2].copy()
|
|
group2 = number[-(9 + 2):].copy()
|
|
for group in group0, group1, group2:
|
|
group.set_background_stroke(color=BLUE, width=5)
|
|
self.add(group)
|
|
self.wait(0.5)
|
|
self.remove(group)
|
|
|
|
|
|
class BlocksAndWallExampleGalacticMass(BlocksAndWallExample):
|
|
CONFIG = {
|
|
"sliding_blocks_config": {
|
|
"block1_config": {
|
|
"mass": 1e10,
|
|
"velocity": -1,
|
|
"label_text": "$100^{(20 - 1)}$\\,kg",
|
|
"width": 2,
|
|
},
|
|
},
|
|
"wait_time": 25,
|
|
"counter_group_shift_vect": 5 * LEFT,
|
|
"count_clacks": False,
|
|
}
|
|
|
|
def setup(self):
|
|
super().setup()
|
|
words = TextMobject(
|
|
"Burst of $10^{38}$ clacks per second"
|
|
)
|
|
words.scale(1.5)
|
|
words.to_edge(UP)
|
|
self.add(words)
|
|
|
|
|
|
class RealPhysicsVsThis(Scene):
|
|
def construct(self):
|
|
physics = TextMobject("Real physics")
|
|
this = TextMobject("This process")
|
|
this.set_color()
|
|
physics.to_edge(LEFT)
|
|
this.next_to(physics)
|
|
self.add(physics, this)
|
|
self.play(
|
|
this.shift, FRAME_WIDTH * RIGHT,
|
|
rate_func=rush_into,
|
|
run_time=3,
|
|
)
|
|
self.wait()
|
|
|
|
|
|
class CompareAlgorithmToPhysics(PiCreatureScene):
|
|
def construct(self):
|
|
morty = self.pi_creature
|
|
right_pic = ImageMobject(
|
|
self.get_image_file_path().replace(
|
|
str(self), "PiComputingAlgorithmsAxes"
|
|
)
|
|
)
|
|
right_rect = SurroundingRectangle(right_pic, buff=0, color=WHITE)
|
|
right_pic.add(right_rect)
|
|
right_pic.set_height(3)
|
|
right_pic.next_to(morty, UR)
|
|
right_pic.shift_onto_screen()
|
|
|
|
left_rect = right_rect.copy()
|
|
left_rect.next_to(morty, UL)
|
|
left_rect.shift_onto_screen()
|
|
|
|
self.play(
|
|
FadeInFromDown(right_pic),
|
|
morty.change, "raise_right_hand",
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
FadeInFromDown(left_rect),
|
|
morty.change, "raise_left_hand",
|
|
)
|
|
self.wait()
|
|
|
|
digits = TexMobject("3.141592653589793238462643383279502884197...")
|
|
digits.set_width(FRAME_WIDTH - 1)
|
|
digits.to_edge(UP)
|
|
self.play(
|
|
FadeOutAndShift(right_pic, 5 * RIGHT),
|
|
# FadeOutAndShift(left_rect, 5 * LEFT),
|
|
FadeOut(left_rect),
|
|
PiCreatureBubbleIntroduction(
|
|
morty, "This doesn't seem \\\\ like me...",
|
|
bubble_class=ThoughtBubble,
|
|
bubble_kwargs={"direction": LEFT},
|
|
target_mode="pondering",
|
|
look_at_arg=left_rect,
|
|
),
|
|
LaggedStart(
|
|
FadeInFrom, digits,
|
|
lambda m: (m, LEFT),
|
|
run_time=5,
|
|
lag_ratio=0.2,
|
|
)
|
|
)
|
|
self.blink()
|
|
self.wait()
|
|
self.play(morty.change, "confused", left_rect)
|
|
self.wait(5)
|
|
|
|
def create_pi_creature(self):
|
|
return Mortimer().flip().to_edge(DOWN)
|
|
|
|
|
|
class AskAboutWhy(TeacherStudentsScene):
|
|
def construct(self):
|
|
circle = Circle(radius=2, color=YELLOW)
|
|
circle.next_to(self.teacher, UL)
|
|
ke_conservation = TexMobject(
|
|
"\\frac{1}{2}m_1 v_1^2 + "
|
|
"\\frac{1}{2}m_2 v_2^2 = \\text{const.}"
|
|
)
|
|
ke_conservation.move_to(circle)
|
|
|
|
self.student_says("But why?")
|
|
self.change_student_modes(
|
|
"erm", "raise_left_hand", "sassy",
|
|
added_anims=[self.teacher.change, "happy"]
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
ShowCreation(circle),
|
|
RemovePiCreatureBubble(self.students[1]),
|
|
self.teacher.change, "raise_right_hand",
|
|
)
|
|
self.change_all_student_modes(
|
|
"pondering", look_at_arg=circle
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
Write(ke_conservation),
|
|
circle.stretch, 1.5, 0,
|
|
)
|
|
self.change_all_student_modes("confused")
|
|
self.look_at(circle)
|
|
self.wait(3)
|
|
|
|
|
|
class LightBouncingNoFanning(LightBouncing):
|
|
CONFIG = {
|
|
"mirror_shift_vect": 2 * DOWN,
|
|
"mirror_length": 6,
|
|
"beam_start_x": 8,
|
|
"beam_height": 0.5,
|
|
}
|
|
|
|
|
|
class LightBouncingFanning(LightBouncingNoFanning):
|
|
CONFIG = {
|
|
"show_fanning": True,
|
|
}
|
|
|
|
|
|
class NextVideo(Scene):
|
|
def construct(self):
|
|
videos = VGroup(*[VideoIcon() for x in range(2)])
|
|
videos.set_height(2)
|
|
for video in videos:
|
|
video.set_color(BLUE)
|
|
video.set_sheen(0.5, UL)
|
|
videos.arrange_submobjects(RIGHT, buff=2)
|
|
|
|
titles = VGroup(
|
|
TextMobject("Here and now"),
|
|
TextMobject("Solution"),
|
|
)
|
|
for title, video in zip(titles, videos):
|
|
# title.scale(1.5)
|
|
title.next_to(video, UP)
|
|
video.add(title)
|
|
|
|
dots = TextMobject(".....")
|
|
dots.scale(2)
|
|
dots.move_to(videos)
|
|
|
|
mid_words = TextMobject(
|
|
"Patient\\\\", "problem\\\\", "solving"
|
|
)
|
|
mid_words.next_to(dots, DOWN)
|
|
randy = Randolph(height=1)
|
|
randy.next_to(dots, UP, SMALL_BUFF)
|
|
thought_bubble = ThoughtBubble(height=2, width=2, direction=LEFT)
|
|
thought_bubble.set_stroke(width=2)
|
|
thought_bubble.move_to(randy.get_corner(UR), DL)
|
|
speech_bubble = SpeechBubble(height=2, width=2)
|
|
speech_bubble.pin_to(randy)
|
|
speech_bubble.write("What do \\\\ you think?")
|
|
friends = VGroup(
|
|
PiCreature(color=BLUE_E),
|
|
PiCreature(color=BLUE_C),
|
|
Mortimer()
|
|
)
|
|
friends.set_height(1)
|
|
friends.arrange_submobjects(RIGHT, buff=MED_SMALL_BUFF)
|
|
friends[:2].next_to(randy, LEFT)
|
|
friends[2].next_to(randy, RIGHT)
|
|
|
|
self.add(videos[0])
|
|
self.wait()
|
|
self.play(
|
|
TransformFromCopy(*videos),
|
|
)
|
|
self.play(Write(dots))
|
|
self.wait()
|
|
self.play(
|
|
LaggedStart(
|
|
FadeInFrom, mid_words,
|
|
lambda m: (m, UP),
|
|
lag_ratio=0.8,
|
|
),
|
|
randy.change, "pondering",
|
|
VFadeIn(randy),
|
|
videos.space_out_submobjects, 1.3,
|
|
)
|
|
self.play(ShowCreation(thought_bubble))
|
|
self.play(Blink(randy))
|
|
self.play(
|
|
Uncreate(thought_bubble),
|
|
ShowCreation(speech_bubble),
|
|
Write(speech_bubble.content),
|
|
randy.change, "maybe", friends[0].eyes,
|
|
LaggedStart(FadeInFromDown, friends),
|
|
videos.space_out_submobjects, 1.6,
|
|
)
|
|
self.play(
|
|
LaggedStart(
|
|
ApplyMethod, friends,
|
|
lambda m: (m.change, "pondering"),
|
|
run_time=1,
|
|
lag_ratio=0.7,
|
|
)
|
|
)
|
|
self.play(Blink(friends[2]))
|
|
self.play(friends[0].change, "confused")
|
|
self.wait()
|
|
|
|
|
|
class EndScreen(Scene):
|
|
def construct(self):
|
|
width = (475 / 1280) * FRAME_WIDTH
|
|
height = width * (323 / 575)
|
|
video_rect = Rectangle(
|
|
width=width,
|
|
height=height,
|
|
)
|
|
video_rect.shift(UP)
|
|
video_rects = VGroup(*[
|
|
video_rect.copy().set_color(color)
|
|
for color in [BLUE_E, BLUE_C, BLUE_D, GREY_BROWN]
|
|
])
|
|
for rect in video_rects[1::2]:
|
|
rect.reverse_points()
|
|
video_rect.set_fill(DARK_GREY, 0.5)
|
|
video_rect.set_stroke(GREY_BROWN, 0.5)
|
|
date = TextMobject(
|
|
"Solution will be\\\\"
|
|
"posted", "1/20/19",
|
|
)
|
|
date[1].set_color(YELLOW)
|
|
date.set_width(video_rect.get_width() - 2 * MED_SMALL_BUFF)
|
|
date.move_to(video_rect)
|
|
|
|
handle = TextMobject("@3blue1brown")
|
|
handle.next_to(video_rect, DOWN, MED_LARGE_BUFF)
|
|
|
|
self.add(video_rect, date, handle)
|
|
for n in range(10):
|
|
self.play(
|
|
FadeOut(video_rects[(n - 1) % 4]),
|
|
ShowCreation(video_rects[n % 4]),
|
|
run_time=2,
|
|
)
|
|
|
|
|
|
class Thumbnail(BlocksAndWallExample, MovingCameraScene):
|
|
CONFIG = {
|
|
"sliding_blocks_config": {
|
|
"block1_config": {
|
|
"mass": 1e4,
|
|
"velocity": -1.5,
|
|
},
|
|
"collect_clack_data": False,
|
|
},
|
|
"wait_time": 0,
|
|
"count_clacks": False,
|
|
"show_flash_animations": False,
|
|
"floor_y": -3.0,
|
|
}
|
|
|
|
def setup(self):
|
|
MovingCameraScene.setup(self)
|
|
BlocksAndWallExample.setup(self)
|
|
|
|
def construct(self):
|
|
self.camera_frame.shift(0.9 * UP)
|
|
self.thicken_lines()
|
|
self.grow_labels()
|
|
self.add_vector()
|
|
self.add_text()
|
|
|
|
def thicken_lines(self):
|
|
self.floor.set_stroke(WHITE, 10)
|
|
self.wall.set_stroke(WHITE, 10)
|
|
self.wall[1:].set_stroke(WHITE, 4)
|
|
|
|
def grow_labels(self):
|
|
blocks = self.blocks
|
|
for block in blocks.block1, blocks.block2:
|
|
block.remove(block.label)
|
|
block.label.scale(2.5, about_point=block.get_top())
|
|
self.add(block.label)
|
|
|
|
def add_vector(self):
|
|
blocks = self.blocks
|
|
arrow = Vector(
|
|
2.5 * LEFT,
|
|
color=RED,
|
|
rectangular_stem_width=1.5,
|
|
tip_length=0.5
|
|
)
|
|
arrow.move_to(blocks.block1.get_center(), RIGHT)
|
|
arrow.add_to_back(
|
|
arrow.copy().set_stroke(GREY, 5)
|
|
)
|
|
self.add(arrow)
|
|
|
|
def add_text(self):
|
|
question = TextMobject("How many\\\\collisions?")
|
|
question.scale(2.5)
|
|
question.to_edge(UP)
|
|
question.set_color(YELLOW)
|
|
question.set_stroke(RED, 2, background=True)
|
|
self.add(question)
|