From 3339aad29ecfa673080bfa07cda383bd5220e4a8 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 25 Jan 2023 10:19:44 -0800 Subject: [PATCH] Separate CameraFrame into its own file --- manimlib/camera/camera.py | 164 +----------------------------- manimlib/camera/camera_frame.py | 173 ++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 162 deletions(-) create mode 100644 manimlib/camera/camera_frame.py diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index b5bbc2cc..5edf5633 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -1,25 +1,20 @@ from __future__ import annotations import itertools as it -import math import moderngl import numpy as np import OpenGL.GL as gl from PIL import Image -from scipy.spatial.transform import Rotation +from manimlib.camera.camera_frame import CameraFrame from manimlib.constants import BLACK -from manimlib.constants import DEGREES, RADIANS from manimlib.constants import DEFAULT_FPS from manimlib.constants import DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH -from manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH -from manimlib.constants import DOWN, LEFT, ORIGIN, OUT, RIGHT, UP +from manimlib.constants import FRAME_WIDTH from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Point from manimlib.utils.color import color_to_rgba -from manimlib.utils.simple_functions import fdiv -from manimlib.utils.space_ops import normalize from typing import TYPE_CHECKING @@ -29,161 +24,6 @@ if TYPE_CHECKING: from manimlib.window import Window from typing import Any, Iterable -class CameraFrame(Mobject): - def __init__( - self, - frame_shape: tuple[float, float] = (FRAME_WIDTH, FRAME_HEIGHT), - center_point: Vect3 = ORIGIN, - focal_dist_to_height: float = 2.0, - **kwargs, - ): - self.frame_shape = frame_shape - self.center_point = center_point - self.focal_dist_to_height = focal_dist_to_height - self.view_matrix = np.identity(4) - super().__init__(**kwargs) - - def init_uniforms(self) -> None: - super().init_uniforms() - # As a quaternion - self.uniforms["orientation"] = Rotation.identity().as_quat() - self.uniforms["focal_dist_to_height"] = self.focal_dist_to_height - - def init_points(self) -> None: - self.set_points([ORIGIN, LEFT, RIGHT, DOWN, UP]) - self.set_width(self.frame_shape[0], stretch=True) - self.set_height(self.frame_shape[1], stretch=True) - self.move_to(self.center_point) - - def set_orientation(self, rotation: Rotation): - self.uniforms["orientation"][:] = rotation.as_quat() - return self - - def get_orientation(self): - return Rotation.from_quat(self.uniforms["orientation"]) - - def to_default_state(self): - self.center() - self.set_height(FRAME_HEIGHT) - self.set_width(FRAME_WIDTH) - self.set_orientation(Rotation.identity()) - return self - - def get_euler_angles(self): - return self.get_orientation().as_euler("zxz")[::-1] - - def get_theta(self): - return self.get_euler_angles()[0] - - def get_phi(self): - return self.get_euler_angles()[1] - - def get_gamma(self): - return self.get_euler_angles()[2] - - def get_inverse_camera_rotation_matrix(self): - return self.get_orientation().as_matrix().T - - def get_view_matrix(self): - """ - Returns a 4x4 for the affine transformation mapping a point - into the camera's internal coordinate system - """ - result = self.view_matrix - result[:] = np.identity(4) - result[:3, 3] = -self.get_center() - rotation = np.identity(4) - rotation[:3, :3] = self.get_inverse_camera_rotation_matrix() - result[:] = np.dot(rotation, result) - return result - - def rotate(self, angle: float, axis: np.ndarray = OUT, **kwargs): - rot = Rotation.from_rotvec(angle * normalize(axis)) - self.set_orientation(rot * self.get_orientation()) - return self - - def set_euler_angles( - self, - theta: float | None = None, - phi: float | None = None, - gamma: float | None = None, - units: float = RADIANS - ): - eulers = self.get_euler_angles() # theta, phi, gamma - for i, var in enumerate([theta, phi, gamma]): - if var is not None: - eulers[i] = var * units - self.set_orientation(Rotation.from_euler("zxz", eulers[::-1])) - return self - - def reorient( - self, - theta_degrees: float | None = None, - phi_degrees: float | None = None, - gamma_degrees: float | None = None, - ): - """ - Shortcut for set_euler_angles, defaulting to taking - in angles in degrees - """ - self.set_euler_angles(theta_degrees, phi_degrees, gamma_degrees, units=DEGREES) - return self - - def set_theta(self, theta: float): - return self.set_euler_angles(theta=theta) - - def set_phi(self, phi: float): - return self.set_euler_angles(phi=phi) - - def set_gamma(self, gamma: float): - return self.set_euler_angles(gamma=gamma) - - def increment_theta(self, dtheta: float): - self.rotate(dtheta, OUT) - return self - - def increment_phi(self, dphi: float): - self.rotate(dphi, self.get_inverse_camera_rotation_matrix()[0]) - return self - - def increment_gamma(self, dgamma: float): - self.rotate(dgamma, self.get_inverse_camera_rotation_matrix()[2]) - return self - - def set_focal_distance(self, focal_distance: float): - self.uniforms["focal_dist_to_height"] = focal_distance / self.get_height() - return self - - def set_field_of_view(self, field_of_view: float): - self.uniforms["focal_dist_to_height"] = 2 * math.tan(field_of_view / 2) - return self - - def get_shape(self): - return (self.get_width(), self.get_height()) - - def get_center(self) -> np.ndarray: - # Assumes first point is at the center - return self.get_points()[0] - - def get_width(self) -> float: - points = self.get_points() - return points[2, 0] - points[1, 0] - - def get_height(self) -> float: - points = self.get_points() - return points[4, 1] - points[3, 1] - - def get_focal_distance(self) -> float: - return self.uniforms["focal_dist_to_height"] * self.get_height() - - def get_field_of_view(self) -> float: - return 2 * math.atan(self.uniforms["focal_dist_to_height"] / 2) - - def get_implied_camera_location(self) -> np.ndarray: - to_camera = self.get_inverse_camera_rotation_matrix()[2] - dist = self.get_focal_distance() - return self.get_center() + dist * to_camera - class Camera(object): def __init__( diff --git a/manimlib/camera/camera_frame.py b/manimlib/camera/camera_frame.py new file mode 100644 index 00000000..ce0a5846 --- /dev/null +++ b/manimlib/camera/camera_frame.py @@ -0,0 +1,173 @@ +from __future__ import annotations + +import math + +import numpy as np +from scipy.spatial.transform import Rotation + +from manimlib.constants import DEGREES, RADIANS +from manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH +from manimlib.constants import DOWN, LEFT, ORIGIN, OUT, RIGHT, UP +from manimlib.mobject.mobject import Mobject +from manimlib.utils.space_ops import normalize + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from manimlib.typing import Vect3 + + +class CameraFrame(Mobject): + def __init__( + self, + frame_shape: tuple[float, float] = (FRAME_WIDTH, FRAME_HEIGHT), + center_point: Vect3 = ORIGIN, + focal_dist_to_height: float = 2.0, + **kwargs, + ): + self.frame_shape = frame_shape + self.center_point = center_point + self.focal_dist_to_height = focal_dist_to_height + self.view_matrix = np.identity(4) + super().__init__(**kwargs) + + def init_uniforms(self) -> None: + super().init_uniforms() + # As a quaternion + self.uniforms["orientation"] = Rotation.identity().as_quat() + self.uniforms["focal_dist_to_height"] = self.focal_dist_to_height + + def init_points(self) -> None: + self.set_points([ORIGIN, LEFT, RIGHT, DOWN, UP]) + self.set_width(self.frame_shape[0], stretch=True) + self.set_height(self.frame_shape[1], stretch=True) + self.move_to(self.center_point) + + def set_orientation(self, rotation: Rotation): + self.uniforms["orientation"][:] = rotation.as_quat() + return self + + def get_orientation(self): + return Rotation.from_quat(self.uniforms["orientation"]) + + def to_default_state(self): + self.center() + self.set_height(FRAME_HEIGHT) + self.set_width(FRAME_WIDTH) + self.set_orientation(Rotation.identity()) + return self + + def get_euler_angles(self): + return self.get_orientation().as_euler("zxz")[::-1] + + def get_theta(self): + return self.get_euler_angles()[0] + + def get_phi(self): + return self.get_euler_angles()[1] + + def get_gamma(self): + return self.get_euler_angles()[2] + + def get_inverse_camera_rotation_matrix(self): + return self.get_orientation().as_matrix().T + + def get_view_matrix(self): + """ + Returns a 4x4 for the affine transformation mapping a point + into the camera's internal coordinate system + """ + result = self.view_matrix + result[:] = np.identity(4) + result[:3, 3] = -self.get_center() + rotation = np.identity(4) + rotation[:3, :3] = self.get_inverse_camera_rotation_matrix() + result[:] = np.dot(rotation, result) + return result + + def rotate(self, angle: float, axis: np.ndarray = OUT, **kwargs): + rot = Rotation.from_rotvec(angle * normalize(axis)) + self.set_orientation(rot * self.get_orientation()) + return self + + def set_euler_angles( + self, + theta: float | None = None, + phi: float | None = None, + gamma: float | None = None, + units: float = RADIANS + ): + eulers = self.get_euler_angles() # theta, phi, gamma + for i, var in enumerate([theta, phi, gamma]): + if var is not None: + eulers[i] = var * units + self.set_orientation(Rotation.from_euler("zxz", eulers[::-1])) + return self + + def reorient( + self, + theta_degrees: float | None = None, + phi_degrees: float | None = None, + gamma_degrees: float | None = None, + ): + """ + Shortcut for set_euler_angles, defaulting to taking + in angles in degrees + """ + self.set_euler_angles(theta_degrees, phi_degrees, gamma_degrees, units=DEGREES) + return self + + def set_theta(self, theta: float): + return self.set_euler_angles(theta=theta) + + def set_phi(self, phi: float): + return self.set_euler_angles(phi=phi) + + def set_gamma(self, gamma: float): + return self.set_euler_angles(gamma=gamma) + + def increment_theta(self, dtheta: float): + self.rotate(dtheta, OUT) + return self + + def increment_phi(self, dphi: float): + self.rotate(dphi, self.get_inverse_camera_rotation_matrix()[0]) + return self + + def increment_gamma(self, dgamma: float): + self.rotate(dgamma, self.get_inverse_camera_rotation_matrix()[2]) + return self + + def set_focal_distance(self, focal_distance: float): + self.uniforms["focal_dist_to_height"] = focal_distance / self.get_height() + return self + + def set_field_of_view(self, field_of_view: float): + self.uniforms["focal_dist_to_height"] = 2 * math.tan(field_of_view / 2) + return self + + def get_shape(self): + return (self.get_width(), self.get_height()) + + def get_center(self) -> np.ndarray: + # Assumes first point is at the center + return self.get_points()[0] + + def get_width(self) -> float: + points = self.get_points() + return points[2, 0] - points[1, 0] + + def get_height(self) -> float: + points = self.get_points() + return points[4, 1] - points[3, 1] + + def get_focal_distance(self) -> float: + return self.uniforms["focal_dist_to_height"] * self.get_height() + + def get_field_of_view(self) -> float: + return 2 * math.atan(self.uniforms["focal_dist_to_height"] / 2) + + def get_implied_camera_location(self) -> np.ndarray: + to_camera = self.get_inverse_camera_rotation_matrix()[2] + dist = self.get_focal_distance() + return self.get_center() + dist * to_camera