因为实习工作需要制作一个如下图所示的可交互的三维坐标轴,制作这个坐标轴,首先需要创建一些三维图形,接着需要熟悉交互模块和鼠标进行交互,最后将它们封装成一个vtkWidget
类
下面是VTK中经常会使用到的类的描述。
渲染场景中数据的可视表达(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的等级结构以便在整个结构平移、旋转或者放缩时能够更合理的控制变换。
许多Props如vtkActor和vtkVolume利用vtkAbstractMapper的子类来保存输入数据的引用以及提供真正的渲染功能。vtkPolyDataMapper是渲染多边形几何数据主要的Mapper类。而对于体数据,VTK提供了多种渲染技术。例如,vtkFixedPointVolumeRayCastMapper用来渲染vtkImageData类型的数据,vtkProjectedTetrahedraMapper则是用来渲染vtkUnstructuredGrid类型的数据。
某些Props采用单独的属性对象来存储控制数据外观显示的参数,这样不同的对象可以轻松的实现外观参数的共享。vtkActor利用vtkProperty对象存储外观(属性)参数,如颜色、不透明度、材质的环境光(Ambient)系数、散射光(Diffuse)系数和反射光(Specular)系数等。而vtkVolume则是采用vtkVolumeProperty对象来获取体对象的绘制参数,如将标量值映射为颜色和不透明度的传输函数(Transfer Function)【译者:也有译成“传递函数”】。另外,一些vtkMapper提供相应的函数设置裁剪面以便显示对象的内部结构。
vtkCamera存储了场景中的摄像机参数,换言之,如何来“看”渲染场景里的对象,主要参数是摄像机的位置、焦点、和场景中的上方向向量。其他参数可以控制视图变换,如平行投影或者透视投影,图像的尺度或者视角,以及视景体的远近裁剪平面等。
组成场景的对象包括Prop,Camara和Light都被集中在一个vtkRenderer对象中。vtkRenderer负责管理场景的渲染过程。一个vtkRenderWindow中可以有多个vtkRenderer对象,而这些vtkRenderer可以渲染在窗口中不同的矩形区域中(视口),甚至可以是覆盖的区域。
vtkRendererWindow将操作系统与VTK渲染引擎连接到一起。不同平台下的vtkRendererWindow子类负责本地计算机系统中窗口创建和渲染过程的管理。当使用VTK开发应用程序时,只需要使用平台无关的vtkRendererWindow类,程序运行时,系统会自动替换为平台相关的vtkRendererWindow子类。vtkRendererWindow中包含了vtkRenderer的集合,以及控制渲染的参数,如立体显示(Stereo)、反走样、运动模糊(Motion Blur)和焦点深度(FocalDepth)
vtkRenderWindowInteractor负责监听鼠标、键盘和时钟消息,并通过VTK中的Command/Observer设计模式进行相应的处理。vtkInteractorStyle监听这些消息并进行处理以完成旋转、拉伸和放缩等运动控制。vtkRenderWindowInteractor自动建立一个默认的3D场景交互器样式(InteractorStyle),当然你也可以选择一个二维图像浏览的交互器样式,或者是创建自定义的交互器样式。
场景中的许多对象,如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;
}
本文封装了一个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类通过指定弯曲箭头需要经过的一系列的点来绘制曲线,并可以设置箭头上椎体的数量,设置颜色,线宽参数
其中绘制曲线涉及到了 vtkParametricSpline
及 vtkParametricFunctionSource
类
#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);
第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 对应的是鼠标在屏幕上的位置,当鼠标触发事件时,可以通过 RenderWindowInteractor
的GetEventPosition()
方法获取鼠标当前的位置
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(); // 获取一系列操作合成出的齐次变换矩阵
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);
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());
参考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 的纯虚函数 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
首先要实现它的纯虚函数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;
}
https://blog.51cto.com/u_15707179/5447267
https://www.cnblogs.com/szitcast/p/15733691.html
解决:Qt项目中出现红色波浪线错误提示
解决:vs打开.ui文件出现Qt Designer后闪退
https://www.bilibili.com/video/BV1S5411D7uj?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=c9263c84459a6eabff37b3b1b85328e6
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》