计算几何---多边形三角剖分算法研究与实现(1):多边形单调划分

1概述

     多边形三角剖分是计算几何( Computational Geometry)中的经典问题,起源于一个有趣的艺术画廊问题。目前有很多不同的算法实现了对多边形的三角剖分,三角化算法所追求的目标主要有两个:形状匀称和计算速度快,这也决定了多边形三角剖分的两个不同的应用方向。在形状匀称方面,人们对三角化的性质、 形状最优准则及算法进行了深入研究 ,采用较多的是 Delaunay 准则。这些算法在保证形状匀称的前提下,也尽可能考虑了提高计算速度。在有限元分析等许多应用场合三角形匀称是必须的,对单元质量是有一定要求的。但有些应用场合对三角形匀称性的要求不高,甚至没有匀称性要求。例如用 OpenGL显示图形时,不同的三角化策略对图形效果基本没有影响。在三角化时考虑匀称性,会使算法思想受到限制,从而影响算法效率。因此追求较高计算效率的三角化算法还是有意义的。本文所探讨的即是时间复杂度为O(n log n)的多边形三角剖分算法,这也是很多经典计算几何教材所给出的经典算法。

      此算法的核心思想是首先对多边形进行单调划分,也就是将多边形分解为若干个单调多边形,然后再对单调多边形进行三角剖分,最终生成对初始多边形的三角剖分。

  以下总结可以看作是自己的学习笔记和经验总结吧。程序源代码下载地址:http://download.csdn.net/source/3169759

   

1基本概念

         简单多边形(Simple Polygon):由单个不相交的封闭的多边形链围成的图形。(不含孔洞、边不相交)

         多边形的三角剖分(Triangulation):通过一组极大地互不相交的对角线,将一个多边形分解为多个三角形的集合。

         定理1:任何简单多边形都存在(至少)一个三角剖分,若其顶点数目为n,则它的三角剖分结果中包含n-2个三角形。

         定理2:(艺术画廊定理)包含n个顶点的任意简单多边形,(在最坏的情况下)最多只需要n/3台摄像机就能保证多边形中的任何一点都可见于至少一台摄像机。

         定理3:任何一个包含n个顶点的简单多边形,总可以在O(n log n)时间内在多边形中确定n/3台摄像机的位置,使得多边形中任一点可见摄像机。

         计算特定多边形所需要摄像机的最小数目是NP难的。

         单调多边形:一个简单多边形关于某条直线L单调(monotone with respect to a line L),如果对任何一条垂直于L的直线L’,L’与多边形的交都是连通的,即它们的交可能是一条线段,或者一个点,也可能是空集。

         本文算法以关于y坐标轴单调的简单多边形为例。

3 多边形的单调划分

         把一个多边形划分为若干个单调多边形是非常重要的一步,本文采用一种类似扫描线的算法。首先确定顶点的类型,并按照Y坐标降序排列,若顶点Y坐标相等,则以X坐标小(左边)的点优先获得一个链表。然后依次遍历该链表,针对不同的顶点类型采取不同的操作策略,完成多边形的单调划分操作。

3.1 多边形顶点分类

普通顶点:Y坐标的大小介于相邻两顶点之间

拐点(歧点):Y坐标比相邻两顶点都大或都小

        起始顶点:Y坐标比相邻两顶点都大,且为凸顶点(内角小于180°)

        分裂顶点:Y坐标比相邻两顶点都大,且为凹顶点(内角大于180°)

        终止顶点:Y坐标比相邻两顶点都小,且为凸顶点(内角小于180°)

       汇合顶点:Y坐标比相邻两顶点都大,且为凹顶点(内角大于180°)

注解:

1、顶点的名称源自于扫描线算法经过这些顶点时的特征,没有什么特殊的含义,仅为类型标识,其定义才是唯一区分标准。

2、对于非简单多边形,即含有孔洞的多边形,其内部边界的顶点类型也可以按照此定义分类,标识类型。

3、算法的关键也就是确定 边e的”助手“ helper(e),翻译为边e的辅助点或许会更专业一些。其定义为:在位于扫描线上方、通过一条完全落在多边形P内部的水平线段与e相连的那些顶点中,高度最低(Y坐标最小)的那个顶点。

单调剖分算法的根本目标是:消除分裂顶点和汇合顶点 也就是  凹 顶点!通过添加若干条不相交的对角线消除凹顶点,这样就把一个多边形划分为若干个单调多边形。

3.2 算法描述:

输入:多边形P,外部边界顶点逆时针编号,内部边界顶点顺时针编号

输出:P的单调子多边形划分,和对角线D

算法:

S1、以顶点Y坐标降序排序,若有顶点Y坐标相同则以X坐标小的优先,得到链表L。同时确定顶点类型     

S2、初始化一棵二分查找树T

S3、遍历链表L

      按优先级遍历L中顶点

      根据顶点类型,选用适当的子程序处理

伪代码:

  • 起始顶点Vi

将Ei插入到T中,Ei.helper=Vi

  • 汇合顶点Vi

if(Ei-1.helper是一个汇合顶点)

     连接Vi和Ei-1.helper

在T中删除Ei-1

对T进行搜索,查找在左侧与Vi紧邻的边Ej

if(Ej.helper是一个汇合顶点)

      连接Vi和Ej.helper

Ej.helper=Vi

  • 普通顶点

if(P的内部处于Vi的右侧)    //与顶点编号顺序有关,逆时针的左链

       then  if(Ei-1.helper是一个汇合顶点)

               连接 Vi 和 Ei-1.helper

       在T中删除Ei-1

       将Ei添加到T中,Ei.helper=Vi

else 对T进行搜索,查找在左侧与Vi紧邻的那条边Ej   //逆时针的右链

       if (Ej.helper是一个汇合顶点)

                then  连接Vi和Ej.helper

       Ej.helper=Vi

  • 分裂顶点

对T进行搜索,查找在左侧与Vi紧邻的那条边Ej

连接Vi 和 Ej.helper

Ej.helper=Vi

将Ei插入T中 , Ei.helper=Vi

  • 终止顶点

if(Ei-1.helper是一个汇合顶点)

       连接Vi和Ei-1.helper

在T中删除E-i                           

3.3 算法说明

1. 一开始生成链表L也可以用一个优先级队列表示,而且在此处优先级队列数据结构可能更恰当,但是考虑到仅仅是对其进行遍历操作,所以一个排序的链表也能满足要求。

2. 二分查找树T具体编程实现起来比较复杂,从实际情况来看,T中元素数量在绝大多数情况下都不会很大,为降低编程难度,所以直接采用一个链表代替,这样可能会有效率上的损失,但是基本可以忽略。

3. 算法的时间复杂度为O(n log n)。

posted @ 2011-04-07 22:01  frcsun  Views(10784)  Comments(7Edit  收藏  举报