@forgeax/engine-gltf 0.1.7 → 0.1.19

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 (39) hide show
  1. package/README.md +9 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/transmission-pack-carrier.integration.test.d.ts +2 -0
  4. package/dist/__tests__/transmission-pack-carrier.integration.test.d.ts.map +1 -0
  5. package/dist/bridge.d.ts.map +1 -1
  6. package/dist/check-extensions.d.ts +3 -3
  7. package/dist/check-extensions.d.ts.map +1 -1
  8. package/dist/cli-gltf.mjs +161 -69
  9. package/dist/cli-gltf.mjs.map +1 -1
  10. package/dist/errors.d.ts +7 -0
  11. package/dist/errors.d.ts.map +1 -1
  12. package/dist/gltf-importer.d.ts.map +1 -1
  13. package/dist/importer-entry.mjs +177 -72
  14. package/dist/importer-entry.mjs.map +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.mjs +177 -72
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/material/parse-material.d.ts +118 -0
  20. package/dist/material/parse-material.d.ts.map +1 -0
  21. package/dist/node-file-entry.mjs +161 -69
  22. package/dist/node-file-entry.mjs.map +1 -1
  23. package/dist/parse-gltf.d.ts +17 -103
  24. package/dist/parse-gltf.d.ts.map +1 -1
  25. package/package.json +12 -12
  26. package/src/__tests__/bridge-material-values.unit.test.ts +46 -0
  27. package/src/__tests__/gltf-error-derived-views.test-d.ts +4 -0
  28. package/src/__tests__/gltf-error-policy-owner.unit.test.ts +26 -178
  29. package/src/__tests__/gltf.unit.test.ts +28 -4
  30. package/src/__tests__/material-pack-refs.integration.test.ts +12 -0
  31. package/src/__tests__/parse-gltf.unit.test.ts +121 -0
  32. package/src/__tests__/transmission-pack-carrier.integration.test.ts +82 -0
  33. package/src/bridge.ts +12 -1
  34. package/src/check-extensions.ts +14 -9
  35. package/src/errors.ts +12 -0
  36. package/src/gltf-importer.ts +6 -0
  37. package/src/index.ts +1 -0
  38. package/src/material/parse-material.ts +342 -0
  39. package/src/parse-gltf.ts +50 -277
@@ -0,0 +1,342 @@
1
+ export interface GltfTextureIr {
2
+ readonly sampler?: number;
3
+ readonly source: number;
4
+ readonly name?: string;
5
+ }
6
+
7
+ export interface GltfImageIr {
8
+ readonly uri?: string;
9
+ readonly mimeType?: string;
10
+ readonly bufferView?: number;
11
+ readonly name?: string;
12
+ }
13
+
14
+ export interface GltfSamplerIr {
15
+ readonly magFilter?: number;
16
+ readonly minFilter?: number;
17
+ readonly wrapS: number;
18
+ readonly wrapT: number;
19
+ readonly name?: string;
20
+ }
21
+
22
+ export interface GltfTextureTransformIr {
23
+ readonly offset?: readonly [number, number];
24
+ readonly rotation?: number;
25
+ readonly scale?: readonly [number, number];
26
+ }
27
+
28
+ export interface GltfTextureInfoIr {
29
+ readonly texture: number;
30
+ readonly sampler?: number;
31
+ readonly texCoord?: number;
32
+ readonly transform?: GltfTextureTransformIr;
33
+ }
34
+
35
+ export interface GltfNormalTextureInfoIr extends GltfTextureInfoIr {
36
+ readonly scale?: number;
37
+ }
38
+
39
+ export interface GltfOcclusionTextureInfoIr extends GltfTextureInfoIr {
40
+ readonly strength?: number;
41
+ }
42
+
43
+ export interface TextureTransformJson {
44
+ readonly offset?: readonly number[];
45
+ readonly rotation?: number;
46
+ readonly scale?: readonly number[];
47
+ readonly texCoord?: number;
48
+ }
49
+
50
+ export interface TextureInfoJson {
51
+ readonly index: number;
52
+ readonly texCoord?: number;
53
+ readonly extensions?: {
54
+ readonly KHR_texture_transform?: TextureTransformJson;
55
+ };
56
+ }
57
+
58
+ export interface NormalTextureInfoJson extends TextureInfoJson {
59
+ readonly scale?: number;
60
+ }
61
+
62
+ export interface OcclusionTextureInfoJson extends TextureInfoJson {
63
+ readonly strength?: number;
64
+ }
65
+
66
+ export interface GltfMaterialJson {
67
+ readonly name?: string;
68
+ readonly pbrMetallicRoughness?: {
69
+ readonly baseColorFactor?: readonly number[];
70
+ readonly baseColorTexture?: TextureInfoJson;
71
+ readonly metallicFactor?: number;
72
+ readonly roughnessFactor?: number;
73
+ readonly metallicRoughnessTexture?: TextureInfoJson;
74
+ };
75
+ readonly normalTexture?: NormalTextureInfoJson;
76
+ readonly emissiveFactor?: readonly number[];
77
+ readonly emissiveTexture?: TextureInfoJson;
78
+ readonly occlusionTexture?: OcclusionTextureInfoJson;
79
+ readonly alphaMode?: string;
80
+ readonly alphaCutoff?: number;
81
+ readonly doubleSided?: boolean;
82
+ readonly extensions?: {
83
+ readonly KHR_materials_transmission?: {
84
+ readonly transmissionFactor?: number;
85
+ readonly transmissionTexture?: TextureInfoJson;
86
+ };
87
+ readonly KHR_materials_ior?: {
88
+ readonly ior?: number;
89
+ };
90
+ readonly KHR_materials_volume?: {
91
+ readonly thicknessFactor?: number;
92
+ readonly thicknessTexture?: TextureInfoJson;
93
+ readonly attenuationColor?: readonly number[];
94
+ readonly attenuationDistance?: number;
95
+ };
96
+ };
97
+ }
98
+
99
+ export interface GltfMaterialIr {
100
+ readonly name?: string;
101
+ readonly baseColorFactor: readonly [number, number, number, number];
102
+ /** glTF emissiveFactor; omitted means the glTF default [0, 0, 0]. */
103
+ readonly emissiveFactor?: readonly [number, number, number];
104
+ readonly baseColorTexture?: GltfTextureInfoIr | number;
105
+ readonly emissiveTexture?: GltfTextureInfoIr | number;
106
+ readonly metallicFactor: number;
107
+ readonly roughnessFactor: number;
108
+ readonly metallicRoughnessTexture?: GltfTextureInfoIr | number;
109
+ readonly normalTexture?: GltfNormalTextureInfoIr | number;
110
+ readonly occlusionTexture?: GltfOcclusionTextureInfoIr | number;
111
+ readonly transmissionFactor?: number;
112
+ readonly transmissionTexture?: GltfTextureInfoIr | number;
113
+ /** KHR_materials_transmission uses the red texture channel. */
114
+ readonly transmissionChannel?: number;
115
+ readonly ior?: number;
116
+ readonly thicknessFactor?: number;
117
+ readonly thicknessTexture?: GltfTextureInfoIr | number;
118
+ /** KHR_materials_volume uses the green texture channel. */
119
+ readonly thicknessChannel?: number;
120
+ readonly attenuationColor?: readonly [number, number, number];
121
+ readonly attenuationDistance?: number;
122
+ readonly alphaMode?: 'OPAQUE' | 'MASK' | 'BLEND';
123
+ readonly alphaCutoff?: number;
124
+ readonly doubleSided?: boolean;
125
+ readonly baseColorTexCoord?: number;
126
+ }
127
+
128
+ function tuple2(values: readonly number[] | undefined): readonly [number, number] | undefined {
129
+ if (values === undefined || values.length < 2) return undefined;
130
+ return [values[0] ?? 0, values[1] ?? 0];
131
+ }
132
+
133
+ function tuple3(
134
+ values: readonly number[] | undefined,
135
+ ): readonly [number, number, number] | undefined {
136
+ if (values === undefined || values.length < 3) return undefined;
137
+ return [values[0] ?? 0, values[1] ?? 0, values[2] ?? 0];
138
+ }
139
+
140
+ export function parseTextureInfo(
141
+ info: TextureInfoJson,
142
+ textures: readonly GltfTextureIr[],
143
+ ): GltfTextureInfoIr {
144
+ const transformJson = info.extensions?.KHR_texture_transform;
145
+ const offset = tuple2(transformJson?.offset);
146
+ const scale = tuple2(transformJson?.scale);
147
+ const transform =
148
+ offset === undefined && transformJson?.rotation === undefined && scale === undefined
149
+ ? undefined
150
+ : {
151
+ ...(offset === undefined ? {} : { offset }),
152
+ ...(transformJson?.rotation === undefined ? {} : { rotation: transformJson.rotation }),
153
+ ...(scale === undefined ? {} : { scale }),
154
+ };
155
+ const sampler = textures[info.index]?.sampler;
156
+ return {
157
+ texture: info.index,
158
+ ...(sampler === undefined ? {} : { sampler }),
159
+ ...(info.texCoord === undefined && transformJson?.texCoord === undefined
160
+ ? {}
161
+ : { texCoord: transformJson?.texCoord ?? info.texCoord }),
162
+ ...(transform === undefined ? {} : { transform }),
163
+ };
164
+ }
165
+
166
+ function finiteOrUndefined(value: number | undefined): number | undefined {
167
+ return value !== undefined && Number.isFinite(value) ? value : undefined;
168
+ }
169
+
170
+ export function parseMaterial(
171
+ matJson: GltfMaterialJson,
172
+ textures: readonly GltfTextureIr[],
173
+ ): Result<GltfMaterialIr, GltfError> {
174
+ const pbr = matJson.pbrMetallicRoughness;
175
+ const baseColor = pbr?.baseColorFactor ?? [1, 1, 1, 1];
176
+ const baseColor4: readonly [number, number, number, number] = [
177
+ baseColor[0] ?? 1,
178
+ baseColor[1] ?? 1,
179
+ baseColor[2] ?? 1,
180
+ baseColor[3] ?? 1,
181
+ ];
182
+ const alphaMode =
183
+ matJson.alphaMode === 'MASK' || matJson.alphaMode === 'BLEND' ? matJson.alphaMode : undefined;
184
+ const alphaCutoff = alphaMode === 'MASK' ? (matJson.alphaCutoff ?? 0.5) : undefined;
185
+ const emissiveFactor = tuple3(matJson.emissiveFactor);
186
+ const transmission = matJson.extensions?.KHR_materials_transmission;
187
+ const ior = matJson.extensions?.KHR_materials_ior;
188
+ const volume = matJson.extensions?.KHR_materials_volume;
189
+ const attenuationColor = tuple3(volume?.attenuationColor);
190
+ const invalid = (
191
+ extension: 'KHR_materials_transmission' | 'KHR_materials_ior' | 'KHR_materials_volume',
192
+ field: string,
193
+ reason: 'type' | 'range' | 'non-finite' | 'blend',
194
+ actual?: unknown,
195
+ ): Result<GltfMaterialIr, GltfError> =>
196
+ err(
197
+ gltfErr('gltf-material-transmission-invalid', {
198
+ extension,
199
+ field,
200
+ reason,
201
+ ...(actual === undefined ? {} : { actual }),
202
+ }),
203
+ );
204
+ if (
205
+ transmission?.transmissionFactor !== undefined &&
206
+ (typeof transmission.transmissionFactor !== 'number' ||
207
+ !Number.isFinite(transmission.transmissionFactor))
208
+ ) {
209
+ return invalid(
210
+ 'KHR_materials_transmission',
211
+ 'transmissionFactor',
212
+ 'non-finite',
213
+ transmission.transmissionFactor,
214
+ );
215
+ }
216
+ if (
217
+ transmission?.transmissionFactor !== undefined &&
218
+ (transmission.transmissionFactor < 0 || transmission.transmissionFactor > 1)
219
+ ) {
220
+ return invalid(
221
+ 'KHR_materials_transmission',
222
+ 'transmissionFactor',
223
+ 'range',
224
+ transmission.transmissionFactor,
225
+ );
226
+ }
227
+ if (ior?.ior !== undefined && (typeof ior.ior !== 'number' || !Number.isFinite(ior.ior))) {
228
+ return invalid('KHR_materials_ior', 'ior', 'non-finite', ior.ior);
229
+ }
230
+ if (ior?.ior !== undefined && ior.ior < 1) {
231
+ return invalid('KHR_materials_ior', 'ior', 'range', ior.ior);
232
+ }
233
+ if (volume?.thicknessFactor !== undefined && !Number.isFinite(volume.thicknessFactor)) {
234
+ return invalid('KHR_materials_volume', 'thicknessFactor', 'non-finite', volume.thicknessFactor);
235
+ }
236
+ if (volume?.thicknessFactor !== undefined && volume.thicknessFactor < 0) {
237
+ return invalid('KHR_materials_volume', 'thicknessFactor', 'range', volume.thicknessFactor);
238
+ }
239
+ if (
240
+ volume?.attenuationColor !== undefined &&
241
+ (attenuationColor === undefined ||
242
+ attenuationColor.some((value) => !Number.isFinite(value) || value < 0 || value > 1))
243
+ ) {
244
+ return invalid('KHR_materials_volume', 'attenuationColor', 'range', volume.attenuationColor);
245
+ }
246
+ if (
247
+ volume?.attenuationDistance !== undefined &&
248
+ !Number.isFinite(volume.attenuationDistance) &&
249
+ volume.attenuationDistance !== Number.POSITIVE_INFINITY
250
+ ) {
251
+ return invalid(
252
+ 'KHR_materials_volume',
253
+ 'attenuationDistance',
254
+ 'non-finite',
255
+ volume.attenuationDistance,
256
+ );
257
+ }
258
+ if (
259
+ volume?.attenuationDistance !== undefined &&
260
+ Number.isFinite(volume.attenuationDistance) &&
261
+ volume.attenuationDistance <= 0
262
+ ) {
263
+ return invalid(
264
+ 'KHR_materials_volume',
265
+ 'attenuationDistance',
266
+ 'range',
267
+ volume.attenuationDistance,
268
+ );
269
+ }
270
+ if (alphaMode === 'BLEND' && (transmission?.transmissionFactor ?? 0) > 0) {
271
+ return invalid('KHR_materials_transmission', 'alphaMode', 'blend', alphaMode);
272
+ }
273
+ return ok({
274
+ ...(matJson.name === undefined ? {} : { name: matJson.name }),
275
+ baseColorFactor: baseColor4,
276
+ ...(emissiveFactor === undefined ? {} : { emissiveFactor }),
277
+ metallicFactor: pbr?.metallicFactor ?? 1.0,
278
+ roughnessFactor: pbr?.roughnessFactor ?? 1.0,
279
+ ...(pbr?.baseColorTexture === undefined
280
+ ? {}
281
+ : { baseColorTexture: parseTextureInfo(pbr.baseColorTexture, textures) }),
282
+ ...(pbr?.metallicRoughnessTexture === undefined
283
+ ? {}
284
+ : { metallicRoughnessTexture: parseTextureInfo(pbr.metallicRoughnessTexture, textures) }),
285
+ ...(matJson.normalTexture === undefined
286
+ ? {}
287
+ : {
288
+ normalTexture: {
289
+ ...parseTextureInfo(matJson.normalTexture, textures),
290
+ ...(matJson.normalTexture.scale === undefined
291
+ ? {}
292
+ : { scale: matJson.normalTexture.scale }),
293
+ },
294
+ }),
295
+ ...(matJson.occlusionTexture === undefined
296
+ ? {}
297
+ : {
298
+ occlusionTexture: {
299
+ ...parseTextureInfo(matJson.occlusionTexture, textures),
300
+ ...(matJson.occlusionTexture.strength === undefined
301
+ ? {}
302
+ : { strength: matJson.occlusionTexture.strength }),
303
+ },
304
+ }),
305
+ ...(matJson.emissiveTexture === undefined
306
+ ? {}
307
+ : { emissiveTexture: parseTextureInfo(matJson.emissiveTexture, textures) }),
308
+ ...(transmission === undefined
309
+ ? {}
310
+ : {
311
+ transmissionFactor: transmission.transmissionFactor ?? 0,
312
+ transmissionChannel: 0,
313
+ ...(transmission.transmissionTexture === undefined
314
+ ? {}
315
+ : {
316
+ transmissionTexture: parseTextureInfo(transmission.transmissionTexture, textures),
317
+ }),
318
+ }),
319
+ ...(ior === undefined ? {} : { ior: ior.ior ?? 1.5 }),
320
+ ...(volume === undefined
321
+ ? {}
322
+ : {
323
+ thicknessFactor: volume.thicknessFactor ?? 0,
324
+ thicknessChannel: 1,
325
+ ...(volume.thicknessTexture === undefined
326
+ ? {}
327
+ : { thicknessTexture: parseTextureInfo(volume.thicknessTexture, textures) }),
328
+ ...(attenuationColor === undefined ? {} : { attenuationColor }),
329
+ ...(finiteOrUndefined(volume.attenuationDistance) === undefined
330
+ ? {}
331
+ : { attenuationDistance: volume.attenuationDistance }),
332
+ }),
333
+ ...(alphaMode === undefined ? {} : { alphaMode }),
334
+ ...(alphaCutoff === undefined ? {} : { alphaCutoff }),
335
+ ...(matJson.doubleSided === true ? { doubleSided: true } : {}),
336
+ ...(pbr?.baseColorTexture?.texCoord === undefined || pbr.baseColorTexture.texCoord === 0
337
+ ? {}
338
+ : { baseColorTexCoord: pbr.baseColorTexture.texCoord }),
339
+ });
340
+ }
341
+
342
+ import { err, type GltfError, gltfErr, ok, type Result } from '../errors.js';