@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
package/README.md
CHANGED
|
@@ -210,4 +210,63 @@ forgeax-engine-remote-gltf import apps/hello/gltf/assets/box.glb
|
|
|
210
210
|
forgeax-engine-remote-gltf import apps/hello/gltf/assets/ --check
|
|
211
211
|
```
|
|
212
212
|
|
|
213
|
+
### End-to-end source-to-Pack route
|
|
214
|
+
|
|
215
|
+
The CLI creates the authoring sidecar; the shared import runner is the single
|
|
216
|
+
Pack/DDC publication path. Keep these steps together when adding a glTF LOD:
|
|
217
|
+
|
|
218
|
+
```mermaid
|
|
219
|
+
flowchart LR
|
|
220
|
+
source["source .gltf/.glb"] --> cli["remote-gltf import"]
|
|
221
|
+
cli --> meta["source.meta.json"]
|
|
222
|
+
meta --> registry["ImporterRegistry + gltfImporter"]
|
|
223
|
+
registry --> runner["runImport"]
|
|
224
|
+
runner --> pack["Pack/DDC + Catalog"]
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
1. Run `forgeax-engine-remote-gltf import <source>` to create or refresh the
|
|
228
|
+
adjacent `<source>.meta.json` and its stable sub-asset GUIDs.
|
|
229
|
+
2. Register `gltfImporter` with the build-time `ImporterRegistry` and pass the
|
|
230
|
+
sidecar to `runImport`; this validates the declared LOD GUID closure and
|
|
231
|
+
writes the Pack/DDC output.
|
|
232
|
+
3. Verify the resulting Catalog/receipt and load the root GUID at runtime. The
|
|
233
|
+
complete `ImporterRegistry`/`runImport` example, including the filesystem
|
|
234
|
+
adapter, lives in [`@forgeax/engine-import`](../import/README.md#the-importload-split).
|
|
235
|
+
|
|
213
236
|
See `.forgeax-harness/forgeax-loop/feat-20260515-gltf-loader-via-asset-system/plan-strategy.md` for the full roadmap.
|
|
237
|
+
|
|
238
|
+
## LOD extensions
|
|
239
|
+
|
|
240
|
+
The glTF adapter admits `MSFT_lod` at node level. The extension's `ids` array
|
|
241
|
+
is preserved in producer order; each referenced root or lower node must resolve
|
|
242
|
+
to a non-empty mesh sub-asset. Material, name, and guessed suffix conventions
|
|
243
|
+
do not create LOD relations.
|
|
244
|
+
|
|
245
|
+
The portable Khronos form stores coverage on the root LOD node:
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"extensions": { "MSFT_lod": { "ids": [1, 2] } },
|
|
250
|
+
"extras": { "MSFT_screencoverage": [0.5, 0.2, 0.01] }
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The array describes the root and lower ranges and includes a terminal discard
|
|
255
|
+
threshold. The adapter keeps `[0.5, 0.2]` as the two lower-level absolute
|
|
256
|
+
`MeshAsset.lods[].screenCoverage` values and drops only that terminal discard
|
|
257
|
+
entry, which has no `MeshAsset` representation. The historical document-level
|
|
258
|
+
`extensions.MSFT_screencoverage.scales` form remains an internal fixture
|
|
259
|
+
adapter; node `extras` takes precedence when both forms are present.
|
|
260
|
+
|
|
261
|
+
| Source fact | Projection | Recovery |
|
|
262
|
+
|:--|:--|:--|
|
|
263
|
+
| `MSFT_lod.ids` | root MeshAsset `lods[]` and `refs[]` | repair node references and reimport |
|
|
264
|
+
| root `extras.MSFT_screencoverage` | absolute coverage values (terminal discard omitted) | repair values and reimport |
|
|
265
|
+
| legacy document `MSFT_screencoverage.scales` | explicit fixture coverage values | migrate to node `extras`, then recook |
|
|
266
|
+
| no coverage extension | shared importer defaults | keep existing sidecar values, then recook |
|
|
267
|
+
|
|
268
|
+
On malformed extension data, a required-but-missing `MSFT_lod` group, an empty
|
|
269
|
+
referenced mesh, or an unresolved mesh sub-asset, the importer returns
|
|
270
|
+
`gltf-lod-invalid` before publication. The old sidecar and Catalog LKG are the
|
|
271
|
+
recovery boundary; no relation is silently dropped by `flatMap` or a partial
|
|
272
|
+
Pack is published.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lod-import.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/lod-import.integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lod-meta-publication.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/lod-meta-publication.integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-extensions.d.ts","sourceRoot":"","sources":["../src/check-extensions.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAO,KAAK,SAAS,EAAe,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAE5E,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"check-extensions.d.ts","sourceRoot":"","sources":["../src/check-extensions.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAO,KAAK,SAAS,EAAe,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAE5E,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAKhD,CAAC;AAUF,MAAM,WAAW,qBAAqB;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,kBAAkB,GACvB,MAAM,CAAC,qBAAqB,EAAE,SAAS,CAAC,CAsB1C"}
|
package/dist/cli-gltf.mjs
CHANGED
|
@@ -3,8 +3,9 @@ import { realpath, stat, readFile, writeFile, readdir } from 'fs/promises';
|
|
|
3
3
|
import { resolve, dirname, join } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { parseArgs } from 'util';
|
|
6
|
-
import {
|
|
6
|
+
import { err, reconcileMeshMaterialSlotTopology, MESH_MATERIAL_SLOT_SOURCE_OVERRIDE_PAYLOAD_SCHEMA, ok, ImportError, IMPORT_ERROR_HINTS } from '@forgeax/engine-types';
|
|
7
7
|
import { vec3, quat, mat4 } from '@forgeax/engine-math';
|
|
8
|
+
import { deriveDefaultLodScreenCoverages, reconcileMeshLodMeta } from '@forgeax/engine-import';
|
|
8
9
|
import { deriveAnimationTargetId } from '@forgeax/engine-animation/target-id';
|
|
9
10
|
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
10
11
|
|
|
@@ -27,6 +28,10 @@ var gltfErrorPolicy = {
|
|
|
27
28
|
expected: "extension listed in v1 allowlist (see EXTENSION_ALLOWLIST in @forgeax/engine-gltf)",
|
|
28
29
|
hint: "see feat-future-gltf-extensions-allowlist; remove this extension or wait for the allowlist to expand"
|
|
29
30
|
},
|
|
31
|
+
"gltf-lod-invalid": {
|
|
32
|
+
expected: "MSFT_lod ids to reference unique existing node indices with valid coverage",
|
|
33
|
+
hint: "repair the MSFT_lod node relation or remove it from extensionsRequired before re-importing"
|
|
34
|
+
},
|
|
30
35
|
"gltf-material-transmission-invalid": {
|
|
31
36
|
expected: "KHR transmission, IOR, and volume values are finite and within their glTF ranges",
|
|
32
37
|
hint: "repair the named glTF material extension value and re-import the source"
|
|
@@ -394,7 +399,9 @@ function decodeColorAccessor(input, flags = {}) {
|
|
|
394
399
|
// src/check-extensions.ts
|
|
395
400
|
var EXTENSION_ALLOWLIST = [
|
|
396
401
|
"EXT_mesh_gpu_instancing",
|
|
397
|
-
"EXT_meshopt_compression"
|
|
402
|
+
"EXT_meshopt_compression",
|
|
403
|
+
"MSFT_lod",
|
|
404
|
+
"MSFT_screencoverage"
|
|
398
405
|
];
|
|
399
406
|
var SUPPORTED_EXTENSIONS = [
|
|
400
407
|
...EXTENSION_ALLOWLIST,
|
|
@@ -454,6 +461,103 @@ function decodeBase64(b64) {
|
|
|
454
461
|
return out;
|
|
455
462
|
}
|
|
456
463
|
|
|
464
|
+
// src/lod/parse-lod.ts
|
|
465
|
+
function parseGltfLodExtension(json) {
|
|
466
|
+
const doc = json;
|
|
467
|
+
const nodes = doc.nodes ?? [];
|
|
468
|
+
const rawCoverage = doc.extensions?.MSFT_screencoverage?.scales;
|
|
469
|
+
const groups = [];
|
|
470
|
+
for (let rootNode = 0; rootNode < nodes.length; rootNode += 1) {
|
|
471
|
+
if (nodes[rootNode]?.extensions?.MSFT_lod === void 0) continue;
|
|
472
|
+
const idsRaw = nodes[rootNode]?.extensions?.MSFT_lod?.ids;
|
|
473
|
+
if (!Array.isArray(idsRaw)) {
|
|
474
|
+
return err(gltfErr("gltf-lod-invalid", { rootNode, ids: [], reason: "not-integer" }));
|
|
475
|
+
}
|
|
476
|
+
const ids = idsRaw.filter((value) => typeof value === "number");
|
|
477
|
+
if (ids.length !== idsRaw.length || ids.some((id) => !Number.isInteger(id) || id < 0 || id >= nodes.length)) {
|
|
478
|
+
return err(gltfErr("gltf-lod-invalid", { rootNode, ids, reason: "missing-node" }));
|
|
479
|
+
}
|
|
480
|
+
if (new Set(ids).size !== ids.length || ids.includes(rootNode)) {
|
|
481
|
+
return err(gltfErr("gltf-lod-invalid", { rootNode, ids, reason: "duplicate-node" }));
|
|
482
|
+
}
|
|
483
|
+
const hasMesh = (node) => {
|
|
484
|
+
if (typeof node?.mesh !== "number" || !Number.isInteger(node.mesh) || node.mesh < 0) {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
const mesh = doc.meshes?.[node.mesh];
|
|
488
|
+
return mesh !== void 0 && Array.isArray(mesh.primitives) && mesh.primitives.length > 0;
|
|
489
|
+
};
|
|
490
|
+
if (!hasMesh(nodes[rootNode]) || ids.some((id) => !hasMesh(nodes[id]))) {
|
|
491
|
+
return err(gltfErr("gltf-lod-invalid", { rootNode, ids, reason: "missing-node" }));
|
|
492
|
+
}
|
|
493
|
+
const nodeCoverage = nodes[rootNode]?.extras?.MSFT_screencoverage;
|
|
494
|
+
const coverage = normalizeScreenCoverages(
|
|
495
|
+
nodeCoverage === void 0 ? rawCoverage : nodeCoverage,
|
|
496
|
+
ids.length,
|
|
497
|
+
nodeCoverage !== void 0
|
|
498
|
+
);
|
|
499
|
+
if (coverage === "invalid") {
|
|
500
|
+
return err(gltfErr("gltf-lod-invalid", { rootNode, ids, reason: "coverage" }));
|
|
501
|
+
}
|
|
502
|
+
groups.push({
|
|
503
|
+
rootNode,
|
|
504
|
+
lodNodeIds: ids,
|
|
505
|
+
screenCoverages: coverage ?? []
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
const first = groups[0];
|
|
509
|
+
if (first === void 0) {
|
|
510
|
+
if (doc.extensionsRequired?.some((extension) => extension === "MSFT_lod")) {
|
|
511
|
+
return err(gltfErr("gltf-lod-invalid", { rootNode: 0, ids: [], reason: "missing-node" }));
|
|
512
|
+
}
|
|
513
|
+
return ok({ rootNode: 0, lodNodeIds: [], screenCoverages: [], groups: [] });
|
|
514
|
+
}
|
|
515
|
+
return ok({
|
|
516
|
+
rootNode: first.rootNode,
|
|
517
|
+
lodNodeIds: first.lodNodeIds,
|
|
518
|
+
screenCoverages: first.screenCoverages,
|
|
519
|
+
groups
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
function normalizeScreenCoverages(raw, lodCount, officialNodeForm) {
|
|
523
|
+
if (raw === void 0) return void 0;
|
|
524
|
+
if (!Array.isArray(raw)) return "invalid";
|
|
525
|
+
if (raw.some(
|
|
526
|
+
(value) => typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > 1
|
|
527
|
+
)) {
|
|
528
|
+
return "invalid";
|
|
529
|
+
}
|
|
530
|
+
const expectedLengths = officialNodeForm ? [lodCount + 1] : [lodCount, lodCount + 1];
|
|
531
|
+
if (!expectedLengths.includes(raw.length)) return "invalid";
|
|
532
|
+
const values = raw.slice(0, lodCount);
|
|
533
|
+
for (let index = 1; index < values.length; index += 1) {
|
|
534
|
+
if (values[index] >= values[index - 1]) return "invalid";
|
|
535
|
+
}
|
|
536
|
+
return values;
|
|
537
|
+
}
|
|
538
|
+
function projectGltfLodMeta(input) {
|
|
539
|
+
const defaults = deriveDefaultLodScreenCoverages(input.levels.length + 1);
|
|
540
|
+
const previous = (input.previous ?? []).map((level) => ({
|
|
541
|
+
sourceKey: level.sourceKey,
|
|
542
|
+
meshGuid: level.guid,
|
|
543
|
+
...level.screenCoverage === void 0 ? {} : { screenCoverage: level.screenCoverage }
|
|
544
|
+
}));
|
|
545
|
+
const next = input.levels.map((level, index) => ({
|
|
546
|
+
sourceKey: level.sourceKey,
|
|
547
|
+
meshGuid: level.guid,
|
|
548
|
+
screenCoverage: level.screenCoverage ?? defaults[index] ?? 0
|
|
549
|
+
}));
|
|
550
|
+
const reconciled = reconcileMeshLodMeta(previous, next);
|
|
551
|
+
if (!reconciled.ok) return err({ code: reconciled.error.code, reason: reconciled.error.reason });
|
|
552
|
+
return ok({
|
|
553
|
+
lods: reconciled.value.lods.map((level) => ({
|
|
554
|
+
sourceKey: level.sourceKey,
|
|
555
|
+
meshGuid: level.meshGuid,
|
|
556
|
+
screenCoverage: level.screenCoverage
|
|
557
|
+
}))
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
457
561
|
// src/material/parse-material.ts
|
|
458
562
|
function tuple2(values) {
|
|
459
563
|
if (values === void 0 || values.length < 2) return void 0;
|
|
@@ -1479,6 +1583,8 @@ async function parseGltfWithBin(json, ctx) {
|
|
|
1479
1583
|
const extResult = checkExtensions(json);
|
|
1480
1584
|
if (!extResult.ok) return err(extResult.error);
|
|
1481
1585
|
const unsupportedExtensions = extResult.value.unsupportedUsed;
|
|
1586
|
+
const lodResult = parseGltfLodExtension(json);
|
|
1587
|
+
if (!lodResult.ok) return err(lodResult.error);
|
|
1482
1588
|
const lightsResult = parsePunctualLights(json, ctx.filePath);
|
|
1483
1589
|
if (!lightsResult.ok) return err(lightsResult.error);
|
|
1484
1590
|
const meshesJson = json.meshes ?? [];
|
|
@@ -2019,7 +2125,8 @@ async function parseGltfWithBin(json, ctx) {
|
|
|
2019
2125
|
matrixTrsCoexistNodes: diagnostics.matrixTrsCoexistNodes
|
|
2020
2126
|
},
|
|
2021
2127
|
meshPrimitiveCount,
|
|
2022
|
-
lights: lightsResult.value
|
|
2128
|
+
lights: lightsResult.value,
|
|
2129
|
+
...lodResult.value.lodNodeIds.length === 0 ? {} : { lod: lodResult.value }
|
|
2023
2130
|
});
|
|
2024
2131
|
}
|
|
2025
2132
|
async function parseGltfForImporter(json, externalLoader, filePath, options = {}) {
|
|
@@ -2158,6 +2265,74 @@ function toAssetPack(doc, existingMeta, source) {
|
|
|
2158
2265
|
const sourceOverrides = {
|
|
2159
2266
|
...existingMeta?.sourceOverrides ?? {}
|
|
2160
2267
|
};
|
|
2268
|
+
const lodGroups = doc.lod === void 0 ? [] : doc.lod.groups.length > 0 ? doc.lod.groups : [doc.lod];
|
|
2269
|
+
for (const lodGroup of lodGroups) {
|
|
2270
|
+
if (lodGroup.lodNodeIds.length === 0) continue;
|
|
2271
|
+
const rootMeshIndex = doc.nodes[lodGroup.rootNode]?.meshIndex;
|
|
2272
|
+
const referencedMeshIndices = [
|
|
2273
|
+
rootMeshIndex,
|
|
2274
|
+
...lodGroup.lodNodeIds.map((nodeIndex) => doc.nodes[nodeIndex]?.meshIndex)
|
|
2275
|
+
];
|
|
2276
|
+
if (referencedMeshIndices.some(
|
|
2277
|
+
(meshIndex) => !Number.isInteger(meshIndex) || meshIndex === null || meshIndex === void 0 || !seenMeshIndices.has(meshIndex)
|
|
2278
|
+
)) {
|
|
2279
|
+
return err(
|
|
2280
|
+
gltfErr("gltf-lod-invalid", {
|
|
2281
|
+
rootNode: lodGroup.rootNode,
|
|
2282
|
+
ids: lodGroup.lodNodeIds,
|
|
2283
|
+
reason: "missing-node"
|
|
2284
|
+
})
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
const rootOutput = rootMeshIndex === void 0 || rootMeshIndex === null ? void 0 : subAssetByKindIndex.get(`mesh:${rootMeshIndex}`);
|
|
2288
|
+
const levels = lodGroup.lodNodeIds.map((nodeIndex, index) => {
|
|
2289
|
+
const meshIndex = doc.nodes[nodeIndex]?.meshIndex;
|
|
2290
|
+
const output = meshIndex === void 0 || meshIndex === null ? void 0 : subAssetByKindIndex.get(`mesh:${meshIndex}`);
|
|
2291
|
+
return {
|
|
2292
|
+
sourceKey: output?.sourceKey ?? `mesh:${meshIndex ?? nodeIndex}`,
|
|
2293
|
+
meshGuid: output?.guid ?? "",
|
|
2294
|
+
...lodGroup.screenCoverages[index] === void 0 ? {} : { screenCoverage: lodGroup.screenCoverages[index] }
|
|
2295
|
+
};
|
|
2296
|
+
});
|
|
2297
|
+
if (rootOutput?.sourceKey !== void 0 && levels.every((level) => level.meshGuid !== "")) {
|
|
2298
|
+
const previousLodMeta = (() => {
|
|
2299
|
+
const raw = existingMeta?.sourceOverrides?.[rootOutput.sourceKey]?.lods;
|
|
2300
|
+
if (!Array.isArray(raw)) return void 0;
|
|
2301
|
+
return raw.flatMap((entry) => {
|
|
2302
|
+
if (entry === null || typeof entry !== "object") return [];
|
|
2303
|
+
const value = entry;
|
|
2304
|
+
return typeof value.sourceKey === "string" && typeof value.meshGuid === "string" ? [
|
|
2305
|
+
{
|
|
2306
|
+
sourceKey: value.sourceKey,
|
|
2307
|
+
guid: value.meshGuid,
|
|
2308
|
+
...typeof value.screenCoverage === "number" ? { screenCoverage: value.screenCoverage } : {}
|
|
2309
|
+
}
|
|
2310
|
+
] : [];
|
|
2311
|
+
});
|
|
2312
|
+
})();
|
|
2313
|
+
const projected = projectGltfLodMeta({
|
|
2314
|
+
rootSourceKey: rootOutput.sourceKey,
|
|
2315
|
+
levels: levels.map((level) => ({
|
|
2316
|
+
sourceKey: level.sourceKey,
|
|
2317
|
+
guid: level.meshGuid,
|
|
2318
|
+
...level.screenCoverage === void 0 ? {} : { screenCoverage: level.screenCoverage }
|
|
2319
|
+
})),
|
|
2320
|
+
...previousLodMeta === void 0 ? {} : { previous: previousLodMeta }
|
|
2321
|
+
});
|
|
2322
|
+
if (!projected.ok)
|
|
2323
|
+
return err(
|
|
2324
|
+
gltfErr("gltf-lod-invalid", {
|
|
2325
|
+
rootNode: lodGroup.rootNode,
|
|
2326
|
+
ids: lodGroup.lodNodeIds,
|
|
2327
|
+
reason: "coverage"
|
|
2328
|
+
})
|
|
2329
|
+
);
|
|
2330
|
+
sourceOverrides[rootOutput.sourceKey] = {
|
|
2331
|
+
...sourceOverrides[rootOutput.sourceKey] ?? {},
|
|
2332
|
+
lods: projected.value.lods
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2161
2336
|
for (const meshIndex of seenMeshIndices) {
|
|
2162
2337
|
const meshOutput = subAssetByKindIndex.get(`mesh:${meshIndex}`);
|
|
2163
2338
|
if (meshOutput?.sourceKey === void 0) continue;
|
|
@@ -2252,13 +2427,13 @@ function serializeMetaJson(meta) {
|
|
|
2252
2427
|
}
|
|
2253
2428
|
|
|
2254
2429
|
// src/cli-gltf.ts
|
|
2255
|
-
function emitError(ctx,
|
|
2430
|
+
function emitError(ctx, err3) {
|
|
2256
2431
|
const payload = {
|
|
2257
|
-
code:
|
|
2258
|
-
expected:
|
|
2259
|
-
hint:
|
|
2432
|
+
code: err3.code,
|
|
2433
|
+
expected: err3.expected,
|
|
2434
|
+
hint: err3.hint
|
|
2260
2435
|
};
|
|
2261
|
-
if (
|
|
2436
|
+
if (err3.detail !== void 0) payload.detail = err3.detail;
|
|
2262
2437
|
ctx.stderrWrite(JSON.stringify(payload));
|
|
2263
2438
|
return 1;
|
|
2264
2439
|
}
|