static void ConnectRightVertex(Tesselator tess, ActiveRegion regUp, HalfEdge eBottomLeft) /* * Purpose: connect a "right" vertex vEvent (one where all edges go left) * to the unprocessed portion of the mesh. Since there are no right-going * edges, two regions (one above vEvent and one below) are being merged * into one. "regUp" is the upper of these two regions. * * There are two reasons for doing this (adding a right-going edge): * - if the two regions being merged are "inside", we must add an edge * to keep them separated (the combined region would not be monotone). * - in any case, we must leave some record of vEvent in the dictionary, * so that we can merge vEvent with features that we have not seen yet. * For example, maybe there is a vertical edge which passes just to * the right of vEvent; we would like to splice vEvent into this edge. * * However, we don't want to connect vEvent to just any vertex. We don''t * want the new edge to cross any other edges; otherwise we will create * intersection vertices even when the input data had no self-intersections. * (This is a bad thing; if the user's input data has no intersections, * we don't want to generate any false intersections ourselves.) * * Our eventual goal is to connect vEvent to the leftmost unprocessed * vertex of the combined region (the union of regUp and regLo). * But because of unseen vertices with all right-going edges, and also * new vertices which may be created by edge intersections, we don''t * know where that leftmost unprocessed vertex is. In the meantime, we * connect vEvent to the closest vertex of either chain, and mark the region * as "fixUpperEdge". This flag says to delete and reconnect this edge * to the next processed vertex on the boundary of the combined region. * Quite possibly the vertex we connected to will turn out to be the * closest one, in which case we won''t need to make any changes. */ { HalfEdge eNew; HalfEdge eTopLeft = eBottomLeft.nextEdgeCCWAroundOrigin; ActiveRegion regLo = RegionBelow(regUp); HalfEdge eUp = regUp.upperHalfEdge; HalfEdge eLo = regLo.upperHalfEdge; bool degenerate = false; if (eUp.directionVertex != eLo.directionVertex) { CheckForIntersect(tess, regUp); } /* Possible new degeneracies: upper or lower edge of regUp may pass * through vEvent, or may coincide with new intersection vertex */ if (eUp.originVertex.VertEq(tess.currentSweepVertex)) { Mesh.meshSplice(eTopLeft.Oprev, eUp); regUp = TopLeftRegion(regUp); eTopLeft = RegionBelow(regUp).upperHalfEdge; FinishLeftRegions(tess, RegionBelow(regUp), regLo); degenerate = true; } if (eLo.originVertex.VertEq(tess.currentSweepVertex)) { Mesh.meshSplice(eBottomLeft, eLo.Oprev); eBottomLeft = FinishLeftRegions(tess, regLo, null); degenerate = true; } if (degenerate) { AddRightEdges(tess, regUp, eBottomLeft.nextEdgeCCWAroundOrigin, eTopLeft, eTopLeft, true); return; } /* Non-degenerate situation -- need to add a temporary, fixable edge. * Connect to the closer of eLo.Org, eUp.Org. */ if (eLo.originVertex.VertLeq(eUp.originVertex)) { eNew = eLo.Oprev; } else { eNew = eUp; } eNew = Mesh.meshConnect(eBottomLeft.Lprev, eNew); /* Prevent cleanup, otherwise eNew might disappear before we've even * had a chance to mark it as a temporary edge. */ AddRightEdges(tess, regUp, eNew, eNew.nextEdgeCCWAroundOrigin, eNew.nextEdgeCCWAroundOrigin, false); eNew.otherHalfOfThisEdge.regionThisIsUpperEdgeOf.fixUpperEdge = true; WalkDirtyRegions(tess, regUp); }
/* __gl_meshAddEdgeVertex( eOrg ) creates a new edge eNew such that * eNew == eOrg.Lnext, and eNew.Dst is a newly created vertex. * eOrg and eNew will have the same left face. */ private static HalfEdge meshAddEdgeVertex(HalfEdge eOrg) { HalfEdge eNewSym; HalfEdge eNew = MakeEdge(eOrg); eNewSym = eNew.otherHalfOfThisEdge; /* Connect the new edge appropriately */ Splice(eNew, eOrg.nextEdgeCCWAroundLeftFace); /* Set the vertex and face information */ eNew.originVertex = eOrg.directionVertex; { ContourVertex newVertex = new ContourVertex(); MakeVertex(newVertex, eNewSym, eNew.originVertex); } eNew.leftFace = eNewSym.leftFace = eOrg.leftFace; return eNew; }
/* MakeEdge creates a new pair of half-edges which form their own loop. * No vertex or face structures are allocated, but these must be assigned * before the current edge operation is completed. */ private static HalfEdge MakeEdge(HalfEdge eNext) { HalfEdge ePrev; EdgePair pair = new EdgePair(); /* Make sure eNext points to the first edge of the edge pair */ if (eNext.otherHalfOfThisEdge.isFirstHalfEdge) { eNext = eNext.otherHalfOfThisEdge; } /* Insert in circular doubly-linked list before eNext. * Note that the prev pointer is stored in Sym.next. */ ePrev = eNext.otherHalfOfThisEdge.nextHalfEdge; pair.eSym.nextHalfEdge = ePrev; ePrev.otherHalfOfThisEdge.nextHalfEdge = pair.e; pair.e.nextHalfEdge = eNext; eNext.otherHalfOfThisEdge.nextHalfEdge = pair.eSym; pair.e.isFirstHalfEdge = true; pair.e.otherHalfOfThisEdge = pair.eSym; pair.e.nextEdgeCCWAroundOrigin = pair.e; pair.e.nextEdgeCCWAroundLeftFace = pair.eSym; pair.e.originVertex = null; pair.e.leftFace = null; pair.e.winding = 0; pair.e.regionThisIsUpperEdgeOf = null; pair.eSym.isFirstHalfEdge = false; pair.eSym.otherHalfOfThisEdge = pair.e; pair.eSym.nextEdgeCCWAroundOrigin = pair.eSym; pair.eSym.nextEdgeCCWAroundLeftFace = pair.e; pair.eSym.originVertex = null; pair.eSym.leftFace = null; pair.eSym.winding = 0; pair.eSym.regionThisIsUpperEdgeOf = null; return pair.e; }
/* Splice( a, b ) is best described by the Guibas/Stolfi paper or the * CS348a notes (see mesh.h). Basically it modifies the mesh so that * a.Onext and b.Onext are exchanged. This can have various effects * depending on whether a and b belong to different face or vertex rings. * For more explanation see __gl_meshSplice() below. */ private static void Splice(HalfEdge a, HalfEdge b) { HalfEdge aOnext = a.nextEdgeCCWAroundOrigin; HalfEdge bOnext = b.nextEdgeCCWAroundOrigin; aOnext.otherHalfOfThisEdge.nextEdgeCCWAroundLeftFace = b; bOnext.otherHalfOfThisEdge.nextEdgeCCWAroundLeftFace = a; a.nextEdgeCCWAroundOrigin = bOnext; b.nextEdgeCCWAroundOrigin = aOnext; }
/* KillEdge( eDel ) destroys an edge (the half-edges eDel and eDel.Sym), * and removes from the global edge list. */ private static void KillEdge(HalfEdge eDel) { HalfEdge ePrev, eNext; /* Half-edges are allocated in pairs, see EdgePair above */ if (eDel.otherHalfOfThisEdge.isFirstHalfEdge) { eDel = eDel.otherHalfOfThisEdge; } /* delete from circular doubly-linked list */ eNext = eDel.nextHalfEdge; ePrev = eDel.otherHalfOfThisEdge.nextHalfEdge; eNext.otherHalfOfThisEdge.nextHalfEdge = ePrev; ePrev.otherHalfOfThisEdge.nextHalfEdge = eNext; }
/* * Add a new active region to the sweep line, *somewhere* below "regAbove" * (according to where the new edge belongs in the sweep-line dictionary). * The upper edge of the new region will be "eNewUp". * Winding number and "inside" flag are not updated. */ static ActiveRegion AddRegionBelow(Tesselator tess, ActiveRegion regAbove, HalfEdge eNewUp) { ActiveRegion regNew = new ActiveRegion(); regNew.upperHalfEdge = eNewUp; /* __gl_dictListInsertBefore */ regNew.upperHalfEdgeDictNode = tess.edgeDictionary.InsertBefore(regAbove.upperHalfEdgeDictNode, regNew); regNew.fixUpperEdge = false; regNew.sentinel = false; regNew.dirty = false; eNewUp.regionThisIsUpperEdgeOf = regNew; return regNew; }
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); }
public void AddVertex(double[] coords3, int data) { int i; double x; double[] clamped = new double[3]; RequireState(ProcessingState.InContour); if (emptyCache) { EmptyCache(); lastHalfEdge = null; } for (i = 0; i < 3; ++i) { x = coords3[i]; if (x < -MAX_COORD) { throw new Exception("Your coordinate exceeded -" + MAX_COORD.ToString() + "."); } if (x > MAX_COORD) { throw new Exception("Your coordinate exceeded " + MAX_COORD.ToString() + "."); } clamped[i] = x; } if (mesh == null) { if (cacheCount < MAX_CACHE_SIZE) { CacheVertex(clamped, data); return; } EmptyCache(); } AddVertex(clamped[0], clamped[1], data); }
public void BeginContour() { RequireState(ProcessingState.InPolygon); processingState = ProcessingState.InContour; lastHalfEdge = null; if (cacheCount > 0) { // Just set a flag so we don't get confused by empty contours emptyCache = true; } }
public int size; /* number of triangles used */ #endregion Fields #region Constructors public FaceCount(int _size, HalfEdge _eStart, RenderDelegate _render) { size = _size; eStart = _eStart; render = _render; }
// routine to render this primitive public void CallRender(Tesselator tess, HalfEdge edge, int data) { render(tess, edge, data); }
public FaceCount(int _size, HalfEdge _eStart, RenderDelegate _render) { size = _size; eStart = _eStart; render = _render; }
/* * Purpose: connect a "right" vertex vEvent (one where all edges go left) * to the unprocessed portion of the mesh. Since there are no right-going * edges, two regions (one above vEvent and one below) are being merged * into one. "regUp" is the upper of these two regions. * * There are two reasons for doing this (adding a right-going edge): * - if the two regions being merged are "inside", we must add an edge * to keep them separated (the combined region would not be monotone). * - in any case, we must leave some record of vEvent in the dictionary, * so that we can merge vEvent with features that we have not seen yet. * For example, maybe there is a vertical edge which passes just to * the right of vEvent; we would like to splice vEvent into this edge. * * However, we don't want to connect vEvent to just any vertex. We don''t * want the new edge to cross any other edges; otherwise we will create * intersection vertices even when the input data had no self-intersections. * (This is a bad thing; if the user's input data has no intersections, * we don't want to generate any false intersections ourselves.) * * Our eventual goal is to connect vEvent to the leftmost unprocessed * vertex of the combined region (the union of regUp and regLo). * But because of unseen vertices with all right-going edges, and also * new vertices which may be created by edge intersections, we don''t * know where that leftmost unprocessed vertex is. In the meantime, we * connect vEvent to the closest vertex of either chain, and mark the region * as "fixUpperEdge". This flag says to delete and reconnect this edge * to the next processed vertex on the boundary of the combined region. * Quite possibly the vertex we connected to will turn out to be the * closest one, in which case we won''t need to make any changes. */ static void ConnectRightVertex(Tesselator tess, ActiveRegion regUp, HalfEdge eBottomLeft) { HalfEdge eNew; HalfEdge eTopLeft = eBottomLeft.nextEdgeCCWAroundOrigin; ActiveRegion regLo = RegionBelow(regUp); HalfEdge eUp = regUp.upperHalfEdge; HalfEdge eLo = regLo.upperHalfEdge; bool degenerate = false; if (eUp.directionVertex != eLo.directionVertex) { CheckForIntersect(tess, regUp); } /* Possible new degeneracies: upper or lower edge of regUp may pass * through vEvent, or may coincide with new intersection vertex */ if (eUp.originVertex.VertEq(tess.currentSweepVertex)) { Mesh.meshSplice(eTopLeft.Oprev, eUp); regUp = TopLeftRegion(regUp); eTopLeft = RegionBelow(regUp).upperHalfEdge; FinishLeftRegions(tess, RegionBelow(regUp), regLo); degenerate = true; } if (eLo.originVertex.VertEq(tess.currentSweepVertex)) { Mesh.meshSplice(eBottomLeft, eLo.Oprev); eBottomLeft = FinishLeftRegions(tess, regLo, null); degenerate = true; } if (degenerate) { AddRightEdges(tess, regUp, eBottomLeft.nextEdgeCCWAroundOrigin, eTopLeft, eTopLeft, true); return; } /* Non-degenerate situation -- need to add a temporary, fixable edge. * Connect to the closer of eLo.Org, eUp.Org. */ if (eLo.originVertex.VertLeq(eUp.originVertex)) { eNew = eLo.Oprev; } else { eNew = eUp; } eNew = Mesh.meshConnect(eBottomLeft.Lprev, eNew); /* Prevent cleanup, otherwise eNew might disappear before we've even * had a chance to mark it as a temporary edge. */ AddRightEdges(tess, regUp, eNew, eNew.nextEdgeCCWAroundOrigin, eNew.nextEdgeCCWAroundOrigin, false); eNew.otherHalfOfThisEdge.regionThisIsUpperEdgeOf.fixUpperEdge = true; WalkDirtyRegions(tess, regUp); }
/* * Purpose: insert right-going edges into the edge dictionary, and update * winding numbers and mesh connectivity appropriately. All right-going * edges share a common origin vOrg. Edges are inserted CCW starting at * eFirst; the last edge inserted is eLast.Oprev. If vOrg has any * left-going edges already processed, then eTopLeft must be the edge * such that an imaginary upward vertical segment from vOrg would be * contained between eTopLeft.Oprev and eTopLeft; otherwise eTopLeft * should be null. */ static void AddRightEdges(Tesselator tess, ActiveRegion regUp, HalfEdge eFirst, HalfEdge eLast, HalfEdge eTopLeft, bool cleanUp) { ActiveRegion reg, regPrev; HalfEdge e, ePrev; bool firstTime = true; /* Insert the new right-going edges in the dictionary */ e = eFirst; do { if (!e.originVertex.VertLeq(e.directionVertex)) { throw new Exception(); } AddRegionBelow(tess, regUp, e.otherHalfOfThisEdge); e = e.nextEdgeCCWAroundOrigin; } while (e != eLast); /* Walk *all* right-going edges from e.Org, in the dictionary order, * updating the winding numbers of each region, and re-linking the mesh * edges to match the dictionary ordering (if necessary). */ if (eTopLeft == null) { eTopLeft = RegionBelow(regUp).upperHalfEdge.Rprev; } regPrev = regUp; ePrev = eTopLeft; for (; ; ) { reg = RegionBelow(regPrev); e = reg.upperHalfEdge.otherHalfOfThisEdge; if (e.originVertex != ePrev.originVertex) break; if (e.nextEdgeCCWAroundOrigin != ePrev) { /* Unlink e from its current position, and relink below ePrev */ Mesh.meshSplice(e.Oprev, e); Mesh.meshSplice(ePrev.Oprev, e); } /* Compute the winding number and "inside" flag for the new regions */ reg.windingNumber = regPrev.windingNumber - e.winding; reg.inside = tess.IsWindingInside(reg.windingNumber); /* Check for two outgoing edges with same slope -- process these * before any intersection tests (see example in __gl_computeInterior). */ regPrev.dirty = true; if (!firstTime && CheckForRightSplice(tess, regPrev)) { AddWinding(e, ePrev); DeleteRegion(regPrev); Mesh.DeleteHalfEdge(ePrev); } firstTime = false; regPrev = reg; ePrev = e; } regPrev.dirty = true; if (regPrev.windingNumber - e.winding != reg.windingNumber) { throw new Exception(); } if (cleanUp) { /* Check for intersections between newly adjacent edges. */ WalkDirtyRegions(tess, regPrev); } }
static bool FixUpperEdge(ActiveRegion reg, HalfEdge newEdge) /* * Replace an upper edge which needs fixing (see ConnectRightVertex). */ { if (!reg.fixUpperEdge) { throw new Exception(); } Mesh.DeleteHalfEdge(reg.upperHalfEdge); reg.fixUpperEdge = false; reg.upperHalfEdge = newEdge; newEdge.regionThisIsUpperEdgeOf = reg; return true; }
static void RenderFan(Tesselator tess, HalfEdge e, int size) { /* Render as many CCW triangles as possible in a fan starting from * edge "e". The fan *should* contain exactly "size" triangles * (otherwise we've goofed up somewhere). */ tess.CallBegin(Tesselator.TriangleListType.TriangleFan); tess.CallVertex(e.originVertex.clientIndex); tess.CallVertex(e.directionVertex.clientIndex); while (!e.leftFace.Marked()) { e.leftFace.marked = true; --size; e = e.nextEdgeCCWAroundOrigin; tess.CallVertex(e.directionVertex.clientIndex); } if (size != 0) { throw new Exception(); } tess.CallEnd(); }
static void AddRightEdges(Tesselator tess, ActiveRegion regUp, HalfEdge eFirst, HalfEdge eLast, HalfEdge eTopLeft, bool cleanUp) /* * Purpose: insert right-going edges into the edge dictionary, and update * winding numbers and mesh connectivity appropriately. All right-going * edges share a common origin vOrg. Edges are inserted CCW starting at * eFirst; the last edge inserted is eLast.Oprev. If vOrg has any * left-going edges already processed, then eTopLeft must be the edge * such that an imaginary upward vertical segment from vOrg would be * contained between eTopLeft.Oprev and eTopLeft; otherwise eTopLeft * should be null. */ { ActiveRegion reg, regPrev; HalfEdge e, ePrev; bool firstTime = true; /* Insert the new right-going edges in the dictionary */ e = eFirst; do { if (!e.originVertex.VertLeq(e.directionVertex)) { throw new Exception(); } AddRegionBelow(tess, regUp, e.otherHalfOfThisEdge); e = e.nextEdgeCCWAroundOrigin; } while (e != eLast); /* Walk *all* right-going edges from e.Org, in the dictionary order, * updating the winding numbers of each region, and re-linking the mesh * edges to match the dictionary ordering (if necessary). */ if (eTopLeft == null) { eTopLeft = RegionBelow(regUp).upperHalfEdge.Rprev; } regPrev = regUp; ePrev = eTopLeft; for (;;) { reg = RegionBelow(regPrev); e = reg.upperHalfEdge.otherHalfOfThisEdge; if (e.originVertex != ePrev.originVertex) break; if (e.nextEdgeCCWAroundOrigin != ePrev) { /* Unlink e from its current position, and relink below ePrev */ Mesh.meshSplice(e.Oprev, e); Mesh.meshSplice(ePrev.Oprev, e); } /* Compute the winding number and "inside" flag for the new regions */ reg.windingNumber = regPrev.windingNumber - e.winding; reg.inside = tess.IsWindingInside(reg.windingNumber); /* Check for two outgoing edges with same slope -- process these * before any intersection tests (see example in __gl_computeInterior). */ regPrev.dirty = true; if (!firstTime && CheckForRightSplice(tess, regPrev)) { AddWinding(e, ePrev); DeleteRegion(regPrev); Mesh.DeleteHalfEdge(ePrev); } firstTime = false; regPrev = reg; ePrev = e; } regPrev.dirty = true; if (regPrev.windingNumber - e.winding != reg.windingNumber) { throw new Exception(); } if (cleanUp) { /* Check for intersections between newly adjacent edges. */ WalkDirtyRegions(tess, regPrev); } }
static void RenderStrip(Tesselator tess, HalfEdge halfEdge, int size) { /* Render as many CCW triangles as possible in a strip starting from * edge "e". The strip *should* contain exactly "size" triangles * (otherwise we've goofed up somewhere). */ tess.CallBegin(Tesselator.TriangleListType.TriangleStrip); tess.CallVertex(halfEdge.originVertex.clientIndex); tess.CallVertex(halfEdge.directionVertex.clientIndex); while (!halfEdge.leftFace.Marked()) { halfEdge.leftFace.marked = true; --size; halfEdge = halfEdge.Dprev; tess.CallVertex(halfEdge.originVertex.clientIndex); if (halfEdge.leftFace.Marked()) break; halfEdge.leftFace.marked = true; --size; halfEdge = halfEdge.nextEdgeCCWAroundOrigin; tess.CallVertex(halfEdge.directionVertex.clientIndex); } if (size != 0) { throw new Exception(); } tess.CallEnd(); }
/* face.TessellateMonoRegion() tessellates a monotone region * (what else would it do??) The region must consist of a single * loop of half-edges (see mesh.h) oriented CCW. "Monotone" in this * case means that any vertical line intersects the interior of the * region in a single interval. * * Tessellation consists of adding interior edges (actually pairs of * half-edges), to split the region into non-overlapping triangles. * * The basic idea is explained in Preparata and Shamos (which I don''t * have handy right now), although their implementation is more * complicated than this one. The are two edge chains, an upper chain * and a lower chain. We process all vertices from both chains in order, * from right to left. * * The algorithm ensures that the following invariant holds after each * vertex is processed: the untessellated region consists of two * chains, where one chain (say the upper) is a single edge, and * the other chain is concave. The left vertex of the single edge * is always to the left of all vertices in the concave chain. * * Each step consists of adding the rightmost unprocessed vertex to one * of the two chains, and forming a fan of triangles from the rightmost * of two chain endpoints. Determining whether we can add each triangle * to the fan is a simple orientation test. By making the fan as large * as possible, we restore the invariant (check it yourself). */ internal bool TessellateMonoRegion() { /* All edges are oriented CCW around the boundary of the region. * First, find the half-edge whose origin vertex is rightmost. * Since the sweep goes from left to right, face.anEdge should * be close to the edge we want. */ HalfEdge up = this.halfEdgeThisIsLeftFaceOf; if (up.nextEdgeCCWAroundLeftFace == up || up.nextEdgeCCWAroundLeftFace.nextEdgeCCWAroundLeftFace == up) { throw new Exception(); } for (; up.directionVertex.VertLeq(up.originVertex); up = up.Lprev) { ; } for (; up.originVertex.VertLeq(up.directionVertex); up = up.nextEdgeCCWAroundLeftFace) { ; } HalfEdge lo = up.Lprev; while (up.nextEdgeCCWAroundLeftFace != lo) { if (up.directionVertex.VertLeq(lo.originVertex)) { /* up.Dst is on the left. It is safe to form triangles from lo.Org. * The EdgeGoesLeft test guarantees progress even when some triangles * are CW, given that the upper and lower chains are truly monotone. */ while (lo.nextEdgeCCWAroundLeftFace != up && (lo.nextEdgeCCWAroundLeftFace.EdgeGoesLeft() || ContourVertex.EdgeSign(lo.originVertex, lo.directionVertex, lo.nextEdgeCCWAroundLeftFace.directionVertex) <= 0)) { HalfEdge tempHalfEdge = Mesh.meshConnect(lo.nextEdgeCCWAroundLeftFace, lo); lo = tempHalfEdge.otherHalfOfThisEdge; } lo = lo.Lprev; } else { /* lo.Org is on the left. We can make CCW triangles from up.Dst. */ while (lo.nextEdgeCCWAroundLeftFace != up && (up.Lprev.EdgeGoesRight() || ContourVertex.EdgeSign(up.directionVertex, up.originVertex, up.Lprev.originVertex) >= 0)) { HalfEdge tempHalfEdge = Mesh.meshConnect(up, up.Lprev); up = tempHalfEdge.otherHalfOfThisEdge; } up = up.nextEdgeCCWAroundLeftFace; } } // Now lo.Org == up.Dst == the leftmost vertex. The remaining region // can be tessellated in a fan from this leftmost vertex. if (lo.nextEdgeCCWAroundLeftFace == up) { throw new Exception(); } while (lo.nextEdgeCCWAroundLeftFace.nextEdgeCCWAroundLeftFace != up) { HalfEdge tempHalfEdge = Mesh.meshConnect(lo.nextEdgeCCWAroundLeftFace, lo); lo = tempHalfEdge.otherHalfOfThisEdge; } return(true); }
bool AddVertex(double x, double y, int data) { HalfEdge e; e = this.lastHalfEdge; if (e == null) { /* Make a self-loop (one vertex, one edge). */ e = this.mesh.MakeEdge(); Mesh.meshSplice(e, e.otherHalfOfThisEdge); } else { /* Create a new vertex and edge which immediately follow e * in the ordering around the left face. */ if (Mesh.meshSplitEdge(e) == null) { return false; } e = e.nextEdgeCCWAroundLeftFace; } /* The new vertex is now e.Org. */ e.originVertex.clientIndex = data; e.originVertex.coords[0] = x; e.originVertex.coords[1] = y; /* The winding of an edge says how the winding number changes as we * cross from the edge''s right face to its left face. We add the * vertices in such an order that a CCW contour will add +1 to * the winding number of the region inside the contour. */ e.winding = 1; e.otherHalfOfThisEdge.winding = -1; this.lastHalfEdge = e; return true; }
/* MakeVertex( newVertex, eOrig, vNext ) attaches a new vertex and makes it the * origin of all edges in the vertex loop to which eOrig belongs. "vNext" gives * a place to insert the new vertex in the global vertex list. We insert * the new vertex *before* vNext so that algorithms which walk the vertex * list will not see the newly created vertices. */ private static void MakeVertex(ContourVertex newVertex, HalfEdge eOrig, ContourVertex vNext) { HalfEdge e; ContourVertex vPrev; ContourVertex vNew = newVertex; /* insert in circular doubly-linked list before vNext */ vPrev = vNext.prevVertex; vNew.prevVertex = vPrev; vPrev.nextVertex = vNew; vNew.nextVertex = vNext; vNext.prevVertex = vNew; vNew.edgeThisIsOriginOf = eOrig; vNew.clientIndex = 0; /* leave coords, s, t undefined */ /* fix other edges on this vertex loop */ e = eOrig; do { e.originVertex = vNew; e = e.nextEdgeCCWAroundOrigin; } while (e != eOrig); }
FaceCount MaximumFan(HalfEdge eOrig) { /* eOrig.Lface is the face we want to render. We want to find the size * of a maximal fan around eOrig.Org. To do this we just walk around * the origin vertex as far as possible in both directions. */ FaceCount newFace = new FaceCount(0, null, new FaceCount.RenderDelegate(RenderFan)); Face trail = null; HalfEdge e; for (e = eOrig; !e.leftFace.Marked(); e = e.nextEdgeCCWAroundOrigin) { Face.AddToTrail(ref e.leftFace, ref trail); ++newFace.size; } for (e = eOrig; !e.rightFace.Marked(); e = e.Oprev) { Face f = e.rightFace; Face.AddToTrail(ref f, ref trail); e.rightFace = f; ++newFace.size; } newFace.eStart = e; Face.FreeTrail(ref trail); return newFace; }
/* __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) { ContourVertex 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) { Face 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; } }
FaceCount MaximumStrip(HalfEdge eOrig) { /* Here we are looking for a maximal strip that contains the vertices * eOrig.Org, eOrig.Dst, eOrig.Lnext.Dst (in that order or the * reverse, such that all triangles are oriented CCW). * * Again we walk forward and backward as far as possible. However for * strips there is a twist: to get CCW orientations, there must be * an *even* number of triangles in the strip on one side of eOrig. * We walk the strip starting on a side with an even number of triangles; * if both side have an odd number, we are forced to shorten one side. */ FaceCount newFace = new FaceCount(0, null, RenderStrip); int headSize = 0, tailSize = 0; Face trail = null; HalfEdge e, eTail, eHead; for (e = eOrig; !e.leftFace.Marked(); ++tailSize, e = e.nextEdgeCCWAroundOrigin) { Face.AddToTrail(ref e.leftFace, ref trail); ++tailSize; e = e.Dprev; if (e.leftFace.Marked()) break; Face.AddToTrail(ref e.leftFace, ref trail); } eTail = e; for (e = eOrig; !e.rightFace.Marked(); ++headSize, e = e.Dnext) { Face f = e.rightFace; Face.AddToTrail(ref f, ref trail); e.rightFace = f; ++headSize; e = e.Oprev; if (e.rightFace.Marked()) break; f = e.rightFace; Face.AddToTrail(ref f, ref trail); e.rightFace = f; } eHead = e; newFace.size = tailSize + headSize; if (IsEven(tailSize)) { newFace.eStart = eTail.otherHalfOfThisEdge; } else if (IsEven(headSize)) { newFace.eStart = eHead; } else { /* Both sides have odd length, we must shorten one of them. In fact, * we must start from eHead to guarantee inclusion of eOrig.Lface. */ --newFace.size; newFace.eStart = eHead.nextEdgeCCWAroundOrigin; } Face.FreeTrail(ref trail); return newFace; }
/* __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) { Face 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); }
void RenderTriangle(Tesselator tess, HalfEdge e, int size) { /* Just add the triangle to a triangle list, so we can render all * the separate triangles at once. */ if (size != 1) { throw new Exception(); } Face.AddToTrail(ref e.leftFace, ref this.lonelyTriList); }
/* __gl_meshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew, * such that eNew == eOrg.Lnext. The new vertex is eOrg.Dst == eNew.Org. * eOrg and eNew will have the same left face. */ public static HalfEdge meshSplitEdge(HalfEdge eOrg) { HalfEdge eNew; HalfEdge tempHalfEdge = meshAddEdgeVertex(eOrg); eNew = tempHalfEdge.otherHalfOfThisEdge; /* Disconnect eOrg from eOrg.Dst and connect it to eNew.Org */ Splice(eOrg.otherHalfOfThisEdge, eOrg.otherHalfOfThisEdge.Oprev); Splice(eOrg.otherHalfOfThisEdge, eNew); /* Set the vertex and face information */ eOrg.directionVertex = eNew.originVertex; eNew.directionVertex.edgeThisIsOriginOf = eNew.otherHalfOfThisEdge; /* may have pointed to eOrg.Sym */ eNew.rightFace = eOrg.rightFace; eNew.winding = eOrg.winding; /* copy old winding information */ eNew.otherHalfOfThisEdge.winding = eOrg.otherHalfOfThisEdge.winding; return eNew; }
public FaceCount(int _size, HalfEdge _eStart, Action <Tesselator, HalfEdge, int> _render) { size = _size; eStart = _eStart; render = _render; }
/* __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) { Face newFace = new Face(); /* We split one loop into two -- the new loop is eNew.Lface */ MakeFace(newFace, eNew, eOrg.leftFace); } return eNew; }
/* * Invariants for the Edge Dictionary. * - each pair of adjacent edges e2=Succ(e1) satisfies EdgeLeq(e1,e2) * at any valid location of the sweep event * - if EdgeLeq(e2,e1) as well (at any valid sweep event), then e1 and e2 * share a common endpoint * - for each e, e.Dst has been processed, but not e.Org * - each edge e satisfies VertLeq(e.Dst,currentSweepVertex) && VertLeq(currentSweepVertex,e.Org) * where "currentSweepVertex" is the current sweep line event. * - no edge e has zero length * * Invariants for the Mesh (the processed portion). * - the portion of the mesh left of the sweep line is a planar graph, * ie. there is *some* way to embed it in the plane * - no processed edge has zero length * - no two processed vertices have identical coordinates * - each "inside" region is monotone, ie. can be broken into two chains * of monotonically increasing vertices according to VertLeq(v1,v2) * - a non-invariant: these chains may intersect (very slightly) * * Invariants for the Sweep. * - if none of the edges incident to the currentSweepVertex vertex have an activeRegion * (ie. none of these edges are in the edge dictionary), then the vertex * has only right-going edges. * - if an edge is marked "fixUpperEdge" (it is a temporary edge introduced * by ConnectRightVertex), then it is the only right-going edge from * its associated vertex. (This says that these edges exist only * when it is necessary.) */ //#undef MAX //#undef MIN //#define MAX(x,y) ((x) >= (y) ? (x) : (y)) //#define MIN(x,y) ((x) <= (y) ? (x) : (y)) /* When we merge two edges into one, we need to compute the combined * winding of the new edge. */ static void AddWinding(HalfEdge eDst, HalfEdge eSrc) { eDst.winding += eSrc.winding; eDst.otherHalfOfThisEdge.winding += eSrc.otherHalfOfThisEdge.winding; }
private FaceCount MaximumStrip(HalfEdge eOrig) { /* Here we are looking for a maximal strip that contains the vertices * eOrig.Org, eOrig.Dst, eOrig.Lnext.Dst (in that order or the * reverse, such that all triangles are oriented CCW). * * Again we walk forward and backward as far as possible. However for * strips there is a twist: to get CCW orientations, there must be * an *even* number of triangles in the strip on one side of eOrig. * We walk the strip starting on a side with an even number of triangles; * if both side have an odd number, we are forced to shorten one side. */ FaceCount newFace = new FaceCount(0, null, RenderStrip); int headSize = 0, tailSize = 0; Face trail = null; HalfEdge e, eTail, eHead; for (e = eOrig; !e.leftFace.Marked(); ++tailSize, e = e.nextEdgeCCWAroundOrigin) { Face.AddToTrail(ref e.leftFace, ref trail); ++tailSize; e = e.Dprev; if (e.leftFace.Marked()) { break; } Face.AddToTrail(ref e.leftFace, ref trail); } eTail = e; for (e = eOrig; !e.rightFace.Marked(); ++headSize, e = e.Dnext) { Face f = e.rightFace; Face.AddToTrail(ref f, ref trail); e.rightFace = f; ++headSize; e = e.Oprev; if (e.rightFace.Marked()) { break; } f = e.rightFace; Face.AddToTrail(ref f, ref trail); e.rightFace = f; } eHead = e; newFace.size = tailSize + headSize; if (IsEven(tailSize)) { newFace.eStart = eTail.otherHalfOfThisEdge; } else if (IsEven(headSize)) { newFace.eStart = eHead; } else { /* Both sides have odd length, we must shorten one of them. In fact, * we must start from eHead to guarantee inclusion of eOrig.Lface. */ --newFace.size; newFace.eStart = eHead.nextEdgeCCWAroundOrigin; } Face.FreeTrail(ref trail); return(newFace); }
static ActiveRegion AddRegionBelow(Tesselator tess, ActiveRegion regAbove, HalfEdge eNewUp) /* * Add a new active region to the sweep line, *somewhere* below "regAbove" * (according to where the new edge belongs in the sweep-line dictionary). * The upper edge of the new region will be "eNewUp". * Winding number and "inside" flag are not updated. */ { ActiveRegion regNew = new ActiveRegion(); regNew.upperHalfEdge = eNewUp; /* __gl_dictListInsertBefore */ regNew.upperHalfEdgeDictNode = tess.edgeDictionary.InsertBefore(regAbove.upperHalfEdgeDictNode, regNew); regNew.fixUpperEdge = false; regNew.sentinel = false; regNew.dirty = false; eNewUp.regionThisIsUpperEdgeOf = regNew; return regNew; }
/* __gl_meshCheckMesh( mesh ) checks a mesh for self-consistency. */ public void CheckMesh() { Face fHead = this.faceHead; ContourVertex vHead = this.vertexHead; HalfEdge eHead = this.halfEdgeHead; Face f, fPrev; ContourVertex v, vPrev; HalfEdge e, ePrev; fPrev = fHead; for (fPrev = fHead; (f = fPrev.nextFace) != fHead; fPrev = f) { if (f.prevFace != fPrev) { throw new Exception(); } e = f.halfEdgeThisIsLeftFaceOf; do { if (e.otherHalfOfThisEdge == e) { throw new Exception(); } if (e.otherHalfOfThisEdge.otherHalfOfThisEdge != e) { throw new Exception(); } if (e.nextEdgeCCWAroundLeftFace.nextEdgeCCWAroundOrigin.otherHalfOfThisEdge != e) { throw new Exception(); } if (e.nextEdgeCCWAroundOrigin.otherHalfOfThisEdge.nextEdgeCCWAroundLeftFace != e) { throw new Exception(); } if (e.leftFace != f) { throw new Exception(); } e = e.nextEdgeCCWAroundLeftFace; } while (e != f.halfEdgeThisIsLeftFaceOf); } if (f.prevFace != fPrev || f.halfEdgeThisIsLeftFaceOf != null) { throw new Exception(); } vPrev = vHead; for (vPrev = vHead; (v = vPrev.nextVertex) != vHead; vPrev = v) { if (v.prevVertex != vPrev) { throw new Exception(); } e = v.edgeThisIsOriginOf; do { if (e.otherHalfOfThisEdge == e) { throw new Exception(); } if (e.otherHalfOfThisEdge.otherHalfOfThisEdge != e) { throw new Exception(); } if (e.nextEdgeCCWAroundLeftFace.nextEdgeCCWAroundOrigin.otherHalfOfThisEdge != e) { throw new Exception(); } if (e.nextEdgeCCWAroundOrigin.otherHalfOfThisEdge.nextEdgeCCWAroundLeftFace != e) { throw new Exception(); } if (e.originVertex != v) { throw new Exception(); } e = e.nextEdgeCCWAroundOrigin; } while (e != v.edgeThisIsOriginOf); } if (v.prevVertex != vPrev || v.edgeThisIsOriginOf != null || v.clientIndex != 0) { throw new Exception(); } ePrev = eHead; for (ePrev = eHead; (e = ePrev.nextHalfEdge) != eHead; ePrev = e) { if (e.otherHalfOfThisEdge.nextHalfEdge != ePrev.otherHalfOfThisEdge) { throw new Exception(); } if (e.otherHalfOfThisEdge == e) { throw new Exception(); } if (e.otherHalfOfThisEdge.otherHalfOfThisEdge != e) { throw new Exception(); } if (e.originVertex == null) { throw new Exception(); } if (e.directionVertex == null) { throw new Exception(); } if (e.nextEdgeCCWAroundLeftFace.nextEdgeCCWAroundOrigin.otherHalfOfThisEdge != e) { throw new Exception(); } if (e.nextEdgeCCWAroundOrigin.otherHalfOfThisEdge.nextEdgeCCWAroundLeftFace != e) { throw new Exception(); } } if (e.otherHalfOfThisEdge.nextHalfEdge != ePrev.otherHalfOfThisEdge || e.otherHalfOfThisEdge != this.otherHalfOfThisEdgeHead || e.otherHalfOfThisEdge.otherHalfOfThisEdge != e || e.originVertex != null || e.directionVertex != null || e.leftFace != null || e.rightFace != null) { throw new Exception(); } }
static void SpliceMergeVertices(Tesselator tess, HalfEdge e1, HalfEdge e2) /* * Two vertices with idential coordinates are combined into one. * e1.Org is kept, while e2.Org is discarded. */ { int[] data4 = new int[4]; double[] weights4 = new double[] { 0.5f, 0.5f, 0, 0 }; data4[0] = e1.originVertex.clientIndex; data4[1] = e2.originVertex.clientIndex; CallCombine(tess, e1.originVertex, data4, weights4, false); Mesh.meshSplice(e1, e2); }
/* * Two vertices with idential coordinates are combined into one. * e1.Org is kept, while e2.Org is discarded. */ static void SpliceMergeVertices(Tesselator tess, HalfEdge e1, HalfEdge e2) { int[] data4 = new int[4]; double[] weights4 = new double[] { 0.5f, 0.5f, 0, 0 }; data4[0] = e1.originVertex.clientIndex; data4[1] = e2.originVertex.clientIndex; CallCombine(tess, e1.originVertex, data4, weights4, false); Mesh.meshSplice(e1, e2); }