From fa66eda7cafdc9fd10303c02c2a96785047b6298 Mon Sep 17 00:00:00 2001 From: Jan Marsch Date: Mon, 4 Apr 2016 00:32:34 +0200 Subject: [PATCH] bundle task added --- Gruntfile.js | 26 +- dist/Triangulate.bundle.js | 1247 +++++++++++++++++++++++++++ dist/Triangulate.debug.js | 34 +- dist/Triangulate.js | 2 +- lib/earcut.custom.js | 647 ++++++++++++++ src/{GeoJSON.js => TTriangulate.js} | 32 +- src/{triangulate.js => split.js} | 2 +- 7 files changed, 1948 insertions(+), 42 deletions(-) create mode 100644 dist/Triangulate.bundle.js create mode 100644 lib/earcut.custom.js rename src/{GeoJSON.js => TTriangulate.js} (86%) rename src/{triangulate.js => split.js} (99%) diff --git a/Gruntfile.js b/Gruntfile.js index ef1ed06..5932bc0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -9,13 +9,15 @@ module.exports = function(grunt) { globals: {} }, all: [ - 'src/GeoJSON.js', - 'src/triangulate.js', - 'src/vector.js' + 'src/vec2.js', + 'src/vec3.js', + 'src/split.js', + 'src/Triangulate.js' ] }, concat: { + default: { options: { separator: '\n', @@ -34,10 +36,24 @@ module.exports = function(grunt) { 'node_modules/earcut/src/earcut.js', 'src/vec2.js', 'src/vec3.js', - 'src/triangulate.js', - 'src/GeoJSON.js' + 'src/split.js', + 'src/Triangulate.js' ], dest: 'dist/<%=pkg.name%>.debug.js' + }, + + 'bundle': { + options: { + separator: '\n' + }, + src: [ + 'lib/earcut.custom.js', + 'src/vec2.js', + 'src/vec3.js', + 'src/split.js', + 'src/Triangulate.js' + ], + dest: 'dist/<%=pkg.name%>.bundle.js' } }, diff --git a/dist/Triangulate.bundle.js b/dist/Triangulate.bundle.js new file mode 100644 index 0000000..d5e1164 --- /dev/null +++ b/dist/Triangulate.bundle.js @@ -0,0 +1,1247 @@ + +var earcut = (function() { + + function earcut(data, holeIndices, dim) { + + dim = dim || 2; + + var hasHoles = holeIndices && holeIndices.length, + outerLen = hasHoles ? holeIndices[0]*dim : data.length, + outerNode = linkedList(data, 0, outerLen, dim, true), + triangles = []; + + if (!outerNode) return triangles; + + var minX, minY, maxX, maxY, x, y, size; + + if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); + + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + if (data.length>80*dim) { + minX = maxX = data[0]; + minY = maxY = data[1]; + + for (var i = dim; imaxX) maxX = x; + if (y>maxY) maxY = y; + } + + // minX, minY and size are later used to transform coords into integers for z-order calculation + size = Math.max(maxX - minX, maxY - minY); + } + + earcutLinked(outerNode, triangles, dim, minX, minY, size); + + return triangles; + } + +// create a circular doubly linked list from polygon points in the specified winding order + function linkedList(data, start, end, dim, clockwise) { + var i, last; + + if (clockwise === (signedArea(data, start, end, dim)>0)) { + for (i = start; i=start; i -= dim) last = insertNode(i, data[i], data[i + 1], last); + } + + if (last && equals(last, last.next)) { + removeNode(last); + last = last.next; + } + + return last; + } + +// eliminate colinear or duplicate points + function filterPoints(start, end) { + if (!start) return start; + if (!end) end = start; + + var p = start, + again; + do { + again = false; + + if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { + removeNode(p); + p = end = p.prev; + if (p === p.next) return null; + again = true; + + } else { + p = p.next; + } + } while (again || p !== end); + + return end; + } + +// main ear slicing loop which triangulates a polygon (given as a linked list) + function earcutLinked(ear, triangles, dim, minX, minY, size, pass) { + if (!ear) return; + + // interlink polygon nodes in z-order + if (!pass && size) indexCurve(ear, minX, minY, size); + + var stop = ear, + prev, next; + + // iterate through ears, slicing them one by one + while (ear.prev !== ear.next) { + prev = ear.prev; + next = ear.next; + + if (size ? isEarHashed(ear, minX, minY, size) : isEar(ear)) { + // cut off the triangle + triangles.push(prev.i/dim); + triangles.push(ear.i/dim); + triangles.push(next.i/dim); + + removeNode(ear); + + // skipping the next vertice leads to less sliver triangles + ear = next.next; + stop = next.next; + + continue; + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + if (ear === stop) { + // try filtering points and slicing again + if (!pass) { + earcutLinked(filterPoints(ear), triangles, dim, minX, minY, size, 1); + + // if this didn't work, try curing all small self-intersections locally + } else if (pass === 1) { + ear = cureLocalIntersections(ear, triangles, dim); + earcutLinked(ear, triangles, dim, minX, minY, size, 2); + + // as a last resort, try splitting the remaining polygon into two + } else if (pass === 2) { + splitEarcut(ear, triangles, dim, minX, minY, size); + } + + break; + } + } + } + +// check whether a polygon node forms a valid ear with adjacent nodes + function isEar(ear) { + var a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c)>=0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + var p = ear.next.next; + + while (p !== ear.prev) { + if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && + area(p.prev, p, p.next)>=0) return false; + p = p.next; + } + + return true; + } + + function isEarHashed(ear, minX, minY, size) { + var a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c)>=0) return false; // reflex, can't be an ear + + // triangle bbox; min & max are calculated like this for speed + var minTX = a.xb.x ? (a.x>c.x ? a.x : c.x) : (b.x>c.x ? b.x : c.x), + maxTY = a.y>b.y ? (a.y>c.y ? a.y : c.y) : (b.y>c.y ? b.y : c.y); + + // z-order range for the current triangle bbox; + var minZ = zOrder(minTX, minTY, minX, minY, size), + maxZ = zOrder(maxTX, maxTY, minX, minY, size); + + // first look for points inside the triangle in increasing z-order + var p = ear.nextZ; + + while (p && p.z<=maxZ) { + if (p !== ear.prev && p !== ear.next && + pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && + area(p.prev, p, p.next)>=0) return false; + p = p.nextZ; + } + + // then look for points in decreasing z-order + p = ear.prevZ; + + while (p && p.z>=minZ) { + if (p !== ear.prev && p !== ear.next && + pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && + area(p.prev, p, p.next)>=0) return false; + p = p.prevZ; + } + + return true; + } + +// go through all polygon nodes and cure small local self-intersections + function cureLocalIntersections(start, triangles, dim) { + var p = start; + do { + var a = p.prev, + b = p.next.next; + + if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { + + triangles.push(a.i/dim); + triangles.push(p.i/dim); + triangles.push(b.i/dim); + + // remove two nodes involved + removeNode(p); + removeNode(p.next); + + p = start = b; + } + p = p.next; + } while (p !== start); + + return p; + } + +// try splitting polygon into two and triangulate them independently + function splitEarcut(start, triangles, dim, minX, minY, size) { + // look for a valid diagonal that divides the polygon into two + var a = start; + do { + var b = a.next.next; + while (b !== a.prev) { + if (a.i !== b.i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + var c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a.next); + c = filterPoints(c, c.next); + + // run earcut on each half + earcutLinked(a, triangles, dim, minX, minY, size); + earcutLinked(c, triangles, dim, minX, minY, size); + return; + } + b = b.next; + } + a = a.next; + } while (a !== start); + } + +// link every hole into the outer loop, producing a single-ring polygon without holes + function eliminateHoles(data, holeIndices, outerNode, dim) { + var queue = [], + i, len, start, end, list; + + for (i = 0, len = holeIndices.length; i=p.next.y) { + var x = p.x + (hy - p.y)*(p.next.x - p.x)/(p.next.y - p.y); + if (x<=hx && x>qx) { + qx = x; + if (x === hx) { + if (hy === p.y) return p; + if (hy === p.next.y) return p.next; + } + m = p.x=p.x && p.x>=mx && + pointInTriangle(hym.x)) && locallyInside(p, hole)) { + m = p; + tanMin = tan; + } + } + + p = p.next; + } + + return m; + } + +// interlink polygon nodes in z-order + function indexCurve(start, minX, minY, size) { + var p = start; + do { + if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, size); + p.prevZ = p.prev; + p.nextZ = p.next; + p = p.next; + } while (p !== start); + + p.prevZ.nextZ = null; + p.prevZ = null; + + sortLinked(p); + } + +// Simon Tatham's linked list merge sort algorithm +// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html + function sortLinked(list) { + var i, p, q, e, tail, numMerges, pSize, qSize, + inSize = 1; + + do { + p = list; + list = null; + tail = null; + numMerges = 0; + + while (p) { + numMerges++; + q = p; + pSize = 0; + for (i = 0; i0 || (qSize>0 && q)) { + + if (pSize === 0) { + e = q; + q = q.nextZ; + qSize--; + } else if (qSize === 0 || !q) { + e = p; + p = p.nextZ; + pSize--; + } else if (p.z<=q.z) { + e = p; + p = p.nextZ; + pSize--; + } else { + e = q; + q = q.nextZ; + qSize--; + } + + if (tail) tail.nextZ = e; + else list = e; + + e.prevZ = tail; + tail = e; + } + + p = q; + } + + tail.nextZ = null; + inSize *= 2; + + } while (numMerges>1); + + return list; + } + +// z-order of a point given coords and size of the data bounding box + function zOrder(x, y, minX, minY, size) { + // coords are transformed into non-negative 15-bit integer range + x = 32767*(x - minX)/size; + y = 32767*(y - minY)/size; + + x = (x | (x<<8)) & 0x00FF00FF; + x = (x | (x<<4)) & 0x0F0F0F0F; + x = (x | (x<<2)) & 0x33333333; + x = (x | (x<<1)) & 0x55555555; + + y = (y | (y<<8)) & 0x00FF00FF; + y = (y | (y<<4)) & 0x0F0F0F0F; + y = (y | (y<<2)) & 0x33333333; + y = (y | (y<<1)) & 0x55555555; + + return x | (y<<1); + } + +// find the leftmost node of a polygon ring + function getLeftmost(start) { + var p = start, + leftmost = start; + do { + if (p.x=0 && + (ax - px)*(by - py) - (bx - px)*(ay - py)>=0 && + (bx - px)*(cy - py) - (cx - px)*(by - py)>=0; + } + +// check if a diagonal between two polygon nodes is valid (lies in polygon interior) + function isValidDiagonal(a, b) { + return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && + locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b); + } + +// signed area of a triangle + function area(p, q, r) { + return (q.y - p.y)*(r.x - q.x) - (q.x - p.x)*(r.y - q.y); + } + +// check if two points are equal + function equals(p1, p2) { + return p1.x === p2.x && p1.y === p2.y; + } + +// check if two segments intersect + function intersects(p1, q1, p2, q2) { + if ((equals(p1, q1) && equals(p2, q2)) || + (equals(p1, q2) && equals(p2, q1))) return true; + return area(p1, q1, p2)>0 !== area(p1, q1, q2)>0 && + area(p2, q2, p1)>0 !== area(p2, q2, q1)>0; + } + +// check if a polygon diagonal intersects any polygon segments + function intersectsPolygon(a, b) { + var p = a; + do { + if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && + intersects(p, p.next, a, b)) return true; + p = p.next; + } while (p !== a); + + return false; + } + +// check if a polygon diagonal is locally inside the polygon + function locallyInside(a, b) { + return area(a.prev, a, a.next)<0 ? + area(a, b, a.next)>=0 && area(a, a.prev, b)>=0 : + area(a, b, a.prev)<0 || area(a, a.next, b)<0; + } + +// check if the middle point of a polygon diagonal is inside the polygon + function middleInside(a, b) { + var p = a, + inside = false, + px = (a.x + b.x)/2, + py = (a.y + b.y)/2; + do { + if (((p.y>py) !== (p.next.y>py)) && (px<(p.next.x - p.x)*(py - p.y)/(p.next.y - p.y) + p.x)) + inside = !inside; + p = p.next; + } while (p !== a); + + return inside; + } + +// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; +// if one belongs to the outer ring and another to a hole, it merges it into a single ring + function splitPolygon(a, b) { + var a2 = new Node(a.i, a.x, a.y), + b2 = new Node(b.i, b.x, b.y), + an = a.next, + bp = b.prev; + + a.next = b; + b.prev = a; + + a2.next = an; + an.prev = a2; + + b2.next = a2; + a2.prev = b2; + + bp.next = b2; + b2.prev = bp; + + return b2; + } + +// create a node and optionally link it with previous one (in a circular doubly linked list) + function insertNode(i, x, y, last) { + var p = new Node(i, x, y); + + if (!last) { + p.prev = p; + p.next = p; + + } else { + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; + } + return p; + } + + function removeNode(p) { + p.next.prev = p.prev; + p.prev.next = p.next; + + if (p.prevZ) p.prevZ.nextZ = p.nextZ; + if (p.nextZ) p.nextZ.prevZ = p.prevZ; + } + + function Node(i, x, y) { + // vertice index in coordinates array + this.i = i; + + // vertex coordinates + this.x = x; + this.y = y; + + // previous and next vertice nodes in a polygon ring + this.prev = null; + this.next = null; + + // z-order curve value + this.z = null; + + // previous and next nodes in z-order + this.prevZ = null; + this.nextZ = null; + + // indicates whether this is a steiner point + this.steiner = false; + } + +// return a percentage difference between the polygon area and its triangulation area; +// used to verify correctness of triangulation + earcut.deviation = function(data, holeIndices, dim, triangles) { + var hasHoles = holeIndices && holeIndices.length; + var outerLen = hasHoles ? holeIndices[0]*dim : data.length; + + var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim)); + if (hasHoles) { + for (var i = 0, len = holeIndices.length; i0) { + holeIndex += data[i - 1].length; + result.holes.push(holeIndex); + } + } + return result; + }; + + return earcut; + +}(this)); + + +var vec2 = { + len: function(a) { + return Math.sqrt(a[0]*a[0] + a[1]*a[1]); + }, + + sub: function(a, b) { + return [a[0]-b[0], a[1]-b[1]]; + } +}; + + +var vec3 = { + len: function(a) { + return Math.sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2]); + }, + + sub: function(a, b) { + return [a[0]-b[0], a[1]-b[1], a[2]-b[2]]; + }, + + unit: function(a) { + var l = this.len(a); + return [a[0]/l, a[1]/l, a[2]/l]; + }, + + normal: function(a, b, c) { + var d1 = this.sub(a, b); + var d2 = this.sub(b, c); + // normalized cross product of d1 and d2 + return this.unit([ + d1[1]*d2[2] - d1[2]*d2[1], + d1[2]*d2[0] - d1[0]*d2[2], + d1[0]*d2[1] - d1[1]*d2[0] + ]); + } +}; + + +var split = { + + NUM_Y_SEGMENTS: 24, + NUM_X_SEGMENTS: 32, + + //function isVertical(a, b, c) { + // return Math.abs(normal(a, b, c)[2]) < 1/5000; + //} + + quad: function(data, a, b, c, d, color) { + this.triangle(data, a, b, c, color); + this.triangle(data, c, d, a, color); + }, + + triangle: function(data, a, b, c, color) { + var n = vec3.normal(a, b, c); + [].push.apply(data.vertices, [].concat(a, c, b)); + [].push.apply(data.normals, [].concat(n, n, n)); + [].push.apply(data.colors, [].concat(color, color, color)); + data.texCoords.push(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + }, + + circle: function(data, center, radius, Z, color) { + Z = Z || 0; + var u, v; + for (var i = 0; i < this.NUM_X_SEGMENTS; i++) { + u = i/this.NUM_X_SEGMENTS; + v = (i+1)/this.NUM_X_SEGMENTS; + this.triangle( + data, + [ center[0] + radius * Math.sin(u*Math.PI*2), center[1] + radius * Math.cos(u*Math.PI*2), Z ], + [ center[0], center[1], Z ], + [ center[0] + radius * Math.sin(v*Math.PI*2), center[1] + radius * Math.cos(v*Math.PI*2), Z ], + color + ); + } + }, + + polygon: function(data, rings, Z, color) { + Z = Z || 0; + // flatten data + var + inVertices = [], inHoleIndex = [], + index = 0, + i, il; + for (i = 0, il = rings.length; i < il; i++) { + for (var j = 0; j < rings[i].length; j++) { + inVertices.push(rings[i][j][0], rings[i][j][1]); + } + if (i) { + index += rings[i - 1].length; + inHoleIndex.push(index); + } + } + + var vertices = earcut(inVertices, inHoleIndex, 2); + + for (i = 0, il = vertices.length-2; i < il; i+=3) { + this.triangle( + data, + [ inVertices[ vertices[i ]*2 ], inVertices[ vertices[i ]*2+1 ], Z ], + [ inVertices[ vertices[i+1]*2 ], inVertices[ vertices[i+1]*2+1 ], Z ], + [ inVertices[ vertices[i+2]*2 ], inVertices[ vertices[i+2]*2+1 ], Z ], + color + ); + } + }, + + //polygon3d: function(data, rings, color) { + // var ring = rings[0]; + // var ringLength = ring.length; + // var vertices, t, tl; + // +//// { r:255, g:0, b:0 } +// + // if (ringLength <= 4) { // 3: a triangle + // this.triangle( + // data, + // ring[0], + // ring[2], + // ring[1], color + // ); + // + // if (ringLength === 4) { // 4: a quad (2 triangles) + // this.triangle( + // data, + // ring[0], + // ring[3], + // ring[2], color + // ); + // } +// return; + // } + // + // if (isVertical(ring[0], ring[1], ring[2])) { + // for (var i = 0, il = rings[0].length; i < il; i++) { + // rings[0][i] = [ + // rings[0][i][2], + // rings[0][i][1], + // rings[0][i][0] + // ]; + // } + // + // vertices = earcut(rings); + // for (t = 0, tl = vertices.length-2; t < tl; t+=3) { + // this.triangle( + // data, + // [ vertices[t ][2], vertices[t ][1], vertices[t ][0] ], + // [ vertices[t+1][2], vertices[t+1][1], vertices[t+1][0] ], + // [ vertices[t+2][2], vertices[t+2][1], vertices[t+2][0] ], color + // ); + // } +// return; + // } + // + // vertices = earcut(rings); + // for (t = 0, tl = vertices.length-2; t < tl; t+=3) { + // this.triangle( + // data, + // [ vertices[t ][0], vertices[t ][1], vertices[t ][2] ], + // [ vertices[t+1][0], vertices[t+1][1], vertices[t+1][2] ], + // [ vertices[t+2][0], vertices[t+2][1], vertices[t+2][2] ], color + // ); + // } + //}, + + cube: function(data, sizeX, sizeY, sizeZ, X, Y, Z, color) { + X = X || 0; + Y = Y || 0; + Z = Z || 0; + + var a = [X, Y, Z]; + var b = [X+sizeX, Y, Z]; + var c = [X+sizeX, Y+sizeY, Z]; + var d = [X, Y+sizeY, Z]; + + var A = [X, Y, Z+sizeZ]; + var B = [X+sizeX, Y, Z+sizeZ]; + var C = [X+sizeX, Y+sizeY, Z+sizeZ]; + var D = [X, Y+sizeY, Z+sizeZ]; + + this.quad(data, b, a, d, c, color); + this.quad(data, A, B, C, D, color); + this.quad(data, a, b, B, A, color); + this.quad(data, b, c, C, B, color); + this.quad(data, c, d, D, C, color); + this.quad(data, d, a, A, D, color); + }, + + cylinder: function(data, center, radius1, radius2, height, Z, color) { + Z = Z || 0; + var + currAngle, nextAngle, + currSin, currCos, + nextSin, nextCos, + num = this.NUM_X_SEGMENTS, + doublePI = Math.PI*2; + + for (var i = 0; i < num; i++) { + currAngle = ( i /num) * doublePI; + nextAngle = ((i+1)/num) * doublePI; + + currSin = Math.sin(currAngle); + currCos = Math.cos(currAngle); + + nextSin = Math.sin(nextAngle); + nextCos = Math.cos(nextAngle); + + this.triangle( + data, + [ center[0] + radius1*currSin, center[1] + radius1*currCos, Z ], + [ center[0] + radius2*nextSin, center[1] + radius2*nextCos, Z+height ], + [ center[0] + radius1*nextSin, center[1] + radius1*nextCos, Z ], + color + ); + + if (radius2 !== 0) { + this.triangle( + data, + [ center[0] + radius2*currSin, center[1] + radius2*currCos, Z+height ], + [ center[0] + radius2*nextSin, center[1] + radius2*nextCos, Z+height ], + [ center[0] + radius1*currSin, center[1] + radius1*currCos, Z ], + color + ); + } + } + }, + + dome: function(data, center, radius, height, Z, color) { + Z = Z || 0; + var + currAngle, nextAngle, + currSin, currCos, + nextSin, nextCos, + currRadius, nextRadius, + nextHeight, nextZ, + num = this.NUM_Y_SEGMENTS/2, + halfPI = Math.PI/2; + + for (var i = 0; i < num; i++) { + currAngle = ( i /num) * halfPI - halfPI; + nextAngle = ((i+1)/num) * halfPI - halfPI; + + currSin = Math.sin(currAngle); + currCos = Math.cos(currAngle); + + nextSin = Math.sin(nextAngle); + nextCos = Math.cos(nextAngle); + + currRadius = currCos*radius; + nextRadius = nextCos*radius; + + nextHeight = (nextSin-currSin)*height; + nextZ = Z - nextSin*height; + + this.cylinder(data, center, nextRadius, currRadius, nextHeight, nextZ, color); + } + }, + + // TODO + sphere: function(data, center, radius, height, Z, color) { + Z = Z || 0; + return this.cylinder(data, center, radius, radius, height, Z, color); + }, + + pyramid: function(data, polygon, center, height, Z, color) { + Z = Z || 0; + polygon = polygon[0]; + for (var i = 0, il = polygon.length-1; i < il; i++) { + this.triangle( + data, + [ polygon[i ][0], polygon[i ][1], Z ], + [ polygon[i+1][0], polygon[i+1][1], Z ], + [ center[0], center[1], Z+height ], + color + ); + } + }, + + extrusion: function(data, polygon, height, Z, color, tx) { + Z = Z || 0; + var + ring, last, a, b, + L, + v0, v1, v2, v3, n, + tx1, tx2, + ty1 = tx[2]*height, ty2 = tx[3]*height; + + for (var i = 0, il = polygon.length; i < il; i++) { + ring = polygon[i]; + last = ring.length-1; + + if (ring[0][0] !== ring[last][0] || ring[0][1] !== ring[last][1]) { + ring.push(ring[0]); + last++; + } + + for (var r = 0; r < last; r++) { + a = ring[r]; + b = ring[r+1]; + L = vec2.len(vec2.sub(a, b)); + + tx1 = (tx[0]*L) <<0; + tx2 = (tx[1]*L) <<0; + + v0 = [ a[0], a[1], Z]; + v1 = [ b[0], b[1], Z]; + v2 = [ b[0], b[1], Z+height]; + v3 = [ a[0], a[1], Z+height]; + + n = vec3.normal(v0, v1, v2); + [].push.apply(data.vertices, [].concat(v0, v2, v1, v0, v3, v2)); + [].push.apply(data.normals, [].concat(n, n, n, n, n, n)); + [].push.apply(data.colors, [].concat(color, color, color, color, color, color)); + + data.texCoords.push( + tx1, ty2, + tx2, ty1, + tx2, ty2, + tx1, ty2, + tx1, ty1, + tx2, ty1 + ); + } + } + } +}; + + +var Triangulate = {}; + +(function() { + + //var EARTH_RADIUS_IN_METERS = 6378137; + //var EARTH_CIRCUMFERENCE_IN_METERS = EARTH_RADIUS_IN_METERS * Math.PI * 2; + //var METERS_PER_DEGREE_LATITUDE = EARTH_CIRCUMFERENCE_IN_METERS / 360; + + var METERS_PER_DEGREE_LATITUDE = 6378137 * Math.PI / 180; + + var DEFAULT_HEIGHT = 10; + var DEFAULT_ROOF_HEIGHT = 3; + var DEFAULT_COLOR = 'rgb(220, 210, 200)'; + + // number of windows per horizontal meter of building wall + var WINDOWS_PER_METER = 0.5; + var METERS_PER_LEVEL = 3; + + var MATERIAL_COLORS = { + brick: '#cc7755', + bronze: '#ffeecc', + canvas: '#fff8f0', + concrete: '#999999', + copper: '#a0e0d0', + glass: '#e8f8f8', + gold: '#ffcc00', + plants: '#009933', + metal: '#aaaaaa', + panel: '#fff8f0', + plaster: '#999999', + roof_tiles: '#f08060', + silver: '#cccccc', + slate: '#666666', + stone: '#996666', + tar_paper: '#333333', + wood: '#deb887' + }; + + var BASE_MATERIALS = { + asphalt: 'tar_paper', + bitumen: 'tar_paper', + block: 'stone', + bricks: 'brick', + glas: 'glass', + glassfront: 'glass', + grass: 'plants', + masonry: 'stone', + granite: 'stone', + panels: 'panel', + paving_stones: 'stone', + plastered: 'plaster', + rooftiles: 'roof_tiles', + roofingfelt: 'tar_paper', + sandstone: 'stone', + sheet: 'canvas', + sheets: 'canvas', + shingle: 'tar_paper', + shingles: 'tar_paper', + slates: 'slate', + steel: 'metal', + tar: 'tar_paper', + tent: 'canvas', + thatch: 'plants', + tile: 'roof_tiles', + tiles: 'roof_tiles' + // cardboard + // eternit + // limestone + // straw + }; + + Triangulate.getPosition = function(geometry) { + var coordinates = geometry.coordinates; + switch (geometry.type) { + case 'Point': + return coordinates; + + case 'MultiPoint': + case 'LineString': + return coordinates[0]; + + case 'MultiLineString': + case 'Polygon': + return coordinates[0][0]; + + case 'MultiPolygon': + return coordinates[0][0][0]; + } + }; + + Triangulate.split = function(res, id, feature, position, color) { + var geometries = flattenGeometry(feature.geometry); + for (var i = 0, il = geometries.length; ic&&(c+=1),c>1&&(c-=1),1/6>c?a+6*(b-a)*c:.5>c?b:2/3>c?a+(b-a)*(2/3-c)*6:a}function c(a,b){return Math.min(b,Math.max(0,a||0))}function d(a,b,c){c=c||2;var d=b&&b.length,f=d?b[0]*c:a.length,h=e(a,0,f,c,!0),i=[];if(!h)return i;var j,k,m,n,o,p,q;if(d&&(h=l(a,b,h,c)),a.length>80*c){j=m=a[0],k=n=a[1];for(var r=c;f>r;r+=c)o=a[r],p=a[r+1],j>o&&(j=o),k>p&&(k=p),o>m&&(m=o),p>n&&(n=p);q=Math.max(m-j,n-k)}return g(h,i,c,j,k,q),i}function e(a,b,c,d,e){var f,g;if(e===F(a,b,c,d)>0)for(f=b;c>f;f+=d)g=C(f,a[f],a[f+1],g);else for(f=c-d;f>=b;f-=d)g=C(f,a[f],a[f+1],g);return g&&w(g,g.next)&&(D(g),g=g.next),g}function f(a,b){if(!a)return a;b||(b=a);var c,d=a;do if(c=!1,d.steiner||!w(d,d.next)&&0!==v(d.prev,d,d.next))d=d.next;else{if(D(d),d=b=d.prev,d===d.next)return null;c=!0}while(c||d!==b);return b}function g(a,b,c,d,e,l,m){if(a){!m&&l&&p(a,d,e,l);for(var n,o,q=a;a.prev!==a.next;)if(n=a.prev,o=a.next,l?i(a,d,e,l):h(a))b.push(n.i/c),b.push(a.i/c),b.push(o.i/c),D(a),a=o.next,q=o.next;else if(a=o,a===q){m?1===m?(a=j(a,b,c),g(a,b,c,d,e,l,2)):2===m&&k(a,b,c,d,e,l):g(f(a),b,c,d,e,l,1);break}}}function h(a){var b=a.prev,c=a,d=a.next;if(v(b,c,d)>=0)return!1;for(var e=a.next.next;e!==a.prev;){if(t(b.x,b.y,c.x,c.y,d.x,d.y,e.x,e.y)&&v(e.prev,e,e.next)>=0)return!1;e=e.next}return!0}function i(a,b,c,d){var e=a.prev,f=a,g=a.next;if(v(e,f,g)>=0)return!1;for(var h=e.xf.x?e.x>g.x?e.x:g.x:f.x>g.x?f.x:g.x,k=e.y>f.y?e.y>g.y?e.y:g.y:f.y>g.y?f.y:g.y,l=r(h,i,b,c,d),m=r(j,k,b,c,d),n=a.nextZ;n&&n.z<=m;){if(n!==a.prev&&n!==a.next&&t(e.x,e.y,f.x,f.y,g.x,g.y,n.x,n.y)&&v(n.prev,n,n.next)>=0)return!1;n=n.nextZ}for(n=a.prevZ;n&&n.z>=l;){if(n!==a.prev&&n!==a.next&&t(e.x,e.y,f.x,f.y,g.x,g.y,n.x,n.y)&&v(n.prev,n,n.next)>=0)return!1;n=n.prevZ}return!0}function j(a,b,c){var d=a;do{var e=d.prev,f=d.next.next;!w(e,f)&&x(e,d,d.next,f)&&z(e,f)&&z(f,e)&&(b.push(e.i/c),b.push(d.i/c),b.push(f.i/c),D(d),D(d.next),d=a=f),d=d.next}while(d!==a);return d}function k(a,b,c,d,e,h){var i=a;do{for(var j=i.next.next;j!==i.prev;){if(i.i!==j.i&&u(i,j)){var k=B(i,j);return i=f(i,i.next),k=f(k,k.next),g(i,b,c,d,e,h),void g(k,b,c,d,e,h)}j=j.next}i=i.next}while(i!==a)}function l(a,b,c,d){var g,h,i,j,k,l=[];for(g=0,h=b.length;h>g;g++)i=b[g]*d,j=h-1>g?b[g+1]*d:a.length,k=e(a,i,j,d,!1),k===k.next&&(k.steiner=!0),l.push(s(k));for(l.sort(m),g=0;g=d.next.y){var h=d.x+(f-d.y)*(d.next.x-d.x)/(d.next.y-d.y);if(e>=h&&h>g){if(g=h,h===e){if(f===d.y)return d;if(f===d.next.y)return d.next}c=d.x=d.x&&d.x>=k&&t(l>f?e:g,f,k,l,l>f?g:e,f,d.x,d.y)&&(i=Math.abs(f-d.y)/(e-d.x),(m>i||i===m&&d.x>c.x)&&z(d,a)&&(c=d,m=i)),d=d.next;return c}function p(a,b,c,d){var e=a;do null===e.z&&(e.z=r(e.x,e.y,b,c,d)),e.prevZ=e.prev,e.nextZ=e.next,e=e.next;while(e!==a);e.prevZ.nextZ=null,e.prevZ=null,q(e)}function q(a){var b,c,d,e,f,g,h,i,j=1;do{for(c=a,a=null,f=null,g=0;c;){for(g++,d=c,h=0,b=0;j>b&&(h++,d=d.nextZ,d);b++);for(i=j;h>0||i>0&&d;)0===h?(e=d,d=d.nextZ,i--):0!==i&&d?c.z<=d.z?(e=c,c=c.nextZ,h--):(e=d,d=d.nextZ,i--):(e=c,c=c.nextZ,h--),f?f.nextZ=e:a=e,e.prevZ=f,f=e;c=d}f.nextZ=null,j*=2}while(g>1);return a}function r(a,b,c,d,e){return a=32767*(a-c)/e,b=32767*(b-d)/e,a=16711935&(a|a<<8),a=252645135&(a|a<<4),a=858993459&(a|a<<2),a=1431655765&(a|a<<1),b=16711935&(b|b<<8),b=252645135&(b|b<<4),b=858993459&(b|b<<2),b=1431655765&(b|b<<1),a|b<<1}function s(a){var b=a,c=a;do b.x=0&&(a-g)*(d-h)-(c-g)*(b-h)>=0&&(c-g)*(f-h)-(e-g)*(d-h)>=0}function u(a,b){return a.next.i!==b.i&&a.prev.i!==b.i&&!y(a,b)&&z(a,b)&&z(b,a)&&A(a,b)}function v(a,b,c){return(b.y-a.y)*(c.x-b.x)-(b.x-a.x)*(c.y-b.y)}function w(a,b){return a.x===b.x&&a.y===b.y}function x(a,b,c,d){return w(a,b)&&w(c,d)||w(a,d)&&w(c,b)?!0:v(a,b,c)>0!=v(a,b,d)>0&&v(c,d,a)>0!=v(c,d,b)>0}function y(a,b){var c=a;do{if(c.i!==a.i&&c.next.i!==a.i&&c.i!==b.i&&c.next.i!==b.i&&x(c,c.next,a,b))return!0;c=c.next}while(c!==a);return!1}function z(a,b){return v(a.prev,a,a.next)<0?v(a,b,a.next)>=0&&v(a,a.prev,b)>=0:v(a,b,a.prev)<0||v(a,a.next,b)<0}function A(a,b){var c=a,d=!1,e=(a.x+b.x)/2,f=(a.y+b.y)/2;do c.y>f!=c.next.y>f&&e<(c.next.x-c.x)*(f-c.y)/(c.next.y-c.y)+c.x&&(d=!d),c=c.next;while(c!==a);return d}function B(a,b){var c=new E(a.i,a.x,a.y),d=new E(b.i,b.x,b.y),e=a.next,f=b.prev;return a.next=b,b.prev=a,c.next=e,e.prev=c,d.next=c,c.prev=d,f.next=d,d.prev=f,d}function C(a,b,c,d){var e=new E(a,b,c);return d?(e.next=d.next,e.prev=d,d.next.prev=e,d.next=e):(e.prev=e,e.next=e),e}function D(a){a.next.prev=a.prev,a.prev.next=a.next,a.prevZ&&(a.prevZ.nextZ=a.nextZ),a.nextZ&&(a.nextZ.prevZ=a.prevZ)}function E(a,b,c){this.i=a,this.x=b,this.y=c,this.prev=null,this.next=null,this.z=null,this.prevZ=null,this.nextZ=null,this.steiner=!1}function F(a,b,c,d){for(var e=0,f=b,g=c-d;c>f;f+=d)e+=(a[g]-a[f])*(a[f+1]+a[g+1]),g=f;return e}var G={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},H=function(a){if(a=a||"","object"==typeof a){var b=a;this.R=c(b.r,max),this.G=c(b.g,max),this.B=c(b.b,max),this.A=void 0!==b.a?c(b.a,1):1}else if("string"==typeof a){a=a.toLowerCase(),a=G[a]||a;var d;(d=a.match(/^#?(\w{2})(\w{2})(\w{2})$/))?(this.R=parseInt(d[1],16)/255,this.G=parseInt(d[2],16)/255,this.B=parseInt(d[3],16)/255,this.A=1):(d=a.match(/rgba?\((\d+)\D+(\d+)\D+(\d+)(\D+([\d.]+))?\)/))&&(this.R=parseInt(d[1],10)/255,this.G=parseInt(d[2],10)/255,this.B=parseInt(d[3],10)/255,this.A=d[4]?parseFloat(d[5]):1)}};H.prototype={toHSL:function(){var a,b,c=Math.max(this.R,this.G,this.B),d=Math.min(this.R,this.G,this.B),e=(c+d)/2,f=c-d;if(f){switch(b=e>.5?f/(2-c-d):f/(c+d),c){case this.R:a=(this.G-this.B)/f+(this.Gh;h++){var j=b[h]*c,k=i-1>h?b[h+1]*c:a.length;g-=Math.abs(F(a,j,k,c))}var l=0;for(h=0;hg;g++)c.vertices.push(a[e][f][g]);e>0&&(d+=a[e-1].length,c.holes.push(d))}return c};var I={len:function(a){return Math.sqrt(a[0]*a[0]+a[1]*a[1])},sub:function(a,b){return[a[0]-b[0],a[1]-b[1]]}},J={len:function(a){return Math.sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2])},sub:function(a,b){return[a[0]-b[0],a[1]-b[1],a[2]-b[2]]},unit:function(a){var b=this.len(a);return[a[0]/b,a[1]/b,a[2]/b]},normal:function(a,b,c){var d=this.sub(a,b),e=this.sub(b,c);return this.unit([d[1]*e[2]-d[2]*e[1],d[2]*e[0]-d[0]*e[2],d[0]*e[1]-d[1]*e[0]])}},K={NUM_Y_SEGMENTS:24,NUM_X_SEGMENTS:32,quad:function(a,b,c,d,e,f){this.triangle(a,b,c,d,f),this.triangle(a,d,e,b,f)},triangle:function(a,b,c,d,e){var f=J.normal(b,c,d);[].push.apply(a.vertices,[].concat(b,d,c)),[].push.apply(a.normals,[].concat(f,f,f)),[].push.apply(a.colors,[].concat(e,e,e)),a.texCoords.push(0,0,0,0,0,0)},circle:function(a,b,c,d,e){d=d||0;for(var f,g,h=0;hf;f++){for(var k=0;kf;f+=3)this.triangle(a,[h[2*l[f]],h[2*l[f]+1],c],[h[2*l[f+1]],h[2*l[f+1]+1],c],[h[2*l[f+2]],h[2*l[f+2]+1],c],e)},cube:function(a,b,c,d,e,f,g,h){e=e||0,f=f||0,g=g||0;var i=[e,f,g],j=[e+b,f,g],k=[e+b,f+c,g],l=[e,f+c,g],m=[e,f,g+d],n=[e+b,f,g+d],o=[e+b,f+c,g+d],p=[e,f+c,g+d];this.quad(a,j,i,l,k,h),this.quad(a,m,n,o,p,h),this.quad(a,i,j,n,m,h),this.quad(a,j,k,o,n,h),this.quad(a,k,l,p,o,h),this.quad(a,l,i,m,p,h)},cylinder:function(a,b,c,d,e,f,g){f=f||0;for(var h,i,j,k,l,m,n=this.NUM_X_SEGMENTS,o=2*Math.PI,p=0;n>p;p++)h=p/n*o,i=(p+1)/n*o,j=Math.sin(h),k=Math.cos(h),l=Math.sin(i),m=Math.cos(i),this.triangle(a,[b[0]+c*j,b[1]+c*k,f],[b[0]+d*l,b[1]+d*m,f+e],[b[0]+c*l,b[1]+c*m,f],g),0!==d&&this.triangle(a,[b[0]+d*j,b[1]+d*k,f+e],[b[0]+d*l,b[1]+d*m,f+e],[b[0]+c*j,b[1]+c*k,f],g)},dome:function(a,b,c,d,e,f){e=e||0;for(var g,h,i,j,k,l,m,n,o,p,q=this.NUM_Y_SEGMENTS/2,r=Math.PI/2,s=0;q>s;s++)g=s/q*r-r,h=(s+1)/q*r-r,i=Math.sin(g),j=Math.cos(g),k=Math.sin(h),l=Math.cos(h),m=j*c,n=l*c,o=(k-i)*d,p=e-k*d,this.cylinder(a,b,n,m,o,p,f)},sphere:function(a,b,c,d,e,f){return e=e||0,this.cylinder(a,b,c,c,d,e,f)},pyramid:function(a,b,c,d,e,f){e=e||0,b=b[0];for(var g=0,h=b.length-1;h>g;g++)this.triangle(a,[b[g][0],b[g][1],e],[b[g+1][0],b[g+1][1],e],[c[0],c[1],e+d],f)},extrusion:function(a,b,c,d,e,f){d=d||0;for(var g,h,i,j,k,l,m,n,o,p,q,r,s=f[2]*c,t=f[3]*c,u=0,v=b.length;v>u;u++){g=b[u],h=g.length-1,g[0][0]===g[h][0]&&g[0][1]===g[h][1]||(g.push(g[0]),h++);for(var w=0;h>w;w++)i=g[w],j=g[w+1],k=I.len(I.sub(i,j)),q=f[0]*k<<0,r=f[1]*k<<0,l=[i[0],i[1],d],m=[j[0],j[1],d],n=[j[0],j[1],d+c],o=[i[0],i[1],d+c],p=J.normal(l,m,n),[].push.apply(a.vertices,[].concat(l,n,m,l,o,n)),[].push.apply(a.normals,[].concat(p,p,p,p,p,p)),[].push.apply(a.colors,[].concat(e,e,e,e,e,e)),a.texCoords.push(q,t,r,s,r,t,q,t,q,s,r,s)}}};if(void 0===L)var L={};!function(){function a(a,f,i,l,m,o){var p=g(l,m),q=h(p[0]),r=(q.maxX-q.minX)/2,s=[q.minX+(q.maxX-q.minX)/2,q.minY+(q.maxY-q.minY)/2],t=i.height||(i.levels?i.levels*n:j),u=i.minHeight||(i.minLevel?i.minLevel*n:0),v=i.roofHeight||k,w=(f/2%2?-1:1)*(f%2?.03:.06),x=d(o||i.roofColor||i.color||e(i.roofMaterial),w),y=d(o||i.wallColor||i.color||e(i.material),w);switch(i.roofShape){case"cone":case"dome":case"onion":case"pyramid":case"pyramidal":t=Math.max(0,t-v);break;default:v=0}b(a,i,p,s,r,t-u,u,y),c(a,i,p,s,r,v,t,x)}function b(a,b,c,d,e,f,g,h){switch(b.shape){case"cylinder":K.cylinder(a,d,e,e,f,g,h);break;case"cone":K.cylinder(a,d,e,0,f,g,h);break;case"dome":K.dome(a,d,e,f||e,g,h);break;case"sphere":K.sphere(a,d,e,f||2*e,g,h);break;case"pyramid":case"pyramidal":K.pyramid(a,c,d,f,g,h);break;case"none":return;default:var i=.2,j=.4;"glass"!==b.material&&(i=0,j=0,b.levels&&(j=parseFloat(b.levels)-parseFloat(b.minLevel||0)<<0)),K.extrusion(a,c,f,g,h,[0,m,i/f,j/f])}}function c(a,b,c,d,e,f,g,h){switch(b.shape){case"cone":case"pyramid":case"pyramidal":return}switch(b.roofShape){case"cone":K.cylinder(a,d,e,0,f,g,h);break;case"dome":case"onion":K.dome(a,d,e,f||e,g,h);break;case"pyramid":case"pyramidal":"cylinder"===b.shape?K.cylinder(a,d,e,0,f,g,h):K.pyramid(a,c,d,f,g,h);break;default:"cylinder"===b.shape?K.circle(a,d,e,g,h):K.polygon(a,c,g,h)}}function d(a,b){var c=new H(a||l).toArray();return[c[0]+b,c[1]+b,c[2]+b]}function e(a){return"string"!=typeof a?null:(a=a.toLowerCase(),"#"===a[0]?a:o[p[a]||a]||null)}function f(a){switch(a.type){case"MultiPolygon":return a.coordinates;case"Polygon":return[a.coordinates];default:return[]}}function g(a,b){var c=i*Math.cos(b[1]/180*Math.PI);return a.map(function(a,d){return 0===d!==isClockWise(a)&&a.reverse(),a.map(function(a){return[(a[0]-b[0])*c,-(a[1]-b[1])*i]})})}function h(a){for(var b=1/0,c=1/0,d=-(1/0),e=-(1/0),f=0;fi;i++)a(b,c,d.properties,h[i],e,g)}}(),"function"==typeof a.define?a.define([],Triangulate):"object"==typeof a.exports?a.exports=Triangulate:a.Triangulate=Triangulate}(this); \ No newline at end of file +!function(a){function b(a,b,c){return 0>c&&(c+=1),c>1&&(c-=1),1/6>c?a+6*(b-a)*c:.5>c?b:2/3>c?a+(b-a)*(2/3-c)*6:a}function c(a,b){return Math.min(b,Math.max(0,a||0))}function d(a,b,c){c=c||2;var d=b&&b.length,f=d?b[0]*c:a.length,h=e(a,0,f,c,!0),i=[];if(!h)return i;var j,k,m,n,o,p,q;if(d&&(h=l(a,b,h,c)),a.length>80*c){j=m=a[0],k=n=a[1];for(var r=c;f>r;r+=c)o=a[r],p=a[r+1],j>o&&(j=o),k>p&&(k=p),o>m&&(m=o),p>n&&(n=p);q=Math.max(m-j,n-k)}return g(h,i,c,j,k,q),i}function e(a,b,c,d,e){var f,g;if(e===F(a,b,c,d)>0)for(f=b;c>f;f+=d)g=C(f,a[f],a[f+1],g);else for(f=c-d;f>=b;f-=d)g=C(f,a[f],a[f+1],g);return g&&w(g,g.next)&&(D(g),g=g.next),g}function f(a,b){if(!a)return a;b||(b=a);var c,d=a;do if(c=!1,d.steiner||!w(d,d.next)&&0!==v(d.prev,d,d.next))d=d.next;else{if(D(d),d=b=d.prev,d===d.next)return null;c=!0}while(c||d!==b);return b}function g(a,b,c,d,e,l,m){if(a){!m&&l&&p(a,d,e,l);for(var n,o,q=a;a.prev!==a.next;)if(n=a.prev,o=a.next,l?i(a,d,e,l):h(a))b.push(n.i/c),b.push(a.i/c),b.push(o.i/c),D(a),a=o.next,q=o.next;else if(a=o,a===q){m?1===m?(a=j(a,b,c),g(a,b,c,d,e,l,2)):2===m&&k(a,b,c,d,e,l):g(f(a),b,c,d,e,l,1);break}}}function h(a){var b=a.prev,c=a,d=a.next;if(v(b,c,d)>=0)return!1;for(var e=a.next.next;e!==a.prev;){if(t(b.x,b.y,c.x,c.y,d.x,d.y,e.x,e.y)&&v(e.prev,e,e.next)>=0)return!1;e=e.next}return!0}function i(a,b,c,d){var e=a.prev,f=a,g=a.next;if(v(e,f,g)>=0)return!1;for(var h=e.xf.x?e.x>g.x?e.x:g.x:f.x>g.x?f.x:g.x,k=e.y>f.y?e.y>g.y?e.y:g.y:f.y>g.y?f.y:g.y,l=r(h,i,b,c,d),m=r(j,k,b,c,d),n=a.nextZ;n&&n.z<=m;){if(n!==a.prev&&n!==a.next&&t(e.x,e.y,f.x,f.y,g.x,g.y,n.x,n.y)&&v(n.prev,n,n.next)>=0)return!1;n=n.nextZ}for(n=a.prevZ;n&&n.z>=l;){if(n!==a.prev&&n!==a.next&&t(e.x,e.y,f.x,f.y,g.x,g.y,n.x,n.y)&&v(n.prev,n,n.next)>=0)return!1;n=n.prevZ}return!0}function j(a,b,c){var d=a;do{var e=d.prev,f=d.next.next;!w(e,f)&&x(e,d,d.next,f)&&z(e,f)&&z(f,e)&&(b.push(e.i/c),b.push(d.i/c),b.push(f.i/c),D(d),D(d.next),d=a=f),d=d.next}while(d!==a);return d}function k(a,b,c,d,e,h){var i=a;do{for(var j=i.next.next;j!==i.prev;){if(i.i!==j.i&&u(i,j)){var k=B(i,j);return i=f(i,i.next),k=f(k,k.next),g(i,b,c,d,e,h),void g(k,b,c,d,e,h)}j=j.next}i=i.next}while(i!==a)}function l(a,b,c,d){var g,h,i,j,k,l=[];for(g=0,h=b.length;h>g;g++)i=b[g]*d,j=h-1>g?b[g+1]*d:a.length,k=e(a,i,j,d,!1),k===k.next&&(k.steiner=!0),l.push(s(k));for(l.sort(m),g=0;g=d.next.y){var h=d.x+(f-d.y)*(d.next.x-d.x)/(d.next.y-d.y);if(e>=h&&h>g){if(g=h,h===e){if(f===d.y)return d;if(f===d.next.y)return d.next}c=d.x=d.x&&d.x>=k&&t(l>f?e:g,f,k,l,l>f?g:e,f,d.x,d.y)&&(i=Math.abs(f-d.y)/(e-d.x),(m>i||i===m&&d.x>c.x)&&z(d,a)&&(c=d,m=i)),d=d.next;return c}function p(a,b,c,d){var e=a;do null===e.z&&(e.z=r(e.x,e.y,b,c,d)),e.prevZ=e.prev,e.nextZ=e.next,e=e.next;while(e!==a);e.prevZ.nextZ=null,e.prevZ=null,q(e)}function q(a){var b,c,d,e,f,g,h,i,j=1;do{for(c=a,a=null,f=null,g=0;c;){for(g++,d=c,h=0,b=0;j>b&&(h++,d=d.nextZ,d);b++);for(i=j;h>0||i>0&&d;)0===h?(e=d,d=d.nextZ,i--):0!==i&&d?c.z<=d.z?(e=c,c=c.nextZ,h--):(e=d,d=d.nextZ,i--):(e=c,c=c.nextZ,h--),f?f.nextZ=e:a=e,e.prevZ=f,f=e;c=d}f.nextZ=null,j*=2}while(g>1);return a}function r(a,b,c,d,e){return a=32767*(a-c)/e,b=32767*(b-d)/e,a=16711935&(a|a<<8),a=252645135&(a|a<<4),a=858993459&(a|a<<2),a=1431655765&(a|a<<1),b=16711935&(b|b<<8),b=252645135&(b|b<<4),b=858993459&(b|b<<2),b=1431655765&(b|b<<1),a|b<<1}function s(a){var b=a,c=a;do b.x=0&&(a-g)*(d-h)-(c-g)*(b-h)>=0&&(c-g)*(f-h)-(e-g)*(d-h)>=0}function u(a,b){return a.next.i!==b.i&&a.prev.i!==b.i&&!y(a,b)&&z(a,b)&&z(b,a)&&A(a,b)}function v(a,b,c){return(b.y-a.y)*(c.x-b.x)-(b.x-a.x)*(c.y-b.y)}function w(a,b){return a.x===b.x&&a.y===b.y}function x(a,b,c,d){return w(a,b)&&w(c,d)||w(a,d)&&w(c,b)?!0:v(a,b,c)>0!=v(a,b,d)>0&&v(c,d,a)>0!=v(c,d,b)>0}function y(a,b){var c=a;do{if(c.i!==a.i&&c.next.i!==a.i&&c.i!==b.i&&c.next.i!==b.i&&x(c,c.next,a,b))return!0;c=c.next}while(c!==a);return!1}function z(a,b){return v(a.prev,a,a.next)<0?v(a,b,a.next)>=0&&v(a,a.prev,b)>=0:v(a,b,a.prev)<0||v(a,a.next,b)<0}function A(a,b){var c=a,d=!1,e=(a.x+b.x)/2,f=(a.y+b.y)/2;do c.y>f!=c.next.y>f&&e<(c.next.x-c.x)*(f-c.y)/(c.next.y-c.y)+c.x&&(d=!d),c=c.next;while(c!==a);return d}function B(a,b){var c=new E(a.i,a.x,a.y),d=new E(b.i,b.x,b.y),e=a.next,f=b.prev;return a.next=b,b.prev=a,c.next=e,e.prev=c,d.next=c,c.prev=d,f.next=d,d.prev=f,d}function C(a,b,c,d){var e=new E(a,b,c);return d?(e.next=d.next,e.prev=d,d.next.prev=e,d.next=e):(e.prev=e,e.next=e),e}function D(a){a.next.prev=a.prev,a.prev.next=a.next,a.prevZ&&(a.prevZ.nextZ=a.nextZ),a.nextZ&&(a.nextZ.prevZ=a.prevZ)}function E(a,b,c){this.i=a,this.x=b,this.y=c,this.prev=null,this.next=null,this.z=null,this.prevZ=null,this.nextZ=null,this.steiner=!1}function F(a,b,c,d){for(var e=0,f=b,g=c-d;c>f;f+=d)e+=(a[g]-a[f])*(a[f+1]+a[g+1]),g=f;return e}var G={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},H=function(a){if(a=a||"","object"==typeof a){var b=a;this.R=c(b.r,max),this.G=c(b.g,max),this.B=c(b.b,max),this.A=void 0!==b.a?c(b.a,1):1}else if("string"==typeof a){a=a.toLowerCase(),a=G[a]||a;var d;(d=a.match(/^#?(\w{2})(\w{2})(\w{2})$/))?(this.R=parseInt(d[1],16)/255,this.G=parseInt(d[2],16)/255,this.B=parseInt(d[3],16)/255,this.A=1):(d=a.match(/rgba?\((\d+)\D+(\d+)\D+(\d+)(\D+([\d.]+))?\)/))&&(this.R=parseInt(d[1],10)/255,this.G=parseInt(d[2],10)/255,this.B=parseInt(d[3],10)/255,this.A=d[4]?parseFloat(d[5]):1)}};H.prototype={toHSL:function(){var a,b,c=Math.max(this.R,this.G,this.B),d=Math.min(this.R,this.G,this.B),e=(c+d)/2,f=c-d;if(f){switch(b=e>.5?f/(2-c-d):f/(c+d),c){case this.R:a=(this.G-this.B)/f+(this.Gh;h++){var j=b[h]*c,k=i-1>h?b[h+1]*c:a.length;g-=Math.abs(F(a,j,k,c))}var l=0;for(h=0;hg;g++)c.vertices.push(a[e][f][g]);e>0&&(d+=a[e-1].length,c.holes.push(d))}return c};var I={len:function(a){return Math.sqrt(a[0]*a[0]+a[1]*a[1])},sub:function(a,b){return[a[0]-b[0],a[1]-b[1]]}},J={len:function(a){return Math.sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2])},sub:function(a,b){return[a[0]-b[0],a[1]-b[1],a[2]-b[2]]},unit:function(a){var b=this.len(a);return[a[0]/b,a[1]/b,a[2]/b]},normal:function(a,b,c){var d=this.sub(a,b),e=this.sub(b,c);return this.unit([d[1]*e[2]-d[2]*e[1],d[2]*e[0]-d[0]*e[2],d[0]*e[1]-d[1]*e[0]])}},K={NUM_Y_SEGMENTS:24,NUM_X_SEGMENTS:32,quad:function(a,b,c,d,e,f){this.triangle(a,b,c,d,f),this.triangle(a,d,e,b,f)},triangle:function(a,b,c,d,e){var f=J.normal(b,c,d);[].push.apply(a.vertices,[].concat(b,d,c)),[].push.apply(a.normals,[].concat(f,f,f)),[].push.apply(a.colors,[].concat(e,e,e)),a.texCoords.push(0,0,0,0,0,0)},circle:function(a,b,c,d,e){d=d||0;for(var f,g,h=0;hf;f++){for(var k=0;kf;f+=3)this.triangle(a,[h[2*l[f]],h[2*l[f]+1],c],[h[2*l[f+1]],h[2*l[f+1]+1],c],[h[2*l[f+2]],h[2*l[f+2]+1],c],e)},cube:function(a,b,c,d,e,f,g,h){e=e||0,f=f||0,g=g||0;var i=[e,f,g],j=[e+b,f,g],k=[e+b,f+c,g],l=[e,f+c,g],m=[e,f,g+d],n=[e+b,f,g+d],o=[e+b,f+c,g+d],p=[e,f+c,g+d];this.quad(a,j,i,l,k,h),this.quad(a,m,n,o,p,h),this.quad(a,i,j,n,m,h),this.quad(a,j,k,o,n,h),this.quad(a,k,l,p,o,h),this.quad(a,l,i,m,p,h)},cylinder:function(a,b,c,d,e,f,g){f=f||0;for(var h,i,j,k,l,m,n=this.NUM_X_SEGMENTS,o=2*Math.PI,p=0;n>p;p++)h=p/n*o,i=(p+1)/n*o,j=Math.sin(h),k=Math.cos(h),l=Math.sin(i),m=Math.cos(i),this.triangle(a,[b[0]+c*j,b[1]+c*k,f],[b[0]+d*l,b[1]+d*m,f+e],[b[0]+c*l,b[1]+c*m,f],g),0!==d&&this.triangle(a,[b[0]+d*j,b[1]+d*k,f+e],[b[0]+d*l,b[1]+d*m,f+e],[b[0]+c*j,b[1]+c*k,f],g)},dome:function(a,b,c,d,e,f){e=e||0;for(var g,h,i,j,k,l,m,n,o,p,q=this.NUM_Y_SEGMENTS/2,r=Math.PI/2,s=0;q>s;s++)g=s/q*r-r,h=(s+1)/q*r-r,i=Math.sin(g),j=Math.cos(g),k=Math.sin(h),l=Math.cos(h),m=j*c,n=l*c,o=(k-i)*d,p=e-k*d,this.cylinder(a,b,n,m,o,p,f)},sphere:function(a,b,c,d,e,f){return e=e||0,this.cylinder(a,b,c,c,d,e,f)},pyramid:function(a,b,c,d,e,f){e=e||0,b=b[0];for(var g=0,h=b.length-1;h>g;g++)this.triangle(a,[b[g][0],b[g][1],e],[b[g+1][0],b[g+1][1],e],[c[0],c[1],e+d],f)},extrusion:function(a,b,c,d,e,f){d=d||0;for(var g,h,i,j,k,l,m,n,o,p,q,r,s=f[2]*c,t=f[3]*c,u=0,v=b.length;v>u;u++){g=b[u],h=g.length-1,g[0][0]===g[h][0]&&g[0][1]===g[h][1]||(g.push(g[0]),h++);for(var w=0;h>w;w++)i=g[w],j=g[w+1],k=I.len(I.sub(i,j)),q=f[0]*k<<0,r=f[1]*k<<0,l=[i[0],i[1],d],m=[j[0],j[1],d],n=[j[0],j[1],d+c],o=[i[0],i[1],d+c],p=J.normal(l,m,n),[].push.apply(a.vertices,[].concat(l,n,m,l,o,n)),[].push.apply(a.normals,[].concat(p,p,p,p,p,p)),[].push.apply(a.colors,[].concat(e,e,e,e,e,e)),a.texCoords.push(q,t,r,s,r,t,q,t,q,s,r,s)}}},L={};!function(){function a(a,f,i,l,m,o){var p=g(l,m),q=h(p[0]),r=(q.maxX-q.minX)/2,s=[q.minX+(q.maxX-q.minX)/2,q.minY+(q.maxY-q.minY)/2],t=i.height||(i.levels?i.levels*n:j),u=i.minHeight||(i.minLevel?i.minLevel*n:0),v=i.roofHeight||k,w=(f/2%2?-1:1)*(f%2?.03:.06),x=d(o||i.roofColor||i.color||e(i.roofMaterial),w),y=d(o||i.wallColor||i.color||e(i.material),w);switch(i.roofShape){case"cone":case"dome":case"onion":case"pyramid":case"pyramidal":t=Math.max(0,t-v);break;default:v=0}b(a,i,p,s,r,t-u,u,y),c(a,i,p,s,r,v,t,x)}function b(a,b,c,d,e,f,g,h){switch(b.shape){case"cylinder":K.cylinder(a,d,e,e,f,g,h);break;case"cone":K.cylinder(a,d,e,0,f,g,h);break;case"dome":K.dome(a,d,e,f||e,g,h);break;case"sphere":K.sphere(a,d,e,f||2*e,g,h);break;case"pyramid":case"pyramidal":K.pyramid(a,c,d,f,g,h);break;case"none":return;default:var i=.2,j=.4;"glass"!==b.material&&(i=0,j=0,b.levels&&(j=parseFloat(b.levels)-parseFloat(b.minLevel||0)<<0)),K.extrusion(a,c,f,g,h,[0,m,i/f,j/f])}}function c(a,b,c,d,e,f,g,h){switch(b.shape){case"cone":case"pyramid":case"pyramidal":return}switch(b.roofShape){case"cone":K.cylinder(a,d,e,0,f,g,h);break;case"dome":case"onion":K.dome(a,d,e,f||e,g,h);break;case"pyramid":case"pyramidal":"cylinder"===b.shape?K.cylinder(a,d,e,0,f,g,h):K.pyramid(a,c,d,f,g,h);break;default:"cylinder"===b.shape?K.circle(a,d,e,g,h):K.polygon(a,c,g,h)}}function d(a,b){var c=new H(a||l).toArray();return[c[0]+b,c[1]+b,c[2]+b]}function e(a){return"string"!=typeof a?null:(a=a.toLowerCase(),"#"===a[0]?a:o[p[a]||a]||null)}function f(a){switch(a.type){case"MultiPolygon":return a.coordinates;case"Polygon":return[a.coordinates];default:return[]}}function g(a,b){var c=i*Math.cos(b[1]/180*Math.PI);return a.map(function(a,d){return 0===d!==isClockWise(a)&&a.reverse(),a.map(function(a){return[(a[0]-b[0])*c,-(a[1]-b[1])*i]})})}function h(a){for(var b=1/0,c=1/0,d=-(1/0),e=-(1/0),f=0;fi;i++)a(b,c,d.properties,h[i],e,g)}}(),"function"==typeof a.define?a.define([],L):"object"==typeof a.exports?a.exports=L:a.Triangulate=L}(this); \ No newline at end of file diff --git a/lib/earcut.custom.js b/lib/earcut.custom.js new file mode 100644 index 0000000..8cac0c0 --- /dev/null +++ b/lib/earcut.custom.js @@ -0,0 +1,647 @@ + +var earcut = (function() { + + function earcut(data, holeIndices, dim) { + + dim = dim || 2; + + var hasHoles = holeIndices && holeIndices.length, + outerLen = hasHoles ? holeIndices[0]*dim : data.length, + outerNode = linkedList(data, 0, outerLen, dim, true), + triangles = []; + + if (!outerNode) return triangles; + + var minX, minY, maxX, maxY, x, y, size; + + if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); + + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + if (data.length>80*dim) { + minX = maxX = data[0]; + minY = maxY = data[1]; + + for (var i = dim; imaxX) maxX = x; + if (y>maxY) maxY = y; + } + + // minX, minY and size are later used to transform coords into integers for z-order calculation + size = Math.max(maxX - minX, maxY - minY); + } + + earcutLinked(outerNode, triangles, dim, minX, minY, size); + + return triangles; + } + +// create a circular doubly linked list from polygon points in the specified winding order + function linkedList(data, start, end, dim, clockwise) { + var i, last; + + if (clockwise === (signedArea(data, start, end, dim)>0)) { + for (i = start; i=start; i -= dim) last = insertNode(i, data[i], data[i + 1], last); + } + + if (last && equals(last, last.next)) { + removeNode(last); + last = last.next; + } + + return last; + } + +// eliminate colinear or duplicate points + function filterPoints(start, end) { + if (!start) return start; + if (!end) end = start; + + var p = start, + again; + do { + again = false; + + if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { + removeNode(p); + p = end = p.prev; + if (p === p.next) return null; + again = true; + + } else { + p = p.next; + } + } while (again || p !== end); + + return end; + } + +// main ear slicing loop which triangulates a polygon (given as a linked list) + function earcutLinked(ear, triangles, dim, minX, minY, size, pass) { + if (!ear) return; + + // interlink polygon nodes in z-order + if (!pass && size) indexCurve(ear, minX, minY, size); + + var stop = ear, + prev, next; + + // iterate through ears, slicing them one by one + while (ear.prev !== ear.next) { + prev = ear.prev; + next = ear.next; + + if (size ? isEarHashed(ear, minX, minY, size) : isEar(ear)) { + // cut off the triangle + triangles.push(prev.i/dim); + triangles.push(ear.i/dim); + triangles.push(next.i/dim); + + removeNode(ear); + + // skipping the next vertice leads to less sliver triangles + ear = next.next; + stop = next.next; + + continue; + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + if (ear === stop) { + // try filtering points and slicing again + if (!pass) { + earcutLinked(filterPoints(ear), triangles, dim, minX, minY, size, 1); + + // if this didn't work, try curing all small self-intersections locally + } else if (pass === 1) { + ear = cureLocalIntersections(ear, triangles, dim); + earcutLinked(ear, triangles, dim, minX, minY, size, 2); + + // as a last resort, try splitting the remaining polygon into two + } else if (pass === 2) { + splitEarcut(ear, triangles, dim, minX, minY, size); + } + + break; + } + } + } + +// check whether a polygon node forms a valid ear with adjacent nodes + function isEar(ear) { + var a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c)>=0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + var p = ear.next.next; + + while (p !== ear.prev) { + if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && + area(p.prev, p, p.next)>=0) return false; + p = p.next; + } + + return true; + } + + function isEarHashed(ear, minX, minY, size) { + var a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c)>=0) return false; // reflex, can't be an ear + + // triangle bbox; min & max are calculated like this for speed + var minTX = a.xb.x ? (a.x>c.x ? a.x : c.x) : (b.x>c.x ? b.x : c.x), + maxTY = a.y>b.y ? (a.y>c.y ? a.y : c.y) : (b.y>c.y ? b.y : c.y); + + // z-order range for the current triangle bbox; + var minZ = zOrder(minTX, minTY, minX, minY, size), + maxZ = zOrder(maxTX, maxTY, minX, minY, size); + + // first look for points inside the triangle in increasing z-order + var p = ear.nextZ; + + while (p && p.z<=maxZ) { + if (p !== ear.prev && p !== ear.next && + pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && + area(p.prev, p, p.next)>=0) return false; + p = p.nextZ; + } + + // then look for points in decreasing z-order + p = ear.prevZ; + + while (p && p.z>=minZ) { + if (p !== ear.prev && p !== ear.next && + pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && + area(p.prev, p, p.next)>=0) return false; + p = p.prevZ; + } + + return true; + } + +// go through all polygon nodes and cure small local self-intersections + function cureLocalIntersections(start, triangles, dim) { + var p = start; + do { + var a = p.prev, + b = p.next.next; + + if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { + + triangles.push(a.i/dim); + triangles.push(p.i/dim); + triangles.push(b.i/dim); + + // remove two nodes involved + removeNode(p); + removeNode(p.next); + + p = start = b; + } + p = p.next; + } while (p !== start); + + return p; + } + +// try splitting polygon into two and triangulate them independently + function splitEarcut(start, triangles, dim, minX, minY, size) { + // look for a valid diagonal that divides the polygon into two + var a = start; + do { + var b = a.next.next; + while (b !== a.prev) { + if (a.i !== b.i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + var c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a.next); + c = filterPoints(c, c.next); + + // run earcut on each half + earcutLinked(a, triangles, dim, minX, minY, size); + earcutLinked(c, triangles, dim, minX, minY, size); + return; + } + b = b.next; + } + a = a.next; + } while (a !== start); + } + +// link every hole into the outer loop, producing a single-ring polygon without holes + function eliminateHoles(data, holeIndices, outerNode, dim) { + var queue = [], + i, len, start, end, list; + + for (i = 0, len = holeIndices.length; i=p.next.y) { + var x = p.x + (hy - p.y)*(p.next.x - p.x)/(p.next.y - p.y); + if (x<=hx && x>qx) { + qx = x; + if (x === hx) { + if (hy === p.y) return p; + if (hy === p.next.y) return p.next; + } + m = p.x=p.x && p.x>=mx && + pointInTriangle(hym.x)) && locallyInside(p, hole)) { + m = p; + tanMin = tan; + } + } + + p = p.next; + } + + return m; + } + +// interlink polygon nodes in z-order + function indexCurve(start, minX, minY, size) { + var p = start; + do { + if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, size); + p.prevZ = p.prev; + p.nextZ = p.next; + p = p.next; + } while (p !== start); + + p.prevZ.nextZ = null; + p.prevZ = null; + + sortLinked(p); + } + +// Simon Tatham's linked list merge sort algorithm +// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html + function sortLinked(list) { + var i, p, q, e, tail, numMerges, pSize, qSize, + inSize = 1; + + do { + p = list; + list = null; + tail = null; + numMerges = 0; + + while (p) { + numMerges++; + q = p; + pSize = 0; + for (i = 0; i0 || (qSize>0 && q)) { + + if (pSize === 0) { + e = q; + q = q.nextZ; + qSize--; + } else if (qSize === 0 || !q) { + e = p; + p = p.nextZ; + pSize--; + } else if (p.z<=q.z) { + e = p; + p = p.nextZ; + pSize--; + } else { + e = q; + q = q.nextZ; + qSize--; + } + + if (tail) tail.nextZ = e; + else list = e; + + e.prevZ = tail; + tail = e; + } + + p = q; + } + + tail.nextZ = null; + inSize *= 2; + + } while (numMerges>1); + + return list; + } + +// z-order of a point given coords and size of the data bounding box + function zOrder(x, y, minX, minY, size) { + // coords are transformed into non-negative 15-bit integer range + x = 32767*(x - minX)/size; + y = 32767*(y - minY)/size; + + x = (x | (x<<8)) & 0x00FF00FF; + x = (x | (x<<4)) & 0x0F0F0F0F; + x = (x | (x<<2)) & 0x33333333; + x = (x | (x<<1)) & 0x55555555; + + y = (y | (y<<8)) & 0x00FF00FF; + y = (y | (y<<4)) & 0x0F0F0F0F; + y = (y | (y<<2)) & 0x33333333; + y = (y | (y<<1)) & 0x55555555; + + return x | (y<<1); + } + +// find the leftmost node of a polygon ring + function getLeftmost(start) { + var p = start, + leftmost = start; + do { + if (p.x=0 && + (ax - px)*(by - py) - (bx - px)*(ay - py)>=0 && + (bx - px)*(cy - py) - (cx - px)*(by - py)>=0; + } + +// check if a diagonal between two polygon nodes is valid (lies in polygon interior) + function isValidDiagonal(a, b) { + return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && + locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b); + } + +// signed area of a triangle + function area(p, q, r) { + return (q.y - p.y)*(r.x - q.x) - (q.x - p.x)*(r.y - q.y); + } + +// check if two points are equal + function equals(p1, p2) { + return p1.x === p2.x && p1.y === p2.y; + } + +// check if two segments intersect + function intersects(p1, q1, p2, q2) { + if ((equals(p1, q1) && equals(p2, q2)) || + (equals(p1, q2) && equals(p2, q1))) return true; + return area(p1, q1, p2)>0 !== area(p1, q1, q2)>0 && + area(p2, q2, p1)>0 !== area(p2, q2, q1)>0; + } + +// check if a polygon diagonal intersects any polygon segments + function intersectsPolygon(a, b) { + var p = a; + do { + if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && + intersects(p, p.next, a, b)) return true; + p = p.next; + } while (p !== a); + + return false; + } + +// check if a polygon diagonal is locally inside the polygon + function locallyInside(a, b) { + return area(a.prev, a, a.next)<0 ? + area(a, b, a.next)>=0 && area(a, a.prev, b)>=0 : + area(a, b, a.prev)<0 || area(a, a.next, b)<0; + } + +// check if the middle point of a polygon diagonal is inside the polygon + function middleInside(a, b) { + var p = a, + inside = false, + px = (a.x + b.x)/2, + py = (a.y + b.y)/2; + do { + if (((p.y>py) !== (p.next.y>py)) && (px<(p.next.x - p.x)*(py - p.y)/(p.next.y - p.y) + p.x)) + inside = !inside; + p = p.next; + } while (p !== a); + + return inside; + } + +// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; +// if one belongs to the outer ring and another to a hole, it merges it into a single ring + function splitPolygon(a, b) { + var a2 = new Node(a.i, a.x, a.y), + b2 = new Node(b.i, b.x, b.y), + an = a.next, + bp = b.prev; + + a.next = b; + b.prev = a; + + a2.next = an; + an.prev = a2; + + b2.next = a2; + a2.prev = b2; + + bp.next = b2; + b2.prev = bp; + + return b2; + } + +// create a node and optionally link it with previous one (in a circular doubly linked list) + function insertNode(i, x, y, last) { + var p = new Node(i, x, y); + + if (!last) { + p.prev = p; + p.next = p; + + } else { + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; + } + return p; + } + + function removeNode(p) { + p.next.prev = p.prev; + p.prev.next = p.next; + + if (p.prevZ) p.prevZ.nextZ = p.nextZ; + if (p.nextZ) p.nextZ.prevZ = p.prevZ; + } + + function Node(i, x, y) { + // vertice index in coordinates array + this.i = i; + + // vertex coordinates + this.x = x; + this.y = y; + + // previous and next vertice nodes in a polygon ring + this.prev = null; + this.next = null; + + // z-order curve value + this.z = null; + + // previous and next nodes in z-order + this.prevZ = null; + this.nextZ = null; + + // indicates whether this is a steiner point + this.steiner = false; + } + +// return a percentage difference between the polygon area and its triangulation area; +// used to verify correctness of triangulation + earcut.deviation = function(data, holeIndices, dim, triangles) { + var hasHoles = holeIndices && holeIndices.length; + var outerLen = hasHoles ? holeIndices[0]*dim : data.length; + + var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim)); + if (hasHoles) { + for (var i = 0, len = holeIndices.length; i0) { + holeIndex += data[i - 1].length; + result.holes.push(holeIndex); + } + } + return result; + }; + + return earcut; + +}(this)); diff --git a/src/GeoJSON.js b/src/TTriangulate.js similarity index 86% rename from src/GeoJSON.js rename to src/TTriangulate.js index c2f0846..5aca9ea 100644 --- a/src/GeoJSON.js +++ b/src/TTriangulate.js @@ -1,7 +1,5 @@ -if (GeoJSON === undefined) { - var GeoJSON = {}; -} +var Triangulate = {}; (function() { @@ -72,7 +70,7 @@ if (GeoJSON === undefined) { // straw }; - GeoJSON.getPosition = function(geometry) { + Triangulate.getPosition = function(geometry) { var coordinates = geometry.coordinates; switch (geometry.type) { case 'Point': @@ -91,7 +89,7 @@ if (GeoJSON === undefined) { } }; - GeoJSON.triangulate = function(res, id, feature, position, color) { + Triangulate.split = function(res, id, feature, position, color) { var geometries = flattenGeometry(feature.geometry); for (var i = 0, il = geometries.length; i