选取、批量编辑与变换
编辑器把每个被选中的体素保存在源网格坐标中,并在模型上显示逐格半透明高亮。选区操作均以整数格为单位;它们会写入一条可撤销历史并只刷新受影响的 Chunk,而不是整模型重新导入。
单点选择与取色
先切换模式,再让用户直接触控模型:
this.editor.setMode(VoxelEditMode.SELECT); // 单击增减一个选中体素
this.editor.setMode(VoxelEditMode.PICK); // 单击吸取颜色
this.editor.setMode(VoxelEditMode.ADD); // 命中面外侧放置
this.editor.setMode(VoxelEditMode.REPLACE);
this.editor.setSelectedColor('#3A86FF');
setSelectedSymbol(symbol) 只接受当前调色板已存在的符号;setSelectedColor(hex) 接受 #RGB 或 #RRGGBB,自动复用相同颜色或新增一个内部符号。新增符号会出现在 VoxelEditorState.palette,并随 JSON/VFP 保存。
开启 setSymmetryX(true) 后,ADD、DELETE、REPLACE 会同时尝试写入 X 轴镜像格;镜像目标越界、与原格重合或不符合该操作条件时会被安全忽略。
三种程序化选区
这些方法适合工具栏按钮、命令菜单或脚本化编辑。返回值均为当前选区体素数;未进入编辑或没有匹配项时返回 0。
| 方法 | 签名 | 选择规则 |
|---|---|---|
| 全选 | selectAll(): number | 选择全部非空体素。 |
| 按颜色选择 | selectByColor(color?: string, append?: boolean): number | color 可为调色板符号或 #RRGGBB;默认使用当前颜色。 |
| 三维坐标盒 | selectByBox(min, max, append?: boolean): number | 选择包含端点在内的源网格轴对齐盒中的非空体素。 |
| 清空 | clearSelection(): void | 仅清空选择,不修改模型。 |
// 选择某个颜色,再把它和已选择内容合并。
this.editor.selectByColor('#6B9E47');
this.editor.selectByColor('#D995A8', true);
// 坐标会被取整并钳制到 0..gridSize-1,两个端点顺序不限。
this.editor.selectByBox(
new VoxelCoordinate(10, 0, 6),
new VoxelCoordinate(34, 22, 28)
);
可以用 getSelectionVoxels() 获取副本、getSelectionSize() 获取数量、getSelectionBounds() 获取包含式 min/max 边界。返回的数组和对象可安全用于 UI,不要修改后期待它们反写模型。
屏幕框选
将模式切换为 BOX_SELECT 后,用户可以从 Viewer 画布的任意位置按下并拖到任意位置:
- 拖动中显示半透明矩形和实时逐格高亮预览;预览使用低采样,避免拖动卡顿。
- 松手后使用更密的逐像素视线采样收敛最终结果。
- 每条视线只取最先命中的可见体素,因此不会把被前景遮挡的背面体素加入选区。
该模式单指拖动不会旋转模型。如果要重新旋转,切回 VIEW 或其他非框选模式。屏幕框选只负责可见表面;若需选取网格内部或不在当前视角内的体素,请使用 selectByBox()。
批量删除与换色
| 方法 | 结果 | 返回值 |
|---|---|---|
deleteSelection() | 删除全部选中体素。 | 实际删除数量。 |
setSelectionSymbol(symbol) | 用已有调色板符号批量换色。 | 实际修改数量。 |
setSelectionColor(hex) | 用任意 #RGB / #RRGGBB 批量换色。 | 实际修改数量。 |
if (this.editor.getSelectionSize() > 0) {
this.editor.setSelectionColor('#F1C7D6');
// 或 this.editor.deleteSelection();
}
空选区不会抛错,操作返回 0 并通过状态 message 提示。批量操作会作为一条 Undo 历史记录。
选区变换
| 方法 | 签名 | 行为 |
|---|---|---|
| 移动 | moveSelectionBy(dx?, dy?, dz?): number | 整数格平移,移动后仍选中移动后的体素。 |
| 复制 | duplicateSelection(offset?): number | 保留原选区,复制品成为当前选区。 |
| 镜像 | mirrorSelection(axis?): number | 围绕选区自身的包含式边界原地镜像。 |
| 旋转 | rotateSelection(axis?, quarterTurns?): number | 围绕选区自身边界按 90° 的整数倍旋转。 |
| 对齐 | alignSelection(mode?): number | 将每个选中体素压到选区的某个最小、中间或最大坐标平面。 |
// 复制一份到右侧两格。
this.editor.duplicateSelection(new VoxelCoordinate(2, 0, 0));
// 将当前选区绕 Y 轴顺时针旋转 90°,然后压到自身底部平面。
this.editor.rotateSelection(VoxelAxis.Y, 1);
this.editor.alignSelection(VoxelSelectionAlign.MIN_Y);
变换前会进行两类安全检查:
- 任何目标格超出
0..gridSize-1时,整次变换取消; - 目标若覆盖未被选中的实心体素,整次变换取消。
因此返回 0 既可能表示空选区,也可能表示越界/冲突;应读取 getState().message 显示具体原因。选区内部体素相互重叠是允许的,最终会合并为一个目标格。
撤销、重做与历史上限
if (this.state?.canUndo) this.editor.undo();
if (this.state?.canRedo) this.editor.redo();
每次添加、删除、替换、批量换色和选区变换记为一条历史记录;选择本身不进入历史。VoxelEditorOptions.maxHistory 默认 100,超出后最早的记录被丢弃。重新导入模型、退出编辑或再次进入一个新场景会清空历史。
选区高亮仅是视觉层,不属于模型数据,也不会写进 JSON/VFP。