Updates to light-bouncing clack solution

This commit is contained in:
Grant Sanderson 2019-01-29 14:23:13 -08:00
parent c8c003ba4b
commit dc24c03349
6 changed files with 714 additions and 64 deletions

View file

@ -2,6 +2,9 @@
# from active_projects.clacks import solution1
from active_projects.clacks.solution2 import block_collision_scenes
from active_projects.clacks.solution2 import simple_scenes
from active_projects.clacks.solution2 import wordy_scenes
from active_projects.clacks.solution2 import pi_creature_scenes
from active_projects.clacks.solution2 import position_phase_space
OUTPUT_DIRECTORY = "clacks_solution2"
ALL_SCENE_CLASSES = [
@ -12,4 +15,7 @@ ALL_SCENE_CLASSES = [
block_collision_scenes.IntroducePreviousTwoVideos,
block_collision_scenes.PreviousTwoVideos,
simple_scenes.TwoSolutionsWrapper,
wordy_scenes.ConnectionToOptics,
pi_creature_scenes.OnAnsweringTwice,
position_phase_space.IntroducePositionPhaseSpace,
]

View file

@ -1,6 +1,4 @@
from big_ol_pile_of_manim_imports import *
import subprocess
from pydub import AudioSegment
class Block(Square):
@ -370,27 +368,6 @@ class BlocksAndWallScene(Scene):
if self.include_sound:
self.add_clack_sounds(self.clack_data)
# TODO, this no longer works
# should use Scene.add_sound instead
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

View file

@ -1,6 +1,47 @@
from big_ol_pile_of_manim_imports import *
class NewSceneName(TeacherStudentsScene):
class OnAnsweringTwice(TeacherStudentsScene):
def construct(self):
pass
question = TextMobject("Why $\\pi$?")
question.move_to(self.screen)
question.to_edge(UP)
other_questions = VGroup(
TextMobject("Frequency of collisions?"),
TextMobject("Efficient simulation?"),
TextMobject("Time until last collision?"),
)
for mob in other_questions:
mob.move_to(self.hold_up_spot, DOWN)
self.add(question)
self.student_says(
"But we already \\\\ solved it",
bubble_kwargs={"direction": LEFT},
target_mode="raise_left_hand",
added_anims=[self.teacher.change, "thinking"]
)
self.change_student_modes("sassy", "angry")
self.wait()
self.play(
RemovePiCreatureBubble(self.students[2]),
self.get_student_changes("erm", "erm"),
ApplyMethod(
question.move_to, self.hold_up_spot, DOWN,
path_arc=-90 * DEGREES,
),
self.teacher.change, "raise_right_hand",
)
shown_questions = VGroup(question)
for oq in other_questions:
self.play(
shown_questions.shift, 0.85 * UP,
FadeInFromDown(oq),
self.get_student_changes(
*["pondering"] * 3,
look_at_arg=oq
)
)
shown_questions.add(oq)
self.wait(3)

View file

@ -0,0 +1,464 @@
from big_ol_pile_of_manim_imports import *
from active_projects.clacks.question import Block
from active_projects.clacks.question import Wall
class PositionPhaseSpaceScene(Scene):
CONFIG = {
"rescale_coordinates": True,
"wall_x": -6,
"wall_config": {
"height": 1.6,
"tick_spacing": 0.35,
"tick_length": 0.2,
},
"wall_height": 1.5,
"floor_y": -3.5,
"block1_config": {
"mass": 10,
"distance": 7,
"velocity": 1,
"width": 1.6,
},
"block2_config": {
"mass": 1,
"distance": 4,
},
"axes_config": {
"x_min": -0.5,
"x_max": 31,
"y_min": -0.5,
"y_max": 10.5,
"x_axis_config": {
"unit_size": 0.4,
"tick_frequency": 2,
},
"y_axis_config": {
"unit_size": 0.4,
"tick_frequency": 2,
},
},
"axes_center": 5 * LEFT + 0.65 * DOWN,
"ps_dot_config": {
"fill_color": RED,
"background_stroke_width": 1,
"background_stroke_color": BLACK,
"radius": 0.05,
}
}
def setup(self):
self.all_items = [
self.get_floor(),
self.get_wall(),
self.get_blocks(),
self.get_axes(),
self.get_phase_space_point(),
self.get_phase_space_x_line(),
self.get_phase_space_y_line(),
self.get_phase_space_dot(),
self.get_phase_space_d1_label(),
self.get_phase_space_d2_label(),
self.get_d1_brace(),
self.get_d2_brace(),
self.get_d1_label(),
self.get_d2_label(),
self.get_d1_eq_d2_line(),
self.get_d1_eq_d2_label(),
self.get_d2_eq_w2_line(),
self.get_d2_eq_w2_label(),
self.get_time_tracker(),
]
def get_corner(self):
return self.wall_x * RIGHT + self.floor_y * UP
def get_mass_ratio(self):
return op.truediv(
self.block1.mass,
self.block2.mass,
)
def d1_to_x(self, d1):
if self.rescale_coordinates:
d1 *= np.sqrt(self.block1.mass)
return d1
def d2_to_y(self, d2):
if self.rescale_coordinates:
d2 *= np.sqrt(self.block2.mass)
return d2
def ds_to_point(self, d1, d2):
return self.axes.coords_to_point(
self.d1_to_x(d1), self.d2_to_y(d2),
)
def point_to_ds(self, point):
x, y = self.axes.point_to_coords(point)
if self.rescale_coordinates:
x /= np.sqrt(self.block1.mass)
y /= np.sqrt(self.block2.mass)
return (x, y)
def get_d1(self):
return self.get_ds()[0]
def get_d2(self):
return self.get_ds()[1]
def get_ds(self):
return self.point_to_ds(self.ps_point.get_location())
def tie_blocks_to_ps_point(self):
def update_blocks(blocks):
d1, d2 = self.point_to_ds(self.ps_point.get_location())
b1, b2 = blocks
corner = self.get_corner()
b1.move_to(corner + d1 * RIGHT, DL)
b2.move_to(corner + d2 * RIGHT, DR)
self.blocks.add_updater(update_blocks)
def time_to_ds(self, time):
# Deals in its own phase space, different
# from the one displayed
m1 = self.block1.mass
m2 = self.block2.mass
v1 = self.block1.velocity
# v2 = self.block2.velocity
d1 = self.block1_config["distance"]
d2 = self.block2_config["distance"]
w2 = self.block2.width
ps_speed = np.sqrt(m1) * abs(v1)
theta = np.arctan(np.sqrt(m2 / m1))
def ds_to_ps_point(d1, d2):
return np.array([
d1 * np.sqrt(m1),
d2 * np.sqrt(m2),
0
])
def ps_point_to_ds(point):
return (
point[0] / np.sqrt(m1),
point[1] / np.sqrt(m2),
)
ps_point = ds_to_ps_point(d1, d2 + w2)
wedge_corner = ds_to_ps_point(w2, w2)
ps_point -= wedge_corner
# Pass into the mirror worlds
ps_point += time * ps_speed * LEFT
# Reflect back to the real world
angle = angle_of_vector(ps_point)
n = int(angle / theta)
if n % 2 == 0:
ps_point = rotate_vector(ps_point, -n * theta)
else:
ps_point = rotate_vector(
ps_point,
-(n + 1) * theta,
)
ps_point[1] = abs(ps_point[1])
ps_point += wedge_corner
return ps_point_to_ds(ps_point)
def get_clack_flashes(self):
pass # TODO
# Mobject getters
def get_floor(self):
floor = self.floor = Line(
self.wall_x * RIGHT,
FRAME_WIDTH * RIGHT / 2,
stroke_color=WHITE,
stroke_width=3,
)
floor.move_to(self.get_corner(), LEFT)
return floor
def get_wall(self):
wall = self.wall = Wall(**self.wall_config)
wall.move_to(self.get_corner(), DR)
return wall
def get_blocks(self):
blocks = self.blocks = VGroup()
for n in [1, 2]:
config = getattr(self, "block{}_config".format(n))
block = Block(**config)
block.move_to(self.get_corner(), DL)
block.shift(config["distance"] * RIGHT)
block.label.move_to(block)
block.label.set_fill(BLACK)
block.label.set_stroke(WHITE, 3, background=True)
self.blocks.add(block)
self.block1, self.block2 = blocks
return blocks
def get_axes(self):
axes = self.axes = Axes(**self.axes_config)
axes.set_stroke(LIGHT_GREY, 2)
axes.shift(
self.axes_center - axes.coords_to_point(0, 0)
)
axes.add(self.get_axes_labels(axes))
return axes
def get_axes_labels(self, axes):
x_label = TexMobject("x = ", "d_1")
y_label = TexMobject("y = ", "d_2")
labels = VGroup(x_label, y_label)
if self.rescale_coordinates:
additions = map(TexMobject, [
"\\sqrt{m_1}", "\\sqrt{m_2}"
])
for label, addition in zip(labels, additions):
addition.move_to(label[1], DL)
label[1].next_to(
addition, RIGHT, SMALL_BUFF,
aligned_edge=DOWN
)
addition[2:].set_color(BLUE)
label.add(addition)
x_label.next_to(axes.x_axis.get_right(), DL, MED_SMALL_BUFF)
y_label.next_to(axes.y_axis.get_top(), DR, MED_SMALL_BUFF)
for label in labels:
label.shift_onto_screen()
return labels
def get_phase_space_point(self):
ps_point = self.ps_point = VectorizedPoint()
ps_point.move_to(self.ds_to_point(
self.block1.distance,
self.block2.distance + self.block2.width
))
self.tie_blocks_to_ps_point()
return ps_point
def get_phase_space_x_line(self):
def get_x_line():
origin = self.axes.coords_to_point(0, 0)
point = self.ps_point.get_location()
y_axis_point = np.array(origin)
y_axis_point[1] = point[1]
return DashedLine(
y_axis_point, point,
color=GREEN,
stroke_width=2,
)
self.x_line = updating_mobject_from_func(get_x_line)
return self.x_line
def get_phase_space_y_line(self):
def get_y_line():
origin = self.axes.coords_to_point(0, 0)
point = self.ps_point.get_location()
x_axis_point = np.array(origin)
x_axis_point[0] = point[0]
return DashedLine(
x_axis_point, point,
color=RED,
stroke_width=2,
)
self.y_line = updating_mobject_from_func(get_y_line)
return self.y_line
def get_phase_space_dot(self):
self.ps_dot = ps_dot = Dot(**self.ps_dot_config)
ps_dot.add_updater(lambda m: m.move_to(self.ps_point))
return ps_dot
def get_d_label(self, n, get_d):
label = VGroup(
TexMobject("d_{}".format(n), "="),
DecimalNumber(),
)
color = GREEN if n == 1 else RED
label[0].set_color_by_tex("d_", color)
label.scale(0.7)
def update_value(label):
lhs, rhs = label
rhs.set_value(get_d())
rhs.next_to(
lhs, RIGHT, SMALL_BUFF,
aligned_edge=DOWN,
)
label.add_updater(update_value)
return label
def get_phase_space_d_label(self, n, get_d, line, vect):
label = self.get_d_label(n, get_d)
label.add_updater(
lambda m: m.next_to(line, vect, SMALL_BUFF)
)
return label
def get_phase_space_d1_label(self):
self.ps_d1_label = self.get_phase_space_d_label(
1, self.get_d1, self.x_line, UP,
)
return self.ps_d1_label
def get_phase_space_d2_label(self):
self.ps_d2_label = self.get_phase_space_d_label(
2, self.get_d2, self.y_line, RIGHT,
)
return self.ps_d2_label
def get_d_brace(self, get_right_point):
line = Line(LEFT, RIGHT).set_width(6)
brace = Brace(line, UP)
def update_brace(brace):
right_point = get_right_point()
left_point = np.array(right_point)
left_point[0] = self.wall_x
line.put_start_and_end_on(left_point, right_point)
brace.match_width(line, stretch=True)
brace.next_to(line, UP, buff=0)
brace.add_updater(update_brace)
return brace
def get_d1_brace(self):
self.d1_brace = self.get_d_brace(
lambda: self.block1.get_corner(UL)
)
return self.d1_brace
def get_d2_brace(self):
self.d2_brace = self.get_d_brace(
lambda: self.block2.get_corner(UR)
)
# self.flip_brace_nip()
return self.d2_brace
def flip_brace_nip(self, brace):
nip_index = (len(brace) // 2) - 1
nip = brace[nip_index:nip_index + 2]
rect = brace[nip_index - 1]
center = rect.get_center()
center[0] = nip.get_center()[0]
nip.rotate(PI, about_point=center)
def get_brace_d_label(self, n, get_d, brace, vect):
label = self.get_d_label(n, get_d)
label.add_updater(
lambda m: m.next_to(brace, vect, 0)
)
return label
def get_d1_label(self):
self.d1_label = self.get_brace_d_label(
1, self.get_d1, self.d1_brace, UP
)
return self.d1_label
def get_d2_label(self):
self.d2_label = self.get_brace_d_label(
2, self.get_d2, self.d2_brace, UP
)
return self.d2_label
def get_d1_eq_d2_line(self):
start = self.ds_to_point(0, 0)
end = self.ds_to_point(15, 15)
self.d1_eq_d2_line = DashedLine(start, end)
self.d1_eq_d2_line.set_stroke(WHITE, 2)
return self.d1_eq_d2_line
def get_d1_eq_d2_label(self):
label = TexMobject("d1 = d2")
label.scale(0.75)
line = self.d1_eq_d2_line
for i in range(len(line)):
if line[i].get_top()[1] > FRAME_HEIGHT / 2:
break
label.next_to(line[i - 3], DR, SMALL_BUFF)
self.d1_eq_d2_label = label
return label
def get_d2_eq_w2_line(self):
w2 = self.block2.width
start = self.ds_to_point(0, w2)
end = self.ds_to_point(30, w2)
self.d2_eq_w2_line = DashedLine(start, end)
self.d2_eq_w2_line.set_stroke(WHITE, 2)
return self.d2_eq_w2_line
def get_d2_eq_w2_label(self):
label = TexMobject("d2 = \\text{block width}")
label.scale(0.75)
label.next_to(self.d2_eq_w2_line, UP, SMALL_BUFF)
label.to_edge(RIGHT, buff=MED_SMALL_BUFF)
self.d2_eq_w_label = label
return label
def get_time_tracker(self):
time_tracker = self.time_tracker = ValueTracker(0)
time_tracker.add_updater(
lambda m, dt: m.increment_value(dt)
)
self.get_time = time_tracker.get_value
self.add(time_tracker)
return time_tracker
class IntroducePositionPhaseSpace(PositionPhaseSpaceScene):
CONFIG = {
"rescale_coordinates": False,
}
def construct(self):
self.show_coordinates()
self.show_xy_line()
self.let_process_play_out()
self.add(*self.all_items)
self.ps_point.add_updater(
lambda m: m.move_to(self.ds_to_point(
*self.time_to_ds(self.get_time())
))
)
self.wait(10)
# self.play(Rotating(
# self.ps_point,
# about_point=self.ps_point.get_location() + RIGHT,
# run_time=3
# ))
def show_coordinates(self):
pass
def show_xy_line(self):
pass
def let_process_play_out(self):
pass
class EqualMassCase(IntroducePositionPhaseSpace):
def construct(self):
self.show_first_point()
self.up_to_first_collision()
self.ask_about_momentum_transfer()
self.up_to_second_collision()
self.up_to_third_collision()
def show_first_point(self):
pass
def up_to_first_collision(self):
pass
def ask_about_momentum_transfer(self):
pass
def up_to_second_collision(self):
pass
def up_to_third_collision(self):
pass

View file

@ -7,42 +7,3 @@ class TwoSolutionsWrapper(Scene):
def construct(self):
pass
class ConnectionToOptics(Scene):
def construct(self):
e_group, m_group = k_groups = self.get_kinematics_groups()
self.add(k_groups)
def get_kinematics_groups(self):
tex_to_color_map = {
"m_1": BLUE,
"m_2": BLUE,
"v_1": RED,
"v_2": RED,
}
energy_eq = TexMobject(
"\\frac{1}{2} m_1 (v_1)^2 + "
"\\frac{1}{2} m_2 (v_2)^2 = "
"\\text{const.}",
tex_to_color_map=tex_to_color_map
)
momentum_eq = TexMobject(
"m_1 v_1 + m_2 v_2 = \\text{const.}",
tex_to_color_map=tex_to_color_map
)
energy_label = TextMobject(
"Conservation of energy"
)
momentum_label = TextMobject(
"Conservation of momentum"
)
energy_group = VGroup(energy_eq, energy_label)
momentum_group = VGroup(momentum_eq, momentum_label)
groups = VGroup(energy_group, momentum_group)
for group in groups:
group.arrange_submobjects(DOWN)
groups.arrange_submobjects(DOWN, LARGE_BUFF)
groups.to_edge(LEFT)
return groups

View file

@ -0,0 +1,201 @@
from big_ol_pile_of_manim_imports import *
class ConnectionToOptics(Scene):
def construct(self):
e_group, m_group = k_groups = self.get_kinematics_groups()
c_group, a_group = o_groups = self.get_optics_groups()
arrows = VGroup()
for g1, g2 in zip(k_groups, o_groups):
g2.align_to(g1, UP)
g2.to_edge(RIGHT)
arrow = TexMobject("\\Rightarrow")
arrow.scale(1.5)
arrow.move_to(interpolate(
g1[0].get_right(), g2[0].get_left(), 0.5
))
arrows.add(arrow)
everything = VGroup(k_groups, arrows, o_groups)
everything.to_edge(UP)
everything.generate_target()
everything.target.scale(0.9)
everything.target.to_edge(DOWN)
width = max([m.get_width() for m in everything.target])
width += 2 * MED_SMALL_BUFF
rects = VGroup()
for k in [0, 2]:
rect = DashedMobject(Rectangle(
height=FRAME_HEIGHT - 1.5,
width=width
), dashes_num=100)
rect.move_to(everything.target[k])
rect.to_edge(DOWN, buff=SMALL_BUFF)
rects.add(rect)
titles = VGroup(
TextMobject("Kinematics"),
TextMobject("Optics"),
)
titles.scale(1.5)
for title, rect in zip(titles, rects):
title.next_to(rect, UP)
titles[0].align_to(titles[1], UP)
self.play(FadeInFromDown(e_group))
self.play(
Write(arrows[0]),
FadeInFrom(c_group, LEFT)
)
self.wait()
self.play(FadeInFromDown(m_group))
self.play(
Write(arrows[1]),
FadeInFrom(a_group, LEFT)
)
self.wait(4)
for k in range(2):
anims = [
ShowCreation(rects[k]),
FadeInFromDown(titles[k]),
]
if k == 0:
anims.append(MoveToTarget(everything))
self.play(*anims)
self.wait()
self.wait()
self.wait(4)
def get_kinematics_groups(self):
tex_to_color_map = {
"m_1": BLUE,
"m_2": BLUE,
"v_1": RED,
"v_2": RED,
}
energy_eq = TexMobject(
"\\frac{1}{2} m_1 (v_1)^2 + "
"\\frac{1}{2} m_2 (v_2)^2 = "
"\\text{const.}",
tex_to_color_map=tex_to_color_map
)
energy_eq.scale(0.8)
momentum_eq = TexMobject(
"m_1 v_1 + m_2 v_2 = \\text{const.}",
tex_to_color_map=tex_to_color_map
)
energy_label = TextMobject(
"Conservation of energy"
)
momentum_label = TextMobject(
"Conservation of momentum"
)
energy_group = VGroup(energy_label, energy_eq)
momentum_group = VGroup(momentum_label, momentum_eq)
groups = VGroup(energy_group, momentum_group)
for group in groups:
group.arrange_submobjects(DOWN, buff=MED_LARGE_BUFF)
group[0].set_color(GREEN)
groups.arrange_submobjects(DOWN, buff=2)
groups.to_edge(LEFT)
return groups
def get_optics_groups(self):
self.time_tracker = ValueTracker(0)
self.time_tracker.add_updater(
lambda m, dt: m.increment_value(dt)
)
self.add(self.time_tracker)
return VGroup(
self.get_speed_group(),
self.get_angle_group()
)
def get_speed_group(self):
speed_label = TextMobject("Constant speed of light")
speed_label.set_color(YELLOW)
speed_light_template = Line(LEFT, RIGHT)
speed_light_template.fade(1)
speed_light_template.match_width(speed_label)
speed_light_template.next_to(speed_label, DOWN, MED_LARGE_BUFF)
speed_light = speed_light_template.deepcopy()
def update_speed_light(light, period=2, time_width=0.05):
time = self.time_tracker.get_value()
alpha = (time / period) % 1
# alpha = 1 - 2 * abs(alpha - 0.5)
alpha *= 1.5
a = alpha - time_width / 2
b = alpha + time_width / 2
light.pointwise_become_partial(
speed_light_template, max(a, 0), min(b, 1)
)
opacity = speed_label.family_members_with_points()[0].get_fill_opacity()
light.set_stroke(YELLOW, width=3, opacity=opacity)
# light.stretch(0.5, 0)
# point = speed_light_template.point_from_proportion(0.25)
# light.stretch(2, 0, about_point=point)
speed_light.add_updater(update_speed_light)
result = VGroup(
speed_label, speed_light_template, speed_light
)
return result
def get_angle_group(self):
title = VGroup(*map(TextMobject, [
"Angle of\\\\Incidence",
"=",
"Angle of\\\\Refraction",
])).arrange_submobjects(RIGHT)
title.set_color(YELLOW)
h_line = Line(LEFT, RIGHT)
h_line.match_width(title)
h_line.set_stroke(LIGHT_GREY)
h_line.set_sheen(1, UL)
points = [
h_line.get_left() + UP,
h_line.get_center(),
h_line.get_right() + UP,
]
dashed_lines = VGroup(
DashedLine(*points[0:2]), DashedLine(*points[1:3])
)
dashed_lines.set_stroke(WHITE, 2)
v_shape = VMobject()
v_shape.set_points_as_corners(points)
v_shape.fade(1)
theta = dashed_lines[1].get_angle()
arcs = VGroup(
Arc(start_angle=0, angle=theta),
Arc(start_angle=PI, angle=-theta),
)
arcs.set_stroke(WHITE, 2)
thetas = VGroup()
for v in LEFT, RIGHT:
theta = TexMobject("\\theta")
theta.next_to(arcs, v, aligned_edge=DOWN)
theta.shift(SMALL_BUFF * UP)
thetas.add(theta)
beam = VMobject()
def update_beam(beam, period=2, time_width=0.05):
time = self.time_tracker.get_value()
alpha = (time / period) % 1
alpha *= 1.5
a = alpha - time_width / 2
b = alpha + time_width / 2
beam.pointwise_become_partial(
v_shape, max(a, 0), min(b, 1)
)
opacity = title.family_members_with_points()[0].get_fill_opacity()
beam.set_stroke(YELLOW, width=3, opacity=opacity)
beam.add_updater(update_beam)
title.next_to(v_shape, UP, MED_LARGE_BUFF)
return VGroup(
title, h_line, arcs, thetas,
dashed_lines, v_shape, beam
)