您现在的位置是:首页 >学无止境 >VTK交互-vtkBoxWidget2网站首页学无止境

VTK交互-vtkBoxWidget2

@左左@右右 2024-06-14 17:19:17
简介VTK交互-vtkBoxWidget2

VTK交互Widget

widget包含两个重要的组成部分:Interaction和Representation.
Interaction是一些名叫vtk*Widget的类(比如vtkBoxWidget2)。它包含了交互的所有选项和事件处理。
Representation是显示并与之交互的一类对象,以名叫vtk*Representation.

在窗口中实现自己的小部件,如果有交互,就要写自己的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的基类是vtkWidgetRepresentation是抽象类定义了小部件和小部件表示类之间的接口

用于定义 API 并部分实现不同类型的小部件的表示。小部件表示(即 vtkWidgetRepresentation 的子类)是一种 vtkProp这意味着它们可以像任何其他 vtkActor 一样与嵌入场景中的 vtkRenderer 端相关联。 但是,vtkWidgetRepresentation 还定义了一个 API,使其能够与子类 vtkAbstractWidget 配对,这意味着它可以由小部件驱动,在小部件响应注册事件时用于表示小部件。

此处定义的 API 应被视为实现小部件和小部件表示的指南。 小部件行为很复杂,表示响应已注册小部件事件的方式也很复杂,因此 API 可能因小部件而异,以反映这种复杂性。

创建交互的步骤

虽然每个Widget都提供了不同的功能以及不同的API,但是,Widget的创建以及使用基本都是类似的。创建Widget的一般步骤如下:

  1. 实例化Widget;
  2. 指定渲染窗口交互器。Widget可以通过它来监听用户事件。
  3. 必要时使用观察者/命令模式创建回调函数。与widget交互时,它会调用一些通用的VTK事件(94个事件列表),如StartInteractionEvent、InteractionEvent、EndInteractionEvent。用户通过监听这些事件并作出响应,从而可以更新数据、可视化参数或者应用程序的用户图形界面。
  4. 创建合适的几何表达实体。并用SetRepresentation()函数把他与Widget关联起来,或者使用Widget默认的几何表达实体。
  5. 最后,必须激活Widget,使其在渲染场景中显示。默认情况下,按键用于激活Widget,使其可以再场景中可见。
    正如之前我们讨论的那样,如果对Widget默认的事件绑定不满意,需要根据自己习惯定义的事件绑定,可以使用VTKWidgetEventTranslator类。同样,也可以使用该类的RemoveTranslation()函数取消已经绑定的事件,具体操作如下:
    translator->RemoveTranslation(vtkCommand::LeftButtonPressEvent);
    translator->RemoveTranslation(vtkCommand::LeftButtonReleaseEvent);
    

如何使用vtkBoxWidget

设置Widget位置

 void PlaceWidget(double bounds[6]) override;

获取Box的polyData

void GetPolyData(vtkPolyData* pd);

polydata由6个四边形面和15个点组成。前八个点定义了八个角点顶点;接下来的六个定义了-x、+x、-y、+y、-z、+z面点;最后一点(15个点中的第15个点)定义了六面体的中心(参考上面我标注的图)。当调用InteractionEvent或EndInteractionEvent事件时,这些点值保证是最新的。用户提供vtkPolyData并向其中添加点和单元格。

Box的vtkTransform

virtual void GetTransform(vtkTransform* t);  
virtual void SetTransform(vtkTransform* t);

SetTransform设置获取表示长方体变换的线性变换矩阵信息。注意:转换与最初调用PlaceWidget的位置有关。此方法修改提供的变换。变换可用于控制vtkProp3D的位置,以及其他变换操作(例如vtkTransformorMPolyData)

获取Box的plane

void GetPlanes(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);

控制句柄(表面上的小球体)的显示

void HandlesOn();
void HandlesOff();

控制功能变量

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 events
  static void SelectAction(vtkAbstractWidget*);
  static void EndSelectAction(vtkAbstractWidget*);
  static void TranslateAction(vtkAbstractWidget*);
  static void ScaleAction(vtkAbstractWidget*);
  static void MoveAction(vtkAbstractWidget*);
  static void SelectAction3D(vtkAbstractWidget*);
  static void EndSelectAction3D(vtkAbstractWidget*);
  static void MoveAction3D(vtkAbstractWidget*);
  static void StepAction3D(vtkAbstractWidget*);

vtkBoxRepresentation 相关代码分析

BoxWidget中15个点的顺序如下图所示,便于后面代码分析。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XJVk2Njn-1683711832364)(https://note.youdao.com/yws/res/32343/WEBRESOURCE75e8d6e859af96038582a32aa177995a)]
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 center
double 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;

//窗口交互
void WidgetInteraction(double e[2]) override;
//复杂的交互
void ComplexInteraction(vtkRenderWindowInteractor* iren, vtkAbstractWidget* widget,
unsigned long event, void* calldata) override;
//计算交互状态
int ComputeComplexInteractionState(vtkRenderWindowInteractor* iren, vtkAbstractWidget* widget,
unsigned long event, void* calldata, int modify = 0) override;
//结束交互状态
void EndComplexInteraction(vtkRenderWindowInteractor* iren, vtkAbstractWidget* widget,
unsigned long event, void* calldata) override;



// 帮助函数,也就是具体实现box平移,旋转,缩放的具体函数实现
virtual void Translate(const double* p1, const double* p2);
virtual void Scale(const double* p1, const double* p2, int X, int Y);
virtual void Rotate(int X, int Y, const double* p1, const double* p2, const double* vpn);
void MovePlusXFace(const double* p1, const double* p2);
void MoveMinusXFace(const double* p1, const double* p2);
void MovePlusYFace(const double* p1, const double* p2);
void MoveMinusYFace(const double* p1, const double* p2);
void MovePlusZFace(const double* p1, const double* p2);
void MoveMinusZFace(const double* p1, const double* p2);
void UpdatePose(const double* p1, const double* d1, const double* p2, const double* d2);

void MoveFace(const double* p1, const double* p2, const double* dir, double* x1, double* x2,
    double* x3, double* x4, double* x5);

Rotate 实现分析

void vtkBoxRepresentation::Rotate(
  int X, int Y, const double* p1, const double* p2, const double* 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 motion
  double axis[3]; // axis of rotation
  double theta;   // rotation angle
  int 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比例来确定旋转的角度
  const int* 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;

  void SetActor(vtkSmartPointer<vtkActor> actor)
  {
    m_actor = actor;
  }

  virtual void Execute(vtkObject* caller, unsigned long, void*)
  {
    vtkSmartPointer<vtkBoxWidget2> boxWidget =
        dynamic_cast<vtkBoxWidget2*>(caller);

    vtkNew<vtkTransform> t;

    dynamic_cast<vtkBoxRepresentation*>(boxWidget->GetRepresentation())
        ->GetTransform(t);
    this->m_actor->SetUserTransform(t);
  }

  vtkBoxCallback()
  {
  }
};
} // namespace

int main(int vtkNotUsed(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")
  # old system
  include(${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 needed
  vtk_module_autoinit(
    TARGETS BoxWidget2
    MODULES ${VTK_LIBRARIES}
    )
endif()


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BEuKXMgz-1683711832364)(https://note.youdao.com/yws/res/32503/WEBRESOURCE32c330512a8e819ec3ebb13e00ab260a)]

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。