mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
4625 lines
146 KiB
Python
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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|