@luma.gl/gltf 9.2.6 → 9.3.0-alpha.10

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 (91) hide show
  1. package/dist/dist.dev.js +1362 -313
  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 +39 -2
  14. package/dist/gltf/create-scenegraph-from-gltf.d.ts.map +1 -1
  15. package/dist/gltf/create-scenegraph-from-gltf.js +76 -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 +1302 -276
  26. package/dist/index.cjs.map +4 -4
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +2 -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 +5 -0
  36. package/dist/parsers/parse-gltf-lights.d.ts.map +1 -0
  37. package/dist/parsers/parse-gltf-lights.js +163 -0
  38. package/dist/parsers/parse-gltf-lights.js.map +1 -0
  39. package/dist/parsers/parse-gltf.d.ts +19 -2
  40. package/dist/parsers/parse-gltf.d.ts.map +1 -1
  41. package/dist/parsers/parse-gltf.js +101 -61
  42. package/dist/parsers/parse-gltf.js.map +1 -1
  43. package/dist/parsers/parse-pbr-material.d.ts +115 -2
  44. package/dist/parsers/parse-pbr-material.d.ts.map +1 -1
  45. package/dist/parsers/parse-pbr-material.js +570 -54
  46. package/dist/parsers/parse-pbr-material.js.map +1 -1
  47. package/dist/pbr/pbr-environment.d.ts +10 -4
  48. package/dist/pbr/pbr-environment.d.ts.map +1 -1
  49. package/dist/pbr/pbr-environment.js +18 -15
  50. package/dist/pbr/pbr-environment.js.map +1 -1
  51. package/dist/pbr/pbr-material.d.ts +13 -3
  52. package/dist/pbr/pbr-material.d.ts.map +1 -1
  53. package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts +12 -1
  54. package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts.map +1 -1
  55. package/dist/webgl-to-webgpu/convert-webgl-attribute.js +3 -0
  56. package/dist/webgl-to-webgpu/convert-webgl-attribute.js.map +1 -1
  57. package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts +11 -5
  58. package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts.map +1 -1
  59. package/dist/webgl-to-webgpu/convert-webgl-sampler.js +16 -12
  60. package/dist/webgl-to-webgpu/convert-webgl-sampler.js.map +1 -1
  61. package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts +2 -9
  62. package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts.map +1 -1
  63. package/dist/webgl-to-webgpu/convert-webgl-topology.js +2 -14
  64. package/dist/webgl-to-webgpu/convert-webgl-topology.js.map +1 -1
  65. package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts +27 -0
  66. package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts.map +1 -0
  67. package/dist/webgl-to-webgpu/gltf-webgl-constants.js +34 -0
  68. package/dist/webgl-to-webgpu/gltf-webgl-constants.js.map +1 -0
  69. package/package.json +8 -9
  70. package/src/gltf/animations/animations.ts +17 -5
  71. package/src/gltf/animations/interpolate.ts +49 -68
  72. package/src/gltf/create-gltf-model.ts +214 -48
  73. package/src/gltf/create-scenegraph-from-gltf.ts +134 -11
  74. package/src/gltf/gltf-animator.ts +34 -25
  75. package/src/gltf/gltf-extension-support.ts +214 -0
  76. package/src/index.ts +11 -2
  77. package/src/parsers/parse-gltf-animations.ts +94 -33
  78. package/src/parsers/parse-gltf-lights.ts +218 -0
  79. package/src/parsers/parse-gltf.ts +170 -90
  80. package/src/parsers/parse-pbr-material.ts +870 -80
  81. package/src/pbr/pbr-environment.ts +44 -21
  82. package/src/pbr/pbr-material.ts +18 -3
  83. package/src/webgl-to-webgpu/convert-webgl-attribute.ts +12 -1
  84. package/src/webgl-to-webgpu/convert-webgl-sampler.ts +38 -29
  85. package/src/webgl-to-webgpu/convert-webgl-topology.ts +2 -14
  86. package/src/webgl-to-webgpu/gltf-webgl-constants.ts +35 -0
  87. package/dist/utils/deep-copy.d.ts +0 -3
  88. package/dist/utils/deep-copy.d.ts.map +0 -1
  89. package/dist/utils/deep-copy.js +0 -21
  90. package/dist/utils/deep-copy.js.map +0 -1
  91. 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,7 +64,9 @@ var __exports__ = (() => {
71
64
  __export(bundle_exports, {
72
65
  GLTFAnimator: () => GLTFAnimator,
73
66
  createScenegraphsFromGLTF: () => createScenegraphsFromGLTF,
67
+ getGLTFExtensionSupport: () => getGLTFExtensionSupport,
74
68
  loadPBREnvironment: () => loadPBREnvironment,
69
+ parseGLTFLights: () => parseGLTFLights,
75
70
  parsePBRMaterial: () => parsePBRMaterial
76
71
  });
77
72
  __reExport(bundle_exports, __toESM(require_core(), 1));
@@ -121,7 +116,7 @@ var __exports__ = (() => {
121
116
  }
122
117
 
123
118
  // ../../node_modules/@loaders.gl/images/dist/lib/utils/version.js
124
- var VERSION = true ? "4.3.2" : "latest";
119
+ var VERSION = true ? "4.4.0-alpha.18" : "latest";
125
120
 
126
121
  // ../../node_modules/@loaders.gl/images/dist/lib/category-api/image-type.js
127
122
  var parseImageNode = globalThis.loaders?.parseImageNode;
@@ -262,7 +257,6 @@ var __exports__ = (() => {
262
257
  }
263
258
 
264
259
  // ../../node_modules/@loaders.gl/images/dist/lib/parsers/parse-to-image-bitmap.js
265
- var EMPTY_OBJECT = {};
266
260
  var imagebitmapOptionsSupported = true;
267
261
  async function parseToImageBitmap(arrayBuffer, options, url) {
268
262
  let blob;
@@ -290,8 +284,13 @@ var __exports__ = (() => {
290
284
  return await createImageBitmap(blob);
291
285
  }
292
286
  function isEmptyObject(object) {
293
- for (const key in object || EMPTY_OBJECT) {
294
- 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
+ }
295
294
  }
296
295
  return true;
297
296
  }
@@ -615,7 +614,8 @@ var __exports__ = (() => {
615
614
 
616
615
  // src/pbr/pbr-environment.ts
617
616
  function loadPBREnvironment(device, props) {
618
- const brdfLutTexture = new import_engine.AsyncTexture(device, {
617
+ const specularMipLevels = props.specularMipLevels ?? 1;
618
+ const brdfLutTexture = new import_engine.DynamicTexture(device, {
619
619
  id: "brdfLUT",
620
620
  sampler: {
621
621
  addressModeU: "clamp-to-edge",
@@ -628,7 +628,9 @@ var __exports__ = (() => {
628
628
  });
629
629
  const diffuseEnvSampler = makeCube(device, {
630
630
  id: "DiffuseEnvSampler",
631
- getTextureForFace: (dir) => loadImageTexture(props.getTexUrl("diffuse", dir, 0)),
631
+ getTextureForFace: (face) => loadImageTexture(
632
+ props.getTexUrl("diffuse", FACES.indexOf(face), 0)
633
+ ),
632
634
  sampler: {
633
635
  addressModeU: "clamp-to-edge",
634
636
  addressModeV: "clamp-to-edge",
@@ -638,12 +640,13 @@ var __exports__ = (() => {
638
640
  });
639
641
  const specularEnvSampler = makeCube(device, {
640
642
  id: "SpecularEnvSampler",
641
- getTextureForFace: (dir) => {
643
+ getTextureForFace: (face) => {
642
644
  const imageArray = [];
643
- for (let lod = 0; lod <= props.specularMipLevels - 1; lod++) {
644
- 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)));
645
648
  }
646
- return imageArray;
649
+ return Promise.all(imageArray);
647
650
  },
648
651
  sampler: {
649
652
  addressModeU: "clamp-to-edge",
@@ -659,32 +662,34 @@ var __exports__ = (() => {
659
662
  specularEnvSampler
660
663
  };
661
664
  }
662
- var FACES = [0, 1, 2, 3, 4, 5];
665
+ var FACES = ["+X", "-X", "+Y", "-Y", "+Z", "-Z"];
663
666
  function makeCube(device, {
664
667
  id,
665
668
  getTextureForFace,
666
669
  sampler
667
670
  }) {
668
- const data = {};
669
- FACES.forEach((face) => {
670
- 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;
671
679
  });
672
- return new import_engine.AsyncTexture(device, {
680
+ return new import_engine.DynamicTexture(device, {
673
681
  id,
674
682
  dimension: "cube",
675
683
  mipmaps: false,
676
684
  sampler,
677
- // @ts-expect-error
678
685
  data
679
686
  });
680
687
  }
681
688
 
682
689
  // src/parsers/parse-pbr-material.ts
683
- var import_constants2 = __toESM(require_constants(), 1);
684
690
  var import_core = __toESM(require_core(), 1);
685
691
 
686
692
  // src/webgl-to-webgpu/convert-webgl-sampler.ts
687
- var import_constants = __toESM(require_constants(), 1);
688
693
  function convertSampler(gltfSampler) {
689
694
  return {
690
695
  addressModeU: convertSamplerWrapMode(gltfSampler.wrapS),
@@ -695,11 +700,11 @@ var __exports__ = (() => {
695
700
  }
696
701
  function convertSamplerWrapMode(mode) {
697
702
  switch (mode) {
698
- case import_constants.GL.CLAMP_TO_EDGE:
703
+ case 33071 /* CLAMP_TO_EDGE */:
699
704
  return "clamp-to-edge";
700
- case import_constants.GL.REPEAT:
705
+ case 10497 /* REPEAT */:
701
706
  return "repeat";
702
- case import_constants.GL.MIRRORED_REPEAT:
707
+ case 33648 /* MIRRORED_REPEAT */:
703
708
  return "mirror-repeat";
704
709
  default:
705
710
  return void 0;
@@ -707,9 +712,9 @@ var __exports__ = (() => {
707
712
  }
708
713
  function convertSamplerMagFilter(mode) {
709
714
  switch (mode) {
710
- case import_constants.GL.NEAREST:
715
+ case 9728 /* NEAREST */:
711
716
  return "nearest";
712
- case import_constants.GL.LINEAR:
717
+ case 9729 /* LINEAR */:
713
718
  return "linear";
714
719
  default:
715
720
  return void 0;
@@ -717,17 +722,17 @@ var __exports__ = (() => {
717
722
  }
718
723
  function convertSamplerMinFilter(mode) {
719
724
  switch (mode) {
720
- case import_constants.GL.NEAREST:
725
+ case 9728 /* NEAREST */:
721
726
  return { minFilter: "nearest" };
722
- case import_constants.GL.LINEAR:
727
+ case 9729 /* LINEAR */:
723
728
  return { minFilter: "linear" };
724
- case import_constants.GL.NEAREST_MIPMAP_NEAREST:
729
+ case 9984 /* NEAREST_MIPMAP_NEAREST */:
725
730
  return { minFilter: "nearest", mipmapFilter: "nearest" };
726
- case import_constants.GL.LINEAR_MIPMAP_NEAREST:
731
+ case 9985 /* LINEAR_MIPMAP_NEAREST */:
727
732
  return { minFilter: "linear", mipmapFilter: "nearest" };
728
- case import_constants.GL.NEAREST_MIPMAP_LINEAR:
733
+ case 9986 /* NEAREST_MIPMAP_LINEAR */:
729
734
  return { minFilter: "nearest", mipmapFilter: "linear" };
730
- case import_constants.GL.LINEAR_MIPMAP_LINEAR:
735
+ case 9987 /* LINEAR_MIPMAP_LINEAR */:
731
736
  return { minFilter: "linear", mipmapFilter: "linear" };
732
737
  default:
733
738
  return {};
@@ -759,7 +764,8 @@ var __exports__ = (() => {
759
764
  if (imageBasedLightingEnvironment) {
760
765
  parsedMaterial.bindings.pbr_diffuseEnvSampler = imageBasedLightingEnvironment.diffuseEnvSampler.texture;
761
766
  parsedMaterial.bindings.pbr_specularEnvSampler = imageBasedLightingEnvironment.specularEnvSampler.texture;
762
- parsedMaterial.bindings.pbr_BrdfLUT = imageBasedLightingEnvironment.brdfLutTexture.texture;
767
+ parsedMaterial.bindings.pbr_brdfLUT = imageBasedLightingEnvironment.brdfLutTexture.texture;
768
+ parsedMaterial.uniforms.IBLenabled = true;
763
769
  parsedMaterial.uniforms.scaleIBLAmbient = [1, 1];
764
770
  }
765
771
  if (options?.pbrDebug) {
@@ -773,86 +779,190 @@ var __exports__ = (() => {
773
779
  parsedMaterial.defines["HAS_TANGENTS"] = true;
774
780
  if (attributes["TEXCOORD_0"])
775
781
  parsedMaterial.defines["HAS_UV"] = true;
782
+ if (attributes["JOINTS_0"] && attributes["WEIGHTS_0"])
783
+ parsedMaterial.defines["HAS_SKIN"] = true;
784
+ if (attributes["COLOR_0"])
785
+ parsedMaterial.defines["HAS_COLORS"] = true;
776
786
  if (options?.imageBasedLightingEnvironment)
777
787
  parsedMaterial.defines["USE_IBL"] = true;
778
788
  if (options?.lights)
779
789
  parsedMaterial.defines["USE_LIGHTS"] = true;
780
790
  if (material) {
781
- parseMaterial(device, material, parsedMaterial);
791
+ if (options.validateAttributes !== false) {
792
+ warnOnMissingExpectedAttributes(material, attributes);
793
+ }
794
+ parseMaterial(device, material, parsedMaterial, options.gltf);
782
795
  }
783
796
  return parsedMaterial;
784
797
  }
785
- function parseMaterial(device, material, parsedMaterial) {
786
- 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
+ );
787
864
  if (material.pbrMetallicRoughness) {
788
- parsePbrMetallicRoughness(device, material.pbrMetallicRoughness, parsedMaterial);
865
+ parsePbrMetallicRoughness(device, material.pbrMetallicRoughness, parsedMaterial, gltf);
789
866
  }
790
867
  if (material.normalTexture) {
791
- addTexture(
792
- device,
793
- material.normalTexture,
794
- "pbr_normalSampler",
795
- "HAS_NORMALMAP",
796
- parsedMaterial
797
- );
868
+ addTexture(device, material.normalTexture, "pbr_normalSampler", parsedMaterial, {
869
+ featureOptions: {
870
+ define: "HAS_NORMALMAP",
871
+ enabledUniformName: "normalMapEnabled"
872
+ },
873
+ gltf
874
+ });
798
875
  const { scale: scale4 = 1 } = material.normalTexture;
799
876
  parsedMaterial.uniforms.normalScale = scale4;
800
877
  }
801
878
  if (material.occlusionTexture) {
802
- addTexture(
803
- device,
804
- material.occlusionTexture,
805
- "pbr_occlusionSampler",
806
- "HAS_OCCLUSIONMAP",
807
- parsedMaterial
808
- );
879
+ addTexture(device, material.occlusionTexture, "pbr_occlusionSampler", parsedMaterial, {
880
+ featureOptions: {
881
+ define: "HAS_OCCLUSIONMAP",
882
+ enabledUniformName: "occlusionMapEnabled"
883
+ },
884
+ gltf
885
+ });
809
886
  const { strength = 1 } = material.occlusionTexture;
810
887
  parsedMaterial.uniforms.occlusionStrength = strength;
811
888
  }
889
+ parsedMaterial.uniforms.emissiveFactor = material.emissiveFactor || [0, 0, 0];
812
890
  if (material.emissiveTexture) {
813
- addTexture(
814
- device,
815
- material.emissiveTexture,
816
- "pbr_emissiveSampler",
817
- "HAS_EMISSIVEMAP",
818
- parsedMaterial
819
- );
820
- 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
+ });
821
898
  }
822
- switch (material.alphaMode || "MASK") {
823
- case "MASK":
899
+ parseMaterialExtensions(device, material.extensions, parsedMaterial, gltf);
900
+ switch (material.alphaMode || "OPAQUE") {
901
+ case "OPAQUE":
902
+ break;
903
+ case "MASK": {
824
904
  const { alphaCutoff = 0.5 } = material;
825
905
  parsedMaterial.defines["ALPHA_CUTOFF"] = true;
906
+ parsedMaterial.uniforms.alphaCutoffEnabled = true;
826
907
  parsedMaterial.uniforms.alphaCutoff = alphaCutoff;
827
908
  break;
909
+ }
828
910
  case "BLEND":
829
911
  import_core.log.warn("glTF BLEND alphaMode might not work well because it requires mesh sorting")();
830
- parsedMaterial.parameters.blend = true;
831
- parsedMaterial.parameters.blendColorOperation = "add";
832
- parsedMaterial.parameters.blendColorSrcFactor = "src-alpha";
833
- parsedMaterial.parameters.blendColorDstFactor = "one-minus-src-alpha";
834
- parsedMaterial.parameters.blendAlphaOperation = "add";
835
- parsedMaterial.parameters.blendAlphaSrcFactor = "one";
836
- parsedMaterial.parameters.blendAlphaDstFactor = "one-minus-src-alpha";
837
- parsedMaterial.glParameters["blend"] = true;
838
- parsedMaterial.glParameters["blendEquation"] = import_constants2.GL.FUNC_ADD;
839
- parsedMaterial.glParameters["blendFunc"] = [
840
- import_constants2.GL.SRC_ALPHA,
841
- import_constants2.GL.ONE_MINUS_SRC_ALPHA,
842
- import_constants2.GL.ONE,
843
- import_constants2.GL.ONE_MINUS_SRC_ALPHA
844
- ];
912
+ applyAlphaBlendParameters(parsedMaterial);
845
913
  break;
846
914
  }
847
915
  }
848
- 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) {
849
953
  if (pbrMetallicRoughness.baseColorTexture) {
850
954
  addTexture(
851
955
  device,
852
956
  pbrMetallicRoughness.baseColorTexture,
853
957
  "pbr_baseColorSampler",
854
- "HAS_BASECOLORMAP",
855
- parsedMaterial
958
+ parsedMaterial,
959
+ {
960
+ featureOptions: {
961
+ define: "HAS_BASECOLORMAP",
962
+ enabledUniformName: "baseColorMapEnabled"
963
+ },
964
+ gltf
965
+ }
856
966
  );
857
967
  }
858
968
  parsedMaterial.uniforms.baseColorFactor = pbrMetallicRoughness.baseColorFactor || [1, 1, 1, 1];
@@ -861,41 +971,431 @@ var __exports__ = (() => {
861
971
  device,
862
972
  pbrMetallicRoughness.metallicRoughnessTexture,
863
973
  "pbr_metallicRoughnessSampler",
864
- "HAS_METALROUGHNESSMAP",
865
- parsedMaterial
974
+ parsedMaterial,
975
+ {
976
+ featureOptions: {
977
+ define: "HAS_METALROUGHNESSMAP",
978
+ enabledUniformName: "metallicRoughnessMapEnabled"
979
+ },
980
+ gltf
981
+ }
866
982
  );
867
983
  }
868
984
  const { metallicFactor = 1, roughnessFactor = 1 } = pbrMetallicRoughness;
869
985
  parsedMaterial.uniforms.metallicRoughnessValues = [metallicFactor, roughnessFactor];
870
986
  }
871
- function addTexture(device, gltfTexture, uniformName, define, parsedMaterial) {
872
- const image = gltfTexture.texture.source.image;
873
- let textureOptions;
874
- if (image.compressed) {
875
- textureOptions = image;
876
- } else {
877
- 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;
878
1245
  }
879
1246
  const gltfSampler = {
880
1247
  wrapS: 10497,
881
1248
  // default REPEAT S (U) wrapping mode.
882
1249
  wrapT: 10497,
883
1250
  // default REPEAT T (V) wrapping mode.
884
- ...gltfTexture?.texture?.sampler
1251
+ minFilter: 9729,
1252
+ // default LINEAR filtering
1253
+ magFilter: 9729,
1254
+ // default LINEAR filtering
1255
+ ...resolvedTextureInfo?.texture?.sampler
885
1256
  };
886
- const texture = device.createTexture({
887
- id: gltfTexture.uniformName || gltfTexture.id,
888
- sampler: convertSampler(gltfSampler),
889
- ...textureOptions
890
- });
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
+ }
891
1273
  parsedMaterial.bindings[uniformName] = texture;
892
1274
  if (define)
893
1275
  parsedMaterial.defines[define] = true;
1276
+ if (enabledUniformName) {
1277
+ parsedMaterial.uniforms[enabledUniformName] = true;
1278
+ }
894
1279
  parsedMaterial.generatedTextures.push(texture);
895
1280
  }
896
-
897
- // src/parsers/parse-gltf.ts
898
- var import_engine3 = __toESM(require_engine(), 1);
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
+ }
899
1399
 
900
1400
  // ../../node_modules/@math.gl/core/dist/lib/common.js
901
1401
  var RADIANS_TO_DEGREES = 1 / Math.PI * 180;
@@ -3468,6 +3968,143 @@ var __exports__ = (() => {
3468
3968
  }
3469
3969
  };
3470
3970
 
3971
+ // src/parsers/parse-gltf-lights.ts
3972
+ var GLTF_COLOR_FACTOR = 255;
3973
+ function parseGLTFLights(gltf) {
3974
+ const lightDefs = (
3975
+ // `postProcessGLTF()` moves KHR_lights_punctual into `gltf.lights`.
3976
+ gltf.lights || gltf.extensions?.["KHR_lights_punctual"]?.["lights"]
3977
+ );
3978
+ if (!lightDefs || !Array.isArray(lightDefs) || lightDefs.length === 0) {
3979
+ return [];
3980
+ }
3981
+ const lights = [];
3982
+ const parentNodeById = createParentNodeMap(gltf.nodes || []);
3983
+ const worldMatrixByNodeId = /* @__PURE__ */ new Map();
3984
+ for (const node of gltf.nodes || []) {
3985
+ const lightIndex = node.light ?? node.extensions?.KHR_lights_punctual?.light;
3986
+ if (typeof lightIndex !== "number") {
3987
+ continue;
3988
+ }
3989
+ const gltfLight = lightDefs[lightIndex];
3990
+ if (!gltfLight) {
3991
+ continue;
3992
+ }
3993
+ const color = normalizeGLTFLightColor(
3994
+ gltfLight.color || [1, 1, 1]
3995
+ );
3996
+ const intensity = gltfLight.intensity ?? 1;
3997
+ const range = gltfLight.range;
3998
+ const worldMatrix = getNodeWorldMatrix(node, parentNodeById, worldMatrixByNodeId);
3999
+ switch (gltfLight.type) {
4000
+ case "directional":
4001
+ lights.push(parseDirectionalLight(worldMatrix, color, intensity));
4002
+ break;
4003
+ case "point":
4004
+ lights.push(parsePointLight(worldMatrix, color, intensity, range));
4005
+ break;
4006
+ case "spot":
4007
+ lights.push(parseSpotLight(worldMatrix, color, intensity, range, gltfLight.spot));
4008
+ break;
4009
+ default:
4010
+ break;
4011
+ }
4012
+ }
4013
+ return lights;
4014
+ }
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);
4020
+ let attenuation = [1, 0, 0];
4021
+ if (range !== void 0 && range > 0) {
4022
+ attenuation = [1, 0, 1 / (range * range)];
4023
+ }
4024
+ return {
4025
+ type: "point",
4026
+ position,
4027
+ color,
4028
+ intensity,
4029
+ attenuation
4030
+ };
4031
+ }
4032
+ function parseDirectionalLight(worldMatrix, color, intensity) {
4033
+ const direction = getLightDirection(worldMatrix);
4034
+ return {
4035
+ type: "directional",
4036
+ direction,
4037
+ color,
4038
+ intensity
4039
+ };
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
+ }
4103
+
4104
+ // src/parsers/parse-gltf.ts
4105
+ var import_engine4 = __toESM(require_engine(), 1);
4106
+ var import_shadertools2 = __toESM(require_shadertools(), 1);
4107
+
3471
4108
  // src/webgl-to-webgpu/convert-webgl-topology.ts
3472
4109
  function convertGLDrawModeToTopology(drawMode) {
3473
4110
  switch (drawMode) {
@@ -3487,56 +4124,95 @@ var __exports__ = (() => {
3487
4124
  }
3488
4125
 
3489
4126
  // src/gltf/create-gltf-model.ts
3490
- var import_core2 = __toESM(require_core(), 1);
3491
- var import_shadertools = __toESM(require_shadertools(), 1);
4127
+ var import_core3 = __toESM(require_core(), 1);
3492
4128
  var import_engine2 = __toESM(require_engine(), 1);
4129
+ var import_shadertools = __toESM(require_shadertools(), 1);
4130
+ var import_engine3 = __toESM(require_engine(), 1);
3493
4131
  var SHADER = (
3494
4132
  /* WGSL */
3495
4133
  `
3496
- layout(0) positions: vec4; // in vec4 POSITION;
3497
-
3498
- #ifdef HAS_NORMALS
3499
- in vec4 normals; // in vec4 NORMAL;
3500
- #endif
3501
-
3502
- #ifdef HAS_TANGENTS
3503
- in vec4 TANGENT;
3504
- #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
+ };
3505
4150
 
3506
- #ifdef HAS_UV
3507
- // in vec2 TEXCOORD_0;
3508
- in vec2 texCoords;
3509
- #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
+ };
3510
4160
 
3511
4161
  @vertex
3512
- void main(void) {
3513
- vec4 _NORMAL = vec4(0.);
3514
- vec4 _TANGENT = vec4(0.);
3515
- 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);
3516
4168
 
3517
- #ifdef HAS_NORMALS
3518
- _NORMAL = normals;
3519
- #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
3520
4186
 
3521
- #ifdef HAS_TANGENTS
3522
- _TANGENT = TANGENT;
3523
- #endif
4187
+ let worldPosition = pbrProjection.modelMatrix * position;
3524
4188
 
3525
- #ifdef HAS_UV
3526
- _TEXCOORD_0 = texCoords;
3527
- #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
3528
4196
 
3529
- pbr_setPositionNormalTangentUV(positions, _NORMAL, _TANGENT, _TEXCOORD_0);
3530
- gl_Position = u_MVPMatrix * positions;
3531
- }
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
+ }
3532
4203
 
3533
4204
  @fragment
3534
- out vec4 fragmentColor;
3535
-
3536
- void main(void) {
3537
- vec3 pos = pbr_vPosition;
3538
- fragmentColor = pbr_filterColor(vec4(1.0));
3539
- }
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
+ }
3540
4216
  `
3541
4217
  );
3542
4218
  var vs = (
@@ -3560,6 +4236,11 @@ layout(0) positions: vec4; // in vec4 POSITION;
3560
4236
  in vec2 texCoords;
3561
4237
  #endif
3562
4238
 
4239
+ #ifdef HAS_SKIN
4240
+ in uvec4 JOINTS_0;
4241
+ in vec4 WEIGHTS_0;
4242
+ #endif
4243
+
3563
4244
  void main(void) {
3564
4245
  vec4 _NORMAL = vec4(0.);
3565
4246
  vec4 _TANGENT = vec4(0.);
@@ -3577,8 +4258,17 @@ layout(0) positions: vec4; // in vec4 POSITION;
3577
4258
  _TEXCOORD_0 = texCoords;
3578
4259
  #endif
3579
4260
 
3580
- pbr_setPositionNormalTangentUV(positions, _NORMAL, _TANGENT, _TEXCOORD_0);
3581
- 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;
3582
4272
  }
3583
4273
  `
3584
4274
  );
@@ -3593,9 +4283,28 @@ layout(0) positions: vec4; // in vec4 POSITION;
3593
4283
  }
3594
4284
  `
3595
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
+ }
3596
4305
  function createGLTFModel(device, options) {
3597
4306
  const { id, geometry, parsedPPBRMaterial, vertexCount, modelOptions = {} } = options;
3598
- import_core2.log.info(4, "createGLTFModel defines: ", parsedPPBRMaterial.defines)();
4307
+ import_core3.log.info(4, "createGLTFModel defines: ", parsedPPBRMaterial.defines)();
3599
4308
  const managedResources = [];
3600
4309
  const parameters = {
3601
4310
  depthWriteEnabled: true,
@@ -3611,20 +4320,57 @@ layout(0) positions: vec4; // in vec4 POSITION;
3611
4320
  geometry,
3612
4321
  topology: geometry.topology,
3613
4322
  vertexCount,
3614
- modules: [import_shadertools.pbrMaterial],
4323
+ modules: [import_shadertools.pbrMaterial, import_shadertools.skin],
3615
4324
  ...modelOptions,
3616
4325
  defines: { ...parsedPPBRMaterial.defines, ...modelOptions.defines },
3617
4326
  parameters: { ...parameters, ...parsedPPBRMaterial.parameters, ...modelOptions.parameters }
3618
4327
  };
3619
- const model = new import_engine2.Model(device, modelProps);
3620
- 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 = {
3621
4335
  ...parsedPPBRMaterial.uniforms,
3622
4336
  ...modelOptions.uniforms,
3623
4337
  ...parsedPPBRMaterial.bindings,
3624
4338
  ...modelOptions.bindings
3625
4339
  };
3626
- model.shaderInputs.setProps({ pbrMaterial: pbrMaterialProps, pbrProjection: { camera } });
3627
- 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;
3628
4374
  }
3629
4375
 
3630
4376
  // src/parsers/parse-gltf.ts
@@ -3635,82 +4381,128 @@ layout(0) positions: vec4; // in vec4 POSITION;
3635
4381
  lights: true,
3636
4382
  useTangents: false
3637
4383
  };
3638
- function parseGLTF(device, gltf, options_ = {}) {
3639
- const options = { ...defaultOptions, ...options_ };
3640
- const sceneNodes = gltf.scenes.map(
3641
- (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
+ })
3642
4402
  );
3643
- return sceneNodes;
3644
- }
3645
- function createScene(device, gltfScene, gltfNodes, options) {
3646
- const gltfSceneNodes = gltfScene.nodes || [];
3647
- const nodes = gltfSceneNodes.map((node) => createNode(device, node, gltfNodes, options));
3648
- const sceneNode = new import_engine3.GroupNode({
3649
- id: gltfScene.name || gltfScene.id,
3650
- children: nodes
4403
+ const gltfMaterialIdToMaterialMap = /* @__PURE__ */ new Map();
4404
+ (gltf.materials || []).forEach((gltfMaterial, materialIndex) => {
4405
+ gltfMaterialIdToMaterialMap.set(gltfMaterial.id, materials[materialIndex]);
3651
4406
  });
3652
- return sceneNode;
3653
- }
3654
- function createNode(device, gltfNode, gltfNodes, options) {
3655
- if (!gltfNode._node) {
3656
- const gltfChildren = gltfNode.children || [];
3657
- 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
+ );
3658
4434
  if (gltfNode.mesh) {
3659
- children.push(createMesh(device, gltfNode.mesh, options));
3660
- }
3661
- const node = new import_engine3.GroupNode({
3662
- id: gltfNode.name || gltfNode.id,
3663
- children
3664
- });
3665
- if (gltfNode.matrix) {
3666
- node.setMatrix(gltfNode.matrix);
3667
- } else {
3668
- node.matrix.identity();
3669
- if (gltfNode.translation) {
3670
- node.matrix.translate(gltfNode.translation);
3671
- }
3672
- if (gltfNode.rotation) {
3673
- const rotationMatrix = new Matrix4().fromQuaternion(gltfNode.rotation);
3674
- node.matrix.multiplyRight(rotationMatrix);
3675
- }
3676
- if (gltfNode.scale) {
3677
- 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}`);
3678
4438
  }
4439
+ gltfNodeIndexToNodeMap.get(idx).add(mesh);
3679
4440
  }
3680
- gltfNode._node = node;
3681
- }
3682
- const topLevelNode = gltfNodes.find((node) => node.id === gltfNode.id);
3683
- topLevelNode._node = gltfNode._node;
3684
- return gltfNode._node;
3685
- }
3686
- function createMesh(device, gltfMesh, options) {
3687
- if (!gltfMesh._mesh) {
3688
- const gltfPrimitives = gltfMesh.primitives || [];
3689
- const primitives = gltfPrimitives.map(
3690
- (gltfPrimitive, i) => createPrimitive(device, gltfPrimitive, i, gltfMesh, options)
3691
- );
3692
- const mesh = new import_engine3.GroupNode({
3693
- id: gltfMesh.name || gltfMesh.id,
3694
- 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;
3695
4448
  });
3696
- gltfMesh._mesh = mesh;
3697
- }
3698
- 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
+ });
3699
4465
  }
3700
- function createPrimitive(device, gltfPrimitive, i, gltfMesh, options) {
3701
- 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}`;
3702
4495
  const topology = convertGLDrawModeToTopology(gltfPrimitive.mode || 4);
3703
4496
  const vertexCount = gltfPrimitive.indices ? gltfPrimitive.indices.count : getVertexCount(gltfPrimitive.attributes);
3704
4497
  const geometry = createGeometry(id, gltfPrimitive, topology);
3705
- const parsedPPBRMaterial = parsePBRMaterial(
3706
- device,
3707
- gltfPrimitive.material,
3708
- geometry.attributes,
3709
- options
3710
- );
4498
+ const parsedPPBRMaterial = parsePBRMaterial(device, gltfPrimitive.material, geometry.attributes, {
4499
+ ...options,
4500
+ gltf
4501
+ });
3711
4502
  const modelNode = createGLTFModel(device, {
3712
4503
  id,
3713
4504
  geometry: createGeometry(id, gltfPrimitive, topology),
4505
+ material: gltfPrimitive.material ? gltfMaterialIdToMaterialMap.get(gltfPrimitive.material.id) || null : null,
3714
4506
  parsedPPBRMaterial,
3715
4507
  modelOptions: options.modelOptions,
3716
4508
  vertexCount
@@ -3727,40 +4519,40 @@ layout(0) positions: vec4; // in vec4 POSITION;
3727
4519
  const { components, size, value } = attribute;
3728
4520
  attributes[attributeName] = { size: size ?? components, value };
3729
4521
  }
3730
- return new import_engine3.Geometry({
4522
+ return new import_engine4.Geometry({
3731
4523
  id,
3732
4524
  topology,
3733
4525
  indices: gltfPrimitive.indices.value,
3734
4526
  attributes
3735
4527
  });
3736
4528
  }
4529
+ function getGLTFMaterialId(gltfMaterial, materialIndex) {
4530
+ return gltfMaterial.name || gltfMaterial.id || `material-${materialIndex}`;
4531
+ }
3737
4532
 
3738
4533
  // src/gltf/gltf-animator.ts
3739
4534
  var import_core6 = __toESM(require_core(), 1);
3740
4535
 
3741
4536
  // src/gltf/animations/interpolate.ts
3742
4537
  var import_core4 = __toESM(require_core(), 1);
3743
- var scratchQuaternion = new Quaternion();
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
+ }
3744
4551
  function interpolate(time, { input, interpolation, output }, target, path) {
3745
4552
  const maxTime = input[input.length - 1];
3746
4553
  const animationTime = time % maxTime;
3747
4554
  const nextIndex = input.findIndex((t) => t >= animationTime);
3748
4555
  const previousIndex = Math.max(0, nextIndex - 1);
3749
- if (!Array.isArray(target[path])) {
3750
- switch (path) {
3751
- case "translation":
3752
- target[path] = [0, 0, 0];
3753
- break;
3754
- case "rotation":
3755
- target[path] = [0, 0, 0, 1];
3756
- break;
3757
- case "scale":
3758
- target[path] = [1, 1, 1];
3759
- break;
3760
- default:
3761
- import_core4.log.warn(`Bad animation path ${path}`)();
3762
- }
3763
- }
3764
4556
  const previousTime = input[previousIndex];
3765
4557
  const nextTime = input[nextIndex];
3766
4558
  switch (interpolation) {
@@ -3770,13 +4562,7 @@ layout(0) positions: vec4; // in vec4 POSITION;
3770
4562
  case "LINEAR":
3771
4563
  if (nextTime > previousTime) {
3772
4564
  const ratio = (animationTime - previousTime) / (nextTime - previousTime);
3773
- linearInterpolate(
3774
- target,
3775
- path,
3776
- output[previousIndex],
3777
- output[nextIndex],
3778
- ratio
3779
- );
4565
+ linearInterpolate(target, path, output[previousIndex], output[nextIndex], ratio);
3780
4566
  }
3781
4567
  break;
3782
4568
  case "CUBICSPLINE":
@@ -3796,18 +4582,14 @@ layout(0) positions: vec4; // in vec4 POSITION;
3796
4582
  }
3797
4583
  }
3798
4584
  function linearInterpolate(target, path, start, stop, ratio) {
3799
- if (!target[path]) {
3800
- throw new Error();
3801
- }
3802
4585
  if (path === "rotation") {
3803
- scratchQuaternion.slerp({ start, target: stop, ratio });
3804
- for (let i = 0; i < scratchQuaternion.length; i++) {
3805
- target[path][i] = scratchQuaternion[i];
3806
- }
4586
+ updateTargetPath(target, path, new Quaternion().slerp({ start, target: stop, ratio }));
3807
4587
  } else {
4588
+ const newVal = [];
3808
4589
  for (let i = 0; i < start.length; i++) {
3809
- target[path][i] = ratio * stop[i] + (1 - ratio) * start[i];
4590
+ newVal[i] = ratio * stop[i] + (1 - ratio) * start[i];
3810
4591
  }
4592
+ updateTargetPath(target, path, newVal);
3811
4593
  }
3812
4594
  }
3813
4595
  function cubicsplineInterpolate(target, path, {
@@ -3818,53 +4600,62 @@ layout(0) positions: vec4; // in vec4 POSITION;
3818
4600
  tDiff,
3819
4601
  ratio: t
3820
4602
  }) {
3821
- if (!target[path]) {
3822
- throw new Error();
3823
- }
3824
- for (let i = 0; i < target[path].length; i++) {
4603
+ const newVal = [];
4604
+ for (let i = 0; i < p0.length; i++) {
3825
4605
  const m0 = outTangent0[i] * tDiff;
3826
4606
  const m1 = inTangent1[i] * tDiff;
3827
- 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;
3828
4608
  }
4609
+ updateTargetPath(target, path, newVal);
3829
4610
  }
3830
4611
  function stepInterpolate(target, path, value) {
3831
- if (!target[path]) {
3832
- throw new Error();
3833
- }
3834
- for (let i = 0; i < value.length; i++) {
3835
- target[path][i] = value[i];
3836
- }
4612
+ updateTargetPath(target, path, value);
3837
4613
  }
3838
4614
 
3839
4615
  // src/gltf/gltf-animator.ts
3840
4616
  var GLTFSingleAnimator = class {
4617
+ /** Animation definition being played. */
3841
4618
  animation;
4619
+ /** Target scenegraph lookup table. */
4620
+ gltfNodeIdToNodeMap;
4621
+ /** Playback start time in seconds. */
3842
4622
  startTime = 0;
4623
+ /** Whether playback is currently enabled. */
3843
4624
  playing = true;
4625
+ /** Playback speed multiplier. */
3844
4626
  speed = 1;
4627
+ /** Creates a single-animation controller. */
3845
4628
  constructor(props) {
3846
4629
  this.animation = props.animation;
4630
+ this.gltfNodeIdToNodeMap = props.gltfNodeIdToNodeMap;
3847
4631
  this.animation.name ||= "unnamed";
3848
4632
  Object.assign(this, props);
3849
4633
  }
4634
+ /** Advances the animation to the supplied wall-clock time in milliseconds. */
3850
4635
  setTime(timeMs) {
3851
4636
  if (!this.playing) {
3852
4637
  return;
3853
4638
  }
3854
4639
  const absTime = timeMs / 1e3;
3855
4640
  const time = (absTime - this.startTime) * this.speed;
3856
- this.animation.channels.forEach(({ sampler, target, path }) => {
3857
- interpolate(time, sampler, target, path);
3858
- 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);
3859
4647
  });
3860
4648
  }
3861
4649
  };
3862
4650
  var GLTFAnimator = class {
4651
+ /** Individual animation controllers. */
3863
4652
  animations;
4653
+ /** Creates an animator for the supplied glTF scenegraph. */
3864
4654
  constructor(props) {
3865
4655
  this.animations = props.animations.map((animation, index) => {
3866
4656
  const name = animation.name || `Animation-${index}`;
3867
4657
  return new GLTFSingleAnimator({
4658
+ gltfNodeIdToNodeMap: props.gltfNodeIdToNodeMap,
3868
4659
  animation: { name, channels: animation.channels }
3869
4660
  });
3870
4661
  });
@@ -3874,27 +4665,18 @@ layout(0) positions: vec4; // in vec4 POSITION;
3874
4665
  import_core6.log.warn("GLTFAnimator#animate is deprecated. Use GLTFAnimator#setTime instead")();
3875
4666
  this.setTime(time);
3876
4667
  }
4668
+ /** Advances every animation to the supplied wall-clock time in milliseconds. */
3877
4669
  setTime(time) {
3878
4670
  this.animations.forEach((animation) => animation.setTime(time));
3879
4671
  }
4672
+ /** Returns the per-animation controllers managed by this animator. */
3880
4673
  getAnimations() {
3881
4674
  return this.animations;
3882
4675
  }
3883
4676
  };
3884
- var scratchMatrix = new Matrix4();
3885
- function applyTranslationRotationScale(gltfNode, node) {
3886
- node.matrix.identity();
3887
- if (gltfNode.translation) {
3888
- node.matrix.translate(gltfNode.translation);
3889
- }
3890
- if (gltfNode.rotation) {
3891
- const rotationMatrix = scratchMatrix.fromQuaternion(gltfNode.rotation);
3892
- node.matrix.multiplyRight(rotationMatrix);
3893
- }
3894
- if (gltfNode.scale) {
3895
- node.matrix.scale(gltfNode.scale);
3896
- }
3897
- }
4677
+
4678
+ // src/parsers/parse-gltf-animations.ts
4679
+ var import_core7 = __toESM(require_core(), 1);
3898
4680
 
3899
4681
  // src/webgl-to-webgpu/convert-webgl-attribute.ts
3900
4682
  var ATTRIBUTE_TYPE_TO_COMPONENTS = {
@@ -3926,64 +4708,331 @@ layout(0) positions: vec4; // in vec4 POSITION;
3926
4708
  // src/parsers/parse-gltf-animations.ts
3927
4709
  function parseGLTFAnimations(gltf) {
3928
4710
  const gltfAnimations = gltf.animations || [];
3929
- return gltfAnimations.map((animation, index) => {
4711
+ const accessorCache1D = /* @__PURE__ */ new Map();
4712
+ const accessorCache2D = /* @__PURE__ */ new Map();
4713
+ return gltfAnimations.flatMap((animation, index) => {
3930
4714
  const name = animation.name || `Animation-${index}`;
3931
- const samplers = animation.samplers.map(
3932
- ({ input, interpolation = "LINEAR", output }) => ({
3933
- input: accessorToJsArray(gltf.accessors[input]),
3934
- interpolation,
3935
- output: accessorToJsArray(gltf.accessors[output])
3936
- })
3937
- );
3938
- const channels = animation.channels.map(({ sampler, target }) => ({
3939
- sampler: samplers[sampler],
3940
- target: gltf.nodes[target.node ?? 0],
3941
- path: target.path
3942
- }));
3943
- 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 }] : [];
3944
4746
  });
3945
4747
  }
3946
- function accessorToJsArray(accessor) {
3947
- if (!accessor._animation) {
3948
- const { typedArray: array, components } = accessorToTypedArray(accessor);
3949
- if (components === 1) {
3950
- accessor._animation = Array.from(array);
3951
- } else {
3952
- const slicedArray = [];
3953
- for (let i = 0; i < array.length; i += components) {
3954
- slicedArray.push(Array.from(array.slice(i, i + components)));
3955
- }
3956
- accessor._animation = slicedArray;
3957
- }
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;
3958
4752
  }
3959
- return accessor._animation;
4753
+ return path;
3960
4754
  }
3961
-
3962
- // src/utils/deep-copy.ts
3963
- function deepCopy(object) {
3964
- if (ArrayBuffer.isView(object) || object instanceof ArrayBuffer || object instanceof ImageBitmap) {
3965
- return object;
4755
+ function accessorToJsArray1D(accessor, accessorCache) {
4756
+ if (accessorCache.has(accessor)) {
4757
+ return accessorCache.get(accessor);
3966
4758
  }
3967
- if (Array.isArray(object)) {
3968
- 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);
4781
+ }
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."
3969
4913
  }
3970
- if (object && typeof object === "object") {
3971
- const result = {};
3972
- for (const key in object) {
3973
- result[key] = deepCopy(object[key]);
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
+ ];
3974
4929
  }
3975
- 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);
3976
4954
  }
3977
- return object;
3978
4955
  }
3979
4956
 
3980
4957
  // src/gltf/create-scenegraph-from-gltf.ts
3981
4958
  function createScenegraphsFromGLTF(device, gltf, options) {
3982
- gltf = deepCopy(gltf);
3983
- const scenes = parseGLTF(device, gltf, options);
4959
+ const { scenes, materials, gltfMeshIdToNodeMap, gltfNodeIdToNodeMap, gltfNodeIndexToNodeMap } = parseGLTF(device, gltf, options);
3984
4960
  const animations = parseGLTFAnimations(gltf);
3985
- const animator = new GLTFAnimator({ animations });
3986
- return { scenes, animator };
4961
+ const animator = new GLTFAnimator({ animations, gltfNodeIdToNodeMap });
4962
+ const lights = parseGLTFLights(gltf);
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);
3987
5036
  }
3988
5037
  return __toCommonJS(bundle_exports);
3989
5038
  })();