/// <summary> /// Both edges must be directed from right to left (this is the canonical /// direction for the upper edge of each region). /// /// The strategy is to evaluate a "t" value for each edge at the /// current sweep line position, given by tess->event. The calculations /// are designed to be very stable, but of course they are not perfect. /// /// Special case: if both edge destinations are at the sweep event, /// we sort the edges by slope (they would otherwise compare equally). /// </summary> private bool EdgeLeq(ActiveRegion reg1, ActiveRegion reg2) { MeshUtils.Edge e1 = reg1._eUp; MeshUtils.Edge e2 = reg2._eUp; if (e1._Dst == _event) { if (e2._Dst != _event) { return(Geom.EdgeSign(e2._Dst, _event, e2._Org) <= 0.0f); } // Two edges right of the sweep line which meet at the sweep event. // Sort them by slope. if (Geom.VertLeq(e1._Org, e2._Org)) { return(Geom.EdgeSign(e2._Dst, e1._Org, e2._Org) <= 0.0f); } return(Geom.EdgeSign(e1._Dst, e2._Org, e1._Org) >= 0.0f); } if (e2._Dst == _event) { return(Geom.EdgeSign(e1._Dst, _event, e1._Org) >= 0.0f); } // General case - compute signed distance *from* e1, e2 to event double t1 = Geom.EdgeEval(e1._Dst, _event, e1._Org); double t2 = Geom.EdgeEval(e2._Dst, _event, e2._Org); return(t1 >= t2); }
/// <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. MeshUtils.Edge up = face._anEdge; Debug.Assert(up._Lnext != up && up._Lnext._Lnext != up); while (Geom.VertLeq(up._Dst, up._Org)) { up = up._Lprev; } while (Geom.VertLeq(up._Org, up._Dst)) { up = up._Lnext; } MeshUtils.Edge 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(_pool, 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(_pool, 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(_pool, lo._Lnext, lo)._Sym; } }
/// <summary> /// Check the upper and lower edge of "regUp", to make sure that the /// eUp->Org is above eLo, or eLo->Org is below eUp (depending on which /// origin is leftmost). /// /// The main purpose is to splice right-going edges with the same /// dest vertex and nearly identical slopes (ie. we can't distinguish /// the slopes numerically). However the splicing can also help us /// to recover from numerical errors. For example, suppose at one /// point we checked eUp and eLo, and decided that eUp->Org is barely /// above eLo. Then later, we split eLo into two edges (eg. from /// a splice operation like this one). This can change the result of /// our test so that now eUp->Org is incident to eLo, or barely below it. /// We must correct this condition to maintain the dictionary invariants. /// /// One possibility is to check these edges for intersection again /// (ie. CheckForIntersect). This is what we do if possible. However /// CheckForIntersect requires that tess->event lies between eUp and eLo, /// so that it has something to fall back on when the intersection /// calculation gives us an unusable answer. So, for those cases where /// we can't check for intersection, this routine fixes the problem /// by just splicing the offending vertex into the other edge. /// This is a guaranteed solution, no matter how degenerate things get. /// Basically this is a combinatorial solution to a numerical problem. /// </summary> private bool CheckForRightSplice(ActiveRegion regUp) { ActiveRegion regLo = RegionBelow(regUp); MeshUtils.Edge eUp = regUp._eUp; MeshUtils.Edge eLo = regLo._eUp; if (Geom.VertLeq(eUp._Org, eLo._Org)) { if (Geom.EdgeSign(eLo._Dst, eUp._Org, eLo._Org) > 0.0f) { return(false); } // eUp.Org appears to be below eLo if (!Geom.VertEq(eUp._Org, eLo._Org)) { // Splice eUp._Org into eLo _mesh.SplitEdge(_pool, eLo._Sym); Mesh.Splice(_pool, eUp, eLo._Oprev); regUp._dirty = regLo._dirty = true; } else if (eUp._Org != eLo._Org) { // merge the two vertices, discarding eUp.Org _pq.Remove(eUp._Org._pqHandle); SpliceMergeVertices(eLo._Oprev, eUp); } } else { if (Geom.EdgeSign(eUp._Dst, eLo._Org, eUp._Org) < 0.0f) { return(false); } // eLo.Org appears to be above eUp, so splice eLo.Org into eUp RegionAbove(regUp)._dirty = regUp._dirty = true; _mesh.SplitEdge(_pool, eUp._Sym); Mesh.Splice(_pool, eLo._Oprev, eUp); } return(true); }
/// <summary> /// Check the upper and lower edge of "regUp", to make sure that the /// eUp->Dst is above eLo, or eLo->Dst is below eUp (depending on which /// destination is rightmost). /// /// Theoretically, this should always be true. However, splitting an edge /// into two pieces can change the results of previous tests. For example, /// suppose at one point we checked eUp and eLo, and decided that eUp->Dst /// is barely above eLo. Then later, we split eLo into two edges (eg. from /// a splice operation like this one). This can change the result of /// the test so that now eUp->Dst is incident to eLo, or barely below it. /// We must correct this condition to maintain the dictionary invariants /// (otherwise new edges might get inserted in the wrong place in the /// dictionary, and bad stuff will happen). /// /// We fix the problem by just splicing the offending vertex into the /// other edge. /// </summary> private bool CheckForLeftSplice(ActiveRegion regUp) { ActiveRegion regLo = RegionBelow(regUp); MeshUtils.Edge eUp = regUp._eUp; MeshUtils.Edge eLo = regLo._eUp; Debug.Assert(!Geom.VertEq(eUp._Dst, eLo._Dst)); if (Geom.VertLeq(eUp._Dst, eLo._Dst)) { if (Geom.EdgeSign(eUp._Dst, eLo._Dst, eUp._Org) < 0.0f) { return(false); } // eLo.Dst is above eUp, so splice eLo.Dst into eUp RegionAbove(regUp)._dirty = regUp._dirty = true; MeshUtils.Edge e = _mesh.SplitEdge(_pool, eUp); Mesh.Splice(_pool, eLo._Sym, e); e._Lface._inside = regUp._inside; } else { if (Geom.EdgeSign(eLo._Dst, eUp._Dst, eLo._Org) > 0.0f) { return(false); } // eUp.Dst is below eLo, so splice eUp.Dst into eLo regUp._dirty = regLo._dirty = true; MeshUtils.Edge e = _mesh.SplitEdge(_pool, eLo); Mesh.Splice(_pool, eUp._Lnext, eLo._Sym); e._Rface._inside = regUp._inside; } return(true); }
/// <summary> /// Check the upper and lower edges of the given region to see if /// they intersect. If so, create the intersection and add it /// to the data structures. /// /// Returns TRUE if adding the new intersection resulted in a recursive /// call to AddRightEdges(); in this case all "dirty" regions have been /// checked for intersections, and possibly regUp has been deleted. /// </summary> private bool CheckForIntersect(ActiveRegion regUp) { ActiveRegion regLo = RegionBelow(regUp); MeshUtils.Edge eUp = regUp._eUp; MeshUtils.Edge eLo = regLo._eUp; MeshUtils.Vertex orgUp = eUp._Org; MeshUtils.Vertex orgLo = eLo._Org; MeshUtils.Vertex dstUp = eUp._Dst; MeshUtils.Vertex dstLo = eLo._Dst; Debug.Assert(!Geom.VertEq(dstLo, dstUp)); Debug.Assert(Geom.EdgeSign(dstUp, _event, orgUp) <= 0.0f); Debug.Assert(Geom.EdgeSign(dstLo, _event, orgLo) >= 0.0f); Debug.Assert(orgUp != _event && orgLo != _event); Debug.Assert(!regUp._fixUpperEdge && !regLo._fixUpperEdge); if (orgUp == orgLo) { // right endpoints are the same return(false); } double tMinUp = Math.Min(orgUp._t, dstUp._t); double tMaxLo = Math.Max(orgLo._t, dstLo._t); if (tMinUp > tMaxLo) { // t ranges do not overlap return(false); } if (Geom.VertLeq(orgUp, orgLo)) { if (Geom.EdgeSign(dstLo, orgUp, orgLo) > 0.0f) { return(false); } } else { if (Geom.EdgeSign(dstUp, orgLo, orgUp) < 0.0f) { return(false); } } // At this point the edges intersect, at least marginally MeshUtils.Vertex isect = _pool.Get <MeshUtils.Vertex>(); Geom.EdgeIntersect(dstUp, orgUp, dstLo, orgLo, isect); // The following properties are guaranteed: Debug.Assert(Math.Min(orgUp._t, dstUp._t) <= isect._t); Debug.Assert(isect._t <= Math.Max(orgLo._t, dstLo._t)); Debug.Assert(Math.Min(dstLo._s, dstUp._s) <= isect._s); Debug.Assert(isect._s <= Math.Max(orgLo._s, orgUp._s)); if (Geom.VertLeq(isect, _event)) { // The intersection point lies slightly to the left of the sweep line, // so move it until it's slightly to the right of the sweep line. // (If we had perfect numerical precision, this would never happen // in the first place). The easiest and safest thing to do is // replace the intersection by tess._event. isect._s = _event._s; isect._t = _event._t; } // Similarly, if the computed intersection lies to the right of the // rightmost origin (which should rarely happen), it can cause // unbelievable inefficiency on sufficiently degenerate inputs. // (If you have the test program, try running test54.d with the // "X zoom" option turned on). MeshUtils.Vertex orgMin = Geom.VertLeq(orgUp, orgLo) ? orgUp : orgLo; if (Geom.VertLeq(orgMin, isect)) { isect._s = orgMin._s; isect._t = orgMin._t; } if (Geom.VertEq(isect, orgUp) || Geom.VertEq(isect, orgLo)) { // Easy case -- intersection at one of the right endpoints CheckForRightSplice(regUp); _pool.Return(isect); return(false); } if (!Geom.VertEq(dstUp, _event) && Geom.EdgeSign(dstUp, _event, isect) >= 0.0f || !Geom.VertEq(dstLo, _event) && Geom.EdgeSign(dstLo, _event, isect) <= 0.0f) { // Very unusual -- the new upper or lower edge would pass on the // wrong side of the sweep event, or through it. This can happen // due to very small numerical errors in the intersection calculation. if (dstLo == _event) { // Splice dstLo into eUp, and process the new region(s) _mesh.SplitEdge(_pool, eUp._Sym); Mesh.Splice(_pool, eLo._Sym, eUp); regUp = TopLeftRegion(regUp); eUp = RegionBelow(regUp)._eUp; FinishLeftRegions(RegionBelow(regUp), regLo); AddRightEdges(regUp, eUp._Oprev, eUp, eUp, true); _pool.Return(isect); return(true); } if (dstUp == _event) { /* Splice dstUp into eLo, and process the new region(s) */ _mesh.SplitEdge(_pool, eLo._Sym); Mesh.Splice(_pool, eUp._Lnext, eLo._Oprev); regLo = regUp; regUp = TopRightRegion(regUp); MeshUtils.Edge e = RegionBelow(regUp)._eUp._Rprev; regLo._eUp = eLo._Oprev; eLo = FinishLeftRegions(regLo, null); AddRightEdges(regUp, eLo._Onext, eUp._Rprev, e, true); _pool.Return(isect); return(true); } // Special case: called from ConnectRightVertex. If either // edge passes on the wrong side of tess._event, split it // (and wait for ConnectRightVertex to splice it appropriately). if (Geom.EdgeSign(dstUp, _event, isect) >= 0.0f) { RegionAbove(regUp)._dirty = regUp._dirty = true; _mesh.SplitEdge(_pool, eUp._Sym); eUp._Org._s = _event._s; eUp._Org._t = _event._t; } if (Geom.EdgeSign(dstLo, _event, isect) <= 0.0f) { regUp._dirty = regLo._dirty = true; _mesh.SplitEdge(_pool, eLo._Sym); eLo._Org._s = _event._s; eLo._Org._t = _event._t; } // leave the rest for ConnectRightVertex _pool.Return(isect); return(false); } // General case -- split both edges, splice into new vertex. // When we do the splice operation, the order of the arguments is // arbitrary as far as correctness goes. However, when the operation // creates a new face, the work done is proportional to the size of // the new face. We expect the faces in the processed part of // the mesh (ie. eUp._Lface) to be smaller than the faces in the // unprocessed original contours (which will be eLo._Oprev._Lface). _mesh.SplitEdge(_pool, eUp._Sym); _mesh.SplitEdge(_pool, eLo._Sym); Mesh.Splice(_pool, eLo._Oprev, eUp); eUp._Org._s = isect._s; eUp._Org._t = isect._t; _pool.Return(isect); isect = null; eUp._Org._pqHandle = _pq.Insert(eUp._Org); if (eUp._Org._pqHandle._handle == PQHandle.Invalid) { throw new InvalidOperationException("PQHandle should not be invalid"); } GetIntersectData(eUp._Org, orgUp, dstUp, orgLo, dstLo); RegionAbove(regUp)._dirty = regUp._dirty = regLo._dirty = true; return(false); }