Merge 3320-fix-antialiasing-opengl into experimental

This commit is contained in:
MrDiver 2023-09-10 14:30:47 +02:00
commit 5d8ea6a4a3
5 changed files with 87 additions and 154 deletions

View file

@ -559,6 +559,11 @@ class Text(SVGMobject):
# anti-aliasing
if height is None and width is None:
self.scale(TEXT_MOB_SCALE_FACTOR)
# Just a temporary hack to get better triangulation
# See pr #1552 for details
for i in self.submobjects:
i.insert_n_curves(len(i.get_all_points()))
self.initial_height = self.height
def __repr__(self):

View file

@ -6,21 +6,13 @@ in vec4 color;
in float fill_all; // Either 0 or 1
in float uv_anti_alias_width;
in vec3 xyz_coords;
in float orientation;
in vec2 uv_coords;
in vec2 uv_b2;
in float bezier_degree;
out vec4 frag_color;
// Needed for quadratic_bezier_distance insertion below
float modify_distance_for_endpoints(vec2 p, float dist, float t)
{
return dist;
}
#include "../include/quadratic_bezier_distance.glsl"
#define ANTI_ALIASING
float sdf()
{
@ -28,30 +20,15 @@ float sdf()
{
return abs(uv_coords[1]);
}
float u2 = uv_b2.x;
float v2 = uv_b2.y;
// For really flat curves, just take the distance to x-axis
if (abs(v2 / u2) < 0.1 * uv_anti_alias_width)
{
return abs(uv_coords[1]);
}
// This converts uv_coords to yet another space where the bezier points sit on
// (0, 0), (1/2, 0) and (1, 1), so that the curve can be expressed implicityly
// as y = x^2.
mat2 to_simple_space = mat2(v2, 0, 2 - u2, 4 * v2);
vec2 p = to_simple_space * uv_coords;
// Sign takes care of whether we should be filling the inside or outside of
// curve.
float sgn = orientation * sign(v2);
float Fp = (p.x * p.x - p.y);
if (sgn * Fp <= 0)
{
return 0.0;
}
else
{
return min_dist_to_curve(uv_coords, uv_b2, bezier_degree);
}
vec2 p = uv_coords;
float sgn = orientation;
float q = (p.x * p.x - p.y);
#ifdef ANTI_ALIASING
return sgn * q / sqrt(dFdx(q) * dFdx(q) + dFdy(q) * dFdy(q));
#endif
#ifndef ANTI_ALIASING
return -sgn * q;
#endif
}
void main()
@ -61,5 +38,10 @@ void main()
frag_color = color;
if (fill_all == 1.0)
return;
frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width);
#ifdef ANTI_ALIASING
frag_color.a *= 0.5 - sdf(); // Anti-aliasing
#endif
#ifndef ANTI_ALIASING
frag_color.a *= float(sdf() > 0); // No anti-aliasing
#endif
}

View file

@ -24,17 +24,11 @@ in float v_vert_index[3];
out vec4 color;
out float fill_all;
out float uv_anti_alias_width;
out vec3 xyz_coords;
out float orientation;
// uv space is where b0 = (0, 0), b1 = (1, 0), and transform is orthogonal
out vec2 uv_coords;
out vec2 uv_b2;
out float bezier_degree;
vec3 unit_normal;
// Analog of import for manim only
// Comment to prevent formatting
#include "../include/quadratic_bezier_geometry_functions.glsl"
@ -45,12 +39,13 @@ vec3 unit_normal;
//
#include "../include/finalize_color.glsl"
const vec2 uv_coords_arr[3] = vec2[3](vec2(0, 0), vec2(0.5, 0), vec2(1, 1));
void emit_vertex_wrapper(vec3 point, int index)
{
color = finalize_color(v_color[index], point, unit_normal, light_source_position, camera_position, reflectiveness,
gloss, shadow);
xyz_coords = point;
gl_Position = get_gl_Position(xyz_coords);
color = finalize_color(v_color[index], point, v_global_unit_normal[index], light_source_position, gloss, shadow);
gl_Position = get_gl_Position(point);
uv_coords = uv_coords_arr[index];
EmitVertex();
}
@ -63,52 +58,6 @@ void emit_simple_triangle()
EndPrimitive();
}
void emit_pentagon(vec3[3] points, vec3 normal)
{
vec3 p0 = points[0];
vec3 p1 = points[1];
vec3 p2 = points[2];
// Tangent vectors
vec3 t01 = normalize(p1 - p0);
vec3 t12 = normalize(p2 - p1);
// Vectors perpendicular to the curve in the plane of the curve pointing
// outside the curve
vec3 p0_perp = cross(t01, normal);
vec3 p2_perp = cross(t12, normal);
bool fill_inside = orientation > 0.0;
float aaw = anti_alias_width * frame_shape.y / pixel_shape.y;
vec3 corners[5];
if (bezier_degree == 1.0)
{
// For straight lines, buff out in both directions
corners = vec3[5](p0 + aaw * p0_perp, p0 - aaw * p0_perp, p1 + 0.5 * aaw * (p0_perp + p2_perp),
p2 - aaw * p2_perp, p2 + aaw * p2_perp);
}
else if (fill_inside)
{
// If curved, and filling insight, just buff out away interior
corners = vec3[5](p0 + aaw * p0_perp, p0, p1 + 0.5 * aaw * (p0_perp + p2_perp), p2, p2 + aaw * p2_perp);
}
else
{
corners = vec3[5](p0, p0 - aaw * p0_perp, p1, p2 - aaw * p2_perp, p2);
}
mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, normal);
uv_b2 = (xyz_to_uv * vec4(p2, 1)).xy;
uv_anti_alias_width = aaw / length(p1 - p0);
for (int i = 0; i < 5; i++)
{
vec3 corner = corners[i];
uv_coords = (xyz_to_uv * vec4(corner, 1)).xy;
int j = int(sign(i - 1) + 1); // Maps i = [0, 1, 2, 3, 4] onto j = [0, 0, 1, 2, 2]
emit_vertex_wrapper(corner, j);
}
EndPrimitive();
}
void main()
{
// If vert indices are sequential, don't fill all
@ -127,7 +76,7 @@ void main()
if (bezier_degree >= 1)
{
emit_pentagon(new_bp, unit_normal);
emit_simple_triangle();
}
// Don't emit any vertices for bezier_degree 0
}

View file

@ -671,73 +671,70 @@ def earclip_triangulation(verts: np.ndarray, ring_ends: list) -> list:
list
A list of indices giving a triangulation of a polygon.
"""
# First, connect all the rings so that the polygon
# with holes is instead treated as a (very convex)
# polygon with one edge. Do this by drawing connections
# between rings close to each other
rings = [list(range(e0, e1)) for e0, e1 in zip([0, *ring_ends], ring_ends)]
attached_rings = rings[:1]
detached_rings = rings[1:]
loop_connections = {}
while detached_rings:
i_range, j_range = (
list(
filter(
# Ignore indices that are already being
# used to draw some connection
lambda i: i not in loop_connections,
it.chain(*ring_group),
),
)
for ring_group in (attached_rings, detached_rings)
rings = [list(range(e0, e1)) for e0, e1 in zip([0, *ring_ends], ring_ends)]
def is_in(point, ring_id):
return (
abs(abs(get_winding_number([i - point for i in verts[rings[ring_id]]])) - 1)
< 1e-5
)
# Closest point on the attached rings to an estimated midpoint
# of the detached rings
tmp_j_vert = midpoint(verts[j_range[0]], verts[j_range[len(j_range) // 2]])
i = min(i_range, key=lambda i: norm_squared(verts[i] - tmp_j_vert))
# Closest point of the detached rings to the aforementioned
# point of the attached rings
j = min(j_range, key=lambda j: norm_squared(verts[i] - verts[j]))
# Recalculate i based on new j
i = min(i_range, key=lambda i: norm_squared(verts[i] - verts[j]))
def ring_area(ring_id):
ring = rings[ring_id]
s = 0
for i, j in zip(ring[1:], ring):
s += cross2d(verts[i], verts[j])
return abs(s) / 2
# Remember to connect the polygon at these points
loop_connections[i] = j
loop_connections[j] = i
# Points at the same position may cause problems
for i in rings:
verts[i[0]] += (verts[i[1]] - verts[i[0]]) * 1e-6
verts[i[-1]] += (verts[i[-2]] - verts[i[-1]]) * 1e-6
# Move the ring which j belongs to from the
# attached list to the detached list
new_ring = next(filter(lambda ring: ring[0] <= j < ring[-1], detached_rings))
detached_rings.remove(new_ring)
attached_rings.append(new_ring)
# First, we should know which rings are directly contained in it for each ring
# Setup linked list
after = []
end0 = 0
for end1 in ring_ends:
after.extend(range(end0 + 1, end1))
after.append(end0)
end0 = end1
right = [max(verts[rings[i], 0]) for i in range(len(rings))]
left = [min(verts[rings[i], 0]) for i in range(len(rings))]
top = [max(verts[rings[i], 1]) for i in range(len(rings))]
bottom = [min(verts[rings[i], 1]) for i in range(len(rings))]
area = [ring_area(i) for i in range(len(rings))]
# Find an ordering of indices walking around the polygon
indices = []
i = 0
for _ in range(len(verts) + len(ring_ends) - 1):
# starting = False
if i in loop_connections:
j = loop_connections[i]
indices.extend([i, j])
i = after[j]
else:
indices.append(i)
i = after[i]
if i == 0:
break
# The larger ring must be outside
rings_sorted = list(range(len(rings)))
rings_sorted.sort(key=lambda x: area[x], reverse=True)
meta_indices = earcut(verts[indices, :2], [len(indices)])
return [indices[mi] for mi in meta_indices]
def is_in_fast(ring_a, ring_b):
# Whether a is in b
return (
left[ring_b] <= left[ring_a] <= right[ring_a] <= right[ring_b]
and bottom[ring_b] <= bottom[ring_a] <= top[ring_a] <= top[ring_b]
and is_in(verts[rings[ring_a][0]], ring_b)
)
children = [[]] * len(rings)
for idx, i in enumerate(rings_sorted):
for j in rings_sorted[:idx][::-1]:
if is_in_fast(i, j):
children[j].append(i)
break
res = []
# Then, we can use earcut for each part
used = [False] * len(rings)
for i in rings_sorted:
if used[i]:
continue
v = rings[i]
ring_ends = [len(v)]
for j in children[i]:
used[j] = True
v += rings[j]
ring_ends.append(len(v))
res += [v[i] for i in earcut(verts[v, :2], ring_ends)]
return res
def cartesian_to_spherical(vec: Sequence[float]) -> np.ndarray: