mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Separate CameraFrame into its own file
This commit is contained in:
parent
a9a3ca08cd
commit
3339aad29e
2 changed files with 175 additions and 162 deletions
|
@ -1,25 +1,20 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import itertools as it
|
import itertools as it
|
||||||
import math
|
|
||||||
|
|
||||||
import moderngl
|
import moderngl
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import OpenGL.GL as gl
|
import OpenGL.GL as gl
|
||||||
from PIL import Image
|
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 BLACK
|
||||||
from manimlib.constants import DEGREES, RADIANS
|
|
||||||
from manimlib.constants import DEFAULT_FPS
|
from manimlib.constants import DEFAULT_FPS
|
||||||
from manimlib.constants import DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH
|
from manimlib.constants import DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH
|
||||||
from manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH
|
from manimlib.constants import FRAME_WIDTH
|
||||||
from manimlib.constants import DOWN, LEFT, ORIGIN, OUT, RIGHT, UP
|
|
||||||
from manimlib.mobject.mobject import Mobject
|
from manimlib.mobject.mobject import Mobject
|
||||||
from manimlib.mobject.mobject import Point
|
from manimlib.mobject.mobject import Point
|
||||||
from manimlib.utils.color import color_to_rgba
|
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
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -29,161 +24,6 @@ if TYPE_CHECKING:
|
||||||
from manimlib.window import Window
|
from manimlib.window import Window
|
||||||
from typing import Any, Iterable
|
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):
|
class Camera(object):
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
173
manimlib/camera/camera_frame.py
Normal file
173
manimlib/camera/camera_frame.py
Normal file
|
@ -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
|
Loading…
Add table
Reference in a new issue