3b1b-manim/active_projects/sphere_area.py

3016 lines
89 KiB
Python
Raw Normal View History

2018-11-24 12:17:01 -08:00
from big_ol_pile_of_manim_imports import *
from active_projects.shadows import *
# Abstract scenes
class Cylinder(Sphere):
"""
Inherits from sphere so as to be as aligned as possible
for transformations
"""
def func(self, u, v):
return np.array([
np.cos(v),
np.sin(v),
np.cos(u)
])
class UnwrappedCylinder(Cylinder):
def func(self, u, v):
return np.array([
v - PI,
-self.radius,
np.cos(u)
])
class ParametricDisc(Sphere):
CONFIG = {
"u_min": 0,
"u_max": 1,
"stroke_width": 0,
"checkerboard_colors": [BLUE_D],
}
def func(self, u, v):
return np.array([
u * np.cos(v),
u * np.sin(v),
0,
])
class SphereCylinderScene(SpecialThreeDScene):
CONFIG = {
"cap_config": {
"stroke_width": 1,
"stroke_color": WHITE,
"fill_color": BLUE_D,
"fill_opacity": 1,
}
}
2018-11-29 17:30:34 -08:00
def get_cylinder(self, **kwargs):
config = merge_config([kwargs, self.sphere_config])
return Cylinder(**config)
def get_cylinder_caps(self):
R = self.sphere_config["radius"]
caps = VGroup(*[
Circle(
radius=R,
**self.cap_config,
).shift(R * vect)
for vect in [IN, OUT]
])
caps.set_shade_in_3d(True)
return caps
def get_unwrapped_cylinder(self):
return UnwrappedCylinder(**self.sphere_config)
def get_xy_plane(self):
pass
def get_ghost_surface(self, surface):
result = surface.copy()
result.set_fill(BLUE_E, opacity=0.5)
result.set_stroke(WHITE, width=0.5, opacity=0.5)
return result
def project_point(self, point):
radius = self.sphere_config["radius"]
result = np.array(point)
result[:2] = normalize(result[:2]) * radius
return result
def project_mobject(self, mobject):
return mobject.apply_function(self.project_point)
# Scenes for video
2018-11-24 12:17:01 -08:00
class AskAboutShadowRelation(SpecialThreeDScene):
CONFIG = {
"R_color": YELLOW,
"space_out_factor": 1.15,
}
2018-11-24 12:17:01 -08:00
def construct(self):
self.show_surface_area()
self.show_four_circles()
self.ask_why()
self.show_all_pieces()
2018-11-24 12:17:01 -08:00
def show_surface_area(self):
sphere = self.get_sphere()
sphere.set_fill(BLUE_E, opacity=0.5)
sphere.add_updater(
lambda s, dt: s.rotate(0.1 * dt, axis=OUT)
)
pieces = sphere.deepcopy()
pieces.space_out_submobjects(1.5)
pieces.shift(IN)
pieces.set_color(GREEN)
# radial_line = Line(ORIGIN, sphere.get_right())
# R_label = TexMobject("R")
# R_label.set_color(BLUE)
# R_label.rotate(90 * DEGREES, RIGHT)
# R_label.next_to(radial_line, OUT, SMALL_BUFF)
sa_equation = TexMobject(
"\\text{Surface area} = 4\\pi R^2",
tex_to_color_map={"R": BLUE}
)
sa_equation.scale(1.5)
sa_equation.to_edge(UP)
self.set_camera_orientation(
phi=70 * DEGREES,
theta=-90 * DEGREES,
)
2018-11-24 12:17:01 -08:00
self.add_fixed_in_frame_mobjects(sa_equation)
self.play(
Write(sphere, stroke_width=1),
2018-11-24 12:17:01 -08:00
FadeInFromDown(sa_equation),
# ShowCreation(radial_line),
# FadeInFrom(R_label, IN),
)
# self.play(
# Transform(
# sphere, pieces,
# rate_func=there_and_back_with_pause,
# run_time=2
# )
# )
2018-11-24 12:17:01 -08:00
self.play(LaggedStart(
UpdateFromAlphaFunc, sphere,
lambda mob: (mob, lambda m, a: m.set_fill(
color=interpolate_color(BLUE_E, YELLOW, a),
opacity=interpolate(0.5, 1, a)
)),
rate_func=there_and_back,
lag_ratio=0.1,
run_time=4
2018-11-24 12:17:01 -08:00
))
self.play(
sphere.scale, 0.75,
sphere.shift, 3 * LEFT,
sa_equation.shift, 3 * LEFT,
)
2018-11-24 12:17:01 -08:00
self.wait(2)
self.sphere = sphere
self.sa_equation = sa_equation
def show_four_circles(self):
sphere = self.sphere
shadow = Circle(
radius=sphere.get_width() / 2,
stroke_color=WHITE,
stroke_width=1,
fill_color=BLUE_E,
fill_opacity=1,
)
radial_line = Line(
shadow.get_center(),
shadow.get_right(),
color=YELLOW
)
R_label = TexMobject("R").set_color(self.R_color)
R_label.scale(0.8)
R_label.next_to(radial_line, DOWN, SMALL_BUFF)
shadow.add(radial_line, R_label)
shadow.move_to(
self.camera.transform_points_pre_display(sphere, [sphere.get_center()])[0]
)
shadows = VGroup(*[shadow.copy() for x in range(4)])
shadows.arrange_submobjects_in_grid(buff=MED_LARGE_BUFF)
shadows.to_edge(RIGHT)
area_label = TexMobject(
"\\pi R^2",
tex_to_color_map={"R": self.R_color}
)
area_label.scale(1.2)
area_labels = VGroup(*[
area_label.copy().move_to(interpolate(
mob.get_center(), mob.get_top(), 0.5
))
for mob in shadows
])
# shadow.move_to(sphere)
self.add_fixed_in_frame_mobjects(shadow)
self.play(DrawBorderThenFill(shadow))
anims = []
for new_shadow in shadows:
old_shadow = shadow.copy()
self.add_fixed_in_frame_mobjects(old_shadow)
anims.append(Transform(
old_shadow, new_shadow, remover=True
))
self.remove(shadow)
self.play(*anims)
self.add_fixed_in_frame_mobjects(shadows, area_labels)
self.play(LaggedStart(FadeInFromLarge, area_labels))
self.wait()
2018-11-24 12:17:01 -08:00
self.shadows = shadows
self.shadow_area_labels = area_labels
def ask_why(self):
2018-11-24 12:17:01 -08:00
pass
def show_all_pieces(self):
shadows = self.shadows
area_labels = self.shadow_area_labels
sphere = self.sphere
shadow_pieces_group = VGroup()
for shadow in shadows:
pieces = ParametricSurface(
func=lambda u, v: np.array([
u * np.cos(TAU * v),
u * np.sin(TAU * v),
0,
]),
resolution=(12, 24)
)
pieces.replace(shadow)
pieces.match_style(sphere)
shadow_pieces_group.add(pieces)
self.add_fixed_in_frame_mobjects(shadow_pieces_group)
self.play(
FadeOut(shadows),
FadeOut(area_labels),
FadeIn(shadow_pieces_group)
)
self.show_area_pieces(sphere)
self.wait()
self.show_area_pieces(*shadow_pieces_group)
self.wait(2)
self.unshow_area_pieces(sphere, *shadow_pieces_group)
self.wait(3)
def show_area_pieces(self, *mobjects):
for mob in mobjects:
mob.generate_target()
mob.target.space_out_submobjects(self.space_out_factor)
self.play(*map(MoveToTarget, mobjects))
self.play(*[
LaggedStart(
ApplyMethod, mob,
lambda m: (m.set_fill, YELLOW, 1),
rate_func=there_and_back,
lag_ratio=0.25,
run_time=1.5
)
for mob in mobjects
])
def unshow_area_pieces(self, *mobjects):
self.play(*[
ApplyMethod(
mob.space_out_submobjects,
1.0 / self.space_out_factor
)
for mob in mobjects
])
class ButWhy(TeacherStudentsScene):
def construct(self):
self.student_says(
"But why?!?",
student_index=2,
target_mode="angry",
bubble_kwargs={"direction": LEFT},
)
self.play(
self.students[0].change, "pondering", self.screen,
self.students[1].change, "pondering", self.screen,
self.teacher.change, "guilty", self.screen,
)
self.wait(3)
class TryFittingCirclesDirectly(ExternallyAnimatedScene):
pass
class PreviewTwoMethods(Scene):
def construct(self):
pass # TODO
class MapSphereOntoCylinder(SphereCylinderScene):
def construct(self):
sphere = self.get_sphere()
sphere_ghost = self.get_ghost_surface(sphere)
sphere_ghost.set_stroke(width=0)
axes = self.get_axes()
cylinder = self.get_cylinder()
cylinder.set_fill(opacity=0.75)
radius = cylinder.get_width() / 2
self.add(axes, sphere_ghost, sphere)
self.wait()
self.begin_ambient_camera_rotation()
self.move_camera(
**self.default_angled_camera_position,
run_time=2,
)
self.wait(2)
self.play(
ReplacementTransform(sphere, cylinder),
run_time=3
)
self.wait(3)
# Get rid of caps
caps = self.get_cylinder_caps()
caps[1].set_shade_in_3d(False)
label = TextMobject("Label")
label.scale(1.5)
label.stretch(0.8, 0)
label.rotate(90 * DEGREES, RIGHT)
label.rotate(90 * DEGREES, OUT)
label.shift(np.log(radius + SMALL_BUFF) * RIGHT)
label.apply_complex_function(np.exp)
label.rotate(90 * DEGREES, IN, about_point=ORIGIN)
label.shift(OUT)
label.set_background_stroke(width=0)
self.play(FadeIn(caps))
self.wait()
self.play(
caps.space_out_submobjects, 2,
caps.fade, 1,
remover=True
)
self.play(Write(label))
self.wait(2)
self.play(FadeOut(label))
# Unwrap
unwrapped_cylinder = self.get_unwrapped_cylinder()
unwrapped_cylinder.set_fill(opacity=0.75)
self.play(
ReplacementTransform(cylinder, unwrapped_cylinder),
run_time=3
)
self.stop_ambient_camera_rotation()
self.move_camera(
phi=90 * DEGREES,
theta=-90 * DEGREES,
)
# Show dimensions
stroke_width = 5
top_line = Line(
PI * radius * LEFT + radius * OUT,
PI * radius * RIGHT + radius * OUT,
color=YELLOW,
stroke_width=stroke_width,
)
side_line = Line(
PI * radius * LEFT + radius * OUT,
PI * radius * LEFT + radius * IN,
color=RED,
stroke_width=stroke_width,
)
lines = VGroup(top_line, side_line)
lines.shift(radius * DOWN)
two_pi_R = TexMobject("2\\pi R")
two_R = TexMobject("2R")
texs = VGroup(two_pi_R, two_R)
for tex in texs:
tex.scale(1.5)
tex.rotate(90 * DEGREES, RIGHT)
two_pi_R.next_to(top_line, OUT)
two_R.next_to(side_line, RIGHT)
2018-11-29 17:30:34 -08:00
self.play(
ShowCreation(top_line),
FadeInFrom(two_pi_R, IN)
)
self.wait()
self.play(
ShowCreation(side_line),
FadeInFrom(two_R, RIGHT)
)
self.wait()
class CircumferenceOfCylinder(SphereCylinderScene):
def construct(self):
sphere = self.get_sphere()
sphere_ghost = self.get_ghost_surface(sphere)
cylinder = self.get_cylinder()
cylinder_ghost = self.get_ghost_surface(cylinder)
cylinder_ghost.set_stroke(width=0)
radius = self.sphere_config["radius"]
circle = Circle(radius=radius)
circle.set_stroke(YELLOW, 5)
circle.shift(radius * OUT)
height = Line(radius * IN, radius * OUT)
height.shift(radius * LEFT)
height.set_stroke(RED, 5)
self.set_camera_orientation(
phi=70 * DEGREES,
theta=-95 * DEGREES,
)
self.begin_ambient_camera_rotation(0.01)
self.add(sphere_ghost, cylinder_ghost)
self.wait()
self.play(ShowCreation(circle))
self.wait(2)
self.play(ShowCreation(height))
self.wait(5)
class UnfoldCircles(Scene):
CONFIG = {
"circle_style": {
"fill_color": GREY_BROWN,
"fill_opacity": 0,
"stroke_color": GREY_BROWN,
"stroke_width": 2,
},
"radius": 1.0,
"dr": 0.01,
}
def construct(self):
self.show_rectangle_with_formula()
self.add_four_circles()
def show_rectangle_with_formula(self):
TexMobject.CONFIG["background_stroke_width"] = 1
R = self.radius
rect = Rectangle(width=TAU * R, height=2 * R)
rect.set_fill(BLUE_E, 1)
rect.set_stroke(width=0)
p0, p1, p2 = [rect.get_corner(v) for v in (DL, UL, UR)]
h_line = Line(p0, p1)
h_line.set_stroke(RED, 3)
w_line = Line(p1, p2)
w_line.set_stroke(YELLOW, 3)
two_R = TexMobject("2", "R")
two_R.next_to(h_line, LEFT)
two_pi_R = TexMobject("2", "\\pi", "R")
two_pi_R.next_to(w_line, UP)
pre_area_label = TexMobject(
"2\\cdot", "2", "\\pi", "R", "\\cdot R"
)
area_label = TexMobject("4", "\\pi", "R^2")
for label in [area_label, pre_area_label]:
label.next_to(rect.get_center(), UP, SMALL_BUFF)
self.rect_group = VGroup(
rect, h_line, w_line,
two_R, two_pi_R, area_label
)
self.area_label = area_label
self.rect = rect
self.add(rect, h_line, w_line, two_R, two_pi_R)
self.play(
TransformFromCopy(two_R[0], pre_area_label[0]),
TransformFromCopy(two_R[1], pre_area_label[-1]),
TransformFromCopy(two_pi_R, pre_area_label[1:-1]),
)
self.wait()
self.play(
ReplacementTransform(pre_area_label[:2], area_label[:1]),
ReplacementTransform(pre_area_label[2], area_label[1]),
ReplacementTransform(pre_area_label[3:], area_label[2:]),
)
self.wait()
self.play(
self.rect_group.to_corner, UL,
{"buff": MED_SMALL_BUFF}
)
def add_four_circles(self):
rect_group = self.rect_group
radius = self.radius
radii_lines = VGroup(*[
Line(radius * UP, ORIGIN).set_stroke(WHITE, 2)
for x in range(4)
])
radii_lines.arrange_submobjects_in_grid(buff=1.3)
radii_lines[2:].shift(RIGHT)
radii_lines.next_to(rect_group, DOWN, buff=1.3)
R_labels = VGroup(*[
TexMobject("R").next_to(line, LEFT, SMALL_BUFF)
for line in radii_lines
])
unwrap_factor_tracker = ValueTracker(0)
def get_circle(line):
return updating_mobject_from_func(
lambda: self.get_unwrapped_circle(
radius=radius, dr=self.dr,
unwrap_factor=unwrap_factor_tracker.get_value(),
center=line.get_top()
)
)
circles = VGroup(*[get_circle(line) for line in radii_lines])
circle_copies = circles.copy()
for mob in circle_copies:
mob.clear_updaters()
self.play(
LaggedStart(Write, circle_copies, lag_ratio=0.7),
LaggedStart(Write, R_labels),
LaggedStart(ShowCreation, radii_lines),
)
self.remove(circle_copies)
self.add(circles, radii_lines, R_labels)
self.wait()
self.play(
radii_lines[2:].shift, 2.9 * RIGHT,
R_labels[2:].shift, 2.9 * RIGHT,
VGroup(
radii_lines[:2], R_labels[:2]
).to_edge, LEFT, {"buff": 1.0}
)
self.play(
unwrap_factor_tracker.set_value, 1,
run_time=2
)
self.wait()
# Move triangles
triangles = circles.copy()
for triangle in triangles:
triangle.clear_updaters()
border = Polygon(*[
triangle.get_corner(vect)
for vect in (DL, UL, DR)
])
border.set_stroke(WHITE, 1)
triangle.add(border)
triangle.generate_target()
rect = self.rect
for i, triangle in enumerate(triangles):
if i % 2 == 1:
triangle.target.rotate(PI)
vect = UP if i < 2 else DOWN
triangle.target.move_to(rect, vect)
self.play(FadeIn(triangles))
self.add(triangles, triangles.copy(), self.area_label)
self.play(MoveToTarget(triangles[0]))
self.wait()
self.play(LaggedStart(MoveToTarget, triangles))
self.wait()
2018-11-29 17:30:34 -08:00
#
def get_unwrapped_circle(self, radius, dr, unwrap_factor=0, center=ORIGIN):
radii = np.arange(0, radius + dr, dr)
rings = VGroup()
for r in radii:
r_factor = 1 + 3 * (r / radius)
alpha = unwrap_factor**r_factor
alpha = np.clip(alpha, 0, 0.9999)
angle = interpolate(TAU, 0, alpha)
length = TAU * r
arc_radius = length / angle
ring = Arc(
start_angle=-90 * DEGREES,
angle=angle,
radius=arc_radius,
**self.circle_style
)
ring.shift(center + (r - arc_radius) * DOWN)
# ring = AnnularSector(
# inner_radius=r1,
# outer_radius=r2,
# angle=TAU,
# start_angle=-TAU / 4,
# **self.circle_style
# )
rings.add(ring)
return rings
class ShowProjection(SphereCylinderScene):
CONFIG = {
# "default_angled_camera_position": {
# "theta": -155 * DEGREES,
# }
}
def construct(self):
self.setup_shapes()
self.show_many_tiny_rectangles()
self.project_all_rectangles()
self.focus_on_one()
self.label_sides()
self.show_length_scaled_up()
self.show_height_scaled_down()
def setup_shapes(self):
self.sphere = self.get_sphere()
self.cylinder = self.get_cylinder()
self.ghost_sphere = self.get_ghost_surface(self.sphere)
self.ghost_sphere.scale(0.99)
self.ghost_cylinder = self.get_ghost_surface(self.cylinder)
self.ghost_cylinder.set_stroke(width=0)
self.add(self.get_axes())
self.set_camera_to_default_position()
self.begin_ambient_camera_rotation()
def show_many_tiny_rectangles(self):
ghost_sphere = self.ghost_sphere
pieces = self.sphere.copy()
random.shuffle(pieces.submobjects)
for piece in pieces:
piece.save_state()
pieces.space_out_submobjects(2)
pieces.fade(1)
self.add(ghost_sphere)
self.play(LaggedStart(Restore, pieces))
self.wait()
self.pieces = pieces
def project_all_rectangles(self):
pieces = self.pieces
for piece in pieces:
piece.save_state()
piece.generate_target()
self.project_mobject(piece.target)
piece.target.set_fill(opacity=0.5)
example_group = self.get_example_group([1, -1, 2])
proj_lines = example_group[1]
self.example_group = example_group
self.play(*map(ShowCreation, proj_lines))
self.play(
LaggedStart(MoveToTarget, pieces),
)
self.wait()
def focus_on_one(self):
ghost_cylinder = self.ghost_cylinder
pieces = self.pieces
example_group = self.example_group
original_rect, rect_proj_lines, rect = example_group
equation = self.get_equation(rect)
lhs, equals, rhs = equation[:3]
lhs.save_state()
rhs.save_state()
self.play(
FadeIn(ghost_cylinder),
FadeOut(pieces),
FadeIn(original_rect),
)
self.play(TransformFromCopy(original_rect, rect))
self.wait()
self.add_fixed_in_frame_mobjects(lhs, equals, rhs)
self.move_fixed_in_frame_mob_to_unfixed_mob(lhs, original_rect)
self.move_fixed_in_frame_mob_to_unfixed_mob(rhs, rect)
self.play(
Restore(lhs),
Restore(rhs),
FadeInFromDown(equals),
)
self.wait(5)
self.equation = equation
self.example_group = example_group
def label_sides(self):
sphere = self.sphere
equation = self.equation
2018-11-29 17:30:34 -08:00
w_brace, h_brace = equation.braces
width_label, height_label = equation.labels
u_values, v_values = sphere.get_u_values_and_v_values()
radius = sphere.radius
lat_lines = VGroup(*[
ParametricFunction(
lambda t: radius * sphere.func(u, t),
t_min=sphere.v_min,
t_max=sphere.v_max,
)
for u in u_values
])
lon_lines = VGroup(*[
ParametricFunction(
lambda t: radius * sphere.func(t, v),
t_min=sphere.u_min,
t_max=sphere.u_max,
)
for v in v_values
])
for lines in lat_lines, lon_lines:
for line in lines:
line.add(DashedMobject(line, spacing=-1))
line.set_points([])
line.set_stroke(width=2)
lines.set_shade_in_3d(True)
lat_lines.set_color(RED)
lon_lines.set_color(PINK)
self.play(
*map(ShowCreationThenDestruction, lat_lines),
run_time=2
)
2018-11-29 17:30:34 -08:00
self.add_fixed_in_frame_mobjects(w_brace, width_label)
self.play(
2018-11-29 17:30:34 -08:00
GrowFromCenter(w_brace),
Write(width_label),
)
self.wait()
self.play(
*map(ShowCreationThenDestruction, lon_lines),
run_time=2
)
self.add_fixed_in_frame_mobjects(h_brace, height_label)
self.play(
GrowFromCenter(h_brace),
Write(height_label),
)
self.wait(2)
def show_length_scaled_up(self):
ghost_sphere = self.ghost_sphere
example_group = self.example_group
equation = self.equation
equation.save_state()
new_example_groups = [
self.get_example_group([1, -1, z])
for z in [6, 0.25]
]
r1, lines, r2 = example_group
self.stop_ambient_camera_rotation()
self.move_camera(
phi=65 * DEGREES,
theta=-80 * DEGREES,
added_anims=[
ghost_sphere.set_stroke, {"opacity": 0.1},
lines.set_stroke, {"width": 3},
]
)
for eg in new_example_groups:
eg[1].set_stroke(width=3)
self.show_length_stretch_of_rect(example_group)
all_example_groups = [example_group, *new_example_groups]
for eg1, eg2 in zip(all_example_groups, all_example_groups[1:]):
if eg1 is new_example_groups[0]:
self.move_camera(
phi=70 * DEGREES,
theta=-110 * DEGREES
)
self.remove(eg1)
self.play(
ReplacementTransform(eg1.deepcopy(), eg2),
Transform(
equation,
self.get_equation(eg2[2])
)
)
if eg1 is example_group:
self.move_camera(
phi=0,
theta=-90 * DEGREES,
)
self.show_length_stretch_of_rect(eg2)
self.play(
ReplacementTransform(all_example_groups[-1], example_group),
Restore(equation)
)
def show_length_stretch_of_rect(self, example_group):
s_rect, proj_lines, c_rect = example_group
rects = VGroup(s_rect, c_rect)
line1, line2 = lines = VGroup(*[
Line(m.get_anchors()[1], m.get_anchors()[2])
for m in rects
])
lines.set_stroke(YELLOW, 5)
lines.set_shade_in_3d(True)
proj_lines_to_fade = VGroup(
proj_lines[0],
proj_lines[3],
)
self.play(
FadeIn(lines[0]),
FadeOut(rects),
FadeOut(proj_lines_to_fade)
)
for x in range(3):
anims = []
if lines[1] in self.mobjects:
anims.append(FadeOut(lines[1]))
self.play(
TransformFromCopy(lines[0], lines[1]),
*anims
)
self.play(
FadeOut(lines),
FadeIn(rects),
FadeIn(proj_lines_to_fade),
)
self.remove(rects, proj_lines_to_fade)
self.add(example_group)
def show_height_scaled_down(self):
ghost_sphere = self.ghost_sphere
ghost_cylinder = self.ghost_cylinder
example_group = self.example_group
equation = self.equation
r1, lines, r2 = example_group
to_fade = VGroup(*[
mob
for mob in it.chain(ghost_sphere, ghost_cylinder)
if np.dot(mob.get_center(), [1, 1, 0]) < 0
])
to_fade.save_state()
new_example_groups = [
self.get_example_group([1, -1, z])
for z in [6, 0.25]
]
for eg in new_example_groups:
eg[::2].set_stroke(YELLOW, 2)
eg.set_stroke(width=1)
all_example_groups = VGroup(example_group, *new_example_groups)
self.play(
to_fade.shift, IN,
to_fade.fade, 1,
remover=True
)
self.move_camera(
phi=85 * DEGREES,
theta=-135 * DEGREES,
added_anims=[
lines.set_stroke, {"width": 1},
r1.set_stroke, YELLOW, 2, 1,
r2.set_stroke, YELLOW, 2, 1,
]
)
self.show_shadow(example_group)
for eg1, eg2 in zip(all_example_groups, all_example_groups[1:]):
self.remove(*eg1.get_family())
self.play(
ReplacementTransform(eg1.deepcopy(), eg2),
Transform(
equation,
self.get_equation(eg2[2])
)
)
self.show_shadow(eg2)
self.move_camera(
phi=70 * DEGREES,
theta=-115 * DEGREES,
added_anims=[
ReplacementTransform(
all_example_groups[-1],
example_group,
),
Restore(equation),
]
)
self.begin_ambient_camera_rotation()
self.play(Restore(to_fade))
self.wait(5)
def show_shadow(self, example_group):
s_rect, lines, c_rect = example_group
for x in range(3):
self.play(TransformFromCopy(s_rect, c_rect))
#
def get_projection_lines(self, piece):
result = VGroup()
radius = self.sphere_config["radius"]
for corner in piece.get_anchors()[:-1]:
start = np.array(corner)
end = np.array(corner)
start[:2] = np.zeros(2)
end[:2] = (radius + 0.03) * normalize(end[:2])
kwargs = {
"color": YELLOW,
"stroke_width": 0.5,
}
result.add(VGroup(*[
Line(p1, p2, **kwargs)
for p1, p2 in [(start, corner), (corner, end)]
]))
result.set_shade_in_3d(True)
return result
def get_equation(self, rect):
length = get_norm(rect.get_anchors()[1] - rect.get_anchors()[0])
height = get_norm(rect.get_anchors()[2] - rect.get_anchors()[1])
lhs = Rectangle(width=length, height=height)
rhs = Rectangle(width=height, height=length)
eq_rects = VGroup(lhs, rhs)
eq_rects.set_stroke(width=0)
eq_rects.set_fill(YELLOW, 1)
eq_rects.scale(2)
equals = TexMobject("=")
equation = VGroup(lhs, equals, rhs)
equation.arrange_submobjects(RIGHT)
equation.to_corner(UR)
brace = Brace(Line(ORIGIN, 0.5 * RIGHT), DOWN)
2018-11-29 17:30:34 -08:00
w_brace = brace.copy().match_width(lhs, stretch=True)
h_brace = brace.copy().rotate(-90 * DEGREES)
h_brace.match_height(lhs, stretch=True)
2018-11-29 17:30:34 -08:00
w_brace.next_to(lhs, DOWN, buff=SMALL_BUFF)
h_brace.next_to(lhs, LEFT, buff=SMALL_BUFF)
2018-11-29 17:30:34 -08:00
braces = VGroup(w_brace, h_brace)
2018-11-29 17:30:34 -08:00
width_label = TextMobject("Width")
height_label = TextMobject("Height")
2018-11-29 17:30:34 -08:00
labels = VGroup(width_label, height_label)
labels.scale(0.75)
2018-11-29 17:30:34 -08:00
width_label.next_to(w_brace, DOWN, SMALL_BUFF)
height_label.next_to(h_brace, LEFT, SMALL_BUFF)
equation.braces = braces
equation.labels = labels
equation.add(braces, labels)
return equation
def move_fixed_in_frame_mob_to_unfixed_mob(self, m1, m2):
phi = self.camera.phi_tracker.get_value()
theta = self.camera.theta_tracker.get_value()
target = m2.get_center()
target = rotate_vector(target, -theta - 90 * DEGREES, OUT)
target = rotate_vector(target, -phi, RIGHT)
m1.move_to(target)
m1.scale(0.1)
m1.shift(SMALL_BUFF * UR)
return m1
def get_example_group(self, vect):
pieces = self.pieces
rect = pieces[np.argmax([
np.dot(r.saved_state.get_center(), vect)
for r in pieces
])].saved_state.copy()
rect_proj_lines = self.get_projection_lines(rect)
rect.set_fill(YELLOW, 1)
original_rect = rect.copy()
self.project_mobject(rect)
rect.shift(
0.001 * np.array([*rect.get_center()[:2], 0])
)
result = VGroup(original_rect, rect_proj_lines, rect)
result.set_shade_in_3d(True)
return result
class SlantedShadowSquishing(ShowShadows):
CONFIG = {
"num_reorientations": 4,
"random_seed": 2,
}
def setup(self):
ShowShadows.setup(self)
self.surface_area_label.fade(1)
self.shadow_area_label.fade(1)
self.shadow_area_decimal.fade(1)
def construct(self):
# Show creation
self.set_camera_orientation(
phi=70 * DEGREES,
theta=-150 * DEGREES,
)
self.begin_ambient_camera_rotation(0.01)
square = self.obj3d.deepcopy()
square.clear_updaters()
shadow = updating_mobject_from_func(lambda: get_shadow(square))
# Reorient
self.add(square, shadow)
for n in range(self.num_reorientations):
angle = 40 * DEGREES
self.play(
Rotate(square, angle, axis=RIGHT),
run_time=2
)
def get_object(self):
square = Square()
square.set_shade_in_3d(True)
square.set_height(2)
square.set_stroke(WHITE, 0.5)
square.set_fill(BLUE_C, 1)
return square
class JustifyLengthStretch(ShowProjection):
CONFIG = {
"R_color": RED,
"d_color": WHITE,
"d_ambiguity_iterations": 4,
}
def construct(self):
self.setup_shapes()
self.add_ghosts()
self.add_example_group()
self.cut_cross_section()
self.label_R()
self.label_d()
self.show_similar_triangles()
def add_ghosts(self):
self.add(self.ghost_sphere, self.ghost_cylinder)
def add_example_group(self):
self.pieces = self.sphere
for piece in self.pieces:
piece.save_state()
self.example_group = self.get_example_group([1, 0.1, 1.5])
self.add(self.example_group)
self.set_camera_orientation(theta=-45 * DEGREES)
def cut_cross_section(self):
sphere = self.ghost_sphere
cylinder = self.ghost_cylinder
to_fade = VGroup(*[
mob
for mob in it.chain(sphere, cylinder)
if np.dot(mob.get_center(), DOWN) > 0
])
self.lost_hemisphere = to_fade
to_fade.save_state()
circle = Circle(
stroke_width=2,
stroke_color=PINK,
radius=self.sphere.radius
)
circle.rotate(90 * DEGREES, RIGHT)
self.circle = circle
self.example_group.set_stroke(YELLOW, 1)
self.stop_ambient_camera_rotation()
self.play(
Rotate(
to_fade, -PI,
axis=OUT,
about_point=sphere.get_left(),
run_time=2
),
VFadeOut(to_fade, run_time=2),
FadeIn(circle),
)
2018-11-29 17:30:34 -08:00
# self.move_camera(
# phi=80 * DEGREES,
# theta=-85 * DEGREES,
# )
def label_R(self):
circle = self.circle
R_line = Line(ORIGIN, circle.get_right())
R_line.set_color(self.R_color)
R_label = TexMobject("R")
2018-11-29 17:30:34 -08:00
R_label.next_to(R_line, IN)
self.add_fixed_orientation_mobjects(R_label)
self.play(
ShowCreation(R_line),
FadeInFrom(R_label, IN),
)
self.wait()
self.R_line = R_line
self.R_label = R_label
def label_d(self):
example_group = self.example_group
s_rect = example_group[0]
d_line = self.get_d_line(
s_rect.get_corner(IN + RIGHT + DOWN)
)
alt_d_line = self.get_d_line(
s_rect.get_corner(OUT + LEFT + DOWN)
)
d_label = TexMobject("d")
2018-11-29 17:30:34 -08:00
d_label.next_to(d_line, IN)
self.add_fixed_orientation_mobjects(d_label)
self.play(
ShowCreation(d_line),
FadeInFrom(d_label, IN),
)
self.wait()
for x in range(self.d_ambiguity_iterations):
to_fade_out = [d_line, alt_d_line][x % 2]
to_fade_in = [d_line, alt_d_line][(x + 1) % 2]
self.play(
FadeIn(to_fade_in),
FadeOut(to_fade_out),
2018-11-29 17:30:34 -08:00
d_label.next_to, to_fade_in, IN,
)
self.d_line = d_line
self.d_label = d_label
def show_similar_triangles(self):
d_line = self.d_line
d_label = self.d_label
R_line = self.R_line
R_label = self.R_label
example_group = self.example_group
2018-11-29 17:30:34 -08:00
s_rect, proj_lines, c_rect = example_group
p1 = s_rect.get_anchors()[1]
p2 = s_rect.get_anchors()[2]
p0 = np.array(p1)
p0[:2] = np.zeros(2)
triangle = Polygon(p0, p1, p2)
triangle.set_stroke(width=0)
triangle.set_fill(GREEN, opacity=1)
base = Line(p1, p2)
base.set_stroke(PINK, 3)
big_triangle = Polygon(
p0, self.project_point(p1), self.project_point(p2)
)
big_triangle.set_stroke(width=0)
big_triangle.set_fill(RED, opacity=1)
equation = VGroup(
2018-11-29 17:30:34 -08:00
triangle.copy().rotate(-3 * DEGREES),
TexMobject("\\sim"),
2018-11-29 17:30:34 -08:00
big_triangle.copy().rotate(-3 * DEGREES)
)
equation.arrange_submobjects(RIGHT, buff=SMALL_BUFF)
equation.to_corner(UL)
eq_d = TexMobject("d").next_to(equation[0], DOWN, SMALL_BUFF)
eq_R = TexMobject("R").next_to(equation[2], DOWN, SMALL_BUFF)
2018-11-29 17:30:34 -08:00
VGroup(equation, eq_d, eq_R).rotate(20 * DEGREES, axis=RIGHT)
for mob in [triangle, big_triangle]:
mob.set_shade_in_3d(True)
mob.set_fill(opacity=0.8)
self.move_camera(
2018-11-29 17:30:34 -08:00
phi=40 * DEGREES,
theta=-85 * DEGREES,
added_anims=[
d_label.next_to, d_line, DOWN, SMALL_BUFF,
2018-11-29 17:30:34 -08:00
d_line.set_stroke, {"width": 1},
FadeOut(proj_lines[0]),
FadeOut(proj_lines[3]),
FadeOut(R_label)
],
run_time=2,
)
2018-11-29 17:30:34 -08:00
point = VectorizedPoint(p0)
point.set_shade_in_3d(True)
self.play(
ReplacementTransform(point, triangle),
Animation(self.camera.phi_tracker)
)
self.add_fixed_in_frame_mobjects(equation, eq_d, eq_R)
self.play(
2018-11-29 17:30:34 -08:00
FadeInFrom(equation[0], 7 * RIGHT + 2.5 * DOWN),
FadeIn(equation[1:]),
FadeInFromDown(eq_d),
FadeInFromDown(eq_R),
2018-11-29 17:30:34 -08:00
Animation(self.camera.phi_tracker)
)
self.wait()
2018-11-29 17:30:34 -08:00
for x in range(2):
self.play(ShowCreationThenDestruction(base))
self.wait()
d_line_copy = d_line.copy().set_stroke(WHITE, 3)
self.play(ShowCreation(d_line_copy))
self.play(FadeOut(d_line_copy))
self.wait(2)
R_label.next_to(R_line, DOWN, SMALL_BUFF)
R_label.shift(0.25 * IN)
self.play(
ReplacementTransform(triangle, big_triangle),
FadeIn(R_label),
2018-11-29 17:30:34 -08:00
Animation(self.camera.phi_tracker)
)
self.wait(3)
self.move_camera(
phi=70 * DEGREES,
theta=-70 * DEGREES,
added_anims=[
2018-11-29 17:30:34 -08:00
big_triangle.set_fill, {"opacity": 0.25},
d_label.next_to, d_line, IN, {"buff": 0.3},
]
)
self.begin_ambient_camera_rotation()
lost_hemisphere = self.lost_hemisphere
lost_hemisphere.restore()
left_point = self.sphere.get_left()
lost_hemisphere.rotate(-PI, axis=OUT, about_point=left_point)
self.play(
Rotate(
lost_hemisphere, PI,
axis=OUT,
about_point=left_point,
2018-11-29 17:30:34 -08:00
run_time=2,
),
VFadeIn(lost_hemisphere),
FadeOut(self.circle),
R_line.set_color, self.R_color,
)
self.wait(10)
#
def get_d_line(self, sphere_point):
end = sphere_point
start = np.array(end)
start[:2] = np.zeros(2)
d_line = Line(start, end)
d_line.set_color(self.d_color)
return d_line
2018-11-29 17:30:34 -08:00
class JustifyLengthStretchHigherRes(JustifyLengthStretch):
CONFIG = {
"sphere_config": {
"resolution": (2 * 24, 2 * 48)
},
}
class JustifyLengthStretchHighestRes(JustifyLengthStretch):
CONFIG = {
"sphere_config": {
"resolution": (4 * 24, 4 * 48)
},
}
class LengthScaleLabel(Scene):
def construct(self):
text = TexMobject(
"\\text{Length scale factor} =",
"\\frac{R}{d}"
)
self.play(Write(text))
self.wait()
class TinierAndTinerRectangles(SphereCylinderScene):
2018-11-29 17:30:34 -08:00
CONFIG = {
"n_iterations": 5,
}
def construct(self):
spheres = [
self.get_sphere(
resolution=(12 * (2**n), 24 * (2**n)),
stroke_width=0,
)
2018-11-29 17:30:34 -08:00
for n in range(self.n_iterations)
]
2018-11-29 17:30:34 -08:00
self.set_camera_to_default_position()
2018-11-29 17:30:34 -08:00
self.add(self.get_axes())
self.begin_ambient_camera_rotation()
self.add(spheres[0])
for s1, s2 in zip(spheres, spheres[1:]):
self.wait()
random.shuffle(s2.submobjects)
for piece in s2:
piece.save_state()
s2.space_out_submobjects(1.2)
s2.fade(1)
for piece in s1:
piece.add(VectorizedPoint(piece.get_center() / 2))
self.play(
LaggedStart(Restore, s2)
)
self.remove(s1)
2018-11-29 17:30:34 -08:00
self.wait(5)
class LimitViewToCrossSection(JustifyLengthStretch):
CONFIG = {
"d_ambiguity_iterations": 0,
}
def construct(self):
self.setup_shapes()
self.add_ghosts()
self.add_example_group()
self.cut_cross_section()
self.label_R()
self.label_d()
self.move_camera(
phi=90 * DEGREES,
theta=-90 * DEGREES,
)
self.play(
FadeOut(self.ghost_sphere),
FadeOut(self.ghost_cylinder),
)
class JustifyHeightSquish(MovingCameraScene):
CONFIG = {
2018-11-29 17:30:34 -08:00
# As measured from previous scene
"top_phi": 0.5242654422649652,
"low_phi": 0.655081802831207,
2018-11-29 17:30:34 -08:00
"radius": 2,
"R_color": RED,
"d_color": WHITE,
"little_triangle_color": BLUE,
"big_triangle_color": GREY_BROWN,
"alpha_color": WHITE,
"beta_color": WHITE,
}
def construct(self):
2018-11-29 17:30:34 -08:00
self.recreate_cross_section()
self.show_little_triangle()
self.show_tangent_to_radius()
self.tweak_parameter()
self.label_angles()
def recreate_cross_section(self):
axes = Axes(
number_line_config={
"unit_size": 2,
}
)
circle = Circle(
radius=self.radius,
stroke_color=PINK,
stroke_width=2,
)
R_line = self.get_R_line(90 * DEGREES)
R_line.set_color(self.R_color)
R_label = TexMobject("R")
R_label.next_to(R_line, DOWN, SMALL_BUFF)
d_lines = VGroup(*[
self.get_d_line(phi)
for phi in [self.low_phi, self.top_phi]
])
d_line = d_lines[0]
d_line.set_color(self.d_color)
d_label = TexMobject("d")
d_label.next_to(d_line, DOWN, SMALL_BUFF)
proj_lines = VGroup(*[
self.get_R_line(phi)
for phi in [self.top_phi, self.low_phi]
])
proj_lines.set_stroke(YELLOW, 1)
s_rect_line, c_rect_line = [
Line(
*[l.get_end() for l in lines],
stroke_color=YELLOW,
stroke_width=2,
)
for lines in [d_lines, proj_lines]
]
mobjects = [
axes, circle,
R_line, d_line,
R_label, d_label,
proj_lines,
s_rect_line, c_rect_line,
]
self.add(*mobjects)
self.set_variables_as_attrs(*mobjects)
def show_little_triangle(self):
phi = self.low_phi
d_phi = abs(self.low_phi - self.top_phi)
tri_group = self.get_double_triangle_group(phi, d_phi)
lil_tri, big_tri = tri_group
frame = self.camera_frame
frame.save_state()
scale_factor = 0.1
sw_sf = 0.2 # stroke_width scale factor
d_sf = 0.3 # d_label scale factor
hyp = lil_tri.hyp
leg = lil_tri.leg2
leg.rotate(PI)
VGroup(hyp, leg).set_stroke(YELLOW, 1)
hyp_word = TextMobject("Rectangle height $\\rightarrow$")
leg_word = TextMobject("$\\leftarrow$ Projection")
words = VGroup(hyp_word, leg_word)
words.set_height(0.4 * lil_tri.get_height())
words.set_background_stroke(width=0)
hyp_word.next_to(hyp.get_center(), LEFT, buff=0.05)
leg_word.next_to(leg, RIGHT, buff=0.02)
stroke_width_changers = VGroup()
for mob in self.mobjects:
if mob in [self.d_label, frame]:
continue
mob.generate_target()
mob.save_state()
mob.target.set_stroke(
width=sw_sf * mob.get_stroke_width()
)
stroke_width_changers.add(mob)
self.play(
frame.scale, scale_factor,
frame.move_to, lil_tri,
self.d_label.scale, d_sf, {"about_edge": UP},
*map(MoveToTarget, stroke_width_changers)
)
self.play(DrawBorderThenFill(lil_tri, stroke_width=0.5))
self.wait()
self.play(
ShowCreation(hyp),
LaggedStart(
DrawBorderThenFill, hyp_word,
stroke_width=0.5,
run_time=1,
),
)
self.wait()
self.play(TransformFromCopy(hyp, leg))
self.play(TransformFromCopy(
hyp_word, leg_word,
path_arc=-PI / 2,
))
self.wait()
self.play(
frame.restore,
self.d_label.scale, 1 / d_sf, {"about_edge": UP},
*map(Restore, stroke_width_changers),
run_time=3
)
self.set_variables_as_attrs(
hyp_word, leg_word, tri_group
)
def show_tangent_to_radius(self):
tri_group = self.tri_group
lil_tri, big_tri = tri_group
lil_hyp = lil_tri.hyp
phi = self.low_phi
circle_point = self.get_circle_point(phi)
tangent = lil_hyp.copy()
tangent.set_stroke(WHITE, 2)
tangent.scale(5 / tangent.get_length())
tangent.move_to(circle_point)
R_line = self.R_line
R_label = self.R_label
d_label = self.d_label
elbow = Elbow(angle=(-phi - PI / 2), width=0.15)
elbow.shift(circle_point)
elbow.set_stroke(WHITE, 1)
self.tangent_elbow = elbow
self.play(GrowFromPoint(tangent, circle_point))
self.wait()
self.play(
Rotate(
R_line, 90 * DEGREES - phi,
about_point=ORIGIN,
),
R_label.next_to, 0.5 * circle_point, DR, {"buff": 0},
d_label.shift, SMALL_BUFF * UL,
)
self.play(ShowCreation(elbow))
self.wait()
self.add(big_tri, d_label, R_line, elbow)
d_label.set_background_stroke(width=0)
self.play(DrawBorderThenFill(big_tri))
self.wait()
self.set_variables_as_attrs(tangent, elbow)
def tweak_parameter(self):
tri_group = self.tri_group
lil_tri = tri_group[0]
d_label = self.d_label
d_line = self.d_line
R_label = self.R_label
R_line = self.R_line
frame = self.camera_frame
to_fade = VGroup(
self.proj_lines,
self.s_rect_line, self.c_rect_line,
self.hyp_word, self.leg_word,
lil_tri.hyp, lil_tri.leg2,
)
rad_tangent = VGroup(
R_line,
self.tangent,
self.elbow,
)
phi_tracker = ValueTracker(self.low_phi)
self.play(
frame.scale, 0.6,
frame.shift, UR,
R_label.scale, 0.6, {"about_edge": UL},
d_label.scale, 0.6,
{"about_point": d_label.get_top() + SMALL_BUFF * DOWN},
*map(FadeOut, to_fade),
)
curr_phi = self.low_phi
d_phi = abs(self.top_phi - self.low_phi)
alt_phis = [
80 * DEGREES,
20 * DEGREES,
50 * DEGREES,
curr_phi
]
for new_phi in alt_phis:
self.add(tri_group, d_label)
self.play(
phi_tracker.set_value, new_phi,
UpdateFromFunc(
tri_group,
lambda tg: tg.become(
self.get_double_triangle_group(
phi_tracker.get_value(),
d_phi
)
)
),
Rotate(
rad_tangent,
-(new_phi - curr_phi),
about_point=ORIGIN,
),
MaintainPositionRelativeTo(R_label, R_line),
UpdateFromFunc(
d_line,
lambda dl: dl.become(
self.get_d_line(phi_tracker.get_value())
),
),
MaintainPositionRelativeTo(d_label, d_line),
run_time=2
)
self.wait()
curr_phi = new_phi
for tri in tri_group:
self.play(Indicate(tri))
self.wait()
self.play(*map(FadeIn, to_fade))
self.remove(phi_tracker)
def label_angles(self):
# Getting pretty hacky here...
tri_group = self.tri_group
lil_tri, big_tri = tri_group
d_label = self.d_label
R_label = self.R_label
frame = self.camera_frame
alpha = self.low_phi
beta = 90 * DEGREES - alpha
circle_point = self.get_circle_point(alpha)
alpha_arc = Arc(
start_angle=90 * DEGREES,
angle=-alpha,
radius=0.2,
stroke_width=2,
)
beta_arc = Arc(
start_angle=PI,
angle=beta,
radius=0.2,
stroke_width=2,
)
beta_arc.shift(circle_point)
alpha_label = TexMobject("\\alpha")
alpha_label.scale(0.5)
alpha_label.set_color(self.alpha_color)
alpha_label.next_to(alpha_arc, UP, buff=SMALL_BUFF)
alpha_label.shift(0.05 * DR)
beta_label = TexMobject("\\beta")
beta_label.scale(0.5)
beta_label.set_color(self.beta_color)
beta_label.next_to(beta_arc, LEFT, buff=SMALL_BUFF)
beta_label.shift(0.07 * DR)
VGroup(alpha_label, beta_label).set_background_stroke(width=0)
elbow = Elbow(width=0.15, angle=-90 * DEGREES)
elbow.shift(big_tri.get_corner(UL))
elbow.set_stroke(width=2)
equation = TexMobject(
"\\alpha", "+", "\\beta", "+",
"90^\\circ", "=", "180^\\circ"
)
equation.scale(0.6)
equation.next_to(frame.get_corner(UR), DL)
movers = VGroup(
alpha_label.deepcopy(), beta_label.deepcopy(),
elbow.copy()
)
indices = [0, 2, 4]
for mover, index in zip(movers, indices):
mover.target = VGroup(equation[index])
# Show equation
self.play(
FadeOut(d_label),
FadeOut(R_label),
ShowCreation(alpha_arc),
ShowCreation(beta_arc),
)
self.wait()
self.play(FadeInFrom(alpha_label, UP))
self.wait()
self.play(FadeInFrom(beta_label, LEFT))
self.wait()
self.play(ShowCreation(elbow))
self.wait()
self.play(
LaggedStart(MoveToTarget, movers),
LaggedStart(FadeInFromDown, equation[1:4:2])
)
self.wait()
self.play(FadeInFrom(equation[-2:], LEFT))
self.remove(equation, movers)
self.add(equation)
self.wait()
# Zoom in
self.remove(self.tangent_elbow)
stroke_width_changers = VGroup(*[
mob for mob in self.mobjects
if mob not in [
beta_arc, beta_label, frame, equation,
]
])
for mob in stroke_width_changers:
mob.generate_target()
mob.save_state()
mob.target.set_stroke(
width=0.3 * mob.get_stroke_width()
)
equation.set_background_stroke(width=0)
scaled_arcs = VGroup(beta_arc, self.tangent_elbow)
beta_label.set_background_stroke(color=BLACK, width=0.3)
self.play(
ApplyMethod(
VGroup(frame, equation).scale, 0.15,
{"about_point": circle_point + 0.1 * LEFT},
),
ApplyMethod(
beta_label.scale, 0.3,
{"about_point": circle_point},
),
scaled_arcs.set_stroke, {"width": 0.3},
scaled_arcs.scale, 0.3, {"about_point": circle_point},
*map(MoveToTarget, stroke_width_changers)
)
# Show small triangle angles
TexMobject.CONFIG["background_stroke_width"] = 0
words = VGroup(self.hyp_word, self.leg_word)
alpha_arc1 = Arc(
start_angle=90 * DEGREES + beta,
angle=0.95 * alpha,
radius=0.3 * 0.2,
stroke_width=beta_arc.get_stroke_width(),
).shift(circle_point)
alpha_arc2 = Arc(
start_angle=0,
angle=-0.95 * alpha,
radius=0.3 * 0.2,
stroke_width=beta_arc.get_stroke_width(),
).shift(lil_tri.hyp.get_end())
beta_arc1 = Arc(
start_angle=90 * DEGREES,
angle=beta,
radius=0.3 * 0.2,
stroke_width=beta_arc.get_stroke_width(),
).shift(circle_point)
deg90 = TexMobject("90^\\circ")
deg90.set_height(0.8 * beta_label.get_height())
deg90.next_to(self.tangent_elbow, DOWN, buff=0.025)
# deg90.set_background_stroke(width=0)
q_mark = TexMobject("?")
q_mark.set_height(0.5 * beta_label.get_height())
q_mark.next_to(alpha_arc1, LEFT, buff=0.025)
q_mark.shift(0.01 * UP)
alpha_label1 = TexMobject("\\alpha")
alpha_label1.set_height(0.7 * q_mark.get_height())
alpha_label1.move_to(q_mark)
alpha_label2 = alpha_label1.copy()
alpha_label2.next_to(
alpha_arc2, RIGHT, buff=0.01
)
alpha_label2.set_background_stroke(color=BLACK, width=0.3)
beta_label1 = beta_label.copy()
beta_label1.scale(0.7)
beta_label1.set_background_stroke(color=BLACK, width=0.3)
beta_label1.next_to(
beta_arc1, UP, buff=0.01
)
beta_label1.shift(0.01 * LEFT)
self.play(FadeOut(words))
self.play(FadeInFrom(deg90, 0.1 * UP))
self.wait(0.25)
self.play(WiggleOutThenIn(beta_label))
self.wait(0.25)
self.play(
ShowCreation(alpha_arc1),
FadeInFrom(q_mark, 0.1 * RIGHT)
)
self.wait()
self.play(ShowPassingFlash(
self.tangent.copy().scale(0.1).set_stroke(PINK, 0.5)
))
self.wait()
self.play(ReplacementTransform(q_mark, alpha_label1))
self.play(CircleThenFadeAround(
equation,
surrounding_rectangle_config={
"buff": 0.015,
"stroke_width": 0.5,
},
))
self.wait()
self.play(
ShowCreation(alpha_arc2),
FadeIn(alpha_label2),
)
self.play(
ShowCreation(beta_arc1),
FadeIn(beta_label1),
)
self.wait()
#
def get_double_triangle_group(self, phi, d_phi):
p0 = self.get_circle_point(phi)
p1 = self.get_circle_point(phi - d_phi)
p2 = np.array(p1)
p2[0] = p0[0]
little_triangle = Polygon(
p0, p1, p2,
stroke_width=0,
fill_color=self.little_triangle_color,
fill_opacity=1,
)
big_triangle = Polygon(
p0, ORIGIN, p0 - p0[0] * RIGHT,
stroke_width=0,
fill_color=self.big_triangle_color,
fill_opacity=1
)
result = VGroup(little_triangle, big_triangle)
for tri in result:
p0, p1, p2 = tri.get_anchors()[:3]
tri.hyp = Line(p0, p1)
tri.leg1 = Line(p1, p2)
tri.leg2 = Line(p2, p0)
tri.side_lines = VGroup(
tri.hyp, tri.leg1, tri.leg2
)
tri.side_lines.set_stroke(WHITE, 1)
result.set_stroke(width=0)
return result
def get_R_line(self, phi):
y = self.radius * np.cos(phi)
x = self.radius
return Line(ORIGIN, x * RIGHT).shift(y * UP)
def get_d_line(self, phi):
end = self.get_circle_point(phi)
start = np.array(end)
start[0] = 0
return Line(start, end)
def get_circle_point(self, phi):
return rotate_vector(self.radius * UP, -phi)
class WhyAreWeDoingThis(TeacherStudentsScene):
def construct(self):
self.student_says(
"Hang on, what \\\\ are we doing?",
student_index=2,
bubble_kwargs={"direction": LEFT},
target_mode="hesitant"
)
self.change_student_modes(
"maybe", "pondering", "hesitant",
added_anims=[self.teacher.change, "tease"]
)
self.wait(3)
self.play(
RemovePiCreatureBubble(self.students[2]),
self.teacher.change, "raise_right_hand",
self.change_student_modes(*2 * ["pondering"])
)
self.look_at(self.screen)
self.wait(2)
class SameEffectAsRotating(Scene):
def construct(self):
rect1 = Rectangle(
height=2, width=1,
stroke_width=0,
fill_color=YELLOW,
fill_opacity=1,
background_stroke_width=2,
background_stroke_color=BLACK,
)
rect2 = rect1.copy().rotate(-90 * DEGREES)
arrow = Arrow(ORIGIN, RIGHT, buff=0, color=WHITE)
group = VGroup(rect1, arrow, rect2)
group.arrange_submobjects(RIGHT)
group.center()
moving_rect = rect1.copy()
low_brace = updating_mobject_from_func(
lambda: Brace(moving_rect, DOWN, buff=SMALL_BUFF)
)
right_brace = updating_mobject_from_func(
lambda: Brace(moving_rect, RIGHT, buff=SMALL_BUFF)
)
times_R_over_d = TexMobject("\\times \\frac{R}{d}")
times_d_over_R = TexMobject("\\times \\frac{d}{R}")
times_R_over_d.add_updater(
lambda m: m.next_to(low_brace, DOWN, SMALL_BUFF)
)
times_d_over_R.add_updater(
lambda m: m.next_to(right_brace, RIGHT, SMALL_BUFF)
)
self.add(rect1, arrow)
self.play(moving_rect.move_to, rect2)
self.add(low_brace)
self.play(
moving_rect.match_width, rect2, {"stretch": True},
FadeIn(times_R_over_d),
)
self.add(right_brace)
self.play(
moving_rect.match_height, rect2, {"stretch": True},
FadeIn(times_d_over_R),
)
self.wait()
self.play(TransformFromCopy(
rect1, rect2,
path_arc=-90 * DEGREES,
run_time=2
))
class RotateAllPiecesWithExpansion(ShowProjection):
CONFIG = {
"sphere_config": {
"radius": 1.5,
},
"with_expansion": True
}
def construct(self):
self.setup_shapes()
self.rotate_all_pieces()
def rotate_all_pieces(self):
sphere = self.sphere
cylinder = self.cylinder
ghost_sphere = self.ghost_sphere
ghost_sphere.scale(0.99)
# Shuffle sphere and cylinder same way
random.seed(0)
random.shuffle(sphere.submobjects)
random.seed(0)
random.shuffle(cylinder.submobjects)
sphere_target = VGroup()
for piece in sphere:
p0, p1, p2, p3 = piece.get_anchors()[:4]
piece.set_points_as_corners([
p3, p0, p1, p2, p3
])
piece.generate_target()
sphere_target.add(piece.target)
piece.target.move_to(
(1 + random.random()) * piece.get_center()
)
self.add(ghost_sphere, sphere)
self.wait()
if self.with_expansion:
self.play(LaggedStart(
MoveToTarget, sphere
))
self.wait()
self.play(*[
Rotate(piece, 90 * DEGREES, axis=piece.get_center())
for piece in sphere
])
self.wait()
self.play(Transform(sphere, cylinder, run_time=2))
self.wait(5)
class RotateAllPiecesWithoutExpansion(RotateAllPiecesWithExpansion):
CONFIG = {
"with_expansion": False,
}
class ThinkingCritically(PiCreatureScene):
def construct(self):
randy = self.pi_creature
self.play(randy.change, "pondering")
self.wait()
self.play(
randy.change, "hesitant", 2 * UP,
)
self.wait()
self.play(randy.change, "sassy")
self.wait()
self.play(randy.change, "angry")
self.wait(4)
class WriteNotEquals(Scene):
def construct(self):
symbol = TexMobject("\\ne")
symbol.scale(2)
symbol.set_background_stroke(width=0)
self.play(Write(symbol))
self.wait()
class RectangulatedSphere(SphereCylinderScene):
CONFIG = {
"sphere_config": {
"resolution": (10, 20)
},
"uniform_color": False,
"wait_time": 10,
}
def construct(self):
sphere = self.get_sphere()
if self.uniform_color:
sphere.set_stroke(BLUE_E, width=0.5)
sphere.set_fill(BLUE_E)
self.set_camera_to_default_position()
self.begin_ambient_camera_rotation(0.05)
self.add(sphere)
self.wait(self.wait_time)
class SmoothSphere(RectangulatedSphere):
CONFIG = {
"sphere_config": {
"resolution": (200, 400),
},
"uniform_color": True,
"wait_time": 0,
}
class SequenceOfSpheres(SphereCylinderScene):
def construct(self):
n_shapes = 4
spheres, cylinders = groups = VGroup(*[
VGroup(*[
func(resolution=(n, 2 * n))
for k in range(1, n_shapes + 1)
for n in [3 * (2**k)]
])
for func in [self.get_sphere, self.get_cylinder]
])
groups.scale(0.5)
for group in groups:
for shape in group:
for piece in shape:
piece.make_jagged()
shape.set_stroke(width=0)
for group in groups:
group.add(self.get_oriented_tex("?").scale(2))
group.arrange_submobjects(RIGHT, buff=LARGE_BUFF)
groups.arrange_submobjects(IN, buff=1.5)
all_equals = VGroup()
for sphere, cylinder in zip(spheres, cylinders):
equals = self.get_oriented_tex("=")
equals.scale(1.5)
equals.rotate(90 * DEGREES, UP)
equals.move_to(interpolate(
sphere.get_nadir(), cylinder.get_zenith(), 0.5
))
all_equals.add(equals)
all_equals.remove(all_equals[-1])
arrow_groups = VGroup()
for group in groups:
arrow_group = VGroup()
for m1, m2 in zip(group, group[1:]):
arrow = self.get_oriented_tex("\\rightarrow")
arrow.move_to(interpolate(
m1.get_right(), m2.get_left(), 0.5
))
arrow_group.add(arrow)
arrow_groups.add(arrow_group)
q_marks = VGroup(*[
group[-1]
for group in groups
])
final_arrows = VGroup(
arrow_groups[0][-1],
arrow_groups[1][-1],
)
for arrow in final_arrows:
dots = self.get_oriented_tex("\\dots")
dots.next_to(arrow, RIGHT, SMALL_BUFF)
arrow.add(dots)
q_marks.shift(MED_LARGE_BUFF * RIGHT)
tilted_final_arrows = VGroup(
final_arrows[0].copy().rotate(
-45 * DEGREES, axis=DOWN
).shift(0.75 * IN),
final_arrows[1].copy().rotate(
45 * DEGREES, axis=DOWN
).shift(0.75 * OUT),
)
final_q_mark = q_marks[0].copy()
final_q_mark.move_to(q_marks)
self.set_camera_orientation(
phi=80 * DEGREES,
theta=-90 * DEGREES,
)
for i in range(n_shapes):
anims = [
FadeInFrom(spheres[i], LEFT),
FadeInFrom(cylinders[i], LEFT),
]
if i > 0:
anims += [
Write(arrow_group[i - 1])
for arrow_group in arrow_groups
]
self.play(*anims, run_time=1)
self.play(GrowFromCenter(all_equals[i]))
self.play(
FadeInFrom(q_marks, LEFT),
Write(final_arrows)
)
self.wait()
self.play(
Transform(final_arrows, tilted_final_arrows),
Transform(q_marks, VGroup(final_q_mark)),
)
self.wait()
def get_oriented_tex(self, tex):
result = TexMobject(tex)
result.rotate(90 * DEGREES, RIGHT)
return result
class WhatIsSurfaceArea(SpecialThreeDScene):
def construct(self):
title = TextMobject("What is surface area?")
title.scale(1.5)
title.to_edge(UP)
title.shift(0.035 * RIGHT)
self.add_fixed_in_frame_mobjects(title)
power_tracker = ValueTracker(1)
surface = updating_mobject_from_func(
lambda: self.get_surface(
radius=3,
amplitude=1,
power=power_tracker.get_value()
)
)
pieces = surface.copy()
pieces.clear_updaters()
random.shuffle(pieces.submobjects)
self.set_camera_to_default_position()
self.begin_ambient_camera_rotation()
self.add(self.get_axes())
self.play(LaggedStart(
DrawBorderThenFill, pieces,
lag_ratio=0.2,
))
self.remove(pieces)
self.add(surface)
self.play(
power_tracker.set_value, 5,
run_time=2
)
self.play(
power_tracker.set_value, 1,
run_time=2
)
self.wait(2)
def get_surface(self, radius, amplitude, power):
def alt_pow(x, y):
return np.sign(x) * (np.abs(x) ** y)
return ParametricSurface(
lambda u, v: radius * np.array([
v * np.cos(TAU * u),
v * np.sin(TAU * u),
0,
]) + amplitude * np.array([
0,
0,
(v**2) * alt_pow(np.sin(5 * TAU * u), power),
]),
resolution=(100, 20),
v_min=0.01
)
class UnwrappedCircleLogic(UnfoldCircles):
def construct(self):
radius = 1.25
dr = 0.001
TexMobject.CONFIG["background_stroke_width"] = 2
unwrap_factor_tracker = ValueTracker(0)
center_tracker = VectorizedPoint()
highligt_prop_tracker = ValueTracker(0.5)
def get_highlight_prop():
return highligt_prop_tracker.get_value()
def get_r():
return radius * get_highlight_prop()
center_tracker.move_to(4.5 * LEFT)
def get_unwrapped_circle():
result = self.get_unwrapped_circle(
radius=radius, dr=dr,
unwrap_factor=unwrap_factor_tracker.get_value(),
center=center_tracker.get_center()
)
self.get_submob_from_prop(
result, get_highlight_prop()
).set_color(YELLOW)
return result
unwrapped_circle = updating_mobject_from_func(get_unwrapped_circle)
circle = unwrapped_circle.copy()
circle.clear_updaters()
R_line = Line(circle.get_center(), circle.get_bottom())
R_line.set_stroke(WHITE, 2)
R_label = TexMobject("R")
R_label.next_to(R_line, LEFT)
circle_group = VGroup(circle, R_line, R_label)
tri_R_line = updating_mobject_from_func(
lambda: Line(
ORIGIN, radius * DOWN
).shift(center_tracker.get_center())
)
# Unwrap
self.play(FadeInFromDown(circle_group))
self.add(circle_group, unwrapped_circle, tri_R_line, R_label)
circle_group.set_stroke(opacity=0.5)
self.play(
unwrap_factor_tracker.set_value, 1,
run_time=2
)
self.play(
center_tracker.move_to,
circle.get_right() + (radius + MED_SMALL_BUFF) * RIGHT,
circle_group.set_stroke, {"opacity": 1},
)
self.wait()
# Change radius
r_line = updating_mobject_from_func(
lambda: Line(
ORIGIN, get_r() * DOWN,
stroke_width=2,
stroke_color=WHITE,
).shift(circle.get_center())
)
r_label = TexMobject("r")
r_label.add_updater(
lambda m: m.next_to(r_line, LEFT, SMALL_BUFF)
)
two_pi_r_label = TexMobject("2\\pi r")
two_pi_r_label.add_updater(
lambda m: m.next_to(
self.get_submob_from_prop(
unwrapped_circle,
get_highlight_prop(),
), DOWN, SMALL_BUFF
)
)
circle.add_updater(
lambda m: m.match_style(unwrapped_circle)
)
self.play(
ReplacementTransform(R_line, r_line),
ReplacementTransform(R_label, r_label),
FadeInFromDown(
two_pi_r_label.copy().clear_updaters(),
remover=True
)
)
self.add(two_pi_r_label)
for prop in [0.2, 0.8, 0.5]:
self.play(
highligt_prop_tracker.set_value, prop,
run_time=2
)
# Show line
line = Line(*[
unwrapped_circle.get_corner(vect)
for vect in (UL, DR)
])
line.set_color(PINK)
line.set_fill(BLACK, 1)
line_word = TextMobject("Line")
line_word.next_to(ORIGIN, UP, SMALL_BUFF)
line_word.rotate(line.get_angle(), about_point=ORIGIN)
line_word.shift(line.get_center())
curve = line.copy()
curve.points[1] = unwrapped_circle.get_corner(DL)
not_line = TextMobject("Not line")
not_line.rotate(line.get_angle() / 2)
not_line.move_to(line_word)
not_line.shift(0.3 * DOWN)
self.play(
ShowCreation(line),
Write(line_word),
)
self.wait()
self.play(highligt_prop_tracker.set_value, 1)
self.wait()
# Bend
line.save_state()
line_word.save_state()
self.play(
Transform(line, curve),
Transform(line_word, not_line),
)
self.wait()
self.play(
Restore(line),
Restore(line_word),
# FadeIn(two_pi_r_label),
)
self.wait()
def get_submob_from_prop(self, mob, prop):
n = len(mob.submobjects)
return mob[min(int(prop * n), n - 1)]
class AskAboutDirectConnection(TeacherStudentsScene, SpecialThreeDScene):
CONFIG = {
"camera_config": {
"light_source_start_point": [-4, 5, 7],
}
}
def construct(self):
sphere = Sphere()
cylinder = Cylinder()
for mob in sphere, cylinder:
mob.rotate(70 * DEGREES, LEFT)
formula = TexMobject("4\\pi R^2")
formula.set_color(BLUE)
circle = Circle()
circle.set_stroke(width=0)
circle.set_fill(GREY_BROWN, 1)
area_label = TexMobject("\\pi R^2", background_stroke_width=0)
area_label.scale(1.5)
circle.add(area_label)
group = VGroup(
sphere, cylinder, formula, circle
)
for mob in group:
mob.set_height(1.5)
formula.scale(0.5)
group.arrange_submobjects(RIGHT, buff=1.5)
group.to_edge(UP, buff=2)
group[1:3].to_edge(UP)
arrows = VGroup()
for m1, m2 in zip(group, group[1:]):
arrow = Arrow(
m1.get_center(), m2.get_center(),
buff=1,
color=WHITE
)
arrows.add(arrow)
direct_arrow = Arrow(
sphere, circle, color=WHITE
)
q_marks = TexMobject(*"???")
q_marks.space_out_submobjects(1.5)
q_marks.scale(1.5)
q_marks.next_to(direct_arrow, DOWN)
self.play(
self.teacher.change, "raise_right_hand",
self.get_student_changes(
*3 * ["pondering"],
look_at_arg=group,
),
LaggedStart(FadeInFromDown, group),
LaggedStart(GrowArrow, arrows)
)
self.wait()
self.play(
self.teacher.change, "pondering",
self.students[2].change, "raise_right_hand",
GrowArrow(direct_arrow),
LaggedStart(
FadeInFrom, q_marks,
lambda m: (m, UP),
lag_ratio=0.8,
run_time=1.5,
)
)
self.change_student_modes(
"erm", "sassy", "raise_right_hand",
)
self.wait(2)
self.look_at(group)
self.wait(2)
class ExercisesGiveLearning(MovingCameraScene):
def construct(self):
bulb = Lightbulb()
arrow1 = Arrow(ORIGIN, RIGHT, buff=0)
lectures = TextMobject("Lectures")
exercises = TextMobject("Exercises")
frame = self.camera_frame
frame.scale(0.7)
bulb.next_to(arrow1, RIGHT)
for word in lectures, exercises:
word.next_to(arrow1, LEFT)
cross = Cross(lectures)
# Knock down lectures
self.add(lectures)
self.play(GrowArrow(arrow1))
self.play(LaggedStart(DrawBorderThenFill, bulb))
self.play(ShowCreation(cross))
self.play(
VGroup(lectures, cross).shift, DOWN,
FadeInFrom(exercises, UP)
)
self.wait()
# Show self
arrow2 = arrow1.copy()
arrow2.next_to(lectures, LEFT)
logo = Logo()
logo.set_height(1)
logo.next_to(arrow2, LEFT)
pupil_copy = logo.pupil.copy()
self.add(logo, pupil_copy)
self.play(
frame.shift, 1.5 * LEFT,
Write(logo, run_time=1.5)
)
self.remove(pupil_copy)
self.play(
GrowArrow(arrow2),
FadeOut(cross)
)
self.wait()
self.play(
VGroup(logo, arrow2).next_to,
exercises, LEFT
)
self.wait()
class NobodyLikesHomework(TeacherStudentsScene):
def construct(self):
self.change_student_modes(
"angry", "pleading", "angry",
added_anims=[self.teacher.change, "guilty"]
)
self.wait()
self.change_all_student_modes(
"tired", look_at_arg=8 * RIGHT + 4 * DOWN,
added_anims=[self.teacher.change, "tease"]
)
self.wait(2)
class SecondProof(SpecialThreeDScene):
CONFIG = {
"sphere_config": {
2018-11-29 22:44:08 -08:00
"resolution": (30, 30),
},
"n_random_subsets": 12,
2018-11-29 17:30:34 -08:00
}
def construct(self):
self.setup_shapes()
self.divide_into_rings()
self.show_shadows()
self.correspond_to_every_other_ring()
self.cut_cross_section()
self.show_theta()
self.enumerate_rings()
2018-11-29 22:44:08 -08:00
self.ask_about_ring_circumference()
2018-11-29 17:30:34 -08:00
self.ask_about_shadow_area()
self.ask_about_2_to_1_correspondance()
2018-11-29 22:44:08 -08:00
self.show_all_shadow_rings()
2018-11-29 17:30:34 -08:00
self.ask_about_global_correspondance()
def setup_shapes(self):
sphere = self.get_sphere()
sphere.set_stroke(WHITE, width=0.25)
self.add(sphere)
self.sphere = sphere
2018-11-29 22:44:08 -08:00
u_values, v_values = sphere.get_u_values_and_v_values()
rings = VGroup(*[VGroup() for u in u_values])
for piece in sphere:
rings[piece.u_index].add(piece.copy())
self.set_ring_colors(rings)
2018-11-29 17:30:34 -08:00
self.rings = rings
self.axes = self.get_axes()
self.add(self.axes)
self.set_camera_to_default_position()
self.begin_ambient_camera_rotation()
def divide_into_rings(self):
rings = self.rings
2018-11-29 22:44:08 -08:00
self.play(FadeIn(rings), FadeOut(self.sphere))
2018-11-29 17:30:34 -08:00
self.play(
rings.space_out_submobjects, 1.5,
rate_func=there_and_back_with_pause,
run_time=3
)
self.wait(2)
2018-11-29 22:44:08 -08:00
rings.save_state()
2018-11-29 17:30:34 -08:00
def show_shadows(self):
rings = self.rings
2018-11-29 22:44:08 -08:00
north_rings = rings[:len(rings) // 2]
2018-11-29 17:30:34 -08:00
ghost_rings = rings.copy()
ghost_rings.set_fill(opacity=0.0)
ghost_rings.set_stroke(opacity=0.2)
north_rings.submobjects.reverse()
shadows = self.get_shadow(north_rings)
for piece in shadows.family_members_with_points():
piece.set_stroke(
piece.get_fill_color(),
width=0.5,
)
for shadow in shadows:
shadow.save_state()
shadows.become(north_rings)
self.add(ghost_rings)
self.play(FadeOut(rings), Animation(shadows))
self.play(LaggedStart(Restore, shadows))
self.wait()
self.move_camera(phi=40 * DEGREES)
self.wait(3)
# Show circle
radius = self.sphere_config["radius"]
radial_line = Line(ORIGIN, radius * RIGHT)
radial_line.set_stroke(RED)
R_label = TexMobject("R")
R_label.set_background_stroke(width=1)
R_label.next_to(radial_line, DOWN)
self.play(
FadeInFromDown(R_label),
ShowCreation(radial_line)
)
self.play(Rotating(
radial_line, angle=TAU,
about_point=ORIGIN,
rate_func=smooth,
run_time=3,
))
self.wait()
self.set_variables_as_attrs(
shadows, ghost_rings,
radial_line, R_label
)
def correspond_to_every_other_ring(self):
rings = self.rings
shadows = self.shadows
2018-11-29 22:44:08 -08:00
shadows.submobjects.reverse()
2018-11-29 17:30:34 -08:00
every_other_ring = rings[1::2]
2018-11-29 22:44:08 -08:00
# every_other_ring.set_fill(opacity=0.5)
every_other_ring.set_stroke(width=0)
2018-11-29 17:30:34 -08:00
self.move_camera(
phi=70 * DEGREES,
theta=-135 * DEGREES,
added_anims=[
FadeOut(self.R_label),
FadeOut(self.radial_line),
],
run_time=2,
)
2018-11-29 22:44:08 -08:00
shadows_copy = shadows.copy()
shadows.fade(1)
2018-11-29 17:30:34 -08:00
self.play(
2018-11-29 22:44:08 -08:00
ReplacementTransform(
shadows_copy, every_other_ring
),
2018-11-29 17:30:34 -08:00
run_time=2,
)
self.wait(5)
self.every_other_ring = every_other_ring
def cut_cross_section(self):
shadows = self.shadows
every_other_ring = self.every_other_ring
ghost_rings = self.ghost_rings
rings = self.rings
back_half = self.get_hemisphere(rings, UP)
front_half = self.get_hemisphere(rings, DOWN)
2018-11-29 22:44:08 -08:00
front_half_ghost = front_half.copy()
2018-11-29 17:30:34 -08:00
# shaded_back_half = back_half.copy()
2018-11-29 22:44:08 -08:00
# for piece in shaded_back_half.family_members_with_points():
# piece.points = piece.points[::-1]
# shaded_back_half.scale(0.999)
# shaded_back_half.set_fill(opacity=0.5)
2018-11-29 17:30:34 -08:00
radius = self.sphere_config["radius"]
circle = Circle(radius=radius)
circle.set_stroke(PINK, 2)
circle.rotate(90 * DEGREES, RIGHT)
every_other_ring_copy = every_other_ring.copy()
self.add(every_other_ring_copy)
rings.set_fill(opacity=0.8)
self.play(
FadeIn(rings),
FadeOut(shadows),
FadeOut(ghost_rings),
)
self.remove(every_other_ring_copy)
2018-11-29 22:44:08 -08:00
# self.add(shaded_back_half)
2018-11-29 17:30:34 -08:00
self.play(
FadeIn(circle),
2018-11-29 22:44:08 -08:00
# FadeOutAndShift(front_half, IN)
FadeOut(front_half),
front_half_ghost.set_fill, None, 0.2,
front_half_ghost.set_stroke, WHITE, 1, 0.0,
2018-11-29 17:30:34 -08:00
)
self.wait()
2018-11-29 22:44:08 -08:00
self.set_variables_as_attrs(
back_half, front_half,
front_half_ghost,
slice_circle=circle
)
2018-11-29 17:30:34 -08:00
def show_theta(self):
theta_tracker = ValueTracker(0)
get_theta = theta_tracker.get_value
theta_group = updating_mobject_from_func(
lambda: self.get_theta_group(get_theta())
)
theta_mob_opacity_tracker = ValueTracker(0)
get_theta_mob_opacity = theta_mob_opacity_tracker.get_value
theta_mob = theta_group[-1]
theta_mob.add_updater(
lambda m: m.set_fill(opacity=get_theta_mob_opacity())
)
theta_mob.add_updater(
lambda m: m.set_background_stroke(
width=get_theta_mob_opacity()
)
)
lit_ring = updating_mobject_from_func(
lambda: self.get_ring_from_theta(
self.rings, get_theta()
).copy().set_color(YELLOW)
)
self.stop_ambient_camera_rotation()
self.move_camera(theta=-60 * DEGREES)
self.add(theta_group, lit_ring)
2018-11-29 22:44:08 -08:00
n_rings = len(self.rings) - 1
lit_ring_index = int((30 / 180) * n_rings)
angle = PI * lit_ring_index / n_rings
for alpha in [angle, 0, PI, angle]:
2018-11-29 17:30:34 -08:00
self.play(
2018-11-29 22:44:08 -08:00
theta_tracker.set_value, alpha,
2018-11-29 17:30:34 -08:00
theta_mob_opacity_tracker.set_value, 1,
Animation(self.camera.phi_tracker),
run_time=2,
)
self.wait()
# Label d-theta
radius = self.sphere_config["radius"]
d_theta = PI / len(self.rings)
alt_theta = get_theta() + d_theta
alt_theta_group = self.get_theta_group(alt_theta)
alt_R_line = alt_theta_group[1]
# d_theta_arc = Arc(
# start_angle=get_theta(),
# angle=d_theta,
# radius=theta_group[0].radius,
# stroke_color=PINK,
# stroke_width=3,
# )
# d_theta_arc.rotate(90 * DEGREES, axis=RIGHT, about_point=ORIGIN)
brace = Brace(Line(ORIGIN, radius * d_theta * RIGHT), UP)
brace.rotate(90 * DEGREES, RIGHT)
brace.next_to(self.sphere, OUT, buff=0)
brace.add_to_back(brace.copy().set_stroke(BLACK, 3))
brace.rotate(
get_theta() + d_theta / 2,
axis=UP,
about_point=ORIGIN,
)
brace_label = TexMobject("R\\,d\\theta")
brace_label.rotate(90 * DEGREES, RIGHT)
brace_label.next_to(brace, OUT + RIGHT, buff=0)
radial_line = self.radial_line
R_label = self.R_label
R_label.rotate(90 * DEGREES, RIGHT)
R_label.next_to(radial_line, IN, SMALL_BUFF)
self.play(
TransformFromCopy(theta_group[1], alt_R_line),
GrowFromCenter(brace),
FadeInFrom(brace_label, IN)
)
self.move_camera(
phi=90 * DEGREES,
theta=-90 * DEGREES,
)
self.wait()
self.play(
ShowCreation(radial_line),
FadeIn(R_label),
)
self.wait()
self.move_camera(
phi=70 * DEGREES,
theta=-70 * DEGREES,
)
2018-11-29 22:44:08 -08:00
self.set_variables_as_attrs(
theta_tracker, lit_ring, theta_group,
brace, brace_label, d_theta,
alt_R_line, theta_mob_opacity_tracker,
)
2018-11-29 17:30:34 -08:00
def enumerate_rings(self):
pass # Skip, for now...
2018-11-29 22:44:08 -08:00
def ask_about_ring_circumference(self):
theta = self.theta_tracker.get_value()
radius = self.sphere_config["radius"]
circle = Circle(
radius=radius * np.sin(theta)
)
circle.shift(radius * np.cos(theta) * OUT)
circle.set_stroke(Color("red"), 5)
to_fade = VGroup(
self.R_label, self.radial_line,
self.brace, self.brace_label
)
self.move_camera(
phi=0 * DEGREES,
theta=-90 * DEGREES,
added_anims=[FadeOut(to_fade)]
)
self.play(ShowCreation(circle))
self.wait()
self.move_camera(
phi=70 * DEGREES,
theta=-70 * DEGREES,
added_anims=[
FadeIn(to_fade),
FadeOut(circle),
]
)
self.wait()
2018-11-29 17:30:34 -08:00
def ask_about_shadow_area(self):
2018-11-29 22:44:08 -08:00
lit_ring = self.lit_ring
lit_ring_copy = lit_ring.copy()
lit_ring_copy.clear_updaters()
all_shadows = self.shadows
all_shadows.set_fill(BLUE_E, 0.5)
for piece in all_shadows.family_members_with_points():
piece.set_stroke(width=0)
radius = self.sphere_config["radius"]
shadow = self.get_shadow(lit_ring)
theta = self.theta_tracker.get_value()
d_theta = self.d_theta
def get_dashed_line(angle):
p0 = np.cos(angle) * OUT + np.sin(angle) * RIGHT
p0 *= radius
p1 = np.array([*p0[:2], 0])
result = DashedLine(p0, p1)
result.set_stroke(WHITE, 1)
result.add_to_back(
result.copy().set_stroke(BLACK, 2)
)
result.set_shade_in_3d(True)
return result
dashed_lines = VGroup(*[
get_dashed_line(t)
for t in [theta, theta + d_theta]
])
self.play(
ReplacementTransform(lit_ring_copy, shadow),
FadeOut(self.R_label),
FadeOut(self.radial_line),
Animation(self.camera.phi_tracker),
*map(ShowCreation, dashed_lines),
run_time=2,
)
self.wait(2)
self.set_variables_as_attrs(
dashed_lines,
lit_shadow=shadow,
)
2018-11-29 17:30:34 -08:00
def ask_about_2_to_1_correspondance(self):
2018-11-29 22:44:08 -08:00
theta_tracker = ValueTracker(0)
get_theta = theta_tracker.get_value
new_lit_ring = updating_mobject_from_func(
lambda: self.get_ring_from_theta(
self.rings, get_theta()
).copy().set_color(PINK)
)
self.add(new_lit_ring)
for angle in [PI, 0, PI]:
self.play(
theta_tracker.set_value, angle,
Animation(self.camera.phi_tracker),
run_time=3
)
self.remove(new_lit_ring)
self.remove(theta_tracker)
def show_all_shadow_rings(self):
lit_ring_copy = self.lit_ring.copy()
lit_ring_copy.clear_updaters()
self.remove(self.lit_ring)
theta_group_copy = self.theta_group.copy()
theta_group_copy.clear_updaters()
self.remove(self.theta_group, *self.theta_group)
to_fade = VGroup(
theta_group_copy, self.alt_R_line,
self.brace, self.brace_label,
lit_ring_copy, self.lit_shadow,
self.slice_circle,
self.dashed_lines,
)
R_label = self.R_label
radial_line = self.radial_line
R_label.rotate(
-90 * DEGREES,
axis=RIGHT, about_point=radial_line.get_center()
)
shadows = self.shadows
shadows.set_fill(opacity=1)
self.set_ring_colors(shadows, [GREEN_E, GREEN_D])
self.play(
FadeOut(to_fade),
LaggedStart(FadeIn, shadows),
self.theta_mob_opacity_tracker.set_value, 0,
ApplyMethod(
self.camera.phi_tracker.set_value, 60 * DEGREES,
run_time=2,
),
ApplyMethod(
self.camera.theta_tracker.set_value, -130 * DEGREES,
run_time=2,
),
)
self.play(
ShowCreation(radial_line),
FadeIn(R_label),
Animation(self.camera.phi_tracker),
)
self.begin_ambient_camera_rotation()
self.wait()
rings = self.rings
rings_copy = rings.saved_state.copy()
self.play(
FadeOut(R_label),
FadeOut(radial_line),
FadeIn(rings_copy)
)
self.remove(rings_copy)
rings.restore()
self.add(rings)
2018-11-29 17:30:34 -08:00
def ask_about_global_correspondance(self):
2018-11-29 22:44:08 -08:00
rings = self.rings
subset_size = len(rings) // 2
def get_highlighted_subset():
subset = random.sample(list(rings), subset_size)
result = VGroup(*subset).copy()
result.set_color(PINK)
return result
for x in range(self.n_random_subsets):
subset = get_highlighted_subset()
self.add(subset)
self.wait(0.5)
self.remove(subset)
2018-11-29 17:30:34 -08:00
#
def set_ring_colors(self, rings, colors=[BLUE_E, BLUE_D]):
for i, ring in enumerate(rings):
color = colors[i % len(colors)]
ring.set_fill(color)
2018-11-29 22:44:08 -08:00
ring.set_stroke(color, width=0.25)
for piece in ring:
piece.insert_n_anchor_points(4)
piece.on_sphere = True
piece.points = np.array([
*piece.points[3:-1],
*piece.points[:3],
piece.points[3]
])
2018-11-29 17:30:34 -08:00
return rings
def get_shadow(self, mobject):
result = mobject.copy()
result.apply_function(
lambda p: np.array([*p[:2], 0])
)
return result
def get_hemisphere(self, group, vect):
if len(group.submobjects) == 0:
if np.dot(group.get_center(), vect) > 0:
return group
else:
return VMobject()
else:
return VGroup(*[
self.get_hemisphere(submob, vect)
for submob in group
])
def get_northern_hemisphere(self, group):
return self.get_hemisphere(group, OUT)
def get_theta(self, ring):
piece = ring[0]
point = piece.points[3]
return np.arccos(point[2] / get_norm(point))
def get_theta_group(self, theta):
arc = Arc(
start_angle=90 * DEGREES,
angle=-theta,
radius=0.5,
)
arc.rotate(90 * DEGREES, RIGHT, about_point=ORIGIN)
arc.set_stroke(YELLOW, 2)
theta_mob = TexMobject("\\theta")
theta_mob.rotate(90 * DEGREES, RIGHT)
vect = np.cos(theta / 2) * OUT + np.sin(theta / 2) * RIGHT
2018-11-29 22:44:08 -08:00
theta_mob.move_to(
(arc.radius + 0.25) * normalize(vect),
2018-11-29 17:30:34 -08:00
)
theta_mob.set_background_stroke(width=1)
radius = self.sphere_config["radius"]
point = arc.point_from_proportion(1)
radial_line = Line(
ORIGIN, radius * normalize(point)
)
radial_line.set_stroke(WHITE, 2)
return VGroup(arc, radial_line, theta_mob)
def get_ring_from_theta(self, rings, theta):
n_rings = len(rings)
index = min(int((theta / PI) * n_rings), n_rings - 1)
return rings[index]
2018-11-29 22:44:08 -08:00
class SecondProofHigherRes(SecondProof):
CONFIG = {
"sphere_config": {
"resolution": (60, 60),
},
}
2018-11-29 17:30:34 -08:00
class RangeFrom0To180(Scene):
def construct(self):
angle = Integer(0, unit="^\\circ")
angle.scale(2)
self.add(angle)
self.wait()
self.play(ChangeDecimalToValue(
angle, 180,
run_time=2,
))
self.wait()