Further animations for clacks solution 2 project

This commit is contained in:
Grant Sanderson 2019-01-29 23:53:37 -08:00
parent a65dbf27d9
commit 5d8c08c114
4 changed files with 673 additions and 65 deletions

View file

@ -8,14 +8,13 @@ from active_projects.clacks.solution2 import position_phase_space
OUTPUT_DIRECTORY = "clacks_solution2"
ALL_SCENE_CLASSES = [
# question.Thumbnail,
# solution1.SolutionThumbnail,
# question.BlocksAndWallExampleMass1e2,
# question.BlocksAndWallExampleMass1e4,
block_collision_scenes.IntroducePreviousTwoVideos,
block_collision_scenes.PreviousTwoVideos,
simple_scenes.TwoSolutionsWrapper,
wordy_scenes.ConnectionToOptics,
pi_creature_scenes.OnAnsweringTwice,
position_phase_space.IntroducePositionPhaseSpace,
position_phase_space.EqualMassCase,
pi_creature_scenes.AskAboutEqualMassMomentumTransfer,
position_phase_space.FailedAngleRelation,
]

View file

@ -215,6 +215,10 @@ class ClackFlashes(ContinualAnimation):
continue
last_time = time
flash = Flash(location, **self.flash_config)
for sm in flash.mobject.family_members_with_points():
if isinstance(sm, VMobject):
sm.set_stroke(YELLOW, 3)
sm.set_stroke(WHITE, 6, 0.5, background=True)
flash.start_time = time
flash.end_time = time + flash.run_time
self.flashes.append(flash)
@ -222,16 +226,17 @@ class ClackFlashes(ContinualAnimation):
def update_mobject(self, dt):
total_time = self.external_time
group = self.mobject
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)
if flash.mobject not in group:
group.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)
if flash.mobject in group:
group.remove(flash.mobject)
class Wall(Line):

View file

@ -45,3 +45,8 @@ class OnAnsweringTwice(TeacherStudentsScene):
)
shown_questions.add(oq)
self.wait(3)
class AskAboutEqualMassMomentumTransfer(TeacherStudentsScene):
def construct(self):
pass

View file

@ -1,6 +1,7 @@
from big_ol_pile_of_manim_imports import *
from active_projects.clacks.question import Block
from active_projects.clacks.question import Wall
from active_projects.clacks.question import ClackFlashes
class PositionPhaseSpaceScene(Scene):
@ -16,7 +17,7 @@ class PositionPhaseSpaceScene(Scene):
"floor_y": -3.5,
"block1_config": {
"mass": 10,
"distance": 7,
"distance": 9,
"velocity": 1,
"width": 1.6,
},
@ -44,7 +45,13 @@ class PositionPhaseSpaceScene(Scene):
"background_stroke_width": 1,
"background_stroke_color": BLACK,
"radius": 0.05,
}
},
"clack_sound": "clack",
"mirror_line_class": Line,
"mirror_line_style": {
"stroke_color": WHITE,
"stroke_width": 1,
},
}
def setup(self):
@ -67,10 +74,9 @@ class PositionPhaseSpaceScene(Scene):
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):
def get_floor_wall_corner(self):
return self.wall_x * RIGHT + self.floor_y * UP
def get_mass_ratio(self):
@ -110,11 +116,12 @@ class PositionPhaseSpaceScene(Scene):
def get_ds(self):
return self.point_to_ds(self.ps_point.get_location())
# Relevant for sliding
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()
corner = self.get_floor_wall_corner()
b1.move_to(corner + d1 * RIGHT, DL)
b2.move_to(corner + d2 * RIGHT, DR)
self.blocks.add_updater(update_blocks)
@ -125,10 +132,10 @@ class PositionPhaseSpaceScene(Scene):
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"]
start_d1 = self.block1_config["distance"]
start_d2 = self.block2_config["distance"]
w2 = self.block2.width
start_d2 += w2
ps_speed = np.sqrt(m1) * abs(v1)
theta = np.arctan(np.sqrt(m2 / m1))
@ -145,7 +152,7 @@ class PositionPhaseSpaceScene(Scene):
point[1] / np.sqrt(m2),
)
ps_point = ds_to_ps_point(d1, d2 + w2)
ps_point = ds_to_ps_point(start_d1, start_d2)
wedge_corner = ds_to_ps_point(w2, w2)
ps_point -= wedge_corner
# Pass into the mirror worlds
@ -164,9 +171,143 @@ class PositionPhaseSpaceScene(Scene):
ps_point += wedge_corner
return ps_point_to_ds(ps_point)
def get_clack_data(self):
# Copying from time_to_ds. Not great, but
# maybe I'll factor things out properly later.
m1 = self.block1.mass
m2 = self.block2.mass
v1 = self.block1.velocity
w2 = self.block2.get_width()
h2 = self.block2.get_height()
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(*self.get_ds())
wedge_corner = ds_to_ps_point(w2, w2)
ps_point -= wedge_corner
y = ps_point[1]
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) / ps_speed
reflected_point = rotate_vector(
clack_ps_point,
-2 * np.ceil((k - 1) / 2) * theta
)
d1, d2 = ps_point_to_ds(reflected_point + wedge_corner)
loc1 = self.get_floor_wall_corner() + h2 * UP / 2 + d2 * RIGHT
if k % 2 == 0:
loc1 += w2 * LEFT
loc2 = self.ds_to_point(d1, d2)
clack_data.append((time, loc1, loc2))
return clack_data
def get_clack_flashes(self):
pass # TODO
def tie_ps_point_to_time_tracker(self):
time_tracker = self.get_time_tracker()
def update_ps_point(p):
time = time_tracker.get_value()
ds = self.time_to_ds(time)
p.move_to(self.ds_to_point(*ds))
self.ps_point.add_updater(update_ps_point)
self.add(time_tracker)
def add_clack_flashes(self):
clack_data = self.get_clack_data()
self.clack_times = [
time for (time, loc1, loc2) in clack_data
]
self.block_flashes = ClackFlashes([
(loc1, time)
for (time, loc1, loc2) in clack_data
])
self.ps_flashes = ClackFlashes([
(loc2, time)
for (time, loc1, loc2) in clack_data
])
self.add(
self.block_flashes,
self.ps_flashes,
)
def begin_sliding(self):
self.tie_ps_point_to_time_tracker()
self.add_clack_flashes()
def end_sliding(self):
self.ps_point.clear_updaters()
self.remove(self.time_tracker)
for attr in ["block_flashes", "ps_flashes"]:
if hasattr(self, attr):
self.remove(getattr(self, attr))
total_time = self.time_tracker.get_value()
for time in self.clack_times:
if time < total_time:
offset = total_time - time
self.add_sound(
"clack",
time_offset=-offset,
)
def get_continually_building_trajectory(self):
trajectory = VMobject()
self.continually_building_trajectory = trajectory
trajectory.set_stroke(YELLOW, 1)
def get_point():
return np.array(self.ps_point.get_location())
points = [get_point(), get_point()]
trajectory.set_points_as_corners(points)
epsilon = 0.001
def update_trajectory(trajectory):
new_point = get_point()
p1, p2 = trajectory.get_anchors()[-2:]
angle = angle_between_vectors(
p2 - p1,
new_point - p2,
)
if angle > epsilon:
points.append(new_point)
else:
points[-1] = new_point
trajectory.set_points_as_corners(points)
trajectory.add_updater(update_trajectory)
return trajectory
def get_ps_point_change_anim(self, d1, d2):
b1 = self.block1
ps_speed = np.sqrt(b1.mass) * abs(b1.velocity)
curr_d1, curr_d2 = self.get_ds()
distance = get_norm([curr_d1 - d1, curr_d2 - d2])
return ApplyMethod(
self.ps_point.move_to,
self.ds_to_point(d1, d2),
run_time=(distance / ps_speed),
rate_func=None,
)
# Mobject getters
def get_floor(self):
floor = self.floor = Line(
@ -175,12 +316,12 @@ class PositionPhaseSpaceScene(Scene):
stroke_color=WHITE,
stroke_width=3,
)
floor.move_to(self.get_corner(), LEFT)
floor.move_to(self.get_floor_wall_corner(), LEFT)
return floor
def get_wall(self):
wall = self.wall = Wall(**self.wall_config)
wall.move_to(self.get_corner(), DR)
wall.move_to(self.get_floor_wall_corner(), DR)
return wall
def get_blocks(self):
@ -188,7 +329,7 @@ class PositionPhaseSpaceScene(Scene):
for n in [1, 2]:
config = getattr(self, "block{}_config".format(n))
block = Block(**config)
block.move_to(self.get_corner(), DL)
block.move_to(self.get_floor_wall_corner(), DL)
block.shift(config["distance"] * RIGHT)
block.label.move_to(block)
block.label.set_fill(BLACK)
@ -203,9 +344,32 @@ class PositionPhaseSpaceScene(Scene):
axes.shift(
self.axes_center - axes.coords_to_point(0, 0)
)
axes.add(self.get_axes_labels(axes))
axes.labels = self.get_axes_labels(axes)
axes.add(axes.labels)
axes.added_lines = self.get_added_axes_lines(axes)
axes.add(axes.added_lines)
return axes
def get_added_axes_lines(self, axes):
c2p = axes.coords_to_point
y_lines = VGroup(*[
Line(
c2p(0, 0), c2p(0, axes.y_max + 1),
).move_to(c2p(x, 0), DOWN)
for x in np.arange(0, axes.x_max)
])
x_lines = VGroup(*[
Line(
c2p(0, 0), c2p(axes.x_max, 0),
).move_to(c2p(0, y), LEFT)
for y in np.arange(0, axes.y_max)
])
line_groups = VGroup(x_lines, y_lines)
for lines in line_groups:
lines.set_stroke(BLUE, 1, 0.5)
lines[1::2].set_stroke(width=0.5, opacity=0.25)
return line_groups
def get_axes_labels(self, axes):
x_label = TexMobject("x = ", "d_1")
y_label = TexMobject("y = ", "d_2")
@ -278,6 +442,7 @@ class PositionPhaseSpaceScene(Scene):
color = GREEN if n == 1 else RED
label[0].set_color_by_tex("d_", color)
label.scale(0.7)
label.set_stroke(BLACK, 3, background=True)
def update_value(label):
lhs, rhs = label
@ -310,17 +475,15 @@ class PositionPhaseSpaceScene(Scene):
def get_d_brace(self, get_right_point):
line = Line(LEFT, RIGHT).set_width(6)
brace = Brace(line, UP)
def update_brace(brace):
def get_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)
return Brace(line, UP, buff=SMALL_BUFF)
brace.add_updater(update_brace)
brace = updating_mobject_from_func(get_brace)
return brace
def get_d1_brace(self):
@ -344,40 +507,44 @@ class PositionPhaseSpaceScene(Scene):
center[0] = nip.get_center()[0]
nip.rotate(PI, about_point=center)
def get_brace_d_label(self, n, get_d, brace, vect):
def get_brace_d_label(self, n, get_d, brace, vect, buff):
label = self.get_d_label(n, get_d)
label.add_updater(
lambda m: m.next_to(brace, vect, 0)
lambda m: m.next_to(brace, vect, buff)
)
return label
def get_d1_label(self):
self.d1_label = self.get_brace_d_label(
1, self.get_d1, self.d1_brace, UP
1, self.get_d1, self.d1_brace, UP, SMALL_BUFF,
)
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
2, self.get_d2, self.d2_brace, UP, 0
)
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)
line = self.d1_eq_d2_line = self.mirror_line_class(start, end)
line.set_style(**self.mirror_line_style)
line.set_color(PINK)
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)
point = interpolate(
line.get_start(), line.get_end(),
0.7,
)
label.next_to(point, DR, SMALL_BUFF)
label.set_stroke(BLACK, 3, background=True)
label.match_color(line)
self.d1_eq_d2_label = label
return label
@ -385,8 +552,8 @@ class PositionPhaseSpaceScene(Scene):
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)
self.d2_eq_w2_line = self.mirror_line_class(start, end)
self.d2_eq_w2_line.set_style(**self.mirror_line_style)
return self.d2_eq_w2_line
def get_d2_eq_w2_label(self):
@ -394,7 +561,7 @@ class PositionPhaseSpaceScene(Scene):
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
self.d2_eq_w2_label = label
return label
def get_time_tracker(self):
@ -402,63 +569,495 @@ class PositionPhaseSpaceScene(Scene):
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,
"block1_config": {
"velocity": 1.5,
},
"slide_wait_time": 30,
}
def setup(self):
super().setup()
self.add(
self.floor,
self.wall,
self.blocks,
self.axes,
)
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
# ))
self.show_w2_line()
def show_coordinates(self):
pass
ps_point = self.ps_point
axes = self.axes
self.play(Write(axes.added_lines))
self.play(FadeInFromLarge(self.ps_dot, scale_factor=10))
self.play(
ShowCreation(self.x_line),
GrowFromPoint(
self.d1_brace,
self.d1_brace.get_left(),
),
Indicate(axes.labels[0]),
)
self.play(
FadeInFromDown(self.ps_d1_label),
FadeInFromDown(self.d1_label),
)
self.play(ps_point.shift, 0.5 * LEFT)
self.play(ps_point.shift, 0.5 * RIGHT)
self.wait()
self.play(
ShowCreation(self.y_line),
GrowFromPoint(
self.d2_brace,
self.d2_brace.get_left(),
),
Indicate(axes.labels[1]),
)
self.play(
FadeInFromDown(self.ps_d2_label),
FadeInFromDown(self.d2_label),
)
self.play(ps_point.shift, 0.5 * UP)
self.play(ps_point.shift, 0.5 * DOWN)
self.wait()
self.play(Rotating(
ps_point,
about_point=ps_point.get_location() + 0.5 * RIGHT,
run_time=3,
rate_func=smooth,
))
self.wait()
def show_xy_line(self):
pass
ps_point = self.ps_point
ps_point.save_state()
d1, d2 = self.point_to_ds(ps_point.get_location())
xy_line = self.d1_eq_d2_line
xy_label = self.d1_eq_d2_label
xy_line.set_stroke(YELLOW)
xy_label.set_color(YELLOW)
self.play(
ShowCreation(xy_line),
Write(xy_label),
)
self.play(
ps_point.move_to, self.ds_to_point(d2, d2),
run_time=3
)
self.wait()
for d in [3, 7]:
self.play(
ps_point.move_to, self.ds_to_point(d, d),
run_time=2
)
self.wait()
self.play(ps_point.restore)
self.wait()
def let_process_play_out(self):
pass
self.begin_sliding()
sliding_trajectory = self.get_continually_building_trajectory()
self.add(sliding_trajectory, self.ps_dot)
self.wait(self.slide_wait_time)
self.end_sliding()
def show_w2_line(self):
line = self.d2_eq_w2_line
label = self.d2_eq_w2_label
self.play(ShowCreation(line))
self.play(FadeInFromDown(label))
self.wait()
class EqualMassCase(IntroducePositionPhaseSpace):
class SpecialShowPassingFlash(ShowPassingFlash):
CONFIG = {
"max_time_width": 0.1,
}
def get_bounds(self, alpha):
tw = self.time_width
max_tw = self.max_time_width
upper = interpolate(0, 1 + max_tw, alpha)
lower = upper - tw
upper = min(upper, 1)
lower = max(lower, 0)
return (lower, upper)
class EqualMassCase(PositionPhaseSpaceScene):
CONFIG = {
"block1_config": {
"mass": 1,
"width": 1,
"velocity": 1.5,
},
"rescale_coordinates": False,
}
def setup(self):
super().setup()
self.add(
self.floor,
self.wall,
self.blocks,
self.axes,
self.ps_dot,
self.x_line,
self.y_line,
self.ps_d1_label,
self.ps_d2_label,
self.d1_eq_d2_line,
self.d1_eq_d2_label,
self.d2_eq_w2_line,
self.d2_eq_w2_label,
)
def construct(self):
self.show_same_mass()
self.show_first_point()
self.up_to_first_collision()
self.ask_about_momentum_transfer()
self.up_to_second_collision()
self.up_to_third_collision()
self.fade_distance_indicators()
self.show_beam_bouncing()
def show_same_mass(self):
blocks = self.blocks
self.play(LaggedStart(
Indicate, blocks,
lag_ratio=0.8,
run_time=1,
))
def show_first_point(self):
pass
ps_dot = self.ps_dot
ps_point = self.ps_point
d1, d2 = self.get_ds()
self.play(FocusOn(ps_dot))
self.play(ShowCreationThenFadeOut(
Circle(color=RED).replace(ps_dot).scale(2),
run_time=1
))
self.wait()
self.play(
ps_point.move_to, self.ds_to_point(d1 - 1, d2),
rate_func=wiggle,
run_time=3,
)
# self.play(ps_point.move_to, self.ds_to_point(d1, d2))
self.wait()
def up_to_first_collision(self):
pass
ps_point = self.ps_point
d1, d2 = self.get_ds()
block1 = self.block1
block2 = self.block2
xy_line = self.d1_eq_d2_line
xy_line_label = self.d1_eq_d2_label
def ask_about_momentum_transfer(self):
pass
block_arrow = Vector(LEFT, color=RED)
block_arrow.block = block1
block_arrow.add_updater(
lambda m: m.shift(
m.block.get_center() - m.get_start()
)
)
ps_arrow = Vector(LEFT, color=RED)
ps_arrow.next_to(ps_point, DL, buff=SMALL_BUFF)
block_labels = VGroup(block1.label, block2.label)
block_label_copies = block_labels.copy()
def update_bl_copies(bl_copies):
for bc, b in zip(bl_copies, block_labels):
bc.move_to(b)
block_label_copies.add_updater(update_bl_copies)
trajectory = self.get_continually_building_trajectory()
self.add(block_arrow, ps_arrow, block_label_copies)
self.play(
GrowArrow(block_arrow),
GrowArrow(ps_arrow),
)
self.add(trajectory)
self.play(self.get_ps_point_change_anim(d2, d2))
block_arrow.block = block2
ps_arrow.rotate(90 * DEGREES)
ps_arrow.next_to(ps_point, DR, SMALL_BUFF)
self.add_sound(self.clack_sound)
self.play(
Flash(ps_point),
Flash(block1.get_left()),
self.get_ps_point_change_anim(d2, d2 - 1)
)
self.play(
ShowPassingFlash(
xy_line.copy().set_stroke(YELLOW, 3)
),
Indicate(xy_line_label),
)
trajectory.suspend_updating()
self.wait()
self.ps_arrow = ps_arrow
self.block_arrow = block_arrow
def up_to_second_collision(self):
pass
trajectory = self.continually_building_trajectory
ps_point = self.ps_point
ps_arrow = self.ps_arrow
block_arrow = self.block_arrow
d1, d2 = self.get_ds()
w2 = self.block2.get_width()
trajectory.resume_updating()
self.play(self.get_ps_point_change_anim(d1, w2))
block_arrow.rotate(PI)
ps_arrow.rotate(PI)
ps_arrow.next_to(ps_point, UR, SMALL_BUFF)
self.add_sound(self.clack_sound)
self.play(
Flash(self.block2.get_left()),
Flash(ps_point),
self.get_ps_point_change_anim(d1, w2 + 1)
)
trajectory.suspend_updating()
self.wait()
def up_to_third_collision(self):
pass
trajectory = self.continually_building_trajectory
ps_point = self.ps_point
ps_arrow = self.ps_arrow
block_arrow = self.block_arrow
d1, d2 = self.get_ds()
trajectory.resume_updating()
self.play(self.get_ps_point_change_anim(d1, d1))
block_arrow.block = self.block1
ps_arrow.rotate(-90 * DEGREES)
ps_arrow.next_to(ps_point, DR, SMALL_BUFF)
self.add_sound(self.clack_sound)
self.play(
Flash(self.block2.get_left()),
Flash(ps_point.get_location()),
self.get_ps_point_change_anim(d1 + 10, d1)
)
trajectory.suspend_updating()
def fade_distance_indicators(self):
trajectory = self.continually_building_trajectory
self.play(
trajectory.set_stroke, {"width": 1},
*map(FadeOut, [
self.ps_arrow,
self.block_arrow,
self.x_line,
self.y_line,
self.ps_d1_label,
self.ps_d2_label,
])
)
trajectory.clear_updaters()
def show_beam_bouncing(self):
d1, d2 = self.get_ds()
d1 = int(d1)
d2 = int(d2)
w2 = self.block2.get_width()
ps_point = self.ps_point
points = []
while d1 > d2:
points.append(self.ds_to_point(d1, d2))
d1 -= 1
while d2 >= int(w2):
points.append(self.ds_to_point(d1, d2))
d2 -= 1
points += list(reversed(points))[1:]
trajectory = VMobject()
trajectory.set_points_as_corners(points)
flashes = [
SpecialShowPassingFlash(
trajectory.copy().set_stroke(YELLOW, width=6 - n),
time_width=(0.01 * n),
max_time_width=0.05,
remover=True
)
for n in np.arange(0, 6, 0.25)
]
flash_mob = flashes[0].mobject # Lol
def update_ps_point_from_flas_mob(ps_point):
if len(flash_mob.points) > 0:
ps_point.move_to(flash_mob.points[-1])
else:
ps_point.move_to(trajectory.points[0])
# Mirror words
xy_line = self.d1_eq_d2_line
w2_line = self.d2_eq_w2_line
lines = VGroup(xy_line, w2_line)
for line in lines:
word = TextMobject("Mirror")
word.next_to(ORIGIN, UP, SMALL_BUFF)
word.rotate(line.get_angle(), about_point=ORIGIN)
word.shift(line.get_center())
line.word = word
for line in lines:
line.set_stroke(LIGHT_GREY)
line.set_sheen(1, LEFT)
self.play(
Write(line.word),
line.set_sheen, 1, RIGHT,
line.set_stroke, {"width": 2},
run_time=1,
)
# TODO, clacks?
for x in range(3):
self.play(
UpdateFromFunc(
ps_point,
update_ps_point_from_flas_mob,
),
*flashes,
run_time=3,
rate_func=None,
)
self.wait()
class FailedAngleRelation(PositionPhaseSpaceScene):
CONFIG = {
"block1_config": {
"distance": 10,
"velocity": -1.5,
},
"block2_config": {
"distance": 5,
},
"rescale_coordinates": False,
}
def setup(self):
super().setup()
self.add(
self.floor,
self.wall,
self.blocks,
self.axes,
self.ps_dot,
self.x_line,
self.y_line,
self.d1_eq_d2_line,
self.d1_eq_d2_label,
self.d2_eq_w2_line,
self.d2_eq_w2_label,
)
def construct(self):
self.show_first_collision()
self.show_angles()
def show_first_collision(self):
ps_point = self.ps_point
trajectory = self.get_continually_building_trajectory()
trajectory.set_stroke(YELLOW, 2)
self.add(ps_point, trajectory)
self.begin_sliding()
self.wait_until(lambda: self.get_ds()[1] < 2)
self.end_sliding()
trajectory.suspend_updating()
self.trajectory = trajectory
def show_angles(self):
trajectory = self.trajectory
arcs = self.get_arcs(trajectory)
equation = self.get_word_equation()
equation.next_to(
trajectory.points[0], UR, MED_SMALL_BUFF,
index_of_submobject_to_align=0,
)
for arc in arcs:
line = Line(ORIGIN, RIGHT)
line.set_stroke(WHITE, 2)
line.rotate(arc.start_angle)
line.shift(arc.arc_center - line.get_start())
arc.line = line
self.play(LaggedStart(
FadeInFromDown,
VGroup(*reversed(equation)),
lag_ratio=0.75,
))
for arc in arcs:
# TODO, add arrows
self.play(
ShowCreation(arc),
arc.line.rotate, arc.angle,
{"about_point": arc.line.get_start()},
UpdateFromAlphaFunc(
arc.line,
lambda m, a: m.set_stroke(
opacity=(there_and_back(a)**0.5)
)
),
run_time=2,
)
#
def get_arcs(self, trajectory):
p0, p1, p2 = trajectory.get_anchors()[1:4]
arc_config = {
"stroke_color": WHITE,
"stroke_width": 2,
"radius": 0.5,
"arc_center": p1,
}
arc1 = Arc(
start_angle=0,
angle=45 * DEGREES,
**arc_config
)
a2_start = angle_of_vector(DL)
a2_angle = angle_between_vectors((p2 - p1), DL)
arc2 = Arc(
start_angle=a2_start,
angle=a2_angle,
**arc_config
)
return VGroup(arc1, arc2)
def get_word_equation(self):
result = VGroup(
TextMobject("Angle of incidence"),
TexMobject("\\ne").rotate(90 * DEGREES),
TextMobject("Angle of refraction")
)
result.arrange_submobjects(DOWN)
return result