点集表征空间是最简便也是较为经济的方式,但是很多时候,点集过于粗糙,无法满足人们对空间划分的精度要求,因此以点集为基础对空间进行的剖分工具就显得异常重要。CGAL中的三维三角剖分包(3D Triangulations Packages)是一款非常灵活的空间划分工具包,它以四面体为基本单元,可对提供的点集进行Delaunay三角剖分和正则三角剖分。
CGAL提供的数据结构一方面能够满足对几何体的几何信息和拓扑的描述,另一方面也要照顾算法灵活性和广泛性的需要,要能够满足算法的需要。本文通过一个示例去查看CGAL-Triangulation中的单元和面的关系。
一、数据准备:
给定4个点的点集数据,存放在points的vector容器中。
4 0 0 0 0 1 1 0 0 2 1 1 0 3 0 0 1
二、三角剖分
对点集进行三角剖分,得到如图所示的两个四面体。
//kernel #include <CGAL/Exact_predicates_exact_constructions_kernel.h> #include <CGAL/Delaunay_triangulation_3.h> #include <CGAL/Delaunay_triangulation_cell_base_with_circumcenter_3.h> #include <CGAL/Triangulation_vertex_base_with_info_3.h> typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel_epec; typedef CGAL::Triangulation_vertex_base_with_info_3<unsigned, Kernel_epec> Vb; typedef CGAL::Delaunay_triangulation_cell_base_with_circumcenter_3<Kernel_epec> Cb; typedef CGAL::Triangulation_data_structure_3<Vb, Cb> Tds; typedef CGAL::Delaunay_triangulation_3<Kernel_epec, Tds> Delaunay; Delaunay T; T.insert(points.begin(), points.end());
三、单元统计
CGAL的三角剖分数据结构满足拓扑的完备性,图中显示的两个四面体是作为两个有限单元(finite cell),另外图中外侧的6个边界三角形面与一个无穷远点(通常以坐标(0,0,0)表示)构成了边界上的无限单元(infinite cell),这样全部的单元数为8。具体各个单元的情况如下表所示。
单元节点序号 | 节点坐标x | 节点坐标y | 节点坐标z | 备注 | |
---|---|---|---|---|---|
单元:0 | 1#单元032面 | 无限元 | |||
无穷点 | 0 | 0 | 0 | ||
0 | 1 | 0 | 0 | ||
2 | 0 | 0 | 0 | ||
3 | 0 | 0 | 1 | ||
单元:1 | 1#单元 | 有限元 | |||
0 | 1 | 0 | 0 | ||
1 | 0 | 1 | 0 | ||
2 | 0 | 0 | 0 | ||
3 | 0 | 0 | 1 | ||
单元:0 | 1#单元123面 | 无限元 | |||
1 | 0 | 1 | 0 | ||
无穷点 | 0 | 0 | 0 | ||
2 | 0 | 0 | 0 | ||
3 | 0 | 0 | 1 | ||
单元:0 | 1#单元021面 | 无限元 | |||
0 | 1 | 0 | 0 | ||
2 | 0 | 0 | 0 | ||
1 | 0 | 1 | 0 | ||
无穷点 | 0 | 0 | 0 | ||
单元:0 | 2#单元013面 | 无限元 | |||
0 | 0 | 0 | 1 | ||
无穷点 | 0 | 0 | 0 | ||
1 | 1 | 0 | 0 | ||
3 | 1 | 1 | 0 | ||
单元:0 | 2#单元032面 | 无限元 | |||
0 | 0 | 0 | 1 | ||
2 | 0 | 1 | 0 | ||
无穷点 | 0 | 0 | 0 | ||
3 | 1 | 1 | 0 | ||
单元:2 | 2#单元 | 有限元 | |||
0 | 0 | 0 | 1 | ||
1 | 1 | 0 | 0 | ||
2 | 0 | 1 | 0 | ||
3 | 1 | 1 | 0 | ||
单元:0 | 2#单元231面 | 无限元 | |||
2 | 0 | 1 | 0 | ||
1 | 1 | 0 | 0 | ||
无穷点 | 0 | 0 | 0 | ||
3 | 1 | 1 | 0 |
四、单元(cell)与面(facet)的关系
每个单元有4个关联面,每个面有2个关联单元。面通过单元句柄+对面的顶点号两个数据构成的数据对表示,即(单元句柄、对面顶点号)。
for (Delaunay::Finite_facets::iterator it = this->T.finite_facets_begin(); it != this->T.finite_facets_end(); ++it) { //输出表示单元的数据对,it->first是单元句柄,it->second是对面顶点号。 std::cout << "单元和指示面的节点:"; std::cout << cells_index[it->first] << " " << it->second ; //如果第i面不是以无限单元表示的,若是则打印提示信息。 if(this->T.is_infinite(it->first)) std::cout << " 该面使用无限元表示。" << std::endl; //对共面的邻居单元的4个顶点遍历,当邻居单元顶点相对的相邻单元==共面的本单元时,输出邻居单元的坐标。 for (int i = 0; i < 4; ++i) { //判断的意涵:与当前单元(it->first)的it->second节点的对面共面的 //相邻单元的节点i的对面的相邻单元==当前单元,此处的i指的是当前单元相邻单元的i节点 if (cells_index[it->first->neighbor(it->second)->neighbor(i)] == cells_index[it->first]) { std::cout << "表示当前面所使用的单元:" << cells_index[it->first] << std::endl; std::cout << "表示当前面所使用单元的邻居单元:" << cells_index[it->first->neighbor(it->second)] << std::endl; std::cout << "表示当前面所使用单元的邻居的邻居单元:" << cells_index[it->first->neighbor(it->second)->neighbor(i)] << std::endl; std::cout << "第 " << i << " 点坐标:" << it->first->neighbor(it->second)->vertex(i)->point() << std::endl; } }
输出结果为:
单元和指示面的节点:0 0 该面使用无限元表示。 表示当前面所使用的单元:0 表示当前面所使用单元的邻居单元:1 表示当前面所使用单元的邻居的邻居单元:0 第 0 点坐标:1 0 0 表示当前面所使用的单元:0 表示当前面所使用单元的邻居单元:1 表示当前面所使用单元的邻居的邻居单元:0 第 1 点坐标:0 1 0 表示当前面所使用的单元:0 表示当前面所使用单元的邻居单元:1 表示当前面所使用单元的邻居的邻居单元:0 第 3 点坐标:0 0 1 单元和指示面的节点:1 0 表示当前面所使用的单元:1 表示当前面所使用单元的邻居单元:0 表示当前面所使用单元的邻居的邻居单元:1 第 1 点坐标:0 0 0 单元和指示面的节点:1 2 表示当前面所使用的单元:1 表示当前面所使用单元的邻居单元:2 表示当前面所使用单元的邻居的邻居单元:1 第 3 点坐标:1 1 0 单元和指示面的节点:1 3 表示当前面所使用的单元:1 表示当前面所使用单元的邻居单元:0 表示当前面所使用单元的邻居的邻居单元:1 第 3 点坐标:0 0 0 单元和指示面的节点:0 1 该面使用无限元表示。 表示当前面所使用的单元:0 表示当前面所使用单元的邻居单元:2 表示当前面所使用单元的邻居的邻居单元:0 第 0 点坐标:0 0 1 表示当前面所使用的单元:0 表示当前面所使用单元的邻居单元:2 表示当前面所使用单元的邻居的邻居单元:0 第 1 点坐标:1 0 0 表示当前面所使用的单元:0 表示当前面所使用单元的邻居单元:2 表示当前面所使用单元的邻居的邻居单元:0 第 2 点坐标:0 1 0 单元和指示面的节点:0 2 该面使用无限元表示。 表示当前面所使用的单元:0 表示当前面所使用单元的邻居单元:2 表示当前面所使用单元的邻居的邻居单元:0 第 0 点坐标:0 0 1 表示当前面所使用的单元:0 表示当前面所使用单元的邻居单元:2 表示当前面所使用单元的邻居的邻居单元:0 第 1 点坐标:1 0 0 表示当前面所使用的单元:0 表示当前面所使用单元的邻居单元:2 表示当前面所使用单元的邻居的邻居单元:0 第 2 点坐标:0 1 0 单元和指示面的节点:2 0 表示当前面所使用的单元:2 表示当前面所使用单元的邻居单元:0 表示当前面所使用单元的邻居的邻居单元:2 第 2 点坐标:0 0 0 共有: 7 个面
从上述输出结果可以看出:
1、每个边界三角形面都对应一个无限单元,虽然无限单元的数量可以很多,但单元标识只有一个,那就是0号单元;
2、有限单元从1按顺序编号;
3、无论是内部面还是外部面都有两个关联单元,面既可以用有限元表示,也可以用无限元表示。为满足唯一性要求,只能选择其中一个关联单元+对顶点表示面。
4、用两次neighbor(),可以再次指向原来的面。当取对 i 时,it->first->neighbor(it->second)->neighbor(i)==it->first。