@forgeax/engine-gltf 0.1.23 → 0.1.24
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 +56 -3
- package/dist/__tests__/physical-clearcoat.unit.test.d.ts +2 -0
- package/dist/__tests__/physical-clearcoat.unit.test.d.ts.map +1 -0
- package/dist/__tests__/physical-material-import.integration.test.d.ts +2 -0
- package/dist/__tests__/physical-material-import.integration.test.d.ts.map +1 -0
- package/dist/bridge.d.ts +1 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/check-extensions.d.ts +1 -1
- package/dist/check-extensions.d.ts.map +1 -1
- package/dist/cli-gltf.mjs +181 -8
- package/dist/cli-gltf.mjs.map +1 -1
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/gltf-importer.d.ts.map +1 -1
- package/dist/image-color-space.d.ts +12 -0
- package/dist/image-color-space.d.ts.map +1 -1
- package/dist/importer-entry.mjs +373 -24
- package/dist/importer-entry.mjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +374 -25
- package/dist/index.mjs.map +1 -1
- package/dist/material/parse-material.d.ts +66 -0
- package/dist/material/parse-material.d.ts.map +1 -1
- package/dist/node-file-entry.mjs +181 -8
- package/dist/node-file-entry.mjs.map +1 -1
- package/package.json +12 -12
- package/src/__tests__/bridge-material-values.unit.test.ts +84 -1
- package/src/__tests__/bridge-skin-stride.unit.test.ts +22 -0
- package/src/__tests__/gltf-error-derived-views.test-d.ts +4 -0
- package/src/__tests__/gltf-error-migration.test.ts +3 -3
- package/src/__tests__/gltf.unit.test.ts +52 -6
- package/src/__tests__/material-pack-refs.integration.test.ts +53 -0
- package/src/__tests__/parse-gltf.unit.test.ts +29 -1
- package/src/__tests__/physical-clearcoat.unit.test.ts +110 -0
- package/src/__tests__/physical-material-import.integration.test.ts +239 -0
- package/src/bridge.ts +258 -13
- package/src/check-extensions.ts +16 -10
- package/src/errors.ts +21 -2
- package/src/gltf-importer.ts +35 -0
- package/src/image-color-space.ts +29 -4
- package/src/index.ts +8 -1
- package/src/material/parse-material.ts +307 -0
package/src/errors.ts
CHANGED
|
@@ -73,6 +73,18 @@ export interface GltfMaterialTransmissionInvalidDetail {
|
|
|
73
73
|
readonly actual?: unknown;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
export interface GltfMaterialPhysicalInvalidDetail {
|
|
77
|
+
readonly extension:
|
|
78
|
+
| 'KHR_materials_clearcoat'
|
|
79
|
+
| 'KHR_materials_anisotropy'
|
|
80
|
+
| 'KHR_materials_sheen'
|
|
81
|
+
| 'KHR_materials_iridescence'
|
|
82
|
+
| 'KHR_materials_specular';
|
|
83
|
+
readonly field: string;
|
|
84
|
+
readonly reason: 'type' | 'range' | 'non-finite';
|
|
85
|
+
readonly actual?: unknown;
|
|
86
|
+
}
|
|
87
|
+
|
|
76
88
|
/** `gltf-accessor-type-mismatch` payload: 4-member closed reason discriminator. */
|
|
77
89
|
export interface GltfAccessorTypeMismatchDetail {
|
|
78
90
|
readonly accessorIndex: number;
|
|
@@ -266,8 +278,9 @@ const gltfErrorPolicy = {
|
|
|
266
278
|
hint: 'rebuild .gltf with valid bufferViews; check accessor index; ensure accessor.byteOffset + EFFECTIVE_STRIDE * (count - 1) + element_size <= bufferView.byteLength',
|
|
267
279
|
},
|
|
268
280
|
'gltf-extension-unsupported': {
|
|
269
|
-
expected:
|
|
270
|
-
|
|
281
|
+
expected:
|
|
282
|
+
'extension listed in the supported allowlist (see EXTENSION_ALLOWLIST in @forgeax/engine-gltf)',
|
|
283
|
+
hint: 'remove the unsupported required extension or use a supported glTF extension; extensionsUsed-only entries remain diagnostic',
|
|
271
284
|
},
|
|
272
285
|
'gltf-lod-invalid': {
|
|
273
286
|
expected: 'MSFT_lod ids to reference unique existing node indices with valid coverage',
|
|
@@ -277,6 +290,11 @@ const gltfErrorPolicy = {
|
|
|
277
290
|
expected: 'KHR transmission, IOR, and volume values are finite and within their glTF ranges',
|
|
278
291
|
hint: 'repair the named glTF material extension value and re-import the source',
|
|
279
292
|
},
|
|
293
|
+
'gltf-material-physical-invalid': {
|
|
294
|
+
expected:
|
|
295
|
+
'KHR clearcoat, anisotropy, sheen, iridescence, and specular values are finite and within their glTF ranges',
|
|
296
|
+
hint: 'repair the named physical material extension value and re-import the source',
|
|
297
|
+
},
|
|
280
298
|
'gltf-accessor-type-mismatch': {
|
|
281
299
|
expected: 'dense fixed-stride accessor with supported componentType',
|
|
282
300
|
hint: 'sparse: see feat-future-gltf-sparse-accessor; morph: see feat-future-gltf-morph; interleaved: see feat-future-gltf-mesh-multi-section',
|
|
@@ -387,6 +405,7 @@ interface DetailFor {
|
|
|
387
405
|
readonly 'gltf-color-accessor-malformed': GltfColorAccessorMalformedDetail;
|
|
388
406
|
readonly 'gltf-mesh-bridge-invalid': GltfMeshBridgeInvalidDetail;
|
|
389
407
|
readonly 'gltf-material-transmission-invalid': GltfMaterialTransmissionInvalidDetail;
|
|
408
|
+
readonly 'gltf-material-physical-invalid': GltfMaterialPhysicalInvalidDetail;
|
|
390
409
|
}
|
|
391
410
|
|
|
392
411
|
/**
|
package/src/gltf-importer.ts
CHANGED
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
gltfDocToSceneAsset,
|
|
60
60
|
meshIrToMeshAsset,
|
|
61
61
|
toMaterialAsset,
|
|
62
|
+
validateMaterialTangentInputs,
|
|
62
63
|
validateMaterialUvSets,
|
|
63
64
|
} from './bridge.js';
|
|
64
65
|
import { gltfErr } from './errors.js';
|
|
@@ -415,6 +416,16 @@ export function materialRefsForPack(
|
|
|
415
416
|
pushRefsForSlot(mat.emissiveTexture, 'emissiveTexture');
|
|
416
417
|
pushRefsForSlot(mat.transmissionTexture, 'transmissionTexture');
|
|
417
418
|
pushRefsForSlot(mat.thicknessTexture, 'thicknessTexture');
|
|
419
|
+
pushRefsForSlot(mat.clearcoatTexture, 'clearcoatTexture');
|
|
420
|
+
pushRefsForSlot(mat.clearcoatRoughnessTexture, 'clearcoatRoughnessTexture');
|
|
421
|
+
pushRefsForSlot(mat.clearcoatNormalTexture, 'clearcoatNormalTexture');
|
|
422
|
+
pushRefsForSlot(mat.anisotropyTexture, 'anisotropyTexture');
|
|
423
|
+
pushRefsForSlot(mat.sheenColorTexture, 'sheenColorTexture');
|
|
424
|
+
pushRefsForSlot(mat.sheenRoughnessTexture, 'sheenRoughnessTexture');
|
|
425
|
+
pushRefsForSlot(mat.iridescenceTexture, 'iridescenceTexture');
|
|
426
|
+
pushRefsForSlot(mat.iridescenceThicknessTexture, 'iridescenceThicknessTexture');
|
|
427
|
+
pushRefsForSlot(mat.specularTexture, 'specularTexture');
|
|
428
|
+
pushRefsForSlot(mat.specularColorTexture, 'specularColorTexture');
|
|
418
429
|
return refs;
|
|
419
430
|
}
|
|
420
431
|
|
|
@@ -444,6 +455,16 @@ function rewriteMaterialAssetRefs(
|
|
|
444
455
|
| 'emissiveTexture'
|
|
445
456
|
| 'transmissionTexture'
|
|
446
457
|
| 'thicknessTexture'
|
|
458
|
+
| 'clearcoatTexture'
|
|
459
|
+
| 'clearcoatRoughnessTexture'
|
|
460
|
+
| 'clearcoatNormalTexture'
|
|
461
|
+
| 'anisotropyTexture'
|
|
462
|
+
| 'sheenColorTexture'
|
|
463
|
+
| 'sheenRoughnessTexture'
|
|
464
|
+
| 'iridescenceTexture'
|
|
465
|
+
| 'iridescenceThicknessTexture'
|
|
466
|
+
| 'specularTexture'
|
|
467
|
+
| 'specularColorTexture'
|
|
447
468
|
),
|
|
448
469
|
GltfTextureInfoIr | number | undefined,
|
|
449
470
|
][] = [
|
|
@@ -454,6 +475,16 @@ function rewriteMaterialAssetRefs(
|
|
|
454
475
|
['emissiveTexture', mat.emissiveTexture],
|
|
455
476
|
['transmissionTexture', mat.transmissionTexture],
|
|
456
477
|
['thicknessTexture', mat.thicknessTexture],
|
|
478
|
+
['clearcoatTexture', mat.clearcoatTexture],
|
|
479
|
+
['clearcoatRoughnessTexture', mat.clearcoatRoughnessTexture],
|
|
480
|
+
['clearcoatNormalTexture', mat.clearcoatNormalTexture],
|
|
481
|
+
['anisotropyTexture', mat.anisotropyTexture],
|
|
482
|
+
['sheenColorTexture', mat.sheenColorTexture],
|
|
483
|
+
['sheenRoughnessTexture', mat.sheenRoughnessTexture],
|
|
484
|
+
['iridescenceTexture', mat.iridescenceTexture],
|
|
485
|
+
['iridescenceThicknessTexture', mat.iridescenceThicknessTexture],
|
|
486
|
+
['specularTexture', mat.specularTexture],
|
|
487
|
+
['specularColorTexture', mat.specularColorTexture],
|
|
457
488
|
];
|
|
458
489
|
let cursor = 0;
|
|
459
490
|
for (const [slot, rawBinding] of slots) {
|
|
@@ -750,6 +781,10 @@ async function importGltf(
|
|
|
750
781
|
if (!uvResult.ok) {
|
|
751
782
|
throw Object.assign(new Error(uvResult.error.message), uvResult.error);
|
|
752
783
|
}
|
|
784
|
+
const tangentResult = validateMaterialTangentInputs(mat, meshIr);
|
|
785
|
+
if (!tangentResult.ok) {
|
|
786
|
+
throw Object.assign(new Error(tangentResult.error.message), tangentResult.error);
|
|
787
|
+
}
|
|
753
788
|
}
|
|
754
789
|
const matAsset = toMaterialAsset(mat, {
|
|
755
790
|
textureHandles: maps.textureHandles,
|
package/src/image-color-space.ts
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
//
|
|
4
4
|
// glTF 2.0 spec section 6.2 ("Material → Texture and Sampler") names which
|
|
5
5
|
// texture slots are colour-encoded vs data-encoded:
|
|
6
|
-
// - sRGB : baseColorTexture, emissiveTexture
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
6
|
+
// - sRGB : baseColorTexture, emissiveTexture, sheenColorTexture,
|
|
7
|
+
// specularColorTexture
|
|
8
|
+
// - linear : metallicRoughnessTexture, normalTexture, occlusionTexture,
|
|
9
|
+
// transmission/volume/clearcoat/anisotropy/sheen-roughness/
|
|
10
|
+
// iridescence/specular-weight data textures
|
|
10
11
|
// We pre-scan the doc so the gltfImporter knows each `images[]` row's
|
|
11
12
|
// colorSpace before decoding (TextureAsset.colorSpace + .format derive
|
|
12
13
|
// from this).
|
|
@@ -36,6 +37,18 @@ export interface MaterialColorSpaceInput {
|
|
|
36
37
|
readonly normalTexture?: TextureBinding;
|
|
37
38
|
readonly emissiveTexture?: TextureBinding;
|
|
38
39
|
readonly occlusionTexture?: TextureBinding;
|
|
40
|
+
readonly transmissionTexture?: TextureBinding;
|
|
41
|
+
readonly thicknessTexture?: TextureBinding;
|
|
42
|
+
readonly clearcoatTexture?: TextureBinding;
|
|
43
|
+
readonly clearcoatRoughnessTexture?: TextureBinding;
|
|
44
|
+
readonly clearcoatNormalTexture?: TextureBinding;
|
|
45
|
+
readonly anisotropyTexture?: TextureBinding;
|
|
46
|
+
readonly sheenColorTexture?: TextureBinding;
|
|
47
|
+
readonly sheenRoughnessTexture?: TextureBinding;
|
|
48
|
+
readonly iridescenceTexture?: TextureBinding;
|
|
49
|
+
readonly iridescenceThicknessTexture?: TextureBinding;
|
|
50
|
+
readonly specularTexture?: TextureBinding;
|
|
51
|
+
readonly specularColorTexture?: TextureBinding;
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
/** Slim view of a parsed `textures[]` row. */
|
|
@@ -89,9 +102,21 @@ export function deriveTextureColorSpace(
|
|
|
89
102
|
for (const mat of input.materials) {
|
|
90
103
|
record(imageOfTexture(mat.baseColorTexture), 'srgb');
|
|
91
104
|
record(imageOfTexture(mat.emissiveTexture), 'srgb');
|
|
105
|
+
record(imageOfTexture(mat.sheenColorTexture), 'srgb');
|
|
106
|
+
record(imageOfTexture(mat.specularColorTexture), 'srgb');
|
|
92
107
|
record(imageOfTexture(mat.metallicRoughnessTexture), 'linear');
|
|
93
108
|
record(imageOfTexture(mat.normalTexture), 'linear');
|
|
94
109
|
record(imageOfTexture(mat.occlusionTexture), 'linear');
|
|
110
|
+
record(imageOfTexture(mat.transmissionTexture), 'linear');
|
|
111
|
+
record(imageOfTexture(mat.thicknessTexture), 'linear');
|
|
112
|
+
record(imageOfTexture(mat.clearcoatTexture), 'linear');
|
|
113
|
+
record(imageOfTexture(mat.clearcoatRoughnessTexture), 'linear');
|
|
114
|
+
record(imageOfTexture(mat.clearcoatNormalTexture), 'linear');
|
|
115
|
+
record(imageOfTexture(mat.anisotropyTexture), 'linear');
|
|
116
|
+
record(imageOfTexture(mat.sheenRoughnessTexture), 'linear');
|
|
117
|
+
record(imageOfTexture(mat.iridescenceTexture), 'linear');
|
|
118
|
+
record(imageOfTexture(mat.iridescenceThicknessTexture), 'linear');
|
|
119
|
+
record(imageOfTexture(mat.specularTexture), 'linear');
|
|
95
120
|
}
|
|
96
121
|
|
|
97
122
|
for (let i = 0; i < input.imageCount; i++) {
|
package/src/index.ts
CHANGED
|
@@ -47,7 +47,13 @@ export type { GltfBridgeContext, MaterialBridgeContext } from './bridge.js';
|
|
|
47
47
|
// hello-gltf + hello-gltf-instancing, feat-20260518 M3 w9; M3 Tier-C material
|
|
48
48
|
// bridge; meshIrToMeshAsset excised from the hello-gltf demo in
|
|
49
49
|
// feat-20260603-asset-import-loader-injection M2 w19).
|
|
50
|
-
export {
|
|
50
|
+
export {
|
|
51
|
+
gltfDocToSceneAsset,
|
|
52
|
+
meshIrToMeshAsset,
|
|
53
|
+
toMaterialAsset,
|
|
54
|
+
validateMaterialTangentInputs,
|
|
55
|
+
validateMaterialUvSets,
|
|
56
|
+
} from './bridge.js';
|
|
51
57
|
export type { ExtensionsCheckResult, GltfExtensionsJson } from './check-extensions.js';
|
|
52
58
|
// KHR extensions gate (w14).
|
|
53
59
|
export { checkExtensions, EXTENSION_ALLOWLIST } from './check-extensions.js';
|
|
@@ -65,6 +71,7 @@ export type {
|
|
|
65
71
|
GltfImageMimeUnsupportedDetail,
|
|
66
72
|
GltfInstancingCountMismatchDetail,
|
|
67
73
|
GltfMalformedHeaderDetail,
|
|
74
|
+
GltfMaterialPhysicalInvalidDetail,
|
|
68
75
|
GltfMaterialTransmissionInvalidDetail,
|
|
69
76
|
GltfMetaMissingDetail,
|
|
70
77
|
GltfMorphInvalidDetail,
|
|
@@ -93,6 +93,38 @@ export interface GltfMaterialJson {
|
|
|
93
93
|
readonly attenuationColor?: readonly number[];
|
|
94
94
|
readonly attenuationDistance?: number;
|
|
95
95
|
};
|
|
96
|
+
readonly KHR_materials_clearcoat?: {
|
|
97
|
+
readonly clearcoatFactor?: number;
|
|
98
|
+
readonly clearcoatTexture?: TextureInfoJson;
|
|
99
|
+
readonly clearcoatRoughnessFactor?: number;
|
|
100
|
+
readonly clearcoatRoughnessTexture?: TextureInfoJson;
|
|
101
|
+
readonly clearcoatNormalTexture?: NormalTextureInfoJson;
|
|
102
|
+
};
|
|
103
|
+
readonly KHR_materials_anisotropy?: {
|
|
104
|
+
readonly anisotropyStrength?: number;
|
|
105
|
+
readonly anisotropyRotation?: number;
|
|
106
|
+
readonly anisotropyTexture?: TextureInfoJson;
|
|
107
|
+
};
|
|
108
|
+
readonly KHR_materials_sheen?: {
|
|
109
|
+
readonly sheenColorFactor?: readonly number[];
|
|
110
|
+
readonly sheenColorTexture?: TextureInfoJson;
|
|
111
|
+
readonly sheenRoughnessFactor?: number;
|
|
112
|
+
readonly sheenRoughnessTexture?: TextureInfoJson;
|
|
113
|
+
};
|
|
114
|
+
readonly KHR_materials_iridescence?: {
|
|
115
|
+
readonly iridescenceFactor?: number;
|
|
116
|
+
readonly iridescenceIor?: number;
|
|
117
|
+
readonly iridescenceThicknessMinimum?: number;
|
|
118
|
+
readonly iridescenceThicknessMaximum?: number;
|
|
119
|
+
readonly iridescenceTexture?: TextureInfoJson;
|
|
120
|
+
readonly iridescenceThicknessTexture?: TextureInfoJson;
|
|
121
|
+
};
|
|
122
|
+
readonly KHR_materials_specular?: {
|
|
123
|
+
readonly specularFactor?: number;
|
|
124
|
+
readonly specularTexture?: TextureInfoJson;
|
|
125
|
+
readonly specularColorFactor?: readonly number[];
|
|
126
|
+
readonly specularColorTexture?: TextureInfoJson;
|
|
127
|
+
};
|
|
96
128
|
};
|
|
97
129
|
}
|
|
98
130
|
|
|
@@ -119,6 +151,40 @@ export interface GltfMaterialIr {
|
|
|
119
151
|
readonly thicknessChannel?: number;
|
|
120
152
|
readonly attenuationColor?: readonly [number, number, number];
|
|
121
153
|
readonly attenuationDistance?: number;
|
|
154
|
+
readonly clearcoatFactor?: number;
|
|
155
|
+
readonly clearcoatChannel?: number;
|
|
156
|
+
readonly clearcoatTexture?: GltfTextureInfoIr | number;
|
|
157
|
+
readonly clearcoatRoughnessFactor?: number;
|
|
158
|
+
readonly clearcoatRoughnessChannel?: number;
|
|
159
|
+
readonly clearcoatRoughnessTexture?: GltfTextureInfoIr | number;
|
|
160
|
+
readonly clearcoatNormalTexture?: GltfNormalTextureInfoIr | number;
|
|
161
|
+
readonly clearcoatNormalChannel?: readonly [number, number];
|
|
162
|
+
readonly anisotropyStrength?: number;
|
|
163
|
+
readonly anisotropyRotation?: number;
|
|
164
|
+
readonly anisotropyTexture?: GltfTextureInfoIr | number;
|
|
165
|
+
/** KHR_materials_anisotropy uses RG direction and B strength. */
|
|
166
|
+
readonly anisotropyChannel?: readonly [number, number, number];
|
|
167
|
+
readonly sheenColorFactor?: readonly [number, number, number];
|
|
168
|
+
readonly sheenColorTexture?: GltfTextureInfoIr | number;
|
|
169
|
+
readonly sheenColorChannel?: readonly [number, number, number];
|
|
170
|
+
readonly sheenRoughnessFactor?: number;
|
|
171
|
+
readonly sheenRoughnessTexture?: GltfTextureInfoIr | number;
|
|
172
|
+
readonly sheenRoughnessChannel?: number;
|
|
173
|
+
readonly iridescenceFactor?: number;
|
|
174
|
+
readonly iridescenceIor?: number;
|
|
175
|
+
readonly iridescenceThicknessMinimum?: number;
|
|
176
|
+
readonly iridescenceThicknessMaximum?: number;
|
|
177
|
+
readonly iridescenceTexture?: GltfTextureInfoIr | number;
|
|
178
|
+
readonly iridescenceChannel?: number;
|
|
179
|
+
readonly iridescenceThicknessTexture?: GltfTextureInfoIr | number;
|
|
180
|
+
readonly iridescenceThicknessChannel?: number;
|
|
181
|
+
readonly specularFactor?: number;
|
|
182
|
+
readonly specularTexture?: GltfTextureInfoIr | number;
|
|
183
|
+
/** KHR_materials_specular specularTexture uses the alpha channel. */
|
|
184
|
+
readonly specularChannel?: number;
|
|
185
|
+
readonly specularColorFactor?: readonly [number, number, number];
|
|
186
|
+
readonly specularColorTexture?: GltfTextureInfoIr | number;
|
|
187
|
+
readonly specularColorChannel?: readonly [number, number, number];
|
|
122
188
|
readonly alphaMode?: 'OPAQUE' | 'MASK' | 'BLEND';
|
|
123
189
|
readonly alphaCutoff?: number;
|
|
124
190
|
readonly doubleSided?: boolean;
|
|
@@ -186,6 +252,11 @@ export function parseMaterial(
|
|
|
186
252
|
const transmission = matJson.extensions?.KHR_materials_transmission;
|
|
187
253
|
const ior = matJson.extensions?.KHR_materials_ior;
|
|
188
254
|
const volume = matJson.extensions?.KHR_materials_volume;
|
|
255
|
+
const clearcoat = matJson.extensions?.KHR_materials_clearcoat;
|
|
256
|
+
const anisotropy = matJson.extensions?.KHR_materials_anisotropy;
|
|
257
|
+
const sheen = matJson.extensions?.KHR_materials_sheen;
|
|
258
|
+
const iridescence = matJson.extensions?.KHR_materials_iridescence;
|
|
259
|
+
const specular = matJson.extensions?.KHR_materials_specular;
|
|
189
260
|
const attenuationColor = tuple3(volume?.attenuationColor);
|
|
190
261
|
const invalid = (
|
|
191
262
|
extension: 'KHR_materials_transmission' | 'KHR_materials_ior' | 'KHR_materials_volume',
|
|
@@ -201,6 +272,25 @@ export function parseMaterial(
|
|
|
201
272
|
...(actual === undefined ? {} : { actual }),
|
|
202
273
|
}),
|
|
203
274
|
);
|
|
275
|
+
const physicalInvalid = (
|
|
276
|
+
extension:
|
|
277
|
+
| 'KHR_materials_clearcoat'
|
|
278
|
+
| 'KHR_materials_anisotropy'
|
|
279
|
+
| 'KHR_materials_sheen'
|
|
280
|
+
| 'KHR_materials_iridescence'
|
|
281
|
+
| 'KHR_materials_specular',
|
|
282
|
+
field: string,
|
|
283
|
+
reason: 'type' | 'range' | 'non-finite',
|
|
284
|
+
actual?: unknown,
|
|
285
|
+
): Result<GltfMaterialIr, GltfError> =>
|
|
286
|
+
err(
|
|
287
|
+
gltfErr('gltf-material-physical-invalid', {
|
|
288
|
+
extension,
|
|
289
|
+
field,
|
|
290
|
+
reason,
|
|
291
|
+
...(actual === undefined ? {} : { actual }),
|
|
292
|
+
}),
|
|
293
|
+
);
|
|
204
294
|
if (
|
|
205
295
|
transmission?.transmissionFactor !== undefined &&
|
|
206
296
|
(typeof transmission.transmissionFactor !== 'number' ||
|
|
@@ -270,6 +360,132 @@ export function parseMaterial(
|
|
|
270
360
|
if (alphaMode === 'BLEND' && (transmission?.transmissionFactor ?? 0) > 0) {
|
|
271
361
|
return invalid('KHR_materials_transmission', 'alphaMode', 'blend', alphaMode);
|
|
272
362
|
}
|
|
363
|
+
if (
|
|
364
|
+
clearcoat?.clearcoatFactor !== undefined &&
|
|
365
|
+
(typeof clearcoat.clearcoatFactor !== 'number' || !Number.isFinite(clearcoat.clearcoatFactor))
|
|
366
|
+
) {
|
|
367
|
+
return physicalInvalid(
|
|
368
|
+
'KHR_materials_clearcoat',
|
|
369
|
+
'clearcoatFactor',
|
|
370
|
+
'non-finite',
|
|
371
|
+
clearcoat.clearcoatFactor,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
if (
|
|
375
|
+
clearcoat?.clearcoatFactor !== undefined &&
|
|
376
|
+
(clearcoat.clearcoatFactor < 0 || clearcoat.clearcoatFactor > 1)
|
|
377
|
+
) {
|
|
378
|
+
return physicalInvalid(
|
|
379
|
+
'KHR_materials_clearcoat',
|
|
380
|
+
'clearcoatFactor',
|
|
381
|
+
'range',
|
|
382
|
+
clearcoat.clearcoatFactor,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (
|
|
386
|
+
clearcoat?.clearcoatRoughnessFactor !== undefined &&
|
|
387
|
+
(typeof clearcoat.clearcoatRoughnessFactor !== 'number' ||
|
|
388
|
+
!Number.isFinite(clearcoat.clearcoatRoughnessFactor))
|
|
389
|
+
) {
|
|
390
|
+
return physicalInvalid(
|
|
391
|
+
'KHR_materials_clearcoat',
|
|
392
|
+
'clearcoatRoughnessFactor',
|
|
393
|
+
'non-finite',
|
|
394
|
+
clearcoat.clearcoatRoughnessFactor,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
if (
|
|
398
|
+
clearcoat?.clearcoatRoughnessFactor !== undefined &&
|
|
399
|
+
(clearcoat.clearcoatRoughnessFactor < 0 || clearcoat.clearcoatRoughnessFactor > 1)
|
|
400
|
+
) {
|
|
401
|
+
return physicalInvalid(
|
|
402
|
+
'KHR_materials_clearcoat',
|
|
403
|
+
'clearcoatRoughnessFactor',
|
|
404
|
+
'range',
|
|
405
|
+
clearcoat.clearcoatRoughnessFactor,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
if (
|
|
409
|
+
clearcoat?.clearcoatNormalTexture?.scale !== undefined &&
|
|
410
|
+
(typeof clearcoat.clearcoatNormalTexture.scale !== 'number' ||
|
|
411
|
+
!Number.isFinite(clearcoat.clearcoatNormalTexture.scale) ||
|
|
412
|
+
clearcoat.clearcoatNormalTexture.scale < 0)
|
|
413
|
+
) {
|
|
414
|
+
return physicalInvalid(
|
|
415
|
+
'KHR_materials_clearcoat',
|
|
416
|
+
'clearcoatNormalTexture.scale',
|
|
417
|
+
'range',
|
|
418
|
+
clearcoat.clearcoatNormalTexture.scale,
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
const validateUnit = (
|
|
422
|
+
extension: Parameters<typeof physicalInvalid>[0],
|
|
423
|
+
field: string,
|
|
424
|
+
value: number | undefined,
|
|
425
|
+
) => {
|
|
426
|
+
if (value === undefined) return undefined;
|
|
427
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
428
|
+
return physicalInvalid(extension, field, 'non-finite', value);
|
|
429
|
+
if (value < 0 || value > 1) return physicalInvalid(extension, field, 'range', value);
|
|
430
|
+
return undefined;
|
|
431
|
+
};
|
|
432
|
+
const validateFinite = (
|
|
433
|
+
extension: Parameters<typeof physicalInvalid>[0],
|
|
434
|
+
field: string,
|
|
435
|
+
value: number | undefined,
|
|
436
|
+
minimum?: number,
|
|
437
|
+
) => {
|
|
438
|
+
if (value === undefined) return undefined;
|
|
439
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
440
|
+
return physicalInvalid(extension, field, 'non-finite', value);
|
|
441
|
+
if (minimum !== undefined && value < minimum)
|
|
442
|
+
return physicalInvalid(extension, field, 'range', value);
|
|
443
|
+
return undefined;
|
|
444
|
+
};
|
|
445
|
+
const validateColor = (
|
|
446
|
+
extension: Parameters<typeof physicalInvalid>[0],
|
|
447
|
+
field: string,
|
|
448
|
+
value: readonly number[] | undefined,
|
|
449
|
+
) => {
|
|
450
|
+
if (value === undefined) return undefined;
|
|
451
|
+
if (
|
|
452
|
+
value.length < 3 ||
|
|
453
|
+
value.slice(0, 3).some((channel) => typeof channel !== 'number' || !Number.isFinite(channel))
|
|
454
|
+
) {
|
|
455
|
+
return physicalInvalid(extension, field, 'type', value);
|
|
456
|
+
}
|
|
457
|
+
if (value.slice(0, 3).some((channel) => channel < 0 || channel > 1)) {
|
|
458
|
+
return physicalInvalid(extension, field, 'range', value);
|
|
459
|
+
}
|
|
460
|
+
return undefined;
|
|
461
|
+
};
|
|
462
|
+
const physicalChecks = [
|
|
463
|
+
validateUnit('KHR_materials_anisotropy', 'anisotropyStrength', anisotropy?.anisotropyStrength),
|
|
464
|
+
validateFinite(
|
|
465
|
+
'KHR_materials_anisotropy',
|
|
466
|
+
'anisotropyRotation',
|
|
467
|
+
anisotropy?.anisotropyRotation,
|
|
468
|
+
),
|
|
469
|
+
validateUnit('KHR_materials_sheen', 'sheenRoughnessFactor', sheen?.sheenRoughnessFactor),
|
|
470
|
+
validateColor('KHR_materials_sheen', 'sheenColorFactor', sheen?.sheenColorFactor),
|
|
471
|
+
validateUnit('KHR_materials_iridescence', 'iridescenceFactor', iridescence?.iridescenceFactor),
|
|
472
|
+
validateFinite('KHR_materials_iridescence', 'iridescenceIor', iridescence?.iridescenceIor, 1),
|
|
473
|
+
validateFinite(
|
|
474
|
+
'KHR_materials_iridescence',
|
|
475
|
+
'iridescenceThicknessMinimum',
|
|
476
|
+
iridescence?.iridescenceThicknessMinimum,
|
|
477
|
+
0,
|
|
478
|
+
),
|
|
479
|
+
validateFinite(
|
|
480
|
+
'KHR_materials_iridescence',
|
|
481
|
+
'iridescenceThicknessMaximum',
|
|
482
|
+
iridescence?.iridescenceThicknessMaximum,
|
|
483
|
+
0,
|
|
484
|
+
),
|
|
485
|
+
validateUnit('KHR_materials_specular', 'specularFactor', specular?.specularFactor),
|
|
486
|
+
validateColor('KHR_materials_specular', 'specularColorFactor', specular?.specularColorFactor),
|
|
487
|
+
];
|
|
488
|
+
for (const check of physicalChecks) if (check !== undefined) return check;
|
|
273
489
|
return ok({
|
|
274
490
|
...(matJson.name === undefined ? {} : { name: matJson.name }),
|
|
275
491
|
baseColorFactor: baseColor4,
|
|
@@ -330,6 +546,97 @@ export function parseMaterial(
|
|
|
330
546
|
? {}
|
|
331
547
|
: { attenuationDistance: volume.attenuationDistance }),
|
|
332
548
|
}),
|
|
549
|
+
...(clearcoat === undefined
|
|
550
|
+
? {}
|
|
551
|
+
: {
|
|
552
|
+
clearcoatFactor: clearcoat.clearcoatFactor ?? 0,
|
|
553
|
+
clearcoatChannel: 0,
|
|
554
|
+
clearcoatRoughnessFactor: clearcoat.clearcoatRoughnessFactor ?? 0,
|
|
555
|
+
clearcoatRoughnessChannel: 1,
|
|
556
|
+
clearcoatNormalChannel: [0, 1] as const,
|
|
557
|
+
...(clearcoat.clearcoatTexture === undefined
|
|
558
|
+
? {}
|
|
559
|
+
: { clearcoatTexture: parseTextureInfo(clearcoat.clearcoatTexture, textures) }),
|
|
560
|
+
...(clearcoat.clearcoatRoughnessTexture === undefined
|
|
561
|
+
? {}
|
|
562
|
+
: {
|
|
563
|
+
clearcoatRoughnessTexture: parseTextureInfo(
|
|
564
|
+
clearcoat.clearcoatRoughnessTexture,
|
|
565
|
+
textures,
|
|
566
|
+
),
|
|
567
|
+
}),
|
|
568
|
+
...(clearcoat.clearcoatNormalTexture === undefined
|
|
569
|
+
? {}
|
|
570
|
+
: {
|
|
571
|
+
clearcoatNormalTexture: {
|
|
572
|
+
...parseTextureInfo(clearcoat.clearcoatNormalTexture, textures),
|
|
573
|
+
...(clearcoat.clearcoatNormalTexture.scale === undefined
|
|
574
|
+
? {}
|
|
575
|
+
: { scale: clearcoat.clearcoatNormalTexture.scale }),
|
|
576
|
+
},
|
|
577
|
+
}),
|
|
578
|
+
}),
|
|
579
|
+
...(anisotropy === undefined
|
|
580
|
+
? {}
|
|
581
|
+
: {
|
|
582
|
+
anisotropyStrength: anisotropy.anisotropyStrength ?? 0,
|
|
583
|
+
anisotropyRotation: anisotropy.anisotropyRotation ?? 0,
|
|
584
|
+
anisotropyChannel: [0, 1, 2] as const,
|
|
585
|
+
...(anisotropy.anisotropyTexture === undefined
|
|
586
|
+
? {}
|
|
587
|
+
: { anisotropyTexture: parseTextureInfo(anisotropy.anisotropyTexture, textures) }),
|
|
588
|
+
}),
|
|
589
|
+
...(sheen === undefined
|
|
590
|
+
? {}
|
|
591
|
+
: {
|
|
592
|
+
sheenColorFactor: tuple3(sheen.sheenColorFactor) ?? [0, 0, 0],
|
|
593
|
+
sheenColorChannel: [0, 1, 2] as const,
|
|
594
|
+
sheenRoughnessFactor: sheen.sheenRoughnessFactor ?? 0,
|
|
595
|
+
sheenRoughnessChannel: 3,
|
|
596
|
+
...(sheen.sheenColorTexture === undefined
|
|
597
|
+
? {}
|
|
598
|
+
: { sheenColorTexture: parseTextureInfo(sheen.sheenColorTexture, textures) }),
|
|
599
|
+
...(sheen.sheenRoughnessTexture === undefined
|
|
600
|
+
? {}
|
|
601
|
+
: { sheenRoughnessTexture: parseTextureInfo(sheen.sheenRoughnessTexture, textures) }),
|
|
602
|
+
}),
|
|
603
|
+
...(iridescence === undefined
|
|
604
|
+
? {}
|
|
605
|
+
: {
|
|
606
|
+
iridescenceFactor: iridescence.iridescenceFactor ?? 0,
|
|
607
|
+
iridescenceIor: iridescence.iridescenceIor ?? 1.3,
|
|
608
|
+
iridescenceThicknessMinimum: iridescence.iridescenceThicknessMinimum ?? 100,
|
|
609
|
+
iridescenceThicknessMaximum: iridescence.iridescenceThicknessMaximum ?? 400,
|
|
610
|
+
iridescenceChannel: 0,
|
|
611
|
+
iridescenceThicknessChannel: 1,
|
|
612
|
+
...(iridescence.iridescenceTexture === undefined
|
|
613
|
+
? {}
|
|
614
|
+
: { iridescenceTexture: parseTextureInfo(iridescence.iridescenceTexture, textures) }),
|
|
615
|
+
...(iridescence.iridescenceThicknessTexture === undefined
|
|
616
|
+
? {}
|
|
617
|
+
: {
|
|
618
|
+
iridescenceThicknessTexture: parseTextureInfo(
|
|
619
|
+
iridescence.iridescenceThicknessTexture,
|
|
620
|
+
textures,
|
|
621
|
+
),
|
|
622
|
+
}),
|
|
623
|
+
}),
|
|
624
|
+
...(specular === undefined
|
|
625
|
+
? {}
|
|
626
|
+
: {
|
|
627
|
+
specularFactor: specular.specularFactor ?? 1,
|
|
628
|
+
specularChannel: 3,
|
|
629
|
+
specularColorFactor: tuple3(specular.specularColorFactor) ?? [1, 1, 1],
|
|
630
|
+
specularColorChannel: [0, 1, 2] as const,
|
|
631
|
+
...(specular.specularTexture === undefined
|
|
632
|
+
? {}
|
|
633
|
+
: { specularTexture: parseTextureInfo(specular.specularTexture, textures) }),
|
|
634
|
+
...(specular.specularColorTexture === undefined
|
|
635
|
+
? {}
|
|
636
|
+
: {
|
|
637
|
+
specularColorTexture: parseTextureInfo(specular.specularColorTexture, textures),
|
|
638
|
+
}),
|
|
639
|
+
}),
|
|
333
640
|
...(alphaMode === undefined ? {} : { alphaMode }),
|
|
334
641
|
...(alphaCutoff === undefined ? {} : { alphaCutoff }),
|
|
335
642
|
...(matJson.doubleSided === true ? { doubleSided: true } : {}),
|