[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:
Francisco Manríquez Novoa 2023-11-27 01:10:52 +01:00 committed by GitHub
commit 9b98fe5af2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 271 additions and 376 deletions

View 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()

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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, ...]]] = {

View file

@ -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)

View file

@ -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()

View file

@ -7,4 +7,4 @@ layout (std140) uniform ubo_camera {
float is_fixed_in_frame;
float is_fixed_orientation;
vec3 fixed_orientation_center;
};
};

View file

@ -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;

View file

@ -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;

View file

@ -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.