murow 0.0.71 → 0.0.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +7 -5
  2. package/dist/cjs/renderer/{base-2d-renderer.js → base/renderer-2d.js} +1 -1
  3. package/dist/cjs/renderer/{base-3d-renderer.js → base/renderer-3d.js} +1 -1
  4. package/dist/cjs/renderer/gltf/helpers.js +1 -0
  5. package/dist/cjs/renderer/gltf/parser.js +1 -0
  6. package/dist/cjs/renderer/gltf/skeletal-animation.js +1 -0
  7. package/dist/cjs/renderer/gltf/skin-parser.js +1 -0
  8. package/dist/cjs/renderer/index.js +1 -1
  9. package/dist/cjs/renderer/math.js +1 -0
  10. package/dist/cjs/renderer/prefab-bucket/concrete.js +1 -0
  11. package/dist/cjs/renderer/prefab-bucket/index.js +1 -0
  12. package/dist/cjs/renderer/prefab-bucket/parsers.js +1 -0
  13. package/dist/cjs/renderer/prefab-bucket/specs.js +1 -0
  14. package/dist/cjs/renderer/spritesheet/helpers.js +1 -0
  15. package/dist/cjs/renderer/spritesheet/parser.js +1 -0
  16. package/dist/cjs/renderer/types.js +1 -1
  17. package/dist/esm/renderer/base/renderer-2d.js +1 -0
  18. package/dist/esm/renderer/base/renderer-3d.js +1 -0
  19. package/dist/esm/renderer/gltf/helpers.js +1 -0
  20. package/dist/esm/renderer/gltf/parser.js +1 -0
  21. package/dist/esm/renderer/gltf/skeletal-animation.js +1 -0
  22. package/dist/esm/renderer/gltf/skin-parser.js +1 -0
  23. package/dist/esm/renderer/index.js +1 -1
  24. package/dist/esm/renderer/math.js +1 -0
  25. package/dist/esm/renderer/prefab-bucket/concrete.js +1 -0
  26. package/dist/esm/renderer/prefab-bucket/index.js +1 -0
  27. package/dist/esm/renderer/prefab-bucket/parsers.js +1 -0
  28. package/dist/esm/renderer/prefab-bucket/specs.js +0 -0
  29. package/dist/esm/renderer/spritesheet/helpers.js +1 -0
  30. package/dist/esm/renderer/spritesheet/parser.js +1 -0
  31. package/dist/types/renderer/{base-2d-renderer.d.ts → base/renderer-2d.d.ts} +2 -2
  32. package/dist/types/renderer/{base-3d-renderer.d.ts → base/renderer-3d.d.ts} +2 -2
  33. package/dist/types/renderer/{base-renderer.d.ts → base/renderer.d.ts} +1 -1
  34. package/dist/types/renderer/gltf/helpers.d.ts +43 -0
  35. package/dist/types/renderer/gltf/parser.d.ts +49 -0
  36. package/dist/{webgpu/types/3d → types/renderer/gltf}/skeletal-animation.d.ts +8 -2
  37. package/dist/{webgpu/types/3d/gltf-skin-parser.d.ts → types/renderer/gltf/skin-parser.d.ts} +11 -5
  38. package/dist/types/renderer/index.d.ts +14 -3
  39. package/dist/types/renderer/prefab-bucket/concrete.d.ts +55 -0
  40. package/dist/types/renderer/prefab-bucket/index.d.ts +113 -0
  41. package/dist/types/renderer/prefab-bucket/parsers.d.ts +8 -0
  42. package/dist/types/renderer/prefab-bucket/specs.d.ts +166 -0
  43. package/dist/types/renderer/spritesheet/helpers.d.ts +38 -0
  44. package/dist/types/renderer/spritesheet/parser.d.ts +21 -0
  45. package/dist/types/renderer/types.d.ts +17 -7
  46. package/dist/webgpu/cjs/index.js +479 -1082
  47. package/dist/webgpu/esm/index.js +485 -1079
  48. package/dist/webgpu/types/2d/renderer.d.ts +34 -3
  49. package/dist/webgpu/types/2d/sprite-accessor.d.ts +1 -1
  50. package/dist/webgpu/types/3d/clip-resync-coordinator.d.ts +20 -0
  51. package/dist/webgpu/types/3d/renderer.d.ts +64 -14
  52. package/dist/webgpu/types/3d/skeletal-animation-compute/index.d.ts +1 -1
  53. package/dist/webgpu/types/3d/skeletal-animation-compute/kernel.d.ts +19 -2
  54. package/dist/webgpu/types/3d/skeletal-animation-compute/packer.d.ts +1 -1
  55. package/dist/webgpu/types/camera/camera-2d.d.ts +1 -1
  56. package/dist/webgpu/types/camera/camera-3d.d.ts +1 -1
  57. package/dist/webgpu/types/index.d.ts +15 -12
  58. package/dist/webgpu/types/particle/emitter.d.ts +1 -1
  59. package/dist/webgpu/types/spritesheet/spritesheet.d.ts +5 -34
  60. package/package.json +1 -1
  61. package/dist/esm/renderer/base-2d-renderer.js +0 -1
  62. package/dist/esm/renderer/base-3d-renderer.js +0 -1
  63. /package/dist/cjs/renderer/{base-renderer.js → base/renderer.js} +0 -0
  64. /package/dist/esm/renderer/{base-renderer.js → base/renderer.js} +0 -0
  65. /package/dist/{webgpu/types/core → types/renderer}/math.d.ts +0 -0
@@ -95,7 +95,6 @@ __export(src_exports, {
95
95
  STATIC_OFFSET_UV_MAX_Y: () => STATIC_OFFSET_UV_MAX_Y,
96
96
  STATIC_OFFSET_UV_MIN_X: () => STATIC_OFFSET_UV_MIN_X,
97
97
  STATIC_OFFSET_UV_MIN_Y: () => STATIC_OFFSET_UV_MIN_Y,
98
- SkeletalAnimation: () => SkeletalAnimation,
99
98
  SkinnedStaticMesh: () => SkinnedStaticMesh,
100
99
  SpriteAccessor: () => SpriteAccessor,
101
100
  SpriteUniforms: () => SpriteUniforms,
@@ -105,14 +104,11 @@ __export(src_exports, {
105
104
  StaticSprite: () => StaticSprite,
106
105
  WebGPU2DRenderer: () => WebGPU2DRenderer,
107
106
  WebGPU3DRenderer: () => WebGPU3DRenderer,
108
- computeGridUVs: () => computeGridUVs,
109
- computeTexturePackerUVs: () => computeTexturePackerUVs,
110
107
  createGeometryDataLayout: () => createGeometryDataLayout,
111
108
  createTextureFromBitmap: () => createTextureFromBitmap,
112
109
  d: () => d,
113
110
  getFieldFloats: () => getFieldFloats,
114
111
  inverseLerp: () => inverseLerp,
115
- loadImage: () => loadImage,
116
112
  remap: () => remap,
117
113
  resolveBuiltInGeometry: () => resolveBuiltInGeometry,
118
114
  rotate2d: () => rotate2d,
@@ -133,7 +129,7 @@ var std = { ..._std };
133
129
  var import_typegpu6 = __toESM(require("typegpu"), 1);
134
130
  var d4 = __toESM(require("typegpu/data"), 1);
135
131
  var import_free_list2 = require("murow/core/free-list");
136
- var import_base_2d_renderer = require("murow/renderer/base-2d-renderer");
132
+ var import_renderer2 = require("murow/renderer");
137
133
 
138
134
  // src/core/constants.ts
139
135
  var DYNAMIC_OFFSET_PREV_X = 0;
@@ -652,6 +648,7 @@ function createSpriteFragment(_spriteLayout, textureLayout) {
652
648
  }
653
649
 
654
650
  // src/spritesheet/spritesheet.ts
651
+ var import_renderer = require("murow/renderer");
655
652
  var Spritesheet = class {
656
653
  constructor(id, texture, textureView, sampler, uvs, width, height) {
657
654
  this.id = id;
@@ -676,41 +673,6 @@ var Spritesheet = class {
676
673
  return this._height;
677
674
  }
678
675
  };
679
- function computeGridUVs(imageWidth, imageHeight, frameWidth, frameHeight) {
680
- const cols = Math.floor(imageWidth / frameWidth);
681
- const rows = Math.floor(imageHeight / frameHeight);
682
- const uvs = [];
683
- for (let row = 0; row < rows; row++) {
684
- for (let col = 0; col < cols; col++) {
685
- uvs.push({
686
- minX: col * frameWidth / imageWidth,
687
- minY: row * frameHeight / imageHeight,
688
- maxX: (col + 1) * frameWidth / imageWidth,
689
- maxY: (row + 1) * frameHeight / imageHeight
690
- });
691
- }
692
- }
693
- return uvs;
694
- }
695
- function computeTexturePackerUVs(data) {
696
- const { w, h } = data.meta.size;
697
- const uvs = [];
698
- for (const key of Object.keys(data.frames)) {
699
- const frame = data.frames[key].frame;
700
- uvs.push({
701
- minX: frame.x / w,
702
- minY: frame.y / h,
703
- maxX: (frame.x + frame.w) / w,
704
- maxY: (frame.y + frame.h) / h
705
- });
706
- }
707
- return uvs;
708
- }
709
- async function loadImage(url) {
710
- const response = await fetch(url);
711
- const blob = await response.blob();
712
- return createImageBitmap(blob);
713
- }
714
676
  function createTextureFromBitmap(device, bitmap) {
715
677
  const texture = device.createTexture({
716
678
  size: [bitmap.width, bitmap.height, 1],
@@ -725,6 +687,9 @@ function createTextureFromBitmap(device, bitmap) {
725
687
  return { texture, view: texture.createView() };
726
688
  }
727
689
 
690
+ // src/2d/renderer.ts
691
+ var import_renderer3 = require("murow/renderer");
692
+
728
693
  // src/geometry/geometry-builder.ts
729
694
  var import_typegpu2 = __toESM(require("typegpu"), 1);
730
695
 
@@ -2203,9 +2168,23 @@ var ComputeBuilder = class {
2203
2168
  };
2204
2169
 
2205
2170
  // src/2d/renderer.ts
2206
- var WebGPU2DRenderer = class extends import_base_2d_renderer.Base2DRenderer {
2171
+ var prefab2DHandles = /* @__PURE__ */ new WeakMap();
2172
+ function isPrefab2D(value) {
2173
+ return value.type === "spritesheet";
2174
+ }
2175
+ function resolveSpritePrefabHandle(prefab) {
2176
+ const h = prefab2DHandles.get(prefab);
2177
+ if (!h) {
2178
+ throw new Error(
2179
+ `Prefab '${prefab.id}' has no GPU handle \u2014 has the renderer's init() been called with this bucket?`
2180
+ );
2181
+ }
2182
+ return h;
2183
+ }
2184
+ var WebGPU2DRenderer = class extends import_renderer2.Base2DRenderer {
2207
2185
  constructor(canvas, options) {
2208
- super(canvas, options);
2186
+ const resolvedMaxSprites = options.maxSprites ?? options.maxInstances ?? 1024;
2187
+ super(canvas, { ...options, maxSprites: resolvedMaxSprites });
2209
2188
  this.staticDirty = false;
2210
2189
  // Per-sheet bind groups
2211
2190
  this.sheetBindGroups = /* @__PURE__ */ new Map();
@@ -2214,12 +2193,13 @@ var WebGPU2DRenderer = class extends import_base_2d_renderer.Base2DRenderer {
2214
2193
  this.uniformData = new Float32Array(20);
2215
2194
  this.resizeObserver = null;
2216
2195
  this.resizeCallbacks = [];
2196
+ this._prefabs = options.prefabs ?? null;
2217
2197
  this.camera = new Camera2D(canvas.width || 800, canvas.height || 600);
2218
- this.freeList = new import_free_list2.FreeList(options.maxSprites);
2219
- this.batcher = new import_sparse_batcher.SparseBatcher(options.maxSprites);
2220
- this.dynamicData = new Float32Array(options.maxSprites * DYNAMIC_FLOATS_PER_SPRITE);
2221
- this.staticData = new Float32Array(options.maxSprites * STATIC_FLOATS_PER_SPRITE);
2222
- this.slotIndexData = new Uint32Array(options.maxSprites);
2198
+ this.freeList = new import_free_list2.FreeList(resolvedMaxSprites);
2199
+ this.batcher = new import_sparse_batcher.SparseBatcher(resolvedMaxSprites);
2200
+ this.dynamicData = new Float32Array(resolvedMaxSprites * DYNAMIC_FLOATS_PER_SPRITE);
2201
+ this.staticData = new Float32Array(resolvedMaxSprites * STATIC_FLOATS_PER_SPRITE);
2202
+ this.slotIndexData = new Uint32Array(resolvedMaxSprites);
2223
2203
  }
2224
2204
  get device() {
2225
2205
  return this._device;
@@ -2273,9 +2253,25 @@ var WebGPU2DRenderer = class extends import_base_2d_renderer.Base2DRenderer {
2273
2253
  this.rawStaticBuffer = this.root.unwrap(this.staticBuffer);
2274
2254
  this.rawSlotIndexBuffer = this.root.unwrap(this.slotIndexBuffer);
2275
2255
  this.rawUniformBuffer = this.root.unwrap(this.uniformBuffer);
2256
+ if (this._prefabs) {
2257
+ this.uploadPrefabBucket(this._prefabs);
2258
+ }
2276
2259
  this.setupResizeObserver();
2277
2260
  this._initialized = true;
2278
2261
  }
2262
+ /**
2263
+ * Upload every prefab in the bucket to the GPU and stash the resulting
2264
+ * SpritesheetHandle on each prefab so `bucket.get(id)` returns something
2265
+ * usable as a sprite source.
2266
+ */
2267
+ uploadPrefabBucket(bucket) {
2268
+ for (const prefab of bucket.entries()) {
2269
+ if (prefab.type === "spritesheet") {
2270
+ const handle = this.uploadParsedSpritesheet(prefab.parsed);
2271
+ prefab2DHandles.set(prefab, handle);
2272
+ }
2273
+ }
2274
+ }
2279
2275
  setupResizeObserver() {
2280
2276
  const supportsDevicePixelBox = (() => {
2281
2277
  try {
@@ -2329,24 +2325,22 @@ var WebGPU2DRenderer = class extends import_base_2d_renderer.Base2DRenderer {
2329
2325
  this.resizeCallbacks.push(callback);
2330
2326
  }
2331
2327
  async loadSpritesheet(source) {
2332
- const bitmap = await loadImage(source.image);
2333
- const { texture, view } = createTextureFromBitmap(this._device, bitmap);
2328
+ const parsed = await (0, import_renderer3.parseSpritesheet)(source);
2329
+ return this.uploadParsedSpritesheet(parsed);
2330
+ }
2331
+ /**
2332
+ * Upload a previously-parsed spritesheet to the GPU. Returns a SpritesheetHandle.
2333
+ * Splitting parse (CPU) from upload (GPU) lets callers parse spritesheets in parallel
2334
+ * before a renderer exists.
2335
+ */
2336
+ uploadParsedSpritesheet(parsed) {
2337
+ const { texture, view } = createTextureFromBitmap(this._device, parsed.bitmap);
2334
2338
  const sampler = this._device.createSampler({
2335
2339
  magFilter: "nearest",
2336
2340
  minFilter: "nearest"
2337
2341
  });
2338
- let uvs;
2339
- if (source.data) {
2340
- const resp = await fetch(source.data);
2341
- const json = await resp.json();
2342
- uvs = computeTexturePackerUVs(json);
2343
- } else if (source.frameWidth && source.frameHeight) {
2344
- uvs = computeGridUVs(bitmap.width, bitmap.height, source.frameWidth, source.frameHeight);
2345
- } else {
2346
- uvs = [{ minX: 0, minY: 0, maxX: 1, maxY: 1 }];
2347
- }
2348
2342
  const id = this.nextSheetId++;
2349
- const sheet = new Spritesheet(id, texture, view, sampler, uvs, bitmap.width, bitmap.height);
2343
+ const sheet = new Spritesheet(id, texture, view, sampler, parsed.uvs, parsed.width, parsed.height);
2350
2344
  this.sheets.set(id, sheet);
2351
2345
  const bindGroup = this._device.createBindGroup({
2352
2346
  layout: this.rawTextureLayout,
@@ -2364,18 +2358,20 @@ var WebGPU2DRenderer = class extends import_base_2d_renderer.Base2DRenderer {
2364
2358
  throw new Error(`Max sprites (${this.maxSprites}) reached`);
2365
2359
  const dynBase = slot * DYNAMIC_FLOATS_PER_SPRITE;
2366
2360
  const statBase = slot * STATIC_FLOATS_PER_SPRITE;
2367
- const x = opts.x ?? 0;
2368
- const y = opts.y ?? 0;
2369
- this.dynamicData[dynBase + DYNAMIC_OFFSET_PREV_X] = x;
2370
- this.dynamicData[dynBase + DYNAMIC_OFFSET_PREV_Y] = y;
2371
- this.dynamicData[dynBase + DYNAMIC_OFFSET_CURR_X] = x;
2372
- this.dynamicData[dynBase + DYNAMIC_OFFSET_CURR_Y] = y;
2361
+ const sheet = isPrefab2D(opts.sheet) ? resolveSpritePrefabHandle(opts.sheet) : opts.sheet;
2362
+ const [px, py] = opts.position ?? [0, 0];
2363
+ this.dynamicData[dynBase + DYNAMIC_OFFSET_PREV_X] = px;
2364
+ this.dynamicData[dynBase + DYNAMIC_OFFSET_PREV_Y] = py;
2365
+ this.dynamicData[dynBase + DYNAMIC_OFFSET_CURR_X] = px;
2366
+ this.dynamicData[dynBase + DYNAMIC_OFFSET_CURR_Y] = py;
2373
2367
  const rotation = opts.rotation ?? 0;
2374
2368
  this.dynamicData[dynBase + DYNAMIC_OFFSET_PREV_ROTATION] = rotation;
2375
2369
  this.dynamicData[dynBase + DYNAMIC_OFFSET_CURR_ROTATION] = rotation;
2376
- this.staticData[statBase + STATIC_OFFSET_SCALE_X] = opts.scaleX ?? 1;
2377
- this.staticData[statBase + STATIC_OFFSET_SCALE_Y] = opts.scaleY ?? 1;
2378
- const uv = opts.sheet.getUV(opts.sprite ?? 0);
2370
+ const s = opts.scale;
2371
+ const [sx, sy] = typeof s === "number" ? [s, s] : s ?? [1, 1];
2372
+ this.staticData[statBase + STATIC_OFFSET_SCALE_X] = sx;
2373
+ this.staticData[statBase + STATIC_OFFSET_SCALE_Y] = sy;
2374
+ const uv = sheet.getUV(opts.sprite ?? 0);
2379
2375
  this.staticData[statBase + STATIC_OFFSET_UV_MIN_X] = uv.minX;
2380
2376
  this.staticData[statBase + STATIC_OFFSET_UV_MIN_Y] = uv.minY;
2381
2377
  this.staticData[statBase + STATIC_OFFSET_UV_MAX_X] = uv.maxX;
@@ -2390,12 +2386,12 @@ var WebGPU2DRenderer = class extends import_base_2d_renderer.Base2DRenderer {
2390
2386
  this.staticData[statBase + STATIC_OFFSET_TINT_B] = tint[2];
2391
2387
  this.staticData[statBase + STATIC_OFFSET_TINT_A] = tint[3];
2392
2388
  this.staticDirty = true;
2393
- this.batcher.add(opts.layer ?? 0, opts.sheet.id, slot);
2389
+ this.batcher.add(opts.layer ?? 0, sheet.id, slot);
2394
2390
  return new SpriteAccessor(
2395
2391
  this.dynamicData,
2396
2392
  this.staticData,
2397
2393
  slot,
2398
- opts.sheet.id,
2394
+ sheet.id,
2399
2395
  () => {
2400
2396
  this.staticDirty = true;
2401
2397
  }
@@ -2517,9 +2513,112 @@ var WebGPU2DRenderer = class extends import_base_2d_renderer.Base2DRenderer {
2517
2513
  }
2518
2514
  };
2519
2515
 
2516
+ // src/2d/animation.ts
2517
+ var AnimationController = class {
2518
+ constructor() {
2519
+ this.clips = [];
2520
+ this.clipsByName = /* @__PURE__ */ new Map();
2521
+ }
2522
+ /**
2523
+ * Register an animation clip. Returns the clip ID.
2524
+ */
2525
+ loadClip(config) {
2526
+ const id = this.clips.length;
2527
+ const clip = {
2528
+ id,
2529
+ name: config.name,
2530
+ frames: new Uint16Array(config.frames),
2531
+ durations: new Float32Array(config.durations),
2532
+ frameCount: config.frames.length,
2533
+ totalDuration: config.durations.reduce((sum, d7) => sum + d7, 0),
2534
+ loop: config.loop
2535
+ };
2536
+ this.clips.push(clip);
2537
+ this.clipsByName.set(config.name, id);
2538
+ return id;
2539
+ }
2540
+ /**
2541
+ * Get a clip by name.
2542
+ */
2543
+ getClipId(name) {
2544
+ const id = this.clipsByName.get(name);
2545
+ if (id === void 0)
2546
+ throw new Error(`Animation clip "${name}" not found`);
2547
+ return id;
2548
+ }
2549
+ /**
2550
+ * Get a clip by ID.
2551
+ */
2552
+ getClip(id) {
2553
+ return this.clips[id];
2554
+ }
2555
+ /**
2556
+ * Create a new animation state for an entity. This is the per-entity data
2557
+ * that tracks playback progress. Store it however you like (flat array, component, etc).
2558
+ */
2559
+ createState(clipId, speed = 1, playing = true) {
2560
+ return { clipId, frame: 0, time: 0, speed, playing };
2561
+ }
2562
+ /**
2563
+ * Advance an animation state by deltaTime (in seconds).
2564
+ * Returns the current sprite frame ID, or -1 if not playing.
2565
+ * Zero allocations.
2566
+ */
2567
+ update(state, deltaTime) {
2568
+ if (!state.playing) {
2569
+ return this.clips[state.clipId].frames[state.frame];
2570
+ }
2571
+ const clip = this.clips[state.clipId];
2572
+ state.time += deltaTime * state.speed * 1e3;
2573
+ while (state.time >= clip.durations[state.frame]) {
2574
+ state.time -= clip.durations[state.frame];
2575
+ state.frame++;
2576
+ if (state.frame >= clip.frameCount) {
2577
+ if (clip.loop) {
2578
+ state.frame = 0;
2579
+ } else {
2580
+ state.frame = clip.frameCount - 1;
2581
+ state.playing = false;
2582
+ break;
2583
+ }
2584
+ }
2585
+ }
2586
+ return clip.frames[state.frame];
2587
+ }
2588
+ /**
2589
+ * Play a different clip on an existing state. Resets frame and time.
2590
+ */
2591
+ play(state, clipId, speed) {
2592
+ state.clipId = clipId;
2593
+ state.frame = 0;
2594
+ state.time = 0;
2595
+ state.playing = true;
2596
+ if (speed !== void 0)
2597
+ state.speed = speed;
2598
+ }
2599
+ /**
2600
+ * Stop playback.
2601
+ */
2602
+ stop(state) {
2603
+ state.playing = false;
2604
+ }
2605
+ /**
2606
+ * Resume playback.
2607
+ */
2608
+ resume(state) {
2609
+ state.playing = true;
2610
+ }
2611
+ /**
2612
+ * Number of loaded clips.
2613
+ */
2614
+ get clipCount() {
2615
+ return this.clips.length;
2616
+ }
2617
+ };
2618
+
2520
2619
  // src/3d/renderer.ts
2521
2620
  var import_typegpu9 = __toESM(require("typegpu"), 1);
2522
- var import_base_3d_renderer = require("murow/renderer/base-3d-renderer");
2621
+ var import_renderer4 = require("murow/renderer");
2523
2622
  var import_free_list3 = require("murow/core/free-list");
2524
2623
  var import_sparse_batcher2 = require("murow/core/sparse-batcher");
2525
2624
 
@@ -3164,661 +3263,8 @@ function createSkinnedMeshVertex(layout) {
3164
3263
  });
3165
3264
  }
3166
3265
 
3167
- // src/core/math.ts
3168
- function mat4IdentityNew() {
3169
- const m = new Float32Array(16);
3170
- m[0] = 1;
3171
- m[5] = 1;
3172
- m[10] = 1;
3173
- m[15] = 1;
3174
- return m;
3175
- }
3176
- function trsToMat4(tx, ty, tz, qx, qy, qz, qw, sx, sy, sz, dst, offset) {
3177
- const xx = qx * qx, yy = qy * qy, zz = qz * qz;
3178
- const xy = qx * qy, xz = qx * qz, yz = qy * qz;
3179
- const wx = qw * qx, wy = qw * qy, wz = qw * qz;
3180
- dst[offset] = (1 - 2 * (yy + zz)) * sx;
3181
- dst[offset + 1] = 2 * (xy + wz) * sx;
3182
- dst[offset + 2] = 2 * (xz - wy) * sx;
3183
- dst[offset + 3] = 0;
3184
- dst[offset + 4] = 2 * (xy - wz) * sy;
3185
- dst[offset + 5] = (1 - 2 * (xx + zz)) * sy;
3186
- dst[offset + 6] = 2 * (yz + wx) * sy;
3187
- dst[offset + 7] = 0;
3188
- dst[offset + 8] = 2 * (xz + wy) * sz;
3189
- dst[offset + 9] = 2 * (yz - wx) * sz;
3190
- dst[offset + 10] = (1 - 2 * (xx + yy)) * sz;
3191
- dst[offset + 11] = 0;
3192
- dst[offset + 12] = tx;
3193
- dst[offset + 13] = ty;
3194
- dst[offset + 14] = tz;
3195
- dst[offset + 15] = 1;
3196
- }
3197
- function nodeToMat4(node) {
3198
- const m = new Float32Array(16);
3199
- if (node.matrix) {
3200
- m.set(node.matrix);
3201
- return m;
3202
- }
3203
- const t = node.translation ?? [0, 0, 0];
3204
- const r = node.rotation ?? [0, 0, 0, 1];
3205
- const s = node.scale ?? [1, 1, 1];
3206
- trsToMat4(t[0], t[1], t[2], r[0], r[1], r[2], r[3], s[0], s[1], s[2], m, 0);
3207
- return m;
3208
- }
3209
- var _mulTemp = new Float32Array(16);
3210
- function mat4Mul(a, aO, b, bO, dst, dO) {
3211
- for (let i = 0; i < 4; i++) {
3212
- for (let j = 0; j < 4; j++) {
3213
- _mulTemp[j * 4 + i] = a[aO + i] * b[bO + j * 4] + a[aO + 4 + i] * b[bO + j * 4 + 1] + a[aO + 8 + i] * b[bO + j * 4 + 2] + a[aO + 12 + i] * b[bO + j * 4 + 3];
3214
- }
3215
- }
3216
- dst.set(_mulTemp, dO);
3217
- }
3218
- function mat4MulNew(a, b) {
3219
- const o = new Float32Array(16);
3220
- for (let i = 0; i < 4; i++) {
3221
- for (let j = 0; j < 4; j++) {
3222
- o[j * 4 + i] = a[i] * b[j * 4] + a[4 + i] * b[j * 4 + 1] + a[8 + i] * b[j * 4 + 2] + a[12 + i] * b[j * 4 + 3];
3223
- }
3224
- }
3225
- return o;
3226
- }
3227
-
3228
- // src/3d/gltf-skin-parser.ts
3229
- function parseSkin(gltf, skinIndex, getAccessorData) {
3230
- const skin = gltf.skins[skinIndex];
3231
- const joints = skin.joints;
3232
- const jointCount = joints.length;
3233
- const ibmAccess = getAccessorData(skin.inverseBindMatrices);
3234
- const inverseBindMatrices = new Float32Array(ibmAccess.data);
3235
- const jointNodeIndices = new Uint16Array(joints);
3236
- const nodeToJoint = /* @__PURE__ */ new Map();
3237
- for (let j = 0; j < jointCount; j++) {
3238
- nodeToJoint.set(joints[j], j);
3239
- }
3240
- const nodeParent = /* @__PURE__ */ new Map();
3241
- for (let i = 0; i < gltf.nodes.length; i++) {
3242
- const children = gltf.nodes[i].children;
3243
- if (children) {
3244
- for (const child of children) {
3245
- nodeParent.set(child, i);
3246
- }
3247
- }
3248
- }
3249
- const parentJointIndices = new Int16Array(jointCount).fill(-1);
3250
- for (let j = 0; j < jointCount; j++) {
3251
- let parentNode = nodeParent.get(joints[j]);
3252
- while (parentNode !== void 0) {
3253
- const parentJoint = nodeToJoint.get(parentNode);
3254
- if (parentJoint !== void 0) {
3255
- parentJointIndices[j] = parentJoint;
3256
- break;
3257
- }
3258
- parentNode = nodeParent.get(parentNode);
3259
- }
3260
- }
3261
- let skeletonRootMatrix = null;
3262
- for (let j = 0; j < jointCount; j++) {
3263
- if (parentJointIndices[j] !== -1)
3264
- continue;
3265
- let ancestorNode = nodeParent.get(joints[j]);
3266
- const ancestorChain = [];
3267
- while (ancestorNode !== void 0 && !nodeToJoint.has(ancestorNode)) {
3268
- ancestorChain.push(ancestorNode);
3269
- ancestorNode = nodeParent.get(ancestorNode);
3270
- }
3271
- if (ancestorChain.length > 0) {
3272
- skeletonRootMatrix = mat4IdentityNew();
3273
- for (let i = ancestorChain.length - 1; i >= 0; i--) {
3274
- const nodeMat = nodeToMat4(gltf.nodes[ancestorChain[i]]);
3275
- skeletonRootMatrix = mat4MulNew(skeletonRootMatrix, nodeMat);
3276
- }
3277
- }
3278
- break;
3279
- }
3280
- return {
3281
- jointCount,
3282
- jointNodeIndices,
3283
- inverseBindMatrices,
3284
- parentJointIndices,
3285
- skeletonRootMatrix
3286
- };
3287
- }
3288
- function parseAnimations(gltf, skinData, getAccessorData) {
3289
- if (!gltf.animations?.length)
3290
- return [];
3291
- const nodeToJoint = /* @__PURE__ */ new Map();
3292
- for (let j = 0; j < skinData.jointCount; j++) {
3293
- nodeToJoint.set(skinData.jointNodeIndices[j], j);
3294
- }
3295
- const clips = [];
3296
- for (const anim of gltf.animations) {
3297
- const channels = [];
3298
- let maxTime = 0;
3299
- for (const channel of anim.channels) {
3300
- const targetNode = channel.target.node;
3301
- const jointIndex = nodeToJoint.get(targetNode);
3302
- if (jointIndex === void 0)
3303
- continue;
3304
- const path = channel.target.path;
3305
- if (path !== "translation" && path !== "rotation" && path !== "scale")
3306
- continue;
3307
- const sampler = anim.samplers[channel.sampler];
3308
- const interpolation = sampler.interpolation ?? "LINEAR";
3309
- if (interpolation !== "LINEAR" && interpolation !== "STEP")
3310
- continue;
3311
- const inputAccess = getAccessorData(sampler.input);
3312
- const timestamps = new Float32Array(inputAccess.data);
3313
- const outputAccess = getAccessorData(sampler.output);
3314
- const values = new Float32Array(outputAccess.data);
3315
- if (timestamps.length > 0) {
3316
- const lastTime = timestamps[timestamps.length - 1];
3317
- if (lastTime > maxTime)
3318
- maxTime = lastTime;
3319
- }
3320
- channels.push({
3321
- jointIndex,
3322
- path,
3323
- timestamps,
3324
- values,
3325
- interpolation
3326
- });
3327
- }
3328
- if (channels.length > 0) {
3329
- clips.push({
3330
- name: anim.name ?? `animation_${clips.length}`,
3331
- duration: maxTime,
3332
- channels
3333
- });
3334
- }
3335
- }
3336
- return clips;
3337
- }
3338
- function parsePrimitiveSkinAttributes(primitive, getAccessorData) {
3339
- if (primitive.attributes.JOINTS_0 === void 0 || primitive.attributes.WEIGHTS_0 === void 0) {
3340
- return null;
3341
- }
3342
- const jointsAccess = getAccessorData(primitive.attributes.JOINTS_0);
3343
- const weightsAccess = getAccessorData(primitive.attributes.WEIGHTS_0);
3344
- const joints = jointsAccess.data instanceof Uint16Array ? jointsAccess.data : new Uint16Array(jointsAccess.data);
3345
- let weights;
3346
- if (weightsAccess.data instanceof Float32Array) {
3347
- weights = weightsAccess.data;
3348
- } else if (weightsAccess.data instanceof Uint8Array) {
3349
- weights = new Float32Array(weightsAccess.data.length);
3350
- for (let i = 0; i < weightsAccess.data.length; i++) {
3351
- weights[i] = weightsAccess.data[i] / 255;
3352
- }
3353
- } else if (weightsAccess.data instanceof Uint16Array) {
3354
- weights = new Float32Array(weightsAccess.data.length);
3355
- for (let i = 0; i < weightsAccess.data.length; i++) {
3356
- weights[i] = weightsAccess.data[i] / 65535;
3357
- }
3358
- } else {
3359
- weights = new Float32Array(weightsAccess.data);
3360
- }
3361
- return { joints, weights };
3362
- }
3363
- function getNodeTRS(node) {
3364
- const trs = new Float32Array(10);
3365
- if (node.matrix) {
3366
- trs[0] = node.matrix[12];
3367
- trs[1] = node.matrix[13];
3368
- trs[2] = node.matrix[14];
3369
- trs[3] = 0;
3370
- trs[4] = 0;
3371
- trs[5] = 0;
3372
- trs[6] = 1;
3373
- trs[7] = 1;
3374
- trs[8] = 1;
3375
- trs[9] = 1;
3376
- return trs;
3377
- }
3378
- const t = node.translation ?? [0, 0, 0];
3379
- const r = node.rotation ?? [0, 0, 0, 1];
3380
- const s = node.scale ?? [1, 1, 1];
3381
- trs[0] = t[0];
3382
- trs[1] = t[1];
3383
- trs[2] = t[2];
3384
- trs[3] = r[0];
3385
- trs[4] = r[1];
3386
- trs[5] = r[2];
3387
- trs[6] = r[3];
3388
- trs[7] = s[0];
3389
- trs[8] = s[1];
3390
- trs[9] = s[2];
3391
- return trs;
3392
- }
3393
- function createPackedAnimationData() {
3394
- return {
3395
- channels: [],
3396
- keyframeData: [],
3397
- clips: [],
3398
- skins: [],
3399
- parentIndices: [],
3400
- topoOrder: [],
3401
- ibmData: [],
3402
- restTRS: [],
3403
- skelRootMats: [],
3404
- jointChannelLookup: []
3405
- };
3406
- }
3407
- function packSkinAndAnimations(packed, skinData, clips, gltfNodes) {
3408
- const skinIndex = packed.skins.length;
3409
- const jc = skinData.jointCount;
3410
- const parentOffset = packed.parentIndices.length;
3411
- const topoOffset = packed.topoOrder.length;
3412
- const ibmOffset = packed.ibmData.length / 16;
3413
- const restTRSOffset = packed.restTRS.length / 10;
3414
- const skelRootMatIndex = packed.skelRootMats.length / 16;
3415
- for (let j = 0; j < jc; j++) {
3416
- packed.parentIndices.push(skinData.parentJointIndices[j]);
3417
- }
3418
- const visited = new Uint8Array(jc);
3419
- const visit = (j) => {
3420
- if (visited[j])
3421
- return;
3422
- visited[j] = 1;
3423
- const parent = skinData.parentJointIndices[j];
3424
- if (parent !== -1)
3425
- visit(parent);
3426
- packed.topoOrder.push(j);
3427
- };
3428
- for (let j = 0; j < jc; j++)
3429
- visit(j);
3430
- for (let i = 0; i < skinData.inverseBindMatrices.length; i++) {
3431
- packed.ibmData.push(skinData.inverseBindMatrices[i]);
3432
- }
3433
- for (let j = 0; j < jc; j++) {
3434
- const trs = getNodeTRS(gltfNodes[skinData.jointNodeIndices[j]]);
3435
- for (let k = 0; k < 10; k++)
3436
- packed.restTRS.push(trs[k]);
3437
- }
3438
- if (skinData.skeletonRootMatrix) {
3439
- for (let i = 0; i < 16; i++)
3440
- packed.skelRootMats.push(skinData.skeletonRootMatrix[i]);
3441
- } else {
3442
- const id = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
3443
- for (let i = 0; i < 16; i++)
3444
- packed.skelRootMats.push(id[i]);
3445
- }
3446
- const clipOffset = packed.clips.length;
3447
- const jointLookupStart = packed.jointChannelLookup.length;
3448
- packed.skins.push({ jointCount: jc, parentOffset, topoOffset, ibmOffset, restTRSOffset, skelRootMatIndex, clipOffset, jointLookupStart });
3449
- const nodeToJoint = /* @__PURE__ */ new Map();
3450
- for (let j = 0; j < jc; j++) {
3451
- nodeToJoint.set(skinData.jointNodeIndices[j], j);
3452
- }
3453
- for (const clip of clips) {
3454
- const channelStart = packed.channels.length;
3455
- const sortedChannels = [...clip.channels].sort((a, b) => a.jointIndex - b.jointIndex);
3456
- for (const ch of sortedChannels) {
3457
- const pathCode = ch.path === "translation" ? 0 : ch.path === "rotation" ? 1 : 2;
3458
- const isStep = ch.interpolation === "STEP" ? 4 : 0;
3459
- const dataOffset = packed.keyframeData.length;
3460
- const n = ch.timestamps.length;
3461
- const compCount = ch.path === "rotation" ? 4 : 3;
3462
- for (let i = 0; i < n; i++)
3463
- packed.keyframeData.push(ch.timestamps[i]);
3464
- for (let i = 0; i < n * compCount; i++)
3465
- packed.keyframeData.push(ch.values[i]);
3466
- packed.channels.push({
3467
- jointIndex: ch.jointIndex,
3468
- pathAndInterp: pathCode | isStep,
3469
- keyframeCount: n,
3470
- dataOffset
3471
- });
3472
- }
3473
- const clipChannelCount = sortedChannels.length;
3474
- let ci = 0;
3475
- for (let j = 0; j < jc; j++) {
3476
- const lookupStart = ci;
3477
- while (ci < clipChannelCount && sortedChannels[ci].jointIndex === j)
3478
- ci++;
3479
- packed.jointChannelLookup.push(channelStart + lookupStart);
3480
- packed.jointChannelLookup.push(ci - lookupStart);
3481
- }
3482
- packed.clips.push({
3483
- channelStart,
3484
- channelCount: clipChannelCount,
3485
- duration: clip.duration,
3486
- looping: 1
3487
- });
3488
- }
3489
- return skinIndex;
3490
- }
3491
-
3492
- // src/3d/skeletal-animation.ts
3493
- var SkeletalAnimation = class {
3494
- // 16 floats, identity if no non-joint ancestors
3495
- constructor(skinData, clips, gltfNodes) {
3496
- this.clips = [];
3497
- this.clipsByName = /* @__PURE__ */ new Map();
3498
- this._scratchVec3 = new Float32Array(3);
3499
- this._scratchQuat = new Float32Array(4);
3500
- this.skinData = skinData;
3501
- const jc = skinData.jointCount;
3502
- this.localMatrices = new Float32Array(jc * 16);
3503
- this.worldMatrices = new Float32Array(jc * 16);
3504
- this.blendScratch = new Float32Array(jc * 16);
3505
- this.originalRestPoseTRS = new Float32Array(jc * 10);
3506
- this.currentTRS = new Float32Array(jc * 10);
3507
- for (let j = 0; j < jc; j++) {
3508
- const nodeTRS = getNodeTRS(gltfNodes[skinData.jointNodeIndices[j]]);
3509
- this.originalRestPoseTRS.set(nodeTRS, j * 10);
3510
- }
3511
- this.skelRootMat = new Float32Array(16);
3512
- if (skinData.skeletonRootMatrix) {
3513
- this.skelRootMat.set(skinData.skeletonRootMatrix);
3514
- } else {
3515
- this.skelRootMat[0] = 1;
3516
- this.skelRootMat[5] = 1;
3517
- this.skelRootMat[10] = 1;
3518
- this.skelRootMat[15] = 1;
3519
- }
3520
- this.topoOrder = new Uint16Array(jc);
3521
- const visited = new Uint8Array(jc);
3522
- let writeIdx = 0;
3523
- const visit = (j) => {
3524
- if (visited[j])
3525
- return;
3526
- visited[j] = 1;
3527
- const parent = skinData.parentJointIndices[j];
3528
- if (parent !== -1)
3529
- visit(parent);
3530
- this.topoOrder[writeIdx++] = j;
3531
- };
3532
- for (let j = 0; j < jc; j++)
3533
- visit(j);
3534
- for (const clip of clips) {
3535
- this.loadClip(clip);
3536
- }
3537
- }
3538
- loadClip(clip, loop = true) {
3539
- const id = this.clips.length;
3540
- this.clips.push({
3541
- id,
3542
- name: clip.name,
3543
- duration: clip.duration,
3544
- channels: clip.channels,
3545
- loop
3546
- });
3547
- this.clipsByName.set(clip.name, id);
3548
- return id;
3549
- }
3550
- getClipId(name) {
3551
- const id = this.clipsByName.get(name);
3552
- if (id === void 0)
3553
- throw new Error(`Skeletal clip "${name}" not found`);
3554
- return id;
3555
- }
3556
- getClipNames() {
3557
- return this.clips.map((c) => c.name);
3558
- }
3559
- getClip(id) {
3560
- return this.clips[id] ?? null;
3561
- }
3562
- get clipCount() {
3563
- return this.clips.length;
3564
- }
3565
- createState(clipIdOrName, speed = 1, playing = true) {
3566
- const clipId = typeof clipIdOrName === "string" ? this.getClipId(clipIdOrName) : clipIdOrName;
3567
- return { clipId, time: 0, speed, playing, loop: true, prevClipId: -1, prevTime: 0, prevSpeed: 1, blendWeight: 1, blendDuration: 0, onEnd: () => null };
3568
- }
3569
- play(state, clipIdOrName, opts) {
3570
- const newClipId = typeof clipIdOrName === "string" ? this.getClipId(clipIdOrName) : clipIdOrName;
3571
- const crossfade = opts?.crossfade ?? 0;
3572
- if (crossfade > 0 && state.playing) {
3573
- state.prevClipId = state.clipId;
3574
- state.prevTime = state.time;
3575
- state.prevSpeed = state.speed;
3576
- state.blendWeight = 0;
3577
- state.blendDuration = crossfade;
3578
- } else {
3579
- state.prevClipId = -1;
3580
- state.blendWeight = 1;
3581
- state.blendDuration = 0;
3582
- }
3583
- state.onEnd = opts?.onEnd ?? (() => null);
3584
- state.loop = opts?.loop ?? true;
3585
- state.clipId = newClipId;
3586
- state.time = 0;
3587
- state.playing = true;
3588
- if (opts?.speed !== void 0)
3589
- state.speed = opts.speed;
3590
- }
3591
- stop(state) {
3592
- state.playing = false;
3593
- }
3594
- resume(state) {
3595
- state.playing = true;
3596
- }
3597
- /**
3598
- * Advance time and compute bone matrices.
3599
- * Writes jointCount mat4s into `output` starting at float index `outputOffset`.
3600
- * Handles crossfade blending automatically.
3601
- */
3602
- update(state, deltaTime, output, outputOffset = 0) {
3603
- const jc = this.skinData.jointCount;
3604
- if (state.playing) {
3605
- this.advanceTime(state, deltaTime);
3606
- }
3607
- const blending = state.prevClipId !== -1 && state.blendDuration > 0 && state.blendWeight < 1;
3608
- if (blending) {
3609
- state.blendWeight += deltaTime / state.blendDuration;
3610
- if (state.blendWeight >= 1) {
3611
- state.blendWeight = 1;
3612
- state.prevClipId = -1;
3613
- state.blendDuration = 0;
3614
- }
3615
- }
3616
- if (blending && state.prevClipId !== -1) {
3617
- const prevClip = this.clips[state.prevClipId];
3618
- if (prevClip) {
3619
- state.prevTime += deltaTime * state.prevSpeed;
3620
- if (prevClip.loop && state.prevTime >= prevClip.duration) {
3621
- state.prevTime %= prevClip.duration;
3622
- }
3623
- }
3624
- this.evaluateClip(state.prevClipId, state.prevTime, this.blendScratch, 0);
3625
- this.evaluateClip(state.clipId, state.time, output, outputOffset);
3626
- const w = state.blendWeight;
3627
- const oneMinusW = 1 - w;
3628
- const scratch = this.blendScratch;
3629
- const floatCount = jc * 16;
3630
- for (let i = 0; i < floatCount; i++) {
3631
- output[outputOffset + i] = scratch[i] * oneMinusW + output[outputOffset + i] * w;
3632
- }
3633
- } else {
3634
- this.evaluateClip(state.clipId, state.time, output, outputOffset);
3635
- }
3636
- }
3637
- /** Advance clip time with looping/clamping. */
3638
- advanceTime(state, deltaTime) {
3639
- const clip = this.clips[state.clipId];
3640
- if (!clip || clip.duration <= 0)
3641
- return;
3642
- state.time += deltaTime * state.speed;
3643
- if (state.time >= clip.duration) {
3644
- if (clip.loop)
3645
- state.time %= clip.duration;
3646
- else {
3647
- state.time = clip.duration - 1e-4;
3648
- state.playing = false;
3649
- }
3650
- }
3651
- if (state.time < 0) {
3652
- if (clip.loop)
3653
- state.time = clip.duration + state.time % clip.duration;
3654
- else {
3655
- state.time = 0;
3656
- state.playing = false;
3657
- }
3658
- }
3659
- }
3660
- /** Evaluate a clip at a specific time and write bone matrices. */
3661
- evaluateClip(clipId, time, output, outputOffset) {
3662
- const clip = this.clips[clipId];
3663
- if (!clip)
3664
- return;
3665
- const skin = this.skinData;
3666
- const jc = skin.jointCount;
3667
- const local = this.localMatrices;
3668
- const world = this.worldMatrices;
3669
- const cur = this.currentTRS;
3670
- cur.set(this.originalRestPoseTRS);
3671
- for (const ch of clip.channels) {
3672
- const j = ch.jointIndex;
3673
- if (j >= jc)
3674
- continue;
3675
- const ro = j * 10;
3676
- const sampled = this.sampleChannel(ch, time);
3677
- if (ch.path === "translation") {
3678
- cur[ro] = sampled[0];
3679
- cur[ro + 1] = sampled[1];
3680
- cur[ro + 2] = sampled[2];
3681
- } else if (ch.path === "rotation") {
3682
- cur[ro + 3] = sampled[0];
3683
- cur[ro + 4] = sampled[1];
3684
- cur[ro + 5] = sampled[2];
3685
- cur[ro + 6] = sampled[3];
3686
- } else if (ch.path === "scale") {
3687
- cur[ro + 7] = sampled[0];
3688
- cur[ro + 8] = sampled[1];
3689
- cur[ro + 9] = sampled[2];
3690
- }
3691
- }
3692
- for (let j = 0; j < jc; j++) {
3693
- const ro = j * 10;
3694
- trsToMat4(
3695
- cur[ro],
3696
- cur[ro + 1],
3697
- cur[ro + 2],
3698
- cur[ro + 3],
3699
- cur[ro + 4],
3700
- cur[ro + 5],
3701
- cur[ro + 6],
3702
- cur[ro + 7],
3703
- cur[ro + 8],
3704
- cur[ro + 9],
3705
- local,
3706
- j * 16
3707
- );
3708
- }
3709
- const topo = this.topoOrder;
3710
- const srm = this.skelRootMat;
3711
- for (let i = 0; i < jc; i++) {
3712
- const j = topo[i];
3713
- const parentJ = skin.parentJointIndices[j];
3714
- if (parentJ === -1) {
3715
- mat4Mul(srm, 0, local, j * 16, world, j * 16);
3716
- } else {
3717
- mat4Mul(world, parentJ * 16, local, j * 16, world, j * 16);
3718
- }
3719
- }
3720
- const ibm = skin.inverseBindMatrices;
3721
- for (let j = 0; j < jc; j++) {
3722
- mat4Mul(world, j * 16, ibm, j * 16, output, outputOffset + j * 16);
3723
- }
3724
- }
3725
- /**
3726
- * Compute bone matrices for the rest/bind pose (no animation).
3727
- */
3728
- computeRestPose(output, outputOffset = 0) {
3729
- const skin = this.skinData;
3730
- const jc = skin.jointCount;
3731
- const local = this.localMatrices;
3732
- const world = this.worldMatrices;
3733
- const rest = this.originalRestPoseTRS;
3734
- for (let j = 0; j < jc; j++) {
3735
- const ro = j * 10;
3736
- trsToMat4(
3737
- rest[ro],
3738
- rest[ro + 1],
3739
- rest[ro + 2],
3740
- rest[ro + 3],
3741
- rest[ro + 4],
3742
- rest[ro + 5],
3743
- rest[ro + 6],
3744
- rest[ro + 7],
3745
- rest[ro + 8],
3746
- rest[ro + 9],
3747
- local,
3748
- j * 16
3749
- );
3750
- }
3751
- const topo = this.topoOrder;
3752
- const srm = this.skelRootMat;
3753
- for (let i = 0; i < jc; i++) {
3754
- const j = topo[i];
3755
- const parentJ = skin.parentJointIndices[j];
3756
- if (parentJ === -1) {
3757
- mat4Mul(srm, 0, local, j * 16, world, j * 16);
3758
- } else {
3759
- mat4Mul(world, parentJ * 16, local, j * 16, world, j * 16);
3760
- }
3761
- }
3762
- const ibm = skin.inverseBindMatrices;
3763
- for (let j = 0; j < jc; j++) {
3764
- mat4Mul(world, j * 16, ibm, j * 16, output, outputOffset + j * 16);
3765
- }
3766
- }
3767
- // --- Private helpers (zero-alloc, operate on Float32Array sub-regions) ---
3768
- /** Sample an animation channel at time t. Returns 3 or 4 floats. */
3769
- sampleChannel(ch, t) {
3770
- const ts = ch.timestamps;
3771
- const vals = ch.values;
3772
- const compCount = ch.path === "rotation" ? 4 : 3;
3773
- if (t <= ts[0])
3774
- return vals.subarray(0, compCount);
3775
- if (t >= ts[ts.length - 1])
3776
- return vals.subarray((ts.length - 1) * compCount, ts.length * compCount);
3777
- let lo = 0, hi = ts.length - 1;
3778
- while (lo < hi - 1) {
3779
- const mid = lo + hi >> 1;
3780
- if (ts[mid] <= t)
3781
- lo = mid;
3782
- else
3783
- hi = mid;
3784
- }
3785
- if (ch.interpolation === "STEP") {
3786
- return vals.subarray(lo * compCount, lo * compCount + compCount);
3787
- }
3788
- const t0 = ts[lo], t1 = ts[hi];
3789
- const f = t1 > t0 ? (t - t0) / (t1 - t0) : 0;
3790
- const a = lo * compCount;
3791
- const b = hi * compCount;
3792
- if (ch.path === "rotation") {
3793
- return this.nlerpQuat(vals, a, vals, b, f);
3794
- }
3795
- const out = this._scratchVec3;
3796
- out[0] = vals[a] + (vals[b] - vals[a]) * f;
3797
- out[1] = vals[a + 1] + (vals[b + 1] - vals[a + 1]) * f;
3798
- out[2] = vals[a + 2] + (vals[b + 2] - vals[a + 2]) * f;
3799
- return out;
3800
- }
3801
- /** Normalized lerp for quaternions (nlerp). */
3802
- nlerpQuat(a, ao, b, bo, t) {
3803
- const out = this._scratchQuat;
3804
- let dot2 = a[ao] * b[bo] + a[ao + 1] * b[bo + 1] + a[ao + 2] * b[bo + 2] + a[ao + 3] * b[bo + 3];
3805
- const sign = dot2 < 0 ? -1 : 1;
3806
- const oneMinusT = 1 - t;
3807
- out[0] = oneMinusT * a[ao] + t * b[bo] * sign;
3808
- out[1] = oneMinusT * a[ao + 1] + t * b[bo + 1] * sign;
3809
- out[2] = oneMinusT * a[ao + 2] + t * b[bo + 2] * sign;
3810
- out[3] = oneMinusT * a[ao + 3] + t * b[bo + 3] * sign;
3811
- const len = Math.sqrt(out[0] * out[0] + out[1] * out[1] + out[2] * out[2] + out[3] * out[3]);
3812
- if (len > 0) {
3813
- const inv = 1 / len;
3814
- out[0] *= inv;
3815
- out[1] *= inv;
3816
- out[2] *= inv;
3817
- out[3] *= inv;
3818
- }
3819
- return out;
3820
- }
3821
- };
3266
+ // src/3d/renderer.ts
3267
+ var import_renderer5 = require("murow/renderer");
3822
3268
 
3823
3269
  // src/3d/skeletal-animation-compute/packer.ts
3824
3270
  function packAnimationData(packed) {
@@ -3900,14 +3346,14 @@ function packAnimationData(packed) {
3900
3346
 
3901
3347
  // src/3d/skeletal-animation-compute/kernel.ts
3902
3348
  var WORKGROUP_SIZE = 64;
3903
- function buildAnimationKernel(root, packed, maxInstances, maxTotalBones) {
3349
+ function buildAnimationKernel(root, packed, maxInstances, maxTotalBones, budgets) {
3904
3350
  const pb = packAnimationData(packed);
3905
3351
  const kernel = new ComputeBuilder("skeletal-animation", { workgroupSize: WORKGROUP_SIZE }, root).buffers({
3906
3352
  uniforms: { uniform: AnimComputeUniforms },
3907
3353
  instances: { storage: d.arrayOf(InstanceAnimStateGPU, maxInstances) },
3908
- skelI32: { storage: d.arrayOf(d.i32, pb.skelI32.length) },
3909
- animF32: { storage: d.arrayOf(d.f32, pb.animF32.length) },
3910
- matrices: { storage: d.arrayOf(d.mat4x4f, pb.totalMats) },
3354
+ skelI32: { storage: d.arrayOf(d.i32, budgets.skelI32Capacity) },
3355
+ animF32: { storage: d.arrayOf(d.f32, budgets.animF32Capacity) },
3356
+ matrices: { storage: d.arrayOf(d.mat4x4f, budgets.matricesCapacity) },
3911
3357
  boneMatrices: { storage: d.arrayOf(d.mat4x4f, maxTotalBones), readwrite: true }
3912
3358
  }).shader(({ uniforms, instances, skelI32, animF32, matrices, boneMatrices }, { globalId }) => {
3913
3359
  const idx = globalId.x;
@@ -4249,14 +3695,51 @@ function buildAnimationKernel(root, packed, maxInstances, maxTotalBones) {
4249
3695
  }
4250
3696
  }
4251
3697
  }).build();
4252
- kernel.write("skelI32", Array.from(pb.skelI32));
4253
- kernel.write("animF32", Array.from(pb.animF32));
4254
- const matBuffer = kernel.getBuffer("matrices");
4255
- const rawMatBuffer = root.unwrap(matBuffer);
4256
- root.device.queue.writeBuffer(rawMatBuffer, 0, pb.matFloats);
4257
- return { kernel, packedBuffers: pb };
3698
+ uploadPackedToKernel(root, kernel, pb);
3699
+ return { kernel, packedBuffers: pb, budgets };
3700
+ }
3701
+ function uploadPackedToKernel(root, kernel, pb) {
3702
+ const queue = root.device.queue;
3703
+ const skelBuf = root.unwrap(kernel.getBuffer("skelI32"));
3704
+ queue.writeBuffer(skelBuf, 0, pb.skelI32);
3705
+ const animBuf = root.unwrap(kernel.getBuffer("animF32"));
3706
+ queue.writeBuffer(animBuf, 0, pb.animF32);
3707
+ const matBuf = root.unwrap(kernel.getBuffer("matrices"));
3708
+ queue.writeBuffer(matBuf, 0, pb.matFloats);
4258
3709
  }
4259
3710
 
3711
+ // src/3d/clip-resync-coordinator.ts
3712
+ var GltfClipResyncCoordinator = class {
3713
+ constructor(bucket) {
3714
+ this.bucket = bucket;
3715
+ this.skinIndexByPrefabId = /* @__PURE__ */ new Map();
3716
+ this._pending = /* @__PURE__ */ new Set();
3717
+ this.bucket.events.on("clips-changed", ({ prefabId }) => {
3718
+ const skinIndex = this.skinIndexByPrefabId.get(prefabId);
3719
+ if (skinIndex !== void 0)
3720
+ this._pending.add(skinIndex);
3721
+ });
3722
+ }
3723
+ /** Map a prefab id to its index in the renderer's `skinnedModels` list. */
3724
+ registerSkin(prefabId, skinIndex) {
3725
+ this.skinIndexByPrefabId.set(prefabId, skinIndex);
3726
+ }
3727
+ /** Skin indices whose clip set has changed since the last `clear()`. */
3728
+ get pending() {
3729
+ return this._pending;
3730
+ }
3731
+ clear() {
3732
+ this._pending.clear();
3733
+ }
3734
+ /** Unsubscribe from the bucket and reset its animation clip sets. */
3735
+ dispose() {
3736
+ this.bucket.events.clear("clips-changed");
3737
+ this.bucket.resetAnimations();
3738
+ this.skinIndexByPrefabId.clear();
3739
+ this._pending.clear();
3740
+ }
3741
+ };
3742
+
4260
3743
  // src/3d/renderer.ts
4261
3744
  var DYN_PREV_PX = 0;
4262
3745
  var DYN_PREV_PY = 1;
@@ -4283,9 +3766,44 @@ var SSTAT_CR = 3;
4283
3766
  var SSTAT_CG = 4;
4284
3767
  var SSTAT_CB = 5;
4285
3768
  var SSTAT_BONE_OFFSET = 6;
4286
- var WebGPU3DRenderer = class extends import_base_3d_renderer.Base3DRenderer {
3769
+ var prefabHandles = /* @__PURE__ */ new WeakMap();
3770
+ function isPrefab3D(value) {
3771
+ return value.type === "gltf" || value.type === "grid";
3772
+ }
3773
+ function resolveTransform(opts) {
3774
+ const [px, py, pz] = opts.position ?? [0, 0, 0];
3775
+ const [rx, ry, rz] = opts.rotation ?? [0, 0, 0];
3776
+ const s = opts.scale;
3777
+ const [sx, sy, sz] = typeof s === "number" ? [s, s, s] : s ?? [1, 1, 1];
3778
+ const [cr, cg, cb] = opts.color ?? [1, 1, 1];
3779
+ return { px, py, pz, rx, ry, rz, sx, sy, sz, cr, cg, cb };
3780
+ }
3781
+ function resolvePrefabHandle(prefab) {
3782
+ const h = prefabHandles.get(prefab);
3783
+ if (!h) {
3784
+ throw new Error(
3785
+ `Prefab '${prefab.id}' has no GPU handle \u2014 has the renderer's init() been called with this bucket?`
3786
+ );
3787
+ }
3788
+ return h;
3789
+ }
3790
+ function computeBucketStats(bucket) {
3791
+ let maxSkinnedParts = 0;
3792
+ let maxJointCount = 0;
3793
+ for (const prefab of bucket.entries()) {
3794
+ if (prefab.type === "gltf") {
3795
+ if (prefab.skinnedPartCount > maxSkinnedParts)
3796
+ maxSkinnedParts = prefab.skinnedPartCount;
3797
+ if (prefab.jointCount > maxJointCount)
3798
+ maxJointCount = prefab.jointCount;
3799
+ }
3800
+ }
3801
+ return { maxSkinnedParts, maxJointCount };
3802
+ }
3803
+ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
4287
3804
  constructor(canvas, options) {
4288
- super(canvas, options);
3805
+ const resolvedMaxModels = options.maxModels ?? (options.prefabs ? options.prefabs.size + 16 : 32);
3806
+ super(canvas, { ...options, maxModels: resolvedMaxModels });
4289
3807
  this.resizeObserver = null;
4290
3808
  this.resizeCallbacks = [];
4291
3809
  // layer=0, sheetId=modelId
@@ -4295,11 +3813,15 @@ var WebGPU3DRenderer = class extends import_base_3d_renderer.Base3DRenderer {
4295
3813
  this.nextModelId = 0;
4296
3814
  // Skinned model data
4297
3815
  this.skinnedModels = [];
3816
+ // Null when constructed without a `prefabs` bucket; lazy load/unload requires it as event source.
3817
+ this.clipResync = null;
4298
3818
  this.boneMatrixDirty = true;
4299
3819
  // GPU animation compute
4300
- this.packedAnimData = createPackedAnimationData();
3820
+ this.packedAnimData = (0, import_renderer5.createPackedAnimationData)();
4301
3821
  this.animComputeKernel = null;
4302
3822
  this.animComputeNeedsRebuild = false;
3823
+ // Capacities the active kernel was built with — used to gate the in-place upload path on resync.
3824
+ this.animKernelBudgets = null;
4303
3825
  this.animClipTableOffset = 0;
4304
3826
  this.animChannelTableOffset = 0;
4305
3827
  this.animJointLookupOffset = 0;
@@ -4313,16 +3835,20 @@ var WebGPU3DRenderer = class extends import_base_3d_renderer.Base3DRenderer {
4313
3835
  // mat4x4 (16) + alpha (1) + lightDir (3) + padding (4)
4314
3836
  this.lastRenderTime = 0;
4315
3837
  this.camera = new Camera3D();
4316
- this.maxSkinnedInstances = options.maxSkinnedInstances ?? 5e3;
4317
- this.maxBonesPerSkin = options.maxBonesPerSkin ?? 64;
3838
+ this._prefabs = options.prefabs ?? null;
3839
+ const SKINNED_PARTS_PER_INSTANCE_DEFAULT_CAP = 3;
3840
+ const bucketStats = this._prefabs ? computeBucketStats(this._prefabs) : null;
3841
+ const maxInstances = options.maxInstances ?? resolvedMaxModels;
3842
+ this.maxSkinnedInstances = options.maxSkinnedInstances ?? (bucketStats ? maxInstances * Math.max(1, Math.min(bucketStats.maxSkinnedParts, SKINNED_PARTS_PER_INSTANCE_DEFAULT_CAP)) : 5e3);
3843
+ this.maxBonesPerSkin = options.maxBonesPerSkin ?? (bucketStats ? Math.max(1, bucketStats.maxJointCount) : 64);
4318
3844
  this.maxTotalBones = this.maxSkinnedInstances * this.maxBonesPerSkin * 2;
4319
3845
  this.updatedBoneOffsets = new Uint8Array(this.maxTotalBones);
4320
- this.freeList = new import_free_list3.FreeList(options.maxModels);
4321
- this.batcher = new import_sparse_batcher2.SparseBatcher(options.maxModels);
4322
- this.dynamicData = new Float32Array(options.maxModels * DYNAMIC_MESH_FLOATS);
4323
- this.staticData = new Float32Array(options.maxModels * STATIC_MESH_FLOATS);
4324
- this.slotIndexData = new Uint32Array(options.maxModels);
4325
- this.instanceModelIds = new Uint8Array(options.maxModels);
3846
+ this.freeList = new import_free_list3.FreeList(resolvedMaxModels);
3847
+ this.batcher = new import_sparse_batcher2.SparseBatcher(resolvedMaxModels);
3848
+ this.dynamicData = new Float32Array(resolvedMaxModels * DYNAMIC_MESH_FLOATS);
3849
+ this.staticData = new Float32Array(resolvedMaxModels * STATIC_MESH_FLOATS);
3850
+ this.slotIndexData = new Uint32Array(resolvedMaxModels);
3851
+ this.instanceModelIds = new Uint8Array(resolvedMaxModels);
4326
3852
  const msi = this.maxSkinnedInstances;
4327
3853
  this.skinnedFreeList = new import_free_list3.FreeList(msi);
4328
3854
  this.skinnedBatcher = new import_sparse_batcher2.SparseBatcher(msi);
@@ -4478,9 +4004,38 @@ var WebGPU3DRenderer = class extends import_base_3d_renderer.Base3DRenderer {
4478
4004
  { binding: 4, resource: { buffer: this.rawBoneMatrixBuffer } }
4479
4005
  ]
4480
4006
  });
4007
+ if (this._prefabs) {
4008
+ this.uploadPrefabBucket(this._prefabs);
4009
+ }
4481
4010
  this.setupResizeObserver();
4482
4011
  this._initialized = true;
4483
4012
  }
4013
+ /**
4014
+ * Upload every prefab in the bucket to the GPU and stash the handle on
4015
+ * each prefab so `bucket.get(id)` resolves to a usable model. Also
4016
+ * subscribes the resync coordinator to the bucket's `clips-changed`
4017
+ * channel for lazy load/unload.
4018
+ */
4019
+ uploadPrefabBucket(bucket) {
4020
+ this.clipResync = new GltfClipResyncCoordinator(bucket);
4021
+ for (const prefab of bucket.entries()) {
4022
+ if (prefab.type === "gltf") {
4023
+ const beforeSkinCount = this.skinnedModels.length;
4024
+ const model = this.uploadParsedGltf(prefab.parsed);
4025
+ prefabHandles.set(prefab, model);
4026
+ if (this.skinnedModels.length > beforeSkinCount) {
4027
+ this.clipResync.registerSkin(prefab.id, beforeSkinCount);
4028
+ }
4029
+ } else if (prefab.type === "grid") {
4030
+ const model = this.createGrid({
4031
+ size: prefab.size,
4032
+ step: prefab.step,
4033
+ lineWidth: prefab.lineWidth
4034
+ });
4035
+ prefabHandles.set(prefab, model);
4036
+ }
4037
+ }
4038
+ }
4484
4039
  setupResizeObserver() {
4485
4040
  const supportsDevicePixelBox = (() => {
4486
4041
  try {
@@ -4814,108 +4369,20 @@ var WebGPU3DRenderer = class extends import_base_3d_renderer.Base3DRenderer {
4814
4369
  * ```
4815
4370
  */
4816
4371
  async loadGltf(url, opts) {
4817
- const response = await fetch(url);
4818
- const baseUrl = url.substring(0, url.lastIndexOf("/") + 1);
4819
- let gltf;
4820
- let glbBinaryChunk = null;
4821
- const arrayBuffer = await response.arrayBuffer();
4822
- const magic = new Uint32Array(arrayBuffer, 0, 1)[0];
4823
- if (magic === 1179937895) {
4824
- let offset = 12;
4825
- while (offset < arrayBuffer.byteLength) {
4826
- const chunkLength = new Uint32Array(arrayBuffer, offset, 1)[0];
4827
- const chunkType = new Uint32Array(arrayBuffer, offset + 4, 1)[0];
4828
- offset += 8;
4829
- if (chunkType === 1313821514) {
4830
- const jsonBytes = new Uint8Array(arrayBuffer, offset, chunkLength);
4831
- gltf = JSON.parse(new TextDecoder().decode(jsonBytes));
4832
- } else if (chunkType === 5130562) {
4833
- glbBinaryChunk = arrayBuffer.slice(offset, offset + chunkLength);
4834
- }
4835
- offset += chunkLength;
4836
- }
4837
- if (!gltf)
4838
- throw new Error(`Invalid GLB: no JSON chunk in ${url}`);
4839
- } else {
4840
- gltf = JSON.parse(new TextDecoder().decode(arrayBuffer));
4841
- }
4842
- if (!gltf.meshes?.length)
4843
- throw new Error(`No meshes found in ${url}`);
4844
- const buffers = [];
4845
- for (let i = 0; i < (gltf.buffers?.length ?? 0); i++) {
4846
- const buf = gltf.buffers[i];
4847
- if (glbBinaryChunk && (!buf.uri || buf.uri === "")) {
4848
- buffers.push(glbBinaryChunk);
4849
- } else if (buf.uri) {
4850
- const r = await fetch(baseUrl + buf.uri);
4851
- buffers.push(await r.arrayBuffer());
4852
- }
4853
- }
4854
- const getAccessorData = (accessorIndex) => {
4855
- const accessor = gltf.accessors[accessorIndex];
4856
- const bufferView = gltf.bufferViews[accessor.bufferView];
4857
- const buffer = buffers[bufferView.buffer];
4858
- const typeMap = { 5120: Int8Array, 5121: Uint8Array, 5122: Int16Array, 5123: Uint16Array, 5125: Uint32Array, 5126: Float32Array };
4859
- const byteSizeMap = { 5120: 1, 5121: 1, 5122: 2, 5123: 2, 5125: 4, 5126: 4 };
4860
- const sizeMap = { SCALAR: 1, VEC2: 2, VEC3: 3, VEC4: 4, MAT2: 4, MAT3: 9, MAT4: 16 };
4861
- const TypedArray = typeMap[accessor.componentType];
4862
- const componentBytes = byteSizeMap[accessor.componentType];
4863
- const elementSize = sizeMap[accessor.type] ?? 1;
4864
- const baseOffset = (bufferView.byteOffset ?? 0) + (accessor.byteOffset ?? 0);
4865
- const stride = bufferView.byteStride ?? componentBytes * elementSize;
4866
- const tightStride = componentBytes * elementSize;
4867
- if (stride === tightStride) {
4868
- const data = new TypedArray(buffer, baseOffset, accessor.count * elementSize);
4869
- return { data, count: accessor.count, elementSize };
4870
- }
4871
- const out = new TypedArray(accessor.count * elementSize);
4872
- const src = new Uint8Array(buffer);
4873
- const dst = new Uint8Array(out.buffer);
4874
- for (let i = 0; i < accessor.count; i++) {
4875
- const srcOff = baseOffset + i * stride;
4876
- const dstOff = i * tightStride;
4877
- for (let b = 0; b < tightStride; b++) {
4878
- dst[dstOff + b] = src[srcOff + b];
4879
- }
4880
- }
4881
- return { data: out, count: accessor.count, elementSize };
4882
- };
4883
- const textureCache = /* @__PURE__ */ new Map();
4884
- const loadTexture = async (imageIndex) => {
4885
- if (textureCache.has(imageIndex))
4886
- return textureCache.get(imageIndex);
4887
- const image = gltf.images?.[imageIndex];
4888
- if (!image)
4889
- return void 0;
4890
- let blob;
4891
- if (image.bufferView !== void 0) {
4892
- const bv = gltf.bufferViews[image.bufferView];
4893
- const buf = buffers[bv.buffer];
4894
- const data = new Uint8Array(buf, bv.byteOffset ?? 0, bv.byteLength);
4895
- blob = new Blob([data], { type: image.mimeType ?? "image/png" });
4896
- } else if (image.uri) {
4897
- const imgUrl = image.uri.startsWith("data:") ? image.uri : baseUrl + image.uri;
4898
- blob = await (await fetch(imgUrl)).blob();
4899
- }
4900
- if (blob) {
4901
- const bmp = await createImageBitmap(blob);
4902
- textureCache.set(imageIndex, bmp);
4903
- return bmp;
4904
- }
4905
- return void 0;
4906
- };
4907
- const meshNodeIndex = gltf.nodes?.findIndex((n) => n.mesh !== void 0) ?? -1;
4908
- const skinIndex = meshNodeIndex !== -1 ? gltf.nodes?.[meshNodeIndex]?.skin : void 0;
4909
- let skinData = null;
4910
- let animClips = [];
4372
+ const parsed = await (0, import_renderer5.parseGltf)(url, opts);
4373
+ return this.uploadParsedGltf(parsed);
4374
+ }
4375
+ /**
4376
+ * Upload a previously-parsed glTF to the GPU. Returns a GltfModel handle.
4377
+ * Splitting parse (CPU) from upload (GPU) lets callers parse models in parallel
4378
+ * before a renderer exists and inspect joint counts / vertex totals to size the
4379
+ * renderer appropriately.
4380
+ */
4381
+ uploadParsedGltf(parsed) {
4911
4382
  let skinnedModelSkinIndex = -1;
4912
- if (skinIndex !== void 0 && gltf.skins?.[skinIndex]) {
4913
- skinData = parseSkin(gltf, skinIndex, getAccessorData);
4914
- animClips = parseAnimations(gltf, skinData, getAccessorData);
4915
- if (opts?.animations) {
4916
- animClips = animClips.filter((clip) => opts.animations.includes(clip.name));
4917
- }
4918
- const animation = new SkeletalAnimation(skinData, animClips, gltf.nodes);
4383
+ if (parsed.skin) {
4384
+ const { data: skinData, animClips } = parsed.skin;
4385
+ const animation = new import_renderer5.SkeletalAnimation(skinData, animClips);
4919
4386
  const ibm = skinData.inverseBindMatrices;
4920
4387
  let maxRadSq = 0;
4921
4388
  for (let j = 0; j < skinData.jointCount; j++) {
@@ -4929,91 +4396,34 @@ var WebGPU3DRenderer = class extends import_base_3d_renderer.Base3DRenderer {
4929
4396
  this.skinnedModels.push({
4930
4397
  animation,
4931
4398
  jointCount: skinData.jointCount,
4932
- boundingRadius: skinnedRadius
4399
+ boundingRadius: skinnedRadius,
4400
+ parsedSkin: parsed.skin
4933
4401
  });
4934
- packSkinAndAnimations(this.packedAnimData, skinData, animClips, gltf.nodes);
4402
+ (0, import_renderer5.packSkinAndAnimations)(this.packedAnimData, skinData, animClips);
4935
4403
  this.animComputeNeedsRebuild = true;
4936
4404
  }
4937
4405
  const handles = [];
4938
- const meshNodeIndices = [];
4939
- for (let i = 0; i < gltf.nodes.length; i++) {
4940
- if (gltf.nodes[i].mesh !== void 0)
4941
- meshNodeIndices.push(i);
4942
- }
4943
- const meshIndicesToLoad = meshNodeIndices.length > 0 ? meshNodeIndices.map((ni) => gltf.nodes[ni].mesh) : [0];
4944
- for (const meshIdx of meshIndicesToLoad) {
4945
- const mesh = gltf.meshes[meshIdx];
4946
- if (!mesh)
4947
- continue;
4948
- const meshNodeForThis = gltf.nodes.find((n) => n.mesh === meshIdx);
4949
- const meshSkinIndex = meshNodeForThis?.skin;
4950
- const isSkinned = skinData && meshSkinIndex !== void 0;
4951
- let thisMeshNodeMatrix = null;
4952
- if (meshNodeForThis && !isSkinned) {
4953
- if (meshNodeForThis.scale || meshNodeForThis.rotation || meshNodeForThis.translation || meshNodeForThis.matrix) {
4954
- thisMeshNodeMatrix = nodeToMat4(meshNodeForThis);
4955
- }
4956
- }
4957
- for (const primitive of mesh.primitives) {
4958
- const posAccess = getAccessorData(primitive.attributes.POSITION);
4959
- const positions = new Float32Array(posAccess.data);
4960
- let normals;
4961
- if (primitive.attributes.NORMAL !== void 0) {
4962
- normals = new Float32Array(getAccessorData(primitive.attributes.NORMAL).data);
4963
- }
4964
- let uvs;
4965
- if (primitive.attributes.TEXCOORD_0 !== void 0) {
4966
- uvs = new Float32Array(getAccessorData(primitive.attributes.TEXCOORD_0).data);
4967
- }
4968
- let indices;
4969
- if (primitive.indices !== void 0) {
4970
- const idxAccess = getAccessorData(primitive.indices);
4971
- indices = idxAccess.data.length > 65535 ? new Uint32Array(idxAccess.data) : new Uint16Array(idxAccess.data);
4972
- }
4973
- if (thisMeshNodeMatrix && !isSkinned) {
4974
- const meshNodeMatrix = thisMeshNodeMatrix;
4975
- const mm = meshNodeMatrix;
4976
- const vertexCount = positions.length / 3;
4977
- for (let v = 0; v < vertexCount; v++) {
4978
- const o = v * 3;
4979
- const px = positions[o], py = positions[o + 1], pz = positions[o + 2];
4980
- positions[o] = mm[0] * px + mm[4] * py + mm[8] * pz + mm[12];
4981
- positions[o + 1] = mm[1] * px + mm[5] * py + mm[9] * pz + mm[13];
4982
- positions[o + 2] = mm[2] * px + mm[6] * py + mm[10] * pz + mm[14];
4983
- if (normals) {
4984
- const nx = normals[o], ny = normals[o + 1], nz = normals[o + 2];
4985
- const tnx = mm[0] * nx + mm[4] * ny + mm[8] * nz;
4986
- const tny = mm[1] * nx + mm[5] * ny + mm[9] * nz;
4987
- const tnz = mm[2] * nx + mm[6] * ny + mm[10] * nz;
4988
- const len = Math.sqrt(tnx * tnx + tny * tny + tnz * tnz);
4989
- if (len > 0) {
4990
- normals[o] = tnx / len;
4991
- normals[o + 1] = tny / len;
4992
- normals[o + 2] = tnz / len;
4993
- }
4994
- }
4995
- }
4996
- }
4997
- let texture;
4998
- if (primitive.material !== void 0) {
4999
- const material = gltf.materials?.[primitive.material];
5000
- const texIndex = material?.pbrMetallicRoughness?.baseColorTexture?.index;
5001
- if (texIndex !== void 0 && gltf.textures?.[texIndex]) {
5002
- texture = await loadTexture(gltf.textures[texIndex].source);
5003
- }
5004
- }
5005
- if (isSkinned) {
5006
- const skinAttrs = parsePrimitiveSkinAttributes(primitive, getAccessorData);
5007
- if (skinAttrs) {
5008
- handles.push(this.loadSkinnedModel(
5009
- { positions, normals, uvs, indices, texture },
5010
- skinAttrs,
5011
- skinnedModelSkinIndex
5012
- ));
5013
- continue;
5014
- }
5015
- }
5016
- handles.push(this.loadModel({ positions, normals, uvs, indices, texture }));
4406
+ for (const prim of parsed.primitives) {
4407
+ if (prim.skinned && prim.skinAttrs) {
4408
+ handles.push(this.loadSkinnedModel(
4409
+ {
4410
+ positions: prim.positions,
4411
+ normals: prim.normals,
4412
+ uvs: prim.uvs,
4413
+ indices: prim.indices,
4414
+ texture: prim.texture
4415
+ },
4416
+ prim.skinAttrs,
4417
+ skinnedModelSkinIndex
4418
+ ));
4419
+ } else {
4420
+ handles.push(this.loadModel({
4421
+ positions: prim.positions,
4422
+ normals: prim.normals,
4423
+ uvs: prim.uvs,
4424
+ indices: prim.indices,
4425
+ texture: prim.texture
4426
+ }));
5017
4427
  }
5018
4428
  }
5019
4429
  let totalVertexCount = 0;
@@ -5025,7 +4435,7 @@ var WebGPU3DRenderer = class extends import_base_3d_renderer.Base3DRenderer {
5025
4435
  totalVertexCount,
5026
4436
  skinned: handles.some((h) => h.skinned),
5027
4437
  animations: animNames,
5028
- src: url
4438
+ src: parsed.src
5029
4439
  };
5030
4440
  }
5031
4441
  /**
@@ -5033,7 +4443,7 @@ var WebGPU3DRenderer = class extends import_base_3d_renderer.Base3DRenderer {
5033
4443
  * with another instance (e.g., when spawning all parts of a character).
5034
4444
  */
5035
4445
  addInstance(opts) {
5036
- const modelOrGltf = opts.model;
4446
+ const modelOrGltf = isPrefab3D(opts.model) ? resolvePrefabHandle(opts.model) : opts.model;
5037
4447
  if ("parts" in modelOrGltf) {
5038
4448
  return this.addGltfInstance(opts, modelOrGltf);
5039
4449
  }
@@ -5047,27 +4457,25 @@ var WebGPU3DRenderer = class extends import_base_3d_renderer.Base3DRenderer {
5047
4457
  throw new Error(`Max instances (${this.maxModels}) reached`);
5048
4458
  const dynBase = slot * DYNAMIC_MESH_FLOATS;
5049
4459
  const statBase = slot * STATIC_MESH_FLOATS;
5050
- const x = opts.x ?? 0, y = opts.y ?? 0, z = opts.z ?? 0;
5051
- this.dynamicData[dynBase + DYN_PREV_PX] = x;
5052
- this.dynamicData[dynBase + DYN_PREV_PY] = y;
5053
- this.dynamicData[dynBase + DYN_PREV_PZ] = z;
5054
- this.dynamicData[dynBase + DYN_CURR_PX] = x;
5055
- this.dynamicData[dynBase + DYN_CURR_PY] = y;
5056
- this.dynamicData[dynBase + DYN_CURR_PZ] = z;
5057
- const rx = opts.rotX ?? 0, ry = opts.rotY ?? 0, rz = opts.rotZ ?? 0;
5058
- this.dynamicData[dynBase + DYN_PREV_RX] = rx;
5059
- this.dynamicData[dynBase + DYN_PREV_RY] = ry;
5060
- this.dynamicData[dynBase + DYN_PREV_RZ] = rz;
5061
- this.dynamicData[dynBase + DYN_CURR_RX] = rx;
5062
- this.dynamicData[dynBase + DYN_CURR_RY] = ry;
5063
- this.dynamicData[dynBase + DYN_CURR_RZ] = rz;
5064
- this.staticData[statBase + STAT_SX] = opts.scaleX ?? 1;
5065
- this.staticData[statBase + STAT_SY] = opts.scaleY ?? 1;
5066
- this.staticData[statBase + STAT_SZ] = opts.scaleZ ?? 1;
5067
- const color = opts.color ?? [1, 1, 1];
5068
- this.staticData[statBase + STAT_CR] = color[0];
5069
- this.staticData[statBase + STAT_CG] = color[1];
5070
- this.staticData[statBase + STAT_CB] = color[2];
4460
+ const t = resolveTransform(opts);
4461
+ this.dynamicData[dynBase + DYN_PREV_PX] = t.px;
4462
+ this.dynamicData[dynBase + DYN_PREV_PY] = t.py;
4463
+ this.dynamicData[dynBase + DYN_PREV_PZ] = t.pz;
4464
+ this.dynamicData[dynBase + DYN_CURR_PX] = t.px;
4465
+ this.dynamicData[dynBase + DYN_CURR_PY] = t.py;
4466
+ this.dynamicData[dynBase + DYN_CURR_PZ] = t.pz;
4467
+ this.dynamicData[dynBase + DYN_PREV_RX] = t.rx;
4468
+ this.dynamicData[dynBase + DYN_PREV_RY] = t.ry;
4469
+ this.dynamicData[dynBase + DYN_PREV_RZ] = t.rz;
4470
+ this.dynamicData[dynBase + DYN_CURR_RX] = t.rx;
4471
+ this.dynamicData[dynBase + DYN_CURR_RY] = t.ry;
4472
+ this.dynamicData[dynBase + DYN_CURR_RZ] = t.rz;
4473
+ this.staticData[statBase + STAT_SX] = t.sx;
4474
+ this.staticData[statBase + STAT_SY] = t.sy;
4475
+ this.staticData[statBase + STAT_SZ] = t.sz;
4476
+ this.staticData[statBase + STAT_CR] = t.cr;
4477
+ this.staticData[statBase + STAT_CG] = t.cg;
4478
+ this.staticData[statBase + STAT_CB] = t.cb;
5071
4479
  this.staticDirty = true;
5072
4480
  this.instanceModelIds[slot] = modelHandle.id;
5073
4481
  this.batcher.add(0, modelHandle.id, slot);
@@ -5153,27 +4561,25 @@ var WebGPU3DRenderer = class extends import_base_3d_renderer.Base3DRenderer {
5153
4561
  this.skinnedAnimStates[slot] = animState;
5154
4562
  const dynBase = slot * DYNAMIC_MESH_FLOATS;
5155
4563
  const statBase = slot * SKINNED_STATIC_MESH_FLOATS;
5156
- const x = opts.x ?? 0, y = opts.y ?? 0, z = opts.z ?? 0;
5157
- this.skinnedDynamicData[dynBase + DYN_PREV_PX] = x;
5158
- this.skinnedDynamicData[dynBase + DYN_PREV_PY] = y;
5159
- this.skinnedDynamicData[dynBase + DYN_PREV_PZ] = z;
5160
- this.skinnedDynamicData[dynBase + DYN_CURR_PX] = x;
5161
- this.skinnedDynamicData[dynBase + DYN_CURR_PY] = y;
5162
- this.skinnedDynamicData[dynBase + DYN_CURR_PZ] = z;
5163
- const rx = opts.rotX ?? 0, ry = opts.rotY ?? 0, rz = opts.rotZ ?? 0;
5164
- this.skinnedDynamicData[dynBase + DYN_PREV_RX] = rx;
5165
- this.skinnedDynamicData[dynBase + DYN_PREV_RY] = ry;
5166
- this.skinnedDynamicData[dynBase + DYN_PREV_RZ] = rz;
5167
- this.skinnedDynamicData[dynBase + DYN_CURR_RX] = rx;
5168
- this.skinnedDynamicData[dynBase + DYN_CURR_RY] = ry;
5169
- this.skinnedDynamicData[dynBase + DYN_CURR_RZ] = rz;
5170
- this.skinnedStaticData[statBase + SSTAT_SX] = opts.scaleX ?? 1;
5171
- this.skinnedStaticData[statBase + SSTAT_SY] = opts.scaleY ?? 1;
5172
- this.skinnedStaticData[statBase + SSTAT_SZ] = opts.scaleZ ?? 1;
5173
- const color = opts.color ?? [1, 1, 1];
5174
- this.skinnedStaticData[statBase + SSTAT_CR] = color[0];
5175
- this.skinnedStaticData[statBase + SSTAT_CG] = color[1];
5176
- this.skinnedStaticData[statBase + SSTAT_CB] = color[2];
4564
+ const t = resolveTransform(opts);
4565
+ this.skinnedDynamicData[dynBase + DYN_PREV_PX] = t.px;
4566
+ this.skinnedDynamicData[dynBase + DYN_PREV_PY] = t.py;
4567
+ this.skinnedDynamicData[dynBase + DYN_PREV_PZ] = t.pz;
4568
+ this.skinnedDynamicData[dynBase + DYN_CURR_PX] = t.px;
4569
+ this.skinnedDynamicData[dynBase + DYN_CURR_PY] = t.py;
4570
+ this.skinnedDynamicData[dynBase + DYN_CURR_PZ] = t.pz;
4571
+ this.skinnedDynamicData[dynBase + DYN_PREV_RX] = t.rx;
4572
+ this.skinnedDynamicData[dynBase + DYN_PREV_RY] = t.ry;
4573
+ this.skinnedDynamicData[dynBase + DYN_PREV_RZ] = t.rz;
4574
+ this.skinnedDynamicData[dynBase + DYN_CURR_RX] = t.rx;
4575
+ this.skinnedDynamicData[dynBase + DYN_CURR_RY] = t.ry;
4576
+ this.skinnedDynamicData[dynBase + DYN_CURR_RZ] = t.rz;
4577
+ this.skinnedStaticData[statBase + SSTAT_SX] = t.sx;
4578
+ this.skinnedStaticData[statBase + SSTAT_SY] = t.sy;
4579
+ this.skinnedStaticData[statBase + SSTAT_SZ] = t.sz;
4580
+ this.skinnedStaticData[statBase + SSTAT_CR] = t.cr;
4581
+ this.skinnedStaticData[statBase + SSTAT_CG] = t.cg;
4582
+ this.skinnedStaticData[statBase + SSTAT_CB] = t.cb;
5177
4583
  new DataView(this.skinnedStaticData.buffer).setUint32(
5178
4584
  (statBase + SSTAT_BONE_OFFSET) * 4,
5179
4585
  boneOffset,
@@ -5217,16 +4623,102 @@ var WebGPU3DRenderer = class extends import_base_3d_renderer.Base3DRenderer {
5217
4623
  }
5218
4624
  };
5219
4625
  }
4626
+ /**
4627
+ * Drain pending resyncs from the coordinator. Per affected skin: rebuild
4628
+ * its `SkeletalAnimation` clip list densely, remap in-flight animStates,
4629
+ * then full-repack `packedAnimData`. Falls back to a kernel rebuild only
4630
+ * when the new data exceeds the kernel's allocated budgets.
4631
+ *
4632
+ * The dense renumbering is load-bearing: the kernel reads each clip by
4633
+ * its per-skin index, which must equal `SkeletalAnimation`'s clip id.
4634
+ */
4635
+ syncLazyAnimationChanges() {
4636
+ const resync = this.clipResync;
4637
+ if (!resync || resync.pending.size === 0)
4638
+ return;
4639
+ const remapsBySkin = /* @__PURE__ */ new Map();
4640
+ for (const skinIndex of resync.pending) {
4641
+ const sm = this.skinnedModels[skinIndex];
4642
+ if (!sm)
4643
+ continue;
4644
+ remapsBySkin.set(skinIndex, sm.animation.replaceClips(sm.parsedSkin.animClips));
4645
+ }
4646
+ resync.clear();
4647
+ for (let slot = 0; slot < this.maxSkinnedInstances; slot++) {
4648
+ const animState = this.skinnedAnimStates[slot];
4649
+ if (!animState)
4650
+ continue;
4651
+ const modelId = this.skinnedInstanceModelIds[slot];
4652
+ const model = this.models[modelId];
4653
+ if (!model || model.skinIndex < 0)
4654
+ continue;
4655
+ const remap3 = remapsBySkin.get(model.skinIndex);
4656
+ if (!remap3)
4657
+ continue;
4658
+ if (animState.clipId >= 0 && animState.clipId < remap3.length) {
4659
+ const next = remap3[animState.clipId];
4660
+ if (next < 0) {
4661
+ animState.clipId = -1;
4662
+ animState.playing = false;
4663
+ } else {
4664
+ animState.clipId = next;
4665
+ }
4666
+ }
4667
+ if (animState.prevClipId >= 0 && animState.prevClipId < remap3.length) {
4668
+ animState.prevClipId = remap3[animState.prevClipId];
4669
+ }
4670
+ }
4671
+ this.packedAnimData = (0, import_renderer5.createPackedAnimationData)();
4672
+ for (const sm of this.skinnedModels) {
4673
+ (0, import_renderer5.packSkinAndAnimations)(this.packedAnimData, sm.parsedSkin.data, sm.parsedSkin.animClips);
4674
+ }
4675
+ if (this.tryUploadInPlace())
4676
+ return;
4677
+ this.animComputeNeedsRebuild = true;
4678
+ }
4679
+ /** Doubling-growth budgets for kernel storage buffers, with sensible floors. */
4680
+ growBudgetsForPacked(packed, previous) {
4681
+ const pb = packAnimationData(packed);
4682
+ const grow = (cur, prev, floor) => Math.max(cur * 2, prev, floor);
4683
+ return {
4684
+ skelI32Capacity: grow(pb.skelI32.length, previous?.skelI32Capacity ?? 0, 256),
4685
+ animF32Capacity: grow(pb.animF32.length, previous?.animF32Capacity ?? 0, 4096),
4686
+ matricesCapacity: grow(pb.totalMats, previous?.matricesCapacity ?? 0, 8)
4687
+ };
4688
+ }
4689
+ /** Upload `packedAnimData` to the existing kernel iff it fits the current budgets. Returns false to force a rebuild. */
4690
+ tryUploadInPlace() {
4691
+ const kernel = this.animComputeKernel;
4692
+ const budgets = this.animKernelBudgets;
4693
+ if (!kernel || !budgets)
4694
+ return false;
4695
+ const pb = packAnimationData(this.packedAnimData);
4696
+ if (pb.skelI32.length > budgets.skelI32Capacity)
4697
+ return false;
4698
+ if (pb.animF32.length > budgets.animF32Capacity)
4699
+ return false;
4700
+ if (pb.totalMats > budgets.matricesCapacity)
4701
+ return false;
4702
+ uploadPackedToKernel(this.root, kernel, pb);
4703
+ this.animClipTableOffset = pb.clipTableOffset;
4704
+ this.animChannelTableOffset = pb.channelTableOffset;
4705
+ this.animJointLookupOffset = pb.jointLookupOffset;
4706
+ return true;
4707
+ }
5220
4708
  updateAnimations(deltaTime) {
4709
+ this.syncLazyAnimationChanges();
5221
4710
  if (this.animComputeNeedsRebuild && this.packedAnimData.clips.length > 0) {
5222
4711
  this.animComputeKernel?.destroy();
4712
+ const budgets = this.growBudgetsForPacked(this.packedAnimData, null);
5223
4713
  const { kernel, packedBuffers } = buildAnimationKernel(
5224
4714
  this.root,
5225
4715
  this.packedAnimData,
5226
4716
  this.maxSkinnedInstances,
5227
- this.maxTotalBones
4717
+ this.maxTotalBones,
4718
+ budgets
5228
4719
  );
5229
4720
  this.animComputeKernel = kernel;
4721
+ this.animKernelBudgets = budgets;
5230
4722
  this.animClipTableOffset = packedBuffers.clipTableOffset;
5231
4723
  this.animChannelTableOffset = packedBuffers.channelTableOffset;
5232
4724
  this.animJointLookupOffset = packedBuffers.jointLookupOffset;
@@ -5243,6 +4735,14 @@ var WebGPU3DRenderer = class extends import_base_3d_renderer.Base3DRenderer {
5243
4735
  { binding: 4, resource: { buffer: rawBoneBuffer } }
5244
4736
  ]
5245
4737
  });
4738
+ this.device.queue.writeBuffer(
4739
+ this.rawBoneMatrixBuffer,
4740
+ 0,
4741
+ this.boneMatrixData.buffer,
4742
+ this.boneMatrixData.byteOffset,
4743
+ this.boneMatrixData.byteLength
4744
+ );
4745
+ this.boneMatrixDirty = false;
5246
4746
  this.animComputeNeedsRebuild = false;
5247
4747
  }
5248
4748
  this.updatedBoneOffsets.fill(0);
@@ -5643,6 +5143,8 @@ var WebGPU3DRenderer = class extends import_base_3d_renderer.Base3DRenderer {
5643
5143
  this.resizeObserver?.disconnect();
5644
5144
  this.resizeObserver = null;
5645
5145
  this.resizeCallbacks.length = 0;
5146
+ this.clipResync?.dispose();
5147
+ this.clipResync = null;
5646
5148
  this.dynamicBuffer?.destroy();
5647
5149
  this.staticBuffer?.destroy();
5648
5150
  this.uniformBuffer?.destroy();
@@ -5790,10 +5292,8 @@ var ParticleEmitter = class {
5790
5292
  const sprite = this.renderer.addSprite({
5791
5293
  sheet: this.config.sheet,
5792
5294
  sprite: this.config.sprite ?? 0,
5793
- x,
5794
- y,
5795
- scaleX: size,
5796
- scaleY: size,
5295
+ position: [x, y],
5296
+ scale: size,
5797
5297
  opacity: 1,
5798
5298
  tint: this.config.color,
5799
5299
  layer: 255
@@ -5845,109 +5345,6 @@ var ParticleEmitter = class {
5845
5345
  }
5846
5346
  };
5847
5347
 
5848
- // src/2d/animation.ts
5849
- var AnimationController = class {
5850
- constructor() {
5851
- this.clips = [];
5852
- this.clipsByName = /* @__PURE__ */ new Map();
5853
- }
5854
- /**
5855
- * Register an animation clip. Returns the clip ID.
5856
- */
5857
- loadClip(config) {
5858
- const id = this.clips.length;
5859
- const clip = {
5860
- id,
5861
- name: config.name,
5862
- frames: new Uint16Array(config.frames),
5863
- durations: new Float32Array(config.durations),
5864
- frameCount: config.frames.length,
5865
- totalDuration: config.durations.reduce((sum, d7) => sum + d7, 0),
5866
- loop: config.loop
5867
- };
5868
- this.clips.push(clip);
5869
- this.clipsByName.set(config.name, id);
5870
- return id;
5871
- }
5872
- /**
5873
- * Get a clip by name.
5874
- */
5875
- getClipId(name) {
5876
- const id = this.clipsByName.get(name);
5877
- if (id === void 0)
5878
- throw new Error(`Animation clip "${name}" not found`);
5879
- return id;
5880
- }
5881
- /**
5882
- * Get a clip by ID.
5883
- */
5884
- getClip(id) {
5885
- return this.clips[id];
5886
- }
5887
- /**
5888
- * Create a new animation state for an entity. This is the per-entity data
5889
- * that tracks playback progress. Store it however you like (flat array, component, etc).
5890
- */
5891
- createState(clipId, speed = 1, playing = true) {
5892
- return { clipId, frame: 0, time: 0, speed, playing };
5893
- }
5894
- /**
5895
- * Advance an animation state by deltaTime (in seconds).
5896
- * Returns the current sprite frame ID, or -1 if not playing.
5897
- * Zero allocations.
5898
- */
5899
- update(state, deltaTime) {
5900
- if (!state.playing) {
5901
- return this.clips[state.clipId].frames[state.frame];
5902
- }
5903
- const clip = this.clips[state.clipId];
5904
- state.time += deltaTime * state.speed * 1e3;
5905
- while (state.time >= clip.durations[state.frame]) {
5906
- state.time -= clip.durations[state.frame];
5907
- state.frame++;
5908
- if (state.frame >= clip.frameCount) {
5909
- if (clip.loop) {
5910
- state.frame = 0;
5911
- } else {
5912
- state.frame = clip.frameCount - 1;
5913
- state.playing = false;
5914
- break;
5915
- }
5916
- }
5917
- }
5918
- return clip.frames[state.frame];
5919
- }
5920
- /**
5921
- * Play a different clip on an existing state. Resets frame and time.
5922
- */
5923
- play(state, clipId, speed) {
5924
- state.clipId = clipId;
5925
- state.frame = 0;
5926
- state.time = 0;
5927
- state.playing = true;
5928
- if (speed !== void 0)
5929
- state.speed = speed;
5930
- }
5931
- /**
5932
- * Stop playback.
5933
- */
5934
- stop(state) {
5935
- state.playing = false;
5936
- }
5937
- /**
5938
- * Resume playback.
5939
- */
5940
- resume(state) {
5941
- state.playing = true;
5942
- }
5943
- /**
5944
- * Number of loaded clips.
5945
- */
5946
- get clipCount() {
5947
- return this.clips.length;
5948
- }
5949
- };
5950
-
5951
5348
  // src/shaders/utils.ts
5952
5349
  var import_typegpu11 = __toESM(require("typegpu"), 1);
5953
5350
  var d6 = __toESM(require("typegpu/data"), 1);