import { Shader, Program } from '@pixi/core'; const smoothVert = `#version 100 precision highp float; const float FILL = 1.0; const float BEVEL = 4.0; const float MITER = 8.0; const float ROUND = 12.0; const float JOINT_CAP_BUTT = 16.0; const float JOINT_CAP_SQUARE = 18.0; const float JOINT_CAP_ROUND = 20.0; const float FILL_EXPAND = 24.0; const float CAP_BUTT = 1.0; const float CAP_SQUARE = 2.0; const float CAP_ROUND = 3.0; const float CAP_BUTT2 = 4.0; const float MITER_LIMIT = 10.0; // === geom === attribute vec2 aPrev; attribute vec2 aPoint1; attribute vec2 aPoint2; attribute vec2 aNext; attribute float aVertexJoint; attribute float aTravel; uniform mat3 projectionMatrix; uniform mat3 translationMatrix; uniform vec4 tint; varying vec4 vLine1; varying vec4 vLine2; varying vec4 vArc; varying float vType; uniform float resolution; uniform float expand; // === style === attribute float aStyleId; attribute vec4 aColor; varying float vTextureId; varying vec4 vColor; varying vec2 vTextureCoord; varying vec2 vTravel; uniform vec2 styleLine[%MAX_STYLES%]; uniform vec3 styleMatrix[2 * %MAX_STYLES%]; uniform float styleTextureId[%MAX_STYLES%]; uniform vec2 samplerSize[%MAX_TEXTURES%]; vec2 doBisect(vec2 norm, float len, vec2 norm2, float len2, float dy, float inner) { vec2 bisect = (norm + norm2) / 2.0; bisect /= dot(norm, bisect); vec2 shift = dy * bisect; if (inner > 0.5) { if (len < len2) { if (abs(dy * (bisect.x * norm.y - bisect.y * norm.x)) > len) { return dy * norm; } } else { if (abs(dy * (bisect.x * norm2.y - bisect.y * norm2.x)) > len2) { return dy * norm; } } } return dy * bisect; } void main(void){ vec2 pointA = (translationMatrix * vec3(aPoint1, 1.0)).xy; vec2 pointB = (translationMatrix * vec3(aPoint2, 1.0)).xy; vec2 xBasis = pointB - pointA; float len = length(xBasis); vec2 forward = xBasis / len; vec2 norm = vec2(forward.y, -forward.x); float type = floor(aVertexJoint / 16.0); float vertexNum = aVertexJoint - type * 16.0; float dx = 0.0, dy = 1.0; float capType = floor(type / 32.0); type -= capType * 32.0; int styleId = int(aStyleId + 0.5); float lineWidth = styleLine[styleId].x; vTextureId = floor(styleTextureId[styleId] / 4.0); float scaleMode = styleTextureId[styleId] - vTextureId * 4.0; float avgScale = 1.0; if (scaleMode > 2.5) { avgScale = length(translationMatrix * vec3(1.0, 0.0, 0.0)); } else if (scaleMode > 1.5) { avgScale = length(translationMatrix * vec3(0.0, 1.0, 0.0)); } else if (scaleMode > 0.5) { vec2 avgDiag = (translationMatrix * vec3(1.0, 1.0, 0.0)).xy; avgScale = sqrt(dot(avgDiag, avgDiag) * 0.5); } lineWidth *= 0.5 * avgScale; float lineAlignment = 2.0 * styleLine[styleId].y - 1.0; vTextureCoord = vec2(0.0); vec2 pos; if (capType == CAP_ROUND) { vertexNum += 4.0; type = JOINT_CAP_ROUND; capType = 0.0; lineAlignment = -lineAlignment; } vLine1 = vec4(0.0, 10.0, 1.0, 0.0); vLine2 = vec4(0.0, 10.0, 1.0, 0.0); vArc = vec4(0.0); if (type == FILL) { pos = pointA; vType = 0.0; vLine2 = vec4(-2.0, -2.0, -2.0, 0.0); vec2 vTexturePixel; vTexturePixel.x = dot(vec3(aPoint1, 1.0), styleMatrix[styleId * 2]); vTexturePixel.y = dot(vec3(aPoint1, 1.0), styleMatrix[styleId * 2 + 1]); vTextureCoord = vTexturePixel / samplerSize[int(vTextureId)]; } else if (type >= FILL_EXPAND && type < FILL_EXPAND + 7.5) { // expand vertices float flags = type - FILL_EXPAND; float flag3 = floor(flags / 4.0); float flag2 = floor((flags - flag3 * 4.0) / 2.0); float flag1 = flags - flag3 * 4.0 - flag2 * 2.0; vec2 prev = (translationMatrix * vec3(aPrev, 1.0)).xy; if (vertexNum < 0.5) { pos = prev; } else if (vertexNum < 1.5) { pos = pointA; } else { pos = pointB; } float len2 = length(aNext); vec2 bisect = (translationMatrix * vec3(aNext, 0.0)).xy; if (len2 > 0.01) { bisect = normalize(bisect) * len2; } vec2 n1 = normalize(vec2(pointA.y - prev.y, -(pointA.x - prev.x))); vec2 n2 = normalize(vec2(pointB.y - pointA.y, -(pointB.x - pointA.x))); vec2 n3 = normalize(vec2(prev.y - pointB.y, -(prev.x - pointB.x))); if (n1.x * n2.y - n1.y * n2.x < 0.0) { n1 = -n1; n2 = -n2; n3 = -n3; } pos += bisect * expand; vLine1 = vec4(16.0, 16.0, 16.0, -1.0); if (flag1 > 0.5) { vLine1.x = -dot(pos - prev, n1); } if (flag2 > 0.5) { vLine1.y = -dot(pos - pointA, n2); } if (flag3 > 0.5) { vLine1.z = -dot(pos - pointB, n3); } vLine1.xyz *= resolution; vType = 2.0; } else if (type >= BEVEL) { float dy = lineWidth + expand; float shift = lineWidth * lineAlignment; float inner = 0.0; if (vertexNum >= 1.5) { dy = -dy; inner = 1.0; } vec2 base, next, xBasis2, bisect; float flag = 0.0; float side2 = 1.0; if (vertexNum < 0.5 || vertexNum > 2.5 && vertexNum < 3.5) { next = (translationMatrix * vec3(aPrev, 1.0)).xy; base = pointA; flag = type - floor(type / 2.0) * 2.0; side2 = -1.0; } else { next = (translationMatrix * vec3(aNext, 1.0)).xy; base = pointB; if (type >= MITER && type < MITER + 3.5) { flag = step(MITER + 1.5, type); // check miter limit here? } } xBasis2 = next - base; float len2 = length(xBasis2); vec2 norm2 = vec2(xBasis2.y, -xBasis2.x) / len2; float D = norm.x * norm2.y - norm.y * norm2.x; if (D < 0.0) { inner = 1.0 - inner; } norm2 *= side2; float collinear = step(0.0, dot(norm, norm2)); vType = 0.0; float dy2 = -1000.0; if (abs(D) < 0.01 && collinear < 0.5) { if (type >= ROUND && type < ROUND + 1.5) { type = JOINT_CAP_ROUND; } //TODO: BUTT here too } vLine1 = vec4(0.0, lineWidth, max(abs(norm.x), abs(norm.y)), min(abs(norm.x), abs(norm.y))); vLine2 = vec4(0.0, lineWidth, max(abs(norm2.x), abs(norm2.y)), min(abs(norm2.x), abs(norm2.y))); if (vertexNum < 3.5) { if (abs(D) < 0.01 && collinear < 0.5) { pos = (shift + dy) * norm; } else { if (flag < 0.5 && inner < 0.5) { pos = (shift + dy) * norm; } else { pos = doBisect(norm, len, norm2, len2, shift + dy, inner); } } vLine2.y = -1000.0; if (capType >= CAP_BUTT && capType < CAP_ROUND) { float extra = step(CAP_SQUARE, capType) * lineWidth; vec2 back = -forward; if (vertexNum < 0.5 || vertexNum > 2.5) { pos += back * (expand + extra); dy2 = expand; } else { dy2 = dot(pos + base - pointA, back) - extra; } } if (type >= JOINT_CAP_BUTT && type < JOINT_CAP_SQUARE + 0.5) { float extra = step(JOINT_CAP_SQUARE, type) * lineWidth; if (vertexNum < 0.5 || vertexNum > 2.5) { vLine2.y = dot(pos + base - pointB, forward) - extra; } else { pos += forward * (expand + extra); vLine2.y = expand; if (capType >= CAP_BUTT) { dy2 -= expand + extra; } } } } else if (type >= JOINT_CAP_ROUND && type < JOINT_CAP_ROUND + 1.5) { base += shift * norm; if (inner > 0.5) { dy = -dy; inner = 0.0; } vec2 d2 = abs(dy) * forward; if (vertexNum < 4.5) { dy = -dy; pos = dy * norm; } else if (vertexNum < 5.5) { pos = dy * norm; } else if (vertexNum < 6.5) { pos = dy * norm + d2; vArc.x = abs(dy); } else { dy = -dy; pos = dy * norm + d2; vArc.x = abs(dy); } vLine2 = vec4(0.0, lineWidth * 2.0 + 10.0, 1.0 , 0.0); // forget about line2 with type=3 vArc.y = dy; vArc.z = 0.0; vArc.w = lineWidth; vType = 3.0; } else if (abs(D) < 0.01 && collinear < 0.5) { pos = dy * norm; } else { if (inner > 0.5) { dy = -dy; inner = 0.0; } float side = sign(dy); vec2 norm3 = normalize(norm + norm2); if (type >= MITER && type < MITER + 3.5) { vec2 farVertex = doBisect(norm, len, norm2, len2, shift + dy, 0.0); if (length(farVertex) > abs(shift + dy) * MITER_LIMIT) { type = BEVEL; } } if (vertexNum < 4.5) { pos = doBisect(norm, len, norm2, len2, shift - dy, 1.0); } else if (vertexNum < 5.5) { pos = (shift + dy) * norm; } else if (vertexNum > 7.5) { pos = (shift + dy) * norm2; } else { if (type >= ROUND && type < ROUND + 1.5) { pos = doBisect(norm, len, norm2, len2, shift + dy, 0.0); float d2 = abs(shift + dy); if (length(pos) > abs(shift + dy) * 1.5) { if (vertexNum < 6.5) { pos.x = (shift + dy) * norm.x - d2 * norm.y; pos.y = (shift + dy) * norm.y + d2 * norm.x; } else { pos.x = (shift + dy) * norm2.x + d2 * norm2.y; pos.y = (shift + dy) * norm2.y - d2 * norm2.x; } } } else if (type >= MITER && type < MITER + 3.5) { pos = doBisect(norm, len, norm2, len2, shift + dy, 0.0); //farVertex } else if (type >= BEVEL && type < BEVEL + 1.5) { float d2 = side / resolution; if (vertexNum < 6.5) { pos = (shift + dy) * norm + d2 * norm3; } else { pos = (shift + dy) * norm2 + d2 * norm3; } } } if (type >= ROUND && type < ROUND + 1.5) { vArc.x = side * dot(pos, norm3); vArc.y = pos.x * norm3.y - pos.y * norm3.x; vArc.z = dot(norm, norm3) * (lineWidth + side * shift); vArc.w = lineWidth + side * shift; vType = 3.0; } else if (type >= MITER && type < MITER + 3.5) { vType = 1.0; } else if (type >= BEVEL && type < BEVEL + 1.5) { vType = 4.0; vArc.z = dot(norm, norm3) * (lineWidth + side * shift) - side * dot(pos, norm3); } dy = side * (dot(pos, norm) - shift); dy2 = side * (dot(pos, norm2) - shift); } pos += base; vLine1.xy = vec2(dy, vLine1.y) * resolution; vLine2.xy = vec2(dy2, vLine2.y) * resolution; vArc = vArc * resolution; vTravel = vec2(aTravel * avgScale + dot(pos - pointA, vec2(-norm.y, norm.x)), avgScale); } gl_Position = vec4((projectionMatrix * vec3(pos, 1.0)).xy, 0.0, 1.0); vColor = aColor * tint; }`; const precision = `#version 100 #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; #else precision mediump float; #endif `; const smoothFrag = `%PRECISION% varying vec4 vColor; varying vec4 vLine1; varying vec4 vLine2; varying vec4 vArc; varying float vType; varying float vTextureId; varying vec2 vTextureCoord; varying vec2 vTravel; uniform sampler2D uSamplers[%MAX_TEXTURES%]; %PIXEL_LINE% void main(void){ %PIXEL_COVERAGE% vec4 texColor; float textureId = floor(vTextureId+0.5); %FOR_LOOP% gl_FragColor = vColor * texColor * alpha; } `; const pixelLineFunc = [` float pixelLine(float x, float A, float B) { return clamp(x + 0.5, 0.0, 1.0); } `, ` float pixelLine(float x, float A, float B) { float y = abs(x), s = sign(x); if (y * 2.0 < A - B) { return 0.5 + s * y / A; } y -= (A - B) * 0.5; y = max(1.0 - y / B, 0.0); return (1.0 + s * (1.0 - y * y)) * 0.5; //return clamp(x + 0.5, 0.0, 1.0); } `]; const pixelCoverage = `float alpha = 1.0; if (vType < 0.5) { float left = pixelLine(-vLine1.y - vLine1.x, vLine1.z, vLine1.w); float right = pixelLine(vLine1.y - vLine1.x, vLine1.z, vLine1.w); float near = vLine2.x - 0.5; float far = min(vLine2.x + 0.5, 0.0); float top = vLine2.y - 0.5; float bottom = min(vLine2.y + 0.5, 0.0); alpha = (right - left) * max(bottom - top, 0.0) * max(far - near, 0.0); } else if (vType < 1.5) { float a1 = pixelLine(- vLine1.y - vLine1.x, vLine1.z, vLine1.w); float a2 = pixelLine(vLine1.y - vLine1.x, vLine1.z, vLine1.w); float b1 = pixelLine(- vLine2.y - vLine2.x, vLine2.z, vLine2.w); float b2 = pixelLine(vLine2.y - vLine2.x, vLine2.z, vLine2.w); alpha = a2 * b2 - a1 * b1; } else if (vType < 2.5) { alpha *= max(min(vLine1.x + 0.5, 1.0), 0.0); alpha *= max(min(vLine1.y + 0.5, 1.0), 0.0); alpha *= max(min(vLine1.z + 0.5, 1.0), 0.0); } else if (vType < 3.5) { float a1 = pixelLine(- vLine1.y - vLine1.x, vLine1.z, vLine1.w); float a2 = pixelLine(vLine1.y - vLine1.x, vLine1.z, vLine1.w); float b1 = pixelLine(- vLine2.y - vLine2.x, vLine2.z, vLine2.w); float b2 = pixelLine(vLine2.y - vLine2.x, vLine2.z, vLine2.w); float alpha_miter = a2 * b2 - a1 * b1; float alpha_plane = clamp(vArc.z - vArc.x + 0.5, 0.0, 1.0); float d = length(vArc.xy); float circle_hor = max(min(vArc.w, d + 0.5) - max(-vArc.w, d - 0.5), 0.0); float circle_vert = min(vArc.w * 2.0, 1.0); float alpha_circle = circle_hor * circle_vert; alpha = min(alpha_miter, max(alpha_circle, alpha_plane)); } else { float a1 = pixelLine(- vLine1.y - vLine1.x, vLine1.z, vLine1.w); float a2 = pixelLine(vLine1.y - vLine1.x, vLine1.z, vLine1.w); float b1 = pixelLine(- vLine2.y - vLine2.x, vLine2.z, vLine2.w); float b2 = pixelLine(vLine2.y - vLine2.x, vLine2.z, vLine2.w); alpha = a2 * b2 - a1 * b1; alpha *= clamp(vArc.z + 0.5, 0.0, 1.0); } `; class SmoothGraphicsShader extends Shader { constructor(settings, vert = smoothVert, frag = smoothFrag, uniforms = {}) { vert = SmoothGraphicsShader.generateVertexSrc(settings, vert); frag = SmoothGraphicsShader.generateFragmentSrc(settings, frag); const { maxStyles, maxTextures } = settings; const sampleValues = new Int32Array(maxTextures); for (let i = 0; i < maxTextures; i++) { sampleValues[i] = i; } super(Program.from(vert, frag), Object.assign(uniforms, { styleMatrix: new Float32Array(6 * maxStyles), styleTextureId: new Float32Array(maxStyles), styleLine: new Float32Array(2 * maxStyles), samplerSize: new Float32Array(2 * maxTextures), uSamplers: sampleValues, tint: new Float32Array([1, 1, 1, 1]), resolution: 1, expand: 1 })); this.settings = settings; } static generateVertexSrc(settings, vertexSrc = smoothVert) { const { maxStyles, maxTextures } = settings; vertexSrc = vertexSrc.replace(/%MAX_TEXTURES%/gi, `${maxTextures}`).replace(/%MAX_STYLES%/gi, `${maxStyles}`); return vertexSrc; } static generateFragmentSrc(settings, fragmentSrc = smoothFrag) { const { maxTextures, pixelLine } = settings; fragmentSrc = fragmentSrc.replace(/%PRECISION%/gi, precision).replace(/%PIXEL_LINE%/gi, pixelLineFunc[pixelLine]).replace(/%PIXEL_COVERAGE%/gi, pixelCoverage).replace(/%MAX_TEXTURES%/gi, `${maxTextures}`).replace(/%FOR_LOOP%/gi, this.generateSampleSrc(maxTextures)); return fragmentSrc; } static generateSampleSrc(maxTextures) { let src = ""; src += "\n"; src += "\n"; for (let i = 0; i < maxTextures; i++) { if (i > 0) { src += "\nelse "; } if (i < maxTextures - 1) { src += `if(textureId < ${i}.5)`; } src += "\n{"; src += ` texColor = texture2D(uSamplers[${i}], vTextureCoord);`; src += "\n}"; } src += "\n"; src += "\n"; return src; } } export { SmoothGraphicsShader }; //# sourceMappingURL=SmoothShader.mjs.map