VTK可视化管线
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
================================================
總結
- 上一篇: Adobe illustrator 魔棒
- 下一篇: 类的虚函数和多态性