学习Jonathan Shewchuk的Triangle:分治法中三角形的几何信息和拓扑信息的操作

介绍

        想必研究网格细分技术的同学们不会不知道Jonathan Richard Shewchuk,凭借作品Triangle获得了2003年数值计算软件威尔金森奖。先拿下他的图来镇下楼(图片出自:点击打开链接点击打开链接):

 

他的作品Triangle用于生成二维网格和Delaunay三角形。

分析
基础概念

        先介绍一下他所定义的几个结构体:

/* The vertex data structure.  Each vertex is actually an array of REALs.    */
/*   The number of REALs is unknown until runtime.  An integer boundary      */
/*   marker, and sometimes a pointer to a triangle, is appended after the    */
/*   REALs.                                                                  */

typedef REAL *vertex;
/* The triangle data structure.  Each triangle contains three pointers to    */
/*   adjoining triangles, plus three pointers to vertices, plus three        */
/*   pointers to subsegments (declared below; these pointers are usually     */
/*   `dummysub').  It may or may not also contain user-defined attributes    */
/*   and/or a floating-point "area constraint."  It may also contain extra   */
/*   pointers for nodes, when the user asks for high-order elements.         */
/*   Because the size and structure of a `triangle' is not decided until     */
/*   runtime, I haven't simply declared the type `triangle' as a struct.     */

typedef REAL **triangle;            /* Really:  typedef triangle *triangle   */
/* An oriented triangle:  includes a pointer to a triangle and orientation.  */
/*   The orientation denotes an edge of the triangle.  Hence, there are      */
/*   three possible orientations.  By convention, each edge always points    */
/*   counterclockwise about the corresponding triangle.                      */

struct otri {
  triangle *tri;
  int orient;                                         /* Ranges from 0 to 2. */
};
第一个定义的是顶点,一个二维或者三维数组;第二个定义的是三角形,别看它定义的类型是double型的二维指针,其实它这里采用了4字节对齐技术,这个triangle里面不仅仅存的是组成三角形的三个顶点,它的存储空间布局是这样的:前三个空间存储的是指向它的邻接三角形的指针,接下来的三个空间存储的是指向顶点的指针,如果存在用户输入的边界边数据,那么接下来的三个空间存储的是指向边界边的指针,还可能会包含任意数量的用户定义的浮点型属性选项,和一个可选的面积限制;第三个定义的带有orientation的triangle, 这个orientation的值区间为[0, 2],用来确定这个三角形的顶点org,dest,apex,所表示的边(顶点org到dest的有向边)以及与这条边邻接的三角形
       下面再来介绍几个宏:

/* Fast lookup arrays to speed some of the mesh manipulation primitives.     */

int plus1mod3[3] = {1, 2, 0};
int minus1mod3[3] = {2, 0, 1};
/* These primitives determine or set the origin, destination, or apex of a   */
/* triangle.                                                                 */

#define org(otri, vertexptr)                                                  \
  vertexptr = (vertex) (otri).tri[plus1mod3[(otri).orient] + 3]

#define dest(otri, vertexptr)                                                 \
  vertexptr = (vertex) (otri).tri[minus1mod3[(otri).orient] + 3]

#define apex(otri, vertexptr)                                                 \
  vertexptr = (vertex) (otri).tri[(otri).orient + 3]

#define setorg(otri, vertexptr)                                               \
  (otri).tri[plus1mod3[(otri).orient] + 3] = (triangle) vertexptr

#define setdest(otri, vertexptr)                                              \
  (otri).tri[minus1mod3[(otri).orient] + 3] = (triangle) vertexptr

#define setapex(otri, vertexptr)                                              \
  (otri).tri[(otri).orient + 3] = (triangle) vertexptr

/* Bond two triangles together.                                              */

#define bond(otri1, otri2)                                                    \
  (otri1).tri[(otri1).orient] = encode(otri2);                                \
  (otri2).tri[(otri2).orient] = encode(otri1)

这几个宏和数组的定义可帮助我们确定在有向三角形otri中三个顶点对应的位置以及两个三角形时如何连接在一起的。当orientation为0时,三角形顶点org,dest,apex在数组中的索引位置为:4,5,3;当orientation为1时,索引位置为:5,3,4;当orientation为2时,索引位置为:3,4,5。再如第三段代码宏定义bond,分别在数组索引位置为orientation的地址处存储指向以顶点org到dest的边为邻接的三角形的指针。
三角形数据结构

        在Jonathan Richard Shewchuk的论文[1]中更一步讲到了有向三角形的设计思想,它这个设计是参考及改进的Leonidas Guibas 和Jorge Stolfi的研究[2]中的四边结构。JRS引入了假三角形(Ghost Triangle)的概念。这里引述一下他论文中的原文和图片:

        To preserve the elegance of Guibas and Stolfi’s presentation of the divide-and-conquer algorithm, each triangulation is surrounded with a layer of “ghost” triangles, one triangle per convex hull edge. The ghost triangles are connected to each other in a ring about a “vertex at infinity” (really just a null pointer). A single edge is represented by two ghost triangles, as illustrated in Figure 2.


每个三角形都包围有一层假三角形,一条凸包边有一个假三角形,这个假三角形中的一个顶点是指向无穷远的点,在Triangle工程中设置这个指向这个无穷远点的指针为NULL。

分治法中三角形的几何信息和拓扑信息的操作

      Triangle工程中分治法三角形剖分迭代函数divconqrecurse的源码如下:

/*****************************************************************************/
/*                                                                           */
/*  divconqrecurse()   Recursively form a Delaunay triangulation by the      */
/*                     divide-and-conquer method.                            */
/*                                                                           */
/*  Recursively breaks down the problem into smaller pieces, which are       */
/*  knitted together by mergehulls().  The base cases (problems of two or    */
/*  three vertices) are handled specially here.                              */
/*                                                                           */
/*  On completion, `farleft' and `farright' are bounding triangles such that */
/*  the origin of `farleft' is the leftmost vertex (breaking ties by         */
/*  choosing the highest leftmost vertex), and the destination of            */
/*  `farright' is the rightmost vertex (breaking ties by choosing the        */
/*  lowest rightmost vertex).                                                */
/*                                                                           */
/*****************************************************************************/

#ifdef ANSI_DECLARATORS
void divconqrecurse(struct mesh *m, struct behavior *b, vertex *sortarray,
                    int vertices, int axis,
                    struct otri *farleft, struct otri *farright)
#else /* not ANSI_DECLARATORS */
void divconqrecurse(m, b, sortarray, vertices, axis, farleft, farright)
struct mesh *m;
struct behavior *b;
vertex *sortarray;
int vertices;
int axis;
struct otri *farleft;
struct otri *farright;
#endif /* not ANSI_DECLARATORS */

{
  struct otri midtri, tri1, tri2, tri3;
  struct otri innerleft, innerright;
  REAL area;
  int divider;

  if (b->verbose > 2) {
    printf("  Triangulating %d vertices.\n", vertices);
  }
  if (vertices == 2) {
    /* The triangulation of two vertices is an edge.  An edge is */
    /*   represented by two bounding triangles.                  */
    maketriangle(m, b, farleft);
    setorg(*farleft, sortarray[0]);
    setdest(*farleft, sortarray[1]);
    /* The apex is intentionally left NULL. */
    maketriangle(m, b, farright);
    setorg(*farright, sortarray[1]);
    setdest(*farright, sortarray[0]);
    /* The apex is intentionally left NULL. */
    bond(*farleft, *farright);
    lprevself(*farleft);
    lnextself(*farright);
    bond(*farleft, *farright);
    lprevself(*farleft);
    lnextself(*farright);
    bond(*farleft, *farright);
    if (b->verbose > 2) {
      printf("  Creating ");
      printtriangle(m, b, farleft);
      printf("  Creating ");
      printtriangle(m, b, farright);
    }
    /* Ensure that the origin of `farleft' is sortarray[0]. */
    lprev(*farright, *farleft);
    return;
  } else if (vertices == 3) {
    /* The triangulation of three vertices is either a triangle (with */
    /*   three bounding triangles) or two edges (with four bounding   */
    /*   triangles).  In either case, four triangles are created.     */
    maketriangle(m, b, &midtri);
    maketriangle(m, b, &tri1);
    maketriangle(m, b, &tri2);
    maketriangle(m, b, &tri3);
    area = counterclockwise(m, b, sortarray[0], sortarray[1], sortarray[2]);
    if (area == 0.0) {
      /* Three collinear vertices; the triangulation is two edges. */
      setorg(midtri, sortarray[0]);
      setdest(midtri, sortarray[1]);
      setorg(tri1, sortarray[1]);
      setdest(tri1, sortarray[0]);
      setorg(tri2, sortarray[2]);
      setdest(tri2, sortarray[1]);
      setorg(tri3, sortarray[1]);
      setdest(tri3, sortarray[2]);
      /* All apices are intentionally left NULL. */
      bond(midtri, tri1);
      bond(tri2, tri3);
      lnextself(midtri);
      lprevself(tri1);
      lnextself(tri2);
      lprevself(tri3);
      bond(midtri, tri3);
      bond(tri1, tri2);
      lnextself(midtri);
      lprevself(tri1);
      lnextself(tri2);
      lprevself(tri3);
      bond(midtri, tri1);
      bond(tri2, tri3);
      /* Ensure that the origin of `farleft' is sortarray[0]. */
      otricopy(tri1, *farleft);
      /* Ensure that the destination of `farright' is sortarray[2]. */
      otricopy(tri2, *farright);
    } else {
      /* The three vertices are not collinear; the triangulation is one */
      /*   triangle, namely `midtri'.                                   */
      setorg(midtri, sortarray[0]);
      setdest(tri1, sortarray[0]);
      setorg(tri3, sortarray[0]);
      /* Apices of tri1, tri2, and tri3 are left NULL. */
      if (area > 0.0) {
        /* The vertices are in counterclockwise order. */
        setdest(midtri, sortarray[1]);
        setorg(tri1, sortarray[1]);
        setdest(tri2, sortarray[1]);
        setapex(midtri, sortarray[2]);
        setorg(tri2, sortarray[2]);
        setdest(tri3, sortarray[2]);
      } else {
        /* The vertices are in clockwise order. */
        setdest(midtri, sortarray[2]);
        setorg(tri1, sortarray[2]);
        setdest(tri2, sortarray[2]);
        setapex(midtri, sortarray[1]);
        setorg(tri2, sortarray[1]);
        setdest(tri3, sortarray[1]);
      }
      /* The topology does not depend on how the vertices are ordered. */
      bond(midtri, tri1);
      lnextself(midtri);
      bond(midtri, tri2);
      lnextself(midtri);
      bond(midtri, tri3);
      lprevself(tri1);
      lnextself(tri2);
      bond(tri1, tri2);
      lprevself(tri1);
      lprevself(tri3);
      bond(tri1, tri3);
      lnextself(tri2);
      lprevself(tri3);
      bond(tri2, tri3);
      /* Ensure that the origin of `farleft' is sortarray[0]. */
      otricopy(tri1, *farleft);
      /* Ensure that the destination of `farright' is sortarray[2]. */
      if (area > 0.0) {
        otricopy(tri2, *farright);
      } else {
        lnext(*farleft, *farright);
      }
    }
    if (b->verbose > 2) {
      printf("  Creating ");
	  printtriangle(m, b, &midtri);
      printf("  Creating ");
      printtriangle(m, b, &tri1);
      printf("  Creating ");
      printtriangle(m, b, &tri2);
      printf("  Creating ");
      printtriangle(m, b, &tri3);
    }
    return;
  } else {
    /* Split the vertices in half. */
    divider = vertices >> 1;
    /* Recursively triangulate each half. */
    divconqrecurse(m, b, sortarray, divider, 1 - axis, farleft, &innerleft);
    divconqrecurse(m, b, &sortarray[divider], vertices - divider, 1 - axis,
                   &innerright, farright);
    if (b->verbose > 1) {
      printf("  Joining triangulations with %d and %d vertices.\n", divider,
             vertices - divider);
    }
    /* Merge the two triangulations into one. */
    mergehulls(m, b, farleft, &innerleft, &innerright, farright, axis);
  }
}
这其中三角形的几何信息的操作宏有:setorg, setdest和setapex,拓扑信息的操作宏有:sym, lnext/lprev, onext/oprev, dnext/oprev, rnext/rprev。在论文[2]中对这些操作有比较详细的介绍。这些几何信息的操作函数比较简单,就是设置顶点org,dest和apex,在这里就不做解释了。下面还是重点讲下拓扑信息的操作宏。 这几个宏之间的关系式如下

onext = lprev * sym;
opre = sym * lnext;
dnext = sym * lpre;
dpre = lnext * sym;
rnext = sym * lnext * sym;
rpre = sym * lpre * sym;
这些宏所产生的结果是

a: org, b: dest, c: apex;
abc表示三角形,ab表示三角形的邻接边,*表示其他未标识的顶点。
sym(abc) = ba*;
lnext(abc) = bca;
lpre(abc) = cab;
onext(abc) = ac*;
opre(abc) = a*b;
dnext(abc) = *ba;
dpre(abc) = cb*;
rnext(abc) = *a*;
rpre(abc) = b**;

所以分析方法divconqrecurse:(假设sortarray[0]: a, sortarray[1]: b, sortarray[2]:c

i.	当顶点数为2时
Step1:
farleft: ab*	lprev: *ab	lprev: b*a
farright: ba*	lnext: a*b	lnext: *ba
Step2:
farright: *ba
lprev(farright, farleft) ->farleft: a*b
ii.	当顶点数为3且三点共线时:
Step1:
midtri: ab*	lnext: b*a	lnext: *ab
tri1: ba*	lprev: *ba	lprev: a*b
tri2: cb*	lnext: b*c	lnext: *cb
tri3: bc*	lprev: *bc	lprev: c*b
Step2:
otricopy(tri1, farleft) ->farleft: a*b
otricopy(tri2, farright) ->farright: *cb
iii.	当顶点为3且为逆序时:
Step1:
midtri: abc	lnext: bca	lnext: cab
tri1: ba*					lprev: *ba	lprev: a*b
tri2: cb*					lnext: b*c			lnext: *cb
tri3: ac*							lprev: *ac	lprev: c*a
Step2:
otricopy(tri1, farleft) ->farleft: a*b
otricopy(tri2, farright) –>farright: *cb
iv.	当顶点为3且顺序时:(保证组装三角形时仍为逆时针)
Step1:
Midtri: acb	lnext: cba	lnext: bac
Tri1: ca*					lprev: *ca	lprev: a*c
Tri2: bc*					lnext: c*b			lnext: *bc
Tri3: ab*							lprev: *ab	lprev: b*a
Step2:
otricopy(tri1, farleft) ->farleft: a*c
lnext(farleft, farright) ->farright: *ca
下面再补充几个概念:

symmetric与flip
        symmetric是对称的意思,它与flip是一个对立的概念。如何对立呢?我先来讲一下direction和orientation这两个概念。direction可以这样子看,一条有向线段,从它的起始点位置到它重点位置的方向,这个方向是direction;orientation则更直观,表示逆时针顺序或者顺时针顺序。定义一条有direction和orientation的边,symmetric是指与direction相反,orientation相同的边;而flipped是指与orientation相反,direction相同的边。如下所示示意图:

left与right, interior与exterior

        l其实代表左面的意思,操作宏rnext中的r代表的是右面的意思。左右面的概念我给出一张图大家就能明白了:


左边的边表示是使用参数化方程来表示的:P = Q(S, T);右边的边表示是使用隐式方程来表示的:Q(S, N) = 0。知道边的切线方向T,要求得边的法向N,将T逆时针旋转90°即得N。如右图所示,假如存在点P使得Q(S, N) < 0,则点P在边的内侧(interior);如果存在点P’使得Q(S, N) > 0,则点P’在边的外侧(exterior);如果存在点P’’使得Q(S, N) = 0,则点P’’在边上。对应的,如左图所示,边的内侧与边的右侧相同。

      一个逆时针环绕的三角形,三角形本身是三角形所有边的左面,三角形外部是三角形所有边的右面

文献:
[1] Jonathan Richard Shewchuk 《Triangle: Engineering a 2D Quality Mesh Generator and Delaunay Triangulator》
[2] LEONIDAS GUIBAS and JORGE STOLFI 《Primitives for the Manipulation of General Subdivisions and the Computation of Voronoi Diagrams》
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值