Rewrite stroke and fill shaders (#1716)

Rewrite vectorized mobject shaders to be compatible with transformation matrices.
This commit is contained in:
Devin Neal 2021-06-30 00:49:55 -07:00 committed by GitHub
commit 3ee3ea1a3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 587 additions and 22 deletions

View file

@ -2,7 +2,6 @@ import os
from pathlib import Path
import manim.utils.opengl as opengl
import manim.utils.space_ops as space_ops
from manim import *
from manim.opengl import *
@ -72,6 +71,24 @@ def get_plane_mesh(context):
return Mesh(shader, attributes)
class TextTest(Scene):
def construct(self):
import string
text = OpenGLText(
string.ascii_lowercase, stroke_width=4, stroke_color=BLUE
).scale(2)
text2 = (
OpenGLText(string.ascii_uppercase, stroke_width=4, stroke_color=BLUE)
.scale(2)
.next_to(text, DOWN)
)
# self.add(text, text2)
self.play(Write(text))
self.play(Write(text2))
self.interactive_embed()
class GuiTest(Scene):
def construct(self):
mesh = get_plane_mesh(self.renderer.context)
@ -390,7 +407,7 @@ class InteractiveDevelopment(Scene):
# lines as if they were part of this construct method.
# In particular, 'square', 'circle' and 'self' will all be
# part of the local namespace in that terminal.
self.embed()
# self.embed()
# Try copying and pasting some of the lines below into
# the interactive shell
@ -400,10 +417,12 @@ class InteractiveDevelopment(Scene):
self.play(Rotate(circle, 90 * DEGREES))
self.play(circle.animate.shift(2 * RIGHT).scale(0.25))
# text = Text("""
# text = Text(
# """
# In general, using the interactive shell
# is very helpful when developing new scenes
# """)
# """
# )
# self.play(Write(text))
# # In the interactive shell, you can just type

View file

@ -104,6 +104,12 @@ enable_gui = False
# --gui_location
gui_location = 0,0
# --use_projection_fill_shaders
use_projection_fill_shaders = False
# --use_projection_stroke_shaders
use_projection_stroke_shaders = False
# If the -t (--transparent) flag is used, these will be replaced with the
# values specified in the [TRANSPARENT] section later in this file.
png_mode = RGB

View file

@ -285,6 +285,8 @@ class ManimConfig(MutableMapping):
"use_webgl_renderer",
"enable_gui",
"gui_location",
"use_projection_fill_shaders",
"use_projection_stroke_shaders",
"verbosity",
"video_dir",
"write_all",
@ -519,6 +521,8 @@ class ManimConfig(MutableMapping):
"use_opengl_renderer",
"use_webgl_renderer",
"enable_gui",
"use_projection_fill_shaders",
"use_projection_stroke_shaders",
]:
setattr(self, key, parser["CLI"].getboolean(key, fallback=False))
@ -653,6 +657,8 @@ class ManimConfig(MutableMapping):
"use_opengl_renderer",
"use_webgl_renderer",
"enable_gui",
"use_projection_fill_shaders",
"use_projection_stroke_shaders",
]:
if hasattr(args, key):
attr = getattr(args, key)
@ -1172,6 +1178,18 @@ class ManimConfig(MutableMapping):
doc="Enable GUI interaction.",
)
use_projection_fill_shaders = property(
lambda self: self._d["use_projection_fill_shaders"],
lambda self, val: self._set_boolean("use_projection_fill_shaders", val),
doc="Use shaders for OpenGLVMobject fill which are compatible with transformation matrices.",
)
use_projection_stroke_shaders = property(
lambda self: self._d["use_projection_stroke_shaders"],
lambda self, val: self._set_boolean("use_projection_stroke_shaders", val),
doc="Use shaders for OpenGLVMobject stroke which are compatible with transformation matrices.",
)
def get_dir(self, key: str, **kwargs: str) -> Path:
"""Resolve a config option that stores a directory.

View file

@ -124,4 +124,14 @@ render_options = option_group(
option(
"-t", "--transparent", is_flag=True, help="Render scenes with alpha channel."
),
option(
"--use_projection_fill_shaders",
is_flag=True,
help="Use shaders for OpenGLVMobject fill which are compatible with transformation matrices.",
),
option(
"--use_projection_stroke_shaders",
is_flag=True,
help="Use shaders for OpenGLVMobject stroke which are compatible with transformation matrices.",
),
)

View file

@ -211,6 +211,7 @@ class OpenGLArc(OpenGLTipableVMobject):
self.n_components = n_components
self.arc_center = arc_center
super().__init__(self, **kwargs)
self.orientation = -1
def init_points(self):
self.set_points(
@ -220,6 +221,7 @@ class OpenGLArc(OpenGLTipableVMobject):
n_components=self.n_components,
)
)
# To maintain proper orientation for fill shaders.
self.scale(self.radius, about_point=ORIGIN)
self.shift(self.arc_center)

View file

@ -253,6 +253,8 @@ class OpenGLSingleStringMathTex(OpenGLSVGMobject):
self.scale(TEX_MOB_SCALE_FACTOR)
if self.organize_left_to_right:
self.organize_submobjects_left_to_right()
for mob in self.submobjects:
mob.orientation = -1
def __repr__(self):
return f"{type(self).__name__}({repr(self.tex_string)})"

View file

@ -7,6 +7,7 @@ from typing import Optional
import moderngl
import numpy as np
from ... import config
from ...constants import *
from ...mobject.opengl_mobject import OpenGLMobject, OpenGLPoint
@ -117,6 +118,7 @@ class OpenGLVMobject(OpenGLMobject):
self.needs_new_triangulation = True
self.triangulation = np.zeros(0, dtype="i4")
self.orientation = 1
super().__init__(**kwargs)
self.refresh_unit_normal()
@ -888,11 +890,14 @@ class OpenGLVMobject(OpenGLMobject):
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(tri1) or not np.all(tri1 == tri2):
self.refresh_triangulation()
if config["use_projection_fill_shaders"]:
self.refresh_triangulation()
else:
if self.has_fill():
tri1 = mobject1.get_triangulation()
tri2 = mobject2.get_triangulation()
if len(tri1) != len(tri1) or not np.all(tri1 == tri2):
self.refresh_triangulation()
return self
def pointwise_become_partial(self, vmobject, a, b):
@ -1108,9 +1113,9 @@ class OpenGLVMobject(OpenGLMobject):
stroke_shader_wrappers = []
back_stroke_shader_wrappers = []
for submob in self.family_members_with_points():
if submob.has_fill():
if submob.has_fill() and not config["use_projection_fill_shaders"]:
fill_shader_wrappers.append(submob.get_fill_shader_wrapper())
if submob.has_stroke():
if submob.has_stroke() and not config["use_projection_stroke_shaders"]:
ssw = submob.get_stroke_shader_wrapper()
if submob.draw_stroke_behind_fill:
back_stroke_shader_wrappers.append(ssw)

View file

@ -14,6 +14,7 @@ from manim.utils.exceptions import EndSceneEarlyException
from ..constants import *
from ..mobject.opengl_mobject import OpenGLMobject, OpenGLPoint
from ..mobject.types.opengl_vectorized_mobject import OpenGLVMobject
from ..scene.scene_file_writer import SceneFileWriter
from ..utils import opengl
from ..utils.simple_functions import clip
@ -26,6 +27,10 @@ from ..utils.space_ops import (
)
from .opengl_renderer_window import Window
from .shader import Mesh, Shader
from .vectorized_mobject_rendering import (
render_opengl_vectorized_mobject_fill,
render_opengl_vectorized_mobject_stroke,
)
class OpenGLCamera(OpenGLMobject):
@ -262,6 +267,13 @@ class OpenGLRenderer:
}
def render_mobject(self, mobject):
if isinstance(mobject, OpenGLVMobject):
if config["use_projection_fill_shaders"]:
render_opengl_vectorized_mobject_fill(self, mobject)
if config["use_projection_stroke_shaders"]:
render_opengl_vectorized_mobject_stroke(self, mobject)
shader_wrapper_list = mobject.get_shader_wrapper_list()
# Convert ShaderWrappers to Meshes.
@ -377,11 +389,11 @@ class OpenGLRenderer:
for obj in scene.meshes:
for mesh in obj.get_meshes():
mesh.shader.set_uniform(
"model_matrix", opengl.matrix_to_shader_input(mesh.model_matrix)
"u_model_matrix", opengl.matrix_to_shader_input(mesh.model_matrix)
)
mesh.shader.set_uniform("view_matrix", opengl_view_matrix)
mesh.shader.set_uniform("u_view_matrix", opengl_view_matrix)
mesh.shader.set_uniform(
"projection_matrix",
"u_projection_matrix",
scene.camera.projection_matrix,
)
mesh.render()

View file

@ -329,6 +329,7 @@ class Shader:
name=None,
source=None,
):
global shader_program_cache
self.context = context
self.name = name
@ -354,7 +355,7 @@ class Shader:
self.shader_program = context.program(**source_dict)
# Cache the shader.
if name is not None:
if name is not None and name not in shader_program_cache:
shader_program_cache[self.name] = self.shader_program
def set_uniform(self, name, value):
@ -370,7 +371,6 @@ class FullScreenQuad(Mesh):
context,
fragment_shader_source=None,
fragment_shader_name=None,
output_color_variable="frag_color",
):
if fragment_shader_source is None and fragment_shader_name is None:
raise Exception("Must either pass shader name or shader source.")

View file

@ -6,5 +6,5 @@ uniform mat4 u_view_matrix;
uniform mat4 u_projection_matrix;
void main() {
gl_Position = u_projection_matrix * u_view_matrix * u_model_matrix * vec4(in_vert, 1);
gl_Position = u_projection_matrix * u_view_matrix * u_model_matrix * vec4(in_vert, 1.0);
}

View file

@ -0,0 +1,9 @@
#version 330
in vec4 v_color;
out vec4 frag_color;
void main() {
frag_color = v_color;
}

View file

@ -0,0 +1,11 @@
#version 330
in vec2 in_vert;
in vec4 in_color;
out vec4 v_color;
void main() {
v_color = in_color;
gl_Position = vec4(in_vert, 0.0, 1.0);
}

View file

@ -0,0 +1,16 @@
#version 330
in vec4 v_color;
in vec2 v_texture_coords;
flat in int v_texture_mode;
out vec4 frag_color;
void main() {
float curve_func = v_texture_coords[0] * v_texture_coords[0] - v_texture_coords[1];
if (v_texture_mode * curve_func >= 0.0) {
frag_color = v_color;
} else {
discard;
}
}

View file

@ -0,0 +1,20 @@
#version 330
uniform mat4 u_view_matrix;
uniform mat4 u_projection_matrix;
in vec3 in_vert;
in vec4 in_color;
in vec2 texture_coords;
in int texture_mode;
out vec4 v_color;
out vec2 v_texture_coords;
flat out int v_texture_mode;
void main() {
v_color = in_color;
v_texture_coords = texture_coords;
v_texture_mode = texture_mode;
gl_Position = u_projection_matrix * u_view_matrix * vec4(in_vert, 1.0);
}

View file

@ -0,0 +1,85 @@
#version 330
in float v_degree;
in float v_thickness;
in vec2 uv_point;
in vec2[3] uv_curve;
in vec4 v_color;
out vec4 frag_color;
// https://www.shadertoy.com/view/ltXSDB
// Test if point p crosses line (a, b), returns sign of result
float testCross(vec2 a, vec2 b, vec2 p) {
return sign((b.y-a.y) * (p.x-a.x) - (b.x-a.x) * (p.y-a.y));
}
// Determine which side we're on (using barycentric parameterization)
float signBezier(vec2 A, vec2 B, vec2 C, vec2 p)
{
vec2 a = C - A, b = B - A, c = p - A;
vec2 bary = vec2(c.x*b.y-b.x*c.y,a.x*c.y-c.x*a.y) / (a.x*b.y-b.x*a.y);
vec2 d = vec2(bary.y * 0.5, 0.0) + 1.0 - bary.x - bary.y;
return mix(sign(d.x * d.x - d.y), mix(-1.0, 1.0,
step(testCross(A, B, p) * testCross(B, C, p), 0.0)),
step((d.x - d.y), 0.0)) * testCross(A, C, B);
}
// Solve cubic equation for roots
vec3 solveCubic(float a, float b, float c)
{
float p = b - a*a / 3.0, p3 = p*p*p;
float q = a * (2.0*a*a - 9.0*b) / 27.0 + c;
float d = q*q + 4.0*p3 / 27.0;
float offset = -a / 3.0;
if(d >= 0.0) {
float z = sqrt(d);
vec2 x = (vec2(z, -z) - q) / 2.0;
vec2 uv = sign(x)*pow(abs(x), vec2(1.0/3.0));
return vec3(offset + uv.x + uv.y);
}
float v = acos(-sqrt(-27.0 / p3) * q / 2.0) / 3.0;
float m = cos(v), n = sin(v)*1.732050808;
return vec3(m + m, -n - m, n - m) * sqrt(-p / 3.0) + offset;
}
// Find the signed distance from a point to a bezier curve
float sdBezier(vec2 A, vec2 B, vec2 C, vec2 p)
{
B = mix(B + vec2(1e-4), B, abs(sign(B * 2.0 - A - C)));
vec2 a = B - A, b = A - B * 2.0 + C, c = a * 2.0, d = A - p;
vec3 k = vec3(3.*dot(a,b),2.*dot(a,a)+dot(d,b),dot(d,a)) / dot(b,b);
vec3 t = clamp(solveCubic(k.x, k.y, k.z), 0.0, 1.0);
vec2 pos = A + (c + b*t.x)*t.x;
float dis = length(pos - p);
pos = A + (c + b*t.y)*t.y;
dis = min(dis, length(pos - p));
pos = A + (c + b*t.z)*t.z;
dis = min(dis, length(pos - p));
return dis * signBezier(A, B, C, p);
}
// https://www.shadertoy.com/view/llcfR7
float dLine(vec2 p1, vec2 p2, vec2 x) {
vec4 colA = vec4(clamp (5.0 - length (x - p1), 0.0, 1.0));
vec4 colB = vec4(clamp (5.0 - length (x - p2), 0.0, 1.0));
vec2 a_p1 = x - p1;
vec2 p2_p1 = p2 - p1;
float h = clamp (dot (a_p1, p2_p1) / dot (p2_p1, p2_p1), 0.0, 1.0);
return length (a_p1 - p2_p1 * h);
}
void main() {
float distance;
if (v_degree == 2.0) {
distance = sdBezier(uv_curve[0], uv_curve[1], uv_curve[2], uv_point);
} else {
distance = dLine(uv_curve[0], uv_curve[2], uv_point);
}
if (abs(distance) < v_thickness) {
frag_color = v_color;
} else {
discard;
}
}

View file

@ -0,0 +1,125 @@
#version 330
uniform vec3 manim_unit_normal;
uniform mat4 u_model_view_matrix;
uniform mat4 u_projection_matrix;
in vec3[3] current_curve;
in vec2 tile_coordinate;
in vec4 in_color;
in float in_width;
out float v_degree;
out float v_thickness;
out vec2 uv_point;
out vec2[3] uv_curve;
out vec4 v_color;
int get_degree(in vec3 points[3], out vec3 normal) {
float length_threshold = 1e-6;
float angle_threshold = 5e-2;
vec3 v01 = (points[1] - points[0]);
vec3 v12 = (points[2] - points[1]);
float dot_prod = clamp(dot(normalize(v01), normalize(v12)), -1, 1);
bool aligned = acos(dot_prod) < angle_threshold;
bool distinct_01 = length(v01) > length_threshold; // v01 is considered nonzero
bool distinct_12 = length(v12) > length_threshold; // v12 is considered nonzero
int num_distinct = int(distinct_01) + int(distinct_12);
bool quadratic = (num_distinct == 2) && !aligned;
bool linear = (num_distinct == 1) || ((num_distinct == 2) && aligned);
bool constant = (num_distinct == 0);
if (quadratic) {
// If the curve is quadratic pass a normal vector to the caller.
normal = normalize(cross(v01, v12));
return 2;
} else if (linear) {
return 1;
} else {
return 0;
}
}
// https://iquilezles.org/www/articles/bezierbbox/bezierbbox.htm
vec4 bboxBezier(in vec2 p0, in vec2 p1, in vec2 p2) {
vec2 mi = min(p0, p2);
vec2 ma = max(p0, p2);
if (p1.x < mi.x || p1.x > ma.x || p1.y < mi.y || p1.y > ma.y) {
vec2 t = clamp((p0 - p1) / (p0 - 2.0 * p1 + p2), 0.0, 1.0);
vec2 s = 1.0 - t;
vec2 q = s * s * p0 + 2.0 * s * t * p1 + t * t * p2;
mi = min(mi, q);
ma = max(ma, q);
}
return vec4(mi, ma);
}
vec2 convert_to_uv(vec3 x_unit, vec3 y_unit, vec3 point) {
return vec2(dot(point, x_unit), dot(point, y_unit));
}
vec3 convert_from_uv(vec3 translation, vec3 x_unit, vec3 y_unit, vec2 point) {
vec3 untranslated_point = point[0] * x_unit + point[1] * y_unit;
return untranslated_point + translation;
}
void main() {
float thickness_multiplier = 0.004;
v_color = in_color;
vec3 computed_normal;
v_degree = get_degree(current_curve, computed_normal);
vec3 tile_x_unit = normalize(current_curve[2] - current_curve[0]);
vec3 unit_normal;
vec3 tile_y_unit;
if (v_degree == 0) {
tile_y_unit = vec3(0.0, 0.0, 0.0);
} else if (v_degree == 1) {
// Since the curve forms a straight line there's no way to compute a normal.
unit_normal = manim_unit_normal;
tile_y_unit = cross(unit_normal, tile_x_unit);
} else {
// Prefer to use a computed normal vector rather than the one from manim.
unit_normal = computed_normal;
// Ensure tile_y_unit is pointing toward p1 from p0.
tile_y_unit = cross(unit_normal, tile_x_unit);
if (dot(tile_y_unit, current_curve[1] - current_curve[0]) < 0) {
tile_y_unit *= -1;
}
}
// Project the curve onto the tile.
for(int i = 0; i < 3; i++) {
uv_curve[i] = convert_to_uv(tile_x_unit, tile_y_unit, current_curve[i]);
}
// Compute the curve's bounding box.
vec4 uv_bounding_box = bboxBezier(uv_curve[0], uv_curve[1], uv_curve[2]);
vec3 tile_translation = unit_normal * dot(current_curve[0], unit_normal);
vec3 bounding_box_min = convert_from_uv(tile_translation, tile_x_unit, tile_y_unit, uv_bounding_box.xy);
vec3 bounding_box_max = convert_from_uv(tile_translation, tile_x_unit, tile_y_unit, uv_bounding_box.zw);
vec3 bounding_box_vec = bounding_box_max - bounding_box_min;
vec3 tile_origin = bounding_box_min;
vec3 tile_x_vec = tile_x_unit * dot(tile_x_unit, bounding_box_vec);
vec3 tile_y_vec = tile_y_unit * dot(tile_y_unit, bounding_box_vec);
// Expand the tile according to the line's thickness.
v_thickness = thickness_multiplier * in_width;
tile_origin = current_curve[0] - v_thickness * (tile_x_unit + tile_y_unit);
tile_x_vec += 2 * v_thickness * tile_x_unit;
tile_y_vec += 2 * v_thickness * tile_y_unit;
vec3 tile_point = tile_origin + \
tile_coordinate[0] * tile_x_vec + \
tile_coordinate[1] * tile_y_vec;
gl_Position = u_projection_matrix * u_model_view_matrix * vec4(tile_point, 1.0);
uv_point = convert_to_uv(tile_x_unit, tile_y_unit, tile_point);
}

View file

@ -1,13 +1,13 @@
#version 330
uniform mat4 u_model_matrix;
uniform mat4 u_view_matrix;
uniform mat4 u_projection_matrix;
in vec4 in_vert;
in vec4 in_color;
out vec4 v_color;
uniform mat4 model_matrix;
uniform mat4 view_matrix;
uniform mat4 projection_matrix;
void main() {
v_color = in_color;
gl_Position = projection_matrix * view_matrix * model_matrix * in_vert;
gl_Position = u_projection_matrix * u_view_matrix * u_model_matrix * in_vert;
}

View file

@ -0,0 +1,226 @@
import numpy as np
from ..constants import *
from ..mobject.types.opengl_vectorized_mobject import OpenGLVMobject
from ..utils.space_ops import cross2d, earclip_triangulation, z_to_vector
from .shader import Shader
def render_opengl_vectorized_mobject_fill(renderer, mobject):
attributes = np.empty(
0,
dtype=[
("in_vert", np.float32, (3,)),
("in_color", np.float32, (4,)),
("texture_coords", np.float32, (2,)),
("texture_mode", np.int32),
],
)
color = np.empty((0, 4), dtype=np.float32)
for submob in mobject.family_members_with_points():
mobject_triangulation = triangulate_mobject(submob)
mobject_color = np.repeat(
submob.data["fill_rgba"], mobject_triangulation.shape[0], axis=0
)
attributes = np.append(attributes, mobject_triangulation)
color = np.append(color, mobject_color, axis=0)
attributes["in_color"] = color
fill_shader = Shader(
renderer.context,
name="vectorized_mobject_fill",
)
fill_shader.set_uniform(
"u_view_matrix",
renderer.camera.get_view_matrix(),
)
fill_shader.set_uniform(
"u_projection_matrix",
renderer.scene.camera.projection_matrix,
)
vbo = renderer.context.buffer(attributes.tobytes())
vao = renderer.context.simple_vertex_array(
fill_shader.shader_program,
vbo,
*attributes.dtype.names,
)
vao.render()
vao.release()
vbo.release()
def triangulate_mobject(mob):
if not mob.needs_new_triangulation:
return mob.triangulation
# 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
# normal_vector = mob.get_unit_normal()
points = mob.get_points()
b0s = points[0::3]
b1s = points[1::3]
b2s = points[2::3]
v01s = b1s - b0s
v12s = b2s - b1s
crosses = cross2d(v01s, v12s)
convexities = np.sign(crosses)
if mob.orientation == 1:
concave_parts = convexities > 0
convex_parts = convexities <= 0
else:
concave_parts = convexities < 0
convex_parts = convexities >= 0
# These are the vertices to which we'll apply a polygon triangulation
atol = mob.tolerance_for_point_equality
end_of_loop = np.zeros(len(b0s), dtype=bool)
end_of_loop[:-1] = (np.abs(b2s[:-1] - b0s[1:]) > atol).any(1)
end_of_loop[-1] = True
indices = np.arange(len(points), dtype=int)
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)]
bezier_triangle_indices = np.reshape(indices, (-1, 3))
concave_triangle_indices = np.reshape(bezier_triangle_indices[concave_parts], (-1))
convex_triangle_indices = np.reshape(bezier_triangle_indices[convex_parts], (-1))
points = points[
np.hstack(
[
concave_triangle_indices,
convex_triangle_indices,
inner_tri_indices,
]
)
]
texture_coords = np.tile(
[
[0.0, 0.0],
[0.5, 0.0],
[1.0, 1.0],
],
(points.shape[0] // 3, 1),
)
texture_mode = np.hstack(
(
np.ones((concave_triangle_indices.shape[0])),
-1 * np.ones((convex_triangle_indices.shape[0])),
np.zeros((inner_tri_indices.shape[0])),
),
)
attributes = np.zeros(
points.shape[0],
dtype=[
("in_vert", np.float32, (3,)),
("in_color", np.float32, (4,)),
("texture_coords", np.float32, (2,)),
("texture_mode", np.int32),
],
)
attributes["in_vert"] = points
attributes["texture_coords"] = texture_coords
attributes["texture_mode"] = texture_mode
mob.triangulation = attributes
mob.needs_new_triangulation = False
return attributes
def render_opengl_vectorized_mobject_stroke(renderer, mobject):
shader = Shader(renderer.context, "vectorized_mobject_stroke")
shader.set_uniform("u_model_view_matrix", renderer.camera.get_view_matrix())
shader.set_uniform(
"u_projection_matrix",
renderer.scene.camera.projection_matrix,
)
points = np.empty((0, 3))
colors = np.empty((0, 4))
widths = np.empty((0))
for submob in mobject.family_members_with_points():
points = np.append(points, submob.data["points"], axis=0)
colors = np.append(
colors,
np.repeat(
submob.data["stroke_rgba"], submob.data["points"].shape[0], axis=0
),
axis=0,
)
widths = np.append(
widths,
np.repeat(submob.data["stroke_width"], submob.data["points"].shape[0]),
)
stroke_data = np.zeros(
len(points),
dtype=[
# ("previous_curve", np.float32, (3, 3)),
("current_curve", np.float32, (3, 3)),
# ("next_curve", np.float32, (3, 3)),
("tile_coordinate", np.float32, (2,)),
("in_color", np.float32, (4,)),
("in_width", np.float32),
],
)
stroke_data["in_color"] = colors
stroke_data["in_width"] = widths
curves = np.reshape(points, (-1, 3, 3))
# stroke_data["previous_curve"] = np.repeat(np.roll(curves, 1, axis=0), 3, axis=0)
stroke_data["current_curve"] = np.repeat(curves, 3, axis=0)
# stroke_data["next_curve"] = np.repeat(np.roll(curves, -1, axis=0), 3, axis=0)
# Repeat each vertex in order to make a tile.
stroke_data = np.tile(stroke_data, 2)
stroke_data["tile_coordinate"] = np.concatenate(
(
np.tile(
[
[0.0, 0.0],
[0.0, 1.0],
[1.0, 1.0],
],
(len(points) // 3, 1),
),
np.tile(
[
[0.0, 0.0],
[1.0, 0.0],
[1.0, 1.0],
],
(len(points) // 3, 1),
),
),
axis=0,
)
shader.set_uniform("color", tuple(mobject.data["stroke_rgba"][0]))
shader.set_uniform("manim_unit_normal", tuple(-mobject.data["unit_normal"][0]))
vbo = renderer.context.buffer(stroke_data.tobytes())
vao = renderer.context.simple_vertex_array(
shader.shader_program, vbo, *stroke_data.dtype.names
)
renderer.frame_buffer_object.use()
vao.render()
vao.release()
vbo.release()

View file

@ -415,7 +415,6 @@ class Scene:
return self
def add_mobjects_from_animations(self, animations):
curr_mobjects = self.get_mobject_family_members()
for animation in animations:
# Anything animated that's not already in the