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() diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index e6f4a76a..b5bbc2cc 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(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 @@ -254,6 +255,59 @@ 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: moderngl.context.Context): + # Experimental + size = self.get_pixel_shape() + self.fill_texture = ctx.texture( + size=size, + components=4, + # 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(size) + self.fill_fbo = ctx.framebuffer(self.fill_texture, fill_depth) + self.fill_prog = 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() { + 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 + self.n_textures += 1 + verts = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) + self.fill_texture_vao = ctx.simple_vertex_array( + self.fill_prog, + ctx.buffer(verts.astype('f4').tobytes()), + 'texcoord', + ) + def set_ctx_blending(self, enable: bool = True) -> None: if enable: self.ctx.enable(moderngl.BLEND) @@ -397,13 +451,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) - render_group["vao"].render(int(shader_wrapper.render_primitive)) + + if shader_wrapper.is_fill: + 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, 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() + self.fill_fbo.use() + self.ctx.blend_func = (moderngl.ONE, moderngl.ONE) + vao.render(render_primitive) + self.ctx.blend_func = moderngl.DEFAULT_BLENDING + self.fbo.use() + 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) @@ -428,19 +508,20 @@ 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)) 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/mobject.py b/manimlib/mobject/mobject.py index ea30b932..874c81a1 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 @@ -1673,15 +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_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.invisible_copy()) self.set_submobjects(new_submobs) return self + def invisible_copy(self): + return self.copy().set_opacity(0) + # Interpolate def interpolate( @@ -1846,7 +1844,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/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 = { diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index be44a4a0..aba18c60 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 @@ -90,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 @@ -104,6 +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 = use_winding_fill self.needs_new_triangulation = True self.triangulation = np.zeros(0, dtype='i4') @@ -129,8 +130,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: @@ -144,6 +143,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( @@ -402,6 +414,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, @@ -808,11 +825,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 @@ -846,8 +867,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) @@ -855,8 +876,15 @@ 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 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: @@ -899,7 +927,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): @@ -910,7 +938,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() @@ -992,11 +1020,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 @@ -1054,8 +1077,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) @@ -1091,6 +1114,7 @@ class VMobject(Mobject): if refresh: self.refresh_triangulation() self.refresh_joint_products() + return self return wrapper @triggers_refreshed_triangulation @@ -1109,10 +1133,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 @@ -1121,14 +1146,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 +1177,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, + is_fill=True, ) self.stroke_shader_wrapper = ShaderWrapper( vert_data=stroke_data, @@ -1192,8 +1209,13 @@ class VMobject(Mobject): 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]) - fill_indices.append(submob.get_triangulation()) + 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: @@ -1208,7 +1230,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, fill_indices or None), self.stroke_shader_wrapper.read_in(stroke_datas), ] diff --git a/manimlib/shader_wrapper.py b/manimlib/shader_wrapper.py index 2d225a62..8d867128 100644 --- a/manimlib/shader_wrapper.py +++ b/manimlib/shader_wrapper.py @@ -34,9 +34,10 @@ class ShaderWrapper(object): depth_test: bool = False, use_clip_plane: bool = False, render_primitive: int = moderngl.TRIANGLE_STRIP, + is_fill: bool = False, ): self.vert_data = vert_data - self.vert_indices = vert_indices + 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() @@ -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.is_fill = is_fill self.init_program_code() self.refresh_id() @@ -66,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: @@ -134,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 @@ -155,10 +153,14 @@ 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: + self.vert_indices = resize_array(self.vert_indices, 0) 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 diff --git a/manimlib/shaders/quadratic_bezier_fill/frag.glsl b/manimlib/shaders/quadratic_bezier_fill/frag.glsl index e5341459..c672455a 100644 --- a/manimlib/shaders/quadratic_bezier_fill/frag.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/frag.glsl @@ -1,30 +1,25 @@ #version 330 -in vec4 color; -in float fill_all; // Either 0 or 1 -in float uv_anti_alias_width; +uniform bool winding; +in vec4 color; +in float fill_all; 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(winding && orientation > 0) frag_color *= -1; + if (bool(fill_all)) return; - frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width); + + float x = uv_coords.x; + float y = uv_coords.y; + 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 256ac4d5..fae7998b 100644 --- a/manimlib/shaders/quadratic_bezier_fill/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -1,129 +1,84 @@ #version 330 layout (triangles) in; -layout (triangle_strip, max_vertices = 5) out; +layout (triangle_strip, max_vertices = 6) out; -uniform float anti_alias_width; -uniform float pixel_size; +uniform bool winding; 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; +// 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 -#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); - 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); - -void emit_simple_triangle(vec3 unit_normal){ for(int i = 0; i < 3; i++){ - emit_vertex_wrapper(verts[i], i, unit_normal); + 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_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 emit_simple_triangle(){ + emit_triangle( + vec3[3](verts[0], verts[1], verts[2]), + vec4[3](v_color[0], v_color[1], v_color[2]) + ); } 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 (winding && 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 ended 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; + vec3 mid_vert; + if(winding){ + // Emit main triangle + fill_all = 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]) + ); + // Edge triangle + fill_all = 0.0; + emit_simple_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_simple_triangle(); } - orientation = v_orientation[1]; - - emit_pentagon( - p0, p1, p2, - normalize(t01), - normalize(t12), - unit_normal - ); } diff --git a/manimlib/shaders/quadratic_bezier_fill/vert.glsl b/manimlib/shaders/quadratic_bezier_fill/vert.glsl index 3d2e2c9a..5818216a 100644 --- a/manimlib/shaders/quadratic_bezier_fill/vert.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/vert.glsl @@ -2,17 +2,16 @@ 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_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 diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index ef5029d0..96e3f1b2 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -99,13 +99,16 @@ 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 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; @@ -142,8 +145,8 @@ void get_corners( // 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); @@ -154,7 +157,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; @@ -164,7 +167,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