/// <summary> /// TessellateMonoRegion( face ) 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). /// </summary> private void TessellateMonoRegion(MeshUtils.Face face) { // 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. var up = face._anEdge; Debug.Assert(up._Lnext != up && up._Lnext._Lnext != up); for (; Geom.VertLeq(up.Dst, up._Org); up = up.Lprev) { } for (; Geom.VertLeq(up._Org, up.Dst); up = up._Lnext) { } var lo = up.Lprev; while (up._Lnext != lo) { if (Geom.VertLeq(up.Dst, lo._Org)) { // 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._Lnext != up && (Geom.EdgeGoesLeft(lo._Lnext) || Geom.EdgeSign(lo._Org, lo.Dst, lo._Lnext.Dst) <= 0.0f)) { lo = _mesh.Connect(lo._Lnext, lo)._Sym; } lo = lo.Lprev; } else { // lo.Org is on the left. We can make CCW triangles from up.Dst. while (lo._Lnext != up && (Geom.EdgeGoesRight(up.Lprev) || Geom.EdgeSign(up.Dst, up._Org, up.Lprev._Org) >= 0.0f)) { up = _mesh.Connect(up, up.Lprev)._Sym; } up = up._Lnext; } } // Now lo.Org == up.Dst == the leftmost vertex. The remaining region // can be tessellated in a fan from this leftmost vertex. Debug.Assert(lo._Lnext != up); while (lo._Lnext._Lnext != up) { lo = _mesh.Connect(lo._Lnext, lo)._Sym; } }
/// <summary> /// 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! /// </summary> public void ZapFace(MeshUtils.Face fZap) { var eStart = fZap._anEdge; // walk around face, deleting edges whose right face is also NULL var eNext = eStart._Lnext; MeshUtils.Edge e, eSym; do { e = eNext; eNext = e._Lnext; e._Lface = null; if (e.Rface == null) { // delete the edge -- see TESSmeshDelete above if (e._Onext == e) { MeshUtils.KillVertex(e._Org, null); } else { // Make sure that e._Org points to a valid half-edge e._Org._anEdge = e._Onext; MeshUtils.Splice(e, e.Oprev); } eSym = e._Sym; if (eSym._Onext == eSym) { MeshUtils.KillVertex(eSym._Org, null); } else { // Make sure that eSym._Org points to a valid half-edge eSym._Org._anEdge = eSym._Onext; MeshUtils.Splice(eSym, eSym.Oprev); } MeshUtils.KillEdge(e); } } while (e != eStart); /* delete from circular doubly-linked list */ var fPrev = fZap._prev; var fNext = fZap._next; fNext._prev = fPrev; fPrev._next = fNext; }
// ReSharper restore InconsistentNaming public Mesh() { var v = _vHead = new MeshUtils.Vertex(); var f = _fHead = new MeshUtils.Face(); var pair = MeshUtils.EdgePair.Create(); var e = _eHead = pair._e; var eSym = _eHeadSym = pair._eSym; v._next = v._prev = v; v._anEdge = null; f._next = f._prev = f; f._anEdge = null; f._trail = null; f._marked = false; f._inside = false; e._next = e; e._Sym = eSym; e._Onext = null; e._Lnext = null; e._Org = null; e._Lface = null; e._winding = 0; e._activeRegion = null; eSym._next = eSym; eSym._Sym = e; eSym._Onext = null; eSym._Lnext = null; eSym._Org = null; eSym._Lface = null; eSym._winding = 0; eSym._activeRegion = null; }