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;
+ };
+
+}
+