Files
openlayers/src/libtess.js/mesh.js
2013-10-01 15:14:12 +02:00

637 lines
19 KiB
JavaScript

/**
* 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?
};