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