8.1 三维三角剖分基础 (Triangulation_3)
上一节: 7.1 二维三角化基础 下一节: 8.2 三维Delaunay三角剖分
8.1.1 数学理论基础
三维三角剖分的定义
三维三角剖分(3D Triangulation)是将三维空间中的点集分解为一系列四面体(tetrahedra)的集合,满足以下条件:
- 覆盖性:所有四面体的并集等于这些点的凸包
- 无重叠性:任意两个四面体的交集要么为空,要么为一个公共面、公共边或公共顶点
- 顶点一致性:四面体的顶点恰好是给定点集中的点
组合结构
三维三角剖分的组合结构可以用**胞腔复形(Cell Complex)**描述:
- 0-胞腔(顶点):给定的三维点
- 1-胞腔(边):连接两个顶点的线段
- 2-胞腔(面):由三个顶点构成的三角形
- 3-胞腔(四面体):由四个顶点构成的三维单形
拓扑关系
在三维三角剖分中,每个四面体包含:
- 4个顶点(vertices)
- 6条边(edges)
- 4个三角形面(facets)
相邻四面体共享一个公共面(facet adjacency)。这种邻接关系构成了三角剖分的拓扑结构。
欧拉示性数
对于球面拓扑的三维三角剖分,欧拉公式为:
其中:
- :顶点数
- :边数
- :面数
- :四面体数
几何与组合的分离
CGAL的Triangulation_3采用几何与拓扑分离的设计哲学:
- 三角剖分类(Triangulation_3):管理组合结构(拓扑)
- 几何特征类(Geometric Traits):提供几何谓词和构造
- 顶点/胞腔类(Vertex/Cell):存储关联数据
这种分离允许用户自定义几何内核(精确计算或近似计算)而不影响组合算法。
8.1.2 架构分析
类层次结构
Triangulation_3<GT, Tds>
├── GT: Geometric Traits(几何特征)
│ ├── Point_3: 三维点类型
│ ├── Segment_3: 线段类型
│ ├── Tetrahedron_3: 四面体类型
│ └── 几何谓词(orientation, side_of_sphere等)
│
└── Tds: Triangulation Data Structure(三角剖分数据结构)
├── Triangulation_ds_vertex_base_3: 顶点基类
├── Triangulation_ds_cell_base_3: 胞腔基类
└── 邻接关系存储
核心组件
1. 几何特征(Geometric Traits)
几何特征类定义了三角剖分所需的所有几何对象和运算:
typename GT::Point_3 // 三维点
typename GT::Segment_3 // 线段
typename GT::Triangle_3 // 三角形
typename GT::Tetrahedron_3 // 四面体
// 几何谓词
GT::orientation(p, q, r, s) // 四点定向(判断s在pqr平面的哪一侧)
GT::side_of_sphere(p, q, r, s, t) // 判断t是否在p,q,r,s的外接球内2. 数据结构(TDS)
三角剖分数据结构存储组合信息:
- 顶点(Vertex):存储几何点、关联的入射胞腔
- 胞腔(Cell):存储4个顶点句柄、4个邻接胞腔句柄
邻接关系通过胞腔的邻居指针实现:
Cell c;
c.vertex(0), c.vertex(1), c.vertex(2), c.vertex(3) // 四个顶点
c.neighbor(0), c.neighbor(1), c.neighbor(2), c.neighbor(3) // 四个邻接胞腔3. 定位结构(Locate Structure)
为了高效地定位点所在的胞腔,CGAL使用分层定位结构(Hierarchy):
- 构建多级粗化三角剖分
- 每级是上一级的随机子集
- 定位时从顶层开始,逐级下降
期望时间复杂度:
内存布局
// 顶点结构
class Vertex {
Point_3 point; // 几何位置
Cell_handle cell; // 一个入射胞腔(用于遍历)
void* info; // 可选用户数据
};
// 胞腔结构
class Cell {
Vertex_handle vertices[4]; // 四个顶点
Cell_handle neighbors[4]; // 四个邻接胞腔(对面邻居)
void* info; // 可选用户数据
};8.1.3 实现细节
点的定位算法
定位(Locate)操作是三角剖分的核心,用于找到包含给定点的胞腔。
线性定位
从已知胞腔开始,沿直线遍历:
// 伪代码
Cell_handle locate(Point p, Cell_handle start) {
Cell_handle c = start;
while (true) {
// 检查p是否在c的每个面的外侧
for (i = 0; i < 4; i++) {
if (orientation(c->vertex((i+1)%4),
c->vertex((i+2)%4),
c->vertex((i+3)%4), p) == NEGATIVE) {
c = c->neighbor(i); // 移动到对面胞腔
break;
}
}
return c; // p在c内部或边界上
}
}分层定位
// 期望O(log n)定位
Cell_handle locate_with_hierarchy(Point p) {
// 从顶层开始
int level = max_level;
Cell_handle c = hierarchy[level]->infinite_cell();
while (level >= 0) {
c = locate_in_level(p, c); // 在当前层定位
if (level > 0) {
c = hierarchy[level-1]->cell_corresponding_to(c); // 下降
}
level--;
}
return c;
}插入操作
Bowyer-Watson算法
插入新点会破坏局部Delaunay性质,需要重新三角剖分:
// 1. 定位包含新点的胞腔
Cell_handle c = locate(p);
// 2. 找到所有外接球包含p的胞腔(冲突区域)
std::vector<Cell_handle> conflict_cells;
find_conflict_cells(p, c, conflict_cells);
// 3. 删除冲突胞腔,形成空腔(cavity)
// 4. 将p与空腔边界连接,形成新胞腔
for (each facet on cavity boundary) {
create_new_cell(p, facet);
}遍历操作
CGAL提供多种遍历器(Iterator)用于访问三角剖分的元素:
// 遍历所有顶点
for (auto vit = T.finite_vertices_begin();
vit != T.finite_vertices_end(); ++vit) {
// 处理顶点
}
// 遍历所有胞腔
for (auto cit = T.finite_cells_begin();
cit != T.finite_cells_end(); ++cit) {
// 处理胞腔
}
// 遍历顶点的邻接胞腔
Vertex_handle v = ...;
auto vc = T.incident_cells(v); // 返回cell_circulator
// 遍历胞腔的所有面
Cell_handle c = ...;
for (int i = 0; i < 4; i++) {
Facet f(c, i); // c的第i个面(与vertex(i)相对的面)
}8.1.4 代码示例
基本用法
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Triangulation_3.h>
#include <vector>
// 定义几何内核
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Triangulation_3<K> Triangulation;
typedef Triangulation::Point Point;
typedef Triangulation::Vertex_handle Vertex_handle;
typedef Triangulation::Cell_handle Cell_handle;
typedef Triangulation::Facet Facet;
int main() {
// 创建三角剖分
Triangulation T;
// 插入点
std::vector<Point> points;
points.push_back(Point(0, 0, 0));
points.push_back(Point(1, 0, 0));
points.push_back(Point(0, 1, 0));
points.push_back(Point(0, 0, 1));
points.push_back(Point(0.5, 0.5, 0.5));
points.push_back(Point(1, 1, 1));
// 批量插入(更高效)
T.insert(points.begin(), points.end());
// 查询基本信息
std::cout << "顶点数: " << T.number_of_vertices() << std::endl;
std::cout << "胞腔数: " << T.number_of_cells() << std::endl;
std::cout << "有限胞腔数: " << T.number_of_finite_cells() << std::endl;
// 定位点
Point query(0.1, 0.1, 0.1);
Cell_handle c = T.locate(query);
if (T.is_infinite(c)) {
std::cout << "查询点在凸包外" << std::endl;
} else {
std::cout << "查询点在胞腔内" << std::endl;
}
// 遍历有限胞腔
int cell_count = 0;
for (auto cit = T.finite_cells_begin();
cit != T.finite_cells_end(); ++cit) {
Cell_handle cell = cit;
// 访问四个顶点
for (int i = 0; i < 4; i++) {
Point p = cell->vertex(i)->point();
std::cout << "顶点 " << i << ": ("
<< p.x() << ", " << p.y() << ", " << p.z() << ")"
<< std::endl;
}
cell_count++;
}
return 0;
}带信息的顶点
#include <CGAL/Triangulation_vertex_base_with_info_3.h>
// 定义带信息的顶点基类
typedef CGAL::Triangulation_vertex_base_with_info_3<int, K> Vb;
typedef CGAL::Triangulation_data_structure_3<Vb> Tds;
typedef CGAL::Triangulation_3<K, Tds> Triangulation;
typedef Triangulation::Vertex_handle Vertex_handle;
int main() {
Triangulation T;
// 插入带信息的点
Vertex_handle v0 = T.insert(Point(0, 0, 0));
v0->info() = 0; // 设置顶点信息
Vertex_handle v1 = T.insert(Point(1, 0, 0));
v1->info() = 1;
// 访问信息
for (auto vit = T.finite_vertices_begin();
vit != T.finite_vertices_end(); ++vit) {
std::cout << "顶点ID: " << vit->info() << std::endl;
}
return 0;
}几何谓词的使用
#include <CGAL/predicates_on_points_3.h>
void test_orientation(const Triangulation& T) {
// 获取四个点
Point p(0, 0, 0), q(1, 0, 0), r(0, 1, 0), s(0, 0, 1);
// 定向测试:判断s在pqr平面的哪一侧
CGAL::Orientation orient = CGAL::orientation(p, q, r, s);
switch (orient) {
case CGAL::POSITIVE:
std::cout << "s在pqr平面的正侧" << std::endl;
break;
case CGAL::NEGATIVE:
std::cout << "s在pqr平面的负侧" << std::endl;
break;
case CGAL::COPLANAR:
std::cout << "s在pqr平面上" << std::endl;
break;
}
}8.1.5 复杂度分析
时间复杂度
| 操作 | 平均复杂度 | 最坏复杂度 | 说明 |
|---|---|---|---|
| 构造空三角剖分 | 初始化 | ||
| 插入单个点 | 分层定位期望 | ||
| 插入n个点 | 批量插入优化 | ||
| 定位点 | 分层结构 | ||
| 删除点 | d为度数 | ||
| 遍历所有胞腔 | 线性遍历 | ||
| 最近邻查询 | 使用Delaunay性质 |
空间复杂度
- 顶点存储:,每个顶点存储几何点和一个入射胞腔指针
- 胞腔存储:,m为胞腔数,每个胞腔存储4个顶点指针和4个邻居指针
- 定位结构:,分层结构的额外开销
对于一般位置的三维点集,胞腔数 ,因此总空间复杂度为 。
理论界限
三维三角剖分的组合复杂度:
- 下界: 个胞腔
- 上界: 个胞腔(凸位置达到上界)
对于随机分布的点集,期望胞腔数为 。
8.1.6 与其他组件的关系
Triangulation_3
├── 继承/特化
│ ├── Delaunay_triangulation_3(Delaunay三角剖分)
│ ├── Regular_triangulation_3(正则三角剖分)
│ └── Periodic_3_triangulation_3(周期性三角剖分)
│
├── 用于
│ ├── Mesh_3(三维网格生成)
│ ├── Alpha_shapes_3(Alpha形状)
│ └── Skin_surface_3(皮肤曲面)
│
└── 依赖
├── Kernel(几何内核)
└── Triangulation_data_structure_3(数据结构)