@vectojs/core 1.15.0 → 1.16.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/dist/{chunk-64UFEHOJ.mjs → chunk-AQTO7OSU.mjs} +246 -32
- package/dist/{chunk-J6NHSFIE.js → chunk-DUYB4GX4.js} +285 -69
- package/dist/{chunk-BEUIB3U7.js → chunk-IYPLG4Z4.js} +93 -43
- package/dist/{chunk-L5BCKFQE.mjs → chunk-JFU56BX4.mjs} +48 -0
- package/dist/index.js +2144 -325
- package/dist/index.mjs +1966 -148
- package/dist/layout.js +3 -1
- package/dist/renderer/CanvasRenderer.d.ts +22 -0
- package/dist/renderer/IRenderer.d.ts +13 -0
- package/dist/renderer.js +4 -3
- package/dist/renderer.mjs +1 -1
- package/dist/text/MSDFTextEntity.d.ts +29 -0
- package/dist/text.js +4 -3
- package/dist/text.mjs +1 -1
- package/dist/tree/ComputeParticleEntity.d.ts +18 -0
- package/dist/tree/DOMPortalEntity.d.ts +18 -0
- package/dist/tree/Entity.d.ts +121 -5
- package/dist/tree/Scene.d.ts +380 -0
- package/dist/wasm/anim-backend.d.ts +86 -0
- package/dist/wasm/asset.d.ts +25 -0
- package/dist/wasm/asset.js +7 -0
- package/dist/wasm/asset.mjs +5 -0
- package/dist/wasm/backend.d.ts +154 -0
- package/dist/wasm/hit-backend.d.ts +92 -0
- package/dist/wasm/hit-store.d.ts +48 -0
- package/dist/wasm/particle-backend.d.ts +104 -0
- package/dist/wasm/scene-store.d.ts +28 -0
- package/dist/wasm/soa.d.ts +146 -0
- package/dist/wasm/vectojs_core.wasm +0 -0
- package/package.json +15 -8
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _createStarExport(obj) { Object.keys(obj) .filter((key) => key !== "default" && key !== "__esModule") .forEach((key) => { if (exports.hasOwnProperty(key)) { return; } Object.defineProperty(exports, key, {enumerable: true, configurable: true, get: () => obj[key]}); }); } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6;
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _createStarExport(obj) { Object.keys(obj) .filter((key) => key !== "default" && key !== "__esModule") .forEach((key) => { if (exports.hasOwnProperty(key)) { return; } Object.defineProperty(exports, key, {enumerable: true, configurable: true, get: () => obj[key]}); }); } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8; var _class9; var _class10;const __vecto_cjs_url=require("url").pathToFileURL(__filename).href;
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
@@ -7,13 +7,17 @@
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
var _chunkBEUIB3U7js = require('./chunk-BEUIB3U7.js');
|
|
11
10
|
|
|
11
|
+
var _chunkIYPLG4Z4js = require('./chunk-IYPLG4Z4.js');
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
var _chunkDUYB4GX4js = require('./chunk-DUYB4GX4.js');
|
|
18
|
+
|
|
19
|
+
// src/tree/Scene.ts
|
|
20
|
+
var _animation = require('@vectojs/animation'); _createStarExport(_animation);
|
|
17
21
|
|
|
18
22
|
// src/tree/ComputeParticleEntity.ts
|
|
19
23
|
var PARTICLE_STRIDE_FLOATS = 8;
|
|
@@ -25,7 +29,7 @@ var PARTICLE_OFFSET_ORIGIN_X = 4;
|
|
|
25
29
|
var PARTICLE_OFFSET_ORIGIN_Y = 5;
|
|
26
30
|
var PARTICLE_OFFSET_SIZE = 6;
|
|
27
31
|
var PARTICLE_OFFSET_LIFE = 7;
|
|
28
|
-
var ComputeParticleEntity = (_class = class extends
|
|
32
|
+
var ComputeParticleEntity = (_class = class extends _chunkDUYB4GX4js.Entity {
|
|
29
33
|
|
|
30
34
|
|
|
31
35
|
|
|
@@ -48,8 +52,12 @@ var ComputeParticleEntity = (_class = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
48
52
|
__init5() {this.computeBindGroup = null}
|
|
49
53
|
/** WebGPU bind group for the render pass (usually same as compute). */
|
|
50
54
|
__init6() {this.renderBindGroup = null}
|
|
55
|
+
/** When the last simulation step ran through the WASM backend, its fused
|
|
56
|
+
* pending-animation flag; `null` when the last step used the JS `updateCPU`
|
|
57
|
+
* path (so `hasPendingAnimations` falls back to its own scan). */
|
|
58
|
+
__init7() {this._wasmPending = null}
|
|
51
59
|
constructor(options = {}) {
|
|
52
|
-
super();_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);;
|
|
60
|
+
super();_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);;
|
|
53
61
|
this.maxParticles = _nullishCoalesce(options.maxParticles, () => ( 1e4));
|
|
54
62
|
this.springK = _nullishCoalesce(options.springK, () => ( 0.05));
|
|
55
63
|
this.damping = _nullishCoalesce(options.damping, () => ( 0.95));
|
|
@@ -177,6 +185,9 @@ var ComputeParticleEntity = (_class = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
177
185
|
* always return `true` and defeat the idle throttle entirely.
|
|
178
186
|
*/
|
|
179
187
|
hasPendingAnimations() {
|
|
188
|
+
if (this._wasmPending !== null) {
|
|
189
|
+
return this._wasmPending || this.pendingExplosion !== null;
|
|
190
|
+
}
|
|
180
191
|
const EPS_VELOCITY = 0.5;
|
|
181
192
|
const EPS_DISTANCE = 0.5;
|
|
182
193
|
for (let i = 0; i < this.maxParticles; i++) {
|
|
@@ -202,6 +213,7 @@ var ComputeParticleEntity = (_class = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
202
213
|
* @param height - Boundary height.
|
|
203
214
|
*/
|
|
204
215
|
updateCPU(dt, mouseX, mouseY, width, height) {
|
|
216
|
+
this._wasmPending = null;
|
|
205
217
|
const safeDt = isNaN(dt) ? 0.016 : Math.max(0, Math.min(dt, 0.1));
|
|
206
218
|
const explosion = this.pendingExplosion;
|
|
207
219
|
const safeWidth = Math.max(1, width);
|
|
@@ -284,6 +296,37 @@ var ComputeParticleEntity = (_class = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
284
296
|
}
|
|
285
297
|
this.pendingExplosion = null;
|
|
286
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Advance the simulation one step through the WASM particle kernel: transpose
|
|
301
|
+
* this entity's AoS buffer into the backend's SoA views, run `particle_step`,
|
|
302
|
+
* and scatter position/velocity/life back. Produces an f32 result (matching
|
|
303
|
+
* the WGSL shader) that differs from {@link updateCPU}'s f64 by <1 ULP/step —
|
|
304
|
+
* the accepted CPU-vs-GPU-class divergence. Caches the kernel's fused
|
|
305
|
+
* pending-animation flag so {@link hasPendingAnimations} needs no second scan.
|
|
306
|
+
*
|
|
307
|
+
* The backend holds one resident SoA store, so a Scene with multiple particle
|
|
308
|
+
* entities reuses it sequentially — origin is therefore re-gathered each call
|
|
309
|
+
* (not upload-once), a couple of extra f32 reads per particle.
|
|
310
|
+
*/
|
|
311
|
+
stepWithBackend(backend, dt, mouseX, mouseY, width, height) {
|
|
312
|
+
const count = this.maxParticles;
|
|
313
|
+
backend.ensure(count);
|
|
314
|
+
backend.gather(this.particleData, count, true);
|
|
315
|
+
this._wasmPending = backend.step(count, {
|
|
316
|
+
dt,
|
|
317
|
+
mouseX,
|
|
318
|
+
mouseY,
|
|
319
|
+
width,
|
|
320
|
+
height,
|
|
321
|
+
springK: this.springK,
|
|
322
|
+
damping: this.damping,
|
|
323
|
+
bounceDamping: this.bounceDamping,
|
|
324
|
+
maxVelocity: this.maxVelocity,
|
|
325
|
+
explosion: this.pendingExplosion
|
|
326
|
+
});
|
|
327
|
+
backend.scatter(this.particleData, count);
|
|
328
|
+
this.pendingExplosion = null;
|
|
329
|
+
}
|
|
287
330
|
destroy() {
|
|
288
331
|
this.destroyGPUResources();
|
|
289
332
|
super.destroy();
|
|
@@ -309,6 +352,749 @@ var ComputeParticleEntity = (_class = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
309
352
|
}
|
|
310
353
|
}, _class);
|
|
311
354
|
|
|
355
|
+
// src/wasm/soa.ts
|
|
356
|
+
var PAD = 8;
|
|
357
|
+
function buildStore(nodes) {
|
|
358
|
+
const count = nodes.length;
|
|
359
|
+
const capacity = count + PAD;
|
|
360
|
+
const childrenOf = Array.from({ length: count }, () => []);
|
|
361
|
+
let rootInput = -1;
|
|
362
|
+
for (let k = 0; k < count; k++) {
|
|
363
|
+
const p = nodes[k].parent;
|
|
364
|
+
if (p === -1) {
|
|
365
|
+
if (rootInput !== -1) throw new Error("buildStore: more than one root (parent === -1)");
|
|
366
|
+
rootInput = k;
|
|
367
|
+
} else {
|
|
368
|
+
if (p < 0 || p >= count)
|
|
369
|
+
throw new Error(`buildStore: node ${k} has out-of-range parent ${p}`);
|
|
370
|
+
childrenOf[p].push(k);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (rootInput === -1) throw new Error("buildStore: no root (need exactly one parent === -1)");
|
|
374
|
+
const store = {
|
|
375
|
+
count,
|
|
376
|
+
capacity,
|
|
377
|
+
x: new Float64Array(capacity),
|
|
378
|
+
y: new Float64Array(capacity),
|
|
379
|
+
sx: new Float64Array(capacity),
|
|
380
|
+
sy: new Float64Array(capacity),
|
|
381
|
+
cos: new Float64Array(capacity),
|
|
382
|
+
sin: new Float64Array(capacity),
|
|
383
|
+
opacity: new Float64Array(capacity),
|
|
384
|
+
wa: new Float64Array(capacity),
|
|
385
|
+
wb: new Float64Array(capacity),
|
|
386
|
+
wc: new Float64Array(capacity),
|
|
387
|
+
wd: new Float64Array(capacity),
|
|
388
|
+
we: new Float64Array(capacity),
|
|
389
|
+
wf: new Float64Array(capacity),
|
|
390
|
+
wo: new Float64Array(capacity),
|
|
391
|
+
bx: new Float64Array(capacity),
|
|
392
|
+
by: new Float64Array(capacity),
|
|
393
|
+
bw: new Float64Array(capacity),
|
|
394
|
+
bh: new Float64Array(capacity),
|
|
395
|
+
aminx: new Float64Array(capacity),
|
|
396
|
+
aminy: new Float64Array(capacity),
|
|
397
|
+
amaxx: new Float64Array(capacity),
|
|
398
|
+
amaxy: new Float64Array(capacity),
|
|
399
|
+
runParent: new Int32Array(count),
|
|
400
|
+
runStart: new Int32Array(count),
|
|
401
|
+
runLen: new Int32Array(count),
|
|
402
|
+
runCount: 0,
|
|
403
|
+
storeIndexOf: new Int32Array(count).fill(-1)
|
|
404
|
+
};
|
|
405
|
+
store.storeIndexOf[rootInput] = 0;
|
|
406
|
+
writeInput(store, 0, nodes[rootInput]);
|
|
407
|
+
let next = 1;
|
|
408
|
+
const queue = [rootInput];
|
|
409
|
+
let head = 0;
|
|
410
|
+
while (head < queue.length) {
|
|
411
|
+
const parentInput = queue[head++];
|
|
412
|
+
const kids = childrenOf[parentInput];
|
|
413
|
+
if (kids.length === 0) continue;
|
|
414
|
+
const runStart = next;
|
|
415
|
+
for (const kid of kids) {
|
|
416
|
+
store.storeIndexOf[kid] = next;
|
|
417
|
+
writeInput(store, next, nodes[kid]);
|
|
418
|
+
next++;
|
|
419
|
+
}
|
|
420
|
+
const r = store.runCount++;
|
|
421
|
+
store.runParent[r] = store.storeIndexOf[parentInput];
|
|
422
|
+
store.runStart[r] = runStart;
|
|
423
|
+
store.runLen[r] = kids.length;
|
|
424
|
+
for (const kid of kids) queue.push(kid);
|
|
425
|
+
}
|
|
426
|
+
return store;
|
|
427
|
+
}
|
|
428
|
+
function writeInput(s, i, n) {
|
|
429
|
+
s.x[i] = n.x;
|
|
430
|
+
s.y[i] = n.y;
|
|
431
|
+
s.sx[i] = n.scaleX;
|
|
432
|
+
s.sy[i] = n.scaleY;
|
|
433
|
+
s.cos[i] = Math.cos(n.rotation);
|
|
434
|
+
s.sin[i] = Math.sin(n.rotation);
|
|
435
|
+
s.opacity[i] = n.opacity;
|
|
436
|
+
s.bx[i] = _nullishCoalesce(n.bx, () => ( 0));
|
|
437
|
+
s.by[i] = _nullishCoalesce(n.by, () => ( 0));
|
|
438
|
+
s.bw[i] = _nullishCoalesce(n.bw, () => ( 0));
|
|
439
|
+
s.bh[i] = _nullishCoalesce(n.bh, () => ( 0));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/wasm/scene-store.ts
|
|
443
|
+
function buildTreeStore(root) {
|
|
444
|
+
const entities = [];
|
|
445
|
+
const inputIndex = /* @__PURE__ */ new Map();
|
|
446
|
+
const collect = (e) => {
|
|
447
|
+
inputIndex.set(e, entities.length);
|
|
448
|
+
entities.push(e);
|
|
449
|
+
const kids = e.children;
|
|
450
|
+
for (let i = 0; i < kids.length; i++) collect(kids[i]);
|
|
451
|
+
};
|
|
452
|
+
collect(root);
|
|
453
|
+
const nodes = entities.map((e) => {
|
|
454
|
+
const parent = e.parent;
|
|
455
|
+
const p = parent !== null && inputIndex.has(parent) ? inputIndex.get(parent) : -1;
|
|
456
|
+
return {
|
|
457
|
+
parent: e === root ? -1 : p,
|
|
458
|
+
x: e.x,
|
|
459
|
+
y: e.y,
|
|
460
|
+
scaleX: e.scaleX,
|
|
461
|
+
scaleY: e.scaleY,
|
|
462
|
+
rotation: e.rotation,
|
|
463
|
+
opacity: e.opacity
|
|
464
|
+
};
|
|
465
|
+
});
|
|
466
|
+
const store = buildStore(nodes);
|
|
467
|
+
const indexOf = /* @__PURE__ */ new Map();
|
|
468
|
+
for (const [e, ii] of inputIndex) indexOf.set(e, store.storeIndexOf[ii]);
|
|
469
|
+
return { store, indexOf };
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/wasm/backend.ts
|
|
473
|
+
var PAD2 = 8;
|
|
474
|
+
var WasmTransformBackend = (_class2 = class {
|
|
475
|
+
__init8() {this.available = true}
|
|
476
|
+
|
|
477
|
+
__init9() {this.cap = 0}
|
|
478
|
+
__init10() {this.runCap = 0}
|
|
479
|
+
// Views over wasm linear memory, valid until the next init().
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
constructor(instance) {;_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);
|
|
506
|
+
this.ex = instance.exports;
|
|
507
|
+
}
|
|
508
|
+
/** Compose world matrices for `store` in WASM, writing back into its
|
|
509
|
+
* `wa..wo` arrays. Result is bit-identical to `composeJS(store)`. */
|
|
510
|
+
compose(store, kernel = "simd") {
|
|
511
|
+
this.ensure(store.count, store.runCount);
|
|
512
|
+
const n = store.count;
|
|
513
|
+
this.vx.set(store.x.subarray(0, n));
|
|
514
|
+
this.vy.set(store.y.subarray(0, n));
|
|
515
|
+
this.vsx.set(store.sx.subarray(0, n));
|
|
516
|
+
this.vsy.set(store.sy.subarray(0, n));
|
|
517
|
+
this.vcos.set(store.cos.subarray(0, n));
|
|
518
|
+
this.vsin.set(store.sin.subarray(0, n));
|
|
519
|
+
this.vop.set(store.opacity.subarray(0, n));
|
|
520
|
+
const rc = store.runCount;
|
|
521
|
+
this.vrp.set(store.runParent.subarray(0, rc));
|
|
522
|
+
this.vrs.set(store.runStart.subarray(0, rc));
|
|
523
|
+
this.vrl.set(store.runLen.subarray(0, rc));
|
|
524
|
+
this.ex.set_run_count(rc);
|
|
525
|
+
if (kernel === "scalar") this.ex.compose_scalar();
|
|
526
|
+
else this.ex.compose_simd();
|
|
527
|
+
store.wa.set(this.vwa.subarray(0, n));
|
|
528
|
+
store.wb.set(this.vwb.subarray(0, n));
|
|
529
|
+
store.wc.set(this.vwc.subarray(0, n));
|
|
530
|
+
store.wd.set(this.vwd.subarray(0, n));
|
|
531
|
+
store.we.set(this.vwe.subarray(0, n));
|
|
532
|
+
store.wf.set(this.vwf.subarray(0, n));
|
|
533
|
+
store.wo.set(this.vwo.subarray(0, n));
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Run the kernel only, over data already resident in WASM memory — no upload,
|
|
537
|
+
* no readback. This is the per-frame cost the *designed* integration pays:
|
|
538
|
+
* entity accessors write `x/y/rotation` straight into the wasm input views
|
|
539
|
+
* (via {@link inputView}) and the renderer reads world matrices straight from
|
|
540
|
+
* the wasm output views (via {@link worldView}), so the batch copies in
|
|
541
|
+
* {@link compose} do not happen every frame. `compose` must have run at least
|
|
542
|
+
* once at the current capacity to size the store and set the run count; call
|
|
543
|
+
* {@link uploadRuns} after a topology change.
|
|
544
|
+
*/
|
|
545
|
+
runKernel(kernel = "simd") {
|
|
546
|
+
if (kernel === "scalar") this.ex.compose_scalar();
|
|
547
|
+
else this.ex.compose_simd();
|
|
548
|
+
}
|
|
549
|
+
/** Upload only the run table + count (topology), leaving per-entity inputs to
|
|
550
|
+
* the resident views. Call when the tree structure changes, not per frame. */
|
|
551
|
+
uploadRuns(store) {
|
|
552
|
+
this.ensure(store.count, store.runCount);
|
|
553
|
+
const rc = store.runCount;
|
|
554
|
+
this.vrp.set(store.runParent.subarray(0, rc));
|
|
555
|
+
this.vrs.set(store.runStart.subarray(0, rc));
|
|
556
|
+
this.vrl.set(store.runLen.subarray(0, rc));
|
|
557
|
+
this.ex.set_run_count(rc);
|
|
558
|
+
}
|
|
559
|
+
/** The resident wasm input views (`x,y,sx,sy,cos,sin,opacity`), valid until
|
|
560
|
+
* the next capacity growth. Writing here is what makes uploads unnecessary. */
|
|
561
|
+
inputView() {
|
|
562
|
+
return {
|
|
563
|
+
x: this.vx,
|
|
564
|
+
y: this.vy,
|
|
565
|
+
sx: this.vsx,
|
|
566
|
+
sy: this.vsy,
|
|
567
|
+
cos: this.vcos,
|
|
568
|
+
sin: this.vsin,
|
|
569
|
+
opacity: this.vop
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
/** The resident wasm world-matrix output views (`wa..wo`). Reading here is
|
|
573
|
+
* what makes readback unnecessary. */
|
|
574
|
+
worldView() {
|
|
575
|
+
return {
|
|
576
|
+
wa: this.vwa,
|
|
577
|
+
wb: this.vwb,
|
|
578
|
+
wc: this.vwc,
|
|
579
|
+
wd: this.vwd,
|
|
580
|
+
we: this.vwe,
|
|
581
|
+
wf: this.vwf,
|
|
582
|
+
wo: this.vwo
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Compute world-space AABBs for `store` in WASM (G1+), writing back into its
|
|
587
|
+
* `aminx/aminy/amaxx/amaxy` arrays. Uploads the local bounds, runs the AABB
|
|
588
|
+
* pass, reads results back. Result is bit-identical to `computeAabbsJS(store)`.
|
|
589
|
+
* `compose` (or `runKernel`) must have populated the world matrices first —
|
|
590
|
+
* this pass reads them. For the resident (no-copy) integration, write bounds
|
|
591
|
+
* via {@link boundsView} and read via {@link aabbView} + call
|
|
592
|
+
* {@link runAabbs} instead.
|
|
593
|
+
*/
|
|
594
|
+
computeAabbs(store) {
|
|
595
|
+
this.ensure(store.count, store.runCount);
|
|
596
|
+
const n = store.count;
|
|
597
|
+
this.vbx.set(store.bx.subarray(0, n));
|
|
598
|
+
this.vby.set(store.by.subarray(0, n));
|
|
599
|
+
this.vbw.set(store.bw.subarray(0, n));
|
|
600
|
+
this.vbh.set(store.bh.subarray(0, n));
|
|
601
|
+
this.ex.compute_aabbs(n);
|
|
602
|
+
store.aminx.set(this.vaminx.subarray(0, n));
|
|
603
|
+
store.aminy.set(this.vaminy.subarray(0, n));
|
|
604
|
+
store.amaxx.set(this.vamaxx.subarray(0, n));
|
|
605
|
+
store.amaxy.set(this.vamaxy.subarray(0, n));
|
|
606
|
+
}
|
|
607
|
+
/** Run the AABB pass only, over `count` entities already resident in wasm
|
|
608
|
+
* memory (bounds written via {@link boundsView}, world matrices already
|
|
609
|
+
* composed). No upload/readback — the per-frame resident path. */
|
|
610
|
+
runAabbs(count) {
|
|
611
|
+
this.ex.compute_aabbs(count);
|
|
612
|
+
}
|
|
613
|
+
/** Resident wasm local-bounds input views (`bx,by,bw,bh`) for the AABB pass. */
|
|
614
|
+
boundsView() {
|
|
615
|
+
return { bx: this.vbx, by: this.vby, bw: this.vbw, bh: this.vbh };
|
|
616
|
+
}
|
|
617
|
+
/** Resident wasm world-AABB output views (`aminx,aminy,amaxx,amaxy`). */
|
|
618
|
+
aabbView() {
|
|
619
|
+
return {
|
|
620
|
+
aminx: this.vaminx,
|
|
621
|
+
aminy: this.vaminy,
|
|
622
|
+
amaxx: this.vamaxx,
|
|
623
|
+
amaxy: this.vamaxy
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
ensure(count, runCount) {
|
|
627
|
+
if (count + PAD2 <= this.cap && runCount <= this.runCap) return;
|
|
628
|
+
this.cap = count + PAD2;
|
|
629
|
+
this.runCap = Math.max(runCount, count, 1);
|
|
630
|
+
this.ex.init(count, this.runCap);
|
|
631
|
+
this.refreshViews();
|
|
632
|
+
}
|
|
633
|
+
/** Rebuild typed-array views after an init() (which may have grown, and thus
|
|
634
|
+
* detached, the memory buffer). */
|
|
635
|
+
refreshViews() {
|
|
636
|
+
const buf = this.ex.memory.buffer;
|
|
637
|
+
const cap = this.cap;
|
|
638
|
+
const rc = this.runCap;
|
|
639
|
+
const f64 = (ptr) => new Float64Array(buf, ptr, cap);
|
|
640
|
+
const i32 = (ptr) => new Int32Array(buf, ptr, rc);
|
|
641
|
+
this.vx = f64(this.ex.p_x());
|
|
642
|
+
this.vy = f64(this.ex.p_y());
|
|
643
|
+
this.vsx = f64(this.ex.p_sx());
|
|
644
|
+
this.vsy = f64(this.ex.p_sy());
|
|
645
|
+
this.vcos = f64(this.ex.p_cos());
|
|
646
|
+
this.vsin = f64(this.ex.p_sin());
|
|
647
|
+
this.vop = f64(this.ex.p_opacity());
|
|
648
|
+
this.vwa = f64(this.ex.p_wa());
|
|
649
|
+
this.vwb = f64(this.ex.p_wb());
|
|
650
|
+
this.vwc = f64(this.ex.p_wc());
|
|
651
|
+
this.vwd = f64(this.ex.p_wd());
|
|
652
|
+
this.vwe = f64(this.ex.p_we());
|
|
653
|
+
this.vwf = f64(this.ex.p_wf());
|
|
654
|
+
this.vwo = f64(this.ex.p_wo());
|
|
655
|
+
this.vbx = f64(this.ex.p_bx());
|
|
656
|
+
this.vby = f64(this.ex.p_by());
|
|
657
|
+
this.vbw = f64(this.ex.p_bw());
|
|
658
|
+
this.vbh = f64(this.ex.p_bh());
|
|
659
|
+
this.vaminx = f64(this.ex.p_aminx());
|
|
660
|
+
this.vaminy = f64(this.ex.p_aminy());
|
|
661
|
+
this.vamaxx = f64(this.ex.p_amaxx());
|
|
662
|
+
this.vamaxy = f64(this.ex.p_amaxy());
|
|
663
|
+
this.vrp = i32(this.ex.p_run_parent());
|
|
664
|
+
this.vrs = i32(this.ex.p_run_start());
|
|
665
|
+
this.vrl = i32(this.ex.p_run_len());
|
|
666
|
+
}
|
|
667
|
+
}, _class2);
|
|
668
|
+
async function instantiateAsync(bytes) {
|
|
669
|
+
try {
|
|
670
|
+
const { instance } = await WebAssembly.instantiate(bytes, {});
|
|
671
|
+
return new WasmTransformBackend(instance);
|
|
672
|
+
} catch (e3) {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
async function instantiateStreaming(source) {
|
|
677
|
+
try {
|
|
678
|
+
const resp = typeof source === "string" || source instanceof URL ? await fetch(String(source)) : await source;
|
|
679
|
+
if (typeof WebAssembly.instantiateStreaming === "function") {
|
|
680
|
+
const buffered = resp.clone();
|
|
681
|
+
try {
|
|
682
|
+
const { instance: instance2 } = await WebAssembly.instantiateStreaming(resp, {});
|
|
683
|
+
return new WasmTransformBackend(instance2);
|
|
684
|
+
} catch (e4) {
|
|
685
|
+
const { instance: instance2 } = await WebAssembly.instantiate(await buffered.arrayBuffer(), {});
|
|
686
|
+
return new WasmTransformBackend(instance2);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const { instance } = await WebAssembly.instantiate(await resp.arrayBuffer(), {});
|
|
690
|
+
return new WasmTransformBackend(instance);
|
|
691
|
+
} catch (e5) {
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/wasm/hit-store.ts
|
|
697
|
+
function gatherHitAABBs(root, currentFrame) {
|
|
698
|
+
const slotEntity = [];
|
|
699
|
+
const boundless = [];
|
|
700
|
+
const minxs = [];
|
|
701
|
+
const minys = [];
|
|
702
|
+
const maxxs = [];
|
|
703
|
+
const maxys = [];
|
|
704
|
+
const scratch = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
|
|
705
|
+
const visit = (node) => {
|
|
706
|
+
const index = slotEntity.length;
|
|
707
|
+
slotEntity.push(node);
|
|
708
|
+
const bounds = node.getBounds();
|
|
709
|
+
if (bounds === null) {
|
|
710
|
+
boundless.push({ entity: node, index });
|
|
711
|
+
minxs.push(0);
|
|
712
|
+
minys.push(0);
|
|
713
|
+
maxxs.push(0);
|
|
714
|
+
maxys.push(0);
|
|
715
|
+
} else {
|
|
716
|
+
if (!node._readWorldCache(currentFrame, scratch)) {
|
|
717
|
+
const t = node.getWorldTransform();
|
|
718
|
+
scratch.a = t.a;
|
|
719
|
+
scratch.b = t.b;
|
|
720
|
+
scratch.c = t.c;
|
|
721
|
+
scratch.d = t.d;
|
|
722
|
+
scratch.e = t.e;
|
|
723
|
+
scratch.f = t.f;
|
|
724
|
+
}
|
|
725
|
+
const { a, b, c, d, e, f } = scratch;
|
|
726
|
+
let minX = Infinity;
|
|
727
|
+
let minY = Infinity;
|
|
728
|
+
let maxX = -Infinity;
|
|
729
|
+
let maxY = -Infinity;
|
|
730
|
+
for (let i = 0; i < 4; i++) {
|
|
731
|
+
const lx = i & 1 ? bounds.x + bounds.width : bounds.x;
|
|
732
|
+
const ly = i & 2 ? bounds.y + bounds.height : bounds.y;
|
|
733
|
+
const wx = a * lx + c * ly + e;
|
|
734
|
+
const wy = b * lx + d * ly + f;
|
|
735
|
+
if (wx < minX) minX = wx;
|
|
736
|
+
if (wx > maxX) maxX = wx;
|
|
737
|
+
if (wy < minY) minY = wy;
|
|
738
|
+
if (wy > maxY) maxY = wy;
|
|
739
|
+
}
|
|
740
|
+
minxs.push(minX);
|
|
741
|
+
minys.push(minY);
|
|
742
|
+
maxxs.push(maxX);
|
|
743
|
+
maxys.push(maxY);
|
|
744
|
+
}
|
|
745
|
+
const kids = node.children;
|
|
746
|
+
for (let i = 0; i < kids.length; i++) visit(kids[i]);
|
|
747
|
+
};
|
|
748
|
+
visit(root);
|
|
749
|
+
return {
|
|
750
|
+
count: slotEntity.length,
|
|
751
|
+
slotEntity,
|
|
752
|
+
boundless,
|
|
753
|
+
minx: Float64Array.from(minxs),
|
|
754
|
+
miny: Float64Array.from(minys),
|
|
755
|
+
maxx: Float64Array.from(maxxs),
|
|
756
|
+
maxy: Float64Array.from(maxys)
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// src/wasm/hit-backend.ts
|
|
761
|
+
var PAD3 = 8;
|
|
762
|
+
var CELLS_PER_ENTITY_HINT = 4;
|
|
763
|
+
var HitTestBackend = (_class3 = class {
|
|
764
|
+
|
|
765
|
+
__init11() {this.entityCap = 0}
|
|
766
|
+
__init12() {this.cellCap = 0}
|
|
767
|
+
__init13() {this.itemCap = 0}
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
/** Grid geometry from the last {@link ensure} call. */
|
|
776
|
+
__init14() {this.gridW = 0}
|
|
777
|
+
__init15() {this.gridH = 0}
|
|
778
|
+
__init16() {this.cellSize = 64}
|
|
779
|
+
constructor(instance) {;_class3.prototype.__init11.call(this);_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);_class3.prototype.__init15.call(this);_class3.prototype.__init16.call(this);
|
|
780
|
+
this.ex = instance.exports;
|
|
781
|
+
}
|
|
782
|
+
/** The resident AABB input views (`minx/miny/maxx/maxy`), valid until the
|
|
783
|
+
* next capacity growth. Writing here is what {@link build} reads from. */
|
|
784
|
+
inputView() {
|
|
785
|
+
return { minx: this.vminx, miny: this.vminy, maxx: this.vmaxx, maxy: this.vmaxy };
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Size (and grow, if needed) capacity for `count` entities over
|
|
789
|
+
* `[0,vw] x [0,vh]` at `cellSize`, and record the grid geometry
|
|
790
|
+
* {@link candidatesAt} needs. Call this BEFORE writing AABBs into
|
|
791
|
+
* {@link inputView} — a capacity growth detaches the previous views, so
|
|
792
|
+
* writing first and sizing after would write into a stale buffer.
|
|
793
|
+
*/
|
|
794
|
+
ensure(count, vw, vh, cellSize) {
|
|
795
|
+
const gw = Math.max(1, Math.ceil(vw / cellSize));
|
|
796
|
+
const gh = Math.max(1, Math.ceil(vh / cellSize));
|
|
797
|
+
this.growIfNeeded(count, gw * gh, count * CELLS_PER_ENTITY_HINT);
|
|
798
|
+
this.gridW = gw;
|
|
799
|
+
this.gridH = gh;
|
|
800
|
+
this.cellSize = cellSize;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Run the kernel's bucketing over whatever is currently resident in
|
|
804
|
+
* {@link inputView} (write the AABBs there, and call {@link ensure} first).
|
|
805
|
+
* Returns `false` if the build overflowed its item budget — the caller must
|
|
806
|
+
* not trust {@link candidatesAt} results for this build and should fall back
|
|
807
|
+
* to the JS walk instead (never return a wrong hit).
|
|
808
|
+
*/
|
|
809
|
+
runBuild(count, vw, vh, cellSize) {
|
|
810
|
+
this.ex.hit_build(count, vw, vh, cellSize);
|
|
811
|
+
return this.ex.hit_overflow() === 0;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Entity indices whose AABB overlaps the cell containing `(px, py)`, in
|
|
815
|
+
* ascending index order (scan from the end for topmost/highest-index
|
|
816
|
+
* first), or `null` if the point falls outside the built grid. This is a
|
|
817
|
+
* coarse candidate LIST, not a hit result — the caller must still confirm
|
|
818
|
+
* each candidate's AABB contains the point and re-check its precise
|
|
819
|
+
* `isPointInside`.
|
|
820
|
+
*/
|
|
821
|
+
candidatesAt(px, py) {
|
|
822
|
+
if (px < 0 || py < 0) return null;
|
|
823
|
+
const cx = Math.floor(px / this.cellSize);
|
|
824
|
+
const cy = Math.floor(py / this.cellSize);
|
|
825
|
+
if (cx < 0 || cy < 0 || cx >= this.gridW || cy >= this.gridH) return null;
|
|
826
|
+
const c = cy * this.gridW + cx;
|
|
827
|
+
if (c >= this.cellCap) return null;
|
|
828
|
+
const start = this.vCellStart[c];
|
|
829
|
+
const count = this.vCellCount[c];
|
|
830
|
+
return this.vItems.subarray(start, start + count);
|
|
831
|
+
}
|
|
832
|
+
growIfNeeded(count, cellCount, itemCount) {
|
|
833
|
+
if (count + PAD3 <= this.entityCap && cellCount <= this.cellCap && itemCount <= this.itemCap) {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
this.entityCap = count + PAD3;
|
|
837
|
+
this.cellCap = Math.max(cellCount, 1);
|
|
838
|
+
this.itemCap = Math.max(itemCount, count, 1);
|
|
839
|
+
this.ex.hit_init(count, this.cellCap, this.itemCap);
|
|
840
|
+
this.refreshViews();
|
|
841
|
+
}
|
|
842
|
+
/** Rebuild typed-array views after a growing `hit_init` (which detaches the
|
|
843
|
+
* memory buffer). */
|
|
844
|
+
refreshViews() {
|
|
845
|
+
const buf = this.ex.memory.buffer;
|
|
846
|
+
const eCap = this.entityCap;
|
|
847
|
+
const cCap = this.cellCap;
|
|
848
|
+
const iCap = this.itemCap;
|
|
849
|
+
this.vminx = new Float64Array(buf, this.ex.p_h_minx(), eCap);
|
|
850
|
+
this.vminy = new Float64Array(buf, this.ex.p_h_miny(), eCap);
|
|
851
|
+
this.vmaxx = new Float64Array(buf, this.ex.p_h_maxx(), eCap);
|
|
852
|
+
this.vmaxy = new Float64Array(buf, this.ex.p_h_maxy(), eCap);
|
|
853
|
+
this.vCellStart = new Int32Array(buf, this.ex.p_h_cell_start(), cCap);
|
|
854
|
+
this.vCellCount = new Int32Array(buf, this.ex.p_h_cell_count(), cCap);
|
|
855
|
+
this.vItems = new Int32Array(buf, this.ex.p_h_items(), iCap);
|
|
856
|
+
}
|
|
857
|
+
}, _class3);
|
|
858
|
+
async function instantiateAsync2(bytes) {
|
|
859
|
+
try {
|
|
860
|
+
const { instance } = await WebAssembly.instantiate(bytes, {});
|
|
861
|
+
return new HitTestBackend(instance);
|
|
862
|
+
} catch (e6) {
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
async function instantiateStreaming2(source) {
|
|
867
|
+
try {
|
|
868
|
+
const resp = typeof source === "string" || source instanceof URL ? await fetch(String(source)) : await source;
|
|
869
|
+
if (typeof WebAssembly.instantiateStreaming === "function") {
|
|
870
|
+
const buffered = resp.clone();
|
|
871
|
+
try {
|
|
872
|
+
const { instance: instance2 } = await WebAssembly.instantiateStreaming(resp, {});
|
|
873
|
+
return new HitTestBackend(instance2);
|
|
874
|
+
} catch (e7) {
|
|
875
|
+
const { instance: instance2 } = await WebAssembly.instantiate(await buffered.arrayBuffer(), {});
|
|
876
|
+
return new HitTestBackend(instance2);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
const { instance } = await WebAssembly.instantiate(await resp.arrayBuffer(), {});
|
|
880
|
+
return new HitTestBackend(instance);
|
|
881
|
+
} catch (e8) {
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// src/wasm/anim-backend.ts
|
|
887
|
+
var PAD4 = 8;
|
|
888
|
+
var AnimBackend = (_class4 = class {
|
|
889
|
+
|
|
890
|
+
__init17() {this.springCap = 0}
|
|
891
|
+
__init18() {this.tweenCap = 0}
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
constructor(instance) {;_class4.prototype.__init17.call(this);_class4.prototype.__init18.call(this);
|
|
895
|
+
this.ex = instance.exports;
|
|
896
|
+
}
|
|
897
|
+
/** The resident spring SoA input/output views, valid until the next capacity
|
|
898
|
+
* growth. Write gathered driver state here before calling {@link stepSprings}. */
|
|
899
|
+
springView() {
|
|
900
|
+
return this.sv;
|
|
901
|
+
}
|
|
902
|
+
/** The resident tween SoA input/output views, valid until the next capacity
|
|
903
|
+
* growth. Write gathered driver state here before calling {@link stepTweens}. */
|
|
904
|
+
tweenView() {
|
|
905
|
+
return this.tv;
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Size (and grow, if needed) capacity for `springCount` springs and
|
|
909
|
+
* `tweenCount` tweens. Call this BEFORE writing into {@link springView}/
|
|
910
|
+
* {@link tweenView} — a capacity growth detaches the previous views, so
|
|
911
|
+
* writing first and sizing after would write into a stale buffer.
|
|
912
|
+
*/
|
|
913
|
+
ensure(springCount, tweenCount) {
|
|
914
|
+
if (springCount + PAD4 <= this.springCap && tweenCount + PAD4 <= this.tweenCap) return;
|
|
915
|
+
this.springCap = springCount + PAD4;
|
|
916
|
+
this.tweenCap = tweenCount + PAD4;
|
|
917
|
+
this.ex.anim_init(this.springCap, this.tweenCap);
|
|
918
|
+
this.refreshViews();
|
|
919
|
+
}
|
|
920
|
+
/** Advance `count` springs (from index 0) by `dtMs` milliseconds, in place. */
|
|
921
|
+
stepSprings(dtMs, count) {
|
|
922
|
+
this.ex.spring_step(dtMs / 1e3, count);
|
|
923
|
+
}
|
|
924
|
+
/** Advance `count` tweens (from index 0) by `dtMs` milliseconds, writing `val`. */
|
|
925
|
+
stepTweens(dtMs, count) {
|
|
926
|
+
this.ex.tween_step(dtMs, count);
|
|
927
|
+
}
|
|
928
|
+
refreshViews() {
|
|
929
|
+
const buf = this.ex.memory.buffer;
|
|
930
|
+
const sCap = this.springCap;
|
|
931
|
+
const tCap = this.tweenCap;
|
|
932
|
+
this.sv = {
|
|
933
|
+
val: new Float64Array(buf, this.ex.p_s_val(), sCap),
|
|
934
|
+
target: new Float64Array(buf, this.ex.p_s_target(), sCap),
|
|
935
|
+
vel: new Float64Array(buf, this.ex.p_s_vel(), sCap),
|
|
936
|
+
stiff: new Float64Array(buf, this.ex.p_s_stiff(), sCap),
|
|
937
|
+
damp: new Float64Array(buf, this.ex.p_s_damp(), sCap),
|
|
938
|
+
mass: new Float64Array(buf, this.ex.p_s_mass(), sCap)
|
|
939
|
+
};
|
|
940
|
+
this.tv = {
|
|
941
|
+
from: new Float64Array(buf, this.ex.p_t_from(), tCap),
|
|
942
|
+
to: new Float64Array(buf, this.ex.p_t_to(), tCap),
|
|
943
|
+
elapsed: new Float64Array(buf, this.ex.p_t_elapsed(), tCap),
|
|
944
|
+
dur: new Float64Array(buf, this.ex.p_t_dur(), tCap),
|
|
945
|
+
delay: new Float64Array(buf, this.ex.p_t_delay(), tCap),
|
|
946
|
+
ease: new Float64Array(buf, this.ex.p_t_ease(), tCap),
|
|
947
|
+
val: new Float64Array(buf, this.ex.p_t_val(), tCap)
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
}, _class4);
|
|
951
|
+
async function instantiateAsync3(bytes) {
|
|
952
|
+
try {
|
|
953
|
+
const { instance } = await WebAssembly.instantiate(bytes, {});
|
|
954
|
+
return new AnimBackend(instance);
|
|
955
|
+
} catch (e9) {
|
|
956
|
+
return null;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
async function instantiateStreaming3(source) {
|
|
960
|
+
try {
|
|
961
|
+
const resp = typeof source === "string" || source instanceof URL ? await fetch(String(source)) : await source;
|
|
962
|
+
if (typeof WebAssembly.instantiateStreaming === "function") {
|
|
963
|
+
const buffered = resp.clone();
|
|
964
|
+
try {
|
|
965
|
+
const { instance: instance2 } = await WebAssembly.instantiateStreaming(resp, {});
|
|
966
|
+
return new AnimBackend(instance2);
|
|
967
|
+
} catch (e10) {
|
|
968
|
+
const { instance: instance2 } = await WebAssembly.instantiate(await buffered.arrayBuffer(), {});
|
|
969
|
+
return new AnimBackend(instance2);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
const { instance } = await WebAssembly.instantiate(await resp.arrayBuffer(), {});
|
|
973
|
+
return new AnimBackend(instance);
|
|
974
|
+
} catch (e11) {
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// src/wasm/particle-backend.ts
|
|
980
|
+
var PAD5 = 8;
|
|
981
|
+
var ParticleBackend = (_class5 = class {
|
|
982
|
+
|
|
983
|
+
__init19() {this.cap = 0}
|
|
984
|
+
|
|
985
|
+
constructor(instance) {;_class5.prototype.__init19.call(this);
|
|
986
|
+
this.ex = instance.exports;
|
|
987
|
+
}
|
|
988
|
+
/** The resident SoA views, valid until the next capacity growth. */
|
|
989
|
+
particleView() {
|
|
990
|
+
return this.view;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Size (and grow, if needed) capacity for `count` particles. Call BEFORE
|
|
994
|
+
* writing into {@link particleView} — a growth detaches the previous views.
|
|
995
|
+
*/
|
|
996
|
+
ensure(count) {
|
|
997
|
+
if (count + PAD5 <= this.cap) return;
|
|
998
|
+
this.cap = count + PAD5;
|
|
999
|
+
this.ex.particle_init(this.cap);
|
|
1000
|
+
this.refreshViews();
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Advance `count` particles one step in place. Returns `true` when at least
|
|
1004
|
+
* one live particle is still moving or off-origin beyond epsilon (the fused
|
|
1005
|
+
* `hasPendingAnimations` flag), so the caller need not re-scan the buffer.
|
|
1006
|
+
*/
|
|
1007
|
+
step(count, p) {
|
|
1008
|
+
const e = p.explosion;
|
|
1009
|
+
const flag = this.ex.particle_step(
|
|
1010
|
+
p.dt,
|
|
1011
|
+
p.mouseX,
|
|
1012
|
+
p.mouseY,
|
|
1013
|
+
p.width,
|
|
1014
|
+
p.height,
|
|
1015
|
+
p.springK,
|
|
1016
|
+
p.damping,
|
|
1017
|
+
p.bounceDamping,
|
|
1018
|
+
p.maxVelocity,
|
|
1019
|
+
e ? 1 : 0,
|
|
1020
|
+
e ? e.x : 0,
|
|
1021
|
+
e ? e.y : 0,
|
|
1022
|
+
e ? e.force : 0,
|
|
1023
|
+
count
|
|
1024
|
+
);
|
|
1025
|
+
return flag !== 0;
|
|
1026
|
+
}
|
|
1027
|
+
/** Transpose the AoS stride-8 buffer into the SoA views (position/velocity/
|
|
1028
|
+
* life every frame; origin upload-once when `withOrigin`). */
|
|
1029
|
+
gather(data, count, withOrigin) {
|
|
1030
|
+
const v = this.view;
|
|
1031
|
+
for (let i = 0; i < count; i++) {
|
|
1032
|
+
const o = i * PARTICLE_STRIDE_FLOATS;
|
|
1033
|
+
v.px[i] = data[o + PARTICLE_OFFSET_POSITION_X];
|
|
1034
|
+
v.py[i] = data[o + PARTICLE_OFFSET_POSITION_Y];
|
|
1035
|
+
v.vx[i] = data[o + PARTICLE_OFFSET_VELOCITY_X];
|
|
1036
|
+
v.vy[i] = data[o + PARTICLE_OFFSET_VELOCITY_Y];
|
|
1037
|
+
v.life[i] = data[o + PARTICLE_OFFSET_LIFE];
|
|
1038
|
+
if (withOrigin) {
|
|
1039
|
+
v.ox[i] = data[o + PARTICLE_OFFSET_ORIGIN_X];
|
|
1040
|
+
v.oy[i] = data[o + PARTICLE_OFFSET_ORIGIN_Y];
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
/** Scatter the mutated position/velocity/life back into the AoS buffer. */
|
|
1045
|
+
scatter(data, count) {
|
|
1046
|
+
const v = this.view;
|
|
1047
|
+
for (let i = 0; i < count; i++) {
|
|
1048
|
+
const o = i * PARTICLE_STRIDE_FLOATS;
|
|
1049
|
+
data[o + PARTICLE_OFFSET_POSITION_X] = v.px[i];
|
|
1050
|
+
data[o + PARTICLE_OFFSET_POSITION_Y] = v.py[i];
|
|
1051
|
+
data[o + PARTICLE_OFFSET_VELOCITY_X] = v.vx[i];
|
|
1052
|
+
data[o + PARTICLE_OFFSET_VELOCITY_Y] = v.vy[i];
|
|
1053
|
+
data[o + PARTICLE_OFFSET_LIFE] = v.life[i];
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
refreshViews() {
|
|
1057
|
+
const buf = this.ex.memory.buffer;
|
|
1058
|
+
const c = this.cap;
|
|
1059
|
+
this.view = {
|
|
1060
|
+
px: new Float32Array(buf, this.ex.pp_px(), c),
|
|
1061
|
+
py: new Float32Array(buf, this.ex.pp_py(), c),
|
|
1062
|
+
vx: new Float32Array(buf, this.ex.pp_vx(), c),
|
|
1063
|
+
vy: new Float32Array(buf, this.ex.pp_vy(), c),
|
|
1064
|
+
ox: new Float32Array(buf, this.ex.pp_ox(), c),
|
|
1065
|
+
oy: new Float32Array(buf, this.ex.pp_oy(), c),
|
|
1066
|
+
life: new Float32Array(buf, this.ex.pp_life(), c)
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
}, _class5);
|
|
1070
|
+
async function instantiateAsync4(bytes) {
|
|
1071
|
+
try {
|
|
1072
|
+
const { instance } = await WebAssembly.instantiate(bytes, {});
|
|
1073
|
+
return new ParticleBackend(instance);
|
|
1074
|
+
} catch (e12) {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
async function instantiateStreaming4(source) {
|
|
1079
|
+
try {
|
|
1080
|
+
const resp = typeof source === "string" || source instanceof URL ? await fetch(String(source)) : await source;
|
|
1081
|
+
if (typeof WebAssembly.instantiateStreaming === "function") {
|
|
1082
|
+
const buffered = resp.clone();
|
|
1083
|
+
try {
|
|
1084
|
+
const { instance: instance2 } = await WebAssembly.instantiateStreaming(resp, {});
|
|
1085
|
+
return new ParticleBackend(instance2);
|
|
1086
|
+
} catch (e13) {
|
|
1087
|
+
const { instance: instance2 } = await WebAssembly.instantiate(await buffered.arrayBuffer(), {});
|
|
1088
|
+
return new ParticleBackend(instance2);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
const { instance } = await WebAssembly.instantiate(await resp.arrayBuffer(), {});
|
|
1092
|
+
return new ParticleBackend(instance);
|
|
1093
|
+
} catch (e14) {
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
312
1098
|
// src/tree/Scene.ts
|
|
313
1099
|
var _text = require('@vectojs/text'); _createStarExport(_text);
|
|
314
1100
|
var INTERACTIVE_A11Y_ROLES = /* @__PURE__ */ new Set([
|
|
@@ -597,13 +1383,13 @@ function extendSelection(selection, anchor, focus) {
|
|
|
597
1383
|
try {
|
|
598
1384
|
selection.setBaseAndExtent(anchor.node, anchor.offset, focus.node, focus.offset);
|
|
599
1385
|
return;
|
|
600
|
-
} catch (
|
|
1386
|
+
} catch (e15) {
|
|
601
1387
|
}
|
|
602
1388
|
try {
|
|
603
1389
|
selection.collapse(anchor.node, anchor.offset);
|
|
604
1390
|
selection.extend(focus.node, focus.offset);
|
|
605
1391
|
return;
|
|
606
|
-
} catch (
|
|
1392
|
+
} catch (e16) {
|
|
607
1393
|
}
|
|
608
1394
|
const anchorRange = document.createRange();
|
|
609
1395
|
anchorRange.setStart(anchor.node, anchor.offset);
|
|
@@ -622,9 +1408,13 @@ function extendSelection(selection, anchor, focus) {
|
|
|
622
1408
|
selection.removeAllRanges();
|
|
623
1409
|
selection.addRange(range);
|
|
624
1410
|
}
|
|
625
|
-
var Scene = (
|
|
1411
|
+
var Scene = (_class6 = class _Scene {
|
|
626
1412
|
static __initStatic() {this.webglCreator = null}
|
|
627
1413
|
static __initStatic2() {this.webgpuManagerClass = null}
|
|
1414
|
+
/** Upper bound (ms) on a single frame's `dt`. Caps the giant elapsed gap a
|
|
1415
|
+
* backgrounded/refocused tab produces so physics advances at most one slow
|
|
1416
|
+
* frame instead of the whole idle duration (~100ms ≈ 6 frames at 60fps). */
|
|
1417
|
+
static __initStatic3() {this.MAX_FRAME_DT = 100}
|
|
628
1418
|
static registerWebGLPointRendererCreator(creator) {
|
|
629
1419
|
_Scene.webglCreator = creator;
|
|
630
1420
|
}
|
|
@@ -634,8 +1424,15 @@ var Scene = (_class2 = class _Scene {
|
|
|
634
1424
|
|
|
635
1425
|
|
|
636
1426
|
|
|
637
|
-
|
|
638
|
-
|
|
1427
|
+
__init20() {this.isRunning = false}
|
|
1428
|
+
/** Whether the canvas is at least partially in the viewport. When it scrolls
|
|
1429
|
+
* fully off-screen the rAF loop pauses (stops rescheduling) instead of
|
|
1430
|
+
* burning frames on a scene nobody can see; an IntersectionObserver resumes
|
|
1431
|
+
* it on re-entry. Defaults true (and stays true where IntersectionObserver
|
|
1432
|
+
* is unavailable, e.g. SSR/jsdom, so behavior is unchanged there). */
|
|
1433
|
+
__init21() {this._canvasOnScreen = true}
|
|
1434
|
+
__init22() {this._canvasObserver = null}
|
|
1435
|
+
__init23() {this.lastTime = 0}
|
|
639
1436
|
|
|
640
1437
|
/**
|
|
641
1438
|
* Redraw strategy:
|
|
@@ -644,133 +1441,700 @@ var Scene = (_class2 = class _Scene {
|
|
|
644
1441
|
* {@link markDirty}) or while an animation is pending. Ideal for static /
|
|
645
1442
|
* event-driven UIs where idle frames should cost ~0.
|
|
646
1443
|
*/
|
|
647
|
-
|
|
648
|
-
|
|
1444
|
+
__init24() {this.renderMode = "always"}
|
|
1445
|
+
__init25() {this.dirty = true}
|
|
649
1446
|
/** Whether to throttle rendering to 2 FPS when the scene is static to save power. */
|
|
650
|
-
|
|
1447
|
+
__init26() {this.autoThrottle = true}
|
|
651
1448
|
// --- Frame telemetry (read via `frameStats`) ---------------------------
|
|
652
1449
|
/** Wall-clock ms spent inside the last `render()` call. */
|
|
653
|
-
|
|
1450
|
+
__init27() {this._lastFrameMs = 0}
|
|
654
1451
|
/** Rolling exponential average of rendered-frame intervals, in ms. */
|
|
655
|
-
|
|
1452
|
+
__init28() {this._avgFrameIntervalMs = 0}
|
|
656
1453
|
/** dt (ms) handed to the last rendered frame. */
|
|
657
|
-
|
|
1454
|
+
__init29() {this._lastDt = 0}
|
|
658
1455
|
/** Count of frames actually rendered since the loop started. */
|
|
659
|
-
|
|
1456
|
+
__init30() {this._renderedFrames = 0}
|
|
660
1457
|
/** Count of rAF ticks skipped (idle / capped) since the loop started. */
|
|
661
|
-
|
|
1458
|
+
__init31() {this._skippedFrames = 0}
|
|
662
1459
|
/** `time` of the previous *rendered* frame, for interval measurement. */
|
|
663
|
-
|
|
1460
|
+
__init32() {this._lastRenderTick = 0}
|
|
664
1461
|
/**
|
|
665
1462
|
* Frame-rate cap (power saving). `0` = uncapped (native refresh). When set,
|
|
666
1463
|
* the loop renders at most `maxFPS` times per second; animations still run,
|
|
667
1464
|
* just less often. See {@link SceneOptions.maxFPS}.
|
|
668
1465
|
*/
|
|
669
|
-
|
|
1466
|
+
__init33() {this.maxFPS = 60}
|
|
670
1467
|
/** Whether the OS prefers-reduced-motion setting auto-caps the loop. */
|
|
671
|
-
|
|
1468
|
+
__init34() {this.respectReducedMotion = true}
|
|
1469
|
+
/**
|
|
1470
|
+
* Reading direction for accessibility tab/traversal order (`'ltr'` default,
|
|
1471
|
+
* `'rtl'`). Controls the inline sort within a visual row in
|
|
1472
|
+
* {@link enforceA11yDomOrder}. Set at runtime to re-flow tab order on the
|
|
1473
|
+
* next sync (also trips a reorder).
|
|
1474
|
+
*/
|
|
1475
|
+
get readingDirection() {
|
|
1476
|
+
return this._readingDirection;
|
|
1477
|
+
}
|
|
1478
|
+
set readingDirection(dir) {
|
|
1479
|
+
if (dir !== this._readingDirection) {
|
|
1480
|
+
this._readingDirection = dir;
|
|
1481
|
+
this.a11yNeedsReorder = true;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
__init35() {this._readingDirection = "ltr"}
|
|
672
1485
|
/** Cached media-query list; `.matches` is read live each frame. */
|
|
673
|
-
|
|
1486
|
+
__init36() {this.reducedMotionQuery = null}
|
|
1487
|
+
/** Cached `(forced-colors: active)` query (Windows High Contrast etc.). A
|
|
1488
|
+
* canvas gets NO automatic forced-colors treatment from the browser (it's
|
|
1489
|
+
* opaque pixels), so components must read {@link forcedColors} and repaint
|
|
1490
|
+
* with system colors themselves; a change listener repaints idle scenes. */
|
|
1491
|
+
__init37() {this.forcedColorsQuery = null}
|
|
1492
|
+
__init38() {this.forcedColorsChangeHandler = null}
|
|
674
1493
|
/** True when the OS asks for reduced motion and we respect it. Read by the animation drivers. */
|
|
675
1494
|
get prefersReducedMotion() {
|
|
676
1495
|
return this.respectReducedMotion && !!_optionalChain([this, 'access', _21 => _21.reducedMotionQuery, 'optionalAccess', _22 => _22.matches]);
|
|
677
1496
|
}
|
|
1497
|
+
/**
|
|
1498
|
+
* True when the OS is in a forced-colors mode (Windows High Contrast, and the
|
|
1499
|
+
* `forced-colors: active` media feature generally). Canvas pixels are exempt
|
|
1500
|
+
* from the browser's forced-colors remapping, so accessible components should
|
|
1501
|
+
* read this and draw with CSS system colors (`CanvasText`, `Canvas`,
|
|
1502
|
+
* `Highlight`, …) instead of their themed palette. Re-rendered automatically
|
|
1503
|
+
* when the setting toggles.
|
|
1504
|
+
*/
|
|
1505
|
+
get forcedColors() {
|
|
1506
|
+
return !!_optionalChain([this, 'access', _23 => _23.forcedColorsQuery, 'optionalAccess', _24 => _24.matches]);
|
|
1507
|
+
}
|
|
678
1508
|
/**
|
|
679
1509
|
* Throttle interval (ms) for the a11y/automation shadow sync. `0` = every
|
|
680
1510
|
* frame. See {@link SceneOptions.a11ySyncInterval}.
|
|
681
1511
|
*/
|
|
682
|
-
|
|
1512
|
+
__init39() {this.a11ySyncInterval = 0}
|
|
683
1513
|
/** Timestamp of the last a11y sync, for throttling. */
|
|
684
|
-
|
|
1514
|
+
__init40() {this.lastA11ySync = -Infinity}
|
|
685
1515
|
/** True if we skipped an a11y sync during animation and need to sync when at rest. */
|
|
686
|
-
|
|
1516
|
+
__init41() {this.a11yPendingSyncAfterAnimation = false}
|
|
687
1517
|
// A11y / Automation Layer. `null` in non-DOM (SSR/Node) environments — the
|
|
688
1518
|
// whole projection degrades to a no-op so the engine's logic stays usable
|
|
689
1519
|
// server-side (e.g. headless layout / vector export) without jsdom.
|
|
690
1520
|
|
|
691
|
-
|
|
1521
|
+
__init42() {this.a11yElements = /* @__PURE__ */ new Map()}
|
|
692
1522
|
/** DOM nodes mirroring static text content, keyed by entity id. */
|
|
693
|
-
|
|
1523
|
+
__init43() {this.contentElements = /* @__PURE__ */ new Map()}
|
|
694
1524
|
/** Pending cold font-calibration frame per projected grid entity. */
|
|
695
|
-
|
|
1525
|
+
__init44() {this.contentGridCalibrationFrames = /* @__PURE__ */ new Map()}
|
|
696
1526
|
/** Detached, untransformed font probes used by the cold calibration pass. */
|
|
697
|
-
|
|
1527
|
+
__init45() {this.contentGridCalibrationProbes = /* @__PURE__ */ new Map()}
|
|
698
1528
|
/** Invalidates grid font calibration after browser font availability changes. */
|
|
699
|
-
|
|
1529
|
+
__init46() {this.contentFontEpoch = 0}
|
|
700
1530
|
/** Cached Canvas-to-client scale for the current font/viewport epoch. */
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1531
|
+
__init47() {this.contentMetricScaleEpoch = -1}
|
|
1532
|
+
__init48() {this.contentMetricScaleX = 1}
|
|
1533
|
+
__init49() {this.contentProjectionEnabled = true}
|
|
704
1534
|
// Virtualization margin (px) for content projection; `undefined` → one
|
|
705
1535
|
// viewport height, resolved at sync time. `Infinity` = materialize everything.
|
|
706
|
-
|
|
1536
|
+
__init50() {this.contentProjectionMargin = void 0}
|
|
707
1537
|
/**
|
|
708
1538
|
* True while a text-selection drag that started on a projection's blank
|
|
709
1539
|
* region (no text node under the press) is being driven manually — the
|
|
710
1540
|
* browser has no native anchor for it, so mousemove extends the Selection
|
|
711
1541
|
* from the position we resolved ourselves.
|
|
712
1542
|
*/
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1543
|
+
__init51() {this.blankRegionSelectionDrag = false}
|
|
1544
|
+
__init52() {this.contentSelectionAnchor = null}
|
|
1545
|
+
__init53() {this.contentSelectionEndListener = null}
|
|
716
1546
|
// Animation/interactive flags collected during the render walk (tree-walk
|
|
717
1547
|
// fusion): the loop reads last frame's answers instead of re-walking the
|
|
718
1548
|
// tree up to 4× per tick. Start true so the first tick stays conservative.
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
1549
|
+
__init54() {this.frameHadAnimation = true}
|
|
1550
|
+
__init55() {this.frameHadInteractive = true}
|
|
1551
|
+
|
|
1552
|
+
/** Active `(resolution: Ndppx)` media query watching for a runtime DPR change
|
|
1553
|
+
* (window moved between monitors, browser zoom) so the canvas backing store
|
|
1554
|
+
* can be re-scaled — otherwise it stays rasterized at the old DPR and blurs.
|
|
1555
|
+
* A resolution media query only fires when leaving its exact value, so the
|
|
1556
|
+
* handler re-arms a fresh query for the new DPR each time. */
|
|
1557
|
+
__init56() {this.dprMediaQuery = null}
|
|
1558
|
+
/** For embedded (`disableWindowResize`) scenes: observes the canvas element so
|
|
1559
|
+
* a CSS/layout-driven size change re-runs `resize()`. A window `resize`
|
|
1560
|
+
* listener never fires for these (the window isn't what changed), so without
|
|
1561
|
+
* this an embedded canvas stayed at its initial size forever. */
|
|
1562
|
+
__init57() {this.canvasResizeObserver = null}
|
|
1563
|
+
__init58() {this.dprChangeHandler = null}
|
|
1564
|
+
__init59() {this.focusedA11yElement = null}
|
|
1565
|
+
/** Last geometry `syncOverlayGeometry` wrote, so an unchanged frame can skip the
|
|
1566
|
+
* style writes entirely. Reset to `null` to force the next sync (a new overlay
|
|
1567
|
+
* layer was created and has never been positioned). */
|
|
1568
|
+
__init60() {this._overlayGeometry = null}
|
|
1569
|
+
/** Shadow elements the pointer is currently inside. Lets a removal that happens
|
|
1570
|
+
* mid-hover synthesize the `pointerleave` the browser never sends for a
|
|
1571
|
+
* detached element, so the entity doesn't keep its hover state. */
|
|
1572
|
+
__init61() {this.hoveredA11yElements = /* @__PURE__ */ new WeakSet()}
|
|
1573
|
+
/** Persistent tabindex=-1 element in a11yRoot. When the focused a11y mirror is
|
|
1574
|
+
* pruned (virtualization/streaming/removal) while it holds focus, we move
|
|
1575
|
+
* focus here instead of letting the browser drop it to <body> — keeping the
|
|
1576
|
+
* screen-reader virtual cursor inside the scene's a11y region. */
|
|
1577
|
+
__init62() {this.focusSentinel = null}
|
|
1578
|
+
__init63() {this.caretBlinkTimer = null}
|
|
1579
|
+
__init64() {this.a11yNeedsReorder = true}
|
|
1580
|
+
__init65() {this.portalRoot = null}
|
|
1581
|
+
__init66() {this.fullViewportElements = []}
|
|
1582
|
+
__init67() {this.normalElements = []}
|
|
1583
|
+
__init68() {this.activeIds = /* @__PURE__ */ new Set()}
|
|
1584
|
+
__init69() {this.activePortalsThisFrame = /* @__PURE__ */ new Set()}
|
|
1585
|
+
__init70() {this.activePortalsPrevFrame = /* @__PURE__ */ new Set()}
|
|
1586
|
+
__init71() {this.portalEntities = /* @__PURE__ */ new Map()}
|
|
1587
|
+
__init72() {this.renderOrderCounter = 0}
|
|
1588
|
+
/**
|
|
1589
|
+
* Monotonic render-frame counter, bumped once per authoritative `render()`
|
|
1590
|
+
* pass. Entities stamp their per-frame world-matrix cache with this value and
|
|
1591
|
+
* {@link Entity.getWorldTransform} trusts that cache only while it still
|
|
1592
|
+
* matches, so a query outside the frame that produced it transparently falls
|
|
1593
|
+
* back to the ancestor walk. Public for the same reason `Entity._getTrig`/
|
|
1594
|
+
* `_setWorldCache` are: it is a cross-class render-internal contract.
|
|
1595
|
+
*/
|
|
1596
|
+
__init73() {this.currentFrame = 0}
|
|
1597
|
+
// ── WASM transform backend (invisible accelerator) ──────────────────────────
|
|
1598
|
+
// When `_transformBackend === 'wasm'`, the main render walk sources each
|
|
1599
|
+
// entity's world matrix from an SoA store composed by `_wasm` (see
|
|
1600
|
+
// `renderNode`), instead of composing it in JS. The JS path is the permanent
|
|
1601
|
+
// fallback and the default: a null backend, a non-main renderer, or any entity
|
|
1602
|
+
// absent from the store all fall back to the JS composition, so WASM can only
|
|
1603
|
+
// ever change *how fast* a world matrix is produced, never *what* it is.
|
|
1604
|
+
__init74() {this._wasm = null}
|
|
1605
|
+
__init75() {this._transformBackend = "js"}
|
|
1606
|
+
// Resident store state (Stage 3). The store layout — slot assignment + sibling
|
|
1607
|
+
// runs — depends only on tree TOPOLOGY, so it is rebuilt only when the
|
|
1608
|
+
// structure changes (add/remove/reparent bump `_structureVersion`). Between
|
|
1609
|
+
// rebuilds the per-frame cost is: gather each entity's transform into the
|
|
1610
|
+
// resident wasm input view + run the kernel — no reallocation, no readback.
|
|
1611
|
+
__init76() {this._treeStore = null}
|
|
1612
|
+
__init77() {this._slotEntity = []}
|
|
1613
|
+
// store slot -> entity (also validates slots)
|
|
1614
|
+
__init78() {this._wasmInputs = null}
|
|
1615
|
+
__init79() {this._wasmWorld = null}
|
|
1616
|
+
__init80() {this._structureVersion = 0}
|
|
1617
|
+
__init81() {this._storeStructureVersion = -1}
|
|
1618
|
+
// Cached list of ComputeParticleEntity instances in the tree, keyed by the
|
|
1619
|
+
// structure version it was gathered at. Rebuilt only on a topology change.
|
|
1620
|
+
__init82() {this._computeEntities = []}
|
|
1621
|
+
__init83() {this._computeEntitiesVersion = -1}
|
|
1622
|
+
/** Invalidate the resident WASM store layout; the next wasm-mode frame rebuilds
|
|
1623
|
+
* it. Called by `Entity.add`/`remove` (topology changes only). */
|
|
1624
|
+
markStructureChanged() {
|
|
1625
|
+
this._structureVersion++;
|
|
1626
|
+
}
|
|
1627
|
+
/** The tree's ComputeParticleEntity instances, cached per structure version so
|
|
1628
|
+
* a compute-free scene doesn't re-walk the whole tree every frame. */
|
|
1629
|
+
_computeEntitiesFor(version) {
|
|
1630
|
+
if (this._computeEntitiesVersion === version) return this._computeEntities;
|
|
1631
|
+
const list = [];
|
|
1632
|
+
const collect = (node) => {
|
|
1633
|
+
if (node instanceof ComputeParticleEntity) list.push(node);
|
|
1634
|
+
for (const child of node.children) collect(child);
|
|
1635
|
+
};
|
|
1636
|
+
collect(this.root);
|
|
1637
|
+
for (const overlay of this.overlayRoot.children) collect(overlay);
|
|
1638
|
+
this._computeEntities = list;
|
|
1639
|
+
this._computeEntitiesVersion = version;
|
|
1640
|
+
return list;
|
|
1641
|
+
}
|
|
1642
|
+
/** Which backend composes world matrices for the main render walk. */
|
|
1643
|
+
get transformBackend() {
|
|
1644
|
+
return this._transformBackend;
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Install (or clear) a WASM transform backend. Passing a backend switches the
|
|
1648
|
+
* main render walk onto it; passing `null` reverts to the JS path. Synchronous
|
|
1649
|
+
* and safe to call between frames — the next `render()` picks it up. Prefer
|
|
1650
|
+
* {@link enableWasmTransforms} for the normal async hot-swap.
|
|
1651
|
+
*/
|
|
1652
|
+
setTransformBackend(backend) {
|
|
1653
|
+
this._wasm = backend;
|
|
1654
|
+
this._transformBackend = backend ? "wasm" : "js";
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Asynchronously instantiate the WASM transform core and, on success, hot-swap
|
|
1658
|
+
* the render walk onto it. Accepts whatever is convenient at the call site:
|
|
1659
|
+
*
|
|
1660
|
+
* ```ts
|
|
1661
|
+
* // The common case — a bundler-emitted, co-located asset URL:
|
|
1662
|
+
* await scene.enableWasmTransforms(new URL('./vectojs_core.wasm', import.meta.url));
|
|
1663
|
+
* // …or a path string, a Response, or raw bytes you already have:
|
|
1664
|
+
* await scene.enableWasmTransforms('/assets/vectojs_core.wasm');
|
|
1665
|
+
* await scene.enableWasmTransforms(await fetch(url));
|
|
1666
|
+
* await scene.enableWasmTransforms(myUint8Array);
|
|
1667
|
+
* ```
|
|
1668
|
+
*
|
|
1669
|
+
* A URL/Response streams (compiles while it downloads, with a buffered
|
|
1670
|
+
* fallback for a wrong MIME type); raw bytes instantiate directly. The Scene
|
|
1671
|
+
* keeps rendering on the JS path until this resolves, and stays on JS if
|
|
1672
|
+
* instantiation fails (CSP `wasm-unsafe-eval`, unsupported SIMD, corrupt or
|
|
1673
|
+
* missing bytes, a 404) — failure is the default state, not an error path.
|
|
1674
|
+
* Resolves `true` if WASM is now active, `false` if the JS path remains.
|
|
1675
|
+
*/
|
|
1676
|
+
async enableWasmTransforms(source) {
|
|
1677
|
+
const isBytes = source instanceof ArrayBuffer || ArrayBuffer.isView(source);
|
|
1678
|
+
const backend = isBytes ? await instantiateAsync(source) : await instantiateStreaming(source);
|
|
1679
|
+
if (!backend) return false;
|
|
1680
|
+
this.setTransformBackend(backend);
|
|
1681
|
+
return true;
|
|
1682
|
+
}
|
|
1683
|
+
// ── WASM hit-test backend (invisible accelerator, G3) ───────────────────────
|
|
1684
|
+
// A separate WASM module instance from the transform backend (each crate
|
|
1685
|
+
// export lives in independent linear memory per instance, so there is no
|
|
1686
|
+
// shared-state hazard in running both) that indexes the main tree's world
|
|
1687
|
+
// AABBs into a dense viewport grid for findEntityAt. The JS depth-first walk
|
|
1688
|
+
// (findHitRecursively) is the permanent fallback: a null backend, a build
|
|
1689
|
+
// that overflows its item budget, or the overlay tree (never indexed — small
|
|
1690
|
+
// and rare, not worth accelerating) all fall through to it, so WASM can only
|
|
1691
|
+
// ever change *how fast* a hit is found, never *which* entity is returned —
|
|
1692
|
+
// every grid candidate is re-confirmed against its own precise
|
|
1693
|
+
// isPointInside before being trusted (see hit-store.ts / hit-backend.ts).
|
|
1694
|
+
__init84() {this._hitWasm = null}
|
|
1695
|
+
// Cache key: which frame + structure version the grid was last (successfully,
|
|
1696
|
+
// non-overflowing) built for. findEntityAt is called ad-hoc (pointer
|
|
1697
|
+
// hover/click), not every frame, so the grid is refreshed lazily on demand
|
|
1698
|
+
// rather than proactively every render() — unlike the transform store, which
|
|
1699
|
+
// every frame's draw depends on.
|
|
1700
|
+
__init85() {this._hitGridFrame = -1}
|
|
1701
|
+
__init86() {this._hitGridOk = false}
|
|
1702
|
+
__init87() {this._hitSlotEntity = []}
|
|
1703
|
+
__init88() {this._hitBoundless = []}
|
|
1704
|
+
/** Which backend answers `findEntityAt` for the main tree. */
|
|
1705
|
+
get hitTestBackend() {
|
|
1706
|
+
return this._hitWasm ? "wasm" : "js";
|
|
1707
|
+
}
|
|
1708
|
+
/** Install (or clear) a WASM hit-test backend directly. Prefer
|
|
1709
|
+
* {@link enableWasmHitTest} for the normal async hot-swap. */
|
|
1710
|
+
setHitTestBackend(backend) {
|
|
1711
|
+
this._hitWasm = backend;
|
|
1712
|
+
this._hitGridFrame = -1;
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Asynchronously instantiate the WASM hit-test core and, on success, hot-swap
|
|
1716
|
+
* `findEntityAt` onto it. Accepts the same source shapes as
|
|
1717
|
+
* {@link enableWasmTransforms} (URL, path string, Response, or raw bytes).
|
|
1718
|
+
* Stays on the JS walk if instantiation fails — failure is the default
|
|
1719
|
+
* state, not an error path. Resolves `true` if WASM is now active.
|
|
1720
|
+
*/
|
|
1721
|
+
async enableWasmHitTest(source) {
|
|
1722
|
+
const isBytes = source instanceof ArrayBuffer || ArrayBuffer.isView(source);
|
|
1723
|
+
const backend = isBytes ? await instantiateAsync2(source) : await instantiateStreaming2(source);
|
|
1724
|
+
if (!backend) return false;
|
|
1725
|
+
this.setHitTestBackend(backend);
|
|
1726
|
+
return true;
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Refresh the hit-test grid for the CURRENT tree state if it is stale (a
|
|
1730
|
+
* structural or transform change may have happened since the last build —
|
|
1731
|
+
* there is no cheap "nothing moved" shortcut for a spatial index the way
|
|
1732
|
+
* there is for the transform store's topology-only run table, since ANY
|
|
1733
|
+
* entity moving invalidates its AABB, not just add/remove/reparent; the
|
|
1734
|
+
* measured build cost is cheap enough to redo per call). Returns `false`
|
|
1735
|
+
* (grid untrustworthy — caller must use the JS walk) when there is no
|
|
1736
|
+
* backend or the build overflowed its item budget.
|
|
1737
|
+
*/
|
|
1738
|
+
_ensureHitGrid() {
|
|
1739
|
+
const backend = this._hitWasm;
|
|
1740
|
+
if (!backend) return false;
|
|
1741
|
+
if (this._hitGridFrame === this.currentFrame) return this._hitGridOk;
|
|
1742
|
+
const gathered = gatherHitAABBs(this.root, this.currentFrame);
|
|
1743
|
+
backend.ensure(gathered.count, this.width, this.height, 64);
|
|
1744
|
+
const view = backend.inputView();
|
|
1745
|
+
view.minx.set(gathered.minx.subarray(0, gathered.count));
|
|
1746
|
+
view.miny.set(gathered.miny.subarray(0, gathered.count));
|
|
1747
|
+
view.maxx.set(gathered.maxx.subarray(0, gathered.count));
|
|
1748
|
+
view.maxy.set(gathered.maxy.subarray(0, gathered.count));
|
|
1749
|
+
const ok = backend.runBuild(gathered.count, this.width, this.height, 64);
|
|
1750
|
+
this._hitSlotEntity = gathered.slotEntity;
|
|
1751
|
+
this._hitBoundless = gathered.boundless;
|
|
1752
|
+
this._hitGridFrame = this.currentFrame;
|
|
1753
|
+
this._hitGridOk = ok;
|
|
1754
|
+
return ok;
|
|
1755
|
+
}
|
|
1756
|
+
/**
|
|
1757
|
+
* `findEntityAt`'s WASM-accelerated path for the main tree. Scans only the
|
|
1758
|
+
* queried cell's candidates (confirming each against its own AABB and precise
|
|
1759
|
+
* `isPointInside`) merged against the (typically empty or tiny) list of
|
|
1760
|
+
* entities with no `getBounds()`, taking whichever confirmed match has the
|
|
1761
|
+
* higher pre-order index — see hit-store.ts for why that is exactly
|
|
1762
|
+
* equivalent to findHitRecursively's topmost-hit priority. Always
|
|
1763
|
+
* conclusive: returns the correct entity or `null`, never "inconclusive".
|
|
1764
|
+
*/
|
|
1765
|
+
_findEntityAtWasm(x, y) {
|
|
1766
|
+
const backend = this._hitWasm;
|
|
1767
|
+
const { minx, miny, maxx, maxy } = backend.inputView();
|
|
1768
|
+
let bestIndex = -1;
|
|
1769
|
+
let bestEntity = null;
|
|
1770
|
+
const cell = backend.candidatesAt(x, y);
|
|
1771
|
+
if (cell) {
|
|
1772
|
+
for (let k = cell.length - 1; k >= 0; k--) {
|
|
1773
|
+
const idx = cell[k];
|
|
1774
|
+
if (x < minx[idx] || x > maxx[idx] || y < miny[idx] || y > maxy[idx]) continue;
|
|
1775
|
+
const entity = this._hitSlotEntity[idx];
|
|
1776
|
+
if (_optionalChain([entity, 'optionalAccess', _25 => _25.isPointInside, 'call', _26 => _26(x, y)]) && this.isHitEligible(entity, x, y)) {
|
|
1777
|
+
bestIndex = idx;
|
|
1778
|
+
bestEntity = entity;
|
|
1779
|
+
break;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
for (const { entity, index } of this._hitBoundless) {
|
|
1784
|
+
if (index > bestIndex && entity.isPointInside(x, y) && this.isHitEligible(entity, x, y)) {
|
|
1785
|
+
bestIndex = index;
|
|
1786
|
+
bestEntity = entity;
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
return bestEntity;
|
|
1790
|
+
}
|
|
1791
|
+
// ── WASM batched-animation backend (invisible accelerator, G2) ──────────────
|
|
1792
|
+
// Advances every currently-active SpringDriver/TweenDriver in one WASM call
|
|
1793
|
+
// each (spring_step/tween_step) instead of Entity.tickDrivers()'s per-driver
|
|
1794
|
+
// JS loop. The JS tick loop is the permanent fallback: a null backend, a
|
|
1795
|
+
// driver count below `animDriverGateCount`, or a TweenDriver using a custom
|
|
1796
|
+
// EasingFn (which cannot cross into WASM) all fall through to it — WASM can
|
|
1797
|
+
// only ever change *how* a driver is advanced, never *what* value it lands
|
|
1798
|
+
// on.
|
|
1799
|
+
__init89() {this._animWasm = null}
|
|
1800
|
+
// Entities with at least one active driver, added by Entity._spawnDriver.
|
|
1801
|
+
// Self-pruning: _tickBatchedDrivers drops an entry the first time it visits
|
|
1802
|
+
// an entity whose drivers have since all completed or been removed. This is
|
|
1803
|
+
// what lets the batch pass find its candidates in O(active drivers), not
|
|
1804
|
+
// O(tree size) — the exact mistake G3's first integrated benchmark made.
|
|
1805
|
+
__init90() {this._activeDriverEntities = /* @__PURE__ */ new Set()}
|
|
1806
|
+
// Reused across frames instead of allocating a fresh array + N {entity,prop,
|
|
1807
|
+
// driver} objects every call — the integrated benchmark
|
|
1808
|
+
// (benchmarks/anim-wasm-scene) found that allocation churn was the
|
|
1809
|
+
// dominant integrated cost, not the wasm kernel itself. Parallel arrays,
|
|
1810
|
+
// truncated to the live count after each use so a stale tail slot never
|
|
1811
|
+
// pins a no-longer-active entity/driver in memory.
|
|
1812
|
+
__init91() {this._springEntities = []}
|
|
1813
|
+
__init92() {this._springProps = []}
|
|
1814
|
+
__init93() {this._springDrivers = []}
|
|
1815
|
+
__init94() {this._tweenEntities = []}
|
|
1816
|
+
__init95() {this._tweenProps = []}
|
|
1817
|
+
__init96() {this._tweenDrivers = []}
|
|
1818
|
+
/**
|
|
1819
|
+
* Minimum number of batchable (spring, or named-easing tween) active drivers
|
|
1820
|
+
* before a frame engages the WASM batch path at all; below it, every driver
|
|
1821
|
+
* ticks on the normal JS per-entity path, unmodified.
|
|
1822
|
+
*
|
|
1823
|
+
* Re-measured on the INTEGRATED path (benchmarks/anim-wasm-scene, real
|
|
1824
|
+
* Chrome 150 / Firefox 153, 2026-07-24 — correctness verified 0 mismatches
|
|
1825
|
+
* across all three kinds before any of these numbers were trusted): the
|
|
1826
|
+
* isolated kernel spike's "<100 drivers, wins everywhere" verdict did NOT
|
|
1827
|
+
* survive integration, and neither did the first integrated pass's single
|
|
1828
|
+
* gate-count verdict once broken out by driver kind. On Chrome, spring and
|
|
1829
|
+
* mixed drivers are a real ~1.4–2.3× win from n=128 up through the tested
|
|
1830
|
+
* ceiling of 16384, but pure-tween drivers are a LOSS at n=128 (0.71×,
|
|
1831
|
+
* i.e. ~40% slower than the JS path) and only turn net-positive around
|
|
1832
|
+
* n≈256 (1.52×). A single scalar gate can't be tight for spring/mixed
|
|
1833
|
+
* without occasionally opening early on a tween-heavy scene and making it
|
|
1834
|
+
* slower — 256 is chosen to keep the gate net-positive across all three
|
|
1835
|
+
* kinds rather than optimal for any one of them; a kind-aware gate (see
|
|
1836
|
+
* `_tickBatchedDrivers`'s per-kind arrays, which already separate spring
|
|
1837
|
+
* from tween) would recover the 128–255 spring/mixed win without the
|
|
1838
|
+
* tween regression, but that's a larger change than this measurement pass
|
|
1839
|
+
* covers. On Firefox it is a net loss at every driver count measured, up
|
|
1840
|
+
* to 16384 — not an allocation artifact (confirmed after removing all
|
|
1841
|
+
* per-frame allocation from the gather/scatter path); SpiderMonkey's
|
|
1842
|
+
* wasm-boundary/property-dispatch cost for this shape of call appears to
|
|
1843
|
+
* structurally exceed the saving, at least at the scales tested here.
|
|
1844
|
+
*
|
|
1845
|
+
* 256 is set as a Chrome-oriented default so an app that opts in (this
|
|
1846
|
+
* path is never engaged without an explicit {@link enableWasmAnimBatching}
|
|
1847
|
+
* call) sees the gate open only where it reliably helps on Chromium,
|
|
1848
|
+
* regardless of whether the scene's active drivers are spring, tween, or
|
|
1849
|
+
* a mix of both. Unlike G1 (safe to default on everywhere) and G3 (opt-in,
|
|
1850
|
+
* but a reliable win once its own gate condition holds), G2 has no
|
|
1851
|
+
* threshold that is safe on every engine — raise or lower this per your
|
|
1852
|
+
* own target browser mix and driver-kind distribution, or leave WASM
|
|
1853
|
+
* animation batching disabled entirely on a Firefox-heavy audience.
|
|
1854
|
+
*/
|
|
1855
|
+
__init97() {this.animDriverGateCount = 256}
|
|
1856
|
+
/** Which backend advances active property drivers on the current gate
|
|
1857
|
+
* decision. Reflects only whether a backend is installed — the per-frame
|
|
1858
|
+
* gate can still choose the JS path even when this reads `'wasm'`. */
|
|
1859
|
+
get animBackend() {
|
|
1860
|
+
return this._animWasm ? "wasm" : "js";
|
|
1861
|
+
}
|
|
1862
|
+
/** Install (or clear) a WASM batched-animation backend directly. Prefer
|
|
1863
|
+
* {@link enableWasmAnimBatching} for the normal async hot-swap. */
|
|
1864
|
+
setAnimBackend(backend) {
|
|
1865
|
+
this._animWasm = backend;
|
|
1866
|
+
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Asynchronously instantiate the WASM batched-animation core and, on
|
|
1869
|
+
* success, make it available to the per-frame gate (see
|
|
1870
|
+
* {@link animDriverGateCount}). Accepts the same source shapes as
|
|
1871
|
+
* {@link enableWasmTransforms}. Stays on the JS tick loop if instantiation
|
|
1872
|
+
* fails — failure is the default state, not an error path. Resolves `true`
|
|
1873
|
+
* if WASM is now available (not necessarily active every frame).
|
|
1874
|
+
*/
|
|
1875
|
+
async enableWasmAnimBatching(source) {
|
|
1876
|
+
const isBytes = source instanceof ArrayBuffer || ArrayBuffer.isView(source);
|
|
1877
|
+
const backend = isBytes ? await instantiateAsync3(source) : await instantiateStreaming3(source);
|
|
1878
|
+
if (!backend) return false;
|
|
1879
|
+
this.setAnimBackend(backend);
|
|
1880
|
+
return true;
|
|
1881
|
+
}
|
|
1882
|
+
// ── WASM particle CPU-sim backend (invisible accelerator, G4) ───────────────
|
|
1883
|
+
// Advances a ComputeParticleEntity's whole buffer in one `particle_step` call
|
|
1884
|
+
// (spring/mouse/explosion/integrate/bounce/life), replacing the per-particle
|
|
1885
|
+
// JS `updateCPU` loop on the GPU-less fallback path. Measured ~2.1-2.5x on
|
|
1886
|
+
// Chrome and ~1.4-2.0x on Firefox including the per-frame AoS<->SoA transpose
|
|
1887
|
+
// (benchmarks/particle-wasm). f32 (matches the WGSL shader), bit-identical to
|
|
1888
|
+
// a JS f32 reference oracle; updateCPU (f64) stays the permanent fallback when
|
|
1889
|
+
// no backend is installed or a scene runs on WebGPU.
|
|
1890
|
+
__init98() {this._particleWasm = null}
|
|
1891
|
+
/** Which backend runs the CPU particle simulation. Reflects only whether a
|
|
1892
|
+
* backend is installed (the WebGPU compute path, when active, is used first
|
|
1893
|
+
* regardless). */
|
|
1894
|
+
get particleSimBackend() {
|
|
1895
|
+
return this._particleWasm ? "wasm" : "js";
|
|
1896
|
+
}
|
|
1897
|
+
/** Install (or clear) a WASM particle backend directly. Prefer
|
|
1898
|
+
* {@link enableWasmParticles} for the normal async hot-swap. */
|
|
1899
|
+
setParticleBackend(backend) {
|
|
1900
|
+
this._particleWasm = backend;
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* Asynchronously instantiate the WASM particle core and, on success, use it
|
|
1904
|
+
* for the CPU particle fallback. Accepts the same source shapes as
|
|
1905
|
+
* {@link enableWasmTransforms}. Stays on the JS `updateCPU` path if
|
|
1906
|
+
* instantiation fails — failure is the default state, not an error path.
|
|
1907
|
+
* Resolves `true` if WASM is now active.
|
|
1908
|
+
*/
|
|
1909
|
+
async enableWasmParticles(source) {
|
|
1910
|
+
const isBytes = source instanceof ArrayBuffer || ArrayBuffer.isView(source);
|
|
1911
|
+
const backend = isBytes ? await instantiateAsync4(source) : await instantiateStreaming4(source);
|
|
1912
|
+
if (!backend) return false;
|
|
1913
|
+
this.setParticleBackend(backend);
|
|
1914
|
+
return true;
|
|
1915
|
+
}
|
|
1916
|
+
/** Internal: called by `Entity._spawnDriver` when a new property driver
|
|
1917
|
+
* starts. See {@link _activeDriverEntities}. */
|
|
1918
|
+
_registerActiveDriverEntity(entity) {
|
|
1919
|
+
this._activeDriverEntities.add(entity);
|
|
1920
|
+
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Drop `entity` and its whole subtree from the batched-driver candidate set.
|
|
1923
|
+
* Called by {@link remove}/{@link hideOverlay} on detach: without this a
|
|
1924
|
+
* removed-but-still-animating entity stays pinned in the Set (a leak) and its
|
|
1925
|
+
* drivers keep ticking every frame even though it is off-tree. If it is later
|
|
1926
|
+
* re-added, {@link registerActiveDriverSubtree} re-registers any node that
|
|
1927
|
+
* still has live drivers, so the motion resumes.
|
|
1928
|
+
*/
|
|
1929
|
+
unregisterActiveDriverSubtree(entity) {
|
|
1930
|
+
if (this._activeDriverEntities.size === 0) return;
|
|
1931
|
+
const stack = [entity];
|
|
1932
|
+
while (stack.length > 0) {
|
|
1933
|
+
const node = stack.pop();
|
|
1934
|
+
this._activeDriverEntities.delete(node);
|
|
1935
|
+
for (const child of node.children) stack.push(child);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Re-register every node in `entity`'s subtree that still has live property
|
|
1940
|
+
* drivers. Called by {@link add}/{@link showOverlay} so re-attaching a subtree
|
|
1941
|
+
* that was removed mid-animation resumes its batched drivers (they were
|
|
1942
|
+
* dropped from the candidate set on removal, but the driver state still lives
|
|
1943
|
+
* on each entity).
|
|
1944
|
+
*/
|
|
1945
|
+
registerActiveDriverSubtree(entity) {
|
|
1946
|
+
const stack = [entity];
|
|
1947
|
+
while (stack.length > 0) {
|
|
1948
|
+
const node = stack.pop();
|
|
1949
|
+
const entries = node._driverEntries();
|
|
1950
|
+
if (entries && entries.size > 0) this._activeDriverEntities.add(node);
|
|
1951
|
+
for (const child of node.children) stack.push(child);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Advance every registered entity's active drivers for this frame, batching
|
|
1956
|
+
* whichever are batchable (`SpringDriver`; `TweenDriver` with a named
|
|
1957
|
+
* easing) through one WASM call each when the driver-count gate is open, and
|
|
1958
|
+
* ticking the rest (a `TweenDriver` using a custom `EasingFn`) directly in
|
|
1959
|
+
* JS regardless of the gate. A "claimed" entity must have ALL its drivers
|
|
1960
|
+
* advanced here so it can be safely stamped `_driversTickedFrame` — leaving
|
|
1961
|
+
* one unclaimed would silently stall it, since `tickDrivers()` skips the
|
|
1962
|
+
* whole entity once stamped.
|
|
1963
|
+
*
|
|
1964
|
+
* Must run before ANY entity's `update()`/`tickDrivers()` this frame (see
|
|
1965
|
+
* the call site in {@link render}) — the same ordering constraint G1 Stage 4
|
|
1966
|
+
* discovered: a value this pass writes must be final before anything reads
|
|
1967
|
+
* it, including the JS-mode interleaved walk and the WASM-mode transform
|
|
1968
|
+
* pre-pass.
|
|
1969
|
+
*/
|
|
1970
|
+
_tickBatchedDrivers(dt) {
|
|
1971
|
+
if (this._activeDriverEntities.size === 0) return;
|
|
1972
|
+
let batchable = 0;
|
|
1973
|
+
for (const entity of this._activeDriverEntities) {
|
|
1974
|
+
const entries = entity._driverEntries();
|
|
1975
|
+
if (!entries || entries.size === 0) {
|
|
1976
|
+
this._activeDriverEntities.delete(entity);
|
|
1977
|
+
continue;
|
|
1978
|
+
}
|
|
1979
|
+
for (const driver of entries.values()) {
|
|
1980
|
+
if (driver instanceof _animation.SpringDriver) batchable++;
|
|
1981
|
+
else if (driver instanceof _animation.TweenDriver && driver.wasmEasingId !== null) batchable++;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
const backend = this._animWasm;
|
|
1985
|
+
if (!backend || batchable < this.animDriverGateCount) return;
|
|
1986
|
+
const sE = this._springEntities;
|
|
1987
|
+
const sP = this._springProps;
|
|
1988
|
+
const sD = this._springDrivers;
|
|
1989
|
+
const tE = this._tweenEntities;
|
|
1990
|
+
const tP = this._tweenProps;
|
|
1991
|
+
const tD = this._tweenDrivers;
|
|
1992
|
+
let springCount = 0;
|
|
1993
|
+
let tweenCount = 0;
|
|
1994
|
+
for (const entity of this._activeDriverEntities) {
|
|
1995
|
+
const entries = entity._driverEntries();
|
|
1996
|
+
for (const [prop, driver] of entries) {
|
|
1997
|
+
if (driver instanceof _animation.SpringDriver) {
|
|
1998
|
+
sE[springCount] = entity;
|
|
1999
|
+
sP[springCount] = prop;
|
|
2000
|
+
sD[springCount] = driver;
|
|
2001
|
+
springCount++;
|
|
2002
|
+
} else if (driver instanceof _animation.TweenDriver && driver.wasmEasingId !== null) {
|
|
2003
|
+
tE[tweenCount] = entity;
|
|
2004
|
+
tP[tweenCount] = prop;
|
|
2005
|
+
tD[tweenCount] = driver;
|
|
2006
|
+
tweenCount++;
|
|
2007
|
+
} else {
|
|
2008
|
+
driver.tick(dt);
|
|
2009
|
+
entity._applyDriverTick(prop, driver);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
entity._driversTickedFrame = this.currentFrame;
|
|
2013
|
+
}
|
|
2014
|
+
sE.length = springCount;
|
|
2015
|
+
sP.length = springCount;
|
|
2016
|
+
sD.length = springCount;
|
|
2017
|
+
tE.length = tweenCount;
|
|
2018
|
+
tP.length = tweenCount;
|
|
2019
|
+
tD.length = tweenCount;
|
|
2020
|
+
backend.ensure(springCount, tweenCount);
|
|
2021
|
+
if (springCount > 0) {
|
|
2022
|
+
const sv = backend.springView();
|
|
2023
|
+
for (let i = 0; i < springCount; i++) {
|
|
2024
|
+
const phys = sD[i].physics;
|
|
2025
|
+
sv.val[i] = phys.value;
|
|
2026
|
+
sv.target[i] = phys.target;
|
|
2027
|
+
sv.vel[i] = phys.velocity;
|
|
2028
|
+
sv.stiff[i] = phys.stiffness;
|
|
2029
|
+
sv.damp[i] = phys.damping;
|
|
2030
|
+
sv.mass[i] = phys.mass;
|
|
2031
|
+
}
|
|
2032
|
+
backend.stepSprings(dt, springCount);
|
|
2033
|
+
for (let i = 0; i < springCount; i++) sD[i].syncExternal(sv.val[i], sv.vel[i]);
|
|
2034
|
+
}
|
|
2035
|
+
if (tweenCount > 0) {
|
|
2036
|
+
const tv = backend.tweenView();
|
|
2037
|
+
for (let i = 0; i < tweenCount; i++) {
|
|
2038
|
+
const d = tD[i];
|
|
2039
|
+
tv.from[i] = d.fromValue;
|
|
2040
|
+
tv.to[i] = d.target;
|
|
2041
|
+
tv.elapsed[i] = d.elapsedMs;
|
|
2042
|
+
tv.dur[i] = d.durationMs;
|
|
2043
|
+
tv.delay[i] = d.delayMs;
|
|
2044
|
+
tv.ease[i] = d.wasmEasingId;
|
|
2045
|
+
}
|
|
2046
|
+
backend.stepTweens(dt, tweenCount);
|
|
2047
|
+
for (let i = 0; i < tweenCount; i++) tD[i].syncExternal(tv.val[i], tv.elapsed[i]);
|
|
2048
|
+
}
|
|
2049
|
+
for (let i = 0; i < springCount; i++) sE[i]._applyDriverTick(sP[i], sD[i]);
|
|
2050
|
+
for (let i = 0; i < tweenCount; i++) tE[i]._applyDriverTick(tP[i], tD[i]);
|
|
2051
|
+
}
|
|
2052
|
+
/**
|
|
2053
|
+
* Compose the whole main tree's world matrices through the resident WASM store
|
|
2054
|
+
* and return the world-matrix views for the render walk to read. Rebuilds the
|
|
2055
|
+
* store layout (slots + runs) only when the tree structure changed since the
|
|
2056
|
+
* last rebuild; otherwise it just gathers current transforms into the resident
|
|
2057
|
+
* input view and runs the kernel. Returns `null` if there is no backend.
|
|
2058
|
+
*/
|
|
2059
|
+
_syncWasmStore() {
|
|
2060
|
+
const backend = this._wasm;
|
|
2061
|
+
if (!backend) return null;
|
|
2062
|
+
if (this._treeStore === null || this._storeStructureVersion !== this._structureVersion) {
|
|
2063
|
+
const built = buildTreeStore(this.root);
|
|
2064
|
+
const slotEntity2 = Array.from({ length: built.store.count });
|
|
2065
|
+
for (const [entity, slot] of built.indexOf) {
|
|
2066
|
+
slotEntity2[slot] = entity;
|
|
2067
|
+
entity._storeSlot = slot;
|
|
2068
|
+
}
|
|
2069
|
+
backend.uploadRuns(built.store);
|
|
2070
|
+
this._treeStore = built.store;
|
|
2071
|
+
this._slotEntity = slotEntity2;
|
|
2072
|
+
this._wasmInputs = backend.inputView();
|
|
2073
|
+
this._wasmWorld = backend.worldView();
|
|
2074
|
+
this._storeStructureVersion = this._structureVersion;
|
|
2075
|
+
}
|
|
2076
|
+
const inp = this._wasmInputs;
|
|
2077
|
+
const slotEntity = this._slotEntity;
|
|
2078
|
+
for (let slot = 1; slot < slotEntity.length; slot++) {
|
|
2079
|
+
const e = slotEntity[slot];
|
|
2080
|
+
inp.x[slot] = e.x;
|
|
2081
|
+
inp.y[slot] = e.y;
|
|
2082
|
+
inp.sx[slot] = e.scaleX;
|
|
2083
|
+
inp.sy[slot] = e.scaleY;
|
|
2084
|
+
const trig = e._getTrig();
|
|
2085
|
+
inp.cos[slot] = trig.cos;
|
|
2086
|
+
inp.sin[slot] = trig.sin;
|
|
2087
|
+
inp.opacity[slot] = e.opacity;
|
|
2088
|
+
}
|
|
2089
|
+
backend.runKernel("simd");
|
|
2090
|
+
return this._wasmWorld;
|
|
2091
|
+
}
|
|
733
2092
|
/**
|
|
734
2093
|
* Authoritative paint order for semantic nodes discovered during the main
|
|
735
2094
|
* render. A node may not have a DOM projection until the following a11y
|
|
736
2095
|
* sync, so retaining the order prevents a newly opened overlay from spending
|
|
737
2096
|
* its first frame below previously projected controls.
|
|
738
2097
|
*/
|
|
739
|
-
|
|
2098
|
+
__init99() {this.a11yRenderOrders = /* @__PURE__ */ new Map()}
|
|
740
2099
|
// Optional WebGL point-cloud layer (see SceneOptions.pointBackend).
|
|
741
|
-
|
|
742
|
-
|
|
2100
|
+
__init100() {this.pointRenderer = null}
|
|
2101
|
+
__init101() {this.glCanvas = null}
|
|
2102
|
+
__init102() {this.glContextLostHandler = null}
|
|
2103
|
+
__init103() {this.glContextRestoredHandler = null}
|
|
743
2104
|
|
|
744
2105
|
|
|
745
2106
|
|
|
746
|
-
|
|
2107
|
+
__init104() {this.disableWindowResize = false}
|
|
747
2108
|
/** See {@link SceneOptions.maxDPR}. `undefined` = uncapped (real DPR). */
|
|
748
2109
|
|
|
749
2110
|
// WebGPU properties
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
2111
|
+
__init105() {this.destroyed = false}
|
|
2112
|
+
__init106() {this.device = null}
|
|
2113
|
+
__init107() {this.deviceLost = false}
|
|
2114
|
+
__init108() {this.particleBackend = "auto"}
|
|
2115
|
+
__init109() {this._webgpuDisabled = false}
|
|
755
2116
|
get webgpuDisabled() {
|
|
756
2117
|
return this._webgpuDisabled || this.particleBackend === "cpu";
|
|
757
2118
|
}
|
|
758
2119
|
set webgpuDisabled(value) {
|
|
759
2120
|
this._webgpuDisabled = value;
|
|
760
2121
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
2122
|
+
__init110() {this.recoveryTimerId = null}
|
|
2123
|
+
__init111() {this.manager = null}
|
|
2124
|
+
__init112() {this.initializingWebGPU = false}
|
|
2125
|
+
__init113() {this.gpuCanvas = null}
|
|
2126
|
+
__init114() {this.gpuContext = null}
|
|
766
2127
|
/** True while the GPU canvas holds a presented particle frame (needs clearing when they leave). */
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
2128
|
+
__init115() {this.gpuHasContent = false}
|
|
2129
|
+
__init116() {this.mouseX = -9999}
|
|
2130
|
+
__init117() {this.mouseY = -9999}
|
|
2131
|
+
__init118() {this.pointerMoveListener = null}
|
|
2132
|
+
__init119() {this.pointerLeaveListener = null}
|
|
2133
|
+
/** Element the pointer listeners are bound to (parent container if present,
|
|
2134
|
+
* else the canvas). Stored so `destroy()` detaches from the same element. */
|
|
2135
|
+
__init120() {this.pointerEventTarget = null}
|
|
2136
|
+
__init121() {this.hasWarnedZeroSize = false}
|
|
2137
|
+
__init122() {this.fontLoadHandler = null}
|
|
774
2138
|
// ── Dev-mode warning infrastructure ──────────────────────────────
|
|
775
2139
|
//
|
|
776
2140
|
// Enable with `Scene.devMode = true` or by setting `globalThis.__DEV__`.
|
|
@@ -779,16 +2143,16 @@ var Scene = (_class2 = class _Scene {
|
|
|
779
2143
|
// Checks run once every ~120 frames (~2s at 60fps) to keep overhead
|
|
780
2144
|
// negligible even when dev mode is on.
|
|
781
2145
|
/** Toggle development-mode runtime warnings globally. */
|
|
782
|
-
static
|
|
2146
|
+
static __initStatic4() {this.devMode = false}
|
|
783
2147
|
static _devModeDetected() {
|
|
784
2148
|
if (_Scene.devMode) return true;
|
|
785
2149
|
const gp = typeof globalThis !== "undefined" ? globalThis : void 0;
|
|
786
|
-
if (_optionalChain([gp, 'optionalAccess',
|
|
787
|
-
if (_optionalChain([gp, 'optionalAccess',
|
|
2150
|
+
if (_optionalChain([gp, 'optionalAccess', _27 => _27.__DEV__])) return true;
|
|
2151
|
+
if (_optionalChain([gp, 'optionalAccess', _28 => _28.process, 'optionalAccess', _29 => _29.env, 'optionalAccess', _30 => _30.NODE_ENV]) === "development") return true;
|
|
788
2152
|
return false;
|
|
789
2153
|
}
|
|
790
2154
|
|
|
791
|
-
|
|
2155
|
+
__init123() {this._devFrameCount = 0}
|
|
792
2156
|
_devWarn(message) {
|
|
793
2157
|
if (!this._devActive) return;
|
|
794
2158
|
console.warn(`[vectojs/dev] ${message}`);
|
|
@@ -815,9 +2179,9 @@ var Scene = (_class2 = class _Scene {
|
|
|
815
2179
|
let checked = 0;
|
|
816
2180
|
const walkProjections = (node) => {
|
|
817
2181
|
if (checked > 10) return;
|
|
818
|
-
const proj = _optionalChain([node, 'access',
|
|
819
|
-
if (_optionalChain([proj, 'optionalAccess',
|
|
820
|
-
const el = _optionalChain([this, 'access',
|
|
2182
|
+
const proj = _optionalChain([node, 'access', _31 => _31.getContentProjection, 'optionalCall', _32 => _32()]);
|
|
2183
|
+
if (_optionalChain([proj, 'optionalAccess', _33 => _33.text]) && proj.selectable !== false) {
|
|
2184
|
+
const el = _optionalChain([this, 'access', _34 => _34.contentElements, 'optionalAccess', _35 => _35.get, 'call', _36 => _36(node.id)]);
|
|
821
2185
|
if (el) {
|
|
822
2186
|
const projectedText = el.textContent || "";
|
|
823
2187
|
if (projectedText !== "" && projectedText !== proj.text) {
|
|
@@ -832,14 +2196,14 @@ var Scene = (_class2 = class _Scene {
|
|
|
832
2196
|
};
|
|
833
2197
|
walkProjections(this.root);
|
|
834
2198
|
}
|
|
835
|
-
constructor(canvas, options = {}) {;
|
|
2199
|
+
constructor(canvas, options = {}) {;_class6.prototype.__init20.call(this);_class6.prototype.__init21.call(this);_class6.prototype.__init22.call(this);_class6.prototype.__init23.call(this);_class6.prototype.__init24.call(this);_class6.prototype.__init25.call(this);_class6.prototype.__init26.call(this);_class6.prototype.__init27.call(this);_class6.prototype.__init28.call(this);_class6.prototype.__init29.call(this);_class6.prototype.__init30.call(this);_class6.prototype.__init31.call(this);_class6.prototype.__init32.call(this);_class6.prototype.__init33.call(this);_class6.prototype.__init34.call(this);_class6.prototype.__init35.call(this);_class6.prototype.__init36.call(this);_class6.prototype.__init37.call(this);_class6.prototype.__init38.call(this);_class6.prototype.__init39.call(this);_class6.prototype.__init40.call(this);_class6.prototype.__init41.call(this);_class6.prototype.__init42.call(this);_class6.prototype.__init43.call(this);_class6.prototype.__init44.call(this);_class6.prototype.__init45.call(this);_class6.prototype.__init46.call(this);_class6.prototype.__init47.call(this);_class6.prototype.__init48.call(this);_class6.prototype.__init49.call(this);_class6.prototype.__init50.call(this);_class6.prototype.__init51.call(this);_class6.prototype.__init52.call(this);_class6.prototype.__init53.call(this);_class6.prototype.__init54.call(this);_class6.prototype.__init55.call(this);_class6.prototype.__init56.call(this);_class6.prototype.__init57.call(this);_class6.prototype.__init58.call(this);_class6.prototype.__init59.call(this);_class6.prototype.__init60.call(this);_class6.prototype.__init61.call(this);_class6.prototype.__init62.call(this);_class6.prototype.__init63.call(this);_class6.prototype.__init64.call(this);_class6.prototype.__init65.call(this);_class6.prototype.__init66.call(this);_class6.prototype.__init67.call(this);_class6.prototype.__init68.call(this);_class6.prototype.__init69.call(this);_class6.prototype.__init70.call(this);_class6.prototype.__init71.call(this);_class6.prototype.__init72.call(this);_class6.prototype.__init73.call(this);_class6.prototype.__init74.call(this);_class6.prototype.__init75.call(this);_class6.prototype.__init76.call(this);_class6.prototype.__init77.call(this);_class6.prototype.__init78.call(this);_class6.prototype.__init79.call(this);_class6.prototype.__init80.call(this);_class6.prototype.__init81.call(this);_class6.prototype.__init82.call(this);_class6.prototype.__init83.call(this);_class6.prototype.__init84.call(this);_class6.prototype.__init85.call(this);_class6.prototype.__init86.call(this);_class6.prototype.__init87.call(this);_class6.prototype.__init88.call(this);_class6.prototype.__init89.call(this);_class6.prototype.__init90.call(this);_class6.prototype.__init91.call(this);_class6.prototype.__init92.call(this);_class6.prototype.__init93.call(this);_class6.prototype.__init94.call(this);_class6.prototype.__init95.call(this);_class6.prototype.__init96.call(this);_class6.prototype.__init97.call(this);_class6.prototype.__init98.call(this);_class6.prototype.__init99.call(this);_class6.prototype.__init100.call(this);_class6.prototype.__init101.call(this);_class6.prototype.__init102.call(this);_class6.prototype.__init103.call(this);_class6.prototype.__init104.call(this);_class6.prototype.__init105.call(this);_class6.prototype.__init106.call(this);_class6.prototype.__init107.call(this);_class6.prototype.__init108.call(this);_class6.prototype.__init109.call(this);_class6.prototype.__init110.call(this);_class6.prototype.__init111.call(this);_class6.prototype.__init112.call(this);_class6.prototype.__init113.call(this);_class6.prototype.__init114.call(this);_class6.prototype.__init115.call(this);_class6.prototype.__init116.call(this);_class6.prototype.__init117.call(this);_class6.prototype.__init118.call(this);_class6.prototype.__init119.call(this);_class6.prototype.__init120.call(this);_class6.prototype.__init121.call(this);_class6.prototype.__init122.call(this);_class6.prototype.__init123.call(this);
|
|
836
2200
|
this.canvas = canvas;
|
|
837
2201
|
this.debugA11y = _nullishCoalesce(options.debugA11y, () => ( false));
|
|
838
2202
|
this.disableWindowResize = _nullishCoalesce(options.disableWindowResize, () => ( false));
|
|
839
2203
|
this.maxDPR = options.maxDPR;
|
|
840
2204
|
if (this.disableWindowResize) {
|
|
841
|
-
const styleWidth = parseInlinePx(_optionalChain([canvas, 'access',
|
|
842
|
-
const styleHeight = parseInlinePx(_optionalChain([canvas, 'access',
|
|
2205
|
+
const styleWidth = parseInlinePx(_optionalChain([canvas, 'access', _37 => _37.style, 'optionalAccess', _38 => _38.width]));
|
|
2206
|
+
const styleHeight = parseInlinePx(_optionalChain([canvas, 'access', _39 => _39.style, 'optionalAccess', _40 => _40.height]));
|
|
843
2207
|
this.width = _nullishCoalesce(styleWidth, () => ( (canvas.width || canvas.clientWidth || 0)));
|
|
844
2208
|
this.height = _nullishCoalesce(styleHeight, () => ( (canvas.height || canvas.clientHeight || 0)));
|
|
845
2209
|
} else {
|
|
@@ -847,7 +2211,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
847
2211
|
this.height = typeof window !== "undefined" ? window.innerHeight : canvas.clientHeight || canvas.height || 600;
|
|
848
2212
|
}
|
|
849
2213
|
const globalProcess = typeof globalThis !== "undefined" ? globalThis.process : void 0;
|
|
850
|
-
const isTest = globalProcess && (_optionalChain([globalProcess, 'access',
|
|
2214
|
+
const isTest = globalProcess && (_optionalChain([globalProcess, 'access', _41 => _41.env, 'optionalAccess', _42 => _42.NODE_ENV]) === "test" || _optionalChain([globalProcess, 'access', _43 => _43.env, 'optionalAccess', _44 => _44.VITEST]) === "true");
|
|
851
2215
|
this.maxFPS = _nullishCoalesce(options.maxFPS, () => ( (isTest ? 0 : 60)));
|
|
852
2216
|
this.respectReducedMotion = _nullishCoalesce(options.respectReducedMotion, () => ( true));
|
|
853
2217
|
this.autoThrottle = _nullishCoalesce(options.autoThrottle, () => ( true));
|
|
@@ -855,9 +2219,15 @@ var Scene = (_class2 = class _Scene {
|
|
|
855
2219
|
this.a11ySyncInterval = _nullishCoalesce(options.a11ySyncInterval, () => ( 0));
|
|
856
2220
|
this.contentProjectionEnabled = _nullishCoalesce(options.contentProjection, () => ( true));
|
|
857
2221
|
this.contentProjectionMargin = options.contentProjectionMargin;
|
|
2222
|
+
this.readingDirection = _nullishCoalesce(options.readingDirection, () => ( "ltr"));
|
|
858
2223
|
this._devActive = _Scene._devModeDetected();
|
|
859
2224
|
this.reducedMotionQuery = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
|
|
860
|
-
|
|
2225
|
+
if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
|
|
2226
|
+
this.forcedColorsQuery = window.matchMedia("(forced-colors: active)");
|
|
2227
|
+
this.forcedColorsChangeHandler = () => this.markDirty();
|
|
2228
|
+
_optionalChain([this, 'access', _45 => _45.forcedColorsQuery, 'access', _46 => _46.addEventListener, 'optionalCall', _47 => _47("change", this.forcedColorsChangeHandler)]);
|
|
2229
|
+
}
|
|
2230
|
+
this.root = new class RootEntity extends _chunkDUYB4GX4js.Entity {
|
|
861
2231
|
isPointInside() {
|
|
862
2232
|
return false;
|
|
863
2233
|
}
|
|
@@ -866,7 +2236,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
866
2236
|
}
|
|
867
2237
|
}("root");
|
|
868
2238
|
this.root._scene = this;
|
|
869
|
-
this.overlayRoot = new class OverlayRoot extends
|
|
2239
|
+
this.overlayRoot = new class OverlayRoot extends _chunkDUYB4GX4js.Entity {
|
|
870
2240
|
isPointInside() {
|
|
871
2241
|
return false;
|
|
872
2242
|
}
|
|
@@ -877,12 +2247,16 @@ var Scene = (_class2 = class _Scene {
|
|
|
877
2247
|
if (options.renderer) {
|
|
878
2248
|
this.renderer = options.renderer;
|
|
879
2249
|
} else {
|
|
880
|
-
this.renderer = new (0,
|
|
2250
|
+
this.renderer = new (0, _chunkIYPLG4Z4js.CanvasRenderer)(
|
|
881
2251
|
canvas,
|
|
882
2252
|
this.disableWindowResize ? { width: this.width, height: this.height } : void 0,
|
|
883
2253
|
this.maxDPR
|
|
884
2254
|
);
|
|
885
2255
|
}
|
|
2256
|
+
_optionalChain([this, 'access', _48 => _48.renderer, 'access', _49 => _49.onContextRestored, 'optionalCall', _50 => _50(() => {
|
|
2257
|
+
this.markDirty();
|
|
2258
|
+
if (_optionalChain([this, 'access', _51 => _51.renderer, 'access', _52 => _52.isContextLost, 'optionalCall', _53 => _53()]) !== true) this.render(this.renderer);
|
|
2259
|
+
})]);
|
|
886
2260
|
if (typeof document !== "undefined") {
|
|
887
2261
|
this.a11yRoot = document.createElement("div");
|
|
888
2262
|
this.a11yRoot.style.position = "absolute";
|
|
@@ -894,6 +2268,15 @@ var Scene = (_class2 = class _Scene {
|
|
|
894
2268
|
this.a11yRoot.style.overflow = "hidden";
|
|
895
2269
|
this.a11yRoot.style.zIndex = "10";
|
|
896
2270
|
this.a11yRoot.style.userSelect = "text";
|
|
2271
|
+
this.focusSentinel = document.createElement("div");
|
|
2272
|
+
this.focusSentinel.setAttribute("data-vecto-focus-sentinel", "");
|
|
2273
|
+
this.focusSentinel.tabIndex = -1;
|
|
2274
|
+
this.focusSentinel.style.position = "absolute";
|
|
2275
|
+
this.focusSentinel.style.width = "0";
|
|
2276
|
+
this.focusSentinel.style.height = "0";
|
|
2277
|
+
this.focusSentinel.style.outline = "none";
|
|
2278
|
+
this.focusSentinel.style.overflow = "hidden";
|
|
2279
|
+
this.a11yRoot.appendChild(this.focusSentinel);
|
|
897
2280
|
this.a11yRoot.addEventListener("mousedown", (e) => {
|
|
898
2281
|
if (e.button !== 0) return;
|
|
899
2282
|
const target = e.target;
|
|
@@ -1010,6 +2393,8 @@ var Scene = (_class2 = class _Scene {
|
|
|
1010
2393
|
pr.resize(this.width, this.height);
|
|
1011
2394
|
this.glCanvas = gl;
|
|
1012
2395
|
this.pointRenderer = pr;
|
|
2396
|
+
this._overlayGeometry = null;
|
|
2397
|
+
this.setupGLContextRecovery(gl);
|
|
1013
2398
|
} else {
|
|
1014
2399
|
gl.remove();
|
|
1015
2400
|
}
|
|
@@ -1028,6 +2413,61 @@ var Scene = (_class2 = class _Scene {
|
|
|
1028
2413
|
}
|
|
1029
2414
|
this.setupEvents();
|
|
1030
2415
|
}
|
|
2416
|
+
/**
|
|
2417
|
+
* Arm a `(resolution: Ndppx)` media query for the current devicePixelRatio and
|
|
2418
|
+
* re-apply the canvas scale when it changes. Such a query only fires when the
|
|
2419
|
+
* DPR leaves its exact value, so on each change the old query is detached and
|
|
2420
|
+
* a fresh one is armed for the new DPR. Re-runs `resize(width, height)` (which
|
|
2421
|
+
* re-scales the backing store via the renderer) so text/vectors stay crisp
|
|
2422
|
+
* after a monitor move or zoom. No-op without `matchMedia`.
|
|
2423
|
+
*/
|
|
2424
|
+
watchDevicePixelRatio() {
|
|
2425
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
2426
|
+
if (this.dprMediaQuery && this.dprChangeHandler) {
|
|
2427
|
+
_optionalChain([this, 'access', _54 => _54.dprMediaQuery, 'access', _55 => _55.removeEventListener, 'optionalCall', _56 => _56("change", this.dprChangeHandler)]);
|
|
2428
|
+
}
|
|
2429
|
+
const dpr = window.devicePixelRatio || 1;
|
|
2430
|
+
const query = window.matchMedia(`(resolution: ${dpr}dppx)`);
|
|
2431
|
+
const handler = () => {
|
|
2432
|
+
this.resize(this.width, this.height);
|
|
2433
|
+
this.watchDevicePixelRatio();
|
|
2434
|
+
};
|
|
2435
|
+
_optionalChain([query, 'access', _57 => _57.addEventListener, 'optionalCall', _58 => _58("change", handler)]);
|
|
2436
|
+
this.dprMediaQuery = query;
|
|
2437
|
+
this.dprChangeHandler = handler;
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Recover the WebGL point layer from a GPU context loss (driver TDR reset,
|
|
2441
|
+
* tab backgrounded on mobile, GPU switch). Two things are required:
|
|
2442
|
+
*
|
|
2443
|
+
* 1. The `webglcontextlost` handler MUST call `preventDefault()`, or the
|
|
2444
|
+
* browser never fires `webglcontextrestored` and the layer is blank
|
|
2445
|
+
* forever. While lost, the old renderer's GL calls are silently ignored,
|
|
2446
|
+
* so we drop it and the render loop simply skips the point layer.
|
|
2447
|
+
* 2. On `webglcontextrestored`, all GL objects (programs, buffers, textures)
|
|
2448
|
+
* are gone, so we rebuild the renderer from scratch via `Scene.webglCreator`
|
|
2449
|
+
* on the same canvas, restore DPR/size, and repaint.
|
|
2450
|
+
*/
|
|
2451
|
+
setupGLContextRecovery(gl) {
|
|
2452
|
+
if (typeof gl.addEventListener !== "function") return;
|
|
2453
|
+
this.glContextLostHandler = (e) => {
|
|
2454
|
+
e.preventDefault();
|
|
2455
|
+
_optionalChain([this, 'access', _59 => _59.pointRenderer, 'optionalAccess', _60 => _60.destroy, 'call', _61 => _61()]);
|
|
2456
|
+
this.pointRenderer = null;
|
|
2457
|
+
};
|
|
2458
|
+
this.glContextRestoredHandler = () => {
|
|
2459
|
+
if (this.destroyed || !this.glCanvas) return;
|
|
2460
|
+
const pr = _Scene.webglCreator ? _Scene.webglCreator(this.glCanvas) : null;
|
|
2461
|
+
if (pr) {
|
|
2462
|
+
pr.maxDPR = this.maxDPR;
|
|
2463
|
+
pr.resize(this.width, this.height);
|
|
2464
|
+
this.pointRenderer = pr;
|
|
2465
|
+
this.markDirty();
|
|
2466
|
+
}
|
|
2467
|
+
};
|
|
2468
|
+
gl.addEventListener("webglcontextlost", this.glContextLostHandler);
|
|
2469
|
+
gl.addEventListener("webglcontextrestored", this.glContextRestoredHandler);
|
|
2470
|
+
}
|
|
1031
2471
|
endContentSelectionDrag() {
|
|
1032
2472
|
this.blankRegionSelectionDrag = false;
|
|
1033
2473
|
this.contentSelectionAnchor = null;
|
|
@@ -1035,10 +2475,57 @@ var Scene = (_class2 = class _Scene {
|
|
|
1035
2475
|
}
|
|
1036
2476
|
releaseContentSelectionForRebuild(el) {
|
|
1037
2477
|
const selection = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
|
|
1038
|
-
const ownsSelection = this.contentSelectionAnchor && el.contains(this.contentSelectionAnchor.node) || (_optionalChain([selection, 'optionalAccess',
|
|
2478
|
+
const ownsSelection = this.contentSelectionAnchor && el.contains(this.contentSelectionAnchor.node) || (_optionalChain([selection, 'optionalAccess', _62 => _62.anchorNode]) ? el.contains(selection.anchorNode) : false) || (_optionalChain([selection, 'optionalAccess', _63 => _63.focusNode]) ? el.contains(selection.focusNode) : false);
|
|
1039
2479
|
if (!ownsSelection) return;
|
|
1040
2480
|
this.endContentSelectionDrag();
|
|
1041
|
-
_optionalChain([selection, 'optionalAccess',
|
|
2481
|
+
_optionalChain([selection, 'optionalAccess', _64 => _64.removeAllRanges, 'call', _65 => _65()]);
|
|
2482
|
+
}
|
|
2483
|
+
/**
|
|
2484
|
+
* Rebuild a content-projection element's DOM (`rebuild`) while preserving a
|
|
2485
|
+
* text selection the user made inside it. A streaming message replaces its
|
|
2486
|
+
* projection children on every appended chunk; without this, a selection in
|
|
2487
|
+
* the UNCHANGED prefix is wiped on each frame ("can't select text in a
|
|
2488
|
+
* message still receiving tokens"). We snapshot the selection's anchor/focus
|
|
2489
|
+
* as linear character offsets within `el` before the rebuild and re-resolve
|
|
2490
|
+
* them against the new DOM after, clamped to the new text length.
|
|
2491
|
+
*
|
|
2492
|
+
* Only fires when `el` owns the current selection and there is no active drag
|
|
2493
|
+
* (mid-drag the browser is authoritative). The virtualization case — where
|
|
2494
|
+
* `el` itself is removed from the DOM — is out of scope here (the node is
|
|
2495
|
+
* genuinely freed; the browser clears the selection and there is nothing to
|
|
2496
|
+
* restore against).
|
|
2497
|
+
*/
|
|
2498
|
+
preserveContentSelectionAcrossRebuild(el, rebuild) {
|
|
2499
|
+
const selection = typeof window !== "undefined" && typeof window.getSelection === "function" ? window.getSelection() : null;
|
|
2500
|
+
const owns = !!selection && !this.blankRegionSelectionDrag && ((selection.anchorNode ? el.contains(selection.anchorNode) : false) || (selection.focusNode ? el.contains(selection.focusNode) : false));
|
|
2501
|
+
if (!owns || !selection.anchorNode || !selection.focusNode) {
|
|
2502
|
+
this.releaseContentSelectionForRebuild(el);
|
|
2503
|
+
rebuild();
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
const anchorNode = selection.anchorNode;
|
|
2507
|
+
const focusNode = selection.focusNode;
|
|
2508
|
+
const anchorOffset = anchorNode instanceof Text ? projectionAbsoluteOffset(el, {
|
|
2509
|
+
node: anchorNode,
|
|
2510
|
+
offset: selection.anchorOffset
|
|
2511
|
+
}) : null;
|
|
2512
|
+
const focusOffset = focusNode instanceof Text ? projectionAbsoluteOffset(el, {
|
|
2513
|
+
node: focusNode,
|
|
2514
|
+
offset: selection.focusOffset
|
|
2515
|
+
}) : null;
|
|
2516
|
+
this.endContentSelectionDrag();
|
|
2517
|
+
selection.removeAllRanges();
|
|
2518
|
+
rebuild();
|
|
2519
|
+
if (anchorOffset === null || focusOffset === null) return;
|
|
2520
|
+
const textLen = (_nullishCoalesce(el.textContent, () => ( ""))).length;
|
|
2521
|
+
if (anchorOffset > textLen || focusOffset > textLen) return;
|
|
2522
|
+
const anchor = projectionCaretAt(el, anchorOffset, "forward");
|
|
2523
|
+
const focus = projectionCaretAt(el, focusOffset, "backward");
|
|
2524
|
+
if (!anchor || !focus) return;
|
|
2525
|
+
try {
|
|
2526
|
+
selection.setBaseAndExtent(anchor.node, anchor.offset, focus.node, focus.offset);
|
|
2527
|
+
} catch (e17) {
|
|
2528
|
+
}
|
|
1042
2529
|
}
|
|
1043
2530
|
/**
|
|
1044
2531
|
* Expose the underlying {@link IRenderer} for advanced direct-draw operations.
|
|
@@ -1050,7 +2537,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1050
2537
|
}
|
|
1051
2538
|
/** Convert browser viewport coordinates into this Scene's logical coordinates. */
|
|
1052
2539
|
clientToScene(clientX, clientY) {
|
|
1053
|
-
const rect = _optionalChain([this, 'access',
|
|
2540
|
+
const rect = _optionalChain([this, 'access', _66 => _66.canvas, 'access', _67 => _67.getBoundingClientRect, 'optionalCall', _68 => _68()]);
|
|
1054
2541
|
if (!rect) return { x: clientX, y: clientY };
|
|
1055
2542
|
const cssWidth = rect.width || this.canvas.clientWidth || this.width;
|
|
1056
2543
|
const cssHeight = rect.height || this.canvas.clientHeight || this.height;
|
|
@@ -1068,6 +2555,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1068
2555
|
*/
|
|
1069
2556
|
add(entity) {
|
|
1070
2557
|
this.root.add(entity);
|
|
2558
|
+
this.registerActiveDriverSubtree(entity);
|
|
1071
2559
|
return this;
|
|
1072
2560
|
}
|
|
1073
2561
|
clearContentGridState(entityId, el) {
|
|
@@ -1076,7 +2564,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1076
2564
|
cancelAnimationFrame(calibrationFrame);
|
|
1077
2565
|
}
|
|
1078
2566
|
this.contentGridCalibrationFrames.delete(entityId);
|
|
1079
|
-
_optionalChain([this, 'access',
|
|
2567
|
+
_optionalChain([this, 'access', _69 => _69.contentGridCalibrationProbes, 'access', _70 => _70.get, 'call', _71 => _71(entityId), 'optionalAccess', _72 => _72.remove, 'call', _73 => _73()]);
|
|
1080
2568
|
this.contentGridCalibrationProbes.delete(entityId);
|
|
1081
2569
|
delete el.dataset.vectoGridCalibrationPending;
|
|
1082
2570
|
delete el.dataset.vectoGridCalibration;
|
|
@@ -1090,6 +2578,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1090
2578
|
}
|
|
1091
2579
|
removeA11yRecursively(node) {
|
|
1092
2580
|
if (node.isDOMPortal) {
|
|
2581
|
+
node.releaseDOMBindings();
|
|
1093
2582
|
node.domElement.remove();
|
|
1094
2583
|
this.portalEntities.delete(node.id);
|
|
1095
2584
|
this.activePortalsThisFrame.delete(node.id);
|
|
@@ -1111,6 +2600,11 @@ var Scene = (_class2 = class _Scene {
|
|
|
1111
2600
|
this.caretBlinkTimer = null;
|
|
1112
2601
|
}
|
|
1113
2602
|
}
|
|
2603
|
+
if (this.hoveredA11yElements.has(el)) {
|
|
2604
|
+
this.hoveredA11yElements.delete(el);
|
|
2605
|
+
node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("pointerleave", node, void 0, false));
|
|
2606
|
+
}
|
|
2607
|
+
this.preserveFocusOnRemoval(el);
|
|
1114
2608
|
el.remove();
|
|
1115
2609
|
this.a11yElements.delete(node.id);
|
|
1116
2610
|
this.a11yNeedsReorder = true;
|
|
@@ -1119,6 +2613,18 @@ var Scene = (_class2 = class _Scene {
|
|
|
1119
2613
|
this.removeA11yRecursively(child);
|
|
1120
2614
|
}
|
|
1121
2615
|
}
|
|
2616
|
+
/**
|
|
2617
|
+
* If `el` is about to be removed from the DOM while it holds browser focus,
|
|
2618
|
+
* move focus to the a11y focus sentinel first. Removing the active element
|
|
2619
|
+
* otherwise drops focus to `<body>`, which pulls a screen reader out of the
|
|
2620
|
+
* scene's a11y region and back to the top of the page — the classic
|
|
2621
|
+
* "lost my place on scroll/stream" bug for virtualized/recycled controls.
|
|
2622
|
+
*/
|
|
2623
|
+
preserveFocusOnRemoval(el) {
|
|
2624
|
+
if (!this.focusSentinel || typeof document === "undefined") return;
|
|
2625
|
+
if (document.activeElement !== el) return;
|
|
2626
|
+
this.focusSentinel.focus({ preventScroll: true });
|
|
2627
|
+
}
|
|
1122
2628
|
/**
|
|
1123
2629
|
* Remove a top-level entity from the scene graph and clean up its
|
|
1124
2630
|
* accessibility shadow elements recursively.
|
|
@@ -1129,6 +2635,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1129
2635
|
remove(entity) {
|
|
1130
2636
|
this.root.remove(entity);
|
|
1131
2637
|
this.removeA11yRecursively(entity);
|
|
2638
|
+
this.unregisterActiveDriverSubtree(entity);
|
|
1132
2639
|
return this;
|
|
1133
2640
|
}
|
|
1134
2641
|
/**
|
|
@@ -1148,6 +2655,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1148
2655
|
*/
|
|
1149
2656
|
showOverlay(overlay) {
|
|
1150
2657
|
this.overlayRoot.add(overlay);
|
|
2658
|
+
this.registerActiveDriverSubtree(overlay);
|
|
1151
2659
|
this.markDirty();
|
|
1152
2660
|
}
|
|
1153
2661
|
/**
|
|
@@ -1156,10 +2664,10 @@ var Scene = (_class2 = class _Scene {
|
|
|
1156
2664
|
hideOverlay(overlay) {
|
|
1157
2665
|
this.overlayRoot.remove(overlay);
|
|
1158
2666
|
this.removeA11yRecursively(overlay);
|
|
2667
|
+
this.unregisterActiveDriverSubtree(overlay);
|
|
1159
2668
|
this.markDirty();
|
|
1160
2669
|
}
|
|
1161
2670
|
destroyEntitySubtree(entity) {
|
|
1162
|
-
while (entity.children.length > 0) this.destroyEntitySubtree(entity.children.at(-1));
|
|
1163
2671
|
entity.destroy();
|
|
1164
2672
|
}
|
|
1165
2673
|
/**
|
|
@@ -1179,21 +2687,37 @@ var Scene = (_class2 = class _Scene {
|
|
|
1179
2687
|
if (typeof window !== "undefined" && !this.disableWindowResize) {
|
|
1180
2688
|
window.removeEventListener("resize", this.resizeHandler);
|
|
1181
2689
|
}
|
|
2690
|
+
if (this.canvasResizeObserver) {
|
|
2691
|
+
this.canvasResizeObserver.disconnect();
|
|
2692
|
+
this.canvasResizeObserver = null;
|
|
2693
|
+
}
|
|
2694
|
+
if (this.dprMediaQuery && this.dprChangeHandler) {
|
|
2695
|
+
_optionalChain([this, 'access', _74 => _74.dprMediaQuery, 'access', _75 => _75.removeEventListener, 'optionalCall', _76 => _76("change", this.dprChangeHandler)]);
|
|
2696
|
+
this.dprMediaQuery = null;
|
|
2697
|
+
this.dprChangeHandler = null;
|
|
2698
|
+
}
|
|
2699
|
+
if (this.forcedColorsQuery && this.forcedColorsChangeHandler) {
|
|
2700
|
+
_optionalChain([this, 'access', _77 => _77.forcedColorsQuery, 'access', _78 => _78.removeEventListener, 'optionalCall', _79 => _79("change", this.forcedColorsChangeHandler)]);
|
|
2701
|
+
this.forcedColorsQuery = null;
|
|
2702
|
+
this.forcedColorsChangeHandler = null;
|
|
2703
|
+
}
|
|
1182
2704
|
if (typeof window !== "undefined" && this.contentSelectionEndListener) {
|
|
1183
2705
|
window.removeEventListener("mouseup", this.contentSelectionEndListener);
|
|
1184
2706
|
window.removeEventListener("blur", this.contentSelectionEndListener);
|
|
1185
2707
|
this.contentSelectionEndListener = null;
|
|
1186
2708
|
}
|
|
1187
|
-
if (typeof window !== "undefined" && this.
|
|
2709
|
+
if (typeof window !== "undefined" && this.pointerEventTarget && typeof this.pointerEventTarget.removeEventListener === "function") {
|
|
1188
2710
|
if (this.pointerMoveListener) {
|
|
1189
|
-
this.
|
|
2711
|
+
this.pointerEventTarget.removeEventListener("pointermove", this.pointerMoveListener);
|
|
1190
2712
|
}
|
|
1191
2713
|
if (this.pointerLeaveListener) {
|
|
1192
|
-
this.
|
|
2714
|
+
this.pointerEventTarget.removeEventListener("pointerleave", this.pointerLeaveListener);
|
|
1193
2715
|
}
|
|
2716
|
+
this.pointerEventTarget = null;
|
|
1194
2717
|
}
|
|
1195
|
-
_optionalChain([this, 'access',
|
|
1196
|
-
|
|
2718
|
+
_optionalChain([this, 'access', _80 => _80.a11yRoot, 'optionalAccess', _81 => _81.remove, 'call', _82 => _82()]);
|
|
2719
|
+
this.focusSentinel = null;
|
|
2720
|
+
_optionalChain([this, 'access', _83 => _83.portalRoot, 'optionalAccess', _84 => _84.remove, 'call', _85 => _85()]);
|
|
1197
2721
|
this.a11yElements.clear();
|
|
1198
2722
|
for (const el of this.contentElements.values()) el.remove();
|
|
1199
2723
|
this.contentElements.clear();
|
|
@@ -1206,10 +2730,20 @@ var Scene = (_class2 = class _Scene {
|
|
|
1206
2730
|
for (const probe of this.contentGridCalibrationProbes.values()) probe.remove();
|
|
1207
2731
|
this.contentGridCalibrationProbes.clear();
|
|
1208
2732
|
this.endContentSelectionDrag();
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
2733
|
+
if (this.glCanvas) {
|
|
2734
|
+
if (this.glContextLostHandler) {
|
|
2735
|
+
this.glCanvas.removeEventListener("webglcontextlost", this.glContextLostHandler);
|
|
2736
|
+
}
|
|
2737
|
+
if (this.glContextRestoredHandler) {
|
|
2738
|
+
this.glCanvas.removeEventListener("webglcontextrestored", this.glContextRestoredHandler);
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
this.glContextLostHandler = null;
|
|
2742
|
+
this.glContextRestoredHandler = null;
|
|
2743
|
+
_optionalChain([this, 'access', _86 => _86.pointRenderer, 'optionalAccess', _87 => _87.destroy, 'call', _88 => _88()]);
|
|
2744
|
+
_optionalChain([this, 'access', _89 => _89.renderer, 'access', _90 => _90.dispose, 'optionalCall', _91 => _91()]);
|
|
2745
|
+
_optionalChain([this, 'access', _92 => _92.glCanvas, 'optionalAccess', _93 => _93.remove, 'call', _94 => _94()]);
|
|
2746
|
+
_optionalChain([this, 'access', _95 => _95.gpuCanvas, 'optionalAccess', _96 => _96.remove, 'call', _97 => _97()]);
|
|
1213
2747
|
this.gpuCanvas = null;
|
|
1214
2748
|
this.gpuContext = null;
|
|
1215
2749
|
if (this.recoveryTimerId) {
|
|
@@ -1221,14 +2755,27 @@ var Scene = (_class2 = class _Scene {
|
|
|
1221
2755
|
this.manager = null;
|
|
1222
2756
|
}
|
|
1223
2757
|
if (this.device) {
|
|
1224
|
-
_optionalChain([this, 'access',
|
|
2758
|
+
_optionalChain([this, 'access', _98 => _98.device, 'access', _99 => _99.destroy, 'optionalCall', _100 => _100()]);
|
|
1225
2759
|
this.device = null;
|
|
1226
2760
|
}
|
|
1227
2761
|
}
|
|
1228
2762
|
setupEvents() {
|
|
1229
2763
|
if (typeof window !== "undefined" && !this.disableWindowResize) {
|
|
1230
2764
|
window.addEventListener("resize", this.resizeHandler);
|
|
2765
|
+
} else if (this.disableWindowResize && typeof ResizeObserver !== "undefined" && this.canvas && typeof this.canvas.getBoundingClientRect === "function") {
|
|
2766
|
+
this.canvasResizeObserver = new ResizeObserver((entries) => {
|
|
2767
|
+
const entry = entries[0];
|
|
2768
|
+
if (!entry) return;
|
|
2769
|
+
const box = entry.contentRect;
|
|
2770
|
+
const w = Math.round(box.width);
|
|
2771
|
+
const h = Math.round(box.height);
|
|
2772
|
+
if (w > 0 && h > 0 && (w !== this.width || h !== this.height)) {
|
|
2773
|
+
this.resize(w, h);
|
|
2774
|
+
}
|
|
2775
|
+
});
|
|
2776
|
+
this.canvasResizeObserver.observe(this.canvas);
|
|
1231
2777
|
}
|
|
2778
|
+
this.watchDevicePixelRatio();
|
|
1232
2779
|
if (typeof window !== "undefined" && this.canvas && typeof this.canvas.addEventListener === "function") {
|
|
1233
2780
|
this.pointerMoveListener = (e) => {
|
|
1234
2781
|
const point = this.clientToScene(e.clientX, e.clientY);
|
|
@@ -1239,8 +2786,9 @@ var Scene = (_class2 = class _Scene {
|
|
|
1239
2786
|
this.mouseX = -9999;
|
|
1240
2787
|
this.mouseY = -9999;
|
|
1241
2788
|
};
|
|
1242
|
-
this.canvas.
|
|
1243
|
-
this.
|
|
2789
|
+
this.pointerEventTarget = _nullishCoalesce(this.canvas.parentElement, () => ( this.canvas));
|
|
2790
|
+
this.pointerEventTarget.addEventListener("pointermove", this.pointerMoveListener);
|
|
2791
|
+
this.pointerEventTarget.addEventListener("pointerleave", this.pointerLeaveListener);
|
|
1244
2792
|
}
|
|
1245
2793
|
}
|
|
1246
2794
|
/**
|
|
@@ -1258,6 +2806,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1258
2806
|
}
|
|
1259
2807
|
this.isRunning = true;
|
|
1260
2808
|
this.lastTime = typeof performance !== "undefined" ? performance.now() : 0;
|
|
2809
|
+
this.watchCanvasVisibility();
|
|
1261
2810
|
this.scheduleFrame();
|
|
1262
2811
|
const isTextFocused = this.focusedA11yElement instanceof HTMLInputElement || this.focusedA11yElement instanceof HTMLTextAreaElement;
|
|
1263
2812
|
if (isTextFocused && this.renderMode === "onDemand" && !this.caretBlinkTimer) {
|
|
@@ -1272,6 +2821,29 @@ var Scene = (_class2 = class _Scene {
|
|
|
1272
2821
|
requestAnimationFrame((t) => this.loop(t));
|
|
1273
2822
|
}
|
|
1274
2823
|
}
|
|
2824
|
+
/**
|
|
2825
|
+
* Observe whether the canvas is on-screen so the rAF loop can pause when it
|
|
2826
|
+
* scrolls fully out of view (a dashboard tab, a chart below the fold) and
|
|
2827
|
+
* resume when it returns — instead of running the full update/render every
|
|
2828
|
+
* frame for a scene nobody can see. No-op (stays "on screen") where
|
|
2829
|
+
* `IntersectionObserver` is unavailable, so SSR/jsdom behavior is unchanged.
|
|
2830
|
+
*/
|
|
2831
|
+
watchCanvasVisibility() {
|
|
2832
|
+
if (this._canvasObserver || typeof IntersectionObserver === "undefined") return;
|
|
2833
|
+
if (!this.canvas || typeof this.canvas.getBoundingClientRect !== "function") return;
|
|
2834
|
+
this._canvasObserver = new IntersectionObserver((entries) => {
|
|
2835
|
+
const entry = entries[entries.length - 1];
|
|
2836
|
+
if (!entry) return;
|
|
2837
|
+
const nowOnScreen = entry.isIntersecting;
|
|
2838
|
+
const wasOffScreen = !this._canvasOnScreen;
|
|
2839
|
+
this._canvasOnScreen = nowOnScreen;
|
|
2840
|
+
if (nowOnScreen && wasOffScreen && this.isRunning) {
|
|
2841
|
+
this.lastTime = typeof performance !== "undefined" ? performance.now() : 0;
|
|
2842
|
+
this.scheduleFrame();
|
|
2843
|
+
}
|
|
2844
|
+
});
|
|
2845
|
+
this._canvasObserver.observe(this.canvas);
|
|
2846
|
+
}
|
|
1275
2847
|
/**
|
|
1276
2848
|
* Halt the render loop after the current frame completes.
|
|
1277
2849
|
*
|
|
@@ -1283,6 +2855,11 @@ var Scene = (_class2 = class _Scene {
|
|
|
1283
2855
|
clearInterval(this.caretBlinkTimer);
|
|
1284
2856
|
this.caretBlinkTimer = null;
|
|
1285
2857
|
}
|
|
2858
|
+
if (this._canvasObserver) {
|
|
2859
|
+
this._canvasObserver.disconnect();
|
|
2860
|
+
this._canvasObserver = null;
|
|
2861
|
+
}
|
|
2862
|
+
this._canvasOnScreen = true;
|
|
1286
2863
|
}
|
|
1287
2864
|
/**
|
|
1288
2865
|
* Manually advance the scene clock by `dt` milliseconds and render synchronously.
|
|
@@ -1373,6 +2950,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1373
2950
|
}
|
|
1374
2951
|
}
|
|
1375
2952
|
if (el.parentNode === this.a11yRoot) {
|
|
2953
|
+
this.preserveFocusOnRemoval(el);
|
|
1376
2954
|
this.a11yRoot.removeChild(el);
|
|
1377
2955
|
}
|
|
1378
2956
|
this.a11yElements.delete(node.id);
|
|
@@ -1400,18 +2978,20 @@ var Scene = (_class2 = class _Scene {
|
|
|
1400
2978
|
el.style.background = "transparent";
|
|
1401
2979
|
}
|
|
1402
2980
|
el.addEventListener("click", (e) => {
|
|
1403
|
-
node.dispatchEvent(new (0,
|
|
2981
|
+
node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("click", node, e));
|
|
1404
2982
|
});
|
|
1405
2983
|
el.addEventListener("dblclick", (e) => {
|
|
1406
|
-
node.dispatchEvent(new (0,
|
|
2984
|
+
node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("dblclick", node, e));
|
|
1407
2985
|
});
|
|
1408
2986
|
el.addEventListener("mouseenter", (e) => {
|
|
1409
2987
|
if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.2)";
|
|
1410
|
-
|
|
2988
|
+
this.hoveredA11yElements.add(el);
|
|
2989
|
+
node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("hover", node, e, false));
|
|
1411
2990
|
});
|
|
1412
2991
|
el.addEventListener("mouseleave", (e) => {
|
|
1413
2992
|
if (this.debugA11y) el.style.backgroundColor = "rgba(56, 189, 248, 0.05)";
|
|
1414
|
-
|
|
2993
|
+
this.hoveredA11yElements.delete(el);
|
|
2994
|
+
node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("pointerleave", node, e, false));
|
|
1415
2995
|
});
|
|
1416
2996
|
const capEl = el;
|
|
1417
2997
|
const releasePointer = (event) => {
|
|
@@ -1427,32 +3007,32 @@ var Scene = (_class2 = class _Scene {
|
|
|
1427
3007
|
};
|
|
1428
3008
|
el.addEventListener("pointerdown", (e) => {
|
|
1429
3009
|
if (typeof capEl.setPointerCapture === "function") capEl.setPointerCapture(e.pointerId);
|
|
1430
|
-
node.dispatchEvent(new (0,
|
|
3010
|
+
node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("pointerdown", node, e));
|
|
1431
3011
|
});
|
|
1432
3012
|
el.addEventListener("pointerup", (e) => {
|
|
1433
3013
|
releasePointer(e);
|
|
1434
|
-
node.dispatchEvent(new (0,
|
|
3014
|
+
node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("pointerup", node, e));
|
|
1435
3015
|
});
|
|
1436
3016
|
el.addEventListener("pointercancel", (e) => {
|
|
1437
3017
|
releasePointer(e);
|
|
1438
|
-
node.dispatchEvent(new (0,
|
|
3018
|
+
node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("pointercancel", node, e));
|
|
1439
3019
|
});
|
|
1440
3020
|
el.addEventListener(
|
|
1441
3021
|
"pointermove",
|
|
1442
|
-
(e) => node.dispatchEvent(new (0,
|
|
3022
|
+
(e) => node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("pointermove", node, e))
|
|
1443
3023
|
);
|
|
1444
3024
|
el.addEventListener(
|
|
1445
3025
|
"wheel",
|
|
1446
3026
|
(e) => {
|
|
1447
|
-
node.dispatchEvent(new (0,
|
|
3027
|
+
node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("wheel", node, e));
|
|
1448
3028
|
},
|
|
1449
3029
|
{ passive: false }
|
|
1450
3030
|
);
|
|
1451
3031
|
el.addEventListener("keydown", (e) => {
|
|
1452
|
-
node.dispatchEvent(new (0,
|
|
3032
|
+
node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("keydown", node, e));
|
|
1453
3033
|
});
|
|
1454
3034
|
el.addEventListener("keyup", (e) => {
|
|
1455
|
-
node.dispatchEvent(new (0,
|
|
3035
|
+
node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("keyup", node, e));
|
|
1456
3036
|
});
|
|
1457
3037
|
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
|
|
1458
3038
|
const input = el;
|
|
@@ -1474,12 +3054,18 @@ var Scene = (_class2 = class _Scene {
|
|
|
1474
3054
|
el.addEventListener("click", forward);
|
|
1475
3055
|
el.addEventListener("select", forward);
|
|
1476
3056
|
el.addEventListener("compositionstart", () => {
|
|
1477
|
-
composition = {
|
|
3057
|
+
composition = {
|
|
3058
|
+
start: _nullishCoalesce(input.selectionStart, () => ( input.value.length)),
|
|
3059
|
+
length: 0
|
|
3060
|
+
};
|
|
1478
3061
|
forward();
|
|
1479
3062
|
});
|
|
1480
3063
|
el.addEventListener("compositionupdate", (e) => {
|
|
1481
3064
|
const data = _nullishCoalesce(e.data, () => ( ""));
|
|
1482
|
-
composition = {
|
|
3065
|
+
composition = {
|
|
3066
|
+
start: _nullishCoalesce(_optionalChain([composition, 'optionalAccess', _101 => _101.start]), () => ( 0)),
|
|
3067
|
+
length: data.length
|
|
3068
|
+
};
|
|
1483
3069
|
forward();
|
|
1484
3070
|
});
|
|
1485
3071
|
el.addEventListener("compositionend", () => {
|
|
@@ -1512,7 +3098,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1512
3098
|
el.addEventListener("keydown", (e) => {
|
|
1513
3099
|
if (e.key === "Enter" || e.key === " ") {
|
|
1514
3100
|
e.preventDefault();
|
|
1515
|
-
node.dispatchEvent(new (0,
|
|
3101
|
+
node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("click", node, e));
|
|
1516
3102
|
}
|
|
1517
3103
|
});
|
|
1518
3104
|
}
|
|
@@ -1550,7 +3136,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1550
3136
|
this.syncOptionalAttribute(
|
|
1551
3137
|
el,
|
|
1552
3138
|
"href",
|
|
1553
|
-
attrs.href === void 0 ? void 0 :
|
|
3139
|
+
attrs.href === void 0 ? void 0 : _chunkIYPLG4Z4js.sanitizeUrl.call(void 0, attrs.href)
|
|
1554
3140
|
);
|
|
1555
3141
|
this.syncOptionalAttribute(el, "target", attrs.target);
|
|
1556
3142
|
}
|
|
@@ -1593,6 +3179,31 @@ var Scene = (_class2 = class _Scene {
|
|
|
1593
3179
|
this.syncOptionalAttribute(el, "aria-activedescendant", attrs.activedescendant);
|
|
1594
3180
|
this.syncOptionalAttribute(el, "aria-valuemin", attrs.valuemin);
|
|
1595
3181
|
this.syncOptionalAttribute(el, "aria-valuemax", attrs.valuemax);
|
|
3182
|
+
this.syncOptionalAttribute(el, "aria-live", attrs.live);
|
|
3183
|
+
this.syncOptionalAttribute(
|
|
3184
|
+
el,
|
|
3185
|
+
"aria-atomic",
|
|
3186
|
+
attrs.atomic === void 0 ? void 0 : String(attrs.atomic)
|
|
3187
|
+
);
|
|
3188
|
+
this.syncOptionalAttribute(el, "aria-relevant", attrs.relevant);
|
|
3189
|
+
this.syncOptionalAttribute(el, "aria-modal", attrs.ariaModal);
|
|
3190
|
+
this.syncOptionalAttribute(el, "aria-labelledby", attrs.labelledby);
|
|
3191
|
+
this.syncOptionalAttribute(el, "aria-describedby", attrs.describedby);
|
|
3192
|
+
this.syncOptionalAttribute(
|
|
3193
|
+
el,
|
|
3194
|
+
"aria-required",
|
|
3195
|
+
attrs.required === void 0 ? void 0 : String(attrs.required)
|
|
3196
|
+
);
|
|
3197
|
+
this.syncOptionalAttribute(
|
|
3198
|
+
el,
|
|
3199
|
+
"aria-invalid",
|
|
3200
|
+
attrs.invalid === void 0 ? void 0 : String(attrs.invalid)
|
|
3201
|
+
);
|
|
3202
|
+
this.syncOptionalAttribute(
|
|
3203
|
+
el,
|
|
3204
|
+
"aria-level",
|
|
3205
|
+
attrs.level === void 0 ? void 0 : String(attrs.level)
|
|
3206
|
+
);
|
|
1596
3207
|
if (attrs.value !== void 0) {
|
|
1597
3208
|
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
|
|
1598
3209
|
if (el.value !== attrs.value) {
|
|
@@ -1624,13 +3235,18 @@ var Scene = (_class2 = class _Scene {
|
|
|
1624
3235
|
el.style.width = `${this.width}px`;
|
|
1625
3236
|
el.style.height = `${this.height}px`;
|
|
1626
3237
|
el.style.transform = "";
|
|
3238
|
+
if (el.style.display === "none") el.style.display = "";
|
|
1627
3239
|
} else {
|
|
1628
|
-
const
|
|
3240
|
+
const worldTf = node.getWorldTransform();
|
|
3241
|
+
const { a, b, c, d, e, f } = worldTf;
|
|
1629
3242
|
el.style.left = `${e + node.a11yOffsetX}px`;
|
|
1630
3243
|
el.style.top = `${f + node.a11yOffsetY}px`;
|
|
1631
3244
|
el.style.width = `${node.width}px`;
|
|
1632
3245
|
el.style.height = `${node.height}px`;
|
|
1633
3246
|
el.style.transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0)`;
|
|
3247
|
+
const visible = this.projectionBoxVisible(node, worldTf, 0);
|
|
3248
|
+
const display = visible ? "" : "none";
|
|
3249
|
+
if (el.style.display !== display) el.style.display = display;
|
|
1634
3250
|
}
|
|
1635
3251
|
}
|
|
1636
3252
|
this.syncContentProjection(node);
|
|
@@ -1698,26 +3314,24 @@ var Scene = (_class2 = class _Scene {
|
|
|
1698
3314
|
}
|
|
1699
3315
|
syncContentProjection(node) {
|
|
1700
3316
|
if (!this.contentProjectionEnabled || !this.a11yRoot) return;
|
|
1701
|
-
const projection = node.getContentProjection();
|
|
1702
3317
|
let el = this.contentElements.get(node.id);
|
|
1703
|
-
|
|
3318
|
+
const releaseProjectionEl = () => {
|
|
1704
3319
|
if (el) {
|
|
1705
3320
|
this.clearContentGridState(node.id, el);
|
|
1706
3321
|
el.remove();
|
|
1707
3322
|
this.contentElements.delete(node.id);
|
|
1708
3323
|
this.a11yNeedsReorder = true;
|
|
1709
3324
|
}
|
|
1710
|
-
|
|
1711
|
-
}
|
|
3325
|
+
};
|
|
1712
3326
|
const worldTf = node.getWorldTransform();
|
|
1713
3327
|
const margin = _nullishCoalesce(this.contentProjectionMargin, () => ( this.height));
|
|
1714
3328
|
if (Number.isFinite(margin) && !this.projectionBoxVisible(node, worldTf, margin)) {
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
3329
|
+
releaseProjectionEl();
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
const projection = node.getContentProjection();
|
|
3333
|
+
if (!projection || !projection.text) {
|
|
3334
|
+
releaseProjectionEl();
|
|
1721
3335
|
return;
|
|
1722
3336
|
}
|
|
1723
3337
|
if (!el) {
|
|
@@ -1736,7 +3350,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1736
3350
|
el.addEventListener(
|
|
1737
3351
|
"wheel",
|
|
1738
3352
|
(e2) => {
|
|
1739
|
-
node.dispatchEvent(new (0,
|
|
3353
|
+
node.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)("wheel", node, e2));
|
|
1740
3354
|
},
|
|
1741
3355
|
{ passive: false }
|
|
1742
3356
|
);
|
|
@@ -1757,35 +3371,48 @@ var Scene = (_class2 = class _Scene {
|
|
|
1757
3371
|
fallbackLineHeight: _nullishCoalesce(projection.lineHeight, () => ( 16))
|
|
1758
3372
|
});
|
|
1759
3373
|
if (el.dataset.vectoProjectionLines !== signature) {
|
|
1760
|
-
this.
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
const
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
3374
|
+
this.preserveContentSelectionAcrossRebuild(el, () => {
|
|
3375
|
+
el.replaceChildren();
|
|
3376
|
+
for (let index = 0; index < lines.length; index++) {
|
|
3377
|
+
const line = lines[index];
|
|
3378
|
+
const lineElement = document.createElement("span");
|
|
3379
|
+
const lineFont = _nullishCoalesce(_nullishCoalesce(line.font, () => ( projection.font)), () => ( ""));
|
|
3380
|
+
const lineHeight2 = _nullishCoalesce(_nullishCoalesce(line.lineHeight, () => ( projection.lineHeight)), () => ( 16));
|
|
3381
|
+
const hasPositionedRuns = !!line.runs && line.runs.some((run) => run.x !== void 0);
|
|
3382
|
+
lineElement.style.position = "absolute";
|
|
3383
|
+
lineElement.dir = hasPositionedRuns ? "ltr" : "auto";
|
|
3384
|
+
lineElement.style.left = `${line.x}px`;
|
|
3385
|
+
lineElement.style.top = `${line.y + line.baseline - _text.cssLineBoxBaseline.call(void 0, lineFont, lineHeight2)}px`;
|
|
3386
|
+
lineElement.style.whiteSpace = "pre";
|
|
3387
|
+
if (lineFont) lineElement.style.font = lineFont;
|
|
3388
|
+
lineElement.style.lineHeight = `${lineHeight2}px`;
|
|
3389
|
+
const separator = _nullishCoalesce(line.separatorAfter, () => ( (index < lines.length - 1 ? "\n" : "")));
|
|
3390
|
+
if (line.runs && line.runs.length > 0) {
|
|
3391
|
+
const positioned = line.runs.some((run) => run.x !== void 0);
|
|
3392
|
+
for (let runIndex = 0; runIndex < line.runs.length; runIndex++) {
|
|
3393
|
+
const run = line.runs[runIndex];
|
|
3394
|
+
const runElement = document.createElement("span");
|
|
3395
|
+
runElement.textContent = run.text + (runIndex === line.runs.length - 1 ? separator : "");
|
|
3396
|
+
if (run.font) runElement.style.font = run.font;
|
|
3397
|
+
runElement.style.lineHeight = `${lineHeight2}px`;
|
|
3398
|
+
if (positioned && run.x !== void 0) {
|
|
3399
|
+
runElement.style.position = "absolute";
|
|
3400
|
+
runElement.style.left = `${run.x - line.x}px`;
|
|
3401
|
+
runElement.style.top = "0";
|
|
3402
|
+
if (run.width !== void 0) runElement.style.width = `${run.width}px`;
|
|
3403
|
+
runElement.style.whiteSpace = "pre";
|
|
3404
|
+
runElement.style.verticalAlign = "top";
|
|
3405
|
+
runElement.style.unicodeBidi = "isolate";
|
|
3406
|
+
runElement.dir = "ltr";
|
|
3407
|
+
}
|
|
3408
|
+
lineElement.appendChild(runElement);
|
|
3409
|
+
}
|
|
3410
|
+
} else {
|
|
3411
|
+
lineElement.textContent = line.text + separator;
|
|
1783
3412
|
}
|
|
1784
|
-
|
|
1785
|
-
lineElement.textContent = line.text + separator;
|
|
3413
|
+
el.appendChild(lineElement);
|
|
1786
3414
|
}
|
|
1787
|
-
|
|
1788
|
-
}
|
|
3415
|
+
});
|
|
1789
3416
|
el.dataset.vectoProjectionLines = signature;
|
|
1790
3417
|
}
|
|
1791
3418
|
} else {
|
|
@@ -1848,15 +3475,15 @@ var Scene = (_class2 = class _Scene {
|
|
|
1848
3475
|
for (let lineIndex = 0; lineIndex < grid.lines.length; lineIndex++) {
|
|
1849
3476
|
const gridLine = grid.lines[lineIndex];
|
|
1850
3477
|
const projectedLine = projectionLines[lineIndex];
|
|
1851
|
-
const lineHeight = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess',
|
|
1852
|
-
const baseline = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess',
|
|
1853
|
-
const lineFont = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess',
|
|
3478
|
+
const lineHeight = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _102 => _102.lineHeight]), () => ( grid.lineHeight));
|
|
3479
|
+
const baseline = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _103 => _103.baseline]), () => ( grid.baseline));
|
|
3480
|
+
const lineFont = _nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _104 => _104.font]), () => ( grid.font));
|
|
1854
3481
|
const lineElement = document.createElement("span");
|
|
1855
3482
|
lineElement.dir = "ltr";
|
|
1856
3483
|
lineElement.dataset.vectoGridLine = `${lineIndex}`;
|
|
1857
3484
|
lineElement.style.position = "absolute";
|
|
1858
|
-
lineElement.style.left = `${_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess',
|
|
1859
|
-
lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess',
|
|
3485
|
+
lineElement.style.left = `${_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _105 => _105.x]), () => ( 0))}px`;
|
|
3486
|
+
lineElement.style.top = `${(_nullishCoalesce(_optionalChain([projectedLine, 'optionalAccess', _106 => _106.y]), () => ( lineIndex * grid.lineHeight))) + baseline - _text.cssLineBoxBaseline.call(void 0, lineFont, lineHeight)}px`;
|
|
1860
3487
|
lineElement.style.width = `${gridLine.width}px`;
|
|
1861
3488
|
lineElement.style.height = `${lineHeight}px`;
|
|
1862
3489
|
lineElement.style.whiteSpace = "pre";
|
|
@@ -1952,7 +3579,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1952
3579
|
if (previous !== void 0 && typeof cancelAnimationFrame === "function") {
|
|
1953
3580
|
cancelAnimationFrame(previous);
|
|
1954
3581
|
}
|
|
1955
|
-
_optionalChain([this, 'access',
|
|
3582
|
+
_optionalChain([this, 'access', _107 => _107.contentGridCalibrationProbes, 'access', _108 => _108.get, 'call', _109 => _109(entityId), 'optionalAccess', _110 => _110.remove, 'call', _111 => _111()]);
|
|
1956
3583
|
this.contentGridCalibrationProbes.delete(entityId);
|
|
1957
3584
|
const calibrationStart = typeof performance !== "undefined" ? performance.now() : 0;
|
|
1958
3585
|
const probe = document.createElement("div");
|
|
@@ -1982,7 +3609,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
1982
3609
|
const sourceLength = Number(_nullishCoalesce(target.dataset.vectoGridSourceLength, () => ( 0)));
|
|
1983
3610
|
const targetWidth = Number(_nullishCoalesce(target.dataset.vectoGridAdvance, () => ( 0)));
|
|
1984
3611
|
if (sourceLength <= 0 || targetWidth <= 0) continue;
|
|
1985
|
-
const sourceText = _nullishCoalesce(_optionalChain([target, 'access',
|
|
3612
|
+
const sourceText = _nullishCoalesce(_optionalChain([target, 'access', _112 => _112.textContent, 'optionalAccess', _113 => _113.slice, 'call', _114 => _114(0, sourceLength)]), () => ( ""));
|
|
1986
3613
|
if (!sourceText) continue;
|
|
1987
3614
|
const measurementKey = JSON.stringify([
|
|
1988
3615
|
target.style.font,
|
|
@@ -2008,7 +3635,12 @@ var Scene = (_class2 = class _Scene {
|
|
|
2008
3635
|
const source = document.createTextNode(sourceText);
|
|
2009
3636
|
carrier.appendChild(source);
|
|
2010
3637
|
probe.appendChild(carrier);
|
|
2011
|
-
const measurement = {
|
|
3638
|
+
const measurement = {
|
|
3639
|
+
targets: [target],
|
|
3640
|
+
targetWidth,
|
|
3641
|
+
sourceLength,
|
|
3642
|
+
source
|
|
3643
|
+
};
|
|
2012
3644
|
measurements.push(measurement);
|
|
2013
3645
|
measurementsByKey.set(measurementKey, measurement);
|
|
2014
3646
|
}
|
|
@@ -2107,6 +3739,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
2107
3739
|
}
|
|
2108
3740
|
}
|
|
2109
3741
|
if (el.parentNode === this.a11yRoot) {
|
|
3742
|
+
this.preserveFocusOnRemoval(el);
|
|
2110
3743
|
this.a11yRoot.removeChild(el);
|
|
2111
3744
|
}
|
|
2112
3745
|
this.a11yElements.delete(id);
|
|
@@ -2116,6 +3749,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
2116
3749
|
this.a11yNeedsReorder = true;
|
|
2117
3750
|
}
|
|
2118
3751
|
if (!this.a11yNeedsReorder) return;
|
|
3752
|
+
this.sortNormalElementsVisually();
|
|
2119
3753
|
const fullLen = this.fullViewportElements.length;
|
|
2120
3754
|
const normalLen = this.normalElements.length;
|
|
2121
3755
|
const totalLen = fullLen + normalLen;
|
|
@@ -2128,18 +3762,73 @@ var Scene = (_class2 = class _Scene {
|
|
|
2128
3762
|
}
|
|
2129
3763
|
this.a11yNeedsReorder = false;
|
|
2130
3764
|
}
|
|
3765
|
+
/**
|
|
3766
|
+
* Reorder `normalElements` (in place) into visual reading order using the
|
|
3767
|
+
* world positions `syncA11y` already wrote to each element's inline style
|
|
3768
|
+
* (`top`/`left`/`height`). Elements are grouped into rows top-to-bottom (an
|
|
3769
|
+
* element belongs to the current row while its top is above the row's
|
|
3770
|
+
* running bottom edge), then sorted within a row by `left` — ascending for
|
|
3771
|
+
* `'ltr'`, descending for `'rtl'`. The sort is stable, so entities at the
|
|
3772
|
+
* same position keep their scene-graph (collection) order as a tiebreak.
|
|
3773
|
+
*/
|
|
3774
|
+
sortNormalElementsVisually() {
|
|
3775
|
+
const els = this.normalElements;
|
|
3776
|
+
if (els.length < 2) return;
|
|
3777
|
+
const rtl = this._readingDirection === "rtl";
|
|
3778
|
+
const topOf = (el) => Number.parseFloat(el.style.top) || 0;
|
|
3779
|
+
const leftOf = (el) => Number.parseFloat(el.style.left) || 0;
|
|
3780
|
+
const heightOf = (el) => Math.max(Number.parseFloat(el.style.height) || 0, 4);
|
|
3781
|
+
const order = els.map((el, i) => ({
|
|
3782
|
+
el,
|
|
3783
|
+
i,
|
|
3784
|
+
top: topOf(el),
|
|
3785
|
+
left: leftOf(el)
|
|
3786
|
+
}));
|
|
3787
|
+
order.sort((p, q) => p.top - q.top || p.i - q.i);
|
|
3788
|
+
const sorted = [];
|
|
3789
|
+
let rowStart = 0;
|
|
3790
|
+
let rowBottom = order.length ? order[0].top + heightOf(order[0].el) : 0;
|
|
3791
|
+
const flushRow = (end) => {
|
|
3792
|
+
const row = order.slice(rowStart, end);
|
|
3793
|
+
row.sort((p, q) => (rtl ? q.left - p.left : p.left - q.left) || p.i - q.i);
|
|
3794
|
+
for (const r of row) sorted.push(r.el);
|
|
3795
|
+
};
|
|
3796
|
+
for (let k = 1; k < order.length; k++) {
|
|
3797
|
+
if (order[k].top < rowBottom) {
|
|
3798
|
+
rowBottom = Math.max(rowBottom, order[k].top + heightOf(order[k].el));
|
|
3799
|
+
} else {
|
|
3800
|
+
flushRow(k);
|
|
3801
|
+
rowStart = k;
|
|
3802
|
+
rowBottom = order[k].top + heightOf(order[k].el);
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
flushRow(order.length);
|
|
3806
|
+
for (let i = 0; i < sorted.length; i++) els[i] = sorted[i];
|
|
3807
|
+
}
|
|
2131
3808
|
/** Keep DOM/WebGL overlay layers aligned with the canvas's CSS box. */
|
|
2132
3809
|
syncOverlayGeometry() {
|
|
2133
3810
|
const parent = this.canvas.parentElement;
|
|
2134
3811
|
if (!parent) return;
|
|
2135
|
-
const canvasRect = _optionalChain([this, 'access',
|
|
2136
|
-
const parentRect = _optionalChain([parent, 'access',
|
|
2137
|
-
const cssWidth = _optionalChain([canvasRect, 'optionalAccess',
|
|
2138
|
-
const cssHeight = _optionalChain([canvasRect, 'optionalAccess',
|
|
2139
|
-
const left = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess',
|
|
2140
|
-
const top = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess',
|
|
3812
|
+
const canvasRect = _optionalChain([this, 'access', _115 => _115.canvas, 'access', _116 => _116.getBoundingClientRect, 'optionalCall', _117 => _117()]);
|
|
3813
|
+
const parentRect = _optionalChain([parent, 'access', _118 => _118.getBoundingClientRect, 'optionalCall', _119 => _119()]);
|
|
3814
|
+
const cssWidth = _optionalChain([canvasRect, 'optionalAccess', _120 => _120.width]) || this.canvas.clientWidth || this.width;
|
|
3815
|
+
const cssHeight = _optionalChain([canvasRect, 'optionalAccess', _121 => _121.height]) || this.canvas.clientHeight || this.height;
|
|
3816
|
+
const left = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _122 => _122.left]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _123 => _123.left]), () => ( 0))) - (parent.clientLeft || 0) + parent.scrollLeft;
|
|
3817
|
+
const top = (_nullishCoalesce(_optionalChain([canvasRect, 'optionalAccess', _124 => _124.top]), () => ( 0))) - (_nullishCoalesce(_optionalChain([parentRect, 'optionalAccess', _125 => _125.top]), () => ( 0))) - (parent.clientTop || 0) + parent.scrollTop;
|
|
2141
3818
|
const scaleX = this.width > 0 ? cssWidth / this.width : 1;
|
|
2142
3819
|
const scaleY = this.height > 0 ? cssHeight / this.height : 1;
|
|
3820
|
+
const prev = this._overlayGeometry;
|
|
3821
|
+
if (prev !== null && prev.left === left && prev.top === top && prev.cssWidth === cssWidth && prev.cssHeight === cssHeight && prev.width === this.width && prev.height === this.height) {
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
3824
|
+
this._overlayGeometry = {
|
|
3825
|
+
left,
|
|
3826
|
+
top,
|
|
3827
|
+
cssWidth,
|
|
3828
|
+
cssHeight,
|
|
3829
|
+
width: this.width,
|
|
3830
|
+
height: this.height
|
|
3831
|
+
};
|
|
2143
3832
|
for (const root of [this.a11yRoot, this.portalRoot]) {
|
|
2144
3833
|
if (!root) continue;
|
|
2145
3834
|
root.style.left = `${left}px`;
|
|
@@ -2207,6 +3896,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
2207
3896
|
if (portal.domElement.parentElement !== this.portalRoot) {
|
|
2208
3897
|
this.portalRoot.appendChild(portal.domElement);
|
|
2209
3898
|
}
|
|
3899
|
+
portal.attachDOMBindings();
|
|
2210
3900
|
if (!portal.domElement.hasAttribute("data-vecto-id")) {
|
|
2211
3901
|
portal.domElement.setAttribute("data-vecto-id", portal.id);
|
|
2212
3902
|
}
|
|
@@ -2247,6 +3937,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
2247
3937
|
const portal = this.portalEntities.get(oldId);
|
|
2248
3938
|
if (portal) {
|
|
2249
3939
|
if (portal.domElement.parentElement === this.portalRoot && (!portal.scene || portal.scene === this)) {
|
|
3940
|
+
portal.releaseDOMBindings();
|
|
2250
3941
|
portal.domElement.remove();
|
|
2251
3942
|
}
|
|
2252
3943
|
this.portalEntities.delete(oldId);
|
|
@@ -2262,13 +3953,14 @@ var Scene = (_class2 = class _Scene {
|
|
|
2262
3953
|
* (and {@link respectReducedMotion} is on). `0` means uncapped.
|
|
2263
3954
|
*/
|
|
2264
3955
|
effectiveMaxFPS() {
|
|
2265
|
-
const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access',
|
|
3956
|
+
const reduced = this.respectReducedMotion && !!_optionalChain([this, 'access', _126 => _126.reducedMotionQuery, 'optionalAccess', _127 => _127.matches]);
|
|
2266
3957
|
if (reduced)
|
|
2267
3958
|
return this.maxFPS > 0 ? Math.min(this.maxFPS, REDUCED_MOTION_FPS) : REDUCED_MOTION_FPS;
|
|
2268
3959
|
return this.maxFPS;
|
|
2269
3960
|
}
|
|
2270
3961
|
loop(time) {
|
|
2271
3962
|
if (!this.isRunning) return;
|
|
3963
|
+
if (!this._canvasOnScreen) return;
|
|
2272
3964
|
let cap = this.effectiveMaxFPS();
|
|
2273
3965
|
const isIdle = !this.dirty && !this.frameHadAnimation;
|
|
2274
3966
|
if (isIdle && this.autoThrottle && this.renderMode === "always" && this.maxFPS > 0) {
|
|
@@ -2284,6 +3976,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
2284
3976
|
const nominal = 1e3 / cap;
|
|
2285
3977
|
if (Math.abs(dt - nominal) < nominal * 0.3) dt = nominal;
|
|
2286
3978
|
}
|
|
3979
|
+
if (dt > _Scene.MAX_FRAME_DT) dt = _Scene.MAX_FRAME_DT;
|
|
2287
3980
|
this.lastTime = time;
|
|
2288
3981
|
if (this.renderMode === "onDemand" && isIdle) {
|
|
2289
3982
|
this._skippedFrames++;
|
|
@@ -2327,6 +4020,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
2327
4020
|
* @param time - Current absolute time in milliseconds (default 0).
|
|
2328
4021
|
*/
|
|
2329
4022
|
render(renderer, dt = 0, time = 0) {
|
|
4023
|
+
if (_optionalChain([renderer, 'access', _128 => _128.isContextLost, 'optionalCall', _129 => _129()])) return;
|
|
2330
4024
|
const isMainRenderer = renderer === this.renderer;
|
|
2331
4025
|
if (isMainRenderer && this.a11yRoot && this.canvas.parentElement) {
|
|
2332
4026
|
const parentStyle = this.canvas.parentElement.style;
|
|
@@ -2336,23 +4030,13 @@ var Scene = (_class2 = class _Scene {
|
|
|
2336
4030
|
this.syncOverlayGeometry();
|
|
2337
4031
|
}
|
|
2338
4032
|
if (isMainRenderer) {
|
|
4033
|
+
this.currentFrame++;
|
|
2339
4034
|
this.renderOrderCounter = 0;
|
|
2340
4035
|
this.a11yRenderOrders.clear();
|
|
2341
4036
|
this.activePortalsThisFrame.clear();
|
|
4037
|
+
this._tickBatchedDrivers(dt);
|
|
2342
4038
|
}
|
|
2343
|
-
const computeEntities =
|
|
2344
|
-
const collectComputeEntities = (node) => {
|
|
2345
|
-
if (node instanceof ComputeParticleEntity) {
|
|
2346
|
-
computeEntities.push(node);
|
|
2347
|
-
}
|
|
2348
|
-
for (const child of node.children) {
|
|
2349
|
-
collectComputeEntities(child);
|
|
2350
|
-
}
|
|
2351
|
-
};
|
|
2352
|
-
collectComputeEntities(this.root);
|
|
2353
|
-
for (const overlay of this.overlayRoot.children) {
|
|
2354
|
-
collectComputeEntities(overlay);
|
|
2355
|
-
}
|
|
4039
|
+
const computeEntities = this._computeEntitiesFor(this._structureVersion);
|
|
2356
4040
|
if (computeEntities.length > 0) {
|
|
2357
4041
|
const isMainRenderPath = renderer === this.renderer;
|
|
2358
4042
|
if (isMainRenderPath && !this.device && !this.webgpuDisabled && !this.initializingWebGPU && !this.deviceLost) {
|
|
@@ -2450,7 +4134,11 @@ var Scene = (_class2 = class _Scene {
|
|
|
2450
4134
|
my = -9999;
|
|
2451
4135
|
}
|
|
2452
4136
|
}
|
|
2453
|
-
|
|
4137
|
+
if (this._particleWasm) {
|
|
4138
|
+
entity.stepWithBackend(this._particleWasm, dt / 1e3, mx, my, this.width, this.height);
|
|
4139
|
+
} else {
|
|
4140
|
+
entity.updateCPU(dt / 1e3, mx, my, this.width, this.height);
|
|
4141
|
+
}
|
|
2454
4142
|
}
|
|
2455
4143
|
}
|
|
2456
4144
|
} else if (isMainRenderer) {
|
|
@@ -2458,37 +4146,75 @@ var Scene = (_class2 = class _Scene {
|
|
|
2458
4146
|
}
|
|
2459
4147
|
renderer.clear();
|
|
2460
4148
|
if (isMainRenderer) {
|
|
2461
|
-
_optionalChain([this, 'access',
|
|
4149
|
+
_optionalChain([this, 'access', _130 => _130.pointRenderer, 'optionalAccess', _131 => _131.begin, 'call', _132 => _132()]);
|
|
2462
4150
|
}
|
|
2463
4151
|
const vw = this.width;
|
|
2464
4152
|
const vh = this.height;
|
|
2465
4153
|
let walkHadAnimation = false;
|
|
2466
4154
|
let walkHadInteractive = false;
|
|
2467
|
-
const
|
|
2468
|
-
|
|
4155
|
+
const runUpdate = (node) => {
|
|
4156
|
+
const overridesUpdate = node.update !== _chunkDUYB4GX4js.Entity.prototype.update;
|
|
4157
|
+
let pending = node.hasPendingAnimations();
|
|
4158
|
+
if (pending || overridesUpdate) {
|
|
2469
4159
|
node.update(dt, time);
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
4160
|
+
pending = node.hasPendingAnimations();
|
|
4161
|
+
}
|
|
4162
|
+
if (pending) walkHadAnimation = true;
|
|
4163
|
+
if (!walkHadInteractive && node.interactive) walkHadInteractive = true;
|
|
4164
|
+
if (this._devActive && this._devFrameCount % 120 === 0) {
|
|
4165
|
+
if (overridesUpdate && node.hasPendingAnimations === _chunkDUYB4GX4js.Entity.prototype.hasPendingAnimations) {
|
|
4166
|
+
this._devWarn(
|
|
4167
|
+
`Entity "${node.id}" overrides update() but not hasPendingAnimations(). Custom motion in update() without overriding hasPendingAnimations() causes the idle throttle to drop the animation to ~2fps. Override hasPendingAnimations() to return true while motion is in flight.`
|
|
4168
|
+
);
|
|
2478
4169
|
}
|
|
2479
4170
|
}
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
const
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
const
|
|
2490
|
-
|
|
2491
|
-
|
|
4171
|
+
};
|
|
4172
|
+
const wasmMain = isMainRenderer && this._wasm !== null && this._transformBackend === "wasm";
|
|
4173
|
+
if (wasmMain) {
|
|
4174
|
+
const updateWalk = (node) => {
|
|
4175
|
+
runUpdate(node);
|
|
4176
|
+
const kids = node.children;
|
|
4177
|
+
for (let i = 0; i < kids.length; i++) updateWalk(kids[i]);
|
|
4178
|
+
};
|
|
4179
|
+
updateWalk(this.root);
|
|
4180
|
+
for (const overlay of this.overlayRoot.children) updateWalk(overlay);
|
|
4181
|
+
}
|
|
4182
|
+
const wasmWorld = wasmMain ? this._syncWasmStore() : null;
|
|
4183
|
+
const wasmSlotEntity = this._slotEntity;
|
|
4184
|
+
const renderNode = (node, pa, pb, pc, pd, pe, pf, parentOpacity) => {
|
|
4185
|
+
if (isMainRenderer && !wasmMain) {
|
|
4186
|
+
runUpdate(node);
|
|
4187
|
+
}
|
|
4188
|
+
let a;
|
|
4189
|
+
let b;
|
|
4190
|
+
let c;
|
|
4191
|
+
let d;
|
|
4192
|
+
let te;
|
|
4193
|
+
let tf;
|
|
4194
|
+
const slot = node._storeSlot;
|
|
4195
|
+
if (wasmWorld !== null && slot >= 0 && wasmSlotEntity[slot] === node) {
|
|
4196
|
+
a = wasmWorld.wa[slot];
|
|
4197
|
+
b = wasmWorld.wb[slot];
|
|
4198
|
+
c = wasmWorld.wc[slot];
|
|
4199
|
+
d = wasmWorld.wd[slot];
|
|
4200
|
+
te = wasmWorld.we[slot];
|
|
4201
|
+
tf = wasmWorld.wf[slot];
|
|
4202
|
+
} else {
|
|
4203
|
+
const trig = node._getTrig();
|
|
4204
|
+
const cos = trig.cos;
|
|
4205
|
+
const sin = trig.sin;
|
|
4206
|
+
te = pa * node.x + pc * node.y + pe;
|
|
4207
|
+
tf = pb * node.x + pd * node.y + pf;
|
|
4208
|
+
const sxCos = node.scaleX * cos;
|
|
4209
|
+
const sxSin = node.scaleX * sin;
|
|
4210
|
+
const syCos = node.scaleY * cos;
|
|
4211
|
+
const sySin = node.scaleY * sin;
|
|
4212
|
+
a = pa * sxCos + pc * sySin;
|
|
4213
|
+
b = pb * sxCos + pd * sySin;
|
|
4214
|
+
c = pa * -sxSin + pc * syCos;
|
|
4215
|
+
d = pb * -sxSin + pd * syCos;
|
|
4216
|
+
}
|
|
4217
|
+
if (isMainRenderer) node._setWorldCache(a, b, c, d, te, tf, this.currentFrame);
|
|
2492
4218
|
const worldScaleX = Math.hypot(a, b);
|
|
2493
4219
|
const worldScaleY = Math.hypot(c, d);
|
|
2494
4220
|
const worldOpacity = parentOpacity * node.opacity;
|
|
@@ -2606,9 +4332,9 @@ var Scene = (_class2 = class _Scene {
|
|
|
2606
4332
|
}
|
|
2607
4333
|
renderer.flush();
|
|
2608
4334
|
if (isMainRenderer) {
|
|
2609
|
-
_optionalChain([this, 'access',
|
|
4335
|
+
_optionalChain([this, 'access', _133 => _133.pointRenderer, 'optionalAccess', _134 => _134.flush, 'call', _135 => _135()]);
|
|
2610
4336
|
}
|
|
2611
|
-
_optionalChain([renderer, 'access',
|
|
4337
|
+
_optionalChain([renderer, 'access', _136 => _136.present, 'optionalCall', _137 => _137()]);
|
|
2612
4338
|
if (this._devActive) {
|
|
2613
4339
|
this._devFrameCount++;
|
|
2614
4340
|
this._devRunChecks();
|
|
@@ -2618,7 +4344,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
2618
4344
|
* Export the current scene state to a lightweight, flat SVG XML string.
|
|
2619
4345
|
*/
|
|
2620
4346
|
toSVG() {
|
|
2621
|
-
const renderer = new (0,
|
|
4347
|
+
const renderer = new (0, _chunkIYPLG4Z4js.SVGRenderer)(this.width, this.height);
|
|
2622
4348
|
this.render(renderer, 0, 0);
|
|
2623
4349
|
return renderer.toXMLString();
|
|
2624
4350
|
}
|
|
@@ -2637,12 +4363,25 @@ var Scene = (_class2 = class _Scene {
|
|
|
2637
4363
|
this.pointRenderer.maxDPR = this.maxDPR;
|
|
2638
4364
|
this.pointRenderer.resize(width, height);
|
|
2639
4365
|
}
|
|
2640
|
-
if (this.gpuCanvas)
|
|
2641
|
-
this.gpuCanvas.width = width;
|
|
2642
|
-
this.gpuCanvas.height = height;
|
|
2643
|
-
}
|
|
4366
|
+
if (this.gpuCanvas) this.sizeGpuCanvas(this.gpuCanvas, width, height);
|
|
2644
4367
|
this.markDirty();
|
|
2645
4368
|
}
|
|
4369
|
+
/** Effective device pixel ratio, matching CanvasRenderer: real DPR clamped to
|
|
4370
|
+
* `maxDPR` when set. */
|
|
4371
|
+
effectiveDPR() {
|
|
4372
|
+
const real = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
4373
|
+
return this.maxDPR !== void 0 ? Math.min(real, this.maxDPR) : real;
|
|
4374
|
+
}
|
|
4375
|
+
/** Size the WebGPU particle canvas: backing store at logical × DPR, CSS box at
|
|
4376
|
+
* the logical size. Sizing the backing store in logical px (the old
|
|
4377
|
+
* behavior) left it rasterized at 1× and CSS-stretched — blurry on HiDPI. */
|
|
4378
|
+
sizeGpuCanvas(gpuCanvas, width, height) {
|
|
4379
|
+
const dpr = this.effectiveDPR();
|
|
4380
|
+
gpuCanvas.width = Math.max(1, Math.round(width * dpr));
|
|
4381
|
+
gpuCanvas.height = Math.max(1, Math.round(height * dpr));
|
|
4382
|
+
gpuCanvas.style.width = `${width}px`;
|
|
4383
|
+
gpuCanvas.style.height = `${height}px`;
|
|
4384
|
+
}
|
|
2646
4385
|
/**
|
|
2647
4386
|
* Gets the accessibility DOM element projected for the given entity ID.
|
|
2648
4387
|
*/
|
|
@@ -2665,6 +4404,9 @@ var Scene = (_class2 = class _Scene {
|
|
|
2665
4404
|
findEntityAt(x, y) {
|
|
2666
4405
|
const overlayHit = this.findHitRecursively(this.overlayRoot, x, y);
|
|
2667
4406
|
if (overlayHit) return overlayHit;
|
|
4407
|
+
if (this._hitWasm && this._ensureHitGrid()) {
|
|
4408
|
+
return this._findEntityAtWasm(x, y);
|
|
4409
|
+
}
|
|
2668
4410
|
return this.findHitRecursively(this.root, x, y);
|
|
2669
4411
|
}
|
|
2670
4412
|
/** Submit one transparent clear pass when particle content lingers on the GPU canvas. */
|
|
@@ -2684,7 +4426,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
2684
4426
|
});
|
|
2685
4427
|
pass.end();
|
|
2686
4428
|
this.device.queue.submit([encoder.finish()]);
|
|
2687
|
-
} catch (
|
|
4429
|
+
} catch (e18) {
|
|
2688
4430
|
}
|
|
2689
4431
|
this.gpuHasContent = false;
|
|
2690
4432
|
}
|
|
@@ -2699,8 +4441,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
2699
4441
|
const device = await adapter.requestDevice();
|
|
2700
4442
|
if (typeof document !== "undefined" && !this.gpuCanvas) {
|
|
2701
4443
|
const gpuCanvas = document.createElement("canvas");
|
|
2702
|
-
gpuCanvas.width
|
|
2703
|
-
gpuCanvas.height = this.height;
|
|
4444
|
+
this.sizeGpuCanvas(gpuCanvas, this.width, this.height);
|
|
2704
4445
|
gpuCanvas.style.position = "absolute";
|
|
2705
4446
|
gpuCanvas.style.top = "0";
|
|
2706
4447
|
gpuCanvas.style.left = "0";
|
|
@@ -2710,6 +4451,7 @@ var Scene = (_class2 = class _Scene {
|
|
|
2710
4451
|
this.canvas.parentElement.appendChild(gpuCanvas);
|
|
2711
4452
|
}
|
|
2712
4453
|
this.gpuCanvas = gpuCanvas;
|
|
4454
|
+
this._overlayGeometry = null;
|
|
2713
4455
|
this.gpuContext = gpuCanvas.getContext("webgpu");
|
|
2714
4456
|
}
|
|
2715
4457
|
if (this.gpuContext) {
|
|
@@ -2806,17 +4548,68 @@ var Scene = (_class2 = class _Scene {
|
|
|
2806
4548
|
}
|
|
2807
4549
|
}
|
|
2808
4550
|
}
|
|
2809
|
-
findHitRecursively(node, x, y) {
|
|
4551
|
+
findHitRecursively(node, x, y, clip = null) {
|
|
4552
|
+
if (node.opacity <= 0) return null;
|
|
4553
|
+
let childClip = clip;
|
|
4554
|
+
if (node.clipChildren) {
|
|
4555
|
+
const box = node.getWorldBounds();
|
|
4556
|
+
childClip = clip ? intersectBounds(clip, box) : box;
|
|
4557
|
+
}
|
|
2810
4558
|
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
2811
|
-
const hit = this.findHitRecursively(node.children[i], x, y);
|
|
4559
|
+
const hit = this.findHitRecursively(node.children[i], x, y, childClip);
|
|
2812
4560
|
if (hit) return hit;
|
|
2813
4561
|
}
|
|
2814
|
-
if (node.isPointInside && node.isPointInside(x, y)) {
|
|
4562
|
+
if (node.isPointInside && node.isPointInside(x, y) && (!clip || pointInBounds(clip, x, y)) && !this.isPointerTransparent(node)) {
|
|
2815
4563
|
return node;
|
|
2816
4564
|
}
|
|
2817
4565
|
return null;
|
|
2818
4566
|
}
|
|
2819
|
-
|
|
4567
|
+
/** Whether `node` opts out of being a pointer hit target: a disabled control
|
|
4568
|
+
* or an explicit `pointerEvents: 'none'` in its a11y attributes. Its children
|
|
4569
|
+
* are still walked (a transparent container can hold hittable descendants). */
|
|
4570
|
+
isPointerTransparent(node) {
|
|
4571
|
+
const attrs = node.getA11yAttributes();
|
|
4572
|
+
return attrs.disabled === true || attrs.pointerEvents === "none";
|
|
4573
|
+
}
|
|
4574
|
+
/**
|
|
4575
|
+
* Whether a confirmed geometric hit on `node` at world `(x, y)` is a REAL hit,
|
|
4576
|
+
* applying the same visibility/input gating as {@link findHitRecursively} but
|
|
4577
|
+
* from a flat candidate (the WASM grid has no recursion clip-stack): the node
|
|
4578
|
+
* and all ancestors are visible (`opacity > 0`), the point lies inside every
|
|
4579
|
+
* `clipChildren` ancestor's world box, and the node isn't pointer-transparent
|
|
4580
|
+
* (disabled / `pointerEvents: 'none'`). Keeps the WASM and JS hit paths in
|
|
4581
|
+
* lockstep so they return the same entity.
|
|
4582
|
+
*/
|
|
4583
|
+
isHitEligible(node, x, y) {
|
|
4584
|
+
if (this.isPointerTransparent(node)) return false;
|
|
4585
|
+
if (node.opacity <= 0) return false;
|
|
4586
|
+
for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
|
|
4587
|
+
if (ancestor.opacity <= 0) return false;
|
|
4588
|
+
if (ancestor.clipChildren && ancestor.width > 0 && ancestor.height > 0) {
|
|
4589
|
+
const local = ancestor.worldToLocal(x, y);
|
|
4590
|
+
if (!local || local.x < 0 || local.y < 0 || local.x > ancestor.width || local.y > ancestor.height) {
|
|
4591
|
+
return false;
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
return true;
|
|
4596
|
+
}
|
|
4597
|
+
}, _class6.__initStatic(), _class6.__initStatic2(), _class6.__initStatic3(), _class6.__initStatic4(), _class6);
|
|
4598
|
+
function intersectBounds(a, b) {
|
|
4599
|
+
const x = Math.max(a.x, b.x);
|
|
4600
|
+
const y = Math.max(a.y, b.y);
|
|
4601
|
+
const right = Math.min(a.x + a.width, b.x + b.width);
|
|
4602
|
+
const bottom = Math.min(a.y + a.height, b.y + b.height);
|
|
4603
|
+
return {
|
|
4604
|
+
x,
|
|
4605
|
+
y,
|
|
4606
|
+
width: Math.max(0, right - x),
|
|
4607
|
+
height: Math.max(0, bottom - y)
|
|
4608
|
+
};
|
|
4609
|
+
}
|
|
4610
|
+
function pointInBounds(b, x, y) {
|
|
4611
|
+
return x >= b.x && x <= b.x + b.width && y >= b.y && y <= b.y + b.height;
|
|
4612
|
+
}
|
|
2820
4613
|
|
|
2821
4614
|
// src/components/TextEntity.ts
|
|
2822
4615
|
|
|
@@ -2828,20 +4621,20 @@ function defaultMeasurer() {
|
|
|
2828
4621
|
if (sharedMeasurer === void 0) sharedMeasurer = _layout.createCanvasMeasurer.call(void 0, "sans-serif");
|
|
2829
4622
|
return sharedMeasurer;
|
|
2830
4623
|
}
|
|
2831
|
-
var TextEntity = (
|
|
4624
|
+
var TextEntity = (_class7 = class extends _chunkDUYB4GX4js.Entity {
|
|
2832
4625
|
|
|
2833
4626
|
|
|
2834
4627
|
|
|
2835
4628
|
|
|
2836
|
-
|
|
4629
|
+
__init124() {this.nodes = []}
|
|
2837
4630
|
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
4631
|
+
__init125() {this.fillStyle = "#94a3b8"}
|
|
4632
|
+
__init126() {this.strokeStyle = null}
|
|
4633
|
+
__init127() {this.hoveredFillStyle = "#ffffff"}
|
|
4634
|
+
__init128() {this.lineWidth = 1}
|
|
4635
|
+
__init129() {this.isHovered = false}
|
|
2843
4636
|
constructor(text, atlas, maxWidth, fontSize = 24) {
|
|
2844
|
-
super();
|
|
4637
|
+
super();_class7.prototype.__init124.call(this);_class7.prototype.__init125.call(this);_class7.prototype.__init126.call(this);_class7.prototype.__init127.call(this);_class7.prototype.__init128.call(this);_class7.prototype.__init129.call(this);;
|
|
2845
4638
|
this.text = text;
|
|
2846
4639
|
this.atlas = atlas;
|
|
2847
4640
|
this.fontSize = fontSize;
|
|
@@ -2951,20 +4744,20 @@ var TextEntity = (_class3 = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
2951
4744
|
renderer.restore();
|
|
2952
4745
|
}
|
|
2953
4746
|
}
|
|
2954
|
-
},
|
|
4747
|
+
}, _class7);
|
|
2955
4748
|
|
|
2956
4749
|
// src/components/GridTextEntity.ts
|
|
2957
|
-
var GridTextEntity = (
|
|
4750
|
+
var GridTextEntity = (_class8 = class extends _chunkDUYB4GX4js.Entity {
|
|
2958
4751
|
|
|
2959
|
-
|
|
2960
|
-
|
|
4752
|
+
__init130() {this.fillStyle = "#ffffff"}
|
|
4753
|
+
__init131() {this.grid = []}
|
|
2961
4754
|
// Array of rows
|
|
2962
|
-
|
|
2963
|
-
|
|
4755
|
+
__init132() {this.cols = 0}
|
|
4756
|
+
__init133() {this.rows = 0}
|
|
2964
4757
|
|
|
2965
4758
|
|
|
2966
4759
|
constructor(_atlas, fontSize = 10) {
|
|
2967
|
-
super();
|
|
4760
|
+
super();_class8.prototype.__init130.call(this);_class8.prototype.__init131.call(this);_class8.prototype.__init132.call(this);_class8.prototype.__init133.call(this);;
|
|
2968
4761
|
this.fontSize = fontSize;
|
|
2969
4762
|
this.charWidth = fontSize * 1;
|
|
2970
4763
|
this.charHeight = fontSize * 1.1;
|
|
@@ -2973,7 +4766,7 @@ var GridTextEntity = (_class4 = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
2973
4766
|
updateGrid(ascii) {
|
|
2974
4767
|
this.grid = ascii;
|
|
2975
4768
|
this.rows = ascii.length;
|
|
2976
|
-
this.cols = _optionalChain([ascii, 'access',
|
|
4769
|
+
this.cols = _optionalChain([ascii, 'access', _138 => _138[0], 'optionalAccess', _139 => _139.length]) || 0;
|
|
2977
4770
|
}
|
|
2978
4771
|
isPointInside(_globalX, _globalY) {
|
|
2979
4772
|
return false;
|
|
@@ -2995,7 +4788,7 @@ var GridTextEntity = (_class4 = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
2995
4788
|
}
|
|
2996
4789
|
}
|
|
2997
4790
|
}
|
|
2998
|
-
},
|
|
4791
|
+
}, _class8);
|
|
2999
4792
|
|
|
3000
4793
|
// src/components/SplineEntity.ts
|
|
3001
4794
|
function polySegmentToBezier(seg) {
|
|
@@ -3048,7 +4841,7 @@ function distSqToSegment(px, py, x1, y1, x2, y2) {
|
|
|
3048
4841
|
const ey = py - cy;
|
|
3049
4842
|
return ex * ex + ey * ey;
|
|
3050
4843
|
}
|
|
3051
|
-
var SplineEntity = (
|
|
4844
|
+
var SplineEntity = (_class9 = class extends _chunkDUYB4GX4js.Entity {
|
|
3052
4845
|
|
|
3053
4846
|
|
|
3054
4847
|
|
|
@@ -3056,23 +4849,23 @@ var SplineEntity = (_class5 = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
3056
4849
|
|
|
3057
4850
|
|
|
3058
4851
|
|
|
3059
|
-
|
|
3060
|
-
|
|
4852
|
+
__init134() {this.offscreen = null}
|
|
4853
|
+
__init135() {this.baked = false}
|
|
3061
4854
|
/** Logical (CSS-pixel) size of the baked bitmap — the blit destination size. */
|
|
3062
|
-
|
|
3063
|
-
|
|
4855
|
+
__init136() {this.bakedWidth = 0}
|
|
4856
|
+
__init137() {this.bakedHeight = 0}
|
|
3064
4857
|
/** Gradient strokes can't be baked to a solid-color bitmap; they render per-frame. */
|
|
3065
4858
|
|
|
3066
4859
|
/** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
|
|
3067
|
-
|
|
4860
|
+
__init138() {this.polylines = null}
|
|
3068
4861
|
/**
|
|
3069
4862
|
* When `true`, the renderer draws a rounded-rect outline of the entity's
|
|
3070
4863
|
* local bounds after painting the curves. Useful for drag feedback and
|
|
3071
4864
|
* debugging hit areas. Defaults to `false`.
|
|
3072
4865
|
*/
|
|
3073
|
-
|
|
4866
|
+
__init139() {this.showBounds = false}
|
|
3074
4867
|
constructor(doc, opts = {}) {
|
|
3075
|
-
super();
|
|
4868
|
+
super();_class9.prototype.__init134.call(this);_class9.prototype.__init135.call(this);_class9.prototype.__init136.call(this);_class9.prototype.__init137.call(this);_class9.prototype.__init138.call(this);_class9.prototype.__init139.call(this);;
|
|
3076
4869
|
this.doc = doc;
|
|
3077
4870
|
this.lineWidth = _nullishCoalesce(opts.lineWidth, () => ( 2));
|
|
3078
4871
|
this.cache = _nullishCoalesce(opts.cache, () => ( true));
|
|
@@ -3083,7 +4876,7 @@ var SplineEntity = (_class5 = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
3083
4876
|
this.width = this.bounds.width;
|
|
3084
4877
|
this.height = this.bounds.height;
|
|
3085
4878
|
const isGradient = (c) => c !== null && !Array.isArray(c);
|
|
3086
|
-
this.containsGradient = (_nullishCoalesce(_optionalChain([this, 'access',
|
|
4879
|
+
this.containsGradient = (_nullishCoalesce(_optionalChain([this, 'access', _140 => _140.doc, 'access', _141 => _141.equations, 'optionalAccess', _142 => _142.some, 'call', _143 => _143((eq) => isGradient(eq.color_rgb))]), () => ( false))) || (_nullishCoalesce(_optionalChain([this, 'access', _144 => _144.doc, 'access', _145 => _145.paths, 'optionalAccess', _146 => _146.some, 'call', _147 => _147((p) => isGradient(p.color_rgb))]), () => ( false)));
|
|
3087
4880
|
this.interactive = true;
|
|
3088
4881
|
}
|
|
3089
4882
|
computeBounds() {
|
|
@@ -3316,14 +5109,14 @@ var SplineEntity = (_class5 = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
3316
5109
|
r.stroke("rgba(0, 150, 255, 0.8)", 2);
|
|
3317
5110
|
}
|
|
3318
5111
|
}
|
|
3319
|
-
},
|
|
5112
|
+
}, _class9);
|
|
3320
5113
|
async function loadSpline(url) {
|
|
3321
5114
|
const res = await fetch(url);
|
|
3322
5115
|
return await res.json();
|
|
3323
5116
|
}
|
|
3324
5117
|
|
|
3325
5118
|
// src/components/Rect.ts
|
|
3326
|
-
var Rect = class extends
|
|
5119
|
+
var Rect = class extends _chunkDUYB4GX4js.Entity {
|
|
3327
5120
|
|
|
3328
5121
|
|
|
3329
5122
|
|
|
@@ -3372,7 +5165,7 @@ var Rect = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
3372
5165
|
};
|
|
3373
5166
|
|
|
3374
5167
|
// src/components/Circle.ts
|
|
3375
|
-
var Circle = class extends
|
|
5168
|
+
var Circle = class extends _chunkDUYB4GX4js.Entity {
|
|
3376
5169
|
|
|
3377
5170
|
|
|
3378
5171
|
|
|
@@ -3432,7 +5225,7 @@ var Circle = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
3432
5225
|
};
|
|
3433
5226
|
|
|
3434
5227
|
// src/components/Group.ts
|
|
3435
|
-
var Group = class extends
|
|
5228
|
+
var Group = class extends _chunkDUYB4GX4js.Entity {
|
|
3436
5229
|
constructor(...children) {
|
|
3437
5230
|
super();
|
|
3438
5231
|
if (children.length > 0) this.add(...children);
|
|
@@ -3448,23 +5241,24 @@ var Group = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
3448
5241
|
|
|
3449
5242
|
|
|
3450
5243
|
var _math = require('@vectojs/math'); _createStarExport(_math);
|
|
3451
|
-
|
|
5244
|
+
|
|
3452
5245
|
|
|
3453
5246
|
// src/tree/DOMPortalEntity.ts
|
|
3454
|
-
var DOMPortalEntity = (
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
5247
|
+
var DOMPortalEntity = (_class10 = class extends _chunkDUYB4GX4js.Entity {
|
|
5248
|
+
|
|
5249
|
+
__init140() {this.isDOMPortal = true}
|
|
5250
|
+
__init141() {this.domListeners = []}
|
|
5251
|
+
__init142() {this.resizeObserver = null}
|
|
5252
|
+
__init143() {this.domBound = false}
|
|
5253
|
+
__init144() {this.cachedWidth = 100}
|
|
5254
|
+
__init145() {this.cachedHeight = 100}
|
|
5255
|
+
__init146() {this.lastWidth = ""}
|
|
5256
|
+
__init147() {this.lastHeight = ""}
|
|
5257
|
+
__init148() {this.lastTransform = ""}
|
|
5258
|
+
__init149() {this.lastZIndex = ""}
|
|
5259
|
+
__init150() {this.lastOpacity = ""}
|
|
3466
5260
|
constructor(domElement, width, height, id) {
|
|
3467
|
-
super(id);
|
|
5261
|
+
super(id);_class10.prototype.__init140.call(this);_class10.prototype.__init141.call(this);_class10.prototype.__init142.call(this);_class10.prototype.__init143.call(this);_class10.prototype.__init144.call(this);_class10.prototype.__init145.call(this);_class10.prototype.__init146.call(this);_class10.prototype.__init147.call(this);_class10.prototype.__init148.call(this);_class10.prototype.__init149.call(this);_class10.prototype.__init150.call(this);;
|
|
3468
5262
|
this.domElement = domElement;
|
|
3469
5263
|
this.width = _nullishCoalesce(width, () => ( 0));
|
|
3470
5264
|
this.height = _nullishCoalesce(height, () => ( 0));
|
|
@@ -3474,50 +5268,84 @@ var DOMPortalEntity = (_class6 = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
3474
5268
|
this.domElement.style.pointerEvents = "auto";
|
|
3475
5269
|
this.cachedWidth = parseFloat(domElement.style.width) || domElement.offsetWidth || 100;
|
|
3476
5270
|
this.cachedHeight = parseFloat(domElement.style.height) || domElement.offsetHeight || 100;
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
this.
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
5271
|
+
this.attachDOMBindings();
|
|
5272
|
+
}
|
|
5273
|
+
}
|
|
5274
|
+
/**
|
|
5275
|
+
* Attach the ResizeObserver + DOM event listeners that bridge native DOM
|
|
5276
|
+
* events into the Vecto event system. Idempotent: safe to call every frame
|
|
5277
|
+
* from the projection path (`Scene.syncPortalGeometry`), which is what lets a
|
|
5278
|
+
* portal survive a `scene.remove()` -> re-add cycle — {@link releaseDOMBindings}
|
|
5279
|
+
* tears these down on removal and the next projection re-attaches them.
|
|
5280
|
+
*/
|
|
5281
|
+
attachDOMBindings() {
|
|
5282
|
+
if (this.domBound || typeof window === "undefined") return;
|
|
5283
|
+
this.domBound = true;
|
|
5284
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
5285
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
5286
|
+
for (const entry of entries) {
|
|
5287
|
+
this.cachedWidth = entry.contentRect.width || entry.target.offsetWidth;
|
|
5288
|
+
this.cachedHeight = entry.contentRect.height || entry.target.offsetHeight;
|
|
5289
|
+
}
|
|
5290
|
+
});
|
|
5291
|
+
this.resizeObserver.observe(this.domElement);
|
|
5292
|
+
}
|
|
5293
|
+
const events = [
|
|
5294
|
+
"click",
|
|
5295
|
+
"pointerdown",
|
|
5296
|
+
"pointerup",
|
|
5297
|
+
"pointercancel",
|
|
5298
|
+
"pointermove",
|
|
5299
|
+
"wheel"
|
|
5300
|
+
];
|
|
5301
|
+
for (const type of events) {
|
|
5302
|
+
const handler = (e) => {
|
|
5303
|
+
this.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)(type, this, e));
|
|
5304
|
+
};
|
|
5305
|
+
this.domElement.addEventListener(type, handler);
|
|
5306
|
+
this.domListeners.push({ type, handler, capture: false });
|
|
5307
|
+
}
|
|
5308
|
+
const hoverEvents = [
|
|
5309
|
+
{ native: "mouseenter", vecto: "hover" },
|
|
5310
|
+
{ native: "mouseleave", vecto: "pointerleave" }
|
|
5311
|
+
];
|
|
5312
|
+
for (const { native, vecto } of hoverEvents) {
|
|
5313
|
+
const handler = (e) => {
|
|
5314
|
+
this.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)(vecto, this, e, false));
|
|
5315
|
+
};
|
|
5316
|
+
this.domElement.addEventListener(native, handler);
|
|
5317
|
+
this.domListeners.push({ type: native, handler, capture: false });
|
|
5318
|
+
}
|
|
5319
|
+
const focusEvents = ["focus", "blur"];
|
|
5320
|
+
for (const type of focusEvents) {
|
|
5321
|
+
const handler = (e) => {
|
|
5322
|
+
this.dispatchEvent(new (0, _chunkDUYB4GX4js.VectoJSEvent)(type, this, e, true));
|
|
5323
|
+
};
|
|
5324
|
+
this.domElement.addEventListener(type, handler, true);
|
|
5325
|
+
this.domListeners.push({ type, handler, capture: true });
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
/**
|
|
5329
|
+
* Disconnect the ResizeObserver and remove the DOM event listeners, without
|
|
5330
|
+
* touching the scene graph or the element's DOM parentage. Called on the
|
|
5331
|
+
* `scene.remove()` path so a detached portal doesn't leak an observer that
|
|
5332
|
+
* keeps its element alive and firing; the element itself is removed from the
|
|
5333
|
+
* document by the scene's portal pruning. Re-attached lazily on the next
|
|
5334
|
+
* projection frame via {@link attachDOMBindings}.
|
|
5335
|
+
*/
|
|
5336
|
+
releaseDOMBindings() {
|
|
5337
|
+
if (!this.domBound) return;
|
|
5338
|
+
this.domBound = false;
|
|
5339
|
+
if (this.resizeObserver) {
|
|
5340
|
+
this.resizeObserver.disconnect();
|
|
5341
|
+
this.resizeObserver = null;
|
|
5342
|
+
}
|
|
5343
|
+
if (this.domElement) {
|
|
5344
|
+
for (const { type, handler, capture } of this.domListeners) {
|
|
5345
|
+
this.domElement.removeEventListener(type, handler, capture);
|
|
3519
5346
|
}
|
|
3520
5347
|
}
|
|
5348
|
+
this.domListeners = [];
|
|
3521
5349
|
}
|
|
3522
5350
|
isPointInside(globalX, globalY) {
|
|
3523
5351
|
const w = this.width > 0 ? this.width : this.cachedWidth;
|
|
@@ -3533,26 +5361,17 @@ var DOMPortalEntity = (_class6 = class extends _chunkJ6NHSFIEjs.Entity {
|
|
|
3533
5361
|
render() {
|
|
3534
5362
|
}
|
|
3535
5363
|
destroy() {
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
this.resizeObserver = null;
|
|
3540
|
-
}
|
|
3541
|
-
if (this.domElement) {
|
|
3542
|
-
for (const { type, handler, capture } of this.domListeners) {
|
|
3543
|
-
this.domElement.removeEventListener(type, handler, capture);
|
|
3544
|
-
}
|
|
3545
|
-
this.domListeners = [];
|
|
3546
|
-
this.domElement.remove();
|
|
3547
|
-
}
|
|
5364
|
+
this.releaseDOMBindings();
|
|
5365
|
+
if (typeof window !== "undefined" && this.domElement) {
|
|
5366
|
+
this.domElement.remove();
|
|
3548
5367
|
}
|
|
3549
5368
|
super.destroy();
|
|
3550
5369
|
}
|
|
3551
|
-
},
|
|
5370
|
+
}, _class10);
|
|
3552
5371
|
|
|
3553
5372
|
// src/index.ts
|
|
3554
|
-
Scene.registerWebGLPointRendererCreator(
|
|
3555
|
-
Scene.registerWebGPUParticleSystemManager(
|
|
5373
|
+
Scene.registerWebGLPointRendererCreator(_chunkIYPLG4Z4js.createWebGLPointRenderer);
|
|
5374
|
+
Scene.registerWebGPUParticleSystemManager(_chunkIYPLG4Z4js.WebGPUParticleSystemManager);
|
|
3556
5375
|
|
|
3557
5376
|
|
|
3558
5377
|
|
|
@@ -3587,4 +5406,4 @@ Scene.registerWebGPUParticleSystemManager(_chunkBEUIB3U7js.WebGPUParticleSystemM
|
|
|
3587
5406
|
|
|
3588
5407
|
|
|
3589
5408
|
|
|
3590
|
-
exports.CanvasRenderer =
|
|
5409
|
+
exports.CanvasRenderer = _chunkIYPLG4Z4js.CanvasRenderer; exports.Circle = Circle; exports.ComputeParticleEntity = ComputeParticleEntity; exports.DOMPortalEntity = DOMPortalEntity; exports.Entity = _chunkDUYB4GX4js.Entity; exports.GridTextEntity = GridTextEntity; exports.Group = Group; exports.MSDFTextEntity = _chunkDUYB4GX4js.MSDFTextEntity; exports.PARTICLE_OFFSET_LIFE = PARTICLE_OFFSET_LIFE; exports.PARTICLE_OFFSET_ORIGIN_X = PARTICLE_OFFSET_ORIGIN_X; exports.PARTICLE_OFFSET_ORIGIN_Y = PARTICLE_OFFSET_ORIGIN_Y; exports.PARTICLE_OFFSET_POSITION_X = PARTICLE_OFFSET_POSITION_X; exports.PARTICLE_OFFSET_POSITION_Y = PARTICLE_OFFSET_POSITION_Y; exports.PARTICLE_OFFSET_SIZE = PARTICLE_OFFSET_SIZE; exports.PARTICLE_OFFSET_VELOCITY_X = PARTICLE_OFFSET_VELOCITY_X; exports.PARTICLE_OFFSET_VELOCITY_Y = PARTICLE_OFFSET_VELOCITY_Y; exports.PARTICLE_STRIDE_FLOATS = PARTICLE_STRIDE_FLOATS; exports.REDUCED_MOTION_FPS = REDUCED_MOTION_FPS; exports.Rect = Rect; exports.SVGEntity = _chunkDUYB4GX4js.SVGEntity; exports.SVGRenderer = _chunkIYPLG4Z4js.SVGRenderer; exports.Scene = Scene; exports.SplineEntity = SplineEntity; exports.TextEntity = TextEntity; exports.TextRasterCache = _chunkIYPLG4Z4js.TextRasterCache; exports.VectoJSEvent = _chunkDUYB4GX4js.VectoJSEvent; exports.WebGPUParticleSystemManager = _chunkIYPLG4Z4js.WebGPUParticleSystemManager; exports.createWebGLPointRenderer = _chunkIYPLG4Z4js.createWebGLPointRenderer; exports.isSafeUrl = _chunkIYPLG4Z4js.isSafeUrl; exports.loadSpline = loadSpline; exports.parseColorToRGBA = _chunkIYPLG4Z4js.parseColorToRGBA; exports.polySegmentToBezier = polySegmentToBezier; exports.sanitizeUrl = _chunkIYPLG4Z4js.sanitizeUrl;
|