From 00dcc14df1f0c52364789e8873beefbe557ae4b0 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sun, 31 May 2020 17:02:05 -0700 Subject: [PATCH] Fixed the random-dimples-on-zeros bug while fixing up the fill shaders --- manimlib/mobject/types/vectorized_mobject.py | 11 +- .../shaders/quadratic_bezier_fill_frag.glsl | 44 ++++--- .../shaders/quadratic_bezier_fill_geom.glsl | 118 +++++------------- .../shaders/quadratic_bezier_fill_vert.glsl | 9 +- .../quadratic_bezier_geometry_functions.glsl | 32 ++--- 5 files changed, 80 insertions(+), 134 deletions(-) diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index c50633b3..98565ca0 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -64,7 +64,6 @@ class VMobject(Mobject): ('point', np.float32, (3,)), ('color', np.float32, (4,)), ('fill_all', np.float32, (1,)), - ('orientation', np.float32, (1,)), ], "stroke_dtype": [ ("point", np.float32, (3,)), @@ -984,16 +983,13 @@ class VMobject(Mobject): # Triangulate inner_verts = points[inner_vert_indices] - inner_tri_indices = inner_vert_indices[ - earclip_triangulation(inner_verts, rings) - ] + inner_tri_indices = inner_vert_indices[earclip_triangulation(inner_verts, rings)] tri_indices = np.hstack([indices, inner_tri_indices]) return tri_indices def get_fill_shader_data(self): points = self.points - orientation = self.get_orientation() tri_indices = self.get_triangulation(orientation) @@ -1007,8 +1003,9 @@ class VMobject(Mobject): # are on the boundary, and the rest are in the interior data["fill_all"][:len(points)] = 0 data["fill_all"][len(points):] = 1 - data["orientation"] = orientation - + # Always send points in a positively oriented way + if orientation < 0: + data["point"][:len(points)] = points[::-1] return data diff --git a/manimlib/shaders/quadratic_bezier_fill_frag.glsl b/manimlib/shaders/quadratic_bezier_fill_frag.glsl index dded4521..b16b6690 100644 --- a/manimlib/shaders/quadratic_bezier_fill_frag.glsl +++ b/manimlib/shaders/quadratic_bezier_fill_frag.glsl @@ -1,11 +1,10 @@ #version 330 in vec4 color; -in float fill_type; +in float fill_all; // Either 0 or 1e in float uv_anti_alias_width; in vec2 uv_coords; -in vec2 wz_coords; in vec2 uv_b2; in float bezier_degree; @@ -16,7 +15,7 @@ const float FILL_OUTSIDE = 1; const float FILL_ALL = 2; -// Needed for quadratic_bezier_distance +// Needed for quadratic_bezier_distance insertion below float modify_distance_for_endpoints(vec2 p, float dist, float t){ return dist; } @@ -26,27 +25,36 @@ float modify_distance_for_endpoints(vec2 p, float dist, float t){ // 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.0; - return min_dist_to_curve(uv_coords, uv_b2, bezier_degree, false); + // For really flat curves, just take the distance to the curve + if(bezier_degree < 2 || abs(uv_b2.y / uv_b2.x) < uv_anti_alias_width){ + return min_dist_to_curve(uv_coords, uv_b2, bezier_degree, false); + } + // This converts uv_coords to a 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. + float u2 = uv_b2.x; + float v2 = uv_b2.y; + mat2 to_simple_space = mat2( + v2, 0, + 2 - u2, 4 * v2 + ); + vec2 p = to_simple_space * uv_coords; + + float Fp = sign(v2) * (p.x * p.x - p.y); + + vec2 grad = vec2( + - 2 * p.x * v2, // del C / del u + 4 * v2 - 4 * p.x * (2 - u2) // del C / del v + ); + return Fp / length(grad); } void main() { if (color.a == 0) discard; frag_color = color; - if (fill_type == FILL_ALL) return; + // TODO, Add shading based on normal vector, light position and gloss + if (fill_all == 1.0) return; frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width); - // frag_color.a += 0.2; } diff --git a/manimlib/shaders/quadratic_bezier_fill_geom.glsl b/manimlib/shaders/quadratic_bezier_fill_geom.glsl index 5d850350..764e66d3 100644 --- a/manimlib/shaders/quadratic_bezier_fill_geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill_geom.glsl @@ -11,17 +11,13 @@ uniform vec3 frame_center; in vec3 bp[3]; in vec4 v_color[3]; in float v_fill_all[3]; -in float v_orientation[3]; out vec4 color; -out float fill_type; +out float fill_all; 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; @@ -38,30 +34,6 @@ const float SQRT5 = 2.236068; #INSERT quadratic_bezier_geometry_functions.glsl #INSERT scale_and_shift_point_for_frame.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 emit_simple_triangle(){ for(int i = 0; i < 3; i++){ color = v_color[i]; @@ -75,29 +47,17 @@ void emit_simple_triangle(){ } -void emit_pentagon(vec2 bp0, vec2 bp1, vec2 bp2, float orientation){ +void emit_pentagon(vec2 bp0, vec2 bp1, vec2 bp2){ // 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; - c_orient *= orientation; - - bool fill_in = (c_orient > 0); - fill_type = fill_in ? FILL_INSIDE : FILL_OUTSIDE; - - // float orient = in_or_out * c_orient; - // Normal vectors // Rotate tangent vector 90-degrees clockwise - // if the curve is positively oriented, otherwise - // rotate it 90-degrees counterclockwise - vec2 n01 = orientation * vec2(t01.y, -t01.x); - vec2 n12 = orientation * vec2(t12.y, -t12.x); + vec2 n01 = vec2(t01.y, -t01.x); + vec2 n12 = vec2(t12.y, -t12.x); + + float c_orient = sign(cross(t01, t12)); + bool fill_in = (c_orient > 0); float aaw = anti_alias_width; vec2 nudge1 = fill_in ? 0.5 * aaw * (n01 + n12) : vec2(0); @@ -113,29 +73,19 @@ void emit_pentagon(vec2 bp0, vec2 bp1, vec2 bp2, float orientation){ if(!fill_in) 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); + int nearest_bp_index_map[5] = int[5](0, 0, 1, 2, 2); for(int i = 0; i < 5; i++){ vec2 corner = corners[coords_index_map[i]]; + float z = bp[nearest_bp_index_map[i]].z; uv_coords = (xy_to_uv * vec3(corner, 1)).xy; - wz_coords = (xy_to_wz * vec3(corner, 1)).xy; - float z; // 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]; - z = bp[0].z; - } - else if(i == 2){ - color = v_color[1]; - z = bp[1].z; - } - else{ - color = v_color[2]; - z = bp[2].z; - } + if(i < 2) color = v_color[0]; + else if(i == 2) color = v_color[1]; + else color = v_color[2]; gl_Position = vec4( scale_and_shift_point_for_frame(vec3(corner, z)), 1.0 @@ -147,32 +97,30 @@ void emit_pentagon(vec2 bp0, vec2 bp1, vec2 bp2, float orientation){ void main(){ - float fill_all = v_fill_all[0]; + fill_all = v_fill_all[0]; if(fill_all == 1){ - fill_type = FILL_ALL; emit_simple_triangle(); - }else{ - vec2 new_bp[3]; - int n = get_reduced_control_points(bp[0].xy, bp[1].xy, bp[2].xy, new_bp); - bezier_degree = float(n); - float orientation = v_orientation[0]; - - 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]; - } - - emit_pentagon(bp0, bp1, bp2, orientation); + return; } + + vec2 new_bp[3]; + int n = get_reduced_control_points(bp[0].xy, bp[1].xy, bp[2].xy, 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]; + } + + emit_pentagon(bp0, bp1, bp2); } diff --git a/manimlib/shaders/quadratic_bezier_fill_vert.glsl b/manimlib/shaders/quadratic_bezier_fill_vert.glsl index 48486841..6284070e 100644 --- a/manimlib/shaders/quadratic_bezier_fill_vert.glsl +++ b/manimlib/shaders/quadratic_bezier_fill_vert.glsl @@ -2,23 +2,16 @@ in vec3 point; in vec4 color; -// fill_all is 0 or 1 -in float fill_all; -// orientation is +1 for counterclockwise curves, -1 otherwise -in float orientation; +in float fill_all; // Either 0 or 1 out vec3 bp; // Bezier control point out vec4 v_color; out float v_fill_all; -out float v_orientation; - #INSERT rotate_point_for_frame.glsl - void main(){ bp = rotate_point_for_frame(point); v_color = color; v_fill_all = fill_all; - v_orientation = orientation; } \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_geometry_functions.glsl b/manimlib/shaders/quadratic_bezier_geometry_functions.glsl index d5cd01d0..e28d4360 100644 --- a/manimlib/shaders/quadratic_bezier_geometry_functions.glsl +++ b/manimlib/shaders/quadratic_bezier_geometry_functions.glsl @@ -30,25 +30,25 @@ mat3 get_xy_to_uv(vec2 b0, vec2 b1){ // 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; + float length_threshold = 1e-6; + float angle_threshold = 1e-3; 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 + // bool aligned = abs(cross(normalize(v01), normalize(v12))) < angle_threshold; + bool aligned = acos(dot(normalize(v01), normalize(v12))) < angle_threshold; + bool distinct_01 = length(v01) > length_threshold; // v01 is considered nonzero + bool distinct_12 = length(v12) > length_threshold; // 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){ + + bool quadratic = (n_uniques == 2) && !aligned; + bool linear = (n_uniques == 1) || ((n_uniques == 2) && aligned); + bool constant = (n_uniques == 0); + if(quadratic){ + new_points[0] = b0; + new_points[1] = b1; + new_points[2] = b2; + return 2; + }else if(linear){ new_points[0] = b0; new_points[1] = b2; return 1;