VTK Tutorial

VTK Tutorial

VTK 新手教程详解

可视化、交互

  官方教程:https://examples.vtk.org/site/Cxx/#tutorial

  关于如何编译运行,我使用官方的方法无法运行,下面介绍我使用的方法。我的配置是windows10 + vs2017 + vtk9.1 + cmake3.30。

第一步:使用cmake gui编译

image-20241220222908306

第二步:

点击一次configure,会弹出一个对话框,选择你的vs版本,以及x64.

image-20241220223128292

第三步:

点击一次后,gui中间可能是红的,不必管他,保证gui最下面出现Configuring done 即可。此时再点击一次Configure,应该就不报红了,然后点击generate。

第四步:

去build文件夹下,用vs打开.sln

image-20241220223556300

改成release,再右键all_build,点击生成。

image-20241220223646925

生成成功。

image-20241220223829133

第五步:

去release文件夹下,执行exe

image-20241220224038069

Step1

  此示例创建了一个圆锥体的多边形模型,然后将其渲染到屏幕上。它将圆锥体旋转 360 度,然后退出。大多数VTK程序的基本流程都是source -> mapper -> actor -> renderer -> renderwindow

代码流程

  1. 创建并设置圆锥源(source): 生成几何数据(例如,圆锥体的几何形状)。创建了一个 vtkConeSource 对象,设置了圆锥的高度、半径和分辨率。这个源对象负责生成圆锥体的几何数据。
  2. 创建 vtkPolyDataMapper 映射器(mapper):将几何数据映射到图形原语(如三角形)。 vtkPolyDataMapper 用于将几何数据(在本例中是圆锥的多边形数据)映射到图形原语(例如三角形)上,使得数据可以被渲染。通过 SetInputConnection 方法将圆锥的数据连接到映射器。
  3. 创建并设置演员 (vtkActor)(actor):负责在场景中呈现物体(管理位置、颜色等属性)。 vtkActor 代表一个可渲染的对象。它是实际显示在场景中的对象,包含了映射器、颜色等信息。将圆锥的映射器设置到演员中,并且通过 GetProperty()->SetColor 设置了演员的颜色为 “Tomato”(番茄红)。
  4. 创建渲染器和渲染窗口(renderer):负责管理场景中的所有演员,执行渲染操作。 vtkRenderer 是负责渲染场景的对象,我们将渲染器添加到渲染窗口 (vtkRenderWindow) 中。渲染窗口是实际显示 3D 场景的窗口。
  5. 创建并设置渲染窗口交互器(renderwindow):展示渲染结果的窗口。 vtkRenderWindowInteractor 允许用户与渲染窗口进行交互,例如旋转、缩放或平移视图。

示例代码

1
2
// 创建一个 vtkNamedColors 的实例,使用它来为对象和背景选择颜色。
vtkNew<vtkNamedColors> colors;
  • vtkNamedColors 是一个类,提供了对常见颜色名称的访问。例如,它可以让我们用 "Tomato""CadetBlue" 等预定义的颜色名称,而不是手动指定 RGB 值。这里创建了一个 colors 对象,方便我们在后续代码中选择颜色。
1
2
3
4
5
6
7
// 现在我们创建一个 vtkConeSource 的实例,并设置它的一些属性。
// vtkConeSource 的实例 "cone" 是可视化管道的一部分(它是一个源处理对象);它生成数据
// (输出类型是 vtkPolyData),这些数据可以被其他过滤器处理。
vtkNew<vtkConeSource> cone;
cone->SetHeight(3.0);
cone->SetRadius(1.0);
cone->SetResolution(10);
  • vtkConeSource 是一个源对象,用于生成一个圆锥体的几何数据。通过设置以下参数来控制圆锥的属性:
    • SetHeight(3.0):设置圆锥的高度为 3.0 单位。
    • SetRadius(1.0):设置圆锥的底面半径为 1.0 单位。
    • SetResolution(10):设置圆锥底面的分辨率,即底面多边形的边数。较高的分辨率会使圆锥底面更平滑。
1
2
3
4
5
// 在这个例子中,我们通过一个映射器处理对象来终结管道。
// (可以在源和映射器之间插入中间过滤器,如 vtkShrinkPolyData。)我们创建了一个
// vtkPolyDataMapper 的实例,用于将多边形数据映射成图形原语。我们将圆锥源的输出连接到映射器的输入。
vtkNew<vtkPolyDataMapper> coneMapper;
coneMapper->SetInputConnection(cone->GetOutputPort());
  • vtkPolyDataMapper 是一个用于将多边形数据(例如圆锥的几何数据)转换为图形原语的对象。它是 VTK 渲染管线中的重要组件。
    • SetInputConnection(cone->GetOutputPort()):将圆锥源的输出数据连接到映射器的输入端,表示映射器将使用圆锥的数据进行渲染。
1
2
3
4
5
6
// 创建一个演员来表示圆锥体。演员协调映射器的图形原语的渲染。
// 演员还通过 vtkProperty 实例引用属性,并包含一个内部变换矩阵。
// 我们将这个演员的映射器设置为我们之前创建的 coneMapper。
vtkNew<vtkActor> coneActor;
coneActor->SetMapper(coneMapper);
coneActor->GetProperty()->SetColor(colors->GetColor3d("MistyRose").GetData());
  • vtkActor 是一个表示可视化对象的核心组件,它将多边形数据通过映射器传递给渲染器。演员将负责场景中对象的可视化表示。
    • SetMapper(coneMapper):将映射器 coneMapper 与演员 coneActor 绑定,使得演员可以使用映射器渲染的数据。
    • 每个演员都有一个 vtkProperty 对象,用于管理它的可视化属性(例如颜色、透明度等)。
      • GetProperty() 获取演员的属性对象。
      • SetColor(colors->GetColor3d("MistyRose").GetData()) 设置演员的颜色为 MistyRose(雾玫瑰色)。colors->GetColor3d("MistyRose") 返回一个 vtkColor3d 对象,它表示 MistyRose 颜色的 RGB 值。通过 .GetData() 方法,实际的 RGB 数据被提取出来并应用到演员的属性上。
1
2
3
4
5
6
// 创建渲染器并将演员分配给它。渲染器像一个视口。
// 它是屏幕上窗口的一部分或全部,负责绘制它所拥有的演员。
// 我们还在这里设置背景颜色。
vtkNew<vtkRenderer> ren1;
ren1->AddActor(coneActor);
ren1->SetBackground(colors->GetColor3d("MidnightBlue").GetData());
  • vtkNew<vtkRenderer> ren1;:使用 vtkNew 创建了一个新的渲染器对象 ren1。渲染器是 VTK 中的核心组件之一,负责控制场景的绘制,类似于一个视口(viewport)。渲染器会管理显示在窗口中的所有对象,并决定如何绘制它们。
  • ren1->AddActor(coneActor);:将之前创建的演员 coneActor 添加到渲染器 ren1 中。演员是负责渲染可视化对象的组件。在这个例子中,演员代表了一个圆锥体对象。渲染器将会根据演员的属性(如颜色、位置等)来绘制它。
  • ren1->SetBackground(colors->GetColor3d("MidnightBlue").GetData());:设置渲染器的背景颜色为 MidnightBlue(午夜蓝)。colors->GetColor3d("MidnightBlue") 返回一个 vtkColor3d 对象,该对象包含 RGB 颜色值, .GetData() 方法用于提取实际的 RGB 数据并传递给 SetBackground() 方法。这使得渲染窗口的背景色变为午夜蓝。
1
2
3
4
5
6
// 最后,我们创建一个渲染窗口,渲染窗口将显示在屏幕上。
// 我们使用 AddRenderer 将渲染器添加到渲染窗口中。我们还设置窗口的大小为 300 像素 x 300 像素。
vtkNew<vtkRenderWindow> renWin;
renWin->AddRenderer(ren1);
renWin->SetSize(300, 300);
renWin->SetWindowName("Tutorial_Step1");
  • vtkNew<vtkRenderWindow> renWin;:创建了一个新的渲染窗口 renWin。渲染窗口是一个图形界面窗口,负责显示渲染器中所有的对象。它通常对应于用户看到的图形输出窗口。
  • renWin->AddRenderer(ren1);:将之前创建的渲染器 ren1 添加到渲染窗口 renWin 中。通过这个设置,渲染窗口 renWin 就能够显示渲染器 ren1 中的图形内容(如圆锥体及其背景色)。
  • renWin->SetSize(300, 300);:设置渲染窗口的大小为 300 像素 x 300 像素。
  • renWin->SetWindowName("Tutorial_Step1");:设置渲染窗口的标题为 "Tutorial_Step1"
1
2
3
4
5
6
7
8
// 现在我们循环 360 度,每次渲染圆锥体。
for (int i = 0; i < 360; ++i)
{
// 渲染图像
renWin->Render();
// 使活动相机旋转 1 度。
ren1->GetActiveCamera()->Azimuth(1);
}
  • renWin->Render();:负责触发渲染窗口的绘制操作。每次调用 Render() 方法时,渲染窗口会根据渲染器中的内容更新图形显示。
  • ren1->GetActiveCamera()->Azimuth(1);:通过 ren1->GetActiveCamera() 获取当前渲染器中正在使用的相机,并调用 Azimuth(1) 方法让相机绕其垂直轴旋转 1 度。Azimuth() 方法用于使相机沿水平方向(围绕 z 轴)旋转指定的角度。

效果

image-20241220162536164

Step2

  VTK 使用命令/观察者设计模式。也就是说,观察者会监视任何vtkObject(或子类)可能对其自身调用的特定事件。例如,vtkRenderer在开始渲染时会调用“StartEvent”。这里我们添加了一个观察者,当观察到此事件时,它会调用命令。

  与step1的区别主要在于随着圆锥体的旋转,回调函数会打印出当前活动摄像机的三维位置。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 首先包含我们正在使用的 VTK 类所需的头文件。
#include <vtkActor.h>
#include <vtkCamera.h>
#include <vtkCommand.h>
#include <vtkConeSource.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>

#include <iostream>

//
// 我们将回调放在一个匿名命名空间中,即没有名称的命名空间。
// 这个匿名命名空间只在你创建它的文件内可访问。
// 因此它是一种声明唯一标识符并避免创建全局静态变量的好方法。
//
namespace {
// 交互回调。
class vtkMyCallback : public vtkCommand
{
public:
static vtkMyCallback* New()
{
return new vtkMyCallback;
}
void Execute(vtkObject* caller, unsigned long, void*) override
{
// 注意使用 reinterpret_cast 将 caller 转换为预期的类型。
auto renderer = reinterpret_cast<vtkRenderer*>(caller);
std::cout << renderer->GetActiveCamera()->GetPosition()[0] << " "
<< renderer->GetActiveCamera()->GetPosition()[1] << " "
<< renderer->GetActiveCamera()->GetPosition()[2] << std::endl;
}
vtkMyCallback() = default;
};
} // namespace
  • 命名空间: 使用匿名命名空间(namespace {})封装回调函数,确保它仅在当前文件中可见。这是一种避免全局变量命名冲突的好方法。
  • vtkMyCallback 类:
    • vtkMyCallback 类继承自 vtkCommand,这是 VTK 中处理事件和回调的基类。
    • Execute() 方法是回调函数的核心。当事件触发时,该方法会被调用。在这里,它从 vtkRenderer 获取活动相机的位置并将其打印出来。
  • reinterpret_cast 使用 reinterpret_cast 将传递给回调函数的 caller 对象转换为 vtkRenderer* 类型。caller 是触发回调的对象(在此为 vtkRenderer),通过这种方式获取相机位置。
  • 打印相机位置: 使用 renderer->GetActiveCamera()->GetPosition() 获取相机的 3D 坐标(x, y, z),并打印到控制台。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
vtkNew<vtkNamedColors> colors;

//
// The pipeline creation is documented in Tutorial_Step1.
//
vtkNew<vtkConeSource> cone;
cone->SetHeight(3.0);
cone->SetRadius(1.0);
cone->SetResolution(10);

vtkNew<vtkPolyDataMapper> coneMapper;
coneMapper->SetInputConnection(cone->GetOutputPort());
vtkNew<vtkActor> coneActor;
coneActor->SetMapper(coneMapper);
coneActor->GetProperty()->SetColor(colors->GetColor3d("MistyRose").GetData());

vtkNew<vtkRenderer> ren1;
ren1->AddActor(coneActor);
ren1->SetBackground(colors->GetColor3d("MidnightBlue").GetData());
ren1->ResetCamera();

vtkNew<vtkRenderWindow> renWin;
renWin->AddRenderer(ren1);
renWin->SetSize(300, 300);
renWin->SetWindowName("Tutorial_Step2");
  • 这部分代码和step1中的完全相同,除了多了一行ren1->ResetCamera();,用于重置相机的位置,使得圆锥体正确显示在视口中。ResetCamera() 会根据场景中的对象自动调整摄像机位置,确保它们可以被清晰地看到。
1
2
3
// Here is where we setup the observer.
vtkNew<vtkMyCallback> mo1;
ren1->AddObserver(vtkCommand::StartEvent, mo1);
  • vtkNew<vtkMyCallback> mo1;:创建一个 vtkMyCallback 类型的回调函数对象 mo1
  • ren1->AddObserver(vtkCommand::StartEvent, mo1);:将回调函数 mo1 添加到渲染器 ren1 上,并监听 StartEvent 事件。vtkCommand::StartEvent 是 VTK 的一个事件类型,当渲染开始时会触发该事件,从而调用 mo1 中的 Execute() 方法。
1
2
3
4
5
6
7
8
// 现在我们循环 360 度,每次渲染圆锥体。
for (int i = 0; i < 360; ++i)
{
// 渲染图像
renWin->Render();
// 使活动相机旋转 1 度。
ren1->GetActiveCamera()->Azimuth(1);
}
  • 与step1类似

效果

image-20241220170753621

  与step1不同的是,多进行了一段屏幕输出(回调函数),共计输出了360行。

step3

  此示例演示如何在渲染窗口内使用多个渲染器。(创建了两个不同的渲染器,显示相同的圆锥体对象在不同的视口中)

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
vtkNew<vtkNamedColors> colors;

vtkNew<vtkConeSource> cone;
cone->SetHeight(3.0);
cone->SetRadius(1.0);
cone->SetResolution(10);

vtkNew<vtkPolyDataMapper> coneMapper;
coneMapper->SetInputConnection(cone->GetOutputPort());

vtkNew<vtkActor> coneActor;
coneActor->SetMapper(coneMapper);
coneActor->GetProperty()->SetColor(colors->GetColor3d("MistyRose").GetData());
  • 和之前完全一致。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建两个渲染器并将演员添加到其中。渲染器将演员渲染到 vtkRenderWindow 内的一个视口中。
// 它是屏幕上窗口的一部分或全部,负责绘制它所拥有的演员。我们还在这里设置了背景颜色。
// 在这个例子中,我们将相同的演员添加到两个不同的渲染器中;当然,也可以将不同的演员添加到不同的渲染器中。
//
vtkNew<vtkRenderer> ren1;
ren1->AddActor(coneActor);
ren1->SetBackground(colors->GetColor3d("RoyalBlue").GetData());

ren1->SetViewport(0.0, 0.0, 0.5, 1.0);

vtkNew<vtkRenderer> ren2;
ren2->AddActor(coneActor);
ren2->SetBackground(colors->GetColor3d("DodgerBlue").GetData());
ren2->SetViewport(0.5, 0.0, 1.0, 1.0);
  • 创建一个渲染器 ren1,并将圆锥体的演员 coneActor 添加到其中。

    • 设置背景颜色为 RoyalBlue

    • SetViewport(0.0, 0.0, 0.5, 1.0):设置渲染器的视口(即渲染区域)。这里,视口的范围是 (0.0, 0.0)(0.5, 1.0),即占据窗口的左半部分。

  • 创建另一个渲染器 ren2,并添加相同的演员 coneActor

    • 设置背景颜色为 DodgerBlue
    • SetViewport(0.5, 0.0, 1.0, 1.0):设置第二个渲染器的视口为右半部分,覆盖窗口的右半部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vtkNew<vtkRenderWindow> renWin;
renWin->AddRenderer(ren1);
renWin->AddRenderer(ren2);
renWin->SetSize(600, 300);
renWin->SetWindowName("Tutorial_Step3");

ren1->ResetCamera();
ren1->GetActiveCamera()->Azimuth(90);

for (int i = 0; i < 360; ++i)
{
renWin->Render();
ren1->GetActiveCamera()->Azimuth(1);
ren2->GetActiveCamera()->Azimuth(1);
}
  • 与之前类似

效果

image-20241220174347952

step4

  此示例演示了如何创建多个参与者以及如何操纵他们的属性和转换。

示例代码

1
2
3
4
5
6
7
8
9
vtkNew<vtkNamedColors> colors;

vtkNew<vtkConeSource> cone;
cone->SetHeight(3.0);
cone->SetRadius(1.0);
cone->SetResolution(10);

vtkNew<vtkPolyDataMapper> coneMapper;
coneMapper->SetInputConnection(cone->GetOutputPort());
  • 与之前相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 创建一个演员来表示第一个圆锥。演员的属性被修改,以赋予它不同的表面属性。
// 默认情况下,一个演员会创建一个属性,因此可以使用 GetProperty() 方法。
vtkNew<vtkActor> coneActor;
coneActor->SetMapper(coneMapper);
coneActor->GetProperty()->SetColor(0.2, 0.63, 0.79); // 设置颜色
coneActor->GetProperty()->SetDiffuse(0.7); // 设置漫反射属性
coneActor->GetProperty()->SetSpecular(0.4); // 设置镜面反射属性
coneActor->GetProperty()->SetSpecularPower(20); // 设置镜面反射的强度

// 创建一个属性并直接操作它。将其分配给第二个演员。
vtkNew<vtkProperty> property;
property->SetColor(colors->GetColor3d("Tomato").GetData()); // 设置颜色为番茄红
property->SetDiffuse(0.7); // 设置漫反射属性
property->SetSpecular(0.4); // 设置镜面反射属性
property->SetSpecularPower(20); // 设置镜面反射的强度

// 创建第二个演员和属性。属性直接操作后分配给演员。
// 以这种方式,一个属性可以在多个演员之间共享。
// 还要注意,我们使用了与第一个演员相同的 mapper。这样可以避免重复几何体,
// 如果几何体很大,这样做可以节省大量内存。
vtkNew<vtkActor> coneActor2;
coneActor2->SetMapper(coneMapper);
coneActor2->GetProperty()->SetColor(
colors->GetColor3d("LightSeaGreen").GetData()); // 设置颜色为浅海绿色
coneActor2->SetProperty(property); // 设置演员的属性为之前创建的属性
coneActor2->SetPosition(0, 2, 0); // 设置第二个圆锥的位置
  • 创建一个 vtkActor 对象,并将圆锥的映射器 coneMapper 设置为该演员的映射器。

    • coneActor->GetProperty()->SetColor(0.2, 0.63, 0.79) 设置演员的颜色(RGB 值为 0.2, 0.63, 0.79)。

    • 设置演员的材质属性:漫反射强度为 0.7,镜面反射强度为 0.4,镜面反射高光强度为 20。这些属性影响演员在渲染时的外观效果。

  • 创建了一个新的 vtkProperty 对象,设置其颜色为 "Tomato",漫反射强度为 0.7,镜面反射强度为 0.4,镜面反射高光强度为 20。

  • 创建第二个演员 coneActor2,并为其设置与第一个演员相同的映射器 coneMapper

    • 设置 coneActor2 的颜色为 "LightSeaGreen",并将之前创建的 property 设置为该演员的材质属性。

    • 将第二个演员 coneActor2 的位置设置为 (0, 2, 0),即在 Y 轴上将其上移 2 个单位。

两个问题:

1.示例代码中对actor2的颜色设置部分存在冗余代码

1
2
3
property->SetColor(colors->GetColor3d("Tomato").GetData());
coneActor2->GetProperty()->SetColor(colors->GetColor3d("LightSeaGreen").GetData());
coneActor2->SetProperty(property);

最终的颜色是Tomato,第二行代码冗余。

2.只设置actor2的位置,没有设置actor的位置。

  • coneActor2->SetPosition(0, 2, 0);: 这行代码设置了 coneActor2 的位置。SetPosition 会将该演员的几何位置设置为 (0, 2, 0),即沿 Y 轴偏移 2 单位。
  • 默认情况下,如果没有特别设置位置,演员会处于原点 (0, 0, 0)。因此,coneActor 没有调用 SetPosition,它会保持在默认位置。
  • 在 VTK 中,演员的坐标是相对于渲染器的原点的。通常,如果你有多个演员,并且希望它们不重叠,可以为每个演员设置不同的位置。在这段代码中,coneActor2 被显式地放置到 (0, 2, 0),而 coneActor 使用默认位置 (0, 0, 0)

image-20241220180902464

step5

  引入交互的概念,定义了一种不同于默认的交互风格。(通过 vtkInteractorStyleTrackballCamera实现)

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
vtkNew<vtkNamedColors> colors;

vtkNew<vtkConeSource> cone;
cone->SetHeight(3.0);
cone->SetRadius(1.0);
cone->SetResolution(10);

vtkNew<vtkPolyDataMapper> coneMapper;
coneMapper->SetInputConnection(cone->GetOutputPort());

vtkNew<vtkActor> coneActor;
coneActor->SetMapper(coneMapper);
coneActor->GetProperty()->SetColor(colors->GetColor3d("Bisque").GetData());

vtkNew<vtkRenderer> ren1;
ren1->AddActor(coneActor);
ren1->SetBackground(colors->GetColor3d("MidnightBlue").GetData());

vtkNew<vtkRenderWindow> renWin;
renWin->AddRenderer(ren1);
renWin->SetSize(300, 300);
renWin->SetWindowName("Tutorial_Step5");

  • 与之前一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// vtkRenderWindowInteractor 类用于监视 vtkRenderWindow 中的事件(例如,按键、鼠标事件)。
// 这些事件会被转换为 VTK 可以理解的事件调用
// 然后,这些 VTK 事件的观察者可以根据需要进行处理。
vtkNew<vtkRenderWindowInteractor> iren;
iren->SetRenderWindow(renWin);

// 默认情况下,vtkRenderWindowInteractor 会实例化一个 vtkInteractorStyle 实例。
// vtkInteractorStyle 会将它观察到的一组事件转化为对 vtkRenderWindow 中的相机、演员和/或属性的操作。
// 在这里,我们指定了一个特定的交互器样式。
vtkNew<vtkInteractorStyleTrackballCamera> style;
iren->SetInteractorStyle(style);

// 与之前的脚本不同,这里我们让事件循环持续运行。用户可以使用鼠标和键盘
// 根据当前的交互样式对场景进行操作。当用户按下 "e" 键时,默认情况下,
// vtkRenderWindowInteractor 会触发一个 ExitEvent,该事件会被捕捉并退出事件循环(通过随后调用的 Start() 方法触发)。
iren->Initialize();
iren->Start();
  • 创建了一个 vtkRenderWindowInteractor 对象 iren,它使得用户能够与渲染窗口进行交互。SetRenderWindow() 将窗口与交互器关联。
  • 创建了一个 vtkInteractorStyleTrackballCamera 对象 style,这是一个常用的交互样式,可以通过鼠标拖动来旋转、平移和缩放视图。并将交互器样式设置为 style,使得用户可以通过鼠标控制视角。
  • iren->Initialize() 初始化交互器。iren->Start() 启动交互,程序将在此处等待用户交互。

效果

image-20241220184447967

  用户可以通过鼠标拖动查看不同视角下的模型。

step6

  此示例介绍了 3D 小部件。3D 小部件利用了前面介绍的事件/观察者设计模式。它们通常在场景中具有特定的表示,可以使用鼠标和键盘进行交互选择和操作。当操作小部件时,它们依次调用 StartInteractionEvent、InteractionEvent 和 EndInteractionEvent 等事件,这些事件可用于操作小部件所嵌入的场景。3D 小部件在上一个示例中设置的事件循环上下文中工作。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace {

class vtkMyCallback : public vtkCommand
{
public:
static vtkMyCallback* New()
{
return new vtkMyCallback;
}
void Execute(vtkObject* caller, unsigned long, void*) override
{
vtkNew<vtkTransform> t;
auto widget = reinterpret_cast<vtkBoxWidget*>(caller);
widget->GetTransform(t);
widget->GetProp3D()->SetUserTransform(t);
}
};
}
  • void Execute(vtkObject* caller, unsigned long, void*)

    Execute 是重写自 vtkCommand 的虚函数。当事件触发时,这个方法会被调用。在这个回调函数中,具体的操作是对 vtkBoxWidget 进行变换的获取和设置。

    • vtkObject* caller
      • caller 是触发事件的对象。在这个回调中,它将被转换为 vtkBoxWidget* 类型。vtkBoxWidget 是一个交互式的控件,通常用于控制对象的变换(例如移动、旋转、缩放等)。
    • unsigned longvoid*
      • 这两个参数通常用于处理事件的 ID 和附加数据,但在这个回调函数中并没有被使用。
    • vtkNew<vtkTransform> t;
      • 创建了一个新的 vtkTransform 对象 t,该对象用于存储 3D 变换(如位置、旋转、缩放)。vtkTransform 是 VTK 用于表示几何变换(如平移、旋转、缩放等)的类。
    • auto widget = reinterpret_cast<vtkBoxWidget*>(caller);
      • 通过 reinterpret_castcaller 转换为 vtkBoxWidget* 类型。caller 是触发事件的对象,我们期望它是一个 vtkBoxWidget,因此需要进行类型转换。
    • widget->GetTransform(t);
      • 调用 vtkBoxWidgetGetTransform 方法,将当前 vtkBoxWidget 的变换信息(例如位置、旋转、缩放)存储到 t 变量中。
    • widget->GetProp3D()->SetUserTransform(t);
      • 获取 vtkBoxWidget 对象的 3D 属性(GetProp3D()),然后调用 SetUserTransform(t),将之前获取的变换应用到控件的 3D 属性上。
      • SetUserTransform 方法允许用户在现有的控件基础上应用额外的变换。
  • vtkBoxWidget 是一个用于交互式操作的 VTK 小部件,它允许用户通过鼠标拖动和操作一个 3D 的盒子(或矩形区域),来进行模型的变换。vtkBoxWidget 具有与这些操作相关的事件和回调机制,通过监听这些事件并响应,可以在 vtkActor 上实现这些变换。

  • vtkTransform 是 VTK 中用于存储和应用变换(如平移、旋转、缩放等)的类。在代码中,我们使用 vtkTransform 来存储和应用 vtkBoxWidget 获取到的变换。

    • widget->GetTransform(t);

      这行代码获取 vtkBoxWidget 当前的变换,并将其存储到 vtkTransform 对象 t 中。vtkBoxWidget 内部会跟踪用户通过交互对物体所做的变换(平移、缩放、旋转等)。GetTransform 会将这些变换信息提取出来。

    • widget->GetProp3D()->SetUserTransform(t);

      最后,通过 widget->GetProp3D() 获取与 vtkBoxWidget 关联的 vtkProp3D 对象(即需要变换的 3D 图形对象)。然后调用 SetUserTransform(t) 方法,应用之前从 vtkBoxWidget 中获取到的变换。SetUserTransform 允许将 vtkTransform 应用到 3D 对象上,使得对象的变换(平移、旋转、缩放)生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
vtkNew<vtkNamedColors> colors;

vtkNew<vtkConeSource> cone;
cone->SetHeight(3.0);
cone->SetRadius(1.0);
cone->SetResolution(10);

vtkNew<vtkPolyDataMapper> coneMapper;
coneMapper->SetInputConnection(cone->GetOutputPort());

vtkNew<vtkActor> coneActor;
coneActor->SetMapper(coneMapper);
coneActor->GetProperty()->SetColor(colors->GetColor3d("Bisque").GetData());

vtkNew<vtkRenderer> ren1;
ren1->AddActor(coneActor);
ren1->SetBackground(colors->GetColor3d("MidnightBlue").GetData());

vtkNew<vtkRenderWindow> renWin;
renWin->AddRenderer(ren1);
renWin->SetSize(300, 300);
renWin->SetWindowName("Tutorial_Step6");

vtkNew<vtkRenderWindowInteractor> iren;
iren->SetRenderWindow(renWin);

vtkNew<vtkInteractorStyleTrackballCamera> style;
iren->SetInteractorStyle(style);
  • 与之前一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
vtkNew<vtkBoxWidget> boxWidget;
boxWidget->SetInteractor(iren);
boxWidget->SetPlaceFactor(1.25);
boxWidget->GetOutlineProperty()->SetColor(colors->GetColor3d("Gold").GetData());

boxWidget->SetProp3D(coneActor);
boxWidget->PlaceWidget();
vtkNew<vtkMyCallback> callback;
boxWidget->AddObserver(vtkCommand::InteractionEvent, callback);

boxWidget->On();

iren->Initialize();
iren->Start();
  • vtkBoxWidget 是 VTK 中一个交互式小部件,允许用户通过拖动一个 3D 方框来变换场景中的对象。用户可以通过拖动盒子的边、角来平移、旋转和缩放物体。
  • boxWidget->SetInteractor(iren); 使得 vtkBoxWidget 能够与交互器 iren 关联,从而能够响应用户的鼠标输入。
  • boxWidget->SetPlaceFactor(1.25); 设置盒子小部件的缩放因子。这个因子影响了盒子的尺寸,通常是通过调整 vtkBoxWidget 形成的盒子与场景中物体的相对比例来控制。1.25 的值表示盒子在默认情况下稍微大于物体本身。
  • boxWidget->GetOutlineProperty()->SetColor(colors->GetColor3d("Gold").GetData()); 设置盒子轮廓的颜色为 “Gold”(金色)。GetOutlineProperty() 获取盒子的属性,之后设置颜色。
  • boxWidget->SetProp3D(coneActor);coneActor 设置为 vtkBoxWidget 的目标物体,意味着用户将通过拖动盒子来变换这个圆锥体 (coneActor)。
  • boxWidget->PlaceWidget(); 调用此方法后,vtkBoxWidget 会根据 coneActor 的边界自动设置盒子的位置和大小,使得盒子能够完全包围住物体。这样,用户就可以通过调整盒子的大小和位置来操作圆锥体。
  • vtkMyCallback 是前面我们分析过的自定义回调类,它会在用户与 vtkBoxWidget 交互时被触发。回调函数 Execute 会在盒子的小部件与用户交互时被调用,用于处理变换(平移、旋转、缩放)。
  • boxWidget->AddObserver(vtkCommand::InteractionEvent, callback); 将回调函数 callback 注册到 vtkBoxWidget 上。当用户进行交互时(如拖动、缩放、旋转盒子),vtkCommand::InteractionEvent 事件会触发,进而调用 vtkMyCallback::Execute 方法,更新 coneActor 的变换。
  • boxWidget->On(); 启用 vtkBoxWidget,使得它可以响应用户的交互。此时用户可以通过鼠标操作盒子,改变 coneActor 的变换。

效果

image-20241220222459509