2022-03-22 11:31:52 -07:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2020-02-04 15:27:21 -08:00
|
|
|
import itertools as it
|
2016-02-23 22:29:32 -08:00
|
|
|
|
2022-04-12 19:19:59 +08:00
|
|
|
import moderngl
|
2018-12-24 12:37:51 -08:00
|
|
|
import numpy as np
|
2022-04-12 19:19:59 +08:00
|
|
|
import OpenGL.GL as gl
|
2022-02-13 19:32:53 +08:00
|
|
|
from PIL import Image
|
2018-03-31 15:11:35 -07:00
|
|
|
|
2023-01-25 10:19:44 -08:00
|
|
|
from manimlib.camera.camera_frame import CameraFrame
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.constants import BLACK
|
2022-05-14 17:47:31 -07:00
|
|
|
from manimlib.constants import DEFAULT_FPS
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.constants import DEFAULT_PIXEL_HEIGHT, DEFAULT_PIXEL_WIDTH
|
2023-01-25 10:19:44 -08:00
|
|
|
from manimlib.constants import FRAME_WIDTH
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.mobject.mobject import Mobject
|
2020-06-02 16:18:44 -07:00
|
|
|
from manimlib.mobject.mobject import Point
|
2022-04-12 19:19:59 +08:00
|
|
|
from manimlib.utils.color import color_to_rgba
|
2020-02-04 15:27:21 -08:00
|
|
|
|
2022-02-14 21:34:56 +08:00
|
|
|
from typing import TYPE_CHECKING
|
2022-02-16 21:08:25 +08:00
|
|
|
|
2022-02-14 21:34:56 +08:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
from manimlib.shader_wrapper import ShaderWrapper
|
2022-12-17 13:16:48 -08:00
|
|
|
from manimlib.typing import ManimColor, Vect3
|
2023-01-23 17:03:46 -08:00
|
|
|
from manimlib.window import Window
|
2022-12-27 14:53:55 -08:00
|
|
|
from typing import Any, Iterable
|
2022-12-14 16:41:19 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-02-23 22:29:32 -08:00
|
|
|
class Camera(object):
|
2022-12-14 16:41:19 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
2023-01-23 17:03:46 -08:00
|
|
|
window: Window | None = None,
|
2022-12-14 16:41:19 -08:00
|
|
|
background_image: str | None = None,
|
2022-12-27 14:53:55 -08:00
|
|
|
frame_config: dict = dict(),
|
2022-12-14 16:41:19 -08:00
|
|
|
pixel_width: int = DEFAULT_PIXEL_WIDTH,
|
|
|
|
pixel_height: int = DEFAULT_PIXEL_HEIGHT,
|
|
|
|
fps: int = DEFAULT_FPS,
|
|
|
|
# Note: frame height and width will be resized to match the pixel aspect ratio
|
|
|
|
background_color: ManimColor = BLACK,
|
|
|
|
background_opacity: float = 1.0,
|
2018-04-06 13:58:59 -07:00
|
|
|
# Points in vectorized mobjects with norm greater
|
|
|
|
# than this value will be rescaled.
|
2022-12-14 16:41:19 -08:00
|
|
|
max_allowable_norm: float = FRAME_WIDTH,
|
|
|
|
image_mode: str = "RGBA",
|
|
|
|
n_channels: int = 4,
|
|
|
|
pixel_array_dtype: type = np.uint8,
|
2022-12-27 14:53:55 -08:00
|
|
|
light_source_position: Vect3 = np.array([-10, 10, 10]),
|
2020-06-08 14:09:31 -07:00
|
|
|
# Although vector graphics handle antialiasing fine
|
|
|
|
# without multisampling, for 3d scenes one might want
|
|
|
|
# to set samples to be greater than 0.
|
2022-12-14 16:41:19 -08:00
|
|
|
samples: int = 0,
|
|
|
|
):
|
|
|
|
self.background_image = background_image
|
2023-01-23 17:03:46 -08:00
|
|
|
self.window = window
|
2023-01-23 14:02:06 -08:00
|
|
|
self.default_pixel_shape = (pixel_width, pixel_height)
|
2022-12-14 16:41:19 -08:00
|
|
|
self.fps = fps
|
|
|
|
self.max_allowable_norm = max_allowable_norm
|
|
|
|
self.image_mode = image_mode
|
|
|
|
self.n_channels = n_channels
|
|
|
|
self.pixel_array_dtype = pixel_array_dtype
|
|
|
|
self.light_source_position = light_source_position
|
|
|
|
self.samples = samples
|
2016-02-23 22:29:32 -08:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
self.rgb_max_val: float = np.iinfo(self.pixel_array_dtype).max
|
2022-04-12 19:19:59 +08:00
|
|
|
self.background_rgba: list[float] = list(color_to_rgba(
|
2022-12-14 16:41:19 -08:00
|
|
|
background_color, background_opacity
|
2022-04-12 19:19:59 +08:00
|
|
|
))
|
2023-01-18 13:59:50 -08:00
|
|
|
self.perspective_uniforms = dict()
|
2022-12-14 16:41:19 -08:00
|
|
|
self.init_frame(**frame_config)
|
2023-01-23 17:03:46 -08:00
|
|
|
self.init_context(window)
|
2020-02-13 10:40:21 -08:00
|
|
|
self.init_textures()
|
2020-06-03 10:38:57 -07:00
|
|
|
self.init_light_source()
|
2020-06-28 12:14:46 -07:00
|
|
|
self.refresh_perspective_uniforms()
|
2023-01-24 15:28:09 -08:00
|
|
|
self.init_fill_fbo(self.ctx) # Experimental
|
2022-04-14 16:27:58 -07:00
|
|
|
# A cached map from mobjects to their associated list of render groups
|
|
|
|
# so that these render groups are not regenerated unnecessarily for static
|
|
|
|
# mobjects
|
|
|
|
self.mob_to_render_groups = {}
|
2020-02-04 15:27:21 -08:00
|
|
|
|
2022-12-14 16:41:19 -08:00
|
|
|
def init_frame(self, **config) -> None:
|
|
|
|
self.frame = CameraFrame(**config)
|
2020-02-04 15:27:21 -08:00
|
|
|
|
2023-01-23 17:03:46 -08:00
|
|
|
def init_context(self, window: Window | None = None) -> None:
|
|
|
|
if window is None:
|
2023-01-24 12:04:43 -08:00
|
|
|
self.ctx = moderngl.create_standalone_context()
|
|
|
|
self.fbo = self.get_fbo(self.samples)
|
2020-02-11 19:51:19 -08:00
|
|
|
else:
|
2023-01-24 12:04:43 -08:00
|
|
|
self.ctx = window.ctx
|
|
|
|
self.fbo = self.ctx.detect_framebuffer()
|
2023-01-20 16:31:09 -08:00
|
|
|
self.fbo.use()
|
2021-11-08 21:46:35 -08:00
|
|
|
self.set_ctx_blending()
|
2020-06-08 14:09:31 -07:00
|
|
|
|
2023-01-18 15:36:00 -08:00
|
|
|
self.ctx.enable(moderngl.PROGRAM_POINT_SIZE)
|
|
|
|
|
2023-01-20 16:31:09 -08:00
|
|
|
# This is the frame buffer we'll draw into when emitting frames
|
2023-01-24 12:04:43 -08:00
|
|
|
self.draw_fbo = self.get_fbo(samples=0)
|
2020-02-11 19:51:19 -08:00
|
|
|
|
2023-01-24 20:03:23 -08:00
|
|
|
def init_fill_fbo(self, ctx: moderngl.context.Context):
|
2023-01-24 13:29:34 -08:00
|
|
|
# Experimental
|
2023-01-24 21:10:57 -08:00
|
|
|
size = self.get_pixel_shape()
|
2023-01-24 15:28:09 -08:00
|
|
|
self.fill_texture = ctx.texture(
|
2023-01-24 21:10:57 -08:00
|
|
|
size=size,
|
2023-01-24 13:29:34 -08:00
|
|
|
components=4,
|
2023-01-24 20:03:23 -08:00
|
|
|
# Important to make sure floating point (not fixed point) is
|
|
|
|
# used so that alpha values are not clipped
|
|
|
|
dtype='f2',
|
2023-01-24 13:29:34 -08:00
|
|
|
)
|
2023-01-24 15:28:09 -08:00
|
|
|
# TODO, depth buffer is not really used yet
|
2023-01-24 21:10:57 -08:00
|
|
|
fill_depth = ctx.depth_renderbuffer(size)
|
2023-01-24 15:28:09 -08:00
|
|
|
self.fill_fbo = ctx.framebuffer(self.fill_texture, fill_depth)
|
|
|
|
self.fill_prog = ctx.program(
|
2023-01-24 13:29:34 -08:00
|
|
|
vertex_shader='''
|
|
|
|
#version 330
|
|
|
|
|
|
|
|
in vec2 texcoord;
|
|
|
|
out vec2 v_textcoord;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
gl_Position = vec4((2.0 * texcoord - 1.0), 0.0, 1.0);
|
|
|
|
v_textcoord = texcoord;
|
|
|
|
}
|
|
|
|
''',
|
|
|
|
fragment_shader='''
|
|
|
|
#version 330
|
|
|
|
|
|
|
|
uniform sampler2D Texture;
|
|
|
|
|
|
|
|
in vec2 v_textcoord;
|
|
|
|
out vec4 frag_color;
|
|
|
|
|
|
|
|
void main() {
|
2023-01-24 15:28:09 -08:00
|
|
|
frag_color = texture(Texture, v_textcoord);
|
2023-01-24 20:03:23 -08:00
|
|
|
frag_color = abs(frag_color);
|
2023-01-24 15:28:09 -08:00
|
|
|
if(frag_color.a == 0) discard;
|
2023-01-24 21:10:57 -08:00
|
|
|
//TODO, set gl_FragDepth;
|
2023-01-24 13:29:34 -08:00
|
|
|
}
|
|
|
|
''',
|
|
|
|
)
|
2023-01-24 21:10:57 -08:00
|
|
|
|
2023-01-24 13:29:34 -08:00
|
|
|
tid = self.n_textures
|
|
|
|
self.fill_texture.use(tid)
|
|
|
|
self.fill_prog['Texture'].value = tid
|
|
|
|
self.n_textures += 1
|
|
|
|
verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
|
2023-01-24 15:28:09 -08:00
|
|
|
self.fill_texture_vao = ctx.simple_vertex_array(
|
2023-01-24 13:29:34 -08:00
|
|
|
self.fill_prog,
|
2023-01-24 15:28:09 -08:00
|
|
|
ctx.buffer(verts.astype('f4').tobytes()),
|
2023-01-24 13:29:34 -08:00
|
|
|
'texcoord',
|
|
|
|
)
|
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def set_ctx_blending(self, enable: bool = True) -> None:
|
2021-11-08 21:46:35 -08:00
|
|
|
if enable:
|
|
|
|
self.ctx.enable(moderngl.BLEND)
|
|
|
|
else:
|
|
|
|
self.ctx.disable(moderngl.BLEND)
|
2020-06-08 14:09:31 -07:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def set_ctx_depth_test(self, enable: bool = True) -> None:
|
2021-11-08 21:46:35 -08:00
|
|
|
if enable:
|
|
|
|
self.ctx.enable(moderngl.DEPTH_TEST)
|
|
|
|
else:
|
|
|
|
self.ctx.disable(moderngl.DEPTH_TEST)
|
2018-05-21 12:11:46 -07:00
|
|
|
|
2023-01-22 09:45:41 -08:00
|
|
|
def set_ctx_clip_plane(self, enable: bool = True) -> None:
|
2023-01-13 21:42:34 -08:00
|
|
|
if enable:
|
|
|
|
gl.glEnable(gl.GL_CLIP_DISTANCE0)
|
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def init_light_source(self) -> None:
|
2020-06-03 10:38:57 -07:00
|
|
|
self.light_source = Point(self.light_source_position)
|
|
|
|
|
2020-02-04 15:27:21 -08:00
|
|
|
# Methods associated with the frame buffer
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_fbo(
|
|
|
|
self,
|
|
|
|
samples: int = 0
|
|
|
|
) -> moderngl.Framebuffer:
|
2023-01-24 12:04:43 -08:00
|
|
|
return self.ctx.framebuffer(
|
|
|
|
color_attachments=self.ctx.texture(
|
2023-01-23 14:02:06 -08:00
|
|
|
self.default_pixel_shape,
|
2020-06-08 14:09:31 -07:00
|
|
|
components=self.n_channels,
|
|
|
|
samples=samples,
|
|
|
|
),
|
2023-01-24 12:04:43 -08:00
|
|
|
depth_attachment=self.ctx.depth_renderbuffer(
|
2023-01-23 14:02:06 -08:00
|
|
|
self.default_pixel_shape,
|
2020-06-08 14:09:31 -07:00
|
|
|
samples=samples
|
|
|
|
)
|
2020-02-13 10:49:43 -08:00
|
|
|
)
|
2018-05-21 12:11:46 -07:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def clear(self) -> None:
|
2020-06-08 14:09:31 -07:00
|
|
|
self.fbo.clear(*self.background_rgba)
|
2020-02-04 15:27:21 -08:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_raw_fbo_data(self, dtype: str = 'f1') -> bytes:
|
2023-01-20 16:31:09 -08:00
|
|
|
# Copy blocks from fbo into draw_fbo using Blit
|
|
|
|
gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, self.fbo.glo)
|
|
|
|
gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, self.draw_fbo.glo)
|
2023-01-23 17:03:46 -08:00
|
|
|
if self.window is not None:
|
|
|
|
src_viewport = self.window.viewport
|
|
|
|
else:
|
|
|
|
src_viewport = self.fbo.viewport
|
2023-01-23 11:54:01 -08:00
|
|
|
gl.glBlitFramebuffer(
|
2023-01-23 17:03:46 -08:00
|
|
|
*src_viewport,
|
2023-01-23 11:54:01 -08:00
|
|
|
*self.draw_fbo.viewport,
|
|
|
|
gl.GL_COLOR_BUFFER_BIT, gl.GL_LINEAR
|
|
|
|
)
|
2023-01-20 16:31:09 -08:00
|
|
|
return self.draw_fbo.read(
|
|
|
|
viewport=self.draw_fbo.viewport,
|
2020-02-13 10:40:21 -08:00
|
|
|
components=self.n_channels,
|
|
|
|
dtype=dtype,
|
|
|
|
)
|
2016-02-23 22:29:32 -08:00
|
|
|
|
2022-02-14 21:22:18 +08:00
|
|
|
def get_image(self) -> Image.Image:
|
2020-02-04 15:27:21 -08:00
|
|
|
return Image.frombytes(
|
2020-02-13 10:40:21 -08:00
|
|
|
'RGBA',
|
|
|
|
self.get_pixel_shape(),
|
2020-02-04 15:27:21 -08:00
|
|
|
self.get_raw_fbo_data(),
|
|
|
|
'raw', 'RGBA', 0, -1
|
2017-09-26 17:41:45 -07:00
|
|
|
)
|
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_pixel_array(self) -> np.ndarray:
|
2020-02-04 15:27:21 -08:00
|
|
|
raw = self.get_raw_fbo_data(dtype='f4')
|
|
|
|
flat_arr = np.frombuffer(raw, dtype='f4')
|
2023-01-24 12:04:43 -08:00
|
|
|
arr = flat_arr.reshape([*reversed(self.draw_fbo.size), self.n_channels])
|
2022-12-29 20:17:54 -08:00
|
|
|
arr = arr[::-1]
|
2020-02-04 15:27:21 -08:00
|
|
|
# Convert from float
|
|
|
|
return (self.rgb_max_val * arr).astype(self.pixel_array_dtype)
|
|
|
|
|
|
|
|
# Needed?
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_texture(self) -> moderngl.Texture:
|
2020-02-04 15:27:21 -08:00
|
|
|
texture = self.ctx.texture(
|
|
|
|
size=self.fbo.size,
|
|
|
|
components=4,
|
|
|
|
data=self.get_raw_fbo_data(),
|
|
|
|
dtype='f4'
|
|
|
|
)
|
|
|
|
return texture
|
2016-02-23 22:29:32 -08:00
|
|
|
|
2020-02-04 15:27:21 -08:00
|
|
|
# Getting camera attributes
|
2023-01-24 12:04:43 -08:00
|
|
|
def get_pixel_size(self) -> float:
|
2023-01-23 14:02:06 -08:00
|
|
|
return self.frame.get_shape()[0] / self.get_pixel_shape()[0]
|
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_pixel_shape(self) -> tuple[int, int]:
|
2023-01-23 17:04:44 -08:00
|
|
|
return self.draw_fbo.size
|
2018-01-31 17:17:58 -08:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_pixel_width(self) -> int:
|
2020-02-04 15:27:21 -08:00
|
|
|
return self.get_pixel_shape()[0]
|
2018-01-31 17:17:58 -08:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_pixel_height(self) -> int:
|
2020-02-04 15:27:21 -08:00
|
|
|
return self.get_pixel_shape()[1]
|
2018-01-31 17:17:58 -08:00
|
|
|
|
2023-01-24 12:04:43 -08:00
|
|
|
def get_aspect_ratio(self):
|
|
|
|
pw, ph = self.get_pixel_shape()
|
|
|
|
return pw / ph
|
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_frame_height(self) -> float:
|
2020-02-04 15:27:21 -08:00
|
|
|
return self.frame.get_height()
|
2018-03-09 10:32:19 -08:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_frame_width(self) -> float:
|
2020-02-04 15:27:21 -08:00
|
|
|
return self.frame.get_width()
|
2018-01-31 17:17:58 -08:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_frame_shape(self) -> tuple[float, float]:
|
2020-02-11 19:51:19 -08:00
|
|
|
return (self.get_frame_width(), self.get_frame_height())
|
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_frame_center(self) -> np.ndarray:
|
2020-02-04 15:27:21 -08:00
|
|
|
return self.frame.get_center()
|
2016-11-23 17:50:25 -08:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_location(self) -> tuple[float, float, float]:
|
2021-11-08 21:46:35 -08:00
|
|
|
return self.frame.get_implied_camera_location()
|
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def resize_frame_shape(self, fixed_dimension: bool = False) -> None:
|
2020-06-08 14:09:31 -07:00
|
|
|
"""
|
|
|
|
Changes frame_shape to match the aspect ratio
|
|
|
|
of the pixels, where fixed_dimension determines
|
|
|
|
whether frame_height or frame_width
|
|
|
|
remains fixed while the other changes accordingly.
|
|
|
|
"""
|
|
|
|
frame_height = self.get_frame_height()
|
|
|
|
frame_width = self.get_frame_width()
|
2023-01-24 12:04:43 -08:00
|
|
|
aspect_ratio = self.get_aspect_ratio()
|
2022-02-13 19:32:53 +08:00
|
|
|
if not fixed_dimension:
|
2020-06-08 14:09:31 -07:00
|
|
|
frame_height = frame_width / aspect_ratio
|
|
|
|
else:
|
|
|
|
frame_width = aspect_ratio * frame_height
|
2023-01-24 12:04:43 -08:00
|
|
|
self.frame.set_height(frame_height, stretch=true)
|
|
|
|
self.frame.set_width(frame_width, stretch=true)
|
2020-06-08 14:09:31 -07:00
|
|
|
|
2020-02-04 15:27:21 -08:00
|
|
|
# Rendering
|
2022-12-27 14:53:55 -08:00
|
|
|
def capture(self, *mobjects: Mobject) -> None:
|
2020-06-26 19:29:34 -07:00
|
|
|
self.refresh_perspective_uniforms()
|
|
|
|
for mobject in mobjects:
|
2020-06-29 18:25:56 -07:00
|
|
|
for render_group in self.get_render_group_list(mobject):
|
|
|
|
self.render(render_group)
|
2020-06-29 18:17:18 -07:00
|
|
|
|
2022-12-27 14:53:55 -08:00
|
|
|
def render(self, render_group: dict[str, Any]) -> None:
|
2020-06-29 18:17:18 -07:00
|
|
|
shader_wrapper = render_group["shader_wrapper"]
|
|
|
|
shader_program = render_group["prog"]
|
2023-01-24 14:09:41 -08:00
|
|
|
primitive = int(shader_wrapper.render_primitive)
|
2020-06-29 18:17:18 -07:00
|
|
|
self.set_shader_uniforms(shader_program, shader_wrapper)
|
2021-11-08 21:46:35 -08:00
|
|
|
self.set_ctx_depth_test(shader_wrapper.depth_test)
|
2023-01-25 10:31:05 -08:00
|
|
|
self.set_ctx_clip_plane(shader_wrapper.use_clip_plane())
|
2023-01-24 13:29:34 -08:00
|
|
|
|
2023-01-24 14:09:41 -08:00
|
|
|
if shader_wrapper.is_fill:
|
2023-01-24 20:03:23 -08:00
|
|
|
self.render_fill(render_group["vao"], primitive, shader_wrapper.vert_indices)
|
2023-01-24 13:29:34 -08:00
|
|
|
else:
|
2023-01-24 14:09:41 -08:00
|
|
|
render_group["vao"].render(primitive)
|
2023-01-24 13:29:34 -08:00
|
|
|
|
2020-06-29 18:25:56 -07:00
|
|
|
if render_group["single_use"]:
|
2020-06-29 18:17:18 -07:00
|
|
|
self.release_render_group(render_group)
|
|
|
|
|
2023-01-24 20:03:23 -08:00
|
|
|
def render_fill(self, vao, render_primitive: int, indices: np.ndarray):
|
2023-01-24 14:09:41 -08:00
|
|
|
"""
|
|
|
|
VMobject fill is handled in a special way, where emited triangles
|
|
|
|
must be blended with moderngl.FUNC_SUBTRACT so as to effectively compute
|
|
|
|
a winding number around each pixel. This is rendered to a separate texture,
|
|
|
|
then that texture is overlayed onto the current fbo
|
|
|
|
"""
|
2023-01-24 20:03:23 -08:00
|
|
|
winding = (len(indices) == 0)
|
|
|
|
vao.program['winding'].value = winding
|
|
|
|
if not winding:
|
|
|
|
vao.render(moderngl.TRIANGLES)
|
|
|
|
return
|
2023-01-25 09:51:27 -08:00
|
|
|
self.fill_fbo.clear()
|
2023-01-24 14:09:41 -08:00
|
|
|
self.fill_fbo.use()
|
2023-01-25 09:51:27 -08:00
|
|
|
self.ctx.blend_func = (moderngl.ONE, moderngl.ONE)
|
2023-01-24 20:03:23 -08:00
|
|
|
vao.render(render_primitive)
|
2023-01-24 14:09:41 -08:00
|
|
|
self.ctx.blend_func = moderngl.DEFAULT_BLENDING
|
|
|
|
self.fbo.use()
|
|
|
|
self.fill_texture_vao.render(moderngl.TRIANGLE_STRIP)
|
|
|
|
|
2022-12-27 14:53:55 -08:00
|
|
|
def get_render_group_list(self, mobject: Mobject) -> Iterable[dict[str, Any]]:
|
2022-04-14 16:27:58 -07:00
|
|
|
if mobject.is_changing():
|
|
|
|
return self.generate_render_group_list(mobject)
|
|
|
|
|
|
|
|
# Otherwise, cache result for later use
|
|
|
|
key = id(mobject)
|
|
|
|
if key not in self.mob_to_render_groups:
|
|
|
|
self.mob_to_render_groups[key] = list(self.generate_render_group_list(mobject))
|
|
|
|
return self.mob_to_render_groups[key]
|
|
|
|
|
2022-12-27 14:53:55 -08:00
|
|
|
def generate_render_group_list(self, mobject: Mobject) -> Iterable[dict[str, Any]]:
|
2022-04-14 16:27:58 -07:00
|
|
|
return (
|
|
|
|
self.get_render_group(sw, single_use=mobject.is_changing())
|
2023-01-25 10:49:30 -08:00
|
|
|
for sw in mobject.get_shader_wrapper_list(self.ctx)
|
2022-04-14 16:27:58 -07:00
|
|
|
)
|
2020-06-29 18:17:18 -07:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_render_group(
|
|
|
|
self,
|
|
|
|
shader_wrapper: ShaderWrapper,
|
|
|
|
single_use: bool = True
|
2022-12-27 14:53:55 -08:00
|
|
|
) -> dict[str, Any]:
|
2023-01-12 15:56:44 -08:00
|
|
|
# Data buffer
|
|
|
|
vert_data = shader_wrapper.vert_data
|
|
|
|
indices = shader_wrapper.vert_indices
|
2023-01-24 15:53:43 -08:00
|
|
|
if len(indices) == 0:
|
2023-01-14 16:03:16 -08:00
|
|
|
ibo = None
|
|
|
|
elif single_use:
|
2023-01-15 21:06:55 -08:00
|
|
|
ibo = self.ctx.buffer(indices.astype(np.uint32))
|
2023-01-14 16:03:16 -08:00
|
|
|
else:
|
2023-01-24 20:03:23 -08:00
|
|
|
ibo = self.ctx.buffer(indices.astype(np.uint32))
|
|
|
|
# # The vao.render call is strangely longer
|
|
|
|
# # when an index buffer is used, so if the
|
|
|
|
# # mobject is not changing, meaning only its
|
|
|
|
# # uniforms are being updated, just create
|
|
|
|
# # a larger data array based on the indices
|
|
|
|
# # and don't bother with the ibo
|
|
|
|
# vert_data = vert_data[indices]
|
|
|
|
# ibo = None
|
2023-01-14 08:31:53 -08:00
|
|
|
vbo = self.ctx.buffer(vert_data)
|
2020-06-29 11:05:09 -07:00
|
|
|
|
2020-06-29 18:17:18 -07:00
|
|
|
# Program and vertex array
|
2023-01-25 11:23:31 -08:00
|
|
|
shader_program = shader_wrapper.program
|
|
|
|
vert_format = shader_wrapper.vert_format
|
2023-01-24 12:04:43 -08:00
|
|
|
attributes = shader_wrapper.vert_attributes
|
2020-06-27 00:00:50 -07:00
|
|
|
vao = self.ctx.vertex_array(
|
2020-06-29 18:17:18 -07:00
|
|
|
program=shader_program,
|
2023-01-24 12:04:43 -08:00
|
|
|
content=[(vbo, vert_format, *attributes)],
|
2020-06-29 11:05:09 -07:00
|
|
|
index_buffer=ibo,
|
2020-06-27 00:00:50 -07:00
|
|
|
)
|
2020-06-29 18:17:18 -07:00
|
|
|
return {
|
|
|
|
"vbo": vbo,
|
|
|
|
"ibo": ibo,
|
|
|
|
"vao": vao,
|
|
|
|
"prog": shader_program,
|
|
|
|
"shader_wrapper": shader_wrapper,
|
2020-06-29 18:25:56 -07:00
|
|
|
"single_use": single_use,
|
2020-06-29 18:17:18 -07:00
|
|
|
}
|
|
|
|
|
2022-12-27 14:53:55 -08:00
|
|
|
def release_render_group(self, render_group: dict[str, Any]) -> None:
|
2020-06-29 18:17:18 -07:00
|
|
|
for key in ["vbo", "ibo", "vao"]:
|
|
|
|
if render_group[key] is not None:
|
|
|
|
render_group[key].release()
|
2020-06-29 11:05:09 -07:00
|
|
|
|
2022-04-14 16:27:58 -07:00
|
|
|
def refresh_static_mobjects(self) -> None:
|
|
|
|
for render_group in it.chain(*self.mob_to_render_groups.values()):
|
|
|
|
self.release_render_group(render_group)
|
|
|
|
self.mob_to_render_groups = {}
|
2020-06-29 18:25:56 -07:00
|
|
|
|
2020-02-11 19:51:19 -08:00
|
|
|
# Shaders
|
2020-06-29 18:17:18 -07:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def set_shader_uniforms(
|
|
|
|
self,
|
|
|
|
shader: moderngl.Program,
|
|
|
|
shader_wrapper: ShaderWrapper
|
|
|
|
) -> None:
|
2020-06-29 18:17:18 -07:00
|
|
|
for name, path in shader_wrapper.texture_paths.items():
|
2020-06-28 12:14:46 -07:00
|
|
|
tid = self.get_texture_id(path)
|
|
|
|
shader[name].value = tid
|
2021-11-17 12:49:08 -08:00
|
|
|
for name, value in it.chain(self.perspective_uniforms.items(), shader_wrapper.uniforms.items()):
|
2022-12-27 14:53:55 -08:00
|
|
|
if name in shader:
|
2022-03-16 12:23:11 -07:00
|
|
|
if isinstance(value, np.ndarray) and value.ndim > 0:
|
2021-08-26 11:44:24 -07:00
|
|
|
value = tuple(value)
|
2020-06-28 12:14:46 -07:00
|
|
|
shader[name].value = value
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def refresh_perspective_uniforms(self) -> None:
|
2021-01-18 16:39:29 -08:00
|
|
|
frame = self.frame
|
2023-01-23 14:41:17 -08:00
|
|
|
view_matrix = frame.get_view_matrix()
|
2023-01-16 19:34:20 -08:00
|
|
|
light_pos = self.light_source.get_location()
|
2023-01-18 13:44:41 -08:00
|
|
|
cam_pos = self.frame.get_implied_camera_location()
|
2021-01-18 16:39:29 -08:00
|
|
|
|
2023-01-18 13:59:50 -08:00
|
|
|
self.perspective_uniforms.update(
|
2023-01-23 17:10:18 -08:00
|
|
|
frame_shape=frame.get_shape(),
|
|
|
|
pixel_size=self.get_pixel_size(),
|
2023-01-23 14:41:17 -08:00
|
|
|
view=tuple(view_matrix.T.flatten()),
|
2023-01-18 13:59:50 -08:00
|
|
|
camera_position=tuple(cam_pos),
|
|
|
|
light_position=tuple(light_pos),
|
|
|
|
focal_distance=frame.get_focal_distance(),
|
|
|
|
)
|
2020-06-08 20:27:07 -07:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def init_textures(self) -> None:
|
|
|
|
self.n_textures: int = 0
|
|
|
|
self.path_to_texture: dict[
|
|
|
|
str, tuple[int, moderngl.Texture]
|
|
|
|
] = {}
|
2020-02-13 10:40:21 -08:00
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def get_texture_id(self, path: str) -> int:
|
2021-10-15 08:52:37 -07:00
|
|
|
if path not in self.path_to_texture:
|
|
|
|
tid = self.n_textures
|
|
|
|
self.n_textures += 1
|
2021-06-15 21:45:13 -07:00
|
|
|
im = Image.open(path).convert("RGBA")
|
2020-02-13 10:40:21 -08:00
|
|
|
texture = self.ctx.texture(
|
|
|
|
size=im.size,
|
|
|
|
components=len(im.getbands()),
|
|
|
|
data=im.tobytes(),
|
|
|
|
)
|
|
|
|
texture.use(location=tid)
|
2021-10-15 08:52:37 -07:00
|
|
|
self.path_to_texture[path] = (tid, texture)
|
|
|
|
return self.path_to_texture[path][0]
|
|
|
|
|
2022-02-13 19:32:53 +08:00
|
|
|
def release_texture(self, path: str):
|
2021-10-15 08:52:37 -07:00
|
|
|
tid_and_texture = self.path_to_texture.pop(path, None)
|
|
|
|
if tid_and_texture:
|
|
|
|
tid_and_texture[1].release()
|
|
|
|
return self
|
2020-06-04 15:41:20 -07:00
|
|
|
|
|
|
|
|
2020-06-26 23:05:25 -07:00
|
|
|
# Mostly just defined so old scenes don't break
|
2020-06-04 15:41:20 -07:00
|
|
|
class ThreeDCamera(Camera):
|
2022-12-28 18:52:05 -08:00
|
|
|
def __init__(self, samples: int = 4, **kwargs):
|
|
|
|
super().__init__(samples=samples, **kwargs)
|