mirror of
https://github.com/3b1b/manim.git
synced 2025-09-01 00:48:45 +00:00
Merge pull request #1977 from 3b1b/video-work
Various clean ups associated with 3d scenes
This commit is contained in:
commit
5490b3be19
25 changed files with 267 additions and 346 deletions
|
@ -554,9 +554,7 @@ class TexAndNumbersExample(Scene):
|
|||
)
|
||||
|
||||
|
||||
class SurfaceExample(Scene):
|
||||
samples = 4
|
||||
|
||||
class SurfaceExample(ThreeDScene):
|
||||
def construct(self):
|
||||
surface_text = Text("For 3d scenes, try using surfaces")
|
||||
surface_text.fix_in_frame()
|
||||
|
@ -588,13 +586,6 @@ class SurfaceExample(Scene):
|
|||
mob.mesh = SurfaceMesh(mob)
|
||||
mob.mesh.set_stroke(BLUE, 1, opacity=0.5)
|
||||
|
||||
# Set perspective
|
||||
frame = self.camera.frame
|
||||
frame.set_euler_angles(
|
||||
theta=-30 * DEGREES,
|
||||
phi=70 * DEGREES,
|
||||
)
|
||||
|
||||
surface = surfaces[0]
|
||||
|
||||
self.play(
|
||||
|
@ -616,12 +607,12 @@ class SurfaceExample(Scene):
|
|||
self.play(
|
||||
Transform(surface, surfaces[2]),
|
||||
# Move camera frame during the transition
|
||||
frame.animate.increment_phi(-10 * DEGREES),
|
||||
frame.animate.increment_theta(-20 * DEGREES),
|
||||
self.frame.animate.increment_phi(-10 * DEGREES),
|
||||
self.frame.animate.increment_theta(-20 * DEGREES),
|
||||
run_time=3
|
||||
)
|
||||
# Add ambient rotation
|
||||
frame.add_updater(lambda m, dt: m.increment_theta(-0.1 * dt))
|
||||
self.frame.add_updater(lambda m, dt: m.increment_theta(-0.1 * dt))
|
||||
|
||||
# Play around with where the light is
|
||||
light_text = Text("You can move around the light source")
|
||||
|
|
|
@ -136,7 +136,6 @@ class DrawBorderThenFill(Animation):
|
|||
if index == 1 and self.sm_to_index[hash(submob)] == 0:
|
||||
# First time crossing over
|
||||
submob.set_data(outline.data)
|
||||
submob.needs_new_triangulation = False
|
||||
self.sm_to_index[hash(submob)] = 1
|
||||
|
||||
if index == 0:
|
||||
|
|
|
@ -72,9 +72,9 @@ class Camera(object):
|
|||
|
||||
def init_context(self) -> None:
|
||||
if self.window is None:
|
||||
self.ctx = moderngl.create_standalone_context()
|
||||
self.ctx: moderngl.Context = moderngl.create_standalone_context()
|
||||
else:
|
||||
self.ctx = self.window.ctx
|
||||
self.ctx: moderngl.Context = self.window.ctx
|
||||
|
||||
self.ctx.enable(moderngl.PROGRAM_POINT_SIZE)
|
||||
self.ctx.enable(moderngl.BLEND)
|
||||
|
@ -169,10 +169,10 @@ class Camera(object):
|
|||
|
||||
# Getting camera attributes
|
||||
def get_pixel_size(self) -> float:
|
||||
return self.frame.get_shape()[0] / self.get_pixel_shape()[0]
|
||||
return self.frame.get_width() / self.get_pixel_shape()[0]
|
||||
|
||||
def get_pixel_shape(self) -> tuple[int, int]:
|
||||
return self.draw_fbo.size
|
||||
return self.fbo.size
|
||||
|
||||
def get_pixel_width(self) -> int:
|
||||
return self.get_pixel_shape()[0]
|
||||
|
@ -231,12 +231,12 @@ class Camera(object):
|
|||
cam_pos = self.frame.get_implied_camera_location()
|
||||
|
||||
self.uniforms.update(
|
||||
frame_shape=frame.get_shape(),
|
||||
pixel_size=self.get_pixel_size(),
|
||||
view=tuple(view_matrix.T.flatten()),
|
||||
focal_distance=frame.get_focal_distance() / frame.get_scale(),
|
||||
frame_scale=frame.get_scale(),
|
||||
pixel_size=self.get_pixel_size(),
|
||||
camera_position=tuple(cam_pos),
|
||||
light_position=tuple(light_pos),
|
||||
focal_distance=frame.get_focal_distance(),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -4,9 +4,10 @@ import math
|
|||
|
||||
import numpy as np
|
||||
from scipy.spatial.transform import Rotation
|
||||
from pyrr import Matrix44
|
||||
|
||||
from manimlib.constants import DEGREES, RADIANS
|
||||
from manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH
|
||||
from manimlib.constants import FRAME_SHAPE
|
||||
from manimlib.constants import DOWN, LEFT, ORIGIN, OUT, RIGHT, UP
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.utils.space_ops import normalize
|
||||
|
@ -20,28 +21,23 @@ if TYPE_CHECKING:
|
|||
class CameraFrame(Mobject):
|
||||
def __init__(
|
||||
self,
|
||||
frame_shape: tuple[float, float] = (FRAME_WIDTH, FRAME_HEIGHT),
|
||||
frame_shape: tuple[float, float] = FRAME_SHAPE,
|
||||
center_point: Vect3 = ORIGIN,
|
||||
focal_dist_to_height: float = 2.0,
|
||||
# Field of view in the y direction
|
||||
fovy: float = 45 * DEGREES,
|
||||
**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
|
||||
self.view_matrix = np.identity(4)
|
||||
self.default_orientation = Rotation.identity()
|
||||
|
||||
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)
|
||||
self.set_points(np.array([ORIGIN, LEFT, RIGHT, DOWN, UP]))
|
||||
self.set_width(frame_shape[0], stretch=True)
|
||||
self.set_height(frame_shape[1], stretch=True)
|
||||
self.move_to(center_point)
|
||||
self.uniforms["orientation"] = Rotation.identity().as_quat()
|
||||
self.uniforms["fovy"] = fovy
|
||||
|
||||
def set_orientation(self, rotation: Rotation):
|
||||
self.uniforms["orientation"][:] = rotation.as_quat()
|
||||
|
@ -50,15 +46,21 @@ class CameraFrame(Mobject):
|
|||
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())
|
||||
def make_orientation_default(self):
|
||||
self.default_orientation = self.get_orientation()
|
||||
return self
|
||||
|
||||
def get_euler_angles(self):
|
||||
return self.get_orientation().as_euler("zxz")[::-1]
|
||||
def to_default_state(self):
|
||||
self.set_shape(*FRAME_SHAPE)
|
||||
self.center()
|
||||
self.set_orientation(self.default_orientation)
|
||||
return self
|
||||
|
||||
def get_euler_angles(self) -> np.ndarray:
|
||||
orientation = self.get_orientation()
|
||||
if all(orientation.as_quat() == [0, 0, 0, 1]):
|
||||
return np.zeros(3)
|
||||
return orientation.as_euler("zxz")[::-1]
|
||||
|
||||
def get_theta(self):
|
||||
return self.get_euler_angles()[0]
|
||||
|
@ -69,6 +71,9 @@ class CameraFrame(Mobject):
|
|||
def get_gamma(self):
|
||||
return self.get_euler_angles()[2]
|
||||
|
||||
def get_scale(self):
|
||||
return self.get_height() / FRAME_SHAPE[1]
|
||||
|
||||
def get_inverse_camera_rotation_matrix(self):
|
||||
return self.get_orientation().as_matrix().T
|
||||
|
||||
|
@ -77,13 +82,14 @@ class CameraFrame(Mobject):
|
|||
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
|
||||
shift = Matrix44.from_translation(-self.get_center()).T
|
||||
rotation = Matrix44.from_quaternion(self.uniforms["orientation"]).T
|
||||
scale = Matrix44(np.identity(3) / self.get_scale())
|
||||
self.view_matrix[:] = shift * rotation * scale
|
||||
return self.view_matrix
|
||||
|
||||
def get_inv_view_matrix(self):
|
||||
return np.linalg.inv(self.get_view_matrix())
|
||||
|
||||
def rotate(self, angle: float, axis: np.ndarray = OUT, **kwargs):
|
||||
rot = Rotation.from_rotvec(angle * normalize(axis))
|
||||
|
@ -101,7 +107,11 @@ class CameraFrame(Mobject):
|
|||
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]))
|
||||
if all(eulers == 0):
|
||||
rot = Rotation.identity()
|
||||
else:
|
||||
rot = Rotation.from_euler("zxz", eulers[::-1])
|
||||
self.set_orientation(rot)
|
||||
return self
|
||||
|
||||
def reorient(
|
||||
|
@ -139,16 +149,20 @@ class CameraFrame(Mobject):
|
|||
return self
|
||||
|
||||
def set_focal_distance(self, focal_distance: float):
|
||||
self.uniforms["focal_dist_to_height"] = focal_distance / self.get_height()
|
||||
self.uniforms["fovy"] = 2 * math.atan(0.5 * self.get_height() / focal_distance)
|
||||
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)
|
||||
self.uniforms["fovy"] = field_of_view
|
||||
return self
|
||||
|
||||
def get_shape(self):
|
||||
return (self.get_width(), self.get_height())
|
||||
|
||||
def get_aspect_ratio(self):
|
||||
width, height = self.get_shape()
|
||||
return width / height
|
||||
|
||||
def get_center(self) -> np.ndarray:
|
||||
# Assumes first point is at the center
|
||||
return self.get_points()[0]
|
||||
|
@ -162,12 +176,22 @@ class CameraFrame(Mobject):
|
|||
return points[4, 1] - points[3, 1]
|
||||
|
||||
def get_focal_distance(self) -> float:
|
||||
return self.uniforms["focal_dist_to_height"] * self.get_height()
|
||||
return 0.5 * self.get_height() / math.tan(0.5 * self.uniforms["fovy"])
|
||||
|
||||
def get_field_of_view(self) -> float:
|
||||
return 2 * math.atan(self.uniforms["focal_dist_to_height"] / 2)
|
||||
return self.uniforms["fovy"]
|
||||
|
||||
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
|
||||
|
||||
def to_fixed_frame_point(self, point: Vect3, relative: bool = False):
|
||||
view = self.get_view_matrix()
|
||||
point4d = [*point, 0 if relative else 1]
|
||||
return np.dot(point4d, view.T)[:3]
|
||||
|
||||
def from_fixed_frame_point(self, point: Vect3, relative: bool = False):
|
||||
inv_view = self.get_inv_view_matrix()
|
||||
point4d = [*point, 0 if relative else 1]
|
||||
return np.dot(point4d, inv_view.T)[:3]
|
||||
|
|
|
@ -11,6 +11,7 @@ if TYPE_CHECKING:
|
|||
ASPECT_RATIO: float = 16.0 / 9.0
|
||||
FRAME_HEIGHT: float = 8.0
|
||||
FRAME_WIDTH: float = FRAME_HEIGHT * ASPECT_RATIO
|
||||
FRAME_SHAPE: tuple[float, float] = (FRAME_WIDTH, FRAME_HEIGHT)
|
||||
FRAME_Y_RADIUS: float = FRAME_HEIGHT / 2
|
||||
FRAME_X_RADIUS: float = FRAME_WIDTH / 2
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import itertools as it
|
|||
from manimlib.constants import BLACK, BLUE, BLUE_D, BLUE_E, GREEN, GREY_A, WHITE, RED
|
||||
from manimlib.constants import DEGREES, PI
|
||||
from manimlib.constants import DL, UL, DOWN, DR, LEFT, ORIGIN, OUT, RIGHT, UP
|
||||
from manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH
|
||||
from manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS
|
||||
from manimlib.constants import MED_SMALL_BUFF, SMALL_BUFF
|
||||
from manimlib.mobject.functions import ParametricCurve
|
||||
|
|
|
@ -448,12 +448,8 @@ class Annulus(VMobject):
|
|||
)
|
||||
|
||||
self.radius = outer_radius
|
||||
# Make sure to add enough components that triangulation doesn't fail
|
||||
kw = dict(
|
||||
n_components=int(max(8, np.ceil(TAU / math.acos(inner_radius / outer_radius))))
|
||||
)
|
||||
outer_path = outer_radius * Arc.create_quadratic_bezier_points(TAU, 0, **kw)
|
||||
inner_path = inner_radius * Arc.create_quadratic_bezier_points(-TAU, 0, **kw)
|
||||
outer_path = outer_radius * Arc.create_quadratic_bezier_points(TAU, 0)
|
||||
inner_path = inner_radius * Arc.create_quadratic_bezier_points(-TAU, 0)
|
||||
self.add_subpath(outer_path)
|
||||
self.add_subpath(inner_path)
|
||||
self.shift(center)
|
||||
|
|
|
@ -48,7 +48,7 @@ from manimlib.utils.space_ops import rotation_matrix_transpose
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Callable, Iterable, Union, Tuple
|
||||
from typing import Callable, Iterable, Union, Tuple, Optional
|
||||
import numpy.typing as npt
|
||||
from manimlib.typing import ManimColor, Vect3, Vect4, Vect3Array, UniformDict
|
||||
from moderngl.context import Context
|
||||
|
@ -408,8 +408,13 @@ class Mobject(object):
|
|||
self.assemble_family()
|
||||
return self
|
||||
|
||||
def remove(self, *to_remove: Mobject, reassemble: bool = True):
|
||||
for parent in self.get_family():
|
||||
def remove(
|
||||
self,
|
||||
*to_remove: Mobject,
|
||||
reassemble: bool = True,
|
||||
recurse: bool = True
|
||||
):
|
||||
for parent in self.get_family(recurse):
|
||||
for child in to_remove:
|
||||
if child in parent.submobjects:
|
||||
parent.submobjects.remove(child)
|
||||
|
@ -419,6 +424,9 @@ class Mobject(object):
|
|||
parent.assemble_family()
|
||||
return self
|
||||
|
||||
def clear(self):
|
||||
self.remove(*self.submobjects, recurse=False)
|
||||
|
||||
def add_to_back(self, *mobjects: Mobject):
|
||||
self.set_submobjects(list_update(mobjects, self.submobjects))
|
||||
return self
|
||||
|
@ -1160,6 +1168,21 @@ class Mobject(object):
|
|||
self.set_depth(min_depth, **kwargs)
|
||||
return self
|
||||
|
||||
def set_shape(
|
||||
self,
|
||||
width: Optional[float] = None,
|
||||
height: Optional[float] = None,
|
||||
depth: Optional[float] = None,
|
||||
**kwargs
|
||||
):
|
||||
if width is not None:
|
||||
self.set_width(width, stretch=True, **kwargs)
|
||||
if height is not None:
|
||||
self.set_height(height, stretch=True, **kwargs)
|
||||
if depth is not None:
|
||||
self.set_depth(depth, stretch=True, **kwargs)
|
||||
return self
|
||||
|
||||
def set_coord(self, value: float, dim: int, direction: Vect3 = ORIGIN):
|
||||
curr = self.get_coord(dim, direction)
|
||||
shift_vect = np.zeros(self.dim)
|
||||
|
@ -1801,35 +1824,41 @@ class Mobject(object):
|
|||
|
||||
def affects_shader_info_id(func: Callable):
|
||||
@wraps(func)
|
||||
def wrapper(self):
|
||||
for mob in self.get_family():
|
||||
func(mob)
|
||||
mob.refresh_shader_wrapper_id()
|
||||
return self
|
||||
def wrapper(self, *args, **kwargs):
|
||||
result = func(self, *args, **kwargs)
|
||||
self.refresh_shader_wrapper_id()
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
@affects_shader_info_id
|
||||
def fix_in_frame(self, recurse: bool = True):
|
||||
def set_uniform(self, recurse: bool = True, **new_uniforms):
|
||||
for mob in self.get_family(recurse):
|
||||
mob.uniforms["is_fixed_in_frame"] = 1.0
|
||||
mob.uniforms.update(new_uniforms)
|
||||
return self
|
||||
|
||||
@affects_shader_info_id
|
||||
def unfix_from_frame(self):
|
||||
self.uniforms["is_fixed_in_frame"] = 0.0
|
||||
def fix_in_frame(self, recurse: bool = True):
|
||||
self.set_uniform(recurse, is_fixed_in_frame=1.0)
|
||||
return self
|
||||
|
||||
@affects_shader_info_id
|
||||
def unfix_from_frame(self, recurse: bool = True):
|
||||
self.set_uniform(recurse, is_fixed_in_frame=0.0)
|
||||
return self
|
||||
|
||||
def is_fixed_in_frame(self) -> bool:
|
||||
return bool(self.uniforms["is_fixed_in_frame"])
|
||||
|
||||
@affects_shader_info_id
|
||||
def apply_depth_test(self):
|
||||
self.depth_test = True
|
||||
def apply_depth_test(self, recurse: bool = True):
|
||||
for mob in self.get_family(recurse):
|
||||
mob.depth_test = True
|
||||
return self
|
||||
|
||||
@affects_shader_info_id
|
||||
def deactivate_depth_test(self):
|
||||
self.depth_test = False
|
||||
def deactivate_depth_test(self, recurse: bool = True):
|
||||
for mob in self.get_family(recurse):
|
||||
mob.depth_test = False
|
||||
return self
|
||||
|
||||
# Shader code manipulation
|
||||
|
|
|
@ -383,7 +383,6 @@ class Bubble(SVGMobject):
|
|||
self.flip()
|
||||
|
||||
self.content = Mobject()
|
||||
self.refresh_triangulation()
|
||||
|
||||
def get_tip(self):
|
||||
# TODO, find a better way
|
||||
|
|
|
@ -30,10 +30,7 @@ class SingleStringTex(SVGMobject):
|
|||
fill_opacity: float = 1.0,
|
||||
stroke_width: float = 0,
|
||||
svg_default: dict = dict(fill_color=WHITE),
|
||||
path_string_config: dict = dict(
|
||||
should_subdivide_sharp_curves=True,
|
||||
should_remove_null_curves=True,
|
||||
),
|
||||
path_string_config: dict = dict(),
|
||||
font_size: int = 48,
|
||||
alignment: str = R"\centering",
|
||||
math_mode: bool = True,
|
||||
|
|
|
@ -295,15 +295,11 @@ class VMobjectFromSVGPath(VMobject):
|
|||
def __init__(
|
||||
self,
|
||||
path_obj: se.Path,
|
||||
should_subdivide_sharp_curves: bool = False,
|
||||
should_remove_null_curves: bool = True,
|
||||
**kwargs
|
||||
):
|
||||
# Get rid of arcs
|
||||
path_obj.approximate_arcs_with_quads()
|
||||
self.path_obj = path_obj
|
||||
self.should_subdivide_sharp_curves = should_subdivide_sharp_curves
|
||||
self.should_remove_null_curves = should_remove_null_curves
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def init_points(self) -> None:
|
||||
|
@ -313,14 +309,6 @@ class VMobjectFromSVGPath(VMobject):
|
|||
path_string = self.path_obj.d()
|
||||
if path_string not in PATH_TO_POINTS:
|
||||
self.handle_commands()
|
||||
if self.should_subdivide_sharp_curves:
|
||||
# For a healthy triangulation later
|
||||
self.subdivide_sharp_curves()
|
||||
if self.should_remove_null_curves:
|
||||
# Get rid of any null curves
|
||||
self.set_points(self.get_points_without_null_curves())
|
||||
# So triangulation doesn't get messed up
|
||||
self.subdivide_intersections()
|
||||
# Save for future use
|
||||
PATH_TO_POINTS[path_string] = self.get_points().copy()
|
||||
else:
|
||||
|
|
|
@ -144,24 +144,14 @@ class VMobject(Mobject):
|
|||
def __getitem__(self, value: int | slice) -> VMobject:
|
||||
return super().__getitem__(value)
|
||||
|
||||
def __iter__(self) -> Iterable[VMobject]:
|
||||
return super().__iter__()
|
||||
|
||||
def add(self, *vmobjects: VMobject):
|
||||
if not all((isinstance(m, VMobject) for m in vmobjects)):
|
||||
raise Exception("All submobjects must be of type VMobject")
|
||||
super().add(*vmobjects)
|
||||
|
||||
def add_background_rectangle(
|
||||
self,
|
||||
color: ManimColor | None = None,
|
||||
opacity: float = 0.75,
|
||||
**kwargs
|
||||
):
|
||||
normal = self.family_members_with_points()[0].get_unit_normal()
|
||||
super().add_background_rectangle(color, opacity, **kwargs)
|
||||
rect = self.background_rectangle
|
||||
if np.dot(rect.get_unit_normal(), normal) < 0:
|
||||
rect.reverse_points()
|
||||
return self
|
||||
|
||||
# Colors
|
||||
def init_colors(self):
|
||||
self.set_fill(
|
||||
|
@ -329,6 +319,10 @@ class VMobject(Mobject):
|
|||
self.set_stroke(opacity=opacity, recurse=recurse)
|
||||
return self
|
||||
|
||||
def set_anti_alias_width(self, anti_alias_width: float, recurse: bool = True):
|
||||
self.set_uniform(recurse, anti_alias_width=anti_alias_width)
|
||||
return self
|
||||
|
||||
def fade(self, darkness: float = 0.5, recurse: bool = True):
|
||||
mobs = self.get_family() if recurse else [self]
|
||||
for mob in mobs:
|
||||
|
@ -399,6 +393,9 @@ class VMobject(Mobject):
|
|||
return self.get_fill_color()
|
||||
return self.get_stroke_color()
|
||||
|
||||
def get_anti_alias_width(self):
|
||||
return self.uniforms["anti_alias_width"]
|
||||
|
||||
def has_stroke(self) -> bool:
|
||||
return any(self.data['stroke_width']) and any(self.data['stroke_rgba'][:, 3])
|
||||
|
||||
|
@ -426,10 +423,34 @@ class VMobject(Mobject):
|
|||
def get_joint_type(self) -> float:
|
||||
return self.uniforms["joint_type"]
|
||||
|
||||
def apply_depth_test(
|
||||
self,
|
||||
anti_alias_width: float = 0,
|
||||
fill_border_width: float = 0,
|
||||
recurse: bool=True
|
||||
):
|
||||
super().apply_depth_test(recurse)
|
||||
self.set_anti_alias_width(anti_alias_width)
|
||||
self.set_fill(border_width=fill_border_width)
|
||||
return self
|
||||
|
||||
def deactivate_depth_test(
|
||||
self,
|
||||
anti_alias_width: float = 1.0,
|
||||
fill_border_width: float = 0.5,
|
||||
recurse: bool=True
|
||||
):
|
||||
super().apply_depth_test(recurse)
|
||||
self.set_anti_alias_width(anti_alias_width)
|
||||
self.set_fill(border_width=fill_border_width)
|
||||
return self
|
||||
|
||||
@Mobject.affects_family_data
|
||||
def use_winding_fill(self, value: bool = True, recurse: bool = True):
|
||||
for submob in self.get_family(recurse):
|
||||
submob._use_winding_fill = value
|
||||
if not value and submob.has_points():
|
||||
submob.subdivide_intersections()
|
||||
return self
|
||||
|
||||
# Points
|
||||
|
@ -870,8 +891,11 @@ class VMobject(Mobject):
|
|||
# If both have fill, and they have the same shape, just
|
||||
# give them the same triangulation so that it's not recalculated
|
||||
# needlessly throughout an animation
|
||||
if not self._use_winding_fill and self.has_fill() \
|
||||
and vmobject.has_fill() and self.has_same_shape_as(vmobject):
|
||||
match_tris = not self._use_winding_fill and \
|
||||
self.has_fill() and \
|
||||
vmobject.has_fill() and \
|
||||
self.has_same_shape_as(vmobject)
|
||||
if match_tris:
|
||||
vmobject.triangulation = self.triangulation
|
||||
return self
|
||||
|
||||
|
@ -892,8 +916,7 @@ class VMobject(Mobject):
|
|||
|
||||
def get_nth_subpath(path_list, n):
|
||||
if n >= len(path_list):
|
||||
# Create a null path at the very end
|
||||
return [path_list[-1][-1]] * 3
|
||||
return [path_list[-1][-1]]
|
||||
return path_list[n]
|
||||
|
||||
for n in range(n_subpaths):
|
||||
|
@ -1277,23 +1300,6 @@ class VMobject(Mobject):
|
|||
submob.get_joint_products()
|
||||
has_fill = submob.has_fill()
|
||||
has_stroke = submob.has_stroke()
|
||||
if has_fill:
|
||||
data = submob.data[fill_names]
|
||||
data["base_point"][:] = data["point"][0]
|
||||
fill_datas.append(data)
|
||||
if self._use_winding_fill:
|
||||
# Add dummy
|
||||
fill_datas.append(data[-1:])
|
||||
else:
|
||||
fill_indices.append(submob.get_triangulation())
|
||||
# Add fill border
|
||||
if not has_stroke:
|
||||
names = list(stroke_names)
|
||||
names[names.index('stroke_rgba')] = 'fill_rgba'
|
||||
names[names.index('stroke_width')] = 'fill_border_width'
|
||||
border_stroke_data = submob.data[names]
|
||||
fill_border_datas.append(border_stroke_data)
|
||||
fill_border_datas.append(border_stroke_data[-1:])
|
||||
if has_stroke:
|
||||
lst = back_stroke_datas if submob.stroke_behind else stroke_datas
|
||||
lst.append(submob.data[stroke_names])
|
||||
|
@ -1301,6 +1307,24 @@ class VMobject(Mobject):
|
|||
# with a dummy vertex added at the end. This is to ensure
|
||||
# it can be safely stacked onto other stroke data arrays.
|
||||
lst.append(submob.data[stroke_names][-1:])
|
||||
if has_fill:
|
||||
data = submob.data[fill_names]
|
||||
data["base_point"][:] = data["point"][0]
|
||||
fill_datas.append(data)
|
||||
if self._use_winding_fill:
|
||||
# Add dummy, as above
|
||||
fill_datas.append(data[-1:])
|
||||
else:
|
||||
fill_indices.append(submob.get_triangulation())
|
||||
if not has_stroke and has_fill:
|
||||
# Add fill border
|
||||
names = list(stroke_names)
|
||||
names[names.index('stroke_rgba')] = 'fill_rgba'
|
||||
names[names.index('stroke_width')] = 'fill_border_width'
|
||||
border_stroke_data = submob.data[names]
|
||||
fill_border_datas.append(border_stroke_data)
|
||||
fill_border_datas.append(border_stroke_data[-1:])
|
||||
|
||||
|
||||
shader_wrappers = [
|
||||
self.back_stroke_shader_wrapper.read_in(
|
||||
|
|
|
@ -126,7 +126,7 @@ class InteractiveScene(Scene):
|
|||
|
||||
def update_selection_rectangle(self, rect: Rectangle):
|
||||
p1 = rect.fixed_corner
|
||||
p2 = self.mouse_point.get_center()
|
||||
p2 = self.frame.to_fixed_frame_point(self.mouse_point.get_center())
|
||||
rect.set_points_as_corners([
|
||||
p1, np.array([p2[0], p1[1], 0]),
|
||||
p2, np.array([p1[0], p2[1], 0]),
|
||||
|
@ -377,7 +377,9 @@ class InteractiveScene(Scene):
|
|||
def enable_selection(self):
|
||||
self.is_selecting = True
|
||||
self.add(self.selection_rectangle)
|
||||
self.selection_rectangle.fixed_corner = self.mouse_point.get_center().copy()
|
||||
self.selection_rectangle.fixed_corner = self.frame.to_fixed_frame_point(
|
||||
self.mouse_point.get_center()
|
||||
)
|
||||
|
||||
def gather_new_selection(self):
|
||||
self.is_selecting = False
|
||||
|
@ -568,7 +570,8 @@ class InteractiveScene(Scene):
|
|||
|
||||
def on_mouse_motion(self, point: np.ndarray, d_point: np.ndarray) -> None:
|
||||
super().on_mouse_motion(point, d_point)
|
||||
self.crosshair.move_to(point)
|
||||
ff_point = self.frame.to_fixed_frame_point(point)
|
||||
self.crosshair.move_to(ff_point)
|
||||
if self.is_grabbing:
|
||||
self.handle_grabbing(point)
|
||||
elif self.window.is_key_pressed(ord(RESIZE_KEY)):
|
||||
|
|
|
@ -1,144 +0,0 @@
|
|||
from manimlib.animation.animation import Animation
|
||||
from manimlib.animation.transform import MoveToTarget
|
||||
from manimlib.animation.transform import Transform
|
||||
from manimlib.animation.update import UpdateFromFunc
|
||||
from manimlib.constants import DOWN, RIGHT
|
||||
from manimlib.constants import MED_LARGE_BUFF, SMALL_BUFF
|
||||
from manimlib.mobject.probability import SampleSpace
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.scene.scene import Scene
|
||||
|
||||
|
||||
class SampleSpaceScene(Scene):
|
||||
def get_sample_space(self, **config):
|
||||
self.sample_space = SampleSpace(**config)
|
||||
return self.sample_space
|
||||
|
||||
def add_sample_space(self, **config):
|
||||
self.add(self.get_sample_space(**config))
|
||||
|
||||
def get_division_change_animations(
|
||||
self, sample_space, parts, p_list,
|
||||
dimension=1,
|
||||
new_label_kwargs=None,
|
||||
**kwargs
|
||||
):
|
||||
if new_label_kwargs is None:
|
||||
new_label_kwargs = {}
|
||||
anims = []
|
||||
p_list = sample_space.complete_p_list(p_list)
|
||||
space_copy = sample_space.copy()
|
||||
|
||||
vect = DOWN if dimension == 1 else RIGHT
|
||||
parts.generate_target()
|
||||
for part, p in zip(parts.target, p_list):
|
||||
part.replace(space_copy, stretch=True)
|
||||
part.stretch(p, dimension)
|
||||
parts.target.arrange(vect, buff=0)
|
||||
parts.target.move_to(space_copy)
|
||||
anims.append(MoveToTarget(parts))
|
||||
if hasattr(parts, "labels") and parts.labels is not None:
|
||||
label_kwargs = parts.label_kwargs
|
||||
label_kwargs.update(new_label_kwargs)
|
||||
new_braces, new_labels = sample_space.get_subdivision_braces_and_labels(
|
||||
parts.target, **label_kwargs
|
||||
)
|
||||
anims += [
|
||||
Transform(parts.braces, new_braces),
|
||||
Transform(parts.labels, new_labels),
|
||||
]
|
||||
return anims
|
||||
|
||||
def get_horizontal_division_change_animations(self, p_list, **kwargs):
|
||||
assert(hasattr(self.sample_space, "horizontal_parts"))
|
||||
return self.get_division_change_animations(
|
||||
self.sample_space, self.sample_space.horizontal_parts, p_list,
|
||||
dimension=1,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def get_vertical_division_change_animations(self, p_list, **kwargs):
|
||||
assert(hasattr(self.sample_space, "vertical_parts"))
|
||||
return self.get_division_change_animations(
|
||||
self.sample_space, self.sample_space.vertical_parts, p_list,
|
||||
dimension=0,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def get_conditional_change_anims(
|
||||
self, sub_sample_space_index, value, post_rects=None,
|
||||
**kwargs
|
||||
):
|
||||
parts = self.sample_space.horizontal_parts
|
||||
sub_sample_space = parts[sub_sample_space_index]
|
||||
anims = self.get_division_change_animations(
|
||||
sub_sample_space, sub_sample_space.vertical_parts, value,
|
||||
dimension=0,
|
||||
**kwargs
|
||||
)
|
||||
if post_rects is not None:
|
||||
anims += self.get_posterior_rectangle_change_anims(post_rects)
|
||||
return anims
|
||||
|
||||
def get_top_conditional_change_anims(self, *args, **kwargs):
|
||||
return self.get_conditional_change_anims(0, *args, **kwargs)
|
||||
|
||||
def get_bottom_conditional_change_anims(self, *args, **kwargs):
|
||||
return self.get_conditional_change_anims(1, *args, **kwargs)
|
||||
|
||||
def get_prior_rectangles(self):
|
||||
return VGroup(*[
|
||||
self.sample_space.horizontal_parts[i].vertical_parts[0]
|
||||
for i in range(2)
|
||||
])
|
||||
|
||||
def get_posterior_rectangles(self, buff=MED_LARGE_BUFF):
|
||||
prior_rects = self.get_prior_rectangles()
|
||||
areas = [
|
||||
rect.get_width() * rect.get_height()
|
||||
for rect in prior_rects
|
||||
]
|
||||
total_area = sum(areas)
|
||||
total_height = prior_rects.get_height()
|
||||
|
||||
post_rects = prior_rects.copy()
|
||||
for rect, area in zip(post_rects, areas):
|
||||
rect.stretch_to_fit_height(total_height * area / total_area)
|
||||
rect.stretch_to_fit_width(
|
||||
area / rect.get_height()
|
||||
)
|
||||
post_rects.arrange(DOWN, buff=0)
|
||||
post_rects.next_to(
|
||||
self.sample_space, RIGHT, buff
|
||||
)
|
||||
return post_rects
|
||||
|
||||
def get_posterior_rectangle_braces_and_labels(
|
||||
self, post_rects, labels, direction=RIGHT, **kwargs
|
||||
):
|
||||
return self.sample_space.get_subdivision_braces_and_labels(
|
||||
post_rects, labels, direction, **kwargs
|
||||
)
|
||||
|
||||
def update_posterior_braces(self, post_rects):
|
||||
braces = post_rects.braces
|
||||
labels = post_rects.labels
|
||||
for rect, brace, label in zip(post_rects, braces, labels):
|
||||
brace.stretch_to_fit_height(rect.get_height())
|
||||
brace.next_to(rect, RIGHT, SMALL_BUFF)
|
||||
label.next_to(brace, RIGHT, SMALL_BUFF)
|
||||
|
||||
def get_posterior_rectangle_change_anims(self, post_rects):
|
||||
def update_rects(rects):
|
||||
new_rects = self.get_posterior_rectangles()
|
||||
Transform(rects, new_rects).update(1)
|
||||
if hasattr(rects, "braces"):
|
||||
self.update_posterior_braces(rects)
|
||||
return rects
|
||||
|
||||
anims = [UpdateFromFunc(post_rects, update_rects)]
|
||||
if hasattr(post_rects, "braces"):
|
||||
anims += list(map(Animation, [
|
||||
post_rects.labels, post_rects.braces
|
||||
]))
|
||||
return anims
|
|
@ -18,12 +18,14 @@ from tqdm.auto import tqdm as ProgressDisplay
|
|||
from manimlib.animation.animation import prepare_animation
|
||||
from manimlib.animation.fading import VFadeInThenOut
|
||||
from manimlib.camera.camera import Camera
|
||||
from manimlib.camera.camera_frame import CameraFrame
|
||||
from manimlib.config import get_module
|
||||
from manimlib.constants import ARROW_SYMBOLS
|
||||
from manimlib.constants import DEFAULT_WAIT_TIME
|
||||
from manimlib.constants import COMMAND_MODIFIER
|
||||
from manimlib.constants import SHIFT_MODIFIER
|
||||
from manimlib.constants import RED
|
||||
from manimlib.constants import FRAME_HEIGHT
|
||||
from manimlib.event_handler import EVENT_DISPATCHER
|
||||
from manimlib.event_handler.event_type import EventType
|
||||
from manimlib.logger import log
|
||||
|
@ -42,6 +44,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Callable, Iterable
|
||||
from manimlib.typing import Vect3
|
||||
|
||||
from PIL.Image import Image
|
||||
|
||||
|
@ -50,19 +53,21 @@ if TYPE_CHECKING:
|
|||
|
||||
PAN_3D_KEY = 'd'
|
||||
FRAME_SHIFT_KEY = 'f'
|
||||
ZOOM_KEY = 'z'
|
||||
RESET_FRAME_KEY = 'r'
|
||||
QUIT_KEY = 'q'
|
||||
|
||||
|
||||
class Scene(object):
|
||||
random_seed: int = 0
|
||||
pan_sensitivity: float = 3.0
|
||||
pan_sensitivity: float = 0.5
|
||||
scroll_sensitivity: float = 20
|
||||
max_num_saved_states: int = 50
|
||||
default_camera_config: dict = dict()
|
||||
default_window_config: dict = dict()
|
||||
default_file_writer_config: dict = dict()
|
||||
samples = 0
|
||||
# Euler angles, in degrees
|
||||
default_frame_orientation = (0, 0)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -108,6 +113,10 @@ class Scene(object):
|
|||
|
||||
# Core state of the scene
|
||||
self.camera: Camera = Camera(**self.camera_config)
|
||||
self.frame: CameraFrame = self.camera.frame
|
||||
self.frame.reorient(*self.default_frame_orientation)
|
||||
self.frame.make_orientation_default()
|
||||
|
||||
self.file_writer = SceneFileWriter(self, **self.file_writer_config)
|
||||
self.mobjects: list[Mobject] = [self.camera.frame]
|
||||
self.id_to_mobject_map: dict[int, Mobject] = dict()
|
||||
|
@ -793,9 +802,10 @@ class Scene(object):
|
|||
|
||||
def on_mouse_motion(
|
||||
self,
|
||||
point: np.ndarray,
|
||||
d_point: np.ndarray
|
||||
point: Vect3,
|
||||
d_point: Vect3
|
||||
) -> None:
|
||||
assert(self.window is not None)
|
||||
self.mouse_point.move_to(point)
|
||||
|
||||
event_data = {"point": point, "d_point": d_point}
|
||||
|
@ -806,25 +816,23 @@ class Scene(object):
|
|||
frame = self.camera.frame
|
||||
# Handle perspective changes
|
||||
if self.window.is_key_pressed(ord(PAN_3D_KEY)):
|
||||
frame.increment_theta(-self.pan_sensitivity * d_point[0])
|
||||
frame.increment_phi(self.pan_sensitivity * d_point[1])
|
||||
ff_d_point = frame.to_fixed_frame_point(d_point, relative=True)
|
||||
ff_d_point *= self.pan_sensitivity
|
||||
frame.increment_theta(-ff_d_point[0])
|
||||
frame.increment_phi(ff_d_point[1])
|
||||
# Handle frame movements
|
||||
elif self.window.is_key_pressed(ord(FRAME_SHIFT_KEY)):
|
||||
shift = -d_point
|
||||
shift[0] *= frame.get_width() / 2
|
||||
shift[1] *= frame.get_height() / 2
|
||||
transform = frame.get_inverse_camera_rotation_matrix()
|
||||
shift = np.dot(np.transpose(transform), shift)
|
||||
frame.shift(shift)
|
||||
frame.shift(-d_point)
|
||||
|
||||
def on_mouse_drag(
|
||||
self,
|
||||
point: np.ndarray,
|
||||
d_point: np.ndarray,
|
||||
point: Vect3,
|
||||
d_point: Vect3,
|
||||
buttons: int,
|
||||
modifiers: int
|
||||
) -> None:
|
||||
self.mouse_drag_point.move_to(point)
|
||||
self.frame.shift(-d_point)
|
||||
|
||||
event_data = {"point": point, "d_point": d_point, "buttons": buttons, "modifiers": modifiers}
|
||||
propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseDragEvent, **event_data)
|
||||
|
@ -833,7 +841,7 @@ class Scene(object):
|
|||
|
||||
def on_mouse_press(
|
||||
self,
|
||||
point: np.ndarray,
|
||||
point: Vect3,
|
||||
button: int,
|
||||
mods: int
|
||||
) -> None:
|
||||
|
@ -845,7 +853,7 @@ class Scene(object):
|
|||
|
||||
def on_mouse_release(
|
||||
self,
|
||||
point: np.ndarray,
|
||||
point: Vect3,
|
||||
button: int,
|
||||
mods: int
|
||||
) -> None:
|
||||
|
@ -856,22 +864,21 @@ class Scene(object):
|
|||
|
||||
def on_mouse_scroll(
|
||||
self,
|
||||
point: np.ndarray,
|
||||
offset: np.ndarray
|
||||
point: Vect3,
|
||||
offset: Vect3,
|
||||
x_pixel_offset: float,
|
||||
y_pixel_offset: float
|
||||
) -> None:
|
||||
event_data = {"point": point, "offset": offset}
|
||||
propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseScrollEvent, **event_data)
|
||||
if propagate_event is not None and propagate_event is False:
|
||||
return
|
||||
|
||||
frame = self.camera.frame
|
||||
if self.window.is_key_pressed(ord(ZOOM_KEY)):
|
||||
factor = 1 + np.arctan(10 * offset[1])
|
||||
frame.scale(1 / factor, about_point=point)
|
||||
else:
|
||||
transform = frame.get_inverse_camera_rotation_matrix()
|
||||
shift = np.dot(np.transpose(transform), offset)
|
||||
frame.shift(-20.0 * shift)
|
||||
rel_offset = y_pixel_offset / self.camera.get_pixel_height()
|
||||
self.frame.scale(
|
||||
1 - self.scroll_sensitivity * rel_offset,
|
||||
about_point=point
|
||||
)
|
||||
|
||||
def on_key_release(
|
||||
self,
|
||||
|
@ -971,3 +978,14 @@ class SceneState():
|
|||
|
||||
class EndScene(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ThreeDScene(Scene):
|
||||
samples = 4
|
||||
default_frame_orientation = (-30, 70)
|
||||
|
||||
def add(self, *mobjects, set_depth_test: bool = True):
|
||||
for mob in mobjects:
|
||||
if set_depth_test and not mob.is_fixed_in_frame():
|
||||
mob.apply_depth_test()
|
||||
super().add(*mobjects)
|
||||
|
|
|
@ -248,8 +248,8 @@ class ShaderWrapper(object):
|
|||
def generate_vao(self, refresh: bool = True):
|
||||
self.release()
|
||||
# Data buffer
|
||||
vbo = self.vbo = self.get_vertex_buffer_object(refresh)
|
||||
ibo = self.ibo = self.get_index_buffer_object(refresh)
|
||||
vbo = self.get_vertex_buffer_object(refresh)
|
||||
ibo = self.get_index_buffer_object(refresh)
|
||||
|
||||
# Vertex array object
|
||||
self.vao = self.ctx.vertex_array(
|
||||
|
|
|
@ -24,14 +24,11 @@ vec4 add_light(vec4 color, vec3 point, vec3 unit_normal){
|
|||
vec3 to_camera = normalize(camera_position - point);
|
||||
vec3 to_light = normalize(light_position - point);
|
||||
|
||||
// Note, this effectively treats surfaces as two-sided
|
||||
// if(dot(to_camera, unit_normal) < 0) unit_normal *= -1;
|
||||
|
||||
float light_to_normal = dot(to_light, unit_normal);
|
||||
// When unit normal points towards light, brighten
|
||||
float bright_factor = max(light_to_normal, 0) * reflectiveness;
|
||||
// For glossy surface, add extra shine if light beam go towards camera
|
||||
vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal);
|
||||
vec3 light_reflection = reflect(-to_light, unit_normal);
|
||||
float light_to_cam = dot(light_reflection, to_camera);
|
||||
float shine = gloss * exp(-3 * pow(1 - light_to_cam, 2));
|
||||
bright_factor += shine;
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
uniform float is_fixed_in_frame;
|
||||
uniform mat4 view;
|
||||
uniform vec2 frame_shape;
|
||||
uniform float focal_distance;
|
||||
|
||||
const vec2 DEFAULT_FRAME_SHAPE = vec2(8.0 * 16.0 / 9.0, 8.0);
|
||||
const float DEFAULT_FRAME_HEIGHT = 8.0;
|
||||
const float DEFAULT_FRAME_WIDTH = DEFAULT_FRAME_HEIGHT * 16.0 / 9.0;
|
||||
|
||||
vec4 get_gl_Position(vec3 point){
|
||||
bool is_fixed = bool(is_fixed_in_frame);
|
||||
vec4 result = vec4(point, 1.0);
|
||||
if(!is_fixed){
|
||||
if(!bool(is_fixed_in_frame)){
|
||||
result = view * result;
|
||||
}
|
||||
|
||||
vec2 shape = is_fixed ? DEFAULT_FRAME_SHAPE : frame_shape;
|
||||
result.x *= 2.0 / shape.x;
|
||||
result.y *= 2.0 / shape.y;
|
||||
result.x *= 2.0 / DEFAULT_FRAME_WIDTH;
|
||||
result.y *= 2.0 / DEFAULT_FRAME_HEIGHT;
|
||||
result.z /= focal_distance;
|
||||
result.w = 1.0 - result.z;
|
||||
// Flip and scale to prevent premature clipping
|
||||
|
|
|
@ -15,8 +15,6 @@ uniform vec3 color6;
|
|||
uniform vec3 color7;
|
||||
uniform vec3 color8;
|
||||
|
||||
uniform vec2 frame_shape;
|
||||
|
||||
in vec3 xyz_coords;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
|
|
@ -26,8 +26,6 @@ uniform float saturation_factor;
|
|||
uniform float black_for_cycles;
|
||||
uniform float is_parameter_space;
|
||||
|
||||
uniform vec2 frame_shape;
|
||||
|
||||
in vec3 xyz_coords;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
|
|
@ -26,7 +26,7 @@ float dist_to_curve(){
|
|||
// Evaluate F(x, y) = y - x^2
|
||||
// divide by its gradient's magnitude
|
||||
float Fxy = y0 - x0 * x0;
|
||||
float approx_dist = abs(Fxy) / sqrt(1.0 + 4 * x0 * x0);
|
||||
float approx_dist = abs(Fxy) * inversesqrt(1.0 + 4 * x0 * x0);
|
||||
if(approx_dist < QUICK_DIST_WIDTH) return approx_dist;
|
||||
|
||||
// Otherwise, solve for the minimal distance.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#version 330
|
||||
|
||||
uniform vec2 frame_shape;
|
||||
uniform float frame_scale;
|
||||
uniform float is_fixed_in_frame;
|
||||
|
||||
in vec3 point;
|
||||
in vec4 stroke_rgba;
|
||||
|
@ -20,7 +21,10 @@ const float STROKE_WIDTH_CONVERSION = 0.01;
|
|||
|
||||
void main(){
|
||||
verts = point;
|
||||
v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width * frame_shape[1] / 8.0;
|
||||
v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width;
|
||||
if(!bool(is_fixed_in_frame)){
|
||||
v_stroke_width *= frame_scale;
|
||||
}
|
||||
v_joint_product = joint_product;
|
||||
v_color = stroke_rgba;
|
||||
v_vert_index = gl_VertexID;
|
||||
|
|
|
@ -7,8 +7,8 @@ import moderngl
|
|||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
from manimlib.constants import DEFAULT_PIXEL_HEIGHT
|
||||
from manimlib.constants import DEFAULT_PIXEL_WIDTH
|
||||
from manimlib.config import parse_cli
|
||||
from manimlib.config import get_configuration
|
||||
from manimlib.utils.customization import get_customization
|
||||
from manimlib.utils.directories import get_shader_dir
|
||||
from manimlib.utils.file_ops import find_file
|
||||
|
@ -103,7 +103,7 @@ def get_colormap_code(rgb_list: Sequence[float]) -> str:
|
|||
|
||||
|
||||
@lru_cache()
|
||||
def get_fill_canvas(ctx) -> Tuple[Framebuffer, VertexArray, Tuple[float, float, float]]:
|
||||
def get_fill_canvas(ctx: moderngl.Context) -> Tuple[Framebuffer, VertexArray, Tuple[float, float, float]]:
|
||||
"""
|
||||
Because VMobjects with fill are rendered in a funny way, using
|
||||
alpha blending to effectively compute the winding number around
|
||||
|
@ -114,15 +114,14 @@ def get_fill_canvas(ctx) -> Tuple[Framebuffer, VertexArray, Tuple[float, float,
|
|||
which can display that texture as a simple quad onto a screen,
|
||||
along with the rgb value which is meant to be discarded.
|
||||
"""
|
||||
cam_config = get_customization()['camera_resolutions']
|
||||
res_name = cam_config['default_resolution']
|
||||
size = tuple(map(int, cam_config[res_name].split("x")))
|
||||
cam_config = get_configuration(parse_cli())['camera_config']
|
||||
size = (cam_config['pixel_width'], cam_config['pixel_height'])
|
||||
|
||||
# Important to make sure dtype is floating point (not fixed point)
|
||||
# so that alpha values can be negative and are not clipped
|
||||
texture = ctx.texture(size=size, components=4, dtype='f2')
|
||||
depth_buffer = ctx.depth_renderbuffer(size) # TODO, currently not used
|
||||
texture_fbo = ctx.framebuffer(texture, depth_buffer)
|
||||
depth_texture = ctx.depth_texture(size=size)
|
||||
texture_fbo = ctx.framebuffer(texture, depth_texture)
|
||||
|
||||
# We'll paint onto a canvas with initially negative rgbs, and
|
||||
# discard any pixels remaining close to this value. This is
|
||||
|
@ -150,6 +149,7 @@ def get_fill_canvas(ctx) -> Tuple[Framebuffer, VertexArray, Tuple[float, float,
|
|||
#version 330
|
||||
|
||||
uniform sampler2D Texture;
|
||||
uniform sampler2D DepthTexture;
|
||||
uniform vec3 null_rgb;
|
||||
|
||||
in vec2 v_textcoord;
|
||||
|
@ -163,13 +163,16 @@ def get_fill_canvas(ctx) -> Tuple[Framebuffer, VertexArray, Tuple[float, float,
|
|||
|
||||
// Un-blend from the null value
|
||||
color.rgb -= (1 - color.a) * null_rgb;
|
||||
// Counteract scaling in fill frag
|
||||
color.a *= 1.01;
|
||||
|
||||
//TODO, set gl_FragDepth;
|
||||
gl_FragDepth = texture(DepthTexture, v_textcoord)[0];
|
||||
}
|
||||
''',
|
||||
)
|
||||
|
||||
simple_program['Texture'].value = get_texture_id(texture)
|
||||
simple_program['DepthTexture'].value = get_texture_id(depth_texture)
|
||||
simple_program['null_rgb'].value = null_rgb
|
||||
|
||||
verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
|
||||
|
|
|
@ -7,6 +7,7 @@ from moderngl_window.context.pyglet.window import Window as PygletWindow
|
|||
from moderngl_window.timers.clock import Timer
|
||||
from screeninfo import get_monitors
|
||||
|
||||
from manimlib.constants import FRAME_SHAPE
|
||||
from manimlib.utils.customization import get_customization
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
@ -77,17 +78,15 @@ class Window(PygletWindow):
|
|||
py: int,
|
||||
relative: bool = False
|
||||
) -> np.ndarray:
|
||||
pw, ph = self.size
|
||||
fw, fh = self.scene.camera.get_frame_shape()
|
||||
fc = self.scene.camera.get_frame_center()
|
||||
if relative:
|
||||
return np.array([px / pw, py / ph, 0])
|
||||
else:
|
||||
return np.array([
|
||||
fc[0] + px * fw / pw - fw / 2,
|
||||
fc[1] + py * fh / ph - fh / 2,
|
||||
0
|
||||
])
|
||||
pixel_shape = np.array(self.size)
|
||||
fixed_frame_shape = np.array(FRAME_SHAPE)
|
||||
frame = self.scene.frame
|
||||
|
||||
coords = np.zeros(3)
|
||||
coords[:2] = (fixed_frame_shape / pixel_shape) * np.array([px, py])
|
||||
if not relative:
|
||||
coords[:2] -= 0.5 * fixed_frame_shape
|
||||
return frame.from_fixed_frame_point(coords, relative)
|
||||
|
||||
def on_mouse_motion(self, x: int, y: int, dx: int, dy: int) -> None:
|
||||
super().on_mouse_motion(x, y, dx, dy)
|
||||
|
@ -115,7 +114,7 @@ class Window(PygletWindow):
|
|||
super().on_mouse_scroll(x, y, x_offset, y_offset)
|
||||
point = self.pixel_coords_to_space_coords(x, y)
|
||||
offset = self.pixel_coords_to_space_coords(x_offset, y_offset, relative=True)
|
||||
self.scene.on_mouse_scroll(point, offset)
|
||||
self.scene.on_mouse_scroll(point, offset, x_offset, y_offset)
|
||||
|
||||
def on_key_press(self, symbol: int, modifiers: int) -> None:
|
||||
self.pressed_keys.add(symbol) # Modifiers?
|
||||
|
|
|
@ -13,6 +13,7 @@ pydub
|
|||
pygments
|
||||
PyOpenGL
|
||||
pyperclip
|
||||
pyrr
|
||||
pyyaml
|
||||
rich
|
||||
scipy
|
||||
|
|
Loading…
Add table
Reference in a new issue