q5 2.0.17 → 2.1.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.
package/q5-webgpu.js ADDED
@@ -0,0 +1,3308 @@
1
+ /**
2
+ * q5.js
3
+ * @version 2.1
4
+ * @author quinton-ashley, Tezumie, and LingDong-
5
+ * @license LGPL-3.0
6
+ * @class Q5
7
+ */
8
+ function Q5(scope, parent, renderer) {
9
+ let $ = this;
10
+ $._q5 = true;
11
+ $._parent = parent;
12
+ $._renderer = renderer || 'q2d';
13
+ $._preloadCount = 0;
14
+
15
+ scope ??= 'global';
16
+ if (scope == 'auto') {
17
+ if (!(window.setup || window.draw)) return;
18
+ scope = 'global';
19
+ }
20
+ $._scope = scope;
21
+ let globalScope;
22
+ if (scope == 'global') {
23
+ Q5._hasGlobal = $._isGlobal = true;
24
+ globalScope = !Q5._nodejs ? window : global;
25
+ }
26
+
27
+ let q = new Proxy($, {
28
+ set: (t, p, v) => {
29
+ $[p] = v;
30
+ if ($._isGlobal) globalScope[p] = v;
31
+ return true;
32
+ }
33
+ });
34
+
35
+ $.canvas = $.ctx = $.drawingContext = null;
36
+ $.pixels = [];
37
+ let looper = null;
38
+
39
+ $.frameCount = 0;
40
+ $.deltaTime = 16;
41
+ $._targetFrameRate = 0;
42
+ $._targetFrameDuration = 16.666666666666668;
43
+ $._frameRate = $._fps = 60;
44
+ $._loop = true;
45
+ $._hooks = {
46
+ postCanvas: [],
47
+ preRender: []
48
+ };
49
+
50
+ let millisStart = 0;
51
+ $.millis = () => performance.now() - millisStart;
52
+
53
+ $.noCanvas = () => {
54
+ if ($.canvas?.remove) $.canvas.remove();
55
+ $.canvas = 0;
56
+ q.ctx = q.drawingContext = 0;
57
+ };
58
+
59
+ if (window) {
60
+ $.windowWidth = window.innerWidth;
61
+ $.windowHeight = window.innerHeight;
62
+ $.deviceOrientation = window.screen?.orientation?.type;
63
+ }
64
+
65
+ $._incrementPreload = () => q._preloadCount++;
66
+ $._decrementPreload = () => q._preloadCount--;
67
+
68
+ $._draw = (timestamp) => {
69
+ let ts = timestamp || performance.now();
70
+ $._lastFrameTime ??= ts - $._targetFrameDuration;
71
+
72
+ if ($._shouldResize) {
73
+ $.windowResized();
74
+ $._shouldResize = false;
75
+ }
76
+
77
+ if ($._loop) looper = raf($._draw);
78
+ else if ($.frameCount && !$._redraw) return;
79
+
80
+ if (looper && $.frameCount) {
81
+ let time_since_last = ts - $._lastFrameTime;
82
+ if (time_since_last < $._targetFrameDuration - 4) return;
83
+ }
84
+ q.deltaTime = ts - $._lastFrameTime;
85
+ $._frameRate = 1000 / $.deltaTime;
86
+ q.frameCount++;
87
+ let pre = performance.now();
88
+ if ($._beginRender) $._beginRender();
89
+ if ($.ctx) $.resetMatrix();
90
+ for (let m of Q5.methods.pre) m.call($);
91
+ $.draw();
92
+ if ($._render) $._render();
93
+ for (let m of Q5.methods.post) m.call($);
94
+ if ($._finishRender) $._finishRender();
95
+ q.pmouseX = $.mouseX;
96
+ q.pmouseY = $.mouseY;
97
+ $._lastFrameTime = ts;
98
+ let post = performance.now();
99
+ $._fps = Math.round(1000 / (post - pre));
100
+ };
101
+ $.noLoop = () => {
102
+ $._loop = false;
103
+ looper = null;
104
+ };
105
+ $.loop = () => {
106
+ $._loop = true;
107
+ if (looper == null) $._draw();
108
+ };
109
+ $.isLooping = () => $._loop;
110
+ $.redraw = (n = 1) => {
111
+ $._redraw = true;
112
+ for (let i = 0; i < n; i++) {
113
+ $._draw();
114
+ }
115
+ $._redraw = false;
116
+ };
117
+ $.remove = () => {
118
+ $.noLoop();
119
+ $.canvas.remove();
120
+ };
121
+
122
+ $.frameRate = (hz) => {
123
+ if (hz) {
124
+ $._targetFrameRate = hz;
125
+ $._targetFrameDuration = 1000 / hz;
126
+ }
127
+ return $._frameRate;
128
+ };
129
+ $.getTargetFrameRate = () => $._targetFrameRate;
130
+ $.getFPS = () => $._fps;
131
+
132
+ $.Element = function (a) {
133
+ this.elt = a;
134
+ };
135
+ $._elements = [];
136
+
137
+ $.TWO_PI = $.TAU = Math.PI * 2;
138
+
139
+ $.log = $.print = console.log;
140
+ $.describe = () => {};
141
+
142
+ for (let m in Q5.modules) {
143
+ Q5.modules[m]($, q);
144
+ }
145
+
146
+ let r = Q5.renderers[$._renderer];
147
+ for (let m in r) {
148
+ r[m]($, q);
149
+ }
150
+
151
+ // INIT
152
+
153
+ for (let k in Q5) {
154
+ if (k[1] != '_' && k[1] == k[1].toUpperCase()) {
155
+ $[k] = Q5[k];
156
+ }
157
+ }
158
+
159
+ if (scope == 'graphics') return;
160
+
161
+ if (scope == 'global') {
162
+ Object.assign(Q5, $);
163
+ delete Q5.Q5;
164
+ }
165
+
166
+ for (let m of Q5.methods.init) {
167
+ m.call($);
168
+ }
169
+
170
+ for (let [n, fn] of Object.entries(Q5.prototype)) {
171
+ if (n[0] != '_' && typeof $[n] == 'function') $[n] = fn.bind($);
172
+ }
173
+
174
+ if (scope == 'global') {
175
+ let props = Object.getOwnPropertyNames($);
176
+ for (let p of props) {
177
+ if (p[0] != '_') globalScope[p] = $[p];
178
+ }
179
+ }
180
+
181
+ if (typeof scope == 'function') scope($);
182
+
183
+ Q5._instanceCount++;
184
+
185
+ let raf =
186
+ window.requestAnimationFrame ||
187
+ function (cb) {
188
+ const idealFrameTime = $._lastFrameTime + $._targetFrameDuration;
189
+ return setTimeout(() => {
190
+ cb(idealFrameTime);
191
+ }, idealFrameTime - performance.now());
192
+ };
193
+
194
+ let t = globalScope || $;
195
+ $._isTouchAware = t.touchStarted || t.touchMoved || t.mouseReleased;
196
+ let preloadDefined = t.preload;
197
+ let userFns = [
198
+ 'setup',
199
+ 'draw',
200
+ 'preload',
201
+ 'mouseMoved',
202
+ 'mousePressed',
203
+ 'mouseReleased',
204
+ 'mouseDragged',
205
+ 'mouseClicked',
206
+ 'keyPressed',
207
+ 'keyReleased',
208
+ 'keyTyped',
209
+ 'touchStarted',
210
+ 'touchMoved',
211
+ 'touchEnded',
212
+ 'windowResized'
213
+ ];
214
+ for (let k of userFns) {
215
+ if (!t[k]) $[k] = () => {};
216
+ else if ($._isGlobal) {
217
+ $[k] = () => {
218
+ try {
219
+ return t[k]();
220
+ } catch (e) {
221
+ if ($._aiErrorAssistance) $._aiErrorAssistance(e);
222
+ else console.error(e);
223
+ }
224
+ };
225
+ }
226
+ }
227
+
228
+ if (!($.setup || $.draw)) return;
229
+
230
+ async function _start() {
231
+ $._startDone = true;
232
+ if ($._preloadCount > 0) return raf(_start);
233
+ millisStart = performance.now();
234
+ await $.setup();
235
+ if ($.frameCount) return;
236
+ if ($.ctx === null) $.createCanvas(100, 100);
237
+ $._setupDone = true;
238
+ if ($.ctx) $.resetMatrix();
239
+ raf($._draw);
240
+ }
241
+
242
+ if ((arguments.length && scope != 'namespace') || preloadDefined) {
243
+ $.preload();
244
+ _start();
245
+ } else {
246
+ t.preload = $.preload = () => {
247
+ if (!$._startDone) _start();
248
+ };
249
+ setTimeout($.preload, 32);
250
+ }
251
+ }
252
+
253
+ Q5.renderers = {};
254
+ Q5.modules = {};
255
+
256
+ Q5._nodejs = typeof process == 'object';
257
+
258
+ Q5._instanceCount = 0;
259
+ Q5._friendlyError = (msg, func) => {
260
+ throw Error(func + ': ' + msg);
261
+ };
262
+ Q5._validateParameters = () => true;
263
+
264
+ Q5.methods = {
265
+ init: [],
266
+ pre: [],
267
+ post: [],
268
+ remove: []
269
+ };
270
+ Q5.prototype.registerMethod = (m, fn) => Q5.methods[m].push(fn);
271
+ Q5.prototype.registerPreloadMethod = (n, fn) => (Q5.prototype[n] = fn[n]);
272
+
273
+ if (Q5._nodejs) global.p5 ??= global.Q5 = Q5;
274
+ else if (typeof window == 'object') window.p5 ??= window.Q5 = Q5;
275
+ else global.window = 0;
276
+
277
+ if (typeof document == 'object') {
278
+ document.addEventListener('DOMContentLoaded', () => {
279
+ if (!Q5._hasGlobal) new Q5('auto');
280
+ });
281
+ }
282
+ Q5.modules.canvas = ($, q) => {
283
+ $._OffscreenCanvas =
284
+ window.OffscreenCanvas ||
285
+ function () {
286
+ return document.createElement('canvas');
287
+ };
288
+
289
+ if (Q5._nodejs) {
290
+ if (Q5._createNodeJSCanvas) {
291
+ q.canvas = Q5._createNodeJSCanvas(100, 100);
292
+ }
293
+ } else if ($._scope == 'image' || $._scope == 'graphics') {
294
+ q.canvas = new $._OffscreenCanvas(100, 100);
295
+ }
296
+
297
+ if (!$.canvas) {
298
+ if (typeof document == 'object') {
299
+ q.canvas = document.createElement('canvas');
300
+ $.canvas.id = 'q5Canvas' + Q5._instanceCount;
301
+ $.canvas.classList.add('q5Canvas');
302
+ } else $.noCanvas();
303
+ }
304
+
305
+ let c = $.canvas;
306
+ c.width = $.width = 100;
307
+ c.height = $.height = 100;
308
+ if ($._scope != 'image') {
309
+ c.renderer = $._renderer;
310
+ c[$._renderer] = true;
311
+ }
312
+ $._pixelDensity = 1;
313
+
314
+ $._adjustDisplay = () => {
315
+ if (c.style) {
316
+ c.style.width = c.w + 'px';
317
+ c.style.height = c.h + 'px';
318
+ }
319
+ };
320
+
321
+ $.createCanvas = function (w, h, options) {
322
+ options ??= arguments[3];
323
+
324
+ let opt = Object.assign({}, Q5.canvasOptions);
325
+ if (typeof options == 'object') Object.assign(opt, options);
326
+
327
+ if ($._scope != 'image') {
328
+ let pd = $.displayDensity();
329
+ if ($._scope == 'graphics') pd = this._pixelDensity;
330
+ else if (window.IntersectionObserver) {
331
+ new IntersectionObserver((e) => {
332
+ c.visible = e[0].isIntersecting;
333
+ }).observe(c);
334
+ }
335
+ $._pixelDensity = Math.ceil(pd);
336
+ }
337
+
338
+ $._setCanvasSize(w, h);
339
+
340
+ Object.assign(c, opt);
341
+ let rend = $._createCanvas(c.w, c.h, opt);
342
+
343
+ if ($._hooks) {
344
+ for (let m of $._hooks.postCanvas) m();
345
+ }
346
+ return rend;
347
+ };
348
+
349
+ $._save = async (data, name, ext) => {
350
+ name = name || 'untitled';
351
+ ext = ext || 'png';
352
+ if (ext == 'jpg' || ext == 'png' || ext == 'webp') {
353
+ if (data instanceof OffscreenCanvas) {
354
+ const blob = await data.convertToBlob({ type: 'image/' + ext });
355
+ data = await new Promise((resolve) => {
356
+ const reader = new FileReader();
357
+ reader.onloadend = () => resolve(reader.result);
358
+ reader.readAsDataURL(blob);
359
+ });
360
+ } else {
361
+ data = data.toDataURL('image/' + ext);
362
+ }
363
+ } else {
364
+ let type = 'text/plain';
365
+ if (ext == 'json') {
366
+ if (typeof data != 'string') data = JSON.stringify(data);
367
+ type = 'text/json';
368
+ }
369
+ data = new Blob([data], { type });
370
+ data = URL.createObjectURL(data);
371
+ }
372
+ let a = document.createElement('a');
373
+ a.href = data;
374
+ a.download = name + '.' + ext;
375
+ a.click();
376
+ URL.revokeObjectURL(a.href);
377
+ };
378
+ $.save = (a, b, c) => {
379
+ if (!a || (typeof a == 'string' && (!b || (!c && b.length < 5)))) {
380
+ c = b;
381
+ b = a;
382
+ a = $.canvas;
383
+ }
384
+ if (c) return $._save(a, b, c);
385
+ if (b) {
386
+ b = b.split('.');
387
+ $._save(a, b[0], b.at(-1));
388
+ } else $._save(a);
389
+ };
390
+
391
+ $._setCanvasSize = (w, h) => {
392
+ w ??= window.innerWidth;
393
+ h ??= window.innerHeight;
394
+ c.w = w = Math.ceil(w);
395
+ c.h = h = Math.ceil(h);
396
+ c.hw = w / 2;
397
+ c.hh = h / 2;
398
+ c.width = Math.ceil(w * $._pixelDensity);
399
+ c.height = Math.ceil(h * $._pixelDensity);
400
+
401
+ if (!$._da) {
402
+ q.width = w;
403
+ q.height = h;
404
+ } else $.flexibleCanvas($._dau);
405
+
406
+ if ($.displayMode && !c.displayMode) $.displayMode();
407
+ else $._adjustDisplay();
408
+ };
409
+
410
+ if ($._scope == 'image') return;
411
+
412
+ if (c && $._scope != 'graphics') {
413
+ c.parent = (el) => {
414
+ if (c.parentElement) c.parentElement.removeChild(c);
415
+
416
+ if (typeof el == 'string') el = document.getElementById(el);
417
+ el.append(c);
418
+
419
+ function parentResized() {
420
+ if ($.frameCount > 1) {
421
+ $._shouldResize = true;
422
+ $._adjustDisplay();
423
+ }
424
+ }
425
+ if (typeof ResizeObserver == 'function') {
426
+ if ($._ro) $._ro.disconnect();
427
+ $._ro = new ResizeObserver(parentResized);
428
+ $._ro.observe(el);
429
+ } else if (!$.frameCount) {
430
+ window.addEventListener('resize', parentResized);
431
+ }
432
+ };
433
+
434
+ function addCanvas() {
435
+ let el = $._parent;
436
+ el ??= document.getElementsByTagName('main')[0];
437
+ if (!el) {
438
+ el = document.createElement('main');
439
+ document.body.append(el);
440
+ }
441
+ c.parent(el);
442
+ }
443
+ if (document.body) addCanvas();
444
+ else document.addEventListener('DOMContentLoaded', addCanvas);
445
+ }
446
+
447
+ $.resizeCanvas = (w, h) => {
448
+ if (!$.ctx) return $.createCanvas(w, h);
449
+ if (w == c.w && h == c.h) return;
450
+
451
+ $._resizeCanvas(w, h);
452
+ };
453
+
454
+ $.canvas.resize = $.resizeCanvas;
455
+ $.canvas.save = $.saveCanvas = $.save;
456
+
457
+ $.displayDensity = () => window.devicePixelRatio;
458
+ $.pixelDensity = (v) => {
459
+ if (!v || v == $._pixelDensity) return $._pixelDensity;
460
+ $._pixelDensity = v;
461
+ $._setCanvasSize(c.w, c.h);
462
+ return v;
463
+ };
464
+
465
+ $.flexibleCanvas = (unit = 400) => {
466
+ if (unit) {
467
+ $._da = c.width / (unit * $._pixelDensity);
468
+ q.width = $._dau = unit;
469
+ q.height = (c.h / c.w) * unit;
470
+ } else $._da = 0;
471
+ };
472
+ };
473
+
474
+ Q5.canvasOptions = {
475
+ alpha: false,
476
+ colorSpace: 'display-p3'
477
+ };
478
+
479
+ if (!window.matchMedia || !matchMedia('(dynamic-range: high) and (color-gamut: p3)').matches) {
480
+ Q5.canvasOptions.colorSpace = 'srgb';
481
+ } else Q5.supportsHDR = true;
482
+ Q5.renderers.q2d = {};
483
+
484
+ Q5.renderers.q2d.canvas = ($, q) => {
485
+ let c = $.canvas;
486
+
487
+ if ($.colorMode) $.colorMode('rgb', 'integer');
488
+
489
+ $._createCanvas = function (w, h, options) {
490
+ q.ctx = q.drawingContext = c.getContext('2d', options);
491
+
492
+ if ($._scope != 'image') {
493
+ // default styles
494
+ $.ctx.fillStyle = 'white';
495
+ $.ctx.strokeStyle = 'black';
496
+ $.ctx.lineCap = 'round';
497
+ $.ctx.lineJoin = 'miter';
498
+ $.ctx.textAlign = 'left';
499
+ }
500
+ $.ctx.scale($._pixelDensity, $._pixelDensity);
501
+ $.ctx.save();
502
+ return c;
503
+ };
504
+
505
+ if ($._scope == 'image') return;
506
+
507
+ $._resizeCanvas = (w, h) => {
508
+ let t = {};
509
+ for (let prop in $.ctx) {
510
+ if (typeof $.ctx[prop] != 'function') t[prop] = $.ctx[prop];
511
+ }
512
+ delete t.canvas;
513
+
514
+ let o = new $._OffscreenCanvas(c.width, c.height);
515
+ o.w = c.w;
516
+ o.h = c.h;
517
+ let oCtx = o.getContext('2d');
518
+ oCtx.drawImage(c, 0, 0);
519
+
520
+ $._setCanvasSize(w, h);
521
+
522
+ for (let prop in t) $.ctx[prop] = t[prop];
523
+ $.scale($._pixelDensity);
524
+ $.ctx.drawImage(o, 0, 0, o.w, o.h);
525
+ };
526
+
527
+ $.strokeWeight = (n) => {
528
+ if (!n) $._doStroke = false;
529
+ if ($._da) n *= $._da;
530
+ $.ctx.lineWidth = n || 0.0001;
531
+ };
532
+ $.stroke = function (c) {
533
+ $._doStroke = true;
534
+ $._strokeSet = true;
535
+ if (Q5.Color) {
536
+ if (!c._q5Color && typeof c != 'string') c = $.color(...arguments);
537
+ else if ($._namedColors[c]) c = $.color(...$._namedColors[c]);
538
+ if (c.a <= 0) return ($._doStroke = false);
539
+ }
540
+ $.ctx.strokeStyle = c.toString();
541
+ };
542
+ $.noStroke = () => ($._doStroke = false);
543
+ $.fill = function (c) {
544
+ $._doFill = true;
545
+ $._fillSet = true;
546
+ if (Q5.Color) {
547
+ if (!c._q5Color && typeof c != 'string') c = $.color(...arguments);
548
+ else if ($._namedColors[c]) c = $.color(...$._namedColors[c]);
549
+ if (c.a <= 0) return ($._doFill = false);
550
+ }
551
+ $.ctx.fillStyle = c.toString();
552
+ };
553
+ $.noFill = () => ($._doFill = false);
554
+
555
+ // DRAWING MATRIX
556
+
557
+ $.translate = (x, y) => {
558
+ if ($._da) {
559
+ x *= $._da;
560
+ y *= $._da;
561
+ }
562
+ $.ctx.translate(x, y);
563
+ };
564
+ $.rotate = (r) => {
565
+ if ($._angleMode == 'degrees') r = $.radians(r);
566
+ $.ctx.rotate(r);
567
+ };
568
+ $.scale = (x, y) => {
569
+ y ??= x;
570
+ $.ctx.scale(x, y);
571
+ };
572
+ $.opacity = (a) => ($.ctx.globalAlpha = a);
573
+ $.applyMatrix = (a, b, c, d, e, f) => $.ctx.transform(a, b, c, d, e, f);
574
+ $.shearX = (ang) => $.ctx.transform(1, 0, $.tan(ang), 1, 0, 0);
575
+ $.shearY = (ang) => $.ctx.transform(1, $.tan(ang), 0, 1, 0, 0);
576
+ $.resetMatrix = () => {
577
+ $.ctx.resetTransform();
578
+ $.scale($._pixelDensity);
579
+ };
580
+
581
+ $._styleNames = [
582
+ '_doStroke',
583
+ '_doFill',
584
+ '_strokeSet',
585
+ '_fillSet',
586
+ '_tint',
587
+ '_imageMode',
588
+ '_rectMode',
589
+ '_ellipseMode',
590
+ '_textFont',
591
+ '_textLeading',
592
+ '_leadingSet',
593
+ '_textSize',
594
+ '_textAlign',
595
+ '_textBaseline',
596
+ '_textStyle',
597
+ '_textWrap'
598
+ ];
599
+ $._styles = [];
600
+
601
+ $.push = $.pushMatrix = () => {
602
+ $.ctx.save();
603
+ let styles = {};
604
+ for (let s of $._styleNames) styles[s] = $[s];
605
+ $._styles.push(styles);
606
+ };
607
+ $.pop = $.popMatrix = () => {
608
+ $.ctx.restore();
609
+ let styles = $._styles.pop();
610
+ for (let s of $._styleNames) $[s] = styles[s];
611
+ };
612
+
613
+ $.createCapture = (x) => {
614
+ var vid = document.createElement('video');
615
+ vid.playsinline = 'playsinline';
616
+ vid.autoplay = 'autoplay';
617
+ navigator.mediaDevices.getUserMedia(x).then((stream) => {
618
+ vid.srcObject = stream;
619
+ });
620
+ vid.style.position = 'absolute';
621
+ vid.style.opacity = 0.00001;
622
+ vid.style.zIndex = -1000;
623
+ document.body.append(vid);
624
+ return vid;
625
+ };
626
+
627
+ $.createGraphics = function (w, h, opt) {
628
+ let g = new Q5('graphics');
629
+ opt ??= {};
630
+ opt.alpha ??= true;
631
+ opt.colorSpace ??= $.canvas.colorSpace;
632
+ g.createCanvas.call($, w, h, opt);
633
+ return g;
634
+ };
635
+
636
+ if (window && $._scope != 'graphics') {
637
+ window.addEventListener('resize', () => {
638
+ $._shouldResize = true;
639
+ q.windowWidth = window.innerWidth;
640
+ q.windowHeight = window.innerHeight;
641
+ q.deviceOrientation = window.screen?.orientation?.type;
642
+ });
643
+ }
644
+ };
645
+ Q5.renderers.q2d.drawing = ($) => {
646
+ $.CHORD = 0;
647
+ $.PIE = 1;
648
+ $.OPEN = 2;
649
+
650
+ $.RADIUS = 'radius';
651
+ $.CORNER = 'corner';
652
+ $.CORNERS = 'corners';
653
+
654
+ $.ROUND = 'round';
655
+ $.SQUARE = 'butt';
656
+ $.PROJECT = 'square';
657
+ $.MITER = 'miter';
658
+ $.BEVEL = 'bevel';
659
+
660
+ $.CLOSE = 1;
661
+
662
+ $.CENTER = 'center';
663
+ $.LEFT = 'left';
664
+ $.RIGHT = 'right';
665
+ $.TOP = 'top';
666
+ $.BOTTOM = 'bottom';
667
+
668
+ $.LANDSCAPE = 'landscape';
669
+ $.PORTRAIT = 'portrait';
670
+
671
+ $.BLEND = 'source-over';
672
+ $.REMOVE = 'destination-out';
673
+ $.ADD = 'lighter';
674
+ $.DARKEST = 'darken';
675
+ $.LIGHTEST = 'lighten';
676
+ $.DIFFERENCE = 'difference';
677
+ $.SUBTRACT = 'subtract';
678
+ $.EXCLUSION = 'exclusion';
679
+ $.MULTIPLY = 'multiply';
680
+ $.SCREEN = 'screen';
681
+ $.REPLACE = 'copy';
682
+ $.OVERLAY = 'overlay';
683
+ $.HARD_LIGHT = 'hard-light';
684
+ $.SOFT_LIGHT = 'soft-light';
685
+ $.DODGE = 'color-dodge';
686
+ $.BURN = 'color-burn';
687
+
688
+ $._doStroke = true;
689
+ $._doFill = true;
690
+ $._strokeSet = false;
691
+ $._fillSet = false;
692
+ $._ellipseMode = $.CENTER;
693
+ $._rectMode = $.CORNER;
694
+ $._curveDetail = 20;
695
+ $._curveAlpha = 0.0;
696
+
697
+ let firstVertex = true;
698
+ let curveBuff = [];
699
+
700
+ function ink() {
701
+ if ($._doFill) $.ctx.fill();
702
+ if ($._doStroke) $.ctx.stroke();
703
+ }
704
+
705
+ // DRAWING SETTINGS
706
+
707
+ $.blendMode = (x) => ($.ctx.globalCompositeOperation = x);
708
+ $.strokeCap = (x) => ($.ctx.lineCap = x);
709
+ $.strokeJoin = (x) => ($.ctx.lineJoin = x);
710
+ $.ellipseMode = (x) => ($._ellipseMode = x);
711
+ $.rectMode = (x) => ($._rectMode = x);
712
+ $.curveDetail = (x) => ($._curveDetail = x);
713
+ $.curveAlpha = (x) => ($._curveAlpha = x);
714
+ $.curveTightness = (x) => ($._curveAlpha = x);
715
+
716
+ // DRAWING
717
+
718
+ $.clear = () => {
719
+ $.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
720
+ };
721
+
722
+ $.background = function (c) {
723
+ if (c.canvas) return $.image(c, 0, 0, $.width, $.height);
724
+ $.ctx.save();
725
+ $.ctx.resetTransform();
726
+ if (Q5.Color) {
727
+ if (!c._q5Color && typeof c != 'string') c = $.color(...arguments);
728
+ else if ($._namedColors[c]) c = $.color(...$._namedColors[c]);
729
+ }
730
+ $.ctx.fillStyle = c.toString();
731
+ $.ctx.fillRect(0, 0, $.canvas.width, $.canvas.height);
732
+ $.ctx.restore();
733
+ };
734
+
735
+ $.line = (x0, y0, x1, y1) => {
736
+ if ($._doStroke) {
737
+ if ($._da) {
738
+ x0 *= $._da;
739
+ y0 *= $._da;
740
+ x1 *= $._da;
741
+ y1 *= $._da;
742
+ }
743
+ $.ctx.beginPath();
744
+ $.ctx.moveTo(x0, y0);
745
+ $.ctx.lineTo(x1, y1);
746
+ $.ctx.stroke();
747
+ }
748
+ };
749
+
750
+ function arc(x, y, w, h, lo, hi, mode, detail) {
751
+ if (!$._doFill && !$._doStroke) return;
752
+ let d = $._angleMode == 'degrees';
753
+ let full = d ? 360 : $.TAU;
754
+ lo %= full;
755
+ hi %= full;
756
+ if (lo < 0) lo += full;
757
+ if (hi < 0) hi += full;
758
+ if (lo == 0 && hi == 0) return;
759
+ if (lo > hi) [lo, hi] = [hi, lo];
760
+ $.ctx.beginPath();
761
+ if (w == h) {
762
+ if (d) {
763
+ lo = $.radians(lo);
764
+ hi = $.radians(hi);
765
+ }
766
+ $.ctx.arc(x, y, w / 2, lo, hi);
767
+ } else {
768
+ for (let i = 0; i < detail + 1; i++) {
769
+ let t = i / detail;
770
+ let a = $.lerp(lo, hi, t);
771
+ let dx = ($.cos(a) * w) / 2;
772
+ let dy = ($.sin(a) * h) / 2;
773
+ $.ctx[i ? 'lineTo' : 'moveTo'](x + dx, y + dy);
774
+ }
775
+ if (mode == $.CHORD) {
776
+ $.ctx.closePath();
777
+ } else if (mode == $.PIE) {
778
+ $.ctx.lineTo(x, y);
779
+ $.ctx.closePath();
780
+ }
781
+ }
782
+ ink();
783
+ }
784
+ $.arc = (x, y, w, h, start, stop, mode, detail = 25) => {
785
+ if (start == stop) return $.ellipse(x, y, w, h);
786
+ mode ??= $.PIE;
787
+ if ($._ellipseMode == $.CENTER) {
788
+ arc(x, y, w, h, start, stop, mode, detail);
789
+ } else if ($._ellipseMode == $.RADIUS) {
790
+ arc(x, y, w * 2, h * 2, start, stop, mode, detail);
791
+ } else if ($._ellipseMode == $.CORNER) {
792
+ arc(x + w / 2, y + h / 2, w, h, start, stop, mode, detail);
793
+ } else if ($._ellipseMode == $.CORNERS) {
794
+ arc((x + w) / 2, (y + h) / 2, w - x, h - y, start, stop, mode, detail);
795
+ }
796
+ };
797
+
798
+ function ellipse(x, y, w, h) {
799
+ if (!$._doFill && !$._doStroke) return;
800
+ if ($._da) {
801
+ x *= $._da;
802
+ y *= $._da;
803
+ w *= $._da;
804
+ h *= $._da;
805
+ }
806
+ $.ctx.beginPath();
807
+ $.ctx.ellipse(x, y, w / 2, h / 2, 0, 0, $.TAU);
808
+ ink();
809
+ }
810
+ $.ellipse = (x, y, w, h) => {
811
+ h ??= w;
812
+ if ($._ellipseMode == $.CENTER) {
813
+ ellipse(x, y, w, h);
814
+ } else if ($._ellipseMode == $.RADIUS) {
815
+ ellipse(x, y, w * 2, h * 2);
816
+ } else if ($._ellipseMode == $.CORNER) {
817
+ ellipse(x + w / 2, y + h / 2, w, h);
818
+ } else if ($._ellipseMode == $.CORNERS) {
819
+ ellipse((x + w) / 2, (y + h) / 2, w - x, h - y);
820
+ }
821
+ };
822
+ $.circle = (x, y, d) => {
823
+ if ($._ellipseMode == $.CENTER) {
824
+ if ($._da) {
825
+ x *= $._da;
826
+ y *= $._da;
827
+ d *= $._da;
828
+ }
829
+ $.ctx.beginPath();
830
+ $.ctx.arc(x, y, d / 2, 0, $.TAU);
831
+ ink();
832
+ } else $.ellipse(x, y, d, d);
833
+ };
834
+ $.point = (x, y) => {
835
+ if (x.x) {
836
+ y = x.y;
837
+ x = x.x;
838
+ }
839
+ if ($._da) {
840
+ x *= $._da;
841
+ y *= $._da;
842
+ }
843
+ $.ctx.save();
844
+ $.ctx.beginPath();
845
+ $.ctx.arc(x, y, $.ctx.lineWidth / 2, 0, $.TAU);
846
+ $.ctx.fillStyle = $.ctx.strokeStyle;
847
+ $.ctx.fill();
848
+ $.ctx.restore();
849
+ };
850
+ function rect(x, y, w, h) {
851
+ if ($._da) {
852
+ x *= $._da;
853
+ y *= $._da;
854
+ w *= $._da;
855
+ h *= $._da;
856
+ }
857
+ $.ctx.beginPath();
858
+ $.ctx.rect(x, y, w, h);
859
+ ink();
860
+ }
861
+ function roundedRect(x, y, w, h, tl, tr, br, bl) {
862
+ if (!$._doFill && !$._doStroke) return;
863
+ if (tl === undefined) {
864
+ return rect(x, y, w, h);
865
+ }
866
+ if (tr === undefined) {
867
+ return roundedRect(x, y, w, h, tl, tl, tl, tl);
868
+ }
869
+ if ($._da) {
870
+ x *= $._da;
871
+ y *= $._da;
872
+ w *= $._da;
873
+ h *= $._da;
874
+ tl *= $._da;
875
+ tr *= $._da;
876
+ bl *= $._da;
877
+ br *= $._da;
878
+ }
879
+ const hh = Math.min(Math.abs(h), Math.abs(w)) / 2;
880
+ tl = Math.min(hh, tl);
881
+ tr = Math.min(hh, tr);
882
+ bl = Math.min(hh, bl);
883
+ br = Math.min(hh, br);
884
+ $.ctx.beginPath();
885
+ $.ctx.moveTo(x + tl, y);
886
+ $.ctx.arcTo(x + w, y, x + w, y + h, tr);
887
+ $.ctx.arcTo(x + w, y + h, x, y + h, br);
888
+ $.ctx.arcTo(x, y + h, x, y, bl);
889
+ $.ctx.arcTo(x, y, x + w, y, tl);
890
+ $.ctx.closePath();
891
+ ink();
892
+ }
893
+
894
+ $.rect = (x, y, w, h = w, tl, tr, br, bl) => {
895
+ if ($._rectMode == $.CENTER) {
896
+ roundedRect(x - w / 2, y - h / 2, w, h, tl, tr, br, bl);
897
+ } else if ($._rectMode == $.RADIUS) {
898
+ roundedRect(x - w, y - h, w * 2, h * 2, tl, tr, br, bl);
899
+ } else if ($._rectMode == $.CORNER) {
900
+ roundedRect(x, y, w, h, tl, tr, br, bl);
901
+ } else if ($._rectMode == $.CORNERS) {
902
+ roundedRect(x, y, w - x, h - y, tl, tr, br, bl);
903
+ }
904
+ };
905
+ $.square = (x, y, s, tl, tr, br, bl) => {
906
+ return $.rect(x, y, s, s, tl, tr, br, bl);
907
+ };
908
+
909
+ $.beginShape = () => {
910
+ curveBuff = [];
911
+ $.ctx.beginPath();
912
+ firstVertex = true;
913
+ };
914
+ $.beginContour = () => {
915
+ $.ctx.closePath();
916
+ curveBuff = [];
917
+ firstVertex = true;
918
+ };
919
+ $.endContour = () => {
920
+ curveBuff = [];
921
+ firstVertex = true;
922
+ };
923
+ $.vertex = (x, y) => {
924
+ if ($._da) {
925
+ x *= $._da;
926
+ y *= $._da;
927
+ }
928
+ curveBuff = [];
929
+ if (firstVertex) {
930
+ $.ctx.moveTo(x, y);
931
+ } else {
932
+ $.ctx.lineTo(x, y);
933
+ }
934
+ firstVertex = false;
935
+ };
936
+ $.bezierVertex = (cp1x, cp1y, cp2x, cp2y, x, y) => {
937
+ if ($._da) {
938
+ cp1x *= $._da;
939
+ cp1y *= $._da;
940
+ cp2x *= $._da;
941
+ cp2y *= $._da;
942
+ x *= $._da;
943
+ y *= $._da;
944
+ }
945
+ curveBuff = [];
946
+ $.ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
947
+ };
948
+ $.quadraticVertex = (cp1x, cp1y, x, y) => {
949
+ if ($._da) {
950
+ cp1x *= $._da;
951
+ cp1y *= $._da;
952
+ x *= $._da;
953
+ y *= $._da;
954
+ }
955
+ curveBuff = [];
956
+ $.ctx.quadraticCurveTo(cp1x, cp1y, x, y);
957
+ };
958
+ $.bezier = (x1, y1, x2, y2, x3, y3, x4, y4) => {
959
+ $.beginShape();
960
+ $.vertex(x1, y1);
961
+ $.bezierVertex(x2, y2, x3, y3, x4, y4);
962
+ $.endShape();
963
+ };
964
+ $.triangle = (x1, y1, x2, y2, x3, y3) => {
965
+ $.beginShape();
966
+ $.vertex(x1, y1);
967
+ $.vertex(x2, y2);
968
+ $.vertex(x3, y3);
969
+ $.endShape($.CLOSE);
970
+ };
971
+ $.quad = (x1, y1, x2, y2, x3, y3, x4, y4) => {
972
+ $.beginShape();
973
+ $.vertex(x1, y1);
974
+ $.vertex(x2, y2);
975
+ $.vertex(x3, y3);
976
+ $.vertex(x4, y4);
977
+ $.endShape($.CLOSE);
978
+ };
979
+ $.endShape = (close) => {
980
+ curveBuff = [];
981
+ if (close) $.ctx.closePath();
982
+ ink();
983
+ };
984
+ function catmullRomSpline(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, numPts, alpha) {
985
+ function catmullromSplineGetT(t, p0x, p0y, p1x, p1y, alpha) {
986
+ let a = Math.pow(p1x - p0x, 2.0) + Math.pow(p1y - p0y, 2.0);
987
+ let b = Math.pow(a, alpha * 0.5);
988
+ return b + t;
989
+ }
990
+ let pts = [];
991
+
992
+ let t0 = 0.0;
993
+ let t1 = catmullromSplineGetT(t0, p0x, p0y, p1x, p1y, alpha);
994
+ let t2 = catmullromSplineGetT(t1, p1x, p1y, p2x, p2y, alpha);
995
+ let t3 = catmullromSplineGetT(t2, p2x, p2y, p3x, p3y, alpha);
996
+
997
+ for (let i = 0; i < numPts; i++) {
998
+ let t = t1 + (i / (numPts - 1)) * (t2 - t1);
999
+ let s = [
1000
+ (t1 - t) / (t1 - t0),
1001
+ (t - t0) / (t1 - t0),
1002
+ (t2 - t) / (t2 - t1),
1003
+ (t - t1) / (t2 - t1),
1004
+ (t3 - t) / (t3 - t2),
1005
+ (t - t2) / (t3 - t2),
1006
+ (t2 - t) / (t2 - t0),
1007
+ (t - t0) / (t2 - t0),
1008
+ (t3 - t) / (t3 - t1),
1009
+ (t - t1) / (t3 - t1)
1010
+ ];
1011
+ for (let j = 0; j < s.length; j += 2) {
1012
+ if (isNaN(s[j])) {
1013
+ s[j] = 1;
1014
+ s[j + 1] = 0;
1015
+ }
1016
+ if (!isFinite(s[j])) {
1017
+ if (s[j] > 0) {
1018
+ s[j] = 1;
1019
+ s[j + 1] = 0;
1020
+ } else {
1021
+ s[j] = 0;
1022
+ s[j + 1] = 1;
1023
+ }
1024
+ }
1025
+ }
1026
+ let a1x = p0x * s[0] + p1x * s[1];
1027
+ let a1y = p0y * s[0] + p1y * s[1];
1028
+ let a2x = p1x * s[2] + p2x * s[3];
1029
+ let a2y = p1y * s[2] + p2y * s[3];
1030
+ let a3x = p2x * s[4] + p3x * s[5];
1031
+ let a3y = p2y * s[4] + p3y * s[5];
1032
+ let b1x = a1x * s[6] + a2x * s[7];
1033
+ let b1y = a1y * s[6] + a2y * s[7];
1034
+ let b2x = a2x * s[8] + a3x * s[9];
1035
+ let b2y = a2y * s[8] + a3y * s[9];
1036
+ let cx = b1x * s[2] + b2x * s[3];
1037
+ let cy = b1y * s[2] + b2y * s[3];
1038
+ pts.push([cx, cy]);
1039
+ }
1040
+ return pts;
1041
+ }
1042
+
1043
+ $.curveVertex = (x, y) => {
1044
+ if ($._da) {
1045
+ x *= $._da;
1046
+ y *= $._da;
1047
+ }
1048
+ curveBuff.push([x, y]);
1049
+ if (curveBuff.length < 4) return;
1050
+ let p0 = curveBuff.at(-4);
1051
+ let p1 = curveBuff.at(-3);
1052
+ let p2 = curveBuff.at(-2);
1053
+ let p3 = curveBuff.at(-1);
1054
+ let pts = catmullRomSpline(...p0, ...p1, ...p2, ...p3, $._curveDetail, $._curveAlpha);
1055
+ for (let i = 0; i < pts.length; i++) {
1056
+ if (firstVertex) {
1057
+ $.ctx.moveTo(...pts[i]);
1058
+ } else {
1059
+ $.ctx.lineTo(...pts[i]);
1060
+ }
1061
+ firstVertex = false;
1062
+ }
1063
+ };
1064
+ $.curve = (x1, y1, x2, y2, x3, y3, x4, y4) => {
1065
+ $.beginShape();
1066
+ $.curveVertex(x1, y1);
1067
+ $.curveVertex(x2, y2);
1068
+ $.curveVertex(x3, y3);
1069
+ $.curveVertex(x4, y4);
1070
+ $.endShape();
1071
+ };
1072
+ $.curvePoint = (a, b, c, d, t) => {
1073
+ const t3 = t * t * t,
1074
+ t2 = t * t,
1075
+ f1 = -0.5 * t3 + t2 - 0.5 * t,
1076
+ f2 = 1.5 * t3 - 2.5 * t2 + 1.0,
1077
+ f3 = -1.5 * t3 + 2.0 * t2 + 0.5 * t,
1078
+ f4 = 0.5 * t3 - 0.5 * t2;
1079
+ return a * f1 + b * f2 + c * f3 + d * f4;
1080
+ };
1081
+ $.bezierPoint = (a, b, c, d, t) => {
1082
+ const adjustedT = 1 - t;
1083
+ return (
1084
+ Math.pow(adjustedT, 3) * a +
1085
+ 3 * Math.pow(adjustedT, 2) * t * b +
1086
+ 3 * adjustedT * Math.pow(t, 2) * c +
1087
+ Math.pow(t, 3) * d
1088
+ );
1089
+ };
1090
+ $.curveTangent = (a, b, c, d, t) => {
1091
+ const t2 = t * t,
1092
+ f1 = (-3 * t2) / 2 + 2 * t - 0.5,
1093
+ f2 = (9 * t2) / 2 - 5 * t,
1094
+ f3 = (-9 * t2) / 2 + 4 * t + 0.5,
1095
+ f4 = (3 * t2) / 2 - t;
1096
+ return a * f1 + b * f2 + c * f3 + d * f4;
1097
+ };
1098
+ $.bezierTangent = (a, b, c, d, t) => {
1099
+ const adjustedT = 1 - t;
1100
+ return (
1101
+ 3 * d * Math.pow(t, 2) -
1102
+ 3 * c * Math.pow(t, 2) +
1103
+ 6 * c * adjustedT * t -
1104
+ 6 * b * adjustedT * t +
1105
+ 3 * b * Math.pow(adjustedT, 2) -
1106
+ 3 * a * Math.pow(adjustedT, 2)
1107
+ );
1108
+ };
1109
+
1110
+ $.erase = function (fillAlpha = 255, strokeAlpha = 255) {
1111
+ $.ctx.save();
1112
+ $.ctx.globalCompositeOperation = 'destination-out';
1113
+ $.ctx.fillStyle = `rgba(0, 0, 0, ${fillAlpha / 255})`;
1114
+ $.ctx.strokeStyle = `rgba(0, 0, 0, ${strokeAlpha / 255})`;
1115
+ };
1116
+
1117
+ $.noErase = function () {
1118
+ $.ctx.globalCompositeOperation = 'source-over';
1119
+ $.ctx.restore();
1120
+ };
1121
+
1122
+ $.inFill = (x, y) => {
1123
+ const pd = $._pixelDensity;
1124
+ return $.ctx.isPointInPath(x * pd, y * pd);
1125
+ };
1126
+
1127
+ $.inStroke = (x, y) => {
1128
+ const pd = $._pixelDensity;
1129
+ return $.ctx.isPointInStroke(x * pd, y * pd);
1130
+ };
1131
+ };
1132
+ Q5.renderers.q2d.image = ($, q) => {
1133
+ class Q5Image {
1134
+ constructor(w, h, opt) {
1135
+ let $ = this;
1136
+ $._scope = 'image';
1137
+ $.canvas = $.ctx = $.drawingContext = null;
1138
+ $.pixels = [];
1139
+ Q5.modules.canvas($, $);
1140
+ let r = Q5.renderers.q2d;
1141
+ for (let m of ['canvas', 'image', 'soft_filters']) {
1142
+ if (r[m]) r[m]($, $);
1143
+ }
1144
+ $.createCanvas(w, h, opt);
1145
+ delete $.createCanvas;
1146
+ $._loop = false;
1147
+ }
1148
+ get w() {
1149
+ return this.width;
1150
+ }
1151
+ get h() {
1152
+ return this.height;
1153
+ }
1154
+ }
1155
+
1156
+ Q5.Image ??= Q5Image;
1157
+
1158
+ $.createImage = (w, h, opt) => {
1159
+ opt ??= {};
1160
+ opt.alpha ??= true;
1161
+ opt.colorSpace ??= $.canvas.colorSpace || Q5.canvasOptions.colorSpace;
1162
+ return new Q5.Image(w, h, opt);
1163
+ };
1164
+
1165
+ $.loadImage = function (url, cb, opt) {
1166
+ if (url.canvas) return url;
1167
+ if (url.slice(-3).toLowerCase() == 'gif') {
1168
+ throw new Error(`q5 doesn't support GIFs due to their impact on performance. Use a video or animation instead.`);
1169
+ }
1170
+ q._preloadCount++;
1171
+ let last = [...arguments].at(-1);
1172
+ opt = typeof last == 'object' ? last : null;
1173
+
1174
+ let g = $.createImage(1, 1, opt);
1175
+
1176
+ function loaded(img) {
1177
+ g.resize(img.naturalWidth || img.width, img.naturalHeight || img.height);
1178
+ g.ctx.drawImage(img, 0, 0);
1179
+ q._preloadCount--;
1180
+ if (cb) cb(g);
1181
+ }
1182
+
1183
+ if (Q5._nodejs && global.CairoCanvas) {
1184
+ global.CairoCanvas.loadImage(url)
1185
+ .then(loaded)
1186
+ .catch((e) => {
1187
+ q._preloadCount--;
1188
+ throw e;
1189
+ });
1190
+ } else {
1191
+ let img = new window.Image();
1192
+ img.src = url;
1193
+ img.crossOrigin = 'Anonymous';
1194
+ img._pixelDensity = 1;
1195
+ img.onload = () => loaded(img);
1196
+ img.onerror = (e) => {
1197
+ q._preloadCount--;
1198
+ throw e;
1199
+ };
1200
+ }
1201
+ return g;
1202
+ };
1203
+
1204
+ $.imageMode = (mode) => ($._imageMode = mode);
1205
+ $.image = (img, dx, dy, dw, dh, sx = 0, sy = 0, sw, sh) => {
1206
+ let drawable = img.canvas || img;
1207
+ if (Q5._createNodeJSCanvas) {
1208
+ drawable = drawable.context.canvas;
1209
+ }
1210
+ dw ??= img.width || img.videoWidth;
1211
+ dh ??= img.height || img.videoHeight;
1212
+ if ($._imageMode == 'center') {
1213
+ dx -= dw * 0.5;
1214
+ dy -= dh * 0.5;
1215
+ }
1216
+ if ($._da) {
1217
+ dx *= $._da;
1218
+ dy *= $._da;
1219
+ dw *= $._da;
1220
+ dh *= $._da;
1221
+ sx *= $._da;
1222
+ sy *= $._da;
1223
+ sw *= $._da;
1224
+ sh *= $._da;
1225
+ }
1226
+ let pd = img._pixelDensity || 1;
1227
+ if (!sw) {
1228
+ sw = drawable.width || drawable.videoWidth;
1229
+ } else sw *= pd;
1230
+ if (!sh) {
1231
+ sh = drawable.height || drawable.videoHeight;
1232
+ } else sh *= pd;
1233
+ $.ctx.drawImage(drawable, sx * pd, sy * pd, sw, sh, dx, dy, dw, dh);
1234
+
1235
+ if ($._tint) {
1236
+ $.ctx.globalCompositeOperation = 'multiply';
1237
+ $.ctx.fillStyle = $._tint.toString();
1238
+ $.ctx.fillRect(dx, dy, dw, dh);
1239
+ $.ctx.globalCompositeOperation = 'source-over';
1240
+ }
1241
+ };
1242
+
1243
+ $._tint = null;
1244
+ let imgData = null;
1245
+
1246
+ $._softFilter = () => {
1247
+ throw new Error('Load q5-2d-soft-filters.js to use software filters.');
1248
+ };
1249
+
1250
+ $.filter = (type, x) => {
1251
+ if (!$.ctx.filter) return $._softFilter(type, x);
1252
+
1253
+ if (typeof type == 'string') f = type;
1254
+ else if (type == Q5.GRAY) f = `saturate(0%)`;
1255
+ else if (type == Q5.INVERT) f = `invert(100%)`;
1256
+ else if (type == Q5.BLUR) {
1257
+ let r = Math.ceil(x * $._pixelDensity) || 1;
1258
+ f = `blur(${r}px)`;
1259
+ } else if (type == Q5.THRESHOLD) {
1260
+ x ??= 0.5;
1261
+ let b = Math.floor((0.5 / Math.max(x, 0.00001)) * 100);
1262
+ f = `saturate(0%) brightness(${b}%) contrast(1000000%)`;
1263
+ } else return $._softFilter(type, x);
1264
+
1265
+ $.ctx.filter = f;
1266
+ $.ctx.drawImage($.canvas, 0, 0, $.canvas.w, $.canvas.h);
1267
+ $.ctx.filter = 'none';
1268
+ };
1269
+
1270
+ if ($._scope == 'image') {
1271
+ $.resize = (w, h) => {
1272
+ let o = new $._OffscreenCanvas($.canvas.width, $.canvas.height);
1273
+ let tmpCtx = o.getContext('2d', {
1274
+ colorSpace: $.canvas.colorSpace
1275
+ });
1276
+ tmpCtx.drawImage($.canvas, 0, 0);
1277
+ $._setCanvasSize(w, h);
1278
+
1279
+ $.ctx.clearRect(0, 0, $.canvas.width, $.canvas.height);
1280
+ $.ctx.drawImage(o, 0, 0, $.canvas.width, $.canvas.height);
1281
+ };
1282
+ }
1283
+
1284
+ $.trim = () => {
1285
+ let pd = $._pixelDensity || 1;
1286
+ let w = $.canvas.width;
1287
+ let h = $.canvas.height;
1288
+ let data = $.ctx.getImageData(0, 0, w, h).data;
1289
+ let left = w,
1290
+ right = 0,
1291
+ top = h,
1292
+ bottom = 0;
1293
+
1294
+ let i = 3;
1295
+ for (let y = 0; y < h; y++) {
1296
+ for (let x = 0; x < w; x++) {
1297
+ if (data[i] !== 0) {
1298
+ if (x < left) left = x;
1299
+ if (x > right) right = x;
1300
+ if (y < top) top = y;
1301
+ if (y > bottom) bottom = y;
1302
+ }
1303
+ i += 4;
1304
+ }
1305
+ }
1306
+ top = Math.floor(top / pd);
1307
+ bottom = Math.floor(bottom / pd);
1308
+ left = Math.floor(left / pd);
1309
+ right = Math.floor(right / pd);
1310
+
1311
+ return $.get(left, top, right - left + 1, bottom - top + 1);
1312
+ };
1313
+
1314
+ $.mask = (img) => {
1315
+ $.ctx.save();
1316
+ $.ctx.resetTransform();
1317
+ let old = $.ctx.globalCompositeOperation;
1318
+ $.ctx.globalCompositeOperation = 'destination-in';
1319
+ $.ctx.drawImage(img.canvas, 0, 0);
1320
+ $.ctx.globalCompositeOperation = old;
1321
+ $.ctx.restore();
1322
+ };
1323
+
1324
+ $.get = (x, y, w, h) => {
1325
+ let pd = $._pixelDensity || 1;
1326
+ if (x !== undefined && w === undefined) {
1327
+ let c = $.ctx.getImageData(x * pd, y * pd, 1, 1).data;
1328
+ return new $.Color(c[0], c[1], c[2], c[3] / 255);
1329
+ }
1330
+ x = (x || 0) * pd;
1331
+ y = (y || 0) * pd;
1332
+ let _w = (w = w || $.width);
1333
+ let _h = (h = h || $.height);
1334
+ w *= pd;
1335
+ h *= pd;
1336
+ let img = $.createImage(w, h);
1337
+ let imgData = $.ctx.getImageData(x, y, w, h);
1338
+ img.ctx.putImageData(imgData, 0, 0);
1339
+ img._pixelDensity = pd;
1340
+ img.width = _w;
1341
+ img.height = _h;
1342
+ return img;
1343
+ };
1344
+
1345
+ $.set = (x, y, c) => {
1346
+ if (c.canvas) {
1347
+ let old = $._tint;
1348
+ $._tint = null;
1349
+ $.image(c, x, y);
1350
+ $._tint = old;
1351
+ return;
1352
+ }
1353
+ if (!$.pixels.length) $.loadPixels();
1354
+ let mod = $._pixelDensity || 1;
1355
+ for (let i = 0; i < mod; i++) {
1356
+ for (let j = 0; j < mod; j++) {
1357
+ let idx = 4 * ((y * mod + i) * $.canvas.width + x * mod + j);
1358
+ $.pixels[idx] = c.r ?? c.l;
1359
+ $.pixels[idx + 1] = c.g ?? c.c;
1360
+ $.pixels[idx + 2] = c.b ?? c.h;
1361
+ $.pixels[idx + 3] = c.a;
1362
+ }
1363
+ }
1364
+ };
1365
+
1366
+ $.loadPixels = () => {
1367
+ imgData = $.ctx.getImageData(0, 0, $.canvas.width, $.canvas.height);
1368
+ q.pixels = imgData.data;
1369
+ };
1370
+ $.updatePixels = () => {
1371
+ if (imgData != null) $.ctx.putImageData(imgData, 0, 0);
1372
+ };
1373
+
1374
+ $.smooth = () => ($.ctx.imageSmoothingEnabled = true);
1375
+ $.noSmooth = () => ($.ctx.imageSmoothingEnabled = false);
1376
+
1377
+ if ($._scope == 'image') return;
1378
+
1379
+ $.tint = function (c) {
1380
+ $._tint = c._q5Color ? c : $.color(...arguments);
1381
+ };
1382
+ $.noTint = () => ($._tint = null);
1383
+ };
1384
+
1385
+ Q5.THRESHOLD = 1;
1386
+ Q5.GRAY = 2;
1387
+ Q5.OPAQUE = 3;
1388
+ Q5.INVERT = 4;
1389
+ Q5.POSTERIZE = 5;
1390
+ Q5.DILATE = 6;
1391
+ Q5.ERODE = 7;
1392
+ Q5.BLUR = 8;
1393
+ Q5.renderers.q2d.text = ($, q) => {
1394
+ $.NORMAL = 'normal';
1395
+ $.ITALIC = 'italic';
1396
+ $.BOLD = 'bold';
1397
+ $.BOLDITALIC = 'italic bold';
1398
+
1399
+ $.CENTER = 'center';
1400
+ $.LEFT = 'left';
1401
+ $.RIGHT = 'right';
1402
+ $.TOP = 'top';
1403
+ $.BOTTOM = 'bottom';
1404
+ $.BASELINE = 'alphabetic';
1405
+
1406
+ $._textFont = 'sans-serif';
1407
+ $._textSize = 12;
1408
+ $._textLeading = 15;
1409
+ $._textLeadDiff = 3;
1410
+ $._textStyle = 'normal';
1411
+
1412
+ $.loadFont = (url, cb) => {
1413
+ q._preloadCount++;
1414
+ let name = url.split('/').pop().split('.')[0].replace(' ', '');
1415
+ let f = new FontFace(name, `url(${url})`);
1416
+ document.fonts.add(f);
1417
+ f.load().then(() => {
1418
+ q._preloadCount--;
1419
+ if (cb) cb(name);
1420
+ });
1421
+ return name;
1422
+ };
1423
+ $.textFont = (x) => ($._textFont = x);
1424
+ $.textSize = (x) => {
1425
+ if (x === undefined) return $._textSize;
1426
+ if ($._da) x *= $._da;
1427
+ $._textSize = x;
1428
+ if (!$._leadingSet) {
1429
+ $._textLeading = x * 1.25;
1430
+ $._textLeadDiff = $._textLeading - x;
1431
+ }
1432
+ };
1433
+ $.textLeading = (x) => {
1434
+ if (x === undefined) return $._textLeading;
1435
+ if ($._da) x *= $._da;
1436
+ $._textLeading = x;
1437
+ $._textLeadDiff = x - $._textSize;
1438
+ $._leadingSet = true;
1439
+ };
1440
+ $.textStyle = (x) => ($._textStyle = x);
1441
+ $.textAlign = (horiz, vert) => {
1442
+ $.ctx.textAlign = horiz;
1443
+ if (vert) {
1444
+ $.ctx.textBaseline = vert == $.CENTER ? 'middle' : vert;
1445
+ }
1446
+ };
1447
+ $.textWidth = (str) => {
1448
+ $.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
1449
+ return $.ctx.measureText(str).width;
1450
+ };
1451
+ $.textAscent = (str) => {
1452
+ $.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
1453
+ return $.ctx.measureText(str).actualBoundingBoxAscent;
1454
+ };
1455
+ $.textDescent = (str) => {
1456
+ $.ctx.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
1457
+ return $.ctx.measureText(str).actualBoundingBoxDescent;
1458
+ };
1459
+ $._textCache = !!Q5.Image;
1460
+ $._TimedCache = class extends Map {
1461
+ constructor() {
1462
+ super();
1463
+ this.maxSize = 500;
1464
+ }
1465
+ set(k, v) {
1466
+ v.lastAccessed = Date.now();
1467
+ super.set(k, v);
1468
+ if (this.size > this.maxSize) this.gc();
1469
+ }
1470
+ get(k) {
1471
+ const v = super.get(k);
1472
+ if (v) v.lastAccessed = Date.now();
1473
+ return v;
1474
+ }
1475
+ gc() {
1476
+ let t = Infinity;
1477
+ let oldest;
1478
+ let i = 0;
1479
+ for (const [k, v] of this.entries()) {
1480
+ if (v.lastAccessed < t) {
1481
+ t = v.lastAccessed;
1482
+ oldest = i;
1483
+ }
1484
+ i++;
1485
+ }
1486
+ i = oldest;
1487
+ for (const k of this.keys()) {
1488
+ if (i == 0) {
1489
+ oldest = k;
1490
+ break;
1491
+ }
1492
+ i--;
1493
+ }
1494
+ this.delete(oldest);
1495
+ }
1496
+ };
1497
+ $._tic = new $._TimedCache();
1498
+ $.textCache = (b, maxSize) => {
1499
+ if (maxSize) $._tic.maxSize = maxSize;
1500
+ if (b !== undefined) $._textCache = b;
1501
+ return $._textCache;
1502
+ };
1503
+ $._genTextImageKey = (str, w, h) => {
1504
+ return (
1505
+ str.slice(0, 200) +
1506
+ $._textStyle +
1507
+ $._textSize +
1508
+ $._textFont +
1509
+ ($._doFill ? $.ctx.fillStyle : '') +
1510
+ '_' +
1511
+ ($._doStroke && $._strokeSet ? $.ctx.lineWidth + $.ctx.strokeStyle + '_' : '') +
1512
+ (w || '') +
1513
+ (h ? 'x' + h : '')
1514
+ );
1515
+ };
1516
+ $.createTextImage = (str, w, h) => {
1517
+ let og = $._textCache;
1518
+ $._textCache = true;
1519
+ $._genTextImage = true;
1520
+ $.text(str, 0, 0, w, h);
1521
+ $._genTextImage = false;
1522
+ let k = $._genTextImageKey(str, w, h);
1523
+ $._textCache = og;
1524
+ return $._tic.get(k);
1525
+ };
1526
+ $.text = (str, x, y, w, h) => {
1527
+ if (str === undefined) return;
1528
+ str = str.toString();
1529
+ if ($._da) {
1530
+ x *= $._da;
1531
+ y *= $._da;
1532
+ }
1533
+ if (!$._doFill && !$._doStroke) return;
1534
+ let c, ti, tg, k, cX, cY, _ascent, _descent;
1535
+ let t = $.ctx.getTransform();
1536
+ let useCache = $._genTextImage || ($._textCache && (t.b != 0 || t.c != 0));
1537
+ if (!useCache) {
1538
+ c = $.ctx;
1539
+ cX = x;
1540
+ cY = y;
1541
+ } else {
1542
+ k = $._genTextImageKey(str, w, h);
1543
+ ti = $._tic.get(k);
1544
+ if (ti && !$._genTextImage) {
1545
+ $.textImage(ti, x, y);
1546
+ return;
1547
+ }
1548
+ tg = $.createGraphics.call($, 1, 1);
1549
+ c = tg.ctx;
1550
+ }
1551
+ c.font = `${$._textStyle} ${$._textSize}px ${$._textFont}`;
1552
+ let lines = str.split('\n');
1553
+ if (useCache) {
1554
+ cX = 0;
1555
+ cY = $._textLeading * lines.length;
1556
+ let m = c.measureText(' ');
1557
+ _ascent = m.fontBoundingBoxAscent;
1558
+ _descent = m.fontBoundingBoxDescent;
1559
+ h ??= cY + _descent;
1560
+ tg.resizeCanvas(Math.ceil(c.measureText(str).width), Math.ceil(h));
1561
+
1562
+ c.fillStyle = $.ctx.fillStyle;
1563
+ c.strokeStyle = $.ctx.strokeStyle;
1564
+ c.lineWidth = $.ctx.lineWidth;
1565
+ }
1566
+ let f = c.fillStyle;
1567
+ if (!$._fillSet) c.fillStyle = 'black';
1568
+ for (let i = 0; i < lines.length; i++) {
1569
+ if ($._doStroke && $._strokeSet) c.strokeText(lines[i], cX, cY);
1570
+ if ($._doFill) c.fillText(lines[i], cX, cY);
1571
+ cY += $._textLeading;
1572
+ if (cY > h) break;
1573
+ }
1574
+ if (!$._fillSet) c.fillStyle = f;
1575
+ if (useCache) {
1576
+ ti = tg.get();
1577
+ ti._ascent = _ascent;
1578
+ ti._descent = _descent;
1579
+ $._tic.set(k, ti);
1580
+ if (!$._genTextImage) $.textImage(ti, x, y);
1581
+ }
1582
+ };
1583
+ $.textImage = (img, x, y) => {
1584
+ let og = $._imageMode;
1585
+ $._imageMode = 'corner';
1586
+ if ($.ctx.textAlign == 'center') x -= img.width * 0.5;
1587
+ else if ($.ctx.textAlign == 'right') x -= img.width;
1588
+ if ($.ctx.textBaseline == 'alphabetic') y -= $._textLeading;
1589
+ if ($.ctx.textBaseline == 'middle') y -= img._descent + img._ascent * 0.5 + $._textLeadDiff;
1590
+ else if ($.ctx.textBaseline == 'bottom') y -= img._ascent + img._descent + $._textLeadDiff;
1591
+ else if ($.ctx.textBaseline == 'top') y -= img._descent + $._textLeadDiff;
1592
+ $.image(img, x, y);
1593
+ $._imageMode = og;
1594
+ };
1595
+
1596
+ $.nf = (n, l, r) => {
1597
+ let neg = n < 0;
1598
+ n = Math.abs(n);
1599
+ let parts = n.toFixed(r).split('.');
1600
+ parts[0] = parts[0].padStart(l, '0');
1601
+ let s = parts.join('.');
1602
+ if (neg) s = '-' + s;
1603
+ return s;
1604
+ };
1605
+ };
1606
+ Q5.modules.ai = ($) => {
1607
+ $.askAI = (question = '') => {
1608
+ throw Error('Ask AI ✨ ' + question);
1609
+ };
1610
+
1611
+ $._aiErrorAssistance = async (e) => {
1612
+ let askAI = e.message?.includes('Ask AI ✨');
1613
+ if (!askAI) console.error(e);
1614
+ if (Q5.disableFriendlyErrors) return;
1615
+ if (askAI || !Q5.errorTolerant) $.noLoop();
1616
+ let stackLines = e.stack?.split('\n');
1617
+ if (!e.stack || stackLines.length <= 1) return;
1618
+
1619
+ let idx = 1;
1620
+ let sep = '(';
1621
+ if (navigator.userAgent.indexOf('Chrome') == -1) {
1622
+ idx = 0;
1623
+ sep = '@';
1624
+ }
1625
+ while (stackLines[idx].indexOf('q5.js:') >= 0) idx++;
1626
+
1627
+ let parts = stackLines[idx].split(sep).at(-1);
1628
+ parts = parts.split(':');
1629
+ let lineNum = parseInt(parts.at(-2));
1630
+ if (askAI) lineNum++;
1631
+ let fileUrl = parts.slice(0, -2).join(':');
1632
+ let fileBase = fileUrl.split('/').at(-1);
1633
+
1634
+ try {
1635
+ let res = await (await fetch(fileUrl)).text();
1636
+ let lines = res.split('\n');
1637
+ let errLine = lines[lineNum - 1].trim();
1638
+
1639
+ let context = '';
1640
+ let i = 1;
1641
+ while (context.length < 1600) {
1642
+ if (lineNum - i >= 0) {
1643
+ context = lines[lineNum - i].trim() + '\n' + context;
1644
+ }
1645
+ if (lineNum + i < lines.length) {
1646
+ context += lines[lineNum + i].trim() + '\n';
1647
+ } else break;
1648
+ i++;
1649
+ }
1650
+
1651
+ let question =
1652
+ askAI && e.message.length > 10 ? e.message.slice(10) : 'Whats+wrong+with+this+line%3F+short+answer';
1653
+
1654
+ let url =
1655
+ 'https://chatgpt.com/?q=q5.js+' +
1656
+ question +
1657
+ (askAI ? '' : '%0A%0A' + encodeURIComponent(e.name + ': ' + e.message)) +
1658
+ '%0A%0ALine%3A+' +
1659
+ encodeURIComponent(errLine) +
1660
+ '%0A%0AExcerpt+for+context%3A%0A%0A' +
1661
+ encodeURIComponent(context);
1662
+
1663
+ if (!askAI) console.log('Error in ' + fileBase + ' on line ' + lineNum + ':\n\n' + errLine);
1664
+
1665
+ console.warn('Ask AI ✨ ' + url);
1666
+
1667
+ if (askAI) window.open(url, '_blank');
1668
+ } catch (err) {}
1669
+ };
1670
+ };
1671
+ Q5.modules.color = ($, q) => {
1672
+ $.RGB = $.RGBA = $._colorMode = 'rgb';
1673
+ $.OKLCH = 'oklch';
1674
+
1675
+ $.colorMode = (mode, format) => {
1676
+ $._colorMode = mode;
1677
+ let srgb = $.canvas.colorSpace == 'srgb' || mode == 'srgb';
1678
+ format ??= srgb ? 'integer' : 'float';
1679
+ $._colorFormat = format;
1680
+ if (mode == 'oklch') {
1681
+ q.Color = Q5.ColorOKLCH;
1682
+ } else {
1683
+ let srgb = $.canvas.colorSpace == 'srgb';
1684
+ if ($._colorFormat == 'integer') {
1685
+ q.Color = srgb ? Q5.ColorRGBA_8 : Q5.ColorRGBA_P3_8;
1686
+ } else {
1687
+ q.Color = srgb ? Q5.ColorRGBA : Q5.ColorRGBA_P3;
1688
+ }
1689
+ $._colorMode = 'rgb';
1690
+ }
1691
+ };
1692
+
1693
+ $._namedColors = {
1694
+ aqua: [0, 255, 255],
1695
+ black: [0, 0, 0],
1696
+ blue: [0, 0, 255],
1697
+ brown: [165, 42, 42],
1698
+ crimson: [220, 20, 60],
1699
+ cyan: [0, 255, 255],
1700
+ darkviolet: [148, 0, 211],
1701
+ gold: [255, 215, 0],
1702
+ green: [0, 128, 0],
1703
+ gray: [128, 128, 128],
1704
+ grey: [128, 128, 128],
1705
+ hotpink: [255, 105, 180],
1706
+ indigo: [75, 0, 130],
1707
+ khaki: [240, 230, 140],
1708
+ lightgreen: [144, 238, 144],
1709
+ lime: [0, 255, 0],
1710
+ magenta: [255, 0, 255],
1711
+ navy: [0, 0, 128],
1712
+ orange: [255, 165, 0],
1713
+ olive: [128, 128, 0],
1714
+ peachpuff: [255, 218, 185],
1715
+ pink: [255, 192, 203],
1716
+ purple: [128, 0, 128],
1717
+ red: [255, 0, 0],
1718
+ skyblue: [135, 206, 235],
1719
+ tan: [210, 180, 140],
1720
+ turquoise: [64, 224, 208],
1721
+ transparent: [0, 0, 0, 0],
1722
+ white: [255, 255, 255],
1723
+ violet: [238, 130, 238],
1724
+ yellow: [255, 255, 0]
1725
+ };
1726
+
1727
+ $.color = function (c0, c1, c2, c3) {
1728
+ let C = $.Color;
1729
+ if (c0._q5Color) return new C(...c0.levels);
1730
+ let args = arguments;
1731
+ if (args.length == 1) {
1732
+ if (typeof c0 == 'string') {
1733
+ let r, g, b, a;
1734
+ if (c0[0] == '#') {
1735
+ if (c0.length <= 5) {
1736
+ r = parseInt(c0[1] + c0[1], 16);
1737
+ g = parseInt(c0[2] + c0[2], 16);
1738
+ b = parseInt(c0[3] + c0[3], 16);
1739
+ a = c0.length == 4 ? null : parseInt(c0[4] + c0[4], 16);
1740
+ } else {
1741
+ r = parseInt(c0.slice(1, 3), 16);
1742
+ g = parseInt(c0.slice(3, 5), 16);
1743
+ b = parseInt(c0.slice(5, 7), 16);
1744
+ a = c0.length == 7 ? null : parseInt(c0.slice(7, 9), 16);
1745
+ }
1746
+ } else if ($._namedColors[c0]) [r, g, b] = $._namedColors[c0];
1747
+ else {
1748
+ console.error(
1749
+ "q5 can't parse color: " + c0 + '\nOnly numeric input, hex, and common named colors are supported.'
1750
+ );
1751
+ return new C(0, 0, 0);
1752
+ }
1753
+ if ($._colorFormat != 'integer') {
1754
+ r /= 255;
1755
+ g /= 255;
1756
+ b /= 255;
1757
+ if (a != null) a /= 255;
1758
+ }
1759
+ return new C(r, g, b, a);
1760
+ }
1761
+ if (Array.isArray(c0)) return new C(...c0);
1762
+ }
1763
+ if ($._colorMode == 'rgb') {
1764
+ if (args.length == 1) return new C(c0, c0, c0);
1765
+ if (args.length == 2) return new C(c0, c0, c0, c1);
1766
+ }
1767
+ if (args.length == 3) return new C(c0, c1, c2);
1768
+ if (args.length == 4) return new C(c0, c1, c2, c3);
1769
+ };
1770
+
1771
+ $.red = (c) => c.r;
1772
+ $.green = (c) => c.g;
1773
+ $.blue = (c) => c.b;
1774
+ $.alpha = (c) => c.a;
1775
+ $.lightness = (c) => {
1776
+ return ((0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) * 100) / 255;
1777
+ };
1778
+
1779
+ $.lerpColor = (a, b, t) => {
1780
+ if ($._colorMode == 'rgb') {
1781
+ return new $.Color(
1782
+ $.constrain($.lerp(a.r, b.r, t), 0, 255),
1783
+ $.constrain($.lerp(a.g, b.g, t), 0, 255),
1784
+ $.constrain($.lerp(a.b, b.b, t), 0, 255),
1785
+ $.constrain($.lerp(a.a, b.a, t), 0, 255)
1786
+ );
1787
+ } else {
1788
+ let deltaH = b.h - a.h;
1789
+ if (deltaH > 180) deltaH -= 360;
1790
+ if (deltaH < -180) deltaH += 360;
1791
+ let h = a.h + t * deltaH;
1792
+ if (h < 0) h += 360;
1793
+ if (h > 360) h -= 360;
1794
+ return new $.Color(
1795
+ $.constrain($.lerp(a.l, b.l, t), 0, 100),
1796
+ $.constrain($.lerp(a.c, b.c, t), 0, 100),
1797
+ h,
1798
+ $.constrain($.lerp(a.a, b.a, t), 0, 255)
1799
+ );
1800
+ }
1801
+ };
1802
+ };
1803
+
1804
+ // COLOR CLASSES
1805
+
1806
+ Q5.Color = class {
1807
+ constructor() {
1808
+ this._q5Color = true;
1809
+ }
1810
+ };
1811
+ Q5.ColorOKLCH = class extends Q5.Color {
1812
+ constructor(l, c, h, a) {
1813
+ super();
1814
+ this.l = l;
1815
+ this.c = c;
1816
+ this.h = h;
1817
+ this.a = a ?? 1;
1818
+ }
1819
+ toString() {
1820
+ return `oklch(${this.l} ${this.c} ${this.h} / ${this.a})`;
1821
+ }
1822
+ };
1823
+ Q5.ColorRGBA = class extends Q5.Color {
1824
+ constructor(r, g, b, a) {
1825
+ super();
1826
+ this.r = r;
1827
+ this.g = g;
1828
+ this.b = b;
1829
+ this.a = a ?? 1;
1830
+ }
1831
+ get levels() {
1832
+ return [this.r, this.g, this.b, this.a];
1833
+ }
1834
+ toString() {
1835
+ return `color(srgb ${this.r} ${this.g} ${this.b} / ${this.a})`;
1836
+ }
1837
+ };
1838
+ Q5.ColorRGBA_P3 = class extends Q5.ColorRGBA {
1839
+ toString() {
1840
+ return `color(display-p3 ${this.r} ${this.g} ${this.b} / ${this.a})`;
1841
+ }
1842
+ };
1843
+ // legacy 8-bit (0-255) integer color format
1844
+ Q5.ColorRGBA_8 = class extends Q5.ColorRGBA {
1845
+ constructor(r, g, b, a) {
1846
+ super(r, g, b, a ?? 255);
1847
+ }
1848
+ setRed(v) {
1849
+ this.r = v;
1850
+ }
1851
+ setGreen(v) {
1852
+ this.g = v;
1853
+ }
1854
+ setBlue(v) {
1855
+ this.b = v;
1856
+ }
1857
+ setAlpha(v) {
1858
+ this.a = v;
1859
+ }
1860
+ get levels() {
1861
+ return [this.r, this.g, this.b, this.a];
1862
+ }
1863
+ toString() {
1864
+ return `rgb(${this.r} ${this.g} ${this.b} / ${this.a / 255})`;
1865
+ }
1866
+ };
1867
+ // p3 10-bit color in integer color format, for backwards compatibility
1868
+ Q5.ColorRGBA_P3_8 = class extends Q5.ColorRGBA {
1869
+ constructor(r, g, b, a) {
1870
+ super(r, g, b, a ?? 255);
1871
+ this._edited = true;
1872
+ }
1873
+ get r() {
1874
+ return this._r;
1875
+ }
1876
+ set r(v) {
1877
+ this._r = v;
1878
+ this._edited = true;
1879
+ }
1880
+ get g() {
1881
+ return this._g;
1882
+ }
1883
+ set g(v) {
1884
+ this._g = v;
1885
+ this._edited = true;
1886
+ }
1887
+ get b() {
1888
+ return this._b;
1889
+ }
1890
+ set b(v) {
1891
+ this._b = v;
1892
+ this._edited = true;
1893
+ }
1894
+ get a() {
1895
+ return this._a;
1896
+ }
1897
+ set a(v) {
1898
+ this._a = v;
1899
+ this._edited = true;
1900
+ }
1901
+ toString() {
1902
+ if (this._edited) {
1903
+ let r = (this._r / 255).toFixed(3);
1904
+ let g = (this._g / 255).toFixed(3);
1905
+ let b = (this._b / 255).toFixed(3);
1906
+ let a = (this._a / 255).toFixed(3);
1907
+ this._css = `color(display-p3 ${r} ${g} ${b} / ${a})`;
1908
+ this._edited = false;
1909
+ }
1910
+ return this._css;
1911
+ }
1912
+ };
1913
+ Q5.modules.display = ($) => {
1914
+ if (!$.canvas || $._scope == 'graphics') return;
1915
+
1916
+ let c = $.canvas;
1917
+
1918
+ if (Q5._instanceCount == 0 && !Q5._nodejs) {
1919
+ document.head.insertAdjacentHTML(
1920
+ 'beforeend',
1921
+ `<style>
1922
+ html, body {
1923
+ margin: 0;
1924
+ padding: 0;
1925
+ }
1926
+ .q5Canvas {
1927
+ outline: none;
1928
+ -webkit-touch-callout: none;
1929
+ -webkit-text-size-adjust: none;
1930
+ -webkit-user-select: none;
1931
+ overscroll-behavior: none;
1932
+ }
1933
+ .q5-pixelated {
1934
+ image-rendering: pixelated;
1935
+ font-smooth: never;
1936
+ -webkit-font-smoothing: none;
1937
+ }
1938
+ .q5-centered,
1939
+ .q5-maxed,
1940
+ .q5-fullscreen {
1941
+ display: flex;
1942
+ align-items: center;
1943
+ justify-content: center;
1944
+ }
1945
+ main.q5-centered,
1946
+ main.q5-maxed,
1947
+ .q5-fullscreen {
1948
+ height: 100vh;
1949
+ }
1950
+ main {
1951
+ overscroll-behavior: none;
1952
+ }
1953
+ </style>`
1954
+ );
1955
+ }
1956
+
1957
+ $._adjustDisplay = () => {
1958
+ let s = c.style;
1959
+ let p = c.parentElement;
1960
+ if (!s || !p || !c.displayMode) return;
1961
+ if (c.renderQuality == 'pixelated') {
1962
+ c.classList.add('q5-pixelated');
1963
+ $.pixelDensity(1);
1964
+ if ($.noSmooth) $.noSmooth();
1965
+ if ($.textFont) $.textFont('monospace');
1966
+ }
1967
+ if (c.displayMode == 'normal') {
1968
+ p.classList.remove('q5-centered', 'q5-maxed', 'q5-fullscreen');
1969
+ s.width = c.w * c.displayScale + 'px';
1970
+ s.height = c.h * c.displayScale + 'px';
1971
+ } else {
1972
+ p.classList.add('q5-' + c.displayMode);
1973
+ p = p.getBoundingClientRect();
1974
+ if (c.w / c.h > p.width / p.height) {
1975
+ if (c.displayMode == 'centered') {
1976
+ s.width = c.w * c.displayScale + 'px';
1977
+ s.maxWidth = '100%';
1978
+ } else s.width = '100%';
1979
+ s.height = 'auto';
1980
+ s.maxHeight = '';
1981
+ } else {
1982
+ s.width = 'auto';
1983
+ s.maxWidth = '';
1984
+ if (c.displayMode == 'centered') {
1985
+ s.height = c.h * c.displayScale + 'px';
1986
+ s.maxHeight = '100%';
1987
+ } else s.height = '100%';
1988
+ }
1989
+ }
1990
+ };
1991
+
1992
+ $.displayMode = (displayMode = 'normal', renderQuality = 'default', displayScale = 1) => {
1993
+ if (typeof displayScale == 'string') {
1994
+ displayScale = parseFloat(displayScale.slice(1));
1995
+ }
1996
+ Object.assign(c, { displayMode, renderQuality, displayScale });
1997
+ $._adjustDisplay();
1998
+ };
1999
+
2000
+ $.fullscreen = (v) => {
2001
+ if (v === undefined) return document.fullscreenElement;
2002
+ if (v) document.body.requestFullscreen();
2003
+ else document.body.exitFullscreen();
2004
+ };
2005
+ };
2006
+ Q5.modules.input = ($, q) => {
2007
+ if ($._scope == 'graphics') return;
2008
+
2009
+ $.mouseX = 0;
2010
+ $.mouseY = 0;
2011
+ $.pmouseX = 0;
2012
+ $.pmouseY = 0;
2013
+ $.touches = [];
2014
+ $.mouseButton = null;
2015
+ $.keyIsPressed = false;
2016
+ $.mouseIsPressed = false;
2017
+ $.key = null;
2018
+ $.keyCode = null;
2019
+
2020
+ $.UP_ARROW = 38;
2021
+ $.DOWN_ARROW = 40;
2022
+ $.LEFT_ARROW = 37;
2023
+ $.RIGHT_ARROW = 39;
2024
+ $.SHIFT = 16;
2025
+ $.TAB = 9;
2026
+ $.BACKSPACE = 8;
2027
+ $.ENTER = $.RETURN = 13;
2028
+ $.ALT = $.OPTION = 18;
2029
+ $.CONTROL = 17;
2030
+ $.DELETE = 46;
2031
+ $.ESCAPE = 27;
2032
+
2033
+ $.ARROW = 'default';
2034
+ $.CROSS = 'crosshair';
2035
+ $.HAND = 'pointer';
2036
+ $.MOVE = 'move';
2037
+ $.TEXT = 'text';
2038
+
2039
+ let keysHeld = {};
2040
+ let mouseBtns = [$.LEFT, $.CENTER, $.RIGHT];
2041
+
2042
+ let c = $.canvas;
2043
+
2044
+ $._startAudio = () => {
2045
+ if ($.getAudioContext && $.getAudioContext()?.state == 'suspended') $.userStartAudio();
2046
+ };
2047
+
2048
+ $._updateMouse = (e) => {
2049
+ if (e.changedTouches) return;
2050
+ if (c) {
2051
+ let rect = c.getBoundingClientRect();
2052
+ let sx = c.scrollWidth / $.width || 1;
2053
+ let sy = c.scrollHeight / $.height || 1;
2054
+ q.mouseX = (e.clientX - rect.left) / sx;
2055
+ q.mouseY = (e.clientY - rect.top) / sy;
2056
+ if (c.renderer == 'webgpu') {
2057
+ q.mouseX -= c.hw;
2058
+ q.mouseY -= c.hh;
2059
+ }
2060
+ } else {
2061
+ q.mouseX = e.clientX;
2062
+ q.mouseY = e.clientY;
2063
+ }
2064
+ };
2065
+ $._onmousedown = (e) => {
2066
+ $._startAudio();
2067
+ $._updateMouse(e);
2068
+ q.mouseIsPressed = true;
2069
+ q.mouseButton = mouseBtns[e.button];
2070
+ $.mousePressed(e);
2071
+ };
2072
+ $._onmousemove = (e) => {
2073
+ $._updateMouse(e);
2074
+ if ($.mouseIsPressed) $.mouseDragged(e);
2075
+ else $.mouseMoved(e);
2076
+ };
2077
+ $._onmouseup = (e) => {
2078
+ $._updateMouse(e);
2079
+ q.mouseIsPressed = false;
2080
+ $.mouseReleased(e);
2081
+ };
2082
+ $._onclick = (e) => {
2083
+ $._updateMouse(e);
2084
+ q.mouseIsPressed = true;
2085
+ $.mouseClicked(e);
2086
+ q.mouseIsPressed = false;
2087
+ };
2088
+
2089
+ $.cursor = (name, x, y) => {
2090
+ let pfx = '';
2091
+ if (name.includes('.')) {
2092
+ name = `url("${name}")`;
2093
+ pfx = ', auto';
2094
+ }
2095
+ if (x !== undefined) {
2096
+ name += ' ' + x + ' ' + y;
2097
+ }
2098
+ $.canvas.style.cursor = name + pfx;
2099
+ };
2100
+ $.noCursor = () => {
2101
+ $.canvas.style.cursor = 'none';
2102
+ };
2103
+ $.requestPointerLock = document.body?.requestPointerLock;
2104
+ $.exitPointerLock = document.exitPointerLock;
2105
+
2106
+ $._onkeydown = (e) => {
2107
+ if (e.repeat) return;
2108
+ $._startAudio();
2109
+ q.keyIsPressed = true;
2110
+ q.key = e.key;
2111
+ q.keyCode = e.keyCode;
2112
+ keysHeld[$.keyCode] = keysHeld[$.key.toLowerCase()] = true;
2113
+ $.keyPressed(e);
2114
+ if (e.key.length == 1) $.keyTyped(e);
2115
+ };
2116
+ $._onkeyup = (e) => {
2117
+ q.keyIsPressed = false;
2118
+ q.key = e.key;
2119
+ q.keyCode = e.keyCode;
2120
+ keysHeld[$.keyCode] = keysHeld[$.key.toLowerCase()] = false;
2121
+ $.keyReleased(e);
2122
+ };
2123
+ $.keyIsDown = (v) => !!keysHeld[typeof v == 'string' ? v.toLowerCase() : v];
2124
+
2125
+ function getTouchInfo(touch) {
2126
+ const rect = $.canvas.getBoundingClientRect();
2127
+ const sx = $.canvas.scrollWidth / $.width || 1;
2128
+ const sy = $.canvas.scrollHeight / $.height || 1;
2129
+ return {
2130
+ x: (touch.clientX - rect.left) / sx,
2131
+ y: (touch.clientY - rect.top) / sy,
2132
+ id: touch.identifier
2133
+ };
2134
+ }
2135
+ $._ontouchstart = (e) => {
2136
+ $._startAudio();
2137
+ q.touches = [...e.touches].map(getTouchInfo);
2138
+ if (!$._isTouchAware) {
2139
+ q.mouseX = $.touches[0].x;
2140
+ q.mouseY = $.touches[0].y;
2141
+ q.mouseIsPressed = true;
2142
+ q.mouseButton = $.LEFT;
2143
+ if (!$.mousePressed(e)) e.preventDefault();
2144
+ }
2145
+ if (!$.touchStarted(e)) e.preventDefault();
2146
+ };
2147
+ $._ontouchmove = (e) => {
2148
+ q.touches = [...e.touches].map(getTouchInfo);
2149
+ if (!$._isTouchAware) {
2150
+ q.mouseX = $.touches[0].x;
2151
+ q.mouseY = $.touches[0].y;
2152
+ if (!$.mouseDragged(e)) e.preventDefault();
2153
+ }
2154
+ if (!$.touchMoved(e)) e.preventDefault();
2155
+ };
2156
+ $._ontouchend = (e) => {
2157
+ q.touches = [...e.touches].map(getTouchInfo);
2158
+ if (!$._isTouchAware && !$.touches.length) {
2159
+ q.mouseIsPressed = false;
2160
+ if (!$.mouseReleased(e)) e.preventDefault();
2161
+ }
2162
+ if (!$.touchEnded(e)) e.preventDefault();
2163
+ };
2164
+
2165
+ if (c) {
2166
+ c.addEventListener('mousedown', (e) => $._onmousedown(e));
2167
+ c.addEventListener('mouseup', (e) => $._onmouseup(e));
2168
+ c.addEventListener('click', (e) => $._onclick(e));
2169
+ c.addEventListener('touchstart', (e) => $._ontouchstart(e));
2170
+ c.addEventListener('touchmove', (e) => $._ontouchmove(e));
2171
+ c.addEventListener('touchcancel', (e) => $._ontouchend(e));
2172
+ c.addEventListener('touchend', (e) => $._ontouchend(e));
2173
+ }
2174
+
2175
+ if (window) {
2176
+ let l = window.addEventListener;
2177
+ l('mousemove', (e) => $._onmousemove(e), false);
2178
+ l('keydown', (e) => $._onkeydown(e), false);
2179
+ l('keyup', (e) => $._onkeyup(e), false);
2180
+ }
2181
+ };
2182
+ Q5.modules.math = ($, q) => {
2183
+ $.DEGREES = 'degrees';
2184
+ $.RADIANS = 'radians';
2185
+
2186
+ $.PI = Math.PI;
2187
+ $.HALF_PI = Math.PI / 2;
2188
+ $.QUARTER_PI = Math.PI / 4;
2189
+
2190
+ $.abs = Math.abs;
2191
+ $.ceil = Math.ceil;
2192
+ $.exp = Math.exp;
2193
+ $.floor = Math.floor;
2194
+ $.loge = Math.log;
2195
+ $.mag = Math.hypot;
2196
+ $.max = Math.max;
2197
+ $.min = Math.min;
2198
+ $.round = Math.round;
2199
+ $.pow = Math.pow;
2200
+ $.sqrt = Math.sqrt;
2201
+
2202
+ $.SHR3 = 1;
2203
+ $.LCG = 2;
2204
+
2205
+ $.angleMode = (mode) => ($._angleMode = mode);
2206
+ $._DEGTORAD = Math.PI / 180;
2207
+ $._RADTODEG = 180 / Math.PI;
2208
+ $.degrees = (x) => x * $._RADTODEG;
2209
+ $.radians = (x) => x * $._DEGTORAD;
2210
+
2211
+ $.map = Q5.prototype.map = (value, istart, istop, ostart, ostop, clamp) => {
2212
+ let val = ostart + (ostop - ostart) * (((value - istart) * 1.0) / (istop - istart));
2213
+ if (!clamp) {
2214
+ return val;
2215
+ }
2216
+ if (ostart < ostop) {
2217
+ return Math.min(Math.max(val, ostart), ostop);
2218
+ } else {
2219
+ return Math.min(Math.max(val, ostop), ostart);
2220
+ }
2221
+ };
2222
+ $.lerp = (a, b, t) => a * (1 - t) + b * t;
2223
+ $.constrain = (x, lo, hi) => Math.min(Math.max(x, lo), hi);
2224
+ $.dist = function () {
2225
+ let a = arguments;
2226
+ if (a.length == 4) return Math.hypot(a[0] - a[2], a[1] - a[3]);
2227
+ else return Math.hypot(a[0] - a[3], a[1] - a[4], a[2] - a[5]);
2228
+ };
2229
+ $.norm = (value, start, stop) => $.map(value, start, stop, 0, 1);
2230
+ $.sq = (x) => x * x;
2231
+ $.fract = (x) => x - Math.floor(x);
2232
+ $.sin = (a) => {
2233
+ if ($._angleMode == 'degrees') a = $.radians(a);
2234
+ return Math.sin(a);
2235
+ };
2236
+ $.cos = (a) => {
2237
+ if ($._angleMode == 'degrees') a = $.radians(a);
2238
+ return Math.cos(a);
2239
+ };
2240
+ $.tan = (a) => {
2241
+ if ($._angleMode == 'degrees') a = $.radians(a);
2242
+ return Math.tan(a);
2243
+ };
2244
+ $.asin = (x) => {
2245
+ let a = Math.asin(x);
2246
+ if ($._angleMode == 'degrees') a = $.degrees(a);
2247
+ return a;
2248
+ };
2249
+ $.acos = (x) => {
2250
+ let a = Math.acos(x);
2251
+ if ($._angleMode == 'degrees') a = $.degrees(a);
2252
+ return a;
2253
+ };
2254
+ $.atan = (x) => {
2255
+ let a = Math.atan(x);
2256
+ if ($._angleMode == 'degrees') a = $.degrees(a);
2257
+ return a;
2258
+ };
2259
+ $.atan2 = (y, x) => {
2260
+ let a = Math.atan2(y, x);
2261
+ if ($._angleMode == 'degrees') a = $.degrees(a);
2262
+ return a;
2263
+ };
2264
+
2265
+ function lcg() {
2266
+ const m = 4294967296;
2267
+ const a = 1664525;
2268
+ const c = 1013904223;
2269
+ let seed, z;
2270
+ return {
2271
+ setSeed(val) {
2272
+ z = seed = (val == null ? Math.random() * m : val) >>> 0;
2273
+ },
2274
+ getSeed() {
2275
+ return seed;
2276
+ },
2277
+ rand() {
2278
+ z = (a * z + c) % m;
2279
+ return z / m;
2280
+ }
2281
+ };
2282
+ }
2283
+ function shr3() {
2284
+ let jsr, seed;
2285
+ let m = 4294967295;
2286
+ return {
2287
+ setSeed(val) {
2288
+ jsr = seed = (val == null ? Math.random() * m : val) >>> 0;
2289
+ },
2290
+ getSeed() {
2291
+ return seed;
2292
+ },
2293
+ rand() {
2294
+ jsr ^= jsr << 17;
2295
+ jsr ^= jsr >> 13;
2296
+ jsr ^= jsr << 5;
2297
+ return (jsr >>> 0) / m;
2298
+ }
2299
+ };
2300
+ }
2301
+ let rng1 = shr3();
2302
+ rng1.setSeed();
2303
+
2304
+ $.randomSeed = (seed) => rng1.setSeed(seed);
2305
+ $.random = (a, b) => {
2306
+ if (a === undefined) return rng1.rand();
2307
+ if (typeof a == 'number') {
2308
+ if (b !== undefined) {
2309
+ return rng1.rand() * (b - a) + a;
2310
+ } else {
2311
+ return rng1.rand() * a;
2312
+ }
2313
+ } else {
2314
+ return a[Math.trunc(a.length * rng1.rand())];
2315
+ }
2316
+ };
2317
+ $.randomGenerator = (method) => {
2318
+ if (method == $.LCG) rng1 = lcg();
2319
+ else if (method == $.SHR3) rng1 = shr3();
2320
+ rng1.setSeed();
2321
+ };
2322
+
2323
+ var ziggurat = new (function () {
2324
+ var iz;
2325
+ var jz;
2326
+ var kn = new Array(128);
2327
+ var ke = new Array(256);
2328
+ var hz;
2329
+ var wn = new Array(128);
2330
+ var fn = new Array(128);
2331
+ var we = new Array(256);
2332
+ var fe = new Array(256);
2333
+ var SHR3 = () => {
2334
+ return rng1.rand() * 4294967296 - 2147483648;
2335
+ };
2336
+ var UNI = () => {
2337
+ return 0.5 + (SHR3() << 0) * 0.2328306e-9;
2338
+ };
2339
+
2340
+ var RNOR = () => {
2341
+ hz = SHR3();
2342
+ iz = hz & 127;
2343
+ return Math.abs(hz) < kn[iz] ? hz * wn[iz] : nfix();
2344
+ };
2345
+ var REXP = () => {
2346
+ jz = SHR3() >>> 0;
2347
+ iz = jz & 255;
2348
+ return jz < kn[iz] ? jz * we[iz] : efix();
2349
+ };
2350
+ var nfix = () => {
2351
+ var r = 3.44262;
2352
+ var x, y;
2353
+ var u1, u2;
2354
+ for (;;) {
2355
+ x = hz * wn[iz];
2356
+ if (iz == 0) {
2357
+ do {
2358
+ u1 = UNI();
2359
+ u2 = UNI();
2360
+ x = -Math.log(u1) * 0.2904764;
2361
+ y = -Math.log(u2);
2362
+ } while (y + y < x * x);
2363
+ return hz > 0 ? r + x : -r - x;
2364
+ }
2365
+
2366
+ if (fn[iz] + UNI() * (fn[iz - 1] - fn[iz]) < Math.exp(-0.5 * x * x)) {
2367
+ return x;
2368
+ }
2369
+ hz = SHR3();
2370
+ iz = hz & 127;
2371
+ if (Math.abs(hz) < kn[iz]) {
2372
+ return hz * wn[iz];
2373
+ }
2374
+ }
2375
+ };
2376
+ var efix = () => {
2377
+ var x;
2378
+ for (;;) {
2379
+ if (iz == 0) {
2380
+ return 7.69711 - Math.log(UNI());
2381
+ }
2382
+ x = jz * we[iz];
2383
+ if (fe[iz] + UNI() * (fe[iz - 1] - fe[iz]) < Math.exp(-x)) {
2384
+ return x;
2385
+ }
2386
+ jz = SHR3();
2387
+ iz = jz & 255;
2388
+ if (jz < ke[iz]) {
2389
+ return jz * we[iz];
2390
+ }
2391
+ }
2392
+ };
2393
+
2394
+ var zigset = () => {
2395
+ var m1 = 2147483648;
2396
+ var m2 = 4294967296;
2397
+ var dn = 3.442619855899;
2398
+ var tn = dn;
2399
+ var vn = 9.91256303526217e-3;
2400
+ var q;
2401
+ var de = 7.697117470131487;
2402
+ var te = de;
2403
+ var ve = 3.949659822581572e-3;
2404
+ var i;
2405
+
2406
+ /* Tables for RNOR */
2407
+ q = vn / Math.exp(-0.5 * dn * dn);
2408
+ kn[0] = Math.floor((dn / q) * m1);
2409
+ kn[1] = 0;
2410
+ wn[0] = q / m1;
2411
+ wn[127] = dn / m1;
2412
+ fn[0] = 1;
2413
+ fn[127] = Math.exp(-0.5 * dn * dn);
2414
+ for (i = 126; i >= 1; i--) {
2415
+ dn = Math.sqrt(-2 * Math.log(vn / dn + Math.exp(-0.5 * dn * dn)));
2416
+ kn[i + 1] = Math.floor((dn / tn) * m1);
2417
+ tn = dn;
2418
+ fn[i] = Math.exp(-0.5 * dn * dn);
2419
+ wn[i] = dn / m1;
2420
+ }
2421
+ /*Tables for REXP */
2422
+ q = ve / Math.exp(-de);
2423
+ ke[0] = Math.floor((de / q) * m2);
2424
+ ke[1] = 0;
2425
+ we[0] = q / m2;
2426
+ we[255] = de / m2;
2427
+ fe[0] = 1;
2428
+ fe[255] = Math.exp(-de);
2429
+ for (i = 254; i >= 1; i--) {
2430
+ de = -Math.log(ve / de + Math.exp(-de));
2431
+ ke[i + 1] = Math.floor((de / te) * m2);
2432
+ te = de;
2433
+ fe[i] = Math.exp(-de);
2434
+ we[i] = de / m2;
2435
+ }
2436
+ };
2437
+ this.SHR3 = SHR3;
2438
+ this.UNI = UNI;
2439
+ this.RNOR = RNOR;
2440
+ this.REXP = REXP;
2441
+ this.zigset = zigset;
2442
+ })();
2443
+ ziggurat.hasInit = false;
2444
+
2445
+ $.randomGaussian = (mean, std) => {
2446
+ if (!ziggurat.hasInit) {
2447
+ ziggurat.zigset();
2448
+ ziggurat.hasInit = true;
2449
+ }
2450
+ return ziggurat.RNOR() * std + mean;
2451
+ };
2452
+
2453
+ $.randomExponential = () => {
2454
+ if (!ziggurat.hasInit) {
2455
+ ziggurat.zigset();
2456
+ ziggurat.hasInit = true;
2457
+ }
2458
+ return ziggurat.REXP();
2459
+ };
2460
+
2461
+ $.PERLIN = 'perlin';
2462
+ $.SIMPLEX = 'simplex';
2463
+ $.BLOCKY = 'blocky';
2464
+
2465
+ $.Noise = Q5.PerlinNoise;
2466
+ let _noise;
2467
+
2468
+ $.noiseMode = (mode) => {
2469
+ q.Noise = Q5[mode[0].toUpperCase() + mode.slice(1) + 'Noise'];
2470
+ _noise = null;
2471
+ };
2472
+ $.noiseSeed = (seed) => {
2473
+ _noise = new $.Noise(seed);
2474
+ };
2475
+ $.noise = (x = 0, y = 0, z = 0) => {
2476
+ _noise ??= new $.Noise();
2477
+ return _noise.noise(x, y, z);
2478
+ };
2479
+ $.noiseDetail = (lod, falloff) => {
2480
+ _noise ??= new $.Noise();
2481
+ if (lod > 0) _noise.octaves = lod;
2482
+ if (falloff > 0) _noise.falloff = falloff;
2483
+ };
2484
+ };
2485
+
2486
+ Q5.Noise = class {};
2487
+
2488
+ Q5.PerlinNoise = class extends Q5.Noise {
2489
+ constructor(seed) {
2490
+ super();
2491
+ this.grad3 = [
2492
+ [1, 1, 0],
2493
+ [-1, 1, 0],
2494
+ [1, -1, 0],
2495
+ [-1, -1, 0],
2496
+ [1, 0, 1],
2497
+ [-1, 0, 1],
2498
+ [1, 0, -1],
2499
+ [-1, 0, -1],
2500
+ [0, 1, 1],
2501
+ [0, -1, 1],
2502
+ [0, 1, -1],
2503
+ [0, -1, -1]
2504
+ ];
2505
+ this.octaves = 1;
2506
+ this.falloff = 0.5;
2507
+ if (seed == undefined) {
2508
+ this.p = Array.from({ length: 256 }, () => Math.floor(Math.random() * 256));
2509
+ } else {
2510
+ this.p = this.seedPermutation(seed);
2511
+ }
2512
+ this.p = this.p.concat(this.p);
2513
+ }
2514
+
2515
+ seedPermutation(seed) {
2516
+ let p = [];
2517
+ for (let i = 0; i < 256; i++) {
2518
+ p[i] = i;
2519
+ }
2520
+
2521
+ let n, q;
2522
+ for (let i = 255; i > 0; i--) {
2523
+ seed = (seed * 16807) % 2147483647;
2524
+ n = seed % (i + 1);
2525
+ q = p[i];
2526
+ p[i] = p[n];
2527
+ p[n] = q;
2528
+ }
2529
+
2530
+ return p;
2531
+ }
2532
+
2533
+ dot(g, x, y, z) {
2534
+ return g[0] * x + g[1] * y + g[2] * z;
2535
+ }
2536
+
2537
+ mix(a, b, t) {
2538
+ return (1 - t) * a + t * b;
2539
+ }
2540
+
2541
+ fade(t) {
2542
+ return t * t * t * (t * (t * 6 - 15) + 10);
2543
+ }
2544
+
2545
+ noise(x, y, z) {
2546
+ let t = this;
2547
+ let total = 0;
2548
+ let freq = 1;
2549
+ let amp = 1;
2550
+ let maxAmp = 0;
2551
+
2552
+ for (let i = 0; i < t.octaves; i++) {
2553
+ const X = Math.floor(x * freq) & 255;
2554
+ const Y = Math.floor(y * freq) & 255;
2555
+ const Z = Math.floor(z * freq) & 255;
2556
+
2557
+ const xf = x * freq - Math.floor(x * freq);
2558
+ const yf = y * freq - Math.floor(y * freq);
2559
+ const zf = z * freq - Math.floor(z * freq);
2560
+
2561
+ const u = t.fade(xf);
2562
+ const v = t.fade(yf);
2563
+ const w = t.fade(zf);
2564
+
2565
+ const A = t.p[X] + Y;
2566
+ const AA = t.p[A] + Z;
2567
+ const AB = t.p[A + 1] + Z;
2568
+ const B = t.p[X + 1] + Y;
2569
+ const BA = t.p[B] + Z;
2570
+ const BB = t.p[B + 1] + Z;
2571
+
2572
+ const lerp1 = t.mix(t.dot(t.grad3[t.p[AA] % 12], xf, yf, zf), t.dot(t.grad3[t.p[BA] % 12], xf - 1, yf, zf), u);
2573
+ const lerp2 = t.mix(
2574
+ t.dot(t.grad3[t.p[AB] % 12], xf, yf - 1, zf),
2575
+ t.dot(t.grad3[t.p[BB] % 12], xf - 1, yf - 1, zf),
2576
+ u
2577
+ );
2578
+ const lerp3 = t.mix(
2579
+ t.dot(t.grad3[t.p[AA + 1] % 12], xf, yf, zf - 1),
2580
+ t.dot(t.grad3[t.p[BA + 1] % 12], xf - 1, yf, zf - 1),
2581
+ u
2582
+ );
2583
+ const lerp4 = t.mix(
2584
+ t.dot(t.grad3[t.p[AB + 1] % 12], xf, yf - 1, zf - 1),
2585
+ t.dot(t.grad3[t.p[BB + 1] % 12], xf - 1, yf - 1, zf - 1),
2586
+ u
2587
+ );
2588
+
2589
+ const mix1 = t.mix(lerp1, lerp2, v);
2590
+ const mix2 = t.mix(lerp3, lerp4, v);
2591
+
2592
+ total += t.mix(mix1, mix2, w) * amp;
2593
+
2594
+ maxAmp += amp;
2595
+ amp *= t.falloff;
2596
+ freq *= 2;
2597
+ }
2598
+
2599
+ return (total / maxAmp + 1) / 2;
2600
+ }
2601
+ };
2602
+ Q5.modules.sound = ($, q) => {
2603
+ $.Sound = Q5.Sound;
2604
+ $.loadSound = (path, cb) => {
2605
+ q._preloadCount++;
2606
+ Q5.aud ??= new window.AudioContext();
2607
+ let a = new Q5.Sound(path, cb);
2608
+ a.addEventListener('canplaythrough', () => {
2609
+ q._preloadCount--;
2610
+ a.loaded = true;
2611
+ if (cb) cb(a);
2612
+ });
2613
+ return a;
2614
+ };
2615
+ $.getAudioContext = () => Q5.aud;
2616
+ $.userStartAudio = () => Q5.aud.resume();
2617
+ };
2618
+
2619
+ Q5.Sound = class extends Audio {
2620
+ constructor(path) {
2621
+ super(path);
2622
+ let a = this;
2623
+ a.load();
2624
+ a.panner = Q5.aud.createStereoPanner();
2625
+ a.source = Q5.aud.createMediaElementSource(a);
2626
+ a.source.connect(a.panner);
2627
+ a.panner.connect(Q5.aud.destination);
2628
+ Object.defineProperty(a, 'pan', {
2629
+ get: () => a.panner.pan.value,
2630
+ set: (v) => (a.panner.pan.value = v)
2631
+ });
2632
+ }
2633
+ setVolume(level) {
2634
+ this.volume = level;
2635
+ }
2636
+ setLoop(loop) {
2637
+ this.loop = loop;
2638
+ }
2639
+ setPan(value) {
2640
+ this.pan = value;
2641
+ }
2642
+ isLoaded() {
2643
+ return this.loaded;
2644
+ }
2645
+ isPlaying() {
2646
+ return !this.paused;
2647
+ }
2648
+ };
2649
+ Q5.modules.util = ($, q) => {
2650
+ $._loadFile = (path, cb, type) => {
2651
+ q._preloadCount++;
2652
+ let ret = {};
2653
+ fetch(path)
2654
+ .then((r) => {
2655
+ if (type == 'json') return r.json();
2656
+ if (type == 'text') return r.text();
2657
+ })
2658
+ .then((r) => {
2659
+ q._preloadCount--;
2660
+ Object.assign(ret, r);
2661
+ if (cb) cb(r);
2662
+ });
2663
+ return ret;
2664
+ };
2665
+
2666
+ $.loadStrings = (path, cb) => $._loadFile(path, cb, 'text');
2667
+ $.loadJSON = (path, cb) => $._loadFile(path, cb, 'json');
2668
+
2669
+ if (typeof localStorage == 'object') {
2670
+ $.storeItem = localStorage.setItem;
2671
+ $.getItem = localStorage.getItem;
2672
+ $.removeItem = localStorage.removeItem;
2673
+ $.clearStorage = localStorage.clear;
2674
+ }
2675
+
2676
+ $.year = () => new Date().getFullYear();
2677
+ $.day = () => new Date().getDay();
2678
+ $.hour = () => new Date().getHours();
2679
+ $.minute = () => new Date().getMinutes();
2680
+ $.second = () => new Date().getSeconds();
2681
+ };
2682
+ Q5.modules.vector = ($) => {
2683
+ $.createVector = (x, y, z) => new Q5.Vector(x, y, z, $);
2684
+ };
2685
+
2686
+ Q5.Vector = class {
2687
+ constructor(_x, _y, _z, _$) {
2688
+ this.x = _x || 0;
2689
+ this.y = _y || 0;
2690
+ this.z = _z || 0;
2691
+ this._$ = _$ || window;
2692
+ this._cn = null;
2693
+ this._cnsq = null;
2694
+ }
2695
+ set(_x, _y, _z) {
2696
+ this.x = _x || 0;
2697
+ this.y = _y || 0;
2698
+ this.z = _z || 0;
2699
+ }
2700
+ copy() {
2701
+ return new Q5.Vector(this.x, this.y, this.z);
2702
+ }
2703
+ _arg2v(x, y, z) {
2704
+ if (x?.x !== undefined) return x;
2705
+ if (y !== undefined) {
2706
+ return { x, y, z: z || 0 };
2707
+ }
2708
+ return { x: x, y: x, z: x };
2709
+ }
2710
+ _calcNorm() {
2711
+ this._cnsq = this.x * this.x + this.y * this.y + this.z * this.z;
2712
+ this._cn = Math.sqrt(this._cnsq);
2713
+ }
2714
+ add() {
2715
+ let u = this._arg2v(...arguments);
2716
+ this.x += u.x;
2717
+ this.y += u.y;
2718
+ this.z += u.z;
2719
+ return this;
2720
+ }
2721
+ rem() {
2722
+ let u = this._arg2v(...arguments);
2723
+ this.x %= u.x;
2724
+ this.y %= u.y;
2725
+ this.z %= u.z;
2726
+ return this;
2727
+ }
2728
+ sub() {
2729
+ let u = this._arg2v(...arguments);
2730
+ this.x -= u.x;
2731
+ this.y -= u.y;
2732
+ this.z -= u.z;
2733
+ return this;
2734
+ }
2735
+ mult() {
2736
+ let u = this._arg2v(...arguments);
2737
+ this.x *= u.x;
2738
+ this.y *= u.y;
2739
+ this.z *= u.z;
2740
+ return this;
2741
+ }
2742
+ div() {
2743
+ let u = this._arg2v(...arguments);
2744
+ if (u.x) this.x /= u.x;
2745
+ else this.x = 0;
2746
+ if (u.y) this.y /= u.y;
2747
+ else this.y = 0;
2748
+ if (u.z) this.z /= u.z;
2749
+ else this.z = 0;
2750
+ return this;
2751
+ }
2752
+ mag() {
2753
+ this._calcNorm();
2754
+ return this._cn;
2755
+ }
2756
+ magSq() {
2757
+ this._calcNorm();
2758
+ return this._cnsq;
2759
+ }
2760
+ dot() {
2761
+ let u = this._arg2v(...arguments);
2762
+ return this.x * u.x + this.y * u.y + this.z * u.z;
2763
+ }
2764
+ dist() {
2765
+ let u = this._arg2v(...arguments);
2766
+ let x = this.x - u.x;
2767
+ let y = this.y - u.y;
2768
+ let z = this.z - u.z;
2769
+ return Math.sqrt(x * x + y * y + z * z);
2770
+ }
2771
+ cross() {
2772
+ let u = this._arg2v(...arguments);
2773
+ let x = this.y * u.z - this.z * u.y;
2774
+ let y = this.z * u.x - this.x * u.z;
2775
+ let z = this.x * u.y - this.y * u.x;
2776
+ this.x = x;
2777
+ this.y = y;
2778
+ this.z = z;
2779
+ return this;
2780
+ }
2781
+ normalize() {
2782
+ this._calcNorm();
2783
+ let n = this._cn;
2784
+ if (n != 0) {
2785
+ this.x /= n;
2786
+ this.y /= n;
2787
+ this.z /= n;
2788
+ }
2789
+ this._cn = 1;
2790
+ this._cnsq = 1;
2791
+ return this;
2792
+ }
2793
+ limit(m) {
2794
+ this._calcNorm();
2795
+ let n = this._cn;
2796
+ if (n > m) {
2797
+ let t = m / n;
2798
+ this.x *= t;
2799
+ this.y *= t;
2800
+ this.z *= t;
2801
+ this._cn = m;
2802
+ this._cnsq = m * m;
2803
+ }
2804
+ return this;
2805
+ }
2806
+ setMag(m) {
2807
+ this._calcNorm();
2808
+ let n = this._cn;
2809
+ let t = m / n;
2810
+ this.x *= t;
2811
+ this.y *= t;
2812
+ this.z *= t;
2813
+ this._cn = m;
2814
+ this._cnsq = m * m;
2815
+ return this;
2816
+ }
2817
+ heading() {
2818
+ return this._$.atan2(this.y, this.x);
2819
+ }
2820
+ rotate(ang) {
2821
+ let costh = this._$.cos(ang);
2822
+ let sinth = this._$.sin(ang);
2823
+ let vx = this.x * costh - this.y * sinth;
2824
+ let vy = this.x * sinth + this.y * costh;
2825
+ this.x = vx;
2826
+ this.y = vy;
2827
+ return this;
2828
+ }
2829
+ angleBetween() {
2830
+ let u = this._arg2v(...arguments);
2831
+ let o = Q5.Vector.cross(this, u);
2832
+ let ang = this._$.atan2(o.mag(), this.dot(u));
2833
+ return ang * Math.sign(o.z || 1);
2834
+ }
2835
+ lerp() {
2836
+ let args = [...arguments];
2837
+ let u = this._arg2v(...args.slice(0, -1));
2838
+ let amt = args.at(-1);
2839
+ this.x += (u.x - this.x) * amt;
2840
+ this.y += (u.y - this.y) * amt;
2841
+ this.z += (u.z - this.z) * amt;
2842
+ return this;
2843
+ }
2844
+ reflect(n) {
2845
+ n.normalize();
2846
+ return this.sub(n.mult(2 * this.dot(n)));
2847
+ }
2848
+ array() {
2849
+ return [this.x, this.y, this.z];
2850
+ }
2851
+ equals(u, epsilon) {
2852
+ epsilon ??= Number.EPSILON || 0;
2853
+ return Math.abs(u.x - this.x) < epsilon && Math.abs(u.y - this.y) < epsilon && Math.abs(u.z - this.z) < epsilon;
2854
+ }
2855
+ fromAngle(th, l) {
2856
+ if (l === undefined) l = 1;
2857
+ this._cn = l;
2858
+ this._cnsq = l * l;
2859
+ this.x = l * this._$.cos(th);
2860
+ this.y = l * this._$.sin(th);
2861
+ this.z = 0;
2862
+ return this;
2863
+ }
2864
+ fromAngles(th, ph, l) {
2865
+ if (l === undefined) l = 1;
2866
+ this._cn = l;
2867
+ this._cnsq = l * l;
2868
+ const cosph = this._$.cos(ph);
2869
+ const sinph = this._$.sin(ph);
2870
+ const costh = this._$.cos(th);
2871
+ const sinth = this._$.sin(th);
2872
+ this.x = l * sinth * sinph;
2873
+ this.y = -l * costh;
2874
+ this.z = l * sinth * cosph;
2875
+ return this;
2876
+ }
2877
+ random2D() {
2878
+ this._cn = this._cnsq = 1;
2879
+ return this.fromAngle(Math.random() * Math.PI * 2);
2880
+ }
2881
+ random3D() {
2882
+ this._cn = this._cnsq = 1;
2883
+ return this.fromAngles(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2);
2884
+ }
2885
+ toString() {
2886
+ return `[${this.x}, ${this.y}, ${this.z}]`;
2887
+ }
2888
+ };
2889
+ Q5.Vector.add = (v, u) => v.copy().add(u);
2890
+ Q5.Vector.cross = (v, u) => v.copy().cross(u);
2891
+ Q5.Vector.dist = (v, u) => Math.hypot(v.x - u.x, v.y - u.y, v.z - u.z);
2892
+ Q5.Vector.div = (v, u) => v.copy().div(u);
2893
+ Q5.Vector.dot = (v, u) => v.copy().dot(u);
2894
+ Q5.Vector.equals = (v, u, epsilon) => v.equals(u, epsilon);
2895
+ Q5.Vector.lerp = (v, u, amt) => v.copy().lerp(u, amt);
2896
+ Q5.Vector.limit = (v, m) => v.copy().limit(m);
2897
+ Q5.Vector.heading = (v) => this._$.atan2(v.y, v.x);
2898
+ Q5.Vector.magSq = (v) => v.x * v.x + v.y * v.y + v.z * v.z;
2899
+ Q5.Vector.mag = (v) => Math.sqrt(Q5.Vector.magSq(v));
2900
+ Q5.Vector.mult = (v, u) => v.copy().mult(u);
2901
+ Q5.Vector.normalize = (v) => v.copy().normalize();
2902
+ Q5.Vector.rem = (v, u) => v.copy().rem(u);
2903
+ Q5.Vector.sub = (v, u) => v.copy().sub(u);
2904
+ for (let k of ['fromAngle', 'fromAngles', 'random2D', 'random3D']) {
2905
+ Q5.Vector[k] = (u, v, t) => new Q5.Vector()[k](u, v, t);
2906
+ }
2907
+ /**
2908
+ * q5-webgpu
2909
+ *
2910
+ * EXPERIMENTAL, for developer testing only!
2911
+ */
2912
+ Q5.renderers.webgpu = {};
2913
+
2914
+ Q5.renderers.webgpu.canvas = ($, q) => {
2915
+ let c = $.canvas;
2916
+
2917
+ c.width = $.width = 500;
2918
+ c.height = $.height = 500;
2919
+
2920
+ if ($.colorMode) $.colorMode('rgb', 'float');
2921
+
2922
+ let colorsStack;
2923
+
2924
+ $._createCanvas = (w, h, opt) => {
2925
+ q.ctx = q.drawingContext = c.getContext('webgpu');
2926
+
2927
+ $._canvasFormat = navigator.gpu.getPreferredCanvasFormat();
2928
+ opt.format = $._canvasFormat;
2929
+ opt.device = Q5.device;
2930
+
2931
+ $.ctx.configure(opt);
2932
+
2933
+ $.pipelines = [];
2934
+
2935
+ // pipeline changes for each draw call
2936
+ $.pipelinesStack = [];
2937
+
2938
+ // vertices for each draw call
2939
+ $.verticesStack = [];
2940
+
2941
+ // number of vertices for each draw call
2942
+ $.drawStack = [];
2943
+
2944
+ // colors used for each draw call
2945
+ colorsStack = $.colorsStack = [];
2946
+
2947
+ // current color index, used to associate a vertex with a color
2948
+ $._colorIndex = -1;
2949
+ };
2950
+
2951
+ $._resizeCanvas = (w, h) => {
2952
+ $._setCanvasSize(w, h);
2953
+ };
2954
+
2955
+ $.resetMatrix = () => {};
2956
+ $.translate = () => {};
2957
+
2958
+ $._beginRender = () => {
2959
+ $.encoder = Q5.device.createCommandEncoder();
2960
+
2961
+ q.pass = $.encoder.beginRenderPass({
2962
+ colorAttachments: [
2963
+ {
2964
+ view: ctx.getCurrentTexture().createView(),
2965
+ loadOp: 'clear',
2966
+ storeOp: 'store'
2967
+ }
2968
+ ]
2969
+ });
2970
+ };
2971
+
2972
+ $._render = () => {
2973
+ // run pre-render methods
2974
+ for (let m of $._hooks.preRender) m();
2975
+
2976
+ $.pass.setPipeline($.pipelines[0]);
2977
+
2978
+ let drawStack = $.drawStack; // local variables used for performance
2979
+ let o = 0; // vertex offset
2980
+ for (let i = 0; i < drawStack.length; i++) {
2981
+ $.pass.draw(drawStack[i], 1, o, 0);
2982
+ o += drawStack[i];
2983
+ }
2984
+ };
2985
+
2986
+ $._finishRender = () => {
2987
+ $.pass.end();
2988
+ const commandBuffer = $.encoder.finish();
2989
+ Q5.device.queue.submit([commandBuffer]);
2990
+ q.pass = $.encoder = null;
2991
+
2992
+ // clear the stacks for the next frame
2993
+ $.verticesStack.length = 0;
2994
+ $.drawStack.length = 0;
2995
+ $.colorsStack.length = 0;
2996
+ $.pipelinesStack.length = 0;
2997
+ $._colorIndex = -1;
2998
+ };
2999
+
3000
+ $.fill = (r, g, b, a = 1) => {
3001
+ // grayscale mode `fill(1, 0.5)`
3002
+ if (b == undefined) {
3003
+ a = g;
3004
+ g = b = r;
3005
+ }
3006
+ let levels;
3007
+ if (r._q5Color) levels = r.levels;
3008
+ else levels = [r, g, b, a];
3009
+
3010
+ colorsStack.push(...levels);
3011
+ $._colorIndex++;
3012
+ };
3013
+ };
3014
+
3015
+ Q5.webgpu = async function (scope, parent) {
3016
+ if (!navigator.gpu) {
3017
+ console.error('q5 WebGPU not supported on this browser!');
3018
+ return new Q5(scope, parent);
3019
+ }
3020
+ let adapter = await navigator.gpu.requestAdapter();
3021
+ if (!adapter) throw new Error('No appropriate GPUAdapter found.');
3022
+ Q5.device = await adapter.requestDevice();
3023
+
3024
+ return new Q5(scope, parent, 'webgpu');
3025
+ };
3026
+ Q5.renderers.webgpu.drawing = ($, q) => {
3027
+ $.CLOSE = 1;
3028
+
3029
+ let verticesStack, drawStack, colorsStack;
3030
+
3031
+ $._hooks.postCanvas.push(() => {
3032
+ verticesStack = $.verticesStack;
3033
+ drawStack = $.drawStack;
3034
+ colorsStack = $.colorsStack;
3035
+
3036
+ let vertexBufferLayout = {
3037
+ arrayStride: 12, // 2 coordinates + 1 color index * 4 bytes each
3038
+ attributes: [
3039
+ {
3040
+ format: 'float32x2',
3041
+ offset: 0,
3042
+ shaderLocation: 0 // position
3043
+ },
3044
+ {
3045
+ format: 'float32',
3046
+ offset: 8,
3047
+ shaderLocation: 1 // colorIndex
3048
+ }
3049
+ ]
3050
+ };
3051
+
3052
+ let vertexShader = Q5.device.createShaderModule({
3053
+ code: `
3054
+ struct VertexOutput {
3055
+ @builtin(position) position: vec4<f32>,
3056
+ @location(1) colorIndex: f32
3057
+ };
3058
+
3059
+ @vertex
3060
+ fn vertexMain(@location(0) pos: vec2<f32>, @location(1) colorIndex: f32) -> VertexOutput {
3061
+ var output: VertexOutput;
3062
+ output.position = vec4<f32>(pos, 0.0, 1.0);
3063
+ output.colorIndex = colorIndex;
3064
+ return output;
3065
+ }
3066
+ `
3067
+ });
3068
+
3069
+ let fragmentShader = Q5.device.createShaderModule({
3070
+ code: `
3071
+ @group(0) @binding(0) var<storage, read> uColors : array<vec4<f32>>;
3072
+
3073
+ @fragment
3074
+ fn fragmentMain(@location(1) colorIndex: f32) -> @location(0) vec4<f32> {
3075
+ let index = u32(colorIndex);
3076
+ return mix(uColors[index], uColors[index + 1u], fract(colorIndex));
3077
+ }
3078
+ `
3079
+ });
3080
+
3081
+ let bindGroupLayout = Q5.device.createBindGroupLayout({
3082
+ entries: [
3083
+ {
3084
+ binding: 0,
3085
+ visibility: GPUShaderStage.FRAGMENT,
3086
+ buffer: {
3087
+ type: 'read-only-storage',
3088
+ hasDynamicOffset: false
3089
+ }
3090
+ }
3091
+ ]
3092
+ });
3093
+
3094
+ let pipelineLayout = Q5.device.createPipelineLayout({
3095
+ bindGroupLayouts: [bindGroupLayout]
3096
+ });
3097
+
3098
+ $.pipelines[0] = Q5.device.createRenderPipeline({
3099
+ layout: pipelineLayout,
3100
+ vertex: {
3101
+ module: vertexShader,
3102
+ entryPoint: 'vertexMain',
3103
+ buffers: [vertexBufferLayout]
3104
+ },
3105
+ fragment: {
3106
+ module: fragmentShader,
3107
+ entryPoint: 'fragmentMain',
3108
+ targets: [
3109
+ {
3110
+ format: $._canvasFormat
3111
+ }
3112
+ ]
3113
+ },
3114
+ primitive: {
3115
+ topology: 'triangle-list'
3116
+ }
3117
+ });
3118
+ });
3119
+
3120
+ let shapeVertices;
3121
+
3122
+ $.beginShape = () => {
3123
+ shapeVertices = [];
3124
+ };
3125
+
3126
+ $.vertex = (x, y) => {
3127
+ shapeVertices.push(x / $.canvas.hw, -y / $.canvas.hh, $._colorIndex);
3128
+ };
3129
+
3130
+ $.endShape = (close) => {
3131
+ if (shapeVertices.length < 6) {
3132
+ throw new Error('A shape must have at least 3 vertices.');
3133
+ }
3134
+ if (close) {
3135
+ // Close the shape by adding the first vertex at the end
3136
+ shapeVertices.push(shapeVertices[0], shapeVertices[1], shapeVertices[2]);
3137
+ }
3138
+ // Convert the shape to triangles
3139
+ let triangles = [];
3140
+ for (let i = 3; i < shapeVertices.length; i += 3) {
3141
+ triangles.push(
3142
+ shapeVertices[0],
3143
+ shapeVertices[1],
3144
+ shapeVertices[2], // First vertex
3145
+ shapeVertices[i - 3],
3146
+ shapeVertices[i - 2],
3147
+ shapeVertices[i - 1], // Previous vertex
3148
+ shapeVertices[i],
3149
+ shapeVertices[i + 1],
3150
+ shapeVertices[i + 2] // Current vertex
3151
+ );
3152
+ }
3153
+
3154
+ verticesStack.push(...triangles);
3155
+ drawStack.push(triangles.length / 3);
3156
+ shapeVertices = [];
3157
+ };
3158
+
3159
+ $.triangle = (x1, y1, x2, y2, x3, y3) => {
3160
+ $.beginShape();
3161
+ $.vertex(x1, y1);
3162
+ $.vertex(x2, y2);
3163
+ $.vertex(x3, y3);
3164
+ $.endShape(1);
3165
+ };
3166
+
3167
+ $.rect = (x, y, w, h) => {
3168
+ let hw = w / 2;
3169
+ let hh = h / 2;
3170
+ // convert the coordinates from pixel space to NDC space
3171
+ let left = (x - hw) / $.canvas.hw;
3172
+ let right = (x + hw) / $.canvas.hw;
3173
+ let top = -(y - hh) / $.canvas.hh; // y is inverted in WebGPU
3174
+ let bottom = -(y + hh) / $.canvas.hh; // y is inverted in WebGPU
3175
+
3176
+ let ci = $._colorIndex;
3177
+ // two triangles make a rectangle
3178
+ verticesStack.push(
3179
+ left,
3180
+ top,
3181
+ ci,
3182
+ right,
3183
+ top,
3184
+ ci,
3185
+ left,
3186
+ bottom,
3187
+ ci,
3188
+ right,
3189
+ top,
3190
+ ci,
3191
+ left,
3192
+ bottom,
3193
+ ci,
3194
+ right,
3195
+ bottom,
3196
+ ci
3197
+ );
3198
+ drawStack.push(6);
3199
+ };
3200
+
3201
+ /**
3202
+ * Derived from: ceil(Math.log(d) * 7) * 2 - ceil(28)
3203
+ * This lookup table is used for better performance.
3204
+ * @param {Number} d diameter of the circle
3205
+ * @returns n number of segments
3206
+ */
3207
+ // prettier-ignore
3208
+ const getArcSegments = (d) =>
3209
+ d < 14 ? 8 :
3210
+ d < 16 ? 10 :
3211
+ d < 18 ? 12 :
3212
+ d < 20 ? 14 :
3213
+ d < 22 ? 16 :
3214
+ d < 24 ? 18 :
3215
+ d < 28 ? 20 :
3216
+ d < 34 ? 22 :
3217
+ d < 42 ? 24 :
3218
+ d < 48 ? 26 :
3219
+ d < 56 ? 28 :
3220
+ d < 64 ? 30 :
3221
+ d < 72 ? 32 :
3222
+ d < 84 ? 34 :
3223
+ d < 96 ? 36 :
3224
+ d < 98 ? 38 :
3225
+ d < 113 ? 40 :
3226
+ d < 149 ? 44 :
3227
+ d < 199 ? 48 :
3228
+ d < 261 ? 52 :
3229
+ d < 353 ? 56 :
3230
+ d < 461 ? 60 :
3231
+ d < 585 ? 64 :
3232
+ d < 1200 ? 70 :
3233
+ d < 1800 ? 80 :
3234
+ d < 2400 ? 90 :
3235
+ 100;
3236
+
3237
+ $.ellipse = (x, y, w, h) => {
3238
+ const n = getArcSegments(w == h ? w : Math.max(w, h));
3239
+
3240
+ let a = Math.max(w, 1) / 2;
3241
+ let b = w == h ? a : Math.max(h, 1) / 2;
3242
+
3243
+ x /= $.canvas.hw;
3244
+ y /= -$.canvas.hh;
3245
+ a /= $.canvas.hw;
3246
+ b /= -$.canvas.hh;
3247
+
3248
+ let t = 0; // theta
3249
+ const angleIncrement = $.TAU / n;
3250
+ const ci = $._colorIndex;
3251
+ let vx1, vy1, vx2, vy2;
3252
+ for (let i = 0; i <= n; i++) {
3253
+ vx1 = vx2;
3254
+ vy1 = vy2;
3255
+ vx2 = x + a * Math.cos(t);
3256
+ vy2 = y + b * Math.sin(t);
3257
+ t += angleIncrement;
3258
+
3259
+ if (i == 0) continue;
3260
+
3261
+ verticesStack.push(x, y, ci, vx1, vy1, ci, vx2, vy2, ci);
3262
+ }
3263
+
3264
+ drawStack.push(n * 3);
3265
+ };
3266
+
3267
+ $.circle = (x, y, d) => $.ellipse(x, y, d, d);
3268
+
3269
+ $.noStroke = () => {};
3270
+
3271
+ $.background = () => {};
3272
+
3273
+ $._hooks.preRender.push(() => {
3274
+ const vertexBuffer = Q5.device.createBuffer({
3275
+ size: verticesStack.length * 6,
3276
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
3277
+ });
3278
+
3279
+ Q5.device.queue.writeBuffer(vertexBuffer, 0, new Float32Array(verticesStack));
3280
+ $.pass.setVertexBuffer(0, vertexBuffer);
3281
+
3282
+ const colorBuffer = Q5.device.createBuffer({
3283
+ size: colorsStack.length * 4,
3284
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
3285
+ });
3286
+
3287
+ Q5.device.queue.writeBuffer(colorBuffer, 0, new Float32Array(colorsStack));
3288
+
3289
+ const bindGroup = Q5.device.createBindGroup({
3290
+ layout: $.pipelines[0].getBindGroupLayout(0),
3291
+ entries: [
3292
+ {
3293
+ binding: 0,
3294
+ resource: {
3295
+ buffer: colorBuffer,
3296
+ offset: 0,
3297
+ size: colorsStack.length * 4
3298
+ }
3299
+ }
3300
+ ]
3301
+ });
3302
+
3303
+ // set the bind group once before rendering
3304
+ $.pass.setBindGroup(0, bindGroup);
3305
+ });
3306
+ };
3307
+ Q5.renderers.webgpu.image = ($, q) => {};
3308
+ Q5.renderers.webgpu.text = ($, q) => {};