public Face trail; /* "stack" for conversion to strips */ #endregion Fields #region Methods public static void AddToTrail(ref Face f, ref Face t) { f.trail = t; t = f; f.marked = true; }
public static void FreeTrail(ref Face t) { while (t != null) { t.marked = false; t = t.trail; } }
/* MakeFace( newFace, eOrig, fNext ) attaches a new face and makes it the left * face of all edges in the face loop to which eOrig belongs. "fNext" gives * a place to insert the new face in the global face list. We insert * the new face *before* fNext so that algorithms which walk the face * list will not see the newly created faces. */ private static void MakeFace(Face newFace, HalfEdge eOrig, Face fNext) { HalfEdge e; Face fPrev; Face fNew = newFace; fNew.indexDebug = faceIndex++; // insert in circular doubly-linked list before fNext fPrev = fNext.prevFace; fNew.prevFace = fPrev; fPrev.nextFace = fNew; fNew.nextFace = fNext; fNext.prevFace = fNew; fNew.halfEdgeThisIsLeftFaceOf = eOrig; fNew.trail = null; fNew.marked = false; // The new face is marked "inside" if the old one was. This is a // convenience for the common case where a face has been split in two. fNew.isInterior = fNext.isInterior; // fix other edges on this face loop e = eOrig; do { e.leftFace = fNew; e = e.nextEdgeCCWAroundLeftFace; } while (e != eOrig); }
/* KillFace( fDel ) destroys a face and removes it from the global face * list. It updates the face loop to point to a given new face. */ private static void KillFace(Face fDel, Face newLface) { HalfEdge e, eStart = fDel.halfEdgeThisIsLeftFaceOf; Face fPrev, fNext; /* change the left face of all affected edges */ e = eStart; do { e.leftFace = newLface; e = e.nextEdgeCCWAroundLeftFace; } while (e != eStart); /* delete from circular doubly-linked list */ fPrev = fDel.prevFace; fNext = fDel.nextFace; fNext.prevFace = fPrev; fPrev.nextFace = fNext; }
// __gl_meshMakeEdge creates one edge, two vertices, and a loop (face). // The loop consists of the two new half-edges. public HalfEdge MakeEdge() { var newVertex1 = new ContourVertex(); var newVertex2 = new ContourVertex(); var newFace = new Face(); HalfEdge e; e = MakeEdge(halfEdgeHead); MakeVertex(newVertex1, e, vertexHead); MakeVertex(newVertex2, e.otherHalfOfThisEdge, vertexHead); MakeFace(newFace, e, faceHead); return e; }
/* __gl_meshZapFace( fZap ) destroys a face and removes it from the * global face list. All edges of fZap will have a null pointer as their * left face. Any edges which also have a null pointer as their right face * are deleted entirely (along with any isolated vertices this produces). * An entire mesh can be deleted by zapping its faces, one at a time, * in any order. Zapped faces cannot be used in further mesh operations! */ public static void meshZapFace(Face fZap) { HalfEdge eStart = fZap.halfEdgeThisIsLeftFaceOf; HalfEdge e, eNext, eSym; Face fPrev, fNext; /* walk around face, deleting edges whose right face is also null */ eNext = eStart.nextEdgeCCWAroundLeftFace; do { e = eNext; eNext = e.nextEdgeCCWAroundLeftFace; e.leftFace = null; if (e.rightFace == null) { /* delete the edge -- see __gl_MeshDelete above */ if (e.nextEdgeCCWAroundOrigin == e) { KillVertex(e.originVertex, null); } else { /* Make sure that e.Org points to a valid half-edge */ e.originVertex.edgeThisIsOriginOf = e.nextEdgeCCWAroundOrigin; Splice(e, e.Oprev); } eSym = e.otherHalfOfThisEdge; if (eSym.nextEdgeCCWAroundOrigin == eSym) { KillVertex(eSym.originVertex, null); } else { /* Make sure that eSym.Org points to a valid half-edge */ eSym.originVertex.edgeThisIsOriginOf = eSym.nextEdgeCCWAroundOrigin; Splice(eSym, eSym.Oprev); } KillEdge(e); } } while (e != eStart); /* delete from circular doubly-linked list */ fPrev = fZap.prevFace; fNext = fZap.nextFace; fNext.prevFace = fPrev; fPrev.nextFace = fNext; fZap = null; }
/* __gl_meshSplice( eOrg, eDst ) is the basic operation for changing the * mesh connectivity and topology. It changes the mesh so that * eOrg.Onext <- OLD( eDst.Onext ) * eDst.Onext <- OLD( eOrg.Onext ) * where OLD(...) means the value before the meshSplice operation. * * This can have two effects on the vertex structure: * - if eOrg.Org != eDst.Org, the two vertices are merged together * - if eOrg.Org == eDst.Org, the origin is split into two vertices * In both cases, eDst.Org is changed and eOrg.Org is untouched. * * Similarly (and independently) for the face structure, * - if eOrg.Lface == eDst.Lface, one loop is split into two * - if eOrg.Lface != eDst.Lface, two distinct loops are joined into one * In both cases, eDst.Lface is changed and eOrg.Lface is unaffected. * * Some special cases: * If eDst == eOrg, the operation has no effect. * If eDst == eOrg.Lnext, the new face will have a single edge. * If eDst == eOrg.Lprev, the old face will have a single edge. * If eDst == eOrg.Onext, the new vertex will have a single edge. * If eDst == eOrg.Oprev, the old vertex will have a single edge. */ public static void meshSplice(HalfEdge eOrg, HalfEdge eDst) { bool joiningLoops = false; bool joiningVertices = false; if (eOrg == eDst) return; if (eDst.originVertex != eOrg.originVertex) { /* We are merging two disjoint vertices -- destroy eDst.Org */ joiningVertices = true; KillVertex(eDst.originVertex, eOrg.originVertex); } if (eDst.leftFace != eOrg.leftFace) { /* We are connecting two disjoint loops -- destroy eDst.Lface */ joiningLoops = true; KillFace(eDst.leftFace, eOrg.leftFace); } /* Change the edge structure */ Splice(eDst, eOrg); if (!joiningVertices) { var newVertex = new ContourVertex(); /* We split one vertex into two -- the new vertex is eDst.Org. * Make sure the old vertex points to a valid half-edge. */ MakeVertex(newVertex, eDst, eOrg.originVertex); eOrg.originVertex.edgeThisIsOriginOf = eOrg; } if (!joiningLoops) { var newFace = new Face(); /* We split one loop into two -- the new loop is eDst.Lface. * Make sure the old face points to a valid half-edge. */ MakeFace(newFace, eDst, eOrg.leftFace); eOrg.leftFace.halfEdgeThisIsLeftFaceOf = eOrg; } }
/* __gl_meshConnect( eOrg, eDst ) creates a new edge from eOrg.Dst * to eDst.Org, and returns the corresponding half-edge eNew. * If eOrg.Lface == eDst.Lface, this splits one loop into two, * and the newly created loop is eNew.Lface. Otherwise, two disjoint * loops are merged into one, and the loop eDst.Lface is destroyed. * * If (eOrg == eDst), the new face will have only two edges. * If (eOrg.Lnext == eDst), the old face is reduced to a single edge. * If (eOrg.Lnext.Lnext == eDst), the old face is reduced to two edges. */ public static HalfEdge meshConnect(HalfEdge eOrg, HalfEdge eDst) { HalfEdge eNewSym; bool joiningLoops = false; HalfEdge eNew = MakeEdge(eOrg); eNewSym = eNew.otherHalfOfThisEdge; if (eDst.leftFace != eOrg.leftFace) { /* We are connecting two disjoint loops -- destroy eDst.Lface */ joiningLoops = true; KillFace(eDst.leftFace, eOrg.leftFace); } /* Connect the new edge appropriately */ Splice(eNew, eOrg.nextEdgeCCWAroundLeftFace); Splice(eNewSym, eDst); /* Set the vertex and face information */ eNew.originVertex = eOrg.directionVertex; eNewSym.originVertex = eDst.originVertex; eNew.leftFace = eNewSym.leftFace = eOrg.leftFace; /* Make sure the old face points to a valid half-edge */ eOrg.leftFace.halfEdgeThisIsLeftFaceOf = eNewSym; if (!joiningLoops) { var newFace = new Face(); /* We split one loop into two -- the new loop is eNew.Lface */ MakeFace(newFace, eNew, eOrg.leftFace); } return eNew; }
/* __gl_meshDelete( eDel ) removes the edge eDel. There are several cases: * if (eDel.Lface != eDel.Rface), we join two loops into one; the loop * eDel.Lface is deleted. Otherwise, we are splitting one loop into two; * the newly created loop will contain eDel.Dst. If the deletion of eDel * would create isolated vertices, those are deleted as well. * * This function could be implemented as two calls to __gl_meshSplice * plus a few calls to free, but this would allocate and delete * unnecessary vertices and faces. */ public static void DeleteHalfEdge(HalfEdge edgeToDelete) { HalfEdge otherHalfOfEdgeToDelete = edgeToDelete.otherHalfOfThisEdge; bool joiningLoops = false; // First step: disconnect the origin vertex eDel.Org. We make all // changes to get a consistent mesh in this "intermediate" state. if (edgeToDelete.leftFace != edgeToDelete.rightFace) { // We are joining two loops into one -- remove the left face joiningLoops = true; KillFace(edgeToDelete.leftFace, edgeToDelete.rightFace); } if (edgeToDelete.nextEdgeCCWAroundOrigin == edgeToDelete) { KillVertex(edgeToDelete.originVertex, null); } else { // Make sure that eDel.Org and eDel.Rface point to valid half-edges edgeToDelete.rightFace.halfEdgeThisIsLeftFaceOf = edgeToDelete.Oprev; edgeToDelete.originVertex.edgeThisIsOriginOf = edgeToDelete.nextEdgeCCWAroundOrigin; Splice(edgeToDelete, edgeToDelete.Oprev); if (!joiningLoops) { var newFace = new Face(); // We are splitting one loop into two -- create a new loop for eDel. MakeFace(newFace, edgeToDelete, edgeToDelete.leftFace); } } // Claim: the mesh is now in a consistent state, except that eDel.Org // may have been deleted. Now we disconnect eDel.Dst. if (otherHalfOfEdgeToDelete.nextEdgeCCWAroundOrigin == otherHalfOfEdgeToDelete) { KillVertex(otherHalfOfEdgeToDelete.originVertex, null); KillFace(otherHalfOfEdgeToDelete.leftFace, null); } else { // Make sure that eDel.Dst and eDel.Lface point to valid half-edges edgeToDelete.leftFace.halfEdgeThisIsLeftFaceOf = otherHalfOfEdgeToDelete.Oprev; otherHalfOfEdgeToDelete.originVertex.edgeThisIsOriginOf = otherHalfOfEdgeToDelete.nextEdgeCCWAroundOrigin; Splice(otherHalfOfEdgeToDelete, otherHalfOfEdgeToDelete.Oprev); } // Any isolated vertices or faces have already been freed. KillEdge(edgeToDelete); }
private void RenderMaximumFaceGroup(Face fOrig) { /* We want to find the largest triangle fan or strip of unmarked faces * which includes the given face fOrig. There are 3 possible fans * passing through fOrig (one centered at each vertex), and 3 possible * strips (one for each CCW permutation of the vertices). Our strategy * is to try all of these, and take the primitive which uses the most * triangles (a greedy approach). */ HalfEdge e = fOrig.halfEdgeThisIsLeftFaceOf; var max = new FaceCount(1, e, RenderTriangle); FaceCount newFace; max.size = 1; max.eStart = e; if (!EdgeCallBackSet) { newFace = MaximumFan(e); if (newFace.size > max.size) { max = newFace; } newFace = MaximumFan(e.nextEdgeCCWAroundLeftFace); if (newFace.size > max.size) { max = newFace; } newFace = MaximumFan(e.Lprev); if (newFace.size > max.size) { max = newFace; } newFace = MaximumStrip(e); if (newFace.size > max.size) { max = newFace; } newFace = MaximumStrip(e.nextEdgeCCWAroundLeftFace); if (newFace.size > max.size) { max = newFace; } newFace = MaximumStrip(e.Lprev); if (newFace.size > max.size) { max = newFace; } } max.CallRender(this, max.eStart, max.size); }
private void RenderLonelyTriangles(Face f) { /* Now we render all the separate triangles which could not be * grouped into a triangle fan or strip. */ HalfEdge e; bool newState = false; bool edgeState = false; /* force edge state output for first vertex */ bool sentFirstEdge = false; CallBegin(TriangleListType.Triangles); for (; f != null; f = f.trail) { /* Loop once for each edge (there will always be 3 edges) */ e = f.halfEdgeThisIsLeftFaceOf; do { if (EdgeCallBackSet) { /* Set the "edge state" to TRUE just before we output the * first vertex of each edge on the polygon boundary. */ newState = !e.rightFace.isInterior; if (edgeState != newState || !sentFirstEdge) { sentFirstEdge = true; edgeState = newState; CallEdegFlag(edgeState); } } CallVertex(e.originVertex.clientIndex); e = e.nextEdgeCCWAroundLeftFace; } while (e != f.halfEdgeThisIsLeftFaceOf); } CallEnd(); }
public void RenderMesh(Mesh mesh) { Face f; /* Make a list of separate triangles so we can render them all at once */ lonelyTriList = null; for (f = mesh.faceHead.nextFace; f != mesh.faceHead; f = f.nextFace) { f.marked = false; } for (f = mesh.faceHead.nextFace; f != mesh.faceHead; f = f.nextFace) { /* We examine all faces in an arbitrary order. Whenever we find * an unprocessed face F, we output a group of faces including F * whose size is maximum. */ if (f.isInterior && !f.marked) { RenderMaximumFaceGroup(f); if (!f.marked) { throw new Exception(); } } } if (lonelyTriList != null) { RenderLonelyTriangles(lonelyTriList); lonelyTriList = null; } }