@luma.gl/gltf 9.3.0-alpha.4 → 9.3.0-alpha.6

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 (79) hide show
  1. package/dist/dist.dev.js +397 -220
  2. package/dist/dist.min.js +98 -46
  3. package/dist/gltf/animations/animations.d.ts +16 -4
  4. package/dist/gltf/animations/animations.d.ts.map +1 -1
  5. package/dist/gltf/animations/interpolate.d.ts +4 -3
  6. package/dist/gltf/animations/interpolate.d.ts.map +1 -1
  7. package/dist/gltf/animations/interpolate.js +27 -36
  8. package/dist/gltf/animations/interpolate.js.map +1 -1
  9. package/dist/gltf/create-gltf-model.d.ts +6 -0
  10. package/dist/gltf/create-gltf-model.d.ts.map +1 -1
  11. package/dist/gltf/create-gltf-model.js +96 -44
  12. package/dist/gltf/create-gltf-model.js.map +1 -1
  13. package/dist/gltf/create-scenegraph-from-gltf.d.ts +15 -1
  14. package/dist/gltf/create-scenegraph-from-gltf.d.ts.map +1 -1
  15. package/dist/gltf/create-scenegraph-from-gltf.js +12 -6
  16. package/dist/gltf/create-scenegraph-from-gltf.js.map +1 -1
  17. package/dist/gltf/gltf-animator.d.ts +26 -0
  18. package/dist/gltf/gltf-animator.d.ts.map +1 -1
  19. package/dist/gltf/gltf-animator.js +22 -19
  20. package/dist/gltf/gltf-animator.js.map +1 -1
  21. package/dist/index.cjs +378 -210
  22. package/dist/index.cjs.map +4 -4
  23. package/dist/index.d.ts +1 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/parsers/parse-gltf-animations.d.ts +1 -0
  27. package/dist/parsers/parse-gltf-animations.d.ts.map +1 -1
  28. package/dist/parsers/parse-gltf-animations.js +46 -23
  29. package/dist/parsers/parse-gltf-animations.js.map +1 -1
  30. package/dist/parsers/parse-gltf-lights.d.ts.map +1 -1
  31. package/dist/parsers/parse-gltf-lights.js +40 -12
  32. package/dist/parsers/parse-gltf-lights.js.map +1 -1
  33. package/dist/parsers/parse-gltf.d.ts +16 -1
  34. package/dist/parsers/parse-gltf.d.ts.map +1 -1
  35. package/dist/parsers/parse-gltf.js +65 -57
  36. package/dist/parsers/parse-gltf.js.map +1 -1
  37. package/dist/parsers/parse-pbr-material.d.ts +46 -1
  38. package/dist/parsers/parse-pbr-material.d.ts.map +1 -1
  39. package/dist/parsers/parse-pbr-material.js +137 -13
  40. package/dist/parsers/parse-pbr-material.js.map +1 -1
  41. package/dist/pbr/pbr-environment.d.ts +6 -0
  42. package/dist/pbr/pbr-environment.d.ts.map +1 -1
  43. package/dist/pbr/pbr-environment.js +1 -0
  44. package/dist/pbr/pbr-environment.js.map +1 -1
  45. package/dist/pbr/pbr-material.d.ts +5 -0
  46. package/dist/pbr/pbr-material.d.ts.map +1 -1
  47. package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts +12 -1
  48. package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts.map +1 -1
  49. package/dist/webgl-to-webgpu/convert-webgl-attribute.js +3 -0
  50. package/dist/webgl-to-webgpu/convert-webgl-attribute.js.map +1 -1
  51. package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts +6 -0
  52. package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts.map +1 -1
  53. package/dist/webgl-to-webgpu/convert-webgl-sampler.js +4 -0
  54. package/dist/webgl-to-webgpu/convert-webgl-sampler.js.map +1 -1
  55. package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts +2 -0
  56. package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts.map +1 -1
  57. package/dist/webgl-to-webgpu/convert-webgl-topology.js +2 -0
  58. package/dist/webgl-to-webgpu/convert-webgl-topology.js.map +1 -1
  59. package/package.json +5 -5
  60. package/src/gltf/animations/animations.ts +17 -5
  61. package/src/gltf/animations/interpolate.ts +49 -68
  62. package/src/gltf/create-gltf-model.ts +101 -43
  63. package/src/gltf/create-scenegraph-from-gltf.ts +39 -11
  64. package/src/gltf/gltf-animator.ts +34 -25
  65. package/src/index.ts +1 -2
  66. package/src/parsers/parse-gltf-animations.ts +63 -26
  67. package/src/parsers/parse-gltf-lights.ts +51 -13
  68. package/src/parsers/parse-gltf.ts +90 -77
  69. package/src/parsers/parse-pbr-material.ts +204 -14
  70. package/src/pbr/pbr-environment.ts +10 -0
  71. package/src/pbr/pbr-material.ts +5 -0
  72. package/src/webgl-to-webgpu/convert-webgl-attribute.ts +12 -1
  73. package/src/webgl-to-webgpu/convert-webgl-sampler.ts +9 -0
  74. package/src/webgl-to-webgpu/convert-webgl-topology.ts +2 -0
  75. package/dist/utils/deep-copy.d.ts +0 -3
  76. package/dist/utils/deep-copy.d.ts.map +0 -1
  77. package/dist/utils/deep-copy.js +0 -21
  78. package/dist/utils/deep-copy.js.map +0 -1
  79. package/src/utils/deep-copy.ts +0 -22
package/dist/index.cjs CHANGED
@@ -93,8 +93,8 @@ function makeCube(device, { id, getTextureForFace, sampler }) {
93
93
  }
94
94
 
95
95
  // dist/parsers/parse-pbr-material.js
96
- var import_constants2 = require("@luma.gl/constants");
97
96
  var import_core = require("@luma.gl/core");
97
+ var import_constants2 = require("@luma.gl/constants");
98
98
 
99
99
  // dist/webgl-to-webgpu/convert-webgl-sampler.js
100
100
  var import_constants = require("@luma.gl/constants");
@@ -186,6 +186,8 @@ function parsePBRMaterial(device, material, attributes, options) {
186
186
  parsedMaterial.defines["HAS_TANGENTS"] = true;
187
187
  if (attributes["TEXCOORD_0"])
188
188
  parsedMaterial.defines["HAS_UV"] = true;
189
+ if (attributes["JOINTS_0"] && attributes["WEIGHTS_0"])
190
+ parsedMaterial.defines["HAS_SKIN"] = true;
189
191
  if (attributes["COLOR_0"])
190
192
  parsedMaterial.defines["HAS_COLORS"] = true;
191
193
  if (options == null ? void 0 : options.imageBasedLightingEnvironment)
@@ -256,12 +258,6 @@ function parsePbrMetallicRoughness(device, pbrMetallicRoughness, parsedMaterial)
256
258
  function addTexture(device, gltfTexture, uniformName, define, parsedMaterial) {
257
259
  var _a;
258
260
  const image = gltfTexture.texture.source.image;
259
- let textureOptions;
260
- if (image.compressed) {
261
- textureOptions = image;
262
- } else {
263
- textureOptions = { data: image };
264
- }
265
261
  const gltfSampler = {
266
262
  wrapS: 10497,
267
263
  // default REPEAT S (U) wrapping mode.
@@ -273,32 +269,136 @@ function addTexture(device, gltfTexture, uniformName, define, parsedMaterial) {
273
269
  // default LINEAR filtering
274
270
  ...(_a = gltfTexture == null ? void 0 : gltfTexture.texture) == null ? void 0 : _a.sampler
275
271
  };
276
- const texture = device.createTexture({
272
+ const baseOptions = {
277
273
  id: gltfTexture.uniformName || gltfTexture.id,
278
- sampler: convertSampler(gltfSampler),
279
- ...textureOptions
280
- });
274
+ sampler: convertSampler(gltfSampler)
275
+ };
276
+ let texture;
277
+ if (image.compressed) {
278
+ texture = createCompressedTexture(device, image, baseOptions);
279
+ } else {
280
+ const { width, height } = device.getExternalImageSize(image);
281
+ texture = device.createTexture({
282
+ ...baseOptions,
283
+ width,
284
+ height,
285
+ data: image
286
+ });
287
+ }
281
288
  parsedMaterial.bindings[uniformName] = texture;
282
289
  if (define)
283
290
  parsedMaterial.defines[define] = true;
284
291
  parsedMaterial.generatedTextures.push(texture);
285
292
  }
293
+ function createCompressedTextureFallback(device, baseOptions) {
294
+ return device.createTexture({
295
+ ...baseOptions,
296
+ format: "rgba8unorm",
297
+ width: 1,
298
+ height: 1,
299
+ mipLevels: 1
300
+ });
301
+ }
302
+ function resolveCompressedTextureFormat(level) {
303
+ return level.textureFormat;
304
+ }
305
+ function getMaxCompressedMipLevels(baseWidth, baseHeight, format) {
306
+ const { blockWidth = 1, blockHeight = 1 } = import_core.textureFormatDecoder.getInfo(format);
307
+ let count = 1;
308
+ for (let i = 1; ; i++) {
309
+ const w = Math.max(1, baseWidth >> i);
310
+ const h = Math.max(1, baseHeight >> i);
311
+ if (w < blockWidth || h < blockHeight)
312
+ break;
313
+ count++;
314
+ }
315
+ return count;
316
+ }
317
+ function createCompressedTexture(device, image, baseOptions) {
318
+ var _a, _b;
319
+ let levels;
320
+ if (Array.isArray(image.data) && ((_a = image.data[0]) == null ? void 0 : _a.data)) {
321
+ levels = image.data;
322
+ } else if ("mipmaps" in image && Array.isArray(image.mipmaps)) {
323
+ levels = image.mipmaps;
324
+ } else {
325
+ levels = [];
326
+ }
327
+ if (levels.length === 0 || !((_b = levels[0]) == null ? void 0 : _b.data)) {
328
+ import_core.log.warn("createCompressedTexture: compressed image has no valid mip levels, creating fallback")();
329
+ return createCompressedTextureFallback(device, baseOptions);
330
+ }
331
+ const baseLevel = levels[0];
332
+ const baseWidth = baseLevel.width ?? image.width ?? 0;
333
+ const baseHeight = baseLevel.height ?? image.height ?? 0;
334
+ if (baseWidth <= 0 || baseHeight <= 0) {
335
+ import_core.log.warn("createCompressedTexture: base level has invalid dimensions, creating fallback")();
336
+ return createCompressedTextureFallback(device, baseOptions);
337
+ }
338
+ const format = resolveCompressedTextureFormat(baseLevel);
339
+ if (!format) {
340
+ import_core.log.warn("createCompressedTexture: compressed image has no textureFormat, creating fallback")();
341
+ return createCompressedTextureFallback(device, baseOptions);
342
+ }
343
+ const maxMipLevels = getMaxCompressedMipLevels(baseWidth, baseHeight, format);
344
+ const levelLimit = Math.min(levels.length, maxMipLevels);
345
+ let validLevelCount = 1;
346
+ for (let i = 1; i < levelLimit; i++) {
347
+ const level = levels[i];
348
+ if (!level.data || level.width <= 0 || level.height <= 0) {
349
+ import_core.log.warn(`createCompressedTexture: mip level ${i} has invalid data/dimensions, truncating`)();
350
+ break;
351
+ }
352
+ const levelFormat = resolveCompressedTextureFormat(level);
353
+ if (levelFormat && levelFormat !== format) {
354
+ import_core.log.warn(`createCompressedTexture: mip level ${i} format '${levelFormat}' differs from base '${format}', truncating`)();
355
+ break;
356
+ }
357
+ const expectedW = Math.max(1, baseWidth >> i);
358
+ const expectedH = Math.max(1, baseHeight >> i);
359
+ if (level.width !== expectedW || level.height !== expectedH) {
360
+ import_core.log.warn(`createCompressedTexture: mip level ${i} dimensions ${level.width}x${level.height} don't match expected ${expectedW}x${expectedH}, truncating`)();
361
+ break;
362
+ }
363
+ validLevelCount++;
364
+ }
365
+ const texture = device.createTexture({
366
+ ...baseOptions,
367
+ format,
368
+ usage: import_core.Texture.TEXTURE | import_core.Texture.COPY_DST,
369
+ width: baseWidth,
370
+ height: baseHeight,
371
+ mipLevels: validLevelCount,
372
+ data: baseLevel.data
373
+ });
374
+ for (let i = 1; i < validLevelCount; i++) {
375
+ texture.writeData(levels[i].data, {
376
+ width: levels[i].width,
377
+ height: levels[i].height,
378
+ mipLevel: i
379
+ });
380
+ }
381
+ return texture;
382
+ }
286
383
 
287
384
  // dist/parsers/parse-gltf-lights.js
288
385
  var import_core2 = require("@math.gl/core");
289
386
  function parseGLTFLights(gltf) {
290
- var _a, _b, _c;
291
- const lightDefs = (_b = (_a = gltf.extensions) == null ? void 0 : _a["KHR_lights_punctual"]) == null ? void 0 : _b["lights"];
387
+ var _a, _b, _c, _d;
388
+ const lightDefs = (
389
+ // `postProcessGLTF()` moves KHR_lights_punctual into `gltf.lights`.
390
+ gltf.lights || ((_b = (_a = gltf.extensions) == null ? void 0 : _a["KHR_lights_punctual"]) == null ? void 0 : _b["lights"])
391
+ );
292
392
  if (!lightDefs || !Array.isArray(lightDefs) || lightDefs.length === 0) {
293
393
  return [];
294
394
  }
295
395
  const lights = [];
296
396
  for (const node of gltf.nodes || []) {
297
- const nodeLight = (_c = node.extensions) == null ? void 0 : _c.KHR_lights_punctual;
298
- if (!nodeLight || typeof nodeLight.light !== "number") {
397
+ const lightIndex = node.light ?? ((_d = (_c = node.extensions) == null ? void 0 : _c.KHR_lights_punctual) == null ? void 0 : _d.light);
398
+ if (typeof lightIndex !== "number") {
299
399
  continue;
300
400
  }
301
- const gltfLight = lightDefs[nodeLight.light];
401
+ const gltfLight = lightDefs[lightIndex];
302
402
  if (!gltfLight) {
303
403
  continue;
304
404
  }
@@ -322,7 +422,7 @@ function parseGLTFLights(gltf) {
322
422
  return lights;
323
423
  }
324
424
  function parsePointLight(node, color, intensity, range) {
325
- const position = node.translation ? [...node.translation] : [0, 0, 0];
425
+ const position = getNodePosition(node);
326
426
  let attenuation = [1, 0, 0];
327
427
  if (range !== void 0 && range > 0) {
328
428
  attenuation = [1, 0, 1 / (range * range)];
@@ -336,11 +436,7 @@ function parsePointLight(node, color, intensity, range) {
336
436
  };
337
437
  }
338
438
  function parseDirectionalLight(node, color, intensity) {
339
- let direction = [0, 0, -1];
340
- if (node.rotation) {
341
- const orientation = new import_core2.Matrix4().fromQuaternion(node.rotation);
342
- direction = orientation.transformDirection([0, 0, -1]);
343
- }
439
+ const direction = getNodeDirection(node);
344
440
  return {
345
441
  type: "directional",
346
442
  direction,
@@ -348,10 +444,27 @@ function parseDirectionalLight(node, color, intensity) {
348
444
  intensity
349
445
  };
350
446
  }
447
+ function getNodePosition(node) {
448
+ if (node.matrix) {
449
+ return new import_core2.Matrix4(node.matrix).transformAsPoint([0, 0, 0]);
450
+ }
451
+ if (node.translation) {
452
+ return [...node.translation];
453
+ }
454
+ return [0, 0, 0];
455
+ }
456
+ function getNodeDirection(node) {
457
+ if (node.matrix) {
458
+ return new import_core2.Matrix4(node.matrix).transformDirection([0, 0, -1]);
459
+ }
460
+ if (node.rotation) {
461
+ return new import_core2.Matrix4().fromQuaternion(node.rotation).transformDirection([0, 0, -1]);
462
+ }
463
+ return [0, 0, -1];
464
+ }
351
465
 
352
466
  // dist/parsers/parse-gltf.js
353
467
  var import_engine3 = require("@luma.gl/engine");
354
- var import_core4 = require("@math.gl/core");
355
468
 
356
469
  // dist/webgl-to-webgpu/convert-webgl-topology.js
357
470
  var GLEnum;
@@ -388,50 +501,88 @@ var import_engine2 = require("@luma.gl/engine");
388
501
  var SHADER = (
389
502
  /* WGSL */
390
503
  `
391
- layout(0) positions: vec4; // in vec4 POSITION;
392
-
393
- #ifdef HAS_NORMALS
394
- in vec4 normals; // in vec4 NORMAL;
395
- #endif
396
-
397
- #ifdef HAS_TANGENTS
398
- in vec4 TANGENT;
399
- #endif
504
+ struct VertexInputs {
505
+ @location(0) positions: vec3f,
506
+ #ifdef HAS_NORMALS
507
+ @location(1) normals: vec3f,
508
+ #endif
509
+ #ifdef HAS_TANGENTS
510
+ @location(2) TANGENT: vec4f,
511
+ #endif
512
+ #ifdef HAS_UV
513
+ @location(3) texCoords: vec2f,
514
+ #endif
515
+ #ifdef HAS_SKIN
516
+ @location(4) JOINTS_0: vec4u,
517
+ @location(5) WEIGHTS_0: vec4f,
518
+ #endif
519
+ };
400
520
 
401
- #ifdef HAS_UV
402
- // in vec2 TEXCOORD_0;
403
- in vec2 texCoords;
404
- #endif
521
+ struct FragmentInputs {
522
+ @builtin(position) position: vec4f,
523
+ @location(0) pbrPosition: vec3f,
524
+ @location(1) pbrUV: vec2f,
525
+ @location(2) pbrNormal: vec3f,
526
+ #ifdef HAS_TANGENTS
527
+ @location(3) pbrTangent: vec4f,
528
+ #endif
529
+ };
405
530
 
406
531
  @vertex
407
- void main(void) {
408
- vec4 _NORMAL = vec4(0.);
409
- vec4 _TANGENT = vec4(0.);
410
- vec2 _TEXCOORD_0 = vec2(0.);
532
+ fn vertexMain(inputs: VertexInputs) -> FragmentInputs {
533
+ var outputs: FragmentInputs;
534
+ var position = vec4f(inputs.positions, 1.0);
535
+ var normal = vec3f(0.0, 0.0, 1.0);
536
+ var tangent = vec4f(1.0, 0.0, 0.0, 1.0);
537
+ var uv = vec2f(0.0, 0.0);
411
538
 
412
- #ifdef HAS_NORMALS
413
- _NORMAL = normals;
414
- #endif
539
+ #ifdef HAS_NORMALS
540
+ normal = inputs.normals;
541
+ #endif
542
+ #ifdef HAS_UV
543
+ uv = inputs.texCoords;
544
+ #endif
545
+ #ifdef HAS_TANGENTS
546
+ tangent = inputs.TANGENT;
547
+ #endif
548
+ #ifdef HAS_SKIN
549
+ let skinMatrix = getSkinMatrix(inputs.WEIGHTS_0, inputs.JOINTS_0);
550
+ position = skinMatrix * position;
551
+ normal = normalize((skinMatrix * vec4f(normal, 0.0)).xyz);
552
+ #ifdef HAS_TANGENTS
553
+ tangent = vec4f(normalize((skinMatrix * vec4f(tangent.xyz, 0.0)).xyz), tangent.w);
554
+ #endif
555
+ #endif
415
556
 
416
- #ifdef HAS_TANGENTS
417
- _TANGENT = TANGENT;
418
- #endif
557
+ let worldPosition = pbrProjection.modelMatrix * position;
419
558
 
420
- #ifdef HAS_UV
421
- _TEXCOORD_0 = texCoords;
422
- #endif
559
+ #ifdef HAS_NORMALS
560
+ normal = normalize((pbrProjection.normalMatrix * vec4f(normal, 0.0)).xyz);
561
+ #endif
562
+ #ifdef HAS_TANGENTS
563
+ let worldTangent = normalize((pbrProjection.modelMatrix * vec4f(tangent.xyz, 0.0)).xyz);
564
+ outputs.pbrTangent = vec4f(worldTangent, tangent.w);
565
+ #endif
423
566
 
424
- pbr_setPositionNormalTangentUV(positions, _NORMAL, _TANGENT, _TEXCOORD_0);
425
- gl_Position = u_MVPMatrix * positions;
426
- }
567
+ outputs.position = pbrProjection.modelViewProjectionMatrix * position;
568
+ outputs.pbrPosition = worldPosition.xyz / worldPosition.w;
569
+ outputs.pbrUV = uv;
570
+ outputs.pbrNormal = normal;
571
+ return outputs;
572
+ }
427
573
 
428
574
  @fragment
429
- out vec4 fragmentColor;
430
-
431
- void main(void) {
432
- vec3 pos = pbr_vPosition;
433
- fragmentColor = pbr_filterColor(vec4(1.0));
434
- }
575
+ fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
576
+ fragmentInputs.pbr_vPosition = inputs.pbrPosition;
577
+ fragmentInputs.pbr_vUV = inputs.pbrUV;
578
+ fragmentInputs.pbr_vNormal = inputs.pbrNormal;
579
+ #ifdef HAS_TANGENTS
580
+ let tangent = normalize(inputs.pbrTangent.xyz);
581
+ let bitangent = normalize(cross(inputs.pbrNormal, tangent)) * inputs.pbrTangent.w;
582
+ fragmentInputs.pbr_vTBN = mat3x3f(tangent, bitangent, inputs.pbrNormal);
583
+ #endif
584
+ return pbr_filterColor(vec4f(1.0));
585
+ }
435
586
  `
436
587
  );
437
588
  var vs = (
@@ -455,6 +606,11 @@ var vs = (
455
606
  in vec2 texCoords;
456
607
  #endif
457
608
 
609
+ #ifdef HAS_SKIN
610
+ in uvec4 JOINTS_0;
611
+ in vec4 WEIGHTS_0;
612
+ #endif
613
+
458
614
  void main(void) {
459
615
  vec4 _NORMAL = vec4(0.);
460
616
  vec4 _TANGENT = vec4(0.);
@@ -472,8 +628,17 @@ var vs = (
472
628
  _TEXCOORD_0 = texCoords;
473
629
  #endif
474
630
 
475
- pbr_setPositionNormalTangentUV(positions, _NORMAL, _TANGENT, _TEXCOORD_0);
476
- gl_Position = pbrProjection.modelViewProjectionMatrix * positions;
631
+ vec4 pos = positions;
632
+
633
+ #ifdef HAS_SKIN
634
+ mat4 skinMat = getSkinMatrix(WEIGHTS_0, JOINTS_0);
635
+ pos = skinMat * pos;
636
+ _NORMAL = skinMat * _NORMAL;
637
+ _TANGENT = vec4((skinMat * vec4(_TANGENT.xyz, 0.)).xyz, _TANGENT.w);
638
+ #endif
639
+
640
+ pbr_setPositionNormalTangentUV(pos, _NORMAL, _TANGENT, _TEXCOORD_0);
641
+ gl_Position = pbrProjection.modelViewProjectionMatrix * pos;
477
642
  }
478
643
  `
479
644
  );
@@ -506,7 +671,7 @@ function createGLTFModel(device, options) {
506
671
  geometry,
507
672
  topology: geometry.topology,
508
673
  vertexCount,
509
- modules: [import_shadertools.pbrMaterial],
674
+ modules: [import_shadertools.pbrMaterial, import_shadertools.skin],
510
675
  ...modelOptions,
511
676
  defines: { ...parsedPPBRMaterial.defines, ...modelOptions.defines },
512
677
  parameters: { ...parameters, ...parsedPPBRMaterial.parameters, ...modelOptions.parameters }
@@ -530,65 +695,69 @@ var defaultOptions = {
530
695
  lights: true,
531
696
  useTangents: false
532
697
  };
533
- function parseGLTF(device, gltf, options_ = {}) {
534
- const options = { ...defaultOptions, ...options_ };
535
- const sceneNodes = gltf.scenes.map((gltfScene) => createScene(device, gltfScene, gltf.nodes, options));
536
- return sceneNodes;
537
- }
538
- function createScene(device, gltfScene, gltfNodes, options) {
539
- const gltfSceneNodes = gltfScene.nodes || [];
540
- const nodes = gltfSceneNodes.map((node) => createNode(device, node, gltfNodes, options));
541
- const sceneNode = new import_engine3.GroupNode({
542
- id: gltfScene.name || gltfScene.id,
543
- children: nodes
698
+ function parseGLTF(device, gltf, options = {}) {
699
+ const combinedOptions = { ...defaultOptions, ...options };
700
+ const gltfMeshIdToNodeMap = /* @__PURE__ */ new Map();
701
+ gltf.meshes.forEach((gltfMesh, idx) => {
702
+ const newMesh = createNodeForGLTFMesh(device, gltfMesh, combinedOptions);
703
+ gltfMeshIdToNodeMap.set(gltfMesh.id, newMesh);
544
704
  });
545
- return sceneNode;
546
- }
547
- function createNode(device, gltfNode, gltfNodes, options) {
548
- if (!gltfNode._node) {
549
- const gltfChildren = gltfNode.children || [];
550
- const children = gltfChildren.map((child) => createNode(device, child, gltfNodes, options));
705
+ const gltfNodeIndexToNodeMap = /* @__PURE__ */ new Map();
706
+ const gltfNodeIdToNodeMap = /* @__PURE__ */ new Map();
707
+ gltf.nodes.forEach((gltfNode, idx) => {
708
+ const newNode = createNodeForGLTFNode(device, gltfNode, combinedOptions);
709
+ gltfNodeIndexToNodeMap.set(idx, newNode);
710
+ gltfNodeIdToNodeMap.set(gltfNode.id, newNode);
711
+ });
712
+ gltf.nodes.forEach((gltfNode, idx) => {
713
+ gltfNodeIndexToNodeMap.get(idx).add((gltfNode.children ?? []).map(({ id }) => {
714
+ const child = gltfNodeIdToNodeMap.get(id);
715
+ if (!child)
716
+ throw new Error(`Cannot find child ${id} of node ${idx}`);
717
+ return child;
718
+ }));
551
719
  if (gltfNode.mesh) {
552
- children.push(createMesh(device, gltfNode.mesh, options));
553
- }
554
- const node = new import_engine3.GroupNode({
555
- id: gltfNode.name || gltfNode.id,
556
- children
557
- });
558
- if (gltfNode.matrix) {
559
- node.setMatrix(gltfNode.matrix);
560
- } else {
561
- node.matrix.identity();
562
- if (gltfNode.translation) {
563
- node.matrix.translate(gltfNode.translation);
564
- }
565
- if (gltfNode.rotation) {
566
- const rotationMatrix = new import_core4.Matrix4().fromQuaternion(gltfNode.rotation);
567
- node.matrix.multiplyRight(rotationMatrix);
568
- }
569
- if (gltfNode.scale) {
570
- node.matrix.scale(gltfNode.scale);
720
+ const mesh = gltfMeshIdToNodeMap.get(gltfNode.mesh.id);
721
+ if (!mesh) {
722
+ throw new Error(`Cannot find mesh child ${gltfNode.mesh.id} of node ${idx}`);
571
723
  }
724
+ gltfNodeIndexToNodeMap.get(idx).add(mesh);
572
725
  }
573
- gltfNode._node = node;
574
- }
575
- const topLevelNode = gltfNodes.find((node) => node.id === gltfNode.id);
576
- topLevelNode._node = gltfNode._node;
577
- return gltfNode._node;
578
- }
579
- function createMesh(device, gltfMesh, options) {
580
- if (!gltfMesh._mesh) {
581
- const gltfPrimitives = gltfMesh.primitives || [];
582
- const primitives = gltfPrimitives.map((gltfPrimitive, i) => createPrimitive(device, gltfPrimitive, i, gltfMesh, options));
583
- const mesh = new import_engine3.GroupNode({
584
- id: gltfMesh.name || gltfMesh.id,
585
- children: primitives
726
+ });
727
+ const scenes = gltf.scenes.map((gltfScene) => {
728
+ const children = (gltfScene.nodes || []).map(({ id }) => {
729
+ const child = gltfNodeIdToNodeMap.get(id);
730
+ if (!child)
731
+ throw new Error(`Cannot find child ${id} of scene ${gltfScene.name || gltfScene.id}`);
732
+ return child;
586
733
  });
587
- gltfMesh._mesh = mesh;
588
- }
589
- return gltfMesh._mesh;
734
+ return new import_engine3.GroupNode({
735
+ id: gltfScene.name || gltfScene.id,
736
+ children
737
+ });
738
+ });
739
+ return { scenes, gltfMeshIdToNodeMap, gltfNodeIdToNodeMap, gltfNodeIndexToNodeMap };
590
740
  }
591
- function createPrimitive(device, gltfPrimitive, i, gltfMesh, options) {
741
+ function createNodeForGLTFNode(device, gltfNode, options) {
742
+ return new import_engine3.GroupNode({
743
+ id: gltfNode.name || gltfNode.id,
744
+ children: [],
745
+ matrix: gltfNode.matrix,
746
+ position: gltfNode.translation,
747
+ rotation: gltfNode.rotation,
748
+ scale: gltfNode.scale
749
+ });
750
+ }
751
+ function createNodeForGLTFMesh(device, gltfMesh, options) {
752
+ const gltfPrimitives = gltfMesh.primitives || [];
753
+ const primitives = gltfPrimitives.map((gltfPrimitive, i) => createNodeForGLTFPrimitive(device, gltfPrimitive, i, gltfMesh, options));
754
+ const mesh = new import_engine3.GroupNode({
755
+ id: gltfMesh.name || gltfMesh.id,
756
+ children: primitives
757
+ });
758
+ return mesh;
759
+ }
760
+ function createNodeForGLTFPrimitive(device, gltfPrimitive, i, gltfMesh, options) {
592
761
  const id = gltfPrimitive.name || `${gltfMesh.name || gltfMesh.id}-primitive-${i}`;
593
762
  const topology = convertGLDrawModeToTopology(gltfPrimitive.mode || 4);
594
763
  const vertexCount = gltfPrimitive.indices ? gltfPrimitive.indices.count : getVertexCount(gltfPrimitive.attributes);
@@ -622,33 +791,29 @@ function createGeometry(id, gltfPrimitive, topology) {
622
791
  }
623
792
 
624
793
  // dist/gltf/gltf-animator.js
625
- var import_core7 = require("@luma.gl/core");
626
- var import_core8 = require("@math.gl/core");
794
+ var import_core6 = require("@luma.gl/core");
627
795
 
628
796
  // dist/gltf/animations/interpolate.js
629
- var import_core5 = require("@luma.gl/core");
630
- var import_core6 = require("@math.gl/core");
631
- var scratchQuaternion = new import_core6.Quaternion();
797
+ var import_core4 = require("@luma.gl/core");
798
+ var import_core5 = require("@math.gl/core");
799
+ function updateTargetPath(target, path, newValue) {
800
+ switch (path) {
801
+ case "translation":
802
+ return target.setPosition(newValue).updateMatrix();
803
+ case "rotation":
804
+ return target.setRotation(newValue).updateMatrix();
805
+ case "scale":
806
+ return target.setScale(newValue).updateMatrix();
807
+ default:
808
+ import_core4.log.warn(`Bad animation path ${path}`)();
809
+ return null;
810
+ }
811
+ }
632
812
  function interpolate(time, { input, interpolation, output }, target, path) {
633
813
  const maxTime = input[input.length - 1];
634
814
  const animationTime = time % maxTime;
635
815
  const nextIndex = input.findIndex((t) => t >= animationTime);
636
816
  const previousIndex = Math.max(0, nextIndex - 1);
637
- if (!Array.isArray(target[path])) {
638
- switch (path) {
639
- case "translation":
640
- target[path] = [0, 0, 0];
641
- break;
642
- case "rotation":
643
- target[path] = [0, 0, 0, 1];
644
- break;
645
- case "scale":
646
- target[path] = [1, 1, 1];
647
- break;
648
- default:
649
- import_core5.log.warn(`Bad animation path ${path}`)();
650
- }
651
- }
652
817
  const previousTime = input[previousIndex];
653
818
  const nextTime = input[nextIndex];
654
819
  switch (interpolation) {
@@ -673,103 +838,96 @@ function interpolate(time, { input, interpolation, output }, target, path) {
673
838
  }
674
839
  break;
675
840
  default:
676
- import_core5.log.warn(`Interpolation ${interpolation} not supported`)();
841
+ import_core4.log.warn(`Interpolation ${interpolation} not supported`)();
677
842
  break;
678
843
  }
679
844
  }
680
845
  function linearInterpolate(target, path, start, stop, ratio) {
681
- if (!target[path]) {
682
- throw new Error();
683
- }
684
846
  if (path === "rotation") {
685
- scratchQuaternion.slerp({ start, target: stop, ratio });
686
- for (let i = 0; i < scratchQuaternion.length; i++) {
687
- target[path][i] = scratchQuaternion[i];
688
- }
847
+ updateTargetPath(target, path, new import_core5.Quaternion().slerp({ start, target: stop, ratio }));
689
848
  } else {
849
+ const newVal = [];
690
850
  for (let i = 0; i < start.length; i++) {
691
- target[path][i] = ratio * stop[i] + (1 - ratio) * start[i];
851
+ newVal[i] = ratio * stop[i] + (1 - ratio) * start[i];
692
852
  }
853
+ updateTargetPath(target, path, newVal);
693
854
  }
694
855
  }
695
856
  function cubicsplineInterpolate(target, path, { p0, outTangent0, inTangent1, p1, tDiff, ratio: t }) {
696
- if (!target[path]) {
697
- throw new Error();
698
- }
699
- for (let i = 0; i < target[path].length; i++) {
857
+ const newVal = [];
858
+ for (let i = 0; i < p0.length; i++) {
700
859
  const m0 = outTangent0[i] * tDiff;
701
860
  const m1 = inTangent1[i] * tDiff;
702
- target[path][i] = (2 * Math.pow(t, 3) - 3 * Math.pow(t, 2) + 1) * p0[i] + (Math.pow(t, 3) - 2 * Math.pow(t, 2) + t) * m0 + (-2 * Math.pow(t, 3) + 3 * Math.pow(t, 2)) * p1[i] + (Math.pow(t, 3) - Math.pow(t, 2)) * m1;
861
+ newVal[i] = (2 * Math.pow(t, 3) - 3 * Math.pow(t, 2) + 1) * p0[i] + (Math.pow(t, 3) - 2 * Math.pow(t, 2) + t) * m0 + (-2 * Math.pow(t, 3) + 3 * Math.pow(t, 2)) * p1[i] + (Math.pow(t, 3) - Math.pow(t, 2)) * m1;
703
862
  }
863
+ updateTargetPath(target, path, newVal);
704
864
  }
705
865
  function stepInterpolate(target, path, value) {
706
- if (!target[path]) {
707
- throw new Error();
708
- }
709
- for (let i = 0; i < value.length; i++) {
710
- target[path][i] = value[i];
711
- }
866
+ updateTargetPath(target, path, value);
712
867
  }
713
868
 
714
869
  // dist/gltf/gltf-animator.js
715
870
  var GLTFSingleAnimator = class {
871
+ /** Animation definition being played. */
716
872
  animation;
873
+ /** Target scenegraph lookup table. */
874
+ gltfNodeIdToNodeMap;
875
+ /** Playback start time in seconds. */
717
876
  startTime = 0;
877
+ /** Whether playback is currently enabled. */
718
878
  playing = true;
879
+ /** Playback speed multiplier. */
719
880
  speed = 1;
881
+ /** Creates a single-animation controller. */
720
882
  constructor(props) {
721
883
  this.animation = props.animation;
884
+ this.gltfNodeIdToNodeMap = props.gltfNodeIdToNodeMap;
722
885
  this.animation.name ||= "unnamed";
723
886
  Object.assign(this, props);
724
887
  }
888
+ /** Advances the animation to the supplied wall-clock time in milliseconds. */
725
889
  setTime(timeMs) {
726
890
  if (!this.playing) {
727
891
  return;
728
892
  }
729
893
  const absTime = timeMs / 1e3;
730
894
  const time = (absTime - this.startTime) * this.speed;
731
- this.animation.channels.forEach(({ sampler, target, path }) => {
732
- interpolate(time, sampler, target, path);
733
- applyTranslationRotationScale(target, target._node);
895
+ this.animation.channels.forEach(({ sampler, targetNodeId, path }) => {
896
+ const targetNode = this.gltfNodeIdToNodeMap.get(targetNodeId);
897
+ if (!targetNode) {
898
+ throw new Error(`Cannot find animation target node ${targetNodeId}`);
899
+ }
900
+ interpolate(time, sampler, targetNode, path);
734
901
  });
735
902
  }
736
903
  };
737
904
  var GLTFAnimator = class {
905
+ /** Individual animation controllers. */
738
906
  animations;
907
+ /** Creates an animator for the supplied glTF scenegraph. */
739
908
  constructor(props) {
740
909
  this.animations = props.animations.map((animation, index) => {
741
910
  const name = animation.name || `Animation-${index}`;
742
911
  return new GLTFSingleAnimator({
912
+ gltfNodeIdToNodeMap: props.gltfNodeIdToNodeMap,
743
913
  animation: { name, channels: animation.channels }
744
914
  });
745
915
  });
746
916
  }
747
917
  /** @deprecated Use .setTime(). Will be removed (deck.gl is using this) */
748
918
  animate(time) {
749
- import_core7.log.warn("GLTFAnimator#animate is deprecated. Use GLTFAnimator#setTime instead")();
919
+ import_core6.log.warn("GLTFAnimator#animate is deprecated. Use GLTFAnimator#setTime instead")();
750
920
  this.setTime(time);
751
921
  }
922
+ /** Advances every animation to the supplied wall-clock time in milliseconds. */
752
923
  setTime(time) {
753
924
  this.animations.forEach((animation) => animation.setTime(time));
754
925
  }
926
+ /** Returns the per-animation controllers managed by this animator. */
755
927
  getAnimations() {
756
928
  return this.animations;
757
929
  }
758
930
  };
759
- var scratchMatrix = new import_core8.Matrix4();
760
- function applyTranslationRotationScale(gltfNode, node) {
761
- node.matrix.identity();
762
- if (gltfNode.translation) {
763
- node.matrix.translate(gltfNode.translation);
764
- }
765
- if (gltfNode.rotation) {
766
- const rotationMatrix = scratchMatrix.fromQuaternion(gltfNode.rotation);
767
- node.matrix.multiplyRight(rotationMatrix);
768
- }
769
- if (gltfNode.scale) {
770
- node.matrix.scale(gltfNode.scale);
771
- }
772
- }
773
931
 
774
932
  // dist/webgl-to-webgpu/convert-webgl-attribute.js
775
933
  var ATTRIBUTE_TYPE_TO_COMPONENTS = {
@@ -802,62 +960,72 @@ function accessorToTypedArray(accessor) {
802
960
  // dist/parsers/parse-gltf-animations.js
803
961
  function parseGLTFAnimations(gltf) {
804
962
  const gltfAnimations = gltf.animations || [];
963
+ const accessorCache1D = /* @__PURE__ */ new Map();
964
+ const accessorCache2D = /* @__PURE__ */ new Map();
805
965
  return gltfAnimations.map((animation, index) => {
806
966
  const name = animation.name || `Animation-${index}`;
807
967
  const samplers = animation.samplers.map(({ input, interpolation = "LINEAR", output }) => ({
808
- input: accessorToJsArray(gltf.accessors[input]),
968
+ input: accessorToJsArray1D(gltf.accessors[input], accessorCache1D),
809
969
  interpolation,
810
- output: accessorToJsArray(gltf.accessors[output])
811
- }));
812
- const channels = animation.channels.map(({ sampler, target }) => ({
813
- sampler: samplers[sampler],
814
- target: gltf.nodes[target.node ?? 0],
815
- path: target.path
970
+ output: accessorToJsArray2D(gltf.accessors[output], accessorCache2D)
816
971
  }));
972
+ const channels = animation.channels.map(({ sampler, target }) => {
973
+ const targetNode = gltf.nodes[target.node ?? 0];
974
+ if (!targetNode) {
975
+ throw new Error(`Cannot find animation target ${target.node}`);
976
+ }
977
+ return {
978
+ sampler: samplers[sampler],
979
+ targetNodeId: targetNode.id,
980
+ path: target.path
981
+ };
982
+ });
817
983
  return { name, channels };
818
984
  });
819
985
  }
820
- function accessorToJsArray(accessor) {
821
- if (!accessor._animation) {
822
- const { typedArray: array, components } = accessorToTypedArray(accessor);
823
- if (components === 1) {
824
- accessor._animation = Array.from(array);
825
- } else {
826
- const slicedArray = [];
827
- for (let i = 0; i < array.length; i += components) {
828
- slicedArray.push(Array.from(array.slice(i, i + components)));
829
- }
830
- accessor._animation = slicedArray;
831
- }
986
+ function accessorToJsArray1D(accessor, accessorCache) {
987
+ if (accessorCache.has(accessor)) {
988
+ return accessorCache.get(accessor);
832
989
  }
833
- return accessor._animation;
990
+ const { typedArray: array, components } = accessorToTypedArray(accessor);
991
+ assert(components === 1, "accessorToJsArray1D must have exactly 1 component");
992
+ const result = Array.from(array);
993
+ accessorCache.set(accessor, result);
994
+ return result;
834
995
  }
835
-
836
- // dist/utils/deep-copy.js
837
- function deepCopy(object) {
838
- if (ArrayBuffer.isView(object) || object instanceof ArrayBuffer || object instanceof ImageBitmap) {
839
- return object;
996
+ function accessorToJsArray2D(accessor, accessorCache) {
997
+ if (accessorCache.has(accessor)) {
998
+ return accessorCache.get(accessor);
840
999
  }
841
- if (Array.isArray(object)) {
842
- return object.map(deepCopy);
1000
+ const { typedArray: array, components } = accessorToTypedArray(accessor);
1001
+ assert(components > 1, "accessorToJsArray2D must have more than 1 component");
1002
+ const result = [];
1003
+ for (let i = 0; i < array.length; i += components) {
1004
+ result.push(Array.from(array.slice(i, i + components)));
843
1005
  }
844
- if (object && typeof object === "object") {
845
- const result = {};
846
- for (const key in object) {
847
- result[key] = deepCopy(object[key]);
848
- }
849
- return result;
1006
+ accessorCache.set(accessor, result);
1007
+ return result;
1008
+ }
1009
+ function assert(condition, message) {
1010
+ if (!condition) {
1011
+ throw new Error(message);
850
1012
  }
851
- return object;
852
1013
  }
853
1014
 
854
1015
  // dist/gltf/create-scenegraph-from-gltf.js
855
1016
  function createScenegraphsFromGLTF(device, gltf, options) {
856
- gltf = deepCopy(gltf);
857
- const scenes = parseGLTF(device, gltf, options);
1017
+ const { scenes, gltfMeshIdToNodeMap, gltfNodeIdToNodeMap, gltfNodeIndexToNodeMap } = parseGLTF(device, gltf, options);
858
1018
  const animations = parseGLTFAnimations(gltf);
859
- const animator = new GLTFAnimator({ animations });
1019
+ const animator = new GLTFAnimator({ animations, gltfNodeIdToNodeMap });
860
1020
  const lights = parseGLTFLights(gltf);
861
- return { scenes, animator, lights };
1021
+ return {
1022
+ scenes,
1023
+ animator,
1024
+ lights,
1025
+ gltfMeshIdToNodeMap,
1026
+ gltfNodeIdToNodeMap,
1027
+ gltfNodeIndexToNodeMap,
1028
+ gltf
1029
+ };
862
1030
  }
863
1031
  //# sourceMappingURL=index.cjs.map