diff --git a/manimlib/shaders/quadratic_bezier_distance.glsl b/manimlib/shaders/quadratic_bezier_distance.glsl new file mode 100644 index 00000000..c928b254 --- /dev/null +++ b/manimlib/shaders/quadratic_bezier_distance.glsl @@ -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); +} \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_fill_frag.glsl b/manimlib/shaders/quadratic_bezier_fill_frag.glsl new file mode 100644 index 00000000..c2cc5b67 --- /dev/null +++ b/manimlib/shaders/quadratic_bezier_fill_frag.glsl @@ -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; +} \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_fill_geom.glsl b/manimlib/shaders/quadratic_bezier_fill_geom.glsl new file mode 100644 index 00000000..a17d6399 --- /dev/null +++ b/manimlib/shaders/quadratic_bezier_fill_geom.glsl @@ -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); + } +} + diff --git a/manimlib/shaders/quadratic_bezier_fill_vert.glsl b/manimlib/shaders/quadratic_bezier_fill_vert.glsl new file mode 100644 index 00000000..9734ab6c --- /dev/null +++ b/manimlib/shaders/quadratic_bezier_fill_vert.glsl @@ -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; +} \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_geometry_functions.glsl b/manimlib/shaders/quadratic_bezier_geometry_functions.glsl new file mode 100644 index 00000000..d5cd01d0 --- /dev/null +++ b/manimlib/shaders/quadratic_bezier_geometry_functions.glsl @@ -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; + } +} \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_stroke_frag.glsl b/manimlib/shaders/quadratic_bezier_stroke_frag.glsl new file mode 100644 index 00000000..8055bfc5 --- /dev/null +++ b/manimlib/shaders/quadratic_bezier_stroke_frag.glsl @@ -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); +} \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_stroke_geom.glsl b/manimlib/shaders/quadratic_bezier_stroke_geom.glsl new file mode 100644 index 00000000..7b8eedef --- /dev/null +++ b/manimlib/shaders/quadratic_bezier_stroke_geom.glsl @@ -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(); +} \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_stroke_vert.glsl b/manimlib/shaders/quadratic_bezier_stroke_vert.glsl new file mode 100644 index 00000000..8170de62 --- /dev/null +++ b/manimlib/shaders/quadratic_bezier_stroke_vert.glsl @@ -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; +} \ No newline at end of file