#!/usr/bin/env python


from big_ol_pile_of_manim_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_CONES = 7 # in first lighthouse scene
NUM_VISIBLE_CONES = 5 # ibidem
ARC_TIP_LENGTH = 0.2

NUM_LEVELS = 15
AMBIENT_FULL = 0.8
AMBIENT_DIMMED = 0.5
AMBIENT_SCALE = 2.0
AMBIENT_RADIUS = 20.0
SPOTLIGHT_FULL = 0.8
SPOTLIGHT_DIMMED = 0.2
SPOTLIGHT_SCALE = 1.0
SPOTLIGHT_RADIUS = 20.0

LIGHT_COLOR = YELLOW
DEGREES = TAU/360


BASELINE_YPOS = -2.5
OBSERVER_POINT = np.array([0,BASELINE_YPOS,0])
LAKE0_RADIUS = 1.5
INDICATOR_RADIUS = 0.6
TICK_SIZE = 0.5
LIGHTHOUSE_HEIGHT = 0.5
LAKE_COLOR = BLUE
LAKE_OPACITY = 0.15
LAKE_STROKE_WIDTH = 5.0
LAKE_STROKE_COLOR = BLUE
TEX_SCALE = 0.8
DOT_COLOR = BLUE

LIGHT_MAX_INT = 1
LIGHT_SCALE = 2.5
LIGHT_CUTOFF = 1

RIGHT_ANGLE_SIZE = 0.3

inverse_power_law = lambda maxint,scale,cutoff,exponent: \
    (lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent)
inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2)


A = np.array([5.,-3.,0.])
B = np.array([-5.,3.,0.])
C = np.array([-5.,-3.,0.])
xA = A[0]
yA = A[1]
xB = B[0]
yB = B[1]
xC = C[0]
yC = C[1]

# find the coords of the altitude point H
# as the solution of a certain LSE
prelim_matrix = np.array([[yA - yB, xB - xA], [xA - xB, yA - yB]]) # sic
prelim_vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)])
H2 = np.linalg.solve(prelim_matrix,prelim_vector)
H = np.append(H2, 0.)




class AngleUpdater(ContinualAnimation):
    def __init__(self, angle_arc, spotlight, **kwargs):
        self.angle_arc = angle_arc

        self.spotlight = spotlight
        ContinualAnimation.__init__(self, self.angle_arc, **kwargs)

    def update_mobject(self, dt):
        new_arc = self.angle_arc.copy().set_bound_angles(
            start = self.spotlight.start_angle(),
            stop = self.spotlight.stop_angle()
        )
        new_arc.generate_points()
        new_arc.move_arc_center_to(self.spotlight.get_source_point())
        self.angle_arc.points = new_arc.points
        self.angle_arc.add_tip(tip_length = ARC_TIP_LENGTH,
            at_start = True, at_end = True)





class LightIndicator(VMobject):
    CONFIG = {
        "radius": 0.5,
        "intensity": 0,
        "opacity_for_unit_intensity": 1,
        "precision": 3,
        "show_reading": True,
        "measurement_point": ORIGIN,
        "light_source": None
    }

    def generate_points(self):
        self.background = Circle(color=BLACK, radius = self.radius)
        self.background.set_fill(opacity=1.0)
        self.foreground = Circle(color=self.color, radius = self.radius)
        self.foreground.set_stroke(color=INDICATOR_STROKE_COLOR,width=INDICATOR_STROKE_WIDTH)

        self.add(self.background, self.foreground)
        self.reading = DecimalNumber(self.intensity,num_decimal_places = self.precision)
        self.reading.set_fill(color=INDICATOR_TEXT_COLOR)
        self.reading.move_to(self.get_center())
        if self.show_reading:
            self.add(self.reading)

    def set_intensity(self, new_int):
        self.intensity = new_int
        new_opacity = min(1, new_int * self.opacity_for_unit_intensity)
        self.foreground.set_fill(opacity=new_opacity)
        ChangeDecimalToValue(self.reading, new_int).update(1)
        return self

    def get_measurement_point(self):
        if self.measurement_point != None:
            return self.measurement_point
        else:
            return self.get_center()


    def measured_intensity(self):
        distance = 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.change_opacity_function(new_of)

                # spotlight_of = copy_func(submob.ambient_light.opacity_function)
                # new_of = lambda r: spotlight_of(r / factor)
                # submob.spotlight.change_opacity_function(new_of)

                new_r = factor * submob.radius
                submob.set_radius(new_r)

                new_r = factor * submob.ambient_light.radius
                submob.ambient_light.radius = new_r

                new_r = factor * submob.spotlight.radius
                submob.spotlight.radius = new_r

                submob.ambient_light.scale_about_point(factor, new_sp.get_center())
                submob.spotlight.scale_about_point(factor, new_sp.get_center())


        Transform.__init__(self,light_sources_mob,ls_target,**kwargs)


















class IntroScene(PiCreatureScene):

    CONFIG = {
        "rect_height" : 0.2,
        "duration" : 0.5,
        "eq_spacing" : 6 * MED_LARGE_BUFF
    }

    def construct(self):

        randy = self.get_primary_pi_creature()
        randy.scale(0.7).to_corner(DOWN+RIGHT)

        self.build_up_euler_sum()
        self.build_up_sum_on_number_line()
        self.show_pi_answer()
        self.other_pi_formulas()
        self.refocus_on_euler_sum()





    def build_up_euler_sum(self):

        self.euler_sum = TexMobject(
           "1", "+", 
           "{1 \\over 4}", "+",
           "{1 \\over 9}", "+",
           "{1 \\over 16}", "+",
           "{1 \\over 25}", "+",
           "\\cdots", "=",
            arg_separator = " \\, "
        )

        self.euler_sum.to_edge(UP)
        self.euler_sum.shift(2*LEFT)
       
        terms = [1./n**2 for n in range(1,6)]
        partial_results_values = np.cumsum(terms)

        self.play(
               FadeIn(self.euler_sum[0], run_time = self.duration)
        )

        equals_sign = self.euler_sum.get_part_by_tex("=")

        self.partial_sum_decimal = DecimalNumber(partial_results_values[1],
                num_decimal_places = 2)
        self.partial_sum_decimal.next_to(equals_sign, RIGHT)



        for i in range(4):

            FadeIn(self.partial_sum_decimal, run_time = self.duration)

            if i == 0:

                self.play(
                    FadeIn(self.euler_sum[1], run_time = self.duration),
                    FadeIn(self.euler_sum[2], run_time = self.duration),
                    FadeIn(equals_sign, run_time = self.duration),
                    FadeIn(self.partial_sum_decimal, run_time = self.duration)
                )

            else:
                self.play(
                    FadeIn(self.euler_sum[2*i+1], run_time = self.duration),
                    FadeIn(self.euler_sum[2*i+2], run_time = self.duration),
                    ChangeDecimalToValue(
                        self.partial_sum_decimal,
                        partial_results_values[i+1], 
                        run_time = self.duration,
                        num_decimal_places = 6,
                        show_ellipsis = True,
                        position_update_func = lambda m: m.next_to(equals_sign, RIGHT)
                    )
                )
                
            self.wait()

        self.q_marks = TextMobject("???").set_color(LIGHT_COLOR)
        self.q_marks.move_to(self.partial_sum_decimal)

        self.play(
            FadeIn(self.euler_sum[-3], run_time = self.duration), # +
            FadeIn(self.euler_sum[-2], run_time = self.duration), # ...
            ReplacementTransform(self.partial_sum_decimal, self.q_marks)
        )

        self.wait()



    def build_up_sum_on_number_line(self):

        self.number_line = NumberLine(
            x_min = 0,
            color = WHITE,
            number_at_center = 1,
            stroke_width = 1,
            numbers_with_elongated_ticks = [0,1,2,3],
            numbers_to_show = np.arange(0,5),
            unit_size = 5,
            tick_frequency = 0.2,
            line_to_number_buff = MED_LARGE_BUFF
        ).shift(LEFT)

        self.number_line_labels = self.number_line.get_number_mobjects()
        self.play(
            FadeIn(self.number_line),
            FadeIn(self.number_line_labels)
        )
        self.wait()

        # create slabs for series terms

        max_n1 = 10
        max_n2 = 100

        terms = [0] + [1./(n**2) for n in range(1, max_n2 + 1)]
        series_terms = np.cumsum(terms)
        lines = VGroup()
        self.rects = VGroup()
        slab_colors = [YELLOW, BLUE] * (max_n2 / 2)

        for t1, t2, color in zip(series_terms, series_terms[1:], slab_colors):
            line = Line(*list(map(self.number_line.number_to_point, [t1, t2])))
            rect = Rectangle()
            rect.stroke_width = 0
            rect.fill_opacity = 1
            rect.set_color(color)
            rect.stretch_to_fit_height(
                self.rect_height,
            )
            rect.stretch_to_fit_width(0.5 * line.get_width())
            rect.move_to(line)

            self.rects.add(rect)
            lines.add(line)

        #self.rects.set_colors_by_radial_gradient(ORIGIN, 5, YELLOW, BLUE)
        
        self.little_euler_terms = VGroup()
        for i in range(1,7):
            if i == 1:
                term = TexMobject("1", fill_color = slab_colors[i-1])
            else:
                term = TexMobject("{1\over " + str(i**2) + "}", fill_color = slab_colors[i-1])
            term.scale(0.4)
            self.little_euler_terms.add(term)


        for i in range(5):
            self.play(
                GrowFromPoint(self.rects[i], self.euler_sum[2*i].get_center(),
                    run_time = 1)
            )
            term = self.little_euler_terms.submobjects[i]
            term.next_to(self.rects[i], UP)
            self.play(FadeIn(term))

        self.ellipsis = TexMobject("\cdots")
        self.ellipsis.scale(0.4)
        for i in range(5, max_n1):
            
            if i == 5:
                self.ellipsis.next_to(self.rects[i+3], UP)
                self.play(
                    FadeIn(self.ellipsis),
                    GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(),
                    run_time = 0.5)
                )
            else:
                self.play(
                    GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(),
                    run_time = 0.5)
                )
        for i in range(max_n1, max_n2):
            self.play(
                    GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(),
                    run_time = 0.01)
                )

        self.wait()

        PI = TAU/2
        P = self.q_marks.get_center() + 0.5 * DOWN + 0.5 * LEFT
        Q = self.rects[-1].get_center() + 0.2 * UP
        self.arrow = CurvedArrow(P, Q,
            angle = TAU/12,
            color = YELLOW
        )

        self.play(FadeIn(self.arrow))

        self.wait()


    def show_pi_answer(self):

        self.pi_answer = TexMobject("{\\pi^2 \\over 6}").set_color(YELLOW)
        self.pi_answer.move_to(self.partial_sum_decimal)
        self.pi_answer.next_to(self.euler_sum[-1], RIGHT, buff = 1,
            submobject_to_align = self.pi_answer[-2])
        self.play(ReplacementTransform(self.q_marks, self.pi_answer))

        self.wait()


    def other_pi_formulas(self):

        self.play(
            FadeOut(self.rects),
            FadeOut(self.number_line_labels),
            FadeOut(self.number_line),
            FadeOut(self.little_euler_terms),
            FadeOut(self.ellipsis),
            FadeOut(self.arrow)
        )

        self.leibniz_sum = TexMobject(
            "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots",
            "=", "\quad\,\,{\\pi \\over 4}", arg_separator = " \\, ")

        self.wallis_product = TexMobject(
            "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" +
             "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots",
             "=", "\quad\,\, {\\pi \\over 2}", arg_separator = " \\, ")

        self.leibniz_sum.next_to(self.euler_sum.get_part_by_tex("="), DOWN,
            buff = 2,
            submobject_to_align = self.leibniz_sum.get_part_by_tex("=")
        )

        self.wallis_product.next_to(self.leibniz_sum.get_part_by_tex("="), DOWN,
            buff = 2,
            submobject_to_align = self.wallis_product.get_part_by_tex("=")
        )


        self.play(
            Write(self.leibniz_sum)
        )
        self.play(
            Write(self.wallis_product)
        )



    def refocus_on_euler_sum(self):

        self.euler_sum.add(self.pi_answer)

        self.play(
            FadeOut(self.leibniz_sum),
            FadeOut(self.wallis_product),
            ApplyMethod(self.euler_sum.shift,
                ORIGIN + 2*UP - self.euler_sum.get_center())
        )

        # focus on pi squared
        pi_squared = self.euler_sum.get_part_by_tex("\\pi")[-3]
        self.play(
            WiggleOutThenIn(pi_squared,
                scale_value = 4,
                angle = 0.003 * TAU,
                run_time = 2
            )
        )



        # Morty thinks of a circle

        q_circle = Circle(
            stroke_color = YELLOW,
            fill_color = YELLOW,
            fill_opacity = 0.25,
            radius = 0.5, 
            stroke_width = 3.0
        )
        q_mark = TexMobject("?")
        q_mark.next_to(q_circle)

        thought = Group(q_circle, q_mark)
        q_mark.set_height(0.6 * q_circle.get_height())

        self.look_at(pi_squared)
        self.pi_creature_thinks(thought,target_mode = "confused",
            bubble_kwargs = { "height" : 2.5, "width" : 5 })
        self.look_at(pi_squared)

        self.wait()




















class FirstLighthouseScene(PiCreatureScene):

    def construct(self):
        self.remove(self.get_primary_pi_creature())
        self.show_lighthouses_on_number_line()



    def show_lighthouses_on_number_line(self):

        self.number_line = NumberLine(
            x_min = 0,
            color = WHITE,
            number_at_center = 1.6,
            stroke_width = 1,
            numbers_with_elongated_ticks = list(range(1,5)),
            numbers_to_show = list(range(1,5)),
            unit_size = 2,
            tick_frequency = 0.2,
            line_to_number_buff = LARGE_BUFF,
            label_direction = UP,
        )

        self.number_line.label_direction = DOWN

        self.number_line_labels = self.number_line.get_number_mobjects()
        self.add(self.number_line,self.number_line_labels)
        self.wait()

        origin_point = self.number_line.number_to_point(0)

        self.default_pi_creature_class = Randolph
        randy = self.get_primary_pi_creature()

        randy.scale(0.5)
        randy.flip()
        right_pupil = randy.pupils[1]
        randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil)



        light_indicator = LightIndicator(radius = INDICATOR_RADIUS,
            opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY,
            color = LIGHT_COLOR)
        light_indicator.reading.scale(0.8)

        bubble = ThoughtBubble(direction = RIGHT,
                            width = 2.5, height = 3.5)
        bubble.next_to(randy,LEFT+UP)
        bubble.add_content(light_indicator)
        self.wait()
        self.play(
            randy.change, "wave_2",
            ShowCreation(bubble),
            FadeIn(light_indicator)
        )

        light_sources = []


        euler_sum_above = TexMobject("1", "+", "{1\over 4}", 
            "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "{1\over 36}")

        for (i,term) in zip(list(range(len(euler_sum_above))),euler_sum_above):
            #horizontal alignment with tick marks
            term.next_to(self.number_line.number_to_point(0.5*i+1),UP,buff = 2)
            # vertical alignment with light indicator
            old_y = term.get_center()[1]
            new_y = light_indicator.get_center()[1]
            term.shift([0,new_y - old_y,0])
            


        for i in range(1,NUM_CONES+1):
            light_source = LightSource(
                opacity_function = inverse_quadratic(1,AMBIENT_SCALE,1),
                num_levels = NUM_LEVELS,
                radius = AMBIENT_RADIUS,
            )
            point = self.number_line.number_to_point(i)
            light_source.move_source_to(point)
            light_sources.append(light_source)

        self.wait()
        for ls in light_sources:
            self.add_foreground_mobject(ls.lighthouse)

        light_indicator.set_intensity(0)

        intensities = np.cumsum(np.array([1./n**2 for n in range(1,NUM_CONES+1)]))
        opacities = intensities * light_indicator.opacity_for_unit_intensity

        self.remove_foreground_mobjects(light_indicator)


        # slowly switch on visible light cones and increment indicator
        for (i,light_source) in zip(list(range(NUM_VISIBLE_CONES)),light_sources[:NUM_VISIBLE_CONES]):
            indicator_start_time = 1.0 * (i+1) * SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size
            indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME
            indicator_rate_func = squish_rate_func(
                smooth,indicator_start_time,indicator_stop_time)
            self.play(
                SwitchOn(light_source.ambient_light),
                FadeIn(euler_sum_above[2*i], run_time = SWITCH_ON_RUN_TIME,
                    rate_func = indicator_rate_func),
                FadeIn(euler_sum_above[2*i - 1], run_time = SWITCH_ON_RUN_TIME,
                    rate_func = indicator_rate_func),
                # this last line *technically* fades in the last term, but it is off-screen
                ChangeDecimalToValue(light_indicator.reading,intensities[i],
                    rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME),
                ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i],
                    rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME)
            )

            if i == 0:
                self.wait()
                # move a copy out of the thought bubble for comparison
                light_indicator_copy = light_indicator.copy()
                old_y = light_indicator_copy.get_center()[1]
                new_y = self.number_line.get_center()[1]
                self.play(
                    light_indicator_copy.shift,[0, new_y - old_y,0]
                )

            self.wait()

        self.wait()

        # quickly switch on off-screen light cones and increment indicator
        for (i,light_source) in zip(list(range(NUM_VISIBLE_CONES,NUM_CONES)),light_sources[NUM_VISIBLE_CONES:NUM_CONES]):
            indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size
            indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME
            indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9)
                smooth,indicator_start_time,indicator_stop_time)
            self.play(
                SwitchOn(light_source.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME),
                ChangeDecimalToValue(light_indicator.reading,intensities[i-1],
                    rate_func = indicator_rate_func, run_time = FAST_SWITCH_ON_RUN_TIME),
                ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i-1])
            )


        # show limit value in light indicator and an equals sign
        limit_reading = TexMobject("{\pi^2 \over 6}")
        limit_reading.move_to(light_indicator.reading)

        equals_sign = TexMobject("=")
        equals_sign.next_to(randy, UP)
        old_y = equals_sign.get_center()[1]
        new_y = euler_sum_above.get_center()[1]
        equals_sign.shift([0,new_y - old_y,0])

        self.play(
            FadeOut(light_indicator.reading),
            FadeIn(limit_reading),
            FadeIn(equals_sign),
        )

            

        self.wait()

        





















class SingleLighthouseScene(PiCreatureScene):

    def construct(self):

        self.setup_elements()
        self.setup_angle() # spotlight and angle msmt change when screen rotates
        self.rotate_screen()
        #self.morph_lighthouse_into_sun()


    def setup_elements(self):

        self.remove(self.get_primary_pi_creature())

        SCREEN_SIZE = 3.0
        DISTANCE_FROM_LIGHTHOUSE = 10.0
        source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0]
        observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0]

        # Light source

        self.light_source = LightSource(
            opacity_function = inverse_quadratic(1,SPOTLIGHT_SCALE,1),
            num_levels = NUM_LEVELS,
            radius = 10,
            max_opacity_ambient = AMBIENT_FULL,
            max_opacity_spotlight = SPOTLIGHT_FULL,

        )

        self.light_source.move_source_to(source_point)


        # Pi Creature

        morty = self.get_primary_pi_creature()
        morty.scale(0.5)
        morty.move_to(observer_point)
        morty.shift(2*OUT)
        self.add_foreground_mobject(morty)

        self.add(self.light_source.lighthouse)

        self.play(
            SwitchOn(self.light_source.ambient_light)
        )

        # Screen

        self.screen = Rectangle(
            width = 0.06,
            height = 2,
            mark_paths_closed = True,
            fill_color = WHITE,
            fill_opacity = 1.0,
            stroke_width = 0.0
        )

        self.screen.rotate(-TAU/6)
        self.screen.next_to(morty,LEFT)

        self.light_source.set_screen(self.screen)

        # Animations

        self.play(FadeIn(self.screen))

        #self.light_source.set_max_opacity_spotlight(0.001)
        #self.play(SwitchOn(self.light_source.spotlight))


        self.wait()




        # just calling .dim_ambient via ApplyMethod does not work, why?
        dimmed_ambient_light = self.light_source.ambient_light.deepcopy()
        dimmed_ambient_light.dimming(AMBIENT_DIMMED)
        self.light_source.update_shadow()

        self.play(
            FadeIn(self.light_source.shadow),
        )
        self.add_foreground_mobject(self.light_source.shadow)
        self.add_foreground_mobject(morty)

        self.play(
            self.light_source.dim_ambient,
            #Transform(self.light_source.ambient_light,dimmed_ambient_light),
            #self.light_source.set_max_opacity_spotlight,1.0,
        )
        self.play(
            FadeIn(self.light_source.spotlight)
        )

        self.screen_tracker = ScreenTracker(self.light_source)
        self.add(self.screen_tracker)

        self.wait()




    def setup_angle(self):

        self.wait()

        
        pointing_screen_at_source = Rotate(self.screen,TAU/6)
        self.play(pointing_screen_at_source)

        # angle msmt (arc)

        arc_angle = self.light_source.spotlight.opening_angle()
        # draw arc arrows to show the opening angle
        self.angle_arc = Arc(radius = 5, start_angle = self.light_source.spotlight.start_angle(),
            angle = self.light_source.spotlight.opening_angle(), tip_length = ARC_TIP_LENGTH)
        #angle_arc.add_tip(at_start = True, at_end = True)
        self.angle_arc.move_arc_center_to(self.light_source.get_source_point())
        

        # angle msmt (decimal number)

        self.angle_indicator = DecimalNumber(arc_angle / DEGREES,
            num_decimal_places = 0,
            unit = "^\\circ",
            fill_opacity = 1.0,
            fill_color = WHITE)
        self.angle_indicator.next_to(self.angle_arc,RIGHT)

        angle_update_func = lambda x: self.light_source.spotlight.opening_angle() / DEGREES
        self.angle_indicator.add_updater(
            lambda d: d.set_value(angle_update_func())
        )
        self.add(self.angle_indicator)

        ca2 = AngleUpdater(self.angle_arc, self.light_source.spotlight)
        self.add(ca2)

        self.play(
            ShowCreation(self.angle_arc),
            ShowCreation(self.angle_indicator)
        )

        self.wait()

    def rotate_screen(self):



        self.play(Rotate(self.light_source.spotlight.screen, TAU/8))
        self.play(Rotate(self.light_source.spotlight.screen, -TAU/4))

        self.play(Rotate(self.light_source.spotlight.screen, TAU/8))

        self.wait()

        self.play(Rotate(self.light_source.spotlight.screen, -TAU/4))

        self.wait()

        self.play(Rotate(self.light_source.spotlight.screen, TAU/4))

### The following is supposed to morph the scene into the Earth scene,
### but it doesn't work


class MorphIntoSunScene(PiCreatureScene):
    def construct(self):
        self.setup_elements()
        self.morph_lighthouse_into_sun()

    def setup_elements(self):
        self.remove(self.get_primary_pi_creature())

        SCREEN_SIZE = 3.0
        DISTANCE_FROM_LIGHTHOUSE = 10.0
        source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0]
        observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0]

        # Light source

        self.light_source = LightSource(
            opacity_function = inverse_quadratic(1,SPOTLIGHT_SCALE,1),
            num_levels = NUM_LEVELS,
            radius = 10,
            max_opacity_ambient = AMBIENT_FULL,
            max_opacity_spotlight = SPOTLIGHT_FULL,

        )

        self.light_source.move_source_to(source_point)


        # Pi Creature

        morty = self.get_primary_pi_creature()
        morty.scale(0.5)
        morty.move_to(observer_point)
        morty.shift(2*OUT)
        self.add_foreground_mobject(morty)

        self.add(self.light_source.lighthouse,self.light_source.ambient_light)
        

        # Screen

        self.screen = Rectangle(
            width = 0.06,
            height = 2,
            mark_paths_closed = True,
            fill_color = WHITE,
            fill_opacity = 1.0,
            stroke_width = 0.0
        )

        self.screen.next_to(morty,LEFT)

        self.light_source.set_screen(self.screen)
        self.add(self.screen,self.light_source.shadow)
        
        self.add_foreground_mobject(self.light_source.shadow)
        self.add_foreground_mobject(morty)
        self.light_source.dim_ambient
        self.add(self.light_source.spotlight)
        self.screen_tracker = ScreenTracker(self.light_source)
        self.add(self.screen_tracker)

        self.wait()


    def morph_lighthouse_into_sun(self):

        sun_position = np.array([-100,0,0])




        # Why does none of this change the opacity function???

        self.sun = self.light_source.copy()

        self.sun.change_spotlight_opacity_function(lambda r: 0.1)
        # self.sun.spotlight.opacity_function = lambda r: 0.1
        # for submob in self.sun.spotlight.submobjects:
        #     submob.set_fill(opacity = 0.1)

        #self.sun.move_source_to(sun_position)
        #self.sun.set_radius(120)

        self.sun.spotlight.generate_points()

        self.wait()

        self.play(
             Transform(self.light_source,self.sun)
        )

        self.wait()




 














class EarthScene(Scene):

    def construct(self):

        SCREEN_THICKNESS = 10

        self.screen_height = 2.0
        self.brightness_rect_height = 1.0

        # screen
        self.screen = VMobject(stroke_color = WHITE, stroke_width = SCREEN_THICKNESS)
        self.screen.set_points_as_corners([
            [0,-self.screen_height/2,0],
            [0,self.screen_height/2,0]
        ])

        # Earth

        earth_center_x = 2
        earth_center = [earth_center_x,0,0]
        earth_radius = 3
        earth = Circle(radius = earth_radius)
        earth.add(self.screen)
        earth.move_to(earth_center)
        #self.remove(self.screen_tracker)

        theta0 = 70 * DEGREES
        dtheta = 10 * DEGREES
        theta1 = theta0 + dtheta
        theta = (theta0 + theta1)/2

        self.add_foreground_mobject(self.screen)

        # background Earth
        background_earth = SVGMobject(
            file_name = "earth",
            width = 2 * earth_radius,
            fill_color = BLUE,
        )
        background_earth.move_to(earth_center)
        # Morty

        morty = Mortimer().scale(0.5).next_to(self.screen, RIGHT, buff = 1.5)
        self.add_foreground_mobject(morty)


        # Light source (far-away Sun)

        sun_position = [-100,0,0]

        self.sun = LightSource(
            opacity_function = lambda r : 0.5,
            max_opacity_ambient = 0,
            max_opacity_spotlight = 0.5,
            num_levels = NUM_LEVELS,
            radius = 150,
            screen = self.screen
        )

        self.sun.move_source_to(sun_position)


        # Add elements to scene

        self.add(self.sun,self.screen)
        self.bring_to_back(self.sun.shadow)
        screen_tracker = ScreenTracker(self.sun)

        self.add(screen_tracker)
        
        self.wait()

        self.play(
            FadeIn(earth),
            FadeIn(background_earth)
        )
        self.add_foreground_mobject(earth)
        self.add_foreground_mobject(self.screen)


        # move screen onto Earth
        screen_on_earth = self.screen.deepcopy()
        screen_on_earth.rotate(-theta)
        screen_on_earth.scale(0.3)
        screen_on_earth.move_to(np.array([
            earth_center_x - earth_radius * np.cos(theta),
            earth_radius * np.sin(theta),
            0]))

        polar_morty = morty.copy().scale(0.5).next_to(screen_on_earth,DOWN,buff = 0.5)
        polar_morty.set_color(BLUE_C)

        self.play(
            Transform(self.screen, screen_on_earth),
            Transform(morty,polar_morty)
        )

        self.wait()


        tropical_morty = polar_morty.copy()
        tropical_morty.move_to(np.array([0,0,0]))
        tropical_morty.set_color(RED)

        morty.target = tropical_morty

        # move screen to equator

        self.play(
            Rotate(earth, theta0 + dtheta/2,run_time = 3),
            MoveToTarget(morty, path_arc = 70*DEGREES, run_time = 3),
        )

























class ScreenShapingScene(ThreeDScene):


    # TODO: Morph from Earth Scene into this scene

    def construct(self):

        #self.force_skipping()
        self.setup_elements()
        self.deform_screen()
        self.create_brightness_rect()
        self.slant_screen()
        self.unslant_screen()
        self.left_shift_screen_while_showing_light_indicator()
        self.add_distance_arrow()
        self.right_shift_screen_while_showing_light_indicator_and_distance_arrow()
        self.left_shift_again()
        #self.revert_to_original_skipping_status()
        
        self.morph_into_3d()
        self.prove_inverse_square_law()


    def setup_elements(self):

        SCREEN_THICKNESS = 10

        self.screen_height = 1.0
        self.brightness_rect_height = 1.0

        # screen
        self.screen = Line([3,-self.screen_height/2,0],[3,self.screen_height/2,0],
            path_arc = 0, num_arc_anchors = 10)
        
        # light source
        self.light_source = LightSource(
            opacity_function = inverse_quadratic(1,5,1),
            num_levels = NUM_LEVELS,
            radius = 10,
            max_opacity = 0.2
            #screen = self.screen
        )
        self.light_source.set_max_opacity_spotlight(0.2)

        self.light_source.set_screen(self.screen)
        self.light_source.move_source_to([-5,0,0])

        # abbreviations
        self.ambient_light = self.light_source.ambient_light
        self.spotlight = self.light_source.spotlight
        self.lighthouse = self.light_source.lighthouse

        
        #self.add_foreground_mobject(self.light_source.shadow)

        # Morty
        self.morty = Mortimer().scale(0.3).next_to(self.screen, RIGHT, buff = 0.5)

        # Add everything to the scene
        self.add(self.lighthouse)
        
        self.wait()
        self.play(FadeIn(self.screen))
        self.wait()

        self.add_foreground_mobject(self.screen)
        self.add_foreground_mobject(self.morty)

        self.play(SwitchOn(self.ambient_light))

        self.play(
            SwitchOn(self.spotlight),
            self.light_source.dim_ambient
        )

        screen_tracker = ScreenTracker(self.light_source)
        self.add(screen_tracker)


        self.wait()



    def deform_screen(self):

        self.wait()

        self.play(ApplyMethod(self.screen.set_path_arc, 45 * DEGREES))
        self.play(ApplyMethod(self.screen.set_path_arc, -90 * DEGREES))
        self.play(ApplyMethod(self.screen.set_path_arc, 0))




    def create_brightness_rect(self):

        # in preparation for the slanting, create a rectangle that shows the brightness

        # a rect a zero width overlaying the screen
        # so we can morph it into the brightness rect above
        brightness_rect0 = Rectangle(width = 0,
            height = self.screen_height).move_to(self.screen.get_center())
        self.add_foreground_mobject(brightness_rect0)

        self.brightness_rect = Rectangle(width = self.brightness_rect_height,
            height = self.brightness_rect_height, fill_color = YELLOW, fill_opacity = 0.5)

        self.brightness_rect.next_to(self.screen, UP, buff = 1)

        self.play(
            ReplacementTransform(brightness_rect0,self.brightness_rect)
        )

        self.unslanted_screen = self.screen.deepcopy()
        self.unslanted_brightness_rect = self.brightness_rect.copy()
        # for unslanting the screen later


    def slant_screen(self):

        SLANTING_AMOUNT = 0.1

        lower_screen_point, upper_screen_point = self.screen.get_start_and_end()

        lower_slanted_screen_point = interpolate(
            lower_screen_point, self.spotlight.get_source_point(), SLANTING_AMOUNT
        )
        upper_slanted_screen_point = interpolate(
            upper_screen_point, self.spotlight.get_source_point(), -SLANTING_AMOUNT
        )

        self.slanted_brightness_rect = self.brightness_rect.copy()
        self.slanted_brightness_rect.width *= 2
        self.slanted_brightness_rect.generate_points()
        self.slanted_brightness_rect.set_fill(opacity = 0.25)

        self.slanted_screen = Line(lower_slanted_screen_point,upper_slanted_screen_point,
            path_arc = 0, num_arc_anchors = 10)
        self.slanted_brightness_rect.move_to(self.brightness_rect.get_center())

        self.play(
             Transform(self.screen,self.slanted_screen),
             Transform(self.brightness_rect,self.slanted_brightness_rect),
        )



    def unslant_screen(self):

        self.wait()        
        self.play(
            Transform(self.screen,self.unslanted_screen),
            Transform(self.brightness_rect,self.unslanted_brightness_rect),
        )




    def left_shift_screen_while_showing_light_indicator(self):

        # Scene 5: constant screen size, changing opening angle

        OPACITY_FOR_UNIT_INTENSITY = 1

        # let's use an actual light indicator instead of just rects

        self.indicator_intensity = 0.25
        indicator_height = 1.25 * self.screen_height

        self.indicator = LightIndicator(radius = indicator_height/2,
            opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY,
            color = LIGHT_COLOR,
            precision = 2)
        self.indicator.set_intensity(self.indicator_intensity)

        self.indicator.move_to(self.brightness_rect.get_center())

        self.play(
            FadeOut(self.brightness_rect),
            FadeIn(self.indicator)
        )

        # Here some digits of the indicator disappear...

        self.add_foreground_mobject(self.indicator.reading)


        self.unit_indicator_intensity = 1.0 # intensity at distance 1
                                            # (where we are about to move to)

        self.left_shift = (self.screen.get_center()[0] - self.spotlight.get_source_point()[0])/2

        self.play(
            self.screen.shift,[-self.left_shift,0,0],
            self.morty.shift,[-self.left_shift,0,0],
            self.indicator.shift,[-self.left_shift,0,0],
            self.indicator.set_intensity,self.unit_indicator_intensity,
        )
        


    def add_distance_arrow(self):

        # distance arrow (length 1)
        left_x = self.spotlight.get_source_point()[0]
        right_x = self.screen.get_center()[0]
        arrow_y = -2
        arrow1 = Arrow([left_x,arrow_y,0],[right_x,arrow_y,0])
        arrow2 = Arrow([right_x,arrow_y,0],[left_x,arrow_y,0])
        arrow1.set_fill(color = WHITE)
        arrow2.set_fill(color = WHITE)
        distance_decimal = Integer(1).next_to(arrow1,DOWN)
        self.arrow = VGroup(arrow1, arrow2,distance_decimal)
        self.add(self.arrow)


        # distance arrow (length 2)
        # will be morphed into
        self.distance_to_source = right_x - left_x
        new_right_x = left_x + 2 * self.distance_to_source
        new_arrow1 = Arrow([left_x,arrow_y,0],[new_right_x,arrow_y,0])
        new_arrow2 = Arrow([new_right_x,arrow_y,0],[left_x,arrow_y,0])
        new_arrow1.set_fill(color = WHITE)
        new_arrow2.set_fill(color = WHITE)
        new_distance_decimal = Integer(2).next_to(new_arrow1,DOWN)
        self.new_arrow = VGroup(new_arrow1, new_arrow2, new_distance_decimal)
        # don't add it yet


    def right_shift_screen_while_showing_light_indicator_and_distance_arrow(self):

        self.wait()

        self.play(
            ReplacementTransform(self.arrow,self.new_arrow),
            ApplyMethod(self.screen.shift,[self.distance_to_source,0,0]),
            ApplyMethod(self.indicator.shift,[self.left_shift,0,0]),
            
            ApplyMethod(self.indicator.set_intensity,self.indicator_intensity),
            # this should trigger ChangingDecimal, but it doesn't
            # maybe bc it's an anim within an anim?

            ApplyMethod(self.morty.shift,[self.distance_to_source,0,0]),
        )


    def left_shift_again(self):

        self.wait()

        self.play(
            ReplacementTransform(self.new_arrow,self.arrow),
            ApplyMethod(self.screen.shift,[-self.distance_to_source,0,0]),
            #ApplyMethod(self.indicator.shift,[-self.left_shift,0,0]),
            ApplyMethod(self.indicator.set_intensity,self.unit_indicator_intensity),
            ApplyMethod(self.morty.shift,[-self.distance_to_source,0,0]),
        )

    def morph_into_3d(self):


        self.play(FadeOut(self.morty))

        axes = ThreeDAxes()
        self.add(axes)

        phi0 = self.camera.get_phi() # default is 0 degs
        theta0 = self.camera.get_theta() # default is -90 degs
        distance0 = self.camera.get_distance()

        phi1 = 60 * DEGREES # angle from zenith (0 to 180)
        theta1 = -135 * DEGREES # azimuth (0 to 360)
        distance1 = distance0
        target_point = self.camera.get_spherical_coords(phi1, theta1, distance1)

        dphi = phi1 - phi0
        dtheta = theta1 - theta0

        camera_target_point = target_point # self.camera.get_spherical_coords(45 * DEGREES, -60 * DEGREES)
        projection_direction = self.camera.spherical_coords_to_point(phi1,theta1, 1)

        new_screen0 = Rectangle(height = self.screen_height,
            width = 0.1, stroke_color = RED, fill_color = RED, fill_opacity = 1)
        new_screen0.rotate(TAU/4,axis = DOWN)
        new_screen0.move_to(self.screen.get_center())
        self.add(new_screen0)
        self.remove(self.screen)
        self.light_source.set_screen(new_screen0)

        self.light_source.set_camera(self.camera)


        new_screen = Rectangle(height = self.screen_height,
            width = self.screen_height, stroke_color = RED, fill_color = RED, fill_opacity = 1)
        new_screen.rotate(TAU/4,axis = DOWN)
        new_screen.move_to(self.screen.get_center())

        self.add_foreground_mobject(self.ambient_light)
        self.add_foreground_mobject(self.spotlight)
        self.add_foreground_mobject(self.light_source.shadow)

        self.play(
             ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point),
             
        )
        self.remove(self.spotlight)

        self.play(Transform(new_screen0,new_screen))

        self.wait()

        self.unit_screen = new_screen0 # better name



    def prove_inverse_square_law(self):

        def orientate(mob):
            mob.move_to(self.unit_screen)
            mob.rotate(TAU/4, axis = LEFT)
            mob.rotate(TAU/4, axis = OUT)
            mob.rotate(TAU/2, axis = LEFT)
            return mob

        unit_screen_copy = self.unit_screen.copy()
        fourfold_screen = self.unit_screen.copy()
        fourfold_screen.scale(2,about_point = self.light_source.get_source_point())

        self.remove(self.spotlight)


        reading1 = TexMobject("1")
        orientate(reading1)

        self.play(FadeIn(reading1))
        self.wait()
        self.play(FadeOut(reading1))
        

        self.play(
            Transform(self.unit_screen, fourfold_screen)
        )

        reading21 = TexMobject("{1\over 4}").scale(0.8)
        orientate(reading21)
        reading22 = reading21.deepcopy()
        reading23 = reading21.deepcopy()
        reading24 = reading21.deepcopy()
        reading21.shift(0.5*OUT + 0.5*UP)
        reading22.shift(0.5*OUT + 0.5*DOWN)
        reading23.shift(0.5*IN + 0.5*UP)
        reading24.shift(0.5*IN + 0.5*DOWN)


        corners = fourfold_screen.get_anchors()
        midpoint1 = (corners[0] + corners[1])/2
        midpoint2 = (corners[1] + corners[2])/2
        midpoint3 = (corners[2] + corners[3])/2
        midpoint4 = (corners[3] + corners[0])/2
        midline1 = Line(midpoint1, midpoint3)
        midline2 = Line(midpoint2, midpoint4)

        self.play(
            ShowCreation(midline1),
            ShowCreation(midline2)
        )

        self.play(
            FadeIn(reading21),
            FadeIn(reading22),
            FadeIn(reading23),
            FadeIn(reading24),
        )

        self.wait()

        self.play(
            FadeOut(reading21),
            FadeOut(reading22),
            FadeOut(reading23),
            FadeOut(reading24),
            FadeOut(midline1),
            FadeOut(midline2)
        )

        ninefold_screen = unit_screen_copy.copy()
        ninefold_screen.scale(3,about_point = self.light_source.get_source_point())

        self.play(
            Transform(self.unit_screen, ninefold_screen)
        )

        reading31 = TexMobject("{1\over 9}").scale(0.8)
        orientate(reading31)
        reading32 = reading31.deepcopy()
        reading33 = reading31.deepcopy()
        reading34 = reading31.deepcopy()
        reading35 = reading31.deepcopy()
        reading36 = reading31.deepcopy()
        reading37 = reading31.deepcopy()
        reading38 = reading31.deepcopy()
        reading39 = reading31.deepcopy()
        reading31.shift(IN + UP)
        reading32.shift(IN)
        reading33.shift(IN + DOWN)
        reading34.shift(UP)
        reading35.shift(ORIGIN)
        reading36.shift(DOWN)
        reading37.shift(OUT + UP)
        reading38.shift(OUT)
        reading39.shift(OUT + DOWN)

        corners = ninefold_screen.get_anchors()
        midpoint11 = (2*corners[0] + corners[1])/3
        midpoint12 = (corners[0] + 2*corners[1])/3
        midpoint21 = (2*corners[1] + corners[2])/3
        midpoint22 = (corners[1] + 2*corners[2])/3
        midpoint31 = (2*corners[2] + corners[3])/3
        midpoint32 = (corners[2] + 2*corners[3])/3
        midpoint41 = (2*corners[3] + corners[0])/3
        midpoint42 = (corners[3] + 2*corners[0])/3
        midline11 = Line(midpoint11, midpoint32)
        midline12 = Line(midpoint12, midpoint31)
        midline21 = Line(midpoint21, midpoint42)
        midline22 = Line(midpoint22, midpoint41)

        self.play(
            ShowCreation(midline11),
            ShowCreation(midline12),
            ShowCreation(midline21),
            ShowCreation(midline22),
        )

        self.play(
            FadeIn(reading31),
            FadeIn(reading32),
            FadeIn(reading33),
            FadeIn(reading34),
            FadeIn(reading35),
            FadeIn(reading36),
            FadeIn(reading37),
            FadeIn(reading38),
            FadeIn(reading39),
        )




class IndicatorScalingScene(Scene):

    def construct(self):

        unit_intensity = 0.6

        indicator1 = LightIndicator(show_reading = False, color = LIGHT_COLOR)
        indicator1.set_intensity(unit_intensity)
        reading1 = TexMobject("1")
        reading1.move_to(indicator1)
        

        indicator2 = LightIndicator(show_reading = False, color = LIGHT_COLOR)
        indicator2.shift(2*RIGHT)
        indicator2.set_intensity(unit_intensity/4)
        reading2 = TexMobject("{1\over 4}").scale(0.8)
        reading2.move_to(indicator2)

        indicator3 = LightIndicator(show_reading = False, color = LIGHT_COLOR)
        indicator3.shift(4*RIGHT)
        indicator3.set_intensity(unit_intensity/9)
        reading3 = TexMobject("{1\over 9}").scale(0.8)
        reading3.move_to(indicator3)

        
        self.play(FadeIn(indicator1))
        self.play(FadeIn(reading1))
        self.wait()
        self.play(FadeOut(reading1))
        self.play(Transform(indicator1, indicator2))
        self.play(FadeIn(reading2))
        self.wait()
        self.play(FadeOut(reading2))
        self.play(Transform(indicator1, indicator3))
        self.play(FadeIn(reading3))
        self.wait()






class BackToEulerSumScene(PiCreatureScene):

   
    def construct(self):
        self.remove(self.get_primary_pi_creature())

        NUM_CONES = 7
        NUM_VISIBLE_CONES = 6
        INDICATOR_RADIUS = 0.5
        OPACITY_FOR_UNIT_INTENSITY = 1.0

        self.number_line = NumberLine(
            x_min = 0,
            color = WHITE,
            number_at_center = 1.6,
            stroke_width = 1,
            numbers_with_elongated_ticks = list(range(1,5)),
            numbers_to_show = list(range(1,5)),
            unit_size = 2,
            tick_frequency = 0.2,
            line_to_number_buff = LARGE_BUFF,
            label_direction = UP,
        )

        self.number_line.label_direction = DOWN
        #self.number_line.shift(3*UP)

        self.number_line_labels = self.number_line.get_number_mobjects()
        self.add(self.number_line,self.number_line_labels)
        self.wait()

        origin_point = self.number_line.number_to_point(0)

        self.default_pi_creature_class = Randolph
        randy = self.get_primary_pi_creature()

        randy.scale(0.5)
        randy.flip()
        right_pupil = randy.pupils[1]
        randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil)

        randy_copy = randy.copy()
        randy_copy.target = randy.copy().shift(DOWN)



        bubble = ThoughtBubble(direction = RIGHT,
                            width = 4, height = 3,
                            file_name = "Bubbles_thought.svg")
        bubble.next_to(randy,LEFT+UP)
        bubble.set_fill(color = BLACK, opacity = 1)
        
        self.play(
            randy.change, "wave_2",
            ShowCreation(bubble),
        )


        euler_sum = TexMobject("1", "+", "{1\over 4}", 
            "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "\cdots", " ")
        # the last entry is a dummy element which makes looping easier
        # used just for putting the fractions into the light indicators
            
        intensities = np.array([1./(n+1)**2 for n in range(NUM_CONES)])
        opacities = intensities * OPACITY_FOR_UNIT_INTENSITY

        # repeat:

        # fade in lighthouse
        # switch on / fade in ambient light
        # show creation / write light indicator
        # move indicator onto origin
            # while morphing and dimming
        # move indicator into thought bubble
            # while indicators already inside shift to the back
            # and while term appears in the series below

        point = self.number_line.number_to_point(1)
        v = point - self.number_line.number_to_point(0)
        light_source = LightSource()
        light_source.move_source_to(point)
        #light_source.ambient_light.move_source_to(point)
        #light_source.lighthouse.move_to(point)

        self.play(FadeIn(light_source.lighthouse))
        self.play(SwitchOn(light_source.ambient_light))


        # create an indicator that will move along the number line
        indicator = LightIndicator(color = LIGHT_COLOR,
                radius = INDICATOR_RADIUS,
                opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY,
                show_reading = False
        )
        indicator_reading = euler_sum[0]
        indicator_reading.set_height(0.5 * indicator.get_height())
        indicator_reading.move_to(indicator.get_center())
        indicator.add(indicator_reading)
        indicator.tex_reading = indicator_reading
        # the TeX reading is too bright at full intensity
        indicator.tex_reading.set_fill(color = BLACK)
        indicator.foreground.set_fill(None,opacities[0])


        indicator.move_to(point)
        indicator.set_intensity(intensities[0])

        self.play(FadeIn(indicator))
        self.add_foreground_mobject(indicator)
        
        collection_point = np.array([-6.,2.,0.])
        left_shift = 0.2*LEFT
        collected_indicators = Mobject()


        for i in range(2, NUM_VISIBLE_CONES + 1):

            previous_point = self.number_line.number_to_point(i - 1)
            point = self.number_line.number_to_point(i)


            v = point - previous_point
            #print v
            # Create and position the target indicator (next on number line).
            indicator_target = indicator.deepcopy()
            indicator_target.shift(v)


            # Here we make a copy that will move into the thought bubble.
            bubble_indicator = indicator.deepcopy()
            # And its target
            bubble_indicator_target = bubble_indicator.deepcopy()
            bubble_indicator_target.set_intensity(intensities[i - 2])

            # give the target the appropriate reading
            euler_sum[2*i-4].move_to(bubble_indicator_target)
            bubble_indicator_target.remove(bubble_indicator_target.tex_reading)
            bubble_indicator_target.tex_reading = euler_sum[2*i-4].copy()
            bubble_indicator_target.add(bubble_indicator_target.tex_reading)
            # center it in the indicator

            if bubble_indicator_target.tex_reading.get_tex_string() != "1":
                bubble_indicator_target.tex_reading.set_height(0.8*indicator.get_height())
            # the target is less bright, possibly switch to a white text color
            if bubble_indicator_target.intensity < 0.7:
                bubble_indicator.tex_reading.set_fill(color = WHITE)

            # position the target in the thought bubble
            bubble_indicator_target.move_to(collection_point)


            self.add_foreground_mobject(bubble_indicator)


            self.wait()

            self.play(
                 Transform(bubble_indicator,bubble_indicator_target),
                 collected_indicators.shift,left_shift,
            )

            collected_indicators.add(bubble_indicator)

            new_light = light_source.deepcopy()
            w = new_light.get_source_point()
            new_light.move_source_to(w + (i-2)*v)
            w2 = new_light.get_source_point()
            
            self.add(new_light.lighthouse)
            self.play(
                  Transform(indicator,indicator_target),
                  new_light.lighthouse.shift,v,
            )
            new_light.move_source_to(w + (i-1)*v)
            new_light.lighthouse.move_to(w + (i-1)*v)

            self.play(SwitchOn(new_light.ambient_light),
            )


            

        # quickly switch on off-screen light cones
        for i in range(NUM_VISIBLE_CONES,NUM_CONES):
            indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.ambient_light.radius * self.number_line.unit_size
            indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME
            indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9)
                smooth,indicator_start_time,indicator_stop_time)
            ls = LightSource()
            point = point = self.number_line.number_to_point(i)
            ls.move_source_to(point)
            self.play(
                SwitchOn(ls.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME),
            )

        # and morph indicator stack into limit value

        sum_indicator = LightIndicator(color = LIGHT_COLOR,
                radius = INDICATOR_RADIUS,
                opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY,
                show_reading = False
            )
        sum_indicator.set_intensity(intensities[0] * np.pi**2/6)
        sum_indicator_reading = TexMobject("{\pi^2 \over 6}")
        sum_indicator_reading.set_fill(color = BLACK)
        sum_indicator_reading.set_height(0.8 * sum_indicator.get_height())
        sum_indicator.add(sum_indicator_reading)
        sum_indicator.move_to(collection_point)

        self.play(
            FadeOut(collected_indicators),
            FadeIn(sum_indicator)
        )

            

        self.wait()





class TwoLightSourcesScene(PiCreatureScene):

    def construct(self):

        MAX_OPACITY = 0.4
        INDICATOR_RADIUS = 0.6
        OPACITY_FOR_UNIT_INTENSITY = 0.5

        morty = self.get_primary_pi_creature()
        morty.scale(0.3).flip()
        right_pupil = morty.pupils[1]
        morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil)

        horizontal = VMobject(stroke_width = 1)
        horizontal.set_points_as_corners([C,A])
        vertical = VMobject(stroke_width = 1)
        vertical.set_points_as_corners([C,B])

        self.play(
            ShowCreation(horizontal),
            ShowCreation(vertical)
        )

        indicator = LightIndicator(color = LIGHT_COLOR,
                radius = INDICATOR_RADIUS,
                opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY,
                show_reading = True,
                precision = 2
        )

        indicator.next_to(morty,LEFT)

        self.play(
            Write(indicator)
        )


        ls1 = LightSource(radius = 20, num_levels = 50)
        ls2 = ls1.deepcopy()
        ls1.move_source_to(A)
        ls2.move_source_to(B)

        self.play(
            FadeIn(ls1.lighthouse),
            FadeIn(ls2.lighthouse),
            SwitchOn(ls1.ambient_light),
            SwitchOn(ls2.ambient_light)
        )

        distance1 = get_norm(C - ls1.get_source_point())
        intensity = ls1.ambient_light.opacity_function(distance1) / indicator.opacity_for_unit_intensity
        distance2 = get_norm(C - ls2.get_source_point())
        intensity += ls2.ambient_light.opacity_function(distance2) / indicator.opacity_for_unit_intensity

        self.play(
            UpdateLightIndicator(indicator,intensity)
        )

        self.wait()

        ls3 = ls1.deepcopy()
        ls3.move_to(np.array([6,3.5,0]))

        new_indicator = indicator.copy()
        new_indicator.light_source = ls3
        new_indicator.measurement_point = C
        self.add(new_indicator)
        self.play(
            indicator.shift, 2 * UP
        )



        #intensity = intensity_for_light_source(ls3)


        self.play(
            SwitchOff(ls1.ambient_light),
            #FadeOut(ls1.lighthouse),
            SwitchOff(ls2.ambient_light),
            #FadeOut(ls2.lighthouse),
            UpdateLightIndicator(new_indicator,0.0)
        )

        # create a *continual* animation for the replacement source
        updater = ContinualLightIndicatorUpdate(new_indicator)
        self.add(updater)

        self.play(
            SwitchOn(ls3.ambient_light),
            FadeIn(ls3.lighthouse),
            
        )

        self.wait()

        # move the light source around
        # TODO: moving along a path arc

        location = np.array([-3,-2.,0.])
        self.play(ls3.move_source_to,location)
        location = np.array([6.,1.,0.])
        self.play(ls3.move_source_to,location)
        location = np.array([5.,2.,0.])
        self.play(ls3.move_source_to,location)
        closer_location = interpolate(location, C, 0.5)
        self.play(ls3.move_source_to,closer_location)
        self.play(ls3.move_source_to,location)

        # maybe move in a circle around C using a loop?



        self.play(ls3.move_source_to,H)



        # draw lines to complete the geometric picture
        # and label the lengths

        line_a = VMobject()
        line_a.set_points_as_corners([B,C])
        line_b = VMobject()
        line_b.set_points_as_corners([A,C])
        line_c = VMobject()
        line_c.set_points_as_corners([A,B])
        line_h = VMobject()
        line_h.set_points_as_corners([H,C])

        label_a = TexMobject("a")
        label_a.next_to(line_a, LEFT, buff = 0.5)
        label_b = TexMobject("b")
        label_b.next_to(line_b, DOWN, buff = 0.5)
        label_h = TexMobject("h")
        label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5)

        self.play(
            ShowCreation(line_a),
            Write(label_a)
        )

        self.play(
            ShowCreation(line_b),
            Write(label_b)
        )

        self.play(
            ShowCreation(line_c),
        )

        self.play(
            ShowCreation(line_h),
            Write(label_h)
        )


        # state the IPT
        theorem_location = np.array([3.,2.,0.])
        theorem = TexMobject("{1\over a^2} + {1\over b^2} = {1\over h^2}")
        theorem_name = TextMobject("Inverse Pythagorean Theorem")
        buffer = 1.2
        theorem_box = Rectangle(width = buffer*theorem.get_width(),
            height = buffer*theorem.get_height())

        theorem.move_to(theorem_location)
        theorem_box.move_to(theorem_location)
        theorem_name.next_to(theorem_box,UP)

        self.play(
            Write(theorem),
        )

        self.play(
            ShowCreation(theorem_box),
            Write(theorem_name),
        )



class IPTScene1(PiCreatureScene):

    def construct(self):

        show_detail = True

        SCREEN_SCALE = 0.1
        SCREEN_THICKNESS = 0.2


        # use the following for the zoomed inset
        if show_detail:
            self.camera.frame_shape = (0.02 * FRAME_HEIGHT, 0.02 * FRAME_WIDTH)
            self.camera.frame_center = C
            SCREEN_SCALE = 0.01
            SCREEN_THICKNESS = 0.02





        morty = self.get_primary_pi_creature()
        self.remove(morty)
        morty.scale(0.3).flip()
        right_pupil = morty.pupils[1]
        morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil)
        
        if not show_detail:
            self.add_foreground_mobject(morty)

        stroke_width = 6
        line_a = Line(B,C,stroke_width = stroke_width)
        line_b = Line(A,C,stroke_width = stroke_width)
        line_c = Line(A,B,stroke_width = stroke_width)
        line_h = Line(C,H,stroke_width = stroke_width)

        length_a = line_a.get_length()
        length_b = line_b.get_length()
        length_c = line_c.get_length()
        length_h = line_h.get_length()

        label_a = TexMobject("a")
        label_a.next_to(line_a, LEFT, buff = 0.5)
        label_b = TexMobject("b")
        label_b.next_to(line_b, DOWN, buff = 0.5)
        label_h = TexMobject("h")
        label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5)

        self.add_foreground_mobject(line_a)
        self.add_foreground_mobject(line_b)
        self.add_foreground_mobject(line_c)
        self.add_foreground_mobject(line_h)
        self.add_foreground_mobject(label_a)
        self.add_foreground_mobject(label_b)
        self.add_foreground_mobject(label_h)
        
        if not show_detail:
            self.add_foreground_mobject(morty)

        ls1 = LightSource(radius = 10)
        ls1.move_source_to(B)

        self.add(ls1.lighthouse)

        if not show_detail:
            self.play(
                SwitchOn(ls1.ambient_light)
            )

        self.wait()

        # adding the first screen

        screen_width_a = SCREEN_SCALE * length_a
        screen_width_b = SCREEN_SCALE * length_b
        screen_width_ap = screen_width_a * length_a / length_c
        screen_width_bp = screen_width_b * length_b / length_c
        screen_width_c = SCREEN_SCALE * length_c

        screen_thickness_a = SCREEN_THICKNESS
        screen_thickness_b = SCREEN_THICKNESS

        screen1 = Rectangle(width = screen_width_b,
            height = screen_thickness_b, 
            stroke_width = 0, 
            fill_opacity = 1.0)
        screen1.move_to(C + screen_width_b/2 * RIGHT + screen_thickness_b/2 * DOWN)
        
        if not show_detail:
            self.add_foreground_mobject(morty)

        self.play(
            FadeIn(screen1)
        )
        self.add_foreground_mobject(screen1)

        ls1.set_screen(screen1)
        screen_tracker = ScreenTracker(ls1)
        self.add(screen_tracker)
        #self.add(ls1.shadow)

        if not show_detail:
            self.play(
                SwitchOn(ls1.ambient_light)
            )

        self.play(
            SwitchOn(ls1.spotlight),
            SwitchOff(ls1.ambient_light)
        )



        # now move the light source to the height point
        # while shifting scaling the screen
        screen1p = screen1.deepcopy()
        screen1pp = screen1.deepcopy()
        #self.add(screen1p)
        angle = np.arccos(length_b / length_c)
        
        screen1p.stretch_to_fit_width(screen_width_bp)
        screen1p.move_to(C + (screen_width_b - screen_width_bp/2) * RIGHT + SCREEN_THICKNESS/2 * DOWN)
        screen1p.rotate(-angle, about_point = C + screen_width_b * RIGHT)
        

        self.play(
            ls1.move_source_to,H,
            Transform(screen1,screen1p)
        )

        # add and move the second light source and screen
        ls2 = ls1.deepcopy()
        ls2.move_source_to(A)
        screen2 = Rectangle(width = screen_width_a,
            height = screen_thickness_a, 
            stroke_width = 0, 
            fill_opacity = 1.0)
        screen2.rotate(-TAU/4)
        screen2.move_to(C + screen_width_a/2 * UP + screen_thickness_a/2 * LEFT)

        self.play(
            FadeIn(screen2)
        )
        self.add_foreground_mobject(screen2)

        if not show_detail:
            self.add_foreground_mobject(morty)

        # the same scene adding sequence as before
        ls2.set_screen(screen2)
        screen_tracker2 = ScreenTracker(ls2)
        self.add(screen_tracker2)

        if not show_detail:
            self.play(
                SwitchOn(ls2.ambient_light)
            )

        self.wait()

        self.play(
            SwitchOn(ls2.spotlight),
            SwitchOff(ls2.ambient_light)
        )



        # now move the light source to the height point
        # while shifting scaling the screen
        screen2p = screen2.deepcopy()
        screen2pp = screen2.deepcopy()
        angle = np.arccos(length_a / length_c)
        screen2p.stretch_to_fit_height(screen_width_ap)
        screen2p.move_to(C + (screen_width_a - screen_width_ap/2) * UP + screen_thickness_a/2 * LEFT)
        screen2p.rotate(angle, about_point = C + screen_width_a * UP)
        # we can reuse the translation vector
        # screen2p.shift(vector)

        self.play(
            ls2.move_source_to,H,
            SwitchOff(ls1.ambient_light),
            Transform(screen2,screen2p)
        )

        # now transform both screens back
        self.play(
            Transform(screen1, screen1pp),
            Transform(screen2, screen2pp),
        )



class IPTScene2(Scene):

    def construct(self):

        intensity1 = 0.3
        intensity2 = 0.2
        formula_scale = 01.2
        indy_radius = 1

        indy1 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius)
        indy1.set_intensity(intensity1)
        reading1 = TexMobject("{1\over a^2}").scale(formula_scale).move_to(indy1)
        indy1.add(reading1)

        indy2 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius)
        indy2.set_intensity(intensity2)
        reading2 = TexMobject("{1\over b^2}").scale(formula_scale).move_to(indy2)
        indy2.add(reading2)

        indy3 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius)
        indy3.set_intensity(intensity1 + intensity2)
        reading3 = TexMobject("{1\over h^2}").scale(formula_scale).move_to(indy3)
        indy3.add(reading3)

        plus_sign = TexMobject("+").scale(formula_scale)
        equals_sign = TexMobject("=").scale(formula_scale)

        plus_sign.next_to(indy1, RIGHT)
        indy2.next_to(plus_sign, RIGHT)
        equals_sign.next_to(indy2, RIGHT)
        indy3.next_to(equals_sign, RIGHT)
        

        formula = VGroup(
            indy1, plus_sign, indy2, equals_sign, indy3
        )

        formula.move_to(ORIGIN)

        self.play(FadeIn(indy1))
        self.play(FadeIn(plus_sign), FadeIn(indy2))
        self.play(FadeIn(equals_sign), FadeIn(indy3))

        buffer = 1.5
        box = Rectangle(width = formula.get_width() * buffer,
            height = formula.get_height() * buffer)
        box.move_to(formula)
        text = TextMobject("Inverse Pythagorean Theorem").scale(formula_scale)
        text.next_to(box,UP)
        self.play(ShowCreation(box),Write(text))








class InscribedAngleScene(ThreeDScene):




    def construct(self):

        BASELINE_YPOS = -2.5
        OBSERVER_POINT = [0,BASELINE_YPOS,0]
        LAKE0_RADIUS = 1.5
        INDICATOR_RADIUS = 0.6
        TICK_SIZE = 0.5
        LIGHTHOUSE_HEIGHT = 0.3
        LAKE_COLOR = BLUE
        LAKE_OPACITY = 0.15
        LAKE_STROKE_WIDTH = 5.0
        LAKE_STROKE_COLOR = BLUE
        TEX_SCALE = 0.8
        DOT_COLOR = BLUE

        LIGHT_MAX_INT = 1
        LIGHT_SCALE = 5
        LIGHT_CUTOFF = 1

        self.cumulated_zoom_factor = 1


        def zoom_out_scene(factor):


            phi0 = self.camera.get_phi() # default is 0 degs
            theta0 = self.camera.get_theta() # default is -90 degs
            distance0 = self.camera.get_distance()

            distance1 = 2 * distance0
            camera_target_point = self.camera.get_spherical_coords(phi0, theta0, distance1)

            self.play(
                ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point),
                self.zoomable_mobs.shift, self.obs_dot.get_center(),
                self.unzoomable_mobs.scale,2,{"about_point" : ORIGIN},
            )

            self.cumulated_zoom_factor *= factor


        def shift_scene(v):
            self.play(
                self.zoomable_mobs.shift,v,
                self.unzoomable_mobs.shift,v
            )

        self.force_skipping()

        self.zoomable_mobs = VMobject()
        self.unzoomable_mobs = VMobject()


        baseline = VMobject()
        baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]])
        baseline.set_stroke(width = 0) # in case it gets accidentally added to the scene
        self.zoomable_mobs.add(baseline) # prob not necessary

        self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR)
        self.ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE)
        self.unzoomable_mobs.add(self.obs_dot) #, self.ls0_dot)

        # lake
        lake0 = Circle(radius = LAKE0_RADIUS,
            stroke_width = 0,
            fill_color = LAKE_COLOR,
            fill_opacity = LAKE_OPACITY
        )
        lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP)
        self.zoomable_mobs.add(lake0)

        # Morty and indicator
        morty = Mortimer().scale(0.3)
        morty.next_to(OBSERVER_POINT,DOWN)
        indicator = LightIndicator(precision = 2,
            radius = INDICATOR_RADIUS,
            show_reading  = False,
            color = LIGHT_COLOR
        )
        indicator.next_to(morty,LEFT)
        self.unzoomable_mobs.add(morty, indicator)

        # first lighthouse
        original_op_func = inverse_quadratic(LIGHT_MAX_INT,AMBIENT_SCALE,LIGHT_CUTOFF)
        ls0 = LightSource(opacity_function = original_op_func, num_levels = NUM_LEVELS)
        ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP)
        self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light)

        self.add(lake0,morty,self.obs_dot,self.ls0_dot, ls0.lighthouse)

        self.wait()


        # shore arcs
        arc_left = Arc(-TAU/2,
            radius = LAKE0_RADIUS,
            start_angle = -TAU/4,
            stroke_width = LAKE_STROKE_WIDTH,
            stroke_color = LAKE_STROKE_COLOR
        )
        arc_left.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP)

        one_left = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE)
        one_left.next_to(arc_left,LEFT)
        

        arc_right = Arc(TAU/2,
            radius = LAKE0_RADIUS,
            start_angle = -TAU/4,
            stroke_width = LAKE_STROKE_WIDTH,
            stroke_color = LAKE_STROKE_COLOR
        )
        arc_right.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP)

        one_right = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE)
        one_right.next_to(arc_right,RIGHT)

        self.play(
            ShowCreation(arc_left),
            Write(one_left),
            ShowCreation(arc_right),
            Write(one_right),
        )


        self.play(
            SwitchOn(ls0.ambient_light),
            lake0.set_stroke,{"color": LAKE_STROKE_COLOR, "width" : LAKE_STROKE_WIDTH},
        )

        self.play(FadeIn(indicator))

        self.play(
            indicator.set_intensity,0.5
        )

        # diameter
        diameter = DoubleArrow(OBSERVER_POINT,
            ls0.get_source_point(),
            buff = 0,
            color = WHITE,
        )
        diameter_text = TexMobject("d").scale(TEX_SCALE)
        diameter_text.next_to(diameter,RIGHT)

        self.play(
            ShowCreation(diameter),
            Write(diameter_text),
            #FadeOut(self.obs_dot),
            FadeOut(self.ls0_dot)
        )

        indicator_reading = TexMobject("{1\over d^2}").scale(TEX_SCALE)
        indicator_reading.move_to(indicator)
        self.unzoomable_mobs.add(indicator_reading)

        self.play(
            FadeIn(indicator_reading)
        )

        # replace d with its value
        new_diameter_text = TexMobject("{2\over \pi}").scale(TEX_SCALE)
        new_diameter_text.color = LAKE_COLOR
        new_diameter_text.move_to(diameter_text)
        self.play(
            Transform(diameter_text,new_diameter_text)
        )

        # insert into indicator reading
        new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE)
        new_reading.move_to(indicator)

        self.play(
            Transform(indicator_reading,new_reading)
        )

        self.play(
            FadeOut(one_left),
            FadeOut(one_right),
            FadeOut(diameter_text),
            FadeOut(arc_left),
            FadeOut(arc_right)
        )




        def indicator_wiggle():
            INDICATOR_WIGGLE_FACTOR = 1.3

            self.play(
                ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle),
                ScaleInPlace(indicator_reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle)
            )


        def angle_for_index(i,step):
            return -TAU/4 + TAU/2**step * (i + 0.5)


        def position_for_index(i, step, scaled_down = False):

            theta = angle_for_index(i,step)
            radial_vector = np.array([np.cos(theta),np.sin(theta),0])
            position = self.lake_center + self.lake_radius * radial_vector

            if scaled_down:
                return position.scale_about_point(self.obs_dot.get_center(),0.5)
            else:
                return position


        def split_light_source(i, step, show_steps = True, run_time = 1):

            ls_new_loc1 = position_for_index(i,step + 1)
            ls_new_loc2 = position_for_index(i + 2**step,step + 1)

            hyp = VMobject()
            hyp1 = Line(self.lake_center,ls_new_loc1)
            hyp2 = Line(self.lake_center,ls_new_loc2)
            hyp.add(hyp2,hyp1)
            self.new_hypotenuses.append(hyp)

            if show_steps == True:
                self.play(
                    ShowCreation(hyp, run_time = run_time)
                )

            leg1 = Line(self.obs_dot.get_center(),ls_new_loc1)
            leg2 = Line(self.obs_dot.get_center(),ls_new_loc2)
            self.new_legs_1.append(leg1)
            self.new_legs_2.append(leg2)

            if show_steps == True:
                self.play(
                    ShowCreation(leg1, run_time = run_time),
                    ShowCreation(leg2, run_time = run_time),
                )

            ls1 = self.light_sources_array[i]


            ls2 = ls1.copy()
            self.add(ls2)
            self.additional_light_sources.append(ls2)

            # check if the light sources are on screen
            ls_old_loc = np.array(ls1.get_source_point())
            onscreen_old = np.all(np.abs(ls_old_loc[:2]) < 10 ** 2**step)
            onscreen_1 = np.all(np.abs(ls_new_loc1[:2][:2]) < 10 ** 2**step)
            onscreen_2 = np.all(np.abs(ls_new_loc2[:2]) < 10 ** 2**step)
            show_animation = (onscreen_old or onscreen_1 or onscreen_2)

            if show_animation:
                print("animating (", i, ",", step, ")")
                self.play(
                    ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time),
                    ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time),
                )
            else:
                ls1.move_source_to(ls_new_loc1)
                ls2.move_source_to(ls_new_loc1)





        def construction_step(n, show_steps = True, run_time = 1,
            simultaneous_splitting = False):

            # we assume that the scene contains:
            # an inner lake, self.inner_lake
            # an outer lake, self.outer_lake
            # light sources, self.light_sources
            # legs from the observer point to each light source
            # self.legs
            # altitudes from the observer point to the
            # locations of the light sources in the previous step
            # self.altitudes
            # hypotenuses connecting antipodal light sources
            # self.hypotenuses

            # these are mobjects!


            # first, fade out all of the hypotenuses and altitudes

            if show_steps == True:
                self.zoomable_mobs.remove(self.hypotenuses, self.altitudes, self.inner_lake)
                self.play(
                    FadeOut(self.hypotenuses),
                    FadeOut(self.altitudes),
                    FadeOut(self.inner_lake)
                )
            else:
                self.zoomable_mobs.remove(self.inner_lake)
                self.play(
                    FadeOut(self.inner_lake)
                )

            # create a new, outer lake
            self.lake_center = self.obs_dot.get_center() + self.lake_radius * UP

            new_outer_lake = Circle(radius = self.lake_radius,
                stroke_width = LAKE_STROKE_WIDTH,
                fill_color = LAKE_COLOR,
                fill_opacity = LAKE_OPACITY,
                stroke_color = LAKE_STROKE_COLOR
            )
            new_outer_lake.move_to(self.lake_center)

            if show_steps == True: 
                self.play(
                    FadeIn(new_outer_lake, run_time = run_time),
                    FadeIn(self.ls0_dot)
                )
            else:
                self.play(
                    FadeIn(new_outer_lake, run_time = run_time),
                )

            self.wait()

            self.inner_lake = self.outer_lake
            self.outer_lake = new_outer_lake
            self.altitudes = self.legs
            #self.lake_center = self.outer_lake.get_center()

            self.additional_light_sources = []
            self.new_legs_1 = []
            self.new_legs_2 = []
            self.new_hypotenuses = [] 

            for i in range(2**n):
                split_light_source(i,
                    step = n,
                    show_steps = show_steps,
                    run_time = run_time
                )



            # collect the newly created mobs (in arrays)
            # into the appropriate Mobject containers

            self.legs = VMobject()
            for leg in self.new_legs_1:
                self.legs.add(leg)
                self.zoomable_mobs.add(leg)
            for leg in self.new_legs_2:
                self.legs.add(leg)
                self.zoomable_mobs.add(leg)

            for hyp in self.hypotenuses.submobjects:
                self.zoomable_mobs.remove(hyp)

            self.hypotenuses = VMobject()
            for hyp in self.new_hypotenuses:
                self.hypotenuses.add(hyp)
                self.zoomable_mobs.add(hyp)

            for ls in self.additional_light_sources:
                self.light_sources.add(ls)
                self.light_sources_array.append(ls)
                self.zoomable_mobs.add(ls)

            # update scene
            self.add(
                self.light_sources,
                self.inner_lake,
                self.outer_lake,
            )
            self.zoomable_mobs.add(self.light_sources, self.inner_lake, self.outer_lake)

            if show_steps == True:
                self.add(
                    self.legs,
                    self.hypotenuses,
                    self.altitudes,
                )
                self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes)


            self.wait()

            if show_steps == True:
                self.play(FadeOut(self.ls0_dot))

            #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP
            self.lake_radius *= 2








        self.lake_center = ls0_loc = ls0.get_source_point()

        self.inner_lake = VMobject()
        self.outer_lake = lake0
        self.legs = VMobject()
        self.legs.add(Line(OBSERVER_POINT,self.lake_center))
        self.altitudes = VMobject()
        self.hypotenuses = VMobject()
        self.light_sources_array = [ls0]
        self.light_sources = VMobject()
        self.light_sources.add(ls0)

        self.lake_radius = 2 * LAKE0_RADIUS # don't ask...

        self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources)

        self.add(self.inner_lake,
            self.outer_lake,
            self.legs,
            self.altitudes,
            self.hypotenuses
        )

        self.play(FadeOut(diameter))
        
        self.additional_light_sources = []
        self.new_legs_1 = []
        self.new_legs_2 = []
        self.new_hypotenuses = []

        self.revert_to_original_skipping_status()

        construction_step(0)
        indicator_wiggle()
        self.play(FadeOut(self.ls0_dot))
        zoom_out_scene(2)

        return

        construction_step(1)
        indicator_wiggle()
        self.play(FadeOut(self.ls0_dot))
        zoom_out_scene(2)

        construction_step(2)
        indicator_wiggle()
        self.play(FadeOut(self.ls0_dot))



        self.revert_to_original_skipping_status()


        ANGLE_COLOR1 = BLUE_C
        ANGLE_COLOR2 = GREEN_D

        
        for mob in self.mobjects:
            mob.fade(1.0)

        for hyp in self.hypotenuses:
            hyp.set_stroke(width = 0)
        for alt in self.altitudes:
            alt.set_stroke(width = 0)
        for leg in self.legs:
            leg.set_stroke(width = 0)
        self.inner_lake.set_stroke(width = 0)
        self.outer_lake.set_stroke(width = 0)

        self.wait()

        inner_lake_center = self.inner_lake.get_center()
        inner_lake_radius = self.lake_radius * 0.25
        inner_ls = VGroup()
        for i in range(4):
            theta = -TAU/4 + (i+0.5) * TAU/4
            point = inner_lake_center + inner_lake_radius * np.array([np.cos(theta), np.sin(theta),0])
            dot = Dot(point, color = LAKE_STROKE_COLOR, radius = 0.3)
            inner_ls.add(dot)

        self.add(inner_ls)

        inner_ls1 = inner_ls.submobjects[0]
        inner_ls2 = inner_ls.submobjects[1]
        inner_ls1_center = inner_ls1.get_center()
        inner_ls2_center = inner_ls2.get_center()

        outer_lake_center = self.outer_lake.get_center()
        outer_lake_radius = self.lake_radius * 0.5
        outer_ls = VGroup()
        for i in range(8):
            theta = -TAU/4 + (i+0.5) * TAU/8
            point = outer_lake_center + outer_lake_radius * np.array([np.cos(theta), np.sin(theta),0])
            dot = Dot(point, color = LAKE_STROKE_COLOR, radius = 0.3)
            outer_ls.add(dot)

        self.add(outer_ls)

        outer_ls1 = outer_ls.submobjects[0]
        outer_ls2 = outer_ls.submobjects[1]
        outer_ls1_center = outer_ls1.get_center()
        outer_ls2_center = outer_ls2.get_center()

        self.wait()

        arc_radius = 2.0

        line1 = Line(inner_lake_center, inner_ls1_center, color = WHITE)
        line2 = Line(inner_lake_center, inner_ls2_center, color = WHITE)


        #arc_point1 = interpolate(inner_lake_center, inner_ls1_center, 0.2)
        #arc_point2 = interpolate(inner_lake_center, inner_ls2_center, 0.2)
        #inner_angle_arc = ArcBetweenPoints(arc_point1, arc_point2, angle = TAU/4)
        inner_angle_arc = Arc(angle = TAU/4, start_angle = -TAU/8, radius = arc_radius,
            stroke_color = ANGLE_COLOR1)
        inner_angle_arc.move_arc_center_to(inner_lake_center)

        inner_label = TexMobject("\\theta", fill_color = ANGLE_COLOR1).scale(3).next_to(inner_angle_arc, LEFT, buff = -0.1)

        self.play(
            ShowCreation(line1),
            ShowCreation(line2),
        )
        self.play(
            ShowCreation(inner_angle_arc),
            FadeIn(inner_label)
        )




        self.wait()



        line3 = Line(outer_lake_center, inner_ls1_center, color = WHITE)
        line4 = Line(outer_lake_center, inner_ls2_center, color = WHITE)
        outer_angle_arc = Arc(angle = TAU/8, start_angle = -3*TAU/16, radius = arc_radius,
            stroke_color = ANGLE_COLOR2)
        outer_angle_arc.move_arc_center_to(outer_lake_center)

        outer_label = TexMobject("{\\theta \over 2}", color = ANGLE_COLOR2).scale(2.5).move_to(outer_angle_arc)
        outer_label.shift([-2,-1,0])

        self.play(
            ShowCreation(line3),
            ShowCreation(line4),
        )
        self.play(
            ShowCreation(outer_angle_arc),
            FadeIn(outer_label)
        )



        self.wait()



        line5 = Line(outer_lake_center, outer_ls1_center, color = WHITE)
        line6 = Line(outer_lake_center, outer_ls2_center, color = WHITE)

        self.play(
            ShowCreation(line5),
            ShowCreation(line6)
        )


        self.wait()

        self.play(
            FadeOut(line1),
            FadeOut(line2),
            FadeOut(line3),
            FadeOut(line4),
            FadeOut(line5),
            FadeOut(line6),
            FadeOut(inner_angle_arc),
            FadeOut(outer_angle_arc),
            FadeOut(inner_label),
            FadeOut(outer_label),
        )


        self.wait()

        inner_lines = VGroup()
        inner_arcs = VGroup()

        for i in range(-2,2):

            theta = -TAU/4 + (i+0.5)*TAU/4
            ls_point = inner_lake_center + inner_lake_radius * np.array([
                np.cos(theta), np.sin(theta),0])
            line = Line(inner_lake_center, ls_point, color = WHITE)
            inner_lines.add(line)

            arc = Arc(angle = TAU/4, start_angle = theta, radius = arc_radius,
                stroke_color = ANGLE_COLOR1)
            arc.move_arc_center_to(inner_lake_center)
            inner_arcs.add(arc)

            if i == 1:
                arc.set_stroke(width = 0)

        for line in inner_lines.submobjects:
            self.play(
                ShowCreation(line),
            )
        self.add_foreground_mobject(inner_lines)
        for arc in inner_arcs.submobjects:
            self.play(
                ShowCreation(arc)
            )

        self.wait()

        outer_lines = VGroup()
        outer_arcs = VGroup()

        for i in range(-2,2):

            theta = -TAU/4 + (i+0.5)*TAU/4

            ls_point = inner_lake_center + inner_lake_radius * np.array([
                np.cos(theta), np.sin(theta),0])
            line = Line(outer_lake_center, ls_point, color = WHITE)
            outer_lines.add(line)

            theta = -TAU/4 + (i+0.5)*TAU/8
            arc = Arc(angle = TAU/8, start_angle = theta, radius = arc_radius,
                stroke_color = ANGLE_COLOR2)
            arc.move_arc_center_to(outer_lake_center)
            outer_arcs.add(arc)

            if i == 1:
                arc.set_stroke(width = 0)

        
        for line in outer_lines.submobjects:
            self.play(
                ShowCreation(line),
            )
        self.add_foreground_mobject(outer_lines)
        for arc in outer_arcs.submobjects:
            self.play(
                ShowCreation(arc)
            )

        self.wait()

        self.play(
            FadeOut(inner_lines),
            FadeOut(inner_arcs)
        )


        outer_lines2 = VGroup()

        for i in range(-2,2):

            theta = -TAU/4 + (i+0.5)*TAU/8
            ls_point = outer_lake_center + outer_lake_radius * np.array([
                np.cos(theta), np.sin(theta),0])
            line = Line(outer_lake_center, ls_point, color = WHITE)
            outer_lines2.add(line)

        self.play(
            ShowCreation(outer_lines2),
        )

        self.wait()

        outer_lines3 = outer_lines2.copy().rotate(TAU/2, about_point = outer_lake_center)
        outer_arcs3 = outer_arcs.copy().rotate(TAU/2, about_point = outer_lake_center)

        self.play(
            ShowCreation(outer_lines3),
        )
        self.add_foreground_mobject(outer_lines3)
        for arc in outer_arcs3.submobjects:
            self.play(
                ShowCreation(arc)
            )

        last_arc = outer_arcs3.submobjects[0].copy()
        last_arc.rotate(-TAU/8, about_point = outer_lake_center)
        last_arc2 = last_arc.copy()
        last_arc2.rotate(TAU/2, about_point = outer_lake_center)

        self.play(
            ShowCreation(last_arc),
            ShowCreation(last_arc2),
        )

        self.wait()

        self.play(
            FadeOut(outer_lines2),
            FadeOut(outer_lines3),
            FadeOut(outer_arcs),
            FadeOut(outer_arcs3),
            FadeOut(last_arc),
            FadeOut(last_arc2),
        )

        self.play(
            FadeOut(inner_ls),
            FadeOut(outer_ls),
        )


        self.wait()


class PondScene(ThreeDScene):





    def construct(self):

        BASELINE_YPOS = -2.5
        OBSERVER_POINT = np.array([0,BASELINE_YPOS,0])
        LAKE0_RADIUS = 1.5
        INDICATOR_RADIUS = 0.6
        TICK_SIZE = 0.5
        LIGHTHOUSE_HEIGHT = 0.5
        LAKE_COLOR = BLUE
        LAKE_OPACITY = 0.15
        LAKE_STROKE_WIDTH = 5.0
        LAKE_STROKE_COLOR = BLUE
        TEX_SCALE = 0.8
        DOT_COLOR = BLUE

        LIGHT_MAX_INT = 1
        LIGHT_SCALE = 2.5
        LIGHT_CUTOFF = 1

        RIGHT_ANGLE_SIZE = 0.3

        self.cumulated_zoom_factor = 1

        STEP_RUN_TIME = 0.5


        #self.force_skipping()


        def right_angle(pointA, pointB, pointC, size = 1):

            v1 = pointA - pointB
            v1 = size * v1/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

        self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR)
        self.ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE)
        self.unzoomable_mobs.add(self.obs_dot)#, self.ls0_dot)

        # lake
        lake0 = Circle(radius = LAKE0_RADIUS,
            stroke_width = 0,
            fill_color = LAKE_COLOR,
            fill_opacity = LAKE_OPACITY
        )
        lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP)
        self.zoomable_mobs.add(lake0)

        # Morty and indicator
        morty = Randolph(color = MAROON_D).scale(0.3)
        morty.next_to(OBSERVER_POINT,DOWN)
        indicator = LightIndicator(precision = 2,
            radius = INDICATOR_RADIUS,
            show_reading  = False,
            color = LIGHT_COLOR
        )
        indicator.next_to(morty,LEFT)
        self.unzoomable_mobs.add(morty, indicator)

        # first lighthouse
        original_op_func = inverse_quadratic(LIGHT_MAX_INT,LIGHT_SCALE,LIGHT_CUTOFF)
        ls0 = LightSource(opacity_function = original_op_func, radius = 15.0, num_levels = 15)
        ls0.lighthouse.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,self.obs_dot,self.ls0_dot, ls0.lighthouse)
        self.add_foreground_mobject(morty)
        self.add_foreground_mobject(self.obs_dot)
        self.add_foreground_mobject(self.ls0_dot)
        self.wait()


        # shore arcs
        arc_left = Arc(-TAU/2,
            radius = LAKE0_RADIUS,
            start_angle = -TAU/4,
            stroke_width = LAKE_STROKE_WIDTH,
            stroke_color = LAKE_STROKE_COLOR
        )
        arc_left.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP)

        one_left = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE)
        one_left.next_to(arc_left,LEFT)
        

        arc_right = Arc(TAU/2,
            radius = LAKE0_RADIUS,
            start_angle = -TAU/4,
            stroke_width = LAKE_STROKE_WIDTH,
            stroke_color = LAKE_STROKE_COLOR
        )
        arc_right.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP)

        one_right = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE)
        one_right.next_to(arc_right,RIGHT)

        self.play(
            ShowCreation(arc_left),
            Write(one_left),
            ShowCreation(arc_right),
            Write(one_right),
        )


        self.play(
            SwitchOn(ls0.ambient_light),
            lake0.set_stroke,{"color": LAKE_STROKE_COLOR, "width" : LAKE_STROKE_WIDTH},
        )

        self.play(FadeIn(indicator))
        self.add_foreground_mobject(indicator)

        self.play(
            indicator.set_intensity,0.5
        )

        diameter_start = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.02)
        diameter_stop = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.98)

        # diameter
        diameter = DoubleArrow(diameter_start,
            diameter_stop,
            buff = 0,
            color = WHITE,
        )
        diameter_text = TexMobject("d").scale(TEX_SCALE)
        diameter_text.next_to(diameter,RIGHT)

        self.play(
            ShowCreation(diameter),
            Write(diameter_text),
            #FadeOut(self.obs_dot),
            FadeOut(self.ls0_dot)
        )

        indicator_reading = TexMobject("{1\over d^2}").scale(TEX_SCALE)
        indicator_reading.move_to(indicator)
        self.unzoomable_mobs.add(indicator_reading)

        self.play(
            FadeIn(indicator_reading)
        )
        self.add_foreground_mobject(indicator_reading)

        # replace d with its value
        new_diameter_text = TexMobject("{2\over \pi}").scale(TEX_SCALE)
        new_diameter_text.color = LAKE_COLOR
        new_diameter_text.move_to(diameter_text)
        self.play(
            Transform(diameter_text,new_diameter_text)
        )

        # insert into indicator reading
        new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE)
        new_reading.move_to(indicator)

        self.play(
            Transform(indicator_reading,new_reading)
        )

        self.wait()

        self.play(
            FadeOut(one_left),
            FadeOut(one_right),
            FadeOut(diameter_text),
            FadeOut(arc_left),
            FadeOut(arc_right)
        )




        def indicator_wiggle():
            INDICATOR_WIGGLE_FACTOR = 1.3

            self.play(
                ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle),
                ScaleInPlace(indicator_reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle)
            )


        def angle_for_index(i,step):
            return -TAU/4 + TAU/2**step * (i + 0.5)


        def position_for_index(i, step, scaled_down = False):

            theta = angle_for_index(i,step)
            radial_vector = np.array([np.cos(theta),np.sin(theta),0])
            position = self.lake_center + self.lake_radius * radial_vector

            if scaled_down:
                return position.scale_about_point(self.obs_dot.get_center(),0.5)
            else:
                return position


        def split_light_source(i, step, show_steps = True, animate = True, run_time = 1):

            ls_new_loc1 = position_for_index(i,step + 1)
            ls_new_loc2 = position_for_index(i + 2**step,step + 1)

            hyp = VMobject()
            hyp1 = Line(self.lake_center,ls_new_loc1)
            hyp2 = Line(self.lake_center,ls_new_loc2)
            hyp.add(hyp2,hyp1)
            self.new_hypotenuses.append(hyp)

            if show_steps == True:
                self.play(
                    ShowCreation(hyp, run_time = run_time)
                )

            leg1 = Line(self.obs_dot.get_center(),ls_new_loc1)
            leg2 = Line(self.obs_dot.get_center(),ls_new_loc2)
            self.new_legs_1.append(leg1)
            self.new_legs_2.append(leg2)

            if show_steps == True:
                self.play(
                    ShowCreation(leg1, run_time = run_time),
                    ShowCreation(leg2, run_time = run_time),
                )

            ls1 = self.light_sources_array[i]


            ls2 = ls1.copy()
            if animate == True:
                self.add(ls2)

            self.additional_light_sources.append(ls2)

            # check if the light sources are on screen
            ls_old_loc = np.array(ls1.get_source_point())
            onscreen_old = np.all(np.abs(ls_old_loc[:2]) < 10 * 2**3)
            onscreen_1 = np.all(np.abs(ls_new_loc1[:2]) < 10 * 2**3)
            onscreen_2 = np.all(np.abs(ls_new_loc2[:2]) < 10 * 2**3)
            show_animation = (onscreen_old or onscreen_1 or onscreen_2)

            if show_animation or animate:
                print("animating (", i, ",", step, ")")
                self.play(
                    ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time),
                    ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time),
                )
            else:
                ls1.move_source_to(ls_new_loc1)
                ls2.move_source_to(ls_new_loc1)





        def construction_step(n, show_steps = True, run_time = 1,
            simultaneous_splitting = False):

            # we assume that the scene contains:
            # an inner lake, self.inner_lake
            # an outer lake, self.outer_lake
            # light sources, self.light_sources
            # legs from the observer point to each light source
            # self.legs
            # altitudes from the observer point to the
            # locations of the light sources in the previous step
            # self.altitudes
            # hypotenuses connecting antipodal light sources
            # self.hypotenuses

            # these are mobjects!


            # first, fade out all of the hypotenuses and altitudes

            if show_steps == True:
                self.zoomable_mobs.remove(self.hypotenuses, self.altitudes, self.inner_lake)
                self.play(
                    FadeOut(self.hypotenuses),
                    FadeOut(self.altitudes),
                    FadeOut(self.inner_lake)
                )
            else:
                self.zoomable_mobs.remove(self.inner_lake)
                self.play(
                    FadeOut(self.inner_lake)
                )

            # create a new, outer lake
            self.lake_center = self.obs_dot.get_center() + self.lake_radius * UP

            new_outer_lake = Circle(radius = self.lake_radius,
                stroke_width = LAKE_STROKE_WIDTH,
                fill_color = LAKE_COLOR,
                fill_opacity = LAKE_OPACITY,
                stroke_color = LAKE_STROKE_COLOR
            )
            new_outer_lake.move_to(self.lake_center)

            if show_steps == True: 
                self.play(
                    FadeIn(new_outer_lake, run_time = run_time),
                    FadeIn(self.ls0_dot)
                )
            else:
                self.play(
                    FadeIn(new_outer_lake, run_time = run_time),
                )

            self.wait()

            self.inner_lake = self.outer_lake
            self.outer_lake = new_outer_lake
            self.altitudes = self.legs
            #self.lake_center = self.outer_lake.get_center()

            self.additional_light_sources = []
            self.new_legs_1 = []
            self.new_legs_2 = []
            self.new_hypotenuses = []

            # WE ALWAYS USE THIS CASE BRANCH
            if simultaneous_splitting == False:

                for i in range(2**n):
                    
                    split_light_source(i,
                        step = n,
                        show_steps = show_steps,
                        run_time = run_time
                    )

                    if n == 1 and i == 0:
                        # show again where the right angles are
                        A = self.light_sources[0].get_center()
                        B = self.additional_light_sources[0].get_center()
                        C = self.obs_dot.get_center()

                        triangle1 = triangle(
                            A, C, B
                        )
                        right_angle1 = right_angle(
                            A, C, B, size = 2 * RIGHT_ANGLE_SIZE
                        )

                        self.play(
                            FadeIn(triangle1),
                            FadeIn(right_angle1)
                        )

                        self.wait()

                        self.play(
                            FadeOut(triangle1),
                            FadeOut(right_angle1)
                        )

                        self.wait()

                        H = self.inner_lake.get_center() + self.lake_radius/2 * RIGHT
                        L = self.outer_lake.get_center()
                        triangle2 = triangle(
                            L, H, C
                        )

                        right_angle2 = right_angle(
                            L, H, C, size = 2 * RIGHT_ANGLE_SIZE
                        )

                        self.play(
                            FadeIn(triangle2),
                            FadeIn(right_angle2)
                        )

                        self.wait()

                        self.play(
                            FadeOut(triangle2),
                            FadeOut(right_angle2)
                        )

                        self.wait()



            # WE DON'T USE THIS CASE BRANCH ANYMORE
            else: # simultaneous splitting

                old_lake = self.outer_lake.copy()
                old_ls = self.light_sources.copy()
                old_ls2 = old_ls.copy()
                for submob in old_ls2.submobjects:
                    old_ls.add(submob)

                self.remove(self.outer_lake, self.light_sources)
                self.add(old_lake, old_ls)

                for i in range(2**n):
                    split_light_source(i,
                        step = n,
                        show_steps = show_steps,
                        run_time = run_time,
                        animate = False
                    )

                self.play(
                    ReplacementTransform(old_ls, self.light_sources, run_time = run_time),
                    ReplacementTransform(old_lake, self.outer_lake, run_time = run_time),
                )




            # collect the newly created mobs (in arrays)
            # into the appropriate Mobject containers

            self.legs = VMobject()
            for leg in self.new_legs_1:
                self.legs.add(leg)
                self.zoomable_mobs.add(leg)
            for leg in self.new_legs_2:
                self.legs.add(leg)
                self.zoomable_mobs.add(leg)

            for hyp in self.hypotenuses.submobjects:
                self.zoomable_mobs.remove(hyp)

            self.hypotenuses = VMobject()
            for hyp in self.new_hypotenuses:
                self.hypotenuses.add(hyp)
                self.zoomable_mobs.add(hyp)

            for ls in self.additional_light_sources:
                self.light_sources.add(ls)
                self.light_sources_array.append(ls)
                self.zoomable_mobs.add(ls)

            # update scene
            self.add(
                self.light_sources,
                self.inner_lake,
                self.outer_lake,
            )
            self.zoomable_mobs.add(self.light_sources, self.inner_lake, self.outer_lake)

            if show_steps == True:
                self.add(
                    self.legs,
                    self.hypotenuses,
                    self.altitudes,
                )
                self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes)


            self.wait()

            if show_steps == True:
                self.play(FadeOut(self.ls0_dot))

            #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP
            self.lake_radius *= 2








        self.lake_center = ls0_loc = ls0.get_source_point()

        self.inner_lake = VMobject()
        self.outer_lake = lake0
        self.legs = VMobject()
        self.legs.add(Line(OBSERVER_POINT,self.lake_center))
        self.altitudes = VMobject()
        self.hypotenuses = VMobject()
        self.light_sources_array = [ls0]
        self.light_sources = VMobject()
        self.light_sources.add(ls0)

        self.lake_radius = 2 * LAKE0_RADIUS # don't ask...

        self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources)

        self.add(self.inner_lake,
            self.outer_lake,
            self.legs,
            self.altitudes,
            self.hypotenuses
        )

        self.play(FadeOut(diameter))
        
        self.additional_light_sources = []
        self.new_legs_1 = []
        self.new_legs_2 = []
        self.new_hypotenuses = []


        construction_step(0, run_time = STEP_RUN_TIME)

        my_triangle = triangle(
            self.light_sources[0].get_source_point(),
            OBSERVER_POINT,
            self.light_sources[1].get_source_point()
        )

        angle_sign1 = right_angle(
            self.light_sources[0].get_source_point(),
            OBSERVER_POINT,
            self.light_sources[1].get_source_point(),
            size = RIGHT_ANGLE_SIZE
        )

        self.play(
            FadeIn(angle_sign1),
            FadeIn(my_triangle)
        )

        angle_sign2 = right_angle(
            self.light_sources[1].get_source_point(),
            self.lake_center,
            OBSERVER_POINT,
            size = RIGHT_ANGLE_SIZE
        )

        self.play(
            FadeIn(angle_sign2)
        )

        self.wait()

        self.play(
            FadeOut(angle_sign1),
            FadeOut(angle_sign2),
            FadeOut(my_triangle)
        )

        indicator_wiggle()
        self.remove(self.ls0_dot)
        zoom_out_scene(2)

        
        construction_step(1, run_time = STEP_RUN_TIME)
        indicator_wiggle()
        #self.play(FadeOut(self.ls0_dot))
        zoom_out_scene(2)


        construction_step(2, run_time = STEP_RUN_TIME)
        indicator_wiggle()
        self.play(FadeOut(self.ls0_dot))




        self.play(
            FadeOut(self.altitudes),
            FadeOut(self.hypotenuses),
            FadeOut(self.legs)
        )

        max_it = 10
        scale = 2**(max_it - 5)
        TEX_SCALE *= scale



        # for i in range(3,max_it + 1):
        #     construction_step(i, show_steps = False, run_time = 4.0/2**i,
        #         simultaneous_splitting = True)


        #print "starting simultaneous expansion"

        # simultaneous expansion of light sources from now on
        self.play(FadeOut(self.inner_lake))

        for n in range(3,max_it + 1):
            print("working on n = ", n, "...")
            new_lake = self.outer_lake.copy().scale(2,about_point = self.obs_dot.get_center())
            for (i,ls) in enumerate(self.light_sources_array[:2**n]):
                #print i
                lsp = ls.copy()
                self.light_sources.add(lsp)
                self.add(lsp)
                self.light_sources_array.append(lsp)

            new_lake_center = new_lake.get_center()
            new_lake_radius = 0.5 * new_lake.get_width()

            self.play(Transform(self.outer_lake,new_lake))
            shift_list = []

            for i in range(2**n):
                #print "==========="
                #print i
                theta = -TAU/4 + (i + 0.5) * TAU / 2**(n+1)
                v = np.array([np.cos(theta), np.sin(theta),0])
                pos1 = new_lake_center + new_lake_radius * v
                pos2 = new_lake_center - new_lake_radius * v
                ls1 = self.light_sources.submobjects[i]
                ls2 = self.light_sources.submobjects[i+2**n]

                ls_old_loc = np.array(ls1.get_source_point())
                onscreen_old = np.all(np.abs(ls_old_loc[:2]) < 10 * 2**2)
                onscreen_1 = np.all(np.abs(pos1[:2]) < 10 * 2**2)
                onscreen_2 = np.all(np.abs(pos2[:2]) < 10 * 2**2)
                
                if onscreen_old or onscreen_1:
                    print("anim1 for step", n, "part", i)
                    print("------------------ moving from", ls_old_loc[:2], "to", pos1[:2])
                    shift_list.append(ApplyMethod(ls1.move_source_to, pos1, run_time = STEP_RUN_TIME))
                else:
                    ls1.move_source_to(pos1)
                if onscreen_old or onscreen_2:
                    print("anim2 for step", n, "part", i)
                    print("------------------ moving from", ls_old_loc[:2], "to", pos2[:2])
                    shift_list.append(ApplyMethod(ls2.move_source_to, pos2, run_time = STEP_RUN_TIME))
                else:
                    ls2.move_source_to(pos2)
                

                #print shift_list

            self.play(*shift_list)
            print("...done")


        #self.revert_to_original_skipping_status()

        # Now create a straight number line and transform into it
        MAX_N = 7

        origin_point = self.obs_dot.get_center()

        self.number_line = NumberLine(
            x_min = -MAX_N,
            x_max = MAX_N + 1,
            color = WHITE,
            number_at_center = 0,
            stroke_width = LAKE_STROKE_WIDTH,
            stroke_color = LAKE_STROKE_COLOR,
            numbers_with_elongated_ticks = [],
            numbers_to_show = list(range(-MAX_N,MAX_N + 1)),#,2),
            unit_size = LAKE0_RADIUS * TAU/4 / 2 * scale,
            tick_frequency = 1,
            tick_size = LAKE_STROKE_WIDTH,
            number_scale_val = 3,
            line_to_number_buff = LARGE_BUFF,
            label_direction = UP,
        ).shift(origin_point - self.number_line.number_to_point(0)) # .shift(scale * 2.5 * DOWN)

        print("scale ", scale)
        print("number line at", self.number_line.get_center())
        print("should be at", origin_point, "or", OBSERVER_POINT)

        self.number_line.tick_marks.fade(1)
        self.number_line_labels = self.number_line.get_number_mobjects()
        self.wait()

        origin_point = self.number_line.number_to_point(0)
        nl_sources = VMobject()
        pond_sources = VMobject()

        for i in range(-MAX_N,MAX_N+1):
            anchor = self.number_line.number_to_point(2*i + 1)
            ls = self.light_sources_array[i].copy()
            ls.move_source_to(anchor)
            nl_sources.add(ls)
            pond_sources.add(self.light_sources_array[i].copy())

        self.add(pond_sources)
        self.remove(self.light_sources)
        for ls in self.light_sources_array:
            self.remove(ls)

        self.outer_lake.rotate(TAU/8)

        # open sea
        open_sea = Rectangle(
            width = 200 * scale,
            height = 100 * scale,
            stroke_width = LAKE_STROKE_WIDTH,
            stroke_color = LAKE_STROKE_COLOR,
            fill_color = LAKE_COLOR,
            fill_opacity = LAKE_OPACITY,
        ).flip().next_to(self.obs_dot.get_center(),UP,buff = 0)

        self.revert_to_original_skipping_status()

        self.play(
            ReplacementTransform(pond_sources,nl_sources),
            #FadeOut(pond_sources),
            #FadeIn(nl_sources),
            ReplacementTransform(self.outer_lake,open_sea),
            #FadeOut(self.inner_lake)
        )
        self.play(FadeIn(self.number_line))

        self.wait()

        v = 4 * scale * UP
        self.play(
            nl_sources.shift,v,
            morty.shift,v,
            self.number_line.shift,v,
            indicator.shift,v,
            indicator_reading.shift,v,
            open_sea.shift,v,
            self.obs_dot.shift,v,
        )
        self.number_line_labels.shift(v)

        origin_point = self.number_line.number_to_point(0)
        #self.remove(self.obs_dot)
        self.play(
            indicator.move_to, origin_point + scale * UP + 2 * UP,
            indicator_reading.move_to, origin_point + scale * UP + 2 * UP,
            FadeOut(open_sea),
            FadeOut(morty),
            FadeIn(self.number_line_labels),
            FadeIn(self.number_line.tick_marks),
        )

        two_sided_sum = TexMobject("\dots", "+", "{1\over (-11)^2}",\
         "+", "{1\over (-9)^2}", " + ", "{1\over (-7)^2}", " + ", "{1\over (-5)^2}", " + ", \
         "{1\over (-3)^2}", " + ", "{1\over (-1)^2}", " + ", "{1\over 1^2}", " + ", \
         "{1\over 3^2}", " + ", "{1\over 5^2}", " + ", "{1\over 7^2}", " + ", \
         "{1\over 9^2}", " + ", "{1\over 11^2}", " + ", "\dots")

        nb_symbols = len(two_sided_sum.submobjects)

        two_sided_sum.scale(TEX_SCALE)
        
        for (i,submob) in zip(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))

        self.wait()

        for ls in nl_sources.submobjects:
            if ls.get_source_point()[0] < 0:
                self.remove_foreground_mobject(ls.ambient_light)
                self.remove(ls.ambient_light)
            else:
                self.add_foreground_mobject(ls.ambient_light)

        for label in self.number_line_labels.submobjects:
            if label.get_center()[0] <= 0:
                self.remove(label)



        covering_rectangle = Rectangle(
            width = FRAME_X_RADIUS * scale,
            height = 2 * FRAME_Y_RADIUS * scale,
            stroke_width = 0,
            fill_color = BLACK,
            fill_opacity = 1,
        )
        covering_rectangle.next_to(ORIGIN, LEFT, buff = 0)
        #for i in range(10):
        #    self.add_foreground_mobject(nl_sources.submobjects[i])

        self.add_foreground_mobject(indicator)
        self.add_foreground_mobject(indicator_reading)


        half_indicator_reading = TexMobject("{\pi^2 \over 8}").scale(TEX_SCALE)
        half_indicator_reading.move_to(indicator)

        central_plus_sign = two_sided_sum[13]

        self.play(
            FadeIn(covering_rectangle),
            Transform(indicator_reading, half_indicator_reading),
            FadeOut(central_plus_sign)
        )


        equals_sign = TexMobject("=").scale(TEX_SCALE)
        equals_sign.move_to(central_plus_sign)
        p = 2 * scale * LEFT + central_plus_sign.get_center()[1] * UP

        self.play(
            indicator.move_to,p,
            indicator_reading.move_to,p,
            FadeIn(equals_sign),
        )

        self.revert_to_original_skipping_status()

        # show Randy admiring the result
        randy = Randolph(color = MAROON_D).scale(scale).move_to(2*scale*DOWN+5*scale*LEFT)
        self.play(FadeIn(randy))
        self.play(randy.change,"happy")
        self.play(randy.change,"hooray")








class WaitScene(TeacherStudentsScene):

    def construct(self):


        self.teacher_says(TexMobject("{1\over 1^2}+{1\over 3^2}+{1\over 5^2}+{1\over 7^2}+\dots = {\pi^2 \over 8}!"))

        student_q = TextMobject("What about")
        full_sum = TexMobject("{1\over 1^2}+{1\over 2^2}+{1\over 3^2}+{1\over 4^2}+\dots?")
        full_sum.next_to(student_q,RIGHT)
        student_q.add(full_sum)


        self.student_says(student_q, target_mode = "angry")


class FinalSumManipulationScene(PiCreatureScene):

    def construct(self):

        LAKE_COLOR = BLUE
        LAKE_OPACITY = 0.15
        LAKE_STROKE_WIDTH = 5.0
        LAKE_STROKE_COLOR = BLUE
        TEX_SCALE = 0.8

        LIGHT_COLOR2 = RED
        LIGHT_COLOR3 = BLUE

        unit_length = 1.5
        vertical_spacing = 2.5 * DOWN
        switch_on_time = 0.2

        sum_vertical_spacing = 1.5

        randy = self.get_primary_pi_creature()
        randy.set_color(MAROON_D)
        randy.color = MAROON_D
        randy.scale(0.7).flip().to_edge(DOWN + LEFT)
        self.wait()

        ls_template = LightSource(
            radius = 1,
            num_levels = 10,
            max_opacity_ambient = 0.5,
            opacity_function = inverse_quadratic(1,0.75,1)
        )


        odd_range = np.arange(1,9,2)
        even_range = np.arange(2,16,2)
        full_range = np.arange(1,8,1)

        self.number_line1 = NumberLine(
            x_min = 0,
            x_max = 11,
            color = LAKE_STROKE_COLOR,
            number_at_center = 0,
            stroke_width = LAKE_STROKE_WIDTH,
            stroke_color = LAKE_STROKE_COLOR,
            #numbers_to_show = full_range,
            number_scale_val = 0.5,
            numbers_with_elongated_ticks = [],
            unit_size = unit_length,
            tick_frequency = 1,
            line_to_number_buff = MED_SMALL_BUFF,
            include_tip = True,
            label_direction = UP,
        )

        self.number_line1.next_to(2.5 * UP + 3 * LEFT, RIGHT, buff = 0.3)
        self.number_line1.add_numbers()

        odd_lights = VMobject()
        for i in odd_range:
            pos = self.number_line1.number_to_point(i)
            ls = ls_template.copy()
            ls.move_source_to(pos)
            odd_lights.add(ls)

        self.play(
            ShowCreation(self.number_line1, run_time = 5),
        )
        self.wait()


        odd_terms = VMobject()
        for i in odd_range:
            if i == 1:
                term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}",
                    fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR)
            else:
                term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}",
                    fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR)

            term.next_to(self.number_line1.number_to_point(i), DOWN, buff = 1.5)
            odd_terms.add(term)


        for (ls, term) in zip(odd_lights.submobjects, odd_terms.submobjects):
            self.play(
                FadeIn(ls.lighthouse, run_time = switch_on_time),
                SwitchOn(ls.ambient_light, run_time = switch_on_time),
                Write(term, run_time = switch_on_time)
            )

        result1 = TexMobject("{\pi^2\over 8} =", fill_color = LIGHT_COLOR,
            stroke_color = LIGHT_COLOR)
        result1.next_to(self.number_line1, LEFT, buff = 0.5)
        result1.shift(0.87 * vertical_spacing)
        self.play(Write(result1))




        self.number_line2 = self.number_line1.copy()
        self.number_line2.numbers_to_show = full_range
        self.number_line2.shift(2 * vertical_spacing)
        self.number_line2.add_numbers()

        full_lights = VMobject()

        for i in full_range:
            pos = self.number_line2.number_to_point(i)
            ls = ls_template.copy()
            ls.color = LIGHT_COLOR3
            ls.move_source_to(pos)
            full_lights.add(ls)

        self.play(
            ShowCreation(self.number_line2, run_time = 5),
        )
        self.wait()


        full_lighthouses = VMobject()
        full_ambient_lights = VMobject()
        for ls in full_lights:
            full_lighthouses.add(ls.lighthouse)
            full_ambient_lights.add(ls.ambient_light)

        self.play(
            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 = 1.3

        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 ArcHighlightOverlayScene(Scene):

    def construct(self):

        BASELINE_YPOS = -2.5
        OBSERVER_POINT = [0,BASELINE_YPOS,0]
        LAKE0_RADIUS = 1.5
        INDICATOR_RADIUS = 0.6
        TICK_SIZE = 0.5
        LIGHTHOUSE_HEIGHT = 0.2
        LAKE_COLOR = BLUE
        LAKE_OPACITY = 0.15
        LAKE_STROKE_WIDTH = 5.0
        LAKE_STROKE_COLOR = BLUE
        TEX_SCALE = 0.8
        DOT_COLOR = BLUE

        FLASH_TIME = 0.25

        def flash_arcs(n):

            angle = TAU/2**n
            arcs = []
            arcs.append(LabeledArc(angle/2, start_angle = -TAU/4, radius = LAKE0_RADIUS, length = 1))

            for i in range(1,2**n):
                arcs.append(LabeledArc(angle, start_angle = -TAU/4 + (i-0.5)*angle, radius = LAKE0_RADIUS, length = 2))
        
            arcs.append(LabeledArc(angle/2, start_angle = -TAU/4 - angle/2, radius = LAKE0_RADIUS, length = 1))

            self.play(
                FadeIn(arcs[0], run_time = FLASH_TIME)
            )

            for i in range(1,2**n + 1):
                self.play(
                    FadeOut(arcs[i-1], run_time = FLASH_TIME),
                    FadeIn(arcs[i], run_time = FLASH_TIME)
                )

            self.play(
                FadeOut(arcs[2**n], run_time = FLASH_TIME),
            )



class ThumbnailScene(Scene):

    def construct(self):

        equation = TexMobject("1+{1\over 4}+{1\over 9}+{1\over 16}+{1\over 25}+\dots")
        equation.scale(1.5)
        equation.move_to(1.5 * UP)
        q_mark = TexMobject("=?", color = LIGHT_COLOR).scale(5)
        q_mark.next_to(equation, DOWN, buff = 1.5)
        #equation.move_to(2 * UP)
        #q_mark = TexMobject("={\pi^2\over 6}", color = LIGHT_COLOR).scale(3)
        #q_mark.next_to(equation, DOWN, buff = 1)

        lake_radius = 6
        lake_center = ORIGIN
        op_scale = 0.4

        lake = Circle(
            fill_color = LAKE_COLOR, 
            fill_opacity = LAKE_OPACITY, 
            radius = lake_radius,
            stroke_color = LAKE_STROKE_COLOR, 
            stroke_width = LAKE_STROKE_WIDTH,
        )
        lake.move_to(lake_center)

        for i in range(16):
            theta = -TAU/4 + (i + 0.5) * TAU/16
            pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0])
            ls = LightSource(
                radius = 15.0, 
                num_levels = 150,
                max_opacity_ambient = 1.0,
                opacity_function = inverse_quadratic(1,op_scale,1)
            )
            ls.move_source_to(pos)
            lake.add(ls.ambient_light, ls.lighthouse)

        self.add(lake)

        self.add(equation, q_mark)

        self.wait()




class InfiniteCircleScene(PiCreatureScene):

    def construct(self):

        morty = self.get_primary_pi_creature()
        morty.set_color(MAROON_D).flip()
        morty.color = MAROON_D
        morty.scale(0.5).move_to(ORIGIN)

        arrow = Arrow(ORIGIN, 2.4 * RIGHT)
        dot = Dot(color = BLUE).next_to(arrow)
        ellipsis = TexMobject("\dots")

        infsum = VGroup()
        infsum.add(ellipsis.copy())

        for i in range(3):
            infsum.add(arrow.copy().next_to(infsum.submobjects[-1]))
            infsum.add(dot.copy().next_to(infsum.submobjects[-1]))

        infsum.add(arrow.copy().next_to(infsum.submobjects[-1]))
        infsum.add(ellipsis.copy().next_to(infsum.submobjects[-1]))

        infsum.next_to(morty,DOWN, buff = 1)

        self.wait()
        self.play(
            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 RightAnglesOverlay(Scene):

    def construct(self):

        BASELINE_YPOS = -2.5
        OBSERVER_POINT = [0,BASELINE_YPOS,0]
        LAKE0_RADIUS = 1.5 * 2
        INDICATOR_RADIUS = 0.6
        TICK_SIZE = 0.5
        LIGHTHOUSE_HEIGHT = 0.2
        LAKE_COLOR = BLUE
        LAKE_OPACITY = 0.15
        LAKE_STROKE_WIDTH = 5.0
        LAKE_STROKE_COLOR = BLUE
        TEX_SCALE = 0.8
        DOT_COLOR = BLUE

        RIGHT_ANGLE_SIZE = 0.3


        def right_angle(pointA, pointB, pointC, size = 1):

            v1 = pointA - pointB
            v1 = size * v1/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


        lake_center = OBSERVER_POINT + LAKE0_RADIUS * UP
        points = []
        lines = VGroup()
        for i in range(4):
            theta = -TAU/4 + (i+0.5)*TAU/4
            v = np.array([np.cos(theta), np.sin(theta), 0])
            P = lake_center + LAKE0_RADIUS * v
            points.append(P)
            lines.add(Line(lake_center, P, stroke_width = 8))

        self.play(FadeIn(lines))

        self.wait()

        for i in range(4):
            sign = right_angle(points[i-1], lake_center, points[i],RIGHT_ANGLE_SIZE)
            self.play(FadeIn(sign))
            self.play(FadeOut(sign))

        self.wait()

        self.play(FadeOut(lines))

        flash_arcs(3)
