├── README.md ├── bundle.js ├── dust.css ├── dust.js ├── index.html ├── package.json ├── pressure.js └── webpack.config.js /README.md: -------------------------------------------------------------------------------- 1 | # dust 2 | 3 | "Imagine the cool phenomenon when the wind blows the falling leaves. 4 | This game simulates the phenomenon with powder (dots)!" -DAN-BALL 5 | 6 | HTML5 Powder Game 7 | 8 | Inspired by Burning Sand, Powder Toy, and Powder Game 9 | 10 | Navier Stokes implementation adapted from Oliver's Simple Fluid Dynamics Simulator: https://nerget.com/fluidSim/ 11 | Which itself is based on Jos Stam's GDC03 paper: http://www.dgp.toronto.edu/people/stam/reality/Research/pdf/GDC03.pdf 12 | -------------------------------------------------------------------------------- /bundle.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) 10 | /******/ return installedModules[moduleId].exports; 11 | 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ exports: {}, 15 | /******/ id: moduleId, 16 | /******/ loaded: false 17 | /******/ }; 18 | 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | 22 | /******/ // Flag the module as loaded 23 | /******/ module.loaded = true; 24 | 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | 29 | 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | 36 | /******/ // __webpack_public_path__ 37 | /******/ __webpack_require__.p = ""; 38 | 39 | /******/ // Load entry module and return exports 40 | /******/ return __webpack_require__(0); 41 | /******/ }) 42 | /************************************************************************/ 43 | /******/ ([ 44 | /* 0 */ 45 | /***/ function(module, exports, __webpack_require__) { 46 | 47 | var husl = __webpack_require__(1); 48 | var _ = __webpack_require__(4); 49 | var FluidField = __webpack_require__(5); 50 | 51 | 52 | // var Dust = (function() { 53 | // Constant properties 54 | var Type = { 55 | Dust: "Dust", 56 | Water: "Water", 57 | Brick: "Brick", 58 | Erase: "Erase", 59 | Tap: "Tap", 60 | Air: "Air", 61 | Fire: "Fire", 62 | Glitch: "Glitch", 63 | Powder: "Powder", 64 | Nitro: "Nitro" 65 | }; 66 | var width = 100; 67 | var height = 100; 68 | var interval = 1000 / (40 /* fps */ ); 69 | var frame = 1; 70 | var color = 0; 71 | var Selection = Type.Dust; 72 | var MouseX = 0; 73 | var MouseY = 0; 74 | var particles = []; 75 | var Grid = new Array(100); 76 | for (var y = 0; y < 100; y++) Grid[y] = new Array(100); 77 | for (var x = 0; x < 100; x++) { 78 | for (var y = 0; y < 100; y++) { 79 | Grid[x][y] = 0; 80 | } 81 | } 82 | var Adjacent = [ 83 | [0, 1], 84 | [-1, 0], 85 | [1, 0], 86 | [0, -1] 87 | ]; 88 | var PortField; 89 | var mouseIsDown = false; 90 | var canvas = document.getElementById('display'); 91 | //############## 92 | var force = 5; 93 | var source = 100; 94 | var sources = []; 95 | sources.push([250, 250]); 96 | var omx, omy; 97 | var mx, my; 98 | var res; 99 | var displaySize = 500; 100 | var fieldRes; 101 | var canvas; 102 | var DisplayFunc; 103 | 104 | function prepareFrame(field) { 105 | if ((omx >= 0 && omx < displaySize && omy >= 0 && omy < displaySize) && mouseIsDown && Selection === Type.Air) { 106 | var dx = mx - omx; 107 | var dy = my - omy; 108 | var length = (Math.sqrt(dx * dx + dy * dy) + 0.5) | 0; 109 | if (length < 1) length = 1; 110 | for (var i = 0; i < length; i++) { 111 | var x = (((omx + dx * (i / length)) / displaySize) * field.width()) | 0 112 | var y = (((omy + dy * (i / length)) / displaySize) * field.height()) | 0; 113 | field.setVelocity(x, y, dx, dy); 114 | field.setDensity(x, y, 50); 115 | } 116 | omx = mx; 117 | omy = my; 118 | } 119 | for (var i = 0; i < sources.length; i++) { 120 | var x = ((sources[i][0] / displaySize) * field.width()) | 0; 121 | var y = ((sources[i][1] / displaySize) * field.height()) | 0; 122 | field.setDensity(x, y, 30); 123 | } 124 | } 125 | 126 | function updatePressure() { 127 | field.update(); 128 | } 129 | // window.onload = function() { 130 | var field = new FluidField(canvas); 131 | // document.getElementById("iterations").value = 10; 132 | res = 100; 133 | field.setUICallback(prepareFrame); 134 | fieldRes = res; 135 | field.setResolution(res, res); 136 | 137 | function getTopLeftOfElement(element) { 138 | var top = 0; 139 | var left = 0; 140 | do { 141 | top += element.offsetTop; 142 | left += element.offsetLeft; 143 | } while (element = element.offsetParent); 144 | return { 145 | left: left, 146 | top: top 147 | }; 148 | } 149 | // canvas.style.cursor = "none"; 150 | function getMousePos(canvas, evt) { 151 | var rect = canvas.getBoundingClientRect(); 152 | return { 153 | x: evt.clientX - rect.left, 154 | y: evt.clientY - rect.top 155 | }; 156 | } 157 | canvas.addEventListener('mousemove', function(evt) { 158 | var mousePos = getMousePos(canvas, evt); 159 | MouseX = Math.min(Math.round(mousePos.x / 5), 99); 160 | MouseY = Math.min(Math.round(mousePos.y / 5), 99); 161 | var o = getTopLeftOfElement(canvas); 162 | mx = evt.clientX - o.left; 163 | my = evt.clientY - o.top; 164 | Cursor(); 165 | }, false); 166 | canvas.onmousedown = function(e) { 167 | e.preventDefault(); 168 | mouseIsDown = true; 169 | var o = getTopLeftOfElement(canvas); 170 | omx = mx = e.clientX - o.left; 171 | omy = my = e.clientY - o.top; 172 | } 173 | canvas.onmouseup = function(e) { 174 | mouseIsDown = false; 175 | } 176 | 177 | function Cursor() { 178 | if (mouseIsDown && (Selection != Type.Air)) { 179 | if (Selection === Type.Erase) { 180 | if (Grid[MouseX][MouseY] != 0) Grid[MouseX][MouseY].remove(); 181 | } else if (Grid[MouseX][MouseY] === 0) new ptc(MouseX, MouseY, Selection); 182 | } 183 | } 184 | window.addEventListener("keydown", onKeyDown, false); 185 | 186 | function KeyEvent(keyCode) { 187 | switch (keyCode) { 188 | case 81: //q 189 | Selection = Type.Dust; 190 | break; 191 | case 87: //w 192 | Selection = Type.Water; 193 | break; 194 | case 82: //r 195 | Selection = Type.Brick; 196 | break; 197 | case 69: //e 198 | Selection = Type.Erase; 199 | break; 200 | case 84: //t 201 | Selection = Type.Tap; 202 | break; 203 | case 65: //a 204 | Selection = Type.Air; 205 | break; 206 | case 70: //f 207 | Selection = Type.Fire; 208 | break; 209 | case 71: //g 210 | Selection = Type.Glitch; 211 | break; 212 | case 88: //g 213 | Selection = Type.Nitro; 214 | break; 215 | case 90: //g 216 | Selection = Type.Powder; 217 | break; 218 | } 219 | } 220 | 221 | function onKeyDown(event) { 222 | var keyCode = event.keyCode; 223 | console.log(keyCode); 224 | KeyEvent(keyCode); 225 | } 226 | 227 | function ptc(x, y, type) { 228 | if (Grid[x][y] != 0) return; 229 | this.x = x; 230 | this.y = y; 231 | Grid[x][y] = this; 232 | this.type = type; 233 | this.SpawnType = type; 234 | this.life = 100; 235 | this.flamable = 0; 236 | switch (type) { 237 | case Type.Dust: //d 238 | h = color += .2; 239 | s = 360; 240 | l = 50; 241 | d = 20; 242 | this.flamable = 1; 243 | break; 244 | case Type.Water: //d 245 | h = 205; 246 | s = 360; 247 | l = 100; 248 | d = 20; 249 | break; 250 | case Type.Brick: //d 251 | h = 12; 252 | s = 77; 253 | l = 30; 254 | d = 0; 255 | break; 256 | case Type.Tap: //d 257 | h = 12; 258 | s = 60; 259 | l = 99; 260 | d = 0; 261 | case Type.Fire: //d 262 | h = 10; 263 | s = 360; 264 | l = 99; 265 | d = 40; 266 | this.life = (Math.random() * 50) | 0; 267 | break; 268 | case Type.Glitch: //d 269 | h = Math.random() * 360 | 0; 270 | s = 100; 271 | l = Math.random() * 100 | 0; 272 | d = 0; 273 | break; 274 | case Type.Powder: //d 275 | h = 50; 276 | s = 10; 277 | l = 20; 278 | d = 35; 279 | this.life = 1; 280 | this.flamable = 6; 281 | break; 282 | case Type.Nitro: //d 283 | h = 130; 284 | s = 100; 285 | l = 50; 286 | d = 15; 287 | this.life = 1; 288 | this.flamable = 10; 289 | break; 290 | } 291 | this.D = d; 292 | this.color = husl.p.toRGB(Math.round(h), s, l); 293 | particles.push(this); 294 | }; 295 | ptc.prototype = { 296 | remove: function() { 297 | var i = particles.indexOf(this); 298 | if (i > -1) particles.splice(i, 1); 299 | Grid[this.x][this.y] = 0; 300 | }, 301 | wind: function(dx, dy) { 302 | dx = Math.round((.7 * dx) + Math.random() * this.D * PortField.getXVelocity(this.x, this.y)); 303 | dy = Math.round((.7 * dy) + Math.random() * this.D * PortField.getYVelocity(this.x, this.y)); 304 | if (dx != 0) dx = (dx / Math.abs(dx)) | 0; 305 | if (dy != 0) dy = (dy / Math.abs(dy)) | 0; 306 | return ([dx, dy]); 307 | }, 308 | tick: function(dir) { 309 | var dy = 0; 310 | var dx = 0; 311 | var delta = [0, 0]; 312 | switch (this.type) { 313 | case Type.Dust: //d 314 | delta = this.wind(0, 1); 315 | break; 316 | case Type.Water: //d 317 | tdx = Math.floor(-1 + Math.random() * 3); //(Math.random() > .5 ? -1 : 1); 318 | delta = this.wind(tdx, 1); 319 | this.color = husl.p.toRGB(205, 360, Math.floor((Math.random() * 40) + 30)); 320 | break; 321 | case Type.Powder: //d 322 | delta = this.wind(0, 1); 323 | break; 324 | case Type.Nitro: //d 325 | tdx = Math.floor(-1 + Math.random() * 3); //(Math.random() > .5 ? -1 : 1); 326 | delta = this.wind(tdx, 1); 327 | break; 328 | case Type.Brick: //d 329 | return; 330 | break; 331 | case Type.Tap: //d 332 | this.Tap(); 333 | return; 334 | break; 335 | case Type.Glitch: //d 336 | this.Glitch(); 337 | return; 338 | break; 339 | case Type.Fire: //d 340 | tdx = Math.floor(-1 + Math.random() * 3); //(Math.random() > .5 ? -1 : 1); 341 | delta = this.wind(0, 0); 342 | this.color = husl.p.toRGB(Math.floor(Math.random() * 20) + 10, 360, Math.floor((Math.random() * 40) + 40)); 343 | this.Burn(); 344 | this.life -= 1; 345 | if (this.life < 0) { 346 | this.remove(); 347 | return 348 | } 349 | break; 350 | } 351 | dx = delta[0]; 352 | dy = delta[1]; 353 | if ((this.x + dx) < 0 || (this.x + dx) > 99 || this.y + dy < 0) { 354 | this.remove(); 355 | return; 356 | } 357 | if ((this.y + dy) > 99) dy = 0; 358 | if (this.Move(0, dy) && (this.type === Type.Water || this.type === Type.Fire) && Math.random() > .2) dx = 0; // water spread 359 | if (!this.Move(dx, 0)) this.Move(-dx, 0); //fix this later 360 | if (this.type === Type.Dust && (this.y + dy < 100) && (this.y + dy > 0) && Grid[this.x][this.y + dy].type === Type.Water) { 361 | temp = Grid[this.x][this.y + dy]; 362 | Grid[this.x][this.y + dy] = this; 363 | Grid[this.x][this.y] = temp; 364 | temp.y -= dy; 365 | this.y += dy; 366 | } 367 | if (this.type === Type.Water && (this.y + dy < 100) && (this.y + dy > 0) && Grid[this.x][this.y + dy].type === Type.Nitro) { 368 | temp = Grid[this.x][this.y + dy]; 369 | Grid[this.x][this.y + dy] = this; 370 | Grid[this.x][this.y] = temp; 371 | temp.y -= dy; 372 | this.y += dy; 373 | } 374 | return; 375 | }, 376 | Move: function(dx, dy) { 377 | if ((this.x + dx) < 0 || ((this.x + dx) > 99)) { 378 | this.remove(); 379 | return (1); 380 | } 381 | if (Grid[this.x + dx][this.y + dy] === 0) { 382 | Grid[this.x + dx][this.y + dy] = this; 383 | Grid[this.x][this.y] = 0; 384 | this.x += dx; 385 | this.y += dy; 386 | return (1); 387 | } 388 | return (0); 389 | }, 390 | Burn: function() { 391 | for (var i = 0; i < 4; i++) { 392 | if (0 > this.x + Adjacent[i][0] || this.x + Adjacent[i][0] > 99 || 0 > this.y + Adjacent[i][1] || this.y + Adjacent[i][1] > 99) continue; 393 | adj = Grid[this.x + Adjacent[i][0]][this.y + Adjacent[i][1]]; 394 | if (adj != 0 && adj.flamable > 0) adj.life -= 3; 395 | if (adj.life < 0) { 396 | PortField.setVelocity(adj.x, adj.y, (Math.random() - .5) * 5 * adj.flamable, (Math.random() - .5) * 5 * adj.flamable); 397 | if (adj.type === Type.Nitro) { 398 | adj.type = Type.Fire; 399 | adj.Burn(); 400 | } 401 | adj.type = Type.Fire; 402 | adj.life = 40; 403 | // adj.Burn(); 404 | } 405 | if (adj != 0 && adj.type === Type.Water) this.life -= 10; 406 | } 407 | PortField.setDensity(this.x, this.y, 60); 408 | PortField.setVelocity(this.x, this.y, 0, -.2); 409 | }, 410 | Glitch: function() { 411 | if (Math.random() < .9) return; 412 | if (this.y < 98 && Grid[this.x][this.y + 1] == 0) new ptc(this.x, this.y + 1, Selection); 413 | else if (this.x < 98 && Grid[this.x + 1][this.y] === 0) new ptc(this.x + 1, this.y, Selection); 414 | else if (this.x > 1 && Grid[this.x - 1][this.y] === 0) new ptc(this.x - 1, this.y, Selection); 415 | else if (this.y > 1 && Grid[this.x][this.y - 1] === 0) new ptc(this.x, this.y - 1, Selection); 416 | }, 417 | Tap: function() { 418 | if (this.SpawnType === Type.Tap) { 419 | for (var i = 0; i < 4; i++) { 420 | if (0 > this.x + Adjacent[i][0] || this.x + Adjacent[i][0] > 99 || 0 > this.y + Adjacent[i][1] || this.y + Adjacent[i][1] > 99) continue; 421 | adj = Grid[this.x + Adjacent[i][0]][this.y + Adjacent[i][1]]; 422 | if (adj != 0 && adj.type != Type.Tap) this.SpawnType = adj.type; 423 | } 424 | } else { 425 | if (Math.random() < .6) return; 426 | if (this.y < 98 && Grid[this.x][this.y + 1] === 0) new ptc(this.x, this.y + 1, this.SpawnType); 427 | else if (this.x < 98 && Grid[this.x + 1][this.y] === 0) new ptc(this.x + 1, this.y, this.SpawnType); 428 | else if (this.x > 1 && Grid[this.x - 1][this.y] === 0) new ptc(this.x - 1, this.y, this.SpawnType); 429 | else if (this.y > 1 && Grid[this.x][this.y - 1] === 0) new ptc(this.x, this.y - 1, this.SpawnType); 430 | } 431 | } 432 | }; 433 | 434 | function init() { 435 | // for (var x = 0; x < 100; x++) { 436 | // for (var y = 0; y < 100; y++) { //spawn n fish and add them to list 437 | // new ptc(x,y*x%100, Type.Tap); // Math.random() * 360, 360, 50) 438 | // } 439 | // } 440 | // // var x = 50; //deprecated 441 | // // var y = 50; 442 | // // for (var i = 0; i < 100; i++) { //spawn n fish and add them to list 443 | // // x = Math.floor(Math.random() * (width - 60)) + 30; 444 | // // y = Math.floor(Math.random() * (height - 60)) + 30; 445 | // // new ptc(x, y, Type.Dust); // Math.random() * 360, 360, 50) 446 | // // } 447 | // // return; 448 | } 449 | 450 | function Dust(equation, canvas) { 451 | // init(); 452 | this.canvas = canvas; 453 | this.scale = 5 //canvas.getAttribute('width') / width; 454 | this.context = canvas.getContext('2d'); 455 | this.imageData = this.context.createImageData(width * this.scale, height * this.scale); 456 | this.then = +Date.now(); 457 | this.paused = false; 458 | field.setDisplayFunction(this.displayVelocity); 459 | this.drawFrame(); 460 | } 461 | Dust.prototype = { 462 | play: function() { 463 | this.paused = false; 464 | this.step(); 465 | }, 466 | pause: function() { 467 | this.paused = true; 468 | }, 469 | step: function() { 470 | if (this.paused) return; 471 | requestAnimFrame(this.step.bind(this)); 472 | var now = +Date.now(); 473 | var delta = now - this.then; 474 | if (delta > interval) { 475 | this.then = now; 476 | this.drawFrame(); 477 | frame++; 478 | } 479 | }, 480 | drawParticle: function(ptc) { 481 | var x = ptc.x; 482 | var y = ptc.y; 483 | var color = ptc.color; 484 | var R = Math.floor(color[0] * 255); // (color & 0xff0000) >>> 16; 485 | var G = Math.floor(color[1] * 255); //(color & 0x00ff00) >>> 8; 486 | var B = Math.floor(color[2] * 255); // (color & 0x0000ff) >>> 0; 487 | var Size = 0; 488 | // if(ptc.type==Type.Water && x>0 && x<98) 489 | // Size = Math.floor(Math.random()*2); 490 | for (var sx = -Size; sx < 5 + Size; sx++) { 491 | for (var sy = -Size; sy < 5 + Size; sy++) { 492 | var i = (((y * this.scale + (sy)) * width * this.scale) + (x * this.scale + (sx))) * 4; 493 | this.imageData.data[i] = R; // % 255; 494 | this.imageData.data[i + 1] = G; // % 255; 495 | this.imageData.data[i + 2] = B; // % 255; 496 | this.imageData.data[i + 3] = 255; 497 | } 498 | } 499 | }, 500 | displayVelocity: function(field) { 501 | PortField = field; 502 | context = canvas.getContext('2d'); 503 | context.save(); 504 | context.lineWidth = .1; 505 | scale = 5 506 | context.strokeStyle = "rgb(255,255,255)"; 507 | var vectorScale = 8; 508 | context.beginPath(); 509 | // console.log(field.getXVelocity(50, 50) + field.getYVelocity(50, 50)); 510 | for (var x = 0; x < field.width(); x++) { 511 | for (var y = 0; y < field.height(); y++) { 512 | if (Math.abs(field.getXVelocity(x, y) * field.getYVelocity(x, y)) > .003) { 513 | context.moveTo(x * scale + 0.5 * scale, y * scale + 0.5 * scale); 514 | context.lineTo((x + 0.5 + vectorScale * field.getXVelocity(x, y)) * scale, (y + 0.5 + vectorScale * field.getYVelocity(x, y)) * scale); 515 | } 516 | } 517 | } 518 | context.stroke(); 519 | context.restore(); 520 | }, 521 | drawFrame: function() { 522 | Cursor(); 523 | this.context.fillRect(0, 0, 500, 500); 524 | this.context.fillStyle = husl.p.toHex(40, 60, 2); ////t'#0010'+offset.toString(16); 525 | this.context.fill(); 526 | updatePressure(); 527 | this.imageData = this.context.getImageData(0, 0, 500, 500); 528 | for (var i = 0; i < particles.length; i++) { 529 | particles[i].tick(); 530 | if (particles[i]) this.drawParticle(particles[i]); 531 | } 532 | this.context.putImageData(this.imageData, 0, 0); 533 | document.getElementById('SelectionDisplay').innerHTML = 'Selection: ' + Selection + ' ------------- Particles: ' + particles.length; 534 | } 535 | }; 536 | var requestAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { 537 | window.setTimeout(callback, 0); 538 | }; 539 | // return Dust; 540 | // })(); 541 | var dust = new Dust('0', document.getElementById('display')); 542 | dust.play(); 543 | 544 | 545 | /***/ }, 546 | /* 1 */ 547 | /***/ function(module, exports, __webpack_require__) { 548 | 549 | var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module) {// Generated by CoffeeScript 1.9.3 550 | (function() { 551 | var L_to_Y, Y_to_L, conv, distanceFromPole, dotProduct, epsilon, fromLinear, getBounds, intersectLineLine, kappa, lengthOfRayUntilIntersect, m, m_inv, maxChromaForLH, maxSafeChromaForL, refU, refV, root, toLinear; 552 | 553 | m = { 554 | R: [3.2409699419045214, -1.5373831775700935, -0.49861076029300328], 555 | G: [-0.96924363628087983, 1.8759675015077207, 0.041555057407175613], 556 | B: [0.055630079696993609, -0.20397695888897657, 1.0569715142428786] 557 | }; 558 | 559 | m_inv = { 560 | X: [0.41239079926595948, 0.35758433938387796, 0.18048078840183429], 561 | Y: [0.21263900587151036, 0.71516867876775593, 0.072192315360733715], 562 | Z: [0.019330818715591851, 0.11919477979462599, 0.95053215224966058] 563 | }; 564 | 565 | refU = 0.19783000664283681; 566 | 567 | refV = 0.468319994938791; 568 | 569 | kappa = 903.2962962962963; 570 | 571 | epsilon = 0.0088564516790356308; 572 | 573 | getBounds = function(L) { 574 | var bottom, channel, j, k, len1, len2, m1, m2, m3, ref, ref1, ref2, ret, sub1, sub2, t, top1, top2; 575 | sub1 = Math.pow(L + 16, 3) / 1560896; 576 | sub2 = sub1 > epsilon ? sub1 : L / kappa; 577 | ret = []; 578 | ref = ['R', 'G', 'B']; 579 | for (j = 0, len1 = ref.length; j < len1; j++) { 580 | channel = ref[j]; 581 | ref1 = m[channel], m1 = ref1[0], m2 = ref1[1], m3 = ref1[2]; 582 | ref2 = [0, 1]; 583 | for (k = 0, len2 = ref2.length; k < len2; k++) { 584 | t = ref2[k]; 585 | top1 = (284517 * m1 - 94839 * m3) * sub2; 586 | top2 = (838422 * m3 + 769860 * m2 + 731718 * m1) * L * sub2 - 769860 * t * L; 587 | bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t; 588 | ret.push([top1 / bottom, top2 / bottom]); 589 | } 590 | } 591 | return ret; 592 | }; 593 | 594 | intersectLineLine = function(line1, line2) { 595 | return (line1[1] - line2[1]) / (line2[0] - line1[0]); 596 | }; 597 | 598 | distanceFromPole = function(point) { 599 | return Math.sqrt(Math.pow(point[0], 2) + Math.pow(point[1], 2)); 600 | }; 601 | 602 | lengthOfRayUntilIntersect = function(theta, line) { 603 | var b1, len, m1; 604 | m1 = line[0], b1 = line[1]; 605 | len = b1 / (Math.sin(theta) - m1 * Math.cos(theta)); 606 | if (len < 0) { 607 | return null; 608 | } 609 | return len; 610 | }; 611 | 612 | maxSafeChromaForL = function(L) { 613 | var b1, j, len1, lengths, m1, ref, ref1, x; 614 | lengths = []; 615 | ref = getBounds(L); 616 | for (j = 0, len1 = ref.length; j < len1; j++) { 617 | ref1 = ref[j], m1 = ref1[0], b1 = ref1[1]; 618 | x = intersectLineLine([m1, b1], [-1 / m1, 0]); 619 | lengths.push(distanceFromPole([x, b1 + x * m1])); 620 | } 621 | return Math.min.apply(Math, lengths); 622 | }; 623 | 624 | maxChromaForLH = function(L, H) { 625 | var hrad, j, l, len1, lengths, line, ref; 626 | hrad = H / 360 * Math.PI * 2; 627 | lengths = []; 628 | ref = getBounds(L); 629 | for (j = 0, len1 = ref.length; j < len1; j++) { 630 | line = ref[j]; 631 | l = lengthOfRayUntilIntersect(hrad, line); 632 | if (l !== null) { 633 | lengths.push(l); 634 | } 635 | } 636 | return Math.min.apply(Math, lengths); 637 | }; 638 | 639 | dotProduct = function(a, b) { 640 | var i, j, ref, ret; 641 | ret = 0; 642 | for (i = j = 0, ref = a.length - 1; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) { 643 | ret += a[i] * b[i]; 644 | } 645 | return ret; 646 | }; 647 | 648 | fromLinear = function(c) { 649 | if (c <= 0.0031308) { 650 | return 12.92 * c; 651 | } else { 652 | return 1.055 * Math.pow(c, 1 / 2.4) - 0.055; 653 | } 654 | }; 655 | 656 | toLinear = function(c) { 657 | var a; 658 | a = 0.055; 659 | if (c > 0.04045) { 660 | return Math.pow((c + a) / (1 + a), 2.4); 661 | } else { 662 | return c / 12.92; 663 | } 664 | }; 665 | 666 | conv = { 667 | 'xyz': {}, 668 | 'luv': {}, 669 | 'lch': {}, 670 | 'husl': {}, 671 | 'huslp': {}, 672 | 'rgb': {}, 673 | 'hex': {} 674 | }; 675 | 676 | conv.xyz.rgb = function(tuple) { 677 | var B, G, R; 678 | R = fromLinear(dotProduct(m.R, tuple)); 679 | G = fromLinear(dotProduct(m.G, tuple)); 680 | B = fromLinear(dotProduct(m.B, tuple)); 681 | return [R, G, B]; 682 | }; 683 | 684 | conv.rgb.xyz = function(tuple) { 685 | var B, G, R, X, Y, Z, rgbl; 686 | R = tuple[0], G = tuple[1], B = tuple[2]; 687 | rgbl = [toLinear(R), toLinear(G), toLinear(B)]; 688 | X = dotProduct(m_inv.X, rgbl); 689 | Y = dotProduct(m_inv.Y, rgbl); 690 | Z = dotProduct(m_inv.Z, rgbl); 691 | return [X, Y, Z]; 692 | }; 693 | 694 | Y_to_L = function(Y) { 695 | if (Y <= epsilon) { 696 | return Y * kappa; 697 | } else { 698 | return 116 * Math.pow(Y, 1 / 3) - 16; 699 | } 700 | }; 701 | 702 | L_to_Y = function(L) { 703 | if (L <= 8) { 704 | return L / kappa; 705 | } else { 706 | return Math.pow((L + 16) / 116, 3); 707 | } 708 | }; 709 | 710 | conv.xyz.luv = function(tuple) { 711 | var L, U, V, X, Y, Z, varU, varV; 712 | X = tuple[0], Y = tuple[1], Z = tuple[2]; 713 | if (Y === 0) { 714 | return [0, 0, 0]; 715 | } 716 | L = Y_to_L(Y); 717 | varU = (4 * X) / (X + (15 * Y) + (3 * Z)); 718 | varV = (9 * Y) / (X + (15 * Y) + (3 * Z)); 719 | U = 13 * L * (varU - refU); 720 | V = 13 * L * (varV - refV); 721 | return [L, U, V]; 722 | }; 723 | 724 | conv.luv.xyz = function(tuple) { 725 | var L, U, V, X, Y, Z, varU, varV; 726 | L = tuple[0], U = tuple[1], V = tuple[2]; 727 | if (L === 0) { 728 | return [0, 0, 0]; 729 | } 730 | varU = U / (13 * L) + refU; 731 | varV = V / (13 * L) + refV; 732 | Y = L_to_Y(L); 733 | X = 0 - (9 * Y * varU) / ((varU - 4) * varV - varU * varV); 734 | Z = (9 * Y - (15 * varV * Y) - (varV * X)) / (3 * varV); 735 | return [X, Y, Z]; 736 | }; 737 | 738 | conv.luv.lch = function(tuple) { 739 | var C, H, Hrad, L, U, V; 740 | L = tuple[0], U = tuple[1], V = tuple[2]; 741 | C = Math.sqrt(Math.pow(U, 2) + Math.pow(V, 2)); 742 | if (C < 0.00000001) { 743 | H = 0; 744 | } else { 745 | Hrad = Math.atan2(V, U); 746 | H = Hrad * 360 / 2 / Math.PI; 747 | if (H < 0) { 748 | H = 360 + H; 749 | } 750 | } 751 | return [L, C, H]; 752 | }; 753 | 754 | conv.lch.luv = function(tuple) { 755 | var C, H, Hrad, L, U, V; 756 | L = tuple[0], C = tuple[1], H = tuple[2]; 757 | Hrad = H / 360 * 2 * Math.PI; 758 | U = Math.cos(Hrad) * C; 759 | V = Math.sin(Hrad) * C; 760 | return [L, U, V]; 761 | }; 762 | 763 | conv.husl.lch = function(tuple) { 764 | var C, H, L, S, max; 765 | H = tuple[0], S = tuple[1], L = tuple[2]; 766 | if (L > 99.9999999 || L < 0.00000001) { 767 | C = 0; 768 | } else { 769 | max = maxChromaForLH(L, H); 770 | C = max / 100 * S; 771 | } 772 | return [L, C, H]; 773 | }; 774 | 775 | conv.lch.husl = function(tuple) { 776 | var C, H, L, S, max; 777 | L = tuple[0], C = tuple[1], H = tuple[2]; 778 | if (L > 99.9999999 || L < 0.00000001) { 779 | S = 0; 780 | } else { 781 | max = maxChromaForLH(L, H); 782 | S = C / max * 100; 783 | } 784 | return [H, S, L]; 785 | }; 786 | 787 | conv.huslp.lch = function(tuple) { 788 | var C, H, L, S, max; 789 | H = tuple[0], S = tuple[1], L = tuple[2]; 790 | if (L > 99.9999999 || L < 0.00000001) { 791 | C = 0; 792 | } else { 793 | max = maxSafeChromaForL(L); 794 | C = max / 100 * S; 795 | } 796 | return [L, C, H]; 797 | }; 798 | 799 | conv.lch.huslp = function(tuple) { 800 | var C, H, L, S, max; 801 | L = tuple[0], C = tuple[1], H = tuple[2]; 802 | if (L > 99.9999999 || L < 0.00000001) { 803 | S = 0; 804 | } else { 805 | max = maxSafeChromaForL(L); 806 | S = C / max * 100; 807 | } 808 | return [H, S, L]; 809 | }; 810 | 811 | conv.rgb.hex = function(tuple) { 812 | var ch, hex, j, len1; 813 | hex = "#"; 814 | for (j = 0, len1 = tuple.length; j < len1; j++) { 815 | ch = tuple[j]; 816 | ch = Math.round(ch * 1e6) / 1e6; 817 | if (ch < 0 || ch > 1) { 818 | throw new Error("Illegal rgb value: " + ch); 819 | } 820 | ch = Math.round(ch * 255).toString(16); 821 | if (ch.length === 1) { 822 | ch = "0" + ch; 823 | } 824 | hex += ch; 825 | } 826 | return hex; 827 | }; 828 | 829 | conv.hex.rgb = function(hex) { 830 | var b, g, j, len1, n, r, ref, results; 831 | if (hex.charAt(0) === "#") { 832 | hex = hex.substring(1, 7); 833 | } 834 | r = hex.substring(0, 2); 835 | g = hex.substring(2, 4); 836 | b = hex.substring(4, 6); 837 | ref = [r, g, b]; 838 | results = []; 839 | for (j = 0, len1 = ref.length; j < len1; j++) { 840 | n = ref[j]; 841 | results.push(parseInt(n, 16) / 255); 842 | } 843 | return results; 844 | }; 845 | 846 | conv.lch.rgb = function(tuple) { 847 | return conv.xyz.rgb(conv.luv.xyz(conv.lch.luv(tuple))); 848 | }; 849 | 850 | conv.rgb.lch = function(tuple) { 851 | return conv.luv.lch(conv.xyz.luv(conv.rgb.xyz(tuple))); 852 | }; 853 | 854 | conv.husl.rgb = function(tuple) { 855 | return conv.lch.rgb(conv.husl.lch(tuple)); 856 | }; 857 | 858 | conv.rgb.husl = function(tuple) { 859 | return conv.lch.husl(conv.rgb.lch(tuple)); 860 | }; 861 | 862 | conv.huslp.rgb = function(tuple) { 863 | return conv.lch.rgb(conv.huslp.lch(tuple)); 864 | }; 865 | 866 | conv.rgb.huslp = function(tuple) { 867 | return conv.lch.huslp(conv.rgb.lch(tuple)); 868 | }; 869 | 870 | root = {}; 871 | 872 | root.fromRGB = function(R, G, B) { 873 | return conv.rgb.husl([R, G, B]); 874 | }; 875 | 876 | root.fromHex = function(hex) { 877 | return conv.rgb.husl(conv.hex.rgb(hex)); 878 | }; 879 | 880 | root.toRGB = function(H, S, L) { 881 | return conv.husl.rgb([H, S, L]); 882 | }; 883 | 884 | root.toHex = function(H, S, L) { 885 | return conv.rgb.hex(conv.husl.rgb([H, S, L])); 886 | }; 887 | 888 | root.p = {}; 889 | 890 | root.p.toRGB = function(H, S, L) { 891 | return conv.xyz.rgb(conv.luv.xyz(conv.lch.luv(conv.huslp.lch([H, S, L])))); 892 | }; 893 | 894 | root.p.toHex = function(H, S, L) { 895 | return conv.rgb.hex(conv.xyz.rgb(conv.luv.xyz(conv.lch.luv(conv.huslp.lch([H, S, L]))))); 896 | }; 897 | 898 | root.p.fromRGB = function(R, G, B) { 899 | return conv.lch.huslp(conv.luv.lch(conv.xyz.luv(conv.rgb.xyz([R, G, B])))); 900 | }; 901 | 902 | root.p.fromHex = function(hex) { 903 | return conv.lch.huslp(conv.luv.lch(conv.xyz.luv(conv.rgb.xyz(conv.hex.rgb(hex))))); 904 | }; 905 | 906 | root._conv = conv; 907 | 908 | root._getBounds = getBounds; 909 | 910 | root._maxChromaForLH = maxChromaForLH; 911 | 912 | root._maxSafeChromaForL = maxSafeChromaForL; 913 | 914 | if (!((typeof module !== "undefined" && module !== null) || (typeof jQuery !== "undefined" && jQuery !== null) || (typeof requirejs !== "undefined" && requirejs !== null))) { 915 | this.HUSL = root; 916 | } 917 | 918 | if (typeof module !== "undefined" && module !== null) { 919 | module.exports = root; 920 | } 921 | 922 | if (typeof jQuery !== "undefined" && jQuery !== null) { 923 | jQuery.husl = root; 924 | } 925 | 926 | if ((typeof requirejs !== "undefined" && requirejs !== null) && ("function" !== "undefined" && __webpack_require__(3) !== null)) { 927 | !(__WEBPACK_AMD_DEFINE_FACTORY__ = (root), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); 928 | } 929 | 930 | }).call(this); 931 | 932 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)(module))) 933 | 934 | /***/ }, 935 | /* 2 */ 936 | /***/ function(module, exports) { 937 | 938 | module.exports = function(module) { 939 | if(!module.webpackPolyfill) { 940 | module.deprecate = function() {}; 941 | module.paths = []; 942 | // module.parent = undefined by default 943 | module.children = []; 944 | module.webpackPolyfill = 1; 945 | } 946 | return module; 947 | } 948 | 949 | 950 | /***/ }, 951 | /* 3 */ 952 | /***/ function(module, exports) { 953 | 954 | module.exports = function() { throw new Error("define cannot be used indirect"); }; 955 | 956 | 957 | /***/ }, 958 | /* 4 */ 959 | /***/ function(module, exports, __webpack_require__) { 960 | 961 | var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Underscore.js 1.8.3 962 | // http://underscorejs.org 963 | // (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 964 | // Underscore may be freely distributed under the MIT license. 965 | 966 | (function() { 967 | 968 | // Baseline setup 969 | // -------------- 970 | 971 | // Establish the root object, `window` in the browser, or `exports` on the server. 972 | var root = this; 973 | 974 | // Save the previous value of the `_` variable. 975 | var previousUnderscore = root._; 976 | 977 | // Save bytes in the minified (but not gzipped) version: 978 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; 979 | 980 | // Create quick reference variables for speed access to core prototypes. 981 | var 982 | push = ArrayProto.push, 983 | slice = ArrayProto.slice, 984 | toString = ObjProto.toString, 985 | hasOwnProperty = ObjProto.hasOwnProperty; 986 | 987 | // All **ECMAScript 5** native function implementations that we hope to use 988 | // are declared here. 989 | var 990 | nativeIsArray = Array.isArray, 991 | nativeKeys = Object.keys, 992 | nativeBind = FuncProto.bind, 993 | nativeCreate = Object.create; 994 | 995 | // Naked function reference for surrogate-prototype-swapping. 996 | var Ctor = function(){}; 997 | 998 | // Create a safe reference to the Underscore object for use below. 999 | var _ = function(obj) { 1000 | if (obj instanceof _) return obj; 1001 | if (!(this instanceof _)) return new _(obj); 1002 | this._wrapped = obj; 1003 | }; 1004 | 1005 | // Export the Underscore object for **Node.js**, with 1006 | // backwards-compatibility for the old `require()` API. If we're in 1007 | // the browser, add `_` as a global object. 1008 | if (true) { 1009 | if (typeof module !== 'undefined' && module.exports) { 1010 | exports = module.exports = _; 1011 | } 1012 | exports._ = _; 1013 | } else { 1014 | root._ = _; 1015 | } 1016 | 1017 | // Current version. 1018 | _.VERSION = '1.8.3'; 1019 | 1020 | // Internal function that returns an efficient (for current engines) version 1021 | // of the passed-in callback, to be repeatedly applied in other Underscore 1022 | // functions. 1023 | var optimizeCb = function(func, context, argCount) { 1024 | if (context === void 0) return func; 1025 | switch (argCount == null ? 3 : argCount) { 1026 | case 1: return function(value) { 1027 | return func.call(context, value); 1028 | }; 1029 | case 2: return function(value, other) { 1030 | return func.call(context, value, other); 1031 | }; 1032 | case 3: return function(value, index, collection) { 1033 | return func.call(context, value, index, collection); 1034 | }; 1035 | case 4: return function(accumulator, value, index, collection) { 1036 | return func.call(context, accumulator, value, index, collection); 1037 | }; 1038 | } 1039 | return function() { 1040 | return func.apply(context, arguments); 1041 | }; 1042 | }; 1043 | 1044 | // A mostly-internal function to generate callbacks that can be applied 1045 | // to each element in a collection, returning the desired result — either 1046 | // identity, an arbitrary callback, a property matcher, or a property accessor. 1047 | var cb = function(value, context, argCount) { 1048 | if (value == null) return _.identity; 1049 | if (_.isFunction(value)) return optimizeCb(value, context, argCount); 1050 | if (_.isObject(value)) return _.matcher(value); 1051 | return _.property(value); 1052 | }; 1053 | _.iteratee = function(value, context) { 1054 | return cb(value, context, Infinity); 1055 | }; 1056 | 1057 | // An internal function for creating assigner functions. 1058 | var createAssigner = function(keysFunc, undefinedOnly) { 1059 | return function(obj) { 1060 | var length = arguments.length; 1061 | if (length < 2 || obj == null) return obj; 1062 | for (var index = 1; index < length; index++) { 1063 | var source = arguments[index], 1064 | keys = keysFunc(source), 1065 | l = keys.length; 1066 | for (var i = 0; i < l; i++) { 1067 | var key = keys[i]; 1068 | if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; 1069 | } 1070 | } 1071 | return obj; 1072 | }; 1073 | }; 1074 | 1075 | // An internal function for creating a new object that inherits from another. 1076 | var baseCreate = function(prototype) { 1077 | if (!_.isObject(prototype)) return {}; 1078 | if (nativeCreate) return nativeCreate(prototype); 1079 | Ctor.prototype = prototype; 1080 | var result = new Ctor; 1081 | Ctor.prototype = null; 1082 | return result; 1083 | }; 1084 | 1085 | var property = function(key) { 1086 | return function(obj) { 1087 | return obj == null ? void 0 : obj[key]; 1088 | }; 1089 | }; 1090 | 1091 | // Helper for collection methods to determine whether a collection 1092 | // should be iterated as an array or as an object 1093 | // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength 1094 | // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 1095 | var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; 1096 | var getLength = property('length'); 1097 | var isArrayLike = function(collection) { 1098 | var length = getLength(collection); 1099 | return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; 1100 | }; 1101 | 1102 | // Collection Functions 1103 | // -------------------- 1104 | 1105 | // The cornerstone, an `each` implementation, aka `forEach`. 1106 | // Handles raw objects in addition to array-likes. Treats all 1107 | // sparse array-likes as if they were dense. 1108 | _.each = _.forEach = function(obj, iteratee, context) { 1109 | iteratee = optimizeCb(iteratee, context); 1110 | var i, length; 1111 | if (isArrayLike(obj)) { 1112 | for (i = 0, length = obj.length; i < length; i++) { 1113 | iteratee(obj[i], i, obj); 1114 | } 1115 | } else { 1116 | var keys = _.keys(obj); 1117 | for (i = 0, length = keys.length; i < length; i++) { 1118 | iteratee(obj[keys[i]], keys[i], obj); 1119 | } 1120 | } 1121 | return obj; 1122 | }; 1123 | 1124 | // Return the results of applying the iteratee to each element. 1125 | _.map = _.collect = function(obj, iteratee, context) { 1126 | iteratee = cb(iteratee, context); 1127 | var keys = !isArrayLike(obj) && _.keys(obj), 1128 | length = (keys || obj).length, 1129 | results = Array(length); 1130 | for (var index = 0; index < length; index++) { 1131 | var currentKey = keys ? keys[index] : index; 1132 | results[index] = iteratee(obj[currentKey], currentKey, obj); 1133 | } 1134 | return results; 1135 | }; 1136 | 1137 | // Create a reducing function iterating left or right. 1138 | function createReduce(dir) { 1139 | // Optimized iterator function as using arguments.length 1140 | // in the main function will deoptimize the, see #1991. 1141 | function iterator(obj, iteratee, memo, keys, index, length) { 1142 | for (; index >= 0 && index < length; index += dir) { 1143 | var currentKey = keys ? keys[index] : index; 1144 | memo = iteratee(memo, obj[currentKey], currentKey, obj); 1145 | } 1146 | return memo; 1147 | } 1148 | 1149 | return function(obj, iteratee, memo, context) { 1150 | iteratee = optimizeCb(iteratee, context, 4); 1151 | var keys = !isArrayLike(obj) && _.keys(obj), 1152 | length = (keys || obj).length, 1153 | index = dir > 0 ? 0 : length - 1; 1154 | // Determine the initial value if none is provided. 1155 | if (arguments.length < 3) { 1156 | memo = obj[keys ? keys[index] : index]; 1157 | index += dir; 1158 | } 1159 | return iterator(obj, iteratee, memo, keys, index, length); 1160 | }; 1161 | } 1162 | 1163 | // **Reduce** builds up a single result from a list of values, aka `inject`, 1164 | // or `foldl`. 1165 | _.reduce = _.foldl = _.inject = createReduce(1); 1166 | 1167 | // The right-associative version of reduce, also known as `foldr`. 1168 | _.reduceRight = _.foldr = createReduce(-1); 1169 | 1170 | // Return the first value which passes a truth test. Aliased as `detect`. 1171 | _.find = _.detect = function(obj, predicate, context) { 1172 | var key; 1173 | if (isArrayLike(obj)) { 1174 | key = _.findIndex(obj, predicate, context); 1175 | } else { 1176 | key = _.findKey(obj, predicate, context); 1177 | } 1178 | if (key !== void 0 && key !== -1) return obj[key]; 1179 | }; 1180 | 1181 | // Return all the elements that pass a truth test. 1182 | // Aliased as `select`. 1183 | _.filter = _.select = function(obj, predicate, context) { 1184 | var results = []; 1185 | predicate = cb(predicate, context); 1186 | _.each(obj, function(value, index, list) { 1187 | if (predicate(value, index, list)) results.push(value); 1188 | }); 1189 | return results; 1190 | }; 1191 | 1192 | // Return all the elements for which a truth test fails. 1193 | _.reject = function(obj, predicate, context) { 1194 | return _.filter(obj, _.negate(cb(predicate)), context); 1195 | }; 1196 | 1197 | // Determine whether all of the elements match a truth test. 1198 | // Aliased as `all`. 1199 | _.every = _.all = function(obj, predicate, context) { 1200 | predicate = cb(predicate, context); 1201 | var keys = !isArrayLike(obj) && _.keys(obj), 1202 | length = (keys || obj).length; 1203 | for (var index = 0; index < length; index++) { 1204 | var currentKey = keys ? keys[index] : index; 1205 | if (!predicate(obj[currentKey], currentKey, obj)) return false; 1206 | } 1207 | return true; 1208 | }; 1209 | 1210 | // Determine if at least one element in the object matches a truth test. 1211 | // Aliased as `any`. 1212 | _.some = _.any = function(obj, predicate, context) { 1213 | predicate = cb(predicate, context); 1214 | var keys = !isArrayLike(obj) && _.keys(obj), 1215 | length = (keys || obj).length; 1216 | for (var index = 0; index < length; index++) { 1217 | var currentKey = keys ? keys[index] : index; 1218 | if (predicate(obj[currentKey], currentKey, obj)) return true; 1219 | } 1220 | return false; 1221 | }; 1222 | 1223 | // Determine if the array or object contains a given item (using `===`). 1224 | // Aliased as `includes` and `include`. 1225 | _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { 1226 | if (!isArrayLike(obj)) obj = _.values(obj); 1227 | if (typeof fromIndex != 'number' || guard) fromIndex = 0; 1228 | return _.indexOf(obj, item, fromIndex) >= 0; 1229 | }; 1230 | 1231 | // Invoke a method (with arguments) on every item in a collection. 1232 | _.invoke = function(obj, method) { 1233 | var args = slice.call(arguments, 2); 1234 | var isFunc = _.isFunction(method); 1235 | return _.map(obj, function(value) { 1236 | var func = isFunc ? method : value[method]; 1237 | return func == null ? func : func.apply(value, args); 1238 | }); 1239 | }; 1240 | 1241 | // Convenience version of a common use case of `map`: fetching a property. 1242 | _.pluck = function(obj, key) { 1243 | return _.map(obj, _.property(key)); 1244 | }; 1245 | 1246 | // Convenience version of a common use case of `filter`: selecting only objects 1247 | // containing specific `key:value` pairs. 1248 | _.where = function(obj, attrs) { 1249 | return _.filter(obj, _.matcher(attrs)); 1250 | }; 1251 | 1252 | // Convenience version of a common use case of `find`: getting the first object 1253 | // containing specific `key:value` pairs. 1254 | _.findWhere = function(obj, attrs) { 1255 | return _.find(obj, _.matcher(attrs)); 1256 | }; 1257 | 1258 | // Return the maximum element (or element-based computation). 1259 | _.max = function(obj, iteratee, context) { 1260 | var result = -Infinity, lastComputed = -Infinity, 1261 | value, computed; 1262 | if (iteratee == null && obj != null) { 1263 | obj = isArrayLike(obj) ? obj : _.values(obj); 1264 | for (var i = 0, length = obj.length; i < length; i++) { 1265 | value = obj[i]; 1266 | if (value > result) { 1267 | result = value; 1268 | } 1269 | } 1270 | } else { 1271 | iteratee = cb(iteratee, context); 1272 | _.each(obj, function(value, index, list) { 1273 | computed = iteratee(value, index, list); 1274 | if (computed > lastComputed || computed === -Infinity && result === -Infinity) { 1275 | result = value; 1276 | lastComputed = computed; 1277 | } 1278 | }); 1279 | } 1280 | return result; 1281 | }; 1282 | 1283 | // Return the minimum element (or element-based computation). 1284 | _.min = function(obj, iteratee, context) { 1285 | var result = Infinity, lastComputed = Infinity, 1286 | value, computed; 1287 | if (iteratee == null && obj != null) { 1288 | obj = isArrayLike(obj) ? obj : _.values(obj); 1289 | for (var i = 0, length = obj.length; i < length; i++) { 1290 | value = obj[i]; 1291 | if (value < result) { 1292 | result = value; 1293 | } 1294 | } 1295 | } else { 1296 | iteratee = cb(iteratee, context); 1297 | _.each(obj, function(value, index, list) { 1298 | computed = iteratee(value, index, list); 1299 | if (computed < lastComputed || computed === Infinity && result === Infinity) { 1300 | result = value; 1301 | lastComputed = computed; 1302 | } 1303 | }); 1304 | } 1305 | return result; 1306 | }; 1307 | 1308 | // Shuffle a collection, using the modern version of the 1309 | // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). 1310 | _.shuffle = function(obj) { 1311 | var set = isArrayLike(obj) ? obj : _.values(obj); 1312 | var length = set.length; 1313 | var shuffled = Array(length); 1314 | for (var index = 0, rand; index < length; index++) { 1315 | rand = _.random(0, index); 1316 | if (rand !== index) shuffled[index] = shuffled[rand]; 1317 | shuffled[rand] = set[index]; 1318 | } 1319 | return shuffled; 1320 | }; 1321 | 1322 | // Sample **n** random values from a collection. 1323 | // If **n** is not specified, returns a single random element. 1324 | // The internal `guard` argument allows it to work with `map`. 1325 | _.sample = function(obj, n, guard) { 1326 | if (n == null || guard) { 1327 | if (!isArrayLike(obj)) obj = _.values(obj); 1328 | return obj[_.random(obj.length - 1)]; 1329 | } 1330 | return _.shuffle(obj).slice(0, Math.max(0, n)); 1331 | }; 1332 | 1333 | // Sort the object's values by a criterion produced by an iteratee. 1334 | _.sortBy = function(obj, iteratee, context) { 1335 | iteratee = cb(iteratee, context); 1336 | return _.pluck(_.map(obj, function(value, index, list) { 1337 | return { 1338 | value: value, 1339 | index: index, 1340 | criteria: iteratee(value, index, list) 1341 | }; 1342 | }).sort(function(left, right) { 1343 | var a = left.criteria; 1344 | var b = right.criteria; 1345 | if (a !== b) { 1346 | if (a > b || a === void 0) return 1; 1347 | if (a < b || b === void 0) return -1; 1348 | } 1349 | return left.index - right.index; 1350 | }), 'value'); 1351 | }; 1352 | 1353 | // An internal function used for aggregate "group by" operations. 1354 | var group = function(behavior) { 1355 | return function(obj, iteratee, context) { 1356 | var result = {}; 1357 | iteratee = cb(iteratee, context); 1358 | _.each(obj, function(value, index) { 1359 | var key = iteratee(value, index, obj); 1360 | behavior(result, value, key); 1361 | }); 1362 | return result; 1363 | }; 1364 | }; 1365 | 1366 | // Groups the object's values by a criterion. Pass either a string attribute 1367 | // to group by, or a function that returns the criterion. 1368 | _.groupBy = group(function(result, value, key) { 1369 | if (_.has(result, key)) result[key].push(value); else result[key] = [value]; 1370 | }); 1371 | 1372 | // Indexes the object's values by a criterion, similar to `groupBy`, but for 1373 | // when you know that your index values will be unique. 1374 | _.indexBy = group(function(result, value, key) { 1375 | result[key] = value; 1376 | }); 1377 | 1378 | // Counts instances of an object that group by a certain criterion. Pass 1379 | // either a string attribute to count by, or a function that returns the 1380 | // criterion. 1381 | _.countBy = group(function(result, value, key) { 1382 | if (_.has(result, key)) result[key]++; else result[key] = 1; 1383 | }); 1384 | 1385 | // Safely create a real, live array from anything iterable. 1386 | _.toArray = function(obj) { 1387 | if (!obj) return []; 1388 | if (_.isArray(obj)) return slice.call(obj); 1389 | if (isArrayLike(obj)) return _.map(obj, _.identity); 1390 | return _.values(obj); 1391 | }; 1392 | 1393 | // Return the number of elements in an object. 1394 | _.size = function(obj) { 1395 | if (obj == null) return 0; 1396 | return isArrayLike(obj) ? obj.length : _.keys(obj).length; 1397 | }; 1398 | 1399 | // Split a collection into two arrays: one whose elements all satisfy the given 1400 | // predicate, and one whose elements all do not satisfy the predicate. 1401 | _.partition = function(obj, predicate, context) { 1402 | predicate = cb(predicate, context); 1403 | var pass = [], fail = []; 1404 | _.each(obj, function(value, key, obj) { 1405 | (predicate(value, key, obj) ? pass : fail).push(value); 1406 | }); 1407 | return [pass, fail]; 1408 | }; 1409 | 1410 | // Array Functions 1411 | // --------------- 1412 | 1413 | // Get the first element of an array. Passing **n** will return the first N 1414 | // values in the array. Aliased as `head` and `take`. The **guard** check 1415 | // allows it to work with `_.map`. 1416 | _.first = _.head = _.take = function(array, n, guard) { 1417 | if (array == null) return void 0; 1418 | if (n == null || guard) return array[0]; 1419 | return _.initial(array, array.length - n); 1420 | }; 1421 | 1422 | // Returns everything but the last entry of the array. Especially useful on 1423 | // the arguments object. Passing **n** will return all the values in 1424 | // the array, excluding the last N. 1425 | _.initial = function(array, n, guard) { 1426 | return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); 1427 | }; 1428 | 1429 | // Get the last element of an array. Passing **n** will return the last N 1430 | // values in the array. 1431 | _.last = function(array, n, guard) { 1432 | if (array == null) return void 0; 1433 | if (n == null || guard) return array[array.length - 1]; 1434 | return _.rest(array, Math.max(0, array.length - n)); 1435 | }; 1436 | 1437 | // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. 1438 | // Especially useful on the arguments object. Passing an **n** will return 1439 | // the rest N values in the array. 1440 | _.rest = _.tail = _.drop = function(array, n, guard) { 1441 | return slice.call(array, n == null || guard ? 1 : n); 1442 | }; 1443 | 1444 | // Trim out all falsy values from an array. 1445 | _.compact = function(array) { 1446 | return _.filter(array, _.identity); 1447 | }; 1448 | 1449 | // Internal implementation of a recursive `flatten` function. 1450 | var flatten = function(input, shallow, strict, startIndex) { 1451 | var output = [], idx = 0; 1452 | for (var i = startIndex || 0, length = getLength(input); i < length; i++) { 1453 | var value = input[i]; 1454 | if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { 1455 | //flatten current level of array or arguments object 1456 | if (!shallow) value = flatten(value, shallow, strict); 1457 | var j = 0, len = value.length; 1458 | output.length += len; 1459 | while (j < len) { 1460 | output[idx++] = value[j++]; 1461 | } 1462 | } else if (!strict) { 1463 | output[idx++] = value; 1464 | } 1465 | } 1466 | return output; 1467 | }; 1468 | 1469 | // Flatten out an array, either recursively (by default), or just one level. 1470 | _.flatten = function(array, shallow) { 1471 | return flatten(array, shallow, false); 1472 | }; 1473 | 1474 | // Return a version of the array that does not contain the specified value(s). 1475 | _.without = function(array) { 1476 | return _.difference(array, slice.call(arguments, 1)); 1477 | }; 1478 | 1479 | // Produce a duplicate-free version of the array. If the array has already 1480 | // been sorted, you have the option of using a faster algorithm. 1481 | // Aliased as `unique`. 1482 | _.uniq = _.unique = function(array, isSorted, iteratee, context) { 1483 | if (!_.isBoolean(isSorted)) { 1484 | context = iteratee; 1485 | iteratee = isSorted; 1486 | isSorted = false; 1487 | } 1488 | if (iteratee != null) iteratee = cb(iteratee, context); 1489 | var result = []; 1490 | var seen = []; 1491 | for (var i = 0, length = getLength(array); i < length; i++) { 1492 | var value = array[i], 1493 | computed = iteratee ? iteratee(value, i, array) : value; 1494 | if (isSorted) { 1495 | if (!i || seen !== computed) result.push(value); 1496 | seen = computed; 1497 | } else if (iteratee) { 1498 | if (!_.contains(seen, computed)) { 1499 | seen.push(computed); 1500 | result.push(value); 1501 | } 1502 | } else if (!_.contains(result, value)) { 1503 | result.push(value); 1504 | } 1505 | } 1506 | return result; 1507 | }; 1508 | 1509 | // Produce an array that contains the union: each distinct element from all of 1510 | // the passed-in arrays. 1511 | _.union = function() { 1512 | return _.uniq(flatten(arguments, true, true)); 1513 | }; 1514 | 1515 | // Produce an array that contains every item shared between all the 1516 | // passed-in arrays. 1517 | _.intersection = function(array) { 1518 | var result = []; 1519 | var argsLength = arguments.length; 1520 | for (var i = 0, length = getLength(array); i < length; i++) { 1521 | var item = array[i]; 1522 | if (_.contains(result, item)) continue; 1523 | for (var j = 1; j < argsLength; j++) { 1524 | if (!_.contains(arguments[j], item)) break; 1525 | } 1526 | if (j === argsLength) result.push(item); 1527 | } 1528 | return result; 1529 | }; 1530 | 1531 | // Take the difference between one array and a number of other arrays. 1532 | // Only the elements present in just the first array will remain. 1533 | _.difference = function(array) { 1534 | var rest = flatten(arguments, true, true, 1); 1535 | return _.filter(array, function(value){ 1536 | return !_.contains(rest, value); 1537 | }); 1538 | }; 1539 | 1540 | // Zip together multiple lists into a single array -- elements that share 1541 | // an index go together. 1542 | _.zip = function() { 1543 | return _.unzip(arguments); 1544 | }; 1545 | 1546 | // Complement of _.zip. Unzip accepts an array of arrays and groups 1547 | // each array's elements on shared indices 1548 | _.unzip = function(array) { 1549 | var length = array && _.max(array, getLength).length || 0; 1550 | var result = Array(length); 1551 | 1552 | for (var index = 0; index < length; index++) { 1553 | result[index] = _.pluck(array, index); 1554 | } 1555 | return result; 1556 | }; 1557 | 1558 | // Converts lists into objects. Pass either a single array of `[key, value]` 1559 | // pairs, or two parallel arrays of the same length -- one of keys, and one of 1560 | // the corresponding values. 1561 | _.object = function(list, values) { 1562 | var result = {}; 1563 | for (var i = 0, length = getLength(list); i < length; i++) { 1564 | if (values) { 1565 | result[list[i]] = values[i]; 1566 | } else { 1567 | result[list[i][0]] = list[i][1]; 1568 | } 1569 | } 1570 | return result; 1571 | }; 1572 | 1573 | // Generator function to create the findIndex and findLastIndex functions 1574 | function createPredicateIndexFinder(dir) { 1575 | return function(array, predicate, context) { 1576 | predicate = cb(predicate, context); 1577 | var length = getLength(array); 1578 | var index = dir > 0 ? 0 : length - 1; 1579 | for (; index >= 0 && index < length; index += dir) { 1580 | if (predicate(array[index], index, array)) return index; 1581 | } 1582 | return -1; 1583 | }; 1584 | } 1585 | 1586 | // Returns the first index on an array-like that passes a predicate test 1587 | _.findIndex = createPredicateIndexFinder(1); 1588 | _.findLastIndex = createPredicateIndexFinder(-1); 1589 | 1590 | // Use a comparator function to figure out the smallest index at which 1591 | // an object should be inserted so as to maintain order. Uses binary search. 1592 | _.sortedIndex = function(array, obj, iteratee, context) { 1593 | iteratee = cb(iteratee, context, 1); 1594 | var value = iteratee(obj); 1595 | var low = 0, high = getLength(array); 1596 | while (low < high) { 1597 | var mid = Math.floor((low + high) / 2); 1598 | if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; 1599 | } 1600 | return low; 1601 | }; 1602 | 1603 | // Generator function to create the indexOf and lastIndexOf functions 1604 | function createIndexFinder(dir, predicateFind, sortedIndex) { 1605 | return function(array, item, idx) { 1606 | var i = 0, length = getLength(array); 1607 | if (typeof idx == 'number') { 1608 | if (dir > 0) { 1609 | i = idx >= 0 ? idx : Math.max(idx + length, i); 1610 | } else { 1611 | length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; 1612 | } 1613 | } else if (sortedIndex && idx && length) { 1614 | idx = sortedIndex(array, item); 1615 | return array[idx] === item ? idx : -1; 1616 | } 1617 | if (item !== item) { 1618 | idx = predicateFind(slice.call(array, i, length), _.isNaN); 1619 | return idx >= 0 ? idx + i : -1; 1620 | } 1621 | for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { 1622 | if (array[idx] === item) return idx; 1623 | } 1624 | return -1; 1625 | }; 1626 | } 1627 | 1628 | // Return the position of the first occurrence of an item in an array, 1629 | // or -1 if the item is not included in the array. 1630 | // If the array is large and already in sort order, pass `true` 1631 | // for **isSorted** to use binary search. 1632 | _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex); 1633 | _.lastIndexOf = createIndexFinder(-1, _.findLastIndex); 1634 | 1635 | // Generate an integer Array containing an arithmetic progression. A port of 1636 | // the native Python `range()` function. See 1637 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 1638 | _.range = function(start, stop, step) { 1639 | if (stop == null) { 1640 | stop = start || 0; 1641 | start = 0; 1642 | } 1643 | step = step || 1; 1644 | 1645 | var length = Math.max(Math.ceil((stop - start) / step), 0); 1646 | var range = Array(length); 1647 | 1648 | for (var idx = 0; idx < length; idx++, start += step) { 1649 | range[idx] = start; 1650 | } 1651 | 1652 | return range; 1653 | }; 1654 | 1655 | // Function (ahem) Functions 1656 | // ------------------ 1657 | 1658 | // Determines whether to execute a function as a constructor 1659 | // or a normal function with the provided arguments 1660 | var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { 1661 | if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); 1662 | var self = baseCreate(sourceFunc.prototype); 1663 | var result = sourceFunc.apply(self, args); 1664 | if (_.isObject(result)) return result; 1665 | return self; 1666 | }; 1667 | 1668 | // Create a function bound to a given object (assigning `this`, and arguments, 1669 | // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if 1670 | // available. 1671 | _.bind = function(func, context) { 1672 | if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 1673 | if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); 1674 | var args = slice.call(arguments, 2); 1675 | var bound = function() { 1676 | return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); 1677 | }; 1678 | return bound; 1679 | }; 1680 | 1681 | // Partially apply a function by creating a version that has had some of its 1682 | // arguments pre-filled, without changing its dynamic `this` context. _ acts 1683 | // as a placeholder, allowing any combination of arguments to be pre-filled. 1684 | _.partial = function(func) { 1685 | var boundArgs = slice.call(arguments, 1); 1686 | var bound = function() { 1687 | var position = 0, length = boundArgs.length; 1688 | var args = Array(length); 1689 | for (var i = 0; i < length; i++) { 1690 | args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; 1691 | } 1692 | while (position < arguments.length) args.push(arguments[position++]); 1693 | return executeBound(func, bound, this, this, args); 1694 | }; 1695 | return bound; 1696 | }; 1697 | 1698 | // Bind a number of an object's methods to that object. Remaining arguments 1699 | // are the method names to be bound. Useful for ensuring that all callbacks 1700 | // defined on an object belong to it. 1701 | _.bindAll = function(obj) { 1702 | var i, length = arguments.length, key; 1703 | if (length <= 1) throw new Error('bindAll must be passed function names'); 1704 | for (i = 1; i < length; i++) { 1705 | key = arguments[i]; 1706 | obj[key] = _.bind(obj[key], obj); 1707 | } 1708 | return obj; 1709 | }; 1710 | 1711 | // Memoize an expensive function by storing its results. 1712 | _.memoize = function(func, hasher) { 1713 | var memoize = function(key) { 1714 | var cache = memoize.cache; 1715 | var address = '' + (hasher ? hasher.apply(this, arguments) : key); 1716 | if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); 1717 | return cache[address]; 1718 | }; 1719 | memoize.cache = {}; 1720 | return memoize; 1721 | }; 1722 | 1723 | // Delays a function for the given number of milliseconds, and then calls 1724 | // it with the arguments supplied. 1725 | _.delay = function(func, wait) { 1726 | var args = slice.call(arguments, 2); 1727 | return setTimeout(function(){ 1728 | return func.apply(null, args); 1729 | }, wait); 1730 | }; 1731 | 1732 | // Defers a function, scheduling it to run after the current call stack has 1733 | // cleared. 1734 | _.defer = _.partial(_.delay, _, 1); 1735 | 1736 | // Returns a function, that, when invoked, will only be triggered at most once 1737 | // during a given window of time. Normally, the throttled function will run 1738 | // as much as it can, without ever going more than once per `wait` duration; 1739 | // but if you'd like to disable the execution on the leading edge, pass 1740 | // `{leading: false}`. To disable execution on the trailing edge, ditto. 1741 | _.throttle = function(func, wait, options) { 1742 | var context, args, result; 1743 | var timeout = null; 1744 | var previous = 0; 1745 | if (!options) options = {}; 1746 | var later = function() { 1747 | previous = options.leading === false ? 0 : _.now(); 1748 | timeout = null; 1749 | result = func.apply(context, args); 1750 | if (!timeout) context = args = null; 1751 | }; 1752 | return function() { 1753 | var now = _.now(); 1754 | if (!previous && options.leading === false) previous = now; 1755 | var remaining = wait - (now - previous); 1756 | context = this; 1757 | args = arguments; 1758 | if (remaining <= 0 || remaining > wait) { 1759 | if (timeout) { 1760 | clearTimeout(timeout); 1761 | timeout = null; 1762 | } 1763 | previous = now; 1764 | result = func.apply(context, args); 1765 | if (!timeout) context = args = null; 1766 | } else if (!timeout && options.trailing !== false) { 1767 | timeout = setTimeout(later, remaining); 1768 | } 1769 | return result; 1770 | }; 1771 | }; 1772 | 1773 | // Returns a function, that, as long as it continues to be invoked, will not 1774 | // be triggered. The function will be called after it stops being called for 1775 | // N milliseconds. If `immediate` is passed, trigger the function on the 1776 | // leading edge, instead of the trailing. 1777 | _.debounce = function(func, wait, immediate) { 1778 | var timeout, args, context, timestamp, result; 1779 | 1780 | var later = function() { 1781 | var last = _.now() - timestamp; 1782 | 1783 | if (last < wait && last >= 0) { 1784 | timeout = setTimeout(later, wait - last); 1785 | } else { 1786 | timeout = null; 1787 | if (!immediate) { 1788 | result = func.apply(context, args); 1789 | if (!timeout) context = args = null; 1790 | } 1791 | } 1792 | }; 1793 | 1794 | return function() { 1795 | context = this; 1796 | args = arguments; 1797 | timestamp = _.now(); 1798 | var callNow = immediate && !timeout; 1799 | if (!timeout) timeout = setTimeout(later, wait); 1800 | if (callNow) { 1801 | result = func.apply(context, args); 1802 | context = args = null; 1803 | } 1804 | 1805 | return result; 1806 | }; 1807 | }; 1808 | 1809 | // Returns the first function passed as an argument to the second, 1810 | // allowing you to adjust arguments, run code before and after, and 1811 | // conditionally execute the original function. 1812 | _.wrap = function(func, wrapper) { 1813 | return _.partial(wrapper, func); 1814 | }; 1815 | 1816 | // Returns a negated version of the passed-in predicate. 1817 | _.negate = function(predicate) { 1818 | return function() { 1819 | return !predicate.apply(this, arguments); 1820 | }; 1821 | }; 1822 | 1823 | // Returns a function that is the composition of a list of functions, each 1824 | // consuming the return value of the function that follows. 1825 | _.compose = function() { 1826 | var args = arguments; 1827 | var start = args.length - 1; 1828 | return function() { 1829 | var i = start; 1830 | var result = args[start].apply(this, arguments); 1831 | while (i--) result = args[i].call(this, result); 1832 | return result; 1833 | }; 1834 | }; 1835 | 1836 | // Returns a function that will only be executed on and after the Nth call. 1837 | _.after = function(times, func) { 1838 | return function() { 1839 | if (--times < 1) { 1840 | return func.apply(this, arguments); 1841 | } 1842 | }; 1843 | }; 1844 | 1845 | // Returns a function that will only be executed up to (but not including) the Nth call. 1846 | _.before = function(times, func) { 1847 | var memo; 1848 | return function() { 1849 | if (--times > 0) { 1850 | memo = func.apply(this, arguments); 1851 | } 1852 | if (times <= 1) func = null; 1853 | return memo; 1854 | }; 1855 | }; 1856 | 1857 | // Returns a function that will be executed at most one time, no matter how 1858 | // often you call it. Useful for lazy initialization. 1859 | _.once = _.partial(_.before, 2); 1860 | 1861 | // Object Functions 1862 | // ---------------- 1863 | 1864 | // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. 1865 | var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); 1866 | var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', 1867 | 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; 1868 | 1869 | function collectNonEnumProps(obj, keys) { 1870 | var nonEnumIdx = nonEnumerableProps.length; 1871 | var constructor = obj.constructor; 1872 | var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; 1873 | 1874 | // Constructor is a special case. 1875 | var prop = 'constructor'; 1876 | if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); 1877 | 1878 | while (nonEnumIdx--) { 1879 | prop = nonEnumerableProps[nonEnumIdx]; 1880 | if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { 1881 | keys.push(prop); 1882 | } 1883 | } 1884 | } 1885 | 1886 | // Retrieve the names of an object's own properties. 1887 | // Delegates to **ECMAScript 5**'s native `Object.keys` 1888 | _.keys = function(obj) { 1889 | if (!_.isObject(obj)) return []; 1890 | if (nativeKeys) return nativeKeys(obj); 1891 | var keys = []; 1892 | for (var key in obj) if (_.has(obj, key)) keys.push(key); 1893 | // Ahem, IE < 9. 1894 | if (hasEnumBug) collectNonEnumProps(obj, keys); 1895 | return keys; 1896 | }; 1897 | 1898 | // Retrieve all the property names of an object. 1899 | _.allKeys = function(obj) { 1900 | if (!_.isObject(obj)) return []; 1901 | var keys = []; 1902 | for (var key in obj) keys.push(key); 1903 | // Ahem, IE < 9. 1904 | if (hasEnumBug) collectNonEnumProps(obj, keys); 1905 | return keys; 1906 | }; 1907 | 1908 | // Retrieve the values of an object's properties. 1909 | _.values = function(obj) { 1910 | var keys = _.keys(obj); 1911 | var length = keys.length; 1912 | var values = Array(length); 1913 | for (var i = 0; i < length; i++) { 1914 | values[i] = obj[keys[i]]; 1915 | } 1916 | return values; 1917 | }; 1918 | 1919 | // Returns the results of applying the iteratee to each element of the object 1920 | // In contrast to _.map it returns an object 1921 | _.mapObject = function(obj, iteratee, context) { 1922 | iteratee = cb(iteratee, context); 1923 | var keys = _.keys(obj), 1924 | length = keys.length, 1925 | results = {}, 1926 | currentKey; 1927 | for (var index = 0; index < length; index++) { 1928 | currentKey = keys[index]; 1929 | results[currentKey] = iteratee(obj[currentKey], currentKey, obj); 1930 | } 1931 | return results; 1932 | }; 1933 | 1934 | // Convert an object into a list of `[key, value]` pairs. 1935 | _.pairs = function(obj) { 1936 | var keys = _.keys(obj); 1937 | var length = keys.length; 1938 | var pairs = Array(length); 1939 | for (var i = 0; i < length; i++) { 1940 | pairs[i] = [keys[i], obj[keys[i]]]; 1941 | } 1942 | return pairs; 1943 | }; 1944 | 1945 | // Invert the keys and values of an object. The values must be serializable. 1946 | _.invert = function(obj) { 1947 | var result = {}; 1948 | var keys = _.keys(obj); 1949 | for (var i = 0, length = keys.length; i < length; i++) { 1950 | result[obj[keys[i]]] = keys[i]; 1951 | } 1952 | return result; 1953 | }; 1954 | 1955 | // Return a sorted list of the function names available on the object. 1956 | // Aliased as `methods` 1957 | _.functions = _.methods = function(obj) { 1958 | var names = []; 1959 | for (var key in obj) { 1960 | if (_.isFunction(obj[key])) names.push(key); 1961 | } 1962 | return names.sort(); 1963 | }; 1964 | 1965 | // Extend a given object with all the properties in passed-in object(s). 1966 | _.extend = createAssigner(_.allKeys); 1967 | 1968 | // Assigns a given object with all the own properties in the passed-in object(s) 1969 | // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) 1970 | _.extendOwn = _.assign = createAssigner(_.keys); 1971 | 1972 | // Returns the first key on an object that passes a predicate test 1973 | _.findKey = function(obj, predicate, context) { 1974 | predicate = cb(predicate, context); 1975 | var keys = _.keys(obj), key; 1976 | for (var i = 0, length = keys.length; i < length; i++) { 1977 | key = keys[i]; 1978 | if (predicate(obj[key], key, obj)) return key; 1979 | } 1980 | }; 1981 | 1982 | // Return a copy of the object only containing the whitelisted properties. 1983 | _.pick = function(object, oiteratee, context) { 1984 | var result = {}, obj = object, iteratee, keys; 1985 | if (obj == null) return result; 1986 | if (_.isFunction(oiteratee)) { 1987 | keys = _.allKeys(obj); 1988 | iteratee = optimizeCb(oiteratee, context); 1989 | } else { 1990 | keys = flatten(arguments, false, false, 1); 1991 | iteratee = function(value, key, obj) { return key in obj; }; 1992 | obj = Object(obj); 1993 | } 1994 | for (var i = 0, length = keys.length; i < length; i++) { 1995 | var key = keys[i]; 1996 | var value = obj[key]; 1997 | if (iteratee(value, key, obj)) result[key] = value; 1998 | } 1999 | return result; 2000 | }; 2001 | 2002 | // Return a copy of the object without the blacklisted properties. 2003 | _.omit = function(obj, iteratee, context) { 2004 | if (_.isFunction(iteratee)) { 2005 | iteratee = _.negate(iteratee); 2006 | } else { 2007 | var keys = _.map(flatten(arguments, false, false, 1), String); 2008 | iteratee = function(value, key) { 2009 | return !_.contains(keys, key); 2010 | }; 2011 | } 2012 | return _.pick(obj, iteratee, context); 2013 | }; 2014 | 2015 | // Fill in a given object with default properties. 2016 | _.defaults = createAssigner(_.allKeys, true); 2017 | 2018 | // Creates an object that inherits from the given prototype object. 2019 | // If additional properties are provided then they will be added to the 2020 | // created object. 2021 | _.create = function(prototype, props) { 2022 | var result = baseCreate(prototype); 2023 | if (props) _.extendOwn(result, props); 2024 | return result; 2025 | }; 2026 | 2027 | // Create a (shallow-cloned) duplicate of an object. 2028 | _.clone = function(obj) { 2029 | if (!_.isObject(obj)) return obj; 2030 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 2031 | }; 2032 | 2033 | // Invokes interceptor with the obj, and then returns obj. 2034 | // The primary purpose of this method is to "tap into" a method chain, in 2035 | // order to perform operations on intermediate results within the chain. 2036 | _.tap = function(obj, interceptor) { 2037 | interceptor(obj); 2038 | return obj; 2039 | }; 2040 | 2041 | // Returns whether an object has a given set of `key:value` pairs. 2042 | _.isMatch = function(object, attrs) { 2043 | var keys = _.keys(attrs), length = keys.length; 2044 | if (object == null) return !length; 2045 | var obj = Object(object); 2046 | for (var i = 0; i < length; i++) { 2047 | var key = keys[i]; 2048 | if (attrs[key] !== obj[key] || !(key in obj)) return false; 2049 | } 2050 | return true; 2051 | }; 2052 | 2053 | 2054 | // Internal recursive comparison function for `isEqual`. 2055 | var eq = function(a, b, aStack, bStack) { 2056 | // Identical objects are equal. `0 === -0`, but they aren't identical. 2057 | // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). 2058 | if (a === b) return a !== 0 || 1 / a === 1 / b; 2059 | // A strict comparison is necessary because `null == undefined`. 2060 | if (a == null || b == null) return a === b; 2061 | // Unwrap any wrapped objects. 2062 | if (a instanceof _) a = a._wrapped; 2063 | if (b instanceof _) b = b._wrapped; 2064 | // Compare `[[Class]]` names. 2065 | var className = toString.call(a); 2066 | if (className !== toString.call(b)) return false; 2067 | switch (className) { 2068 | // Strings, numbers, regular expressions, dates, and booleans are compared by value. 2069 | case '[object RegExp]': 2070 | // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') 2071 | case '[object String]': 2072 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 2073 | // equivalent to `new String("5")`. 2074 | return '' + a === '' + b; 2075 | case '[object Number]': 2076 | // `NaN`s are equivalent, but non-reflexive. 2077 | // Object(NaN) is equivalent to NaN 2078 | if (+a !== +a) return +b !== +b; 2079 | // An `egal` comparison is performed for other numeric values. 2080 | return +a === 0 ? 1 / +a === 1 / b : +a === +b; 2081 | case '[object Date]': 2082 | case '[object Boolean]': 2083 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their 2084 | // millisecond representations. Note that invalid dates with millisecond representations 2085 | // of `NaN` are not equivalent. 2086 | return +a === +b; 2087 | } 2088 | 2089 | var areArrays = className === '[object Array]'; 2090 | if (!areArrays) { 2091 | if (typeof a != 'object' || typeof b != 'object') return false; 2092 | 2093 | // Objects with different constructors are not equivalent, but `Object`s or `Array`s 2094 | // from different frames are. 2095 | var aCtor = a.constructor, bCtor = b.constructor; 2096 | if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && 2097 | _.isFunction(bCtor) && bCtor instanceof bCtor) 2098 | && ('constructor' in a && 'constructor' in b)) { 2099 | return false; 2100 | } 2101 | } 2102 | // Assume equality for cyclic structures. The algorithm for detecting cyclic 2103 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 2104 | 2105 | // Initializing stack of traversed objects. 2106 | // It's done here since we only need them for objects and arrays comparison. 2107 | aStack = aStack || []; 2108 | bStack = bStack || []; 2109 | var length = aStack.length; 2110 | while (length--) { 2111 | // Linear search. Performance is inversely proportional to the number of 2112 | // unique nested structures. 2113 | if (aStack[length] === a) return bStack[length] === b; 2114 | } 2115 | 2116 | // Add the first object to the stack of traversed objects. 2117 | aStack.push(a); 2118 | bStack.push(b); 2119 | 2120 | // Recursively compare objects and arrays. 2121 | if (areArrays) { 2122 | // Compare array lengths to determine if a deep comparison is necessary. 2123 | length = a.length; 2124 | if (length !== b.length) return false; 2125 | // Deep compare the contents, ignoring non-numeric properties. 2126 | while (length--) { 2127 | if (!eq(a[length], b[length], aStack, bStack)) return false; 2128 | } 2129 | } else { 2130 | // Deep compare objects. 2131 | var keys = _.keys(a), key; 2132 | length = keys.length; 2133 | // Ensure that both objects contain the same number of properties before comparing deep equality. 2134 | if (_.keys(b).length !== length) return false; 2135 | while (length--) { 2136 | // Deep compare each member 2137 | key = keys[length]; 2138 | if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; 2139 | } 2140 | } 2141 | // Remove the first object from the stack of traversed objects. 2142 | aStack.pop(); 2143 | bStack.pop(); 2144 | return true; 2145 | }; 2146 | 2147 | // Perform a deep comparison to check if two objects are equal. 2148 | _.isEqual = function(a, b) { 2149 | return eq(a, b); 2150 | }; 2151 | 2152 | // Is a given array, string, or object empty? 2153 | // An "empty" object has no enumerable own-properties. 2154 | _.isEmpty = function(obj) { 2155 | if (obj == null) return true; 2156 | if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; 2157 | return _.keys(obj).length === 0; 2158 | }; 2159 | 2160 | // Is a given value a DOM element? 2161 | _.isElement = function(obj) { 2162 | return !!(obj && obj.nodeType === 1); 2163 | }; 2164 | 2165 | // Is a given value an array? 2166 | // Delegates to ECMA5's native Array.isArray 2167 | _.isArray = nativeIsArray || function(obj) { 2168 | return toString.call(obj) === '[object Array]'; 2169 | }; 2170 | 2171 | // Is a given variable an object? 2172 | _.isObject = function(obj) { 2173 | var type = typeof obj; 2174 | return type === 'function' || type === 'object' && !!obj; 2175 | }; 2176 | 2177 | // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. 2178 | _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { 2179 | _['is' + name] = function(obj) { 2180 | return toString.call(obj) === '[object ' + name + ']'; 2181 | }; 2182 | }); 2183 | 2184 | // Define a fallback version of the method in browsers (ahem, IE < 9), where 2185 | // there isn't any inspectable "Arguments" type. 2186 | if (!_.isArguments(arguments)) { 2187 | _.isArguments = function(obj) { 2188 | return _.has(obj, 'callee'); 2189 | }; 2190 | } 2191 | 2192 | // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, 2193 | // IE 11 (#1621), and in Safari 8 (#1929). 2194 | if (typeof /./ != 'function' && typeof Int8Array != 'object') { 2195 | _.isFunction = function(obj) { 2196 | return typeof obj == 'function' || false; 2197 | }; 2198 | } 2199 | 2200 | // Is a given object a finite number? 2201 | _.isFinite = function(obj) { 2202 | return isFinite(obj) && !isNaN(parseFloat(obj)); 2203 | }; 2204 | 2205 | // Is the given value `NaN`? (NaN is the only number which does not equal itself). 2206 | _.isNaN = function(obj) { 2207 | return _.isNumber(obj) && obj !== +obj; 2208 | }; 2209 | 2210 | // Is a given value a boolean? 2211 | _.isBoolean = function(obj) { 2212 | return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; 2213 | }; 2214 | 2215 | // Is a given value equal to null? 2216 | _.isNull = function(obj) { 2217 | return obj === null; 2218 | }; 2219 | 2220 | // Is a given variable undefined? 2221 | _.isUndefined = function(obj) { 2222 | return obj === void 0; 2223 | }; 2224 | 2225 | // Shortcut function for checking if an object has a given property directly 2226 | // on itself (in other words, not on a prototype). 2227 | _.has = function(obj, key) { 2228 | return obj != null && hasOwnProperty.call(obj, key); 2229 | }; 2230 | 2231 | // Utility Functions 2232 | // ----------------- 2233 | 2234 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 2235 | // previous owner. Returns a reference to the Underscore object. 2236 | _.noConflict = function() { 2237 | root._ = previousUnderscore; 2238 | return this; 2239 | }; 2240 | 2241 | // Keep the identity function around for default iteratees. 2242 | _.identity = function(value) { 2243 | return value; 2244 | }; 2245 | 2246 | // Predicate-generating functions. Often useful outside of Underscore. 2247 | _.constant = function(value) { 2248 | return function() { 2249 | return value; 2250 | }; 2251 | }; 2252 | 2253 | _.noop = function(){}; 2254 | 2255 | _.property = property; 2256 | 2257 | // Generates a function for a given object that returns a given property. 2258 | _.propertyOf = function(obj) { 2259 | return obj == null ? function(){} : function(key) { 2260 | return obj[key]; 2261 | }; 2262 | }; 2263 | 2264 | // Returns a predicate for checking whether an object has a given set of 2265 | // `key:value` pairs. 2266 | _.matcher = _.matches = function(attrs) { 2267 | attrs = _.extendOwn({}, attrs); 2268 | return function(obj) { 2269 | return _.isMatch(obj, attrs); 2270 | }; 2271 | }; 2272 | 2273 | // Run a function **n** times. 2274 | _.times = function(n, iteratee, context) { 2275 | var accum = Array(Math.max(0, n)); 2276 | iteratee = optimizeCb(iteratee, context, 1); 2277 | for (var i = 0; i < n; i++) accum[i] = iteratee(i); 2278 | return accum; 2279 | }; 2280 | 2281 | // Return a random integer between min and max (inclusive). 2282 | _.random = function(min, max) { 2283 | if (max == null) { 2284 | max = min; 2285 | min = 0; 2286 | } 2287 | return min + Math.floor(Math.random() * (max - min + 1)); 2288 | }; 2289 | 2290 | // A (possibly faster) way to get the current timestamp as an integer. 2291 | _.now = Date.now || function() { 2292 | return new Date().getTime(); 2293 | }; 2294 | 2295 | // List of HTML entities for escaping. 2296 | var escapeMap = { 2297 | '&': '&', 2298 | '<': '<', 2299 | '>': '>', 2300 | '"': '"', 2301 | "'": ''', 2302 | '`': '`' 2303 | }; 2304 | var unescapeMap = _.invert(escapeMap); 2305 | 2306 | // Functions for escaping and unescaping strings to/from HTML interpolation. 2307 | var createEscaper = function(map) { 2308 | var escaper = function(match) { 2309 | return map[match]; 2310 | }; 2311 | // Regexes for identifying a key that needs to be escaped 2312 | var source = '(?:' + _.keys(map).join('|') + ')'; 2313 | var testRegexp = RegExp(source); 2314 | var replaceRegexp = RegExp(source, 'g'); 2315 | return function(string) { 2316 | string = string == null ? '' : '' + string; 2317 | return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; 2318 | }; 2319 | }; 2320 | _.escape = createEscaper(escapeMap); 2321 | _.unescape = createEscaper(unescapeMap); 2322 | 2323 | // If the value of the named `property` is a function then invoke it with the 2324 | // `object` as context; otherwise, return it. 2325 | _.result = function(object, property, fallback) { 2326 | var value = object == null ? void 0 : object[property]; 2327 | if (value === void 0) { 2328 | value = fallback; 2329 | } 2330 | return _.isFunction(value) ? value.call(object) : value; 2331 | }; 2332 | 2333 | // Generate a unique integer id (unique within the entire client session). 2334 | // Useful for temporary DOM ids. 2335 | var idCounter = 0; 2336 | _.uniqueId = function(prefix) { 2337 | var id = ++idCounter + ''; 2338 | return prefix ? prefix + id : id; 2339 | }; 2340 | 2341 | // By default, Underscore uses ERB-style template delimiters, change the 2342 | // following template settings to use alternative delimiters. 2343 | _.templateSettings = { 2344 | evaluate : /<%([\s\S]+?)%>/g, 2345 | interpolate : /<%=([\s\S]+?)%>/g, 2346 | escape : /<%-([\s\S]+?)%>/g 2347 | }; 2348 | 2349 | // When customizing `templateSettings`, if you don't want to define an 2350 | // interpolation, evaluation or escaping regex, we need one that is 2351 | // guaranteed not to match. 2352 | var noMatch = /(.)^/; 2353 | 2354 | // Certain characters need to be escaped so that they can be put into a 2355 | // string literal. 2356 | var escapes = { 2357 | "'": "'", 2358 | '\\': '\\', 2359 | '\r': 'r', 2360 | '\n': 'n', 2361 | '\u2028': 'u2028', 2362 | '\u2029': 'u2029' 2363 | }; 2364 | 2365 | var escaper = /\\|'|\r|\n|\u2028|\u2029/g; 2366 | 2367 | var escapeChar = function(match) { 2368 | return '\\' + escapes[match]; 2369 | }; 2370 | 2371 | // JavaScript micro-templating, similar to John Resig's implementation. 2372 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 2373 | // and correctly escapes quotes within interpolated code. 2374 | // NB: `oldSettings` only exists for backwards compatibility. 2375 | _.template = function(text, settings, oldSettings) { 2376 | if (!settings && oldSettings) settings = oldSettings; 2377 | settings = _.defaults({}, settings, _.templateSettings); 2378 | 2379 | // Combine delimiters into one regular expression via alternation. 2380 | var matcher = RegExp([ 2381 | (settings.escape || noMatch).source, 2382 | (settings.interpolate || noMatch).source, 2383 | (settings.evaluate || noMatch).source 2384 | ].join('|') + '|$', 'g'); 2385 | 2386 | // Compile the template source, escaping string literals appropriately. 2387 | var index = 0; 2388 | var source = "__p+='"; 2389 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 2390 | source += text.slice(index, offset).replace(escaper, escapeChar); 2391 | index = offset + match.length; 2392 | 2393 | if (escape) { 2394 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 2395 | } else if (interpolate) { 2396 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 2397 | } else if (evaluate) { 2398 | source += "';\n" + evaluate + "\n__p+='"; 2399 | } 2400 | 2401 | // Adobe VMs need the match returned to produce the correct offest. 2402 | return match; 2403 | }); 2404 | source += "';\n"; 2405 | 2406 | // If a variable is not specified, place data values in local scope. 2407 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 2408 | 2409 | source = "var __t,__p='',__j=Array.prototype.join," + 2410 | "print=function(){__p+=__j.call(arguments,'');};\n" + 2411 | source + 'return __p;\n'; 2412 | 2413 | try { 2414 | var render = new Function(settings.variable || 'obj', '_', source); 2415 | } catch (e) { 2416 | e.source = source; 2417 | throw e; 2418 | } 2419 | 2420 | var template = function(data) { 2421 | return render.call(this, data, _); 2422 | }; 2423 | 2424 | // Provide the compiled source as a convenience for precompilation. 2425 | var argument = settings.variable || 'obj'; 2426 | template.source = 'function(' + argument + '){\n' + source + '}'; 2427 | 2428 | return template; 2429 | }; 2430 | 2431 | // Add a "chain" function. Start chaining a wrapped Underscore object. 2432 | _.chain = function(obj) { 2433 | var instance = _(obj); 2434 | instance._chain = true; 2435 | return instance; 2436 | }; 2437 | 2438 | // OOP 2439 | // --------------- 2440 | // If Underscore is called as a function, it returns a wrapped object that 2441 | // can be used OO-style. This wrapper holds altered versions of all the 2442 | // underscore functions. Wrapped objects may be chained. 2443 | 2444 | // Helper function to continue chaining intermediate results. 2445 | var result = function(instance, obj) { 2446 | return instance._chain ? _(obj).chain() : obj; 2447 | }; 2448 | 2449 | // Add your own custom functions to the Underscore object. 2450 | _.mixin = function(obj) { 2451 | _.each(_.functions(obj), function(name) { 2452 | var func = _[name] = obj[name]; 2453 | _.prototype[name] = function() { 2454 | var args = [this._wrapped]; 2455 | push.apply(args, arguments); 2456 | return result(this, func.apply(_, args)); 2457 | }; 2458 | }); 2459 | }; 2460 | 2461 | // Add all of the Underscore functions to the wrapper object. 2462 | _.mixin(_); 2463 | 2464 | // Add all mutator Array functions to the wrapper. 2465 | _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 2466 | var method = ArrayProto[name]; 2467 | _.prototype[name] = function() { 2468 | var obj = this._wrapped; 2469 | method.apply(obj, arguments); 2470 | if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; 2471 | return result(this, obj); 2472 | }; 2473 | }); 2474 | 2475 | // Add all accessor Array functions to the wrapper. 2476 | _.each(['concat', 'join', 'slice'], function(name) { 2477 | var method = ArrayProto[name]; 2478 | _.prototype[name] = function() { 2479 | return result(this, method.apply(this._wrapped, arguments)); 2480 | }; 2481 | }); 2482 | 2483 | // Extracts the result from a wrapped and chained object. 2484 | _.prototype.value = function() { 2485 | return this._wrapped; 2486 | }; 2487 | 2488 | // Provide unwrapping proxy for some methods used in engine operations 2489 | // such as arithmetic and JSON stringification. 2490 | _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; 2491 | 2492 | _.prototype.toString = function() { 2493 | return '' + this._wrapped; 2494 | }; 2495 | 2496 | // AMD registration happens at the end for compatibility with AMD loaders 2497 | // that may not enforce next-turn semantics on modules. Even though general 2498 | // practice for AMD registration is to be anonymous, underscore registers 2499 | // as a named module because, like jQuery, it is a base library that is 2500 | // popular enough to be bundled in a third party lib, but not be part of 2501 | // an AMD load request. Those cases could generate an error when an 2502 | // anonymous define() is called outside of a loader request. 2503 | if (true) { 2504 | !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function() { 2505 | return _; 2506 | }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); 2507 | } 2508 | }.call(this)); 2509 | 2510 | 2511 | /***/ }, 2512 | /* 5 */ 2513 | /***/ function(module, exports) { 2514 | 2515 | // Based on http://www.dgp.toronto.edu/people/stam/reality/Research/pdf/GDC03.pdf 2516 | /** 2517 | * Copyright (c) 2009 Oliver Hunt 2518 | * 2519 | * Permission is hereby granted, free of charge, to any person 2520 | * obtaining a copy of this software and associated documentation 2521 | * files (the "Software"), to deal in the Software without 2522 | * restriction, including without limitation the rights to use, 2523 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 2524 | * copies of the Software, and to permit persons to whom the 2525 | * Software is furnished to do so, subject to the following 2526 | * conditions: 2527 | * 2528 | * The above copyright notice and this permission notice shall be 2529 | * included in all copies or substantial portions of the Software. 2530 | * 2531 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 2532 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 2533 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 2534 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 2535 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 2536 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 2537 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 2538 | * OTHER DEALINGS IN THE SOFTWARE. 2539 | */ 2540 | 2541 | module.exports = function FluidField(canvas) { 2542 | function addFields(x, s, dt) 2543 | { 2544 | for (var i=0; i Wp5) 2677 | x = Wp5; 2678 | var i0 = x | 0; 2679 | var i1 = i0 + 1; 2680 | if (y < 0.5) 2681 | y = 0.5; 2682 | else if (y > Hp5) 2683 | y = Hp5; 2684 | var j0 = y | 0; 2685 | var j1 = j0 + 1; 2686 | var s1 = x - i0; 2687 | var s0 = 1 - s1; 2688 | var t1 = y - j0; 2689 | var t0 = 1 - t1; 2690 | var row1 = j0 * rowSize; 2691 | var row2 = j1 * rowSize; 2692 | d[pos] = s0 * (t0 * d0[i0 + row1] + t1 * d0[i0 + row2]) + s1 * (t0 * d0[i1 + row1] + t1 * d0[i1 + row2]); 2693 | } 2694 | } 2695 | set_bnd(b, d); 2696 | } 2697 | 2698 | function project(u, v, p, div) 2699 | { 2700 | var h = -0.5 / Math.sqrt(width * height); 2701 | for (var j = 1 ; j <= height; j++ ) { 2702 | var row = j * rowSize; 2703 | var previousRow = (j - 1) * rowSize; 2704 | var prevValue = row - 1; 2705 | var currentRow = row; 2706 | var nextValue = row + 1; 2707 | var nextRow = (j + 1) * rowSize; 2708 | for (var i = 1; i <= width; i++ ) { 2709 | div[++currentRow] = h * (u[++nextValue] - u[++prevValue] + v[++nextRow] - v[++previousRow]); 2710 | p[currentRow] = 0; 2711 | } 2712 | } 2713 | set_bnd(0, div); 2714 | set_bnd(0, p); 2715 | 2716 | lin_solve(0, p, div, 1, 4 ); 2717 | var wScale = 0.5 * width; 2718 | var hScale = 0.5 * height; 2719 | for (var j = 1; j<= height; j++ ) { 2720 | var prevPos = j * rowSize - 1; 2721 | var currentPos = j * rowSize; 2722 | var nextPos = j * rowSize + 1; 2723 | var prevRow = (j - 1) * rowSize; 2724 | var currentRow = j * rowSize; 2725 | var nextRow = (j + 1) * rowSize; 2726 | 2727 | for (var i = 1; i<= width; i++) { 2728 | u[++currentPos] -= wScale * (p[++nextPos] - p[++prevPos]); 2729 | v[currentPos] -= hScale * (p[++nextRow] - p[++prevRow]); 2730 | } 2731 | } 2732 | set_bnd(1, u); 2733 | set_bnd(2, v); 2734 | } 2735 | 2736 | function dens_step(x, x0, u, v, dt) 2737 | { 2738 | addFields(x, x0, dt); 2739 | diffuse(0, x0, x, dt ); 2740 | advect(0, x, x0, u, v, dt ); 2741 | } 2742 | 2743 | function vel_step(u, v, u0, v0, dt) 2744 | { 2745 | addFields(u, u0, dt ); 2746 | addFields(v, v0, dt ); 2747 | var temp = u0; u0 = u; u = temp; 2748 | var temp = v0; v0 = v; v = temp; 2749 | diffuse2(u,u0,v,v0, dt); 2750 | project(u, v, u0, v0); 2751 | var temp = u0; u0 = u; u = temp; 2752 | var temp = v0; v0 = v; v = temp; 2753 | advect(1, u, u0, u0, v0, dt); 2754 | advect(2, v, v0, u0, v0, dt); 2755 | project(u, v, u0, v0 ); 2756 | } 2757 | var uiCallback = function(d,u,v) {}; 2758 | 2759 | function Field(dens, u, v) { 2760 | // Just exposing the fields here rather than using accessors is a measurable win during display (maybe 5%) 2761 | // but makes the code ugly. 2762 | this.setDensity = function(x, y, d) { 2763 | dens[(x + 1) + (y + 1) * rowSize] = d; 2764 | } 2765 | this.getDensity = function(x, y) { 2766 | return dens[(x + 1) + (y + 1) * rowSize]; 2767 | } 2768 | this.setVelocity = function(x, y, xv, yv) { 2769 | u[(x + 1) + (y + 1) * rowSize] = xv; 2770 | v[(x + 1) + (y + 1) * rowSize] = yv; 2771 | } 2772 | this.getXVelocity = function(x, y) { 2773 | return u[(x + 1) + (y + 1) * rowSize]; 2774 | } 2775 | this.getYVelocity = function(x, y) { 2776 | return v[(x + 1) + (y + 1) * rowSize]; 2777 | } 2778 | this.width = function() { return width; } 2779 | this.height = function() { return height; } 2780 | } 2781 | function queryUI(d, u, v) 2782 | { 2783 | for (var i = 0; i < size; i++) 2784 | u[i] = v[i] = d[i] = 0.0; 2785 | uiCallback(new Field(d, u, v)); 2786 | } 2787 | 2788 | this.update = function () { 2789 | queryUI(dens_prev, u_prev, v_prev); 2790 | vel_step(u, v, u_prev, v_prev, dt); 2791 | dens_step(dens, dens_prev, u, v, dt); 2792 | displayFunc(new Field(dens, u, v)); 2793 | } 2794 | this.setDisplayFunction = function(func) { 2795 | displayFunc = func; 2796 | } 2797 | 2798 | this.iterations = function() { return iterations; } 2799 | this.setIterations = function(iters) { 2800 | if (iters > 0 && iters <= 100) 2801 | iterations = iters; 2802 | } 2803 | this.setUICallback = function(callback) { 2804 | uiCallback = callback; 2805 | } 2806 | var iterations = 15; 2807 | var visc = 0.8; 2808 | var dt = 0.1; 2809 | var dens; 2810 | var dens_prev; 2811 | var u; 2812 | var u_prev; 2813 | var v; 2814 | var v_prev; 2815 | var width; 2816 | var height; 2817 | var rowSize; 2818 | var size; 2819 | var displayFunc; 2820 | function reset() 2821 | { 2822 | rowSize = width + 2; 2823 | size = (width+2)*(height+2); 2824 | dens = new Array(size); 2825 | dens_prev = new Array(size); 2826 | u = new Array(size); 2827 | u_prev = new Array(size); 2828 | v = new Array(size); 2829 | v_prev = new Array(size); 2830 | for (var i = 0; i < size; i++) 2831 | dens_prev[i] = u_prev[i] = v_prev[i] = dens[i] = u[i] = v[i] = 0; 2832 | } 2833 | this.reset = reset; 2834 | this.setResolution = function (hRes, wRes) 2835 | { 2836 | var res = wRes * hRes; 2837 | if (res > 0 && res < 1000000 && (wRes != width || hRes != height)) { 2838 | width = wRes; 2839 | height = hRes; 2840 | reset(); 2841 | return true; 2842 | } 2843 | return false; 2844 | } 2845 | this.setResolution(100, 100); 2846 | } 2847 | 2848 | 2849 | 2850 | /***/ } 2851 | /******/ ]); -------------------------------------------------------------------------------- /dust.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 10px; 3 | padding: 3px; 4 | background: #0C0C18 ; 5 | font-family: monospace; 6 | font-size:normal; 7 | color:#cccccc; 8 | 9 | } 10 | 11 | /* Display error mesasges in bright red */ 12 | #error-display { 13 | color: #ff0000; 14 | } 15 | 16 | div[data-formula] { 17 | display: table; 18 | } 19 | 20 | a { 21 | color: #3333ee; 22 | text-decoration: none; 23 | } 24 | 25 | a:hover { 26 | text-decoration: underline; 27 | } 28 | 29 | .example-label { 30 | vertical-align: center; 31 | } 32 | 33 | div[data-formula] canvas { 34 | float: left; 35 | margin-right: 10px; 36 | } 37 | 38 | .stop-scrolling { 39 | height: 100%; 40 | overflow: hidden; 41 | } 42 | 43 | ocean-display { 44 | transform-origin: 0% 0%; 45 | -ms-transform-origin: 0% 0%; 46 | -webkit-transform-origin: 0% 0%; 47 | -o-transform-origin: 0% 0%; 48 | -moz-transform-origin: 0% 0%; 49 | transform: scale(5, 5); 50 | -ms-transform: scale(5, 5); 51 | -webkit-transform: scale(5, 5); 52 | -o-transform: scale(5, 5); 53 | -moz-transform: scale(5, 5); 54 | image-rendering: crisp-edges; 55 | image-rendering: -moz-crisp-edges; 56 | image-rendering: -webkit-optimize-contrast; 57 | image-rendering:-webkit-pixelated; 58 | image-rendering: optimize-contrast; 59 | -ms-interpolation-mode: nearest-neighbor; 60 | } -------------------------------------------------------------------------------- /dust.js: -------------------------------------------------------------------------------- 1 | var husl = require('husl'); 2 | var _ = require('underscore'); 3 | var FluidField = require('./pressure.js'); 4 | 5 | 6 | // var Dust = (function() { 7 | // Constant properties 8 | var Type = { 9 | Dust: "Dust", 10 | Water: "Water", 11 | Brick: "Brick", 12 | Erase: "Erase", 13 | Tap: "Tap", 14 | Air: "Air", 15 | Fire: "Fire", 16 | Glitch: "Glitch", 17 | Powder: "Powder", 18 | Nitro: "Nitro" 19 | }; 20 | var width = 100; 21 | var height = 100; 22 | var interval = 1000 / (40 /* fps */ ); 23 | var frame = 1; 24 | var color = 0; 25 | var Selection = Type.Dust; 26 | var MouseX = 0; 27 | var MouseY = 0; 28 | var particles = []; 29 | var Grid = new Array(100); 30 | for (var y = 0; y < 100; y++) Grid[y] = new Array(100); 31 | for (var x = 0; x < 100; x++) { 32 | for (var y = 0; y < 100; y++) { 33 | Grid[x][y] = 0; 34 | } 35 | } 36 | var Adjacent = [ 37 | [0, 1], 38 | [-1, 0], 39 | [1, 0], 40 | [0, -1] 41 | ]; 42 | var PortField; 43 | var mouseIsDown = false; 44 | var canvas = document.getElementById('display'); 45 | //############## 46 | var force = 5; 47 | var source = 100; 48 | var sources = []; 49 | sources.push([250, 250]); 50 | var omx, omy; 51 | var mx, my; 52 | var res; 53 | var displaySize = 500; 54 | var fieldRes; 55 | var canvas; 56 | var DisplayFunc; 57 | 58 | function prepareFrame(field) { 59 | if ((omx >= 0 && omx < displaySize && omy >= 0 && omy < displaySize) && mouseIsDown && Selection === Type.Air) { 60 | var dx = mx - omx; 61 | var dy = my - omy; 62 | var length = (Math.sqrt(dx * dx + dy * dy) + 0.5) | 0; 63 | if (length < 1) length = 1; 64 | for (var i = 0; i < length; i++) { 65 | var x = (((omx + dx * (i / length)) / displaySize) * field.width()) | 0 66 | var y = (((omy + dy * (i / length)) / displaySize) * field.height()) | 0; 67 | field.setVelocity(x, y, dx, dy); 68 | field.setDensity(x, y, 50); 69 | } 70 | omx = mx; 71 | omy = my; 72 | } 73 | for (var i = 0; i < sources.length; i++) { 74 | var x = ((sources[i][0] / displaySize) * field.width()) | 0; 75 | var y = ((sources[i][1] / displaySize) * field.height()) | 0; 76 | field.setDensity(x, y, 30); 77 | } 78 | } 79 | 80 | function updatePressure() { 81 | field.update(); 82 | } 83 | // window.onload = function() { 84 | var field = new FluidField(canvas); 85 | // document.getElementById("iterations").value = 10; 86 | res = 100; 87 | field.setUICallback(prepareFrame); 88 | fieldRes = res; 89 | field.setResolution(res, res); 90 | 91 | function getTopLeftOfElement(element) { 92 | var top = 0; 93 | var left = 0; 94 | do { 95 | top += element.offsetTop; 96 | left += element.offsetLeft; 97 | } while (element = element.offsetParent); 98 | return { 99 | left: left, 100 | top: top 101 | }; 102 | } 103 | // canvas.style.cursor = "none"; 104 | function getMousePos(canvas, evt) { 105 | var rect = canvas.getBoundingClientRect(); 106 | return { 107 | x: evt.clientX - rect.left, 108 | y: evt.clientY - rect.top 109 | }; 110 | } 111 | canvas.addEventListener('mousemove', function(evt) { 112 | var mousePos = getMousePos(canvas, evt); 113 | MouseX = Math.min(Math.round(mousePos.x / 5), 99); 114 | MouseY = Math.min(Math.round(mousePos.y / 5), 99); 115 | var o = getTopLeftOfElement(canvas); 116 | mx = evt.clientX - o.left; 117 | my = evt.clientY - o.top; 118 | Cursor(); 119 | }, false); 120 | canvas.onmousedown = function(e) { 121 | e.preventDefault(); 122 | mouseIsDown = true; 123 | var o = getTopLeftOfElement(canvas); 124 | omx = mx = e.clientX - o.left; 125 | omy = my = e.clientY - o.top; 126 | } 127 | canvas.onmouseup = function(e) { 128 | mouseIsDown = false; 129 | } 130 | 131 | function Cursor() { 132 | if (mouseIsDown && (Selection != Type.Air)) { 133 | if (Selection === Type.Erase) { 134 | if (Grid[MouseX][MouseY] != 0) Grid[MouseX][MouseY].remove(); 135 | } else if (Grid[MouseX][MouseY] === 0) new ptc(MouseX, MouseY, Selection); 136 | } 137 | } 138 | window.addEventListener("keydown", onKeyDown, false); 139 | 140 | function KeyEvent(keyCode) { 141 | switch (keyCode) { 142 | case 81: //q 143 | Selection = Type.Dust; 144 | break; 145 | case 87: //w 146 | Selection = Type.Water; 147 | break; 148 | case 82: //r 149 | Selection = Type.Brick; 150 | break; 151 | case 69: //e 152 | Selection = Type.Erase; 153 | break; 154 | case 84: //t 155 | Selection = Type.Tap; 156 | break; 157 | case 65: //a 158 | Selection = Type.Air; 159 | break; 160 | case 70: //f 161 | Selection = Type.Fire; 162 | break; 163 | case 71: //g 164 | Selection = Type.Glitch; 165 | break; 166 | case 88: //g 167 | Selection = Type.Nitro; 168 | break; 169 | case 90: //g 170 | Selection = Type.Powder; 171 | break; 172 | } 173 | } 174 | 175 | function onKeyDown(event) { 176 | var keyCode = event.keyCode; 177 | console.log(keyCode); 178 | KeyEvent(keyCode); 179 | } 180 | 181 | function ptc(x, y, type) { 182 | if (Grid[x][y] != 0) return; 183 | this.x = x; 184 | this.y = y; 185 | Grid[x][y] = this; 186 | this.type = type; 187 | this.SpawnType = type; 188 | this.life = 100; 189 | this.flamable = 0; 190 | switch (type) { 191 | case Type.Dust: //d 192 | h = color += .2; 193 | s = 360; 194 | l = 50; 195 | d = 20; 196 | this.flamable = 1; 197 | break; 198 | case Type.Water: //d 199 | h = 205; 200 | s = 360; 201 | l = 100; 202 | d = 20; 203 | break; 204 | case Type.Brick: //d 205 | h = 12; 206 | s = 77; 207 | l = 30; 208 | d = 0; 209 | break; 210 | case Type.Tap: //d 211 | h = 12; 212 | s = 60; 213 | l = 99; 214 | d = 0; 215 | case Type.Fire: //d 216 | h = 10; 217 | s = 360; 218 | l = 99; 219 | d = 40; 220 | this.life = (Math.random() * 50) | 0; 221 | break; 222 | case Type.Glitch: //d 223 | h = Math.random() * 360 | 0; 224 | s = 100; 225 | l = Math.random() * 100 | 0; 226 | d = 0; 227 | break; 228 | case Type.Powder: //d 229 | h = 50; 230 | s = 10; 231 | l = 20; 232 | d = 35; 233 | this.life = 1; 234 | this.flamable = 6; 235 | break; 236 | case Type.Nitro: //d 237 | h = 130; 238 | s = 100; 239 | l = 50; 240 | d = 15; 241 | this.life = 1; 242 | this.flamable = 10; 243 | break; 244 | } 245 | this.D = d; 246 | this.color = husl.p.toRGB(Math.round(h), s, l); 247 | particles.push(this); 248 | }; 249 | ptc.prototype = { 250 | remove: function() { 251 | var i = particles.indexOf(this); 252 | if (i > -1) particles.splice(i, 1); 253 | Grid[this.x][this.y] = 0; 254 | }, 255 | wind: function(dx, dy) { 256 | dx = Math.round((.7 * dx) + Math.random() * this.D * PortField.getXVelocity(this.x, this.y)); 257 | dy = Math.round((.7 * dy) + Math.random() * this.D * PortField.getYVelocity(this.x, this.y)); 258 | if (dx != 0) dx = (dx / Math.abs(dx)) | 0; 259 | if (dy != 0) dy = (dy / Math.abs(dy)) | 0; 260 | return ([dx, dy]); 261 | }, 262 | tick: function(dir) { 263 | var dy = 0; 264 | var dx = 0; 265 | var delta = [0, 0]; 266 | switch (this.type) { 267 | case Type.Dust: //d 268 | delta = this.wind(0, 1); 269 | break; 270 | case Type.Water: //d 271 | tdx = Math.floor(-1 + Math.random() * 3); //(Math.random() > .5 ? -1 : 1); 272 | delta = this.wind(tdx, 1); 273 | this.color = husl.p.toRGB(205, 360, Math.floor((Math.random() * 40) + 30)); 274 | break; 275 | case Type.Powder: //d 276 | delta = this.wind(0, 1); 277 | break; 278 | case Type.Nitro: //d 279 | tdx = Math.floor(-1 + Math.random() * 3); //(Math.random() > .5 ? -1 : 1); 280 | delta = this.wind(tdx, 1); 281 | break; 282 | case Type.Brick: //d 283 | return; 284 | break; 285 | case Type.Tap: //d 286 | this.Tap(); 287 | return; 288 | break; 289 | case Type.Glitch: //d 290 | this.Glitch(); 291 | return; 292 | break; 293 | case Type.Fire: //d 294 | tdx = Math.floor(-1 + Math.random() * 3); //(Math.random() > .5 ? -1 : 1); 295 | delta = this.wind(0, 0); 296 | this.color = husl.p.toRGB(Math.floor(Math.random() * 20) + 10, 360, Math.floor((Math.random() * 40) + 40)); 297 | this.Burn(); 298 | this.life -= 1; 299 | if (this.life < 0) { 300 | this.remove(); 301 | return 302 | } 303 | break; 304 | } 305 | dx = delta[0]; 306 | dy = delta[1]; 307 | if ((this.x + dx) < 0 || (this.x + dx) > 99 || this.y + dy < 0) { 308 | this.remove(); 309 | return; 310 | } 311 | if ((this.y + dy) > 99) dy = 0; 312 | if (this.Move(0, dy) && (this.type === Type.Water || this.type === Type.Fire) && Math.random() > .2) dx = 0; // water spread 313 | if (!this.Move(dx, 0)) this.Move(-dx, 0); //fix this later 314 | if (this.type === Type.Dust && (this.y + dy < 100) && (this.y + dy > 0) && Grid[this.x][this.y + dy].type === Type.Water) { 315 | temp = Grid[this.x][this.y + dy]; 316 | Grid[this.x][this.y + dy] = this; 317 | Grid[this.x][this.y] = temp; 318 | temp.y -= dy; 319 | this.y += dy; 320 | } 321 | if (this.type === Type.Water && (this.y + dy < 100) && (this.y + dy > 0) && Grid[this.x][this.y + dy].type === Type.Nitro) { 322 | temp = Grid[this.x][this.y + dy]; 323 | Grid[this.x][this.y + dy] = this; 324 | Grid[this.x][this.y] = temp; 325 | temp.y -= dy; 326 | this.y += dy; 327 | } 328 | return; 329 | }, 330 | Move: function(dx, dy) { 331 | if ((this.x + dx) < 0 || ((this.x + dx) > 99)) { 332 | this.remove(); 333 | return (1); 334 | } 335 | if (Grid[this.x + dx][this.y + dy] === 0) { 336 | Grid[this.x + dx][this.y + dy] = this; 337 | Grid[this.x][this.y] = 0; 338 | this.x += dx; 339 | this.y += dy; 340 | return (1); 341 | } 342 | return (0); 343 | }, 344 | Burn: function() { 345 | for (var i = 0; i < 4; i++) { 346 | if (0 > this.x + Adjacent[i][0] || this.x + Adjacent[i][0] > 99 || 0 > this.y + Adjacent[i][1] || this.y + Adjacent[i][1] > 99) continue; 347 | adj = Grid[this.x + Adjacent[i][0]][this.y + Adjacent[i][1]]; 348 | if (adj != 0 && adj.flamable > 0) adj.life -= 3; 349 | if (adj.life < 0) { 350 | PortField.setVelocity(adj.x, adj.y, (Math.random() - .5) * 5 * adj.flamable, (Math.random() - .5) * 5 * adj.flamable); 351 | if (adj.type === Type.Nitro) { 352 | adj.type = Type.Fire; 353 | adj.Burn(); 354 | } 355 | adj.type = Type.Fire; 356 | adj.life = 40; 357 | // adj.Burn(); 358 | } 359 | if (adj != 0 && adj.type === Type.Water) this.life -= 10; 360 | } 361 | PortField.setDensity(this.x, this.y, 60); 362 | PortField.setVelocity(this.x, this.y, 0, -.2); 363 | }, 364 | Glitch: function() { 365 | if (Math.random() < .9) return; 366 | if (this.y < 98 && Grid[this.x][this.y + 1] == 0) new ptc(this.x, this.y + 1, Selection); 367 | else if (this.x < 98 && Grid[this.x + 1][this.y] === 0) new ptc(this.x + 1, this.y, Selection); 368 | else if (this.x > 1 && Grid[this.x - 1][this.y] === 0) new ptc(this.x - 1, this.y, Selection); 369 | else if (this.y > 1 && Grid[this.x][this.y - 1] === 0) new ptc(this.x, this.y - 1, Selection); 370 | }, 371 | Tap: function() { 372 | if (this.SpawnType === Type.Tap) { 373 | for (var i = 0; i < 4; i++) { 374 | if (0 > this.x + Adjacent[i][0] || this.x + Adjacent[i][0] > 99 || 0 > this.y + Adjacent[i][1] || this.y + Adjacent[i][1] > 99) continue; 375 | adj = Grid[this.x + Adjacent[i][0]][this.y + Adjacent[i][1]]; 376 | if (adj != 0 && adj.type != Type.Tap) this.SpawnType = adj.type; 377 | } 378 | } else { 379 | if (Math.random() < .6) return; 380 | if (this.y < 98 && Grid[this.x][this.y + 1] === 0) new ptc(this.x, this.y + 1, this.SpawnType); 381 | else if (this.x < 98 && Grid[this.x + 1][this.y] === 0) new ptc(this.x + 1, this.y, this.SpawnType); 382 | else if (this.x > 1 && Grid[this.x - 1][this.y] === 0) new ptc(this.x - 1, this.y, this.SpawnType); 383 | else if (this.y > 1 && Grid[this.x][this.y - 1] === 0) new ptc(this.x, this.y - 1, this.SpawnType); 384 | } 385 | } 386 | }; 387 | 388 | function init() { 389 | // for (var x = 0; x < 100; x++) { 390 | // for (var y = 0; y < 100; y++) { //spawn n fish and add them to list 391 | // new ptc(x,y*x%100, Type.Tap); // Math.random() * 360, 360, 50) 392 | // } 393 | // } 394 | // // var x = 50; //deprecated 395 | // // var y = 50; 396 | // // for (var i = 0; i < 100; i++) { //spawn n fish and add them to list 397 | // // x = Math.floor(Math.random() * (width - 60)) + 30; 398 | // // y = Math.floor(Math.random() * (height - 60)) + 30; 399 | // // new ptc(x, y, Type.Dust); // Math.random() * 360, 360, 50) 400 | // // } 401 | // // return; 402 | } 403 | 404 | function Dust(equation, canvas) { 405 | // init(); 406 | this.canvas = canvas; 407 | this.scale = 5 //canvas.getAttribute('width') / width; 408 | this.context = canvas.getContext('2d'); 409 | this.imageData = this.context.createImageData(width * this.scale, height * this.scale); 410 | this.then = +Date.now(); 411 | this.paused = false; 412 | field.setDisplayFunction(this.displayVelocity); 413 | this.drawFrame(); 414 | } 415 | Dust.prototype = { 416 | play: function() { 417 | this.paused = false; 418 | this.step(); 419 | }, 420 | pause: function() { 421 | this.paused = true; 422 | }, 423 | step: function() { 424 | if (this.paused) return; 425 | requestAnimFrame(this.step.bind(this)); 426 | var now = +Date.now(); 427 | var delta = now - this.then; 428 | if (delta > interval) { 429 | this.then = now; 430 | this.drawFrame(); 431 | frame++; 432 | } 433 | }, 434 | drawParticle: function(ptc) { 435 | var x = ptc.x; 436 | var y = ptc.y; 437 | var color = ptc.color; 438 | var R = Math.floor(color[0] * 255); // (color & 0xff0000) >>> 16; 439 | var G = Math.floor(color[1] * 255); //(color & 0x00ff00) >>> 8; 440 | var B = Math.floor(color[2] * 255); // (color & 0x0000ff) >>> 0; 441 | var Size = 0; 442 | // if(ptc.type==Type.Water && x>0 && x<98) 443 | // Size = Math.floor(Math.random()*2); 444 | for (var sx = -Size; sx < 5 + Size; sx++) { 445 | for (var sy = -Size; sy < 5 + Size; sy++) { 446 | var i = (((y * this.scale + (sy)) * width * this.scale) + (x * this.scale + (sx))) * 4; 447 | this.imageData.data[i] = R; // % 255; 448 | this.imageData.data[i + 1] = G; // % 255; 449 | this.imageData.data[i + 2] = B; // % 255; 450 | this.imageData.data[i + 3] = 255; 451 | } 452 | } 453 | }, 454 | displayVelocity: function(field) { 455 | PortField = field; 456 | context = canvas.getContext('2d'); 457 | context.save(); 458 | context.lineWidth = .1; 459 | scale = 5 460 | context.strokeStyle = "rgb(255,255,255)"; 461 | var vectorScale = 8; 462 | context.beginPath(); 463 | // console.log(field.getXVelocity(50, 50) + field.getYVelocity(50, 50)); 464 | for (var x = 0; x < field.width(); x++) { 465 | for (var y = 0; y < field.height(); y++) { 466 | if (Math.abs(field.getXVelocity(x, y) * field.getYVelocity(x, y)) > .003) { 467 | context.moveTo(x * scale + 0.5 * scale, y * scale + 0.5 * scale); 468 | context.lineTo((x + 0.5 + vectorScale * field.getXVelocity(x, y)) * scale, (y + 0.5 + vectorScale * field.getYVelocity(x, y)) * scale); 469 | } 470 | } 471 | } 472 | context.stroke(); 473 | context.restore(); 474 | }, 475 | drawFrame: function() { 476 | Cursor(); 477 | this.context.fillRect(0, 0, 500, 500); 478 | this.context.fillStyle = husl.p.toHex(40, 60, 2); ////t'#0010'+offset.toString(16); 479 | this.context.fill(); 480 | updatePressure(); 481 | this.imageData = this.context.getImageData(0, 0, 500, 500); 482 | for (var i = 0; i < particles.length; i++) { 483 | particles[i].tick(); 484 | if (particles[i]) this.drawParticle(particles[i]); 485 | } 486 | this.context.putImageData(this.imageData, 0, 0); 487 | document.getElementById('SelectionDisplay').innerHTML = 'Selection: ' + Selection + ' ------------- Particles: ' + particles.length; 488 | } 489 | }; 490 | var requestAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { 491 | window.setTimeout(callback, 0); 492 | }; 493 | // return Dust; 494 | // })(); 495 | var dust = new Dust('0', document.getElementById('display')); 496 | dust.play(); 497 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dust 6 | 7 | 9 | 10 | 11 |

Dust

12 | 13 |
no script has run
14 | 15 | 16 | 17 | 18 |

19 | Q:Dust W: Water E: Erase R: Brick T: Tap
20 | A: Air F: Fire G: Glitch
21 | Z: Powder X: Nitro
22 |

23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dust", 3 | "version": "1.0.0", 4 | "description": "\"Imagine the cool phenomenon when the wind blows the falling leaves. This game simulates the phenomenon with powder (dots)!\" -DAN-BALL", 5 | "main": "dust.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/MaxBittker/dust.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/MaxBittker/dust/issues" 17 | }, 18 | "homepage": "https://github.com/MaxBittker/dust#readme", 19 | "dependencies": { 20 | "husl": "^6.0.1", 21 | "underscore": "^1.8.3" 22 | }, 23 | "devDependencies": { 24 | "jshint": "^2.8.0", 25 | "jshint-loader": "^0.8.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pressure.js: -------------------------------------------------------------------------------- 1 | // Based on http://www.dgp.toronto.edu/people/stam/reality/Research/pdf/GDC03.pdf 2 | /** 3 | * Copyright (c) 2009 Oliver Hunt 4 | * 5 | * Permission is hereby granted, free of charge, to any person 6 | * obtaining a copy of this software and associated documentation 7 | * files (the "Software"), to deal in the Software without 8 | * restriction, including without limitation the rights to use, 9 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following 12 | * conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | * OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | module.exports = function FluidField(canvas) { 28 | function addFields(x, s, dt) 29 | { 30 | for (var i=0; i Wp5) 163 | x = Wp5; 164 | var i0 = x | 0; 165 | var i1 = i0 + 1; 166 | if (y < 0.5) 167 | y = 0.5; 168 | else if (y > Hp5) 169 | y = Hp5; 170 | var j0 = y | 0; 171 | var j1 = j0 + 1; 172 | var s1 = x - i0; 173 | var s0 = 1 - s1; 174 | var t1 = y - j0; 175 | var t0 = 1 - t1; 176 | var row1 = j0 * rowSize; 177 | var row2 = j1 * rowSize; 178 | d[pos] = s0 * (t0 * d0[i0 + row1] + t1 * d0[i0 + row2]) + s1 * (t0 * d0[i1 + row1] + t1 * d0[i1 + row2]); 179 | } 180 | } 181 | set_bnd(b, d); 182 | } 183 | 184 | function project(u, v, p, div) 185 | { 186 | var h = -0.5 / Math.sqrt(width * height); 187 | for (var j = 1 ; j <= height; j++ ) { 188 | var row = j * rowSize; 189 | var previousRow = (j - 1) * rowSize; 190 | var prevValue = row - 1; 191 | var currentRow = row; 192 | var nextValue = row + 1; 193 | var nextRow = (j + 1) * rowSize; 194 | for (var i = 1; i <= width; i++ ) { 195 | div[++currentRow] = h * (u[++nextValue] - u[++prevValue] + v[++nextRow] - v[++previousRow]); 196 | p[currentRow] = 0; 197 | } 198 | } 199 | set_bnd(0, div); 200 | set_bnd(0, p); 201 | 202 | lin_solve(0, p, div, 1, 4 ); 203 | var wScale = 0.5 * width; 204 | var hScale = 0.5 * height; 205 | for (var j = 1; j<= height; j++ ) { 206 | var prevPos = j * rowSize - 1; 207 | var currentPos = j * rowSize; 208 | var nextPos = j * rowSize + 1; 209 | var prevRow = (j - 1) * rowSize; 210 | var currentRow = j * rowSize; 211 | var nextRow = (j + 1) * rowSize; 212 | 213 | for (var i = 1; i<= width; i++) { 214 | u[++currentPos] -= wScale * (p[++nextPos] - p[++prevPos]); 215 | v[currentPos] -= hScale * (p[++nextRow] - p[++prevRow]); 216 | } 217 | } 218 | set_bnd(1, u); 219 | set_bnd(2, v); 220 | } 221 | 222 | function dens_step(x, x0, u, v, dt) 223 | { 224 | addFields(x, x0, dt); 225 | diffuse(0, x0, x, dt ); 226 | advect(0, x, x0, u, v, dt ); 227 | } 228 | 229 | function vel_step(u, v, u0, v0, dt) 230 | { 231 | addFields(u, u0, dt ); 232 | addFields(v, v0, dt ); 233 | var temp = u0; u0 = u; u = temp; 234 | var temp = v0; v0 = v; v = temp; 235 | diffuse2(u,u0,v,v0, dt); 236 | project(u, v, u0, v0); 237 | var temp = u0; u0 = u; u = temp; 238 | var temp = v0; v0 = v; v = temp; 239 | advect(1, u, u0, u0, v0, dt); 240 | advect(2, v, v0, u0, v0, dt); 241 | project(u, v, u0, v0 ); 242 | } 243 | var uiCallback = function(d,u,v) {}; 244 | 245 | function Field(dens, u, v) { 246 | // Just exposing the fields here rather than using accessors is a measurable win during display (maybe 5%) 247 | // but makes the code ugly. 248 | this.setDensity = function(x, y, d) { 249 | dens[(x + 1) + (y + 1) * rowSize] = d; 250 | } 251 | this.getDensity = function(x, y) { 252 | return dens[(x + 1) + (y + 1) * rowSize]; 253 | } 254 | this.setVelocity = function(x, y, xv, yv) { 255 | u[(x + 1) + (y + 1) * rowSize] = xv; 256 | v[(x + 1) + (y + 1) * rowSize] = yv; 257 | } 258 | this.getXVelocity = function(x, y) { 259 | return u[(x + 1) + (y + 1) * rowSize]; 260 | } 261 | this.getYVelocity = function(x, y) { 262 | return v[(x + 1) + (y + 1) * rowSize]; 263 | } 264 | this.width = function() { return width; } 265 | this.height = function() { return height; } 266 | } 267 | function queryUI(d, u, v) 268 | { 269 | for (var i = 0; i < size; i++) 270 | u[i] = v[i] = d[i] = 0.0; 271 | uiCallback(new Field(d, u, v)); 272 | } 273 | 274 | this.update = function () { 275 | queryUI(dens_prev, u_prev, v_prev); 276 | vel_step(u, v, u_prev, v_prev, dt); 277 | dens_step(dens, dens_prev, u, v, dt); 278 | displayFunc(new Field(dens, u, v)); 279 | } 280 | this.setDisplayFunction = function(func) { 281 | displayFunc = func; 282 | } 283 | 284 | this.iterations = function() { return iterations; } 285 | this.setIterations = function(iters) { 286 | if (iters > 0 && iters <= 100) 287 | iterations = iters; 288 | } 289 | this.setUICallback = function(callback) { 290 | uiCallback = callback; 291 | } 292 | var iterations = 15; 293 | var visc = 0.8; 294 | var dt = 0.1; 295 | var dens; 296 | var dens_prev; 297 | var u; 298 | var u_prev; 299 | var v; 300 | var v_prev; 301 | var width; 302 | var height; 303 | var rowSize; 304 | var size; 305 | var displayFunc; 306 | function reset() 307 | { 308 | rowSize = width + 2; 309 | size = (width+2)*(height+2); 310 | dens = new Array(size); 311 | dens_prev = new Array(size); 312 | u = new Array(size); 313 | u_prev = new Array(size); 314 | v = new Array(size); 315 | v_prev = new Array(size); 316 | for (var i = 0; i < size; i++) 317 | dens_prev[i] = u_prev[i] = v_prev[i] = dens[i] = u[i] = v[i] = 0; 318 | } 319 | this.reset = reset; 320 | this.setResolution = function (hRes, wRes) 321 | { 322 | var res = wRes * hRes; 323 | if (res > 0 && res < 1000000 && (wRes != width || hRes != height)) { 324 | width = wRes; 325 | height = hRes; 326 | reset(); 327 | return true; 328 | } 329 | return false; 330 | } 331 | this.setResolution(100, 100); 332 | } 333 | 334 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: "./dust.js", 3 | output: { 4 | path: __dirname, 5 | filename: "bundle.js" 6 | }, 7 | module: { 8 | preLoaders: [{ 9 | test: /\.js?$/, 10 | loaders: ['jshint'], 11 | // define an include so we check just the files we need 12 | include: "./dust.js" 13 | }], 14 | loaders: [ 15 | // { test: /\.css$/, loader: "style!css" } 16 | ] 17 | } 18 | }; 19 | --------------------------------------------------------------------------------