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

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 (90) hide show
  1. package/dist/dist.dev.js +1305 -327
  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 +15 -1
  10. package/dist/gltf/create-gltf-model.d.ts.map +1 -1
  11. package/dist/gltf/create-gltf-model.js +154 -48
  12. package/dist/gltf/create-gltf-model.js.map +1 -1
  13. package/dist/gltf/create-scenegraph-from-gltf.d.ts +37 -2
  14. package/dist/gltf/create-scenegraph-from-gltf.d.ts.map +1 -1
  15. package/dist/gltf/create-scenegraph-from-gltf.js +74 -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/gltf/gltf-extension-support.d.ts +10 -0
  22. package/dist/gltf/gltf-extension-support.d.ts.map +1 -0
  23. package/dist/gltf/gltf-extension-support.js +173 -0
  24. package/dist/gltf/gltf-extension-support.js.map +1 -0
  25. package/dist/index.cjs +1247 -294
  26. package/dist/index.cjs.map +4 -4
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/parsers/parse-gltf-animations.d.ts +1 -0
  32. package/dist/parsers/parse-gltf-animations.d.ts.map +1 -1
  33. package/dist/parsers/parse-gltf-animations.js +73 -28
  34. package/dist/parsers/parse-gltf-animations.js.map +1 -1
  35. package/dist/parsers/parse-gltf-lights.d.ts.map +1 -1
  36. package/dist/parsers/parse-gltf-lights.js +112 -18
  37. package/dist/parsers/parse-gltf-lights.js.map +1 -1
  38. package/dist/parsers/parse-gltf.d.ts +19 -2
  39. package/dist/parsers/parse-gltf.d.ts.map +1 -1
  40. package/dist/parsers/parse-gltf.js +101 -61
  41. package/dist/parsers/parse-gltf.js.map +1 -1
  42. package/dist/parsers/parse-pbr-material.d.ts +115 -2
  43. package/dist/parsers/parse-pbr-material.d.ts.map +1 -1
  44. package/dist/parsers/parse-pbr-material.js +565 -54
  45. package/dist/parsers/parse-pbr-material.js.map +1 -1
  46. package/dist/pbr/pbr-environment.d.ts +6 -0
  47. package/dist/pbr/pbr-environment.d.ts.map +1 -1
  48. package/dist/pbr/pbr-environment.js +15 -12
  49. package/dist/pbr/pbr-environment.js.map +1 -1
  50. package/dist/pbr/pbr-material.d.ts +13 -3
  51. package/dist/pbr/pbr-material.d.ts.map +1 -1
  52. package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts +12 -1
  53. package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts.map +1 -1
  54. package/dist/webgl-to-webgpu/convert-webgl-attribute.js +3 -0
  55. package/dist/webgl-to-webgpu/convert-webgl-attribute.js.map +1 -1
  56. package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts +11 -5
  57. package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts.map +1 -1
  58. package/dist/webgl-to-webgpu/convert-webgl-sampler.js +16 -12
  59. package/dist/webgl-to-webgpu/convert-webgl-sampler.js.map +1 -1
  60. package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts +2 -9
  61. package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts.map +1 -1
  62. package/dist/webgl-to-webgpu/convert-webgl-topology.js +2 -14
  63. package/dist/webgl-to-webgpu/convert-webgl-topology.js.map +1 -1
  64. package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts +27 -0
  65. package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts.map +1 -0
  66. package/dist/webgl-to-webgpu/gltf-webgl-constants.js +34 -0
  67. package/dist/webgl-to-webgpu/gltf-webgl-constants.js.map +1 -0
  68. package/package.json +8 -9
  69. package/src/gltf/animations/animations.ts +17 -5
  70. package/src/gltf/animations/interpolate.ts +49 -68
  71. package/src/gltf/create-gltf-model.ts +214 -48
  72. package/src/gltf/create-scenegraph-from-gltf.ts +131 -12
  73. package/src/gltf/gltf-animator.ts +34 -25
  74. package/src/gltf/gltf-extension-support.ts +214 -0
  75. package/src/index.ts +10 -2
  76. package/src/parsers/parse-gltf-animations.ts +94 -33
  77. package/src/parsers/parse-gltf-lights.ts +147 -20
  78. package/src/parsers/parse-gltf.ts +170 -90
  79. package/src/parsers/parse-pbr-material.ts +865 -80
  80. package/src/pbr/pbr-environment.ts +38 -15
  81. package/src/pbr/pbr-material.ts +18 -3
  82. package/src/webgl-to-webgpu/convert-webgl-attribute.ts +12 -1
  83. package/src/webgl-to-webgpu/convert-webgl-sampler.ts +38 -29
  84. package/src/webgl-to-webgpu/convert-webgl-topology.ts +2 -14
  85. package/src/webgl-to-webgpu/gltf-webgl-constants.ts +35 -0
  86. package/dist/utils/deep-copy.d.ts +0 -3
  87. package/dist/utils/deep-copy.d.ts.map +0 -1
  88. package/dist/utils/deep-copy.js +0 -21
  89. package/dist/utils/deep-copy.js.map +0 -1
  90. package/src/utils/deep-copy.ts +0 -22
package/dist/dist.dev.js CHANGED
@@ -52,13 +52,6 @@ var __exports__ = (() => {
52
52
  }
53
53
  });
54
54
 
55
- // external-global-plugin:@luma.gl/constants
56
- var require_constants = __commonJS({
57
- "external-global-plugin:@luma.gl/constants"(exports, module) {
58
- module.exports = globalThis.luma;
59
- }
60
- });
61
-
62
55
  // external-global-plugin:@luma.gl/shadertools
63
56
  var require_shadertools = __commonJS({
64
57
  "external-global-plugin:@luma.gl/shadertools"(exports, module) {
@@ -71,6 +64,7 @@ var __exports__ = (() => {
71
64
  __export(bundle_exports, {
72
65
  GLTFAnimator: () => GLTFAnimator,
73
66
  createScenegraphsFromGLTF: () => createScenegraphsFromGLTF,
67
+ getGLTFExtensionSupport: () => getGLTFExtensionSupport,
74
68
  loadPBREnvironment: () => loadPBREnvironment,
75
69
  parseGLTFLights: () => parseGLTFLights,
76
70
  parsePBRMaterial: () => parsePBRMaterial
@@ -122,7 +116,7 @@ var __exports__ = (() => {
122
116
  }
123
117
 
124
118
  // ../../node_modules/@loaders.gl/images/dist/lib/utils/version.js
125
- var VERSION = true ? "4.3.2" : "latest";
119
+ var VERSION = true ? "4.4.0-alpha.18" : "latest";
126
120
 
127
121
  // ../../node_modules/@loaders.gl/images/dist/lib/category-api/image-type.js
128
122
  var parseImageNode = globalThis.loaders?.parseImageNode;
@@ -263,7 +257,6 @@ var __exports__ = (() => {
263
257
  }
264
258
 
265
259
  // ../../node_modules/@loaders.gl/images/dist/lib/parsers/parse-to-image-bitmap.js
266
- var EMPTY_OBJECT = {};
267
260
  var imagebitmapOptionsSupported = true;
268
261
  async function parseToImageBitmap(arrayBuffer, options, url) {
269
262
  let blob;
@@ -291,8 +284,13 @@ var __exports__ = (() => {
291
284
  return await createImageBitmap(blob);
292
285
  }
293
286
  function isEmptyObject(object) {
294
- for (const key in object || EMPTY_OBJECT) {
295
- return false;
287
+ if (!object) {
288
+ return true;
289
+ }
290
+ for (const key in object) {
291
+ if (Object.prototype.hasOwnProperty.call(object, key)) {
292
+ return false;
293
+ }
296
294
  }
297
295
  return true;
298
296
  }
@@ -616,6 +614,7 @@ var __exports__ = (() => {
616
614
 
617
615
  // src/pbr/pbr-environment.ts
618
616
  function loadPBREnvironment(device, props) {
617
+ const specularMipLevels = props.specularMipLevels ?? 1;
619
618
  const brdfLutTexture = new import_engine.DynamicTexture(device, {
620
619
  id: "brdfLUT",
621
620
  sampler: {
@@ -629,7 +628,9 @@ var __exports__ = (() => {
629
628
  });
630
629
  const diffuseEnvSampler = makeCube(device, {
631
630
  id: "DiffuseEnvSampler",
632
- getTextureForFace: (dir) => loadImageTexture(props.getTexUrl("diffuse", dir, 0)),
631
+ getTextureForFace: (face) => loadImageTexture(
632
+ props.getTexUrl("diffuse", FACES.indexOf(face), 0)
633
+ ),
633
634
  sampler: {
634
635
  addressModeU: "clamp-to-edge",
635
636
  addressModeV: "clamp-to-edge",
@@ -639,12 +640,13 @@ var __exports__ = (() => {
639
640
  });
640
641
  const specularEnvSampler = makeCube(device, {
641
642
  id: "SpecularEnvSampler",
642
- getTextureForFace: (dir) => {
643
+ getTextureForFace: (face) => {
643
644
  const imageArray = [];
644
- for (let lod = 0; lod <= props.specularMipLevels - 1; lod++) {
645
- imageArray.push(loadImageTexture(props.getTexUrl("specular", dir, lod)));
645
+ const direction = FACES.indexOf(face);
646
+ for (let lod = 0; lod < specularMipLevels; lod++) {
647
+ imageArray.push(loadImageTexture(props.getTexUrl("specular", direction, lod)));
646
648
  }
647
- return imageArray;
649
+ return Promise.all(imageArray);
648
650
  },
649
651
  sampler: {
650
652
  addressModeU: "clamp-to-edge",
@@ -660,32 +662,34 @@ var __exports__ = (() => {
660
662
  specularEnvSampler
661
663
  };
662
664
  }
663
- var FACES = [0, 1, 2, 3, 4, 5];
665
+ var FACES = ["+X", "-X", "+Y", "-Y", "+Z", "-Z"];
664
666
  function makeCube(device, {
665
667
  id,
666
668
  getTextureForFace,
667
669
  sampler
668
670
  }) {
669
- const data = {};
670
- FACES.forEach((face) => {
671
- data[String(face)] = getTextureForFace(face);
671
+ const data = Promise.all(
672
+ FACES.map((face) => getTextureForFace(face))
673
+ ).then((faceDataArray) => {
674
+ const cubeData = {};
675
+ FACES.forEach((face, index) => {
676
+ cubeData[face] = faceDataArray[index];
677
+ });
678
+ return cubeData;
672
679
  });
673
680
  return new import_engine.DynamicTexture(device, {
674
681
  id,
675
682
  dimension: "cube",
676
683
  mipmaps: false,
677
684
  sampler,
678
- // @ts-expect-error
679
685
  data
680
686
  });
681
687
  }
682
688
 
683
689
  // src/parsers/parse-pbr-material.ts
684
- var import_constants2 = __toESM(require_constants(), 1);
685
690
  var import_core = __toESM(require_core(), 1);
686
691
 
687
692
  // src/webgl-to-webgpu/convert-webgl-sampler.ts
688
- var import_constants = __toESM(require_constants(), 1);
689
693
  function convertSampler(gltfSampler) {
690
694
  return {
691
695
  addressModeU: convertSamplerWrapMode(gltfSampler.wrapS),
@@ -696,11 +700,11 @@ var __exports__ = (() => {
696
700
  }
697
701
  function convertSamplerWrapMode(mode) {
698
702
  switch (mode) {
699
- case import_constants.GL.CLAMP_TO_EDGE:
703
+ case 33071 /* CLAMP_TO_EDGE */:
700
704
  return "clamp-to-edge";
701
- case import_constants.GL.REPEAT:
705
+ case 10497 /* REPEAT */:
702
706
  return "repeat";
703
- case import_constants.GL.MIRRORED_REPEAT:
707
+ case 33648 /* MIRRORED_REPEAT */:
704
708
  return "mirror-repeat";
705
709
  default:
706
710
  return void 0;
@@ -708,9 +712,9 @@ var __exports__ = (() => {
708
712
  }
709
713
  function convertSamplerMagFilter(mode) {
710
714
  switch (mode) {
711
- case import_constants.GL.NEAREST:
715
+ case 9728 /* NEAREST */:
712
716
  return "nearest";
713
- case import_constants.GL.LINEAR:
717
+ case 9729 /* LINEAR */:
714
718
  return "linear";
715
719
  default:
716
720
  return void 0;
@@ -718,17 +722,17 @@ var __exports__ = (() => {
718
722
  }
719
723
  function convertSamplerMinFilter(mode) {
720
724
  switch (mode) {
721
- case import_constants.GL.NEAREST:
725
+ case 9728 /* NEAREST */:
722
726
  return { minFilter: "nearest" };
723
- case import_constants.GL.LINEAR:
727
+ case 9729 /* LINEAR */:
724
728
  return { minFilter: "linear" };
725
- case import_constants.GL.NEAREST_MIPMAP_NEAREST:
729
+ case 9984 /* NEAREST_MIPMAP_NEAREST */:
726
730
  return { minFilter: "nearest", mipmapFilter: "nearest" };
727
- case import_constants.GL.LINEAR_MIPMAP_NEAREST:
731
+ case 9985 /* LINEAR_MIPMAP_NEAREST */:
728
732
  return { minFilter: "linear", mipmapFilter: "nearest" };
729
- case import_constants.GL.NEAREST_MIPMAP_LINEAR:
733
+ case 9986 /* NEAREST_MIPMAP_LINEAR */:
730
734
  return { minFilter: "nearest", mipmapFilter: "linear" };
731
- case import_constants.GL.LINEAR_MIPMAP_LINEAR:
735
+ case 9987 /* LINEAR_MIPMAP_LINEAR */:
732
736
  return { minFilter: "linear", mipmapFilter: "linear" };
733
737
  default:
734
738
  return {};
@@ -760,7 +764,8 @@ var __exports__ = (() => {
760
764
  if (imageBasedLightingEnvironment) {
761
765
  parsedMaterial.bindings.pbr_diffuseEnvSampler = imageBasedLightingEnvironment.diffuseEnvSampler.texture;
762
766
  parsedMaterial.bindings.pbr_specularEnvSampler = imageBasedLightingEnvironment.specularEnvSampler.texture;
763
- parsedMaterial.bindings.pbr_BrdfLUT = imageBasedLightingEnvironment.brdfLutTexture.texture;
767
+ parsedMaterial.bindings.pbr_brdfLUT = imageBasedLightingEnvironment.brdfLutTexture.texture;
768
+ parsedMaterial.uniforms.IBLenabled = true;
764
769
  parsedMaterial.uniforms.scaleIBLAmbient = [1, 1];
765
770
  }
766
771
  if (options?.pbrDebug) {
@@ -774,6 +779,8 @@ var __exports__ = (() => {
774
779
  parsedMaterial.defines["HAS_TANGENTS"] = true;
775
780
  if (attributes["TEXCOORD_0"])
776
781
  parsedMaterial.defines["HAS_UV"] = true;
782
+ if (attributes["JOINTS_0"] && attributes["WEIGHTS_0"])
783
+ parsedMaterial.defines["HAS_SKIN"] = true;
777
784
  if (attributes["COLOR_0"])
778
785
  parsedMaterial.defines["HAS_COLORS"] = true;
779
786
  if (options?.imageBasedLightingEnvironment)
@@ -781,81 +788,181 @@ var __exports__ = (() => {
781
788
  if (options?.lights)
782
789
  parsedMaterial.defines["USE_LIGHTS"] = true;
783
790
  if (material) {
784
- parseMaterial(device, material, parsedMaterial);
791
+ if (options.validateAttributes !== false) {
792
+ warnOnMissingExpectedAttributes(material, attributes);
793
+ }
794
+ parseMaterial(device, material, parsedMaterial, options.gltf);
785
795
  }
786
796
  return parsedMaterial;
787
797
  }
788
- function parseMaterial(device, material, parsedMaterial) {
789
- parsedMaterial.uniforms.unlit = Boolean(material.unlit);
798
+ function warnOnMissingExpectedAttributes(material, attributes) {
799
+ const uvDependentTextureSlots = getUvDependentTextureSlots(material);
800
+ if (uvDependentTextureSlots.length > 0 && !attributes["TEXCOORD_0"]) {
801
+ import_core.log.warn(
802
+ `glTF material uses ${uvDependentTextureSlots.join(", ")} but primitive is missing TEXCOORD_0; textured shading will sample the default UV coordinates`
803
+ )();
804
+ }
805
+ const isUnlitMaterial = Boolean(material.unlit || material.extensions?.KHR_materials_unlit);
806
+ if (isUnlitMaterial || attributes["NORMAL"]) {
807
+ return;
808
+ }
809
+ const missingNormalReason = material.normalTexture ? "lit PBR shading with normalTexture" : "lit PBR shading";
810
+ import_core.log.warn(
811
+ `glTF primitive is missing NORMAL while using ${missingNormalReason}; shading will fall back to geometric normals`
812
+ )();
813
+ }
814
+ function getUvDependentTextureSlots(material) {
815
+ const uvDependentTextureSlots = [];
816
+ if (material.pbrMetallicRoughness?.baseColorTexture) {
817
+ uvDependentTextureSlots.push("baseColorTexture");
818
+ }
819
+ if (material.pbrMetallicRoughness?.metallicRoughnessTexture) {
820
+ uvDependentTextureSlots.push("metallicRoughnessTexture");
821
+ }
822
+ if (material.normalTexture) {
823
+ uvDependentTextureSlots.push("normalTexture");
824
+ }
825
+ if (material.occlusionTexture) {
826
+ uvDependentTextureSlots.push("occlusionTexture");
827
+ }
828
+ if (material.emissiveTexture) {
829
+ uvDependentTextureSlots.push("emissiveTexture");
830
+ }
831
+ if (material.extensions?.KHR_materials_specular?.specularTexture) {
832
+ uvDependentTextureSlots.push("KHR_materials_specular.specularTexture");
833
+ }
834
+ if (material.extensions?.KHR_materials_specular?.specularColorTexture) {
835
+ uvDependentTextureSlots.push("KHR_materials_specular.specularColorTexture");
836
+ }
837
+ if (material.extensions?.KHR_materials_transmission?.transmissionTexture) {
838
+ uvDependentTextureSlots.push("KHR_materials_transmission.transmissionTexture");
839
+ }
840
+ if (material.extensions?.KHR_materials_clearcoat?.clearcoatTexture) {
841
+ uvDependentTextureSlots.push("KHR_materials_clearcoat.clearcoatTexture");
842
+ }
843
+ if (material.extensions?.KHR_materials_clearcoat?.clearcoatRoughnessTexture) {
844
+ uvDependentTextureSlots.push("KHR_materials_clearcoat.clearcoatRoughnessTexture");
845
+ }
846
+ if (material.extensions?.KHR_materials_sheen?.sheenColorTexture) {
847
+ uvDependentTextureSlots.push("KHR_materials_sheen.sheenColorTexture");
848
+ }
849
+ if (material.extensions?.KHR_materials_sheen?.sheenRoughnessTexture) {
850
+ uvDependentTextureSlots.push("KHR_materials_sheen.sheenRoughnessTexture");
851
+ }
852
+ if (material.extensions?.KHR_materials_iridescence?.iridescenceTexture) {
853
+ uvDependentTextureSlots.push("KHR_materials_iridescence.iridescenceTexture");
854
+ }
855
+ if (material.extensions?.KHR_materials_anisotropy?.anisotropyTexture) {
856
+ uvDependentTextureSlots.push("KHR_materials_anisotropy.anisotropyTexture");
857
+ }
858
+ return uvDependentTextureSlots;
859
+ }
860
+ function parseMaterial(device, material, parsedMaterial, gltf) {
861
+ parsedMaterial.uniforms.unlit = Boolean(
862
+ material.unlit || material.extensions?.KHR_materials_unlit
863
+ );
790
864
  if (material.pbrMetallicRoughness) {
791
- parsePbrMetallicRoughness(device, material.pbrMetallicRoughness, parsedMaterial);
865
+ parsePbrMetallicRoughness(device, material.pbrMetallicRoughness, parsedMaterial, gltf);
792
866
  }
793
867
  if (material.normalTexture) {
794
- addTexture(
795
- device,
796
- material.normalTexture,
797
- "pbr_normalSampler",
798
- "HAS_NORMALMAP",
799
- parsedMaterial
800
- );
868
+ addTexture(device, material.normalTexture, "pbr_normalSampler", parsedMaterial, {
869
+ featureOptions: {
870
+ define: "HAS_NORMALMAP",
871
+ enabledUniformName: "normalMapEnabled"
872
+ },
873
+ gltf
874
+ });
801
875
  const { scale: scale4 = 1 } = material.normalTexture;
802
876
  parsedMaterial.uniforms.normalScale = scale4;
803
877
  }
804
878
  if (material.occlusionTexture) {
805
- addTexture(
806
- device,
807
- material.occlusionTexture,
808
- "pbr_occlusionSampler",
809
- "HAS_OCCLUSIONMAP",
810
- parsedMaterial
811
- );
879
+ addTexture(device, material.occlusionTexture, "pbr_occlusionSampler", parsedMaterial, {
880
+ featureOptions: {
881
+ define: "HAS_OCCLUSIONMAP",
882
+ enabledUniformName: "occlusionMapEnabled"
883
+ },
884
+ gltf
885
+ });
812
886
  const { strength = 1 } = material.occlusionTexture;
813
887
  parsedMaterial.uniforms.occlusionStrength = strength;
814
888
  }
889
+ parsedMaterial.uniforms.emissiveFactor = material.emissiveFactor || [0, 0, 0];
815
890
  if (material.emissiveTexture) {
816
- addTexture(
817
- device,
818
- material.emissiveTexture,
819
- "pbr_emissiveSampler",
820
- "HAS_EMISSIVEMAP",
821
- parsedMaterial
822
- );
823
- parsedMaterial.uniforms.emissiveFactor = material.emissiveFactor || [0, 0, 0];
891
+ addTexture(device, material.emissiveTexture, "pbr_emissiveSampler", parsedMaterial, {
892
+ featureOptions: {
893
+ define: "HAS_EMISSIVEMAP",
894
+ enabledUniformName: "emissiveMapEnabled"
895
+ },
896
+ gltf
897
+ });
824
898
  }
825
- switch (material.alphaMode || "MASK") {
826
- case "MASK":
899
+ parseMaterialExtensions(device, material.extensions, parsedMaterial, gltf);
900
+ switch (material.alphaMode || "OPAQUE") {
901
+ case "OPAQUE":
902
+ break;
903
+ case "MASK": {
827
904
  const { alphaCutoff = 0.5 } = material;
828
905
  parsedMaterial.defines["ALPHA_CUTOFF"] = true;
906
+ parsedMaterial.uniforms.alphaCutoffEnabled = true;
829
907
  parsedMaterial.uniforms.alphaCutoff = alphaCutoff;
830
908
  break;
909
+ }
831
910
  case "BLEND":
832
911
  import_core.log.warn("glTF BLEND alphaMode might not work well because it requires mesh sorting")();
833
- parsedMaterial.parameters.blend = true;
834
- parsedMaterial.parameters.blendColorOperation = "add";
835
- parsedMaterial.parameters.blendColorSrcFactor = "src-alpha";
836
- parsedMaterial.parameters.blendColorDstFactor = "one-minus-src-alpha";
837
- parsedMaterial.parameters.blendAlphaOperation = "add";
838
- parsedMaterial.parameters.blendAlphaSrcFactor = "one";
839
- parsedMaterial.parameters.blendAlphaDstFactor = "one-minus-src-alpha";
840
- parsedMaterial.glParameters["blend"] = true;
841
- parsedMaterial.glParameters["blendEquation"] = import_constants2.GL.FUNC_ADD;
842
- parsedMaterial.glParameters["blendFunc"] = [
843
- import_constants2.GL.SRC_ALPHA,
844
- import_constants2.GL.ONE_MINUS_SRC_ALPHA,
845
- import_constants2.GL.ONE,
846
- import_constants2.GL.ONE_MINUS_SRC_ALPHA
847
- ];
912
+ applyAlphaBlendParameters(parsedMaterial);
848
913
  break;
849
914
  }
850
915
  }
851
- function parsePbrMetallicRoughness(device, pbrMetallicRoughness, parsedMaterial) {
916
+ function applyAlphaBlendParameters(parsedMaterial) {
917
+ parsedMaterial.parameters.blend = true;
918
+ parsedMaterial.parameters.blendColorOperation = "add";
919
+ parsedMaterial.parameters.blendColorSrcFactor = "src-alpha";
920
+ parsedMaterial.parameters.blendColorDstFactor = "one-minus-src-alpha";
921
+ parsedMaterial.parameters.blendAlphaOperation = "add";
922
+ parsedMaterial.parameters.blendAlphaSrcFactor = "one";
923
+ parsedMaterial.parameters.blendAlphaDstFactor = "one-minus-src-alpha";
924
+ parsedMaterial.glParameters["blend"] = true;
925
+ parsedMaterial.glParameters["blendEquation"] = 32774 /* FUNC_ADD */;
926
+ parsedMaterial.glParameters["blendFunc"] = [
927
+ 770 /* SRC_ALPHA */,
928
+ 771 /* ONE_MINUS_SRC_ALPHA */,
929
+ 1 /* ONE */,
930
+ 771 /* ONE_MINUS_SRC_ALPHA */
931
+ ];
932
+ }
933
+ function applyTransmissionBlendApproximation(parsedMaterial) {
934
+ parsedMaterial.parameters.blend = true;
935
+ parsedMaterial.parameters.depthWriteEnabled = false;
936
+ parsedMaterial.parameters.blendColorOperation = "add";
937
+ parsedMaterial.parameters.blendColorSrcFactor = "one";
938
+ parsedMaterial.parameters.blendColorDstFactor = "one-minus-src-alpha";
939
+ parsedMaterial.parameters.blendAlphaOperation = "add";
940
+ parsedMaterial.parameters.blendAlphaSrcFactor = "one";
941
+ parsedMaterial.parameters.blendAlphaDstFactor = "one-minus-src-alpha";
942
+ parsedMaterial.glParameters["blend"] = true;
943
+ parsedMaterial.glParameters["depthMask"] = false;
944
+ parsedMaterial.glParameters["blendEquation"] = 32774 /* FUNC_ADD */;
945
+ parsedMaterial.glParameters["blendFunc"] = [
946
+ 1 /* ONE */,
947
+ 771 /* ONE_MINUS_SRC_ALPHA */,
948
+ 1 /* ONE */,
949
+ 771 /* ONE_MINUS_SRC_ALPHA */
950
+ ];
951
+ }
952
+ function parsePbrMetallicRoughness(device, pbrMetallicRoughness, parsedMaterial, gltf) {
852
953
  if (pbrMetallicRoughness.baseColorTexture) {
853
954
  addTexture(
854
955
  device,
855
956
  pbrMetallicRoughness.baseColorTexture,
856
957
  "pbr_baseColorSampler",
857
- "HAS_BASECOLORMAP",
858
- parsedMaterial
958
+ parsedMaterial,
959
+ {
960
+ featureOptions: {
961
+ define: "HAS_BASECOLORMAP",
962
+ enabledUniformName: "baseColorMapEnabled"
963
+ },
964
+ gltf
965
+ }
859
966
  );
860
967
  }
861
968
  parsedMaterial.uniforms.baseColorFactor = pbrMetallicRoughness.baseColorFactor || [1, 1, 1, 1];
@@ -864,20 +971,277 @@ var __exports__ = (() => {
864
971
  device,
865
972
  pbrMetallicRoughness.metallicRoughnessTexture,
866
973
  "pbr_metallicRoughnessSampler",
867
- "HAS_METALROUGHNESSMAP",
868
- parsedMaterial
974
+ parsedMaterial,
975
+ {
976
+ featureOptions: {
977
+ define: "HAS_METALROUGHNESSMAP",
978
+ enabledUniformName: "metallicRoughnessMapEnabled"
979
+ },
980
+ gltf
981
+ }
869
982
  );
870
983
  }
871
984
  const { metallicFactor = 1, roughnessFactor = 1 } = pbrMetallicRoughness;
872
985
  parsedMaterial.uniforms.metallicRoughnessValues = [metallicFactor, roughnessFactor];
873
986
  }
874
- function addTexture(device, gltfTexture, uniformName, define, parsedMaterial) {
875
- const image = gltfTexture.texture.source.image;
876
- let textureOptions;
877
- if (image.compressed) {
878
- textureOptions = image;
879
- } else {
880
- textureOptions = { data: image };
987
+ function parseMaterialExtensions(device, extensions, parsedMaterial, gltf) {
988
+ if (!extensions) {
989
+ return;
990
+ }
991
+ if (hasMaterialExtensionShading(extensions)) {
992
+ parsedMaterial.defines["USE_MATERIAL_EXTENSIONS"] = true;
993
+ }
994
+ parseSpecularExtension(device, extensions.KHR_materials_specular, parsedMaterial, gltf);
995
+ parseIorExtension(extensions.KHR_materials_ior, parsedMaterial);
996
+ parseTransmissionExtension(device, extensions.KHR_materials_transmission, parsedMaterial, gltf);
997
+ parseVolumeExtension(device, extensions.KHR_materials_volume, parsedMaterial, gltf);
998
+ parseClearcoatExtension(device, extensions.KHR_materials_clearcoat, parsedMaterial, gltf);
999
+ parseSheenExtension(device, extensions.KHR_materials_sheen, parsedMaterial, gltf);
1000
+ parseIridescenceExtension(device, extensions.KHR_materials_iridescence, parsedMaterial, gltf);
1001
+ parseAnisotropyExtension(device, extensions.KHR_materials_anisotropy, parsedMaterial, gltf);
1002
+ parseEmissiveStrengthExtension(extensions.KHR_materials_emissive_strength, parsedMaterial);
1003
+ }
1004
+ function hasMaterialExtensionShading(extensions) {
1005
+ return Boolean(
1006
+ extensions.KHR_materials_specular || extensions.KHR_materials_ior || extensions.KHR_materials_transmission || extensions.KHR_materials_volume || extensions.KHR_materials_clearcoat || extensions.KHR_materials_sheen || extensions.KHR_materials_iridescence || extensions.KHR_materials_anisotropy
1007
+ );
1008
+ }
1009
+ function parseSpecularExtension(device, extension, parsedMaterial, gltf) {
1010
+ if (!extension) {
1011
+ return;
1012
+ }
1013
+ if (extension.specularColorFactor) {
1014
+ parsedMaterial.uniforms.specularColorFactor = extension.specularColorFactor;
1015
+ }
1016
+ if (extension.specularFactor !== void 0) {
1017
+ parsedMaterial.uniforms.specularIntensityFactor = extension.specularFactor;
1018
+ }
1019
+ if (extension.specularColorTexture) {
1020
+ addTexture(device, extension.specularColorTexture, "pbr_specularColorSampler", parsedMaterial, {
1021
+ featureOptions: {
1022
+ define: "HAS_SPECULARCOLORMAP",
1023
+ enabledUniformName: "specularColorMapEnabled"
1024
+ },
1025
+ gltf
1026
+ });
1027
+ }
1028
+ if (extension.specularTexture) {
1029
+ addTexture(device, extension.specularTexture, "pbr_specularIntensitySampler", parsedMaterial, {
1030
+ featureOptions: {
1031
+ define: "HAS_SPECULARINTENSITYMAP",
1032
+ enabledUniformName: "specularIntensityMapEnabled"
1033
+ },
1034
+ gltf
1035
+ });
1036
+ }
1037
+ }
1038
+ function parseIorExtension(extension, parsedMaterial) {
1039
+ if (extension?.ior !== void 0) {
1040
+ parsedMaterial.uniforms.ior = extension.ior;
1041
+ }
1042
+ }
1043
+ function parseTransmissionExtension(device, extension, parsedMaterial, gltf) {
1044
+ if (!extension) {
1045
+ return;
1046
+ }
1047
+ if (extension.transmissionFactor !== void 0) {
1048
+ parsedMaterial.uniforms.transmissionFactor = extension.transmissionFactor;
1049
+ }
1050
+ if (extension.transmissionTexture) {
1051
+ addTexture(device, extension.transmissionTexture, "pbr_transmissionSampler", parsedMaterial, {
1052
+ featureOptions: {
1053
+ define: "HAS_TRANSMISSIONMAP",
1054
+ enabledUniformName: "transmissionMapEnabled"
1055
+ },
1056
+ gltf
1057
+ });
1058
+ }
1059
+ if ((extension.transmissionFactor ?? 0) > 0 || extension.transmissionTexture) {
1060
+ import_core.log.warn(
1061
+ "KHR_materials_transmission uses a premultiplied-alpha blending approximation and may require mesh sorting"
1062
+ )();
1063
+ applyTransmissionBlendApproximation(parsedMaterial);
1064
+ }
1065
+ }
1066
+ function parseVolumeExtension(device, extension, parsedMaterial, gltf) {
1067
+ if (!extension) {
1068
+ return;
1069
+ }
1070
+ if (extension.thicknessFactor !== void 0) {
1071
+ parsedMaterial.uniforms.thicknessFactor = extension.thicknessFactor;
1072
+ }
1073
+ if (extension.thicknessTexture) {
1074
+ addTexture(device, extension.thicknessTexture, "pbr_thicknessSampler", parsedMaterial, {
1075
+ featureOptions: {
1076
+ define: "HAS_THICKNESSMAP"
1077
+ },
1078
+ gltf
1079
+ });
1080
+ }
1081
+ if (extension.attenuationDistance !== void 0) {
1082
+ parsedMaterial.uniforms.attenuationDistance = extension.attenuationDistance;
1083
+ }
1084
+ if (extension.attenuationColor) {
1085
+ parsedMaterial.uniforms.attenuationColor = extension.attenuationColor;
1086
+ }
1087
+ }
1088
+ function parseClearcoatExtension(device, extension, parsedMaterial, gltf) {
1089
+ if (!extension) {
1090
+ return;
1091
+ }
1092
+ if (extension.clearcoatFactor !== void 0) {
1093
+ parsedMaterial.uniforms.clearcoatFactor = extension.clearcoatFactor;
1094
+ }
1095
+ if (extension.clearcoatRoughnessFactor !== void 0) {
1096
+ parsedMaterial.uniforms.clearcoatRoughnessFactor = extension.clearcoatRoughnessFactor;
1097
+ }
1098
+ if (extension.clearcoatTexture) {
1099
+ addTexture(device, extension.clearcoatTexture, "pbr_clearcoatSampler", parsedMaterial, {
1100
+ featureOptions: {
1101
+ define: "HAS_CLEARCOATMAP",
1102
+ enabledUniformName: "clearcoatMapEnabled"
1103
+ },
1104
+ gltf
1105
+ });
1106
+ }
1107
+ if (extension.clearcoatRoughnessTexture) {
1108
+ addTexture(
1109
+ device,
1110
+ extension.clearcoatRoughnessTexture,
1111
+ "pbr_clearcoatRoughnessSampler",
1112
+ parsedMaterial,
1113
+ {
1114
+ featureOptions: {
1115
+ define: "HAS_CLEARCOATROUGHNESSMAP",
1116
+ enabledUniformName: "clearcoatRoughnessMapEnabled"
1117
+ },
1118
+ gltf
1119
+ }
1120
+ );
1121
+ }
1122
+ if (extension.clearcoatNormalTexture) {
1123
+ addTexture(
1124
+ device,
1125
+ extension.clearcoatNormalTexture,
1126
+ "pbr_clearcoatNormalSampler",
1127
+ parsedMaterial,
1128
+ {
1129
+ featureOptions: {
1130
+ define: "HAS_CLEARCOATNORMALMAP"
1131
+ },
1132
+ gltf
1133
+ }
1134
+ );
1135
+ }
1136
+ }
1137
+ function parseSheenExtension(device, extension, parsedMaterial, gltf) {
1138
+ if (!extension) {
1139
+ return;
1140
+ }
1141
+ if (extension.sheenColorFactor) {
1142
+ parsedMaterial.uniforms.sheenColorFactor = extension.sheenColorFactor;
1143
+ }
1144
+ if (extension.sheenRoughnessFactor !== void 0) {
1145
+ parsedMaterial.uniforms.sheenRoughnessFactor = extension.sheenRoughnessFactor;
1146
+ }
1147
+ if (extension.sheenColorTexture) {
1148
+ addTexture(device, extension.sheenColorTexture, "pbr_sheenColorSampler", parsedMaterial, {
1149
+ featureOptions: {
1150
+ define: "HAS_SHEENCOLORMAP",
1151
+ enabledUniformName: "sheenColorMapEnabled"
1152
+ },
1153
+ gltf
1154
+ });
1155
+ }
1156
+ if (extension.sheenRoughnessTexture) {
1157
+ addTexture(
1158
+ device,
1159
+ extension.sheenRoughnessTexture,
1160
+ "pbr_sheenRoughnessSampler",
1161
+ parsedMaterial,
1162
+ {
1163
+ featureOptions: {
1164
+ define: "HAS_SHEENROUGHNESSMAP",
1165
+ enabledUniformName: "sheenRoughnessMapEnabled"
1166
+ },
1167
+ gltf
1168
+ }
1169
+ );
1170
+ }
1171
+ }
1172
+ function parseIridescenceExtension(device, extension, parsedMaterial, gltf) {
1173
+ if (!extension) {
1174
+ return;
1175
+ }
1176
+ if (extension.iridescenceFactor !== void 0) {
1177
+ parsedMaterial.uniforms.iridescenceFactor = extension.iridescenceFactor;
1178
+ }
1179
+ if (extension.iridescenceIor !== void 0) {
1180
+ parsedMaterial.uniforms.iridescenceIor = extension.iridescenceIor;
1181
+ }
1182
+ if (extension.iridescenceThicknessMinimum !== void 0 || extension.iridescenceThicknessMaximum !== void 0) {
1183
+ parsedMaterial.uniforms.iridescenceThicknessRange = [
1184
+ extension.iridescenceThicknessMinimum ?? 100,
1185
+ extension.iridescenceThicknessMaximum ?? 400
1186
+ ];
1187
+ }
1188
+ if (extension.iridescenceTexture) {
1189
+ addTexture(device, extension.iridescenceTexture, "pbr_iridescenceSampler", parsedMaterial, {
1190
+ featureOptions: {
1191
+ define: "HAS_IRIDESCENCEMAP",
1192
+ enabledUniformName: "iridescenceMapEnabled"
1193
+ },
1194
+ gltf
1195
+ });
1196
+ }
1197
+ if (extension.iridescenceThicknessTexture) {
1198
+ addTexture(
1199
+ device,
1200
+ extension.iridescenceThicknessTexture,
1201
+ "pbr_iridescenceThicknessSampler",
1202
+ parsedMaterial,
1203
+ {
1204
+ featureOptions: {
1205
+ define: "HAS_IRIDESCENCETHICKNESSMAP"
1206
+ },
1207
+ gltf
1208
+ }
1209
+ );
1210
+ }
1211
+ }
1212
+ function parseAnisotropyExtension(device, extension, parsedMaterial, gltf) {
1213
+ if (!extension) {
1214
+ return;
1215
+ }
1216
+ if (extension.anisotropyStrength !== void 0) {
1217
+ parsedMaterial.uniforms.anisotropyStrength = extension.anisotropyStrength;
1218
+ }
1219
+ if (extension.anisotropyRotation !== void 0) {
1220
+ parsedMaterial.uniforms.anisotropyRotation = extension.anisotropyRotation;
1221
+ }
1222
+ if (extension.anisotropyTexture) {
1223
+ addTexture(device, extension.anisotropyTexture, "pbr_anisotropySampler", parsedMaterial, {
1224
+ featureOptions: {
1225
+ define: "HAS_ANISOTROPYMAP",
1226
+ enabledUniformName: "anisotropyMapEnabled"
1227
+ },
1228
+ gltf
1229
+ });
1230
+ }
1231
+ }
1232
+ function parseEmissiveStrengthExtension(extension, parsedMaterial) {
1233
+ if (extension?.emissiveStrength !== void 0) {
1234
+ parsedMaterial.uniforms.emissiveStrength = extension.emissiveStrength;
1235
+ }
1236
+ }
1237
+ function addTexture(device, gltfTexture, uniformName, parsedMaterial, textureParseOptions = {}) {
1238
+ const { featureOptions = {}, gltf } = textureParseOptions;
1239
+ const { define, enabledUniformName } = featureOptions;
1240
+ const resolvedTextureInfo = resolveTextureInfo(gltfTexture, gltf);
1241
+ const image = resolvedTextureInfo.texture?.source?.image;
1242
+ if (!image) {
1243
+ import_core.log.warn(`Skipping unresolved glTF texture for ${String(uniformName)}`)();
1244
+ return;
881
1245
  }
882
1246
  const gltfSampler = {
883
1247
  wrapS: 10497,
@@ -888,18 +1252,150 @@ var __exports__ = (() => {
888
1252
  // default LINEAR filtering
889
1253
  magFilter: 9729,
890
1254
  // default LINEAR filtering
891
- ...gltfTexture?.texture?.sampler
1255
+ ...resolvedTextureInfo?.texture?.sampler
892
1256
  };
893
- const texture = device.createTexture({
894
- id: gltfTexture.uniformName || gltfTexture.id,
895
- sampler: convertSampler(gltfSampler),
896
- ...textureOptions
897
- });
1257
+ const baseOptions = {
1258
+ id: resolvedTextureInfo.uniformName || resolvedTextureInfo.id,
1259
+ sampler: convertSampler(gltfSampler)
1260
+ };
1261
+ let texture;
1262
+ if (image.compressed) {
1263
+ texture = createCompressedTexture(device, image, baseOptions);
1264
+ } else {
1265
+ const { width, height } = device.getExternalImageSize(image);
1266
+ texture = device.createTexture({
1267
+ ...baseOptions,
1268
+ width,
1269
+ height,
1270
+ data: image
1271
+ });
1272
+ }
898
1273
  parsedMaterial.bindings[uniformName] = texture;
899
1274
  if (define)
900
1275
  parsedMaterial.defines[define] = true;
1276
+ if (enabledUniformName) {
1277
+ parsedMaterial.uniforms[enabledUniformName] = true;
1278
+ }
901
1279
  parsedMaterial.generatedTextures.push(texture);
902
1280
  }
1281
+ function resolveTextureInfo(gltfTexture, gltf) {
1282
+ if (gltfTexture.texture || gltfTexture.index === void 0 || !gltf?.textures) {
1283
+ return gltfTexture;
1284
+ }
1285
+ const resolvedTextureEntry = gltf.textures[gltfTexture.index];
1286
+ if (!resolvedTextureEntry) {
1287
+ return gltfTexture;
1288
+ }
1289
+ if ("texture" in resolvedTextureEntry && resolvedTextureEntry.texture) {
1290
+ return {
1291
+ ...resolvedTextureEntry,
1292
+ ...gltfTexture,
1293
+ texture: resolvedTextureEntry.texture
1294
+ };
1295
+ }
1296
+ if (!("source" in resolvedTextureEntry)) {
1297
+ return gltfTexture;
1298
+ }
1299
+ return {
1300
+ ...gltfTexture,
1301
+ texture: resolvedTextureEntry
1302
+ };
1303
+ }
1304
+ function createCompressedTextureFallback(device, baseOptions) {
1305
+ return device.createTexture({
1306
+ ...baseOptions,
1307
+ format: "rgba8unorm",
1308
+ width: 1,
1309
+ height: 1,
1310
+ mipLevels: 1
1311
+ });
1312
+ }
1313
+ function resolveCompressedTextureFormat(level) {
1314
+ return level.textureFormat;
1315
+ }
1316
+ function getMaxCompressedMipLevels(baseWidth, baseHeight, format) {
1317
+ const { blockWidth = 1, blockHeight = 1 } = import_core.textureFormatDecoder.getInfo(format);
1318
+ let count = 1;
1319
+ for (let i = 1; ; i++) {
1320
+ const w = Math.max(1, baseWidth >> i);
1321
+ const h = Math.max(1, baseHeight >> i);
1322
+ if (w < blockWidth || h < blockHeight)
1323
+ break;
1324
+ count++;
1325
+ }
1326
+ return count;
1327
+ }
1328
+ function createCompressedTexture(device, image, baseOptions) {
1329
+ let levels;
1330
+ if (Array.isArray(image.data) && image.data[0]?.data) {
1331
+ levels = image.data;
1332
+ } else if ("mipmaps" in image && Array.isArray(image.mipmaps)) {
1333
+ levels = image.mipmaps;
1334
+ } else {
1335
+ levels = [];
1336
+ }
1337
+ if (levels.length === 0 || !levels[0]?.data) {
1338
+ import_core.log.warn(
1339
+ "createCompressedTexture: compressed image has no valid mip levels, creating fallback"
1340
+ )();
1341
+ return createCompressedTextureFallback(device, baseOptions);
1342
+ }
1343
+ const baseLevel = levels[0];
1344
+ const baseWidth = baseLevel.width ?? image.width ?? 0;
1345
+ const baseHeight = baseLevel.height ?? image.height ?? 0;
1346
+ if (baseWidth <= 0 || baseHeight <= 0) {
1347
+ import_core.log.warn("createCompressedTexture: base level has invalid dimensions, creating fallback")();
1348
+ return createCompressedTextureFallback(device, baseOptions);
1349
+ }
1350
+ const format = resolveCompressedTextureFormat(baseLevel);
1351
+ if (!format) {
1352
+ import_core.log.warn("createCompressedTexture: compressed image has no textureFormat, creating fallback")();
1353
+ return createCompressedTextureFallback(device, baseOptions);
1354
+ }
1355
+ const maxMipLevels = getMaxCompressedMipLevels(baseWidth, baseHeight, format);
1356
+ const levelLimit = Math.min(levels.length, maxMipLevels);
1357
+ let validLevelCount = 1;
1358
+ for (let i = 1; i < levelLimit; i++) {
1359
+ const level = levels[i];
1360
+ if (!level.data || level.width <= 0 || level.height <= 0) {
1361
+ import_core.log.warn(`createCompressedTexture: mip level ${i} has invalid data/dimensions, truncating`)();
1362
+ break;
1363
+ }
1364
+ const levelFormat = resolveCompressedTextureFormat(level);
1365
+ if (levelFormat && levelFormat !== format) {
1366
+ import_core.log.warn(
1367
+ `createCompressedTexture: mip level ${i} format '${levelFormat}' differs from base '${format}', truncating`
1368
+ )();
1369
+ break;
1370
+ }
1371
+ const expectedW = Math.max(1, baseWidth >> i);
1372
+ const expectedH = Math.max(1, baseHeight >> i);
1373
+ if (level.width !== expectedW || level.height !== expectedH) {
1374
+ import_core.log.warn(
1375
+ `createCompressedTexture: mip level ${i} dimensions ${level.width}x${level.height} don't match expected ${expectedW}x${expectedH}, truncating`
1376
+ )();
1377
+ break;
1378
+ }
1379
+ validLevelCount++;
1380
+ }
1381
+ const texture = device.createTexture({
1382
+ ...baseOptions,
1383
+ format,
1384
+ usage: import_core.Texture.TEXTURE | import_core.Texture.COPY_DST,
1385
+ width: baseWidth,
1386
+ height: baseHeight,
1387
+ mipLevels: validLevelCount,
1388
+ data: baseLevel.data
1389
+ });
1390
+ for (let i = 1; i < validLevelCount; i++) {
1391
+ texture.writeData(levels[i].data, {
1392
+ width: levels[i].width,
1393
+ height: levels[i].height,
1394
+ mipLevel: i
1395
+ });
1396
+ }
1397
+ return texture;
1398
+ }
903
1399
 
904
1400
  // ../../node_modules/@math.gl/core/dist/lib/common.js
905
1401
  var RADIANS_TO_DEGREES = 1 / Math.PI * 180;
@@ -3473,33 +3969,42 @@ var __exports__ = (() => {
3473
3969
  };
3474
3970
 
3475
3971
  // src/parsers/parse-gltf-lights.ts
3972
+ var GLTF_COLOR_FACTOR = 255;
3476
3973
  function parseGLTFLights(gltf) {
3477
- const lightDefs = gltf.extensions?.["KHR_lights_punctual"]?.["lights"];
3974
+ const lightDefs = (
3975
+ // `postProcessGLTF()` moves KHR_lights_punctual into `gltf.lights`.
3976
+ gltf.lights || gltf.extensions?.["KHR_lights_punctual"]?.["lights"]
3977
+ );
3478
3978
  if (!lightDefs || !Array.isArray(lightDefs) || lightDefs.length === 0) {
3479
3979
  return [];
3480
3980
  }
3481
3981
  const lights = [];
3982
+ const parentNodeById = createParentNodeMap(gltf.nodes || []);
3983
+ const worldMatrixByNodeId = /* @__PURE__ */ new Map();
3482
3984
  for (const node of gltf.nodes || []) {
3483
- const nodeLight = node.extensions?.KHR_lights_punctual;
3484
- if (!nodeLight || typeof nodeLight.light !== "number") {
3985
+ const lightIndex = node.light ?? node.extensions?.KHR_lights_punctual?.light;
3986
+ if (typeof lightIndex !== "number") {
3485
3987
  continue;
3486
3988
  }
3487
- const gltfLight = lightDefs[nodeLight.light];
3989
+ const gltfLight = lightDefs[lightIndex];
3488
3990
  if (!gltfLight) {
3489
3991
  continue;
3490
3992
  }
3491
- const color = gltfLight.color || [1, 1, 1];
3993
+ const color = normalizeGLTFLightColor(
3994
+ gltfLight.color || [1, 1, 1]
3995
+ );
3492
3996
  const intensity = gltfLight.intensity ?? 1;
3493
3997
  const range = gltfLight.range;
3998
+ const worldMatrix = getNodeWorldMatrix(node, parentNodeById, worldMatrixByNodeId);
3494
3999
  switch (gltfLight.type) {
3495
4000
  case "directional":
3496
- lights.push(parseDirectionalLight(node, color, intensity));
4001
+ lights.push(parseDirectionalLight(worldMatrix, color, intensity));
3497
4002
  break;
3498
4003
  case "point":
3499
- lights.push(parsePointLight(node, color, intensity, range));
4004
+ lights.push(parsePointLight(worldMatrix, color, intensity, range));
3500
4005
  break;
3501
4006
  case "spot":
3502
- lights.push(parsePointLight(node, color, intensity, range));
4007
+ lights.push(parseSpotLight(worldMatrix, color, intensity, range, gltfLight.spot));
3503
4008
  break;
3504
4009
  default:
3505
4010
  break;
@@ -3507,8 +4012,11 @@ var __exports__ = (() => {
3507
4012
  }
3508
4013
  return lights;
3509
4014
  }
3510
- function parsePointLight(node, color, intensity, range) {
3511
- const position = node.translation ? [...node.translation] : [0, 0, 0];
4015
+ function normalizeGLTFLightColor(color) {
4016
+ return color.map((component) => component * GLTF_COLOR_FACTOR);
4017
+ }
4018
+ function parsePointLight(worldMatrix, color, intensity, range) {
4019
+ const position = getLightPosition(worldMatrix);
3512
4020
  let attenuation = [1, 0, 0];
3513
4021
  if (range !== void 0 && range > 0) {
3514
4022
  attenuation = [1, 0, 1 / (range * range)];
@@ -3521,12 +4029,8 @@ var __exports__ = (() => {
3521
4029
  attenuation
3522
4030
  };
3523
4031
  }
3524
- 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
- }
4032
+ function parseDirectionalLight(worldMatrix, color, intensity) {
4033
+ const direction = getLightDirection(worldMatrix);
3530
4034
  return {
3531
4035
  type: "directional",
3532
4036
  direction,
@@ -3534,9 +4038,72 @@ var __exports__ = (() => {
3534
4038
  intensity
3535
4039
  };
3536
4040
  }
4041
+ function parseSpotLight(worldMatrix, color, intensity, range, spot = {}) {
4042
+ const position = getLightPosition(worldMatrix);
4043
+ const direction = getLightDirection(worldMatrix);
4044
+ let attenuation = [1, 0, 0];
4045
+ if (range !== void 0 && range > 0) {
4046
+ attenuation = [1, 0, 1 / (range * range)];
4047
+ }
4048
+ return {
4049
+ type: "spot",
4050
+ position,
4051
+ direction,
4052
+ color,
4053
+ intensity,
4054
+ attenuation,
4055
+ innerConeAngle: spot.innerConeAngle ?? 0,
4056
+ outerConeAngle: spot.outerConeAngle ?? Math.PI / 4
4057
+ };
4058
+ }
4059
+ function createParentNodeMap(nodes) {
4060
+ const parentNodeById = /* @__PURE__ */ new Map();
4061
+ for (const node of nodes) {
4062
+ for (const childNode of node.children || []) {
4063
+ parentNodeById.set(childNode.id, node);
4064
+ }
4065
+ }
4066
+ return parentNodeById;
4067
+ }
4068
+ function getNodeWorldMatrix(node, parentNodeById, worldMatrixByNodeId) {
4069
+ const cachedWorldMatrix = worldMatrixByNodeId.get(node.id);
4070
+ if (cachedWorldMatrix) {
4071
+ return cachedWorldMatrix;
4072
+ }
4073
+ const localMatrix = getNodeLocalMatrix(node);
4074
+ const parentNode = parentNodeById.get(node.id);
4075
+ const worldMatrix = parentNode ? new Matrix4(
4076
+ getNodeWorldMatrix(parentNode, parentNodeById, worldMatrixByNodeId)
4077
+ ).multiplyRight(localMatrix) : localMatrix;
4078
+ worldMatrixByNodeId.set(node.id, worldMatrix);
4079
+ return worldMatrix;
4080
+ }
4081
+ function getNodeLocalMatrix(node) {
4082
+ if (node.matrix) {
4083
+ return new Matrix4(node.matrix);
4084
+ }
4085
+ const matrix = new Matrix4();
4086
+ if (node.translation) {
4087
+ matrix.translate(node.translation);
4088
+ }
4089
+ if (node.rotation) {
4090
+ matrix.multiplyRight(new Matrix4().fromQuaternion(node.rotation));
4091
+ }
4092
+ if (node.scale) {
4093
+ matrix.scale(node.scale);
4094
+ }
4095
+ return matrix;
4096
+ }
4097
+ function getLightPosition(worldMatrix) {
4098
+ return worldMatrix.transformAsPoint([0, 0, 0]);
4099
+ }
4100
+ function getLightDirection(worldMatrix) {
4101
+ return worldMatrix.transformDirection([0, 0, -1]);
4102
+ }
3537
4103
 
3538
4104
  // src/parsers/parse-gltf.ts
3539
- var import_engine3 = __toESM(require_engine(), 1);
4105
+ var import_engine4 = __toESM(require_engine(), 1);
4106
+ var import_shadertools2 = __toESM(require_shadertools(), 1);
3540
4107
 
3541
4108
  // src/webgl-to-webgpu/convert-webgl-topology.ts
3542
4109
  function convertGLDrawModeToTopology(drawMode) {
@@ -3558,55 +4125,94 @@ var __exports__ = (() => {
3558
4125
 
3559
4126
  // src/gltf/create-gltf-model.ts
3560
4127
  var import_core3 = __toESM(require_core(), 1);
3561
- var import_shadertools = __toESM(require_shadertools(), 1);
3562
4128
  var import_engine2 = __toESM(require_engine(), 1);
4129
+ var import_shadertools = __toESM(require_shadertools(), 1);
4130
+ var import_engine3 = __toESM(require_engine(), 1);
3563
4131
  var SHADER = (
3564
4132
  /* WGSL */
3565
4133
  `
3566
- layout(0) positions: vec4; // in vec4 POSITION;
3567
-
3568
- #ifdef HAS_NORMALS
3569
- in vec4 normals; // in vec4 NORMAL;
3570
- #endif
3571
-
3572
- #ifdef HAS_TANGENTS
3573
- in vec4 TANGENT;
3574
- #endif
4134
+ struct VertexInputs {
4135
+ @location(0) positions: vec3f,
4136
+ #ifdef HAS_NORMALS
4137
+ @location(1) normals: vec3f,
4138
+ #endif
4139
+ #ifdef HAS_TANGENTS
4140
+ @location(2) TANGENT: vec4f,
4141
+ #endif
4142
+ #ifdef HAS_UV
4143
+ @location(3) texCoords: vec2f,
4144
+ #endif
4145
+ #ifdef HAS_SKIN
4146
+ @location(4) JOINTS_0: vec4u,
4147
+ @location(5) WEIGHTS_0: vec4f,
4148
+ #endif
4149
+ };
3575
4150
 
3576
- #ifdef HAS_UV
3577
- // in vec2 TEXCOORD_0;
3578
- in vec2 texCoords;
3579
- #endif
4151
+ struct FragmentInputs {
4152
+ @builtin(position) position: vec4f,
4153
+ @location(0) pbrPosition: vec3f,
4154
+ @location(1) pbrUV: vec2f,
4155
+ @location(2) pbrNormal: vec3f,
4156
+ #ifdef HAS_TANGENTS
4157
+ @location(3) pbrTangent: vec4f,
4158
+ #endif
4159
+ };
3580
4160
 
3581
4161
  @vertex
3582
- void main(void) {
3583
- vec4 _NORMAL = vec4(0.);
3584
- vec4 _TANGENT = vec4(0.);
3585
- vec2 _TEXCOORD_0 = vec2(0.);
4162
+ fn vertexMain(inputs: VertexInputs) -> FragmentInputs {
4163
+ var outputs: FragmentInputs;
4164
+ var position = vec4f(inputs.positions, 1.0);
4165
+ var normal = vec3f(0.0, 0.0, 1.0);
4166
+ var tangent = vec4f(1.0, 0.0, 0.0, 1.0);
4167
+ var uv = vec2f(0.0, 0.0);
3586
4168
 
3587
- #ifdef HAS_NORMALS
3588
- _NORMAL = normals;
3589
- #endif
4169
+ #ifdef HAS_NORMALS
4170
+ normal = inputs.normals;
4171
+ #endif
4172
+ #ifdef HAS_UV
4173
+ uv = inputs.texCoords;
4174
+ #endif
4175
+ #ifdef HAS_TANGENTS
4176
+ tangent = inputs.TANGENT;
4177
+ #endif
4178
+ #ifdef HAS_SKIN
4179
+ let skinMatrix = getSkinMatrix(inputs.WEIGHTS_0, inputs.JOINTS_0);
4180
+ position = skinMatrix * position;
4181
+ normal = normalize((skinMatrix * vec4f(normal, 0.0)).xyz);
4182
+ #ifdef HAS_TANGENTS
4183
+ tangent = vec4f(normalize((skinMatrix * vec4f(tangent.xyz, 0.0)).xyz), tangent.w);
4184
+ #endif
4185
+ #endif
3590
4186
 
3591
- #ifdef HAS_TANGENTS
3592
- _TANGENT = TANGENT;
3593
- #endif
4187
+ let worldPosition = pbrProjection.modelMatrix * position;
3594
4188
 
3595
- #ifdef HAS_UV
3596
- _TEXCOORD_0 = texCoords;
3597
- #endif
4189
+ #ifdef HAS_NORMALS
4190
+ normal = normalize((pbrProjection.normalMatrix * vec4f(normal, 0.0)).xyz);
4191
+ #endif
4192
+ #ifdef HAS_TANGENTS
4193
+ let worldTangent = normalize((pbrProjection.modelMatrix * vec4f(tangent.xyz, 0.0)).xyz);
4194
+ outputs.pbrTangent = vec4f(worldTangent, tangent.w);
4195
+ #endif
3598
4196
 
3599
- pbr_setPositionNormalTangentUV(positions, _NORMAL, _TANGENT, _TEXCOORD_0);
3600
- gl_Position = u_MVPMatrix * positions;
3601
- }
4197
+ outputs.position = pbrProjection.modelViewProjectionMatrix * position;
4198
+ outputs.pbrPosition = worldPosition.xyz / worldPosition.w;
4199
+ outputs.pbrUV = uv;
4200
+ outputs.pbrNormal = normal;
4201
+ return outputs;
4202
+ }
3602
4203
 
3603
4204
  @fragment
3604
- out vec4 fragmentColor;
3605
-
3606
- void main(void) {
3607
- vec3 pos = pbr_vPosition;
3608
- fragmentColor = pbr_filterColor(vec4(1.0));
3609
- }
4205
+ fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
4206
+ fragmentInputs.pbr_vPosition = inputs.pbrPosition;
4207
+ fragmentInputs.pbr_vUV = inputs.pbrUV;
4208
+ fragmentInputs.pbr_vNormal = inputs.pbrNormal;
4209
+ #ifdef HAS_TANGENTS
4210
+ let tangent = normalize(inputs.pbrTangent.xyz);
4211
+ let bitangent = normalize(cross(inputs.pbrNormal, tangent)) * inputs.pbrTangent.w;
4212
+ fragmentInputs.pbr_vTBN = mat3x3f(tangent, bitangent, inputs.pbrNormal);
4213
+ #endif
4214
+ return pbr_filterColor(vec4f(1.0));
4215
+ }
3610
4216
  `
3611
4217
  );
3612
4218
  var vs = (
@@ -3630,6 +4236,11 @@ layout(0) positions: vec4; // in vec4 POSITION;
3630
4236
  in vec2 texCoords;
3631
4237
  #endif
3632
4238
 
4239
+ #ifdef HAS_SKIN
4240
+ in uvec4 JOINTS_0;
4241
+ in vec4 WEIGHTS_0;
4242
+ #endif
4243
+
3633
4244
  void main(void) {
3634
4245
  vec4 _NORMAL = vec4(0.);
3635
4246
  vec4 _TANGENT = vec4(0.);
@@ -3647,8 +4258,17 @@ layout(0) positions: vec4; // in vec4 POSITION;
3647
4258
  _TEXCOORD_0 = texCoords;
3648
4259
  #endif
3649
4260
 
3650
- pbr_setPositionNormalTangentUV(positions, _NORMAL, _TANGENT, _TEXCOORD_0);
3651
- gl_Position = pbrProjection.modelViewProjectionMatrix * positions;
4261
+ vec4 pos = positions;
4262
+
4263
+ #ifdef HAS_SKIN
4264
+ mat4 skinMat = getSkinMatrix(WEIGHTS_0, JOINTS_0);
4265
+ pos = skinMat * pos;
4266
+ _NORMAL = skinMat * _NORMAL;
4267
+ _TANGENT = vec4((skinMat * vec4(_TANGENT.xyz, 0.)).xyz, _TANGENT.w);
4268
+ #endif
4269
+
4270
+ pbr_setPositionNormalTangentUV(pos, _NORMAL, _TANGENT, _TEXCOORD_0);
4271
+ gl_Position = pbrProjection.modelViewProjectionMatrix * pos;
3652
4272
  }
3653
4273
  `
3654
4274
  );
@@ -3663,6 +4283,25 @@ layout(0) positions: vec4; // in vec4 POSITION;
3663
4283
  }
3664
4284
  `
3665
4285
  );
4286
+ function createGLTFMaterial(device, options) {
4287
+ const materialFactory = options.materialFactory || new import_engine3.MaterialFactory(device, { modules: [import_shadertools.pbrMaterial] });
4288
+ const pbrMaterialProps = { ...options.parsedPPBRMaterial.uniforms };
4289
+ delete pbrMaterialProps.camera;
4290
+ const materialBindings = Object.fromEntries(
4291
+ Object.entries({
4292
+ ...pbrMaterialProps,
4293
+ ...options.parsedPPBRMaterial.bindings
4294
+ }).filter(
4295
+ ([name, value]) => materialFactory.ownsBinding(name) && isMaterialBindingResource(value)
4296
+ )
4297
+ );
4298
+ const material = materialFactory.createMaterial({
4299
+ id: options.id,
4300
+ bindings: materialBindings
4301
+ });
4302
+ material.setProps({ pbrMaterial: pbrMaterialProps });
4303
+ return material;
4304
+ }
3666
4305
  function createGLTFModel(device, options) {
3667
4306
  const { id, geometry, parsedPPBRMaterial, vertexCount, modelOptions = {} } = options;
3668
4307
  import_core3.log.info(4, "createGLTFModel defines: ", parsedPPBRMaterial.defines)();
@@ -3681,20 +4320,57 @@ layout(0) positions: vec4; // in vec4 POSITION;
3681
4320
  geometry,
3682
4321
  topology: geometry.topology,
3683
4322
  vertexCount,
3684
- modules: [import_shadertools.pbrMaterial],
4323
+ modules: [import_shadertools.pbrMaterial, import_shadertools.skin],
3685
4324
  ...modelOptions,
3686
4325
  defines: { ...parsedPPBRMaterial.defines, ...modelOptions.defines },
3687
4326
  parameters: { ...parameters, ...parsedPPBRMaterial.parameters, ...modelOptions.parameters }
3688
4327
  };
3689
- const model = new import_engine2.Model(device, modelProps);
3690
- const { camera, ...pbrMaterialProps } = {
4328
+ const material = options.material || createGLTFMaterial(device, {
4329
+ id: id ? `${id}-material` : void 0,
4330
+ parsedPPBRMaterial
4331
+ });
4332
+ modelProps.material = material;
4333
+ const model = new import_engine3.Model(device, modelProps);
4334
+ const sceneShaderInputValues = {
3691
4335
  ...parsedPPBRMaterial.uniforms,
3692
4336
  ...modelOptions.uniforms,
3693
4337
  ...parsedPPBRMaterial.bindings,
3694
4338
  ...modelOptions.bindings
3695
4339
  };
3696
- model.shaderInputs.setProps({ pbrMaterial: pbrMaterialProps, pbrProjection: { camera } });
3697
- return new import_engine2.ModelNode({ managedResources, model });
4340
+ const sceneShaderInputProps = getSceneShaderInputProps(
4341
+ model.shaderInputs.getModules(),
4342
+ material,
4343
+ sceneShaderInputValues
4344
+ );
4345
+ model.shaderInputs.setProps(sceneShaderInputProps);
4346
+ return new import_engine3.ModelNode({ managedResources, model });
4347
+ }
4348
+ function isMaterialBindingResource(value) {
4349
+ return value instanceof import_core3.Buffer || value instanceof import_engine2.DynamicTexture || value instanceof import_core3.Sampler || value instanceof import_core3.Texture || value instanceof import_core3.TextureView;
4350
+ }
4351
+ function getSceneShaderInputProps(modules, material, shaderInputValues) {
4352
+ const propertyToModuleNameMap = /* @__PURE__ */ new Map();
4353
+ for (const module of modules) {
4354
+ for (const uniformName of Object.keys(module.uniformTypes || {})) {
4355
+ propertyToModuleNameMap.set(uniformName, module.name);
4356
+ }
4357
+ for (const binding of module.bindingLayout || []) {
4358
+ propertyToModuleNameMap.set(binding.name, module.name);
4359
+ }
4360
+ }
4361
+ const sceneShaderInputProps = {};
4362
+ for (const [propertyName, value] of Object.entries(shaderInputValues)) {
4363
+ if (value === void 0) {
4364
+ continue;
4365
+ }
4366
+ const moduleName = propertyToModuleNameMap.get(propertyName);
4367
+ if (!moduleName || material.ownsModule(moduleName)) {
4368
+ continue;
4369
+ }
4370
+ sceneShaderInputProps[moduleName] ||= {};
4371
+ sceneShaderInputProps[moduleName][propertyName] = value;
4372
+ }
4373
+ return sceneShaderInputProps;
3698
4374
  }
3699
4375
 
3700
4376
  // src/parsers/parse-gltf.ts
@@ -3705,82 +4381,128 @@ layout(0) positions: vec4; // in vec4 POSITION;
3705
4381
  lights: true,
3706
4382
  useTangents: false
3707
4383
  };
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)
4384
+ function parseGLTF(device, gltf, options = {}) {
4385
+ const combinedOptions = { ...defaultOptions, ...options };
4386
+ const materialFactory = new import_engine4.MaterialFactory(device, { modules: [import_shadertools2.pbrMaterial] });
4387
+ const materials = (gltf.materials || []).map(
4388
+ (gltfMaterial, materialIndex) => createGLTFMaterial(device, {
4389
+ id: getGLTFMaterialId(gltfMaterial, materialIndex),
4390
+ parsedPPBRMaterial: parsePBRMaterial(
4391
+ device,
4392
+ gltfMaterial,
4393
+ {},
4394
+ {
4395
+ ...combinedOptions,
4396
+ gltf,
4397
+ validateAttributes: false
4398
+ }
4399
+ ),
4400
+ materialFactory
4401
+ })
3712
4402
  );
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
4403
+ const gltfMaterialIdToMaterialMap = /* @__PURE__ */ new Map();
4404
+ (gltf.materials || []).forEach((gltfMaterial, materialIndex) => {
4405
+ gltfMaterialIdToMaterialMap.set(gltfMaterial.id, materials[materialIndex]);
3721
4406
  });
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));
4407
+ const gltfMeshIdToNodeMap = /* @__PURE__ */ new Map();
4408
+ gltf.meshes.forEach((gltfMesh, idx) => {
4409
+ const newMesh = createNodeForGLTFMesh(
4410
+ device,
4411
+ gltfMesh,
4412
+ gltf,
4413
+ gltfMaterialIdToMaterialMap,
4414
+ combinedOptions
4415
+ );
4416
+ gltfMeshIdToNodeMap.set(gltfMesh.id, newMesh);
4417
+ });
4418
+ const gltfNodeIndexToNodeMap = /* @__PURE__ */ new Map();
4419
+ const gltfNodeIdToNodeMap = /* @__PURE__ */ new Map();
4420
+ gltf.nodes.forEach((gltfNode, idx) => {
4421
+ const newNode = createNodeForGLTFNode(device, gltfNode, combinedOptions);
4422
+ gltfNodeIndexToNodeMap.set(idx, newNode);
4423
+ gltfNodeIdToNodeMap.set(gltfNode.id, newNode);
4424
+ });
4425
+ gltf.nodes.forEach((gltfNode, idx) => {
4426
+ gltfNodeIndexToNodeMap.get(idx).add(
4427
+ (gltfNode.children ?? []).map(({ id }) => {
4428
+ const child = gltfNodeIdToNodeMap.get(id);
4429
+ if (!child)
4430
+ throw new Error(`Cannot find child ${id} of node ${idx}`);
4431
+ return child;
4432
+ })
4433
+ );
3728
4434
  if (gltfNode.mesh) {
3729
- children.push(createMesh(device, gltfNode.mesh, options));
3730
- }
3731
- const node = new import_engine3.GroupNode({
3732
- id: gltfNode.name || gltfNode.id,
3733
- children
3734
- });
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);
4435
+ const mesh = gltfMeshIdToNodeMap.get(gltfNode.mesh.id);
4436
+ if (!mesh) {
4437
+ throw new Error(`Cannot find mesh child ${gltfNode.mesh.id} of node ${idx}`);
3748
4438
  }
4439
+ gltfNodeIndexToNodeMap.get(idx).add(mesh);
3749
4440
  }
3750
- gltfNode._node = node;
3751
- }
3752
- const topLevelNode = gltfNodes.find((node) => node.id === gltfNode.id);
3753
- topLevelNode._node = gltfNode._node;
3754
- return gltfNode._node;
3755
- }
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
4441
+ });
4442
+ const scenes = gltf.scenes.map((gltfScene) => {
4443
+ const children = (gltfScene.nodes || []).map(({ id }) => {
4444
+ const child = gltfNodeIdToNodeMap.get(id);
4445
+ if (!child)
4446
+ throw new Error(`Cannot find child ${id} of scene ${gltfScene.name || gltfScene.id}`);
4447
+ return child;
3765
4448
  });
3766
- gltfMesh._mesh = mesh;
3767
- }
3768
- return gltfMesh._mesh;
4449
+ return new import_engine4.GroupNode({
4450
+ id: gltfScene.name || gltfScene.id,
4451
+ children
4452
+ });
4453
+ });
4454
+ return { scenes, materials, gltfMeshIdToNodeMap, gltfNodeIdToNodeMap, gltfNodeIndexToNodeMap };
4455
+ }
4456
+ function createNodeForGLTFNode(device, gltfNode, options) {
4457
+ return new import_engine4.GroupNode({
4458
+ id: gltfNode.name || gltfNode.id,
4459
+ children: [],
4460
+ matrix: gltfNode.matrix,
4461
+ position: gltfNode.translation,
4462
+ rotation: gltfNode.rotation,
4463
+ scale: gltfNode.scale
4464
+ });
3769
4465
  }
3770
- function createPrimitive(device, gltfPrimitive, i, gltfMesh, options) {
3771
- const id = gltfPrimitive.name || `${gltfMesh.name || gltfMesh.id}-primitive-${i}`;
4466
+ function createNodeForGLTFMesh(device, gltfMesh, gltf, gltfMaterialIdToMaterialMap, options) {
4467
+ const gltfPrimitives = gltfMesh.primitives || [];
4468
+ const primitives = gltfPrimitives.map(
4469
+ (gltfPrimitive, i) => createNodeForGLTFPrimitive({
4470
+ device,
4471
+ gltfPrimitive,
4472
+ primitiveIndex: i,
4473
+ gltfMesh,
4474
+ gltf,
4475
+ gltfMaterialIdToMaterialMap,
4476
+ options
4477
+ })
4478
+ );
4479
+ const mesh = new import_engine4.GroupNode({
4480
+ id: gltfMesh.name || gltfMesh.id,
4481
+ children: primitives
4482
+ });
4483
+ return mesh;
4484
+ }
4485
+ function createNodeForGLTFPrimitive({
4486
+ device,
4487
+ gltfPrimitive,
4488
+ primitiveIndex,
4489
+ gltfMesh,
4490
+ gltf,
4491
+ gltfMaterialIdToMaterialMap,
4492
+ options
4493
+ }) {
4494
+ const id = gltfPrimitive.name || `${gltfMesh.name || gltfMesh.id}-primitive-${primitiveIndex}`;
3772
4495
  const topology = convertGLDrawModeToTopology(gltfPrimitive.mode || 4);
3773
4496
  const vertexCount = gltfPrimitive.indices ? gltfPrimitive.indices.count : getVertexCount(gltfPrimitive.attributes);
3774
4497
  const geometry = createGeometry(id, gltfPrimitive, topology);
3775
- const parsedPPBRMaterial = parsePBRMaterial(
3776
- device,
3777
- gltfPrimitive.material,
3778
- geometry.attributes,
3779
- options
3780
- );
4498
+ const parsedPPBRMaterial = parsePBRMaterial(device, gltfPrimitive.material, geometry.attributes, {
4499
+ ...options,
4500
+ gltf
4501
+ });
3781
4502
  const modelNode = createGLTFModel(device, {
3782
4503
  id,
3783
4504
  geometry: createGeometry(id, gltfPrimitive, topology),
4505
+ material: gltfPrimitive.material ? gltfMaterialIdToMaterialMap.get(gltfPrimitive.material.id) || null : null,
3784
4506
  parsedPPBRMaterial,
3785
4507
  modelOptions: options.modelOptions,
3786
4508
  vertexCount
@@ -3797,40 +4519,40 @@ layout(0) positions: vec4; // in vec4 POSITION;
3797
4519
  const { components, size, value } = attribute;
3798
4520
  attributes[attributeName] = { size: size ?? components, value };
3799
4521
  }
3800
- return new import_engine3.Geometry({
4522
+ return new import_engine4.Geometry({
3801
4523
  id,
3802
4524
  topology,
3803
4525
  indices: gltfPrimitive.indices.value,
3804
4526
  attributes
3805
4527
  });
3806
4528
  }
4529
+ function getGLTFMaterialId(gltfMaterial, materialIndex) {
4530
+ return gltfMaterial.name || gltfMaterial.id || `material-${materialIndex}`;
4531
+ }
3807
4532
 
3808
4533
  // src/gltf/gltf-animator.ts
3809
- var import_core7 = __toESM(require_core(), 1);
4534
+ var import_core6 = __toESM(require_core(), 1);
3810
4535
 
3811
4536
  // src/gltf/animations/interpolate.ts
3812
- var import_core5 = __toESM(require_core(), 1);
3813
- var scratchQuaternion = new Quaternion();
4537
+ var import_core4 = __toESM(require_core(), 1);
4538
+ function updateTargetPath(target, path, newValue) {
4539
+ switch (path) {
4540
+ case "translation":
4541
+ return target.setPosition(newValue).updateMatrix();
4542
+ case "rotation":
4543
+ return target.setRotation(newValue).updateMatrix();
4544
+ case "scale":
4545
+ return target.setScale(newValue).updateMatrix();
4546
+ default:
4547
+ import_core4.log.warn(`Bad animation path ${path}`)();
4548
+ return null;
4549
+ }
4550
+ }
3814
4551
  function interpolate(time, { input, interpolation, output }, target, path) {
3815
4552
  const maxTime = input[input.length - 1];
3816
4553
  const animationTime = time % maxTime;
3817
4554
  const nextIndex = input.findIndex((t) => t >= animationTime);
3818
4555
  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
4556
  const previousTime = input[previousIndex];
3835
4557
  const nextTime = input[nextIndex];
3836
4558
  switch (interpolation) {
@@ -3840,13 +4562,7 @@ layout(0) positions: vec4; // in vec4 POSITION;
3840
4562
  case "LINEAR":
3841
4563
  if (nextTime > previousTime) {
3842
4564
  const ratio = (animationTime - previousTime) / (nextTime - previousTime);
3843
- linearInterpolate(
3844
- target,
3845
- path,
3846
- output[previousIndex],
3847
- output[nextIndex],
3848
- ratio
3849
- );
4565
+ linearInterpolate(target, path, output[previousIndex], output[nextIndex], ratio);
3850
4566
  }
3851
4567
  break;
3852
4568
  case "CUBICSPLINE":
@@ -3861,23 +4577,19 @@ layout(0) positions: vec4; // in vec4 POSITION;
3861
4577
  }
3862
4578
  break;
3863
4579
  default:
3864
- import_core5.log.warn(`Interpolation ${interpolation} not supported`)();
4580
+ import_core4.log.warn(`Interpolation ${interpolation} not supported`)();
3865
4581
  break;
3866
4582
  }
3867
4583
  }
3868
4584
  function linearInterpolate(target, path, start, stop, ratio) {
3869
- if (!target[path]) {
3870
- throw new Error();
3871
- }
3872
4585
  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
- }
4586
+ updateTargetPath(target, path, new Quaternion().slerp({ start, target: stop, ratio }));
3877
4587
  } else {
4588
+ const newVal = [];
3878
4589
  for (let i = 0; i < start.length; i++) {
3879
- target[path][i] = ratio * stop[i] + (1 - ratio) * start[i];
4590
+ newVal[i] = ratio * stop[i] + (1 - ratio) * start[i];
3880
4591
  }
4592
+ updateTargetPath(target, path, newVal);
3881
4593
  }
3882
4594
  }
3883
4595
  function cubicsplineInterpolate(target, path, {
@@ -3888,83 +4600,83 @@ layout(0) positions: vec4; // in vec4 POSITION;
3888
4600
  tDiff,
3889
4601
  ratio: t
3890
4602
  }) {
3891
- if (!target[path]) {
3892
- throw new Error();
3893
- }
3894
- for (let i = 0; i < target[path].length; i++) {
4603
+ const newVal = [];
4604
+ for (let i = 0; i < p0.length; i++) {
3895
4605
  const m0 = outTangent0[i] * tDiff;
3896
4606
  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;
4607
+ 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
4608
  }
4609
+ updateTargetPath(target, path, newVal);
3899
4610
  }
3900
4611
  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
- }
4612
+ updateTargetPath(target, path, value);
3907
4613
  }
3908
4614
 
3909
4615
  // src/gltf/gltf-animator.ts
3910
4616
  var GLTFSingleAnimator = class {
4617
+ /** Animation definition being played. */
3911
4618
  animation;
4619
+ /** Target scenegraph lookup table. */
4620
+ gltfNodeIdToNodeMap;
4621
+ /** Playback start time in seconds. */
3912
4622
  startTime = 0;
4623
+ /** Whether playback is currently enabled. */
3913
4624
  playing = true;
4625
+ /** Playback speed multiplier. */
3914
4626
  speed = 1;
4627
+ /** Creates a single-animation controller. */
3915
4628
  constructor(props) {
3916
4629
  this.animation = props.animation;
4630
+ this.gltfNodeIdToNodeMap = props.gltfNodeIdToNodeMap;
3917
4631
  this.animation.name ||= "unnamed";
3918
4632
  Object.assign(this, props);
3919
4633
  }
4634
+ /** Advances the animation to the supplied wall-clock time in milliseconds. */
3920
4635
  setTime(timeMs) {
3921
4636
  if (!this.playing) {
3922
4637
  return;
3923
4638
  }
3924
4639
  const absTime = timeMs / 1e3;
3925
4640
  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);
4641
+ this.animation.channels.forEach(({ sampler, targetNodeId, path }) => {
4642
+ const targetNode = this.gltfNodeIdToNodeMap.get(targetNodeId);
4643
+ if (!targetNode) {
4644
+ throw new Error(`Cannot find animation target node ${targetNodeId}`);
4645
+ }
4646
+ interpolate(time, sampler, targetNode, path);
3929
4647
  });
3930
4648
  }
3931
4649
  };
3932
4650
  var GLTFAnimator = class {
4651
+ /** Individual animation controllers. */
3933
4652
  animations;
4653
+ /** Creates an animator for the supplied glTF scenegraph. */
3934
4654
  constructor(props) {
3935
4655
  this.animations = props.animations.map((animation, index) => {
3936
4656
  const name = animation.name || `Animation-${index}`;
3937
4657
  return new GLTFSingleAnimator({
4658
+ gltfNodeIdToNodeMap: props.gltfNodeIdToNodeMap,
3938
4659
  animation: { name, channels: animation.channels }
3939
4660
  });
3940
4661
  });
3941
4662
  }
3942
4663
  /** @deprecated Use .setTime(). Will be removed (deck.gl is using this) */
3943
4664
  animate(time) {
3944
- import_core7.log.warn("GLTFAnimator#animate is deprecated. Use GLTFAnimator#setTime instead")();
4665
+ import_core6.log.warn("GLTFAnimator#animate is deprecated. Use GLTFAnimator#setTime instead")();
3945
4666
  this.setTime(time);
3946
4667
  }
4668
+ /** Advances every animation to the supplied wall-clock time in milliseconds. */
3947
4669
  setTime(time) {
3948
4670
  this.animations.forEach((animation) => animation.setTime(time));
3949
4671
  }
4672
+ /** Returns the per-animation controllers managed by this animator. */
3950
4673
  getAnimations() {
3951
4674
  return this.animations;
3952
4675
  }
3953
4676
  };
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
- }
4677
+
4678
+ // src/parsers/parse-gltf-animations.ts
4679
+ var import_core7 = __toESM(require_core(), 1);
3968
4680
 
3969
4681
  // src/webgl-to-webgpu/convert-webgl-attribute.ts
3970
4682
  var ATTRIBUTE_TYPE_TO_COMPONENTS = {
@@ -3996,65 +4708,331 @@ layout(0) positions: vec4; // in vec4 POSITION;
3996
4708
  // src/parsers/parse-gltf-animations.ts
3997
4709
  function parseGLTFAnimations(gltf) {
3998
4710
  const gltfAnimations = gltf.animations || [];
3999
- return gltfAnimations.map((animation, index) => {
4711
+ const accessorCache1D = /* @__PURE__ */ new Map();
4712
+ const accessorCache2D = /* @__PURE__ */ new Map();
4713
+ return gltfAnimations.flatMap((animation, index) => {
4000
4714
  const name = animation.name || `Animation-${index}`;
4001
- const samplers = animation.samplers.map(
4002
- ({ input, interpolation = "LINEAR", output }) => ({
4003
- input: accessorToJsArray(gltf.accessors[input]),
4004
- interpolation,
4005
- output: accessorToJsArray(gltf.accessors[output])
4006
- })
4007
- );
4008
- const channels = animation.channels.map(({ sampler, target }) => ({
4009
- sampler: samplers[sampler],
4010
- target: gltf.nodes[target.node ?? 0],
4011
- path: target.path
4012
- }));
4013
- return { name, channels };
4715
+ const samplerCache = /* @__PURE__ */ new Map();
4716
+ const channels = animation.channels.flatMap(({ sampler, target }) => {
4717
+ const path = getSupportedAnimationPath(target.path);
4718
+ if (!path) {
4719
+ return [];
4720
+ }
4721
+ const targetNode = gltf.nodes[target.node ?? 0];
4722
+ if (!targetNode) {
4723
+ throw new Error(`Cannot find animation target ${target.node}`);
4724
+ }
4725
+ let parsedSampler = samplerCache.get(sampler);
4726
+ if (!parsedSampler) {
4727
+ const gltfSampler = animation.samplers[sampler];
4728
+ if (!gltfSampler) {
4729
+ throw new Error(`Cannot find animation sampler ${sampler}`);
4730
+ }
4731
+ const { input, interpolation = "LINEAR", output } = gltfSampler;
4732
+ parsedSampler = {
4733
+ input: accessorToJsArray1D(gltf.accessors[input], accessorCache1D),
4734
+ interpolation,
4735
+ output: accessorToJsArray2D(gltf.accessors[output], accessorCache2D)
4736
+ };
4737
+ samplerCache.set(sampler, parsedSampler);
4738
+ }
4739
+ return {
4740
+ sampler: parsedSampler,
4741
+ targetNodeId: targetNode.id,
4742
+ path
4743
+ };
4744
+ });
4745
+ return channels.length ? [{ name, channels }] : [];
4014
4746
  });
4015
4747
  }
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
- }
4748
+ function getSupportedAnimationPath(path) {
4749
+ if (path === "pointer") {
4750
+ import_core7.log.warn("KHR_animation_pointer channels are not supported and will be skipped")();
4751
+ return null;
4028
4752
  }
4029
- return accessor._animation;
4753
+ return path;
4030
4754
  }
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;
4755
+ function accessorToJsArray1D(accessor, accessorCache) {
4756
+ if (accessorCache.has(accessor)) {
4757
+ return accessorCache.get(accessor);
4036
4758
  }
4037
- if (Array.isArray(object)) {
4038
- return object.map(deepCopy);
4759
+ const { typedArray: array, components } = accessorToTypedArray(accessor);
4760
+ assert3(components === 1, "accessorToJsArray1D must have exactly 1 component");
4761
+ const result = Array.from(array);
4762
+ accessorCache.set(accessor, result);
4763
+ return result;
4764
+ }
4765
+ function accessorToJsArray2D(accessor, accessorCache) {
4766
+ if (accessorCache.has(accessor)) {
4767
+ return accessorCache.get(accessor);
4768
+ }
4769
+ const { typedArray: array, components } = accessorToTypedArray(accessor);
4770
+ assert3(components >= 1, "accessorToJsArray2D must have at least 1 component");
4771
+ const result = [];
4772
+ for (let i = 0; i < array.length; i += components) {
4773
+ result.push(Array.from(array.slice(i, i + components)));
4774
+ }
4775
+ accessorCache.set(accessor, result);
4776
+ return result;
4777
+ }
4778
+ function assert3(condition, message) {
4779
+ if (!condition) {
4780
+ throw new Error(message);
4039
4781
  }
4040
- if (object && typeof object === "object") {
4041
- const result = {};
4042
- for (const key in object) {
4043
- result[key] = deepCopy(object[key]);
4782
+ }
4783
+
4784
+ // src/gltf/gltf-extension-support.ts
4785
+ var UNKNOWN_EXTENSION_SUPPORT = {
4786
+ supportLevel: "none",
4787
+ comment: "Not currently listed in the luma.gl glTF extension support registry."
4788
+ };
4789
+ var GLTF_EXTENSION_SUPPORT_REGISTRY = {
4790
+ KHR_draco_mesh_compression: {
4791
+ supportLevel: "built-in",
4792
+ comment: "Decoded by loaders.gl before luma.gl builds the scenegraph."
4793
+ },
4794
+ EXT_meshopt_compression: {
4795
+ supportLevel: "built-in",
4796
+ comment: "Meshopt-compressed primitives are decoded during load."
4797
+ },
4798
+ KHR_mesh_quantization: {
4799
+ supportLevel: "built-in",
4800
+ comment: "Quantized accessors are unpacked before geometry creation."
4801
+ },
4802
+ KHR_lights_punctual: {
4803
+ supportLevel: "built-in",
4804
+ comment: "Parsed into luma.gl Light objects."
4805
+ },
4806
+ KHR_materials_unlit: {
4807
+ supportLevel: "built-in",
4808
+ comment: "Unlit materials bypass the default lighting path."
4809
+ },
4810
+ KHR_materials_emissive_strength: {
4811
+ supportLevel: "built-in",
4812
+ comment: "Applied by the stock PBR shader."
4813
+ },
4814
+ KHR_texture_basisu: {
4815
+ supportLevel: "built-in",
4816
+ comment: "BasisU / KTX2 textures pass through when the device supports them."
4817
+ },
4818
+ KHR_texture_transform: {
4819
+ supportLevel: "built-in",
4820
+ comment: "UV transforms are applied during load."
4821
+ },
4822
+ EXT_texture_webp: {
4823
+ supportLevel: "loader-only",
4824
+ comment: "Texture source is resolved during load; final support depends on browser and device decode support."
4825
+ },
4826
+ EXT_texture_avif: {
4827
+ supportLevel: "loader-only",
4828
+ comment: "Texture source is resolved during load; final support depends on browser and device decode support."
4829
+ },
4830
+ KHR_materials_specular: {
4831
+ supportLevel: "built-in",
4832
+ comment: "The stock shader now applies specular factors and textures to the dielectric F0 term."
4833
+ },
4834
+ KHR_materials_ior: {
4835
+ supportLevel: "built-in",
4836
+ comment: "The stock shader now drives dielectric reflectance from the glTF IOR value."
4837
+ },
4838
+ KHR_materials_transmission: {
4839
+ supportLevel: "built-in",
4840
+ comment: "The stock shader now applies transmission to the base layer and exposes transparency through alpha, without a scene-color refraction buffer."
4841
+ },
4842
+ KHR_materials_volume: {
4843
+ supportLevel: "built-in",
4844
+ comment: "Thickness and attenuation now tint transmitted light in the stock shader."
4845
+ },
4846
+ KHR_materials_clearcoat: {
4847
+ supportLevel: "built-in",
4848
+ comment: "The stock shader now adds a secondary clearcoat specular lobe."
4849
+ },
4850
+ KHR_materials_sheen: {
4851
+ supportLevel: "built-in",
4852
+ comment: "The stock shader now adds a sheen lobe for cloth-like materials."
4853
+ },
4854
+ KHR_materials_iridescence: {
4855
+ supportLevel: "built-in",
4856
+ comment: "The stock shader now tints specular response with a view-dependent thin-film iridescence approximation."
4857
+ },
4858
+ KHR_materials_anisotropy: {
4859
+ supportLevel: "built-in",
4860
+ comment: "The stock shader now shapes highlights and IBL response with an anisotropy-direction approximation."
4861
+ },
4862
+ KHR_materials_pbrSpecularGlossiness: {
4863
+ supportLevel: "loader-only",
4864
+ comment: "Extension data can be loaded, but it is not translated into the default metallic-roughness material path."
4865
+ },
4866
+ KHR_materials_variants: {
4867
+ supportLevel: "loader-only",
4868
+ comment: "Variant metadata can be loaded, but applications must choose and apply variants."
4869
+ },
4870
+ EXT_mesh_gpu_instancing: {
4871
+ supportLevel: "none",
4872
+ comment: "GPU instancing data is not yet converted into luma.gl instanced draw setup."
4873
+ },
4874
+ KHR_node_visibility: {
4875
+ supportLevel: "none",
4876
+ comment: "Node-visibility animations and toggles are not mapped onto runtime scenegraph state."
4877
+ },
4878
+ KHR_animation_pointer: {
4879
+ supportLevel: "none",
4880
+ comment: "Animation pointers are not mapped onto runtime scenegraph updates."
4881
+ },
4882
+ KHR_materials_diffuse_transmission: {
4883
+ supportLevel: "none",
4884
+ comment: "Diffuse-transmission shading is not implemented in the stock PBR shader."
4885
+ },
4886
+ KHR_materials_dispersion: {
4887
+ supportLevel: "none",
4888
+ comment: "Chromatic dispersion is not implemented in the stock PBR shader."
4889
+ },
4890
+ KHR_materials_volume_scatter: {
4891
+ supportLevel: "none",
4892
+ comment: "Volume scattering is not implemented in the stock PBR shader."
4893
+ },
4894
+ KHR_xmp: {
4895
+ supportLevel: "none",
4896
+ comment: "Metadata payloads remain in the loaded glTF, but luma.gl does not interpret them."
4897
+ },
4898
+ KHR_xmp_json_ld: {
4899
+ supportLevel: "none",
4900
+ comment: "Metadata is preserved in the glTF, but luma.gl does not interpret it."
4901
+ },
4902
+ EXT_lights_image_based: {
4903
+ supportLevel: "none",
4904
+ comment: "Use loadPBREnvironment() or custom environment setup instead."
4905
+ },
4906
+ EXT_texture_video: {
4907
+ supportLevel: "none",
4908
+ comment: "Video textures are not created automatically by the stock pipeline."
4909
+ },
4910
+ MSFT_lod: {
4911
+ supportLevel: "none",
4912
+ comment: "Level-of-detail switching is not implemented in the stock scenegraph loader."
4913
+ }
4914
+ };
4915
+ function getGLTFExtensionSupport(gltf) {
4916
+ const extensionNames = Array.from(collectGLTFExtensionNames(gltf)).sort();
4917
+ const extensionSupportEntries = extensionNames.map(
4918
+ (extensionName) => {
4919
+ const extensionSupportDefinition = GLTF_EXTENSION_SUPPORT_REGISTRY[extensionName] || UNKNOWN_EXTENSION_SUPPORT;
4920
+ return [
4921
+ extensionName,
4922
+ {
4923
+ extensionName,
4924
+ supported: extensionSupportDefinition.supportLevel === "built-in",
4925
+ supportLevel: extensionSupportDefinition.supportLevel,
4926
+ comment: extensionSupportDefinition.comment
4927
+ }
4928
+ ];
4044
4929
  }
4045
- return result;
4930
+ );
4931
+ return new Map(extensionSupportEntries);
4932
+ }
4933
+ function collectGLTFExtensionNames(gltf) {
4934
+ const gltfWithRemovedExtensions = gltf;
4935
+ const extensionNames = /* @__PURE__ */ new Set();
4936
+ addExtensionNames(extensionNames, gltf.extensionsUsed);
4937
+ addExtensionNames(extensionNames, gltf.extensionsRequired);
4938
+ addExtensionNames(extensionNames, gltfWithRemovedExtensions.extensionsRemoved);
4939
+ addExtensionNames(extensionNames, Object.keys(gltf.extensions || {}));
4940
+ if (gltfWithRemovedExtensions.lights?.length || gltf.nodes.some((node) => "light" in node)) {
4941
+ extensionNames.add("KHR_lights_punctual");
4942
+ }
4943
+ if (gltf.materials.some((material) => {
4944
+ const gltfMaterial = material;
4945
+ return gltfMaterial.unlit || gltfMaterial.extensions?.KHR_materials_unlit;
4946
+ })) {
4947
+ extensionNames.add("KHR_materials_unlit");
4948
+ }
4949
+ return extensionNames;
4950
+ }
4951
+ function addExtensionNames(extensionNames, newExtensionNames = []) {
4952
+ for (const extensionName of newExtensionNames) {
4953
+ extensionNames.add(extensionName);
4046
4954
  }
4047
- return object;
4048
4955
  }
4049
4956
 
4050
4957
  // src/gltf/create-scenegraph-from-gltf.ts
4051
4958
  function createScenegraphsFromGLTF(device, gltf, options) {
4052
- gltf = deepCopy(gltf);
4053
- const scenes = parseGLTF(device, gltf, options);
4959
+ const { scenes, materials, gltfMeshIdToNodeMap, gltfNodeIdToNodeMap, gltfNodeIndexToNodeMap } = parseGLTF(device, gltf, options);
4054
4960
  const animations = parseGLTFAnimations(gltf);
4055
- const animator = new GLTFAnimator({ animations });
4961
+ const animator = new GLTFAnimator({ animations, gltfNodeIdToNodeMap });
4056
4962
  const lights = parseGLTFLights(gltf);
4057
- return { scenes, animator, lights };
4963
+ const extensionSupport = getGLTFExtensionSupport(gltf);
4964
+ const sceneBounds = scenes.map((scene) => getScenegraphBounds(scene.getBounds()));
4965
+ const modelBounds = getCombinedScenegraphBounds(sceneBounds);
4966
+ return {
4967
+ scenes,
4968
+ materials,
4969
+ animator,
4970
+ lights,
4971
+ extensionSupport,
4972
+ sceneBounds,
4973
+ modelBounds,
4974
+ gltfMeshIdToNodeMap,
4975
+ gltfNodeIdToNodeMap,
4976
+ gltfNodeIndexToNodeMap,
4977
+ gltf
4978
+ };
4979
+ }
4980
+ function getScenegraphBounds(bounds) {
4981
+ if (!bounds) {
4982
+ return {
4983
+ bounds: null,
4984
+ center: [0, 0, 0],
4985
+ size: [0, 0, 0],
4986
+ radius: 0.5,
4987
+ recommendedOrbitDistance: 1
4988
+ };
4989
+ }
4990
+ const normalizedBounds = [
4991
+ [bounds[0][0], bounds[0][1], bounds[0][2]],
4992
+ [bounds[1][0], bounds[1][1], bounds[1][2]]
4993
+ ];
4994
+ const size = [
4995
+ normalizedBounds[1][0] - normalizedBounds[0][0],
4996
+ normalizedBounds[1][1] - normalizedBounds[0][1],
4997
+ normalizedBounds[1][2] - normalizedBounds[0][2]
4998
+ ];
4999
+ const center = [
5000
+ normalizedBounds[0][0] + size[0] * 0.5,
5001
+ normalizedBounds[0][1] + size[1] * 0.5,
5002
+ normalizedBounds[0][2] + size[2] * 0.5
5003
+ ];
5004
+ const maxHalfExtent = Math.max(size[0], size[1], size[2]) * 0.5;
5005
+ const radius = Math.max(0.5 * Math.hypot(size[0], size[1], size[2]), 1e-3);
5006
+ return {
5007
+ bounds: normalizedBounds,
5008
+ center,
5009
+ size,
5010
+ radius,
5011
+ recommendedOrbitDistance: Math.max(
5012
+ Math.max(maxHalfExtent, 1e-3) / Math.tan(Math.PI / 6) * 1.15,
5013
+ radius * 1.1
5014
+ )
5015
+ };
5016
+ }
5017
+ function getCombinedScenegraphBounds(sceneBounds) {
5018
+ let combinedBounds = null;
5019
+ for (const sceneBoundInfo of sceneBounds) {
5020
+ if (!sceneBoundInfo.bounds) {
5021
+ continue;
5022
+ }
5023
+ if (!combinedBounds) {
5024
+ combinedBounds = [
5025
+ [...sceneBoundInfo.bounds[0]],
5026
+ [...sceneBoundInfo.bounds[1]]
5027
+ ];
5028
+ continue;
5029
+ }
5030
+ for (let axis = 0; axis < 3; axis++) {
5031
+ combinedBounds[0][axis] = Math.min(combinedBounds[0][axis], sceneBoundInfo.bounds[0][axis]);
5032
+ combinedBounds[1][axis] = Math.max(combinedBounds[1][axis], sceneBoundInfo.bounds[1][axis]);
5033
+ }
5034
+ }
5035
+ return getScenegraphBounds(combinedBounds);
4058
5036
  }
4059
5037
  return __toCommonJS(bundle_exports);
4060
5038
  })();