Fenrier Lab

Delaunay 网格划分算法

将一块区域进行网格化是计算几何中的一项重要议题,基于化繁为简的思想,任何复杂的模型都能分解成简单的几何元素。在应用上,整体的非线性行为,便可以由局部的线性拼接得到。

举个简单的例子,一段向上的楼梯,老远看去就是一条斜向上的直线,但实际上是由一段段水平和竖直的线段连成的。从某种意义上讲,将斜线表述成水平线和竖直线的组合降低了描述的层次,但代价是我们需要维护大量的低层次信息。或许对于一段斜直线来讲,这种代价并不值得,但对于更高层次的复杂信息,局部简化是我们目前唯一的办法。

定义

Delaunay 网格划分不是一种具体的算法,而是一种规范,它确保了网格中的三角形的形状处于可控的状态。这是网格划分中不得不考虑的问题,如果同一个单元中的节点离得老远,那么在插值的时候,其误差便没有保证,因为我们知道插值算法的精度依赖于距离。

考虑网格中相邻边的两个三角形

按左边连接方式,会发现 D 点位于三角形 ABC 的外接圆内,而如果换成右边的连接,则 C 点位于 ABD 的外接圆之外,可以证明右边的三角形最小内角大于左边,也意味着右边的三角剖分优于左边。这是一种普遍的情况,于是可以将 BC 边标记为非法边,在网格划分算法中遇到这种情况就需要对其进行翻转,即删除 BC、连接 AD、删除 ABC,BDC、生成 ABD,ACD 。

平面点集的网格划分

首先考虑一种比较简单的需求,已知平面上的一组点,现在要将其连接成网格,并满足 Delaunay 条件。随机增量算法将是一种很好的方式,思想是假设使用前 n 个点已经形成了满足条件的网格,然后插入第 n + 1 个点,增量执行这一过程,直到所有点都加入到网格中,则完成了这堆点的网格划分。现在来看看将第 n + 1 个点插入网格需要完成的操作

这里存在两种情况,位于三角形内部,或者位于三角形边上。在第一种情况下,首先找到此点所在的三角形,然后分别连接 n+1 与三角形的三个端点,这样就将新点插入到了网格中,但是很显然,除了还没检查的新生成边,原本合法的边也变成了潜在的非法边。

上图左边显示了插入一个点后,需要检查的边的情况,首先对原三角形的边进行检查并翻转非法边,得到右边的图形,由于翻转操作生成了新的三角形,又需要标记检查新的潜在非法边,持续这一过程直到所有边都合法为止。

关于初始化

前面描述了增量过程,但是在最开始我们并没有一个现成的 Delaunay 网格。一种简单的方式是用一个足够大的初始三角形将所有点包含进来,然后使用增量方式插点,完成之后再删除外围的点和线段。

建议的初始外围三角形为 A(-4m, -4m), B(4m, 0), C(0, 4m),其中 m 为所有点中绝对值最大的坐标值。

数据结构

在插入点的过程中,要进行非常多的查询操作,例如查询给定点所在的三角形编号、一条边所在的一个或两个三角形编号等等,因此有必要维护一个高效的数据结构来支持这些查询。

对于三角形集合,我们使用一个有向无环图来表示所有的剖分历史,以最初的外围三角形为根节点,第一次剖分引入三个小三角形,对应于从根节点指向三个子节点

而如下图的一次翻转操作则对应用于将涉及的两个节点 T31 和 T13 指向新节点 TA 和 TB

使用这种结构,若要查询给定点所在的三角形,则从根节点开始,依次计算三个子节点,找到那个包含查询点的三角形,然后再计算它的子节点,以此类推,最终到达的叶节点即为要查询三角形。

另一方面,为了对三角形进行快速查询,我们使用数组来存储网格单元,其中每一行是三角形顶点编号,并且所有顶点也用数组来存储。

triangles:

Index v1 v2 v3 flag
0 0 1 2  
1 1 2 3  
2 1 2 4  
 

vertices:

Index x y
0 0 0
1 10 0
2 0 10

前面所说的有向无环结构存储的实际上是三角形的索引号。由于查询点的过程依赖于网格剖分历史,所以在删除单元的时候不能直接将其从数组中移除,而应该使用标记删除的方法,上面 triangles 数组中的标志 flag 位即用于此目的。

在插入点生成新的单元之后,需要检查旧单元的边是否合法,这就需要使用两个点的索引号来查询边,并找到邻边的三角形。这里仍使用数组来存储边的信息,但为了高效的通过顶点查询,我们使用搜索二叉树来存储边的索引号,搜索方式为边的中点坐标,从左到右,从下到上。

同时,边的信息应该包含其所在的一个或两个三角形,以及一个删除标志位(因为二叉树存储了边的索引号,不能直接将边从数组中移除)。

edges:

Index v1 v2 t1 t2 flag
0 0 1 0 1  
1 1 2 1 2  
2 2 3 3 3  

非法边检测

假设相邻的两个三角形为 ABC 和 BCD ,四个点的坐标分别为

\[A(a_x, a_y),\quad B(b_x, b_y),\quad C(c_x, c_y),\quad D(d_x, d_y)\]

那么可以计算得到三角形 ABC 的外接圆心坐标为

\[x = (c_1 * a_2 - c_2 * a_1) / (a_2 * b_1 - a_1 * b_2)\\ y = (c_1 * b_2 - c_2 * b_1) / (a_1 * b_2 - a_2 * b_1)\]

其中

\[\begin{aligned} a_1 &= b_x - a_x\\ b_1 &= b_y - a_y\\ c_1 &= \frac 1 2 (b_x^2 - a_x^2 + b_y^2 - a_y^2)\\ a_2 &= c_x - b_x\\ b_2 &= c_y - b_y\\ c_1 &= \frac 1 2 (c_x^2 - b_x^2 + c_y^2 - b_y^2) \end{aligned}\]

然后再计算 D 点与圆心的距离是否大于外接圆半径就可以判断了。

待续

本文遵守 CC-BY-NC-4.0 许可协议。

Creative Commons License

欢迎转载,转载需注明出处,且禁止用于商业目的。