aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/scenegraph/shaders_ng/shapestroke.frag
blob: cedfa0845ab1352188155f94a86beded6e3ddc76 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#version 440

layout(location = 0) in vec4 P;
layout(location = 1) in vec2 A;
layout(location = 2) in vec2 B;
layout(location = 3) in vec2 C;
layout(location = 4) in vec2 HG;
layout(location = 5) in float offset;

layout(location = 0) out vec4 fragColor;

layout(std140, binding = 0) uniform buf {
#if QSHADER_VIEW_COUNT >= 2
    mat4 qt_Matrix[QSHADER_VIEW_COUNT];
#else
    mat4 qt_Matrix;
#endif

    float matrixScale;
    float opacity;
    float reserved2;
    float reserved3;

    vec4 strokeColor;

    float strokeWidth;
    float debug;
    float reserved5;
    float reserved6;
} ubuf;

float cuberoot(float x)
{
    return sign(x) * pow(abs(x), 1 / 3.);
}

#define PI 3.1415926538

vec3 solveDepressedCubic(float p, float q)
{
    float D = q * q / 4. + p * p * p / 27.;

    float u1 = cuberoot(-q / 2. - sqrt(D));
    float u2 = cuberoot(-q / 2. + sqrt(D));
    vec3 rootsD1 = vec3(u1 - p / (3. * u1), u2 - p / (3. * u2), 0);

    float v = 2.*sqrt(-p / 3.);
    float t = acos(3. * q / p / v) / 3.;
    float k = 2. * PI / 3.;
    vec3 rootsD2 = vec3(v * cos(t), v * cos(t - k), v * cos(t - 2. * k));

    return D > 0 ? rootsD1 : rootsD2;
}

mat2 qInverse(mat2 matrix) {
    float a = matrix[0][0], b = matrix[0][1];
    float c = matrix[1][0], d = matrix[1][1];

    float determinant = a * d - b * c;
    float invDet = 1.0 / determinant;

    mat2 inverseMatrix;
    inverseMatrix[0][0] = d * invDet;
    inverseMatrix[0][1] = -b * invDet;
    inverseMatrix[1][0] = -c * invDet;
    inverseMatrix[1][1] = a * invDet;

    return inverseMatrix;
}

void main()
{
    vec3 s = solveDepressedCubic(HG.x, HG.y) - vec3(offset, offset, offset);

    vec2 Qmin = vec2(1e10, 1e10);
    float dmin = 1e4;
    for (int i = 0; i < 3; i++) {
        float t = clamp(s[i], 0., 1.);
        vec2 Q = A * t * t + B * t + C;
        float d = length(Q - P.xy);
        float foundNewMin = step(d, dmin);
        dmin = min(d, dmin);
        Qmin = foundNewMin * Q + (1. - foundNewMin) * Qmin;
    }
    vec2 n = (P.xy - Qmin) / dmin;
    vec2 Q1 = (Qmin + ubuf.strokeWidth / 2. * n);
    vec2 Q2 = (Qmin - ubuf.strokeWidth / 2. * n);

    // Converting to screen coordinates:
#if defined(USE_DERIVATIVES)
    mat2 T = mat2(dFdx(P.x), dFdy(P.x), dFdx(P.y), dFdy(P.y));
    mat2 Tinv = qInverse(T);
    vec2 Q1_s = Tinv * Q1;
    vec2 Q2_s = Tinv * Q2;
    vec2 P_s = Tinv * P.xy;
    vec2 n_s = Tinv * n;
    n_s = n_s / length(n_s);
#else
    vec2 Q1_s = ubuf.matrixScale * Q1;
    vec2 Q2_s = ubuf.matrixScale * Q2;
    vec2 P_s = ubuf.matrixScale * P.xy;
    vec2 n_s = n;
#endif

    // Geometric solution for anti aliasing using the known distances
    // to the edges of the path in the screen coordinate system.
    float dist1 = dot(P_s - Q1_s, n_s);
    float dist2 = dot(P_s - Q2_s, n_s);

    // Calculate the fill coverage if the line is crossing the square cell
    // normally (vertically or horizontally).
    // float fillCoverageLin = clamp(0.5-dist1, 0., 1.) - clamp(0.5-dist2, 0., 1.);

    // Calculate the fill coverage if the line is crossing the square cell
    // diagonally.
    float fillCoverageDia = clamp(step(0., -dist1) + sign(dist1) * pow(max(0., sqrt(2.) / 2. - abs(dist1)), 2.), 0., 1.) -
                            clamp(step(0., -dist2) + sign(dist2) * pow(max(0., sqrt(2.) / 2. - abs(dist2)), 2.), 0., 1.);

    // Merge the normal and the diagonal solution. The merge factor is periodic
    // in 90 degrees and 0/1 at 0 and 45 degree. The simple equation was
    // estimated after numerical experiments.
    // float mergeFactor = 2 * abs(n_s.x) * abs(n_s.y);
    // float fillCoverage = mergeFactor * fillCoverageDia + (1-mergeFactor) * fillCoverageLin;

    // It seems to be sufficient to use the equation for the diagonal.
    float fillCoverage = fillCoverageDia;

    // The center line is sometimes not filled because of numerical issues. This fixes this.
    float centerline = step(ubuf.strokeWidth * 0.01, dmin);
    fillCoverage = fillCoverage * centerline + min(1., ubuf.strokeWidth * ubuf.matrixScale) * (1. - centerline);

    fragColor = vec4(ubuf.strokeColor.rgb, 1.0) *ubuf.strokeColor.a * fillCoverage * ubuf.opacity
                + ubuf.debug * vec4(0.0, 0.5, 1.0, 1.0) * (1.0 - fillCoverage) * ubuf.opacity;
}