VTK 设计模式:组合模式
系列:Qt / VTK 设计模式 · VTK 09/10
参考:vtkAssembly、vtkMultiBlockDataSet
引子
在一个复杂的机械装配体三维场景中(如包含螺丝、轴承、齿轮、外壳的完整车床),如果需要对整个装配体进行整体平移、旋转或隐显控制,若对每一个零配件 vtkActor 分别设置坐标变换,代码将变得不可维护。
解决此问题的经典设计模式是组合模式(Composite Pattern):将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。在 VTK 中,组合模式在渲染层体现为 vtkAssembly 场景图,在数据层体现为 vtkMultiBlockDataSet。
核心痛点与 GoF 组合结构
痛点场景:手动维护层次坐标变换的灾难
cpp
// 坏方案:平铺管理所有子部件 Actor
actor1->SetUserMatrix(parentMatrix);
actor2->SetUserMatrix(parentMatrix); // 需要手动同步更新整组 Actor
// 无法形成层次化的父子坐标系嵌套- 父子联动失效:无法像人体骨骼或机械关节那样实现“父节点旋转,子节点连带旋转”。
- 一致性处理困难:无法像对待单个 Actor 一样对一整组零件统一应用可见性、Pick 拾取与材质着色。
GoF 组合模式结构映射
| GoF 组合角色 | VTK 对应实现 | 职责说明 |
|---|---|---|
| 抽象构件 (Component) | vtkProp / vtkDataObject | 定义叶子与树节点共有的统一接口 (RenderOpaqueGeometry(), SetVisibility()) |
| 叶子构件 (Leaf) | vtkActor / vtkVolume | 树形结构的终端叶子,保存实际的几何数据或渲染材质 |
| 复合构件 (Composite) | vtkAssembly / vtkMultiBlockDataSet | 容器节点,持有子 Component 集合,实现父子变换级联与遍历 |
底层原理:矩阵变换级联与场景图遍历
vtkAssembly 继承自 vtkProp,内部维护了一个 vtkPropCollection 子节点集合。在渲染遍历时,它会自动进行矩阵乘法级联(Matrix Concatenation):
- 统一接口:
vtkAssembly本身就是一个vtkProp,因此可以直接调用renderer->AddActor(assembly)。 - 层次变换:调用
assemblyArm->RotateY(45)时,其包含的所有ActorHand会自动在T1 * T2的级联坐标系下旋转。
完整代码示例
1. 渲染侧:使用 vtkAssembly 构建机械臂场景图
cpp
#include <vtkNew.h>
#include <vtkCubeSource.h>
#include <vtkCylinderSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkAssembly.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
int main() {
// 1. 创建基座 (Base) 叶子
vtkNew<vtkCylinderSource> baseSource;
baseSource->SetRadius(2.0);
baseSource->SetHeight(0.5);
vtkNew<vtkPolyDataMapper> baseMapper;
baseMapper->SetInputConnection(baseSource->GetOutputPort());
vtkNew<vtkActor> baseActor;
baseActor->SetMapper(baseMapper);
// 2. 创建手臂 (Arm) 叶子
vtkNew<vtkCubeSource> armSource;
armSource->SetBounds(-0.5, 0.5, 0.0, 4.0, -0.5, 0.5);
vtkNew<vtkPolyDataMapper> armMapper;
armMapper->SetInputConnection(armSource->GetOutputPort());
vtkNew<vtkActor> armActor;
armActor->SetMapper(armMapper);
// 3. 构建组合节点 (vtkAssembly)
vtkNew<vtkAssembly> robotAssembly;
robotAssembly->AddPart(baseActor);
robotAssembly->AddPart(armActor);
// 4. 对组合节点进行整体操作(父子联动)
robotAssembly->SetPosition(0.0, 0.0, 0.0);
robotAssembly->RotateZ(45.0); // 基座与手臂整体旋转 45 度
// 5. 渲染器透明加入组合节点
vtkNew<vtkRenderer> renderer;
renderer->AddActor(robotAssembly); // 一致性接口:像对待普通 Actor 一样加入 Assembly
renderer->SetBackground(0.1, 0.1, 0.1);
vtkNew<vtkRenderWindow> renWin;
renWin->AddRenderer(renderer);
vtkNew<vtkRenderWindowInteractor> iren;
iren->SetRenderWindow(renWin);
renWin->Render();
iren->Start();
return 0;
}2. 数据侧:使用 vtkMultiBlockDataSet 组织分块网格
cpp
#include <vtkNew.h>
#include <vtkMultiBlockDataSet.h>
#include <vtkPolyData.h>
#include <vtkSphereSource.h>
void buildMultiBlockData() {
vtkNew<vtkSphereSource> sphere1;
sphere1->Update();
vtkNew<vtkSphereSource> sphere2;
sphere2->SetCenter(5.0, 0.0, 0.0);
sphere2->Update();
// 几何数据树形组合
vtkNew<vtkMultiBlockDataSet> multiBlock;
multiBlock->SetNumberOfBlocks(2);
multiBlock->SetBlock(0, sphere1->GetOutput());
multiBlock->SetBlock(1, sphere2->GetOutput());
}易混淆概念对比
| 组合组件 | 侧重领域 | 核心用途 |
|---|---|---|
vtkAssembly | 渲染侧场景图 | 管理多个 vtkProp 的层次坐标变换与集中渲染 |
vtkMultiBlockDataSet | 数据侧复合容器 | 管理包含多种块/组件的科学计算网格文件 (如 OpenFOAM / ANSYS) |
最佳实践与踩坑避坑
- 拾取 (Pick) 时的组合判定:
- 使用
vtkPropPicker拾取场景中的物体时,拾取结果可能会返回vtkAssembly节点或内部具体的vtkActor叶子。代码中需要调用picker->GetAssembly()进行判断,以获取完整的零件树。
- 使用
- 避免过深的 Assembly 层级:
- 矩阵级联计算在层次过深时会有浮点精度累积误差,一般嵌套层级控制在 3-5 层以内为宜。
重点与注意
重点:
vtkAssembly实现了渲染侧的组合模式,将多个vtkActor组合为树,支持统一的坐标变换与隐显控制。
重点:vtkProp是渲染层组合模式的统一接口(vtkActor、vtkVolume、vtkAssembly均继承自vtkProp)。
注意:vtkAssembly用于渲染场景图控制;vtkMultiBlockDataSet用于底层科学计算数据的块级结构管理。
小结
VTK 组合模式树立了“整体与部分一致对待”的抽象规范,构成了复杂 3D 装配体与场景图管理的基础。
延伸阅读