04-VTK可视化管线(1)


4、VTK可视化管线

通过第3章的学习,我们已经了解了VTK的一些基础概念。在这一章里,我们将更深入地学习VTK,其中包括VTK的系统框架结构、引用计数、智能指针、Observer/Command设计机制以及本章的重点内容——VTK可视化管线结构。通过本章的学习,可能你对VTK的设计框架将会有更深一层的理解。

所谓追根溯源,首先我们先了解一下VTK里绝大多数类的共同的父类vtkObjectBase和vtkObject。

4.1 vtkObjectBase和vtkObject

vtkObjectBase是一个抽象基类,派生出绝大多数的VTK类。它是VTK里所有引用计数(Reference Counting)类的基类,著名的子类包括:vtkCommand,vtkInformationKey和vtkObject。

4.1.1 引用计数

如果很多对象有相同的值,将这个值存储多次是很无聊的。更好的办法是让所有的对象共享这个值的实现。这么做不但节省内存,而且可以使得程序运行更快,因为不需要构造和析构这个值的拷贝。引用计数就是这样一个技巧,它允许多个有相同值的对象共享这个值的实现。引用计数是个简单的垃圾回收体系,只要其它对象引用某对象(记为对象O),对象O就会存在一个引用计数,当最后引用对象O的对象移除,O对象就会自动析构。VTK里使用引用计数的好处是,可以实现数据之间的共享而不用拷贝,从而达到节省内存的目的。

我们可以看一个简单的例子(4.1.1_ReferenceCounting):

 

///ReferenceCount.cpp/

  1:  #include"vtkSmartPointer.h"

  2:  #include"vtkBMPReader.h"

  3:  #include"vtkImageData.h"

  4:  

  5:  int main(int argc, char*argv[])

  6:  {

  7:  vtkSmartPointer<vtkBMPReader>reader = vtkSmartPointer<vtkBMPReader>::New();

  8:   reader->SetFileName("../test.bmp");

  9:    reader->Update();

 10:  

 11:   std::cout<<"Reference Count of reader->GetOutput (BeforeAssignment) = "

 12:       <<reader->GetOutput()->GetReferenceCount()<<std::endl;

 13:  

 14:    vtkSmartPointer<vtkImageData> image1 = reader->GetOutput();

 15:   std::cout<<"Reference Count of reader->GetOutput (Assignto image1) = "

 16:       <<reader->GetOutput()->GetReferenceCount()<<std::endl;

 17:   std::cout<<"Reference Count of image1 = "

 18:       <<image1->GetReferenceCount()<<std::endl;

 19:  

 20:   vtkSmartPointer<vtkImageData> image2 = reader->GetOutput();

 21:   std::cout<<"Reference Count of reader->GetOutput (Assignto image2) = "

 22:       <<reader->GetOutput()->GetReferenceCount()<<std::endl;

 23:   std::cout<<"Reference Count of image2 = "

 24:       <<image2->GetReferenceCount()<<std::endl;

 25:  

 26:    return 0;

 27:  }

//

 

程序输出结果如图4.1所示。

图4.1ReferenceCount运行结果

在ReferenceCount示例里,我们先用vtkBMPReader读入一幅BMP图像test.bmp,在赋值之前我们输出了reader->GetOutput()的引用计数值,其值为1(使用方法New()创建对象以后,初始的引用计数值就等于1);然后我们创建了一个vtkImageData类型的对象image1,并把reader的输出赋给了image1,这时image1就指向了reader的输出,也就是说,reader的输出多了一个引用,这个时候输出的reader->GetOutput()和image1的引用计数都为2;接着我们又创建一个类型同样为vtkImageData的对象image2,同样也是把reader的输出赋值给image2,这时,image2也指向reader的输出,亦即reader的输出又多了一个引用,所以输出的reader->GetOutput()和image2的引用计数值变成了3。image1和image2的数据结构可以简单地描述为图4.2。

图4.2image1,image2,reader->GetOutput()及引用计数之间的结构关系

一旦某个对象的引用计数等于0时,就表明没有别的对象再引用它,它的使命也宣告完成,程序就会自动的析构这个对象。在ReferenceCount这个示例里,我们看不到引用计数减少的相关代码,这是因为我们使用了智能指针vtkSmartPointer。

4.1.2 智能指针

智能指针会自动管理引用计数的增加与减少,如果检测到某对象的引用计数值减少为0,则会自动地释放该对象的资源,从而达到自动管理内存的目的。

在前面的内容我们已经介绍过,VTK里,要创建一个对象可以用两种方法,一种是使用vtkObjectBase里的静态成员变量New(),用Delete()方法析构;另一种就是我们示例里使用多次的使用智能指针vtkSmartPointer<T>。

对于第一种方法,用New()创建的对象,程序最后必须要调用Delete()方法释放对应的内存,而且由于vtkObjectBase及其子类的构造函数都是声明为受保护的,这意味着它们不能在栈区(栈区上的内存是由编译器自动分配与释放的,堆区上的内存则是由程序员分配和手动释放的。)上分配内存。比如:

vtkBMPReader*reader = vtkBMPReader::New(); //创建vtkBMPReader对象

……

reader->Delete();//程序最后要调用Delete(),这里并没有直接析构对象,而是使引用计数值减1。

用New()创建的对象,如果没有用Delete()方法删除的话,程序有可能会出现内存泄漏,即用户负责对象内存的管理。

如果使用智能指针创建的对象,则无需手动调用Delete()方法让引用计数减少,因为引用计数的增加与减少都是由智能指针自动完成的。使用智能指针时,首先是要包含智能指针的头文件:#include "vtkSmartPointer.h"。vtkSmartPointer是一个模板类,所需的模板参数就是待创建的对象的类名,如:

vtkSmartPointer<vtkImageData>image = vtkSmartPointer< vtkImageData >::New();

注意上面一行代码等号右边写法,不能写为:

vtkSmartPointer<vtkImageData > image = vtkImageData::New();

也就是不能把对象的原始指针赋给智能指针,上行代码编译的时候可以通过,但程序退出时会有内存泄漏,就是因为智能指针无法自动释放该对象的内存,如图4.3所示。

图4.3 将对象的原始指针赋予智能指针会引起内存泄漏

如果没有给对象分配内存,仍然可以使用智能指针,比如:

vtkSmartPointer<vtkBMPReader>reader =vtkSmartPointer<vtkBMPReader>::New();

vtkImageData* imageData=reader->GetOutput();

或者:

vtkSmartPointer< vtkImageData> imageData = reader->GetOutput();

第一种情况,当reader超出其作用域时,数据即会被删除;第二种情况,使用了智能指针,所以数据的引用计数会自动加1,除非reader和imageData都超出它们的作用域,数据才会被删除。

智能指针类型同样也可以作为函数的返回值。正确的写法类似:

vtkSmartPointer<vtkImageData>MyFunction()

{

  vtkSmartPointer<vtkImageData> myObject= vtkSmartPointer<vtkImageData>::New();

  return myObject;

}

调用时则是:

vtkSmartPointer<vtkImageData>MyImageData = MyFunction();

函数MyFunction()的返回值是通过拷贝的方式,将数据赋予调用的变量,因此该数据的引用计数保持不变,而且函数MyFunction里的myObject对象也不会删除。

下面的函数形式和函数调用是错误的,应该引起注意:

vtkImageData* MyFunction()

{

  vtkSmartPointer< vtkImageData >MyObject = vtkSmartPointer< vtkImageData >::New();

  return MyObject;

}

vtkImageData* MyImageData = MyFunction();

在函数MyFunction()里定义的是智能指针类型的,最后返回时转换成原始指针类型,当函数调用结束时,智能指针的引用计数会减为0,即函数MyFunction里的MyObject对象会被删除掉,也就是说MyFunction()返回的是悬空指针,这时再赋予MyImageData变量就会出错。

智能指针类型也可以作为类的成员变量,而且会使得类在析构时更加容易,不用人为去做任何释放内存的事情,把这些工作都交给智能指针来完成,例如:

classMyClass

{

  vtkSmartPointer<vtkFloatArray>Distances;

};

然后在类的构造函数里进行初始化:

MyClass::MyClass()

{

  Distances = vtkSmartPointer<vtkFloatArray>::New();

}

在类的析构函数里不用调用Delete()去删除任何东西。

智能指针有一个让人困惑的地方:当你创建一个智能指针类型的对象,然后改变它的指向,这时引用计数就会出错。例如:

vtkSmartPointer<vtkImageData>imageData = vtkSmartPointer<vtkImageData>::New();

imageData= Reader->GetOutput();

上面两行代码里,我们首先创建一个imageData,并给他分配好了内存,接着我们又把imageData指向Reader的输出,而不是一直指向我们创建的那块内存。对于这种情况,我们只要简单地调用:

vtkImageData*imageData = Reader->GetOutput();

这里没有必要使用智能指针,因为我们没有实际创建任何新的对象。

综上所述,可以看出引用计数和智能指针是息息相关的,它们主要都是用于内存管理。使用智能指针可以免去很多手动删除变量的烦恼,所以在本教程里,我们从一开始都使用智能指针来创建VTK对象。如果你想了解更多关于引用计数和智能指针的内容,可以参考C++的经典著作《More Effective C++》这本书。

4.1.3 运行时类型识别 (Run-Time Type Information,RTTI)

在C++里,对象类型是通过typeid (需要包含头文件#include<type_info>)获取的;VTK里在vtkObjectBase定义了获取对象类型的方法:GetClassName()和IsA()。GetClassName()返回的是该对象类名的字符串(VTK用类名来识别各个对象),如:

vtkSmartPointer<vtkBMPReader>Reader = vtkSmartPointer<vtkBMPReader>::New();

constchar* type = Reader->GetClassName(); //返回“vtkBMPReader”字符串

IsA()方法用于测试某个对象是否为指定字符串的类型或其子类,比如:

if(Reader->IsA(“vtkImageReader”) ) {……}; // 这里IsA()会返回真。

类比C++里的操作RTTI操作符,除了typeid之外,还有dynamic_cast,主要用于基类向子类的类型转换,称为向下转型。VTK里同样提供了类似的方法,也就是vtkObject里定义的SafeDownCast(),它是vtkObject里的静态成员函数,意味着它是属于类的,而不是属于对象的,即可以用vtkObject::SafeDownCast()直接调用,比如:

vtkSmartPointer<vtkImageReader>ReaderBase = vtkSmartPointer<vtkImageReader>::New();

vtkBMPReader*bmpReader = vtkBMPReader::SafeDownCast(ReaderBase);

与dynamic_cast类似,SafeDownCast也是运行时才转换的,这种转换只有当bmpReader的类型确实是ReaderBase的派生类时才有效,否则返回空指针。

除了运行时类型识别,vtkObjectBase还提供了用于调试的状态输出接口Print()。虽然vtkObjectBase里除了Print()还提供PrintSelf()、PrintHeader()、PrintTrailer()等公共接口,但在调试VTK程序时,如果需要输出某个对象的状态信息时,一般都是调用Print()函数,如:

bmpReader->Print(std::cout);

4.1.4 关于vtkObject的两三事

以上的几小节都是在讨论vtkObjectBase这个VTK始祖类的一些特性,接下来我们看一下vtkObjectBase这个始祖类其中一个“儿子”的本领。vtkObject是大多数VTK类的父类,是一个抽象基类,大多数在VTK框架里的类都应该是它的子类或其某个子类的子类。这一小节我们起名为“关于vtkObject的两三事”,因为vtkObject刚好做了三件重要的事情。

第一件事是,vtkObject里定义了与程序调试相关的一些公共接口,包括:

 

DebugOn() / DebugOff()

GetDebug() / SetDebug(unsignedchar)

 

SetGlobalWarningDisplay(int) / GetGlobalWarningDisplay()

GlobalWarningDisplayOn() / GlobalWarningDisplayOff()

 

其中后四个是静态成员函数。我们可以通过示例(4.1.4_vtkObjectDemo)来看看vtkObject做的第一件事到底是什么。

 

///vtkObjectDemo.cpp/

  1:  #include"vtkSmartPointer.h"

  2:  #include"vtkBMPReader.h"

  3:  #include "vtkImageViewer2.h"

  4:  #include"vtkRenderWindowInteractor.h"

  5:  

  6:  int main(int argc, char*argv[])

  7:  {

  8:   vtkSmartPointer<vtkBMPReader> reader =vtkSmartPointer<vtkBMPReader>::New();

  9:   reader->SetFileName("../monochrome.bmp");

  10:   reader->Allow8BitBMPOn();

 11:    reader->SetDebug(1);

 12:    //reader->SetDebug(0);

 13:    //reader->GlobalWarningDisplayOff();

 14:    reader->Update();

 15:  

 16:   vtkSmartPointer<vtkImageViewer2> viewer =vtkSmartPointer<vtkImageViewer2>::New();

 17:   viewer->SetInput(reader->GetOutput());

 18:   

 19:   vtkSmartPointer<vtkRenderWindowInteractor> interactor =

 20:       vtkSmartPointer<vtkRenderWindowInteractor>::New();

 21:   viewer->SetupInteractor(interactor);

 22:    viewer->Render();

 23:  

 24:   interactor->Initialize();

 25:    interactor->Start();

 26:  

 27:    return 0;

28:  }

//

第11行SetDebug(1)作用等同于DebugOn(),第12行SetDebug(0)等同于DebugOff()。示例里我们读入的BMP图像是单色的,由于VTK不支持单色的BMP图像的读取,所以如果调用方法SetDebug(1)或者DebugOn()时,则会弹出图4.4-A所示的窗口;调用DebugOff()时,弹出的窗口如图4.4-B所示,如果不想看到vtkOutputWindow窗口,可以调用GlobalWarningDisplayOff()或者SetGlobalWarningDisplay(0)。

图4.4vtkOutputWindow窗口

因为VTK不支持1位BMP图像的读取,所以调用reader->GetOutput()时不能得到正确的输出,最终会导致程序在关闭时,出现内存溢出的错误,如图4.5所示。

图4.5 示例4.1.4_vtkObjectDemo程序退出时内存溢出错误

:示例4.1.4_vtkObjectDemo也可以作一下更改,以便让它支持命令行参数,作为BMP图像的浏览器,修改后的工程为BMPImageViewer。比如在CMD窗口里输入命令:

D:\\Toolkits\\VTK\\Examples\\4.1.4_vtkObjectDemo\\bin\\Debug\\BMPImageViewer.exeD:\\Toolkits\\VTK\\Examples\\4.1.4_vtkObjectDemo\\test.bmp (回车,即可运行程序)

或者可以直接在VS2008里输出需要的命令行参数。右击“BMPImageViewer”工程,选择属性,然后选择属性对话框左边的Debugging,接着在“Command Arguments”一栏输出所需的参数,比如..\\test.bmp (可以用绝对路径,如果路径里含有空格,记得用双引号把整个路径括起来)。确定,保存所做的更改。最后在VS2008下F5运行程序。

如果你感觉程序运行时后面老是跟着控制台窗口,让你觉得不爽的话,你可以在main()函数之前加入下面的语句隐藏控制台窗口,一般建议在发布程序之前不要隐藏,方便调试程序时随时输出调试信息。

#pragmacomment(linker,"/subsystem:\\"windows\\"/entry:\\"mainCRTStartup\\"")

vtkObject第二件事是,实现观察者/命令(Observer/Command)设计模式。本教程不是专门介绍设计模式的,如果你想更深入地了解Observer/Command设计模式,可以翻翻相关的书籍,下面只是就这两种设计模式蜻蜓点水般的做一概述。

vtkObject定义了与观察者Observer相关的方法(如,AddObserver()/RemoveObserver()),观察者模式主要针对两个对象:Object和Observer。一个Object可以有多个Observer,它定义对象间的一种一对多的依赖关系,当—个Object对象的状态发生改变时,所有依赖于它的Observer对象都得到通知被自动更新。

命令模式属于对象行为模式,它将—个请求封装为一个对象,并提供一致性发送请求的接口,当一个事件发生时,它不直接把事件传递到事件调用者,而是在命令和调用者之间增加—个中间者,将这种直接关系切断,同时两者之间都隔离。事件调用者只是和接口打交道,不和具体实现交互。命令模式的实现是由vtkObjectBase的另外一个重要子类vtkCommand及其派生类实现的。

如果你还是觉得抽象的话,我们就看看下面的示例(ObserverCommandDemo.cpp):

 

ObserverCommandDemo.cpp///

  1:  #include"vtkSmartPointer.h"

  2:  #include"vtkBMPReader.h"

  3:  #include"vtkImageViewer2.h"

  4:  #include"vtkRenderWindowInteractor.h"

  5:  #include"vtkCallbackCommand.h"

  6:  

  7:  long pressCounts = 0;

  8:  

  9:  //第一步,定义回调函数。

 10:  //注意回调函数的签名,不能更改。

 11:  void MyCallbackFunc(vtkObject*, unsigned long eid, void* clientdata,void *calldata)

 12:  {

 13:     std::cout<<"You have clicked:"<<++pressCounts<<" times."<<std::endl;

 14:  }

 15:  

 16:  int main(int argc, char*argv[])

 17:  {

 18:   vtkSmartPointer<vtkBMPReader> reader =

 19:       vtkSmartPointer<vtkBMPReader>::New();

 20:   reader->SetFileName("../test.bmp");

 21:   reader->Allow8BitBMPOn();

 22:    reader->SetDebug(0);

 23:   reader->GlobalWarningDisplayOff();

 24:    reader->Update();

 25:  

 26:   vtkSmartPointer<vtkImageViewer2> viewer =

 27:        vtkSmartPointer<vtkImageViewer2>::New();

 28:   viewer->SetInput(reader->GetOutput());

 29:   

 30:   vtkSmartPointer<vtkRenderWindowInteractor> interactor =

 31:       vtkSmartPointer<vtkRenderWindowInteractor>::New();

 32:    viewer->SetupInteractor(interactor);

 33:    viewer->Render();

 34:  

 35:    //第二步,设置回调函数。

 36:    vtkSmartPointer<vtkCallbackCommand> mouseCallback =

 37:       vtkSmartPointer<vtkCallbackCommand>::New();

 38:    mouseCallback->SetCallback ( MyCallbackFunc );

 39:  

 40:    //第三步,将vtkCallbackCommand对象添加到观察者列表。

 41:   interactor->SetRenderWindow(viewer->GetRenderWindow());

 42:    interactor->AddObserver(vtkCommand::LeftButtonPressEvent,mouseCallback);

 43:  

 44:   interactor->Initialize();

 45:    interactor->Start();

 46:  

 47:    return 0;

 48:  }

//

 

示例ObserverCommandDemo运行结果如图4.6所示。

图4.6 示例ObserverCommandDemo运行结果

从示例ObserverCommandDemo可以看到,VTK里使用事件回调函数时,需要分三步走。首先,定义回调函数。回调函数的签名只能是以下形式:

void MyCallbackFunc(vtkObject*obj,unsigned long eid, void* clientdata, void *calldata)

其次是创建一个vtkCallbackCommand对象,并调用vtkCallbackCommand::SetCallback()设置第一步定义的回调函数。

最后是将vtkCallbackCommand对象添加到对象的观察者列表。VTK里,所有的事件都是通过vtkInteractorObserver(vtkRenderWindowInteractor的父类,vtkInteractorObserver派生自vtkObject) 进行监听的,所以,我们把vtkCallbackCommand对象加入到vtkRenderWindowInteractor对象的观察者列表中,调用的就是vtkObject提供的公共接口AddObserver(),其原型为:

unsigned longAddObserver (unsigned long event, vtkCommand *, float priority=0.0f)

第一个参数是要监听的事件,这些事件定义在vtkCommand类里,如:

RenderEvent /ProgressEvent / PickEvent / StartPickEvent / EndPickEvent / ExitEvent /LeftButtonPressEvent / LeftButtonReleaseEvent / MiddleButtonPressEvent /MiddleButtonReleaseEvent / RightButtonPressEvent / RightButtonReleaseEvent /KeyPressEvent / KeyReleaseEvent     /CharEvent / TimerEvent

示例中我们监听的是鼠标左键的单击事件,即LeftButtonPressEvent。

AddObserver的第二个参数是vtkCommand类型的指针,即我们创建的已经绑定回调函数的vtkCallbackCommand对象。第三个参数是设置命令响应的优先权。

AddObserver函数的返回值是unsigned long型的,可以用于把观察者从观察者列表中删除RemoveObserver(eventID)。

示例ObserverCommandDemo监听交互过程中的鼠标左键单击事件,如果监听到该事件,就在控制台中输出目前为止鼠标的单击次数,该示例仅仅是为了演示观察者/命令模式的工作方式,除此之外没有任何实用价值。除了用以上介绍的回调函数形式来完成事件/回调的工作,同样也可以用类(派生自vtkCommand)的形式来完成。示例ObserverCommandDemo2里,我们会看到稍微复杂一点的应用。

 

ObserverCommandDemo2.cpp/

  1:  #include"vtkSmartPointer.h"

  2:  #include"vtkConeSource.h"

  3:  #include"vtkPolyDataMapper.h"

  4:  #include"vtkRenderWindow.h"

  5:  #include"vtkRenderWindowInteractor.h"

  6:  #include"vtkCamera.h"

  7:  #include"vtkActor.h"

  8:  #include"vtkRenderer.h"

  9:  #include"vtkCommand.h"

 10:  #include"vtkBoxWidget.h"

 11:  #include"vtkTransform.h"

 12:  #include"vtkInteractorStyleTrackballCamera.h"

 13:  

 14:  //第一步

 15:  class vtkMyCallback : publicvtkCommand

 16:  {

 17:  public:

 18:    static vtkMyCallback *New()

 19:      { return newvtkMyCallback; }

 20:  

 21:    virtual voidExecute(vtkObject *caller, unsigned long eventId, void* callData)

 22:      {

 23:        vtkTransform *t =vtkTransform::New();

 24:        vtkBoxWidget *widget =reinterpret_cast<vtkBoxWidget*>(caller);

 25:       widget->GetTransform(t);

 26:       widget->GetProp3D()->SetUserTransform(t);

 27:        t->Delete();

 28:      }

 29:  };

 30:  

 31:  int main()

 32:  {

 33:   vtkSmartPointer<vtkConeSource> cone =vtkSmartPointer<vtkConeSource>::New();

 34:    cone->SetHeight( 3.0 );

 35:    cone->SetRadius( 1.0 );

 36:    cone->SetResolution( 10);

 37:  

 38:   vtkSmartPointer<vtkPolyDataMapper> coneMapper =

 39:       vtkSmartPointer<vtkPolyDataMapper>::New();

 40:   coneMapper->SetInputConnection( cone->GetOutputPort() );

 41:  

 42:   vtkSmartPointer<vtkActor> coneActor = vtkSmartPointer<vtkActor>::New();

 43:    coneActor->SetMapper(coneMapper );

 44:  

 45:   vtkSmartPointer<vtkRenderer> ren1=vtkSmartPointer<vtkRenderer>::New();

 46:    ren1->AddActor(coneActor );

 47:    ren1->SetBackground(0.1, 0.2, 0.4 );

 48:  

 49:   vtkSmartPointer<vtkRenderWindow> renWin =

 50:       vtkSmartPointer<vtkRenderWindow>::New();

 51:    renWin->AddRenderer(ren1 );

 52:    renWin->SetSize( 300,300 );

 53:  

 54:   vtkSmartPointer<vtkRenderWindowInteractor> iren =

 55:        vtkSmartPointer<vtkRenderWindowInteractor>::New();

 56:   iren->SetRenderWindow(renWin);

 57:  

 58:   vtkSmartPointer<vtkInteractorStyleTrackballCamera> style =

 59:     vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();

 60:    iren->SetInteractorStyle(style);

 61:  

 62:    //通过vtkBoxWidget可以控制coneActor的变换矩阵,从而实现coneActor的形变

 63:   vtkSmartPointer<vtkBoxWidget> boxWidget =vtkSmartPointer<vtkBoxWidget>::New();

 64:   boxWidget->SetInteractor(iren);

 65:    boxWidget->SetPlaceFactor(1.25);

 66:   boxWidget->SetProp3D(coneActor);

 67:   boxWidget->PlaceWidget();

 68:  

 69:    //第二步

 70:   vtkSmartPointer<vtkMyCallback> callback =vtkSmartPointer<vtkMyCallback>::New();

 71:  

 72:    //第三步

 73:    boxWidget->AddObserver(vtkCommand::InteractionEvent,callback);

 74:  

 75:    //激活Widget。按“i”键可以关闭或激活Widget。

 76:    boxWidget->On();

 77:  

 78:    iren->Initialize();

 79:    iren->Start();

 80:  

 81:    return 0;

 82:  }

//

 

示例的第31行一直到61行,都是我们比较熟悉的代码,唯一比较陌生的类是vtkBoxWidget,你可以先查看一下这个类的说明文档,关于Widget的使用后续章节也会重点介绍,这里暂且不提,我们的重点是Observer/Command的应用。

与回调函数的类似,首先我们从vtkCommand派生出类vtkMyCallback,该类主要实现两个方法,一个是New(),用于为创建的对象申请内存;一个是Execute(),这是父类vtkCommand里定义的纯虚函数,其原型为:

virtual voidExecute(vtkObject *caller, unsigned long eventId,void *callData) = 0;

这意味着,只要从vtkCommand派生的类,都必须实现这个方法。而这个方法的作用就是一旦监听到所要监听的事件,就会自动地调用该方法。监听到事件后,要完成什么样的操作,都是在Execute()方法里实现,所以我们把精力都放在这个方法上。第一个参数是caller,指向调用观察者的对象,即调用AddObserver()方法的那个对象,如本例中的boxWidget。第二个参数eventId是事件的编号。第三个参数callData,是传递给Execute函数的数据,本例我们没有给该函数传递任何数据,如果你很想知道这个参数有值时应该如何使用,可以找找你计算机上的VTK目录(D:\\Toolkits\\VTK\\VTK-5.10\\Examples\\Tutorial\\Step2\\Cxx\\Cone2.cxx)。

将Execute()与前面介绍的回调函数作一对比:

void MyCallbackFunc(vtkObject*obj,unsigned long eid, void* clientdata, void *calldata)

前两个参数都是一样的,MyCallbackFunc的第四个参数与Execute()的第三个参数一样,第三个参数是clientdata,这个数据是指回调函数里需要访问主程序里的数据时,由主程序向回调函数传递的,可以通过方法vtkCallbackCommand::SetClientData()设置。

接下来我们看看MyCallback::Execute()的实现(23到27行)。首先我们定义一个vtkTransform对象;然后把传递过来的数据caller用C++操作符reinterpret_cast<>转换成类型vtkBoxWidget*,示例里的这个转换是可以成功的,因为调用观察者的对象也是vtkBoxWidget*类型的(73行)。紧接着,我们把从vtkBoxWidget对象获取到的变换重新应用到vtkBoxWidget里的Prop对象,也就是说Prop对象会跟着vtkBoxWidget对象做同样的变换,或伸或缩。

第二步(69-70行),实例化一个vtkMyCallback对象。

第三步(72-73行),监听vtkBoxWidget对象的交互事件(InteractionEvent)。也就是说,当用户与vtkBoxWidget对象交互时,事件InteractionEvent就会被触发,程序就会自动调用vtkMyCallback里的Execute()事件。

示例ObserverCommandDemo2运行结果如图4.7。

图4.7 ObserverCommandDemo2运行结果(按“i”键可以关闭或激活Widget)

Observer/Command模式在VTK里的应用是非常广泛也是非常重要的,可以用回调函数或者类的形式来实现,以上的内容已经给出比较详细的介绍,使用VTK的话,这两种方式是没有理由不掌握的。

我们继续看vtkObject的第三件事。翻翻vtkObject.h文件,里面有一个受保护的数据成员MTime,与这个MTime相关的公共接口有GetMTime() (返回MTime的值)以及Modified()。MTime全称就是Modification Time,也就是修改时间。vtkObject的第三件事就是我们接下来要学习的内容——“可视化管线”。

==========欢迎转载,转载时请保留该声明信息==========

版权归@东灵工作室所有,更多信息请访问东灵工作室


教程系列导航:http://blog.csdn.net/www_doling_net/article/details/8763686

================================================



© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容