q5 2.9.20 → 2.9.22

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.
package/q5.js CHANGED
@@ -9,7 +9,12 @@ function Q5(scope, parent, renderer) {
9
9
  let $ = this;
10
10
  $._q5 = true;
11
11
  $._parent = parent;
12
- $._renderer = renderer || 'q2d';
12
+ if (renderer == 'webgpu-fallback') {
13
+ $._webgpuFallback = true;
14
+ $._renderer = 'q2d';
15
+ } else {
16
+ $._renderer = renderer || 'q2d';
17
+ }
13
18
  $._preloadCount = 0;
14
19
 
15
20
  let autoLoaded = scope == 'auto';
@@ -172,6 +177,11 @@ function Q5(scope, parent, renderer) {
172
177
  delete Q5.Q5;
173
178
  }
174
179
 
180
+ if ($._webgpuFallback) {
181
+ $.colorMode('rgb', 1);
182
+ $._beginRender = () => $.translate($.canvas.hw, $.canvas.hh);
183
+ }
184
+
175
185
  for (let m of Q5.methods.init) {
176
186
  m.call($);
177
187
  }
@@ -452,7 +462,7 @@ Q5.modules.canvas = ($, q) => {
452
462
  return g;
453
463
  };
454
464
 
455
- $._save = async (data, name, ext) => {
465
+ async function saveFile(data, name, ext) {
456
466
  name = name || 'untitled';
457
467
  ext = ext || 'png';
458
468
  if (ext == 'jpg' || ext == 'png' || ext == 'webp') {
@@ -480,18 +490,19 @@ Q5.modules.canvas = ($, q) => {
480
490
  a.download = name + '.' + ext;
481
491
  a.click();
482
492
  URL.revokeObjectURL(a.href);
483
- };
493
+ }
494
+
484
495
  $.save = (a, b, c) => {
485
496
  if (!a || (typeof a == 'string' && (!b || (!c && b.length < 5)))) {
486
497
  c = b;
487
498
  b = a;
488
499
  a = $.canvas;
489
500
  }
490
- if (c) return $._save(a, b, c);
501
+ if (c) return saveFile(a, b, c);
491
502
  if (b) {
492
503
  b = b.split('.');
493
- $._save(a, b[0], b.at(-1));
494
- } else $._save(a);
504
+ saveFile(a, b[0], b.at(-1));
505
+ } else saveFile(a);
495
506
  };
496
507
 
497
508
  $._setCanvasSize = (w, h) => {
@@ -706,7 +717,7 @@ Q5.renderers.q2d.canvas = ($, q) => {
706
717
  }
707
718
  $.ctx.fillStyle = $._fill = c.toString();
708
719
  };
709
- $.noFill = () => ($._doFill = false);
720
+
710
721
  $.stroke = function (c) {
711
722
  $._doStroke = $._strokeSet = true;
712
723
  if (Q5.Color) {
@@ -718,13 +729,15 @@ Q5.renderers.q2d.canvas = ($, q) => {
718
729
  }
719
730
  $.ctx.strokeStyle = $._stroke = c.toString();
720
731
  };
732
+
721
733
  $.strokeWeight = (n) => {
722
734
  if (!n) $._doStroke = false;
723
735
  if ($._da) n *= $._da;
724
736
  $.ctx.lineWidth = $._strokeWeight = n || 0.0001;
725
737
  };
726
- $.noStroke = () => ($._doStroke = false);
727
738
 
739
+ $.noFill = () => ($._doFill = false);
740
+ $.noStroke = () => ($._doStroke = false);
728
741
  $.opacity = (a) => ($.ctx.globalAlpha = a);
729
742
 
730
743
  // DRAWING MATRIX
@@ -736,10 +749,12 @@ Q5.renderers.q2d.canvas = ($, q) => {
736
749
  }
737
750
  $.ctx.translate(x, y);
738
751
  };
752
+
739
753
  $.rotate = (r) => {
740
754
  if ($._angleMode) r = $.radians(r);
741
755
  $.ctx.rotate(r);
742
756
  };
757
+
743
758
  $.scale = (x, y) => {
744
759
  if (x.x) {
745
760
  y = x.y;
@@ -748,9 +763,11 @@ Q5.renderers.q2d.canvas = ($, q) => {
748
763
  y ??= x;
749
764
  $.ctx.scale(x, y);
750
765
  };
766
+
751
767
  $.applyMatrix = (a, b, c, d, e, f) => $.ctx.transform(a, b, c, d, e, f);
752
768
  $.shearX = (ang) => $.ctx.transform(1, 0, $.tan(ang), 1, 0, 0);
753
769
  $.shearY = (ang) => $.ctx.transform(1, $.tan(ang), 0, 1, 0, 0);
770
+
754
771
  $.resetMatrix = () => {
755
772
  if ($.ctx) {
756
773
  $.ctx.resetTransform();
@@ -842,12 +859,7 @@ Q5.renderers.q2d.drawing = ($) => {
842
859
 
843
860
  $.line = (x0, y0, x1, y1) => {
844
861
  if ($._doStroke) {
845
- if ($._da) {
846
- x0 *= $._da;
847
- y0 *= $._da;
848
- x1 *= $._da;
849
- y1 *= $._da;
850
- }
862
+ $._da && ((x0 *= $._da), (y0 *= $._da), (x1 *= $._da), (y1 *= $._da));
851
863
  $.ctx.beginPath();
852
864
  $.ctx.moveTo(x0, y0);
853
865
  $.ctx.lineTo(x1, y1);
@@ -887,6 +899,7 @@ Q5.renderers.q2d.drawing = ($) => {
887
899
  $.ctx.stroke();
888
900
  }
889
901
  }
902
+
890
903
  $.arc = (x, y, w, h, start, stop, mode) => {
891
904
  if (start == stop) return $.ellipse(x, y, w, h);
892
905
 
@@ -896,7 +909,6 @@ Q5.renderers.q2d.drawing = ($) => {
896
909
  w *= $._da;
897
910
  h *= $._da;
898
911
  }
899
-
900
912
  mode ??= $.PIE_OPEN;
901
913
 
902
914
  if ($._ellipseMode == $.CENTER) {
@@ -915,6 +927,7 @@ Q5.renderers.q2d.drawing = ($) => {
915
927
  $.ctx.ellipse(x, y, w / 2, h / 2, 0, 0, $.TAU);
916
928
  ink();
917
929
  }
930
+
918
931
  $.ellipse = (x, y, w, h) => {
919
932
  h ??= w;
920
933
  if ($._da) {
@@ -933,6 +946,7 @@ Q5.renderers.q2d.drawing = ($) => {
933
946
  ellipse((x + w) / 2, (y + h) / 2, w - x, h - y);
934
947
  }
935
948
  };
949
+
936
950
  $.circle = (x, y, d) => {
937
951
  if ($._ellipseMode == $.CENTER) {
938
952
  if ($._da) {
@@ -974,6 +988,7 @@ Q5.renderers.q2d.drawing = ($) => {
974
988
  $.ctx.rect(x, y, w, h);
975
989
  ink();
976
990
  }
991
+
977
992
  function roundedRect(x, y, w, h, tl, tr, br, bl) {
978
993
  if (tl === undefined) {
979
994
  return rect(x, y, w, h);
@@ -1006,30 +1021,34 @@ Q5.renderers.q2d.drawing = ($) => {
1006
1021
  roundedRect(x, y, w - x, h - y, tl, tr, br, bl);
1007
1022
  }
1008
1023
  };
1024
+
1009
1025
  $.square = (x, y, s, tl, tr, br, bl) => {
1010
1026
  return $.rect(x, y, s, s, tl, tr, br, bl);
1011
1027
  };
1012
1028
 
1013
1029
  $.beginShape = () => {
1014
- curveBuff = [];
1030
+ curveBuff.length = 0;
1015
1031
  $.ctx.beginPath();
1016
1032
  firstVertex = true;
1017
1033
  };
1034
+
1018
1035
  $.beginContour = () => {
1019
1036
  $.ctx.closePath();
1020
- curveBuff = [];
1037
+ curveBuff.length = 0;
1021
1038
  firstVertex = true;
1022
1039
  };
1040
+
1023
1041
  $.endContour = () => {
1024
- curveBuff = [];
1042
+ curveBuff.length = 0;
1025
1043
  firstVertex = true;
1026
1044
  };
1045
+
1027
1046
  $.vertex = (x, y) => {
1028
1047
  if ($._da) {
1029
1048
  x *= $._da;
1030
1049
  y *= $._da;
1031
1050
  }
1032
- curveBuff = [];
1051
+ curveBuff.length = 0;
1033
1052
  if (firstVertex) {
1034
1053
  $.ctx.moveTo(x, y);
1035
1054
  } else {
@@ -1037,6 +1056,7 @@ Q5.renderers.q2d.drawing = ($) => {
1037
1056
  }
1038
1057
  firstVertex = false;
1039
1058
  };
1059
+
1040
1060
  $.bezierVertex = (cp1x, cp1y, cp2x, cp2y, x, y) => {
1041
1061
  if ($._da) {
1042
1062
  cp1x *= $._da;
@@ -1046,9 +1066,10 @@ Q5.renderers.q2d.drawing = ($) => {
1046
1066
  x *= $._da;
1047
1067
  y *= $._da;
1048
1068
  }
1049
- curveBuff = [];
1069
+ curveBuff.length = 0;
1050
1070
  $.ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
1051
1071
  };
1072
+
1052
1073
  $.quadraticVertex = (cp1x, cp1y, x, y) => {
1053
1074
  if ($._da) {
1054
1075
  cp1x *= $._da;
@@ -1056,15 +1077,17 @@ Q5.renderers.q2d.drawing = ($) => {
1056
1077
  x *= $._da;
1057
1078
  y *= $._da;
1058
1079
  }
1059
- curveBuff = [];
1080
+ curveBuff.length = 0;
1060
1081
  $.ctx.quadraticCurveTo(cp1x, cp1y, x, y);
1061
1082
  };
1083
+
1062
1084
  $.bezier = (x1, y1, x2, y2, x3, y3, x4, y4) => {
1063
1085
  $.beginShape();
1064
1086
  $.vertex(x1, y1);
1065
1087
  $.bezierVertex(x2, y2, x3, y3, x4, y4);
1066
1088
  $.endShape();
1067
1089
  };
1090
+
1068
1091
  $.triangle = (x1, y1, x2, y2, x3, y3) => {
1069
1092
  $.beginShape();
1070
1093
  $.vertex(x1, y1);
@@ -1072,6 +1095,7 @@ Q5.renderers.q2d.drawing = ($) => {
1072
1095
  $.vertex(x3, y3);
1073
1096
  $.endShape($.CLOSE);
1074
1097
  };
1098
+
1075
1099
  $.quad = (x1, y1, x2, y2, x3, y3, x4, y4) => {
1076
1100
  $.beginShape();
1077
1101
  $.vertex(x1, y1);
@@ -1080,69 +1104,12 @@ Q5.renderers.q2d.drawing = ($) => {
1080
1104
  $.vertex(x4, y4);
1081
1105
  $.endShape($.CLOSE);
1082
1106
  };
1107
+
1083
1108
  $.endShape = (close) => {
1084
- curveBuff = [];
1109
+ curveBuff.length = 0;
1085
1110
  if (close) $.ctx.closePath();
1086
1111
  ink();
1087
1112
  };
1088
- function catmullRomSpline(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, numPts, alpha) {
1089
- function catmullromSplineGetT(t, p0x, p0y, p1x, p1y, alpha) {
1090
- let a = Math.pow(p1x - p0x, 2.0) + Math.pow(p1y - p0y, 2.0);
1091
- let b = Math.pow(a, alpha * 0.5);
1092
- return b + t;
1093
- }
1094
- let pts = [];
1095
-
1096
- let t0 = 0.0;
1097
- let t1 = catmullromSplineGetT(t0, p0x, p0y, p1x, p1y, alpha);
1098
- let t2 = catmullromSplineGetT(t1, p1x, p1y, p2x, p2y, alpha);
1099
- let t3 = catmullromSplineGetT(t2, p2x, p2y, p3x, p3y, alpha);
1100
-
1101
- for (let i = 0; i < numPts; i++) {
1102
- let t = t1 + (i / (numPts - 1)) * (t2 - t1);
1103
- let s = [
1104
- (t1 - t) / (t1 - t0),
1105
- (t - t0) / (t1 - t0),
1106
- (t2 - t) / (t2 - t1),
1107
- (t - t1) / (t2 - t1),
1108
- (t3 - t) / (t3 - t2),
1109
- (t - t2) / (t3 - t2),
1110
- (t2 - t) / (t2 - t0),
1111
- (t - t0) / (t2 - t0),
1112
- (t3 - t) / (t3 - t1),
1113
- (t - t1) / (t3 - t1)
1114
- ];
1115
- for (let j = 0; j < s.length; j += 2) {
1116
- if (isNaN(s[j])) {
1117
- s[j] = 1;
1118
- s[j + 1] = 0;
1119
- }
1120
- if (!isFinite(s[j])) {
1121
- if (s[j] > 0) {
1122
- s[j] = 1;
1123
- s[j + 1] = 0;
1124
- } else {
1125
- s[j] = 0;
1126
- s[j + 1] = 1;
1127
- }
1128
- }
1129
- }
1130
- let a1x = p0x * s[0] + p1x * s[1];
1131
- let a1y = p0y * s[0] + p1y * s[1];
1132
- let a2x = p1x * s[2] + p2x * s[3];
1133
- let a2y = p1y * s[2] + p2y * s[3];
1134
- let a3x = p2x * s[4] + p3x * s[5];
1135
- let a3y = p2y * s[4] + p3y * s[5];
1136
- let b1x = a1x * s[6] + a2x * s[7];
1137
- let b1y = a1y * s[6] + a2y * s[7];
1138
- let b2x = a2x * s[8] + a3x * s[9];
1139
- let b2y = a2y * s[8] + a3y * s[9];
1140
- let cx = b1x * s[2] + b2x * s[3];
1141
- let cy = b1y * s[2] + b2y * s[3];
1142
- pts.push([cx, cy]);
1143
- }
1144
- return pts;
1145
- }
1146
1113
 
1147
1114
  $.curveVertex = (x, y) => {
1148
1115
  if ($._da) {
@@ -1151,20 +1118,24 @@ Q5.renderers.q2d.drawing = ($) => {
1151
1118
  }
1152
1119
  curveBuff.push([x, y]);
1153
1120
  if (curveBuff.length < 4) return;
1154
- let p0 = curveBuff.at(-4);
1155
- let p1 = curveBuff.at(-3);
1156
- let p2 = curveBuff.at(-2);
1157
- let p3 = curveBuff.at(-1);
1158
- let pts = catmullRomSpline(...p0, ...p1, ...p2, ...p3, $._curveDetail, $._curveAlpha);
1159
- for (let i = 0; i < pts.length; i++) {
1160
- if (firstVertex) {
1161
- $.ctx.moveTo(...pts[i]);
1162
- } else {
1163
- $.ctx.lineTo(...pts[i]);
1164
- }
1121
+
1122
+ let p0 = curveBuff.at(-4),
1123
+ p1 = curveBuff.at(-3),
1124
+ p2 = curveBuff.at(-2),
1125
+ p3 = curveBuff.at(-1);
1126
+
1127
+ let cp1x = p1[0] + (p2[0] - p0[0]) / 6,
1128
+ cp1y = p1[1] + (p2[1] - p0[1]) / 6,
1129
+ cp2x = p2[0] - (p3[0] - p1[0]) / 6,
1130
+ cp2y = p2[1] - (p3[1] - p1[1]) / 6;
1131
+
1132
+ if (firstVertex) {
1133
+ $.ctx.moveTo(p1[0], p1[1]);
1165
1134
  firstVertex = false;
1166
1135
  }
1136
+ $.ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2[0], p2[1]);
1167
1137
  };
1138
+
1168
1139
  $.curve = (x1, y1, x2, y2, x3, y3, x4, y4) => {
1169
1140
  $.beginShape();
1170
1141
  $.curveVertex(x1, y1);
@@ -1173,6 +1144,7 @@ Q5.renderers.q2d.drawing = ($) => {
1173
1144
  $.curveVertex(x4, y4);
1174
1145
  $.endShape();
1175
1146
  };
1147
+
1176
1148
  $.curvePoint = (a, b, c, d, t) => {
1177
1149
  const t3 = t * t * t,
1178
1150
  t2 = t * t,
@@ -1182,6 +1154,7 @@ Q5.renderers.q2d.drawing = ($) => {
1182
1154
  f4 = 0.5 * t3 - 0.5 * t2;
1183
1155
  return a * f1 + b * f2 + c * f3 + d * f4;
1184
1156
  };
1157
+
1185
1158
  $.bezierPoint = (a, b, c, d, t) => {
1186
1159
  const adjustedT = 1 - t;
1187
1160
  return (
@@ -1191,6 +1164,7 @@ Q5.renderers.q2d.drawing = ($) => {
1191
1164
  Math.pow(t, 3) * d
1192
1165
  );
1193
1166
  };
1167
+
1194
1168
  $.curveTangent = (a, b, c, d, t) => {
1195
1169
  const t2 = t * t,
1196
1170
  f1 = (-3 * t2) / 2 + 2 * t - 0.5,
@@ -1199,6 +1173,7 @@ Q5.renderers.q2d.drawing = ($) => {
1199
1173
  f4 = (3 * t2) / 2 - t;
1200
1174
  return a * f1 + b * f2 + c * f3 + d * f4;
1201
1175
  };
1176
+
1202
1177
  $.bezierTangent = (a, b, c, d, t) => {
1203
1178
  const adjustedT = 1 - t;
1204
1179
  return (
@@ -1316,6 +1291,7 @@ Q5.renderers.q2d.image = ($, q) => {
1316
1291
  };
1317
1292
 
1318
1293
  $.imageMode = (mode) => ($._imageMode = mode);
1294
+
1319
1295
  $.image = (img, dx, dy, dw, dh, sx = 0, sy = 0, sw, sh) => {
1320
1296
  if (!img) return;
1321
1297
  let drawable = img?.canvas || img;
@@ -1557,6 +1533,7 @@ Q5.renderers.q2d.text = ($, q) => {
1557
1533
  fontMod = true;
1558
1534
  styleHash = -1;
1559
1535
  };
1536
+
1560
1537
  $.textSize = (x) => {
1561
1538
  if (x == undefined || x == $._textSize) return $._textSize;
1562
1539
  if ($._da) x *= $._da;
@@ -1568,12 +1545,14 @@ Q5.renderers.q2d.text = ($, q) => {
1568
1545
  leadDiff = leading - x;
1569
1546
  }
1570
1547
  };
1548
+
1571
1549
  $.textStyle = (x) => {
1572
1550
  if (!x || x == emphasis) return emphasis;
1573
1551
  emphasis = x;
1574
1552
  fontMod = true;
1575
1553
  styleHash = -1;
1576
1554
  };
1555
+
1577
1556
  $.textLeading = (x) => {
1578
1557
  leadingSet = true;
1579
1558
  if (x == undefined || x == leading) return leading;
@@ -1582,6 +1561,7 @@ Q5.renderers.q2d.text = ($, q) => {
1582
1561
  leadDiff = x - $._textSize;
1583
1562
  styleHash = -1;
1584
1563
  };
1564
+
1585
1565
  $.textAlign = (horiz, vert) => {
1586
1566
  $.ctx.textAlign = $._textAlign = horiz;
1587
1567
  if (vert) {
@@ -1611,6 +1591,7 @@ Q5.renderers.q2d.text = ($, q) => {
1611
1591
  if (enable !== undefined) useCache = enable;
1612
1592
  return useCache;
1613
1593
  };
1594
+
1614
1595
  $.createTextImage = (str, w, h) => {
1615
1596
  genTextImage = true;
1616
1597
  img = $.text(str, 0, 0, w, h);
@@ -1619,6 +1600,7 @@ Q5.renderers.q2d.text = ($, q) => {
1619
1600
  };
1620
1601
 
1621
1602
  let lines = [];
1603
+
1622
1604
  $.text = (str, x, y, w, h) => {
1623
1605
  if (str === undefined || (!$._doFill && !$._doStroke)) return;
1624
1606
  str = str.toString();
@@ -1744,6 +1726,7 @@ Q5.renderers.q2d.text = ($, q) => {
1744
1726
  $.textImage(img, x, y);
1745
1727
  }
1746
1728
  };
1729
+
1747
1730
  $.textImage = (img, x, y) => {
1748
1731
  if (typeof img == 'string') img = $.createTextImage(img);
1749
1732
 
@@ -1987,6 +1970,7 @@ Q5.Color = class {
1987
1970
  this._q5Color = true;
1988
1971
  }
1989
1972
  };
1973
+
1990
1974
  Q5.ColorOKLCH = class extends Q5.Color {
1991
1975
  constructor(l, c, h, a) {
1992
1976
  super();
@@ -1999,6 +1983,7 @@ Q5.ColorOKLCH = class extends Q5.Color {
1999
1983
  return `oklch(${this.l} ${this.c} ${this.h} / ${this.a})`;
2000
1984
  }
2001
1985
  };
1986
+
2002
1987
  Q5.ColorRGBA = class extends Q5.Color {
2003
1988
  constructor(r, g, b, a) {
2004
1989
  super();
@@ -2014,11 +1999,13 @@ Q5.ColorRGBA = class extends Q5.Color {
2014
1999
  return `color(srgb ${this.r} ${this.g} ${this.b} / ${this.a})`;
2015
2000
  }
2016
2001
  };
2002
+
2017
2003
  Q5.ColorRGBA_P3 = class extends Q5.ColorRGBA {
2018
2004
  toString() {
2019
2005
  return `color(display-p3 ${this.r} ${this.g} ${this.b} / ${this.a})`;
2020
2006
  }
2021
2007
  };
2008
+
2022
2009
  // legacy 8-bit (0-255) integer color format
2023
2010
  Q5.ColorRGBA_8 = class extends Q5.ColorRGBA {
2024
2011
  constructor(r, g, b, a) {
@@ -2043,6 +2030,7 @@ Q5.ColorRGBA_8 = class extends Q5.ColorRGBA {
2043
2030
  return `rgb(${this.r} ${this.g} ${this.b} / ${this.a / 255})`;
2044
2031
  }
2045
2032
  };
2033
+
2046
2034
  // p3 10-bit color in integer color format, for backwards compatibility
2047
2035
  Q5.ColorRGBA_P3_8 = class extends Q5.ColorRGBA {
2048
2036
  constructor(r, g, b, a) {
@@ -2251,6 +2239,7 @@ Q5.modules.input = ($, q) => {
2251
2239
  q.moveX = e.movementX;
2252
2240
  q.moveY = e.movementY;
2253
2241
  };
2242
+
2254
2243
  $._onmousedown = (e) => {
2255
2244
  $._startAudio();
2256
2245
  $._updateMouse(e);
@@ -2258,22 +2247,26 @@ Q5.modules.input = ($, q) => {
2258
2247
  q.mouseButton = mouseBtns[e.button];
2259
2248
  $.mousePressed(e);
2260
2249
  };
2250
+
2261
2251
  $._onmousemove = (e) => {
2262
2252
  $._updateMouse(e);
2263
2253
  if ($.mouseIsPressed) $.mouseDragged(e);
2264
2254
  else $.mouseMoved(e);
2265
2255
  };
2256
+
2266
2257
  $._onmouseup = (e) => {
2267
2258
  $._updateMouse(e);
2268
2259
  q.mouseIsPressed = false;
2269
2260
  $.mouseReleased(e);
2270
2261
  };
2262
+
2271
2263
  $._onclick = (e) => {
2272
2264
  $._updateMouse(e);
2273
2265
  q.mouseIsPressed = true;
2274
2266
  $.mouseClicked(e);
2275
2267
  q.mouseIsPressed = false;
2276
2268
  };
2269
+
2277
2270
  $._onwheel = (e) => {
2278
2271
  $._updateMouse(e);
2279
2272
  e.delta = e.deltaY;
@@ -2291,9 +2284,11 @@ Q5.modules.input = ($, q) => {
2291
2284
  }
2292
2285
  $.canvas.style.cursor = name + pfx;
2293
2286
  };
2287
+
2294
2288
  $.noCursor = () => {
2295
2289
  $.canvas.style.cursor = 'none';
2296
2290
  };
2291
+
2297
2292
  if (window) {
2298
2293
  $.requestPointerLock = document.body?.requestPointerLock;
2299
2294
  $.exitPointerLock = document.exitPointerLock;
@@ -2309,6 +2304,7 @@ Q5.modules.input = ($, q) => {
2309
2304
  $.keyPressed(e);
2310
2305
  if (e.key.length == 1) $.keyTyped(e);
2311
2306
  };
2307
+
2312
2308
  $._onkeyup = (e) => {
2313
2309
  q.keyIsPressed = false;
2314
2310
  q.key = e.key;
@@ -2316,6 +2312,7 @@ Q5.modules.input = ($, q) => {
2316
2312
  keysHeld[$.keyCode] = keysHeld[$.key.toLowerCase()] = false;
2317
2313
  $.keyReleased(e);
2318
2314
  };
2315
+
2319
2316
  $.keyIsDown = (v) => !!keysHeld[typeof v == 'string' ? v.toLowerCase() : v];
2320
2317
 
2321
2318
  function getTouchInfo(touch) {
@@ -2328,6 +2325,7 @@ Q5.modules.input = ($, q) => {
2328
2325
  id: touch.identifier
2329
2326
  };
2330
2327
  }
2328
+
2331
2329
  $._ontouchstart = (e) => {
2332
2330
  $._startAudio();
2333
2331
  q.touches = [...e.touches].map(getTouchInfo);
@@ -2340,6 +2338,7 @@ Q5.modules.input = ($, q) => {
2340
2338
  }
2341
2339
  if (!$.touchStarted(e)) e.preventDefault();
2342
2340
  };
2341
+
2343
2342
  $._ontouchmove = (e) => {
2344
2343
  q.touches = [...e.touches].map(getTouchInfo);
2345
2344
  if (!$._isTouchAware) {
@@ -2349,6 +2348,7 @@ Q5.modules.input = ($, q) => {
2349
2348
  }
2350
2349
  if (!$.touchMoved(e)) e.preventDefault();
2351
2350
  };
2351
+
2352
2352
  $._ontouchend = (e) => {
2353
2353
  q.touches = [...e.touches].map(getTouchInfo);
2354
2354
  if (!$._isTouchAware && !$.touches.length) {
@@ -2420,13 +2420,15 @@ Q5.modules.math = ($, q) => {
2420
2420
  return Math.min(Math.max(val, ostop), ostart);
2421
2421
  }
2422
2422
  };
2423
- $.lerp = (a, b, t) => a * (1 - t) + b * t;
2424
- $.constrain = (x, lo, hi) => Math.min(Math.max(x, lo), hi);
2423
+
2425
2424
  $.dist = function () {
2426
2425
  let a = arguments;
2427
2426
  if (a.length == 4) return Math.hypot(a[0] - a[2], a[1] - a[3]);
2428
2427
  else return Math.hypot(a[0] - a[3], a[1] - a[4], a[2] - a[5]);
2429
2428
  };
2429
+
2430
+ $.lerp = (a, b, t) => a * (1 - t) + b * t;
2431
+ $.constrain = (x, lo, hi) => Math.min(Math.max(x, lo), hi);
2430
2432
  $.norm = (value, start, stop) => $.map(value, start, stop, 0, 1);
2431
2433
  $.sq = (x) => x * x;
2432
2434
  $.fract = (x) => x - Math.floor(x);
@@ -2447,7 +2449,6 @@ Q5.modules.math = ($, q) => {
2447
2449
  let a = Math.atan(x);
2448
2450
  return !angleMode ? a : a * RADTODEG;
2449
2451
  };
2450
-
2451
2452
  $.atan2 = (y, x) => {
2452
2453
  let a = Math.atan2(y, x);
2453
2454
  return !angleMode ? a : a * RADTODEG;
@@ -2471,6 +2472,7 @@ Q5.modules.math = ($, q) => {
2471
2472
  }
2472
2473
  };
2473
2474
  }
2475
+
2474
2476
  function shr3() {
2475
2477
  let jsr, seed;
2476
2478
  let m = 4294967295;
@@ -2489,6 +2491,7 @@ Q5.modules.math = ($, q) => {
2489
2491
  }
2490
2492
  };
2491
2493
  }
2494
+
2492
2495
  let rng1 = shr3();
2493
2496
  rng1.setSeed();
2494
2497
 
@@ -2505,6 +2508,7 @@ Q5.modules.math = ($, q) => {
2505
2508
  return a[Math.trunc(a.length * rng1.rand())];
2506
2509
  }
2507
2510
  };
2511
+
2508
2512
  $.randomGenerator = (method) => {
2509
2513
  if (method == $.LCG) rng1 = lcg();
2510
2514
  else if (method == $.SHR3) rng1 = shr3();
@@ -2660,13 +2664,16 @@ Q5.modules.math = ($, q) => {
2660
2664
  q.Noise = Q5[mode[0].toUpperCase() + mode.slice(1) + 'Noise'];
2661
2665
  _noise = null;
2662
2666
  };
2667
+
2663
2668
  $.noiseSeed = (seed) => {
2664
2669
  _noise = new $.Noise(seed);
2665
2670
  };
2671
+
2666
2672
  $.noise = (x = 0, y = 0, z = 0) => {
2667
2673
  _noise ??= new $.Noise();
2668
2674
  return _noise.noise(x, y, z);
2669
2675
  };
2676
+
2670
2677
  $.noiseDetail = (lod, falloff) => {
2671
2678
  _noise ??= new $.Noise();
2672
2679
  if (lod > 0) _noise.octaves = lod;
@@ -2902,15 +2909,18 @@ Q5.Vector = class {
2902
2909
  this._cn = null;
2903
2910
  this._cnsq = null;
2904
2911
  }
2912
+
2905
2913
  set(x, y, z) {
2906
2914
  this.x = x?.x || x || 0;
2907
2915
  this.y = x?.y || y || 0;
2908
2916
  this.z = x?.z || z || 0;
2909
2917
  return this;
2910
2918
  }
2919
+
2911
2920
  copy() {
2912
2921
  return new Q5.Vector(this.x, this.y, this.z);
2913
2922
  }
2923
+
2914
2924
  _arg2v(x, y, z) {
2915
2925
  if (x?.x !== undefined) return x;
2916
2926
  if (y !== undefined) {
@@ -2918,10 +2928,12 @@ Q5.Vector = class {
2918
2928
  }
2919
2929
  return { x: x, y: x, z: x };
2920
2930
  }
2931
+
2921
2932
  _calcNorm() {
2922
2933
  this._cnsq = this.x * this.x + this.y * this.y + this.z * this.z;
2923
2934
  this._cn = Math.sqrt(this._cnsq);
2924
2935
  }
2936
+
2925
2937
  add() {
2926
2938
  let u = this._arg2v(...arguments);
2927
2939
  this.x += u.x;
@@ -2929,6 +2941,7 @@ Q5.Vector = class {
2929
2941
  this.z += u.z;
2930
2942
  return this;
2931
2943
  }
2944
+
2932
2945
  rem() {
2933
2946
  let u = this._arg2v(...arguments);
2934
2947
  this.x %= u.x;
@@ -2936,6 +2949,7 @@ Q5.Vector = class {
2936
2949
  this.z %= u.z;
2937
2950
  return this;
2938
2951
  }
2952
+
2939
2953
  sub() {
2940
2954
  let u = this._arg2v(...arguments);
2941
2955
  this.x -= u.x;
@@ -2943,6 +2957,7 @@ Q5.Vector = class {
2943
2957
  this.z -= u.z;
2944
2958
  return this;
2945
2959
  }
2960
+
2946
2961
  mult() {
2947
2962
  let u = this._arg2v(...arguments);
2948
2963
  this.x *= u.x;
@@ -2950,6 +2965,7 @@ Q5.Vector = class {
2950
2965
  this.z *= u.z;
2951
2966
  return this;
2952
2967
  }
2968
+
2953
2969
  div() {
2954
2970
  let u = this._arg2v(...arguments);
2955
2971
  if (u.x) this.x /= u.x;
@@ -2960,18 +2976,22 @@ Q5.Vector = class {
2960
2976
  else this.z = 0;
2961
2977
  return this;
2962
2978
  }
2979
+
2963
2980
  mag() {
2964
2981
  this._calcNorm();
2965
2982
  return this._cn;
2966
2983
  }
2984
+
2967
2985
  magSq() {
2968
2986
  this._calcNorm();
2969
2987
  return this._cnsq;
2970
2988
  }
2989
+
2971
2990
  dot() {
2972
2991
  let u = this._arg2v(...arguments);
2973
2992
  return this.x * u.x + this.y * u.y + this.z * u.z;
2974
2993
  }
2994
+
2975
2995
  dist() {
2976
2996
  let u = this._arg2v(...arguments);
2977
2997
  let x = this.x - u.x;
@@ -2979,6 +2999,7 @@ Q5.Vector = class {
2979
2999
  let z = this.z - u.z;
2980
3000
  return Math.sqrt(x * x + y * y + z * z);
2981
3001
  }
3002
+
2982
3003
  cross() {
2983
3004
  let u = this._arg2v(...arguments);
2984
3005
  let x = this.y * u.z - this.z * u.y;
@@ -2989,6 +3010,7 @@ Q5.Vector = class {
2989
3010
  this.z = z;
2990
3011
  return this;
2991
3012
  }
3013
+
2992
3014
  normalize() {
2993
3015
  this._calcNorm();
2994
3016
  let n = this._cn;
@@ -3001,6 +3023,7 @@ Q5.Vector = class {
3001
3023
  this._cnsq = 1;
3002
3024
  return this;
3003
3025
  }
3026
+
3004
3027
  limit(m) {
3005
3028
  this._calcNorm();
3006
3029
  let n = this._cn;
@@ -3014,6 +3037,7 @@ Q5.Vector = class {
3014
3037
  }
3015
3038
  return this;
3016
3039
  }
3040
+
3017
3041
  setMag(m) {
3018
3042
  this._calcNorm();
3019
3043
  let n = this._cn;
@@ -3025,15 +3049,18 @@ Q5.Vector = class {
3025
3049
  this._cnsq = m * m;
3026
3050
  return this;
3027
3051
  }
3052
+
3028
3053
  heading() {
3029
3054
  return this._$.atan2(this.y, this.x);
3030
3055
  }
3056
+
3031
3057
  setHeading(ang) {
3032
3058
  let mag = this.mag();
3033
3059
  this.x = mag * this._$.cos(ang);
3034
3060
  this.y = mag * this._$.sin(ang);
3035
3061
  return this;
3036
3062
  }
3063
+
3037
3064
  rotate(ang) {
3038
3065
  let costh = this._$.cos(ang);
3039
3066
  let sinth = this._$.sin(ang);
@@ -3043,12 +3070,14 @@ Q5.Vector = class {
3043
3070
  this.y = vy;
3044
3071
  return this;
3045
3072
  }
3073
+
3046
3074
  angleBetween() {
3047
3075
  let u = this._arg2v(...arguments);
3048
3076
  let o = Q5.Vector.cross(this, u);
3049
3077
  let ang = this._$.atan2(o.mag(), this.dot(u));
3050
3078
  return ang * Math.sign(o.z || 1);
3051
3079
  }
3080
+
3052
3081
  lerp() {
3053
3082
  let args = [...arguments];
3054
3083
  let amt = args.at(-1);
@@ -3059,6 +3088,7 @@ Q5.Vector = class {
3059
3088
  this.z += (u.z - this.z) * amt;
3060
3089
  return this;
3061
3090
  }
3091
+
3062
3092
  slerp() {
3063
3093
  let args = [...arguments];
3064
3094
  let amt = args.at(-1);
@@ -3097,17 +3127,21 @@ Q5.Vector = class {
3097
3127
  this.z = this.z * cosMultiplier + ey.z * sinMultiplier;
3098
3128
  return this;
3099
3129
  }
3130
+
3100
3131
  reflect(n) {
3101
3132
  n.normalize();
3102
3133
  return this.sub(n.mult(2 * this.dot(n)));
3103
3134
  }
3135
+
3104
3136
  array() {
3105
3137
  return [this.x, this.y, this.z];
3106
3138
  }
3139
+
3107
3140
  equals(u, epsilon) {
3108
3141
  epsilon ??= Number.EPSILON || 0;
3109
3142
  return Math.abs(u.x - this.x) < epsilon && Math.abs(u.y - this.y) < epsilon && Math.abs(u.z - this.z) < epsilon;
3110
3143
  }
3144
+
3111
3145
  fromAngle(th, l) {
3112
3146
  if (l === undefined) l = 1;
3113
3147
  this._cn = l;
@@ -3117,6 +3151,7 @@ Q5.Vector = class {
3117
3151
  this.z = 0;
3118
3152
  return this;
3119
3153
  }
3154
+
3120
3155
  fromAngles(th, ph, l) {
3121
3156
  if (l === undefined) l = 1;
3122
3157
  this._cn = l;
@@ -3130,18 +3165,22 @@ Q5.Vector = class {
3130
3165
  this.z = l * sinth * cosph;
3131
3166
  return this;
3132
3167
  }
3168
+
3133
3169
  random2D() {
3134
3170
  this._cn = this._cnsq = 1;
3135
3171
  return this.fromAngle(Math.random() * Math.PI * 2);
3136
3172
  }
3173
+
3137
3174
  random3D() {
3138
3175
  this._cn = this._cnsq = 1;
3139
3176
  return this.fromAngles(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2);
3140
3177
  }
3178
+
3141
3179
  toString() {
3142
3180
  return `[${this.x}, ${this.y}, ${this.z}]`;
3143
3181
  }
3144
3182
  };
3183
+
3145
3184
  Q5.Vector.add = (v, u) => v.copy().add(u);
3146
3185
  Q5.Vector.cross = (v, u) => v.copy().cross(u);
3147
3186
  Q5.Vector.dist = (v, u) => Math.hypot(v.x - u.x, v.y - u.y, v.z - u.z);
@@ -3158,6 +3197,7 @@ Q5.Vector.mult = (v, u) => v.copy().mult(u);
3158
3197
  Q5.Vector.normalize = (v) => v.copy().normalize();
3159
3198
  Q5.Vector.rem = (v, u) => v.copy().rem(u);
3160
3199
  Q5.Vector.sub = (v, u) => v.copy().sub(u);
3200
+
3161
3201
  for (let k of ['fromAngle', 'fromAngles', 'random2D', 'random3D']) {
3162
3202
  Q5.Vector[k] = (u, v, t) => new Q5.Vector()[k](u, v, t);
3163
3203
  }
@@ -3477,6 +3517,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3477
3517
  if ($._matrixDirty) $._saveMatrix();
3478
3518
  $._transformIndexStack.push($._transformIndex);
3479
3519
  };
3520
+
3480
3521
  $.popMatrix = () => {
3481
3522
  if (!$._transformIndexStack.length) {
3482
3523
  return console.warn('Matrix index stack is empty!');
@@ -3492,6 +3533,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3492
3533
  $.pushMatrix();
3493
3534
  $.pushStyles();
3494
3535
  };
3536
+
3495
3537
  $.pop = () => {
3496
3538
  $.popMatrix();
3497
3539
  $.popStyles();
@@ -3573,6 +3615,7 @@ Q5.renderers.webgpu.canvas = ($, q) => {
3573
3615
  }
3574
3616
 
3575
3617
  $._blendMode = 'normal';
3618
+
3576
3619
  $.blendMode = (mode) => {
3577
3620
  if (mode == $._blendMode) return;
3578
3621
  if (mode == 'source-over') mode = 'normal';
@@ -3717,10 +3760,7 @@ Q5.initWebGPU = async () => {
3717
3760
  Q5.webgpu = async function (scope, parent) {
3718
3761
  if (!scope || scope == 'global') Q5._hasGlobal = true;
3719
3762
  if (!(await Q5.initWebGPU())) {
3720
- let q = new Q5(scope, parent);
3721
- q.colorMode('rgb', 1);
3722
- q._beginRender = () => q.translate(q.canvas.hw, q.canvas.hh);
3723
- return q;
3763
+ return new Q5(scope, parent, 'webgpu-fallback');
3724
3764
  }
3725
3765
  return new Q5(scope, parent, 'webgpu');
3726
3766
  };
@@ -4048,10 +4088,12 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
4048
4088
 
4049
4089
  let shapeVertCount;
4050
4090
  let sv = []; // shape vertices
4091
+ let curveVertices = []; // curve vertices
4051
4092
 
4052
4093
  $.beginShape = () => {
4053
4094
  shapeVertCount = 0;
4054
4095
  sv = [];
4096
+ curveVertices = [];
4055
4097
  };
4056
4098
 
4057
4099
  $.vertex = (x, y) => {
@@ -4060,12 +4102,58 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
4060
4102
  shapeVertCount++;
4061
4103
  };
4062
4104
 
4105
+ $.curveVertex = (x, y) => {
4106
+ if ($._matrixDirty) $._saveMatrix();
4107
+ curveVertices.push({ x: x, y: -y });
4108
+ };
4109
+
4063
4110
  $.endShape = (close) => {
4111
+ if (curveVertices.length > 0) {
4112
+ // Duplicate start and end points if necessary
4113
+ let points = [...curveVertices];
4114
+ if (points.length < 4) {
4115
+ // Duplicate first and last points
4116
+ while (points.length < 4) {
4117
+ points.unshift(points[0]);
4118
+ points.push(points[points.length - 1]);
4119
+ }
4120
+ }
4121
+
4122
+ for (let i = 0; i < points.length - 3; i++) {
4123
+ let p0 = points[i];
4124
+ let p1 = points[i + 1];
4125
+ let p2 = points[i + 2];
4126
+ let p3 = points[i + 3];
4127
+
4128
+ for (let t = 0; t <= 1; t += 0.1) {
4129
+ let t2 = t * t;
4130
+ let t3 = t2 * t;
4131
+
4132
+ let x =
4133
+ 0.5 *
4134
+ (2 * p1.x +
4135
+ (-p0.x + p2.x) * t +
4136
+ (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 +
4137
+ (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3);
4138
+
4139
+ let y =
4140
+ 0.5 *
4141
+ (2 * p1.y +
4142
+ (-p0.y + p2.y) * t +
4143
+ (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 +
4144
+ (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3);
4145
+
4146
+ sv.push(x, y, $._fill, $._transformIndex);
4147
+ shapeVertCount++;
4148
+ }
4149
+ }
4150
+ }
4151
+
4064
4152
  if (shapeVertCount < 3) {
4065
4153
  throw new Error('A shape must have at least 3 vertices.');
4066
4154
  }
4067
4155
 
4068
- // close the stroke if required
4156
+ // Close the shape if needed
4069
4157
  if (close) {
4070
4158
  let firstIndex = 0;
4071
4159
  let lastIndex = (shapeVertCount - 1) * 4;
@@ -4076,14 +4164,13 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
4076
4164
  let lastY = sv[lastIndex + 1];
4077
4165
 
4078
4166
  if (firstX !== lastX || firstY !== lastY) {
4079
- // append the first vertex to close the shape
4080
4167
  sv.push(firstX, firstY, sv[firstIndex + 2], sv[firstIndex + 3]);
4081
4168
  shapeVertCount++;
4082
4169
  }
4083
4170
  }
4084
4171
 
4085
4172
  if ($._doFill) {
4086
- // triangulate the shape
4173
+ // Triangulate the shape
4087
4174
  for (let i = 1; i < shapeVertCount - 1; i++) {
4088
4175
  let v0 = 0;
4089
4176
  let v1 = i * 4;
@@ -4097,7 +4184,7 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
4097
4184
  }
4098
4185
 
4099
4186
  if ($._doStroke) {
4100
- // draw lines between vertices
4187
+ // Draw lines between vertices
4101
4188
  for (let i = 0; i < shapeVertCount - 1; i++) {
4102
4189
  let v1 = i * 4;
4103
4190
  let v2 = (i + 1) * 4;
@@ -4110,9 +4197,10 @@ fn fragmentMain(@location(0) color: vec4f) -> @location(0) vec4f {
4110
4197
  }
4111
4198
  }
4112
4199
 
4113
- // reset for the next shape
4200
+ // Reset for the next shape
4114
4201
  shapeVertCount = 0;
4115
4202
  sv = [];
4203
+ curveVertices = [];
4116
4204
  };
4117
4205
 
4118
4206
  $.triangle = (x1, y1, x2, y2, x3, y3) => {
@@ -4537,6 +4625,7 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
4537
4625
  mipmapFilter: 'linear',
4538
4626
  maxAnisotropy: 16
4539
4627
  });
4628
+
4540
4629
  let fontBindGroupLayout = Q5.device.createBindGroupLayout({
4541
4630
  label: 'MSDF font group layout',
4542
4631
  entries: [
@@ -4574,6 +4663,7 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
4574
4663
  primitive: { topology: 'triangle-strip', stripIndexFormat: 'uint32' },
4575
4664
  multisample: { count: 4 }
4576
4665
  };
4666
+
4577
4667
  $._pipelines[2] = Q5.device.createRenderPipeline($._pipelineConfigs[2]);
4578
4668
 
4579
4669
  class MsdfFont {
@@ -4722,6 +4812,7 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
4722
4812
  $.textFont = (fontName) => {
4723
4813
  $._font = fonts[fontName];
4724
4814
  };
4815
+
4725
4816
  $.textSize = (size) => {
4726
4817
  $._textSize = size;
4727
4818
  if (!leadingSet) {
@@ -4729,12 +4820,14 @@ fn fragmentMain(input : VertexOutput) -> @location(0) vec4f {
4729
4820
  leadDiff = leading - size;
4730
4821
  }
4731
4822
  };
4823
+
4732
4824
  $.textLeading = (lineHeight) => {
4733
4825
  $._font.lineHeight = leading = lineHeight;
4734
4826
  leadDiff = leading - $._textSize;
4735
4827
  leadPercent = leading / $._textSize;
4736
4828
  leadingSet = true;
4737
4829
  };
4830
+
4738
4831
  $.textAlign = (horiz, vert) => {
4739
4832
  $._textAlign = horiz;
4740
4833
  if (vert) $._textBaseline = vert;