Merge pull request #1972 from 3b1b/winding-fill

Winding fill
This commit is contained in:
Grant Sanderson
2023-01-25 10:01:31 -08:00
committed by GitHub
10 changed files with 244 additions and 195 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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