【VTK】平面的平移和旋转

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/theArcticOcean/article/details/83714321

本文探讨的平面操作主要是平移和绕轴旋转。
在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“跟着走”。

获取线性变换后的origin和normal

求解origin

    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<vtkTransform> transform =
            vtkSmartPointer<vtkTransform>::New();
    transform->SetMatrix( actor->GetMatrix() );
    double *newPos = transform->TransformDoublePoint( origin[0], origin[1], origin[2] );

    printf( "newPos: %lf, %lf, %lf\n", newPos[0], newPos[1], newPos[2] );
    // newPos: 1.000000, 1.000000, 1.000000

求解normal

    double oldNormal[3] = { 0, -1, 0 };
    printf( "oldNormal: %lf, %lf, %lf\n", oldNormal[0], oldNormal[1], oldNormal[2] );
    // oldNormal: 0.000000, -1.000000, 0.000000

    actor->RotateZ( 90 );

    vtkSmartPointer<vtkTransform> transform =
            vtkSmartPointer<vtkTransform>::New();
    transform->SetMatrix( actor->GetMatrix() );
    double *newNormal = transform->TransformDoubleVector( oldNormal );
    printf( "newNormal: %lf, %lf, %lf\n", newNormal[0], newNormal[1], newNormal[2] );
    // newNormal: 1.000000, -0.000000, 0.000000

关于获取线性变换后的origin和normal 相关博文:
【VTK】vtk actor空间变换后的点映射与向量映射

阅读更多
换一批

没有更多推荐了,返回首页