Skip to content

VTK 设计模式:组合模式

系列:Qt / VTK 设计模式 · VTK 09/10
参考:vtkAssemblyvtkMultiBlockDataSet


引子

在一个复杂的机械装配体三维场景中(如包含螺丝、轴承、齿轮、外壳的完整车床),如果需要对整个装配体进行整体平移、旋转或隐显控制,若对每一个零配件 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):

  1. 统一接口vtkAssembly 本身就是一个 vtkProp,因此可以直接调用 renderer->AddActor(assembly)
  2. 层次变换:调用 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)

最佳实践与踩坑避坑

  1. 拾取 (Pick) 时的组合判定
    • 使用 vtkPropPicker 拾取场景中的物体时,拾取结果可能会返回 vtkAssembly 节点或内部具体的 vtkActor 叶子。代码中需要调用 picker->GetAssembly() 进行判断,以获取完整的零件树。
  2. 避免过深的 Assembly 层级
    • 矩阵级联计算在层次过深时会有浮点精度累积误差,一般嵌套层级控制在 3-5 层以内为宜。

重点与注意

重点vtkAssembly 实现了渲染侧的组合模式,将多个 vtkActor 组合为树,支持统一的坐标变换与隐显控制。
重点vtkProp 是渲染层组合模式的统一接口(vtkActorvtkVolumevtkAssembly 均继承自 vtkProp)。
注意vtkAssembly 用于渲染场景图控制;vtkMultiBlockDataSet 用于底层科学计算数据的块级结构管理。


小结

VTK 组合模式树立了“整体与部分一致对待”的抽象规范,构成了复杂 3D 装配体与场景图管理的基础。

延伸阅读

基于 VitePress 强力驱动 | 记录技术与生活