mirror of
https://github.com/ManimCommunity/manim.git
synced 2026-06-22 10:01:47 +00:00
[EXPERIMENTAL] Mobject shader cleanup + Scene changes + Undo orientation input in vertex shader + Transparency Fix (#3474)
* Updated Scene.remove * Updated Scene.replace * When shader file is missing, log its absolute path * Undo adding 'orientation' in vert.glsl, revert to 'unit_normal' in OpenGLVMobject * Added removed docstring to Scene.replace * Removed and rewrote multiple OpenGL(V)Mobject methods * Fixed OpenGLVMobject.get_style() * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixing transparency and generator bug * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: MrDiver <mrdiverlp@gmail.com>
This commit is contained in:
parent
eb1ff92ccd
commit
9b98fe5af2
11 changed files with 271 additions and 376 deletions
162
example_scenes/new_test_new.py
Normal file
162
example_scenes/new_test_new.py
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import time
|
||||
|
||||
import numpy as np
|
||||
import pyglet
|
||||
from PIL import Image
|
||||
from pyglet import shapes
|
||||
from pyglet.gl import Config
|
||||
from pyglet.window import Window
|
||||
|
||||
import manim.utils.color.manim_colors as col
|
||||
from manim._config import config, tempconfig
|
||||
from manim.animation.creation import Create, DrawBorderThenFill, Write
|
||||
from manim.animation.fading import FadeIn
|
||||
from manim.animation.transform import Transform
|
||||
from manim.camera.camera import OpenGLCameraFrame
|
||||
from manim.constants import LEFT, OUT, RIGHT, UP
|
||||
from manim.mobject.geometry.arc import Circle
|
||||
from manim.mobject.geometry.polygram import Square
|
||||
from manim.mobject.logo import ManimBanner
|
||||
from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject
|
||||
from manim.mobject.text.numbers import DecimalNumber
|
||||
from manim.mobject.text.text_mobject import Text
|
||||
from manim.renderer.opengl_renderer import OpenGLRenderer
|
||||
|
||||
|
||||
def progress_through_animations(animations):
|
||||
dt = t - last_t
|
||||
last_t = t
|
||||
for animation in animations:
|
||||
animation.update_mobjects(dt)
|
||||
alpha = t / animation.run_time
|
||||
animation.interpolate(alpha)
|
||||
self.update_frame(dt)
|
||||
self.emit_frame()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with tempconfig({"renderer": "opengl"}):
|
||||
win = Window(
|
||||
width=1920,
|
||||
height=1080,
|
||||
vsync=True,
|
||||
config=Config(double_buffer=True, samples=0),
|
||||
)
|
||||
renderer = OpenGLRenderer(1920, 1080, background_color=col.GRAY)
|
||||
# vm = OpenGLVMobject([col.RED, col.GREEN])
|
||||
vm = (
|
||||
Circle(
|
||||
radius=1,
|
||||
stroke_color=col.YELLOW,
|
||||
)
|
||||
.shift(3 * RIGHT + OUT)
|
||||
.set_opacity(0.6)
|
||||
)
|
||||
vm2 = Square(stroke_color=col.GREEN, fill_opacity=0, stroke_opacity=1).move_to(
|
||||
(0, 0, -0.5)
|
||||
)
|
||||
vm3 = ManimBanner().set_opacity(0.6)
|
||||
vm4 = (
|
||||
Circle(0.5, col.GREEN)
|
||||
.set_opacity(0.6)
|
||||
.shift(OUT)
|
||||
.set_fill(col.BLUE, opacity=0.2)
|
||||
)
|
||||
# vm.set_points_as_corners([[-1920/2, 0, 0], [1920/2, 0, 0], [0, 1080/2, 0]])
|
||||
# print(vm.color)
|
||||
# print(vm.fill_color)
|
||||
# print(vm.stroke_color)
|
||||
|
||||
clock_mobject = DecimalNumber(0.0).shift(4 * LEFT + 2.5 * UP)
|
||||
clock_mobject.fix_in_frame()
|
||||
|
||||
camera = OpenGLCameraFrame()
|
||||
camera.save_state()
|
||||
# renderer.init_camera(camera)
|
||||
|
||||
# renderer.render(camera, [vm, vm2])
|
||||
# image = renderer.get_pixels()
|
||||
# print(image.shape)
|
||||
# Image.fromarray(image, "RGBA").show()
|
||||
# exit(0)
|
||||
renderer.use_window()
|
||||
|
||||
clock = pyglet.clock.get_default()
|
||||
|
||||
def update_circle(dt):
|
||||
vm.move_to((np.sin(dt) * 4, np.cos(dt) * 4, -1))
|
||||
|
||||
def p2m(x, y, z):
|
||||
from manim._config import config
|
||||
|
||||
return (
|
||||
config.frame_width * (x / config.pixel_width - 0.5),
|
||||
config.frame_height * (y / config.pixel_height - 0.5),
|
||||
z,
|
||||
)
|
||||
|
||||
@win.event
|
||||
def on_close():
|
||||
win.close()
|
||||
|
||||
@win.event
|
||||
def on_mouse_motion(x, y, dx, dy):
|
||||
# vm.move_to((14.2222 * (x / 1920 - 0.5), 8 * (y / 1080 - 0.5), 0))
|
||||
# camera.move_to(p2m(x,y,camera.get_center()[2]))
|
||||
from scipy.spatial.transform import Rotation
|
||||
|
||||
camera.set_orientation(
|
||||
Rotation.from_rotvec(
|
||||
(-UP * (x / 1920 - 0.5) + RIGHT * (y / 1080 - 0.5)) * 2 * 3.1415
|
||||
)
|
||||
)
|
||||
# vm.set_color(col.RED.interpolate(col.GREEN,x/1920))
|
||||
# print(x,y)
|
||||
|
||||
@win.event
|
||||
def on_draw():
|
||||
dt = clock.update_time()
|
||||
renderer.render(camera, [vm2, vm3, vm4, clock_mobject, vm])
|
||||
# update_circle(counter)
|
||||
|
||||
@win.event
|
||||
def on_resize(width, height):
|
||||
super(Window, win).on_resize(width, height)
|
||||
|
||||
# pyglet.app.run()
|
||||
has_started = False
|
||||
is_finished = False
|
||||
|
||||
run_time = 5
|
||||
new_vm = Square(fill_color=col.GREEN, stroke_color=col.BLUE).shift(
|
||||
2.5 * RIGHT - UP + 2 * OUT
|
||||
)
|
||||
animation = DrawBorderThenFill(vm3, run_time=run_time)
|
||||
|
||||
real_time = 0
|
||||
virtual_time = 0
|
||||
start_timestamp = time.time()
|
||||
dt = 1 / 30
|
||||
|
||||
while True:
|
||||
# pyglet.app.platform_event_loop.step()
|
||||
win.switch_to()
|
||||
if not has_started:
|
||||
animation.begin()
|
||||
has_started = True
|
||||
|
||||
real_time = time.time() - start_timestamp
|
||||
while virtual_time < real_time:
|
||||
virtual_time += dt
|
||||
if not is_finished:
|
||||
if virtual_time >= run_time:
|
||||
animation.finish()
|
||||
has_finished = True
|
||||
else:
|
||||
animation.update_mobjects(dt)
|
||||
animation.interpolate(virtual_time / run_time)
|
||||
# update_circle(virtual_time)
|
||||
clock_mobject.set_value(virtual_time)
|
||||
win.dispatch_event("on_draw")
|
||||
win.dispatch_events()
|
||||
win.flip()
|
||||
|
|
@ -174,10 +174,6 @@ class OpenGLMobject:
|
|||
self.init_points()
|
||||
self.color = ManimColor.parse(color)
|
||||
self.init_colors()
|
||||
self.init_shader_data()
|
||||
|
||||
if self.depth_test:
|
||||
self.apply_depth_test()
|
||||
|
||||
@classmethod
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
|
|
@ -2639,8 +2635,6 @@ class OpenGLMobject:
|
|||
self.align_data(mobject)
|
||||
|
||||
def align_data(self, mobject) -> None:
|
||||
# In case any data arrays get resized when aligned to shader data
|
||||
self.refresh_shader_data()
|
||||
for mob1, mob2 in zip(self.get_family(), mobject.get_family()):
|
||||
# Separate out how points are treated so that subclasses
|
||||
# can handle that case differently if they choose
|
||||
|
|
@ -2731,24 +2725,9 @@ class OpenGLMobject:
|
|||
|
||||
self.add(dotL, dotR, dotMiddle)
|
||||
"""
|
||||
for key in self.data:
|
||||
if key in self.locked_data_keys:
|
||||
continue
|
||||
if len(self.data[key]) == 0: # type: ignore
|
||||
continue
|
||||
if key not in mobject1.data or key not in mobject2.data:
|
||||
continue
|
||||
|
||||
if key in ("points", "bounding_box"):
|
||||
func = path_func
|
||||
else:
|
||||
func = interpolate
|
||||
|
||||
self.data[key][:] = func(mobject1.data[key], mobject2.data[key], alpha) # type: ignore
|
||||
for key in self.uniforms:
|
||||
self.uniforms[key] = interpolate( # type: ignore
|
||||
mobject1.uniforms[key], mobject2.uniforms[key], alpha
|
||||
)
|
||||
# TODO: replace with list of attribute names with a locking system
|
||||
self.points = path_func(mobject1.points, mobject2.points, alpha)
|
||||
self.interpolate_color(mobject1, mobject2, alpha)
|
||||
return self
|
||||
|
||||
def pointwise_become_partial(self, mobject, a, b):
|
||||
|
|
@ -2906,44 +2885,36 @@ class OpenGLMobject:
|
|||
return bool(np.isclose(points1, points2).all())
|
||||
|
||||
# Operations touching shader uniforms
|
||||
|
||||
@affects_shader_info_id
|
||||
def fix_in_frame(self) -> Self:
|
||||
self.uniforms["is_fixed_in_frame"] = float(1.0)
|
||||
self.is_fixed_in_frame = True
|
||||
return self
|
||||
|
||||
@affects_shader_info_id
|
||||
def fix_orientation(self) -> Self:
|
||||
self.uniforms["is_fixed_orientation"] = float(1.0)
|
||||
self.is_fixed_orientation = True
|
||||
self.fixed_orientation_center = tuple(self.get_center())
|
||||
return self
|
||||
|
||||
@affects_shader_info_id
|
||||
def unfix_from_frame(self) -> Self:
|
||||
self.uniforms["is_fixed_in_frame"] = float(0.0)
|
||||
self.is_fixed_in_frame = False
|
||||
return self
|
||||
|
||||
@affects_shader_info_id
|
||||
def unfix_orientation(self):
|
||||
self.is_fixed_orientation = 0.0
|
||||
self.fixed_orientation_center = (0, 0, 0)
|
||||
return self
|
||||
|
||||
@affects_shader_info_id
|
||||
def apply_depth_test(self):
|
||||
self.depth_test = True
|
||||
return self
|
||||
|
||||
@affects_shader_info_id
|
||||
def deactivate_depth_test(self):
|
||||
self.depth_test = False
|
||||
return self
|
||||
|
||||
# Shader code manipulation
|
||||
|
||||
def replace_shader_code(self, old, new):
|
||||
# TODO, will this work with VMobject structure, given
|
||||
# that it does not simpler return shader_wrappers of
|
||||
|
|
@ -2988,47 +2959,6 @@ class OpenGLMobject:
|
|||
)
|
||||
return self
|
||||
|
||||
# For shader data
|
||||
def init_shader_data(self):
|
||||
# TODO, only call this when needed?
|
||||
self.shader_data = np.zeros(len(self.points), dtype=self.shader_dtype)
|
||||
self.shader_indices = None
|
||||
self.shader_wrapper = ShaderWrapper(
|
||||
vert_data=self.shader_data,
|
||||
shader_folder=self.shader_folder,
|
||||
texture_paths=self.texture_paths,
|
||||
depth_test=self.depth_test,
|
||||
render_primitive=self.render_primitive,
|
||||
)
|
||||
|
||||
def refresh_shader_wrapper_id(self):
|
||||
self.shader_wrapper.refresh_id()
|
||||
return self
|
||||
|
||||
def get_shader_wrapper(self):
|
||||
self.shader_wrapper.vert_data = self.get_shader_data()
|
||||
self.shader_wrapper.vert_indices = self.get_shader_vert_indices()
|
||||
self.shader_wrapper.uniforms = self.get_shader_uniforms()
|
||||
self.shader_wrapper.depth_test = self.depth_test
|
||||
return self.shader_wrapper
|
||||
|
||||
def get_shader_wrapper_list(self):
|
||||
shader_wrappers = it.chain(
|
||||
[self.get_shader_wrapper()],
|
||||
*(sm.get_shader_wrapper_list() for sm in self.submobjects),
|
||||
)
|
||||
batches = batch_by_property(shader_wrappers, lambda sw: sw.get_id())
|
||||
|
||||
result = []
|
||||
for wrapper_group, _ in batches:
|
||||
shader_wrapper = wrapper_group[0]
|
||||
if not shader_wrapper.is_valid():
|
||||
continue
|
||||
shader_wrapper.combine_with(*wrapper_group[1:])
|
||||
if len(shader_wrapper.vert_data) > 0:
|
||||
result.append(shader_wrapper)
|
||||
return result
|
||||
|
||||
def check_data_alignment(self, array, data_key):
|
||||
# Makes sure that self.data[key] can be broadcast into
|
||||
# the given array, meaning its length has to be either 1
|
||||
|
|
@ -3041,33 +2971,6 @@ class OpenGLMobject:
|
|||
)
|
||||
return self
|
||||
|
||||
def get_resized_shader_data_array(self, length: int) -> np.ndarray:
|
||||
# If possible, try to populate an existing array, rather
|
||||
# than recreating it each frame
|
||||
if len(self.shader_data) != length:
|
||||
self.shader_data = resize_array(self.shader_data, length)
|
||||
return self.shader_data
|
||||
|
||||
def read_data_to_shader(self, shader_data, shader_data_key, data_key):
|
||||
if data_key in self.locked_data_keys:
|
||||
return
|
||||
self.check_data_alignment(shader_data, data_key)
|
||||
shader_data[shader_data_key] = self.data[data_key]
|
||||
|
||||
def get_shader_data(self):
|
||||
shader_data = self.get_resized_shader_data_array(self.get_num_points())
|
||||
self.read_data_to_shader(shader_data, "point", "points")
|
||||
return shader_data
|
||||
|
||||
def refresh_shader_data(self):
|
||||
self.get_shader_data()
|
||||
|
||||
def get_shader_uniforms(self):
|
||||
return self.uniforms
|
||||
|
||||
def get_shader_vert_indices(self):
|
||||
return self.shader_indices
|
||||
|
||||
# Event Handlers
|
||||
"""
|
||||
Event handling follows the Event Bubbling model of DOM in javascript.
|
||||
|
|
|
|||
|
|
@ -55,16 +55,6 @@ DEFAULT_STROKE_COLOR = GREY_A
|
|||
DEFAULT_FILL_COLOR = GREY_C
|
||||
|
||||
|
||||
def triggers_refreshed_triangulation(func: Callable):
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
res = func(self, *args, **kwargs)
|
||||
self.refresh_triangulation()
|
||||
return res
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class OpenGLVMobject(OpenGLMobject):
|
||||
"""A vectorized mobject."""
|
||||
|
||||
|
|
@ -73,7 +63,7 @@ class OpenGLVMobject(OpenGLMobject):
|
|||
fill_shader_folder = "quadratic_bezier_fill"
|
||||
fill_dtype = [
|
||||
("point", np.float32, (3,)),
|
||||
("orientation", np.float32, (3,)),
|
||||
("unit_normal", np.float32, (3,)),
|
||||
("color", np.float32, (4,)),
|
||||
("vert_index", np.float32, (1,)),
|
||||
]
|
||||
|
|
@ -157,7 +147,7 @@ class OpenGLVMobject(OpenGLMobject):
|
|||
"fill_rgba": np.zeros((1, 4)),
|
||||
"stroke_rgba": np.zeros((1, 4)),
|
||||
"stroke_width": np.zeros((1, 1)),
|
||||
"orientation": np.zeros((1, 1)),
|
||||
"unit_normal": np.zeros((1, 3)),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -188,11 +178,6 @@ class OpenGLVMobject(OpenGLMobject):
|
|||
raise Exception("All submobjects must be of type OpenGLVMobject")
|
||||
super().add(*vmobjects)
|
||||
|
||||
def copy(self, deep: bool = False) -> Self:
|
||||
result = super().copy(deep)
|
||||
result.shader_wrapper_list = [sw.copy() for sw in self.shader_wrapper_list]
|
||||
return result
|
||||
|
||||
# Colors
|
||||
def init_colors(self):
|
||||
# self.set_fill(
|
||||
|
|
@ -343,10 +328,10 @@ class OpenGLVMobject(OpenGLMobject):
|
|||
|
||||
def get_style(self):
|
||||
return {
|
||||
"fill_rgba": self.data["fill_rgba"].copy(),
|
||||
"stroke_rgba": self.data["stroke_rgba"].copy(),
|
||||
"stroke_width": self.data["stroke_width"].copy(),
|
||||
"stroke_background": self.draw_stroke_behind_fill,
|
||||
"fill_color": self.fill_color.copy(),
|
||||
"stroke_color": self.stroke_color.copy(),
|
||||
"stroke_width": self.stroke_width.copy(),
|
||||
# "stroke_background": self.draw_stroke_behind_fill,
|
||||
"reflectiveness": self.get_reflectiveness(),
|
||||
"gloss": self.get_gloss(),
|
||||
"shadow": self.get_shadow(),
|
||||
|
|
@ -395,13 +380,13 @@ class OpenGLVMobject(OpenGLMobject):
|
|||
return self.fill_color
|
||||
|
||||
def get_fill_opacities(self) -> np.ndarray:
|
||||
return (c.to_rgba()[3] for c in self.fill_color)
|
||||
return [c.to_rgba()[3] for c in self.fill_color]
|
||||
|
||||
def get_stroke_colors(self):
|
||||
return self.stroke_color
|
||||
|
||||
def get_stroke_opacities(self) -> np.ndarray:
|
||||
return (c.to_rgba()[3] for c in self.stroke_color)
|
||||
return [c.to_rgba()[3] for c in self.stroke_color]
|
||||
|
||||
def get_stroke_widths(self) -> np.ndarray:
|
||||
return self.stroke_width
|
||||
|
|
@ -1334,14 +1319,43 @@ class OpenGLVMobject(OpenGLMobject):
|
|||
new_points += partial_quadratic_bezier_points(group, a1, a2)
|
||||
return np.vstack(new_points)
|
||||
|
||||
def interpolate(self, mobject1, mobject2, alpha, *args, **kwargs):
|
||||
super().interpolate(mobject1, mobject2, alpha, *args, **kwargs)
|
||||
if self.has_fill():
|
||||
tri1 = mobject1.get_triangulation()
|
||||
tri2 = mobject2.get_triangulation()
|
||||
if len(tri1) != len(tri2) or not np.all(tri1 == tri2):
|
||||
self.refresh_triangulation()
|
||||
return self
|
||||
def interpolate_color(self, mobject1, mobject2, alpha):
|
||||
attrs = [
|
||||
"fill_color",
|
||||
"stroke_color",
|
||||
"opacity",
|
||||
"reflectiveness",
|
||||
"shadow",
|
||||
"gloss",
|
||||
"stroke_width",
|
||||
# TODO: eventually add these attributes to OpenGLVMobject
|
||||
# "background_stroke_width",
|
||||
# "sheen_direction",
|
||||
# "sheen_factor",
|
||||
]
|
||||
|
||||
def interp(obj1, obj2, alpha):
|
||||
result = None
|
||||
if isinstance(obj1, ManimColor) or isinstance(obj2, ManimColor):
|
||||
result = obj1.interpolate(obj2, alpha)
|
||||
else:
|
||||
result = interpolate(obj1, obj2, alpha)
|
||||
return result
|
||||
|
||||
for attr in attrs:
|
||||
if alpha == 1.0:
|
||||
setattr(self, attr, getattr(mobject2, attr))
|
||||
continue
|
||||
|
||||
attr1 = getattr(mobject1, attr)
|
||||
attr2 = getattr(mobject2, attr)
|
||||
if isinstance(attr1, list) or isinstance(attr2, list):
|
||||
result = [
|
||||
interp(elem1, elem2, alpha) for elem1, elem2 in zip(attr1, attr2)
|
||||
]
|
||||
else:
|
||||
result = interp(attr1, attr2, alpha)
|
||||
setattr(self, attr, result)
|
||||
|
||||
# TODO: compare to 3b1b/manim again check if something changed so we don't need the cairo interpolation anymore
|
||||
def pointwise_become_partial(
|
||||
|
|
@ -1435,215 +1449,35 @@ class OpenGLVMobject(OpenGLMobject):
|
|||
|
||||
# Related to triangulation
|
||||
|
||||
def refresh_triangulation(self):
|
||||
for mob in self.get_family():
|
||||
mob.needs_new_triangulation = True
|
||||
mob.data["orientation"] = resize_array(
|
||||
mob.data["orientation"], mob.get_num_points()
|
||||
)
|
||||
return self
|
||||
|
||||
def get_triangulation(self):
|
||||
# Figure out how to triangulate the interior to know
|
||||
# how to send the points as to the vertex shader.
|
||||
# First triangles come directly from the points
|
||||
if not self.needs_new_triangulation:
|
||||
return self.triangulation
|
||||
|
||||
points = self.points
|
||||
|
||||
if len(points) <= 1:
|
||||
self.triangulation = np.zeros(0, dtype="i4")
|
||||
self.needs_new_triangulation = False
|
||||
return self.triangulation
|
||||
|
||||
normal_vector = self.get_unit_normal()
|
||||
indices = np.arange(len(points), dtype=int)
|
||||
|
||||
# Rotate points such that unit normal vector is OUT
|
||||
if not np.isclose(normal_vector, OUT).all():
|
||||
points = np.dot(points, z_to_vector(normal_vector))
|
||||
|
||||
atol = self.tolerance_for_point_equality
|
||||
end_of_loop = np.zeros(len(points) // 3, dtype=bool)
|
||||
end_of_loop[:-1] = (np.abs(points[2:-3:3] - points[3::3]) > atol).any(1)
|
||||
end_of_loop[-1] = True
|
||||
|
||||
v01s = points[1::3] - points[0::3]
|
||||
v12s = points[2::3] - points[1::3]
|
||||
curve_orientations = np.sign(cross2d(v01s, v12s))
|
||||
self.data["orientation"] = np.transpose([curve_orientations.repeat(3)])
|
||||
|
||||
concave_parts = curve_orientations < 0
|
||||
|
||||
# These are the vertices to which we'll apply a polygon triangulation
|
||||
inner_vert_indices = np.hstack(
|
||||
[
|
||||
indices[0::3],
|
||||
indices[1::3][concave_parts],
|
||||
indices[2::3][end_of_loop],
|
||||
]
|
||||
)
|
||||
inner_vert_indices.sort()
|
||||
rings = np.arange(1, len(inner_vert_indices) + 1)[inner_vert_indices % 3 == 2]
|
||||
|
||||
# Triangulate
|
||||
inner_verts = points[inner_vert_indices]
|
||||
inner_tri_indices = inner_vert_indices[
|
||||
earclip_triangulation(inner_verts, rings)
|
||||
]
|
||||
|
||||
tri_indices = np.hstack([indices, inner_tri_indices])
|
||||
self.triangulation = tri_indices
|
||||
self.needs_new_triangulation = False
|
||||
return tri_indices
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
def set_points(self, points):
|
||||
super().set_points(points)
|
||||
return self
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
def append_points(self, points):
|
||||
return super().append_points(points)
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
def reverse_points(self):
|
||||
return super().reverse_points()
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
def set_data(self, data):
|
||||
super().set_data(data)
|
||||
return self
|
||||
|
||||
# TODO, how to be smart about tangents here?
|
||||
@triggers_refreshed_triangulation
|
||||
def apply_function(self, function, make_smooth=False, **kwargs):
|
||||
super().apply_function(function, **kwargs)
|
||||
if self.make_smooth_after_applying_functions or make_smooth:
|
||||
self.make_approximately_smooth()
|
||||
return self
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
def apply_points_function(self, *args, **kwargs):
|
||||
super().apply_points_function(*args, **kwargs)
|
||||
return self
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
def flip(self, *args, **kwargs):
|
||||
super().flip(*args, **kwargs)
|
||||
return self
|
||||
|
||||
# For shaders
|
||||
def init_shader_data(self):
|
||||
self.fill_data = np.zeros(0, dtype=self.fill_dtype)
|
||||
self.stroke_data = np.zeros(0, dtype=self.stroke_dtype)
|
||||
self.fill_shader_wrapper = ShaderWrapper(
|
||||
vert_data=self.fill_data,
|
||||
vert_indices=np.zeros(0, dtype="i4"),
|
||||
uniforms=self.uniforms,
|
||||
shader_folder=self.fill_shader_folder,
|
||||
render_primitive=self.render_primitive,
|
||||
)
|
||||
self.stroke_shader_wrapper = ShaderWrapper(
|
||||
vert_data=self.stroke_data,
|
||||
uniforms=self.uniforms,
|
||||
shader_folder=self.stroke_shader_folder,
|
||||
render_primitive=self.render_primitive,
|
||||
)
|
||||
|
||||
self.shader_wrapper_list = [
|
||||
self.stroke_shader_wrapper.copy(), # Use for back stroke
|
||||
self.fill_shader_wrapper.copy(),
|
||||
self.stroke_shader_wrapper.copy(),
|
||||
]
|
||||
for sw in self.shader_wrapper_list:
|
||||
sw.uniforms = self.uniforms
|
||||
|
||||
def refresh_shader_wrapper_id(self):
|
||||
for wrapper in [self.fill_shader_wrapper, self.stroke_shader_wrapper]:
|
||||
wrapper.refresh_id()
|
||||
return self
|
||||
|
||||
def get_fill_shader_wrapper(self) -> ShaderWrapper:
|
||||
self.fill_shader_wrapper.vert_indices = self.get_fill_shader_vert_indices()
|
||||
self.fill_shader_wrapper.vert_data = self.get_fill_shader_data()
|
||||
self.fill_shader_wrapper.uniforms = self.get_shader_uniforms()
|
||||
self.fill_shader_wrapper.depth_test = self.depth_test
|
||||
return self.fill_shader_wrapper
|
||||
|
||||
def get_stroke_shader_wrapper(self) -> ShaderWrapper:
|
||||
self.stroke_shader_wrapper.vert_data = self.get_stroke_shader_data()
|
||||
self.stroke_shader_wrapper.uniforms = self.get_shader_uniforms()
|
||||
self.stroke_shader_wrapper.depth_test = self.depth_test
|
||||
return self.stroke_shader_wrapper
|
||||
|
||||
def get_shader_wrapper_list(self):
|
||||
# Build up data lists
|
||||
fill_shader_wrappers = []
|
||||
stroke_shader_wrappers = []
|
||||
back_stroke_shader_wrappers = []
|
||||
for submob in self.family_members_with_points():
|
||||
if submob.has_fill():
|
||||
fill_shader_wrappers.append(submob.get_fill_shader_wrapper())
|
||||
if submob.has_stroke():
|
||||
ssw = submob.get_stroke_shader_wrapper()
|
||||
if submob.draw_stroke_behind_fill:
|
||||
back_stroke_shader_wrappers.append(ssw)
|
||||
else:
|
||||
stroke_shader_wrappers.append(ssw)
|
||||
|
||||
# Combine data lists
|
||||
sw_lists = [
|
||||
back_stroke_shader_wrappers,
|
||||
fill_shader_wrappers,
|
||||
stroke_shader_wrappers,
|
||||
]
|
||||
for sw, sw_list in zip(self.shader_wrapper_list, sw_lists):
|
||||
if not sw_list:
|
||||
continue
|
||||
sw.read_in(*sw_list)
|
||||
sw.depth_test = any(sw.depth_test for sw in sw_list)
|
||||
sw.uniforms.update(sw_list[0].uniforms)
|
||||
return list(filter(lambda sw: len(sw.vert_data) > 0, self.shader_wrapper_list))
|
||||
|
||||
def get_stroke_shader_data(self) -> np.ndarray:
|
||||
points = self.points
|
||||
if len(self.stroke_data) != len(points):
|
||||
self.stroke_data = resize_array(self.stroke_data, len(points))
|
||||
|
||||
if "points" not in self.locked_data_keys:
|
||||
nppc = self.n_points_per_curve
|
||||
self.stroke_data["point"] = points
|
||||
self.stroke_data["prev_point"][:nppc] = points[-nppc:]
|
||||
self.stroke_data["prev_point"][nppc:] = points[:-nppc]
|
||||
self.stroke_data["next_point"][:-nppc] = points[nppc:]
|
||||
self.stroke_data["next_point"][-nppc:] = points[:nppc]
|
||||
|
||||
self.read_data_to_shader(self.stroke_data, "color", "stroke_rgba")
|
||||
self.read_data_to_shader(self.stroke_data, "stroke_width", "stroke_width")
|
||||
|
||||
return self.stroke_data
|
||||
|
||||
def get_fill_shader_data(self) -> np.ndarray:
|
||||
points = self.points
|
||||
if len(self.fill_data) != len(points):
|
||||
self.fill_data = resize_array(self.fill_data, len(points))
|
||||
self.fill_data["vert_index"][:, 0] = range(len(points))
|
||||
|
||||
self.read_data_to_shader(self.fill_data, "point", "points")
|
||||
self.read_data_to_shader(self.fill_data, "color", "fill_rgba")
|
||||
self.read_data_to_shader(self.fill_data, "orientation", "orientation")
|
||||
|
||||
return self.fill_data
|
||||
|
||||
def refresh_shader_data(self):
|
||||
self.get_fill_shader_data()
|
||||
self.get_stroke_shader_data()
|
||||
|
||||
def get_fill_shader_vert_indices(self) -> np.ndarray:
|
||||
return self.get_triangulation()
|
||||
|
||||
|
||||
class OpenGLVGroup(OpenGLVMobject):
|
||||
"""A group of vectorized mobjects.
|
||||
|
|
|
|||
|
|
@ -372,10 +372,9 @@ class VMobject(Mobject):
|
|||
|
||||
return ret
|
||||
|
||||
def match_style(self, vmobject, family=True):
|
||||
self.set_style(**vmobject.get_style(), family=False)
|
||||
|
||||
if family:
|
||||
def match_style(self, vmobject: VMobject, recurse: bool = True):
|
||||
self.set_style(**vmobject.get_style(), recurse=False)
|
||||
if recurse:
|
||||
# Does its best to match up submobject lists, and
|
||||
# match styles accordingly
|
||||
submobs1, submobs2 = self.submobjects, vmobject.submobjects
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import numpy as np
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
class STD140BufferFormat:
|
||||
_GL_DTYPES: dict[str, tuple[str, int, tuple[int, ...]]] = {
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ from typing_extensions import override
|
|||
|
||||
import manim.constants as const
|
||||
import manim.utils.color.manim_colors as color
|
||||
from manim.renderer.buffers.buffer import STD140BufferFormat
|
||||
from manim._config import config, logger
|
||||
from manim.camera.camera import OpenGLCameraFrame
|
||||
from manim.mobject.types.vectorized_mobject import VMobject
|
||||
from manim.renderer.buffers.buffer import STD140BufferFormat
|
||||
from manim.renderer.opengl_shader_program import load_shader_program_by_folder
|
||||
from manim.renderer.renderer import ImageType, Renderer, RendererData
|
||||
from manim.renderer.shader_wrapper import ShaderWrapper
|
||||
|
|
@ -406,7 +406,8 @@ class OpenGLRenderer(Renderer):
|
|||
self.ctx.disable(gl.DEPTH_TEST) # type: ignore
|
||||
|
||||
for sub in mob.family_members_with_points():
|
||||
if sub.renderer_data is None:
|
||||
# TODO: review this renderer data optimization attempt
|
||||
if True: # if sub.renderer_data is None:
|
||||
# Initialize
|
||||
GLVMobjectManager.init_render_data(sub)
|
||||
|
||||
|
|
@ -422,6 +423,27 @@ class OpenGLRenderer(Renderer):
|
|||
# mob.renderer_data.mesh = ... # Triangulation todo
|
||||
|
||||
num_mobs = len(mob.family_members_with_points())
|
||||
|
||||
# Another stroke pass is needed in the beginning to deal with transparency properly
|
||||
for counter, sub in enumerate(mob.family_members_with_points()):
|
||||
if not isinstance(sub.renderer_data, GLRenderData):
|
||||
return
|
||||
enable_depth(sub)
|
||||
uniforms = GLVMobjectManager.read_uniforms(sub)
|
||||
uniforms["index"] = (counter + 1) / num_mobs / 2
|
||||
uniforms["disable_stencil"] = float(True)
|
||||
# uniforms['z_shift'] = counter/9 + 1/20
|
||||
self.ctx.copy_framebuffer(self.stencil_texture_fbo, self.stencil_buffer_fbo)
|
||||
self.stencil_texture.use(0)
|
||||
self.vmobject_stroke_program["stencil_texture"] = 0
|
||||
if sub.has_stroke():
|
||||
ProgramManager.write_uniforms(self.vmobject_stroke_program, uniforms)
|
||||
self.render_program(
|
||||
self.vmobject_stroke_program,
|
||||
self.get_stroke_shader_data(sub),
|
||||
np.array(range(len(sub.points))),
|
||||
)
|
||||
|
||||
for counter, sub in enumerate(mob.family_members_with_points()):
|
||||
if not isinstance(sub.renderer_data, GLRenderData):
|
||||
return
|
||||
|
|
@ -429,6 +451,7 @@ class OpenGLRenderer(Renderer):
|
|||
uniforms = GLVMobjectManager.read_uniforms(sub)
|
||||
# uniforms['z_shift'] = counter/9
|
||||
uniforms["index"] = (counter + 1) / num_mobs
|
||||
uniforms["disable_stencil"] = float(False)
|
||||
self.ctx.copy_framebuffer(self.stencil_texture_fbo, self.stencil_buffer_fbo)
|
||||
self.stencil_texture.use(0)
|
||||
self.vmobject_fill_program["stencil_texture"] = 0
|
||||
|
|
@ -446,6 +469,7 @@ class OpenGLRenderer(Renderer):
|
|||
enable_depth(sub)
|
||||
uniforms = GLVMobjectManager.read_uniforms(sub)
|
||||
uniforms["index"] = (counter + 1) / num_mobs
|
||||
uniforms["disable_stencil"] = float(False)
|
||||
# uniforms['z_shift'] = counter/9 + 1/20
|
||||
self.ctx.copy_framebuffer(self.stencil_texture_fbo, self.stencil_buffer_fbo)
|
||||
self.stencil_texture.use(0)
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ def get_shader_code_from_file(filename: Path) -> str | None:
|
|||
directories=[get_shader_dir(), Path("/")],
|
||||
)
|
||||
except OSError:
|
||||
logger.warning(f"Could not find shader file {filename}")
|
||||
logger.warning(f"Could not find shader file {filename.absolute()}")
|
||||
return None
|
||||
|
||||
result = filepath.read_text()
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ layout (std140) uniform ubo_camera {
|
|||
float is_fixed_in_frame;
|
||||
float is_fixed_orientation;
|
||||
vec3 fixed_orientation_center;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
#include ../include/camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
in float orientation;
|
||||
in vec3 unit_normal;
|
||||
in vec4 color;
|
||||
in float vert_index;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include ../include/camera_uniform_declarations.glsl
|
||||
uniform vec2 pixel_shape;
|
||||
uniform float index;
|
||||
uniform float disable_stencil;
|
||||
|
||||
in vec2 uv_coords;
|
||||
in vec2 uv_b2;
|
||||
|
|
@ -88,8 +89,12 @@ float modify_distance_for_endpoints(vec2 p, float dist, float t){
|
|||
|
||||
void main() {
|
||||
// Use the default value as standard output
|
||||
stencil_value.rgb = vec3(index);
|
||||
stencil_value.a = 1.0;
|
||||
if(disable_stencil==1.0){
|
||||
stencil_value = vec4(0.0);
|
||||
}else{
|
||||
stencil_value.rgb = vec3(index);
|
||||
stencil_value.a = 1.0;
|
||||
}
|
||||
gl_FragDepth = gl_FragCoord.z;
|
||||
// Get the previous index that was written to this fragment
|
||||
float previous_index =
|
||||
|
|
@ -113,7 +118,10 @@ void main() {
|
|||
if (color.a == 1.0)
|
||||
discard;
|
||||
else
|
||||
gl_FragDepth = gl_FragCoord.z - 3*index / 1000.0;
|
||||
gl_FragDepth = gl_FragCoord.z + index / 1000.0;
|
||||
}
|
||||
if(disable_stencil==1.0){
|
||||
gl_FragDepth = gl_FragCoord.z + 4.5 * index / 1000.0;
|
||||
}
|
||||
if (uv_stroke_width == 0)
|
||||
discard;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ from manim.utils.family_ops import (
|
|||
extract_mobject_family_members,
|
||||
recursive_mobject_remove,
|
||||
)
|
||||
from manim.utils.iterables import list_difference_update
|
||||
from manim.utils.module_ops import get_module
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -406,28 +407,15 @@ class Scene:
|
|||
For example, if the scene includes Group(m1, m2, m3), and we call scene.remove(m1),
|
||||
the desired behavior is for the scene to then include m2 and m3 (ungrouped).
|
||||
"""
|
||||
if config.renderer == RendererType.OPENGL:
|
||||
mobjects_to_remove = []
|
||||
meshes_to_remove = set()
|
||||
for mobject_or_mesh in mobjects:
|
||||
if isinstance(mobject_or_mesh, Object3D):
|
||||
meshes_to_remove.add(mobject_or_mesh)
|
||||
else:
|
||||
mobjects_to_remove.append(mobject_or_mesh)
|
||||
self.mobjects = restructure_list_to_exclude_certain_family_members(
|
||||
self.mobjects,
|
||||
mobjects_to_remove,
|
||||
)
|
||||
self.meshes = list(
|
||||
filter(lambda mesh: mesh not in set(meshes_to_remove), self.meshes),
|
||||
)
|
||||
return self
|
||||
elif config.renderer == RendererType.CAIRO:
|
||||
for list_name in "mobjects", "foreground_mobjects":
|
||||
self.restructure_mobjects(mobjects, list_name, False)
|
||||
return self
|
||||
for mob in mobjects_to_remove:
|
||||
# First restructure self.mobjects so that parents/grandparents/etc. are replaced
|
||||
# with their children, likewise for all ancestors in the extended family.
|
||||
for ancestor in mob.get_ancestors(extended=True):
|
||||
self.replace(ancestor, *ancestor.submobjects)
|
||||
self.mobjects = list_difference_update(self.mobjects, mob.get_family())
|
||||
return self
|
||||
|
||||
def replace(self, old_mobject: Mobject, new_mobject: Mobject) -> None:
|
||||
def replace(self, mobject: Mobject, *replacements: Mobject):
|
||||
"""Replace one mobject in the scene with another, preserving draw order.
|
||||
|
||||
If ``old_mobject`` is a submobject of some other Mobject (e.g. a
|
||||
|
|
@ -442,37 +430,14 @@ class Scene:
|
|||
A mobject which must not already be in the scene.
|
||||
|
||||
"""
|
||||
if old_mobject is None or new_mobject is None:
|
||||
raise ValueError("Specified mobjects cannot be None")
|
||||
|
||||
def replace_in_list(
|
||||
mobj_list: list[Mobject], old_m: Mobject, new_m: Mobject
|
||||
) -> bool:
|
||||
# We use breadth-first search because some Mobjects get very deep and
|
||||
# we expect top-level elements to be the most common targets for replace.
|
||||
for i in range(0, len(mobj_list)):
|
||||
# Is this the old mobject?
|
||||
if mobj_list[i] == old_m:
|
||||
# If so, write the new object to the same spot and stop looking.
|
||||
mobj_list[i] = new_m
|
||||
return True
|
||||
# Now check all the children of all these mobs.
|
||||
for mob in mobj_list: # noqa: SIM110
|
||||
if replace_in_list(mob.submobjects, old_m, new_m):
|
||||
# If we found it in a submobject, stop looking.
|
||||
return True
|
||||
# If we did not find the mobject in the mobject list or any submobjects,
|
||||
# (or the list was empty), indicate we did not make the replacement.
|
||||
return False
|
||||
|
||||
# Make use of short-circuiting conditionals to check mobjects and then
|
||||
# foreground_mobjects
|
||||
replaced = replace_in_list(
|
||||
self.mobjects, old_mobject, new_mobject
|
||||
) or replace_in_list(self.foreground_mobjects, old_mobject, new_mobject)
|
||||
|
||||
if not replaced:
|
||||
raise ValueError(f"Could not find {old_mobject} in scene")
|
||||
if mobject in self.mobjects:
|
||||
index = self.mobjects.index(mobject)
|
||||
self.mobjects = [
|
||||
*self.mobjects[:index],
|
||||
*replacements,
|
||||
*self.mobjects[index + 1 :],
|
||||
]
|
||||
return self
|
||||
|
||||
def add_updater(self, func: Callable[[float], None]) -> None:
|
||||
"""Add an update function to the scene.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue