from manim import *
from manim_voiceover import VoiceoverScene
from manim_voiceover.services.azure import AzureService
from vectors import Vector

from pyrr import Vector3

def create_camera():
    cam = SVGMobject("resources/Camera.svg").scale(0.25)
    cam.set_stroke(color=BLUE_A, width=2)
    cam.set_fill(color=BLUE_B)
    return cam

class CreateCircle(VoiceoverScene):
    def construct(self):

        self.set_speech_service(
            AzureService(
                voice="en-US-JennyNeural",
                style="newscast",
            )
        )

        STARTING_DIST = 3

        NINETY_DEG_LINE_COLOR = BLUE
        CIRCLE_RADIUS = 1.0

        circle_radius_l = None
        circle_radius_l_brace = None
        circle_radius_l_brace_text = None

        FOV = 45.0 * DEGREES
        HALF_FOV = FOV * 0.5
        ANGLE_RADIUS = 0.5
        
        camera_icon = create_camera()
        camera_origin = Dot(ORIGIN - RIGHT * STARTING_DIST)

        with self.voiceover(
            """We have a camera that is looking towards a sphere. We would like to calculate the ideal distance so that the camera's view frustrum grazes our sphere.
            We shall first define the <bookmark mark='sph_radius'/>radius of the sphere as 'r'.
            """
        ):
            main_circle = Circle(arc_center=ORIGIN + RIGHT * STARTING_DIST, radius=CIRCLE_RADIUS)
            
            self.play(Create(main_circle), DrawBorderThenFill(camera_icon.next_to(camera_origin, LEFT)), GrowFromCenter(camera_origin))

            circle_radius_l = Line(main_circle.get_center(), main_circle.get_center() + UP * main_circle.radius)
            circle_radius_l_brace = Brace(circle_radius_l, direction=RIGHT)

            self.wait_until_bookmark("sph_radius")
            circle_radius_l_brace_text = circle_radius_l_brace.get_text("r")
            self.play(Create(circle_radius_l), GrowFromCenter(circle_radius_l_brace), FadeIn(circle_radius_l_brace_text))

        frustrum_fov_lines = []
        fov_angle = None
        fov_symbol = None

        with self.voiceover(
            """We shall now place our camera and begin defining its <bookmark mark='cam_frustrum'/>frustrum.
            In this 2D simplification, the
            camera frustrum can be thought of as an isosceles triangle with the apex angle being our field of view, that we will name <bookmark mark='cam_fov_angle'/>'f'."""
        ):
            self.wait_until_bookmark("cam_frustrum")

            frustrum_base_line = Line(camera_origin.get_center(), main_circle.get_center())            
            #frustrum_base_line = Line(camera_origin.get_center(), frustrum_base_line.get_center() + frustrum_base_line.get_unit_vector() * frustrum_base_line.get_length())

            frustrum_fov_lines = [frustrum_base_line, frustrum_base_line.copy()]

            self.play(*[Create(i) for i in frustrum_fov_lines])

            new_frustrum_fov_lines = [line.copy().rotate(-HALF_FOV + (HALF_FOV*2*i), about_point=camera_origin.get_center()) for i, line in enumerate(frustrum_fov_lines)]

            fov_angle = Angle(new_frustrum_fov_lines[0], new_frustrum_fov_lines[1], radius=ANGLE_RADIUS)
            self.play(GrowFromCenter(fov_angle), *[Transform(frustrum_fov_lines[i], new_frustrum_fov_lines[i]) for i in range(len(frustrum_fov_lines))])
            
            self.wait_until_bookmark("cam_fov_angle")

            fov_symbol = MathTex("f", font_size=DEFAULT_FONT_SIZE / 2).move_to(
                Angle(
                    frustrum_fov_lines[0], frustrum_fov_lines[1], radius=ANGLE_RADIUS + 3 * SMALL_BUFF, other_angle=False
                ).point_from_proportion(0.5)
            )
            self.play(Write(fov_symbol))
        with self.voiceover("""
            We will now bisect our frustrum.
            <bookmark mark='frustrum_bad_90'/>One may be tempted to then simply use the camera right vector and solve for the camera distance <bookmark mark='frustrum_len_var'/>x by using trigonometry, this however is incorrect, and will lead to
            our frustrum intersecting the sphere. As you can see this doesn't work the way we want it to, let's try something different.
            """):
            new_frustrum_mid_line = Line(camera_origin.get_center(), main_circle.get_center(), color=YELLOW)
            new_top_frustrum_fov_line = frustrum_fov_lines[1].copy().rotate(-HALF_FOV, about_point=camera_origin.get_center())

            new_fov_symbol = MathTex("h", font_size=DEFAULT_FONT_SIZE / 2).move_to(
                Angle(
                    frustrum_fov_lines[0], new_top_frustrum_fov_line, radius=ANGLE_RADIUS + 3 * SMALL_BUFF, other_angle=False
                ).point_from_proportion(0.5)
            )

            self.play(
                Transform(frustrum_fov_lines[1], new_frustrum_mid_line),
                Transform(fov_angle, Angle(frustrum_fov_lines[0], new_top_frustrum_fov_line)),
                Transform(fov_symbol, new_fov_symbol)
            )

            self.wait_until_bookmark("frustrum_bad_90")
            right_vector_line = Line(camera_origin.get_center(), camera_origin.get_center() + frustrum_fov_lines[1].get_unit_vector(), color=BLUE, stroke_width=DEFAULT_STROKE_WIDTH * 0.5)
            right_vector_line.rotate(-90 * DEGREES, about_point=camera_origin.get_center())
            self.play(Create(right_vector_line))

            frustrum_len_var = Brace(frustrum_fov_lines[1], direction=UP)
            frustrum_len_var_text = frustrum_len_var.get_text("x")

            right_at_circle = right_vector_line.copy().move_to(main_circle.get_center()).set_length(10000)

            bad_line_start = line_intersection(
                [right_at_circle.get_start(), right_at_circle.get_end()],
                [frustrum_fov_lines[1].get_start(), frustrum_fov_lines[1].get_end()]
            )

            bad_line_end = line_intersection(
                [right_at_circle.get_start(), right_at_circle.get_end()],
                [frustrum_fov_lines[0].get_start(), frustrum_fov_lines[0].get_end()]
            )

            right_at_circle = right_at_circle.copy().put_start_and_end_on(bad_line_start, bad_line_end)
            self.play(Transform(right_vector_line, right_at_circle))

            self.wait_until_bookmark("frustrum_len_var")
            self.play(GrowFromCenter(frustrum_len_var), FadeIn(frustrum_len_var_text))

            self.wait()

            new_wrong_dist = CIRCLE_RADIUS / np.tan(HALF_FOV)

            frustrum_group = Group(*[frustrum_fov_lines[0], frustrum_fov_lines[1], camera_icon, camera_origin, fov_angle, fov_symbol])
            circle_group = Group(*[main_circle, circle_radius_l, circle_radius_l_brace, circle_radius_l_brace_text])

            camera_to_circle_dir = frustrum_fov_lines[1].get_unit_vector()
            new_frustrum_mid_line = frustrum_fov_lines[1].copy().put_start_and_end_on(camera_to_circle_dir *(new_wrong_dist*-0.5), camera_to_circle_dir *(new_wrong_dist*0.5))

            frustrum_len_var_new = Brace(new_frustrum_mid_line, direction=UP)


            bad_formula = MathTex("x = \\frac{r}{tan(h)}").move_to((LEFT + UP) * 2.0)
            self.play(Write(bad_formula))


            prev_mid_line = frustrum_fov_lines[1].copy()
            prev_len_var = frustrum_len_var.copy()
            prev_circle_group_pos = circle_group.get_center()
            frustrum_group_shift = RIGHT * (STARTING_DIST - new_wrong_dist * 0.5)
            new_circle_pos = camera_to_circle_dir * new_wrong_dist * 0.5

            self.play(
                circle_group.animate.move_to(new_circle_pos),
                frustrum_group.animate.shift(frustrum_group_shift),
                Transform(frustrum_fov_lines[1], new_frustrum_mid_line),
                Transform(frustrum_len_var, frustrum_len_var_new),
                right_vector_line.animate.put_start_and_end_on(new_circle_pos, new_circle_pos + right_vector_line.get_unit_vector() * CIRCLE_RADIUS)
            )

            self.wait_for_voiceover()
            
            self.play(
                Unwrite(bad_formula),
                Uncreate(right_vector_line),
                circle_group.animate.move_to(prev_circle_group_pos ),
                frustrum_group.animate.shift(-frustrum_group_shift),
                Transform(frustrum_fov_lines[1], prev_mid_line),
                Transform(frustrum_len_var, prev_len_var)
            )

        with self.voiceover("""The correct approach is to use a vector orthogonal to the frustrum instead of using the camera's right vector.<bookmark mark='formula'/>
        We can then use trigonometry to find X, which in this case will just be the hypotenuse."""):
            new_x = CIRCLE_RADIUS / np.sin(HALF_FOV)
            dot_origin_l = CIRCLE_RADIUS/np.tan(HALF_FOV)
            dot = Dot(camera_origin.get_center() + frustrum_fov_lines[0].get_unit_vector() * dot_origin_l)
            opposite_line = Line(dot.get_center(), dot.get_center() + frustrum_fov_lines[0].copy().rotate(90*DEGREES, about_point=dot.get_center()).get_unit_vector() * CIRCLE_RADIUS, color=PINK)
            right_angle = RightAngle(opposite_line, frustrum_fov_lines[0])
            self.play(GrowFromPoint(dot, camera_origin.get_center()), GrowFromPoint(opposite_line, camera_origin.get_center()), Create(right_angle))
            
            
            self.wait_until_bookmark("formula")

            good_formula = MathTex("x = \\frac{r}{sin(h)}").move_to((LEFT + UP) * 2.0)
            self.play(Write(good_formula))

            self.wait()

            frustrum_group_shift = RIGHT * (STARTING_DIST - new_x * 0.5)

            frustrum_group = Group(*[frustrum_fov_lines[0], frustrum_fov_lines[1], camera_icon, camera_origin, fov_angle, fov_symbol, dot, opposite_line, right_angle])
            circle_group = Group(*[main_circle, circle_radius_l, circle_radius_l_brace, circle_radius_l_brace_text])

            new_circle_pos = RIGHT * new_x * 0.5

            camera_to_circle_dir = frustrum_fov_lines[1].get_unit_vector()
            new_frustrum_mid_line = frustrum_fov_lines[1].copy().put_start_and_end_on(camera_to_circle_dir * (new_x*-0.5), camera_to_circle_dir * (new_x*0.5))

            frustrum_len_var_new = Brace(new_frustrum_mid_line, direction=UP)

            self.play(
                circle_group.animate.move_to(new_circle_pos),
                frustrum_group.animate.shift(frustrum_group_shift),
                Transform(frustrum_fov_lines[1], new_frustrum_mid_line),
                Transform(frustrum_len_var, frustrum_len_var_new),
            )

            self.wait_for_voiceover()
            self.wait(2)
            

            # ninety_deg_line = Line(camera_origin.get_center(), color=NINETY_DEG_LINE_COLOR)
            # ninety_deg_line.rotate(-HALF_FOV+90*DEGREES, about_point=camera_origin.get_center())

            # ninety_deg_line_2 = Line(main_circle.get_center() + RIGHT, end=main_circle.get_center())
            # ninety_deg_line_2 = ninety_deg_line_2.rotate(-HALF_FOV+90*DEGREES + 180 * DEGREES, about_point=main_circle.get_center())

            # ninety_deg_line_2 = Line(line_intersection(
            #     [ninety_deg_line_2.get_start(), ninety_deg_line_2.get_end()],
            #     [frustrum_fov_lines[0].get_start(), frustrum_fov_lines[0].get_end()],
            # ), main_circle.get_center(), color=NINETY_DEG_LINE_COLOR)


            # self.play(Create(ninety_deg_line))

            # line_to_obj = Line(camera_origin.get_center(), main_circle.get_center())
            # curr_dist_to_object = line_to_obj.get_length()
            # desired_dist_to_object = CIRCLE_RADIUS/np.sin(HALF_FOV)
            # direction_to_object = line_to_obj.get_unit_vector()
            # dist_to_object_line = Line(camera_origin.get_center(),  camera_origin.get_center() + line_to_obj.get_unit_vector() * desired_dist_to_object)
            # self.add(dist_to_object_line)

            # fcg = VGroup(*[frustrum_fov_lines[0], frustrum_fov_lines[1], camera_origin, dist_to_object_line])

            # self.play(fcg.animate.shift( direction_to_object * (curr_dist_to_object - desired_dist_to_object)))

            # a2 = Angle(frustrum_fov_lines[0], ninety_deg_line_2)
            # self.play(Transform(a, a2))
            # self.play(Transform(ninety_deg_line, ninety_deg_line_2))