web-dev-qa-db-ja.com

頂点からのハーフエッジデータ構造の初期化

私はさまざまな細分化アルゴリズム(catmull-clarkなど)の実装に取り​​組んでいます。これを効率的に行うには、テッセレートされたポリゴンのグリッドに関する情報を格納するための優れた方法が必要です。ハーフエッジデータ構造を フリップコードで概説 として実装しましたが、頂点からデータ構造を設定する方法がわかりません!

私の最初の試みは

  • 頂点を作成する
  • 頂点を面にグループ化する
  • 面内の頂点を並べ替えます(図心に対する角度を使用)
  • 面ごとに、最初の頂点を取得してから、並べ替えられた頂点リストをウォークスルーして、ハーフエッジリストを作成します。

ただし、これにより、隣接する面に関する情報がない面(ハーフエッジ付き)のリストが作成されます。面が実際には一流のオブジェクトであり、エッジが補助情報を提供しているように見えるため、これも少し間違っているように感じます。頂点からエッジを作成し、そこから面を並べ替える必要があると本当に感じています。しかし、繰り返しになりますが、その方法がよくわかりません。最初に面を作成せずに、ハーフエッジのリストを作成する方法を考えることはできません。

頂点(および面)に関するデータをハーフエッジに変換するための最良の方法について何か提案はありますか?

22

まず、ハーフエッジデータ構造の優れたC++実装を紹介します: OpenMesh 。それを使用したい場合は、チュートリアルを順調に進めてください。それを行う場合(そしてその場合のみ)、OpenMeshの操作は非常に簡単です。また、細分割または縮小アルゴリズムを実装できるいくつかの優れたメソッドも含まれています。

今あなたの質問に:

ただし、これにより、隣接する面に関する情報がない面(ハーフエッジ付き)のリストが作成されます。面が実際には一流のオブジェクトであり、エッジが補助情報を提供しているように見えるため、これも少し間違っているように感じます

これは、ハーフエッジデータ構造のポイントをいくらか見逃していると思います。ハーフエッジ構造では、最も多くの情報を伝達するのはハーフエッジです。

OpenMeshドキュメント から恥知らずに引用(そこの図も参照):

  • 各頂点は、1つの出力ハーフエッジ、つまりこの頂点から始まるハーフエッジを参照します。
  • 各面は、それを囲むハーフエッジの1つを参照します。
  • 各ハーフエッジはへのハンドルを提供します
    • それが指す頂点、
    • それが属する顔
    • 面の内側の次のハーフエッジ(反時計回りに順序付け)、
    • 反対側のハーフエッジ、
    • (オプション:面の前のハーフエッジ)。

ご覧のとおり、ほとんどの情報はハーフエッジに格納されています-これらは主要なオブジェクトです。このデータ構造のメッシュを反復処理することは、ポインターを巧みに追跡することです。

ただし、これにより、隣接する面に関する情報がない面(ハーフエッジ付き)のリストが作成されます。

これは完全に大丈夫です!上で見たように、面はonly1つの境界の半分のエッジを参照します。三角形メッシュを想定すると、特定の面Fに隣接する3つの三角形を取得するためにたどるポインタのチェーンは次のとおりです。

F -> halfEdge -> oppositeHalfEdge -> face

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

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

オプションで、「前の」ポインタを使用しない場合は、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) ];
       }
    }
 }

EDIT:Edgesマップとポインターについてより明確にするために、コードの疑似を少し少なくしました。

23
DCS