VTK交互Widget
widget包含两个重要的组成部分:Interaction和Representation.
Interaction是一些名叫vtkWidget的类(比如vtkBoxWidget2)。它包含了交互的所有选项和事件处理。
Representation是显示并与之交互的一类对象,以名叫vtkRepresentation.
在窗口中实现自己的小部件,如果有交互,就要写自己的Representation和Widget。可以参考vtk已有的widget。
vtk已经实现的Widget如下图
由上图可知Widget基类是vtkAbstracWidget,它定义小部件/小部件表示的API。vtkAbstractWidget定义了一个API并实现了所有使用交互/表示设计的widget所共有的方法。在这个设计中,术语交互是指小部件中执行事件处理的部分,而表示则对应于用来表示小部件的vtkProp(或子类vtkWidgetRepresentation)。vtkAbstractWidget还实现了一些所有子类共有的方法。
vtkAbstractWidget 提供对 vtkWidgetEventTranslator 的访问。 此类负责将 VTK 事件(在 vtkCommand.h 中定义)转换为小部件事件(在 vtkWidgetEvent.h 中定义)。 可以操作此类,以便将不同的 VTK 事件映射到小部件事件,从而允许修改事件绑定。 vtkAbstractWidget 的每个子类都定义了它响应的事件。
vtkAbstractWidget 派生自 vtkInteractorObserver。vtkInteractorObserver是一个抽象的超类,用于观察由vtkRenderWindowInteractor调用的事件的子类。这些子类通常是像3D部件这样的东西;与场景中的演员交互的对象,或交互地探测场景的信息。
vtkInteractorObserver定义了SetInteractor()方法,并启用和禁用了vtkInteractorObserver对事件的处理。使用EnabledOn()或SetEnabled(1)方法来打开交互器观察器,使用EnabledOff()或SetEnabled(0)方法来关闭交互器。初始值为0。
为了支持对对象的交互式操作,这个类(和子类)调用了StartInteractionEvent、InteractionEvent和EndInteractionEvent等事件。当vtkInteractorObserver进入一个需要快速反应的状态时,这些事件被调用:鼠标运动等。例如,这些事件可以用来设置所需的更新帧率(StartInteractionEvent),操作数据或更新管道(InteractionEvent),并将所需的帧率设置为正常值(EndInteractionEvent)。另外两个事件,EnableEvent和DisableEvent,在交互器观察者被启用或禁用时被调用。
所以窗口小部件Widget类本质就是一个观察者监听来自Interactor的事件
由上图可知
vtkWidgetRepresentation
的基类是
vtkProp
,我们知道
vtkActor2D
和
vtkActor
的基类(间接基类)是
vtkProp
。vtkWidgetRepresentation是抽象类定义了小部件和小部件表示类之间的接口用于定义 API 并部分实现不同类型的小部件的表示。小部件表示(即 vtkWidgetRepresentation 的子类)是一种 vtkProp; 这意味着它们可以像任何其他 vtkActor 一样与嵌入场景中的 vtkRenderer 端相关联。 但是,vtkWidgetRepresentation 还定义了一个 API,使其能够与子类 vtkAbstractWidget 配对,这意味着它可以由小部件驱动,在小部件响应注册事件时用于表示小部件。
此处定义的 API 应被视为实现小部件和小部件表示的指南。 小部件行为很复杂,表示响应已注册小部件事件的方式也很复杂,因此 API 可能因小部件而异,以反映这种复杂性。
创建交互的步骤
虽然每个Widget都提供了不同的功能以及不同的API,但是,Widget的创建以及使用基本都是类似的。创建Widget的一般步骤如下:
- 实例化Widget;
- 指定渲染窗口交互器。Widget可以通过它来监听用户事件。
- 必要时使用观察者/命令模式创建回调函数。与widget交互时,它会调用一些通用的VTK事件(94个事件列表),如StartInteractionEvent、InteractionEvent、EndInteractionEvent。用户通过监听这些事件并作出响应,从而可以更新数据、可视化参数或者应用程序的用户图形界面。
- 创建合适的几何表达实体。并用SetRepresentation()函数把他与Widget关联起来,或者使用Widget默认的几何表达实体。
- 最后,必须激活Widget,使其在渲染场景中显示。默认情况下,按键用于激活Widget,使其可以再场景中可见。 正如之前我们讨论的那样,如果对Widget默认的事件绑定不满意,需要根据自己习惯定义的事件绑定,可以使用VTKWidgetEventTranslator类。同样,也可以使用该类的RemoveTranslation()函数取消已经绑定的事件,具体操作如下:
translator->RemoveTranslation(vtkCommand::LeftButtonPressEvent);translator->RemoveTranslation(vtkCommand::LeftButtonReleaseEvent);
如何使用vtkBoxWidget
设置Widget位置
voidPlaceWidget(double bounds[6]) override;
获取Box的polyData
voidGetPolyData(vtkPolyData* pd);
polydata由6个四边形面和15个点组成。前八个点定义了八个角点顶点;接下来的六个定义了-x、+x、-y、+y、-z、+z面点;最后一点(15个点中的第15个点)定义了六面体的中心(参考上面我标注的图)。当调用InteractionEvent或EndInteractionEvent事件时,这些点值保证是最新的。用户提供vtkPolyData并向其中添加点和单元格。
Box的vtkTransform
virtual voidGetTransform(vtkTransform* t);
virtual voidSetTransform(vtkTransform* t);
SetTransform设置获取表示长方体变换的线性变换矩阵信息。注意:转换与最初调用PlaceWidget的位置有关。此方法修改提供的变换。变换可用于控制vtkProp3D的位置,以及其他变换操作(例如vtkTransformorMPolyData)
获取Box的plane
voidGetPlanes(vtkPlanes* planes);
GetPlanes方法可以获取描述由box小部件定义的隐式函数的平面。用户必须提供类vtkPlanes的实例。需要注意的是:vtkPlanes是vtkImplicitFunction的一个子类,这意味着各种过滤器都可以使用它来执行数据的剪裁、剪切和选择(可以反转平面法线的方向以启用InsideOut标志)。
句柄属性
- HandleProperty是句柄属性(小球就是句柄),可以设置选定和正常时控制柄的属性。
- SelectedHandleProperty是当前被选中的句柄属性。
- FaceProperty是面属性(长方体的面),可以设置选定面和法线时面的特性。
- SelectedFaceProperty是当前被选中的面属性。
- OutlineProperty是轮廓属性(外边框的轮廓),可以设置被选中的和正常时轮廓的属性。
- SelectedOutlineProperty是当前被选中的轮廓属性。
vtkGetObjectMacro(HandleProperty, vtkProperty);vtkGetObjectMacro(SelectedHandleProperty, vtkProperty);vtkGetObjectMacro(FaceProperty, vtkProperty);vtkGetObjectMacro(SelectedFaceProperty, vtkProperty);vtkGetObjectMacro(OutlineProperty, vtkProperty);vtkGetObjectMacro(SelectedOutlineProperty, vtkProperty);
控制句柄(表面上的小球体)的显示
voidHandlesOn();voidHandlesOff();
控制功能变量
TranslationEnabled/ScalingEnabled/RotationEnabled 用来控制小部件的行为,可以启用和禁用平移、旋转和缩放的行为。
vtkSetMacro(TranslationEnabled, vtkTypeBool);vtkGetMacro(TranslationEnabled, vtkTypeBool);vtkBooleanMacro(TranslationEnabled, vtkTypeBool);vtkSetMacro(ScalingEnabled, vtkTypeBool);vtkGetMacro(ScalingEnabled, vtkTypeBool);vtkBooleanMacro(ScalingEnabled, vtkTypeBool);vtkSetMacro(RotationEnabled, vtkTypeBool);vtkGetMacro(RotationEnabled, vtkTypeBool);vtkBooleanMacro(RotationEnabled, vtkTypeBool);
vtkBoxWidget2 相关源码分析
vtkBoxWidget2构造函数
构造函数主要定义了widget的事件,并将VTK事件翻译成Widget事件,从下面的代码可以看出使用
SetCallbackMethod
将VTK消息与实际的操作函数联系起来
SetCallbackMethod
调用了
vtkWidgetEventTranslator::SetTranslation()
方法将VTK事件翻译成Widget事件。这种机制有点类似Qt里面的信号槽连接。
// Define widget events
this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonPressEvent, vtkEvent::NoModifier,0,0, nullptr, vtkWidgetEvent::Select, this, vtkBoxWidget2::SelectAction);
this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonReleaseEvent, vtkEvent::NoModifier,0,0, nullptr, vtkWidgetEvent::EndSelect, this, vtkBoxWidget2::EndSelectAction);
this->CallbackMapper->SetCallbackMethod(vtkCommand::MiddleButtonPressEvent,
vtkWidgetEvent::Translate, this, vtkBoxWidget2::TranslateAction);
this->CallbackMapper->SetCallbackMethod(vtkCommand::MiddleButtonReleaseEvent,
vtkWidgetEvent::EndTranslate, this, vtkBoxWidget2::EndSelectAction);
this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonPressEvent,
vtkEvent::ControlModifier,0,0, nullptr, vtkWidgetEvent::Translate, this,
vtkBoxWidget2::TranslateAction);
事件处理函数
// These methods handle eventsstaticvoidSelectAction(vtkAbstractWidget*);staticvoidEndSelectAction(vtkAbstractWidget*);staticvoidTranslateAction(vtkAbstractWidget*);staticvoidScaleAction(vtkAbstractWidget*);staticvoidMoveAction(vtkAbstractWidget*);staticvoidSelectAction3D(vtkAbstractWidget*);staticvoidEndSelectAction3D(vtkAbstractWidget*);staticvoidMoveAction3D(vtkAbstractWidget*);staticvoidStepAction3D(vtkAbstractWidget*);
vtkBoxRepresentation 相关代码分析
BoxWidget中15个点的顺序如下图所示,便于后面代码分析。
vtkBoxRepresentation的构造函数中主要初始化连接渲染Box的管道。如上图可知Box中有6个面,15条线段,15个点。
vtkBoxRepresentation中相关变量介绍如下
//交互box的状态控制状态定义enum{
Outside =0,
MoveF0,
MoveF1,
MoveF2,
MoveF3,
MoveF4,
MoveF5,
Translating,
Rotating,
Scaling
};// 构建6个面变量
vtkActor* HexActor;
vtkPolyDataMapper* HexMapper;
vtkPolyData* HexPolyData;
vtkPoints* Points;// 8 corners; 6 faces; 1 centerdouble N[6][3];// 6个面的方向。存储顺序是(-x,+x,-y,+y,-z,+z)//被选中的六面体其中的一个面的相关变量
vtkActor* HexFace;
vtkPolyDataMapper* HexFaceMapper;
vtkPolyData* HexFacePolyData;// Handle 15个点的相关变量
vtkActor** Handle;
vtkPolyDataMapper** HandleMapper;
vtkSphereSource** HandleGeometry;// 单元拾取相关,也是交互重要环节之一。
vtkCellPicker* HandlePicker;
vtkCellPicker* HexPicker;
vtkActor* CurrentHandle;int CurrentHexFace;
vtkCellPicker* LastPicker;
vtkBoxRepresentation中相关API
//获取包围盒double*GetBounds()VTK_SIZEHINT(6) override;//窗口交互voidWidgetInteraction(double e[2]) override;//复杂的交互voidComplexInteraction(vtkRenderWindowInteractor* iren, vtkAbstractWidget* widget,unsignedlong event,void* calldata) override;//计算交互状态intComputeComplexInteractionState(vtkRenderWindowInteractor* iren, vtkAbstractWidget* widget,unsignedlong event,void* calldata,int modify =0) override;//结束交互状态voidEndComplexInteraction(vtkRenderWindowInteractor* iren, vtkAbstractWidget* widget,unsignedlong event,void* calldata) override;// 帮助函数,也就是具体实现box平移,旋转,缩放的具体函数实现
virtual voidTranslate(constdouble* p1,constdouble* p2);
virtual voidScale(constdouble* p1,constdouble* p2,int X,int Y);
virtual voidRotate(int X,int Y,constdouble* p1,constdouble* p2,constdouble* vpn);voidMovePlusXFace(constdouble* p1,constdouble* p2);voidMoveMinusXFace(constdouble* p1,constdouble* p2);voidMovePlusYFace(constdouble* p1,constdouble* p2);voidMoveMinusYFace(constdouble* p1,constdouble* p2);voidMovePlusZFace(constdouble* p1,constdouble* p2);voidMoveMinusZFace(constdouble* p1,constdouble* p2);voidUpdatePose(constdouble* p1,constdouble* d1,constdouble* p2,constdouble* d2);voidMoveFace(constdouble* p1,constdouble* p2,constdouble* dir,double* x1,double* x2,double* x3,double* x4,double* x5);
Rotate 实现分析
void vtkBoxRepresentation::Rotate(int X,int Y,constdouble* p1,constdouble* p2,constdouble* vpn){double* pts = static_cast<vtkDoubleArray*>(this->Points->GetData())->GetPointer(0);double* center = static_cast<vtkDoubleArray*>(this->Points->GetData())->GetPointer(3*14);double v[3];// vector of motiondouble axis[3];// axis of rotationdouble theta;// rotation angleint i;
v[0]= p2[0]- p1[0];
v[1]= p2[1]- p1[1];
v[2]= p2[2]- p1[2];// Create axis of rotation and angle of rotation
vtkMath::Cross(vpn, v, axis);if(vtkMath::Normalize(axis)==0.0){return;}//根据移动的L2距离和屏幕的尺寸L2比例来确定旋转的角度constint* size = this->Renderer->GetSize();double l2 =(X - this->LastEventPosition[0])*(X - this->LastEventPosition[0])+(Y - this->LastEventPosition[1])*(Y - this->LastEventPosition[1]);
theta =360.0*sqrt(l2 /(size[0]* size[0]+ size[1]* size[1]));// 旋转变换,VTK Transform的标准操作
this->Transform->Identity();
this->Transform->Translate(center[0], center[1], center[2]);
this->Transform->RotateWXYZ(theta, axis);
this->Transform->Translate(-center[0],-center[1],-center[2]);// 设置拐角点// 首先把Points中的点进行transform//得到新的点,并跟新
vtkPoints* newPts = vtkPoints::New(VTK_DOUBLE);
this->Transform->TransformPoints(this->Points, newPts);for(i =0; i <8; i++, pts +=3){
this->Points->SetPoint(i, newPts->GetPoint(i));}
newPts->Delete();//根据这8个点去计算其他要设置的点,并更新
this->PositionHandles();}
vtkBoxWidget 例子
- BoxWidget2.cxx
#include<vtkActor.h>#include<vtkBoxRepresentation.h>#include<vtkBoxWidget2.h>#include<vtkCommand.h>#include<vtkConeSource.h>#include<vtkInteractorStyleTrackballCamera.h>#include<vtkNamedColors.h>#include<vtkNew.h>#include<vtkPolyDataMapper.h>#include<vtkProperty.h>#include<vtkRenderWindow.h>#include<vtkRenderWindowInteractor.h>#include<vtkRenderer.h>#include<vtkTransform.h>#include<vtkOrientationMarkerWidget.h>#include<vtkAxesActor.h>#include<vtkPlanes.h>
namespace {
class vtkBoxCallback : public vtkCommand
{
public:static vtkBoxCallback*New(){return new vtkBoxCallback;}
vtkSmartPointer<vtkActor> m_actor;voidSetActor(vtkSmartPointer<vtkActor> actor){
m_actor = actor;}
virtual voidExecute(vtkObject* caller,unsignedlong,void*){
vtkSmartPointer<vtkBoxWidget2> boxWidget =
dynamic_cast<vtkBoxWidget2*>(caller);
vtkNew<vtkTransform> t;
dynamic_cast<vtkBoxRepresentation*>(boxWidget->GetRepresentation())->GetTransform(t);
this->m_actor->SetUserTransform(t);}vtkBoxCallback(){}};}// namespaceintmain(intvtkNotUsed(argc),char*vtkNotUsed(argv)[]){
vtkNew<vtkNamedColors> colors;// 显示坐标系的vtk组件
vtkSmartPointer<vtkAxesActor> axes_actor = vtkSmartPointer<vtkAxesActor>::New();
axes_actor->SetPosition(0,0,0);
axes_actor->SetTotalLength(2,2,2);
axes_actor->SetShaftType(0);
axes_actor->SetCylinderRadius(0.02);
vtkNew<vtkConeSource> coneSource;
coneSource->SetHeight(1.5);
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputConnection(coneSource->GetOutputPort());
vtkNew<vtkActor> actor;
actor->SetMapper(mapper);
actor->GetProperty()->SetColor(colors->GetColor3d("BurlyWood").GetData());
vtkNew<vtkRenderer> renderer;
renderer->AddActor(actor);//renderer->SetBackground(colors->GetColor3d("Blue").GetData());
renderer->SetBackground(colors->GetColor3d("Black").GetData());
renderer->ResetCamera();// Reposition camera so the whole scene is visible
vtkNew<vtkRenderWindow> renderWindow;
renderWindow->AddRenderer(renderer);
renderWindow->SetWindowName("BoxWidget2");
vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
renderWindowInteractor->SetRenderWindow(renderWindow);// Use the "trackball camera" interactor style, rather than the default// "joystick camera"
vtkNew<vtkInteractorStyleTrackballCamera> style;
renderWindowInteractor->SetInteractorStyle(style);
vtkNew<vtkBoxWidget2> boxWidget;
boxWidget->SetInteractor(renderWindowInteractor);
boxWidget->GetRepresentation()->SetPlaceFactor(1);// Default is 0.5
boxWidget->GetRepresentation()->PlaceWidget(actor->GetBounds());
vtkNew<vtkPlanes> planes;
vtkBoxRepresentation::SafeDownCast(boxWidget->GetRepresentation())->GetPlanes(planes);// Set up a callback for the interactor to call so we can manipulate the actor
vtkNew<vtkBoxCallback> boxCallback;
boxCallback->SetActor(actor);
boxWidget->AddObserver(vtkCommand::InteractionEvent, boxCallback);
boxWidget->On();// 控制坐标系,使之随视角共同变化
vtkSmartPointer<vtkOrientationMarkerWidget> widget = vtkSmartPointer<vtkOrientationMarkerWidget>::New();
widget->SetOrientationMarker(axes_actor);
widget->SetInteractor(renderWindowInteractor);
widget->On();
renderWindow->Render();
renderWindowInteractor->Start();return EXIT_SUCCESS;}
- CMakeLists.txt
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)project(BoxWidget2)set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../../../bin)set(VTK_DIR S:/VTK-build)find_package(ITK REQUIRED)IF(ITK_FOUND)INCLUDE(${ITK_USE_FILE})ELSE(ITK_FOUND)MESSAGE(FATAL_ERROR
"ITK not found. Please set ITK_DIR.")ENDIF(ITK_FOUND)#message("ITK_USE_FILE BoxWidget2: ${ITK_USE_FILE}")find_package(VTK REQUIRED)if(NOT VTK_FOUND)message("Skipping BoxWidget2: ${VTK_NOT_FOUND_MESSAGE}")return()endif()message(STATUS "VTK_VERSION: ${VTK_VERSION}")if(VTK_VERSION VERSION_LESS "8.90.0")#oldsysteminclude(${VTK_USE_FILE})add_executable(BoxWidget2 MACOSX_BUNDLE BoxWidget2.cxx )target_link_libraries(BoxWidget2 PRIVATE ${VTK_LIBRARIES})else()#Prevent a "command line is too long"failure in Windows.set(CMAKE_NINJA_FORCE_RESPONSE_FILE "ON" CACHE BOOL "Force Ninja to use response files.")add_executable(BoxWidget2 MACOSX_BUNDLE BoxWidget2.cxx )target_link_libraries(BoxWidget2 PRIVATE ${VTK_LIBRARIES} ${ITK_LIBRARIES})#vtk_module_autoinit is neededvtk_module_autoinit(
TARGETS BoxWidget2
MODULES ${VTK_LIBRARIES})endif()
版权归原作者 @左左@右右 所有, 如有侵权,请联系我们删除。