q5 2.26.1 → 2.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/deno.json +1 -1
  2. package/package.json +1 -1
  3. package/q5.d.ts +227 -36
  4. package/q5.js +120 -80
  5. package/q5.min.js +2 -2
package/q5.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * q5.js
3
- * @version 2.26
3
+ * @version 2.27
4
4
  * @author quinton-ashley
5
5
  * @contributors Tezumie, LingDong-
6
6
  * @license LGPL-3.0
@@ -160,7 +160,7 @@ function Q5(scope, parent, renderer) {
160
160
  };
161
161
 
162
162
  $.frameRate = (hz) => {
163
- if (hz != $._targetFrameRate) {
163
+ if (hz && hz != $._targetFrameRate) {
164
164
  $._targetFrameRate = hz;
165
165
  $._targetFrameDuration = 1000 / hz;
166
166
 
@@ -256,18 +256,8 @@ function Q5(scope, parent, renderer) {
256
256
  };
257
257
 
258
258
  let t = globalScope || $;
259
- $._isTouchAware = t.touchStarted || t.touchMoved || t.touchEnded;
260
259
 
261
- if ($._isGlobal) {
262
- $.preload = t.preload;
263
- $.setup = t.setup;
264
- $.draw = t.draw;
265
- $.postProcess = t.postProcess;
266
- }
267
- $.preload ??= () => {};
268
- $.setup ??= () => {};
269
- $.draw ??= () => {};
270
- $.postProcess ??= () => {};
260
+ $._isTouchAware = t.touchStarted || t.touchMoved || t.touchEnded;
271
261
 
272
262
  let userFns = [
273
263
  'setup',
@@ -277,6 +267,7 @@ function Q5(scope, parent, renderer) {
277
267
  'mouseReleased',
278
268
  'mouseDragged',
279
269
  'mouseClicked',
270
+ 'doubleClicked',
280
271
  'mouseWheel',
281
272
  'keyPressed',
282
273
  'keyReleased',
@@ -286,12 +277,15 @@ function Q5(scope, parent, renderer) {
286
277
  'touchEnded',
287
278
  'windowResized'
288
279
  ];
289
- for (let k of userFns) {
290
- if (!t[k]) $[k] = () => {};
280
+ // shim if undefined
281
+ for (let name of userFns) $[name] ??= () => {};
282
+
283
+ function wrapWithFES(fn) {
284
+ if (!t[fn]) $[fn] = () => {};
291
285
  else if ($._isGlobal) {
292
- $[k] = (event) => {
286
+ $[fn] = (event) => {
293
287
  try {
294
- return t[k](event);
288
+ return t[fn](event);
295
289
  } catch (e) {
296
290
  if ($._fes) $._fes(e);
297
291
  throw e;
@@ -300,28 +294,24 @@ function Q5(scope, parent, renderer) {
300
294
  }
301
295
  }
302
296
 
303
- async function _setup() {
304
- $._startDone = true;
297
+ async function _start() {
298
+ wrapWithFES('preload');
299
+ $.preload();
305
300
  await Promise.all($._preloadPromises);
306
301
  if ($._g) await Promise.all($._g._preloadPromises);
302
+
303
+ for (let name of userFns) wrapWithFES(name);
304
+
305
+ $.draw = t.draw || (() => {});
306
+
307
307
  millisStart = performance.now();
308
308
  await $.setup();
309
309
  $._setupDone = true;
310
- if ($.frameCount) return;
311
310
  if ($.ctx === null) $.createCanvas(200, 200);
311
+ if ($.frameCount) return;
312
312
  raf($._draw);
313
313
  }
314
314
 
315
- function _start() {
316
- try {
317
- $.preload();
318
- if (!$._startDone) _setup();
319
- } catch (e) {
320
- if ($._fes) $._fes(e);
321
- throw e;
322
- }
323
- }
324
-
325
315
  if (autoLoaded) _start();
326
316
  else setTimeout(_start, 32);
327
317
  }
@@ -363,7 +353,7 @@ function createCanvas(w, h, opt) {
363
353
  }
364
354
  }
365
355
 
366
- Q5.version = Q5.VERSION = '2.26';
356
+ Q5.version = Q5.VERSION = '2.27';
367
357
 
368
358
  if (typeof document == 'object') {
369
359
  document.addEventListener('DOMContentLoaded', () => {
@@ -430,21 +420,44 @@ Q5.modules.canvas = ($, q) => {
430
420
 
431
421
  if ($._scope != 'image') {
432
422
  if ($._scope == 'graphics') $._pixelDensity = this._pixelDensity;
433
- else if (window.IntersectionObserver) {
434
- let wasObserved = false;
435
- new IntersectionObserver((e) => {
436
- c.visible = e[0].isIntersecting;
437
- if (!wasObserved) {
438
- $._wasLooping = $._loop;
439
- wasObserved = true;
440
- }
441
- if (c.visible) {
442
- if ($._wasLooping && !$._loop) $.loop();
443
- } else {
444
- $._wasLooping = $._loop;
445
- $.noLoop();
446
- }
447
- }).observe(c);
423
+ else if (!Q5._server) {
424
+ // the canvas can become detached from the DOM
425
+ // if the innerHTML of one of its parents is edited
426
+ // check if canvas is still attached to the DOM
427
+ let el = c;
428
+ while (el && el.parentElement != document.body) {
429
+ el = el.parentElement;
430
+ }
431
+ if (!el) {
432
+ // reattach canvas to the DOM
433
+ document.getElementById(c.id)?.remove();
434
+ addCanvas();
435
+ }
436
+
437
+ if (window.IntersectionObserver) {
438
+ let wasObserved = false;
439
+ new IntersectionObserver((e) => {
440
+ let isIntersecting = e[0].isIntersecting;
441
+
442
+ if (!isIntersecting) {
443
+ // the canvas might still be onscreen, just behind other elements
444
+ let r = c.getBoundingClientRect();
445
+ c.visible = r.top < window.innerHeight && r.bottom > 0 && r.left < window.innerWidth && r.right > 0;
446
+ } else c.visible = true;
447
+
448
+ if (!wasObserved) {
449
+ $._wasLooping = $._loop;
450
+ wasObserved = true;
451
+ }
452
+
453
+ if (c.visible) {
454
+ if ($._wasLooping && !$._loop) $.loop();
455
+ } else {
456
+ $._wasLooping = $._loop;
457
+ $.noLoop();
458
+ }
459
+ }).observe(c);
460
+ }
448
461
  }
449
462
  }
450
463
 
@@ -609,7 +622,9 @@ Q5.modules.canvas = ($, q) => {
609
622
  $.popStyles = () => {
610
623
  let styles = $._styles.pop();
611
624
  for (let s of $._styleNames) $[s] = styles[s];
612
- q.Color = styles.Color;
625
+
626
+ if ($._webgpu) $.colorMode($._colorMode, $._colorFormat);
627
+ else q.Color = styles.Color;
613
628
  };
614
629
 
615
630
  if (window && $._scope != 'graphics') {
@@ -3091,7 +3106,12 @@ Q5.modules.input = ($, q) => {
3091
3106
 
3092
3107
  $._updateMouse = (e) => {
3093
3108
  if (e.changedTouches) return;
3094
- if (c) {
3109
+
3110
+ if (document.pointerLockElement) {
3111
+ // In pointer lock mode, update position based on movement
3112
+ q.mouseX += e.movementX;
3113
+ q.mouseY += e.movementY;
3114
+ } else if (c) {
3095
3115
  let rect = c.getBoundingClientRect();
3096
3116
  let sx = c.scrollWidth / $.width || 1;
3097
3117
  let sy = c.scrollHeight / $.height || 1;
@@ -3109,10 +3129,8 @@ Q5.modules.input = ($, q) => {
3109
3129
  q.moveY = e.movementY;
3110
3130
  };
3111
3131
 
3112
- let pressedInCanvas = 0;
3113
-
3114
3132
  $._onmousedown = (e) => {
3115
- pressedInCanvas++;
3133
+ if (!c?.visible) return;
3116
3134
  $._startAudio();
3117
3135
  $._updateMouse(e);
3118
3136
  q.mouseIsPressed = true;
@@ -3127,19 +3145,30 @@ Q5.modules.input = ($, q) => {
3127
3145
  };
3128
3146
 
3129
3147
  $._onmouseup = (e) => {
3148
+ if (!c?.visible) return;
3130
3149
  $._updateMouse(e);
3131
3150
  q.mouseIsPressed = false;
3132
3151
  $.mouseReleased(e);
3133
3152
  };
3134
3153
 
3135
3154
  $._onclick = (e) => {
3155
+ if (!c?.visible) return;
3136
3156
  $._updateMouse(e);
3137
3157
  q.mouseIsPressed = true;
3138
3158
  $.mouseClicked(e);
3139
3159
  q.mouseIsPressed = false;
3140
3160
  };
3141
3161
 
3162
+ $._ondblclick = (e) => {
3163
+ if (!c?.visible) return;
3164
+ $._updateMouse(e);
3165
+ q.mouseIsPressed = true;
3166
+ $.doubleClicked(e);
3167
+ q.mouseIsPressed = false;
3168
+ };
3169
+
3142
3170
  $._onwheel = (e) => {
3171
+ if (!c?.visible) return;
3143
3172
  $._updateMouse(e);
3144
3173
  e.delta = e.deltaY;
3145
3174
  if ($.mouseWheel(e) == false || $._noScroll) e.preventDefault();
@@ -3160,10 +3189,10 @@ Q5.modules.input = ($, q) => {
3160
3189
  $.noCursor = () => ($.canvas.style.cursor = 'none');
3161
3190
  $.noScroll = () => ($._noScroll = true);
3162
3191
 
3163
- if (window) {
3164
- $.lockMouse = document.body?.requestPointerLock;
3165
- $.unlockMouse = document.exitPointerLock;
3166
- }
3192
+ $.requestPointerLock = (unadjustedMovement = false) => {
3193
+ return document.body?.requestPointerLock({ unadjustedMovement });
3194
+ };
3195
+ $.exitPointerLock = () => document.exitPointerLock();
3167
3196
 
3168
3197
  $._onkeydown = (e) => {
3169
3198
  if (e.repeat) return;
@@ -3204,6 +3233,7 @@ Q5.modules.input = ($, q) => {
3204
3233
  }
3205
3234
 
3206
3235
  $._ontouchstart = (e) => {
3236
+ if (!c?.visible) return;
3207
3237
  $._startAudio();
3208
3238
  q.touches = [...e.touches].map(getTouchInfo);
3209
3239
  if (!$._isTouchAware) {
@@ -3217,6 +3247,7 @@ Q5.modules.input = ($, q) => {
3217
3247
  };
3218
3248
 
3219
3249
  $._ontouchmove = (e) => {
3250
+ if (!c?.visible) return;
3220
3251
  q.touches = [...e.touches].map(getTouchInfo);
3221
3252
  if (!$._isTouchAware) {
3222
3253
  q.mouseX = $.touches[0].x;
@@ -3227,6 +3258,7 @@ Q5.modules.input = ($, q) => {
3227
3258
  };
3228
3259
 
3229
3260
  $._ontouchend = (e) => {
3261
+ if (!c?.visible) return;
3230
3262
  q.touches = [...e.touches].map(getTouchInfo);
3231
3263
  if (!$._isTouchAware && !$.touches.length) {
3232
3264
  q.mouseIsPressed = false;
@@ -3235,11 +3267,18 @@ Q5.modules.input = ($, q) => {
3235
3267
  if (!$.touchEnded(e)) e.preventDefault();
3236
3268
  };
3237
3269
 
3238
- if (c) {
3239
- let l = c.addEventListener.bind(c);
3270
+ if (window) {
3271
+ let l = window.addEventListener;
3272
+ l('keydown', (e) => $._onkeydown(e), false);
3273
+ l('keyup', (e) => $._onkeyup(e), false);
3274
+
3240
3275
  l('mousedown', (e) => $._onmousedown(e));
3241
- l('wheel', (e) => $._onwheel(e));
3276
+ l('mousemove', (e) => $._onmousemove(e), false);
3277
+ l('mouseup', (e) => $._onmouseup(e));
3242
3278
  l('click', (e) => $._onclick(e));
3279
+ l('dblclick', (e) => $._ondblclick(e));
3280
+
3281
+ if (!c) l('wheel', (e) => $._onwheel(e));
3243
3282
 
3244
3283
  l('touchstart', (e) => $._ontouchstart(e));
3245
3284
  l('touchmove', (e) => $._ontouchmove(e));
@@ -3247,25 +3286,10 @@ Q5.modules.input = ($, q) => {
3247
3286
  l('touchcancel', (e) => $._ontouchend(e));
3248
3287
  }
3249
3288
 
3250
- if (window) {
3251
- let l = window.addEventListener;
3252
- l('keydown', (e) => $._onkeydown(e), false);
3253
- l('keyup', (e) => $._onkeyup(e), false);
3254
-
3255
- if (!c) {
3256
- l('mousedown', (e) => $._onmousedown(e));
3257
- l('wheel', (e) => $._onwheel(e));
3258
- l('click', (e) => $._onclick(e));
3259
- }
3260
-
3261
- l('mousemove', (e) => $._onmousemove(e), false);
3262
- l('mouseup', (e) => {
3263
- if (pressedInCanvas > 0) {
3264
- pressedInCanvas--;
3265
- $._onmouseup(e);
3266
- }
3267
- });
3268
- }
3289
+ // making the window level event listener for wheel events
3290
+ // not passive would be necessary to be able to use `e.preventDefault`
3291
+ // but browsers warn that it's bad for performance
3292
+ if (c) c.addEventListener('wheel', (e) => $._onwheel(e));
3269
3293
  };
3270
3294
  Q5.modules.math = ($, q) => {
3271
3295
  $.RADIANS = 0;
@@ -4134,6 +4158,10 @@ Q5.modules.sound = ($, q) => {
4134
4158
  return Q5.aud.resume();
4135
4159
  }
4136
4160
  };
4161
+
4162
+ $.outputVolume = (level) => {
4163
+ if (Q5.soundOut) Q5.soundOut.gain.value = level;
4164
+ };
4137
4165
  };
4138
4166
 
4139
4167
  if (window.OfflineAudioContext) {
@@ -4183,6 +4211,7 @@ Q5.Sound = class {
4183
4211
  source.onended = () => {
4184
4212
  if (!this.paused) {
4185
4213
  this.ended = true;
4214
+ if (this._onended) this._onended();
4186
4215
  this.sources.delete(source);
4187
4216
  }
4188
4217
  };
@@ -4278,6 +4307,9 @@ Q5.Sound = class {
4278
4307
  isLooping() {
4279
4308
  return this._loop;
4280
4309
  }
4310
+ onended(cb) {
4311
+ this._onended = cb;
4312
+ }
4281
4313
  };
4282
4314
  Q5.modules.util = ($, q) => {
4283
4315
  $._loadFile = (url, cb, type) => {
@@ -4950,6 +4982,8 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
4950
4982
  createMainView();
4951
4983
  };
4952
4984
 
4985
+ // since these values are checked so often in `addColor`,
4986
+ // they're stored in local variables for better performance
4953
4987
  let usingRGB = true,
4954
4988
  colorFormat = 1;
4955
4989
 
@@ -6021,7 +6055,10 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6021
6055
  };
6022
6056
 
6023
6057
  let curveSegments = 20;
6024
- $.curveDetail = (x) => (curveSegments = x);
6058
+ $.curveDetail = (v) => (curveSegments = v);
6059
+
6060
+ let bezierSegments = 20;
6061
+ $.bezierDetail = (v) => (bezierSegments = v);
6025
6062
 
6026
6063
  let shapeVertCount;
6027
6064
  let sv = []; // shape vertices
@@ -6053,7 +6090,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6053
6090
  let startX = sv[prevIndex];
6054
6091
  let startY = sv[prevIndex + 1];
6055
6092
 
6056
- let step = 1 / curveSegments;
6093
+ let step = 1 / bezierSegments;
6057
6094
 
6058
6095
  let vx, vy;
6059
6096
  let quadratic = arguments.length == 4;
@@ -6101,6 +6138,9 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6101
6138
  }
6102
6139
  }
6103
6140
 
6141
+ // Use curveSegments to determine step size
6142
+ let step = 1 / curveSegments;
6143
+
6104
6144
  // calculate catmull-rom spline curve points
6105
6145
  for (let i = 0; i < points.length - 3; i++) {
6106
6146
  let p0 = points[i];
@@ -6108,7 +6148,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
6108
6148
  let p2 = points[i + 2];
6109
6149
  let p3 = points[i + 3];
6110
6150
 
6111
- for (let t = 0; t <= 1; t += 0.1) {
6151
+ for (let t = 0; t <= 1; t += step) {
6112
6152
  let t2 = t * t;
6113
6153
  let t3 = t2 * t;
6114
6154