交互器样式(如vtkInteractorStyleImage)主要是根据不同的键盘、鼠标等消息来控制相机(vtkCamera)/Actor等相关参数,从而达到了交互的目的!
然而,在渲染场景中,这些交互器样式是没有表达实体的。也就是说,在交互之前,我们(用户)必须知道那些键盘消息或者鼠标消息是与哪些事件绑定的,在整个交互过程中,用户“看不到”交互器样式长什么样子,比如,使用vtkInteractorStyleImage交互器样式时,必须知道按键<R>是用于窗宽窗位、相机参数等的重置,鼠标中键可以平移图像,按住鼠标左键不放然后移动鼠标可以调节窗宽窗位等。
在与渲染场景中的对象进行交互时,如果可以“看得见”交互的样式,这样的交互过程就会更加的人性化,比如,要在地图上测量AB两点之间的距离,直观的做法就是:在A点上单击,当松开鼠标后,程序在单击的位置上生成一个端点(该端点可以是圆形、十字形或者其他任何形状),然后移动鼠标至终点,鼠标移动过程中,在A点与鼠标光标的当前位置生成一条直线,当鼠标移动至B点时,再单击B点位置,即可显示出AB两点的距离以及在两点之间生成一条直线。显然,这样的交互方式比交互器样式更加直观、生动。
VTK提供了功能强大的、可以看得见的交互部件,即Widget。VTK的Widget类主要包括vtk3DWidget和vtkAbstractWidget两个父类。继承关系如下图所示:
从上图中,我们可以看到,vtk3DWidget和vtkAbstractWidget都派生自vtkInteractorObserver,其中,前者主要是在三维渲染场景中生成一个可以用于控制数据的可视化实体,比如点,线段(曲线)、平面、球体、包围盒(线框)等;而后者是VTK里实现“交互/表达实体”设计的所有Widget的基类。
vtk3DWidget和vtkAbstractWidget的共同基类vtkInteractorObserver里的虚函数OnChar(),主要是用于响应交互的开关状态,对应的方法为:
vtkInteractorObserver::SetEnable(int);
vtkInteractorObserver::EnableOn();
vtkInteractorObserver::EnableOff();
vtkInteractorObserver::On();
vtkInteractorObserver::Off();
VTK中Widget的设计是从VTK 5.0版本开始引入的,最初的Widget是从vtk3DWidget派生出来的,从VTK5.1版本开始,VTK中的Widget重新进行设计,主要的设计理念是将Widget的消息处理与几何表达实体分离,但还是保留了vtk3DWidget及其子类。
2.1 vtkAbstractWidget
vtkAbstractWidget作为基类,只定义一些公共的API以及实现了“交互/表达实体”分离的设计机制,其中,把从vtkRenderWindowInteractor路由过来的消息(事件)交给vtkAbstractWidget的“交互”部分处理,而Widget的“表达实体”则对应一个vtkProp对象(或者是vtkWidgetRepresentation的子类)。
这样做的好处是:事件处理与Widget的表达实体互不干扰,而且可以实现同类Widget使用不同的表达形式,比如,对于测量距离的Widget,可以定义两个十字形作为Widget的两个端点(也可以定义两个球体来表达)。
此外,vtkAbstractWidget类提供了访问vtkWidgetEventTranslator对象的函数,GetEventTranslator(),该对象的作用可以将VTK事件映射为Widget事件(定义在vtkWidgetEvent.h文件中),通过vtkWidgetEventTranslator类,我们可以定制与符合自己习惯使用的控制事件相绑定。比如,对于一个测量长度的vtkDistanceWidget,默认的操作是鼠标左键可以确定两个端点的位置,如果对这种操作不习惯,想用鼠标右键实现同样的功能,可以通过代码来实现:
1 vtkWidgetEventTranslation* eventTranslation = widget->GetEventTranslator();
2 eventTranslation->SetTranslation(
3 vtkCommand::RightButtonPressEvent,
4 vtkWidgetEvent::Select);
5 eventTranslation->SetTranslation(
6 vtkCommand::RightButtonReleaseEvent,
7 vtkWidgetEvent::Select);
2.2 VTK事件与Widget事件间的转换关系
上面已经初步分析了何如定制属于自己的Widget交互部件。下面进一步讨论:
每个vtkAbstractWidget子类的内部,都会根据各个子类的功能,使用类vtkWidgetEventTranslator,将VTK事件爱你翻译成Widget事件。同时,利用类vtkWidgetCallbackMapper将相应的Widget事件与各个受保护的静态操作函数关联起来,具体关系如下图所示:
以vtkDistanceWidget为例,在该类的构造函数中,有如下代码:
1 this->CallbackMapper->SetCallbackMethod(
2 vtkCommand::LeftButtonPressEvent,
3 vtkWidgetEvent::AddPoint,
4 this,vtkDistanceWidget::AddPointAction);
5 this->CallbackMapper->SetCallbackMethod(
6 vtkCommand::MouseMoveEvent,
7 vtkWidgetEvent::Move,
8 this,vtkDistanceWidget::MoveAction);
9 this->CallbackMapper->SetCallbackMethod(
10 vtkCommand::LeftButtonReleaseEvent,
11 vtkWidgetEvent::EndSelect,
12 this,vtkDistanceWidget::EndSelectAction);
上述代码中的CallbackMapper即为vtkWidgetCallbackMapper类型,SetCallbackMethod()函数代码如下:
1 void vtkWidgetCallbackMapper::SetCallbackMethod(
2 unsigned long VTKEvent,
3 unsigned long widgetEvent,
4 vtkAbstractWidget* w,
5 CallbackType f)
6 {
7 this->EventTranslator->SetTranslation(VTKEvent, widgetEvent);
8 this->SetCallbackMethod(widgetEvent,w,f);
9 }
2.3 小结
通过上面可知,vtkWidgetCallbackMapper::SetCallbackMathod()将VTK消息与实际的操作函数联系起来,SetCallbackMethod()函数内部则是调用vtkWidgetEventTranslator::SetTranslation方法将VTK事件翻译成Widget事件,这种实现机制有点类似Qt里的信号-槽连接。