/* * Delete any degenerate faces with only two edges. WalkDirtyRegions() * will catch almost all of these, but it won't catch degenerate faces * produced by splice operations on already-processed edges. * The two places this can happen are in FinishLeftRegions(), when * we splice in a "temporary" edge produced by ConnectRightVertex(), * and in CheckForLeftSplice(), where we splice already-processed * edges to ensure that our dictionary invariants are not violated * by numerical errors. * * In both these cases it is *very* dangerous to delete the offending * edge at the time, since one of the routines further up the stack * will sometimes be keeping a pointer to that edge. */ private static bool RemoveDegenerateFaces(Mesh mesh) { Face f, fNext; HalfEdge e; for (f = mesh.faceHead.nextFace; f != mesh.faceHead; f = fNext) { fNext = f.nextFace; e = f.halfEdgeThisIsLeftFaceOf; if (e.nextEdgeCCWAroundLeftFace == e) { throw new Exception(); } if (e.nextEdgeCCWAroundLeftFace.nextEdgeCCWAroundLeftFace == e) { /* A face with only two edges */ AddWinding(e.nextEdgeCCWAroundOrigin, e); Mesh.DeleteHalfEdge(e); } } return true; }
/* __gl_meshUnion( mesh1, mesh2 ) forms the union of all structures in * both meshes, and returns the new mesh (the old meshes are destroyed). */ private Mesh meshUnion(Mesh mesh1, Mesh mesh2) { Face f1 = mesh1.faceHead; ContourVertex v1 = mesh1.vertexHead; HalfEdge e1 = mesh1.halfEdgeHead; Face f2 = mesh2.faceHead; ContourVertex v2 = mesh2.vertexHead; HalfEdge e2 = mesh2.halfEdgeHead; /* Add the faces, vertices, and edges of mesh2 to those of mesh1 */ if (f2.nextFace != f2) { f1.prevFace.nextFace = f2.nextFace; f2.nextFace.prevFace = f1.prevFace; f2.prevFace.nextFace = f1; f1.prevFace = f2.prevFace; } if (v2.nextVertex != v2) { v1.prevVertex.nextVertex = v2.nextVertex; v2.nextVertex.prevVertex = v1.prevVertex; v2.prevVertex.nextVertex = v1; v1.prevVertex = v2.prevVertex; } if (e2.nextHalfEdge != e2) { e1.otherHalfOfThisEdge.nextHalfEdge.otherHalfOfThisEdge.nextHalfEdge = e2.nextHalfEdge; e2.nextHalfEdge.otherHalfOfThisEdge.nextHalfEdge = e1.otherHalfOfThisEdge.nextHalfEdge; e2.otherHalfOfThisEdge.nextHalfEdge.otherHalfOfThisEdge.nextHalfEdge = e1; e1.otherHalfOfThisEdge.nextHalfEdge = e2.otherHalfOfThisEdge.nextHalfEdge; } mesh2 = null; return mesh1; }
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; } }
/************************ Boundary contour decomposition ******************/ /* Takes a mesh, and outputs one * contour for each face marked "inside". The rendering output is * provided as callbacks. */ public void RenderBoundary(Mesh mesh) { for (Face curFace = mesh.faceHead.nextFace; curFace != mesh.faceHead; curFace = curFace.nextFace) { if (curFace.isInterior) { CallBegin(TriangleListType.LineLoop); HalfEdge curHalfEdge = curFace.halfEdgeThisIsLeftFaceOf; do { CallVertex(curHalfEdge.originVertex.clientIndex); curHalfEdge = curHalfEdge.nextEdgeCCWAroundLeftFace; } while (curHalfEdge != curFace.halfEdgeThisIsLeftFaceOf); CallEnd(); } } }
public void EndPolygon() { RequireState(ProcessingState.InPolygon); processingState = ProcessingState.Dormant; if (mesh == null) { if (!EdgeCallBackSet && callMesh == null) { /* Try some special code to make the easy cases go quickly * (eg. convex polygons). This code does NOT handle multiple contours, * intersections, edge flags, and of course it does not generate * an explicit mesh either. */ if (RenderCache()) { return; } } EmptyCache(); /* could've used a label*/ } /* Determine the polygon normal and project vertices onto the plane * of the polygon. */ ProjectPolygon(); /* __gl_computeInterior( this ) computes the planar arrangement specified * by the given contours, and further subdivides this arrangement * into regions. Each region is marked "inside" if it belongs * to the polygon, according to the rule given by this.windingRule. * Each interior region is guaranteed to be monotone. */ ActiveRegion.ComputeInterior(this); bool rc = true; /* If the user wants only the boundary contours, we throw away all edges * except those which separate the interior from the exterior. * Otherwise we tessellate all the regions marked "inside". */ if (boundaryOnly) { rc = mesh.SetWindingNumber(1, true); } else { rc = mesh.TessellateInterior(); } mesh.CheckMesh(); if (callBegin != null || callEnd != null || callVertex != null || callEdgeFlag != null) { if (boundaryOnly) { RenderBoundary(mesh); /* output boundary contours */ } else { RenderMesh(mesh); /* output strips and fans */ } } if (callMesh != null) { /* Throw away the exterior faces, so that all faces are interior. * This way the user doesn't have to check the "inside" flag, * and we don't need to even reveal its existence. It also leaves * the freedom for an implementation to not generate the exterior * faces in the first place. */ mesh.DiscardExterior(); callMesh(mesh); /* user wants the mesh itself */ mesh = null; return; } mesh = null; }
public void EmptyCache() { VertexAndIndex[] v = simpleVertexCache; mesh = new Mesh(); for (int i = 0; i < cacheCount; i++) { AddVertex(v[i].x, v[i].y, v[i].vertexIndex); } cacheCount = 0; emptyCache = false; }
public virtual void BeginPolygon() { RequireState(ProcessingState.Dormant); processingState = ProcessingState.InPolygon; cacheCount = 0; emptyCache = false; mesh = null; }