mirror of
https://github.com/3b1b/manim.git
synced 2025-11-13 19:57:48 +00:00
Added quadratic bezier shader files
This commit is contained in:
parent
3c57c461b5
commit
8638f7303a
8 changed files with 860 additions and 0 deletions
139
manimlib/shaders/quadratic_bezier_distance.glsl
Normal file
139
manimlib/shaders/quadratic_bezier_distance.glsl
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
// This file is not a shader, it's just a set of
|
||||
// functions meant to be inserted into other shaders.
|
||||
|
||||
// All of this is with respect to a curve that's been rotated/scaled
|
||||
// so that b0 = (0, 0) and b1 = (1, 0). That is, b2 entirely
|
||||
// determines the shape of the curve
|
||||
|
||||
vec2 bezier(float t, vec2 b2){
|
||||
// Quick returns for the 0 and 1 cases
|
||||
if (t == 0) return vec2(0, 0);
|
||||
else if (t == 1) return b2;
|
||||
// Everything else
|
||||
return vec2(
|
||||
2 * t * (1 - t) + b2.x * t*t,
|
||||
b2.y * t * t
|
||||
);
|
||||
}
|
||||
|
||||
void compute_C_and_grad_C(float a, float b, vec2 p, out float Cxy, out vec2 grad_Cxy){
|
||||
// Curve has the implicit form x = a*y + b*sqrt(y), which is also
|
||||
// 0 = -x^2 + 2axy + b^2 y - a^2 y^2.
|
||||
Cxy = -p.x*p.x + 2 * a * p.x*p.y + b*b * p.y - a*a * p.y*p.y;
|
||||
|
||||
// Approximate distance to curve using the gradient of -x^2 + 2axy + b^2 y - a^2 y^2
|
||||
grad_Cxy = vec2(
|
||||
-2 * p.x + 2 * a * p.y, // del C / del x
|
||||
2 * a * p.x + b*b - 2 * a*a * p.y // del C / del y
|
||||
);
|
||||
}
|
||||
|
||||
// This function is flawed.
|
||||
float cheap_dist_to_curve(vec2 p, vec2 b2){
|
||||
float a = (b2.x - 2.0) / b2.y;
|
||||
float b = sign(b2.y) * 2.0 / sqrt(abs(b2.y));
|
||||
float x = p.x;
|
||||
float y = p.y;
|
||||
|
||||
// Curve has the implicit form x = a*y + b*sqrt(y), which is also
|
||||
// 0 = -x^2 + 2axy + b^2 y - a^2 y^2.
|
||||
float Cxy = -x * x + 2 * a * x * y + sign(b2.y) * b * b * y - a * a * y * y;
|
||||
|
||||
// Approximate distance to curve using the gradient of -x^2 + 2axy + b^2 y - a^2 y^2
|
||||
vec2 grad_Cxy = 2 * vec2(
|
||||
-x + a * y, // del C / del x
|
||||
a * x + b * b / 2 - a * a * y // del C / del y
|
||||
);
|
||||
return abs(Cxy / length(grad_Cxy));
|
||||
}
|
||||
|
||||
float cube_root(float x){
|
||||
return sign(x) * pow(abs(x), 1.0 / 3.0);
|
||||
}
|
||||
|
||||
|
||||
int cubic_solve(float a, float b, float c, float d, out float roots[3]){
|
||||
// Normalize so a = 1
|
||||
b = b / a;
|
||||
c = c / a;
|
||||
d = d / a;
|
||||
|
||||
float p = c - b*b / 3.0;
|
||||
float q = b * (2.0*b*b - 9.0*c) / 27.0 + d;
|
||||
float p3 = p*p*p;
|
||||
float disc = q*q + 4.0*p3 / 27.0;
|
||||
float offset = -b / 3.0;
|
||||
if(disc >= 0.0){
|
||||
float z = sqrt(disc);
|
||||
float u = (-q + z) / 2.0;
|
||||
float v = (-q - z) / 2.0;
|
||||
u = cube_root(u);
|
||||
v = cube_root(v);
|
||||
roots[0] = offset + u + v;
|
||||
return 1;
|
||||
}
|
||||
float u = sqrt(-p / 3.0);
|
||||
float v = acos(-sqrt( -27.0 / p3) * q / 2.0) / 3.0;
|
||||
float m = cos(v);
|
||||
float n = sin(v) * 1.732050808;
|
||||
|
||||
float all_roots[3] = float[3](
|
||||
offset + u * (n - m),
|
||||
offset - u * (n + m),
|
||||
offset + u * (m + m)
|
||||
);
|
||||
|
||||
// Only accept roots with a positive derivative
|
||||
int n_valid_roots = 0;
|
||||
for(int i = 0; i < 3; i++){
|
||||
float r = all_roots[i];
|
||||
if(3*r*r + 2*b*r + c > 0){
|
||||
roots[n_valid_roots] = r;
|
||||
n_valid_roots++;
|
||||
}
|
||||
}
|
||||
return n_valid_roots;
|
||||
}
|
||||
|
||||
float dist_to_line(vec2 p, vec2 b2){
|
||||
float t = clamp(p.x / b2.x, 0, 1);
|
||||
float dist;
|
||||
if(t == 0) dist = length(p);
|
||||
else if(t == 1) dist = distance(p, b2);
|
||||
else dist = abs(p.y);
|
||||
|
||||
return modify_distance_for_endpoints(p, dist, t);
|
||||
}
|
||||
|
||||
|
||||
float dist_to_point_on_curve(vec2 p, float t, vec2 b2){
|
||||
t = clamp(t, 0, 1);
|
||||
return modify_distance_for_endpoints(
|
||||
p, length(p - bezier(t, b2)), t
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
float min_dist_to_curve(vec2 p, vec2 b2, float degree, bool quick_approx){
|
||||
// Check if curve is really a a line
|
||||
if(degree == 1) return dist_to_line(p, b2);
|
||||
if(quick_approx) return cheap_dist_to_curve(p, b2);
|
||||
|
||||
// Try finding the exact sdf by solving the equation
|
||||
// (d/dt) dist^2(t) = 0, which amount to the following
|
||||
// cubic.
|
||||
float xm2 = uv_b2.x - 2.0;
|
||||
float y = uv_b2.y;
|
||||
float a = xm2*xm2 + y*y;
|
||||
float b = 3 * xm2;
|
||||
float c = -(p.x*xm2 + p.y*y) + 2;
|
||||
float d = -p.x;
|
||||
|
||||
float roots[3];
|
||||
int n = cubic_solve(a, b, c, d, roots);
|
||||
// At most 2 roots will have been populated.
|
||||
float d0 = dist_to_point_on_curve(p, roots[0], b2);
|
||||
if(n == 1) return d0;
|
||||
float d1 = dist_to_point_on_curve(p, roots[1], b2);
|
||||
return min(d0, d1);
|
||||
}
|
||||
52
manimlib/shaders/quadratic_bezier_fill_frag.glsl
Normal file
52
manimlib/shaders/quadratic_bezier_fill_frag.glsl
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#version 330
|
||||
|
||||
in vec4 color;
|
||||
in float fill_type;
|
||||
in float uv_anti_alias_width;
|
||||
|
||||
in vec2 uv_coords;
|
||||
in vec2 wz_coords;
|
||||
in vec2 uv_b2;
|
||||
in float bezier_degree;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
const float FILL_INSIDE = 0;
|
||||
const float FILL_OUTSIDE = 1;
|
||||
const float FILL_ALL = 2;
|
||||
|
||||
|
||||
// Needed for quadratic_bezier_distance
|
||||
float modify_distance_for_endpoints(vec2 p, float dist, float t){
|
||||
return dist;
|
||||
}
|
||||
|
||||
// To my knowledge, there is no notion of #include for shaders,
|
||||
// so to share functionality between this and others, the caller
|
||||
// replaces this line with the contents of quadratic_bezier_sdf.glsl
|
||||
#INSERT quadratic_bezier_distance.glsl
|
||||
|
||||
|
||||
bool is_inside_curve(){
|
||||
if(bezier_degree < 2) return false;
|
||||
|
||||
float value = wz_coords.x * wz_coords.x - wz_coords.y;
|
||||
if(fill_type == FILL_INSIDE) return value < 0;
|
||||
if(fill_type == FILL_OUTSIDE) return value > 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
float sdf(){
|
||||
if(is_inside_curve()) return -1;
|
||||
return min_dist_to_curve(uv_coords, uv_b2, bezier_degree, true);
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
if (color.a == 0) discard;
|
||||
frag_color = color;
|
||||
if (fill_type == FILL_ALL) return;
|
||||
frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width);
|
||||
// frag_color.a += 0.2;
|
||||
}
|
||||
157
manimlib/shaders/quadratic_bezier_fill_geom.glsl
Normal file
157
manimlib/shaders/quadratic_bezier_fill_geom.glsl
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
#version 330
|
||||
|
||||
layout (triangles) in;
|
||||
layout (triangle_strip, max_vertices = 5) out;
|
||||
|
||||
uniform float scale;
|
||||
uniform float aspect_ratio;
|
||||
uniform float anti_alias_width;
|
||||
|
||||
in vec2 bp[3];
|
||||
in vec4 v_color[3];
|
||||
in float v_fill_type[3];
|
||||
|
||||
out vec4 color;
|
||||
out float fill_type;
|
||||
out float uv_anti_alias_width;
|
||||
|
||||
// uv space is where b0 = (0, 0), b1 = (1, 0), and transform is orthogonal
|
||||
out vec2 uv_coords;
|
||||
out vec2 uv_b2;
|
||||
// wz space is where b0 = (0, 0), b1 = (0.5, 0), b2 = (1, 1)
|
||||
out vec2 wz_coords;
|
||||
|
||||
out float bezier_degree;
|
||||
|
||||
const float FILL_INSIDE = 0;
|
||||
const float FILL_OUTSIDE = 1;
|
||||
const float FILL_ALL = 2;
|
||||
|
||||
const float SQRT5 = 2.236068;
|
||||
|
||||
|
||||
// To my knowledge, there is no notion of #include for shaders,
|
||||
// so to share functionality between this and others, the caller
|
||||
// replaces this line with the contents of named file
|
||||
#INSERT quadratic_bezier_geometry_functions.glsl
|
||||
|
||||
|
||||
mat3 get_xy_to_wz(vec2 b0, vec2 b1, vec2 b2){
|
||||
// If linear or null, this matrix is not needed
|
||||
if(bezier_degree < 2) return mat3(1.0);
|
||||
|
||||
vec2 inv_col1 = 2 * (b1 - b0);
|
||||
vec2 inv_col2 = b2 - 2 * b1 + b0;
|
||||
float inv_det = cross(inv_col1, inv_col2);
|
||||
|
||||
mat3 transform = mat3(
|
||||
inv_col2.y, -inv_col1.y, 0,
|
||||
-inv_col2.x, inv_col1.x, 0,
|
||||
0, 0, inv_det
|
||||
) / inv_det;
|
||||
|
||||
mat3 shift = mat3(
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
-b0.x, -b0.y, 1
|
||||
);
|
||||
return transform * shift;
|
||||
}
|
||||
|
||||
void set_gl_Position(vec2 p){
|
||||
vec2 result = p / scale;
|
||||
result.x /= aspect_ratio;
|
||||
gl_Position = vec4(result, 0.0, 1.0);
|
||||
}
|
||||
|
||||
|
||||
void emit_simple_triangle(){
|
||||
for(int i = 0; i < 3; i++){
|
||||
color = v_color[i];
|
||||
set_gl_Position(bp[i]);
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
|
||||
void emit_pentagon(vec2 bp0, vec2 bp1, vec2 bp2, float in_or_out){
|
||||
// Tangent vectors
|
||||
vec2 t01 = normalize(bp1 - bp0);
|
||||
vec2 t12 = normalize(bp2 - bp1);
|
||||
|
||||
// Inside and left turn -> rot right -> -1
|
||||
// Outside and left turn -> rot left -> +1
|
||||
// Inside and right turn -> rot left -> +1
|
||||
// Outside and right turn -> rot right -> -1
|
||||
float c_orient = (cross(t01, t12) > 0) ? 1 : -1;
|
||||
float orient = in_or_out * c_orient;
|
||||
|
||||
// Normal vectors
|
||||
vec2 n01 = -orient * vec2(-t01.y, t01.x);
|
||||
vec2 n12 = -orient * vec2(-t12.y, t12.x);
|
||||
|
||||
float aaw = 1 * anti_alias_width;
|
||||
vec2 nudge1 = (fill_type == FILL_OUTSIDE) ? vec2(0) : 0.5 * aaw * (n01 + n12);
|
||||
vec2 corners[5] = vec2[5](
|
||||
bp0 + aaw * n01,
|
||||
bp0,
|
||||
bp1 + nudge1,
|
||||
bp2,
|
||||
bp2 + aaw * n12
|
||||
);
|
||||
|
||||
int coords_index_map[5] = int[5](0, 1, 2, 3, 4);
|
||||
if(in_or_out < 0) coords_index_map = int[5](1, 0, 2, 4, 3);
|
||||
|
||||
mat3 xy_to_uv = get_xy_to_uv(bp0, bp1);
|
||||
mat3 xy_to_wz = get_xy_to_wz(bp0, bp1, bp2);
|
||||
uv_b2 = (xy_to_uv * vec3(bp2, 1)).xy;
|
||||
uv_anti_alias_width = anti_alias_width / length(bp1 - bp0);
|
||||
|
||||
for(int i = 0; i < 5; i++){
|
||||
vec2 corner = corners[coords_index_map[i]];
|
||||
uv_coords = (xy_to_uv * vec3(corner, 1)).xy;
|
||||
wz_coords = (xy_to_wz * vec3(corner, 1)).xy;
|
||||
// I haven't a clue why an index map doesn't work just
|
||||
// as well here, but for some reason it doesn't.
|
||||
if(i < 2) color = v_color[0];
|
||||
else if(i == 2) color = v_color[1];
|
||||
else color = v_color[2];
|
||||
// color = v_color[1];
|
||||
set_gl_Position(corner);
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
|
||||
|
||||
void main(){
|
||||
fill_type = v_fill_type[0];
|
||||
|
||||
if(fill_type == FILL_ALL){
|
||||
emit_simple_triangle();
|
||||
}else{
|
||||
vec2 new_bp[3];
|
||||
int n = get_reduced_control_points(bp[0], bp[1], bp[2], new_bp);
|
||||
bezier_degree = float(n);
|
||||
|
||||
vec2 bp0, bp1, bp2;
|
||||
if(n == 0){
|
||||
return; // Don't emit any vertices
|
||||
}
|
||||
else if(n == 1){
|
||||
bp0 = new_bp[0];
|
||||
bp2 = new_bp[1];
|
||||
bp1 = 0.5 * (bp0 + bp2);
|
||||
}else{
|
||||
bp0 = new_bp[0];
|
||||
bp1 = new_bp[1];
|
||||
bp2 = new_bp[2];
|
||||
}
|
||||
|
||||
float orient = (fill_type == FILL_INSIDE) ? 1 : -1;
|
||||
emit_pentagon(bp0, bp1, bp2, orient);
|
||||
}
|
||||
}
|
||||
|
||||
16
manimlib/shaders/quadratic_bezier_fill_vert.glsl
Normal file
16
manimlib/shaders/quadratic_bezier_fill_vert.glsl
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#version 330
|
||||
|
||||
in vec2 point;
|
||||
in vec4 color;
|
||||
in float fill_type;
|
||||
|
||||
out vec2 bp; // Bezier control point
|
||||
out vec4 v_color;
|
||||
out float v_fill_type;
|
||||
|
||||
|
||||
void main(){
|
||||
bp = point;
|
||||
v_color = color;
|
||||
v_fill_type = fill_type;
|
||||
}
|
||||
59
manimlib/shaders/quadratic_bezier_geometry_functions.glsl
Normal file
59
manimlib/shaders/quadratic_bezier_geometry_functions.glsl
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// This file is not a shader, it's just a set of
|
||||
// functions meant to be inserted into other shaders.
|
||||
|
||||
float cross(vec2 v, vec2 w){
|
||||
return v.x * w.y - w.x * v.y;
|
||||
}
|
||||
|
||||
// Matrix to convert to a uv space defined so that
|
||||
// b0 goes to [0, 0] and b1 goes to [1, 0]
|
||||
mat3 get_xy_to_uv(vec2 b0, vec2 b1){
|
||||
vec2 T = b1 - b0;
|
||||
|
||||
mat3 shift = mat3(
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
-b0.x, -b0.y, 1
|
||||
);
|
||||
mat3 rotate_and_scale = mat3(
|
||||
T.x, -T.y, 0,
|
||||
T.y, T.x, 0,
|
||||
0, 0, 1
|
||||
) / dot(T, T);
|
||||
return rotate_and_scale * shift;
|
||||
}
|
||||
|
||||
|
||||
// Returns 0 for null curve, 1 for linear, 2 for quadratic.
|
||||
// Populates new_points with bezier control points for the curve,
|
||||
// which for quadratics will be the same, but for linear and null
|
||||
// might change. The idea is to inform the caller of the degree,
|
||||
// while also passing tangency information in the linear case.
|
||||
int get_reduced_control_points(vec2 b0, vec2 b1, vec2 b2, out vec2 new_points[3]){
|
||||
float epsilon = 1e-6;
|
||||
vec2 v01 = (b1 - b0);
|
||||
vec2 v12 = (b2 - b1);
|
||||
bool distinct_01 = length(v01) > epsilon; // v01 is considered nonzero
|
||||
bool distinct_12 = length(v12) > epsilon; // v12 is considered nonzero
|
||||
int n_uniques = int(distinct_01) + int(distinct_12);
|
||||
if(n_uniques == 2){
|
||||
bool linear = dot(normalize(v01), normalize(v12)) > 1 - epsilon;
|
||||
if(linear){
|
||||
new_points[0] = b0;
|
||||
new_points[1] = b2;
|
||||
return 1;
|
||||
}else{
|
||||
new_points[0] = b0;
|
||||
new_points[1] = b1;
|
||||
new_points[2] = b2;
|
||||
return 2;
|
||||
}
|
||||
}else if(n_uniques == 1){
|
||||
new_points[0] = b0;
|
||||
new_points[1] = b2;
|
||||
return 1;
|
||||
}else{
|
||||
new_points[0] = b0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
91
manimlib/shaders/quadratic_bezier_stroke_frag.glsl
Normal file
91
manimlib/shaders/quadratic_bezier_stroke_frag.glsl
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#version 330
|
||||
|
||||
in vec2 uv_coords;
|
||||
in vec2 uv_b2;
|
||||
|
||||
in float uv_stroke_width;
|
||||
in vec4 color;
|
||||
in float uv_anti_alias_width;
|
||||
|
||||
in float has_prev;
|
||||
in float has_next;
|
||||
in float bevel_start;
|
||||
in float bevel_end;
|
||||
in float angle_from_prev;
|
||||
in float angle_to_next;
|
||||
|
||||
in float bezier_degree;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
|
||||
float cross(vec2 v, vec2 w){
|
||||
return v.x * w.y - w.x * v.y;
|
||||
}
|
||||
|
||||
|
||||
float modify_distance_for_endpoints(vec2 p, float dist, float t){
|
||||
float buff = 0.5 * uv_stroke_width - uv_anti_alias_width;
|
||||
// Check the beginning of the curve
|
||||
if(t == 0){
|
||||
// Clip the start
|
||||
if(has_prev == 0) return max(dist, -p.x + buff);
|
||||
// Bevel start
|
||||
if(bevel_start == 1){
|
||||
float a = angle_from_prev;
|
||||
mat2 rot = mat2(
|
||||
cos(a), sin(a),
|
||||
-sin(a), cos(a)
|
||||
);
|
||||
// Dist for intersection of two lines
|
||||
float bevel_d = max(abs(p.y), abs((rot * p).y));
|
||||
// Dist for union of this intersection with the real curve
|
||||
return min(dist, bevel_d);
|
||||
}
|
||||
// Otherwise, start will be rounded off
|
||||
}else if(t == 1){
|
||||
// Check the end of the curve
|
||||
// TODO, too much code repetition
|
||||
vec2 v21 = (bezier_degree == 2) ? vec2(1, 0) - uv_b2 : vec2(-1, 0);
|
||||
float len_v21 = length(v21);
|
||||
if(len_v21 == 0){
|
||||
v21 = -uv_b2;
|
||||
len_v21 = length(v21);
|
||||
}
|
||||
|
||||
float perp_dist = dot(p - uv_b2, v21) / len_v21;
|
||||
if(has_next == 0) return max(dist, -perp_dist + buff);
|
||||
// Bevel end
|
||||
if(bevel_end == 1){
|
||||
float a = -angle_to_next;
|
||||
mat2 rot = mat2(
|
||||
cos(a), sin(a),
|
||||
-sin(a), cos(a)
|
||||
);
|
||||
vec2 v21_unit = v21 / length(v21);
|
||||
float bevel_d = max(
|
||||
abs(cross(p - uv_b2, v21_unit)),
|
||||
abs(cross((rot * (p - uv_b2)), v21_unit))
|
||||
);
|
||||
return min(dist, bevel_d);
|
||||
}
|
||||
// Otherwise, end will be rounded off
|
||||
}
|
||||
return dist;
|
||||
}
|
||||
|
||||
// To my knowledge, there is no notion of #include for shaders,
|
||||
// so to share functionality between this and others, the caller
|
||||
// replaces this line with the contents of named file
|
||||
#INSERT quadratic_bezier_distance.glsl
|
||||
|
||||
|
||||
void main() {
|
||||
if (uv_stroke_width == 0) discard;
|
||||
|
||||
frag_color = color;
|
||||
float dist_to_curve = min_dist_to_curve(uv_coords, uv_b2, bezier_degree, false);
|
||||
// An sdf for the region around the curve we wish to color.
|
||||
float signed_dist = abs(dist_to_curve) - 0.5 * uv_stroke_width;
|
||||
frag_color.a *= smoothstep(0.5, -0.5, signed_dist / uv_anti_alias_width);
|
||||
}
|
||||
316
manimlib/shaders/quadratic_bezier_stroke_geom.glsl
Normal file
316
manimlib/shaders/quadratic_bezier_stroke_geom.glsl
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
#version 330
|
||||
|
||||
layout (triangles) in;
|
||||
layout (triangle_strip, max_vertices = 5) out;
|
||||
|
||||
uniform float scale;
|
||||
uniform float aspect_ratio;
|
||||
uniform float anti_alias_width;
|
||||
|
||||
in vec2 bp[3];
|
||||
in vec2 prev_bp[3];
|
||||
in vec2 next_bp[3];
|
||||
|
||||
in vec4 v_color[3];
|
||||
in float v_stroke_width[3];
|
||||
in float v_joint_type[3];
|
||||
|
||||
out vec4 color;
|
||||
out float uv_stroke_width;
|
||||
out float uv_anti_alias_width;
|
||||
|
||||
out float has_prev;
|
||||
out float has_next;
|
||||
out float bevel_start;
|
||||
out float bevel_end;
|
||||
out float angle_from_prev;
|
||||
out float angle_to_next;
|
||||
|
||||
out float bezier_degree;
|
||||
|
||||
out vec2 uv_coords;
|
||||
out vec2 uv_b2;
|
||||
|
||||
// Codes for joint types
|
||||
const float AUTO_JOINT = 0;
|
||||
const float ROUND_JOINT = 1;
|
||||
const float BEVEL_JOINT = 2;
|
||||
const float MITER_JOINT = 3;
|
||||
|
||||
|
||||
// To my knowledge, there is no notion of #include for shaders,
|
||||
// so to share functionality between this and others, the caller
|
||||
// replaces this line with the contents of named file
|
||||
#INSERT quadratic_bezier_geometry_functions.glsl
|
||||
|
||||
|
||||
float angle_between_vectors(vec2 v1, vec2 v2){
|
||||
vec2 nv1 = normalize(v1);
|
||||
vec2 nv2 = normalize(v2);
|
||||
float unsigned_angle = acos(clamp(dot(nv1, nv2), -1, 1));
|
||||
float sn = sign(cross(nv1, nv2));
|
||||
return sn * unsigned_angle;
|
||||
}
|
||||
|
||||
|
||||
bool find_intersection(vec2 p0, vec2 v0, vec2 p1, vec2 v1, out vec2 intersection){
|
||||
// Find the intersection of a line passing through
|
||||
// p0 in the direction v0 and one passing through p1 in
|
||||
// the direction p1.
|
||||
// That is, find a solutoin to p0 + v0 * t = p1 + v1 * s
|
||||
// float det = -v0.x * v1.y + v1.x * v0.y;
|
||||
float det = cross(v1, v0);
|
||||
if(det == 0){
|
||||
// intersection = p0;
|
||||
return false;
|
||||
}
|
||||
float t = cross(p0 - p1, v1) / det;
|
||||
intersection = p0 + v0 * t;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool is_between(vec2 p, vec2 a, vec2 b){
|
||||
// Assumes three points fall on a line, returns whether
|
||||
// or not p sits between a and b.
|
||||
float d_pa = distance(p, a);
|
||||
float d_pb = distance(p, b);
|
||||
float d_ab = distance(a, b);
|
||||
return (d_ab >= d_pa && d_ab >= d_pb);
|
||||
}
|
||||
|
||||
|
||||
// Tries to detect if one of the corners defined by the buffer around
|
||||
// b0 and b2 should be modified to form a better convex hull
|
||||
bool should_motify_corner(vec2 c, vec2 from_c, vec2 o1, vec2 o2, vec2 from_o, float buff){
|
||||
vec2 int1;
|
||||
vec2 int2;
|
||||
find_intersection(c, from_c, o1, from_o, int1);
|
||||
find_intersection(c, from_c, o2, from_o, int2);
|
||||
return !is_between(int2, c + 1 * from_c * buff, int1);
|
||||
}
|
||||
|
||||
|
||||
void create_joint(float angle, vec2 unit_tan, float buff, float should_bevel,
|
||||
vec2 static_c0, out vec2 changing_c0,
|
||||
vec2 static_c1, out vec2 changing_c1){
|
||||
float shift;
|
||||
float joint_type = v_joint_type[0];
|
||||
bool miter = (
|
||||
(joint_type == AUTO_JOINT && abs(angle) > 2.8 && should_bevel == 1) ||
|
||||
(joint_type == MITER_JOINT)
|
||||
);
|
||||
if(abs(angle) < 1e-3){
|
||||
// No joint
|
||||
shift = 0;
|
||||
}else if(miter){
|
||||
shift = buff * (-1.0 - cos(angle)) / sin(angle);
|
||||
}else{
|
||||
// For a Bevel joint
|
||||
shift = buff * (1.0 - cos(angle)) / sin(angle);
|
||||
}
|
||||
changing_c0 = static_c0 - shift * unit_tan;
|
||||
changing_c1 = static_c1 + shift * unit_tan;
|
||||
}
|
||||
|
||||
|
||||
// This function is responsible for finding the corners of
|
||||
// a bounding region around the bezier curve, which can be
|
||||
// emitted as a triangle fan
|
||||
int get_corners(vec2 controls[3], int degree, out vec2 corners[5]){
|
||||
// Unit vectors for directions between
|
||||
// Various control points
|
||||
vec2 v02, v20, v10, v01, v12, v21;
|
||||
|
||||
vec2 p0 = controls[0];
|
||||
vec2 p2 = controls[degree];
|
||||
v02 = normalize(p2 - p0);
|
||||
v20 = -v02;
|
||||
if(degree == 2){
|
||||
v10 = normalize(p0 - controls[1]);
|
||||
v12 = normalize(p2 - controls[1]);
|
||||
}else{
|
||||
v10 = v20;
|
||||
v12 = v02;
|
||||
}
|
||||
v01 = -v10;
|
||||
v21 = -v12;
|
||||
|
||||
// Find bounding points around ends
|
||||
vec2 p0_perp = vec2(-v01.y, v01.x);
|
||||
vec2 p2_perp = vec2(-v21.y, v21.x);
|
||||
|
||||
float buff0 = 0.5 * v_stroke_width[0] + anti_alias_width;
|
||||
float buff2 = 0.5 * v_stroke_width[2] + anti_alias_width;
|
||||
float aaw0 = (1 - has_prev) * anti_alias_width;
|
||||
float aaw2 = (1 - has_next) * anti_alias_width;
|
||||
|
||||
vec2 c0 = p0 - buff0 * p0_perp + aaw0 * v10;
|
||||
vec2 c1 = p0 + buff0 * p0_perp + aaw0 * v10;
|
||||
vec2 c2 = p2 - p2_perp * buff2 + aaw2 * v12;
|
||||
vec2 c3 = p2 + p2_perp * buff2 + aaw2 * v12;
|
||||
|
||||
// Account for previous and next control points
|
||||
if(has_prev == 1){
|
||||
create_joint(angle_from_prev, v01, buff0, bevel_start, c0, c0, c1, c1);
|
||||
}
|
||||
if(has_next == 1){
|
||||
create_joint(-angle_to_next, v21, buff2, bevel_end, c2, c2, c3, c3);
|
||||
}
|
||||
|
||||
// Linear case is the simplets
|
||||
if(degree == 1){
|
||||
// Swap between 2 and 3 is deliberate, the order of corners
|
||||
// should be for a triangle_strip. Last entry is a dummy
|
||||
corners = vec2[5](c0, c1, c3, c2, vec2(0.0));
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Some admitedly complicated logic to (hopefully efficiently)
|
||||
// make sure corners forms a convex hull around the curve.
|
||||
if(cross(v10, v12) > 0){
|
||||
bool change_c0 = (
|
||||
has_prev == 0 &&
|
||||
dot(v21, v20) > 0 &&
|
||||
should_motify_corner(c0, v01, c2, c3, v21, buff0)
|
||||
);
|
||||
if(change_c0) c0 = p0 + p2_perp * buff0;
|
||||
|
||||
bool change_c3 = (
|
||||
has_next == 0 &&
|
||||
dot(v01, v02) > 0 &&
|
||||
should_motify_corner(c3, v21, c1, c0, v01, buff2)
|
||||
);
|
||||
if(change_c3) c3 = p2 - p0_perp * buff2;
|
||||
|
||||
vec2 i12;
|
||||
find_intersection(c1, v01, c2, v21, i12);
|
||||
corners = vec2[5](c1, c0, i12, c3, c2);
|
||||
}else{
|
||||
bool change_c1 = (
|
||||
has_prev == 0 &&
|
||||
dot(v21, v20) > 0 &&
|
||||
should_motify_corner(c1, v01, c3, c2, v21, buff0)
|
||||
);
|
||||
if(change_c1) c1 = p0 - p2_perp * buff0;
|
||||
|
||||
bool change_c2 = (
|
||||
has_next == 0 &&
|
||||
dot(v01, v02) > 0 &&
|
||||
should_motify_corner(c2, v21, c0, c1, v01, buff2)
|
||||
);
|
||||
if(change_c2) c2 = p2 + p0_perp * buff2;
|
||||
|
||||
vec2 i03;
|
||||
find_intersection(c0, v01, c3, v21, i03);
|
||||
corners = vec2[5](c0, c1, i03, c2, c3);
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
|
||||
|
||||
void set_adjascent_info(vec2 c0, vec2 tangent,
|
||||
int degree, int mult, int flip,
|
||||
vec2 adj[3],
|
||||
out float has,
|
||||
out float bevel,
|
||||
out float angle
|
||||
){
|
||||
float joint_type = v_joint_type[0];
|
||||
|
||||
has = 0;
|
||||
bevel = 0;
|
||||
angle = 0;
|
||||
|
||||
vec2 new_adj[3];
|
||||
int adj_degree = get_reduced_control_points(
|
||||
adj[0], adj[1], adj[2], new_adj
|
||||
);
|
||||
has = float(adj_degree > 0);
|
||||
if(has == 1){
|
||||
vec2 adj = new_adj[mult * adj_degree - flip];
|
||||
angle = flip * angle_between_vectors(c0 - adj, tangent);
|
||||
}
|
||||
// Decide on joint type
|
||||
bool one_linear = (degree == 1 || adj_degree == 1);
|
||||
bool should_bevel = (
|
||||
(joint_type == AUTO_JOINT && one_linear) ||
|
||||
joint_type == BEVEL_JOINT
|
||||
);
|
||||
bevel = should_bevel ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
|
||||
void set_previous_and_next(vec2 controls[3], int degree){
|
||||
float a_tol = 1e-10;
|
||||
|
||||
if(distance(prev_bp[2], bp[0]) < a_tol){
|
||||
vec2 tangent = controls[1] - controls[0];
|
||||
set_adjascent_info(
|
||||
controls[0], tangent, degree, 1, 1,
|
||||
vec2[3](prev_bp[0], prev_bp[1], prev_bp[2]),
|
||||
has_prev, bevel_start, angle_from_prev
|
||||
);
|
||||
}
|
||||
if(distance(next_bp[0], bp[2]) < a_tol){
|
||||
vec2 tangent = controls[degree - 1] - controls[degree];
|
||||
set_adjascent_info(
|
||||
controls[degree], tangent, degree, 0, -1,
|
||||
vec2[3](next_bp[0], next_bp[1], next_bp[2]),
|
||||
has_next, bevel_end, angle_to_next
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void set_gl_Position(vec2 p){
|
||||
vec2 result = p / scale;
|
||||
result.x /= aspect_ratio;
|
||||
gl_Position = vec4(result, 0.0, 1.0);
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
vec2 controls[3];
|
||||
int degree = get_reduced_control_points(bp[0], bp[1], bp[2], controls);
|
||||
bezier_degree = float(degree);
|
||||
|
||||
// Null curve or linear with higher index than needed
|
||||
if(degree == 0) return;
|
||||
|
||||
set_previous_and_next(controls, degree);
|
||||
|
||||
// Find uv conversion matrix
|
||||
mat3 xy_to_uv = get_xy_to_uv(controls[0], controls[1]);
|
||||
float scale_factor = length(controls[1] - controls[0]);
|
||||
uv_anti_alias_width = anti_alias_width / scale_factor;
|
||||
uv_b2 = (xy_to_uv * vec3(bp[2], 1.0)).xy;
|
||||
|
||||
// Corners of a bounding region around curve
|
||||
vec2 corners[5];
|
||||
int n_corners = get_corners(controls, degree, corners);
|
||||
|
||||
// Get style info aligned to the corners
|
||||
float stroke_widths[5];
|
||||
vec4 stroke_colors[5];
|
||||
int index_map[5];
|
||||
if(n_corners == 4) index_map = int[5](0, 0, 2, 2, 2);
|
||||
else index_map = int[5](0, 0, 1, 2, 2);
|
||||
for(int i = 0; i < 5; i++){
|
||||
stroke_widths[i] = v_stroke_width[index_map[i]];
|
||||
stroke_colors[i] = v_color[index_map[i]];
|
||||
}
|
||||
|
||||
// Emit each corner
|
||||
for(int i = 0; i < n_corners; i++){
|
||||
vec2 corner = corners[i];
|
||||
uv_coords = (xy_to_uv * vec3(corner, 1.0)).xy;
|
||||
|
||||
uv_stroke_width = stroke_widths[i] / scale_factor;
|
||||
color = stroke_colors[i];
|
||||
|
||||
set_gl_Position(corner);
|
||||
EmitVertex();
|
||||
}
|
||||
EndPrimitive();
|
||||
}
|
||||
30
manimlib/shaders/quadratic_bezier_stroke_vert.glsl
Normal file
30
manimlib/shaders/quadratic_bezier_stroke_vert.glsl
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#version 330
|
||||
|
||||
in vec2 point;
|
||||
in vec2 prev_point;
|
||||
in vec2 next_point;
|
||||
|
||||
in float stroke_width;
|
||||
in vec4 color;
|
||||
in float joint_type;
|
||||
|
||||
out vec2 bp; // Bezier control point
|
||||
out vec2 prev_bp;
|
||||
out vec2 next_bp;
|
||||
|
||||
out float v_stroke_width;
|
||||
out vec4 v_color;
|
||||
out float v_joint_type;
|
||||
|
||||
// TODO, this should maybe depent on scale
|
||||
const float STROKE_WIDTH_CONVERSION = 0.01;
|
||||
|
||||
void main(){
|
||||
v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width;
|
||||
v_color = color;
|
||||
v_joint_type = joint_type;
|
||||
|
||||
bp = point;
|
||||
prev_bp = prev_point;
|
||||
next_bp = next_point;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue