├── shaders ├── basic.vs ├── displayscalar.fs ├── displayvector.fs ├── boundary.fs ├── splat.fs ├── vorticity.fs ├── jacobivector.fs ├── jacobiscalar.fs ├── divergence.fs ├── gradient.fs ├── advect.fs └── vorticityforce.fs ├── css └── main.css ├── LICENSE ├── README.md ├── js ├── f2d │ ├── slabop │ │ ├── slabopbase.js │ │ ├── vorticity.js │ │ ├── divergence.js │ │ ├── gradient.js │ │ ├── splat.js │ │ ├── advect.js │ │ ├── jacobi.js │ │ ├── vorticityconfinement.js │ │ └── boundary.js │ ├── slab.js │ ├── display.js │ ├── fileloader.js │ ├── mouse.js │ ├── main.js │ └── solver.js └── vendor │ ├── stats.min.js │ └── dat.gui.min.js └── index.html /shaders/basic.vs: -------------------------------------------------------------------------------- 1 | varying vec2 texCoord; 2 | 3 | void main() 4 | { 5 | texCoord = uv; 6 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 7 | } 8 | -------------------------------------------------------------------------------- /shaders/displayscalar.fs: -------------------------------------------------------------------------------- 1 | uniform sampler2D read; 2 | 3 | uniform vec3 bias; 4 | uniform vec3 scale; 5 | 6 | varying vec2 texCoord; 7 | 8 | void main() 9 | { 10 | gl_FragColor = vec4(bias + scale * texture2D(read, texCoord).xxx, 1.0); 11 | } 12 | -------------------------------------------------------------------------------- /shaders/displayvector.fs: -------------------------------------------------------------------------------- 1 | uniform sampler2D read; 2 | 3 | uniform vec3 bias; 4 | uniform vec3 scale; 5 | 6 | varying vec2 texCoord; 7 | 8 | void main() 9 | { 10 | gl_FragColor = vec4(bias + scale * texture2D(read, texCoord).xyz, 1.0); 11 | } 12 | -------------------------------------------------------------------------------- /shaders/boundary.fs: -------------------------------------------------------------------------------- 1 | uniform sampler2D read; 2 | 3 | uniform vec2 gridSize; 4 | uniform vec2 gridOffset; 5 | uniform float scale; 6 | 7 | void main() 8 | { 9 | vec2 uv = (gl_FragCoord.xy + gridOffset.xy) / gridSize.xy; 10 | gl_FragColor = vec4(scale * texture2D(read, uv).xyz, 1.0); 11 | } 12 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | overflow: hidden; 5 | color: #ddd; 6 | background: #fff; 7 | background-color: #ddd; 8 | } 9 | 10 | canvas { 11 | width: 100%; 12 | height: 100%; 13 | } 14 | 15 | #info { 16 | font-size: 0.8em; 17 | text-align: center; 18 | position: absolute; 19 | top: 0px; 20 | width: 100%; 21 | padding: 5px; 22 | } 23 | 24 | a { 25 | color: #888; 26 | } 27 | -------------------------------------------------------------------------------- /shaders/splat.fs: -------------------------------------------------------------------------------- 1 | uniform sampler2D read; 2 | 3 | uniform vec2 gridSize; 4 | 5 | uniform vec3 color; 6 | uniform vec2 point; 7 | uniform float radius; 8 | 9 | float gauss(vec2 p, float r) 10 | { 11 | return exp(-dot(p, p) / r); 12 | } 13 | 14 | void main() 15 | { 16 | vec2 uv = gl_FragCoord.xy / gridSize.xy; 17 | vec3 base = texture2D(read, uv).xyz; 18 | vec2 coord = point.xy - gl_FragCoord.xy; 19 | vec3 splat = color * gauss(coord, gridSize.x * radius); 20 | gl_FragColor = vec4(base + splat, 1.0); 21 | } 22 | -------------------------------------------------------------------------------- /shaders/vorticity.fs: -------------------------------------------------------------------------------- 1 | uniform sampler2D velocity; 2 | 3 | uniform vec2 gridSize; 4 | uniform float gridScale; 5 | 6 | void main() 7 | { 8 | vec2 uv = gl_FragCoord.xy / gridSize.xy; 9 | 10 | vec2 xOffset = vec2(1.0 / gridSize.x, 0.0); 11 | vec2 yOffset = vec2(0.0, 1.0 / gridSize.y); 12 | 13 | float vl = texture2D(velocity, uv - xOffset).y; 14 | float vr = texture2D(velocity, uv + xOffset).y; 15 | float vb = texture2D(velocity, uv - yOffset).x; 16 | float vt = texture2D(velocity, uv + yOffset).x; 17 | 18 | float scale = 0.5 / gridScale; 19 | gl_FragColor = vec4(scale * (vr - vl - vt + vb), 0.0, 0.0, 1.0); 20 | } 21 | -------------------------------------------------------------------------------- /shaders/jacobivector.fs: -------------------------------------------------------------------------------- 1 | uniform sampler2D x; 2 | uniform sampler2D b; 3 | 4 | uniform vec2 gridSize; 5 | 6 | uniform float alpha; 7 | uniform float beta; 8 | 9 | void main() 10 | { 11 | vec2 uv = gl_FragCoord.xy / gridSize.xy; 12 | 13 | vec2 xOffset = vec2(1.0 / gridSize.x, 0.0); 14 | vec2 yOffset = vec2(0.0, 1.0 / gridSize.y); 15 | 16 | vec2 xl = texture2D(x, uv - xOffset).xy; 17 | vec2 xr = texture2D(x, uv + xOffset).xy; 18 | vec2 xb = texture2D(x, uv - yOffset).xy; 19 | vec2 xt = texture2D(x, uv + yOffset).xy; 20 | 21 | vec2 bc = texture2D(b, uv).xy; 22 | 23 | gl_FragColor = vec4((xl + xr + xb + xt + alpha * bc) / beta, 0.0, 1.0); 24 | } 25 | -------------------------------------------------------------------------------- /shaders/jacobiscalar.fs: -------------------------------------------------------------------------------- 1 | uniform sampler2D x; 2 | uniform sampler2D b; 3 | 4 | uniform vec2 gridSize; 5 | 6 | uniform float alpha; 7 | uniform float beta; 8 | 9 | void main() 10 | { 11 | vec2 uv = gl_FragCoord.xy / gridSize.xy; 12 | 13 | vec2 xOffset = vec2(1.0 / gridSize.x, 0.0); 14 | vec2 yOffset = vec2(0.0, 1.0 / gridSize.y); 15 | 16 | float xl = texture2D(x, uv - xOffset).x; 17 | float xr = texture2D(x, uv + xOffset).x; 18 | float xb = texture2D(x, uv - yOffset).x; 19 | float xt = texture2D(x, uv + yOffset).x; 20 | 21 | float bc = texture2D(b, uv).x; 22 | 23 | gl_FragColor = vec4((xl + xr + xb + xt + alpha * bc) / beta, 0.0, 0.0, 1.0); 24 | } 25 | -------------------------------------------------------------------------------- /shaders/divergence.fs: -------------------------------------------------------------------------------- 1 | uniform sampler2D velocity; 2 | 3 | uniform vec2 gridSize; 4 | uniform float gridScale; 5 | 6 | void main() 7 | { 8 | vec2 uv = gl_FragCoord.xy / gridSize.xy; 9 | 10 | vec2 xOffset = vec2(1.0 / gridSize.x, 0.0); 11 | vec2 yOffset = vec2(0.0, 1.0 / gridSize.y); 12 | 13 | float vl = texture2D(velocity, uv - xOffset).x; 14 | float vr = texture2D(velocity, uv + xOffset).x; 15 | float vb = texture2D(velocity, uv - yOffset).y; 16 | float vt = texture2D(velocity, uv + yOffset).y; 17 | 18 | float scale = 0.5 / gridScale; 19 | float divergence = scale * (vr - vl + vt - vb); 20 | 21 | gl_FragColor = vec4(divergence, 0.0, 0.0, 1.0); 22 | } 23 | -------------------------------------------------------------------------------- /shaders/gradient.fs: -------------------------------------------------------------------------------- 1 | uniform sampler2D p; 2 | uniform sampler2D w; 3 | 4 | uniform vec2 gridSize; 5 | uniform float gridScale; 6 | 7 | void main() 8 | { 9 | vec2 uv = gl_FragCoord.xy / gridSize.xy; 10 | 11 | vec2 xOffset = vec2(1.0 / gridSize.x, 0.0); 12 | vec2 yOffset = vec2(0.0, 1.0 / gridSize.y); 13 | 14 | float pl = texture2D(p, uv - xOffset).x; 15 | float pr = texture2D(p, uv + xOffset).x; 16 | float pb = texture2D(p, uv - yOffset).x; 17 | float pt = texture2D(p, uv + yOffset).x; 18 | 19 | float scale = 0.5 / gridScale; 20 | vec2 gradient = scale * vec2(pr - pl, pt - pb); 21 | 22 | vec2 wc = texture2D(w, uv).xy; 23 | 24 | gl_FragColor = vec4(wc - gradient, 0.0, 1.0); 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | “fluids-2d” fluid solver in WebGL 2 | Copyright (C) 2015 Mattias Harrysson 3 | 4 | This program is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License 6 | as published by the Free Software Foundation; either version 2 7 | of the License, or (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Fluids-2D 2 | ========= 3 | Real-time fluid dynamics running on the GPU with the help of WebGL and [Three.js](https://github.com/mrdoob/three.js). It is simulated with the Navier-Stokes Equations with the simplified assumption of incompressible, homogeneous fluid. 4 | 5 | Demo 6 | ---- 7 | Play around with fluids [here](http://www.csc.kth.se/~mathar/fluids-2d/). Note that you will most likely have performance issues if you are running on a integrated graphics card. 8 | 9 | References 10 | ---------- 11 | 1. Jos Stam. Stable Fluids. In Proceedings of SIGGRAPH. 1999. 12 | 2. Jos Stam. Real-Time Fluid Dynamics for Games. Proceedings of the Game Developer Conference. 2003. 13 | 3. Mark Harris. Fast Fluid Dynamics Simulation on the GPU. In GPU Gems: Programming Techniques, Tips, and Tricks for Real-Time Graphics (Chapter 38). 2004. 14 | -------------------------------------------------------------------------------- /js/f2d/slabop/slabopbase.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | (function(F2D) { 4 | "use strict"; 5 | 6 | F2D.SlabopBase = function(fs, uniforms, grid) { 7 | var geometry = new THREE.PlaneBufferGeometry(2 * (grid.size.x - 2) / grid.size.x, 2 * (grid.size.y - 2) / grid.size.y); 8 | var material = new THREE.ShaderMaterial({ 9 | uniforms: uniforms, 10 | fragmentShader: fs, 11 | depthWrite: false, 12 | depthTest: false, 13 | blending: THREE.NoBlending 14 | }); 15 | var quad = new THREE.Mesh(geometry, material); 16 | 17 | this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); 18 | this.scene = new THREE.Scene(); 19 | this.scene.add(quad); 20 | }; 21 | 22 | F2D.SlabopBase.prototype = { 23 | constructor: F2D.SlabopBase 24 | }; 25 | 26 | }(F2D)); 27 | -------------------------------------------------------------------------------- /shaders/advect.fs: -------------------------------------------------------------------------------- 1 | uniform sampler2D velocity; 2 | uniform sampler2D advected; 3 | 4 | uniform vec2 gridSize; 5 | uniform float gridScale; 6 | 7 | uniform float timestep; 8 | uniform float dissipation; 9 | 10 | vec2 bilerp(sampler2D d, vec2 p) 11 | { 12 | vec4 ij; // i0, j0, i1, j1 13 | ij.xy = floor(p - 0.5) + 0.5; 14 | ij.zw = ij.xy + 1.0; 15 | 16 | vec4 uv = ij / gridSize.xyxy; 17 | vec2 d11 = texture2D(d, uv.xy).xy; 18 | vec2 d21 = texture2D(d, uv.zy).xy; 19 | vec2 d12 = texture2D(d, uv.xw).xy; 20 | vec2 d22 = texture2D(d, uv.zw).xy; 21 | 22 | vec2 a = p - ij.xy; 23 | 24 | return mix(mix(d11, d21, a.x), mix(d12, d22, a.x), a.y); 25 | } 26 | 27 | void main() 28 | { 29 | vec2 uv = gl_FragCoord.xy / gridSize.xy; 30 | float scale = 1.0 / gridScale; 31 | 32 | // trace point back in time 33 | vec2 p = gl_FragCoord.xy - timestep * scale * texture2D(velocity, uv).xy; 34 | 35 | gl_FragColor = vec4(dissipation * bilerp(advected, p), 0.0, 1.0); 36 | } 37 | -------------------------------------------------------------------------------- /shaders/vorticityforce.fs: -------------------------------------------------------------------------------- 1 | uniform sampler2D velocity; 2 | uniform sampler2D vorticity; 3 | 4 | uniform vec2 gridSize; 5 | uniform float gridScale; 6 | 7 | uniform float timestep; 8 | uniform float epsilon; 9 | uniform vec2 curl; 10 | 11 | void main() 12 | { 13 | vec2 uv = gl_FragCoord.xy / gridSize.xy; 14 | 15 | vec2 xOffset = vec2(1.0 / gridSize.x, 0.0); 16 | vec2 yOffset = vec2(0.0, 1.0 / gridSize.y); 17 | 18 | float vl = texture2D(vorticity, uv - xOffset).x; 19 | float vr = texture2D(vorticity, uv + xOffset).x; 20 | float vb = texture2D(vorticity, uv - yOffset).x; 21 | float vt = texture2D(vorticity, uv + yOffset).x; 22 | float vc = texture2D(vorticity, uv).x; 23 | 24 | float scale = 0.5 / gridScale; 25 | vec2 force = scale * vec2(abs(vt) - abs(vb), abs(vr) - abs(vl)); 26 | float lengthSquared = max(epsilon, dot(force, force)); 27 | force *= inversesqrt(lengthSquared) * curl * vc; 28 | force.y *= -1.0; 29 | 30 | vec2 velc = texture2D(velocity, uv).xy; 31 | gl_FragColor = vec4(velc + (timestep * force), 0.0, 1.0); 32 | } 33 | -------------------------------------------------------------------------------- /js/f2d/slabop/vorticity.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | (function(F2D) { 4 | "use strict"; 5 | 6 | F2D.Vorticity = function(fs, grid) { 7 | this.grid = grid; 8 | 9 | this.uniforms = { 10 | velocity: { 11 | type: "t" 12 | }, 13 | gridSize: { 14 | type: "v2" 15 | }, 16 | gridScale: { 17 | type: "f" 18 | }, 19 | }; 20 | 21 | F2D.SlabopBase.call(this, fs, this.uniforms, grid); 22 | }; 23 | 24 | F2D.Vorticity.prototype = Object.create(F2D.SlabopBase.prototype); 25 | F2D.Vorticity.prototype.constructor = F2D.Vorticity; 26 | 27 | F2D.Vorticity.prototype.compute = function(renderer, velocity, output) { 28 | this.uniforms.velocity.value = velocity.read; 29 | this.uniforms.gridSize.value = this.grid.size; 30 | this.uniforms.gridScale.value = this.grid.scale; 31 | 32 | renderer.render(this.scene, this.camera, output.write, false); 33 | output.swap(); 34 | }; 35 | 36 | }(F2D)); 37 | -------------------------------------------------------------------------------- /js/f2d/slab.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | (function(F2D) { 4 | "use strict"; 5 | 6 | F2D.Slab = function(width, height, options) { 7 | this.read = new THREE.WebGLRenderTarget(width, height, options); 8 | this.write = this.read.clone(); 9 | }; 10 | 11 | F2D.Slab.prototype = { 12 | constructor: F2D.Slab, 13 | 14 | swap: function() { 15 | var tmp = this.read; 16 | this.read = this.write; 17 | this.write = tmp; 18 | } 19 | }; 20 | 21 | var options = { 22 | wrapS: THREE.ClampToEdgeWrapping, 23 | wrapT: THREE.ClampToEdgeWrapping, 24 | magFilter: THREE.NearestFilter, 25 | minFilter: THREE.NearestFilter, 26 | format: THREE.RGBAFormat, 27 | type: THREE.FloatType, 28 | depthBuffer: false, 29 | stencilBuffer: false, 30 | generateMipmaps: false, 31 | shareDepthFrom: null 32 | }; 33 | 34 | F2D.Slab.make = function(width, height) { 35 | return new F2D.Slab(width, height, options); 36 | }; 37 | 38 | }(F2D)); 39 | -------------------------------------------------------------------------------- /js/f2d/slabop/divergence.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | (function(F2D) { 4 | "use strict"; 5 | 6 | F2D.Divergence = function(fs, grid) { 7 | this.grid = grid; 8 | 9 | this.uniforms = { 10 | velocity: { 11 | type: "t" 12 | }, 13 | gridSize: { 14 | type: "v2" 15 | }, 16 | gridScale: { 17 | type: "f" 18 | }, 19 | }; 20 | 21 | F2D.SlabopBase.call(this, fs, this.uniforms, grid); 22 | }; 23 | 24 | F2D.Divergence.prototype = Object.create(F2D.SlabopBase.prototype); 25 | F2D.Divergence.prototype.constructor = F2D.Divergence; 26 | 27 | F2D.Divergence.prototype.compute = function(renderer, velocity, divergence) { 28 | this.uniforms.velocity.value = velocity.read; 29 | this.uniforms.gridSize.value = this.grid.size; 30 | this.uniforms.gridScale.value = this.grid.scale; 31 | 32 | renderer.render(this.scene, this.camera, divergence.write, false); 33 | divergence.swap(); 34 | }; 35 | 36 | }(F2D)); 37 | -------------------------------------------------------------------------------- /js/f2d/slabop/gradient.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | (function(F2D) { 4 | "use strict"; 5 | 6 | F2D.Gradient = function(fs, grid) { 7 | this.grid = grid; 8 | 9 | this.uniforms = { 10 | p: { 11 | type: "t" 12 | }, 13 | w: { 14 | type: "t" 15 | }, 16 | gridSize: { 17 | type: "v2" 18 | }, 19 | gridScale: { 20 | type: "f" 21 | }, 22 | }; 23 | 24 | F2D.SlabopBase.call(this, fs, this.uniforms, grid); 25 | }; 26 | 27 | F2D.Gradient.prototype = Object.create(F2D.SlabopBase.prototype); 28 | F2D.Gradient.prototype.constructor = F2D.Gradient; 29 | 30 | F2D.Gradient.prototype.compute = function(renderer, p, w, output) { 31 | this.uniforms.p.value = p.read; 32 | this.uniforms.w.value = w.read; 33 | this.uniforms.gridSize.value = this.grid.size; 34 | this.uniforms.gridScale.value = this.grid.scale; 35 | 36 | renderer.render(this.scene, this.camera, output.write, false); 37 | output.swap(); 38 | }; 39 | 40 | }(F2D)); 41 | -------------------------------------------------------------------------------- /js/f2d/slabop/splat.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | (function(F2D) { 4 | "use strict"; 5 | 6 | F2D.Splat = function(fs, grid, radius) { 7 | this.grid = grid; 8 | this.radius = radius === undefined ? 0.01 : radius; 9 | 10 | this.uniforms = { 11 | read: { 12 | type: "t" 13 | }, 14 | gridSize: { 15 | type: "v2" 16 | }, 17 | color: { 18 | type: "v3" 19 | }, 20 | point: { 21 | type: "v2" 22 | }, 23 | radius: { 24 | type: "f" 25 | } 26 | }; 27 | 28 | F2D.SlabopBase.call(this, fs, this.uniforms, grid); 29 | }; 30 | 31 | F2D.Splat.prototype = Object.create(F2D.SlabopBase.prototype); 32 | F2D.Splat.prototype.constructor = F2D.Splat; 33 | 34 | F2D.Splat.prototype.compute = function(renderer, input, color, point, output) { 35 | this.uniforms.gridSize.value = this.grid.size; 36 | this.uniforms.read.value = input.read; 37 | this.uniforms.color.value = color; 38 | this.uniforms.point.value = point; 39 | this.uniforms.radius.value = this.radius; 40 | 41 | renderer.render(this.scene, this.camera, output.write, false); 42 | output.swap(); 43 | }; 44 | 45 | }(F2D)); 46 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fluids-2d 6 | 7 | 8 | 9 |
10 | https://github.com/mharrys/fluids-2d
11 | Mouse1: Add force Mouse2: Add source
12 | Try also Mouse1+Mouse2: at the same time 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /js/f2d/slabop/advect.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | (function(F2D) { 4 | "use strict"; 5 | 6 | F2D.Advect = function(fs, grid, time, dissipation) { 7 | this.grid = grid; 8 | this.time = time; 9 | this.dissipation = dissipation === undefined ? 0.998 : dissipation; 10 | 11 | this.uniforms = { 12 | velocity: { 13 | type: "t" 14 | }, 15 | advected: { 16 | type: "t" 17 | }, 18 | gridSize: { 19 | type: "v2" 20 | }, 21 | gridScale: { 22 | type: "f" 23 | }, 24 | timestep: { 25 | type: "f" 26 | }, 27 | dissipation: { 28 | type: "f" 29 | } 30 | }; 31 | 32 | F2D.SlabopBase.call(this, fs, this.uniforms, grid); 33 | }; 34 | 35 | F2D.Advect.prototype = Object.create(F2D.SlabopBase.prototype); 36 | F2D.Advect.prototype.constructor = F2D.Advect; 37 | 38 | F2D.Advect.prototype.compute = function(renderer, velocity, advected, output) { 39 | this.uniforms.velocity.value = velocity.read; 40 | this.uniforms.advected.value = advected.read; 41 | this.uniforms.gridSize.value = this.grid.size; 42 | this.uniforms.gridScale.value = this.grid.scale; 43 | this.uniforms.timestep.value = this.time.step; 44 | this.uniforms.dissipation.value = this.dissipation; 45 | 46 | renderer.render(this.scene, this.camera, output.write, false); 47 | output.swap(); 48 | }; 49 | 50 | }(F2D)); 51 | -------------------------------------------------------------------------------- /js/f2d/slabop/jacobi.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | (function(F2D) { 4 | "use strict"; 5 | 6 | F2D.Jacobi = function(fs, grid, iterations, alpha, beta) { 7 | this.grid = grid; 8 | this.iterations = iterations === undefined ? 50 : iterations; 9 | this.alpha = alpha === undefined ? -1 : alpha; 10 | this.beta = beta === undefined ? 4 : beta; 11 | 12 | this.uniforms = { 13 | x: { 14 | type: "t" 15 | }, 16 | b: { 17 | type: "t" 18 | }, 19 | gridSize: { 20 | type: "v2" 21 | }, 22 | alpha: { 23 | type: "f" 24 | }, 25 | beta: { 26 | type: "f" 27 | }, 28 | }; 29 | 30 | F2D.SlabopBase.call(this, fs, this.uniforms, grid); 31 | }; 32 | 33 | F2D.Jacobi.prototype = Object.create(F2D.SlabopBase.prototype); 34 | F2D.Jacobi.prototype.constructor = F2D.Jacobi; 35 | 36 | F2D.Jacobi.prototype.compute = function(renderer, x, b, output, boundary, scale) { 37 | for (var i = 0; i < this.iterations; i++) { 38 | this.step(renderer, x, b, output); 39 | boundary.compute(renderer, output, scale, output); 40 | } 41 | }; 42 | 43 | F2D.Jacobi.prototype.step = function(renderer, x, b, output) { 44 | this.uniforms.x.value = x.read; 45 | this.uniforms.b.value = b.read; 46 | this.uniforms.gridSize.value = this.grid.size; 47 | this.uniforms.alpha.value = this.alpha; 48 | this.uniforms.beta.value = this.beta; 49 | 50 | renderer.render(this.scene, this.camera, output.write, false); 51 | output.swap(); 52 | }; 53 | 54 | }(F2D)); 55 | -------------------------------------------------------------------------------- /js/f2d/display.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | (function(F2D) { 4 | "use strict"; 5 | 6 | F2D.Display = function(vs, fs, bias, scale) { 7 | this.bias = bias === undefined ? new THREE.Vector3(0, 0, 0) : bias; 8 | this.scale = scale === undefined ? new THREE.Vector3(1, 1, 1) : scale; 9 | 10 | this.uniforms = { 11 | read: { 12 | type: "t" 13 | }, 14 | bias: { 15 | type: "v3" 16 | }, 17 | scale: { 18 | type: "v3" 19 | } 20 | }; 21 | this.material = new THREE.ShaderMaterial({ 22 | uniforms: this.uniforms, 23 | vertexShader: vs, 24 | fragmentShader: fs, 25 | depthWrite: false, 26 | depthTest: false, 27 | blending: THREE.NoBlending 28 | }); 29 | var quad = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), this.material); 30 | 31 | this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); 32 | this.scene = new THREE.Scene(); 33 | this.scene.add(quad); 34 | }; 35 | 36 | F2D.Display.prototype = { 37 | constructor: F2D.Display, 38 | 39 | // set bias and scale for including range of negative values 40 | scaleNegative: function() { 41 | var v = 0.5; 42 | this.bias.set(v, v, v); 43 | this.scale.set(v, v, v); 44 | }, 45 | 46 | render: function(renderer, read) { 47 | this.uniforms.read.value = read; 48 | this.uniforms.bias.value = this.bias; 49 | this.uniforms.scale.value = this.scale; 50 | renderer.render(this.scene, this.camera); 51 | } 52 | }; 53 | 54 | }(F2D)); 55 | -------------------------------------------------------------------------------- /js/vendor/stats.min.js: -------------------------------------------------------------------------------- 1 | // stats.js - http://github.com/mrdoob/stats.js 2 | var Stats=function(){function f(a,e,b){a=document.createElement(a);a.id=e;a.style.cssText=b;return a}function l(a,e,b){var c=f("div",a,"padding:0 0 3px 3px;text-align:left;background:"+b),d=f("div",a+"Text","font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px;color:"+e);d.innerHTML=a.toUpperCase();c.appendChild(d);a=f("div",a+"Graph","width:74px;height:30px;background:"+e);c.appendChild(a);for(e=0;74>e;e++)a.appendChild(f("span","","width:1px;height:30px;float:left;opacity:0.9;background:"+ 3 | b));return c}function m(a){for(var b=c.children,d=0;dr+1E3&&(d=Math.round(1E3* 5 | t/(a-r)),u=Math.min(u,d),v=Math.max(v,d),A.textContent=d+" FPS ("+u+"-"+v+")",p(B,d/100),r=a,t=0,void 0!==h)){var b=performance.memory.usedJSHeapSize,c=performance.memory.jsHeapSizeLimit;h=Math.round(9.54E-7*b);y=Math.min(y,h);z=Math.max(z,h);E.textContent=h+" MB ("+y+"-"+z+")";p(F,b/c)}return a},update:function(){k=this.end()}}};"object"===typeof module&&(module.exports=Stats); 6 | -------------------------------------------------------------------------------- /js/f2d/slabop/vorticityconfinement.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | (function(F2D) { 4 | "use strict"; 5 | 6 | F2D.VorticityConfinement = function(fs, grid, time, epsilon, curl) { 7 | this.grid = grid; 8 | this.time = time; 9 | this.epsilon = epsilon === undefined ? 2.4414e-4 : epsilon; 10 | this.curl = curl === undefined ? 0.3 : curl; 11 | 12 | this.uniforms = { 13 | velocity: { 14 | type: "t" 15 | }, 16 | vorticity: { 17 | type: "t" 18 | }, 19 | gridSize: { 20 | type: "v2" 21 | }, 22 | gridScale: { 23 | type: "f" 24 | }, 25 | timestep: { 26 | type: "f" 27 | }, 28 | epsilon: { 29 | type: "f" 30 | }, 31 | curl: { 32 | type: "v2", 33 | value: new THREE.Vector2() 34 | }, 35 | }; 36 | 37 | F2D.SlabopBase.call(this, fs, this.uniforms, grid); 38 | }; 39 | 40 | F2D.VorticityConfinement.prototype = Object.create(F2D.SlabopBase.prototype); 41 | F2D.VorticityConfinement.prototype.constructor = F2D.VorticityConfinement; 42 | 43 | F2D.VorticityConfinement.prototype.compute = function(renderer, velocity, vorticity, output) { 44 | this.uniforms.velocity.value = velocity.read; 45 | this.uniforms.vorticity.value = vorticity.read; 46 | this.uniforms.gridSize.value = this.grid.size; 47 | this.uniforms.gridScale.value = this.grid.scale; 48 | this.uniforms.timestep.value = this.time.step; 49 | this.uniforms.epsilon.value = this.epsilon; 50 | this.uniforms.curl.value.set( 51 | this.curl * this.grid.scale, 52 | this.curl * this.grid.scale 53 | ); 54 | 55 | renderer.render(this.scene, this.camera, output.write, false); 56 | output.swap(); 57 | }; 58 | 59 | }(F2D)); 60 | -------------------------------------------------------------------------------- /js/f2d/fileloader.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | // Loads arbitrary number of files in a batch and gives a callback when every 4 | // file has been loaded with its response text. 5 | (function(F2D) { 6 | "use strict"; 7 | 8 | // Construct a file loader with a suffix path that is prepended to all 9 | // names. 10 | F2D.FileLoader = function(path, names) { 11 | this.path = path; 12 | this.queue = []; 13 | for (var i = 0; i < names.length; i++) { 14 | var name = names[i]; 15 | var url = path + "/" + name; 16 | var file = { 17 | name: name, 18 | url: url 19 | }; 20 | this.queue.push(file); 21 | } 22 | }; 23 | 24 | F2D.FileLoader.prototype = { 25 | constructor: F2D.FileLoader, 26 | 27 | // Load all files currently in the queue, calls onDone when all files 28 | // has been downloaded. 29 | run: function(onDone) { 30 | var files = {}; 31 | var filesRemaining = this.queue.length; 32 | 33 | var fileLoaded = function(file) { 34 | files[file.name] = file.text; 35 | filesRemaining--; 36 | if (filesRemaining === 0) { 37 | onDone(files); 38 | } 39 | }; 40 | 41 | var loadFile = function(file) { 42 | var request = new XMLHttpRequest(); 43 | request.onload = function() { 44 | if (request.status === 200) { 45 | file.text = request.responseText; 46 | } 47 | fileLoaded(file); 48 | }; 49 | request.open("GET", file.url, true); 50 | request.send(); 51 | }; 52 | 53 | for (var i = 0; i < this.queue.length; i++) { 54 | loadFile(this.queue[i]); 55 | } 56 | this.queue = []; 57 | } 58 | }; 59 | 60 | }(F2D)); 61 | -------------------------------------------------------------------------------- /js/f2d/mouse.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | (function(F2D) { 4 | "use strict"; 5 | 6 | F2D.Mouse = function(grid) { 7 | this.grid = grid; 8 | 9 | this.left = false; 10 | this.right = false; 11 | this.position = new THREE.Vector2(); 12 | this.motions = []; 13 | 14 | document.addEventListener("mousedown", this.mouseDown.bind(this), false); 15 | document.addEventListener("mouseup", this.mouseUp.bind(this), false); 16 | document.addEventListener("mousemove", this.mouseMove.bind(this), false); 17 | document.addEventListener("contextmenu", this.contextMenu.bind(this), false); 18 | }; 19 | 20 | F2D.Mouse.prototype = { 21 | constructor: F2D.Mouse, 22 | 23 | mouseDown: function(event) { 24 | this.position.set(event.clientX, event.clientY); 25 | this.left = event.button === 0 ? true : this.left; 26 | this.right = event.button === 2 ? true : this.right; 27 | }, 28 | 29 | mouseUp: function(event) { 30 | event.preventDefault(); 31 | this.left = event.button === 0 ? false : this.left; 32 | this.right = event.button === 2 ? false : this.right; 33 | }, 34 | 35 | mouseMove: function(event) { 36 | event.preventDefault(); 37 | var r = this.grid.scale; 38 | 39 | var x = event.clientX; 40 | var y = event.clientY; 41 | 42 | if (this.left || this.right) { 43 | var dx = x - this.position.x; 44 | var dy = y - this.position.y; 45 | 46 | var drag = { 47 | x: Math.min(Math.max(dx, -r), r), 48 | y: Math.min(Math.max(dy, -r), r) 49 | }; 50 | 51 | var position = { 52 | x: x, 53 | y: y 54 | }; 55 | 56 | this.motions.push({ 57 | left: this.left, 58 | right: this.right, 59 | drag: drag, 60 | position: position 61 | }); 62 | } 63 | 64 | this.position.set(x, y); 65 | }, 66 | 67 | contextMenu: function(event) { 68 | event.preventDefault(); 69 | } 70 | }; 71 | 72 | }(F2D)); 73 | -------------------------------------------------------------------------------- /js/f2d/slabop/boundary.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | (function(F2D) { 4 | "use strict"; 5 | 6 | F2D.Boundary = function(fs, grid) { 7 | this.grid = grid; 8 | 9 | this.uniforms = { 10 | read: { 11 | type: "t" 12 | }, 13 | gridSize: { 14 | type: "v2" 15 | }, 16 | gridOffset: { 17 | type: "v2" 18 | }, 19 | scale: { 20 | type: "f" 21 | }, 22 | }; 23 | var material = new THREE.ShaderMaterial({ 24 | uniforms: this.uniforms, 25 | fragmentShader: fs, 26 | depthWrite: false, 27 | depthTest: false, 28 | blending: THREE.NoBlending 29 | }); 30 | 31 | var createLine = function(positions) { 32 | var vertices = new Float32Array(positions.length * 3); 33 | for (var i = 0; i < positions.length; i++) { 34 | vertices[i * 3 ] = positions[i][0]; 35 | vertices[i * 3 + 1] = positions[i][1]; 36 | vertices[i * 3 + 2] = positions[i][2]; 37 | } 38 | 39 | var geometry = new THREE.BufferGeometry(); 40 | geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3)); 41 | 42 | return new THREE.Line(geometry, material); 43 | }; 44 | 45 | var ax = (this.grid.size.x - 2) / this.grid.size.x; 46 | var ay = (this.grid.size.y - 2) / this.grid.size.y; 47 | var bx = (this.grid.size.x - 1) / this.grid.size.x; 48 | var by = (this.grid.size.y - 1) / this.grid.size.y; 49 | 50 | this.lineL = createLine([[-ax, -ay, 0], [-bx, by, 0]]); 51 | this.lineR = createLine([[ ax, -ay, 0], [ bx, by, 0]]); 52 | this.lineB = createLine([[-ax, -ay, 0], [ bx, -by, 0]]); 53 | this.lineT = createLine([[-ax, ay, 0], [ bx, by, 0]]); 54 | 55 | this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); 56 | this.scene = new THREE.Scene(); 57 | 58 | this.gridOffset = new THREE.Vector3(); 59 | }; 60 | 61 | F2D.Boundary.prototype = { 62 | constructor: F2D.Boundary, 63 | 64 | compute: function(renderer, input, scale, output) { 65 | if (!this.grid.applyBoundaries) 66 | return; 67 | 68 | this.uniforms.read.value = input.read; 69 | this.uniforms.gridSize.value = this.grid.size; 70 | this.uniforms.scale.value = scale; 71 | 72 | this.renderLine(renderer, this.lineL, [ 1, 0], output); 73 | this.renderLine(renderer, this.lineR, [-1, 0], output); 74 | this.renderLine(renderer, this.lineB, [ 0, 1], output); 75 | this.renderLine(renderer, this.lineT, [ 0, -1], output); 76 | }, 77 | 78 | renderLine: function(renderer, line, offset, output) { 79 | this.scene.add(line); 80 | this.gridOffset.set(offset[0], offset[1]); 81 | this.uniforms.gridOffset.value = this.gridOffset; 82 | renderer.render(this.scene, this.camera, output.write, false); 83 | this.scene.remove(line); 84 | // we do not swap output, the next slab operation will fill in the 85 | // iterior and swap it 86 | } 87 | }; 88 | 89 | }(F2D)); 90 | -------------------------------------------------------------------------------- /js/f2d/main.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | 4 | var windowSize = new THREE.Vector2(window.innerWidth, window.innerHeight); 5 | 6 | var renderer = new THREE.WebGLRenderer(); 7 | renderer.autoClear = false; 8 | renderer.sortObjects = false; 9 | renderer.setPixelRatio(window.devicePixelRatio); 10 | renderer.setSize(windowSize.x, windowSize.y); 11 | renderer.setClearColor(0x00ff00); 12 | document.body.appendChild(renderer.domElement); 13 | 14 | var stats = new Stats(); 15 | stats.setMode(0); 16 | stats.domElement.style.position = "absolute"; 17 | stats.domElement.style.left = "0px"; 18 | stats.domElement.style.top = "0px"; 19 | document.body.appendChild(stats.domElement); 20 | 21 | var grid = { 22 | size: new THREE.Vector2(512, 256), 23 | scale: 1, 24 | applyBoundaries: true 25 | }; 26 | var time = { 27 | step: 1, 28 | }; 29 | var displayScalar, displayVector; 30 | var displaySettings = { 31 | slab: "density" 32 | }; 33 | var solver, gui; 34 | var mouse = new F2D.Mouse(grid); 35 | 36 | function init(shaders) { 37 | solver = F2D.Solver.make(grid, time, windowSize, shaders); 38 | 39 | displayScalar = new F2D.Display(shaders.basic, shaders.displayscalar); 40 | displayVector = new F2D.Display(shaders.basic, shaders.displayvector); 41 | 42 | gui = new dat.GUI(); 43 | gui.add(displaySettings, "slab", [ 44 | "density", 45 | "velocity", 46 | "divergence", 47 | "pressure" 48 | ]); 49 | gui.add(time, "step").min(0).step(0.01); 50 | 51 | var advectFolder = gui.addFolder("Advect"); 52 | advectFolder.add(solver.advect, "dissipation", { 53 | "none": 1, 54 | "slow": 0.998, 55 | "fast": 0.992, 56 | "very fast": 0.9 57 | }); 58 | 59 | var viscosityFolder = gui.addFolder("Viscosity"); 60 | viscosityFolder.add(solver, "applyViscosity"); 61 | viscosityFolder.add(solver, "viscosity").min(0).step(0.01); 62 | 63 | var vorticityFolder = gui.addFolder("Vorticity"); 64 | vorticityFolder.add(solver, "applyVorticity"); 65 | vorticityFolder.add(solver.vorticityConfinement, "curl").min(0).step(0.01); 66 | 67 | var poissonPressureEqFolder = gui.addFolder("Poisson Pressure Equation"); 68 | poissonPressureEqFolder.add(solver.poissonPressureEq, "iterations", 0, 500, 1); 69 | 70 | // we need a splat color "adapter" since we want values between 0 and 71 | // 1 but also since dat.GUI requires a JavaScript array over a Three.js 72 | // vector 73 | var splatSettings = { 74 | color: [ 75 | solver.ink.x * 255, 76 | solver.ink.y * 255, 77 | solver.ink.z * 255 78 | ] 79 | }; 80 | var splatFolder = gui.addFolder("Splat"); 81 | splatFolder.add(solver.splat, "radius").min(0); 82 | splatFolder.addColor(splatSettings, "color").onChange(function(value) { 83 | solver.ink.set(value[0] / 255, value[1] / 255, value[2] / 255); 84 | }); 85 | 86 | var gridFolder = gui.addFolder("Grid"); 87 | gridFolder.add(grid, "applyBoundaries"); 88 | gridFolder.add(grid, "scale"); 89 | 90 | requestAnimationFrame(update); 91 | } 92 | 93 | function update() { 94 | stats.begin(); 95 | 96 | solver.step(renderer, mouse); 97 | render(); 98 | 99 | stats.end(); 100 | requestAnimationFrame(update); 101 | } 102 | 103 | function render() { 104 | var display, read; 105 | switch (displaySettings.slab) { 106 | case "velocity": 107 | display = displayVector; 108 | display.scaleNegative(); 109 | read = solver.velocity.read; 110 | break; 111 | case "density": 112 | display = displayScalar; 113 | display.scale.copy(solver.ink); 114 | display.bias.set(0, 0, 0); 115 | read = solver.density.read; 116 | break; 117 | case "divergence": 118 | display = displayScalar; 119 | display.scaleNegative(); 120 | read = solver.velocityDivergence.read; 121 | break; 122 | case "pressure": 123 | display = displayScalar; 124 | display.scaleNegative(); 125 | read = solver.pressure.read; 126 | break; 127 | } 128 | display.render(renderer, read); 129 | } 130 | 131 | function resize() { 132 | windowSize.set(window.innerWidth, window.innerHeight); 133 | renderer.setSize(windowSize.x, windowSize.y); 134 | } 135 | window.onresize = resize; 136 | 137 | var loader = new F2D.FileLoader("shaders", [ 138 | "advect.fs", 139 | "basic.vs", 140 | "gradient.fs", 141 | "jacobiscalar.fs", 142 | "jacobivector.fs", 143 | "displayscalar.fs", 144 | "displayvector.fs", 145 | "divergence.fs", 146 | "splat.fs", 147 | "vorticity.fs", 148 | "vorticityforce.fs", 149 | "boundary.fs" 150 | ]); 151 | loader.run(function(files) { 152 | // remove file extension before passing shaders to init 153 | var shaders = {}; 154 | for (var name in files) { 155 | shaders[name.split(".")[0]] = files[name]; 156 | } 157 | init(shaders); 158 | }); 159 | }()); 160 | -------------------------------------------------------------------------------- /js/f2d/solver.js: -------------------------------------------------------------------------------- 1 | var F2D = F2D === undefined ? {} : F2D; 2 | 3 | (function(F2D) { 4 | "use strict"; 5 | 6 | F2D.Solver = function(grid, time, windowSize, slabs, slabop) { 7 | this.grid = grid; 8 | this.time = time; 9 | this.windowSize = windowSize; 10 | 11 | // slabs 12 | this.velocity = slabs.velocity; 13 | this.density = slabs.density; 14 | this.velocityDivergence = slabs.velocityDivergence; 15 | this.velocityVorticity = slabs.velocityVorticity; 16 | this.pressure = slabs.pressure; 17 | 18 | // slab operations 19 | this.advect = slabop.advect; 20 | this.diffuse = slabop.diffuse; 21 | this.divergence = slabop.divergence; 22 | this.poissonPressureEq = slabop.poissonPressureEq; 23 | this.gradient = slabop.gradient; 24 | this.splat = slabop.splat; 25 | this.vorticity = slabop.vorticity; 26 | this.vorticityConfinement = slabop.vorticityConfinement; 27 | this.boundary = slabop.boundary; 28 | 29 | this.viscosity = 0.3; 30 | this.applyViscosity = false; 31 | this.applyVorticity = false; 32 | 33 | // density attributes 34 | this.source = new THREE.Vector3(0.8, 0.0, 0.0); 35 | this.ink = new THREE.Vector3(0.0, 0.06, 0.19); 36 | }; 37 | 38 | F2D.Solver.prototype = { 39 | constructor: F2D.Solver, 40 | 41 | step: function(renderer, mouse) { 42 | // we only want the quantity carried by the velocity field to be 43 | // affected by the dissipation 44 | var temp = this.advect.dissipation; 45 | this.advect.dissipation = 1; 46 | this.advect.compute(renderer, this.velocity, this.velocity, this.velocity); 47 | this.boundary.compute(renderer, this.velocity, -1, this.velocity); 48 | 49 | this.advect.dissipation = temp; 50 | this.advect.compute(renderer, this.velocity, this.density, this.density); 51 | 52 | this.addForces(renderer, mouse); 53 | 54 | if (this.applyVorticity) { 55 | this.vorticity.compute(renderer, this.velocity, this.velocityVorticity); 56 | this.vorticityConfinement.compute( 57 | renderer, 58 | this.velocity, 59 | this.velocityVorticity, 60 | this.velocity 61 | ); 62 | this.boundary.compute(renderer, this.velocity, -1, this.velocity); 63 | } 64 | 65 | if (this.applyViscosity && this.viscosity > 0) { 66 | var s = this.grid.scale; 67 | 68 | this.diffuse.alpha = (s * s) / (this.viscosity * this.time.step); 69 | this.diffuse.beta = 4 + this.diffuse.alpha; 70 | this.diffuse.compute(renderer, this.velocity, this.velocity, this.velocity, this.boundary, -1); 71 | } 72 | 73 | this.project(renderer); 74 | }, 75 | 76 | addForces: (function() { 77 | var point = new THREE.Vector2(); 78 | var force = new THREE.Vector3(); 79 | return function(renderer, mouse) { 80 | for (var i = 0; i < mouse.motions.length; i++) { 81 | var motion = mouse.motions[i]; 82 | 83 | point.set(motion.position.x, this.windowSize.y - motion.position.y); 84 | // normalize to [0, 1] and scale to grid size 85 | point.x = (point.x / this.windowSize.x) * this.grid.size.x; 86 | point.y = (point.y / this.windowSize.y) * this.grid.size.y; 87 | 88 | if (motion.left) { 89 | force.set( 90 | motion.drag.x, 91 | -motion.drag.y, 92 | 0 93 | ); 94 | this.splat.compute( 95 | renderer, 96 | this.velocity, 97 | force, 98 | point, 99 | this.velocity 100 | ); 101 | this.boundary.compute(renderer, this.velocity, -1, this.velocity); 102 | } 103 | 104 | if (motion.right) { 105 | this.splat.compute( 106 | renderer, 107 | this.density, 108 | this.source, 109 | point, 110 | this.density 111 | ); 112 | } 113 | } 114 | mouse.motions = []; 115 | }; 116 | })(), 117 | 118 | // solve poisson equation and subtract pressure gradient 119 | project: function(renderer) { 120 | this.divergence.compute( 121 | renderer, 122 | this.velocity, 123 | this.velocityDivergence 124 | ); 125 | 126 | // 0 is our initial guess for the poisson equation solver 127 | this.clearSlab(renderer, this.pressure); 128 | 129 | this.poissonPressureEq.alpha = -this.grid.scale * this.grid.scale; 130 | this.poissonPressureEq.compute( 131 | renderer, 132 | this.pressure, 133 | this.velocityDivergence, 134 | this.pressure, 135 | this.boundary, 136 | 1 137 | ); 138 | 139 | this.gradient.compute( 140 | renderer, 141 | this.pressure, 142 | this.velocity, 143 | this.velocity 144 | ); 145 | this.boundary.compute(renderer, this.velocity, -1, this.velocity); 146 | }, 147 | 148 | clearSlab: function(renderer, slab) { 149 | renderer.clearTarget(slab.write, true, false, false); 150 | slab.swap(); 151 | } 152 | }; 153 | 154 | F2D.Solver.make = function(grid, time, windowSize, shaders) { 155 | var w = grid.size.x, 156 | h = grid.size.y; 157 | 158 | var slabs = { 159 | // vec2 160 | velocity: F2D.Slab.make(w, h), 161 | // scalar 162 | density: F2D.Slab.make(w, h), 163 | velocityDivergence: F2D.Slab.make(w, h), 164 | velocityVorticity: F2D.Slab.make(w, h), 165 | pressure: F2D.Slab.make(w, h), 166 | }; 167 | 168 | var slabop = { 169 | advect: new F2D.Advect(shaders.advect, grid, time), 170 | diffuse: new F2D.Jacobi(shaders.jacobivector, grid), 171 | divergence: new F2D.Divergence(shaders.divergence, grid), 172 | poissonPressureEq: new F2D.Jacobi(shaders.jacobiscalar, grid), 173 | gradient: new F2D.Gradient(shaders.gradient, grid), 174 | splat: new F2D.Splat(shaders.splat, grid), 175 | vorticity: new F2D.Vorticity(shaders.vorticity, grid), 176 | vorticityConfinement: new F2D.VorticityConfinement(shaders.vorticityforce, grid, time), 177 | boundary: new F2D.Boundary(shaders.boundary, grid) 178 | }; 179 | 180 | return new F2D.Solver(grid, time, windowSize, slabs, slabop); 181 | }; 182 | 183 | }(F2D)); 184 | -------------------------------------------------------------------------------- /js/vendor/dat.gui.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dat-gui JavaScript Controller Library 3 | * http://code.google.com/p/dat-gui 4 | * 5 | * Copyright 2011 Data Arts Team, Google Creative Lab 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(f,a){a=a||document;var d=a.createElement("link");d.type="text/css";d.rel="stylesheet";d.href=f;a.getElementsByTagName("head")[0].appendChild(d)},inject:function(f,a){a=a||document;var d=document.createElement("style");d.type="text/css";d.innerHTML=f;a.getElementsByTagName("head")[0].appendChild(d)}}}(); 14 | dat.utils.common=function(){var f=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(a[c])||(d[c]=a[c])},this);return d},defaults:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(d[c])&&(d[c]=a[c])},this);return d},compose:function(){var d=a.call(arguments);return function(){for(var e=a.call(arguments),c=d.length-1;0<=c;c--)e=[d[c].apply(this,e)];return e[0]}}, 15 | each:function(a,e,c){if(a)if(f&&a.forEach&&a.forEach===f)a.forEach(e,c);else if(a.length===a.length+0)for(var b=0,p=a.length;bthis.__max&&(a=this.__max);void 0!==this.__step&&0!=a%this.__step&&(a=Math.round(a/this.__step)*this.__step);return e.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__impliedStep=this.__step=a;this.__precision=d(a);return this}});return e}(dat.controllers.Controller,dat.utils.common); 29 | dat.controllers.NumberControllerBox=function(f,a,d){var e=function(c,b,f){function q(){var a=parseFloat(n.__input.value);d.isNaN(a)||n.setValue(a)}function l(a){var b=u-a.clientY;n.setValue(n.getValue()+b*n.__impliedStep);u=a.clientY}function r(){a.unbind(window,"mousemove",l);a.unbind(window,"mouseup",r)}this.__truncationSuspended=!1;e.superclass.call(this,c,b,f);var n=this,u;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",q);a.bind(this.__input, 30 | "blur",function(){q();n.__onFinishChange&&n.__onFinishChange.call(n,n.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",l);a.bind(window,"mouseup",r);u=b.clientY});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&(n.__truncationSuspended=!0,this.blur(),n.__truncationSuspended=!1)});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype,f.prototype,{updateDisplay:function(){var a=this.__input,b;if(this.__truncationSuspended)b= 31 | this.getValue();else{b=this.getValue();var d=Math.pow(10,this.__precision);b=Math.round(b*d)/d}a.value=b;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); 32 | dat.controllers.NumberControllerSlider=function(f,a,d,e,c){function b(a,b,c,e,d){return e+(a-b)/(c-b)*(d-e)}var p=function(c,e,d,f,u){function A(c){c.preventDefault();var e=a.getOffset(k.__background),d=a.getWidth(k.__background);k.setValue(b(c.clientX,e.left,e.left+d,k.__min,k.__max));return!1}function g(){a.unbind(window,"mousemove",A);a.unbind(window,"mouseup",g);k.__onFinishChange&&k.__onFinishChange.call(k,k.getValue())}p.superclass.call(this,c,e,{min:d,max:f,step:u});var k=this;this.__background= 33 | document.createElement("div");this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",A);a.bind(window,"mouseup",g);A(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};p.superclass=f;p.useDefaultStyles=function(){d.inject(c)};e.extend(p.prototype,f.prototype,{updateDisplay:function(){var a= 34 | (this.getValue()-this.__min)/(this.__max-this.__min);this.__foreground.style.width=100*a+"%";return p.superclass.prototype.updateDisplay.call(this)}});return p}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,"/**\n * dat-gui JavaScript Controller Library\n * http://code.google.com/p/dat-gui\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\n.slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); 35 | dat.controllers.FunctionController=function(f,a,d){var e=function(c,b,d){e.superclass.call(this,c,b);var f=this;this.__button=document.createElement("div");this.__button.innerHTML=void 0===d?"Fire":d;a.bind(this.__button,"click",function(a){a.preventDefault();f.fire();return!1});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};e.superclass=f;d.extend(e.prototype,f.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.getValue().call(this.object); 36 | this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue())}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 37 | dat.controllers.BooleanController=function(f,a,d){var e=function(c,b){e.superclass.call(this,c,b);var d=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){d.setValue(!d.__prev)},!1);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};e.superclass=f;d.extend(e.prototype,f.prototype,{setValue:function(a){a=e.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& 38 | this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){!0===this.getValue()?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=!0):this.__checkbox.checked=!1;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 39 | dat.color.toString=function(f){return function(a){if(1==a.a||f.isUndefined(a.a)){for(a=a.hex.toString(16);6>a.length;)a="0"+a;return"#"+a}return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); 40 | dat.color.interpret=function(f,a){var d,e,c=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:f},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:f},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); 41 | return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:f},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:f}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return 3!= 42 | a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return 4!=a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&& 43 | a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){e=!1; 44 | var b=1\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n', 71 | ".dg {\n /** Clear list styles */\n /* Auto-place container */\n /* Auto-placed GUI's */\n /* Line items that don't contain folders. */\n /** Folder names */\n /** Hides closed items */\n /** Controller row */\n /** Name-half (left) */\n /** Controller-half (right) */\n /** Controller placement */\n /** Shorter number boxes when slider is present. */\n /** Ensure the entire boolean and function row shows a hand */ }\n .dg ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n clear: both; }\n .dg.ac {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 0;\n z-index: 0; }\n .dg:not(.ac) .main {\n /** Exclude mains in ac so that we don't hide close button */\n overflow: hidden; }\n .dg.main {\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear; }\n .dg.main.taller-than-window {\n overflow-y: auto; }\n .dg.main.taller-than-window .close-button {\n opacity: 1;\n /* TODO, these are style notes */\n margin-top: -1px;\n border-top: 1px solid #2c2c2c; }\n .dg.main ul.closed .close-button {\n opacity: 1 !important; }\n .dg.main:hover .close-button,\n .dg.main .close-button.drag {\n opacity: 1; }\n .dg.main .close-button {\n /*opacity: 0;*/\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear;\n border: 0;\n position: absolute;\n line-height: 19px;\n height: 20px;\n /* TODO, these are style notes */\n cursor: pointer;\n text-align: center;\n background-color: #000; }\n .dg.main .close-button:hover {\n background-color: #111; }\n .dg.a {\n float: right;\n margin-right: 15px;\n overflow-x: hidden; }\n .dg.a.has-save > ul {\n margin-top: 27px; }\n .dg.a.has-save > ul.closed {\n margin-top: 0; }\n .dg.a .save-row {\n position: fixed;\n top: 0;\n z-index: 1002; }\n .dg li {\n -webkit-transition: height 0.1s ease-out;\n -o-transition: height 0.1s ease-out;\n -moz-transition: height 0.1s ease-out;\n transition: height 0.1s ease-out; }\n .dg li:not(.folder) {\n cursor: auto;\n height: 27px;\n line-height: 27px;\n overflow: hidden;\n padding: 0 4px 0 5px; }\n .dg li.folder {\n padding: 0;\n border-left: 4px solid rgba(0, 0, 0, 0); }\n .dg li.title {\n cursor: pointer;\n margin-left: -4px; }\n .dg .closed li:not(.title),\n .dg .closed ul li,\n .dg .closed ul li > * {\n height: 0;\n overflow: hidden;\n border: 0; }\n .dg .cr {\n clear: both;\n padding-left: 3px;\n height: 27px; }\n .dg .property-name {\n cursor: default;\n float: left;\n clear: left;\n width: 40%;\n overflow: hidden;\n text-overflow: ellipsis; }\n .dg .c {\n float: left;\n width: 60%; }\n .dg .c input[type=text] {\n border: 0;\n margin-top: 4px;\n padding: 3px;\n width: 100%;\n float: right; }\n .dg .has-slider input[type=text] {\n width: 30%;\n /*display: none;*/\n margin-left: 0; }\n .dg .slider {\n float: left;\n width: 66%;\n margin-left: -5px;\n margin-right: 0;\n height: 19px;\n margin-top: 4px; }\n .dg .slider-fg {\n height: 100%; }\n .dg .c input[type=checkbox] {\n margin-top: 9px; }\n .dg .c select {\n margin-top: 5px; }\n .dg .cr.function,\n .dg .cr.function .property-name,\n .dg .cr.function *,\n .dg .cr.boolean,\n .dg .cr.boolean * {\n cursor: pointer; }\n .dg .selector {\n display: none;\n position: absolute;\n margin-left: -9px;\n margin-top: 23px;\n z-index: 10; }\n .dg .c:hover .selector,\n .dg .selector.drag {\n display: block; }\n .dg li.save-row {\n padding: 0; }\n .dg li.save-row .button {\n display: inline-block;\n padding: 0px 6px; }\n .dg.dialogue {\n background-color: #222;\n width: 460px;\n padding: 15px;\n font-size: 13px;\n line-height: 15px; }\n\n/* TODO Separate style and structure */\n#dg-new-constructor {\n padding: 10px;\n color: #222;\n font-family: Monaco, monospace;\n font-size: 10px;\n border: 0;\n resize: none;\n box-shadow: inset 1px 1px 1px #888;\n word-wrap: break-word;\n margin: 12px 0;\n display: block;\n width: 440px;\n overflow-y: scroll;\n height: 100px;\n position: relative; }\n\n#dg-local-explain {\n display: none;\n font-size: 11px;\n line-height: 17px;\n border-radius: 3px;\n background-color: #333;\n padding: 8px;\n margin-top: 10px; }\n #dg-local-explain code {\n font-size: 10px; }\n\n#dat-gui-save-locally {\n display: none; }\n\n/** Main type */\n.dg {\n color: #eee;\n font: 11px 'Lucida Grande', sans-serif;\n text-shadow: 0 -1px 0 #111;\n /** Auto place */\n /* Controller row,
  • */\n /** Controllers */ }\n .dg.main {\n /** Scrollbar */ }\n .dg.main::-webkit-scrollbar {\n width: 5px;\n background: #1a1a1a; }\n .dg.main::-webkit-scrollbar-corner {\n height: 0;\n display: none; }\n .dg.main::-webkit-scrollbar-thumb {\n border-radius: 5px;\n background: #676767; }\n .dg li:not(.folder) {\n background: #1a1a1a;\n border-bottom: 1px solid #2c2c2c; }\n .dg li.save-row {\n line-height: 25px;\n background: #dad5cb;\n border: 0; }\n .dg li.save-row select {\n margin-left: 5px;\n width: 108px; }\n .dg li.save-row .button {\n margin-left: 5px;\n margin-top: 1px;\n border-radius: 2px;\n font-size: 9px;\n line-height: 7px;\n padding: 4px 4px 5px 4px;\n background: #c5bdad;\n color: #fff;\n text-shadow: 0 1px 0 #b0a58f;\n box-shadow: 0 -1px 0 #b0a58f;\n cursor: pointer; }\n .dg li.save-row .button.gears {\n background: #c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;\n height: 7px;\n width: 8px; }\n .dg li.save-row .button:hover {\n background-color: #bab19e;\n box-shadow: 0 -1px 0 #b0a58f; }\n .dg li.folder {\n border-bottom: 0; }\n .dg li.title {\n padding-left: 16px;\n background: black url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2); }\n .dg .closed li.title {\n background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==); }\n .dg .cr.boolean {\n border-left: 3px solid #806787; }\n .dg .cr.function {\n border-left: 3px solid #e61d5f; }\n .dg .cr.number {\n border-left: 3px solid #2fa1d6; }\n .dg .cr.number input[type=text] {\n color: #2fa1d6; }\n .dg .cr.string {\n border-left: 3px solid #1ed36f; }\n .dg .cr.string input[type=text] {\n color: #1ed36f; }\n .dg .cr.function:hover, .dg .cr.boolean:hover {\n background: #111; }\n .dg .c input[type=text] {\n background: #303030;\n outline: none; }\n .dg .c input[type=text]:hover {\n background: #3c3c3c; }\n .dg .c input[type=text]:focus {\n background: #494949;\n color: #fff; }\n .dg .c .slider {\n background: #303030;\n cursor: ew-resize; }\n .dg .c .slider-fg {\n background: #2fa1d6; }\n .dg .c .slider:hover {\n background: #3c3c3c; }\n .dg .c .slider:hover .slider-fg {\n background: #44abda; }\n", 72 | dat.controllers.factory=function(f,a,d,e,c,b,p){return function(q,l,r,n){var u=q[l];if(p.isArray(r)||p.isObject(r))return new f(q,l,r);if(p.isNumber(u))return p.isNumber(r)&&p.isNumber(n)?new d(q,l,r,n):new a(q,l,{min:r,max:n});if(p.isString(u))return new e(q,l);if(p.isFunction(u))return new c(q,l,"");if(p.isBoolean(u))return new b(q,l)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(f,a,d){var e= 73 | function(c,b){function d(){f.setValue(f.__input.value)}e.superclass.call(this,c,b);var f=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",d);a.bind(this.__input,"change",d);a.bind(this.__input,"blur",function(){f.__onFinishChange&&f.__onFinishChange.call(f,f.getValue())});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype, 74 | f.prototype,{updateDisplay:function(){a.isActive(this.__input)||(this.__input.value=this.getValue());return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, 75 | dat.controllers.ColorController=function(f,a,d,e,c){function b(a,b,d,e){a.style.background="";c.each(l,function(c){a.style.cssText+="background: "+c+"linear-gradient("+b+", "+d+" 0%, "+e+" 100%); "})}function p(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; 76 | a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var q=function(f,n){function u(b){v(b);a.bind(window,"mousemove",v);a.bind(window, 77 | "mouseup",l)}function l(){a.unbind(window,"mousemove",v);a.unbind(window,"mouseup",l)}function g(){var a=e(this.value);!1!==a?(t.__color.__state=a,t.setValue(t.__color.toOriginal())):this.value=t.__color.toString()}function k(){a.unbind(window,"mousemove",w);a.unbind(window,"mouseup",k)}function v(b){b.preventDefault();var c=a.getWidth(t.__saturation_field),d=a.getOffset(t.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c;b=1-(b.clientY-d.top+document.body.scrollTop)/c;1 78 | b&&(b=0);1e&&(e=0);t.__color.v=b;t.__color.s=e;t.setValue(t.__color.toOriginal());return!1}function w(b){b.preventDefault();var c=a.getHeight(t.__hue_field),d=a.getOffset(t.__hue_field);b=1-(b.clientY-d.top+document.body.scrollTop)/c;1b&&(b=0);t.__color.h=360*b;t.setValue(t.__color.toOriginal());return!1}q.superclass.call(this,f,n);this.__color=new d(this.getValue());this.__temp=new d(0);var t=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement,!1); 79 | this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input=document.createElement("input"); 80 | this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){13===a.keyCode&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(b){a.addClass(this,"drag").bind(window,"mouseup",function(b){a.removeClass(t.__selector,"drag")})});var y=document.createElement("div");c.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"});c.extend(this.__field_knob.style, 81 | {position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(.5>this.__color.v?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});c.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});c.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});c.extend(y.style,{width:"100%",height:"100%", 82 | background:"none"});b(y,"top","rgba(0,0,0,0)","#000");c.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});p(this.__hue_field);c.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",u);a.bind(this.__field_knob,"mousedown",u);a.bind(this.__hue_field,"mousedown",function(b){w(b);a.bind(window, 83 | "mousemove",w);a.bind(window,"mouseup",k)});this.__saturation_field.appendChild(y);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};q.superclass=f;c.extend(q.prototype,f.prototype,{updateDisplay:function(){var a=e(this.getValue());if(!1!==a){var f=!1; 84 | c.each(d.COMPONENTS,function(b){if(!c.isUndefined(a[b])&&!c.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return f=!0,{}},this);f&&c.extend(this.__color.__state,a)}c.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var l=.5>this.__color.v||.5a&&(a+=1);return{h:360*a,s:c/b,v:b/255}},rgb_to_hex:function(a,d,e){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,d);return a=this.hex_with_component(a,0,e)},component_from_hex:function(a,d){return a>>8*d&255},hex_with_component:function(a,d,e){return e<<(f=8*d)|a&~(255<