From 870e88f8c9ef95af11c70011006aae39d89f2b89 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 17 Jan 2023 15:46:09 -0800 Subject: [PATCH] First attempt at finding uv coords from 3d space instead of 2d --- manimlib/shaders/inserts/get_xy_to_uv.glsl | 139 ++++++++++++++++++ .../shaders/quadratic_bezier_stroke/geom.glsl | 17 ++- 2 files changed, 150 insertions(+), 6 deletions(-) diff --git a/manimlib/shaders/inserts/get_xy_to_uv.glsl b/manimlib/shaders/inserts/get_xy_to_uv.glsl index 5f3e2deb..f7df5107 100644 --- a/manimlib/shaders/inserts/get_xy_to_uv.glsl +++ b/manimlib/shaders/inserts/get_xy_to_uv.glsl @@ -27,6 +27,27 @@ vec2 xs_on_clean_parabola(vec2 b0, vec2 b1, vec2 b2){ } +vec2 xs_on_clean_parabola(vec3 b0, vec3 b1, vec3 b2){ + /* + Given three control points for a quadratic bezier, + this returns the two values (x0, x2) such that the + section of the parabola y = x^2 between those values + is isometric to the given quadratic bezier. + + Adapated from https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html + */ + vec3 dd = 2 * b1 - b0 - b2; + + float u0 = dot(b1 - b0, dd); + float u2 = dot(b2 - b1, dd); + vec3 cp = cross(b2 - b0, dd); + float sgn = sign(cp.z); + float denom = sgn * length(cp); + + return vec2(u0 / denom, u2 / denom); +} + + mat3 map_point_pairs(vec2 src0, vec2 src1, vec2 dest0, vec2 dest1){ /* Returns an orthogonal matrix which will map @@ -55,6 +76,124 @@ mat3 map_point_pairs(vec2 src0, vec2 src1, vec2 dest0, vec2 dest1){ } +mat4 map_triangles(vec3 src0, vec3 src1, vec3 src2, vec3 dst0, vec3 dst1, vec3 dst2){ + /* + Return an affine transform which maps the triangle (src0, src1, src2) + onto the triangle (dst0, dst1, dst2) + */ + mat4 src_mat = mat4( + src0.x, src0.y, src0.z, 1.0, + src1.x, src1.y, src1.z, 1.0, + src2.x, src2.y, src2.z, 1.0, + 1.0, 1.0, 1.0, 1.0 + ); + mat4 dst_mat = mat4( + dst0.x, dst0.y, dst0.z, 1.0, + dst1.x, dst1.y, dst1.z, 1.0, + dst2.x, dst2.y, dst2.z, 1.0, + 1.0, 1.0, 1.0, 1.0 + ); + return dst_mat * inverse(src_mat); +} + + +mat4 map_point_pairs(vec3 src0, vec3 src1, vec3 dest0, vec3 dest1){ + /* + Returns an orthogonal matrix which will map + src0 onto dest0 and src1 onto dest1. + */ + mat4 shift1 = mat4( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + -src0.x, -src0.y, -src0.z, 1.0 + ); + mat4 shift2 = mat4( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + dest0.x, dest0.y, dest0.z, 1.0 + ); + + // Find rotation matrix between unit vectors in each direction + vec3 src_v = src1 - src0; + vec3 dst_v = dest1 - dest0; + float src_len = length(src_v); + float dst_len = length(dst_v); + float scale = dst_len / src_len; + src_v /= src_len; + dst_v /= dst_len; + + vec3 cp = cross(src_v, dst_v); + float dp = dot(src_v, dst_v); + + float s = length(cp); // Sine of the angle between them + float c = dp; // Cosine of the angle between them + + if(s < 1e-8){ + // No rotation needed + return shift2 * shift1; + } + + vec3 axis = cp / s; // Axis of rotation + float oc = 1.0 - c; + float ax = axis.x; + float ay = axis.y; + float az = axis.z; + + // Rotation matrix about axis, with a given angle corresponding to s and c. + mat4 rotate = scale * mat4( + oc * ax * ax + c, oc * ax * ay + az * s, oc * az * ax - ay * s, 0.0, + oc * ax * ay - az * s, oc * ay * ay + c, oc * ay * az + ax * s, 0.0, + oc * az * ax + ay * s, oc * ay * az - ax * s, oc * az * az + c, 0.0, + 0.0, 0.0, 0.0, 1.0 / scale + ); + + return shift2 * rotate * shift1; +} + + +mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 b2, float temp_is_linear, out float is_linear){ + /* + Returns a matrix for an affine transformation which maps a set of quadratic + bezier controls points into a new coordinate system such that the bezier curve + coincides with y = x^2, or in the case of a linear curve, it's mapped to the x-axis. + */ + vec3 dest0; + vec3 dest1; + vec3 dest2; + vec3 src1; + is_linear = temp_is_linear; + // Portions of the parabola y = x^2 where abs(x) exceeds + // this value are treated as straight lines. + float thresh = 2.0; + if (!bool(is_linear)){ + vec2 xs = xs_on_clean_parabola(b0, b1, b2); + float x0 = xs.x; + float x2 = xs.y; + if((x0 > thresh && x2 > thresh) || (x0 < -thresh && x2 < -thresh)){ + is_linear = 1.0; + }else{ + dest0 = vec3(x0, x0 * x0, 0.0); + dest1 = vec3(0.5 * (x0 + x2), x0 * x2, 0.0); + dest2 = vec3(x2, x2 * x2, 0.0); + src1 = b1; + } + } + // Check if is_linear status changed above + if (bool(is_linear)){ + dest0 = vec3(0.0, 0.0, 0.0); + dest1 = vec3(0.0, 1.0, 0.0); + dest2 = vec3(1.0, 0.0, 0.0); + vec3 v = b2 - b0; + src1 = b0 + length(v) * normalize(cross(v, vec3(0, 0, 1))); + } + + // return map_point_pairs(b0, b2, dest0, dest1); + return map_triangles(b0, src1, b2, dest0, dest1, dest2); +} + + mat3 get_xy_to_uv(vec2 b0, vec2 b1, vec2 b2, float temp_is_linear, out float is_linear){ /* Returns a matrix for an affine transformation which maps a set of quadratic diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl index 9f0231b3..558543e1 100644 --- a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -112,9 +112,9 @@ void get_corners( // Unit normal and joint angles vec3 normal0 = get_joint_normal(v_joint_product[0]); vec3 normal2 = get_joint_normal(v_joint_product[2]); - // Chose the normal in the positive z direction - normal0 *= sign(normal0.z); - normal2 *= sign(normal2.z); + + // Make sure normals point in the same direction + if(dot(normal0, normal2) < 0) normal2 *= -1; // Perpendicular vectors to the left of the curve vec3 p0_perp; @@ -125,6 +125,9 @@ void get_corners( }else{ p0_perp = buff0 * normal0; p2_perp = buff2 * normal2; + // vec3 to_cam = transpose(camera_rotation)[2]; + // p0_perp = buff0 * to_cam; + // p2_perp = buff2 * to_cam; } vec3 p1_perp = 0.5 * (p0_perp + p2_perp); @@ -175,9 +178,10 @@ void main() { // coincides with y = x^2, between some values x0 and x2. Or, in // the case of a linear curve (bezier degree 1), just put it on // the segment from (0, 0) to (1, 0) - mat3 xy_to_uv = get_xy_to_uv(p0.xy, p1.xy, p2.xy, is_linear, is_linear); + // mat3 xy_to_uv = get_xy_to_uv(p0.xy, p1.xy, p2.xy, is_linear, is_linear); + mat4 xyz_to_uv = get_xyz_to_uv(p0, p1, p2, is_linear, is_linear); - float uv_scale_factor = length(xy_to_uv[0].xy); + float uv_scale_factor = length(xyz_to_uv[0].xyz); float scaled_aaw = anti_alias_width * (frame_shape.y / pixel_shape.y); uv_anti_alias_width = uv_scale_factor * scaled_aaw; @@ -187,7 +191,8 @@ void main() { // Emit each corner for(int i = 0; i < 6; i++){ int vert_index = i / 2; - uv_coords = (xy_to_uv * vec3(corners[i].xy, 1)).xy; + // uv_coords = (xy_to_uv * vec3(corners[i].xy, 1)).xy; + uv_coords = (xyz_to_uv * vec4(corners[i], 1)).xy; uv_stroke_width = uv_scale_factor * v_stroke_width[vert_index]; color = finalize_color( v_color[vert_index],