├── .gitignore ├── docs └── index.html ├── .jshintrc ├── package.json ├── Gruntfile.js ├── LICENSE ├── test └── index.js ├── README.md ├── index.html ├── src └── index.js └── dat.gui.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node" : true, 3 | "browser" : true, 4 | 5 | "boss" : false, 6 | "curly": true, 7 | "debug": false, 8 | "devel": false, 9 | "eqeqeq": true, 10 | "eqnull": true, 11 | "evil": false, 12 | "forin": false, 13 | "immed": true, 14 | "laxbreak": false, 15 | "newcap": true, 16 | "noarg": true, 17 | "noempty": true, 18 | "nonew": true, 19 | "plusplus": false, 20 | "regexp": false, 21 | "smarttabs": true, 22 | "sub": true, 23 | "strict": false, 24 | "trailing" : true, 25 | "undef": true, 26 | "unused": true, 27 | "indent": 4, 28 | "shadow" : true, 29 | "laxcomma": true 30 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "poly-decomp", 3 | "version": "0.3.0", 4 | "description": "Convex decomposition for 2D polygons", 5 | "author": "Stefan Hedman (http://steffe.se)", 6 | "keywords": [ 7 | "convex", 8 | "decomposition", 9 | "polygon", 10 | "2d" 11 | ], 12 | "engines": { 13 | "node": "*" 14 | }, 15 | "main": "./src/index.js", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/schteppe/poly-decomp.js.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/schteppe/poly-decomp.js/issues" 22 | }, 23 | "license": "MIT", 24 | "dependencies": {}, 25 | "devDependencies": { 26 | "browserify": "^3.44.2", 27 | "grunt": "^1.0.1", 28 | "grunt-browserify": "^2.0.8", 29 | "grunt-contrib-nodeunit": "^1.0.0", 30 | "grunt-contrib-uglify": "^0.4.1", 31 | "nodeunit": "^0.9.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | 6 | browserify : { 7 | decomp : { 8 | src : ["src/index.js"], 9 | dest : 'build/decomp.js', 10 | options:{ 11 | bundleOptions : { 12 | standalone : "decomp" 13 | } 14 | } 15 | } 16 | }, 17 | 18 | uglify : { 19 | build : { 20 | src : ['build/decomp.js'], 21 | dest : 'build/decomp.min.js' 22 | } 23 | }, 24 | 25 | nodeunit: { 26 | all: ['test/**/*.js'], 27 | } 28 | }); 29 | 30 | grunt.loadNpmTasks('grunt-browserify'); 31 | grunt.loadNpmTasks('grunt-contrib-uglify'); 32 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 33 | 34 | grunt.registerTask('default', ['nodeunit', 'browserify','uglify']); 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Stefan Hedman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var polyDecomp = require('../src'); 2 | 3 | var concave = [ 4 | [-1, 1], 5 | [-1, 0], 6 | [1, 0], 7 | [1, 1], 8 | [0.5, 0.5] 9 | ]; 10 | 11 | var circle = [], 12 | N = 10; 13 | for(var i=0; i[2*point[0]+100,1*point[1]+500]); 89 | polyDecomp.makeCCW(path); 90 | var polys = polyDecomp.quickDecomp(path); 91 | test.equal(polys.length, 3); 92 | 93 | path = [ 94 | [0,-134], 95 | [50,-139], 96 | [60,-215], 97 | [70,-6], 98 | [80,-236], 99 | [110,-120], 100 | [110,0], 101 | [0,0] 102 | ].map((point)=>[3*point[0]+100,1*point[1]+500]); 103 | polyDecomp.makeCCW(path); 104 | var polys = polyDecomp.quickDecomp(path); 105 | test.equal(polys.length, 3); 106 | 107 | path = [ 108 | [0,-134], 109 | [50,-139], 110 | [60,-215], 111 | [70,-6], 112 | [80,-236], 113 | [110,-120], 114 | [110,0], 115 | [0,0] 116 | ].map((point)=>[-3*point[0],-point[1]]); 117 | polyDecomp.makeCCW(path); 118 | var polys = polyDecomp.quickDecomp(path); 119 | test.equal(polys.length, 3); 120 | 121 | path = [[331,384],[285,361],[238,386],[283,408],[191,469],[213,372],[298,314],[342,340]]; 122 | polyDecomp.makeCCW(path); 123 | var polys = polyDecomp.quickDecomp(path); 124 | test.equal(polys.length, 3); 125 | 126 | test.done(); 127 | } 128 | 129 | }; 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | poly-decomp.js 2 | ============== 3 | 4 | Library for decomposing a 2D polygon into convex pieces. 5 | 6 | ![Decomposing a convcave polygon into convex regions](https://cloud.githubusercontent.com/assets/1063152/18008563/edccfe86-6ba8-11e6-9e20-a090c1812c95.gif) 7 | 8 | [Launch the demo!](http://schteppe.github.io/poly-decomp.js/) 9 | 10 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=Z2G8VHLDJ9N3L&lc=GB&item_name=Stefan%20Hedman&item_number=schteppe¤cy_code=USD&bn=PP%2dDonationsBF%3aDonate%2dPayPal%2dgreen%2esvg%3aNonHosted) 11 | 12 | The library implements two algorithms, one optimal (but slow) and one less optimal (but fast). 13 | It's is a manual port of the C++ library [Poly Decomp](https://mpen.ca/406/bayazit) by [Mark Penner](https://mpen.ca). 14 | 15 | ### Install 16 | ##### Browser 17 | Download [decomp.js](build/decomp.js) or [decomp.min.js](build/decomp.min.js) and include the script in your HTML: 18 | ```html 19 | 20 | 21 | 22 | ``` 23 | 24 | Then you can use the ```decomp``` global. 25 | 26 | ##### Node.js 27 | ``` 28 | npm install poly-decomp 29 | ``` 30 | 31 | Then require it like so: 32 | 33 | ```js 34 | var decomp = require('poly-decomp'); 35 | ``` 36 | 37 | ### Basic usage 38 | ```js 39 | // Create a concave polygon 40 | var concavePolygon = [ 41 | [ -1, 1], 42 | [ -1, 0], 43 | [ 1, 0], 44 | [ 1, 1], 45 | [0.5, 0.5] 46 | ]; 47 | 48 | // Make sure the polygon has counter-clockwise winding. Skip this step if you know it's already counter-clockwise. 49 | decomp.makeCCW(concavePolygon); 50 | 51 | // Decompose into convex polygons, using the faster algorithm 52 | var convexPolygons = decomp.quickDecomp(concavePolygon); 53 | 54 | // ==> [ [[1,0],[1,1],[0.5,0.5]], [[0.5,0.5],[-1,1],[-1,0],[1,0]] ] 55 | 56 | // Decompose using the slow (but optimal) algorithm 57 | var convexPolygons = decomp.decomp(concavePolygon); 58 | 59 | // ==> [ [[-1,1],[-1,0],[1,0],[0.5,0.5]], [[1,0],[1,1],[0.5,0.5]] ] 60 | ``` 61 | 62 | ### Advanced usage 63 | ```js 64 | // Get user input as an array of points. 65 | var polygon = getUserInput(); 66 | 67 | // Check if the polygon self-intersects 68 | if(decomp.isSimple(polygon)){ 69 | 70 | // Reverse the polygon to make sure it uses counter-clockwise winding 71 | decomp.makeCCW(polygon); 72 | 73 | // Decompose into convex pieces 74 | var convexPolygons = decomp.quickDecomp(polygon); 75 | 76 | // Draw each point on an HTML5 Canvas context 77 | for(var i=0; i 2 | 3 | 4 | poly-decomp.js 5 | 42 | 43 | 44 |
45 |

poly-decomp.js

46 |

Decomposition of 2D polygons into convex pieces in JavaScript. See the Github repo.

47 |

Try drawing a polygon below!

48 |
49 | 50 | 51 | 52 | 456 | 457 | 458 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | decomp: polygonDecomp, 3 | quickDecomp: polygonQuickDecomp, 4 | isSimple: polygonIsSimple, 5 | removeCollinearPoints: polygonRemoveCollinearPoints, 6 | removeDuplicatePoints: polygonRemoveDuplicatePoints, 7 | makeCCW: polygonMakeCCW 8 | }; 9 | 10 | /** 11 | * Compute the intersection between two lines. 12 | * @static 13 | * @method lineInt 14 | * @param {Array} l1 Line vector 1 15 | * @param {Array} l2 Line vector 2 16 | * @param {Number} precision Precision to use when checking if the lines are parallel 17 | * @return {Array} The intersection point. 18 | */ 19 | function lineInt(l1,l2,precision){ 20 | precision = precision || 0; 21 | var i = [0,0]; // point 22 | var a1, b1, c1, a2, b2, c2, det; // scalars 23 | a1 = l1[1][1] - l1[0][1]; 24 | b1 = l1[0][0] - l1[1][0]; 25 | c1 = a1 * l1[0][0] + b1 * l1[0][1]; 26 | a2 = l2[1][1] - l2[0][1]; 27 | b2 = l2[0][0] - l2[1][0]; 28 | c2 = a2 * l2[0][0] + b2 * l2[0][1]; 29 | det = a1 * b2 - a2*b1; 30 | if (!scalar_eq(det, 0, precision)) { // lines are not parallel 31 | i[0] = (b2 * c1 - b1 * c2) / det; 32 | i[1] = (a1 * c2 - a2 * c1) / det; 33 | } 34 | return i; 35 | } 36 | 37 | /** 38 | * Checks if two line segments intersects. 39 | * @method segmentsIntersect 40 | * @param {Array} p1 The start vertex of the first line segment. 41 | * @param {Array} p2 The end vertex of the first line segment. 42 | * @param {Array} q1 The start vertex of the second line segment. 43 | * @param {Array} q2 The end vertex of the second line segment. 44 | * @return {Boolean} True if the two line segments intersect 45 | */ 46 | function lineSegmentsIntersect(p1, p2, q1, q2){ 47 | var dx = p2[0] - p1[0]; 48 | var dy = p2[1] - p1[1]; 49 | var da = q2[0] - q1[0]; 50 | var db = q2[1] - q1[1]; 51 | 52 | // segments are parallel 53 | if((da*dy - db*dx) === 0){ 54 | return false; 55 | } 56 | 57 | var s = (dx * (q1[1] - p1[1]) + dy * (p1[0] - q1[0])) / (da * dy - db * dx); 58 | var t = (da * (p1[1] - q1[1]) + db * (q1[0] - p1[0])) / (db * dx - da * dy); 59 | 60 | return (s>=0 && s<=1 && t>=0 && t<=1); 61 | } 62 | 63 | /** 64 | * Get the area of a triangle spanned by the three given points. Note that the area will be negative if the points are not given in counter-clockwise order. 65 | * @static 66 | * @method area 67 | * @param {Array} a 68 | * @param {Array} b 69 | * @param {Array} c 70 | * @return {Number} 71 | */ 72 | function triangleArea(a,b,c){ 73 | return (((b[0] - a[0])*(c[1] - a[1]))-((c[0] - a[0])*(b[1] - a[1]))); 74 | } 75 | 76 | function isLeft(a,b,c){ 77 | return triangleArea(a,b,c) > 0; 78 | } 79 | 80 | function isLeftOn(a,b,c) { 81 | return triangleArea(a, b, c) >= 0; 82 | } 83 | 84 | function isRight(a,b,c) { 85 | return triangleArea(a, b, c) < 0; 86 | } 87 | 88 | function isRightOn(a,b,c) { 89 | return triangleArea(a, b, c) <= 0; 90 | } 91 | 92 | var tmpPoint1 = [], 93 | tmpPoint2 = []; 94 | 95 | /** 96 | * Check if three points are collinear 97 | * @method collinear 98 | * @param {Array} a 99 | * @param {Array} b 100 | * @param {Array} c 101 | * @param {Number} [thresholdAngle=0] Threshold angle to use when comparing the vectors. The function will return true if the angle between the resulting vectors is less than this value. Use zero for max precision. 102 | * @return {Boolean} 103 | */ 104 | function collinear(a,b,c,thresholdAngle) { 105 | if(!thresholdAngle){ 106 | return triangleArea(a, b, c) === 0; 107 | } else { 108 | var ab = tmpPoint1, 109 | bc = tmpPoint2; 110 | 111 | ab[0] = b[0]-a[0]; 112 | ab[1] = b[1]-a[1]; 113 | bc[0] = c[0]-b[0]; 114 | bc[1] = c[1]-b[1]; 115 | 116 | var dot = ab[0]*bc[0] + ab[1]*bc[1], 117 | magA = Math.sqrt(ab[0]*ab[0] + ab[1]*ab[1]), 118 | magB = Math.sqrt(bc[0]*bc[0] + bc[1]*bc[1]), 119 | angle = Math.acos(dot/(magA*magB)); 120 | return angle < thresholdAngle; 121 | } 122 | } 123 | 124 | function sqdist(a,b){ 125 | var dx = b[0] - a[0]; 126 | var dy = b[1] - a[1]; 127 | return dx * dx + dy * dy; 128 | } 129 | 130 | /** 131 | * Get a vertex at position i. It does not matter if i is out of bounds, this function will just cycle. 132 | * @method at 133 | * @param {Number} i 134 | * @return {Array} 135 | */ 136 | function polygonAt(polygon, i){ 137 | var s = polygon.length; 138 | return polygon[i < 0 ? i % s + s : i % s]; 139 | } 140 | 141 | /** 142 | * Clear the polygon data 143 | * @method clear 144 | * @return {Array} 145 | */ 146 | function polygonClear(polygon){ 147 | polygon.length = 0; 148 | } 149 | 150 | /** 151 | * Append points "from" to "to"-1 from an other polygon "poly" onto this one. 152 | * @method append 153 | * @param {Polygon} poly The polygon to get points from. 154 | * @param {Number} from The vertex index in "poly". 155 | * @param {Number} to The end vertex index in "poly". Note that this vertex is NOT included when appending. 156 | * @return {Array} 157 | */ 158 | function polygonAppend(polygon, poly, from, to){ 159 | for(var i=from; i v[br][0])) { 175 | br = i; 176 | } 177 | } 178 | 179 | // reverse poly if clockwise 180 | if (!isLeft(polygonAt(polygon, br - 1), polygonAt(polygon, br), polygonAt(polygon, br + 1))) { 181 | polygonReverse(polygon); 182 | return true; 183 | } else { 184 | return false; 185 | } 186 | } 187 | 188 | /** 189 | * Reverse the vertices in the polygon 190 | * @method reverse 191 | */ 192 | function polygonReverse(polygon){ 193 | var tmp = []; 194 | var N = polygon.length; 195 | for(var i=0; i!==N; i++){ 196 | tmp.push(polygon.pop()); 197 | } 198 | for(var i=0; i!==N; i++){ 199 | polygon[i] = tmp[i]; 200 | } 201 | } 202 | 203 | /** 204 | * Check if a point in the polygon is a reflex point 205 | * @method isReflex 206 | * @param {Number} i 207 | * @return {Boolean} 208 | */ 209 | function polygonIsReflex(polygon, i){ 210 | return isRight(polygonAt(polygon, i - 1), polygonAt(polygon, i), polygonAt(polygon, i + 1)); 211 | } 212 | 213 | var tmpLine1=[], 214 | tmpLine2=[]; 215 | 216 | /** 217 | * Check if two vertices in the polygon can see each other 218 | * @method canSee 219 | * @param {Number} a Vertex index 1 220 | * @param {Number} b Vertex index 2 221 | * @return {Boolean} 222 | */ 223 | function polygonCanSee(polygon, a,b) { 224 | var p, dist, l1=tmpLine1, l2=tmpLine2; 225 | 226 | if (isLeftOn(polygonAt(polygon, a + 1), polygonAt(polygon, a), polygonAt(polygon, b)) && isRightOn(polygonAt(polygon, a - 1), polygonAt(polygon, a), polygonAt(polygon, b))) { 227 | return false; 228 | } 229 | dist = sqdist(polygonAt(polygon, a), polygonAt(polygon, b)); 230 | for (var i = 0; i !== polygon.length; ++i) { // for each edge 231 | if ((i + 1) % polygon.length === a || i === a){ // ignore incident edges 232 | continue; 233 | } 234 | if (isLeftOn(polygonAt(polygon, a), polygonAt(polygon, b), polygonAt(polygon, i + 1)) && isRightOn(polygonAt(polygon, a), polygonAt(polygon, b), polygonAt(polygon, i))) { // if diag intersects an edge 235 | l1[0] = polygonAt(polygon, a); 236 | l1[1] = polygonAt(polygon, b); 237 | l2[0] = polygonAt(polygon, i); 238 | l2[1] = polygonAt(polygon, i + 1); 239 | p = lineInt(l1,l2); 240 | if (sqdist(polygonAt(polygon, a), p) < dist) { // if edge is blocking visibility to b 241 | return false; 242 | } 243 | } 244 | } 245 | 246 | return true; 247 | } 248 | 249 | /** 250 | * Check if two vertices in the polygon can see each other 251 | * @method canSee2 252 | * @param {Number} a Vertex index 1 253 | * @param {Number} b Vertex index 2 254 | * @return {Boolean} 255 | */ 256 | function polygonCanSee2(polygon, a,b) { 257 | // for each edge 258 | for (var i = 0; i !== polygon.length; ++i) { 259 | // ignore incident edges 260 | if (i === a || i === b || (i + 1) % polygon.length === a || (i + 1) % polygon.length === b){ 261 | continue; 262 | } 263 | if( lineSegmentsIntersect(polygonAt(polygon, a), polygonAt(polygon, b), polygonAt(polygon, i), polygonAt(polygon, i+1)) ){ 264 | return false; 265 | } 266 | } 267 | return true; 268 | } 269 | 270 | /** 271 | * Copy the polygon from vertex i to vertex j. 272 | * @method copy 273 | * @param {Number} i 274 | * @param {Number} j 275 | * @param {Polygon} [targetPoly] Optional target polygon to save in. 276 | * @return {Polygon} The resulting copy. 277 | */ 278 | function polygonCopy(polygon, i,j,targetPoly){ 279 | var p = targetPoly || []; 280 | polygonClear(p); 281 | if (i < j) { 282 | // Insert all vertices from i to j 283 | for(var k=i; k<=j; k++){ 284 | p.push(polygon[k]); 285 | } 286 | 287 | } else { 288 | 289 | // Insert vertices 0 to j 290 | for(var k=0; k<=j; k++){ 291 | p.push(polygon[k]); 292 | } 293 | 294 | // Insert vertices i to end 295 | for(var k=i; k 0){ 345 | return polygonSlice(polygon, edges); 346 | } else { 347 | return [polygon]; 348 | } 349 | } 350 | 351 | /** 352 | * Slices the polygon given one or more cut edges. If given one, this function will return two polygons (false on failure). If many, an array of polygons. 353 | * @method slice 354 | * @param {Array} cutEdges A list of edges, as returned by .getCutEdges() 355 | * @return {Array} 356 | */ 357 | function polygonSlice(polygon, cutEdges){ 358 | if(cutEdges.length === 0){ 359 | return [polygon]; 360 | } 361 | if(cutEdges instanceof Array && cutEdges.length && cutEdges[0] instanceof Array && cutEdges[0].length===2 && cutEdges[0][0] instanceof Array){ 362 | 363 | var polys = [polygon]; 364 | 365 | for(var i=0; i maxlevel){ 474 | console.warn("quickDecomp: max level ("+maxlevel+") reached."); 475 | return result; 476 | } 477 | 478 | for (var i = 0; i < polygon.length; ++i) { 479 | if (polygonIsReflex(poly, i)) { 480 | reflexVertices.push(poly[i]); 481 | upperDist = lowerDist = Number.MAX_VALUE; 482 | 483 | 484 | for (var j = 0; j < polygon.length; ++j) { 485 | if (isLeft(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j)) && isRightOn(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j - 1))) { // if line intersects with an edge 486 | p = getIntersectionPoint(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j), polygonAt(poly, j - 1)); // find the point of intersection 487 | if (isRight(polygonAt(poly, i + 1), polygonAt(poly, i), p)) { // make sure it's inside the poly 488 | d = sqdist(poly[i], p); 489 | if (d < lowerDist) { // keep only the closest intersection 490 | lowerDist = d; 491 | lowerInt = p; 492 | lowerIndex = j; 493 | } 494 | } 495 | } 496 | if (isLeft(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j + 1)) && isRightOn(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j))) { 497 | p = getIntersectionPoint(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j), polygonAt(poly, j + 1)); 498 | if (isLeft(polygonAt(poly, i - 1), polygonAt(poly, i), p)) { 499 | d = sqdist(poly[i], p); 500 | if (d < upperDist) { 501 | upperDist = d; 502 | upperInt = p; 503 | upperIndex = j; 504 | } 505 | } 506 | } 507 | } 508 | 509 | // if there are no vertices to connect to, choose a point in the middle 510 | if (lowerIndex === (upperIndex + 1) % polygon.length) { 511 | //console.log("Case 1: Vertex("+i+"), lowerIndex("+lowerIndex+"), upperIndex("+upperIndex+"), poly.size("+polygon.length+")"); 512 | p[0] = (lowerInt[0] + upperInt[0]) / 2; 513 | p[1] = (lowerInt[1] + upperInt[1]) / 2; 514 | steinerPoints.push(p); 515 | 516 | if (i < upperIndex) { 517 | //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.begin() + upperIndex + 1); 518 | polygonAppend(lowerPoly, poly, i, upperIndex+1); 519 | lowerPoly.push(p); 520 | upperPoly.push(p); 521 | if (lowerIndex !== 0){ 522 | //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.end()); 523 | polygonAppend(upperPoly, poly,lowerIndex,poly.length); 524 | } 525 | //upperPoly.insert(upperPoly.end(), poly.begin(), poly.begin() + i + 1); 526 | polygonAppend(upperPoly, poly,0,i+1); 527 | } else { 528 | if (i !== 0){ 529 | //lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.end()); 530 | polygonAppend(lowerPoly, poly,i,poly.length); 531 | } 532 | //lowerPoly.insert(lowerPoly.end(), poly.begin(), poly.begin() + upperIndex + 1); 533 | polygonAppend(lowerPoly, poly,0,upperIndex+1); 534 | lowerPoly.push(p); 535 | upperPoly.push(p); 536 | //upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.begin() + i + 1); 537 | polygonAppend(upperPoly, poly,lowerIndex,i+1); 538 | } 539 | } else { 540 | // connect to the closest point within the triangle 541 | //console.log("Case 2: Vertex("+i+"), closestIndex("+closestIndex+"), poly.size("+polygon.length+")\n"); 542 | 543 | if (lowerIndex > upperIndex) { 544 | upperIndex += polygon.length; 545 | } 546 | closestDist = Number.MAX_VALUE; 547 | 548 | if(upperIndex < lowerIndex){ 549 | return result; 550 | } 551 | 552 | for (var j = lowerIndex; j <= upperIndex; ++j) { 553 | if ( 554 | isLeftOn(polygonAt(poly, i - 1), polygonAt(poly, i), polygonAt(poly, j)) && 555 | isRightOn(polygonAt(poly, i + 1), polygonAt(poly, i), polygonAt(poly, j)) 556 | ) { 557 | d = sqdist(polygonAt(poly, i), polygonAt(poly, j)); 558 | if (d < closestDist && polygonCanSee2(poly, i, j)) { 559 | closestDist = d; 560 | closestIndex = j % polygon.length; 561 | } 562 | } 563 | } 564 | 565 | if (i < closestIndex) { 566 | polygonAppend(lowerPoly, poly,i,closestIndex+1); 567 | if (closestIndex !== 0){ 568 | polygonAppend(upperPoly, poly,closestIndex,v.length); 569 | } 570 | polygonAppend(upperPoly, poly,0,i+1); 571 | } else { 572 | if (i !== 0){ 573 | polygonAppend(lowerPoly, poly,i,v.length); 574 | } 575 | polygonAppend(lowerPoly, poly,0,closestIndex+1); 576 | polygonAppend(upperPoly, poly,closestIndex,i+1); 577 | } 578 | } 579 | 580 | // solve smallest poly first 581 | if (lowerPoly.length < upperPoly.length) { 582 | polygonQuickDecomp(lowerPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level); 583 | polygonQuickDecomp(upperPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level); 584 | } else { 585 | polygonQuickDecomp(upperPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level); 586 | polygonQuickDecomp(lowerPoly,result,reflexVertices,steinerPoints,delta,maxlevel,level); 587 | } 588 | 589 | return result; 590 | } 591 | } 592 | result.push(polygon); 593 | 594 | return result; 595 | } 596 | 597 | /** 598 | * Remove collinear points in the polygon. 599 | * @method removeCollinearPoints 600 | * @param {Number} [precision] The threshold angle to use when determining whether two edges are collinear. Use zero for finest precision. 601 | * @return {Number} The number of points removed 602 | */ 603 | function polygonRemoveCollinearPoints(polygon, precision){ 604 | var num = 0; 605 | for(var i=polygon.length-1; polygon.length>3 && i>=0; --i){ 606 | if(collinear(polygonAt(polygon, i-1),polygonAt(polygon, i),polygonAt(polygon, i+1),precision)){ 607 | // Remove the middle point 608 | polygon.splice(i%polygon.length,1); 609 | num++; 610 | } 611 | } 612 | return num; 613 | } 614 | 615 | /** 616 | * Remove duplicate points in the polygon. 617 | * @method removeDuplicatePoints 618 | * @param {Number} [precision] The threshold to use when determining whether two points are the same. Use zero for best precision. 619 | */ 620 | function polygonRemoveDuplicatePoints(polygon, precision){ 621 | for(var i=polygon.length-1; i>=1; --i){ 622 | var pi = polygon[i]; 623 | for(var j=i-1; j>=0; --j){ 624 | if(points_eq(pi, polygon[j], precision)){ 625 | polygon.splice(i,1); 626 | continue; 627 | } 628 | } 629 | } 630 | } 631 | 632 | /** 633 | * Check if two scalars are equal 634 | * @static 635 | * @method eq 636 | * @param {Number} a 637 | * @param {Number} b 638 | * @param {Number} [precision] 639 | * @return {Boolean} 640 | */ 641 | function scalar_eq(a,b,precision){ 642 | precision = precision || 0; 643 | return Math.abs(a-b) <= precision; 644 | } 645 | 646 | /** 647 | * Check if two points are equal 648 | * @static 649 | * @method points_eq 650 | * @param {Array} a 651 | * @param {Array} b 652 | * @param {Number} [precision] 653 | * @return {Boolean} 654 | */ 655 | function points_eq(a,b,precision){ 656 | return scalar_eq(a[0],b[0],precision) && scalar_eq(a[1],b[1],precision); 657 | } 658 | -------------------------------------------------------------------------------- /dat.gui.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dat-gui JavaScript Controller Library 3 | * http://code.google.com/p/dat-gui 4 | * 5 | * Copyright 2011 Data Arts Team, Google Creative Lab 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(e,a){var a=a||document,c=a.createElement("link");c.type="text/css";c.rel="stylesheet";c.href=e;a.getElementsByTagName("head")[0].appendChild(c)},inject:function(e,a){var a=a||document,c=document.createElement("style");c.type="text/css";c.innerHTML=e;a.getElementsByTagName("head")[0].appendChild(c)}}}(); 14 | dat.utils.common=function(){var e=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(c){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(a[f])||(c[f]=a[f])},this);return c},defaults:function(c){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(c[f])&&(c[f]=a[f])},this);return c},compose:function(){var c=a.call(arguments);return function(){for(var d=a.call(arguments),f=c.length-1;f>=0;f--)d=[c[f].apply(this,d)];return d[0]}}, 15 | each:function(a,d,f){if(e&&a.forEach===e)a.forEach(d,f);else if(a.length===a.length+0)for(var b=0,n=a.length;b-1?d.length-d.indexOf(".")-1:0};c.superclass=e;a.extend(c.prototype,e.prototype,{setValue:function(a){if(this.__min!==void 0&&athis.__max)a=this.__max;this.__step!==void 0&&a%this.__step!=0&&(a=Math.round(a/this.__step)*this.__step);return c.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__step=a;return this}});return c}(dat.controllers.Controller,dat.utils.common); 29 | dat.controllers.NumberControllerBox=function(e,a,c){var d=function(f,b,e){function h(){var a=parseFloat(l.__input.value);c.isNaN(a)||l.setValue(a)}function j(a){var b=o-a.clientY;l.setValue(l.getValue()+b*l.__impliedStep);o=a.clientY}function m(){a.unbind(window,"mousemove",j);a.unbind(window,"mouseup",m)}this.__truncationSuspended=false;d.superclass.call(this,f,b,e);var l=this,o;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",h); 30 | a.bind(this.__input,"blur",function(){h();l.__onFinishChange&&l.__onFinishChange.call(l,l.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",j);a.bind(window,"mouseup",m);o=b.clientY});a.bind(this.__input,"keydown",function(a){if(a.keyCode===13)l.__truncationSuspended=true,this.blur(),l.__truncationSuspended=false});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;c.extend(d.prototype,e.prototype,{updateDisplay:function(){var a=this.__input, 31 | b;if(this.__truncationSuspended)b=this.getValue();else{b=this.getValue();var c=Math.pow(10,this.__precision);b=Math.round(b*c)/c}a.value=b;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); 32 | dat.controllers.NumberControllerSlider=function(e,a,c,d,f){var b=function(d,c,f,e,l){function o(b){b.preventDefault();var d=a.getOffset(g.__background),c=a.getWidth(g.__background);g.setValue(g.__min+(g.__max-g.__min)*((b.clientX-d.left)/(d.left+c-d.left)));return false}function y(){a.unbind(window,"mousemove",o);a.unbind(window,"mouseup",y);g.__onFinishChange&&g.__onFinishChange.call(g,g.getValue())}b.superclass.call(this,d,c,{min:f,max:e,step:l});var g=this;this.__background=document.createElement("div"); 33 | this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",o);a.bind(window,"mouseup",y);o(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};b.superclass=e;b.useDefaultStyles=function(){c.inject(f)};d.extend(b.prototype,e.prototype,{updateDisplay:function(){this.__foreground.style.width= 34 | (this.getValue()-this.__min)/(this.__max-this.__min)*100+"%";return b.superclass.prototype.updateDisplay.call(this)}});return b}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,".slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); 35 | dat.controllers.FunctionController=function(e,a,c){var d=function(c,b,e){d.superclass.call(this,c,b);var h=this;this.__button=document.createElement("div");this.__button.innerHTML=e===void 0?"Fire":e;a.bind(this.__button,"click",function(a){a.preventDefault();h.fire();return false});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};d.superclass=e;c.extend(d.prototype,e.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.__onFinishChange&&this.__onFinishChange.call(this, 36 | this.getValue());this.getValue().call(this.object)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 37 | dat.controllers.BooleanController=function(e,a,c){var d=function(c,b){d.superclass.call(this,c,b);var e=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){e.setValue(!e.__prev)},false);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};d.superclass=e;c.extend(d.prototype,e.prototype,{setValue:function(a){a=d.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& 38 | this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){this.getValue()===true?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=true):this.__checkbox.checked=false;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 39 | dat.color.toString=function(e){return function(a){if(a.a==1||e.isUndefined(a.a)){for(a=a.hex.toString(16);a.length<6;)a="0"+a;return"#"+a}else return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); 40 | dat.color.interpret=function(e,a){var c,d,f=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return a===null?false:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:e},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return a===null?false:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:e},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); 41 | return a===null?false:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:e},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return a===null?false:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:e}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return a.length!= 42 | 3?false:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return a.length!=4?false:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:false},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&& 43 | a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:false},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:false},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:false},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){d= 44 | false;var b=arguments.length>1?a.toArray(arguments):arguments[0];a.each(f,function(e){if(e.litmus(b))return a.each(e.conversions,function(e,f){c=e.read(b);if(d===false&&c!==false)return d=c,c.conversionName=f,c.conversion=e,a.BREAK}),a.BREAK});return d}}(dat.color.toString,dat.utils.common); 45 | dat.GUI=dat.gui.GUI=function(e,a,c,d,f,b,n,h,j,m,l,o,y,g,i){function q(a,b,r,c){if(b[r]===void 0)throw Error("Object "+b+' has no property "'+r+'"');c.color?b=new l(b,r):(b=[b,r].concat(c.factoryArgs),b=d.apply(a,b));if(c.before instanceof f)c.before=c.before.__li;t(a,b);g.addClass(b.domElement,"c");r=document.createElement("span");g.addClass(r,"property-name");r.innerHTML=b.property;var e=document.createElement("div");e.appendChild(r);e.appendChild(b.domElement);c=s(a,e,c.before);g.addClass(c,k.CLASS_CONTROLLER_ROW); 46 | g.addClass(c,typeof b.getValue());p(a,c,b);a.__controllers.push(b);return b}function s(a,b,d){var c=document.createElement("li");b&&c.appendChild(b);d?a.__ul.insertBefore(c,params.before):a.__ul.appendChild(c);a.onResize();return c}function p(a,d,c){c.__li=d;c.__gui=a;i.extend(c,{options:function(b){if(arguments.length>1)return c.remove(),q(a,c.object,c.property,{before:c.__li.nextElementSibling,factoryArgs:[i.toArray(arguments)]});if(i.isArray(b)||i.isObject(b))return c.remove(),q(a,c.object,c.property, 47 | {before:c.__li.nextElementSibling,factoryArgs:[b]})},name:function(a){c.__li.firstElementChild.firstElementChild.innerHTML=a;return c},listen:function(){c.__gui.listen(c);return c},remove:function(){c.__gui.remove(c);return c}});if(c instanceof j){var e=new h(c.object,c.property,{min:c.__min,max:c.__max,step:c.__step});i.each(["updateDisplay","onChange","onFinishChange"],function(a){var b=c[a],H=e[a];c[a]=e[a]=function(){var a=Array.prototype.slice.call(arguments);b.apply(c,a);return H.apply(e,a)}}); 48 | g.addClass(d,"has-slider");c.domElement.insertBefore(e.domElement,c.domElement.firstElementChild)}else if(c instanceof h){var f=function(b){return i.isNumber(c.__min)&&i.isNumber(c.__max)?(c.remove(),q(a,c.object,c.property,{before:c.__li.nextElementSibling,factoryArgs:[c.__min,c.__max,c.__step]})):b};c.min=i.compose(f,c.min);c.max=i.compose(f,c.max)}else if(c instanceof b)g.bind(d,"click",function(){g.fakeEvent(c.__checkbox,"click")}),g.bind(c.__checkbox,"click",function(a){a.stopPropagation()}); 49 | else if(c instanceof n)g.bind(d,"click",function(){g.fakeEvent(c.__button,"click")}),g.bind(d,"mouseover",function(){g.addClass(c.__button,"hover")}),g.bind(d,"mouseout",function(){g.removeClass(c.__button,"hover")});else if(c instanceof l)g.addClass(d,"color"),c.updateDisplay=i.compose(function(a){d.style.borderLeftColor=c.__color.toString();return a},c.updateDisplay),c.updateDisplay();c.setValue=i.compose(function(b){a.getRoot().__preset_select&&c.isModified()&&B(a.getRoot(),true);return b},c.setValue)} 50 | function t(a,b){var c=a.getRoot(),d=c.__rememberedObjects.indexOf(b.object);if(d!=-1){var e=c.__rememberedObjectIndecesToControllers[d];e===void 0&&(e={},c.__rememberedObjectIndecesToControllers[d]=e);e[b.property]=b;if(c.load&&c.load.remembered){c=c.load.remembered;if(c[a.preset])c=c[a.preset];else if(c[w])c=c[w];else return;if(c[d]&&c[d][b.property]!==void 0)d=c[d][b.property],b.initialValue=d,b.setValue(d)}}}function I(a){var b=a.__save_row=document.createElement("li");g.addClass(a.domElement, 51 | "has-save");a.__ul.insertBefore(b,a.__ul.firstChild);g.addClass(b,"save-row");var c=document.createElement("span");c.innerHTML=" ";g.addClass(c,"button gears");var d=document.createElement("span");d.innerHTML="Save";g.addClass(d,"button");g.addClass(d,"save");var e=document.createElement("span");e.innerHTML="New";g.addClass(e,"button");g.addClass(e,"save-as");var f=document.createElement("span");f.innerHTML="Revert";g.addClass(f,"button");g.addClass(f,"revert");var m=a.__preset_select=document.createElement("select"); 52 | a.load&&a.load.remembered?i.each(a.load.remembered,function(b,c){C(a,c,c==a.preset)}):C(a,w,false);g.bind(m,"change",function(){for(var b=0;b0){a.preset=this.preset;if(!a.remembered)a.remembered={};a.remembered[this.preset]=z(this)}a.folders={};i.each(this.__folders,function(b, 69 | c){a.folders[c]=b.getSaveObject()});return a},save:function(){if(!this.load.remembered)this.load.remembered={};this.load.remembered[this.preset]=z(this);B(this,false)},saveAs:function(a){if(!this.load.remembered)this.load.remembered={},this.load.remembered[w]=z(this,true);this.load.remembered[a]=z(this);this.preset=a;C(this,a,true)},revert:function(a){i.each(this.__controllers,function(b){this.getRoot().load.remembered?t(a||this.getRoot(),b):b.setValue(b.initialValue)},this);i.each(this.__folders, 70 | function(a){a.revert(a)});a||B(this.getRoot(),false)},listen:function(a){var b=this.__listening.length==0;this.__listening.push(a);b&&E(this.__listening)}});return k}(dat.utils.css,'
\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n
', 71 | ".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear;border:0;position:absolute;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-x:hidden}.dg.a.has-save ul{margin-top:27px}.dg.a.has-save ul.closed{margin-top:0}.dg.a .save-row{position:fixed;top:0;z-index:1002}.dg li{-webkit-transition:height 0.1s ease-out;-o-transition:height 0.1s ease-out;-moz-transition:height 0.1s ease-out;transition:height 0.1s ease-out}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;overflow:hidden;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li > *{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:9px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2fa1d6}.dg .cr.number input[type=text]{color:#2fa1d6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2fa1d6}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n", 72 | dat.controllers.factory=function(e,a,c,d,f,b,n){return function(h,j,m,l){var o=h[j];if(n.isArray(m)||n.isObject(m))return new e(h,j,m);if(n.isNumber(o))return n.isNumber(m)&&n.isNumber(l)?new c(h,j,m,l):new a(h,j,{min:m,max:l});if(n.isString(o))return new d(h,j);if(n.isFunction(o))return new f(h,j,"");if(n.isBoolean(o))return new b(h,j)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(e,a,c){var d= 73 | function(c,b){function e(){h.setValue(h.__input.value)}d.superclass.call(this,c,b);var h=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",e);a.bind(this.__input,"change",e);a.bind(this.__input,"blur",function(){h.__onFinishChange&&h.__onFinishChange.call(h,h.getValue())});a.bind(this.__input,"keydown",function(a){a.keyCode===13&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;c.extend(d.prototype, 74 | e.prototype,{updateDisplay:function(){if(!a.isActive(this.__input))this.__input.value=this.getValue();return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, 75 | dat.controllers.ColorController=function(e,a,c,d,f){function b(a,b,c,d){a.style.background="";f.each(j,function(e){a.style.cssText+="background: "+e+"linear-gradient("+b+", "+c+" 0%, "+d+" 100%); "})}function n(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; 76 | a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var h=function(e,l){function o(b){q(b);a.bind(window,"mousemove",q);a.bind(window, 77 | "mouseup",j)}function j(){a.unbind(window,"mousemove",q);a.unbind(window,"mouseup",j)}function g(){var a=d(this.value);a!==false?(p.__color.__state=a,p.setValue(p.__color.toOriginal())):this.value=p.__color.toString()}function i(){a.unbind(window,"mousemove",s);a.unbind(window,"mouseup",i)}function q(b){b.preventDefault();var c=a.getWidth(p.__saturation_field),d=a.getOffset(p.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c,b=1-(b.clientY-d.top+document.body.scrollTop)/c;b>1?b= 78 | 1:b<0&&(b=0);e>1?e=1:e<0&&(e=0);p.__color.v=b;p.__color.s=e;p.setValue(p.__color.toOriginal());return false}function s(b){b.preventDefault();var c=a.getHeight(p.__hue_field),d=a.getOffset(p.__hue_field),b=1-(b.clientY-d.top+document.body.scrollTop)/c;b>1?b=1:b<0&&(b=0);p.__color.h=b*360;p.setValue(p.__color.toOriginal());return false}h.superclass.call(this,e,l);this.__color=new c(this.getValue());this.__temp=new c(0);var p=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement, 79 | false);this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input= 80 | document.createElement("input");this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){a.keyCode===13&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(){a.addClass(this,"drag").bind(window,"mouseup",function(){a.removeClass(p.__selector,"drag")})});var t=document.createElement("div");f.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"}); 81 | f.extend(this.__field_knob.style,{position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(this.__color.v<0.5?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});f.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});f.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});f.extend(t.style, 82 | {width:"100%",height:"100%",background:"none"});b(t,"top","rgba(0,0,0,0)","#000");f.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});n(this.__hue_field);f.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",o);a.bind(this.__field_knob,"mousedown",o);a.bind(this.__hue_field,"mousedown", 83 | function(b){s(b);a.bind(window,"mousemove",s);a.bind(window,"mouseup",i)});this.__saturation_field.appendChild(t);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};h.superclass=e;f.extend(h.prototype,e.prototype,{updateDisplay:function(){var a=d(this.getValue()); 84 | if(a!==false){var e=false;f.each(c.COMPONENTS,function(b){if(!f.isUndefined(a[b])&&!f.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return e=true,{}},this);e&&f.extend(this.__color.__state,a)}f.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var h=this.__color.v<0.5||this.__color.s>0.5?255:0,j=255-h;f.extend(this.__field_knob.style,{marginLeft:100*this.__color.s-7+"px",marginTop:100*(1-this.__color.v)-7+"px",backgroundColor:this.__temp.toString(),border:this.__field_knob_border+ 85 | "rgb("+h+","+h+","+h+")"});this.__hue_knob.style.marginTop=(1-this.__color.h/360)*100+"px";this.__temp.s=1;this.__temp.v=1;b(this.__saturation_field,"left","#fff",this.__temp.toString());f.extend(this.__input.style,{backgroundColor:this.__input.value=this.__color.toString(),color:"rgb("+h+","+h+","+h+")",textShadow:this.__input_textShadow+"rgba("+j+","+j+","+j+",.7)"})}});var j=["-moz-","-o-","-webkit-","-ms-",""];return h}(dat.controllers.Controller,dat.dom.dom,dat.color.Color=function(e,a,c,d){function f(a, 86 | b,c){Object.defineProperty(a,b,{get:function(){if(this.__state.space==="RGB")return this.__state[b];n(this,b,c);return this.__state[b]},set:function(a){if(this.__state.space!=="RGB")n(this,b,c),this.__state.space="RGB";this.__state[b]=a}})}function b(a,b){Object.defineProperty(a,b,{get:function(){if(this.__state.space==="HSV")return this.__state[b];h(this);return this.__state[b]},set:function(a){if(this.__state.space!=="HSV")h(this),this.__state.space="HSV";this.__state[b]=a}})}function n(b,c,e){if(b.__state.space=== 87 | "HEX")b.__state[c]=a.component_from_hex(b.__state.hex,e);else if(b.__state.space==="HSV")d.extend(b.__state,a.hsv_to_rgb(b.__state.h,b.__state.s,b.__state.v));else throw"Corrupted color state";}function h(b){var c=a.rgb_to_hsv(b.r,b.g,b.b);d.extend(b.__state,{s:c.s,v:c.v});if(d.isNaN(c.h)){if(d.isUndefined(b.__state.h))b.__state.h=0}else b.__state.h=c.h}var j=function(){this.__state=e.apply(this,arguments);if(this.__state===false)throw"Failed to interpret color arguments";this.__state.a=this.__state.a|| 88 | 1};j.COMPONENTS="r,g,b,h,s,v,hex,a".split(",");d.extend(j.prototype,{toString:function(){return c(this)},toOriginal:function(){return this.__state.conversion.write(this)}});f(j.prototype,"r",2);f(j.prototype,"g",1);f(j.prototype,"b",0);b(j.prototype,"h");b(j.prototype,"s");b(j.prototype,"v");Object.defineProperty(j.prototype,"a",{get:function(){return this.__state.a},set:function(a){this.__state.a=a}});Object.defineProperty(j.prototype,"hex",{get:function(){if(!this.__state.space!=="HEX")this.__state.hex= 89 | a.rgb_to_hex(this.r,this.g,this.b);return this.__state.hex},set:function(a){this.__state.space="HEX";this.__state.hex=a}});return j}(dat.color.interpret,dat.color.math=function(){var e;return{hsv_to_rgb:function(a,c,d){var e=a/60-Math.floor(a/60),b=d*(1-c),n=d*(1-e*c),c=d*(1-(1-e)*c),a=[[d,c,b],[n,d,b],[b,d,c],[b,n,d],[c,b,d],[d,b,n]][Math.floor(a/60)%6];return{r:a[0]*255,g:a[1]*255,b:a[2]*255}},rgb_to_hsv:function(a,c,d){var e=Math.min(a,c,d),b=Math.max(a,c,d),e=b-e;if(b==0)return{h:NaN,s:0,v:0}; 90 | a=a==b?(c-d)/e:c==b?2+(d-a)/e:4+(a-c)/e;a/=6;a<0&&(a+=1);return{h:a*360,s:e/b,v:b/255}},rgb_to_hex:function(a,c,d){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,c);return a=this.hex_with_component(a,0,d)},component_from_hex:function(a,c){return a>>c*8&255},hex_with_component:function(a,c,d){return d<<(e=c*8)|a&~(255<