@forgeax/engine-gltf 0.1.20 → 0.1.23

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 (49) hide show
  1. package/README.md +59 -0
  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/check-extensions.d.ts.map +1 -1
  7. package/dist/cli-gltf.mjs +183 -8
  8. package/dist/cli-gltf.mjs.map +1 -1
  9. package/dist/errors.d.ts +6 -0
  10. package/dist/errors.d.ts.map +1 -1
  11. package/dist/gltf-importer.d.ts.map +1 -1
  12. package/dist/importer-entry.mjs +145 -3
  13. package/dist/importer-entry.mjs.map +1 -1
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.mjs +238 -6
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/lod/parse-lod.d.ts +15 -0
  19. package/dist/lod/parse-lod.d.ts.map +1 -0
  20. package/dist/lod/project-meta.d.ts +23 -0
  21. package/dist/lod/project-meta.d.ts.map +1 -0
  22. package/dist/node-file-entry.mjs +86 -2
  23. package/dist/node-file-entry.mjs.map +1 -1
  24. package/dist/parse-gltf.d.ts +3 -1
  25. package/dist/parse-gltf.d.ts.map +1 -1
  26. package/dist/reimport-reuse-meta.d.ts +1 -1
  27. package/dist/reimport-reuse-meta.d.ts.map +1 -1
  28. package/package.json +12 -12
  29. package/src/__tests__/fixtures/lod/core-only.gltf +1 -0
  30. package/src/__tests__/fixtures/lod/msft-lod-malformed.gltf +1 -0
  31. package/src/__tests__/fixtures/lod/msft-lod-multi-root.gltf +1 -0
  32. package/src/__tests__/fixtures/lod/msft-lod-node-extras.gltf +1 -0
  33. package/src/__tests__/fixtures/lod/msft-lod.gltf +1 -0
  34. package/src/__tests__/gltf-error-derived-views.test-d.ts +4 -0
  35. package/src/__tests__/gltf.unit.test.ts +12 -3
  36. package/src/__tests__/lod-import.integration.test.ts +106 -0
  37. package/src/__tests__/lod-meta-publication.integration.test.ts +24 -0
  38. package/src/__tests__/parse-gltf.unit.test.ts +22 -0
  39. package/src/__tests__/source-key-producer.integration.test.ts +6 -2
  40. package/src/bridge.ts +1 -1
  41. package/src/check-extensions.ts +2 -0
  42. package/src/errors.ts +11 -0
  43. package/src/gltf-importer.ts +87 -1
  44. package/src/index.ts +2 -0
  45. package/src/lod/parse-lod.ts +129 -0
  46. package/src/lod/project-meta.ts +48 -0
  47. package/src/parse-gltf.ts +97 -1
  48. package/src/reimport-reuse-meta.ts +3 -1
  49. package/dist/.tsbuildinfo +0 -1
package/dist/index.d.ts CHANGED
@@ -10,6 +10,8 @@ export { checkExtensions, EXTENSION_ALLOWLIST } from './check-extensions.js';
10
10
  export type { GltfAccessorTypeMismatchDetail, GltfAnimationCubicsplineUnsupportedDetail, GltfBufferOutOfBoundsDetail, GltfColorAccessorMalformedDetail, GltfColorAccessorUnsupportedDetail, GltfError, GltfErrorCode, GltfErrorDetail, GltfExtensionUnsupportedDetail, GltfImageExtractFailedDetail, GltfImageMimeUnsupportedDetail, GltfInstancingCountMismatchDetail, GltfMalformedHeaderDetail, GltfMaterialTransmissionInvalidDetail, GltfMetaMissingDetail, GltfMorphInvalidDetail, GltfMorphUnsupportedDetail, GltfSkinAttrAsymmetricDetail, GltfSkinJointCountExceededDetail, GltfSkinJointNameMissingDetail, GltfTextureLoadFailedDetail, GltfVersionUnsupportedDetail, Result, } from './errors.js';
11
11
  export { err, GLTF_ERROR_HINTS, gltfErr, ok } from './errors.js';
12
12
  export { gltfImporter } from './gltf-importer.js';
13
+ export { type GltfLodRelation, parseGltfLodExtension } from './lod/parse-lod.js';
14
+ export { projectGltfLodMeta } from './lod/project-meta.js';
13
15
  export type { GlbChunks } from './parse-glb-chunks.js';
14
16
  export { parseGlbChunks, parseGltfHeader } from './parse-glb-chunks.js';
15
17
  export type { ExternalLoader, GltfAssetPack, GltfAssetPackResult, GltfDiagnosticsIr, GltfDoc, GltfImageIr, GltfMaterialIr, GltfMeshIr, GltfNodeIr, GltfNormalTextureInfoIr, GltfOcclusionTextureInfoIr, GltfSamplerIr, GltfSceneIr, GltfTextureInfoIr, GltfTextureIr, GltfTextureTransformIr, MeshJson, MeshPrimitiveJson, NodeInstancingIr, } from './parse-gltf.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkBA,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,WAAW,EACX,OAAO,EACP,cAAc,EACd,cAAc,EACd,QAAQ,EACR,WAAW,EACX,OAAO,EACP,sBAAsB,EACtB,UAAU,GACX,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,eAAe,GAChB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/E,YAAY,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAK5E,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACtF,YAAY,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEvF,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC7E,YAAY,EACV,8BAA8B,EAC9B,yCAAyC,EACzC,2BAA2B,EAC3B,gCAAgC,EAChC,kCAAkC,EAClC,SAAS,EACT,aAAa,EACb,eAAe,EACf,8BAA8B,EAC9B,4BAA4B,EAC5B,8BAA8B,EAC9B,iCAAiC,EACjC,yBAAyB,EACzB,qCAAqC,EACrC,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC1B,4BAA4B,EAC5B,gCAAgC,EAChC,8BAA8B,EAC9B,2BAA2B,EAC3B,4BAA4B,EAC5B,MAAM,GACP,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAGjE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxE,YAAY,EACV,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,iBAAiB,EACjB,OAAO,EACP,WAAW,EACX,cAAc,EACd,UAAU,EACV,UAAU,EACV,uBAAuB,EACvB,0BAA0B,EAC1B,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,sBAAsB,EACtB,QAAQ,EACR,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AASnE,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,YAAY,EACV,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,GACZ,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,iBAAiB,EACjB,WAAW,GACZ,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EACV,0BAA0B,EAC1B,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,YAAY,EACV,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,EACT,SAAS,GACV,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -3,6 +3,7 @@ export { err, ok } from '@forgeax/engine-types';
3
3
  import { 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
 
@@ -26,6 +27,10 @@ var gltfErrorPolicy = {
26
27
  expected: "extension listed in v1 allowlist (see EXTENSION_ALLOWLIST in @forgeax/engine-gltf)",
27
28
  hint: "see feat-future-gltf-extensions-allowlist; remove this extension or wait for the allowlist to expand"
28
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"
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"
@@ -966,7 +971,9 @@ function toMaterialAsset(mat, ctx) {
966
971
  // src/check-extensions.ts
967
972
  var EXTENSION_ALLOWLIST = [
968
973
  "EXT_mesh_gpu_instancing",
969
- "EXT_meshopt_compression"
974
+ "EXT_meshopt_compression",
975
+ "MSFT_lod",
976
+ "MSFT_screencoverage"
970
977
  ];
971
978
  var SUPPORTED_EXTENSIONS = [
972
979
  ...EXTENSION_ALLOWLIST,
@@ -1169,8 +1176,8 @@ function parseGlbChunks(buffer, filePath) {
1169
1176
 
1170
1177
  // src/extract-image-bytes.ts
1171
1178
  var DATA_URI_MIME_RE = /^data:([^;,]+)(?:;[^,;]+)*;base64,/;
1172
- function describeImportError(err2) {
1173
- return err2.message;
1179
+ function describeImportError(err3) {
1180
+ return err3.message;
1174
1181
  }
1175
1182
  function classifyMime(declaredMime, bytes) {
1176
1183
  if (declaredMime === "image/png" || declaredMime === "image/jpeg") return declaredMime;
@@ -1424,6 +1431,103 @@ function deriveTextureColorSpace(input) {
1424
1431
  return result;
1425
1432
  }
1426
1433
 
1434
+ // src/lod/parse-lod.ts
1435
+ function parseGltfLodExtension(json) {
1436
+ const doc = json;
1437
+ const nodes = doc.nodes ?? [];
1438
+ const rawCoverage = doc.extensions?.MSFT_screencoverage?.scales;
1439
+ const groups = [];
1440
+ for (let rootNode = 0; rootNode < nodes.length; rootNode += 1) {
1441
+ if (nodes[rootNode]?.extensions?.MSFT_lod === void 0) continue;
1442
+ const idsRaw = nodes[rootNode]?.extensions?.MSFT_lod?.ids;
1443
+ if (!Array.isArray(idsRaw)) {
1444
+ return err(gltfErr("gltf-lod-invalid", { rootNode, ids: [], reason: "not-integer" }));
1445
+ }
1446
+ const ids = idsRaw.filter((value) => typeof value === "number");
1447
+ if (ids.length !== idsRaw.length || ids.some((id) => !Number.isInteger(id) || id < 0 || id >= nodes.length)) {
1448
+ return err(gltfErr("gltf-lod-invalid", { rootNode, ids, reason: "missing-node" }));
1449
+ }
1450
+ if (new Set(ids).size !== ids.length || ids.includes(rootNode)) {
1451
+ return err(gltfErr("gltf-lod-invalid", { rootNode, ids, reason: "duplicate-node" }));
1452
+ }
1453
+ const hasMesh = (node) => {
1454
+ if (typeof node?.mesh !== "number" || !Number.isInteger(node.mesh) || node.mesh < 0) {
1455
+ return false;
1456
+ }
1457
+ const mesh = doc.meshes?.[node.mesh];
1458
+ return mesh !== void 0 && Array.isArray(mesh.primitives) && mesh.primitives.length > 0;
1459
+ };
1460
+ if (!hasMesh(nodes[rootNode]) || ids.some((id) => !hasMesh(nodes[id]))) {
1461
+ return err(gltfErr("gltf-lod-invalid", { rootNode, ids, reason: "missing-node" }));
1462
+ }
1463
+ const nodeCoverage = nodes[rootNode]?.extras?.MSFT_screencoverage;
1464
+ const coverage = normalizeScreenCoverages(
1465
+ nodeCoverage === void 0 ? rawCoverage : nodeCoverage,
1466
+ ids.length,
1467
+ nodeCoverage !== void 0
1468
+ );
1469
+ if (coverage === "invalid") {
1470
+ return err(gltfErr("gltf-lod-invalid", { rootNode, ids, reason: "coverage" }));
1471
+ }
1472
+ groups.push({
1473
+ rootNode,
1474
+ lodNodeIds: ids,
1475
+ screenCoverages: coverage ?? []
1476
+ });
1477
+ }
1478
+ const first = groups[0];
1479
+ if (first === void 0) {
1480
+ if (doc.extensionsRequired?.some((extension) => extension === "MSFT_lod")) {
1481
+ return err(gltfErr("gltf-lod-invalid", { rootNode: 0, ids: [], reason: "missing-node" }));
1482
+ }
1483
+ return ok({ rootNode: 0, lodNodeIds: [], screenCoverages: [], groups: [] });
1484
+ }
1485
+ return ok({
1486
+ rootNode: first.rootNode,
1487
+ lodNodeIds: first.lodNodeIds,
1488
+ screenCoverages: first.screenCoverages,
1489
+ groups
1490
+ });
1491
+ }
1492
+ function normalizeScreenCoverages(raw, lodCount, officialNodeForm) {
1493
+ if (raw === void 0) return void 0;
1494
+ if (!Array.isArray(raw)) return "invalid";
1495
+ if (raw.some(
1496
+ (value) => typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > 1
1497
+ )) {
1498
+ return "invalid";
1499
+ }
1500
+ const expectedLengths = officialNodeForm ? [lodCount + 1] : [lodCount, lodCount + 1];
1501
+ if (!expectedLengths.includes(raw.length)) return "invalid";
1502
+ const values = raw.slice(0, lodCount);
1503
+ for (let index = 1; index < values.length; index += 1) {
1504
+ if (values[index] >= values[index - 1]) return "invalid";
1505
+ }
1506
+ return values;
1507
+ }
1508
+ function projectGltfLodMeta(input) {
1509
+ const defaults = deriveDefaultLodScreenCoverages(input.levels.length + 1);
1510
+ const previous = (input.previous ?? []).map((level) => ({
1511
+ sourceKey: level.sourceKey,
1512
+ meshGuid: level.guid,
1513
+ ...level.screenCoverage === void 0 ? {} : { screenCoverage: level.screenCoverage }
1514
+ }));
1515
+ const next = input.levels.map((level, index) => ({
1516
+ sourceKey: level.sourceKey,
1517
+ meshGuid: level.guid,
1518
+ screenCoverage: level.screenCoverage ?? defaults[index] ?? 0
1519
+ }));
1520
+ const reconciled = reconcileMeshLodMeta(previous, next);
1521
+ if (!reconciled.ok) return err({ code: reconciled.error.code, reason: reconciled.error.reason });
1522
+ return ok({
1523
+ lods: reconciled.value.lods.map((level) => ({
1524
+ sourceKey: level.sourceKey,
1525
+ meshGuid: level.meshGuid,
1526
+ screenCoverage: level.screenCoverage
1527
+ }))
1528
+ });
1529
+ }
1530
+
1427
1531
  // src/material/parse-material.ts
1428
1532
  function tuple2(values) {
1429
1533
  if (values === void 0 || values.length < 2) return void 0;
@@ -2336,6 +2440,8 @@ async function parseGltfWithBin(json, ctx) {
2336
2440
  const extResult = checkExtensions(json);
2337
2441
  if (!extResult.ok) return err(extResult.error);
2338
2442
  const unsupportedExtensions = extResult.value.unsupportedUsed;
2443
+ const lodResult = parseGltfLodExtension(json);
2444
+ if (!lodResult.ok) return err(lodResult.error);
2339
2445
  const lightsResult = parsePunctualLights(json, ctx.filePath);
2340
2446
  if (!lightsResult.ok) return err(lightsResult.error);
2341
2447
  const meshesJson = json.meshes ?? [];
@@ -2876,7 +2982,8 @@ async function parseGltfWithBin(json, ctx) {
2876
2982
  matrixTrsCoexistNodes: diagnostics.matrixTrsCoexistNodes
2877
2983
  },
2878
2984
  meshPrimitiveCount,
2879
- lights: lightsResult.value
2985
+ lights: lightsResult.value,
2986
+ ...lodResult.value.lodNodeIds.length === 0 ? {} : { lod: lodResult.value }
2880
2987
  });
2881
2988
  }
2882
2989
  async function parseGltfForImporter(json, externalLoader, filePath, options = {}) {
@@ -3015,6 +3122,74 @@ function toAssetPack(doc, existingMeta, source) {
3015
3122
  const sourceOverrides = {
3016
3123
  ...existingMeta?.sourceOverrides ?? {}
3017
3124
  };
3125
+ const lodGroups = doc.lod === void 0 ? [] : doc.lod.groups.length > 0 ? doc.lod.groups : [doc.lod];
3126
+ for (const lodGroup of lodGroups) {
3127
+ if (lodGroup.lodNodeIds.length === 0) continue;
3128
+ const rootMeshIndex = doc.nodes[lodGroup.rootNode]?.meshIndex;
3129
+ const referencedMeshIndices = [
3130
+ rootMeshIndex,
3131
+ ...lodGroup.lodNodeIds.map((nodeIndex) => doc.nodes[nodeIndex]?.meshIndex)
3132
+ ];
3133
+ if (referencedMeshIndices.some(
3134
+ (meshIndex) => !Number.isInteger(meshIndex) || meshIndex === null || meshIndex === void 0 || !seenMeshIndices.has(meshIndex)
3135
+ )) {
3136
+ return err(
3137
+ gltfErr("gltf-lod-invalid", {
3138
+ rootNode: lodGroup.rootNode,
3139
+ ids: lodGroup.lodNodeIds,
3140
+ reason: "missing-node"
3141
+ })
3142
+ );
3143
+ }
3144
+ const rootOutput = rootMeshIndex === void 0 || rootMeshIndex === null ? void 0 : subAssetByKindIndex.get(`mesh:${rootMeshIndex}`);
3145
+ const levels = lodGroup.lodNodeIds.map((nodeIndex, index) => {
3146
+ const meshIndex = doc.nodes[nodeIndex]?.meshIndex;
3147
+ const output = meshIndex === void 0 || meshIndex === null ? void 0 : subAssetByKindIndex.get(`mesh:${meshIndex}`);
3148
+ return {
3149
+ sourceKey: output?.sourceKey ?? `mesh:${meshIndex ?? nodeIndex}`,
3150
+ meshGuid: output?.guid ?? "",
3151
+ ...lodGroup.screenCoverages[index] === void 0 ? {} : { screenCoverage: lodGroup.screenCoverages[index] }
3152
+ };
3153
+ });
3154
+ if (rootOutput?.sourceKey !== void 0 && levels.every((level) => level.meshGuid !== "")) {
3155
+ const previousLodMeta = (() => {
3156
+ const raw = existingMeta?.sourceOverrides?.[rootOutput.sourceKey]?.lods;
3157
+ if (!Array.isArray(raw)) return void 0;
3158
+ return raw.flatMap((entry) => {
3159
+ if (entry === null || typeof entry !== "object") return [];
3160
+ const value = entry;
3161
+ return typeof value.sourceKey === "string" && typeof value.meshGuid === "string" ? [
3162
+ {
3163
+ sourceKey: value.sourceKey,
3164
+ guid: value.meshGuid,
3165
+ ...typeof value.screenCoverage === "number" ? { screenCoverage: value.screenCoverage } : {}
3166
+ }
3167
+ ] : [];
3168
+ });
3169
+ })();
3170
+ const projected = projectGltfLodMeta({
3171
+ rootSourceKey: rootOutput.sourceKey,
3172
+ levels: levels.map((level) => ({
3173
+ sourceKey: level.sourceKey,
3174
+ guid: level.meshGuid,
3175
+ ...level.screenCoverage === void 0 ? {} : { screenCoverage: level.screenCoverage }
3176
+ })),
3177
+ ...previousLodMeta === void 0 ? {} : { previous: previousLodMeta }
3178
+ });
3179
+ if (!projected.ok)
3180
+ return err(
3181
+ gltfErr("gltf-lod-invalid", {
3182
+ rootNode: lodGroup.rootNode,
3183
+ ids: lodGroup.lodNodeIds,
3184
+ reason: "coverage"
3185
+ })
3186
+ );
3187
+ sourceOverrides[rootOutput.sourceKey] = {
3188
+ ...sourceOverrides[rootOutput.sourceKey] ?? {},
3189
+ lods: projected.value.lods
3190
+ };
3191
+ }
3192
+ }
3018
3193
  for (const meshIndex of seenMeshIndices) {
3019
3194
  const meshOutput = subAssetByKindIndex.get(`mesh:${meshIndex}`);
3020
3195
  if (meshOutput?.sourceKey === void 0) continue;
@@ -3456,7 +3631,55 @@ async function importGltf(ctx, meshopt) {
3456
3631
  })
3457
3632
  };
3458
3633
  }
3459
- const meshPayload = stabilizeMeshMaterialSlots(bridged.value, ctx, sub.guid, sub.sourceKey);
3634
+ const stabilizedMesh = stabilizeMeshMaterialSlots(
3635
+ bridged.value,
3636
+ ctx,
3637
+ sub.guid,
3638
+ sub.sourceKey
3639
+ );
3640
+ const lodGroup = doc.lod?.groups?.find(
3641
+ (group) => doc.nodes[group.rootNode]?.meshIndex === sub.sourceIndex
3642
+ ) ?? (doc.lod?.rootNode !== void 0 && doc.nodes[doc.lod.rootNode]?.meshIndex === sub.sourceIndex ? doc.lod : void 0);
3643
+ const rootMeshIndex = lodGroup === void 0 ? void 0 : sub.sourceIndex;
3644
+ if (lodGroup !== void 0) {
3645
+ const referencedMeshIndices = [
3646
+ sub.sourceIndex,
3647
+ ...lodGroup.lodNodeIds.map((nodeIndex) => doc.nodes[nodeIndex]?.meshIndex)
3648
+ ];
3649
+ const missingMeshIndex = referencedMeshIndices.find(
3650
+ (meshIndex) => !Number.isInteger(meshIndex) || meshIndex === null || meshIndex === void 0 || maps.meshGuidByIndex.get(meshIndex) === void 0
3651
+ );
3652
+ if (missingMeshIndex !== void 0) {
3653
+ return {
3654
+ ok: false,
3655
+ error: new ImportError({
3656
+ code: "import-internal-error",
3657
+ expected: "every MSFT_lod node to resolve a cooked mesh sub-asset",
3658
+ actual: String(missingMeshIndex),
3659
+ hint: "repair the referenced glTF mesh primitives and re-run the importer",
3660
+ detail: {
3661
+ reason: `MSFT_lod group for mesh ${sub.sourceIndex} references an unprojected mesh`
3662
+ }
3663
+ })
3664
+ };
3665
+ }
3666
+ }
3667
+ const authoredLods = sub.sourceKey === void 0 ? void 0 : ctx.sourceOverrides?.[sub.sourceKey]?.lods;
3668
+ const lodEntries = Array.isArray(authoredLods) ? authoredLods : [];
3669
+ const lodLevels = rootMeshIndex === sub.sourceIndex && lodGroup !== void 0 ? lodGroup.lodNodeIds.flatMap((nodeIndex, index) => {
3670
+ const meshIndex = doc.nodes[nodeIndex]?.meshIndex;
3671
+ const guid = meshIndex === void 0 || meshIndex === null ? void 0 : maps.meshGuidByIndex.get(meshIndex);
3672
+ if (guid === void 0) return [];
3673
+ const authored = lodEntries[index];
3674
+ const authoredCoverage = authored !== null && typeof authored === "object" ? authored.screenCoverage : void 0;
3675
+ const coverage = typeof authoredCoverage === "number" ? authoredCoverage : lodGroup.screenCoverages[index] ?? deriveDefaultLodScreenCoverages(lodGroup.lodNodeIds.length + 1)[index];
3676
+ const parsed2 = AssetGuid.parse(guid);
3677
+ return parsed2.ok && coverage !== void 0 ? [{ mesh: parsed2.value, screenCoverage: coverage, guid }] : [];
3678
+ }) : [];
3679
+ const meshPayload = lodLevels.length === 0 ? stabilizedMesh : {
3680
+ ...stabilizedMesh,
3681
+ lods: lodLevels.map(({ mesh, screenCoverage }) => ({ mesh, screenCoverage }))
3682
+ };
3460
3683
  const materialRefs = [];
3461
3684
  const seenMaterialGuids = /* @__PURE__ */ new Set();
3462
3685
  for (let slotIndex = 0; slotIndex < meshPayload.materialSlots.length; slotIndex++) {
@@ -3470,6 +3693,15 @@ async function importGltf(ctx, meshopt) {
3470
3693
  });
3471
3694
  }
3472
3695
  }
3696
+ for (const [lodIndex, level] of lodLevels.entries()) {
3697
+ if (!seenMaterialGuids.has(level.guid.toLowerCase())) {
3698
+ seenMaterialGuids.add(level.guid.toLowerCase());
3699
+ materialRefs.push({
3700
+ guid: level.guid,
3701
+ sourceField: { fieldName: "lods", arrayIndex: lodIndex }
3702
+ });
3703
+ }
3704
+ }
3473
3705
  out.push({
3474
3706
  guid: sub.guid,
3475
3707
  kind: "mesh",
@@ -3773,6 +4005,6 @@ function serializeMetaJson(meta) {
3773
4005
  `;
3774
4006
  }
3775
4007
 
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 };
4008
+ 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 };
3777
4009
  //# sourceMappingURL=index.mjs.map
3778
4010
  //# sourceMappingURL=index.mjs.map