@luma.gl/gltf 9.3.0-alpha.2 → 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 +403 -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 +384 -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 +142 -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 +210 -15
  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,10 @@ 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;
783
+ if (attributes["COLOR_0"])
784
+ parsedMaterial.defines["HAS_COLORS"] = true;
777
785
  if (options?.imageBasedLightingEnvironment)
778
786
  parsedMaterial.defines["USE_IBL"] = true;
779
787
  if (options?.lights)
@@ -871,29 +879,133 @@ var __exports__ = (() => {
871
879
  }
872
880
  function addTexture(device, gltfTexture, uniformName, define, parsedMaterial) {
873
881
  const image = gltfTexture.texture.source.image;
874
- let textureOptions;
875
- if (image.compressed) {
876
- textureOptions = image;
877
- } else {
878
- textureOptions = { data: image };
879
- }
880
882
  const gltfSampler = {
881
883
  wrapS: 10497,
882
884
  // default REPEAT S (U) wrapping mode.
883
885
  wrapT: 10497,
884
886
  // default REPEAT T (V) wrapping mode.
887
+ minFilter: 9729,
888
+ // default LINEAR filtering
889
+ magFilter: 9729,
890
+ // default LINEAR filtering
885
891
  ...gltfTexture?.texture?.sampler
886
892
  };
887
- const texture = device.createTexture({
893
+ const baseOptions = {
888
894
  id: gltfTexture.uniformName || gltfTexture.id,
889
- sampler: convertSampler(gltfSampler),
890
- ...textureOptions
891
- });
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
+ }
892
909
  parsedMaterial.bindings[uniformName] = texture;
893
910
  if (define)
894
911
  parsedMaterial.defines[define] = true;
895
912
  parsedMaterial.generatedTextures.push(texture);
896
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
+ }
897
1009
 
898
1010
  // ../../node_modules/@math.gl/core/dist/lib/common.js
899
1011
  var RADIANS_TO_DEGREES = 1 / Math.PI * 180;
@@ -3468,17 +3580,20 @@ var __exports__ = (() => {
3468
3580
 
3469
3581
  // src/parsers/parse-gltf-lights.ts
3470
3582
  function parseGLTFLights(gltf) {
3471
- 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
+ );
3472
3587
  if (!lightDefs || !Array.isArray(lightDefs) || lightDefs.length === 0) {
3473
3588
  return [];
3474
3589
  }
3475
3590
  const lights = [];
3476
3591
  for (const node of gltf.nodes || []) {
3477
- const nodeLight = node.extensions?.KHR_lights_punctual;
3478
- if (!nodeLight || typeof nodeLight.light !== "number") {
3592
+ const lightIndex = node.light ?? node.extensions?.KHR_lights_punctual?.light;
3593
+ if (typeof lightIndex !== "number") {
3479
3594
  continue;
3480
3595
  }
3481
- const gltfLight = lightDefs[nodeLight.light];
3596
+ const gltfLight = lightDefs[lightIndex];
3482
3597
  if (!gltfLight) {
3483
3598
  continue;
3484
3599
  }
@@ -3502,7 +3617,7 @@ var __exports__ = (() => {
3502
3617
  return lights;
3503
3618
  }
3504
3619
  function parsePointLight(node, color, intensity, range) {
3505
- const position = node.translation ? [...node.translation] : [0, 0, 0];
3620
+ const position = getNodePosition(node);
3506
3621
  let attenuation = [1, 0, 0];
3507
3622
  if (range !== void 0 && range > 0) {
3508
3623
  attenuation = [1, 0, 1 / (range * range)];
@@ -3516,11 +3631,7 @@ var __exports__ = (() => {
3516
3631
  };
3517
3632
  }
3518
3633
  function parseDirectionalLight(node, color, intensity) {
3519
- let direction = [0, 0, -1];
3520
- if (node.rotation) {
3521
- const orientation = new Matrix4().fromQuaternion(node.rotation);
3522
- direction = orientation.transformDirection([0, 0, -1]);
3523
- }
3634
+ const direction = getNodeDirection(node);
3524
3635
  return {
3525
3636
  type: "directional",
3526
3637
  direction,
@@ -3528,6 +3639,24 @@ var __exports__ = (() => {
3528
3639
  intensity
3529
3640
  };
3530
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
+ }
3531
3660
 
3532
3661
  // src/parsers/parse-gltf.ts
3533
3662
  var import_engine3 = __toESM(require_engine(), 1);
@@ -3557,50 +3686,88 @@ var __exports__ = (() => {
3557
3686
  var SHADER = (
3558
3687
  /* WGSL */
3559
3688
  `
3560
- 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
+ };
3561
3705
 
3562
- #ifdef HAS_NORMALS
3563
- in vec4 normals; // in vec4 NORMAL;
3564
- #endif
3565
-
3566
- #ifdef HAS_TANGENTS
3567
- in vec4 TANGENT;
3568
- #endif
3569
-
3570
- #ifdef HAS_UV
3571
- // in vec2 TEXCOORD_0;
3572
- in vec2 texCoords;
3573
- #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
+ };
3574
3715
 
3575
3716
  @vertex
3576
- void main(void) {
3577
- vec4 _NORMAL = vec4(0.);
3578
- vec4 _TANGENT = vec4(0.);
3579
- 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);
3580
3723
 
3581
- #ifdef HAS_NORMALS
3582
- _NORMAL = normals;
3583
- #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
3584
3741
 
3585
- #ifdef HAS_TANGENTS
3586
- _TANGENT = TANGENT;
3587
- #endif
3742
+ let worldPosition = pbrProjection.modelMatrix * position;
3588
3743
 
3589
- #ifdef HAS_UV
3590
- _TEXCOORD_0 = texCoords;
3591
- #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
3592
3751
 
3593
- pbr_setPositionNormalTangentUV(positions, _NORMAL, _TANGENT, _TEXCOORD_0);
3594
- gl_Position = u_MVPMatrix * positions;
3595
- }
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
+ }
3596
3758
 
3597
3759
  @fragment
3598
- out vec4 fragmentColor;
3599
-
3600
- void main(void) {
3601
- vec3 pos = pbr_vPosition;
3602
- fragmentColor = pbr_filterColor(vec4(1.0));
3603
- }
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
+ }
3604
3771
  `
3605
3772
  );
3606
3773
  var vs = (
@@ -3624,6 +3791,11 @@ layout(0) positions: vec4; // in vec4 POSITION;
3624
3791
  in vec2 texCoords;
3625
3792
  #endif
3626
3793
 
3794
+ #ifdef HAS_SKIN
3795
+ in uvec4 JOINTS_0;
3796
+ in vec4 WEIGHTS_0;
3797
+ #endif
3798
+
3627
3799
  void main(void) {
3628
3800
  vec4 _NORMAL = vec4(0.);
3629
3801
  vec4 _TANGENT = vec4(0.);
@@ -3641,8 +3813,17 @@ layout(0) positions: vec4; // in vec4 POSITION;
3641
3813
  _TEXCOORD_0 = texCoords;
3642
3814
  #endif
3643
3815
 
3644
- pbr_setPositionNormalTangentUV(positions, _NORMAL, _TANGENT, _TEXCOORD_0);
3645
- 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;
3646
3827
  }
3647
3828
  `
3648
3829
  );
@@ -3675,7 +3856,7 @@ layout(0) positions: vec4; // in vec4 POSITION;
3675
3856
  geometry,
3676
3857
  topology: geometry.topology,
3677
3858
  vertexCount,
3678
- modules: [import_shadertools.pbrMaterial],
3859
+ modules: [import_shadertools.pbrMaterial, import_shadertools.skin],
3679
3860
  ...modelOptions,
3680
3861
  defines: { ...parsedPPBRMaterial.defines, ...modelOptions.defines },
3681
3862
  parameters: { ...parameters, ...parsedPPBRMaterial.parameters, ...modelOptions.parameters }
@@ -3699,69 +3880,73 @@ layout(0) positions: vec4; // in vec4 POSITION;
3699
3880
  lights: true,
3700
3881
  useTangents: false
3701
3882
  };
3702
- function parseGLTF(device, gltf, options_ = {}) {
3703
- const options = { ...defaultOptions, ...options_ };
3704
- const sceneNodes = gltf.scenes.map(
3705
- (gltfScene) => createScene(device, gltfScene, gltf.nodes, options)
3706
- );
3707
- return sceneNodes;
3708
- }
3709
- function createScene(device, gltfScene, gltfNodes, options) {
3710
- const gltfSceneNodes = gltfScene.nodes || [];
3711
- const nodes = gltfSceneNodes.map((node) => createNode(device, node, gltfNodes, options));
3712
- const sceneNode = new import_engine3.GroupNode({
3713
- id: gltfScene.name || gltfScene.id,
3714
- 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);
3715
3889
  });
3716
- return sceneNode;
3717
- }
3718
- function createNode(device, gltfNode, gltfNodes, options) {
3719
- if (!gltfNode._node) {
3720
- const gltfChildren = gltfNode.children || [];
3721
- 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
+ );
3722
3906
  if (gltfNode.mesh) {
3723
- 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);
3724
3912
  }
3725
- const node = new import_engine3.GroupNode({
3726
- 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,
3727
3923
  children
3728
3924
  });
3729
- if (gltfNode.matrix) {
3730
- node.setMatrix(gltfNode.matrix);
3731
- } else {
3732
- node.matrix.identity();
3733
- if (gltfNode.translation) {
3734
- node.matrix.translate(gltfNode.translation);
3735
- }
3736
- if (gltfNode.rotation) {
3737
- const rotationMatrix = new Matrix4().fromQuaternion(gltfNode.rotation);
3738
- node.matrix.multiplyRight(rotationMatrix);
3739
- }
3740
- if (gltfNode.scale) {
3741
- node.matrix.scale(gltfNode.scale);
3742
- }
3743
- }
3744
- gltfNode._node = node;
3745
- }
3746
- const topLevelNode = gltfNodes.find((node) => node.id === gltfNode.id);
3747
- topLevelNode._node = gltfNode._node;
3748
- 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
+ });
3749
3937
  }
3750
- function createMesh(device, gltfMesh, options) {
3751
- if (!gltfMesh._mesh) {
3752
- const gltfPrimitives = gltfMesh.primitives || [];
3753
- const primitives = gltfPrimitives.map(
3754
- (gltfPrimitive, i) => createPrimitive(device, gltfPrimitive, i, gltfMesh, options)
3755
- );
3756
- const mesh = new import_engine3.GroupNode({
3757
- id: gltfMesh.name || gltfMesh.id,
3758
- children: primitives
3759
- });
3760
- gltfMesh._mesh = mesh;
3761
- }
3762
- 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;
3763
3948
  }
3764
- function createPrimitive(device, gltfPrimitive, i, gltfMesh, options) {
3949
+ function createNodeForGLTFPrimitive(device, gltfPrimitive, i, gltfMesh, options) {
3765
3950
  const id = gltfPrimitive.name || `${gltfMesh.name || gltfMesh.id}-primitive-${i}`;
3766
3951
  const topology = convertGLDrawModeToTopology(gltfPrimitive.mode || 4);
3767
3952
  const vertexCount = gltfPrimitive.indices ? gltfPrimitive.indices.count : getVertexCount(gltfPrimitive.attributes);
@@ -3800,31 +3985,28 @@ layout(0) positions: vec4; // in vec4 POSITION;
3800
3985
  }
3801
3986
 
3802
3987
  // src/gltf/gltf-animator.ts
3803
- var import_core7 = __toESM(require_core(), 1);
3988
+ var import_core6 = __toESM(require_core(), 1);
3804
3989
 
3805
3990
  // src/gltf/animations/interpolate.ts
3806
- var import_core5 = __toESM(require_core(), 1);
3807
- 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
+ }
3808
4005
  function interpolate(time, { input, interpolation, output }, target, path) {
3809
4006
  const maxTime = input[input.length - 1];
3810
4007
  const animationTime = time % maxTime;
3811
4008
  const nextIndex = input.findIndex((t) => t >= animationTime);
3812
4009
  const previousIndex = Math.max(0, nextIndex - 1);
3813
- if (!Array.isArray(target[path])) {
3814
- switch (path) {
3815
- case "translation":
3816
- target[path] = [0, 0, 0];
3817
- break;
3818
- case "rotation":
3819
- target[path] = [0, 0, 0, 1];
3820
- break;
3821
- case "scale":
3822
- target[path] = [1, 1, 1];
3823
- break;
3824
- default:
3825
- import_core5.log.warn(`Bad animation path ${path}`)();
3826
- }
3827
- }
3828
4010
  const previousTime = input[previousIndex];
3829
4011
  const nextTime = input[nextIndex];
3830
4012
  switch (interpolation) {
@@ -3834,13 +4016,7 @@ layout(0) positions: vec4; // in vec4 POSITION;
3834
4016
  case "LINEAR":
3835
4017
  if (nextTime > previousTime) {
3836
4018
  const ratio = (animationTime - previousTime) / (nextTime - previousTime);
3837
- linearInterpolate(
3838
- target,
3839
- path,
3840
- output[previousIndex],
3841
- output[nextIndex],
3842
- ratio
3843
- );
4019
+ linearInterpolate(target, path, output[previousIndex], output[nextIndex], ratio);
3844
4020
  }
3845
4021
  break;
3846
4022
  case "CUBICSPLINE":
@@ -3855,23 +4031,19 @@ layout(0) positions: vec4; // in vec4 POSITION;
3855
4031
  }
3856
4032
  break;
3857
4033
  default:
3858
- import_core5.log.warn(`Interpolation ${interpolation} not supported`)();
4034
+ import_core4.log.warn(`Interpolation ${interpolation} not supported`)();
3859
4035
  break;
3860
4036
  }
3861
4037
  }
3862
4038
  function linearInterpolate(target, path, start, stop, ratio) {
3863
- if (!target[path]) {
3864
- throw new Error();
3865
- }
3866
4039
  if (path === "rotation") {
3867
- scratchQuaternion.slerp({ start, target: stop, ratio });
3868
- for (let i = 0; i < scratchQuaternion.length; i++) {
3869
- target[path][i] = scratchQuaternion[i];
3870
- }
4040
+ updateTargetPath(target, path, new Quaternion().slerp({ start, target: stop, ratio }));
3871
4041
  } else {
4042
+ const newVal = [];
3872
4043
  for (let i = 0; i < start.length; i++) {
3873
- target[path][i] = ratio * stop[i] + (1 - ratio) * start[i];
4044
+ newVal[i] = ratio * stop[i] + (1 - ratio) * start[i];
3874
4045
  }
4046
+ updateTargetPath(target, path, newVal);
3875
4047
  }
3876
4048
  }
3877
4049
  function cubicsplineInterpolate(target, path, {
@@ -3882,83 +4054,80 @@ layout(0) positions: vec4; // in vec4 POSITION;
3882
4054
  tDiff,
3883
4055
  ratio: t
3884
4056
  }) {
3885
- if (!target[path]) {
3886
- throw new Error();
3887
- }
3888
- for (let i = 0; i < target[path].length; i++) {
4057
+ const newVal = [];
4058
+ for (let i = 0; i < p0.length; i++) {
3889
4059
  const m0 = outTangent0[i] * tDiff;
3890
4060
  const m1 = inTangent1[i] * tDiff;
3891
- 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;
3892
4062
  }
4063
+ updateTargetPath(target, path, newVal);
3893
4064
  }
3894
4065
  function stepInterpolate(target, path, value) {
3895
- if (!target[path]) {
3896
- throw new Error();
3897
- }
3898
- for (let i = 0; i < value.length; i++) {
3899
- target[path][i] = value[i];
3900
- }
4066
+ updateTargetPath(target, path, value);
3901
4067
  }
3902
4068
 
3903
4069
  // src/gltf/gltf-animator.ts
3904
4070
  var GLTFSingleAnimator = class {
4071
+ /** Animation definition being played. */
3905
4072
  animation;
4073
+ /** Target scenegraph lookup table. */
4074
+ gltfNodeIdToNodeMap;
4075
+ /** Playback start time in seconds. */
3906
4076
  startTime = 0;
4077
+ /** Whether playback is currently enabled. */
3907
4078
  playing = true;
4079
+ /** Playback speed multiplier. */
3908
4080
  speed = 1;
4081
+ /** Creates a single-animation controller. */
3909
4082
  constructor(props) {
3910
4083
  this.animation = props.animation;
4084
+ this.gltfNodeIdToNodeMap = props.gltfNodeIdToNodeMap;
3911
4085
  this.animation.name ||= "unnamed";
3912
4086
  Object.assign(this, props);
3913
4087
  }
4088
+ /** Advances the animation to the supplied wall-clock time in milliseconds. */
3914
4089
  setTime(timeMs) {
3915
4090
  if (!this.playing) {
3916
4091
  return;
3917
4092
  }
3918
4093
  const absTime = timeMs / 1e3;
3919
4094
  const time = (absTime - this.startTime) * this.speed;
3920
- this.animation.channels.forEach(({ sampler, target, path }) => {
3921
- interpolate(time, sampler, target, path);
3922
- 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);
3923
4101
  });
3924
4102
  }
3925
4103
  };
3926
4104
  var GLTFAnimator = class {
4105
+ /** Individual animation controllers. */
3927
4106
  animations;
4107
+ /** Creates an animator for the supplied glTF scenegraph. */
3928
4108
  constructor(props) {
3929
4109
  this.animations = props.animations.map((animation, index) => {
3930
4110
  const name = animation.name || `Animation-${index}`;
3931
4111
  return new GLTFSingleAnimator({
4112
+ gltfNodeIdToNodeMap: props.gltfNodeIdToNodeMap,
3932
4113
  animation: { name, channels: animation.channels }
3933
4114
  });
3934
4115
  });
3935
4116
  }
3936
4117
  /** @deprecated Use .setTime(). Will be removed (deck.gl is using this) */
3937
4118
  animate(time) {
3938
- 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")();
3939
4120
  this.setTime(time);
3940
4121
  }
4122
+ /** Advances every animation to the supplied wall-clock time in milliseconds. */
3941
4123
  setTime(time) {
3942
4124
  this.animations.forEach((animation) => animation.setTime(time));
3943
4125
  }
4126
+ /** Returns the per-animation controllers managed by this animator. */
3944
4127
  getAnimations() {
3945
4128
  return this.animations;
3946
4129
  }
3947
4130
  };
3948
- var scratchMatrix = new Matrix4();
3949
- function applyTranslationRotationScale(gltfNode, node) {
3950
- node.matrix.identity();
3951
- if (gltfNode.translation) {
3952
- node.matrix.translate(gltfNode.translation);
3953
- }
3954
- if (gltfNode.rotation) {
3955
- const rotationMatrix = scratchMatrix.fromQuaternion(gltfNode.rotation);
3956
- node.matrix.multiplyRight(rotationMatrix);
3957
- }
3958
- if (gltfNode.scale) {
3959
- node.matrix.scale(gltfNode.scale);
3960
- }
3961
- }
3962
4131
 
3963
4132
  // src/webgl-to-webgpu/convert-webgl-attribute.ts
3964
4133
  var ATTRIBUTE_TYPE_TO_COMPONENTS = {
@@ -3990,65 +4159,79 @@ layout(0) positions: vec4; // in vec4 POSITION;
3990
4159
  // src/parsers/parse-gltf-animations.ts
3991
4160
  function parseGLTFAnimations(gltf) {
3992
4161
  const gltfAnimations = gltf.animations || [];
4162
+ const accessorCache1D = /* @__PURE__ */ new Map();
4163
+ const accessorCache2D = /* @__PURE__ */ new Map();
3993
4164
  return gltfAnimations.map((animation, index) => {
3994
4165
  const name = animation.name || `Animation-${index}`;
3995
4166
  const samplers = animation.samplers.map(
3996
4167
  ({ input, interpolation = "LINEAR", output }) => ({
3997
- input: accessorToJsArray(gltf.accessors[input]),
4168
+ input: accessorToJsArray1D(gltf.accessors[input], accessorCache1D),
3998
4169
  interpolation,
3999
- output: accessorToJsArray(gltf.accessors[output])
4170
+ output: accessorToJsArray2D(gltf.accessors[output], accessorCache2D)
4000
4171
  })
4001
4172
  );
4002
- const channels = animation.channels.map(({ sampler, target }) => ({
4003
- sampler: samplers[sampler],
4004
- target: gltf.nodes[target.node ?? 0],
4005
- path: target.path
4006
- }));
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
+ });
4007
4184
  return { name, channels };
4008
4185
  });
4009
4186
  }
4010
- function accessorToJsArray(accessor) {
4011
- if (!accessor._animation) {
4012
- const { typedArray: array, components } = accessorToTypedArray(accessor);
4013
- if (components === 1) {
4014
- accessor._animation = Array.from(array);
4015
- } else {
4016
- const slicedArray = [];
4017
- for (let i = 0; i < array.length; i += components) {
4018
- slicedArray.push(Array.from(array.slice(i, i + components)));
4019
- }
4020
- accessor._animation = slicedArray;
4021
- }
4187
+ function accessorToJsArray1D(accessor, accessorCache) {
4188
+ if (accessorCache.has(accessor)) {
4189
+ return accessorCache.get(accessor);
4022
4190
  }
4023
- 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;
4024
4196
  }
4025
-
4026
- // src/utils/deep-copy.ts
4027
- function deepCopy(object) {
4028
- if (ArrayBuffer.isView(object) || object instanceof ArrayBuffer || object instanceof ImageBitmap) {
4029
- return object;
4197
+ function accessorToJsArray2D(accessor, accessorCache) {
4198
+ if (accessorCache.has(accessor)) {
4199
+ return accessorCache.get(accessor);
4030
4200
  }
4031
- if (Array.isArray(object)) {
4032
- 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)));
4033
4206
  }
4034
- if (object && typeof object === "object") {
4035
- const result = {};
4036
- for (const key in object) {
4037
- result[key] = deepCopy(object[key]);
4038
- }
4039
- return result;
4207
+ accessorCache.set(accessor, result);
4208
+ return result;
4209
+ }
4210
+ function assert3(condition, message) {
4211
+ if (!condition) {
4212
+ throw new Error(message);
4040
4213
  }
4041
- return object;
4042
4214
  }
4043
4215
 
4044
4216
  // src/gltf/create-scenegraph-from-gltf.ts
4045
4217
  function createScenegraphsFromGLTF(device, gltf, options) {
4046
- gltf = deepCopy(gltf);
4047
- const scenes = parseGLTF(device, gltf, options);
4218
+ const { scenes, gltfMeshIdToNodeMap, gltfNodeIdToNodeMap, gltfNodeIndexToNodeMap } = parseGLTF(
4219
+ device,
4220
+ gltf,
4221
+ options
4222
+ );
4048
4223
  const animations = parseGLTFAnimations(gltf);
4049
- const animator = new GLTFAnimator({ animations });
4224
+ const animator = new GLTFAnimator({ animations, gltfNodeIdToNodeMap });
4050
4225
  const lights = parseGLTFLights(gltf);
4051
- return { scenes, animator, lights };
4226
+ return {
4227
+ scenes,
4228
+ animator,
4229
+ lights,
4230
+ gltfMeshIdToNodeMap,
4231
+ gltfNodeIdToNodeMap,
4232
+ gltfNodeIndexToNodeMap,
4233
+ gltf
4234
+ };
4052
4235
  }
4053
4236
  return __toCommonJS(bundle_exports);
4054
4237
  })();