mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
Very rudimentary 3dSurface type
This commit is contained in:
parent
e4419204cb
commit
4c33b99d39
12 changed files with 196 additions and 464 deletions
|
@ -166,11 +166,11 @@ class Camera(object):
|
||||||
self.fbo = self.get_fbo()
|
self.fbo = self.get_fbo()
|
||||||
self.fbo.use()
|
self.fbo.use()
|
||||||
|
|
||||||
|
flag = moderngl.BLEND
|
||||||
if self.apply_depth_test:
|
if self.apply_depth_test:
|
||||||
self.ctx.enable(moderngl.DEPTH_TEST | moderngl.BLEND)
|
flag |= moderngl.DEPTH_TEST
|
||||||
else:
|
self.ctx.enable(flag)
|
||||||
self.ctx.enable(moderngl.BLEND)
|
self.ctx.blend_func = ( # Needed?
|
||||||
self.ctx.blend_func = (
|
|
||||||
moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA,
|
moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA,
|
||||||
moderngl.ONE, moderngl.ONE
|
moderngl.ONE, moderngl.ONE
|
||||||
)
|
)
|
||||||
|
@ -379,3 +379,8 @@ class Camera(object):
|
||||||
texture.use(location=tid)
|
texture.use(location=tid)
|
||||||
self.path_to_texture_id[path] = tid
|
self.path_to_texture_id[path] = tid
|
||||||
return self.path_to_texture_id[path]
|
return self.path_to_texture_id[path]
|
||||||
|
|
||||||
|
|
||||||
|
class ThreeDCamera(Camera):
|
||||||
|
# Purely here to keep old scenes happy
|
||||||
|
pass
|
||||||
|
|
|
@ -1,232 +0,0 @@
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from manimlib.camera.camera import Camera
|
|
||||||
from manimlib.constants import *
|
|
||||||
from manimlib.mobject.three_d_utils import get_3d_vmob_end_corner
|
|
||||||
from manimlib.mobject.three_d_utils import get_3d_vmob_end_corner_unit_normal
|
|
||||||
from manimlib.mobject.three_d_utils import get_3d_vmob_start_corner
|
|
||||||
from manimlib.mobject.three_d_utils import get_3d_vmob_start_corner_unit_normal
|
|
||||||
from manimlib.mobject.types.point_cloud_mobject import Point
|
|
||||||
from manimlib.mobject.value_tracker import ValueTracker
|
|
||||||
from manimlib.utils.color import get_shaded_rgb
|
|
||||||
from manimlib.utils.simple_functions import clip_in_place
|
|
||||||
from manimlib.utils.space_ops import rotation_about_z
|
|
||||||
from manimlib.utils.space_ops import rotation_matrix
|
|
||||||
|
|
||||||
|
|
||||||
class ThreeDCamera(Camera):
|
|
||||||
CONFIG = {
|
|
||||||
"shading_factor": 0.2,
|
|
||||||
"distance": 20.0,
|
|
||||||
"default_distance": 5.0,
|
|
||||||
"phi": 0, # Angle off z axis
|
|
||||||
"theta": -90 * DEGREES, # Rotation about z axis
|
|
||||||
"gamma": 0, # Rotation about normal vector to camera
|
|
||||||
"light_source_start_point": 9 * DOWN + 7 * LEFT + 10 * OUT,
|
|
||||||
"frame_center": ORIGIN,
|
|
||||||
"should_apply_shading": True,
|
|
||||||
"exponential_projection": False,
|
|
||||||
"max_allowable_norm": 3 * FRAME_WIDTH,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
Camera.__init__(self, *args, **kwargs)
|
|
||||||
self.phi_tracker = ValueTracker(self.phi)
|
|
||||||
self.theta_tracker = ValueTracker(self.theta)
|
|
||||||
self.distance_tracker = ValueTracker(self.distance)
|
|
||||||
self.gamma_tracker = ValueTracker(self.gamma)
|
|
||||||
self.light_source = Point(self.light_source_start_point)
|
|
||||||
self.frame_center = Point(self.frame_center)
|
|
||||||
self.fixed_orientation_mobjects = dict()
|
|
||||||
self.fixed_in_frame_mobjects = set()
|
|
||||||
self.reset_rotation_matrix()
|
|
||||||
|
|
||||||
def capture(self, *mobjects, **kwargs):
|
|
||||||
self.reset_rotation_matrix()
|
|
||||||
Camera.capture(self, *mobjects, **kwargs)
|
|
||||||
|
|
||||||
def get_value_trackers(self):
|
|
||||||
return [
|
|
||||||
self.phi_tracker,
|
|
||||||
self.theta_tracker,
|
|
||||||
self.distance_tracker,
|
|
||||||
self.gamma_tracker,
|
|
||||||
]
|
|
||||||
|
|
||||||
def modified_rgbas(self, vmobject, rgbas):
|
|
||||||
if not self.should_apply_shading:
|
|
||||||
return rgbas
|
|
||||||
if vmobject.shade_in_3d and (vmobject.get_num_points() > 0):
|
|
||||||
light_source_point = self.light_source.points[0]
|
|
||||||
if len(rgbas) < 2:
|
|
||||||
shaded_rgbas = rgbas.repeat(2, axis=0)
|
|
||||||
else:
|
|
||||||
shaded_rgbas = np.array(rgbas[:2])
|
|
||||||
shaded_rgbas[0, :3] = get_shaded_rgb(
|
|
||||||
shaded_rgbas[0, :3],
|
|
||||||
get_3d_vmob_start_corner(vmobject),
|
|
||||||
get_3d_vmob_start_corner_unit_normal(vmobject),
|
|
||||||
light_source_point,
|
|
||||||
)
|
|
||||||
shaded_rgbas[1, :3] = get_shaded_rgb(
|
|
||||||
shaded_rgbas[1, :3],
|
|
||||||
get_3d_vmob_end_corner(vmobject),
|
|
||||||
get_3d_vmob_end_corner_unit_normal(vmobject),
|
|
||||||
light_source_point,
|
|
||||||
)
|
|
||||||
return shaded_rgbas
|
|
||||||
return rgbas
|
|
||||||
|
|
||||||
def get_stroke_rgbas(self, vmobject, background=False):
|
|
||||||
return self.modified_rgbas(
|
|
||||||
vmobject, vmobject.get_stroke_rgbas(background)
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_fill_rgbas(self, vmobject):
|
|
||||||
return self.modified_rgbas(
|
|
||||||
vmobject, vmobject.get_fill_rgbas()
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_mobjects_to_display(self, *args, **kwargs):
|
|
||||||
mobjects = Camera.get_mobjects_to_display(
|
|
||||||
self, *args, **kwargs
|
|
||||||
)
|
|
||||||
rot_matrix = self.get_rotation_matrix()
|
|
||||||
|
|
||||||
def z_key(mob):
|
|
||||||
if not (hasattr(mob, "shade_in_3d") and mob.shade_in_3d):
|
|
||||||
return np.inf
|
|
||||||
# Assign a number to a three dimensional mobjects
|
|
||||||
# based on how close it is to the camera
|
|
||||||
return np.dot(
|
|
||||||
mob.get_z_index_reference_point(),
|
|
||||||
rot_matrix.T
|
|
||||||
)[2]
|
|
||||||
return sorted(mobjects, key=z_key)
|
|
||||||
|
|
||||||
def get_phi(self):
|
|
||||||
return self.phi_tracker.get_value()
|
|
||||||
|
|
||||||
def get_theta(self):
|
|
||||||
return self.theta_tracker.get_value()
|
|
||||||
|
|
||||||
def get_distance(self):
|
|
||||||
return self.distance_tracker.get_value()
|
|
||||||
|
|
||||||
def get_gamma(self):
|
|
||||||
return self.gamma_tracker.get_value()
|
|
||||||
|
|
||||||
def get_frame_center(self):
|
|
||||||
return self.frame_center.points[0]
|
|
||||||
|
|
||||||
def set_phi(self, value):
|
|
||||||
self.phi_tracker.set_value(value)
|
|
||||||
|
|
||||||
def set_theta(self, value):
|
|
||||||
self.theta_tracker.set_value(value)
|
|
||||||
|
|
||||||
def set_distance(self, value):
|
|
||||||
self.distance_tracker.set_value(value)
|
|
||||||
|
|
||||||
def set_gamma(self, value):
|
|
||||||
self.gamma_tracker.set_value(value)
|
|
||||||
|
|
||||||
def set_frame_center(self, point):
|
|
||||||
self.frame_center.move_to(point)
|
|
||||||
|
|
||||||
def reset_rotation_matrix(self):
|
|
||||||
self.rotation_matrix = self.generate_rotation_matrix()
|
|
||||||
|
|
||||||
def get_rotation_matrix(self):
|
|
||||||
return self.rotation_matrix
|
|
||||||
|
|
||||||
def generate_rotation_matrix(self):
|
|
||||||
phi = self.get_phi()
|
|
||||||
theta = self.get_theta()
|
|
||||||
gamma = self.get_gamma()
|
|
||||||
matrices = [
|
|
||||||
rotation_about_z(-theta - 90 * DEGREES),
|
|
||||||
rotation_matrix(-phi, RIGHT),
|
|
||||||
rotation_about_z(gamma),
|
|
||||||
]
|
|
||||||
result = np.identity(3)
|
|
||||||
for matrix in matrices:
|
|
||||||
result = np.dot(matrix, result)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def project_points(self, points):
|
|
||||||
frame_center = self.get_frame_center()
|
|
||||||
distance = self.get_distance()
|
|
||||||
rot_matrix = self.get_rotation_matrix()
|
|
||||||
|
|
||||||
points = points - frame_center
|
|
||||||
points = np.dot(points, rot_matrix.T)
|
|
||||||
zs = points[:, 2]
|
|
||||||
for i in 0, 1:
|
|
||||||
if self.exponential_projection:
|
|
||||||
# Proper projedtion would involve multiplying
|
|
||||||
# x and y by d / (d-z). But for points with high
|
|
||||||
# z value that causes weird artifacts, and applying
|
|
||||||
# the exponential helps smooth it out.
|
|
||||||
factor = np.exp(zs / distance)
|
|
||||||
lt0 = zs < 0
|
|
||||||
factor[lt0] = (distance / (distance - zs[lt0]))
|
|
||||||
else:
|
|
||||||
factor = (distance / (distance - zs))
|
|
||||||
factor[(distance - zs) < 0] = 10**6
|
|
||||||
# clip_in_place(factor, 0, 10**6)
|
|
||||||
points[:, i] *= factor
|
|
||||||
points = points + frame_center
|
|
||||||
return points
|
|
||||||
|
|
||||||
def project_point(self, point):
|
|
||||||
return self.project_points(point.reshape((1, 3)))[0, :]
|
|
||||||
|
|
||||||
def transform_points_pre_display(self, mobject, points):
|
|
||||||
points = super().transform_points_pre_display(mobject, points)
|
|
||||||
fixed_orientation = mobject in self.fixed_orientation_mobjects
|
|
||||||
fixed_in_frame = mobject in self.fixed_in_frame_mobjects
|
|
||||||
|
|
||||||
if fixed_in_frame:
|
|
||||||
return points
|
|
||||||
if fixed_orientation:
|
|
||||||
center_func = self.fixed_orientation_mobjects[mobject]
|
|
||||||
center = center_func()
|
|
||||||
new_center = self.project_point(center)
|
|
||||||
return points + (new_center - center)
|
|
||||||
else:
|
|
||||||
return self.project_points(points)
|
|
||||||
|
|
||||||
def add_fixed_orientation_mobjects(
|
|
||||||
self, *mobjects,
|
|
||||||
use_static_center_func=False,
|
|
||||||
center_func=None):
|
|
||||||
# This prevents the computation of mobject.get_center
|
|
||||||
# every single time a projetion happens
|
|
||||||
def get_static_center_func(mobject):
|
|
||||||
point = mobject.get_center()
|
|
||||||
return (lambda: point)
|
|
||||||
|
|
||||||
for mobject in mobjects:
|
|
||||||
if center_func:
|
|
||||||
func = center_func
|
|
||||||
elif use_static_center_func:
|
|
||||||
func = get_static_center_func(mobject)
|
|
||||||
else:
|
|
||||||
func = mobject.get_center
|
|
||||||
for submob in mobject.get_family():
|
|
||||||
self.fixed_orientation_mobjects[submob] = func
|
|
||||||
|
|
||||||
def add_fixed_in_frame_mobjects(self, *mobjects):
|
|
||||||
for mobject in self.extract_mobject_family_members(mobjects):
|
|
||||||
self.fixed_in_frame_mobjects.add(mobject)
|
|
||||||
|
|
||||||
def remove_fixed_orientation_mobjects(self, *mobjects):
|
|
||||||
for mobject in self.extract_mobject_family_members(mobjects):
|
|
||||||
if mobject in self.fixed_orientation_mobjects:
|
|
||||||
self.fixed_orientation_mobjects.remove(mobject)
|
|
||||||
|
|
||||||
def remove_fixed_in_frame_mobjects(self, *mobjects):
|
|
||||||
for mobject in self.extract_mobject_family_members(mobjects):
|
|
||||||
if mobject in self.fixed_in_frame_mobjects:
|
|
||||||
self.fixed_in_frame_mobjects.remove(mobject)
|
|
|
@ -30,9 +30,6 @@ from manimlib.animation.transform import *
|
||||||
from manimlib.animation.update import *
|
from manimlib.animation.update import *
|
||||||
|
|
||||||
from manimlib.camera.camera import *
|
from manimlib.camera.camera import *
|
||||||
from manimlib.camera.mapping_camera import *
|
|
||||||
from manimlib.camera.moving_camera import *
|
|
||||||
from manimlib.camera.three_d_camera import *
|
|
||||||
|
|
||||||
from manimlib.mobject.coordinate_systems import *
|
from manimlib.mobject.coordinate_systems import *
|
||||||
from manimlib.mobject.changing import *
|
from manimlib.mobject.changing import *
|
||||||
|
@ -50,10 +47,10 @@ from manimlib.mobject.svg.drawings import *
|
||||||
from manimlib.mobject.svg.svg_mobject import *
|
from manimlib.mobject.svg.svg_mobject import *
|
||||||
from manimlib.mobject.svg.tex_mobject import *
|
from manimlib.mobject.svg.tex_mobject import *
|
||||||
from manimlib.mobject.svg.text_mobject import *
|
from manimlib.mobject.svg.text_mobject import *
|
||||||
from manimlib.mobject.three_d_utils import *
|
|
||||||
from manimlib.mobject.three_dimensions import *
|
from manimlib.mobject.three_dimensions import *
|
||||||
from manimlib.mobject.types.image_mobject import *
|
from manimlib.mobject.types.image_mobject import *
|
||||||
from manimlib.mobject.types.point_cloud_mobject import *
|
from manimlib.mobject.types.point_cloud_mobject import *
|
||||||
|
from manimlib.mobject.types.surface_mobject import *
|
||||||
from manimlib.mobject.types.vectorized_mobject import *
|
from manimlib.mobject.types.vectorized_mobject import *
|
||||||
from manimlib.mobject.mobject_update_utils import *
|
from manimlib.mobject.mobject_update_utils import *
|
||||||
from manimlib.mobject.value_tracker import *
|
from manimlib.mobject.value_tracker import *
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from manimlib.constants import ORIGIN
|
|
||||||
from manimlib.utils.space_ops import get_unit_normal
|
|
||||||
|
|
||||||
|
|
||||||
# TODO, these ideas should be deprecated
|
|
||||||
|
|
||||||
def get_3d_vmob_gradient_start_and_end_points(vmob):
|
|
||||||
return (
|
|
||||||
get_3d_vmob_start_corner(vmob),
|
|
||||||
get_3d_vmob_end_corner(vmob),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_start_corner_index(vmob):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_end_corner_index(vmob):
|
|
||||||
return ((len(vmob.points) - 1) // 6) * 3
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_start_corner(vmob):
|
|
||||||
if vmob.get_num_points() == 0:
|
|
||||||
return np.array(ORIGIN)
|
|
||||||
return vmob.points[get_3d_vmob_start_corner_index(vmob)]
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_end_corner(vmob):
|
|
||||||
if vmob.get_num_points() == 0:
|
|
||||||
return np.array(ORIGIN)
|
|
||||||
return vmob.points[get_3d_vmob_end_corner_index(vmob)]
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_unit_normal(vmob, point_index):
|
|
||||||
n_points = vmob.get_num_points()
|
|
||||||
if vmob.get_num_points() == 0:
|
|
||||||
return np.array(ORIGIN)
|
|
||||||
i = point_index
|
|
||||||
im1 = i - 1 if i > 0 else (n_points - 2)
|
|
||||||
ip1 = i + 1 if i < (n_points - 1) else 1
|
|
||||||
return get_unit_normal(
|
|
||||||
vmob.points[ip1] - vmob.points[i],
|
|
||||||
vmob.points[im1] - vmob.points[i],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_start_corner_unit_normal(vmob):
|
|
||||||
return get_3d_vmob_unit_normal(
|
|
||||||
vmob, get_3d_vmob_start_corner_index(vmob)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_end_corner_unit_normal(vmob):
|
|
||||||
return get_3d_vmob_unit_normal(
|
|
||||||
vmob, get_3d_vmob_end_corner_index(vmob)
|
|
||||||
)
|
|
|
@ -1,64 +0,0 @@
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from manimlib.constants import ORIGIN
|
|
||||||
from manimlib.constants import UP
|
|
||||||
from manimlib.utils.space_ops import get_norm
|
|
||||||
from manimlib.utils.space_ops import get_unit_normal
|
|
||||||
|
|
||||||
|
|
||||||
# TODO, these ideas should be deprecated
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_gradient_start_and_end_points(vmob):
|
|
||||||
return (
|
|
||||||
get_3d_vmob_start_corner(vmob),
|
|
||||||
get_3d_vmob_end_corner(vmob),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_start_corner_index(vmob):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_end_corner_index(vmob):
|
|
||||||
return ((len(vmob.points) - 1) // 6) * 3
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_start_corner(vmob):
|
|
||||||
if vmob.get_num_points() == 0:
|
|
||||||
return np.array(ORIGIN)
|
|
||||||
return vmob.points[get_3d_vmob_start_corner_index(vmob)]
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_end_corner(vmob):
|
|
||||||
if vmob.get_num_points() == 0:
|
|
||||||
return np.array(ORIGIN)
|
|
||||||
return vmob.points[get_3d_vmob_end_corner_index(vmob)]
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_unit_normal(vmob, point_index):
|
|
||||||
n_points = vmob.get_num_points()
|
|
||||||
if len(vmob.get_anchors()) <= 2:
|
|
||||||
return np.array(UP)
|
|
||||||
i = point_index
|
|
||||||
im3 = i - 3 if i > 2 else (n_points - 4)
|
|
||||||
ip3 = i + 3 if i < (n_points - 3) else 3
|
|
||||||
unit_normal = get_unit_normal(
|
|
||||||
vmob.points[ip3] - vmob.points[i],
|
|
||||||
vmob.points[im3] - vmob.points[i],
|
|
||||||
)
|
|
||||||
if get_norm(unit_normal) == 0:
|
|
||||||
return np.array(UP)
|
|
||||||
return unit_normal
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_start_corner_unit_normal(vmob):
|
|
||||||
return get_3d_vmob_unit_normal(
|
|
||||||
vmob, get_3d_vmob_start_corner_index(vmob)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_3d_vmob_end_corner_unit_normal(vmob):
|
|
||||||
return get_3d_vmob_unit_normal(
|
|
||||||
vmob, get_3d_vmob_end_corner_index(vmob)
|
|
||||||
)
|
|
|
@ -1,29 +1,14 @@
|
||||||
from manimlib.constants import *
|
from manimlib.constants import *
|
||||||
from manimlib.mobject.geometry import Square
|
from manimlib.mobject.geometry import Square
|
||||||
|
from manimlib.mobject.types.surface_mobject import SurfaceMobject
|
||||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
|
||||||
from manimlib.utils.iterables import listify
|
|
||||||
from manimlib.utils.space_ops import z_to_vector
|
|
||||||
|
|
||||||
##############
|
|
||||||
|
|
||||||
|
|
||||||
# TODO, replace these with a special 3d type, not VMobject
|
class ParametricSurface(SurfaceMobject):
|
||||||
|
|
||||||
|
|
||||||
class ThreeDVMobject(VMobject):
|
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"shade_in_3d": True,
|
"u_range": (0, 1),
|
||||||
}
|
"v_range": (0, 1),
|
||||||
|
"resolution": (32, 32),
|
||||||
|
|
||||||
class ParametricSurface(VGroup):
|
|
||||||
CONFIG = {
|
|
||||||
"u_min": 0,
|
|
||||||
"u_max": 1,
|
|
||||||
"v_min": 0,
|
|
||||||
"v_max": 1,
|
|
||||||
"resolution": 32,
|
|
||||||
"surface_piece_config": {},
|
"surface_piece_config": {},
|
||||||
"fill_color": BLUE_D,
|
"fill_color": BLUE_D,
|
||||||
"fill_opacity": 1.0,
|
"fill_opacity": 1.0,
|
||||||
|
@ -34,93 +19,72 @@ class ParametricSurface(VGroup):
|
||||||
"pre_function_handle_to_anchor_scale_factor": 0.00001,
|
"pre_function_handle_to_anchor_scale_factor": 0.00001,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, func, **kwargs):
|
def __init__(self, function=None, **kwargs):
|
||||||
VGroup.__init__(self, **kwargs)
|
if function is None:
|
||||||
self.func = func
|
self.uv_func = self.func
|
||||||
self.setup_in_uv_space()
|
|
||||||
self.apply_function(lambda p: func(p[0], p[1]))
|
|
||||||
if self.should_make_jagged:
|
|
||||||
self.make_jagged()
|
|
||||||
|
|
||||||
def get_u_values_and_v_values(self):
|
|
||||||
res = listify(self.resolution)
|
|
||||||
if len(res) == 1:
|
|
||||||
u_res = v_res = res[0]
|
|
||||||
else:
|
else:
|
||||||
u_res, v_res = res
|
self.uv_func = function
|
||||||
u_min = self.u_min
|
super().__init__(**kwargs)
|
||||||
u_max = self.u_max
|
|
||||||
v_min = self.v_min
|
|
||||||
v_max = self.v_max
|
|
||||||
|
|
||||||
u_values = np.linspace(u_min, u_max, u_res + 1)
|
def init_points(self):
|
||||||
v_values = np.linspace(v_min, v_max, v_res + 1)
|
epsilon = 1e-6 # For differentials
|
||||||
|
nu, nv = self.resolution
|
||||||
return u_values, v_values
|
u_range = np.linspace(*self.u_range, nu + 1)
|
||||||
|
v_range = np.linspace(*self.v_range, nv + 1)
|
||||||
def setup_in_uv_space(self):
|
# List of three grids, [Pure uv values, those nudged by du, those nudged by dv]
|
||||||
u_values, v_values = self.get_u_values_and_v_values()
|
uv_grids = [
|
||||||
faces = VGroup()
|
np.array([[[u, v] for v in v_range] for u in u_range])
|
||||||
for i in range(len(u_values) - 1):
|
for (du, dv) in [(0, 0), (epsilon, 0), (0, epsilon)]
|
||||||
for j in range(len(v_values) - 1):
|
]
|
||||||
u1, u2 = u_values[i:i + 2]
|
point_grid, points_nudged_du, points_nudged_dv = [
|
||||||
v1, v2 = v_values[j:j + 2]
|
np.apply_along_axis(lambda p: self.uv_func(*p), 2, uv_grid)
|
||||||
face = ThreeDVMobject()
|
for uv_grid in uv_grids
|
||||||
face.set_points_as_corners([
|
]
|
||||||
[u1, v1, 0],
|
normal_grid = np.cross(
|
||||||
[u2, v1, 0],
|
(points_nudged_du - point_grid) / epsilon,
|
||||||
[u2, v2, 0],
|
(points_nudged_dv - point_grid) / epsilon,
|
||||||
[u1, v2, 0],
|
|
||||||
[u1, v1, 0],
|
|
||||||
])
|
|
||||||
faces.add(face)
|
|
||||||
face.u_index = i
|
|
||||||
face.v_index = j
|
|
||||||
face.u1 = u1
|
|
||||||
face.u2 = u2
|
|
||||||
face.v1 = v1
|
|
||||||
face.v2 = v2
|
|
||||||
faces.set_fill(
|
|
||||||
color=self.fill_color,
|
|
||||||
opacity=self.fill_opacity
|
|
||||||
)
|
)
|
||||||
faces.set_stroke(
|
|
||||||
color=self.stroke_color,
|
self.set_points(
|
||||||
width=self.stroke_width,
|
self.get_triangle_ready_array_from_grid(point_grid),
|
||||||
opacity=self.stroke_opacity,
|
self.get_triangle_ready_array_from_grid(normal_grid),
|
||||||
)
|
)
|
||||||
self.add(*faces)
|
|
||||||
if self.checkerboard_colors:
|
|
||||||
self.set_fill_by_checkerboard(*self.checkerboard_colors)
|
|
||||||
|
|
||||||
def set_fill_by_checkerboard(self, *colors, opacity=None):
|
# self.points = point_grid[indices]
|
||||||
n_colors = len(colors)
|
|
||||||
for face in self:
|
def get_triangle_ready_array_from_grid(self, grid):
|
||||||
c_index = (face.u_index + face.v_index) % n_colors
|
# Given a grid, say of points or normals, this returns an Nx3 array
|
||||||
face.set_fill(colors[c_index], opacity=opacity)
|
# whose rows are elements from this grid in such such a way that successive
|
||||||
|
# triplets of points form triangles covering the grid.
|
||||||
|
nu = grid.shape[0] - 1
|
||||||
|
nv = grid.shape[1] - 1
|
||||||
|
dim = grid.shape[2]
|
||||||
|
arr = np.zeros((nu * nv * 6, dim))
|
||||||
|
# To match the triangles covering this surface
|
||||||
|
arr[0::6] = grid[:-1, :-1].reshape((nu * nv, 3)) # Top left
|
||||||
|
arr[1::6] = grid[+1:, :-1].reshape((nu * nv, 3)) # Bottom left
|
||||||
|
arr[2::6] = grid[:-1, +1:].reshape((nu * nv, 3)) # Top right
|
||||||
|
arr[3::6] = grid[:-1, +1:].reshape((nu * nv, 3)) # Top right
|
||||||
|
arr[4::6] = grid[+1:, :-1].reshape((nu * nv, 3)) # Bottom left
|
||||||
|
arr[5::6] = grid[+1:, +1:].reshape((nu * nv, 3)) # Bottom right
|
||||||
|
return arr
|
||||||
|
|
||||||
|
def func(self, u, v):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Specific shapes
|
# Sphere, cylinder, cube, prism
|
||||||
|
|
||||||
|
|
||||||
class Sphere(ParametricSurface):
|
class Sphere(ParametricSurface):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"resolution": (12, 24),
|
"resolution": (12, 24),
|
||||||
"radius": 1,
|
"radius": 1,
|
||||||
"u_min": 0.001,
|
"u_range": (0, PI),
|
||||||
"u_max": PI - 0.001,
|
"v_range": (0, TAU),
|
||||||
"v_min": 0,
|
|
||||||
"v_max": TAU,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
ParametricSurface.__init__(
|
|
||||||
self, self.func, **kwargs
|
|
||||||
)
|
|
||||||
self.scale(self.radius)
|
|
||||||
|
|
||||||
def func(self, u, v):
|
def func(self, u, v):
|
||||||
return np.array([
|
return self.radius * np.array([
|
||||||
np.cos(v) * np.sin(u),
|
np.cos(v) * np.sin(u),
|
||||||
np.sin(v) * np.sin(u),
|
np.sin(v) * np.sin(u),
|
||||||
np.cos(u)
|
np.cos(u)
|
||||||
|
|
76
manimlib/mobject/types/surface_mobject.py
Normal file
76
manimlib/mobject/types/surface_mobject.py
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import numpy as np
|
||||||
|
import moderngl
|
||||||
|
|
||||||
|
# from PIL import Image
|
||||||
|
|
||||||
|
from manimlib.constants import *
|
||||||
|
from manimlib.mobject.mobject import Mobject
|
||||||
|
from manimlib.utils.color import color_to_rgba
|
||||||
|
|
||||||
|
|
||||||
|
class SurfaceMobject(Mobject):
|
||||||
|
CONFIG = {
|
||||||
|
"color": GREY,
|
||||||
|
"opacity": 1,
|
||||||
|
"gloss": 1.0,
|
||||||
|
"render_primative": moderngl.TRIANGLES,
|
||||||
|
# "render_primative": moderngl.TRIANGLE_STRIP,
|
||||||
|
"vert_shader_file": "surface_vert.glsl",
|
||||||
|
"frag_shader_file": "surface_frag.glsl",
|
||||||
|
"shader_dtype": [
|
||||||
|
('point', np.float32, (3,)),
|
||||||
|
('normal', np.float32, (3,)),
|
||||||
|
('color', np.float32, (4,)),
|
||||||
|
('gloss', np.float32, (1,)),
|
||||||
|
# ('im_coords', np.float32, (2,)),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
def init_points(self):
|
||||||
|
self.points = np.zeros((0, self.dim))
|
||||||
|
self.normals = np.zeros((0, self.dim))
|
||||||
|
|
||||||
|
def init_colors(self):
|
||||||
|
self.set_color(self.color, self.opacity)
|
||||||
|
|
||||||
|
def set_points(self, points, normals=None):
|
||||||
|
self.points = np.array(points)
|
||||||
|
if normals is None:
|
||||||
|
v01 = points[1:-1] - points[:-2]
|
||||||
|
v02 = points[2:] - points[:-2]
|
||||||
|
crosses = np.cross(v01, v02)
|
||||||
|
crosses[1::2] *= -1 # Because of reversed orientation of every other triangle in the strip
|
||||||
|
self.normals = np.vstack([
|
||||||
|
crosses,
|
||||||
|
crosses[-1:].repeat(2, 0) # Repeat last entry twice
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
self.normals = np.array(normals)
|
||||||
|
|
||||||
|
def set_color(self, color, opacity):
|
||||||
|
# TODO, allow for multiple colors
|
||||||
|
rgba = color_to_rgba(color, opacity)
|
||||||
|
self.rgbas = np.array([rgba])
|
||||||
|
|
||||||
|
def apply_function(self, function, **kwargs):
|
||||||
|
# Apply it to infinitesimal neighbors to preserve normals
|
||||||
|
pass
|
||||||
|
|
||||||
|
def rotate(self, axis, angle, **kwargs):
|
||||||
|
# Account for normals
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stretch(self, factor, dim, **kwargs):
|
||||||
|
# Account for normals
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_shader_data(self):
|
||||||
|
data = self.get_blank_shader_data_array(len(self.points))
|
||||||
|
data["point"] = self.points
|
||||||
|
data["normal"] = self.normals
|
||||||
|
data["color"] = self.rgbas
|
||||||
|
data["gloss"] = self.gloss
|
||||||
|
return data
|
|
@ -8,7 +8,6 @@ from functools import reduce
|
||||||
from manimlib.constants import *
|
from manimlib.constants import *
|
||||||
from manimlib.mobject.mobject import Mobject
|
from manimlib.mobject.mobject import Mobject
|
||||||
from manimlib.mobject.mobject import Point
|
from manimlib.mobject.mobject import Point
|
||||||
from manimlib.mobject.three_d_utils import get_3d_vmob_gradient_start_and_end_points
|
|
||||||
from manimlib.utils.bezier import bezier
|
from manimlib.utils.bezier import bezier
|
||||||
from manimlib.utils.bezier import get_smooth_handle_points
|
from manimlib.utils.bezier import get_smooth_handle_points
|
||||||
from manimlib.utils.bezier import get_quadratic_approximation_of_cubic
|
from manimlib.utils.bezier import get_quadratic_approximation_of_cubic
|
||||||
|
@ -63,7 +62,7 @@ class VMobject(Mobject):
|
||||||
# Could also be Bevel, Miter, Round
|
# Could also be Bevel, Miter, Round
|
||||||
"joint_type": "auto",
|
"joint_type": "auto",
|
||||||
# Positive gloss up to 1 makes it reflect the light.
|
# Positive gloss up to 1 makes it reflect the light.
|
||||||
"gloss": 0.0,
|
"gloss": 0.2,
|
||||||
"render_primative": moderngl.TRIANGLES,
|
"render_primative": moderngl.TRIANGLES,
|
||||||
"triangulation_locked": False,
|
"triangulation_locked": False,
|
||||||
"fill_dtype": [
|
"fill_dtype": [
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from manimlib.animation.transform import ApplyMethod
|
from manimlib.animation.transform import ApplyMethod
|
||||||
from manimlib.camera.three_d_camera import ThreeDCamera
|
|
||||||
from manimlib.constants import DEGREES
|
from manimlib.constants import DEGREES
|
||||||
from manimlib.constants import PRODUCTION_QUALITY_CAMERA_CONFIG
|
from manimlib.constants import PRODUCTION_QUALITY_CAMERA_CONFIG
|
||||||
from manimlib.mobject.coordinate_systems import ThreeDAxes
|
from manimlib.mobject.coordinate_systems import ThreeDAxes
|
||||||
|
@ -12,9 +11,10 @@ from manimlib.utils.config_ops import digest_config
|
||||||
from manimlib.utils.config_ops import merge_dicts_recursively
|
from manimlib.utils.config_ops import merge_dicts_recursively
|
||||||
|
|
||||||
|
|
||||||
|
# TODO, these seem deprecated.
|
||||||
|
|
||||||
class ThreeDScene(Scene):
|
class ThreeDScene(Scene):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"camera_class": ThreeDCamera,
|
|
||||||
"ambient_camera_rotation": None,
|
"ambient_camera_rotation": None,
|
||||||
"default_angled_camera_orientation_kwargs": {
|
"default_angled_camera_orientation_kwargs": {
|
||||||
"phi": 70 * DEGREES,
|
"phi": 70 * DEGREES,
|
||||||
|
|
|
@ -7,15 +7,17 @@ vec4 add_light(vec4 raw_color, vec3 point, vec3 unit_normal, vec3 light_coords,
|
||||||
unit_normal *= -1;
|
unit_normal *= -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
float camera_distance = 6;
|
float camera_distance = 6; // TODO, read this in as a uniform?
|
||||||
// Assume everything has already been rotated such that camera is in the z-direction
|
// Assume everything has already been rotated such that camera is in the z-direction
|
||||||
vec3 to_camera = vec3(0, 0, camera_distance) - point;
|
vec3 to_camera = vec3(0, 0, camera_distance) - point;
|
||||||
vec3 to_light = light_coords - point;
|
vec3 to_light = light_coords - point;
|
||||||
vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal);
|
vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal);
|
||||||
float dot_prod = dot(normalize(light_reflection), normalize(to_camera));
|
float dot_prod = dot(normalize(light_reflection), normalize(to_camera));
|
||||||
float shine = gloss * exp(-3 * pow(1 - dot_prod, 2));
|
// float shine = gloss * exp(-3 * pow(1 - dot_prod, 2));
|
||||||
|
float shine = 2 * gloss * exp(-1 * pow(1 - dot_prod, 2));
|
||||||
|
float dp2 = dot(normalize(to_light), unit_normal);
|
||||||
return vec4(
|
return vec4(
|
||||||
mix(raw_color.rgb, vec3(1.0), shine),
|
mix(0.5, 1.0, max(dp2, 0)) * mix(raw_color.rgb, vec3(1.0), shine),
|
||||||
raw_color.a
|
raw_color.a
|
||||||
);
|
);
|
||||||
}
|
}
|
12
manimlib/shaders/surface_frag.glsl
Normal file
12
manimlib/shaders/surface_frag.glsl
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
// uniform sampler2D Texture;
|
||||||
|
|
||||||
|
// in vec2 v_im_coords;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
out vec4 frag_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
frag_color = v_color;
|
||||||
|
}
|
31
manimlib/shaders/surface_vert.glsl
Normal file
31
manimlib/shaders/surface_vert.glsl
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
uniform float aspect_ratio;
|
||||||
|
uniform float anti_alias_width;
|
||||||
|
uniform mat4 to_screen_space;
|
||||||
|
uniform float focal_distance;
|
||||||
|
uniform vec3 light_source_position;
|
||||||
|
|
||||||
|
// uniform sampler2D Texture;
|
||||||
|
|
||||||
|
in vec3 point;
|
||||||
|
in vec3 normal;
|
||||||
|
// in vec2 im_coords;
|
||||||
|
in vec4 color;
|
||||||
|
in float gloss;
|
||||||
|
|
||||||
|
// out vec2 v_im_coords;
|
||||||
|
out vec4 v_color;
|
||||||
|
|
||||||
|
// Analog of import for manim only
|
||||||
|
#INSERT position_point_into_frame.glsl
|
||||||
|
#INSERT get_gl_Position.glsl
|
||||||
|
#INSERT add_light.glsl
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
vec3 xyz_coords = position_point_into_frame(point);
|
||||||
|
vec3 unit_normal = normalize(position_point_into_frame(normal));
|
||||||
|
// v_im_coords = im_coords;
|
||||||
|
v_color = add_light(color, xyz_coords, unit_normal, light_source_position, gloss);
|
||||||
|
gl_Position = get_gl_Position(xyz_coords);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue