@forgeax/engine-gltf 0.1.23 → 0.1.25

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 (43) hide show
  1. package/README.md +56 -3
  2. package/dist/__tests__/physical-clearcoat.unit.test.d.ts +2 -0
  3. package/dist/__tests__/physical-clearcoat.unit.test.d.ts.map +1 -0
  4. package/dist/__tests__/physical-material-import.integration.test.d.ts +2 -0
  5. package/dist/__tests__/physical-material-import.integration.test.d.ts.map +1 -0
  6. package/dist/bridge.d.ts +1 -0
  7. package/dist/bridge.d.ts.map +1 -1
  8. package/dist/check-extensions.d.ts +1 -1
  9. package/dist/check-extensions.d.ts.map +1 -1
  10. package/dist/cli-gltf.mjs +181 -8
  11. package/dist/cli-gltf.mjs.map +1 -1
  12. package/dist/errors.d.ts +7 -0
  13. package/dist/errors.d.ts.map +1 -1
  14. package/dist/gltf-importer.d.ts.map +1 -1
  15. package/dist/image-color-space.d.ts +12 -0
  16. package/dist/image-color-space.d.ts.map +1 -1
  17. package/dist/importer-entry.mjs +377 -26
  18. package/dist/importer-entry.mjs.map +1 -1
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.mjs +378 -27
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/material/parse-material.d.ts +66 -0
  24. package/dist/material/parse-material.d.ts.map +1 -1
  25. package/dist/node-file-entry.mjs +181 -8
  26. package/dist/node-file-entry.mjs.map +1 -1
  27. package/package.json +12 -12
  28. package/src/__tests__/bridge-material-values.unit.test.ts +107 -1
  29. package/src/__tests__/bridge-skin-stride.unit.test.ts +22 -0
  30. package/src/__tests__/gltf-error-derived-views.test-d.ts +4 -0
  31. package/src/__tests__/gltf-error-migration.test.ts +3 -3
  32. package/src/__tests__/gltf.unit.test.ts +61 -6
  33. package/src/__tests__/material-pack-refs.integration.test.ts +53 -0
  34. package/src/__tests__/parse-gltf.unit.test.ts +29 -1
  35. package/src/__tests__/physical-clearcoat.unit.test.ts +110 -0
  36. package/src/__tests__/physical-material-import.integration.test.ts +239 -0
  37. package/src/bridge.ts +260 -15
  38. package/src/check-extensions.ts +16 -10
  39. package/src/errors.ts +21 -2
  40. package/src/gltf-importer.ts +35 -0
  41. package/src/image-color-space.ts +29 -4
  42. package/src/index.ts +8 -1
  43. package/src/material/parse-material.ts +307 -0
package/README.md CHANGED
@@ -21,6 +21,36 @@ the render phase. The carrier validates the source, Pack, GUID load, and rendere
21
21
 
22
22
  If a source requests a UV set that the primitive does not provide, handle `gltf-material-uv-set-missing` using its material, primitive, slot, requested set, and available sets. Add the source UV set and re-import; do not substitute a custom mesh or discard the slot transform.
23
23
 
24
+ ### KHR physical material extensions
25
+
26
+ The importer accepts the five second-stage material extensions below in both
27
+ `extensionsUsed` and `extensionsRequired`. A required extension outside the
28
+ supported set fails closed with `gltf-extension-unsupported`; a supported
29
+ extension is projected into the same Standard `MaterialAsset` root rather than
30
+ an extension-specific runtime type.
31
+
32
+ | Extension | Standard values / slots | Channel and color-space rule | Admission / recovery |
33
+ |:--|:--|:--|:--|
34
+ | `KHR_materials_clearcoat` | `clearcoat`, `clearcoatRoughness`, `clearcoatTexture`, `clearcoatRoughnessTexture`, `clearcoatNormalTexture`, `clearcoatNormalScale` | weight R, roughness G, coat normal RG; data textures are linear | coat normal uses the mesh tangent producer |
35
+ | `KHR_materials_anisotropy` | `anisotropyStrength`, `anisotropyRotation`, `anisotropyTexture` | direction RG, strength B; linear data | scalar and mapped forms both require a tangent frame |
36
+ | `KHR_materials_sheen` | `sheenColor`, `sheenRoughness`, `sheenColorTexture`, `sheenRoughnessTexture` | color RGB is sRGB; roughness A is linear | preserve each slot's UV transform; repair `gltf-material-uv-set-missing` on conflict |
37
+ | `KHR_materials_iridescence` | `iridescence`, `iridescenceIor`, `iridescenceThicknessMinimum/Maximum`, `iridescenceTexture`, `iridescenceThicknessTexture` | strength R and thickness G; both maps are linear | minimum greater than maximum is accepted per glTF semantics |
38
+ | `KHR_materials_specular` | `specular`, `specularTexture`, `specularColor`, `specularColorTexture` | weight A is linear; color RGB is sRGB | texture declarations select the physical Forward contract |
39
+
40
+ All five paths preserve `textureInfo.texCoord` and
41
+ `KHR_texture_transform` into `MaterialTextureValue.coordinates`. A material
42
+ with any of these declarations owns an extended Standard root, so its scalar
43
+ defaults and only its authored physical texture bindings participate in cook,
44
+ reflection, Pack refs, Catalog/load, and runtime sampling.
45
+
46
+ > [!IMPORTANT]
47
+ > `KHR_materials_anisotropy` always needs a finite tangent frame, even when
48
+ > only `anisotropyStrength` or `anisotropyRotation` is authored. If `TANGENT`
49
+ > is absent, the producer may generate it from `NORMAL + TEXCOORD_n + triangle
50
+ > topology`; if those inputs are missing or degenerate, import returns the
51
+ > structured `material-tangent-required` error. The importer persists generated
52
+ > tangents in `MeshAsset.attributes.tangent` and never writes an identity guess.
53
+
24
54
  > Runtime glTF 2.0 importer (Tier-C subset). Pure-function pipeline `parseGlb` / `parseGltf` / `toAssetPack` consumed by build-time CLI plugin bin `forgeax-engine-remote-gltf` (resolved via PATH-prefix scan for `forgeax-engine-remote-`) writing `<source>.meta.json` (external-asset-package; dispatch on top-level `importer: 'gltf'`); runtime spawn happens via the existing `loadByGuid<SceneAsset>` plus `world.instantiateScene` 4-step recipe (no `loadGltf(url)` parallel API).
25
55
 
26
56
  > [!IMPORTANT]
@@ -39,7 +69,7 @@ This package consumes:
39
69
  - `scenes` + `nodes` (TRS or matrix decomposed via `mat4.decompose` wrapper)
40
70
  - `meshes` with **multiple primitives** -- each primitive produces an independent `MeshIr` with UUIDv7 GUID; `SceneAsset` nodes reference individual primitive-level mesh sub-assets
41
71
  - Vertex attributes: `POSITION` (VEC3, mandatory), `NORMAL` (VEC3), `TEXCOORD_0` (VEC2), `TANGENT` (VEC4, optional), and `COLOR_0` (see the support matrix below) -- decoded via the accessor SoA path; `INDICES` (U8/U16/U32 scalar — U8 widens to U16; U32 preserved, narrowed to U16 by bridge when maxIndex < 65536)
42
- - `materials` with metallic-roughness PBR mapping to pass-based `MaterialAsset` (see MaterialIr table below)
72
+ - `materials` with metallic-roughness and five KHR physical extensions mapped to pass-based `MaterialAsset` (see MaterialIr table below)
43
73
  - `textures` / `images` / `samplers` top-level arrays parsed into `GltfDoc` IR; texture index -> image index -> URI two-hop resolution via `externalLoader`
44
74
  - `cameras` of `type: 'perspective'`
45
75
 
@@ -102,6 +132,21 @@ The complete closed error/detail union remains owned by
102
132
  | `metallicRoughnessTexture` | `number` (texture index) | no | Same two-hop resolution |
103
133
  | `normalTexture` | `number` (texture index) | no | Same two-hop resolution; TANGENT optional decode |
104
134
 
135
+ The physical extension fields are projected into the same IR object:
136
+
137
+ | Extension | Scalar fields | Texture fields |
138
+ |:--|:--|:--|
139
+ | `KHR_materials_clearcoat` | `clearcoatFactor`, `clearcoatRoughnessFactor` | `clearcoatTexture`, `clearcoatRoughnessTexture`, `clearcoatNormalTexture` |
140
+ | `KHR_materials_anisotropy` | `anisotropyStrength`, `anisotropyRotation` | `anisotropyTexture` |
141
+ | `KHR_materials_sheen` | `sheenColorFactor`, `sheenRoughnessFactor` | `sheenColorTexture`, `sheenRoughnessTexture` |
142
+ | `KHR_materials_iridescence` | `iridescenceFactor`, `iridescenceIor`, `iridescenceThicknessMinimum/Maximum` | `iridescenceTexture`, `iridescenceThicknessTexture` |
143
+ | `KHR_materials_specular` | `specularFactor`, `specularColorFactor` | `specularTexture`, `specularColorTexture` |
144
+
145
+ Each texture field is a `GltfTextureInfoIr | number` with an explicit texture
146
+ index and optional UV/transform facts. The bridge creates a complete extended
147
+ root for the declarations it sees; base-only materials may still inherit the
148
+ configured `standardMaterialGuid`.
149
+
105
150
  ### GltfDoc IR (textures / images / samplers)
106
151
 
107
152
  | Array | Element | Notes |
@@ -119,7 +164,7 @@ only its slot index. Canonical `SceneAsset` nodes carry `MeshFilter` plus an
119
164
  empty `MeshRenderer.materials` override vector, so imported defaults remain
120
165
  mesh-owned and reimport-safe.
121
166
 
122
- Out of scope (each routed to its own `feat-future-*` anchor in `requirements.md` OOS-1 .. OOS-15): KHR extensions other than the supported `EXT_mesh_gpu_instancing` and `KHR_texture_transform` paths / morph targets other than the explicit `COLOR_0` deferred signal / orthographic camera / sparse accessors / inspector future fields / pixel-parity vs three.js. Dense interleaved `COLOR_0` is supported; other interleaved accessor consumers retain their existing scope. v1.1 OOS additions (locked by feat-20260518-gltf-instancing-and-name-component): multi-primitive instancing / mesh-level + material-level + scene-level Name (only node.name lands as ECS `Name`) / instancing hard cap / SoA TRS direct-to-GPU pipe / IR-to-GPU direct path / ROTATION BYTE/SHORT normalized encoding / Babylon thin-instances style SoA channel / Bevy multi-tier Name propagation.
167
+ Out of scope (each routed to its own `feat-future-*` anchor in `requirements.md` OOS-1 .. OOS-15): KHR material extensions other than the supported transmission/IOR/volume, clearcoat, anisotropy, sheen, iridescence, and specular paths / morph targets other than the explicit `COLOR_0` deferred signal / orthographic camera / sparse accessors / inspector future fields / pixel-parity vs three.js. Dense interleaved `COLOR_0` is supported; other interleaved accessor consumers retain their existing scope. v1.1 OOS additions (locked by feat-20260518-gltf-instancing-and-name-component): multi-primitive instancing / mesh-level + material-level + scene-level Name (only node.name lands as ECS `Name`) / instancing hard cap / SoA TRS direct-to-GPU pipe / IR-to-GPU direct path / ROTATION BYTE/SHORT normalized encoding / Babylon thin-instances style SoA channel / Bevy multi-tier Name propagation.
123
168
 
124
169
  ## Importer sub-asset PODs (7 kinds)
125
170
 
@@ -150,7 +195,7 @@ Sample reference: `apps/hello/skin` -- 3 Khronos Fox foxes side-by-side, each ru
150
195
  | `gltf-malformed-header` | GLB magic / version / length header rejection or missing JSON chunk |
151
196
  | `gltf-version-unsupported` | `asset.version` is not `'2.0'` |
152
197
  | `gltf-buffer-out-of-bounds` | accessor reads past `bufferView.byteLength` |
153
- | `gltf-extension-unsupported` | `extensionsRequired[]` lists an extension outside the supported allowlist (`EXT_mesh_gpu_instancing`, `KHR_texture_transform`) |
198
+ | `gltf-extension-unsupported` | `extensionsRequired[]` lists an extension outside the supported allowlist exported as `EXTENSION_ALLOWLIST` (`EXT_mesh_gpu_instancing`, `EXT_meshopt_compression`, `KHR_lights_punctual`, `KHR_texture_transform`, transmission/IOR/volume, clearcoat, anisotropy, sheen, iridescence, specular) |
154
199
  | `gltf-accessor-type-mismatch` | sparse / morph / interleaved / unknown componentType accessor (4 reasons) |
155
200
  | `gltf-texture-load-failed` | `externalLoader` rejected for a texture `uri`; `detail.uri` carries the failing URI; hint: `'check sidecar meta.json + textures/ directory + vite-plugin-pack /__pack/lookup'` |
156
201
  | `gltf-meta-missing` | sidecar `<source>.meta.json` is absent next to the `.gltf` / `.glb` source file |
@@ -162,6 +207,14 @@ Sample reference: `apps/hello/skin` -- 3 Khronos Fox foxes side-by-side, each ru
162
207
  | `gltf-morph-unsupported` | animation channel targets morph weights (`path==='weights'`, OOS-skin-morph-anim) |
163
208
  | `gltf-color-accessor-unsupported` | `COLOR_0` type/component/normalized combination, sparse input, or morph-target input is deferred |
164
209
  | `gltf-color-accessor-malformed` | `COLOR_0` count, finite/range, buffer bounds, or reference validation failed |
210
+ | `gltf-material-physical-invalid` | a clearcoat/anisotropy/sheen/iridescence/specular scalar or color violates its finite glTF range |
211
+
212
+ Mesh tangent admission is a material error because the geometry producer owns
213
+ the repairable frame. Branch on `material-tangent-required` and read its
214
+ `detail.material`, `detail.mesh`, `detail.layer`, `detail.uv`,
215
+ `detail.attributes`, and `detail.reason`; fix the source attributes and rerun
216
+ the same importer. Do not parse the human-facing message or silently continue
217
+ with an identity tangent.
165
218
 
166
219
  Source-key conflicts are producer failures, not automatic renames. A
167
220
  `duplicate-source-key` or `ambiguous-source-key` detail includes the semantic
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=physical-clearcoat.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"physical-clearcoat.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/physical-clearcoat.unit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=physical-material-import.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"physical-material-import.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/physical-material-import.integration.test.ts"],"names":[],"mappings":""}
package/dist/bridge.d.ts CHANGED
@@ -98,6 +98,7 @@ export interface MaterialBridgeContext {
98
98
  readonly skinned?: boolean;
99
99
  }
100
100
  export declare function validateMaterialUvSets(mat: GltfMaterialIr, primitive: string, availableSets: readonly number[]): Result<void, MaterialError>;
101
+ export declare function validateMaterialTangentInputs(mat: GltfMaterialIr, mesh: GltfMeshIr, layer?: string): Result<void, MaterialError>;
101
102
  /** Convert a parsed GltfMaterialIr into a standard-root derived MaterialAsset. */
102
103
  export declare function toMaterialAsset(mat: GltfMaterialIr, ctx?: MaterialBridgeContext): MaterialAsset;
103
104
  //# sourceMappingURL=bridge.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EACV,SAAS,EACT,MAAM,EAEN,aAAa,EACb,aAAa,EAEb,SAAS,EAIT,MAAM,EACN,UAAU,EAIX,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAA4B,KAAK,SAAS,EAAe,MAAM,aAAa,CAAC;AACpF,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,UAAU,EACV,UAAU,EAGX,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,UAAU,EAAE,EAC5B,SAAS,GAAE;IACT,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnD,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpD,GACL,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CA0U9B;AAED,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IACzE,4DAA4D;IAC5D,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjF;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChE;AA+DD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB,GAAG,UAAU,CA8NpF;AAED,uFAAuF;AACvF,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC;AAEtC,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,QAAQ,CAAC,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC;IAChF,0DAA0D;IAC1D,QAAQ,CAAC,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC;IAChF,8EAA8E;IAC9E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC;IACtC;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAuDD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,cAAc,EACnB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,CA4B7B;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,qBAAqB,GAAG,aAAa,CAiF/F"}
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EACV,SAAS,EACT,MAAM,EAEN,aAAa,EACb,aAAa,EAEb,SAAS,EAIT,MAAM,EACN,UAAU,EAIX,MAAM,uBAAuB,CAAC;AAQ/B,OAAO,EAA4B,KAAK,SAAS,EAAe,MAAM,aAAa,CAAC;AACpF,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,UAAU,EACV,UAAU,EAGX,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,UAAU,EAAE,EAC5B,SAAS,GAAE;IACT,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnD,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpD,GACL,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CA0V9B;AAED,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IACzE,4DAA4D;IAC5D,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjF;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChE;AA+DD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB,GAAG,UAAU,CA8NpF;AAED,uFAAuF;AACvF,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC;AAEtC,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,QAAQ,CAAC,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC;IAChF,0DAA0D;IAC1D,QAAQ,CAAC,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAC;IAChF,8EAA8E;IAC9E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC;IACtC;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAuKD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,cAAc,EACnB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,CAsC7B;AAED,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,UAAU,EAChB,KAAK,SAAc,GAClB,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,CAoD7B;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,qBAAqB,GAAG,aAAa,CA2H/F"}
@@ -1,5 +1,5 @@
1
1
  import { type GltfError, type Result } from './errors.js';
2
- /** Legacy v1 mesh-only list kept for callers that display that surface. */
2
+ /** Public supported extension list used by the required/used declaration gate. */
3
3
  export declare const EXTENSION_ALLOWLIST: readonly string[];
4
4
  export interface ExtensionsCheckResult {
5
5
  /** Names listed in extensionsUsed but not in the supported extension set. */
@@ -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,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"}
1
+ {"version":3,"file":"check-extensions.d.ts","sourceRoot":"","sources":["../src/check-extensions.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAO,KAAK,SAAS,EAAe,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAE5E,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAahD,CAAC;AAOF,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
@@ -25,8 +25,8 @@ var gltfErrorPolicy = {
25
25
  hint: "rebuild .gltf with valid bufferViews; check accessor index; ensure accessor.byteOffset + EFFECTIVE_STRIDE * (count - 1) + element_size <= bufferView.byteLength"
26
26
  },
27
27
  "gltf-extension-unsupported": {
28
- expected: "extension listed in v1 allowlist (see EXTENSION_ALLOWLIST in @forgeax/engine-gltf)",
29
- hint: "see feat-future-gltf-extensions-allowlist; remove this extension or wait for the allowlist to expand"
28
+ expected: "extension listed in the supported allowlist (see EXTENSION_ALLOWLIST in @forgeax/engine-gltf)",
29
+ hint: "remove the unsupported required extension or use a supported glTF extension; extensionsUsed-only entries remain diagnostic"
30
30
  },
31
31
  "gltf-lod-invalid": {
32
32
  expected: "MSFT_lod ids to reference unique existing node indices with valid coverage",
@@ -36,6 +36,10 @@ var gltfErrorPolicy = {
36
36
  expected: "KHR transmission, IOR, and volume values are finite and within their glTF ranges",
37
37
  hint: "repair the named glTF material extension value and re-import the source"
38
38
  },
39
+ "gltf-material-physical-invalid": {
40
+ expected: "KHR clearcoat, anisotropy, sheen, iridescence, and specular values are finite and within their glTF ranges",
41
+ hint: "repair the named physical material extension value and re-import the source"
42
+ },
39
43
  "gltf-accessor-type-mismatch": {
40
44
  expected: "dense fixed-stride accessor with supported componentType",
41
45
  hint: "sparse: see feat-future-gltf-sparse-accessor; morph: see feat-future-gltf-morph; interleaved: see feat-future-gltf-mesh-multi-section"
@@ -400,16 +404,21 @@ function decodeColorAccessor(input, flags = {}) {
400
404
  var EXTENSION_ALLOWLIST = [
401
405
  "EXT_mesh_gpu_instancing",
402
406
  "EXT_meshopt_compression",
403
- "MSFT_lod",
404
- "MSFT_screencoverage"
405
- ];
406
- var SUPPORTED_EXTENSIONS = [
407
- ...EXTENSION_ALLOWLIST,
408
407
  "KHR_lights_punctual",
409
408
  "KHR_texture_transform",
410
409
  "KHR_materials_transmission",
411
410
  "KHR_materials_ior",
412
- "KHR_materials_volume"
411
+ "KHR_materials_volume",
412
+ "KHR_materials_clearcoat",
413
+ "KHR_materials_anisotropy",
414
+ "KHR_materials_sheen",
415
+ "KHR_materials_iridescence",
416
+ "KHR_materials_specular"
417
+ ];
418
+ var SUPPORTED_EXTENSIONS = [
419
+ ...EXTENSION_ALLOWLIST,
420
+ "MSFT_lod",
421
+ "MSFT_screencoverage"
413
422
  ];
414
423
  function checkExtensions(json) {
415
424
  const required = json.extensionsRequired ?? [];
@@ -602,6 +611,11 @@ function parseMaterial(matJson, textures) {
602
611
  const transmission = matJson.extensions?.KHR_materials_transmission;
603
612
  const ior = matJson.extensions?.KHR_materials_ior;
604
613
  const volume = matJson.extensions?.KHR_materials_volume;
614
+ const clearcoat = matJson.extensions?.KHR_materials_clearcoat;
615
+ const anisotropy = matJson.extensions?.KHR_materials_anisotropy;
616
+ const sheen = matJson.extensions?.KHR_materials_sheen;
617
+ const iridescence = matJson.extensions?.KHR_materials_iridescence;
618
+ const specular = matJson.extensions?.KHR_materials_specular;
605
619
  const attenuationColor = tuple3(volume?.attenuationColor);
606
620
  const invalid = (extension, field, reason, actual) => err(
607
621
  gltfErr("gltf-material-transmission-invalid", {
@@ -611,6 +625,14 @@ function parseMaterial(matJson, textures) {
611
625
  ...actual === void 0 ? {} : { actual }
612
626
  })
613
627
  );
628
+ const physicalInvalid = (extension, field, reason, actual) => err(
629
+ gltfErr("gltf-material-physical-invalid", {
630
+ extension,
631
+ field,
632
+ reason,
633
+ ...actual === void 0 ? {} : { actual }
634
+ })
635
+ );
614
636
  if (transmission?.transmissionFactor !== void 0 && (typeof transmission.transmissionFactor !== "number" || !Number.isFinite(transmission.transmissionFactor))) {
615
637
  return invalid(
616
638
  "KHR_materials_transmission",
@@ -661,6 +683,98 @@ function parseMaterial(matJson, textures) {
661
683
  if (alphaMode === "BLEND" && (transmission?.transmissionFactor ?? 0) > 0) {
662
684
  return invalid("KHR_materials_transmission", "alphaMode", "blend", alphaMode);
663
685
  }
686
+ if (clearcoat?.clearcoatFactor !== void 0 && (typeof clearcoat.clearcoatFactor !== "number" || !Number.isFinite(clearcoat.clearcoatFactor))) {
687
+ return physicalInvalid(
688
+ "KHR_materials_clearcoat",
689
+ "clearcoatFactor",
690
+ "non-finite",
691
+ clearcoat.clearcoatFactor
692
+ );
693
+ }
694
+ if (clearcoat?.clearcoatFactor !== void 0 && (clearcoat.clearcoatFactor < 0 || clearcoat.clearcoatFactor > 1)) {
695
+ return physicalInvalid(
696
+ "KHR_materials_clearcoat",
697
+ "clearcoatFactor",
698
+ "range",
699
+ clearcoat.clearcoatFactor
700
+ );
701
+ }
702
+ if (clearcoat?.clearcoatRoughnessFactor !== void 0 && (typeof clearcoat.clearcoatRoughnessFactor !== "number" || !Number.isFinite(clearcoat.clearcoatRoughnessFactor))) {
703
+ return physicalInvalid(
704
+ "KHR_materials_clearcoat",
705
+ "clearcoatRoughnessFactor",
706
+ "non-finite",
707
+ clearcoat.clearcoatRoughnessFactor
708
+ );
709
+ }
710
+ if (clearcoat?.clearcoatRoughnessFactor !== void 0 && (clearcoat.clearcoatRoughnessFactor < 0 || clearcoat.clearcoatRoughnessFactor > 1)) {
711
+ return physicalInvalid(
712
+ "KHR_materials_clearcoat",
713
+ "clearcoatRoughnessFactor",
714
+ "range",
715
+ clearcoat.clearcoatRoughnessFactor
716
+ );
717
+ }
718
+ if (clearcoat?.clearcoatNormalTexture?.scale !== void 0 && (typeof clearcoat.clearcoatNormalTexture.scale !== "number" || !Number.isFinite(clearcoat.clearcoatNormalTexture.scale) || clearcoat.clearcoatNormalTexture.scale < 0)) {
719
+ return physicalInvalid(
720
+ "KHR_materials_clearcoat",
721
+ "clearcoatNormalTexture.scale",
722
+ "range",
723
+ clearcoat.clearcoatNormalTexture.scale
724
+ );
725
+ }
726
+ const validateUnit = (extension, field, value) => {
727
+ if (value === void 0) return void 0;
728
+ if (typeof value !== "number" || !Number.isFinite(value))
729
+ return physicalInvalid(extension, field, "non-finite", value);
730
+ if (value < 0 || value > 1) return physicalInvalid(extension, field, "range", value);
731
+ return void 0;
732
+ };
733
+ const validateFinite = (extension, field, value, minimum) => {
734
+ if (value === void 0) return void 0;
735
+ if (typeof value !== "number" || !Number.isFinite(value))
736
+ return physicalInvalid(extension, field, "non-finite", value);
737
+ if (minimum !== void 0 && value < minimum)
738
+ return physicalInvalid(extension, field, "range", value);
739
+ return void 0;
740
+ };
741
+ const validateColor = (extension, field, value) => {
742
+ if (value === void 0) return void 0;
743
+ if (value.length < 3 || value.slice(0, 3).some((channel) => typeof channel !== "number" || !Number.isFinite(channel))) {
744
+ return physicalInvalid(extension, field, "type", value);
745
+ }
746
+ if (value.slice(0, 3).some((channel) => channel < 0 || channel > 1)) {
747
+ return physicalInvalid(extension, field, "range", value);
748
+ }
749
+ return void 0;
750
+ };
751
+ const physicalChecks = [
752
+ validateUnit("KHR_materials_anisotropy", "anisotropyStrength", anisotropy?.anisotropyStrength),
753
+ validateFinite(
754
+ "KHR_materials_anisotropy",
755
+ "anisotropyRotation",
756
+ anisotropy?.anisotropyRotation
757
+ ),
758
+ validateUnit("KHR_materials_sheen", "sheenRoughnessFactor", sheen?.sheenRoughnessFactor),
759
+ validateColor("KHR_materials_sheen", "sheenColorFactor", sheen?.sheenColorFactor),
760
+ validateUnit("KHR_materials_iridescence", "iridescenceFactor", iridescence?.iridescenceFactor),
761
+ validateFinite("KHR_materials_iridescence", "iridescenceIor", iridescence?.iridescenceIor, 1),
762
+ validateFinite(
763
+ "KHR_materials_iridescence",
764
+ "iridescenceThicknessMinimum",
765
+ iridescence?.iridescenceThicknessMinimum,
766
+ 0
767
+ ),
768
+ validateFinite(
769
+ "KHR_materials_iridescence",
770
+ "iridescenceThicknessMaximum",
771
+ iridescence?.iridescenceThicknessMaximum,
772
+ 0
773
+ ),
774
+ validateUnit("KHR_materials_specular", "specularFactor", specular?.specularFactor),
775
+ validateColor("KHR_materials_specular", "specularColorFactor", specular?.specularColorFactor)
776
+ ];
777
+ for (const check of physicalChecks) if (check !== void 0) return check;
664
778
  return ok({
665
779
  ...matJson.name === void 0 ? {} : { name: matJson.name },
666
780
  baseColorFactor: baseColor4,
@@ -697,6 +811,65 @@ function parseMaterial(matJson, textures) {
697
811
  ...attenuationColor === void 0 ? {} : { attenuationColor },
698
812
  ...finiteOrUndefined(volume.attenuationDistance) === void 0 ? {} : { attenuationDistance: volume.attenuationDistance }
699
813
  },
814
+ ...clearcoat === void 0 ? {} : {
815
+ clearcoatFactor: clearcoat.clearcoatFactor ?? 0,
816
+ clearcoatChannel: 0,
817
+ clearcoatRoughnessFactor: clearcoat.clearcoatRoughnessFactor ?? 0,
818
+ clearcoatRoughnessChannel: 1,
819
+ clearcoatNormalChannel: [0, 1],
820
+ ...clearcoat.clearcoatTexture === void 0 ? {} : { clearcoatTexture: parseTextureInfo(clearcoat.clearcoatTexture, textures) },
821
+ ...clearcoat.clearcoatRoughnessTexture === void 0 ? {} : {
822
+ clearcoatRoughnessTexture: parseTextureInfo(
823
+ clearcoat.clearcoatRoughnessTexture,
824
+ textures
825
+ )
826
+ },
827
+ ...clearcoat.clearcoatNormalTexture === void 0 ? {} : {
828
+ clearcoatNormalTexture: {
829
+ ...parseTextureInfo(clearcoat.clearcoatNormalTexture, textures),
830
+ ...clearcoat.clearcoatNormalTexture.scale === void 0 ? {} : { scale: clearcoat.clearcoatNormalTexture.scale }
831
+ }
832
+ }
833
+ },
834
+ ...anisotropy === void 0 ? {} : {
835
+ anisotropyStrength: anisotropy.anisotropyStrength ?? 0,
836
+ anisotropyRotation: anisotropy.anisotropyRotation ?? 0,
837
+ anisotropyChannel: [0, 1, 2],
838
+ ...anisotropy.anisotropyTexture === void 0 ? {} : { anisotropyTexture: parseTextureInfo(anisotropy.anisotropyTexture, textures) }
839
+ },
840
+ ...sheen === void 0 ? {} : {
841
+ sheenColorFactor: tuple3(sheen.sheenColorFactor) ?? [0, 0, 0],
842
+ sheenColorChannel: [0, 1, 2],
843
+ sheenRoughnessFactor: sheen.sheenRoughnessFactor ?? 0,
844
+ sheenRoughnessChannel: 3,
845
+ ...sheen.sheenColorTexture === void 0 ? {} : { sheenColorTexture: parseTextureInfo(sheen.sheenColorTexture, textures) },
846
+ ...sheen.sheenRoughnessTexture === void 0 ? {} : { sheenRoughnessTexture: parseTextureInfo(sheen.sheenRoughnessTexture, textures) }
847
+ },
848
+ ...iridescence === void 0 ? {} : {
849
+ iridescenceFactor: iridescence.iridescenceFactor ?? 0,
850
+ iridescenceIor: iridescence.iridescenceIor ?? 1.3,
851
+ iridescenceThicknessMinimum: iridescence.iridescenceThicknessMinimum ?? 100,
852
+ iridescenceThicknessMaximum: iridescence.iridescenceThicknessMaximum ?? 400,
853
+ iridescenceChannel: 0,
854
+ iridescenceThicknessChannel: 1,
855
+ ...iridescence.iridescenceTexture === void 0 ? {} : { iridescenceTexture: parseTextureInfo(iridescence.iridescenceTexture, textures) },
856
+ ...iridescence.iridescenceThicknessTexture === void 0 ? {} : {
857
+ iridescenceThicknessTexture: parseTextureInfo(
858
+ iridescence.iridescenceThicknessTexture,
859
+ textures
860
+ )
861
+ }
862
+ },
863
+ ...specular === void 0 ? {} : {
864
+ specularFactor: specular.specularFactor ?? 1,
865
+ specularChannel: 3,
866
+ specularColorFactor: tuple3(specular.specularColorFactor) ?? [1, 1, 1],
867
+ specularColorChannel: [0, 1, 2],
868
+ ...specular.specularTexture === void 0 ? {} : { specularTexture: parseTextureInfo(specular.specularTexture, textures) },
869
+ ...specular.specularColorTexture === void 0 ? {} : {
870
+ specularColorTexture: parseTextureInfo(specular.specularColorTexture, textures)
871
+ }
872
+ },
700
873
  ...alphaMode === void 0 ? {} : { alphaMode },
701
874
  ...alphaCutoff === void 0 ? {} : { alphaCutoff },
702
875
  ...matJson.doubleSided === true ? { doubleSided: true } : {},