876 lines
24 KiB
JavaScript
876 lines
24 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 Eric Veach, July 1994
|
|
* @author Brendan Kenny
|
|
*/
|
|
|
|
// require libtess
|
|
// require libtess.mesh
|
|
// require libtess.tessmono
|
|
// require libtess.render
|
|
// require libtess.normal
|
|
// require libtess.sweep
|
|
/*global libtess */
|
|
|
|
// TODO(bckenny): options for just triangles, just tristrips, single tristrip w/ resets
|
|
// other primitives with index buffer? would have to add a better tristrip extractor
|
|
// monotone poly -> tristrip seems possible...
|
|
|
|
// TODO(bckenny): create more javascript-y API, e.g. make gluTessEndPolygon async,
|
|
// don't require so many temp objects created
|
|
|
|
/**
|
|
* [GluTesselator description]
|
|
*
|
|
* @constructor
|
|
*/
|
|
libtess.GluTesselator = function() {
|
|
// Only initialize fields which can be changed by the api. Other fields
|
|
// are initialized where they are used.
|
|
|
|
// TODO(bckenny): many of these can be made private
|
|
// TODO(bckenny): can we combine call* and call*Data functions?
|
|
|
|
/*** state needed for collecting the input data ***/
|
|
|
|
/**
|
|
* what begin/end calls have we seen?
|
|
* @type {libtess.tessState}
|
|
*/
|
|
this.state = libtess.tessState.T_DORMANT;
|
|
|
|
/**
|
|
* lastEdge_.org is the most recent vertex
|
|
* @private
|
|
* @type {libtess.GluHalfEdge}
|
|
*/
|
|
this.lastEdge_ = null;
|
|
|
|
/**
|
|
* stores the input contours, and eventually the tessellation itself
|
|
* @type {libtess.GluMesh}
|
|
*/
|
|
this.mesh = null;
|
|
// NOTE(bckenny): initialized in this.emptyCache_
|
|
|
|
/**
|
|
* Error callback.
|
|
* @private
|
|
* @type {?function((libtess.errorType|libtess.gluEnum))}
|
|
*/
|
|
this.callError_ = null;
|
|
|
|
|
|
/*** state needed for projecting onto the sweep plane ***/
|
|
|
|
/**
|
|
* user-specified normal (if provided)
|
|
* @type {Array.<number>}
|
|
*/
|
|
this.normal = [0, 0, 0];
|
|
// TODO(bckenny): better way to init these arrays?
|
|
|
|
/**
|
|
* unit vector in s-direction (debugging)
|
|
* @type {Array.<number>}
|
|
*/
|
|
this.sUnit = [0, 0, 0];
|
|
|
|
/**
|
|
* unit vector in t-direction (debugging)
|
|
* @type {Array.<number>}
|
|
*/
|
|
this.tUnit = [0, 0, 0];
|
|
|
|
/*** state needed for the line sweep ***/
|
|
// TODO(bckenny): this could be moved to a sweep state object of some sort
|
|
|
|
/**
|
|
* tolerance for merging features
|
|
* @type {number}
|
|
*/
|
|
this.relTolerance = libtess.GLU_TESS_DEFAULT_TOLERANCE;
|
|
|
|
/**
|
|
* rule for determining polygon interior
|
|
* @type {libtess.windingRule}
|
|
*/
|
|
this.windingRule = libtess.windingRule.GLU_TESS_WINDING_ODD;
|
|
|
|
/**
|
|
* fatal error: needed combine callback
|
|
* @type {boolean}
|
|
*/
|
|
this.fatalError = false;
|
|
|
|
/**
|
|
* edge dictionary for sweep line
|
|
* @type {libtess.Dict}
|
|
*/
|
|
this.dict = null;
|
|
// NOTE(bckenny): dict initialized in sweep.initEdgeDict_, removed in sweep.doneEdgeDict_
|
|
|
|
/**
|
|
* priority queue of vertex events
|
|
* @type {libtess.PriorityQ}
|
|
*/
|
|
this.pq = null;
|
|
// NOTE(bckenny): pq initialized in sweep.initPriorityQ
|
|
|
|
/**
|
|
* current sweep event being processed
|
|
* @type {libtess.GluVertex}
|
|
*/
|
|
this.event = null;
|
|
|
|
/**
|
|
* Combine callback.
|
|
* @private
|
|
* @type {?function(Array.<number>, Array.<Object>, Array.<number>): Object}
|
|
*/
|
|
this.callCombine_ = null;
|
|
|
|
/*** state needed for rendering callbacks (see render.js) ***/
|
|
|
|
/**
|
|
* mark boundary edges (use EdgeFlag)
|
|
* @type {boolean}
|
|
*/
|
|
this.flagBoundary = false;
|
|
|
|
/**
|
|
* Extract contours, not triangles
|
|
* @type {boolean}
|
|
*/
|
|
this.boundaryOnly = false;
|
|
|
|
/**
|
|
* list of triangles which could not be rendered as strips or fans
|
|
* @type {libtess.GluFace}
|
|
*/
|
|
this.lonelyTriList = null;
|
|
|
|
/**
|
|
* Begin callback.
|
|
* @private
|
|
* @type {?function(libtess.primitiveType)}
|
|
*/
|
|
this.callBegin_ = null;
|
|
|
|
/**
|
|
* Edge flag callback.
|
|
* @private
|
|
* @type {?function(boolean)}
|
|
*/
|
|
this.callEdgeFlag_ = null;
|
|
|
|
/**
|
|
* Vertex callback.
|
|
* @private
|
|
* @type {?function(Object)}
|
|
*/
|
|
this.callVertex_ = null;
|
|
|
|
/**
|
|
* End callback.
|
|
* @private
|
|
* @type {?function()}
|
|
*/
|
|
this.callEnd_ = null;
|
|
|
|
/**
|
|
* Mesh callback.
|
|
* @private
|
|
* @type {?function(libtess.GluMesh)}
|
|
*/
|
|
this.callMesh_ = null;
|
|
|
|
/*** rendering callbacks that also pass polygon data ***/
|
|
/**
|
|
* BeginData callback.
|
|
* @private
|
|
* @type {?function(libtess.primitiveType, Object)}
|
|
*/
|
|
this.callBeginData_ = null;
|
|
|
|
/**
|
|
* EdgeFlagData callback.
|
|
* @private
|
|
* @type {?function(boolean, Object)}
|
|
*/
|
|
this.callEdgeFlagData_ = null;
|
|
|
|
/**
|
|
* VertexData callback.
|
|
* @private
|
|
* @type {?function(Object, Object)}
|
|
*/
|
|
this.callVertexData_ = null;
|
|
|
|
/**
|
|
* EndData callback.
|
|
* @private
|
|
* @type {?function(Object)}
|
|
*/
|
|
this.callEndData_ = null;
|
|
|
|
/**
|
|
* ErrorData callback.
|
|
* @private
|
|
* @type {?function((libtess.errorType|libtess.gluEnum), Object)}
|
|
*/
|
|
this.callErrorData_ = null;
|
|
|
|
/**
|
|
* CombineData callback.
|
|
* @private
|
|
* @type {?function(Array.<number>, Array.<Object>, Array.<number>, Object): Object}
|
|
*/
|
|
this.callCombineData_ = null;
|
|
|
|
/**
|
|
* client data for current polygon
|
|
* @private
|
|
* @type {Object}
|
|
*/
|
|
this.polygonData_ = null;
|
|
|
|
/*** state needed to cache single-contour polygons for renderCache() ***/
|
|
/**
|
|
* empty cache on next vertex() call
|
|
* @type {boolean}
|
|
*/
|
|
this.emptyCache = false;
|
|
// TODO(bckenny): possibly rename to be clear it's a boolean
|
|
|
|
/**
|
|
* number of cached vertices
|
|
* @type {number}
|
|
*/
|
|
this.cacheCount = 0;
|
|
|
|
/**
|
|
* the vertex data
|
|
* @type {Array.<libtess.CachedVertex>}
|
|
*/
|
|
this.cache = new Array(libtess.TESS_MAX_CACHE);
|
|
|
|
// TODO(bckenny): fill now? or init on demand
|
|
for (var i = 0; i < libtess.TESS_MAX_CACHE; i++) {
|
|
this.cache[i] = new libtess.CachedVertex();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Destory the tesselator object. See README.
|
|
*/
|
|
libtess.GluTesselator.prototype.gluDeleteTess = function() {
|
|
// TODO(bckenny): do we need a public API for this still?
|
|
this.requireState_(libtess.tessState.T_DORMANT);
|
|
// memFree(tess); TODO(bckenny)
|
|
};
|
|
|
|
/**
|
|
* Set properties for control over tesselation. See README.
|
|
* @param {libtess.gluEnum} which [description]
|
|
* @param {number|boolean} value [description]
|
|
*/
|
|
libtess.GluTesselator.prototype.gluTessProperty = function(which, value) {
|
|
// TODO(bckenny): split into more setters?
|
|
// TODO(bckenny): in any case, we can do better than this switch statement
|
|
|
|
switch (which) {
|
|
case libtess.gluEnum.GLU_TESS_TOLERANCE:
|
|
if (value < 0 || value > 1) {
|
|
break;
|
|
}
|
|
this.relTolerance = /** @type {number} */(value);
|
|
return;
|
|
|
|
case libtess.gluEnum.GLU_TESS_WINDING_RULE:
|
|
var windingRule = /** @type {libtess.windingRule} */(value);
|
|
|
|
switch (windingRule) {
|
|
case libtess.windingRule.GLU_TESS_WINDING_ODD:
|
|
case libtess.windingRule.GLU_TESS_WINDING_NONZERO:
|
|
case libtess.windingRule.GLU_TESS_WINDING_POSITIVE:
|
|
case libtess.windingRule.GLU_TESS_WINDING_NEGATIVE:
|
|
case libtess.windingRule.GLU_TESS_WINDING_ABS_GEQ_TWO:
|
|
this.windingRule = windingRule;
|
|
return;
|
|
default:
|
|
}
|
|
break;
|
|
|
|
case libtess.gluEnum.GLU_TESS_BOUNDARY_ONLY:
|
|
// TODO(bckenny): added boolean param type. make sure ok.
|
|
this.boundaryOnly = !!value;
|
|
return;
|
|
|
|
default:
|
|
this.callErrorOrErrorData(libtess.gluEnum.GLU_INVALID_ENUM);
|
|
return;
|
|
}
|
|
this.callErrorOrErrorData(libtess.gluEnum.GLU_INVALID_VALUE);
|
|
};
|
|
|
|
/**
|
|
* Returns tessellator property
|
|
* @param {libtess.gluEnum} which [description]
|
|
* @return {number|boolean} [description]
|
|
*/
|
|
libtess.GluTesselator.prototype.gluGetTessProperty = function(which) {
|
|
// TODO(bckenny): as above, split into more getters? and improve on switch statement
|
|
// why are these being asserted in getter but not setter?
|
|
|
|
switch (which) {
|
|
case libtess.gluEnum.GLU_TESS_TOLERANCE:
|
|
// tolerance should be in range [0..1]
|
|
libtess.assert(0 <= this.relTolerance && this.relTolerance <= 1);
|
|
return this.relTolerance;
|
|
|
|
case libtess.gluEnum.GLU_TESS_WINDING_RULE:
|
|
var rule = this.windingRule;
|
|
libtess.assert(rule === libtess.windingRule.GLU_TESS_WINDING_ODD ||
|
|
rule === libtess.windingRule.GLU_TESS_WINDING_NONZERO ||
|
|
rule === libtess.windingRule.GLU_TESS_WINDING_POSITIVE ||
|
|
rule === libtess.windingRule.GLU_TESS_WINDING_NEGATIVE ||
|
|
rule === libtess.windingRule.GLU_TESS_WINDING_ABS_GEQ_TWO);
|
|
return rule;
|
|
|
|
case libtess.gluEnum.GLU_TESS_BOUNDARY_ONLY:
|
|
libtess.assert(this.boundaryOnly === true || this.boundaryOnly === false);
|
|
return this.boundaryOnly;
|
|
|
|
default:
|
|
this.callErrorOrErrorData(libtess.gluEnum.GLU_INVALID_ENUM);
|
|
break;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Lets the user supply the polygon normal, if known. All input data
|
|
* is projected into a plane perpendicular to the normal before
|
|
* tesselation. All output triangles are oriented CCW with
|
|
* respect to the normal (CW orientation can be obtained by
|
|
* reversing the sign of the supplied normal). For example, if
|
|
* you know that all polygons lie in the x-y plane, call
|
|
* "tess.gluTessNormal(0.0, 0.0, 1.0)" before rendering any polygons.
|
|
*
|
|
* @param {number} x [description]
|
|
* @param {number} y [description]
|
|
* @param {number} z [description]
|
|
*/
|
|
libtess.GluTesselator.prototype.gluTessNormal = function(x, y, z) {
|
|
this.normal[0] = x;
|
|
this.normal[1] = y;
|
|
this.normal[2] = z;
|
|
};
|
|
|
|
/**
|
|
* Specify callbacks. See README. A null or undefined opt_fn removes current callback.
|
|
*
|
|
* @param {libtess.gluEnum} which [description]
|
|
* @param {?function()=} opt_fn [description]
|
|
*/
|
|
libtess.GluTesselator.prototype.gluTessCallback = function(which, opt_fn) {
|
|
var fn = !opt_fn ? null : opt_fn;
|
|
// TODO(bckenny): better opt_fn typing?
|
|
|
|
switch(which) {
|
|
case libtess.gluEnum.GLU_TESS_BEGIN:
|
|
this.callBegin_ = /** @type {function(libtess.primitiveType)} */ (fn);
|
|
return;
|
|
|
|
case libtess.gluEnum.GLU_TESS_BEGIN_DATA:
|
|
this.callBeginData_ =
|
|
/** @type {function(libtess.primitiveType, Object)} */ (fn);
|
|
return;
|
|
|
|
case libtess.gluEnum.GLU_TESS_EDGE_FLAG:
|
|
this.callEdgeFlag_ = /** @type {function(boolean)} */ (fn);
|
|
// If the client wants boundary edges to be flagged,
|
|
// we render everything as separate triangles (no strips or fans).
|
|
this.flagBoundary = (!!fn);
|
|
return;
|
|
|
|
case libtess.gluEnum.GLU_TESS_EDGE_FLAG_DATA:
|
|
this.callEdgeFlagData_ = /** @type {function(boolean, Object)} */ (fn);
|
|
// If the client wants boundary edges to be flagged,
|
|
// we render everything as separate triangles (no strips or fans).
|
|
this.flagBoundary = (!!fn);
|
|
return;
|
|
|
|
case libtess.gluEnum.GLU_TESS_VERTEX:
|
|
this.callVertex_ = /** @type {function(Object)} */ (fn);
|
|
return;
|
|
|
|
case libtess.gluEnum.GLU_TESS_VERTEX_DATA:
|
|
this.callVertexData_ = /** @type {function(Object, Object)} */ (fn);
|
|
return;
|
|
|
|
case libtess.gluEnum.GLU_TESS_END:
|
|
this.callEnd_ = /** @type {function()} */ (fn);
|
|
return;
|
|
|
|
case libtess.gluEnum.GLU_TESS_END_DATA:
|
|
this.callEndData_ = /** @type {function(Object)} */ (fn);
|
|
return;
|
|
|
|
case libtess.gluEnum.GLU_TESS_ERROR:
|
|
this.callError_ = /** @type {function((libtess.errorType|libtess.gluEnum))} */ (fn);
|
|
return;
|
|
|
|
case libtess.gluEnum.GLU_TESS_ERROR_DATA:
|
|
this.callErrorData_ =
|
|
/** @type {function((libtess.errorType|libtess.gluEnum), Object)} */ (fn);
|
|
return;
|
|
|
|
case libtess.gluEnum.GLU_TESS_COMBINE:
|
|
this.callCombine_ = /** @type {function(Array.<number>, Array.<Object>, Array.<number>): Object} */ (fn);
|
|
return;
|
|
|
|
case libtess.gluEnum.GLU_TESS_COMBINE_DATA:
|
|
this.callCombineData_ = /** @type {function(Array.<number>, Array.<Object>, Array.<number>, Object): Object} */ (fn);
|
|
return;
|
|
|
|
case libtess.gluEnum.GLU_TESS_MESH:
|
|
this.callMesh_ = /** @type {function(libtess.GluMesh)} */ (fn);
|
|
return;
|
|
|
|
default:
|
|
this.callErrorOrErrorData(libtess.gluEnum.GLU_INVALID_ENUM);
|
|
return;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Specify a vertex and associated data. Must be within calls to
|
|
* beginContour/endContour. See README.
|
|
*
|
|
* @param {Array.<number>} coords [description]
|
|
* @param {Object} data [description]
|
|
*/
|
|
libtess.GluTesselator.prototype.gluTessVertex = function(coords, data) {
|
|
var tooLarge = false;
|
|
|
|
// TODO(bckenny): pool allocation?
|
|
var clamped = [0, 0, 0];
|
|
|
|
this.requireState_(libtess.tessState.T_IN_CONTOUR);
|
|
|
|
if (this.emptyCache) {
|
|
this.emptyCache_();
|
|
this.lastEdge_ = null;
|
|
}
|
|
|
|
for (var i = 0; i < 3; ++i) {
|
|
var x = coords[i];
|
|
if (x < -libtess.GLU_TESS_MAX_COORD) {
|
|
x = -libtess.GLU_TESS_MAX_COORD;
|
|
tooLarge = true;
|
|
}
|
|
if (x > libtess.GLU_TESS_MAX_COORD) {
|
|
x = libtess.GLU_TESS_MAX_COORD;
|
|
tooLarge = true;
|
|
}
|
|
clamped[i] = x;
|
|
}
|
|
|
|
if (tooLarge) {
|
|
this.callErrorOrErrorData(libtess.errorType.GLU_TESS_COORD_TOO_LARGE);
|
|
}
|
|
|
|
if (this.mesh === null) {
|
|
if (this.cacheCount < libtess.TESS_MAX_CACHE) {
|
|
this.cacheVertex_(clamped, data);
|
|
return;
|
|
}
|
|
|
|
// cache is full, create mesh and add cached verts to it
|
|
this.emptyCache_();
|
|
}
|
|
|
|
this.addVertex_(clamped, data);
|
|
};
|
|
|
|
/**
|
|
* [gluTessBeginPolygon description]
|
|
* @param {Object} data Client data for current polygon
|
|
*/
|
|
libtess.GluTesselator.prototype.gluTessBeginPolygon = function(data) {
|
|
this.requireState_(libtess.tessState.T_DORMANT);
|
|
|
|
this.state = libtess.tessState.T_IN_POLYGON;
|
|
this.cacheCount = 0;
|
|
this.emptyCache = false;
|
|
this.mesh = null;
|
|
|
|
this.polygonData_ = data;
|
|
};
|
|
|
|
/**
|
|
* [gluTessBeginContour description]
|
|
*/
|
|
libtess.GluTesselator.prototype.gluTessBeginContour = function() {
|
|
this.requireState_(libtess.tessState.T_IN_POLYGON);
|
|
|
|
this.state = libtess.tessState.T_IN_CONTOUR;
|
|
this.lastEdge_ = null;
|
|
if (this.cacheCount > 0) {
|
|
// Just set a flag so we don't get confused by empty contours
|
|
// -- these can be generated accidentally with the obsolete
|
|
// NextContour() interface.
|
|
// TODO(bckenny): we aren't implementing NextContour() interface.
|
|
this.emptyCache = true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* [gluTessEndContour description]
|
|
*/
|
|
libtess.GluTesselator.prototype.gluTessEndContour = function() {
|
|
this.requireState_(libtess.tessState.T_IN_CONTOUR);
|
|
this.state = libtess.tessState.T_IN_POLYGON;
|
|
};
|
|
|
|
/**
|
|
* [gluTessEndPolygon description]
|
|
*/
|
|
libtess.GluTesselator.prototype.gluTessEndPolygon = function() {
|
|
this.requireState_(libtess.tessState.T_IN_POLYGON);
|
|
this.state = libtess.tessState.T_DORMANT;
|
|
|
|
if (this.mesh === null) {
|
|
if (!this.flagBoundary && !this.callMesh_) {
|
|
// Try some special code to make the easy cases go quickly
|
|
// (eg. convex polygons). This code does NOT handle multiple contours,
|
|
// intersections, edge flags, and of course it does not generate
|
|
// an explicit mesh either.
|
|
if (libtess.render.renderCache(this)) {
|
|
// TODO(bckenny): why only clear polygonData? does more need to be cleared?
|
|
this.polygonData_ = null;
|
|
return;
|
|
}
|
|
}
|
|
this.emptyCache_();
|
|
}
|
|
|
|
// Determine the polygon normal and project vertices onto the plane
|
|
// of the polygon.
|
|
libtess.normal.projectPolygon(this);
|
|
|
|
// computeInterior(tess) 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 this.windingRule.
|
|
// Each interior region is guaranteed be monotone.
|
|
libtess.sweep.computeInterior(this);
|
|
|
|
if (!this.fatalError) {
|
|
// If the user wants only the boundary contours, we throw away all edges
|
|
// except those which separate the interior from the exterior.
|
|
// Otherwise we tessellate all the regions marked "inside".
|
|
if (this.boundaryOnly) {
|
|
libtess.tessmono.setWindingNumber(this.mesh, 1, true);
|
|
|
|
} else {
|
|
libtess.tessmono.tessellateInterior(this.mesh);
|
|
}
|
|
|
|
this.mesh.checkMesh();
|
|
|
|
if (this.callBegin_ || this.callEnd_ || this.callVertex_ ||
|
|
this.callEdgeFlag_ || this.callBeginData_ || this.callEndData_ ||
|
|
this.callVertexData_ || this.callEdgeFlagData_) {
|
|
|
|
if (this.boundaryOnly) {
|
|
// output boundary contours
|
|
libtess.render.renderBoundary(this, this.mesh);
|
|
|
|
} else {
|
|
// output strips and fans
|
|
libtess.render.renderMesh(this, this.mesh);
|
|
}
|
|
}
|
|
|
|
if (this.callMesh_) {
|
|
// Throw away the exterior faces, so that all faces are interior.
|
|
// This way the user doesn't have to check the "inside" flag,
|
|
// and we don't need to even reveal its existence. It also leaves
|
|
// the freedom for an implementation to not generate the exterior
|
|
// faces in the first place.
|
|
libtess.tessmono.discardExterior(this.mesh);
|
|
// user wants the mesh itself
|
|
this.callMesh_(this.mesh);
|
|
|
|
this.mesh = null;
|
|
this.polygonData_ = null;
|
|
return;
|
|
}
|
|
}
|
|
|
|
libtess.mesh.deleteMesh(this.mesh);
|
|
this.polygonData_ = null;
|
|
this.mesh = null;
|
|
};
|
|
|
|
/**
|
|
* Return the tessellator to its original dormant state.
|
|
* @private
|
|
*/
|
|
libtess.GluTesselator.prototype.makeDormant_ = function() {
|
|
if (this.mesh) {
|
|
libtess.mesh.deleteMesh(this.mesh);
|
|
}
|
|
this.state = libtess.tessState.T_DORMANT;
|
|
this.lastEdge_ = null;
|
|
this.mesh = null;
|
|
};
|
|
|
|
/**
|
|
* [requireState_ description]
|
|
* @private
|
|
* @param {libtess.tessState} state [description]
|
|
*/
|
|
libtess.GluTesselator.prototype.requireState_ = function(state) {
|
|
if (this.state !== state) {
|
|
this.gotoState_(state);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* [gotoState_ description]
|
|
* @private
|
|
* @param {libtess.tessState} newState [description]
|
|
*/
|
|
libtess.GluTesselator.prototype.gotoState_ = function(newState) {
|
|
while (this.state !== newState) {
|
|
// We change the current state one level at a time, to get to the desired
|
|
// state.
|
|
if (this.state < newState) {
|
|
switch (this.state) {
|
|
case libtess.tessState.T_DORMANT:
|
|
this.callErrorOrErrorData(
|
|
libtess.errorType.GLU_TESS_MISSING_BEGIN_POLYGON);
|
|
this.gluTessBeginPolygon(null);
|
|
break;
|
|
|
|
case libtess.tessState.T_IN_POLYGON:
|
|
this.callErrorOrErrorData(
|
|
libtess.errorType.GLU_TESS_MISSING_BEGIN_CONTOUR);
|
|
this.gluTessBeginContour();
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
switch (this.state) {
|
|
case libtess.tessState.T_IN_CONTOUR:
|
|
this.callErrorOrErrorData(
|
|
libtess.errorType.GLU_TESS_MISSING_END_CONTOUR);
|
|
this.gluTessEndContour();
|
|
break;
|
|
|
|
case libtess.tessState.T_IN_POLYGON:
|
|
this.callErrorOrErrorData(
|
|
libtess.errorType.GLU_TESS_MISSING_END_POLYGON);
|
|
// this.gluTessEndPolygon() is too much work!
|
|
this.makeDormant_();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* [addVertex_ description]
|
|
* @private
|
|
* @param {Array.<number>} coords [description]
|
|
* @param {Object} data [description]
|
|
*/
|
|
libtess.GluTesselator.prototype.addVertex_ = function(coords, data) {
|
|
var e = this.lastEdge_;
|
|
if (e === null) {
|
|
// Make a self-loop (one vertex, one edge).
|
|
e = libtess.mesh.makeEdge(this.mesh);
|
|
libtess.mesh.meshSplice(e, e.sym);
|
|
|
|
} else {
|
|
// Create a new vertex and edge which immediately follow e
|
|
// in the ordering around the left face.
|
|
libtess.mesh.splitEdge(e);
|
|
e = e.lNext;
|
|
}
|
|
|
|
// The new vertex is now e.org.
|
|
e.org.data = data;
|
|
e.org.coords[0] = coords[0];
|
|
e.org.coords[1] = coords[1];
|
|
e.org.coords[2] = coords[2];
|
|
|
|
// The winding of an edge says how the winding number changes as we
|
|
// cross from the edge''s right face to its left face. We add the
|
|
// vertices in such an order that a CCW contour will add +1 to
|
|
// the winding number of the region inside the contour.
|
|
e.winding = 1;
|
|
e.sym.winding = -1;
|
|
|
|
this.lastEdge_ = e;
|
|
};
|
|
|
|
/**
|
|
* [cacheVertex_ description]
|
|
* @private
|
|
* @param {Array.<number>} coords [description]
|
|
* @param {Object} data [description]
|
|
*/
|
|
libtess.GluTesselator.prototype.cacheVertex_ = function(coords, data) {
|
|
var v = this.cache[this.cacheCount];
|
|
|
|
v.data = data;
|
|
v.coords[0] = coords[0];
|
|
v.coords[1] = coords[1];
|
|
v.coords[2] = coords[2];
|
|
++this.cacheCount;
|
|
};
|
|
|
|
/**
|
|
* [emptyCache_ description]
|
|
* @private
|
|
*/
|
|
libtess.GluTesselator.prototype.emptyCache_ = function() {
|
|
// NOTE(bckenny): surprise!
|
|
this.mesh = new libtess.GluMesh();
|
|
|
|
for (var i = 0; i < this.cacheCount; i++) {
|
|
var v = this.cache[i];
|
|
this.addVertex_(v.coords, v.data);
|
|
}
|
|
|
|
this.cacheCount = 0;
|
|
this.emptyCache = false;
|
|
};
|
|
|
|
// TODO(bckenny): all following conditional callbacks could be simplified
|
|
// TODO(bckenny): using null for now, but may rework
|
|
// TODO(bckenny): should add documentation that references in callback are volatile (or make a copy)
|
|
// see README callback descriptions
|
|
/**
|
|
* [callBeginOrBeginData description]
|
|
* @param {libtess.primitiveType} type [description]
|
|
*/
|
|
libtess.GluTesselator.prototype.callBeginOrBeginData = function(type) {
|
|
if (this.callBeginData_) {
|
|
this.callBeginData_(type, this.polygonData_);
|
|
|
|
} else if (this.callBegin_) {
|
|
this.callBegin_(type);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* [callVertexOrVertexData description]
|
|
* @param {Object} data [description]
|
|
*/
|
|
libtess.GluTesselator.prototype.callVertexOrVertexData = function(data) {
|
|
if (this.callVertexData_) {
|
|
this.callVertexData_(data, this.polygonData_);
|
|
|
|
} else if (this.callVertex_) {
|
|
this.callVertex_(data);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* [callEdgeFlagOrEdgeFlagData description]
|
|
* @param {boolean} flag [description]
|
|
*/
|
|
libtess.GluTesselator.prototype.callEdgeFlagOrEdgeFlagData = function(flag) {
|
|
if (this.callEdgeFlagData_) {
|
|
this.callEdgeFlagData_(flag, this.polygonData_);
|
|
|
|
} else if (this.callEdgeFlag_) {
|
|
this.callEdgeFlag_(flag);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* [callEndOrEndData description]
|
|
*/
|
|
libtess.GluTesselator.prototype.callEndOrEndData = function() {
|
|
if (this.callEndData_) {
|
|
this.callEndData_(this.polygonData_);
|
|
|
|
} else if (this.callEnd_) {
|
|
this.callEnd_();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* [callCombineOrCombineData description]
|
|
* @param {Array.<number>} coords [description]
|
|
* @param {Array.<Object>} data [description]
|
|
* @param {Array.<number>} weight [description]
|
|
* @return {Object} Interpolated vertex
|
|
*/
|
|
libtess.GluTesselator.prototype.callCombineOrCombineData =
|
|
function(coords, data, weight) {
|
|
|
|
var interpData;
|
|
if (this.callCombineData_) {
|
|
interpData = this.callCombineData_(coords, data, weight, this.polygonData_);
|
|
|
|
} else if (this.callCombine_) {
|
|
interpData = this.callCombine_(coords, data, weight);
|
|
}
|
|
|
|
// TODO(bckenny): can't be undefined
|
|
if (interpData === undefined) {
|
|
interpData = null;
|
|
}
|
|
return interpData;
|
|
};
|
|
|
|
// TODO(bckenny): combine the enums in libtess
|
|
/**
|
|
* [callErrorOrErrorData description]
|
|
* @param {(libtess.errorType|libtess.gluEnum)} errno [description]
|
|
*/
|
|
libtess.GluTesselator.prototype.callErrorOrErrorData = function(errno) {
|
|
if (this.callErrorData_) {
|
|
this.callErrorData_(errno, this.polygonData_);
|
|
|
|
} else if (this.callError_) {
|
|
this.callError_(errno);
|
|
}
|
|
};
|