VTK可视化管线
4、VTK可視化管線
通過第3章的學習,我們已經(jīng)了解了VTK的一些基礎概念。在這一章里,我們將更深入地學習VTK,其中包括VTK的系統(tǒng)框架結(jié)構(gòu)、引用計數(shù)、智能指針、Observer/Command設計機制以及本章的重點內(nèi)容——VTK可視化管線結(jié)構(gòu)。通過本章的學習,可能你對VTK的設計框架將會有更深一層的理解。
所謂追根溯源,首先我們先了解一下VTK里絕大多數(shù)類的共同的父類vtkObjectBase和vtkObject。
4.1 vtkObjectBase和vtkObject
vtkObjectBase是一個抽象基類,派生出絕大多數(shù)的VTK類。它是VTK里所有引用計數(shù)(Reference Counting)類的基類,著名的子類包括:vtkCommand,vtkInformationKey和vtkObject。
4.1.1 引用計數(shù)
如果很多對象有相同的值,將這個值存儲多次是很無聊的。更好的辦法是讓所有的對象共享這個值的實現(xiàn)。這么做不但節(jié)省內(nèi)存,而且可以使得程序運行更快,因為不需要構(gòu)造和析構(gòu)這個值的拷貝。引用計數(shù)就是這樣一個技巧,它允許多個有相同值的對象共享這個值的實現(xiàn)。引用計數(shù)是個簡單的垃圾回收體系,只要其它對象引用某對象(記為對象O),對象O就會存在一個引用計數(shù),當最后引用對象O的對象移除,O對象就會自動析構(gòu)。VTK里使用引用計數(shù)的好處是,可以實現(xiàn)數(shù)據(jù)之間的共享而不用拷貝,從而達到節(jié)省內(nèi)存的目的。
我們可以看一個簡單的例子(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:? }
//
?
程序輸出結(jié)果如圖4.1所示。
圖4.1ReferenceCount運行結(jié)果
在ReferenceCount示例里,我們先用vtkBMPReader讀入一幅BMP圖像test.bmp,在賦值之前我們輸出了reader->GetOutput()的引用計數(shù)值,其值為1(使用方法New()創(chuàng)建對象以后,初始的引用計數(shù)值就等于1);然后我們創(chuàng)建了一個vtkImageData類型的對象image1,并把reader的輸出賦給了image1,這時image1就指向了reader的輸出,也就是說,reader的輸出多了一個引用,這個時候輸出的reader->GetOutput()和image1的引用計數(shù)都為2;接著我們又創(chuàng)建一個類型同樣為vtkImageData的對象image2,同樣也是把reader的輸出賦值給image2,這時,image2也指向reader的輸出,亦即reader的輸出又多了一個引用,所以輸出的reader->GetOutput()和image2的引用計數(shù)值變成了3。image1和image2的數(shù)據(jù)結(jié)構(gòu)可以簡單地描述為圖4.2。
圖4.2image1,image2,reader->GetOutput()及引用計數(shù)之間的結(jié)構(gòu)關系
一旦某個對象的引用計數(shù)等于0時,就表明沒有別的對象再引用它,它的使命也宣告完成,程序就會自動的析構(gòu)這個對象。在ReferenceCount這個示例里,我們看不到引用計數(shù)減少的相關代碼,這是因為我們使用了智能指針vtkSmartPointer。
4.1.2 智能指針
智能指針會自動管理引用計數(shù)的增加與減少,如果檢測到某對象的引用計數(shù)值減少為0,則會自動地釋放該對象的資源,從而達到自動管理內(nèi)存的目的。
在前面的內(nèi)容我們已經(jīng)介紹過,VTK里,要創(chuàng)建一個對象可以用兩種方法,一種是使用vtkObjectBase里的靜態(tài)成員變量New(),用Delete()方法析構(gòu);另一種就是我們示例里使用多次的使用智能指針vtkSmartPointer<T>。
對于第一種方法,用New()創(chuàng)建的對象,程序最后必須要調(diào)用Delete()方法釋放對應的內(nèi)存,而且由于vtkObjectBase及其子類的構(gòu)造函數(shù)都是聲明為受保護的,這意味著它們不能在棧區(qū)(棧區(qū)上的內(nèi)存是由編譯器自動分配與釋放的,堆區(qū)上的內(nèi)存則是由程序員分配和手動釋放的。)上分配內(nèi)存。比如:
vtkBMPReader*reader = vtkBMPReader::New(); //創(chuàng)建vtkBMPReader對象
……
reader->Delete();//程序最后要調(diào)用Delete(),這里并沒有直接析構(gòu)對象,而是使引用計數(shù)值減1。
用New()創(chuàng)建的對象,如果沒有用Delete()方法刪除的話,程序有可能會出現(xiàn)內(nèi)存泄漏,即用戶負責對象內(nèi)存的管理。
如果使用智能指針創(chuàng)建的對象,則無需手動調(diào)用Delete()方法讓引用計數(shù)減少,因為引用計數(shù)的增加與減少都是由智能指針自動完成的。使用智能指針時,首先是要包含智能指針的頭文件:#include "vtkSmartPointer.h"。vtkSmartPointer是一個模板類,所需的模板參數(shù)就是待創(chuàng)建的對象的類名,如:
vtkSmartPointer<vtkImageData>image = vtkSmartPointer< vtkImageData >::New();
注意上面一行代碼等號右邊寫法,不能寫為:
vtkSmartPointer<vtkImageData > image = vtkImageData::New();
也就是不能把對象的原始指針賦給智能指針,上行代碼編譯的時候可以通過,但程序退出時會有內(nèi)存泄漏,就是因為智能指針無法自動釋放該對象的內(nèi)存,如圖4.3所示。
圖4.3 將對象的原始指針賦予智能指針會引起內(nèi)存泄漏
如果沒有給對象分配內(nèi)存,仍然可以使用智能指針,比如:
vtkSmartPointer<vtkBMPReader>reader =vtkSmartPointer<vtkBMPReader>::New();
vtkImageData* imageData=reader->GetOutput();
或者:
vtkSmartPointer< vtkImageData> imageData = reader->GetOutput();
第一種情況,當reader超出其作用域時,數(shù)據(jù)即會被刪除;第二種情況,使用了智能指針,所以數(shù)據(jù)的引用計數(shù)會自動加1,除非reader和imageData都超出它們的作用域,數(shù)據(jù)才會被刪除。
智能指針類型同樣也可以作為函數(shù)的返回值。正確的寫法類似:
vtkSmartPointer<vtkImageData>MyFunction()
{
? vtkSmartPointer<vtkImageData> myObject= vtkSmartPointer<vtkImageData>::New();
? return myObject;
}
調(diào)用時則是:
vtkSmartPointer<vtkImageData>MyImageData = MyFunction();
函數(shù)MyFunction()的返回值是通過拷貝的方式,將數(shù)據(jù)賦予調(diào)用的變量,因此該數(shù)據(jù)的引用計數(shù)保持不變,而且函數(shù)MyFunction里的myObject對象也不會刪除。
下面的函數(shù)形式和函數(shù)調(diào)用是錯誤的,應該引起注意:
vtkImageData* MyFunction()
{
? vtkSmartPointer< vtkImageData >MyObject = vtkSmartPointer< vtkImageData >::New();
? return MyObject;
}
vtkImageData* MyImageData = MyFunction();
在函數(shù)MyFunction()里定義的是智能指針類型的,最后返回時轉(zhuǎn)換成原始指針類型,當函數(shù)調(diào)用結(jié)束時,智能指針的引用計數(shù)會減為0,即函數(shù)MyFunction里的MyObject對象會被刪除掉,也就是說MyFunction()返回的是懸空指針,這時再賦予MyImageData變量就會出錯。
智能指針類型也可以作為類的成員變量,而且會使得類在析構(gòu)時更加容易,不用人為去做任何釋放內(nèi)存的事情,把這些工作都交給智能指針來完成,例如:
classMyClass
{
? vtkSmartPointer<vtkFloatArray>Distances;
};
然后在類的構(gòu)造函數(shù)里進行初始化:
MyClass::MyClass()
{
? Distances = vtkSmartPointer<vtkFloatArray>::New();
}
在類的析構(gòu)函數(shù)里不用調(diào)用Delete()去刪除任何東西。
智能指針有一個讓人困惑的地方:當你創(chuàng)建一個智能指針類型的對象,然后改變它的指向,這時引用計數(shù)就會出錯。例如:
vtkSmartPointer<vtkImageData>imageData = vtkSmartPointer<vtkImageData>::New();
imageData= Reader->GetOutput();
上面兩行代碼里,我們首先創(chuàng)建一個imageData,并給他分配好了內(nèi)存,接著我們又把imageData指向Reader的輸出,而不是一直指向我們創(chuàng)建的那塊內(nèi)存。對于這種情況,我們只要簡單地調(diào)用:
vtkImageData*imageData = Reader->GetOutput();
這里沒有必要使用智能指針,因為我們沒有實際創(chuàng)建任何新的對象。
綜上所述,可以看出引用計數(shù)和智能指針是息息相關的,它們主要都是用于內(nèi)存管理。使用智能指針可以免去很多手動刪除變量的煩惱,所以在本教程里,我們從一開始都使用智能指針來創(chuàng)建VTK對象。如果你想了解更多關于引用計數(shù)和智能指針的內(nèi)容,可以參考C++的經(jīng)典著作《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,主要用于基類向子類的類型轉(zhuǎn)換,稱為向下轉(zhuǎn)型。VTK里同樣提供了類似的方法,也就是vtkObject里定義的SafeDownCast(),它是vtkObject里的靜態(tài)成員函數(shù),意味著它是屬于類的,而不是屬于對象的,即可以用vtkObject::SafeDownCast()直接調(diào)用,比如:
vtkSmartPointer<vtkImageReader>ReaderBase = vtkSmartPointer<vtkImageReader>::New();
vtkBMPReader*bmpReader = vtkBMPReader::SafeDownCast(ReaderBase);
與dynamic_cast類似,SafeDownCast也是運行時才轉(zhuǎn)換的,這種轉(zhuǎn)換只有當bmpReader的類型確實是ReaderBase的派生類時才有效,否則返回空指針。
除了運行時類型識別,vtkObjectBase還提供了用于調(diào)試的狀態(tài)輸出接口Print()。雖然vtkObjectBase里除了Print()還提供PrintSelf()、PrintHeader()、PrintTrailer()等公共接口,但在調(diào)試VTK程序時,如果需要輸出某個對象的狀態(tài)信息時,一般都是調(diào)用Print()函數(shù),如:
bmpReader->Print(std::cout);
4.1.4 關于vtkObject的兩三事
以上的幾小節(jié)都是在討論vtkObjectBase這個VTK始祖類的一些特性,接下來我們看一下vtkObjectBase這個始祖類其中一個“兒子”的本領。vtkObject是大多數(shù)VTK類的父類,是一個抽象基類,大多數(shù)在VTK框架里的類都應該是它的子類或其某個子類的子類。這一小節(jié)我們起名為“關于vtkObject的兩三事”,因為vtkObject剛好做了三件重要的事情。
第一件事是,vtkObject里定義了與程序調(diào)試相關的一些公共接口,包括:
?
DebugOn() / DebugOff()
GetDebug() / SetDebug(unsignedchar)
?
SetGlobalWarningDisplay(int) / GetGlobalWarningDisplay()
GlobalWarningDisplayOn() / GlobalWarningDisplayOff()
?
其中后四個是靜態(tài)成員函數(shù)。我們可以通過示例(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圖像的讀取,所以如果調(diào)用方法SetDebug(1)或者DebugOn()時,則會彈出圖4.4-A所示的窗口;調(diào)用DebugOff()時,彈出的窗口如圖4.4-B所示,如果不想看到vtkOutputWindow窗口,可以調(diào)用GlobalWarningDisplayOff()或者SetGlobalWarningDisplay(0)。
圖4.4vtkOutputWindow窗口
因為VTK不支持1位BMP圖像的讀取,所以調(diào)用reader->GetOutput()時不能得到正確的輸出,最終會導致程序在關閉時,出現(xiàn)內(nèi)存溢出的錯誤,如圖4.5所示。
圖4.5 示例4.1.4_vtkObjectDemo程序退出時內(nèi)存溢出錯誤
注:示例4.1.4_vtkObjectDemo也可以作一下更改,以便讓它支持命令行參數(shù),作為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里輸出需要的命令行參數(shù)。右擊“BMPImageViewer”工程,選擇屬性,然后選擇屬性對話框左邊的Debugging,接著在“Command Arguments”一欄輸出所需的參數(shù),比如..\test.bmp (可以用絕對路徑,如果路徑里含有空格,記得用雙引號把整個路徑括起來)。確定,保存所做的更改。最后在VS2008下F5運行程序。
如果你感覺程序運行時后面老是跟著控制臺窗口,讓你覺得不爽的話,你可以在main()函數(shù)之前加入下面的語句隱藏控制臺窗口,一般建議在發(fā)布程序之前不要隱藏,方便調(diào)試程序時隨時輸出調(diào)試信息。
#pragmacomment(linker,"/subsystem:\"windows\"/entry:\"mainCRTStartup\"")
vtkObject的第二件事是,實現(xiàn)觀察者/命令(Observer/Command)設計模式。本教程不是專門介紹設計模式的,如果你想更深入地了解Observer/Command設計模式,可以翻翻相關的書籍,下面只是就這兩種設計模式蜻蜓點水般的做一概述。
vtkObject定義了與觀察者Observer相關的方法(如,AddObserver()/RemoveObserver()),觀察者模式主要針對兩個對象:Object和Observer。一個Object可以有多個Observer,它定義對象間的一種一對多的依賴關系,當—個Object對象的狀態(tài)發(fā)生改變時,所有依賴于它的Observer對象都得到通知被自動更新。
命令模式屬于對象行為模式,它將—個請求封裝為一個對象,并提供一致性發(fā)送請求的接口,當一個事件發(fā)生時,它不直接把事件傳遞到事件調(diào)用者,而是在命令和調(diào)用者之間增加—個中間者,將這種直接關系切斷,同時兩者之間都隔離。事件調(diào)用者只是和接口打交道,不和具體實現(xiàn)交互。命令模式的實現(xiàn)是由vtkObjectBase的另外一個重要子類vtkCommand及其派生類實現(xiàn)的。
如果你還是覺得抽象的話,我們就看看下面的示例(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:? //第一步,定義回調(diào)函數(shù)。
?10:? //注意回調(diào)函數(shù)的簽名,不能更改。
?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:??? //第二步,設置回調(diào)函數(shù)。
?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運行結(jié)果如圖4.6所示。
圖4.6 示例ObserverCommandDemo運行結(jié)果
從示例ObserverCommandDemo可以看到,VTK里使用事件回調(diào)函數(shù)時,需要分三步走。首先,定義回調(diào)函數(shù)。回調(diào)函數(shù)的簽名只能是以下形式:
void MyCallbackFunc(vtkObject*obj,unsigned long eid, void* clientdata, void *calldata)
其次是創(chuàng)建一個vtkCallbackCommand對象,并調(diào)用vtkCallbackCommand::SetCallback()設置第一步定義的回調(diào)函數(shù)。
最后是將vtkCallbackCommand對象添加到對象的觀察者列表。VTK里,所有的事件都是通過vtkInteractorObserver(vtkRenderWindowInteractor的父類,vtkInteractorObserver派生自vtkObject) 進行監(jiān)聽的,所以,我們把vtkCallbackCommand對象加入到vtkRenderWindowInteractor對象的觀察者列表中,調(diào)用的就是vtkObject提供的公共接口AddObserver(),其原型為:
unsigned longAddObserver (unsigned long event, vtkCommand *, float priority=0.0f)
第一個參數(shù)是要監(jiān)聽的事件,這些事件定義在vtkCommand類里,如:
RenderEvent /ProgressEvent / PickEvent / StartPickEvent / EndPickEvent / ExitEvent /LeftButtonPressEvent / LeftButtonReleaseEvent / MiddleButtonPressEvent /MiddleButtonReleaseEvent / RightButtonPressEvent / RightButtonReleaseEvent /KeyPressEvent / KeyReleaseEvent???? /CharEvent / TimerEvent
示例中我們監(jiān)聽的是鼠標左鍵的單擊事件,即LeftButtonPressEvent。
AddObserver的第二個參數(shù)是vtkCommand類型的指針,即我們創(chuàng)建的已經(jīng)綁定回調(diào)函數(shù)的vtkCallbackCommand對象。第三個參數(shù)是設置命令響應的優(yōu)先權(quán)。
AddObserver函數(shù)的返回值是unsigned long型的,可以用于把觀察者從觀察者列表中刪除RemoveObserver(eventID)。
示例ObserverCommandDemo監(jiān)聽交互過程中的鼠標左鍵單擊事件,如果監(jiān)聽到該事件,就在控制臺中輸出目前為止鼠標的單擊次數(shù),該示例僅僅是為了演示觀察者/命令模式的工作方式,除此之外沒有任何實用價值。除了用以上介紹的回調(diào)函數(shù)形式來完成事件/回調(diào)的工作,同樣也可以用類(派生自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的變換矩陣,從而實現(xiàn)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的使用后續(xù)章節(jié)也會重點介紹,這里暫且不提,我們的重點是Observer/Command的應用。
與回調(diào)函數(shù)的類似,首先我們從vtkCommand派生出類vtkMyCallback,該類主要實現(xiàn)兩個方法,一個是New(),用于為創(chuàng)建的對象申請內(nèi)存;一個是Execute(),這是父類vtkCommand里定義的純虛函數(shù),其原型為:
virtual voidExecute(vtkObject *caller, unsigned long eventId,void *callData) = 0;
這意味著,只要從vtkCommand派生的類,都必須實現(xiàn)這個方法。而這個方法的作用就是一旦監(jiān)聽到所要監(jiān)聽的事件,就會自動地調(diào)用該方法。監(jiān)聽到事件后,要完成什么樣的操作,都是在Execute()方法里實現(xiàn),所以我們把精力都放在這個方法上。第一個參數(shù)是caller,指向調(diào)用觀察者的對象,即調(diào)用AddObserver()方法的那個對象,如本例中的boxWidget。第二個參數(shù)eventId是事件的編號。第三個參數(shù)callData,是傳遞給Execute函數(shù)的數(shù)據(jù),本例我們沒有給該函數(shù)傳遞任何數(shù)據(jù),如果你很想知道這個參數(shù)有值時應該如何使用,可以找找你計算機上的VTK目錄(D:\Toolkits\VTK\VTK-5.10\Examples\Tutorial\Step2\Cxx\Cone2.cxx)。
將Execute()與前面介紹的回調(diào)函數(shù)作一對比:
void MyCallbackFunc(vtkObject*obj,unsigned long eid, void* clientdata, void *calldata)
前兩個參數(shù)都是一樣的,MyCallbackFunc的第四個參數(shù)與Execute()的第三個參數(shù)一樣,第三個參數(shù)是clientdata,這個數(shù)據(jù)是指回調(diào)函數(shù)里需要訪問主程序里的數(shù)據(jù)時,由主程序向回調(diào)函數(shù)傳遞的,可以通過方法vtkCallbackCommand::SetClientData()設置。
接下來我們看看MyCallback::Execute()的實現(xiàn)(23到27行)。首先我們定義一個vtkTransform對象;然后把傳遞過來的數(shù)據(jù)caller用C++操作符reinterpret_cast<>轉(zhuǎn)換成類型vtkBoxWidget*,示例里的這個轉(zhuǎn)換是可以成功的,因為調(diào)用觀察者的對象也是vtkBoxWidget*類型的(73行)。緊接著,我們把從vtkBoxWidget對象獲取到的變換重新應用到vtkBoxWidget里的Prop對象,也就是說Prop對象會跟著vtkBoxWidget對象做同樣的變換,或伸或縮。
第二步(69-70行),實例化一個vtkMyCallback對象。
第三步(72-73行),監(jiān)聽vtkBoxWidget對象的交互事件(InteractionEvent)。也就是說,當用戶與vtkBoxWidget對象交互時,事件InteractionEvent就會被觸發(fā),程序就會自動調(diào)用vtkMyCallback里的Execute()事件。
示例ObserverCommandDemo2運行結(jié)果如圖4.7。
圖4.7 ObserverCommandDemo2運行結(jié)果(按“i”鍵可以關閉或激活Widget)
Observer/Command模式在VTK里的應用是非常廣泛也是非常重要的,可以用回調(diào)函數(shù)或者類的形式來實現(xiàn),以上的內(nèi)容已經(jīng)給出比較詳細的介紹,使用VTK的話,這兩種方式是沒有理由不掌握的。
我們繼續(xù)看vtkObject的第三件事。翻翻vtkObject.h文件,里面有一個受保護的數(shù)據(jù)成員MTime,與這個MTime相關的公共接口有GetMTime() (返回MTime的值)以及Modified()。MTime全稱就是Modification Time,也就是修改時間。vtkObject的第三件事就是我們接下來要學習的內(nèi)容——“可視化管線”。
==========歡迎轉(zhuǎn)載,轉(zhuǎn)載時請保留該聲明信息==========
版權(quán)歸@東靈工作室所有,更多信息請訪問東靈工作室
教程系列導航:http://blog.csdn.net/www_doling_net/article/details/8763686
================================================
總結(jié)
- 上一篇: Adobe illustrator 魔棒
- 下一篇: 类的虚函数和多态性