@forgeax/engine-fbx 0.1.4 → 0.1.7

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.
@@ -1,13 +1,6 @@
1
1
  // fbx-importer.ts — TS wrapper around the ufbx WASM parser.
2
2
 
3
- import {
4
- IMPORT_ERROR_HINTS,
5
- type ImportContext,
6
- type ImportDiagnostic,
7
- ImportError,
8
- type Importer,
9
- type ImportResult,
10
- } from '@forgeax/engine-types';
3
+ import type { ImportContext, Importer, ImportResult } from '@forgeax/engine-types';
11
4
  import { fbxErr } from './errors.js';
12
5
  import { initFbxWasm, parseFbx } from './index.js';
13
6
  import {
@@ -21,13 +14,11 @@ import { type FbxRawNodes, parseScene } from './parse-scene.js';
21
14
  import { type FbxRawSkeletonDoc, parseSkeleton } from './parse-skeleton.js';
22
15
  import { type FbxRawSkinDoc, parseSkin } from './parse-skin.js';
23
16
  import { parseTextures } from './parse-texture.js';
24
- import { resolveFbxTexturePath } from './resolve-texture-path.js';
25
- import { type FbxDecodedTexture, toAssetPack } from './to-asset-pack.js';
17
+ import { toAssetPack } from './to-asset-pack.js';
26
18
 
27
19
  export interface FbxSourceKeyOutput {
28
20
  readonly kind: string;
29
21
  readonly name?: string;
30
- readonly sourceKey?: string;
31
22
  }
32
23
 
33
24
  export type FbxSourceKeyResult =
@@ -41,8 +32,6 @@ export type FbxSourceKeyResult =
41
32
  export function sourceKeyForFbxOutput(output: FbxSourceKeyOutput): string | undefined {
42
33
  const kind = output.kind.trim();
43
34
  if (kind.length === 0) return undefined;
44
- const explicit = output.sourceKey?.trim();
45
- if (explicit !== undefined && explicit.length > 0) return explicit;
46
35
  const name = output.name?.trim();
47
36
  return name === undefined || name.length === 0 ? `fbx:${kind}` : `fbx:${kind}:${name}`;
48
37
  }
@@ -64,97 +53,6 @@ export function deriveFbxSourceKeys(outputs: readonly FbxSourceKeyOutput[]): Fbx
64
53
  return { ok: true, keys: keys as string[] };
65
54
  }
66
55
 
67
- function validationError(
68
- ctx: ImportContext,
69
- textureIndex: number,
70
- code: string,
71
- actual: string,
72
- hint: string,
73
- ): ImportError {
74
- const diagnostic: ImportDiagnostic = {
75
- code,
76
- severity: 'error',
77
- sourcePath: `${ctx.source}#textures[${textureIndex}]`,
78
- sourceRange: { start: 0, end: 0, line: 1, column: 1 },
79
- rule: 'fbx-external-texture-dependency-closure',
80
- expected:
81
- 'every bound FBX texture resolves to one readable supported image in the authorized scope',
82
- actual,
83
- hint,
84
- };
85
- return new ImportError({
86
- code: 'source-validation-failed',
87
- expected: diagnostic.expected,
88
- hint: IMPORT_ERROR_HINTS['source-validation-failed'],
89
- detail: { diagnostics: [diagnostic] },
90
- });
91
- }
92
-
93
- const FBX_PARSE_DIAGNOSTIC_MAX = 256;
94
- const FBX_PARSE_EXPECTED = 'a readable FBX source that ufbx can parse';
95
-
96
- function boundedErrorMessage(error: unknown): string {
97
- const message = error instanceof Error ? error.message : String(error);
98
- return message.length <= FBX_PARSE_DIAGNOSTIC_MAX
99
- ? message
100
- : `${message.slice(0, FBX_PARSE_DIAGNOSTIC_MAX - 3)}...`;
101
- }
102
-
103
- function parseValidationError(ctx: ImportContext, error: unknown): ImportError {
104
- const actual = boundedErrorMessage(error);
105
- const diagnostic: ImportDiagnostic = {
106
- code: 'fbx-source-parse-failed',
107
- severity: 'error',
108
- sourcePath: ctx.source,
109
- sourceRange: { start: 0, end: 0, line: 1, column: 1 },
110
- rule: 'fbx-source-parse',
111
- expected: FBX_PARSE_EXPECTED,
112
- actual,
113
- hint: 'repair or re-export the FBX source, then retry the same importer and source path',
114
- };
115
- return new ImportError({
116
- code: 'source-validation-failed',
117
- expected: FBX_PARSE_EXPECTED,
118
- actual,
119
- hint: IMPORT_ERROR_HINTS['source-validation-failed'],
120
- detail: { diagnostics: [diagnostic] },
121
- });
122
- }
123
-
124
- function importCandidates(ctx: ImportContext): readonly { readonly relativePath: string }[] {
125
- const raw = ctx.importSettings.fbxCandidatePaths;
126
- if (!Array.isArray(raw)) return [];
127
- return raw.flatMap((path): { readonly relativePath: string }[] =>
128
- typeof path === 'string' ? [{ relativePath: path }] : [],
129
- );
130
- }
131
-
132
- function mimeForTexture(
133
- path: string,
134
- bytes: Uint8Array,
135
- ): 'image/png' | 'image/jpeg' | 'image/x-tga' {
136
- const lower = path.toLowerCase();
137
- if (lower.endsWith('.png') || (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e)) {
138
- return 'image/png';
139
- }
140
- if (
141
- lower.endsWith('.jpg') ||
142
- lower.endsWith('.jpeg') ||
143
- (bytes[0] === 0xff && bytes[1] === 0xd8)
144
- ) {
145
- return 'image/jpeg';
146
- }
147
- return 'image/x-tga';
148
- }
149
-
150
- function textureColorSpace(slot: string): 'srgb' | 'linear' {
151
- return slot === 'normalTexture' ||
152
- slot === 'metallicRoughnessTexture' ||
153
- slot === 'occlusionTexture'
154
- ? 'linear'
155
- : 'srgb';
156
- }
157
-
158
56
  export const fbxImporter: Importer = {
159
57
  key: 'fbx',
160
58
 
@@ -164,31 +62,17 @@ export const fbxImporter: Importer = {
164
62
  // addon / SDK build step (the WASM module self-loads its .wasm).
165
63
  const read = await ctx.readSource();
166
64
  if (!read.ok) {
167
- return {
168
- ok: false,
169
- error: new ImportError({
170
- code: 'source-read-failed',
171
- expected: `readable source file at meta.source "${ctx.source}"`,
172
- hint: IMPORT_ERROR_HINTS['source-read-failed'],
173
- detail: {
174
- source: ctx.source,
175
- reason: read.error instanceof Error ? read.error.message : String(read.error),
176
- },
177
- }),
178
- };
65
+ const wrapper = new Error(`fbx-source-unreadable: ${ctx.source}`);
66
+ (wrapper as { cause?: unknown }).cause = read.error;
67
+ throw wrapper;
179
68
  }
180
69
 
181
70
  await initFbxWasm();
182
- let doc: FbxRawDocument & FbxRawSkeletonDoc & FbxRawSkinDoc & FbxRawAnimDoc;
183
- try {
184
- const jsonStr = parseFbx(read.value);
185
- doc = JSON.parse(jsonStr) as FbxRawDocument &
186
- FbxRawSkeletonDoc &
187
- FbxRawSkinDoc &
188
- FbxRawAnimDoc;
189
- } catch (error) {
190
- return { ok: false, error: parseValidationError(ctx, error) };
191
- }
71
+ const jsonStr = parseFbx(read.value);
72
+ const doc = JSON.parse(jsonStr) as FbxRawDocument &
73
+ FbxRawSkeletonDoc &
74
+ FbxRawSkinDoc &
75
+ FbxRawAnimDoc;
192
76
 
193
77
  // NURBS / patch fail-fast: the bridge emits an error envelope for
194
78
  // unsupported surface types (charter P3 explicit failure).
@@ -241,115 +125,6 @@ export const fbxImporter: Importer = {
241
125
  }
242
126
  const animationClips = parseAnimationClips(doc);
243
127
 
244
- const textureSlots = new Map<number, string>();
245
- for (const material of materials) {
246
- for (const binding of material.textureBindings ?? []) {
247
- const prior = textureSlots.get(binding.textureIndex);
248
- const colorSpace = textureColorSpace(binding.slot);
249
- if (prior !== undefined && prior !== colorSpace) {
250
- return {
251
- ok: false,
252
- error: validationError(
253
- ctx,
254
- binding.textureIndex,
255
- 'fbx-texture-color-space-conflict',
256
- `texture ${binding.textureIndex} is used as both ${prior} and ${colorSpace}`,
257
- 'split the source texture or author one color-space semantic per FBX texture',
258
- ),
259
- };
260
- }
261
- textureSlots.set(binding.textureIndex, colorSpace);
262
- }
263
- }
264
-
265
- const decodedTextures = new Map<number, FbxDecodedTexture>();
266
- for (const texture of textures) {
267
- const guidDeclared = ctx.subAssets.some(
268
- (subAsset) => subAsset.kind === 'texture' && subAsset.sourceIndex === texture.sourceIndex,
269
- );
270
- if (!guidDeclared) continue;
271
-
272
- let bytes: Uint8Array;
273
- let sourcePath = texture.filePath;
274
- if (texture.embeddedBytes !== undefined && texture.embeddedBytes.byteLength > 0) {
275
- bytes = texture.embeddedBytes;
276
- sourcePath = texture.relativeFilePath ?? texture.filePath;
277
- } else {
278
- if (texture.type !== undefined && texture.type !== 'file') {
279
- return {
280
- ok: false,
281
- error: validationError(
282
- ctx,
283
- texture.sourceIndex,
284
- 'fbx-material-texture-slot-unsupported',
285
- `texture type ${texture.type} is not a single file texture`,
286
- 'flatten layered/procedural FBX textures to a file texture before importing',
287
- ),
288
- };
289
- }
290
- const resolution = resolveFbxTexturePath(
291
- ctx.source,
292
- {
293
- ...(texture.relativeFilePath === undefined
294
- ? {}
295
- : { declaredRelativePath: texture.relativeFilePath }),
296
- ...(texture.filePath.length === 0 ? {} : { declaredFilename: texture.filePath }),
297
- ...(texture.absoluteFilePath === undefined
298
- ? {}
299
- : { declaredAbsolutePath: texture.absoluteFilePath }),
300
- },
301
- importCandidates(ctx),
302
- );
303
- if (!resolution.ok) {
304
- return {
305
- ok: false,
306
- error: validationError(
307
- ctx,
308
- texture.sourceIndex,
309
- resolution.code,
310
- `${resolution.requestedPath}; candidates=${resolution.candidates.join(', ') || '<none>'}`,
311
- resolution.code === 'fbx-external-texture-ambiguous'
312
- ? 'select a folder whose dependency path is unique; first-match guessing is disabled'
313
- : 'select the containing folder so the FBX and its referenced texture are in the authorized candidate scope',
314
- ),
315
- };
316
- }
317
- const read = await ctx.readSibling(resolution.readUri);
318
- if (!read.ok) return read;
319
- bytes = read.value;
320
- sourcePath = resolution.relativePath;
321
- }
322
-
323
- const slot =
324
- [...textureSlots.entries()].find(([index]) => index === texture.sourceIndex)?.[1] ?? 'srgb';
325
- const decoded = await ctx.decodeImage(bytes, mimeForTexture(sourcePath, bytes), {
326
- ...ctx.importSettings,
327
- colorSpace: slot,
328
- });
329
- if (!decoded.ok) {
330
- const reason =
331
- decoded.error.code === 'image-decode-failed'
332
- ? decoded.error.detail.reason
333
- : `${decoded.error.code}: ${JSON.stringify(decoded.error.detail)}`;
334
- return {
335
- ok: false,
336
- error: validationError(
337
- ctx,
338
- texture.sourceIndex,
339
- 'fbx-external-texture-decode-failed',
340
- `${sourcePath}: ${reason}`,
341
- 'repair or re-export the referenced image, then retry the FBX import',
342
- ),
343
- };
344
- }
345
- decodedTextures.set(texture.sourceIndex, {
346
- texture: decoded.value.texture,
347
- bytes: decoded.value.bytes,
348
- ...(decoded.value.mediaType === undefined ? {} : { mediaType: decoded.value.mediaType }),
349
- ...(decoded.value.assetCodec === undefined ? {} : { assetCodec: decoded.value.assetCodec }),
350
- });
351
- }
352
-
353
128
  return {
354
129
  ok: true,
355
130
  value: {
@@ -362,7 +137,6 @@ export const fbxImporter: Importer = {
362
137
  skin,
363
138
  animationClips,
364
139
  subAssets: ctx.subAssets,
365
- decodedTextures,
366
140
  ...(ctx.sourceOverrides === undefined ? {} : { sourceOverrides: ctx.sourceOverrides }),
367
141
  }),
368
142
  sourceDependencies: [],
@@ -395,118 +395,6 @@ static void write_nodes(Buf *b, ufbx_scene *scene) {
395
395
 
396
396
  /* ── Materials writing ─────────────────────────────────────────────── */
397
397
 
398
- static int texture_index(ufbx_scene *scene, const ufbx_texture *texture) {
399
- if (!texture) return -1;
400
- for (size_t i = 0; i < scene->textures.count; i++) {
401
- if (scene->textures.data[i] == texture) return (int)i;
402
- }
403
- return -1;
404
- }
405
-
406
- static int map_texture_index(ufbx_scene *scene, const ufbx_material_map *map) {
407
- if (!map || !map->texture_enabled || !map->texture) return -1;
408
- return texture_index(scene, map->texture);
409
- }
410
-
411
- static void write_texture_binding(
412
- Buf *b,
413
- ufbx_scene *scene,
414
- const char *slot,
415
- const ufbx_material_map *map,
416
- int *first
417
- ) {
418
- int index = map_texture_index(scene, map);
419
- if (index < 0) return;
420
- if (!*first) buf_char(b, ',');
421
- *first = 0;
422
- buf_str(b, "{\"slot\":");
423
- buf_quoted(b, slot);
424
- buf_str(b, ",\"textureIndex\":");
425
- buf_int(b, index);
426
- buf_char(b, '}');
427
- }
428
-
429
- static void write_texture_bindings(Buf *b, ufbx_scene *scene, ufbx_material *mat) {
430
- const ufbx_material_map *base_color =
431
- map_texture_index(scene, &mat->pbr.base_color) >= 0 ? &mat->pbr.base_color : &mat->fbx.diffuse_color;
432
- const ufbx_material_map *normal =
433
- map_texture_index(scene, &mat->pbr.normal_map) >= 0 ? &mat->pbr.normal_map : &mat->fbx.normal_map;
434
- const ufbx_material_map *specular =
435
- map_texture_index(scene, &mat->pbr.specular_color) >= 0 ? &mat->pbr.specular_color : &mat->fbx.specular_color;
436
- const ufbx_material_map *emissive =
437
- map_texture_index(scene, &mat->pbr.emission_color) >= 0 ? &mat->pbr.emission_color : &mat->fbx.emission_color;
438
- const ufbx_material_map *occlusion = &mat->pbr.ambient_occlusion;
439
- const ufbx_material_map *metalness = &mat->pbr.metalness;
440
- const ufbx_material_map *roughness = &mat->pbr.roughness;
441
- int has_any =
442
- map_texture_index(scene, base_color) >= 0 ||
443
- map_texture_index(scene, normal) >= 0 ||
444
- map_texture_index(scene, specular) >= 0 ||
445
- map_texture_index(scene, emissive) >= 0 ||
446
- map_texture_index(scene, occlusion) >= 0 ||
447
- (map_texture_index(scene, metalness) >= 0 && map_texture_index(scene, roughness) >= 0 &&
448
- map_texture_index(scene, metalness) == map_texture_index(scene, roughness));
449
- if (!has_any) return;
450
-
451
- buf_str(b, ",\"textureBindings\":[");
452
- int first = 1;
453
- write_texture_binding(b, scene, "baseColorTexture", base_color, &first);
454
- write_texture_binding(b, scene, "normalTexture", normal, &first);
455
- write_texture_binding(b, scene, "specularTintTexture", specular, &first);
456
- write_texture_binding(b, scene, "emissiveTexture", emissive, &first);
457
- write_texture_binding(b, scene, "occlusionTexture", occlusion, &first);
458
- if (map_texture_index(scene, metalness) >= 0 &&
459
- map_texture_index(scene, metalness) == map_texture_index(scene, roughness)) {
460
- write_texture_binding(b, scene, "metallicRoughnessTexture", metalness, &first);
461
- }
462
- buf_char(b, ']');
463
- }
464
-
465
- static const char *texture_type_name(ufbx_texture_type type) {
466
- switch (type) {
467
- case UFBX_TEXTURE_FILE: return "file";
468
- case UFBX_TEXTURE_LAYERED: return "layered";
469
- case UFBX_TEXTURE_PROCEDURAL: return "procedural";
470
- case UFBX_TEXTURE_SHADER: return "shader";
471
- default: return "unknown";
472
- }
473
- }
474
-
475
- static void write_textures(Buf *b, ufbx_scene *scene) {
476
- if (scene->textures.count == 0) return;
477
- buf_str(b, ",\"textures\":[");
478
- for (size_t i = 0; i < scene->textures.count; i++) {
479
- ufbx_texture *texture = scene->textures.data[i];
480
- if (i > 0) buf_char(b, ',');
481
- buf_str(b, "{\"name\":");
482
- buf_quoted(b, texture->name.data ? texture->name.data : "");
483
- buf_str(b, ",\"type\":");
484
- buf_quoted(b, texture_type_name(texture->type));
485
- buf_str(b, ",\"filePath\":");
486
- if (texture->relative_filename.data && texture->relative_filename.data[0] != '\0') {
487
- buf_quoted(b, texture->relative_filename.data);
488
- } else if (texture->filename.data && texture->filename.data[0] != '\0') {
489
- buf_quoted(b, texture->filename.data);
490
- } else {
491
- buf_quoted(b, "");
492
- }
493
- buf_str(b, ",\"relativeFilePath\":");
494
- buf_quoted(b, texture->relative_filename.data ? texture->relative_filename.data : "");
495
- buf_str(b, ",\"absoluteFilePath\":");
496
- buf_quoted(b, texture->absolute_filename.data ? texture->absolute_filename.data : "");
497
- buf_str(b, ",\"sourceIndex\":");
498
- buf_size(b, i);
499
- buf_str(b, ",\"embeddedBytes\":[");
500
- const unsigned char *content = (const unsigned char *)texture->content.data;
501
- for (size_t byte = 0; content && byte < texture->content.size; byte++) {
502
- if (byte > 0) buf_char(b, ',');
503
- buf_int(b, content[byte]);
504
- }
505
- buf_str(b, "]}");
506
- }
507
- buf_char(b, ']');
508
- }
509
-
510
398
  static void write_materials(Buf *b, ufbx_scene *scene) {
511
399
  if (scene->materials.count == 0) return;
512
400
 
@@ -596,8 +484,6 @@ static void write_materials(Buf *b, ufbx_scene *scene) {
596
484
  buf_str(b, "\"kind\":\"fallback\"");
597
485
  }
598
486
 
599
- write_texture_bindings(b, scene, mat);
600
-
601
487
  buf_str(b, ",\"sourceIndex\":");
602
488
  buf_size(b, i);
603
489
 
@@ -1024,10 +910,6 @@ void parseFbxWasm(const void *data, size_t size) {
1024
910
  /* Nodes */
1025
911
  write_nodes(&b, scene);
1026
912
 
1027
- /* Textures must be emitted before materials so the JSON remains easy to
1028
- inspect; material bindings refer to these stable scene texture indices. */
1029
- write_textures(&b, scene);
1030
-
1031
913
  /* Materials */
1032
914
  write_materials(&b, scene);
1033
915
 
@@ -7,13 +7,7 @@
7
7
  // 3. Lambert → same formula as Phong (max_gloss=100 default)
8
8
  // 4. Fallback → default grey PBR: baseColor=[0.5,0.5,0.5], metal=0, rough=0.5
9
9
 
10
- import type { MaterialPod, MaterialTextureBindingPod } from '@forgeax/engine-types';
11
-
12
- export interface FbxRawTextureBinding {
13
- readonly slot: MaterialTextureBindingPod['slot'];
14
- readonly textureIndex: number;
15
- readonly texCoord?: number;
16
- }
10
+ import type { MaterialPod } from '@forgeax/engine-types';
17
11
 
18
12
  /** JSON shape emitted by binding.cc WriteMaterials for a single material. */
19
13
  export interface FbxRawMaterial {
@@ -23,7 +17,6 @@ export interface FbxRawMaterial {
23
17
  readonly diffuse?: readonly [number, number, number];
24
18
  readonly shininess?: number;
25
19
  readonly specular?: readonly [number, number, number];
26
- readonly textureBindings?: readonly FbxRawTextureBinding[];
27
20
  readonly stingrayProps?: {
28
21
  readonly baseColor?: readonly [number, number, number];
29
22
  readonly metallic?: number;
@@ -50,43 +43,6 @@ function phongRoughness(shininess: number, maxGloss = 100): number {
50
43
  export function parseMaterial(raw: FbxRawMaterial, sourceIndex: number): MaterialPod {
51
44
  void sourceIndex; // reserved for future cross-referencing
52
45
 
53
- const textureBindings = raw.textureBindings?.map((binding) => ({
54
- slot: binding.slot,
55
- textureIndex: binding.textureIndex,
56
- ...(binding.texCoord === undefined ? {} : { texCoord: binding.texCoord }),
57
- }));
58
- const textureFields: Pick<
59
- MaterialPod,
60
- | 'textureBindings'
61
- | 'baseColorTextureIndex'
62
- | 'metallicRoughnessTextureIndex'
63
- | 'normalTextureIndex'
64
- | 'occlusionTextureIndex'
65
- | 'emissiveTextureIndex'
66
- | 'specularTintTextureIndex'
67
- > =
68
- textureBindings === undefined || textureBindings.length === 0
69
- ? {}
70
- : {
71
- textureBindings,
72
- ...Object.fromEntries(
73
- textureBindings.map((binding) => [
74
- binding.slot === 'baseColorTexture'
75
- ? 'baseColorTextureIndex'
76
- : binding.slot === 'metallicRoughnessTexture'
77
- ? 'metallicRoughnessTextureIndex'
78
- : binding.slot === 'normalTexture'
79
- ? 'normalTextureIndex'
80
- : binding.slot === 'occlusionTexture'
81
- ? 'occlusionTextureIndex'
82
- : binding.slot === 'emissiveTexture'
83
- ? 'emissiveTextureIndex'
84
- : 'specularTintTextureIndex',
85
- binding.textureIndex,
86
- ]),
87
- ),
88
- };
89
-
90
46
  switch (raw.kind) {
91
47
  case 'stingray-pbs': {
92
48
  const sp = raw.stingrayProps ?? {};
@@ -100,7 +56,6 @@ export function parseMaterial(raw: FbxRawMaterial, sourceIndex: number): Materia
100
56
  ],
101
57
  metallicFactor: sp.metallic ?? 0,
102
58
  roughnessFactor: sp.roughness ?? 0.5,
103
- ...textureFields,
104
59
  };
105
60
  }
106
61
 
@@ -112,7 +67,6 @@ export function parseMaterial(raw: FbxRawMaterial, sourceIndex: number): Materia
112
67
  baseColorFactor: [d[0] ?? 0.5, d[1] ?? 0.5, d[2] ?? 0.5, 1],
113
68
  metallicFactor: 0,
114
69
  roughnessFactor: phongRoughness(gloss),
115
- ...textureFields,
116
70
  };
117
71
  }
118
72
 
@@ -123,7 +77,6 @@ export function parseMaterial(raw: FbxRawMaterial, sourceIndex: number): Materia
123
77
  baseColorFactor: [d[0] ?? 0.5, d[1] ?? 0.5, d[2] ?? 0.5, 1],
124
78
  metallicFactor: 0,
125
79
  roughnessFactor: 0.5, // lambert has no specular → default roughness
126
- ...textureFields,
127
80
  };
128
81
  }
129
82
 
@@ -133,7 +86,6 @@ export function parseMaterial(raw: FbxRawMaterial, sourceIndex: number): Materia
133
86
  baseColorFactor: [0.5, 0.5, 0.5, 1],
134
87
  metallicFactor: 0,
135
88
  roughnessFactor: 0.5,
136
- ...textureFields,
137
89
  };
138
90
  }
139
91
  }
@@ -4,11 +4,7 @@ import type { TexturePod } from '@forgeax/engine-types';
4
4
 
5
5
  export interface FbxRawTexture {
6
6
  readonly name?: string;
7
- readonly filePath?: string;
8
- readonly relativeFilePath?: string;
9
- readonly absoluteFilePath?: string;
10
- readonly embeddedBytes?: readonly number[];
11
- readonly type?: 'file' | 'layered' | 'procedural' | 'shader' | 'unknown';
7
+ readonly filePath: string;
12
8
  readonly sourceIndex: number;
13
9
  }
14
10
 
@@ -16,21 +12,11 @@ export interface FbxRawTextures {
16
12
  readonly textures?: readonly FbxRawTexture[];
17
13
  }
18
14
 
19
- function normalizePath(path: string): string {
20
- return path.replaceAll('\\', '/');
21
- }
22
-
23
15
  export function parseTextures(raw: FbxRawTextures): TexturePod[] {
24
16
  const textures = raw.textures ?? [];
25
17
  return textures.map((t) => ({
26
- name: t.name ?? t.filePath ?? t.relativeFilePath ?? `texture-${t.sourceIndex}`,
27
- filePath: normalizePath(t.filePath ?? t.relativeFilePath ?? ''),
28
- ...(t.relativeFilePath === undefined
29
- ? {}
30
- : { relativeFilePath: normalizePath(t.relativeFilePath) }),
31
- ...(t.absoluteFilePath === undefined ? {} : { absoluteFilePath: t.absoluteFilePath }),
32
- ...(t.embeddedBytes === undefined ? {} : { embeddedBytes: Uint8Array.from(t.embeddedBytes) }),
33
- ...(t.type === undefined ? {} : { type: t.type }),
18
+ name: t.name ?? t.filePath,
19
+ filePath: t.filePath,
34
20
  sourceIndex: t.sourceIndex,
35
21
  }));
36
22
  }