├── 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 | r+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<