翻译文章来自:http://www.flipcode.com/archives/The_Half-Edge_Data_Structure.shtml
介绍
表示多边形网格(polygon mesh)的一个常用方式就是使用共享的顶点列表和面的列表(里面包含面所含的顶点)。这样的表示方法在许多情况下都非常方便和高效,但是在某些特定的领域,反而会效率比较低。
举例来说,网格简化(mesh simplification)通常需要把一条边退化成一个顶点。这个操作需要删除与这条边邻接的面并且更新面中存储的与这条边相关的顶点数据。这种多边形“手术”需要我们了解网格的组成部分的邻接关系,例如面和顶点。虽然我们肯定可以用之前提及的网格表示方法来实现网格简化,但是代价会比较高。许多情况下需要遍历面或者点的列表,或者二者都有。
在一个多边形网格上其他类型的的临近查询包括:
- 哪条边用到了这个点
- 哪条边用到这个点
- 哪个面临近这条边
- 哪条边临近这个面
- 哪个面临近这个面
为了使得这些类型的临近查询更加有效,发展出更加精细的边界表示方案(b-reps),更加明确地建模表示点、边和面,并且也临近信息也相应地储存进去了。
这些表示类型中最常见的其中一种是“翼边数据结构”(winged-edge)。其中每条边包含了指向其两个顶点,两个邻接面的指针以及指向从其终点延伸出去的四条边的指针。这种结构可以让我们在常数时间内判断哪些点与面与该边连接,但是面对其他类型的查询,这种结构会带来更大的开销。
半边(half-edge)数据结构是一种略微复杂的边表示方法(b-reps)并且在其上做之前提到的所有操作都可以在常数时间完成(*)。更优秀的是,即使包含了面、顶点和边的邻接信息,数据结构的大小是固定的(没有使用动态数组)且紧凑的。
这些特性是的半边数据结构成为了许多应用的最佳选择,但是其只能表示流形表面(manifold surfaces),而在某些情况下被证明是不可用的(流形的数学定义指的是曲面上的每一点都有一个小邻域,小邻域有一个圆盘的拓扑结构)。。对于多边形网格来说,这意味着每条边是且只能是两个面的边,T-型接合(t-junctions)和内部多边形(internal polygons)和网格中的空缺时,不能使用半边结构。
(*)更确切地说,每个片段信息的常数时间集中。比如,当查询一个顶点周围所有的临近边时,在临近顶点的边的操作上与数量是线性的,但是每条边是常数时间。
结构
之所以称为半边数据结构,是因为在这个结构中并不会存储网格的边的信息,取而代之的是半边(half-edges)。半边这么叫,就是因为它其实是一条边的一半,等于是把一条边保持长度不变,形式上分为两条半边(因为按照定义边没有宽度或者至少是单位宽度,这就是一个假象划分)。这两条半边组合在一起称为一条边,也就是一条边等于一对半边。半边是有方向的,并且一条边的一对半边有相反的方向。
下图展示了三角形网格的一小部分的半边描述。蓝色是顶点,橙色是半边,其中的箭头表示指针(为了不画得太乱,省略了一些)
可以很清楚地看到围绕一个面的三条边形成了一个环(circular linked list)。这个链表既可以顺时针也可以逆时针,只要在使用中保持一致就好。环中的每半边存储一个指向以其为边的面的指针(图中没有画出来)、指向半边终点的指针(同样没画出来)和指向它的另一条半边(就是合起来组成一条边的那个半边)的指针。在C语言中,结构如下
- struct HE_edge
- {
- HE_vert* vert; // vertex at the end of the half-edge
- HE_edge* pair; // oppositely oriented adjacent half-edge
- HE_face* face; // face the half-edge borders
- HE_edge* next; // next half-edge around the face
- };
struct HE_edge
{
HE_vert* vert; // vertex at the end of the half-edge
HE_edge* pair; // oppositely oriented adjacent half-edge
HE_face* face; // face the half-edge borders
HE_edge* next; // next half-edge around the face
};
在半边数据结构中的点储存着x,y,z的位置和以其为起始点的半边的指针。在任意给定的点上存在超过一条我们可以选择的半边
,但是我们只需要选择其中一条并且是哪一条没关系,在下面的查询方法中我们会看到解释。在C中点数据结构如下
- struct HE_vert
- {
- float x;
- float y;
- float z;
- HE_edge* edge; // one of the half-edges emantating from the vertex
- };
struct HE_vert
{
float x;
float y;
float z;
HE_edge* edge; // one of the half-edges emantating from the vertex
};
对于一个半边数据结构的简单形式,一个面仅仅需要储存一个围绕它的边的指针,在一些特定场合可能要求我们储存比如材质和法向一类的信息。和上面一样,虽然有很多边围绕着面,我们只需要储存其中一条,而无所谓是哪一条。下面是在C中面的数据结构
struct HE_face
{
HE_edge* edge; // one of the half-edges bordering the face
};
邻近查询
关于邻近查询的大多数答案就储存在边、点和面的数据结构里。比如,围绕着半边的面或者点可以轻易地找出
- HE_vert* vert1 = edge->vert;
- HE_vert* vert2 = edge->pair->vert;
- HE_face* face1 = edge->face;
- HE_face* face2 = edge->pair->face;
HE_vert* vert1 = edge->vert;
HE_vert* vert2 = edge->pair->vert;
HE_face* face1 = edge->face;
HE_face* face2 = edge->pair->face;
稍微复杂一点的例子,找到围绕着面的所有半边。因为围绕着面的所有半边形成了一个环状列表,并且面结构中储存了其中一个半边的指针,因此我们可以
- HE_edge* edge = face->edge;
- do {
- // do something with edge
- edge = edge->next;
- } while (edge != face->edge);
HE_edge* edge = face->edge;
do {
// do something with edge
edge = edge->next;
} while (edge != face->edge);
类似地,我们可能对围绕着特定点的边或者是面感兴趣。参照之前的图解,指针组成了围绕点的一个圈,迭代程序类似,如下:
- HE_edge* edge = vert->edge;
- do {
- // do something with edge, edge->pair or edge->face
- edge = edge->pair->next;
- } while (edge != vert->edge);
HE_edge* edge = vert->edge;
do {
// do something with edge, edge->pair or edge->face
edge = edge->pair->next;
} while (edge != vert->edge);
值得注意的是所有的例子中都没有检查空指针,这是因为流形曲面的限制。为此要求,所有的指针都必须是有效的。
其他邻近关系都可以通过下面的这些例子被迅速找到。
附注
哈佛图形档案网格实验室有一套完整的半边数据结构实现代码,可以参看http://www.cs.deas.harvard.edu/~xgu/mesh/
之前提到半边数据结构在某型情况下不能用。但是翼边数据结构可以。更多的信息可以查看"Computer Graphics: Principles and Practice"