3b1b-manim/old_projects/basel/basel2.py

4625 lines
146 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from big_ol_pile_of_manim_imports import *
from old_projects.basel.light import *
import types
import functools
LIGHT_COLOR = YELLOW
INDICATOR_RADIUS = 0.7
INDICATOR_STROKE_WIDTH = 1
INDICATOR_STROKE_COLOR = WHITE
INDICATOR_TEXT_COLOR = WHITE
INDICATOR_UPDATE_TIME = 0.2
FAST_INDICATOR_UPDATE_TIME = 0.1
OPACITY_FOR_UNIT_INTENSITY = 0.2
SWITCH_ON_RUN_TIME = 1.5
FAST_SWITCH_ON_RUN_TIME = 0.1
NUM_LEVELS = 30
NUM_CONES = 7 # in first lighthouse scene
NUM_VISIBLE_CONES = 5 # ibidem
ARC_TIP_LENGTH = 0.2
AMBIENT_FULL = 0.5
AMBIENT_DIMMED = 0.2
SPOTLIGHT_FULL = 0.9
SPOTLIGHT_DIMMED = 0.2
LIGHT_COLOR = YELLOW
DEGREES = TAU/360
inverse_power_law = lambda maxint,scale,cutoff,exponent: \
(lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent)
inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2)
# A = np.array([5.,-3.,0.])
# B = np.array([-5.,3.,0.])
# C = np.array([-5.,-3.,0.])
# xA = A[0]
# yA = A[1]
# xB = B[0]
# yB = B[1]
# xC = C[0]
# yC = C[1]
# find the coords of the altitude point H
# as the solution of a certain LSE
# prelim_matrix = np.array([[yA - yB, xB - xA], [xA - xB, yA - yB]]) # sic
# prelim_vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)])
# H2 = np.linalg.solve(prelim_matrix,prelim_vector)
# H = np.append(H2, 0.)
class AngleUpdater(ContinualAnimation):
def __init__(self, angle_arc, spotlight, **kwargs):
self.angle_arc = angle_arc
self.spotlight = spotlight
ContinualAnimation.__init__(self, self.angle_arc, **kwargs)
def update_mobject(self, dt):
new_arc = self.angle_arc.copy().set_bound_angles(
start = self.spotlight.start_angle(),
stop = self.spotlight.stop_angle()
)
new_arc.generate_points()
new_arc.move_arc_center_to(self.spotlight.get_source_point())
self.angle_arc.points = new_arc.points
self.angle_arc.add_tip(
tip_length = ARC_TIP_LENGTH,
at_start = True, at_end = True
)
class LightIndicator(Mobject):
CONFIG = {
"radius": 0.5,
"reading_height" : 0.25,
"intensity": 0,
"opacity_for_unit_intensity": 1,
"fill_color" : YELLOW,
"precision": 3,
"show_reading": True,
"measurement_point": ORIGIN,
"light_source": None
}
def generate_points(self):
self.background = Circle(color=BLACK, radius = self.radius)
self.background.set_fill(opacity = 1.0)
self.foreground = Circle(color=self.color, radius = self.radius)
self.foreground.set_stroke(
color=INDICATOR_STROKE_COLOR,
width=INDICATOR_STROKE_WIDTH
)
self.foreground.set_fill(color = self.fill_color)
self.add(self.background, self.foreground)
self.reading = DecimalNumber(self.intensity,num_decimal_points = self.precision)
self.reading.set_fill(color=INDICATOR_TEXT_COLOR)
self.reading.scale_to_fit_height(self.reading_height)
self.reading.move_to(self.get_center())
if self.show_reading:
self.add(self.reading)
def set_intensity(self, new_int):
self.intensity = new_int
new_opacity = min(1, new_int * self.opacity_for_unit_intensity)
self.foreground.set_fill(opacity=new_opacity)
ChangeDecimalToValue(self.reading, new_int).update(1)
if new_int > 1.1:
self.reading.set_fill(color = BLACK)
else:
self.reading.set_fill(color = WHITE)
return self
def get_measurement_point(self):
if self.measurement_point is not None:
return self.measurement_point
else:
return self.get_center()
def measured_intensity(self):
distance = np.linalg.norm(
self.get_measurement_point() -
self.light_source.get_source_point()
)
intensity = self.light_source.opacity_function(distance) / self.opacity_for_unit_intensity
return intensity
def continual_update(self):
if self.light_source == None:
print "Indicator cannot update, reason: no light source found"
self.set_intensity(self.measured_intensity())
class UpdateLightIndicator(AnimationGroup):
def __init__(self, indicator, intensity, **kwargs):
if not isinstance(indicator,LightIndicator):
raise Exception("This transform applies only to LightIndicator")
target_foreground = indicator.copy().set_intensity(intensity).foreground
change_opacity = Transform(
indicator.foreground, target_foreground
)
changing_decimal = ChangeDecimalToValue(indicator.reading, intensity)
AnimationGroup.__init__(self, changing_decimal, change_opacity, **kwargs)
self.mobject = indicator
class ContinualLightIndicatorUpdate(ContinualAnimation):
def update_mobject(self,dt):
self.mobject.continual_update()
def copy_func(f):
"""Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)"""
g = types.FunctionType(f.func_code, f.func_globals, name=f.func_name,
argdefs=f.func_defaults,
closure=f.func_closure)
g = functools.update_wrapper(g, f)
return g
class ScaleLightSources(Transform):
def __init__(self, light_sources_mob, factor, about_point = None, **kwargs):
if about_point == None:
about_point = light_sources_mob.get_center()
ls_target = light_sources_mob.copy()
for submob in ls_target:
if type(submob) == LightSource:
new_sp = submob.source_point.copy() # a mob
new_sp.scale(factor,about_point = about_point)
submob.move_source_to(new_sp.get_location())
#ambient_of = copy_func(submob.ambient_light.opacity_function)
#new_of = lambda r: ambient_of(r/factor)
#submob.ambient_light.opacity_function = new_of
#spotlight_of = copy_func(submob.ambient_light.opacity_function)
#new_of = lambda r: spotlight_of(r/factor)
#submob.spotlight.change_opacity_function(new_of)
new_r = factor * submob.radius
submob.set_radius(new_r)
new_r = factor * submob.ambient_light.radius
submob.ambient_light.radius = new_r
new_r = factor * submob.spotlight.radius
submob.spotlight.radius = new_r
submob.ambient_light.scale_about_point(factor, new_sp.get_center())
submob.spotlight.scale_about_point(factor, new_sp.get_center())
Transform.__init__(self,light_sources_mob,ls_target,**kwargs)
class ThreeDSpotlight(VGroup):
CONFIG = {
"fill_color" : YELLOW,
}
def __init__(self, screen, ambient_light, source_point_func, **kwargs):
self.screen = screen
self.ambient_light = ambient_light
self.source_point_func = source_point_func
self.dr = ambient_light.radius/ambient_light.num_levels
VGroup.__init__(self, **kwargs)
def update(self):
screen = self.screen
source_point = self.source_point_func()
dr = self.dr
corners = screen.get_anchors()
self.submobjects = [VGroup() for a in screen.get_anchors()]
distance = np.linalg.norm(
screen.get_center() - source_point
)
n_parts = np.ceil(distance/dr)
alphas = np.linspace(0, 1, n_parts+1)
for face, (c1, c2) in zip(self, adjacent_pairs(corners)):
face.submobjects = []
for a1, a2 in zip(alphas, alphas[1:]):
face.add(Polygon(
interpolate(source_point, c1, a1),
interpolate(source_point, c1, a2),
interpolate(source_point, c2, a2),
interpolate(source_point, c2, a1),
fill_color = self.fill_color,
fill_opacity = self.ambient_light.opacity_function(a1*distance),
stroke_width = 0
))
class ContinualThreeDLightConeUpdate(ContinualAnimation):
def update(self, dt):
self.mobject.update()
###
class ThinkAboutPondScene(PiCreatureScene):
CONFIG = {
"default_pi_creature_class" : Randolph,
}
def construct(self):
randy = self.pi_creature
randy.to_corner(DOWN+LEFT)
bubble = ThoughtBubble(
width = 11,
height = 8,
)
circles = bubble[:3]
angle = -15*DEGREES
circles.rotate(angle, about_point = bubble.get_bubble_center())
circles.shift(LARGE_BUFF*LEFT)
for circle in circles:
circle.rotate(-angle)
bubble.pin_to(randy)
bubble.shift(DOWN)
bubble[:3].rotate(np.pi, axis = UP+2*RIGHT, about_edge = UP+LEFT)
bubble[:3].scale(0.7, about_edge = DOWN+RIGHT)
bubble[:3].shift(1.5*DOWN)
for oval in bubble[:3]:
oval.rotate(TAU/3)
self.play(
randy.change, "thinking",
ShowCreation(bubble)
)
self.wait(2)
self.play(randy.change, "happy", bubble)
self.wait(4)
self.play(randy.change, "hooray", bubble)
self.wait(2)
class IntroScene(PiCreatureScene):
CONFIG = {
"rect_height" : 0.075,
"duration" : 1.0,
"eq_spacing" : 3 * MED_LARGE_BUFF,
"n_rects_to_show" : 30,
}
def construct(self):
randy = self.get_primary_pi_creature()
randy.scale(0.7).to_corner(DOWN+RIGHT)
self.build_up_euler_sum()
self.show_history()
# self.other_pi_formulas()
# self.refocus_on_euler_sum()
def build_up_euler_sum(self):
morty = self.pi_creature
euler_sum = self.euler_sum = TexMobject(
"1", "+",
"{1 \\over 4}", "+",
"{1 \\over 9}", "+",
"{1 \\over 16}", "+",
"{1 \\over 25}", "+",
"\\cdots", "=",
arg_separator = " \\, "
)
equals_sign = euler_sum.get_part_by_tex("=")
plusses = euler_sum.get_parts_by_tex("+")
term_mobjects = euler_sum.get_parts_by_tex("1")
self.euler_sum.to_edge(UP)
self.euler_sum.shift(2*LEFT)
max_n = self.n_rects_to_show
terms = [1./(n**2) for n in range(1, max_n + 1)]
series_terms = list(np.cumsum(terms))
series_terms.append(np.pi**2/6) ##Just force this up there
partial_sum_decimal = self.partial_sum_decimal = DecimalNumber(
series_terms[1],
num_decimal_points = 2
)
partial_sum_decimal.next_to(equals_sign, RIGHT)
## Number line
number_line = self.number_line = NumberLine(
x_min = 0,
color = WHITE,
number_at_center = 1,
stroke_width = 1,
numbers_with_elongated_ticks = [0,1,2,3],
numbers_to_show = np.arange(0,5),
unit_size = 5,
tick_frequency = 0.2,
line_to_number_buff = MED_LARGE_BUFF
)
number_line.add_numbers()
number_line.to_edge(LEFT)
number_line.shift(MED_LARGE_BUFF*UP)
# create slabs for series terms
lines = VGroup()
rects = self.rects = VGroup()
rect_labels = VGroup()
slab_colors = it.cycle([YELLOW, BLUE])
rect_anims = []
rect_label_anims = []
for i, t1, t2 in zip(it.count(1), [0]+series_terms, series_terms):
color = slab_colors.next()
line = Line(*map(number_line.number_to_point, [t1, t2]))
rect = Rectangle(
stroke_width = 0,
fill_opacity = 1,
fill_color = color
)
rect.match_width(line)
rect.stretch_to_fit_height(self.rect_height)
rect.move_to(line)
if i <= 5:
if i == 1:
rect_label = TexMobject("1")
else:
rect_label = TexMobject("\\frac{1}{%d}"%(i**2))
rect_label.scale(0.75)
max_width = 0.7*rect.get_width()
if rect_label.get_width() > max_width:
rect_label.scale_to_fit_width(max_width)
rect_label.next_to(rect, UP, buff = MED_LARGE_BUFF/(i+1))
term_mobject = term_mobjects[i-1]
rect_anim = GrowFromPoint(rect, term_mobject.get_center())
rect_label_anim = ReplacementTransform(
term_mobject.copy(), rect_label
)
else:
rect_label = VectorizedPoint()
rect_anim = GrowFromPoint(rect, rect.get_left())
rect_label_anim = FadeIn(rect_label)
rects.add(rect)
rect_labels.add(rect_label)
rect_anims.append(rect_anim)
rect_label_anims.append(rect_label_anim)
lines.add(line)
dots = TexMobject("\\dots").scale(0.5)
last_rect = rect_anims[-1].target_mobject
dots.scale_to_fit_width(0.9*last_rect.get_width())
dots.move_to(last_rect, UP+RIGHT)
rects.submobjects[-1] = dots
rect_anims[-1] = FadeIn(dots)
self.add(number_line)
self.play(FadeIn(euler_sum[0]))
self.play(
rect_anims[0],
rect_label_anims[0]
)
for i in range(4):
self.play(
FadeIn(term_mobjects[i+1]),
FadeIn(plusses[i]),
)
anims = [
rect_anims[i+1],
rect_label_anims[i+1],
]
if i == 0:
anims += [
FadeIn(equals_sign),
FadeIn(partial_sum_decimal)
]
elif i <= 5:
anims += [
ChangeDecimalToValue(
partial_sum_decimal,
series_terms[i+1],
run_time = 1,
num_decimal_points = 6,
position_update_func = lambda m: m.next_to(equals_sign, RIGHT)
)
]
self.play(*anims)
for i in range(4, len(series_terms)-2):
anims = [
rect_anims[i+1],
ChangeDecimalToValue(
partial_sum_decimal,
series_terms[i+1],
num_decimal_points = 6,
),
]
if i == 5:
anims += [
FadeIn(euler_sum[-3]), # +
FadeIn(euler_sum[-2]), # ...
]
self.play(*anims, run_time = 2./i)
brace = self.brace = Brace(partial_sum_decimal, DOWN)
q_marks = self.q_marks = TextMobject("???")
q_marks.next_to(brace, DOWN)
q_marks.set_color(LIGHT_COLOR)
self.play(
GrowFromCenter(brace),
Write(q_marks),
ChangeDecimalToValue(
partial_sum_decimal,
series_terms[-1],
num_decimal_points = 6,
),
morty.change, "confused",
)
self.wait()
self.number_line_group = VGroup(
number_line, rects, rect_labels
)
def show_history(self):
# Pietro Mengoli in 1644
morty = self.pi_creature
pietro = ImageMobject("Pietro_Mengoli")
euler = ImageMobject("Euler")
pietro_words = TextMobject("Challenge posed by \\\\ Pietro Mengoli in 1644")
pietro_words.scale(0.75)
pietro_words.next_to(pietro, DOWN)
pietro.add(pietro_words)
euler_words = TextMobject("Solved by Leonard \\\\ Euler in 1735")
euler_words.scale(0.75)
euler_words.next_to(euler, DOWN)
euler.add(euler_words)
pietro.next_to(FRAME_X_RADIUS*LEFT, LEFT)
euler.next_to(FRAME_X_RADIUS*RIGHT, RIGHT)
pi_answer = self.pi_answer = TexMobject("{\\pi^2 \\over 6}")
pi_answer.set_color(YELLOW)
pi_answer.move_to(self.partial_sum_decimal, LEFT)
equals_sign = TexMobject("=")
equals_sign.next_to(pi_answer, RIGHT)
pi_answer.shift(SMALL_BUFF*UP)
self.partial_sum_decimal.generate_target()
self.partial_sum_decimal.target.next_to(equals_sign, RIGHT)
pi = pi_answer[0]
pi_rect = SurroundingRectangle(pi, color = RED)
pi_rect.save_state()
pi_rect.scale_to_fit_height(FRAME_Y_RADIUS)
pi_rect.center()
pi_rect.set_stroke(width = 0)
squared = pi_answer[1]
squared_rect = SurroundingRectangle(squared, color = BLUE)
brace = Brace(
VGroup(self.euler_sum, self.partial_sum_decimal.target),
DOWN, buff = SMALL_BUFF
)
basel_text = brace.get_text("Basel problem", buff = SMALL_BUFF)
self.number_line_group.save_state()
self.play(
pietro.next_to, ORIGIN, LEFT, LARGE_BUFF,
self.number_line_group.next_to, FRAME_Y_RADIUS*DOWN, DOWN,
morty.change, "pondering",
)
self.wait(2)
self.play(euler.next_to, ORIGIN, RIGHT, LARGE_BUFF)
self.wait(2)
self.play(
ReplacementTransform(self.q_marks, pi_answer),
FadeIn(equals_sign),
FadeOut(self.brace),
MoveToTarget(self.partial_sum_decimal)
)
self.wait()
self.play(morty.change, "surprised")
self.play(pi_rect.restore)
self.wait()
self.play(Transform(pi_rect, squared_rect))
self.play(FadeOut(pi_rect))
self.play(morty.change, "hesitant")
self.wait(2)
self.play(
GrowFromCenter(brace),
euler.to_edge, DOWN,
pietro.to_edge, DOWN,
self.number_line_group.restore,
self.number_line_group.shift, LARGE_BUFF*RIGHT,
)
self.play(Write(basel_text))
self.play(morty.change, "happy")
self.wait(4)
def other_pi_formulas(self):
self.play(
FadeOut(self.rects),
FadeOut(self.number_line)
)
self.leibniz_sum = TexMobject(
"1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots",
"=", "{\\pi \\over 4}")
self.wallis_product = TexMobject(
"{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" +
"\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots",
"=", "{\\pi \\over 2}")
self.leibniz_sum.next_to(self.euler_sum.get_part_by_tex("="), DOWN,
buff = self.eq_spacing,
submobject_to_align = self.leibniz_sum.get_part_by_tex("=")
)
self.wallis_product.next_to(self.leibniz_sum.get_part_by_tex("="), DOWN,
buff = self.eq_spacing,
submobject_to_align = self.wallis_product.get_part_by_tex("=")
)
self.play(
Write(self.leibniz_sum)
)
self.play(
Write(self.wallis_product)
)
def refocus_on_euler_sum(self):
self.euler_sum.add(self.pi_answer)
self.play(
FadeOut(self.leibniz_sum),
FadeOut(self.wallis_product),
ApplyMethod(self.euler_sum.shift,
ORIGIN + 2*UP - self.euler_sum.get_center())
)
# focus on pi squared
pi_squared = self.euler_sum.get_part_by_tex("\\pi")[-3]
self.play(
ScaleInPlace(pi_squared,2,rate_func = wiggle)
)
# Morty thinks of a circle
q_circle = Circle(
stroke_color = YELLOW,
fill_color = YELLOW,
fill_opacity = 0.5,
radius = 0.4,
stroke_width = 10.0
)
q_mark = TexMobject("?")
q_mark.next_to(q_circle)
thought = Group(q_circle, q_mark)
q_mark.scale_to_fit_height(0.8 * q_circle.get_height())
self.pi_creature_thinks(thought,target_mode = "confused",
bubble_kwargs = { "height" : 2, "width" : 3 })
self.wait()
class PiHidingWrapper(Scene):
def construct(self):
title = TextMobject("Pi hiding in prime regularities")
title.to_edge(UP)
screen = ScreenRectangle(height = 6)
screen.next_to(title, DOWN)
self.add(title)
self.play(ShowCreation(screen))
self.wait(2)
class MathematicalWebOfConnections(PiCreatureScene):
def construct(self):
self.complain_that_pi_is_not_about_circles()
self.show_other_pi_formulas()
self.question_fundamental()
self.draw_circle()
self.remove_all_but_basel_sum()
self.show_web_of_connections()
self.show_light()
def complain_that_pi_is_not_about_circles(self):
jerk, randy = self.pi_creatures
words = self.words = TextMobject(
"I am not",
"fundamentally \\\\",
"about circles"
)
words.set_color_by_tex("fundamentally", YELLOW)
self.play(PiCreatureSays(
jerk, words,
target_mode = "angry"
))
self.play(randy.change, "guilty")
self.wait(2)
def show_other_pi_formulas(self):
jerk, randy = self.pi_creatures
words = self.words
basel_sum = TexMobject(
"1 + {1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots",
"=", "{\\pi^2 \\over 6}"
)
leibniz_sum = TexMobject(
"1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots",
"=", "{\\pi \\over 4}")
wallis_product = TexMobject(
"{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" +
"\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots",
"=", "{\\pi \\over 2}")
basel_sum.move_to(randy)
basel_sum.to_edge(UP)
basel_equals = basel_sum.get_part_by_tex("=")
formulas = VGroup(basel_sum, leibniz_sum, wallis_product)
formulas.scale(0.75)
formulas.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF)
for formula in formulas:
basel_equals_x = basel_equals.get_center()[0]
formula_equals_x = formula.get_part_by_tex("=").get_center()[0]
formula.shift((basel_equals_x - formula_equals_x)*RIGHT)
formulas.to_corner(UP+RIGHT)
formulas.shift(2*LEFT)
self.formulas = formulas
self.play(
jerk.change, "sassy",
randy.change, "raise_right_hand",
FadeOut(jerk.bubble),
words.next_to, jerk, UP,
FadeIn(basel_sum, submobject_mode = "lagged_start", run_time = 3)
)
for formula in formulas[1:]:
self.play(
FadeIn(
formula,
submobject_mode = "lagged_start",
run_time = 3
),
)
self.wait()
def question_fundamental(self):
jerk, randy = self.pi_creatures
words = self.words
fundamentally = words.get_part_by_tex("fundamentally")
words.remove(fundamentally)
self.play(
fundamentally.move_to, self.pi_creatures,
fundamentally.shift, UP,
FadeOut(words),
jerk.change, "pondering",
randy.change, "pondering",
)
self.wait()
question = TextMobject("Does this mean \\\\ anything?")
question.scale(0.8)
question.set_stroke(WHITE, 0.5)
question.next_to(fundamentally, DOWN, LARGE_BUFF)
arrow = Arrow(question, fundamentally)
arrow.set_color(WHITE)
self.play(
FadeIn(question),
GrowArrow(arrow)
)
self.wait()
fundamentally.add(question, arrow)
self.fundamentally = fundamentally
def draw_circle(self):
semi_circle = Arc(angle = np.pi, radius = 2)
radius = Line(ORIGIN, semi_circle.points[0])
radius.set_color(BLUE)
semi_circle.set_color(YELLOW)
VGroup(radius, semi_circle).move_to(
FRAME_X_RADIUS*LEFT/2 + FRAME_Y_RADIUS*UP/2,
)
decimal = DecimalNumber(0)
def decimal_position_update_func(decimal):
decimal.move_to(semi_circle.points[-1])
decimal.shift(0.3*radius.get_vector())
one = TexMobject("1")
one.next_to(radius, UP)
self.play(ShowCreation(radius), FadeIn(one))
self.play(
Rotate(radius, np.pi, about_point = radius.get_start()),
ShowCreation(semi_circle),
ChangeDecimalToValue(
decimal, np.pi,
position_update_func = decimal_position_update_func
),
MaintainPositionRelativeTo(one, radius),
run_time = 3,
)
self.wait(2)
self.circle_group = VGroup(semi_circle, radius, one, decimal)
def remove_all_but_basel_sum(self):
to_shift_down = VGroup(
self.circle_group, self.pi_creatures,
self.fundamentally, self.formulas[1:],
)
to_shift_down.generate_target()
for part in to_shift_down.target:
part.move_to(FRAME_HEIGHT*DOWN)
basel_sum = self.formulas[0]
self.play(
MoveToTarget(to_shift_down),
basel_sum.scale, 1.5,
basel_sum.move_to, 1.5*DOWN,
)
self.basel_sum = basel_sum
def show_web_of_connections(self):
self.remove(self.pi_creatures)
title = TextMobject("Interconnected web of mathematics")
title.to_edge(UP)
basel_sum = self.basel_sum
dots = VGroup(*[
Dot(radius = 0.1).move_to(
(j - 0.5*(i%2))*RIGHT + \
(np.sqrt(3)/2.0)* i*DOWN + \
0.5*(random.random()*RIGHT + random.random()*UP),
)
for i in range(4)
for j in range(7+(i%2))
])
dots.scale_to_fit_height(3)
dots.next_to(title, DOWN, MED_LARGE_BUFF)
edges = VGroup()
for x in range(100):
d1, d2 = random.sample(dots, 2)
edge = Line(d1.get_center(), d2.get_center())
edge.set_stroke(YELLOW, 0.5)
edges.add(edge)
## Choose special path
path_dots = VGroup(
dots[-7],
dots[-14],
dots[9],
dots[19],
dots[14],
)
path_edges = VGroup(*[
Line(
d1.get_center(), d2.get_center(),
color = RED
)
for d1, d2 in zip(path_dots, path_dots[1:])
])
circle = Circle(color = YELLOW, radius = 1)
radius = Line(circle.get_center(), circle.get_right())
radius.set_color(BLUE)
VGroup(circle, radius).next_to(path_dots[-1], RIGHT)
self.play(
Write(title),
LaggedStart(ShowCreation, edges, run_time = 3),
LaggedStart(GrowFromCenter, dots, run_time = 3)
)
self.play(path_dots[0].set_color, RED)
for dot, edge in zip(path_dots[1:], path_edges):
self.play(
ShowCreation(edge),
dot.set_color, RED
)
self.play(ShowCreation(radius))
radius.set_points_as_corners(radius.get_anchors())
self.play(
ShowCreation(circle),
Rotate(radius, angle = 0.999*TAU, about_point = radius.get_start()),
run_time = 2
)
self.wait()
graph = VGroup(dots, edges, path_edges, title)
circle.add(radius)
basel_sum.generate_target()
basel_sum.target.to_edge(UP)
arrow = Arrow(
UP, DOWN,
rectangular_stem_width = 0.1,
tip_length = 0.45,
color = RED,
)
arrow.next_to(basel_sum.target, DOWN, buff = MED_LARGE_BUFF)
self.play(
MoveToTarget(basel_sum),
graph.next_to, basel_sum.target, UP, LARGE_BUFF,
circle.next_to, arrow, DOWN, MED_LARGE_BUFF,
)
self.play(GrowArrow(arrow))
self.wait()
self.arrow = arrow
self.circle = circle
def show_light(self):
light = AmbientLight(
num_levels = 500, radius = 13,
opacity_function = lambda r : 1.0/(r+1),
)
pi = self.basel_sum[-1][0]
pi.set_stroke(BLACK, 0.5)
light.move_to(pi)
self.play(
SwitchOn(light, run_time = 3),
Animation(self.arrow),
Animation(self.circle),
Animation(self.basel_sum),
)
self.wait()
###
def create_pi_creatures(self):
jerk = PiCreature(color = GREEN_D)
randy = Randolph().flip()
jerk.move_to(0.5*FRAME_X_RADIUS*LEFT).to_edge(DOWN)
randy.move_to(0.5*FRAME_X_RADIUS*RIGHT).to_edge(DOWN)
return VGroup(jerk, randy)
class FirstLighthouseScene(PiCreatureScene):
CONFIG = {
"num_levels" : 100,
"opacity_function" : inverse_quadratic(1,2,1),
}
def construct(self):
self.remove(self.pi_creature)
self.show_lighthouses_on_number_line()
self.describe_brightness_of_each()
self.ask_about_rearrangements()
def show_lighthouses_on_number_line(self):
number_line = self.number_line = NumberLine(
x_min = 0,
color = WHITE,
number_at_center = 1.6,
stroke_width = 1,
numbers_with_elongated_ticks = range(1,6),
numbers_to_show = range(1,6),
unit_size = 2,
tick_frequency = 0.2,
line_to_number_buff = LARGE_BUFF,
label_direction = DOWN,
)
number_line.add_numbers()
self.add(number_line)
origin_point = number_line.number_to_point(0)
morty = self.pi_creature
morty.scale(0.75)
morty.flip()
right_pupil = morty.eyes[1]
morty.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil)
light_sources = VGroup()
for i in range(1,NUM_CONES+1):
light_source = LightSource(
opacity_function = self.opacity_function,
num_levels = self.num_levels,
radius = 12.0,
)
point = number_line.number_to_point(i)
light_source.move_source_to(point)
light_sources.add(light_source)
lighthouses = self.lighthouses = VGroup(*[
ls.lighthouse
for ls in light_sources[:NUM_VISIBLE_CONES+1]
])
morty.save_state()
morty.scale(3)
morty.fade(1)
morty.center()
self.play(morty.restore)
self.play(
morty.change, "pondering",
LaggedStart(
FadeIn, lighthouses,
run_time = 1
)
)
self.play(LaggedStart(
SwitchOn, VGroup(*[
ls.ambient_light
for ls in light_sources
]),
run_time = 5,
lag_ratio = 0.1,
rate_func = rush_into,
), Animation(lighthouses))
self.wait()
self.light_sources = light_sources
def describe_brightness_of_each(self):
number_line = self.number_line
morty = self.pi_creature
light_sources = self.light_sources
lighthouses = self.lighthouses
light_indicator = LightIndicator(
radius = INDICATOR_RADIUS,
opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY,
color = LIGHT_COLOR
)
light_indicator.reading.scale(0.8)
light_indicator.set_intensity(0)
intensities = np.cumsum(np.array([1./n**2 for n in range(1,NUM_CONES+1)]))
opacities = intensities * light_indicator.opacity_for_unit_intensity
bubble = ThoughtBubble(
direction = RIGHT,
width = 2.5, height = 3.5
)
bubble.pin_to(morty)
bubble.add_content(light_indicator)
euler_sum_above = TexMobject(
"1", "+",
"{1\over 4}", "+",
"{1\over 9}", "+",
"{1\over 16}", "+",
"{1\over 25}", "+",
"{1\over 36}"
)
euler_sum_terms = euler_sum_above[::2]
plusses = euler_sum_above[1::2]
for i, term in enumerate(euler_sum_above):
#horizontal alignment with tick marks
term.next_to(number_line.number_to_point(0.5*i+1), UP , buff = 2)
# vertical alignment with light indicator
old_y = term.get_center()[1]
new_y = light_indicator.get_center()[1]
term.shift([0,new_y - old_y,0])
# show limit value in light indicator and an equals sign
limit_reading = TexMobject("{\pi^2 \over 6}")
limit_reading.move_to(light_indicator.reading)
equals_sign = TexMobject("=")
equals_sign.next_to(morty, UP)
old_y = equals_sign.get_center()[1]
new_y = euler_sum_above.get_center()[1]
equals_sign.shift([0,new_y - old_y,0])
#Triangle of light to morty's eye
ls0 = light_sources[0]
ls0.save_state()
eye = morty.eyes[1]
triangle = Polygon(
number_line.number_to_point(1),
eye.get_top(), eye.get_bottom(),
stroke_width = 0,
fill_color = YELLOW,
fill_opacity = 1,
)
triangle_anim = GrowFromPoint(
triangle, triangle.get_right(),
point_color = YELLOW
)
# First lighthouse has apparent reading
self.play(LaggedStart(FadeOut, light_sources[1:]))
self.wait()
self.play(
triangle_anim,
# Animation(eye)
)
for x in range(4):
triangle_copy = triangle.copy()
self.play(
FadeOut(triangle.copy()),
triangle_anim,
)
self.play(
FadeOut(triangle),
ShowCreation(bubble),
FadeIn(light_indicator),
)
self.play(
UpdateLightIndicator(light_indicator, 1),
FadeIn(euler_sum_terms[0])
)
self.wait(2)
# Second lighthouse is 1/4, third is 1/9, etc.
for i in range(1, 5):
self.play(
ApplyMethod(
ls0.move_to, light_sources[i],
run_time = 3
),
UpdateLightIndicator(light_indicator, 1./(i+1)**2, run_time = 3),
FadeIn(
euler_sum_terms[i],
run_time = 3,
rate_func = squish_rate_func(smooth, 0.5, 1)
),
)
self.wait()
self.play(
ApplyMethod(ls0.restore),
UpdateLightIndicator(light_indicator, 1)
)
#Switch them all on
self.play(
LaggedStart(FadeIn, lighthouses[1:]),
morty.change, "hooray",
)
self.play(
LaggedStart(
SwitchOn, VGroup(*[
ls.ambient_light
for ls in light_sources[1:]
]),
run_time = 5,
rate_func = rush_into,
),
Animation(lighthouses),
Animation(euler_sum_above),
Write(plusses),
UpdateLightIndicator(light_indicator, np.pi**2/6, run_time = 5),
morty.change, "happy",
)
self.wait()
self.play(
FadeOut(light_indicator.reading),
FadeIn(limit_reading),
morty.change, "confused",
)
self.play(Write(equals_sign))
self.wait()
def ask_about_rearrangements(self):
light_sources = self.light_sources
origin = self.number_line.number_to_point(0)
morty = self.pi_creature
self.play(
LaggedStart(
Rotate, light_sources,
lambda m : (m, (2*random.random()-1)*90*DEGREES),
about_point = origin,
rate_func = lambda t : wiggle(t, 4),
run_time = 10,
lag_ratio = 0.9,
),
morty.change, "pondering",
)
class RearrangeWords(Scene):
def construct(self):
words = TextMobject("Rearrange without changing \\\\ the apparent brightness")
self.play(Write(words))
self.wait(5)
class ThatJustSeemsUseless(TeacherStudentsScene):
def construct(self):
self.student_says(
"How would \\\\ that help?",
target_mode = "sassy",
student_index = 2,
bubble_kwargs = {"direction" : LEFT},
)
self.play(
self.teacher.change, "guilty",
self.get_student_changes(*3*['sassy'])
)
self.wait()
class AskAboutBrightness(TeacherStudentsScene):
CONFIG = {
"num_levels" : 200,
"radius" : 10,
}
def construct(self):
light_source = LightSource(
num_levels = self.num_levels,
radius = self.radius,
opacity_function = inverse_quadratic(1,2,1),
)
light_source.lighthouse.scale(0.5, about_edge = UP)
light_source.move_source_to(5*LEFT + 2*UP)
self.add_foreground_mobjects(self.pi_creatures)
self.student_says(
"What do you mean \\\\ by ``brightness''?",
added_anims = [
SwitchOn(light_source.ambient_light),
Animation(light_source.lighthouse)
]
)
self.play(self.teacher.change, "happy")
self.wait(4)
class IntroduceScreen(Scene):
CONFIG = {
"num_levels" : 100,
"radius" : 10,
"num_rays" : 250,
"min_ray_angle" : 0,
"max_ray_angle" : TAU,
"source_point" : 2.5*LEFT,
"observer_point" : 3.5*RIGHT,
"screen_height" : 2,
}
def construct(self):
self.setup_elements()
self.setup_angle() # spotlight and angle msmt change when screen rotates
self.rotate_screen()
# self.morph_lighthouse_into_sun()
def setup_elements(self):
SCREEN_SIZE = 3.0
source_point = self.source_point
observer_point = self.observer_point,
# Light source
light_source = self.light_source = self.get_light_source()
# Screen
screen = self.screen = Rectangle(
width = 0.05,
height = self.screen_height,
mark_paths_closed = True,
fill_color = WHITE,
fill_opacity = 1.0,
stroke_width = 0.0
)
screen.next_to(observer_point, LEFT)
screen_label = TextMobject("Screen")
screen_label.next_to(screen, UP+LEFT)
screen_arrow = Arrow(
screen_label.get_bottom(),
screen.get_center(),
)
# Pi creature
morty = Mortimer()
morty.shift(screen.get_center() - morty.eyes.get_left())
morty.look_at(source_point)
# Camera
camera = SVGMobject(file_name = "camera")
camera.rotate(TAU/4)
camera.scale_to_fit_height(1.5)
camera.move_to(morty.eyes, LEFT)
# Animations
light_source.set_max_opacity_spotlight(0.001)
screen_tracker = self.screen_tracker = ScreenTracker(light_source)
self.add(light_source.lighthouse)
self.play(SwitchOn(light_source.ambient_light))
self.play(
Write(screen_label),
GrowArrow(screen_arrow),
FadeIn(screen)
)
self.wait()
self.play(*map(FadeOut, [screen_label, screen_arrow]))
screen.save_state()
self.play(
FadeIn(morty),
screen.match_height, morty.eyes,
screen.next_to, morty.eyes, LEFT, SMALL_BUFF
)
self.play(Blink(morty))
self.play(
FadeOut(morty),
FadeIn(camera),
screen.scale, 2, {"about_edge" : UP},
)
self.wait()
self.play(
FadeOut(camera),
screen.restore,
)
light_source.set_screen(screen)
light_source.spotlight.opacity_function = lambda r : 0.2/(r+1)
screen_tracker.update(0)
## Ask about proportion
self.add_foreground_mobjects(light_source.shadow, screen)
self.shoot_rays()
##
self.play(SwitchOn(light_source.spotlight))
def setup_angle(self):
self.wait()
# angle msmt (arc)
arc_angle = self.light_source.spotlight.opening_angle()
# draw arc arrows to show the opening angle
self.angle_arc = Arc(
radius = 3,
start_angle = self.light_source.spotlight.start_angle(),
angle = self.light_source.spotlight.opening_angle(),
tip_length = ARC_TIP_LENGTH
)
#angle_arc.add_tip(at_start = True, at_end = True)
self.angle_arc.move_arc_center_to(self.light_source.get_source_point())
# angle msmt (decimal number)
self.angle_indicator = DecimalNumber(
arc_angle / DEGREES,
num_decimal_points = 0,
unit = "^\\circ"
)
self.angle_indicator.next_to(self.angle_arc, RIGHT)
angle_update_func = lambda x: self.light_source.spotlight.opening_angle() / DEGREES
angle_tracker = ContinualChangingDecimal(
self.angle_indicator, angle_update_func
)
self.add(angle_tracker)
arc_tracker = AngleUpdater(
self.angle_arc,
self.light_source.spotlight
)
self.add(arc_tracker)
self.play(
ShowCreation(self.angle_arc),
ShowCreation(self.angle_indicator)
)
self.wait()
def rotate_screen(self):
self.add(
ContinualUpdateFromFunc(
self.light_source,
lambda m : m.update()
),
)
self.add(
ContinualUpdateFromFunc(
self.angle_indicator,
lambda m : m.set_stroke(width = 0).set_fill(opacity = 1)
)
)
self.remove(self.light_source.ambient_light)
def rotate_screen(angle):
self.play(
Rotate(self.light_source.spotlight.screen, angle),
Animation(self.angle_arc),
run_time = 2,
)
for angle in TAU/8, -TAU/4, TAU/8, -TAU/6:
rotate_screen(angle)
self.wait()
self.shoot_rays()
rotate_screen(TAU/6)
##
def get_light_source(self):
light_source = LightSource(
opacity_function = inverse_quadratic(1,2,1),
num_levels = self.num_levels,
radius = self.radius,
max_opacity_ambient = AMBIENT_FULL,
)
light_source.move_source_to(self.source_point)
return light_source
def shoot_rays(self, show_creation_kwargs = None):
if show_creation_kwargs is None:
show_creation_kwargs = {}
source_point = self.source_point
screen = self.screen
# Rays
step_size = (self.max_ray_angle - self.min_ray_angle)/self.num_rays
rays = VGroup(*[
Line(ORIGIN, self.radius*rotate_vector(RIGHT, angle))
for angle in np.arange(
self.min_ray_angle,
self.max_ray_angle,
step_size
)
])
rays.shift(source_point)
rays.set_stroke(YELLOW, 1)
max_angle = np.max([
angle_of_vector(point - source_point)
for point in screen.points
])
min_angle = np.min([
angle_of_vector(point - source_point)
for point in screen.points
])
for ray in rays:
if min_angle <= ray.get_angle() <= max_angle:
ray.target_color = GREEN
else:
ray.target_color = RED
self.play(*[
ShowCreation(ray, run_time = 3, **show_creation_kwargs)
for ray in rays
])
self.play(*[
ApplyMethod(ray.set_color, ray.target_color)
for ray in rays
])
self.wait()
self.play(FadeOut(rays))
class EarthScene(IntroduceScreen):
CONFIG = {
"screen_height" : 0.5,
"screen_thickness" : 0,
"radius" : 100 + FRAME_X_RADIUS,
"source_point" : 100*LEFT,
"min_ray_angle" : -1.65*DEGREES,
"max_ray_angle" : 1.65*DEGREES,
"num_rays" : 100,
}
def construct(self):
# Earth
earth_radius = 3
earth = ImageMobject("earth")
earth_circle = Circle(radius = earth_radius)
earth_circle.to_edge(RIGHT)
earth.replace(earth_circle)
black_rect = Rectangle(
height = FRAME_HEIGHT,
width = earth_radius + LARGE_BUFF,
stroke_width = 0,
fill_color = BLACK,
fill_opacity = 1
)
black_rect.move_to(earth.get_center(), LEFT)
self.add_foreground_mobjects(black_rect, earth)
# screen
screen = self.screen = Line(
self.screen_height*UP, ORIGIN,
stroke_color = WHITE,
stroke_width = self.screen_thickness,
)
screen.move_to(earth.get_left())
screen.generate_target()
screen.target.rotate(
-60*DEGREES, about_point = earth_circle.get_center()
)
equator_arrow = Vector(
DOWN+2*RIGHT, color = WHITE,
use_rectangular_stem = False,
)
equator_arrow.next_to(screen.get_center(), UP+LEFT, SMALL_BUFF)
pole_arrow = Vector(
UP+3*RIGHT,
color = WHITE,
use_rectangular_stem = False,
path_arc = -60*DEGREES,
)
pole_arrow.shift(
screen.target.get_center()+SMALL_BUFF*LEFT - \
pole_arrow.get_end()
)
for arrow in equator_arrow, pole_arrow:
arrow.pointwise_become_partial(arrow, 0, 0.95)
equator_words = TextMobject("Some", "unit of area")
pole_words = TextMobject("The same\\\\", "unit of area")
pole_words.next_to(pole_arrow.get_start(), DOWN)
equator_words.next_to(equator_arrow.get_start(), UP)
# Light source (far-away Sun)
sun = sun = LightSource(
opacity_function = lambda r : 0.5,
max_opacity_ambient = 0,
max_opacity_spotlight = 0.5,
num_levels = 5,
radius = self.radius,
screen = screen
)
sun.move_source_to(self.source_point)
sunlight = sun.spotlight
sunlight.opacity_function = lambda r : 5./(r+1)
screen_tracker = ScreenTracker(sun)
# Add elements to scene
self.add(screen)
self.play(SwitchOn(
sunlight,
rate_func = squish_rate_func(smooth, 0.7, 0.8),
))
self.add(screen_tracker)
self.play(
Write(equator_words),
GrowArrow(equator_arrow)
)
self.add_foreground_mobjects(equator_words, equator_arrow)
self.shoot_rays(show_creation_kwargs = {
"rate_func" : lambda t : interpolate(0.98, 1, smooth(t))
})
self.wait()
# Point to patch
self.play(
MoveToTarget(screen),
Transform(equator_arrow, pole_arrow),
Transform(
equator_words, pole_words,
rate_func = squish_rate_func(smooth, 0.6, 1),
),
Animation(sunlight),
run_time = 3,
)
self.shoot_rays(show_creation_kwargs = {
"rate_func" : lambda t : interpolate(0.98, 1, smooth(t))
})
self.wait()
class ShowLightInThreeDimensions(IntroduceScreen, ThreeDScene):
CONFIG = {
"num_levels" : 200,
}
def construct(self):
light_source = self.get_light_source()
screens = VGroup(
Square(),
RegularPolygon(8),
Circle().insert_n_anchor_points(25),
)
for screen in screens:
screen.scale_to_fit_height(self.screen_height)
screens.rotate(TAU/4, UP)
screens.next_to(self.observer_point, LEFT)
screens.set_stroke(WHITE, 2)
screens.set_fill(WHITE, 0.5)
screen = screens[0]
cone = ThreeDSpotlight(
screen, light_source.ambient_light,
light_source.get_source_point
)
cone_update_anim = ContinualThreeDLightConeUpdate(cone)
self.add(light_source, screen, cone)
self.add(cone_update_anim)
self.move_camera(
phi = 60*DEGREES,
theta = -155*DEGREES,
run_time = 3,
)
self.begin_ambient_camera_rotation()
kwargs = {"run_time" : 2}
self.play(screen.stretch, 0.5, 1, **kwargs)
self.play(screen.stretch, 2, 2, **kwargs)
self.play(Rotate(
screen, TAU/4,
axis = UP+OUT,
rate_func = there_and_back,
run_time = 3,
))
self.play(Transform(screen, screens[1], **kwargs))
self.play(screen.stretch, 0.5, 2, **kwargs)
self.play(Transform(screen, screens[2], **kwargs))
self.wait(2)
self.play(
screen.stretch, 0.5, 1,
screen.stretch, 2, 2,
**kwargs
)
self.play(
screen.stretch, 3, 1,
screen.stretch, 0.7, 2,
**kwargs
)
self.wait(2)
class LightInThreeDimensionsOverlay(Scene):
def construct(self):
words = TextMobject("""
``Solid angle'' \\\\
(measured in ``steradians'')
""")
self.play(Write(words))
self.wait()
class InverseSquareLaw(ThreeDScene):
CONFIG = {
"screen_height" : 1.0,
"source_point" : 5*LEFT,
"unit_distance" : 4,
"num_levels" : 100,
}
def construct(self):
self.move_screen_farther_away()
self.morph_into_3d()
def move_screen_farther_away(self):
source_point = self.source_point
unit_distance = self.unit_distance
# screen
screen = self.screen = Line(self.screen_height*UP, ORIGIN)
screen.get_reference_point = screen.get_center
screen.shift(
source_point + unit_distance*RIGHT -\
screen.get_reference_point()
)
# light source
light_source = self.light_source = LightSource(
# opacity_function = inverse_quadratic(1,5,1),
opacity_function = lambda r : 1./(r+1),
num_levels = self.num_levels,
radius = 10,
max_opacity = 0.2
)
light_source.set_max_opacity_spotlight(0.2)
light_source.set_screen(screen)
light_source.move_source_to(source_point)
# abbreviations
ambient_light = light_source.ambient_light
spotlight = light_source.spotlight
lighthouse = light_source.lighthouse
shadow = light_source.shadow
# Morty
morty = self.morty = Mortimer().scale(0.3)
morty.next_to(screen, RIGHT, buff = MED_LARGE_BUFF)
#Screen tracker
def update_spotlight(spotlight):
spotlight.update_sectors()
spotlight_update = ContinualUpdateFromFunc(spotlight, update_spotlight)
shadow_update = ContinualUpdateFromFunc(
shadow, lambda m : light_source.update_shadow()
)
# Light indicator
light_indicator = self.light_indicator = LightIndicator(
opacity_for_unit_intensity = 0.5,
)
def update_light_indicator(light_indicator):
distance = np.linalg.norm(screen.get_reference_point() - source_point)
light_indicator.set_intensity(1.0/(distance/unit_distance)**2)
light_indicator.next_to(morty, UP, MED_LARGE_BUFF)
light_indicator_update = ContinualUpdateFromFunc(
light_indicator, update_light_indicator
)
light_indicator_update.update(0)
continual_updates = self.continual_updates = [
spotlight_update, light_indicator_update, shadow_update
]
# Distance indicators
one_arrow = DoubleArrow(ORIGIN, unit_distance*RIGHT, buff = 0)
two_arrow = DoubleArrow(ORIGIN, 2*unit_distance*RIGHT, buff = 0)
arrows = VGroup(one_arrow, two_arrow)
arrows.set_color(WHITE)
one_arrow.move_to(source_point + DOWN, LEFT)
two_arrow.move_to(source_point + 1.75*DOWN, LEFT)
one = Integer(1).next_to(one_arrow, UP, SMALL_BUFF)
two = Integer(2).next_to(two_arrow, DOWN, SMALL_BUFF)
arrow_group = VGroup(one_arrow, one, two_arrow, two)
# Animations
self.add_foreground_mobjects(lighthouse, screen, morty)
self.add(shadow_update)
self.play(
SwitchOn(ambient_light),
morty.change, "pondering"
)
self.play(
SwitchOn(spotlight),
FadeIn(light_indicator)
)
# self.remove(spotlight)
self.add(*continual_updates)
self.wait()
for distance in -0.5, 0.5:
self.shift_by_distance(distance)
self.wait()
self.add_foreground_mobjects(one_arrow, one)
self.play(GrowFromCenter(one_arrow), Write(one))
self.wait()
self.add_foreground_mobjects(two_arrow, two)
self.shift_by_distance(1,
GrowFromPoint(two_arrow, two_arrow.get_left()),
Write(two, rate_func = squish_rate_func(smooth, 0.5, 1))
)
self.wait()
q_marks = TextMobject("???")
q_marks.next_to(light_indicator, UP)
self.play(
Write(q_marks),
morty.change, "confused", q_marks
)
self.play(Blink(morty))
self.play(FadeOut(q_marks), morty.change, "pondering")
self.wait()
self.shift_by_distance(-1, arrow_group.shift, DOWN)
self.set_variables_as_attrs(
ambient_light, spotlight, shadow, lighthouse,
morty, arrow_group,
*continual_updates
)
def morph_into_3d(self):
# axes = ThreeDAxes()
old_screen = self.screen
spotlight = self.spotlight
source_point = self.source_point
ambient_light = self.ambient_light
unit_distance = self.unit_distance
light_indicator = self.light_indicator
morty = self.morty
new_screen = Square(
side_length = self.screen_height,
stroke_color = WHITE,
stroke_width = 1,
fill_color = WHITE,
fill_opacity = 0.5
)
new_screen.rotate(TAU/4, UP)
new_screen.move_to(old_screen, IN)
old_screen.fade(1)
cone = ThreeDSpotlight(
new_screen, ambient_light,
source_point_func = lambda : source_point
)
cone_update_anim = ContinualThreeDLightConeUpdate(cone)
self.remove(self.spotlight_update, self.light_indicator_update)
self.add(
ContinualAnimation(new_screen),
cone_update_anim
)
self.remove(spotlight)
self.move_camera(
phi = 60*DEGREES,
theta = -145*DEGREES,
added_anims = [
# ApplyMethod(
# old_screen.scale, 1.8, {"about_edge" : DOWN},
# run_time = 2,
# ),
ApplyFunction(
lambda m : m.fade(1).shift(1.5*DOWN),
light_indicator,
remover = True
),
FadeOut(morty)
],
run_time = 2,
)
self.wait()
## Create screen copies
def get_screen_copy_group(distance):
n = int(distance)**2
copies = VGroup(*[new_screen.copy() for x in range(n)])
copies.rotate(-TAU/4, axis = UP)
copies.arrange_submobjects_in_grid(buff = 0)
copies.rotate(TAU/4, axis = UP)
copies.move_to(source_point, IN)
copies.shift(distance*RIGHT*unit_distance)
return copies
screen_copy_groups = map(get_screen_copy_group, range(1, 8))
def get_screen_copy_group_anim(n):
group = screen_copy_groups[n]
prev_group = screen_copy_groups[n-1]
group.save_state()
group.fade(1)
group.replace(prev_group, dim_to_match = 1)
return ApplyMethod(group.restore)
# corner_directions = [UP+OUT, DOWN+OUT, DOWN+IN, UP+IN]
# edge_directions = [
# UP, UP+OUT, OUT, DOWN+OUT, DOWN, DOWN+IN, IN, UP+IN, ORIGIN
# ]
# four_copies = VGroup(*[new_screen.copy() for x in range(4)])
# nine_copies = VGroup(*[new_screen.copy() for x in range(9)])
# def update_four_copies(four_copies):
# for mob, corner_direction in zip(four_copies, corner_directions):
# mob.move_to(new_screen, corner_direction)
# four_copies_update_anim = UpdateFromFunc(four_copies, update_four_copies)
# def update_nine_copies(nine_copies):
# for mob, corner_direction in zip(nine_copies, edge_directions):
# mob.move_to(new_screen, corner_direction)
# nine_copies_update_anim = UpdateFromFunc(nine_copies, update_nine_copies)
three_arrow = DoubleArrow(
source_point + 4*DOWN,
source_point + 4*DOWN + 3*unit_distance*RIGHT,
buff = 0,
color = WHITE
)
three = Integer(3)
three.next_to(three_arrow, DOWN)
new_screen.fade(1)
# self.add(
# ContinualAnimation(screen_copy),
# ContinualAnimation(four_copies),
# )
self.add(ContinualAnimation(screen_copy_groups[0]))
self.add(ContinualAnimation(screen_copy_groups[1]))
self.play(
new_screen.scale, 2, {"about_edge" : IN},
new_screen.shift, unit_distance*RIGHT,
get_screen_copy_group_anim(1),
run_time = 2,
)
self.wait()
self.move_camera(
phi = 75*DEGREES,
theta = -155*DEGREES,
distance = 7,
run_time = 10,
)
self.begin_ambient_camera_rotation(rate = -0.01)
self.add(ContinualAnimation(screen_copy_groups[2]))
self.play(
new_screen.scale, 3./2, {"about_edge" : IN},
new_screen.shift, unit_distance*RIGHT,
get_screen_copy_group_anim(2),
GrowFromPoint(three_arrow, three_arrow.get_left()),
Write(three, rate_func = squish_rate_func(smooth, 0.5, 1)),
run_time = 2,
)
self.begin_ambient_camera_rotation(rate = -0.01)
self.play(LaggedStart(
ApplyMethod, screen_copy_groups[2],
lambda m : (m.set_color, RED),
run_time = 5,
rate_func = there_and_back,
))
self.wait(2)
self.move_camera(distance = 18)
self.play(*[
ApplyMethod(mob.fade, 1)
for mob in screen_copy_groups[:2]
])
last_group = screen_copy_groups[2]
for n in range(4, len(screen_copy_groups)+1):
group = screen_copy_groups[n-1]
self.add(ContinualAnimation(group))
self.play(
new_screen.scale, float(n)/(n-1), {"about_edge" : IN},
new_screen.shift, unit_distance*RIGHT,
get_screen_copy_group_anim(n-1),
last_group.fade, 1,
)
last_group = group
self.wait()
###
def shift_by_distance(self, distance, *added_anims):
anims = [
self.screen.shift, self.unit_distance*distance*RIGHT,
]
if self.morty in self.mobjects:
anims.append(MaintainPositionRelativeTo(self.morty, self.screen))
anims += added_anims
self.play(*anims, run_time = 2)
class OtherInstanceOfInverseSquareLaw(Scene):
def construct(self):
title = TextMobject("Where the inverse square law shows up")
title.to_edge(UP)
h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS)
h_line.next_to(title, DOWN)
self.add(title, h_line)
items = VGroup(*[
TextMobject("- %s"%s).scale(1)
for s in [
"Heat", "Sound", "Radio waves", "Electric fields",
]
])
items.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT)
items.next_to(h_line, DOWN, LARGE_BUFF)
items.to_edge(LEFT)
dot = Dot()
dot.move_to(4*RIGHT)
self.add(dot)
def get_broadcast():
return Broadcast(dot, big_radius = 5, run_time = 5)
self.play(
LaggedStart(FadeIn, items, run_time = 4, lag_ratio = 0.7),
Succession(*[
get_broadcast()
for x in range(2)
])
)
self.play(get_broadcast())
self.wait()
class ScreensIntroWrapper(TeacherStudentsScene):
def construct(self):
point = VectorizedPoint(FRAME_X_RADIUS*LEFT/2 + FRAME_Y_RADIUS*UP/2)
self.play(self.teacher.change, "raise_right_hand")
self.change_student_modes(
"pondering", "erm", "confused",
look_at_arg = point,
)
self.play(self.teacher.look_at, point)
self.wait(5)
class ManipulateLightsourceSetups(PiCreatureScene):
CONFIG = {
"num_levels" : 100,
"radius" : 10,
"pi_creature_point" : 2*LEFT + 2*DOWN,
}
def construct(self):
unit_distance = 3
# Morty
morty = self.pi_creature
observer_point = morty.eyes[1].get_center()
bubble = ThoughtBubble(height = 3, width = 4, direction = RIGHT)
bubble.set_fill(BLACK, 1)
bubble.pin_to(morty)
# Indicator
light_indicator = LightIndicator(
opacity_for_unit_intensity = 0.5,
fill_color = YELLOW,
radius = 0.4,
reading_height = 0.2,
)
light_indicator.move_to(bubble.get_bubble_center())
def update_light_indicator(light_indicator):
distance = np.linalg.norm(light_source.get_source_point()-observer_point)
light_indicator.set_intensity((unit_distance/distance)**2)
#Light source
light_source = LightSource(
opacity_function = inverse_quadratic(1,2,1),
num_levels = self.num_levels,
radius = self.radius,
max_opacity_ambient = AMBIENT_FULL,
)
light_source.move_to(observer_point + unit_distance*RIGHT)
#Light source copies
light_source_copies = VGroup(*[light_source.copy() for x in range(2)])
for lsc, vect in zip(light_source_copies, [RIGHT, UP]):
lsc.move_to(observer_point + np.sqrt(2)*unit_distance*vect)
self.add(light_source)
self.add_foreground_mobjects(morty, bubble, light_indicator)
self.add(ContinualUpdateFromFunc(light_indicator, update_light_indicator))
self.play(
ApplyMethod(
light_source.shift, 0.66*unit_distance*LEFT,
rate_func = wiggle,
run_time = 5,
),
morty.change, "erm",
)
self.play(
UpdateFromAlphaFunc(
light_source,
lambda ls, a : ls.move_to(
observer_point + rotate_vector(
unit_distance*RIGHT, (1+1./8)*a*TAU
)
),
run_time = 6,
rate_func = bezier([0, 0, 1, 1])
),
morty.change, "pondering",
UpdateFromFunc(morty, lambda m : m.look_at(light_source))
)
self.wait()
plus = TexMobject("+")
point = light_indicator.get_center()
plus.move_to(point)
light_indicator_copy = light_indicator.copy()
self.add_foreground_mobjects(plus, light_indicator_copy)
self.play(
ReplacementTransform(
light_source, light_source_copies[0]
),
ReplacementTransform(
light_source.copy().fade(1),
light_source_copies[1]
),
FadeIn(plus),
UpdateFromFunc(
light_indicator_copy,
lambda li : update_light_indicator(li),
),
UpdateFromAlphaFunc(
light_indicator, lambda m, a : m.move_to(
point + a*0.75*RIGHT,
)
),
UpdateFromAlphaFunc(
light_indicator_copy, lambda m, a : m.move_to(
point + a*0.75*LEFT,
)
),
run_time = 2
)
self.play(morty.change, "hooray")
self.wait(2)
##
def create_pi_creature(self):
morty = Mortimer()
morty.flip()
morty.scale(0.5)
morty.move_to(self.pi_creature_point)
return morty
class TwoLightSourcesScene(ManipulateLightsourceSetups):
CONFIG = {
"num_levels" : 200,
"radius" : 15,
"a" : 9,
"b" : 5,
"origin_point" : 5*LEFT + 2.5*DOWN
}
def construct(self):
MAX_OPACITY = 0.4
INDICATOR_RADIUS = 0.6
OPACITY_FOR_UNIT_INTENSITY = 0.5
origin_point = self.origin_point
#Morty
morty = self.pi_creature
morty.change("hooray") # From last scen
morty.generate_target()
morty.target.change("plain")
morty.target.scale(0.6)
morty.target.next_to(
origin_point, LEFT, buff = 0,
submobject_to_align = morty.target.eyes[1]
)
#Axes
axes = Axes(
x_min = -1, x_max = 10.5,
y_min = -1, y_max = 6.5,
)
axes.shift(origin_point)
#Important reference points
A = axes.coords_to_point(self.a, 0)
B = axes.coords_to_point(0, self.b)
C = axes.coords_to_point(0, 0)
xA = A[0]
yA = A[1]
xB = B[0]
yB = B[1]
xC = C[0]
yC = C[1]
# find the coords of the altitude point H
# as the solution of a certain LSE
prelim_matrix = np.array([
[yA - yB, xB - xA],
[xA - xB, yA - yB]
]) # sic
prelim_vector = np.array(
[xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]
)
H2 = np.linalg.solve(prelim_matrix, prelim_vector)
H = np.append(H2, 0.)
#Lightsources
lsA = LightSource(
radius = self.radius,
num_levels = self.num_levels,
opacity_function = inverse_power_law(2, 1, 1, 1.5),
)
lsB = lsA.deepcopy()
lsA.move_source_to(A)
lsB.move_source_to(B)
lsC = lsA.deepcopy()
lsC.move_source_to(H)
#Lighthouse labels
A_label = TextMobject("A")
A_label.next_to(lsA.lighthouse, RIGHT)
B_label = TextMobject("B")
B_label.next_to(lsB.lighthouse, LEFT)
#Identical lighthouse labels
identical_lighthouses_words = TextMobject("All identical \\\\ lighthouses")
identical_lighthouses_words.to_corner(UP+RIGHT)
identical_lighthouses_words.shift(LEFT)
identical_lighthouses_arrows = VGroup(*[
Arrow(
identical_lighthouses_words.get_corner(DOWN+LEFT),
ls.get_source_point(),
buff = SMALL_BUFF,
color = WHITE,
)
for ls in lsA, lsB, lsC
])
#Lines
line_a = Line(C, A)
line_a.set_color(BLUE)
line_b = Line(C, B)
line_b.set_color(RED)
line_c = Line(A, B)
line_h = Line(H, C)
line_h.set_color(GREEN)
label_a = TexMobject("a")
label_a.match_color(line_a)
label_a.next_to(line_a, DOWN, buff = SMALL_BUFF)
label_b = TexMobject("b")
label_b.match_color(line_b)
label_b.next_to(line_b, LEFT, buff = SMALL_BUFF)
label_h = TexMobject("h")
label_h.match_color(line_h)
label_h.next_to(line_h.get_center(), RIGHT, buff = SMALL_BUFF)
perp_mark = VMobject().set_points_as_corners([
RIGHT, RIGHT+DOWN, DOWN
])
perp_mark.scale(0.25, about_point = ORIGIN)
perp_mark.rotate(line_c.get_angle() + TAU/4, about_point = ORIGIN)
perp_mark.shift(H)
# perp_mark.set_color(BLACK)
#Indicators
indicator = LightIndicator(
color = LIGHT_COLOR,
radius = INDICATOR_RADIUS,
opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY,
show_reading = True,
precision = 2,
)
indicator.next_to(origin_point, UP+LEFT)
def update_indicator(indicator):
intensity = 0
for ls in lsA, lsB, lsC:
if ls in self.mobjects:
distance = np.linalg.norm(ls.get_source_point() - origin_point)
d_indensity = fdiv(
3./(distance**2),
indicator.opacity_for_unit_intensity
)
d_indensity *= ls.ambient_light.submobjects[1].get_fill_opacity()
intensity += d_indensity
indicator.set_intensity(intensity)
indicator_update_anim = ContinualUpdateFromFunc(indicator, update_indicator)
new_indicator = indicator.copy()
new_indicator.light_source = lsC
new_indicator.measurement_point = C
#Note sure what this is...
distance1 = np.linalg.norm(origin_point - lsA.get_source_point())
intensity = lsA.ambient_light.opacity_function(distance1) / indicator.opacity_for_unit_intensity
distance2 = np.linalg.norm(origin_point - lsB.get_source_point())
intensity += lsB.ambient_light.opacity_function(distance2) / indicator.opacity_for_unit_intensity
# IPT Theorem
theorem = TexMobject(
"{1 \over ", "a^2}", "+",
"{1 \over", "b^2}", "=", "{1 \over","h^2}"
)
theorem.set_color_by_tex_to_color_map({
"a" : line_a.get_color(),
"b" : line_b.get_color(),
"h" : line_h.get_color(),
})
theorem_name = TextMobject("Inverse Pythagorean Theorem")
theorem_name.to_corner(UP+RIGHT)
theorem.next_to(theorem_name, DOWN, buff = MED_LARGE_BUFF)
theorem_box = SurroundingRectangle(theorem, color = WHITE)
#Transition from last_scene
self.play(
ShowCreation(axes, run_time = 2),
MoveToTarget(morty),
FadeIn(indicator),
)
#Move lsC around
self.add(lsC)
indicator_update_anim.update(0)
intensity = indicator.reading.number
self.play(
SwitchOn(lsC.ambient_light),
FadeIn(lsC.lighthouse),
UpdateFromAlphaFunc(
indicator, lambda i, a : i.set_intensity(a*intensity)
)
)
self.add(indicator_update_anim)
self.play(Animation(lsC), run_time = 0) #Why is this needed?
for point in axes.coords_to_point(5, 2), H:
self.play(
lsC.move_source_to, point,
path_arc = TAU/4,
run_time = 1.5,
)
self.wait()
# Draw line
self.play(
ShowCreation(line_h),
morty.change, "pondering"
)
self.wait()
self.play(
ShowCreation(line_c),
ShowCreation(perp_mark)
)
self.wait()
self.add_foreground_mobjects(line_c, line_h)
#Add alternate light_sources
for ls in lsA, lsB:
ls.save_state()
ls.move_to(lsC)
ls.fade(1)
self.add(ls)
self.play(
ls.restore,
run_time = 2
)
self.wait()
A_label.save_state()
A_label.center().fade(1)
self.play(A_label.restore)
self.wait()
self.play(ReplacementTransform(
A_label.copy().fade(1), B_label
))
self.wait(2)
#Compare combined of laA + lsB with lsC
rect = SurroundingRectangle(indicator, color = RED)
self.play(
FadeOut(lsA),
FadeOut(lsB),
)
self.play(ShowCreation(rect))
self.play(FadeOut(rect))
self.play(FadeOut(lsC))
self.add(lsA, lsB)
self.play(
FadeIn(lsA),
FadeIn(lsB),
)
self.play(ShowCreation(rect))
self.play(FadeOut(rect))
self.wait(2)
# All standard lighthouses
self.add(lsC)
self.play(FadeIn(lsC))
self.play(
Write(identical_lighthouses_words),
LaggedStart(GrowArrow, identical_lighthouses_arrows)
)
self.wait()
self.play(*map(FadeOut, [
identical_lighthouses_words,
identical_lighthouses_arrows,
]))
#Show labels of lengths
self.play(ShowCreation(line_a), Write(label_a))
self.wait()
self.play(ShowCreation(line_b), Write(label_b))
self.wait()
self.play(Write(label_h))
self.wait()
#Write IPT
a_part = theorem[:2]
b_part = theorem[2:5]
h_part = theorem[5:]
for part in a_part, b_part, h_part:
part.save_state()
part.scale(3)
part.fade(1)
a_part.move_to(lsA)
b_part.move_to(lsB)
h_part.move_to(lsC)
self.play(*map(FadeOut, [lsA, lsB, lsC, indicator]))
for ls, part in (lsA, a_part), (lsB, b_part), (lsC, h_part):
self.add(ls)
self.play(
SwitchOn(ls.ambient_light, run_time = 2),
FadeIn(ls.lighthouse),
part.restore
)
self.wait()
self.play(
Write(theorem_name),
ShowCreation(theorem_box)
)
self.play(morty.change, "confused")
self.wait(2)
class MathologerVideoWrapper(Scene):
def construct(self):
title = TextMobject("""
Mathologer's excellent video on \\\\
the many Pythagorean theorem cousins
""")
# title.scale(0.7)
title.to_edge(UP)
logo = ImageMobject("mathologer_logo")
logo.scale_to_fit_height(1)
logo.to_corner(UP+LEFT)
logo.shift(FRAME_WIDTH*RIGHT)
screen = ScreenRectangle(height = 5.5)
screen.next_to(title, DOWN)
self.play(
logo.shift, FRAME_WIDTH*LEFT,
LaggedStart(FadeIn, title),
run_time = 2
)
self.play(ShowCreation(screen))
self.wait(5)
class SimpleIPTProof(Scene):
def construct(self):
A = 5*RIGHT
B = 3*UP
C = ORIGIN
#Dumb and inefficient
alphas = np.linspace(0, 1, 500)
i = np.argmin(map(
lambda a : np.linalg.norm(interpolate(A, B, a)),
alphas
))
H = interpolate(A, B, alphas[i])
triangle = VGroup(
Line(C, A, color = BLUE),
Line(C, B, color = RED),
Line(A, B, color = WHITE),
Line(C, H, color = GREEN)
)
for line, char in zip(triangle, ["a", "b", "c", "h"]):
label = TexMobject(char)
label.match_color(line)
vect = line.get_center() - triangle.get_center()
vect /= np.linalg.norm(vect)
label.next_to(line.get_center(), vect)
triangle.add(label)
if char == "h":
label.next_to(line.get_center(), UP+LEFT, SMALL_BUFF)
triangle.to_corner(UP+LEFT)
self.add(triangle)
argument_lines = VGroup(
TexMobject(
"\\text{Area} = ",
"{1 \\over 2}", "a", "b", "=",
"{1 \\over 2}", "c", "h"
),
TexMobject("\\Downarrow"),
TexMobject("a^2", "b^2", "=", "c^2", "h^2"),
TexMobject("\\Downarrow"),
TexMobject(
"a^2", "b^2", "=",
"(", "a^2", "+", "b^2", ")", "h^2"
),
TexMobject("\\Downarrow"),
TexMobject(
"{1 \\over ", "h^2}", "=",
"{1 \\over ", "b^2}", "+",
"{1 \\over ", "a^2}",
),
)
argument_lines.arrange_submobjects(DOWN)
for line in argument_lines:
line.set_color_by_tex_to_color_map({
"a" : BLUE,
"b" : RED,
"h" : GREEN,
"Area" : WHITE,
"Downarrow" : WHITE,
})
all_equals = line.get_parts_by_tex("=")
if all_equals:
line.alignment_mob = all_equals[-1]
else:
line.alignment_mob = line[0]
line.shift(-line.alignment_mob.get_center()[0]*RIGHT)
argument_lines.next_to(triangle, RIGHT)
argument_lines.to_edge(UP)
prev_line = argument_lines[0]
self.play(FadeIn(prev_line))
for arrow, line in zip(argument_lines[1::2], argument_lines[2::2]):
line.save_state()
line.shift(
prev_line.alignment_mob.get_center() - \
line.alignment_mob.get_center()
)
line.fade(1)
self.play(
line.restore,
GrowFromPoint(arrow, arrow.get_top())
)
self.wait()
prev_line = line
class WeCanHaveMoreFunThanThat(TeacherStudentsScene):
def construct(self):
point = VectorizedPoint(FRAME_X_RADIUS*LEFT/2 + FRAME_Y_RADIUS*UP/2)
self.teacher_says(
"We can have \\\\ more fun than that!",
target_mode = "hooray"
)
self.change_student_modes(*3*["erm"], look_at_arg = point)
self.wait()
self.play(
RemovePiCreatureBubble(
self.teacher,
target_mode = "raise_right_hand",
look_at_arg = point,
),
self.get_student_changes(*3*["pondering"], look_at_arg = point)
)
self.wait(3)
class IPTScene(TwoLightSourcesScene, ZoomedScene):
CONFIG = {
"max_opacity_ambient" : 0.2,
"num_levels" : 200,
}
def construct(self):
#Copy pasting from TwoLightSourcesScene....Very bad...
origin_point = self.origin_point
self.remove(self.pi_creature)
#Axes
axes = Axes(
x_min = -1, x_max = 10.5,
y_min = -1, y_max = 6.5,
)
axes.shift(origin_point)
#Important reference points
A = axes.coords_to_point(self.a, 0)
B = axes.coords_to_point(0, self.b)
C = axes.coords_to_point(0, 0)
xA = A[0]
yA = A[1]
xB = B[0]
yB = B[1]
xC = C[0]
yC = C[1]
# find the coords of the altitude point H
# as the solution of a certain LSE
prelim_matrix = np.array([
[yA - yB, xB - xA],
[xA - xB, yA - yB]
]) # sic
prelim_vector = np.array(
[xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]
)
H2 = np.linalg.solve(prelim_matrix, prelim_vector)
H = np.append(H2, 0.)
#Lightsources
lsA = LightSource(
radius = self.radius,
num_levels = self.num_levels,
opacity_function = inverse_power_law(2, 1, 1, 1.5),
max_opacity_ambient = self.max_opacity_ambient,
)
lsA.lighthouse.scale(0.5, about_edge = UP)
lsB = lsA.deepcopy()
lsA.move_source_to(A)
lsB.move_source_to(B)
lsC = lsA.deepcopy()
lsC.move_source_to(H)
#Lighthouse labels
A_label = TextMobject("A")
A_label.next_to(lsA.lighthouse, RIGHT)
B_label = TextMobject("B")
B_label.next_to(lsB.lighthouse, LEFT)
#Lines
line_a = Line(C, A)
line_a.set_color(BLUE)
line_b = Line(C, B)
line_b.set_color(RED)
line_c = Line(A, B)
line_h = Line(H, C)
line_h.set_color(GREEN)
label_a = TexMobject("a")
label_a.match_color(line_a)
label_a.next_to(line_a, DOWN, buff = SMALL_BUFF)
label_b = TexMobject("b")
label_b.match_color(line_b)
label_b.next_to(line_b, LEFT, buff = SMALL_BUFF)
label_h = TexMobject("h")
label_h.match_color(line_h)
label_h.next_to(line_h.get_center(), RIGHT, buff = SMALL_BUFF)
perp_mark = VMobject().set_points_as_corners([
RIGHT, RIGHT+DOWN, DOWN
])
perp_mark.scale(0.25, about_point = ORIGIN)
perp_mark.rotate(line_c.get_angle() + TAU/4, about_point = ORIGIN)
perp_mark.shift(H)
# Mini triangle
m_hyp_a = Line(H, A)
m_a = line_a.copy()
m_hyp_b = Line(H, B)
m_b = line_b.copy()
mini_triangle = VGroup(m_a, m_hyp_a, m_b, m_hyp_b)
mini_triangle.set_stroke(width = 5)
mini_triangle.generate_target()
mini_triangle.target.scale(0.1, about_point = origin_point)
for part, part_target in zip(mini_triangle, mini_triangle.target):
part.target = part_target
# Screen label
screen_word = TextMobject("Screen")
screen_word.next_to(mini_triangle.target, UP+RIGHT, LARGE_BUFF)
screen_arrow = Arrow(
screen_word.get_bottom(),
mini_triangle.target.get_center(),
color = WHITE,
)
# IPT Theorem
theorem = TexMobject(
"{1 \over ", "a^2}", "+",
"{1 \over", "b^2}", "=", "{1 \over","h^2}"
)
theorem.set_color_by_tex_to_color_map({
"a" : line_a.get_color(),
"b" : line_b.get_color(),
"h" : line_h.get_color(),
})
theorem_name = TextMobject("Inverse Pythagorean Theorem")
theorem_name.to_corner(UP+RIGHT)
theorem.next_to(theorem_name, DOWN, buff = MED_LARGE_BUFF)
theorem_box = SurroundingRectangle(theorem, color = WHITE)
# Setup spotlights
spotlight_a = VGroup()
spotlight_a.screen = m_hyp_a
spotlight_b = VGroup()
spotlight_b.screen = m_hyp_b
for spotlight in spotlight_a, spotlight_b:
spotlight.get_source_point = lsC.get_source_point
dr = lsC.ambient_light.radius/lsC.ambient_light.num_levels
def update_spotlight(spotlight):
spotlight.submobjects = []
source_point = spotlight.get_source_point()
c1, c2 = spotlight.screen.get_start(), spotlight.screen.get_end()
distance = max(
np.linalg.norm(c1 - source_point),
np.linalg.norm(c2 - source_point),
)
n_parts = np.ceil(distance/dr)
alphas = np.linspace(0, 1, n_parts+1)
for a1, a2 in zip(alphas, alphas[1:]):
spotlight.add(Polygon(
interpolate(source_point, c1, a1),
interpolate(source_point, c1, a2),
interpolate(source_point, c2, a2),
interpolate(source_point, c2, a1),
fill_color = YELLOW,
fill_opacity = 2*lsC.ambient_light.opacity_function(a1*distance),
stroke_width = 0
))
def update_spotlights(spotlights):
for spotlight in spotlights:
update_spotlight(spotlight)
def get_spotlight_triangle(spotlight):
sp = spotlight.get_source_point()
c1 = spotlight.screen.get_start()
c2 = spotlight.screen.get_end()
return Polygon(
sp, c1, c2,
stroke_width = 0,
fill_color = YELLOW,
fill_opacity = 0.5,
)
spotlights = VGroup(spotlight_a, spotlight_b)
spotlights_update_anim = ContinualUpdateFromFunc(
spotlights, update_spotlights
)
# Add components
self.add(
axes,
lsA.ambient_light,
lsB.ambient_light,
lsC.ambient_light,
line_c,
)
self.add_foreground_mobjects(
lsA.lighthouse, A_label,
lsB.lighthouse, B_label,
lsC.lighthouse, line_h,
theorem, theorem_name, theorem_box,
)
# Show miniature triangle
self.play(ShowCreation(mini_triangle, submobject_mode = "all_at_once"))
self.play(
MoveToTarget(mini_triangle),
run_time = 2,
)
self.add_foreground_mobject(mini_triangle)
# Show beams of light
self.play(
Write(screen_word),
GrowArrow(screen_arrow),
)
self.wait()
spotlights_update_anim.update(0)
self.play(
LaggedStart(FadeIn, spotlight_a),
LaggedStart(FadeIn, spotlight_b),
Animation(screen_arrow),
)
self.add(spotlights_update_anim)
self.play(*map(FadeOut, [screen_word, screen_arrow]))
self.wait()
# Reshape screen
m_hyps = [m_hyp_a, m_hyp_b]
for hyp, line in (m_hyp_a, m_a), (m_hyp_b, m_b):
hyp.save_state()
hyp.alt_version = line.copy()
hyp.alt_version.set_color(WHITE)
for x in range(2):
self.play(*[
Transform(m, m.alt_version)
for m in m_hyps
])
self.wait()
self.play(*[m.restore for m in m_hyps])
self.wait()
# Show spotlight a key point
def show_beaming_light(spotlight):
triangle = get_spotlight_triangle(spotlight)
for x in range(3):
anims = []
if x > 0:
anims.append(FadeOut(triangle.copy()))
anims.append(GrowFromPoint(triangle, triangle.points[0]))
self.play(*anims)
self.play(FadeOut(triangle))
pass
def show_key_point(spotlight, new_point):
screen = spotlight.screen
update_spotlight_anim = UpdateFromFunc(spotlight, update_spotlight)
self.play(
Transform(screen, screen.alt_version),
update_spotlight_anim,
)
show_beaming_light(spotlight)
self.play(screen.restore, update_spotlight_anim)
self.wait()
self.play(
lsC.move_source_to, new_point,
Transform(screen, screen.alt_version),
update_spotlight_anim,
run_time = 2
)
show_beaming_light(spotlight)
self.wait()
self.play(
lsC.move_source_to, H,
screen.restore,
update_spotlight_anim,
run_time = 2
)
self.wait()
self.remove(spotlights_update_anim)
self.add(spotlight_b)
self.play(*map(FadeOut, [
spotlight_a, lsA.ambient_light, lsB.ambient_light
]))
show_key_point(spotlight_b, A)
self.play(
FadeOut(spotlight_b),
FadeIn(spotlight_a),
)
show_key_point(spotlight_a, B)
self.wait()
class HomeworkWrapper(Scene):
def construct(self):
title = TextMobject("Homework")
title.to_edge(UP)
screen = ScreenRectangle(height = 6)
screen.center()
self.add(title)
self.play(ShowCreation(screen))
self.wait(5)
class HeresWhereThingsGetGood(TeacherStudentsScene):
def construct(self):
self.teacher_says("Now for the \\\\ good part!")
self.change_student_modes(*["hooray"]*3)
self.change_student_modes(*["happy"]*3)
self.wait()
class DiameterTheorem(TeacherStudentsScene):
def construct(self):
circle = Circle(radius = 2, color = WHITE)
circle.next_to(self.students[2], UP)
self.add(circle)
center = Dot(circle.get_center(), color = WHITE)
self.add_foreground_mobject(center)
diameter_word = TextMobject("Diameter")
diameter_word.next_to(center, DOWN, SMALL_BUFF)
point = VectorizedPoint(circle.get_top())
triangle = Polygon(LEFT, RIGHT, UP)
triangle.set_stroke(BLUE)
triangle.set_fill(WHITE, 0.5)
def update_triangle(triangle):
triangle.set_points_as_corners([
circle.get_left(), circle.get_right(),
point.get_center(), circle.get_left(),
])
triangle_update_anim = ContinualUpdateFromFunc(
triangle, update_triangle
)
triangle_update_anim.update(0)
perp_mark = VMobject()
perp_mark.set_points_as_corners([LEFT, DOWN, RIGHT])
perp_mark.shift(DOWN)
perp_mark.scale(0.15, about_point = ORIGIN)
perp_mark.shift(point.get_center())
perp_mark.add(point.copy())
self.play(
self.teacher.change, "raise_right_hand",
DrawBorderThenFill(triangle),
Write(diameter_word),
)
self.play(
ShowCreation(perp_mark),
self.get_student_changes(*["pondering"]*3)
)
self.add_foreground_mobjects(perp_mark)
self.add(triangle_update_anim)
for angle in 0.2*TAU, -0.4*TAU, 0.3*TAU:
point.generate_target()
point.target.rotate(angle, about_point = circle.get_center())
perp_mark.generate_target()
perp_mark.target.rotate(angle/2)
perp_mark.target.shift(
point.target.get_center() - \
perp_mark.target[1].get_center()
)
self.play(
MoveToTarget(point),
MoveToTarget(perp_mark),
path_arc = angle,
run_time = 3,
)
class InscribedeAngleThreorem(TeacherStudentsScene):
def construct(self):
circle = Circle(radius = 2, color = WHITE)
circle.next_to(self.students[2], UP)
self.add(circle)
title = TextMobject("Inscribed angle \\\\ theorem")
title.to_corner(UP+LEFT)
self.add(title)
center = Dot(circle.get_center(), color = WHITE)
self.add_foreground_mobject(center)
point = VectorizedPoint(circle.get_left())
shape = Polygon(UP+LEFT, ORIGIN, DOWN+LEFT, RIGHT)
shape.set_stroke(BLUE)
def update_shape(shape):
shape.set_points_as_corners([
point.get_center(),
circle.point_from_proportion(7./8),
circle.get_center(),
circle.point_from_proportion(1./8),
point.get_center(),
])
shape_update_anim = ContinualUpdateFromFunc(
shape, update_shape
)
shape_update_anim.update(0)
angle_mark = Arc(start_angle = -TAU/8, angle = TAU/4)
angle_mark.scale(0.3, about_point = ORIGIN)
angle_mark.shift(circle.get_center())
theta = TexMobject("\\theta").set_color(RED)
theta.next_to(angle_mark, RIGHT, MED_SMALL_BUFF)
angle_mark.match_color(theta)
half_angle_mark = Arc(start_angle = -TAU/16, angle = TAU/8)
half_angle_mark.scale(0.3, about_point = ORIGIN)
half_angle_mark.shift(point.get_center())
half_angle_mark.add(point.copy())
theta_halves = TexMobject("\\theta/2").set_color(GREEN)
theta_halves.scale(0.7)
half_angle_mark.match_color(theta_halves)
theta_halves_update = UpdateFromFunc(
theta_halves, lambda m : m.move_to(interpolate(
point.get_center(),
half_angle_mark.point_from_proportion(0.5),
2.5,
))
)
theta_halves_update.update(0)
self.play(
self.teacher.change, "raise_right_hand",
ShowCreation(shape, rate_func = None),
)
self.play(*map(FadeIn, [angle_mark, theta]))
self.play(
ShowCreation(half_angle_mark),
Write(theta_halves),
self.get_student_changes(*["pondering"]*3)
)
self.add_foreground_mobjects(half_angle_mark, theta_halves)
self.add(shape_update_anim)
for angle in 0.25*TAU, -0.4*TAU, 0.3*TAU, -0.35*TAU:
point.generate_target()
point.target.rotate(angle, about_point = circle.get_center())
half_angle_mark.generate_target()
half_angle_mark.target.rotate(angle/2)
half_angle_mark.target.shift(
point.target.get_center() - \
half_angle_mark.target[1].get_center()
)
self.play(
MoveToTarget(point),
MoveToTarget(half_angle_mark),
theta_halves_update,
path_arc = angle,
run_time = 3,
)
class PondScene(ThreeDScene):
def construct(self):
BASELINE_YPOS = -2.5
OBSERVER_POINT = np.array([0,BASELINE_YPOS,0])
LAKE0_RADIUS = 1.5
INDICATOR_RADIUS = 0.6
TICK_SIZE = 0.5
LIGHTHOUSE_HEIGHT = 0.5
LAKE_COLOR = BLUE
LAKE_OPACITY = 0.15
LAKE_STROKE_WIDTH = 5.0
LAKE_STROKE_COLOR = BLUE
TEX_SCALE = 0.8
DOT_COLOR = BLUE
LIGHT_MAX_INT = 1
LIGHT_SCALE = 2.5
LIGHT_CUTOFF = 1
RIGHT_ANGLE_SIZE = 0.3
self.cumulated_zoom_factor = 1
def right_angle(pointA, pointB, pointC, size = 1):
v1 = pointA - pointB
v1 = size * v1/np.linalg.norm(v1)
v2 = pointC - pointB
v2 = size * v2/np.linalg.norm(v2)
P = pointB
Q = pointB + v1
R = Q + v2
S = R - v1
angle_sign = VMobject()
angle_sign.set_points_as_corners([P,Q,R,S,P])
angle_sign.mark_paths_closed = True
angle_sign.set_fill(color = WHITE, opacity = 1)
angle_sign.set_stroke(width = 0)
return angle_sign
def triangle(pointA, pointB, pointC):
mob = VMobject()
mob.set_points_as_corners([pointA, pointB, pointC, pointA])
mob.mark_paths_closed = True
mob.set_fill(color = WHITE, opacity = 0.5)
mob.set_stroke(width = 0)
return mob
def zoom_out_scene(factor):
self.remove_foreground_mobject(self.ls0_dot)
self.remove(self.ls0_dot)
phi0 = self.camera.get_phi() # default is 0 degs
theta0 = self.camera.get_theta() # default is -90 degs
distance0 = self.camera.get_distance()
distance1 = 2 * distance0
camera_target_point = self.camera.get_spherical_coords(phi0, theta0, distance1)
self.play(
ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point),
self.zoomable_mobs.shift, self.obs_dot.get_center(),
self.unzoomable_mobs.scale,2,{"about_point" : ORIGIN},
)
self.cumulated_zoom_factor *= factor
# place ls0_dot by hand
#old_radius = self.ls0_dot.radius
#self.ls0_dot.radius = 2 * old_radius
#v = self.ls0_dot.get_center() - self.obs_dot.get_center()
#self.ls0_dot.shift(v)
#self.ls0_dot.move_to(self.outer_lake.get_center())
self.ls0_dot.scale(2, about_point = ORIGIN)
#self.add_foreground_mobject(self.ls0_dot)
def shift_scene(v):
self.play(
self.zoomable_mobs.shift,v,
self.unzoomable_mobs.shift,v
)
self.zoomable_mobs = VMobject()
self.unzoomable_mobs = VMobject()
baseline = VMobject()
baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]])
baseline.set_stroke(width = 0) # in case it gets accidentally added to the scene
self.zoomable_mobs.add(baseline) # prob not necessary
obs_dot = self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR)
ls0_dot = self.ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE)
self.unzoomable_mobs.add(self.obs_dot)#, self.ls0_dot)
# lake
lake0 = Circle(radius = LAKE0_RADIUS,
stroke_width = 0,
fill_color = LAKE_COLOR,
fill_opacity = LAKE_OPACITY
)
lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP)
self.zoomable_mobs.add(lake0)
# Morty and indicator
morty = Mortimer().flip().scale(0.3)
morty.next_to(OBSERVER_POINT,DOWN)
indicator = LightIndicator(precision = 2,
radius = INDICATOR_RADIUS,
show_reading = False,
color = LIGHT_COLOR
)
indicator.next_to(morty,LEFT)
self.unzoomable_mobs.add(morty, indicator)
# first lighthouse
original_op_func = inverse_quadratic(LIGHT_MAX_INT,LIGHT_SCALE,LIGHT_CUTOFF)
ls0 = LightSource(opacity_function = original_op_func, radius = 15.0, num_levels = 150)
ls0.lighthouse.scale_to_fit_height(LIGHTHOUSE_HEIGHT)
ls0.lighthouse.height = LIGHTHOUSE_HEIGHT
ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP)
self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light)
# self.add(lake0, morty, obs_dot, ls0_dot, ls0.lighthouse)
# shore arcs
arc_left = Arc(-TAU/2,
radius = LAKE0_RADIUS,
start_angle = -TAU/4,
stroke_width = LAKE_STROKE_WIDTH,
stroke_color = LAKE_STROKE_COLOR
)
arc_left.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP)
one_left = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE)
one_left.next_to(arc_left,LEFT)
arc_right = Arc(TAU/2,
radius = LAKE0_RADIUS,
start_angle = -TAU/4,
stroke_width = LAKE_STROKE_WIDTH,
stroke_color = LAKE_STROKE_COLOR
)
arc_right.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP)
one_right = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE)
one_right.next_to(arc_right,RIGHT)
# New introduction
lake0.save_state()
morty.save_state()
lake0.scale_to_fit_height(6)
morty.to_corner(UP+LEFT)
morty.fade(1)
lake0.center()
lake_word = TextMobject("Lake")
lake_word.scale(2)
lake_word.move_to(lake0)
self.play(
DrawBorderThenFill(lake0, stroke_width = 1),
Write(lake_word)
)
self.play(
lake0.restore,
lake_word.scale, 0.5, {"about_point" : lake0.get_bottom()},
lake_word.fade, 1
)
self.remove(lake_word)
self.play(morty.restore)
self.play(
GrowFromCenter(obs_dot),
GrowFromCenter(ls0_dot),
FadeIn(ls0.lighthouse)
)
self.add_foreground_mobjects(ls0.lighthouse, obs_dot, ls0_dot)
self.play(
SwitchOn(ls0.ambient_light),
Animation(ls0.lighthouse),
)
self.wait()
self.play(
morty.move_to, ls0.lighthouse,
run_time = 3,
path_arc = TAU/2,
rate_func = there_and_back
)
self.play(
ShowCreation(arc_right),
Write(one_right),
)
self.play(
ShowCreation(arc_left),
Write(one_left),
)
self.play(
lake0.set_stroke, {
"color": LAKE_STROKE_COLOR,
"width" : LAKE_STROKE_WIDTH
},
)
self.wait()
self.add_foreground_mobjects(morty)
# Show indicator
self.play(FadeIn(indicator))
self.play(indicator.set_intensity, 0.5)
diameter_start = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.02)
diameter_stop = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.98)
# diameter
diameter = DoubleArrow(diameter_start,
diameter_stop,
buff = 0,
color = WHITE,
)
diameter_text = TexMobject("d").scale(TEX_SCALE)
diameter_text.next_to(diameter,RIGHT)
self.play(
GrowFromCenter(diameter),
Write(diameter_text),
#FadeOut(self.obs_dot),
FadeOut(ls0_dot)
)
self.wait()
indicator_reading = TexMobject("{1 \over d^2}").scale(TEX_SCALE)
indicator_reading.move_to(indicator)
self.unzoomable_mobs.add(indicator_reading)
self.play(
ReplacementTransform(
diameter_text[0].copy(),
indicator_reading[2],
),
FadeIn(indicator_reading)
)
self.wait()
# replace d with its value
new_diameter_text = TexMobject("{2 \over \pi}").scale(TEX_SCALE)
new_diameter_text.color = LAKE_COLOR
new_diameter_text.move_to(diameter_text)
self.play(FadeOut(diameter_text))
self.play(FadeIn(new_diameter_text))
self.wait(2)
# insert into indicator reading
new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE)
new_reading.move_to(indicator)
new_diameter_text_copy = new_diameter_text.copy()
new_diameter_text_copy.submobjects.reverse()
self.play(
FadeOut(indicator_reading),
ReplacementTransform(
new_diameter_text_copy,
new_reading,
parth_arc = 30*DEGREES
)
)
indicator_reading = new_reading
self.wait(2)
self.play(
FadeOut(one_left),
FadeOut(one_right),
FadeOut(new_diameter_text),
FadeOut(arc_left),
FadeOut(arc_right)
)
self.add_foreground_mobjects(indicator, indicator_reading)
self.unzoomable_mobs.add(indicator_reading)
def indicator_wiggle():
INDICATOR_WIGGLE_FACTOR = 1.3
self.play(
ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle),
ScaleInPlace(indicator_reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle)
)
def angle_for_index(i,step):
return -TAU/4 + TAU/2**step * (i + 0.5)
def position_for_index(i, step, scaled_down = False):
theta = angle_for_index(i,step)
radial_vector = np.array([np.cos(theta),np.sin(theta),0])
position = self.lake_center + self.lake_radius * radial_vector
if scaled_down:
return position.scale_about_point(self.obs_dot.get_center(),0.5)
else:
return position
def split_light_source(i, step, show_steps = True, animate = True, run_time = 1):
ls_new_loc1 = position_for_index(i,step + 1)
ls_new_loc2 = position_for_index(i + 2**step,step + 1)
hyp = VMobject()
hyp1 = Line(self.lake_center,ls_new_loc1)
hyp2 = Line(self.lake_center,ls_new_loc2)
hyp.add(hyp2,hyp1)
self.new_hypotenuses.append(hyp)
if show_steps == True:
self.play(
ShowCreation(hyp, run_time = run_time)
)
leg1 = Line(self.obs_dot.get_center(),ls_new_loc1)
leg2 = Line(self.obs_dot.get_center(),ls_new_loc2)
self.new_legs_1.append(leg1)
self.new_legs_2.append(leg2)
if show_steps == True:
self.play(
ShowCreation(leg1, run_time = run_time),
ShowCreation(leg2, run_time = run_time),
)
ls1 = self.light_sources_array[i]
ls2 = ls1.copy()
if animate == True:
self.add(ls2)
self.additional_light_sources.append(ls2)
# check if the light sources are on screen
ls_old_loc = np.array(ls1.get_source_point())
onscreen_old = np.any(np.abs(ls_old_loc) < 10)
onscreen_1 = np.any(np.abs(ls_new_loc1) < 10)
onscreen_2 = np.any(np.abs(ls_new_loc2) < 10)
show_animation = (onscreen_old or onscreen_1 or onscreen_2)
if show_animation or animate:
ls1.generate_target()
ls2.generate_target()
ls1.target.move_source_to(ls_new_loc1)
ls2.target.move_source_to(ls_new_loc2)
ls1.fade(1)
self.play(
MoveToTarget(ls1), MoveToTarget(ls2),
run_time = run_time
)
else:
ls1.move_source_to(ls_new_loc1)
ls2.move_source_to(ls_new_loc1)
def construction_step(n, show_steps = True, run_time = 1,
simultaneous_splitting = False):
# we assume that the scene contains:
# an inner lake, self.inner_lake
# an outer lake, self.outer_lake
# light sources, self.light_sources
# legs from the observer point to each light source
# self.legs
# altitudes from the observer point to the
# locations of the light sources in the previous step
# self.altitudes
# hypotenuses connecting antipodal light sources
# self.hypotenuses
# these are mobjects!
# first, fade out all of the hypotenuses and altitudes
if show_steps == True:
self.zoomable_mobs.remove(self.hypotenuses, self.altitudes, self.inner_lake)
self.play(
FadeOut(self.hypotenuses),
FadeOut(self.altitudes),
FadeOut(self.inner_lake)
)
else:
self.zoomable_mobs.remove(self.inner_lake)
self.play(
FadeOut(self.inner_lake)
)
# create a new, outer lake
self.lake_center = self.obs_dot.get_center() + self.lake_radius * UP
new_outer_lake = Circle(radius = self.lake_radius,
stroke_width = LAKE_STROKE_WIDTH,
fill_color = LAKE_COLOR,
fill_opacity = LAKE_OPACITY,
stroke_color = LAKE_STROKE_COLOR
)
new_outer_lake.move_to(self.lake_center)
if show_steps == True:
self.play(
FadeIn(new_outer_lake, run_time = run_time),
FadeIn(self.ls0_dot)
)
else:
self.play(
FadeIn(new_outer_lake, run_time = run_time),
)
self.wait()
self.inner_lake = self.outer_lake
self.outer_lake = new_outer_lake
self.altitudes = self.legs
#self.lake_center = self.outer_lake.get_center()
self.additional_light_sources = []
self.new_legs_1 = []
self.new_legs_2 = []
self.new_hypotenuses = []
if simultaneous_splitting == False:
for i in range(2**n):
split_light_source(i,
step = n,
show_steps = show_steps,
run_time = run_time
)
if n == 1 and i == 0:
# show again where the right angles are
A = self.light_sources[0].get_center()
B = self.additional_light_sources[0].get_center()
C = self.obs_dot.get_center()
triangle1 = triangle(
A, C, B
)
right_angle1 = right_angle(
A, C, B, size = 2 * RIGHT_ANGLE_SIZE
)
self.play(
FadeIn(triangle1),
FadeIn(right_angle1)
)
self.wait()
self.play(
FadeOut(triangle1),
FadeOut(right_angle1)
)
self.wait()
H = self.inner_lake.get_center() + self.lake_radius/2 * RIGHT
L = self.outer_lake.get_center()
triangle2 = triangle(
L, H, C
)
right_angle2 = right_angle(
L, H, C, size = 2 * RIGHT_ANGLE_SIZE
)
self.play(
FadeIn(triangle2),
FadeIn(right_angle2)
)
self.wait()
self.play(
FadeOut(triangle2),
FadeOut(right_angle2)
)
self.wait()
else: # simultaneous splitting
old_lake = self.outer_lake.copy()
old_ls = self.light_sources.copy()
old_ls2 = old_ls.copy()
for submob in old_ls2.submobjects:
old_ls.add(submob)
self.remove(self.outer_lake, self.light_sources)
self.add(old_lake, old_ls)
for i in range(2**n):
split_light_source(i,
step = n,
show_steps = show_steps,
run_time = run_time,
animate = False
)
self.play(
ReplacementTransform(old_ls, self.light_sources, run_time = run_time),
ReplacementTransform(old_lake, self.outer_lake, run_time = run_time),
)
# collect the newly created mobs (in arrays)
# into the appropriate Mobject containers
self.legs = VMobject()
for leg in self.new_legs_1:
self.legs.add(leg)
self.zoomable_mobs.add(leg)
for leg in self.new_legs_2:
self.legs.add(leg)
self.zoomable_mobs.add(leg)
for hyp in self.hypotenuses.submobjects:
self.zoomable_mobs.remove(hyp)
self.hypotenuses = VMobject()
for hyp in self.new_hypotenuses:
self.hypotenuses.add(hyp)
self.zoomable_mobs.add(hyp)
for ls in self.additional_light_sources:
self.light_sources.add(ls)
self.light_sources_array.append(ls)
self.zoomable_mobs.add(ls)
# update scene
self.add(
self.light_sources,
self.inner_lake,
self.outer_lake,
)
self.zoomable_mobs.add(self.light_sources, self.inner_lake, self.outer_lake)
if show_steps == True:
self.add(
self.legs,
self.hypotenuses,
self.altitudes,
)
self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes)
self.wait()
if show_steps == True:
self.play(FadeOut(self.ls0_dot))
#self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP
self.lake_radius *= 2
self.lake_center = ls0_loc = ls0.get_source_point()
self.inner_lake = VMobject()
self.outer_lake = lake0
self.legs = VMobject()
self.legs.add(Line(OBSERVER_POINT,self.lake_center))
self.altitudes = VMobject()
self.hypotenuses = VMobject()
self.light_sources_array = [ls0]
self.light_sources = VMobject()
self.light_sources.add(ls0)
self.lake_radius = 2 * LAKE0_RADIUS # don't ask...
self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources)
self.add(
self.inner_lake,
self.outer_lake,
self.legs,
self.altitudes,
self.hypotenuses
)
self.play(FadeOut(diameter))
self.additional_light_sources = []
self.new_legs_1 = []
self.new_legs_2 = []
self.new_hypotenuses = []
construction_step(0)
my_triangle = triangle(
self.light_sources[0].get_source_point(),
OBSERVER_POINT,
self.light_sources[1].get_source_point()
)
angle_sign1 = right_angle(
self.light_sources[0].get_source_point(),
OBSERVER_POINT,
self.light_sources[1].get_source_point(),
size = RIGHT_ANGLE_SIZE
)
self.play(
FadeIn(angle_sign1),
FadeIn(my_triangle)
)
angle_sign2 = right_angle(
self.light_sources[1].get_source_point(),
self.lake_center,
OBSERVER_POINT,
size = RIGHT_ANGLE_SIZE
)
self.play(
FadeIn(angle_sign2)
)
self.wait()
self.play(
FadeOut(angle_sign1),
FadeOut(angle_sign2),
FadeOut(my_triangle)
)
indicator_wiggle()
self.remove(self.ls0_dot)
zoom_out_scene(2)
construction_step(1)
indicator_wiggle()
#self.play(FadeOut(self.ls0_dot))
zoom_out_scene(2)
construction_step(2)
indicator_wiggle()
self.play(FadeOut(self.ls0_dot))
self.play(
FadeOut(self.altitudes),
FadeOut(self.hypotenuses),
FadeOut(self.legs)
)
max_it = 6
scale = 2**(max_it - 4)
TEX_SCALE *= scale
# for i in range(3,max_it + 1):
# construction_step(i, show_steps = False, run_time = 4.0/2**i,
# simultaneous_splitting = True)
# simultaneous expansion of light sources from now on
self.play(FadeOut(self.inner_lake))
for n in range(3,max_it + 1):
new_lake = self.outer_lake.copy().scale(2,about_point = self.obs_dot.get_center())
for ls in self.light_sources_array:
lsp = ls.copy()
self.light_sources.add(lsp)
self.add(lsp)
self.light_sources_array.append(lsp)
new_lake_center = new_lake.get_center()
new_lake_radius = 0.5 * new_lake.get_width()
shift_list = (Transform(self.outer_lake,new_lake),)
for i in range(2**n):
theta = -TAU/4 + (i + 0.5) * TAU / 2**n
v = np.array([np.cos(theta), np.sin(theta),0])
pos1 = new_lake_center + new_lake_radius * v
pos2 = new_lake_center - new_lake_radius * v
shift_list += (self.light_sources.submobjects[i].move_source_to,pos1)
shift_list += (self.light_sources.submobjects[i+2**n].move_source_to,pos2)
self.play(*shift_list)
#self.revert_to_original_skipping_status()
# Now create a straight number line and transform into it
MAX_N = 17
origin_point = self.obs_dot.get_center()
self.number_line = NumberLine(
x_min = -MAX_N,
x_max = MAX_N + 1,
color = WHITE,
number_at_center = 0,
stroke_width = LAKE_STROKE_WIDTH,
stroke_color = LAKE_STROKE_COLOR,
#numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1),
numbers_to_show = range(-MAX_N,MAX_N + 1,2),
unit_size = LAKE0_RADIUS * TAU/4 / 2 * scale,
tick_frequency = 1,
line_to_number_buff = LARGE_BUFF,
label_direction = UP,
).shift(scale * 2.5 * DOWN)
self.number_line.label_direction = DOWN
self.number_line_labels = self.number_line.get_number_mobjects()
self.wait()
origin_point = self.number_line.number_to_point(0)
nl_sources = VMobject()
pond_sources = VMobject()
for i in range(-MAX_N,MAX_N+1):
anchor = self.number_line.number_to_point(2*i + 1)
ls = self.light_sources_array[i].copy()
ls.move_source_to(anchor)
nl_sources.add(ls)
pond_sources.add(self.light_sources_array[i].copy())
self.add(pond_sources)
self.remove(self.light_sources)
self.outer_lake.rotate(TAU/8)
# open sea
open_sea = Rectangle(
width = 20 * scale,
height = 10 * scale,
stroke_width = LAKE_STROKE_WIDTH,
stroke_color = LAKE_STROKE_COLOR,
fill_color = LAKE_COLOR,
fill_opacity = LAKE_OPACITY,
).flip().next_to(origin_point,UP,buff = 0)
self.play(
ReplacementTransform(pond_sources,nl_sources),
ReplacementTransform(self.outer_lake,open_sea),
FadeOut(self.inner_lake)
)
self.play(FadeIn(self.number_line))
self.wait()
v = 4 * scale * UP
self.play(
nl_sources.shift,v,
morty.shift,v,
self.number_line.shift,v,
indicator.shift,v,
indicator_reading.shift,v,
open_sea.shift,v,
self.obs_dot.shift,v,
)
self.number_line_labels.shift(v)
origin_point = self.number_line.number_to_point(0)
#self.remove(self.obs_dot)
self.play(
indicator.move_to, origin_point + scale * UP,
indicator_reading.move_to, origin_point + scale * UP,
FadeOut(open_sea),
FadeOut(morty),
FadeIn(self.number_line_labels)
)
two_sided_sum = TexMobject("\dots", "+", "{1\over (-11)^2}",\
"+", "{1\over (-9)^2}", " + ", "{1\over (-7)^2}", " + ", "{1\over (-5)^2}", " + ", \
"{1\over (-3)^2}", " + ", "{1\over (-1)^2}", " + ", "{1\over 1^2}", " + ", \
"{1\over 3^2}", " + ", "{1\over 5^2}", " + ", "{1\over 7^2}", " + ", \
"{1\over 9^2}", " + ", "{1\over 11^2}", " + ", "\dots")
nb_symbols = len(two_sided_sum.submobjects)
two_sided_sum.scale(TEX_SCALE)
for (i,submob) in zip(range(nb_symbols),two_sided_sum.submobjects):
submob.next_to(self.number_line.number_to_point(i - 13),DOWN, buff = 2*scale)
if (i == 0 or i % 2 == 1 or i == nb_symbols - 1): # non-fractions
submob.shift(0.3 * scale * DOWN)
self.play(Write(two_sided_sum))
for i in range(MAX_N - 5, MAX_N):
self.remove(nl_sources.submobjects[i].ambient_light)
for i in range(MAX_N, MAX_N + 5):
self.add_foreground_mobject(nl_sources.submobjects[i].ambient_light)
self.wait()
covering_rectangle = Rectangle(
width = FRAME_X_RADIUS * scale,
height = 2 * FRAME_Y_RADIUS * scale,
stroke_width = 0,
fill_color = BLACK,
fill_opacity = 1,
)
covering_rectangle.next_to(ORIGIN,LEFT,buff = 0)
for i in range(10):
self.add_foreground_mobject(nl_sources.submobjects[i])
self.add_foreground_mobject(indicator)
self.add_foreground_mobject(indicator_reading)
half_indicator_reading = TexMobject("{\pi^2 \over 8}").scale(TEX_SCALE)
half_indicator_reading.move_to(indicator)
central_plus_sign = two_sided_sum[13]
self.play(
FadeIn(covering_rectangle),
Transform(indicator_reading, half_indicator_reading),
FadeOut(central_plus_sign)
)
equals_sign = TexMobject("=").scale(TEX_SCALE)
equals_sign.move_to(central_plus_sign)
p = 2 * scale * LEFT + central_plus_sign.get_center()[1] * UP
self.play(
indicator.move_to,p,
indicator_reading.move_to,p,
FadeIn(equals_sign),
)
self.revert_to_original_skipping_status()
# show Randy admiring the result
randy = Randolph(color = MAROON_E).scale(scale).move_to(2*scale*DOWN+5*scale*LEFT)
self.play(FadeIn(randy))
self.play(randy.change,"happy")
self.play(randy.change,"hooray")
class CircumferenceText(Scene):
CONFIG = {"n" : 16}
def construct(self):
words = TextMobject("Circumference %d"%self.n)
words.scale(1.25)
words.to_corner(UP+LEFT)
self.add(words)
class CenterOfLargerCircleOverlayText(Scene):
def construct(self):
words = TextMobject("Center of \\\\ larger circle")
arrow = Vector(DOWN+LEFT, color = WHITE)
arrow.shift(words.get_bottom() + SMALL_BUFF*DOWN - arrow.get_start())
group = VGroup(words, arrow)
group.scale_to_fit_height(FRAME_HEIGHT - 1)
group.to_edge(UP)
self.add(group)
class DiameterWordOverlay(Scene):
def construct(self):
word = TextMobject("Diameter")
word.scale_to_fit_width(FRAME_X_RADIUS)
word.rotate(-45*DEGREES)
self.play(Write(word))
self.wait()
class YayIPTApplies(TeacherStudentsScene):
def construct(self):
self.teacher_says(
"Heyo! The Inverse \\\\ Pythagorean Theorem \\\\ applies!",
bubble_kwargs = {"width" : 5},
target_mode = "surprised"
)
self.change_student_modes(*3*["hooray"])
self.wait(2)
class WalkThroughOneMoreStep(TeacherStudentsScene):
def construct(self):
self.student_says("""
Wait...can you walk \\\\
through one more step?
""")
self.play(self.teacher.change, "happy")
self.wait(4)
class ThinkBackToHowAmazingThisIs(ThreeDScene):
CONFIG = {
"x_radius" : 100,
"max_shown_n" : 20,
}
def construct(self):
self.show_sum()
self.show_giant_circle()
def show_sum(self):
number_line = NumberLine(
x_min = -self.x_radius,
x_max = self.x_radius,
numbers_to_show = range(-self.max_shown_n, self.max_shown_n),
)
number_line.add_numbers()
number_line.shift(2*DOWN)
positive_dots, negative_dots = [
VGroup(*[
Dot(number_line.number_to_point(u*x))
for x in range(1, int(self.x_radius), 2)
])
for u in 1, -1
]
dot_pairs = it.starmap(VGroup, zip(positive_dots, negative_dots))
# Decimal
decimal = DecimalNumber(0, num_decimal_points = 6)
decimal.to_edge(UP)
terms = [2./(n**2) for n in range(1, 100, 2)]
partial_sums = np.cumsum(terms)
# pi^2/4 label
brace = Brace(decimal, DOWN)
pi_term = TexMobject("\pi^2 \over 4")
pi_term.next_to(brace, DOWN)
term_mobjects = VGroup()
for n in range(1, self.max_shown_n, 2):
p_term = TexMobject("\\left(\\frac{1}{%d}\\right)^2"%n)
n_term = TexMobject("\\left(\\frac{-1}{%d}\\right)^2"%n)
group = VGroup(p_term, n_term)
group.scale(0.7)
p_term.next_to(number_line.number_to_point(n), UP, LARGE_BUFF)
n_term.next_to(number_line.number_to_point(-n), UP, LARGE_BUFF)
term_mobjects.add(group)
term_mobjects.set_color_by_gradient(BLUE, YELLOW)
plusses = VGroup(*[
VGroup(*[
TexMobject("+").next_to(
number_line.number_to_point(u*n), UP, buff = 1.25,
)
for u in -1, 1
])
for n in range(0, self.max_shown_n, 2)
])
zoom_out = AmbientMovement(
self.camera.rotation_mobject,
direction = OUT, rate = 0.4
)
def update_decimal(decimal):
z = self.camera.rotation_mobject.get_center()[2]
decimal.scale_to_fit_height(0.07*z)
decimal.move_to(0.7*z*UP)
scale_decimal = ContinualUpdateFromFunc(decimal, update_decimal)
self.add(number_line, *dot_pairs)
self.add(zoom_out, scale_decimal)
tuples = zip(term_mobjects, plusses, partial_sums)
run_time = 1
for term_mobs, plus_pair, partial_sum in tuples:
self.play(
FadeIn(term_mobs),
Write(plus_pair, run_time = 1),
ChangeDecimalToValue(decimal, partial_sum),
run_time = run_time
)
self.wait(run_time)
run_time *= 0.9
self.play(ChangeDecimalToValue(decimal, np.pi**2/4, run_time = 5))
zoom_out.begin_wind_down()
self.wait()
self.remove(zoom_out, scale_decimal)
self.play(*map(FadeOut, it.chain(
term_mobjects, plusses,
number_line.numbers, [decimal]
)))
self.number_line = number_line
def show_giant_circle(self):
self.number_line.main_line.insert_n_anchor_points(10000)
everything = VGroup(*self.mobjects)
circle = everything.copy()
circle.move_to(ORIGIN)
circle.apply_function(
lambda (x, y, z) : complex_to_R3(7*np.exp(complex(0, 0.0315*x)))
)
circle.rotate(-TAU/4, about_point = ORIGIN)
circle.center()
self.play(Transform(everything, circle, run_time = 6))
class ButWait(TeacherStudentsScene):
def construct(self):
self.student_says(
"But wait!",
target_mode = "angry",
run_time = 1,
)
self.change_student_modes(
"sassy", "angry", "sassy",
added_anims = [self.teacher.change, "guilty"],
run_time = 1
)
self.student_says(
"""
You promised us \\\\
$1+{1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots$
""",
target_mode = "sassy",
)
self.wait(3)
self.teacher_says("Yes, but that's \\\\ very close.")
self.change_student_modes(*["plain"]*3)
self.wait(2)
class FinalSumManipulationScene(PiCreatureScene):
def construct(self):
LAKE_COLOR = BLUE
LAKE_OPACITY = 0.15
LAKE_STROKE_WIDTH = 5.0
LAKE_STROKE_COLOR = BLUE
TEX_SCALE = 0.8
LIGHT_COLOR2 = RED
LIGHT_COLOR3 = BLUE
unit_length = 1.5
vertical_spacing = 2.5 * DOWN
switch_on_time = 0.2
sum_vertical_spacing = 1.5
randy = self.get_primary_pi_creature()
randy.set_color(MAROON_D)
randy.color = MAROON_D
randy.scale(0.7).flip().to_edge(DOWN + LEFT)
self.wait()
ls_template = LightSource(
radius = 1,
num_levels = 10,
max_opacity_ambient = 0.5,
opacity_function = inverse_quadratic(1,0.75,1)
)
odd_range = np.arange(1,9,2)
even_range = np.arange(2,16,2)
full_range = np.arange(1,8,1)
self.number_line1 = NumberLine(
x_min = 0,
x_max = 11,
color = LAKE_STROKE_COLOR,
number_at_center = 0,
stroke_width = LAKE_STROKE_WIDTH,
stroke_color = LAKE_STROKE_COLOR,
#numbers_to_show = full_range,
number_scale_val = 0.5,
numbers_with_elongated_ticks = [],
unit_size = unit_length,
tick_frequency = 1,
line_to_number_buff = MED_SMALL_BUFF,
include_tip = True,
label_direction = UP,
)
self.number_line1.next_to(2.5 * UP + 3 * LEFT, RIGHT, buff = 0.3)
self.number_line1.add_numbers()
odd_lights = VMobject()
for i in odd_range:
pos = self.number_line1.number_to_point(i)
ls = ls_template.copy()
ls.move_source_to(pos)
odd_lights.add(ls)
self.play(
ShowCreation(self.number_line1, run_time = 5),
)
self.wait()
odd_terms = VMobject()
for i in odd_range:
if i == 1:
term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}",
fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR)
else:
term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}",
fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR)
term.next_to(self.number_line1.number_to_point(i), DOWN, buff = 1.5)
odd_terms.add(term)
for (ls, term) in zip(odd_lights.submobjects, odd_terms.submobjects):
self.play(
FadeIn(ls.lighthouse, run_time = switch_on_time),
SwitchOn(ls.ambient_light, run_time = switch_on_time),
Write(term, run_time = switch_on_time)
)
result1 = TexMobject("{\pi^2\over 8} =", fill_color = LIGHT_COLOR,
stroke_color = LIGHT_COLOR)
result1.next_to(self.number_line1, LEFT, buff = 0.5)
result1.shift(0.87 * vertical_spacing)
self.play(Write(result1))
self.number_line2 = self.number_line1.copy()
self.number_line2.numbers_to_show = full_range
self.number_line2.shift(2 * vertical_spacing)
self.number_line2.add_numbers()
full_lights = VMobject()
for i in full_range:
pos = self.number_line2.number_to_point(i)
ls = ls_template.copy()
ls.color = LIGHT_COLOR3
ls.move_source_to(pos)
full_lights.add(ls)
self.play(
ShowCreation(self.number_line2, run_time = 5),
)
self.wait()
full_lighthouses = VMobject()
full_ambient_lights = VMobject()
for ls in full_lights:
full_lighthouses.add(ls.lighthouse)
full_ambient_lights.add(ls.ambient_light)
self.play(
LaggedStart(FadeIn, full_lighthouses, lag_ratio = 0.2, run_time = 3),
)
self.play(
LaggedStart(SwitchOn, full_ambient_lights, lag_ratio = 0.2, run_time = 3)
)
# for ls in full_lights.submobjects:
# self.play(
# FadeIn(ls.lighthouse, run_time = 0.1),#5 * switch_on_time),
# SwitchOn(ls.ambient_light, run_time = 0.1)#5 * switch_on_time),
# )
even_terms = VMobject()
for i in even_range:
term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR)
term.next_to(self.number_line1.number_to_point(i), DOWN, buff = sum_vertical_spacing)
even_terms.add(term)
even_lights = VMobject()
for i in even_range:
pos = self.number_line1.number_to_point(i)
ls = ls_template.copy()
ls.color = LIGHT_COLOR2
ls.move_source_to(pos)
even_lights.add(ls)
for (ls, term) in zip(even_lights.submobjects, even_terms.submobjects):
self.play(
SwitchOn(ls.ambient_light, run_time = switch_on_time),
Write(term)
)
self.wait()
# now morph the even lights into the full lights
full_lights_copy = full_lights.copy()
even_lights_copy = even_lights.copy()
self.play(
Transform(even_lights,full_lights, run_time = 2)
)
self.wait()
for i in range(6):
self.play(
Transform(even_lights[i], even_lights_copy[i])
)
self.wait()
# draw arrows
P1 = self.number_line2.number_to_point(1)
P2 = even_terms.submobjects[0].get_center()
Q1 = interpolate(P1, P2, 0.2)
Q2 = interpolate(P1, P2, 0.8)
quarter_arrow = Arrow(Q1, Q2,
color = LIGHT_COLOR2)
quarter_label = TexMobject("\\times {1\over 4}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR2)
quarter_label.scale(0.7)
quarter_label.next_to(quarter_arrow.get_center(), RIGHT)
self.play(
ShowCreation(quarter_arrow),
Write(quarter_label),
)
self.wait()
P3 = odd_terms.submobjects[0].get_center()
R1 = interpolate(P1, P3, 0.2)
R2 = interpolate(P1, P3, 0.8)
three_quarters_arrow = Arrow(R1, R2,
color = LIGHT_COLOR)
three_quarters_label = TexMobject("\\times {3\over 4}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR)
three_quarters_label.scale(0.7)
three_quarters_label.next_to(three_quarters_arrow.get_center(), LEFT)
self.play(
ShowCreation(three_quarters_arrow),
Write(three_quarters_label)
)
self.wait()
four_thirds_arrow = Arrow(R2, R1, color = LIGHT_COLOR)
four_thirds_label = TexMobject("\\times {4\over 3}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR)
four_thirds_label.scale(0.7)
four_thirds_label.next_to(four_thirds_arrow.get_center(), LEFT)
self.play(
FadeOut(quarter_label),
FadeOut(quarter_arrow),
FadeOut(even_lights),
FadeOut(even_terms)
)
self.wait()
self.play(
ReplacementTransform(three_quarters_arrow, four_thirds_arrow),
ReplacementTransform(three_quarters_label, four_thirds_label)
)
self.wait()
full_terms = VMobject()
for i in range(1,8): #full_range:
if i == 1:
term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3)
elif i == 7:
term = TexMobject("+\,\,\,\dots", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3)
else:
term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3)
term.move_to(self.number_line2.number_to_point(i))
full_terms.add(term)
#return
self.play(
FadeOut(self.number_line1),
FadeOut(odd_lights),
FadeOut(self.number_line2),
FadeOut(full_lights),
FadeIn(full_terms)
)
self.wait()
v = (sum_vertical_spacing + 0.5) * UP
self.play(
odd_terms.shift, v,
result1.shift, v,
four_thirds_arrow.shift, v,
four_thirds_label.shift, v,
odd_terms.shift, v,
full_terms.shift, v
)
arrow_copy = four_thirds_arrow.copy()
label_copy = four_thirds_label.copy()
arrow_copy.shift(2.5 * LEFT)
label_copy.shift(2.5 * LEFT)
self.play(
FadeIn(arrow_copy),
FadeIn(label_copy)
)
self.wait()
final_result = TexMobject("{\pi^2 \over 6}=", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3)
final_result.next_to(arrow_copy, DOWN)
self.play(
Write(final_result),
randy.change_mode,"hooray"
)
self.wait()
equation = VMobject()
equation.add(final_result)
equation.add(full_terms)
self.play(
FadeOut(result1),
FadeOut(odd_terms),
FadeOut(arrow_copy),
FadeOut(label_copy),
FadeOut(four_thirds_arrow),
FadeOut(four_thirds_label),
full_terms.shift,LEFT,
)
self.wait()
self.play(equation.shift, -equation.get_center()[1] * UP + UP + 1.5 * LEFT)
result_box = Rectangle(width = 1.1 * equation.get_width(),
height = 2 * equation.get_height(), color = LIGHT_COLOR3)
result_box.move_to(equation)
self.play(
ShowCreation(result_box)
)
self.wait()
class LabeledArc(Arc):
CONFIG = {
"length" : 1
}
def __init__(self, angle, **kwargs):
BUFFER = 0.8
Arc.__init__(self,angle,**kwargs)
label = DecimalNumber(self.length, num_decimal_points = 0)
r = BUFFER * self.radius
theta = self.start_angle + self.angle/2
label_pos = r * np.array([np.cos(theta), np.sin(theta), 0])
label.move_to(label_pos)
self.add(label)
class ArcHighlightOverlaySceneCircumferenceEight(Scene):
CONFIG = {
"n" : 2,
}
def construct(self):
BASELINE_YPOS = -2.5
OBSERVER_POINT = [0,BASELINE_YPOS,0]
LAKE0_RADIUS = 2.5
INDICATOR_RADIUS = 0.6
TICK_SIZE = 0.5
LIGHTHOUSE_HEIGHT = 0.2
LAKE_COLOR = BLUE
LAKE_OPACITY = 0.15
LAKE_STROKE_WIDTH = 5.0
LAKE_STROKE_COLOR = BLUE
TEX_SCALE = 0.8
DOT_COLOR = BLUE
FLASH_TIME = 1
def flash_arcs(n):
angle = TAU/2**n
arcs = []
arcs.append(LabeledArc(angle/2, start_angle = -TAU/4, radius = LAKE0_RADIUS, length = 1))
for i in range(1,2**n):
arcs.append(LabeledArc(angle, start_angle = -TAU/4 + (i-0.5)*angle, radius = LAKE0_RADIUS, length = 2))
arcs.append(LabeledArc(angle/2, start_angle = -TAU/4 - angle/2, radius = LAKE0_RADIUS, length = 1))
self.play(
FadeIn(arcs[0], run_time = FLASH_TIME)
)
for i in range(1,2**n + 1):
self.play(
FadeOut(arcs[i-1], run_time = FLASH_TIME),
FadeIn(arcs[i], run_time = FLASH_TIME)
)
self.play(
FadeOut(arcs[2**n], run_time = FLASH_TIME),
)
flash_arcs(self.n)
class ArcHighlightOverlaySceneCircumferenceSixteen(ArcHighlightOverlaySceneCircumferenceEight):
CONFIG = {
"n" : 3,
}
class InfiniteCircleScene(PiCreatureScene):
def construct(self):
morty = self.get_primary_pi_creature()
morty.set_color(MAROON_D).flip()
morty.color = MAROON_D
morty.scale(0.5).move_to(ORIGIN)
arrow = Arrow(ORIGIN, 2.4 * RIGHT)
dot = Dot(color = BLUE).next_to(arrow)
ellipsis = TexMobject("\dots")
infsum = VGroup()
infsum.add(ellipsis.copy())
for i in range(3):
infsum.add(arrow.copy().next_to(infsum.submobjects[-1]))
infsum.add(dot.copy().next_to(infsum.submobjects[-1]))
infsum.add(arrow.copy().next_to(infsum.submobjects[-1]))
infsum.add(ellipsis.copy().next_to(infsum.submobjects[-1]))
infsum.next_to(morty,DOWN, buff = 1)
self.wait()
self.play(
LaggedStart(FadeIn,infsum,lag_ratio = 0.2)
)
self.wait()
A = infsum.submobjects[-1].get_center() + 0.5 * RIGHT
B = A + RIGHT + 1.3 * UP + 0.025 * LEFT
right_arc = DashedLine(TAU/4*UP, ORIGIN, stroke_color = YELLOW,
stroke_width = 8).apply_complex_function(np.exp)
right_arc.rotate(-TAU/4).next_to(infsum, RIGHT).shift(0.5 * UP)
right_tip_line = Arrow(B - UP, B, color = WHITE)
right_tip_line.add_tip()
right_tip = right_tip_line.get_tip()
right_tip.set_fill(color = YELLOW)
right_arc.add(right_tip)
C = B + 3.2 * UP
right_line = DashedLine(B + 0.2 * DOWN,C + 0.2 * UP, stroke_color = YELLOW,
stroke_width = 8)
ru_arc = right_arc.copy().rotate(angle = TAU/4)
ru_arc.remove(ru_arc.submobjects[-1])
ru_arc.to_edge(UP+RIGHT, buff = 0.15)
D = np.array([5.85, 3.85,0])
E = np.array([-D[0],D[1],0])
up_line = DashedLine(D, E, stroke_color = YELLOW,
stroke_width = 8)
lu_arc = ru_arc.copy().flip().to_edge(LEFT + UP, buff = 0.15)
left_line = right_line.copy().flip(axis = RIGHT).to_edge(LEFT, buff = 0.15)
left_arc = right_arc.copy().rotate(-TAU/4)
left_arc.next_to(infsum, LEFT).shift(0.5 * UP + 0.1 * LEFT)
right_arc.shift(0.2 * RIGHT)
right_line.shift(0.2 * RIGHT)
self.play(FadeIn(right_arc))
self.play(ShowCreation(right_line))
self.play(FadeIn(ru_arc))
self.play(ShowCreation(up_line))
self.play(FadeIn(lu_arc))
self.play(ShowCreation(left_line))
self.play(FadeIn(left_arc))
self.wait()
class Credits(Scene):
def construct(self):
credits = VGroup(*[
VGroup(*map(TextMobject, pair))
for pair in [
("Primary writer and animator:", "Ben Hambrecht"),
("Editing, advising, narrating:", "Grant Sanderson"),
("Based on a paper originally by:", "Johan Wästlund"),
]
])
for credit, color in zip(credits, [MAROON_D, BLUE_D, WHITE]):
credit[1].set_color(color)
credit.arrange_submobjects(DOWN, buff = SMALL_BUFF)
credits.arrange_submobjects(DOWN, buff = LARGE_BUFF)
credits.center()
patreon_logo = PatreonLogo()
patreon_logo.to_edge(UP)
for credit in credits:
self.play(LaggedStart(FadeIn, credit[0]))
self.play(FadeIn(credit[1]))
self.wait()
self.play(
credits.next_to, patreon_logo.get_bottom(), DOWN, MED_LARGE_BUFF,
DrawBorderThenFill(patreon_logo)
)
self.wait()
class Promotion(PiCreatureScene):
CONFIG = {
"seconds_to_blink" : 5,
}
def construct(self):
url = TextMobject("https://brilliant.org/3b1b/")
url.to_corner(UP+LEFT)
rect = Rectangle(height = 9, width = 16)
rect.scale_to_fit_height(5.5)
rect.next_to(url, DOWN)
rect.to_edge(LEFT)
self.play(
Write(url),
self.pi_creature.change, "raise_right_hand"
)
self.play(ShowCreation(rect))
self.wait(2)
self.change_mode("thinking")
self.wait()
self.look_at(url)
self.wait(10)
self.change_mode("happy")
self.wait(10)
self.change_mode("raise_right_hand")
self.wait(10)
self.remove(rect)
self.play(
url.next_to, self.pi_creature, UP+LEFT
)
url_rect = SurroundingRectangle(url)
self.play(ShowCreation(url_rect))
self.play(FadeOut(url_rect))
self.wait(3)
class BaselPatreonThanks(PatreonEndScreen):
CONFIG = {
"specific_patrons" : [
"CrypticSwarm ",
"Ali Yahya",
"Juan Benet",
"Markus Persson",
"Damion Kistler",
"Burt Humburg",
"Yu Jun",
"Dave Nicponski",
"Kaustuv DeBiswas",
"Joseph John Cox",
"Luc Ritchie",
"Achille Brighton",
"Rish Kundalia",
"Yana Chernobilsky",
"Shìmín Ku$\\overline{\\text{a}}$ng",
"Mathew Bramson",
"Jerry Ling",
"Mustafa Mahdi",
"Meshal Alshammari",
"Mayank M. Mehrotra",
"Lukas Biewald",
"Robert Teed",
"Samantha D. Suplee",
"Mark Govea",
"John Haley",
"Julian Pulgarin",
"Jeff Linse",
"Cooper Jones",
"Desmos ",
"Boris Veselinovich",
"Ryan Dahl",
"Ripta Pasay",
"Eric Lavault",
"Randall Hunt",
"Andrew Busey",
"Mads Elvheim",
"Tianyu Ge",
"Awoo",
"Dr. David G. Stork",
"Linh Tran",
"Jason Hise",
"Bernd Sing",
"James H. Park",
"Ankalagon ",
"Devin Scott",
"Mathias Jansson",
"David Clark",
"Ted Suzman",
"Eric Chow",
"Michael Gardner",
"David Kedmey",
"Jonathan Eppele",
"Clark Gaebel",
"Jordan Scales",
"Ryan Atallah",
"supershabam ",
"1stViewMaths ",
"Jacob Magnuson",
"Chloe Zhou",
"Ross Garber",
"Thomas Tarler",
"Isak Hietala",
"Egor Gumenuk",
"Waleed Hamied",
"Oliver Steele",
"Yaw Etse",
"David B",
"Delton Ding",
"James Thornton",
"Felix Tripier",
"Arthur Zey",
"George Chiesa",
"Norton Wang",
"Kevin Le",
"Alexander Feldman",
"David MacCumber",
"Jacob Kohl",
"Sergei ",
"Frank Secilia",
"Patrick Mézard",
"George John",
"Akash Kumar",
"Britt Selvitelle",
"Jonathan Wilson",
"Ignacio Freiberg",
"Zhilong Yang",
"Karl Niu",
"Dan Esposito",
"Michael Kunze",
"Giovanni Filippi",
"Eric Younge",
"Prasant Jagannath",
"Andrejs Olins",
"Cody Brocious",
],
}
def construct(self):
next_video = TextMobject("$\\uparrow$ Next video $\\uparrow$")
next_video.to_edge(RIGHT, buff = 1.5)
next_video.shift(MED_SMALL_BUFF*UP)
next_video.set_color(YELLOW)
self.add_foreground_mobject(next_video)
PatreonEndScreen.construct(self)
class Thumbnail(Scene):
CONFIG = {
"light_source_config" : {
"num_levels" : 250,
"radius" : 10.0,
"max_opacity_ambient" : 1.0,
"opacity_function" : inverse_quadratic(1,0.25,1)
}
}
def construct(self):
equation = TexMobject(
"1", "+", "{1\over 4}", "+",
"{1\over 9}","+", "{1\over 16}","+",
"{1\over 25}", "+", "\cdots"
)
equation.scale(1.8)
equation.move_to(2*UP)
equation.set_stroke(RED, 1)
answer = TexMobject("= \\frac{\\pi^2}{6}", color = LIGHT_COLOR)
answer.scale(3)
answer.set_stroke(RED, 1)
# answer.next_to(equation, DOWN, buff = 1)
answer.move_to(1.25*DOWN)
#equation.move_to(2 * UP)
#answer = TexMobject("={\pi^2\over 6}", color = LIGHT_COLOR).scale(3)
#answer.next_to(equation, DOWN, buff = 1)
lake_radius = 6
lake_center = ORIGIN
lake = Circle(
fill_color = BLUE,
fill_opacity = 0.15,
radius = lake_radius,
stroke_color = BLUE_D,
stroke_width = 3,
)
lake.move_to(lake_center)
for i in range(16):
theta = -TAU/4 + (i + 0.5) * TAU/16
pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0])
ls = LightSource(**self.light_source_config)
ls.move_source_to(pos)
lake.add(ls.ambient_light)
lake.add(ls.lighthouse)
self.add(lake)
self.add(equation, answer)
self.wait()