UNPKG

tess2

Version:

GLU tesselator ported to Javascript, performs polygon boolean operations and triangulation

1,536 lines (1,330 loc) 112 kB
/* ** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) ** Copyright (C) [dates of first publication] 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. */ /* ** Author: Mikko Mononen, Aug 2013. ** The code is based on GLU libtess by Eric Veach, July 1994 */ "use strict"; /* Public API */ var Tess2 = {}; module.exports = Tess2; Tess2.WINDING_ODD = 0; Tess2.WINDING_NONZERO = 1; Tess2.WINDING_POSITIVE = 2; Tess2.WINDING_NEGATIVE = 3; Tess2.WINDING_ABS_GEQ_TWO = 4; Tess2.POLYGONS = 0; Tess2.CONNECTED_POLYGONS = 1; Tess2.BOUNDARY_CONTOURS = 2; Tess2.tesselate = function(opts) { var debug = opts.debug || false; var tess = new Tesselator(); for (var i = 0; i < opts.contours.length; i++) { tess.addContour(opts.vertexSize || 2, opts.contours[i]); } tess.tesselate(opts.windingRule || Tess2.WINDING_ODD, opts.elementType || Tess2.POLYGONS, opts.polySize || 3, opts.vertexSize || 2, opts.normal || [0,0,1]); return { vertices: tess.vertices, vertexIndices: tess.vertexIndices, vertexCount: tess.vertexCount, elements: tess.elements, elementCount: tess.elementCount, mesh: debug ? tess.mesh : undefined }; }; /* Internal */ var assert = function(cond) { if (!cond) { throw "Assertion Failed!"; } } /* The mesh structure is similar in spirit, notation, and operations * to the "quad-edge" structure (see L. Guibas and J. Stolfi, Primitives * for the manipulation of general subdivisions and the computation of * Voronoi diagrams, ACM Transactions on Graphics, 4(2):74-123, April 1985). * For a simplified description, see the course notes for CS348a, * "Mathematical Foundations of Computer Graphics", available at the * Stanford bookstore (and taught during the fall quarter). * The implementation also borrows a tiny subset of the graph-based approach * use in Mantyla's Geometric Work Bench (see M. Mantyla, An Introduction * to Sold Modeling, Computer Science Press, Rockville, Maryland, 1988). * * The fundamental data structure is the "half-edge". Two half-edges * go together to make an edge, but they point in opposite directions. * Each half-edge has a pointer to its mate (the "symmetric" half-edge Sym), * its origin vertex (Org), the face on its left side (Lface), and the * adjacent half-edges in the CCW direction around the origin vertex * (Onext) and around the left face (Lnext). There is also a "next" * pointer for the global edge list (see below). * * The notation used for mesh navigation: * Sym = the mate of a half-edge (same edge, but opposite direction) * Onext = edge CCW around origin vertex (keep same origin) * Dnext = edge CCW around destination vertex (keep same dest) * Lnext = edge CCW around left face (dest becomes new origin) * Rnext = edge CCW around right face (origin becomes new dest) * * "prev" means to substitute CW for CCW in the definitions above. * * The mesh keeps global lists of all vertices, faces, and edges, * stored as doubly-linked circular lists with a dummy header node. * The mesh stores pointers to these dummy headers (vHead, fHead, eHead). * * The circular edge list is special; since half-edges always occur * in pairs (e and e->Sym), each half-edge stores a pointer in only * one direction. Starting at eHead and following the e->next pointers * will visit each *edge* once (ie. e or e->Sym, but not both). * e->Sym stores a pointer in the opposite direction, thus it is * always true that e->Sym->next->Sym->next == e. * * Each vertex has a pointer to next and previous vertices in the * circular list, and a pointer to a half-edge with this vertex as * the origin (NULL if this is the dummy header). There is also a * field "data" for client data. * * Each face has a pointer to the next and previous faces in the * circular list, and a pointer to a half-edge with this face as * the left face (NULL if this is the dummy header). There is also * a field "data" for client data. * * Note that what we call a "face" is really a loop; faces may consist * of more than one loop (ie. not simply connected), but there is no * record of this in the data structure. The mesh may consist of * several disconnected regions, so it may not be possible to visit * the entire mesh by starting at a half-edge and traversing the edge * structure. * * The mesh does NOT support isolated vertices; a vertex is deleted along * with its last edge. Similarly when two faces are merged, one of the * faces is deleted (see tessMeshDelete below). For mesh operations, * all face (loop) and vertex pointers must not be NULL. However, once * mesh manipulation is finished, TESSmeshZapFace can be used to delete * faces of the mesh, one at a time. All external faces can be "zapped" * before the mesh is returned to the client; then a NULL face indicates * a region which is not part of the output polygon. */ function TESSvertex() { this.next = null; /* next vertex (never NULL) */ this.prev = null; /* previous vertex (never NULL) */ this.anEdge = null; /* a half-edge with this origin */ /* Internal data (keep hidden) */ this.coords = [0,0,0]; /* vertex location in 3D */ this.s = 0.0; this.t = 0.0; /* projection onto the sweep plane */ this.pqHandle = 0; /* to allow deletion from priority queue */ this.n = 0; /* to allow identify unique vertices */ this.idx = 0; /* to allow map result to original verts */ } function TESSface() { this.next = null; /* next face (never NULL) */ this.prev = null; /* previous face (never NULL) */ this.anEdge = null; /* a half edge with this left face */ /* Internal data (keep hidden) */ this.trail = null; /* "stack" for conversion to strips */ this.n = 0; /* to allow identiy unique faces */ this.marked = false; /* flag for conversion to strips */ this.inside = false; /* this face is in the polygon interior */ }; function TESShalfEdge(side) { this.next = null; /* doubly-linked list (prev==Sym->next) */ this.Sym = null; /* same edge, opposite direction */ this.Onext = null; /* next edge CCW around origin */ this.Lnext = null; /* next edge CCW around left face */ this.Org = null; /* origin vertex (Overtex too long) */ this.Lface = null; /* left face */ /* Internal data (keep hidden) */ this.activeRegion = null; /* a region with this upper edge (sweep.c) */ this.winding = 0; /* change in winding number when crossing from the right face to the left face */ this.side = side; }; TESShalfEdge.prototype = { get Rface() { return this.Sym.Lface; }, set Rface(v) { this.Sym.Lface = v; }, get Dst() { return this.Sym.Org; }, set Dst(v) { this.Sym.Org = v; }, get Oprev() { return this.Sym.Lnext; }, set Oprev(v) { this.Sym.Lnext = v; }, get Lprev() { return this.Onext.Sym; }, set Lprev(v) { this.Onext.Sym = v; }, get Dprev() { return this.Lnext.Sym; }, set Dprev(v) { this.Lnext.Sym = v; }, get Rprev() { return this.Sym.Onext; }, set Rprev(v) { this.Sym.Onext = v; }, get Dnext() { return /*this.Rprev*/this.Sym.Onext.Sym; }, /* 3 pointers */ set Dnext(v) { /*this.Rprev*/this.Sym.Onext.Sym = v; }, /* 3 pointers */ get Rnext() { return /*this.Oprev*/this.Sym.Lnext.Sym; }, /* 3 pointers */ set Rnext(v) { /*this.Oprev*/this.Sym.Lnext.Sym = v; }, /* 3 pointers */ }; function TESSmesh() { var v = new TESSvertex(); var f = new TESSface(); var e = new TESShalfEdge(0); var eSym = new TESShalfEdge(1); v.next = v.prev = v; v.anEdge = null; f.next = f.prev = f; f.anEdge = null; f.trail = null; f.marked = false; f.inside = false; e.next = e; e.Sym = eSym; e.Onext = null; e.Lnext = null; e.Org = null; e.Lface = null; e.winding = 0; e.activeRegion = null; eSym.next = eSym; eSym.Sym = e; eSym.Onext = null; eSym.Lnext = null; eSym.Org = null; eSym.Lface = null; eSym.winding = 0; eSym.activeRegion = null; this.vHead = v; /* dummy header for vertex list */ this.fHead = f; /* dummy header for face list */ this.eHead = e; /* dummy header for edge list */ this.eHeadSym = eSym; /* and its symmetric counterpart */ }; /* The mesh operations below have three motivations: completeness, * convenience, and efficiency. The basic mesh operations are MakeEdge, * Splice, and Delete. All the other edge operations can be implemented * in terms of these. The other operations are provided for convenience * and/or efficiency. * * When a face is split or a vertex is added, they are inserted into the * global list *before* the existing vertex or face (ie. e->Org or e->Lface). * This makes it easier to process all vertices or faces in the global lists * without worrying about processing the same data twice. As a convenience, * when a face is split, the "inside" flag is copied from the old face. * Other internal data (v->data, v->activeRegion, f->data, f->marked, * f->trail, e->winding) is set to zero. * * ********************** Basic Edge Operations ************************** * * tessMeshMakeEdge( mesh ) creates one edge, two vertices, and a loop. * The loop (face) consists of the two new half-edges. * * tessMeshSplice( eOrg, eDst ) is the basic operation for changing the * mesh connectivity and topology. It changes the mesh so that * eOrg->Onext <- OLD( eDst->Onext ) * eDst->Onext <- OLD( eOrg->Onext ) * where OLD(...) means the value before the meshSplice operation. * * This can have two effects on the vertex structure: * - if eOrg->Org != eDst->Org, the two vertices are merged together * - if eOrg->Org == eDst->Org, the origin is split into two vertices * In both cases, eDst->Org is changed and eOrg->Org is untouched. * * Similarly (and independently) for the face structure, * - if eOrg->Lface == eDst->Lface, one loop is split into two * - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one * In both cases, eDst->Lface is changed and eOrg->Lface is unaffected. * * tessMeshDelete( eDel ) removes the edge eDel. There are several cases: * if (eDel->Lface != eDel->Rface), we join two loops into one; the loop * eDel->Lface is deleted. Otherwise, we are splitting one loop into two; * the newly created loop will contain eDel->Dst. If the deletion of eDel * would create isolated vertices, those are deleted as well. * * ********************** Other Edge Operations ************************** * * tessMeshAddEdgeVertex( eOrg ) creates a new edge eNew such that * eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex. * eOrg and eNew will have the same left face. * * tessMeshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew, * such that eNew == eOrg->Lnext. The new vertex is eOrg->Dst == eNew->Org. * eOrg and eNew will have the same left face. * * tessMeshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst * to eDst->Org, and returns the corresponding half-edge eNew. * If eOrg->Lface == eDst->Lface, this splits one loop into two, * and the newly created loop is eNew->Lface. Otherwise, two disjoint * loops are merged into one, and the loop eDst->Lface is destroyed. * * ************************ Other Operations ***************************** * * tessMeshNewMesh() creates a new mesh with no edges, no vertices, * and no loops (what we usually call a "face"). * * tessMeshUnion( mesh1, mesh2 ) forms the union of all structures in * both meshes, and returns the new mesh (the old meshes are destroyed). * * tessMeshDeleteMesh( mesh ) will free all storage for any valid mesh. * * tessMeshZapFace( fZap ) destroys a face and removes it from the * global face list. All edges of fZap will have a NULL pointer as their * left face. Any edges which also have a NULL pointer as their right face * are deleted entirely (along with any isolated vertices this produces). * An entire mesh can be deleted by zapping its faces, one at a time, * in any order. Zapped faces cannot be used in further mesh operations! * * tessMeshCheckMesh( mesh ) checks a mesh for self-consistency. */ TESSmesh.prototype = { /* MakeEdge creates a new pair of half-edges which form their own loop. * No vertex or face structures are allocated, but these must be assigned * before the current edge operation is completed. */ //static TESShalfEdge *MakeEdge( TESSmesh* mesh, TESShalfEdge *eNext ) makeEdge_: function(eNext) { var e = new TESShalfEdge(0); var eSym = new TESShalfEdge(1); /* Make sure eNext points to the first edge of the edge pair */ if( eNext.Sym.side < eNext.side ) { eNext = eNext.Sym; } /* Insert in circular doubly-linked list before eNext. * Note that the prev pointer is stored in Sym->next. */ var ePrev = eNext.Sym.next; eSym.next = ePrev; ePrev.Sym.next = e; e.next = eNext; eNext.Sym.next = eSym; e.Sym = eSym; e.Onext = e; e.Lnext = eSym; e.Org = null; e.Lface = null; e.winding = 0; e.activeRegion = null; eSym.Sym = e; eSym.Onext = eSym; eSym.Lnext = e; eSym.Org = null; eSym.Lface = null; eSym.winding = 0; eSym.activeRegion = null; return e; }, /* Splice( a, b ) is best described by the Guibas/Stolfi paper or the * CS348a notes (see mesh.h). Basically it modifies the mesh so that * a->Onext and b->Onext are exchanged. This can have various effects * depending on whether a and b belong to different face or vertex rings. * For more explanation see tessMeshSplice() below. */ // static void Splice( TESShalfEdge *a, TESShalfEdge *b ) splice_: function(a, b) { var aOnext = a.Onext; var bOnext = b.Onext; aOnext.Sym.Lnext = b; bOnext.Sym.Lnext = a; a.Onext = bOnext; b.Onext = aOnext; }, /* MakeVertex( newVertex, eOrig, vNext ) attaches a new vertex and makes it the * origin of all edges in the vertex loop to which eOrig belongs. "vNext" gives * a place to insert the new vertex in the global vertex list. We insert * the new vertex *before* vNext so that algorithms which walk the vertex * list will not see the newly created vertices. */ //static void MakeVertex( TESSvertex *newVertex, TESShalfEdge *eOrig, TESSvertex *vNext ) makeVertex_: function(newVertex, eOrig, vNext) { var vNew = newVertex; assert(vNew !== null); /* insert in circular doubly-linked list before vNext */ var vPrev = vNext.prev; vNew.prev = vPrev; vPrev.next = vNew; vNew.next = vNext; vNext.prev = vNew; vNew.anEdge = eOrig; /* leave coords, s, t undefined */ /* fix other edges on this vertex loop */ var e = eOrig; do { e.Org = vNew; e = e.Onext; } while(e !== eOrig); }, /* MakeFace( newFace, eOrig, fNext ) attaches a new face and makes it the left * face of all edges in the face loop to which eOrig belongs. "fNext" gives * a place to insert the new face in the global face list. We insert * the new face *before* fNext so that algorithms which walk the face * list will not see the newly created faces. */ // static void MakeFace( TESSface *newFace, TESShalfEdge *eOrig, TESSface *fNext ) makeFace_: function(newFace, eOrig, fNext) { var fNew = newFace; assert(fNew !== null); /* insert in circular doubly-linked list before fNext */ var fPrev = fNext.prev; fNew.prev = fPrev; fPrev.next = fNew; fNew.next = fNext; fNext.prev = fNew; fNew.anEdge = eOrig; fNew.trail = null; fNew.marked = false; /* The new face is marked "inside" if the old one was. This is a * convenience for the common case where a face has been split in two. */ fNew.inside = fNext.inside; /* fix other edges on this face loop */ var e = eOrig; do { e.Lface = fNew; e = e.Lnext; } while(e !== eOrig); }, /* KillEdge( eDel ) destroys an edge (the half-edges eDel and eDel->Sym), * and removes from the global edge list. */ //static void KillEdge( TESSmesh *mesh, TESShalfEdge *eDel ) killEdge_: function(eDel) { /* Half-edges are allocated in pairs, see EdgePair above */ if( eDel.Sym.side < eDel.side ) { eDel = eDel.Sym; } /* delete from circular doubly-linked list */ var eNext = eDel.next; var ePrev = eDel.Sym.next; eNext.Sym.next = ePrev; ePrev.Sym.next = eNext; }, /* KillVertex( vDel ) destroys a vertex and removes it from the global * vertex list. It updates the vertex loop to point to a given new vertex. */ //static void KillVertex( TESSmesh *mesh, TESSvertex *vDel, TESSvertex *newOrg ) killVertex_: function(vDel, newOrg) { var eStart = vDel.anEdge; /* change the origin of all affected edges */ var e = eStart; do { e.Org = newOrg; e = e.Onext; } while(e !== eStart); /* delete from circular doubly-linked list */ var vPrev = vDel.prev; var vNext = vDel.next; vNext.prev = vPrev; vPrev.next = vNext; }, /* KillFace( fDel ) destroys a face and removes it from the global face * list. It updates the face loop to point to a given new face. */ //static void KillFace( TESSmesh *mesh, TESSface *fDel, TESSface *newLface ) killFace_: function(fDel, newLface) { var eStart = fDel.anEdge; /* change the left face of all affected edges */ var e = eStart; do { e.Lface = newLface; e = e.Lnext; } while(e !== eStart); /* delete from circular doubly-linked list */ var fPrev = fDel.prev; var fNext = fDel.next; fNext.prev = fPrev; fPrev.next = fNext; }, /****************** Basic Edge Operations **********************/ /* tessMeshMakeEdge creates one edge, two vertices, and a loop (face). * The loop consists of the two new half-edges. */ //TESShalfEdge *tessMeshMakeEdge( TESSmesh *mesh ) makeEdge: function() { var newVertex1 = new TESSvertex(); var newVertex2 = new TESSvertex(); var newFace = new TESSface(); var e = this.makeEdge_( this.eHead); this.makeVertex_( newVertex1, e, this.vHead ); this.makeVertex_( newVertex2, e.Sym, this.vHead ); this.makeFace_( newFace, e, this.fHead ); return e; }, /* tessMeshSplice( eOrg, eDst ) is the basic operation for changing the * mesh connectivity and topology. It changes the mesh so that * eOrg->Onext <- OLD( eDst->Onext ) * eDst->Onext <- OLD( eOrg->Onext ) * where OLD(...) means the value before the meshSplice operation. * * This can have two effects on the vertex structure: * - if eOrg->Org != eDst->Org, the two vertices are merged together * - if eOrg->Org == eDst->Org, the origin is split into two vertices * In both cases, eDst->Org is changed and eOrg->Org is untouched. * * Similarly (and independently) for the face structure, * - if eOrg->Lface == eDst->Lface, one loop is split into two * - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one * In both cases, eDst->Lface is changed and eOrg->Lface is unaffected. * * Some special cases: * If eDst == eOrg, the operation has no effect. * If eDst == eOrg->Lnext, the new face will have a single edge. * If eDst == eOrg->Lprev, the old face will have a single edge. * If eDst == eOrg->Onext, the new vertex will have a single edge. * If eDst == eOrg->Oprev, the old vertex will have a single edge. */ //int tessMeshSplice( TESSmesh* mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst ) splice: function(eOrg, eDst) { var joiningLoops = false; var joiningVertices = false; if( eOrg === eDst ) return; if( eDst.Org !== eOrg.Org ) { /* We are merging two disjoint vertices -- destroy eDst->Org */ joiningVertices = true; this.killVertex_( eDst.Org, eOrg.Org ); } if( eDst.Lface !== eOrg.Lface ) { /* We are connecting two disjoint loops -- destroy eDst->Lface */ joiningLoops = true; this.killFace_( eDst.Lface, eOrg.Lface ); } /* Change the edge structure */ this.splice_( eDst, eOrg ); if( ! joiningVertices ) { var newVertex = new TESSvertex(); /* We split one vertex into two -- the new vertex is eDst->Org. * Make sure the old vertex points to a valid half-edge. */ this.makeVertex_( newVertex, eDst, eOrg.Org ); eOrg.Org.anEdge = eOrg; } if( ! joiningLoops ) { var newFace = new TESSface(); /* We split one loop into two -- the new loop is eDst->Lface. * Make sure the old face points to a valid half-edge. */ this.makeFace_( newFace, eDst, eOrg.Lface ); eOrg.Lface.anEdge = eOrg; } }, /* tessMeshDelete( eDel ) removes the edge eDel. There are several cases: * if (eDel->Lface != eDel->Rface), we join two loops into one; the loop * eDel->Lface is deleted. Otherwise, we are splitting one loop into two; * the newly created loop will contain eDel->Dst. If the deletion of eDel * would create isolated vertices, those are deleted as well. * * This function could be implemented as two calls to tessMeshSplice * plus a few calls to memFree, but this would allocate and delete * unnecessary vertices and faces. */ //int tessMeshDelete( TESSmesh *mesh, TESShalfEdge *eDel ) delete: function(eDel) { var eDelSym = eDel.Sym; var joiningLoops = false; /* First step: disconnect the origin vertex eDel->Org. We make all * changes to get a consistent mesh in this "intermediate" state. */ if( eDel.Lface !== eDel.Rface ) { /* We are joining two loops into one -- remove the left face */ joiningLoops = true; this.killFace_( eDel.Lface, eDel.Rface ); } if( eDel.Onext === eDel ) { this.killVertex_( eDel.Org, null ); } else { /* Make sure that eDel->Org and eDel->Rface point to valid half-edges */ eDel.Rface.anEdge = eDel.Oprev; eDel.Org.anEdge = eDel.Onext; this.splice_( eDel, eDel.Oprev ); if( ! joiningLoops ) { var newFace = new TESSface(); /* We are splitting one loop into two -- create a new loop for eDel. */ this.makeFace_( newFace, eDel, eDel.Lface ); } } /* Claim: the mesh is now in a consistent state, except that eDel->Org * may have been deleted. Now we disconnect eDel->Dst. */ if( eDelSym.Onext === eDelSym ) { this.killVertex_( eDelSym.Org, null ); this.killFace_( eDelSym.Lface, null ); } else { /* Make sure that eDel->Dst and eDel->Lface point to valid half-edges */ eDel.Lface.anEdge = eDelSym.Oprev; eDelSym.Org.anEdge = eDelSym.Onext; this.splice_( eDelSym, eDelSym.Oprev ); } /* Any isolated vertices or faces have already been freed. */ this.killEdge_( eDel ); }, /******************** Other Edge Operations **********************/ /* All these routines can be implemented with the basic edge * operations above. They are provided for convenience and efficiency. */ /* tessMeshAddEdgeVertex( eOrg ) creates a new edge eNew such that * eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex. * eOrg and eNew will have the same left face. */ // TESShalfEdge *tessMeshAddEdgeVertex( TESSmesh *mesh, TESShalfEdge *eOrg ); addEdgeVertex: function(eOrg) { var eNew = this.makeEdge_( eOrg ); var eNewSym = eNew.Sym; /* Connect the new edge appropriately */ this.splice_( eNew, eOrg.Lnext ); /* Set the vertex and face information */ eNew.Org = eOrg.Dst; var newVertex = new TESSvertex(); this.makeVertex_( newVertex, eNewSym, eNew.Org ); eNew.Lface = eNewSym.Lface = eOrg.Lface; return eNew; }, /* tessMeshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew, * such that eNew == eOrg->Lnext. The new vertex is eOrg->Dst == eNew->Org. * eOrg and eNew will have the same left face. */ // TESShalfEdge *tessMeshSplitEdge( TESSmesh *mesh, TESShalfEdge *eOrg ); splitEdge: function(eOrg, eDst) { var tempHalfEdge = this.addEdgeVertex( eOrg ); var eNew = tempHalfEdge.Sym; /* Disconnect eOrg from eOrg->Dst and connect it to eNew->Org */ this.splice_( eOrg.Sym, eOrg.Sym.Oprev ); this.splice_( eOrg.Sym, eNew ); /* Set the vertex and face information */ eOrg.Dst = eNew.Org; eNew.Dst.anEdge = eNew.Sym; /* may have pointed to eOrg->Sym */ eNew.Rface = eOrg.Rface; eNew.winding = eOrg.winding; /* copy old winding information */ eNew.Sym.winding = eOrg.Sym.winding; return eNew; }, /* tessMeshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst * to eDst->Org, and returns the corresponding half-edge eNew. * If eOrg->Lface == eDst->Lface, this splits one loop into two, * and the newly created loop is eNew->Lface. Otherwise, two disjoint * loops are merged into one, and the loop eDst->Lface is destroyed. * * If (eOrg == eDst), the new face will have only two edges. * If (eOrg->Lnext == eDst), the old face is reduced to a single edge. * If (eOrg->Lnext->Lnext == eDst), the old face is reduced to two edges. */ // TESShalfEdge *tessMeshConnect( TESSmesh *mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst ); connect: function(eOrg, eDst) { var joiningLoops = false; var eNew = this.makeEdge_( eOrg ); var eNewSym = eNew.Sym; if( eDst.Lface !== eOrg.Lface ) { /* We are connecting two disjoint loops -- destroy eDst->Lface */ joiningLoops = true; this.killFace_( eDst.Lface, eOrg.Lface ); } /* Connect the new edge appropriately */ this.splice_( eNew, eOrg.Lnext ); this.splice_( eNewSym, eDst ); /* Set the vertex and face information */ eNew.Org = eOrg.Dst; eNewSym.Org = eDst.Org; eNew.Lface = eNewSym.Lface = eOrg.Lface; /* Make sure the old face points to a valid half-edge */ eOrg.Lface.anEdge = eNewSym; if( ! joiningLoops ) { var newFace = new TESSface(); /* We split one loop into two -- the new loop is eNew->Lface */ this.makeFace_( newFace, eNew, eOrg.Lface ); } return eNew; }, /* tessMeshZapFace( fZap ) destroys a face and removes it from the * global face list. All edges of fZap will have a NULL pointer as their * left face. Any edges which also have a NULL pointer as their right face * are deleted entirely (along with any isolated vertices this produces). * An entire mesh can be deleted by zapping its faces, one at a time, * in any order. Zapped faces cannot be used in further mesh operations! */ zapFace: function( fZap ) { var eStart = fZap.anEdge; var e, eNext, eSym; var fPrev, fNext; /* walk around face, deleting edges whose right face is also NULL */ eNext = eStart.Lnext; do { e = eNext; eNext = e.Lnext; e.Lface = null; if( e.Rface === null ) { /* delete the edge -- see TESSmeshDelete above */ if( e.Onext === e ) { this.killVertex_( e.Org, null ); } else { /* Make sure that e->Org points to a valid half-edge */ e.Org.anEdge = e.Onext; this.splice_( e, e.Oprev ); } eSym = e.Sym; if( eSym.Onext === eSym ) { this.killVertex_( eSym.Org, null ); } else { /* Make sure that eSym->Org points to a valid half-edge */ eSym.Org.anEdge = eSym.Onext; this.splice_( eSym, eSym.Oprev ); } this.killEdge_( e ); } } while( e != eStart ); /* delete from circular doubly-linked list */ fPrev = fZap.prev; fNext = fZap.next; fNext.prev = fPrev; fPrev.next = fNext; }, countFaceVerts_: function(f) { var eCur = f.anEdge; var n = 0; do { n++; eCur = eCur.Lnext; } while (eCur !== f.anEdge); return n; }, //int tessMeshMergeConvexFaces( TESSmesh *mesh, int maxVertsPerFace ) mergeConvexFaces: function(maxVertsPerFace) { var f; var eCur, eNext, eSym; var vStart; var curNv, symNv; for( f = this.fHead.next; f !== this.fHead; f = f.next ) { // Skip faces which are outside the result. if( !f.inside ) continue; eCur = f.anEdge; vStart = eCur.Org; while (true) { eNext = eCur.Lnext; eSym = eCur.Sym; // Try to merge if the neighbour face is valid. if( eSym && eSym.Lface && eSym.Lface.inside ) { // Try to merge the neighbour faces if the resulting polygons // does not exceed maximum number of vertices. curNv = this.countFaceVerts_( f ); symNv = this.countFaceVerts_( eSym.Lface ); if( (curNv+symNv-2) <= maxVertsPerFace ) { // Merge if the resulting poly is convex. if( Geom.vertCCW( eCur.Lprev.Org, eCur.Org, eSym.Lnext.Lnext.Org ) && Geom.vertCCW( eSym.Lprev.Org, eSym.Org, eCur.Lnext.Lnext.Org ) ) { eNext = eSym.Lnext; this.delete( eSym ); eCur = null; eSym = null; } } } if( eCur && eCur.Lnext.Org === vStart ) break; // Continue to next edge. eCur = eNext; } } return true; }, /* tessMeshCheckMesh( mesh ) checks a mesh for self-consistency. */ check: function() { var fHead = this.fHead; var vHead = this.vHead; var eHead = this.eHead; var f, fPrev, v, vPrev, e, ePrev; fPrev = fHead; for( fPrev = fHead ; (f = fPrev.next) !== fHead; fPrev = f) { assert( f.prev === fPrev ); e = f.anEdge; do { assert( e.Sym !== e ); assert( e.Sym.Sym === e ); assert( e.Lnext.Onext.Sym === e ); assert( e.Onext.Sym.Lnext === e ); assert( e.Lface === f ); e = e.Lnext; } while( e !== f.anEdge ); } assert( f.prev === fPrev && f.anEdge === null ); vPrev = vHead; for( vPrev = vHead ; (v = vPrev.next) !== vHead; vPrev = v) { assert( v.prev === vPrev ); e = v.anEdge; do { assert( e.Sym !== e ); assert( e.Sym.Sym === e ); assert( e.Lnext.Onext.Sym === e ); assert( e.Onext.Sym.Lnext === e ); assert( e.Org === v ); e = e.Onext; } while( e !== v.anEdge ); } assert( v.prev === vPrev && v.anEdge === null ); ePrev = eHead; for( ePrev = eHead ; (e = ePrev.next) !== eHead; ePrev = e) { assert( e.Sym.next === ePrev.Sym ); assert( e.Sym !== e ); assert( e.Sym.Sym === e ); assert( e.Org !== null ); assert( e.Dst !== null ); assert( e.Lnext.Onext.Sym === e ); assert( e.Onext.Sym.Lnext === e ); } assert( e.Sym.next === ePrev.Sym && e.Sym === this.eHeadSym && e.Sym.Sym === e && e.Org === null && e.Dst === null && e.Lface === null && e.Rface === null ); } }; var Geom = {}; Geom.vertEq = function(u,v) { return (u.s === v.s && u.t === v.t); }; /* Returns TRUE if u is lexicographically <= v. */ Geom.vertLeq = function(u,v) { return ((u.s < v.s) || (u.s === v.s && u.t <= v.t)); }; /* Versions of VertLeq, EdgeSign, EdgeEval with s and t transposed. */ Geom.transLeq = function(u,v) { return ((u.t < v.t) || (u.t === v.t && u.s <= v.s)); }; Geom.edgeGoesLeft = function(e) { return Geom.vertLeq( e.Dst, e.Org ); }; Geom.edgeGoesRight = function(e) { return Geom.vertLeq( e.Org, e.Dst ); }; Geom.vertL1dist = function(u,v) { return (Math.abs(u.s - v.s) + Math.abs(u.t - v.t)); }; //TESSreal tesedgeEval( TESSvertex *u, TESSvertex *v, TESSvertex *w ) Geom.edgeEval = function( u, v, w ) { /* Given three vertices u,v,w such that VertLeq(u,v) && VertLeq(v,w), * evaluates the t-coord of the edge uw at the s-coord of the vertex v. * Returns v->t - (uw)(v->s), ie. the signed distance from uw to v. * If uw is vertical (and thus passes thru v), the result is zero. * * The calculation is extremely accurate and stable, even when v * is very close to u or w. In particular if we set v->t = 0 and * let r be the negated result (this evaluates (uw)(v->s)), then * r is guaranteed to satisfy MIN(u->t,w->t) <= r <= MAX(u->t,w->t). */ assert( Geom.vertLeq( u, v ) && Geom.vertLeq( v, w )); var gapL = v.s - u.s; var gapR = w.s - v.s; if( gapL + gapR > 0.0 ) { if( gapL < gapR ) { return (v.t - u.t) + (u.t - w.t) * (gapL / (gapL + gapR)); } else { return (v.t - w.t) + (w.t - u.t) * (gapR / (gapL + gapR)); } } /* vertical line */ return 0.0; }; //TESSreal tesedgeSign( TESSvertex *u, TESSvertex *v, TESSvertex *w ) Geom.edgeSign = function( u, v, w ) { /* Returns a number whose sign matches EdgeEval(u,v,w) but which * is cheaper to evaluate. Returns > 0, == 0 , or < 0 * as v is above, on, or below the edge uw. */ assert( Geom.vertLeq( u, v ) && Geom.vertLeq( v, w )); var gapL = v.s - u.s; var gapR = w.s - v.s; if( gapL + gapR > 0.0 ) { return (v.t - w.t) * gapL + (v.t - u.t) * gapR; } /* vertical line */ return 0.0; }; /*********************************************************************** * Define versions of EdgeSign, EdgeEval with s and t transposed. */ //TESSreal testransEval( TESSvertex *u, TESSvertex *v, TESSvertex *w ) Geom.transEval = function( u, v, w ) { /* Given three vertices u,v,w such that TransLeq(u,v) && TransLeq(v,w), * evaluates the t-coord of the edge uw at the s-coord of the vertex v. * Returns v->s - (uw)(v->t), ie. the signed distance from uw to v. * If uw is vertical (and thus passes thru v), the result is zero. * * The calculation is extremely accurate and stable, even when v * is very close to u or w. In particular if we set v->s = 0 and * let r be the negated result (this evaluates (uw)(v->t)), then * r is guaranteed to satisfy MIN(u->s,w->s) <= r <= MAX(u->s,w->s). */ assert( Geom.transLeq( u, v ) && Geom.transLeq( v, w )); var gapL = v.t - u.t; var gapR = w.t - v.t; if( gapL + gapR > 0.0 ) { if( gapL < gapR ) { return (v.s - u.s) + (u.s - w.s) * (gapL / (gapL + gapR)); } else { return (v.s - w.s) + (w.s - u.s) * (gapR / (gapL + gapR)); } } /* vertical line */ return 0.0; }; //TESSreal testransSign( TESSvertex *u, TESSvertex *v, TESSvertex *w ) Geom.transSign = function( u, v, w ) { /* Returns a number whose sign matches TransEval(u,v,w) but which * is cheaper to evaluate. Returns > 0, == 0 , or < 0 * as v is above, on, or below the edge uw. */ assert( Geom.transLeq( u, v ) && Geom.transLeq( v, w )); var gapL = v.t - u.t; var gapR = w.t - v.t; if( gapL + gapR > 0.0 ) { return (v.s - w.s) * gapL + (v.s - u.s) * gapR; } /* vertical line */ return 0.0; }; //int tesvertCCW( TESSvertex *u, TESSvertex *v, TESSvertex *w ) Geom.vertCCW = function( u, v, w ) { /* For almost-degenerate situations, the results are not reliable. * Unless the floating-point arithmetic can be performed without * rounding errors, *any* implementation will give incorrect results * on some degenerate inputs, so the client must have some way to * handle this situation. */ return (u.s*(v.t - w.t) + v.s*(w.t - u.t) + w.s*(u.t - v.t)) >= 0.0; }; /* Given parameters a,x,b,y returns the value (b*x+a*y)/(a+b), * or (x+y)/2 if a==b==0. It requires that a,b >= 0, and enforces * this in the rare case that one argument is slightly negative. * The implementation is extremely stable numerically. * In particular it guarantees that the result r satisfies * MIN(x,y) <= r <= MAX(x,y), and the results are very accurate * even when a and b differ greatly in magnitude. */ Geom.interpolate = function(a,x,b,y) { return (a = (a < 0) ? 0 : a, b = (b < 0) ? 0 : b, ((a <= b) ? ((b == 0) ? ((x+y) / 2) : (x + (y-x) * (a/(a+b)))) : (y + (x-y) * (b/(a+b))))); }; /* #ifndef FOR_TRITE_TEST_PROGRAM #define Interpolate(a,x,b,y) RealInterpolate(a,x,b,y) #else // Claim: the ONLY property the sweep algorithm relies on is that // MIN(x,y) <= r <= MAX(x,y). This is a nasty way to test that. #include <stdlib.h> extern int RandomInterpolate; double Interpolate( double a, double x, double b, double y) { printf("*********************%d\n",RandomInterpolate); if( RandomInterpolate ) { a = 1.2 * drand48() - 0.1; a = (a < 0) ? 0 : ((a > 1) ? 1 : a); b = 1.0 - a; } return RealInterpolate(a,x,b,y); } #endif*/ Geom.intersect = function( o1, d1, o2, d2, v ) { /* Given edges (o1,d1) and (o2,d2), compute their point of intersection. * The computed point is guaranteed to lie in the intersection of the * bounding rectangles defined by each edge. */ var z1, z2; var t; /* This is certainly not the most efficient way to find the intersection * of two line segments, but it is very numerically stable. * * Strategy: find the two middle vertices in the VertLeq ordering, * and interpolate the intersection s-value from these. Then repeat * using the TransLeq ordering to find the intersection t-value. */ if( ! Geom.vertLeq( o1, d1 )) { t = o1; o1 = d1; d1 = t; } //swap( o1, d1 ); } if( ! Geom.vertLeq( o2, d2 )) { t = o2; o2 = d2; d2 = t; } //swap( o2, d2 ); } if( ! Geom.vertLeq( o1, o2 )) { t = o1; o1 = o2; o2 = t; t = d1; d1 = d2; d2 = t; }//swap( o1, o2 ); swap( d1, d2 ); } if( ! Geom.vertLeq( o2, d1 )) { /* Technically, no intersection -- do our best */ v.s = (o2.s + d1.s) / 2; } else if( Geom.vertLeq( d1, d2 )) { /* Interpolate between o2 and d1 */ z1 = Geom.edgeEval( o1, o2, d1 ); z2 = Geom.edgeEval( o2, d1, d2 ); if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } v.s = Geom.interpolate( z1, o2.s, z2, d1.s ); } else { /* Interpolate between o2 and d2 */ z1 = Geom.edgeSign( o1, o2, d1 ); z2 = -Geom.edgeSign( o1, d2, d1 ); if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } v.s = Geom.interpolate( z1, o2.s, z2, d2.s ); } /* Now repeat the process for t */ if( ! Geom.transLeq( o1, d1 )) { t = o1; o1 = d1; d1 = t; } //swap( o1, d1 ); } if( ! Geom.transLeq( o2, d2 )) { t = o2; o2 = d2; d2 = t; } //swap( o2, d2 ); } if( ! Geom.transLeq( o1, o2 )) { t = o1; o1 = o2; o2 = t; t = d1; d1 = d2; d2 = t; } //swap( o1, o2 ); swap( d1, d2 ); } if( ! Geom.transLeq( o2, d1 )) { /* Technically, no intersection -- do our best */ v.t = (o2.t + d1.t) / 2; } else if( Geom.transLeq( d1, d2 )) { /* Interpolate between o2 and d1 */ z1 = Geom.transEval( o1, o2, d1 ); z2 = Geom.transEval( o2, d1, d2 ); if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } v.t = Geom.interpolate( z1, o2.t, z2, d1.t ); } else { /* Interpolate between o2 and d2 */ z1 = Geom.transSign( o1, o2, d1 ); z2 = -Geom.transSign( o1, d2, d1 ); if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } v.t = Geom.interpolate( z1, o2.t, z2, d2.t ); } }; function DictNode() { this.key = null; this.next = null; this.prev = null; }; function Dict(frame, leq) { this.head = new DictNode(); this.head.next = this.head; this.head.prev = this.head; this.frame = frame; this.leq = leq; }; Dict.prototype = { min: function() { return this.head.next; }, max: function() { return this.head.prev; }, insert: function(k) { return this.insertBefore(this.head, k); }, search: function(key) { /* Search returns the node with the smallest key greater than or equal * to the given key. If there is no such key, returns a node whose * key is NULL. Similarly, Succ(Max(d)) has a NULL key, etc. */ var node = this.head; do { node = node.next; } while( node.key !== null && ! this.leq(this.frame, key, node.key)); return node; }, insertBefore: function(node, key) { do { node = node.prev; } while( node.key !== null && ! this.leq(this.frame, node.key, key)); var newNode = new DictNode(); newNode.key = key; newNode.next = node.next; node.next.prev = newNode; newNode.prev = node; node.next = newNode; return newNode; }, delete: function(node) { node.next.prev = node.prev; node.prev.next = node.next; } }; function PQnode() { this.handle = null; } function PQhandleElem() { this.key = null; this.node = null; } function PriorityQ(size, leq) { this.size = 0; this.max = size; this.nodes = []; this.nodes.length = size+1; for (var i = 0; i < this.nodes.length; i++) this.nodes[i] = new PQnode(); this.handles = []; this.handles.length = size+1; for (var i = 0; i < this.handles.length; i++) this.handles[i] = new PQhandleElem(); this.initialized = false; this.freeList = 0; this.leq = leq; this.nodes[1].handle = 1; /* so that Minimum() returns NULL */ this.handles[1].key = null; }; PriorityQ.prototype = { floatDown_: function( curr ) { var n = this.nodes; var h = this.handles; var hCurr, hChild; var child; hCurr = n[curr].handle; for( ;; ) { child = curr << 1; if( child < this.size && this.leq( h[n[child+1].handle].key, h[n[child].handle].key )) { ++child; } assert(child <= this.max); hChild = n[child].handle; if( child > this.size || this.leq( h[hCurr].key, h[hChild].key )) { n[curr].handle = hCurr; h[hCurr].node = curr; break; } n[curr].handle = hChild; h[hChild].node = curr; curr = child; } }, floatUp_: function( curr ) { var n = this.nodes; var h = this.handles; var hCurr, hParent; var parent; hCurr = n[curr].handle; for( ;; ) { parent = curr >> 1; hParent = n[parent].handle; if( parent == 0 || this.leq( h[hParent].key, h[hCurr].key )) { n[curr].handle = hCurr; h[hCurr].node = curr; break; } n[curr].handle = hParent; h[hParent].node = curr; curr = parent; } }, init: function() { /* This method of building a heap is O(n), rather than O(n lg n). */ for( var i = this.size; i >= 1; --i ) { this.floatDown_( i ); } this.initialized = true; }, min: function() { return this.handles[this.nodes[1].handle].key; }, isEmpty: function() { this.size === 0; }, /* really pqHeapInsert */ /* returns INV_HANDLE iff out of memory */ //PQhandle pqHeapInsert( TESSalloc* alloc, PriorityQHeap *pq, PQkey keyNew ) insert: function(keyNew) { var curr; var free; curr = ++this.size; if( (curr*2) > this.max ) { this.max *= 2; var s; s = this.nodes.length; this.nodes.length = this.max+1; for (var i = s; i < this.nodes.length; i++) this.nodes[i] = new PQnode(); s = this.handles.length; this.handles.length = this.max+1; for (var i = s; i < this.handles.length; i++) this.handles[i] = new PQhandleElem(); } if( this.freeList === 0 ) { free = curr; } else { free = this.freeList; this.freeList = this.handles[free].node; } this.nodes[curr].handle = free; this.handles[free].node = curr; this.handles[free].key = keyNew; if( this.initialized ) { this.floatUp_( curr ); } return free; }, //PQkey pqHeapExtractMin( PriorityQHeap *pq ) extractMin: function() { var n = this.nodes; var h = this.handles; var hMin = n[1].handle; var min = h[hMin].key; if( this.size > 0 ) { n[1].handle = n[this.size].handle; h[n[1].handle].node = 1; h[hMin].key = null; h[hMin].node = this.freeList; this.freeList = hMin; --this.size; if( this.size > 0 ) { this.floatDown_( 1 ); } } return min; }, delete: function( hCurr ) { var n = this.nodes; var h = this.handles; var curr; assert( hCurr >= 1 && hCurr <= this.max && h[hCurr].key !== null ); curr = h[hCurr].node; n[curr].handle = n[this.size].handle; h[n[curr].handle].node = curr; --this.size; if( curr <= this.size ) { if( curr <= 1 || this.leq( h[n[curr>>1].handle].key, h[n[curr].handle].key )) { this.floatDown_( curr ); } else { this.floatUp_( curr ); } } h[hCurr].key = null; h[hCurr].node = this.freeList; this.freeList = hCurr; } }; /* For each pair of adjacent edges crossing the sweep line, there is * an ActiveRegion to represent the region between them. The active * regions are kept in sorted order in a dynamic dictionary. As the * sweep line crosses each vertex, we update the affected regions. */ function ActiveRegion() { this.eUp = null; /* upper edge, directed right to left */ this.nodeUp = null; /* dictionary node corresponding to eUp */ this.windingNumber = 0; /* used to determine which regions are * inside the polygon */ this.inside = false; /* is this region inside the polygon? */ this.sentinel = false; /* marks fake edges at t = +/-infinity */ this.dirty = false; /* marks regions where the upper or lower * edge has changed, but we haven't checked * whether they intersect yet */ this.fixUpperEdge = false; /* marks temporary edges introduced when * we process a "right vertex" (one without * any edges leaving to the right) */ }; var Sweep = {}; Sweep.regionBelow = function(r) { return r.nodeUp.prev.key; } Sweep.regionAbove = function(r) { return r.nodeUp.next.key; } Sweep.debugEvent = function( tess ) { // empty } /* * Invariants for the Edge Dictionary. * - each pair of adjacent edges e2=Succ(e1) satisfies EdgeLeq(e1,e2) * at any valid location of the sweep event * - if EdgeLeq(e2,e1) as well (at any valid sweep event), then e1 and e2 * share a common endpoint * - for each e, e->Dst has been processed, but not e->Org * - each edge e satisfies VertLeq(e->Dst,event) && VertLeq(event,e->Org) * where "event" is the current sweep line event. * - no edge e has zero length * * Invariants for the Mesh (the processed portion). * - the portion of the mesh left of the sweep line is a planar graph, * ie. there is *some* way to embed it in the plane * - no processed edge has zero length * - no two processed vertices have identical coordinates * - each "inside" region is monotone, ie. can be broken into two chains * of monotonically increasing vertices according to VertLeq(v1,v2) * - a non-invariant: these chains may intersect (very slightly) * * Invariants for the Sweep. * - if none of the edges incident to the event vertex have an activeRegion * (ie. none of these edges are in the edge dictionary), then the vertex * has only right-going edges. * - if an edge is marked "fixUpperEdge" (it is a temporary edge introduced * by ConnectRightVertex), then it is the only right-going edge from * its associated vertex. (This says that these edges exist only * when it is necessary.) */ /* When we merge two edges into one, we need to compute the combined * winding of the new edge. */ Sweep.addWinding = function(eDst,eSrc) { eDst.winding += eSrc.winding; eDst.Sym.winding += eSrc.Sym.winding; } //static int EdgeLeq( TESStesselator *tess, ActiveRegion *reg1, ActiveRegion *reg2 ) Sweep.edgeLeq = function( tess, reg1, reg2 ) { /* * 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). */ var ev = tess.event; var t1, t2; var e1 = reg1.eUp; var e2 = reg2.eUp; if( e1.Dst === ev ) { if( e2.Dst === ev ) { /* 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; } return Geom.edgeSign( e1.Dst, e2.Org, e1.Org ) >= 0; } return Geom.edgeSign( e2.Dst, ev, e2.Org ) <= 0; } if( e2.Dst === ev ) { return Geom.edgeSign( e1.Dst, ev, e1.Org ) >= 0; } /* General case - compute signed distance *from* e1, e2 to event */ var t1 = Geom.edgeEval( e1.Dst, ev, e1.Org ); var t2 = Geom.edgeEval( e2.Dst, ev, e2.Org ); return (t1 >= t2); } //static void DeleteRegion( TESStesselator *tess, ActiveRegion *reg ) Sweep.deleteRegion = function( tess, 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). */ assert( reg.eUp.winding === 0 ); } reg.eUp.activeRegion = null; tess.dict.delete( reg.nodeUp ); } //static int FixUpperEdge( TESStesselator *tess, ActiveRegion *reg, TESShalfEdge *newEdge ) Sweep.fixUpperEdge = function( tess, reg, newEdge ) { /* * Replace an upper edge which needs fixing (see ConnectRightVertex). */ assert( reg.fixUpperEdge ); tess.mesh.delete( reg.eUp ); reg.fixUpperEdge = false; reg.eUp = newEdge; newEdge.activeRegion = reg; } //static ActiveRegion *TopLeftRegion(