• VTK可拖动三维坐标系


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

    请添加图片描述

    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图像图像开发进阶 第二章的内容

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

    VTKUsersGuide 第三章的内容

    可视化管线

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

    请添加图片描述

    使用预定义的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 设置平面图形的大小

    在这里插入图片描述

    接着使用 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()方法。

    在这里插入图片描述

    本文中使用的是 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

    {x2=k1x1+b1y2=k2y1+b2z2=k3z1+b3" role="presentation">{x2=k1x1+b1y2=k2y1+b2z2=k3z1+b3
    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(
    x2y2z21" role="presentation">x2y2z21
    \right) = \left[
    k100b10k20b200k3b30001" role="presentation">k100b10k20b200k3b30001
    \right] \left(
    x1y1z11" role="presentation">x1y1z11
    \right)
    x2y2z21 = k10000k20000k30b1b2b31 x1y1z11

    A点对应的齐次变换矩阵为
    [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] \left[
    1000010000100001" role="presentation">1000010000100001
    \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[
    k100b10k20b200k3b30001" role="presentation">k100b10k20b200k3b30001
    \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图形图像开发进阶 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事件与各个受保护的静态操作函数关联起来。

    在这里插入图片描述

    可以使用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》

  • 相关阅读:
    2023江西理工大学计算机考研信息汇总
    【无标题】
    面试题:在 Java 中 new 一个对象的流程是怎样的?彻底被问懵了。。
    web协议-接口测试-Python自动化面试题
    初识设计模式 - 备忘录模式
    前端学习案例-有哪些是你成为一名开发之后才知道的事情2021
    SOME/IP 协议介绍(六)接口设计的兼容性规则
    数据集成实施过程注意点总结
    【DockerCE】Docker-CE 20.10.18正式版发布
    Mediapipe 在Android studio 运行官方的 FaceDetection
  • 原文地址:https://blog.csdn.net/Zz_er/article/details/127098449