@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
@@ -1,6 +1,7 @@
1
1
  import { ImportError } from '@forgeax/engine-types';
2
2
  import { COMPONENT_TYPE } from './accessor/decode-accessor.js';
3
3
  import { type GltfError, type Result } from './errors.js';
4
+ import { type GltfLodRelation } from './lod/parse-lod.js';
4
5
  import { type GltfImageIr, type GltfMaterialIr, type GltfSamplerIr, type GltfTextureIr } from './material/parse-material.js';
5
6
  import { type GltfBufferViewDecodeCapability } from './meshopt-decode.js';
6
7
  import { type GltfAnimationClipRecord } from './parse-animation.js';
@@ -129,6 +130,7 @@ export interface GltfDoc {
129
130
  * fixtures; producers from parseGltf / parseGlb always populate it.
130
131
  */
131
132
  readonly meshPrimitiveCount?: ReadonlyMap<number, number>;
133
+ readonly lod?: GltfLodRelation;
132
134
  }
133
135
  export type ExternalLoader = (uri: string) => Promise<ArrayBuffer>;
134
136
  type GltfParseError = GltfError | ImportError;
@@ -156,7 +158,7 @@ export interface GltfAssetPack {
156
158
  readonly meta: GltfMetaJson;
157
159
  readonly subAssets: readonly GltfSubAssetEntry[];
158
160
  }
159
- export type GltfAssetPackResult = Result<GltfAssetPack, GltfSourceKeyError>;
161
+ export type GltfAssetPackResult = Result<GltfAssetPack, GltfSourceKeyError | GltfError>;
160
162
  /**
161
163
  * Project a parsed `GltfDoc` into the disk-shape `<source>.meta.json` plus
162
164
  * the freshly minted `subAssets[]` list (UUIDv7 + reimport-reuse-meta
@@ -1 +1 @@
1
- {"version":3,"file":"parse-gltf.d.ts","sourceRoot":"","sources":["../src/parse-gltf.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parse-gltf.d.ts","sourceRoot":"","sources":["../src/parse-gltf.ts"],"names":[],"mappings":"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"}
@@ -19,7 +19,7 @@ export interface GltfMetaJson {
19
19
  readonly importer: 'gltf';
20
20
  readonly source: string;
21
21
  readonly subAssets: readonly GltfSubAssetEntry[];
22
- readonly sourceOverrides?: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
22
+ readonly sourceOverrides?: Readonly<Record<string, Readonly<Record<string, unknown>>>> | undefined;
23
23
  readonly sourceOverrideDescriptors?: readonly import('@forgeax/engine-types').SourceOverrideDescriptor[];
24
24
  readonly importSettings: {
25
25
  readonly defaultSceneIndex: number;
@@ -1 +1 @@
1
- {"version":3,"file":"reimport-reuse-meta.d.ts","sourceRoot":"","sources":["../src/reimport-reuse-meta.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAEL,KAAK,kBAAkB,EAExB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,eAAe,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEvE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC;AAE1C,gGAAgG;AAChG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACjD,QAAQ,CAAC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACvF,QAAQ,CAAC,yBAAyB,CAAC,EAAE,SAAS,OAAO,uBAAuB,EAAE,wBAAwB,EAAE,CAAC;IACzG,QAAQ,CAAC,cAAc,EAAE;QACvB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;QACnC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;QACvC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;QACxC,QAAQ,CAAC,WAAW,EAAE;YACpB,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;YACtC,QAAQ,CAAC,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;YAClD,QAAQ,CAAC,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;SACnD,CAAC;KACH,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAClD;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAAC;AAEjF;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,YAAY,EAAE,YAAY,GAAG,SAAS,GACrC,mBAAmB,CAmDrB"}
1
+ {"version":3,"file":"reimport-reuse-meta.d.ts","sourceRoot":"","sources":["../src/reimport-reuse-meta.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAEL,KAAK,kBAAkB,EAExB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,eAAe,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEvE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,CAAC;AACvB,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC;AAE1C,gGAAgG;AAChG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACjD,QAAQ,CAAC,eAAe,CAAC,EACrB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,GAC3D,SAAS,CAAC;IACd,QAAQ,CAAC,yBAAyB,CAAC,EAAE,SAAS,OAAO,uBAAuB,EAAE,wBAAwB,EAAE,CAAC;IACzG,QAAQ,CAAC,cAAc,EAAE;QACvB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;QACnC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;QACvC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;QACxC,QAAQ,CAAC,WAAW,EAAE;YACpB,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;YACtC,QAAQ,CAAC,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;YAClD,QAAQ,CAAC,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;SACnD,CAAC;KACH,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAClD;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAAC;AAEjF;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,YAAY,EAAE,YAAY,GAAG,SAAS,GACrC,mBAAmB,CAmDrB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeax/engine-gltf",
3
- "version": "0.1.20",
3
+ "version": "0.1.23",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -38,21 +38,21 @@
38
38
  "LICENSE"
39
39
  ],
40
40
  "dependencies": {
41
- "@forgeax/engine-animation": "0.1.20",
42
- "@forgeax/engine-geometry": "0.1.20",
43
- "@forgeax/engine-import": "0.1.20",
44
- "@forgeax/engine-math": "0.1.20",
45
- "@forgeax/engine-pack": "0.1.20",
46
- "@forgeax/engine-scene": "0.1.20",
47
- "@forgeax/engine-types": "0.1.20",
41
+ "@forgeax/engine-animation": "0.1.23",
42
+ "@forgeax/engine-geometry": "0.1.23",
43
+ "@forgeax/engine-import": "0.1.23",
44
+ "@forgeax/engine-math": "0.1.23",
45
+ "@forgeax/engine-pack": "0.1.23",
46
+ "@forgeax/engine-scene": "0.1.23",
47
+ "@forgeax/engine-types": "0.1.23",
48
48
  "@webgpu/types": "^0.1.71",
49
49
  "meshoptimizer": "^0.20.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@forgeax/engine-assets-runtime": "0.1.20",
53
- "@forgeax/engine-picking": "0.1.20",
54
- "@forgeax/engine-render": "0.1.20",
55
- "@forgeax/engine-runtime": "0.1.20",
52
+ "@forgeax/engine-assets-runtime": "0.1.23",
53
+ "@forgeax/engine-picking": "0.1.23",
54
+ "@forgeax/engine-render": "0.1.23",
55
+ "@forgeax/engine-runtime": "0.1.23",
56
56
  "@types/node": "^20.14.0"
57
57
  },
58
58
  "forgeax": {
@@ -0,0 +1 @@
1
+ {"asset":{"version":"2.0"},"nodes":[{"mesh":0}],"meshes":[{"primitives":[]}],"scenes":[{"nodes":[0]}]}
@@ -0,0 +1 @@
1
+ {"asset":{"version":"2.0"},"extensionsRequired":["MSFT_lod"],"extensionsUsed":["MSFT_lod"],"nodes":[{"mesh":0,"extensions":{"MSFT_lod":{"ids":[99]}}}],"scenes":[{"nodes":[0]}]}
@@ -0,0 +1 @@
1
+ {"asset":{"version":"2.0"},"extensionsUsed":["MSFT_lod"],"meshes":[{"primitives":[{}]},{"primitives":[{}]},{"primitives":[{}]},{"primitives":[{}]},{"primitives":[{}]},{"primitives":[{}]}],"nodes":[{"mesh":0,"extensions":{"MSFT_lod":{"ids":[1,2]}}},{"mesh":1},{"mesh":2},{"mesh":3,"extensions":{"MSFT_lod":{"ids":[4,5]}}},{"mesh":4},{"mesh":5}],"extensions":{"MSFT_screencoverage":{"scales":[0.5,0.2]}},"scenes":[{"nodes":[0,3]}]}
@@ -0,0 +1 @@
1
+ {"asset":{"version":"2.0"},"extensionsUsed":["MSFT_lod"],"meshes":[{"primitives":[{}]},{"primitives":[{}]},{"primitives":[{}]}],"nodes":[{"mesh":0,"extensions":{"MSFT_lod":{"ids":[1,2]}},"extras":{"MSFT_screencoverage":[0.5,0.2,0.01]}},{"mesh":1},{"mesh":2}],"scenes":[{"nodes":[0]}]}
@@ -0,0 +1 @@
1
+ {"asset":{"version":"2.0"},"extensionsUsed":["MSFT_lod"],"meshes":[{"primitives":[{}]},{"primitives":[{}]},{"primitives":[{}]}],"nodes":[{"mesh":0,"extensions":{"MSFT_lod":{"ids":[1,2]}}},{"mesh":1},{"mesh":2}],"extensions":{"MSFT_screencoverage":{"scales":[0.5,0.2]}},"scenes":[{"nodes":[0]}]}
@@ -13,6 +13,7 @@ import {
13
13
  type GltfImageExtractFailedDetail,
14
14
  type GltfImageMimeUnsupportedDetail,
15
15
  type GltfInstancingCountMismatchDetail,
16
+ type GltfLodInvalidDetail,
16
17
  type GltfMalformedHeaderDetail,
17
18
  type GltfMaterialTransmissionInvalidDetail,
18
19
  type GltfMeshBridgeInvalidDetail,
@@ -52,6 +53,7 @@ type ExpectedDetails = {
52
53
  readonly 'gltf-color-accessor-unsupported': GltfColorAccessorUnsupportedDetail;
53
54
  readonly 'gltf-color-accessor-malformed': GltfColorAccessorMalformedDetail;
54
55
  readonly 'gltf-mesh-bridge-invalid': GltfMeshBridgeInvalidDetail;
56
+ readonly 'gltf-lod-invalid': GltfLodInvalidDetail;
55
57
  readonly 'gltf-material-transmission-invalid': GltfMaterialTransmissionInvalidDetail;
56
58
  };
57
59
 
@@ -75,6 +77,8 @@ function exhaustive(error: GltfError): string {
75
77
  return `${error.detail.accessor}:${error.detail.bufferIndex}`;
76
78
  case 'gltf-extension-unsupported':
77
79
  return error.detail.extension;
80
+ case 'gltf-lod-invalid':
81
+ return String(error.detail.rootNode);
78
82
  case 'gltf-accessor-type-mismatch':
79
83
  return error.detail.reason;
80
84
  case 'gltf-texture-load-failed':
@@ -309,6 +309,8 @@ function unwrapReimport(result: ReturnType<typeof reimportReuseMeta>) {
309
309
  return 'oob';
310
310
  case 'gltf-extension-unsupported':
311
311
  return 'ext';
312
+ case 'gltf-lod-invalid':
313
+ return 'lod';
312
314
  case 'gltf-material-transmission-invalid':
313
315
  return 'material-transmission-invalid';
314
316
  case 'gltf-accessor-type-mismatch':
@@ -360,6 +362,8 @@ function unwrapReimport(result: ReturnType<typeof reimportReuseMeta>) {
360
362
  return gltfErr(code, { accessor: 0, byteOffset: 0, byteLength: 0, bufferIndex: 0 });
361
363
  case 'gltf-extension-unsupported':
362
364
  return gltfErr(code, { extension: 'KHR_x', source: 'extensionsRequired' });
365
+ case 'gltf-lod-invalid':
366
+ return gltfErr(code, { rootNode: 0, ids: [1], reason: 'missing-node' });
363
367
  case 'gltf-material-transmission-invalid':
364
368
  return gltfErr(code, {
365
369
  extension: 'KHR_materials_transmission',
@@ -456,8 +460,8 @@ function unwrapReimport(result: ReturnType<typeof reimportReuseMeta>) {
456
460
  });
457
461
 
458
462
  describe('GltfErrorCode roster', () => {
459
- it('GLTF_ERROR_HINTS exposes exactly 23 keys', () => {
460
- expect(Object.keys(GLTF_ERROR_HINTS).length).toBe(23);
463
+ it('GLTF_ERROR_HINTS exposes exactly 24 keys', () => {
464
+ expect(Object.keys(GLTF_ERROR_HINTS).length).toBe(24);
461
465
  });
462
466
 
463
467
  it.each(ALL_CODES)('hint for %s is a non-empty string', (code) => {
@@ -2020,7 +2024,12 @@ function unwrapReimport(result: ReturnType<typeof reimportReuseMeta>) {
2020
2024
  });
2021
2025
 
2022
2026
  it('v1 allowlist contains EXT_mesh_gpu_instancing (literal)', () => {
2023
- expect(EXTENSION_ALLOWLIST).toEqual(['EXT_mesh_gpu_instancing', 'EXT_meshopt_compression']);
2027
+ expect(EXTENSION_ALLOWLIST).toEqual([
2028
+ 'EXT_mesh_gpu_instancing',
2029
+ 'EXT_meshopt_compression',
2030
+ 'MSFT_lod',
2031
+ 'MSFT_screencoverage',
2032
+ ]);
2024
2033
  });
2025
2034
 
2026
2035
  it('(a) rejects unsupported extensionsRequired entries', () => {
@@ -0,0 +1,106 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { parseGltfLodExtension } from '../lod/parse-lod.js';
4
+
5
+ async function fixture(name: string): Promise<Record<string, unknown>> {
6
+ return JSON.parse(
7
+ await readFile(new URL(`./fixtures/lod/${name}`, import.meta.url), 'utf8'),
8
+ ) as Record<string, unknown>;
9
+ }
10
+
11
+ describe('glTF MSFT_lod producer contract', () => {
12
+ it('adopts node-level ids in author order and suppresses lower nodes', async () => {
13
+ const json = await fixture('msft-lod.gltf');
14
+ const result = parseGltfLodExtension(json);
15
+ expect(result).toMatchObject({
16
+ ok: true,
17
+ value: { rootNode: 0, lodNodeIds: [1, 2], screenCoverages: [0.5, 0.2] },
18
+ });
19
+ });
20
+
21
+ it('adopts the official node extras coverage array and drops its terminal discard hint', async () => {
22
+ const json = await fixture('msft-lod-node-extras.gltf');
23
+ const result = parseGltfLodExtension(json);
24
+ expect(result).toMatchObject({
25
+ ok: true,
26
+ value: {
27
+ rootNode: 0,
28
+ lodNodeIds: [1, 2],
29
+ screenCoverages: [0.5, 0.2],
30
+ groups: [{ rootNode: 0, lodNodeIds: [1, 2], screenCoverages: [0.5, 0.2] }],
31
+ },
32
+ });
33
+ });
34
+
35
+ it('rejects an official node extras array whose root/level count is incomplete', () => {
36
+ expect(
37
+ parseGltfLodExtension({
38
+ meshes: [{ primitives: [{}] }, { primitives: [{}] }, { primitives: [{}] }],
39
+ nodes: [
40
+ {
41
+ extensions: { MSFT_lod: { ids: [1, 2] } },
42
+ extras: { MSFT_screencoverage: [0.5, 0.2] },
43
+ },
44
+ {},
45
+ {},
46
+ ],
47
+ }),
48
+ ).toMatchObject({ ok: false, error: { code: 'gltf-lod-invalid' } });
49
+ });
50
+
51
+ it('fails closed when a required MSFT_lod extension has no normalizable group', () => {
52
+ expect(
53
+ parseGltfLodExtension({ extensionsRequired: ['MSFT_lod'], nodes: [{ mesh: 0 }] }),
54
+ ).toMatchObject({ ok: false, error: { code: 'gltf-lod-invalid' } });
55
+ });
56
+
57
+ it('requires every referenced node to resolve a mesh sub-asset', () => {
58
+ expect(
59
+ parseGltfLodExtension({
60
+ nodes: [{ mesh: 0, extensions: { MSFT_lod: { ids: [1] } } }, {}],
61
+ }),
62
+ ).toMatchObject({ ok: false, error: { code: 'gltf-lod-invalid' } });
63
+ });
64
+
65
+ it('rejects a referenced mesh index outside the source mesh table', () => {
66
+ expect(
67
+ parseGltfLodExtension({
68
+ meshes: [{}],
69
+ nodes: [{ mesh: 0, extensions: { MSFT_lod: { ids: [1] } } }, { mesh: 2 }],
70
+ }),
71
+ ).toMatchObject({ ok: false, error: { code: 'gltf-lod-invalid' } });
72
+ });
73
+
74
+ it('rejects a referenced mesh with no primitives before publication', () => {
75
+ expect(
76
+ parseGltfLodExtension({
77
+ meshes: [{ primitives: [] }, { primitives: [{}] }],
78
+ nodes: [{ mesh: 0, extensions: { MSFT_lod: { ids: [1] } } }, { mesh: 1 }],
79
+ }),
80
+ ).toMatchObject({ ok: false, error: { code: 'gltf-lod-invalid' } });
81
+ });
82
+
83
+ it('rejects malformed required extension instead of loading only LOD0', async () => {
84
+ const json = await fixture('msft-lod-malformed.gltf');
85
+ expect(parseGltfLodExtension(json)).toMatchObject({
86
+ ok: false,
87
+ error: { code: 'gltf-lod-invalid' },
88
+ });
89
+ });
90
+
91
+ it('retains every node-level relation for multi-root scenes', async () => {
92
+ const json = await fixture('msft-lod-multi-root.gltf');
93
+ const result = parseGltfLodExtension(json);
94
+ expect(result).toMatchObject({
95
+ ok: true,
96
+ value: {
97
+ rootNode: 0,
98
+ lodNodeIds: [1, 2],
99
+ groups: [
100
+ { rootNode: 0, lodNodeIds: [1, 2] },
101
+ { rootNode: 3, lodNodeIds: [4, 5] },
102
+ ],
103
+ },
104
+ });
105
+ });
106
+ });
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { projectGltfLodMeta } from '../lod/project-meta.js';
3
+
4
+ describe('glTF LOD Meta publication candidate', () => {
5
+ it('uses declared GUIDs and leaves publication to the shared host', () => {
6
+ const result = projectGltfLodMeta({
7
+ rootSourceKey: 'mesh/root',
8
+ levels: [
9
+ { sourceKey: 'mesh/lod1', guid: 'guid-1' },
10
+ { sourceKey: 'mesh/lod2', guid: 'guid-2' },
11
+ ],
12
+ previous: undefined,
13
+ });
14
+ expect(result).toMatchObject({
15
+ ok: true,
16
+ value: {
17
+ lods: [
18
+ { meshGuid: 'guid-1', screenCoverage: 0.5 },
19
+ { meshGuid: 'guid-2', screenCoverage: 0.2 },
20
+ ],
21
+ },
22
+ });
23
+ });
24
+ });
@@ -162,6 +162,28 @@ describe('glTF transmission material IR', () => {
162
162
  });
163
163
  });
164
164
 
165
+ describe('glTF required extension admission', () => {
166
+ it.each([
167
+ 'KHR_materials_transmission',
168
+ 'KHR_materials_ior',
169
+ 'KHR_materials_volume',
170
+ ])('accepts %s through the parse path', async (extension) => {
171
+ const result = await parseGltf(
172
+ {
173
+ asset: { version: '2.0' },
174
+ extensionsUsed: [extension],
175
+ extensionsRequired: [extension],
176
+ },
177
+ noopLoader,
178
+ `/required-${extension}.gltf`,
179
+ );
180
+
181
+ expect(result.ok).toBe(true);
182
+ if (!result.ok) return;
183
+ expect(result.value.diagnostics.unsupportedExtensions).toEqual([]);
184
+ });
185
+ });
186
+
165
187
  describe('glTF COLOR_0 importer carrier', () => {
166
188
  it('publishes normalized VEC3 FLOAT colors as importer-owned RGBA', async () => {
167
189
  const fixture = JSON.parse(
@@ -86,7 +86,9 @@ describe('glTF producer source-key boundary', () => {
86
86
  expect(result.ok).toBe(false);
87
87
  if (result.ok) return;
88
88
  expect(result.error.code).toBe('duplicate-source-key');
89
- expect(result.error.detail.sourceIndices).toEqual([0, 1]);
89
+ expect((result.error.detail as { sourceIndices: readonly number[] }).sourceIndices).toEqual([
90
+ 0, 1,
91
+ ]);
90
92
  });
91
93
 
92
94
  it('applies the same preflight to duplicate material names', () => {
@@ -95,7 +97,9 @@ describe('glTF producer source-key boundary', () => {
95
97
  expect(result.ok).toBe(false);
96
98
  if (result.ok) return;
97
99
  expect(result.error.code).toBe('duplicate-source-key');
98
- expect(result.error.detail.sourceIndices).toEqual([0, 1]);
100
+ expect((result.error.detail as { sourceIndices: readonly number[] }).sourceIndices).toEqual([
101
+ 0, 1,
102
+ ]);
99
103
  });
100
104
 
101
105
  it('publishes producer-valid output when semantic identity is unique', () => {
package/src/bridge.ts CHANGED
@@ -541,7 +541,7 @@ export function gltfDocToSceneAsset(doc: GltfDoc, ctx: GltfBridgeContext): Scene
541
541
 
542
542
  // bug-20260613: SceneAsset entities also emit ChildOf when a glTF node has a
543
543
  // parent (line 547 below). Runtime propagateTransforms then derives
544
- // Transform.world via `parent.world * compose(child.local TRS)`. If we
544
+ // GlobalTransform.world via `parent.world * compose(child.local TRS)`. If we
545
545
  // wrote the *world* TRS into Transform here, every child node would get
546
546
  // baked twice -- once at importer time and again at propagate time --
547
547
  // collapsing the skin so vertices fly to (parent.world)^2 space and the
@@ -27,6 +27,8 @@ import { err, type GltfError, gltfErr, ok, type Result } from './errors.js';
27
27
  export const EXTENSION_ALLOWLIST: readonly string[] = [
28
28
  'EXT_mesh_gpu_instancing',
29
29
  'EXT_meshopt_compression',
30
+ 'MSFT_lod',
31
+ 'MSFT_screencoverage',
30
32
  ];
31
33
  const SUPPORTED_EXTENSIONS: readonly string[] = [
32
34
  ...EXTENSION_ALLOWLIST,
package/src/errors.ts CHANGED
@@ -60,6 +60,12 @@ export interface GltfExtensionUnsupportedDetail {
60
60
  readonly source: 'extensionsRequired' | 'extensionsUsed';
61
61
  }
62
62
 
63
+ export interface GltfLodInvalidDetail {
64
+ readonly rootNode: number;
65
+ readonly ids: readonly number[];
66
+ readonly reason: 'missing-node' | 'duplicate-node' | 'not-integer' | 'coverage';
67
+ }
68
+
63
69
  export interface GltfMaterialTransmissionInvalidDetail {
64
70
  readonly extension: 'KHR_materials_transmission' | 'KHR_materials_ior' | 'KHR_materials_volume';
65
71
  readonly field: string;
@@ -263,6 +269,10 @@ const gltfErrorPolicy = {
263
269
  expected: 'extension listed in v1 allowlist (see EXTENSION_ALLOWLIST in @forgeax/engine-gltf)',
264
270
  hint: 'see feat-future-gltf-extensions-allowlist; remove this extension or wait for the allowlist to expand',
265
271
  },
272
+ 'gltf-lod-invalid': {
273
+ expected: 'MSFT_lod ids to reference unique existing node indices with valid coverage',
274
+ hint: 'repair the MSFT_lod node relation or remove it from extensionsRequired before re-importing',
275
+ },
266
276
  'gltf-material-transmission-invalid': {
267
277
  expected: 'KHR transmission, IOR, and volume values are finite and within their glTF ranges',
268
278
  hint: 'repair the named glTF material extension value and re-import the source',
@@ -357,6 +367,7 @@ interface DetailFor {
357
367
  readonly 'gltf-version-unsupported': GltfVersionUnsupportedDetail;
358
368
  readonly 'gltf-buffer-out-of-bounds': GltfBufferOutOfBoundsDetail;
359
369
  readonly 'gltf-extension-unsupported': GltfExtensionUnsupportedDetail;
370
+ readonly 'gltf-lod-invalid': GltfLodInvalidDetail;
360
371
  readonly 'gltf-accessor-type-mismatch': GltfAccessorTypeMismatchDetail;
361
372
  readonly 'gltf-texture-load-failed': GltfTextureLoadFailedDetail;
362
373
  readonly 'gltf-meta-missing': GltfMetaMissingDetail;
@@ -32,6 +32,7 @@
32
32
  // gap as `import-produced-no-assets`. A texture sub-asset that fails byte
33
33
  // extraction surfaces as `gltf-image-extract-failed` (D-6).
34
34
 
35
+ import { deriveDefaultLodScreenCoverages } from '@forgeax/engine-import';
35
36
  import { packMeshBinV4 } from '@forgeax/engine-import/mesh-bin';
36
37
  import { AssetGuid as AssetGuidCodec } from '@forgeax/engine-pack/guid';
37
38
  import type {
@@ -591,7 +592,83 @@ async function importGltf(
591
592
  }),
592
593
  };
593
594
  }
594
- const meshPayload = stabilizeMeshMaterialSlots(bridged.value, ctx, sub.guid, sub.sourceKey);
595
+ const stabilizedMesh = stabilizeMeshMaterialSlots(
596
+ bridged.value,
597
+ ctx,
598
+ sub.guid,
599
+ sub.sourceKey,
600
+ );
601
+ const lodGroup =
602
+ doc.lod?.groups?.find(
603
+ (group) => doc.nodes[group.rootNode]?.meshIndex === sub.sourceIndex,
604
+ ) ??
605
+ (doc.lod?.rootNode !== undefined &&
606
+ doc.nodes[doc.lod.rootNode]?.meshIndex === sub.sourceIndex
607
+ ? doc.lod
608
+ : undefined);
609
+ const rootMeshIndex = lodGroup === undefined ? undefined : sub.sourceIndex;
610
+ if (lodGroup !== undefined) {
611
+ const referencedMeshIndices = [
612
+ sub.sourceIndex,
613
+ ...lodGroup.lodNodeIds.map((nodeIndex) => doc.nodes[nodeIndex]?.meshIndex),
614
+ ];
615
+ const missingMeshIndex = referencedMeshIndices.find(
616
+ (meshIndex) =>
617
+ !Number.isInteger(meshIndex) ||
618
+ meshIndex === null ||
619
+ meshIndex === undefined ||
620
+ maps.meshGuidByIndex.get(meshIndex as number) === undefined,
621
+ );
622
+ if (missingMeshIndex !== undefined) {
623
+ return {
624
+ ok: false,
625
+ error: new ImportError({
626
+ code: 'import-internal-error',
627
+ expected: 'every MSFT_lod node to resolve a cooked mesh sub-asset',
628
+ actual: String(missingMeshIndex),
629
+ hint: 'repair the referenced glTF mesh primitives and re-run the importer',
630
+ detail: {
631
+ reason: `MSFT_lod group for mesh ${sub.sourceIndex} references an unprojected mesh`,
632
+ },
633
+ }),
634
+ };
635
+ }
636
+ }
637
+ const authoredLods =
638
+ sub.sourceKey === undefined ? undefined : ctx.sourceOverrides?.[sub.sourceKey]?.lods;
639
+ const lodEntries = Array.isArray(authoredLods) ? authoredLods : [];
640
+ const lodLevels =
641
+ rootMeshIndex === sub.sourceIndex && lodGroup !== undefined
642
+ ? lodGroup.lodNodeIds.flatMap((nodeIndex, index) => {
643
+ const meshIndex = doc.nodes[nodeIndex]?.meshIndex;
644
+ const guid =
645
+ meshIndex === undefined || meshIndex === null
646
+ ? undefined
647
+ : maps.meshGuidByIndex.get(meshIndex);
648
+ if (guid === undefined) return [];
649
+ const authored = lodEntries[index];
650
+ const authoredCoverage =
651
+ authored !== null && typeof authored === 'object'
652
+ ? (authored as { readonly screenCoverage?: unknown }).screenCoverage
653
+ : undefined;
654
+ const coverage =
655
+ typeof authoredCoverage === 'number'
656
+ ? authoredCoverage
657
+ : (lodGroup.screenCoverages[index] ??
658
+ deriveDefaultLodScreenCoverages(lodGroup.lodNodeIds.length + 1)[index]);
659
+ const parsed = AssetGuidCodec.parse(guid);
660
+ return parsed.ok && coverage !== undefined
661
+ ? [{ mesh: parsed.value, screenCoverage: coverage, guid }]
662
+ : [];
663
+ })
664
+ : [];
665
+ const meshPayload: MeshAsset =
666
+ lodLevels.length === 0
667
+ ? stabilizedMesh
668
+ : {
669
+ ...stabilizedMesh,
670
+ lods: lodLevels.map(({ mesh, screenCoverage }) => ({ mesh, screenCoverage })),
671
+ };
595
672
  const materialRefs: AssetRef[] = [];
596
673
  const seenMaterialGuids = new Set<string>();
597
674
  for (let slotIndex = 0; slotIndex < meshPayload.materialSlots.length; slotIndex++) {
@@ -606,6 +683,15 @@ async function importGltf(
606
683
  });
607
684
  }
608
685
  }
686
+ for (const [lodIndex, level] of lodLevels.entries()) {
687
+ if (!seenMaterialGuids.has(level.guid.toLowerCase())) {
688
+ seenMaterialGuids.add(level.guid.toLowerCase());
689
+ materialRefs.push({
690
+ guid: level.guid,
691
+ sourceField: { fieldName: 'lods', arrayIndex: lodIndex },
692
+ });
693
+ }
694
+ }
609
695
  out.push({
610
696
  guid: sub.guid,
611
697
  kind: 'mesh',
package/src/index.ts CHANGED
@@ -81,6 +81,8 @@ export { err, GLTF_ERROR_HINTS, gltfErr, ok } from './errors.js';
81
81
  // gltfImporter: the build-time `{ key: 'gltf', import }` Importer
82
82
  // (feat-20260603-asset-import-loader-injection M2 / w19).
83
83
  export { gltfImporter } from './gltf-importer.js';
84
+ export { type GltfLodRelation, parseGltfLodExtension } from './lod/parse-lod.js';
85
+ export { projectGltfLodMeta } from './lod/project-meta.js';
84
86
  export type { GlbChunks } from './parse-glb-chunks.js';
85
87
  // Header / chunk parsers (w8).
86
88
  export { parseGlbChunks, parseGltfHeader } from './parse-glb-chunks.js';