Note: The description became a little longer than expected. Do you know a readable implementation of this algorithm using this mesh? Please let me know!
I'm trying to implement Catmull-Clark subdivision using Matlab, because later on the results have to be compared with some other stuff already implemented in Matlab. First try was with a Vertex-Face mesh, the algorithm works but it is not very efficient, since you need neighbouring information for edges and faces. Therefore, I'm now using a half-edge mesh. See also
Designing a Data Structure for Polyhedral Surfaces, by Lutz Kettner (PDF link on that page).
My problem lies in finding the Twin HalfEdges, I'm just not sure how to do this. Below I'm describing my thoughts on the implementation, trying to keep it concise.
Half-Edge mesh (using indices to Vertices/HalfEdges/Faces):
Vertex (x,y,z,Outgoing_HalfEdge)
HalfEdge (HeadVertex (or TailVertex, which one should I use), Next, Face, Twin).
Face (HalfEdge)
To keep it simple for now, assume that every face is a quadrilateral. The actual mesh is a list of Vertices, HalfEdges and Faces. The new mesh will consist of NewVertices, NewHalfEdges and NewFaces, like this (note: Number_... is the number of ...):
NumberNewVertices: Number_Faces + Number_HalfEdges/2 + Number_Vertices
NumberNewHalfEdges: 4 * 4 * NumberFaces
NumberNewfaces: 4 * NumberFaces
Catmull-Clark:
Find the FacePoint (centroid) of each Face:
--> Just average the x,y,z values of the vertices, save as a NewVertex.
Find the EdgePoint of each HalfEdge:
--> To prevent duplicates (each HalfEdge has a Twin which would result in the same HalfEdge)
--> Only calculate EdgePoints of the HalfEdge which has the lowest index of the Pair.
Update old Vertices
Ok, now all the new Vertices are calculated (however, their Outgoing_HalfEdge is still unknown). Next step to save the new HalfEdges and Faces. This is the part causing me problems!
Loop through each old Face, there are 4 new Faces to be created
(because of the quadrilateral assumption)
First create the 4 new HalfEdges per New Face, starting at the FacePoint to the Edgepoint
Next a new HalfEdge from the EdgePoint to an Updated Vertex
Another new one from the Updated Vertex to the next EdgePoint
Finally the fourth new HalfEdge from the EdgePoint back to the FacePoint.
The HeadVertex of each new HalfEdge is known, the Next HalfEdge too. The Face is also known (since it is the new face you're creating!). Only the Twin HalfEdge is unknown, how should I know this?
By the way, while looping through the Vertices of the new Face, assign the Outgoing_HalfEdge to the Vertices. This is probably the place to find out which HalfEdge is the Twin.
Finally, after the 4 new HalfEdges are created, save the Face with the HalfVertex index the last newly created HalfVertex.
I hope this is clear, if needed I can post my (obviously not-yet-finished) Matlab code.
Edit: Thanks for moving my thread. I posted a link to the source code in a comment, please notice that this implementation considers general polygonal meshes, so not only quadrilateral meshes.
Furthermore, the twins of new HalfEdges 1 and 4 (red numbers in each new Face) are rather easy to find if you consider an old quadrilateral face divided into 4 new Faces (green numbers):
So, how to find the Twins of the 2- and 3 HalfEdges?
It seems the conceptual problem you've had is that you're trying to add half-edges one at a time and then wondering how they add up. Edges, though, are the real unit of modification, so you should always add them in pairs.
To implement (a single pass of) the algorithm, I'll annotate each of the model elements with a "newly created" flag, which indicates whether the element was created as a result of the algorithm. The top-level loop is to iterate on non-modified faces.
First ensure that each of the original edges of the face has been split. While doing this, create a list of each "new" vertex indicent to the face; these are the midpoints.
-- a. To split an edge, we first locate the corresponding half-edge. Create a new vertex. We insert a new pair of half-edges into each linked list, adjusting the endpoints to the new vertex. Mark all four of the half-edges as new as well as the new vertex.
The first stop of subdividing the face is different than the others. Create a new vertex V to be in the middle of the old face and select a new vertex W incident to the face. We'll connect them as follows. Suppose the linked list of edges near W looks like ..aWb... Create a new pair of half edges c and d. Replace W in the linked list with WcVdW to make the list '..aWcVdWb..'. This creates a "floating edge" in the middle of the face. The data structure, however, ensures that we have a linked list of half-edges that represent the inner perimeter of the polygon. Mark vertex W and the half edges c and d as new.
Now for each remaining new vertex, we'll create a new pair of half-edges, but this time each pair will also create a new face. Select the previous ..cVdWb.. linked list sequence. Because all the original edge have been subdivided, that list extends to ..cVdWbXeYf... X is an old vertex and Y is a new one. Create a new pair of half edges g and g that will connect vertices V and Y. Extract the sequence VdWbXeY from the linked list and add g to it to create a new face [VdWbXeYg]. Add the half edge h to connect V and Y in the old face to make ..cVhYf... Mark the new face as new. If there are not more new vertices, we're done. If not, map the names ..cVhYf.. to ..cVdWb.. for iteration.
The notation is kind of nasty, but conceptually its pretty easy. Each of the three steps adds half-edges in pairs; in step 1 by dividing an edge, in steps 2 and 3 by adding them. Each of these additions keeps the incidence invariants of the polyhedron representation intact, which means you get improved locality of modification in the code.
Related
I'm working through the polygon triangulation algorithm in Computational Geometry:
Algorithms and Applications, 3rd edition, by Mark de Berg and others. The data structure used to represent the polygon is called a "doubly-connected edge list". As the book describes, "[it] contains a record for each face, edge, and vertex". Each edge is actually stored as two "half edges", representing each side of the edge. This makes it easier to walk the half edges around a face. The half edge records look like:
// Pseudocode. No particular language. The properties need to be pointers/references of some kind.
struct HalfEdge {
Vertex origin;
HalfEdge twin;
Face incidentFace;
HalfEdge next;
HalfEdge previous;
}
Now imagine I'm processing this polygon, with one face (so far) called Face1. I need to add a diagonal at the dotted line. This will create a new face (Face2). It seems like I need to walk all those HalfEdges that now surround Face2 and set their incidentFace property to point to Face2.
But the book says:
The diagonals computed for the split and merge vertices are added to the doubly-connected edge list. To access the doubly-connected edge list we use cross-pointers between the edges in the status structure and the corresponding edges in the doubly-connected edge list. Adding a diagonal can then be done in constant time with some simple pointer manipulations.
I don't see any further explanation. I'm not sure what a "cross pointer" means here. The "status structure" refers to a binary search tree of edges that is used to easily find the edge to the left of a given vertex, as the polygon is processed from top to bottom.
Can anyone explain this further? How are they able to update all the edges in constant time?
The same book (page 33) says:
Even more important is to realize that in
many applications the faces of the subdivision carry no interesting meaning
(think of the network of rivers or roads that we looked at before). If that is the case, we can completely forget about the face records, and the IncidentFace() field of half-edges. As we will see, the algorithm of the next section doesn’t need these fields (and is actually simpler to implement if we don’t need to update them).
So, you don't need to modify the incidentFace field in any half-edges after each diagonal insertion - you will be able to find vertices of all the monotone polygons using the next field of the HalfEdge record after all the necessary diagonals are inserted into the DCEL.
The most intricate job here is to set next and prev fields of newly inserted half-edges, and to modify these fields in existing half-edges. Exactly four existing half-edges need to be updated after each insertion - but it's not easy to find them.
As you already know, each DCEL vertex record contains a field, pointing to any half-edge, originating in this vertex. If you keep this field pointing to an internal half-edge or most recently inserted half-edge, then it'll be a little bit easier to find half-edges, which need updates in their next and prev fields after each insertion.
I'm trying to implement a vector graphics "drafting" system, where, in essence, users can draw lines on the screen and interact with the regions created by intersecting lines. I'm struggling with determining/evaluating what these regions are.
I've tried a few different solutions to this problem, chiefly keeping a list of edges and running a BFS to find shortest cycles, but this brought up a myriad of issues where the BFS would shortcut in illegal ways, and holes and degenerate edges caused more problems than I could count, so I moved on to a DCEL, half-edge system.
I've read seemingly everything I can on this topic, including two articles referenced frequently on here: http://kaba.hilvi.org/homepage/blog/halfedge/halfedge.htm and http://www.flipcode.com/archives/The_Half-Edge_Data_Structure.shtml. However, neither of these seem to answer this problem that I have when it comes to dynamically adding edges to the graph.
Let's say I start out with this single edge. Image
The half-edges connect with each other in a cycle, and the global, unbounded "outside face" is connected to one of the half edges. Easy, got that.
Then we add another edge, attached to the center vertex: Image
The new half-edges work fine, and we update the edges that flow into v1's next pointers to be the only other edges available that aren't their twins. Again, makes sense to me.
What confuses me to no end is what happens here, when we add a third edge to the center vertex: Image
I know that's what it's supposed to look like and link up to be, but I am so bewildered on how to achieve that programmatically, because I'm not sure how I can determine whether the edge (4,1) should point to edge (1,2), or edge (1,3) (similarly for what edge should point to (1,4)).
The answer seems obvious when looking at the image, but when you try to rationalize it in a robust, airtight algorithmic way, my brain melts and I can't figure it out. The textbook I'm reading (Computational Geometry, Mark de Berg et al., pg 35), just says
"[to test where the edge] should be in the cyclic order of the edges
around vertex v".
The algorithm given in the hilvi.org article for finding the outgoing and incoming edges to link up with doesn't even seem to work, as it will take vertex 1, and follow its outgoing edge's twin until it finds a "free" edge, which in this case, is (2,1) which is wrong. (Unless I'm understanding it incorrectly, I could be understanding this whole problem wrong.)
So I'm absolutely stumped. My only idea now is to create some sort of heading attribute for each half edge, where I measure the angle created by the edge, and choose the edges that way, and maybe that's right, but that seems so against what the half-edge structure seems to support, at least in the articles I'm reading about it, nothing seems to mention anything like that. Any help would be extremely appreciated. I've been on this problem for over a week now and just can't seem to get unstuck.
Right, so I've spent a lot of time thinking about this problem, and to be honest I'm kinda surprised that I can't find a direct answer to this issue. So, in case anyone in the future runs into a similar problem of wanting to populate a half-edge graph from the ground up, here's a solution that works. I don't have a blog, so I'm writing it here.
I have no idea if it's the best answer, but it works in linear time and seems simple to me.
I'll be dealing with the following objects/classes, that vary slightly from the conventional DCEL:
class Vertex {
x;
y;
edges = []; //A list of all Half Edges with their origin at this vertex.
//Technically speaking this could be calculated as needed,
and you could just keep a single outgoing edge, but I'm not
in crucial need of space in my application so I'm just
using an array of all of them.
}
class HalfEdge {
origin; //The Vertex which this half-edge emanates from
twin; // The half-edge pair to this half-edge
face; // The region/face this half-edge is incident to
next; // The half-edge that this half-edge points to
prev; // The half-edge that points to this half-edge
angle; //The number of degrees this hedge is CW from the segment (0, 0) -> (inf, 0)
}
class Face {
outer_edge; //An arbitrary half-edge on the outer boundary defining this face.
inner_edges = []; //A collection of arbitrary half-edges, each defining
//A hole in the face.
global; //A boolean describing if the face is the global face or not.
//This could also be done by having a single "global face" Face instance.
//This is simply how I did it.
}
For initializing a vertex at(x,y):
Verify that a vertex with the given (x,y) coordinates does not already exist. If it does, you don't have to do anything (except maybe return this existing vertex if you're using it immediately).
If it doesn't, allocate space for and create a new vertex with the corresponding x,y values, and with its incident edge null.
For initializing an edge from vertex A to vertex B:
Similarly to many of the articles about this topic, we create two new instances of HalfEdge, one from vertex A to B, one from B to A. They link to each other in that we set their twin, prev, and next pointers all to the other half-edge (hedge).
We also set the angle of the hedge. The angle is calculated clockwise from the positive x-axis. The function I implemented is below. This is super important to making this data structure work properly, and that fact that I haven't read anything in the literature about this being important makes me think there has to be a better way, but I digress.
setAngle(){
const dx = this.destination().x - this.origin.x;
const dy = this.destination().y - this.origin.y;
const l = Math.sqrt(dx * dx + dy * dy);
if (dy > 0) {
this.angle = toDeg(Math.acos(dx / l));
} else {
this.angle = toDeg(Math.PI * 2 - Math.acos(dx / l));
}
function toDeg(rads) {
return 180 * rads / Math.PI;
}
}
Next, we pair the vertices with their new edges by adding them to their Vertex's edge list, and then we sort the edge list by the hedge's angles from smallest (0) to largest (359).
Then and this is the crucial step, in order to link everything up properly, we grab the closest hedge to the new hedge we're trying to link up in CCW order. Basically, wherever our new hedge ends up in the edge list, it's that index - 1 (if index = 0, we return edges[edges.length - 1]). Take that edge's twin, and that becomes our AIn described in the hivli article above. BOut = AIn.next.
We set AIn.next = hedgeAB and similarly, hedgeAB.prev = AIn, then hedgeBA.next = AOut, AOut.prev = hedgeBA. Perform steps 3-5 for the hedgeBA as well, except running the CCW search on vertex B.
Then, if both vertex A and B were "old" vertices, meaning their edge lists now have at least 2 elements each, a new face has potentially been added, and we need to find it (the edge case is having two isolated edges and connecting them to create an unbounded bucket or cap shape)
For initializing a Face:
We need to find all the cycles in a graph. For my first implementation of this, I recalculated all of the cycles every single time, resetting all the faces. This isn't necessary, but it also isn't too expensive, since we're not running a search, everything is in linear time with respect to the number of cycles and number of vertices in each cycle.
To do this, we get a list of all the hedges in the graph. It doesn't really matter how you do this, I decided to keep an array of every hedge that I passed into my cycle-finder function each time.
Then we look through that list, while the list isn't empty, we take the first item and run its cycle, removing every hedge we find along the way from the list, and adding it into a new cycle, which we add to another list
With this new list of cycles, we need to determine whether the cycle is an inside/outside cycle. There are a lot of ways to do this, and the Computational Geometry book mentioned above has a great section about it. The one that I used is to calculate the area defined by each cycle. If the area is >= 0, the cycle is defined by "inside" hedges. Otherwise, it's defined by "outside" hedges.
The last step is to set all the face records, again, the aforementioned textbook has a lot of great details about this, but the basic idea is to basically create a virtual "graph" of these cycles, and connect outside cycles (which are holes in faces), to their corresponding inside cycles, which are the outer boundaries of faces. To do this, you look at the leftmost vertex of the cycle and extend a ray out infinitely to the left, and "connect" the cycle with the first downward-facing hedge of a cycle the ray hits (I'll leave the implementation up to you, I have no idea if my way is the best, in short, I checked every cycle with a leftmost vertex left of the current cycle and calculated the rightmost intersection with the y value of the leftmost vertex of the current cycle, and then checked if that was facing downward).
With this graph of cycles, run a BFS/DFS starting from each "inside-hedge" cycle (not the holes), and create a face with an arbitrary hedge from the inside-hedge cycle as the outer edge, (or null if it's the global face), and an arbitrary hedge from each found hole-cycle to the face's inner components.
Hey presto, that's it. If you check everything every time, that handles everything. It handles face splitting like a charm and is very robust and quick. I don't know if it's right, but it works.
I have a set of (boundary) points and I want to start triangulating them. From all references that I found, the authors only mentioned methods on how to add new points and hence create new edges (at the same time remove the old, bad points/edges), assuming that the points have been triangulated initially. So, how do I do the FIRST triangulation before I can use all the suggested methods (to add new points and edges) in the literature. For a simple case, say, if I have a circle with boundary points (x,y)=(r cos\theta,r sin\theta), I could possibly create a triangle for every three neighboring points. But then, there will be no edge crossing the region near the center of the circle.
Normal marching cubes finds 12 edges per cube, but you can do 3 edges per cube, save the edges inside an array, and then go through the cubes again, referencing the edges from the cubes adjacent rather than calculating them.
The process to reference adjacent cubes isn't clearly discussed on the Internet so anyone using marching cubes would be welcome to help find the details of the solution. do you know an implementation already?
here is a picture showing the 3 edges in yellow that you need for each cube, instead of 12.
EDIT- I just found this solution, although it's just a part of it:
Imagine 3 edges coming from the corner of the cube with lowerest coordinates. Then all other edges just belong to other cubes. If our cube has coordinates (x,y,z), the neiboring cubes have coordinates (x+1,y,z), (x,y+1,z), (x,y,z+1), (x+1,y+1,z), (x+1,y,z+1), (x,y+1,z+1). You can imagine the edge as a vector. Then the corner of the cube have edges (1,0,0), (0,1,0), (0,0,1). The cube with coordinates (x+1,y,z) have edges (0,1,0) and (0,0,1) that belong to our cube. The cube (x+1,y+1,z) has only one edge (0,0,1) that belongs to our cube. So if you store 4 elements for the cube you can access them like that:
edge1 = cube[x][y][z][0];
edge2 = cube[x][y][z][1];
edge3 = cube[x][y][z][2];
edge4 = cube[x+1][y][z][1];
edge5 = cube[x+1][y][z][2];
edge6 = cube[x][y+1][z][0];
edge7 = cube[x][y+1][z][2];
edge8 = cube[x][y][z+1][0];
edge9 = cube[x][y][z+1][1];
edge10 = cube[x+1][y+1][z][2];
edge11 = cube[x+1][y][z+1][1];
edge12 = cube[x][y+1][z+1][0];
Now which points edge7 connect? The answer is (x,y+1,z) and (x,y+1,z)+(0,0,1)=(x,y+1,z+1).
Now which cubes edge7 connect? It is more harder. We see that coordinate z is changes along the edge this means that neibour cube has the same z coordinate. Now all others coordinates change. Where we have +1, the cube has large coordinate. Where we have +0, the cube has smaller coordinates. So the edge connects cubes (x,y,z) and (x-1,y+1,z). Other 2 cubes that has the same edge are (x,y+1,z) and (x-1,y,z).
-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-=-=--=-=
EDIT2-
So I am doing this, and it isn't so simple. I have a loop which simultaneously calculate 8 points, 12 edges, the interpolation of edges, the bit values and a vertex the values for the edges, all in one loop.
so I am doing a new loop previous to it to calculate as much as possible and place it in arrays to used in the complicated loop.
I can recycle the interpolated values of the intersection points along edges, in an array, although I will have to recalculate all the points again in the complicated loop, because the values of the points I used to decide bit numbers that reference values in the vertex table. That confuses me! I thought that once I have the edge intersection values, I could use those directly to get the triangle tables, without having to calculate the points all over again!
in fact no.
anyway, here is another bit of information with someone that already did it, if only it was readable!
http://www.new-npac.org/projects/sv2all/sv2/vtk/patented/vtkImageMarchingCubes.cxx
scroll to this line: Cubes are responsible for edges on their min faces.
A simple way to reduce edge calculations in the way you are suggesting is to compute cubes one axis aligned plane at a time.
If you kept all of the cubes, with their edges, in memory, it would be easy to compute each edge only once and to find adjacent edges by indexing. However, you usually don't want to keep all the cubes in memory at once because of the space requirements.
A solution to this is to compute one plane of cubes at a time. i.e. an axis aligned cross-section, starting from one side and progressing to the opposite side. You then only need to keep at most two full planes of cubes in memory at a time. As you move through each plane you can reference shared edges in the previous plane and previously computed cubes in the current plane. As you move to the next plane you can deallocate the plane you will no longer need.
Edit: This article discusses doing just what I suggest:
http://alphanew.net/index.php?section=articles&site=marchoptim&lang=eng
Funny, because when I implemented my own MCs I came up with similar solution.
When you start working with MCs you treat them as a distinct cubes but if you want to go for high performance you'll need to create entire mesh as a whole, and creating vertex indices etc. is not so easy here. It gets even more interesting when you want to add smooth per-vertex normals :).
To solve this I created a simple index cache mechanism to store vertex indices for each edge.
Then, for each computed edge I have cube position x,y,z and edge index and I do as follows:
For each axis:
if the edge is on '+' side of axis:
replace edge index with its '-' side sibling
increment cube position along axis
This simple operation gives me the correct cube position, and edge index of 0,1,2. Then I compute a total cache index from x,y,z,edgeIndex values with simple bit rotations.
When I have cache index I check if it's bigger than -1. If it is then there was an already computed vertex at this edge and I can reuse it. If it's -1 I need to create a new vertex and store its index in the cache. This way you'll compute each vertex only once, and you can even add a normal value shared between every triangle containing your vertex.
Yes, I think I do it similar to kolenda. I have a struct with 5 ints: (cube)index and 4 vertexindices (A, B, C, D).
for the most inner loop (x), I have just lastXCache and nextXCache. On the 4 edges pointing in the -x direction, i ask if lastXCache.A != -1 and if so, assign the previously calculated value, etc.
In the +x direction I store calculated vertices in nextXCache. when cube is done: lastXCache = nextXCache;
For y and z direction it needs to be a list (unity term for mutable array), next y is next row (so sizex) and next z is the next plane (so sizex * sizey)
only diadvantage is that this way it has to run cube after cube, so serially. But you can calculate different chunks in parallel.
Another way I thought of that could be more parallel would need 2 passes: 1. calculate 3 edges every cube, when 1 is done -> 2. draw the triangles.
Don't really know what is better, but the way it actually works seems to be fast enough. even better with unity jobs. Create one IJob for 1 chunk/mesh.
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?
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.
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).