从顶点初始化半边数据结构

Posted

技术标签:

【中文标题】从顶点初始化半边数据结构【英文标题】:Initializing Half-edge data structure from vertices 【发布时间】:2013-02-28 05:11:50 【问题描述】:

我正在努力实现各种细分算法(例如 catmull-clark);要有效地做到这一点,需要一种很好的方法来存储有关镶嵌多边形网格的信息。我将半边数据结构实现为outlined by flipcode,但现在我不确定如何从顶点填充数据结构!

我最初的尝试是

创建顶点 将顶点分组为面 对面内的顶点进行排序(使用它们相对于质心的角度) 对于每个面,抓取第一个顶点,然后遍历已排序的顶点列表以创建半边列表。

但是,这会创建一个没有关于相邻面的任何信息的面列表(带有半边)!这也感觉有点不对劲,因为看起来人脸真的是一等物体,边缘提供了辅助信息;我真的觉得我应该从顶点创建边缘,然后从那里整理出面。但同样,我真的不知道该怎么做——我想不出一种方法来创建一个半边列表而不先创建面。

关于将顶点(和面)的数据转换为半边的最佳方法有什么建议吗?

【问题讨论】:

【参考方案1】:

首先,我想向您指出半边数据结构的出色 C++ 实现:OpenMesh。如果您想使用它,请确保您按照教程进行操作。如果(且仅当)您这样做,使用 OpenMesh 将非常简单。它还包含一些不错的方法,您可以在这些方法之上实现细分或归约算法。

现在回答你的问题:

但是,这会创建一个没有关于相邻面的任何信息的面列表(带有半边)!这也感觉有点不对劲,因为看起来人脸真的是第一类对象,边缘提供了辅助信息

我认为这有点忽略了半边数据结构的要点。在半边结构中,承载信息最多的是半边!

无耻地引用OpenMesh documentation(另见那里的图):

每个顶点引用一个传出半边,即从该顶点开始的半边。 每个面都引用一个包围它的半边。 每个半边提供一个句柄 它指向的顶点, 它所属的脸 面内的下一个半边(逆时针顺序), 对面的半边, (可选:面部的前半边)。

如您所见,大部分信息都存储在半边中 - 这些是主要对象。在这个数据结构中迭代网格就是巧妙地遵循指针。

但是,这会创建一个没有关于相邻面的任何信息的面列表(带有半边)!

这完全没问题!正如您在上面看到的,一个面引用一个边界半边。假设一个三角形网格,您遵循的指针链将 3 个相邻三角形指向给定面 F 如下:

F -> halfEdge -> oppositeHalfEdge -> face

F -> halfEdge -> nextHalfEdge -> oppositeHalfEdge -> face

F -> halfEdge -> previousHalfEdge -> oppositeHalfEdge -> face

如果您不使用“previous”指针,您可以选择使用nextHalfEdge -> nextHalfEdge。当然,这很容易推广到四边形或更高阶的多边形。

如果您在构建网格时正确设置了上面列出的指针,那么您可以像这样迭代网格中的各种邻接。如果你使用 OpenMesh,你可以使用一堆特殊的迭代器来为你追逐指针。

在从“三角形汤”构建半边结构时,设置“对边半边”指针当然是棘手的部分。我建议使用某种地图数据结构来跟踪已创建的半边。

更具体地说,这里有一些非常概念性的伪代码,用于从面创建半边网格。顶点部分我省略了,比较简单,可以本着同样的精神来实现。我假设对面边缘的迭代是有序的(例如顺时针)。

我假设半边被实现为HalfEdge 类型的结构,其中包含上面列出的指针作为成员。

   struct HalfEdge
   
      HalfEdge * oppositeHalfEdge;
      HalfEdge * nextHalfEdge;
      Vertex * vertex;
      Face * face;
   

Edges 是从顶点标识符对到指向实际半边实例的指针的映射,例如

map< pair<unsigned int, unsigned int>, HalfEdge* > Edges;

在 C++ 中。这是构造伪代码(没有顶点和面部分):

map< pair<unsigned int, unsigned int>, HalfEdge* > Edges;

for each face F

   for each edge (u,v) of F
   
      Edges[ pair(u,v) ] = new HalfEdge();
      Edges[ pair(u,v) ]->face = F;
   
   for each edge (u,v) of F
   
      set Edges[ pair(u,v) ]->nextHalfEdge to next half-edge in F
      if ( Edges.find( pair(v,u) ) != Edges.end() )
      
         Edges[ pair(u,v) ]->oppositeHalfEdge = Edges[ pair(v,u) ];
         Edges[ pair(v,u) ]->oppositeHalfEdge = Edges[ pair(u,v) ];
       
    
 

编辑:减少了代码的伪性,以便更清楚地了解 Edges 映射和指针。

【讨论】:

感谢您的周到回复!我已经阅读了 OpenMesh 文档,它很好地描述了如何从数据结构中检索信息。但是,它并没有很好地解释数据结构是如何初始化的。鉴于我只有一个充满顶点的文件(以及知道相关顶点的面),我很好奇如何创建一个新的半边数据结构。并且,特别是,如果它可以在不知道面是什么的情况下做到这一点(因为现在在我看来,具有面-顶点结构是先决条件)。 我添加了一个非常概念性的伪代码,以更具体地从头开始创建。 “不知道面孔是什么”是什么意思?您的意思是网格化点云的问题(即根本没有人脸定义的点云)吗? 假设您使用四边形网格。您正在创建一个具有顶点 (a,b,c,d) 的面 - 您拥有该信息,否则您无法创建它。假设您在第二个循环中处于边缘 (b,c) 处。然后你知道下半边必须是 (c,d),并且你知道 map 包含一个键 (c,d),它映射到你需要的指针: Edges[ pair(b,c) ]-> nextHalfedgePtr = 边[对(c,d)]. 这并没有解释我们如何处理网格的外边缘。一定有一些边在两边都没有面。我们如何给它们相对的半边? @Geo Half-edge 仅适用于流形网格(没有“切割”并且可以在任何点用圆盘近似的网格,即边的每一侧都有两个面)。对于非流形网格,请考虑翼边数据结构。

以上是关于从顶点初始化半边数据结构的主要内容,如果未能解决你的问题,请参考以下文章

HalfEdge半边数据结构详解

半边数据结构Half Edge

数据结构6——DFS

数据结构7——BFS

<2x1;OpenMesh译稿:使用并理解OpenMesh-OpenMesh的功能和目标

图的邻接矩阵存储