@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.
- package/README.md +59 -0
- package/dist/__tests__/lod-import.integration.test.d.ts +2 -0
- package/dist/__tests__/lod-import.integration.test.d.ts.map +1 -0
- package/dist/__tests__/lod-meta-publication.integration.test.d.ts +2 -0
- package/dist/__tests__/lod-meta-publication.integration.test.d.ts.map +1 -0
- package/dist/check-extensions.d.ts.map +1 -1
- package/dist/cli-gltf.mjs +183 -8
- package/dist/cli-gltf.mjs.map +1 -1
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/gltf-importer.d.ts.map +1 -1
- package/dist/importer-entry.mjs +145 -3
- package/dist/importer-entry.mjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +238 -6
- package/dist/index.mjs.map +1 -1
- package/dist/lod/parse-lod.d.ts +15 -0
- package/dist/lod/parse-lod.d.ts.map +1 -0
- package/dist/lod/project-meta.d.ts +23 -0
- package/dist/lod/project-meta.d.ts.map +1 -0
- package/dist/node-file-entry.mjs +86 -2
- package/dist/node-file-entry.mjs.map +1 -1
- package/dist/parse-gltf.d.ts +3 -1
- package/dist/parse-gltf.d.ts.map +1 -1
- package/dist/reimport-reuse-meta.d.ts +1 -1
- package/dist/reimport-reuse-meta.d.ts.map +1 -1
- package/package.json +12 -12
- package/src/__tests__/fixtures/lod/core-only.gltf +1 -0
- package/src/__tests__/fixtures/lod/msft-lod-malformed.gltf +1 -0
- package/src/__tests__/fixtures/lod/msft-lod-multi-root.gltf +1 -0
- package/src/__tests__/fixtures/lod/msft-lod-node-extras.gltf +1 -0
- package/src/__tests__/fixtures/lod/msft-lod.gltf +1 -0
- package/src/__tests__/gltf-error-derived-views.test-d.ts +4 -0
- package/src/__tests__/gltf.unit.test.ts +12 -3
- package/src/__tests__/lod-import.integration.test.ts +106 -0
- package/src/__tests__/lod-meta-publication.integration.test.ts +24 -0
- package/src/__tests__/parse-gltf.unit.test.ts +22 -0
- package/src/__tests__/source-key-producer.integration.test.ts +6 -2
- package/src/bridge.ts +1 -1
- package/src/check-extensions.ts +2 -0
- package/src/errors.ts +11 -0
- package/src/gltf-importer.ts +87 -1
- package/src/index.ts +2 -0
- package/src/lod/parse-lod.ts +129 -0
- package/src/lod/project-meta.ts +48 -0
- package/src/parse-gltf.ts +97 -1
- package/src/reimport-reuse-meta.ts +3 -1
- package/dist/.tsbuildinfo +0 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { err, gltfErr, ok, type Result } from '../errors.js';
|
|
2
|
+
|
|
3
|
+
export interface GltfLodRelation {
|
|
4
|
+
readonly rootNode: number;
|
|
5
|
+
readonly lodNodeIds: readonly number[];
|
|
6
|
+
readonly screenCoverages: readonly number[];
|
|
7
|
+
/** All node-level MSFT_lod groups, retained for multi-root scenes. */
|
|
8
|
+
readonly groups: readonly GltfLodGroup[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface GltfLodGroup {
|
|
12
|
+
readonly rootNode: number;
|
|
13
|
+
readonly lodNodeIds: readonly number[];
|
|
14
|
+
readonly screenCoverages: readonly number[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface RawNode {
|
|
18
|
+
readonly mesh?: unknown;
|
|
19
|
+
readonly extensions?: { readonly MSFT_lod?: { readonly ids?: unknown } };
|
|
20
|
+
readonly extras?: { readonly MSFT_screencoverage?: unknown };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface RawMesh {
|
|
24
|
+
readonly primitives?: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface RawGltf {
|
|
28
|
+
readonly extensionsRequired?: readonly unknown[];
|
|
29
|
+
readonly meshes?: readonly RawMesh[];
|
|
30
|
+
readonly nodes?: readonly RawNode[];
|
|
31
|
+
readonly extensions?: { readonly MSFT_screencoverage?: { readonly scales?: unknown } };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function parseGltfLodExtension(
|
|
35
|
+
json: unknown,
|
|
36
|
+
): Result<GltfLodRelation, ReturnType<typeof gltfErr<'gltf-lod-invalid'>>> {
|
|
37
|
+
const doc = json as RawGltf;
|
|
38
|
+
const nodes = doc.nodes ?? [];
|
|
39
|
+
const rawCoverage = doc.extensions?.MSFT_screencoverage?.scales;
|
|
40
|
+
const groups: GltfLodGroup[] = [];
|
|
41
|
+
for (let rootNode = 0; rootNode < nodes.length; rootNode += 1) {
|
|
42
|
+
if (nodes[rootNode]?.extensions?.MSFT_lod === undefined) continue;
|
|
43
|
+
const idsRaw = nodes[rootNode]?.extensions?.MSFT_lod?.ids;
|
|
44
|
+
if (!Array.isArray(idsRaw)) {
|
|
45
|
+
return err(gltfErr('gltf-lod-invalid', { rootNode, ids: [], reason: 'not-integer' }));
|
|
46
|
+
}
|
|
47
|
+
const ids = idsRaw.filter((value): value is number => typeof value === 'number');
|
|
48
|
+
if (
|
|
49
|
+
ids.length !== idsRaw.length ||
|
|
50
|
+
ids.some((id) => !Number.isInteger(id) || id < 0 || id >= nodes.length)
|
|
51
|
+
) {
|
|
52
|
+
return err(gltfErr('gltf-lod-invalid', { rootNode, ids, reason: 'missing-node' }));
|
|
53
|
+
}
|
|
54
|
+
if (new Set(ids).size !== ids.length || ids.includes(rootNode)) {
|
|
55
|
+
return err(gltfErr('gltf-lod-invalid', { rootNode, ids, reason: 'duplicate-node' }));
|
|
56
|
+
}
|
|
57
|
+
const hasMesh = (node: RawNode | undefined): boolean => {
|
|
58
|
+
if (typeof node?.mesh !== 'number' || !Number.isInteger(node.mesh) || node.mesh < 0) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const mesh = doc.meshes?.[node.mesh];
|
|
62
|
+
// An extension relation is publishable only when its target mesh has at
|
|
63
|
+
// least one primitive. The parser normally receives the source mesh
|
|
64
|
+
// table, so an empty table/primitive list is a source-contract failure;
|
|
65
|
+
// treating it as a valid relation would let toAssetPack silently drop
|
|
66
|
+
// the LOD level later.
|
|
67
|
+
return mesh !== undefined && Array.isArray(mesh.primitives) && mesh.primitives.length > 0;
|
|
68
|
+
};
|
|
69
|
+
if (!hasMesh(nodes[rootNode]) || ids.some((id) => !hasMesh(nodes[id]))) {
|
|
70
|
+
return err(gltfErr('gltf-lod-invalid', { rootNode, ids, reason: 'missing-node' }));
|
|
71
|
+
}
|
|
72
|
+
const nodeCoverage = nodes[rootNode]?.extras?.MSFT_screencoverage;
|
|
73
|
+
const coverage = normalizeScreenCoverages(
|
|
74
|
+
nodeCoverage === undefined ? rawCoverage : nodeCoverage,
|
|
75
|
+
ids.length,
|
|
76
|
+
nodeCoverage !== undefined,
|
|
77
|
+
);
|
|
78
|
+
if (coverage === 'invalid') {
|
|
79
|
+
return err(gltfErr('gltf-lod-invalid', { rootNode, ids, reason: 'coverage' }));
|
|
80
|
+
}
|
|
81
|
+
groups.push({
|
|
82
|
+
rootNode,
|
|
83
|
+
lodNodeIds: ids,
|
|
84
|
+
screenCoverages: coverage ?? [],
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const first = groups[0];
|
|
88
|
+
if (first === undefined) {
|
|
89
|
+
if (doc.extensionsRequired?.some((extension) => extension === 'MSFT_lod')) {
|
|
90
|
+
return err(gltfErr('gltf-lod-invalid', { rootNode: 0, ids: [], reason: 'missing-node' }));
|
|
91
|
+
}
|
|
92
|
+
return ok({ rootNode: 0, lodNodeIds: [], screenCoverages: [], groups: [] });
|
|
93
|
+
}
|
|
94
|
+
return ok({
|
|
95
|
+
rootNode: first.rootNode,
|
|
96
|
+
lodNodeIds: first.lodNodeIds,
|
|
97
|
+
screenCoverages: first.screenCoverages,
|
|
98
|
+
groups,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Normalize the official node `extras.MSFT_screencoverage` form and the
|
|
104
|
+
* historical ForgeaX document-extension adapter form. The official array
|
|
105
|
+
* includes one terminal threshold for the discard range, while MeshAsset
|
|
106
|
+
* stores only the thresholds that select lower geometry levels.
|
|
107
|
+
*/
|
|
108
|
+
function normalizeScreenCoverages(
|
|
109
|
+
raw: unknown,
|
|
110
|
+
lodCount: number,
|
|
111
|
+
officialNodeForm: boolean,
|
|
112
|
+
): readonly number[] | undefined | 'invalid' {
|
|
113
|
+
if (raw === undefined) return undefined;
|
|
114
|
+
if (!Array.isArray(raw)) return 'invalid';
|
|
115
|
+
if (
|
|
116
|
+
raw.some(
|
|
117
|
+
(value) => typeof value !== 'number' || !Number.isFinite(value) || value <= 0 || value > 1,
|
|
118
|
+
)
|
|
119
|
+
) {
|
|
120
|
+
return 'invalid';
|
|
121
|
+
}
|
|
122
|
+
const expectedLengths = officialNodeForm ? [lodCount + 1] : [lodCount, lodCount + 1];
|
|
123
|
+
if (!expectedLengths.includes(raw.length)) return 'invalid';
|
|
124
|
+
const values = raw.slice(0, lodCount) as number[];
|
|
125
|
+
for (let index = 1; index < values.length; index += 1) {
|
|
126
|
+
if ((values[index] as number) >= (values[index - 1] as number)) return 'invalid';
|
|
127
|
+
}
|
|
128
|
+
return values;
|
|
129
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { deriveDefaultLodScreenCoverages, reconcileMeshLodMeta } from '@forgeax/engine-import';
|
|
2
|
+
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
3
|
+
|
|
4
|
+
export interface GltfLodMetaLevel {
|
|
5
|
+
readonly sourceKey: string;
|
|
6
|
+
readonly guid: string;
|
|
7
|
+
readonly screenCoverage?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface GltfLodMetaCandidate {
|
|
11
|
+
readonly rootSourceKey: string;
|
|
12
|
+
readonly levels: readonly GltfLodMetaLevel[];
|
|
13
|
+
readonly previous?: readonly GltfLodMetaLevel[] | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface GltfLodMetaOutput {
|
|
17
|
+
readonly sourceKey: string;
|
|
18
|
+
readonly meshGuid: string;
|
|
19
|
+
readonly screenCoverage: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function projectGltfLodMeta(
|
|
23
|
+
input: GltfLodMetaCandidate,
|
|
24
|
+
): Result<
|
|
25
|
+
{ readonly lods: readonly GltfLodMetaOutput[] },
|
|
26
|
+
{ readonly code: string; readonly reason: string }
|
|
27
|
+
> {
|
|
28
|
+
const defaults = deriveDefaultLodScreenCoverages(input.levels.length + 1);
|
|
29
|
+
const previous = (input.previous ?? []).map((level) => ({
|
|
30
|
+
sourceKey: level.sourceKey,
|
|
31
|
+
meshGuid: level.guid,
|
|
32
|
+
...(level.screenCoverage === undefined ? {} : { screenCoverage: level.screenCoverage }),
|
|
33
|
+
}));
|
|
34
|
+
const next = input.levels.map((level, index) => ({
|
|
35
|
+
sourceKey: level.sourceKey,
|
|
36
|
+
meshGuid: level.guid,
|
|
37
|
+
screenCoverage: level.screenCoverage ?? defaults[index] ?? 0,
|
|
38
|
+
}));
|
|
39
|
+
const reconciled = reconcileMeshLodMeta(previous, next);
|
|
40
|
+
if (!reconciled.ok) return err({ code: reconciled.error.code, reason: reconciled.error.reason });
|
|
41
|
+
return ok({
|
|
42
|
+
lods: reconciled.value.lods.map((level) => ({
|
|
43
|
+
sourceKey: level.sourceKey,
|
|
44
|
+
meshGuid: level.meshGuid,
|
|
45
|
+
screenCoverage: level.screenCoverage,
|
|
46
|
+
})),
|
|
47
|
+
});
|
|
48
|
+
}
|
package/src/parse-gltf.ts
CHANGED
|
@@ -19,6 +19,8 @@ import { decodeColorAccessor } from './accessor/decode-color.js';
|
|
|
19
19
|
import { checkExtensions, type GltfExtensionsJson } from './check-extensions.js';
|
|
20
20
|
import { Base64DecodeError, dataUriBase64Payload, decodeBase64 } from './data-uri.js';
|
|
21
21
|
import { err, type GltfError, gltfErr, ok, type Result } from './errors.js';
|
|
22
|
+
import { type GltfLodRelation, parseGltfLodExtension } from './lod/parse-lod.js';
|
|
23
|
+
import { projectGltfLodMeta } from './lod/project-meta.js';
|
|
22
24
|
import {
|
|
23
25
|
type GltfImageIr,
|
|
24
26
|
type GltfMaterialIr,
|
|
@@ -341,6 +343,7 @@ export interface GltfDoc {
|
|
|
341
343
|
* fixtures; producers from parseGltf / parseGlb always populate it.
|
|
342
344
|
*/
|
|
343
345
|
readonly meshPrimitiveCount?: ReadonlyMap<number, number>;
|
|
346
|
+
readonly lod?: GltfLodRelation;
|
|
344
347
|
}
|
|
345
348
|
|
|
346
349
|
interface BuffersJson {
|
|
@@ -363,6 +366,7 @@ interface RootGltfJson extends GltfExtensionsJson {
|
|
|
363
366
|
readonly rotation?: readonly number[];
|
|
364
367
|
readonly scale?: readonly number[];
|
|
365
368
|
readonly weights?: readonly number[];
|
|
369
|
+
readonly extras?: { readonly MSFT_screencoverage?: readonly number[] };
|
|
366
370
|
readonly extensions?: {
|
|
367
371
|
readonly KHR_lights_punctual?: {
|
|
368
372
|
readonly light?: number;
|
|
@@ -374,6 +378,7 @@ interface RootGltfJson extends GltfExtensionsJson {
|
|
|
374
378
|
readonly SCALE?: number;
|
|
375
379
|
};
|
|
376
380
|
};
|
|
381
|
+
readonly MSFT_lod?: { readonly ids?: readonly number[] };
|
|
377
382
|
};
|
|
378
383
|
}>;
|
|
379
384
|
readonly extensions?: {
|
|
@@ -389,6 +394,7 @@ interface RootGltfJson extends GltfExtensionsJson {
|
|
|
389
394
|
};
|
|
390
395
|
}>;
|
|
391
396
|
};
|
|
397
|
+
readonly MSFT_screencoverage?: { readonly scales?: readonly number[] };
|
|
392
398
|
};
|
|
393
399
|
readonly skins?: ReadonlyArray<{
|
|
394
400
|
readonly name?: string;
|
|
@@ -565,6 +571,8 @@ async function parseGltfWithBin(
|
|
|
565
571
|
const extResult = checkExtensions(json);
|
|
566
572
|
if (!extResult.ok) return err(extResult.error);
|
|
567
573
|
const unsupportedExtensions = extResult.value.unsupportedUsed;
|
|
574
|
+
const lodResult = parseGltfLodExtension(json);
|
|
575
|
+
if (!lodResult.ok) return err(lodResult.error);
|
|
568
576
|
const lightsResult = parsePunctualLights(json, ctx.filePath);
|
|
569
577
|
if (!lightsResult.ok) return err(lightsResult.error);
|
|
570
578
|
|
|
@@ -1183,6 +1191,7 @@ async function parseGltfWithBin(
|
|
|
1183
1191
|
},
|
|
1184
1192
|
meshPrimitiveCount,
|
|
1185
1193
|
lights: lightsResult.value,
|
|
1194
|
+
...(lodResult.value.lodNodeIds.length === 0 ? {} : { lod: lodResult.value }),
|
|
1186
1195
|
});
|
|
1187
1196
|
}
|
|
1188
1197
|
|
|
@@ -1293,7 +1302,7 @@ export interface GltfAssetPack {
|
|
|
1293
1302
|
readonly subAssets: readonly GltfSubAssetEntry[];
|
|
1294
1303
|
}
|
|
1295
1304
|
|
|
1296
|
-
export type GltfAssetPackResult = Result<GltfAssetPack, GltfSourceKeyError>;
|
|
1305
|
+
export type GltfAssetPackResult = Result<GltfAssetPack, GltfSourceKeyError | GltfError>;
|
|
1297
1306
|
|
|
1298
1307
|
/**
|
|
1299
1308
|
* Project a parsed `GltfDoc` into the disk-shape `<source>.meta.json` plus
|
|
@@ -1407,6 +1416,93 @@ export function toAssetPack(
|
|
|
1407
1416
|
const sourceOverrides: Record<string, Readonly<Record<string, unknown>>> = {
|
|
1408
1417
|
...(existingMeta?.sourceOverrides ?? {}),
|
|
1409
1418
|
};
|
|
1419
|
+
const lodGroups =
|
|
1420
|
+
doc.lod === undefined ? [] : doc.lod.groups.length > 0 ? doc.lod.groups : [doc.lod];
|
|
1421
|
+
for (const lodGroup of lodGroups) {
|
|
1422
|
+
if (lodGroup.lodNodeIds.length === 0) continue;
|
|
1423
|
+
const rootMeshIndex = doc.nodes[lodGroup.rootNode]?.meshIndex;
|
|
1424
|
+
const referencedMeshIndices = [
|
|
1425
|
+
rootMeshIndex,
|
|
1426
|
+
...lodGroup.lodNodeIds.map((nodeIndex) => doc.nodes[nodeIndex]?.meshIndex),
|
|
1427
|
+
];
|
|
1428
|
+
if (
|
|
1429
|
+
referencedMeshIndices.some(
|
|
1430
|
+
(meshIndex) =>
|
|
1431
|
+
!Number.isInteger(meshIndex) ||
|
|
1432
|
+
meshIndex === null ||
|
|
1433
|
+
meshIndex === undefined ||
|
|
1434
|
+
!seenMeshIndices.has(meshIndex as number),
|
|
1435
|
+
)
|
|
1436
|
+
) {
|
|
1437
|
+
return err(
|
|
1438
|
+
gltfErr('gltf-lod-invalid', {
|
|
1439
|
+
rootNode: lodGroup.rootNode,
|
|
1440
|
+
ids: lodGroup.lodNodeIds,
|
|
1441
|
+
reason: 'missing-node',
|
|
1442
|
+
}),
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
const rootOutput =
|
|
1446
|
+
rootMeshIndex === undefined || rootMeshIndex === null
|
|
1447
|
+
? undefined
|
|
1448
|
+
: subAssetByKindIndex.get(`mesh:${rootMeshIndex}`);
|
|
1449
|
+
const levels = lodGroup.lodNodeIds.map((nodeIndex, index) => {
|
|
1450
|
+
const meshIndex = doc.nodes[nodeIndex]?.meshIndex;
|
|
1451
|
+
const output =
|
|
1452
|
+
meshIndex === undefined || meshIndex === null
|
|
1453
|
+
? undefined
|
|
1454
|
+
: subAssetByKindIndex.get(`mesh:${meshIndex}`);
|
|
1455
|
+
return {
|
|
1456
|
+
sourceKey: output?.sourceKey ?? `mesh:${meshIndex ?? nodeIndex}`,
|
|
1457
|
+
meshGuid: output?.guid ?? '',
|
|
1458
|
+
...(lodGroup.screenCoverages[index] === undefined
|
|
1459
|
+
? {}
|
|
1460
|
+
: { screenCoverage: lodGroup.screenCoverages[index] }),
|
|
1461
|
+
};
|
|
1462
|
+
});
|
|
1463
|
+
if (rootOutput?.sourceKey !== undefined && levels.every((level) => level.meshGuid !== '')) {
|
|
1464
|
+
const previousLodMeta = (() => {
|
|
1465
|
+
const raw = existingMeta?.sourceOverrides?.[rootOutput.sourceKey]?.lods;
|
|
1466
|
+
if (!Array.isArray(raw)) return undefined;
|
|
1467
|
+
return raw.flatMap((entry) => {
|
|
1468
|
+
if (entry === null || typeof entry !== 'object') return [];
|
|
1469
|
+
const value = entry as Record<string, unknown>;
|
|
1470
|
+
return typeof value.sourceKey === 'string' && typeof value.meshGuid === 'string'
|
|
1471
|
+
? [
|
|
1472
|
+
{
|
|
1473
|
+
sourceKey: value.sourceKey,
|
|
1474
|
+
guid: value.meshGuid,
|
|
1475
|
+
...(typeof value.screenCoverage === 'number'
|
|
1476
|
+
? { screenCoverage: value.screenCoverage }
|
|
1477
|
+
: {}),
|
|
1478
|
+
},
|
|
1479
|
+
]
|
|
1480
|
+
: [];
|
|
1481
|
+
});
|
|
1482
|
+
})();
|
|
1483
|
+
const projected = projectGltfLodMeta({
|
|
1484
|
+
rootSourceKey: rootOutput.sourceKey,
|
|
1485
|
+
levels: levels.map((level) => ({
|
|
1486
|
+
sourceKey: level.sourceKey,
|
|
1487
|
+
guid: level.meshGuid,
|
|
1488
|
+
...(level.screenCoverage === undefined ? {} : { screenCoverage: level.screenCoverage }),
|
|
1489
|
+
})),
|
|
1490
|
+
...(previousLodMeta === undefined ? {} : { previous: previousLodMeta }),
|
|
1491
|
+
});
|
|
1492
|
+
if (!projected.ok)
|
|
1493
|
+
return err(
|
|
1494
|
+
gltfErr('gltf-lod-invalid', {
|
|
1495
|
+
rootNode: lodGroup.rootNode,
|
|
1496
|
+
ids: lodGroup.lodNodeIds,
|
|
1497
|
+
reason: 'coverage',
|
|
1498
|
+
}),
|
|
1499
|
+
);
|
|
1500
|
+
sourceOverrides[rootOutput.sourceKey] = {
|
|
1501
|
+
...(sourceOverrides[rootOutput.sourceKey] ?? {}),
|
|
1502
|
+
lods: projected.value.lods,
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1410
1506
|
for (const meshIndex of seenMeshIndices) {
|
|
1411
1507
|
const meshOutput = subAssetByKindIndex.get(`mesh:${meshIndex}`);
|
|
1412
1508
|
if (meshOutput?.sourceKey === undefined) continue;
|
|
@@ -49,7 +49,9 @@ export interface GltfMetaJson {
|
|
|
49
49
|
readonly importer: 'gltf';
|
|
50
50
|
readonly source: string;
|
|
51
51
|
readonly subAssets: readonly GltfSubAssetEntry[];
|
|
52
|
-
readonly sourceOverrides?:
|
|
52
|
+
readonly sourceOverrides?:
|
|
53
|
+
| Readonly<Record<string, Readonly<Record<string, unknown>>>>
|
|
54
|
+
| undefined;
|
|
53
55
|
readonly sourceOverrideDescriptors?: readonly import('@forgeax/engine-types').SourceOverrideDescriptor[];
|
|
54
56
|
readonly importSettings: {
|
|
55
57
|
readonly defaultSceneIndex: number;
|