/** * Copyright 2000, Silicon Graphics, Inc. All Rights Reserved. * Copyright 2012, Google 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. * * Original Code. The Original Code is: OpenGL Sample Implementation, * Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, * Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc. * Copyright in any portions created by third parties is as indicated * elsewhere herein. All Rights Reserved. */ /** * @author ericv@cs.stanford.edu (Eric Veach) * @author bckenny@google.com (Brendan Kenny) */ // require libtess // require libtess.GluFace // require libtess.GluHalfEdge // require libtess.GluMesh // require libtess.GluVertex /*global libtess */ goog.provide('libtess.mesh'); goog.require('libtess'); goog.require('libtess.GluFace'); goog.require('libtess.GluHalfEdge'); goog.require('libtess.GluVertex'); // TODO(bckenny): could maybe merge GluMesh and mesh.js since these are // operations on the mesh /****************** Basic Edge Operations **********************/ /** * makeEdge creates one edge, two vertices, and a loop (face). * The loop consists of the two new half-edges. * * @param {libtess.GluMesh} mesh [description]. * @return {libtess.GluHalfEdge} [description]. */ libtess.mesh.makeEdge = function(mesh) { // TODO(bckenny): probably move to GluMesh, but needs Make* methods with it var e = libtess.mesh.makeEdgePair_(mesh.eHead); // complete edge with vertices and face (see mesh.makeEdgePair_) libtess.mesh.makeVertex_(e, mesh.vHead); libtess.mesh.makeVertex_(e.sym, mesh.vHead); libtess.mesh.makeFace_(e, mesh.fHead); return e; }; /** * meshSplice(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. * * @param {libtess.GluHalfEdge} eOrg [description]. * @param {libtess.GluHalfEdge} eDst [description]. */ libtess.mesh.meshSplice = function(eOrg, eDst) { // TODO: more descriptive name? 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; libtess.mesh.killVertex_(eDst.org, eOrg.org); } if (eDst.lFace !== eOrg.lFace) { // We are connecting two disjoint loops -- destroy eDst.lFace joiningLoops = true; libtess.mesh.killFace_(eDst.lFace, eOrg.lFace); } // Change the edge structure libtess.mesh.splice_(eDst, eOrg); if (!joiningVertices) { // We split one vertex into two -- the new vertex is eDst.org. // Make sure the old vertex points to a valid half-edge. libtess.mesh.makeVertex_(eDst, eOrg.org); eOrg.org.anEdge = eOrg; } if (!joiningLoops) { // We split one loop into two -- the new loop is eDst.lFace. // Make sure the old face points to a valid half-edge. libtess.mesh.makeFace_(eDst, eOrg.lFace); eOrg.lFace.anEdge = eOrg; } }; /** * deleteEdge(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 __gl_meshSplice * plus a few calls to memFree, but this would allocate and delete * unnecessary vertices and faces. * * @param {libtess.GluHalfEdge} eDel [description]. */ libtess.mesh.deleteEdge = 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; libtess.mesh.killFace_(eDel.lFace, eDel.rFace()); } if (eDel.oNext === eDel) { libtess.mesh.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; libtess.mesh.splice_(eDel, eDel.oPrev()); if (!joiningLoops) { // We are splitting one loop into two -- create a new loop for eDel. libtess.mesh.makeFace_(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) { libtess.mesh.killVertex_(eDelSym.org, null); libtess.mesh.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; libtess.mesh.splice_(eDelSym, eDelSym.oPrev()); } // Any isolated vertices or faces have already been freed. libtess.mesh.killEdge_(eDel); }; /******************** Other Edge Operations **********************/ /* All these routines can be implemented with the basic edge * operations above. They are provided for convenience and efficiency. */ /** * addEdgeVertex(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. * * @param {libtess.GluHalfEdge} eOrg [description]. * @return {libtess.GluHalfEdge} [description]. */ libtess.mesh.addEdgeVertex = function(eOrg) { // TODO(bckenny): why is it named this? var eNew = libtess.mesh.makeEdgePair_(eOrg); var eNewSym = eNew.sym; // Connect the new edge appropriately libtess.mesh.splice_(eNew, eOrg.lNext); // Set the vertex and face information eNew.org = eOrg.dst(); libtess.mesh.makeVertex_(eNewSym, eNew.org); eNew.lFace = eNewSym.lFace = eOrg.lFace; return eNew; }; /** * splitEdge(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. * * @param {libtess.GluHalfEdge} eOrg [description]. * @return {!libtess.GluHalfEdge} [description]. */ libtess.mesh.splitEdge = function(eOrg) { var tempHalfEdge = libtess.mesh.addEdgeVertex(eOrg); var eNew = tempHalfEdge.sym; // Disconnect eOrg from eOrg.dst() and connect it to eNew.org libtess.mesh.splice_(eOrg.sym, eOrg.sym.oPrev()); libtess.mesh.splice_(eOrg.sym, eNew); // Set the vertex and face information eOrg.sym.org = eNew.org; // NOTE(bckenny): assignment to dst eNew.dst().anEdge = eNew.sym; // may have pointed to eOrg.sym eNew.sym.lFace = eOrg.rFace(); // NOTE(bckenny): assignment to rFace eNew.winding = eOrg.winding; // copy old winding information eNew.sym.winding = eOrg.sym.winding; return eNew; }; /** * connect(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. * * @param {libtess.GluHalfEdge} eOrg [description]. * @param {libtess.GluHalfEdge} eDst [description]. * @return {!libtess.GluHalfEdge} [description]. */ libtess.mesh.connect = function(eOrg, eDst) { var joiningLoops = false; var eNew = libtess.mesh.makeEdgePair_(eOrg); var eNewSym = eNew.sym; if (eDst.lFace !== eOrg.lFace) { // We are connecting two disjoint loops -- destroy eDst.lFace joiningLoops = true; libtess.mesh.killFace_(eDst.lFace, eOrg.lFace); } // Connect the new edge appropriately libtess.mesh.splice_(eNew, eOrg.lNext); libtess.mesh.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) { // We split one loop into two -- the new loop is eNew.lFace libtess.mesh.makeFace_(eNew, eOrg.lFace); } return eNew; }; /******************** Other Operations **********************/ /** * zapFace(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! * * @param {libtess.GluFace} fZap [description]. */ libtess.mesh.zapFace = function(fZap) { var eStart = fZap.anEdge; // walk around face, deleting edges whose right face is also NULL var eNext = eStart.lNext; var e; do { e = eNext; eNext = e.lNext; e.lFace = null; if (e.rFace() === null) { // delete the edge -- see mesh.deleteEdge above if (e.oNext === e) { libtess.mesh.killVertex_(e.org, null); } else { // Make sure that e.org points to a valid half-edge e.org.anEdge = e.oNext; libtess.mesh.splice_(e, e.oPrev()); } var eSym = e.sym; if (eSym.oNext === eSym) { libtess.mesh.killVertex_(eSym.org, null); } else { // Make sure that eSym.org points to a valid half-edge eSym.org.anEdge = eSym.oNext; libtess.mesh.splice_(eSym, eSym.oPrev()); } libtess.mesh.killEdge_(e); } } while (e !== eStart); // delete from circular doubly-linked list var fPrev = fZap.prev; var fNext = fZap.next; fNext.prev = fPrev; fPrev.next = fNext; // TODO(bckenny): memFree( fZap ); // TODO(bckenny): probably null at callsite }; /** * meshUnion() forms the union of all structures in * both meshes, and returns the new mesh (the old meshes are destroyed). * * @param {libtess.GluMesh} mesh1 [description]. * @param {libtess.GluMesh} mesh2 [description]. * @return {libtess.GluMesh} [description]. */ libtess.mesh.meshUnion = function(mesh1, mesh2) { // TODO(bceknny): probably move to GluMesh method var f1 = mesh1.fHead; var v1 = mesh1.vHead; var e1 = mesh1.eHead; var f2 = mesh2.fHead; var v2 = mesh2.vHead; var e2 = mesh2.eHead; // Add the faces, vertices, and edges of mesh2 to those of mesh1 if (f2.next !== f2) { f1.prev.next = f2.next; f2.next.prev = f1.prev; f2.prev.next = f1; f1.prev = f2.prev; } if (v2.next !== v2) { v1.prev.next = v2.next; v2.next.prev = v1.prev; v2.prev.next = v1; v1.prev = v2.prev; } if (e2.next !== e2) { e1.sym.next.sym.next = e2.next; e2.next.sym.next = e1.sym.next; e2.sym.next.sym.next = e1; e1.sym.next = e2.sym.next; } // TODO(bckenny): memFree(mesh2); // TODO(bckenny): probably null at callsite return mesh1; }; /** * deleteMesh(mesh) will free all storage for any valid mesh. * @param {libtess.GluMesh} mesh [description]. */ libtess.mesh.deleteMesh = function(mesh) { // TODO(bckenny): unnecessary, I think. // TODO(bckenny): might want to explicitly null at callsite // lots of memFrees. see also DELETE_BY_ZAPPING }; /************************ Utility Routines ************************/ /** * 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. * * TODO(bckenny): warning about eNext strictly being first of pair? (see code) * * @private * @param {libtess.GluHalfEdge} eNext [description]. * @return {libtess.GluHalfEdge} [description]. */ libtess.mesh.makeEdgePair_ = function(eNext) { var e = new libtess.GluHalfEdge(); var eSym = new libtess.GluHalfEdge(); // TODO(bckenny): how do we ensure this? see above comment in jsdoc // Make sure eNext points to the first edge of the edge pair // if (eNext->Sym < eNext ) { eNext = eNext->Sym; } // NOTE(bckenny): check this for bugs in current implementation! // 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; eSym.sym = e; eSym.oNext = eSym; eSym.lNext = e; return e; }; /** * splice_ is best described by the Guibas/Stolfi paper or the * CS348a notes. 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 mesh.meshSplice below. * * @private * @param {libtess.GluHalfEdge} a [description]. * @param {libtess.GluHalfEdge} b [description]. */ libtess.mesh.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_(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. * * NOTE: unlike original, acutally allocates new vertex. * * @private * @param {libtess.GluHalfEdge} eOrig [description]. * @param {libtess.GluVertex} vNext [description]. */ libtess.mesh.makeVertex_ = function(eOrig, vNext) { // insert in circular doubly-linked list before vNext var vPrev = vNext.prev; var vNew = new libtess.GluVertex(vNext, vPrev); vPrev.next = vNew; vNext.prev = vNew; vNew.anEdge = eOrig; // leave coords, s, t undefined // TODO(bckenny): does above line mean 0 specifically, or does it matter? // fix other edges on this vertex loop var e = eOrig; do { e.org = vNew; e = e.oNext; } while (e !== eOrig); }; /** * makeFace_(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. * * NOTE: unlike original, acutally allocates new face. * * @private * @param {libtess.GluHalfEdge} eOrig [description]. * @param {libtess.GluFace} fNext [description]. */ libtess.mesh.makeFace_ = function(eOrig, fNext) { // insert in circular doubly-linked list before fNext var fPrev = fNext.prev; var fNew = new libtess.GluFace(fNext, fPrev); fPrev.next = fNew; fNext.prev = fNew; fNew.anEdge = eOrig; // 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_ destroys an edge (the half-edges eDel and eDel.sym), * and removes from the global edge list. * * @private * @param {libtess.GluHalfEdge} eDel [description]. */ libtess.mesh.killEdge_ = function(eDel) { // TODO(bckenny): in this case, no need to worry(?), but check when checking mesh.makeEdgePair_ // Half-edges are allocated in pairs, see EdgePair above // if (eDel->Sym < eDel ) { 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; // TODO(bckenny): memFree( eDel ); (which also frees eDel.sym) // TODO(bckenny): need to null at callsites? }; /** * killVertex_ destroys a vertex and removes it from the global * vertex list. It updates the vertex loop to point to a given new vertex. * * @private * @param {libtess.GluVertex} vDel [description]. * @param {libtess.GluVertex} newOrg [description]. */ libtess.mesh.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; // TODO(bckenny): memFree( vDel ); // TODO(bckenny): need to null at callsites? }; /** * killFace_ destroys a face and removes it from the global face * list. It updates the face loop to point to a given new face. * * @private * @param {libtess.GluFace} fDel [description]. * @param {libtess.GluFace} newLFace [description]. */ libtess.mesh.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; // TODO(bckenny): memFree( fDel ); // TODO(bckenny): need to null at callsites? };