From 61cfb8800ddfd16ff92ae56031d0725e0fec5b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 24 Sep 2026 17:24:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9F=A5=E7=9C=8B=E5=99=A8=E8=AE=A4=E5=BE=97?= =?UTF-8?q?=20ASCII=20FBX=20=E7=9A=84=E5=AD=97=E8=8A=82=E5=BC=80=E5=A4=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - packages/model3d-viewer/src/loader.ts:字节魔数多认一种 ASCII FBX 开头(`; FBX`,Blender 等工具的默认导出),此前这批文件在没显式给 format 时会被判 unsupported-format,尽管 FBXLoader 能解析 - packages/model3d-viewer/src/loader.ts:BOM / 前导空白的跳过抽成 resolveModel3dViewerContentStart,JSON 与 ASCII FBX 两处判据共用 - packages/model3d-viewer/src/loader.test.ts:补 ASCII FBX(含前导空白)的字节判定用例 --- packages/model3d-viewer/src/loader.test.ts | 13 +++++++++ packages/model3d-viewer/src/loader.ts | 33 ++++++++++++++++++---- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/model3d-viewer/src/loader.test.ts b/packages/model3d-viewer/src/loader.test.ts index a5d6cb88a..50413d648 100644 --- a/packages/model3d-viewer/src/loader.test.ts +++ b/packages/model3d-viewer/src/loader.test.ts @@ -84,6 +84,19 @@ describe('模型格式判定', () => { new TextEncoder().encode('\uFEFF{"asset":{}}'), ), ).toBe('gltf'); + // ASCII FBX(Blender 默认导出)没有 Kaydara 魔数,按开头这行认。 + expect( + resolveModel3dViewerFormatFromBytes( + new TextEncoder().encode( + '; FBX 7.4.0 project file\nFBXHeaderExtension: {', + ), + ), + ).toBe('fbx'); + expect( + resolveModel3dViewerFormatFromBytes( + new TextEncoder().encode('\n ; FBX 7.3.0 project file'), + ), + ).toBe('fbx'); expect( resolveModel3dViewerFormatFromBytes(new Uint8Array([0x00, 0x01])), ).toBeNull(); diff --git a/packages/model3d-viewer/src/loader.ts b/packages/model3d-viewer/src/loader.ts index 46707477a..f28303b8d 100644 --- a/packages/model3d-viewer/src/loader.ts +++ b/packages/model3d-viewer/src/loader.ts @@ -43,6 +43,8 @@ const MODEL3D_VIEWER_FBX_MAGIC = [ 0x4b, 0x61, 0x79, 0x64, 0x61, 0x72, 0x61, 0x20, 0x46, 0x42, 0x58, 0x20, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, ]; +/** ASCII FBX 的开头:「; FBX 7.4.0 project file」(Blender 等工具的默认导出)。 */ +const MODEL3D_VIEWER_ASCII_FBX_MAGIC = [0x3b, 0x20, 0x46, 0x42, 0x58]; const MODEL3D_VIEWER_SUPPORTED_FORMAT_TEXT = MODEL3D_VIEWER_SUPPORTED_FORMATS.join(' / '); @@ -56,18 +58,36 @@ function matchesModel3dViewerMagic( return magic.every((value, index) => bytes[index] === value); } -function isModel3dViewerJsonStart(bytes: Uint8Array) { - for (const byte of bytes) { - // BOM 与空白之后是 `{` 才当 glTF JSON:多文件 glTF 的正文就是一个 JSON 文档。 +/** 跳过 BOM 与前导空白后的正文起点;全是噪声时返回 -1。 */ +function resolveModel3dViewerContentStart(bytes: Uint8Array) { + for (let index = 0; index < bytes.length; index += 1) { + const byte = bytes[index]; if (byte === 0xef || byte === 0xbb || byte === 0xbf) { continue; } if (byte === 0x20 || byte === 0x09 || byte === 0x0a || byte === 0x0d) { continue; } - return byte === 0x7b; + return index; } - return false; + return -1; +} + +function isModel3dViewerJsonStart(bytes: Uint8Array) { + // BOM 与空白之后是 `{` 才当 glTF JSON:多文件 glTF 的正文就是一个 JSON 文档。 + const start = resolveModel3dViewerContentStart(bytes); + return start >= 0 && bytes[start] === 0x7b; +} + +/** ASCII FBX 只有「看起来像」的判据,识别不出就交给显式 format 或扩展名。 */ +function isModel3dViewerAsciiFbxStart(bytes: Uint8Array) { + const start = resolveModel3dViewerContentStart(bytes); + if (start < 0) { + return false; + } + return MODEL3D_VIEWER_ASCII_FBX_MAGIC.every( + (value, offset) => bytes[start + offset] === value, + ); } /** 声明类型里的模型格式;不是可渲染格式时返回 null。 */ @@ -96,6 +116,9 @@ export function resolveModel3dViewerFormatFromBytes( if (matchesModel3dViewerMagic(view, MODEL3D_VIEWER_FBX_MAGIC)) { return 'fbx'; } + if (isModel3dViewerAsciiFbxStart(view)) { + return 'fbx'; + } if (isModel3dViewerJsonStart(view)) { return 'gltf'; }