@forgeax/engine-import 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 +91 -3
- package/dist/__tests__/ies-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/ies-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/ies-producer.unit.test.d.ts +2 -0
- package/dist/__tests__/ies-producer.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-lod-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-lod-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-lod-reimport.integration.test.d.ts +2 -0
- package/dist/__tests__/mesh-lod-reimport.integration.test.d.ts.map +1 -0
- package/dist/__tests__/parameterized-scriptable-pack.unit.test.d.ts +2 -0
- package/dist/__tests__/parameterized-scriptable-pack.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-file-snapshot.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-file-snapshot.unit.test.d.ts.map +1 -0
- package/dist/__tests__/source-package-publication-lod.integration.test.d.ts +2 -0
- package/dist/__tests__/source-package-publication-lod.integration.test.d.ts.map +1 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.mjs +230 -3
- package/dist/browser.mjs.map +1 -1
- package/dist/build-production.d.ts +1 -8
- package/dist/build-production.d.ts.map +1 -1
- package/dist/ies/ies-importer.d.ts +4 -0
- package/dist/ies/ies-importer.d.ts.map +1 -0
- package/dist/ies/parse-lm63.d.ts +13 -0
- package/dist/ies/parse-lm63.d.ts.map +1 -0
- package/dist/ies/resample-type-c.d.ts +4 -0
- package/dist/ies/resample-type-c.d.ts.map +1 -0
- package/dist/import-runner.d.ts.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +2550 -1029
- package/dist/index.mjs.map +1 -1
- package/dist/mesh-bin.d.ts.map +1 -1
- package/dist/mesh-bin.mjs +29 -1
- package/dist/mesh-bin.mjs.map +1 -1
- package/dist/mesh-lod.d.ts +60 -0
- package/dist/mesh-lod.d.ts.map +1 -0
- package/dist/parameterized-scriptable-pack.d.ts +68 -0
- package/dist/parameterized-scriptable-pack.d.ts.map +1 -0
- package/dist/scriptable-pack-file-snapshot.d.ts +71 -0
- package/dist/scriptable-pack-file-snapshot.d.ts.map +1 -0
- package/dist/scriptable-pack-host.d.ts +70 -39
- package/dist/scriptable-pack-host.d.ts.map +1 -1
- package/dist/scriptable-pack-output-producers.d.ts +1 -0
- package/dist/scriptable-pack-output-producers.d.ts.map +1 -1
- package/dist/scriptable-pack-staged-snapshot.d.ts +2 -2
- package/dist/scriptable-pack-staged-snapshot.d.ts.map +1 -1
- package/dist/scriptable-pack.d.ts +13 -26
- package/dist/scriptable-pack.d.ts.map +1 -1
- package/dist/source-package-errors.d.ts.map +1 -1
- package/dist/source-package-publication.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/__tests__/ies-contract.unit.test.ts +31 -0
- package/src/__tests__/ies-producer.unit.test.ts +104 -0
- package/src/__tests__/mesh-bin.test.ts +69 -0
- package/src/__tests__/mesh-lod-contract.unit.test.ts +92 -0
- package/src/__tests__/mesh-lod-reimport.integration.test.ts +15 -0
- package/src/__tests__/parameterized-scriptable-pack.unit.test.ts +270 -0
- package/src/__tests__/scriptable-pack-file-snapshot.unit.test.ts +177 -0
- package/src/__tests__/scriptable-pack-host.unit.test.ts +196 -4
- package/src/__tests__/scriptable-pack-production-producers.unit.test.ts +7 -1
- package/src/__tests__/scriptable-pack-staged-snapshot.unit.test.ts +43 -12
- package/src/__tests__/source-package-publication-lod.integration.test.ts +29 -0
- package/src/__tests__/source-package-publication.integration.test.ts +26 -1
- package/src/browser.ts +5 -0
- package/src/build-production.ts +498 -141
- package/src/ies/ies-importer.ts +126 -0
- package/src/ies/parse-lm63.ts +98 -0
- package/src/ies/resample-type-c.ts +108 -0
- package/src/import-runner.ts +58 -0
- package/src/index.ts +49 -19
- package/src/mesh-bin.ts +38 -0
- package/src/mesh-lod.ts +265 -0
- package/src/parameterized-scriptable-pack.ts +725 -0
- package/src/scriptable-pack-file-snapshot.ts +346 -0
- package/src/scriptable-pack-host.ts +790 -343
- package/src/scriptable-pack-output-producers.ts +58 -1
- package/src/scriptable-pack-staged-snapshot.ts +12 -11
- package/src/scriptable-pack.ts +16 -485
- package/src/source-package-errors.ts +6 -0
- package/src/source-package-publication.ts +32 -2
- package/dist/.tsbuildinfo +0 -1
- package/dist/__tests__/scriptable-pack-product.contract.test.d.ts +0 -2
- package/dist/__tests__/scriptable-pack-product.contract.test.d.ts.map +0 -1
- package/dist/__tests__/scriptable-pack.integration.test.d.ts +0 -2
- package/dist/__tests__/scriptable-pack.integration.test.d.ts.map +0 -1
- package/dist/scriptable-source-package.d.ts +0 -14
- package/dist/scriptable-source-package.d.ts.map +0 -1
- package/src/__tests__/scriptable-pack-product.contract.test.ts +0 -46
- package/src/__tests__/scriptable-pack.integration.test.ts +0 -435
- package/src/scriptable-source-package.ts +0 -88
package/README.md
CHANGED
|
@@ -2,12 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## Authoring and recovery index
|
|
4
4
|
|
|
5
|
+
## 灯光资产 producer 入口
|
|
6
|
+
|
|
7
|
+
三条最短入口:
|
|
8
|
+
|
|
9
|
+
1. `RectAreaLight`、`SpotLight` 与 `LightProbe` 由 render 的 ECS schema 创作,scene producer 只外化 `shared<T>` GUID。
|
|
10
|
+
2. `iesImporter` 只在 build-time 接受 LM-63 Type C `TILT=NONE`,输出固定 cooked `IesProfileAsset`;runtime 不解析 `.ies` 文本。
|
|
11
|
+
3. `SpotLight.cookie` 走现有 `TextureAsset`/image producer;不要建立 Cookie registry 或第二条采样路径。
|
|
12
|
+
|
|
13
|
+
producer 持有 authored/source authority,Pack 产出 published 记录,Catalog 是 admitted/current 的投影;失败只返回结构化 `import-failed` 并保留原 GUID,修复 producer 后按同 GUID 重试。`lastKnownGood` 只可预览,不能冒充 verified/current。
|
|
14
|
+
|
|
15
|
+
Extended-lighting imports keep the same source key across Rect, Spot modifier,
|
|
16
|
+
Probe, and recovery carriers. IES is a build-time Type C `TILT=NONE` producer;
|
|
17
|
+
Cookie uses the existing texture producer; Probe and Sky are not new asset
|
|
18
|
+
registries. A failed publish is a structured producer result, and runtime must
|
|
19
|
+
not parse source text, invent a cooked payload, or promote LKG to current.
|
|
20
|
+
|
|
5
21
|
ScriptablePack sources use the public `@forgeax/engine-import` producer bridge.
|
|
6
22
|
The output is the same Pack v2 durable payload used by ordinary importers. The
|
|
7
|
-
|
|
23
|
+
17 `SCRIPTABLE_PACK_ASSET_KINDS` are `mesh`, `material`, `scene`, `texture`,
|
|
8
24
|
`equirect`, `sampler`, `font`, `render-pipeline`, `tileset`, `video`,
|
|
9
25
|
`skeleton`, `skin`, `animation-clip`, `animation-graph`, `audio`, and
|
|
10
|
-
`particle-effect`; all are loadable by GUID, while `refs`,
|
|
26
|
+
`particle-effect`, and `ies-profile`; all are loadable by GUID, while `refs`,
|
|
11
27
|
`artifacts`, `mediaType`, and `programFingerprint` remain producer facts.
|
|
12
28
|
Producer or dependency failures return `code`, `expected`, `hint`, and
|
|
13
29
|
`detail`; inspect the owning evidence, rebuild or cold-cook, and retry.
|
|
@@ -23,6 +39,28 @@ The import contract is [`asset-authority.schema.json`](../../asset-authority.sch
|
|
|
23
39
|
|
|
24
40
|
The Importer owns neither DDC lifecycle nor Editor authoring writes. Editor writes go through its asset-authoring gateway, and runtime consumes the validated projection.
|
|
25
41
|
|
|
42
|
+
### Parameterized Pack generation
|
|
43
|
+
|
|
44
|
+
`buildParameterizedScriptablePack()` accepts a Pack v2 definition, resolves the
|
|
45
|
+
default/inherited values, and executes `build()` once for the current subject
|
|
46
|
+
identity. The result is a dynamic `sourceKey -> Asset` map; the bridge derives
|
|
47
|
+
`AssetGuid` values from `(subjectPackageId, sourceKey)` and sends the resulting
|
|
48
|
+
assets through the same registered output producers and Pack v2 finalizer used
|
|
49
|
+
by ordinary imports.
|
|
50
|
+
|
|
51
|
+
`buildParameterizedScriptablePackWorklist()` is the internal clean-generation
|
|
52
|
+
fixed point. Subjects are evaluated in stable order, a forward `readByGuid()`
|
|
53
|
+
content miss waits for the subject that can materialize that GUID, and a pass
|
|
54
|
+
with no progress returns `pack-content-dependency-stalled`. Once all subjects
|
|
55
|
+
settle, the bridge validates references and incoming-reference deletions before
|
|
56
|
+
publication. A GUID written into a payload is only a reference dependency;
|
|
57
|
+
only `readByGuid()` creates a content dependency.
|
|
58
|
+
|
|
59
|
+
`produceParameterizedScriptablePackProducts()` and
|
|
60
|
+
`materializePreparedParameterizedScriptablePack()` adapt that result to the
|
|
61
|
+
existing DDC/Catalog/Pack v2 publication tuple. No parameter, parent, sourceKey,
|
|
62
|
+
or TypeScript execution contract crosses into the runtime package.
|
|
63
|
+
|
|
26
64
|
Build-time asset **import** runner + `ImporterRegistry` — the build-time half of
|
|
27
65
|
the engine's import/load split.
|
|
28
66
|
|
|
@@ -103,7 +141,7 @@ flowchart LR
|
|
|
103
141
|
|
|
104
142
|
Domain producers return structured `ImportError`; the generic bridge contains no per-kind switch. `createStandardAssetOutputProducerRegistry(sceneComponents)` composes the production Scene, Material, and Mesh owners with the definition-bound scene schema. Scene refs use the ECS externalization kernel shared with runtime save, Material refs cover parent/texture/sampler GUIDs, and Mesh emits the versioned binary artifact. The fingerprint covers canonical Meta, the definition-bound scene schema, the recursive source closure, observed external evidence, authoring contract version, and registered producer versions.
|
|
105
143
|
|
|
106
|
-
`createScriptablePackStagedAssetSnapshotSource()` owns clean-build content reads. It resolves a GUID to its local source owner, lazily builds that owner inside one fixed generation, memoizes private clones and digests, and reports `pack-
|
|
144
|
+
`createScriptablePackStagedAssetSnapshotSource()` owns clean-build content reads. It resolves a GUID to its local source owner, lazily builds that owner inside one fixed generation, memoizes private clones and digests, and reports `pack-content-dependency-stalled` for a cycle without consulting an older fallback generation. A fallback is only an explicit prebuilt authority for GUIDs with no local owner.
|
|
107
145
|
|
|
108
146
|
`createStandardAssetOutputProducerRegistry(sceneComponents)` registers the production
|
|
109
147
|
Material, Mesh, and Scene producers. Material output derives
|
|
@@ -132,6 +170,13 @@ binary metadata.
|
|
|
132
170
|
> so the importer cannot publish a partial mesh product. v2/v3 mesh binaries
|
|
133
171
|
> are not compatibility inputs.
|
|
134
172
|
|
|
173
|
+
For prebuilt ordinary Pack dependencies, use
|
|
174
|
+
`createScriptablePackFileAssetSnapshotSource({ assetRoots })` from the same
|
|
175
|
+
package. It recursively indexes validated `.pack.json` assets by GUID and
|
|
176
|
+
returns private `ScriptablePackAssetSnapshot` clones with deterministic
|
|
177
|
+
generation/digest evidence; missing, malformed, unreadable, and colliding
|
|
178
|
+
inputs remain structured `Result` errors.
|
|
179
|
+
|
|
135
180
|
At the low-level `buildScriptablePack` boundary, `assetSource` is optional only
|
|
136
181
|
when `build` never calls `reader.readByGuid`. The first attempted content read
|
|
137
182
|
without a source returns structured `asset-not-found`; no empty snapshot or
|
|
@@ -220,8 +265,51 @@ Result boundary. A digest-capability refusal during CookProduct finalization
|
|
|
220
265
|
does not publish a partial product; repair that capability and retry the same
|
|
221
266
|
Meta/GUID through the same `ImporterRegistry`.
|
|
222
267
|
|
|
268
|
+
### Source-format LOD adapters
|
|
269
|
+
|
|
270
|
+
Format adapters project producer facts into the same `MeshAsset.lods` and Pack
|
|
271
|
+
closure. The adapter owns interpretation; the sidecar owns retained author
|
|
272
|
+
facts and the importer only produces validated derived output.
|
|
273
|
+
|
|
274
|
+
| Source fact | Normalized result | Required closure |
|
|
275
|
+
|:--|:--|:--|
|
|
276
|
+
| glTF `MSFT_lod` node relation | ordered root and lower mesh GUIDs | root `refs[]` contains every lower mesh |
|
|
277
|
+
| FBX `FbxLODGroup` level relation | ordered root and lower mesh GUIDs | root `refs[]` contains every lower mesh |
|
|
278
|
+
| sidecar `sourceOverrides.mesh.lods` | retained `screenCoverage` and source keys | every GUID resolves in the same package |
|
|
279
|
+
|
|
280
|
+
On first import the helper derives lower-level coverage with
|
|
281
|
+
|
|
282
|
+
$$c_i = round6(0.5 \times 0.4^{i-1})$$
|
|
283
|
+
|
|
284
|
+
for `i = 1 .. n`, where `n` is the number of lower levels. This yields
|
|
285
|
+
`0.5` for two total LODs, `0.5, 0.2` for three, and `0.5, 0.2, 0.08`
|
|
286
|
+
for four. Existing legal sidecar values always win; only a newly appended
|
|
287
|
+
suffix receives defaults. A changed middle source key or mesh GUID is rejected
|
|
288
|
+
and the previous sidecar plus last-known-good product remains authoritative.
|
|
289
|
+
|
|
223
290
|
The import package stops at build-time source conversion and product
|
|
224
291
|
publication. It does not write authoring state, own Editor operations, or add
|
|
225
292
|
runtime transport branches. A dev `ImportTransport` is an explicit host
|
|
226
293
|
adapter; a shipped bundle must carry its build-time product and keeps
|
|
227
294
|
`asset-not-imported` fail-fast semantics.
|
|
295
|
+
|
|
296
|
+
## Mesh LOD normalization and recovery
|
|
297
|
+
|
|
298
|
+
The shared helper in `src/mesh-lod.ts` owns validation, default coverage, and
|
|
299
|
+
prefix-stable reimport. For `n` lower levels, first-import defaults are derived
|
|
300
|
+
once with:
|
|
301
|
+
|
|
302
|
+
$$c_i = round6(0.5 * 0.4^{i-1}), 1 <= i <= n$$
|
|
303
|
+
|
|
304
|
+
Thus two levels produce `0.5`, three produce `0.5, 0.2`, and four produce
|
|
305
|
+
`0.5, 0.2, 0.08`. Existing legal sidecar values win; newly appended suffix
|
|
306
|
+
levels receive defaults. A middle sourceKey or GUID change is a structured
|
|
307
|
+
topology/authority error, so the previous sidecar and LKG remain intact.
|
|
308
|
+
|
|
309
|
+
```mermaid
|
|
310
|
+
flowchart LR
|
|
311
|
+
A["source plus Meta"] --> B["validate LOD facts"]
|
|
312
|
+
B -->|"valid"| C["reconcile prefix and defaults"]
|
|
313
|
+
C --> D["publish Pack and Catalog"]
|
|
314
|
+
B -->|"invalid"| E["restore old sidecar and LKG"]
|
|
315
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ies-contract.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ies-contract.unit.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ies-producer.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ies-producer.unit.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mesh-lod-contract.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/mesh-lod-contract.unit.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mesh-lod-reimport.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/mesh-lod-reimport.integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parameterized-scriptable-pack.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/parameterized-scriptable-pack.unit.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scriptable-pack-file-snapshot.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/scriptable-pack-file-snapshot.unit.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-package-publication-lod.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/source-package-publication-lod.integration.test.ts"],"names":[],"mappings":""}
|
package/dist/browser.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { IMPORT_ERROR_HINTS, ImportError } from '@forgeax/engine-types';
|
|
|
2
2
|
export { type DdcPack, type ImportRunnerFs, normaliseForPack, type RunImportMeta, type RunImportOk, type RunImportProductResult, type RunImportResult, runImport, SHADER_RESERVED_IMPORTER_KEY, } from './import-runner.js';
|
|
3
3
|
export { ImporterRegistry } from './importer-registry.js';
|
|
4
4
|
export { packMeshBinV4 } from './mesh-bin.js';
|
|
5
|
+
export { deriveDefaultLodScreenCoverages, reconcileMeshLodMeta, validateMeshLodContract, } from './mesh-lod.js';
|
|
5
6
|
//# sourceMappingURL=browser.d.ts.map
|
package/dist/browser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,cAAc,EACnB,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,SAAS,EACT,4BAA4B,GAC7B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,cAAc,EACnB,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,SAAS,EACT,4BAA4B,GAC7B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,+BAA+B,EAC/B,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,eAAe,CAAC"}
|
package/dist/browser.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { validateSourceOverrideMap, ImportError, IMPORT_ERROR_HINTS, canonicalizeSourceOverrides
|
|
1
|
+
import { err, ok, validateSourceOverrideMap, ImportError, IMPORT_ERROR_HINTS, canonicalizeSourceOverrides } from '@forgeax/engine-types';
|
|
2
2
|
export { IMPORT_ERROR_HINTS, ImportError } from '@forgeax/engine-types';
|
|
3
3
|
import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
|
|
4
4
|
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
@@ -113,6 +113,171 @@ function finalizeImportProducts(product, inputFingerprint) {
|
|
|
113
113
|
return products;
|
|
114
114
|
})();
|
|
115
115
|
}
|
|
116
|
+
function deriveDefaultLodScreenCoverages(levelCount) {
|
|
117
|
+
if (!Number.isInteger(levelCount) || levelCount < 1 || levelCount > 8) {
|
|
118
|
+
throw new RangeError("levelCount must be an integer in [1, 8]");
|
|
119
|
+
}
|
|
120
|
+
return Array.from(
|
|
121
|
+
{ length: levelCount - 1 },
|
|
122
|
+
(_, index) => Math.round(0.5 * 0.4 ** index * 1e6) / 1e6
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
function validateMeshLodContract(input) {
|
|
126
|
+
if (input.lods.length > 7) {
|
|
127
|
+
return err({
|
|
128
|
+
code: "mesh-lod-contract-invalid",
|
|
129
|
+
reason: "at most seven lower-detail levels are supported"
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (input.lodHysteresis !== void 0 && (!Number.isFinite(input.lodHysteresis) || input.lodHysteresis < 0 || input.lodHysteresis >= 1)) {
|
|
133
|
+
return err({
|
|
134
|
+
code: "mesh-lod-contract-invalid",
|
|
135
|
+
reason: "lodHysteresis must be finite and in [0, 1)"
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (input.generation !== void 0 && (!Number.isSafeInteger(input.generation) || input.generation < 0)) {
|
|
139
|
+
return err({
|
|
140
|
+
code: "mesh-lod-contract-invalid",
|
|
141
|
+
reason: "generation must be a non-negative safe integer"
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const guids = /* @__PURE__ */ new Set();
|
|
145
|
+
let previous = 1;
|
|
146
|
+
for (const level of input.lods) {
|
|
147
|
+
if (level.meshGuid.trim() === "" || guids.has(level.meshGuid)) {
|
|
148
|
+
return err({
|
|
149
|
+
code: "mesh-lod-contract-invalid",
|
|
150
|
+
reason: "LOD mesh GUIDs must be unique and non-empty"
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (!Number.isFinite(level.screenCoverage) || level.screenCoverage <= 0 || level.screenCoverage > 1) {
|
|
154
|
+
return err({
|
|
155
|
+
code: "mesh-lod-contract-invalid",
|
|
156
|
+
reason: "screenCoverage must be finite and in (0, 1]"
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (level.screenCoverage >= previous) {
|
|
160
|
+
return err({
|
|
161
|
+
code: "mesh-lod-contract-invalid",
|
|
162
|
+
reason: "screenCoverage must strictly decrease by level"
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
guids.add(level.meshGuid);
|
|
166
|
+
previous = level.screenCoverage;
|
|
167
|
+
}
|
|
168
|
+
if (input.refs !== void 0) {
|
|
169
|
+
const refs = new Set(input.refs);
|
|
170
|
+
for (const level of input.lods) {
|
|
171
|
+
if (!refs.has(level.meshGuid)) {
|
|
172
|
+
return err({
|
|
173
|
+
code: "mesh-lod-contract-invalid",
|
|
174
|
+
reason: "every lower-detail mesh GUID must be enclosed by root refs"
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (input.rootMeshGuid !== void 0 && input.refs !== void 0 && !input.refs.includes(input.rootMeshGuid)) {
|
|
180
|
+
return err({
|
|
181
|
+
code: "mesh-lod-contract-invalid",
|
|
182
|
+
reason: "root mesh GUID must be included in refs"
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (input.rootBounds !== void 0) {
|
|
186
|
+
if (!validBounds(input.rootBounds)) {
|
|
187
|
+
return err({
|
|
188
|
+
code: "mesh-lod-contract-invalid",
|
|
189
|
+
reason: "root bounds must be finite and ordered"
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
for (const bounds of input.lodBounds ?? []) {
|
|
193
|
+
if (!validBounds(bounds) || !encloses(input.rootBounds, bounds)) {
|
|
194
|
+
return err({
|
|
195
|
+
code: "mesh-lod-contract-invalid",
|
|
196
|
+
reason: "root bounds must enclose every lower-detail bounds"
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (input.lodBounds !== void 0 && input.lodBounds.length !== input.lods.length) {
|
|
202
|
+
return err({
|
|
203
|
+
code: "mesh-lod-contract-invalid",
|
|
204
|
+
reason: "lod bounds must cover every lower level"
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
if (input.rootMaterialSlots !== void 0 || input.lodMaterialSlots !== void 0) {
|
|
208
|
+
const rootSlots = input.rootMaterialSlots ?? [];
|
|
209
|
+
const lowerSlots = input.lodMaterialSlots ?? [];
|
|
210
|
+
if (lowerSlots.length !== input.lods.length || lowerSlots.some((slots) => !sameSlots(rootSlots, slots))) {
|
|
211
|
+
return err({
|
|
212
|
+
code: "mesh-lod-contract-invalid",
|
|
213
|
+
reason: "lower-detail material slots must preserve root sourceKey order"
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (input.relations !== void 0 && hasCycle(input.relations)) {
|
|
218
|
+
return err({ code: "mesh-lod-contract-invalid", reason: "LOD relations must be acyclic" });
|
|
219
|
+
}
|
|
220
|
+
return ok({ lods: input.lods });
|
|
221
|
+
}
|
|
222
|
+
function validBounds(bounds) {
|
|
223
|
+
const [minX, minY, minZ] = bounds.min;
|
|
224
|
+
const [maxX, maxY, maxZ] = bounds.max;
|
|
225
|
+
return [minX, minY, minZ, maxX, maxY, maxZ].every(Number.isFinite) && minX <= maxX && minY <= maxY && minZ <= maxZ;
|
|
226
|
+
}
|
|
227
|
+
function encloses(root, child) {
|
|
228
|
+
return child.min[0] >= root.min[0] && child.min[1] >= root.min[1] && child.min[2] >= root.min[2] && child.max[0] <= root.max[0] && child.max[1] <= root.max[1] && child.max[2] <= root.max[2];
|
|
229
|
+
}
|
|
230
|
+
function sameSlots(root, child) {
|
|
231
|
+
return root.length === child.length && root.every((slot, index) => slot.sourceKey === child[index]?.sourceKey);
|
|
232
|
+
}
|
|
233
|
+
function hasCycle(relations) {
|
|
234
|
+
const edges = /* @__PURE__ */ new Map();
|
|
235
|
+
for (const relation of relations)
|
|
236
|
+
edges.set(relation.from, [...edges.get(relation.from) ?? [], relation.to]);
|
|
237
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
238
|
+
const visited = /* @__PURE__ */ new Set();
|
|
239
|
+
const visit = (node) => {
|
|
240
|
+
if (visiting.has(node)) return true;
|
|
241
|
+
if (visited.has(node)) return false;
|
|
242
|
+
visiting.add(node);
|
|
243
|
+
for (const next of edges.get(node) ?? []) if (visit(next)) return true;
|
|
244
|
+
visiting.delete(node);
|
|
245
|
+
visited.add(node);
|
|
246
|
+
return false;
|
|
247
|
+
};
|
|
248
|
+
return [...edges.keys()].some(visit);
|
|
249
|
+
}
|
|
250
|
+
function reconcileMeshLodMeta(previous, next) {
|
|
251
|
+
const overlap = Math.min(previous.length, next.length);
|
|
252
|
+
for (let index = 0; index < overlap; index++) {
|
|
253
|
+
const oldEntry = previous[index];
|
|
254
|
+
const nextEntry = next[index];
|
|
255
|
+
if (oldEntry?.sourceKey !== nextEntry?.sourceKey) {
|
|
256
|
+
return err({
|
|
257
|
+
code: "mesh-lod-topology-change",
|
|
258
|
+
reason: "existing LOD source keys must remain prefix-stable",
|
|
259
|
+
previousIndices: [index],
|
|
260
|
+
nextIndices: [index]
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (oldEntry?.meshGuid !== nextEntry?.meshGuid) {
|
|
264
|
+
return err({
|
|
265
|
+
code: "mesh-lod-authority-conflict",
|
|
266
|
+
reason: `sourceKey ${nextEntry?.sourceKey ?? "<missing>"} changed mesh GUID`
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const defaults = deriveDefaultLodScreenCoverages(next.length + 1);
|
|
271
|
+
const lods = next.map((entry, index) => ({
|
|
272
|
+
...entry,
|
|
273
|
+
screenCoverage: previous[index]?.screenCoverage ?? entry.screenCoverage ?? defaults[index] ?? 0
|
|
274
|
+
}));
|
|
275
|
+
const valid = validateMeshLodContract({
|
|
276
|
+
lods: lods.map(({ meshGuid, screenCoverage }) => ({ meshGuid, screenCoverage }))
|
|
277
|
+
});
|
|
278
|
+
if (!valid.ok) return valid;
|
|
279
|
+
return ok({ lods });
|
|
280
|
+
}
|
|
116
281
|
|
|
117
282
|
// src/import-runner.ts
|
|
118
283
|
var SHADER_RESERVED_IMPORTER_KEY = "shader";
|
|
@@ -125,6 +290,9 @@ function isModuleLoadFailure(e) {
|
|
|
125
290
|
const msg = e.message;
|
|
126
291
|
return msg.includes("Cannot find module") || msg.includes("native addon") || msg.includes(".node");
|
|
127
292
|
}
|
|
293
|
+
function declarationsSourceKey(declarations, guid) {
|
|
294
|
+
return declarations.find((declaration) => declaration.guid === guid)?.sourceKey;
|
|
295
|
+
}
|
|
128
296
|
function normaliseForPack(value) {
|
|
129
297
|
if (value === null || value === void 0) return value;
|
|
130
298
|
if (value instanceof Float32Array || value instanceof Float64Array || value instanceof Uint8Array || value instanceof Uint16Array || value instanceof Uint32Array || value instanceof Int8Array || value instanceof Int16Array || value instanceof Int32Array) {
|
|
@@ -434,6 +602,37 @@ async function runImport(meta, registry, fs) {
|
|
|
434
602
|
);
|
|
435
603
|
}
|
|
436
604
|
const produced = product.assets;
|
|
605
|
+
for (const asset of produced) {
|
|
606
|
+
if (asset.kind !== "mesh" || asset.payload === null || typeof asset.payload !== "object")
|
|
607
|
+
continue;
|
|
608
|
+
const payload = asset.payload;
|
|
609
|
+
if (payload.lods === void 0) continue;
|
|
610
|
+
const lods = payload.lods.map((level) => ({
|
|
611
|
+
meshGuid: typeof level.meshGuid === "string" ? level.meshGuid : typeof level.mesh === "string" ? level.mesh : JSON.stringify(level.mesh),
|
|
612
|
+
screenCoverage: level.screenCoverage
|
|
613
|
+
}));
|
|
614
|
+
const validated = validateMeshLodContract({
|
|
615
|
+
lods,
|
|
616
|
+
...payload.lodHysteresis === void 0 ? {} : { lodHysteresis: payload.lodHysteresis }
|
|
617
|
+
});
|
|
618
|
+
if (!validated.ok) {
|
|
619
|
+
return errResult(
|
|
620
|
+
new ImportError({
|
|
621
|
+
code: validated.error.code,
|
|
622
|
+
expected: "MeshAsset LOD facts to satisfy the shared contract",
|
|
623
|
+
hint: IMPORT_ERROR_HINTS[validated.error.code],
|
|
624
|
+
detail: {
|
|
625
|
+
meshLodSourceKey: declarationsSourceKey(meta.subAssets, asset.guid),
|
|
626
|
+
reason: validated.error.reason,
|
|
627
|
+
...validated.error.code === "mesh-lod-topology-change" ? {
|
|
628
|
+
previousIndices: validated.error.previousIndices,
|
|
629
|
+
nextIndices: validated.error.nextIndices
|
|
630
|
+
} : {}
|
|
631
|
+
}
|
|
632
|
+
})
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
437
636
|
const declared = new Set(meta.subAssets.map((s) => s.guid));
|
|
438
637
|
const producedGuids = new Set(produced.map((a) => a.guid));
|
|
439
638
|
const unexpectedGuids = [...producedGuids].filter((g) => !declared.has(g));
|
|
@@ -651,12 +850,40 @@ function refsMeta(payload, refs) {
|
|
|
651
850
|
};
|
|
652
851
|
}
|
|
653
852
|
);
|
|
853
|
+
if (payload.lods !== void 0 && payload.lods.length > 7) {
|
|
854
|
+
throw new Error("MeshAsset LOD chain supports at most seven lower-detail levels");
|
|
855
|
+
}
|
|
856
|
+
let previousCoverage = 1;
|
|
857
|
+
const seenLodGuids = /* @__PURE__ */ new Set();
|
|
858
|
+
const lods = payload.lods?.map((lod, lodIndex) => {
|
|
859
|
+
const guid = AssetGuid.format(lod.mesh).toLowerCase();
|
|
860
|
+
const meshRef = refs.findIndex((candidate) => candidate.toLowerCase() === guid);
|
|
861
|
+
if (meshRef < 0) {
|
|
862
|
+
throw new Error(`LOD ${lodIndex} mesh ${guid} is absent from refs`);
|
|
863
|
+
}
|
|
864
|
+
if (seenLodGuids.has(guid)) {
|
|
865
|
+
throw new Error(`LOD ${lodIndex} mesh ${guid} is duplicated`);
|
|
866
|
+
}
|
|
867
|
+
if (!Number.isFinite(lod.screenCoverage) || lod.screenCoverage <= 0 || lod.screenCoverage > 1 || lod.screenCoverage >= previousCoverage) {
|
|
868
|
+
throw new Error(
|
|
869
|
+
`LOD ${lodIndex} screenCoverage must be finite, in (0, 1], and strictly decreasing`
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
seenLodGuids.add(guid);
|
|
873
|
+
previousCoverage = lod.screenCoverage;
|
|
874
|
+
return { meshRef, screenCoverage: lod.screenCoverage };
|
|
875
|
+
});
|
|
876
|
+
if (payload.lodHysteresis !== void 0 && (!Number.isFinite(payload.lodHysteresis) || payload.lodHysteresis < 0 || payload.lodHysteresis >= 1)) {
|
|
877
|
+
throw new Error("lodHysteresis must be finite and in [0, 1)");
|
|
878
|
+
}
|
|
654
879
|
return {
|
|
655
880
|
submeshes: payload.submeshes === void 0 || payload.submeshes.length === 0 ? [{ indexOffset: 0, indexCount: payload.indices?.length ?? 0, materialSlot: 0 }] : payload.submeshes,
|
|
656
881
|
materialSlots,
|
|
657
882
|
...payload.aabb === void 0 ? {} : { aabb: jsonValue(payload.aabb) },
|
|
658
883
|
...payload.morphTargets === void 0 ? {} : { morphTargets: jsonValue(payload.morphTargets) },
|
|
659
|
-
...payload.morphWeights === void 0 ? {} : { morphWeights: jsonValue(payload.morphWeights) }
|
|
884
|
+
...payload.morphWeights === void 0 ? {} : { morphWeights: jsonValue(payload.morphWeights) },
|
|
885
|
+
...lods === void 0 ? {} : { lods },
|
|
886
|
+
...payload.lodHysteresis === void 0 ? {} : { lodHysteresis: payload.lodHysteresis }
|
|
660
887
|
};
|
|
661
888
|
}
|
|
662
889
|
function packMeshBinV4(payload, sourceKey, refs = []) {
|
|
@@ -780,6 +1007,6 @@ function packMeshBinV4(payload, sourceKey, refs = []) {
|
|
|
780
1007
|
}
|
|
781
1008
|
}
|
|
782
1009
|
|
|
783
|
-
export { ImporterRegistry, SHADER_RESERVED_IMPORTER_KEY, normaliseForPack, packMeshBinV4, runImport };
|
|
1010
|
+
export { ImporterRegistry, SHADER_RESERVED_IMPORTER_KEY, deriveDefaultLodScreenCoverages, normaliseForPack, packMeshBinV4, reconcileMeshLodMeta, runImport, validateMeshLodContract };
|
|
784
1011
|
//# sourceMappingURL=browser.mjs.map
|
|
785
1012
|
//# sourceMappingURL=browser.mjs.map
|