保存、另存为与性能
编辑器只在写入成功后把会话标记为已保存。它不会因为一次普通编辑自动把轻量 VFP 变成带 GLB 预览和缩略图的大型发布包;VFP 的派生区段默认继承输入文件的布局,需要时才明确覆盖。
三种保存入口
| 方法 | 适合场景 | 返回值 |
|---|---|---|
save(options?) | 文件来自 Picker,且当前运行期仍持有可写 URI。 | Promise<VoxelEditorSaveResult> |
saveToUri(uri, options?) | 宿主自己维护了保存 URI 或文件权限。 | Promise<VoxelEditorSaveResult> |
saveAs(context, options?) | 内嵌资源、loadBytes()、无可写权限,或希望用户另选位置。 | Promise<VoxelEditorSaveResult | undefined> |
canSaveToSource() 可用于决定是否显示“保存”按钮。它仅表示本会话有可写来源 URI,不代表目标文件一定仍可写;真实 I/O 异常仍会使 Promise reject。
private async saveModel(): Promise<void> {
try {
if (this.editor.canSaveToSource()) {
await this.editor.save();
} else {
await this.editor.saveAs(this.getUIContext().getHostContext());
}
} catch (error) {
console.error('保存失败:' + (error as Error).message);
}
}
saveAs() 返回 undefined 表示用户取消系统 Picker,不是保存错误。保存中 VoxelEditorState.saving=true;业务 UI 应禁用重复保存,并等待 Promise 完成。
格式选择
VoxelEditorSaveOptions.format 决定输出格式:
| 值 | 规则 |
|---|---|
VoxelSaveFormat.SOURCE | 默认值。保持输入格式:JSON 输入仍输出 JSON,VFP 输入仍输出 VFP。 |
VoxelSaveFormat.JSON | 输出体素 JSON。 |
VoxelSaveFormat.VFP | 使用闭源 Native 编译器异步生成 VFP 1.3。 |
const options = new VoxelEditorSaveOptions();
options.format = VoxelSaveFormat.VFP;
options.fileName = 'edited-house.vfp';
options.vfpChunkSize = 16;
const result = await this.editor.saveAs(
this.getUIContext().getHostContext(),
options
);
if (result) {
console.info(`${result.fileName}: ${result.byteLength} bytes`);
}
VoxelEditorSaveResult 的 uri、fileName、format 和 byteLength 只在字节全部写入成功后返回。VFP 编译和写文件期间不会重建当前预览场景;保存成功后,编辑文档会以新权威数据为基线,后续退出/再进入不会回到旧导入内容。
VFP 派生区段控制
编辑后 VFP 总会重新生成与权威数据一致的 META、PAL0、SCNE、CHIX、VOX0 和 MSH0。以下内容是可选派生区段,由 VoxelEditorSaveOptions.vfp 控制:
| 字段 | 关联区段 | 默认策略 | 适合场景 |
|---|---|---|---|
includeRuntimeCaches | VBUF / PMSH | 继承来源;新 VFP 默认为启用。 | 快速预览。 |
includeRenderCache | RND0 | 继承来源;新 VFP 默认关闭。 | 已知目标渲染器的专用缓存。 |
includeAnimationCache | ANM0 | 继承来源;新 VFP 默认为启用。 | 载入动画快速规划。 |
includePreviewGlb | PRVW | 继承来源;新 VFP 默认关闭。 | 需要对外提供 GLB 预览。 |
includeThumbnail | THMB | 继承来源;新 VFP 默认关闭。 | 文件列表、素材库缩略图。 |
字段保持 undefined 时表示“继承来源包是否已有该区段”,而不是关闭。这样常规修改轻量包不会无意新增 PRVW 或 THMB。若输入是 JSON 或明确新建 VFP,默认轻量编辑配置为 VBUF/PMSH + ANM0,不含 RND0/PRVW/THMB。
const options = new VoxelEditorSaveOptions();
options.format = VoxelSaveFormat.VFP;
// 只有制作需要离线分发的发布包时才显式增加较大的派生资源。
options.vfp.includePreviewGlb = true;
options.vfp.includeThumbnail = true;
await this.editor.saveAs(this.getUIContext().getHostContext(), options);
不要把 PRVW 当作正式编辑或预览网格来源。它是可丢弃的 GLB 缓存;编辑器和 Viewer 的正式路径仍以权威体素和体素网格缓存为准。
JSON 内存导出
exportJsonText() 不访问文件系统,直接返回当前编辑文档的 JSON 文本:
try {
const text = this.editor.exportJsonText();
// 交给网络、剪贴板、业务数据库或宿主自己的写文件流程。
} catch (error) {
// 未进入编辑模式时会抛出错误。
}
它适合预览、网络提交或业务层自行管理存储;如需要 VFP,则使用保存 API,让 Native 编译器生成容器和缓存。
性能与交互建议
- 普通预览不要提前调用
enterEdit()。VFP 预览默认不恢复完整VOX0,进入编辑才会做权威恢复和 hash 校验。 - 128³ 模型的编辑准备、框选最终收敛和首次大选区高亮都比单格放置更重;用
state.preparing/state.updating禁用重复命令。 - 连续单格编辑会合并局部重建;
placementRebuildIdleMs(默认180)控制放置类操作的聚合窗口,directRebuildIdleMs(默认16)用于撤销、重做和直接批量命令。不要为追求即时感把两者设置成负数或在每次状态回调中重建控制器。 - 屏幕框选拖动期间是低采样实时预览;松手后以高精度 DDA 获取最终选择。要选择不可见内部体素时使用坐标盒 API,避免不必要的大范围屏幕采样。
maxHistory过大意味着保留更多体素修改批次;默认100适合一般交互。对长时间批量编辑应在业务层提示用户及时保存。
编辑的性能指标与普通预览首帧不同。应分别观察“进入编辑就绪”和“单格/连续放置响应”,不要把 VFP 读取、动画首帧和大框选最终收敛混为同一个耗时指标。