本章的目的是为您提供可视化工具包系统的概述,并向您展示用c++, Java, Tcl和Python创建应用程序所需的基本信息。我们首先介绍基本的系统概念和对象模型抽象。我们将通过演示这些概念并描述构建应用程序所需了解的内容来结束本章。
3.1系统架构
Visualization Toolkit由两个基本子系统组成:一个编译的c++类库和一个“解释的”包装器层,该层允许您使用Java、Tcl和Java等语言操作编译的类
Python。如图3-1所示。
这种体系结构的优点是,您可以在编译后的c++语言中构建高效的(在CPU和内存使用方面)算法,并保留解释型语言的快速代码开发特性(避免编译/链接循环,简单但功能强大的工具,以及对GUI工具的访问)。当然,对于那些精通c++并拥有相应工具的人来说,应用程序可以完全用c++构建。
可视化工具箱是一个面向对象的系统。有效使用VTK的关键是对底层对象模型有一个很好的理解。这样做将消除围绕在系统中数百个对象的使用周围的神秘感。有了这样的理解,组合对象来构建应用程序就容易多了。你还需要了解系统中许多对象的能力;图3-1可视化工具包由编译(c++)核心组成,其中包含各种解释语言(Java, Tcl, Python)。解释包装器(Tcl, Java, Python)编译核心(c++) 示例和在线文档。在本用户指南中,我们试图为您提供有用的VTK对象组合,您可以根据自己的应用程序进行调整。
在本节的其余部分中,我们将介绍Visualization Toolkit的两个主要组件:可视化管道和呈现引擎。可视化管道用于获取或创建数据、处理数据,并将结果写入文件或将结果传递给呈现引擎以进行显示。呈现引擎负责创建数据的可视化表示。请注意,这些并不是VTK真正严格的架构组件,而是概念组件。本章中的讨论将是相当高级的,但是当您将其与本章和下一章中的具体示例以及VTK源代码发行版中的数百个可用示例结合起来时,您将对这些组件有很好的理解。
低级对象模型
VTK对象模型可以被认为是植根于超类vtkObject。几乎所有的VTK类都是从这个类派生的,或者在某些特殊情况下是从它的超类vtkObjectBase派生的。所有VTK必须使用对象的New()方法创建,并且必须使用对象的Delete()方法销毁。VTK对象不能在堆栈上分配,因为构造函数是受保护的方法。使用一个共同的超类和创建和销毁对象的统一方法,VTK能够提供几个基本的面向对象的操作。
引用计数。对象显式存储引用它们的指针数量的计数。当通过类的静态New()方法创建对象时,其初始引用计数为1,因为必须使用原始指针来引用新对象:
vtkObjectBase* obj = vtkExampleClass::New();
当创建或销毁对对象的其他引用时,使用Register()和UnRegister()方法增加或减少引用计数。通常这是由对象API中提供的各种“set”方法自动处理的:
otherObject->SetExample(obj);
引用计数现在是2,因为原始指针和存储在另一个对象中的指针都引用了它。当最初存储对象的原始指针不再需要时,使用Delete()方法删除引用:
obj->Delete();
从这一点开始,使用原始指针访问对象不再安全,因为指针不拥有对它的引用。为了确保正确管理对象引用,每次对New()的调用都必须与随后对Delete()的调用配对,以确保没有引用泄露。
一个“智能指针”的实现是由类模板vtkSmartPointer<>提供的,它简化了对象管理。上面的例子可以重写:
vtkSmartPointer obj = vtkSmartPointer::New(); otherObject->SetExample(obj);
在这种情况下,智能指针自动管理它拥有的引用。当智能指针变量超出作用域并且不再使用时,例如当它是局部变量的函数返回时,它会自动通过减少引用计数来通知对象。通过使用智能指针提供的静态New()方法,不需要原始指针保存对对象的引用,因此不需要调用Delete()。
运行时类型信息。在c++中,对象的实际类型可能不同于用来引用它的指针的类型。VTK的公共接口中的所有类都有简单的类名标识符(没有模板),因此字符串足以识别它们。VTK对象的类型可以在运行时通过GetClassName()方法获得:
const char* type = obj->GetClassName();
可以使用IsA()方法测试对象是特定类的实例还是其子类之一:
if(obj->IsA("vtkExampleClass")) { ... }
超类类型的指针可以使用派生类型的类提供的静态SafeDownCast()方法安全地转换为更派生的类型:
vtkExampleClass* example = vtkExampleClass::SafeDownCast(obj)
只有当对象确实是派生类型的实例时,这才会在运行时成功,否则将返回空指针。
对象状态显示。在调试时,显示对象当前状态的人类可读描述通常是有用的。这可以使用Print()方法获得VTK对象:
obj->Print(cout);
渲染引擎 /数据渲染
VTK渲染引擎由VTK中的类组成,这些类负责获取可视化管道的结果并将其显示到窗口中。这涉及到以下组件。请注意,这不是一个详尽的列表,而是渲染引擎中最常用的对象。这里使用的子标题是VTK中表示这种类型对象的最高级别的超类,在许多情况下,有多种选择,这些是定义跨实现功能的各种具体子类的基本API的抽象类。
vtkProp。场景中存在的数据的可见描述由vtkProp的子类表示。在3D中显示对象最常用的vtkProp子类是vtkActor(用于表示场景中的几何数据)和vtkVolume(用于表示场景中的体积数据)。还有一些道具可以在2D中表示数据,例如vtkActor2D。vtkProp子类通常负责知道它在场景中的位置、大小和方向。用于控制道具位置的参数通常取决于道具是场景中的3D对象还是2D注释。对于3D道具,如vtkActor和vtkVolume (vtkProp3D的两个子类,它本身是vtkProp的子类),您可以直接控制参数,如
对象的3D位置,方向和比例,或者您可以使用4x4变换矩阵。对于提供注释的2D道具,如vtkScalarBarActor,注释的大小和位置可以通过各种方式定义,包括指定相对于整个视口大小的位置、宽度和高度。除了提供放置控制之外,道具通常还有一个保存数据并知道如何呈现数据的mapper对象,以及一个控制诸如颜色和不透明度等参数的属性对象。
有大量(超过50)的专门道具,如vtkImageActor(用于显示图像)和vtkPieChartActor(用于创建数据数组的饼状图可视化表示)(a)图像数据(vtkImageData) (e)多边形数据(vtkPolyData) (c)结构化网格(vtkStructuredGrid) (f)非结构化网格(vtkStructuredGrid) (b)直线网格(vtkrecturedgrid) (d)非结构化点(使用vtkPolyData)图3-2 VTK中发现的数据集类型。请注意,非结构化点既可以用多边形数据表示,也可以用非结构化网格表示,因此在系统中没有显式表示。其中一些特殊的道具直接包含控制外观的参数,并直接引用要呈现的输入数据,因此不需要使用属性或映射器。vtkFollower道具是vtkActor的一个专门子类,它将自动更新其方向,以便持续面对指定的相机。这对于在3D场景中显示广告牌或文本并在用户旋转时保持可见非常有用。vtkLODActor也是vtkActor的一个子类,自动改变其几何表示,以保持交互帧率,vtkLODProp3D是vtkProp3D的一个子类,在许多不同的映射器(甚至可能是体积和几何映射器的混合物)之间进行选择,以提供交互性。vtkAssembly允许角色的层次结构,当层次结构被转换、旋转或缩放时,适当地管理转换。
vtkAbstractMapper。一些道具,如vtkActor和vtkVolume,使用vtkAbstractMapper的子类来保存对输入数据的引用,并提供实际的呈现功能。vtkPolyDataMapper是渲染多边形几何的主要映射器。对于体积对象,VTK提供了几种渲染技术,包括vtkFixedPointVolumeRayCastMapper,可用于渲染vtkImageData,以及vtkProjectedTetrahedra mapper,可用于渲染vtkUnstructuredGrid数据。
vtkProperty和vtkVolumeProperty。一些道具使用单独的属性对象来保存控制数据外观的各种参数。这使您可以更轻松地在场景中的不同对象之间共享外观设置。vtkActor对象使用vtkProperty来存储参数,如颜色,不透明度,以及材料的环境,漫反射和高光系数。相反,vtkVolume对象使用vtkVolumeProperty来捕获适用于volumetric对象的参数,例如将标量值映射到颜色和不透明度的传递函数。许多映射器还提供了设置裁剪平面的功能,可以用来显示内部结构。
vtkCamera。vtkCamera包含控制您如何查看场景的参数。vtkCamera有一个位置、一个焦点和一个定义场景中“向上”方向的矢量。其他参数控制特定的观看变换(平行或透视),图像的比例或视角,以及视锥的近裁剪面和远裁剪面。
vtkLight。当为场景计算照明时,需要一个或多个vtkLight对象。vtkLight对象存储光的位置和方向,以及颜色和强度。光也有一个类型,描述了光将如何移动相对于相机。例如,前灯总是位于相机的位置并照射在相机的焦点上,而SceneLight则位于场景中的固定位置。
vtkRenderer。组成场景的对象包括道具,相机和灯光在vtkRenderer中收集在一起。vtkRenderer负责管理场景的渲染过程。多个vtkRenderer对象可以在单个vtkRenderWindow中一起使用。这些渲染器可以渲染到渲染窗口的不同矩形区域(称为视口),或者可能重叠。
vtkRenderWindow。vtkRenderWindow提供了操作系统和VTK渲染引擎之间的连接。vtkRenderWindow的平台特定子类负责在计算机上的本机窗口系统中打开窗口并管理显示进程。当您使用VTK开发时,您只需使用与平台无关的vtkRenderWindow,它会在运行时自动替换为正确的特定于平台的子类。vtkRenderWindow包含vtkRenderers的集合,以及控制渲染功能的参数,如立体声,抗混叠,运动模糊和焦点深度。
vtkRenderWindowInteractor。vtkRenderWindowInteractor负责处理鼠标、键和计时器事件,并通过VTK的命令/观察者设计模式实现这些事件。vtkInteractorStyle监听这些事件并处理它们,以便提供旋转、平移和缩放等运动控制。vtkRenderWindowInteractor自动创建一个默认的交互器样式,适用于3D场景,但你可以选择一个用于2D图像查看,或者创建你自己的自定义交互器样式。
vtkTransform。场景中许多需要放置的对象,如道具、灯光和相机,都有一个vtkTransform参数,可以用来轻松地操纵对象的位置和方向。vtkTransform可以用来描述三维空间中线性(也称为仿射)坐标变换的全部范围,其内部表示为4x4齐次变换矩阵。vtkTransform对象将从默认的单位矩阵开始,或者您可以以管道方式将转换链在一起以创建复杂的行为。管道机制保证,如果您修改管道中的任何转换,所有后续转换都会相应地更新。
vtkLookupTable、vtkColorTransferFunction、vtkPiecewiseFunction。可视化标量数据通常涉及定义从标量值到颜色和不透明度的映射。这在几何表面渲染中都是正确的,其中不透明度将定义表面的半透明,以及在体渲染中,不透明度将表示沿着穿过体的光线长度累积的不透明度。对于几何渲染,这个映射通常是使用vtkLookupTable创建的,而在体渲染中,vtkColorTransferFunction和vtkPiecewiseFunction都将被利用。
一个最小的例子。下面的例子(改编自。/VTK/Examples/Rendering/Cxx/Cylinder.cxx)展示了如何使用这些对象来指定和渲染场景。
vtkCylinderSource *cylinder = vtkCylinderSource::New();
vtkPolyDataMapper *cylinderMapper = vtkPolyDataMapper::New();
cylinderMapper->SetInputConnection(cylinder->GetOutputPort());
vtkActor *cylinderActor = vtkActor::New();
cylinderActor->SetMapper(cylinderMapper);
vtkRenderer *ren1 = vtkRenderer::New();
ren1->AddActor(cylinderActor);
vtkRenderWindow *renWin = vtkRenderWindow::New();
renWin->AddRenderer(ren1);
vtkRenderWindowInteractor *iren = vtkRenderWindowInteractor::New();
iren->SetRenderWindow(renWin);
renWin->Render();
iren->Start();
在这个例子中,我们直接创建了vtkActor, vtkPolyDataMapper, vtkRenderer, vtkRenderWindow和vtkRenderWindowInteractor。注意,vtkProperty是由actor自动创建的,vtkLight和vtkCamera是由vtkRenderer自动创建的。
可视化管道
VTK中的可视化管道可用于读取或创建数据,分析和创建此数据的派生版本,并将数据写入磁盘或将其传递给呈现引擎进行显示。例如,您可以从磁盘读取一个3D数据卷,对其进行处理以创建一组三角形,通过该卷表示等值表面,然后将该几何对象写回磁盘。或者,您可以创建一组球体和圆柱体来表示原子和键,然后将它们传递给呈现引擎进行显示。
可视化工具包使用数据流方法将信息转换为图形数据。这种方法涉及两种基本类型的对象。
•vtkDataObject
•vtkAlgorithm
数据对象表示各种类型的数据。类vtkDataObject可以被看作是一个通用的数据“blob”。具有正式结构的数据被称为数据集(类vtkDataSet)。图3 - 2显示了VTK支持的数据集对象。数据集由几何和拓扑结构(点和单元)组成,如图所示;它们还具有相关的属性数据,如标量或向量。属性数据可以与数据集的点或单元相关联。细胞是点的拓扑组织;单元格构成数据集的原子,用于在点之间插入信息。图19-20和图19-21显示了VTK支持的23种最常见的单元格类型。VTK支持的属性数据如图3-3所示。
算法(通常也称为过滤器)对数据对象进行操作以产生新的数据对象。算法和数据对象连接在一起,形成可视化管道(即数据流网络)。图3-4是可视化管道的描述。
此图与图3-5一起说明了一些重要的可视化概念。源算法通过读取(读取器对象)或构造一个或多个数据对象(过程源对象)来生成数据。过滤器摄取一个或多个数据对象,并在输出时生成一个或多个数据对象。映射器(或者在某些情况下,是专门的参与者)获取数据并将其转换为呈现引擎显示的可视化表示。可以将写入器视为一种映射器,它将数据写入文件或流。
关于可视化管道的构造有几个重要的问题,我们将在这里简要介绍一下。首先,使用
aFilter->SetInputConnection( anotherFilter->GetOutputPort() );
它将过滤器filter的输入设置为过滤器anotherFilter的输出。(具有多个输入和输出的过滤器也有类似的方法。)其次,我们必须有一个机制来控制管道的执行。我们只想执行使输出更新所需的管道部分。可视化工具包使用延迟计算方案(只执行)
(当请求数据时)基于每个对象的内部修改时间。第三,管道的组装要求只有那些彼此兼容的对象才能与SetInputConnection()和GetOutputPort()方法组合在一起。如果数据对象类型不兼容,VTK会在运行时产生错误。最后,我们必须决定在管道执行后是缓存还是保留数据对象。由于可视化数据集通常非常大,这对于可视化工具的成功应用非常重要。VTK提供了打开和关闭数据缓存的方法,使用引用计数来避免复制数据,如果整个数据集不能保存在内存中,则可以将数据分段流式传输。(我们建议您查看可视化工具箱中的可视化管道章节:面向对象的3D图形文本方法。)
请注意,算法和数据对象都有很多种。图16-2显示了当前版本VTK支持的六种最常见的数据对象类型。算法对象在输入数据和输出数据的类型上有所不同,当然在实现的特定算法上也有所不同。
管道执行。在前一节中,我们讨论了控制可视化管道执行的需要。在本节中,我们将扩展对管道执行的一些关键概念的理解。
如前一节所述,VTK可视化管道仅在需要数据进行计算(延迟求值)时执行。考虑这个例子,我们实例化了一个阅读器对象,并请求如下所示的点数。(这里显示的语言是Tcl。)
vtkPLOT3DReader reader
reader SetXYZFileName $VTK_DATA_ROOT/Data/combxyz.bin
[reader GetOutput] GetNumberOfPoints
reader对象将从GetNumberOfPoints()方法调用返回“0”,尽管数据文件包含数千个点。但是,如果您添加Update()方法
reader Update
[reader GetOutput] GetNumberOfPoints
阅读器对象将返回正确的数字。在第一个示例中,GetNumberOfPoints()方法不需要计算,对象只是返回当前的点数,即“0”。在第二个示例中,Update()方法强制执行管道,从而强制读取器执行并从指定的文件中读取数据。一旦阅读器执行完毕,其输出中的点数就被正确设置。
通常,您不需要手动调用Update(),因为过滤器连接到可视化管道中。在这种情况下,当参与者接收到呈现自己的请求时,它将该方法转发给它的映射器,并且Update()方法通过可视化管道自动发送。管道执行的高级视图如图3-6所示。如图所示,Render()方法经常发起数据请求;然后,该请求通过管道向上传递。根据管道的哪些部分是过期的,管道中的过滤器可以重新执行,从而使管道末端的数据是最新的;然后由参与者呈现最新的数据。(有关执行过程的更多信息,请参见第15章“管理管道执行”。)
图像处理。VTK支持一套广泛的图像处理和体积渲染功能。在VTK中,2D(图像)和3D(体积)数据都被称为vtkImageData。VTK中的图像数据集是其中数据以规则的、轴对齐的数组排列的数据集。图像、像素图和位图是2D图像数据集的示例;volumes(一堆2D图像)是一个3D图像数据集。
成像流水线中的算法总是输入和输出图像数据对象。由于数据的规则性和简单性,成像管道还具有其他重要特征。体绘制用于可视化3D vtkImageData(参见第139页的“体绘制”),特殊的图像查看器用于查看2D vtkImageData。几乎所有成像流水线中的算法都是多线程的,并且能够将数据分段流式传输以满足用户指定的内存限制。过滤器自动感知系统上可用的内核和处理器的数量,并创建图3-6流水线执行的概念概述。Source Filter Mapper Actor Update()方法的方向数据流的方向(通过算法RequestData()方法生成的数据)Render()线程,以及自动将数据分成流经管道的流。(参见第325页的“vtkStreamingDemandDrivenPipeline”了解更多信息。)
这就是我们对可视化工具箱系统体系结构的简要概述。我们推荐《可视化工具箱——面向对象的3D图形方法》一书,以了解VTK中许多算法的更多细节。以身作则是另一种有用的方法。第4章到第13章包含许多注释示例,演示了VTK的各种功能。此外,由于源代码是可用的,您可能希望研究在VTK源代码树的VTK/ examples目录中找到的示例。
有了这个简短的介绍,让我们看看用c++、Tcl、Java和Python创建应用程序的方法
3.2创建应用
介绍使用Tcl、c++、Java和Python四种编程语言开发VTK应用程序所需的基本信息。在阅读了这篇介绍之后,您应该跳到讨论您感兴趣的语言的小节。除了向您提供如何创建和运行简单应用程序的说明外,每个部分还将向您展示如何利用该语言中的回调。
用户方法、观察者和命令
回调(或用户方法)在VTK中使用主体/观察者和命令设计模式实现。这意味着几乎VTK中的每个类(vtkObject的每个子类)都有一个AddObserver()方法,可以用来设置VTK的回调。观察者查看对象上调用的每个事件,如果它与观察者正在观察的事件之一匹配,则调用一个相关的命令(即回调)。例如,所有VTK过滤器在开始执行之前都会调用一个StartEvent。如果你添加一个观察者来监视StartEvent,那么每次过滤器开始执行时,它都会被调用。考虑下面的Tcl脚本,它创建了一个vtkElevationFilter的实例,并为StartEvent添加了一个观察者来调用PrintStatus过程。
proc PrintStatus (){
puts "Starting to execute the elevation filter"
}
vtkElevationFilter foo
foo AddObserver StartEvent PrintStatus
这种类型的功能(即回调)在VTK支持的所有语言中都可用。接下来的每一节都将展示一个如何使用它的简短示例。在第421页的“与窗口系统集成”中提供了关于用户方法的进一步讨论。(本节还讨论了用户界面集成问题。)
要创建自己的应用程序,我们建议从VTK附带的一个示例开始。它们可以在源代码分发版的VTK/Examples中找到。在源代码分布中,示例首先按主题组织,然后按语言组织。在VTK/Examples下,你会找到不同主题的目录,在目录下,会有不同语言的子目录,比如Tcl。
Tcl
Tcl是开始创建VTK应用程序的最简单的语言之一。一旦安装了VTK,就应该能够运行该发行版附带的Tcl示例。在UNIX下,你必须像第14页“在UNIX系统上安装VTK”中提到的那样,用Tcl支持编译VTK。在Windows下,你可以像第10页“在Windows XP, Vista或更高版本上安装VTK”中所描述的那样安装自解压缩文件。
Windows。在Windows下,只需双击文件(Cone. xml)就可以运行Tcl脚本。TCL(本例中为TCL)。如果什么都没有发生,您可能在脚本中出现错误,或者在将Tcl文件与vtk.exe可执行文件关联时出现问题。要检测这个,你需要先运行vtk.exe。VTK. exe可以在VTK下的开始菜单中找到。执行开始后,应该出现一个控制台窗口,其中有一个提示符。在此提示符下键入cd命令以更改到Cone所在的目录。TCL定位。下面给出了两个例子:
% cd "c:/VTK/Examples/Tutorial/Step1/Tcl"
然后,您需要使用以下命令获取示例脚本的源代码。
% source Cone.tcl
Tcl将尝试执行Cone。Tcl,您将能够看到错误或警告消息,否则将不会出现。
Unix。在UNIX下,Tcl开发可以通过运行二进制目录(例如,VTK-bin/bin/ VTK, VTKSolaris/bin/ VTK等)中可以找到的VTK可执行文件(在编译源代码之后)来完成,然后提供Tcl脚本作为第一个参数,如下所示。
unix machine> cd VTK/Examples/Tutorial/Step1/Tcl
unix machine> /home/VTK-Solaris/bin/vtk Cone.tcl
可以按照本节介绍部分所示的方式设置用户方法。中可以找到一个例子
例子/教程步骤2 / Tcl / Cone2.tcl。关键的更改如下所示。
proc myCallback {} {
puts "Starting to render"
}
vtkRenderer ren1 ren1
AddObserver StartEvent myCallback
您可以直接将过程体提供给AddObserver()。
vtkRenderer ren1
ren1 AddObserver StartEvent {puts "Starting to render"}
C++
与大多数其他语言相比,使用c++作为开发语言通常会产生更小、更快、更容易部署的应用程序。c++开发还有一个优点,即您不需要编译任何对Tcl、Java或Python的额外支持。本节将向您展示如何使用Microsoft Visual c++为PC创建一个简单的VTK c++应用程序,也可以使用适当的编译器为UNIX创建一个VTK c++应用程序。我们将从一个名为Cone的简单示例开始。cxx可以在Examples/Tutorial/Step1/ cxx中找到。对于Windows和UNIX,您可以使用VTK的源代码安装或已安装的二进制文件。这些示例将同时适用于这两种情况。
构建c++程序的第一步是使用CMake生成makefile或workspace文件,具体取决于编译器。Cone附带的CMakeList.txt文件。cxx(如下所示)使用了FindVTK和UseVTK CMake模块。这些模块试图定位VTK,然后为构建c++程序设置包含路径和链接行。如果他们没有成功找到VTK,您将不得不手动指定适当的CMake参数,并在必要时重新运行CMake。
PROJECT (Step1)
FIND_PACKAGE(VTK REQUIRED)
IF(NOT VTK_USE_RENDERING)
MESSAGE(FATAL_ERROR
"Example ${PROJECT_NAME} requires VTK_USE_RENDERING.")
ENDIF(NOT VTK_USE_RENDERING)
INCLUDE(${VTK_USE_FILE})
ADD_EXECUTABLE(Cone Cone.cxx)
TARGET_LINK_LIBRARIES(Cone vtkRendering)
Microsoft Visual c++。一旦你为Cone的例子运行了CMake,你就准备好启动microsoftvisualc++并加载生成的解决方案文件。对于当前. net版本的编译,它将被命名为cone .sln.您现在可以选择一个构建类型(例如Release或Debug)并构建您的应用程序。如果您想将VTK集成到不使用CMake的现有项目中,您可以将这个简单示例中的设置复制到您现有的工作区中。
现在考虑一个真正的Windows应用程序的示例。这个过程与我们上面所做的非常相似,除了我们创建的是一个windows应用程序而不是控制台应用程序,如下所示。大部分代码是标准的Windows代码,对于任何Windows开发人员来说都很熟悉。这个例子可以在VTK/Examples/GUI/Win32/SimpleCxx/ Win32Cone.cxx中找到。请注意,对CMakeLists.txt文件的唯一重要更改是在ADD_EXECUTABLE命令中添加了WIN32参数。
#include "windows.h"
#include "vtkConeSource.h"
#include "vtkPolyDataMapper.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
static HANDLE hinst;
long FAR PASCAL WndProc(HWND, UINT, UINT, LONG);
// define the vtk part as a simple c++ class
class myVTKApp
{
public:
myVTKApp(HWND parent);
~myVTKApp();
private:
vtkRenderWindow *renWin;
vtkRenderer *renderer;
vtkRenderWindowInteractor *iren;
vtkConeSource *cone;
vtkPolyDataMapper *coneMapper;
vtkActor *coneActor;
};
我们首先包含所需的VTK包含文件。接下来,我们有两个标准的窗口原型,然后是一个名为myVTKApp的小类定义。在使用c++进行开发时,应该尝试使用面向对象的方法,而不是在许多Tcl示例中发现的脚本编程风格。这里我们将应用程序的VTK组件封装到一个小类中。
这是myVTKApp的构造函数。正如您所看到的,它分配所需的VTK对象,设置它们的实例变量,然后将它们连接起来形成可视化管道。除了vtkRenderWindow之外,大部分都是直接的VTK代码。这个构造函数接受父窗口的HWND句柄,父窗口应该包含VTK渲染窗口。然后我们在vtkRenderWindow的SetParentId()方法中使用它,这样它就会创建自己的窗口,作为传递给构造函数的窗口的子窗口。
myVTKApp::myVTKApp(HWND hwnd)
{
// Similar to Examples/Tutorial/Step1/Cxx/Cone.cxx
// We create the basic parts of a pipeline and connect them
this->renderer = vtkRenderer::New();
this->renWin = vtkRenderWindow::New();
this->renWin->AddRenderer(this->renderer);
// setup the parent window
this->renWin->SetParentId(hwnd);
this->iren = vtkRenderWindowInteractor::New();
this->iren->SetRenderWindow(this->renWin);
this->cone = vtkConeSource::New();
this->cone->SetHeight( 3.0 );
this->cone->SetRadius( 1.0 );
this->cone->SetResolution( 10 );
this->coneMapper = vtkPolyDataMapper::New();
this->coneMapper->SetInputConnection(this->cone->GetOutputPort());
this->coneActor = vtkActor::New();
this->coneActor->SetMapper(this->coneMapper);
this->renderer->AddActor(this->coneActor);
this->renderer->SetBackground(0.2,0.4,0.3);
this->renWin->SetSize(400,400);
// Finally we start the interactor so that event will be handled
this->renWin->Render();
}
析构函数只是释放构造函数中分配的所有VTK对象。
myVTKApp::~myVTKApp()
{
renWin->Delete();
renderer->Delete();
iren->Delete();
cone->Delete();
coneMapper->Delete();
coneActor->Delete();
}
这里的WinMain代码都是标准的windows代码,没有VTK引用。如您所见,应用程序控制了事件循环。事件由本节后面描述的WndProc处理。
int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdParam, int nCmdShow)
{
static char szAppName[] = "Win32Cone";
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
if (!hPrevInstance)
{
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
wndclass.lpszMenuName = NULL;
wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wndclass.lpszClassName = szAppName;
RegisterClass (&wndclass);
}
hinst = hInstance;
hwnd = CreateWindow ( szAppName,
"Draw Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
400,
480,
NULL,
NULL,
hInstance,
NULL);
ShowWindow (hwnd, nCmdShow);
UpdateWindow (hwnd);
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
return msg.wParam;
}
这个windowproc是一个非常简单的事件处理程序。对于一个完整的应用程序来说,这要复杂得多,但关键的集成问题是相同的。在这个函数的顶部,我们声明了一个对myVTKApp实例的静态引用。当处理WM_CREATE方法时,我们创建一个退出按钮,然后构造一个myVTKApp的实例,将句柄传递给当前窗口。vtkrenderwindowwinteractor将处理vtkRenderWindow的所有事件,所以你不需要在这里处理它们。您可能需要添加代码来处理调整大小事件,以便呈现窗口根据您的整个用户界面适当地调整大小。如果你没有设置vtkRenderWindow的ParentId,它将显示为一个顶层独立的窗口。其他的一切都应该和以前一样。
long FAR PASCAL WndProc (HWND hwnd, UINT message,
UINT wParam, LONG lParam)
{
static HWND ewin;
static myVTKApp *theVTKApp;
switch (message)
{
case WM_CREATE:
{
ewin = CreateWindow("button","Exit",
WS_CHILD | WS_VISIBLE | SS_CENTER,
0,400,400,60,
hwnd,(HMENU)2,
(HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE),
NULL);
theVTKApp = new myVTKApp(hwnd);
return 0;
}
case WM_COMMAND:
switch (wParam)
{
case 2:
PostQuitMessage (0);
if (theVTKApp)
{
delete theVTKApp;
theVTKApp = NULL;
}
break;
}
return 0;
case WM_DESTROY:
PostQuitMessage (0);
if (theVTKApp)
{
delete theVTKApp;
theVTKApp = NULL;
}
return 0;
}
return DefWindowProc (hwnd, message, wParam, lParam);
}
UNIX。在UNIX上创建一个c++应用程序是通过运行CMake和make来完成的。CMake创建一个makefile来指定包含路径、链接行和依赖项。然后,make程序使用这个make文件来编译应用程序。这将产生一个您可以运行的Cone可执行文件。如果锥。CXX不编译,然后检查错误并改正。确保CMakeCache.txt顶部的值是有效的。如果它确实可以编译,但是当您尝试运行它时收到错误,那么您可能需要设置LD_LIBRARY_PATH,如第2章所述。
c++中的用户方法。通过创建覆盖Execute()方法的vtkCommand的子类,可以在c++中添加用户方法(使用观察者/命令设计模式)。考虑下面的例子取自VTK/Examples/Tutorial/Step2/Cxx/Cone2.cxx。
class vtkMyCallback : public vtkCommand {
static myCallback *New() {return new vtkMyCallback;}
virtual void Execute(vtkObject *caller, unsigned long, void *)
{
vtkRenderer *renderer = reinterpret_cast<vtkRenderer*>(caller);
cout << renderer->GetActiveCamera()->GetPosition()[0] << " "
<< renderer->GetActiveCamera()->GetPosition()[1] << " "
<< renderer->GetActiveCamera()->GetPosition()[2] << "\n";
}
};
虽然Execute()方法总是传递给调用对象(调用者),但您不需要使用它。
如果使用调用者,通常需要对实际类型执行SafeDownCast()。例如:
virtual void Execute(vtkObject *caller, unsigned long, void *callData)
{
vtkRenderer *ren = vtkRenderer::SafeDownCast(caller);
if (ren) { ren->SetBackground(0.2,0.3,0.4); }
}
一旦你创建了vtkCommand的子类,你就可以添加一个观察者,它将在某些事件上调用你的命令。可以这样做。
// Here is where we setup the observer,
//we do a new and ren1 will eventually free the observer
vtkMyCallback *mo1 = vtkMyCallback::New();
ren1->AddObserver(vtkCommand::StartEvent,mo1);
mo1->Delete();
上面的代码创建了myCallback的实例,然后在ren1上为myCallback添加了一个观察者
StartEvent。每当ren1开始渲染时,将调用vtkMyCallback的Execute()方法。当ren1被删除时,回调也将被删除。
Java
要创建Java应用程序,首先必须有一个可工作的Java开发环境。本节提供在Windows或UNIX上使用Sun的JDK 1.3或更高版本的说明。一旦你安装了JDK和VTK,你需要设置CLASSPATH环境变量来包含VTK类。在Microsoft Windows下,可以通过右键单击“我的电脑”图标,选择属性选项,然后选择“高级”选项卡,然后单击“环境变量”按钮来设置。然后添加一个CLASSPATH环境变量,并将其设置为包含vtk.jar文件、包装/Java目录和当前目录的路径。对于Windows版本,它将类似于“C:\vtk-bin\bin\vtk.jar;C:\vtkbin\ wrapped \Java;.”。在UNIX下,您应该将CLASSPATH环境变量设置为类似于“/yourdisk/vtk-bin/bin/vtk.jar;/yourdisk/vtk-bin/ wrapped / Java;”的内容。
下一步是对Java程序进行字节编译。对于初学者来说,尝试字节编译(使用javac)在VTK/Examples/Tutorial/Step1/Java下的VTK附带的Cone.java示例。然后,您应该能够使用java命令运行生成的应用程序。它应该显示一个旋转360度然后退出的圆锥体。下一步是使用提供的示例作为起点创建您自己的应用程序
public void myCallback()
{
System.out.println("Starting a render");
}
通过传递三个参数来设置回调。第一个是你感兴趣的事件的名称,第二个是类的实例,第三个是你想调用的方法的名称。在这个例子中,我们设置了StartEvent来调用myCallback方法
Cone2)。myCallback方法当然必须是Cone2的有效方法,以避免错误。(此代码片段来自VTK/Examples/Tutorial/Step2/Java/cone2.java)
Cone2 me = new Cone2();
ren1.AddObserver("StartEvent",me,"myCallback");
Python
如果您使用Python支持构建VTK,将创建一个vtkpython可执行文件。使用此可执行文件,您应该能够运行Examples/Tutorial/Step1/Python/Cone.py,如下所示。
vtkpython Cone.py
使用我们的一些示例脚本作为起点,创建自己的Python脚本非常简单。用户方法可以通过定义一个函数,然后将其作为参数传递给
AddObserver如下所示。
def myCallback(obj,event):
print "Starting to render"
ren1.AddObserver("StartEvent",myCallback)
上面所示示例的完整源代码位于VTK/Examples/Tutorial/Step2/
Python / Cone2.py。
3.3语言间转换
正如我们所看到的,VTK的核心是用c++实现的,然后用Tcl、Java和Python编程语言包装。这意味着在开发应用程序时可以选择一种语言。您的选择取决于您最熟悉的语言、应用程序的性质,以及您是否需要访问内部数据结构和/或有特殊的性能要求。当您需要访问内部数据结构或需要尽可能高性能的应用程序时,c++比其他语言提供了几个优势。然而,使用c++意味着编译/链接周期的额外负担,这通常会减慢软件开发过程。您可能会发现自己使用解释性语言(如Tcl)开发原型,然后将其转换为c++。或者,您可能会发现希望转换为实现语言的示例代码(在VTK发行版中或来自其他用户)。将VTK代码从一种语言转换为另一种语言相当简单。不同语言的类名和方法名保持相同;实现细节和GUI界面(如果有的话)有哪些变化。例如,c++语句
anActor->GetProperty()->SetColor(red,green,blue);
在Tcl中变为
[anActor GetProperty] SetColor $red $green $blue
在Java中变成
anActor.GetProperty().SetColor(red,green,blue);
在Python中变成
anActor.GetProperty().SetColor(red,green,blue)
您将发现的一个主要限制是,由于指针操作,一些c++应用程序无法转换为其他三种语言。虽然总是可以从包装语言中获取和设置单独的值,但并不总是可以获得一个原始指针来快速遍历、检查或修改大型结构。如果您的应用程序需要这种级别的数据检查或操作,您可以直接在c++中开发或在c++级别使用所需的高性能类扩展VTK,然后使用您首选的解释语言中的这些新类。
本书为英文翻译而来,供学习vtk.js的人参考。