【VTK】平面的平移和旋转
本文探讨的平面操作主要是平移和绕轴旋转。
在vtkPlane中保存有数据成员
double Normal[3];
double Origin[3];
同时,vtkPlane也提供了原点读写函数,法向量读写函数
/**
* Set/get point through which plane passes. Plane is defined by point
* and normal.
*/
vtkSetVector3Macro(Origin,double);
vtkGetVectorMacro(Origin,double,3);
/**
* Set/get plane normal. Plane is defined by point and normal.
*/
vtkSetVector3Macro(Normal,double);
vtkGetVectorMacro(Normal,double,3);
我们可以用他们来保存每次平面变换后新的原点与法向量。
故,vtkPlane是一个我们可以依赖的类。
在工程中,很多时候平面的形成依赖于其他模型的形状与位置,甚至平面的外在表现也有要求(比如增添网格),所以,不能直接使用vtkPlaneSource, vtkRegularPolygonSource等现成的类。在这类具有特殊需求的case中,我们需要自己画出平面。画出平面并不复杂,只需要向vtkPolyData对象插入不同的空间点,设置不同的cell,然后依次传递给mapper,actor即可,相关的文章:【VTK】vtkPolyData生成网格与平面 。
假想场景:
关于平面的两类操作,这里有两种方案实现。
方案1: 每次计算出新的origin与normal,再更新平面
更新origin
每一次移动,从vtkRenderWindowInteractor获取LastEventPosition以及EventPosition。这两个点的数值是Display坐标系中的坐标,计算出对应的向量,再将其投影到法向量上,投影向量的长度就是移动的距离。原点加上移动向量就是新的原点。
更新法向量
在操作rotate button之后,会产生两个点,LastEventPosition和EventPosition。绕轴旋转,无论是绕Z轴旋转,X轴旋转,甚至Y轴旋转,这些旋转轴都通过同一个点,那就是原点origin。据此,可以构造两个向量,origin => LastEventPosition, origin => EventPosition. 由叉积计算出旋转轴,再通过vtkMath::AngleBetweenVectors
计算出旋转弧度。得到的旋转弧度就是法向量的旋转弧度,再将法向量绕轴旋转rotateVectorByWXYZ即可。
更新平面
vtkTransform提供了各类线性变换的方法: 旋转 RotateWXYZ,平移 Translate,缩放 Scale。这些借口一旦被调用,都会影响vtkTransform里面的变换矩阵vtkMatrix4x4,具体是PreMatrix,还是PostMatrix,取决于开发者的设置。比如trans->PostMultiply();
就是将vtkTransform对象设置成后乘模式。有了这些作为基础,就可以计算新的vtkTransform对象,将其赋予PlaneActor,达到更新平面的目的。
方案2: origin、normal的更新和平面的空间变换分离操作
方案1能正常工作的前提是平面的轴和world坐标系中的轴是一样的。比如,Z轴和world坐标系中的Z轴方向,如果两者的偏差太大,旋转上的效果差异就会显露出来。因为不是严格绕平面的Z轴旋转,而是在绕事件坐标系的Z轴旋转,所以可以发现平面在慢慢发生位置偏移。那么我们能否给方法rotateVectorByWXYZ
传入正确的旋转轴向量,而不是简单的:
double q[4] = { radianDelta, 0, 0, 1 }; //rotate delta radian around Z axis
double r[3] = {0}; // r array store new origin normal
rotateVectorByWXYZ( originNormal.point, q, r );
更新平面
vtkActor提供了和vtkTransform类似的线性变换方法:
//旋转
RotateWXYZ
RotateX
RotateY
RotateZ
//缩放
Scale
//平移
AddPosition
不过,使用vtk提供的RotateZ等旋转方法,有几个新的问题需要处理。
假设在实验中,我们先将平面绕通过自身中心的Z轴向量旋转5度,然后改变相机的位置,将这个平面绕通过自身中心的X轴旋转5度,再回到原来的视角,此时的Z轴向量已经发生了变化。在旋转之后,我们能通过Actor的方法GetMatrix获取新的变换矩阵,但是userTransform却并不会发生变换,这个可以被证明。我在rotate之后获取平面的userTransform,将其赋予button,即设置button的userTransform为平面的userTransform,如果旋转后发生了变换,那么button应该跟着平面走才对。但是我在测试后,发现它并不会。此外,尽管我们可以使用RotateWXYZ和AddPosition完成旋转和平移操作,但是平面的中心点不会因此更新!
我们可以尝试这个例子:
main.cpp
#include <stdio.h>
#include <vtkSmartPointer.h>
#include <vtkSphereSource.h>
#include <vtkActor.h>
#include <vtkPlaneSource.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkLight.h>
#include <vtkCamera.h>
#include <vtkActor2D.h>
using namespace std;
int main()
{
setbuf( stdout, NULL );
vtkSmartPointer<vtkPlaneSource> plane =
vtkSmartPointer<vtkPlaneSource>::New();
vtkSmartPointer<vtkPolyDataMapper> mapper =
vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputConnection( plane->GetOutputPort() );
vtkSmartPointer<vtkActor> actor =
vtkSmartPointer<vtkActor>::New();
actor->SetMapper( mapper );
double *origin = actor->GetOrigin();
// actor->AddPosition( 1, 1, 1 );
printf( "origin: %lf, %lf, %lf\n", origin[0], origin[1], origin[2] );
// origin: 0.000000, 0.000000, 0.000000
vtkSmartPointer<vtkRenderer> renderer =
vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(actor);
renderer->SetBackground( 0, 0, 0 );
vtkSmartPointer<vtkRenderWindow> renderWindow =
vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->AddRenderer( renderer );
vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
renderWindowInteractor->SetRenderWindow( renderWindow );
renderer->ResetCamera();
renderWindow->Render();
renderWindowInteractor->Start();
return 0;
}
相关的CMakeLists.txt可以从 【VTK】C++ 简单绘制 copy,然后更改project name即可。
注意到,无论有没有actor->AddPosition( 1, 1, 1 );
,输出的origin一直都是0.000000, 0.000000, 0.000000
。
我们不能不在乎origin和normal,因为每一次旋转都需要知道中心点(这样才能构成相关的向量),每一次平移都需要知道法向量(我们沿着法向量平移)。
但是actor提供的数据不是world坐标系中的值,而是这个plane独立系统的值。
庆幸的是,我们只需要在平面进行旋转,缩放的时候给接口传递增量即可。
注意到,AddPosition需要移动向量,RotateZ等旋转方法需要的是旋转角度,RotateZ中的Z轴就是平面的Z轴!
那么我们可以计算并保存每次变换后新的origin和normal,但是不传递给actor,我们传递的是增量。
那么之前提到过的让button随着平面的变化更改位置又该如何去实现呢?
之前提到过,我们能通过Actor的方法GetMatrix获取新的变换矩阵,每一次移动或者旋转平面,矩阵都会更新。
我们能得到新的矩阵,利用它来创建新的vtkTransform,将得到的transform对象作为RotateButtonActor->SetUserTransform
的参数,这样就实现了button“跟着走”。