@forgeax/engine-gltf 0.1.21 → 0.1.24

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 (68) hide show
  1. package/README.md +115 -3
  2. package/dist/__tests__/lod-import.integration.test.d.ts +2 -0
  3. package/dist/__tests__/lod-import.integration.test.d.ts.map +1 -0
  4. package/dist/__tests__/lod-meta-publication.integration.test.d.ts +2 -0
  5. package/dist/__tests__/lod-meta-publication.integration.test.d.ts.map +1 -0
  6. package/dist/__tests__/physical-clearcoat.unit.test.d.ts +2 -0
  7. package/dist/__tests__/physical-clearcoat.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/physical-material-import.integration.test.d.ts +2 -0
  9. package/dist/__tests__/physical-material-import.integration.test.d.ts.map +1 -0
  10. package/dist/bridge.d.ts +1 -0
  11. package/dist/bridge.d.ts.map +1 -1
  12. package/dist/check-extensions.d.ts +1 -1
  13. package/dist/check-extensions.d.ts.map +1 -1
  14. package/dist/cli-gltf.mjs +362 -14
  15. package/dist/cli-gltf.mjs.map +1 -1
  16. package/dist/errors.d.ts +13 -0
  17. package/dist/errors.d.ts.map +1 -1
  18. package/dist/gltf-importer.d.ts.map +1 -1
  19. package/dist/image-color-space.d.ts +12 -0
  20. package/dist/image-color-space.d.ts.map +1 -1
  21. package/dist/importer-entry.mjs +516 -25
  22. package/dist/importer-entry.mjs.map +1 -1
  23. package/dist/index.d.ts +4 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.mjs +609 -28
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/lod/parse-lod.d.ts +15 -0
  28. package/dist/lod/parse-lod.d.ts.map +1 -0
  29. package/dist/lod/project-meta.d.ts +23 -0
  30. package/dist/lod/project-meta.d.ts.map +1 -0
  31. package/dist/material/parse-material.d.ts +66 -0
  32. package/dist/material/parse-material.d.ts.map +1 -1
  33. package/dist/node-file-entry.mjs +265 -8
  34. package/dist/node-file-entry.mjs.map +1 -1
  35. package/dist/parse-gltf.d.ts +3 -1
  36. package/dist/parse-gltf.d.ts.map +1 -1
  37. package/dist/reimport-reuse-meta.d.ts +1 -1
  38. package/dist/reimport-reuse-meta.d.ts.map +1 -1
  39. package/package.json +12 -12
  40. package/src/__tests__/bridge-material-values.unit.test.ts +84 -1
  41. package/src/__tests__/bridge-skin-stride.unit.test.ts +22 -0
  42. package/src/__tests__/fixtures/lod/core-only.gltf +1 -0
  43. package/src/__tests__/fixtures/lod/msft-lod-malformed.gltf +1 -0
  44. package/src/__tests__/fixtures/lod/msft-lod-multi-root.gltf +1 -0
  45. package/src/__tests__/fixtures/lod/msft-lod-node-extras.gltf +1 -0
  46. package/src/__tests__/fixtures/lod/msft-lod.gltf +1 -0
  47. package/src/__tests__/gltf-error-derived-views.test-d.ts +8 -0
  48. package/src/__tests__/gltf-error-migration.test.ts +3 -3
  49. package/src/__tests__/gltf.unit.test.ts +60 -5
  50. package/src/__tests__/lod-import.integration.test.ts +106 -0
  51. package/src/__tests__/lod-meta-publication.integration.test.ts +24 -0
  52. package/src/__tests__/material-pack-refs.integration.test.ts +53 -0
  53. package/src/__tests__/parse-gltf.unit.test.ts +50 -0
  54. package/src/__tests__/physical-clearcoat.unit.test.ts +110 -0
  55. package/src/__tests__/physical-material-import.integration.test.ts +239 -0
  56. package/src/__tests__/source-key-producer.integration.test.ts +6 -2
  57. package/src/bridge.ts +259 -14
  58. package/src/check-extensions.ts +16 -8
  59. package/src/errors.ts +32 -2
  60. package/src/gltf-importer.ts +122 -1
  61. package/src/image-color-space.ts +29 -4
  62. package/src/index.ts +10 -1
  63. package/src/lod/parse-lod.ts +129 -0
  64. package/src/lod/project-meta.ts +48 -0
  65. package/src/material/parse-material.ts +307 -0
  66. package/src/parse-gltf.ts +97 -1
  67. package/src/reimport-reuse-meta.ts +3 -1
  68. package/dist/.tsbuildinfo +0 -1
package/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
- import { err, ok, reconcileMeshMaterialSlotTopology, MESH_MATERIAL_SLOT_SOURCE_OVERRIDE_PAYLOAD_SCHEMA, ImportError, IMPORT_ERROR_HINTS, toShared, resolveMeshMaterialSlotDefaultGuid, createMaterialError } from '@forgeax/engine-types';
1
+ import { err, ok, createMaterialError, standardMaterialParameters, reconcileMeshMaterialSlotTopology, MESH_MATERIAL_SLOT_SOURCE_OVERRIDE_PAYLOAD_SCHEMA, STANDARD_MATERIAL_PARAM_SCHEMA, STANDARD_PHYSICAL_PARAMETER_NAMES, STANDARD_TRANSMISSION_PARAMETER_NAMES, ImportError, STANDARD_LAYER_PARAMETER_GROUPS, IMPORT_ERROR_HINTS, toShared, resolveMeshMaterialSlotDefaultGuid } from '@forgeax/engine-types';
2
2
  export { err, ok } from '@forgeax/engine-types';
3
- import { packInterleavedVertexAttributes } from '@forgeax/engine-geometry';
3
+ import { computeTangentVec4, packInterleavedVertexAttributes } from '@forgeax/engine-geometry';
4
4
  import { box3, mat4, vec3, quat } from '@forgeax/engine-math';
5
5
  import { AssetGuid } from '@forgeax/engine-pack/guid';
6
+ import { deriveDefaultLodScreenCoverages, reconcileMeshLodMeta } from '@forgeax/engine-import';
6
7
  import { packMeshBinV4 } from '@forgeax/engine-import/mesh-bin';
7
8
  import { deriveAnimationTargetId } from '@forgeax/engine-animation/target-id';
8
9
 
@@ -23,13 +24,21 @@ var gltfErrorPolicy = {
23
24
  hint: "rebuild .gltf with valid bufferViews; check accessor index; ensure accessor.byteOffset + EFFECTIVE_STRIDE * (count - 1) + element_size <= bufferView.byteLength"
24
25
  },
25
26
  "gltf-extension-unsupported": {
26
- expected: "extension listed in v1 allowlist (see EXTENSION_ALLOWLIST in @forgeax/engine-gltf)",
27
- hint: "see feat-future-gltf-extensions-allowlist; remove this extension or wait for the allowlist to expand"
27
+ expected: "extension listed in the supported allowlist (see EXTENSION_ALLOWLIST in @forgeax/engine-gltf)",
28
+ hint: "remove the unsupported required extension or use a supported glTF extension; extensionsUsed-only entries remain diagnostic"
29
+ },
30
+ "gltf-lod-invalid": {
31
+ expected: "MSFT_lod ids to reference unique existing node indices with valid coverage",
32
+ hint: "repair the MSFT_lod node relation or remove it from extensionsRequired before re-importing"
28
33
  },
29
34
  "gltf-material-transmission-invalid": {
30
35
  expected: "KHR transmission, IOR, and volume values are finite and within their glTF ranges",
31
36
  hint: "repair the named glTF material extension value and re-import the source"
32
37
  },
38
+ "gltf-material-physical-invalid": {
39
+ expected: "KHR clearcoat, anisotropy, sheen, iridescence, and specular values are finite and within their glTF ranges",
40
+ hint: "repair the named physical material extension value and re-import the source"
41
+ },
33
42
  "gltf-accessor-type-mismatch": {
34
43
  expected: "dense fixed-stride accessor with supported componentType",
35
44
  hint: "sparse: see feat-future-gltf-sparse-accessor; morph: see feat-future-gltf-morph; interleaved: see feat-future-gltf-mesh-multi-section"
@@ -486,6 +495,16 @@ function meshIrToMeshAsset(prims, materials = {}) {
486
495
  const materialSlot = slotFor(mesh.materialIndex);
487
496
  const primVertexCount = mesh.positions.length / 3;
488
497
  const primIndexCount = mesh.indices === void 0 ? 0 : mesh.indices.length;
498
+ let generatedTangents;
499
+ if (mesh.tangents === void 0 && mesh.normals !== void 0 && mesh.texcoord0 !== void 0) {
500
+ const generated = computeTangentVec4(
501
+ mesh.positions,
502
+ mesh.normals,
503
+ mesh.texcoord0,
504
+ mesh.indices
505
+ );
506
+ if (generated.ok) generatedTangents = generated.value;
507
+ }
489
508
  if (mesh.colors0 !== void 0 && mesh.colors0.length !== primVertexCount * 4) {
490
509
  return err(
491
510
  gltfErr("gltf-mesh-bridge-invalid", {
@@ -517,12 +536,13 @@ function meshIrToMeshAsset(prims, materials = {}) {
517
536
  uvsCat[(vertexCursor + i) * 2 + 0] = mesh.texcoord0[t + 0];
518
537
  uvsCat[(vertexCursor + i) * 2 + 1] = mesh.texcoord0[t + 1];
519
538
  }
520
- if (mesh.tangents !== void 0) {
539
+ const sourceTangents = mesh.tangents ?? generatedTangents;
540
+ if (sourceTangents !== void 0) {
521
541
  const g = i * 4;
522
- tangentsCat[(vertexCursor + i) * 4 + 0] = mesh.tangents[g + 0];
523
- tangentsCat[(vertexCursor + i) * 4 + 1] = mesh.tangents[g + 1];
524
- tangentsCat[(vertexCursor + i) * 4 + 2] = mesh.tangents[g + 2];
525
- tangentsCat[(vertexCursor + i) * 4 + 3] = mesh.tangents[g + 3];
542
+ tangentsCat[(vertexCursor + i) * 4 + 0] = sourceTangents[g + 0];
543
+ tangentsCat[(vertexCursor + i) * 4 + 1] = sourceTangents[g + 1];
544
+ tangentsCat[(vertexCursor + i) * 4 + 2] = sourceTangents[g + 2];
545
+ tangentsCat[(vertexCursor + i) * 4 + 3] = sourceTangents[g + 3];
526
546
  } else {
527
547
  tangentsCat[(vertexCursor + i) * 4 + 0] = 1;
528
548
  tangentsCat[(vertexCursor + i) * 4 + 3] = 1;
@@ -857,6 +877,12 @@ function textureValue(info, slot, ctx) {
857
877
  return { ...value, normalScale: normal.scale };
858
878
  }
859
879
  }
880
+ if (slot === "clearcoatNormalTexture") {
881
+ const normal = info;
882
+ if (typeof normal === "object" && normal?.scale !== void 0) {
883
+ return { ...value, normalScale: normal.scale };
884
+ }
885
+ }
860
886
  if (slot === "occlusionTexture") {
861
887
  const occlusion = info;
862
888
  if (typeof occlusion === "object" && occlusion?.strength !== void 0) {
@@ -865,6 +891,47 @@ function textureValue(info, slot, ctx) {
865
891
  }
866
892
  return value;
867
893
  }
894
+ function standardRootParameterNames(mat) {
895
+ const names = new Set(
896
+ STANDARD_MATERIAL_PARAM_SCHEMA.filter(
897
+ (entry) => !STANDARD_PHYSICAL_PARAMETER_NAMES.has(entry.name) && !STANDARD_TRANSMISSION_PARAMETER_NAMES.has(entry.name)
898
+ ).map((entry) => entry.name)
899
+ );
900
+ names.add("ior");
901
+ const addLayer = (layer) => {
902
+ for (const name of STANDARD_LAYER_PARAMETER_GROUPS[layer]) names.add(name);
903
+ };
904
+ const addTexture = (name, info) => {
905
+ if (info !== void 0) names.add(name);
906
+ };
907
+ const clearcoat = mat.clearcoatFactor !== void 0 || mat.clearcoatRoughnessFactor !== void 0 || mat.clearcoatNormalTexture !== void 0 || mat.clearcoatTexture !== void 0 || mat.clearcoatRoughnessTexture !== void 0;
908
+ const anisotropy = mat.anisotropyStrength !== void 0 || mat.anisotropyRotation !== void 0 || mat.anisotropyTexture !== void 0;
909
+ const sheen = mat.sheenColorFactor !== void 0 || mat.sheenRoughnessFactor !== void 0 || mat.sheenColorTexture !== void 0 || mat.sheenRoughnessTexture !== void 0;
910
+ const iridescence = mat.iridescenceFactor !== void 0 || mat.iridescenceIor !== void 0 || mat.iridescenceThicknessMinimum !== void 0 || mat.iridescenceThicknessMaximum !== void 0 || mat.iridescenceTexture !== void 0 || mat.iridescenceThicknessTexture !== void 0;
911
+ if (clearcoat) addLayer("clearcoat");
912
+ if (anisotropy) addLayer("anisotropy");
913
+ if (sheen) addLayer("sheen");
914
+ if (iridescence) addLayer("iridescence");
915
+ if (clearcoat && mat.clearcoatNormalTexture !== void 0) names.add("clearcoatNormalScale");
916
+ addTexture("clearcoatTexture", mat.clearcoatTexture);
917
+ addTexture("clearcoatRoughnessTexture", mat.clearcoatRoughnessTexture);
918
+ addTexture("clearcoatNormalTexture", mat.clearcoatNormalTexture);
919
+ addTexture("anisotropyTexture", mat.anisotropyTexture);
920
+ addTexture("sheenColorTexture", mat.sheenColorTexture);
921
+ addTexture("sheenRoughnessTexture", mat.sheenRoughnessTexture);
922
+ addTexture("iridescenceTexture", mat.iridescenceTexture);
923
+ addTexture("iridescenceThicknessTexture", mat.iridescenceThicknessTexture);
924
+ addTexture("specularTexture", mat.specularTexture);
925
+ addTexture("specularColorTexture", mat.specularColorTexture);
926
+ const transmission = mat.transmissionFactor !== void 0 || mat.transmissionTexture !== void 0 || mat.ior !== void 0 || mat.thicknessFactor !== void 0 || mat.thicknessTexture !== void 0 || mat.attenuationColor !== void 0 || mat.attenuationDistance !== void 0;
927
+ if (transmission) {
928
+ for (const name of STANDARD_TRANSMISSION_PARAMETER_NAMES) names.add(name);
929
+ }
930
+ return {
931
+ names,
932
+ extended: clearcoat || anisotropy || sheen || iridescence || transmission || mat.specularTexture !== void 0 || mat.specularColorTexture !== void 0
933
+ };
934
+ }
868
935
  function validateMaterialUvSets(mat, primitive, availableSets) {
869
936
  const available = new Set(availableSets);
870
937
  const slots = [
@@ -874,7 +941,17 @@ function validateMaterialUvSets(mat, primitive, availableSets) {
874
941
  ["occlusionTexture", mat.occlusionTexture],
875
942
  ["emissiveTexture", mat.emissiveTexture],
876
943
  ["transmissionTexture", mat.transmissionTexture],
877
- ["thicknessTexture", mat.thicknessTexture]
944
+ ["thicknessTexture", mat.thicknessTexture],
945
+ ["clearcoatTexture", mat.clearcoatTexture],
946
+ ["clearcoatRoughnessTexture", mat.clearcoatRoughnessTexture],
947
+ ["clearcoatNormalTexture", mat.clearcoatNormalTexture],
948
+ ["anisotropyTexture", mat.anisotropyTexture],
949
+ ["sheenColorTexture", mat.sheenColorTexture],
950
+ ["sheenRoughnessTexture", mat.sheenRoughnessTexture],
951
+ ["iridescenceTexture", mat.iridescenceTexture],
952
+ ["iridescenceThicknessTexture", mat.iridescenceThicknessTexture],
953
+ ["specularTexture", mat.specularTexture],
954
+ ["specularColorTexture", mat.specularColorTexture]
878
955
  ];
879
956
  for (const [slot, rawBinding] of slots) {
880
957
  const binding = textureInfo(rawBinding);
@@ -894,7 +971,44 @@ function validateMaterialUvSets(mat, primitive, availableSets) {
894
971
  }
895
972
  return ok(void 0);
896
973
  }
974
+ function validateMaterialTangentInputs(mat, mesh, layer = "clearcoat") {
975
+ const anisotropyDeclared = mat.anisotropyStrength !== void 0 || mat.anisotropyRotation !== void 0 || mat.anisotropyTexture !== void 0;
976
+ const tangentSlot = anisotropyDeclared ? { layer: "anisotropy", info: mat.anisotropyTexture } : mat.clearcoatNormalTexture !== void 0 ? { layer: "clearcoat", info: mat.clearcoatNormalTexture } : void 0;
977
+ if (tangentSlot === void 0) return ok(void 0);
978
+ const selected = textureInfo(tangentSlot.info);
979
+ const uvSet = selected?.texCoord ?? 0;
980
+ const uv = mesh[`texcoord${uvSet === 0 ? "0" : uvSet}`];
981
+ const attributes = ["NORMAL", `TEXCOORD_${uvSet}`, "TANGENT"];
982
+ const fail = (reason) => err(
983
+ createMaterialError("material-tangent-required", {
984
+ code: "material-tangent-required",
985
+ material: mat.name ?? "<unnamed>",
986
+ mesh: mesh.name ?? "<unnamed>",
987
+ layer: tangentSlot.layer ?? layer,
988
+ uv: `TEXCOORD_${uvSet}`,
989
+ attributes,
990
+ reason
991
+ })
992
+ );
993
+ if (mesh.tangents !== void 0) {
994
+ if (mesh.tangents.length !== mesh.positions.length / 3 * 4 || mesh.tangents.some((value) => !Number.isFinite(value))) {
995
+ return fail("imported TANGENT must be finite vec4 per vertex");
996
+ }
997
+ return ok(void 0);
998
+ }
999
+ if (mesh.normals === void 0) return fail("NORMAL is required to generate tangent");
1000
+ if (!(uv instanceof Float32Array))
1001
+ return fail("the selected UV set is required to generate tangent");
1002
+ const generated = computeTangentVec4(mesh.positions, mesh.normals, uv, mesh.indices);
1003
+ if (!generated.ok) {
1004
+ const detail = generated.error.detail;
1005
+ const reason = detail !== void 0 && "reason" in detail ? String(detail.reason) : "tangent producer rejected topology";
1006
+ return fail(reason);
1007
+ }
1008
+ return ok(void 0);
1009
+ }
897
1010
  function toMaterialAsset(mat, ctx) {
1011
+ const rootContract = standardRootParameterNames(mat);
898
1012
  const values = {
899
1013
  baseColor: mat.baseColorFactor,
900
1014
  metallic: mat.metallicFactor,
@@ -911,7 +1025,17 @@ function toMaterialAsset(mat, ctx) {
911
1025
  ["occlusionTexture", mat.occlusionTexture],
912
1026
  ["emissiveTexture", mat.emissiveTexture],
913
1027
  ["transmissionTexture", mat.transmissionTexture],
914
- ["thicknessTexture", mat.thicknessTexture]
1028
+ ["thicknessTexture", mat.thicknessTexture],
1029
+ ["clearcoatTexture", mat.clearcoatTexture],
1030
+ ["clearcoatRoughnessTexture", mat.clearcoatRoughnessTexture],
1031
+ ["clearcoatNormalTexture", mat.clearcoatNormalTexture],
1032
+ ["anisotropyTexture", mat.anisotropyTexture],
1033
+ ["sheenColorTexture", mat.sheenColorTexture],
1034
+ ["sheenRoughnessTexture", mat.sheenRoughnessTexture],
1035
+ ["iridescenceTexture", mat.iridescenceTexture],
1036
+ ["iridescenceThicknessTexture", mat.iridescenceThicknessTexture],
1037
+ ["specularTexture", mat.specularTexture],
1038
+ ["specularColorTexture", mat.specularColorTexture]
915
1039
  ];
916
1040
  for (const [slot, info] of textureSlots) {
917
1041
  const value = textureValue(info, slot, ctx);
@@ -920,11 +1044,35 @@ function toMaterialAsset(mat, ctx) {
920
1044
  if (mat.occlusionTexture !== void 0 && values.occlusionStrength === void 0) {
921
1045
  values.occlusionStrength = 1;
922
1046
  }
923
- if (mat.transmissionFactor !== void 0) values.transmission = mat.transmissionFactor;
924
- if (mat.ior !== void 0) values.ior = mat.ior;
925
- if (mat.thicknessFactor !== void 0) values.thickness = mat.thicknessFactor;
926
- if (mat.attenuationColor !== void 0) values.attenuationColor = mat.attenuationColor;
927
- if (mat.attenuationDistance !== void 0) values.attenuationDistance = mat.attenuationDistance;
1047
+ if (rootContract.extended && rootContract.names.has("transmission")) {
1048
+ values.transmission = mat.transmissionFactor ?? 0;
1049
+ values.ior = mat.ior ?? 1.5;
1050
+ values.thickness = mat.thicknessFactor ?? 0;
1051
+ values.attenuationColor = mat.attenuationColor ?? [1, 1, 1];
1052
+ if (mat.attenuationDistance !== void 0) values.attenuationDistance = mat.attenuationDistance;
1053
+ }
1054
+ if (rootContract.names.has("clearcoat")) {
1055
+ values.clearcoat = mat.clearcoatFactor ?? 0;
1056
+ values.clearcoatRoughness = mat.clearcoatRoughnessFactor ?? 0;
1057
+ }
1058
+ const clearcoatNormal = textureInfo(mat.clearcoatNormalTexture);
1059
+ if (clearcoatNormal?.scale !== void 0) values.clearcoatNormalScale = clearcoatNormal.scale;
1060
+ if (rootContract.names.has("anisotropyStrength")) {
1061
+ values.anisotropyStrength = mat.anisotropyStrength ?? 0;
1062
+ values.anisotropyRotation = mat.anisotropyRotation ?? 0;
1063
+ }
1064
+ if (rootContract.names.has("sheenColor")) {
1065
+ values.sheenColor = mat.sheenColorFactor ?? [0, 0, 0];
1066
+ values.sheenRoughness = mat.sheenRoughnessFactor ?? 0;
1067
+ }
1068
+ if (rootContract.names.has("iridescence")) {
1069
+ values.iridescence = mat.iridescenceFactor ?? 0;
1070
+ values.iridescenceIor = mat.iridescenceIor ?? 1.3;
1071
+ values.iridescenceThicknessMinimum = mat.iridescenceThicknessMinimum ?? 100;
1072
+ values.iridescenceThicknessMaximum = mat.iridescenceThicknessMaximum ?? 400;
1073
+ }
1074
+ if (mat.specularFactor !== void 0) values.specular = mat.specularFactor;
1075
+ if (mat.specularColorFactor !== void 0) values.specularColor = mat.specularColorFactor;
928
1076
  const module = ctx?.skinned === true ? "forgeax::pbr-skin" : "forgeax::default-standard-pbr";
929
1077
  const isMask = mat.alphaMode === "MASK";
930
1078
  const alphaCutoff = isMask ? mat.alphaCutoff ?? 0.5 : void 0;
@@ -957,8 +1105,9 @@ function toMaterialAsset(mat, ctx) {
957
1105
  return {
958
1106
  kind: "material",
959
1107
  colorSpace: "linear",
960
- ...ctx?.standardRootGuid === void 0 ? {} : { parent: ctx.standardRootGuid },
1108
+ ...!rootContract.extended && ctx?.standardRootGuid !== void 0 ? { parent: ctx.standardRootGuid } : {},
961
1109
  passes: [pass],
1110
+ ...ctx?.standardRootGuid !== void 0 && !rootContract.extended ? {} : { parameters: standardMaterialParameters(rootContract.names) },
962
1111
  values
963
1112
  };
964
1113
  }
@@ -966,15 +1115,22 @@ function toMaterialAsset(mat, ctx) {
966
1115
  // src/check-extensions.ts
967
1116
  var EXTENSION_ALLOWLIST = [
968
1117
  "EXT_mesh_gpu_instancing",
969
- "EXT_meshopt_compression"
970
- ];
971
- var SUPPORTED_EXTENSIONS = [
972
- ...EXTENSION_ALLOWLIST,
1118
+ "EXT_meshopt_compression",
973
1119
  "KHR_lights_punctual",
974
1120
  "KHR_texture_transform",
975
1121
  "KHR_materials_transmission",
976
1122
  "KHR_materials_ior",
977
- "KHR_materials_volume"
1123
+ "KHR_materials_volume",
1124
+ "KHR_materials_clearcoat",
1125
+ "KHR_materials_anisotropy",
1126
+ "KHR_materials_sheen",
1127
+ "KHR_materials_iridescence",
1128
+ "KHR_materials_specular"
1129
+ ];
1130
+ var SUPPORTED_EXTENSIONS = [
1131
+ ...EXTENSION_ALLOWLIST,
1132
+ "MSFT_lod",
1133
+ "MSFT_screencoverage"
978
1134
  ];
979
1135
  function checkExtensions(json) {
980
1136
  const required = json.extensionsRequired ?? [];
@@ -1169,8 +1325,8 @@ function parseGlbChunks(buffer, filePath) {
1169
1325
 
1170
1326
  // src/extract-image-bytes.ts
1171
1327
  var DATA_URI_MIME_RE = /^data:([^;,]+)(?:;[^,;]+)*;base64,/;
1172
- function describeImportError(err2) {
1173
- return err2.message;
1328
+ function describeImportError(err3) {
1329
+ return err3.message;
1174
1330
  }
1175
1331
  function classifyMime(declaredMime, bytes) {
1176
1332
  if (declaredMime === "image/png" || declaredMime === "image/jpeg") return declaredMime;
@@ -1412,9 +1568,21 @@ function deriveTextureColorSpace(input) {
1412
1568
  for (const mat of input.materials) {
1413
1569
  record(imageOfTexture(mat.baseColorTexture), "srgb");
1414
1570
  record(imageOfTexture(mat.emissiveTexture), "srgb");
1571
+ record(imageOfTexture(mat.sheenColorTexture), "srgb");
1572
+ record(imageOfTexture(mat.specularColorTexture), "srgb");
1415
1573
  record(imageOfTexture(mat.metallicRoughnessTexture), "linear");
1416
1574
  record(imageOfTexture(mat.normalTexture), "linear");
1417
1575
  record(imageOfTexture(mat.occlusionTexture), "linear");
1576
+ record(imageOfTexture(mat.transmissionTexture), "linear");
1577
+ record(imageOfTexture(mat.thicknessTexture), "linear");
1578
+ record(imageOfTexture(mat.clearcoatTexture), "linear");
1579
+ record(imageOfTexture(mat.clearcoatRoughnessTexture), "linear");
1580
+ record(imageOfTexture(mat.clearcoatNormalTexture), "linear");
1581
+ record(imageOfTexture(mat.anisotropyTexture), "linear");
1582
+ record(imageOfTexture(mat.sheenRoughnessTexture), "linear");
1583
+ record(imageOfTexture(mat.iridescenceTexture), "linear");
1584
+ record(imageOfTexture(mat.iridescenceThicknessTexture), "linear");
1585
+ record(imageOfTexture(mat.specularTexture), "linear");
1418
1586
  }
1419
1587
  for (let i = 0; i < input.imageCount; i++) {
1420
1588
  if (!result.has(i)) {
@@ -1424,6 +1592,103 @@ function deriveTextureColorSpace(input) {
1424
1592
  return result;
1425
1593
  }
1426
1594
 
1595
+ // src/lod/parse-lod.ts
1596
+ function parseGltfLodExtension(json) {
1597
+ const doc = json;
1598
+ const nodes = doc.nodes ?? [];
1599
+ const rawCoverage = doc.extensions?.MSFT_screencoverage?.scales;
1600
+ const groups = [];
1601
+ for (let rootNode = 0; rootNode < nodes.length; rootNode += 1) {
1602
+ if (nodes[rootNode]?.extensions?.MSFT_lod === void 0) continue;
1603
+ const idsRaw = nodes[rootNode]?.extensions?.MSFT_lod?.ids;
1604
+ if (!Array.isArray(idsRaw)) {
1605
+ return err(gltfErr("gltf-lod-invalid", { rootNode, ids: [], reason: "not-integer" }));
1606
+ }
1607
+ const ids = idsRaw.filter((value) => typeof value === "number");
1608
+ if (ids.length !== idsRaw.length || ids.some((id) => !Number.isInteger(id) || id < 0 || id >= nodes.length)) {
1609
+ return err(gltfErr("gltf-lod-invalid", { rootNode, ids, reason: "missing-node" }));
1610
+ }
1611
+ if (new Set(ids).size !== ids.length || ids.includes(rootNode)) {
1612
+ return err(gltfErr("gltf-lod-invalid", { rootNode, ids, reason: "duplicate-node" }));
1613
+ }
1614
+ const hasMesh = (node) => {
1615
+ if (typeof node?.mesh !== "number" || !Number.isInteger(node.mesh) || node.mesh < 0) {
1616
+ return false;
1617
+ }
1618
+ const mesh = doc.meshes?.[node.mesh];
1619
+ return mesh !== void 0 && Array.isArray(mesh.primitives) && mesh.primitives.length > 0;
1620
+ };
1621
+ if (!hasMesh(nodes[rootNode]) || ids.some((id) => !hasMesh(nodes[id]))) {
1622
+ return err(gltfErr("gltf-lod-invalid", { rootNode, ids, reason: "missing-node" }));
1623
+ }
1624
+ const nodeCoverage = nodes[rootNode]?.extras?.MSFT_screencoverage;
1625
+ const coverage = normalizeScreenCoverages(
1626
+ nodeCoverage === void 0 ? rawCoverage : nodeCoverage,
1627
+ ids.length,
1628
+ nodeCoverage !== void 0
1629
+ );
1630
+ if (coverage === "invalid") {
1631
+ return err(gltfErr("gltf-lod-invalid", { rootNode, ids, reason: "coverage" }));
1632
+ }
1633
+ groups.push({
1634
+ rootNode,
1635
+ lodNodeIds: ids,
1636
+ screenCoverages: coverage ?? []
1637
+ });
1638
+ }
1639
+ const first = groups[0];
1640
+ if (first === void 0) {
1641
+ if (doc.extensionsRequired?.some((extension) => extension === "MSFT_lod")) {
1642
+ return err(gltfErr("gltf-lod-invalid", { rootNode: 0, ids: [], reason: "missing-node" }));
1643
+ }
1644
+ return ok({ rootNode: 0, lodNodeIds: [], screenCoverages: [], groups: [] });
1645
+ }
1646
+ return ok({
1647
+ rootNode: first.rootNode,
1648
+ lodNodeIds: first.lodNodeIds,
1649
+ screenCoverages: first.screenCoverages,
1650
+ groups
1651
+ });
1652
+ }
1653
+ function normalizeScreenCoverages(raw, lodCount, officialNodeForm) {
1654
+ if (raw === void 0) return void 0;
1655
+ if (!Array.isArray(raw)) return "invalid";
1656
+ if (raw.some(
1657
+ (value) => typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > 1
1658
+ )) {
1659
+ return "invalid";
1660
+ }
1661
+ const expectedLengths = officialNodeForm ? [lodCount + 1] : [lodCount, lodCount + 1];
1662
+ if (!expectedLengths.includes(raw.length)) return "invalid";
1663
+ const values = raw.slice(0, lodCount);
1664
+ for (let index = 1; index < values.length; index += 1) {
1665
+ if (values[index] >= values[index - 1]) return "invalid";
1666
+ }
1667
+ return values;
1668
+ }
1669
+ function projectGltfLodMeta(input) {
1670
+ const defaults = deriveDefaultLodScreenCoverages(input.levels.length + 1);
1671
+ const previous = (input.previous ?? []).map((level) => ({
1672
+ sourceKey: level.sourceKey,
1673
+ meshGuid: level.guid,
1674
+ ...level.screenCoverage === void 0 ? {} : { screenCoverage: level.screenCoverage }
1675
+ }));
1676
+ const next = input.levels.map((level, index) => ({
1677
+ sourceKey: level.sourceKey,
1678
+ meshGuid: level.guid,
1679
+ screenCoverage: level.screenCoverage ?? defaults[index] ?? 0
1680
+ }));
1681
+ const reconciled = reconcileMeshLodMeta(previous, next);
1682
+ if (!reconciled.ok) return err({ code: reconciled.error.code, reason: reconciled.error.reason });
1683
+ return ok({
1684
+ lods: reconciled.value.lods.map((level) => ({
1685
+ sourceKey: level.sourceKey,
1686
+ meshGuid: level.meshGuid,
1687
+ screenCoverage: level.screenCoverage
1688
+ }))
1689
+ });
1690
+ }
1691
+
1427
1692
  // src/material/parse-material.ts
1428
1693
  function tuple2(values) {
1429
1694
  if (values === void 0 || values.length < 2) return void 0;
@@ -1468,6 +1733,11 @@ function parseMaterial(matJson, textures) {
1468
1733
  const transmission = matJson.extensions?.KHR_materials_transmission;
1469
1734
  const ior = matJson.extensions?.KHR_materials_ior;
1470
1735
  const volume = matJson.extensions?.KHR_materials_volume;
1736
+ const clearcoat = matJson.extensions?.KHR_materials_clearcoat;
1737
+ const anisotropy = matJson.extensions?.KHR_materials_anisotropy;
1738
+ const sheen = matJson.extensions?.KHR_materials_sheen;
1739
+ const iridescence = matJson.extensions?.KHR_materials_iridescence;
1740
+ const specular = matJson.extensions?.KHR_materials_specular;
1471
1741
  const attenuationColor = tuple3(volume?.attenuationColor);
1472
1742
  const invalid = (extension, field, reason, actual) => err(
1473
1743
  gltfErr("gltf-material-transmission-invalid", {
@@ -1477,6 +1747,14 @@ function parseMaterial(matJson, textures) {
1477
1747
  ...actual === void 0 ? {} : { actual }
1478
1748
  })
1479
1749
  );
1750
+ const physicalInvalid = (extension, field, reason, actual) => err(
1751
+ gltfErr("gltf-material-physical-invalid", {
1752
+ extension,
1753
+ field,
1754
+ reason,
1755
+ ...actual === void 0 ? {} : { actual }
1756
+ })
1757
+ );
1480
1758
  if (transmission?.transmissionFactor !== void 0 && (typeof transmission.transmissionFactor !== "number" || !Number.isFinite(transmission.transmissionFactor))) {
1481
1759
  return invalid(
1482
1760
  "KHR_materials_transmission",
@@ -1527,6 +1805,98 @@ function parseMaterial(matJson, textures) {
1527
1805
  if (alphaMode === "BLEND" && (transmission?.transmissionFactor ?? 0) > 0) {
1528
1806
  return invalid("KHR_materials_transmission", "alphaMode", "blend", alphaMode);
1529
1807
  }
1808
+ if (clearcoat?.clearcoatFactor !== void 0 && (typeof clearcoat.clearcoatFactor !== "number" || !Number.isFinite(clearcoat.clearcoatFactor))) {
1809
+ return physicalInvalid(
1810
+ "KHR_materials_clearcoat",
1811
+ "clearcoatFactor",
1812
+ "non-finite",
1813
+ clearcoat.clearcoatFactor
1814
+ );
1815
+ }
1816
+ if (clearcoat?.clearcoatFactor !== void 0 && (clearcoat.clearcoatFactor < 0 || clearcoat.clearcoatFactor > 1)) {
1817
+ return physicalInvalid(
1818
+ "KHR_materials_clearcoat",
1819
+ "clearcoatFactor",
1820
+ "range",
1821
+ clearcoat.clearcoatFactor
1822
+ );
1823
+ }
1824
+ if (clearcoat?.clearcoatRoughnessFactor !== void 0 && (typeof clearcoat.clearcoatRoughnessFactor !== "number" || !Number.isFinite(clearcoat.clearcoatRoughnessFactor))) {
1825
+ return physicalInvalid(
1826
+ "KHR_materials_clearcoat",
1827
+ "clearcoatRoughnessFactor",
1828
+ "non-finite",
1829
+ clearcoat.clearcoatRoughnessFactor
1830
+ );
1831
+ }
1832
+ if (clearcoat?.clearcoatRoughnessFactor !== void 0 && (clearcoat.clearcoatRoughnessFactor < 0 || clearcoat.clearcoatRoughnessFactor > 1)) {
1833
+ return physicalInvalid(
1834
+ "KHR_materials_clearcoat",
1835
+ "clearcoatRoughnessFactor",
1836
+ "range",
1837
+ clearcoat.clearcoatRoughnessFactor
1838
+ );
1839
+ }
1840
+ if (clearcoat?.clearcoatNormalTexture?.scale !== void 0 && (typeof clearcoat.clearcoatNormalTexture.scale !== "number" || !Number.isFinite(clearcoat.clearcoatNormalTexture.scale) || clearcoat.clearcoatNormalTexture.scale < 0)) {
1841
+ return physicalInvalid(
1842
+ "KHR_materials_clearcoat",
1843
+ "clearcoatNormalTexture.scale",
1844
+ "range",
1845
+ clearcoat.clearcoatNormalTexture.scale
1846
+ );
1847
+ }
1848
+ const validateUnit = (extension, field, value) => {
1849
+ if (value === void 0) return void 0;
1850
+ if (typeof value !== "number" || !Number.isFinite(value))
1851
+ return physicalInvalid(extension, field, "non-finite", value);
1852
+ if (value < 0 || value > 1) return physicalInvalid(extension, field, "range", value);
1853
+ return void 0;
1854
+ };
1855
+ const validateFinite = (extension, field, value, minimum) => {
1856
+ if (value === void 0) return void 0;
1857
+ if (typeof value !== "number" || !Number.isFinite(value))
1858
+ return physicalInvalid(extension, field, "non-finite", value);
1859
+ if (minimum !== void 0 && value < minimum)
1860
+ return physicalInvalid(extension, field, "range", value);
1861
+ return void 0;
1862
+ };
1863
+ const validateColor = (extension, field, value) => {
1864
+ if (value === void 0) return void 0;
1865
+ if (value.length < 3 || value.slice(0, 3).some((channel) => typeof channel !== "number" || !Number.isFinite(channel))) {
1866
+ return physicalInvalid(extension, field, "type", value);
1867
+ }
1868
+ if (value.slice(0, 3).some((channel) => channel < 0 || channel > 1)) {
1869
+ return physicalInvalid(extension, field, "range", value);
1870
+ }
1871
+ return void 0;
1872
+ };
1873
+ const physicalChecks = [
1874
+ validateUnit("KHR_materials_anisotropy", "anisotropyStrength", anisotropy?.anisotropyStrength),
1875
+ validateFinite(
1876
+ "KHR_materials_anisotropy",
1877
+ "anisotropyRotation",
1878
+ anisotropy?.anisotropyRotation
1879
+ ),
1880
+ validateUnit("KHR_materials_sheen", "sheenRoughnessFactor", sheen?.sheenRoughnessFactor),
1881
+ validateColor("KHR_materials_sheen", "sheenColorFactor", sheen?.sheenColorFactor),
1882
+ validateUnit("KHR_materials_iridescence", "iridescenceFactor", iridescence?.iridescenceFactor),
1883
+ validateFinite("KHR_materials_iridescence", "iridescenceIor", iridescence?.iridescenceIor, 1),
1884
+ validateFinite(
1885
+ "KHR_materials_iridescence",
1886
+ "iridescenceThicknessMinimum",
1887
+ iridescence?.iridescenceThicknessMinimum,
1888
+ 0
1889
+ ),
1890
+ validateFinite(
1891
+ "KHR_materials_iridescence",
1892
+ "iridescenceThicknessMaximum",
1893
+ iridescence?.iridescenceThicknessMaximum,
1894
+ 0
1895
+ ),
1896
+ validateUnit("KHR_materials_specular", "specularFactor", specular?.specularFactor),
1897
+ validateColor("KHR_materials_specular", "specularColorFactor", specular?.specularColorFactor)
1898
+ ];
1899
+ for (const check of physicalChecks) if (check !== void 0) return check;
1530
1900
  return ok({
1531
1901
  ...matJson.name === void 0 ? {} : { name: matJson.name },
1532
1902
  baseColorFactor: baseColor4,
@@ -1563,6 +1933,65 @@ function parseMaterial(matJson, textures) {
1563
1933
  ...attenuationColor === void 0 ? {} : { attenuationColor },
1564
1934
  ...finiteOrUndefined(volume.attenuationDistance) === void 0 ? {} : { attenuationDistance: volume.attenuationDistance }
1565
1935
  },
1936
+ ...clearcoat === void 0 ? {} : {
1937
+ clearcoatFactor: clearcoat.clearcoatFactor ?? 0,
1938
+ clearcoatChannel: 0,
1939
+ clearcoatRoughnessFactor: clearcoat.clearcoatRoughnessFactor ?? 0,
1940
+ clearcoatRoughnessChannel: 1,
1941
+ clearcoatNormalChannel: [0, 1],
1942
+ ...clearcoat.clearcoatTexture === void 0 ? {} : { clearcoatTexture: parseTextureInfo(clearcoat.clearcoatTexture, textures) },
1943
+ ...clearcoat.clearcoatRoughnessTexture === void 0 ? {} : {
1944
+ clearcoatRoughnessTexture: parseTextureInfo(
1945
+ clearcoat.clearcoatRoughnessTexture,
1946
+ textures
1947
+ )
1948
+ },
1949
+ ...clearcoat.clearcoatNormalTexture === void 0 ? {} : {
1950
+ clearcoatNormalTexture: {
1951
+ ...parseTextureInfo(clearcoat.clearcoatNormalTexture, textures),
1952
+ ...clearcoat.clearcoatNormalTexture.scale === void 0 ? {} : { scale: clearcoat.clearcoatNormalTexture.scale }
1953
+ }
1954
+ }
1955
+ },
1956
+ ...anisotropy === void 0 ? {} : {
1957
+ anisotropyStrength: anisotropy.anisotropyStrength ?? 0,
1958
+ anisotropyRotation: anisotropy.anisotropyRotation ?? 0,
1959
+ anisotropyChannel: [0, 1, 2],
1960
+ ...anisotropy.anisotropyTexture === void 0 ? {} : { anisotropyTexture: parseTextureInfo(anisotropy.anisotropyTexture, textures) }
1961
+ },
1962
+ ...sheen === void 0 ? {} : {
1963
+ sheenColorFactor: tuple3(sheen.sheenColorFactor) ?? [0, 0, 0],
1964
+ sheenColorChannel: [0, 1, 2],
1965
+ sheenRoughnessFactor: sheen.sheenRoughnessFactor ?? 0,
1966
+ sheenRoughnessChannel: 3,
1967
+ ...sheen.sheenColorTexture === void 0 ? {} : { sheenColorTexture: parseTextureInfo(sheen.sheenColorTexture, textures) },
1968
+ ...sheen.sheenRoughnessTexture === void 0 ? {} : { sheenRoughnessTexture: parseTextureInfo(sheen.sheenRoughnessTexture, textures) }
1969
+ },
1970
+ ...iridescence === void 0 ? {} : {
1971
+ iridescenceFactor: iridescence.iridescenceFactor ?? 0,
1972
+ iridescenceIor: iridescence.iridescenceIor ?? 1.3,
1973
+ iridescenceThicknessMinimum: iridescence.iridescenceThicknessMinimum ?? 100,
1974
+ iridescenceThicknessMaximum: iridescence.iridescenceThicknessMaximum ?? 400,
1975
+ iridescenceChannel: 0,
1976
+ iridescenceThicknessChannel: 1,
1977
+ ...iridescence.iridescenceTexture === void 0 ? {} : { iridescenceTexture: parseTextureInfo(iridescence.iridescenceTexture, textures) },
1978
+ ...iridescence.iridescenceThicknessTexture === void 0 ? {} : {
1979
+ iridescenceThicknessTexture: parseTextureInfo(
1980
+ iridescence.iridescenceThicknessTexture,
1981
+ textures
1982
+ )
1983
+ }
1984
+ },
1985
+ ...specular === void 0 ? {} : {
1986
+ specularFactor: specular.specularFactor ?? 1,
1987
+ specularChannel: 3,
1988
+ specularColorFactor: tuple3(specular.specularColorFactor) ?? [1, 1, 1],
1989
+ specularColorChannel: [0, 1, 2],
1990
+ ...specular.specularTexture === void 0 ? {} : { specularTexture: parseTextureInfo(specular.specularTexture, textures) },
1991
+ ...specular.specularColorTexture === void 0 ? {} : {
1992
+ specularColorTexture: parseTextureInfo(specular.specularColorTexture, textures)
1993
+ }
1994
+ },
1566
1995
  ...alphaMode === void 0 ? {} : { alphaMode },
1567
1996
  ...alphaCutoff === void 0 ? {} : { alphaCutoff },
1568
1997
  ...matJson.doubleSided === true ? { doubleSided: true } : {},
@@ -2336,6 +2765,8 @@ async function parseGltfWithBin(json, ctx) {
2336
2765
  const extResult = checkExtensions(json);
2337
2766
  if (!extResult.ok) return err(extResult.error);
2338
2767
  const unsupportedExtensions = extResult.value.unsupportedUsed;
2768
+ const lodResult = parseGltfLodExtension(json);
2769
+ if (!lodResult.ok) return err(lodResult.error);
2339
2770
  const lightsResult = parsePunctualLights(json, ctx.filePath);
2340
2771
  if (!lightsResult.ok) return err(lightsResult.error);
2341
2772
  const meshesJson = json.meshes ?? [];
@@ -2876,7 +3307,8 @@ async function parseGltfWithBin(json, ctx) {
2876
3307
  matrixTrsCoexistNodes: diagnostics.matrixTrsCoexistNodes
2877
3308
  },
2878
3309
  meshPrimitiveCount,
2879
- lights: lightsResult.value
3310
+ lights: lightsResult.value,
3311
+ ...lodResult.value.lodNodeIds.length === 0 ? {} : { lod: lodResult.value }
2880
3312
  });
2881
3313
  }
2882
3314
  async function parseGltfForImporter(json, externalLoader, filePath, options = {}) {
@@ -3015,6 +3447,74 @@ function toAssetPack(doc, existingMeta, source) {
3015
3447
  const sourceOverrides = {
3016
3448
  ...existingMeta?.sourceOverrides ?? {}
3017
3449
  };
3450
+ const lodGroups = doc.lod === void 0 ? [] : doc.lod.groups.length > 0 ? doc.lod.groups : [doc.lod];
3451
+ for (const lodGroup of lodGroups) {
3452
+ if (lodGroup.lodNodeIds.length === 0) continue;
3453
+ const rootMeshIndex = doc.nodes[lodGroup.rootNode]?.meshIndex;
3454
+ const referencedMeshIndices = [
3455
+ rootMeshIndex,
3456
+ ...lodGroup.lodNodeIds.map((nodeIndex) => doc.nodes[nodeIndex]?.meshIndex)
3457
+ ];
3458
+ if (referencedMeshIndices.some(
3459
+ (meshIndex) => !Number.isInteger(meshIndex) || meshIndex === null || meshIndex === void 0 || !seenMeshIndices.has(meshIndex)
3460
+ )) {
3461
+ return err(
3462
+ gltfErr("gltf-lod-invalid", {
3463
+ rootNode: lodGroup.rootNode,
3464
+ ids: lodGroup.lodNodeIds,
3465
+ reason: "missing-node"
3466
+ })
3467
+ );
3468
+ }
3469
+ const rootOutput = rootMeshIndex === void 0 || rootMeshIndex === null ? void 0 : subAssetByKindIndex.get(`mesh:${rootMeshIndex}`);
3470
+ const levels = lodGroup.lodNodeIds.map((nodeIndex, index) => {
3471
+ const meshIndex = doc.nodes[nodeIndex]?.meshIndex;
3472
+ const output = meshIndex === void 0 || meshIndex === null ? void 0 : subAssetByKindIndex.get(`mesh:${meshIndex}`);
3473
+ return {
3474
+ sourceKey: output?.sourceKey ?? `mesh:${meshIndex ?? nodeIndex}`,
3475
+ meshGuid: output?.guid ?? "",
3476
+ ...lodGroup.screenCoverages[index] === void 0 ? {} : { screenCoverage: lodGroup.screenCoverages[index] }
3477
+ };
3478
+ });
3479
+ if (rootOutput?.sourceKey !== void 0 && levels.every((level) => level.meshGuid !== "")) {
3480
+ const previousLodMeta = (() => {
3481
+ const raw = existingMeta?.sourceOverrides?.[rootOutput.sourceKey]?.lods;
3482
+ if (!Array.isArray(raw)) return void 0;
3483
+ return raw.flatMap((entry) => {
3484
+ if (entry === null || typeof entry !== "object") return [];
3485
+ const value = entry;
3486
+ return typeof value.sourceKey === "string" && typeof value.meshGuid === "string" ? [
3487
+ {
3488
+ sourceKey: value.sourceKey,
3489
+ guid: value.meshGuid,
3490
+ ...typeof value.screenCoverage === "number" ? { screenCoverage: value.screenCoverage } : {}
3491
+ }
3492
+ ] : [];
3493
+ });
3494
+ })();
3495
+ const projected = projectGltfLodMeta({
3496
+ rootSourceKey: rootOutput.sourceKey,
3497
+ levels: levels.map((level) => ({
3498
+ sourceKey: level.sourceKey,
3499
+ guid: level.meshGuid,
3500
+ ...level.screenCoverage === void 0 ? {} : { screenCoverage: level.screenCoverage }
3501
+ })),
3502
+ ...previousLodMeta === void 0 ? {} : { previous: previousLodMeta }
3503
+ });
3504
+ if (!projected.ok)
3505
+ return err(
3506
+ gltfErr("gltf-lod-invalid", {
3507
+ rootNode: lodGroup.rootNode,
3508
+ ids: lodGroup.lodNodeIds,
3509
+ reason: "coverage"
3510
+ })
3511
+ );
3512
+ sourceOverrides[rootOutput.sourceKey] = {
3513
+ ...sourceOverrides[rootOutput.sourceKey] ?? {},
3514
+ lods: projected.value.lods
3515
+ };
3516
+ }
3517
+ }
3018
3518
  for (const meshIndex of seenMeshIndices) {
3019
3519
  const meshOutput = subAssetByKindIndex.get(`mesh:${meshIndex}`);
3020
3520
  if (meshOutput?.sourceKey === void 0) continue;
@@ -3341,6 +3841,16 @@ function materialRefsForPack(mat, doc, textureGuidByIndex, samplerGuidByIndex =
3341
3841
  pushRefsForSlot(mat.emissiveTexture, "emissiveTexture");
3342
3842
  pushRefsForSlot(mat.transmissionTexture, "transmissionTexture");
3343
3843
  pushRefsForSlot(mat.thicknessTexture, "thicknessTexture");
3844
+ pushRefsForSlot(mat.clearcoatTexture, "clearcoatTexture");
3845
+ pushRefsForSlot(mat.clearcoatRoughnessTexture, "clearcoatRoughnessTexture");
3846
+ pushRefsForSlot(mat.clearcoatNormalTexture, "clearcoatNormalTexture");
3847
+ pushRefsForSlot(mat.anisotropyTexture, "anisotropyTexture");
3848
+ pushRefsForSlot(mat.sheenColorTexture, "sheenColorTexture");
3849
+ pushRefsForSlot(mat.sheenRoughnessTexture, "sheenRoughnessTexture");
3850
+ pushRefsForSlot(mat.iridescenceTexture, "iridescenceTexture");
3851
+ pushRefsForSlot(mat.iridescenceThicknessTexture, "iridescenceThicknessTexture");
3852
+ pushRefsForSlot(mat.specularTexture, "specularTexture");
3853
+ pushRefsForSlot(mat.specularColorTexture, "specularColorTexture");
3344
3854
  return refs;
3345
3855
  }
3346
3856
  function availableUvSets(mesh) {
@@ -3361,7 +3871,17 @@ function rewriteMaterialAssetRefs(matAsset, mat, doc, maps) {
3361
3871
  ["occlusionTexture", mat.occlusionTexture],
3362
3872
  ["emissiveTexture", mat.emissiveTexture],
3363
3873
  ["transmissionTexture", mat.transmissionTexture],
3364
- ["thicknessTexture", mat.thicknessTexture]
3874
+ ["thicknessTexture", mat.thicknessTexture],
3875
+ ["clearcoatTexture", mat.clearcoatTexture],
3876
+ ["clearcoatRoughnessTexture", mat.clearcoatRoughnessTexture],
3877
+ ["clearcoatNormalTexture", mat.clearcoatNormalTexture],
3878
+ ["anisotropyTexture", mat.anisotropyTexture],
3879
+ ["sheenColorTexture", mat.sheenColorTexture],
3880
+ ["sheenRoughnessTexture", mat.sheenRoughnessTexture],
3881
+ ["iridescenceTexture", mat.iridescenceTexture],
3882
+ ["iridescenceThicknessTexture", mat.iridescenceThicknessTexture],
3883
+ ["specularTexture", mat.specularTexture],
3884
+ ["specularColorTexture", mat.specularColorTexture]
3365
3885
  ];
3366
3886
  let cursor = 0;
3367
3887
  for (const [slot, rawBinding] of slots) {
@@ -3456,7 +3976,55 @@ async function importGltf(ctx, meshopt) {
3456
3976
  })
3457
3977
  };
3458
3978
  }
3459
- const meshPayload = stabilizeMeshMaterialSlots(bridged.value, ctx, sub.guid, sub.sourceKey);
3979
+ const stabilizedMesh = stabilizeMeshMaterialSlots(
3980
+ bridged.value,
3981
+ ctx,
3982
+ sub.guid,
3983
+ sub.sourceKey
3984
+ );
3985
+ const lodGroup = doc.lod?.groups?.find(
3986
+ (group) => doc.nodes[group.rootNode]?.meshIndex === sub.sourceIndex
3987
+ ) ?? (doc.lod?.rootNode !== void 0 && doc.nodes[doc.lod.rootNode]?.meshIndex === sub.sourceIndex ? doc.lod : void 0);
3988
+ const rootMeshIndex = lodGroup === void 0 ? void 0 : sub.sourceIndex;
3989
+ if (lodGroup !== void 0) {
3990
+ const referencedMeshIndices = [
3991
+ sub.sourceIndex,
3992
+ ...lodGroup.lodNodeIds.map((nodeIndex) => doc.nodes[nodeIndex]?.meshIndex)
3993
+ ];
3994
+ const missingMeshIndex = referencedMeshIndices.find(
3995
+ (meshIndex) => !Number.isInteger(meshIndex) || meshIndex === null || meshIndex === void 0 || maps.meshGuidByIndex.get(meshIndex) === void 0
3996
+ );
3997
+ if (missingMeshIndex !== void 0) {
3998
+ return {
3999
+ ok: false,
4000
+ error: new ImportError({
4001
+ code: "import-internal-error",
4002
+ expected: "every MSFT_lod node to resolve a cooked mesh sub-asset",
4003
+ actual: String(missingMeshIndex),
4004
+ hint: "repair the referenced glTF mesh primitives and re-run the importer",
4005
+ detail: {
4006
+ reason: `MSFT_lod group for mesh ${sub.sourceIndex} references an unprojected mesh`
4007
+ }
4008
+ })
4009
+ };
4010
+ }
4011
+ }
4012
+ const authoredLods = sub.sourceKey === void 0 ? void 0 : ctx.sourceOverrides?.[sub.sourceKey]?.lods;
4013
+ const lodEntries = Array.isArray(authoredLods) ? authoredLods : [];
4014
+ const lodLevels = rootMeshIndex === sub.sourceIndex && lodGroup !== void 0 ? lodGroup.lodNodeIds.flatMap((nodeIndex, index) => {
4015
+ const meshIndex = doc.nodes[nodeIndex]?.meshIndex;
4016
+ const guid = meshIndex === void 0 || meshIndex === null ? void 0 : maps.meshGuidByIndex.get(meshIndex);
4017
+ if (guid === void 0) return [];
4018
+ const authored = lodEntries[index];
4019
+ const authoredCoverage = authored !== null && typeof authored === "object" ? authored.screenCoverage : void 0;
4020
+ const coverage = typeof authoredCoverage === "number" ? authoredCoverage : lodGroup.screenCoverages[index] ?? deriveDefaultLodScreenCoverages(lodGroup.lodNodeIds.length + 1)[index];
4021
+ const parsed2 = AssetGuid.parse(guid);
4022
+ return parsed2.ok && coverage !== void 0 ? [{ mesh: parsed2.value, screenCoverage: coverage, guid }] : [];
4023
+ }) : [];
4024
+ const meshPayload = lodLevels.length === 0 ? stabilizedMesh : {
4025
+ ...stabilizedMesh,
4026
+ lods: lodLevels.map(({ mesh, screenCoverage }) => ({ mesh, screenCoverage }))
4027
+ };
3460
4028
  const materialRefs = [];
3461
4029
  const seenMaterialGuids = /* @__PURE__ */ new Set();
3462
4030
  for (let slotIndex = 0; slotIndex < meshPayload.materialSlots.length; slotIndex++) {
@@ -3470,6 +4038,15 @@ async function importGltf(ctx, meshopt) {
3470
4038
  });
3471
4039
  }
3472
4040
  }
4041
+ for (const [lodIndex, level] of lodLevels.entries()) {
4042
+ if (!seenMaterialGuids.has(level.guid.toLowerCase())) {
4043
+ seenMaterialGuids.add(level.guid.toLowerCase());
4044
+ materialRefs.push({
4045
+ guid: level.guid,
4046
+ sourceField: { fieldName: "lods", arrayIndex: lodIndex }
4047
+ });
4048
+ }
4049
+ }
3473
4050
  out.push({
3474
4051
  guid: sub.guid,
3475
4052
  kind: "mesh",
@@ -3521,6 +4098,10 @@ async function importGltf(ctx, meshopt) {
3521
4098
  if (!uvResult.ok) {
3522
4099
  throw Object.assign(new Error(uvResult.error.message), uvResult.error);
3523
4100
  }
4101
+ const tangentResult = validateMaterialTangentInputs(mat, meshIr);
4102
+ if (!tangentResult.ok) {
4103
+ throw Object.assign(new Error(tangentResult.error.message), tangentResult.error);
4104
+ }
3524
4105
  }
3525
4106
  const matAsset = toMaterialAsset(mat, {
3526
4107
  textureHandles: maps.textureHandles,
@@ -3773,6 +4354,6 @@ function serializeMetaJson(meta) {
3773
4354
  `;
3774
4355
  }
3775
4356
 
3776
- export { COMPONENT_TYPE, EXTENSION_ALLOWLIST, GLTF_ERROR_HINTS, checkExtensions, decodeAccessor, decodeColorAccessor, decomposeNodeTransform, deriveGltfSourceKeys, gltfDocToSceneAsset, gltfErr, gltfImporter, meshIrToMeshAsset, parseGlb, parseGlbChunks, parseGltf, parseGltfHeader, reimportReuseMeta, serializeMetaJson, sourceKeyForGltfOutput, subAssetKey, toAssetPack, toMaterialAsset };
4357
+ export { COMPONENT_TYPE, EXTENSION_ALLOWLIST, GLTF_ERROR_HINTS, checkExtensions, decodeAccessor, decodeColorAccessor, decomposeNodeTransform, deriveGltfSourceKeys, gltfDocToSceneAsset, gltfErr, gltfImporter, meshIrToMeshAsset, parseGlb, parseGlbChunks, parseGltf, parseGltfHeader, parseGltfLodExtension, projectGltfLodMeta, reimportReuseMeta, serializeMetaJson, sourceKeyForGltfOutput, subAssetKey, toAssetPack, toMaterialAsset, validateMaterialTangentInputs, validateMaterialUvSets };
3777
4358
  //# sourceMappingURL=index.mjs.map
3778
4359
  //# sourceMappingURL=index.mjs.map