mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
4514 lines
132 KiB
Python
4514 lines
132 KiB
Python
#!/usr/bin/env python
|
|
|
|
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_CONES = 7 # in first lighthouse scene
|
|
NUM_VISIBLE_CONES = 5 # ibidem
|
|
ARC_TIP_LENGTH = 0.2
|
|
|
|
NUM_LEVELS = 15
|
|
AMBIENT_FULL = 0.8
|
|
AMBIENT_DIMMED = 0.5
|
|
AMBIENT_SCALE = 2.0
|
|
AMBIENT_RADIUS = 20.0
|
|
SPOTLIGHT_FULL = 0.8
|
|
SPOTLIGHT_DIMMED = 0.2
|
|
SPOTLIGHT_SCALE = 1.0
|
|
SPOTLIGHT_RADIUS = 20.0
|
|
|
|
LIGHT_COLOR = YELLOW
|
|
DEGREES = TAU/360
|
|
|
|
|
|
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
|
|
|
|
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(VMobject):
|
|
CONFIG = {
|
|
"radius": 0.5,
|
|
"intensity": 0,
|
|
"opacity_for_unit_intensity": 1,
|
|
"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.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.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)
|
|
return self
|
|
|
|
def get_measurement_point(self):
|
|
if self.measurement_point != 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.change_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 IntroScene(PiCreatureScene):
|
|
|
|
CONFIG = {
|
|
"rect_height" : 0.2,
|
|
"duration" : 0.5,
|
|
"eq_spacing" : 6 * MED_LARGE_BUFF
|
|
}
|
|
|
|
def construct(self):
|
|
|
|
randy = self.get_primary_pi_creature()
|
|
randy.scale(0.7).to_corner(DOWN+RIGHT)
|
|
|
|
self.build_up_euler_sum()
|
|
self.build_up_sum_on_number_line()
|
|
self.show_pi_answer()
|
|
self.other_pi_formulas()
|
|
self.refocus_on_euler_sum()
|
|
|
|
|
|
|
|
|
|
|
|
def build_up_euler_sum(self):
|
|
|
|
self.euler_sum = TexMobject(
|
|
"1", "+",
|
|
"{1 \\over 4}", "+",
|
|
"{1 \\over 9}", "+",
|
|
"{1 \\over 16}", "+",
|
|
"{1 \\over 25}", "+",
|
|
"\\cdots", "=",
|
|
arg_separator = " \\, "
|
|
)
|
|
|
|
self.euler_sum.to_edge(UP)
|
|
self.euler_sum.shift(2*LEFT)
|
|
|
|
terms = [1./n**2 for n in range(1,6)]
|
|
partial_results_values = np.cumsum(terms)
|
|
|
|
self.play(
|
|
FadeIn(self.euler_sum[0], run_time = self.duration)
|
|
)
|
|
|
|
equals_sign = self.euler_sum.get_part_by_tex("=")
|
|
|
|
self.partial_sum_decimal = DecimalNumber(partial_results_values[1],
|
|
num_decimal_points = 2)
|
|
self.partial_sum_decimal.next_to(equals_sign, RIGHT)
|
|
|
|
|
|
|
|
for i in range(4):
|
|
|
|
FadeIn(self.partial_sum_decimal, run_time = self.duration)
|
|
|
|
if i == 0:
|
|
|
|
self.play(
|
|
FadeIn(self.euler_sum[1], run_time = self.duration),
|
|
FadeIn(self.euler_sum[2], run_time = self.duration),
|
|
FadeIn(equals_sign, run_time = self.duration),
|
|
FadeIn(self.partial_sum_decimal, run_time = self.duration)
|
|
)
|
|
|
|
else:
|
|
self.play(
|
|
FadeIn(self.euler_sum[2*i+1], run_time = self.duration),
|
|
FadeIn(self.euler_sum[2*i+2], run_time = self.duration),
|
|
ChangeDecimalToValue(
|
|
self.partial_sum_decimal,
|
|
partial_results_values[i+1],
|
|
run_time = self.duration,
|
|
num_decimal_points = 6,
|
|
show_ellipsis = True,
|
|
position_update_func = lambda m: m.next_to(equals_sign, RIGHT)
|
|
)
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.q_marks = TextMobject("???").set_color(LIGHT_COLOR)
|
|
self.q_marks.move_to(self.partial_sum_decimal)
|
|
|
|
self.play(
|
|
FadeIn(self.euler_sum[-3], run_time = self.duration), # +
|
|
FadeIn(self.euler_sum[-2], run_time = self.duration), # ...
|
|
ReplacementTransform(self.partial_sum_decimal, self.q_marks)
|
|
)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
def build_up_sum_on_number_line(self):
|
|
|
|
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
|
|
).shift(LEFT)
|
|
|
|
self.number_line_labels = self.number_line.get_number_mobjects()
|
|
self.play(
|
|
FadeIn(self.number_line),
|
|
FadeIn(self.number_line_labels)
|
|
)
|
|
self.wait()
|
|
|
|
# create slabs for series terms
|
|
|
|
max_n1 = 10
|
|
max_n2 = 100
|
|
|
|
terms = [0] + [1./(n**2) for n in range(1, max_n2 + 1)]
|
|
series_terms = np.cumsum(terms)
|
|
lines = VGroup()
|
|
self.rects = VGroup()
|
|
slab_colors = [YELLOW, BLUE] * (max_n2 / 2)
|
|
|
|
for t1, t2, color in zip(series_terms, series_terms[1:], slab_colors):
|
|
line = Line(*map(self.number_line.number_to_point, [t1, t2]))
|
|
rect = Rectangle()
|
|
rect.stroke_width = 0
|
|
rect.fill_opacity = 1
|
|
rect.set_color(color)
|
|
rect.stretch_to_fit_height(
|
|
self.rect_height,
|
|
)
|
|
rect.stretch_to_fit_width(0.5 * line.get_width())
|
|
rect.move_to(line)
|
|
|
|
self.rects.add(rect)
|
|
lines.add(line)
|
|
|
|
#self.rects.set_colors_by_radial_gradient(ORIGIN, 5, YELLOW, BLUE)
|
|
|
|
self.little_euler_terms = VGroup()
|
|
for i in range(1,7):
|
|
if i == 1:
|
|
term = TexMobject("1", fill_color = slab_colors[i-1])
|
|
else:
|
|
term = TexMobject("{1\over " + str(i**2) + "}", fill_color = slab_colors[i-1])
|
|
term.scale(0.4)
|
|
self.little_euler_terms.add(term)
|
|
|
|
|
|
for i in range(5):
|
|
self.play(
|
|
GrowFromPoint(self.rects[i], self.euler_sum[2*i].get_center(),
|
|
run_time = 1)
|
|
)
|
|
term = self.little_euler_terms.submobjects[i]
|
|
term.next_to(self.rects[i], UP)
|
|
self.play(FadeIn(term))
|
|
|
|
self.ellipsis = TexMobject("\cdots")
|
|
self.ellipsis.scale(0.4)
|
|
for i in range(5, max_n1):
|
|
|
|
if i == 5:
|
|
self.ellipsis.next_to(self.rects[i+3], UP)
|
|
self.play(
|
|
FadeIn(self.ellipsis),
|
|
GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(),
|
|
run_time = 0.5)
|
|
)
|
|
else:
|
|
self.play(
|
|
GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(),
|
|
run_time = 0.5)
|
|
)
|
|
for i in range(max_n1, max_n2):
|
|
self.play(
|
|
GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(),
|
|
run_time = 0.01)
|
|
)
|
|
|
|
self.wait()
|
|
|
|
PI = TAU/2
|
|
P = self.q_marks.get_center() + 0.5 * DOWN + 0.5 * LEFT
|
|
Q = self.rects[-1].get_center() + 0.2 * UP
|
|
self.arrow = CurvedArrow(P, Q,
|
|
angle = TAU/12,
|
|
color = YELLOW
|
|
)
|
|
|
|
self.play(FadeIn(self.arrow))
|
|
|
|
self.wait()
|
|
|
|
|
|
def show_pi_answer(self):
|
|
|
|
self.pi_answer = TexMobject("{\\pi^2 \\over 6}").set_color(YELLOW)
|
|
self.pi_answer.move_to(self.partial_sum_decimal)
|
|
self.pi_answer.next_to(self.euler_sum[-1], RIGHT, buff = 1,
|
|
submobject_to_align = self.pi_answer[-2])
|
|
self.play(ReplacementTransform(self.q_marks, self.pi_answer))
|
|
|
|
self.wait()
|
|
|
|
|
|
def other_pi_formulas(self):
|
|
|
|
self.play(
|
|
FadeOut(self.rects),
|
|
FadeOut(self.number_line_labels),
|
|
FadeOut(self.number_line),
|
|
FadeOut(self.little_euler_terms),
|
|
FadeOut(self.ellipsis),
|
|
FadeOut(self.arrow)
|
|
)
|
|
|
|
self.leibniz_sum = TexMobject(
|
|
"1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots",
|
|
"=", "\quad\,\,{\\pi \\over 4}", arg_separator = " \\, ")
|
|
|
|
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",
|
|
"=", "\quad\,\, {\\pi \\over 2}", arg_separator = " \\, ")
|
|
|
|
self.leibniz_sum.next_to(self.euler_sum.get_part_by_tex("="), DOWN,
|
|
buff = 2,
|
|
submobject_to_align = self.leibniz_sum.get_part_by_tex("=")
|
|
)
|
|
|
|
self.wallis_product.next_to(self.leibniz_sum.get_part_by_tex("="), DOWN,
|
|
buff = 2,
|
|
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(
|
|
WiggleOutThenIn(pi_squared,
|
|
scale_value = 4,
|
|
angle = 0.003 * TAU,
|
|
run_time = 2
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# Morty thinks of a circle
|
|
|
|
q_circle = Circle(
|
|
stroke_color = YELLOW,
|
|
fill_color = YELLOW,
|
|
fill_opacity = 0.25,
|
|
radius = 0.5,
|
|
stroke_width = 3.0
|
|
)
|
|
q_mark = TexMobject("?")
|
|
q_mark.next_to(q_circle)
|
|
|
|
thought = Group(q_circle, q_mark)
|
|
q_mark.scale_to_fit_height(0.6 * q_circle.get_height())
|
|
|
|
self.look_at(pi_squared)
|
|
self.pi_creature_thinks(thought,target_mode = "confused",
|
|
bubble_kwargs = { "height" : 2.5, "width" : 5 })
|
|
self.look_at(pi_squared)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FirstLighthouseScene(PiCreatureScene):
|
|
|
|
def construct(self):
|
|
self.remove(self.get_primary_pi_creature())
|
|
self.show_lighthouses_on_number_line()
|
|
|
|
|
|
|
|
def show_lighthouses_on_number_line(self):
|
|
|
|
self.number_line = NumberLine(
|
|
x_min = 0,
|
|
color = WHITE,
|
|
number_at_center = 1.6,
|
|
stroke_width = 1,
|
|
numbers_with_elongated_ticks = range(1,5),
|
|
numbers_to_show = range(1,5),
|
|
unit_size = 2,
|
|
tick_frequency = 0.2,
|
|
line_to_number_buff = LARGE_BUFF,
|
|
label_direction = UP,
|
|
)
|
|
|
|
self.number_line.label_direction = DOWN
|
|
|
|
self.number_line_labels = self.number_line.get_number_mobjects()
|
|
self.add(self.number_line,self.number_line_labels)
|
|
self.wait()
|
|
|
|
origin_point = self.number_line.number_to_point(0)
|
|
|
|
self.default_pi_creature_class = Randolph
|
|
randy = self.get_primary_pi_creature()
|
|
|
|
randy.scale(0.5)
|
|
randy.flip()
|
|
right_pupil = randy.pupils[1]
|
|
randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil)
|
|
|
|
|
|
|
|
light_indicator = LightIndicator(radius = INDICATOR_RADIUS,
|
|
opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY,
|
|
color = LIGHT_COLOR)
|
|
light_indicator.reading.scale(0.8)
|
|
|
|
bubble = ThoughtBubble(direction = RIGHT,
|
|
width = 2.5, height = 3.5)
|
|
bubble.next_to(randy,LEFT+UP)
|
|
bubble.add_content(light_indicator)
|
|
self.wait()
|
|
self.play(
|
|
randy.change, "wave_2",
|
|
ShowCreation(bubble),
|
|
FadeIn(light_indicator)
|
|
)
|
|
|
|
light_sources = []
|
|
|
|
|
|
euler_sum_above = TexMobject("1", "+", "{1\over 4}",
|
|
"+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "{1\over 36}")
|
|
|
|
for (i,term) in zip(range(len(euler_sum_above)),euler_sum_above):
|
|
#horizontal alignment with tick marks
|
|
term.next_to(self.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])
|
|
|
|
|
|
|
|
for i in range(1,NUM_CONES+1):
|
|
light_source = LightSource(
|
|
opacity_function = inverse_quadratic(1,AMBIENT_SCALE,1),
|
|
num_levels = NUM_LEVELS,
|
|
radius = AMBIENT_RADIUS,
|
|
)
|
|
point = self.number_line.number_to_point(i)
|
|
light_source.move_source_to(point)
|
|
light_sources.append(light_source)
|
|
|
|
self.wait()
|
|
for ls in light_sources:
|
|
self.add_foreground_mobject(ls.lighthouse)
|
|
|
|
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
|
|
|
|
self.remove_foreground_mobjects(light_indicator)
|
|
|
|
|
|
# slowly switch on visible light cones and increment indicator
|
|
for (i,light_source) in zip(range(NUM_VISIBLE_CONES),light_sources[:NUM_VISIBLE_CONES]):
|
|
indicator_start_time = 1.0 * (i+1) * SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size
|
|
indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME
|
|
indicator_rate_func = squish_rate_func(
|
|
smooth,indicator_start_time,indicator_stop_time)
|
|
self.play(
|
|
SwitchOn(light_source.ambient_light),
|
|
FadeIn(euler_sum_above[2*i], run_time = SWITCH_ON_RUN_TIME,
|
|
rate_func = indicator_rate_func),
|
|
FadeIn(euler_sum_above[2*i - 1], run_time = SWITCH_ON_RUN_TIME,
|
|
rate_func = indicator_rate_func),
|
|
# this last line *technically* fades in the last term, but it is off-screen
|
|
ChangeDecimalToValue(light_indicator.reading,intensities[i],
|
|
rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME),
|
|
ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i],
|
|
rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME)
|
|
)
|
|
|
|
if i == 0:
|
|
self.wait()
|
|
# move a copy out of the thought bubble for comparison
|
|
light_indicator_copy = light_indicator.copy()
|
|
old_y = light_indicator_copy.get_center()[1]
|
|
new_y = self.number_line.get_center()[1]
|
|
self.play(
|
|
light_indicator_copy.shift,[0, new_y - old_y,0]
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.wait()
|
|
|
|
# quickly switch on off-screen light cones and increment indicator
|
|
for (i,light_source) in zip(range(NUM_VISIBLE_CONES,NUM_CONES),light_sources[NUM_VISIBLE_CONES:NUM_CONES]):
|
|
indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size
|
|
indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME
|
|
indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9)
|
|
smooth,indicator_start_time,indicator_stop_time)
|
|
self.play(
|
|
SwitchOn(light_source.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME),
|
|
ChangeDecimalToValue(light_indicator.reading,intensities[i-1],
|
|
rate_func = indicator_rate_func, run_time = FAST_SWITCH_ON_RUN_TIME),
|
|
ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i-1])
|
|
)
|
|
|
|
|
|
# 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(randy, 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])
|
|
|
|
self.play(
|
|
FadeOut(light_indicator.reading),
|
|
FadeIn(limit_reading),
|
|
FadeIn(equals_sign),
|
|
)
|
|
|
|
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SingleLighthouseScene(PiCreatureScene):
|
|
|
|
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):
|
|
|
|
self.remove(self.get_primary_pi_creature())
|
|
|
|
SCREEN_SIZE = 3.0
|
|
DISTANCE_FROM_LIGHTHOUSE = 10.0
|
|
source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0]
|
|
observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0]
|
|
|
|
# Light source
|
|
|
|
self.light_source = LightSource(
|
|
opacity_function = inverse_quadratic(1,SPOTLIGHT_SCALE,1),
|
|
num_levels = NUM_LEVELS,
|
|
radius = 10,
|
|
max_opacity_ambient = AMBIENT_FULL,
|
|
max_opacity_spotlight = SPOTLIGHT_FULL,
|
|
|
|
)
|
|
|
|
self.light_source.move_source_to(source_point)
|
|
|
|
|
|
# Pi Creature
|
|
|
|
morty = self.get_primary_pi_creature()
|
|
morty.scale(0.5)
|
|
morty.move_to(observer_point)
|
|
morty.shift(2*OUT)
|
|
self.add_foreground_mobject(morty)
|
|
|
|
self.add(self.light_source.lighthouse)
|
|
|
|
self.play(
|
|
SwitchOn(self.light_source.ambient_light)
|
|
)
|
|
|
|
# Screen
|
|
|
|
self.screen = Rectangle(
|
|
width = 0.06,
|
|
height = 2,
|
|
mark_paths_closed = True,
|
|
fill_color = WHITE,
|
|
fill_opacity = 1.0,
|
|
stroke_width = 0.0
|
|
)
|
|
|
|
self.screen.rotate(-TAU/6)
|
|
self.screen.next_to(morty,LEFT)
|
|
|
|
self.light_source.set_screen(self.screen)
|
|
|
|
# Animations
|
|
|
|
self.play(FadeIn(self.screen))
|
|
|
|
#self.light_source.set_max_opacity_spotlight(0.001)
|
|
#self.play(SwitchOn(self.light_source.spotlight))
|
|
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
|
|
# just calling .dim_ambient via ApplyMethod does not work, why?
|
|
dimmed_ambient_light = self.light_source.ambient_light.deepcopy()
|
|
dimmed_ambient_light.dimming(AMBIENT_DIMMED)
|
|
self.light_source.update_shadow()
|
|
|
|
self.play(
|
|
FadeIn(self.light_source.shadow),
|
|
)
|
|
self.add_foreground_mobject(self.light_source.shadow)
|
|
self.add_foreground_mobject(morty)
|
|
|
|
self.play(
|
|
self.light_source.dim_ambient,
|
|
#Transform(self.light_source.ambient_light,dimmed_ambient_light),
|
|
#self.light_source.set_max_opacity_spotlight,1.0,
|
|
)
|
|
self.play(
|
|
FadeIn(self.light_source.spotlight)
|
|
)
|
|
|
|
self.screen_tracker = ScreenTracker(self.light_source)
|
|
self.add(self.screen_tracker)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
|
|
def setup_angle(self):
|
|
|
|
self.wait()
|
|
|
|
|
|
pointing_screen_at_source = Rotate(self.screen,TAU/6)
|
|
self.play(pointing_screen_at_source)
|
|
|
|
# angle msmt (arc)
|
|
|
|
arc_angle = self.light_source.spotlight.opening_angle()
|
|
# draw arc arrows to show the opening angle
|
|
self.angle_arc = Arc(radius = 5, 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",
|
|
fill_opacity = 1.0,
|
|
fill_color = WHITE)
|
|
self.angle_indicator.next_to(self.angle_arc,RIGHT)
|
|
|
|
angle_update_func = lambda x: self.light_source.spotlight.opening_angle() / DEGREES
|
|
ca1 = ContinualChangingDecimal(self.angle_indicator,angle_update_func)
|
|
self.add(ca1)
|
|
|
|
ca2 = AngleUpdater(self.angle_arc, self.light_source.spotlight)
|
|
self.add(ca2)
|
|
|
|
self.play(
|
|
ShowCreation(self.angle_arc),
|
|
ShowCreation(self.angle_indicator)
|
|
)
|
|
|
|
self.wait()
|
|
|
|
def rotate_screen(self):
|
|
|
|
|
|
|
|
self.play(Rotate(self.light_source.spotlight.screen, TAU/8))
|
|
self.play(Rotate(self.light_source.spotlight.screen, -TAU/4))
|
|
|
|
self.play(Rotate(self.light_source.spotlight.screen, TAU/8))
|
|
|
|
self.wait()
|
|
|
|
self.play(Rotate(self.light_source.spotlight.screen, -TAU/4))
|
|
|
|
self.wait()
|
|
|
|
self.play(Rotate(self.light_source.spotlight.screen, TAU/4))
|
|
|
|
### The following is supposed to morph the scene into the Earth scene,
|
|
### but it doesn't work
|
|
|
|
|
|
class MorphIntoSunScene(PiCreatureScene):
|
|
def construct(self):
|
|
self.setup_elements()
|
|
self.morph_lighthouse_into_sun()
|
|
|
|
def setup_elements(self):
|
|
self.remove(self.get_primary_pi_creature())
|
|
|
|
SCREEN_SIZE = 3.0
|
|
DISTANCE_FROM_LIGHTHOUSE = 10.0
|
|
source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0]
|
|
observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0]
|
|
|
|
# Light source
|
|
|
|
self.light_source = LightSource(
|
|
opacity_function = inverse_quadratic(1,SPOTLIGHT_SCALE,1),
|
|
num_levels = NUM_LEVELS,
|
|
radius = 10,
|
|
max_opacity_ambient = AMBIENT_FULL,
|
|
max_opacity_spotlight = SPOTLIGHT_FULL,
|
|
|
|
)
|
|
|
|
self.light_source.move_source_to(source_point)
|
|
|
|
|
|
# Pi Creature
|
|
|
|
morty = self.get_primary_pi_creature()
|
|
morty.scale(0.5)
|
|
morty.move_to(observer_point)
|
|
morty.shift(2*OUT)
|
|
self.add_foreground_mobject(morty)
|
|
|
|
self.add(self.light_source.lighthouse,self.light_source.ambient_light)
|
|
|
|
|
|
# Screen
|
|
|
|
self.screen = Rectangle(
|
|
width = 0.06,
|
|
height = 2,
|
|
mark_paths_closed = True,
|
|
fill_color = WHITE,
|
|
fill_opacity = 1.0,
|
|
stroke_width = 0.0
|
|
)
|
|
|
|
self.screen.next_to(morty,LEFT)
|
|
|
|
self.light_source.set_screen(self.screen)
|
|
self.add(self.screen,self.light_source.shadow)
|
|
|
|
self.add_foreground_mobject(self.light_source.shadow)
|
|
self.add_foreground_mobject(morty)
|
|
self.light_source.dim_ambient
|
|
self.add(self.light_source.spotlight)
|
|
self.screen_tracker = ScreenTracker(self.light_source)
|
|
self.add(self.screen_tracker)
|
|
|
|
self.wait()
|
|
|
|
|
|
def morph_lighthouse_into_sun(self):
|
|
|
|
sun_position = np.array([-100,0,0])
|
|
|
|
|
|
|
|
|
|
# Why does none of this change the opacity function???
|
|
|
|
self.sun = self.light_source.copy()
|
|
|
|
self.sun.change_spotlight_opacity_function(lambda r: 0.1)
|
|
# self.sun.spotlight.opacity_function = lambda r: 0.1
|
|
# for submob in self.sun.spotlight.submobjects:
|
|
# submob.set_fill(opacity = 0.1)
|
|
|
|
#self.sun.move_source_to(sun_position)
|
|
#self.sun.set_radius(120)
|
|
|
|
self.sun.spotlight.generate_points()
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
Transform(self.light_source,self.sun)
|
|
)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class EarthScene(Scene):
|
|
|
|
def construct(self):
|
|
|
|
SCREEN_THICKNESS = 10
|
|
|
|
self.screen_height = 2.0
|
|
self.brightness_rect_height = 1.0
|
|
|
|
# screen
|
|
self.screen = VMobject(stroke_color = WHITE, stroke_width = SCREEN_THICKNESS)
|
|
self.screen.set_points_as_corners([
|
|
[0,-self.screen_height/2,0],
|
|
[0,self.screen_height/2,0]
|
|
])
|
|
|
|
# Earth
|
|
|
|
earth_center_x = 2
|
|
earth_center = [earth_center_x,0,0]
|
|
earth_radius = 3
|
|
earth = Circle(radius = earth_radius)
|
|
earth.add(self.screen)
|
|
earth.move_to(earth_center)
|
|
#self.remove(self.screen_tracker)
|
|
|
|
theta0 = 70 * DEGREES
|
|
dtheta = 10 * DEGREES
|
|
theta1 = theta0 + dtheta
|
|
theta = (theta0 + theta1)/2
|
|
|
|
self.add_foreground_mobject(self.screen)
|
|
|
|
# background Earth
|
|
background_earth = SVGMobject(
|
|
file_name = "earth",
|
|
width = 2 * earth_radius,
|
|
fill_color = BLUE,
|
|
)
|
|
background_earth.move_to(earth_center)
|
|
# Morty
|
|
|
|
morty = Mortimer().scale(0.5).next_to(self.screen, RIGHT, buff = 1.5)
|
|
self.add_foreground_mobject(morty)
|
|
|
|
|
|
# Light source (far-away Sun)
|
|
|
|
sun_position = [-100,0,0]
|
|
|
|
self.sun = LightSource(
|
|
opacity_function = lambda r : 0.5,
|
|
max_opacity_ambient = 0,
|
|
max_opacity_spotlight = 0.5,
|
|
num_levels = NUM_LEVELS,
|
|
radius = 150,
|
|
screen = self.screen
|
|
)
|
|
|
|
self.sun.move_source_to(sun_position)
|
|
|
|
|
|
# Add elements to scene
|
|
|
|
self.add(self.sun,self.screen)
|
|
self.bring_to_back(self.sun.shadow)
|
|
screen_tracker = ScreenTracker(self.sun)
|
|
|
|
self.add(screen_tracker)
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
FadeIn(earth),
|
|
FadeIn(background_earth)
|
|
)
|
|
self.add_foreground_mobject(earth)
|
|
self.add_foreground_mobject(self.screen)
|
|
|
|
|
|
# move screen onto Earth
|
|
screen_on_earth = self.screen.deepcopy()
|
|
screen_on_earth.rotate(-theta)
|
|
screen_on_earth.scale(0.3)
|
|
screen_on_earth.move_to(np.array([
|
|
earth_center_x - earth_radius * np.cos(theta),
|
|
earth_radius * np.sin(theta),
|
|
0]))
|
|
|
|
polar_morty = morty.copy().scale(0.5).next_to(screen_on_earth,DOWN,buff = 0.5)
|
|
polar_morty.set_color(BLUE_C)
|
|
|
|
self.play(
|
|
Transform(self.screen, screen_on_earth),
|
|
Transform(morty,polar_morty)
|
|
)
|
|
|
|
self.wait()
|
|
|
|
|
|
tropical_morty = polar_morty.copy()
|
|
tropical_morty.move_to(np.array([0,0,0]))
|
|
tropical_morty.set_color(RED)
|
|
|
|
morty.target = tropical_morty
|
|
|
|
# move screen to equator
|
|
|
|
self.play(
|
|
Rotate(earth, theta0 + dtheta/2,run_time = 3),
|
|
MoveToTarget(morty, path_arc = 70*DEGREES, run_time = 3),
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ScreenShapingScene(ThreeDScene):
|
|
|
|
|
|
# TODO: Morph from Earth Scene into this scene
|
|
|
|
def construct(self):
|
|
|
|
#self.force_skipping()
|
|
self.setup_elements()
|
|
self.deform_screen()
|
|
self.create_brightness_rect()
|
|
self.slant_screen()
|
|
self.unslant_screen()
|
|
self.left_shift_screen_while_showing_light_indicator()
|
|
self.add_distance_arrow()
|
|
self.right_shift_screen_while_showing_light_indicator_and_distance_arrow()
|
|
self.left_shift_again()
|
|
#self.revert_to_original_skipping_status()
|
|
|
|
self.morph_into_3d()
|
|
self.prove_inverse_square_law()
|
|
|
|
|
|
def setup_elements(self):
|
|
|
|
SCREEN_THICKNESS = 10
|
|
|
|
self.screen_height = 1.0
|
|
self.brightness_rect_height = 1.0
|
|
|
|
# screen
|
|
self.screen = Line([3,-self.screen_height/2,0],[3,self.screen_height/2,0],
|
|
path_arc = 0, num_arc_anchors = 10)
|
|
|
|
# light source
|
|
self.light_source = LightSource(
|
|
opacity_function = inverse_quadratic(1,5,1),
|
|
num_levels = NUM_LEVELS,
|
|
radius = 10,
|
|
max_opacity = 0.2
|
|
#screen = self.screen
|
|
)
|
|
self.light_source.set_max_opacity_spotlight(0.2)
|
|
|
|
self.light_source.set_screen(self.screen)
|
|
self.light_source.move_source_to([-5,0,0])
|
|
|
|
# abbreviations
|
|
self.ambient_light = self.light_source.ambient_light
|
|
self.spotlight = self.light_source.spotlight
|
|
self.lighthouse = self.light_source.lighthouse
|
|
|
|
|
|
#self.add_foreground_mobject(self.light_source.shadow)
|
|
|
|
# Morty
|
|
self.morty = Mortimer().scale(0.3).next_to(self.screen, RIGHT, buff = 0.5)
|
|
|
|
# Add everything to the scene
|
|
self.add(self.lighthouse)
|
|
|
|
self.wait()
|
|
self.play(FadeIn(self.screen))
|
|
self.wait()
|
|
|
|
self.add_foreground_mobject(self.screen)
|
|
self.add_foreground_mobject(self.morty)
|
|
|
|
self.play(SwitchOn(self.ambient_light))
|
|
|
|
self.play(
|
|
SwitchOn(self.spotlight),
|
|
self.light_source.dim_ambient
|
|
)
|
|
|
|
screen_tracker = ScreenTracker(self.light_source)
|
|
self.add(screen_tracker)
|
|
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
def deform_screen(self):
|
|
|
|
self.wait()
|
|
|
|
self.play(ApplyMethod(self.screen.set_path_arc, 45 * DEGREES))
|
|
self.play(ApplyMethod(self.screen.set_path_arc, -90 * DEGREES))
|
|
self.play(ApplyMethod(self.screen.set_path_arc, 0))
|
|
|
|
|
|
|
|
|
|
def create_brightness_rect(self):
|
|
|
|
# in preparation for the slanting, create a rectangle that shows the brightness
|
|
|
|
# a rect a zero width overlaying the screen
|
|
# so we can morph it into the brightness rect above
|
|
brightness_rect0 = Rectangle(width = 0,
|
|
height = self.screen_height).move_to(self.screen.get_center())
|
|
self.add_foreground_mobject(brightness_rect0)
|
|
|
|
self.brightness_rect = Rectangle(width = self.brightness_rect_height,
|
|
height = self.brightness_rect_height, fill_color = YELLOW, fill_opacity = 0.5)
|
|
|
|
self.brightness_rect.next_to(self.screen, UP, buff = 1)
|
|
|
|
self.play(
|
|
ReplacementTransform(brightness_rect0,self.brightness_rect)
|
|
)
|
|
|
|
self.unslanted_screen = self.screen.deepcopy()
|
|
self.unslanted_brightness_rect = self.brightness_rect.copy()
|
|
# for unslanting the screen later
|
|
|
|
|
|
def slant_screen(self):
|
|
|
|
SLANTING_AMOUNT = 0.1
|
|
|
|
lower_screen_point, upper_screen_point = self.screen.get_start_and_end()
|
|
|
|
lower_slanted_screen_point = interpolate(
|
|
lower_screen_point, self.spotlight.get_source_point(), SLANTING_AMOUNT
|
|
)
|
|
upper_slanted_screen_point = interpolate(
|
|
upper_screen_point, self.spotlight.get_source_point(), -SLANTING_AMOUNT
|
|
)
|
|
|
|
self.slanted_brightness_rect = self.brightness_rect.copy()
|
|
self.slanted_brightness_rect.width *= 2
|
|
self.slanted_brightness_rect.generate_points()
|
|
self.slanted_brightness_rect.set_fill(opacity = 0.25)
|
|
|
|
self.slanted_screen = Line(lower_slanted_screen_point,upper_slanted_screen_point,
|
|
path_arc = 0, num_arc_anchors = 10)
|
|
self.slanted_brightness_rect.move_to(self.brightness_rect.get_center())
|
|
|
|
self.play(
|
|
Transform(self.screen,self.slanted_screen),
|
|
Transform(self.brightness_rect,self.slanted_brightness_rect),
|
|
)
|
|
|
|
|
|
|
|
def unslant_screen(self):
|
|
|
|
self.wait()
|
|
self.play(
|
|
Transform(self.screen,self.unslanted_screen),
|
|
Transform(self.brightness_rect,self.unslanted_brightness_rect),
|
|
)
|
|
|
|
|
|
|
|
|
|
def left_shift_screen_while_showing_light_indicator(self):
|
|
|
|
# Scene 5: constant screen size, changing opening angle
|
|
|
|
OPACITY_FOR_UNIT_INTENSITY = 1
|
|
|
|
# let's use an actual light indicator instead of just rects
|
|
|
|
self.indicator_intensity = 0.25
|
|
indicator_height = 1.25 * self.screen_height
|
|
|
|
self.indicator = LightIndicator(radius = indicator_height/2,
|
|
opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY,
|
|
color = LIGHT_COLOR,
|
|
precision = 2)
|
|
self.indicator.set_intensity(self.indicator_intensity)
|
|
|
|
self.indicator.move_to(self.brightness_rect.get_center())
|
|
|
|
self.play(
|
|
FadeOut(self.brightness_rect),
|
|
FadeIn(self.indicator)
|
|
)
|
|
|
|
# Here some digits of the indicator disappear...
|
|
|
|
self.add_foreground_mobject(self.indicator.reading)
|
|
|
|
|
|
self.unit_indicator_intensity = 1.0 # intensity at distance 1
|
|
# (where we are about to move to)
|
|
|
|
self.left_shift = (self.screen.get_center()[0] - self.spotlight.get_source_point()[0])/2
|
|
|
|
self.play(
|
|
self.screen.shift,[-self.left_shift,0,0],
|
|
self.morty.shift,[-self.left_shift,0,0],
|
|
self.indicator.shift,[-self.left_shift,0,0],
|
|
self.indicator.set_intensity,self.unit_indicator_intensity,
|
|
)
|
|
|
|
|
|
|
|
def add_distance_arrow(self):
|
|
|
|
# distance arrow (length 1)
|
|
left_x = self.spotlight.get_source_point()[0]
|
|
right_x = self.screen.get_center()[0]
|
|
arrow_y = -2
|
|
arrow1 = Arrow([left_x,arrow_y,0],[right_x,arrow_y,0])
|
|
arrow2 = Arrow([right_x,arrow_y,0],[left_x,arrow_y,0])
|
|
arrow1.set_fill(color = WHITE)
|
|
arrow2.set_fill(color = WHITE)
|
|
distance_decimal = Integer(1).next_to(arrow1,DOWN)
|
|
self.arrow = VGroup(arrow1, arrow2,distance_decimal)
|
|
self.add(self.arrow)
|
|
|
|
|
|
# distance arrow (length 2)
|
|
# will be morphed into
|
|
self.distance_to_source = right_x - left_x
|
|
new_right_x = left_x + 2 * self.distance_to_source
|
|
new_arrow1 = Arrow([left_x,arrow_y,0],[new_right_x,arrow_y,0])
|
|
new_arrow2 = Arrow([new_right_x,arrow_y,0],[left_x,arrow_y,0])
|
|
new_arrow1.set_fill(color = WHITE)
|
|
new_arrow2.set_fill(color = WHITE)
|
|
new_distance_decimal = Integer(2).next_to(new_arrow1,DOWN)
|
|
self.new_arrow = VGroup(new_arrow1, new_arrow2, new_distance_decimal)
|
|
# don't add it yet
|
|
|
|
|
|
def right_shift_screen_while_showing_light_indicator_and_distance_arrow(self):
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
ReplacementTransform(self.arrow,self.new_arrow),
|
|
ApplyMethod(self.screen.shift,[self.distance_to_source,0,0]),
|
|
ApplyMethod(self.indicator.shift,[self.left_shift,0,0]),
|
|
|
|
ApplyMethod(self.indicator.set_intensity,self.indicator_intensity),
|
|
# this should trigger ChangingDecimal, but it doesn't
|
|
# maybe bc it's an anim within an anim?
|
|
|
|
ApplyMethod(self.morty.shift,[self.distance_to_source,0,0]),
|
|
)
|
|
|
|
|
|
def left_shift_again(self):
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
ReplacementTransform(self.new_arrow,self.arrow),
|
|
ApplyMethod(self.screen.shift,[-self.distance_to_source,0,0]),
|
|
#ApplyMethod(self.indicator.shift,[-self.left_shift,0,0]),
|
|
ApplyMethod(self.indicator.set_intensity,self.unit_indicator_intensity),
|
|
ApplyMethod(self.morty.shift,[-self.distance_to_source,0,0]),
|
|
)
|
|
|
|
def morph_into_3d(self):
|
|
|
|
|
|
self.play(FadeOut(self.morty))
|
|
|
|
axes = ThreeDAxes()
|
|
self.add(axes)
|
|
|
|
phi0 = self.camera.get_phi() # default is 0 degs
|
|
theta0 = self.camera.get_theta() # default is -90 degs
|
|
distance0 = self.camera.get_distance()
|
|
|
|
phi1 = 60 * DEGREES # angle from zenith (0 to 180)
|
|
theta1 = -135 * DEGREES # azimuth (0 to 360)
|
|
distance1 = distance0
|
|
target_point = self.camera.get_spherical_coords(phi1, theta1, distance1)
|
|
|
|
dphi = phi1 - phi0
|
|
dtheta = theta1 - theta0
|
|
|
|
camera_target_point = target_point # self.camera.get_spherical_coords(45 * DEGREES, -60 * DEGREES)
|
|
projection_direction = self.camera.spherical_coords_to_point(phi1,theta1, 1)
|
|
|
|
new_screen0 = Rectangle(height = self.screen_height,
|
|
width = 0.1, stroke_color = RED, fill_color = RED, fill_opacity = 1)
|
|
new_screen0.rotate(TAU/4,axis = DOWN)
|
|
new_screen0.move_to(self.screen.get_center())
|
|
self.add(new_screen0)
|
|
self.remove(self.screen)
|
|
self.light_source.set_screen(new_screen0)
|
|
|
|
self.light_source.set_camera(self.camera)
|
|
|
|
|
|
new_screen = Rectangle(height = self.screen_height,
|
|
width = self.screen_height, stroke_color = RED, fill_color = RED, fill_opacity = 1)
|
|
new_screen.rotate(TAU/4,axis = DOWN)
|
|
new_screen.move_to(self.screen.get_center())
|
|
|
|
self.add_foreground_mobject(self.ambient_light)
|
|
self.add_foreground_mobject(self.spotlight)
|
|
self.add_foreground_mobject(self.light_source.shadow)
|
|
|
|
self.play(
|
|
ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point),
|
|
|
|
)
|
|
self.remove(self.spotlight)
|
|
|
|
self.play(Transform(new_screen0,new_screen))
|
|
|
|
self.wait()
|
|
|
|
self.unit_screen = new_screen0 # better name
|
|
|
|
|
|
|
|
def prove_inverse_square_law(self):
|
|
|
|
def orientate(mob):
|
|
mob.move_to(self.unit_screen)
|
|
mob.rotate(TAU/4, axis = LEFT)
|
|
mob.rotate(TAU/4, axis = OUT)
|
|
mob.rotate(TAU/2, axis = LEFT)
|
|
return mob
|
|
|
|
unit_screen_copy = self.unit_screen.copy()
|
|
fourfold_screen = self.unit_screen.copy()
|
|
fourfold_screen.scale(2,about_point = self.light_source.get_source_point())
|
|
|
|
self.remove(self.spotlight)
|
|
|
|
|
|
reading1 = TexMobject("1")
|
|
orientate(reading1)
|
|
|
|
self.play(FadeIn(reading1))
|
|
self.wait()
|
|
self.play(FadeOut(reading1))
|
|
|
|
|
|
self.play(
|
|
Transform(self.unit_screen, fourfold_screen)
|
|
)
|
|
|
|
reading21 = TexMobject("{1\over 4}").scale(0.8)
|
|
orientate(reading21)
|
|
reading22 = reading21.deepcopy()
|
|
reading23 = reading21.deepcopy()
|
|
reading24 = reading21.deepcopy()
|
|
reading21.shift(0.5*OUT + 0.5*UP)
|
|
reading22.shift(0.5*OUT + 0.5*DOWN)
|
|
reading23.shift(0.5*IN + 0.5*UP)
|
|
reading24.shift(0.5*IN + 0.5*DOWN)
|
|
|
|
|
|
corners = fourfold_screen.get_anchors()
|
|
midpoint1 = (corners[0] + corners[1])/2
|
|
midpoint2 = (corners[1] + corners[2])/2
|
|
midpoint3 = (corners[2] + corners[3])/2
|
|
midpoint4 = (corners[3] + corners[0])/2
|
|
midline1 = Line(midpoint1, midpoint3)
|
|
midline2 = Line(midpoint2, midpoint4)
|
|
|
|
self.play(
|
|
ShowCreation(midline1),
|
|
ShowCreation(midline2)
|
|
)
|
|
|
|
self.play(
|
|
FadeIn(reading21),
|
|
FadeIn(reading22),
|
|
FadeIn(reading23),
|
|
FadeIn(reading24),
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
FadeOut(reading21),
|
|
FadeOut(reading22),
|
|
FadeOut(reading23),
|
|
FadeOut(reading24),
|
|
FadeOut(midline1),
|
|
FadeOut(midline2)
|
|
)
|
|
|
|
ninefold_screen = unit_screen_copy.copy()
|
|
ninefold_screen.scale(3,about_point = self.light_source.get_source_point())
|
|
|
|
self.play(
|
|
Transform(self.unit_screen, ninefold_screen)
|
|
)
|
|
|
|
reading31 = TexMobject("{1\over 9}").scale(0.8)
|
|
orientate(reading31)
|
|
reading32 = reading31.deepcopy()
|
|
reading33 = reading31.deepcopy()
|
|
reading34 = reading31.deepcopy()
|
|
reading35 = reading31.deepcopy()
|
|
reading36 = reading31.deepcopy()
|
|
reading37 = reading31.deepcopy()
|
|
reading38 = reading31.deepcopy()
|
|
reading39 = reading31.deepcopy()
|
|
reading31.shift(IN + UP)
|
|
reading32.shift(IN)
|
|
reading33.shift(IN + DOWN)
|
|
reading34.shift(UP)
|
|
reading35.shift(ORIGIN)
|
|
reading36.shift(DOWN)
|
|
reading37.shift(OUT + UP)
|
|
reading38.shift(OUT)
|
|
reading39.shift(OUT + DOWN)
|
|
|
|
corners = ninefold_screen.get_anchors()
|
|
midpoint11 = (2*corners[0] + corners[1])/3
|
|
midpoint12 = (corners[0] + 2*corners[1])/3
|
|
midpoint21 = (2*corners[1] + corners[2])/3
|
|
midpoint22 = (corners[1] + 2*corners[2])/3
|
|
midpoint31 = (2*corners[2] + corners[3])/3
|
|
midpoint32 = (corners[2] + 2*corners[3])/3
|
|
midpoint41 = (2*corners[3] + corners[0])/3
|
|
midpoint42 = (corners[3] + 2*corners[0])/3
|
|
midline11 = Line(midpoint11, midpoint32)
|
|
midline12 = Line(midpoint12, midpoint31)
|
|
midline21 = Line(midpoint21, midpoint42)
|
|
midline22 = Line(midpoint22, midpoint41)
|
|
|
|
self.play(
|
|
ShowCreation(midline11),
|
|
ShowCreation(midline12),
|
|
ShowCreation(midline21),
|
|
ShowCreation(midline22),
|
|
)
|
|
|
|
self.play(
|
|
FadeIn(reading31),
|
|
FadeIn(reading32),
|
|
FadeIn(reading33),
|
|
FadeIn(reading34),
|
|
FadeIn(reading35),
|
|
FadeIn(reading36),
|
|
FadeIn(reading37),
|
|
FadeIn(reading38),
|
|
FadeIn(reading39),
|
|
)
|
|
|
|
|
|
|
|
|
|
class IndicatorScalingScene(Scene):
|
|
|
|
def construct(self):
|
|
|
|
unit_intensity = 0.6
|
|
|
|
indicator1 = LightIndicator(show_reading = False, color = LIGHT_COLOR)
|
|
indicator1.set_intensity(unit_intensity)
|
|
reading1 = TexMobject("1")
|
|
reading1.move_to(indicator1)
|
|
|
|
|
|
indicator2 = LightIndicator(show_reading = False, color = LIGHT_COLOR)
|
|
indicator2.shift(2*RIGHT)
|
|
indicator2.set_intensity(unit_intensity/4)
|
|
reading2 = TexMobject("{1\over 4}").scale(0.8)
|
|
reading2.move_to(indicator2)
|
|
|
|
indicator3 = LightIndicator(show_reading = False, color = LIGHT_COLOR)
|
|
indicator3.shift(4*RIGHT)
|
|
indicator3.set_intensity(unit_intensity/9)
|
|
reading3 = TexMobject("{1\over 9}").scale(0.8)
|
|
reading3.move_to(indicator3)
|
|
|
|
|
|
self.play(FadeIn(indicator1))
|
|
self.play(FadeIn(reading1))
|
|
self.wait()
|
|
self.play(FadeOut(reading1))
|
|
self.play(Transform(indicator1, indicator2))
|
|
self.play(FadeIn(reading2))
|
|
self.wait()
|
|
self.play(FadeOut(reading2))
|
|
self.play(Transform(indicator1, indicator3))
|
|
self.play(FadeIn(reading3))
|
|
self.wait()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BackToEulerSumScene(PiCreatureScene):
|
|
|
|
|
|
def construct(self):
|
|
self.remove(self.get_primary_pi_creature())
|
|
|
|
NUM_CONES = 7
|
|
NUM_VISIBLE_CONES = 6
|
|
INDICATOR_RADIUS = 0.5
|
|
OPACITY_FOR_UNIT_INTENSITY = 1.0
|
|
|
|
self.number_line = NumberLine(
|
|
x_min = 0,
|
|
color = WHITE,
|
|
number_at_center = 1.6,
|
|
stroke_width = 1,
|
|
numbers_with_elongated_ticks = range(1,5),
|
|
numbers_to_show = range(1,5),
|
|
unit_size = 2,
|
|
tick_frequency = 0.2,
|
|
line_to_number_buff = LARGE_BUFF,
|
|
label_direction = UP,
|
|
)
|
|
|
|
self.number_line.label_direction = DOWN
|
|
#self.number_line.shift(3*UP)
|
|
|
|
self.number_line_labels = self.number_line.get_number_mobjects()
|
|
self.add(self.number_line,self.number_line_labels)
|
|
self.wait()
|
|
|
|
origin_point = self.number_line.number_to_point(0)
|
|
|
|
self.default_pi_creature_class = Randolph
|
|
randy = self.get_primary_pi_creature()
|
|
|
|
randy.scale(0.5)
|
|
randy.flip()
|
|
right_pupil = randy.pupils[1]
|
|
randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil)
|
|
|
|
randy_copy = randy.copy()
|
|
randy_copy.target = randy.copy().shift(DOWN)
|
|
|
|
|
|
|
|
bubble = ThoughtBubble(direction = RIGHT,
|
|
width = 4, height = 3,
|
|
file_name = "Bubbles_thought.svg")
|
|
bubble.next_to(randy,LEFT+UP)
|
|
bubble.set_fill(color = BLACK, opacity = 1)
|
|
|
|
self.play(
|
|
randy.change, "wave_2",
|
|
ShowCreation(bubble),
|
|
)
|
|
|
|
|
|
euler_sum = TexMobject("1", "+", "{1\over 4}",
|
|
"+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "\cdots", " ")
|
|
# the last entry is a dummy element which makes looping easier
|
|
# used just for putting the fractions into the light indicators
|
|
|
|
intensities = np.array([1./(n+1)**2 for n in range(NUM_CONES)])
|
|
opacities = intensities * OPACITY_FOR_UNIT_INTENSITY
|
|
|
|
# repeat:
|
|
|
|
# fade in lighthouse
|
|
# switch on / fade in ambient light
|
|
# show creation / write light indicator
|
|
# move indicator onto origin
|
|
# while morphing and dimming
|
|
# move indicator into thought bubble
|
|
# while indicators already inside shift to the back
|
|
# and while term appears in the series below
|
|
|
|
point = self.number_line.number_to_point(1)
|
|
v = point - self.number_line.number_to_point(0)
|
|
light_source = LightSource()
|
|
light_source.move_source_to(point)
|
|
#light_source.ambient_light.move_source_to(point)
|
|
#light_source.lighthouse.move_to(point)
|
|
|
|
self.play(FadeIn(light_source.lighthouse))
|
|
self.play(SwitchOn(light_source.ambient_light))
|
|
|
|
|
|
# create an indicator that will move along the number line
|
|
indicator = LightIndicator(color = LIGHT_COLOR,
|
|
radius = INDICATOR_RADIUS,
|
|
opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY,
|
|
show_reading = False
|
|
)
|
|
indicator_reading = euler_sum[0]
|
|
indicator_reading.scale_to_fit_height(0.5 * indicator.get_height())
|
|
indicator_reading.move_to(indicator.get_center())
|
|
indicator.add(indicator_reading)
|
|
indicator.tex_reading = indicator_reading
|
|
# the TeX reading is too bright at full intensity
|
|
indicator.tex_reading.set_fill(color = BLACK)
|
|
indicator.foreground.set_fill(None,opacities[0])
|
|
|
|
|
|
indicator.move_to(point)
|
|
indicator.set_intensity(intensities[0])
|
|
|
|
self.play(FadeIn(indicator))
|
|
self.add_foreground_mobject(indicator)
|
|
|
|
collection_point = np.array([-6.,2.,0.])
|
|
left_shift = 0.2*LEFT
|
|
collected_indicators = Mobject()
|
|
|
|
|
|
for i in range(2, NUM_VISIBLE_CONES + 1):
|
|
|
|
previous_point = self.number_line.number_to_point(i - 1)
|
|
point = self.number_line.number_to_point(i)
|
|
|
|
|
|
v = point - previous_point
|
|
#print v
|
|
# Create and position the target indicator (next on number line).
|
|
indicator_target = indicator.deepcopy()
|
|
indicator_target.shift(v)
|
|
|
|
|
|
# Here we make a copy that will move into the thought bubble.
|
|
bubble_indicator = indicator.deepcopy()
|
|
# And its target
|
|
bubble_indicator_target = bubble_indicator.deepcopy()
|
|
bubble_indicator_target.set_intensity(intensities[i - 2])
|
|
|
|
# give the target the appropriate reading
|
|
euler_sum[2*i-4].move_to(bubble_indicator_target)
|
|
bubble_indicator_target.remove(bubble_indicator_target.tex_reading)
|
|
bubble_indicator_target.tex_reading = euler_sum[2*i-4].copy()
|
|
bubble_indicator_target.add(bubble_indicator_target.tex_reading)
|
|
# center it in the indicator
|
|
|
|
if bubble_indicator_target.tex_reading.get_tex_string() != "1":
|
|
bubble_indicator_target.tex_reading.scale_to_fit_height(0.8*indicator.get_height())
|
|
# the target is less bright, possibly switch to a white text color
|
|
if bubble_indicator_target.intensity < 0.7:
|
|
bubble_indicator.tex_reading.set_fill(color = WHITE)
|
|
|
|
# position the target in the thought bubble
|
|
bubble_indicator_target.move_to(collection_point)
|
|
|
|
|
|
self.add_foreground_mobject(bubble_indicator)
|
|
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
Transform(bubble_indicator,bubble_indicator_target),
|
|
collected_indicators.shift,left_shift,
|
|
)
|
|
|
|
collected_indicators.add(bubble_indicator)
|
|
|
|
new_light = light_source.deepcopy()
|
|
w = new_light.get_source_point()
|
|
new_light.move_source_to(w + (i-2)*v)
|
|
w2 = new_light.get_source_point()
|
|
|
|
self.add(new_light.lighthouse)
|
|
self.play(
|
|
Transform(indicator,indicator_target),
|
|
new_light.lighthouse.shift,v,
|
|
)
|
|
new_light.move_source_to(w + (i-1)*v)
|
|
new_light.lighthouse.move_to(w + (i-1)*v)
|
|
|
|
self.play(SwitchOn(new_light.ambient_light),
|
|
)
|
|
|
|
|
|
|
|
|
|
# quickly switch on off-screen light cones
|
|
for i in range(NUM_VISIBLE_CONES,NUM_CONES):
|
|
indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.ambient_light.radius * self.number_line.unit_size
|
|
indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME
|
|
indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9)
|
|
smooth,indicator_start_time,indicator_stop_time)
|
|
ls = LightSource()
|
|
point = point = self.number_line.number_to_point(i)
|
|
ls.move_source_to(point)
|
|
self.play(
|
|
SwitchOn(ls.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME),
|
|
)
|
|
|
|
# and morph indicator stack into limit value
|
|
|
|
sum_indicator = LightIndicator(color = LIGHT_COLOR,
|
|
radius = INDICATOR_RADIUS,
|
|
opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY,
|
|
show_reading = False
|
|
)
|
|
sum_indicator.set_intensity(intensities[0] * np.pi**2/6)
|
|
sum_indicator_reading = TexMobject("{\pi^2 \over 6}")
|
|
sum_indicator_reading.set_fill(color = BLACK)
|
|
sum_indicator_reading.scale_to_fit_height(0.8 * sum_indicator.get_height())
|
|
sum_indicator.add(sum_indicator_reading)
|
|
sum_indicator.move_to(collection_point)
|
|
|
|
self.play(
|
|
FadeOut(collected_indicators),
|
|
FadeIn(sum_indicator)
|
|
)
|
|
|
|
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
|
|
|
|
class TwoLightSourcesScene(PiCreatureScene):
|
|
|
|
def construct(self):
|
|
|
|
MAX_OPACITY = 0.4
|
|
INDICATOR_RADIUS = 0.6
|
|
OPACITY_FOR_UNIT_INTENSITY = 0.5
|
|
|
|
morty = self.get_primary_pi_creature()
|
|
morty.scale(0.3).flip()
|
|
right_pupil = morty.pupils[1]
|
|
morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil)
|
|
|
|
horizontal = VMobject(stroke_width = 1)
|
|
horizontal.set_points_as_corners([C,A])
|
|
vertical = VMobject(stroke_width = 1)
|
|
vertical.set_points_as_corners([C,B])
|
|
|
|
self.play(
|
|
ShowCreation(horizontal),
|
|
ShowCreation(vertical)
|
|
)
|
|
|
|
indicator = LightIndicator(color = LIGHT_COLOR,
|
|
radius = INDICATOR_RADIUS,
|
|
opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY,
|
|
show_reading = True,
|
|
precision = 2
|
|
)
|
|
|
|
indicator.next_to(morty,LEFT)
|
|
|
|
self.play(
|
|
Write(indicator)
|
|
)
|
|
|
|
|
|
ls1 = LightSource(radius = 20, num_levels = 50)
|
|
ls2 = ls1.deepcopy()
|
|
ls1.move_source_to(A)
|
|
ls2.move_source_to(B)
|
|
|
|
self.play(
|
|
FadeIn(ls1.lighthouse),
|
|
FadeIn(ls2.lighthouse),
|
|
SwitchOn(ls1.ambient_light),
|
|
SwitchOn(ls2.ambient_light)
|
|
)
|
|
|
|
distance1 = np.linalg.norm(C - ls1.get_source_point())
|
|
intensity = ls1.ambient_light.opacity_function(distance1) / indicator.opacity_for_unit_intensity
|
|
distance2 = np.linalg.norm(C - ls2.get_source_point())
|
|
intensity += ls2.ambient_light.opacity_function(distance2) / indicator.opacity_for_unit_intensity
|
|
|
|
self.play(
|
|
UpdateLightIndicator(indicator,intensity)
|
|
)
|
|
|
|
self.wait()
|
|
|
|
ls3 = ls1.deepcopy()
|
|
ls3.move_to(np.array([6,3.5,0]))
|
|
|
|
new_indicator = indicator.copy()
|
|
new_indicator.light_source = ls3
|
|
new_indicator.measurement_point = C
|
|
self.add(new_indicator)
|
|
self.play(
|
|
indicator.shift, 2 * UP
|
|
)
|
|
|
|
|
|
|
|
#intensity = intensity_for_light_source(ls3)
|
|
|
|
|
|
self.play(
|
|
SwitchOff(ls1.ambient_light),
|
|
#FadeOut(ls1.lighthouse),
|
|
SwitchOff(ls2.ambient_light),
|
|
#FadeOut(ls2.lighthouse),
|
|
UpdateLightIndicator(new_indicator,0.0)
|
|
)
|
|
|
|
# create a *continual* animation for the replacement source
|
|
updater = ContinualLightIndicatorUpdate(new_indicator)
|
|
self.add(updater)
|
|
|
|
self.play(
|
|
SwitchOn(ls3.ambient_light),
|
|
FadeIn(ls3.lighthouse),
|
|
|
|
)
|
|
|
|
self.wait()
|
|
|
|
# move the light source around
|
|
# TODO: moving along a path arc
|
|
|
|
location = np.array([-3,-2.,0.])
|
|
self.play(ls3.move_source_to,location)
|
|
location = np.array([6.,1.,0.])
|
|
self.play(ls3.move_source_to,location)
|
|
location = np.array([5.,2.,0.])
|
|
self.play(ls3.move_source_to,location)
|
|
closer_location = interpolate(location, C, 0.5)
|
|
self.play(ls3.move_source_to,closer_location)
|
|
self.play(ls3.move_source_to,location)
|
|
|
|
# maybe move in a circle around C using a loop?
|
|
|
|
|
|
|
|
self.play(ls3.move_source_to,H)
|
|
|
|
|
|
|
|
# draw lines to complete the geometric picture
|
|
# and label the lengths
|
|
|
|
line_a = VMobject()
|
|
line_a.set_points_as_corners([B,C])
|
|
line_b = VMobject()
|
|
line_b.set_points_as_corners([A,C])
|
|
line_c = VMobject()
|
|
line_c.set_points_as_corners([A,B])
|
|
line_h = VMobject()
|
|
line_h.set_points_as_corners([H,C])
|
|
|
|
label_a = TexMobject("a")
|
|
label_a.next_to(line_a, LEFT, buff = 0.5)
|
|
label_b = TexMobject("b")
|
|
label_b.next_to(line_b, DOWN, buff = 0.5)
|
|
label_h = TexMobject("h")
|
|
label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5)
|
|
|
|
self.play(
|
|
ShowCreation(line_a),
|
|
Write(label_a)
|
|
)
|
|
|
|
self.play(
|
|
ShowCreation(line_b),
|
|
Write(label_b)
|
|
)
|
|
|
|
self.play(
|
|
ShowCreation(line_c),
|
|
)
|
|
|
|
self.play(
|
|
ShowCreation(line_h),
|
|
Write(label_h)
|
|
)
|
|
|
|
|
|
# state the IPT
|
|
theorem_location = np.array([3.,2.,0.])
|
|
theorem = TexMobject("{1\over a^2} + {1\over b^2} = {1\over h^2}")
|
|
theorem_name = TextMobject("Inverse Pythagorean Theorem")
|
|
buffer = 1.2
|
|
theorem_box = Rectangle(width = buffer*theorem.get_width(),
|
|
height = buffer*theorem.get_height())
|
|
|
|
theorem.move_to(theorem_location)
|
|
theorem_box.move_to(theorem_location)
|
|
theorem_name.next_to(theorem_box,UP)
|
|
|
|
self.play(
|
|
Write(theorem),
|
|
)
|
|
|
|
self.play(
|
|
ShowCreation(theorem_box),
|
|
Write(theorem_name),
|
|
)
|
|
|
|
|
|
|
|
class IPTScene1(PiCreatureScene):
|
|
|
|
def construct(self):
|
|
|
|
show_detail = True
|
|
|
|
SCREEN_SCALE = 0.1
|
|
SCREEN_THICKNESS = 0.2
|
|
|
|
|
|
# use the following for the zoomed inset
|
|
if show_detail:
|
|
self.camera.frame_shape = (0.02 * FRAME_HEIGHT, 0.02 * FRAME_WIDTH)
|
|
self.camera.space_center = C
|
|
SCREEN_SCALE = 0.01
|
|
SCREEN_THICKNESS = 0.02
|
|
|
|
|
|
|
|
|
|
|
|
morty = self.get_primary_pi_creature()
|
|
self.remove(morty)
|
|
morty.scale(0.3).flip()
|
|
right_pupil = morty.pupils[1]
|
|
morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil)
|
|
|
|
if not show_detail:
|
|
self.add_foreground_mobject(morty)
|
|
|
|
stroke_width = 6
|
|
line_a = Line(B,C,stroke_width = stroke_width)
|
|
line_b = Line(A,C,stroke_width = stroke_width)
|
|
line_c = Line(A,B,stroke_width = stroke_width)
|
|
line_h = Line(C,H,stroke_width = stroke_width)
|
|
|
|
length_a = line_a.get_length()
|
|
length_b = line_b.get_length()
|
|
length_c = line_c.get_length()
|
|
length_h = line_h.get_length()
|
|
|
|
label_a = TexMobject("a")
|
|
label_a.next_to(line_a, LEFT, buff = 0.5)
|
|
label_b = TexMobject("b")
|
|
label_b.next_to(line_b, DOWN, buff = 0.5)
|
|
label_h = TexMobject("h")
|
|
label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5)
|
|
|
|
self.add_foreground_mobject(line_a)
|
|
self.add_foreground_mobject(line_b)
|
|
self.add_foreground_mobject(line_c)
|
|
self.add_foreground_mobject(line_h)
|
|
self.add_foreground_mobject(label_a)
|
|
self.add_foreground_mobject(label_b)
|
|
self.add_foreground_mobject(label_h)
|
|
|
|
if not show_detail:
|
|
self.add_foreground_mobject(morty)
|
|
|
|
ls1 = LightSource(radius = 10)
|
|
ls1.move_source_to(B)
|
|
|
|
self.add(ls1.lighthouse)
|
|
|
|
if not show_detail:
|
|
self.play(
|
|
SwitchOn(ls1.ambient_light)
|
|
)
|
|
|
|
self.wait()
|
|
|
|
# adding the first screen
|
|
|
|
screen_width_a = SCREEN_SCALE * length_a
|
|
screen_width_b = SCREEN_SCALE * length_b
|
|
screen_width_ap = screen_width_a * length_a / length_c
|
|
screen_width_bp = screen_width_b * length_b / length_c
|
|
screen_width_c = SCREEN_SCALE * length_c
|
|
|
|
screen_thickness_a = SCREEN_THICKNESS
|
|
screen_thickness_b = SCREEN_THICKNESS
|
|
|
|
screen1 = Rectangle(width = screen_width_b,
|
|
height = screen_thickness_b,
|
|
stroke_width = 0,
|
|
fill_opacity = 1.0)
|
|
screen1.move_to(C + screen_width_b/2 * RIGHT + screen_thickness_b/2 * DOWN)
|
|
|
|
if not show_detail:
|
|
self.add_foreground_mobject(morty)
|
|
|
|
self.play(
|
|
FadeIn(screen1)
|
|
)
|
|
self.add_foreground_mobject(screen1)
|
|
|
|
ls1.set_screen(screen1)
|
|
screen_tracker = ScreenTracker(ls1)
|
|
self.add(screen_tracker)
|
|
#self.add(ls1.shadow)
|
|
|
|
if not show_detail:
|
|
self.play(
|
|
SwitchOn(ls1.ambient_light)
|
|
)
|
|
|
|
self.play(
|
|
SwitchOn(ls1.spotlight),
|
|
SwitchOff(ls1.ambient_light)
|
|
)
|
|
|
|
|
|
|
|
# now move the light source to the height point
|
|
# while shifting scaling the screen
|
|
screen1p = screen1.deepcopy()
|
|
screen1pp = screen1.deepcopy()
|
|
#self.add(screen1p)
|
|
angle = np.arccos(length_b / length_c)
|
|
|
|
screen1p.stretch_to_fit_width(screen_width_bp)
|
|
screen1p.move_to(C + (screen_width_b - screen_width_bp/2) * RIGHT + SCREEN_THICKNESS/2 * DOWN)
|
|
screen1p.rotate(-angle, about_point = C + screen_width_b * RIGHT)
|
|
|
|
|
|
self.play(
|
|
ls1.move_source_to,H,
|
|
Transform(screen1,screen1p)
|
|
)
|
|
|
|
# add and move the second light source and screen
|
|
ls2 = ls1.deepcopy()
|
|
ls2.move_source_to(A)
|
|
screen2 = Rectangle(width = screen_width_a,
|
|
height = screen_thickness_a,
|
|
stroke_width = 0,
|
|
fill_opacity = 1.0)
|
|
screen2.rotate(-TAU/4)
|
|
screen2.move_to(C + screen_width_a/2 * UP + screen_thickness_a/2 * LEFT)
|
|
|
|
self.play(
|
|
FadeIn(screen2)
|
|
)
|
|
self.add_foreground_mobject(screen2)
|
|
|
|
if not show_detail:
|
|
self.add_foreground_mobject(morty)
|
|
|
|
# the same scene adding sequence as before
|
|
ls2.set_screen(screen2)
|
|
screen_tracker2 = ScreenTracker(ls2)
|
|
self.add(screen_tracker2)
|
|
|
|
if not show_detail:
|
|
self.play(
|
|
SwitchOn(ls2.ambient_light)
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
SwitchOn(ls2.spotlight),
|
|
SwitchOff(ls2.ambient_light)
|
|
)
|
|
|
|
|
|
|
|
# now move the light source to the height point
|
|
# while shifting scaling the screen
|
|
screen2p = screen2.deepcopy()
|
|
screen2pp = screen2.deepcopy()
|
|
angle = np.arccos(length_a / length_c)
|
|
screen2p.stretch_to_fit_height(screen_width_ap)
|
|
screen2p.move_to(C + (screen_width_a - screen_width_ap/2) * UP + screen_thickness_a/2 * LEFT)
|
|
screen2p.rotate(angle, about_point = C + screen_width_a * UP)
|
|
# we can reuse the translation vector
|
|
# screen2p.shift(vector)
|
|
|
|
self.play(
|
|
ls2.move_source_to,H,
|
|
SwitchOff(ls1.ambient_light),
|
|
Transform(screen2,screen2p)
|
|
)
|
|
|
|
# now transform both screens back
|
|
self.play(
|
|
Transform(screen1, screen1pp),
|
|
Transform(screen2, screen2pp),
|
|
)
|
|
|
|
|
|
|
|
class IPTScene2(Scene):
|
|
|
|
def construct(self):
|
|
|
|
intensity1 = 0.3
|
|
intensity2 = 0.2
|
|
formula_scale = 01.2
|
|
indy_radius = 1
|
|
|
|
indy1 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius)
|
|
indy1.set_intensity(intensity1)
|
|
reading1 = TexMobject("{1\over a^2}").scale(formula_scale).move_to(indy1)
|
|
indy1.add(reading1)
|
|
|
|
indy2 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius)
|
|
indy2.set_intensity(intensity2)
|
|
reading2 = TexMobject("{1\over b^2}").scale(formula_scale).move_to(indy2)
|
|
indy2.add(reading2)
|
|
|
|
indy3 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius)
|
|
indy3.set_intensity(intensity1 + intensity2)
|
|
reading3 = TexMobject("{1\over h^2}").scale(formula_scale).move_to(indy3)
|
|
indy3.add(reading3)
|
|
|
|
plus_sign = TexMobject("+").scale(formula_scale)
|
|
equals_sign = TexMobject("=").scale(formula_scale)
|
|
|
|
plus_sign.next_to(indy1, RIGHT)
|
|
indy2.next_to(plus_sign, RIGHT)
|
|
equals_sign.next_to(indy2, RIGHT)
|
|
indy3.next_to(equals_sign, RIGHT)
|
|
|
|
|
|
formula = VGroup(
|
|
indy1, plus_sign, indy2, equals_sign, indy3
|
|
)
|
|
|
|
formula.move_to(ORIGIN)
|
|
|
|
self.play(FadeIn(indy1))
|
|
self.play(FadeIn(plus_sign), FadeIn(indy2))
|
|
self.play(FadeIn(equals_sign), FadeIn(indy3))
|
|
|
|
buffer = 1.5
|
|
box = Rectangle(width = formula.get_width() * buffer,
|
|
height = formula.get_height() * buffer)
|
|
box.move_to(formula)
|
|
text = TextMobject("Inverse Pythagorean Theorem").scale(formula_scale)
|
|
text.next_to(box,UP)
|
|
self.play(ShowCreation(box),Write(text))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class InscribedAngleScene(ThreeDScene):
|
|
|
|
|
|
|
|
|
|
def construct(self):
|
|
|
|
BASELINE_YPOS = -2.5
|
|
OBSERVER_POINT = [0,BASELINE_YPOS,0]
|
|
LAKE0_RADIUS = 1.5
|
|
INDICATOR_RADIUS = 0.6
|
|
TICK_SIZE = 0.5
|
|
LIGHTHOUSE_HEIGHT = 0.3
|
|
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 = 5
|
|
LIGHT_CUTOFF = 1
|
|
|
|
self.cumulated_zoom_factor = 1
|
|
|
|
|
|
def zoom_out_scene(factor):
|
|
|
|
|
|
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
|
|
|
|
|
|
def shift_scene(v):
|
|
self.play(
|
|
self.zoomable_mobs.shift,v,
|
|
self.unzoomable_mobs.shift,v
|
|
)
|
|
|
|
self.force_skipping()
|
|
|
|
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
|
|
|
|
self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR)
|
|
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().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,AMBIENT_SCALE,LIGHT_CUTOFF)
|
|
ls0 = LightSource(opacity_function = original_op_func, num_levels = NUM_LEVELS)
|
|
ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP)
|
|
self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light)
|
|
|
|
self.add(lake0,morty,self.obs_dot,self.ls0_dot, ls0.lighthouse)
|
|
|
|
self.wait()
|
|
|
|
|
|
# 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)
|
|
|
|
self.play(
|
|
ShowCreation(arc_left),
|
|
Write(one_left),
|
|
ShowCreation(arc_right),
|
|
Write(one_right),
|
|
)
|
|
|
|
|
|
self.play(
|
|
SwitchOn(ls0.ambient_light),
|
|
lake0.set_stroke,{"color": LAKE_STROKE_COLOR, "width" : LAKE_STROKE_WIDTH},
|
|
)
|
|
|
|
self.play(FadeIn(indicator))
|
|
|
|
self.play(
|
|
indicator.set_intensity,0.5
|
|
)
|
|
|
|
# diameter
|
|
diameter = DoubleArrow(OBSERVER_POINT,
|
|
ls0.get_source_point(),
|
|
buff = 0,
|
|
color = WHITE,
|
|
)
|
|
diameter_text = TexMobject("d").scale(TEX_SCALE)
|
|
diameter_text.next_to(diameter,RIGHT)
|
|
|
|
self.play(
|
|
ShowCreation(diameter),
|
|
Write(diameter_text),
|
|
#FadeOut(self.obs_dot),
|
|
FadeOut(self.ls0_dot)
|
|
)
|
|
|
|
indicator_reading = TexMobject("{1\over d^2}").scale(TEX_SCALE)
|
|
indicator_reading.move_to(indicator)
|
|
self.unzoomable_mobs.add(indicator_reading)
|
|
|
|
self.play(
|
|
FadeIn(indicator_reading)
|
|
)
|
|
|
|
# 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(
|
|
Transform(diameter_text,new_diameter_text)
|
|
)
|
|
|
|
# insert into indicator reading
|
|
new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE)
|
|
new_reading.move_to(indicator)
|
|
|
|
self.play(
|
|
Transform(indicator_reading,new_reading)
|
|
)
|
|
|
|
self.play(
|
|
FadeOut(one_left),
|
|
FadeOut(one_right),
|
|
FadeOut(diameter_text),
|
|
FadeOut(arc_left),
|
|
FadeOut(arc_right)
|
|
)
|
|
|
|
|
|
|
|
|
|
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, 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()
|
|
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.all(np.abs(ls_old_loc[:2]) < 10 ** 2**step)
|
|
onscreen_1 = np.all(np.abs(ls_new_loc1[:2][:2]) < 10 ** 2**step)
|
|
onscreen_2 = np.all(np.abs(ls_new_loc2[:2]) < 10 ** 2**step)
|
|
show_animation = (onscreen_old or onscreen_1 or onscreen_2)
|
|
|
|
if show_animation:
|
|
print "animating (", i, ",", step, ")"
|
|
self.play(
|
|
ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time),
|
|
ApplyMethod(ls2.move_source_to,ls_new_loc2, 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 = []
|
|
|
|
for i in range(2**n):
|
|
split_light_source(i,
|
|
step = n,
|
|
show_steps = show_steps,
|
|
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 = []
|
|
|
|
self.revert_to_original_skipping_status()
|
|
|
|
construction_step(0)
|
|
indicator_wiggle()
|
|
self.play(FadeOut(self.ls0_dot))
|
|
zoom_out_scene(2)
|
|
|
|
return
|
|
|
|
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.revert_to_original_skipping_status()
|
|
|
|
|
|
ANGLE_COLOR1 = BLUE_C
|
|
ANGLE_COLOR2 = GREEN_D
|
|
|
|
|
|
for mob in self.mobjects:
|
|
mob.fade(1.0)
|
|
|
|
for hyp in self.hypotenuses:
|
|
hyp.set_stroke(width = 0)
|
|
for alt in self.altitudes:
|
|
alt.set_stroke(width = 0)
|
|
for leg in self.legs:
|
|
leg.set_stroke(width = 0)
|
|
self.inner_lake.set_stroke(width = 0)
|
|
self.outer_lake.set_stroke(width = 0)
|
|
|
|
self.wait()
|
|
|
|
inner_lake_center = self.inner_lake.get_center()
|
|
inner_lake_radius = self.lake_radius * 0.25
|
|
inner_ls = VGroup()
|
|
for i in range(4):
|
|
theta = -TAU/4 + (i+0.5) * TAU/4
|
|
point = inner_lake_center + inner_lake_radius * np.array([np.cos(theta), np.sin(theta),0])
|
|
dot = Dot(point, color = LAKE_STROKE_COLOR, radius = 0.3)
|
|
inner_ls.add(dot)
|
|
|
|
self.add(inner_ls)
|
|
|
|
inner_ls1 = inner_ls.submobjects[0]
|
|
inner_ls2 = inner_ls.submobjects[1]
|
|
inner_ls1_center = inner_ls1.get_center()
|
|
inner_ls2_center = inner_ls2.get_center()
|
|
|
|
outer_lake_center = self.outer_lake.get_center()
|
|
outer_lake_radius = self.lake_radius * 0.5
|
|
outer_ls = VGroup()
|
|
for i in range(8):
|
|
theta = -TAU/4 + (i+0.5) * TAU/8
|
|
point = outer_lake_center + outer_lake_radius * np.array([np.cos(theta), np.sin(theta),0])
|
|
dot = Dot(point, color = LAKE_STROKE_COLOR, radius = 0.3)
|
|
outer_ls.add(dot)
|
|
|
|
self.add(outer_ls)
|
|
|
|
outer_ls1 = outer_ls.submobjects[0]
|
|
outer_ls2 = outer_ls.submobjects[1]
|
|
outer_ls1_center = outer_ls1.get_center()
|
|
outer_ls2_center = outer_ls2.get_center()
|
|
|
|
self.wait()
|
|
|
|
arc_radius = 2.0
|
|
|
|
line1 = Line(inner_lake_center, inner_ls1_center, color = WHITE)
|
|
line2 = Line(inner_lake_center, inner_ls2_center, color = WHITE)
|
|
|
|
|
|
#arc_point1 = interpolate(inner_lake_center, inner_ls1_center, 0.2)
|
|
#arc_point2 = interpolate(inner_lake_center, inner_ls2_center, 0.2)
|
|
#inner_angle_arc = ArcBetweenPoints(arc_point1, arc_point2, angle = TAU/4)
|
|
inner_angle_arc = Arc(angle = TAU/4, start_angle = -TAU/8, radius = arc_radius,
|
|
stroke_color = ANGLE_COLOR1)
|
|
inner_angle_arc.move_arc_center_to(inner_lake_center)
|
|
|
|
inner_label = TexMobject("\\theta", fill_color = ANGLE_COLOR1).scale(3).next_to(inner_angle_arc, LEFT, buff = -0.1)
|
|
|
|
self.play(
|
|
ShowCreation(line1),
|
|
ShowCreation(line2),
|
|
)
|
|
self.play(
|
|
ShowCreation(inner_angle_arc),
|
|
FadeIn(inner_label)
|
|
)
|
|
|
|
|
|
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
line3 = Line(outer_lake_center, inner_ls1_center, color = WHITE)
|
|
line4 = Line(outer_lake_center, inner_ls2_center, color = WHITE)
|
|
outer_angle_arc = Arc(angle = TAU/8, start_angle = -3*TAU/16, radius = arc_radius,
|
|
stroke_color = ANGLE_COLOR2)
|
|
outer_angle_arc.move_arc_center_to(outer_lake_center)
|
|
|
|
outer_label = TexMobject("{\\theta \over 2}", color = ANGLE_COLOR2).scale(2.5).move_to(outer_angle_arc)
|
|
outer_label.shift([-2,-1,0])
|
|
|
|
self.play(
|
|
ShowCreation(line3),
|
|
ShowCreation(line4),
|
|
)
|
|
self.play(
|
|
ShowCreation(outer_angle_arc),
|
|
FadeIn(outer_label)
|
|
)
|
|
|
|
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
line5 = Line(outer_lake_center, outer_ls1_center, color = WHITE)
|
|
line6 = Line(outer_lake_center, outer_ls2_center, color = WHITE)
|
|
|
|
self.play(
|
|
ShowCreation(line5),
|
|
ShowCreation(line6)
|
|
)
|
|
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
FadeOut(line1),
|
|
FadeOut(line2),
|
|
FadeOut(line3),
|
|
FadeOut(line4),
|
|
FadeOut(line5),
|
|
FadeOut(line6),
|
|
FadeOut(inner_angle_arc),
|
|
FadeOut(outer_angle_arc),
|
|
FadeOut(inner_label),
|
|
FadeOut(outer_label),
|
|
)
|
|
|
|
|
|
self.wait()
|
|
|
|
inner_lines = VGroup()
|
|
inner_arcs = VGroup()
|
|
|
|
for i in range(-2,2):
|
|
|
|
theta = -TAU/4 + (i+0.5)*TAU/4
|
|
ls_point = inner_lake_center + inner_lake_radius * np.array([
|
|
np.cos(theta), np.sin(theta),0])
|
|
line = Line(inner_lake_center, ls_point, color = WHITE)
|
|
inner_lines.add(line)
|
|
|
|
arc = Arc(angle = TAU/4, start_angle = theta, radius = arc_radius,
|
|
stroke_color = ANGLE_COLOR1)
|
|
arc.move_arc_center_to(inner_lake_center)
|
|
inner_arcs.add(arc)
|
|
|
|
if i == 1:
|
|
arc.set_stroke(width = 0)
|
|
|
|
for line in inner_lines.submobjects:
|
|
self.play(
|
|
ShowCreation(line),
|
|
)
|
|
self.add_foreground_mobject(inner_lines)
|
|
for arc in inner_arcs.submobjects:
|
|
self.play(
|
|
ShowCreation(arc)
|
|
)
|
|
|
|
self.wait()
|
|
|
|
outer_lines = VGroup()
|
|
outer_arcs = VGroup()
|
|
|
|
for i in range(-2,2):
|
|
|
|
theta = -TAU/4 + (i+0.5)*TAU/4
|
|
|
|
ls_point = inner_lake_center + inner_lake_radius * np.array([
|
|
np.cos(theta), np.sin(theta),0])
|
|
line = Line(outer_lake_center, ls_point, color = WHITE)
|
|
outer_lines.add(line)
|
|
|
|
theta = -TAU/4 + (i+0.5)*TAU/8
|
|
arc = Arc(angle = TAU/8, start_angle = theta, radius = arc_radius,
|
|
stroke_color = ANGLE_COLOR2)
|
|
arc.move_arc_center_to(outer_lake_center)
|
|
outer_arcs.add(arc)
|
|
|
|
if i == 1:
|
|
arc.set_stroke(width = 0)
|
|
|
|
|
|
for line in outer_lines.submobjects:
|
|
self.play(
|
|
ShowCreation(line),
|
|
)
|
|
self.add_foreground_mobject(outer_lines)
|
|
for arc in outer_arcs.submobjects:
|
|
self.play(
|
|
ShowCreation(arc)
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
FadeOut(inner_lines),
|
|
FadeOut(inner_arcs)
|
|
)
|
|
|
|
|
|
outer_lines2 = VGroup()
|
|
|
|
for i in range(-2,2):
|
|
|
|
theta = -TAU/4 + (i+0.5)*TAU/8
|
|
ls_point = outer_lake_center + outer_lake_radius * np.array([
|
|
np.cos(theta), np.sin(theta),0])
|
|
line = Line(outer_lake_center, ls_point, color = WHITE)
|
|
outer_lines2.add(line)
|
|
|
|
self.play(
|
|
ShowCreation(outer_lines2),
|
|
)
|
|
|
|
self.wait()
|
|
|
|
outer_lines3 = outer_lines2.copy().rotate(TAU/2, about_point = outer_lake_center)
|
|
outer_arcs3 = outer_arcs.copy().rotate(TAU/2, about_point = outer_lake_center)
|
|
|
|
self.play(
|
|
ShowCreation(outer_lines3),
|
|
)
|
|
self.add_foreground_mobject(outer_lines3)
|
|
for arc in outer_arcs3.submobjects:
|
|
self.play(
|
|
ShowCreation(arc)
|
|
)
|
|
|
|
last_arc = outer_arcs3.submobjects[0].copy()
|
|
last_arc.rotate(-TAU/8, about_point = outer_lake_center)
|
|
last_arc2 = last_arc.copy()
|
|
last_arc2.rotate(TAU/2, about_point = outer_lake_center)
|
|
|
|
self.play(
|
|
ShowCreation(last_arc),
|
|
ShowCreation(last_arc2),
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
FadeOut(outer_lines2),
|
|
FadeOut(outer_lines3),
|
|
FadeOut(outer_arcs),
|
|
FadeOut(outer_arcs3),
|
|
FadeOut(last_arc),
|
|
FadeOut(last_arc2),
|
|
)
|
|
|
|
self.play(
|
|
FadeOut(inner_ls),
|
|
FadeOut(outer_ls),
|
|
)
|
|
|
|
|
|
self.wait()
|
|
|
|
|
|
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
|
|
|
|
STEP_RUN_TIME = 0.5
|
|
|
|
|
|
#self.force_skipping()
|
|
|
|
|
|
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
|
|
|
|
self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR)
|
|
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 = Randolph(color = MAROON_D).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 = 15)
|
|
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,self.obs_dot,self.ls0_dot, ls0.lighthouse)
|
|
self.add_foreground_mobject(morty)
|
|
self.add_foreground_mobject(self.obs_dot)
|
|
self.add_foreground_mobject(self.ls0_dot)
|
|
self.wait()
|
|
|
|
|
|
# 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)
|
|
|
|
self.play(
|
|
ShowCreation(arc_left),
|
|
Write(one_left),
|
|
ShowCreation(arc_right),
|
|
Write(one_right),
|
|
)
|
|
|
|
|
|
self.play(
|
|
SwitchOn(ls0.ambient_light),
|
|
lake0.set_stroke,{"color": LAKE_STROKE_COLOR, "width" : LAKE_STROKE_WIDTH},
|
|
)
|
|
|
|
self.play(FadeIn(indicator))
|
|
self.add_foreground_mobject(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(
|
|
ShowCreation(diameter),
|
|
Write(diameter_text),
|
|
#FadeOut(self.obs_dot),
|
|
FadeOut(self.ls0_dot)
|
|
)
|
|
|
|
indicator_reading = TexMobject("{1\over d^2}").scale(TEX_SCALE)
|
|
indicator_reading.move_to(indicator)
|
|
self.unzoomable_mobs.add(indicator_reading)
|
|
|
|
self.play(
|
|
FadeIn(indicator_reading)
|
|
)
|
|
self.add_foreground_mobject(indicator_reading)
|
|
|
|
# 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(
|
|
Transform(diameter_text,new_diameter_text)
|
|
)
|
|
|
|
# insert into indicator reading
|
|
new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE)
|
|
new_reading.move_to(indicator)
|
|
|
|
self.play(
|
|
Transform(indicator_reading,new_reading)
|
|
)
|
|
|
|
self.wait()
|
|
|
|
self.play(
|
|
FadeOut(one_left),
|
|
FadeOut(one_right),
|
|
FadeOut(diameter_text),
|
|
FadeOut(arc_left),
|
|
FadeOut(arc_right)
|
|
)
|
|
|
|
|
|
|
|
|
|
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.all(np.abs(ls_old_loc[:2]) < 10 * 2**3)
|
|
onscreen_1 = np.all(np.abs(ls_new_loc1[:2]) < 10 * 2**3)
|
|
onscreen_2 = np.all(np.abs(ls_new_loc2[:2]) < 10 * 2**3)
|
|
show_animation = (onscreen_old or onscreen_1 or onscreen_2)
|
|
|
|
if show_animation or animate:
|
|
print "animating (", i, ",", step, ")"
|
|
self.play(
|
|
ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time),
|
|
ApplyMethod(ls2.move_source_to,ls_new_loc2, 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 = []
|
|
|
|
# WE ALWAYS USE THIS CASE BRANCH
|
|
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()
|
|
|
|
|
|
|
|
# WE DON'T USE THIS CASE BRANCH ANYMORE
|
|
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, run_time = STEP_RUN_TIME)
|
|
|
|
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, run_time = STEP_RUN_TIME)
|
|
indicator_wiggle()
|
|
#self.play(FadeOut(self.ls0_dot))
|
|
zoom_out_scene(2)
|
|
|
|
|
|
construction_step(2, run_time = STEP_RUN_TIME)
|
|
indicator_wiggle()
|
|
self.play(FadeOut(self.ls0_dot))
|
|
|
|
|
|
|
|
|
|
self.play(
|
|
FadeOut(self.altitudes),
|
|
FadeOut(self.hypotenuses),
|
|
FadeOut(self.legs)
|
|
)
|
|
|
|
max_it = 10
|
|
scale = 2**(max_it - 5)
|
|
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)
|
|
|
|
|
|
#print "starting simultaneous expansion"
|
|
|
|
# simultaneous expansion of light sources from now on
|
|
self.play(FadeOut(self.inner_lake))
|
|
|
|
for n in range(3,max_it + 1):
|
|
print "working on n = ", n, "..."
|
|
new_lake = self.outer_lake.copy().scale(2,about_point = self.obs_dot.get_center())
|
|
for (i,ls) in enumerate(self.light_sources_array[:2**n]):
|
|
#print i
|
|
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()
|
|
|
|
self.play(Transform(self.outer_lake,new_lake))
|
|
shift_list = []
|
|
|
|
for i in range(2**n):
|
|
#print "==========="
|
|
#print i
|
|
theta = -TAU/4 + (i + 0.5) * TAU / 2**(n+1)
|
|
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
|
|
ls1 = self.light_sources.submobjects[i]
|
|
ls2 = self.light_sources.submobjects[i+2**n]
|
|
|
|
ls_old_loc = np.array(ls1.get_source_point())
|
|
onscreen_old = np.all(np.abs(ls_old_loc[:2]) < 10 * 2**2)
|
|
onscreen_1 = np.all(np.abs(pos1[:2]) < 10 * 2**2)
|
|
onscreen_2 = np.all(np.abs(pos2[:2]) < 10 * 2**2)
|
|
|
|
if onscreen_old or onscreen_1:
|
|
print "anim1 for step", n, "part", i
|
|
print "------------------ moving from", ls_old_loc[:2], "to", pos1[:2]
|
|
shift_list.append(ApplyMethod(ls1.move_source_to, pos1, run_time = STEP_RUN_TIME))
|
|
else:
|
|
ls1.move_source_to(pos1)
|
|
if onscreen_old or onscreen_2:
|
|
print "anim2 for step", n, "part", i
|
|
print "------------------ moving from", ls_old_loc[:2], "to", pos2[:2]
|
|
shift_list.append(ApplyMethod(ls2.move_source_to, pos2, run_time = STEP_RUN_TIME))
|
|
else:
|
|
ls2.move_source_to(pos2)
|
|
|
|
|
|
#print shift_list
|
|
|
|
self.play(*shift_list)
|
|
print "...done"
|
|
|
|
|
|
#self.revert_to_original_skipping_status()
|
|
|
|
# Now create a straight number line and transform into it
|
|
MAX_N = 7
|
|
|
|
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 = [],
|
|
numbers_to_show = range(-MAX_N,MAX_N + 1),#,2),
|
|
unit_size = LAKE0_RADIUS * TAU/4 / 2 * scale,
|
|
tick_frequency = 1,
|
|
tick_size = LAKE_STROKE_WIDTH,
|
|
number_scale_val = 3,
|
|
line_to_number_buff = LARGE_BUFF,
|
|
label_direction = UP,
|
|
).shift(origin_point - self.number_line.number_to_point(0)) # .shift(scale * 2.5 * DOWN)
|
|
|
|
print "scale ", scale
|
|
print "number line at", self.number_line.get_center()
|
|
print "should be at", origin_point, "or", OBSERVER_POINT
|
|
|
|
self.number_line.tick_marks.fade(1)
|
|
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)
|
|
for ls in self.light_sources_array:
|
|
self.remove(ls)
|
|
|
|
self.outer_lake.rotate(TAU/8)
|
|
|
|
# open sea
|
|
open_sea = Rectangle(
|
|
width = 200 * scale,
|
|
height = 100 * scale,
|
|
stroke_width = LAKE_STROKE_WIDTH,
|
|
stroke_color = LAKE_STROKE_COLOR,
|
|
fill_color = LAKE_COLOR,
|
|
fill_opacity = LAKE_OPACITY,
|
|
).flip().next_to(self.obs_dot.get_center(),UP,buff = 0)
|
|
|
|
self.revert_to_original_skipping_status()
|
|
|
|
self.play(
|
|
ReplacementTransform(pond_sources,nl_sources),
|
|
#FadeOut(pond_sources),
|
|
#FadeIn(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 + 2 * UP,
|
|
indicator_reading.move_to, origin_point + scale * UP + 2 * UP,
|
|
FadeOut(open_sea),
|
|
FadeOut(morty),
|
|
FadeIn(self.number_line_labels),
|
|
FadeIn(self.number_line.tick_marks),
|
|
)
|
|
|
|
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))
|
|
|
|
self.wait()
|
|
|
|
for ls in nl_sources.submobjects:
|
|
if ls.get_source_point()[0] < 0:
|
|
self.remove_foreground_mobject(ls.ambient_light)
|
|
self.remove(ls.ambient_light)
|
|
else:
|
|
self.add_foreground_mobject(ls.ambient_light)
|
|
|
|
for label in self.number_line_labels.submobjects:
|
|
if label.get_center()[0] <= 0:
|
|
self.remove(label)
|
|
|
|
|
|
|
|
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_D).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 WaitScene(TeacherStudentsScene):
|
|
|
|
def construct(self):
|
|
|
|
|
|
self.teacher_says(TexMobject("{1\over 1^2}+{1\over 3^2}+{1\over 5^2}+{1\over 7^2}+\dots = {\pi^2 \over 8}!"))
|
|
|
|
student_q = TextMobject("What about")
|
|
full_sum = TexMobject("{1\over 1^2}+{1\over 2^2}+{1\over 3^2}+{1\over 4^2}+\dots?")
|
|
full_sum.next_to(student_q,RIGHT)
|
|
student_q.add(full_sum)
|
|
|
|
|
|
self.student_says(student_q, target_mode = "angry")
|
|
|
|
|
|
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 = 1.3
|
|
|
|
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 ArcHighlightOverlayScene(Scene):
|
|
|
|
def construct(self):
|
|
|
|
BASELINE_YPOS = -2.5
|
|
OBSERVER_POINT = [0,BASELINE_YPOS,0]
|
|
LAKE0_RADIUS = 1.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 = 0.25
|
|
|
|
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),
|
|
)
|
|
|
|
|
|
|
|
class ThumbnailScene(Scene):
|
|
|
|
def construct(self):
|
|
|
|
equation = TexMobject("1+{1\over 4}+{1\over 9}+{1\over 16}+{1\over 25}+\dots")
|
|
equation.scale(1.5)
|
|
equation.move_to(1.5 * UP)
|
|
q_mark = TexMobject("=?", color = LIGHT_COLOR).scale(5)
|
|
q_mark.next_to(equation, DOWN, buff = 1.5)
|
|
#equation.move_to(2 * UP)
|
|
#q_mark = TexMobject("={\pi^2\over 6}", color = LIGHT_COLOR).scale(3)
|
|
#q_mark.next_to(equation, DOWN, buff = 1)
|
|
|
|
lake_radius = 6
|
|
lake_center = ORIGIN
|
|
op_scale = 0.4
|
|
|
|
lake = Circle(
|
|
fill_color = LAKE_COLOR,
|
|
fill_opacity = LAKE_OPACITY,
|
|
radius = lake_radius,
|
|
stroke_color = LAKE_STROKE_COLOR,
|
|
stroke_width = LAKE_STROKE_WIDTH,
|
|
)
|
|
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(
|
|
radius = 15.0,
|
|
num_levels = 150,
|
|
max_opacity_ambient = 1.0,
|
|
opacity_function = inverse_quadratic(1,op_scale,1)
|
|
)
|
|
ls.move_source_to(pos)
|
|
lake.add(ls.ambient_light, ls.lighthouse)
|
|
|
|
self.add(lake)
|
|
|
|
self.add(equation, q_mark)
|
|
|
|
self.wait()
|
|
|
|
|
|
|
|
|
|
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 RightAnglesOverlay(Scene):
|
|
|
|
def construct(self):
|
|
|
|
BASELINE_YPOS = -2.5
|
|
OBSERVER_POINT = [0,BASELINE_YPOS,0]
|
|
LAKE0_RADIUS = 1.5 * 2
|
|
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
|
|
|
|
RIGHT_ANGLE_SIZE = 0.3
|
|
|
|
|
|
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
|
|
|
|
|
|
lake_center = OBSERVER_POINT + LAKE0_RADIUS * UP
|
|
points = []
|
|
lines = VGroup()
|
|
for i in range(4):
|
|
theta = -TAU/4 + (i+0.5)*TAU/4
|
|
v = np.array([np.cos(theta), np.sin(theta), 0])
|
|
P = lake_center + LAKE0_RADIUS * v
|
|
points.append(P)
|
|
lines.add(Line(lake_center, P, stroke_width = 8))
|
|
|
|
self.play(FadeIn(lines))
|
|
|
|
self.wait()
|
|
|
|
for i in range(4):
|
|
sign = right_angle(points[i-1], lake_center, points[i],RIGHT_ANGLE_SIZE)
|
|
self.play(FadeIn(sign))
|
|
self.play(FadeOut(sign))
|
|
|
|
self.wait()
|
|
|
|
self.play(FadeOut(lines))
|
|
|
|
flash_arcs(3)
|