Merge pull request #1770 from 3b1b/video-work

Small camera/3d updates
This commit is contained in:
Grant Sanderson 2022-03-29 20:35:33 -07:00 committed by GitHub
commit 769a4bbaf9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 22 deletions

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import moderngl import moderngl
from colour import Color from colour import Color
import OpenGL.GL as gl import OpenGL.GL as gl
import math
import itertools as it import itertools as it
@ -27,13 +28,14 @@ class CameraFrame(Mobject):
CONFIG = { CONFIG = {
"frame_shape": (FRAME_WIDTH, FRAME_HEIGHT), "frame_shape": (FRAME_WIDTH, FRAME_HEIGHT),
"center_point": ORIGIN, "center_point": ORIGIN,
"focal_distance": 2, "focal_dist_to_height": 2,
} }
def init_uniforms(self) -> None: def init_uniforms(self) -> None:
super().init_uniforms() super().init_uniforms()
# As a quaternion # As a quaternion
self.uniforms["orientation"] = Rotation.identity().as_quat() self.uniforms["orientation"] = Rotation.identity().as_quat()
self.uniforms["focal_dist_to_height"] = self.focal_dist_to_height
def init_points(self) -> None: def init_points(self) -> None:
self.set_points([ORIGIN, LEFT, RIGHT, DOWN, UP]) self.set_points([ORIGIN, LEFT, RIGHT, DOWN, UP])
@ -56,7 +58,7 @@ class CameraFrame(Mobject):
return self return self
def get_euler_angles(self): def get_euler_angles(self):
return self.get_orientation().as_euler("xzy") return self.get_orientation().as_euler("zxz")[::-1]
def get_inverse_camera_rotation_matrix(self): def get_inverse_camera_rotation_matrix(self):
return self.get_orientation().as_matrix().T return self.get_orientation().as_matrix().T
@ -73,11 +75,11 @@ class CameraFrame(Mobject):
gamma: float | None = None, gamma: float | None = None,
units: float = RADIANS units: float = RADIANS
): ):
eulers = self.get_euler_angles() # phi, theta, gamma eulers = self.get_euler_angles() # theta, phi, gamma
for i, var in enumerate([phi, theta, gamma]): for i, var in enumerate([theta, phi, gamma]):
if var is not None: if var is not None:
eulers[i] = var * units eulers[i] = var * units
self.set_orientation(Rotation.from_euler('xzy', eulers)) self.set_orientation(Rotation.from_euler("zxz", eulers[::-1]))
return self return self
def reorient( def reorient(
@ -114,6 +116,14 @@ class CameraFrame(Mobject):
self.rotate(dgamma, self.get_inverse_camera_rotation_matrix()[2]) self.rotate(dgamma, self.get_inverse_camera_rotation_matrix()[2])
return self 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): def get_shape(self):
return (self.get_width(), self.get_height()) return (self.get_width(), self.get_height())
@ -130,7 +140,10 @@ class CameraFrame(Mobject):
return points[4, 1] - points[3, 1] return points[4, 1] - points[3, 1]
def get_focal_distance(self) -> float: def get_focal_distance(self) -> float:
return self.focal_distance * self.get_height() 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: def get_implied_camera_location(self) -> np.ndarray:
to_camera = self.get_inverse_camera_rotation_matrix()[2] to_camera = self.get_inverse_camera_rotation_matrix()[2]

View file

@ -206,6 +206,13 @@ class Cube(SGroup):
return Square3D(resolution=self.square_resolution) return Square3D(resolution=self.square_resolution)
class Prism(Cube):
def __init__(self, width: float = 3.0, height: float = 2.0, depth: float = 1.0, **kwargs):
super().__init__(**kwargs)
for dim, value in enumerate([width, height, depth]):
self.rescale_to_fit(value, dim, stretch=True)
class VCube(VGroup): class VCube(VGroup):
CONFIG = { CONFIG = {
"fill_color": BLUE_D, "fill_color": BLUE_D,
@ -213,18 +220,25 @@ class VCube(VGroup):
"stroke_width": 0, "stroke_width": 0,
"gloss": 0.5, "gloss": 0.5,
"shadow": 0.5, "shadow": 0.5,
"joint_type": "round",
} }
def __init__(self, side_length: int = 2, **kwargs): def __init__(self, side_length: float = 2.0, **kwargs):
super().__init__(**kwargs)
face = Square(side_length=side_length) face = Square(side_length=side_length)
face.get_triangulation() super().__init__(*Cube.square_to_cube_faces(face), **kwargs)
self.add(*Cube.square_to_cube_faces(face))
self.init_colors() self.init_colors()
self.set_joint_type(self.joint_type)
self.apply_depth_test() self.apply_depth_test()
self.refresh_unit_normal() self.refresh_unit_normal()
class VPrism(VCube):
def __init__(self, width: float = 3.0, height: float = 2.0, depth: float = 1.0, **kwargs):
super().__init__(**kwargs)
for dim, value in enumerate([width, height, depth]):
self.rescale_to_fit(value, dim, stretch=True)
class Dodecahedron(VGroup): class Dodecahedron(VGroup):
CONFIG = { CONFIG = {
"fill_color": BLUE_E, "fill_color": BLUE_E,
@ -272,20 +286,9 @@ class Dodecahedron(VGroup):
# self.add(pentagon2.copy().apply_matrix(matrix, about_point=ORIGIN)) # self.add(pentagon2.copy().apply_matrix(matrix, about_point=ORIGIN))
class Prism(Cube):
CONFIG = {
"dimensions": [3, 2, 1]
}
def init_points(self) -> None:
Cube.init_points(self)
for dim, value in enumerate(self.dimensions):
self.rescale_to_fit(value, dim, stretch=True)
class Prismify(VGroup): class Prismify(VGroup):
CONFIG = { CONFIG = {
"apply_depth_test": True "apply_depth_test": True,
} }
def __init__(self, vmobject, depth=1.0, direction=IN, **kwargs): def __init__(self, vmobject, depth=1.0, direction=IN, **kwargs):

View file

@ -341,6 +341,14 @@ class VMobject(Mobject):
def get_flat_stroke(self) -> bool: def get_flat_stroke(self) -> bool:
return self.flat_stroke return self.flat_stroke
def set_joint_type(self, joint_type: str, recurse: bool = True):
for mob in self.get_family(recurse):
mob.joint_type = joint_type
return self
def get_joint_type(self) -> str:
return self.joint_type
# Points # Points
def set_anchors_and_handles( def set_anchors_and_handles(
self, self,