3b1b-videos/_2021/shadows.py
2022-12-27 16:31:01 -08:00

6626 lines
206 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from manim_imports_ext import *
import scipy.spatial
# Helpers
def project_to_xy_plane(p1, p2):
"""
Draw a line from source to p1 to p2. Where does it
intersect the xy plane?
"""
x1, y1, z1 = p1
x2, y2, z2 = p2
if z2 < z1:
z2 = z1 + 1e-2 # TODO, bad hack
vect = p2 - p1
return p1 - (z2 / vect[2]) * vect
def flat_project(point):
# return [*point[:2], 0]
return [*point[:2], 0.05 * point[2]] # TODO
def get_pre_shadow(mobject, opacity):
result = mobject.deepcopy()
if isinstance(result, Group) and all((isinstance(sm, VMobject) for sm in mobject)):
result = VGroup(*result)
result.clear_updaters()
for sm in result.family_members_with_points():
color = interpolate_color(sm.get_color(), BLACK, opacity)
sm.set_color(color)
sm.set_opacity(opacity)
if isinstance(sm, VMobject):
sm.set_stroke(
interpolate_color(sm.get_stroke_color(), BLACK, opacity)
)
sm.set_gloss(sm.get_gloss() * 0.5)
sm.set_shadow(0)
sm.set_reflectiveness(0)
return result
def update_shadow(shadow, mobject, light_source):
lp = light_source.get_center() if light_source is not None else None
def project(point):
if lp is None:
return flat_project(point)
else:
return project_to_xy_plane(lp, point)
for sm, mm in zip(shadow.family_members_with_points(), mobject.family_members_with_points()):
sm.set_points(np.apply_along_axis(project, 1, mm.get_points()))
if isinstance(sm, VMobject) and sm.get_unit_normal()[2] < 0:
sm.reverse_points()
if isinstance(sm, VMobject):
sm.set_fill(opacity=mm.get_fill_opacity())
else:
sm.set_opacity(mm.get_opacity())
def get_shadow(mobject, light_source=None, opacity=0.7):
shadow = get_pre_shadow(mobject, opacity)
shadow.add_updater(lambda s: update_shadow(s, mobject, light_source))
return shadow
def get_area(shadow):
return 0.5 * sum(
get_norm(sm.get_area_vector())
for sm in shadow.get_family()
)
def get_convex_hull(mobject):
points = mobject.get_all_points()
hull = scipy.spatial.ConvexHull(points[:, :2])
return points[hull.vertices]
def sort_to_camera(mobject, camera_frame):
cl = camera_frame.get_implied_camera_location()
mobject.sort(lambda p: -get_norm(p - cl))
return mobject
def cube_sdf(point, cube):
c = cube.get_center()
vect = point - c
face_vects = [face.get_center() - c for face in cube]
return max(*(
abs(np.dot(fv, vect) / np.dot(fv, fv))
for fv in face_vects
)) - 1
def is_in_cube(point, cube):
return cube_sdf(point, cube) < 0
def get_overline(mob):
overline = Underline(mob).next_to(mob, UP, buff=0.05)
overline.set_stroke(WHITE, 2)
return overline
def get_key_result(solid_name, color=BLUE):
eq = OldTex(
"\\text{Area}\\big(\\text{Shadow}(\\text{" + solid_name + "})\\big)",
"=",
"\\frac{1}{2}", "{c}", "\\cdot",
"(\\text{Surface area})",
tex_to_color_map={
"\\text{Shadow}": GREY_B,
f"\\text{{{solid_name}}}": color,
"\\text{Solid}": BLUE,
"{c}": RED,
}
)
eq.add_to_back(get_overline(eq[:5]))
return eq
def get_surface_area(solid):
return sum(get_norm(f.get_area_vector()) for f in solid)
# Scenes
class ShadowScene(ThreeDScene):
object_center = [0, 0, 3]
frame_center = [0, 0, 2]
area_label_center = [0, -1.5, 0]
surface_area = 6.0
num_reorientations = 10
plane_dims = (20, 20)
plane_style = {
"stroke_width": 0,
"fill_color": GREY_A,
"fill_opacity": 0.5,
"gloss": 0.5,
"shadow": 0.2,
}
limited_plane_extension = 0
object_style = {
"stroke_color": WHITE,
"stroke_width": 0.5,
"fill_color": BLUE_E,
"fill_opacity": 0.7,
"reflectiveness": 0.3,
"gloss": 0.1,
"shadow": 0.5,
}
inf_light = False
glow_radius = 10
glow_factor = 10
area_label_center = [-2, -1, 0]
unit_size = 2
def setup(self):
self.camera.frame.reorient(-30, 75)
self.camera.frame.move_to(self.frame_center)
self.add_plane()
self.add_solid()
self.add_shadow()
self.setup_light_source()
def add_plane(self):
width, height = self.plane_dims
grid = NumberPlane(
x_range=(-width // 2, width // 2, 2),
y_range=(-height // 2, height // 2, 2),
background_line_style={
"stroke_color": GREY_B,
"stroke_width": 1,
},
faded_line_ratio=4,
)
grid.shift(-grid.get_origin())
grid.set_width(width)
grid.axes.match_style(grid.background_lines)
grid.set_flat_stroke(True)
grid.insert_n_curves(3)
plane = Rectangle()
plane.replace(grid, stretch=True)
plane.set_style(**self.plane_style)
plane.set_stroke(width=0)
if self.limited_plane_extension > 0:
plane.set_height(height // 2 + self.limited_plane_extension, about_edge=UP, stretch=True)
self.plane = plane
plane.add(grid)
self.add(plane)
def add_solid(self):
self.solid = self.get_solid()
self.solid.move_to(self.object_center)
self.add(self.solid)
def get_solid(self):
cube = VCube()
cube.deactivate_depth_test()
cube.set_height(2)
cube.set_style(**self.object_style)
# Wrap in group so that strokes and fills
# are rendered in separate passes
cube = self.cube = Group(*cube)
cube.add_updater(lambda m: self.sort_to_camera(m))
return cube
def add_shadow(self):
light_source = None if self.inf_light else self.camera.light_source
shadow = get_shadow(self.solid, light_source)
self.add(shadow, self.solid)
self.shadow = shadow
def setup_light_source(self):
self.light = self.camera.light_source
if self.inf_light:
self.light.move_to(100 * OUT)
else:
glow = self.glow = TrueDot(
radius=self.glow_radius,
glow_factor=self.glow_factor,
)
glow.set_color(interpolate_color(YELLOW, WHITE, 0.5))
glow.add_updater(lambda m: m.move_to(self.light))
self.add(glow)
def sort_to_camera(self, mobject):
return sort_to_camera(mobject, self.camera.frame)
def get_shadow_area_label(self):
text = OldTexText("Shadow area: ")
decimal = DecimalNumber(100)
label = VGroup(text, decimal)
label.arrange(RIGHT)
label.move_to(self.area_label_center - decimal.get_center())
label.fix_in_frame()
label.set_backstroke()
decimal.add_updater(lambda d: d.set_value(
get_area(self.shadow) / (self.unit_size**2)
).set_backstroke())
return label
def begin_ambient_rotation(self, mobject, speed=0.2, about_point=None, initial_axis=[1, 1, 1]):
mobject.rot_axis = np.array(initial_axis)
def update_mob(mob, dt):
mob.rotate(speed * dt, mob.rot_axis, about_point=about_point)
mob.rot_axis = rotate_vector(mob.rot_axis, speed * dt, OUT)
return mob
mobject.add_updater(update_mob)
return mobject
def get_shadow_outline(self, stroke_width=1):
outline = VMobject()
outline.set_stroke(WHITE, stroke_width)
outline.add_updater(lambda m: m.set_points_as_corners(get_convex_hull(self.shadow)).close_path())
return outline
def get_light_lines(self, outline=None, n_lines=100, only_vertices=False):
if outline is None:
outline = self.get_shadow_outline()
def update_lines(lines):
lp = self.light.get_center()
if only_vertices:
points = outline.get_vertices()
else:
points = [outline.pfp(a) for a in np.linspace(0, 1, n_lines)]
for line, point in zip(lines, points):
if self.inf_light:
line.set_points_as_corners([point + 10 * OUT, point])
else:
line.set_points_as_corners([lp, point])
line = Line(IN, OUT)
light_lines = line.replicate(n_lines)
light_lines.set_stroke(YELLOW, 0.5, 0.1)
light_lines.add_updater(update_lines)
return light_lines
def random_toss(self, mobject=None, angle=TAU, about_point=None, meta_speed=5, **kwargs):
if mobject is None:
mobject = self.solid
mobject.rot_axis = normalize(np.random.random(3))
mobject.rot_time = 0
def update(mob, time):
dt = time - mob.rot_time
mob.rot_time = time
mob.rot_axis = rotate_vector(mob.rot_axis, meta_speed * dt, normalize(np.random.random(3)))
mob.rotate(angle * dt, mob.rot_axis, about_point=about_point)
self.play(
UpdateFromAlphaFunc(mobject, update),
**kwargs
)
def randomly_reorient(self, solid=None, about_point=None):
solid = self.solid if solid is None else solid
solid.rotate(
random.uniform(0, TAU),
axis=normalize(np.random.uniform(-1, 1, 3)),
about_point=about_point,
)
return solid
def init_frame_rotation(self, factor=0.0025, max_speed=0.01):
frame = self.camera.frame
frame.d_theta = 0
def update_frame(frame, dt):
frame.d_theta += -factor * frame.get_theta()
frame.increment_theta(clip(
factor * frame.d_theta,
-max_speed * dt,
max_speed * dt
))
frame.add_updater(update_frame)
return frame
class SimpleWriting(Scene):
text = ""
font = "Better Grade"
color = WHITE
font_size = 48
def construct(self):
words = Text(self.text, font=self.font, font_size=self.font_size)
words.set_color(self.color)
self.play(Write(words))
self.wait()
class AliceName(SimpleWriting):
text = "Alice"
font_size = 72
class BobName(SimpleWriting):
text = "Bob"
font = "Kalam"
class BobWords(SimpleWriting):
font = "Kalam"
font_size = 24
words1 = "Embraces calculations"
words2 = "Loves specifics"
def construct(self):
words = VGroup(*(
Text(text, font=self.font, font_size=self.font_size)
for text in (self.words1, self.words2)
))
words.arrange(DOWN)
for word in words:
self.play(Write(word))
self.wait()
class AliceWords(BobWords):
font = "Better Grade"
words1 = "Procrastinates calculations"
words2 = "Seeks generality"
font_size = 48
class AskAboutConditions(SimpleWriting):
text = "Which properties matter?"
class IntroduceShadow(ShadowScene):
area_label_center = [-2.5, -2, 0]
plane_dims = (28, 20)
def construct(self):
# Setup
light = self.light
light.move_to([0, 0, 20])
self.add(light)
cube = self.solid
cube.scale(0.945) # Hack to make the appropriate area 1
shadow = self.shadow
outline = self.get_shadow_outline()
frame = self.camera.frame
frame.add_updater(lambda f, dt: f.increment_theta(0.01 * dt)) # Ambient rotation
area_label = self.get_shadow_area_label()
light_lines = self.get_light_lines(outline)
# Question
question = OldTexText(
"Puzzle: Find the average\\\\area of a cube's shadow",
font_size=48,
)
question.to_corner(UL)
question.fix_in_frame()
subquestion = Text("(Averaged over all orientations)")
subquestion.match_width(question)
subquestion.next_to(question, DOWN, MED_LARGE_BUFF)
subquestion.set_fill(BLUE_D)
subquestion.fix_in_frame()
subquestion.set_backstroke()
# Introductory animations
self.shadow.update()
self.play(
FadeIn(question, UP),
*(
LaggedStartMap(DrawBorderThenFill, mob, lag_ratio=0.1, run_time=3)
for mob in (cube, shadow)
)
)
self.random_toss(run_time=3, angle=TAU)
# Change size and orientation
outline.update()
area_label.update()
self.play(
FadeIn(area_label),
ShowCreation(outline),
)
self.play(
cube.animate.scale(0.5),
run_time=2,
rate_func=there_and_back,
)
self.random_toss(run_time=2, angle=PI)
self.wait()
self.begin_ambient_rotation(cube)
self.play(FadeIn(subquestion, 0.5 * DOWN))
self.wait(7)
# Where is the light?
light_comment = Text("Where is the light?")
light_comment.set_color(YELLOW)
light_comment.to_corner(UR)
light_comment.set_backstroke()
light_comment.fix_in_frame()
cube.clear_updaters()
cube.add_updater(lambda m: self.sort_to_camera(cube))
self.play(
FadeIn(light_comment, 0.5 * UP),
light.animate.next_to(cube, OUT, buff=1.5),
run_time=2,
)
light_lines.update()
self.play(
ShowCreation(light_lines, lag_ratio=0.01, run_time=3),
)
self.play(
light.animate.shift(1.0 * IN),
rate_func=there_and_back,
run_time=3
)
self.play(
light.animate.shift(4 * RIGHT),
run_time=5
)
self.play(
Rotate(light, PI, about_point=light.get_z() * OUT),
run_time=8,
)
self.play(light.animate.shift(4 * RIGHT), run_time=5)
self.wait()
# Light straight above
self.play(
frame.animate.set_height(12).set_z(4),
light.animate.set_z(10),
run_time=3,
)
self.wait()
self.play(light.animate.move_to(75 * OUT), run_time=3)
self.wait()
self.play(
frame.animate.set_height(8).set_z(2),
LaggedStart(*map(FadeOut, (question, subquestion, light_comment))),
run_time=2
)
# Flat projection
verts = np.array([*cube[0].get_vertices(), *cube[5].get_vertices()])
vert_dots = DotCloud(verts)
vert_dots.set_glow_factor(0.5)
vert_dots.set_color(WHITE)
proj_dots = vert_dots.copy()
proj_dots.apply_function(flat_project)
proj_dots.set_color(GREY_B)
vert_proj_lines = VGroup(*(
DashedLine(*pair)
for pair in zip(verts, proj_dots.get_points())
))
vert_proj_lines.set_stroke(WHITE, 1, 0.5)
point = verts[np.argmax(verts[:, 0])]
xyz_label = OldTex("(x, y, z)")
xy0_label = OldTex("(x, y, 0)")
for label in xyz_label, xy0_label:
label.rotate(PI / 2, RIGHT)
label.set_backstroke()
xyz_label.next_to(point, RIGHT)
xy0_label.next_to(flat_project(point), RIGHT)
vert_dots.save_state()
vert_dots.set_glow_factor(5)
vert_dots.set_radius(0.5)
vert_dots.set_opacity(0)
self.play(
Restore(vert_dots),
Write(xyz_label),
)
self.wait()
self.play(
TransformFromCopy(
cube.deepcopy().clear_updaters().set_opacity(0.5),
shadow.deepcopy().clear_updaters().set_opacity(0),
remover=True
),
TransformFromCopy(vert_dots, proj_dots),
TransformFromCopy(xyz_label, xy0_label),
*map(ShowCreation, vert_proj_lines),
)
self.wait(3)
self.play(LaggedStart(*map(FadeOut, (
vert_dots, vert_proj_lines, proj_dots,
xyz_label, xy0_label
))))
# Square projection
top_face = cube[np.argmax([f.get_z() for f in cube])]
normal_vect = top_face.get_unit_normal()
theta = np.arccos(normal_vect[2])
axis = normalize(rotate_vector([*normal_vect[:2], 0], PI / 2, OUT))
self.play(Rotate(cube, -theta, axis))
top_face = cube[np.argmax([f.get_z() for f in cube])]
verts = top_face.get_vertices()
vect = verts[3] - verts[2]
angle = angle_of_vector(vect)
self.play(Rotate(cube, -angle, OUT))
self.wait()
corner = cube.get_corner(DL + OUT)
edge_lines = VGroup(
Line(corner, cube.get_corner(DR + OUT)),
Line(corner, cube.get_corner(UL + OUT)),
Line(corner, cube.get_corner(DL + IN)),
)
edge_lines.set_stroke(RED, 2)
s_labels = OldTex("s").replicate(3)
s_labels.set_color(RED)
s_labels.rotate(PI / 2, RIGHT)
s_labels.set_stroke(BLACK, 3, background=True)
for label, line, vect in zip(s_labels, edge_lines, [OUT, LEFT, LEFT]):
label.next_to(line, vect, buff=SMALL_BUFF)
s_labels[1].next_to(edge_lines[1], OUT)
s_labels[2].next_to(edge_lines[2], LEFT)
s_squared = OldTex("s^2")
s_squared.match_style(s_labels[0])
s_squared.move_to(self.shadow)
frame.generate_target()
frame.target.reorient(10, 60)
frame.target.set_height(6.5)
self.play(
LaggedStartMap(ShowCreation, edge_lines),
LaggedStartMap(FadeIn, s_labels, scale=2),
MoveToTarget(frame, run_time=3)
)
self.wait()
self.play(
TransformFromCopy(s_labels[:2], s_squared),
)
self.wait(2)
rect = SurroundingRectangle(area_label)
rect.fix_in_frame()
rect.set_stroke(YELLOW, 3)
s_eq = OldTex("s = 1")
s_eq.next_to(area_label, DOWN)
s_eq.set_color(RED)
s_eq.set_stroke(BLACK, 3, background=True)
s_eq.fix_in_frame()
self.play(ShowCreation(rect))
self.play(FadeIn(s_eq, 0.5 * DOWN))
self.wait()
self.play(LaggedStart(*map(FadeOut, (
rect, s_eq, *edge_lines, *s_labels, s_squared,
))))
self.wait()
# Hexagonal orientation
axis = UL
angle = np.arccos(1 / math.sqrt(3))
area_label.suspend_updating()
self.play(
Rotate(cube, -angle, axis),
frame.animate.reorient(-10, 70),
ChangeDecimalToValue(area_label[1], math.sqrt(3)),
UpdateFromFunc(area_label[1], lambda m: m.fix_in_frame()),
run_time=2
)
self.add(area_label)
diagonal = Line(cube.get_nadir(), cube.get_zenith())
diagonal.set_stroke(WHITE, 2)
diagonal.scale(2)
diagonal.move_to(ORIGIN, IN)
self.add(diagonal, cube)
self.play(ShowCreation(diagonal))
self.wait(2)
frame.save_state()
cube_opacity = cube[0].get_fill_opacity()
cube.save_state()
angle = angle_of_vector(outline.get_anchors()[-1] - outline.get_anchors()[-2])
self.play(
frame.animate.reorient(0, 0),
cube.animate.rotate(-angle).set_opacity(0.2),
run_time=3,
)
frame.suspend_updating()
outline_copy = outline.copy().clear_updaters()
outline_copy.set_stroke(RED, 5)
title = Text("Regular hexagon")
title.set_color(RED)
title.next_to(outline_copy, UP)
title.set_backstroke()
self.play(
ShowCreationThenFadeOut(outline_copy),
Write(title, run_time=1),
)
self.play(
FadeOut(title),
Restore(frame),
cube.animate.set_opacity(cube_opacity).rotate(angle),
run_time=3,
)
frame.resume_updating()
hex_area_label = OldTex("\\sqrt{3} s^2")
hex_area_label.set_color(RED)
hex_area_label.move_to(self.shadow)
hex_area_label.shift(0.35 * DOWN)
self.play(Write(hex_area_label))
self.wait(10)
area_label.resume_updating()
self.play(
Uncreate(diagonal),
FadeOut(hex_area_label),
Rotate(cube, 4, RIGHT)
)
# Talk about averages
light_lines.clear_updaters()
self.begin_ambient_rotation(cube)
self.play(
FadeOut(light_lines),
FadeIn(question, 0.5 * UP),
ApplyMethod(frame.set_height, 8, run_time=2)
)
self.play(FadeIn(subquestion, 0.5 * UP))
self.wait(7)
cube.clear_updaters()
cube.add_updater(lambda m: self.sort_to_camera(m))
samples = VGroup(VectorizedPoint())
samples.to_corner(UR)
samples.shift(1.5 * LEFT)
self.add(samples)
for x in range(9):
self.random_toss()
sample = area_label[1].copy()
sample.clear_updaters()
sample.fix_in_frame()
self.play(
sample.animate.next_to(samples, DOWN),
run_time=0.5
)
samples.add(sample)
v_dots = OldTex("\\vdots")
v_dots.next_to(samples, DOWN)
v_dots.fix_in_frame()
samples.add(v_dots)
brace = Brace(samples, LEFT)
brace.fix_in_frame()
brace.next_to(samples, LEFT, SMALL_BUFF)
text = OldTexText(
"Take the mean.", "\\\\What does that\\\\approach?",
font_size=30
)
text[0].shift(MED_SMALL_BUFF * UP)
text.next_to(brace, LEFT)
text.fix_in_frame()
VGroup(text, brace).set_stroke(BLACK, 3, background=True)
self.play(
GrowFromCenter(brace),
FadeIn(text),
Write(v_dots),
)
self.wait()
for x in range(10):
self.random_toss()
self.wait()
class AskAboutAveraging(TeacherStudentsScene):
def construct(self):
self.remove(self.background)
sts = self.students
tch = self.teacher
self.play_student_changes(
"maybe", "thinking", "erm",
look_at=self.screen,
added_anims=[self.teacher.change("raise_right_hand", self.screen)]
)
self.wait(3)
self.play(
PiCreatureBubbleIntroduction(
sts[2], OldTexText("What does that\\\\mean, exactly?"),
target_mode="hesitant",
look_at=self.screen,
bubble_config={"direction": LEFT}
),
LaggedStart(
sts[0].change("confused", self.screen),
sts[1].change("pondering", self.screen),
tch.change("tease", sts[2].eyes),
)
)
self.wait(4)
self.student_says(
"Can we do an experiment?",
target_mode="raise_left_hand",
index=1,
)
self.wait(4)
self.student_says(
OldTexText("But what defines a\\\\``random'' toss?"),
look_at=self.screen,
target_mode="hesitant",
index=2,
added_anims=[
self.teacher.change("guilty"),
self.students[0].change("erm"),
]
)
self.wait(4)
self.play(LaggedStart(
self.students[0].change("pondering", self.screen),
self.students[1].change("maybe", self.screen),
self.teacher.change("tease", self.screen),
))
self.wait(2)
self.teacher_says(OldTexText("Hold off until\\\\the end"))
self.wait(3)
self.play_student_changes(
"thinking", "tease", "pondering",
look_at=self.screen,
added_anims=[self.teacher.change("tease", self.students)]
)
self.wait(4)
class MeanCalculation(Scene):
def construct(self):
values = [1.55, 1.33, 1.46, 1.34, 1.50, 1.26, 1.42, 1.54, 1.51]
nums = VGroup(*(
DecimalNumber(x)
for x in values
))
nums.arrange(DOWN, aligned_edge=LEFT)
nums.to_corner(UR, buff=LARGE_BUFF).shift(0.5 * LEFT)
self.add(nums)
mean_label = Text("Mean", font_size=36)
mean_label.set_color(GREEN)
mean_label.set_backstroke()
mean_arrow = Vector(0.25 * UR)
mean_arrow.match_color(mean_label)
mean_arrow.next_to(mean_label, UR, SMALL_BUFF)
mean_label.add(mean_arrow)
for n in range(len(nums)):
brace = Brace(nums[:n + 1], LEFT, buff=SMALL_BUFF)
mean = DecimalNumber(np.mean(values[:n + 1]))
mean.next_to(brace, LEFT)
mean.match_color(mean_label)
VGroup(brace, mean).set_backstroke()
mean_label.next_to(mean, DL, SMALL_BUFF)
self.add(brace, mean, mean_label)
self.wait(0.5)
self.remove(brace, mean)
self.add(brace, mean)
self.wait()
# Embed
self.embed()
class DescribeSO3(ShadowScene):
def construct(self):
frame = self.camera.frame
frame.set_z(1)
frame.reorient(0)
cube = self.solid
cube.set_opacity(0.95)
cube.move_to(ORIGIN)
self.remove(self.plane)
self.remove(self.shadow)
x_point = VectorizedPoint(cube.get_right())
y_point = VectorizedPoint(cube.get_top())
z_point = VectorizedPoint(cube.get_zenith())
cube.add(x_point, y_point, z_point)
def get_matrix():
return np.array([
x_point.get_center(),
y_point.get_center(),
z_point.get_center(),
]).T
def get_mat_mob():
matrix = DecimalMatrix(
get_matrix(),
element_to_mobject_config=dict(
num_decimal_places=2,
edge_to_fix=LEFT,
include_sign=True,
),
h_buff=2.0,
element_alignment_corner=LEFT,
)
matrix.fix_in_frame()
matrix.set_height(1.25)
brackets = matrix.get_brackets()
brackets[1].move_to(brackets[0].get_center() + 3.45 * RIGHT)
matrix.to_corner(UL)
return matrix
matrix = always_redraw(get_mat_mob)
self.add(matrix)
# Space of orientations
self.begin_ambient_rotation(cube, speed=0.4)
self.wait(2)
question = Text("What is the space of all orientations?")
question.to_corner(UR)
question.fix_in_frame()
SO3 = OldTex("SO(3)")
SO3.next_to(question, DOWN)
SO3.set_color(BLUE)
SO3.fix_in_frame()
self.play(Write(question))
self.wait(2)
self.play(FadeIn(SO3, DOWN))
self.wait(2)
self.play(SO3.animate.next_to(matrix, DOWN, MED_LARGE_BUFF))
self.wait(5)
new_question = Text(
"What probability distribution are we placing\n"
"on the space of all orientations?",
t2c={"probability distribution": YELLOW},
t2s={"probability distribution": ITALIC},
)
new_question.match_width(question)
new_question.move_to(question, UP)
new_question.fix_in_frame()
n = len("the space of all orientations?")
self.play(
FadeTransform(question[-n:], new_question[-n:]),
FadeOut(question[:-n]),
FadeIn(new_question[:-n]),
)
self.wait()
cube.clear_updaters()
N = 15
cube_field = cube.get_grid(N, N)
cube_field.set_height(10)
for n, c in enumerate(cube_field):
c.rotate(PI * (n // N) / N, axis=RIGHT)
c.rotate(PI * (n % N) / N, axis=UP)
for face in c:
face.set_stroke(width=0)
self.sort_to_camera(c)
matrix.clear_updaters()
self.play(
FadeTransform(cube, cube_field[0]),
LaggedStartMap(FadeIn, cube_field, run_time=15, lag_ratio=0.1)
)
self.add(cube_field)
self.wait()
class PauseAndPonder(TeacherStudentsScene):
def construct(self):
self.remove(self.background)
self.teacher_says(
OldTexText("The goal is\\\\not speed."),
added_anims=[self.change_students(
"tease", "well", "pondering",
look_at=self.screen
)]
)
self.wait(2)
self.play(
RemovePiCreatureBubble(self.teacher, target_mode="tease"),
PiCreatureBubbleIntroduction(
self.students[2],
Lightbulb(),
bubble_type=ThoughtBubble,
bubble_creation_class=lambda m: FadeIn(m, lag_ratio=0.1),
bubble_config=dict(
height=3,
width=3,
direction=LEFT,
),
target_mode="thinking",
look_at=self.screen,
)
)
self.wait(3)
self.teacher_says(
"Pause and ponder!",
target_mode="well",
added_anims=[self.change_students(
"pondering", "tease", "thinking"
)],
run_time=1
)
self.wait(5)
self.embed()
class StartSimple(Scene):
def construct(self):
# Words
title = Text("Universal problem-solving advice")
title.set_width(FRAME_WIDTH - 4)
title.to_edge(UP)
title.set_color(BLUE)
title.set_backstroke()
line = Underline(title, buff=-0.035)
line.set_width(FRAME_WIDTH - 1)
line.set_color(BLUE_B)
line.set_stroke(width=[0, 3, 3, 3, 0])
line.insert_n_curves(101)
words = Text(
"Start with the simplest non-trivial\n"
"variant of the problem you can."
)
words.next_to(line, DOWN, MED_SMALL_BUFF)
rect = BackgroundRectangle(words, fill_opacity=1, buff=SMALL_BUFF)
words.set_backstroke(width=5)
# Shapes
cube = VCube()
cube.deactivate_depth_test()
cube.set_color(BLUE_E)
cube.set_opacity(0.75)
cube.set_stroke(WHITE, 0.5, 0.5)
cube.set_height(2)
cube.rotate(PI / 4, [1, 2, 0])
cube.sort(lambda p: p[2])
cube = Group(*cube)
cube.set_gloss(1)
arrow = Arrow(LEFT, RIGHT)
face = cube[np.argmax([f.get_z() for f in cube])].copy()
group = Group(cube, arrow, face)
group.arrange(RIGHT, buff=MED_LARGE_BUFF)
group.next_to(words, DOWN, LARGE_BUFF)
group.set_width(2)
group.to_edge(RIGHT)
group.set_y(0)
self.camera.light_source.set_x(-4)
self.play(
ShowCreation(line),
Write(title, run_time=1),
)
self.wait()
self.play(
FadeIn(rect),
FadeIn(words, lag_ratio=0.1),
run_time=2
)
self.wait()
self.play(FlashAround(words.get_part_by_text("non-trivial"), run_time=2))
self.wait()
self.play(
LaggedStart(*map(DrawBorderThenFill, cube)),
ShowCreation(arrow),
TransformFromCopy(cube[-1], face)
)
self.wait(3)
class FocusOnOneFace(ShadowScene):
inf_light = True
limited_plane_extension = 10
def construct(self):
# Some random tumbling
cube = self.solid
shadow = self.shadow
frame = self.camera.frame
words = VGroup(
Text("Just one orientation"),
Text("Just one face"),
)
words.fix_in_frame()
words.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT)
words.to_corner(UL)
average_words = Text("Average over all orientations")
average_words.move_to(words[0], LEFT)
average_words.fix_in_frame()
self.add(average_words)
self.random_toss(run_time=3, rate_func=linear)
self.play(
FadeIn(words[0], 0.75 * UP),
FadeOut(average_words, 0.75 * UP),
run_time=0.5,
)
self.wait()
# Just one face
cube.update()
index = np.argmax([f.get_z() for f in cube])
face = cube[index]
prev_opacity = face.get_fill_opacity()
cube.generate_target(use_deepcopy=True)
cube.target.clear_updaters()
cube.target.space_out_submobjects(2, about_point=face.get_center())
cube.target.set_opacity(0)
cube.target[index].set_opacity(prev_opacity)
self.shadow.set_stroke(width=0)
self.play(
MoveToTarget(cube),
FadeIn(words[1]),
)
self.play(
frame.animate.reorient(-10, 65),
FlashAround(words[1], rate_func=squish_rate_func(smooth, 0.2, 0.5)),
FlashAround(words[0], rate_func=squish_rate_func(smooth, 0.5, 0.8)),
run_time=5,
)
frame.add_updater(lambda f, dt: f.increment_theta(0.01 * dt))
self.solid = face
self.remove(shadow)
self.add_shadow()
shadow = self.shadow
# Ask about area
area_q = Text("Area?")
area_q.add_updater(lambda m: m.move_to(shadow))
self.play(Write(area_q))
self.wait()
# Orient straight up
unit_normal = face.get_unit_normal()
axis = rotate_vector(normalize([*unit_normal[:2], 0]), PI / 2, OUT)
angle = np.arccos(unit_normal[2])
face.generate_target()
face.target.rotate(-angle, axis)
face.target.move_to(3 * OUT)
face.target.rotate(-PI / 4, OUT)
self.play(MoveToTarget(face))
light_lines = self.get_light_lines(n_lines=4, outline=shadow, only_vertices=True)
light_lines.set_stroke(YELLOW, 1, 0.5)
self.play(
frame.animate.set_phi(70 * DEGREES),
FadeIn(light_lines, lag_ratio=0.5),
TransformFromCopy(face, face.deepcopy().set_opacity(0).set_z(0), remover=True),
run_time=3,
)
self.wait(3)
self.play(
Rotate(face, PI / 2, UP),
FadeOut(area_q, scale=0),
run_time=3,
)
self.wait(3)
self.play(
Rotate(face, -PI / 3, UP),
UpdateFromAlphaFunc(light_lines, lambda m, a: m.set_opacity(0.5 * (1 - a)), remover=True),
run_time=2,
)
# Show normal vector
z_axis = VGroup(
Line(ORIGIN, face.get_center()),
Line(face.get_center(), 10 * OUT),
)
z_axis.set_stroke(WHITE, 1)
normal_vect = Vector()
get_fc = face.get_center
def get_un():
return face.get_unit_normal(recompute=True)
def get_theta():
return np.arccos(get_un()[2])
normal_vect.add_updater(lambda v: v.put_start_and_end_on(
get_fc(), get_fc() + get_un(),
))
arc = always_redraw(lambda: Arc(
start_angle=PI / 2,
angle=-get_theta(),
radius=0.5,
stroke_width=2,
).rotate(PI / 2, RIGHT, about_point=ORIGIN).shift(get_fc()))
theta = OldTex("\\theta", font_size=30)
theta.set_backstroke()
theta.rotate(PI / 2, RIGHT)
theta.add_updater(lambda m: m.move_to(
get_fc() + 1.3 * (arc.pfp(0.5) - get_fc())
))
theta.add_updater(lambda m: m.set_width(min(0.123, max(0.01, arc.get_width()))))
self.play(ShowCreation(normal_vect))
self.wait()
self.add(z_axis[0], face, z_axis[1], normal_vect)
self.play(*map(FadeIn, z_axis))
self.play(
FadeIn(theta, 0.5 * OUT), ShowCreation(arc),
)
# Vary Theta
frame.reorient(2)
face.rotate(-35 * DEGREES, get_un(), about_point=face.get_center())
self.play(
Rotate(face, 50 * DEGREES, UP),
rate_func=there_and_back,
run_time=8,
)
# Show shadow area in the corner
axes = Axes(
(0, 180, 22.5), (0, 1, 0.25),
width=5,
height=2,
axis_config={
"include_tip": False,
"tick_size": 0.05,
"numbers_to_exclude": [],
},
)
axes.to_corner(UR, buff=MED_SMALL_BUFF)
axes.x_axis.add_numbers([0, 45, 90, 135, 180], unit="^\\circ")
y_label = OldTexText("Shadow's area", font_size=24)
y_label.next_to(axes.y_axis.get_top(), RIGHT, MED_SMALL_BUFF)
y_label.set_backstroke()
ly_label = OldTex("s^2", font_size=24)
ly_label.next_to(axes.y_axis.get_top(), LEFT, SMALL_BUFF)
ly_label.shift(0.05 * UP)
axes.add(y_label, ly_label)
axes.fix_in_frame()
graph = axes.get_graph(
lambda x: math.cos(x * DEGREES),
x_range=(0, 90),
)
graph.set_stroke(RED, 3)
graph.fix_in_frame()
question = Text("Can you guess?", font_size=36)
question.to_corner(UR)
question.set_color(RED)
dot = Dot(color=RED)
dot.scale(0.5)
dot.move_to(axes.c2p(0, 1))
dot.fix_in_frame()
self.play(
FadeIn(axes),
Rotate(face, -get_theta(), UP, run_time=2),
)
self.play(FadeIn(dot, shift=2 * UP + RIGHT))
self.wait(2)
self.add(graph, axes)
self.play(
UpdateFromFunc(dot, lambda d: d.move_to(graph.get_end())),
ShowCreation(graph),
Rotate(face, PI / 2, UP),
run_time=5
)
self.play(frame.animate.reorient(45), run_time=2)
self.play(frame.animate.reorient(5), run_time=4)
# Show vertical plane
plane = Rectangle(width=self.plane.get_width(), height=5)
plane.insert_n_curves(100)
plane.set_fill(WHITE, 0.25)
plane.set_stroke(width=0)
plane.apply_depth_test()
plane.rotate(PI / 2, RIGHT)
plane.move_to(ORIGIN, IN)
plane.save_state()
plane.stretch(0, 2, about_edge=IN)
face.apply_depth_test()
z_axis.apply_depth_test()
self.shadow.apply_depth_test()
self.play(
LaggedStartMap(FadeOut, VGroup(*words, graph, axes, dot)),
Restore(plane, run_time=3)
)
self.play(Rotate(face, -60 * DEGREES, UP, run_time=2))
# Slice up face
face_copy = face.deepcopy()
face_copy.rotate(-get_theta(), UP)
face_copy.move_to(ORIGIN)
n_slices = 25
rects = Rectangle().replicate(n_slices)
rects.arrange(DOWN, buff=0)
rects.replace(face_copy, stretch=True)
slices = VGroup(*(Intersection(face_copy, rect) for rect in rects))
slices.match_style(face_copy)
slices.set_stroke(width=0)
slices.rotate(get_theta(), UP)
slices.move_to(face)
slices.apply_depth_test()
slices.save_state()
slice_outlines = slices.copy()
slice_outlines.set_stroke(RED, 1)
slice_outlines.set_fill(opacity=0)
slice_outlines.deactivate_depth_test()
frame.clear_updaters()
self.play(
frame.animate.set_euler_angles(PI / 2, get_theta()),
FadeOut(VGroup(theta, arc)),
run_time=2
)
self.play(ShowCreation(slice_outlines, lag_ratio=0.05))
self.remove(face)
self.add(slices)
self.remove(self.shadow)
self.solid = slices
self.add_shadow()
self.shadow.set_stroke(width=0)
self.add(normal_vect, plane, slice_outlines)
slices.insert_n_curves(10)
slices.generate_target()
for sm in slices.target:
sm.stretch(0.5, 1)
self.play(
MoveToTarget(slices),
FadeOut(slice_outlines),
run_time=2
)
self.wait(2)
# Focus on one slice
long_slice = slices[len(slices) // 2].deepcopy()
line = Line(long_slice.get_corner(LEFT + OUT), long_slice.get_corner(RIGHT + IN))
line.scale(0.97)
line.set_stroke(BLUE, 3)
frame.generate_target()
frame.target.reorient(0, 90)
frame.target.set_height(6)
frame.target.move_to(2.5 * OUT)
self.shadow.clear_updaters()
self.play(
MoveToTarget(frame),
*map(FadeIn, (theta, arc)),
FadeOut(plane),
FadeOut(slices),
FadeOut(self.shadow),
FadeIn(line),
run_time=2,
)
self.wait()
# Analyze slice
shadow = line.copy()
shadow.stretch(0, 2, about_edge=IN)
shadow.set_stroke(BLUE_E)
vert_line = Line(line.get_start(), shadow.get_start())
vert_line.set_stroke(GREY_B, 3)
shadow_label = Text("Shadow")
shadow_label.set_fill(BLUE_E)
shadow_label.set_backstroke()
shadow_label.rotate(PI / 2, RIGHT)
shadow_label.next_to(shadow, IN, SMALL_BUFF)
self.play(
TransformFromCopy(line, shadow),
FadeIn(shadow_label, 0.5 * IN),
)
self.wait()
self.play(ShowCreation(vert_line))
self.wait()
top_theta_group = VGroup(
z_axis[1].copy(),
arc.copy().clear_updaters(),
theta.copy().clear_updaters(),
Line(*normal_vect.get_start_and_end()).match_style(z_axis[1].copy()),
)
self.play(
top_theta_group.animate.move_to(line.get_start(), LEFT + IN)
)
elbow = Elbow(angle=-get_theta())
elbow.set_stroke(WHITE, 2)
ul_arc = Arc(
radius=0.4,
start_angle=-get_theta(),
angle=-(PI / 2 - get_theta())
)
ul_arc.match_style(elbow)
supl = OldTex("90^\\circ - \\theta", font_size=24)
supl.next_to(ul_arc, DOWN, SMALL_BUFF, aligned_edge=LEFT)
supl.set_backstroke()
supl[0][:3].shift(SMALL_BUFF * RIGHT / 2)
ul_angle_group = VGroup(elbow, ul_arc, supl)
ul_angle_group.rotate(PI / 2, RIGHT, about_point=ORIGIN)
ul_angle_group.shift(line.get_start())
dr_arc = Arc(
radius=0.4,
start_angle=PI,
angle=-get_theta(),
)
dr_arc.match_style(ul_arc)
dr_arc.rotate(PI / 2, RIGHT, about_point=ORIGIN)
dr_arc.shift(line.get_end())
dr_theta = OldTex("\\theta", font_size=24)
dr_theta.rotate(PI / 2, RIGHT)
dr_theta.next_to(dr_arc, LEFT, SMALL_BUFF)
dr_theta.shift(SMALL_BUFF * OUT / 2)
self.play(ShowCreation(elbow))
self.play(
ShowCreation(ul_arc),
FadeTransform(top_theta_group[2].copy(), supl),
)
self.play(
TransformFromCopy(ul_arc, dr_arc),
TransformFromCopy(supl[0][4].copy().set_stroke(width=0), dr_theta[0][0]),
)
self.wait()
# Highlight lower right
rect = Rectangle(0.8, 0.5)
rect.set_stroke(YELLOW, 2)
rect.rotate(PI / 2, RIGHT)
rect.move_to(dr_theta, LEFT).shift(SMALL_BUFF * LEFT)
self.play(
ShowCreation(rect),
top_theta_group.animate.fade(0.8),
ul_angle_group.animate.fade(0.8),
)
self.wait()
# Show cosine
cos_formula = OldTex(
"\\cos(\\theta)", "=",
"{\\text{Length of }", "\\text{shadow}",
"\\over",
"\\text{Length of }", "\\text{slice}"
"}",
)
cos_formula[2:].scale(0.75, about_edge=LEFT)
cos_formula.to_corner(UR)
cos_formula.fix_in_frame()
lower_formula = OldTex(
"\\text{shadow}", "=",
"\\cos(\\theta)", "\\cdot", "\\text{slice}"
)
lower_formula.match_width(cos_formula)
lower_formula.next_to(cos_formula, DOWN, MED_LARGE_BUFF)
lower_formula.fix_in_frame()
for tex in cos_formula, lower_formula:
tex.set_color_by_tex("shadow", BLUE_D)
tex.set_color_by_tex("slice", BLUE_B)
self.play(Write(cos_formula))
self.wait()
self.play(TransformMatchingTex(
VGroup(*(cos_formula[i].copy() for i in [0, 1, 3, 6])),
lower_formula,
path_arc=PI / 4,
))
self.wait()
# Bring full face back
frame.generate_target()
frame.target.reorient(20, 75)
frame.target.set_height(6)
frame.target.set_z(2)
line_shadow = get_shadow(line)
line_shadow.set_stroke(BLUE_E, opacity=0.5)
self.solid = face
self.add_shadow()
self.add(z_axis[0], face, z_axis[1], line, normal_vect, theta, arc)
self.play(
MoveToTarget(frame, run_time=5),
FadeIn(face, run_time=3),
FadeIn(self.shadow, run_time=3),
FadeIn(line_shadow, run_time=3),
LaggedStart(*map(FadeOut, [
top_theta_group, ul_angle_group, rect,
dr_theta, dr_arc,
vert_line, shadow, shadow_label,
]), run_time=4),
)
frame.add_updater(lambda f, dt: f.increment_theta(0.01 * dt))
self.wait(2)
# Show perpendicular
perp = Line(
face.pfp(binary_search(
lambda a: face.pfp(a)[2],
face.get_center()[2], 0, 0.5,
)),
face.pfp(binary_search(
lambda a: face.pfp(a)[2],
face.get_center()[2], 0.5, 1.0,
)),
)
perp.set_stroke(RED, 3)
perp_shadow = get_shadow(perp)
perp_shadow.set_stroke(RED_E, 3, opacity=0.2)
self.add(perp, normal_vect, arc)
self.play(
ShowCreation(perp),
ShowCreation(perp_shadow),
)
face.add(line)
self.play(Rotate(face, 45 * DEGREES, UP), run_time=3)
self.play(Rotate(face, -55 * DEGREES, UP), run_time=3)
self.play(Rotate(face, 20 * DEGREES, UP), run_time=2)
# Give final area formula
final_formula = OldTex(
"\\text{Area}(", "\\text{shadow}", ")",
"=",
"|", "\\cos(\\theta)", "|", "s^2"
)
final_formula.set_color_by_tex("shadow", BLUE_D)
final_formula.match_width(lower_formula)
final_formula.next_to(lower_formula, DOWN, MED_LARGE_BUFF)
final_formula.fix_in_frame()
final_formula.get_parts_by_tex("|").set_opacity(0)
final_formula.set_stroke(BLACK, 3, background=True)
rect = SurroundingRectangle(final_formula)
rect.set_stroke(YELLOW, 2)
rect.fix_in_frame()
self.play(Write(final_formula))
self.play(ShowCreation(rect))
final_formula.add(rect)
self.wait(10)
# Absolute value
face.remove(line)
self.play(
frame.animate.shift(0.5 * DOWN + RIGHT).reorient(10),
LaggedStart(*map(FadeOut, [cos_formula, lower_formula])),
FadeIn(graph),
FadeIn(axes),
FadeOut(line),
FadeOut(line_shadow),
FadeOut(perp),
FadeOut(perp_shadow),
final_formula.animate.shift(2 * DOWN),
run_time=2
)
self.play(
Rotate(face, PI / 2 - get_theta(), UP),
run_time=2
)
new_graph = axes.get_graph(
lambda x: math.cos(x * DEGREES),
(90, 180),
)
new_graph.match_style(graph)
new_graph.fix_in_frame()
self.play(
Rotate(face, PI / 2, UP),
ShowCreation(new_graph),
run_time=5,
)
self.play(
Rotate(face, -PI / 4, UP),
run_time=2,
)
self.wait(3)
alt_normal = normal_vect.copy()
alt_normal.clear_updaters()
alt_normal.rotate(PI, UP, about_point=face.get_center())
alt_normal.set_color(YELLOW)
self.add(alt_normal, face, normal_vect, arc, theta)
self.play(ShowCreation(alt_normal))
self.wait()
self.play(FadeOut(alt_normal))
new_graph.generate_target()
new_graph.target.flip(RIGHT)
new_graph.target.move_to(graph.get_end(), DL)
self.play(
MoveToTarget(new_graph),
final_formula.get_parts_by_tex("|").animate.set_opacity(1),
)
self.play(
final_formula.animate.next_to(axes, DOWN)
)
self.wait()
self.play(Rotate(face, -PI / 2, UP), run_time=5)
self.wait(10)
class NotQuiteRight(TeacherStudentsScene):
def construct(self):
self.remove(self.background)
self.teacher_says(
"Not quite right...",
target_mode="hesitant",
bubble_config={"height": 3, "width": 4},
added_anims=[
self.change_students(
"pondering", "thinking", "erm",
look_at=self.screen,
)
]
)
self.wait(4)
class DiscussLinearity(Scene):
def construct(self):
# Set background
background = FullScreenRectangle()
self.add(background)
panels = Rectangle(4, 4).replicate(3)
panels.set_fill(BLACK, 1)
panels.set_stroke(WHITE, 2)
panels.set_height(FRAME_HEIGHT - 1)
panels.arrange(RIGHT, buff=LARGE_BUFF)
panels.set_width(FRAME_WIDTH - 1)
panels.center()
self.add(panels)
# Arrows
arrows = VGroup(*(
Arrow(
p1.get_top(), p2.get_top(), path_arc=-0.6 * PI
).scale(0.75, about_edge=DOWN)
for p1, p2 in zip(panels, panels[1:])
))
arrows.space_out_submobjects(0.8)
arrows.rotate(PI, RIGHT, about_point=panels.get_center())
arrow_labels = VGroup(
Text("Rotation", font_size=30),
Text("Flat projection", font_size=30),
)
arrow_labels.set_backstroke()
for arrow, label in zip(arrows, arrow_labels):
label.next_to(arrow.pfp(0.5), UP, buff=0.35)
shape_labels = VGroup(
Text("Some shape"),
Text("Any shape"),
)
shape_labels.next_to(panels[0].get_top(), UP, SMALL_BUFF)
# self.play(Write(shape_labels[0], run_time=1))
# self.wait()
for arrow, label in zip(arrows, arrow_labels):
self.play(
ShowCreation(arrow),
FadeIn(label, lag_ratio=0.1)
)
self.wait()
# Linear!
lin_text = Text(
"Both are linear transformations!",
t2c={"linear": YELLOW}
)
lin_text.next_to(panels, UP, MED_SMALL_BUFF)
self.play(FadeIn(lin_text, lag_ratio=0.1))
self.wait()
# Stretch words
uniform_words = Text("Uniform stretching here", font_size=36).replicate(2)
for words, panel in zip(uniform_words, panels[0::2]):
words.next_to(panel.get_top(), DOWN, SMALL_BUFF)
words.set_color(YELLOW)
words.set_backstroke()
self.play(
FadeIn(words, lag_ratio=0.1),
)
self.wait()
# Transition
lin_part = lin_text.get_part_by_text("linear")
lin_copies = lin_part.copy().replicate(2)
lin_copies.scale(0.6)
for lin_copy, arrow in zip(lin_copies, arrows):
lin_copy.next_to(arrow.pfp(0.5), DOWN, buff=0.15)
self.play(
TransformFromCopy(lin_part.replicate(2), lin_copies),
LaggedStart(
FadeOut(lin_text, lag_ratio=0.1),
*map(FadeOut, uniform_words)
)
)
# Areas
area_labels = VGroup(
Text("Area(shape)", t2c={"shape": BLUE}),
Text("Area(shadow)", t2c={"shadow": BLUE_E}),
)
area_exprs = VGroup(
OldTex("A").set_color(BLUE),
OldTex("(\\text{some factor})", "\\cdot ", "A"),
)
area_exprs[1][2].set_color(BLUE)
area_exprs[1][0].set_color(GREY_C)
equals = VGroup()
for label, expr, panel in zip(area_labels, area_exprs, panels[0::2]):
label.match_x(panel)
label.to_edge(UP, buff=MED_SMALL_BUFF)
eq = OldTex("=")
eq.rotate(PI / 2)
eq.next_to(label, DOWN, buff=0.15)
equals.add(eq)
expr.next_to(eq, DOWN, buff=0.15)
self.play(
*map(Write, area_labels),
run_time=1
)
self.play(
*(FadeIn(eq, 0.5 * DOWN) for eq in equals),
*(FadeIn(expr, DOWN) for expr in area_exprs),
)
self.wait()
f_rot = OldTex("f(\\text{Rot})")
f_rot.set_color(GREY_B)
times_A = area_exprs[1][1:]
f_rot.next_to(times_A, LEFT, buff=0.2)
times_A.generate_target()
VGroup(f_rot, times_A.target).match_x(panels[2])
self.play(
FadeTransform(area_exprs[1][0], f_rot),
MoveToTarget(times_A)
)
self.play(ShowCreationThenFadeAround(f_rot, run_time=2))
self.wait(1)
# Determinant
factor = area_exprs[1].get_part_by_tex('factor')
rect = SurroundingRectangle(factor, buff=SMALL_BUFF)
rect.set_stroke(YELLOW, 2)
rot = Matrix([["v_1", "w_1"], ["v_2", "w_2"], ["v_3", "w_3"]], h_buff=1.0)
rot.set_column_colors(GREEN, RED)
proj = Matrix([["1", "0", "0"], ["0", "1", "0"]], h_buff=0.6)
prod = VGroup(proj, rot)
prod.arrange(RIGHT, buff=SMALL_BUFF)
prod.set_height(0.8)
det = OldTex(
"\\text{det}", "\\Big(", "\\Big)",
tex_to_color_map={
"\\text{det}": YELLOW,
# "rot": BLUE_D,
# "proj": BLUE_B,
},
font_size=36
)
det[1:].match_height(prod, stretch=True)
det.to_edge(UP)
prod.next_to(det[1], RIGHT, SMALL_BUFF)
det[2].next_to(prod, RIGHT, SMALL_BUFF)
det.add(prod)
det.center().to_edge(UP, buff=0.25)
det_rect = SurroundingRectangle(det, buff=SMALL_BUFF)
det_rect.set_stroke(YELLOW, 1)
rot_brace = Brace(rot, DOWN, buff=SMALL_BUFF)
details = Text("Need to work out rotation matrix...", font_size=20)
details.next_to(rot_brace, DOWN, SMALL_BUFF)
details.set_color(GREY_A)
arrow = Arrow(rect.get_corner(UL), det.get_right())
arrow.set_color(YELLOW)
self.play(ShowCreation(rect))
self.play(
FadeTransform(rect.copy(), det_rect),
FadeTransform(factor.copy(), det),
ShowCreation(arrow)
)
self.wait()
self.play(
FadeOut(det_rect),
GrowFromCenter(rot_brace),
FadeIn(details),
)
self.wait()
self.play(LaggedStart(*map(FadeOut, (
*det, rot_brace, details
)), lag_ratio=0.3, run_time=2))
# Any shape
ind_words = Text("Independent of the shape!", font_size=30)
ind_words.move_to(det)
ind_words.set_color(GREEN)
self.play(
arrow.animate.match_points(Arrow(factor.get_corner(UL), ind_words.get_corner(DR))),
Write(ind_words, run_time=1),
)
self.wait()
self.play(LaggedStart(*map(FadeOut, (ind_words, arrow, rect))))
self.wait()
# Cross out right
cross = Cross(VGroup(equals[1], f_rot, times_A))
cross.insert_n_curves(20)
self.play(ShowCreation(cross))
self.wait(3)
class Matrices(Scene):
def construct(self):
self.add(FullScreenRectangle())
kw = {
"v_buff": 0.7,
"bracket_v_buff": 0.15,
"bracket_h_buff": 0.15,
}
matrices = VGroup(
Matrix([["v_1", "w_1"], ["v_2", "w_2"], ["v_3", "w_3"]], h_buff=1.0, **kw),
Matrix([["1", "0", "0"], ["0", "1", "0"]], h_buff=0.6, **kw),
)
matrices.set_color(GREY_A)
matrices[0].set_column_colors(GREEN, RED)
matrices.arrange(LEFT, buff=SMALL_BUFF)
matrices.scale(0.5)
mat_product = matrices[:2].copy()
vectors = VGroup(
Matrix([["x_0"], ["y_0"]], **kw),
Matrix([["x_1"], ["y_1"], ["z_1"]], **kw),
Matrix([["x_2"], ["y_2"]], **kw),
)
for vect, x in zip(vectors, [-6, 0, 6]):
vect.set_x(x)
vect.set_y(2.5)
arrows = VGroup(
Arrow(vectors[0], vectors[1]),
Arrow(vectors[1], vectors[2]),
Arrow(vectors[0], vectors[2]),
)
for mat, arrow in zip((*matrices[:2], mat_product), arrows):
mat.next_to(arrow, UP, SMALL_BUFF)
# Animations
self.add(vectors[0])
for i in range(2):
self.play(
FadeTransform(vectors[i].copy(), vectors[i + 1]),
ShowCreation(arrows[i]),
FadeIn(matrices[i], 0.5 * RIGHT)
)
self.wait()
self.play(
Transform(arrows[0], arrows[2]),
Transform(arrows[1], arrows[2]),
Transform(matrices, mat_product),
FadeOut(vectors[1], scale=0),
)
class DefineDeterminant(Scene):
def construct(self):
# Planes
plane = NumberPlane((-2, 2), (-3, 3))
plane.set_height(FRAME_HEIGHT)
planes = VGroup(plane, plane.deepcopy())
planes[0].to_edge(LEFT, buff=0)
planes[1].to_edge(RIGHT, buff=0)
planes[1].set_stroke(GREY_A, 1, 0.5)
planes[1].faded_lines.set_opacity(0)
titles = VGroup(
Text("Input"),
Text("Output"),
)
for title, plane in zip(titles, planes):
title.next_to(plane.get_top(), DOWN)
title.add_background_rectangle()
self.add(planes)
# Area
square = Square()
square.set_stroke(YELLOW, 2)
square.set_fill(YELLOW, 0.5)
square.replace(Line(planes[0].c2p(-1, -1), planes[0].c2p(1, 1)))
area_label = OldTexText("Area", "=", "$A$")
area_label.set_color_by_tex("$A$", YELLOW)
area_label.next_to(square, UP)
area_label.add_background_rectangle()
self.play(
DrawBorderThenFill(square),
FadeIn(area_label, 0.25 * UP, rate_func=squish_rate_func(smooth, 0.5, 1))
)
self.wait()
# Arrow
arrow = Arrow(*planes)
arrow_label = Text("Linear transformation", font_size=30)
arrow_label.next_to(arrow, UP)
mat_mob = Matrix([["a", "b"], ["c", "d"]], h_buff=0.7, v_buff=0.7)
mat_mob.set_height(0.7)
mat_mob.next_to(arrow, DOWN)
# Apply matrix
matrix = [
[0.5, 0.4],
[0.25, 0.75],
]
for mob in planes[0], square:
mob.output = mob.deepcopy()
mob.output.apply_matrix(matrix, about_point=planes[0].c2p(0, 0))
mob.output.move_to(planes[1].get_center())
planes[0].output.set_stroke(width=1, opacity=1)
planes[0].output.faded_lines.set_opacity(0)
self.play(
ReplacementTransform(planes[0].copy().fade(1), planes[0].output, run_time=2),
ReplacementTransform(square.copy().fade(1), square.output, run_time=2),
ShowCreation(arrow),
FadeIn(arrow_label, 0.25 * RIGHT),
FadeIn(mat_mob, 0.25 * RIGHT),
)
self.wait()
# New area
new_area_label = OldTex(
"\\text{Area} = ", "{c}", "\\cdot", "{A}",
tex_to_color_map={
"{c}": RED,
"{A}": YELLOW,
}
)
new_area_label.add_background_rectangle()
new_area_label.next_to(square.output, UP)
new_area_label.shift(0.5 * RIGHT)
mmc = mat_mob.copy()
mmc.scale(1.5)
det = VGroup(get_det_text(mmc), mmc)
det.set_height(new_area_label.get_height() * 1.2)
det.move_to(new_area_label.get_part_by_tex("c"), RIGHT)
det.match_y(new_area_label[-1])
det_name = OldTexText("``Determinant''", font_size=36)
det_name.next_to(det, UP, MED_LARGE_BUFF)
det_name.set_color(RED)
det_name.add_background_rectangle()
self.play(FadeTransform(area_label.copy(), new_area_label))
self.wait()
self.play(
FadeTransform(mat_mob.copy(), det),
FadeTransform(new_area_label.get_part_by_tex("c"), det_name),
new_area_label[1].animate.next_to(det, LEFT, SMALL_BUFF).match_y(new_area_label[1]),
)
self.wait()
class AmbientShapeRotationPreimage(ShadowScene):
inf_light = False
display_mode = "preimage_only" # Or "full_3d" or "shadow_only"
rotate_in_3d = True
only_show_shadow = False
def construct(self):
# Setup
display_mode = self.display_mode
frame = self.camera.frame
frame.set_height(6)
light = self.light
light.move_to(75 * OUT)
shape = self.solid
fc = 2.5 * OUT
shape.move_to(fc)
self.solid.rotate(-0.5 * PI)
self.solid.insert_n_curves(20)
preimage = self.solid.deepcopy()
preimage.move_to(ORIGIN)
rotated = self.solid
self.remove(self.shadow)
shadow = rotated.deepcopy()
shadow.set_fill(interpolate_color(BLUE_E, BLACK, 0.5), 0.7)
shadow.set_stroke(BLACK, 1)
def update_shadow(shadow):
shadow.set_points(
np.apply_along_axis(
lambda p: project_to_xy_plane(self.light.get_center(), p),
1, rotated.get_points()
)
)
shadow.refresh_triangulation()
return shadow
shadow.add_updater(update_shadow)
rotated.axis_tracker = VectorizedPoint(RIGHT)
rotated.angle_tracker = ValueTracker(0)
rotated.rot_speed_tracker = ValueTracker(0.15)
def update_rotated(mob, dt):
mob.set_points(preimage.get_points())
mob.shift(fc)
mob.refresh_triangulation()
axis = mob.axis_tracker.get_location()
angle = mob.angle_tracker.get_value()
speed = mob.rot_speed_tracker.get_value()
mob.axis_tracker.rotate(speed * dt, axis=OUT, about_point=ORIGIN)
mob.angle_tracker.increment_value(speed * dt)
mob.rotate(angle, axis, about_point=fc)
return rotated
rotated.add_updater(update_rotated)
# Conditionals
if display_mode == "full_3d":
preimage.set_opacity(0)
self.add(shadow)
self.add(rotated)
z_axis = VGroup(
Line(ORIGIN, fc),
Line(fc, 10 * OUT),
)
z_axis.set_stroke(WHITE, 1)
self.add(z_axis[0], rotated, z_axis[1])
orientation_arrows = VGroup(
Vector(RIGHT, stroke_color=RED_D),
Vector(UP, stroke_color=GREEN_D),
Vector(OUT, stroke_color=BLUE_D),
)
orientation_arrows.set_stroke(opacity=0.85)
orientation_arrows.shift(fc)
orientation_arrows.save_state()
orientation_arrows.add_updater(lambda m: m.restore().rotate(
rotated.angle_tracker.get_value(),
rotated.axis_tracker.get_location(),
))
orientation_arrows.add_updater(lambda m: m.shift(fc - m[0].get_start()))
orientation_arrows.apply_depth_test()
self.add(orientation_arrows)
proj_lines = always_redraw(lambda: VGroup(*(
Line(
rotated.pfp(a),
flat_project(rotated.pfp(a))
).set_stroke(WHITE, 0.5, 0.2)
for a in np.linspace(0, 1, 100)
)))
self.add(proj_lines)
frame.reorient(20, 70)
self.init_frame_rotation()
# frame_speed = -0.02
# frame.add_updater(lambda f, dt: f.increment_theta(frame_speed * dt))
elif display_mode == "shadow_only":
frame.reorient(0, 0)
frame.set_height(3)
rotated.set_opacity(0)
preimage.set_opacity(0)
self.glow.set_opacity(0.2)
self.add(rotated)
self.add(shadow)
elif display_mode == "preimage_only":
self.glow.set_opacity(0)
self.remove(self.plane)
self.add(preimage)
frame.reorient(0, 0)
frame.set_height(3)
rotated.set_opacity(0)
# Just hang around
self.wait(15)
# Change to cat
cat = SVGMobject("cat_outline").family_members_with_points()[0]
dog = SVGMobject("dog_outline").family_members_with_points()[0]
dog.insert_n_curves(87)
for mob in cat, dog:
mob.match_style(preimage)
mob.replace(preimage, dim_to_match=0)
pass
# Stretch
self.play(rotated.rot_speed_tracker.animate.set_value(0))
rotated.rot_speed = 0
for axis, diag in zip((0, 1, 0, 1), (False, False, True, True)):
preimage.generate_target()
if diag:
preimage.target.rotate(PI / 4)
preimage.target.stretch(2, axis)
if diag:
preimage.target.rotate(-PI / 4)
self.play(
MoveToTarget(preimage),
rate_func=there_and_back,
run_time=4
)
self.wait(5)
self.play(rotated.rot_speed_tracker.animate.set_value(0.1))
# Change shape
cat = SVGMobject("cat_outline").family_members_with_points()[0]
dog = SVGMobject("dog_outline").family_members_with_points()[0]
dog.insert_n_curves(87)
for mob in cat, dog:
mob.match_style(preimage)
mob.replace(preimage, dim_to_match=0)
self.play(Transform(preimage, cat, run_time=4))
cat.insert_n_curves(87)
preimage.become(cat)
self.wait(2)
# More shape changes
self.play(
preimage.animate.scale(2),
rate_func=there_and_back,
run_time=3,
)
self.play(
preimage.animate.become(dog),
path_arc=PI,
rate_func=there_and_back_with_pause, # Or rather, with paws...
run_time=5,
)
self.wait(6)
# Bring light source closer
self.play(rotated.rot_speed_tracker.animate.set_value(0))
anims = [
light.animate.move_to(4 * OUT)
]
angle = rotated.angle_tracker.get_value()
angle_anim = rotated.angle_tracker.animate.set_value(np.round(angle / TAU, 0) * TAU)
if self.display_mode == "full_3d":
light_lines = self.get_light_lines(shadow)
lso = light_lines[0].get_stroke_opacity()
pso = proj_lines[0].get_stroke_opacity()
proj_lines.clear_updaters()
anims += [
UpdateFromAlphaFunc(proj_lines, lambda m, a: m.set_stroke(opacity=pso * (1 - a))),
UpdateFromAlphaFunc(light_lines, lambda m, a: m.set_stroke(opacity=lso * a)),
angle_anim,
frame.animate.reorient(20, 70).set_height(8),
]
frame.clear_updaters()
if self.display_mode == "shadow_only":
anims += [
frame.animate.set_height(10),
angle_anim,
]
self.play(*anims, run_time=4)
self.wait()
rotated.axis_tracker.move_to(UP)
self.play(
rotated.angle_tracker.animate.set_value(70 * DEGREES + TAU),
run_time=2
)
self.play(
preimage.animate.stretch(1.5, 0),
rate_func=there_and_back,
run_time=5,
)
anims = [rotated.axis_tracker.animate.move_to(RIGHT)]
if self.display_mode == "full_3d":
anims.append(frame.animate.reorient(-20, 70))
self.play(*anims, run_time=2)
self.play(
preimage.animate.stretch(2, 1),
rate_func=there_and_back,
run_time=7,
)
# More ambient motion
self.play(rotated.rot_speed_tracker.animate.set_value(0.1))
self.wait(30)
def get_solid(self):
face = Square(side_length=2)
face.set_fill(BLUE, 0.5)
face.set_stroke(WHITE, 1)
return face
class AmbientShapeRotationFull3d(AmbientShapeRotationPreimage):
display_mode = "full_3d"
class AmbientShapeRotationShadowOnly(AmbientShapeRotationPreimage):
display_mode = "shadow_only"
class IsntThatObvious(TeacherStudentsScene):
def construct(self):
self.remove(self.background)
self.student_says(
OldTexText("Isn't that obvious?"),
bubble_config={
"height": 3,
"width": 4,
"direction": LEFT,
},
target_mode="angry",
look_at=self.screen,
added_anims=[LaggedStart(
self.teacher.change("guilty"),
self.students[0].change("pondering", self.screen),
self.students[1].change("erm", self.screen),
)]
)
self.wait(2)
self.play(
self.students[0].change("hesitant"),
)
self.wait(2)
class StretchLabel(Scene):
def construct(self):
label = VGroup(
Vector(0.5 * LEFT),
OldTex("1.5 \\times"),
Vector(0.5 * RIGHT)
)
label.set_color(YELLOW)
label.arrange(RIGHT, buff=SMALL_BUFF)
self.play(
*map(ShowCreation, label[::2]),
Write(label[1]),
)
self.wait()
class WonderAboutAverage(Scene):
def construct(self):
randy = Randolph()
randy.to_edge(DOWN)
randy.look(RIGHT)
self.play(PiCreatureBubbleIntroduction(
randy, OldTexText("How do you think\\\\about this average"),
target_mode="confused",
run_time=2
))
for x in range(2):
self.play(Blink(randy))
self.wait(2)
class SingleFaceRandomRotation(ShadowScene):
initial_wait_time = 0
inf_light = True
n_rotations = 1
total_time = 60
plane_dims = (8, 8)
frame_rot_speed = 0.02
theta0 = -20 * DEGREES
CONFIG = {"random_seed": 0}
def setup(self):
super().setup()
np.random.seed(self.random_seed)
frame = self.camera.frame
frame.set_height(5.0)
frame.set_z(1.75)
frame.set_theta(self.theta0)
face = self.solid
face.shift(0.25 * IN)
fc = face.get_center()
z_axis = self.z_axis = VGroup(Line(ORIGIN, fc), Line(fc, 10 * OUT))
z_axis.set_stroke(WHITE, 0.5)
self.add(z_axis[0], face, z_axis[1])
arrows = VGroup(
Line(ORIGIN, RIGHT, color=RED_D),
Line(ORIGIN, UP, color=GREEN_D),
VGroup(
Vector(OUT, stroke_width=4, stroke_color=BLACK),
Vector(OUT, stroke_width=3, stroke_color=BLUE_D),
)
)
arrows[:2].set_stroke(width=2)
arrows.set_stroke(opacity=0.8)
arrows.shift(fc)
arrows.set_stroke(opacity=0.8)
face.add(arrows[:2])
face = Group(face, arrows[2])
face.add_updater(lambda m: self.sort_to_camera(face))
self.face = self.solid = face
arrow_shadow = get_shadow(arrows)
arrow_shadow.set_stroke(width=1)
arrow_shadow[2].set_stroke(width=[1, 1, 4, 0])
self.add(arrow_shadow)
self.add(z_axis[0], face, z_axis[1])
def construct(self):
frame = self.camera.frame
face = self.face
frame.add_updater(lambda f, dt: f.increment_theta(self.frame_rot_speed * dt))
self.wait(self.initial_wait_time)
for x in range(self.n_rotations):
self.random_toss(
face,
about_point=fc,
angle=3 * PI,
# run_time=1.5,
run_time=8,
rate_func=smooth,
)
self.wait()
self.wait(self.total_time - 2 - self.initial_wait_time)
def get_solid(self):
face = Square(side_length=2)
face.set_fill(BLUE_E, 0.75)
face.set_stroke(WHITE, 0.5)
return face
class RandomRotations1(SingleFaceRandomRotation):
initial_wait_time = 1
theta0 = -30 * DEGREES
CONFIG = {"random_seed": 10}
class RandomRotations2(SingleFaceRandomRotation):
initial_wait_time = 1.5
theta0 = -25 * DEGREES
CONFIG = {"random_seed": 4}
class RandomRotations3(SingleFaceRandomRotation):
initial_wait_time = 2
theta0 = -20 * DEGREES
CONFIG = {"random_seed": 5}
class RandomRotations4(SingleFaceRandomRotation):
initial_wait_time = 2.5
theta0 = -15 * DEGREES
CONFIG = {"random_seed": 6}
class AverageFaceShadow(SingleFaceRandomRotation):
inf_light = True
plane_dims = (16, 8)
n_samples = 50
def construct(self):
# Random shadows
self.camera.frame.set_height(6)
face = self.face
shadow = self.shadow
shadow.add_updater(lambda m: m.set_fill(BLACK, 0.25))
shadow.update()
point = face[0].get_center()
shadows = VGroup()
n_samples = self.n_samples
self.remove(self.z_axis)
self.init_frame_rotation()
self.add(shadows)
for n in range(n_samples):
self.randomly_reorient(face, about_point=point)
if n == n_samples - 1:
normal = next(
sm.get_unit_normal()
for sm in face.family_members_with_points()
if isinstance(sm, VMobject) and sm.get_fill_opacity() > 0
)
mat = z_to_vector(normal)
# face.apply_matrix(np.linalg.inv(mat), about_point=point)
shadow.update()
sc = shadow.copy()
sc.clear_updaters()
shadows.add(sc)
shadows.set_fill(BLACK, 1.5 / len(shadows))
shadows.set_stroke(opacity=10 / len(shadows))
self.wait(0.1)
# Fade out shadow
self.remove(shadow)
sc = shadow.copy().clear_updaters()
self.play(FadeOut(sc))
self.wait()
# Scaling
self.play(
face.animate.scale(0.5, about_point=point),
shadows.animate.scale(0.5, about_point=ORIGIN),
run_time=3,
rate_func=there_and_back,
)
for axis in [0, 1]:
self.play(
face.animate.stretch(2, axis, about_point=point),
shadows.animate.stretch(2, axis, about_point=ORIGIN),
run_time=3,
rate_func=there_and_back,
)
self.wait()
# Ambient rotations 106 plays
self.play(
self.camera.frame.animate.reorient(-10).shift(2 * LEFT),
)
self.add(shadow)
for n in range(100):
self.randomly_reorient(face, about_point=point)
self.wait(0.2)
class AverageCatShadow(AverageFaceShadow):
n_samples = 50
def setup(self):
super().setup()
self.replace_face()
def replace_face(self):
face = self.face
shape = self.get_shape().family_members_with_points()[0]
shape.match_style(face[0])
shape.replace(face[0])
face[0].set_points(shape.get_points())
face[0].set_gloss(0.25)
face[0][0].set_gloss(0)
self.solid = face
self.remove(self.shadow)
self.add_shadow()
def get_shape(self):
return SVGMobject("cat_outline")
class AveragePentagonShadow(AverageCatShadow):
def get_shape(self):
return RegularPolygon(5)
class AverageShadowAnnotation(Scene):
def construct(self):
# Many shadows
many_shadows = Text("Many shadows")
many_shadows.move_to(3 * DOWN)
self.play(Write(many_shadows))
self.wait(2)
# Formula
# shape_name = "2d shape"
shape_name = "Square"
t2c = {
"Shadow": GREY_B,
shape_name: BLUE,
"$c$": RED,
}
formula = VGroup(
OldTexText(
f"Area(Shadow({shape_name}))",
tex_to_color_map=t2c,
),
OldTex("=").rotate(PI / 2),
OldTexText(
"$c$", " $\\cdot$", f"(Area({shape_name}))",
tex_to_color_map=t2c
)
)
overline = get_overline(formula[0])
formula[0].add(overline)
formula.arrange(DOWN)
formula.to_corner(UL)
self.play(FadeTransform(many_shadows, formula[0]))
self.wait()
self.play(
VShowPassingFlash(
overline.copy().insert_n_curves(100).set_stroke(YELLOW, 5),
time_width=0.75,
run_time=2,
)
)
self.wait()
self.play(
Write(formula[1]),
FadeIn(formula[2], DOWN)
)
self.wait()
# Append half
half = OldTex("\\frac{1}{2}")
half.set_color(RED)
c = formula[2].get_part_by_tex("$c$")
half.move_to(c, RIGHT)
self.play(
FadeOut(c, 0.5 * UP),
FadeIn(half, 0.5 * UP),
)
self.wait()
class AlicesFaceAverage(Scene):
def construct(self):
# Background
background = FullScreenRectangle()
self.add(background)
panels = Rectangle(2, 2.5).replicate(5)
panels.set_stroke(WHITE, 1)
panels.set_fill(BLACK, 1)
dots = OldTex("\\dots")
panels.replace_submobject(3, dots)
panels.arrange(RIGHT, buff=0.25)
panels.set_width(FRAME_WIDTH - 1)
panels.move_to(2 * DOWN, DOWN)
self.add(panels)
panels = VGroup(*panels[:-2], panels[-1])
# Label the rotations
indices = ["1", "2", "3", "n"]
rot_labels = VGroup(*(
OldTex(f"R_{i}") for i in indices
))
for label, panel in zip(rot_labels, panels):
label.set_height(0.3)
label.next_to(panel, DOWN)
rot_words = Text("Sequence of random rotations")
rot_words.next_to(rot_labels, DOWN, MED_LARGE_BUFF)
self.play(Write(rot_words, run_time=2))
self.wait(2)
self.play(LaggedStartMap(
FadeIn, rot_labels,
shift=0.25 * DOWN,
lag_ratio=0.5
))
self.wait()
# Show the shadow areas
font_size = 30
fra_labels = VGroup(*(
OldTex(
f"f(R_{i})", "\\cdot ", "A",
tex_to_color_map={"A": BLUE},
font_size=font_size
)
for i in indices
))
DARK_BLUE = interpolate_color(BLUE_D, BLUE_E, 0.5)
area_shadow_labels = VGroup(*(
OldTex(
"\\text{Area}(", "\\text{Shadow}_" + i, ")",
tex_to_color_map={"\\text{Shadow}_" + i: DARK_BLUE},
font_size=font_size
)
for i in indices
))
s_labels = VGroup(*(
OldTex(
f"S_{i}", "=",
tex_to_color_map={f"S_{i}": DARK_BLUE},
font_size=font_size
)
for i in indices
))
label_arrows = VGroup()
for fra, area, s_label, panel in zip(fra_labels, area_shadow_labels, s_labels, panels):
fra.next_to(panel, UP, SMALL_BUFF)
area.next_to(fra, UP)
area.to_edge(UP, buff=LARGE_BUFF)
label_arrows.add(Arrow(area, fra, buff=0.2, stroke_width=3))
fra.generate_target()
eq = VGroup(s_label, fra.target)
eq.arrange(RIGHT, buff=SMALL_BUFF)
eq.move_to(fra, DOWN)
self.add(area_shadow_labels)
self.add(fra_labels)
self.add(label_arrows)
lr = 0.2
self.play(
LaggedStartMap(FadeIn, area_shadow_labels, lag_ratio=lr),
LaggedStartMap(ShowCreation, label_arrows, lag_ratio=lr),
LaggedStartMap(FadeIn, fra_labels, shift=DOWN, lag_ratio=lr),
)
self.wait()
self.play(
LaggedStart(*(
FadeTransform(area, area_s)
for area, area_s in zip(area_shadow_labels, s_labels)
), lag_ratio=lr),
LaggedStartMap(MoveToTarget, fra_labels, lag_ratio=lr),
LaggedStartMap(Uncreate, label_arrows, lag_ratio=lr),
)
# Show average
sample_average = OldTex(
"\\text{Sample average}", "=",
"\\frac{1}{n}", "\\left(",
"f(R_1)", "\\cdot ", "A", "+",
"f(R_2)", "\\cdot ", "A", "+",
"f(R_3)", "\\cdot ", "A", "+",
"\\cdots ",
"f(R_n)", "\\cdot ", "A",
"\\right)",
font_size=font_size
)
sample_average.set_color_by_tex("A", BLUE)
sample_average.to_edge(UP, buff=MED_SMALL_BUFF)
for tex in ["\\left", "\\right"]:
part = sample_average.get_part_by_tex(tex)
part.scale(1.5)
part.stretch(1.5, 1)
self.play(FadeIn(sample_average[:2]))
self.play(
TransformMatchingShapes(
fra_labels.copy(),
sample_average[4:-1]
)
)
self.wait()
self.play(
Write(VGroup(*sample_average[2:4], sample_average[-1]))
)
self.wait()
# Factor out A
sample_average.generate_target()
cdots = sample_average.target.get_parts_by_tex("\\cdot", substring=False)
As = sample_average.target.get_parts_by_tex("A", substring=False)
new_pieces = VGroup(*(
sm for sm in sample_average.target
if sm.get_tex() not in ["A", "\\cdot"]
))
new_A = As[0].copy()
new_cdot = cdots[0].copy()
new_pieces.insert_submobject(2, new_cdot)
new_pieces.insert_submobject(2, new_A)
new_pieces.arrange(RIGHT, buff=SMALL_BUFF)
new_pieces.move_to(sample_average)
for group, target in (As, new_A), (cdots, new_cdot):
for sm in group:
sm.replace(target)
group[1:].set_opacity(0)
self.play(LaggedStart(
*(
FlashAround(mob, time_width=3)
for mob in sample_average.get_parts_by_tex("A")
),
lag_ratio=0.1,
run_time=2
))
self.play(MoveToTarget(sample_average, path_arc=-PI / 5))
self.wait()
# True average
brace = Brace(new_pieces[4:], DOWN, buff=SMALL_BUFF, font_size=30)
lim = OldTex("n \\to \\infty", font_size=30)
lim.next_to(brace, DOWN)
VGroup(brace, lim).set_color(YELLOW)
sample = sample_average[0][:len("Sample")]
cross = Cross(sample)
cross.insert_n_curves(20)
cross.scale(1.5)
self.play(
FlashAround(sample_average[2:], run_time=3, time_width=1.5)
)
self.play(
FlashUnder(sample_average[:2], color=RED),
run_time=2
)
self.play(
GrowFromCenter(brace),
FadeIn(lim, 0.25 * DOWN),
ShowCreation(cross)
)
self.play(
LaggedStart(*map(FadeOut, [
*fra_labels, *s_labels, *panels, dots, *rot_labels, rot_words
]))
)
self.wait()
# Some constant
rect = SurroundingRectangle(
VGroup(new_pieces[4:], brace, lim),
buff=SMALL_BUFF,
)
rect.set_stroke(YELLOW, 1)
rect.stretch(0.98, 0)
words = Text("Some constant")
words.next_to(rect, DOWN)
subwords = Text("Independent of the size and shape of the 2d piece")
subwords.scale(0.5)
subwords.next_to(words, DOWN)
subwords.set_fill(GREY_A)
self.play(
ShowCreation(rect),
FadeIn(words, 0.25 * DOWN)
)
self.wait()
self.play(Write(subwords))
self.wait()
class ManyShadows(SingleFaceRandomRotation):
plane_dims = (4, 4)
limited_plane_extension = 2
def construct(self):
self.clear()
self.camera.frame.reorient(0, 0)
plane = self.plane
face = self.solid
shadow = self.shadow
n_rows = 3
n_cols = 10
planes = plane.replicate(n_rows * n_cols)
for n, plane in zip(it.count(1), planes):
face.rotate(angle=random.uniform(0, TAU), axis=normalize(np.random.uniform(-1, 1, 3)))
shadow.update()
sc = shadow.deepcopy()
sc.clear_updaters()
sc.set_fill(interpolate_color(BLUE_E, BLACK, 0.5), 0.75)
plane.set_gloss(0)
plane.add_to_back(sc)
area = DecimalNumber(get_norm(sc.get_area_vector() / 4.0), font_size=56)
label = VGroup(OldTex(f"f(R_{n}) = "), area)
label.arrange(RIGHT)
label.set_width(0.8 * plane.get_width())
label.next_to(plane, UP, SMALL_BUFF)
label.set_color(WHITE)
plane.add(label)
planes.arrange_in_grid(n_rows, n_cols, buff=LARGE_BUFF)
planes.set_width(15)
planes.to_edge(DOWN)
planes.update()
self.play(
LaggedStart(
*(
FadeIn(plane, scale=1.1)
for plane in planes
),
lag_ratio=0.6, run_time=10
)
)
self.wait()
self.embed()
class ComingUp(VideoWrapper):
title = "Bob will compute this directly"
wait_time = 10
animate_boundary = False
class AllPossibleOrientations(ShadowScene):
inf_light = True
limited_plane_extension = 6
plane_dims = (12, 8)
def construct(self):
# Setup
frame = self.camera.frame
frame.reorient(-20, 80)
frame.set_height(5)
frame.d_theta = 0
def update_frame(frame, dt):
frame.d_theta += -0.0025 * frame.get_theta()
frame.increment_theta(clip(0.0025 * frame.d_theta, -0.01 * dt, 0.01 * dt))
frame.add_updater(update_frame)
face = self.solid
square, normal_vect = face
normal_vect.set_flat_stroke()
self.solid = square
self.remove(self.shadow)
self.add_shadow()
self.shadow.deactivate_depth_test()
self.solid = face
fc = square.get_center().copy()
# Sphere points
sphere = Sphere(radius=1)
sphere.set_color(GREY_E, 0.7)
sphere.move_to(fc)
sphere.always_sort_to_camera(self.camera)
n_lat_lines = 40
theta_step = PI / n_lat_lines
sphere_points = np.array([
sphere.uv_func(phi, theta + theta_step * (phi / TAU))
for theta in np.arange(0, PI, theta_step)
for phi in np.linspace(
0, TAU, int(2 * n_lat_lines * math.sin(theta)) + 1
)
])
sphere_points[:, 2] *= -1
original_sphere_points = sphere_points.copy()
sphere_points += fc
sphere_dots = DotCloud(sphere_points)
sphere_dots.set_radius(0.0125)
sphere_dots.set_glow_factor(0.5)
sphere_dots.make_3d()
sphere_dots.apply_depth_test()
sphere_dots.add_updater(lambda m: m)
sphere_lines = VGroup(*(
Line(sphere.get_center(), p)
for p in sphere_dots.get_points()
))
sphere_lines.set_stroke(WHITE, 1, 0.05)
sphere_words = OldTexText("All normal vectors = Sphere")
uniform_words = OldTexText("All points equally likely")
for words in [sphere_words, uniform_words]:
words.fix_in_frame()
words.to_edge(UP)
# Trace sphere
N = len(original_sphere_points)
self.play(FadeIn(sphere_words))
self.play(
ShowCreation(sphere_dots),
ShowIncreasingSubsets(sphere_lines),
UpdateFromAlphaFunc(
face,
lambda m, a: m.apply_matrix(
rotation_between_vectors(
normal_vect.get_vector(),
original_sphere_points[int(a * (N - 1))],
),
about_point=fc
)
),
run_time=30,
rate_func=smooth,
)
self.play(
FadeOut(sphere_words, UP),
FadeIn(uniform_words, UP),
)
last_dot = Mobject()
for x in range(20):
point = random.choice(sphere_points)
dot = TrueDot(
point,
radius=1,
glow_factor=10,
color=YELLOW,
)
self.add(dot)
self.play(
face.animate.apply_matrix(rotation_between_vectors(
normal_vect.get_vector(),
point - fc
), about_point=fc),
FadeOut(last_dot, run_time=0.25),
FadeIn(dot),
run_time=0.5,
)
self.wait(0.25)
last_dot = dot
self.play(FadeOut(last_dot))
self.wait()
# Sphere itself
sphere_mesh = SurfaceMesh(sphere, resolution=(21, 11))
sphere_mesh.set_stroke(BLUE_E, 1, 1)
for sm in sphere_mesh.get_family():
sm.uniforms["anti_alias_width"] = 0
v1 = normal_vect.get_vector()
normal_vect.scale(0.99, about_point=fc)
v2 = DR + OUT
frame.reorient(-5)
self.play(
Rotate(
face, angle_between_vectors(v1, v2),
axis=normalize(cross(v1, v2))
),
UpdateFromAlphaFunc(
self.plane, lambda m, a: square.scale(0.9).set_opacity(0.5 - a * 0.5)
),
)
self.play(
ShowCreation(sphere_mesh, lag_ratio=0.5),
FadeIn(sphere),
sphere_dots.animate.set_radius(0),
FadeOut(sphere_lines),
frame.animate.reorient(0),
run_time=3,
)
self.remove(sphere_dots)
# Show patch
def get_patch(u, v, delta_u=0.05, delta_v=0.1):
patch = ParametricSurface(
sphere.uv_func,
u_range=(u * TAU, (u + delta_u) * TAU),
v_range=(v * PI, (v + delta_v) * PI),
)
patch.shift(fc)
patch.set_color(YELLOW, 0.75)
patch.always_sort_to_camera(self.camera)
return patch
patch = get_patch(0.85, 0.6)
self.add(patch, sphere)
self.play(
ShowCreation(patch),
frame.animate.reorient(10, 75),
run_time=2,
)
# Probability expression
patch_copy = patch.deepcopy()
sphere_copy = sphere.deepcopy()
sphere_copy.set_color(GREY_D, 0.7)
for mob in patch_copy, sphere_copy:
mob.apply_matrix(frame.get_inverse_camera_rotation_matrix())
mob.fix_in_frame()
mob.center()
patch_copy2 = patch_copy.copy()
prob = Group(*Tex(
"P(", "0.", ")", "=", "{Num ", "\\over ", "Den}",
font_size=60
))
prob.fix_in_frame()
prob.to_corner(UR)
prob.shift(DOWN)
for i, mob in [(1, patch_copy), (4, patch_copy2), (6, sphere_copy)]:
mob.replace(prob[i], dim_to_match=1)
prob.replace_submobject(i, mob)
sphere_copy.scale(3, about_edge=UP)
self.play(FadeIn(prob, lag_ratio=0.1))
self.wait()
for i in (4, 6):
self.play(ShowCreationThenFadeOut(
SurroundingRectangle(prob[i], stroke_width=2).fix_in_frame()
))
self.wait()
# Many patches
patches = Group(
get_patch(0.65, 0.5),
get_patch(0.55, 0.8),
get_patch(0.85, 0.8),
get_patch(0.75, 0.4, 0.1, 0.2),
)
patch.deactivate_depth_test()
self.add(sphere, patch)
for new_patch in patches:
self.play(
Transform(patch, new_patch),
)
self.wait()
# Non-specified orientation
self.play(
LaggedStart(*map(FadeOut, (sphere, sphere_mesh, patch, *prob, uniform_words)))
)
self.play(
square.animate.set_fill(opacity=0.5),
frame.animate.reorient(-30),
run_time=3,
)
self.play(
Rotate(square, TAU, normal_vect.get_vector()),
run_time=8,
)
self.wait()
# Show theta
def get_normal():
return normal_vect.get_vector()
def get_theta():
return np.arccos(get_normal()[2] / get_norm(get_normal()))
def get_arc():
result = Arc(PI / 2, -get_theta(), radius=0.25)
result.rotate(PI / 2, RIGHT, about_point=ORIGIN)
result.rotate(angle_of_vector([*get_normal()[:2], 0]), OUT, about_point=ORIGIN)
result.shift(fc)
result.set_stroke(WHITE, 1)
result.apply_depth_test()
return result
arc = always_redraw(get_arc)
theta = OldTex("\\theta", font_size=20)
theta.rotate(PI / 2, RIGHT)
theta.set_backstroke(width=2)
theta.add_updater(lambda m: m.next_to(arc.pfp(0.5), OUT + RIGHT, buff=0.05))
z_axis = Line(ORIGIN, 10 * OUT)
z_axis.set_stroke(WHITE, 1)
z_axis.apply_depth_test()
self.add(z_axis, face, theta, arc)
self.play(
ShowCreation(z_axis),
ShowCreation(arc),
FadeIn(theta, 0.5 * OUT),
)
self.wait()
# Show shadow area
shadow_area = OldTexText("Shadow area =", "$|\\cos(\\theta)|s^2$")
shadow_area.fix_in_frame()
shadow_area.to_edge(RIGHT)
shadow_area.set_y(-3)
shadow_area.set_backstroke()
self.play(
Write(shadow_area, run_time=3),
Rotate(face, TAU, normal_vect.get_vector(), run_time=10),
)
self.wait(4)
shadow_area[1].generate_target()
shadow_area[1].target.to_corner(UR, buff=MED_LARGE_BUFF)
shadow_area[1].target.shift(LEFT)
brace = Brace(shadow_area[1].target, DOWN)
brace_text = OldTexText("How do you average this\\\\over the sphere?", font_size=36)
brace_text.next_to(brace, DOWN, SMALL_BUFF)
brace.fix_in_frame()
brace_text.fix_in_frame()
self.play(
GrowFromCenter(brace),
MoveToTarget(shadow_area[1]),
FadeOut(shadow_area[0]),
square.animate.set_fill(opacity=0),
)
face.generate_target()
face.target[1].set_length(0.98, about_point=fc)
sphere.set_opacity(0.35)
sphere_mesh.set_stroke(width=0.5)
self.play(
MoveToTarget(face),
FadeIn(brace_text, 0.5 * DOWN),
Write(sphere_mesh, run_time=2, stroke_width=1),
FadeIn(sphere),
)
# Sum expression
def update_theta_ring(ring):
theta = get_theta()
phi = angle_of_vector([*get_normal()[:2], 0])
ring.set_width(max(2 * 1.01 * math.sin(theta), 1e-3))
ring.rotate(phi - angle_of_vector([*ring.get_start()[:2], 0]))
ring.move_to(fc + math.cos(theta) * OUT)
return ring
theta_ring = Circle()
theta_ring.set_stroke(YELLOW, 2)
theta_ring.apply_depth_test()
theta_ring.uniforms["anti_alias_width"] = 0
loose_sum = OldTex(
"\\sum_{\\theta \\in [0, \\pi]}",
"P(\\theta)",
"\\cdot ",
"|\\cos(\\theta)|s^2"
)
loose_sum.fix_in_frame()
loose_sum.next_to(brace_text, DOWN, LARGE_BUFF)
loose_sum.to_edge(RIGHT)
prob_words = OldTexText("How likely is a given value of $\\theta$?", font_size=36)
prob_words.fix_in_frame()
prob_words.next_to(loose_sum[1], DOWN)
prob_words.to_edge(RIGHT, buff=MED_SMALL_BUFF)
finite_words = Text("If finite...")
finite_words.next_to(brace_text, DOWN, LARGE_BUFF).fix_in_frame()
self.add(finite_words)
face.rotate(-angle_of_vector([*get_normal()[:2], 0]))
face.shift(fc - normal_vect.get_start())
for d_theta in (*[-0.2] * 10, *[0.2] * 10):
face.rotate(d_theta, np.cross(get_normal(), OUT), about_point=fc)
self.wait(0.25)
self.play(
Write(loose_sum.get_part_by_tex("P(\\theta)")),
FadeIn(prob_words, 0.5 * DOWN),
FadeOut(finite_words),
ApplyMethod(frame.set_x, 1, run_time=2)
)
update_theta_ring(theta_ring)
self.add(theta_ring, sphere)
self.play(
Rotate(face, TAU, OUT, about_point=fc, run_time=4),
ShowCreation(theta_ring, run_time=4),
)
theta_ring.add_updater(update_theta_ring)
self.wait()
self.play(
FadeTransform(shadow_area[1].copy(), loose_sum.get_part_by_tex("cos")),
Write(loose_sum.get_part_by_tex("\\cdot")),
FadeOut(prob_words, 0.5 * DOWN)
)
self.wait(2)
self.play(
Write(loose_sum[0], run_time=2),
run_time=3,
)
face.rotate(get_theta(), axis=np.cross(get_normal(), OUT), about_point=fc)
for x in np.arange(0.2, PI, 0.2):
face.rotate(0.2, UP, about_point=fc)
self.wait(0.5)
self.wait(5)
# Continuous
sum_brace = Brace(loose_sum[0], DOWN, buff=SMALL_BUFF)
continuum = OldTexText("Continuum\\\\(uncountably infinite)", font_size=36)
continuum.next_to(sum_brace, DOWN, SMALL_BUFF)
zero = OldTex('0')
zero.next_to(loose_sum[1], DOWN, buff=1.5)
zero.shift(1.5 * RIGHT)
zero_arrow = Arrow(loose_sum[1], zero, buff=SMALL_BUFF)
nonsense_brace = Brace(loose_sum, UP)
nonsense = nonsense_brace.get_text("Not really a sensible expression", font_size=36)
for mob in [sum_brace, continuum, zero, zero_arrow, nonsense_brace, nonsense]:
mob.fix_in_frame()
mob.set_color(YELLOW)
VGroup(nonsense_brace, nonsense).set_color(RED)
face.start_time = self.time
face.clear_updaters()
face.add_updater(lambda f, dt: f.rotate(
angle=0.25 * dt * math.cos(0.1 * (self.time - f.start_time)),
axis=np.cross(get_normal(), OUT),
about_point=fc,
).shift(fc - f[1].get_start()))
self.play(
GrowFromCenter(sum_brace),
FadeIn(continuum, 0.5 * DOWN)
)
self.wait(4)
self.play(
ShowCreation(zero_arrow),
GrowFromPoint(zero, zero_arrow.get_start()),
)
self.wait(2)
inf_sum_group = VGroup(
nonsense_brace, nonsense,
sum_brace, continuum,
zero_arrow, zero,
loose_sum,
)
top_part = inf_sum_group[:2]
top_part.set_opacity(0)
self.play(
inf_sum_group.animate.to_corner(UR),
FadeOut(VGroup(brace, brace_text, shadow_area[1])),
run_time=2,
)
top_part.set_fill(opacity=1)
self.play(
GrowFromCenter(nonsense_brace),
Write(nonsense),
)
self.wait(10)
# Swap for an integral
integral = OldTex(
"\\int_0^\\pi ",
"p(\\theta)",
"\\cdot ",
"|\\cos(\\theta)| s^2",
"d\\theta",
)
integral.shift(loose_sum[-1].get_right() - integral[-1].get_right())
integral.fix_in_frame()
self.play(LaggedStart(*map(FadeOut, inf_sum_group[:-1])))
self.play(
TransformMatchingShapes(
loose_sum[0], integral[0],
fade_transform_mismatches=True,
)
)
self.play(
FadeTransformPieces(loose_sum[1:4], integral[1:4]),
Write(integral[4])
)
self.wait(5)
face.clear_updaters()
self.wait(5)
# Show 2d slice
back_half_sphere = Sphere(u_range=(0, PI))
back_half_sphere.match_color(sphere)
back_half_sphere.set_opacity(sphere.get_opacity())
back_half_sphere.shift(fc)
back_half_mesh = SurfaceMesh(back_half_sphere, resolution=(11, 11))
back_half_mesh.set_stroke(BLUE_D, 1, 0.75)
circle = Circle()
circle.set_stroke(TEAL, 1)
circle.rotate(PI / 2, RIGHT)
circle.move_to(fc)
frame.clear_updaters()
theta_ring.deactivate_depth_test()
theta_ring.uniforms.pop("anti_alias_width")
theta_ring.set_stroke(width=1)
self.play(
FadeOut(sphere),
sphere_mesh.animate.set_stroke(opacity=0.25),
FadeIn(circle),
theta_ring.animate.set_stroke(width=1),
frame.animate.reorient(-6, 87).set_height(4),
integral.animate.set_height(0.5).set_opacity(0).to_corner(UR),
run_time=2,
)
self.remove(integral)
# Finite sample
def get_tick_marks(theta_samples, tl=0.05):
return VGroup(*(
Line((1 - tl / 2) * p, (1 + tl / 2) * p).shift(fc)
for theta in theta_samples
for p in [np.array([math.sin(theta), 0, math.cos(theta)])]
)).set_stroke(YELLOW, 1)
factor = 1
theta_samples = np.linspace(0, PI, factor * sphere_mesh.resolution[0])
dtheta = theta_samples[1] - theta_samples[0]
tick_marks = get_tick_marks(theta_samples)
def set_theta(face, theta):
face.apply_matrix(rotation_between_vectors(
normal_vect.get_vector(), OUT
), about_point=fc)
face.rotate(theta, UP, about_point=fc)
self.play(
ShowIncreasingSubsets(tick_marks[:-1]),
UpdateFromAlphaFunc(
face, lambda f, a: set_theta(face, theta_samples[int(a * (len(theta_samples) - 2))])
),
run_time=4
)
self.add(tick_marks)
self.wait(2)
tsi = factor * 6 # theta sample index
dt_line = Line(tick_marks[tsi].get_center(), tick_marks[tsi + 1].get_center())
dt_brace = Brace(
Line(ORIGIN, RIGHT), UP
)
dt_brace.scale(0.5)
dt_brace.set_width(dt_line.get_length(), stretch=True)
dt_brace.rotate(PI / 2, RIGHT)
dt_brace.rotate(theta_samples[tsi], UP)
dt_brace.move_to(dt_line)
dt_brace.shift(SMALL_BUFF * normalize(dt_line.get_center() - fc))
dt_label = OldTex("\\Delta\\theta", font_size=24)
dt_label.rotate(PI / 2, RIGHT)
dt_label.next_to(dt_brace, OUT + RIGHT, buff=0.05)
self.play(
Write(dt_brace),
Write(dt_label),
run_time=1,
)
sphere.set_opacity(0.1)
self.play(
frame.animate.reorient(10, 70),
Rotate(face, -get_theta() + theta_samples[tsi], UP, about_point=fc),
sphere_mesh.animate.set_stroke(opacity=0.5),
FadeIn(sphere),
run_time=3
)
frame.add_updater(update_frame)
self.wait()
# Latitude band
def get_band(index):
band = Sphere(
u_range=(0, TAU), v_range=theta_samples[index:index + 2],
prefered_creation_axis=1,
)
band.set_color(YELLOW, 0.5)
band.stretch(-1, 2, about_point=ORIGIN)
band.shift(fc)
return band
band = get_band(tsi)
self.add(band, sphere_mesh, sphere)
self.play(
ShowCreation(band),
Rotate(face, dtheta, UP, about_point=fc),
run_time=3,
)
self.play(Rotate(face, -dtheta, UP, about_point=fc), run_time=3)
self.wait(2)
area_question = Text("Area of this band?")
area_question.set_color(YELLOW)
area_question.fix_in_frame()
area_question.set_y(1.75)
area_question.to_edge(RIGHT, buff=2.5)
self.play(Write(area_question))
self.wait()
random_points = [sphere.pfp(random.random()) - fc for x in range(30)]
random_points.append(normal_vect.get_end() - fc)
glow_dots = Group(*(TrueDot(p) for p in random_points))
for dot in glow_dots:
dot.shift(fc)
dot.set_radius(0.2)
dot.set_color(BLUE)
dot.set_glow_factor(2)
theta_ring.suspend_updating()
last_dot = VectorizedPoint()
for dot in glow_dots:
face.apply_matrix(rotation_between_vectors(
get_normal(), dot.get_center() - fc,
), about_point=fc)
self.add(dot)
self.play(FadeOut(last_dot), run_time=0.25)
last_dot = dot
self.play(FadeOut(last_dot))
self.wait()
# Find the area of the band
frame.clear_updaters()
self.play(
frame.animate.reorient(-7.5, 78),
sphere_mesh.animate.set_stroke(opacity=0.2),
band.animate.set_opacity(0.2),
)
one = OldTex("1", font_size=24)
one.rotate(PI / 2, RIGHT)
one.next_to(normal_vect.get_center(), IN + RIGHT, buff=0.05)
radial_line = Line(
[0, 0, normal_vect.get_end()[2]],
normal_vect.get_end()
)
radial_line.set_stroke(BLUE, 2)
r_label = OldTex("r", font_size=20)
sin_label = OldTex("\\sin(\\theta)", font_size=16)
for label in r_label, sin_label:
label.rotate(PI / 2, RIGHT)
label.next_to(radial_line, OUT, buff=0.05)
label.set_color(BLUE)
label.set_backstroke()
self.play(Write(one))
self.wait()
self.play(
TransformFromCopy(normal_vect, radial_line),
FadeTransform(one.copy(), r_label)
)
self.wait()
self.play(FadeTransform(r_label, sin_label))
self.wait()
band_area = OldTex("2\\pi \\sin(\\theta)", "\\Delta\\theta")
band_area.next_to(area_question, DOWN, LARGE_BUFF)
band_area.set_backstroke()
band_area.fix_in_frame()
circ_label, dt_copy = band_area
circ_brace = Brace(circ_label, DOWN, buff=SMALL_BUFF)
circ_words = circ_brace.get_text("Circumference")
approx = OldTex("\\approx")
approx.rotate(PI / 2)
approx.move_to(midpoint(band_area.get_top(), area_question.get_bottom()))
VGroup(circ_brace, circ_words, approx).set_backstroke().fix_in_frame()
self.play(
frame.animate.reorient(10, 60),
)
theta_ring.update()
self.play(
ShowCreation(theta_ring),
Rotate(face, TAU, OUT, about_point=fc),
FadeIn(circ_label, 0.5 * DOWN, rate_func=squish_rate_func(smooth, 0, 0.5)),
GrowFromCenter(circ_brace),
Write(circ_words),
run_time=3,
)
self.wait()
self.play(frame.animate.reorient(-5, 75))
self.play(FadeTransform(area_question[-1], approx))
area_question.remove(area_question[-1])
self.play(Write(dt_copy))
self.wait(3)
# Probability of falling in band
prob = OldTex(
"P(\\text{Vector} \\text{ in } \\text{Band})", "=",
"{2\\pi \\sin(\\theta) \\Delta\\theta", "\\over", " 4\\pi}",
tex_to_color_map={
"\\text{Vector}": GREY_B,
"\\text{Band}": YELLOW,
}
)
prob.fix_in_frame()
prob.to_edge(RIGHT)
prob.set_y(1)
prob.set_backstroke()
numer = prob.get_part_by_tex("\\sin")
numer_rect = SurroundingRectangle(numer, buff=0.05)
numer_rect.set_stroke(YELLOW, 1)
numer_rect.fix_in_frame()
area_question.generate_target()
area_question.target.match_width(numer_rect)
area_question.target.next_to(numer_rect, UP, SMALL_BUFF)
denom_rect = SurroundingRectangle(prob.get_part_by_tex("4\\pi"), buff=0.05)
denom_rect.set_stroke(BLUE, 2)
denom_rect.fix_in_frame()
denom_label = OldTexText("Surface area of\\\\a unit sphere")
denom_label.scale(area_question.target[0].get_height() / denom_label[0][0].get_height())
denom_label.set_color(BLUE)
denom_label.next_to(denom_rect, DOWN, SMALL_BUFF)
denom_label.fix_in_frame()
i = prob.index_of_part_by_tex("sin")
self.play(
FadeTransform(band_area, prob.get_part_by_tex("sin"), remover=True),
MoveToTarget(area_question),
FadeIn(prob[:i]),
FadeIn(prob[i + 1:]),
FadeIn(numer_rect),
*map(FadeOut, [approx, circ_brace, circ_words]),
frame.animate.set_x(1.5),
)
self.add(prob)
self.remove(band_area)
self.wait()
self.play(
ShowCreation(denom_rect),
FadeIn(denom_label, 0.5 * DOWN),
)
sc = sphere.copy().flip(UP).scale(1.01).set_color(BLUE, 0.5)
self.add(sc, sphere_mesh)
self.play(ShowCreation(sc), run_time=3)
self.play(FadeOut(sc))
self.wait()
# Expression for average
sphere_group = Group(
sphere, sphere_mesh, theta_ring, band,
circle, radial_line, sin_label, one, tick_marks,
dt_brace, dt_label,
)
average_eq = OldTex(
"\\text{Average shadow} \\\\",
"\\sum_{\\theta}",
"{2\\pi", "\\sin(\\theta)", " \\Delta\\theta", "\\over", " 4\\pi}",
"\\cdot", "|\\cos(\\theta)|", "s^2"
)
average_eq.fix_in_frame()
average_eq.move_to(prob).to_edge(UP)
average_eq[0].scale(1.25)
average_eq[0].shift(MED_SMALL_BUFF * UP)
average_eq[0].match_x(average_eq[1:])
new_prob = average_eq[2:7]
prob_rect = SurroundingRectangle(new_prob)
prob_rect.set_stroke(YELLOW, 2)
prob_rect.fix_in_frame()
self.play(
FadeIn(average_eq[:1]),
FadeIn(prob_rect),
prob[:5].animate.match_width(prob_rect).next_to(prob_rect, DOWN, buff=0.15),
FadeTransform(prob[-3:], new_prob),
*map(FadeOut, [prob[5], numer_rect, denom_rect, area_question, denom_label])
)
self.wait()
self.play(
FadeOut(sphere_group),
FadeIn(average_eq[-3:]),
UpdateFromAlphaFunc(face, lambda f, a: f[0].set_fill(opacity=0.5 * a))
)
self.wait()
band.set_opacity(0.5)
bands = Group(*(get_band(i) for i in range(len(theta_samples) - 1)))
sphere_mesh.set_stroke(opacity=0.5)
self.add(sphere_mesh, sphere, bands)
self.play(
FadeIn(average_eq[1]),
UpdateFromAlphaFunc(face, lambda f, a: f[0].set_fill(opacity=0.5 * (1 - a))),
FadeIn(sphere),
FadeIn(tick_marks),
FadeIn(sphere_mesh),
LaggedStartMap(
FadeIn, bands,
rate_func=there_and_back,
lag_ratio=0.5,
run_time=8,
remover=True
),
)
# Simplify
average2 = OldTex(
"{2\\pi", "\\over", "4\\pi}", "s^2",
"\\sum_{\\theta}",
"\\sin(\\theta)", "\\Delta\\theta",
"\\cdot", "|\\cos(\\theta)|"
)
average2.fix_in_frame()
average2.move_to(average_eq[1:], RIGHT)
half = OldTex("1 \\over 2")
pre_half = average2[:3]
half.move_to(pre_half, RIGHT)
half_rect = SurroundingRectangle(pre_half, buff=SMALL_BUFF)
half_rect.set_stroke(RED, 1)
VGroup(half, half_rect).fix_in_frame()
self.play(
FadeOut(prob_rect),
FadeOut(prob[:5]),
*(
FadeTransform(average_eq[i], average2[j], path_arc=10 * DEGREES)
for i, j in [
(1, 4),
(2, 0),
(3, 5),
(4, 6),
(5, 1),
(6, 2),
(7, 7),
(8, 8),
(9, 3),
]
),
run_time=2,
)
self.play(ShowCreation(half_rect))
self.play(
FadeTransform(pre_half, half),
FadeOut(half_rect),
)
sin, dt, dot, cos = average2[5:]
tail = VGroup(cos, dot, sin, dt)
tail.generate_target()
tail.target.arrange(RIGHT, buff=SMALL_BUFF)
tail.target.move_to(tail, LEFT)
tail.target[-1].align_to(sin[0], DOWN)
self.play(
MoveToTarget(tail, path_arc=PI / 2),
)
self.wait(2)
integral = OldTex("\\int_0^\\pi ")
integral.next_to(tail, LEFT, SMALL_BUFF)
integral.fix_in_frame()
dtheta = OldTex("d\\theta").fix_in_frame()
dtheta.move_to(tail[-1], LEFT)
average_copy = VGroup(half, average2[3:]).copy()
average_copy.set_backstroke()
self.play(
VGroup(half, average2[3]).animate.next_to(integral, LEFT, SMALL_BUFF),
FadeTransform(average2[4], integral),
FadeTransform(tail[-1], dtheta),
average_copy.animate.shift(2.5 * DOWN),
frame.animate.set_phi(80 * DEGREES),
)
self.wait()
self.play(LaggedStart(
ShowCreationThenFadeOut(SurroundingRectangle(average_copy[1][-3]).fix_in_frame()),
ShowCreationThenFadeOut(SurroundingRectangle(dtheta).fix_in_frame()),
lag_ratio=0.5
))
self.wait()
# The limit
brace = Brace(average_copy, UP, buff=SMALL_BUFF)
brace_text = brace.get_text(
"What does this approach for finer subdivisions?",
font_size=30
)
arrow = Arrow(integral.get_bottom(), brace_text)
VGroup(brace, brace_text, arrow).set_color(YELLOW).fix_in_frame()
brace_text.set_backstroke()
self.play(
GrowFromCenter(brace),
ShowCreation(arrow),
FadeIn(brace_text, lag_ratio=0.1)
)
for n in range(1, 4):
new_ticks = get_tick_marks(
np.linspace(0, PI, sphere_mesh.resolution[0] * 2**n),
tl=0.05 / n
)
self.play(
ShowCreation(new_ticks),
FadeOut(tick_marks),
run_time=2,
)
self.wait()
tick_marks = new_ticks
# Make room for computation
face[0].set_fill(BLUE_D, opacity=0.75)
face[0].set_stroke(WHITE, 0.5, 1)
rect = Rectangle(fill_color=BLACK, fill_opacity=1, stroke_width=0)
rect.replace(self.plane, stretch=True)
rect.stretch(4 / 12, dim=0, about_edge=RIGHT)
rect.scale(1.01)
top_line = VGroup(half, average2[3], integral, tail[:-1], dtheta)
self.add(face[0], sphere)
self.play(
LaggedStart(*map(FadeOut, [arrow, brace_text, brace, average_copy])),
# UpdateFromAlphaFunc(face, lambda f, a: f[0].set_fill(opacity=0.5 * a)),
GrowFromCenter(face[0], remover=True),
frame.animate.set_height(6).set_x(3.5),
FadeIn(rect),
FadeOut(tick_marks),
top_line.animate.set_width(4).to_edge(UP).to_edge(RIGHT, buff=LARGE_BUFF),
FadeOut(average_eq[0], UP),
run_time=2,
)
self.add(face, sphere)
self.begin_ambient_rotation(face, about_point=fc, speed=0.1)
# Computation
new_lines = VGroup(
OldTex("{1 \\over 2} s^2 \\cdot 2 \\int_0^{\\pi / 2} \\cos(\\theta)\\sin(\\theta)\\,d\\theta"),
OldTex("{1 \\over 2} s^2 \\cdot \\int_0^{\\pi / 2} \\sin(2\\theta)\\,d\\theta"),
OldTex("{1 \\over 2} s^2 \\cdot \\left[ -\\frac{1}{2} \\cos(2\\theta) \\right]_0^{\\pi / 2}"),
OldTex("{1 \\over 2} s^2 \\cdot \\left(-\\left(-\\frac{1}{2}\\right) - \\left(-\\frac{1}{2}\\right)\\right)"),
OldTex("{1 \\over 2} s^2"),
)
new_lines.scale(top_line.get_height() / new_lines[0].get_height())
kw = {"buff": 0.35, "aligned_edge": LEFT}
new_lines.arrange(DOWN, **kw)
new_lines.next_to(top_line, DOWN, **kw)
new_lines.fix_in_frame()
annotations = VGroup(
OldTexText("To avoid the annoying absolute value, just\\\\cover the northern hemisphere and double it."),
OldTexText("Trig identity: $\\sin(2\\theta) = 2\\cos(\\theta)\\sin(\\theta)$"),
OldTexText("Antiderivative"),
OldTexText("Try not to get lost in\\\\the sea of negatives..."),
OldTexText("Whoa, that turned out nice!"),
)
annotations.fix_in_frame()
annotations.set_color(YELLOW)
annotations.scale(0.5)
rect = SurroundingRectangle(new_lines[-1], buff=SMALL_BUFF)
rect.set_stroke(YELLOW, 2).fix_in_frame()
for note, line in zip(annotations, new_lines):
note.next_to(line, LEFT, MED_LARGE_BUFF)
self.play(
LaggedStartMap(FadeIn, new_lines, lag_ratio=0.7),
LaggedStartMap(FadeIn, annotations, lag_ratio=0.7),
run_time=5,
)
self.wait(20)
self.play(
new_lines[:-1].animate.set_opacity(0.5),
annotations[:-1].animate.set_opacity(0.5),
ShowCreation(rect),
)
self.wait(10)
def get_solid(self):
face = Square(side_length=2)
face.set_fill(BLUE, 0.5)
face.set_stroke(width=0)
normal = Vector(OUT)
normal.shift(2e-2 * OUT)
face = VGroup(face, normal)
face.set_stroke(background=True)
face.apply_depth_test()
return face
class AskAboutAverageCosValue(AllPossibleOrientations):
def construct(self):
self.remove(self.solid)
self.remove(self.shadow)
frame = self.camera.frame
frame.set_height(5)
frame.reorient(-5, 80)
frame.shift(2 * RIGHT)
self.init_frame_rotation()
# Copy pasting from above...not great
fc = 3 * OUT
sphere = Sphere(radius=1)
sphere.set_color(GREY_E, 0.25)
sphere.move_to(fc)
sphere.always_sort_to_camera(self.camera)
sphere_mesh = SurfaceMesh(sphere, resolution=(21, 11))
sphere_mesh.set_stroke(BLUE_E, 1, 0.5)
for sm in sphere_mesh.get_family():
sm.uniforms["anti_alias_width"] = 0
self.add(sphere, sphere_mesh)
normal_vect = Arrow(sphere.get_center(), sphere.pfp(0.2), buff=0)
def randomly_place_vect():
theta = random.uniform(0.1, PI - 0.1)
phi = random.uniform(-PI / 4, PI / 4) + random.choice([0, PI])
point = fc + np.array([
math.sin(theta) * math.cos(phi),
math.sin(theta) * math.sin(phi),
math.cos(theta),
])
normal_vect.put_start_and_end_on(sphere.get_center(), point)
def get_normal():
return normal_vect.get_vector()
def get_theta():
return np.arccos(get_normal()[2] / get_norm(get_normal()))
def get_arc():
result = Arc(PI / 2, -get_theta(), radius=0.25)
result.rotate(PI / 2, RIGHT, about_point=ORIGIN)
result.rotate(angle_of_vector([*get_normal()[:2], 0]), OUT, about_point=ORIGIN)
result.shift(fc)
result.set_stroke(WHITE, 1)
result.apply_depth_test()
return result
arc = always_redraw(get_arc)
theta = OldTex("\\theta", font_size=20)
theta.rotate(PI / 2, RIGHT)
theta.set_backstroke(width=2)
theta.add_updater(lambda m: m.next_to(
arc.pfp(0.5), arc.pfp(0.5) - fc, buff=0.05)
)
z_axis = Line(ORIGIN, 10 * OUT)
z_axis.set_stroke(WHITE, 1)
z_axis.apply_depth_test()
self.add(z_axis, normal_vect, arc, theta)
self.add(sphere_mesh, sphere)
# Show random samples
question = OldTexText("What's the mean?")
question.to_corner(UR)
question.to_edge(UP, buff=MED_SMALL_BUFF)
question.fix_in_frame()
arrow = Arrow(question, question.get_center() + DOWN)
arrow.fix_in_frame()
values = VGroup()
lhss = VGroup()
self.add(question, arrow)
for n in range(15):
randomly_place_vect()
lhs = OldTex("|\\cos(\\theta_{" + str(n + 1) + "})| = ", font_size=30)
value = DecimalNumber(abs(math.cos(get_theta())), font_size=30)
value.next_to(values, DOWN)
for mob in lhs, value:
mob.fix_in_frame()
mob.set_backstroke()
values.add(value)
values.next_to(arrow, DOWN)
lhs.next_to(value, LEFT, buff=SMALL_BUFF)
lhss.add(lhs)
self.add(values, lhss)
self.wait(0.5)
class ThreeCamps(TeacherStudentsScene):
def construct(self):
self.remove(self.background)
# Setup
teacher = self.teacher
students = self.students
image = ImageMobject("Shadows_Integral_Intro")
image.center().set_height(FRAME_HEIGHT)
image.generate_target()
image.target.replace(self.screen)
self.screen.set_stroke(WHITE, 1)
self.screen.save_state()
self.screen.replace(image).set_stroke(width=0)
self.play(
LaggedStart(*(
student.change("pondering", image.target)
for student in students
), run_time=2, lag_ratio=0.2),
teacher.change("tease")
)
self.wait()
# Reactions
phrases = [
Text("How fun!", font_size=40),
Text("Wait, what?", font_size=40),
Text("Okay give\nme a sec...", font_size=35),
]
modes = ["hooray", "erm", "confused"]
heights = np.linspace(2.0, 2.5, 3)
for student, phrase, mode, height in zip(reversed(students), phrases, modes, heights):
self.play(
PiCreatureSays(
student, phrase, target_mode=mode,
look_at=image,
bubble_config={
"direction": LEFT,
"width": 3,
"height": height,
},
bubble_type=ThoughtBubble,
run_time=2
)
)
self.wait(4)
# Let's go over the definition
integral = OldTex("\\int_0^\\pi \\dots d\\theta")
integral.move_to(self.hold_up_spot, DOWN)
brace = Brace(integral, UP)
words = OldTexText("Let's go over the definition", font_size=36)
words.next_to(brace, UP, SMALL_BUFF)
words2 = OldTexText("It can't hurt, right?", font_size=36)
words2.move_to(words)
VGroup(brace, words, words2).set_color(YELLOW)
self.play(
LaggedStart(*(
FadeOut(VGroup(student.bubble, student.bubble.content))
for student in reversed(students)
)),
LaggedStart(*(
student.change("pondering", integral)
for student in students
)),
FadeIn(integral, UP),
teacher.change("raise_right_hand", integral),
)
self.play(
GrowFromCenter(brace),
Write(words)
)
self.wait(2)
self.play(
words.animate.shift(0.75 * UP).set_opacity(0.5),
FadeIn(words2, 0.2 * UP),
LaggedStart(
self.teacher.change("shruggie"),
self.students[0].change("sassy", words2),
self.students[1].change("thinking", words2),
self.students[2].change("well", words2),
)
)
self.wait(2)
self.play(self.teacher.change("speaking", words2))
self.wait(3)
class ParticularValuesUnhelpfulOverlay(Scene):
def construct(self):
# Particular value
expr = OldTex("P(\\theta =", "\\pi / 4", ")", "=", "0")
expr.set_color_by_tex("\\pi / 4", YELLOW)
brace = Brace(expr.get_part_by_tex("\\pi / 4"), UP, buff=SMALL_BUFF)
brace.stretch(0.5, 1, about_edge=DOWN)
words = Text("Some specific value", font_size=24)
words.next_to(brace, UP, SMALL_BUFF)
VGroup(brace, words).set_color(YELLOW)
VGroup(expr, brace, words).to_corner(UR)
self.play(FadeIn(expr, lag_ratio=1))
self.play(
GrowFromCenter(brace),
FadeIn(words, shift=0.2 * UP),
)
self.wait()
# Unhelpful
question = OldTexText("What are you going\\\\to do with that?", font_size=24)
question.next_to(expr, DOWN, LARGE_BUFF)
arrow = Arrow(question, expr.get_part_by_tex("0"), buff=SMALL_BUFF)
self.play(
FadeIn(question),
ShowCreation(arrow),
)
self.wait()
# New expr
range_expr = OldTex(
"P(\\pi / 4 < \\theta < \\pi / 4 + \\Delta\\theta) > 0",
tex_to_color_map={
"\\pi / 4": YELLOW,
"\\Delta\\theta": GREY_A,
},
font_size=40
)
range_expr.move_to(expr, RIGHT)
new_brace = Brace(range_expr.slice_by_tex("\\pi / 4", ")"), UP, buff=SMALL_BUFF)
new_words = Text("Range of values", font_size=24)
new_words.next_to(new_brace, UP, SMALL_BUFF)
VGroup(new_brace, new_words).set_color(YELLOW)
self.play(
FadeOut(question),
FadeOut(arrow),
TransformMatchingShapes(expr, range_expr),
FadeTransform(brace, new_brace),
FadeTransform(words, new_words),
)
self.wait()
class SurfaceAreaOfSphere(Scene):
def construct(self):
sphere = Sphere(radius=3)
sphere.set_color(BLUE_E, 1)
sphere.always_sort_to_camera(self.camera)
sphere.rotate(5 * DEGREES, OUT)
sphere.rotate(80 * DEGREES, LEFT)
sphere.move_to(0.5 * DOWN)
sphere_mesh = SurfaceMesh(sphere)
sphere_mesh.set_stroke(WHITE, 0.5, 0.5)
equation = OldTex(
"\\text{Surface area} = 4\\pi R^2",
tex_to_color_map={
"R": YELLOW,
"\\text{Surface area}": BLUE,
},
)
equation.to_edge(UP)
self.add(equation, sphere, sphere_mesh)
self.play(
Write(sphere_mesh, lag_ratio=0.02, stroke_width=1),
ShowCreation(sphere, rate_func=squish_rate_func(smooth, 0.25, 1)),
run_time=5,
)
self.wait()
class IntegralOverlay(Scene):
def construct(self):
integral = OldTex("\\int_0^\\pi")
integral.set_color(YELLOW)
self.play(Write(integral, run_time=2))
self.play(integral.animate.set_color(WHITE))
self.play(LaggedStart(*(
FlashAround(sm, time_width=3)
for sm in integral[0][:0:-1]
), lag_ratio=0.5, run_time=3))
self.wait()
class AlicesInsights(Scene):
def construct(self):
title = OldTexText("Alice's insights", font_size=72)
title.to_edge(UP)
title.set_backstroke()
underline = Underline(title, buff=-0.05)
underline.scale(1.5)
underline.insert_n_curves(50)
underline.set_stroke(WHITE, width=[0, *4 * [3], 0], opacity=1)
self.add(underline, title)
kw = dict(
t2c={
"double cover": YELLOW,
"mean": RED,
"means": RED,
"sum": BLUE,
"Sum": BLUE,
"Average": RED,
}
)
insights = VGroup(
Text("1. The face shadows double cover the cube shadow", **kw),
# Text("2. The mean of the sum is the sum of the means", **kw),
Text("2. Average(Sum(Face shadows)) = Sum(Average(Face shadow))", **kw),
Text("3. Use a sphere to deduce the unknown constant", **kw),
)
insights.arrange(DOWN, buff=LARGE_BUFF, aligned_edge=LEFT)
insights.next_to(underline, DOWN, LARGE_BUFF)
insights.to_edge(LEFT)
self.play(LaggedStart(*(
FadeIn(insight[:2], 0.25 * DOWN)
for insight in insights
)))
self.wait()
for insight in insights:
self.play(FadeIn(insight[2:], lag_ratio=0.1))
self.wait()
class HalfBathedInLight(ShadowScene):
def construct(self):
frame = self.camera.frame
frame.set_height(12)
frame.add_updater(lambda m, dt: m.increment_theta(0.05 * dt))
cube = self.solid
light = self.light
light.next_to(cube, OUT, 2)
self.add(light)
self.remove(self.plane)
self.shadow.add_updater(lambda m: m.set_opacity(0))
cube.move_to(OUT)
cube.set_opacity(0.95)
cube.rotate(PI / 2, DL)
# cube.add_updater(lambda m: self.sort_to_camera(cube))
cube.update()
cube.clear_updaters()
light_lines = self.get_light_lines()
light_lines.add_updater(lambda m: m.set_stroke(YELLOW, 2))
self.add(light_lines, light)
self.wait(2)
for s, color in zip([slice(3, None), slice(0, 3)], [WHITE, GREY_D]):
cube.generate_target()
sorted_cube = Group(*cube.target)
sorted_cube.sort(lambda p: p[2])
sorted_cube[s].space_out_submobjects(2)
sorted_cube[s].set_color(color)
self.play(
MoveToTarget(cube),
rate_func=there_and_back_with_pause,
run_time=3,
)
self.wait(5)
# Embed
self.embed()
class TwoToOneCover(ShadowScene):
inf_light = True
plane_dims = (20, 12)
limited_plane_extension = 8
highlighted_face_color = YELLOW
def construct(self):
# Setup
frame = self.camera.frame
frame.reorient(-20, 75)
frame.set_z(3)
self.init_frame_rotation()
cube = self.solid
for face in cube:
face.set_fill(opacity=0.9)
face.set_reflectiveness(0.1)
face.set_gloss(0.2)
cube.add_updater(lambda m: self.sort_to_camera(m))
cube.rotate(PI / 3, normalize([3, 4, 5]))
shadow = self.shadow
outline = self.get_shadow_outline()
# Inequality
ineq = self.get_top_expression("$<$")
ineq.fix_in_frame()
lhs = ineq.slice_by_tex(None, "<")
lt = ineq.get_part_by_tex("<")
rhs = ineq.slice_by_tex("sum", None)
af_label = ineq[-7:]
lhs.save_state()
lhs.set_x(0)
# Shadow of the cube
wireframe = cube.copy()
for face in wireframe:
face.set_fill(opacity=0)
face.set_stroke(WHITE, 1)
wireframe_shadow = wireframe.copy()
wireframe_shadow.apply_function(flat_project)
wireframe_shadow.set_gloss(0)
wireframe_shadow.set_reflectiveness(0)
wireframe_shadow.set_shadow(0)
for face in wireframe_shadow:
face.set_stroke(GREY_D, 1)
self.play(
ShowCreation(wireframe, lag_ratio=0.1),
Write(lhs[2:-1])
)
self.play(TransformFromCopy(wireframe, wireframe_shadow))
self.play(*map(FadeOut, (wireframe, wireframe_shadow)))
self.wait()
self.play(
FadeIn(lhs[:2]), FadeIn(lhs[-1]),
Write(outline),
VShowPassingFlash(
outline.copy().set_stroke(YELLOW, 4),
time_width=1.5
),
)
self.wait()
# Show faces and shadows
cube.save_state()
faces, face_shadows = self.get_faces_and_face_shadows()
faces[:3].set_opacity(0.1)
face_shadow_lines = VGroup(*(
VGroup(*(
Line(v1, v2)
for v1, v2 in zip(f.get_vertices(), fs.get_vertices())
))
for f, fs in zip(faces, face_shadows)
))
face_shadow_lines.set_stroke(YELLOW, 0.5, 0.5)
self.play(
Restore(lhs),
FadeIn(af_label, shift=0.5 * RIGHT)
)
self.play(
*(
LaggedStart(*(
VFadeInThenOut(sm)
for sm in reversed(mobject)
), lag_ratio=0.5, run_time=6)
for mobject in [faces, face_shadows, face_shadow_lines]
),
)
self.play(
ApplyMethod(cube.space_out_submobjects, 1.7, rate_func=there_and_back_with_pause, run_time=8),
ApplyMethod(frame.reorient, 20, run_time=8),
Write(lt),
Write(rhs[0]),
)
self.wait(2)
# Show a given pair of faces
face_pair = Group(faces[3], faces[5]).copy()
face_pair[1].set_color(RED)
face_pair.save_state()
fp_shadow = get_shadow(face_pair)
self.add(fp_shadow)
self.play(
FadeOut(cube),
*map(VFadeOut, shadow),
FadeOut(outline),
*map(Write, face_pair),
)
self.wait(1)
self.play(Rotate(
face_pair, PI / 2, DOWN,
about_point=cube.get_center(),
run_time=3,
))
self.wait()
self.random_toss(face_pair, about_point=cube.get_center(), run_time=6)
fp_shadow.clear_updaters()
self.play(
FadeIn(cube),
*map(VFadeIn, shadow),
FadeOut(face_pair, scale=0),
FadeOut(fp_shadow, scale=0),
)
self.add(shadow)
# Half of sum
new_expression = self.get_top_expression(" = ", "$\\displaystyle \\frac{1}{2}$")
new_expression.fix_in_frame()
eq_half = VGroup(
new_expression.get_part_by_tex("="),
new_expression.get_part_by_tex("frac"),
)
cube.save_state()
cube.generate_target(use_deepcopy=True)
cube.target.clear_updaters()
z_sorted_faces = Group(*sorted(list(cube.target), key=lambda f: f.get_z()))
z_sorted_faces[:3].shift(2 * LEFT)
z_sorted_faces[3:].shift(2 * RIGHT)
cube.clear_updaters()
self.play(
MoveToTarget(cube),
ApplyMethod(frame.reorient, 0, run_time=2)
)
self.play(
FadeOut(lt, UP),
Write(eq_half),
ReplacementTransform(rhs, new_expression[-len(rhs):]),
ReplacementTransform(lhs, new_expression[:len(lhs)]),
)
self.remove(ineq)
self.add(new_expression)
self.wait(2)
anims = []
for part in z_sorted_faces:
pc = part.copy()
pc.set_fill(YELLOW, 0.25)
pc_shadow = get_shadow(pc)
pc_shadow.clear_updaters()
pc_shadow.match_style(pc)
lines = VGroup(*(
Line(v, flat_project(v))
for v in pc.get_vertices()
))
lines.set_stroke(YELLOW, 1, 0.1)
anims.append(AnimationGroup(
VFadeInThenOut(pc),
VFadeInThenOut(pc_shadow),
VFadeInThenOut(lines),
))
self.play(LaggedStart(*anims, lag_ratio=0.4, run_time=6))
self.play(Restore(cube))
# Show the double cover
shadow_point = shadow.get_bottom() + [0.5, 0.75, 0]
dot = GlowDot(shadow_point)
line = DashedLine(
shadow_point + 5 * OUT, shadow_point,
dash_length=0.025
)
line.set_stroke(YELLOW, 1)
def update_line(line):
line.move_to(dot.get_center(), IN)
for dash in line:
dist = cube_sdf(dash.get_center(), cube)
dash.set_stroke(
opacity=interpolate(0.1, 1.0, clip(10 * dist, -0.5, 0.5) + 0.5)
)
dash.inside = (dist < 0)
line.add_updater(update_line)
entry_point = next(dash for dash in line if dash.inside).get_center()
exit_point = next(dash for dash in reversed(line) if dash.inside).get_center()
arrows = VGroup(*(
Arrow(point + RIGHT, point, buff=0.1)
for point in (entry_point, exit_point)
))
self.play(ShowCreation(line, rate_func=rush_into))
self.play(FadeIn(dot, scale=10, rate_func=rush_from, run_time=0.5))
self.wait()
self.play(
Rotate(
dot, TAU,
about_point=ORIGIN,
run_time=6,
),
)
self.wait()
for arrow in arrows:
self.play(ShowCreation(arrow))
self.wait()
self.play(FadeOut(arrows))
cube.add_updater(lambda m: self.sort_to_camera(m))
self.random_toss(cube, angle=1.5 * TAU, run_time=8)
# Just show wireframe
cube.save_state()
cube.generate_target()
for sm in cube.target:
sm.set_fill(opacity=0)
sm.set_stroke(WHITE, 2)
outline = self.get_shadow_outline()
outline.rotate(PI)
self.play(
MoveToTarget(cube),
dot.animate.move_to(outline.get_start())
)
self.play(MoveAlongPath(dot, outline, run_time=8))
self.wait(2)
self.play(Restore(cube))
self.play(dot.animate.move_to(outline.get_center()), run_time=2)
# Make room for equation animations
# Start here for new scene
area_label = self.get_shadow_area_label()
area_label.shift(1.75 * DOWN + 2.25 * RIGHT)
area_label[0].scale(0, about_edge=RIGHT)
area_label.scale(0.7)
outline.update()
line.clear_updaters()
self.play(
FadeOut(dot),
FadeOut(line),
ShowCreation(outline),
VFadeIn(area_label),
)
# Single out a face
self.remove(new_expression)
self.wait(2)
face = cube[np.argmax([f.get_z() for f in cube])].copy()
face.set_color(YELLOW)
face_shadow = get_shadow(face)
face_shadow_area = DecimalNumber(get_norm(face_shadow.get_area_vector()) / (self.unit_size**2))
face_shadow_area.scale(0.65)
face_shadow_area.move_to(area_label)
face_shadow_area.shift(flat_project(face.get_center() - cube.get_center()))
face_shadow_area.shift(SMALL_BUFF * UR)
face_shadow_area.fix_in_frame()
cube.save_state()
self.remove(cube)
self.play(
*(
f.animate.set_fill(opacity=0)
for f in cube
),
FadeOut(outline),
FadeOut(area_label),
Write(face),
FadeIn(face_shadow),
FadeIn(face_shadow_area),
)
self.wait(2)
self.play(
Restore(cube),
*map(FadeOut, (face, face_shadow, face_shadow_area)),
*map(FadeIn, (outline, area_label)),
)
# Show simple rotations
for x in range(2):
self.random_toss(cube)
self.wait()
self.wait()
# Many random orientations
for x in range(40):
self.randomly_reorient(cube)
self.wait(0.25)
# Show sum of faces again (play 78)
self.random_toss(cube, 2 * TAU, run_time=8, meta_speed=10)
self.wait()
cube.save_state()
sff = 1.5
self.play(
VFadeOut(outline),
VFadeOut(area_label),
cube.animate.space_out_submobjects(sff)
)
for x in range(3):
self.random_toss(cube, angle=PI)
self.wait()
self.play(cube.animate.space_out_submobjects(1 / sff))
# Mean shadow of a single face
cube_style = cube[0].get_style()
def isolate_face_anims(i, color=YELLOW):
return (
shadow.animate.set_fill(
interpolate_color(color, BLACK, 0.75)
),
*(
f.animate.set_fill(
color if f is cube[i] else BLUE,
0.75 if f is cube[i] else 0,
)
for f in cube
)
)
def tour_orientations():
self.random_toss(cube, 2 * TAU, run_time=5, meta_speed=10)
self.play(*isolate_face_anims(5))
tour_orientations()
for i, color in ((4, GREEN), (3, RED)):
self.play(
*isolate_face_anims(i, color),
)
tour_orientations()
cube.update()
self.play(
*(
f.animate.set_style(**cube_style)
for f in cube
),
shadow.animate.set_fill(interpolate_color(BLUE, BLACK, 0.85)),
VFadeIn(outline),
VFadeIn(area_label),
)
self.add(cube)
# Ambient rotation
self.add(cube)
self.begin_ambient_rotation(cube, speed=0.4)
self.wait(20)
def get_top_expression(self, *mid_tex, n_faces=6):
t2c = {
"Shadow": GREY_B,
"Cube": BLUE_D,
"Face$_j$": YELLOW,
}
ineq = OldTexText(
"Area(Shadow(Cube))",
*mid_tex,
" $\\displaystyle \\sum_{j=1}^" + f"{{{n_faces}}}" + "$ ",
"Area(Shadow(Face$_j$))",
tex_to_color_map=t2c,
isolate=["(", ")"],
)
ineq.to_edge(UP, buff=MED_SMALL_BUFF)
return ineq
def get_faces_and_face_shadows(self):
faces = self.solid.deepcopy()
VGroup(*faces).set_fill(self.highlighted_face_color)
shadows = get_pre_shadow(faces, opacity=0.7)
shadows.apply_function(flat_project)
return faces, shadows
class ConvexityPrelude(Scene):
def construct(self):
square = Square(side_length=3)
square.rotate(-PI / 4)
square.flip()
square.set_stroke(BLUE, 2)
points = [square.pfp(1 / 8), square.pfp(7 / 8)]
beam = VGroup(
Line(points[0] + 3 * UP, points[0], stroke_width=3),
Line(points[0], points[1], stroke_width=2),
Line(points[1], points[1] + 3 * DOWN, stroke_width=1),
)
beam.set_stroke(YELLOW)
words = OldTexText("2 intersections\\\\", "(almost always)")
words[1].scale(0.7, about_edge=UP).set_color(GREY_B)
words.to_edge(LEFT)
arrows = VGroup(*(
Arrow(words[0].get_right(), point)
for point in points
))
dots = GlowDot()
dots.set_points(points)
dots.set_color(WHITE)
dots.set_radius(0.2)
dots.set_glow_factor(3)
self.add(square)
self.add(words)
self.play(
ShowCreation(beam),
FadeIn(arrows, lag_ratio=0.7),
FadeIn(dots, lag_ratio=0.7),
)
self.wait()
question1 = Text("Why is this true?")
question2 = Text("Is this true for all shapes?")
question2.next_to(question1, DOWN, aligned_edge=LEFT)
VGroup(question1, question2).to_corner(UR)
self.play(Write(question1, run_time=1))
self.wait()
self.play(FadeIn(question2, 0.25 * DOWN))
self.wait()
# Convexity
square.generate_target()
convex_shapes = VGroup(
square.target,
RegularPolygon(5, color=TEAL),
Rectangle(2, 1, color=TEAL_E),
RegularPolygon(6, color=GREEN),
Circle(color=GREEN_B),
)
convex_shapes[2].apply_matrix([[1, 0.5], [0, 1]])
convex_shapes[3].shift(2 * RIGHT).apply_complex_function(np.exp)
convex_shapes[3].make_jagged()
for shape in convex_shapes:
shape.set_height(1)
shape.set_stroke(width=2)
convex_shapes.arrange(DOWN)
convex_shapes.set_height(6)
v_line = Line(UP, DOWN).set_height(FRAME_HEIGHT)
h_line = Line(LEFT, RIGHT).set_width(FRAME_WIDTH)
h_line.set_y(3)
VGroup(v_line, h_line).set_stroke(WHITE, 2)
convex_title = Text("Convex")
non_convex_title = Text("Non-convex")
for title, vect in zip([convex_title, non_convex_title], [LEFT, RIGHT]):
title.scale(1.5)
title.next_to(h_line, UP)
title.shift(vect * FRAME_WIDTH / 4)
convex_shapes.next_to(h_line, DOWN)
convex_shapes.match_x(convex_title)
self.play(
MoveToTarget(square),
FadeIn(convex_shapes[1:], lag_ratio=0.5),
FadeTransform(beam, v_line),
ShowCreation(h_line),
FadeIn(convex_title),
LaggedStart(*map(FadeOut, (
dots, arrows, words,
question1, question2,
))),
)
self.wait()
# Non-convex shapes
pi = OldTex("\\pi").family_members_with_points()[0]
pent = RegularPolygon(5)
pent.set_points_as_corners([ORIGIN, *pent.get_vertices()[1:], ORIGIN])
n_mob = OldTex("N").family_members_with_points()[0]
nc_shapes = VGroup(pi, pent, n_mob)
nc_shapes.set_fill(opacity=0)
nc_shapes.set_stroke(width=2)
nc_shapes.set_submobject_colors_by_gradient(RED, PINK)
for shape in nc_shapes:
shape.set_height(1)
nc_shapes.arrange(DOWN)
nc_shapes.replace(convex_shapes, dim_to_match=1)
nc_shapes.match_x(non_convex_title)
self.play(
Write(non_convex_title, run_time=1),
LaggedStartMap(FadeIn, nc_shapes),
)
self.wait()
# Embed
self.embed()
class DefineConvexity(Scene):
def construct(self):
# Shape
definition = "A set is convex if the line connecting any\n"\
"two points within it is contained in the set"
set_color = BLUE
line_color = GREEN
title = Text(
definition,
t2c={
"convex": set_color,
"line connecting any\ntwo points": line_color,
},
t2s={"convex": ITALIC},
)
title.to_edge(UP)
title.set_opacity(0.2)
shape = Square(4.5)
shape.set_fill(set_color, 0.25)
shape.set_stroke(set_color, 2)
shape.next_to(title, DOWN, LARGE_BUFF)
self.add(title)
self.play(
title.get_part_by_text("A set is convex").animate.set_opacity(1),
Write(shape)
)
self.wait()
# Show two points
line = Line(shape.pfp(0.1), shape.pfp(0.5))
line.scale(0.7)
line.set_stroke(line_color, 2)
dots = DotCloud(line.get_start_and_end())
dots.set_color(line_color)
dots.make_3d(0.5)
dots.save_state()
dots.set_radius(0)
dots.set_opacity(0)
self.play(
title[definition.index("if"):definition.index("is", 16)].animate.set_opacity(1),
Restore(dots),
)
self.play(ShowCreation(line))
self.wait()
self.play(
title[definition.index("is", 16):].animate.set_opacity(1),
)
# Alternate places
dots.add_updater(lambda m: m.set_points(line.get_start_and_end()))
def show_sample_lines(n=5):
for x in range(5):
self.play(
line.animate.put_start_and_end_on(
shape.pfp(random.random()),
shape.pfp(random.random()),
).scale(random.random(), about_point=shape.get_center())
)
self.wait(0.5)
show_sample_lines()
# Letter π
def tex_to_shape(tex):
result = OldTex(tex).family_members_with_points()[0]
result.match_style(shape)
result.match_height(shape)
result.move_to(shape)
result_points = result.get_points().copy()
index = np.argmax([np.dot(p, UR) for p in result_points])
index = 3 * (index // 3)
result.set_points([*result_points[index:], *result_points[:index]])
return result
pi = tex_to_shape("\\pi")
letter_c = tex_to_shape("\\textbf{C}")
letter_c.insert_n_curves(50)
line.generate_target()
line.target.put_start_and_end_on(*(
pi.get_boundary_point(v) for v in (DL, DR)
))
line.target.scale(0.9)
not_convex_label = Text("Not convex!", color=RED)
not_convex_label.next_to(pi, LEFT)
shape.insert_n_curves(100)
self.play(
Transform(shape, pi, path_arc=PI / 2),
MoveToTarget(line),
run_time=2,
)
self.play(
FadeIn(not_convex_label, scale=2)
)
self.wait()
shape.insert_n_curves(80)
self.play(
Transform(shape, letter_c),
line.animate.put_start_and_end_on(*(
letter_c.pfp(a) for a in (0, 0.5)
)),
run_time=2,
)
self.play(UpdateFromAlphaFunc(
line,
lambda l, a: l.put_start_and_end_on(
letter_c.pfp(0.4 * a), l.get_end()
),
run_time=6,
))
self.wait()
# Polygon
convex_label = OldTexText("Convex \\ding{51}")
convex_label.set_color(YELLOW)
convex_label.move_to(not_convex_label)
polygon = RegularPolygon(7)
polygon.match_height(shape)
polygon.move_to(shape)
polygon.match_style(shape)
self.remove(not_convex_label)
self.play(
FadeTransform(shape, polygon),
line.animate.put_start_and_end_on(
polygon.get_vertices()[1],
polygon.get_vertices()[-1],
),
TransformMatchingShapes(not_convex_label.copy(), convex_label),
run_time=2
)
self.wait()
polygon.generate_target()
new_tip = 2 * line.get_center() - polygon.get_start()
polygon.target.set_points_as_corners([
new_tip, *polygon.get_vertices()[1:], new_tip
])
self.play(
MoveToTarget(polygon),
TransformMatchingShapes(convex_label, not_convex_label),
)
self.wait()
# Show light beam
beam = DashedLine(ORIGIN, FRAME_WIDTH * RIGHT)
beam.set_stroke(YELLOW, 1)
beam.next_to(line, DOWN, MED_SMALL_BUFF)
flash_line = Line(beam.get_start(), beam.get_end())
flash_line.set_stroke(YELLOW, 5)
flash_line.insert_n_curves(100)
self.play(
ShowCreation(beam),
VShowPassingFlash(flash_line),
run_time=1,
)
self.wait()
class NonConvexDoughnut(ShadowScene):
def construct(self):
# Setup
frame = self.camera.frame
self.remove(self.solid, self.shadow)
torus = Torus()
torus.set_width(4)
torus.set_z(3)
torus.set_color(BLUE_D)
torus.set_opacity(0.7)
torus.set_reflectiveness(0)
torus.set_shadow(0.5)
torus.always_sort_to_camera(self.camera)
shadow = get_shadow(torus)
shadow.always_sort_to_camera(self.camera)
self.add(torus)
self.add(shadow)
self.play(ShowCreation(torus), run_time=2)
self.play(Rotate(torus, PI / 2, LEFT), run_time=2)
self.wait()
# Light beam
dot = GlowDot(0.25 * LEFT)
line = DashedLine(dot.get_center(), dot.get_center() + 10 * OUT)
line.set_stroke(YELLOW, 1)
line_shadow = line.copy().set_stroke(opacity=0.1)
self.add(line, torus, line_shadow)
self.play(
FadeIn(dot),
ShowCreation(line),
ShowCreation(line_shadow),
ApplyMethod(frame.reorient, 20, run_time=7)
)
self.wait()
class ShowGridSum(TwoToOneCover):
def construct(self):
# Setup
self.clear()
shape_name = "Cube"
n_faces = 6
# shape_name = "Dodec."
# n_faces = 12
frame = self.camera.frame
frame.reorient(0, 0)
frame.move_to(ORIGIN)
equation = self.get_top_expression(" = ", "$\\displaystyle \\frac{1}{2}$", n_faces=n_faces)
self.add(equation)
lhs = equation.slice_by_tex(None, "=")
summand = equation.slice_by_tex("sum", None)[1:]
# Abbreviate
t2c = {
f"\\text{{{shape_name}}}": BLUE,
"\\text{F}_j": YELLOW,
"\\text{Face}": YELLOW,
"\\text{Total}": GREEN,
"S": GREY_B,
"{c}": RED,
}
kw = {
"tex_to_color_map": t2c
}
lhs.alt1 = OldTex(f"S(\\text{{{shape_name}}})", **kw)
summand.alt1 = OldTex("S(\\text{F}_j)", **kw)
def get_s_cube_term(i="i"):
return OldTex(f"S\\big(R_{{{i}}}", f"(\\text{{{shape_name}}})\\big)", **kw)
def get_s_f_term(i="i", j="j"):
result = OldTex(
f"S\\big(R_{{{i}}}",
"(", "\\text{F}_{" + str(j) + "}", ")",
"\\big)",
**kw
)
result[3].set_color(YELLOW)
return result
lhs.alt2 = get_s_cube_term(1)
summand.alt2 = get_s_f_term(1)
for mob, vect in (lhs, RIGHT), (summand, LEFT):
mob.brace = Brace(mob, DOWN, buff=SMALL_BUFF)
mob.alt1.next_to(mob.brace, DOWN)
self.play(
GrowFromCenter(mob.brace),
FadeIn(mob.alt1, shift=0.5 * DOWN),
)
self.wait()
self.play(
FadeOut(mob.brace, scale=0.5),
FadeOut(mob, shift=0.5 * UP),
mob.alt1.animate.move_to(mob, vect),
)
mob.alt2.move_to(mob, vect)
self.wait()
for mob in lhs, summand:
self.play(TransformMatchingShapes(mob.alt1, mob.alt2))
self.wait()
# Add up many rotations
lhss = VGroup(
get_s_cube_term(1),
get_s_cube_term(2),
get_s_cube_term(3),
OldTex("\\vdots"),
get_s_cube_term("n"),
)
buff = 0.6
lhss.arrange(DOWN, buff=buff)
lhss.move_to(lhs.alt2, UP)
self.remove(lhs.alt2)
self.play(
LaggedStart(*(
ReplacementTransform(lhs.alt2.copy(), target)
for target in lhss
)),
frame.animate.set_height(10, about_edge=UP),
run_time=2,
)
# Show empirical mean
h_line = Line(LEFT, RIGHT)
h_line.set_width(lhss.get_width() + 0.75)
h_line.next_to(lhss, DOWN, MED_SMALL_BUFF, aligned_edge=RIGHT)
plus = OldTex("+")
plus.align_to(h_line, LEFT).shift(0.1 * RIGHT)
plus.match_y(lhss[-1])
total = Text("Total", font_size=60)
total.set_color(GREEN)
total.next_to(h_line, DOWN, buff=0.35)
total.match_x(lhss)
mean_sa = OldTex(
f"S(\\text{{{shape_name}}})", "=", "\\frac{1}{n}",
"\\sum_{i=1}^n S\\big(R_i(" + f"\\text{{{shape_name}}})\\big)",
**kw,
)
mean_sa.add_to_back(get_overline(mean_sa.slice_by_tex(None, "=")))
mean_sa.next_to(total, DOWN, LARGE_BUFF, aligned_edge=RIGHT)
corner_rect = SurroundingRectangle(mean_sa, buff=0.25)
corner_rect.set_stroke(WHITE, 2)
corner_rect.set_fill(GREY_E, 1)
corner_rect.move_to(frame, DL)
corner_rect.shift(0.025 * UR)
mean_sa.move_to(corner_rect)
sum_part = mean_sa.slice_by_tex("sum")
sigma = sum_part[0]
sigma.save_state()
lhss_rect = SurroundingRectangle(lhss)
lhss_rect.set_stroke(BLUE, 2)
sigma.next_to(lhss_rect, LEFT)
sum_group = VGroup(lhss, lhss_rect)
self.play(
Write(lhss_rect),
Write(sigma),
)
self.wait()
self.add(corner_rect, sigma)
self.play(
FadeIn(corner_rect),
*(
FadeTransform(term.copy(), sum_part[1:])
for term in lhss
),
Restore(sigma),
)
self.play(Write(mean_sa.get_part_by_tex("frac")))
self.wait()
self.play(
FadeIn(mean_sa.slice_by_tex(None, "frac")),
)
self.wait()
# Create grid
sf = get_s_f_term
grid_terms = [
[sf(1, 1), sf(1, 2), OldTex("\\dots"), sf(1, n_faces)],
[sf(2, 1), sf(2, 2), OldTex("\\dots"), sf(2, n_faces)],
[sf(3, 1), sf(3, 2), OldTex("\\dots"), sf(3, n_faces)],
[Tex("\\vdots"), OldTex("\\vdots"), OldTex("\\ddots"), OldTex("\\vdots")],
[sf("n", 1), sf("n", 2), OldTex("\\dots"), sf("n", n_faces)],
]
grid = VGroup(*(VGroup(*row) for row in grid_terms))
for lhs, row in zip(lhss, grid):
for i in range(len(row) - 1, 0, -1):
is_dots = "dots" in row[0].get_tex()
sym = VectorizedPoint() if is_dots else OldTex("+")
row.insert_submobject(i, sym)
row.arrange(RIGHT, buff=MED_SMALL_BUFF)
for m1, m2 in zip(row, grid[0]):
m1.match_x(m2)
m1.match_y(lhs)
if not is_dots:
parens = OldTex("[]", font_size=72)[0]
parens.set_stroke(width=2)
parens.set_color(BLUE_B)
parens[0].next_to(row, LEFT, buff=SMALL_BUFF)
parens[1].next_to(row, RIGHT, buff=SMALL_BUFF)
row.add(*parens)
eq_half = OldTex("=", "\\frac{1}{2}")
eq_half[1].match_height(parens)
eq_half.next_to(parens[0], LEFT, MED_SMALL_BUFF)
row.add(*eq_half)
grid.set_x(frame.get_right()[0] - 1.5, RIGHT)
self.remove(summand.alt2)
self.play(
sum_group.animate.set_x(grid.get_left()[0] - MED_SMALL_BUFF, RIGHT),
TransformMatchingShapes(
VGroup(
equation.get_part_by_tex("frac"),
equation.get_part_by_tex("="),
),
grid[0][-4:],
),
LaggedStart(*(
FadeTransform(summand.alt2.copy(), part)
for part in grid[0][0:7:2]
)),
FadeOut(equation.get_part_by_tex("sum"), scale=0.25),
Write(grid[0][1:7:2]), # Plus signs
)
self.wait(2)
self.play(FadeTransform(grid[0].copy(), grid[1]))
self.play(FadeTransform(grid[1].copy(), grid[2]))
self.play(FadeTransform(grid[2].copy(), grid[4]), FadeIn(grid[3]))
self.wait(2)
# Average along columns
cols = VGroup(*(
VGroup(*(row[i] for row in grid)).copy()
for i in [0, 2, 6]
))
col_rects = VGroup(*(
SurroundingRectangle(col, buff=SMALL_BUFF)
for col in cols
))
col_rects.set_stroke(YELLOW, 1)
mean_face = OldTex("S(\\text{Face})", **kw)
mean_face.add_to_back(get_overline(mean_face))
mean_face.next_to(grid, DOWN, buff=2)
mean_face_words = OldTexText("Average shadow\\\\of one face")
mean_face_words.move_to(mean_face, UP)
arrows = VGroup(*(
Arrow(rect.get_bottom(), mean_face)
for rect in col_rects
))
arrow_labels = OldTex("\\frac{1}{n} \\sum \\cdots", font_size=30).replicate(3)
for arrow, label in zip(arrows, arrow_labels):
vect = rotate_vector(normalize(arrow.get_vector()), PI / 2)
label.next_to(arrow.pfp(0.5), vect, SMALL_BUFF)
self.add(cols[0])
self.play(
grid.animate.set_opacity(0.4),
ShowCreation(col_rects[0])
)
self.wait()
self.play(
ShowCreation(arrows[0]),
FadeIn(arrow_labels[0]),
FadeIn(mean_face_words, DR),
)
self.wait()
self.play(
mean_face_words.animate.scale(0.7).next_to(mean_face, DOWN, MED_LARGE_BUFF),
FadeIn(mean_face, scale=2),
)
self.wait()
for i in [1, 2]:
self.play(
FadeOut(cols[i - 1]),
FadeIn(cols[i]),
*(
ReplacementTransform(group[i - 1], group[i])
for group in (col_rects, arrows, arrow_labels)
)
)
self.wait()
self.wait()
# Reposition
frame.generate_target()
frame.target.align_to(total, DOWN)
frame.target.shift(0.5 * DOWN)
frame.target.scale(1.15)
frame.target.align_to(lhss, LEFT).shift(0.25 * LEFT)
self.play(LaggedStart(
VGroup(corner_rect, mean_sa).animate.scale(1.25).move_to(
frame.target, UL
).shift(0.1 * DR),
MoveToTarget(frame),
FadeOut(mean_face_words),
FadeOut(mean_face),
grid.animate.set_opacity(1),
*(
FadeOut(group[-1])
for group in (cols, col_rects, arrows, arrow_labels)
),
run_time=3
))
mean_sa.refresh_bounding_box() # ??
# Show final result
rhss = VGroup(
OldTex("=", "\\frac{1}{2}", "\\sum_{j=1}^" + f"{{{n_faces}}}", " S(\\text{F}_j})", **kw),
OldTex("=", "\\frac{1}{2}", "\\sum_{j=1}^" + f"{{{n_faces}}}", " {c}", "\\cdot ", "A(\\text{F}_j)", **kw),
OldTex("=", "\\frac{1}{2}", "{c}", "\\cdot ", "(\\text{Surface area})", **kw),
)
rhss[0].add(get_overline(rhss[0].slice_by_tex("S")))
rhss[2][-2].set_color(WHITE)
rhss.arrange(RIGHT)
rhss.next_to(mean_sa, RIGHT)
corner_rect.generate_target()
corner_rect.target.set_width(
frame.get_width() - 0.2,
stretch=True,
about_edge=LEFT,
)
grid_rect = SurroundingRectangle(grid, buff=SMALL_BUFF)
grid_rect.set_stroke(YELLOW, 1)
grid_rect.set_fill(YELLOW, 0.25)
rects = VGroup(
SurroundingRectangle(mean_sa.slice_by_tex("frac")),
SurroundingRectangle(rhss[0][1:])
)
for rect in rects:
rect.match_height(corner_rect.target, stretch=True)
rect.match_y(corner_rect.target)
rects[0].set_color(BLUE)
rects[1].set_color(YELLOW)
rects.set_stroke(width=2)
rects.set_fill(opacity=0.25)
rows_first = Text("Rows first")
rows_first.next_to(rects[0], DOWN)
rows_first.match_color(rects[0])
cols_first = Text("Columns first")
cols_first.next_to(rects[1], DOWN)
cols_first.match_color(rects[1])
self.add(corner_rect, mean_sa)
self.play(
MoveToTarget(corner_rect),
Write(rhss[0])
)
self.add(grid_rect, grid)
self.play(VFadeInThenOut(grid_rect, run_time=2))
self.wait()
self.play(
Write(rects[0]),
Write(rows_first),
)
self.wait()
self.play(
Write(rects[1]),
Write(cols_first),
)
self.wait()
self.play(LaggedStart(*map(FadeOut, (
*rects, rows_first, cols_first
))))
self.play(
TransformMatchingShapes(rhss[0].copy(), rhss[1])
)
self.wait()
self.play(
FadeTransform(rhss[1].copy(), rhss[2]),
)
self.wait()
key_part = rhss[2][1:]
final_rect = SurroundingRectangle(key_part)
final_rect.set_stroke(YELLOW, 1)
self.play(
corner_rect.animate.set_stroke(width=0).scale(1.1),
FlashAround(key_part, time_width=1.5),
FadeIn(final_rect),
run_time=2,
)
class LimitBrace(Scene):
def construct(self):
brace = Brace(Line().set_width(3), UP)
tex = brace.get_tex("n \\to \\infty")
VGroup(brace, tex).set_color(TEAL)
VGroup(brace, tex).set_backstroke()
self.play(
GrowFromCenter(brace),
FadeIn(tex, shift=0.25 * UP)
)
self.wait()
class FromRowsToColumns(Scene):
def construct(self):
n = 5
grid = Dot().get_grid(n, n, buff=MED_LARGE_BUFF)
grids = grid.get_grid(1, 2, buff=3)
buff = 0.2
row_rects = VGroup(*(
SurroundingRectangle(grids[0][k:k + n], buff=buff)
for k in range(0, n * n, n)
))
col_rects = VGroup(*(
SurroundingRectangle(grids[1][k::n], buff=buff)
for k in range(n)
))
rects = VGroup(row_rects, col_rects)
rects.set_fill(opacity=0.25)
rects.set_stroke(width=2)
row_rects.set_color(BLUE)
col_rects.set_color(YELLOW)
# plus_template = OldTex("+")
# plus_template.match_height(grids[0][0])
# for grid in grids:
# plusses = VGroup()
# for k, dot in enumerate(grid):
# if k % n != n - 1:
# pc = plus_template.copy()
# pc.move_to(midpoint(dot.get_center(), grid[k + 1].get_center()))
# plusses.add(pc)
# grid.add(plusses)
arrow = Arrow(grids[0], grids[1], buff=0.5)
self.add(grids[0])
self.play(FadeIn(row_rects, lag_ratio=0.2))
self.wait()
self.play(
TransformFromCopy(grids[0], grids[1]),
ShowCreation(arrow)
)
self.play(FadeIn(col_rects, lag_ratio=0.2))
self.wait()
class ComplainAboutProgress(TeacherStudentsScene):
def construct(self):
self.student_says(
OldTexText("Wait, is that all\\\\we've accomplished?"),
target_mode="angry",
)
self.play_student_changes(
"guilty", "erm",
look_at=self.students[2].eyes,
added_anims=[self.teacher.change("guilty")],
)
self.wait(4)
class SupposedlyObviousProportionality(ShadowScene):
solid_name = "Cube"
def construct(self):
# Setup
cube = self.solid
shadow = self.shadow
frame = self.camera.frame
frame.set_z(3)
frame.reorient(-20, 80)
self.init_frame_rotation()
light = self.light
light.next_to(cube, OUT, 50)
equation = get_key_result(self.solid_name)
equation.fix_in_frame()
equation.to_edge(UP)
self.add(equation)
# Rotation
self.begin_ambient_rotation(cube)
self.wait()
# Ask about constant
question = OldTexText(
"What is $c$?!",
font_size=72,
tex_to_color_map={"$c$": RED}
)
question.fix_in_frame()
question.next_to(equation, DOWN, LARGE_BUFF)
question.to_edge(RIGHT, buff=LARGE_BUFF)
c_arrow = Arrow(
equation.get_part_by_tex("{c}"),
question.get_corner(UL),
)
c_arrow.set_color(RED)
c_arrow.fix_in_frame()
self.play(
ShowCreation(c_arrow),
Write(question)
)
self.wait(4)
# "Obvious"
obvious_words = OldTexText("Isn't this obvious?", font_size=36)
obvious_words.set_color(GREY_A)
obvious_words.match_y(question)
obvious_words.to_edge(LEFT)
obvious_arrow = Arrow(
obvious_words, equation.get_corner(DL) + RIGHT
)
VGroup(obvious_words, obvious_arrow).fix_in_frame()
self.play(
TransformMatchingShapes(question, obvious_words),
ReplacementTransform(c_arrow, obvious_arrow),
)
self.wait()
# 2d quantities
cube.clear_updaters()
shadow_label = OldTexText("2D")
shadow_label.move_to(shadow)
face_labels = VGroup()
for face in cube[3:]:
lc = shadow_label.copy()
normal = face.get_unit_normal()
lc.rotate(angle_of_vector(flat_project(normal)) + PI / 2)
lc.apply_matrix(
rotation_between_vectors(OUT, normal)
)
lc.move_to(face)
face.label = lc
face_labels.add(lc)
self.play(
Write(shadow_label),
Write(face_labels),
run_time=2,
)
self.wait()
scalars = [cube, face_labels, shadow_label]
self.play(
*(
mob.animate.scale(0.5)
for mob in scalars
),
rate_func=there_and_back,
run_time=3
)
self.play(
*(
mob.animate.stretch(2, 0)
for mob in scalars
),
rate_func=there_and_back,
run_time=3
)
self.wait()
# No not really
no_words = Text("No, not really", font_size=30)
no_words.set_color(RED)
no_words.fix_in_frame()
no_words.next_to(obvious_words, DOWN, MED_LARGE_BUFF)
self.play(
FadeIn(no_words, 0.25 * DOWN),
FadeOut(shadow_label),
FadeOut(face_labels),
)
# Move light
self.play(
light.animate.next_to(cube, OUT + RIGHT, 2),
run_time=4,
rate_func=rush_from,
)
for s in (1.5, 0.25, 2 / 0.75):
self.play(
cube.animate.scale(s),
run_time=2,
)
# To finish
cube.add_updater(lambda m: self.sort_to_camera(m))
self.begin_ambient_rotation(cube)
self.play(
light.animate.next_to(cube, OUT, 50),
LaggedStart(*map(FadeOut, (
no_words, obvious_words, obvious_arrow,
))),
run_time=3,
)
self.wait(33)
class LurkingAssumption(VideoWrapper):
title = "There's a subtle hidden assumption..."
wait_time = 4
animate_boundary = False
class WhatIsC(Scene):
def construct(self):
words = OldTexText(
"What is $c$?!",
tex_to_color_map={"$c$": RED},
font_size=72,
)
self.play(Write(words))
self.play(FlashUnder(words, color=RED))
self.wait()
class BobsFinalAnswer(Scene):
def construct(self):
answer = OldTex(
"S(\\text{Cube})", "=",
"\\frac{1}{2}", "\\cdot", "{\\frac{1}{2}}",
"(\\text{Surface area})", "=",
"\\frac{1}{4} \\big(6s^2\\big)", "=",
"\\frac{3}{2} s^2",
tex_to_color_map={
"\\text{Cube}": BLUE,
"{\\frac{1}{2}}": RED,
}
)
answer.add_to_back(get_overline(answer[:3]))
equals = answer.get_parts_by_tex("=")
eq_indices = list(map(answer.index_of_part, equals))
eq1 = answer[:eq_indices[1]].deepcopy()
eq2 = answer[:eq_indices[2]].deepcopy()
eq3 = answer.deepcopy()
for eq in eq1, eq2, eq3:
eq.to_edge(RIGHT)
eq.shift(1.25 * UP)
self.play(FadeIn(eq1, DOWN))
self.wait()
for m1, m2 in (eq1, eq2), (eq2, eq3):
self.play(
FadeIn(m2[len(m1):]),
m1.animate.move_to(m2, LEFT),
)
self.remove(m1)
self.add(m2)
self.wait()
rect = SurroundingRectangle(eq3[-1])
rect.set_stroke(YELLOW, 2)
self.play(
ShowCreation(rect),
FlashAround(eq3[-1], stroke_width=5, time_width=1.5, run_time=1.5)
)
self.wait()
class ShowSeveralConvexShapes(Scene):
def construct(self):
frame = self.camera.frame
frame.reorient(0, 70)
dodec = Dodecahedron()
dodec.set_fill(BLUE_D)
spike = VGroup()
hexagon = RegularPolygon(6)
spike.add(hexagon)
for v1, v2 in adjacent_pairs(hexagon.get_vertices()):
spike.add(Polygon(v1, v2, 2 * OUT))
spike.set_fill(BLUE_E)
blob = Group(Sphere())
blob.stretch(0.5, 0)
blob.stretch(0.5, 1)
blob.set_color(BLUE_E)
blob.apply_function(
lambda p: [*(2 - p[2]) * p[:2], p[2]]
)
blob.set_color(BLUE_E)
cylinder = Group(Cylinder())
cylinder.set_color(GREY_BROWN)
cylinder.rotate(PI / 4, UP)
examples = Group(*(
Group(*mob)
for mob in (dodec, spike, blob, cylinder)
))
for ex in examples:
ex.set_depth(2)
ex.deactivate_depth_test()
ex.set_gloss(0.5)
ex.set_shadow(0.5)
ex.set_reflectiveness(0.2)
ex.rotate(20 * DEGREES, OUT)
sort_to_camera(ex, self.camera.frame)
for sm in ex:
if isinstance(sm, VMobject):
sm.set_stroke(WHITE, 1)
sm.set_fill(opacity=0.9)
else:
sm.always_sort_to_camera(self.camera)
examples.arrange(RIGHT, buff=LARGE_BUFF)
self.play(LaggedStart(*(
LaggedStartMap(Write, ex, run_time=1)
if isinstance(ex[0], VMobject)
else ShowCreation(ex[0])
for ex in examples
), lag_ratio=0.5, run_time=8))
self.wait()
class KeyResult(Scene):
def construct(self):
eq1, eq2 = [
get_key_result(word)
for word in ("Cube", "Solid")
]
VGroup(eq1, eq2).to_edge(UP)
self.play(FadeIn(eq1, lag_ratio=0.1))
self.wait()
cube_underline = Underline(eq2.get_part_by_tex("Solid"), buff=0.05)
cube_underline.set_stroke(BLUE, 1)
general_words = Text("Assume convex", font_size=36)
general_words.set_color(BLUE)
general_words.next_to(cube_underline, DOWN, buff=1.0)
general_words.shift(LEFT)
general_arrow = Arrow(general_words, cube_underline.get_center(), buff=0.1)
self.play(
ShowCreation(cube_underline),
FadeIn(general_words),
ShowCreation(general_arrow),
FadeTransformPieces(eq1, eq2),
)
self.wait()
const_rect = SurroundingRectangle(VGroup(
eq.get_part_by_tex("frac"),
eq.get_part_by_tex("{c}")
), buff=SMALL_BUFF)
const_rect.set_stroke(RED, 1)
const_words = Text("Universal constant!", font_size=36)
const_words.set_color(RED)
const_words.match_y(general_words)
const_words.set_x(const_rect.get_x() + 1)
const_arrow = Arrow(const_words, const_rect, buff=0.1)
self.play(
ShowCreation(const_rect),
FadeIn(const_words),
ShowCreation(const_arrow),
)
self.wait()
class ShadowsOfDodecahedron(ShadowScene):
inf_light = True
def construct(self):
# Setup
self.camera.frame.set_height(7)
self.camera.frame.shift(OUT)
dodec = self.solid
dodec.scale(5 / dodec[0].get_arc_length())
outline = self.get_shadow_outline()
area = DecimalNumber(font_size=36)
area.move_to(outline)
area.add_updater(lambda m: m.set_value(get_norm(outline.get_area_vector()) / (self.unit_size**2)))
area.add_updater(lambda m: m.fix_in_frame())
area.move_to(3.15 * DOWN)
self.init_frame_rotation()
ssf = 1.5
self.wait()
self.play(dodec.animate.space_out_submobjects(ssf))
self.play(Rotate(dodec, PI, axis=RIGHT, run_time=6))
self.play(dodec.animate.space_out_submobjects(1 / ssf))
self.begin_ambient_rotation(dodec)
self.play(
VFadeIn(outline),
VFadeIn(area),
)
# Add dot and line
dot = GlowDot(0.5 * DR)
line = DashedLine(10 * OUT, ORIGIN)
line.set_stroke(YELLOW, 1)
def dodec_sdf(point):
return max(*(
np.dot(point - pent.get_center(), pent.get_unit_normal())
for pent in dodec
))
def update_line(line):
line.move_to(dot.get_center(), IN)
for dash in line:
dist = dodec_sdf(dash.get_center())
dash.set_stroke(
opacity=interpolate(0.1, 1.0, clip(10 * dist, -0.5, 0.5) + 0.5)
)
dash.inside = (dist < 0)
line.add_updater(update_line)
self.play(ShowCreation(line, rate_func=rush_into))
self.play(FadeIn(dot, rate_func=rush_from))
# Just wait
for n in range(8):
self.play(dot.animate.move_to(midpoint(
outline.get_center(),
outline.pfp(random.random()),
)), run_time=5)
def get_solid(self):
solid = self.get_solid_no_style()
solid.set_stroke(WHITE, 1)
solid.set_fill(self.solid_fill_color, 0.8)
solid.set_gloss(0.1)
solid.set_shadow(0.4)
solid.set_reflectiveness(0.4)
group = Group(*solid)
group.deactivate_depth_test()
group.add_updater(lambda m: self.sort_to_camera(m))
return group
def get_solid_no_style(self):
dodec = Dodecahedron()
dodec.scale((32 / get_surface_area(dodec))**0.5)
return dodec
class AmbientDodecahedronShadow(ShadowsOfDodecahedron):
solid_name = "Dodecahedron"
solid_fill_color = BLUE_E
name_color = BLUE_D
def construct(self):
self.camera.frame.reorient(20, 80)
self.camera.frame.set_z(3)
eq = get_key_result(self.solid_name, color=self.name_color)
eq.to_edge(UP)
eq.fix_in_frame()
self.add(eq)
self.init_frame_rotation()
self.play(LaggedStart(*map(Write, self.solid)))
self.add(self.solid)
outline = self.get_shadow_outline()
area_label = self.get_shadow_area_label()
# area_label.scale(0.7)
area_label.move_to(2.75 * DOWN).to_edge(LEFT)
area_label.add_updater(lambda m: m.fix_in_frame())
surface_area = get_surface_area(self.solid)
surface_area /= (self.unit_size**2)
sa_label = VGroup(Text("Surface area: "), DecimalNumber(surface_area))
sa_label.arrange(RIGHT)
sa_label.match_y(area_label)
sa_label.to_edge(RIGHT)
sa_label.set_backstroke()
sa_label.fix_in_frame()
self.play(
*map(VFadeIn, (outline, area_label, sa_label))
)
self.add(outline)
self.add(area_label)
self.begin_ambient_rotation(self.solid, about_point=self.solid.get_center())
self.wait(30)
class AmbientTriPrismSum(AmbientDodecahedronShadow):
solid_name = "Triangular Prism"
solid_fill_color = interpolate_color(TEAL_E, BLACK, 0.25)
name_color = TEAL_D
def get_solid_no_style(self):
triangle = RegularPolygon(3)
tri1, tri2 = triangle.replicate(2)
tri2.shift(3 * OUT)
sides = []
verts1 = tri1.get_anchors()
verts2 = tri2.get_anchors()
for (a, b), (c, d) in zip(adjacent_pairs(verts1), adjacent_pairs(verts2)):
sides.append(Polygon(a, b, d, c))
result = VGroup(tri1, *sides, tri2)
result.scale((16 / get_surface_area(result))**0.5)
return result
class AmbientPyramidSum(AmbientDodecahedronShadow):
solid_name = "Pyramid"
solid_fill_color = GREY_BROWN
name_color = interpolate_color(GREY_BROWN, WHITE, 0.5)
def get_solid_no_style(self):
base = Square(side_length=1)
result = VGroup(base)
for v1, v2 in adjacent_pairs(base.get_vertices()):
result.add(Polygon(v1, v2, math.sqrt(3) * OUT / 2))
result.set_height(2)
return result
class AmbientCubeWithLabels(AmbientDodecahedronShadow):
solid_name = "Cube"
def get_solid_no_style(self):
return VCube()
class DodecahedronFaceSum(Scene):
def construct(self):
expr = OldTexText(
"Area(Shadow(Dodecahedron))", "=",
"$\\displaystyle \\frac{1}{2}$",
" $\\displaystyle \\sum_{j=1}^{12}$ ",
"Area(Shadow(Face$_j$))",
tex_to_color_map={
"Shadow": GREY_B,
"Dodecahedron": BLUE_D,
"Face$_j$": YELLOW,
}
)
expr.to_edge(UP, buff=MED_SMALL_BUFF)
self.add(expr.slice_by_tex(None, "="))
self.wait()
self.play(FadeIn(expr.slice_by_tex("="), shift=0.25 * UP))
self.wait()
self.play(FlashAround(expr.get_part_by_tex("frac"), run_time=2))
self.play(FlashUnder(expr[-5:], run_time=2))
self.wait(2)
class SphereShadow(ShadowScene):
inf_light = True
def construct(self):
frame = self.camera.frame
frame.set_height(7)
frame.shift(OUT)
sphere = self.solid
shadow = self.shadow
# shadow[1].always_sort_to_camera(self.camera)
shadow_circle = Circle()
shadow_circle.set_fill(BLACK, 0.8)
shadow_circle.replace(shadow)
shadow_circle.set_stroke(WHITE, 1)
self.add(shadow_circle)
self.begin_ambient_rotation(
sphere, speed=0.3,
initial_axis=[-1, -1, 0.5]
)
self.wait(60)
def get_solid(self):
ep = 1e-3
sphere = Sphere(
radius=1.5,
u_range=(ep, TAU - ep),
v_range=(ep, PI - ep),
)
sphere = TexturedSurface(sphere, "EarthTextureMap", "NightEarthTextureMap")
sphere.set_opacity(1)
sphere.always_sort_to_camera(self.camera)
mesh = SurfaceMesh(sphere)
mesh.set_stroke(WHITE, 0.5, 0.25)
return Group(sphere, mesh)
class SphereInfo(Scene):
def construct(self):
kw = {
"tex_to_color_map": {
"{c}": RED,
"=": WHITE,
"R": BLUE
},
"font_size": 36
}
shadow = OldTex("\\text{Average shadow area} = \\pi R^2", **kw)
surface = OldTex("\\text{Surface area} = 4 \\pi R^2", **kw)
conclusion = OldTex("\\frac{1}{2}", "{c}", "=", "\\frac{1}{4}", **kw)
eqs = VGroup(shadow, surface, conclusion)
eqs.arrange(DOWN, buff=MED_LARGE_BUFF)
for eq in eqs:
eq.shift(eq.get_part_by_tex("=").get_x() * LEFT)
eqs.to_corner(UR)
for eq in eqs[:2]:
eq[0].set_color(GREY_A)
for eq in eqs:
self.play(FadeIn(eq, lag_ratio=0.1))
self.wait()
self.play(eqs[2].animate.scale(2, about_edge=UP))
rect = SurroundingRectangle(eqs[2])
rect.set_stroke(YELLOW, 2)
self.play(ShowCreation(rect))
self.wait()
class PiRSquared(Scene):
def construct(self):
form = OldTex("\\pi R^2")[0]
form[1].set_color(BLUE)
self.play(Write(form))
self.wait()
class SwapConstantForFourth(Scene):
def construct(self):
eq = get_key_result("Dodecahedron")
eq.to_edge(UP)
parts = VGroup(
eq.get_part_by_tex("frac"),
eq.get_part_by_tex("{c}")
)
fourth = OldTex("\\frac{1}{4}")
fourth.move_to(parts, LEFT)
fourth.set_color(RED)
self.add(eq)
self.wait()
self.play(
FadeOut(parts, UP),
FadeIn(fourth, UP),
eq.slice_by_tex("\\cdot").animate.next_to(fourth, RIGHT),
)
self.wait()
class ButSpheresAreSmooth(TeacherStudentsScene):
def construct(self):
self.student_says(
OldTexText("But spheres don't\\\\have flat faces!"),
target_mode="angry",
index=2,
added_anims=[self.teacher.change("guilty")]
)
self.play_student_changes(
"erm", "hesitant", "angry",
look_at=self.screen,
)
self.wait(6)
class RepeatedRelation(Scene):
def construct(self):
# Relations
relation = VGroup(
Text("Average shadow"),
OldTex("=").rotate(PI / 2),
OldTex("\\frac{1}{2}", "c", "\\cdot (", "\\text{Surface area}", ")")
)
relation[0].set_color(GREY_A)
relation[2][1].set_color(RED)
relation[2][3].set_color(BLUE)
relation.arrange(DOWN)
relation.scale(0.6)
repeats = relation.get_grid(1, 4, buff=0.8)
repeats.to_edge(LEFT, buff=MED_LARGE_BUFF)
repeats.shift(0.5 * DOWN)
for repeat in repeats:
self.play(FadeIn(repeat[0], lag_ratio=0.1))
self.play(
Write(repeat[1]),
FadeIn(repeat[2], 0.5 * DOWN)
)
self.wait()
# Limit
limit = OldTex(
"\\lim_{|F| \\to 0}",
"\\left(", "{\\text{Average shadow}", "\\over ", "\\text{Surface area}}", "\\right)",
"=", "\\frac{1}{2}", "{c}",
)
limit.set_color_by_tex("Average shadow", GREY_A)
limit.set_color_by_tex("Surface area", BLUE)
limit.set_color_by_tex("{c}", RED)
limit.move_to(2.5 * DOWN)
limit.match_x(repeats)
new_rhs = OldTex("=", "{\\pi R^2", "\\over", "4\\pi R^2}")
new_rhs.set_color_by_tex("\\pi R^2", GREY_A)
new_rhs.set_color_by_tex("4\\pi R^2", BLUE)
new_rhs.move_to(limit.get_part_by_tex("="), LEFT)
self.play(Write(limit))
self.wait()
self.play(
limit.slice_by_tex("=").animate.next_to(new_rhs, RIGHT),
GrowFromCenter(new_rhs)
)
self.wait()
class SimpleCross(Scene):
def construct(self):
lines = VGroup(
Line(UP, DOWN).set_height(FRAME_HEIGHT),
Line(LEFT, RIGHT).set_width(FRAME_WIDTH),
)
self.play(ShowCreation(lines, lag_ratio=0.5))
self.wait()
# Not needed?
class AmbientCubeTurningIntoNewShapes(Scene):
def construct(self):
pass
class PopularizaitonVsDoing(Scene):
def construct(self):
# Words
popular = Text("Popularization of math")
doing = Text("Doing math")
words = VGroup(popular, doing)
words.arrange(DOWN, buff=3)
words.move_to(UP)
self.play(FadeIn(popular, UP))
self.wait()
self.play(
TransformFromCopy(
popular.get_part_by_text("math"),
doing.get_part_by_text("math"),
),
Write(doing[:len("Doing")])
)
self.wait()
# Bars
width = 8
bar = Rectangle(width, 0.5)
bar.set_stroke(WHITE, 1)
bar.next_to(popular, DOWN)
left_bar, right_bar = bar.replicate(2)
left_bar.set_fill(BLUE_E, 1)
right_bar.set_fill(RED_E, 1)
left_bar.stretch(0.5, 0, about_edge=LEFT)
right_bar.stretch(0.5, 0, about_edge=RIGHT)
left_brace = always_redraw(lambda: Brace(left_bar, DOWN, buff=SMALL_BUFF))
right_brace = always_redraw(lambda: Brace(right_bar, DOWN, buff=SMALL_BUFF))
left_label = Text("Insights", font_size=30, color=GREY_B)
right_label = Text("Computations", font_size=30, color=GREY_B)
always(left_label.next_to, left_brace, DOWN, SMALL_BUFF)
always(right_label.next_to, right_brace, DOWN, SMALL_BUFF)
bar_group = VGroup(
bar,
left_bar, right_bar,
left_brace, right_brace,
left_label, right_label,
)
def set_bar_alpha(alpha, **kwargs):
self.play(
left_bar.animate.set_width(alpha * width, about_edge=LEFT, stretch=True),
right_bar.animate.set_width((1 - alpha) * width, about_edge=RIGHT, stretch=True),
**kwargs
)
self.play(FadeIn(bar_group, lag_ratio=0.1))
set_bar_alpha(0.95, run_time=2)
self.wait()
self.add(bar_group.deepcopy().clear_updaters())
self.play(
bar_group.animate.shift(doing.get_center() - popular.get_center())
)
set_bar_alpha(0.05, run_time=5)
self.wait()
# Embed
self.embed()
class MultipleMathematicalBackgrounds(TeacherStudentsScene):
def construct(self):
self.remove(self.background)
labels = VGroup(
OldTexText("$\\le$ High school"),
OldTexText("$\\approx$ Undergrad"),
OldTexText("$\\ge$ Ph.D."),
)
for student, label in zip(self.students, labels):
label.scale(0.7)
label.next_to(student, UP)
words = OldTexText("Explanation doesn't vary\\\\with backgrounds")
words.to_edge(UP)
lines = VGroup(*(
DashedLine(words, label, buff=0.5)
for label in labels
))
lines.set_stroke(WHITE, 2)
self.add(words)
self.play(
self.teacher.change("raise_right_hand"),
self.change_students(
"pondering", "thinking", "pondering",
look_at=self.teacher.eyes,
),
)
self.play(
LaggedStartMap(ShowCreation, lines, lag_ratio=0.5),
LaggedStartMap(FadeIn, labels, lag_ratio=0.5),
)
self.wait(3)
self.play(
self.teacher.change("dejected").look(UP),
self.change_students("hesitant", "well", "thinking"),
LaggedStartMap(FadeOut, lines, scale=0.5),
FadeOut(words, DOWN),
)
self.wait(4)
# Different levels
kw = {"font_size": 30}
methods = VGroup(
OldTexText("Calculus\\\\primer", **kw),
OldTexText("Quickly show\\\\key steps", **kw),
OldTexText("Describe as a\\\\measure on SO(3)", **kw),
)
new_lines = VGroup()
colors = [GREEN_B, GREEN_C, GREEN_D]
for method, label, color in zip(methods, labels, colors):
method.move_to(label)
method.shift(2.5 * UP)
method.set_color(color)
line = DashedLine(method, label, buff=0.25)
line.set_stroke(color, 2)
new_lines.add(line)
self.play(
self.teacher.change("raise_right_hand"),
self.change_students(
"erm", "pondering", "thinking",
look_at=self.students.get_center() + 4 * UP
),
LaggedStartMap(FadeIn, methods, lag_ratio=0.5),
LaggedStartMap(ShowCreation, new_lines, lag_ratio=0.5),
)
self.wait(4)
class WatchingAVideo(Scene):
def construct(self):
self.add(FullScreenRectangle())
randy = Randolph()
randy.to_corner(DL)
screen = ScreenRectangle(height=5)
screen.set_fill(BLACK, 1)
screen.to_corner(UR)
def blink_wait(n=1):
for x in range(n):
self.wait()
self.play(Blink(randy))
self.wait()
self.add(screen)
self.add(randy)
self.play(randy.change("pondering", screen))
blink_wait()
self.play(randy.change("thinking", screen))
blink_wait()
self.play(randy.change("hesitant", screen))
blink_wait(2)
class CleverProofExample(Scene):
def construct(self):
initial_sum = OldTex("1^2 + 2^2 + 3^2 + \\cdots + n^2")
tripple_tris, final_tri = self.get_triangle_sums()
initial_sum.set_width(10)
self.play(FadeIn(initial_sum, lag_ratio=0.1))
self.wait()
tripple_tris.set_width(10)
tripple_tris.to_edge(DOWN, buff=2)
tris = tripple_tris[0]
tri = tris[0]
tri.save_state()
tri.set_height(4)
tri.center().to_edge(DOWN, buff=1)
self.play(
initial_sum.animate.set_width(8).to_edge(UP),
FadeIn(tri, lag_ratio=0.1)
)
self.wait()
self.play(
Restore(tri),
FadeIn(tripple_tris[1:])
)
for i in (0, 1):
bt1 = tris[i].copy()
bt1.generate_target()
bt1.target.rotate(120 * DEGREES)
bt1.target.replace(tris[i + 1])
bt1.target.set_opacity(0)
tris[i + 1].save_state()
tris[i + 1].rotate(-120 * DEGREES)
tris[i + 1].replace(tris[i])
tris[i + 1].set_opacity(0)
self.play(
MoveToTarget(bt1, remover=True),
Restore(tris[i + 1])
)
self.wait()
final_tri.set_height(3)
final_tri.move_to(tripple_tris, UP)
initial_sum.generate_target()
eq = OldTex("=").scale(2)
tripple_tris.generate_target()
top_row = VGroup(initial_sum.target, eq, tripple_tris.target)
top_row.arrange(RIGHT, buff=0.5)
top_row.set_width(FRAME_WIDTH - 1)
top_row.center().to_edge(UP)
self.play(
MoveToTarget(initial_sum),
FadeIn(eq),
MoveToTarget(tripple_tris),
FadeTransform(tripple_tris.copy(), final_tri)
)
self.wait()
final1 = OldTex("= \\frac{2n + 1}{3} (1 + 2 + 3 + \\cdots + n)")
final2 = OldTex("= \\frac{2n + 1}{3} \\frac{(n + 1)n}{2}")
final3 = OldTex("= \\frac{(2n + 1)(n + 1)(n)}{6}")
final_tri.generate_target()
final_tri.target.set_height(2).to_edge(LEFT)
for final in (final1, final2, final3):
final.next_to(final_tri.target, RIGHT)
self.play(
MoveToTarget(final_tri),
Write(final1),
)
self.wait()
self.play(
FadeOut(final1, UP),
FadeIn(final2, UP),
)
self.wait()
self.play(
FadeOut(final2, UP),
FadeIn(final3, UP),
)
self.play(VGroup(final_tri, final3).animate.set_x(0))
self.wait()
# Embed
self.embed()
def get_triangle_sums(self):
dl_dots = OldTex("\\vdots").rotate(-30 * DEGREES)
dr_dots = OldTex("\\vdots").rotate(30 * DEGREES)
blank = Integer(0).set_opacity(0)
n = OldTex("n")
np1 = OldTex("(2n + 1)")
dots = OldTex("\\dots")
tri1 = VGroup(
Integer(1).replicate(1),
Integer(2).replicate(2),
Integer(3).replicate(3),
VGroup(dl_dots, blank.copy(), blank.copy(), dr_dots),
VGroup(n.copy(), n.copy(), dots, n.copy(), n.copy()),
)
tri2 = VGroup(
n.replicate(1),
VGroup(dl_dots, n).copy(),
VGroup(Integer(3), blank, dr_dots).copy(),
VGroup(Integer(2), Integer(3), dots, n).copy(),
VGroup(Integer(1), Integer(2), Integer(3), dots, n).copy(),
)
tri3 = VGroup(
n.replicate(1),
VGroup(n, dr_dots).copy(),
VGroup(dl_dots, blank, Integer(3)).copy(),
VGroup(n, dots, Integer(3), Integer(2)).copy(),
VGroup(n, dots, Integer(3), Integer(2), Integer(1)).copy(),
)
sum_tri = VGroup(
np1.replicate(1),
np1.replicate(2),
np1.replicate(3),
VGroup(dl_dots, *blank.replicate(6), dr_dots).copy(),
VGroup(np1.copy(), np1.copy(), dots.copy(), np1.copy(), np1.copy()),
)
tris = VGroup(tri1, tri2, tri3)
for tri in (*tris, sum_tri):
for row in tri:
row.arrange(RIGHT, buff=0.5)
tri.arrange(DOWN, buff=0.5)
tris.arrange(RIGHT, buff=2.0)
tris.set_width(6)
plusses = VGroup(
OldTex("+").move_to(tris[:2]),
OldTex("+").move_to(tris[1:]),
)
parens = OldTex("()")[0]
parens.stretch(2, 1)
parens.match_height(tris)
parens[0].next_to(tris, LEFT)
parens[1].next_to(tris, RIGHT)
frac = OldTex("\\frac{1}{3}")
frac.next_to(parens, LEFT)
lhs = VGroup(tris, frac, parens, plusses)
parens_copy = parens.copy()
sum_tri.match_height(lhs)
sum_tri.move_to(tris, LEFT)
parens_copy[1].next_to(sum_tri, RIGHT)
rhs = VGroup(frac.copy(), sum_tri, parens_copy)
rhs.next_to(lhs, DOWN, LARGE_BUFF, aligned_edge=LEFT)
# eq = OldTex("=")
# eq.next_to(rhs, LEFT)
return VGroup(lhs, rhs)
class BlendOfMindsets(Scene):
def construct(self):
Text("Calculate specifics")
Text("Understand generalities")
Text("You need both")
class ListernerEmail(Scene):
def construct(self):
# Letter
rect = Rectangle(4, 7)
rect.set_stroke(WHITE, 2)
rect.set_fill("#060606", 1)
lines = Line(LEFT, RIGHT).get_grid(15, 1)
lines.set_width(0.8 * rect.get_width())
lines.arrange(DOWN)
lines.set_height(0.7 * rect.get_height(), stretch=True)
for n in [3, 8, -1]:
lines[n].stretch(0.5, 0, about_edge=LEFT)
if n > 0:
lines[n + 1].set_opacity(0)
lines.move_to(rect)
salutation = Text("Hi Prof. Kontorovich,", font_size=30)
salutation.next_to(lines, UP, aligned_edge=LEFT)
lines.shift(0.2 * DOWN)
letter = VGroup(rect, lines, salutation)
self.add(rect)
self.play(
Write(salutation, run_time=1),
ShowCreation(lines, rate_func=linear, run_time=3, lag_ratio=0.5),
)
self.add(letter)
self.wait()
self.play(letter.animate.to_edge(LEFT))
# Phrases
phrases = VGroup(
Text("Im a PhD student..."),
Text(
"...I had noticed my mathematical capabilities\n"
"starting to fade (to which I attributed getting\n"
"older and not being as sharp)..."
),
Text(
"...I realized that the entire problem, for me at least,\n"
"was entirely about my lack of problems and drills."
),
)
phrases.arrange(DOWN, buff=2.0, aligned_edge=LEFT)
phrases.set_width(8)
phrases.next_to(letter, RIGHT, LARGE_BUFF)
highlights = VGroup()
for i, w in [(0, 1), (5, 3), (11, 2.5)]:
hrect = Rectangle(w, 0.1)
hrect.set_stroke(width=0)
hrect.set_fill(YELLOW, 0.5)
hrect.move_to(lines[i], LEFT)
highlights.add(hrect)
highlights[0].shift(1.5 * RIGHT)
highlights[2].align_to(lines, RIGHT)
hlines = VGroup()
for highlight, phrase in zip(highlights, phrases):
hlines.add(VGroup(
DashedLine(highlight.get_corner(UR), phrase.get_corner(UL), buff=0.1),
DashedLine(highlight.get_corner(DR), phrase.get_corner(DL), buff=0.1),
))
hlines.set_stroke(YELLOW, 1)
for i in range(3):
self.play(
FadeIn(highlights[i]),
*map(ShowCreation, hlines[i]),
GrowFromPoint(phrases[i], highlights[i].get_right())
)
self.wait(2)
# Embed
self.embed()
class FamousMathematicians(Scene):
im_height = 3.5
def construct(self):
# Portraits
images = Group(
ImageMobject("Newton"),
ImageMobject("Euler"),
ImageMobject("Gauss"),
ImageMobject("Fourier"),
ImageMobject("Riemann_cropped"),
ImageMobject("Cauchy"),
ImageMobject("Noether"),
ImageMobject("Ramanujan"),
)
names = VGroup(
Text("Isaac Newton"),
Text("Leonhard Euler"),
Text("Carl Friedrich Gauss"),
Text("Joseph Fourier"),
Text("Bernhard Riemann"),
Text("Augustin Cauchy"),
Text("Emmy Noether"),
Text("Srinivasa Ramanujan"),
)
im_groups = Group()
for im, name in zip(images, names):
im.set_height(self.im_height)
name.scale(0.6)
name.set_color(GREY_A)
name.next_to(im, DOWN)
im_groups.add(Group(im, name))
# im_groups.arrange(RIGHT, aligned_edge=UP, buff=LARGE_BUFF)
im_groups.arrange_in_grid(2, 4, aligned_edge=UP, buff=LARGE_BUFF)
im_groups.set_width(FRAME_WIDTH - 2)
im_groups.to_edge(LEFT)
dots = OldTex("\\dots", font_size=72).replicate(2)
dots[0].next_to(images[-5], RIGHT, MED_LARGE_BUFF)
dots[1].next_to(images[-1], RIGHT, MED_LARGE_BUFF)
self.play(
LaggedStart(*map(FadeIn, (*im_groups, dots)), lag_ratio=0.25),
run_time=5
)
self.wait()
self.play(
im_groups[0].animate.set_height(6).center().to_edge(LEFT),
LaggedStart(*(
FadeOut(mob, DR)
for mob in (*im_groups[1:], dots)
), lag_ratio=0.25),
run_time=2,
)
self.wait()
# Papers (do in editor)
class InventingMath(Scene):
def construct(self):
pass
class AmbientHourglass(ShadowScene):
inf_light = True
def construct(self):
frame = self.camera.frame
frame.set_z(3)
self.init_frame_rotation()
self.remove(self.solid, self.shadow)
qint_func = bezier([0, 1, -1.25, 1, 0])
def func(u, v):
qf = qint_func(v)
x = qf * math.cos(u)
y = qf * math.sin(u)
x = np.sign(x) * abs(x)**0.5
y = np.sign(y) * abs(y)**0.5
return [x, y, 0.5 - v]
ep = 1e-6
hourglass = ParametricSurface(func, (0, TAU), (0 + ep, 1 - ep))
hourglass.set_depth(2)
hourglass.set_z(3)
hourglass.set_color(BLUE_D)
hourglass.set_opacity(0.5)
hourglass.set_reflectiveness(0.1)
hourglass.set_gloss(0.1)
hourglass.set_shadow(0.5)
hourglass.always_sort_to_camera(self.camera)
mesh = SurfaceMesh(hourglass)
mesh.set_flat_stroke(False)
mesh.set_stroke(BLUE_B, 0.2, 0.5)
mesh_shadow = mesh.copy()
mesh_shadow.deactivate_depth_test()
solid_group = Group(mesh_shadow, hourglass, mesh)
shadow = self.shadow = get_shadow(solid_group)
shadow[1].always_sort_to_camera(self.camera)
self.add(solid_group, shadow)
for x in range(30):
self.random_toss(solid_group)
self.wait()
self.begin_ambient_rotation(
solid_group,
speed=0.5,
initial_axis=[1, 0, 1],
)
self.wait(35)
class QuantifyConvexity(Scene):
def construct(self):
# Ask question
nonconvex = Text("Non-convex")
nonconvex.to_edge(UP)
nonconvex.set_color(RED)
question = Text("Can we quantify this?")
question.next_to(nonconvex, DOWN, buff=1.5)
question.to_edge(LEFT)
arrow = Arrow(question, nonconvex.get_corner(DL))
self.play(Write(nonconvex))
self.wait()
self.play(
FadeIn(question, 0.5 * DOWN),
ShowCreation(arrow),
)
self.wait()
# Binary choice
double_arrow = OldTex("\\leftrightarrow")
double_arrow.move_to(nonconvex)
convex = Text("Convex")
convex.set_color(GREEN)
convex.next_to(double_arrow, RIGHT)
self.play(
nonconvex.animate.next_to(double_arrow, LEFT),
Write(double_arrow),
FadeIn(convex, shift=0.25 * RIGHT),
Uncreate(arrow),
FadeOut(question, 0.5 * DOWN),
)
self.wait()
# Spectrum
interval = UnitInterval(width=7)
interval.add_numbers()
interval.to_corner(UL, buff=LARGE_BUFF)
self.play(
FadeTransform(double_arrow, interval),
convex.animate.scale(0.5).next_to(interval.n2p(1), UP),
nonconvex.animate.scale(0.5).next_to(interval.n2p(0), UP),
)
self.wait()
# Fraction
shadow = get_key_result("Solid").slice_by_tex(None, "=")
shadow.add(shadow[0].copy())
shadow.remove(shadow[0])
four_shadow = VGroup(OldTex("4 \\cdot"), shadow)
four_shadow.arrange(RIGHT, buff=SMALL_BUFF)
sa = Text("Surface area")
frac = VGroup(
four_shadow,
Line().match_width(four_shadow).set_stroke(width=2),
sa
)
frac.arrange(DOWN)
frac.set_width(3)
frac.to_corner(UR)
frac.match_y(interval)
self.play(Write(shadow))
self.play(FadeIn(four_shadow[0]))
self.wait()
self.play(ShowCreation(frac[1]))
self.play(FadeIn(sa))
self.wait()
# Dot
dot = GlowDot()
dot.scale(2)
dot.move_to(interval.n2p(1))
self.play(FadeIn(dot, RIGHT))
self.wait()
self.play(dot.animate.move_to(interval.n2p(0.6)), run_time=2)
self.wait()
class GoalsOfMath(TeacherStudentsScene):
def construct(self):
words = Text("The goal of math\nis to answer questions")
words.move_to(self.hold_up_spot, DOWN)
words.to_edge(RIGHT, buff=2.0)
aq = words.get_part_by_text("answer questions")
aq.set_color(BLUE)
dni = Text(
"develop new ideas",
t2c={"new ideas": YELLOW},
t2s={"new ideas": ITALIC},
)
dni.move_to(aq, LEFT)
self.play(
self.teacher.change("raise_right_hand", words),
self.change_students(*3 * ["pondering"], look_at=words),
Write(words)
)
self.wait(2)
self.add(aq, self.teacher)
self.play(
aq.animate.shift(0.5 * DOWN).set_opacity(0.2),
Write(dni),
self.teacher.change("well", words),
self.change_students(*3 * ["thinking"], look_at=words)
)
self.wait(3)
class InfatuationWithGenerality(TeacherStudentsScene):
def construct(self):
self.student_says(
OldTexText("Why are mathematicians\\\\obsessed with abstractions?"),
index=0,
added_anims=[
self.students[1].change("tease"),
self.students[2].change("pondering"),
]
)
self.play(
self.teacher.change("well"),
)
self.wait(6)
class NumberphileFrame(VideoWrapper):
animate_boundary = True
title = "Bertrand's Paradox (with Numberphile)"
title_config = {
"font_size": 48
}
wait_time = 16
class ByLine(Scene):
def construct(self):
lines = VGroup(
OldTexText("Artwork by\\\\", "Kurt Bruns"),
OldTexText("Music by\\\\", "Vince Rubinetti"),
OldTexText("Other stuff\\\\", "Grant Sanderson"),
)
for line in lines:
line[0].set_color(GREY_B)
line[1].scale(1.2, about_edge=UP)
lines.arrange(DOWN, buff=1.5)
self.add(lines)
class EndScreen(PatreonEndScreen):
pass
class ThumbnailBackground(ShadowScene):
plane_dims = (32, 20)
def construct(self):
frame = self.camera.frame
frame.reorient(0)
cube = self.solid
cube.set_shadow(0.5)
light = self.light
light.next_to(cube, OUT, buff=2)
light.shift(2 * LEFT)
light.move_to(50 * OUT)
gc = self.glow.replicate(10)
gc.set_opacity(0.3)
gc.clear_updaters()
gc.arrange(RIGHT).match_width(cube)
gc.move_to(6 * OUT)
self.add(gc)
outline = self.get_shadow_outline()
light_lines = self.get_light_lines(outline)
self.add(outline, light_lines)
self.randomly_reorient(cube)
self.randomly_reorient(cube)
self.wait()