VTK可交互三维坐标轴

因为实习工作需要制作一个如下图所示的可交互的三维坐标轴,制作这个坐标轴,首先需要创建一些三维图形,接着需要熟悉交互模块和鼠标进行交互,最后将它们封装成一个vtkWidget

VTK可交互三维坐标轴_第1张图片

VTK中一些基础类介绍

下面是VTK中经常会使用到的类的描述。

vtkProp

渲染场景中数据的可视表达(Visible Depictions)是由vtkProp的子类负责。三维空间中渲染对象最常用的vtkProp子类是vtkActor和vtkVolume,其中vtkActor用于表示场景中的几何数据(Geometry Data),vtkVolume表示场景中的体数据(Volumetric Data)。vtkActor2D常用来表示二维空间中的数据。vtkProp的子类负责确定场景中对象的位置、大小和方向信息。控制Prop位置信息的参数依赖于对象是否在渲染场景中,比如一个三维物体或者二维注释,它们的位置信息控制方式是有所区别的。三维的Prop如vtkActor和vtkVolume(vtkActor和vtkVolume都是vtkProp3D的子类,而vtkProp3D继承自vtkProp),既可以直接控制对象的位置、方向和放缩信息,也可以通过一个4×4的变换矩阵来实现。而对于二维注释功能的Props如vtkScalarBarActor,其大小和位置有许多的定义方式,其中包括指定相对于视口的位置、宽度和高度。Prop除了提供对象的位置信息控制之外,Prop内部通常还有两个对象,一个是Mapper对象,负责存放数据和渲染信息,另一个是Property(属性)对象,负责控制颜色、不透明度等参数。

VTK中定义了大量的功能细化的Prop(超过50个),如vtkImageActor(负责图像显示)和vtkPieChartActor(用于创建数组数据的饼图可视表示)。其中的有些Props内部直接包括了控制显示的参数和待渲染数据的索引,因此并不需要额外的Property和Mapper对象。vtkActor的子类vtkFollower可以自动的更新方向信息保持自身始终面向一个特定的相机,这样无论如何旋转渲染场景中的对象,vtkFellower对象都是可见的,适用于三维场景中的广告板(Billboards)或者是文本。vtkActor的子类vtkLodActor可以自动改变自身的几何表示来实现所要求的交互帧率,vtkProp3D的子类vtkLODProp3D则是通过从许多Mapper中进行选择来实现不同的交互性(可以是Volumetric Mapper和GeometricMapper的集合)。vtkAssembly建立了Actor的等级结构以便在整个结构平移、旋转或者放缩时能够更合理的控制变换。

vtkAbstractMapper

许多Props如vtkActor和vtkVolume利用vtkAbstractMapper的子类来保存输入数据的引用以及提供真正的渲染功能。vtkPolyDataMapper是渲染多边形几何数据主要的Mapper类。而对于体数据,VTK提供了多种渲染技术。例如,vtkFixedPointVolumeRayCastMapper用来渲染vtkImageData类型的数据,vtkProjectedTetrahedraMapper则是用来渲染vtkUnstructuredGrid类型的数据。

vtkProperty和vtkVolumeProperty

某些Props采用单独的属性对象来存储控制数据外观显示的参数,这样不同的对象可以轻松的实现外观参数的共享。vtkActor利用vtkProperty对象存储外观(属性)参数,如颜色、不透明度、材质的环境光(Ambient)系数、散射光(Diffuse)系数和反射光(Specular)系数等。而vtkVolume则是采用vtkVolumeProperty对象来获取体对象的绘制参数,如将标量值映射为颜色和不透明度的传输函数(Transfer Function)【译者:也有译成“传递函数”】。另外,一些vtkMapper提供相应的函数设置裁剪面以便显示对象的内部结构。

vtkCamera

vtkCamera存储了场景中的摄像机参数,换言之,如何来“看”渲染场景里的对象,主要参数是摄像机的位置、焦点、和场景中的上方向向量。其他参数可以控制视图变换,如平行投影或者透视投影,图像的尺度或者视角,以及视景体的远近裁剪平面等。

vtkRenderer

组成场景的对象包括Prop,Camara和Light都被集中在一个vtkRenderer对象中。vtkRenderer负责管理场景的渲染过程。一个vtkRenderWindow中可以有多个vtkRenderer对象,而这些vtkRenderer可以渲染在窗口中不同的矩形区域中(视口),甚至可以是覆盖的区域。

vtkRendererWindow

vtkRendererWindow将操作系统与VTK渲染引擎连接到一起。不同平台下的vtkRendererWindow子类负责本地计算机系统中窗口创建和渲染过程的管理。当使用VTK开发应用程序时,只需要使用平台无关的vtkRendererWindow类,程序运行时,系统会自动替换为平台相关的vtkRendererWindow子类。vtkRendererWindow中包含了vtkRenderer的集合,以及控制渲染的参数,如立体显示(Stereo)、反走样、运动模糊(Motion Blur)和焦点深度(FocalDepth)

vtkRenderWindowInteractor

vtkRenderWindowInteractor负责监听鼠标、键盘和时钟消息,并通过VTK中的Command/Observer设计模式进行相应的处理。vtkInteractorStyle监听这些消息并进行处理以完成旋转、拉伸和放缩等运动控制。vtkRenderWindowInteractor自动建立一个默认的3D场景交互器样式(InteractorStyle),当然你也可以选择一个二维图像浏览的交互器样式,或者是创建自定义的交互器样式。

vtkTransform

场景中的许多对象,如Prop、光源Light、照相机Camera等都需要在场景中合理的放置,它们通过vtkTransform参数可以方便的控制对象的位置和方向。vtkTransform能够描述三维空间中的线性坐标变换,其内部表示为一个4×4的齐次变换矩阵。vtkTransform对象初始化为一个单位矩阵,你可以通过管线连接的方式将变换进行组合来完成复杂的变换。管线方式能够确保当其中任一个变换被修改时,其后续的变换都会相应的进行更新
VTK可交互三维坐标轴_第2张图片

更多基础类的内容可以参考

VTK图像图像开发进阶 第二章的内容

第03章-VTK系统概述(1)_DolingStudio的博客-CSDN博客

VTKUsersGuide 第三章的内容

可视化管线

第03章-VTK系统概述(2)_DolingStudio的博客-CSDN博客

VTK可交互三维坐标轴_第3张图片

使用预定义的Source对象创建三维图形的数据(或通过Reader对象从文件中读取数据),一个或多个数据对象传入Filter后,经过处理产生新的数据对象,使用Mapper接收数据,并将其装换为可被渲染引擎绘制的可视化表达,再将Mapper绑定到Actor对象上。

一个例子
#include "vtkSmartPointer.h"
#include "vtkSphereSource.h"
#include "vtkPolyDataMapper.h"
#include "vtkActor.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"

#include "vtkAutoInit.h"
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);
VTK_MODULE_INIT(vtkRenderingFreeType);
VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2);

int main()
{
    // 实例化一个球体的数据源对象
	vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
    // 实例化一个Mapper
	vtkSmartPointer<vtkPolyDataMapper> sphereMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    // 实例化一个Actor
	vtkSmartPointer<vtkActor> sphereActor = vtkSmartPointer<vtkActor>::New();

	sphereSource->SetRadius(0.2); // 设置球体的Source图像源半径
	sphereMapper->SetInputConnection(sphereSource->GetOutputPort());	// 关联Source的输出与Mapper的输入口
	sphereActor->SetMapper(sphereMapper);	// 将Mapper绑定到Actor上
	// 实例化一个Renderer对象
	vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
    // 实例化一个窗口对象
	vtkSmartPointer<vtkRenderWindow> renWin = vtkSmartPointer<vtkRenderWindow>::New();
    // 实例化一个窗口的交互器对象
	vtkSmartPointer<vtkRenderWindowInteractor> iren = vtkSmartPointer<vtkRenderWindowInteractor>::New();

	renderer->AddActor(sphereActor);	// 一个Renderer包含多个Actor
	renWin->AddRenderer(renderer);	// 一个RenderWindow包含多个Renderer,可以为不同Renderer设置视口
	iren->SetRenderWindow(renWin);		// 关联窗口和交互器

	renWin->Render();				// RenderWindow 开始渲染
	iren->Initialize();			// vtkRenderWindowInteractor 初始化事件监听
	iren->Start();			// vtkRenderWindowInteractor开启事件监听循环
	return 0;
}

创建坐标轴三维图形

创建XYZ坐标轴

本文封装了一个Arrow类通过指定起点和终点来创建一个箭头,并可以设置箭头上椎体的数量,设置颜色,线宽参数

#include "vtkSmartPointer.h"
#include "vtkActor.h"
#include "vtkLineSource.h"
#include "vtkConeSource.h"

namespace myvtk {

    class Arrow
    {
    public:
        Arrow() :m_line(nullptr), m_cone1(nullptr), m_cone2(nullptr) {}
        Arrow(double origin[3], double target[3], int coneCount = 1, double color[3] = nullptr, float lineWidth = AXISWIDTH) { CreateArrow(origin, target, coneCount, color, lineWidth); }
        /*函数名: CreateArrow
        * @param origin[3]  起点坐标
        * @param target[3]  终点坐标
        * @param coneCount  椎体数量
        * @param color      颜色
        * @param lineWidth  线宽
        */
        void CreateArrow(double origin[3], double target[3], int coneCount = 1, double color[3] = nullptr, float lineWidth = AXISWIDTH)
        {
            {
                // 直线部分
                m_lineSource = vtkSmartPointer<vtkLineSource>::New();
                vtkNew<vtkPolyDataMapper> lineMapper;
                m_line = vtkSmartPointer<vtkActor>::New();

                lineMapper->SetInputConnection(m_lineSource->GetOutputPort());
                m_line->SetMapper(lineMapper);

                m_lineSource->SetPoint1(origin);
                m_lineSource->SetPoint2(target);

                m_line->GetProperty()->SetLineWidth(lineWidth);
                if (color != nullptr) m_line->GetProperty()->SetColor(color);
                // 箭头部分
                double directVec[3] = { 0 };

                vtkMath::Subtract(target, origin, directVec);
                // 箭头1
                m_coneCount = coneCount;
                if (coneCount >= 1)
                {
                    m_coneSource1 = vtkSmartPointer<vtkConeSource>::New();
                    vtkNew<vtkPolyDataMapper> coneMapper1;
                    m_cone1 = vtkSmartPointer<vtkActor>::New();

                    m_coneSource1->SetCenter(target);
                    m_coneSource1->SetHeight(0.5);
                    m_coneSource1->SetRadius(0.08);
                    m_coneSource1->SetResolution(10);
                    m_coneSource1->SetDirection(directVec);

                    coneMapper1->SetInputConnection(m_coneSource1->GetOutputPort());

                    m_cone1->SetMapper(coneMapper1);
                    if (color != nullptr) m_cone1->GetProperty()->SetColor(color);
                }

                // 箭头2
                if (coneCount >= 2)
                {
                    m_coneSource2 = vtkSmartPointer<vtkConeSource>::New();
                    vtkNew<vtkPolyDataMapper> coneMapper2;
                    m_cone2 = vtkSmartPointer<vtkActor>::New();

                    vtkMath::Subtract(origin, target, directVec);

                    m_coneSource2->SetCenter(origin);
                    m_coneSource2->SetHeight(0.5);
                    m_coneSource2->SetRadius(0.08);
                    m_coneSource2->SetResolution(10);
                    m_coneSource2->SetDirection(directVec);

                    coneMapper2->SetInputConnection(m_coneSource2->GetOutputPort());

                    m_cone2->SetMapper(coneMapper2);
                    if (color != nullptr) m_cone2->GetProperty()->SetColor(color);
                }

            }
        }

        int m_coneCount = 0;
        vtkSmartPointer<vtkActor> m_line = nullptr;
        vtkSmartPointer<vtkActor> m_cone1 = nullptr;
        vtkSmartPointer<vtkActor> m_cone2 = nullptr;

        vtkSmartPointer<vtkLineSource> m_lineSource = nullptr;
        vtkSmartPointer<vtkConeSource> m_coneSource1 = nullptr;
        vtkSmartPointer<vtkConeSource> m_coneSource2 = nullptr;
    };
}
创建弯曲箭头

本文封装了一个CurveArrow类通过指定弯曲箭头需要经过的一系列的点来绘制曲线,并可以设置箭头上椎体的数量,设置颜色,线宽参数

其中绘制曲线涉及到了 vtkParametricSplinevtkParametricFunctionSource

#include "vtkSmartPointer.h"
#include "vtkPoints.h"
#include "vtkParametricFunctionSource.h"
#include "vtkParametricSpline.h"
#include "vtkPolyDataMapper.h"
#include "vtkActor.h"
#include "vtkProperty.h"
#include "vtkConeSource.h"

#define AXISWIDTH 3

namespace myvtk {

    class CurveArrow
    {
    public:
        CurveArrow() :m_curve(nullptr), m_cone1(nullptr), m_cone2(nullptr) {}
        CurveArrow(vtkSmartPointer<vtkPoints> points, int coneCount = 1, double color[3] = nullptr, float lineWidth = AXISWIDTH) { CreateArrow(points, coneCount, color, lineWidth); }
        /*函数名: CreateArrow
        * @param points     曲线经过的一组点
        * @param coneCount  椎体数量
        * @param color      颜色
        * @param lineWidth  线宽
        */
        void CreateArrow(vtkSmartPointer<vtkPoints> points, int coneCount = 1, double color[3] = nullptr, float lineWidth = AXISWIDTH)
        {

            if (points->GetNumberOfPoints() < 2) throw "input points must have at least two points!";
            // 曲线部分
            m_curve = vtkSmartPointer<vtkActor>::New();
            m_spline = vtkSmartPointer<vtkParametricSpline>::New();
            m_functionSource = vtkSmartPointer<vtkParametricFunctionSource>::New();

            m_spline->SetPoints(points);
            m_functionSource->SetParametricFunction(m_spline);
            m_functionSource->Update();


            vtkNew<vtkPolyDataMapper> lineMapper;

            lineMapper->SetInputConnection(m_functionSource->GetOutputPort());
            m_curve->SetMapper(lineMapper);

            m_curve->GetProperty()->SetLineWidth(lineWidth);
            if (color != nullptr) m_curve->GetProperty()->SetColor(color);
            // 箭头部分
            double directVec[3] = { 0 };
            int pointsNum = points->GetNumberOfPoints();
            //double* target = points->GetPoint(pointsNum - 1);
            double target[3]{ 0 }, origin[3]{ 0 };
            points->GetPoint(pointsNum - 1, target);
            points->GetPoint(pointsNum - 2, origin);
            //double* origin = points->GetPoint(pointsNum - 2);
            vtkMath::Subtract(target, origin, directVec);
            // 箭头1
            m_coneCount = coneCount;
            if (coneCount >= 1)
            {
                m_coneSource1 = vtkSmartPointer<vtkConeSource>::New();
                vtkNew<vtkPolyDataMapper> coneMapper1;
                m_cone1 = vtkSmartPointer<vtkActor>::New();

                m_coneSource1->SetCenter(target);
                m_coneSource1->SetHeight(0.5);
                m_coneSource1->SetRadius(0.08);
                m_coneSource1->SetResolution(10);
                m_coneSource1->SetDirection(directVec);

                coneMapper1->SetInputConnection(m_coneSource1->GetOutputPort());

                m_cone1->SetMapper(coneMapper1);
                if (color != nullptr) m_cone1->GetProperty()->SetColor(color);
            }

            // 箭头2
            if (coneCount >= 2)
            {
                points->GetPoint(0, target);
                points->GetPoint(0, origin);
                m_coneSource2 = vtkSmartPointer<vtkConeSource>::New();
                vtkNew<vtkPolyDataMapper> coneMapper2;
                m_cone2 = vtkSmartPointer<vtkActor>::New();

                vtkMath::Subtract(target, origin, directVec);

                m_coneSource2->SetCenter(origin);
                m_coneSource2->SetHeight(0.5);
                m_coneSource2->SetRadius(0.08);
                m_coneSource2->SetResolution(10);
                m_coneSource2->SetDirection(directVec);

                coneMapper2->SetInputConnection(m_coneSource2->GetOutputPort());

                m_cone2->SetMapper(coneMapper2);
                if (color != nullptr) m_cone2->GetProperty()->SetColor(color);
            }


        }

        int m_coneCount = 0;
        vtkSmartPointer<vtkActor> m_curve = nullptr;
        vtkSmartPointer<vtkActor> m_cone1 = nullptr;
        vtkSmartPointer<vtkActor> m_cone2 = nullptr;

        vtkSmartPointer<vtkParametricSpline> m_spline = nullptr;
        vtkSmartPointer<vtkParametricFunctionSource> m_functionSource = nullptr;
        vtkSmartPointer<vtkConeSource> m_coneSource1 = nullptr;
        vtkSmartPointer<vtkConeSource> m_coneSource2 = nullptr;
    };
};
创建有厚度的平面

VTK笔记-图形相关-平面-vtkPlaneSource_黑山老妖的博客的博客-CSDN博客_vtk平面图

(4)建立一个标准尺寸的平面,并对其进行着色贴图、拉伸一定的厚度_rexinx的博客-CSDN博客

本文封装了一个Plane,首先通过 vtkPlaneSource 数据源获取一个平面的点及拓扑数据,通过设置 origin, point1, point2 设置平面图形的大小

VTK可交互三维坐标轴_第4张图片

接着使用 vtkLinearExtrusionFilter 通过SetVector()及SetScaleFactor()方法设置平面的厚度

#include "vtkSmartPointer.h"
#include "vtkActor.h"
#include "vtkPlaneSource.h"
#include "vtkLinearExtrusionFilter.h"
#include "vtkTriangleFilter.h"
#include "vtkPolyDataMapper.h"
#include "vtkProperty.h"

namespace myvtk {

    class Plane
    {
    public:
        Plane() :m_plane(nullptr), m_planeSource(nullptr), m_planeLinearExtrusionFilter(nullptr), m_planeTriangleFilter(nullptr) {}
        Plane(double origin[3], double point1[3], double point2[3], double width, double color[3]) { CreatePlane(origin, point1, point2, width, color); };
        /*函数名: CreatePlane
        * @param origin     平面矩形的一个顶点
        * @param point1     平面矩形的一个与origin相邻的顶点
        * @param point2     平面矩形的一个与origin相邻的顶点
        * @param width      平面厚度
        * @param color      颜色
        */
        void CreatePlane(double origin[3], double point1[3], double point2[3], double width, double color[3])
        {
            m_planeSource = vtkSmartPointer<vtkPlaneSource>::New();
            vtkNew<vtkPolyDataMapper> PlaneMapper;
            m_plane = vtkSmartPointer<vtkActor>::New();

            m_planeSource->SetOrigin(origin);
            m_planeSource->SetPoint1(point1);
            m_planeSource->SetPoint2(point2);

            m_planeSource->Update();



            // Apply linear extrusion
            m_planeLinearExtrusionFilter = vtkSmartPointer<vtkLinearExtrusionFilter>::New();
            m_planeLinearExtrusionFilter->SetInputConnection(m_planeSource->GetOutputPort());
            m_planeLinearExtrusionFilter->SetExtrusionTypeToNormalExtrusion();
            double vec[3]{ 1,0,0 };
            m_planeLinearExtrusionFilter->SetVector(vec);
            m_planeLinearExtrusionFilter->SetScaleFactor(width);

            m_planeTriangleFilter = vtkSmartPointer<vtkTriangleFilter>::New();
            m_planeTriangleFilter->SetInputConnection(m_planeLinearExtrusionFilter->GetOutputPort());



            PlaneMapper->SetInputConnection(m_planeTriangleFilter->GetOutputPort());
            m_plane->SetMapper(PlaneMapper);
            m_plane->GetProperty()->SetColor(color);
        }

    public:
        vtkSmartPointer<vtkActor> m_plane;
        vtkSmartPointer<vtkPlaneSource> m_planeSource;
        vtkSmartPointer<vtkLinearExtrusionFilter> m_planeLinearExtrusionFilter;
        vtkSmartPointer<vtkTriangleFilter> m_planeTriangleFilter;
    };
};
创建始终跟随镜头的坐标轴文本

第04章-VTK基础(5)_DolingStudio的博客-CSDN博客

使用 vtkVectorText 作为数据源,vtkFollower是vtkActor的子类,通过SetCamera()方法设置后,可以始终朝向指定的镜头

auto textsSource = vtkSmartPointer<vtkVectorText>::New();
auto textMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
auto texts = vtkSmartPointer<vtkFollower>::New();

textsSource->SetText("x");
textMapper->SetInputConnection(textsSource->GetOutputPort());
texts->SetMapper(textMapper);
texts->SetScale(0.1, 0.1, 0.1);

vtkMath::MultiplyScalar(point2, 1.05);
texts->SetPosition(point2);
texts->GetProperty()->SetColor(color);
texts->SetCamera(renderer->GetActiveCamera());
创建中心圆球
// 绘制中心圆球
m_centerSphereSource = vtkSmartPointer<vtkSphereSource>::New();
vtkNew<vtkPolyDataMapper> sphereMapper;
m_centerSphere = vtkSmartPointer<vtkActor>::New();

m_centerSphereSource->SetRadius(0.2);
m_centerSphereSource->SetCenter(0, 0, 0);
sphereMapper->SetInputConnection(m_centerSphereSource->GetOutputPort());
m_centerSphere->SetMapper(sphereMapper);

m_centerSphere->GetProperty()->SetColor(0.8,0.8,0.8);	// 初始设置较暗的颜色
m_centerSphere->GetProperty()->SetDiffuse(1.0);
m_centerSphere->SetPosition(0, 0, 0);

Picker通过鼠标拾取三维图形

第04章-VTK基础(4)_DolingStudio的博客-CSDN博客

拾取操作是可视化应用程序中常见的一种功能。拾取主要是用于选择数据和Actor或者获取底层的数据值。在显示位置(以像素为坐标值)中拾取时,就会调用vtkAbstractPicker的Pick()方法。

VTK可交互三维坐标轴_第5张图片

本文中使用的是 vtkPropPicker

初始化Picker

// 初始化 vtkPropPicker
this->m_picker = vtkPropPicker::New();
// 将所有的三维坐标系中的Actor使用 AddPickList() 方法添加到 picker 的列表中
TravelAllActors([this](vtkActor* actor) {
    this->m_picker->AddPickList(actor);
});
// 限定picker只拾取列表中的Actor
this->m_picker->PickFromListOn();

使用Picker获取鼠标位置的Actor

vtkActor* GetPickedActor(double x, double y) {
    m_picker->Pick(x, y, 0, this->Renderer);
    return m_picker->GetActor();
}

其中 x, y 对应的是鼠标在屏幕上的位置,当鼠标触发事件时,可以通过 RenderWindowInteractorGetEventPosition()方法获取鼠标当前的位置

double X = self->Interactor->GetEventPosition()[0];
double Y = self->Interactor->GetEventPosition()[1];

现在我们已经完成了拾取鼠标位置的Actor操作,但由于三维坐标系由许多的 Actor 组合而成,为了知道我们拾取的Actor是什么,当我们按下鼠标左键开始拾取的时候是点击了X轴,还是Y轴,还是Z轴,或者是点击了平面,又或者是点击了拖动旋转的箭头,我们需要对拾取的Actor在对象组中进行识别。

int GetSelectedState(vtkActor* actor)
{
    int offset = 0;
    // X Y Z 轴
    for (int i = 0; i < m_axesArrow.size(); i++)
    {
        if ((unsigned long)(m_axesArrow[i]->m_line.GetPointer()) == (unsigned long)actor ||
            (m_axesArrow[i]->m_cone1 != nullptr && (unsigned long)(m_axesArrow[i]->m_cone1.GetPointer()) == (unsigned long)actor))
        {
            return SelAxis(i);
        }
    }
    offset += m_axesArrow.size();
    // 中心圆球
    if ((unsigned long)(m_centerSphere.GetPointer()) == (unsigned long)actor)
    {
        return SelAxis(offset);

    }
    offset += 1;
    // plane
    for (int i = 0; i < m_planes.size(); i++)
    {
        if ((unsigned long)(m_planes[i]->m_plane.GetPointer()) == (unsigned long)actor)
        {
            return SelAxis(i + offset);
        }
    }
    offset += m_planes.size();
    // rotate
    for (int i = 0; i < m_rotateArrow.size(); i++)
    {
        if ((unsigned long)(m_rotateArrow[i]->m_curve.GetPointer()) == (unsigned long)actor ||
            (m_rotateArrow[i]->m_cone1 != nullptr && (unsigned long)(m_rotateArrow[i]->m_cone1.GetPointer()) == (unsigned long)actor) ||
            (m_rotateArrow[i]->m_cone2 != nullptr && (unsigned long)(m_rotateArrow[i]->m_cone2.GetPointer()) == (unsigned long)actor))
        {
            return SelAxis(i + offset);

        }
    }
    return noAxis;
}

依次将拾取的 Actor 地址与X Y Z 轴中心圆球平面旋转箭头对象组中各个成员进行比较,确定当前选中了哪个对象组,使用一个int类型的变量InteractionState配合SelAxis枚举类型进行指示。

坐标变换

齐次坐标

当知道了拾取的对象组之后就可以配合相关变量对操作进行响应

对坐标轴的平移,旋转,缩放操作,都可以通过操作Actor对象的齐次变换矩阵(UserMatrix)来完成,假设当前的坐标点 A(x1, y1, z1) 与移动后的坐标B(x2, y2, z2)
{ x 2 = k 1 x 1 + b 1 y 2 = k 2 y 1 + b 2 z 2 = k 3 z 1 + b 3 \begin{cases} x_2 = k_{1}x_1 + b_{1} \\ y_2 = k_{2}y_1 + b_{2} \\ z_2 = k_{3}z_1 + b_{3} \end{cases} x2=k1x1+b1y2=k2y1+b2z2=k3z1+b3
转换为矩阵形式
( x 2 y 2 z 2 1 ) = [ k 1 0 0 b 1 0 k 2 0 b 2 0 0 k 3 b 3 0 0 0 1 ] ( x 1 y 1 z 1 1 ) \left( \begin{matrix} x_2 \\ y_2 \\ z_2 \\ 1 \end{matrix} \right) = \left[ \begin{matrix} k_{1}&0&0&b_{1} \\ 0&k_{2}&0&b_{2} \\ 0&0&k_{3}&b_{3} \\ 0&0&0&1 \end{matrix} \right] \left( \begin{matrix} x_1 \\ y_1 \\ z_1 \\ 1 \end{matrix} \right) x2y2z21 = k10000k20000k30b1b2b31 x1y1z11
A点对应的齐次变换矩阵为
[ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] \left[ \begin{matrix} 1&0&0&0 \\ 0&1&0&0 \\ 0&0&1&0 \\ 0&0&0&1 \end{matrix} \right] 1000010000100001
移动到B点对应的齐次变换矩阵为
[ k 1 0 0 b 1 0 k 2 0 b 2 0 0 k 3 b 3 0 0 0 1 ] \left[ \begin{matrix} k_{1}&0&0&b_{1} \\ 0&k_{2}&0&b_{2} \\ 0&0&k_{3}&b_{3} \\ 0&0&0&1 \end{matrix} \right] k10000k20000k30b1b2b31

vtk中对矩阵的变换可以通过vtkTransform类方便的进行

【VTK学习】空间几何变换_JinSu_的博客-CSDN博客_vtk 矩阵乘法

vtkNew<vtkTransform> transform;
transform->PostMultiply(); //M=A*M
transform->RotateZ(30);		// 绕Z轴旋转30度
transform->Translate(1, 0, 0);	// 平移一个 (1,0,0) 向量

transform->GetMatrix();	// 获取一系列操作合成出的齐次变换矩阵
沿指定轴(X,Y,Z)移动

motion_vector 为坐标轴移动到鼠标位置的向量,m_moveByVec为指定移动的轴方向,m_userMatrix4x4为预定义的vtkMatrix4x4矩阵

double length = vtkMath::Dot(motion_vector, m_moveByVec);

double vec[3] = { 0 };
vec[InteractionState] = length;

vtkNew<vtkTransform> transform;
transform->SetMatrix(m_userMatrix4x4);
transform->Translate(vec);
transform->GetMatrix(m_userMatrix4x4);

actor->GetUserMatrix()->DeepCopy(m_userMatrix4x4);
沿xyz所有方向移动
double vec[3] = { 0 };
vec[0] = motion_vector[0];
vec[1] = motion_vector[1];
vec[2] = motion_vector[2];
printf("move vec (%f, %f, %f)\n", vec[0], vec[1], vec[2]);

vtkNew<vtkTransform> transform;
transform->SetMatrix(m_userMatrix4x4);
transform->Translate(vec);
transform->GetMatrix(m_userMatrix4x4);

actor->GetUserMatrix()->DeepCopy(m_userMatrix4x4);
沿指定平面移动
double vec[3];
vec[0] = m_moveByVec[0] * motion_vector[0];
vec[1] = m_moveByVec[1] * motion_vector[1];
vec[2] = m_moveByVec[2] * motion_vector[2];
printf("move vec (%f, %f, %f)\n", vec[0], vec[1], vec[2]);

vtkNew<vtkTransform> transform;
transform->SetMatrix(m_userMatrix4x4);
transform->Translate(vec);
transform->GetMatrix(m_userMatrix4x4);

actor->GetUserMatrix()->DeepCopy(m_userMatrix4x4);
绕指定轴旋转

oldPoint为鼠标上次触发事件时的位置,newPoint为鼠标当前触发事件时的位置

// 将鼠标位置移动到自身坐标系下,求两次鼠标位置向量在投影平面的夹角
vtkNew<vtkTransform> trans;
trans->SetMatrix(m_userMatrix4x4);

double pos_t1[4]{ oldPoint[0], oldPoint[1], oldPoint[2], 1 };
double pos_t2[4]{ newPoint[0], newPoint[1], newPoint[2], 1 };
vtkNew<vtkMatrix4x4> posture_inv;
vtkMatrix4x4::Invert(m_userMatrix4x4, posture_inv);
auto pos_t = posture_inv->MultiplyDoublePoint(pos_t1);
double v1[3] = { pos_t[0], pos_t[1], pos_t[2] };
pos_t = posture_inv->MultiplyDoublePoint(pos_t2);
double v2[3] = { pos_t[0], pos_t[1], pos_t[2] };

double projection1[3], projection2[3];
GetPlaneProjection(m_moveByVec, v1, projection1);
GetPlaneProjection(m_moveByVec, v2, projection2);
vtkMath::Normalize(projection1);
vtkMath::Normalize(projection2);
double axis[3];
vtkMath::Cross(projection1, projection2, axis);
double radians = acos(vtkMath::Dot(projection1, projection2));
double degrees = vtkMath::DegreesFromRadians(radians);
trans->RotateWXYZ(degrees, axis);
trans->Update();

m_userMatrix4x4->DeepCopy(trans->GetMatrix());

vtkWidget类

VTK可交互三维坐标轴_第6张图片

参考VTK图形图像开发进阶 8.3 VTK Widget

vtkInteractorObserver.h

vtkInteractorObserver.cxx

vtkAbstractWidget.h

vtkAbstractWidget.cxx

vtkWidgetRepresentation.h

vtkWidgetRepresentation.cxx

VTK中Widget的设计是从VTK 5.0版本开始引入的,最初的Widget是从vtk3DWidget派生出来的,从VTK5.1版本开始,VTK Widget从新进行设计,主要的设计理念是将Widget的消息处理与几何表达实体分离,但还是保留了 vtk3DWidget及其子类。vtkAbstractWidget作为基类,只定义一些公共的API以及实现了“交互/表达实体”分离的设计机制,其中,把从vtkRenderWindowInteractor路由过来的消息(事件)交给vtkAbstractWidget的“交互”部分处理,而Widget的“表达实体”则对应一个vtkProp对象(或者是vtkWidgetRepresentation的子类)。这样做的好处是:事件的处理与Widget的表达实体互不干扰,而且可以实现同类Widget使用不同的表达形式,每个VTKAbstractWidget子类内部包含一个vtkWidgetEventTranslate对象和一个vtkWidgetCallbackMapper对象,vtkWidgetEventTranslate的作用是将外部的VTK事件映射为Widget事件(定义于 vtkWidgetEvenet.h文件中), vtkWidgetCallbackMapper将相应的Widget事件与各个受保护的静态操作函数关联起来。

VTK可交互三维坐标轴_第7张图片

可以使用vtkWidgetCallbackMapper的SetCallbackMethod函数设置 VTK Event, Widget Event, Method Invocation之间的关联

void vtkWidgetCallbackMapper::SetCallbackMethod(unsigned long VTKEvent, unsigned long widgetEvent, vtkAbstractWidget* w, CallbackType f)
{
    this->EventTranslator->SetTranslation(VTKEvent, widgetEvent);
    this->SetCallbackMethod(widgetEvent, w, f);
}

示例:

this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonReleaseEvent,
                                       vtkWidgetEvent::EndSelect,
                                       this, vtkDistanceWidget::EndSelectAction);

本文将 可拖动的三维坐标轴封装为 vtkAbstractWidget 的子类,作为交互与表达实体相分离的部件,还需要一个继承vtkWidgetRepresentation的子类对象,

继承vtkAbstractWidget类

继承vtkAbstractWidget类只需实现 vtkAbstractWidget 的纯虚函数 virtual void CreateDefaultRepresentation() = 0;即可

void vtkMoveableAxesWidget::CreateDefaultRepresentation()
{
    if (!this->WidgetRep)
    {
        auto rep = vtkMoveableAxesRepresentation::New();
        this->SetWidgetRepresentation(rep);
    }
}

此外 vtkWidget 类还应该实现事件到方法的映射,通过在构造函数执行时调用 vtkWidgetCallbackMapper 对象的 SetCallbackMethod 方法关联 VTK Event, Widget Event, Method Invocation

vtkMoveableAxesWidget::vtkMoveableAxesWidget()
    : m_cursorActor(nullptr),
	WidgetState(vtkMoveableAxesWidget::Start)
{

    this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonPressEvent, vtkWidgetEvent::Select, this, vtkMoveableAxesWidget::SelectAction);
    this->CallbackMapper->SetCallbackMethod(vtkCommand::MouseMoveEvent, vtkWidgetEvent::Move, this, vtkMoveableAxesWidget::MoveAction);
    this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonReleaseEvent, vtkWidgetEvent::EndSelect, this, vtkMoveableAxesWidget::EndSelectAction);

}

接着定义响应事件的回调函数 SelectAction, MoveAction, EndSelectAction 这些回调函数必须定义为静态成员,并且设置一个形参vtkAbstractWidget *w

void vtkMoveableAxesWidget::SelectAction(vtkAbstractWidget *w)
{
    vtkMoveableAxesWidget* self = reinterpret_cast<vtkMoveableAxesWidget*>(w);

    // Get the event position
    int X = self->Interactor->GetEventPosition()[0];
    int Y = self->Interactor->GetEventPosition()[1];
    if (!self->CurrentRenderer || !self->CurrentRenderer->IsInViewport(X, Y))
    {
        self->WidgetState = vtkMoveableAxesWidget::Start;
        return;
    }

    double e[2];
    e[0] = static_cast<double>(X);
    e[1] = static_cast<double>(Y);
    // 调用 vtkWidgetRepresentation 子类中定义的交互接口函数
    self->WidgetRep->StartWidgetInteraction(e);
    
    int interactionState = self->WidgetRep->GetInteractionState();
    if (interactionState < 0)
    {
        return;
    }

    // 更新鼠标指针
    self->UpdateCursorShape(interactionState);
    // 设置组件状态为 激活
    self->WidgetState = vtkMoveableAxesWidget::Active;

    // 阻断事件传递,发送StartInteractionEvent事件,渲染
    self->GrabFocus(self->EventCallbackCommand);
    self->StartInteraction();
    self->EventCallbackCommand->SetAbortFlag(1);
    self->InvokeEvent(vtkCommand::StartInteractionEvent, nullptr);
    self->Render();
}



void vtkMoveableAxesWidget::MoveAction(vtkAbstractWidget* w)
{
    vtkMoveableAxesWidget* self = reinterpret_cast<vtkMoveableAxesWidget*>(w);

    double X = self->Interactor->GetEventPosition()[0];
    double Y = self->Interactor->GetEventPosition()[1];
    // 鼠标移动时高亮功能
    self->MoveHighLight(X, Y);

    // 判断组件是否激活
    if (self->WidgetState == vtkMoveableAxesWidget::Start)
    {
        return;
    }

    // 开始交互,调用 vtkWidgetRepresentation 子类中定义的交互接口函数
    double e[2];
    e[0] = static_cast<double>(X);
    e[1] = static_cast<double>(Y);
    self->WidgetRep->WidgetInteraction(e);

    // 阻断事件传递,发送InteractionEvent事件,渲染
    self->EventCallbackCommand->SetAbortFlag(1);
    self->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
    self->Render();
}


void vtkMoveableAxesWidget::EndSelectAction(vtkAbstractWidget* w)
{
    vtkMoveableAxesWidget* self = reinterpret_cast<vtkMoveableAxesWidget*>(w);
    self->WidgetState = vtkMoveableAxesWidget::Start;
    
    // 调用 vtkWidgetRepresentation 子类中定义的交互接口函数
    self->WidgetRep->EndWidgetInteraction(nullptr);
    
    // 更新鼠标指针
    self->UpdateCursorShape(-1);

    // 阻断事件传递,发送InteractionEvent事件,渲染
    self->ReleaseFocus();
    self->EventCallbackCommand->SetAbortFlag(1);
    self->EndInteraction();
    self->InvokeEvent(vtkCommand::EndInteractionEvent, nullptr);
    self->Render();
}
继承vtkWidgetRepresentation类

继承vtkWidgetRepresentation首先要实现它的纯虚函数virtual void BuildRepresentation() = 0;

void vtkMoveableAxesRepresentation::BuildRepresentation()
{
    // 刷新Rep
    if (!this->Renderer || !this->Renderer->GetRenderWindow()) {
        return;
    }
    // actor、source重新计算
    if (this->GetMTime() > this->BuildTime
        || this->Renderer->GetRenderWindow()->GetMTime() > this->BuildTime) {

    }
    // 重建和renderwindow更改时调整控制柄的大小
    if (this->GetMTime() > this->BuildTime
        || this->Renderer->GetRenderWindow()->GetMTime() > this->BuildTime) {
        this->SizeHandles();
        this->BuildTime.Modified();
    }
}

根据 vtkWidgetRepresentation.h中所述
为了让 vtkWidgetRepresentation 子类表现得像个 vtkProp对象子类需要实现以下方法

  /**
   * Methods to make this class behave as a vtkProp. They are repeated here (from the
   * vtkProp superclass) as a reminder to the widget implementor. Failure to implement
   * these methods properly may result in the representation not appearing in the scene
   * (i.e., not implementing the Render() methods properly) or leaking graphics resources
   * (i.e., not implementing ReleaseGraphicsResources() properly).
   */

  double *GetBounds() override {return nullptr;}
  void ShallowCopy(vtkProp *prop) override;
  void GetActors(vtkPropCollection *) override {}
  void GetActors2D(vtkPropCollection *) override {}
  void GetVolumes(vtkPropCollection *) override {}
  void ReleaseGraphicsResources(vtkWindow *) override {}
  int RenderOverlay(vtkViewport *vtkNotUsed(viewport)) override {return 0;}
  int RenderOpaqueGeometry(vtkViewport *vtkNotUsed(viewport)) override {return 0;}
  int RenderTranslucentPolygonalGeometry(vtkViewport *vtkNotUsed(viewport)) override {return 0;}
  int RenderVolumetricGeometry(vtkViewport *vtkNotUsed(viewport)) override {return 0;}
  int HasTranslucentPolygonalGeometry() override { return 0; }

本文由于只使用到了Actor对象,根据需要实现了如下方法

    double* GetBounds() VTK_SIZEHINT(6) override;
    void GetActors(vtkPropCollection* pc) override;
    void ReleaseGraphicsResources(vtkWindow*) override;
    int RenderOpaqueGeometry(vtkViewport*) override;
    int RenderTranslucentPolygonalGeometry(vtkViewport*) override;
    vtkTypeBool HasTranslucentPolygonalGeometry() override;

具体实现可以查阅VTK提供的完整的源码,部分示例如下

void vtkMoveableAxesRepresentation::GetActors(vtkPropCollection* pc)
{
    pc = vtkPropCollection::New();

    TravelAllActors([&pc](vtkActor* actor) {
        	pc->AddItem(actor);
        });
}

可以使用TravelAllActors传入一个匿名函数的方式遍历所有Actor对象

#include 
class vtkActor;

using travelActorsCallback = std::function<void(vtkActor*)>;
void TravelAllActors(travelActorsCallback);

void vtkMoveableAxesRepresentation::TravelAllActors(travelActorsCallback callback)
{
    vtkActor* actor;
    // x,y,z轴
    for (int i = 0; i < m_axesArrow.size(); i++)
    {
        actor = m_axesArrow[i]->m_line.GetPointer();
        callback(actor);
        actor = m_axesArrow[i]->m_cone1.GetPointer();
        callback(actor);
    }
    // 坐标轴文本
    for (int i = 0; i < this->m_texts.size(); i++)
    {
        actor = this->m_texts[i];
        callback(actor);
    }
    // 拓展部分
    m_extendActors->InitTraversal();
    while ((actor = m_extendActors->GetNextActor()) != nullptr)
    {
        callback(actor);
    }
}

以下是推荐的vtkWidgetRepresentation 子类与 vtkWidget交互的接口函数

  /**
   * The following is a suggested API for widget representations. These methods
   * define the communication between the widget and its representation. These
   * methods are only suggestions because widgets take on so many different
   * forms that a universal API is not deemed practical. However, these methods
   * should be implemented when possible to insure that the VTK widget hierarchy
   * remains self-consistent.
   * 
   * PlaceWidget() - given a bounding box (xmin,xmax,ymin,ymax,zmin,zmax), place
   * the widget inside of it. The current orientation of the widget
   * is preserved, only scaling and translation is performed.
   * StartWidgetInteraction() - generally corresponds to a initial event (e.g.,
   * mouse down) that starts the interaction process
   * with the widget.
   * WidgetInteraction() - invoked when an event causes the widget to change
   * appearance.
   * EndWidgetInteraction() - generally corresponds to a final event (e.g., mouse up)
   * and completes the interaction sequence.
   * ComputeInteractionState() - given (X,Y) display coordinates in a renderer, with a
   * possible flag that modifies the computation,
   * what is the state of the widget?
   * GetInteractionState() - return the current state of the widget. Note that the
   * value of "0" typically refers to "outside". The
   * interaction state is strictly a function of the
   * representation, and the widget/represent must agree
   * on what they mean.
   * Highlight() - turn on or off any highlights associated with the widget.
   * Highlights are generally turned on when the widget is selected.
   * 
* Note that subclasses may ignore some of these methods and implement their own * depending on the specifics of the widget. */
virtual void PlaceWidget(double* vtkNotUsed(bounds[6])) {} virtual void StartWidgetInteraction(double eventPos[2]) { (void)eventPos; } virtual void WidgetInteraction(double newEventPos[2]) { (void)newEventPos; } virtual void EndWidgetInteraction(double newEventPos[2]) { (void)newEventPos; } virtual int ComputeInteractionState(int X, int Y, int modify=0); virtual int GetInteractionState() {return this->InteractionState;} virtual void Highlight(int vtkNotUsed(highlightOn)) {}

与picker相关的接口函数

  /**
   * Register internal Pickers in the Picking Manager.
   * Must be reimplemented by concrete widget representations to register
   * their pickers.
   */
  virtual void RegisterPickers();

  /**
   * Unregister internal pickers from the Picking Manager.
   */
  virtual void UnRegisterPickers();

  /**
   * Update the pickers registered in the Picking Manager when pickers are
   * modified.
   */
  virtual void PickersModified();

明确了满足 vtkAbstractWidget 与 vtkWidgetRepresentation 类需要实现的接口,以及它们之间交互需要的接口,完整代码见源码。

使用

#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkAutoInit.h"
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);
VTK_MODULE_INIT(vtkRenderingFreeType);
VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2);

#include "vtkMoveableAxesWidget.h"

int main()
{
    vtkNew<vtkRenderer> renderer;
    vtkNew<vtkRenderWindow> renWin;
    vtkNew<vtkRenderWindowInteractor> renWinI;

    renderer->GetActiveCamera()->SetPosition(8, 8, 8);
    renWin->AddRenderer(renderer);
    renWinI->SetRenderWindow(renWin);
    renWinI->Initialize();
	// Widget的创建需要在 renWinI->SetRenderWindow(renWin) 和 renWinI->Initialize() 之后
    // 创建可移动坐标轴
    vtkMoveableAxesWidget* axes = vtkMoveableAxesWidget::New();
    axes->SetInteractor(renWinI);
    axes->On();

    renWin->Render();
    renWinI->Start();

    return EXIT_SUCCESS;
}

其他

Qt VS Tools插件安装

https://blog.51cto.com/u_15707179/5447267

VS配置Qt开发环境

https://www.cnblogs.com/szitcast/p/15733691.html

解决:Qt项目中出现红色波浪线错误提示

解决:vs打开.ui文件出现Qt Designer后闪退

VTK源码编译

https://www.bilibili.com/video/BV1S5411D7uj?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=c9263c84459a6eabff37b3b1b85328e6

Visual Studio C++项目使用第三方库

https://blog.csdn.net/u012611644/article/details/81105539

https://blog.csdn.net/yangfchn/article/details/85162934

控制台显示调试信息

工程属性->链接器->系统->子系统 设置为 控制台

参考

VTK官方网址
VTK官方文档
VTK源码
VTK可移动三维坐标轴 vtkMovableAxesWidget
【VTK】可拖动的坐标轴MovableAxesWidget
第03章-VTK系统概述(1)_DolingStudio的博客-CSDN博客
VTK笔记-图形相关-平面-vtkPlaneSource_黑山老妖的博客的博客-CSDN博客_vtk平面图
(4)建立一个标准尺寸的平面,并对其进行着色贴图、拉伸一定的厚度_rexinx的博客-CSDN博客
第04章-VTK基础(4)_DolingStudio的博客-CSDN博客
【VTK学习】空间几何变换_JinSu_的博客-CSDN博客_vtk 矩阵乘法
第04章-VTK基础(5)_DolingStudio的博客-CSDN博客
《VTK图形图像开发进阶》
《VTKUsersGuide》

你可能感兴趣的:(VTK,c++)