vtk 自定义交互之 通过鼠标控制camera同时使用键盘精确定量控制actor
场景需求:
vtk中可以使用 vtkInteractorStyleTrackballActor 交互器类来通过鼠标对单个actor的position进行调整,可以用于图像配准微调。另外,使用 vtkInteractorStyleTrackballCamera 类来通过鼠标对相机进行操作,调整观察整个vtk空间的角度。结合两者,可以实现调整观察角度和调整actor来进行某些操作,如动画,微调等。 但两者均通过鼠标来操作,不能同时操作。 虽然可以通过回调函数切换交互器来进行操作,但频繁操作不具有可行性,并且鼠标拖拽不能精确量化控制。
本文实现通过键盘控制actor,同时 通过鼠标控制相机,实现类似于fps游戏的效果。
说明
特点:精确量化控制,同时操作相机和actor
注意事项:
-
键盘交互时会拾取焦点,确保焦点在想要移动的actor上。 -
当actor的pickable状态设置为false时,可屏蔽本操作。 -
如发现本代码有bug,烦请指出以改正。
实现效果
默认通过键盘 “w” “a” “s” “d” “e” “f” 控制actor旋转, 按 左ctrl 键在旋转和平移间切换,然后 “w” “a” “s” “d” “e” “f” 控制actor的平移。
展示见如下视频:
代码
本代码基于vtk8.2测试通过,
#include <ImportLibVTK8_2_0.h>
为导入vtk静态库的代码, 可通过别的方式加载。
ppzInteractorStyleTrackballActor.h
#ifndef PPZ_INTERACTORSTYLETRACKBALLACTOR_H
#define PPZ_INTERACTORSTYLETRACKBALLACTOR_H
#include <vtkInteractionStyleModule.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkInteractorStyle.h>
#include <vtkSmartPointer.h>
#include <vtkAutoInit.h>
class vtkCellPicker;
class InteractorStyleTrackballActor : public vtkInteractorStyleTrackballCamera
{
public:
static InteractorStyleTrackballActor* New();
vtkTypeMacro(InteractorStyleTrackballActor, vtkInteractorStyleTrackballCamera);
void PrintSelf(ostream& os, vtkIndent indent) override;
void OnLeftButtonDown() override;
void Rotate(const double x,const double y, const double z) ;
void Pan(const double x, const double y, const double z);
void OnChar() override;
void OnKeyPress() override;
protected:
InteractorStyleTrackballActor();
~InteractorStyleTrackballActor() override;
void FindPickedActor(int x, int y);
void Prop3DTransform(vtkProp3D* prop3D,
double* boxCenter,
int NumRotation,
double** rotate,
double* scale);
double MotionFactor;
vtkProp3D* InteractionProp;
vtkCellPicker* InteractionPicker;
private:
InteractorStyleTrackballActor(const InteractorStyleTrackballActor&) = delete;
void operator=(const InteractorStyleTrackballActor&) = delete;
bool m_isDoTranslation;
};
#endif
ppzInteractorStyleTrackballActor.cpp
#include "ppzInteractorStyleTrackballActor.h"
#include "vtkCamera.h"
#include "vtkCellPicker.h"
#include "vtkCallbackCommand.h"
#include "vtkMath.h"
#include "vtkMatrix4x4.h"
#include "vtkObjectFactory.h"
#include "vtkProp3D.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkTransform.h"
vtkStandardNewMacro(InteractorStyleTrackballActor);
InteractorStyleTrackballActor::InteractorStyleTrackballActor()
: m_isDoTranslation(false)
{
this->MotionFactor = 10.0;
this->InteractionProp = nullptr;
this->InteractionPicker = vtkCellPicker::New();
this->InteractionPicker->SetTolerance(0.001);
}
InteractorStyleTrackballActor::~InteractorStyleTrackballActor()
{
this->InteractionPicker->Delete();
}
void InteractorStyleTrackballActor::Rotate(const double x, const double y,const double z)
{
if (this->CurrentRenderer == nullptr || this->InteractionProp == nullptr)
{
return;
}
vtkRenderWindowInteractor* rwi = this->Interactor;
vtkCamera* cam = this->CurrentRenderer->GetActiveCamera();
double* obj_center = this->InteractionProp->GetCenter();
double boundRadius = this->InteractionProp->GetLength() * 0.5;
double view_up[3], view_look[3], view_right[3];
cam->OrthogonalizeViewUp();
cam->ComputeViewPlaneNormal();
cam->GetViewUp(view_up);
vtkMath::Normalize(view_up);
cam->GetViewPlaneNormal(view_look);
vtkMath::Cross(view_up, view_look, view_right);
vtkMath::Normalize(view_right);
double outsidept[3];
outsidept[0] = obj_center[0] + view_right[0] * boundRadius;
outsidept[1] = obj_center[1] + view_right[1] * boundRadius;
outsidept[2] = obj_center[2] + view_right[2] * boundRadius;
double disp_obj_center[3];
this->ComputeWorldToDisplay(obj_center[0], obj_center[1], obj_center[2],
disp_obj_center);
this->ComputeWorldToDisplay(outsidept[0], outsidept[1], outsidept[2],
outsidept);
double radius = sqrt(vtkMath::Distance2BetweenPoints(disp_obj_center,
outsidept));
double nxf = (rwi->GetEventPosition()[0] - disp_obj_center[0]) / radius;
double nyf = (rwi->GetEventPosition()[1] - disp_obj_center[1]) / radius;
double oxf = (rwi->GetLastEventPosition()[0] - disp_obj_center[0]) / radius;
double oyf = (rwi->GetLastEventPosition()[1] - disp_obj_center[1]) / radius;
{
double scale[3];
scale[0] = scale[1] = scale[2] = 1.0;
double** rotate = new double* [3];
rotate[0] = new double[4];
rotate[1] = new double[4];
rotate[2] = new double[4];
rotate[0][0] = x;
rotate[0][1] = view_up[0];
rotate[0][2] = view_up[1];
rotate[0][3] = view_up[2];
rotate[1][0] = y;
rotate[1][1] = view_right[0];
rotate[1][2] = view_right[1];
rotate[1][3] = view_right[2];
rotate[2][0] = z;
rotate[2][1] = view_look[0];
rotate[2][2] = view_look[1];
rotate[2][3] = view_look[2];
this->Prop3DTransform(this->InteractionProp,
obj_center,
3,
rotate,
scale);
delete[] rotate[0];
delete[] rotate[1];
delete[] rotate[2];
delete[] rotate;
if (this->AutoAdjustCameraClippingRange)
{
this->CurrentRenderer->ResetCameraClippingRange();
}
rwi->Render();
}
}
void InteractorStyleTrackballActor::Pan(const double x, const double y, const double z)
{
if (this->CurrentRenderer == nullptr || this->InteractionProp == nullptr)
{
return;
}
vtkRenderWindowInteractor* rwi = this->Interactor;
double* obj_center = this->InteractionProp->GetCenter();
double disp_obj_center[3], new_pick_point[4];
double old_pick_point[4], motion_vector[3];
this->ComputeWorldToDisplay(obj_center[0], obj_center[1], obj_center[2],
disp_obj_center);
this->ComputeDisplayToWorld(
disp_obj_center[0]+x,
disp_obj_center[1]+y,
disp_obj_center[2]+z,
new_pick_point);
this->ComputeDisplayToWorld(
disp_obj_center[0],
disp_obj_center[1],
disp_obj_center[2],
old_pick_point);
motion_vector[0] = new_pick_point[0] - old_pick_point[0];
motion_vector[1] = new_pick_point[1] - old_pick_point[1];
motion_vector[2] = new_pick_point[2] - old_pick_point[2];
if (this->InteractionProp->GetUserMatrix() != nullptr)
{
vtkTransform* t = vtkTransform::New();
t->PostMultiply();
t->SetMatrix(this->InteractionProp->GetUserMatrix());
t->Translate(motion_vector[0], motion_vector[1], motion_vector[2]);
this->InteractionProp->GetUserMatrix()->DeepCopy(t->GetMatrix());
t->Delete();
}
else
{
this->InteractionProp->AddPosition(motion_vector[0],
motion_vector[1],
motion_vector[2]);
}
if (this->AutoAdjustCameraClippingRange)
{
this->CurrentRenderer->ResetCameraClippingRange();
}
rwi->Render();
}
void InteractorStyleTrackballActor::OnChar()
{
if (m_isDoTranslation)
{
switch (this->Interactor->GetKeyCode())
{
case 'w':
Pan(0, 1, 0);
break;
case 'a':
Pan(-1, 0, 0);
break;
case 's':
Pan(0, -1, 0);
break;
case 'd':
Pan(1, 0, 0);
break;
case 'q':
Pan(0, 0, 0.01);
break;
case 'e':
Pan(0, 0, -0.01);
break;
}
}
else
{
switch (this->Interactor->GetKeyCode())
{
case 'w':
Rotate(0, -3, 0);
break;
case 'a':
Rotate(-3, 0, 0);
break;
case 's':
Rotate(0, 3, 0);
break;
case 'd':
Rotate(3, 0, 0);
break;
case 'q':
Rotate(0, 0, 3);
break;
case 'e':
Rotate(0, 0, -3);
break;
}
}
}
void InteractorStyleTrackballActor::OnKeyPress()
{
auto keyChar = this->Interactor->GetKeySym();
const char* Control_L = "Control_L";
auto isEqual = strcmp(keyChar, Control_L);
if (isEqual == 0)
{
m_isDoTranslation = !m_isDoTranslation;
if (m_isDoTranslation)
{
std::cout << "translation" << std::endl;
}
else
{
std::cout << "rotation" << std::endl;
}
}
}
void InteractorStyleTrackballActor::PrintSelf(ostream& os, vtkIndent indent)
{
this->Superclass::PrintSelf(os, indent);
}
void InteractorStyleTrackballActor::OnLeftButtonDown()
{
int x = this->Interactor->GetEventPosition()[0];
int y = this->Interactor->GetEventPosition()[1];
this->FindPokedRenderer(x, y);
this->FindPickedActor(x, y);
vtkInteractorStyleTrackballCamera::OnLeftButtonDown();
}
void InteractorStyleTrackballActor::FindPickedActor(int x, int y)
{
this->InteractionPicker->Pick(x, y, 0.0, this->CurrentRenderer);
vtkProp* prop = this->InteractionPicker->GetViewProp();
if (prop != nullptr)
{
this->InteractionProp = vtkProp3D::SafeDownCast(prop);
}
else
{
this->InteractionProp = nullptr;
}
}
void InteractorStyleTrackballActor::Prop3DTransform(vtkProp3D* prop3D,
double* boxCenter,
int numRotation,
double** rotate,
double* scale)
{
vtkMatrix4x4* oldMatrix = vtkMatrix4x4::New();
prop3D->GetMatrix(oldMatrix);
double orig[3];
prop3D->GetOrigin(orig);
vtkTransform* newTransform = vtkTransform::New();
newTransform->PostMultiply();
if (prop3D->GetUserMatrix() != nullptr)
{
newTransform->SetMatrix(prop3D->GetUserMatrix());
}
else
{
newTransform->SetMatrix(oldMatrix);
}
newTransform->Translate(-(boxCenter[0]), -(boxCenter[1]), -(boxCenter[2]));
for (int i = 0; i < numRotation; i++)
{
newTransform->RotateWXYZ(rotate[i][0], rotate[i][1],
rotate[i][2], rotate[i][3]);
}
if ((scale[0] * scale[1] * scale[2]) != 0.0)
{
newTransform->Scale(scale[0], scale[1], scale[2]);
}
newTransform->Translate(boxCenter[0], boxCenter[1], boxCenter[2]);
newTransform->Translate(-(orig[0]), -(orig[1]), -(orig[2]));
newTransform->PreMultiply();
newTransform->Translate(orig[0], orig[1], orig[2]);
if (prop3D->GetUserMatrix() != nullptr)
{
newTransform->GetMatrix(prop3D->GetUserMatrix());
}
else
{
prop3D->SetPosition(newTransform->GetPosition());
prop3D->SetScale(newTransform->GetScale());
prop3D->SetOrientation(newTransform->GetOrientation());
}
oldMatrix->Delete();
newTransform->Delete();
}
测试代码 testQuantitativeMovement.cpp
#include <vtkActor.h>
#include <vtkAxesActor.h>
#include <vtkCamera.h>
#include <vtkSmartPointer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkCylinderSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkCallbackCommand.h>
#include <vtkProperty.h>
#include <vtkActor.h>
#include <vtkNamedColors.h>
#include <vtkSphereSource.h>
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingFreeType)
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);
#include <ImportLibVTK8_2_0.h>
#pragma comment(lib,"vtkRenderingOpenGL2-8.2.lib")
#include "ppzInteractorStyleTrackballActor.h"
int main()
{
vtkSmartPointer<vtkCylinderSource> cylinder =
vtkSmartPointer<vtkCylinderSource>::New();
cylinder->SetHeight(8.0);
cylinder->SetRadius(2.0);
cylinder->SetResolution(10);
vtkSmartPointer<vtkPolyDataMapper> cylinderMapper =
vtkSmartPointer<vtkPolyDataMapper>::New();
cylinderMapper->SetInputConnection(cylinder->GetOutputPort());
vtkSmartPointer<vtkActor> cylinderActor =
vtkSmartPointer<vtkActor>::New();
cylinderActor->SetPickable(false);
cylinderActor->SetMapper(cylinderMapper);
vtkSmartPointer<vtkCylinderSource> cylinder2 =
vtkSmartPointer<vtkCylinderSource>::New();
cylinder2->SetHeight(8.0);
cylinder2->SetRadius(2.0);
cylinder2->SetResolution(10);
vtkSmartPointer<vtkPolyDataMapper> cylinderMapper2 =
vtkSmartPointer<vtkPolyDataMapper>::New();
cylinderMapper2->SetInputConnection(cylinder2->GetOutputPort());
vtkSmartPointer<vtkActor> cylinderActor2 =
vtkSmartPointer<vtkActor>::New();
cylinderMapper2->ScalarVisibilityOff();
cylinderActor2->SetMapper(cylinderMapper2);
cylinderActor2->SetPosition(7,8,9);
cylinderActor2->GetProperty()->SetColor(1.0000, 0.3882, 0.2784);
vtkNew<vtkAxesActor> axesActor;
axesActor->SetTotalLength(10, 10, 10);
vtkSmartPointer<vtkRenderer> renderer =
vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(cylinderActor);
renderer->AddActor(cylinderActor2);
renderer->AddActor(axesActor);
renderer->SetBackground(0.1, 0.2, 0.4);
vtkSmartPointer<vtkRenderWindow> renWin =
vtkSmartPointer<vtkRenderWindow>::New();
renWin->AddRenderer(renderer);
renWin->SetSize(800, 800);
vtkSmartPointer<vtkRenderWindowInteractor> iren =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
InteractorStyleTrackballActor* myStyleActor =
InteractorStyleTrackballActor::New();
iren->SetRenderWindow(renWin);
iren->SetInteractorStyle(myStyleActor);
iren->Initialize();
iren->Start();
return EXIT_SUCCESS;
}
|