mirror of
https://github.com/3b1b/manim.git
synced 2025-07-30 05:24:22 +08:00
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 = {
|
||||
|
@ -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),
|
||||
]
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
||||
|
Reference in New Issue
Block a user