pnltri
Version:
Polygon near-linear time triangulation (based on Seidel's algorithm) with ear clip for small cases. Handles degenerate cases of colinear edges and vertices touching edges.
1,494 lines (1,256 loc) • 71.8 kB
JavaScript
// pnltri.js / raw.github.com/jahting/pnltri.js/master/LICENSE
var self = self || {};
/**
* @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<j; i++) {
this.addPolygonChain( inPolygonChainList[i] );
}
}
};
PNLTRI.PolygonData.prototype = {
constructor: PNLTRI.PolygonData,
/* Accessors */
nbVertices: function () {
return this.vertices.length;
},
getSegments: function () {
return this.segments;
},
getFirstSegment: function () {
return this.segments[0];
},
getMonoSubPolys: function () {
return this.monoSubPolyChains;
},
getTriangles: function () {
return this.triangles.concat();
},
nbPolyChains: function () {
return this.idNextPolyChain;
},
// 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 () {
return this.PolyLeftArr.concat();
},
set_PolyLeft_wrong: function ( inChainId ) {
this.PolyLeftArr[inChainId] = false;
},
/* Helper */
// checks winding order by calculating the area of the polygon
isClockWise: function ( inStartSeg ) {
var cursor = inStartSeg, doubleArea = 0;
do {
doubleArea += ( cursor.vFrom.x - cursor.vTo.x ) * ( cursor.vFrom.y + cursor.vTo.y );
cursor = cursor.snext;
} while ( cursor != inStartSeg );
return ( doubleArea < 0 );
},
/* Operations */
appendVertexEntry: function ( inVertexX, inVertexY ) { // private
var vertex = {
id: this.vertices.length, // vertex id, representing input sequence
x: inVertexX, // coordinates
y: inVertexY,
};
this.vertices.push( vertex );
return vertex;
},
createSegmentEntry: function ( inVertexFrom, inVertexTo ) { // private
return {
chainId: this.idNextPolyChain,
// end points of segment
vFrom: inVertexFrom, // -> 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,w>
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 =