from manimlib.imports import *
from active_projects.diffyq.part1.shared_constructs import *


class Pendulum(VGroup):
    CONFIG = {
        "length": 3,
        "gravity": 9.8,
        "weight_diameter": 0.5,
        "initial_theta": 0.3,
        "omega": 0,
        "damping": 0.1,
        "top_point": 2 * UP,
        "rod_style": {
            "stroke_width": 3,
            "stroke_color": LIGHT_GREY,
            "sheen_direction": UP,
            "sheen_factor": 1,
        },
        "weight_style": {
            "stroke_width": 0,
            "fill_opacity": 1,
            "fill_color": GREY_BROWN,
            "sheen_direction": UL,
            "sheen_factor": 0.5,
            "background_stroke_color": BLACK,
            "background_stroke_width": 3,
            "background_stroke_opacity": 0.5,
        },
        "dashed_line_config": {
            "num_dashes": 25,
            "stroke_color": WHITE,
            "stroke_width": 2,
        },
        "angle_arc_config": {
            "radius": 1,
            "stroke_color": WHITE,
            "stroke_width": 2,
        },
        "velocity_vector_config": {
            "color": RED,
        },
        "theta_label_height": 0.25,
        "set_theta_label_height_cap": False,
        "n_steps_per_frame": 100,
        "include_theta_label": True,
        "include_velocity_vector": False,
        "velocity_vector_multiple": 0.5,
        "max_velocity_vector_length_to_length_ratio": 0.5,
    }

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.create_fixed_point()
        self.create_rod()
        self.create_weight()
        self.rotating_group = VGroup(self.rod, self.weight)
        self.create_dashed_line()
        self.create_angle_arc()
        if self.include_theta_label:
            self.add_theta_label()
        if self.include_velocity_vector:
            self.add_velocity_vector()

        self.set_theta(self.initial_theta)
        self.update()

    def create_fixed_point(self):
        self.fixed_point_tracker = VectorizedPoint(self.top_point)
        self.add(self.fixed_point_tracker)
        return self

    def create_rod(self):
        rod = self.rod = Line(UP, DOWN)
        rod.set_height(self.length)
        rod.set_style(**self.rod_style)
        rod.move_to(self.get_fixed_point(), UP)
        self.add(rod)

    def create_weight(self):
        weight = self.weight = Circle()
        weight.set_width(self.weight_diameter)
        weight.set_style(**self.weight_style)
        weight.move_to(self.rod.get_end())
        self.add(weight)

    def create_dashed_line(self):
        line = self.dashed_line = DashedLine(
            self.get_fixed_point(),
            self.get_fixed_point() + self.length * DOWN,
            **self.dashed_line_config
        )
        line.add_updater(
            lambda l: l.move_to(self.get_fixed_point(), UP)
        )
        self.add_to_back(line)

    def create_angle_arc(self):
        self.angle_arc = always_redraw(lambda: Arc(
            arc_center=self.get_fixed_point(),
            start_angle=-90 * DEGREES,
            angle=self.get_arc_angle_theta(),
            **self.angle_arc_config,
        ))
        self.add(self.angle_arc)

    def get_arc_angle_theta(self):
        # Might be changed in certain scenes
        return self.get_theta()

    def add_velocity_vector(self):
        def make_vector():
            omega = self.get_omega()
            theta = self.get_theta()
            mvlr = self.max_velocity_vector_length_to_length_ratio
            max_len = mvlr * self.rod.get_length()
            vvm = self.velocity_vector_multiple
            multiple = np.clip(
                vvm * omega, -max_len, max_len
            )
            vector = Vector(
                multiple * RIGHT,
                **self.velocity_vector_config,
            )
            vector.rotate(theta, about_point=ORIGIN)
            vector.shift(self.rod.get_end())
            return vector

        self.velocity_vector = always_redraw(make_vector)
        self.add(self.velocity_vector)
        return self

    def add_theta_label(self):
        self.theta_label = always_redraw(self.get_label)
        self.add(self.theta_label)

    def get_label(self):
        label = TexMobject("\\theta")
        label.set_height(self.theta_label_height)
        if self.set_theta_label_height_cap:
            max_height = self.angle_arc.get_width()
            if label.get_height() > max_height:
                label.set_height(max_height)
        top = self.get_fixed_point()
        arc_center = self.angle_arc.point_from_proportion(0.5)
        vect = arc_center - top
        norm = get_norm(vect)
        vect = normalize(vect) * (norm + self.theta_label_height)
        label.move_to(top + vect)
        return label

    #
    def get_theta(self):
        theta = self.rod.get_angle() - self.dashed_line.get_angle()
        theta = (theta + PI) % TAU - PI
        return theta

    def set_theta(self, theta):
        self.rotating_group.rotate(
            theta - self.get_theta()
        )
        self.rotating_group.shift(
            self.get_fixed_point() - self.rod.get_start(),
        )
        return self

    def get_omega(self):
        return self.omega

    def set_omega(self, omega):
        self.omega = omega
        return self

    def get_fixed_point(self):
        return self.fixed_point_tracker.get_location()

    #
    def start_swinging(self):
        self.add_updater(Pendulum.update_by_gravity)

    def end_swinging(self):
        self.remove_updater(Pendulum.update_by_gravity)

    def update_by_gravity(self, dt):
        theta = self.get_theta()
        omega = self.get_omega()
        nspf = self.n_steps_per_frame
        for x in range(nspf):
            d_theta = omega * dt / nspf
            d_omega = op.add(
                -self.damping * omega,
                -(self.gravity / self.length) * np.sin(theta),
            ) * dt / nspf
            theta += d_theta
            omega += d_omega
        self.set_theta(theta)
        self.set_omega(omega)
        return self


class GravityVector(Vector):
    CONFIG = {
        "color": YELLOW,
        "length_multiple": 1 / 9.8,
        # TODO, continually update the length based
        # on the pendulum's gravity?
    }

    def __init__(self, pendulum, **kwargs):
        super().__init__(DOWN, **kwargs)
        self.pendulum = pendulum
        self.scale(self.length_multiple * pendulum.gravity)
        self.attach_to_pendulum(pendulum)

    def attach_to_pendulum(self, pendulum):
        self.add_updater(lambda m: m.shift(
            pendulum.weight.get_center() - self.get_start(),
        ))

    def add_component_lines(self):
        self.component_lines = always_redraw(self.create_component_lines)
        self.add(self.component_lines)

    def create_component_lines(self):
        theta = self.pendulum.get_theta()
        x_new = rotate(RIGHT, theta)
        base = self.get_start()
        tip = self.get_end()
        vect = tip - base
        corner = base + x_new * np.dot(vect, x_new)
        kw = {"dash_length": 0.025}
        return VGroup(
            DashedLine(base, corner, **kw),
            DashedLine(corner, tip, **kw),
        )


class ThetaValueDisplay(VGroup):
    CONFIG = {

    }

    def __init__(self, **kwargs):
        super().__init__(**kwargs)


class ThetaVsTAxes(Axes):
    CONFIG = {
        "x_min": 0,
        "x_max": 8,
        "y_min": -PI / 2,
        "y_max": PI / 2,
        "y_axis_config": {
            "tick_frequency": PI / 8,
            "unit_size": 1.5,
        },
        "axis_config": {
            "color": "#EEEEEE",
            "stroke_width": 2,
            "include_tip": False,
        },
        "graph_style": {
            "stroke_color": GREEN,
            "stroke_width": 3,
            "fill_opacity": 0,
        },
    }

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.add_labels()

    def add_axes(self):
        self.axes = Axes(**self.axes_config)
        self.add(self.axes)

    def add_labels(self):
        x_axis = self.get_x_axis()
        y_axis = self.get_y_axis()

        t_label = self.t_label = TexMobject("t")
        t_label.next_to(x_axis.get_right(), UP, MED_SMALL_BUFF)
        x_axis.label = t_label
        x_axis.add(t_label)
        theta_label = self.theta_label = TexMobject("\\theta(t)")
        theta_label.next_to(y_axis.get_top(), UP, SMALL_BUFF)
        y_axis.label = theta_label
        y_axis.add(theta_label)

        self.y_axis_label = theta_label
        self.x_axis_label = t_label

        x_axis.add_numbers()
        y_axis.add(self.get_y_axis_coordinates(y_axis))

    def get_y_axis_coordinates(self, y_axis):
        texs = [
            # "\\pi \\over 4",
            # "\\pi \\over 2",
            # "3 \\pi \\over 4",
            # "\\pi",
            "\\pi / 4",
            "\\pi / 2",
            "3 \\pi / 4",
            "\\pi",
        ]
        values = np.arange(1, 5) * PI / 4
        labels = VGroup()
        for pos_tex, pos_value in zip(texs, values):
            neg_tex = "-" + pos_tex
            neg_value = -1 * pos_value
            for tex, value in (pos_tex, pos_value), (neg_tex, neg_value):
                if value > self.y_max or value < self.y_min:
                    continue
                symbol = TexMobject(tex)
                symbol.scale(0.5)
                point = y_axis.number_to_point(value)
                symbol.next_to(point, LEFT, MED_SMALL_BUFF)
                labels.add(symbol)
        return labels

    def get_live_drawn_graph(self, pendulum,
                             t_max=None,
                             t_step=1.0 / 60,
                             **style):
        style = merge_dicts_recursively(self.graph_style, style)
        if t_max is None:
            t_max = self.x_max

        graph = VMobject()
        graph.set_style(**style)

        graph.all_coords = [(0, pendulum.get_theta())]
        graph.time = 0
        graph.time_of_last_addition = 0

        def update_graph(graph, dt):
            graph.time += dt
            if graph.time > t_max:
                graph.remove_updater(update_graph)
                return
            new_coords = (graph.time, pendulum.get_theta())
            if graph.time - graph.time_of_last_addition >= t_step:
                graph.all_coords.append(new_coords)
                graph.time_of_last_addition = graph.time
            points = [
                self.coords_to_point(*coords)
                for coords in [*graph.all_coords, new_coords]
            ]
            graph.set_points_smoothly(points)

        graph.add_updater(update_graph)
        return graph


# Scenes
class IntroducePendulum(PiCreatureScene, MovingCameraScene):
    CONFIG = {
        "pendulum_config": {
            "length": 3,
            "top_point": 4 * RIGHT,
            "weight_diameter": 0.35,
            "gravity": 20,
        },
        "theta_vs_t_axes_config": {
            "y_max": PI / 4,
            "y_min": -PI / 4,
            "y_axis_config": {
                "tick_frequency": PI / 16,
                "unit_size": 2,
                "tip_length": 0.3,
            },
            "x_max": 12,
            "axis_config": {
                "stroke_width": 2,
            }
        },
    }

    def setup(self):
        MovingCameraScene.setup(self)
        PiCreatureScene.setup(self)

    def construct(self):
        self.add_pendulum()
        # self.label_pi_creatures()
        self.label_pendulum()
        self.add_graph()
        self.label_function()
        self.show_graph_period()
        self.show_length_and_gravity()
        # self.tweak_length_and_gravity()

    def create_pi_creatures(self):
        randy = Randolph(color=BLUE_C)
        morty = Mortimer(color=MAROON_E)
        creatures = VGroup(randy, morty)
        creatures.scale(0.5)
        creatures.arrange(RIGHT, buff=2.5)
        creatures.to_corner(DR)
        return creatures

    def add_pendulum(self):
        pendulum = self.pendulum = Pendulum(**self.pendulum_config)
        pendulum.start_swinging()
        frame = self.camera_frame
        frame.save_state()
        frame.scale(0.5)
        frame.move_to(pendulum.dashed_line)

        self.add(pendulum, frame)

    def label_pi_creatures(self):
        randy, morty = self.pi_creatures
        randy_label = TextMobject("Physics\\\\", "student")
        morty_label = TextMobject("Physics\\\\", "teacher")
        labels = VGroup(randy_label, morty_label)
        labels.scale(0.5)
        randy_label.next_to(randy, UP, LARGE_BUFF)
        morty_label.next_to(morty, UP, LARGE_BUFF)

        for label, pi in zip(labels, self.pi_creatures):
            label.arrow = Arrow(
                label.get_bottom(), pi.eyes.get_top()
            )
            label.arrow.set_color(WHITE)
            label.arrow.set_stroke(width=5)

        morty.labels = VGroup(
            morty_label,
            morty_label.arrow,
        )

        self.play(
            FadeInFromDown(randy_label),
            GrowArrow(randy_label.arrow),
            randy.change, "hooray",
        )
        self.play(
            Animation(self.pendulum.fixed_point_tracker),
            TransformFromCopy(randy_label[0], morty_label[0]),
            FadeIn(morty_label[1]),
            GrowArrow(morty_label.arrow),
            morty.change, "raise_right_hand",
        )
        self.wait(2)

    def label_pendulum(self):
        pendulum = self.pendulum
        randy, morty = self.pi_creatures
        label = pendulum.theta_label
        rect = SurroundingRectangle(label, buff=0.5 * SMALL_BUFF)
        rect.add_updater(lambda r: r.move_to(label))

        for pi in randy, morty:
            pi.add_updater(
                lambda m: m.look_at(pendulum.weight)
            )

        self.play(randy.change, "pondering")
        self.play(morty.change, "pondering")
        self.wait(3)
        randy.clear_updaters()
        morty.clear_updaters()
        self.play(
            ShowCreationThenFadeOut(rect),
        )
        self.wait()

    def add_graph(self):
        axes = self.axes = ThetaVsTAxes(**self.theta_vs_t_axes_config)
        axes.y_axis.label.next_to(axes.y_axis, UP, buff=0)
        axes.to_corner(UL)

        self.play(
            Restore(
                self.camera_frame,
                rate_func=squish_rate_func(smooth, 0, 0.9),
            ),
            DrawBorderThenFill(
                axes,
                rate_func=squish_rate_func(smooth, 0.5, 1),
                lag_ratio=0.9,
            ),
            Transform(
                self.pendulum.theta_label.copy().clear_updaters(),
                axes.y_axis.label.copy(),
                remover=True,
                rate_func=squish_rate_func(smooth, 0, 0.8),
            ),
            run_time=3,
        )

        self.wait(1.5)
        self.graph = axes.get_live_drawn_graph(self.pendulum)
        self.add(self.graph)

    def label_function(self):
        hm_word = TextMobject("Simple harmonic motion")
        hm_word.scale(1.25)
        hm_word.to_edge(UP)

        formula = TexMobject(
            "=\\theta_0 \\cos(\\sqrt{g / L} t)"
        )
        formula.next_to(
            self.axes.y_axis_label, RIGHT, SMALL_BUFF
        )
        formula.set_stroke(width=0, background=True)

        self.play(FadeInFrom(hm_word, DOWN))
        self.wait()
        self.play(
            Write(formula),
            hm_word.to_corner, UR
        )
        self.wait(4)

    def show_graph_period(self):
        pendulum = self.pendulum
        axes = self.axes

        period = self.period = TAU * np.sqrt(
            pendulum.length / pendulum.gravity
        )
        amplitude = pendulum.initial_theta

        line = Line(
            axes.coords_to_point(0, amplitude),
            axes.coords_to_point(period, amplitude),
        )
        line.shift(SMALL_BUFF * RIGHT)
        brace = Brace(line, UP, buff=SMALL_BUFF)
        brace.add_to_back(brace.copy().set_style(BLACK, 10))
        formula = get_period_formula()
        formula.next_to(brace, UP, SMALL_BUFF)

        self.period_formula = formula
        self.period_brace = brace

        self.play(
            GrowFromCenter(brace),
            FadeInFromDown(formula),
        )
        self.wait(2)

    def show_length_and_gravity(self):
        formula = self.period_formula
        L = formula.get_part_by_tex("L")
        g = formula.get_part_by_tex("g")

        rod = self.pendulum.rod
        new_rod = rod.copy()
        new_rod.set_stroke(BLUE, 7)
        new_rod.add_updater(lambda r: r.put_start_and_end_on(
            *rod.get_start_and_end()
        ))

        g_vect = GravityVector(
            self.pendulum,
            length_multiple=0.5 / 9.8,
        )
        down_vectors = self.get_down_vectors()
        down_vectors.set_color(YELLOW)
        down_vectors.set_opacity(0.5)

        self.play(
            ShowCreationThenDestructionAround(L),
            ShowCreation(new_rod),
        )
        self.play(FadeOut(new_rod))

        self.play(
            ShowCreationThenDestructionAround(g),
            GrowArrow(g_vect),
        )
        self.play(self.get_down_vectors_animation(down_vectors))
        self.wait(6)

        self.gravity_vector = g_vect

    def tweak_length_and_gravity(self):
        pendulum = self.pendulum
        axes = self.axes
        graph = self.graph
        brace = self.period_brace
        formula = self.period_formula
        g_vect = self.gravity_vector
        randy, morty = self.pi_creatures

        graph.clear_updaters()
        period2 = self.period * np.sqrt(2)
        period3 = self.period / np.sqrt(2)
        amplitude = pendulum.initial_theta
        graph2, graph3 = [
            axes.get_graph(
                lambda t: amplitude * np.cos(TAU * t / p),
                color=RED,
            )
            for p in (period2, period3)
        ]
        formula.add_updater(lambda m: m.next_to(
            brace, UP, SMALL_BUFF
        ))

        new_pendulum_config = dict(self.pendulum_config)
        new_pendulum_config["length"] *= 2
        new_pendulum_config["top_point"] += 3.5 * UP
        # new_pendulum_config["initial_theta"] = pendulum.get_theta()
        new_pendulum = Pendulum(**new_pendulum_config)

        down_vectors = self.get_down_vectors()

        self.play(randy.change, "happy")
        self.play(
            ReplacementTransform(pendulum, new_pendulum),
            morty.change, "horrified",
            morty.shift, 3 * RIGHT,
            morty.labels.shift, 3 * RIGHT,
        )
        self.remove(morty, morty.labels)
        g_vect.attach_to_pendulum(new_pendulum)
        new_pendulum.start_swinging()
        self.play(
            ReplacementTransform(graph, graph2),
            brace.stretch, np.sqrt(2), 0, {"about_edge": LEFT},
        )
        self.add(g_vect)
        self.wait(3)

        new_pendulum.gravity *= 4
        g_vect.scale(2)
        self.play(
            FadeOut(graph2),
            self.get_down_vectors_animation(down_vectors)
        )
        self.play(
            FadeIn(graph3),
            brace.stretch, 0.5, 0, {"about_edge": LEFT},
        )
        self.wait(6)

    #
    def get_down_vectors(self):
        down_vectors = VGroup(*[
            Vector(0.5 * DOWN)
            for x in range(10 * 150)
        ])
        down_vectors.arrange_in_grid(10, 150, buff=MED_SMALL_BUFF)
        down_vectors.set_color_by_gradient(BLUE, RED)
        # for vect in down_vectors:
        #     vect.shift(0.1 * np.random.random(3))
        down_vectors.to_edge(RIGHT)
        return down_vectors

    def get_down_vectors_animation(self, down_vectors):
        return LaggedStart(
            *[
                GrowArrow(v, rate_func=there_and_back)
                for v in down_vectors
            ],
            lag_ratio=0.0005,
            run_time=2,
            remover=True
        )


class MultiplePendulumsOverlayed(Scene):
    CONFIG = {
        "initial_thetas": [
            150 * DEGREES,
            90 * DEGREES,
            60 * DEGREES,
            30 * DEGREES,
            10 * DEGREES,
        ],
        "weight_colors": [
            PINK, RED, GREEN, BLUE, GREY,
        ],
        "pendulum_config": {
            "top_point": ORIGIN,
            "length": 3,
        },
    }

    def construct(self):
        pendulums = VGroup(*[
            Pendulum(
                initial_theta=theta,
                weight_style={
                    "fill_color": wc,
                    "fill_opacity": 0.5,
                },
                **self.pendulum_config,
            )
            for theta, wc in zip(
                self.initial_thetas,
                self.weight_colors,
            )
        ])
        for pendulum in pendulums:
            pendulum.start_swinging()
            pendulum.remove(pendulum.theta_label)

        randy = Randolph(color=BLUE_C)
        randy.to_corner(DL)
        randy.add_updater(lambda r: r.look_at(pendulums[0].weight))

        axes = ThetaVsTAxes(
            x_max=20,
            y_axis_config={
                "unit_size": 0.5,
                "tip_length": 0.3,
            },
        )
        axes.to_corner(UL)
        graphs = VGroup(*[
            axes.get_live_drawn_graph(
                pendulum,
                stroke_color=pendulum.weight.get_color(),
                stroke_width=1,
            )
            for pendulum in pendulums
        ])

        self.add(pendulums)
        self.add(axes, *graphs)
        self.play(randy.change, "sassy")
        self.wait(2)
        self.play(Blink(randy))
        self.wait(5)
        self.play(randy.change, "angry")
        self.play(Blink(randy))
        self.wait(10)


class LowAnglePendulum(Scene):
    CONFIG = {
        "pendulum_config": {
            "initial_theta": 20 * DEGREES,
            "length": 2.0,
            "damping": 0,
            "top_point": ORIGIN,
        },
        "axes_config": {
            "y_axis_config": {"unit_size": 0.75},
            "x_axis_config": {
                "unit_size": 0.5,
                "numbers_to_show": range(2, 25, 2),
                "number_scale_val": 0.5,
            },
            "x_max": 25,
            "axis_config": {
                "tip_length": 0.3,
                "stroke_width": 2,
            }
        },
        "axes_corner": UL,
    }

    def construct(self):
        pendulum = Pendulum(**self.pendulum_config)
        axes = ThetaVsTAxes(**self.axes_config)
        axes.center()
        axes.to_corner(self.axes_corner, buff=LARGE_BUFF)
        graph = axes.get_live_drawn_graph(pendulum)

        L = pendulum.length
        g = pendulum.gravity
        theta0 = pendulum.initial_theta
        prediction = axes.get_graph(
            lambda t: theta0 * np.cos(t * np.sqrt(g / L))
        )
        dashed_prediction = DashedVMobject(prediction, num_dashes=300)
        dashed_prediction.set_stroke(WHITE, 1)
        prediction_formula = TexMobject(
            "\\theta_0", "\\cos(\\sqrt{g / L} \\cdot t)"
        )
        prediction_formula.scale(0.75)
        prediction_formula.next_to(
            dashed_prediction, UP, SMALL_BUFF,
        )

        theta0 = prediction_formula.get_part_by_tex("\\theta_0")
        theta0_brace = Brace(theta0, UP, buff=SMALL_BUFF)
        theta0_brace.stretch(0.5, 1, about_edge=DOWN)
        theta0_label = Integer(
            pendulum.initial_theta * 180 / PI,
            unit="^\\circ"
        )
        theta0_label.scale(0.75)
        theta0_label.next_to(theta0_brace, UP, SMALL_BUFF)

        group = VGroup(theta0_brace, theta0_label, prediction_formula)
        group.shift_onto_screen(buff=MED_SMALL_BUFF)

        self.add(axes, dashed_prediction, pendulum)
        self.play(
            ShowCreation(dashed_prediction, run_time=2),
            FadeInFromDown(prediction_formula),
            FadeInFromDown(theta0_brace),
            FadeInFromDown(theta0_label),
        )
        self.play(
            ShowCreationThenFadeAround(theta0_label),
            ShowCreationThenFadeAround(pendulum.theta_label),
        )
        self.wait()

        pendulum.start_swinging()
        self.add(graph)
        self.wait(30)


class ApproxWordsLowAnglePendulum(Scene):
    def construct(self):
        period = TexMobject(
            "\\text{Period}", "\\approx",
            "2\\pi \\sqrt{\\,{L} / {g}}",
            **Lg_formula_config
        )
        checkmark = TexMobject("\\checkmark")
        checkmark.set_color(GREEN)
        checkmark.scale(2)
        checkmark.next_to(period, RIGHT, MED_LARGE_BUFF)

        self.add(period, checkmark)


class MediumAnglePendulum(LowAnglePendulum):
    CONFIG = {
        "pendulum_config": {
            "initial_theta": 50 * DEGREES,
            "n_steps_per_frame": 1000,
        },
        "axes_config": {
            "y_axis_config": {"unit_size": 0.75},
            "y_max": PI / 2,
            "y_min": -PI / 2,
            "axis_config": {
                "tip_length": 0.3,
                "stroke_width": 2,
            }
        },
        "pendulum_shift_vect": 1 * RIGHT,
    }


class MediumHighAnglePendulum(MediumAnglePendulum):
    CONFIG = {
        "pendulum_config": {
            "initial_theta": 90 * DEGREES,
            "n_steps_per_frame": 1000,
        },
    }


class HighAnglePendulum(LowAnglePendulum):
    CONFIG = {
        "pendulum_config": {
            "initial_theta": 175 * DEGREES,
            "n_steps_per_frame": 1000,
            "top_point": 1.5 * DOWN,
            "length": 2,
        },
        "axes_config": {
            "y_axis_config": {"unit_size": 0.5},
            "y_max": PI,
            "y_min": -PI,
            "axis_config": {
                "tip_length": 0.3,
                "stroke_width": 2,
            }
        },
        "pendulum_shift_vect": 1 * RIGHT,
    }


class VeryLowAnglePendulum(LowAnglePendulum):
    CONFIG = {
        "pendulum_config": {
            "initial_theta": 10 * DEGREES,
            "n_steps_per_frame": 1000,
            "top_point": ORIGIN,
            "length": 3,
        },
        "axes_config": {
            "y_axis_config": {"unit_size": 2},
            "y_max": PI / 4,
            "y_min": -PI / 4,
            "axis_config": {
                "tip_length": 0.3,
                "stroke_width": 2,
            }
        },
        "pendulum_shift_vect": 1 * RIGHT,
    }


class WherePendulumLeads(PiCreatureScene):
    def construct(self):
        pendulum = Pendulum(
            top_point=UP,
            length=3,
            gravity=20,
        )
        pendulum.start_swinging()

        l_title = TextMobject("Linearization")
        l_title.scale(1.5)
        l_title.to_corner(UL)
        c_title = TextMobject("Chaos")
        c_title.scale(1.5)
        c_title.move_to(l_title)
        c_title.move_to(
            c_title.get_center() * np.array([-1, 1, 1])
        )

        get_theta = pendulum.get_theta
        spring = always_redraw(
            lambda: ParametricFunction(
                lambda t: np.array([
                    np.cos(TAU * t) + (1.4 + get_theta()) * t,
                    np.sin(TAU * t) - 0.5,
                    0,
                ]),
                t_min=-0.5,
                t_max=7,
                color=GREY,
                sheen_factor=1,
                sheen_direction=UL,
            ).scale(0.2).to_edge(LEFT, buff=0)
        )
        spring_rect = SurroundingRectangle(
            spring, buff=MED_LARGE_BUFF,
            stroke_width=0,
            fill_color=BLACK,
            fill_opacity=0,
        )

        weight = Dot(radius=0.25)
        weight.add_updater(lambda m: m.move_to(
            spring.points[-1]
        ))
        weight.set_color(BLUE)
        weight.set_sheen(1, UL)
        spring_system = VGroup(spring, weight)

        linear_formula = TexMobject(
            "\\frac{d \\vec{\\textbf{x}}}{dt}="
            "A\\vec{\\textbf{x}}"
        )
        linear_formula.next_to(spring, UP, LARGE_BUFF)
        linear_formula.match_x(l_title)

        randy = self.pi_creature
        randy.set_height(2)
        randy.center()
        randy.to_edge(DOWN)
        randy.shift(3 * LEFT)
        q_marks = TexMobject("???")
        q_marks.next_to(randy, UP)

        self.add(pendulum, randy)
        self.play(
            randy.change, "pondering", pendulum,
            FadeInFromDown(q_marks, lag_ratio=0.3)
        )
        self.play(randy.look_at, pendulum)
        self.wait(5)
        self.play(
            Animation(VectorizedPoint(pendulum.get_top())),
            FadeOutAndShift(q_marks, UP, lag_ratio=0.3),
        )
        self.add(spring_system)
        self.play(
            FadeOut(spring_rect),
            FadeInFrom(linear_formula, UP),
            FadeInFromDown(l_title),
        )
        self.play(FadeInFromDown(c_title))
        self.wait(8)


class LongDoublePendulum(ExternallyAnimatedScene):
    pass


class AnalyzePendulumForce(MovingCameraScene):
    CONFIG = {
        "pendulum_config": {
            "length": 5,
            "top_point": 3.5 * UP,
            "initial_theta": 60 * DEGREES,
            "set_theta_label_height_cap": True,
        },
        "g_vect_config": {
            "length_multiple": 0.25,
        },
        "tan_line_color": BLUE,
        "perp_line_color": PINK,
    }

    def construct(self):
        self.add_pendulum()
        self.show_arc_length()
        self.add_g_vect()
        self.show_constraint()
        self.break_g_vect_into_components()
        self.show_angle_geometry()
        self.show_gsin_formula()
        self.show_sign()
        self.show_acceleration_formula()
        # self.ask_about_what_to_do()

        # self.emphasize_theta()
        # self.show_angular_velocity()
        # self.show_angular_acceleration()
        # self.circle_g_sin_formula()

    def add_pendulum(self):
        pendulum = Pendulum(**self.pendulum_config)
        theta_tracker = ValueTracker(pendulum.get_theta())
        pendulum.add_updater(lambda p: p.set_theta(
            theta_tracker.get_value()
        ))

        self.add(pendulum)
        self.pendulum = pendulum
        self.theta_tracker = theta_tracker

    def show_arc_length(self):
        pendulum = self.pendulum
        angle = pendulum.get_theta()
        height = pendulum.length
        top = pendulum.get_fixed_point()

        line = Line(UP, DOWN)
        line.set_height(height)
        line.move_to(top, UP)
        arc = always_redraw(lambda: Arc(
            start_angle=-90 * DEGREES,
            angle=pendulum.get_theta(),
            arc_center=pendulum.get_fixed_point(),
            radius=pendulum.length,
            stroke_color=GREEN,
        ))

        brace = Brace(Line(ORIGIN, 5 * UP), RIGHT)
        brace.point = VectorizedPoint(brace.get_right())
        brace.add(brace.point)
        brace.set_height(angle)
        brace.move_to(ORIGIN, DL)
        brace.apply_complex_function(np.exp)
        brace.scale(height)
        brace.rotate(-90 * DEGREES)
        brace.move_to(arc)
        brace.shift(MED_SMALL_BUFF * normalize(
            arc.point_from_proportion(0.5) - top
        ))
        x_sym = TexMobject("x")
        x_sym.set_color(GREEN)
        x_sym.next_to(brace.point, DR, buff=SMALL_BUFF)

        rhs = TexMobject("=", "L", "\\theta")
        rhs.set_color_by_tex("\\theta", BLUE)
        rhs.next_to(x_sym, RIGHT)
        rhs.shift(0.7 * SMALL_BUFF * UP)
        line_L = TexMobject("L")
        line_L.next_to(
            pendulum.rod.get_center(), UR, SMALL_BUFF,
        )

        self.play(
            ShowCreation(arc),
            Rotate(line, angle, about_point=top),
            UpdateFromAlphaFunc(
                line, lambda m, a: m.set_stroke(
                    width=2 * there_and_back(a)
                )
            ),
            GrowFromPoint(
                brace, line.get_bottom(),
                path_arc=angle
            ),
        )
        self.play(FadeInFrom(x_sym, UP))
        self.wait()

        # Show equation
        line.set_stroke(BLUE, 5)
        self.play(
            ShowCreationThenFadeOut(line),
            FadeInFromDown(line_L)
        )
        self.play(
            TransformFromCopy(
                line_L, rhs.get_part_by_tex("L")
            ),
            Write(rhs.get_part_by_tex("="))
        )
        self.play(
            TransformFromCopy(
                pendulum.theta_label,
                rhs.get_parts_by_tex("\\theta"),
            )
        )
        self.add(rhs)

        x_eq = VGroup(x_sym, rhs)

        self.play(
            FadeOut(brace),
            x_eq.rotate, angle / 2,
            x_eq.next_to, arc.point_from_proportion(0.5),
            UL, {"buff": -MED_SMALL_BUFF}
        )

        self.x_eq = x_eq
        self.arc = arc
        self.line_L = line_L

    def add_g_vect(self):
        pendulum = self.pendulum

        g_vect = self.g_vect = GravityVector(
            pendulum, **self.g_vect_config,
        )
        g_word = self.g_word = TextMobject("Gravity")
        g_word.rotate(-90 * DEGREES)
        g_word.scale(0.75)
        g_word.add_updater(lambda m: m.next_to(
            g_vect, RIGHT, buff=-SMALL_BUFF,
        ))

        self.play(
            GrowArrow(g_vect),
            FadeInFrom(g_word, UP, lag_ratio=0.1),
        )
        self.wait()

    def show_constraint(self):
        pendulum = self.pendulum

        arcs = VGroup()
        for u in [-1, 2, -1]:
            d_theta = 40 * DEGREES * u
            arc = Arc(
                start_angle=pendulum.get_theta() - 90 * DEGREES,
                angle=d_theta,
                radius=pendulum.length,
                arc_center=pendulum.get_fixed_point(),
                stroke_width=2,
                stroke_color=YELLOW,
                stroke_opacity=0.5,
            )
            self.play(
                self.theta_tracker.increment_value, d_theta,
                ShowCreation(arc)
            )
            arcs.add(arc)
        self.play(FadeOut(arcs))

    def break_g_vect_into_components(self):
        g_vect = self.g_vect
        g_vect.component_lines = always_redraw(
            g_vect.create_component_lines
        )
        tan_line, perp_line = g_vect.component_lines
        g_vect.tangent = always_redraw(lambda: Arrow(
            tan_line.get_start(),
            tan_line.get_end(),
            buff=0,
            color=self.tan_line_color,
        ))
        g_vect.perp = always_redraw(lambda: Arrow(
            perp_line.get_start(),
            perp_line.get_end(),
            buff=0,
            color=self.perp_line_color,
        ))

        self.play(ShowCreation(g_vect.component_lines))
        self.play(GrowArrow(g_vect.tangent))
        self.wait()
        self.play(GrowArrow(g_vect.perp))
        self.wait()

    def show_angle_geometry(self):
        g_vect = self.g_vect

        arc = Arc(
            start_angle=90 * DEGREES,
            angle=self.pendulum.get_theta(),
            radius=0.5,
            arc_center=g_vect.get_end(),
        )
        q_mark = TexMobject("?")
        q_mark.next_to(arc.get_center(), UL, SMALL_BUFF)
        theta_label = TexMobject("\\theta")
        theta_label.move_to(q_mark)

        self.add(g_vect)
        self.play(
            ShowCreation(arc),
            Write(q_mark)
        )
        self.play(ShowCreationThenFadeAround(q_mark))
        self.wait()
        self.play(ShowCreationThenFadeAround(
            self.pendulum.theta_label
        ))
        self.play(
            TransformFromCopy(
                self.pendulum.theta_label,
                theta_label,
            ),
            FadeOut(q_mark)
        )
        self.wait()
        self.play(WiggleOutThenIn(g_vect.tangent))
        self.play(WiggleOutThenIn(
            Line(
                *g_vect.get_start_and_end(),
                buff=0,
            ).add_tip().match_style(g_vect),
            remover=True
        ))
        self.wait()
        self.play(
            FadeOut(arc),
            FadeOut(theta_label),
        )

    def show_gsin_formula(self):
        g_vect = self.g_vect
        g_word = self.g_word
        g_word.clear_updaters()

        g_term = self.g_term = TexMobject("-g")
        g_term.add_updater(lambda m: m.next_to(
            g_vect,
            RIGHT if self.pendulum.get_theta() >= 0 else LEFT,
            SMALL_BUFF
        ))

        def create_vect_label(vect, tex, direction):
            label = TexMobject(tex)
            label.set_stroke(width=0, background=True)
            label.add_background_rectangle()
            label.scale(0.7)
            max_width = 0.9 * vect.get_length()
            if label.get_width() > max_width:
                label.set_width(max_width)
            angle = vect.get_angle()
            angle = (angle + PI / 2) % PI - PI / 2
            label.next_to(ORIGIN, direction, SMALL_BUFF)
            label.rotate(angle, about_point=ORIGIN)
            label.shift(vect.get_center())
            return label

        g_sin_label = always_redraw(lambda: create_vect_label(
            g_vect.tangent, "-g\\sin(\\theta)", UP,
        ))
        g_cos_label = always_redraw(lambda: create_vect_label(
            g_vect.perp, "-g\\cos(\\theta)", DOWN,
        ))

        self.play(
            ReplacementTransform(g_word[0][0], g_term[0][1]),
            FadeOut(g_word[0][1:]),
            Write(g_term[0][0]),
        )
        self.add(g_term)
        self.wait()
        for label in g_sin_label, g_cos_label:
            self.play(
                GrowFromPoint(label[0], g_term.get_center()),
                TransformFromCopy(g_term, label[1][:2]),
                GrowFromPoint(label[1][2:], g_term.get_center()),
                remover=True
            )
            self.add(label)
            self.wait()

        self.g_sin_label = g_sin_label
        self.g_cos_label = g_cos_label

    def show_sign(self):
        get_theta = self.pendulum.get_theta
        theta_decimal = DecimalNumber(include_sign=True)
        theta_decimal.add_updater(lambda d: d.set_value(
            get_theta()
        ))
        theta_decimal.add_updater(lambda m: m.next_to(
            self.pendulum.theta_label, DOWN
        ))
        theta_decimal.add_updater(lambda m: m.set_color(
            GREEN if get_theta() > 0 else RED
        ))

        self.play(
            FadeInFrom(theta_decimal, UP),
            FadeOut(self.x_eq),
            FadeOut(self.line_L),
        )
        self.set_theta(-60 * DEGREES, run_time=4)
        self.set_theta(60 * DEGREES, run_time=4)
        self.play(
            FadeOut(theta_decimal),
            FadeIn(self.x_eq),
        )

    def show_acceleration_formula(self):
        x_eq = self.x_eq
        g_sin_theta = self.g_sin_label

        equation = TexMobject(
            "a", "=",
            "\\ddot", "x",
            "=",
            "-", "g", "\\sin\\big(", "\\theta", "\\big)",
        )
        equation.to_edge(LEFT)

        second_deriv = equation[2:4]
        x_part = equation.get_part_by_tex("x")
        x_part.set_color(GREEN)
        a_eq = equation[:2]
        eq2 = equation.get_parts_by_tex("=")[1]
        rhs = equation[5:]

        second_deriv_L_form = TexMobject(
            "L", "\\ddot", "\\theta"
        )
        second_deriv_L_form.move_to(second_deriv, DOWN)
        eq3 = TexMobject("=")
        eq3.rotate(90 * DEGREES)
        eq3.next_to(second_deriv_L_form, UP)

        g_L_frac = TexMobject(
            "-", "{g", "\\over", "L}"
        )
        g_L_frac.move_to(rhs[:2], LEFT)
        g_L_frac.shift(SMALL_BUFF * UP / 2)

        mu_term = TexMobject(
            "-\\mu", "\\dot", "\\theta",
        )
        mu_term.next_to(g_L_frac, LEFT)
        mu_term.shift(SMALL_BUFF * UP / 2)

        mu_brace = Brace(mu_term, UP)
        mu_word = mu_brace.get_text("Air resistance")

        for mob in equation, second_deriv_L_form, mu_term:
            mob.set_color_by_tex("\\theta", BLUE)

        self.play(
            TransformFromCopy(x_eq[0], x_part),
            Write(equation[:3]),
        )
        self.wait()
        self.play(
            Write(eq2),
            TransformFromCopy(g_sin_theta, rhs)
        )
        self.wait()
        #
        self.show_acceleration_at_different_angles()
        #
        self.play(
            FadeInFromDown(second_deriv_L_form),
            Write(eq3),
            second_deriv.next_to, eq3, UP,
            a_eq.shift, SMALL_BUFF * LEFT,
            eq2.shift, SMALL_BUFF * RIGHT,
            rhs.shift, SMALL_BUFF * RIGHT,
        )
        self.wait()
        self.wait()
        self.play(
            FadeOut(a_eq),
            FadeOut(second_deriv),
            FadeOut(eq3),
            ReplacementTransform(
                second_deriv_L_form.get_part_by_tex("L"),
                g_L_frac.get_part_by_tex("L"),
            ),
            ReplacementTransform(
                equation.get_part_by_tex("-"),
                g_L_frac.get_part_by_tex("-"),
            ),
            ReplacementTransform(
                equation.get_part_by_tex("g"),
                g_L_frac.get_part_by_tex("g"),
            ),
            Write(g_L_frac.get_part_by_tex("\\over")),
            rhs[2:].next_to, g_L_frac, RIGHT, {"buff": SMALL_BUFF},
        )
        self.wait()
        self.play(
            GrowFromCenter(mu_term),
            VGroup(eq2, second_deriv_L_form[1:]).next_to,
            mu_term, LEFT,
        )
        self.play(
            GrowFromCenter(mu_brace),
            FadeInFromDown(mu_word),
        )

    def show_acceleration_at_different_angles(self):
        to_fade = VGroup(
            self.g_cos_label,
            self.g_vect.perp,
        )
        new_comp_line_sytle = {
            "stroke_width": 0.5,
            "stroke_opacity": 0.25,
        }

        self.play(
            FadeOut(self.x_eq),
            to_fade.set_opacity, 0.25,
            self.g_vect.component_lines.set_style,
            new_comp_line_sytle
        )
        self.g_vect.component_lines.add_updater(
            lambda m: m.set_style(**new_comp_line_sytle)
        )
        for mob in to_fade:
            mob.add_updater(lambda m: m.set_opacity(0.25))

        self.set_theta(0)
        self.wait(2)
        self.set_theta(89.9 * DEGREES, run_time=3)
        self.wait(2)
        self.set_theta(
            60 * DEGREES,
            FadeIn(self.x_eq),
            run_time=2,
        )
        self.wait()

    def ask_about_what_to_do(self):
        g_vect = self.g_vect
        g_sin_label = self.g_sin_label
        angle = g_vect.tangent.get_angle()
        angle = (angle - PI) % TAU

        randy = You()
        randy.to_corner(DL)
        bubble = randy.get_bubble(
            height=2,
            width=3.5,
        )
        g_sin_copy = g_sin_label.copy()
        g_sin_copy.remove(g_sin_copy[0])
        g_sin_copy.generate_target()
        g_sin_copy.target.scale(1 / 0.75)
        g_sin_copy.target.rotate(-angle)
        a_eq = TexMobject("a=")
        thought_term = VGroup(a_eq, g_sin_copy.target)
        thought_term.arrange(RIGHT, buff=SMALL_BUFF)
        thought_term.move_to(bubble.get_bubble_center())

        rect = SurroundingRectangle(g_sin_copy.target)
        rect.rotate(angle)
        rect.move_to(g_sin_label)

        randy.save_state()
        randy.fade(1)
        self.play(randy.restore, randy.change, "pondering")
        self.play(ShowCreationThenFadeOut(rect))
        self.play(
            ShowCreation(bubble),
            Write(a_eq),
            MoveToTarget(g_sin_copy),
            randy.look_at, bubble,
        )
        thought_term.remove(g_sin_copy.target)
        thought_term.add(g_sin_copy)
        self.play(Blink(randy))
        self.wait()
        self.play(
            ShowCreationThenDestruction(
                thought_term.copy().set_style(
                    stroke_color=YELLOW,
                    stroke_width=2,
                    fill_opacity=0,
                ),
                run_time=2,
                lag_ratio=0.2,
            ),
            randy.change, "confused", thought_term,
        )
        self.play(Blink(randy))
        self.play(
            FadeOut(randy),
            FadeOut(bubble),
            thought_term.next_to, self.pendulum, DOWN, LARGE_BUFF
        )

        self.accleration_equation = thought_term

    def emphasize_theta(self):
        pendulum = self.pendulum

        self.play(FocusOn(pendulum.theta_label))
        self.play(Indicate(pendulum.theta_label))

        pendulum_copy = pendulum.deepcopy()
        pendulum_copy.clear_updaters()
        pendulum_copy.fade(1)
        pendulum_copy.start_swinging()

        def new_updater(p):
            p.set_theta(pendulum_copy.get_theta())
        pendulum.add_updater(new_updater)

        self.add(pendulum_copy)
        self.wait(5)
        pendulum_copy.end_swinging()
        self.remove(pendulum_copy)
        pendulum.remove_updater(new_updater)
        self.update_mobjects(0)

    def show_angular_velocity(self):
        pass

    def show_angular_acceleration(self):
        pass

    def circle_g_sin_formula(self):
        self.play(
            ShowCreationThenFadeAround(
                self.accleration_equation
            )
        )

    #
    def set_theta(self, value, *added_anims, **kwargs):
        kwargs["run_time"] = kwargs.get("run_time", 2)
        self.play(
            self.theta_tracker.set_value, value,
            *added_anims,
            **kwargs,
        )


class BuildUpEquation(Scene):
    CONFIG = {
        "tex_config": {
            "tex_to_color_map": {
                "{a}": YELLOW,
                "{v}": RED,
                "{x}": GREEN,
                "\\theta": BLUE,
                "{L}": WHITE,
            }
        }
    }

    def construct(self):
        # self.add_center_line()
        self.show_derivatives()
        self.show_theta_double_dot_equation()
        self.talk_about_sine_component()
        self.add_air_resistance()

    def add_center_line(self):
        line = Line(UP, DOWN)
        line.set_height(FRAME_HEIGHT)
        line.set_stroke(WHITE, 1)
        self.add(line)

    def show_derivatives(self):
        a_eq = TexMobject(
            "{a}", "=", "{d{v} \\over dt}",
            **self.tex_config,
        )
        v_eq = TexMobject(
            "{v}", "=", "{d{x} \\over dt}",
            **self.tex_config,
        )
        x_eq = TexMobject(
            "{x} = {L} \\theta",
            **self.tex_config,
        )
        eqs = VGroup(a_eq, v_eq, x_eq)
        eqs.arrange(DOWN, buff=LARGE_BUFF)
        eqs.to_corner(UL)

        v_rhs = TexMobject(
            "={L}{d\\theta \\over dt}",
            "=", "{L}\\dot{\\theta}",
            **self.tex_config,
        )

        v_rhs.next_to(v_eq, RIGHT, SMALL_BUFF)
        v_rhs.shift(
            UP * (v_eq[1].get_bottom()[1] - v_rhs[0].get_bottom()[1])
        )
        a_rhs = TexMobject(
            "={L}{d", "\\dot{\\theta}", "\\over dt}",
            "=", "{L}\\ddot{\\theta}",
            **self.tex_config,
        )
        a_rhs.next_to(a_eq, RIGHT, SMALL_BUFF)
        a_rhs.shift(
            UP * (a_eq[1].get_bottom()[1] - a_rhs[0].get_bottom()[1])
        )

        # a_eq
        self.play(Write(a_eq))
        self.wait()

        # v_eq
        self.play(
            TransformFromCopy(
                a_eq.get_part_by_tex("{v}"),
                v_eq.get_part_by_tex("{v}"),
            )
        )
        self.play(TransformFromCopy(v_eq[:1], v_eq[1:]))
        self.wait()

        # x_eq
        self.play(
            TransformFromCopy(
                v_eq.get_part_by_tex("{x}"),
                x_eq.get_part_by_tex("{x}"),
            )
        )
        self.play(Write(x_eq[1:]))
        self.wait()
        for tex in "L", "\\theta":
            self.play(ShowCreationThenFadeAround(
                x_eq.get_part_by_tex(tex)
            ))
        self.wait()

        # v_rhs
        self.play(*[
            TransformFromCopy(
                x_eq.get_part_by_tex(tex),
                v_rhs.get_part_by_tex(tex),
            )
            for tex in ("=", "{L}", "\\theta")
        ])
        self.play(
            TransformFromCopy(v_eq[-3], v_rhs[2]),
            TransformFromCopy(v_eq[-1], v_rhs[4]),
        )
        self.wait()
        self.play(
            Write(v_rhs[-5]),
            TransformFromCopy(*v_rhs.get_parts_by_tex("{L}")),
            TransformFromCopy(v_rhs[3:4], v_rhs[-3:])
        )
        self.wait()
        self.play(ShowCreationThenFadeAround(v_rhs[2:4]))
        self.play(ShowCreationThenFadeAround(v_rhs[4]))
        self.wait()

        # a_rhs
        self.play(*[
            TransformFromCopy(
                v_rhs.get_parts_by_tex(tex)[-1],
                a_rhs.get_part_by_tex(tex),
            )
            for tex in ("=", "{L}", "\\theta", "\\dot")
        ])
        self.play(
            TransformFromCopy(a_eq[-3], a_rhs[2]),
            TransformFromCopy(a_eq[-1], a_rhs[6]),
        )
        self.wait()
        self.play(
            Write(a_rhs[-5]),
            TransformFromCopy(*a_rhs.get_parts_by_tex("{L}")),
            TransformFromCopy(a_rhs[3:4], a_rhs[-3:]),
        )
        self.wait()

        self.equations = VGroup(
            a_eq, v_eq, x_eq,
            v_rhs, a_rhs,
        )

    def show_theta_double_dot_equation(self):
        equations = self.equations
        a_deriv = equations[0]
        a_rhs = equations[-1][-5:].copy()

        shift_vect = 1.5 * DOWN

        equals = TexMobject("=")
        equals.rotate(90 * DEGREES)
        equals.next_to(a_deriv[0], UP, MED_LARGE_BUFF)
        g_sin_eq = TexMobject(
            "-", "g", "\\sin", "(", "\\theta", ")",
            **self.tex_config,
        )
        g_sin_eq.next_to(
            equals, UP,
            buff=MED_LARGE_BUFF,
            aligned_edge=LEFT,
        )
        g_sin_eq.to_edge(LEFT)
        g_sin_eq.shift(shift_vect)

        shift_vect += (
            g_sin_eq[1].get_center() -
            a_deriv[0].get_center()
        )[0] * RIGHT

        equals.shift(shift_vect)
        a_rhs.shift(shift_vect)

        self.play(
            equations.shift, shift_vect,
            Write(equals),
            GrowFromPoint(
                g_sin_eq, 2 * RIGHT + 3 * DOWN
            )
        )
        self.wait()
        self.play(
            a_rhs.next_to, g_sin_eq, RIGHT,
            a_rhs.shift, SMALL_BUFF * UP,
        )
        self.wait()

        # Fade equations
        self.play(
            FadeOut(equals),
            equations.shift, DOWN,
            equations.fade, 0.5,
        )

        # Rotate sides
        equals, L, ddot, theta, junk = a_rhs
        L_dd_theta = VGroup(L, ddot, theta)
        minus, g, sin, lp, theta2, rp = g_sin_eq
        m2, g2, over, L2 = frac = TexMobject("-", "{g", "\\over", "L}")
        frac.next_to(equals, RIGHT)

        self.play(
            L_dd_theta.next_to, equals, LEFT,
            L_dd_theta.shift, SMALL_BUFF * UP,
            g_sin_eq.next_to, equals, RIGHT,
            path_arc=PI / 2,
        )
        self.play(
            ReplacementTransform(g, g2),
            ReplacementTransform(minus, m2),
            ReplacementTransform(L, L2),
            Write(over),
            g_sin_eq[2:].next_to, over, RIGHT, SMALL_BUFF,
        )
        self.wait()

        # Surround
        rect = SurroundingRectangle(VGroup(g_sin_eq, frac, ddot))
        rect.stretch(1.1, 0)
        dashed_rect = DashedVMobject(
            rect, num_dashes=50, positive_space_ratio=1,
        )
        dashed_rect.shuffle()
        dashed_rect.save_state()
        dashed_rect.space_out_submobjects(1.1)
        for piece in dashed_rect:
            piece.rotate(90 * DEGREES)
        dashed_rect.fade(1)
        self.play(Restore(dashed_rect, lag_ratio=0.05))
        dashed_rect.generate_target()
        dashed_rect.target.space_out_submobjects(0.9)
        dashed_rect.target.fade(1)
        for piece in dashed_rect.target:
            piece.rotate(90 * DEGREES)
        self.play(MoveToTarget(
            dashed_rect,
            lag_ratio=0.05,
            remover=True
        ))
        self.wait()

        self.main_equation = VGroup(
            ddot, theta, equals,
            m2, L2, over, g2,
            sin, lp, theta2, rp,
        )

    def talk_about_sine_component(self):
        main_equation = self.main_equation
        gL_part = main_equation[4:7]
        sin_part = main_equation[7:]
        sin = sin_part[0]

        morty = Mortimer(height=1.5)
        morty.next_to(sin, DR, buff=LARGE_BUFF)
        morty.add_updater(lambda m: m.look_at(sin))

        self.play(ShowCreationThenFadeAround(gL_part))
        self.wait()
        self.play(ShowCreationThenFadeAround(sin_part))
        self.wait()

        self.play(FadeIn(morty))
        sin.save_state()
        self.play(
            morty.change, "angry",
            sin.next_to, morty, LEFT, {"aligned_edge": UP},
        )
        self.play(Blink(morty))
        morty.clear_updaters()
        self.play(
            morty.change, "concerned_musician",
            morty.look, DR,
        )
        self.play(Restore(sin))
        self.play(FadeOut(morty))
        self.wait()

        # Emphasize theta as input
        theta = sin_part[2]
        arrow = Vector(0.5 * UP, color=WHITE)
        arrow.next_to(theta, DOWN, SMALL_BUFF)
        word = TextMobject("Input")
        word.next_to(arrow, DOWN)

        self.play(
            FadeInFrom(word, UP),
            GrowArrow(arrow)
        )
        self.play(
            ShowCreationThenDestruction(
                theta.copy().set_style(
                    fill_opacity=0,
                    stroke_width=2,
                    stroke_color=YELLOW,
                ),
                lag_ratio=0.1,
            )
        )
        self.play(FadeOut(arrow), FadeOut(word))

    def add_air_resistance(self):
        main_equation = self.main_equation
        tdd_eq = main_equation[:3]
        rhs = main_equation[3:]

        new_term = TexMobject(
            "-", "\\mu", "\\dot{", "\\theta}",
        )
        new_term.set_color_by_tex("\\theta", BLUE)
        new_term.move_to(main_equation)
        new_term.shift(0.5 * SMALL_BUFF * UP)
        new_term[0].align_to(rhs[0], UP)

        brace = Brace(new_term, DOWN)
        words = brace.get_text("Air resistance")

        self.play(
            FadeInFromDown(new_term),
            tdd_eq.next_to, new_term, LEFT,
            tdd_eq.align_to, tdd_eq, UP,
            rhs.next_to, new_term, RIGHT,
            rhs.align_to, rhs, UP,
        )
        self.play(
            GrowFromCenter(brace),
            Write(words)
        )
        self.wait()


class SimpleDampenedPendulum(Scene):
    def construct(self):
        pendulum = Pendulum(
            top_point=ORIGIN,
            initial_theta=150 * DEGREES,
            mu=0.5,
        )
        self.add(pendulum)
        pendulum.start_swinging()
        self.wait(20)


class NewSceneName(Scene):
    def construct(self):
        pass
