q5 4.2.6 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/deno.json +1 -1
  2. package/package.json +1 -1
  3. package/q5.d.ts +50 -16
  4. package/q5.js +460 -125
  5. package/q5.min.js +2 -2
package/q5.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * q5.js
3
- * @version 4.2
3
+ * @version 4.4
4
4
  * @author quinton-ashley
5
5
  * @contributors evanalulu, Tezumie, ormaq, Dukemz, LingDong-
6
6
  * @license LGPL-3.0
@@ -487,11 +487,10 @@ if (typeof window == 'object') {
487
487
  }
488
488
  };
489
489
 
490
- window.addEventListener('beforeunload', cleanup);
491
490
  window.addEventListener('pagehide', cleanup);
492
491
  } else global.window = 0;
493
492
 
494
- Q5.version = Q5.VERSION = '4.2';
493
+ Q5.version = Q5.VERSION = '4.4';
495
494
 
496
495
  if (typeof document == 'object') {
497
496
  document.addEventListener('DOMContentLoaded', () => {
@@ -3556,18 +3555,16 @@ Q5.modules.input = ($, q) => {
3556
3555
  };
3557
3556
 
3558
3557
  $._updatePointer = (e) => {
3559
- let id = e.pointerId || $.pointers[0]?.id;
3560
- if (id == undefined) {
3561
- if (e instanceof MouseEvent) id = 0;
3562
- else return;
3563
- }
3558
+ let id = e.pointerId ?? $.pointers[0]?.id;
3564
3559
 
3565
3560
  let p = $.pointers.find((p) => p.id === id);
3566
3561
  if (!p) {
3567
3562
  p = { id };
3568
- $.pointers.push(p);
3563
+ if (e.type != 'wheel') $.pointers.push(p);
3569
3564
  }
3570
- p.event = e;
3565
+
3566
+ if (e.type != 'wheel') p.event = e;
3567
+ else $._wheel = p;
3571
3568
 
3572
3569
  let x, y;
3573
3570
  if (c) {
@@ -3595,16 +3592,19 @@ Q5.modules.input = ($, q) => {
3595
3592
 
3596
3593
  $._updateMouse = (e) => {
3597
3594
  let p = $.pointers[0];
3598
- if (e.pointerId != undefined && e.pointerId != p.id) return;
3599
3595
 
3600
3596
  if (document.pointerLockElement) {
3601
3597
  if (e.movementX != undefined) {
3602
3598
  q.mouseX += e.movementX;
3603
3599
  q.mouseY += e.movementY;
3604
3600
  }
3605
- } else {
3601
+ } else if (p) {
3602
+ if (e.pointerId != undefined && e.pointerId != p.id) return;
3606
3603
  q.mouseX = p.canvasPos?.x ?? p.x;
3607
3604
  q.mouseY = p.canvasPos?.y ?? p.y;
3605
+ } else if ($._wheel) {
3606
+ q.mouseX = $._wheel.x;
3607
+ q.mouseY = $._wheel.y;
3608
3608
  }
3609
3609
 
3610
3610
  if (e.movementX != undefined) {
@@ -3635,15 +3635,16 @@ Q5.modules.input = ($, q) => {
3635
3635
 
3636
3636
  $._onpointerup = (e) => {
3637
3637
  q.mouseIsPressed = false;
3638
- if (pressAmt > 0) pressAmt--;
3639
- else return;
3640
- $._updatePointer(e);
3641
- $._updateMouse(e);
3642
- if (e.pointerType === 'touch' || e.pointerType === 'pen') {
3638
+ if (pressAmt > 0) {
3639
+ pressAmt--;
3640
+ $._updatePointer(e);
3641
+ $._updateMouse(e);
3642
+ $.mouseReleased(e);
3643
+ }
3644
+ if (e.pointerType == 'touch' || e.pointerType == 'pen') {
3643
3645
  let p = $.pointers.find((p) => p.id === e.pointerId);
3644
3646
  if (p) p._ended = true;
3645
3647
  }
3646
- $.mouseReleased(e);
3647
3648
  };
3648
3649
 
3649
3650
  $._onclick = (e) => {
@@ -6152,11 +6153,16 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6152
6153
  $.pop();
6153
6154
  } else {
6154
6155
  addColor(r, g, b, a);
6155
- let lx = -c.hw,
6156
+ let ci = colorIndex,
6157
+ lx = -c.hw,
6156
6158
  rx = c.hw,
6157
6159
  ty = -c.hh,
6158
6160
  by = c.hh;
6159
- addQuad(lx, ty, rx, ty, rx, by, lx, by, colorIndex, 0);
6161
+ addVert(lx, ty, ci, 0);
6162
+ addVert(rx, ty, ci, 0);
6163
+ addVert(lx, by, ci, 0);
6164
+ addVert(rx, by, ci, 0);
6165
+ drawStack.push(1, 4); // always use the default shapes pipeline
6160
6166
  }
6161
6167
  };
6162
6168
 
@@ -6196,7 +6202,15 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6196
6202
  shouldClear = false;
6197
6203
  };
6198
6204
 
6199
- let transformsBuffer, colorsBuffer, shapesVertBuff, imgVertBuff, charBuffer, textBuffer;
6205
+ let transformsBuffer,
6206
+ colorsBuffer,
6207
+ shapesVertBuff,
6208
+ imgVertBuff,
6209
+ polygonVertBuff,
6210
+ polyPtsBuffer,
6211
+ polyPtsBindGroup,
6212
+ charBuffer,
6213
+ textBuffer;
6200
6214
  let mainBindGroup, lastTransformsBuffer, lastColorsBuffer;
6201
6215
 
6202
6216
  $._render = () => {
@@ -6275,6 +6289,37 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6275
6289
 
6276
6290
  // prepare to render images and videos
6277
6291
 
6292
+ if (polygonVertIdx) {
6293
+ let polygonVertSize = polygonVertIdx * 4; // 4 bytes per float
6294
+ if (!polygonVertBuff || polygonVertBuff.size < polygonVertSize) {
6295
+ if (polygonVertBuff) polygonVertBuff.destroy();
6296
+ polygonVertBuff = Q5.device.createBuffer({
6297
+ size: polygonVertSize * 2,
6298
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
6299
+ });
6300
+ }
6301
+
6302
+ Q5.device.queue.writeBuffer(polygonVertBuff, 0, polygonVertStack.subarray(0, polygonVertIdx));
6303
+ $._pass.setVertexBuffer(2, polygonVertBuff);
6304
+
6305
+ if (polyPtsIdx) {
6306
+ let polyPtsSize = polyPtsIdx * 4;
6307
+ if (!polyPtsBuffer || polyPtsBuffer.size < polyPtsSize) {
6308
+ if (polyPtsBuffer) polyPtsBuffer.destroy();
6309
+ polyPtsBuffer = Q5.device.createBuffer({
6310
+ size: Math.max(polyPtsSize * 2, 64),
6311
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
6312
+ });
6313
+ }
6314
+ Q5.device.queue.writeBuffer(polyPtsBuffer, 0, polyPtsStack.subarray(0, polyPtsIdx));
6315
+
6316
+ polyPtsBindGroup = Q5.device.createBindGroup({
6317
+ layout: polygonBindGroupLayout,
6318
+ entries: [{ binding: 0, resource: { buffer: polyPtsBuffer } }]
6319
+ });
6320
+ }
6321
+ }
6322
+
6278
6323
  if (imgVertIdx) {
6279
6324
  $._pass.setPipeline($._pipelines[2]); // images pipeline
6280
6325
 
@@ -6373,6 +6418,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6373
6418
  let drawVertOffset = 0,
6374
6419
  imageVertOffset = 0,
6375
6420
  textCharOffset = 0,
6421
+ polygonVertOffset = 0,
6376
6422
  rectIdx = 0,
6377
6423
  ellipseIdx = 0,
6378
6424
  curPipelineIndex = -1;
@@ -6400,12 +6446,18 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6400
6446
  } else if (curPipelineIndex == 6) {
6401
6447
  pass.setIndexBuffer(ellipseIndexBuffer, 'uint16');
6402
6448
  pass.setBindGroup(1, ellipseBindGroup);
6449
+ } else if (curPipelineIndex == 7) {
6450
+ pass.setBindGroup(1, polyPtsBindGroup);
6403
6451
  } else if ($._customBindHandlers[curPipelineIndex]) {
6404
6452
  $._customBindHandlers[curPipelineIndex](pass);
6405
6453
  }
6406
6454
  }
6407
6455
 
6408
- if (curPipelineIndex == 6) {
6456
+ if (curPipelineIndex == 7) {
6457
+ // draw a polygon
6458
+ pass.draw(v, 1, polygonVertOffset, 0);
6459
+ polygonVertOffset += v;
6460
+ } else if (curPipelineIndex == 6) {
6409
6461
  // draw an ellipse
6410
6462
  pass.drawIndexed(18, v, 0, 0, ellipseIdx);
6411
6463
  ellipseIdx += v;
@@ -6482,6 +6534,8 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
6482
6534
 
6483
6535
  // reset
6484
6536
  shapesVertIdx = 0;
6537
+ polygonVertIdx = 0;
6538
+ polyPtsIdx = 0;
6485
6539
  imgVertIdx = 0;
6486
6540
  // Remove video frames without creating new array
6487
6541
  if (vidFrames > 0) {
@@ -6632,19 +6686,16 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6632
6686
  };
6633
6687
 
6634
6688
  $.vertex = (x, y) => {
6635
- if (matrixDirty) saveMatrix();
6636
- sv.push(x, y, fillIdx, matrixIdx);
6689
+ sv.push(x, y, fillIdx);
6637
6690
  shapeVertCount++;
6638
6691
  };
6639
6692
 
6640
6693
  $.curveVertex = (x, y) => {
6641
- if (matrixDirty) saveMatrix();
6642
6694
  curveVertices.push({ x, y });
6643
6695
  };
6644
6696
 
6645
6697
  $.bezierVertex = function (cx1, cy1, cx2, cy2, x, y) {
6646
6698
  if (shapeVertCount === 0) throw new Error('Shape needs a vertex()');
6647
- if (matrixDirty) saveMatrix();
6648
6699
 
6649
6700
  // Get the last vertex as the starting point (P₀)
6650
6701
  let prevIndex = (shapeVertCount - 1) * 4;
@@ -6680,136 +6731,413 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6680
6731
  vy = mt3 * startY + 3 * mt2 * t * cy1 + 3 * mt * t2 * cy2 + t3 * y;
6681
6732
  }
6682
6733
 
6683
- sv.push(vx, vy, fillIdx, matrixIdx);
6734
+ sv.push(vx, vy, fillIdx);
6684
6735
  shapeVertCount++;
6685
6736
  }
6686
6737
  };
6687
6738
 
6688
6739
  $.quadraticVertex = (cx, cy, x, y) => $.bezierVertex(cx, cy, x, y);
6689
6740
 
6741
+ function addQuad(x1, y1, x2, y2, x3, y3, x4, y4, ci, ti) {
6742
+ addVert(x1, y1, ci, ti); // v0
6743
+ addVert(x2, y2, ci, ti); // v1
6744
+ addVert(x4, y4, ci, ti); // v3
6745
+ addVert(x3, y3, ci, ti); // v2
6746
+ drawStack.push(shapesPL, 4);
6747
+ }
6748
+
6749
+ $.plane = (x, y, w, h) => {
6750
+ h ??= w;
6751
+ let [l, r, t, b] = calcBox(x, y, w, h, 'center');
6752
+ if (matrixDirty) saveMatrix();
6753
+ addQuad(l, t, r, t, r, b, l, b, fillIdx, matrixIdx);
6754
+ };
6755
+
6756
+ $.curve = (x1, y1, x2, y2, x3, y3, x4, y4) => {
6757
+ $.beginShape();
6758
+ $.curveVertex(x1, y1);
6759
+ $.curveVertex(x2, y2);
6760
+ $.curveVertex(x3, y3);
6761
+ $.curveVertex(x4, y4);
6762
+ $.endShape();
6763
+ };
6764
+
6765
+ $.bezier = (x1, y1, x2, y2, x3, y3, x4, y4) => {
6766
+ $.beginShape();
6767
+ $.vertex(x1, y1);
6768
+ $.bezierVertex(x2, y2, x3, y3, x4, y4);
6769
+ $.endShape();
6770
+ };
6771
+
6772
+ /* POLYGONS */
6773
+
6774
+ let polygonPL = 7;
6775
+
6776
+ $._polygonShaderCode =
6777
+ $._baseShaderCode +
6778
+ /* wgsl */ `
6779
+ struct VertexParams {
6780
+ @builtin(vertex_index) vertexIndex : u32,
6781
+ @location(0) pos: vec2f,
6782
+ @location(1) polyStart: f32,
6783
+ @location(2) polyCount: f32,
6784
+ @location(3) fillIndex: f32,
6785
+ @location(4) strokeIndex: f32,
6786
+ @location(5) strokeWeight: f32,
6787
+ @location(6) matrixIndex: f32
6788
+ }
6789
+
6790
+ struct FragParams {
6791
+ @builtin(position) position: vec4f,
6792
+ @location(0) localPos: vec2f,
6793
+ @location(1) @interpolate(flat) polyStart: u32,
6794
+ @location(2) @interpolate(flat) polyCount: u32,
6795
+ @location(3) @interpolate(flat) fillIndex: f32,
6796
+ @location(4) @interpolate(flat) strokeIndex: f32,
6797
+ @location(5) @interpolate(flat) strokeWeight: f32,
6798
+ @location(6) @interpolate(flat) isClosed: f32
6799
+ }
6800
+
6801
+ @group(0) @binding(0) var<uniform> q: Q5;
6802
+ @group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
6803
+ @group(0) @binding(2) var<storage> colors : array<vec4f>;
6804
+
6805
+ @group(1) @binding(0) var<storage, read> polyPts: array<vec4f>;
6806
+
6807
+ fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
6808
+ var vert = vec4f(pos, 0.0, 1.0);
6809
+ vert = transforms[i32(matrixIndex)] * vert;
6810
+ vert.x /= q.halfWidth;
6811
+ vert.y /= q.halfHeight;
6812
+ return vert;
6813
+ }
6814
+
6815
+ fn getPolyColor(p: vec2f, start: u32, count: u32, fIdx: f32) -> vec4f {
6816
+ let uniformColor = colors[i32(fIdx)];
6817
+ if (uniformColor.a == 0.0) {
6818
+ return uniformColor;
6819
+ }
6820
+
6821
+ var sumWeight: f32 = 0.0;
6822
+ var sumColor = vec4f(0.0);
6823
+ for (var i: u32 = 0u; i < count; i = i + 1u) {
6824
+ let pt = polyPts[start + i];
6825
+ let d = distance(p, pt.xy);
6826
+ if (d < 0.1) {
6827
+ return colors[i32(pt.z)];
6828
+ }
6829
+ let w = 1.0 / (d * d * d);
6830
+ sumWeight += w;
6831
+ sumColor += colors[i32(pt.z)] * w;
6832
+ }
6833
+ return sumColor / sumWeight;
6834
+ }
6835
+
6836
+ fn sdPolygon(p: vec2f, start: u32, count: u32, isClosed: f32) -> f32 {
6837
+ var d: f32 = dot(p - polyPts[start].xy, p - polyPts[start].xy);
6838
+ var s: f32 = 1.0;
6839
+ var j: u32 = count - 1u;
6840
+ for (var i: u32 = 0u; i < count; i = i + 1u) {
6841
+ let vi = polyPts[start + i].xy;
6842
+ let vj = polyPts[start + j].xy;
6843
+ let e = vj - vi;
6844
+ let w = p - vi;
6845
+ let b = w - e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
6846
+ let bSq = dot(b, b);
6847
+ if (isClosed != 0.0 || i != 0u) {
6848
+ if (bSq < d) { d = bSq; }
6849
+ }
6850
+
6851
+ let condX = p.y >= vi.y;
6852
+ let condY = p.y < vj.y;
6853
+ let condZ = e.x * w.y > e.y * w.x;
6854
+ if ((condX && condY && condZ) || (!condX && !condY && !condZ)) {
6855
+ s = -s;
6856
+ }
6857
+ j = i;
6858
+ }
6859
+ if (isClosed == 0.0) {
6860
+ return sqrt(d);
6861
+ }
6862
+ return s * sqrt(d);
6863
+ }
6864
+
6865
+ @vertex
6866
+ fn vertexMain(v: VertexParams) -> FragParams {
6867
+ var f: FragParams;
6868
+
6869
+ // manually apply transform
6870
+ var vert = vec4f(v.pos, 0.0, 1.0);
6871
+ vert = transforms[i32(v.matrixIndex)] * vert;
6872
+ vert.x /= q.halfWidth;
6873
+ vert.y /= q.halfHeight;
6874
+
6875
+ f.position = vert;
6876
+ f.localPos = v.pos;
6877
+ f.polyStart = u32(v.polyStart + 0.1);
6878
+ f.polyCount = u32(abs(v.polyCount) + 0.1);
6879
+ f.isClosed = step(0.0, v.polyCount);
6880
+ f.fillIndex = v.fillIndex;
6881
+ f.strokeIndex = v.strokeIndex;
6882
+ f.strokeWeight = v.strokeWeight;
6883
+ return f;
6884
+ }
6885
+
6886
+ @fragment
6887
+ fn fragMain(f: FragParams) -> @location(0) vec4f {
6888
+ let dist = sdPolygon(f.localPos, f.polyStart, f.polyCount, f.isClosed);
6889
+ let fill = getPolyColor(f.localPos, f.polyStart, f.polyCount, f.fillIndex);
6890
+ let stroke = colors[i32(f.strokeIndex)];
6891
+
6892
+ let dpdx_d = dpdx(dist);
6893
+ let dpdy_d = dpdy(dist);
6894
+ let distGrad = sqrt(dpdx_d * dpdx_d + dpdy_d * dpdy_d);
6895
+ let aa = clamp(distGrad * 1.5, 0.001, 2.0);
6896
+
6897
+ let halfStroke = f.strokeWeight * 0.5;
6898
+
6899
+ var outFragColor: vec4f;
6900
+
6901
+ if (fill.a != 0.0 && f.strokeWeight == 0.0) {
6902
+ let fillAlpha = 1.0 - smoothstep(-aa, aa, dist);
6903
+ if (fillAlpha <= 0.0) { discard; }
6904
+ outFragColor = vec4f(fill.rgb, fill.a * fillAlpha);
6905
+ } else if (fill.a != 0.0) {
6906
+ let fillAlpha = 1.0 - smoothstep(-aa, aa, dist);
6907
+ let strokeDist = abs(dist) - halfStroke;
6908
+ let strokeAlphaMask = 1.0 - smoothstep(-aa, aa, strokeDist);
6909
+
6910
+ if (fillAlpha <= 0.0 && strokeAlphaMask <= 0.0) { discard; }
6911
+
6912
+ let sA = stroke.a * strokeAlphaMask;
6913
+ let fA = fill.a * fillAlpha;
6914
+ let outAlpha = sA + fA * (1.0 - sA);
6915
+ let outCol = stroke.rgb * sA + fill.rgb * fA * (1.0 - sA);
6916
+ outFragColor = vec4f(outCol / max(outAlpha, 1e-5), outAlpha);
6917
+ } else {
6918
+ let strokeDist = abs(dist) - halfStroke;
6919
+ let strokeAlpha = 1.0 - smoothstep(-aa, aa, strokeDist);
6920
+
6921
+ if (strokeAlpha <= 0.0) { discard; }
6922
+ outFragColor = vec4f(stroke.rgb, stroke.a * strokeAlpha);
6923
+ }
6924
+ return outFragColor;
6925
+ }
6926
+ `;
6927
+
6928
+ let polygonShader = Q5.device.createShaderModule({
6929
+ label: 'polygonShader',
6930
+ code: $._polygonShaderCode
6931
+ });
6932
+
6933
+ let polygonVertStack = new Float32Array($._isGraphics ? 1000 : 1e7),
6934
+ polygonVertIdx = 0;
6935
+ let polyPtsStack = new Float32Array($._isGraphics ? 1000 : 1e7),
6936
+ polyPtsIdx = 0;
6937
+
6938
+ let polygonVertBuffLayout = {
6939
+ arrayStride: 32, // 8 floats * 4 bytes
6940
+ attributes: [
6941
+ { format: 'float32x2', offset: 0, shaderLocation: 0 },
6942
+ { format: 'float32', offset: 8, shaderLocation: 1 },
6943
+ { format: 'float32', offset: 12, shaderLocation: 2 },
6944
+ { format: 'float32', offset: 16, shaderLocation: 3 },
6945
+ { format: 'float32', offset: 20, shaderLocation: 4 },
6946
+ { format: 'float32', offset: 24, shaderLocation: 5 },
6947
+ { format: 'float32', offset: 28, shaderLocation: 6 }
6948
+ ]
6949
+ };
6950
+
6951
+ let polygonBindGroupLayout = Q5.device.createBindGroupLayout({
6952
+ entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } }]
6953
+ });
6954
+
6955
+ let polygonPipelineLayout = Q5.device.createPipelineLayout({
6956
+ label: 'polygonPipelineLayout',
6957
+ bindGroupLayouts: [mainLayout, polygonBindGroupLayout]
6958
+ });
6959
+
6960
+ $._pipelineConfigs[7] = {
6961
+ label: 'polygonPipeline',
6962
+ layout: polygonPipelineLayout,
6963
+ vertex: { module: polygonShader, entryPoint: 'vertexMain', buffers: [null, null, polygonVertBuffLayout] },
6964
+ fragment: {
6965
+ module: polygonShader,
6966
+ entryPoint: 'fragMain',
6967
+ targets: [{ format: 'bgra8unorm', blend: $.blendConfigs['source-over'] }]
6968
+ },
6969
+ primitive: { topology: 'triangle-list' },
6970
+ multisample: { count: 4 }
6971
+ };
6972
+ $._pipelines[7] = Q5.device.createRenderPipeline($._pipelineConfigs[7]);
6973
+
6974
+ const addPolygonVert = (x, y, start, count, fIdx, sIdx, sWeight, mIdx) => {
6975
+ let v = polygonVertStack,
6976
+ i = polygonVertIdx;
6977
+ v[i++] = x;
6978
+ v[i++] = y;
6979
+ v[i++] = start;
6980
+ v[i++] = count;
6981
+ v[i++] = fIdx;
6982
+ v[i++] = sIdx;
6983
+ v[i++] = sWeight;
6984
+ v[i++] = mIdx;
6985
+ polygonVertIdx = i;
6986
+ };
6987
+
6690
6988
  $.endShape = (close) => {
6691
6989
  if (curveVertices.length > 0) {
6692
- // duplicate start and end points if necessary
6693
6990
  let points = [...curveVertices];
6694
6991
  if (points.length < 4) {
6695
- // duplicate first and last points
6696
6992
  while (points.length < 4) {
6697
6993
  points.unshift(points[0]);
6698
6994
  points.push(points[points.length - 1]);
6699
6995
  }
6700
6996
  }
6701
-
6702
- // Use curveSegments to determine step size
6703
6997
  let step = 1 / curveSegments;
6704
-
6705
- // calculate catmull-rom spline curve points
6706
6998
  for (let i = 0; i < points.length - 3; i++) {
6707
- let p0 = points[i];
6708
- let p1 = points[i + 1];
6709
- let p2 = points[i + 2];
6710
- let p3 = points[i + 3];
6711
-
6712
- for (let t = 0; t <= 1; t += step) {
6713
- let t2 = t * t;
6714
- let t3 = t2 * t;
6715
-
6999
+ let p0 = points[i],
7000
+ p1 = points[i + 1],
7001
+ p2 = points[i + 2],
7002
+ p3 = points[i + 3];
7003
+ let startT = i === 0 ? 0 : 1;
7004
+ for (let j = startT; j <= curveSegments; j++) {
7005
+ let t = j / curveSegments;
7006
+ let t2 = t * t,
7007
+ t3 = t2 * t;
6716
7008
  let x =
6717
7009
  0.5 *
6718
7010
  (2 * p1.x +
6719
7011
  (-p0.x + p2.x) * t +
6720
7012
  (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 +
6721
7013
  (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3);
6722
-
6723
7014
  let y =
6724
7015
  0.5 *
6725
7016
  (2 * p1.y +
6726
7017
  (-p0.y + p2.y) * t +
6727
7018
  (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 +
6728
7019
  (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3);
6729
-
6730
- sv.push(x, y, fillIdx, matrixIdx);
7020
+ sv.push(x, y, fillIdx);
6731
7021
  shapeVertCount++;
6732
7022
  }
6733
7023
  }
6734
7024
  }
6735
7025
 
6736
7026
  if (!shapeVertCount) return;
6737
- if (shapeVertCount == 1) return $.point(sv[0], sv[1]);
6738
- if (shapeVertCount == 2) return $.line(sv[0], sv[1], sv[4], sv[5]);
6739
-
6740
- // close the shape if requested
6741
- if (close) {
6742
- let firstIndex = 0;
6743
- let lastIndex = (shapeVertCount - 1) * 4;
6744
-
6745
- let firstX = sv[firstIndex];
6746
- let firstY = sv[firstIndex + 1];
6747
- let lastX = sv[lastIndex];
6748
- let lastY = sv[lastIndex + 1];
6749
-
6750
- if (firstX !== lastX || firstY !== lastY) {
6751
- sv.push(firstX, firstY, sv[firstIndex + 2], sv[firstIndex + 3]);
6752
- shapeVertCount++;
6753
- }
7027
+ if (shapeVertCount == 1) {
7028
+ $.point(sv[0], sv[1]);
7029
+ shapeVertCount = 0;
7030
+ sv = [];
7031
+ curveVertices = [];
7032
+ return;
7033
+ }
7034
+ if (shapeVertCount == 2) {
7035
+ $.line(sv[0], sv[1], sv[3], sv[4]);
7036
+ shapeVertCount = 0;
7037
+ sv = [];
7038
+ curveVertices = [];
7039
+ return;
6754
7040
  }
6755
7041
 
6756
- if (doFill) {
6757
- if (shapeVertCount == 5) {
6758
- // for quads, draw two triangles
6759
- addVert(sv[0], sv[1], sv[2], sv[3]); // v0
6760
- addVert(sv[4], sv[5], sv[6], sv[7]); // v1
6761
- addVert(sv[12], sv[13], sv[14], sv[15]); // v3
6762
- addVert(sv[8], sv[9], sv[10], sv[11]); // v2
6763
- drawStack.push(shapesPL, 4);
6764
- } else {
6765
- // triangulate the shape
6766
- for (let i = 1; i < shapeVertCount - 1; i++) {
6767
- let v0 = 0;
6768
- let v1 = i * 4;
6769
- let v2 = (i + 1) * 4;
6770
-
6771
- addVert(sv[v0], sv[v0 + 1], sv[v0 + 2], sv[v0 + 3]);
6772
- addVert(sv[v1], sv[v1 + 1], sv[v1 + 2], sv[v1 + 3]);
6773
- addVert(sv[v2], sv[v2 + 1], sv[v2 + 2], sv[v2 + 3]);
6774
- }
6775
- drawStack.push(shapesPL, (shapeVertCount - 2) * 3);
6776
- }
7042
+ if (matrixDirty) saveMatrix();
7043
+ let ti = matrixIdx;
7044
+
7045
+ let isAutoClosed = false;
7046
+ let isClosedPath = false;
7047
+ let firstX = sv[0],
7048
+ firstY = sv[1];
7049
+ let lastX = sv[(shapeVertCount - 1) * 3],
7050
+ lastY = sv[(shapeVertCount - 1) * 3 + 1];
7051
+
7052
+ if (firstX === lastX && firstY === lastY) {
7053
+ isClosedPath = true;
7054
+ } else if (close || doFill) {
7055
+ sv.push(firstX, firstY, sv[2]);
7056
+ shapeVertCount++;
7057
+ isAutoClosed = !close;
7058
+ isClosedPath = true;
6777
7059
  }
6778
7060
 
6779
- if (doStroke) {
6780
- // draw lines between vertices
6781
- for (let i = 0; i < shapeVertCount - 1; i++) {
6782
- let v1 = i * 4;
6783
- let v2 = (i + 1) * 4;
6784
- $.line(sv[v1], sv[v1 + 1], sv[v2], sv[v2 + 1]);
7061
+ let runSDF = shapeVertCount >= 3 && doStroke;
7062
+
7063
+ if (runSDF) {
7064
+ let fi = doFill ? fillIdx : 0,
7065
+ si = strokeIdx;
7066
+
7067
+ let polyStart = polyPtsIdx / 4,
7068
+ polyCount = isClosedPath ? shapeVertCount - 1 : shapeVertCount;
7069
+ for (let i = 0; i < polyCount; i++) {
7070
+ polyPtsStack[polyPtsIdx++] = sv[i * 3];
7071
+ polyPtsStack[polyPtsIdx++] = sv[i * 3 + 1];
7072
+ polyPtsStack[polyPtsIdx++] = sv[i * 3 + 2];
7073
+ polyPtsStack[polyPtsIdx++] = ti;
7074
+ }
7075
+
7076
+ let minX = Infinity,
7077
+ minY = Infinity,
7078
+ maxX = -Infinity,
7079
+ maxY = -Infinity;
7080
+ for (let i = 0; i < shapeVertCount; i++) {
7081
+ let vx = sv[i * 3],
7082
+ vy = sv[i * 3 + 1];
7083
+ if (vx < minX) minX = vx;
7084
+ if (vx > maxX) maxX = vx;
7085
+ if (vy < minY) minY = vy;
7086
+ if (vy > maxY) maxY = vy;
7087
+ }
7088
+ let padding = sw * 0.5 + 1.0; // padding for stroke and AA
7089
+ minX -= padding;
7090
+ minY -= padding;
7091
+ maxX += padding;
7092
+ maxY += padding;
7093
+
7094
+ let passedCount = isClosedPath ? polyCount : -polyCount;
7095
+ addPolygonVert(minX, minY, polyStart, passedCount, fi, si, sw, ti);
7096
+ addPolygonVert(maxX, minY, polyStart, passedCount, fi, si, sw, ti);
7097
+ addPolygonVert(minX, maxY, polyStart, passedCount, fi, si, sw, ti);
7098
+
7099
+ addPolygonVert(maxX, minY, polyStart, passedCount, fi, si, sw, ti);
7100
+ addPolygonVert(maxX, maxY, polyStart, passedCount, fi, si, sw, ti);
7101
+ addPolygonVert(minX, maxY, polyStart, passedCount, fi, si, sw, ti);
7102
+
7103
+ drawStack.push(polygonPL, 6);
7104
+ } else {
7105
+ if (doFill) {
7106
+ if (shapeVertCount == 5) {
7107
+ // Quads
7108
+ addVert(sv[0], sv[1], sv[2], ti);
7109
+ addVert(sv[3], sv[4], sv[5], ti);
7110
+ addVert(sv[9], sv[10], sv[11], ti);
7111
+ addVert(sv[6], sv[7], sv[8], ti);
7112
+ drawStack.push(shapesPL, 4);
7113
+ } else {
7114
+ // Triangulation fan
7115
+ for (let i = 1; i < shapeVertCount - 1; i++) {
7116
+ let v0 = 0,
7117
+ v1 = i * 3,
7118
+ v2 = (i + 1) * 3;
7119
+ addVert(sv[v0], sv[v0 + 1], sv[v0 + 2], ti);
7120
+ addVert(sv[v1], sv[v1 + 1], sv[v1 + 2], ti);
7121
+ addVert(sv[v2], sv[v2 + 1], sv[v2 + 2], ti);
7122
+ }
7123
+ drawStack.push(shapesPL, (shapeVertCount - 2) * 3);
7124
+ }
7125
+ }
7126
+ if (doStroke) {
7127
+ let maxLines = isAutoClosed ? shapeVertCount - 2 : shapeVertCount - 1;
7128
+ for (let i = 0; i < maxLines; i++) {
7129
+ let v1 = i * 3,
7130
+ v2 = (i + 1) * 3;
7131
+ $.line(sv[v1], sv[v1 + 1], sv[v2], sv[v2 + 1]);
7132
+ }
6785
7133
  }
6786
- let v1 = (shapeVertCount - 1) * 4;
6787
- let v2 = 0;
6788
- if (close) $.line(sv[v1], sv[v1 + 1], sv[v2], sv[v2 + 1]);
6789
7134
  }
6790
7135
 
6791
- // reset for the next shape
6792
7136
  shapeVertCount = 0;
6793
7137
  sv = [];
6794
7138
  curveVertices = [];
6795
7139
  };
6796
7140
 
6797
- $.curve = (x1, y1, x2, y2, x3, y3, x4, y4) => {
6798
- $.beginShape();
6799
- $.curveVertex(x1, y1);
6800
- $.curveVertex(x2, y2);
6801
- $.curveVertex(x3, y3);
6802
- $.curveVertex(x4, y4);
6803
- $.endShape();
6804
- };
6805
-
6806
- $.bezier = (x1, y1, x2, y2, x3, y3, x4, y4) => {
6807
- $.beginShape();
6808
- $.vertex(x1, y1);
6809
- $.bezierVertex(x2, y2, x3, y3, x4, y4);
6810
- $.endShape();
6811
- };
6812
-
6813
7141
  $.triangle = (x1, y1, x2, y2, x3, y3) => {
6814
7142
  $.beginShape();
6815
7143
  $.vertex(x1, y1);
@@ -6827,21 +7155,6 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6827
7155
  $.endShape(true);
6828
7156
  };
6829
7157
 
6830
- function addQuad(x1, y1, x2, y2, x3, y3, x4, y4, ci, ti) {
6831
- addVert(x1, y1, ci, ti); // v0
6832
- addVert(x2, y2, ci, ti); // v1
6833
- addVert(x4, y4, ci, ti); // v3
6834
- addVert(x3, y3, ci, ti); // v2
6835
- drawStack.push(shapesPL, 4);
6836
- }
6837
-
6838
- $.plane = (x, y, w, h) => {
6839
- h ??= w;
6840
- let [l, r, t, b] = calcBox(x, y, w, h, 'center');
6841
- if (matrixDirty) saveMatrix();
6842
- addQuad(l, t, r, t, r, b, l, b, fillIdx, matrixIdx);
6843
- };
6844
-
6845
7158
  /* RECT */
6846
7159
 
6847
7160
  let rectPL = 5;
@@ -7134,7 +7447,21 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
7134
7447
  };
7135
7448
 
7136
7449
  $.line = (x1, y1, x2, y2) => {
7137
- if (doStroke) addCapsule(x1, y1, x2, y2, qsw, hsw, 0);
7450
+ if (!doStroke) return;
7451
+ if (matrixDirty) saveMatrix();
7452
+
7453
+ let dx = x2 - x1,
7454
+ dy = y2 - y1,
7455
+ sqLen = dx * dx + dy * dy;
7456
+
7457
+ if (sqLen === 0) return;
7458
+
7459
+ let len = Math.sqrt(sqLen),
7460
+ ratio = hsw / len,
7461
+ nx = -dy * ratio,
7462
+ ny = dx * ratio;
7463
+
7464
+ addQuad(x1 + nx, y1 + ny, x1 - nx, y1 - ny, x2 - nx, y2 - ny, x2 + nx, y2 + ny, strokeIdx, matrixIdx);
7138
7465
  };
7139
7466
 
7140
7467
  /* ELLIPSE */
@@ -8777,6 +9104,8 @@ fn fragMain(f : FragParams) -> @location(0) vec4f {
8777
9104
  transformsBuffer?.destroy();
8778
9105
  colorsBuffer?.destroy();
8779
9106
  shapesVertBuff?.destroy();
9107
+ polygonVertBuff?.destroy();
9108
+ polyPtsBuffer?.destroy();
8780
9109
  imgVertBuff?.destroy();
8781
9110
  charBuffer?.destroy();
8782
9111
  textBuffer?.destroy();
@@ -8856,6 +9185,12 @@ Q5.initWebGPU = async () => {
8856
9185
 
8857
9186
  Q5.device = device;
8858
9187
 
9188
+ if (typeof window == 'object') {
9189
+ window.addEventListener('pagehide', () => {
9190
+ if (device) device.destroy();
9191
+ });
9192
+ }
9193
+
8859
9194
  return true;
8860
9195
  };
8861
9196