From 954476a3c0f0f7c220ea90d0b291b0e57c2ee92e Mon Sep 17 00:00:00 2001 From: Ricky Reusser <572717+rreusser@users.noreply.github.com> Date: Tue, 16 Nov 2021 20:45:52 -0800 Subject: [PATCH] v0.0.21 --- API.md | 2 +- CHANGELOG.md | 13 +++ dist/regl-gpu-lines.compat.js | 32 ++++-- dist/regl-gpu-lines.compat.min.js | 2 +- dist/regl-gpu-lines.js | 51 ++++++--- dist/regl-gpu-lines.min.js | 2 +- docs/border.html | 88 +++++++++++---- docs/debug.html | 10 +- docs/multiple.html | 22 +++- examples/border.js | 44 ++++++-- examples/debug.js | 5 +- examples/multiple.js | 11 +- package.json | 2 +- src/draw-segment.js | 65 ++++++----- .../miter/auto-caps/round-cap/expected.png | Bin 1407 -> 0 bytes .../miter/auto-caps/square-cap/expected.png | Bin 1407 -> 0 bytes .../no-cap => insert-caps/none}/expected.png | Bin .../no-cap => insert-caps/none}/fixture.json | 0 .../miter/insert-caps/round/expected.png | Bin 0 -> 1545 bytes .../round}/fixture.json | 1 - .../miter/insert-caps/square/expected.png | Bin 0 -> 1492 bytes .../square}/fixture.json | 1 - .../manual-caps}/none/expected.png | Bin .../miter/manual-caps/none/fixture.json | 105 ++++++++++++++++++ .../miter/manual-caps/round/expected.png | Bin 0 -> 1535 bytes .../miter/manual-caps/round/fixture.json | 105 ++++++++++++++++++ .../manual-caps}/square/expected.png | Bin .../miter/manual-caps/square/fixture.json | 105 ++++++++++++++++++ .../round/auto-caps/round/expected.png | Bin 1539 -> 0 bytes .../round/insert-caps/none/expected.png | Bin 0 -> 1397 bytes .../none/fixture.json | 0 .../round/insert-caps/round/expected.png | Bin 0 -> 1535 bytes .../round/fixture.json | 0 .../round/insert-caps/square/expected.png | Bin 0 -> 1481 bytes .../square/fixture.json | 0 .../round/manual-caps/none/fixture.json | 8 +- .../round/manual-caps/round/fixture.json | 8 +- .../round/manual-caps/square/fixture.json | 8 +- 38 files changed, 571 insertions(+), 119 deletions(-) delete mode 100644 test/fixtures/miter/auto-caps/round-cap/expected.png delete mode 100644 test/fixtures/miter/auto-caps/square-cap/expected.png rename test/fixtures/miter/{auto-caps/no-cap => insert-caps/none}/expected.png (100%) rename test/fixtures/miter/{auto-caps/no-cap => insert-caps/none}/fixture.json (100%) create mode 100644 test/fixtures/miter/insert-caps/round/expected.png rename test/fixtures/miter/{auto-caps/round-cap => insert-caps/round}/fixture.json (98%) create mode 100644 test/fixtures/miter/insert-caps/square/expected.png rename test/fixtures/miter/{auto-caps/square-cap => insert-caps/square}/fixture.json (98%) rename test/fixtures/{round/auto-caps => miter/manual-caps}/none/expected.png (100%) create mode 100644 test/fixtures/miter/manual-caps/none/fixture.json create mode 100644 test/fixtures/miter/manual-caps/round/expected.png create mode 100644 test/fixtures/miter/manual-caps/round/fixture.json rename test/fixtures/{round/auto-caps => miter/manual-caps}/square/expected.png (100%) create mode 100644 test/fixtures/miter/manual-caps/square/fixture.json delete mode 100644 test/fixtures/round/auto-caps/round/expected.png create mode 100644 test/fixtures/round/insert-caps/none/expected.png rename test/fixtures/round/{auto-caps => insert-caps}/none/fixture.json (100%) create mode 100644 test/fixtures/round/insert-caps/round/expected.png rename test/fixtures/round/{auto-caps => insert-caps}/round/fixture.json (100%) create mode 100644 test/fixtures/round/insert-caps/square/expected.png rename test/fixtures/round/{auto-caps => insert-caps}/square/fixture.json (100%) diff --git a/API.md b/API.md index ec13ea1..ba99d92 100644 --- a/API.md +++ b/API.md @@ -25,7 +25,7 @@ Instantiate a drawing command using the specified shaders. - `vert` (string): vertex shader, using pragma specification defined below - `frag` (string): fragment shader - `debug`: Debug mode, which exposes additional properties for viewing triangle mesh -- `insertCaps` (boolean, default: `false`) (*experimental*): Automatically insert a cap wherever a break is encountered, signaled by a position with `w = 0`. Only implemented for round joins. Allows drawing lines and caps with a single draw call, although caps may be lower resolution since they are constructed from the potentially-lower number of join vertices. +- `insertCaps` (boolean, default: `false`) Automatically insert a cap wherever a break is encountered, signaled by a position with `w = 0`. Allows drawing lines and caps with a single draw call. *Use this option with care though*. If using miter joins and round caps with a high cap resolution, then *every segment instance* will have enough points to draw two caps, even if they never actually result in valid triangles. Additional configuration parameters are forwarded to a `regl` command which wraps drawing. diff --git a/CHANGELOG.md b/CHANGELOG.md index 385b71e..019e148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,23 @@ +## 0.0.21 + +### Features + +- Clean up one of the worst parts of the shader code and get end cap insertion working with all combinations of joins and caps. :tada: + ## 0.0.20 +### Features + - Add optional `extrapolate` keyword, as in `#pragma lines: extrapolate varying float name` to distinguish between varyings which are extrapolated outside the bounds of their respective segment endpoint values, and varyings which are clamped to the range of the segment. This can be used to dash caps and joins or to ensure colors are not extrapolated. ## 0.0.19 +### Features + - Add `insertCaps` option to be explicit about when caps are automatically inserted + +### Bugfixes + - Switch to preferring `w = 0` instead of `NaN` since `NaN` detection is a bit unreliable in GLSL. ## 0.0.18 diff --git a/dist/regl-gpu-lines.compat.js b/dist/regl-gpu-lines.compat.js index 252283d..8dab6eb 100644 --- a/dist/regl-gpu-lines.compat.js +++ b/dist/regl-gpu-lines.compat.js @@ -213,22 +213,31 @@ var spec = isEndpoints ? endpointSpec : segmentSpec; var verts = ['B', 'C', 'D']; if (!isEndpoints) verts.unshift('A'); + + function computeCount(props) { + return insertCaps ? isEndpoints // Cap has fixed number, join could either be a cap or a join + ? [props.capResolution, Math.max(props.capResolution, props.joinResolution)] // Both could be either a cap or a join + : [Math.max(props.capResolution, props.joinResolution), Math.max(props.capResolution, props.joinResolution)] : isEndpoints // Draw a cap + ? [props.capResolution, props.joinResolution] // Draw two joins + : [props.joinResolution, props.joinResolution]; + } + return regl({ - vert: "".concat(meta.glsl, "\nconst float CAP_START = ").concat(ORIENTATION$2.CAP_START, ".0;\nconst float CAP_END = ").concat(ORIENTATION$2.CAP_END, ".0;\n\n").concat(spec.glsl, "\n\nattribute float index;\n").concat(debug ? 'attribute float debugInstanceID;' : '', "\n\nuniform vec3 joinRes;\nuniform vec2 resolution, capScale;\nuniform float miterLimit;\n").concat(meta.orientation || !isEndpoints ? '' : 'uniform float orientation;', "\n\nvarying vec3 lineCoord;\nvarying float dir;\n").concat(debug ? 'varying vec2 triStripCoord;' : '', "\n").concat(debug ? 'varying float instanceID;' : '', "\n\nbool isnan(float val) {\n return (val < 0.0 || 0.0 < val || val == 0.0) ? false : true;\n}\n\nbool invalid(vec4 p) {\n return p.w == 0.0 || isnan(p.x);\n}\n\nconst bool isRound = ").concat(isRound ? 'true' : 'false', ";\nconst float pi = 3.141592653589793;\n\nvoid main() {\n lineCoord = vec3(0);\n\n ").concat(debug ? "instanceID = ".concat(isEndpoints ? '-1.0' : 'debugInstanceID', ";") : '', "\n ").concat(debug ? 'triStripCoord = vec2(floor(index / 2.0), mod(index, 2.0));' : '', "\n\n ").concat(verts.map(function (vert) { + vert: "".concat(meta.glsl, "\nconst float CAP_START = ").concat(ORIENTATION$2.CAP_START, ".0;\nconst float CAP_END = ").concat(ORIENTATION$2.CAP_END, ".0;\n\n").concat(spec.glsl, "\n\nattribute float index;\n").concat(debug ? 'attribute float debugInstanceID;' : '', "\n\nuniform vec2 vertexCount, capJoinRes;\nuniform vec2 resolution, capScale;\nuniform float miterLimit;\n").concat(meta.orientation || !isEndpoints ? '' : 'uniform float orientation;', "\n\nvarying vec3 lineCoord;\nvarying float dir;\n").concat(debug ? 'varying vec2 triStripCoord;' : '', "\n").concat(debug ? 'varying float instanceID;' : '', "\n\nbool isnan(float val) {\n return (val < 0.0 || 0.0 < val || val == 0.0) ? false : true;\n}\n\nbool invalid(vec4 p) {\n return p.w == 0.0 || isnan(p.x);\n}\n\nconst bool isRound = ").concat(isRound ? 'true' : 'false', ";\nconst float pi = 3.141592653589793;\n\nvoid main() {\n lineCoord = vec3(0);\n\n ").concat(debug ? "instanceID = ".concat(isEndpoints ? '-1.0' : 'debugInstanceID', ";") : '', "\n ").concat(debug ? 'triStripCoord = vec2(floor(index / 2.0), mod(index, 2.0));' : '', "\n\n ").concat(verts.map(function (vert) { return "vec4 p".concat(vert, " = ").concat(meta.position.generate(vert), ";"); - }).join('\n'), "\n\n // Check for invalid vertices\n if (invalid(pB) || invalid(pC)").concat(insertCaps ? '' : "".concat(isEndpoints ? '' : '|| invalid(pA)', " || invalid(pD)"), ") {\n gl_Position = vec4(1,1,1,0);\n return;\n }\n\n // If we're past the first half-join and half of the segment, then we swap all vertices and start\n // over from the opposite end.\n bool isMirrored = index > joinRes.x * 2.0 + 3.0;\n\n // Convert to screen-pixel coordinates\n // Save w so we can perspective re-multiply at the end to get varyings depth-correct\n float pw = isMirrored ? pC.w : pB.w;\n ").concat(verts.map(function (v) { + }).join('\n'), "\n\n // Check for invalid vertices\n if (invalid(pB) || invalid(pC)) {\n gl_Position = vec4(1,1,1,0);\n return;\n }\n\n float mirrorIndex = 2.0 * vertexCount.x + 3.0;\n float totalVertexCount = mirrorIndex + 2.0 + 2.0 * vertexCount.y;\n\n // If we're past the first half-join and half of the segment, then we swap all vertices and start\n // over from the opposite end.\n bool isMirrored = index > mirrorIndex;\n\n // When rendering dedicated endoints, this allows us to insert an end cap *alone* (without the attached\n // segment and join)\n ").concat(isEndpoints ? 'if (invalid(pD) && isMirrored) { gl_Position = vec4(0); return; }' : '', "\n\n // Convert to screen-pixel coordinates\n // Save w so we can perspective re-multiply at the end to get varyings depth-correct\n float pw = isMirrored ? pC.w : pB.w;\n ").concat(verts.map(function (v) { return "p".concat(v, " = vec4(vec3(p").concat(v, ".xy * resolution, p").concat(v, ".z) / p").concat(v, ".w, 1);"); - }).join('\n'), "\n\n // If it's a cap, mirror A back onto C to accomplish a round\n ").concat(isEndpoints ? "vec4 pA = pC;" : '', "\n\n vec2 res = isMirrored ? joinRes.yx : joinRes.xy;\n\n float mirrorSign = isMirrored ? -1.0 : 1.0;\n if (isMirrored) {\n vec4 tmp;\n tmp = pC; pC = pB; pB = tmp;\n tmp = pD; pD = pA; pA = tmp;\n }\n\n ").concat(isEndpoints ? "bool isCap = !isMirrored;" : "bool isCap = false;", ";\n\n if (invalid(pA)) { pA = pC; isCap = true; }\n if (invalid(pD)) { pD = pB; }\n\n float width = isMirrored ? ").concat(meta.width.generate('C'), " : ").concat(meta.width.generate('B'), ";\n\n // Invalidate triangles too far in front of or behind the camera plane\n if (max(abs(pB.z), abs(pC.z)) > 1.0) {\n gl_Position = vec4(1,1,1,0);\n return;\n }\n\n // Tangent and normal vectors\n vec2 tBC = pC.xy - pB.xy;\n float lBC = length(tBC);\n tBC /= lBC;\n vec2 nBC = vec2(-tBC.y, tBC.x);\n\n vec2 tAB = pB.xy - pA.xy;\n float lAB = length(tAB);\n if (lAB > 0.0) tAB /= lAB;\n vec2 nAB = vec2(-tAB.y, tAB.x);\n\n vec2 tCD = pD.xy - pC.xy;\n float lCD = length(tCD);\n if (lCD > 0.0) tCD /= lCD;\n vec2 nCD = vec2(-tCD.y, tCD.x);\n\n float cosB = clamp(dot(tAB, tBC), -1.0, 1.0);\n\n // This section is very fragile. When lines are collinear, signs flip randomly and break orientation\n // of the middle segment. The fix appears straightforward, but this took a few hours to get right.\n const float tol = 1e-4;\n float dirB = -dot(tBC, nAB);\n float dirC = dot(tBC, nCD);\n bool bCollinear = abs(dirB) < tol;\n bool cCollinear = abs(dirC) < tol;\n bool bIsHairpin = bCollinear && cosB < 0.0;\n bool cIsHairpin = cCollinear && dot(tBC, tCD) < 0.0;\n dirB = bCollinear ? -mirrorSign : sign(dirB);\n dirC = cCollinear ? -mirrorSign : sign(dirC);\n\n vec2 miter = bIsHairpin ? -tBC : 0.5 * (nAB + nBC) * dirB;\n\n // The second half of the triangle strip instance is just the first, reversed, and with vertices swapped!\n float i = index < 2.0 * joinRes.x + 4.0 ? index : 2.0 * (res.x + res.y) + 5.0 - index;\n\n // Chop off the join to get at the segment part index\n float iSeg = i - 2.0 * res.x;\n\n // After the first half-join, repeat two vertices of the segment strip in order to get the orientation correct\n // for the next join. These are wasted vertices, but they enable using a triangle strip. for two joins which\n // might be oriented differently.\n if (iSeg > 1.0 && iSeg <= 3.0) {\n iSeg -= 2.0;\n if (dirB * dirC >= 0.0) iSeg += iSeg == 0.0 ? 1.0 : -1.0;\n }\n\n vec2 xBasis = tBC;\n vec2 yBasis = nBC * dirB;\n vec2 xy = vec2(0, 1);\n\n lineCoord.y = dirB * mirrorSign;\n\n if (iSeg < 0.0) {\n // Draw half of a join\n float m2 = dot(miter, miter);\n float lm = length(miter);\n float tBCm = dot(tBC, miter);\n yBasis = miter / lm;\n bool isBevel = 1.0 > miterLimit * m2;\n\n if (mod(i, 2.0) == 0.0) {\n // Outer joint points\n if (isRound || isCap) {\n // Round joins\n xBasis = dirB * vec2(yBasis.y, -yBasis.x);\n float divisor = ").concat(isEndpoints ? 'res.x' : 'min(res.x, isCap ? joinRes.z : res.x)', " * 2.0;\n float theta = -0.5 * (acos(cosB) * (min(i, divisor) / divisor) - pi) * (isCap ? 2.0 : 1.0);\n xy = vec2(cos(theta), sin(theta));\n\n if (isCap) {\n if (xy.y > 0.5) xy *= capScale;\n lineCoord.xy = xy.yx * lineCoord.y;\n }\n } else {\n // Miter joins\n yBasis = bIsHairpin ? vec2(0) : miter;\n if (!isBevel) xy.y /= m2;\n }\n } else {\n // Repeat vertex B to create a triangle fan\n lineCoord.y = 0.0;\n xy = vec2(0);\n\n // Offset the center vertex position to get bevel SDF correct\n if (!isRound && isBevel && !isCap) {\n xy.y = -1.0 + sqrt((1.0 + cosB) * 0.5);\n }\n }\n //} else if (iSeg == 0.0) { // No op: vertex B + line B-C normal\n } else if (iSeg > 0.0) {\n // vertex B + inner miter\n lineCoord.y = -lineCoord.y;\n\n float miterExt = 0.0;\n if (cosB > -0.9999) {\n float sinB = tAB.x * tBC.y - tAB.y * tBC.x;\n miterExt = sinB / (1.0 + cosB);\n }\n float m = abs(miterExt);\n m = min(m, min(lBC, lAB) / width);\n xy = vec2(m, -1);\n }\n\n ").concat(isEndpoints ? "float orientation = ".concat(meta.orientation ? meta.orientation.generate('') : 'mod(orientation,2.0)', ";") : '', ";\n ").concat(isEndpoints ? "if (orientation == CAP_END) lineCoord.xy = -lineCoord.xy;" : '', "\n\n vec2 dP = mat2(xBasis, yBasis) * xy;\n float dC = dot(dP, tBC) * mirrorSign;\n\n float useC = (isMirrored ? 1.0 : 0.0) + dC * (width / lBC);\n lineCoord.z = useC < 0.0 || useC > 1.0 ? 1.0 : 0.0;\n ").concat(_toConsumableArray(meta.varyings.values()).map(function (varying) { + }).join('\n'), "\n\n // If it's a cap, mirror A back onto C to accomplish a round\n ").concat(isEndpoints ? "vec4 pA = pC;" : '', "\n\n float mirrorSign = isMirrored ? -1.0 : 1.0;\n if (isMirrored) {\n vec4 tmp;\n tmp = pC; pC = pB; pB = tmp;\n tmp = pD; pD = pA; pA = tmp;\n }\n\n ").concat(isEndpoints ? "bool isCap = !isMirrored;" : "bool isCap = false;", ";\n\n if (invalid(pA)) { ").concat(insertCaps ? 'pA = pC; isCap = true;' : 'pA = 2.0 * pB - pC;', " }\n if (invalid(pD)) { ").concat(insertCaps ? 'pD = pB;' : 'pD = 2.0 * pC - pB;', " }\n\n float width = isMirrored ? ").concat(meta.width.generate('C'), " : ").concat(meta.width.generate('B'), ";\n\n // Invalidate triangles too far in front of or behind the camera plane\n if (max(abs(pB.z), abs(pC.z)) > 1.0) {\n gl_Position = vec4(0);\n return;\n }\n\n // Tangent and normal vectors\n vec2 tBC = pC.xy - pB.xy;\n float lBC = length(tBC);\n tBC /= lBC;\n vec2 nBC = vec2(-tBC.y, tBC.x);\n\n vec2 tAB = pB.xy - pA.xy;\n float lAB = length(tAB);\n if (lAB > 0.0) tAB /= lAB;\n vec2 nAB = vec2(-tAB.y, tAB.x);\n\n vec2 tCD = pD.xy - pC.xy;\n float lCD = length(tCD);\n if (lCD > 0.0) tCD /= lCD;\n vec2 nCD = vec2(-tCD.y, tCD.x);\n\n float cosB = clamp(dot(tAB, tBC), -1.0, 1.0);\n\n // This section is very fragile. When lines are collinear, signs flip randomly and break orientation\n // of the middle segment. The fix appears straightforward, but this took a few hours to get right.\n const float tol = 1e-4;\n float dirB = -dot(tBC, nAB);\n float dirC = dot(tBC, nCD);\n bool bCollinear = abs(dirB) < tol;\n bool cCollinear = abs(dirC) < tol;\n bool bIsHairpin = bCollinear && cosB < 0.0;\n bool cIsHairpin = cCollinear && dot(tBC, tCD) < 0.0;\n dirB = bCollinear ? -mirrorSign : sign(dirB);\n dirC = cCollinear ? -mirrorSign : sign(dirC);\n\n vec2 miter = bIsHairpin ? -tBC : 0.5 * (nAB + nBC) * dirB;\n\n // The second half of the triangle strip instance is just the first, reversed, and with vertices swapped!\n float i = index <= mirrorIndex ? index : totalVertexCount - index;\n\n // Chop off the join to get at the segment part index\n float iSeg = i - 2.0 * (isMirrored ? vertexCount.y : vertexCount.x);\n\n // After the first half-join, repeat two vertices of the segment strip in order to get the orientation correct\n // for the next join. These are wasted vertices, but they enable using a triangle strip. for two joins which\n // might be oriented differently.\n if (iSeg > 1.0 && iSeg <= 3.0) {\n iSeg -= 2.0;\n if (dirB * dirC >= 0.0) iSeg += iSeg == 0.0 ? 1.0 : -1.0;\n }\n\n vec2 xBasis = tBC;\n vec2 yBasis = nBC * dirB;\n vec2 xy = vec2(0, 1);\n\n lineCoord.y = dirB * mirrorSign;\n\n if (iSeg < 0.0) {\n // Draw half of a join\n float m2 = dot(miter, miter);\n float lm = length(miter);\n float tBCm = dot(tBC, miter);\n yBasis = miter / lm;\n bool isBevel = 1.0 > miterLimit * m2;\n\n if (mod(i, 2.0) == 0.0) {\n // Outer joint points\n if (isRound || isCap) {\n // Round joins\n xBasis = dirB * vec2(yBasis.y, -yBasis.x);\n float cnt = (isCap ? capJoinRes.x : capJoinRes.y) * 2.0;\n float theta = -0.5 * (acos(cosB) * (min(i, cnt) / cnt) - pi) * (isCap ? 2.0 : 1.0);\n xy = vec2(cos(theta), sin(theta));\n\n if (isCap) {\n if (xy.y > 0.001) xy *= capScale;\n lineCoord.xy = xy.yx * lineCoord.y;\n }\n } else {\n // Miter joins\n yBasis = bIsHairpin ? vec2(0) : miter;\n if (!isBevel) xy.y /= m2;\n }\n } else {\n // Repeat vertex B to create a triangle fan\n lineCoord.y = 0.0;\n xy = vec2(0);\n\n // Offset the center vertex position to get bevel SDF correct\n if (!isRound && isBevel && !isCap) {\n xy.y = -1.0 + sqrt((1.0 + cosB) * 0.5);\n }\n }\n //} else if (iSeg == 0.0) { // No op: vertex B + line B-C normal\n } else if (iSeg > 0.0) {\n // vertex B + inner miter\n lineCoord.y = -lineCoord.y;\n\n float miterExt = 0.0;\n if (cosB > -0.9999) {\n float sinB = tAB.x * tBC.y - tAB.y * tBC.x;\n miterExt = sinB / (1.0 + cosB);\n }\n float m = abs(miterExt);\n m = min(m, min(lBC, lAB) / width);\n xy = vec2(m, -1);\n }\n\n ").concat(isEndpoints ? "float orientation = ".concat(meta.orientation ? meta.orientation.generate('') : 'mod(orientation,2.0)', ";") : '', ";\n ").concat(isEndpoints ? "if (orientation == CAP_END) lineCoord.xy = -lineCoord.xy;" : '', "\n\n vec2 dP = mat2(xBasis, yBasis) * xy;\n float dC = dot(dP, tBC) * mirrorSign;\n\n float useC = (isMirrored ? 1.0 : 0.0) + dC * (width / lBC);\n lineCoord.z = useC < 0.0 || useC > 1.0 ? 1.0 : 0.0;\n ").concat(_toConsumableArray(meta.varyings.values()).map(function (varying) { return varying.generate('useC', 'B', 'C'); }).join('\n'), "\n\n gl_Position = pB;\n gl_Position.xy += width * dP;\n gl_Position.xy /= resolution;\n gl_Position *= pw;\n}"), frag: frag, attributes: _objectSpread2(_objectSpread2({}, indexAttributes), spec.attrs), uniforms: { - joinRes: function joinRes(ctx, props) { - return [// First half-join is actually a cap if we're drawing endpoints - isEndpoints ? props.capResolution : props.joinResolution, // Second half-join is always a join - props.joinResolution, // The resolution of inserted caps - props.capType === 'square' ? props.capResolution : props.capType === 'none' ? 0 : props.joinResolution]; + vertexCount: function vertexCount(ctx, props) { + return computeCount(props); + }, + capJoinRes: function capJoinRes(ctx, props) { + return [props.capResolution, props.joinResolution]; }, miterLimit: function miterLimit(ctx, props) { return props.miterLimit * props.miterLimit; @@ -242,10 +251,9 @@ } : function (ctx, props) { return props.count - 3; }, - count: isRound ? function (ctx, props) { - return 6 + 2 * (props.joinResolution + (isEndpoints ? props.capResolution : props.joinResolution)); - } : function (ctx, props) { - return 6 + 2 * (1 + (isEndpoints ? props.capResolution : 1)) + 10; + count: function count(ctx, props) { + var count = computeCount(props); + return 6 + 2 * (count[0] + count[1]); } }); } diff --git a/dist/regl-gpu-lines.compat.min.js b/dist/regl-gpu-lines.compat.min.js index 09a7568..0aa4081 100644 --- a/dist/regl-gpu-lines.compat.min.js +++ b/dist/regl-gpu-lines.compat.min.js @@ -1 +1 @@ -!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(n="undefined"!=typeof globalThis?globalThis:n||self).reglLines=t()}(this,(function(){"use strict";function n(n,t){var e=Object.keys(n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(n);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(n,t).enumerable}))),e.push.apply(e,r)}return e}function t(t){for(var r=1;rn.length)&&(t=n.length);for(var e=0,r=new Array(t);e=n.length?{done:!0}:{done:!1,value:n[r++]}},e:function(n){throw n},f:i}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable,non-array objects must have a [Symbol.iterator]()method.")}var a,s=!0,c=!1;return{s:function(){e=e.call(n)},n:function(){var n=e.next();return s=n.done,n},e:function(n){c=!0,a=n},f:function(){try{s||null==e.return||e.return()}finally{if(c)throw a}}}}var c={CAP_START:0,CAP_END:1,CAP_SHORT:2},f=c,l=function(n,e,r){var o=r.regl,a=r.meta,s=r.frag,c=r.segmentSpec,l=r.endpointSpec,u=r.indexAttributes,p=r.insertCaps,d=r.debug,v=e?l:c,y=["B","C","D"];e||y.unshift("A");return o({vert:"".concat(a.glsl,"\nconst float CAP_START=").concat(f.CAP_START,".0;const float CAP_END=").concat(f.CAP_END,".0;").concat(v.glsl,"\nattribute float index;").concat(d?"attribute float debugInstanceID;":"","\nuniform vec3 joinRes;uniform vec2 resolution,capScale;uniform float miterLimit;").concat(a.orientation||!e?"":"uniform float orientation;","\nvarying vec3 lineCoord;varying float dir;").concat(d?"varying vec2 triStripCoord;":"","\n").concat(d?"varying float instanceID;":"","\nbool isnan(float val){return(val<0.0||0.0joinRes.x*2.0+3.0;float pw=isMirrored?pC.w:pB.w;").concat(y.map((function(n){return"p".concat(n,"=vec4(vec3(p").concat(n,".xy*resolution,p").concat(n,".z)/p").concat(n,".w,1);")})).join("\n"),"\n").concat(e?"vec4 pA=pC;":"","\nvec2 res=isMirrored?joinRes.yx:joinRes.xy;float mirrorSign=isMirrored?-1.0:1.0;if(isMirrored){vec4 tmp;tmp=pC; pC=pB; pB=tmp;tmp=pD; pD=pA; pA=tmp;}").concat(e?"bool isCap=!isMirrored;":"bool isCap=false;",";if(invalid(pA)){ pA=pC; isCap=true; }if(invalid(pD)){ pD=pB; }float width=isMirrored?").concat(a.width.generate("C"),":").concat(a.width.generate("B"),";if(max(abs(pB.z),abs(pC.z))>1.0){gl_Position=vec4(1,1,1,0);return;}vec2 tBC=pC.xy-pB.xy;float lBC=length(tBC);tBC/=lBC;vec2 nBC=vec2(-tBC.y,tBC.x);vec2 tAB=pB.xy-pA.xy;float lAB=length(tAB);if(lAB>0.0)tAB/=lAB;vec2 nAB=vec2(-tAB.y,tAB.x);vec2 tCD=pD.xy-pC.xy;float lCD=length(tCD);if(lCD>0.0)tCD/=lCD;vec2 nCD=vec2(-tCD.y,tCD.x);float cosB=clamp(dot(tAB,tBC),-1.0,1.0);const float tol=1e-4;float dirB=-dot(tBC,nAB);float dirC=dot(tBC,nCD);bool bCollinear=abs(dirB)1.0 && iSeg<=3.0){iSeg-=2.0;if(dirB*dirC>=0.0)iSeg+=iSeg==0.0?1.0:-1.0;}vec2 xBasis=tBC;vec2 yBasis=nBC*dirB;vec2 xy=vec2(0,1);lineCoord.y=dirB*mirrorSign;if(iSeg<0.0){float m2=dot(miter,miter);float lm=length(miter);float tBCm=dot(tBC,miter);yBasis=miter/lm;bool isBevel=1.0>miterLimit*m2;if(mod(i,2.0)==0.0){if(isRound||isCap){xBasis=dirB*vec2(yBasis.y,-yBasis.x);float divisor=").concat(e?"res.x":"min(res.x,isCap?joinRes.z:res.x)","*2.0;float theta=-0.5*(acos(cosB)*(min(i,divisor)/divisor)-pi)*(isCap?2.0:1.0);xy=vec2(cos(theta),sin(theta));if(isCap){if(xy.y>0.5)xy*=capScale;lineCoord.xy=xy.yx*lineCoord.y;}} else {yBasis=bIsHairpin?vec2(0):miter;if(!isBevel)xy.y/=m2;}} else {lineCoord.y=0.0;xy=vec2(0);if(!isRound && isBevel && !isCap){xy.y=-1.0+sqrt((1.0+cosB)*0.5);}}} else if(iSeg>0.0){lineCoord.y=-lineCoord.y;float miterExt=0.0;if(cosB>-0.9999){float sinB=tAB.x*tBC.y-tAB.y*tBC.x;miterExt=sinB/(1.0+cosB);}float m=abs(miterExt);m=min(m,min(lBC,lAB)/width);xy=vec2(m,-1);}").concat(e?"float orientation=".concat(a.orientation?a.orientation.generate(""):"mod(orientation,2.0)",";"):"",";").concat(e?"if(orientation==CAP_END)lineCoord.xy=-lineCoord.xy;":"","\nvec2 dP=mat2(xBasis,yBasis)*xy;float dC=dot(dP,tBC)*mirrorSign;float useC=(isMirrored?1.0:0.0)+dC*(width/lBC);lineCoord.z=useC<0.0||useC>1.0?1.0:0.0;").concat(i(a.varyings.values()).map((function(n){return n.generate("useC","B","C")})).join("\n"),"\ngl_Position=pB;gl_Position.xy+=width*dP;gl_Position.xy/=resolution;gl_Position*=pw;}"),frag:s,attributes:t(t({},u),v.attrs),uniforms:{joinRes:function(n,t){return[e?t.capResolution:t.joinResolution,t.joinResolution,"square"===t.capType?t.capResolution:"none"===t.capType?0:t.joinResolution]},miterLimit:function(n,t){return t.miterLimit*t.miterLimit},orientation:o.prop("orientation"),capScale:o.prop("capScale")},primitive:"triangle strip",instances:e?function(n,t){return t.splitCaps?t.orientation===f.CAP_START?Math.ceil(t.count/2):Math.floor(t.count/2):t.count}:function(n,t){return t.count-3},count:n?function(n,t){return 6+2*(t.joinResolution+(e?t.capResolution:t.joinResolution))}:function(n,t){return 6+2*(1+(e?t.capResolution:1))+10}})};var u={NONE:0,REGULAR:1,EXTENDED:2,PER_INSTANCE:4},p=u,d=function(n){for(var e=[],r=n.split("\n"),i=0;i1&&void 0!==arguments[1]?arguments[1]:{},r=e.vert,o=void 0===r?null:r,a=e.frag,c=void 0===a?null:a,f=e.debug,l=void 0!==f&&f,u=e.insertCaps,p=void 0!==u&&u,d=t({},e),v=0,y=["vert","frag","debug","insertCaps"];vn.length)&&(t=n.length);for(var e=0,r=new Array(t);e=n.length?{done:!0}:{done:!1,value:n[r++]}},e:function(n){throw n},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable,non-array objects must have a [Symbol.iterator]()method.")}var a,s=!0,c=!1;return{s:function(){e=e.call(n)},n:function(){var n=e.next();return s=n.done,n},e:function(n){c=!0,a=n},f:function(){try{s||null==e.return||e.return()}finally{if(c)throw a}}}}var c={CAP_START:0,CAP_END:1,CAP_SHORT:2},f=c,l=function(n,e,r){var i=r.regl,a=r.meta,s=r.frag,c=r.segmentSpec,l=r.endpointSpec,u=r.indexAttributes,p=r.insertCaps,d=r.debug,v=e?l:c,y=["B","C","D"];e||y.unshift("A");function m(n){return p?e?[n.capResolution,Math.max(n.capResolution,n.joinResolution)]:[Math.max(n.capResolution,n.joinResolution),Math.max(n.capResolution,n.joinResolution)]:e?[n.capResolution,n.joinResolution]:[n.joinResolution,n.joinResolution]}return i({vert:"".concat(a.glsl,"\nconst float CAP_START=").concat(f.CAP_START,".0;const float CAP_END=").concat(f.CAP_END,".0;").concat(v.glsl,"\nattribute float index;").concat(d?"attribute float debugInstanceID;":"","\nuniform vec2 vertexCount,capJoinRes;uniform vec2 resolution,capScale;uniform float miterLimit;").concat(a.orientation||!e?"":"uniform float orientation;","\nvarying vec3 lineCoord;varying float dir;").concat(d?"varying vec2 triStripCoord;":"","\n").concat(d?"varying float instanceID;":"","\nbool isnan(float val){return(val<0.0||0.0mirrorIndex;").concat(e?"if(invalid(pD)&& isMirrored){ gl_Position=vec4(0); return; }":"","\nfloat pw=isMirrored?pC.w:pB.w;").concat(y.map((function(n){return"p".concat(n,"=vec4(vec3(p").concat(n,".xy*resolution,p").concat(n,".z)/p").concat(n,".w,1);")})).join("\n"),"\n").concat(e?"vec4 pA=pC;":"","\nfloat mirrorSign=isMirrored?-1.0:1.0;if(isMirrored){vec4 tmp;tmp=pC; pC=pB; pB=tmp;tmp=pD; pD=pA; pA=tmp;}").concat(e?"bool isCap=!isMirrored;":"bool isCap=false;",";if(invalid(pA)){ ").concat(p?"pA=pC; isCap=true;":"pA=2.0*pB-pC;"," }if(invalid(pD)){ ").concat(p?"pD=pB;":"pD=2.0*pC-pB;"," }float width=isMirrored?").concat(a.width.generate("C"),":").concat(a.width.generate("B"),";if(max(abs(pB.z),abs(pC.z))>1.0){gl_Position=vec4(0);return;}vec2 tBC=pC.xy-pB.xy;float lBC=length(tBC);tBC/=lBC;vec2 nBC=vec2(-tBC.y,tBC.x);vec2 tAB=pB.xy-pA.xy;float lAB=length(tAB);if(lAB>0.0)tAB/=lAB;vec2 nAB=vec2(-tAB.y,tAB.x);vec2 tCD=pD.xy-pC.xy;float lCD=length(tCD);if(lCD>0.0)tCD/=lCD;vec2 nCD=vec2(-tCD.y,tCD.x);float cosB=clamp(dot(tAB,tBC),-1.0,1.0);const float tol=1e-4;float dirB=-dot(tBC,nAB);float dirC=dot(tBC,nCD);bool bCollinear=abs(dirB)1.0 && iSeg<=3.0){iSeg-=2.0;if(dirB*dirC>=0.0)iSeg+=iSeg==0.0?1.0:-1.0;}vec2 xBasis=tBC;vec2 yBasis=nBC*dirB;vec2 xy=vec2(0,1);lineCoord.y=dirB*mirrorSign;if(iSeg<0.0){float m2=dot(miter,miter);float lm=length(miter);float tBCm=dot(tBC,miter);yBasis=miter/lm;bool isBevel=1.0>miterLimit*m2;if(mod(i,2.0)==0.0){if(isRound||isCap){xBasis=dirB*vec2(yBasis.y,-yBasis.x);float cnt=(isCap?capJoinRes.x:capJoinRes.y)*2.0;float theta=-0.5*(acos(cosB)*(min(i,cnt)/cnt)-pi)*(isCap?2.0:1.0);xy=vec2(cos(theta),sin(theta));if(isCap){if(xy.y>0.001)xy*=capScale;lineCoord.xy=xy.yx*lineCoord.y;}} else {yBasis=bIsHairpin?vec2(0):miter;if(!isBevel)xy.y/=m2;}} else {lineCoord.y=0.0;xy=vec2(0);if(!isRound && isBevel && !isCap){xy.y=-1.0+sqrt((1.0+cosB)*0.5);}}} else if(iSeg>0.0){lineCoord.y=-lineCoord.y;float miterExt=0.0;if(cosB>-0.9999){float sinB=tAB.x*tBC.y-tAB.y*tBC.x;miterExt=sinB/(1.0+cosB);}float m=abs(miterExt);m=min(m,min(lBC,lAB)/width);xy=vec2(m,-1);}").concat(e?"float orientation=".concat(a.orientation?a.orientation.generate(""):"mod(orientation,2.0)",";"):"",";").concat(e?"if(orientation==CAP_END)lineCoord.xy=-lineCoord.xy;":"","\nvec2 dP=mat2(xBasis,yBasis)*xy;float dC=dot(dP,tBC)*mirrorSign;float useC=(isMirrored?1.0:0.0)+dC*(width/lBC);lineCoord.z=useC<0.0||useC>1.0?1.0:0.0;").concat(o(a.varyings.values()).map((function(n){return n.generate("useC","B","C")})).join("\n"),"\ngl_Position=pB;gl_Position.xy+=width*dP;gl_Position.xy/=resolution;gl_Position*=pw;}"),frag:s,attributes:t(t({},u),v.attrs),uniforms:{vertexCount:function(n,t){return m(t)},capJoinRes:function(n,t){return[t.capResolution,t.joinResolution]},miterLimit:function(n,t){return t.miterLimit*t.miterLimit},orientation:i.prop("orientation"),capScale:i.prop("capScale")},primitive:"triangle strip",instances:e?function(n,t){return t.splitCaps?t.orientation===f.CAP_START?Math.ceil(t.count/2):Math.floor(t.count/2):t.count}:function(n,t){return t.count-3},count:function(n,t){var e=m(t);return 6+2*(e[0]+e[1])}})};var u={NONE:0,REGULAR:1,EXTENDED:2,PER_INSTANCE:4},p=u,d=function(n){for(var e=[],r=n.split("\n"),o=0;o1&&void 0!==arguments[1]?arguments[1]:{},r=e.vert,i=void 0===r?null:r,a=e.frag,c=void 0===a?null:a,f=e.debug,l=void 0!==f&&f,u=e.insertCaps,p=void 0!==u&&u,d=t({},e),v=0,y=["vert","frag","debug","insertCaps"];v `vec4 p${vert} = ${meta.position.generate(vert)};`).join('\n')} // Check for invalid vertices - if (invalid(pB) || invalid(pC)${insertCaps ? '' : `${isEndpoints ? '' : '|| invalid(pA)'} || invalid(pD)`}) { + if (invalid(pB) || invalid(pC)) { gl_Position = vec4(1,1,1,0); return; } + float mirrorIndex = 2.0 * vertexCount.x + 3.0; + float totalVertexCount = mirrorIndex + 2.0 + 2.0 * vertexCount.y; + // If we're past the first half-join and half of the segment, then we swap all vertices and start // over from the opposite end. - bool isMirrored = index > joinRes.x * 2.0 + 3.0; + bool isMirrored = index > mirrorIndex; + + // When rendering dedicated endoints, this allows us to insert an end cap *alone* (without the attached + // segment and join) + ${isEndpoints ? 'if (invalid(pD) && isMirrored) { gl_Position = vec4(0); return; }' : ''} // Convert to screen-pixel coordinates // Save w so we can perspective re-multiply at the end to get varyings depth-correct @@ -86,8 +102,6 @@ void main() { // If it's a cap, mirror A back onto C to accomplish a round ${isEndpoints ? `vec4 pA = pC;` : ''} - vec2 res = isMirrored ? joinRes.yx : joinRes.xy; - float mirrorSign = isMirrored ? -1.0 : 1.0; if (isMirrored) { vec4 tmp; @@ -97,14 +111,14 @@ void main() { ${isEndpoints ? `bool isCap = !isMirrored;` : `bool isCap = false;`}; - if (invalid(pA)) { pA = pC; isCap = true; } - if (invalid(pD)) { pD = pB; } + if (invalid(pA)) { ${insertCaps ? 'pA = pC; isCap = true;' : 'pA = 2.0 * pB - pC;'} } + if (invalid(pD)) { ${insertCaps ? 'pD = pB;' : 'pD = 2.0 * pC - pB;'} } float width = isMirrored ? ${meta.width.generate('C')} : ${meta.width.generate('B')}; // Invalidate triangles too far in front of or behind the camera plane if (max(abs(pB.z), abs(pC.z)) > 1.0) { - gl_Position = vec4(1,1,1,0); + gl_Position = vec4(0); return; } @@ -141,10 +155,10 @@ void main() { vec2 miter = bIsHairpin ? -tBC : 0.5 * (nAB + nBC) * dirB; // The second half of the triangle strip instance is just the first, reversed, and with vertices swapped! - float i = index < 2.0 * joinRes.x + 4.0 ? index : 2.0 * (res.x + res.y) + 5.0 - index; + float i = index <= mirrorIndex ? index : totalVertexCount - index; // Chop off the join to get at the segment part index - float iSeg = i - 2.0 * res.x; + float iSeg = i - 2.0 * (isMirrored ? vertexCount.y : vertexCount.x); // After the first half-join, repeat two vertices of the segment strip in order to get the orientation correct // for the next join. These are wasted vertices, but they enable using a triangle strip. for two joins which @@ -173,12 +187,12 @@ void main() { if (isRound || isCap) { // Round joins xBasis = dirB * vec2(yBasis.y, -yBasis.x); - float divisor = ${isEndpoints ? 'res.x' : 'min(res.x, isCap ? joinRes.z : res.x)'} * 2.0; - float theta = -0.5 * (acos(cosB) * (min(i, divisor) / divisor) - pi) * (isCap ? 2.0 : 1.0); + float cnt = (isCap ? capJoinRes.x : capJoinRes.y) * 2.0; + float theta = -0.5 * (acos(cosB) * (min(i, cnt) / cnt) - pi) * (isCap ? 2.0 : 1.0); xy = vec2(cos(theta), sin(theta)); if (isCap) { - if (xy.y > 0.5) xy *= capScale; + if (xy.y > 0.001) xy *= capScale; lineCoord.xy = xy.yx * lineCoord.y; } } else { @@ -231,17 +245,18 @@ void main() { ...spec.attrs }, uniforms: { - joinRes: (ctx, props) => [// First half-join is actually a cap if we're drawing endpoints - isEndpoints ? props.capResolution : props.joinResolution, // Second half-join is always a join - props.joinResolution, // The resolution of inserted caps - props.capType === 'square' ? props.capResolution : props.capType === 'none' ? 0 : props.joinResolution], + vertexCount: (ctx, props) => computeCount(props), + capJoinRes: (ctx, props) => [props.capResolution, props.joinResolution], miterLimit: (ctx, props) => props.miterLimit * props.miterLimit, orientation: regl.prop('orientation'), capScale: regl.prop('capScale') }, primitive: 'triangle strip', instances: isEndpoints ? (ctx, props) => props.splitCaps ? props.orientation === ORIENTATION$2.CAP_START ? Math.ceil(props.count / 2) : Math.floor(props.count / 2) : props.count : (ctx, props) => props.count - 3, - count: isRound ? (ctx, props) => 6 + 2 * (props.joinResolution + (isEndpoints ? props.capResolution : props.joinResolution)) : (ctx, props) => 6 + 2 * (1 + (isEndpoints ? props.capResolution : 1)) + 10 + count: (ctx, props) => { + const count = computeCount(props); + return 6 + 2 * (count[0] + count[1]); + } }); } diff --git a/dist/regl-gpu-lines.min.js b/dist/regl-gpu-lines.min.js index 9de4b53..5a4c4de 100644 --- a/dist/regl-gpu-lines.min.js +++ b/dist/regl-gpu-lines.min.js @@ -1 +1 @@ -!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e="undefined"!=typeof globalThis?globalThis:e||self).reglLines=n()}(this,(function(){"use strict";var e={CAP_START:0,CAP_END:1,CAP_SHORT:2};const n=e;var t=function(e,t,{regl:i,meta:r,frag:o,segmentSpec:s,endpointSpec:a,indexAttributes:f,insertCaps:p,debug:l}){const c=t?a:s,d=["B","C","D"];t||d.unshift("A");return i({vert:`${r.glsl}const float CAP_START=${n.CAP_START}.0;const float CAP_END=${n.CAP_END}.0;${c.glsl}attribute float index;${l?"attribute float debugInstanceID;":""}uniform vec3 joinRes;uniform vec2 resolution,capScale;uniform float miterLimit;${r.orientation||!t?"":"uniform float orientation;"}varying vec3 lineCoord;varying float dir;${l?"varying vec2 triStripCoord;":""}${l?"varying float instanceID;":""}bool isnan(float val){return(val<0.0||0.0`vec4 p${e}=${r.position.generate(e)};`)).join("\n")}if(invalid(pB)||invalid(pC)${p?"":(t?"":"||invalid(pA)")+"||invalid(pD)"}){gl_Position=vec4(1,1,1,0);return;}bool isMirrored=index>joinRes.x*2.0+3.0;float pw=isMirrored?pC.w:pB.w;${d.map((e=>`p${e}=vec4(vec3(p${e}.xy*resolution,p${e}.z)/p${e}.w,1);`)).join("\n")}${t?"vec4 pA=pC;":""}vec2 res=isMirrored?joinRes.yx:joinRes.xy;float mirrorSign=isMirrored?-1.0:1.0;if(isMirrored){vec4 tmp;tmp=pC; pC=pB; pB=tmp;tmp=pD; pD=pA; pA=tmp;}${t?"bool isCap=!isMirrored;":"bool isCap=false;"};if(invalid(pA)){ pA=pC; isCap=true; }if(invalid(pD)){ pD=pB; }float width=isMirrored?${r.width.generate("C")}:${r.width.generate("B")};if(max(abs(pB.z),abs(pC.z))>1.0){gl_Position=vec4(1,1,1,0);return;}vec2 tBC=pC.xy-pB.xy;float lBC=length(tBC);tBC/=lBC;vec2 nBC=vec2(-tBC.y,tBC.x);vec2 tAB=pB.xy-pA.xy;float lAB=length(tAB);if(lAB>0.0)tAB/=lAB;vec2 nAB=vec2(-tAB.y,tAB.x);vec2 tCD=pD.xy-pC.xy;float lCD=length(tCD);if(lCD>0.0)tCD/=lCD;vec2 nCD=vec2(-tCD.y,tCD.x);float cosB=clamp(dot(tAB,tBC),-1.0,1.0);const float tol=1e-4;float dirB=-dot(tBC,nAB);float dirC=dot(tBC,nCD);bool bCollinear=abs(dirB)1.0 && iSeg<=3.0){iSeg-=2.0;if(dirB*dirC>=0.0)iSeg+=iSeg==0.0?1.0:-1.0;}vec2 xBasis=tBC;vec2 yBasis=nBC*dirB;vec2 xy=vec2(0,1);lineCoord.y=dirB*mirrorSign;if(iSeg<0.0){float m2=dot(miter,miter);float lm=length(miter);float tBCm=dot(tBC,miter);yBasis=miter/lm;bool isBevel=1.0>miterLimit*m2;if(mod(i,2.0)==0.0){if(isRound||isCap){xBasis=dirB*vec2(yBasis.y,-yBasis.x);float divisor=${t?"res.x":"min(res.x,isCap?joinRes.z:res.x)"}*2.0;float theta=-0.5*(acos(cosB)*(min(i,divisor)/divisor)-pi)*(isCap?2.0:1.0);xy=vec2(cos(theta),sin(theta));if(isCap){if(xy.y>0.5)xy*=capScale;lineCoord.xy=xy.yx*lineCoord.y;}} else {yBasis=bIsHairpin?vec2(0):miter;if(!isBevel)xy.y/=m2;}} else {lineCoord.y=0.0;xy=vec2(0);if(!isRound && isBevel && !isCap){xy.y=-1.0+sqrt((1.0+cosB)*0.5);}}} else if(iSeg>0.0){lineCoord.y=-lineCoord.y;float miterExt=0.0;if(cosB>-0.9999){float sinB=tAB.x*tBC.y-tAB.y*tBC.x;miterExt=sinB/(1.0+cosB);}float m=abs(miterExt);m=min(m,min(lBC,lAB)/width);xy=vec2(m,-1);}${t?`float orientation=${r.orientation?r.orientation.generate(""):"mod(orientation,2.0)"};`:""};${t?"if(orientation==CAP_END)lineCoord.xy=-lineCoord.xy;":""}vec2 dP=mat2(xBasis,yBasis)*xy;float dC=dot(dP,tBC)*mirrorSign;float useC=(isMirrored?1.0:0.0)+dC*(width/lBC);lineCoord.z=useC<0.0||useC>1.0?1.0:0.0;${[...r.varyings.values()].map((e=>e.generate("useC","B","C"))).join("\n")}gl_Position=pB;gl_Position.xy+=width*dP;gl_Position.xy/=resolution;gl_Position*=pw;}`,frag:o,attributes:{...f,...c.attrs},uniforms:{joinRes:(e,n)=>[t?n.capResolution:n.joinResolution,n.joinResolution,"square"===n.capType?n.capResolution:"none"===n.capType?0:n.joinResolution],miterLimit:(e,n)=>n.miterLimit*n.miterLimit,orientation:i.prop("orientation"),capScale:i.prop("capScale")},primitive:"triangle strip",instances:t?(e,t)=>t.splitCaps?t.orientation===n.CAP_START?Math.ceil(t.count/2):Math.floor(t.count/2):t.count:(e,n)=>n.count-3,count:e?(e,n)=>6+2*(n.joinResolution+(t?n.capResolution:n.joinResolution)):(e,n)=>6+2*(1+(t?n.capResolution:1))+10})};var i={NONE:0,REGULAR:1,EXTENDED:2,PER_INSTANCE:4};const r=i;var o=function(e){const n=[],t=e.split("\n");for(let e=0;ee.trim())).filter((e=>!!e)),o=(e,n)=>`${i}(${r.map((t=>(n||"")+t+e)).join(",")})`;return{type:"property",property:e,returnType:t,name:i,inputs:r,generate:o}}if(n=e.match(p)){const e="extrapolate"===n[1],t=n[2],i=n[3],r=n[4],o=n[5].split(",").map((e=>e.trim())).filter((e=>!!e)),s=(n,t,s)=>{const a=e?n:`clamp(${n},0.0,1.0)`;return`${i}=${r}(${o.map((e=>`mix(${e+t},${e+s},${a})`)).join(",")});`};return{type:"varying",returnType:t,name:i,getter:r,inputs:o,generate:s}}throw new Error(`Unrecognized lines pragma:"${e}"`)}function d(e){const n=new Map,t=new Map;for(const i of e)"attribute"===i.type?(n.set(i.name,i),i.vertexUsage=r.NONE,i.endpointUsage=r.NONE):"varying"===i.type&&t.set(i.name,i);let i,o,s;for(const t of e)if("property"===t.type){switch(t.property){case"width":if(i)throw new Error(`Unexpected duplicate pragma for property "${t.property}"`);i=t;break;case"position":if(o)throw new Error(`Unexpected duplicate pragma for property "${t.property}"`);o=t;break;case"orientation":if(s)throw new Error(`Unexpected duplicate pragma for property "${t.property}"`);s=t;break;default:throw new Error(`Invalid pragma property "${t.property}"`)}for(const e of t.inputs)if(!n.has(e))throw new Error(`Missing attribute ${e} of property ${t.property}`)}for(const t of e)if(t.inputs)for(const e of t.inputs){const i=n.get(e);"property"!==t.type&&"varying"!==t.type||("position"===t.property?(i.vertexUsage|=r.EXTENDED,i.endpointUsage|=r.EXTENDED):"orientation"===t.property?i.endpointUsage|=r.PER_INSTANCE:(i.endpointUsage|=r.REGULAR,i.vertexUsage|=r.REGULAR))}return{varyings:t,attrs:n,width:i,position:o,orientation:s}}const u=[];u[5120]=1,u[5122]=2,u[5124]=4,u[5121]=1,u[5123]=2,u[5125]=4,u[5126]=4;var v=function(e,n,t){const i={};if(!n)return i;for(let[r,o]of e.attrs){const e=n[r];if(!(t?o.endpointUsage:o.vertexUsage))continue;const s={buffer:null,dimension:o.dimension,offset:0,type:NaN,stride:NaN,divisor:1,bytesPerElement:NaN};if(!e)throw new Error(`Missing buffer for ${t?"endpoint":"vertex"} attribute '${r}'`);if("buffer"===e._reglType)s.buffer=e,s.type=s.buffer._buffer.dtype;else{if("buffer"!==e.buffer._reglType)throw new Error(`Invalid buffer for attribute '${r}'`);if(s.buffer=e.buffer,y(e,"dimension")&&e.dimension!==s.dimension)throw new Error(`Size of attribute(${e.dimension})does not match dimension specified in shader pragma(${o.dimension})`);y(e,"offset")&&(s.offset=e.offset),y(e,"type")?s.type=g[e.type]:s.type=s.buffer._buffer.dtype,y(e,"divisor")&&(s.divisor=e.divisor),y(e,"stride")&&(s.stride=e.stride)}s.bytesPerElement=m[s.type],Number.isNaN(s.stride)&&(s.stride=s.bytesPerElement*o.dimension),i[r]=s}return i};const m=u,g={int8:5120,int16:5122,int32:5124,uint8:5121,uint16:5123,uint32:5125,float:5126,float32:5126};function y(e,n){return Object.prototype.hasOwnProperty.call(e,n)}const C=[];C[1]="float",C[2]="vec2",C[3]="vec3",C[4]="vec4";var h=function(e,n,t){const i=t?["B","C","D"]:["A","B","C","D"],r=[],o={};return e.attrs.forEach(((e,s)=>{const a=t?e.endpointUsage:e.vertexUsage;if(!a)return;const f=[];function p(e,i){const r=s+i;if(f.push(r),t){const t=a&b.PER_INSTANCE?1:3;o[r]={buffer:n.prop(`buffers.${s}.buffer`),offset:(n,t)=>t.buffers[s].offset+t.buffers[s].stride*((t.orientation!==x.CAP_START&&t.splitCaps?3:0)+e),stride:(e,n)=>n.buffers[s].stride*t*(n.splitCaps?2:1),divisor:(e,n)=>n.buffers[s].divisor}}else o[r]={buffer:n.prop(`buffers.${s}.buffer`),offset:(n,t)=>t.buffers[s].offset+t.buffers[s].stride*e,stride:(e,n)=>n.buffers[s].stride,divisor:(e,n)=>n.buffers[s].divisor}}if(a&b.PER_INSTANCE&&p(0,""),a&b.REGULAR||a&b.EXTENDED)for(let e=0;e{r.push(`varying ${e.returnType} ${n};`)})),{glsl:r.join("\n"),attrs:o}};const b=i,B=C,x=e;const A=t,w=o,$=v,E=h,R=function(e,n,t){return function(i){if(!i)return t;if(-1===n.indexOf(i))throw new Error(`Invalid ${e} type. Valid options are:${n.join(",")}.`);return i}},D=e;var S=I;I.CAP_START=D.CAP_START,I.CAP_END=D.CAP_END;const T=new Set(["count","instances","attributes","elements"]),j=["round","bevel","miter"],N=["round","square","none"],P=[1,1],_=[2,2/Math.sqrt(3)];function I(e,n={}){const{vert:t=null,frag:i=null,debug:r=!1,insertCaps:o=!1}=n,s={...n};for(const e of["vert","frag","debug","insertCaps"])delete s[e];const a=Object.keys(s),f=0===a.length;if(a.forEach((e=>{if(T.has(e))throw new Error(`Invalid parameter '${e}'. Parameters ${[...T].map((e=>`'${e}'`)).join(",")} may not be forwarded to regl.`)})),!t)throw new Error("Missing vertex shader,`vert`");if(!i)throw new Error("Missing fragment shader,`frag`");const p=w(t),l=E(p,e,!1),c=E(p,e,!0),d=e({uniforms:{resolution:e=>[e.viewportWidth,e.viewportHeight]}}),u=f?(e,n)=>n():e(s),v={};r&&(v.debugInstanceID={buffer:e.buffer(new Uint16Array([...Array(16384).keys()])),divisor:1}),v.index={buffer:e.buffer(new Int8Array([...Array(184).keys()])),divisor:0};const m={regl:e,meta:p,segmentSpec:l,endpointSpec:c,frag:i,indexAttributes:v,debug:r,insertCaps:o},g=A(!1,!1,m),y=A(!0,!1,m),C=A(!1,!0,m),h=A(!0,!0,m),b=R("join",j,"miter"),B=R("cap",N,"square"),x=[],S=[],I=[],U=[];function M(e){u(e,(()=>{x.length&&y(x),I.length&&g(I),S.length&&h(S),U.length&&C(U),x.length=0,I.length=0,S.length=0,U.length=0}))}return function(e){if(!e)return;const n=Array.isArray(e);n||(e=[e]);const t=f&&!n;d((()=>{for(const n of e){const e=b(n.join),i=B(n.cap);let r=void 0===n.capResolution?12:n.capResolution;"square"===i?r=3:"none"===i&&(r=1);let o=1;"round"===e&&(o=void 0===n.joinResolution?8:n.joinResolution);const s="bevel"===e?1:void 0===n.miterLimit?4:n.miterLimit,a={joinResolution:o,capResolution:r,capScale:"square"===i?_:P,capType:i,miterLimit:s};if(n.endpointAttributes&&n.endpointCount){const t={buffers:$(p,n.endpointAttributes,!0),count:n.endpointCount,...a},i="round"===e?S:U;p.orientation?i.push({...t,splitCaps:!1}):i.push({...t,orientation:D.CAP_START,splitCaps:!0},{...t,orientation:D.CAP_END,splitCaps:!0})}if(n.vertexAttributes&&n.vertexCount){("round"===e?x:I).push({buffers:$(p,n.vertexAttributes,!1),count:n.vertexCount,...a})}t||M(n)}t&&M(e)}))}}return S})); \ No newline at end of file +!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(n="undefined"!=typeof globalThis?globalThis:n||self).reglLines=e()}(this,(function(){"use strict";var n={CAP_START:0,CAP_END:1,CAP_SHORT:2};const e=n;var t=function(n,t,{regl:i,meta:r,frag:o,segmentSpec:s,endpointSpec:a,indexAttributes:f,insertCaps:p,debug:l}){const c=t?a:s,d=["B","C","D"];t||d.unshift("A");function u(n){return p?t?[n.capResolution,Math.max(n.capResolution,n.joinResolution)]:[Math.max(n.capResolution,n.joinResolution),Math.max(n.capResolution,n.joinResolution)]:t?[n.capResolution,n.joinResolution]:[n.joinResolution,n.joinResolution]}return i({vert:`${r.glsl}const float CAP_START=${e.CAP_START}.0;const float CAP_END=${e.CAP_END}.0;${c.glsl}attribute float index;${l?"attribute float debugInstanceID;":""}uniform vec2 vertexCount,capJoinRes;uniform vec2 resolution,capScale;uniform float miterLimit;${r.orientation||!t?"":"uniform float orientation;"}varying vec3 lineCoord;varying float dir;${l?"varying vec2 triStripCoord;":""}${l?"varying float instanceID;":""}bool isnan(float val){return(val<0.0||0.0`vec4 p${n}=${r.position.generate(n)};`)).join("\n")}if(invalid(pB)||invalid(pC)){gl_Position=vec4(1,1,1,0);return;}float mirrorIndex=2.0*vertexCount.x+3.0;float totalVertexCount=mirrorIndex+2.0+2.0*vertexCount.y;bool isMirrored=index>mirrorIndex;${t?"if(invalid(pD)&& isMirrored){ gl_Position=vec4(0); return; }":""}float pw=isMirrored?pC.w:pB.w;${d.map((n=>`p${n}=vec4(vec3(p${n}.xy*resolution,p${n}.z)/p${n}.w,1);`)).join("\n")}${t?"vec4 pA=pC;":""}float mirrorSign=isMirrored?-1.0:1.0;if(isMirrored){vec4 tmp;tmp=pC; pC=pB; pB=tmp;tmp=pD; pD=pA; pA=tmp;}${t?"bool isCap=!isMirrored;":"bool isCap=false;"};if(invalid(pA)){ ${p?"pA=pC; isCap=true;":"pA=2.0*pB-pC;"} }if(invalid(pD)){ ${p?"pD=pB;":"pD=2.0*pC-pB;"} }float width=isMirrored?${r.width.generate("C")}:${r.width.generate("B")};if(max(abs(pB.z),abs(pC.z))>1.0){gl_Position=vec4(0);return;}vec2 tBC=pC.xy-pB.xy;float lBC=length(tBC);tBC/=lBC;vec2 nBC=vec2(-tBC.y,tBC.x);vec2 tAB=pB.xy-pA.xy;float lAB=length(tAB);if(lAB>0.0)tAB/=lAB;vec2 nAB=vec2(-tAB.y,tAB.x);vec2 tCD=pD.xy-pC.xy;float lCD=length(tCD);if(lCD>0.0)tCD/=lCD;vec2 nCD=vec2(-tCD.y,tCD.x);float cosB=clamp(dot(tAB,tBC),-1.0,1.0);const float tol=1e-4;float dirB=-dot(tBC,nAB);float dirC=dot(tBC,nCD);bool bCollinear=abs(dirB)1.0 && iSeg<=3.0){iSeg-=2.0;if(dirB*dirC>=0.0)iSeg+=iSeg==0.0?1.0:-1.0;}vec2 xBasis=tBC;vec2 yBasis=nBC*dirB;vec2 xy=vec2(0,1);lineCoord.y=dirB*mirrorSign;if(iSeg<0.0){float m2=dot(miter,miter);float lm=length(miter);float tBCm=dot(tBC,miter);yBasis=miter/lm;bool isBevel=1.0>miterLimit*m2;if(mod(i,2.0)==0.0){if(isRound||isCap){xBasis=dirB*vec2(yBasis.y,-yBasis.x);float cnt=(isCap?capJoinRes.x:capJoinRes.y)*2.0;float theta=-0.5*(acos(cosB)*(min(i,cnt)/cnt)-pi)*(isCap?2.0:1.0);xy=vec2(cos(theta),sin(theta));if(isCap){if(xy.y>0.001)xy*=capScale;lineCoord.xy=xy.yx*lineCoord.y;}} else {yBasis=bIsHairpin?vec2(0):miter;if(!isBevel)xy.y/=m2;}} else {lineCoord.y=0.0;xy=vec2(0);if(!isRound && isBevel && !isCap){xy.y=-1.0+sqrt((1.0+cosB)*0.5);}}} else if(iSeg>0.0){lineCoord.y=-lineCoord.y;float miterExt=0.0;if(cosB>-0.9999){float sinB=tAB.x*tBC.y-tAB.y*tBC.x;miterExt=sinB/(1.0+cosB);}float m=abs(miterExt);m=min(m,min(lBC,lAB)/width);xy=vec2(m,-1);}${t?`float orientation=${r.orientation?r.orientation.generate(""):"mod(orientation,2.0)"};`:""};${t?"if(orientation==CAP_END)lineCoord.xy=-lineCoord.xy;":""}vec2 dP=mat2(xBasis,yBasis)*xy;float dC=dot(dP,tBC)*mirrorSign;float useC=(isMirrored?1.0:0.0)+dC*(width/lBC);lineCoord.z=useC<0.0||useC>1.0?1.0:0.0;${[...r.varyings.values()].map((n=>n.generate("useC","B","C"))).join("\n")}gl_Position=pB;gl_Position.xy+=width*dP;gl_Position.xy/=resolution;gl_Position*=pw;}`,frag:o,attributes:{...f,...c.attrs},uniforms:{vertexCount:(n,e)=>u(e),capJoinRes:(n,e)=>[e.capResolution,e.joinResolution],miterLimit:(n,e)=>e.miterLimit*e.miterLimit,orientation:i.prop("orientation"),capScale:i.prop("capScale")},primitive:"triangle strip",instances:t?(n,t)=>t.splitCaps?t.orientation===e.CAP_START?Math.ceil(t.count/2):Math.floor(t.count/2):t.count:(n,e)=>e.count-3,count:(n,e)=>{const t=u(e);return 6+2*(t[0]+t[1])}})};var i={NONE:0,REGULAR:1,EXTENDED:2,PER_INSTANCE:4};const r=i;var o=function(n){const e=[],t=n.split("\n");for(let n=0;nn.trim())).filter((n=>!!n)),o=(n,e)=>`${i}(${r.map((t=>(e||"")+t+n)).join(",")})`;return{type:"property",property:n,returnType:t,name:i,inputs:r,generate:o}}if(e=n.match(p)){const n="extrapolate"===e[1],t=e[2],i=e[3],r=e[4],o=e[5].split(",").map((n=>n.trim())).filter((n=>!!n)),s=(e,t,s)=>{const a=n?e:`clamp(${e},0.0,1.0)`;return`${i}=${r}(${o.map((n=>`mix(${n+t},${n+s},${a})`)).join(",")});`};return{type:"varying",returnType:t,name:i,getter:r,inputs:o,generate:s}}throw new Error(`Unrecognized lines pragma:"${n}"`)}function d(n){const e=new Map,t=new Map;for(const i of n)"attribute"===i.type?(e.set(i.name,i),i.vertexUsage=r.NONE,i.endpointUsage=r.NONE):"varying"===i.type&&t.set(i.name,i);let i,o,s;for(const t of n)if("property"===t.type){switch(t.property){case"width":if(i)throw new Error(`Unexpected duplicate pragma for property "${t.property}"`);i=t;break;case"position":if(o)throw new Error(`Unexpected duplicate pragma for property "${t.property}"`);o=t;break;case"orientation":if(s)throw new Error(`Unexpected duplicate pragma for property "${t.property}"`);s=t;break;default:throw new Error(`Invalid pragma property "${t.property}"`)}for(const n of t.inputs)if(!e.has(n))throw new Error(`Missing attribute ${n} of property ${t.property}`)}for(const t of n)if(t.inputs)for(const n of t.inputs){const i=e.get(n);"property"!==t.type&&"varying"!==t.type||("position"===t.property?(i.vertexUsage|=r.EXTENDED,i.endpointUsage|=r.EXTENDED):"orientation"===t.property?i.endpointUsage|=r.PER_INSTANCE:(i.endpointUsage|=r.REGULAR,i.vertexUsage|=r.REGULAR))}return{varyings:t,attrs:e,width:i,position:o,orientation:s}}const u=[];u[5120]=1,u[5122]=2,u[5124]=4,u[5121]=1,u[5123]=2,u[5125]=4,u[5126]=4;var m=function(n,e,t){const i={};if(!e)return i;for(let[r,o]of n.attrs){const n=e[r];if(!(t?o.endpointUsage:o.vertexUsage))continue;const s={buffer:null,dimension:o.dimension,offset:0,type:NaN,stride:NaN,divisor:1,bytesPerElement:NaN};if(!n)throw new Error(`Missing buffer for ${t?"endpoint":"vertex"} attribute '${r}'`);if("buffer"===n._reglType)s.buffer=n,s.type=s.buffer._buffer.dtype;else{if("buffer"!==n.buffer._reglType)throw new Error(`Invalid buffer for attribute '${r}'`);if(s.buffer=n.buffer,C(n,"dimension")&&n.dimension!==s.dimension)throw new Error(`Size of attribute(${n.dimension})does not match dimension specified in shader pragma(${o.dimension})`);C(n,"offset")&&(s.offset=n.offset),C(n,"type")?s.type=g[n.type]:s.type=s.buffer._buffer.dtype,C(n,"divisor")&&(s.divisor=n.divisor),C(n,"stride")&&(s.stride=n.stride)}s.bytesPerElement=v[s.type],Number.isNaN(s.stride)&&(s.stride=s.bytesPerElement*o.dimension),i[r]=s}return i};const v=u,g={int8:5120,int16:5122,int32:5124,uint8:5121,uint16:5123,uint32:5125,float:5126,float32:5126};function C(n,e){return Object.prototype.hasOwnProperty.call(n,e)}const y=[];y[1]="float",y[2]="vec2",y[3]="vec3",y[4]="vec4";var h=function(n,e,t){const i=t?["B","C","D"]:["A","B","C","D"],r=[],o={};return n.attrs.forEach(((n,s)=>{const a=t?n.endpointUsage:n.vertexUsage;if(!a)return;const f=[];function p(n,i){const r=s+i;if(f.push(r),t){const t=a&b.PER_INSTANCE?1:3;o[r]={buffer:e.prop(`buffers.${s}.buffer`),offset:(e,t)=>t.buffers[s].offset+t.buffers[s].stride*((t.orientation!==B.CAP_START&&t.splitCaps?3:0)+n),stride:(n,e)=>e.buffers[s].stride*t*(e.splitCaps?2:1),divisor:(n,e)=>e.buffers[s].divisor}}else o[r]={buffer:e.prop(`buffers.${s}.buffer`),offset:(e,t)=>t.buffers[s].offset+t.buffers[s].stride*n,stride:(n,e)=>e.buffers[s].stride,divisor:(n,e)=>e.buffers[s].divisor}}if(a&b.PER_INSTANCE&&p(0,""),a&b.REGULAR||a&b.EXTENDED)for(let n=0;n{r.push(`varying ${n.returnType} ${e};`)})),{glsl:r.join("\n"),attrs:o}};const b=i,x=y,B=n;const w=t,A=o,$=m,E=h,R=function(n,e,t){return function(i){if(!i)return t;if(-1===e.indexOf(i))throw new Error(`Invalid ${n} type. Valid options are:${e.join(",")}.`);return i}},D=n;var S=I;I.CAP_START=D.CAP_START,I.CAP_END=D.CAP_END;const T=new Set(["count","instances","attributes","elements"]),j=["round","bevel","miter"],N=["round","square","none"],P=[1,1],_=[2,2/Math.sqrt(3)];function I(n,e={}){const{vert:t=null,frag:i=null,debug:r=!1,insertCaps:o=!1}=e,s={...e};for(const n of["vert","frag","debug","insertCaps"])delete s[n];const a=Object.keys(s),f=0===a.length;if(a.forEach((n=>{if(T.has(n))throw new Error(`Invalid parameter '${n}'. Parameters ${[...T].map((n=>`'${n}'`)).join(",")} may not be forwarded to regl.`)})),!t)throw new Error("Missing vertex shader,`vert`");if(!i)throw new Error("Missing fragment shader,`frag`");const p=A(t),l=E(p,n,!1),c=E(p,n,!0),d=n({uniforms:{resolution:n=>[n.viewportWidth,n.viewportHeight]}}),u=f?(n,e)=>e():n(s),m={};r&&(m.debugInstanceID={buffer:n.buffer(new Uint16Array([...Array(16384).keys()])),divisor:1}),m.index={buffer:n.buffer(new Int8Array([...Array(184).keys()])),divisor:0};const v={regl:n,meta:p,segmentSpec:l,endpointSpec:c,frag:i,indexAttributes:m,debug:r,insertCaps:o},g=w(!1,!1,v),C=w(!0,!1,v),y=w(!1,!0,v),h=w(!0,!0,v),b=R("join",j,"miter"),x=R("cap",N,"square"),B=[],S=[],I=[],M=[];function U(n){u(n,(()=>{B.length&&C(B),I.length&&g(I),S.length&&h(S),M.length&&y(M),B.length=0,I.length=0,S.length=0,M.length=0}))}return function(n){if(!n)return;const e=Array.isArray(n);e||(n=[n]);const t=f&&!e;d((()=>{for(const e of n){const n=b(e.join),i=x(e.cap);let r=void 0===e.capResolution?12:e.capResolution;"square"===i?r=3:"none"===i&&(r=1);let o=1;"round"===n&&(o=void 0===e.joinResolution?8:e.joinResolution);const s="bevel"===n?1:void 0===e.miterLimit?4:e.miterLimit,a={joinResolution:o,capResolution:r,capScale:"square"===i?_:P,capType:i,miterLimit:s};if(e.endpointAttributes&&e.endpointCount){const t={buffers:$(p,e.endpointAttributes,!0),count:e.endpointCount,...a},i="round"===n?S:M;p.orientation?i.push({...t,splitCaps:!1}):i.push({...t,orientation:D.CAP_START,splitCaps:!0},{...t,orientation:D.CAP_END,splitCaps:!0})}if(e.vertexAttributes&&e.vertexCount){("round"===n?B:I).push({buffers:$(p,e.vertexAttributes,!1),count:e.vertexCount,...a})}t||U(e)}t&&U(n)}))}}return S})); \ No newline at end of file diff --git a/docs/border.html b/docs/border.html index b7dc329..1623d63 100644 --- a/docs/border.html +++ b/docs/border.html @@ -33,9 +33,10 @@ #pragma lines: width = getWidth(xy); uniform float width; + uniform vec2 translate, scale; // Implement the functions above - vec4 getPosition(vec2 xy) { return vec4(xy, 0, 1); } + vec4 getPosition(vec2 xy) { return vec4(xy * scale + translate, 0, 1); } float getWidth(vec2 xy) { return width; } float getX(vec2 xy) { return xy.x; }`, frag: ` @@ -46,12 +47,11 @@ // Convert the line coord into an SDF float sdf = length(lineCoord.xy) * width; - // Use the y-value of the line coord to distinguish between sides of the line - vec3 borderColor = lineCoord.y < 0.0 ? vec3(1, 0.5, 0.5) : vec3(0.5, 0.5, 1); + vec3 borderColor = 0.5 + 0.5 * vec3(lineCoord.xy, 0); // Apply a border with 1px transition gl_FragColor = vec4( - mix(vec3(0), borderColor, smoothstep(width - borderWidth - 0.5, width - borderWidth + 0.5, sdf)), + mix(vec3(0), borderColor, smoothstep(width - borderWidth - 1.0, width - borderWidth + 1.0, sdf)), 1); }`, @@ -59,20 +59,21 @@ uniforms: { width: (ctx, props) => ctx.pixelRatio * props.width, borderWidth: (ctx, props) => ctx.pixelRatio * props.borderWidth, + translate: regl.prop('translate'), + scale: regl.prop('scale') }, + depth: {enable: false} }); // Construct an array of xy pairs -const n = 21; +const n = 11; const xy = [...Array(n).keys()] .map(i => (i / (n - 1) * 2.0 - 1.0) * 0.8) - .map(t => [t, 0.5 * Math.sin(8.0 * t)]); + .map(t => [t, 0.5 * Math.sin(54.0 * t)]); // Set up the data to be drawn. Note that we preallocate buffers and don't create // them on every draw call. const lineData = { - join: 'round', - cap: 'round', vertexCount: xy.length, vertexAttributes: { xy: regl.buffer(xy) @@ -82,14 +83,35 @@ xy: regl.buffer([xy.slice(0, 3), xy.slice(-3).reverse()]) }, - width: 25, - borderWidth: 10 + width: 35, + borderWidth: 10, + miterLimit: 3.0, + scale: [1, 1] }; +window.addEventListener('mousemove', e => { + lineData.scale = [ + e.offsetX / window.innerWidth * 2 - 1, + -e.offsetY / window.innerHeight * 2 + 1 + ]; + draw(); +}); + + function draw () { regl.poll(); regl.clear({color: [0.2, 0.2, 0.2, 1]}); - drawLines(lineData); + drawLines([{ + ...lineData, + translate: [0, -0.4], + cap: 'round', + join: 'round' + }, { + ...lineData, + translate: [0, 0.4], + cap: 'round', + join: 'miter' + }]); } draw(); @@ -133,9 +155,10 @@ #pragma lines: width = getWidth(xy); uniform float width; + uniform vec2 translate, scale; // Implement the functions above - vec4 getPosition(vec2 xy) { return vec4(xy, 0, 1); } + vec4 getPosition(vec2 xy) { return vec4(xy * scale + translate, 0, 1); } float getWidth(vec2 xy) { return width; } float getX(vec2 xy) { return xy.x; }`, frag: ` @@ -146,12 +169,11 @@ // Convert the line coord into an SDF float sdf = length(lineCoord.xy) * width; - // Use the y-value of the line coord to distinguish between sides of the line - vec3 borderColor = lineCoord.y < 0.0 ? vec3(1, 0.5, 0.5) : vec3(0.5, 0.5, 1); + vec3 borderColor = 0.5 + 0.5 * vec3(lineCoord.xy, 0); // Apply a border with 1px transition gl_FragColor = vec4( - mix(vec3(0), borderColor, smoothstep(width - borderWidth - 0.5, width - borderWidth + 0.5, sdf)), + mix(vec3(0), borderColor, smoothstep(width - borderWidth - 1.0, width - borderWidth + 1.0, sdf)), 1); }`, @@ -159,20 +181,21 @@ uniforms: { width: (ctx, props) => ctx.pixelRatio * props.width, borderWidth: (ctx, props) => ctx.pixelRatio * props.borderWidth, + translate: regl.prop('translate'), + scale: regl.prop('scale') }, + depth: {enable: false} }); // Construct an array of xy pairs -const n = 21; +const n = 11; const xy = [...Array(n).keys()] .map(i => (i / (n - 1) * 2.0 - 1.0) * 0.8) - .map(t => [t, 0.5 * Math.sin(8.0 * t)]); + .map(t => [t, 0.5 * Math.sin(54.0 * t)]); // Set up the data to be drawn. Note that we preallocate buffers and don't create // them on every draw call. const lineData = { - join: 'round', - cap: 'round', vertexCount: xy.length, vertexAttributes: { xy: regl.buffer(xy) @@ -182,14 +205,35 @@ xy: regl.buffer([xy.slice(0, 3), xy.slice(-3).reverse()]) }, - width: 25, - borderWidth: 10 + width: 35, + borderWidth: 10, + miterLimit: 3.0, + scale: [1, 1] }; +window.addEventListener('mousemove', e => { + lineData.scale = [ + e.offsetX / window.innerWidth * 2 - 1, + -e.offsetY / window.innerHeight * 2 + 1 + ]; + draw(); +}); + + function draw () { regl.poll(); regl.clear({color: [0.2, 0.2, 0.2, 1]}); - drawLines(lineData); + drawLines([{ + ...lineData, + translate: [0, -0.4], + cap: 'round', + join: 'round' + }, { + ...lineData, + translate: [0, 0.4], + cap: 'round', + join: 'miter' + }]); } draw(); diff --git a/docs/debug.html b/docs/debug.html index e3e5c5a..131ee22 100644 --- a/docs/debug.html +++ b/docs/debug.html @@ -31,7 +31,7 @@ const state = wrapGUI(State({ lineConfig: State.Section({ capResolution: State.Slider(8, {min: 1, max: 30, step: 1}), - joinResolution: State.Slider(8, {min: 1, max: 30, step: 1}), + joinResolution: State.Slider(6, {min: 1, max: 30, step: 1}), cap: State.Select('round', {options: ['round', 'square', 'none']}), join: State.Select('round', {options: ['round', 'miter', 'bevel']}), miterLimit: State.Slider(4, {min: 1, max: 8, step: 0.01}), @@ -41,7 +41,7 @@ flip: State.Slider(1, {min: -1, max: 1, step: 0.001}), }, {expanded: true}), line: State.Section({ - width: State.Slider(40, {min: 1, max: 100, step: 0.1}), + width: State.Slider(70, {min: 1, max: 100, step: 0.1}), opacity: State.Slider(0.5, {min: 0, max: 1, step: 0.01}), }, {label: 'line', expanded: false}), border: State.Section({ @@ -361,6 +361,7 @@ lineColor: [0, 0, 0, state.line.opacity], borderColor: [0, 0, 0, state.border.opacity], dashColor: [0, 0, 0, state.dash.opacity], + //vertexCount: 0 }); } @@ -405,7 +406,7 @@ const state = wrapGUI(State({ lineConfig: State.Section({ capResolution: State.Slider(8, {min: 1, max: 30, step: 1}), - joinResolution: State.Slider(8, {min: 1, max: 30, step: 1}), + joinResolution: State.Slider(6, {min: 1, max: 30, step: 1}), cap: State.Select('round', {options: ['round', 'square', 'none']}), join: State.Select('round', {options: ['round', 'miter', 'bevel']}), miterLimit: State.Slider(4, {min: 1, max: 8, step: 0.01}), @@ -415,7 +416,7 @@ flip: State.Slider(1, {min: -1, max: 1, step: 0.001}), }, {expanded: true}), line: State.Section({ - width: State.Slider(40, {min: 1, max: 100, step: 0.1}), + width: State.Slider(70, {min: 1, max: 100, step: 0.1}), opacity: State.Slider(0.5, {min: 0, max: 1, step: 0.01}), }, {label: 'line', expanded: false}), border: State.Section({ @@ -735,6 +736,7 @@ lineColor: [0, 0, 0, state.line.opacity], borderColor: [0, 0, 0, state.border.opacity], dashColor: [0, 0, 0, state.dash.opacity], + //vertexCount: 0 }); } diff --git a/docs/multiple.html b/docs/multiple.html index c6544f3..057e90e 100644 --- a/docs/multiple.html +++ b/docs/multiple.html @@ -43,11 +43,18 @@ varying vec2 pos; void main () { // Convert the x-coordinate into a color - gl_FragColor = vec4(0.6 + 0.4 * cos(8.0 * (pos.x - vec3(0, 1, 2) * 3.141 / 3.0)), 1.0); + gl_FragColor = vec4(0.6 + 0.4 * cos(8.0 * (pos.x - vec3(0, 1, 2) * 3.141 / 3.0)), 0.7); }`, + // Turn off depth and turn on blending to make it very clear if we accidentally + // draw end caps twice + depth: { enable: false }, + blend: { + enable: true, + func: { srcRGB: "src alpha", srcAlpha: 1, dstRGB: "one minus src alpha", dstAlpha: 1 } + }, }); -const n = 51; +const n = 31; const lineCount = 10; // Detecting NaN in GLSL can be questionable, so we can just be verbose and use a separate @@ -140,11 +147,18 @@ varying vec2 pos; void main () { // Convert the x-coordinate into a color - gl_FragColor = vec4(0.6 + 0.4 * cos(8.0 * (pos.x - vec3(0, 1, 2) * 3.141 / 3.0)), 1.0); + gl_FragColor = vec4(0.6 + 0.4 * cos(8.0 * (pos.x - vec3(0, 1, 2) * 3.141 / 3.0)), 0.7); }`, + // Turn off depth and turn on blending to make it very clear if we accidentally + // draw end caps twice + depth: { enable: false }, + blend: { + enable: true, + func: { srcRGB: "src alpha", srcAlpha: 1, dstRGB: "one minus src alpha", dstAlpha: 1 } + }, }); -const n = 51; +const n = 31; const lineCount = 10; // Detecting NaN in GLSL can be questionable, so we can just be verbose and use a separate diff --git a/examples/border.js b/examples/border.js index 57e33bc..3339659 100644 --- a/examples/border.js +++ b/examples/border.js @@ -16,9 +16,10 @@ const drawLines = reglLines(regl, { #pragma lines: width = getWidth(xy); uniform float width; + uniform vec2 translate, scale; // Implement the functions above - vec4 getPosition(vec2 xy) { return vec4(xy, 0, 1); } + vec4 getPosition(vec2 xy) { return vec4(xy * scale + translate, 0, 1); } float getWidth(vec2 xy) { return width; } float getX(vec2 xy) { return xy.x; }`, frag: ` @@ -29,12 +30,11 @@ const drawLines = reglLines(regl, { // Convert the line coord into an SDF float sdf = length(lineCoord.xy) * width; - // Use the y-value of the line coord to distinguish between sides of the line - vec3 borderColor = lineCoord.y < 0.0 ? vec3(1, 0.5, 0.5) : vec3(0.5, 0.5, 1); + vec3 borderColor = 0.5 + 0.5 * vec3(lineCoord.xy, 0); // Apply a border with 1px transition gl_FragColor = vec4( - mix(vec3(0), borderColor, smoothstep(width - borderWidth - 0.5, width - borderWidth + 0.5, sdf)), + mix(vec3(0), borderColor, smoothstep(width - borderWidth - 1.0, width - borderWidth + 1.0, sdf)), 1); }`, @@ -42,20 +42,21 @@ const drawLines = reglLines(regl, { uniforms: { width: (ctx, props) => ctx.pixelRatio * props.width, borderWidth: (ctx, props) => ctx.pixelRatio * props.borderWidth, + translate: regl.prop('translate'), + scale: regl.prop('scale') }, + depth: {enable: false} }); // Construct an array of xy pairs -const n = 21; +const n = 11; const xy = [...Array(n).keys()] .map(i => (i / (n - 1) * 2.0 - 1.0) * 0.8) - .map(t => [t, 0.5 * Math.sin(8.0 * t)]); + .map(t => [t, 0.5 * Math.sin(54.0 * t)]); // Set up the data to be drawn. Note that we preallocate buffers and don't create // them on every draw call. const lineData = { - join: 'round', - cap: 'round', vertexCount: xy.length, vertexAttributes: { xy: regl.buffer(xy) @@ -65,14 +66,35 @@ const lineData = { xy: regl.buffer([xy.slice(0, 3), xy.slice(-3).reverse()]) }, - width: 25, - borderWidth: 10 + width: 35, + borderWidth: 10, + miterLimit: 3.0, + scale: [1, 1] }; +window.addEventListener('mousemove', e => { + lineData.scale = [ + e.offsetX / window.innerWidth * 2 - 1, + -e.offsetY / window.innerHeight * 2 + 1 + ]; + draw(); +}); + + function draw () { regl.poll(); regl.clear({color: [0.2, 0.2, 0.2, 1]}); - drawLines(lineData); + drawLines([{ + ...lineData, + translate: [0, -0.4], + cap: 'round', + join: 'round' + }, { + ...lineData, + translate: [0, 0.4], + cap: 'round', + join: 'miter' + }]); } draw(); diff --git a/examples/debug.js b/examples/debug.js index 44451d8..67b7dfc 100644 --- a/examples/debug.js +++ b/examples/debug.js @@ -14,7 +14,7 @@ regl._gl.canvas.style.position = 'fixed'; const state = wrapGUI(State({ lineConfig: State.Section({ capResolution: State.Slider(8, {min: 1, max: 30, step: 1}), - joinResolution: State.Slider(8, {min: 1, max: 30, step: 1}), + joinResolution: State.Slider(6, {min: 1, max: 30, step: 1}), cap: State.Select('round', {options: ['round', 'square', 'none']}), join: State.Select('round', {options: ['round', 'miter', 'bevel']}), miterLimit: State.Slider(4, {min: 1, max: 8, step: 0.01}), @@ -24,7 +24,7 @@ const state = wrapGUI(State({ flip: State.Slider(1, {min: -1, max: 1, step: 0.001}), }, {expanded: true}), line: State.Section({ - width: State.Slider(40, {min: 1, max: 100, step: 0.1}), + width: State.Slider(70, {min: 1, max: 100, step: 0.1}), opacity: State.Slider(0.5, {min: 0, max: 1, step: 0.01}), }, {label: 'line', expanded: false}), border: State.Section({ @@ -344,6 +344,7 @@ function draw () { lineColor: [0, 0, 0, state.line.opacity], borderColor: [0, 0, 0, state.border.opacity], dashColor: [0, 0, 0, state.dash.opacity], + //vertexCount: 0 }); } diff --git a/examples/multiple.js b/examples/multiple.js index 59f3c8b..1c37bec 100644 --- a/examples/multiple.js +++ b/examples/multiple.js @@ -26,11 +26,18 @@ const drawLines = reglLines(regl, { varying vec2 pos; void main () { // Convert the x-coordinate into a color - gl_FragColor = vec4(0.6 + 0.4 * cos(8.0 * (pos.x - vec3(0, 1, 2) * 3.141 / 3.0)), 1.0); + gl_FragColor = vec4(0.6 + 0.4 * cos(8.0 * (pos.x - vec3(0, 1, 2) * 3.141 / 3.0)), 0.7); }`, + // Turn off depth and turn on blending to make it very clear if we accidentally + // draw end caps twice + depth: { enable: false }, + blend: { + enable: true, + func: { srcRGB: "src alpha", srcAlpha: 1, dstRGB: "one minus src alpha", dstAlpha: 1 } + }, }); -const n = 51; +const n = 31; const lineCount = 10; // Detecting NaN in GLSL can be questionable, so we can just be verbose and use a separate diff --git a/package.json b/package.json index ef0d29e..79645d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "regl-gpu-lines", - "version": "0.0.20", + "version": "0.0.21", "description": "Pure GPU instanced, screen-projected lines for regl", "main": "dist/regl-gpu-lines.min.js", "repository": { diff --git a/src/draw-segment.js b/src/draw-segment.js index aff7ec9..6dc9e26 100644 --- a/src/draw-segment.js +++ b/src/draw-segment.js @@ -18,6 +18,20 @@ function createDrawSegmentCommand(isRound, isEndpoints, { const verts = ['B', 'C', 'D']; if (!isEndpoints) verts.unshift('A'); + function computeCount(props) { + return insertCaps + ? isEndpoints + // Cap has fixed number, join could either be a cap or a join + ? [props.capResolution, Math.max(props.capResolution, props.joinResolution)] + // Both could be either a cap or a join + : [Math.max(props.capResolution, props.joinResolution), Math.max(props.capResolution, props.joinResolution)] + : isEndpoints + // Draw a cap + ? [props.capResolution, props.joinResolution] + // Draw two joins + : [props.joinResolution, props.joinResolution]; + } + return regl({ vert: `${meta.glsl} const float CAP_START = ${ORIENTATION.CAP_START}.0; @@ -28,7 +42,7 @@ ${spec.glsl} attribute float index; ${debug ? 'attribute float debugInstanceID;' : ''} -uniform vec3 joinRes; +uniform vec2 vertexCount, capJoinRes; uniform vec2 resolution, capScale; uniform float miterLimit; ${meta.orientation || !isEndpoints ? '' : 'uniform float orientation;'} @@ -58,14 +72,21 @@ void main() { ${verts.map(vert => `vec4 p${vert} = ${meta.position.generate(vert)};`).join('\n')} // Check for invalid vertices - if (invalid(pB) || invalid(pC)${insertCaps ? '' : `${isEndpoints ? '' : '|| invalid(pA)'} || invalid(pD)`}) { + if (invalid(pB) || invalid(pC)) { gl_Position = vec4(1,1,1,0); return; } + float mirrorIndex = 2.0 * vertexCount.x + 3.0; + float totalVertexCount = mirrorIndex + 2.0 + 2.0 * vertexCount.y; + // If we're past the first half-join and half of the segment, then we swap all vertices and start // over from the opposite end. - bool isMirrored = index > joinRes.x * 2.0 + 3.0; + bool isMirrored = index > mirrorIndex; + + // When rendering dedicated endoints, this allows us to insert an end cap *alone* (without the attached + // segment and join) + ${isEndpoints ? 'if (invalid(pD) && isMirrored) { gl_Position = vec4(0); return; }' : ''} // Convert to screen-pixel coordinates // Save w so we can perspective re-multiply at the end to get varyings depth-correct @@ -75,8 +96,6 @@ void main() { // If it's a cap, mirror A back onto C to accomplish a round ${isEndpoints ? `vec4 pA = pC;` : ''} - vec2 res = isMirrored ? joinRes.yx : joinRes.xy; - float mirrorSign = isMirrored ? -1.0 : 1.0; if (isMirrored) { vec4 tmp; @@ -86,14 +105,14 @@ void main() { ${isEndpoints ? `bool isCap = !isMirrored;` : `bool isCap = false;`}; - if (invalid(pA)) { pA = pC; isCap = true; } - if (invalid(pD)) { pD = pB; } + if (invalid(pA)) { ${insertCaps ? 'pA = pC; isCap = true;' : 'pA = 2.0 * pB - pC;'} } + if (invalid(pD)) { ${insertCaps ? 'pD = pB;' : 'pD = 2.0 * pC - pB;'} } float width = isMirrored ? ${meta.width.generate('C')} : ${meta.width.generate('B')}; // Invalidate triangles too far in front of or behind the camera plane if (max(abs(pB.z), abs(pC.z)) > 1.0) { - gl_Position = vec4(1,1,1,0); + gl_Position = vec4(0); return; } @@ -130,10 +149,10 @@ void main() { vec2 miter = bIsHairpin ? -tBC : 0.5 * (nAB + nBC) * dirB; // The second half of the triangle strip instance is just the first, reversed, and with vertices swapped! - float i = index < 2.0 * joinRes.x + 4.0 ? index : 2.0 * (res.x + res.y) + 5.0 - index; + float i = index <= mirrorIndex ? index : totalVertexCount - index; // Chop off the join to get at the segment part index - float iSeg = i - 2.0 * res.x; + float iSeg = i - 2.0 * (isMirrored ? vertexCount.y : vertexCount.x); // After the first half-join, repeat two vertices of the segment strip in order to get the orientation correct // for the next join. These are wasted vertices, but they enable using a triangle strip. for two joins which @@ -162,12 +181,12 @@ void main() { if (isRound || isCap) { // Round joins xBasis = dirB * vec2(yBasis.y, -yBasis.x); - float divisor = ${isEndpoints ? 'res.x' : 'min(res.x, isCap ? joinRes.z : res.x)'} * 2.0; - float theta = -0.5 * (acos(cosB) * (min(i, divisor) / divisor) - pi) * (isCap ? 2.0 : 1.0); + float cnt = (isCap ? capJoinRes.x : capJoinRes.y) * 2.0; + float theta = -0.5 * (acos(cosB) * (min(i, cnt) / cnt) - pi) * (isCap ? 2.0 : 1.0); xy = vec2(cos(theta), sin(theta)); if (isCap) { - if (xy.y > 0.5) xy *= capScale; + if (xy.y > 0.001) xy *= capScale; lineCoord.xy = xy.yx * lineCoord.y; } } else { @@ -221,16 +240,8 @@ void main() { ...spec.attrs }, uniforms: { - joinRes: (ctx, props) => [ - // First half-join is actually a cap if we're drawing endpoints - isEndpoints ? props.capResolution : props.joinResolution, - - // Second half-join is always a join - props.joinResolution, - - // The resolution of inserted caps - props.capType === 'square' ? props.capResolution : props.capType === 'none' ? 0 : props.joinResolution, - ], + vertexCount: (ctx, props) => computeCount(props), + capJoinRes: (ctx, props) => [props.capResolution, props.joinResolution], miterLimit: (ctx, props) => props.miterLimit * props.miterLimit, orientation: regl.prop('orientation'), capScale: regl.prop('capScale'), @@ -239,8 +250,10 @@ void main() { instances: isEndpoints ? (ctx, props) => props.splitCaps ? (props.orientation === ORIENTATION.CAP_START ? Math.ceil(props.count / 2) : Math.floor(props.count / 2)) : props.count : (ctx, props) => props.count - 3, - count: isRound - ? (ctx, props) => 6 + 2 * (props.joinResolution + (isEndpoints ? props.capResolution : props.joinResolution)) - : (ctx, props) => 6 + 2 * (1 + (isEndpoints ? props.capResolution : 1)) + 10 + count: (ctx, props) => { + const count = computeCount(props); + return 6 + 2 * (count[0] + count[1]); + } }); + } diff --git a/test/fixtures/miter/auto-caps/round-cap/expected.png b/test/fixtures/miter/auto-caps/round-cap/expected.png deleted file mode 100644 index fb5ac09f3e6622cd43b397167661f373edeb1b88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1407 zcmaKse@qi+7{~8gIGBKd#%U{&bDhIzkZC;;9PCs)TSto=9t31=SxTGT93&;m#38r2 zxj75FW>MoI{_0VBC({V)Tvc>#bKWuwaXV(FIFqgEk}5TEDMYvEp3{)bfA+_7@4fea z-{*br`+1*t_j>!!{jwy_ds~tl{x3SzM)jX(EF-lZ zb#>r=Dq$wseAdb3G>E0PuunRG(${*U9x7&I4_U+Md~qI8vzUI=8(pAYL8Lzdp?>cg zDP)3avF++;Skb9{c91>j5?qz~NUEvO=P%VhFoD_;cO2w}GtWYj&LjTPrpOPf+6F7%W7xPogCFe3vDij3VN>Ts|G2r|Ofb1U{?36O zd0+5J*sbB;zPyueVY+|bC)fR=g=K82lF7=YIwhNm9p%npibtrM$-4|iNEXqek7RRK z0@8NW{ememm+ZtGl&w3LADAs&K#I(`nT-{3^;ebOxzS|aKqQuv@i!cE$?7DK4Q43a z9XUxK3qC|TjUK2Rcn&{q2%$KLoGQrIQHwB3NeWDJVPcfBr&}Pgfmo}ja|_+m$k--W zcv}*lC^4nzj$xM1(_mxQ5b5&lqFKCbz_;|}Gr^}LxPo9@OXR*A-G4CZQq}S-2Qb#W-tJFR%N9$q1C!y{S zyj^tF$tttn!LZe4x1NnH2B@Lw#9mLmM<_ld7;smwAO4}-*4+i2VSv*Zc^=>Z#C!AE z_&N>FOYaGFpTTHTW!0yJ8l1b8RhMtj%#YWfM6c*<+9pjG-btk*3j_&NChjPzfrXM3?xYLtNkH$T3cR2+oI$c;c)#4Wa+k zIjFD!ktRA0f^CZc`M6Fz?~Lr`R3qr_rvp$RL?@;}N|JqTqij#Lj5ST}ezFy}9*q*D z{7}RcFBQ9?zPk&Y2x5epeOy6n!ke@*xlK1$n-U8M*AXXX>E0tOG)V1I$f(s?JA0zh zv679K1C|FyqGhSOnT_RXz-AJ|kTsiXEVH#~C|Y`*4W^kr{_!3tni-OGtD~M1iZekv zw8}Qv6mfuEa3K2ghy-D(5dHBANI6Uo=m>IrnLCSM3bc~iDO|s%45=I1b_vDbPZrBQ zxp*G2{YJkTXSUcMvjSeG;6l7Wl3LSvpGW-`*_NrfJ_jFO>PB!jo}`%I@K;SBk zRq{|W9OGhO@*;f!bth}=PI2`k$~W9-hQ_3gj5{3JWVKdkVKj8syA!EwhALPL%DyD! z6Kc^^OUWEi)})9#jG-(%Q4b>rv-#GWiWQY^1OMoJGS*Dl5zEc#pu2^N=bIuVDeusR pDo8fh#N_G3|K19dx5HCyBJ+M=+El%C6iB+$^|1bE%Zms|T diff --git a/test/fixtures/miter/auto-caps/square-cap/expected.png b/test/fixtures/miter/auto-caps/square-cap/expected.png deleted file mode 100644 index fb5ac09f3e6622cd43b397167661f373edeb1b88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1407 zcmaKse@qi+7{~8gIGBKd#%U{&bDhIzkZC;;9PCs)TSto=9t31=SxTGT93&;m#38r2 zxj75FW>MoI{_0VBC({V)Tvc>#bKWuwaXV(FIFqgEk}5TEDMYvEp3{)bfA+_7@4fea z-{*br`+1*t_j>!!{jwy_ds~tl{x3SzM)jX(EF-lZ zb#>r=Dq$wseAdb3G>E0PuunRG(${*U9x7&I4_U+Md~qI8vzUI=8(pAYL8Lzdp?>cg zDP)3avF++;Skb9{c91>j5?qz~NUEvO=P%VhFoD_;cO2w}GtWYj&LjTPrpOPf+6F7%W7xPogCFe3vDij3VN>Ts|G2r|Ofb1U{?36O zd0+5J*sbB;zPyueVY+|bC)fR=g=K82lF7=YIwhNm9p%npibtrM$-4|iNEXqek7RRK z0@8NW{ememm+ZtGl&w3LADAs&K#I(`nT-{3^;ebOxzS|aKqQuv@i!cE$?7DK4Q43a z9XUxK3qC|TjUK2Rcn&{q2%$KLoGQrIQHwB3NeWDJVPcfBr&}Pgfmo}ja|_+m$k--W zcv}*lC^4nzj$xM1(_mxQ5b5&lqFKCbz_;|}Gr^}LxPo9@OXR*A-G4CZQq}S-2Qb#W-tJFR%N9$q1C!y{S zyj^tF$tttn!LZe4x1NnH2B@Lw#9mLmM<_ld7;smwAO4}-*4+i2VSv*Zc^=>Z#C!AE z_&N>FOYaGFpTTHTW!0yJ8l1b8RhMtj%#YWfM6c*<+9pjG-btk*3j_&NChjPzfrXM3?xYLtNkH$T3cR2+oI$c;c)#4Wa+k zIjFD!ktRA0f^CZc`M6Fz?~Lr`R3qr_rvp$RL?@;}N|JqTqij#Lj5ST}ezFy}9*q*D z{7}RcFBQ9?zPk&Y2x5epeOy6n!ke@*xlK1$n-U8M*AXXX>E0tOG)V1I$f(s?JA0zh zv679K1C|FyqGhSOnT_RXz-AJ|kTsiXEVH#~C|Y`*4W^kr{_!3tni-OGtD~M1iZekv zw8}Qv6mfuEa3K2ghy-D(5dHBANI6Uo=m>IrnLCSM3bc~iDO|s%45=I1b_vDbPZrBQ zxp*G2{YJkTXSUcMvjSeG;6l7Wl3LSvpGW-`*_NrfJ_jFO>PB!jo}`%I@K;SBk zRq{|W9OGhO@*;f!bth}=PI2`k$~W9-hQ_3gj5{3JWVKdkVKj8syA!EwhALPL%DyD! z6Kc^^OUWEi)})9#jG-(%Q4b>rv-#GWiWQY^1OMoJGS*Dl5zEc#pu2^N=bIuVDeusR pDo8fh#N_G3|K19dx5HCyBJ+M=+El%C6iB+$^|1bE%Zms|T diff --git a/test/fixtures/miter/auto-caps/no-cap/expected.png b/test/fixtures/miter/insert-caps/none/expected.png similarity index 100% rename from test/fixtures/miter/auto-caps/no-cap/expected.png rename to test/fixtures/miter/insert-caps/none/expected.png diff --git a/test/fixtures/miter/auto-caps/no-cap/fixture.json b/test/fixtures/miter/insert-caps/none/fixture.json similarity index 100% rename from test/fixtures/miter/auto-caps/no-cap/fixture.json rename to test/fixtures/miter/insert-caps/none/fixture.json diff --git a/test/fixtures/miter/insert-caps/round/expected.png b/test/fixtures/miter/insert-caps/round/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..64e85c1738a85c09e428b6d263c951b5ab17b9c9 GIT binary patch literal 1545 zcmZ{ke@qi+7{~8zg(3>b5-|nD(;CSXQK=UK>NpjyWvocATphyjSEcTplC&cX1-WgS zOfArE(ajTojQ&`!C@gfv!6w8R!#RXTJs>cD{IRAi6>!1S1suBf_Q(F%vVSi3JkR?+ z&-*@~=lh;2DpbWpCq^R(5|gjaB@qO`UxbN@gx`Sgx)_k;=WZ?j2uvg%zmFghKZ(}l z&H0Th`@p=LMay4L{{Ce_V*Hx)JI!8i_tPkE{oKo5lDht#({tod0rhD3oTvNRTB@qF z)D!CLEFL<1nDhKH1{YxD58CI|xaYAwSdG8fNEWzfqA?{NDi>;rfpxe(iL2CkSyql# z9j8;A!5Aw=gtpV=`2!2B*{iT)q||WIHk*SPzpjW>Z)sw87D)qIMW!=&TDXgFeeN#S zmQ-Y}rYjgBhs(;w@RSXv4Rq)!jCL)+sxi|(nqCH@&F9(K2YkMp8__AHfrEn3N5rZ% z7tg@L{wuDY%D`cHqN^<*rcgi*UeKu*waqR&8v-8Hp< zZ?yGxjnKINMjkkK*C8Xc?AG~xm z?P)ydI7ce&kjSG8tRI0lo%@Pa!S0bG_F$^;Bj~uVt~nlOG z$ZSOWJPkV5o6K|@4vX$87-;3dwA+@XHD`(($Az*{K9A%6fC)`I>A~C>tIATwu)DUy z)oZl6JNz9;L&W%-AUxzh=D7W$u~-mB8j?@2-LY`I^jck$yar%ZJ7C7zT+Ox24kboi z*6RG39a4;{sx3{>>8O`dST;{69x@{(d&ME#>fg zMc6Tf>=S9lBb>h>4Z>G2LJfrPU4n*{5MPZw2sM=gPcjpW`Ud$zrnoMWw*)Lcn)NH(BNsFsz z2XwUR%tl43)Ahnx5y{Irt~W6Ya!Q8BNIuEVKICu0X2QWS?4UzNqFk+c?=r0M#OWHA z*SvvIy&~!cc5H+%ggj2LM;mRoA;4G6X1aeH)Z^yPv=*1=l9Z1wO(@b)eqcBYU!_D` zpIBpMJ!5g!8y2CB`Lj?nq+&MrtJg{cN|DhVQscDE#-nRsPjw?m;2;sw;pGt!L3TQ7 zTu;`6z(Nu8xWuGaWJU&P(dYwRpW7}VCZm`k+1i>^vuF{fk~n%`=h;obq*A2H9K+HK zdvJmgmWi^gsKi4)xHW498t$>>G47HG^+g#RCO_O|G%QCeg<6@ldlejv6yQjKvx@e* zXO>!5L&vLF_2hno5%=^&tGD?7@45X)u=Pm)^!xnL@5fh)FMWPqVQ$;DJ)isqDmlB` literal 0 HcmV?d00001 diff --git a/test/fixtures/miter/auto-caps/round-cap/fixture.json b/test/fixtures/miter/insert-caps/round/fixture.json similarity index 98% rename from test/fixtures/miter/auto-caps/round-cap/fixture.json rename to test/fixtures/miter/insert-caps/round/fixture.json index e8bc49a..9684789 100644 --- a/test/fixtures/miter/auto-caps/round-cap/fixture.json +++ b/test/fixtures/miter/insert-caps/round/fixture.json @@ -1,6 +1,5 @@ { "shape": [256, 128], - "skip": true, "command": { "insertCaps": true, "vert": [ diff --git a/test/fixtures/miter/insert-caps/square/expected.png b/test/fixtures/miter/insert-caps/square/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..2e9027a4f52bcd7e5823e6b5380db7642f2ad55a GIT binary patch literal 1492 zcmZ{ke@qi+7{~8=P(Vdw4uvxCmYYF`KWPpE2v&hobRfO9c6Bn05}1dRq@?MJ1I`(z zGwp2W=HO;E$_af7!=$V;FwmGKdYiUJcSyRSOSW50PJ~2LjWXHZ+v0!QAJ5&r`944H zc|LjGPiku_ljo`CAqbLOr7Ncp1cjdnlcTxR6pD!9u|x*YGA!(0Q~NJ zt-N~N4!}f46OSp^_s%5o=*?Eiq zjhT#>z_K@d5zO5d<}(psL%a2lQ}N(sPZpwAW1N)B2+gjkM4hSb z+DR`0{LK-{bX^7fTWcRL#Sf%|?*U51Omu0Fk{{C2X1Y=469kSgJ#AfV6X-^%r#;cP zw9-~#^fQ@bG9BK}%~gafvNY2ZJ*HjK7hNa^wSz*8)SQNWk^OS%j_Q!tHTgOFybH$l zQ$wsLv(_+MtH#6{!I)=VPfIbGTro)&Sa;LXBs@40$Xq=p>WQ4Qz0+lo)tC?5ca9!p zcbtGZ&gc!ZnT1`fzg};+P!J$UlP!x#p$w*(J|$0lYa9=}G;MZVd9`V5iF?dx9N&u; z-qvL)_$J6dk?(g2KQSLJg%E6=n(!{}Qata00|dvm!raxOZ^oRSgQvNeMZ+#Dhv zlhr-w@Q5cK9WIth&N7p?_z(&%!&w_T6@g=(Oq4o~=JiCw%)maeN+XP_+(p?ii5cVO z_KFHd7)HAFtxAnFp{-Hs>g4K`FeJl4;6Wu!c?r`!Y!WLNN_9R5w=8f8HA@M>(9&Zq`mYdd1gBfvFdctqHlebRBNOW z;ncXUT^GvmL0=? zzxKg}x(h53z5;+fP_b6f(i!a)-4GW3~;_1{DsUwD2_cGtr}_uj!ouU2|p^CYf8TYVRE4`nzMi%5HdSSQnZtgyxt zu@uc90{5hCHEt#Y>u@^;T}x7UuXB}x|50`;#O(#vGL6&>50)f%nrlPSQPz%9vv7H% z5+lGa<8)&(tfn}wG~Y^Tq$23*NW)_#L=M7DC&Bbh2)OSWO?STuF?J}qPuB-9T>g-X m8R3C*Dh@8*^_Q*v*Sm#XyLEE+a=cu=w5uv=$_F>^`si;shmzI+ literal 0 HcmV?d00001 diff --git a/test/fixtures/miter/auto-caps/square-cap/fixture.json b/test/fixtures/miter/insert-caps/square/fixture.json similarity index 98% rename from test/fixtures/miter/auto-caps/square-cap/fixture.json rename to test/fixtures/miter/insert-caps/square/fixture.json index 495e275..8bc5052 100644 --- a/test/fixtures/miter/auto-caps/square-cap/fixture.json +++ b/test/fixtures/miter/insert-caps/square/fixture.json @@ -1,6 +1,5 @@ { "shape": [256, 128], - "skip": true, "command": { "insertCaps": true, "vert": [ diff --git a/test/fixtures/round/auto-caps/none/expected.png b/test/fixtures/miter/manual-caps/none/expected.png similarity index 100% rename from test/fixtures/round/auto-caps/none/expected.png rename to test/fixtures/miter/manual-caps/none/expected.png diff --git a/test/fixtures/miter/manual-caps/none/fixture.json b/test/fixtures/miter/manual-caps/none/fixture.json new file mode 100644 index 0000000..14f749b --- /dev/null +++ b/test/fixtures/miter/manual-caps/none/fixture.json @@ -0,0 +1,105 @@ +{ + "shape": [256, 128], + "command": { + "vert": [ + "precision highp float;", + "#pragma lines: attribute vec2 xy", + "#pragma lines: attribute float break", + "#pragma lines: position = getPosition(xy, break)", + "#pragma lines: width = getWidth()", + "float getWidth() { return 20.0; }", + "vec4 getPosition(vec2 xy, float isBreak) {", + " if (isBreak > 0.0) return vec4(0);", + " return vec4(xy, 0, 1);", + "}" + ], + "frag": [ + "precision lowp float;", + "void main () {", + " gl_FragColor = vec4(0,0,0,0.5);", + "}" + ], + "blend": { + "enable": true, + "func": { + "srcRGB": "src alpha", + "srcAlpha": 1, + "dstRGB": "one minus src alpha", + "dstAlpha": 1 + } + }, + "depth": { + "enable": false + } + }, + "vertexAttributes": { + "xy": [ + [-0.8, 0.5], + [-0.3, 0.7], + [0.3, 0.5], + [0.8, 0.7], + + [0,0], + + [-0.8, -0.1], + [-0.3, 0.1], + [0.3, -0.1], + [0.8, 0.1], + + [0,0], + + [-0.8, -0.7], + [-0.3, -0.5], + [0.3, -0.7], + [0.8, -0.5] + ], + "break": [ + 0,0,0,0, + 1, + 0,0,0,0, + 1, + 0,0,0,0 + ] + }, + "endpointAttributes": { + "xy": [ + [ + [-0.8, 0.5], + [-0.3, 0.7], + [0.3, 0.5] + ], [ + [0.8, 0.7], + [0.3, 0.5], + [-0.3, 0.7] + ], [ + [-0.8, -0.1], + [-0.3, 0.1], + [0.3, -0.1] + ], [ + [0.8, 0.1], + [0.3, -0.1], + [-0.3, 0.1] + ], [ + [-0.8, -0.7], + [-0.3, -0.5], + [0.3, -0.7] + ], [ + [0.8, -0.5], + [0.3, -0.7], + [-0.3, -0.5] + ] + ], + "break": [ + [0, 0, 0], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], + [0, 0, 0] + ] + }, + "data": { + "join": "round", + "cap": "none" + } +} diff --git a/test/fixtures/miter/manual-caps/round/expected.png b/test/fixtures/miter/manual-caps/round/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..59a93ce1794d2e4a04da03b04ee7b1aa155b27bf GIT binary patch literal 1535 zcmZvce^3-<7{_<-4h}&e#w71hT)HNw5DjoOKwvRkKt1I4IGDpwid!=p z=^=YF8LZhqAwlpNe_`R&1{Lf4fzOZm2K zf_&pf{=nhGyzuKI_ybPeHceCz!eFz%f|y!EZT3tD)*0uOggZl{I>*?g2 zQF&z*bTI?c`J`l=h>kCfr{!Vj?F_*y@X|dry&QTQ8@QRD#T*a+(4452_5;vMhGiR@ z&cKWNFShm^k?sLd&NrarW}2>buAt15Bz(aT&(AqVvW){39{!2Wy2WT24HuhltaP^u zeS3d+nHJclNxZg!311(f*B_Ap8CVD~?aA{c9 z8`O}>wiVVKB%w)yv+5KEem`9Qfu$J^LqOU|c#cW^3>hQ_qMB9ETg8)kx zvonhngBGiZFO4B&JH+3afK{x$j=X^PYur-`DIdb$(OWe&Tq?rvg{|WcV!#tJQsweT z5OOy>;R|7B8l3l-Zqo!*PdI$ZF)dqUx2+(AG_`xD{duaa1*<@}HhW+QR_%Jdv+6VM zX3f;Y?_H_z6e%@5JR$=)Jbf4B^ zb^6X2ys#CXm~(Kk`p!3DRtm_ibRQAQW;wRka5HJp(3b0Ms%`v)dsGhz+1;(rXh{RW zJ03{#MOrR1Xq#L66wRmd`~_WQ3|@X5-f^s+E3!*DAb&0wy4JyR8d6N=$wjoBL`Is; zmRa?4JTBEVX=p-gwc|!Wi4<99e}+kWzYhhPD~|Gw^idQxMevyzS7XK zknM-!F#oBR&XIB!b6H~}jXTtAvyR`2RxXC%9JQWPAm zl{S$jDzOP_&kO}x@(wmxTt>(oWLl|Vs~(GC%wH}zEd^+pPQg0roJ}Y{^Ex)U{=dWZ cfAXA16McI&f{cPfWxnTZ%*|}u@b-s)1N3UZ@Bjb+ literal 0 HcmV?d00001 diff --git a/test/fixtures/miter/manual-caps/round/fixture.json b/test/fixtures/miter/manual-caps/round/fixture.json new file mode 100644 index 0000000..9cfe7e7 --- /dev/null +++ b/test/fixtures/miter/manual-caps/round/fixture.json @@ -0,0 +1,105 @@ +{ + "shape": [256, 128], + "command": { + "vert": [ + "precision highp float;", + "#pragma lines: attribute vec2 xy", + "#pragma lines: attribute float break", + "#pragma lines: position = getPosition(xy, break)", + "#pragma lines: width = getWidth()", + "float getWidth() { return 20.0; }", + "vec4 getPosition(vec2 xy, float isBreak) {", + " if (isBreak > 0.0) return vec4(0);", + " return vec4(xy, 0, 1);", + "}" + ], + "frag": [ + "precision lowp float;", + "void main () {", + " gl_FragColor = vec4(0,0,0,0.5);", + "}" + ], + "blend": { + "enable": true, + "func": { + "srcRGB": "src alpha", + "srcAlpha": 1, + "dstRGB": "one minus src alpha", + "dstAlpha": 1 + } + }, + "depth": { + "enable": false + } + }, + "vertexAttributes": { + "xy": [ + [-0.8, 0.5], + [-0.3, 0.7], + [0.3, 0.5], + [0.8, 0.7], + + [0,0], + + [-0.8, -0.1], + [-0.3, 0.1], + [0.3, -0.1], + [0.8, 0.1], + + [0,0], + + [-0.8, -0.7], + [-0.3, -0.5], + [0.3, -0.7], + [0.8, -0.5] + ], + "break": [ + 0,0,0,0, + 1, + 0,0,0,0, + 1, + 0,0,0,0 + ] + }, + "endpointAttributes": { + "xy": [ + [ + [-0.8, 0.5], + [-0.3, 0.7], + [0.3, 0.5] + ], [ + [0.8, 0.7], + [0.3, 0.5], + [-0.3, 0.7] + ], [ + [-0.8, -0.1], + [-0.3, 0.1], + [0.3, -0.1] + ], [ + [0.8, 0.1], + [0.3, -0.1], + [-0.3, 0.1] + ], [ + [-0.8, -0.7], + [-0.3, -0.5], + [0.3, -0.7] + ], [ + [0.8, -0.5], + [0.3, -0.7], + [-0.3, -0.5] + ] + ], + "break": [ + [0, 0, 0], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], + [0, 0, 0] + ] + }, + "data": { + "join": "round", + "cap": "round" + } +} diff --git a/test/fixtures/round/auto-caps/square/expected.png b/test/fixtures/miter/manual-caps/square/expected.png similarity index 100% rename from test/fixtures/round/auto-caps/square/expected.png rename to test/fixtures/miter/manual-caps/square/expected.png diff --git a/test/fixtures/miter/manual-caps/square/fixture.json b/test/fixtures/miter/manual-caps/square/fixture.json new file mode 100644 index 0000000..4f798e8 --- /dev/null +++ b/test/fixtures/miter/manual-caps/square/fixture.json @@ -0,0 +1,105 @@ +{ + "shape": [256, 128], + "command": { + "vert": [ + "precision highp float;", + "#pragma lines: attribute vec2 xy", + "#pragma lines: attribute float break", + "#pragma lines: position = getPosition(xy, break)", + "#pragma lines: width = getWidth()", + "float getWidth() { return 20.0; }", + "vec4 getPosition(vec2 xy, float isBreak) {", + " if (isBreak > 0.0) return vec4(0);", + " return vec4(xy, 0, 1);", + "}" + ], + "frag": [ + "precision lowp float;", + "void main () {", + " gl_FragColor = vec4(0,0,0,0.5);", + "}" + ], + "blend": { + "enable": true, + "func": { + "srcRGB": "src alpha", + "srcAlpha": 1, + "dstRGB": "one minus src alpha", + "dstAlpha": 1 + } + }, + "depth": { + "enable": false + } + }, + "vertexAttributes": { + "xy": [ + [-0.8, 0.5], + [-0.3, 0.7], + [0.3, 0.5], + [0.8, 0.7], + + [0,0], + + [-0.8, -0.1], + [-0.3, 0.1], + [0.3, -0.1], + [0.8, 0.1], + + [0,0], + + [-0.8, -0.7], + [-0.3, -0.5], + [0.3, -0.7], + [0.8, -0.5] + ], + "break": [ + 0,0,0,0, + 1, + 0,0,0,0, + 1, + 0,0,0,0 + ] + }, + "endpointAttributes": { + "xy": [ + [ + [-0.8, 0.5], + [-0.3, 0.7], + [0.3, 0.5] + ], [ + [0.8, 0.7], + [0.3, 0.5], + [-0.3, 0.7] + ], [ + [-0.8, -0.1], + [-0.3, 0.1], + [0.3, -0.1] + ], [ + [0.8, 0.1], + [0.3, -0.1], + [-0.3, 0.1] + ], [ + [-0.8, -0.7], + [-0.3, -0.5], + [0.3, -0.7] + ], [ + [0.8, -0.5], + [0.3, -0.7], + [-0.3, -0.5] + ] + ], + "break": [ + [0, 0, 0], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], + [0, 0, 0] + ] + }, + "data": { + "join": "round", + "cap": "square" + } +} diff --git a/test/fixtures/round/auto-caps/round/expected.png b/test/fixtures/round/auto-caps/round/expected.png deleted file mode 100644 index 5e6043469ca90040bf347bf8bad48995a184478f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1539 zcmZvceM}Q)9LMik+Ms^DGouM6N)+C5<1-Q^|nbC zH<0TVr=BPal%{tm#IkBYg|NkO1`CNhU}YNqSOd$cV}leUTHSN|)9jzi{a)^Q?)iRx z-{(wGfiYsCZXpCg5xGP*2|+M;h3HTa)-t5q#@isDJb5r)L3Um^Dc;Y+c2q?dlO$8kjCZ(#XOXeJ!>yv;m=E}G2GRDO#= z8lbO26^8tsVGOF}QsJ2cxK$MYfirsS^BTmc2BkY{Mz8D6K(@mmxTf?c84iUAaWGgR zURhmsClkfnu=@dV;tX3w0$xK~yc9)WO(S3bFzEu~a$~016p&a~ zZKFDHApMR!Z#yRgjWOBMWuQ}HxlhR9xMwn@?bJ|q1Y@i(r`a9vgYIOL`JQmk#ZAe8 z{}jcC!si^fR}Pkn;8d}v*`7rJb&1K`sI7&unnJMR@$7tX8a-xdHkpUh-Ac^jNktmR z-4|Fs^Q$#s9LERY(mjJKD4sfnm-u9qDOz<#?q4iQocZXMK4nGq08Lg$tJ(QlMTt}Hn&cGZCG27AIt^3=D%U}MM{4Zx~#?7TL=>EW?L1S*yfyT z1K!edTwhEEz!VEsj;&+o9}4|0&IabDqL7lR47-T@oy}m6i8kk`wsr{+AC!Qf#>a#L zu{d~|J$lxD7dY(|y^$K+O7YUh8!0VL{)$pqSQcGmwhY4+=fH)e!%K9v44@jx+^H8k z=$9Ylj~MBVgTxx8UoYD(2-SiUMK7(Y&cc}ol%(Y0>I`Wj#9}bk~rWDmB zXu8@@$-w~J{jFUCn{1!pe}#iqR#{zxE(>w=szHziR~ykpjbl_c+!I=CHbg@$*2B2u zkqNFq0=@Pe+EuC|E^7d~!UpI@q;JzTeM}Q)9Dc8q1Lb?hu>zCR3&SbMwB8&<@N`(Nn=MkP1Ozvi0_L2Ml`sYjz0J*S zLb1)FhEwo^qx7wgWsqvBqH|leTV^3{Z#5`tGMg@`+Qpe_=7#P)H*?=dbEUFS>q> zWwb1x%hwATbyAVRHvoGH)5W1+w=QJh`}MwfdqFl+x!Ck@D7XNf1Ms@bJK|Uky)=M8ejmb4S1)0$<3oELU2DrHhU!Uq(gly( z_S?l1PvRjx2c1Ptfo~DG<|=EKGW`}EV??)zbnyZV9x?pC6l#Jh`Ba*8f=NH|5x-C}jBy+M-?l9|@1K#FkA1v?1q zGjwDw+e2D(hQ3VuXgWLt^0kzf59JFr=j9)Tsc5@~iM+suU$e|**QqgWEJ5zGWvD+C z2LL=y^p=gzqmF321cgcU2-9EEEh4$X7~IxCN;PSFv)!qhc|W+z;Dv?pR&6(RLjv^v*$l>w9jV_5Yh!_FpDbDz`XK1wO9@`?aKey zaX*GEBQV6x_rM85;vm zgl=FDt9Fb2?kPcRkH_+diw#+H>VUK7kueZ@9ka^Ie{y1%v@Wi8Rz8OYz7F)2NDJ?>=u$X3Tj?khY7LF3RJmqH~8>@b%+}>~R-lF&&pj zw3NBUxZcfIDWE+IsXPeVwid!=p z=^=YF8LZhqAwlpNe_`R&1{Lf4fzOZm2K zf_&pf{=nhGyzuKI_ybPeHceCz!eFz%f|y!EZT3tD)*0uOggZl{I>*?g2 zQF&z*bTI?c`J`l=h>kCfr{!Vj?F_*y@X|dry&QTQ8@QRD#T*a+(4452_5;vMhGiR@ z&cKWNFShm^k?sLd&NrarW}2>buAt15Bz(aT&(AqVvW){39{!2Wy2WT24HuhltaP^u zeS3d+nHJclNxZg!311(f*B_Ap8CVD~?aA{c9 z8`O}>wiVVKB%w)yv+5KEem`9Qfu$J^LqOU|c#cW^3>hQ_qMB9ETg8)kx zvonhngBGiZFO4B&JH+3afK{x$j=X^PYur-`DIdb$(OWe&Tq?rvg{|WcV!#tJQsweT z5OOy>;R|7B8l3l-Zqo!*PdI$ZF)dqUx2+(AG_`xD{duaa1*<@}HhW+QR_%Jdv+6VM zX3f;Y?_H_z6e%@5JR$=)Jbf4B^ zb^6X2ys#CXm~(Kk`p!3DRtm_ibRQAQW;wRka5HJp(3b0Ms%`v)dsGhz+1;(rXh{RW zJ03{#MOrR1Xq#L66wRmd`~_WQ3|@X5-f^s+E3!*DAb&0wy4JyR8d6N=$wjoBL`Is; zmRa?4JTBEVX=p-gwc|!Wi4<99e}+kWzYhhPD~|Gw^idQxMevyzS7XK zknM-!F#oBR&XIB!b6H~}jXTtAvyR`2RxXC%9JQWPAm zl{S$jDzOP_&kO}x@(wmxTt>(oWLl|Vs~(GC%wH}zEd^+pPQg0roJ}Y{^Ex)U{=dWZ cfAXA16McI&f{cPfWxnTZ%*|}u@b-s)1N3UZ@Bjb+ literal 0 HcmV?d00001 diff --git a/test/fixtures/round/auto-caps/round/fixture.json b/test/fixtures/round/insert-caps/round/fixture.json similarity index 100% rename from test/fixtures/round/auto-caps/round/fixture.json rename to test/fixtures/round/insert-caps/round/fixture.json diff --git a/test/fixtures/round/insert-caps/square/expected.png b/test/fixtures/round/insert-caps/square/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..6d24f00745ba616e58409766159020ca1718bda3 GIT binary patch literal 1481 zcmZ{ke@qi+7{~8gS}Kaj4AK^gmvoC$#^!)i0dXh6Ql{eR*k!%Zy4<*ggaB-Yj*~UeE2Ge)fCE=>!^Y`Oh~`Q-PKffjd@ z8s6!6D!KiCCl~@B*r|Caa8?*O>roMoX2!LPU)MX{0d8VK4ZVMfHh!2H zM!F^qS+{E7b<)b)<@#b>*zLn-j=N<(TL(NBFCw#*Atf1iFn0E0N!YJ5ZZZ_Nz)GFh z`<&eO3KM$gssD|f8HyWmDqDO)GP!Fu01mK}_ z?Fo;6t04TW8u`rQFYI&|34;HTNgB=C>St_o>#qdfe}BG$`?S>@S&og|(-&*Fy|9Ih zvnqvZd{)7!$l#mAbS~W>9Y`edm{=v7`J*UheKwr4N^^U>t`zZhE7;3ss-QHUMGfz& z{g?_!M5pI&3fHH!DY!Jet-{nP-M3SlF5}lF*6PBavls2r^%S_Wn1dn}X~p?d?$bCu zjl-fF7obTgJ&2TjxX_A@%%)-dV#M6TC zcrh7o(iYJs%f#FJmXVftGsBM7Y_#O;ezBrP3Ya6i@sT0DMYDvfQwtZc85L=HM>4}` z<12|v5=bVHBBnZlfnB>8M(i+BE+&dopel{)!4Nm8*yaRdY_&x~zm!gk=K4AFpS@Eu9h5Yv#iyWf=dwKDvVi zjS%?5H>lV;?cN0b4hAviu%KDDEkPgYXTcNaBzD|$7!&+|WQ@$?{DoitKYWfA%s;ub zrKNYgO&41Z4k9%;cxH}Tix4^rgAK~bvX%5|*c@gD8ifMC`!VOyZqAePt_@*i2ei+j zImjS8Okfj)c=@EKr$QP{)uvzYUNb@I^Im(JHoe=s2jPhbwh^&h5GLq6Y19dj9m=3V z6wA(gksSaE;CC^ziP_RC7q9B>>kZYNuzHC$JyA8L!pPCV449zyy)JKT8xtdSS-jUj z{NYds@WFAT_xHc}xwwn)3X^3Tk5*_cjsw)W_xW>4a8S*M|vkIgDD2Hi0OQ~ z0Uua~(pR&YVKi-_LSkA{t21cQxjd!Ni4{muHORI{#2?tIWQ1@HVa**-5ZP!b$Jwf1 zMH!qOJh#XG8nq>Vh9H>xGW}EvOQAnhAMJu^Vi4L2Dz-|Si1C4W zP*;WtgRNJT+zKdbmhtCmY7;5&G)W(UH2-u?Cg?34LtptT@4RenH)ocNF8bx8W9Uy^ MzQI@;DA{r3KY!|$IsgCw literal 0 HcmV?d00001 diff --git a/test/fixtures/round/auto-caps/square/fixture.json b/test/fixtures/round/insert-caps/square/fixture.json similarity index 100% rename from test/fixtures/round/auto-caps/square/fixture.json rename to test/fixtures/round/insert-caps/square/fixture.json diff --git a/test/fixtures/round/manual-caps/none/fixture.json b/test/fixtures/round/manual-caps/none/fixture.json index f9de247..14f749b 100644 --- a/test/fixtures/round/manual-caps/none/fixture.json +++ b/test/fixtures/round/manual-caps/none/fixture.json @@ -91,10 +91,10 @@ ], "break": [ [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], [0, 0, 0] ] }, diff --git a/test/fixtures/round/manual-caps/round/fixture.json b/test/fixtures/round/manual-caps/round/fixture.json index 539d3e0..9cfe7e7 100644 --- a/test/fixtures/round/manual-caps/round/fixture.json +++ b/test/fixtures/round/manual-caps/round/fixture.json @@ -91,10 +91,10 @@ ], "break": [ [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], [0, 0, 0] ] }, diff --git a/test/fixtures/round/manual-caps/square/fixture.json b/test/fixtures/round/manual-caps/square/fixture.json index 78d04e8..4f798e8 100644 --- a/test/fixtures/round/manual-caps/square/fixture.json +++ b/test/fixtures/round/manual-caps/square/fixture.json @@ -91,10 +91,10 @@ ], "break": [ [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], - [0, 0, 0], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], [0, 0, 0] ] },