2020-02-04 15:27:21 -08:00
|
|
|
import moderngl
|
|
|
|
from colour import Color
|
2020-06-08 14:09:31 -07:00
|
|
|
import OpenGL.GL as gl
|
2016-02-23 22:29:32 -08:00
|
|
|
|
2018-03-31 15:11:35 -07:00
|
|
|
from PIL import Image
|
2018-12-24 12:37:51 -08:00
|
|
|
import numpy as np
|
2020-02-04 15:27:21 -08:00
|
|
|
import itertools as it
|
2018-03-31 15:11:35 -07:00
|
|
|
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.constants import *
|
|
|
|
from manimlib.mobject.mobject import Mobject
|
2020-06-02 16:18:44 -07:00
|
|
|
from manimlib.mobject.mobject import Point
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.utils.config_ops import digest_config
|
2020-06-08 14:09:31 -07:00
|
|
|
from manimlib.utils.bezier import interpolate
|
2018-12-24 12:37:51 -08:00
|
|
|
from manimlib.utils.simple_functions import fdiv
|
2020-06-03 10:38:57 -07:00
|
|
|
from manimlib.utils.simple_functions import clip
|
|
|
|
from manimlib.utils.space_ops import angle_of_vector
|
2020-06-01 16:21:18 -07:00
|
|
|
from manimlib.utils.space_ops import rotation_matrix_transpose_from_quaternion
|
2020-06-03 10:38:57 -07:00
|
|
|
from manimlib.utils.space_ops import rotation_matrix_transpose
|
2020-06-01 16:21:18 -07:00
|
|
|
from manimlib.utils.space_ops import quaternion_from_angle_axis
|
|
|
|
from manimlib.utils.space_ops import quaternion_mult
|
2020-02-04 15:27:21 -08:00
|
|
|
|
|
|
|
|
|
|
|
class CameraFrame(Mobject):
|
|
|
|
CONFIG = {
|
2020-06-08 17:55:41 -07:00
|
|
|
"frame_shape": (FRAME_WIDTH, FRAME_HEIGHT),
|
2020-06-01 16:21:18 -07:00
|
|
|
"center_point": ORIGIN,
|
2020-06-03 10:38:57 -07:00
|
|
|
# Theta, phi, gamma
|
|
|
|
"euler_angles": [0, 0, 0],
|
2021-01-09 10:25:59 -08:00
|
|
|
"focal_distance": 2,
|
2020-02-04 15:27:21 -08:00
|
|
|
}
|
|
|
|
|
2020-02-11 19:51:19 -08:00
|
|
|
def init_points(self):
|
2020-06-09 20:40:36 -07:00
|
|
|
self.points = np.array([ORIGIN, LEFT, RIGHT, DOWN, UP])
|
2020-06-08 17:55:41 -07:00
|
|
|
self.set_width(self.frame_shape[0], stretch=True)
|
|
|
|
self.set_height(self.frame_shape[1], stretch=True)
|
|
|
|
self.move_to(self.center_point)
|
2020-06-03 10:38:57 -07:00
|
|
|
self.euler_angles = np.array(self.euler_angles, dtype='float64')
|
2020-06-27 12:10:51 -07:00
|
|
|
self.refresh_camera_rotation_matrix()
|
2020-06-01 16:21:18 -07:00
|
|
|
|
|
|
|
def to_default_state(self):
|
|
|
|
self.center()
|
|
|
|
self.set_height(FRAME_HEIGHT)
|
|
|
|
self.set_width(FRAME_WIDTH)
|
2020-06-03 10:38:57 -07:00
|
|
|
self.set_rotation(0, 0, 0)
|
2020-06-01 16:21:18 -07:00
|
|
|
return self
|
|
|
|
|
2020-06-02 16:18:44 -07:00
|
|
|
def get_inverse_camera_position_matrix(self):
|
2020-06-27 12:10:51 -07:00
|
|
|
mat = np.identity(4)
|
2020-11-24 13:31:21 -08:00
|
|
|
# Shift so that origin of real space coincides with camera origin
|
2020-06-27 12:10:51 -07:00
|
|
|
mat[:3, 3] = -self.get_center().T
|
2020-06-01 16:21:18 -07:00
|
|
|
# Rotate based on camera orientation
|
2020-11-24 13:31:21 -08:00
|
|
|
mat[:3, :4] = np.dot(self.inverse_camera_rotation_matrix, mat[:3, :4])
|
2020-06-27 12:10:51 -07:00
|
|
|
return mat
|
2020-06-01 16:21:18 -07:00
|
|
|
|
2020-06-27 12:10:51 -07:00
|
|
|
def refresh_camera_rotation_matrix(self):
|
2020-06-03 10:38:57 -07:00
|
|
|
theta, phi, gamma = self.euler_angles
|
|
|
|
quat = quaternion_mult(
|
2020-06-09 20:40:36 -07:00
|
|
|
quaternion_from_angle_axis(theta, OUT, axis_normalized=True),
|
|
|
|
quaternion_from_angle_axis(phi, RIGHT, axis_normalized=True),
|
|
|
|
quaternion_from_angle_axis(gamma, OUT, axis_normalized=True),
|
2020-06-03 10:38:57 -07:00
|
|
|
)
|
2020-06-27 12:10:51 -07:00
|
|
|
self.inverse_camera_rotation_matrix = rotation_matrix_transpose_from_quaternion(quat)
|
|
|
|
return self
|
2020-06-03 10:38:57 -07:00
|
|
|
|
|
|
|
def rotate(self, angle, axis=OUT, **kwargs):
|
|
|
|
curr_rot_T = self.get_inverse_camera_rotation_matrix()
|
|
|
|
added_rot_T = rotation_matrix_transpose(angle, axis)
|
|
|
|
new_rot_T = np.dot(curr_rot_T, added_rot_T)
|
|
|
|
Fz = new_rot_T[2]
|
|
|
|
phi = np.arccos(Fz[2])
|
|
|
|
theta = angle_of_vector(Fz[:2]) + PI / 2
|
|
|
|
partial_rot_T = np.dot(
|
|
|
|
rotation_matrix_transpose(phi, RIGHT),
|
|
|
|
rotation_matrix_transpose(theta, OUT),
|
|
|
|
)
|
|
|
|
gamma = angle_of_vector(np.dot(partial_rot_T, new_rot_T.T)[:, 0])
|
|
|
|
# TODO, write a function that converts quaternions to euler angles
|
|
|
|
self.euler_angles[:] = theta, phi, gamma
|
2020-06-01 16:21:18 -07:00
|
|
|
return self
|
|
|
|
|
2020-06-27 12:10:51 -07:00
|
|
|
def set_rotation(self, theta=None, phi=None, gamma=None):
|
|
|
|
if theta is not None:
|
|
|
|
self.euler_angles[0] = theta
|
|
|
|
if phi is not None:
|
|
|
|
self.euler_angles[1] = phi
|
|
|
|
if gamma is not None:
|
|
|
|
self.euler_angles[2] = gamma
|
|
|
|
self.refresh_camera_rotation_matrix()
|
2020-06-01 16:21:18 -07:00
|
|
|
return self
|
|
|
|
|
2020-06-09 21:25:00 -07:00
|
|
|
def set_theta(self, theta):
|
2020-06-27 12:10:51 -07:00
|
|
|
return self.set_rotation(theta=theta)
|
2020-06-09 21:25:00 -07:00
|
|
|
|
|
|
|
def set_phi(self, phi):
|
2020-06-27 12:10:51 -07:00
|
|
|
return self.set_rotation(phi=phi)
|
2020-06-09 21:25:00 -07:00
|
|
|
|
|
|
|
def set_gamma(self, gamma):
|
2020-06-27 12:10:51 -07:00
|
|
|
return self.set_rotation(phi=phi)
|
2020-06-09 21:25:00 -07:00
|
|
|
|
2020-06-01 16:21:18 -07:00
|
|
|
def increment_theta(self, dtheta):
|
2020-06-27 12:10:51 -07:00
|
|
|
return self.set_rotation(theta=self.euler_angles[0] + dtheta)
|
2020-06-01 16:21:18 -07:00
|
|
|
|
|
|
|
def increment_phi(self, dphi):
|
2020-06-27 12:10:51 -07:00
|
|
|
new_phi = clip(self.euler_angles[1] + dphi, 0, PI)
|
|
|
|
return self.set_rotation(phi=new_phi)
|
2020-06-03 10:38:57 -07:00
|
|
|
|
|
|
|
def increment_gamma(self, dgamma):
|
2020-06-27 12:10:51 -07:00
|
|
|
return self.set_rotation(theta=self.euler_angles[2] + dgamma)
|
2020-06-01 16:21:18 -07:00
|
|
|
|
2020-06-08 17:55:41 -07:00
|
|
|
def get_shape(self):
|
2020-06-09 20:40:36 -07:00
|
|
|
return (
|
2020-06-27 12:10:51 -07:00
|
|
|
self.points[2, 0] - self.points[1, 0],
|
|
|
|
self.points[4, 1] - self.points[3, 1],
|
2020-06-09 20:40:36 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
def get_center(self):
|
|
|
|
# Assumes first point is at the center
|
|
|
|
return self.points[0]
|
2020-06-01 16:21:18 -07:00
|
|
|
|
|
|
|
def get_focal_distance(self):
|
2021-01-09 10:25:59 -08:00
|
|
|
return self.focal_distance * self.get_height()
|
2020-06-01 16:21:18 -07:00
|
|
|
|
2020-06-08 14:09:31 -07:00
|
|
|
def interpolate(self, frame1, frame2, alpha, path_func):
|
2020-06-27 12:17:53 -07:00
|
|
|
self.euler_angles[:] = interpolate(frame1.euler_angles, frame2.euler_angles, alpha)
|
|
|
|
self.refresh_camera_rotation_matrix()
|
|
|
|
self.points = interpolate(frame1.points, frame2.points, alpha)
|
2018-02-11 18:21:31 -08:00
|
|
|
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2016-02-23 22:29:32 -08:00
|
|
|
class Camera(object):
|
2016-02-27 16:32:53 -08:00
|
|
|
CONFIG = {
|
2018-04-06 13:58:59 -07:00
|
|
|
"background_image": None,
|
2020-06-01 16:21:18 -07:00
|
|
|
"frame_config": {},
|
2018-05-21 12:11:46 -07:00
|
|
|
"pixel_width": DEFAULT_PIXEL_WIDTH,
|
2021-01-02 23:02:02 -08:00
|
|
|
"pixel_height": DEFAULT_PIXEL_HEIGHT,
|
2021-01-02 22:38:07 -08:00
|
|
|
"frame_rate": DEFAULT_FRAME_RATE,
|
2018-05-21 12:11:46 -07:00
|
|
|
# Note: frame height and width will be resized to match
|
|
|
|
# the pixel aspect ratio
|
2018-04-06 13:58:59 -07:00
|
|
|
"background_color": BLACK,
|
2018-08-08 11:50:34 -07:00
|
|
|
"background_opacity": 1,
|
2018-04-06 13:58:59 -07:00
|
|
|
# Points in vectorized mobjects with norm greater
|
|
|
|
# than this value will be rescaled.
|
|
|
|
"max_allowable_norm": FRAME_WIDTH,
|
|
|
|
"image_mode": "RGBA",
|
2018-08-09 17:56:05 -07:00
|
|
|
"n_channels": 4,
|
2018-04-06 13:58:59 -07:00
|
|
|
"pixel_array_dtype": 'uint8',
|
2020-06-08 14:09:31 -07:00
|
|
|
"light_source_position": [-10, 10, 10],
|
|
|
|
# Measured in pixel widths, used for vector graphics
|
2020-06-09 12:34:00 -07:00
|
|
|
"anti_alias_width": 1.5,
|
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.
|
|
|
|
"samples": 0,
|
2016-02-23 22:29:32 -08:00
|
|
|
}
|
|
|
|
|
2020-02-13 10:40:21 -08:00
|
|
|
def __init__(self, ctx=None, **kwargs):
|
2016-02-23 22:29:32 -08:00
|
|
|
digest_config(self, kwargs, locals())
|
2018-02-16 12:15:16 -08:00
|
|
|
self.rgb_max_val = np.iinfo(self.pixel_array_dtype).max
|
2020-06-08 14:09:31 -07:00
|
|
|
self.background_rgba = [
|
|
|
|
*Color(self.background_color).get_rgb(),
|
|
|
|
self.background_opacity
|
|
|
|
]
|
2020-02-04 15:27:21 -08:00
|
|
|
self.init_frame()
|
2020-02-13 10:40:21 -08:00
|
|
|
self.init_context(ctx)
|
2020-02-04 15:27:21 -08:00
|
|
|
self.init_shaders()
|
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()
|
2020-06-29 18:17:18 -07:00
|
|
|
self.static_mobject_to_render_group_list = {}
|
2020-02-04 15:27:21 -08:00
|
|
|
|
|
|
|
def init_frame(self):
|
|
|
|
self.frame = CameraFrame(**self.frame_config)
|
|
|
|
|
2020-02-13 10:40:21 -08:00
|
|
|
def init_context(self, ctx=None):
|
2020-06-08 14:09:31 -07:00
|
|
|
if ctx is None:
|
|
|
|
ctx = moderngl.create_standalone_context()
|
|
|
|
fbo = self.get_fbo(ctx, 0)
|
2020-02-11 19:51:19 -08:00
|
|
|
else:
|
2020-06-08 14:09:31 -07:00
|
|
|
fbo = ctx.detect_framebuffer()
|
|
|
|
|
|
|
|
# For multisample antialiasing
|
|
|
|
fbo_msaa = self.get_fbo(ctx, self.samples)
|
|
|
|
fbo_msaa.use()
|
2020-02-11 19:51:19 -08:00
|
|
|
|
2020-06-14 17:41:47 -07:00
|
|
|
ctx.enable(moderngl.BLEND)
|
2020-06-08 14:09:31 -07:00
|
|
|
ctx.blend_func = (
|
2020-06-04 15:41:20 -07:00
|
|
|
moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA,
|
|
|
|
moderngl.ONE, moderngl.ONE
|
|
|
|
)
|
2020-06-08 14:09:31 -07:00
|
|
|
|
|
|
|
self.ctx = ctx
|
|
|
|
self.fbo = fbo
|
|
|
|
self.fbo_msaa = fbo_msaa
|
2018-05-21 12:11:46 -07:00
|
|
|
|
2020-06-03 10:38:57 -07:00
|
|
|
def init_light_source(self):
|
|
|
|
self.light_source = Point(self.light_source_position)
|
|
|
|
|
2020-02-04 15:27:21 -08:00
|
|
|
# Methods associated with the frame buffer
|
2020-06-08 14:09:31 -07:00
|
|
|
def get_fbo(self, ctx, samples=0):
|
|
|
|
pw = self.pixel_width
|
|
|
|
ph = self.pixel_height
|
|
|
|
return ctx.framebuffer(
|
|
|
|
color_attachments=ctx.texture(
|
|
|
|
(pw, ph),
|
|
|
|
components=self.n_channels,
|
|
|
|
samples=samples,
|
|
|
|
),
|
|
|
|
depth_attachment=ctx.depth_renderbuffer(
|
|
|
|
(pw, ph),
|
|
|
|
samples=samples
|
|
|
|
)
|
2020-02-13 10:49:43 -08:00
|
|
|
)
|
2018-05-21 12:11:46 -07:00
|
|
|
|
2020-02-04 15:27:21 -08:00
|
|
|
def clear(self):
|
2020-06-08 14:09:31 -07:00
|
|
|
self.fbo.clear(*self.background_rgba)
|
|
|
|
self.fbo_msaa.clear(*self.background_rgba)
|
2020-02-04 15:27:21 -08:00
|
|
|
|
2020-02-11 19:51:19 -08:00
|
|
|
def reset_pixel_shape(self, new_width, new_height):
|
2020-02-04 15:27:21 -08:00
|
|
|
self.pixel_width = new_width
|
|
|
|
self.pixel_height = new_height
|
2020-06-27 12:10:51 -07:00
|
|
|
self.refresh_perspective_uniforms()
|
2020-02-04 15:27:21 -08:00
|
|
|
|
|
|
|
def get_raw_fbo_data(self, dtype='f1'):
|
2020-06-08 14:09:31 -07:00
|
|
|
# Copy blocks from the fbo_msaa to the drawn fbo using Blit
|
|
|
|
pw, ph = (self.pixel_width, self.pixel_height)
|
|
|
|
gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, self.fbo_msaa.glo)
|
|
|
|
gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, self.fbo.glo)
|
|
|
|
gl.glBlitFramebuffer(0, 0, pw, ph, 0, 0, pw, ph, gl.GL_COLOR_BUFFER_BIT, gl.GL_LINEAR)
|
2020-02-13 10:40:21 -08:00
|
|
|
return self.fbo.read(
|
|
|
|
viewport=self.fbo.viewport,
|
|
|
|
components=self.n_channels,
|
|
|
|
dtype=dtype,
|
|
|
|
)
|
2016-02-23 22:29:32 -08:00
|
|
|
|
2018-08-11 20:54:27 -07:00
|
|
|
def get_image(self, pixel_array=None):
|
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
|
|
|
)
|
|
|
|
|
|
|
|
def get_pixel_array(self):
|
2020-02-04 15:27:21 -08:00
|
|
|
raw = self.get_raw_fbo_data(dtype='f4')
|
|
|
|
flat_arr = np.frombuffer(raw, dtype='f4')
|
|
|
|
arr = flat_arr.reshape([*self.fbo.size, self.n_channels])
|
|
|
|
# Convert from float
|
|
|
|
return (self.rgb_max_val * arr).astype(self.pixel_array_dtype)
|
|
|
|
|
|
|
|
# Needed?
|
|
|
|
def get_texture(self):
|
|
|
|
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
|
|
|
|
def get_pixel_shape(self):
|
2020-02-13 10:40:21 -08:00
|
|
|
return self.fbo.viewport[2:4]
|
|
|
|
# return (self.pixel_width, self.pixel_height)
|
2018-01-31 17:17:58 -08:00
|
|
|
|
2020-02-04 15:27:21 -08:00
|
|
|
def get_pixel_width(self):
|
|
|
|
return self.get_pixel_shape()[0]
|
2018-01-31 17:17:58 -08:00
|
|
|
|
2020-02-04 15:27:21 -08:00
|
|
|
def get_pixel_height(self):
|
|
|
|
return self.get_pixel_shape()[1]
|
2018-01-31 17:17:58 -08:00
|
|
|
|
2020-02-04 15:27:21 -08:00
|
|
|
def get_frame_height(self):
|
|
|
|
return self.frame.get_height()
|
2018-03-09 10:32:19 -08:00
|
|
|
|
2020-02-04 15:27:21 -08:00
|
|
|
def get_frame_width(self):
|
|
|
|
return self.frame.get_width()
|
2018-01-31 17:17:58 -08:00
|
|
|
|
2020-02-11 19:51:19 -08:00
|
|
|
def get_frame_shape(self):
|
|
|
|
return (self.get_frame_width(), self.get_frame_height())
|
|
|
|
|
2020-02-04 15:27:21 -08:00
|
|
|
def get_frame_center(self):
|
|
|
|
return self.frame.get_center()
|
2016-11-23 17:50:25 -08:00
|
|
|
|
2020-06-08 14:09:31 -07:00
|
|
|
def resize_frame_shape(self, fixed_dimension=0):
|
|
|
|
"""
|
|
|
|
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.
|
|
|
|
"""
|
|
|
|
pixel_height = self.get_pixel_height()
|
|
|
|
pixel_width = self.get_pixel_width()
|
|
|
|
frame_height = self.get_frame_height()
|
|
|
|
frame_width = self.get_frame_width()
|
|
|
|
aspect_ratio = fdiv(pixel_width, pixel_height)
|
|
|
|
if fixed_dimension == 0:
|
|
|
|
frame_height = frame_width / aspect_ratio
|
|
|
|
else:
|
|
|
|
frame_width = aspect_ratio * frame_height
|
|
|
|
self.frame.set_height(frame_height)
|
|
|
|
self.frame.set_width(frame_width)
|
|
|
|
|
2020-02-11 19:51:19 -08:00
|
|
|
def pixel_coords_to_space_coords(self, px, py, relative=False):
|
2021-01-06 12:46:46 -08:00
|
|
|
pw, ph = self.fbo.size
|
2020-02-11 19:51:19 -08:00
|
|
|
fw, fh = self.get_frame_shape()
|
|
|
|
fc = self.get_frame_center()
|
|
|
|
if relative:
|
|
|
|
return 2 * np.array([px / pw, py / ph, 0])
|
|
|
|
else:
|
|
|
|
# Only scale wrt one axis
|
|
|
|
scale = fh / ph
|
2020-02-14 10:52:39 -08:00
|
|
|
return fc + scale * np.array([(px - pw / 2), (py - ph / 2), 0])
|
2020-02-11 19:51:19 -08:00
|
|
|
|
2020-02-04 15:27:21 -08:00
|
|
|
# Rendering
|
2020-02-13 15:42:53 -08:00
|
|
|
def capture(self, *mobjects, **kwargs):
|
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
|
|
|
|
2020-06-29 18:25:56 -07:00
|
|
|
def render(self, render_group):
|
2020-06-29 18:17:18 -07:00
|
|
|
shader_wrapper = render_group["shader_wrapper"]
|
|
|
|
shader_program = render_group["prog"]
|
|
|
|
self.set_shader_uniforms(shader_program, shader_wrapper)
|
|
|
|
self.update_depth_test(shader_wrapper)
|
2020-12-04 08:11:33 -08:00
|
|
|
render_group["vao"].render(int(shader_wrapper.render_primitive))
|
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)
|
|
|
|
|
|
|
|
def update_depth_test(self, shader_wrapper):
|
|
|
|
if shader_wrapper.depth_test:
|
2020-06-14 17:41:47 -07:00
|
|
|
self.ctx.enable(moderngl.DEPTH_TEST)
|
|
|
|
else:
|
|
|
|
self.ctx.disable(moderngl.DEPTH_TEST)
|
|
|
|
|
2020-06-29 18:25:56 -07:00
|
|
|
def get_render_group_list(self, mobject):
|
|
|
|
try:
|
|
|
|
return self.static_mobject_to_render_group_list[id(mobject)]
|
|
|
|
except KeyError:
|
|
|
|
return map(self.get_render_group, mobject.get_shader_wrapper_list())
|
2020-06-29 18:17:18 -07:00
|
|
|
|
2020-06-29 18:25:56 -07:00
|
|
|
def get_render_group(self, shader_wrapper, single_use=True):
|
2020-06-29 18:17:18 -07:00
|
|
|
# Data buffers
|
|
|
|
vbo = self.ctx.buffer(shader_wrapper.vert_data.tobytes())
|
|
|
|
if shader_wrapper.vert_indices is None:
|
2020-06-29 11:05:09 -07:00
|
|
|
ibo = None
|
|
|
|
else:
|
2020-06-29 18:17:18 -07:00
|
|
|
ibo = self.ctx.buffer(shader_wrapper.vert_indices.astype('i4').tobytes())
|
2020-06-29 11:05:09 -07:00
|
|
|
|
2020-06-29 18:17:18 -07:00
|
|
|
# Program and vertex array
|
|
|
|
shader_program, vert_format = self.get_shader_program(shader_wrapper)
|
2020-06-27 00:00:50 -07:00
|
|
|
vao = self.ctx.vertex_array(
|
2020-06-29 18:17:18 -07:00
|
|
|
program=shader_program,
|
|
|
|
content=[(vbo, vert_format, *shader_wrapper.vert_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
|
|
|
}
|
|
|
|
|
|
|
|
def release_render_group(self, render_group):
|
|
|
|
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
|
|
|
|
2020-06-29 18:25:56 -07:00
|
|
|
def set_mobjects_as_static(self, *mobjects):
|
|
|
|
# Creates buffer and array objects holding each mobjects shader data
|
|
|
|
for mob in mobjects:
|
|
|
|
self.static_mobject_to_render_group_list[id(mob)] = [
|
|
|
|
self.get_render_group(sw, single_use=False)
|
|
|
|
for sw in mob.get_shader_wrapper_list()
|
|
|
|
]
|
|
|
|
|
|
|
|
def release_static_mobjects(self):
|
|
|
|
for rg_list in self.static_mobject_to_render_group_list.values():
|
|
|
|
for render_group in rg_list:
|
|
|
|
self.release_render_group(render_group)
|
|
|
|
self.static_mobject_to_render_group_list = {}
|
|
|
|
|
2020-02-11 19:51:19 -08:00
|
|
|
# Shaders
|
2020-02-04 15:27:21 -08:00
|
|
|
def init_shaders(self):
|
2020-02-13 10:40:21 -08:00
|
|
|
# Initialize with the null id going to None
|
2020-06-29 18:17:18 -07:00
|
|
|
self.id_to_shader_program = {"": None}
|
2020-02-04 15:27:21 -08:00
|
|
|
|
2020-06-29 18:17:18 -07:00
|
|
|
def get_shader_program(self, shader_wrapper):
|
|
|
|
sid = shader_wrapper.get_program_id()
|
|
|
|
if sid not in self.id_to_shader_program:
|
2020-06-14 19:01:04 -07:00
|
|
|
# Create shader program for the first time, then cache
|
2020-06-29 18:17:18 -07:00
|
|
|
# in the id_to_shader_program dictionary
|
|
|
|
program = self.ctx.program(**shader_wrapper.get_program_code())
|
|
|
|
vert_format = moderngl.detect_format(program, shader_wrapper.vert_attributes)
|
|
|
|
self.id_to_shader_program[sid] = (program, vert_format)
|
|
|
|
return self.id_to_shader_program[sid]
|
|
|
|
|
|
|
|
def set_shader_uniforms(self, shader, shader_wrapper):
|
|
|
|
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
|
2020-06-29 18:17:18 -07:00
|
|
|
for name, value in it.chain(shader_wrapper.uniforms.items(), self.perspective_uniforms.items()):
|
2020-06-26 19:29:34 -07:00
|
|
|
try:
|
2020-06-28 12:14:46 -07:00
|
|
|
shader[name].value = value
|
2020-06-26 19:29:34 -07:00
|
|
|
except KeyError:
|
|
|
|
pass
|
2018-04-06 13:58:59 -07:00
|
|
|
|
2020-06-26 19:29:34 -07:00
|
|
|
def refresh_perspective_uniforms(self):
|
2020-06-08 14:09:31 -07:00
|
|
|
pw, ph = self.get_pixel_shape()
|
2020-06-09 12:34:00 -07:00
|
|
|
fw, fh = self.frame.get_shape()
|
2020-06-14 19:01:04 -07:00
|
|
|
# TODO, this should probably be a mobject uniform, with
|
|
|
|
# the camera taking care of the conversion factor
|
2020-06-09 12:34:00 -07:00
|
|
|
anti_alias_width = self.anti_alias_width / (ph / fh)
|
2020-06-02 16:18:44 -07:00
|
|
|
transform = self.frame.get_inverse_camera_position_matrix()
|
|
|
|
light = self.light_source.get_location()
|
|
|
|
transformed_light = np.dot(transform, [*light, 1])[:3]
|
2020-06-26 19:29:34 -07:00
|
|
|
self.perspective_uniforms = {
|
2020-06-14 19:01:04 -07:00
|
|
|
'to_screen_space': tuple(transform.T.flatten()),
|
|
|
|
'frame_shape': self.frame.get_shape(),
|
|
|
|
'focal_distance': self.frame.get_focal_distance(),
|
|
|
|
'anti_alias_width': anti_alias_width,
|
|
|
|
'light_source_position': tuple(transformed_light),
|
|
|
|
}
|
2020-06-08 20:27:07 -07:00
|
|
|
|
2020-02-13 10:40:21 -08:00
|
|
|
def init_textures(self):
|
|
|
|
self.path_to_texture_id = {}
|
|
|
|
|
|
|
|
def get_texture_id(self, path):
|
|
|
|
if path not in self.path_to_texture_id:
|
|
|
|
# A way to increase tid's sequentially
|
|
|
|
tid = len(self.path_to_texture_id)
|
|
|
|
im = Image.open(path)
|
|
|
|
texture = self.ctx.texture(
|
|
|
|
size=im.size,
|
|
|
|
components=len(im.getbands()),
|
|
|
|
data=im.tobytes(),
|
|
|
|
)
|
|
|
|
texture.use(location=tid)
|
|
|
|
self.path_to_texture_id[path] = tid
|
|
|
|
return self.path_to_texture_id[path]
|
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):
|
2020-06-06 16:55:56 -07:00
|
|
|
CONFIG = {
|
2021-01-03 12:04:05 -08:00
|
|
|
"samples": 4,
|
2020-06-06 16:55:56 -07:00
|
|
|
}
|