交互器樣式(如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里的信號-槽連接。