我说的"凸包三角网"指的是, 这个三角网的边缘是凸多边形. 在构建Delanuay三角剖分时,
这是一个可选的要求.
用增量算法构建的三角网的边缘不一定是点集的凸包, 为了填补边缘和凸包之前的空隙, 我试过两种方法:
(1) 用三角形带填充 edge 和 hull. edge 和 hull
是两根逆时针的闭合多边形. 使用两个顶点指针 i 和 j,
分别指示 edge 和 hull 上的当前顶点.
记 edge 的顶点集合为 E,
hull 的为 H. 一个事实是, H 中的每个点都属于 E, 反之则不然.
开始时 E[i] 和 H[j] 是重复的, 然后两个指针逐渐递增, 速度一般并不相等, 也不均匀, 能够保证的是:
(a) E[i] 到 H[j] 的距离比它到 H 中的任何其它点的距离都短, 反之亦然.
(b) 两个指针同时走完一圈, 那时它们的值分别是 i + E.size() 和 j + H.size().
如果 E[i] == H[j], E[k] == H[j+1],
(假设这里的[]运算符能自动对下标取模.) 则点集{ H[j], H[j+1], E[k-1], E[k-2],
... E[i+1] }是一个闭合多变形, 记为S. 要做到事就是用一些三角形填满这个多边形区域.
在我的实现中, 有一个一度让我很抓狂的 BUG: 有时候填充的三角形竟然会与原来的三角网重叠, 这直接导致我的三角网边缘搜索算法失败.这个 BUG
源于我先前在填补三角形没有遵守"最小外接圆"性质, 导致新创建的三角形内部有其它顶点落入. 现在看来,
合理的填充三角形的规则是:
(a) 如果 E[i] == H[j], 就创建三角形 { H[j], E[i+1], E[i] }, 然后
i++.
(b) 如果 E[i] != H[j], 就要么创建三角形 { H[j], H[j+1], E[i] } 然后
j++, 要么 { H[j], E[i+1], E[i] }
然后 i++, 取决于线段 <E[i], H[j+1]> 和 <H[j], E[i+1]> 的长度比较.
(2) 迭代三角网边缘, 遇到一个坑就用相应的三角形填上. 这个过程中, 三角网上的坑被逐渐填平. 在我的实现中,
三角网的边缘至少要迭代一遍, 最后一遍只是检查有没有未填平的坑. 在迭代过程中, 每填补一个坑, 边缘上的顶点就减少一个. 这种方法不需要事先寻找点集的凸包,
但是由于集合 E 在迭代过程中是要反复删除其中的项目的,
如果不想每填一个小坑(这个小坑可能是大坑的一部分)就退出当前迭代, 然后从头开始, 就得在迭代器上费点事了. 我使用 vector, 没用迭代器, 而是用下标, 所以没任何问题. 如果用 list,
会麻烦些.
P.S.
开始时, 我在这个项目中用杨师兄的代码做delaunay三角剖分,
很快就发现有缺陷, 手头只有编译了的 ActiveX 控件, 没有源代码. 就到网上找了别人的代码,
用了个把月才发现他的代码也有问题, 而且没有改造的空间. 不得已, 自己写了, 又几个月过去了, 我的代码修正了好几次, 但仍然不敢说它就无敌了, 只能说,
已经碰到的任何情形, 它都能正确快速地处理, 但谁知道以后会不会碰到新的麻烦!
一段正确无误的程序, 需要多少心血啊. 首饰有瑕疵, 可以作为残次品便宜卖. 可代码呢,
你甚至不能说"这段代码95%是正确的", 因为这讲不通, 崩溃就是崩溃, 跟机器没有什么好商量的. 一个由几万个三角形拼成的网, 就因为其中一个三角形没守规矩,
导致所有经过它的搜索线路都死翘翘, 进得去出不来, 导致程序的后续数据处理全部终止. 有时候我会想, 我喜欢敲代码的根本动机, 到底是写出有用的程序,
还是在机器里创建一个完美的秩序?