From d01658bc5b89cf950e5087909014c90cafbea124 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 13:04:13 -0800 Subject: [PATCH 01/22] Fix multi-color setting --- manimlib/mobject/mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index ea30b932..390355fb 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -1264,7 +1264,7 @@ class Mobject(object): if color is not None: if isinstance(color, list): rgbs = np.array(list(map(color_to_rgb, color))) - resize_with_interpolation(rgbs, len(data)) + rgbs = resize_with_interpolation(rgbs, len(data)) else: rgbs = color_to_rgb(color) data[name][:, :3] = rgbs From 4774d2bc3b34792c7f551bfaebb4230e9b6df779 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 13:29:34 -0800 Subject: [PATCH 02/22] First pass at a winding-based fill approach --- manimlib/camera/camera.py | 68 ++++++++- manimlib/mobject/types/vectorized_mobject.py | 33 ++--- manimlib/shader_wrapper.py | 2 + .../shaders/quadratic_bezier_fill/frag.glsl | 23 +--- .../shaders/quadratic_bezier_fill/geom.glsl | 129 +++++------------- .../shaders/quadratic_bezier_fill/vert.glsl | 10 +- 6 files changed, 128 insertions(+), 137 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index e6f4a76a..43e97d73 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -231,6 +231,7 @@ class Camera(object): self.init_textures() self.init_light_source() self.refresh_perspective_uniforms() + self.init_fill_fbo() # Experimental # A cached map from mobjects to their associated list of render groups # so that these render groups are not regenerated unnecessarily for static # mobjects @@ -254,6 +255,54 @@ class Camera(object): # This is the frame buffer we'll draw into when emitting frames self.draw_fbo = self.get_fbo(samples=0) + def init_fill_fbo(self): + # Experimental + self.fill_texture = self.ctx.texture( + size=self.get_pixel_shape(), + components=4, + samples=self.samples, + ) + fill_depth = self.ctx.depth_renderbuffer(self.get_pixel_shape(), samples=self.samples) + self.fill_fbo = self.ctx.framebuffer(self.fill_texture, fill_depth) + self.fill_prog = self.ctx.program( + vertex_shader=''' + #version 330 + + in vec2 texcoord; + out vec2 v_textcoord; + + void main() { + gl_Position = vec4((2.0 * texcoord - 1.0), 0.0, 1.0); + v_textcoord = texcoord; + } + ''', + fragment_shader=''' + #version 330 + + uniform sampler2D Texture; + + in vec2 v_textcoord; + out vec4 frag_color; + + void main() { + vec4 color = texture(Texture, v_textcoord); + if(color.a == 0) discard; + frag_color = color; + // frag_color = vec4(1, 0, 0, 0.2); + } + ''', + ) + tid = self.n_textures + self.fill_texture.use(tid) + self.fill_prog['Texture'].value = tid + self.n_textures += 1 + verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) + self.fill_vao = self.ctx.simple_vertex_array( + self.fill_prog, + self.ctx.buffer(verts.astype('f4').tobytes()), + 'texcoord', + ) + def set_ctx_blending(self, enable: bool = True) -> None: if enable: self.ctx.enable(moderngl.BLEND) @@ -400,7 +449,24 @@ class Camera(object): self.set_shader_uniforms(shader_program, shader_wrapper) self.set_ctx_depth_test(shader_wrapper.depth_test) self.set_ctx_clip_plane(shader_wrapper.use_clip_plane) - render_group["vao"].render(int(shader_wrapper.render_primitive)) + + # TODO + if shader_wrapper.render_to_texture: + self.fill_fbo.clear(0.0, 0.0, 0.0, 0.0) + self.fill_fbo.use() + self.ctx.enable(moderngl.BLEND) + self.ctx.blend_func = moderngl.ONE, moderngl.ONE + self.ctx.blend_equation = moderngl.FUNC_SUBTRACT + render_group["vao"].render(int(shader_wrapper.render_primitive)) + self.ctx.blend_func = moderngl.DEFAULT_BLENDING + self.ctx.blend_equation = moderngl.FUNC_ADD + self.fbo.use() + self.fill_texture.use(0) + self.fill_prog['Texture'].value = 0 + self.fill_vao.render(moderngl.TRIANGLE_STRIP) + else: + render_group["vao"].render(int(shader_wrapper.render_primitive)) + if render_group["single_use"]: self.release_render_group(render_group) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index d3061ff6..0e9be0bc 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -59,13 +59,12 @@ class VMobject(Mobject): ('stroke_width', np.float32, (1,)), ('joint_product', np.float32, (4,)), ('fill_rgba', np.float32, (4,)), - ('orientation', np.float32, (1,)), - ('vert_index', np.float32, (1,)), + ('base_point', np.float32, (3,)), ]) - fill_data_names = ['point', 'fill_rgba', 'orientation', 'vert_index'] + fill_data_names = ['point', 'fill_rgba', 'base_point'] stroke_data_names = ['point', 'stroke_rgba', 'stroke_width', 'joint_product'] - fill_render_primitive: int = moderngl.TRIANGLES + fill_render_primitive: int = moderngl.TRIANGLE_STRIP stroke_render_primitive: int = moderngl.TRIANGLE_STRIP pre_function_handle_to_anchor_scale_factor: float = 0.01 @@ -992,10 +991,10 @@ class VMobject(Mobject): v12s = points[2::2] - points[1::2] curve_orientations = np.sign(cross2d(v01s, v12s)) - # Reset orientation data - self.data["orientation"][1::2, 0] = curve_orientations - if "orientation" in self.locked_data_keys: - self.locked_data_keys.remove("orientation") + # # Reset orientation data + # self.data["orientation"][1::2, 0] = curve_orientations + # if "orientation" in self.locked_data_keys: + # self.locked_data_keys.remove("orientation") concave_parts = curve_orientations < 0 @@ -1121,14 +1120,6 @@ class VMobject(Mobject): super().set_data(data) return self - def resize_points( - self, - new_length: int, - resize_func: Callable[[np.ndarray, int], np.ndarray] = resize_array - ): - super().resize_points(new_length, resize_func) - self.data["vert_index"][:, 0] = np.arange(new_length) - # TODO, how to be smart about tangents here? @triggers_refreshed_triangulation def apply_function( @@ -1160,10 +1151,10 @@ class VMobject(Mobject): stroke_data = np.zeros(0, dtype=stroke_dtype) self.fill_shader_wrapper = ShaderWrapper( vert_data=fill_data, - vert_indices=np.zeros(0, dtype='i4'), uniforms=self.uniforms, shader_folder=self.fill_shader_folder, render_primitive=self.fill_render_primitive, + render_to_texture=True, ) self.stroke_shader_wrapper = ShaderWrapper( vert_data=stroke_data, @@ -1187,13 +1178,15 @@ class VMobject(Mobject): # Build up data lists fill_datas = [] - fill_indices = [] stroke_datas = [] back_stroke_data = [] for submob in family: if submob.has_fill(): + submob.data["base_point"][:] = submob.data["point"][0] + # submob.data["base_color"][:] = submob.data["fill_color"][0] fill_datas.append(submob.data[fill_names]) - fill_indices.append(submob.get_triangulation()) + # Add dummy + fill_datas.append(submob.data[fill_names][-1:]) if submob.has_stroke(): submob.get_joint_products() if submob.stroke_behind: @@ -1208,7 +1201,7 @@ class VMobject(Mobject): shader_wrappers = [ self.back_stroke_shader_wrapper.read_in(back_stroke_data), - self.fill_shader_wrapper.read_in(fill_datas, fill_indices), + self.fill_shader_wrapper.read_in(fill_datas), self.stroke_shader_wrapper.read_in(stroke_datas), ] diff --git a/manimlib/shader_wrapper.py b/manimlib/shader_wrapper.py index 2d225a62..da11836b 100644 --- a/manimlib/shader_wrapper.py +++ b/manimlib/shader_wrapper.py @@ -34,6 +34,7 @@ class ShaderWrapper(object): depth_test: bool = False, use_clip_plane: bool = False, render_primitive: int = moderngl.TRIANGLE_STRIP, + render_to_texture: bool = False, ): self.vert_data = vert_data self.vert_indices = vert_indices @@ -44,6 +45,7 @@ class ShaderWrapper(object): self.depth_test = depth_test self.use_clip_plane = use_clip_plane self.render_primitive = str(render_primitive) + self.render_to_texture = render_to_texture self.init_program_code() self.refresh_id() diff --git a/manimlib/shaders/quadratic_bezier_fill/frag.glsl b/manimlib/shaders/quadratic_bezier_fill/frag.glsl index e5341459..8a8136b2 100644 --- a/manimlib/shaders/quadratic_bezier_fill/frag.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/frag.glsl @@ -2,29 +2,20 @@ in vec4 color; in float fill_all; // Either 0 or 1 -in float uv_anti_alias_width; in float orientation; in vec2 uv_coords; -in float is_linear; out vec4 frag_color; -float sdf(){ - float x0 = uv_coords.x; - float y0 = uv_coords.y; - if(bool(is_linear)) return abs(y0); - - float Fxy = y0 - x0 * x0; - if(orientation * Fxy >= 0) return 0.0; - - return abs(Fxy) / sqrt(1 + 4 * x0 * x0); -} - - void main() { if (color.a == 0) discard; frag_color = color; - if (bool(fill_all)) return; - frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width); + if (orientation == 0) return; + + float x0 = uv_coords.x; + float y0 = uv_coords.y; + float Fxy = y0 - x0 * x0; + if(orientation * Fxy < 0) discard; + } diff --git a/manimlib/shaders/quadratic_bezier_fill/geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl index 256ac4d5..6550f7ff 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -1,129 +1,68 @@ #version 330 layout (triangles) in; -layout (triangle_strip, max_vertices = 5) out; +layout (triangle_strip, max_vertices = 7) out; uniform float anti_alias_width; uniform float pixel_size; +uniform vec3 corner; in vec3 verts[3]; -in float v_orientation[3]; in vec4 v_color[3]; +in vec3 v_base_point[3]; in float v_vert_index[3]; + out vec4 color; -out float fill_all; -out float uv_anti_alias_width; out float orientation; // uv space is where the curve coincides with y = x^2 out vec2 uv_coords; -out float is_linear; - -const float ANGLE_THRESHOLD = 1e-3; // Analog of import for manim only #INSERT get_gl_Position.glsl -#INSERT get_xyz_to_uv.glsl +#INSERT get_unit_normal.glsl #INSERT finalize_color.glsl -void emit_vertex_wrapper(vec3 point, int index, vec3 unit_normal){ - color = finalize_color(v_color[index], point, unit_normal); +void emit_vertex_wrapper(vec3 point, vec4 v_color, vec3 unit_normal){ + color = finalize_color(v_color, point, unit_normal); gl_Position = get_gl_Position(point); EmitVertex(); } -void emit_simple_triangle(vec3 unit_normal){ - for(int i = 0; i < 3; i++){ - emit_vertex_wrapper(verts[i], i, unit_normal); - } - EndPrimitive(); -} - - -void emit_pentagon( - // Triangle vertices - vec3 p0, - vec3 p1, - vec3 p2, - // Unit tangent vector - vec3 t01, - vec3 t12, - vec3 unit_normal -){ - // Vectors perpendicular to the curve in the plane of the curve - // pointing outside the curve - vec3 p0_perp = cross(t01, unit_normal); - vec3 p2_perp = cross(t12, unit_normal); - - float angle = acos(clamp(dot(t01, t12), -1, 1)); - is_linear = float(angle < ANGLE_THRESHOLD); - - if(bool(is_linear)){ - // Cross with unit z vector - p0_perp = normalize(vec3(-t01.y, t01.x, 0)); - p2_perp = p0_perp; - } - - bool fill_inside = orientation > 0.0; - float aaw = anti_alias_width * pixel_size; - vec3 corners[5] = vec3[5](p0, p0, p1, p2, p2); - - if(fill_inside || bool(is_linear)){ - // Add buffer outside the curve - corners[0] += aaw * p0_perp; - corners[2] += 0.5 * aaw * (p0_perp + p2_perp); - corners[4] += aaw * p2_perp; - } else{ - // Add buffer inside the curve - corners[1] -= aaw * p0_perp; - corners[3] -= aaw * p2_perp; - } - - // Compute xy_to_uv matrix, and potentially re-evaluate bezier degree - bool too_steep; - mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, p2, 10.0, too_steep); - if(too_steep) is_linear = 1.0; - uv_anti_alias_width = aaw * length(xyz_to_uv[0].xyz); - - for(int i = 0; i < 5; i++){ - int j = int[5](0, 0, 1, 2, 2)[i]; - vec3 corner = corners[i]; - uv_coords = (xyz_to_uv * vec4(corner, 1.0)).xy; - emit_vertex_wrapper(corner, j, unit_normal); - } - EndPrimitive(); -} - - void main(){ - // If vert indices are sequential, don't fill all - fill_all = float( - (v_vert_index[1] - v_vert_index[0]) != 1.0 || - (v_vert_index[2] - v_vert_index[1]) != 1.0 - ); + // We use the triangle strip primative, but + // actually only need every other strip element + if (int(v_vert_index[0]) % 2 == 1) return; - vec3 p0 = verts[0]; - vec3 p1 = verts[1]; - vec3 p2 = verts[2]; - vec3 t01 = p1 - p0; - vec3 t12 = p2 - p1; - vec3 unit_normal = normalize(cross(t01, t12)); + // Curves are marked as eneded when the handle after + // the first anchor is set equal to that anchor + if (verts[0] == verts[1]) return; - if(bool(fill_all)){ - emit_simple_triangle(unit_normal); - return; - } - orientation = v_orientation[1]; + vec3 unit_normal = get_unit_normal(verts[0], verts[1], verts[2]); - emit_pentagon( - p0, p1, p2, - normalize(t01), - normalize(t12), - unit_normal - ); + // Emit main triangle + orientation = 0.0; + uv_coords = vec2(0.0); + emit_vertex_wrapper(verts[2], v_color[2], unit_normal); + emit_vertex_wrapper(v_base_point[0], v_color[1], unit_normal); + emit_vertex_wrapper(verts[0], v_color[0], unit_normal); + + // Emit edge triangle + orientation = 1.0; + uv_coords = vec2(0, 0); + // Two dummies + emit_vertex_wrapper(verts[0], v_color[0], unit_normal); + emit_vertex_wrapper(verts[0], v_color[0], unit_normal); + // Inner corner + uv_coords = vec2(0.5, 0); + emit_vertex_wrapper(verts[1], v_color[1], unit_normal); + // Last corner + uv_coords = vec2(1.0, 1.0); + emit_vertex_wrapper(verts[2], v_color[2], unit_normal); + EndPrimitive(); } diff --git a/manimlib/shaders/quadratic_bezier_fill/vert.glsl b/manimlib/shaders/quadratic_bezier_fill/vert.glsl index 3d2e2c9a..8fa8f80f 100644 --- a/manimlib/shaders/quadratic_bezier_fill/vert.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/vert.glsl @@ -2,17 +2,17 @@ in vec3 point; in vec4 fill_rgba; -in float orientation; -in float vert_index; +in vec3 base_point; out vec3 verts; // Bezier control point -out float v_orientation; +out vec4 v_joint_product; out vec4 v_color; +out vec3 v_base_point; out float v_vert_index; void main(){ verts = point; - v_orientation = orientation; v_color = fill_rgba; - v_vert_index = vert_index; + v_base_point = base_point; + v_vert_index = gl_VertexID; } \ No newline at end of file From 6e56c31d67ce0d923adcc316b0179196393d1f95 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 13:49:43 -0800 Subject: [PATCH 03/22] Use gl_InstanceID instead of hacking triangle_strip --- manimlib/camera/camera.py | 5 +- manimlib/mobject/types/vectorized_mobject.py | 1 - .../shaders/quadratic_bezier_fill/frag.glsl | 14 ++---- .../shaders/quadratic_bezier_fill/geom.glsl | 47 +++++++++---------- .../shaders/quadratic_bezier_fill/vert.glsl | 2 + .../shaders/quadratic_bezier_stroke/geom.glsl | 2 +- 6 files changed, 34 insertions(+), 37 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 43e97d73..64d7843d 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -457,7 +457,10 @@ class Camera(object): self.ctx.enable(moderngl.BLEND) self.ctx.blend_func = moderngl.ONE, moderngl.ONE self.ctx.blend_equation = moderngl.FUNC_SUBTRACT - render_group["vao"].render(int(shader_wrapper.render_primitive)) + render_group["vao"].render( + int(shader_wrapper.render_primitive), + instances=2, + ) self.ctx.blend_func = moderngl.DEFAULT_BLENDING self.ctx.blend_equation = moderngl.FUNC_ADD self.fbo.use() diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 0e9be0bc..d2f31ed8 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -1183,7 +1183,6 @@ class VMobject(Mobject): for submob in family: if submob.has_fill(): submob.data["base_point"][:] = submob.data["point"][0] - # submob.data["base_color"][:] = submob.data["fill_color"][0] fill_datas.append(submob.data[fill_names]) # Add dummy fill_datas.append(submob.data[fill_names][-1:]) diff --git a/manimlib/shaders/quadratic_bezier_fill/frag.glsl b/manimlib/shaders/quadratic_bezier_fill/frag.glsl index 8a8136b2..eaf3a8e6 100644 --- a/manimlib/shaders/quadratic_bezier_fill/frag.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/frag.glsl @@ -1,9 +1,7 @@ #version 330 in vec4 color; -in float fill_all; // Either 0 or 1 - -in float orientation; +in float fill_all; in vec2 uv_coords; out vec4 frag_color; @@ -11,11 +9,9 @@ out vec4 frag_color; void main() { if (color.a == 0) discard; frag_color = color; - if (orientation == 0) return; - - float x0 = uv_coords.x; - float y0 = uv_coords.y; - float Fxy = y0 - x0 * x0; - if(orientation * Fxy < 0) discard; + if (bool(fill_all)) return; + float x = uv_coords.x; + float y = uv_coords.y; + if(y - x * x < 0) discard; } diff --git a/manimlib/shaders/quadratic_bezier_fill/geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl index 6550f7ff..25516623 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -1,7 +1,7 @@ #version 330 layout (triangles) in; -layout (triangle_strip, max_vertices = 7) out; +layout (triangle_strip, max_vertices = 3) out; uniform float anti_alias_width; uniform float pixel_size; @@ -11,11 +11,10 @@ in vec3 verts[3]; in vec4 v_color[3]; in vec3 v_base_point[3]; in float v_vert_index[3]; - +in float v_inst_id[3]; out vec4 color; - -out float orientation; +out float fill_all; // uv space is where the curve coincides with y = x^2 out vec2 uv_coords; @@ -38,31 +37,29 @@ void main(){ // actually only need every other strip element if (int(v_vert_index[0]) % 2 == 1) return; - // Curves are marked as eneded when the handle after + // Curves are marked as ended when the handle after // the first anchor is set equal to that anchor if (verts[0] == verts[1]) return; vec3 unit_normal = get_unit_normal(verts[0], verts[1], verts[2]); - // Emit main triangle - orientation = 0.0; - uv_coords = vec2(0.0); - emit_vertex_wrapper(verts[2], v_color[2], unit_normal); - emit_vertex_wrapper(v_base_point[0], v_color[1], unit_normal); - emit_vertex_wrapper(verts[0], v_color[0], unit_normal); - - // Emit edge triangle - orientation = 1.0; - uv_coords = vec2(0, 0); - // Two dummies - emit_vertex_wrapper(verts[0], v_color[0], unit_normal); - emit_vertex_wrapper(verts[0], v_color[0], unit_normal); - // Inner corner - uv_coords = vec2(0.5, 0); - emit_vertex_wrapper(verts[1], v_color[1], unit_normal); - // Last corner - uv_coords = vec2(1.0, 1.0); - emit_vertex_wrapper(verts[2], v_color[2], unit_normal); - EndPrimitive(); + if(int(v_inst_id[0]) % 2 == 0){ + // Emit main triangle + fill_all = float(true); + uv_coords = vec2(0.0); + emit_vertex_wrapper(verts[0], v_color[0], unit_normal); + emit_vertex_wrapper(v_base_point[0], v_color[0], unit_normal); + emit_vertex_wrapper(verts[2], v_color[2], unit_normal); + }else{ + // Emit edge triangle + fill_all = float(false); + uv_coords = vec2(0.0, 0.0); + emit_vertex_wrapper(verts[0], v_color[0], unit_normal); + uv_coords = vec2(0.5, 0); + emit_vertex_wrapper(verts[1], v_color[1], unit_normal); + uv_coords = vec2(1.0, 1.0); + emit_vertex_wrapper(verts[2], v_color[2], unit_normal); + EndPrimitive(); + } } diff --git a/manimlib/shaders/quadratic_bezier_fill/vert.glsl b/manimlib/shaders/quadratic_bezier_fill/vert.glsl index 8fa8f80f..46d677bd 100644 --- a/manimlib/shaders/quadratic_bezier_fill/vert.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/vert.glsl @@ -9,10 +9,12 @@ out vec4 v_joint_product; out vec4 v_color; out vec3 v_base_point; out float v_vert_index; +out float v_inst_id; void main(){ verts = point; v_color = fill_rgba; v_base_point = base_point; v_vert_index = gl_VertexID; + v_inst_id = gl_InstanceID; } \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index ef5029d0..6c9c4a22 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -154,7 +154,7 @@ void main() { // actually only need every other strip element if (int(v_vert_index[0]) % 2 == 1) return; - // Curves are marked as eneded when the handle after + // Curves are marked as ended when the handle after // the first anchor is set equal to that anchor if (verts[0] == verts[1]) return; From 87afdac6a4956a9d7455a3aeb55e65a2cbaae75e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 14:09:41 -0800 Subject: [PATCH 04/22] Small clean up --- manimlib/camera/camera.py | 44 +++++++++++-------- manimlib/mobject/types/vectorized_mobject.py | 2 +- manimlib/shader_wrapper.py | 4 +- .../shaders/quadratic_bezier_fill/geom.glsl | 19 ++++---- 4 files changed, 39 insertions(+), 30 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 64d7843d..191341af 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -297,7 +297,7 @@ class Camera(object): self.fill_prog['Texture'].value = tid self.n_textures += 1 verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) - self.fill_vao = self.ctx.simple_vertex_array( + self.fill_texture_vao = self.ctx.simple_vertex_array( self.fill_prog, self.ctx.buffer(verts.astype('f4').tobytes()), 'texcoord', @@ -446,33 +446,39 @@ class Camera(object): def render(self, render_group: dict[str, Any]) -> None: shader_wrapper = render_group["shader_wrapper"] shader_program = render_group["prog"] + primitive = int(shader_wrapper.render_primitive) self.set_shader_uniforms(shader_program, shader_wrapper) self.set_ctx_depth_test(shader_wrapper.depth_test) self.set_ctx_clip_plane(shader_wrapper.use_clip_plane) - # TODO - if shader_wrapper.render_to_texture: - self.fill_fbo.clear(0.0, 0.0, 0.0, 0.0) - self.fill_fbo.use() - self.ctx.enable(moderngl.BLEND) - self.ctx.blend_func = moderngl.ONE, moderngl.ONE - self.ctx.blend_equation = moderngl.FUNC_SUBTRACT - render_group["vao"].render( - int(shader_wrapper.render_primitive), - instances=2, - ) - self.ctx.blend_func = moderngl.DEFAULT_BLENDING - self.ctx.blend_equation = moderngl.FUNC_ADD - self.fbo.use() - self.fill_texture.use(0) - self.fill_prog['Texture'].value = 0 - self.fill_vao.render(moderngl.TRIANGLE_STRIP) + if shader_wrapper.is_fill: + self.render_fill(render_group["vao"], primitive) else: - render_group["vao"].render(int(shader_wrapper.render_primitive)) + render_group["vao"].render(primitive) if render_group["single_use"]: self.release_render_group(render_group) + def render_fill(self, vao, render_primitive: int): + """ + VMobject fill is handled in a special way, where emited triangles + must be blended with moderngl.FUNC_SUBTRACT so as to effectively compute + a winding number around each pixel. This is rendered to a separate texture, + then that texture is overlayed onto the current fbo + """ + self.fill_fbo.clear(0.0, 0.0, 0.0, 0.0) + self.fill_fbo.use() + self.ctx.enable(moderngl.BLEND) + self.ctx.blend_func = moderngl.ONE, moderngl.ONE + self.ctx.blend_equation = moderngl.FUNC_SUBTRACT + vao.render(render_primitive, instances=2) + self.ctx.blend_func = moderngl.DEFAULT_BLENDING + self.ctx.blend_equation = moderngl.FUNC_ADD + self.fbo.use() + self.fill_texture.use(0) + self.fill_prog['Texture'].value = 0 + self.fill_texture_vao.render(moderngl.TRIANGLE_STRIP) + def get_render_group_list(self, mobject: Mobject) -> Iterable[dict[str, Any]]: if mobject.is_changing(): return self.generate_render_group_list(mobject) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index d2f31ed8..7ad8cd26 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -1154,7 +1154,7 @@ class VMobject(Mobject): uniforms=self.uniforms, shader_folder=self.fill_shader_folder, render_primitive=self.fill_render_primitive, - render_to_texture=True, + is_fill=True, ) self.stroke_shader_wrapper = ShaderWrapper( vert_data=stroke_data, diff --git a/manimlib/shader_wrapper.py b/manimlib/shader_wrapper.py index da11836b..8401349d 100644 --- a/manimlib/shader_wrapper.py +++ b/manimlib/shader_wrapper.py @@ -34,7 +34,7 @@ class ShaderWrapper(object): depth_test: bool = False, use_clip_plane: bool = False, render_primitive: int = moderngl.TRIANGLE_STRIP, - render_to_texture: bool = False, + is_fill: bool = False, ): self.vert_data = vert_data self.vert_indices = vert_indices @@ -45,7 +45,7 @@ class ShaderWrapper(object): self.depth_test = depth_test self.use_clip_plane = use_clip_plane self.render_primitive = str(render_primitive) - self.render_to_texture = render_to_texture + self.is_fill = is_fill self.init_program_code() self.refresh_id() diff --git a/manimlib/shaders/quadratic_bezier_fill/geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl index 25516623..bb808136 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -47,19 +47,22 @@ void main(){ // Emit main triangle fill_all = float(true); uv_coords = vec2(0.0); - emit_vertex_wrapper(verts[0], v_color[0], unit_normal); emit_vertex_wrapper(v_base_point[0], v_color[0], unit_normal); + emit_vertex_wrapper(verts[0], v_color[0], unit_normal); emit_vertex_wrapper(verts[2], v_color[2], unit_normal); }else{ // Emit edge triangle fill_all = float(false); - uv_coords = vec2(0.0, 0.0); - emit_vertex_wrapper(verts[0], v_color[0], unit_normal); - uv_coords = vec2(0.5, 0); - emit_vertex_wrapper(verts[1], v_color[1], unit_normal); - uv_coords = vec2(1.0, 1.0); - emit_vertex_wrapper(verts[2], v_color[2], unit_normal); - EndPrimitive(); + vec2 uv_coords_arr[3] = vec2[3]( + vec2(0.0, 0.0), + vec2(0.5, 0), + vec2(1.0, 1.0) + ); + for(int i = 0; i < 3; i ++){ + uv_coords = uv_coords_arr[i]; + emit_vertex_wrapper(verts[i], v_color[i], unit_normal); + } } + EndPrimitive(); } From 945aa9713f49ff300d7107d1dd7f3c2da63925cb Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 15:02:06 -0800 Subject: [PATCH 05/22] Fix aligned subpaths bug --- manimlib/mobject/types/vectorized_mobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 7ad8cd26..aec56291 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -845,8 +845,8 @@ class VMobject(Mobject): sp2 = self.insert_n_curves_to_point_list(diff2, sp2) if n > 0: # Add intermediate anchor to mark path end - new_subpaths1.append(new_subpaths1[0][-1]) - new_subpaths2.append(new_subpaths2[0][-1]) + new_subpaths1.append(new_subpaths1[-1][-1]) + new_subpaths2.append(new_subpaths2[-1][-1]) new_subpaths1.append(sp1) new_subpaths2.append(sp2) From 88ed1a2fdb3582ca0e30927a1c23debf0a24e314 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 15:28:09 -0800 Subject: [PATCH 06/22] Have init_fill_fbo take in ctx as an argument --- manimlib/camera/camera.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 191341af..2359de06 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -231,7 +231,7 @@ class Camera(object): self.init_textures() self.init_light_source() self.refresh_perspective_uniforms() - self.init_fill_fbo() # Experimental + self.init_fill_fbo(self.ctx) # Experimental # A cached map from mobjects to their associated list of render groups # so that these render groups are not regenerated unnecessarily for static # mobjects @@ -255,16 +255,17 @@ class Camera(object): # This is the frame buffer we'll draw into when emitting frames self.draw_fbo = self.get_fbo(samples=0) - def init_fill_fbo(self): + def init_fill_fbo(self, ctx): # Experimental - self.fill_texture = self.ctx.texture( + self.fill_texture = ctx.texture( size=self.get_pixel_shape(), components=4, samples=self.samples, ) - fill_depth = self.ctx.depth_renderbuffer(self.get_pixel_shape(), samples=self.samples) - self.fill_fbo = self.ctx.framebuffer(self.fill_texture, fill_depth) - self.fill_prog = self.ctx.program( + # TODO, depth buffer is not really used yet + fill_depth = ctx.depth_renderbuffer(self.get_pixel_shape(), samples=self.samples) + self.fill_fbo = ctx.framebuffer(self.fill_texture, fill_depth) + self.fill_prog = ctx.program( vertex_shader=''' #version 330 @@ -285,10 +286,8 @@ class Camera(object): out vec4 frag_color; void main() { - vec4 color = texture(Texture, v_textcoord); - if(color.a == 0) discard; - frag_color = color; - // frag_color = vec4(1, 0, 0, 0.2); + frag_color = texture(Texture, v_textcoord); + if(frag_color.a == 0) discard; } ''', ) @@ -297,9 +296,9 @@ class Camera(object): self.fill_prog['Texture'].value = tid self.n_textures += 1 verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) - self.fill_texture_vao = self.ctx.simple_vertex_array( + self.fill_texture_vao = ctx.simple_vertex_array( self.fill_prog, - self.ctx.buffer(verts.astype('f4').tobytes()), + ctx.buffer(verts.astype('f4').tobytes()), 'texcoord', ) From b93e284695ceb6e44637fa265b4cacc94b599183 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 15:29:09 -0800 Subject: [PATCH 07/22] In aligning families, scale inserted submobjects to 0 --- manimlib/mobject/mobject.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 390355fb..b2738c1b 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -1673,12 +1673,7 @@ class Mobject(object): for submob, sf in zip(self.submobjects, split_factors): new_submobs.append(submob) for k in range(1, sf): - new_submob = submob.copy() - # If the submobject is at all transparent, then - # make the copy completely transparent - if submob.get_opacity() < 1: - new_submob.set_opacity(0) - new_submobs.append(new_submob) + new_submobs.append(submob.copy().scale(0)) self.set_submobjects(new_submobs) return self From 516fe9155e4f5c3db0b5d13de6afd653fbe0cb29 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 15:29:35 -0800 Subject: [PATCH 08/22] Small tweaks --- manimlib/shaders/quadratic_bezier_fill/geom.glsl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/manimlib/shaders/quadratic_bezier_fill/geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl index bb808136..a4115013 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -41,18 +41,21 @@ void main(){ // the first anchor is set equal to that anchor if (verts[0] == verts[1]) return; + if (v_color[0].a == 0 && v_color[1].a == 0 && v_color[2].a == 0) return; + vec3 unit_normal = get_unit_normal(verts[0], verts[1], verts[2]); if(int(v_inst_id[0]) % 2 == 0){ // Emit main triangle - fill_all = float(true); + fill_all = 1.0; uv_coords = vec2(0.0); emit_vertex_wrapper(v_base_point[0], v_color[0], unit_normal); emit_vertex_wrapper(verts[0], v_color[0], unit_normal); emit_vertex_wrapper(verts[2], v_color[2], unit_normal); }else{ // Emit edge triangle - fill_all = float(false); + fill_all = 0.0; + // A quadratic bezier curve with these points coincides with y = x^2 vec2 uv_coords_arr[3] = vec2[3]( vec2(0.0, 0.0), vec2(0.5, 0), From 72da9786a32b4f2f02017bd98e39a1fb204a79bf Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 15:53:43 -0800 Subject: [PATCH 09/22] Use null array for vert indices in place of None --- manimlib/camera/camera.py | 2 +- manimlib/mobject/mobject.py | 2 +- manimlib/shader_wrapper.py | 17 ++++++++--------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 2359de06..2711a43a 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -502,7 +502,7 @@ class Camera(object): # Data buffer vert_data = shader_wrapper.vert_data indices = shader_wrapper.vert_indices - if indices is None: + if len(indices) == 0: ibo = None elif single_use: ibo = self.ctx.buffer(indices.astype(np.uint32)) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index b2738c1b..96a7018b 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -1841,7 +1841,7 @@ class Mobject(object): def init_shader_data(self): # TODO, only call this when needed? - self.shader_indices = None + self.shader_indices = np.zeros(0) self.shader_wrapper = ShaderWrapper( vert_data=self.data, shader_folder=self.shader_folder, diff --git a/manimlib/shader_wrapper.py b/manimlib/shader_wrapper.py index 8401349d..f3159a5d 100644 --- a/manimlib/shader_wrapper.py +++ b/manimlib/shader_wrapper.py @@ -37,7 +37,7 @@ class ShaderWrapper(object): is_fill: bool = False, ): self.vert_data = vert_data - self.vert_indices = vert_indices + self.vert_indices = vert_indices or np.zeros(0) self.vert_attributes = vert_data.dtype.names self.shader_folder = shader_folder self.uniforms = uniforms or dict() @@ -68,9 +68,8 @@ class ShaderWrapper(object): def copy(self): result = copy.copy(self) - result.vert_data = np.array(self.vert_data) - if result.vert_indices is not None: - result.vert_indices = np.array(self.vert_indices) + result.vert_data = self.vert_data.copy() + result.vert_indices = self.vert_indices.copy() if self.uniforms: result.uniforms = {key: np.array(value) for key, value in self.uniforms.items()} if self.texture_paths: @@ -136,10 +135,7 @@ class ShaderWrapper(object): def combine_with(self, *shader_wrappers: ShaderWrapper) -> ShaderWrapper: if len(shader_wrappers) > 0: data_list = [self.vert_data, *(sw.vert_data for sw in shader_wrappers)] - if self.vert_indices is not None: - indices_list = [self.vert_indices, *(sw.vert_indices for sw in shader_wrappers)] - else: - indices_list = None + indices_list = [self.vert_indices, *(sw.vert_indices for sw in shader_wrappers)] self.read_in(data_list, indices_list) return self @@ -157,10 +153,13 @@ class ShaderWrapper(object): # Stack the data np.concatenate(data_list, out=self.vert_data) - if indices_list is None or self.vert_indices is None: + if indices_list is None: return self total_verts = sum(len(vi) for vi in indices_list) + if total_verts == 0: + return self + self.vert_indices = resize_array(self.vert_indices, total_verts) # Stack vert_indices, but adding the appropriate offset From e9c70dbfd9b737ea501da7ad268e0618ef2404af Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 16:58:47 -0800 Subject: [PATCH 10/22] Ensure vert_indices are always of type int --- manimlib/shader_wrapper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manimlib/shader_wrapper.py b/manimlib/shader_wrapper.py index f3159a5d..8d867128 100644 --- a/manimlib/shader_wrapper.py +++ b/manimlib/shader_wrapper.py @@ -37,7 +37,7 @@ class ShaderWrapper(object): is_fill: bool = False, ): self.vert_data = vert_data - self.vert_indices = vert_indices or np.zeros(0) + self.vert_indices = (vert_indices or np.zeros(0)).astype(int) self.vert_attributes = vert_data.dtype.names self.shader_folder = shader_folder self.uniforms = uniforms or dict() @@ -154,6 +154,7 @@ class ShaderWrapper(object): np.concatenate(data_list, out=self.vert_data) if indices_list is None: + self.vert_indices = resize_array(self.vert_indices, 0) return self total_verts = sum(len(vi) for vi in indices_list) From f0df5c759d84c6da2233c5d3a3010deb189208e3 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 20:03:23 -0800 Subject: [PATCH 11/22] Make winding fill optional, and make winding additive rather than toggling --- manimlib/camera/camera.py | 36 +++++---- manimlib/mobject/types/vectorized_mobject.py | 40 ++++++---- .../shaders/quadratic_bezier_fill/frag.glsl | 10 ++- .../shaders/quadratic_bezier_fill/geom.glsl | 77 +++++++++++-------- .../shaders/quadratic_bezier_fill/vert.glsl | 3 - 5 files changed, 100 insertions(+), 66 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 2711a43a..d326ffdd 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -255,12 +255,15 @@ class Camera(object): # This is the frame buffer we'll draw into when emitting frames self.draw_fbo = self.get_fbo(samples=0) - def init_fill_fbo(self, ctx): + def init_fill_fbo(self, ctx: moderngl.context.Context): # Experimental self.fill_texture = ctx.texture( size=self.get_pixel_shape(), components=4, samples=self.samples, + # Important to make sure floating point (not fixed point) is + # used so that alpha values are not clipped + dtype='f2', ) # TODO, depth buffer is not really used yet fill_depth = ctx.depth_renderbuffer(self.get_pixel_shape(), samples=self.samples) @@ -287,6 +290,7 @@ class Camera(object): void main() { frag_color = texture(Texture, v_textcoord); + frag_color = abs(frag_color); if(frag_color.a == 0) discard; } ''', @@ -451,28 +455,31 @@ class Camera(object): self.set_ctx_clip_plane(shader_wrapper.use_clip_plane) if shader_wrapper.is_fill: - self.render_fill(render_group["vao"], primitive) + self.render_fill(render_group["vao"], primitive, shader_wrapper.vert_indices) else: render_group["vao"].render(primitive) if render_group["single_use"]: self.release_render_group(render_group) - def render_fill(self, vao, render_primitive: int): + def render_fill(self, vao, render_primitive: int, indices: np.ndarray): """ VMobject fill is handled in a special way, where emited triangles must be blended with moderngl.FUNC_SUBTRACT so as to effectively compute a winding number around each pixel. This is rendered to a separate texture, then that texture is overlayed onto the current fbo """ + winding = (len(indices) == 0) + vao.program['winding'].value = winding + if not winding: + vao.render(moderngl.TRIANGLES) + return self.fill_fbo.clear(0.0, 0.0, 0.0, 0.0) self.fill_fbo.use() self.ctx.enable(moderngl.BLEND) self.ctx.blend_func = moderngl.ONE, moderngl.ONE - self.ctx.blend_equation = moderngl.FUNC_SUBTRACT - vao.render(render_primitive, instances=2) + vao.render(render_primitive) self.ctx.blend_func = moderngl.DEFAULT_BLENDING - self.ctx.blend_equation = moderngl.FUNC_ADD self.fbo.use() self.fill_texture.use(0) self.fill_prog['Texture'].value = 0 @@ -507,14 +514,15 @@ class Camera(object): elif single_use: ibo = self.ctx.buffer(indices.astype(np.uint32)) else: - # The vao.render call is strangely longer - # when an index buffer is used, so if the - # mobject is not changing, meaning only its - # uniforms are being updated, just create - # a larger data array based on the indices - # and don't bother with the ibo - vert_data = vert_data[indices] - ibo = None + ibo = self.ctx.buffer(indices.astype(np.uint32)) + # # The vao.render call is strangely longer + # # when an index buffer is used, so if the + # # mobject is not changing, meaning only its + # # uniforms are being updated, just create + # # a larger data array based on the indices + # # and don't bother with the ibo + # vert_data = vert_data[indices] + # ibo = None vbo = self.ctx.buffer(vert_data) # Program and vertex array diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index aec56291..4d3102fd 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -103,6 +103,7 @@ class VMobject(Mobject): self.flat_stroke = flat_stroke self.use_simple_quadratic_approx = use_simple_quadratic_approx self.anti_alias_width = anti_alias_width + self._use_winding_fill = True self.needs_new_triangulation = True self.triangulation = np.zeros(0, dtype='i4') @@ -128,8 +129,6 @@ class VMobject(Mobject): return super().family_members_with_points() def replicate(self, n: int) -> VGroup: - if self.has_fill(): - self.get_triangulation() return super().replicate(n) def get_grid(self, *args, **kwargs) -> VGroup: @@ -401,6 +400,11 @@ class VMobject(Mobject): def get_joint_type(self) -> float: return self.uniforms["joint_type"] + def use_winding_fill(self, value: bool = True, recurse: bool = True): + for submob in self.get_family(recurse): + submob._use_winding_fill = value + return self + # Points def set_anchors_and_handles( self, @@ -807,11 +811,15 @@ class VMobject(Mobject): # Alignment def align_points(self, vmobject: VMobject): + winding = self._use_winding_fill and vmobject._use_winding_fill + self.use_winding_fill(winding) + vmobject.use_winding_fill(winding) if self.get_num_points() == len(vmobject.get_points()): # If both have fill, and they have the same shape, just # give them the same triangulation so that it's not recalculated # needlessly throughout an animation - if self.has_fill() and vmobject.has_fill() and self.has_same_shape_as(vmobject): + if self._use_winding_fill and self.has_fill() \ + and vmobject.has_fill() and self.has_same_shape_as(vmobject): vmobject.triangulation = self.triangulation return self @@ -898,7 +906,7 @@ class VMobject(Mobject): *args, **kwargs ): super().interpolate(mobject1, mobject2, alpha, *args, **kwargs) - if self.has_fill(): + if self.has_fill() and not self._use_winding_fill: tri1 = mobject1.get_triangulation() tri2 = mobject2.get_triangulation() if not arrays_match(tri1, tri2): @@ -991,11 +999,6 @@ class VMobject(Mobject): v12s = points[2::2] - points[1::2] curve_orientations = np.sign(cross2d(v01s, v12s)) - # # Reset orientation data - # self.data["orientation"][1::2, 0] = curve_orientations - # if "orientation" in self.locked_data_keys: - # self.locked_data_keys.remove("orientation") - concave_parts = curve_orientations < 0 # These are the vertices to which we'll apply a polygon triangulation @@ -1108,10 +1111,11 @@ class VMobject(Mobject): def reverse_points(self): # This will reset which anchors are # considered path ends - if not self.has_points(): - return self - inner_ends = self.get_subpath_end_indices()[:-1] - self.data["point"][inner_ends + 1] = self.data["point"][inner_ends + 2] + for mob in self.get_family(): + if not mob.has_points(): + continue + inner_ends = mob.get_subpath_end_indices()[:-1] + mob.data["point"][inner_ends + 1] = mob.data["point"][inner_ends + 2] super().reverse_points() return self @@ -1178,14 +1182,18 @@ class VMobject(Mobject): # Build up data lists fill_datas = [] + fill_indices = [] stroke_datas = [] back_stroke_data = [] for submob in family: if submob.has_fill(): submob.data["base_point"][:] = submob.data["point"][0] fill_datas.append(submob.data[fill_names]) - # Add dummy - fill_datas.append(submob.data[fill_names][-1:]) + if self._use_winding_fill: + # Add dummy + fill_datas.append(submob.data[fill_names][-1:]) + else: + fill_indices.append(submob.get_triangulation()) if submob.has_stroke(): submob.get_joint_products() if submob.stroke_behind: @@ -1200,7 +1208,7 @@ class VMobject(Mobject): shader_wrappers = [ self.back_stroke_shader_wrapper.read_in(back_stroke_data), - self.fill_shader_wrapper.read_in(fill_datas), + self.fill_shader_wrapper.read_in(fill_datas, fill_indices or None), self.stroke_shader_wrapper.read_in(stroke_datas), ] diff --git a/manimlib/shaders/quadratic_bezier_fill/frag.glsl b/manimlib/shaders/quadratic_bezier_fill/frag.glsl index eaf3a8e6..c672455a 100644 --- a/manimlib/shaders/quadratic_bezier_fill/frag.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/frag.glsl @@ -1,7 +1,10 @@ #version 330 +uniform bool winding; + in vec4 color; in float fill_all; +in float orientation; in vec2 uv_coords; out vec4 frag_color; @@ -9,9 +12,14 @@ out vec4 frag_color; void main() { if (color.a == 0) discard; frag_color = color; + + if(winding && orientation > 0) frag_color *= -1; + if (bool(fill_all)) return; float x = uv_coords.x; float y = uv_coords.y; - if(y - x * x < 0) discard; + float Fxy = (y - x * x); + if(!winding && orientation > 0) Fxy *= -1; + if(Fxy < 0) discard; } diff --git a/manimlib/shaders/quadratic_bezier_fill/geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl index a4115013..95d18296 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -1,23 +1,27 @@ #version 330 layout (triangles) in; -layout (triangle_strip, max_vertices = 3) out; +layout (triangle_strip, max_vertices = 6) out; -uniform float anti_alias_width; -uniform float pixel_size; -uniform vec3 corner; +uniform bool winding; in vec3 verts[3]; in vec4 v_color[3]; in vec3 v_base_point[3]; in float v_vert_index[3]; -in float v_inst_id[3]; out vec4 color; out float fill_all; +out float orientation; // uv space is where the curve coincides with y = x^2 out vec2 uv_coords; +// A quadratic bezier curve with these points coincides with y = x^2 +const vec2 SIMPLE_QUADRATIC[3] = vec2[3]( + vec2(0.0, 0.0), + vec2(0.5, 0), + vec2(1.0, 1.0) +); // Analog of import for manim only #INSERT get_gl_Position.glsl @@ -25,47 +29,56 @@ out vec2 uv_coords; #INSERT finalize_color.glsl -void emit_vertex_wrapper(vec3 point, vec4 v_color, vec3 unit_normal){ - color = finalize_color(v_color, point, unit_normal); - gl_Position = get_gl_Position(point); - EmitVertex(); +void emit_triangle(vec3 points[3], vec4 v_color[3]){ + vec3 unit_normal = get_unit_normal(points[0], points[1], points[2]); + orientation = sign(unit_normal.z); + + for(int i = 0; i < 3; i++){ + uv_coords = SIMPLE_QUADRATIC[i]; + color = finalize_color(v_color[i], points[i], unit_normal); + gl_Position = get_gl_Position(points[i]); + EmitVertex(); + } + EndPrimitive(); +} + + +void emit_in_triangle(){ + emit_triangle( + vec3[3](verts[0], verts[1], verts[2]), + vec4[3](v_color[0], v_color[1], v_color[2]) + ); } void main(){ // We use the triangle strip primative, but // actually only need every other strip element - if (int(v_vert_index[0]) % 2 == 1) return; + if (winding && int(v_vert_index[0]) % 2 == 1) return; // Curves are marked as ended when the handle after // the first anchor is set equal to that anchor if (verts[0] == verts[1]) return; - if (v_color[0].a == 0 && v_color[1].a == 0 && v_color[2].a == 0) return; - - vec3 unit_normal = get_unit_normal(verts[0], verts[1], verts[2]); - - if(int(v_inst_id[0]) % 2 == 0){ + vec3 mid_vert; + if(winding){ // Emit main triangle fill_all = 1.0; - uv_coords = vec2(0.0); - emit_vertex_wrapper(v_base_point[0], v_color[0], unit_normal); - emit_vertex_wrapper(verts[0], v_color[0], unit_normal); - emit_vertex_wrapper(verts[2], v_color[2], unit_normal); - }else{ - // Emit edge triangle - fill_all = 0.0; - // A quadratic bezier curve with these points coincides with y = x^2 - vec2 uv_coords_arr[3] = vec2[3]( - vec2(0.0, 0.0), - vec2(0.5, 0), - vec2(1.0, 1.0) + emit_triangle( + vec3[3](v_base_point[0], verts[0], verts[2]), + vec4[3](v_color[1], v_color[0], v_color[2]) ); - for(int i = 0; i < 3; i ++){ - uv_coords = uv_coords_arr[i]; - emit_vertex_wrapper(verts[i], v_color[i], unit_normal); - } + // Edge triangle + fill_all = 0.0; + emit_in_triangle(); + }else{ + // In this case, one should fill all if the vertices are + // not in sequential order + fill_all = float( + (v_vert_index[1] - v_vert_index[0]) != 1.0 || + (v_vert_index[2] - v_vert_index[1]) != 1.0 + ); + emit_in_triangle(); } - EndPrimitive(); } diff --git a/manimlib/shaders/quadratic_bezier_fill/vert.glsl b/manimlib/shaders/quadratic_bezier_fill/vert.glsl index 46d677bd..5818216a 100644 --- a/manimlib/shaders/quadratic_bezier_fill/vert.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/vert.glsl @@ -5,16 +5,13 @@ in vec4 fill_rgba; in vec3 base_point; out vec3 verts; // Bezier control point -out vec4 v_joint_product; out vec4 v_color; out vec3 v_base_point; out float v_vert_index; -out float v_inst_id; void main(){ verts = point; v_color = fill_rgba; v_base_point = base_point; v_vert_index = gl_VertexID; - v_inst_id = gl_InstanceID; } \ No newline at end of file From 98eccab977d8edbe976d7c5457a10bae3e2f782b Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 20:03:33 -0800 Subject: [PATCH 12/22] Ensure background rectangle matches orientation --- manimlib/mobject/types/vectorized_mobject.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 4d3102fd..dca08d69 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -142,6 +142,19 @@ class VMobject(Mobject): raise Exception("All submobjects must be of type VMobject") super().add(*vmobjects) + def add_background_rectangle( + self, + color: ManimColor | None = None, + opacity: float = 0.75, + **kwargs + ): + normal = self.family_members_with_points()[0].get_unit_normal() + super().add_background_rectangle(color, opacity, **kwargs) + rect = self.background_rectangle + if np.dot(rect.get_unit_normal(), normal) < 0: + rect.reverse_points() + return self + # Colors def init_colors(self): self.set_fill( From 307487e087fd285644ab14c50b69896fdfd9be46 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 21:10:26 -0800 Subject: [PATCH 13/22] Don't pre-normalize joint_products --- manimlib/mobject/types/vectorized_mobject.py | 5 +++-- .../shaders/quadratic_bezier_stroke/geom.glsl | 20 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index dca08d69..5ff9431b 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -875,6 +875,7 @@ class VMobject(Mobject): new_points = np.vstack(paths) mob.resize_points(len(new_points), resize_func=resize_preserving_order) mob.set_points(new_points) + mob.get_joint_products() return self def insert_n_curves(self, n: int, recurse: bool = True): @@ -1069,8 +1070,8 @@ class VMobject(Mobject): # Find all the unit tangent vectors at each joint a0, h, a1 = points[0:-1:2], points[1::2], points[2::2] - a0_to_h = normalize_along_axis(h - a0, 1) - h_to_a1 = normalize_along_axis(a1 - h, 1) + a0_to_h = h - a0 + h_to_a1 = a1 - h vect_to_vert = np.zeros(points.shape) vect_from_vert = np.zeros(points.shape) diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 6c9c4a22..1364ae60 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -99,13 +99,17 @@ void get_corners( float buff0 = 0.5 * v_stroke_width[0] + aaw; float buff2 = 0.5 * v_stroke_width[2] + aaw; + vec4 jp0 = normalize(v_joint_product[0]); + vec4 jp1 = normalize(v_joint_product[1]); + vec4 jp2 = normalize(v_joint_product[2]); + // Add correction for sharp angles to prevent weird bevel effects - if(v_joint_product[0].w < -0.9) buff0 *= 10 * (v_joint_product[0].w + 1.0); - if(v_joint_product[2].w < -0.9) buff2 *= 10 * (v_joint_product[2].w + 1.0); + if(jp0.w < -0.9) buff0 *= 10 * (jp0.w + 1.0); + if(jp2.w < -0.9) buff2 *= 10 * (jp2.w + 1.0); // Unit normal and joint angles - vec3 normal0 = get_joint_unit_normal(v_joint_product[0]); - vec3 normal2 = get_joint_unit_normal(v_joint_product[2]); + vec3 normal0 = get_joint_unit_normal(jp0); + vec3 normal2 = get_joint_unit_normal(jp2); // Set global unit normal unit_normal = normal0; @@ -136,14 +140,14 @@ void get_corners( vec3 c5 = p2 - p2_perp; // Move the inner middle control point to make // room for the curve - float orientation = dot(normal0, v_joint_product[1].xyz); + float orientation = dot(normal0, jp1.xyz); if(orientation >= 0.0) c2 = 0.5 * (c0 + c4); else if(orientation < 0.0) c3 = 0.5 * (c1 + c5); // Account for previous and next control points if(bool(flat_stroke)){ - create_joint(v_joint_product[0], v01, buff0, c1, c1, c0, c0); - create_joint(v_joint_product[2], -v12, buff2, c5, c5, c4, c4); + create_joint(jp0, v01, buff0, c1, c1, c0, c0); + create_joint(jp2, -v12, buff2, c5, c5, c4, c4); } corners = vec3[6](c0, c1, c2, c3, c4, c5); @@ -164,7 +168,7 @@ void main() { vec3 v01 = normalize(p1 - p0); vec3 v12 = normalize(p2 - p1); - float cos_angle = v_joint_product[1].w; + float cos_angle = normalize(v_joint_product[1]).w; is_linear = float(cos_angle > COS_THRESHOLD); // We want to change the coordinates to a space where the curve From 088a2f65a3f80743a513e53d94042b9c202cd1ef Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 21:10:57 -0800 Subject: [PATCH 14/22] Misc. clean up --- manimlib/camera/camera.py | 16 +++++++++------- manimlib/shaders/quadratic_bezier_fill/geom.glsl | 6 +++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index d326ffdd..79a2dbed 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -257,16 +257,16 @@ class Camera(object): def init_fill_fbo(self, ctx: moderngl.context.Context): # Experimental + size = self.get_pixel_shape() self.fill_texture = ctx.texture( - size=self.get_pixel_shape(), + size=size, components=4, - samples=self.samples, # Important to make sure floating point (not fixed point) is # used so that alpha values are not clipped dtype='f2', ) # TODO, depth buffer is not really used yet - fill_depth = ctx.depth_renderbuffer(self.get_pixel_shape(), samples=self.samples) + fill_depth = ctx.depth_renderbuffer(size) self.fill_fbo = ctx.framebuffer(self.fill_texture, fill_depth) self.fill_prog = ctx.program( vertex_shader=''' @@ -292,9 +292,11 @@ class Camera(object): frag_color = texture(Texture, v_textcoord); frag_color = abs(frag_color); if(frag_color.a == 0) discard; + //TODO, set gl_FragDepth; } ''', ) + tid = self.n_textures self.fill_texture.use(tid) self.fill_prog['Texture'].value = tid @@ -476,13 +478,13 @@ class Camera(object): return self.fill_fbo.clear(0.0, 0.0, 0.0, 0.0) self.fill_fbo.use() - self.ctx.enable(moderngl.BLEND) - self.ctx.blend_func = moderngl.ONE, moderngl.ONE + self.ctx.blend_func = ( + moderngl.ONE, moderngl.ZERO, + moderngl.ONE, moderngl.ONE, + ) vao.render(render_primitive) self.ctx.blend_func = moderngl.DEFAULT_BLENDING self.fbo.use() - self.fill_texture.use(0) - self.fill_prog['Texture'].value = 0 self.fill_texture_vao.render(moderngl.TRIANGLE_STRIP) def get_render_group_list(self, mobject: Mobject) -> Iterable[dict[str, Any]]: diff --git a/manimlib/shaders/quadratic_bezier_fill/geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl index 95d18296..fae7998b 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -43,7 +43,7 @@ void emit_triangle(vec3 points[3], vec4 v_color[3]){ } -void emit_in_triangle(){ +void emit_simple_triangle(){ emit_triangle( vec3[3](verts[0], verts[1], verts[2]), vec4[3](v_color[0], v_color[1], v_color[2]) @@ -70,7 +70,7 @@ void main(){ ); // Edge triangle fill_all = 0.0; - emit_in_triangle(); + emit_simple_triangle(); }else{ // In this case, one should fill all if the vertices are // not in sequential order @@ -78,7 +78,7 @@ void main(){ (v_vert_index[1] - v_vert_index[0]) != 1.0 || (v_vert_index[2] - v_vert_index[1]) != 1.0 ); - emit_in_triangle(); + emit_simple_triangle(); } } From 346d252451fe53c7fa52fda09e8c04fc1f6fea2c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 21:33:00 -0800 Subject: [PATCH 15/22] Don't save triangulation, but do orient svg paths positively --- manimlib/mobject/svg/svg_mobject.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index ce782933..6ca56596 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -29,7 +29,7 @@ if TYPE_CHECKING: SVG_HASH_TO_MOB_MAP: dict[int, list[VMobject]] = {} -PATH_TO_POINTS: dict[str, Tuple[Vect3Array, np.ndarray]] = {} +PATH_TO_POINTS: dict[str, Vect3Array] = {} def _convert_point_to_3d(x: float, y: float) -> np.ndarray: @@ -320,16 +320,14 @@ class VMobjectFromSVGPath(VMobject): self.set_points(self.get_points_without_null_curves()) # So triangulation doesn't get messed up self.subdivide_intersections() + # Always default to orienting outward + if self.get_unit_normal()[2] < 0: + self.reverse_points() # Save for future use - PATH_TO_POINTS[path_string] = ( - self.get_points().copy(), - self.get_triangulation().copy() - ) + PATH_TO_POINTS[path_string] = self.get_points().copy() else: - points, triangulation = PATH_TO_POINTS[path_string] + points = PATH_TO_POINTS[path_string] self.set_points(points) - self.triangulation = triangulation - self.needs_new_triangulation = False def handle_commands(self) -> None: segment_class_to_func_map = { From 0e2d21bed3df1a3ee47565798178826d028df179 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 21:46:34 -0800 Subject: [PATCH 16/22] Don't necessarily use VGroup with FadeTransform --- manimlib/animation/fading.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/manimlib/animation/fading.py b/manimlib/animation/fading.py index c8c29065..3ea17ade 100644 --- a/manimlib/animation/fading.py +++ b/manimlib/animation/fading.py @@ -103,12 +103,8 @@ class FadeTransform(Transform): self.stretch = stretch self.dim_to_match = dim_to_match - group_type = Group - if isinstance(mobject, VMobject) and isinstance(target_mobject, VMobject): - group_type = VGroup - mobject.save_state() - super().__init__(group_type(mobject, target_mobject.copy()), **kwargs) + super().__init__(Group(mobject, target_mobject.copy()), **kwargs) def begin(self) -> None: self.ending_mobject = self.mobject.copy() From 272925fa19e9014913e6b21606051a4d06132ea4 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 21:46:52 -0800 Subject: [PATCH 17/22] Change winding fill blend_func --- manimlib/camera/camera.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 79a2dbed..7a18ecbe 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -479,7 +479,7 @@ class Camera(object): self.fill_fbo.clear(0.0, 0.0, 0.0, 0.0) self.fill_fbo.use() self.ctx.blend_func = ( - moderngl.ONE, moderngl.ZERO, + moderngl.ONE, moderngl.ONE, moderngl.ONE, moderngl.ONE, ) vao.render(render_primitive) From 6cf8c8d2e8f42789c7f7b412115a44d221efc790 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 24 Jan 2023 21:47:12 -0800 Subject: [PATCH 18/22] Do refresh in pointwise_become_partial --- manimlib/mobject/types/vectorized_mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index 5ff9431b..cf9eed5b 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -931,7 +931,7 @@ class VMobject(Mobject): assert(isinstance(vmobject, VMobject)) vm_points = vmobject.get_points() if a <= 0 and b >= 1: - self.set_points(vm_points, refresh=False) + self.set_points(vm_points) return self num_curves = vmobject.get_num_curves() From 4cb9c9c2fc930af55181ded6aa73f945872c3322 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 25 Jan 2023 08:18:10 -0800 Subject: [PATCH 19/22] Remove unnecessary normalize --- manimlib/shaders/quadratic_bezier_stroke/geom.glsl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 1364ae60..96e3f1b2 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -100,7 +100,6 @@ void get_corners( float buff2 = 0.5 * v_stroke_width[2] + aaw; vec4 jp0 = normalize(v_joint_product[0]); - vec4 jp1 = normalize(v_joint_product[1]); vec4 jp2 = normalize(v_joint_product[2]); // Add correction for sharp angles to prevent weird bevel effects @@ -140,7 +139,7 @@ void get_corners( vec3 c5 = p2 - p2_perp; // Move the inner middle control point to make // room for the curve - float orientation = dot(normal0, jp1.xyz); + float orientation = dot(normal0, v_joint_product[1].xyz); if(orientation >= 0.0) c2 = 0.5 * (c0 + c4); else if(orientation < 0.0) c3 = 0.5 * (c1 + c5); From 93dd9f687b50c8f484a7362076429a5a9ac9d73c Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 25 Jan 2023 09:50:16 -0800 Subject: [PATCH 20/22] Ensure align_family works well with VMobject fill --- manimlib/mobject/mobject.py | 5 ++++- manimlib/mobject/types/vectorized_mobject.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 96a7018b..874c81a1 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -1673,10 +1673,13 @@ class Mobject(object): for submob, sf in zip(self.submobjects, split_factors): new_submobs.append(submob) for k in range(1, sf): - new_submobs.append(submob.copy().scale(0)) + new_submobs.append(submob.invisible_copy()) self.set_submobjects(new_submobs) return self + def invisible_copy(self): + return self.copy().set_opacity(0) + # Interpolate def interpolate( diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index cf9eed5b..dca1e321 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -878,6 +878,12 @@ class VMobject(Mobject): mob.get_joint_products() return self + def invisible_copy(self): + result = self.copy() + result.append_vectorized_mobject(self.copy().reverse_points()) + result.set_opacity(0) + return result + def insert_n_curves(self, n: int, recurse: bool = True): for mob in self.get_family(recurse): if mob.get_num_curves() > 0: @@ -1107,6 +1113,7 @@ class VMobject(Mobject): if refresh: self.refresh_triangulation() self.refresh_joint_products() + return self return wrapper @triggers_refreshed_triangulation From 7deaf4cb114734f0125bea8ae2b7a72da3bd6233 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 25 Jan 2023 09:51:27 -0800 Subject: [PATCH 21/22] Small clean up --- manimlib/camera/camera.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 7a18ecbe..b5bbc2cc 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -476,12 +476,9 @@ class Camera(object): if not winding: vao.render(moderngl.TRIANGLES) return - self.fill_fbo.clear(0.0, 0.0, 0.0, 0.0) + self.fill_fbo.clear() self.fill_fbo.use() - self.ctx.blend_func = ( - moderngl.ONE, moderngl.ONE, - moderngl.ONE, moderngl.ONE, - ) + self.ctx.blend_func = (moderngl.ONE, moderngl.ONE) vao.render(render_primitive) self.ctx.blend_func = moderngl.DEFAULT_BLENDING self.fbo.use() From bc5c78de83287206c9775ed7a3ba2b3e67e45a07 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Wed, 25 Jan 2023 09:56:07 -0800 Subject: [PATCH 22/22] Add winding fill to VMobject args --- manimlib/mobject/types/vectorized_mobject.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index dca1e321..5228b9f0 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -89,6 +89,7 @@ class VMobject(Mobject): use_simple_quadratic_approx: bool = False, # Measured in pixel widths anti_alias_width: float = 1.0, + use_winding_fill: bool = True, **kwargs ): self.fill_color = fill_color or color or DEFAULT_FILL_COLOR @@ -103,7 +104,7 @@ class VMobject(Mobject): self.flat_stroke = flat_stroke self.use_simple_quadratic_approx = use_simple_quadratic_approx self.anti_alias_width = anti_alias_width - self._use_winding_fill = True + self._use_winding_fill = use_winding_fill self.needs_new_triangulation = True self.triangulation = np.zeros(0, dtype='i4')