From 994c711e34466cf10bbbedfcfa9e1974565ec488 Mon Sep 17 00:00:00 2001 From: wuyize Date: Mon, 6 Feb 2023 18:09:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=9E=E7=8E=B0=E5=9B=BE?= =?UTF-8?q?=E5=85=83=E7=BB=98=E5=88=B6=E5=88=B0QImage=E7=9A=84=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ArchitectureColoredPainting.vcxproj | 7 +- ...rchitectureColoredPainting.vcxproj.filters | 21 +- ArchitectureColoredPainting/MainWindow.qrc | 1 + .../Shaders/element.comp | 1053 +++++++++++++++++ .../src/Editor/GraphicElement.cpp | 2 +- .../src/Renderer/Painting/Element.cpp | 15 + .../src/Renderer/Painting/Element.h | 3 + .../src/Renderer/Painting/Painting.cpp | 5 +- .../src/Renderer/Preview/ElementRenderer.cpp | 160 +++ .../src/Renderer/Preview/ElementRenderer.h | 35 + 10 files changed, 1290 insertions(+), 12 deletions(-) create mode 100644 ArchitectureColoredPainting/Shaders/element.comp create mode 100644 ArchitectureColoredPainting/src/Renderer/Preview/ElementRenderer.cpp create mode 100644 ArchitectureColoredPainting/src/Renderer/Preview/ElementRenderer.h diff --git a/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj b/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj index 5f5e54c..b85acdd 100644 --- a/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj +++ b/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj @@ -133,6 +133,7 @@ + @@ -152,6 +153,7 @@ + @@ -159,7 +161,9 @@ - + + Document + @@ -188,6 +192,7 @@ + diff --git a/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj.filters b/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj.filters index d6f4019..68a6fe6 100644 --- a/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj.filters +++ b/ArchitectureColoredPainting/ArchitectureColoredPainting.vcxproj.filters @@ -59,6 +59,12 @@ {96f98afe-4250-44cb-a505-682a1d5932c3} + + {2a8e109f-7791-46ad-8c86-fe22a651cbe7} + + + {7ead1a66-586a-4584-ae80-9e7a4e667364} + @@ -192,6 +198,9 @@ Source Files\Editor\util + + Source Files\Renderer\Preview + @@ -284,6 +293,9 @@ Resource Files + + Resource Files\Shaders + @@ -360,9 +372,6 @@ Header Files\Renderer\Painting - - Header Files\Editor\util - Header Files\Renderer\Painting @@ -387,12 +396,12 @@ Header Files\Renderer\Painting - - Header Files\Renderer\Painting - Header Files\Renderer\Painting + + Header Files\Renderer\Preview + diff --git a/ArchitectureColoredPainting/MainWindow.qrc b/ArchitectureColoredPainting/MainWindow.qrc index 8a9a1ea..a653907 100644 --- a/ArchitectureColoredPainting/MainWindow.qrc +++ b/ArchitectureColoredPainting/MainWindow.qrc @@ -23,6 +23,7 @@ images/icon_window_restore.png darkstyle.qss lightstyle.qss + Shaders/element.comp qt.conf diff --git a/ArchitectureColoredPainting/Shaders/element.comp b/ArchitectureColoredPainting/Shaders/element.comp new file mode 100644 index 0000000..7ccc0b6 --- /dev/null +++ b/ArchitectureColoredPainting/Shaders/element.comp @@ -0,0 +1,1053 @@ +#version 450 core + +layout(local_size_x = 8, local_size_y = 8) in; +layout(rgba8, binding = 0) uniform image2D gBaseColor; + +layout(std430, binding = 10) buffer pathBuffer +{ + vec2 path[]; +}; + +layout(std430, binding = 11) buffer styleBuffer +{ + float style[]; +}; + +uniform int pathSize; +uniform int styleSize; +uniform vec2 leftTop; +uniform float pixelRatio; + +/************************************************************************************/ + +// Modified from http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c +// Credits to Doublefresh for hinting there +int solve_quadric(vec2 coeffs, inout vec2 roots) +{ + + // normal form: x^2 + px + q = 0 + float p = coeffs[1] / 2.; + float q = coeffs[0]; + + float D = p * p - q; + + if (D < 0.) + { + return 0; + } + else + { + roots[0] = -sqrt(D) - p; + roots[1] = sqrt(D) - p; + + return 2; + } +} + +// From Trisomie21 +// But instead of his cancellation fix i'm using a newton iteration +int solve_cubic(vec3 coeffs, inout vec3 r) +{ + + float a = coeffs[2]; + float b = coeffs[1]; + float c = coeffs[0]; + + float p = b - a * a / 3.0; + float q = a * (2.0 * a * a - 9.0 * b) / 27.0 + c; + float p3 = p * p * p; + float d = q * q + 4.0 * p3 / 27.0; + float offset = -a / 3.0; + if (d >= 0.0) + { // Single solution + float z = sqrt(d); + float u = (-q + z) / 2.0; + float v = (-q - z) / 2.0; + u = sign(u) * pow(abs(u), 1.0 / 3.0); + v = sign(v) * pow(abs(v), 1.0 / 3.0); + r[0] = offset + u + v; + + // Single newton iteration to account for cancellation + float f = ((r[0] + a) * r[0] + b) * r[0] + c; + float f1 = (3. * r[0] + 2. * a) * r[0] + b; + + r[0] -= f / f1; + + return 1; + } + float u = sqrt(-p / 3.0); + float v = acos(-sqrt(-27.0 / p3) * q / 2.0) / 3.0; + float m = cos(v), n = sin(v) * 1.732050808; + + // Single newton iteration to account for cancellation + //(once for every root) + r[0] = offset + u * (m + m); + r[1] = offset - u * (n + m); + r[2] = offset + u * (n - m); + + vec3 f = ((r + a) * r + b) * r + c; + vec3 f1 = (3. * r + 2. * a) * r + b; + + r -= f / f1; + + return 3; +} + +int segment_int_test(vec2 uv, vec2 p0, vec2 p1) +{ + p0 -= uv; + p1 -= uv; + + int ret; + + if (p0.y * p1.y < 0.) + { + vec2 nor = p0 - p1; + nor = vec2(nor.y, -nor.x); + + float sgn; + + if (p0.y > p1.y) + { + sgn = 1.; + } + else + { + sgn = -1.; + } + + if (dot(nor, p0) * sgn < 0.) + { + ret = 0; + } + else + { + ret = 1; + } + } + else + { + ret = 0; + } + + return ret; +} + +int cubic_bezier_int_test(vec2 uv, vec2 p0, vec2 p1, vec2 p2, vec2 p3) +{ + + float cu = (-p0.y + 3. * p1.y - 3. * p2.y + p3.y); + float qu = (3. * p0.y - 6. * p1.y + 3. * p2.y); + float li = (-3. * p0.y + 3. * p1.y); + float co = p0.y - uv.y; + + vec3 roots = vec3(1e38); + int n_roots; + + int n_ints = 0; + + if (uv.x < min(min(p0.x, p1.x), min(p2.x, p3.x))) + { + if (uv.y >= min(p0.y, p3.y) && uv.y <= max(p0.y, p3.y)) + { + n_ints = 1; + } + } + else + { + + if (abs(cu) < .0001) + { + n_roots = solve_quadric(vec2(co / qu, li / qu), roots.xy); + } + else + { + n_roots = solve_cubic(vec3(co / cu, li / cu, qu / cu), roots); + } + + for (int i = 0; i < n_roots; i++) + { + if (roots[i] >= 0. && roots[i] <= 1.) + { + float x_pos = -p0.x + 3. * p1.x - 3. * p2.x + p3.x; + x_pos = x_pos * roots[i] + 3. * p0.x - 6. * p1.x + 3. * p2.x; + x_pos = x_pos * roots[i] + -3. * p0.x + 3. * p1.x; + x_pos = x_pos * roots[i] + p0.x; + + if (x_pos > uv.x) + { + n_ints++; + } + } + } + } + + return n_ints; +} + +bvec3 segment_sign_test(vec2 uv, vec2 p0, vec2 p1) +{ + p0 -= uv; + p1 -= uv; + bvec3 ret; + vec2 nor = p0 - p1; + nor = vec2(nor.y, -nor.x); + + float sgn; + + if (p0.y > p1.y) + { + sgn = 1.; + } + else + { + sgn = -1.; + } + + if (dot(nor, p0) * sgn < 0.) + { + if (p0.y * p1.y < 0.) + ret.y = false; + else + ret.y = false; + ret.xz = bvec2(false); + } + else + { + if (p0.y * p1.y < 0.) + ret.y = true; + else + ret.y = false; + ret.xz = bvec2(true); + } + return ret; +} + +bvec3 cubic_bezier_sign_test(vec2 uv, vec2 p0, vec2 p1, vec2 p2, vec2 p3) +{ + // if(abs(p3.y-p0.y)< 1e-4) + // { + // return segment_sign_test(uv, p0,p3); + // } + + float cu = (-p0.y + 3. * p1.y - 3. * p2.y + p3.y); + float qu = (3. * p0.y - 6. * p1.y + 3. * p2.y); + float li = (-3. * p0.y + 3. * p1.y); + float co = p0.y - uv.y; + + vec3 roots = vec3(1e38); + int n_roots = solve_cubic(vec3(co / cu, li / cu, qu / cu), roots); + + // int n_ints = 0; + bvec3 result = bvec3(false); + for (int i = 0; i < 3; i++) + { + if (i < n_roots) + { + if (roots[i] >= 0. && roots[i] <= 1.) + { + float x_pos = -p0.x + 3. * p1.x - 3. * p2.x + p3.x; + x_pos = x_pos * roots[i] + 3. * p0.x - 6. * p1.x + 3. * p2.x; + x_pos = x_pos * roots[i] + -3. * p0.x + 3. * p1.x; + x_pos = x_pos * roots[i] + p0.x; + + if (x_pos > uv.x) + { + result[1] = !result[1]; + } + } + } + } + + vec2 tang1 = p0.xy - p1.xy; + vec2 tang2 = p2.xy - p3.xy; + + vec2 nor1 = vec2(tang1.y, -tang1.x); + vec2 nor2 = vec2(tang2.y, -tang2.x); + + if (p0.y < p1.y) + { + if ((uv.y <= p0.y) && (dot(uv - p0.xy, nor1) > 0.)) + { + result[0] = !result[0]; + } + } + else + { + if (!(uv.y <= p0.y) && !(dot(uv - p0.xy, nor1) > 0.)) + { + result[0] = !result[0]; + } + } + + if (p2.y < p3.y) + { + if (!(uv.y <= p3.y) && dot(uv - p3.xy, nor2) > 0.) + { + result[2] = !result[2]; + } + } + else + { + if ((uv.y <= p3.y) && !(dot(uv - p3.xy, nor2) > 0.)) + { + result[2] = !result[2]; + } + } + + return result; + // if(n_ints==0 || n_ints==2 || n_ints==4){ + // return 1; + // } + // else{ + // return 0; + // } +} + +const float eps = .000005; +const int halley_iterations = 8; +// lagrange positive real root upper bound +// see for example: https://doi.org/10.1016/j.jsc.2014.09.038 +float upper_bound_lagrange5(float a0, float a1, float a2, float a3, float a4) +{ + + vec4 coeffs1 = vec4(a0, a1, a2, a3); + + vec4 neg1 = max(-coeffs1, vec4(0)); + float neg2 = max(-a4, 0.); + + const vec4 indizes1 = vec4(0, 1, 2, 3); + const float indizes2 = 4.; + + vec4 bounds1 = pow(neg1, 1. / (5. - indizes1)); + float bounds2 = pow(neg2, 1. / (5. - indizes2)); + + vec2 min1_2 = min(bounds1.xz, bounds1.yw); + vec2 max1_2 = max(bounds1.xz, bounds1.yw); + + float maxmin = max(min1_2.x, min1_2.y); + float minmax = min(max1_2.x, max1_2.y); + + float max3 = max(max1_2.x, max1_2.y); + + float max_max = max(max3, bounds2); + float max_max2 = max(min(max3, bounds2), max(minmax, maxmin)); + + return max_max + max_max2; +} + +// lagrange upper bound applied to f(-x) to get lower bound +float lower_bound_lagrange5(float a0, float a1, float a2, float a3, float a4) +{ + + vec4 coeffs1 = vec4(-a0, a1, -a2, a3); + + vec4 neg1 = max(-coeffs1, vec4(0)); + float neg2 = max(-a4, 0.); + + const vec4 indizes1 = vec4(0, 1, 2, 3); + const float indizes2 = 4.; + + vec4 bounds1 = pow(neg1, 1. / (5. - indizes1)); + float bounds2 = pow(neg2, 1. / (5. - indizes2)); + + vec2 min1_2 = min(bounds1.xz, bounds1.yw); + vec2 max1_2 = max(bounds1.xz, bounds1.yw); + + float maxmin = max(min1_2.x, min1_2.y); + float minmax = min(max1_2.x, max1_2.y); + + float max3 = max(max1_2.x, max1_2.y); + + float max_max = max(max3, bounds2); + float max_max2 = max(min(max3, bounds2), max(minmax, maxmin)); + + return -max_max - max_max2; +} + +vec2 parametric_cub_bezier(float t, vec2 p0, vec2 p1, vec2 p2, vec2 p3) +{ + vec2 a0 = (-p0 + 3. * p1 - 3. * p2 + p3); + vec2 a1 = (3. * p0 - 6. * p1 + 3. * p2); + vec2 a2 = (-3. * p0 + 3. * p1); + vec2 a3 = p0; + + return (((a0 * t) + a1) * t + a2) * t + a3; +} + +void sort_roots3(inout vec3 roots) +{ + vec3 tmp; + + tmp[0] = min(roots[0], min(roots[1], roots[2])); + tmp[1] = max(roots[0], min(roots[1], roots[2])); + tmp[2] = max(roots[0], max(roots[1], roots[2])); + + roots = tmp; +} + +void sort_roots4(inout vec4 roots) +{ + vec4 tmp; + + vec2 min1_2 = min(roots.xz, roots.yw); + vec2 max1_2 = max(roots.xz, roots.yw); + + float maxmin = max(min1_2.x, min1_2.y); + float minmax = min(max1_2.x, max1_2.y); + + tmp[0] = min(min1_2.x, min1_2.y); + tmp[1] = min(maxmin, minmax); + tmp[2] = max(minmax, maxmin); + tmp[3] = max(max1_2.x, max1_2.y); + + roots = tmp; +} + +float eval_poly5(float a0, float a1, float a2, float a3, float a4, float x) +{ + + float f = ((((x + a4) * x + a3) * x + a2) * x + a1) * x + a0; + + return f; +} + +// halley's method +// basically a variant of newton raphson which converges quicker and has bigger basins of convergence +// see http://mathworld.wolfram.com/HalleysMethod.html +// or https://en.wikipedia.org/wiki/Halley%27s_method +float halley_iteration5(float a0, float a1, float a2, float a3, float a4, float x) +{ + + float f = ((((x + a4) * x + a3) * x + a2) * x + a1) * x + a0; + float f1 = (((5. * x + 4. * a4) * x + 3. * a3) * x + 2. * a2) * x + a1; + float f2 = ((20. * x + 12. * a4) * x + 6. * a3) * x + 2. * a2; + + return x - (2. * f * f1) / (2. * f1 * f1 - f * f2); +} + +float halley_iteration4(vec4 coeffs, float x) +{ + + float f = (((x + coeffs[3]) * x + coeffs[2]) * x + coeffs[1]) * x + coeffs[0]; + float f1 = ((4. * x + 3. * coeffs[3]) * x + 2. * coeffs[2]) * x + coeffs[1]; + float f2 = (12. * x + 6. * coeffs[3]) * x + 2. * coeffs[2]; + + return x - (2. * f * f1) / (2. * f1 * f1 - f * f2); +} + +// Modified from http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c +// Credits to Doublefresh for hinting there +int solve_quartic(vec4 coeffs, inout vec4 s) +{ + + float a = coeffs[3]; + float b = coeffs[2]; + float c = coeffs[1]; + float d = coeffs[0]; + + /* substitute x = y - A/4 to eliminate cubic term: + x^4 + px^2 + qx + r = 0 */ + + float sq_a = a * a; + float p = -3. / 8. * sq_a + b; + float q = 1. / 8. * sq_a * a - 1. / 2. * a * b + c; + float r = -3. / 256. * sq_a * sq_a + 1. / 16. * sq_a * b - 1. / 4. * a * c + d; + + int num; + + /* doesn't seem to happen for me */ + // if(abs(r) -eps) + { + u = sqrt(abs(u)); + } + else + { + return 0; + } + + if (v > -eps) + { + v = sqrt(abs(v)); + } + else + { + return 0; + } + + vec2 quad_coeffs; + + quad_coeffs[0] = z - u; + quad_coeffs[1] = q < 0. ? -v : v; + + num = solve_quadric(quad_coeffs, s.xy); + + quad_coeffs[0] = z + u; + quad_coeffs[1] = q < 0. ? v : -v; + + vec2 tmp = vec2(1e38); + int old_num = num; + + num += solve_quadric(quad_coeffs, tmp); + if (old_num != num) + { + if (old_num == 0) + { + s[0] = tmp[0]; + s[1] = tmp[1]; + } + else + { // old_num == 2 + s[2] = tmp[0]; + s[3] = tmp[1]; + } + } + } + + /* resubstitute */ + + float sub = 1. / 4. * a; + + /* single halley iteration to fix cancellation */ + for (int i = 0; i < 4; i += 2) + { + if (i < num) + { + s[i] -= sub; + s[i] = halley_iteration4(coeffs, s[i]); + + s[i + 1] -= sub; + s[i + 1] = halley_iteration4(coeffs, s[i + 1]); + } + } + + return num; +} +float cubic_bezier_dis(vec2 uv, vec2 p0, vec2 p1, vec2 p2, vec2 p3, bool roundEnd) +{ + +// switch points when near to end point to minimize numerical error +// only needed when control point(s) very far away +#if 0 + vec2 mid_curve = parametric_cub_bezier(.5,p0,p1,p2,p3); + vec2 mid_points = (p0 + p3)/2.; + + vec2 tang = mid_curve-mid_points; + vec2 nor = vec2(tang.y,-tang.x); + + if(sign(dot(nor,uv-mid_curve)) != sign(dot(nor,p0-mid_curve))){ + vec2 tmp = p0; + p0 = p3; + p3 = tmp; + + tmp = p2; + p2 = p1; + p1 = tmp; + } +#endif + + vec2 a3 = (-p0 + 3. * p1 - 3. * p2 + p3); + vec2 a2 = (3. * p0 - 6. * p1 + 3. * p2); + vec2 a1 = (-3. * p0 + 3. * p1); + vec2 a0 = p0 - uv; + + // compute polynomial describing distance to current pixel dependent on a parameter t + float bc6 = dot(a3, a3); + float bc5 = 2. * dot(a3, a2); + float bc4 = dot(a2, a2) + 2. * dot(a1, a3); + float bc3 = 2. * (dot(a1, a2) + dot(a0, a3)); + float bc2 = dot(a1, a1) + 2. * dot(a0, a2); + float bc1 = 2. * dot(a0, a1); + float bc0 = dot(a0, a0); + + bc5 /= bc6; + bc4 /= bc6; + bc3 /= bc6; + bc2 /= bc6; + bc1 /= bc6; + bc0 /= bc6; + + // compute derivatives of this polynomial + + float b0 = bc1 / 6.; + float b1 = 2. * bc2 / 6.; + float b2 = 3. * bc3 / 6.; + float b3 = 4. * bc4 / 6.; + float b4 = 5. * bc5 / 6.; + + vec4 c1 = vec4(b1, 2. * b2, 3. * b3, 4. * b4) / 5.; + vec3 c2 = vec3(c1[1], 2. * c1[2], 3. * c1[3]) / 4.; + vec2 c3 = vec2(c2[1], 2. * c2[2]) / 3.; + float c4 = c3[1] / 2.; + + vec4 roots_drv = vec4(1e38); + + int num_roots_drv = solve_quartic(c1, roots_drv); + sort_roots4(roots_drv); + + float ub = upper_bound_lagrange5(b0, b1, b2, b3, b4); + float lb = lower_bound_lagrange5(b0, b1, b2, b3, b4); + + vec3 a = vec3(1e38); + vec3 b = vec3(1e38); + + vec3 roots = vec3(1e38); + + int num_roots = 0; + + // compute root isolating intervals by roots of derivative and outer root bounds + // only roots going form - to + considered, because only those result in a minimum + if (num_roots_drv == 4) + { + if (eval_poly5(b0, b1, b2, b3, b4, roots_drv[0]) > 0.) + { + a[0] = lb; + b[0] = roots_drv[0]; + num_roots = 1; + } + + if (sign(eval_poly5(b0, b1, b2, b3, b4, roots_drv[1])) != sign(eval_poly5(b0, b1, b2, b3, b4, roots_drv[2]))) + { + if (num_roots == 0) + { + a[0] = roots_drv[1]; + b[0] = roots_drv[2]; + num_roots = 1; + } + else + { + a[1] = roots_drv[1]; + b[1] = roots_drv[2]; + num_roots = 2; + } + } + + if (eval_poly5(b0, b1, b2, b3, b4, roots_drv[3]) < 0.) + { + if (num_roots == 0) + { + a[0] = roots_drv[3]; + b[0] = ub; + num_roots = 1; + } + else if (num_roots == 1) + { + a[1] = roots_drv[3]; + b[1] = ub; + num_roots = 2; + } + else + { + a[2] = roots_drv[3]; + b[2] = ub; + num_roots = 3; + } + } + } + else + { + if (num_roots_drv == 2) + { + if (eval_poly5(b0, b1, b2, b3, b4, roots_drv[0]) < 0.) + { + num_roots = 1; + a[0] = roots_drv[1]; + b[0] = ub; + } + else if (eval_poly5(b0, b1, b2, b3, b4, roots_drv[1]) > 0.) + { + num_roots = 1; + a[0] = lb; + b[0] = roots_drv[0]; + } + else + { + num_roots = 2; + + a[0] = lb; + b[0] = roots_drv[0]; + + a[1] = roots_drv[1]; + b[1] = ub; + } + } + else + { // num_roots_drv==0 + vec3 roots_snd_drv = vec3(1e38); + int num_roots_snd_drv = solve_cubic(c2, roots_snd_drv); + + vec2 roots_trd_drv = vec2(1e38); + int num_roots_trd_drv = solve_quadric(c3, roots_trd_drv); + num_roots = 1; + + a[0] = lb; + b[0] = ub; + } + + // further subdivide intervals to guarantee convergence of halley's method + // by using roots of further derivatives + vec3 roots_snd_drv = vec3(1e38); + int num_roots_snd_drv = solve_cubic(c2, roots_snd_drv); + sort_roots3(roots_snd_drv); + + int num_roots_trd_drv = 0; + vec2 roots_trd_drv = vec2(1e38); + + if (num_roots_snd_drv != 3) + { + num_roots_trd_drv = solve_quadric(c3, roots_trd_drv); + } + + for (int i = 0; i < 3; i++) + { + if (i < num_roots) + { + for (int j = 0; j < 3; j += 2) + { + if (j < num_roots_snd_drv) + { + if (a[i] < roots_snd_drv[j] && b[i] > roots_snd_drv[j]) + { + if (eval_poly5(b0, b1, b2, b3, b4, roots_snd_drv[j]) > 0.) + { + b[i] = roots_snd_drv[j]; + } + else + { + a[i] = roots_snd_drv[j]; + } + } + } + } + for (int j = 0; j < 2; j++) + { + if (j < num_roots_trd_drv) + { + if (a[i] < roots_trd_drv[j] && b[i] > roots_trd_drv[j]) + { + if (eval_poly5(b0, b1, b2, b3, b4, roots_trd_drv[j]) > 0.) + { + b[i] = roots_trd_drv[j]; + } + else + { + a[i] = roots_trd_drv[j]; + } + } + } + } + } + } + } + + float d0 = 1e38; + + // compute roots with halley's method + + for (int i = 0; i < 3; i++) + { + if (i < num_roots) + { + roots[i] = .5 * (a[i] + b[i]); + + for (int j = 0; j < halley_iterations; j++) + { + roots[i] = halley_iteration5(b0, b1, b2, b3, b4, roots[i]); + } + + // compute squared distance to nearest point on curve + if (roundEnd) + { + roots[i] = clamp(roots[i], 0., 1.); + vec2 to_curve = uv - parametric_cub_bezier(roots[i], p0, p1, p2, p3); + d0 = min(d0, dot(to_curve, to_curve)); + } + else + { + if (roots[i] < 0. || roots[i] > 1.) + d0 = min(d0, 1e38); + else + { + vec2 to_curve = uv - parametric_cub_bezier(roots[i], p0, p1, p2, p3); + d0 = min(d0, dot(to_curve, to_curve)); + } + } + } + } + + return sqrt(d0); +} + +int cubic_bezier_int_test2(vec2 uv, vec2 p0, vec2 p1, vec2 p2, vec2 p3, bool reverse) +{ + float cu = (-p0.y + 3. * p1.y - 3. * p2.y + p3.y); + float qu = (3. * p0.y - 6. * p1.y + 3. * p2.y); + float li = (-3. * p0.y + 3. * p1.y); + float co = p0.y - uv.y; + + vec3 roots = vec3(1e38); + int n_roots; + + int n_ints = 0; + + if (reverse ? uv.x > max(max(p0.x, p1.x), max(p2.x, p3.x)) : uv.x < min(min(p0.x, p1.x), min(p2.x, p3.x))) + { + if (uv.y >= min(p0.y, p3.y) && uv.y <= max(p0.y, p3.y)) + n_ints = 1; + } + else + { + if (abs(cu) < .0001) + n_roots = solve_quadric(vec2(co / qu, li / qu), roots.xy); + else + n_roots = solve_cubic(vec3(co / cu, li / cu, qu / cu), roots); + + for (int i = 0; i < n_roots; i++) + { + if (roots[i] >= 0. && roots[i] <= 1.) + { + float x_pos = -p0.x + 3. * p1.x - 3. * p2.x + p3.x; + x_pos = x_pos * roots[i] + 3. * p0.x - 6. * p1.x + 3. * p2.x; + x_pos = x_pos * roots[i] + -3. * p0.x + 3. * p1.x; + x_pos = x_pos * roots[i] + p0.x; + + if (reverse ? x_pos < uv.x : x_pos > uv.x) + n_ints++; + } + } + } + return n_ints; +} + +int ray_int_test(vec2 uv, vec2 p0, vec2 direction, bool reverse) +{ + p0 -= uv; + if (-p0.y * direction.y > 0.) + { + vec2 nor = -direction; + nor = vec2(nor.y, -nor.x); + float sgn = p0.y > direction.y ? 1. : -1.; + if (reverse) + sgn = -sgn; + return dot(nor, p0) * sgn < 0. ? 0 : 1; + } + else + return 0; +} + +vec2 bezierTangent(float t, vec2 p0, vec2 p1, vec2 p2, vec2 p3) +{ + float u = 1 - t; + float uu = u * u; + float tu = t * u; + float tt = t * t; + + vec2 P = p0 * 3 * uu * (-1.0); + P += p1 * 3 * (uu - 2 * tu); + P += p2 * 3 * (2 * tu - tt); + P += p3 * 3 * tt; + + // 返回单位向量 + return normalize(P); +} + +// 判断两向量夹角(向量A逆时针到向量B)是否大于180°,大于180°返回真,否则返回假 +bool angleLargeThanPi(vec2 a, vec2 b) +{ + return a.x * b.y - b.x * a.y < 0; +} + +/************************************************************************************/ + +void drawLine(in float d, in uint styleIndex, out vec4 elementColor, out vec2 metallicRoughness) +{ + elementColor = vec4(1); + metallicRoughness = vec2(0.8); + // switch(int(elementData[styleIndex+3])) + switch (0) + { + case 0: { + elementColor = vec4(unpackUnorm4x8(floatBitsToUint(style[styleIndex + 2])).rgb, 1); + metallicRoughness = vec2(unpackUnorm4x8(floatBitsToUint(style[styleIndex + 1])).rg); + break; + } + case 1: { + elementColor = vec4(mix(vec3(0), vec3(1), d), 1); + metallicRoughness = vec2(0, 0.8); + break; + } + case 2: { + float levels[] = {0.25, 0.5, 0.75}; + vec3 colors[] = {vec3(1, 0, 0), vec3(0, 1, 0), vec3(0, 0, 1), vec3(1, 1, 0)}; + int i = 0; + while (i < 3) + { + if (d < levels[i]) + { + elementColor = vec4(colors[i], 1); + metallicRoughness = vec2(0, 0.8); + break; + } + i++; + } + if (i == 3) + { + elementColor = vec4(colors[i], 1); + metallicRoughness = vec2(0, 0.8); + } + break; + } + } +} + +void main() +{ + uvec2 pixelLocation = gl_GlobalInvocationID.xy; + // imageStore(gBaseColor, ivec2(pixelLocation), vec4(vec2(pixelLocation)/vec2(imageSize(gBaseColor)), 1,1)); + + vec4 color = vec4(0); + // if(isinf(path[0].x)) imageStore(gBaseColor, ivec2(pixelLocation), + // vec4(vec2(pixelLocation)/vec2(imageSize(gBaseColor)), 1,1)); return; + for (uint styleIndex = 0; styleIndex < styleSize; styleIndex++) + { + + styleIndex += 6; + vec2 localUV = vec2(pixelLocation) / pixelRatio + leftTop; + + bool hitElement = false; + vec4 elementColor; + vec2 pBegin; + vec2 p3Last = vec2(1e38); + vec2 p2Last = vec2(1e38); + float strokeWidth = style[styleIndex]; + if (strokeWidth <= 0) // Fill + { + uint num_its = 0; + for (uint pathIndex = 0; pathIndex < pathSize; pathIndex++) + { + vec2 pTemp = path[pathIndex]; + if (isinf(pTemp.x)) + { + pBegin = path[++pathIndex]; + p3Last = pBegin; + continue; + } + mat4x2 p = mat4x2(p3Last, pTemp, path[++pathIndex], path[++pathIndex]); + + num_its += cubic_bezier_int_test(localUV, p[0], p[1], p[2], p[3]); + p3Last = p[3]; + p2Last = p[2]; + } + if (num_its % 2 == 1) + { + hitElement = true; + elementColor = vec4(1, 1, 0, 0); + + vec4 head = unpackUnorm4x8(floatBitsToUint(style[styleIndex])); + if (head.z == 0) + { + elementColor = vec4(unpackUnorm4x8(floatBitsToUint(style[++styleIndex])).rgb, 1); + } + } + } + else // Stroke + { + float minDistance = 1e38; + // float lineType = elementData[styleIndex+4]; + float lineType = 2; + int debugBegin = 0; + for (uint pathIndex = 0; pathIndex < pathSize; pathIndex++) + { + vec2 pTemp = path[pathIndex]; + if (isinf(pTemp.x)) + { + // TODO: 检测是否封闭并处理 + + pBegin = path[++pathIndex]; + p3Last = pBegin; + continue; + } + mat4x2 p = mat4x2(p3Last, pTemp, path[++pathIndex], path[++pathIndex]); + + float d = cubic_bezier_dis(localUV, p[0], p[1], p[2], p[3], true); + if (d <= strokeWidth) + { + bool onBegin = distance(localUV, p[0]) <= strokeWidth && p3Last == p[0]; + bool fill = true; + if (onBegin) + { + vec2 normalLast = normalize(mat2(0, 1, -1, 0) * (p3Last - p2Last)); + vec2 normalNow = normalize(mat2(0, 1, -1, 0) * (p[1] - p[0])); + vec2 normal = normalLast + normalNow; + fill = angleLargeThanPi(normal, localUV - p[0]); + } + if (onBegin ? fill : d < minDistance) + { + minDistance = min(minDistance, d); + + bool reverse = p[3].y - p[0].y < 0.; + + vec2 tangentBegin = normalize(p[0] - p[1]); + vec2 tangentEnd = normalize(p[3] - p[2]); + if (tangentBegin.y == 0.) + tangentBegin.y = reverse ? eps : -eps; + if (tangentEnd.y == 0.) + tangentEnd.y = reverse ? -eps : eps; + int intTest = cubic_bezier_int_test2(localUV, p[0], p[1], p[2], p[3], reverse) + + ray_int_test(localUV, p[0], tangentBegin, reverse) + + ray_int_test(localUV, p[3], tangentEnd, reverse); + if (lineType == 2 || intTest % 2 == int(lineType)) + { + hitElement = true; + elementColor = vec4(1, 1, 0, 1); + // drawLine(minDistance / strokeWidth, styleIndex, elementColor, metallicRoughness); + } + else if (p3Last == p[0]) + hitElement = false; + } + } + p3Last = p[3]; + p2Last = p[2]; + } + } + if (hitElement) + color = elementColor; + styleIndex += 100; + } + if (color.a != 0) + imageStore(gBaseColor, ivec2(pixelLocation), color); +} \ No newline at end of file diff --git a/ArchitectureColoredPainting/src/Editor/GraphicElement.cpp b/ArchitectureColoredPainting/src/Editor/GraphicElement.cpp index 47f4ee1..21f210b 100644 --- a/ArchitectureColoredPainting/src/Editor/GraphicElement.cpp +++ b/ArchitectureColoredPainting/src/Editor/GraphicElement.cpp @@ -1,5 +1,5 @@ #include "GraphicElement.h" -#include "third-party modules/util/SvgFileLoader.h" +#include "util/SvgFileLoader.h" using namespace std; QPainterPath SimpleElement::getPaintObject() const { diff --git a/ArchitectureColoredPainting/src/Renderer/Painting/Element.cpp b/ArchitectureColoredPainting/src/Renderer/Painting/Element.cpp index b716fa7..d697fa8 100644 --- a/ArchitectureColoredPainting/src/Renderer/Painting/Element.cpp +++ b/ArchitectureColoredPainting/src/Renderer/Painting/Element.cpp @@ -1 +1,16 @@ #include "Element.h" + +void Renderer::ElementTransform::applyTransformStyle(const TransformStyle& t) +{ + center += t.translation; + size *= t.scale; + rotation += t.rotation; + flip ^= t.flip; +} + +Renderer::ElementTransform Renderer::ElementTransform::appliedTransformStyle(const TransformStyle& t) const +{ + ElementTransform result = *this; + result.applyTransformStyle(t); + return result; +} diff --git a/ArchitectureColoredPainting/src/Renderer/Painting/Element.h b/ArchitectureColoredPainting/src/Renderer/Painting/Element.h index d4f887f..bb869ff 100644 --- a/ArchitectureColoredPainting/src/Renderer/Painting/Element.h +++ b/ArchitectureColoredPainting/src/Renderer/Painting/Element.h @@ -21,5 +21,8 @@ namespace Renderer float rotation; /// 角度制 glm::bvec2 flip; GLuint zIndex; + + void applyTransformStyle(const TransformStyle& t); + ElementTransform appliedTransformStyle(const TransformStyle& t) const; }; } \ No newline at end of file diff --git a/ArchitectureColoredPainting/src/Renderer/Painting/Painting.cpp b/ArchitectureColoredPainting/src/Renderer/Painting/Painting.cpp index 5539faf..f1b8337 100644 --- a/ArchitectureColoredPainting/src/Renderer/Painting/Painting.cpp +++ b/ArchitectureColoredPainting/src/Renderer/Painting/Painting.cpp @@ -79,10 +79,7 @@ void Renderer::Painting::addElement(const Element& element, const ElementTransfo { auto& style = it->second[i]; ElementTransform trans = transform; - trans.center += style.transform->translation; - trans.size *= style.transform->scale; - trans.rotation += style.transform->rotation; - trans.flip ^= style.transform->flip; + trans.applyTransformStyle(*style.transform); trans.zIndex = trans.zIndex * 10 + i; addElement(ElementWithTransform{ BaseElement{element.contour, style.material}, BaseTransform(trans) }); } diff --git a/ArchitectureColoredPainting/src/Renderer/Preview/ElementRenderer.cpp b/ArchitectureColoredPainting/src/Renderer/Preview/ElementRenderer.cpp new file mode 100644 index 0000000..2787c96 --- /dev/null +++ b/ArchitectureColoredPainting/src/Renderer/Preview/ElementRenderer.cpp @@ -0,0 +1,160 @@ +#include "ElementRenderer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "../Painting/Element.h" +#include "../Painting/Painting.h" +#include "../Painting/MaterialStyleStroke.h" +using namespace Renderer; + +Renderer::ElementRenderer::ElementRenderer(QOpenGLWidget* glWidget) + : glWidget(glWidget) +{ +} + +void Renderer::ElementRenderer::initialize() +{ + initializeOpenGLFunctions(); + shader = std::make_shared(); + if (!shader->addShaderFromSourceFile(QOpenGLShader::Compute, ":/Shaders/element.comp")) + qDebug() << "ERROR: " << shader->log(); + if (!shader->link()) + qDebug() << "ERROR: " << shader->log(); + +} + +std::vector generatePathBuffer(const QPainterPath& path) +{ + std::vector pathBuffer; + for (int i = 0; i < path.elementCount(); i++) + { + QPainterPath::Element element = path.elementAt(i); + switch (element.type) + { + case QPainterPath::MoveToElement: + qDebug() << "MoveToElement"; + pathBuffer.push_back(glm::vec2(std::numeric_limits::infinity())); + pathBuffer.push_back(glm::vec2(element.x, element.y)); + break; + case QPainterPath::LineToElement: + qDebug() << "LineToElement"; + glm::vec2 end = glm::vec2(element.x, element.y); + glm::vec2 mid = (pathBuffer.back() + end) / 2.f; + pathBuffer.push_back(mid); + pathBuffer.push_back(mid); + pathBuffer.push_back(end); + break; + case QPainterPath::CurveToElement: + qDebug() << "CurveToElement"; + pathBuffer.push_back(glm::vec2(element.x, element.y)); + break; + case QPainterPath::CurveToDataElement: + qDebug() << "CurveToDataElement"; + pathBuffer.push_back(glm::vec2(element.x, element.y)); + break; + } + qDebug() << element; + } + return pathBuffer; +} + +std::vector generateStyleBuffer(const std::vector& styles) +{ + std::vector styleBuffer; + for (auto& style : styles) + { + styleBuffer.push_back(style.transform->translation.x); + styleBuffer.push_back(style.transform->translation.y); + styleBuffer.push_back(style.transform->scale.x); + styleBuffer.push_back(style.transform->scale.y); + styleBuffer.push_back(style.transform->rotation); + styleBuffer.push_back(glm::uintBitsToFloat(glm::packUnorm2x16(style.transform->flip))); + auto encoded = style.material->encoded(); + styleBuffer.insert(styleBuffer.end(), encoded.begin(), encoded.end()); + } + return styleBuffer; +} + +QRectF calcBoundingRect(const QPainterPath& path, const std::vector& styles) +{ + QRectF bound = path.boundingRect(); + + ElementTransform originTransform{ glm::vec2(bound.center().x(), bound.center().y()), glm::vec2(bound.width(), bound.height()), 0, glm::bvec2(false), 0 }; + + glm::vec2 leftTop(std::numeric_limits::max()), rightBottom(std::numeric_limits::min()); + + for (auto& baseStyle : styles) + { + BaseTransform transform(originTransform.appliedTransformStyle(*baseStyle.transform)); + if (baseStyle.material->type() == MaterialStyleType::kStroke) + { + float halfWidth = std::static_pointer_cast(baseStyle.material)->getWidth() / 2; + transform.bound.x -= halfWidth; + transform.bound.y -= halfWidth; + transform.bound.z += halfWidth; + transform.bound.w += halfWidth; + } + glm::vec2 points[] = { + glm::vec2(transform.bound.x, transform.bound.y), + glm::vec2(transform.bound.x, transform.bound.w), + glm::vec2(transform.bound.z, transform.bound.w), + glm::vec2(transform.bound.z, transform.bound.y) + }; + for (auto& point : points) + { + float angle = glm::radians(baseStyle.transform->rotation); + point = glm::mat2{ {cos(angle), -sin(angle)}, {sin(angle), cos(angle)} } *point; + leftTop = glm::min(leftTop, point); + rightBottom = glm::max(rightBottom, point); + } + } + return QRectF(QPointF(leftTop.x, leftTop.y), QPointF(rightBottom.x, rightBottom.y)); +} + +std::pair Renderer::ElementRenderer::drawElement(const QPainterPath& path, const ElementStyle& style, float pixelRatio, bool releaseContext) +{ + auto baseStyles = style.toBaseStyles(); + QRectF bound = calcBoundingRect(path, baseStyles); + QPointF leftTop = bound.topLeft(); + QSize size(ceil(bound.width() * pixelRatio), ceil(bound.height() * pixelRatio)); + + auto pathBuffer = generatePathBuffer(path); + auto styleBuffer = generateStyleBuffer(baseStyles); + + if (releaseContext) glWidget->makeCurrent(); + + GLuint ssbo[2]; + glGenBuffers(2, ssbo); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[0]); + glBufferData(GL_SHADER_STORAGE_BUFFER, pathBuffer.size() * sizeof(glm::vec2), pathBuffer.data(), GL_STATIC_READ); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo[1]); + glBufferData(GL_SHADER_STORAGE_BUFFER, styleBuffer.size() * sizeof(GLfloat), styleBuffer.data(), GL_STATIC_READ); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + auto fbo = QOpenGLFramebufferObject(size, QOpenGLFramebufferObject::NoAttachment, GL_TEXTURE_2D); + fbo.bind(); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + fbo.release(); + + shader->bind(); + shader->setUniformValue("pathSize", (GLint)pathBuffer.size()); + shader->setUniformValue("styleSize", (GLint)styleBuffer.size()); + shader->setUniformValue("leftTop", leftTop); + shader->setUniformValue("pixelRatio", pixelRatio); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 10, ssbo[0]); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 11, ssbo[1]); + glBindImageTexture(0, fbo.texture(), 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA8); + glDispatchCompute(ceil(size.width() / 8.), ceil(size.height() / 8.), 1); + shader->release(); + + auto image = fbo.toImage(false); + glDeleteBuffers(2, ssbo); + if (releaseContext) glWidget->doneCurrent(); + return { image, leftTop }; +} diff --git a/ArchitectureColoredPainting/src/Renderer/Preview/ElementRenderer.h b/ArchitectureColoredPainting/src/Renderer/Preview/ElementRenderer.h new file mode 100644 index 0000000..d105757 --- /dev/null +++ b/ArchitectureColoredPainting/src/Renderer/Preview/ElementRenderer.h @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include +#include "../Painting/ElementStyle.h" + +namespace Renderer +{ + class ElementRenderer : protected QOpenGLFunctions_4_5_Core + { + public: + ElementRenderer(QOpenGLWidget* glWidget); + + /** + * @brief 须在initializeGL函数中调用 + */ + void initialize(); + + /** + * @brief 将图元绘制到QImage + * @param path 图元几何数据 + * @param style 图元样式 + * @param pixelRatio path中的单位坐标对应的像素大小 + * @param releaseContext 若在initializeGL, resizeGL或paintGL中调用须传入false + * @return QImage 绘制得到的位图 + * @return QPointF 位图原点(左上角)在QPainterPath坐标系下的坐标 + */ + std::pair drawElement(const QPainterPath& path, const ElementStyle& style, float pixelRatio, bool releaseContext = true); + protected: + QOpenGLWidget* glWidget; + std::shared_ptr shader; + }; + +} +