三角剖分是代数拓扑学里最基本的研究方法。 以曲面为例, 我们把曲面剖开成一块块碎片,要求满足下面条件: (1)每块碎片都是曲边三角形;
(
2)曲面上任何两个这样的曲边三角形,要么不相交,要么恰好相交于一条公共边(不能同时交两条或两条以上的边)
拓扑学的一个已知事实告诉我们:任何曲面都存在三角剖分。
假设曲面上有一个三角剖分, 我们把所有三角形的顶点总个数记为p(公共顶点只看成一个,下同),边数记为l,三角形的个数记为n,则e=p-l+n是曲面的拓扑不变量! 也就是说不管是什么剖分, e总是得到相同的数值。 e被称为称为欧拉示性数。
假设g是曲面上洞眼的个数(比如球面没有洞,故g=0;又如环面有一个洞,故g=1),那么e=2-2g。
g也是拓扑不变量,称为曲面的亏格(genus)。
上面例举曲面的情形。对一般的拓扑对象(复形),我们有类似的剖分,通常成为单纯剖分。 分割出的每块碎片称为单纯形(简称单形)
2.1 定义
三角剖分:假设V是二维实数域上的有限点集,边e是由点集中的点作为端点构成的封闭线段, E为e的集合。那么该点集V的一个三角剖分T=(V,E)是一个平面图G,该平面图满足条件:
1.除了端点,平面图中的边不包含点集中的任何点。
2.没有相交边。
3.平面图中所有的面都是三角面,且所有三角面的合集是散点集V的凸包。
在实际中运用的最多的三角剖分是Delaunay三角剖分,它是一种特殊的三角剖分。
2.2 先从Delaunay边说起
Delaunay边:
假设E中的一条边e(两个端点为a,b),e若满足下列条件,则称之为Delaunay边:
存在一个圆经过a,b两点,圆内(注意是圆内,圆上最多三点共圆)不含点集V中任何其他的点,这一特性又称空圆特性。
Delaunay三角剖分:
如果点集V的一个三角剖分T只包含Delaunay边,那么该三角剖分称为Delaunay三角剖分。
假设T为V的任一三角剖分,则T是V的一个Delaunay三角剖分,当前仅当T中的每个三角形的外接圆的内部不包含V中任何的点。
通过离散点组成Delaunay三角模型如下图所示:
关于Delaunay三角形的算法,有翻边算法、逐点插入算法、分割合并算法、Bowyer-Watson算法等。
而在这几种算法中,逐点插入算法比较简单、易懂,在本文中只针对该算法进行讨论,该算法也是目前使用最为广泛的Delaunay算法。
在该算法中,主要应用Delaunay三角形,理解下来就是每一个三角形的外接圆圆内不能存在点集内的其它任何一点,而有时候会出现点在外接圆上的情况,这种情况被称为“退化”。
文章《Triangulate》对该方法进行了分析,并提出了伪代码思路:
subroutine triangulate
input : vertex list
output : triangle list
initialize the triangle list
determine the supertriangle
add supertriangle vertices to the end of the vertex list
add the supertriangle to the triangle list
for each sample point in the vertex list
initialize the edge buffer
for each triangle currently in the triangle list
calculate the triangle circumcircle center and radius
if the point lies in the triangle circumcircle then
add the three triangle edges to the edge buffer
remove the triangle from the triangle list
endif
endfor
delete all doubly specified edges from the edge buffer
this leaves the edges of the enclosing polygon only
add to the triangle list all triangles formed between the point
and the edges of the enclosing polygon
endfor
remove any triangles from the triangle list that use the supertriangle vertices
remove the supertriangle vertices from the vertex list
end
其方法虽然可实现三角化,但是效率还是不太高。
在看过https://github.com/ironwallaby/delaunay该js也是基于该伪代码进行编写的,但是作者在其中进行了一次排序优化,使得代码运行效率得到了提高
优化后的伪代码为:
input: 顶点列表(vertices) //vertices为外部生成的随机或乱序顶点列表
output:已确定的三角形列表(triangles)
初始化顶点列表
创建索引列表(indices = new Array(vertices.length))//indices数组中的值为0,1,2,3,.....,vertices.length-1
基于vertices中的顶点x坐标对indices进行sort //sort后的indices顺序为顶点坐标x从小到大排序本例针对x坐标)
确定超级三角形
将超级三角形保存至未确定三角形列表(temp triangles)
将超级三角形push到triangles列表
遍历基于indices顺序的vertices中每一个点 //基于indices后,则顶点则是由x从小到大出现
初始化边缓存数组(edge buffer)
遍历temp triangles中的每一个三角形
计算该三角形的圆心和半径
如果该点在外接圆的右侧
则该三角形为Delaunay三角形,保存到triangles
并在temp里去除掉
跳过
如果该点在外接圆外(即也不是外接圆右侧)
则该三角形为不确定 //后面会在问题中讨论
跳过
如果该点在外接圆内
则该三角形不为Delaunay三角形
将三边保存至edge buffer
在temp中去除掉该三角形
对edge buffer进行去重
将edge buffer中的边与当前的点进行组合成若干三角形并保存至temp triangles中
将triangles与temp triangles进行合并
除去与超级三角形有关的三角形
end
我们先用三点来做实例,
具体的图形化解释如下所示:
根据离散点的最大分布来求得随机一个超级三角形(超级三角形意味着该三角形包含了点集中所有的点)
我的方法是根据相似三角形定理求得与矩形一半的小矩形的对角三角形,扩大一倍后则扩大后的直角三角形斜边经过点(Xmax,Ymin)
但是为了将所有的点包含在超级三角形内,在右下角对该三角形的顶点进行了横和高的扩展,并要保证这个扩展三角形底大于高,才能实现包含。这样求得的超级三角形不会特别大使得计算复杂,而且过程也简单,并将超级三角形放入temp triangles中。
接下来就像是伪代码中描述的那样,对temp triangle中的的三角形遍历画外接圆,这时先对左边的第一个点进行判断,其在圆内。
所以该三角形不为Delaunay三角形,将其三边保存至edge buffer中,temp triangle中删除该三角形。
将该点与edge buffer中的每一个边相连,组成三个三角形,加入到temp triangles中。
再将重复对temp triangles的遍历并画外接圆,这时使用的是第二个点来进行判断
该点在三角形1外接圆右侧,则表示左侧三角形为Delaunay三角形,将该三角形保存至triangles中
该点在三角形2外接圆外侧,为不确定三角形,所以跳过(后面会讲到为什么要跳过该三角形),但并不在temp triangles中删除
该点在三角形3外接圆内侧,则这时向清空后的edge buffer加入该三角形的三条边,并用该点写edge buffer中的三角边进行组合,组合成了三个三角形并加入到temp triangles中
再次对temp triangles进行遍历,这里该数组里则含有四个三角形,一个是上次检查跳过的含有第一个点的三角形和新根据第二个点生成的三个三角形
该点在三角形1外接圆右侧,则该三角形为Delaunay三角形,保存至triangles中,并在temp triangles中删除
该点在三角形2外接圆外侧,跳过
该点在三角形3外接圆内侧,将该三边保存至temp buffer中,并在temp triangles中删除
该点在三角形4外接圆内侧,将该三边保存至temp buffer中,并在temp triangles中删除
这时,temp buffer 中有六条边,triangles中有两个三角形,temp triangles中有1个三角形
对temp buffer中的六条边进行去重,得到五条边,将该点与这五条边组合成五个三角形并加入到temp triagnles 中,这时temp triangles中有6个三角形
由于三个点已经遍历结束,到了不会再对第三个点形成的三角形做外接圆,这时则将triangles与temp trianlges合并,合并后的数组表示包含已经确定的Delaunay三角形和剩下的三角形
这时除去合并后数组中的和超级三角形三个点有关的所有三角形,即进行数组坐标限定,得到最后的结果:
这是用最少的三个点来做讲解,点数越多的话计算量会越大,但是都是在上面步骤下进行的。
这里仅对三个点的特殊情况进行研究,多点情况类似。下面仅给出一张仿真图作为结尾: