/* ** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) ** Copyright (C) 2011 Silicon Graphics, Inc. ** All Rights Reserved. ** ** Permission is hereby granted, free of charge, to any person obtaining a copy ** of this software and associated documentation files (the "Software"), to deal ** in the Software without restriction, including without limitation the rights ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies ** of the Software, and to permit persons to whom the Software is furnished to do so, ** subject to the following conditions: ** ** The above copyright notice including the dates of first publication and either this ** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be ** included in all copies or substantial portions of the Software. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, ** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A ** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. ** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE ** OR OTHER DEALINGS IN THE SOFTWARE. ** ** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not ** be used in advertising or otherwise to promote the sale, use or other dealings in ** this Software without prior written authorization from Silicon Graphics, Inc. */ /* ** Original Author: Eric Veach, July 1994. ** libtess2: Mikko Mononen, http://code.google.com/p/libtess2/. ** LibTessDotNet: Remi Gillig, https://github.com/speps/LibTessDotNet */ using System; using System.Diagnostics; namespace UnityEngine.Rendering.Universal { using Real = System.Single; namespace LibTessDotNet { internal partial class Tess { internal class ActiveRegion { internal MeshUtils.Edge _eUp; internal Dict.Node _nodeUp; internal int _windingNumber; internal bool _inside, _sentinel, _dirty, _fixUpperEdge; } private ActiveRegion RegionBelow(ActiveRegion reg) { return reg._nodeUp._prev._key; } private ActiveRegion RegionAbove(ActiveRegion reg) { return reg._nodeUp._next._key; } /// /// 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). /// private bool EdgeLeq(ActiveRegion reg1, ActiveRegion reg2) { var e1 = reg1._eUp; var e2 = reg2._eUp; if (e1._Dst == _event) { if (e2._Dst == _event) { // 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; } return Geom.EdgeSign(e2._Dst, _event, e2._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 var t1 = Geom.EdgeEval(e1._Dst, _event, e1._Org); var t2 = Geom.EdgeEval(e2._Dst, _event, e2._Org); return (t1 >= t2); } private void DeleteRegion(ActiveRegion reg) { if (reg._fixUpperEdge) { // It was created with zero winding number, so it better be // deleted with zero winding number (ie. it better not get merged // with a real edge). Debug.Assert(reg._eUp._winding == 0); } reg._eUp._activeRegion = null; _dict.Remove(reg._nodeUp); } /// /// Replace an upper edge which needs fixing (see ConnectRightVertex). /// private void FixUpperEdge(ActiveRegion reg, MeshUtils.Edge newEdge) { Debug.Assert(reg._fixUpperEdge); _mesh.Delete(reg._eUp); reg._fixUpperEdge = false; reg._eUp = newEdge; newEdge._activeRegion = reg; } private ActiveRegion TopLeftRegion(ActiveRegion reg) { var org = reg._eUp._Org; // Find the region above the uppermost edge with the same origin do { reg = RegionAbove(reg); } while (reg._eUp._Org == org); // If the edge above was a temporary edge introduced by ConnectRightVertex, // now is the time to fix it. if (reg._fixUpperEdge) { var e = _mesh.Connect(RegionBelow(reg)._eUp._Sym, reg._eUp._Lnext); FixUpperEdge(reg, e); reg = RegionAbove(reg); } return reg; } private ActiveRegion TopRightRegion(ActiveRegion reg) { var dst = reg._eUp._Dst; // Find the region above the uppermost edge with the same destination do { reg = RegionAbove(reg); } while (reg._eUp._Dst == dst); return reg; } /// /// 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. /// private ActiveRegion AddRegionBelow(ActiveRegion regAbove, MeshUtils.Edge eNewUp) { var regNew = new ActiveRegion(); regNew._eUp = eNewUp; regNew._nodeUp = _dict.InsertBefore(regAbove._nodeUp, regNew); regNew._fixUpperEdge = false; regNew._sentinel = false; regNew._dirty = false; eNewUp._activeRegion = regNew; return regNew; } private void ComputeWinding(ActiveRegion reg) { reg._windingNumber = RegionAbove(reg)._windingNumber + reg._eUp._winding; reg._inside = Geom.IsWindingInside(_windingRule, reg._windingNumber); } /// /// Delete a region from the sweep line. This happens when the upper /// and lower chains of a region meet (at a vertex on the sweep line). /// The "inside" flag is copied to the appropriate mesh face (we could /// not do this before -- since the structure of the mesh is always /// changing, this face may not have even existed until now). /// private void FinishRegion(ActiveRegion reg) { var e = reg._eUp; var f = e._Lface; f._inside = reg._inside; f._anEdge = e; DeleteRegion(reg); } /// /// We are given a vertex with one or more left-going edges. All affected /// edges should be in the edge dictionary. Starting at regFirst->eUp, /// we walk down deleting all regions where both edges have the same /// origin vOrg. At the same time we copy the "inside" flag from the /// active region to the face, since at this point each face will belong /// to at most one region (this was not necessarily true until this point /// in the sweep). The walk stops at the region above regLast; if regLast /// is null we walk as far as possible. At the same time we relink the /// mesh if necessary, so that the ordering of edges around vOrg is the /// same as in the dictionary. /// private MeshUtils.Edge FinishLeftRegions(ActiveRegion regFirst, ActiveRegion regLast) { var regPrev = regFirst; var ePrev = regFirst._eUp; while (regPrev != regLast) { regPrev._fixUpperEdge = false; // placement was OK var reg = RegionBelow(regPrev); var e = reg._eUp; if (e._Org != ePrev._Org) { if (!reg._fixUpperEdge) { // Remove the last left-going edge. Even though there are no further // edges in the dictionary with this origin, there may be further // such edges in the mesh (if we are adding left edges to a vertex // that has already been processed). Thus it is important to call // FinishRegion rather than just DeleteRegion. FinishRegion(regPrev); break; } // If the edge below was a temporary edge introduced by // ConnectRightVertex, now is the time to fix it. e = _mesh.Connect(ePrev._Lprev, e._Sym); FixUpperEdge(reg, e); } // Relink edges so that ePrev.Onext == e if (ePrev._Onext != e) { _mesh.Splice(e._Oprev, e); _mesh.Splice(ePrev, e); } FinishRegion(regPrev); // may change reg.eUp ePrev = reg._eUp; regPrev = reg; } return ePrev; } /// /// 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. /// private void AddRightEdges(ActiveRegion regUp, MeshUtils.Edge eFirst, MeshUtils.Edge eLast, MeshUtils.Edge eTopLeft, bool cleanUp) { bool firstTime = true; var e = eFirst; do { Debug.Assert(Geom.VertLeq(e._Org, e._Dst)); AddRegionBelow(regUp, e._Sym); e = e._Onext; } 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)._eUp._Rprev; } ActiveRegion regPrev = regUp, reg; var ePrev = eTopLeft; while (true) { reg = RegionBelow(regPrev); e = reg._eUp._Sym; if (e._Org != ePrev._Org) break; if (e._Onext != ePrev) { // Unlink e from its current position, and relink below ePrev _mesh.Splice(e._Oprev, e); _mesh.Splice(ePrev._Oprev, e); } // Compute the winding number and "inside" flag for the new regions reg._windingNumber = regPrev._windingNumber - e._winding; reg._inside = Geom.IsWindingInside(_windingRule, reg._windingNumber); // Check for two outgoing edges with same slope -- process these // before any intersection tests (see example in tessComputeInterior). regPrev._dirty = true; if (!firstTime && CheckForRightSplice(regPrev)) { Geom.AddWinding(e, ePrev); DeleteRegion(regPrev); _mesh.Delete(ePrev); } firstTime = false; regPrev = reg; ePrev = e; } regPrev._dirty = true; Debug.Assert(regPrev._windingNumber - e._winding == reg._windingNumber); if (cleanUp) { // Check for intersections between newly adjacent edges. WalkDirtyRegions(regPrev); } } /// /// Two vertices with idential coordinates are combined into one. /// e1.Org is kept, while e2.Org is discarded. /// private void SpliceMergeVertices(MeshUtils.Edge e1, MeshUtils.Edge e2) { _mesh.Splice(e1, e2); } /// /// Find some weights which describe how the intersection vertex is /// a linear combination of "org" and "dest". Each of the two edges /// which generated "isect" is allocated 50% of the weight; each edge /// splits the weight between its org and dst according to the /// relative distance to "isect". /// private void VertexWeights(MeshUtils.Vertex isect, MeshUtils.Vertex org, MeshUtils.Vertex dst, out Real w0, out Real w1) { var t1 = Geom.VertL1dist(org, isect); var t2 = Geom.VertL1dist(dst, isect); w0 = (t2 / (t1 + t2)) / 2.0f; w1 = (t1 / (t1 + t2)) / 2.0f; isect._coords.X += w0 * org._coords.X + w1 * dst._coords.X; isect._coords.Y += w0 * org._coords.Y + w1 * dst._coords.Y; isect._coords.Z += w0 * org._coords.Z + w1 * dst._coords.Z; } /// /// We've computed a new intersection point, now we need a "data" pointer /// from the user so that we can refer to this new vertex in the /// rendering callbacks. /// private void GetIntersectData(MeshUtils.Vertex isect, MeshUtils.Vertex orgUp, MeshUtils.Vertex dstUp, MeshUtils.Vertex orgLo, MeshUtils.Vertex dstLo) { isect._coords = Vec3.Zero; Real w0, w1, w2, w3; VertexWeights(isect, orgUp, dstUp, out w0, out w1); VertexWeights(isect, orgLo, dstLo, out w2, out w3); if (_combineCallback != null) { isect._data = _combineCallback( isect._coords, new object[] { orgUp._data, dstUp._data, orgLo._data, dstLo._data }, new Real[] { w0, w1, w2, w3 } ); } } /// /// 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. /// private bool CheckForRightSplice(ActiveRegion regUp) { var regLo = RegionBelow(regUp); var eUp = regUp._eUp; var 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(eLo._Sym); _mesh.Splice(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(eUp._Sym); _mesh.Splice(eLo._Oprev, eUp); } return true; } /// /// 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. /// private bool CheckForLeftSplice(ActiveRegion regUp) { var regLo = RegionBelow(regUp); var eUp = regUp._eUp; var 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; var e = _mesh.SplitEdge(eUp); _mesh.Splice(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; var e = _mesh.SplitEdge(eLo); _mesh.Splice(eUp._Lnext, eLo._Sym); e._Rface._inside = regUp._inside; } return true; } /// /// 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. /// private bool CheckForIntersect(ActiveRegion regUp) { var regLo = RegionBelow(regUp); var eUp = regUp._eUp; var eLo = regLo._eUp; var orgUp = eUp._Org; var orgLo = eLo._Org; var dstUp = eUp._Dst; var 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; } var tMinUp = Math.Min(orgUp._t, dstUp._t); var 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 var isect = MeshUtils.Vertex.Create(); 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). var 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); 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(eUp._Sym); _mesh.Splice(eLo._Sym, eUp); regUp = TopLeftRegion(regUp); eUp = RegionBelow(regUp)._eUp; FinishLeftRegions(RegionBelow(regUp), regLo); AddRightEdges(regUp, eUp._Oprev, eUp, eUp, true); return true; } if (dstUp == _event) { /* Splice dstUp into eLo, and process the new region(s) */ _mesh.SplitEdge(eLo._Sym); _mesh.Splice(eUp._Lnext, eLo._Oprev); regLo = regUp; regUp = TopRightRegion(regUp); var e = RegionBelow(regUp)._eUp._Rprev; regLo._eUp = eLo._Oprev; eLo = FinishLeftRegions(regLo, null); AddRightEdges(regUp, eLo._Onext, eUp._Rprev, e, true); 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(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(eLo._Sym); eLo._Org._s = _event._s; eLo._Org._t = _event._t; } // leave the rest for ConnectRightVertex 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(eUp._Sym); _mesh.SplitEdge(eLo._Sym); _mesh.Splice(eLo._Oprev, eUp); eUp._Org._s = isect._s; eUp._Org._t = isect._t; 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; } /// /// When the upper or lower edge of any region changes, the region is /// marked "dirty". This routine walks through all the dirty regions /// and makes sure that the dictionary invariants are satisfied /// (see the comments at the beginning of this file). Of course /// new dirty regions can be created as we make changes to restore /// the invariants. /// private void WalkDirtyRegions(ActiveRegion regUp) { var regLo = RegionBelow(regUp); MeshUtils.Edge eUp, eLo; while (true) { // Find the lowest dirty region (we walk from the bottom up). while (regLo._dirty) { regUp = regLo; regLo = RegionBelow(regLo); } if (!regUp._dirty) { regLo = regUp; regUp = RegionAbove(regUp); if (regUp == null || !regUp._dirty) { // We've walked all the dirty regions return; } } regUp._dirty = false; eUp = regUp._eUp; eLo = regLo._eUp; if (eUp._Dst != eLo._Dst) { // Check that the edge ordering is obeyed at the Dst vertices. if (CheckForLeftSplice(regUp)) { // If the upper or lower edge was marked fixUpperEdge, then // we no longer need it (since these edges are needed only for // vertices which otherwise have no right-going edges). if (regLo._fixUpperEdge) { DeleteRegion(regLo); _mesh.Delete(eLo); regLo = RegionBelow(regUp); eLo = regLo._eUp; } else if (regUp._fixUpperEdge) { DeleteRegion(regUp); _mesh.Delete(eUp); regUp = RegionAbove(regLo); eUp = regUp._eUp; } } } if (eUp._Org != eLo._Org) { if (eUp._Dst != eLo._Dst && !regUp._fixUpperEdge && !regLo._fixUpperEdge && (eUp._Dst == _event || eLo._Dst == _event)) { // When all else fails in CheckForIntersect(), it uses tess._event // as the intersection location. To make this possible, it requires // that tess._event lie between the upper and lower edges, and also // that neither of these is marked fixUpperEdge (since in the worst // case it might splice one of these edges into tess.event, and // violate the invariant that fixable edges are the only right-going // edge from their associated vertex). if (CheckForIntersect(regUp)) { // WalkDirtyRegions() was called recursively; we're done return; } } else { // Even though we can't use CheckForIntersect(), the Org vertices // may violate the dictionary edge ordering. Check and correct this. CheckForRightSplice(regUp); } } if (eUp._Org == eLo._Org && eUp._Dst == eLo._Dst) { // A degenerate loop consisting of only two edges -- delete it. Geom.AddWinding(eLo, eUp); DeleteRegion(regUp); _mesh.Delete(eUp); regUp = RegionAbove(regLo); } } } /// /// 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. /// private void ConnectRightVertex(ActiveRegion regUp, MeshUtils.Edge eBottomLeft) { var eTopLeft = eBottomLeft._Onext; var regLo = RegionBelow(regUp); var eUp = regUp._eUp; var eLo = regLo._eUp; bool degenerate = false; if (eUp._Dst != eLo._Dst) { CheckForIntersect(regUp); } // Possible new degeneracies: upper or lower edge of regUp may pass // through vEvent, or may coincide with new intersection vertex if (Geom.VertEq(eUp._Org, _event)) { _mesh.Splice(eTopLeft._Oprev, eUp); regUp = TopLeftRegion(regUp); eTopLeft = RegionBelow(regUp)._eUp; FinishLeftRegions(RegionBelow(regUp), regLo); degenerate = true; } if (Geom.VertEq(eLo._Org, _event)) { _mesh.Splice(eBottomLeft, eLo._Oprev); eBottomLeft = FinishLeftRegions(regLo, null); degenerate = true; } if (degenerate) { AddRightEdges(regUp, eBottomLeft._Onext, eTopLeft, eTopLeft, true); return; } // Non-degenerate situation -- need to add a temporary, fixable edge. // Connect to the closer of eLo.Org, eUp.Org. MeshUtils.Edge eNew; if (Geom.VertLeq(eLo._Org, eUp._Org)) { eNew = eLo._Oprev; } else { eNew = eUp; } eNew = _mesh.Connect(eBottomLeft._Lprev, eNew); // Prevent cleanup, otherwise eNew might disappear before we've even // had a chance to mark it as a temporary edge. AddRightEdges(regUp, eNew, eNew._Onext, eNew._Onext, false); eNew._Sym._activeRegion._fixUpperEdge = true; WalkDirtyRegions(regUp); } /// /// The event vertex lies exacty on an already-processed edge or vertex. /// Adding the new vertex involves splicing it into the already-processed /// part of the mesh. /// private void ConnectLeftDegenerate(ActiveRegion regUp, MeshUtils.Vertex vEvent) { var e = regUp._eUp; if (Geom.VertEq(e._Org, vEvent)) { // e.Org is an unprocessed vertex - just combine them, and wait // for e.Org to be pulled from the queue // C# : in the C version, there is a flag but it was never implemented // the vertices are before beginning the tessellation throw new InvalidOperationException("Vertices should have been merged before"); } if (!Geom.VertEq(e._Dst, vEvent)) { // General case -- splice vEvent into edge e which passes through it _mesh.SplitEdge(e._Sym); if (regUp._fixUpperEdge) { // This edge was fixable -- delete unused portion of original edge _mesh.Delete(e._Onext); regUp._fixUpperEdge = false; } _mesh.Splice(vEvent._anEdge, e); SweepEvent(vEvent); // recurse return; } // See above throw new InvalidOperationException("Vertices should have been merged before"); } /// /// Purpose: connect a "left" vertex (one where both edges go right) /// to the processed portion of the mesh. Let R be the active region /// containing vEvent, and let U and L be the upper and lower edge /// chains of R. There are two possibilities: /// /// - the normal case: split R into two regions, by connecting vEvent to /// the rightmost vertex of U or L lying to the left of the sweep line /// /// - the degenerate case: if vEvent is close enough to U or L, we /// merge vEvent into that edge chain. The subcases are: /// - merging with the rightmost vertex of U or L /// - merging with the active edge of U or L /// - merging with an already-processed portion of U or L /// private void ConnectLeftVertex(MeshUtils.Vertex vEvent) { var tmp = new ActiveRegion(); // Get a pointer to the active region containing vEvent tmp._eUp = vEvent._anEdge._Sym; var regUp = _dict.Find(tmp).Key; var regLo = RegionBelow(regUp); if (regLo == null) { // This may happen if the input polygon is coplanar. return; } var eUp = regUp._eUp; var eLo = regLo._eUp; // Try merging with U or L first if (Geom.EdgeSign(eUp._Dst, vEvent, eUp._Org) == 0.0f) { ConnectLeftDegenerate(regUp, vEvent); return; } // Connect vEvent to rightmost processed vertex of either chain. // e._Dst is the vertex that we will connect to vEvent. var reg = Geom.VertLeq(eLo._Dst, eUp._Dst) ? regUp : regLo; if (regUp._inside || reg._fixUpperEdge) { MeshUtils.Edge eNew; if (reg == regUp) { eNew = _mesh.Connect(vEvent._anEdge._Sym, eUp._Lnext); } else { eNew = _mesh.Connect(eLo._Dnext, vEvent._anEdge)._Sym; } if (reg._fixUpperEdge) { FixUpperEdge(reg, eNew); } else { ComputeWinding(AddRegionBelow(regUp, eNew)); } SweepEvent(vEvent); } else { // The new vertex is in a region which does not belong to the polygon. // We don't need to connect this vertex to the rest of the mesh. AddRightEdges(regUp, vEvent._anEdge, vEvent._anEdge, null, true); } } /// /// Does everything necessary when the sweep line crosses a vertex. /// Updates the mesh and the edge dictionary. /// private void SweepEvent(MeshUtils.Vertex vEvent) { _event = vEvent; // Check if this vertex is the right endpoint of an edge that is // already in the dictionary. In this case we don't need to waste // time searching for the location to insert new edges. var e = vEvent._anEdge; while (e._activeRegion == null) { e = e._Onext; if (e == vEvent._anEdge) { // All edges go right -- not incident to any processed edges ConnectLeftVertex(vEvent); return; } } // Processing consists of two phases: first we "finish" all the // active regions where both the upper and lower edges terminate // at vEvent (ie. vEvent is closing off these regions). // We mark these faces "inside" or "outside" the polygon according // to their winding number, and delete the edges from the dictionary. // This takes care of all the left-going edges from vEvent. var regUp = TopLeftRegion(e._activeRegion); var reg = RegionBelow(regUp); var eTopLeft = reg._eUp; var eBottomLeft = FinishLeftRegions(reg, null); // Next we process all the right-going edges from vEvent. This // involves adding the edges to the dictionary, and creating the // associated "active regions" which record information about the // regions between adjacent dictionary edges. if (eBottomLeft._Onext == eTopLeft) { // No right-going edges -- add a temporary "fixable" edge ConnectRightVertex(regUp, eBottomLeft); } else { AddRightEdges(regUp, eBottomLeft._Onext, eTopLeft, eTopLeft, true); } } /// /// Make the sentinel coordinates big enough that they will never be /// merged with real input features. /// /// We add two sentinel edges above and below all other edges, /// to avoid special cases at the top and bottom. /// private void AddSentinel(Real smin, Real smax, Real t) { var e = _mesh.MakeEdge(); e._Org._s = smax; e._Org._t = t; e._Dst._s = smin; e._Dst._t = t; _event = e._Dst; // initialize it var reg = new ActiveRegion(); reg._eUp = e; reg._windingNumber = 0; reg._inside = false; reg._fixUpperEdge = false; reg._sentinel = true; reg._dirty = false; reg._nodeUp = _dict.Insert(reg); } /// /// We maintain an ordering of edge intersections with the sweep line. /// This order is maintained in a dynamic dictionary. /// private void InitEdgeDict() { _dict = new Dict(EdgeLeq); AddSentinel(-SentinelCoord, SentinelCoord, -SentinelCoord); AddSentinel(-SentinelCoord, SentinelCoord, +SentinelCoord); } private void DoneEdgeDict() { int fixedEdges = 0; ActiveRegion reg; while ((reg = _dict.Min().Key) != null) { // At the end of all processing, the dictionary should contain // only the two sentinel edges, plus at most one "fixable" edge // created by ConnectRightVertex(). if (!reg._sentinel) { Debug.Assert(reg._fixUpperEdge); Debug.Assert(++fixedEdges == 1); } Debug.Assert(reg._windingNumber == 0); DeleteRegion(reg); } _dict = null; } /// /// Remove zero-length edges, and contours with fewer than 3 vertices. /// private void RemoveDegenerateEdges() { MeshUtils.Edge eHead = _mesh._eHead, e, eNext, eLnext; for (e = eHead._next; e != eHead; e = eNext) { eNext = e._next; eLnext = e._Lnext; if (Geom.VertEq(e._Org, e._Dst) && e._Lnext._Lnext != e) { // Zero-length edge, contour has at least 3 edges SpliceMergeVertices(eLnext, e); // deletes e.Org _mesh.Delete(e); // e is a self-loop e = eLnext; eLnext = e._Lnext; } if (eLnext._Lnext == e) { // Degenerate contour (one or two edges) if (eLnext != e) { if (eLnext == eNext || eLnext == eNext._Sym) { eNext = eNext._next; } _mesh.Delete(eLnext); } if (e == eNext || e == eNext._Sym) { eNext = eNext._next; } _mesh.Delete(e); } } } /// /// Insert all vertices into the priority queue which determines the /// order in which vertices cross the sweep line. /// private void InitPriorityQ() { MeshUtils.Vertex vHead = _mesh._vHead, v; int vertexCount = 0; for (v = vHead._next; v != vHead; v = v._next) { vertexCount++; } // Make sure there is enough space for sentinels. vertexCount += 8; _pq = new PriorityQueue(vertexCount, Geom.VertLeq); vHead = _mesh._vHead; for (v = vHead._next; v != vHead; v = v._next) { v._pqHandle = _pq.Insert(v); if (v._pqHandle._handle == PQHandle.Invalid) { throw new InvalidOperationException("PQHandle should not be invalid"); } } _pq.Init(); } private void DonePriorityQ() { _pq = null; } /// /// 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 void RemoveDegenerateFaces() { MeshUtils.Face f, fNext; MeshUtils.Edge e; for (f = _mesh._fHead._next; f != _mesh._fHead; f = fNext) { fNext = f._next; e = f._anEdge; Debug.Assert(e._Lnext != e); if (e._Lnext._Lnext == e) { // A face with only two edges Geom.AddWinding(e._Onext, e); _mesh.Delete(e); } } } /// /// ComputeInterior 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 windingRule. /// Each interior region is guaranteed to be monotone. /// protected void ComputeInterior() { // Each vertex defines an event for our sweep line. Start by inserting // all the vertices in a priority queue. Events are processed in // lexicographic order, ie. // // e1 < e2 iff e1.x < e2.x || (e1.x == e2.x && e1.y < e2.y) RemoveDegenerateEdges(); InitPriorityQ(); RemoveDegenerateFaces(); InitEdgeDict(); MeshUtils.Vertex v, vNext; while ((v = _pq.ExtractMin()) != null) { while (true) { vNext = _pq.Minimum(); if (vNext == null || !Geom.VertEq(vNext, v)) { break; } // Merge together all vertices at exactly the same location. // This is more efficient than processing them one at a time, // simplifies the code (see ConnectLeftDegenerate), and is also // important for correct handling of certain degenerate cases. // For example, suppose there are two identical edges A and B // that belong to different contours (so without this code they would // be processed by separate sweep events). Suppose another edge C // crosses A and B from above. When A is processed, we split it // at its intersection point with C. However this also splits C, // so when we insert B we may compute a slightly different // intersection point. This might leave two edges with a small // gap between them. This kind of error is especially obvious // when using boundary extraction (BoundaryOnly). vNext = _pq.ExtractMin(); SpliceMergeVertices(v._anEdge, vNext._anEdge); } SweepEvent(v); } DoneEdgeDict(); DonePriorityQ(); RemoveDegenerateFaces(); _mesh.Check(); } } } }