重庆交通大学2012届毕业设计?毕业论文
在VTK的管道流中,可视化对象通过映射器映射成为图形对象再由计算机硬件进行绘制,而可视化数据本身是有层次(维度)分别的。对于空间实体的网格,由于其几何与拓扑的复杂性,根本不可能找到一种通用的手段直接就能构建出任一个实体网格。因而通常的做法是先由点连线,由线组成面,最后再由面来围成一个空间实体。众多的空间实体聚在一起就形成了空间网格。这样的做法并不是最优的,因为它存在对于某些表面的重复构造,当网格数据庞大的时候,代码的执行效率是很低的。但这种思路提供了一种重要的体绘制方法,通过分解各单元的维度再重构的方式来绘制空间实体。本课题的程序设计采用了这一种方法:线单元由两个点来唯一确定;平面三角形单元由三个点来唯一的构成;四面体单元则是由四个三角形表面围成,每一个三角形表面可以看做构成空间四面体的基本单元,称为一个元组(Tuple),它由三个节点来唯一构成;六面体单元由6个四边形面(Tuple)构成,每个四边形仅包含8个节点中的4个,并不是每个节点间都是有(直线)联系的,因而对于六面体单元来说,仅仅知道8个节点的编号是不够的,还需要知道它每个面包含了哪些节点——即元组的构成,才能按照上述思路来构建;最后,对于五面体单元的情形就复杂一些,可将它看成由2个三角形面和3个四边形面围成的空间实体单元。
综上所述,空间实体单元的形成并不是空间区域的填充,而是通过节点形成的表面(也即是“元组”)来围成的,所以在四面体单元类ElementVolume_Tet、五面体单元类ElementVolume_Wedge和六面体单元类ElementVolume_Hex中除了指明单元所具有的节点编号外,还应知道实体单元的各个表面都各自对应了哪些节点。
以四面体单元类ElementVolume_Tet为例,其头文件中除了有类似ElementLine中的基本成员外,还增加了如下对于元组(Tuple)描述的代码:
//ElementVolume_Tet.h #pragma once
class ElementVolume_Tet { public: …//其余成员略
int** GetSurfaceWithNode();//获取各表面对应的节点编号
protected:
void SetSurfaceWithNode();//设置各表面对应的节点编号
private:
…//其余数据成员略
19
薛健:基于VTK的有限元网格可视化研究及软件设计
};
int m_surface[4][3];//记录每个表面对应的节点号 int *p[4];//该指针数组用于提取m_surface[4][3]的行
整型数组m_surface[4][3]的行代表四面体单元表面编号,列代表节点编号,它用来记录元组(表面)的信息。指针数组p[4]用来提取m_surface[4][3]的行。
在这个类中,增加了一个重要的成员函数void SetSurfaceWithNode()来设置四面体各表面对应的节点编号(即元组的结构),在它的实现中将指针数组p的各指针元素指向了m_surface对应的行。各指针的地址值通过函数int** GetSurfaceWithNode()由数组p返回,这样也就获得了元组及其相应节点的地址信息,通过对地址的访问也就可以获得元组节点编号的信息。当创建一个四面体对象的实例时,表明它的每个表面与对应的节点编号及坐标也唯一地确定下来;在每次重置了节点编号后,也需要及时地将编号信息绑定到对应的表面上去,才算形成了一个有意义的四面体单元。因此,对应的,在构造函数、复制构造函数、设置节点编号的各个重载函数中均需要对函数SetSurfaceWithNode()进行调用,而类的对象则是不需要调用它来设置元组信息的,故将函数SetSurfaceWithNode()设计为四面体单元类ElementVolume_Tet的保护成员,适当地封装起来。
与此类似地,六面体单元具有6个表面,每个表面包含4个节点,因此在六面体单元类ElementVolume_Hex中将其元组信息存放在数组int m_surface[6][4]中,而用数组指针int *p[6]来提取m_surface的各个行。而对于五面体单元,由于其包含两种类型的表面,因而在ElementVolume_Wedge中将其元组信息分别存放在数组m_surface1[2][3]和m_surface2[3][4]中,用数组指针*p[5]来提取m_surface1和m_surface1的各行。通过这样的方式就对空间实体单元有了一个详尽的描述。
ElementVolume_Hex、ElementVolume_Wedge的其余成员与ElementVolume_Tet类似。
3.2 数据文件的读入
3.2.1 网格数据的读入种类、控制与特点
依据不同的绘图功能要求以及算法实现,本课题程序共有四种数据需要从文本文件中选择性地读入:节点数据(编号、坐标)、二维Delaunay边界点数据、节点位移数据(编号、位移分量u、v、w)以及单元数据(单元编号、节点编号)。
节点数据是离散点集的绘制和各种单元类型的绘制必须读入的,它是整个有限元网
20
重庆交通大学2012届毕业设计?毕业论文
格的基础,也是VTK中几何属性的重要表达;二维Delaunay边界点数据在二维约束Delaunay剖分中用来确定网格区域的边界,在进行二维约束Delaunay剖分时读入;节点位移数据可反映网格的变形,在需要绘制网格的变形图时读入;各种单元数据则在进行相应的单元类型的绘制显示时读入,这些单元类型包括:线单元、三角形单元、四面体单元、五面体单元、六面体单元。
在程序中,由三个整形变量作为标志来控制数据的读入:flag0、flag_del、flag_uvw。 flag0控制读入的单元类型(节点数据必读)以及进行何种绘制。当flag0=0时,仅读入离散点集进行绘制;当flag0=1时,读入线单元模型数据进行绘制;当flag0=2时,读入三角形单元模型进行绘制;当flag0=3时,读入四面体单元模型进行绘制;当flag0=4时读入五面体单元模型进行绘制;当flag0=5时,读入六面体单元模型进行绘制;当flag0=6时,读入二维Delaunay三角网格数据(点集)。
flag_del控制Delaunay三角剖分的类型:当flag_del=0是,仅读入离散点集数据(节点)进行离散点集的Delaunay三角剖分,剖分结果为Delaunay三角形网格的单连通凸区域;当flag_del=1时,进行约束边界的Delaunay三角剖分,此时除了读入节点数据外,还需要读入边界点数据。需要说明的是,在进行二维Delaunay三角剖分时用到了vtkDelaunay2D这个类,它在进行网格划分时是利用边界点为网格节点的,当插入非边界节点时将出现一些问题,这将在第4.9.6节讨论。但在flag_del=1时仍然将非边界节点数据读入了进来。
flag_uvw控制位移数据的读入:当flag_uvw=0时,不读入位移数据;当flag_uvw=1时读入节点位移数据以用于进行网格变形图的绘制。
由于有限元模型的复杂程度不同,网格划分的方式也会随之而不同;即便对于同样的模型,不同的计算要求下网格划分的精度、单元类型的选择也可能会不一样,节点个数、单元个数以及单元类型可能就存在很大的差别。这就导致了一个问题:读入文件时事先并不知道有多少个节点或单元的数据会被读入。这样,也就不能像通常所做的那样,定义一个常规的数组或类对象数组来接收数据,因为数组的大小无法在定义时给出。
第3.2.2小节至第3.2.5小节将针对上述问题对数据文件的读入操作以及数据文本的组织作一些说明,所设计的方法较容易实现但并不是最优的,一些不足之处在第3.2.2小节中也会加以说明。 3.2.2 节点编号与坐标的读入
以读入离散点集为例,说明打开节点数据文件读入数据的方法。 通过以下方式来打开数据文件:
21
薛健:基于VTK的有限元网格可视化研究及软件设计
if(flag0==0){//离散点集
err0=fopen_s(&fp0,”Node_1.txt”,”rt”);
fp1=NULL; err1=NULL; fpb=NULL; errb=NULL; if(err0!=NULL){
AfxMessageBox(“Cannot open the Specified Files!\\n”); exit(1); } }
当flag0=0时执行读文件操作,将文件指针fp0是指向文件节点数据文件Node_1.txt。若打开文件失败,则弹出错误信息。
在文本文件中,节点数据是一个n×4的数表,其组织形式例如: 1 -1.0502 42.0225 -88.1344 2 1.6750 31.8291 -85.9646 3 4.9684 34.3991 -93.6955 ??
其中,每一行代表一个节点:第一列是节点编号(整型数据),第二、三、四列分别为节点的x、y、z坐标(浮点型数据)。
对于一个文本文件,它的行数是确定的。但对于不同的问题,不同的文本文件的行数(节点数)却可能不相同。为此,特意通过动态数组类Array创建动态数组来保存节点数据,以这样的方法来动态调整数组的大小,适应不同节点数据的需要。动态数组类Array是王家林老师(重庆交通大学)编写的DLL动态库中包含的一个算法类。
通过动态数组类Array,定义三种类型的动态数组如下: Array
Array
data_id.Zerolize(); data_ad.Zerolize(); node.Zerolize();
于是,读入节点的方法可以通过下面的方式来实现:
//从文件中读入节点数据
22
ifstream fin0(fp0);

