diff --git a/web/app/engine.js b/web/app/engine.js
index 51353d57..fe59cd5e 100644
--- a/web/app/engine.js
+++ b/web/app/engine.js
@@ -121,6 +121,52 @@ TCAD.utils.equal = function(v1, v2) {
return TCAD.utils.areEqual(v1, v2, TCAD.TOLERANCE);
};
+
+TCAD.utils.isPointInsidePolygon = function( inPt, inPolygon ) {
+ var EPSILON = TCAD.TOLERANCE;
+
+ var polyLen = inPolygon.length;
+
+ // inPt on polygon contour => immediate success or
+ // toggling of inside/outside at every single! intersection point of an edge
+ // with the horizontal line through inPt, left of inPt
+ // not counting lowerY endpoints of edges and whole edges on that line
+ var inside = false;
+ for( var p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) {
+ var edgeLowPt = inPolygon[ p ];
+ var edgeHighPt = inPolygon[ q ];
+
+ var edgeDx = edgeHighPt.x - edgeLowPt.x;
+ var edgeDy = edgeHighPt.y - edgeLowPt.y;
+
+ if ( Math.abs(edgeDy) > EPSILON ) { // not parallel
+ if ( edgeDy < 0 ) {
+ edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx;
+ edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy;
+ }
+ if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue;
+
+ if ( inPt.y == edgeLowPt.y ) {
+ if ( inPt.x == edgeLowPt.x ) return true; // inPt is on contour ?
+ // continue; // no intersection or edgeLowPt => doesn't count !!!
+ } else {
+ var perpEdge = edgeDy * (inPt.x - edgeLowPt.x) - edgeDx * (inPt.y - edgeLowPt.y);
+ if ( perpEdge == 0 ) return true; // inPt is on contour ?
+ if ( perpEdge < 0 ) continue;
+ inside = ! inside; // true intersection left of inPt
+ }
+ } else { // parallel or colinear
+ if ( inPt.y != edgeLowPt.y ) continue; // parallel
+ // egde lies on the same horizontal line as inPt
+ if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) ||
+ ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour !
+ // continue;
+ }
+ }
+
+ return inside;
+}
+
TCAD.utils.sketchToPolygons = function(geom) {
var dict = {};
@@ -282,7 +328,7 @@ TCAD.Solid = function(polygons, material) {
continue;
}
pushVertices(poly.shell);
- for ( var h = 0; h < poly.holes; ++ h ) {
+ for ( var h = 0; h < poly.holes.length; ++ h ) {
pushVertices(poly.holes[ h ]);
}
var polyFace = new TCAD.SketchFace(this, poly);
@@ -392,6 +438,13 @@ TCAD.Polygon = function(shell, holes, normal) {
normal = TCAD.geom.normalOfCCWSeq(shell);
} else {
shell = TCAD.utils.fixCCW(shell, normal);
+ if (holes.length > 0) {
+ var neg = normal.negate();
+ for (var h = 0; h < holes.length; ++h) {
+ holes[h] = TCAD.utils.fixCCW(holes[h], neg);
+ }
+ }
+
}
this.normal = normal;
@@ -448,6 +501,11 @@ TCAD.Polygon.prototype.to2D = function() {
TCAD.Polygon.prototype.triangulate = function() {
+ function triangulateShape( contour, holes ) {
+ var myTriangulator = new PNLTRI.Triangulator();
+ return myTriangulator.triangulate_polygon( [ contour ].concat(holes) );
+ }
+
var i, h;
var f2d = this.to2D();
@@ -459,7 +517,8 @@ TCAD.Polygon.prototype.triangulate = function() {
f2d.holes[h][i] = f2d.holes[h][i].three();
}
}
- return THREE.Shape.utils.triangulateShape( f2d.shell, f2d.holes );
+ return triangulateShape( f2d.shell, f2d.holes );
+// return THREE.Shape.utils.triangulateShape( f2d.shell, f2d.holes );
};
diff --git a/web/app/workbench.js b/web/app/workbench.js
index 23ad3788..c76613bb 100644
--- a/web/app/workbench.js
+++ b/web/app/workbench.js
@@ -415,14 +415,121 @@ TCAD.craft.cut = function(app, face, faces, height) {
var cut = CSG.fromPolygons(work).subtract(CSG.fromPolygons(cutter));
face.polygon.__face = undefined;
- return TCAD.craft._mergeCSGPolygons(cut.polygons).map(function(path) {
- return new TCAD.Polygon(path.vertices, [], path.normal);
- });
- function sortPaths() {
+ function pInP(p1, p2) {
+ var notEqPoints = [];
+ for (var i = 0; i < p1.length; ++i) {
+ var v1 = p1[i];
+ for (var j = 0; j < p2.length; ++j) {
+ var v2 = p2[j];
+ if (!v1.equals(v2)) {
+ notEqPoints.push(v1);
+ break;
+ }
+ }
+ }
+
+ if (notEqPoints.length == 0) {
+ return true;
+ }
+
+ for (var i = 0; i < notEqPoints.length; ++i) {
+ var v = notEqPoints[i];
+ if (!TCAD.utils.isPointInsidePolygon(v, p2)) {
+ return false;
+ }
+ }
+
+ return true;
}
+ function sortPaths(paths3D) {
+
+ paths = paths3D.map(function(path) {
+ return {
+ vertices : new TCAD.Polygon(path.vertices, [], path.normal).to2D().shell,
+ normal : path.normal
+ }
+ });
+
+ var index = [];
+ for (var pi = 0; pi < paths.length; ++pi) {
+ index[pi] = [];
+ paths3D[pi].holes = [];
+ }
+
+ for (var pi = 0; pi < paths.length; ++pi) {
+ var path = paths[pi];
+ depth = path.vertices[0].dot(path.normal);
+ for (var piTest = 0; piTest < paths.length; ++piTest) {
+ var pathTest = paths[piTest];
+ if (piTest === pi) continue;
+ if (!pathTest.normal.equals(path.normal)) continue;
+ depthTest = pathTest.vertices[0].dot(pathTest.normal);
+ if (!TCAD.utils.equal(depthTest, depth)) continue;
+
+ if (pInP(pathTest.vertices, path.vertices)) {
+ index[piTest].push(pi);
+ }
+ }
+ }
+ function collect(master, level) {
+ var success = false;
+ for (var i = 0; i < index.length; ++i) {
+ var masters = index[i];
+ if (level != masters.length) continue;
+ for (var j = 0; j < masters.length; ++j) {
+ var m = masters[j];
+ if (m === master) {
+ paths3D[m].holes.push(paths3D[i])
+ success = true;
+ }
+ }
+ }
+ return success;
+ }
+
+ for (var success = true, level = 1;
+ level < paths3D.length && success;
+ level ++, success = false) {
+
+ for (var i = 0; i < index.length; ++i) {
+ var masters = index[i];
+ if (masters.length == level - 1) {
+ if (collect(i, level)) {
+ success = true;
+ }
+ }
+ }
+ }
+
+ function separate(path, separated) {
+ separated.push(path);
+ for (var i = 0; i < path.holes.length; ++i) {
+ var hole = path.holes[i];
+ for (var j = 0; j < hole.holes.length; ++j) {
+ var inner = hole.holes[j];
+ separate(inner, separated)
+ }
+ }
+ }
+
+ var separated = [];
+ for (var i = 0; i < index.length; ++i) {
+ var masters = index[i];
+ if (masters.length == 0) {
+ separate(paths3D[i], separated);
+ }
+ }
+ return separated;
+ }
+
+ var merged = TCAD.craft._mergeCSGPolygons(cut.polygons);
+ var sorted = sortPaths(merged);
+ return sorted.map(function(path) {
+ return new TCAD.Polygon(path.vertices, path.holes.map(function(path){return path.vertices}), path.normal);
+ });
// return cut.polygons.map(function(e) {
// return new TCAD.Polygon(e.vertices.map(
diff --git a/web/index.html b/web/index.html
index 128a5a9e..9dc080ee 100644
--- a/web/index.html
+++ b/web/index.html
@@ -14,6 +14,7 @@
+
diff --git a/web/lib/pnltri.js b/web/lib/pnltri.js
new file mode 100644
index 00000000..78f7f1ef
--- /dev/null
+++ b/web/lib/pnltri.js
@@ -0,0 +1,2096 @@
+// pnltri.js / raw.github.com/jahting/pnltri.js/master/LICENSE
+/**
+ * @author jahting / http://www.ameco.tv/
+ *
+ * (Simple) Polygon Near-Linear Triangulation
+ * with fast ear-clipping for polygons without holes
+ *
+ */
+
+var PNLTRI = { REVISION: '2.1.1' };
+
+// ##### Global Constants #####
+
+
+// ##### Global Variables #####
+
+
+/**
+ * @author jahting / http://www.ameco.tv/
+ */
+
+PNLTRI.Math = {
+
+ random: Math.random, // function to use for random number generation
+
+ // generate random ordering in place:
+ // Fisher-Yates shuffle
+ array_shuffle: function( inoutArray ) {
+ for (var i = inoutArray.length - 1; i > 0; i-- ) {
+ var j = Math.floor( PNLTRI.Math.random() * (i+1) );
+ var tmp = inoutArray[i];
+ inoutArray[i] = inoutArray[j];
+ inoutArray[j] = tmp;
+ }
+ return inoutArray;
+ },
+
+
+ // like compare (<=>)
+ // yA > yB resp. xA > xB: 1, equal: 0, otherwise: -1
+ compare_pts_yx: function ( inPtA, inPtB ) {
+ var deltaY = inPtA.y - inPtB.y;
+ if ( deltaY < PNLTRI.Math.EPSILON_N ) {
+ return -1;
+ } else if ( deltaY > PNLTRI.Math.EPSILON_P ) {
+ return 1;
+ } else {
+ var deltaX = inPtA.x - inPtB.x;
+ if ( deltaX < PNLTRI.Math.EPSILON_N ) {
+ return -1;
+ } else if ( deltaX > PNLTRI.Math.EPSILON_P ) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ },
+
+
+ ptsCrossProd: function ( inPtVertex, inPtFrom, inPtTo ) {
+ // two vectors: ( v0: inPtVertex -> inPtFrom ), ( v1: inPtVertex -> inPtTo )
+ // CROSS_SINE: sin(theta) * len(v0) * len(v1)
+ return ( inPtFrom.x - inPtVertex.x ) * ( inPtTo.y - inPtVertex.y ) -
+ ( inPtFrom.y - inPtVertex.y ) * ( inPtTo.x - inPtVertex.x );
+ // <=> crossProd( inPtFrom-inPtVertex, inPtTo-inPtVertex )
+ // == 0: colinear (angle == 0 or 180 deg == PI rad)
+ // > 0: v1 lies left of v0, CCW angle from v0 to v1 is convex ( < 180 deg )
+ // < 0: v1 lies right of v0, CW angle from v0 to v1 is convex ( < 180 deg )
+ },
+
+};
+
+// precision of floating point arithmetic
+// PNLTRI.Math.EPSILON_P = Math.pow(2,-32); // ~ 0.0000000001
+ PNLTRI.Math.EPSILON_P = Math.pow(2,-43); // ~ 0.0000000000001
+ PNLTRI.Math.EPSILON_N = -PNLTRI.Math.EPSILON_P;
+
+// Problem with EPSILON-compares:
+// - especially when there is a x-coordinate ordering on equal y-coordinates
+// => either NO EPSILON-compares on y-coordinates, since almost equal y
+// can have very different x - so they are not nearly close
+// or EPSILON must be bigger: Solution so far.
+/**
+ * @author jahting / http://www.ameco.tv/
+ */
+
+/** @constructor */
+PNLTRI.PolygonData = function ( inPolygonChainList ) {
+
+ // list of polygon vertices
+ // .x, .y: coordinates
+ this.vertices = [];
+
+ // list of polygon segments, original polygons ane holes
+ // and additional ones added during the subdivision into
+ // uni-y-monotone polygons (s. this.monoSubPolyChains)
+ // doubly linked by: snext, sprev
+ this.segments = [];
+ this.diagonals = [];
+
+ // for the ORIGINAL polygon chains
+ this.idNextPolyChain = 0;
+ // for each original chain: lies the polygon inside to the left?
+ // "true": winding order is CCW for a contour or CW for a hole
+ // "false": winding order is CW for a contour or CCW for a hole
+ this.PolyLeftArr = [];
+
+ // indices into this.segments: at least one for each monoton chain for the polygon
+ // these subdivide the polygon into uni-y-monotone polygons, that is
+ // polygons that have only one segment between ymax and ymin on one side
+ // and the other side has monotone increasing y from ymin to ymax
+ // the monoSubPolyChains are doubly linked by: mnext, mprev
+ this.monoSubPolyChains = [];
+
+ // list of triangles: each 3 indices into this.vertices
+ this.triangles = [];
+
+ // initialize optional polygon chains
+ if ( inPolygonChainList ) {
+ for (var i=0, j=inPolygonChainList.length; i start point entry in vertices
+ vTo: inVertexTo, // -> end point entry in vertices
+ // upward segment? (i.e. vTo > vFrom) !!! only valid for sprev,snext NOT for mprev,mnext !!!
+ upward: ( PNLTRI.Math.compare_pts_yx(inVertexTo, inVertexFrom) == 1 ),
+ // doubly linked list of original polygon chains (not the monoChains !)
+ sprev: null, // previous segment
+ snext: null, // next segment
+ //
+ // for performance reasons:
+ // initialization of all fields added later
+ //
+ // for trapezoids
+ rootFrom: null, // root of partial tree where vFrom is located
+ rootTo: null, // root of partial tree where vTo is located
+ is_inserted: false, // already inserted into QueryStructure ?
+ // for assigning depth: trapezoids
+ trLeft: null, // one trapezoid bordering on the left of this segment
+ trRight: null, // one trapezoid bordering on the right of this segment
+ // for monochains
+ mprev: null, // doubly linked list for monotone chains (sub-polygons)
+ mnext: null,
+ marked: false, // already visited during unique monoChain identification ?
+ };
+ },
+
+ appendSegmentEntry: function ( inSegment ) { // private
+ this.segments.push( inSegment );
+ return inSegment;
+ },
+
+
+ appendDiagonalsEntry: function ( inDiagonal ) { // <<<<< public
+ this.diagonals.push( inDiagonal );
+ return inDiagonal;
+ },
+
+
+ addVertexChain: function ( inRawPointList ) { // private
+
+ function verts_equal( inVert1, inVert2 ) {
+ return ( ( Math.abs(inVert1.x - inVert2.x) < PNLTRI.Math.EPSILON_P ) &&
+ ( Math.abs(inVert1.y - inVert2.y) < PNLTRI.Math.EPSILON_P ) );
+ }
+
+ function verts_colinear_chain( inVert1, inVert2, inVert3 ) {
+ if ( Math.abs( PNLTRI.Math.ptsCrossProd( inVert2, inVert1, inVert3 ) ) > PNLTRI.Math.EPSILON_P ) return false;
+ // only real sequences, not direction reversals
+ var low, middle, high;
+ if ( Math.abs( inVert1.y - inVert2.y ) < PNLTRI.Math.EPSILON_P ) {
+ // horizontal line
+ middle = inVert2.x;
+ if ( inVert1.x < inVert3.x ) {
+ low = inVert1.x;
+ high = inVert3.x;
+ } else {
+ low = inVert3.x;
+ high = inVert1.x;
+ }
+ } else {
+ middle = inVert2.y;
+ if ( inVert1.y < inVert3.y ) {
+ low = inVert1.y;
+ high = inVert3.y;
+ } else {
+ low = inVert3.y;
+ high = inVert1.y;
+ }
+ }
+ return ( ( ( low - middle ) < PNLTRI.Math.EPSILON_P ) && ( ( middle - high ) < PNLTRI.Math.EPSILON_P ) );
+ }
+
+ var newVertices = [];
+ var newVertex, acceptVertex, lastIdx;
+ for ( var i=0; i < inRawPointList.length; i++ ) {
+ newVertex = this.appendVertexEntry( inRawPointList[i].x, inRawPointList[i].y );
+ // suppresses zero-length segments
+ acceptVertex = true;
+ lastIdx = newVertices.length-1;
+ if ( lastIdx >= 0 ) {
+ if ( verts_equal( newVertex, newVertices[lastIdx] ) ) {
+ acceptVertex = false;
+ } else if ( lastIdx > 0 ) {
+ if ( verts_colinear_chain( newVertices[lastIdx-1], newVertices[lastIdx], newVertex ) ) {
+ newVertices.pop();
+ }
+ }
+ }
+ if ( acceptVertex ) newVertices.push( newVertex );
+ }
+ // compare last vertices to first: suppresses zero-length and co-linear segments
+ lastIdx = newVertices.length - 1;
+ if ( ( lastIdx > 0 ) &&
+ verts_equal( newVertices[lastIdx], newVertices[0] ) ) {
+ newVertices.pop();
+ lastIdx--;
+ }
+ if ( lastIdx > 1 ) {
+ if ( verts_colinear_chain( newVertices[lastIdx-1], newVertices[lastIdx], newVertices[0] ) ) {
+ newVertices.pop();
+ lastIdx--;
+ }
+ if ( ( lastIdx > 1 ) &&
+ verts_colinear_chain( newVertices[lastIdx], newVertices[0], newVertices[1] ) ) {
+ newVertices.shift();
+ }
+ }
+
+ return newVertices;
+ },
+
+
+ addPolygonChain: function ( inRawPointList ) { // <<<<<< public
+
+ // vertices
+ var newVertices = this.addVertexChain( inRawPointList );
+ if ( newVertices.length < 3 ) {
+ console.log( "Polygon has < 3 vertices!", newVertices );
+ return 0;
+ }
+
+ // segments
+ var saveSegListLength = this.segments.length;
+ //
+ var segment, firstSeg, prevSeg;
+ for ( var i=0; i < newVertices.length-1; i++ ) {
+ segment = this.createSegmentEntry( newVertices[i], newVertices[i+1] );
+ if (prevSeg) {
+ segment.sprev = prevSeg;
+ prevSeg.snext = segment;
+ } else {
+ firstSeg = segment;
+ }
+ prevSeg = segment;
+ this.appendSegmentEntry( segment );
+ }
+ // close polygon
+ segment = this.createSegmentEntry( newVertices[newVertices.length-1], newVertices[0] );
+ segment.sprev = prevSeg;
+ prevSeg.snext = segment;
+ this.appendSegmentEntry( segment );
+ firstSeg.sprev = segment;
+ segment.snext = firstSeg;
+
+ this.PolyLeftArr[this.idNextPolyChain++] = true;
+ return this.segments.length - saveSegListLength;
+ },
+
+
+ /* Monotone Polygon Chains */
+
+ // Generate the uni-y-monotone sub-polygons from
+ // the trapezoidation of the polygon.
+
+ create_mono_chains: function () { // <<<<<< public
+ var newMono, newMonoTo, toFirstOutSeg, fromRevSeg;
+ for ( var i = 0, j = this.segments.length; i < j; i++) {
+ newMono = this.segments[i];
+ if ( this.PolyLeftArr[newMono.chainId] ) {
+ // preserve winding order
+ newMonoTo = newMono.vTo; // target of segment
+ newMono.mprev = newMono.sprev; // doubly linked list for monotone chains (sub-polygons)
+ newMono.mnext = newMono.snext;
+ } else {
+ // reverse winding order
+ newMonoTo = newMono.vFrom;
+ newMono = newMono.snext;
+ newMono.mprev = newMono.snext;
+ newMono.mnext = newMono.sprev;
+ }
+ if ( fromRevSeg = newMono.vFrom.lastInDiag ) { // assignment !
+ fromRevSeg.mnext = newMono;
+ newMono.mprev = fromRevSeg;
+ newMono.vFrom.lastInDiag = null; // cleanup
+ }
+ if ( toFirstOutSeg = newMonoTo.firstOutDiag ) { // assignment !
+ toFirstOutSeg.mprev = newMono;
+ newMono.mnext = toFirstOutSeg;
+ newMonoTo.firstOutDiag = null; // cleanup
+ }
+ }
+ },
+
+ // For each monotone polygon, find the ymax (to determine the two
+ // y-monotone chains) and skip duplicate monotone polygons
+
+ unique_monotone_chains_max: function () { // <<<<<< public
+
+ function find_monotone_chain_max( frontMono ) {
+ var frontPt, firstPt, ymaxPt;
+
+ var monoPosmax = frontMono;
+ firstPt = ymaxPt = frontMono.vFrom;
+
+ frontMono.marked = true;
+ frontMono = frontMono.mnext;
+ while ( frontPt = frontMono.vFrom ) { // assignment !
+ if (frontMono.marked) {
+ if ( frontPt == firstPt ) break; // mono chain completed
+ console.log("ERR unique_monotone: segment in two chains", firstPt, frontMono );
+ return null;
+ } else {
+/* if ( frontPt == firstPt ) { // check for robustness
+ console.log("ERR unique_monotone: point double", firstPt, frontMono );
+ } */
+ frontMono.marked = true;
+ }
+ if ( PNLTRI.Math.compare_pts_yx( frontPt, ymaxPt ) == 1 ) {
+ ymaxPt = frontPt;
+ monoPosmax = frontMono;
+ }
+ frontMono = frontMono.mnext;
+ }
+ return monoPosmax;
+ }
+
+ var frontMono, monoPosmax;
+
+ // assumes attribute "marked" is NOT yet "true" for any mono chain segment
+ this.monoSubPolyChains = [];
+ // loop through all original segments
+ for ( var i = 0, j = this.segments.length; i < j; i++ ) {
+ frontMono = this.segments[i];
+ if ( frontMono.marked ) continue; // already in a processed mono chain
+ monoPosmax = find_monotone_chain_max( frontMono );
+ if ( monoPosmax ) this.monoSubPolyChains.push( monoPosmax );
+ }
+ // loop through all additional segments (diagonals) // TODO: Testcase for mono chain without original segments !!!
+/* for ( var i = 0, j = this.diagonals.length; i < j; i++ ) {
+ frontMono = this.diagonals[i];
+ if ( frontMono.marked ) continue; // already in a processed mono chain
+ monoPosmax = find_monotone_chain_max( frontMono );
+ if ( monoPosmax ) this.monoSubPolyChains.push( monoPosmax );
+ } */
+ return this.monoSubPolyChains;
+ },
+
+
+ /* Triangles */
+
+ clearTriangles: function () {
+ this.triangles = [];
+ },
+
+ addTriangle: function ( inVert1, inVert2, inVert3 ) {
+ this.triangles.push( [ inVert1.id, inVert2.id, inVert3.id ] );
+ },
+
+};
+
+/**
+ * Simple Polygon Triangulation by Ear Clipping
+ *
+ * description of technique employed:
+ * http://www.siggraph.org/education/materials/HyperGraph/scanline/outprims/polygon1.htm
+ *
+ * This code is a quick port of code written in C++ which was submitted to
+ * flipcode.com by John W. Ratcliff // July 22, 2000
+ * See original code and more information here:
+ * http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml
+ *
+ * ported to actionscript by Zevan Rosser
+ * http://actionsnippet.com/?p=1462
+ *
+ * ported to javascript by Joshua Koo
+ * http://www.lab4games.net/zz85/blog
+ *
+ * adapted to doubly linked list by Juergen Ahting
+ * http://www.ameco.tv
+ *
+ */
+
+/** @constructor */
+PNLTRI.EarClipTriangulator = function ( inPolygonData ) {
+
+ this.polyData = inPolygonData;
+
+};
+
+
+PNLTRI.EarClipTriangulator.prototype = {
+
+ constructor: PNLTRI.EarClipTriangulator,
+
+
+ // triangulates first doubly linked segment list in this.polyData
+ // algorithm uses ear-clipping and runs in O(n^2) time
+
+ triangulate_polygon_no_holes: function () {
+
+ function isEarAt( vertex ) {
+
+ var prevX = vertex.mprev.vFrom.x;
+ var prevY = vertex.mprev.vFrom.y;
+
+ var vertX = vertex.vFrom.x;
+ var vertY = vertex.vFrom.y;
+
+ var nextX = vertex.mnext.vFrom.x;
+ var nextY = vertex.mnext.vFrom.y;
+
+ var vnX = nextX - vertX, vnY = nextY - vertY;
+ var npX = prevX - nextX, npY = prevY - nextY;
+ var pvX = vertX - prevX, pvY = vertY - prevY;
+
+ // concave angle at vertex -> not an ear to cut off
+ if ( PNLTRI.Math.EPSILON_P > ( ( pvX * vnY ) - ( vnX * pvY ) ) ) return false;
+
+ // check whether any other point lieas within the triangle abc
+ var vStop = vertex.mprev.mprev;
+ var vOther = vertex.mnext;
+ while ( vOther != vStop ) {
+ vOther = vOther.mnext;
+ var otherX = vOther.vFrom.x;
+ var otherY = vOther.vFrom.y;
+
+ var poX = otherX - prevX, poY = otherY - prevY;
+ // just in case there are several vertices with the same coordinate
+ if ( ( poX === 0 ) && ( poY === 0 ) ) continue; // vOther == vertex.mprev
+ var voX = otherX - vertX, voY = otherY - vertY;
+ if ( ( voX === 0 ) && ( voY === 0 ) ) continue; // vOther == vertex
+ var noX = otherX - nextX, noY = otherY - nextY;
+ if ( ( noX === 0 ) && ( noY === 0 ) ) continue; // vOther == vertex.mnext
+
+ // if vOther is inside triangle abc -> not an ear to cut off
+ if ( ( ( vnX * voY - vnY * voX ) >= PNLTRI.Math.EPSILON_N ) &&
+ ( ( pvX * poY - pvY * poX ) >= PNLTRI.Math.EPSILON_N ) &&
+ ( ( npX * noY - npY * noX ) >= PNLTRI.Math.EPSILON_N ) ) return false;
+ }
+ return true;
+
+ }
+
+ var myPolyData = this.polyData;
+ var startSeg = myPolyData.getFirstSegment();
+
+ // create a counter-clockwise ordered doubly linked list (monoChain links)
+
+ var cursor = startSeg;
+ if ( myPolyData.isClockWise( startSeg ) ) {
+ do { // reverses chain order
+ cursor.mprev = cursor.snext;
+ cursor.mnext = cursor.sprev;
+ cursor = cursor.sprev;
+ } while ( cursor != startSeg );
+ myPolyData.set_PolyLeft_wrong(0);
+ } else {
+ do {
+ cursor.mprev = cursor.sprev;
+ cursor.mnext = cursor.snext;
+ cursor = cursor.snext;
+ } while ( cursor != startSeg );
+ }
+
+ // remove all vertices except 2, creating 1 triangle every time
+
+ var vertex = startSeg;
+ var fullLoop = vertex; // prevent infinite loop on "defective" polygons
+
+ while ( vertex.mnext != vertex.mprev ) {
+ if ( isEarAt( vertex ) ) {
+ // found a triangle ear to cut off
+ this.polyData.addTriangle( vertex.mprev.vFrom, vertex.vFrom, vertex.mnext.vFrom );
+ // remove vertex from the remaining chain
+ vertex.mprev.mnext = vertex.mnext;
+ vertex.mnext.mprev = vertex.mprev;
+ vertex = vertex.mnext;
+ fullLoop = vertex; // reset error detection
+ } else {
+ vertex = vertex.mnext;
+ // loop?: probably non-simple polygon -> stop with error
+ if ( vertex == fullLoop ) return false;
+ }
+ }
+
+ return true;
+
+ },
+
+/* // takes one element of a double linked segment list
+ // works on array of vertices
+
+ triangulate_polygon_no_holes: function () {
+ var startSeg = this.polyData.getFirstSegment();
+
+ function vertList( inStartSeg ) {
+ var verts = [];
+ // we want a counter-clockwise polygon in verts
+ var doubleArea = 0.0;
+ var cursor = inStartSeg;
+ var p,q;
+ var idx = 0;
+ do {
+ p = cursor.sprev.vFrom;
+ q = cursor.vFrom;
+ doubleArea += p.x * q.y - q.x * p.y;
+ verts[idx++] = q;
+ cursor = cursor.snext;
+ } while ( cursor != inStartSeg );
+ if ( doubleArea < 0.0 ) {
+ verts = verts.reverse();
+ var tmp = verts.pop();
+ verts.unshift( tmp );
+ }
+ return verts;
+ }
+
+ function snip( verts, u, v, w, n ) {
+
+ var ax = verts[ u ].x;
+ var ay = verts[ u ].y;
+
+ var bx = verts[ v ].x;
+ var by = verts[ v ].y;
+
+ var cx = verts[ w ].x;
+ var cy = verts[ w ].y;
+
+ if ( PNLTRI.Math.EPSILON_P > ( ( bx - ax ) * ( cy - ay ) - ( by - ay ) * ( cx - ax ) ) ) return false;
+
+ var aX, aY, bX, bY, cX, cY;
+
+ aX = cx - bx; aY = cy - by;
+ bX = ax - cx; bY = ay - cy;
+ cX = bx - ax; cY = by - ay;
+
+ var p, px, py;
+
+ var apx, apy, bpx, bpy, cpx, cpy;
+ var cCROSSap, bCROSScp, aCROSSbp;
+
+ for ( p = 0; p < n; p ++ ) {
+
+ px = verts[ p ].x
+ py = verts[ p ].y
+
+ apx = px - ax; apy = py - ay;
+ if ( ( apx == 0 ) && ( apy == 0 ) ) continue;
+ bpx = px - bx; bpy = py - by;
+ if ( ( bpx == 0 ) && ( bpy == 0 ) ) continue;
+ cpx = px - cx; cpy = py - cy;
+ if ( ( cpx == 0 ) && ( cpy == 0 ) ) continue;
+
+ // see if p is inside triangle abc
+
+ aCROSSbp = aX * bpy - aY * bpx;
+ cCROSSap = cX * apy - cY * apx;
+ bCROSScp = bX * cpy - bY * cpx;
+
+ if ( ( aCROSSbp >= PNLTRI.Math.EPSILON_N ) &&
+ ( bCROSScp >= PNLTRI.Math.EPSILON_N ) &&
+ ( cCROSSap >= PNLTRI.Math.EPSILON_N ) ) return false;
+
+ }
+
+ return true;
+
+ };
+
+ var result = [];
+
+ var verts = vertList( startSeg );
+
+ var n = verts.length;
+ var nv = n;
+
+ var u, v, w;
+
+ // remove nv - 2 vertices, creating 1 triangle every time
+
+ var count = 2 * nv; // error detection
+
+ for ( v = nv - 1; nv > 2; ) {
+
+ // if we loop, it is probably a non-simple polygon
+
+ if ( ( count -- ) <= 0 ) return false;
+
+ // three consecutive vertices in current polygon,
+
+ u = v; if ( nv <= u ) u = 0; // previous
+ v = u + 1; if ( nv <= v ) v = 0; // new v
+ w = v + 1; if ( nv <= w ) w = 0; // next
+
+ if ( snip( verts, u, v, w, nv ) ) {
+
+ // output Triangle
+
+ this.polyData.addTriangle( verts[ u ], verts[ v ], verts[ w ] );
+
+ // remove v from the remaining polygon
+
+ var s, t;
+
+ for ( s = v, t = v + 1; t < nv; s++, t++ ) {
+
+ verts[ s ] = verts[ t ];
+
+ }
+
+ nv --;
+
+ v --;
+ if ( v < 0 ) v = nv-1;
+
+ // reset error detection counter
+
+ count = 2 * nv;
+
+ }
+
+ }
+
+ return true;
+
+ }, */
+
+};
+
+/**
+ * @author jahting / http://www.ameco.tv/
+ *
+ * Algorithm to create the trapezoidation of a polygon with holes
+ * according to Seidel's algorithm [Sei91]
+ */
+
+/** @constructor */
+PNLTRI.Trapezoid = function ( inHigh, inLow, inLeft, inRight ) {
+
+ this.vHigh = inHigh ? inHigh : { x: Number.POSITIVE_INFINITY, y: Number.POSITIVE_INFINITY };
+ this.vLow = inLow ? inLow : { x: Number.NEGATIVE_INFINITY, y: Number.NEGATIVE_INFINITY };
+
+ this.lseg = inLeft;
+ this.rseg = inRight;
+
+// this.uL = null; // -> Trapezoid: upper left neighbor
+// this.uR = null; // -> Trapezoid: upper right neighbor
+// this.dL = null; // -> Trapezoid: lower left neighbor
+// this.dR = null; // -> Trapezoid: lower right neighbor
+
+// this.sink = null; // link to corresponding SINK-Node in QueryStructure
+
+// this.usave = null; // temp: uL/uR, preserved for next step
+// this.uleft = null; // temp: from uL? (true) or uR (false)
+
+ this.depth = -1; // no depth assigned yet
+
+ this.monoDone = false; // monotonization: done with trying to split this trapezoid ?
+
+};
+
+PNLTRI.Trapezoid.prototype = {
+
+ constructor: PNLTRI.Trapezoid,
+
+ clone: function () {
+ var newTrap = new PNLTRI.Trapezoid( this.vHigh, this.vLow, this.lseg, this.rseg );
+
+ newTrap.uL = this.uL;
+ newTrap.uR = this.uR;
+
+ newTrap.dL = this.dL;
+ newTrap.dR = this.dR;
+
+ newTrap.sink = this.sink;
+
+ return newTrap;
+ },
+
+
+ splitOffLower: function ( inSplitPt ) {
+ var trLower = this.clone(); // new lower trapezoid
+
+ this.vLow = trLower.vHigh = inSplitPt;
+
+ // L/R unknown, anyway changed later
+ this.dL = trLower; // setBelow
+ trLower.uL = this; // setAbove
+ this.dR = trLower.uR = null;
+
+ // setAbove
+ if ( trLower.dL ) trLower.dL.uL = trLower; // dL always connects to uL
+ if ( trLower.dR ) trLower.dR.uR = trLower; // dR always connects to uR
+
+ return trLower;
+ },
+
+};
+
+
+/*==============================================================================
+ *
+ *============================================================================*/
+
+// PNLTRI.qsCounter = 0;
+
+/** @constructor */
+PNLTRI.QsNode = function ( inTrapezoid ) {
+// this.qsId = PNLTRI.qsCounter++; // Debug only
+ // Only SINK-nodes are created directly.
+ // The others originate from splitting trapezoids
+ // - by a horizontal line: SINK-Node -> Y-Node
+ // - by a segment: SINK-Node -> X-Node
+ this.trap = inTrapezoid;
+ inTrapezoid.sink = this;
+};
+
+PNLTRI.QsNode.prototype = {
+
+ constructor: PNLTRI.QsNode,
+
+};
+
+/*==============================================================================
+ *
+ *============================================================================*/
+
+/** @constructor */
+PNLTRI.QueryStructure = function ( inPolygonData ) {
+ // initialise the query structure and trapezoid list
+ var initialTrap = new PNLTRI.Trapezoid( null, null, null, null );
+ this.trapArray = [];
+ this.appendTrapEntry( initialTrap );
+
+// PNLTRI.qsCounter = 0;
+ this.root = new PNLTRI.QsNode( initialTrap );
+
+ if ( inPolygonData ) {
+ /*
+ * adds and initializes specific attributes for all segments
+ * // -> QueryStructure: roots of partial tree where vertex is located
+ * rootFrom, rootTo: for vFrom, vTo
+ * // marker
+ * is_inserted: already inserted into QueryStructure ?
+ */
+ var segListArray = inPolygonData.getSegments();
+ for ( var i = 0; i < segListArray.length; i++ ) {
+ segListArray[i].rootFrom = segListArray[i].rootTo = this.root;
+ segListArray[i].is_inserted = false;
+ }
+ }
+};
+
+PNLTRI.QueryStructure.prototype = {
+
+ constructor: PNLTRI.QueryStructure,
+
+ getRoot: function () {
+ return this.root;
+ },
+
+
+ appendTrapEntry: function ( inTrapezoid ) {
+ inTrapezoid.trapID = this.trapArray.length; // for Debug
+ this.trapArray.push( inTrapezoid );
+ },
+ cloneTrap: function ( inTrapezoid ) {
+ var trap = inTrapezoid.clone();
+ this.appendTrapEntry( trap );
+ return trap;
+ },
+
+
+ splitNodeAtPoint: function ( inNode, inPoint, inReturnUpper ) {
+ // inNode: SINK-Node with trapezoid containing inPoint
+ var trUpper = inNode.trap; // trUpper: trapezoid includes the point
+ if (trUpper.vHigh == inPoint) return inNode; // (ERROR) inPoint is already inserted
+ if (trUpper.vLow == inPoint) return inNode; // (ERROR) inPoint is already inserted
+ var trLower = trUpper.splitOffLower( inPoint ); // trLower: new lower trapezoid
+ this.appendTrapEntry( trLower );
+
+ // SINK-Node -> Y-Node
+ inNode.yval = inPoint;
+ inNode.trap = null;
+
+ inNode.right = new PNLTRI.QsNode( trUpper ); // Upper trapezoid sink
+ inNode.left = new PNLTRI.QsNode( trLower ); // Lower trapezoid sink
+
+ return inReturnUpper ? trUpper.sink : trLower.sink;
+ },
+
+
+ /*
+ * Mathematics & Geometry helper methods
+ */
+
+ fpEqual: function ( inNum0, inNum1 ) {
+ return Math.abs( inNum0 - inNum1 ) < PNLTRI.Math.EPSILON_P;
+ },
+
+
+ // Checks, whether the vertex inPt is to the left of line segment inSeg.
+ // Returns:
+ // >0: inPt is left of inSeg,
+ // <0: inPt is right of inSeg,
+ // =0: inPt is co-linear with inSeg
+ //
+ // ATTENTION: always viewed from -y, not as if moving along the segment chain !!
+
+ is_left_of: function ( inSeg, inPt, inBetweenY ) {
+ var retVal;
+ var dXfrom = inSeg.vFrom.x - inPt.x;
+ var dXto = inSeg.vTo.x - inPt.x;
+ var dYfromZero = this.fpEqual( inSeg.vFrom.y, inPt.y );
+ if ( this.fpEqual( inSeg.vTo.y, inPt.y ) ) {
+ if ( dYfromZero ) return 0; // all points on a horizontal line
+ retVal = dXto;
+ } else if ( dYfromZero ) {
+ retVal = dXfrom;
+/* } else if ( inBetweenY && ( dXfrom * dXto > 0 ) ) {
+ // both x-coordinates of inSeg are on the same side of inPt
+ if ( Math.abs( dXto ) >= PNLTRI.Math.EPSILON_P ) return dXto;
+ retVal = dXfrom; */
+ } else {
+ if ( inSeg.upward ) {
+ return PNLTRI.Math.ptsCrossProd( inSeg.vFrom, inSeg.vTo, inPt );
+ } else {
+ return PNLTRI.Math.ptsCrossProd( inSeg.vTo, inSeg.vFrom, inPt );
+ }
+ }
+ if ( Math.abs( retVal ) < PNLTRI.Math.EPSILON_P ) return 0;
+ return retVal;
+ },
+
+
+ /*
+ * Query structure main methods
+ */
+
+ // This method finds the Nodes in the QueryStructure corresponding
+ // to the trapezoids that contain the endpoints of inSegment,
+ // starting from Nodes rootFrom/rootTo and replacing them with the results.
+
+ segNodes: function ( inSegment ) {
+ this.ptNode( inSegment, true );
+ this.ptNode( inSegment, false );
+ },
+
+ // TODO: may need to prevent infinite loop in case of messed up
+ // trapezoid structure (s. test_add_segment_special_6)
+
+ ptNode: function ( inSegment, inUseFrom ) {
+ var ptMain, ptOther, qsNode;
+ if ( inUseFrom ) {
+ ptMain = inSegment.vFrom;
+ ptOther = inSegment.vTo; // used if ptMain is not sufficient
+ qsNode = inSegment.rootFrom;
+ } else {
+ ptMain = inSegment.vTo;
+ ptOther = inSegment.vFrom;
+ qsNode = inSegment.rootTo;
+ }
+ var compPt, compRes;
+ var isInSegmentShorter;
+
+ while ( qsNode ) {
+ if ( qsNode.yval ) { // Y-Node: horizontal line
+ // 4 times as often as X-Node
+ qsNode = ( PNLTRI.Math.compare_pts_yx( ( ( ptMain == qsNode.yval ) ? // is the point already inserted ?
+ ptOther : ptMain ), qsNode.yval ) == -1 ) ?
+ qsNode.left : qsNode.right; // below : above
+ } else if ( qsNode.seg ) { // X-Node: segment (~vertical line)
+ // 0.8 to 1.5 times as often as SINK-Node
+ if ( ( ptMain == qsNode.seg.vFrom ) ||
+ ( ptMain == qsNode.seg.vTo ) ) {
+ // the point is already inserted
+ if ( this.fpEqual( ptMain.y, ptOther.y ) ) {
+ // horizontal segment
+ if ( !this.fpEqual( qsNode.seg.vFrom.y, qsNode.seg.vTo.y ) ) {
+ qsNode = ( ptOther.x < ptMain.x ) ? qsNode.left : qsNode.right; // left : right
+ } else { // co-linear horizontal reversal: test_add_segment_special_7
+ if ( ptMain == qsNode.seg.vFrom ) {
+ // connected at qsNode.seg.vFrom
+// console.log("ptNode: co-linear horizontal reversal, connected at qsNode.seg.vFrom", inUseFrom, inSegment, qsNode )
+ isInSegmentShorter = inSegment.upward ?
+ ( ptOther.x >= qsNode.seg.vTo.x ) :
+ ( ptOther.x < qsNode.seg.vTo.x );
+ qsNode = ( isInSegmentShorter ?
+ inSegment.sprev.upward :
+ qsNode.seg.snext.upward ) ? qsNode.right : qsNode.left; // above : below
+ } else {
+ // connected at qsNode.seg.vTo
+// console.log("ptNode: co-linear horizontal reversal, connected at qsNode.seg.vTo", inUseFrom, inSegment, qsNode );
+ isInSegmentShorter = inSegment.upward ?
+ ( ptOther.x < qsNode.seg.vFrom.x ) :
+ ( ptOther.x >= qsNode.seg.vFrom.x );
+ qsNode = ( isInSegmentShorter ?
+ inSegment.snext.upward :
+ qsNode.seg.sprev.upward ) ? qsNode.left : qsNode.right; // below : above
+ }
+ }
+ continue;
+ } else {
+ compRes = this.is_left_of( qsNode.seg, ptOther, false );
+ if ( compRes === 0 ) {
+ // co-linear reversal (not horizontal)
+ // a co-linear continuation would not reach this point
+ // since the previous Y-node comparison would have led to a sink instead
+// console.log("ptNode: co-linear, going back on previous segment", ptMain, ptOther, qsNode );
+ // now as we have two consecutive co-linear segments we have to avoid a cross-over
+ // for this we need the far point on the "next" segment to the SHORTER of our two
+ // segments to avoid that "next" segment to cross the longer of our two segments
+ if ( ptMain == qsNode.seg.vFrom ) {
+ // connected at qsNode.seg.vFrom
+// console.log("ptNode: co-linear, going back on previous segment, connected at qsNode.seg.vFrom", ptMain, ptOther, qsNode );
+ isInSegmentShorter = inSegment.upward ?
+ ( ptOther.y >= qsNode.seg.vTo.y ) :
+ ( ptOther.y < qsNode.seg.vTo.y );
+ compRes = isInSegmentShorter ?
+ this.is_left_of( qsNode.seg, inSegment.sprev.vFrom, false ) :
+ -this.is_left_of( qsNode.seg, qsNode.seg.snext.vTo, false );
+ } else {
+ // connected at qsNode.seg.vTo
+// console.log("ptNode: co-linear, going back on previous segment, connected at qsNode.seg.vTo", ptMain, ptOther, qsNode );
+ isInSegmentShorter = inSegment.upward ?
+ ( ptOther.y < qsNode.seg.vFrom.y ) :
+ ( ptOther.y >= qsNode.seg.vFrom.y );
+ compRes = isInSegmentShorter ?
+ this.is_left_of( qsNode.seg, inSegment.snext.vTo, false ) :
+ -this.is_left_of( qsNode.seg, qsNode.seg.sprev.vFrom, false );
+ }
+ }
+ }
+ } else {
+/* if ( ( PNLTRI.Math.compare_pts_yx( ptMain, qsNode.seg.vFrom ) * // TODO: Testcase
+ PNLTRI.Math.compare_pts_yx( ptMain, qsNode.seg.vTo )
+ ) == 0 ) {
+ console.log("ptNode: Pts too close together#2: ", ptMain, qsNode.seg );
+ } */
+ compRes = this.is_left_of( qsNode.seg, ptMain, true );
+ if ( compRes === 0 ) {
+ // touching: ptMain lies on qsNode.seg but is none of its endpoints
+ // should happen quite seldom
+ compRes = this.is_left_of( qsNode.seg, ptOther, false );
+ if ( compRes === 0 ) {
+ // co-linear: inSegment and qsNode.seg
+ // includes case with ptOther connected to qsNode.seg
+ var tmpPtOther = inUseFrom ? inSegment.sprev.vFrom : inSegment.snext.vTo;
+ compRes = this.is_left_of( qsNode.seg, tmpPtOther, false );
+ }
+ }
+ }
+ if ( compRes > 0 ) {
+ qsNode = qsNode.left;
+ } else if ( compRes < 0 ) {
+ qsNode = qsNode.right;
+ } else {
+ // ??? TODO - not reached with current tests
+ // possible at all ?
+ return qsNode;
+ // qsNode = qsNode.left; // left
+ // qsNode = qsNode.right; // right
+ }
+ } else { // SINK-Node: trapezoid area
+ // least often
+ if ( !qsNode.trap ) { console.log("ptNode: unknown type", qsNode); }
+ if ( inUseFrom ) { inSegment.rootFrom = qsNode; }
+ else { inSegment.rootTo = qsNode; }
+ return qsNode;
+ }
+ } // end while - should not exit here
+ },
+
+
+ // Add a new segment into the trapezoidation and update QueryStructure and Trapezoids
+ // 1) locates the two endpoints of the segment in the QueryStructure and inserts them
+ // 2) goes from the high-end trapezoid down to the low-end trapezoid
+ // changing all the trapezoids in between.
+ // Except for the high-end and low-end no new trapezoids are created.
+ // For all in between either:
+ // - the existing trapezoid is restricted to the left of the new segment
+ // and on the right side the trapezoid from above is extended downwards
+ // - or the other way round:
+ // the existing trapezoid is restricted to the right of the new segment
+ // and on the left side the trapezoid from above is extended downwards
+
+ add_segment: function ( inSegment ) {
+ var scope = this;
+
+ // functions handling the relationship to the upper neighbors (uL, uR)
+ // of trNewLeft and trNewRight
+
+ function fresh_seg_or_upward_cusp() {
+ // trCurrent has at most 1 upper neighbor
+ // and should also have at least 1, since the high-point trapezoid
+ // has been split off another one, which is now above
+ var trUpper = trCurrent.uL || trCurrent.uR;
+
+ // trNewLeft and trNewRight CANNOT have been extended from above
+ if ( trUpper.dL && trUpper.dR ) {
+ // upward cusp: top forms a triangle
+
+ // ATTENTION: the decision whether trNewLeft or trNewRight is the
+ // triangle trapezoid formed by the two segments has already been taken
+ // when selecting trCurrent as the left or right lower neighbor to trUpper !!
+
+ if ( trCurrent == trUpper.dL ) {
+ // *** Case: FUC_UC_LEFT; prev: ----
+ // console.log( "fresh_seg_or_upward_cusp: upward cusp, new seg to the left!" );
+ // upper
+ // -------*-------
+ // + \
+ // NL + \
+ // + NR \
+ // + \
+ trNewRight.uL = null; // setAbove; trNewRight.uR, trNewLeft unchanged
+ trUpper.dL = trNewLeft; // setBelow; dR: unchanged, NEVER null
+ } else {
+ // *** Case: FUC_UC_RIGHT; prev: ----
+ // console.log( "fresh_seg_or_upward_cusp: upward cusp, new seg from the right!" );
+ // upper
+ // -------*-------
+ // / +
+ // / + NR
+ // / NL +
+ // / +
+ trNewLeft.uR = null; // setAbove; trNewLeft.uL, trNewRight unchanged
+ trUpper.dR = trNewRight; // setBelow; dL: unchanged, NEVER null
+ }
+ } else {
+ // *** Case: FUC_FS; prev: "splitOffLower"
+ // console.log( "fresh_seg_or_upward_cusp: fresh segment, high adjacent segment still missing" );
+ // upper
+ // -------*-------
+ // +
+ // NL +
+ // + NR
+ // +
+ trNewRight.uL = null; // setAbove; trNewLeft unchanged, set by "splitOffLower"
+ trNewRight.uR = trUpper;
+ trUpper.dR = trNewRight; // setBelow; trUpper.dL unchanged, set by "splitOffLower"
+ }
+ }
+
+ function continue_chain_from_above() {
+ // trCurrent has at least 2 upper neighbors
+ if ( trCurrent.usave ) {
+ // 3 upper neighbors (part II)
+ if ( trCurrent.uleft ) {
+ // *** Case: CC_3UN_LEFT; prev: 1B_3UN_LEFT
+ // console.log( "continue_chain_from_above: 3 upper neighbors (part II): u0a, u0b, uR(usave)" );
+ // => left gets one, right gets two of the upper neighbors
+ // !! trNewRight cannot have been extended from above
+ // and trNewLeft must have been !!
+ // + /
+ // C.uL + C.uR / C.usave
+ // - - - -+----*----------
+ // NL + NR
+ trNewRight.uL = trCurrent.uR; // setAbove
+ trNewRight.uR = trCurrent.usave;
+ trNewRight.uL.dL = trNewRight; // setBelow; trNewRight.uL.dR == null, unchanged
+ trNewRight.uR.dR = trNewRight; // setBelow; trNewRight.uR.dL == null, unchanged
+ } else {
+ // *** Case: CC_3UN_RIGHT; prev: 1B_3UN_RIGHT
+ // console.log( "continue_chain_from_above: 3 upper neighbors (part II): uL(usave), u1a, u1b" );
+ // => left gets two, right gets one of the upper neighbors
+ // !! trNewLeft cannot have been extended from above
+ // and trNewRight must have been !!
+ // \ +
+ // C.usave \ C.uL + C.uR
+ // ---------*----+- - - -
+ // NL + NR
+ trNewLeft.uR = trCurrent.uL; // setAbove; first uR !!!
+ trNewLeft.uL = trCurrent.usave;
+ trNewLeft.uL.dL = trNewLeft; // setBelow; dR == null, unchanged
+ trNewLeft.uR.dR = trNewLeft; // setBelow; dL == null, unchanged
+ }
+ trNewLeft.usave = trNewRight.usave = null;
+ } else if ( trCurrent.vHigh == trFirst.vHigh ) { // && meetsHighAdjSeg ??? TODO
+ // *** Case: CC_2UN_CONN; prev: ----
+ // console.log( "continue_chain_from_above: 2 upper neighbors, fresh seg, continues high adjacent seg" );
+ // !! trNewLeft and trNewRight cannot have been extended from above !!
+ // C.uL / C.uR
+ // -------*---------
+ // NL + NR
+ trNewRight.uR.dR = trNewRight; // setBelow; dL == null, unchanged
+ trNewLeft.uR = trNewRight.uL = null; // setAbove; trNewLeft.uL, trNewRight.uR unchanged
+ } else {
+ // *** Case: CC_2UN; prev: 1B_1UN_CONT, 2B_NOCON_RIGHT/LEFT, 2B_TOUCH_RIGHT/LEFT, 2B_COLIN_RIGHT/LEFT
+ // console.log( "continue_chain_from_above: simple case, 2 upper neighbors (no usave, not fresh seg)" );
+ // !! trNewLeft XOR trNewRight will have been extended from above !!
+ // C.uL + C.uR
+ // -------+---------
+ // NL + NR
+ if ( trNewRight == trCurrent ) { // trNewLeft has been extended from above
+ // setAbove
+ trNewRight.uL = trNewRight.uR;
+ trNewRight.uR = null;
+ // setBelow; dR: unchanged, is NOT always null (prev: 2B_NOCON_LEFT, 2B_TOUCH_LEFT, 2B_COLIN_LEFT)
+ trNewRight.uL.dL = trNewRight;
+ } else { // trNewRight has been extended from above
+ trNewLeft.uR = trNewLeft.uL; // setAbove; first uR !!!
+ trNewLeft.uL = null;
+ }
+ }
+ }
+
+ // functions handling the relationship to the lower neighbors (dL, dR)
+ // of trNewLeft and trNewRight
+ // trNewLeft or trNewRight MIGHT have been extended from above
+ // !! in that case dL and dR are different from trCurrent and MUST be set here !!
+
+ function only_one_trap_below( inTrNext ) {
+
+ if ( trCurrent.vLow == trLast.vLow ) {
+ // final part of segment
+
+ if ( meetsLowAdjSeg ) {
+ // downward cusp: bottom forms a triangle
+
+ // ATTENTION: the decision whether trNewLeft and trNewRight are to the
+ // left or right of the already inserted segment the new one meets here
+ // has already been taken when selecting trLast to the left or right
+ // of that already inserted segment !!
+
+ if ( trCurrent.dL ) {
+ // *** Case: 1B_DC_LEFT; next: ----
+ // console.log( "only_one_trap_below: downward cusp, new seg from the left!" );
+ // + /
+ // + NR /
+ // NL + /
+ // + /
+ // -------*-------
+ // C.dL = next
+
+ // setAbove
+ inTrNext.uL = trNewLeft; // uR: unchanged, NEVER null
+ // setBelow part 1
+ trNewLeft.dL = inTrNext;
+ trNewRight.dR = null;
+ } else {
+ // *** Case: 1B_DC_RIGHT; next: ----
+ // console.log( "only_one_trap_below: downward cusp, new seg to the right!" );
+ // \ +
+ // \ NL +
+ // \ + NR
+ // \ +
+ // -------*-------
+ // C.dR = next
+
+ // setAbove
+ inTrNext.uR = trNewRight; // uL: unchanged, NEVER null
+ // setBelow part 1
+ trNewLeft.dL = null;
+ trNewRight.dR = inTrNext;
+ }
+ } else {
+ // *** Case: 1B_1UN_END; next: ----
+ // console.log( "only_one_trap_below: simple case, new seg ends here, low adjacent seg still missing" );
+ // +
+ // NL + NR
+ // +
+ // ------*-------
+ // next
+
+ // setAbove
+ inTrNext.uL = trNewLeft; // trNewLeft must
+ inTrNext.uR = trNewRight; // must
+ // setBelow part 1
+ trNewLeft.dL = trNewRight.dR = inTrNext; // Error
+// trNewRight.dR = inTrNext;
+ }
+ // setBelow part 2
+ trNewLeft.dR = trNewRight.dL = null;
+ } else {
+ // NOT final part of segment
+
+ if ( inTrNext.uL && inTrNext.uR ) {
+ // inTrNext has two upper neighbors
+ // => a segment ends on the upper Y-line of inTrNext
+ // => inTrNext has temporarily 3 upper neighbors
+ // => marks whether the new segment cuts through
+ // uL or uR of inTrNext and saves the other in .usave
+ if ( inTrNext.uL == trCurrent ) {
+ // *** Case: 1B_3UN_LEFT; next: CC_3UN_LEFT
+ // console.log( "only_one_trap_below: inTrNext has 3 upper neighbors (part I): u0a, u0b, uR(usave)" );
+ // + /
+ // NL + NR /
+ // + /
+ // - - - -+--*----
+ // +
+ // next
+// if ( inTrNext.uR != trNewRight ) { // for robustness TODO: prevent
+ inTrNext.usave = inTrNext.uR;
+ inTrNext.uleft = true;
+ // trNewLeft: L/R undefined, will be extended down and changed anyway
+ // } else {
+ // ERROR: should not happen
+ // console.log( "ERR add_segment: Trapezoid Loop right", inTrNext, trCurrent, trNewLeft, trNewRight, inSegment, this );
+// }
+ } else {
+ // *** Case: 1B_3UN_RIGHT; next: CC_3UN_RIGHT
+ // console.log( "only_one_trap_below: inTrNext has 3 upper neighbors (part I): uL(usave), u1a, u1b" );
+ // \ +
+ // \ NL + NR
+ // \ +
+ // ---*---+- - - -
+ // +
+ // next
+// if ( inTrNext.uL != trNewLeft ) { // for robustness TODO: prevent
+ inTrNext.usave = inTrNext.uL;
+ inTrNext.uleft = false;
+ // trNewRight: L/R undefined, will be extended down and changed anyway
+ // } else {
+ // ERROR: should not happen
+ // console.log( "ERR add_segment: Trapezoid Loop left", inTrNext, trCurrent, trNewLeft, trNewRight, inSegment, this );
+// }
+ }
+ //} else {
+ // *** Case: 1B_1UN_CONT; next: CC_2UN
+ // console.log( "only_one_trap_below: simple case, new seg continues down" );
+ // +
+ // NL + NR
+ // +
+ // ------+-------
+ // +
+ // next
+
+ // L/R for one side undefined, which one is not fixed
+ // but that one will be extended down and changed anyway
+ // for the other side, vLow must lie at the opposite end
+ // thus both are set accordingly
+ }
+ // setAbove
+ inTrNext.uL = trNewLeft;
+ inTrNext.uR = trNewRight;
+ // setBelow
+ trNewLeft.dR = trNewRight.dL = inTrNext;
+ trNewLeft.dL = trNewRight.dR = null;
+ }
+ }
+
+ function two_trap_below() {
+ // Find out which one (dL,dR) is intersected by this segment and
+ // continue down that one
+ var trNext;
+ if ( ( trCurrent.vLow == trLast.vLow ) && meetsLowAdjSeg ) { // meetsLowAdjSeg necessary? TODO
+ // *** Case: 2B_CON_END; next: ----
+ // console.log( "two_trap_below: finished, meets low adjacent segment" );
+ // +
+ // NL + NR
+ // +
+ // ------*-------
+ // \ C.dR
+ // C.dL \
+
+ // setAbove
+ trCurrent.dL.uL = trNewLeft;
+ trCurrent.dR.uR = trNewRight;
+ // setBelow; sequence of assignments essential, just in case: trCurrent == trNewLeft
+ trNewLeft.dL = trCurrent.dL;
+ trNewRight.dR = trCurrent.dR;
+ trNewLeft.dR = trNewRight.dL = null;
+
+ trNext = null; // segment finished
+ } else {
+ // setAbove part 1
+ trCurrent.dL.uL = trNewLeft;
+ trCurrent.dR.uR = trNewRight;
+
+ var goDownRight;
+ // passes left or right of an already inserted NOT connected segment
+ // trCurrent.vLow: high-end of existing segment
+ var compRes = scope.is_left_of( inSegment, trCurrent.vLow, true );
+ if ( compRes > 0 ) { // trCurrent.vLow is left of inSegment
+ // *** Case: 2B_NOCON_RIGHT; next: CC_2UN
+ // console.log( "two_trap_below: (intersecting dR)" );
+ // +
+ // NL + NR
+ // +
+ // ---*---+- - - -
+ // \ +
+ // C.dL \ C.dR
+ goDownRight = true;
+ } else if ( compRes < 0 ) { // trCurrent.vLow is right of inSegment
+ // *** Case: 2B_NOCON_LEFT; next: CC_2UN
+ // console.log( "two_trap_below: (intersecting dL)" );
+ // +
+ // NL + NR
+ // +
+ // - - -+---*-------
+ // + \ C.dR
+ // C.dL \
+ goDownRight = false;
+ } else { // trCurrent.vLow lies ON inSegment
+ var vLowSeg = trCurrent.dL.rseg;
+ var directionIsUp = vLowSeg.upward;
+ var otherPt = directionIsUp ? vLowSeg.vFrom : vLowSeg.vTo;
+ compRes = scope.is_left_of( inSegment, otherPt, false );
+ if ( compRes > 0 ) { // otherPt is left of inSegment
+ // *** Case: 2B_TOUCH_RIGHT; next: CC_2UN
+ // console.log( "two_trap_below: vLow ON new segment, touching from right" );
+ // +
+ // NL + NR
+ // +
+ // -------*- - - -
+ // / +
+ // C.dL / C.dR
+ goDownRight = true; // like intersecting dR
+ } else if ( compRes < 0 ) { // otherPt is right of inSegment
+ // *** Case: 2B_TOUCH_LEFT; next: CC_2UN
+ // console.log( "two_trap_below: vLow ON new segment, touching from left" );
+ // +
+ // NL + NR
+ // +
+ // - - -*-------
+ // + \ C.dR
+ // C.dL \
+ goDownRight = false; // like intersecting dL
+ } else { // otherPt lies ON inSegment
+ vLowSeg = directionIsUp ? vLowSeg.snext : vLowSeg.sprev; // other segment with trCurrent.vLow
+ otherPt = directionIsUp ? vLowSeg.vTo : vLowSeg.vFrom;
+ compRes = scope.is_left_of( inSegment, otherPt, false );
+ if ( compRes > 0 ) { // otherPt is left of inSegment
+ // *** Case: 2B_COLIN_RIGHT; next: CC_2UN
+ // console.log( "two_trap_below: vLow ON new segment, touching from right" );
+ // +
+ // NL + NR
+ // -------*- - - -
+ // C.dL \+ C.dR
+ // \+
+ goDownRight = true; // like intersecting dR
+ // } else if ( compRes == 0 ) { // NOT POSSIBLE, since 3 points on a line is prevented during input of polychains
+ // goDownRight = true; // like intersecting dR
+ } else { // otherPt is right of inSegment
+ // *** Case: 2B_COLIN_LEFT; next: CC_2UN
+ // console.log( "two_trap_below: vLow ON new segment, touching from left" );
+ // +
+ // NL + NR
+ // - - - -*-------
+ // C.dL +/ C.dR
+ // +/
+ goDownRight = false; // TODO: for test_add_segment_special_4 -> like intersecting dL
+ }
+ }
+ }
+ if ( goDownRight ) {
+ trNext = trCurrent.dR;
+ // setAbove part 2
+ trCurrent.dR.uL = trNewLeft;
+ // setBelow part 1
+ trNewLeft.dL = trCurrent.dL;
+ trNewRight.dR = null; // L/R undefined, will be extended down and changed anyway
+ } else {
+ trNext = trCurrent.dL;
+ // setAbove part 2
+ trCurrent.dL.uR = trNewRight;
+ // setBelow part 1
+ trNewRight.dR = trCurrent.dR;
+ trNewLeft.dL = null; // L/R undefined, will be extended down and changed anyway
+ }
+ // setBelow part 2
+ trNewLeft.dR = trNewRight.dL = trNext;
+ }
+
+ return trNext;
+ }
+
+ //
+ // main function body
+ //
+
+/* if ( ( inSegment.sprev.vTo != inSegment.vFrom ) || ( inSegment.vTo != inSegment.snext.vFrom ) ) {
+ console.log( "add_segment: inconsistent point order of adjacent segments: ",
+ inSegment.sprev.vTo, inSegment.vFrom, inSegment.vTo, inSegment.snext.vFrom );
+ return;
+ } */
+
+ // Find the top-most and bottom-most intersecting trapezoids -> rootXXX
+ this.segNodes( inSegment );
+
+ var segLowVert , segLowNode, meetsLowAdjSeg; // y-min vertex
+ var segHighVert, segHighNode, meetsHighAdjSeg; // y-max vertex
+
+ if ( inSegment.upward ) {
+ segLowVert = inSegment.vFrom;
+ segHighVert = inSegment.vTo;
+ segLowNode = inSegment.rootFrom;
+ segHighNode = inSegment.rootTo;
+ // was lower point already inserted earlier? => segments meet at their ends
+ meetsLowAdjSeg = inSegment.sprev.is_inserted;
+ // was higher point already inserted earlier? => segments meet at their ends
+ meetsHighAdjSeg = inSegment.snext.is_inserted;
+ } else {
+ segLowVert = inSegment.vTo;
+ segHighVert = inSegment.vFrom;
+ segLowNode = inSegment.rootTo;
+ segHighNode = inSegment.rootFrom;
+ meetsLowAdjSeg = inSegment.snext.is_inserted;
+ meetsHighAdjSeg = inSegment.sprev.is_inserted;
+ }
+
+ // insert higher vertex into QueryStructure
+ if ( !meetsHighAdjSeg ) {
+ // higher vertex not yet inserted => split trapezoid horizontally
+ var tmpNode = this.splitNodeAtPoint( segHighNode, segHighVert, false );
+ // move segLowNode to new (lower) trapezoid, if it was the one which was just split
+ if ( segHighNode == segLowNode ) segLowNode = tmpNode;
+ segHighNode = tmpNode;
+ }
+ var trFirst = segHighNode.trap; // top-most trapezoid for this segment
+
+ // check for robustness // TODO: prevent
+ if ( !trFirst.uL && !trFirst.uR ) {
+ console.log("ERR add_segment: missing trFirst.uX: ", trFirst );
+ return;
+ }
+ if ( trFirst.vHigh != segHighVert ) {
+ console.log("ERR add_segment: trFirstHigh != segHigh: ", trFirst );
+ return;
+ }
+
+ // insert lower vertex into QueryStructure
+ if ( !meetsLowAdjSeg ) {
+ // lower vertex not yet inserted => split trapezoid horizontally
+ segLowNode = this.splitNodeAtPoint( segLowNode, segLowVert, true );
+ }
+ var trLast = segLowNode.trap; // bottom-most trapezoid for this segment
+
+ //
+ // Thread the segment into the query "tree" from top to bottom.
+ // All the trapezoids which are intersected by inSegment are "split" into two.
+ // For each the SINK-QsNode is converted into an X-Node and
+ // new sinks for the new partial trapezoids are added.
+ // In fact a real split only happens at the top and/or bottom end of the segment
+ // since at every y-line seperating two trapezoids is traverses it
+ // cuts off the "beam" from the y-vertex on one side, so that at that side
+ // the trapezoid from above can be extended down.
+ //
+
+ var trCurrent = trFirst;
+
+ var trNewLeft, trNewRight, trPrevLeft, trPrevRight;
+
+ var counter = this.trapArray.length + 2; // just to prevent infinite loop
+ var trNext;
+ while ( trCurrent ) {
+ if ( --counter < 0 ) {
+ console.log( "ERR add_segment: infinite loop", trCurrent, inSegment, this );
+ return;
+ }
+ if ( !trCurrent.dL && !trCurrent.dR ) {
+ // ERROR: no successors, cannot arise if data is correct
+ console.log( "ERR add_segment: missing successors", trCurrent, inSegment, this );
+ return;
+ }
+
+ var qs_trCurrent = trCurrent.sink;
+ // SINK-Node -> X-Node
+ qs_trCurrent.seg = inSegment;
+ qs_trCurrent.trap = null;
+ //
+ // successive trapezoids bordered by the same segments are merged
+ // by extending the trPrevRight or trPrevLeft down
+ // and redirecting the parent X-Node to the extended sink
+ // !!! destroys tree structure since several nodes now point to the same SINK-Node !!!
+ // TODO: maybe it's not a problem;
+ // merging of X-Nodes is no option, since they are used as "rootFrom/rootTo" !
+ //
+ if ( trPrevRight && ( trPrevRight.rseg == trCurrent.rseg ) ) {
+ // console.log( "add_segment: extending right predecessor down!", trPrevRight );
+ trNewLeft = trCurrent;
+ trNewRight = trPrevRight;
+ trNewRight.vLow = trCurrent.vLow;
+ // redirect parent X-Node to extended sink
+ qs_trCurrent.left = new PNLTRI.QsNode( trNewLeft ); // trCurrent -> left SINK-Node
+ qs_trCurrent.right = trPrevRight.sink; // deforms tree by multiple links to trPrevRight.sink
+ } else if ( trPrevLeft && ( trPrevLeft.lseg == trCurrent.lseg ) ) {
+ // console.log( "add_segment: extending left predecessor down!", trPrevLeft );
+ trNewRight = trCurrent;
+ trNewLeft = trPrevLeft;
+ trNewLeft.vLow = trCurrent.vLow;
+ // redirect parent X-Node to extended sink
+ qs_trCurrent.left = trPrevLeft.sink; // deforms tree by multiple links to trPrevLeft.sink
+ qs_trCurrent.right = new PNLTRI.QsNode( trNewRight ); // trCurrent -> right SINK-Node
+ } else {
+ trNewLeft = trCurrent;
+ trNewRight = this.cloneTrap(trCurrent);
+ qs_trCurrent.left = new PNLTRI.QsNode( trNewLeft ); // trCurrent -> left SINK-Node
+ qs_trCurrent.right = new PNLTRI.QsNode( trNewRight ); // new clone -> right SINK-Node
+ }
+
+ // handle neighbors above
+ if ( trCurrent.uL && trCurrent.uR ) {
+ continue_chain_from_above();
+ } else {
+ fresh_seg_or_upward_cusp();
+ }
+
+ // handle neighbors below
+ if ( trCurrent.dL && trCurrent.dR ) {
+ trNext = two_trap_below();
+ } else {
+ if ( trCurrent.dL ) {
+ // console.log( "add_segment: only_one_trap_below! (dL)" );
+ trNext = trCurrent.dL;
+ } else {
+ // console.log( "add_segment: only_one_trap_below! (dR)" );
+ trNext = trCurrent.dR;
+ }
+ only_one_trap_below( trNext );
+ }
+
+ if ( trNewLeft.rseg ) trNewLeft.rseg.trLeft = trNewRight;
+ if ( trNewRight.lseg ) trNewRight.lseg.trRight = trNewLeft;
+ trNewLeft.rseg = trNewRight.lseg = inSegment;
+ inSegment.trLeft = trNewLeft;
+ inSegment.trRight = trNewRight;
+
+ // further loop-step down ?
+ if ( trCurrent.vLow != trLast.vLow ) {
+ trPrevLeft = trNewLeft;
+ trPrevRight = trNewRight;
+
+ trCurrent = trNext;
+ } else {
+ trCurrent = null;
+ }
+ } // end while
+
+ inSegment.is_inserted = true;
+ // console.log( "add_segment: ###### DONE ######" );
+ },
+
+ // Assigns a depth to all trapezoids;
+ // 0: outside, 1: main polygon, 2: holes, 3:polygons in holes, ...
+ // Checks segment orientation and marks those polygon chains for reversal
+ // where the polygon inside lies to their right (contour in CW, holes in CCW)
+ assignDepths: function ( inPolyData ) {
+ var thisDepth = [ this.trapArray[0] ];
+ var nextDepth = [];
+
+ var thisTrap, borderSeg, curDepth = 0;
+ do {
+ // rseg should exactely go upward on trapezoids inside the polygon (odd depth)
+ var expectedRsegUpward = ( ( curDepth % 2 ) == 1 );
+ while ( thisTrap = thisDepth.pop() ) { // assignment !
+ if ( thisTrap.depth != -1 ) continue;
+ thisTrap.depth = curDepth;
+ //
+ if ( thisTrap.uL ) thisDepth.push( thisTrap.uL );
+ if ( thisTrap.uR ) thisDepth.push( thisTrap.uR );
+ if ( thisTrap.dL ) thisDepth.push( thisTrap.dL );
+ if ( thisTrap.dR ) thisDepth.push( thisTrap.dR );
+ //
+ if ( ( borderSeg = thisTrap.lseg ) && ( borderSeg.trLeft.depth == -1 ) ) // assignment !
+ nextDepth.push( borderSeg.trLeft );
+ if ( borderSeg = thisTrap.rseg ) { // assignment !
+ if ( borderSeg.trRight.depth == -1 )
+ nextDepth.push( borderSeg.trRight );
+ if ( borderSeg.upward != expectedRsegUpward )
+ inPolyData.set_PolyLeft_wrong( borderSeg.chainId );
+ }
+ }
+ thisDepth = nextDepth; nextDepth = [];
+ curDepth++;
+ } while ( thisDepth.length > 0 );
+ },
+
+ // creates the visibility map:
+ // for each vertex the list of all vertices in CW order which are directly
+ // visible through neighboring trapezoids and thus can be connected by a diagonal
+
+ create_visibility_map: function ( inPolygonData ) {
+ // positional slots for neighboring trapezoid-diagonals
+ var DIAG_UL = 0, DIAG_UM = 1, DIAG_ULR = 2, DIAG_UR = 3;
+ var DIAG_DR = 4, DIAG_DM = 5, DIAG_DLR = 6, DIAG_DL = 7;
+
+ var i, j;
+ var nbVertices = inPolygonData.nbVertices();
+
+ // initialize arrays for neighboring trapezoid-diagonals and vertices
+ var myVisibleDiagonals = new Array(nbVertices);
+ for ( i = 0; i < nbVertices; i++ ) {
+ myVisibleDiagonals[i] = new Array(DIAG_DL+1);
+ }
+ // create the list of neighboring trapezoid-diagonals
+ // put into their positional slots
+ var myExternalNeighbors = new Array(nbVertices);
+ for ( i = 0, j = this.trapArray.length; i < j; i++ ) {
+ var curTrap = this.trapArray[i];
+ var highPos = curTrap.uL ?
+ ( curTrap.uR ? DIAG_DM : DIAG_DL ) :
+ ( curTrap.uR ? DIAG_DR : DIAG_DLR );
+ var lowPos = curTrap.dL ?
+ ( curTrap.dR ? DIAG_UM : DIAG_UL ) :
+ ( curTrap.dR ? DIAG_UR : DIAG_ULR );
+
+ if ( ( curTrap.depth % 2 ) == 1 ) { // inside ?
+ if ( ( highPos == DIAG_DM ) || ( lowPos == DIAG_UM ) ||
+ ( ( highPos == DIAG_DL ) && ( lowPos == DIAG_UR ) ) ||
+ ( ( highPos == DIAG_DR ) && ( lowPos == DIAG_UL ) ) ) {
+ var lhDiag = inPolygonData.appendDiagonalsEntry( {
+ vFrom: curTrap.vLow, vTo: curTrap.vHigh,
+ mprev: null, mnext: null, marked: false } );
+ var hlDiag = inPolygonData.appendDiagonalsEntry( {
+ vFrom: curTrap.vHigh, vTo: curTrap.vLow, revDiag: lhDiag,
+ mprev: null, mnext: null, marked: false } );
+ lhDiag.revDiag = hlDiag;
+ myVisibleDiagonals[ curTrap.vLow.id][ lowPos] = lhDiag;
+ myVisibleDiagonals[curTrap.vHigh.id][highPos] = hlDiag;
+ }
+ } else { // outside, hole
+ if ( curTrap.vHigh.id !== null ) myExternalNeighbors[curTrap.vHigh.id] = highPos;
+ if ( curTrap.vLow.id !== null ) myExternalNeighbors[ curTrap.vLow.id] = lowPos;
+ }
+ }
+ // create the list of outgoing diagonals in the right order (CW)
+ // from the ordered list of neighboring trapezoid-diagonals
+ // - starting from an external one
+ // and connect those incoming to
+ var curDiag, curDiags, firstElem, fromVertex, lastIncoming;
+ for ( i = 0; i < nbVertices; i++ ) {
+ curDiags = myVisibleDiagonals[i];
+ firstElem = myExternalNeighbors[i];
+ if ( firstElem == null ) continue; // eg. skipped vertices (zero length, co-linear // NOT: === !
+ j = firstElem;
+ lastIncoming = null;
+ do {
+ if ( j++ > DIAG_DL ) j = DIAG_UL; // circular positional list
+ if ( curDiag = curDiags[j] ) {
+ if ( lastIncoming ) {
+ curDiag.mprev = lastIncoming;
+ lastIncoming.mnext = curDiag;
+ } else {
+ fromVertex = curDiag.vFrom;
+ fromVertex.firstOutDiag = curDiag;
+ }
+ lastIncoming = curDiag.revDiag;
+ }
+ } while ( j != firstElem );
+ if ( lastIncoming ) fromVertex.lastInDiag = lastIncoming;
+ }
+ },
+
+
+};
+
+
+/*==============================================================================
+ *
+ *============================================================================*/
+
+/** @constructor */
+PNLTRI.Trapezoider = function ( inPolygonData ) {
+
+ this.polyData = inPolygonData;
+ this.queryStructure = new PNLTRI.QueryStructure( this.polyData );
+
+};
+
+PNLTRI.Trapezoider.prototype = {
+
+ constructor: PNLTRI.Trapezoider,
+
+
+ /*
+ * Mathematics helper methods
+ */
+
+ optimise_randomlist: function ( inOutSegListArray ) {
+ // makes sure that the first N segments are one from each of the N polygon chains
+ var mainIdx = 0;
+ var helpIdx = this.polyData.nbPolyChains();
+ if ( helpIdx == 1 ) return;
+ var chainMarker = new Array(helpIdx);
+ var oldSegListArray = inOutSegListArray.concat();
+ for (var i=0; i 1 ) ? Math.floor( nbSegs / logstar ) : nbSegs;
+
+ // Core: adds next partition of the segments
+ for (; current < partEnd; current++ ) { myQs.add_segment( randSegListArray[current] ); }
+// console.log( nbSegs, current );
+
+ // To speed up the segment insertion into the trapezoidation
+ // the endponts of those segments not yet inserted
+ // are repeatedly pre-located,
+ // thus their final location-query can start at the top of the
+ // appropriate sub-tree instead of the root of the whole
+ // query structure.
+ //
+ for (i = current; i < nbSegs; i++) { this.queryStructure.segNodes( randSegListArray[i] ); }
+ }
+
+ myQs.assignDepths( this.polyData );
+ // cleanup to support garbage collection
+ for (i = 0; i < nbSegs; i++) { randSegListArray[i].trLeft = randSegListArray[i].trRight = null; }
+ },
+
+ // Creates a visibility map:
+ // for each vertex the list of all vertices in CW order which are directly
+ // visible through neighboring trapezoids and thus can be connected by a diagonal
+
+ create_visibility_map: function () {
+ return this.queryStructure.create_visibility_map( this.polyData );
+ },
+
+};
+
+/**
+ * @author jahting / http://www.ameco.tv/
+ *
+ * Algorithm to split a polygon into uni-y-monotone sub-polygons
+ *
+ * 1) creates a trapezoidation of the main polygon according to Seidel's
+ * algorithm [Sei91]
+ * 2) uses diagonals of the trapezoids as additional segments
+ * to split the main polygon into uni-y-monotone sub-polygons
+ */
+
+/** @constructor */
+PNLTRI.MonoSplitter = function ( inPolygonData ) {
+
+ this.polyData = inPolygonData;
+
+ this.trapezoider = null;
+
+};
+
+
+PNLTRI.MonoSplitter.prototype = {
+
+ constructor: PNLTRI.MonoSplitter,
+
+
+ monotonate_trapezoids: function () { // <<<<<<<<<< public
+ // Trapezoidation
+ this.trapezoider = new PNLTRI.Trapezoider( this.polyData );
+ // => one triangular trapezoid which lies inside the polygon
+ this.trapezoider.trapezoide_polygon();
+
+ // create segments for diagonals
+ this.trapezoider.create_visibility_map();
+ // create mono chains by inserting diagonals
+ this.polyData.create_mono_chains();
+
+ // create UNIQUE monotone sub-polygons
+ this.polyData.unique_monotone_chains_max();
+ },
+
+};
+
+/**
+ * @author jahting / http://www.ameco.tv/
+ *
+ * Algorithm to triangulate uni-y-monotone polygons [FoM84]
+ *
+ * expects list of doubly linked monoChains, with Y-max as first vertex
+ */
+
+
+/** @constructor */
+PNLTRI.MonoTriangulator = function ( inPolygonData ) {
+
+ this.polyData = inPolygonData;
+
+};
+
+
+PNLTRI.MonoTriangulator.prototype = {
+
+ constructor: PNLTRI.MonoTriangulator,
+
+
+ // Pass each uni-y-monotone polygon with start at Y-max for greedy triangulation.
+
+ triangulate_all_polygons: function () { // <<<<<<<<<< public
+ var normedMonoChains = this.polyData.getMonoSubPolys();
+ this.polyData.clearTriangles();
+ for ( var i=0; i monoPosmin is next to monoPosmax (left or right)
+ var monoPosmax = normedMonoChains[i];
+ var prevMono = monoPosmax.mprev;
+ var nextMono = monoPosmax.mnext;
+
+ if ( nextMono.mnext == prevMono ) { // already a triangle
+ this.polyData.addTriangle( monoPosmax.vFrom, nextMono.vFrom, prevMono.vFrom );
+ } else { // triangulate the polygon
+ this.triangulate_monotone_polygon( monoPosmax );
+ }
+ }
+ },
+
+ // algorithm to triangulate an uni-y-monotone polygon in O(n) time.[FoM84]
+
+ triangulate_monotone_polygon: function ( monoPosmax ) { // private
+ var scope = this;
+
+ function error_cleanup() {
+ // Error in algorithm OR polygon is not uni-y-monotone
+ console.log( "ERR uni-y-monotone: only concave angles left", vertBackLog );
+ // push all "wrong" triangles => loop ends
+ while (vertBackLogIdx > 1) {
+ vertBackLogIdx--;
+ scope.polyData.addTriangle( vertBackLog[vertBackLogIdx-1],
+ vertBackLog[vertBackLogIdx],
+ vertBackLog[vertBackLogIdx+1] );
+ }
+ }
+
+ //
+ // Decisive for this algorithm to work correctly is to make sure
+ // the polygon stays uni-y-monotone when cutting off ears, i.e.
+ // to make sure the top-most and bottom-most vertices are removed last
+ // Usually this is done by handling the LHS-case ("LeftHandSide is a single segment")
+ // and the RHS-case ("RightHandSide segment is a single segment")
+ // differently by starting at the bottom for LHS and at the top for RHS.
+ // This is not necessary. It can be seen easily, that starting
+ // from the vertex next to top handles both cases correctly.
+ //
+
+ var frontMono = monoPosmax.mnext; // == LHS: YminPoint; RHS: YmaxPoint.mnext
+ var endVert = monoPosmax.vFrom;
+
+ var vertBackLog = [ frontMono.vFrom ];
+ var vertBackLogIdx = 0;
+
+ frontMono = frontMono.mnext;
+ var frontVert = frontMono.vFrom;
+
+ // check for robustness // TODO
+ if (frontVert == endVert) return; // Error: only 2 vertices
+
+ while ( (frontVert != endVert) || (vertBackLogIdx > 1) ) {
+ if ( vertBackLogIdx > 0 ) {
+ // vertBackLog is not empty
+ var insideAngleCCW = PNLTRI.Math.ptsCrossProd( vertBackLog[vertBackLogIdx], frontVert, vertBackLog[vertBackLogIdx-1] );
+ if ( Math.abs(insideAngleCCW) <= PNLTRI.Math.EPSILON_P ) {
+ // co-linear
+ if ( (frontVert == endVert) || // all remaining triangles are co-linear (180 degree)
+ ( PNLTRI.Math.compare_pts_yx( vertBackLog[vertBackLogIdx], frontVert ) == // co-linear-reversal
+ PNLTRI.Math.compare_pts_yx( vertBackLog[vertBackLogIdx], vertBackLog[vertBackLogIdx-1] ) ) ) {
+ insideAngleCCW = 1; // => create triangle
+ }
+ }
+ if ( insideAngleCCW > 0 ) {
+ // convex corner: cut if off
+ this.polyData.addTriangle( vertBackLog[vertBackLogIdx-1], vertBackLog[vertBackLogIdx], frontVert );
+ vertBackLogIdx--;
+ } else {
+ // non-convex: add frontVert to the vertBackLog
+ vertBackLog[++vertBackLogIdx] = frontVert;
+ if (frontVert == endVert) error_cleanup(); // should never happen !!
+ else {
+ frontMono = frontMono.mnext;
+ frontVert = frontMono.vFrom;
+ }
+ }
+ } else {
+ // vertBackLog contains only start vertex:
+ // add frontVert to the vertBackLog and advance frontVert
+ vertBackLog[++vertBackLogIdx] = frontVert;
+ frontMono = frontMono.mnext;
+ frontVert = frontMono.vFrom;
+ }
+ }
+ // reached the last vertex. Add in the triangle formed
+ this.polyData.addTriangle( vertBackLog[vertBackLogIdx - 1], vertBackLog[vertBackLogIdx], frontVert );
+ },
+
+};
+
+/**
+ * @author jahting / http://www.ameco.tv/
+ */
+
+/*******************************************************************************
+ *
+ * Triangulator for Simple Polygons with Holes
+ *
+ * polygon with holes:
+ * - one closed contour polygon chain
+ * - zero or more closed hole polygon chains
+ *
+ * polygon chain (closed):
+ * - Array of vertex Objects with attributes "x" and "y"
+ * - representing the sequence of line segments
+ * - closing line segment (last->first vertex) is implied
+ * - line segments are non-zero length and non-crossing
+ *
+ * "global vertex index":
+ * - vertex number resulting from concatenation all polygon chains (starts with 0)
+ *
+ *
+ * Parameters (will not be changed):
+ * inPolygonChains:
+ * - Array of polygon chains
+ *
+ * Results (are a fresh copy):
+ * triangulate_polygon:
+ * - Array of Triangles ( Array of 3 "global vertex index" values )
+ *
+ ******************************************************************************/
+
+/** @constructor */
+PNLTRI.Triangulator = function () {
+
+ this.lastPolyData = null; // for Debug purposes only
+
+};
+
+
+PNLTRI.Triangulator.prototype = {
+
+ constructor: PNLTRI.Triangulator,
+
+
+ clear_lastData: function () { // save memory after Debug
+ this.lastPolyData = null;
+ },
+
+ // for the polygon data AFTER triangulation
+ // returns an Array of flags, one flag for each polygon chain:
+ // lies the inside of the polygon to the left?
+ // "true" implies CCW for contours and CW for holes
+ get_PolyLeftArr: function () {
+ if ( this.lastPolyData ) return this.lastPolyData.get_PolyLeftArr();
+ return null;
+ },
+
+
+ triangulate_polygon: function ( inPolygonChains, inForceTrapezoidation ) {
+
+ // collected conditions for selecting EarClipTriangulator over Seidel's algorithm
+ function is_basic_polygon() {
+ if (inForceTrapezoidation) return false;
+ return ( myPolygonData.nbPolyChains() == 1 );
+ }
+
+
+ this.clear_lastData();
+ if ( ( !inPolygonChains ) || ( inPolygonChains.length === 0 ) ) return [];
+ //
+ // initializes general polygon data structure
+ //
+ var myPolygonData = new PNLTRI.PolygonData( inPolygonChains );
+ //
+ var basicPolygon = is_basic_polygon();
+ var myTriangulator;
+ if ( basicPolygon ) {
+ //
+ // triangulates single polygon without holes
+ //
+ myTriangulator = new PNLTRI.EarClipTriangulator( myPolygonData );
+ basicPolygon = myTriangulator.triangulate_polygon_no_holes();
+ }
+ if ( !basicPolygon ) {
+ //
+ // splits polygon into uni-y-monotone sub-polygons
+ //
+ var myMonoSplitter = new PNLTRI.MonoSplitter( myPolygonData );
+ myMonoSplitter.monotonate_trapezoids();
+ //
+ // triangulates all uni-y-monotone sub-polygons
+ //
+ myTriangulator = new PNLTRI.MonoTriangulator( myPolygonData );
+ myTriangulator.triangulate_all_polygons();
+ }
+ //
+ this.lastPolyData = myPolygonData;
+ return myPolygonData.getTriangles(); // copy of triangle list
+ }
+
+
+};