@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/dist.dev.js CHANGED
@@ -122,7 +122,7 @@ var __exports__ = (() => {
122
122
  }
123
123
 
124
124
  // ../../node_modules/@loaders.gl/images/dist/lib/utils/version.js
125
- var VERSION = true ? "4.3.2" : "latest";
125
+ var VERSION = true ? "4.4.0-alpha.18" : "latest";
126
126
 
127
127
  // ../../node_modules/@loaders.gl/images/dist/lib/category-api/image-type.js
128
128
  var parseImageNode = globalThis.loaders?.parseImageNode;
@@ -263,7 +263,6 @@ var __exports__ = (() => {
263
263
  }
264
264
 
265
265
  // ../../node_modules/@loaders.gl/images/dist/lib/parsers/parse-to-image-bitmap.js
266
- var EMPTY_OBJECT = {};
267
266
  var imagebitmapOptionsSupported = true;
268
267
  async function parseToImageBitmap(arrayBuffer, options, url) {
269
268
  let blob;
@@ -291,8 +290,13 @@ var __exports__ = (() => {
291
290
  return await createImageBitmap(blob);
292
291
  }
293
292
  function isEmptyObject(object) {
294
- for (const key in object || EMPTY_OBJECT) {
295
- return false;
293
+ if (!object) {
294
+ return true;
295
+ }
296
+ for (const key in object) {
297
+ if (Object.prototype.hasOwnProperty.call(object, key)) {
298
+ return false;
299
+ }
296
300
  }
297
301
  return true;
298
302
  }
@@ -681,8 +685,8 @@ var __exports__ = (() => {
681
685
  }
682
686
 
683
687
  // src/parsers/parse-pbr-material.ts
684
- var import_constants2 = __toESM(require_constants(), 1);
685
688
  var import_core = __toESM(require_core(), 1);
689
+ var import_constants2 = __toESM(require_constants(), 1);
686
690
 
687
691
  // src/webgl-to-webgpu/convert-webgl-sampler.ts
688
692
  var import_constants = __toESM(require_constants(), 1);
@@ -774,6 +778,8 @@ var __exports__ = (() => {
774
778
  parsedMaterial.defines["HAS_TANGENTS"] = true;
775
779
  if (attributes["TEXCOORD_0"])
776
780
  parsedMaterial.defines["HAS_UV"] = true;
781
+ if (attributes["JOINTS_0"] && attributes["WEIGHTS_0"])
782
+ parsedMaterial.defines["HAS_SKIN"] = true;
777
783
  if (attributes["COLOR_0"])
778
784
  parsedMaterial.defines["HAS_COLORS"] = true;
779
785
  if (options?.imageBasedLightingEnvironment)
@@ -873,12 +879,6 @@ var __exports__ = (() => {
873
879
  }
874
880
  function addTexture(device, gltfTexture, uniformName, define, parsedMaterial) {
875
881
  const image = gltfTexture.texture.source.image;
876
- let textureOptions;
877
- if (image.compressed) {
878
- textureOptions = image;
879
- } else {
880
- textureOptions = { data: image };
881
- }
882
882
  const gltfSampler = {
883
883
  wrapS: 10497,
884
884
  // default REPEAT S (U) wrapping mode.
@@ -890,16 +890,122 @@ var __exports__ = (() => {
890
890
  // default LINEAR filtering
891
891
  ...gltfTexture?.texture?.sampler
892
892
  };
893
- const texture = device.createTexture({
893
+ const baseOptions = {
894
894
  id: gltfTexture.uniformName || gltfTexture.id,
895
- sampler: convertSampler(gltfSampler),
896
- ...textureOptions
897
- });
895
+ sampler: convertSampler(gltfSampler)
896
+ };
897
+ let texture;
898
+ if (image.compressed) {
899
+ texture = createCompressedTexture(device, image, baseOptions);
900
+ } else {
901
+ const { width, height } = device.getExternalImageSize(image);
902
+ texture = device.createTexture({
903
+ ...baseOptions,
904
+ width,
905
+ height,
906
+ data: image
907
+ });
908
+ }
898
909
  parsedMaterial.bindings[uniformName] = texture;
899
910
  if (define)
900
911
  parsedMaterial.defines[define] = true;
901
912
  parsedMaterial.generatedTextures.push(texture);
902
913
  }
914
+ function createCompressedTextureFallback(device, baseOptions) {
915
+ return device.createTexture({
916
+ ...baseOptions,
917
+ format: "rgba8unorm",
918
+ width: 1,
919
+ height: 1,
920
+ mipLevels: 1
921
+ });
922
+ }
923
+ function resolveCompressedTextureFormat(level) {
924
+ return level.textureFormat;
925
+ }
926
+ function getMaxCompressedMipLevels(baseWidth, baseHeight, format) {
927
+ const { blockWidth = 1, blockHeight = 1 } = import_core.textureFormatDecoder.getInfo(format);
928
+ let count = 1;
929
+ for (let i = 1; ; i++) {
930
+ const w = Math.max(1, baseWidth >> i);
931
+ const h = Math.max(1, baseHeight >> i);
932
+ if (w < blockWidth || h < blockHeight)
933
+ break;
934
+ count++;
935
+ }
936
+ return count;
937
+ }
938
+ function createCompressedTexture(device, image, baseOptions) {
939
+ let levels;
940
+ if (Array.isArray(image.data) && image.data[0]?.data) {
941
+ levels = image.data;
942
+ } else if ("mipmaps" in image && Array.isArray(image.mipmaps)) {
943
+ levels = image.mipmaps;
944
+ } else {
945
+ levels = [];
946
+ }
947
+ if (levels.length === 0 || !levels[0]?.data) {
948
+ import_core.log.warn(
949
+ "createCompressedTexture: compressed image has no valid mip levels, creating fallback"
950
+ )();
951
+ return createCompressedTextureFallback(device, baseOptions);
952
+ }
953
+ const baseLevel = levels[0];
954
+ const baseWidth = baseLevel.width ?? image.width ?? 0;
955
+ const baseHeight = baseLevel.height ?? image.height ?? 0;
956
+ if (baseWidth <= 0 || baseHeight <= 0) {
957
+ import_core.log.warn("createCompressedTexture: base level has invalid dimensions, creating fallback")();
958
+ return createCompressedTextureFallback(device, baseOptions);
959
+ }
960
+ const format = resolveCompressedTextureFormat(baseLevel);
961
+ if (!format) {
962
+ import_core.log.warn("createCompressedTexture: compressed image has no textureFormat, creating fallback")();
963
+ return createCompressedTextureFallback(device, baseOptions);
964
+ }
965
+ const maxMipLevels = getMaxCompressedMipLevels(baseWidth, baseHeight, format);
966
+ const levelLimit = Math.min(levels.length, maxMipLevels);
967
+ let validLevelCount = 1;
968
+ for (let i = 1; i < levelLimit; i++) {
969
+ const level = levels[i];
970
+ if (!level.data || level.width <= 0 || level.height <= 0) {
971
+ import_core.log.warn(`createCompressedTexture: mip level ${i} has invalid data/dimensions, truncating`)();
972
+ break;
973
+ }
974
+ const levelFormat = resolveCompressedTextureFormat(level);
975
+ if (levelFormat && levelFormat !== format) {
976
+ import_core.log.warn(
977
+ `createCompressedTexture: mip level ${i} format '${levelFormat}' differs from base '${format}', truncating`
978
+ )();
979
+ break;
980
+ }
981
+ const expectedW = Math.max(1, baseWidth >> i);
982
+ const expectedH = Math.max(1, baseHeight >> i);
983
+ if (level.width !== expectedW || level.height !== expectedH) {
984
+ import_core.log.warn(
985
+ `createCompressedTexture: mip level ${i} dimensions ${level.width}x${level.height} don't match expected ${expectedW}x${expectedH}, truncating`
986
+ )();
987
+ break;
988
+ }
989
+ validLevelCount++;
990
+ }
991
+ const texture = device.createTexture({
992
+ ...baseOptions,
993
+ format,
994
+ usage: import_core.Texture.TEXTURE | import_core.Texture.COPY_DST,
995
+ width: baseWidth,
996
+ height: baseHeight,
997
+ mipLevels: validLevelCount,
998
+ data: baseLevel.data
999
+ });
1000
+ for (let i = 1; i < validLevelCount; i++) {
1001
+ texture.writeData(levels[i].data, {
1002
+ width: levels[i].width,
1003
+ height: levels[i].height,
1004
+ mipLevel: i
1005
+ });
1006
+ }
1007
+ return texture;
1008
+ }
903
1009
 
904
1010
  // ../../node_modules/@math.gl/core/dist/lib/common.js
905
1011
  var RADIANS_TO_DEGREES = 1 / Math.PI * 180;
@@ -3474,17 +3580,20 @@ var __exports__ = (() => {
3474
3580
 
3475
3581
  // src/parsers/parse-gltf-lights.ts
3476
3582
  function parseGLTFLights(gltf) {
3477
- const lightDefs = gltf.extensions?.["KHR_lights_punctual"]?.["lights"];
3583
+ const lightDefs = (
3584
+ // `postProcessGLTF()` moves KHR_lights_punctual into `gltf.lights`.
3585
+ gltf.lights || gltf.extensions?.["KHR_lights_punctual"]?.["lights"]
3586
+ );
3478
3587
  if (!lightDefs || !Array.isArray(lightDefs) || lightDefs.length === 0) {
3479
3588
  return [];
3480
3589
  }
3481
3590
  const lights = [];
3482
3591
  for (const node of gltf.nodes || []) {
3483
- const nodeLight = node.extensions?.KHR_lights_punctual;
3484
- if (!nodeLight || typeof nodeLight.light !== "number") {
3592
+ const lightIndex = node.light ?? node.extensions?.KHR_lights_punctual?.light;
3593
+ if (typeof lightIndex !== "number") {
3485
3594
  continue;
3486
3595
  }
3487
- const gltfLight = lightDefs[nodeLight.light];
3596
+ const gltfLight = lightDefs[lightIndex];
3488
3597
  if (!gltfLight) {
3489
3598
  continue;
3490
3599
  }
@@ -3508,7 +3617,7 @@ var __exports__ = (() => {
3508
3617
  return lights;
3509
3618
  }
3510
3619
  function parsePointLight(node, color, intensity, range) {
3511
- const position = node.translation ? [...node.translation] : [0, 0, 0];
3620
+ const position = getNodePosition(node);
3512
3621
  let attenuation = [1, 0, 0];
3513
3622
  if (range !== void 0 && range > 0) {
3514
3623
  attenuation = [1, 0, 1 / (range * range)];
@@ -3522,11 +3631,7 @@ var __exports__ = (() => {
3522
3631
  };
3523
3632
  }
3524
3633
  function parseDirectionalLight(node, color, intensity) {
3525
- let direction = [0, 0, -1];
3526
- if (node.rotation) {
3527
- const orientation = new Matrix4().fromQuaternion(node.rotation);
3528
- direction = orientation.transformDirection([0, 0, -1]);
3529
- }
3634
+ const direction = getNodeDirection(node);
3530
3635
  return {
3531
3636
  type: "directional",
3532
3637
  direction,
@@ -3534,6 +3639,24 @@ var __exports__ = (() => {
3534
3639
  intensity
3535
3640
  };
3536
3641
  }
3642
+ function getNodePosition(node) {
3643
+ if (node.matrix) {
3644
+ return new Matrix4(node.matrix).transformAsPoint([0, 0, 0]);
3645
+ }
3646
+ if (node.translation) {
3647
+ return [...node.translation];
3648
+ }
3649
+ return [0, 0, 0];
3650
+ }
3651
+ function getNodeDirection(node) {
3652
+ if (node.matrix) {
3653
+ return new Matrix4(node.matrix).transformDirection([0, 0, -1]);
3654
+ }
3655
+ if (node.rotation) {
3656
+ return new Matrix4().fromQuaternion(node.rotation).transformDirection([0, 0, -1]);
3657
+ }
3658
+ return [0, 0, -1];
3659
+ }
3537
3660
 
3538
3661
  // src/parsers/parse-gltf.ts
3539
3662
  var import_engine3 = __toESM(require_engine(), 1);
@@ -3563,50 +3686,88 @@ var __exports__ = (() => {
3563
3686
  var SHADER = (
3564
3687
  /* WGSL */
3565
3688
  `
3566
- layout(0) positions: vec4; // in vec4 POSITION;
3689
+ struct VertexInputs {
3690
+ @location(0) positions: vec3f,
3691
+ #ifdef HAS_NORMALS
3692
+ @location(1) normals: vec3f,
3693
+ #endif
3694
+ #ifdef HAS_TANGENTS
3695
+ @location(2) TANGENT: vec4f,
3696
+ #endif
3697
+ #ifdef HAS_UV
3698
+ @location(3) texCoords: vec2f,
3699
+ #endif
3700
+ #ifdef HAS_SKIN
3701
+ @location(4) JOINTS_0: vec4u,
3702
+ @location(5) WEIGHTS_0: vec4f,
3703
+ #endif
3704
+ };
3567
3705
 
3568
- #ifdef HAS_NORMALS
3569
- in vec4 normals; // in vec4 NORMAL;
3570
- #endif
3571
-
3572
- #ifdef HAS_TANGENTS
3573
- in vec4 TANGENT;
3574
- #endif
3575
-
3576
- #ifdef HAS_UV
3577
- // in vec2 TEXCOORD_0;
3578
- in vec2 texCoords;
3579
- #endif
3706
+ struct FragmentInputs {
3707
+ @builtin(position) position: vec4f,
3708
+ @location(0) pbrPosition: vec3f,
3709
+ @location(1) pbrUV: vec2f,
3710
+ @location(2) pbrNormal: vec3f,
3711
+ #ifdef HAS_TANGENTS
3712
+ @location(3) pbrTangent: vec4f,
3713
+ #endif
3714
+ };
3580
3715
 
3581
3716
  @vertex
3582
- void main(void) {
3583
- vec4 _NORMAL = vec4(0.);
3584
- vec4 _TANGENT = vec4(0.);
3585
- vec2 _TEXCOORD_0 = vec2(0.);
3717
+ fn vertexMain(inputs: VertexInputs) -> FragmentInputs {
3718
+ var outputs: FragmentInputs;
3719
+ var position = vec4f(inputs.positions, 1.0);
3720
+ var normal = vec3f(0.0, 0.0, 1.0);
3721
+ var tangent = vec4f(1.0, 0.0, 0.0, 1.0);
3722
+ var uv = vec2f(0.0, 0.0);
3586
3723
 
3587
- #ifdef HAS_NORMALS
3588
- _NORMAL = normals;
3589
- #endif
3724
+ #ifdef HAS_NORMALS
3725
+ normal = inputs.normals;
3726
+ #endif
3727
+ #ifdef HAS_UV
3728
+ uv = inputs.texCoords;
3729
+ #endif
3730
+ #ifdef HAS_TANGENTS
3731
+ tangent = inputs.TANGENT;
3732
+ #endif
3733
+ #ifdef HAS_SKIN
3734
+ let skinMatrix = getSkinMatrix(inputs.WEIGHTS_0, inputs.JOINTS_0);
3735
+ position = skinMatrix * position;
3736
+ normal = normalize((skinMatrix * vec4f(normal, 0.0)).xyz);
3737
+ #ifdef HAS_TANGENTS
3738
+ tangent = vec4f(normalize((skinMatrix * vec4f(tangent.xyz, 0.0)).xyz), tangent.w);
3739
+ #endif
3740
+ #endif
3590
3741
 
3591
- #ifdef HAS_TANGENTS
3592
- _TANGENT = TANGENT;
3593
- #endif
3742
+ let worldPosition = pbrProjection.modelMatrix * position;
3594
3743
 
3595
- #ifdef HAS_UV
3596
- _TEXCOORD_0 = texCoords;
3597
- #endif
3744
+ #ifdef HAS_NORMALS
3745
+ normal = normalize((pbrProjection.normalMatrix * vec4f(normal, 0.0)).xyz);
3746
+ #endif
3747
+ #ifdef HAS_TANGENTS
3748
+ let worldTangent = normalize((pbrProjection.modelMatrix * vec4f(tangent.xyz, 0.0)).xyz);
3749
+ outputs.pbrTangent = vec4f(worldTangent, tangent.w);
3750
+ #endif
3598
3751
 
3599
- pbr_setPositionNormalTangentUV(positions, _NORMAL, _TANGENT, _TEXCOORD_0);
3600
- gl_Position = u_MVPMatrix * positions;
3601
- }
3752
+ outputs.position = pbrProjection.modelViewProjectionMatrix * position;
3753
+ outputs.pbrPosition = worldPosition.xyz / worldPosition.w;
3754
+ outputs.pbrUV = uv;
3755
+ outputs.pbrNormal = normal;
3756
+ return outputs;
3757
+ }
3602
3758
 
3603
3759
  @fragment
3604
- out vec4 fragmentColor;
3605
-
3606
- void main(void) {
3607
- vec3 pos = pbr_vPosition;
3608
- fragmentColor = pbr_filterColor(vec4(1.0));
3609
- }
3760
+ fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
3761
+ fragmentInputs.pbr_vPosition = inputs.pbrPosition;
3762
+ fragmentInputs.pbr_vUV = inputs.pbrUV;
3763
+ fragmentInputs.pbr_vNormal = inputs.pbrNormal;
3764
+ #ifdef HAS_TANGENTS
3765
+ let tangent = normalize(inputs.pbrTangent.xyz);
3766
+ let bitangent = normalize(cross(inputs.pbrNormal, tangent)) * inputs.pbrTangent.w;
3767
+ fragmentInputs.pbr_vTBN = mat3x3f(tangent, bitangent, inputs.pbrNormal);
3768
+ #endif
3769
+ return pbr_filterColor(vec4f(1.0));
3770
+ }
3610
3771
  `
3611
3772
  );
3612
3773
  var vs = (
@@ -3630,6 +3791,11 @@ layout(0) positions: vec4; // in vec4 POSITION;
3630
3791
  in vec2 texCoords;
3631
3792
  #endif
3632
3793
 
3794
+ #ifdef HAS_SKIN
3795
+ in uvec4 JOINTS_0;
3796
+ in vec4 WEIGHTS_0;
3797
+ #endif
3798
+
3633
3799
  void main(void) {
3634
3800
  vec4 _NORMAL = vec4(0.);
3635
3801
  vec4 _TANGENT = vec4(0.);
@@ -3647,8 +3813,17 @@ layout(0) positions: vec4; // in vec4 POSITION;
3647
3813
  _TEXCOORD_0 = texCoords;
3648
3814
  #endif
3649
3815
 
3650
- pbr_setPositionNormalTangentUV(positions, _NORMAL, _TANGENT, _TEXCOORD_0);
3651
- gl_Position = pbrProjection.modelViewProjectionMatrix * positions;
3816
+ vec4 pos = positions;
3817
+
3818
+ #ifdef HAS_SKIN
3819
+ mat4 skinMat = getSkinMatrix(WEIGHTS_0, JOINTS_0);
3820
+ pos = skinMat * pos;
3821
+ _NORMAL = skinMat * _NORMAL;
3822
+ _TANGENT = vec4((skinMat * vec4(_TANGENT.xyz, 0.)).xyz, _TANGENT.w);
3823
+ #endif
3824
+
3825
+ pbr_setPositionNormalTangentUV(pos, _NORMAL, _TANGENT, _TEXCOORD_0);
3826
+ gl_Position = pbrProjection.modelViewProjectionMatrix * pos;
3652
3827
  }
3653
3828
  `
3654
3829
  );
@@ -3681,7 +3856,7 @@ layout(0) positions: vec4; // in vec4 POSITION;
3681
3856
  geometry,
3682
3857
  topology: geometry.topology,
3683
3858
  vertexCount,
3684
- modules: [import_shadertools.pbrMaterial],
3859
+ modules: [import_shadertools.pbrMaterial, import_shadertools.skin],
3685
3860
  ...modelOptions,
3686
3861
  defines: { ...parsedPPBRMaterial.defines, ...modelOptions.defines },
3687
3862
  parameters: { ...parameters, ...parsedPPBRMaterial.parameters, ...modelOptions.parameters }
@@ -3705,69 +3880,73 @@ layout(0) positions: vec4; // in vec4 POSITION;
3705
3880
  lights: true,
3706
3881
  useTangents: false
3707
3882
  };
3708
- function parseGLTF(device, gltf, options_ = {}) {
3709
- const options = { ...defaultOptions, ...options_ };
3710
- const sceneNodes = gltf.scenes.map(
3711
- (gltfScene) => createScene(device, gltfScene, gltf.nodes, options)
3712
- );
3713
- return sceneNodes;
3714
- }
3715
- function createScene(device, gltfScene, gltfNodes, options) {
3716
- const gltfSceneNodes = gltfScene.nodes || [];
3717
- const nodes = gltfSceneNodes.map((node) => createNode(device, node, gltfNodes, options));
3718
- const sceneNode = new import_engine3.GroupNode({
3719
- id: gltfScene.name || gltfScene.id,
3720
- children: nodes
3883
+ function parseGLTF(device, gltf, options = {}) {
3884
+ const combinedOptions = { ...defaultOptions, ...options };
3885
+ const gltfMeshIdToNodeMap = /* @__PURE__ */ new Map();
3886
+ gltf.meshes.forEach((gltfMesh, idx) => {
3887
+ const newMesh = createNodeForGLTFMesh(device, gltfMesh, combinedOptions);
3888
+ gltfMeshIdToNodeMap.set(gltfMesh.id, newMesh);
3721
3889
  });
3722
- return sceneNode;
3723
- }
3724
- function createNode(device, gltfNode, gltfNodes, options) {
3725
- if (!gltfNode._node) {
3726
- const gltfChildren = gltfNode.children || [];
3727
- const children = gltfChildren.map((child) => createNode(device, child, gltfNodes, options));
3890
+ const gltfNodeIndexToNodeMap = /* @__PURE__ */ new Map();
3891
+ const gltfNodeIdToNodeMap = /* @__PURE__ */ new Map();
3892
+ gltf.nodes.forEach((gltfNode, idx) => {
3893
+ const newNode = createNodeForGLTFNode(device, gltfNode, combinedOptions);
3894
+ gltfNodeIndexToNodeMap.set(idx, newNode);
3895
+ gltfNodeIdToNodeMap.set(gltfNode.id, newNode);
3896
+ });
3897
+ gltf.nodes.forEach((gltfNode, idx) => {
3898
+ gltfNodeIndexToNodeMap.get(idx).add(
3899
+ (gltfNode.children ?? []).map(({ id }) => {
3900
+ const child = gltfNodeIdToNodeMap.get(id);
3901
+ if (!child)
3902
+ throw new Error(`Cannot find child ${id} of node ${idx}`);
3903
+ return child;
3904
+ })
3905
+ );
3728
3906
  if (gltfNode.mesh) {
3729
- children.push(createMesh(device, gltfNode.mesh, options));
3907
+ const mesh = gltfMeshIdToNodeMap.get(gltfNode.mesh.id);
3908
+ if (!mesh) {
3909
+ throw new Error(`Cannot find mesh child ${gltfNode.mesh.id} of node ${idx}`);
3910
+ }
3911
+ gltfNodeIndexToNodeMap.get(idx).add(mesh);
3730
3912
  }
3731
- const node = new import_engine3.GroupNode({
3732
- id: gltfNode.name || gltfNode.id,
3913
+ });
3914
+ const scenes = gltf.scenes.map((gltfScene) => {
3915
+ const children = (gltfScene.nodes || []).map(({ id }) => {
3916
+ const child = gltfNodeIdToNodeMap.get(id);
3917
+ if (!child)
3918
+ throw new Error(`Cannot find child ${id} of scene ${gltfScene.name || gltfScene.id}`);
3919
+ return child;
3920
+ });
3921
+ return new import_engine3.GroupNode({
3922
+ id: gltfScene.name || gltfScene.id,
3733
3923
  children
3734
3924
  });
3735
- if (gltfNode.matrix) {
3736
- node.setMatrix(gltfNode.matrix);
3737
- } else {
3738
- node.matrix.identity();
3739
- if (gltfNode.translation) {
3740
- node.matrix.translate(gltfNode.translation);
3741
- }
3742
- if (gltfNode.rotation) {
3743
- const rotationMatrix = new Matrix4().fromQuaternion(gltfNode.rotation);
3744
- node.matrix.multiplyRight(rotationMatrix);
3745
- }
3746
- if (gltfNode.scale) {
3747
- node.matrix.scale(gltfNode.scale);
3748
- }
3749
- }
3750
- gltfNode._node = node;
3751
- }
3752
- const topLevelNode = gltfNodes.find((node) => node.id === gltfNode.id);
3753
- topLevelNode._node = gltfNode._node;
3754
- return gltfNode._node;
3925
+ });
3926
+ return { scenes, gltfMeshIdToNodeMap, gltfNodeIdToNodeMap, gltfNodeIndexToNodeMap };
3927
+ }
3928
+ function createNodeForGLTFNode(device, gltfNode, options) {
3929
+ return new import_engine3.GroupNode({
3930
+ id: gltfNode.name || gltfNode.id,
3931
+ children: [],
3932
+ matrix: gltfNode.matrix,
3933
+ position: gltfNode.translation,
3934
+ rotation: gltfNode.rotation,
3935
+ scale: gltfNode.scale
3936
+ });
3755
3937
  }
3756
- function createMesh(device, gltfMesh, options) {
3757
- if (!gltfMesh._mesh) {
3758
- const gltfPrimitives = gltfMesh.primitives || [];
3759
- const primitives = gltfPrimitives.map(
3760
- (gltfPrimitive, i) => createPrimitive(device, gltfPrimitive, i, gltfMesh, options)
3761
- );
3762
- const mesh = new import_engine3.GroupNode({
3763
- id: gltfMesh.name || gltfMesh.id,
3764
- children: primitives
3765
- });
3766
- gltfMesh._mesh = mesh;
3767
- }
3768
- return gltfMesh._mesh;
3938
+ function createNodeForGLTFMesh(device, gltfMesh, options) {
3939
+ const gltfPrimitives = gltfMesh.primitives || [];
3940
+ const primitives = gltfPrimitives.map(
3941
+ (gltfPrimitive, i) => createNodeForGLTFPrimitive(device, gltfPrimitive, i, gltfMesh, options)
3942
+ );
3943
+ const mesh = new import_engine3.GroupNode({
3944
+ id: gltfMesh.name || gltfMesh.id,
3945
+ children: primitives
3946
+ });
3947
+ return mesh;
3769
3948
  }
3770
- function createPrimitive(device, gltfPrimitive, i, gltfMesh, options) {
3949
+ function createNodeForGLTFPrimitive(device, gltfPrimitive, i, gltfMesh, options) {
3771
3950
  const id = gltfPrimitive.name || `${gltfMesh.name || gltfMesh.id}-primitive-${i}`;
3772
3951
  const topology = convertGLDrawModeToTopology(gltfPrimitive.mode || 4);
3773
3952
  const vertexCount = gltfPrimitive.indices ? gltfPrimitive.indices.count : getVertexCount(gltfPrimitive.attributes);
@@ -3806,31 +3985,28 @@ layout(0) positions: vec4; // in vec4 POSITION;
3806
3985
  }
3807
3986
 
3808
3987
  // src/gltf/gltf-animator.ts
3809
- var import_core7 = __toESM(require_core(), 1);
3988
+ var import_core6 = __toESM(require_core(), 1);
3810
3989
 
3811
3990
  // src/gltf/animations/interpolate.ts
3812
- var import_core5 = __toESM(require_core(), 1);
3813
- var scratchQuaternion = new Quaternion();
3991
+ var import_core4 = __toESM(require_core(), 1);
3992
+ function updateTargetPath(target, path, newValue) {
3993
+ switch (path) {
3994
+ case "translation":
3995
+ return target.setPosition(newValue).updateMatrix();
3996
+ case "rotation":
3997
+ return target.setRotation(newValue).updateMatrix();
3998
+ case "scale":
3999
+ return target.setScale(newValue).updateMatrix();
4000
+ default:
4001
+ import_core4.log.warn(`Bad animation path ${path}`)();
4002
+ return null;
4003
+ }
4004
+ }
3814
4005
  function interpolate(time, { input, interpolation, output }, target, path) {
3815
4006
  const maxTime = input[input.length - 1];
3816
4007
  const animationTime = time % maxTime;
3817
4008
  const nextIndex = input.findIndex((t) => t >= animationTime);
3818
4009
  const previousIndex = Math.max(0, nextIndex - 1);
3819
- if (!Array.isArray(target[path])) {
3820
- switch (path) {
3821
- case "translation":
3822
- target[path] = [0, 0, 0];
3823
- break;
3824
- case "rotation":
3825
- target[path] = [0, 0, 0, 1];
3826
- break;
3827
- case "scale":
3828
- target[path] = [1, 1, 1];
3829
- break;
3830
- default:
3831
- import_core5.log.warn(`Bad animation path ${path}`)();
3832
- }
3833
- }
3834
4010
  const previousTime = input[previousIndex];
3835
4011
  const nextTime = input[nextIndex];
3836
4012
  switch (interpolation) {
@@ -3840,13 +4016,7 @@ layout(0) positions: vec4; // in vec4 POSITION;
3840
4016
  case "LINEAR":
3841
4017
  if (nextTime > previousTime) {
3842
4018
  const ratio = (animationTime - previousTime) / (nextTime - previousTime);
3843
- linearInterpolate(
3844
- target,
3845
- path,
3846
- output[previousIndex],
3847
- output[nextIndex],
3848
- ratio
3849
- );
4019
+ linearInterpolate(target, path, output[previousIndex], output[nextIndex], ratio);
3850
4020
  }
3851
4021
  break;
3852
4022
  case "CUBICSPLINE":
@@ -3861,23 +4031,19 @@ layout(0) positions: vec4; // in vec4 POSITION;
3861
4031
  }
3862
4032
  break;
3863
4033
  default:
3864
- import_core5.log.warn(`Interpolation ${interpolation} not supported`)();
4034
+ import_core4.log.warn(`Interpolation ${interpolation} not supported`)();
3865
4035
  break;
3866
4036
  }
3867
4037
  }
3868
4038
  function linearInterpolate(target, path, start, stop, ratio) {
3869
- if (!target[path]) {
3870
- throw new Error();
3871
- }
3872
4039
  if (path === "rotation") {
3873
- scratchQuaternion.slerp({ start, target: stop, ratio });
3874
- for (let i = 0; i < scratchQuaternion.length; i++) {
3875
- target[path][i] = scratchQuaternion[i];
3876
- }
4040
+ updateTargetPath(target, path, new Quaternion().slerp({ start, target: stop, ratio }));
3877
4041
  } else {
4042
+ const newVal = [];
3878
4043
  for (let i = 0; i < start.length; i++) {
3879
- target[path][i] = ratio * stop[i] + (1 - ratio) * start[i];
4044
+ newVal[i] = ratio * stop[i] + (1 - ratio) * start[i];
3880
4045
  }
4046
+ updateTargetPath(target, path, newVal);
3881
4047
  }
3882
4048
  }
3883
4049
  function cubicsplineInterpolate(target, path, {
@@ -3888,83 +4054,80 @@ layout(0) positions: vec4; // in vec4 POSITION;
3888
4054
  tDiff,
3889
4055
  ratio: t
3890
4056
  }) {
3891
- if (!target[path]) {
3892
- throw new Error();
3893
- }
3894
- for (let i = 0; i < target[path].length; i++) {
4057
+ const newVal = [];
4058
+ for (let i = 0; i < p0.length; i++) {
3895
4059
  const m0 = outTangent0[i] * tDiff;
3896
4060
  const m1 = inTangent1[i] * tDiff;
3897
- 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;
4061
+ 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;
3898
4062
  }
4063
+ updateTargetPath(target, path, newVal);
3899
4064
  }
3900
4065
  function stepInterpolate(target, path, value) {
3901
- if (!target[path]) {
3902
- throw new Error();
3903
- }
3904
- for (let i = 0; i < value.length; i++) {
3905
- target[path][i] = value[i];
3906
- }
4066
+ updateTargetPath(target, path, value);
3907
4067
  }
3908
4068
 
3909
4069
  // src/gltf/gltf-animator.ts
3910
4070
  var GLTFSingleAnimator = class {
4071
+ /** Animation definition being played. */
3911
4072
  animation;
4073
+ /** Target scenegraph lookup table. */
4074
+ gltfNodeIdToNodeMap;
4075
+ /** Playback start time in seconds. */
3912
4076
  startTime = 0;
4077
+ /** Whether playback is currently enabled. */
3913
4078
  playing = true;
4079
+ /** Playback speed multiplier. */
3914
4080
  speed = 1;
4081
+ /** Creates a single-animation controller. */
3915
4082
  constructor(props) {
3916
4083
  this.animation = props.animation;
4084
+ this.gltfNodeIdToNodeMap = props.gltfNodeIdToNodeMap;
3917
4085
  this.animation.name ||= "unnamed";
3918
4086
  Object.assign(this, props);
3919
4087
  }
4088
+ /** Advances the animation to the supplied wall-clock time in milliseconds. */
3920
4089
  setTime(timeMs) {
3921
4090
  if (!this.playing) {
3922
4091
  return;
3923
4092
  }
3924
4093
  const absTime = timeMs / 1e3;
3925
4094
  const time = (absTime - this.startTime) * this.speed;
3926
- this.animation.channels.forEach(({ sampler, target, path }) => {
3927
- interpolate(time, sampler, target, path);
3928
- applyTranslationRotationScale(target, target._node);
4095
+ this.animation.channels.forEach(({ sampler, targetNodeId, path }) => {
4096
+ const targetNode = this.gltfNodeIdToNodeMap.get(targetNodeId);
4097
+ if (!targetNode) {
4098
+ throw new Error(`Cannot find animation target node ${targetNodeId}`);
4099
+ }
4100
+ interpolate(time, sampler, targetNode, path);
3929
4101
  });
3930
4102
  }
3931
4103
  };
3932
4104
  var GLTFAnimator = class {
4105
+ /** Individual animation controllers. */
3933
4106
  animations;
4107
+ /** Creates an animator for the supplied glTF scenegraph. */
3934
4108
  constructor(props) {
3935
4109
  this.animations = props.animations.map((animation, index) => {
3936
4110
  const name = animation.name || `Animation-${index}`;
3937
4111
  return new GLTFSingleAnimator({
4112
+ gltfNodeIdToNodeMap: props.gltfNodeIdToNodeMap,
3938
4113
  animation: { name, channels: animation.channels }
3939
4114
  });
3940
4115
  });
3941
4116
  }
3942
4117
  /** @deprecated Use .setTime(). Will be removed (deck.gl is using this) */
3943
4118
  animate(time) {
3944
- import_core7.log.warn("GLTFAnimator#animate is deprecated. Use GLTFAnimator#setTime instead")();
4119
+ import_core6.log.warn("GLTFAnimator#animate is deprecated. Use GLTFAnimator#setTime instead")();
3945
4120
  this.setTime(time);
3946
4121
  }
4122
+ /** Advances every animation to the supplied wall-clock time in milliseconds. */
3947
4123
  setTime(time) {
3948
4124
  this.animations.forEach((animation) => animation.setTime(time));
3949
4125
  }
4126
+ /** Returns the per-animation controllers managed by this animator. */
3950
4127
  getAnimations() {
3951
4128
  return this.animations;
3952
4129
  }
3953
4130
  };
3954
- var scratchMatrix = new Matrix4();
3955
- function applyTranslationRotationScale(gltfNode, node) {
3956
- node.matrix.identity();
3957
- if (gltfNode.translation) {
3958
- node.matrix.translate(gltfNode.translation);
3959
- }
3960
- if (gltfNode.rotation) {
3961
- const rotationMatrix = scratchMatrix.fromQuaternion(gltfNode.rotation);
3962
- node.matrix.multiplyRight(rotationMatrix);
3963
- }
3964
- if (gltfNode.scale) {
3965
- node.matrix.scale(gltfNode.scale);
3966
- }
3967
- }
3968
4131
 
3969
4132
  // src/webgl-to-webgpu/convert-webgl-attribute.ts
3970
4133
  var ATTRIBUTE_TYPE_TO_COMPONENTS = {
@@ -3996,65 +4159,79 @@ layout(0) positions: vec4; // in vec4 POSITION;
3996
4159
  // src/parsers/parse-gltf-animations.ts
3997
4160
  function parseGLTFAnimations(gltf) {
3998
4161
  const gltfAnimations = gltf.animations || [];
4162
+ const accessorCache1D = /* @__PURE__ */ new Map();
4163
+ const accessorCache2D = /* @__PURE__ */ new Map();
3999
4164
  return gltfAnimations.map((animation, index) => {
4000
4165
  const name = animation.name || `Animation-${index}`;
4001
4166
  const samplers = animation.samplers.map(
4002
4167
  ({ input, interpolation = "LINEAR", output }) => ({
4003
- input: accessorToJsArray(gltf.accessors[input]),
4168
+ input: accessorToJsArray1D(gltf.accessors[input], accessorCache1D),
4004
4169
  interpolation,
4005
- output: accessorToJsArray(gltf.accessors[output])
4170
+ output: accessorToJsArray2D(gltf.accessors[output], accessorCache2D)
4006
4171
  })
4007
4172
  );
4008
- const channels = animation.channels.map(({ sampler, target }) => ({
4009
- sampler: samplers[sampler],
4010
- target: gltf.nodes[target.node ?? 0],
4011
- path: target.path
4012
- }));
4173
+ const channels = animation.channels.map(({ sampler, target }) => {
4174
+ const targetNode = gltf.nodes[target.node ?? 0];
4175
+ if (!targetNode) {
4176
+ throw new Error(`Cannot find animation target ${target.node}`);
4177
+ }
4178
+ return {
4179
+ sampler: samplers[sampler],
4180
+ targetNodeId: targetNode.id,
4181
+ path: target.path
4182
+ };
4183
+ });
4013
4184
  return { name, channels };
4014
4185
  });
4015
4186
  }
4016
- function accessorToJsArray(accessor) {
4017
- if (!accessor._animation) {
4018
- const { typedArray: array, components } = accessorToTypedArray(accessor);
4019
- if (components === 1) {
4020
- accessor._animation = Array.from(array);
4021
- } else {
4022
- const slicedArray = [];
4023
- for (let i = 0; i < array.length; i += components) {
4024
- slicedArray.push(Array.from(array.slice(i, i + components)));
4025
- }
4026
- accessor._animation = slicedArray;
4027
- }
4187
+ function accessorToJsArray1D(accessor, accessorCache) {
4188
+ if (accessorCache.has(accessor)) {
4189
+ return accessorCache.get(accessor);
4028
4190
  }
4029
- return accessor._animation;
4191
+ const { typedArray: array, components } = accessorToTypedArray(accessor);
4192
+ assert3(components === 1, "accessorToJsArray1D must have exactly 1 component");
4193
+ const result = Array.from(array);
4194
+ accessorCache.set(accessor, result);
4195
+ return result;
4030
4196
  }
4031
-
4032
- // src/utils/deep-copy.ts
4033
- function deepCopy(object) {
4034
- if (ArrayBuffer.isView(object) || object instanceof ArrayBuffer || object instanceof ImageBitmap) {
4035
- return object;
4197
+ function accessorToJsArray2D(accessor, accessorCache) {
4198
+ if (accessorCache.has(accessor)) {
4199
+ return accessorCache.get(accessor);
4036
4200
  }
4037
- if (Array.isArray(object)) {
4038
- return object.map(deepCopy);
4201
+ const { typedArray: array, components } = accessorToTypedArray(accessor);
4202
+ assert3(components > 1, "accessorToJsArray2D must have more than 1 component");
4203
+ const result = [];
4204
+ for (let i = 0; i < array.length; i += components) {
4205
+ result.push(Array.from(array.slice(i, i + components)));
4039
4206
  }
4040
- if (object && typeof object === "object") {
4041
- const result = {};
4042
- for (const key in object) {
4043
- result[key] = deepCopy(object[key]);
4044
- }
4045
- return result;
4207
+ accessorCache.set(accessor, result);
4208
+ return result;
4209
+ }
4210
+ function assert3(condition, message) {
4211
+ if (!condition) {
4212
+ throw new Error(message);
4046
4213
  }
4047
- return object;
4048
4214
  }
4049
4215
 
4050
4216
  // src/gltf/create-scenegraph-from-gltf.ts
4051
4217
  function createScenegraphsFromGLTF(device, gltf, options) {
4052
- gltf = deepCopy(gltf);
4053
- const scenes = parseGLTF(device, gltf, options);
4218
+ const { scenes, gltfMeshIdToNodeMap, gltfNodeIdToNodeMap, gltfNodeIndexToNodeMap } = parseGLTF(
4219
+ device,
4220
+ gltf,
4221
+ options
4222
+ );
4054
4223
  const animations = parseGLTFAnimations(gltf);
4055
- const animator = new GLTFAnimator({ animations });
4224
+ const animator = new GLTFAnimator({ animations, gltfNodeIdToNodeMap });
4056
4225
  const lights = parseGLTFLights(gltf);
4057
- return { scenes, animator, lights };
4226
+ return {
4227
+ scenes,
4228
+ animator,
4229
+ lights,
4230
+ gltfMeshIdToNodeMap,
4231
+ gltfNodeIdToNodeMap,
4232
+ gltfNodeIndexToNodeMap,
4233
+ gltf
4234
+ };
4058
4235
  }
4059
4236
  return __toCommonJS(bundle_exports);
4060
4237
  })();