export const DIRECTOR_MAX_GLB_BYTES = 25 * 1024 * 1024; /** Only self-contained GLB 2.0 files are accepted; external buffers/textures are not portable. */ export function validateDirectorGlb(buffer: ArrayBuffer) { if (buffer.byteLength < 20 || buffer.byteLength > DIRECTOR_MAX_GLB_BYTES) throw new Error("请上传 25 MiB 以内的有效 GLB 模型"); const view = new DataView(buffer); if ( view.getUint32(0, true) !== 0x46546c67 || view.getUint32(4, true) !== 2 || view.getUint32(8, true) !== buffer.byteLength ) throw new Error("文件不是有效的 GLB 2.0 模型"); const length = view.getUint32(12, true); if ( view.getUint32(16, true) !== 0x4e4f534a || length % 4 || length > buffer.byteLength - 20 ) throw new Error("GLB 场景数据不完整"); let document: any; try { document = JSON.parse( new TextDecoder().decode(new Uint8Array(buffer, 20, length)), ); } catch { throw new Error("GLB 场景数据无效"); } if ( document?.asset?.version !== "2.0" || !Array.isArray(document.meshes) || !document.meshes.length ) throw new Error("GLB 中没有可显示的网格"); for (const resource of [ ...(document.buffers || []), ...(document.images || []), ]) { if (resource.uri && !/^data:/i.test(resource.uri)) throw new Error("请将纹理和缓冲区嵌入 GLB 后重新上传"); } return document; }