1.前言
一个强大的可视化系统不仅需要强大的数据处理能力,也需要方便易用的交互功能。图形处理软件ParaView(hhttp://www.paraview.org)、德国癌症研究中心研发的MITK(http://www.mitk.org)等开源软件系统都提供了强大的交互功能,作为ParaView、MITK等软件构件基础的VTK同样也提供了各种各样的交互功能。VTK的交互除了可以监听来自鼠标、键盘等外部设备的消息,还可以在渲染场景中生成功能各异的交互部件(Widget),用于控制可视化过程的参数,达到用户的渲染要求。
2.观察者/命令模式(Observe/Command)
观察者/命令模式是VTK里用的比较多的设计模式。VTK中绝大多数的类都派生自vtkObject。查看类vtkObject的接口可以找到AddObserve()、RemoveObserve()、GetCommand()等函数。观察者/命令模式是指一个Object可以有多个Observe,他定义了对象间的一种“一对多”的依赖关系,当一个Object对象的状态发生改变时,所有依赖于它的Observe对象都得到通知而被自动更新。命令模式属于对象行为模式,他将一个请求封装为一个对象,并提供一致性发送请求的接口,当一个事件发生时,他不直接把事件传递给事件调用者,而是在命令和调用者之间增加一个中间者,讲这种直接关系切断,同时将两者都隔离。事件调用者只是和接口打交道,不和具体事件实现交互。在VTK中,可以通过两种方式来实现观察者/命令模式,他们分别是使用时间回调函数、从VTKCommand派生出具体的子类。2.1 观察者-事件回调方案
在vtkObject中,有如下函数:1 unsigned long AddObserver(unsigned long event, vtkCommand *, float priority = 0.0f); 2 unsigned long AddObserver(const char* event, vtkCommand *, float priority = 0.0f);
AddObserver()函数的作用就是针对某个事件添加挂插着到某个VTK对象中。当该对象发生观察者感兴趣的事件时,就会自动调用回调函数,执行相关操作。下面是一个非常简单的例子演示了在VTK里是如何使用“观察者-事件回调函数”方案的:1 #include <vtkAutoInit.h> 2 VTK_MODULE_INIT(vtkRenderingOpenGL); 3 VTK_MODULE_INIT(vtkInteractionStyle); 4 5 #include <vtkCallbackCommand.h> 6 7 long cntPress = 0; 8 void MyCallbackFunc(vtkObject*, unsigned long eid, void* clientdata, void* calldata) 9 { 10 std::cout << "You have clicked : " << ++cntPress << " times" << std::endl; 11 } 12 13 #include <vtkSmartPointer.h> 14 #include <vtkPNGReader.h> 15 #include <vtkImageViewer2.h> 16 #include <vtkRenderer.h> 17 #include <vtkRenderWindow.h> 18 #include <vtkRenderWindowInteractor.h> 19 20 int main() 21 { 22 vtkSmartPointer<vtkPNGReader> reader = 23 vtkSmartPointer<vtkPNGReader>::New(); 24 reader->SetFileName("vtk.png"); 25 reader->Update(); 26 27 vtkSmartPointer<vtkImageViewer2> viewer = 28 vtkSmartPointer<vtkImageViewer2>::New(); 29 viewer->SetInputData(reader->GetOutput()); 30 31 viewer->GetRenderer()->SetBackground(0, 0, 0); 32 viewer->SetSize(480, 320); 33 viewer->GetRenderWindow()->SetWindowName("Observer-Callback"); 34 35 vtkSmartPointer<vtkRenderWindowInteractor> rwi = 36 vtkSmartPointer<vtkRenderWindowInteractor>::New(); 37 viewer->SetupInteractor(rwi); 38 viewer->Render(); 39 /*************************************************************/ 40 //Step1:设置事件回调函数 41 vtkSmartPointer<vtkCallbackCommand> mouseCallback = 42 vtkSmartPointer<vtkCallbackCommand>::New(); 43 mouseCallback->SetCallback(MyCallbackFunc); //很重要!!! 44 45 //Step2:将vtkCallbackCommand对象添加到观察者列表。 46 rwi->SetRenderWindow(viewer->GetRenderWindow()); //唤醒显示窗口 47 rwi->AddObserver(vtkCommand::LeftButtonPressEvent, mouseCallback); 48 49 rwi->Initialize(); 50 rwi->Start(); 51 return 0; 52 }
输出结果:通过上例,我们可以总结一下“观察者-回调函数”交互方案主要可以分为以下三个步骤:1.定义回调函数。回调函数的函数签名只能是以下形式:void long MyCallbackFunc(vtkObject* obj, unsigned long eid, void* clientdata, void* calldata);其中,obj:是调用事件的对象(即调用AddObserver()函数的对象,对应于本例的rwi);eid:是所要监听的事件ID,VTK中的事件定义在vtkCommand.h文件中;clientdata:是与VTKCallbackCommand实例相关的数据,简单地说,是指回调函数里需要访问主程序里面得数据时,由主程序向回调函数传递的数据。calldata:是执行vtkObject::InvokeEvent()函数时,随着回调函数发送得数据,比如说,当调用ProgressEvent事件时,会自动发送当前的进度值作为callback。2.创建一个VTKCallbackCommand对象,并调用VTKCallbackCommand::SetCallback()函数设置所定义的回调函数。1 //Step1:设置事件回调函数 2 vtkSmartPointer<vtkCallbackCommand> mouseCallback = 3 vtkSmartPointer<vtkCallbackCommand>::New(); 4 mouseCallback->SetCallback(MyCallbackFunc); //很重要!!!
3.将VTKCallbackCommand对象添加到对象的观察者列表中。
1 //Step2:将vtkCallbackCommand对象添加到观察者列表。 2 rwi->SetRenderWindow(viewer->GetRenderWindow()); //唤醒显示窗口 3 rwi->AddObserver(vtkCommand::LeftButtonPressEvent, mouseCallback);
注意:VTK中的vtkRenderWindowInteractor提供了一种独立于平台的交互机制,用来响应不同平台的鼠标、按键和时钟等消息。当渲染窗口中有事件发生时(比如说单机消息),vtkRenderWindowInteractor内部会调用与平台相关的子类,将该消息转换成对应平台的消息。观察者/命令模式除了使用事件回调函数外,还可以直接从vtkCommand类中派生出子类来实现。示例代码如下:1 #include <vtkAutoInit.h> 2 VTK_MODULE_INIT(vtkRenderingOpenGL); 3 VTK_MODULE_INIT(vtkInteractionStyle); 4 5 #include <vtkCommand.h> 6 #include <vtkSmartPointer.h> 7 #include <vtkConeSource.h> 8 #include <vtkPolyDataMapper.h> 9 #include <vtkActor.h> 10 #include <vtkRenderer.h> 11 #include <vtkRenderWindow.h> 12 #include <vtkRenderWindowInteractor.h> 13 #include <vtkInteractorStyleTrackballCamera.h> 14 15 class myCallback :public vtkCommand 16 { 17 public: 18 static myCallback* New() 19 { 20 return new myCallback; 21 } 22 void SetObject(vtkConeSource* cone) 23 { 24 m_cone = cone; 25 } 26 virtual void Execute(vtkObject* caller, unsigned long eventId, void* callData) 27 { 28 std::cout << "LeftButton has been pressed: " << std::endl 29 << "The Height: " << m_cone->GetHeight() << std::endl 30 << "The Radius: " << m_cone->GetRadius() << std::endl; 31 } 32 33 private: 34 vtkConeSource* m_cone; 35 }; 36 int main() 37 { 38 vtkSmartPointer<vtkConeSource> cone = 39 vtkSmartPointer<vtkConeSource>::New(); 40 cone->SetHeight(10); 41 cone->SetRadius(4); 42 cone->Update(); 43 /*************************************************************/ 44 vtkSmartPointer<vtkPolyDataMapper> mapper = 45 vtkSmartPointer<vtkPolyDataMapper>::New(); 46 mapper->SetInputConnection(cone->GetOutputPort()); 47 48 vtkSmartPointer<vtkActor> actor = 49 vtkSmartPointer<vtkActor>::New(); 50 actor->SetMapper(mapper); 51 52 vtkSmartPointer<vtkRenderer> render = 53 vtkSmartPointer<vtkRenderer>::New(); 54 render->AddActor(actor); 55 render->SetBackground(1, 1, 1); 56 57 vtkSmartPointer<vtkRenderWindow> rw = 58 vtkSmartPointer<vtkRenderWindow>::New(); 59 rw->AddRenderer(render); 60 rw->SetWindowName("Interaction By SubCommand"); 61 rw->SetSize(320, 320); 62 63 vtkSmartPointer<vtkRenderWindowInteractor> rwi = 64 vtkSmartPointer<vtkRenderWindowInteractor>::New(); 65 rwi->SetRenderWindow(rw); 66 67 68 vtkSmartPointer<vtkInteractorStyleTrackballCamera> style = 69 vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New(); 70 rwi->SetInteractorStyle(style); 71 72 vtkSmartPointer<myCallback> callback = 73 vtkSmartPointer<myCallback>::New(); 74 callback->SetObject(cone); 75 76 rwi->AddObserver(vtkCommand::LeftButtonPressEvent, callback); 77 rwi->Initialize(); 78 rwi->Start(); 79 return 0; 80 }
输出结果为:这个例子实现的功能也是监听鼠标左键单机的消息。基于“观察者-vtkCommand子类”的实现方案也遵循三个步骤。1. 从vtkCommand类中派生出子类,并实现vtkCommand::Execute()虚函数。该函数原型为:virtual void Execute(vtkObject* caller, unsigned long eventId, void* callData )= 0;Execute()时纯虚函数,所以,从vtkCommand派生类中都必须要实现这个方法。2.实例化vtkCommand子类的对象,并调用相关的方法。3.调用观察者函数。调用AddObserver()函数监听感兴趣的事件,如果所监听的事件发生,就会调用vtkCommand子类中定义的Execute()函数。因此,针对所监听的事件,程序需要把实现的功能放在Execute函数中。