12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238 |
- /*
- ** 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.Experimental.Rendering.Universal
- {
- using Real = System.Single;
- namespace LibTessDotNet
- {
- internal partial class Tess
- {
- internal class ActiveRegion
- {
- internal MeshUtils.Edge _eUp;
- internal Dict<ActiveRegion>.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;
- }
- /// <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)
- {
- 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);
- }
- /// <summary>
- /// Replace an upper edge which needs fixing (see ConnectRightVertex).
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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);
- }
- /// <summary>
- /// 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).
- /// </summary>
- private void FinishRegion(ActiveRegion reg)
- {
- var e = reg._eUp;
- var f = e._Lface;
- f._inside = reg._inside;
- f._anEdge = e;
- DeleteRegion(reg);
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// Two vertices with idential coordinates are combined into one.
- /// e1.Org is kept, while e2.Org is discarded.
- /// </summary>
- private void SpliceMergeVertices(MeshUtils.Edge e1, MeshUtils.Edge e2)
- {
- _mesh.Splice(e1, e2);
- }
- /// <summary>
- /// 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".
- /// </summary>
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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 }
- );
- }
- }
- /// <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)
- {
- 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;
- }
-
- /// <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)
- {
- 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;
- }
- /// <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)
- {
- 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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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);
- }
- }
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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);
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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");
- }
- /// <summary>
- /// 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
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// Does everything necessary when the sweep line crosses a vertex.
- /// Updates the mesh and the edge dictionary.
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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);
- }
- /// <summary>
- /// We maintain an ordering of edge intersections with the sweep line.
- /// This order is maintained in a dynamic dictionary.
- /// </summary>
- private void InitEdgeDict()
- {
- _dict = new Dict<ActiveRegion>(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;
- }
- /// <summary>
- /// Remove zero-length edges, and contours with fewer than 3 vertices.
- /// </summary>
- 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);
- }
- }
- }
- /// <summary>
- /// Insert all vertices into the priority queue which determines the
- /// order in which vertices cross the sweep line.
- /// </summary>
- 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<MeshUtils.Vertex>(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;
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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);
- }
- }
- }
- /// <summary>
- /// 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.
- /// </summary>
- 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();
- }
- }
- }
- }
|