27

I'm working on implementing various subdivision algorithms (such as catmull-clark); to do this efficiently requires a good way to store information about a grid of tesselated polygons. I implemented the half-edge data structure as outlined by flipcode, but now I'm not sure how to populate the data structure from vertices!

My initial attempt was to

  • create vertices
  • group vertices into faces
  • sort vertices within faces (using their angle relative to the centroid)
  • for each face, grab the first vertex and then walk through the sorted vertex list to create a half-edge list.

However, this creates a list of faces (with half-edges) that don't have any information about adjacent faces! This also feels a bit wrong, because it seems as if the faces are really the first-class object and the edges provide auxiliary information; I really feel like I should be creating edges from the vertices and then sorting out the faces from there. But again, I'm not really sure how to go about it that way -- I can't think of a way to create a list of half-edges without creating the faces first.

Any suggestions for what the best way to go turning data about vertices (and faces) into half-edges?

Amir
  • 10,600
  • 9
  • 48
  • 75
nathan lachenmyer
  • 5,298
  • 8
  • 36
  • 57

2 Answers2

28

First, I'd like to point you to an excellent C++ implementation of the half-edge data structure: OpenMesh. If you want to use it, make sure you work you way through the tutorial. If (and only if) you do that, working with OpenMesh is quite straightforward. It also contains some nice methods on top of which you can implement subdivision or reduction algorithms.

Now to your question:

However, this creates a list of faces (with half-edges) that don't have any information about adjacent faces! This also feels a bit wrong, because it seems as if the faces are really the first-class object and the edges provide auxiliary information

I think this somewhat misses the point of the half-edge data structure. In a half-edge structure, it is the half-edges that carry the most information!

Quoting shamelessly from the OpenMesh documentation (see also the figure there):

  • Each vertex references one outgoing halfedge, i.e. a halfedge that starts at this vertex.
  • Each face references one of the halfedges bounding it.
  • Each halfedge provides a handle to
    • the vertex it points to ,
    • the face it belongs to
    • the next halfedge inside the face (ordered counter-clockwise) ,
    • the opposite halfedge ,
    • (optionally: the previous halfedge in the face ).

As you see, most information is stored in the half-edges - these are the primary objects. Iterating over meshes in this data-structure is all about cleverly following pointers.

However, this creates a list of faces (with half-edges) that don't have any information about adjacent faces!

This is perfectly ok! As you see above, a face references only one bounding half edge. Assuming a triangle mesh, the chain of pointers you follow to get the 3 adjacent triangles to a given face F is the following:

F -> halfEdge -> oppositeHalfEdge -> face

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

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

Optionally, you can use nextHalfEdge -> nextHalfEdge if you don't use the 'previous' pointers. This, of course, generalizes easily to quads or higher order polygons.

If you set the pointers listed above correctly when building your mesh, then you can iterate over all kinds of adjacencies in your mesh like this. If you use OpenMesh, you can use a bunch of special iterators that to the pointer chasing for you.

Setting the "opposite half edge" pointers is of course the tricky part when building a half-edge structure from a "triangle soup". I suggest to use a map data-structure of some kind to keep track of half-edges already created.

To be more specific, here is some very conceptual pseudo-code for creating a half-edge mesh from faces. I omitted the vertex part, which is simpler, and can be implemented in the same spirit. I assume that iteration over a face edges is ordered (e.g. clock-wise).

I assume half edges are implemented as structs of type HalfEdge, which contain the pointers listed above as members.

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

Let Edges be a map from pairs of vertex identifiers to pointers to the actual half-edge instances, e.g.

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

in C++. Here is the construction pseudo-code (without the vertex and face part):

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: Made the code a bit less pseudo, to be more clear about the Edges map and the pointers.

DCS
  • 3,354
  • 1
  • 24
  • 40
  • 2
    Thanks for the thoughtful response! I've read the OpenMesh documentation, and it describes quite well how to *retrieve* information from the data structure. However, it doesn't explain very well how the data structure is *initialized*. Given that I have nothing but a file full of vertices (and faces that have knowledge of their associated vertices) I'm curious about how to create a new half-edge data structure. And, in particular, if its possible to do it without knowledge of what the faces are (because it appears to me right now that having a face-vertex structure is a prerequisite). – nathan lachenmyer Mar 12 '13 at 16:33
  • I added a very conceptual pseudo-code to be more specific about creation from scratch. What do you mean by "without knowledge of what the faces are"? Do you mean the problem of meshing a point cloud (i.e. one that does have no face definition at all)? – DCS Mar 12 '13 at 17:01
  • Glad it helped! Make sure you get the post with my latest edit, there was a small bug initially. – DCS Mar 12 '13 at 17:39
  • After trying to implement your solution, I've realized that the tricky part is the matching up the edges with their twins and their neighbors. I have to go through the face once to create all half-edges, and then a second time to create the linkages between half-edges. But it is not clear how to do that in an organized manner that doesn't forget to assign neighbors/twins to the half-edges... – nathan lachenmyer Mar 14 '13 at 22:22
  • This problem should be solved with the map I called "Edges" in the pseudo code. It should map edge IDs, implemented as vertex index pairs, e.g. (u,v), to pointers to your actual half edge data structure. Something like map< pair, HalfEdge > in C++. When you go through your half edges the second time you can look in the map if there is an opposite half edge, retrieve the pointer you need from the map, an set pointers in both half edges. – DCS Mar 14 '13 at 22:35
  • 1
    Lets say you work with a quad mesh. You are creating a face with vertices (a,b,c,d) - you have that information, otherwise you could not create it. Say you are at edge (b,c) in the second loop. Then you know that the next half edge must be (c,d), and you know that the map contains a key (c,d) which maps to the pointer to you need: Edges[ pair(b,c) ]->nextHalfedgePtr = Edges[ pair(c,d) ]. – DCS Mar 15 '13 at 06:46
  • Wanting to keep my checkmark, I clarified the pseudo-code with respect to the Edges map. The problem is, when I post complete C++ code I need to make a bunch of assumptions about how stuff is represented, and it will become quite lengthy. – DCS Mar 15 '13 at 12:23
  • Any of you have the solution? I want to make this half-edge too, but kinda stuck along the way. – Bla... Feb 13 '16 at 08:42
  • 1
    This doesn't explain what we do about the outside edges of the mesh. There must be some edges that don't have faces on both sides. How do we give them opposite half edges? – Geo Sep 21 '17 at 03:55
  • 1
    @Geo Half-edge only works for manifold meshes (meshes that have no "cuts" and can be approximated by a disk at any point, i.e. have two faces at each side of edge). For non-manifold meshes consider winged-edge data structure. – Nikole Feb 12 '18 at 01:03
0

Consider we have a list of triangles, each of which is specified by 3 vertex IDs in counterclockwise order, and the task is to construct the half-edge data structure representing this triangulation. If the vertices are given as 3d-vectors (as in STL file format) then the works starts from giving unique identifiers to each distinct vertex, which is accomplish via a hash map from 3d-vector to vertex ID.

After that we progressively consider every triangle from the list. And the first thing is to find or create its three edges (each of which consists of a pair of half-edges). If some edge is shared by two triangles and the other triangle was already added in the data structure then we find that edge, otherwise new edge must be created.

To find whether an edge between vertices v1 and v2 already exists, we use a flat map from a vertex to one of half-edges with the origin in it. All other half-edges with the same origin can be enumerated using already constructed half-edge data structure. So the algorithm for this step is to look at every half-edge originating in v1 and test whether its destination is v2. If no such edge is found, then two half-edges must be created. In the terms of flipcode: pair of one created half-edge points on the other, vert on v1 and v2 respectively, and next on existing half-edges in v1 and v2, or if it was the first half-edge in a vertex then on itself.

When all 3 edges of the current triangle are found or created, face field of half-edges having that triangle at the left points to new HE_face record representing the triangle.

After all triangles are passed, the data structure is ready. There are certain optimizations and improvements that can be implemented, but the basic idea is as presented above.

All mesh libraries based on half-edge data structure construct it during mesh opening from a standard file format, such as STL/PLY/OBJ/OFF/… So it is a good idea at least to look at their code, and probably even integrate them in your software instead of creating your own implementation:

  • OpenMesh is a good C++ library already presented in the other answer.
  • MeshLib is a younger library also written in C++, which opens standard file formats considerably faster (based on my observations especially STLs); this suggests better optimizations in the conversion (see MRMeshBuilder.cpp) in half-edge data structure (MRMeshTopology.h).
Fedor
  • 17,146
  • 13
  • 40
  • 131