fluid.js - bitreich-www - the bitreich www website generator
 (HTM) git clone git://bitreich.org/bitreich-www/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/bitreich-www/
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
       ---
       fluid.js (47405B)
       ---
            1 /*
            2 MIT License
            3 
            4 Copyright (c) 2017 Pavel Dobryakov
            5 
            6 Permission is hereby granted, free of charge, to any person obtaining a copy
            7 of this software and associated documentation files (the "Software"), to deal
            8 in the Software without restriction, including without limitation the rights
            9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
           10 copies of the Software, and to permit persons to whom the Software is
           11 furnished to do so, subject to the following conditions:
           12 
           13 The above copyright notice and this permission notice shall be included in all
           14 copies or substantial portions of the Software.
           15 
           16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
           17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
           18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
           19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
           20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
           21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
           22 SOFTWARE.
           23 */
           24 
           25 'use strict';
           26 
           27 
           28 // Simulation section
           29 
           30 const canvas = document.getElementById('fluidCanvas');
           31 resizeCanvas();
           32 
           33 let config = {
           34     SIM_RESOLUTION: 128,
           35     DYE_RESOLUTION: 1024,
           36     CAPTURE_RESOLUTION: 512,
           37     DENSITY_DISSIPATION: 3,
           38     VELOCITY_DISSIPATION: 0.17,
           39     PRESSURE: 0.22,
           40     PRESSURE_ITERATIONS: 20,
           41     CURL: 0,
           42     SPLAT_RADIUS: 0.3,
           43     SPLAT_FORCE: 6000,
           44     SHADING: true,
           45     COLORFUL: true,
           46     COLOR_UPDATE_SPEED: 10,
           47     PAUSED: false,
           48     BACK_COLOR: { r: 0, g: 0, b: 0 },
           49     TRANSPARENT: false,
           50     BLOOM: false,
           51     BLOOM_ITERATIONS: 8,
           52     BLOOM_RESOLUTION: 256,
           53     BLOOM_INTENSITY: 0.8,
           54     BLOOM_THRESHOLD: 0.6,
           55     BLOOM_SOFT_KNEE: 0.7,
           56     SUNRAYS: true,
           57     SUNRAYS_RESOLUTION: 196,
           58     SUNRAYS_WEIGHT: 1.0,
           59 }
           60 
           61 function pointerPrototype () {
           62     this.id = -1;
           63     this.texcoordX = 0;
           64     this.texcoordY = 0;
           65     this.prevTexcoordX = 0;
           66     this.prevTexcoordY = 0;
           67     this.deltaX = 0;
           68     this.deltaY = 0;
           69     this.down = false;
           70     this.moved = false;
           71     this.color = [30, 0, 300];
           72 }
           73 
           74 let pointers = [];
           75 let splatStack = [];
           76 pointers.push(new pointerPrototype());
           77 
           78 const { gl, ext } = getWebGLContext(canvas);
           79 
           80 if (isMobile()) {
           81     config.DYE_RESOLUTION = 512;
           82 }
           83 if (!ext.supportLinearFiltering) {
           84     config.DYE_RESOLUTION = 512;
           85     config.SHADING = false;
           86     config.BLOOM = false;
           87     config.SUNRAYS = false;
           88 }
           89 
           90 
           91 
           92 function getWebGLContext (canvas) {
           93     const params = { alpha: true, depth: false, stencil: false, antialias: false, preserveDrawingBuffer: false };
           94 
           95     let gl = canvas.getContext('webgl2', params);
           96     const isWebGL2 = !!gl;
           97     if (!isWebGL2)
           98         gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params);
           99 
          100     let halfFloat;
          101     let supportLinearFiltering;
          102     if (isWebGL2) {
          103         gl.getExtension('EXT_color_buffer_float');
          104         supportLinearFiltering = gl.getExtension('OES_texture_float_linear');
          105     } else {
          106         halfFloat = gl.getExtension('OES_texture_half_float');
          107         supportLinearFiltering = gl.getExtension('OES_texture_half_float_linear');
          108     }
          109 
          110     gl.clearColor(0.0, 0.0, 0.0, 1.0);
          111 
          112     const halfFloatTexType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES;
          113     let formatRGBA;
          114     let formatRG;
          115     let formatR;
          116 
          117     if (isWebGL2)
          118     {
          119         formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType);
          120         formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType);
          121         formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType);
          122     }
          123     else
          124     {
          125         formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
          126         formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
          127         formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
          128     }
          129 
          130     //ga('send', 'event', isWebGL2 ? 'webgl2' : 'webgl', formatRGBA == null ? 'not supported' : 'supported');
          131 
          132     return {
          133         gl,
          134         ext: {
          135             formatRGBA,
          136             formatRG,
          137             formatR,
          138             halfFloatTexType,
          139             supportLinearFiltering
          140         }
          141     };
          142 }
          143 
          144 function getSupportedFormat (gl, internalFormat, format, type)
          145 {
          146     if (!supportRenderTextureFormat(gl, internalFormat, format, type))
          147     {
          148         switch (internalFormat)
          149         {
          150             case gl.R16F:
          151                 return getSupportedFormat(gl, gl.RG16F, gl.RG, type);
          152             case gl.RG16F:
          153                 return getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, type);
          154             default:
          155                 return null;
          156         }
          157     }
          158 
          159     return {
          160         internalFormat,
          161         format
          162     }
          163 }
          164 
          165 function supportRenderTextureFormat (gl, internalFormat, format, type) {
          166     let texture = gl.createTexture();
          167     gl.bindTexture(gl.TEXTURE_2D, texture);
          168     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
          169     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
          170     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
          171     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
          172     gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 4, 4, 0, format, type, null);
          173 
          174     let fbo = gl.createFramebuffer();
          175     gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
          176     gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
          177 
          178     let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
          179     return status == gl.FRAMEBUFFER_COMPLETE;
          180 }
          181 
          182 function isMobile () {
          183     return /Mobi|Android/i.test(navigator.userAgent);
          184 }
          185 
          186 function captureScreenshot () {
          187     let res = getResolution(config.CAPTURE_RESOLUTION);
          188     let target = createFBO(res.width, res.height, ext.formatRGBA.internalFormat, ext.formatRGBA.format, ext.halfFloatTexType, gl.NEAREST);
          189     render(target);
          190 
          191     let texture = framebufferToTexture(target);
          192     texture = normalizeTexture(texture, target.width, target.height);
          193 
          194     let captureCanvas = textureToCanvas(texture, target.width, target.height);
          195     let datauri = captureCanvas.toDataURL();
          196     downloadURI('fluid.png', datauri);
          197     URL.revokeObjectURL(datauri);
          198 }
          199 
          200 function framebufferToTexture (target) {
          201     gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);
          202     let length = target.width * target.height * 4;
          203     let texture = new Float32Array(length);
          204     gl.readPixels(0, 0, target.width, target.height, gl.RGBA, gl.FLOAT, texture);
          205     return texture;
          206 }
          207 
          208 function normalizeTexture (texture, width, height) {
          209     let result = new Uint8Array(texture.length);
          210     let id = 0;
          211     for (let i = height - 1; i >= 0; i--) {
          212         for (let j = 0; j < width; j++) {
          213             let nid = i * width * 4 + j * 4;
          214             result[nid + 0] = clamp01(texture[id + 0]) * 255;
          215             result[nid + 1] = clamp01(texture[id + 1]) * 255;
          216             result[nid + 2] = clamp01(texture[id + 2]) * 255;
          217             result[nid + 3] = clamp01(texture[id + 3]) * 255;
          218             id += 4;
          219         }
          220     }
          221     return result;
          222 }
          223 
          224 function clamp01 (input) {
          225     return Math.min(Math.max(input, 0), 1);
          226 }
          227 
          228 function textureToCanvas (texture, width, height) {
          229     let captureCanvas = document.createElement('canvas');
          230     let ctx = captureCanvas.getContext('2d');
          231     captureCanvas.width = width;
          232     captureCanvas.height = height;
          233 
          234     let imageData = ctx.createImageData(width, height);
          235     imageData.data.set(texture);
          236     ctx.putImageData(imageData, 0, 0);
          237 
          238     return captureCanvas;
          239 }
          240 
          241 function downloadURI (filename, uri) {
          242     let link = document.createElement('a');
          243     link.download = filename;
          244     link.href = uri;
          245     document.body.appendChild(link);
          246     link.click();
          247     document.body.removeChild(link);
          248 }
          249 
          250 class Material {
          251     constructor (vertexShader, fragmentShaderSource) {
          252         this.vertexShader = vertexShader;
          253         this.fragmentShaderSource = fragmentShaderSource;
          254         this.programs = [];
          255         this.activeProgram = null;
          256         this.uniforms = [];
          257     }
          258 
          259     setKeywords (keywords) {
          260         let hash = 0;
          261         for (let i = 0; i < keywords.length; i++)
          262             hash += hashCode(keywords[i]);
          263 
          264         let program = this.programs[hash];
          265         if (program == null)
          266         {
          267             let fragmentShader = compileShader(gl.FRAGMENT_SHADER, this.fragmentShaderSource, keywords);
          268             program = createProgram(this.vertexShader, fragmentShader);
          269             this.programs[hash] = program;
          270         }
          271 
          272         if (program == this.activeProgram) return;
          273 
          274         this.uniforms = getUniforms(program);
          275         this.activeProgram = program;
          276     }
          277 
          278     bind () {
          279         gl.useProgram(this.activeProgram);
          280     }
          281 }
          282 
          283 class Program {
          284     constructor (vertexShader, fragmentShader) {
          285         this.uniforms = {};
          286         this.program = createProgram(vertexShader, fragmentShader);
          287         this.uniforms = getUniforms(this.program);
          288     }
          289 
          290     bind () {
          291         gl.useProgram(this.program);
          292     }
          293 }
          294 
          295 function createProgram (vertexShader, fragmentShader) {
          296     let program = gl.createProgram();
          297     gl.attachShader(program, vertexShader);
          298     gl.attachShader(program, fragmentShader);
          299     gl.linkProgram(program);
          300 
          301     if (!gl.getProgramParameter(program, gl.LINK_STATUS))
          302         console.trace(gl.getProgramInfoLog(program));
          303 
          304     return program;
          305 }
          306 
          307 function getUniforms (program) {
          308     let uniforms = [];
          309     let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
          310     for (let i = 0; i < uniformCount; i++) {
          311         let uniformName = gl.getActiveUniform(program, i).name;
          312         uniforms[uniformName] = gl.getUniformLocation(program, uniformName);
          313     }
          314     return uniforms;
          315 }
          316 
          317 function compileShader (type, source, keywords) {
          318     source = addKeywords(source, keywords);
          319 
          320     const shader = gl.createShader(type);
          321     gl.shaderSource(shader, source);
          322     gl.compileShader(shader);
          323 
          324     if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
          325         console.trace(gl.getShaderInfoLog(shader));
          326 
          327     return shader;
          328 };
          329 
          330 function addKeywords (source, keywords) {
          331     if (keywords == null) return source;
          332     let keywordsString = '';
          333     keywords.forEach(keyword => {
          334         keywordsString += '#define ' + keyword + '\n';
          335     });
          336     return keywordsString + source;
          337 }
          338 
          339 const baseVertexShader = compileShader(gl.VERTEX_SHADER, `
          340     precision highp float;
          341 
          342     attribute vec2 aPosition;
          343     varying vec2 vUv;
          344     varying vec2 vL;
          345     varying vec2 vR;
          346     varying vec2 vT;
          347     varying vec2 vB;
          348     uniform vec2 texelSize;
          349 
          350     void main () {
          351         vUv = aPosition * 0.5 + 0.5;
          352         vL = vUv - vec2(texelSize.x, 0.0);
          353         vR = vUv + vec2(texelSize.x, 0.0);
          354         vT = vUv + vec2(0.0, texelSize.y);
          355         vB = vUv - vec2(0.0, texelSize.y);
          356         gl_Position = vec4(aPosition, 0.0, 1.0);
          357     }
          358 `);
          359 
          360 const blurVertexShader = compileShader(gl.VERTEX_SHADER, `
          361     precision highp float;
          362 
          363     attribute vec2 aPosition;
          364     varying vec2 vUv;
          365     varying vec2 vL;
          366     varying vec2 vR;
          367     uniform vec2 texelSize;
          368 
          369     void main () {
          370         vUv = aPosition * 0.5 + 0.5;
          371         float offset = 1.33333333;
          372         vL = vUv - texelSize * offset;
          373         vR = vUv + texelSize * offset;
          374         gl_Position = vec4(aPosition, 0.0, 1.0);
          375     }
          376 `);
          377 
          378 const blurShader = compileShader(gl.FRAGMENT_SHADER, `
          379     precision mediump float;
          380     precision mediump sampler2D;
          381 
          382     varying vec2 vUv;
          383     varying vec2 vL;
          384     varying vec2 vR;
          385     uniform sampler2D uTexture;
          386 
          387     void main () {
          388         vec4 sum = texture2D(uTexture, vUv) * 0.29411764;
          389         sum += texture2D(uTexture, vL) * 0.35294117;
          390         sum += texture2D(uTexture, vR) * 0.35294117;
          391         gl_FragColor = sum;
          392     }
          393 `);
          394 
          395 const copyShader = compileShader(gl.FRAGMENT_SHADER, `
          396     precision mediump float;
          397     precision mediump sampler2D;
          398 
          399     varying highp vec2 vUv;
          400     uniform sampler2D uTexture;
          401 
          402     void main () {
          403         gl_FragColor = texture2D(uTexture, vUv);
          404     }
          405 `);
          406 
          407 const clearShader = compileShader(gl.FRAGMENT_SHADER, `
          408     precision mediump float;
          409     precision mediump sampler2D;
          410 
          411     varying highp vec2 vUv;
          412     uniform sampler2D uTexture;
          413     uniform float value;
          414 
          415     void main () {
          416         gl_FragColor = value * texture2D(uTexture, vUv);
          417     }
          418 `);
          419 
          420 const colorShader = compileShader(gl.FRAGMENT_SHADER, `
          421     precision mediump float;
          422 
          423     uniform vec4 color;
          424 
          425     void main () {
          426         gl_FragColor = color;
          427     }
          428 `);
          429 
          430 const checkerboardShader = compileShader(gl.FRAGMENT_SHADER, `
          431     precision highp float;
          432     precision highp sampler2D;
          433 
          434     varying vec2 vUv;
          435     uniform sampler2D uTexture;
          436     uniform float aspectRatio;
          437 
          438     #define SCALE 25.0
          439 
          440     void main () {
          441         vec2 uv = floor(vUv * SCALE * vec2(aspectRatio, 1.0));
          442         float v = mod(uv.x + uv.y, 2.0);
          443         v = v * 0.1 + 0.8;
          444         gl_FragColor = vec4(vec3(v), 1.0);
          445     }
          446 `);
          447 
          448 const displayShaderSource = `
          449     precision highp float;
          450     precision highp sampler2D;
          451 
          452     varying vec2 vUv;
          453     varying vec2 vL;
          454     varying vec2 vR;
          455     varying vec2 vT;
          456     varying vec2 vB;
          457     uniform sampler2D uTexture;
          458     uniform sampler2D uBloom;
          459     uniform sampler2D uSunrays;
          460     uniform sampler2D uDithering;
          461     uniform vec2 ditherScale;
          462     uniform vec2 texelSize;
          463 
          464     vec3 linearToGamma (vec3 color) {
          465         color = max(color, vec3(0));
          466         return max(1.055 * pow(color, vec3(0.416666667)) - 0.055, vec3(0));
          467     }
          468 
          469     void main () {
          470         vec3 c = texture2D(uTexture, vUv).rgb;
          471 
          472     #ifdef SHADING
          473         vec3 lc = texture2D(uTexture, vL).rgb;
          474         vec3 rc = texture2D(uTexture, vR).rgb;
          475         vec3 tc = texture2D(uTexture, vT).rgb;
          476         vec3 bc = texture2D(uTexture, vB).rgb;
          477 
          478         float dx = length(rc) - length(lc);
          479         float dy = length(tc) - length(bc);
          480 
          481         vec3 n = normalize(vec3(dx, dy, length(texelSize)));
          482         vec3 l = vec3(0.0, 0.0, 1.0);
          483 
          484         float diffuse = clamp(dot(n, l) + 0.7, 0.7, 1.0);
          485         c *= diffuse;
          486     #endif
          487 
          488     #ifdef BLOOM
          489         vec3 bloom = texture2D(uBloom, vUv).rgb;
          490     #endif
          491 
          492     #ifdef SUNRAYS
          493         float sunrays = texture2D(uSunrays, vUv).r;
          494         c *= sunrays;
          495     #ifdef BLOOM
          496         bloom *= sunrays;
          497     #endif
          498     #endif
          499 
          500     #ifdef BLOOM
          501         float noise = texture2D(uDithering, vUv * ditherScale).r;
          502         noise = noise * 2.0 - 1.0;
          503         bloom += noise / 255.0;
          504         bloom = linearToGamma(bloom);
          505         c += bloom;
          506     #endif
          507 
          508         float a = max(c.r, max(c.g, c.b));
          509         gl_FragColor = vec4(c, a);
          510     }
          511 `;
          512 
          513 const bloomPrefilterShader = compileShader(gl.FRAGMENT_SHADER, `
          514     precision mediump float;
          515     precision mediump sampler2D;
          516 
          517     varying vec2 vUv;
          518     uniform sampler2D uTexture;
          519     uniform vec3 curve;
          520     uniform float threshold;
          521 
          522     void main () {
          523         vec3 c = texture2D(uTexture, vUv).rgb;
          524         float br = max(c.r, max(c.g, c.b));
          525         float rq = clamp(br - curve.x, 0.0, curve.y);
          526         rq = curve.z * rq * rq;
          527         c *= max(rq, br - threshold) / max(br, 0.0001);
          528         gl_FragColor = vec4(c, 0.0);
          529     }
          530 `);
          531 
          532 const bloomBlurShader = compileShader(gl.FRAGMENT_SHADER, `
          533     precision mediump float;
          534     precision mediump sampler2D;
          535 
          536     varying vec2 vL;
          537     varying vec2 vR;
          538     varying vec2 vT;
          539     varying vec2 vB;
          540     uniform sampler2D uTexture;
          541 
          542     void main () {
          543         vec4 sum = vec4(0.0);
          544         sum += texture2D(uTexture, vL);
          545         sum += texture2D(uTexture, vR);
          546         sum += texture2D(uTexture, vT);
          547         sum += texture2D(uTexture, vB);
          548         sum *= 0.25;
          549         gl_FragColor = sum;
          550     }
          551 `);
          552 
          553 const bloomFinalShader = compileShader(gl.FRAGMENT_SHADER, `
          554     precision mediump float;
          555     precision mediump sampler2D;
          556 
          557     varying vec2 vL;
          558     varying vec2 vR;
          559     varying vec2 vT;
          560     varying vec2 vB;
          561     uniform sampler2D uTexture;
          562     uniform float intensity;
          563 
          564     void main () {
          565         vec4 sum = vec4(0.0);
          566         sum += texture2D(uTexture, vL);
          567         sum += texture2D(uTexture, vR);
          568         sum += texture2D(uTexture, vT);
          569         sum += texture2D(uTexture, vB);
          570         sum *= 0.25;
          571         gl_FragColor = sum * intensity;
          572     }
          573 `);
          574 
          575 const sunraysMaskShader = compileShader(gl.FRAGMENT_SHADER, `
          576     precision highp float;
          577     precision highp sampler2D;
          578 
          579     varying vec2 vUv;
          580     uniform sampler2D uTexture;
          581 
          582     void main () {
          583         vec4 c = texture2D(uTexture, vUv);
          584         float br = max(c.r, max(c.g, c.b));
          585         c.a = 1.0 - min(max(br * 20.0, 0.0), 0.8);
          586         gl_FragColor = c;
          587     }
          588 `);
          589 
          590 const sunraysShader = compileShader(gl.FRAGMENT_SHADER, `
          591     precision highp float;
          592     precision highp sampler2D;
          593 
          594     varying vec2 vUv;
          595     uniform sampler2D uTexture;
          596     uniform float weight;
          597 
          598     #define ITERATIONS 16
          599 
          600     void main () {
          601         float Density = 0.3;
          602         float Decay = 0.95;
          603         float Exposure = 0.7;
          604 
          605         vec2 coord = vUv;
          606         vec2 dir = vUv - 0.5;
          607 
          608         dir *= 1.0 / float(ITERATIONS) * Density;
          609         float illuminationDecay = 1.0;
          610 
          611         float color = texture2D(uTexture, vUv).a;
          612 
          613         for (int i = 0; i < ITERATIONS; i++)
          614         {
          615             coord -= dir;
          616             float col = texture2D(uTexture, coord).a;
          617             color += col * illuminationDecay * weight;
          618             illuminationDecay *= Decay;
          619         }
          620 
          621         gl_FragColor = vec4(color * Exposure, 0.0, 0.0, 1.0);
          622     }
          623 `);
          624 
          625 const splatShader = compileShader(gl.FRAGMENT_SHADER, `
          626     precision highp float;
          627     precision highp sampler2D;
          628 
          629     varying vec2 vUv;
          630     uniform sampler2D uTarget;
          631     uniform float aspectRatio;
          632     uniform vec3 color;
          633     uniform vec2 point;
          634     uniform float radius;
          635 
          636     void main () {
          637         vec2 p = vUv - point.xy;
          638         p.x *= aspectRatio;
          639         vec3 splat = exp(-dot(p, p) / radius) * color;
          640         vec3 base = texture2D(uTarget, vUv).xyz;
          641         gl_FragColor = vec4(base + splat, 1.0);
          642     }
          643 `);
          644 
          645 const advectionShader = compileShader(gl.FRAGMENT_SHADER, `
          646     precision highp float;
          647     precision highp sampler2D;
          648 
          649     varying vec2 vUv;
          650     uniform sampler2D uVelocity;
          651     uniform sampler2D uSource;
          652     uniform vec2 texelSize;
          653     uniform vec2 dyeTexelSize;
          654     uniform float dt;
          655     uniform float dissipation;
          656 
          657     vec4 bilerp (sampler2D sam, vec2 uv, vec2 tsize) {
          658         vec2 st = uv / tsize - 0.5;
          659 
          660         vec2 iuv = floor(st);
          661         vec2 fuv = fract(st);
          662 
          663         vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize);
          664         vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize);
          665         vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize);
          666         vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize);
          667 
          668         return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y);
          669     }
          670 
          671     void main () {
          672     #ifdef MANUAL_FILTERING
          673         vec2 coord = vUv - dt * bilerp(uVelocity, vUv, texelSize).xy * texelSize;
          674         vec4 result = bilerp(uSource, coord, dyeTexelSize);
          675     #else
          676         vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize;
          677         vec4 result = texture2D(uSource, coord);
          678     #endif
          679         float decay = 1.0 + dissipation * dt;
          680         gl_FragColor = result / decay;
          681     }`,
          682     ext.supportLinearFiltering ? null : ['MANUAL_FILTERING']
          683 );
          684 
          685 const divergenceShader = compileShader(gl.FRAGMENT_SHADER, `
          686     precision mediump float;
          687     precision mediump sampler2D;
          688 
          689     varying highp vec2 vUv;
          690     varying highp vec2 vL;
          691     varying highp vec2 vR;
          692     varying highp vec2 vT;
          693     varying highp vec2 vB;
          694     uniform sampler2D uVelocity;
          695 
          696     void main () {
          697         float L = texture2D(uVelocity, vL).x;
          698         float R = texture2D(uVelocity, vR).x;
          699         float T = texture2D(uVelocity, vT).y;
          700         float B = texture2D(uVelocity, vB).y;
          701 
          702         vec2 C = texture2D(uVelocity, vUv).xy;
          703         if (vL.x < 0.0) { L = -C.x; }
          704         if (vR.x > 1.0) { R = -C.x; }
          705         if (vT.y > 1.0) { T = -C.y; }
          706         if (vB.y < 0.0) { B = -C.y; }
          707 
          708         float div = 0.5 * (R - L + T - B);
          709         gl_FragColor = vec4(div, 0.0, 0.0, 1.0);
          710     }
          711 `);
          712 
          713 const curlShader = compileShader(gl.FRAGMENT_SHADER, `
          714     precision mediump float;
          715     precision mediump sampler2D;
          716 
          717     varying highp vec2 vUv;
          718     varying highp vec2 vL;
          719     varying highp vec2 vR;
          720     varying highp vec2 vT;
          721     varying highp vec2 vB;
          722     uniform sampler2D uVelocity;
          723 
          724     void main () {
          725         float L = texture2D(uVelocity, vL).y;
          726         float R = texture2D(uVelocity, vR).y;
          727         float T = texture2D(uVelocity, vT).x;
          728         float B = texture2D(uVelocity, vB).x;
          729         float vorticity = R - L - T + B;
          730         gl_FragColor = vec4(0.5 * vorticity, 0.0, 0.0, 1.0);
          731     }
          732 `);
          733 
          734 const vorticityShader = compileShader(gl.FRAGMENT_SHADER, `
          735     precision highp float;
          736     precision highp sampler2D;
          737 
          738     varying vec2 vUv;
          739     varying vec2 vL;
          740     varying vec2 vR;
          741     varying vec2 vT;
          742     varying vec2 vB;
          743     uniform sampler2D uVelocity;
          744     uniform sampler2D uCurl;
          745     uniform float curl;
          746     uniform float dt;
          747 
          748     void main () {
          749         float L = texture2D(uCurl, vL).x;
          750         float R = texture2D(uCurl, vR).x;
          751         float T = texture2D(uCurl, vT).x;
          752         float B = texture2D(uCurl, vB).x;
          753         float C = texture2D(uCurl, vUv).x;
          754 
          755         vec2 force = 0.5 * vec2(abs(T) - abs(B), abs(R) - abs(L));
          756         force /= length(force) + 0.0001;
          757         force *= curl * C;
          758         force.y *= -1.0;
          759 
          760         vec2 velocity = texture2D(uVelocity, vUv).xy;
          761         velocity += force * dt;
          762         velocity = min(max(velocity, -1000.0), 1000.0);
          763         gl_FragColor = vec4(velocity, 0.0, 1.0);
          764     }
          765 `);
          766 
          767 const pressureShader = compileShader(gl.FRAGMENT_SHADER, `
          768     precision mediump float;
          769     precision mediump sampler2D;
          770 
          771     varying highp vec2 vUv;
          772     varying highp vec2 vL;
          773     varying highp vec2 vR;
          774     varying highp vec2 vT;
          775     varying highp vec2 vB;
          776     uniform sampler2D uPressure;
          777     uniform sampler2D uDivergence;
          778 
          779     void main () {
          780         float L = texture2D(uPressure, vL).x;
          781         float R = texture2D(uPressure, vR).x;
          782         float T = texture2D(uPressure, vT).x;
          783         float B = texture2D(uPressure, vB).x;
          784         float C = texture2D(uPressure, vUv).x;
          785         float divergence = texture2D(uDivergence, vUv).x;
          786         float pressure = (L + R + B + T - divergence) * 0.25;
          787         gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0);
          788     }
          789 `);
          790 
          791 const gradientSubtractShader = compileShader(gl.FRAGMENT_SHADER, `
          792     precision mediump float;
          793     precision mediump sampler2D;
          794 
          795     varying highp vec2 vUv;
          796     varying highp vec2 vL;
          797     varying highp vec2 vR;
          798     varying highp vec2 vT;
          799     varying highp vec2 vB;
          800     uniform sampler2D uPressure;
          801     uniform sampler2D uVelocity;
          802 
          803     void main () {
          804         float L = texture2D(uPressure, vL).x;
          805         float R = texture2D(uPressure, vR).x;
          806         float T = texture2D(uPressure, vT).x;
          807         float B = texture2D(uPressure, vB).x;
          808         vec2 velocity = texture2D(uVelocity, vUv).xy;
          809         velocity.xy -= vec2(R - L, T - B);
          810         gl_FragColor = vec4(velocity, 0.0, 1.0);
          811     }
          812 `);
          813 
          814 const blit = (() => {
          815     gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
          816     gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW);
          817     gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
          818     gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW);
          819     gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
          820     gl.enableVertexAttribArray(0);
          821 
          822     return (target, clear = false) => {
          823         if (target == null)
          824         {
          825             gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
          826             gl.bindFramebuffer(gl.FRAMEBUFFER, null);
          827         }
          828         else
          829         {
          830             gl.viewport(0, 0, target.width, target.height);
          831             gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);
          832         }
          833         if (clear)
          834         {
          835             gl.clearColor(0.0, 0.0, 0.0, 1.0);
          836             gl.clear(gl.COLOR_BUFFER_BIT);
          837         }
          838         // CHECK_FRAMEBUFFER_STATUS();
          839         gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
          840     }
          841 })();
          842 
          843 function CHECK_FRAMEBUFFER_STATUS () {
          844     let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
          845     if (status != gl.FRAMEBUFFER_COMPLETE)
          846         console.trace("Framebuffer error: " + status);
          847 }
          848 
          849 let dye;
          850 let velocity;
          851 let divergence;
          852 let curl;
          853 let pressure;
          854 let bloom;
          855 let bloomFramebuffers = [];
          856 let sunrays;
          857 let sunraysTemp;
          858 
          859 let ditheringTexture = createTextureAsync('LDR_LLL1_0.png');
          860 
          861 const blurProgram            = new Program(blurVertexShader, blurShader);
          862 const copyProgram            = new Program(baseVertexShader, copyShader);
          863 const clearProgram           = new Program(baseVertexShader, clearShader);
          864 const colorProgram           = new Program(baseVertexShader, colorShader);
          865 const checkerboardProgram    = new Program(baseVertexShader, checkerboardShader);
          866 const bloomPrefilterProgram  = new Program(baseVertexShader, bloomPrefilterShader);
          867 const bloomBlurProgram       = new Program(baseVertexShader, bloomBlurShader);
          868 const bloomFinalProgram      = new Program(baseVertexShader, bloomFinalShader);
          869 const sunraysMaskProgram     = new Program(baseVertexShader, sunraysMaskShader);
          870 const sunraysProgram         = new Program(baseVertexShader, sunraysShader);
          871 const splatProgram           = new Program(baseVertexShader, splatShader);
          872 const advectionProgram       = new Program(baseVertexShader, advectionShader);
          873 const divergenceProgram      = new Program(baseVertexShader, divergenceShader);
          874 const curlProgram            = new Program(baseVertexShader, curlShader);
          875 const vorticityProgram       = new Program(baseVertexShader, vorticityShader);
          876 const pressureProgram        = new Program(baseVertexShader, pressureShader);
          877 const gradienSubtractProgram = new Program(baseVertexShader, gradientSubtractShader);
          878 
          879 const displayMaterial = new Material(baseVertexShader, displayShaderSource);
          880 
          881 function initFramebuffers () {
          882     let simRes = getResolution(config.SIM_RESOLUTION);
          883     let dyeRes = getResolution(config.DYE_RESOLUTION);
          884 
          885     const texType = ext.halfFloatTexType;
          886     const rgba    = ext.formatRGBA;
          887     const rg      = ext.formatRG;
          888     const r       = ext.formatR;
          889     const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST;
          890 
          891     gl.disable(gl.BLEND);
          892 
          893     if (dye == null)
          894         dye = createDoubleFBO(dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering);
          895     else
          896         dye = resizeDoubleFBO(dye, dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering);
          897 
          898     if (velocity == null)
          899         velocity = createDoubleFBO(simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering);
          900     else
          901         velocity = resizeDoubleFBO(velocity, simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering);
          902 
          903     divergence = createFBO      (simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST);
          904     curl       = createFBO      (simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST);
          905     pressure   = createDoubleFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST);
          906 
          907     initBloomFramebuffers();
          908     initSunraysFramebuffers();
          909 }
          910 
          911 function initBloomFramebuffers () {
          912     let res = getResolution(config.BLOOM_RESOLUTION);
          913 
          914     const texType = ext.halfFloatTexType;
          915     const rgba = ext.formatRGBA;
          916     const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST;
          917 
          918     bloom = createFBO(res.width, res.height, rgba.internalFormat, rgba.format, texType, filtering);
          919 
          920     bloomFramebuffers.length = 0;
          921     for (let i = 0; i < config.BLOOM_ITERATIONS; i++)
          922     {
          923         let width = res.width >> (i + 1);
          924         let height = res.height >> (i + 1);
          925 
          926         if (width < 2 || height < 2) break;
          927 
          928         let fbo = createFBO(width, height, rgba.internalFormat, rgba.format, texType, filtering);
          929         bloomFramebuffers.push(fbo);
          930     }
          931 }
          932 
          933 function initSunraysFramebuffers () {
          934     let res = getResolution(config.SUNRAYS_RESOLUTION);
          935 
          936     const texType = ext.halfFloatTexType;
          937     const r = ext.formatR;
          938     const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST;
          939 
          940     sunrays     = createFBO(res.width, res.height, r.internalFormat, r.format, texType, filtering);
          941     sunraysTemp = createFBO(res.width, res.height, r.internalFormat, r.format, texType, filtering);
          942 }
          943 
          944 function createFBO (w, h, internalFormat, format, type, param) {
          945     gl.activeTexture(gl.TEXTURE0);
          946     let texture = gl.createTexture();
          947     gl.bindTexture(gl.TEXTURE_2D, texture);
          948     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param);
          949     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param);
          950     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
          951     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
          952     gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null);
          953 
          954     let fbo = gl.createFramebuffer();
          955     gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
          956     gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
          957     gl.viewport(0, 0, w, h);
          958     gl.clear(gl.COLOR_BUFFER_BIT);
          959 
          960     let texelSizeX = 1.0 / w;
          961     let texelSizeY = 1.0 / h;
          962 
          963     return {
          964         texture,
          965         fbo,
          966         width: w,
          967         height: h,
          968         texelSizeX,
          969         texelSizeY,
          970         attach (id) {
          971             gl.activeTexture(gl.TEXTURE0 + id);
          972             gl.bindTexture(gl.TEXTURE_2D, texture);
          973             return id;
          974         }
          975     };
          976 }
          977 
          978 function createDoubleFBO (w, h, internalFormat, format, type, param) {
          979     let fbo1 = createFBO(w, h, internalFormat, format, type, param);
          980     let fbo2 = createFBO(w, h, internalFormat, format, type, param);
          981 
          982     return {
          983         width: w,
          984         height: h,
          985         texelSizeX: fbo1.texelSizeX,
          986         texelSizeY: fbo1.texelSizeY,
          987         get read () {
          988             return fbo1;
          989         },
          990         set read (value) {
          991             fbo1 = value;
          992         },
          993         get write () {
          994             return fbo2;
          995         },
          996         set write (value) {
          997             fbo2 = value;
          998         },
          999         swap () {
         1000             let temp = fbo1;
         1001             fbo1 = fbo2;
         1002             fbo2 = temp;
         1003         }
         1004     }
         1005 }
         1006 
         1007 function resizeFBO (target, w, h, internalFormat, format, type, param) {
         1008     let newFBO = createFBO(w, h, internalFormat, format, type, param);
         1009     copyProgram.bind();
         1010     gl.uniform1i(copyProgram.uniforms.uTexture, target.attach(0));
         1011     blit(newFBO);
         1012     return newFBO;
         1013 }
         1014 
         1015 function resizeDoubleFBO (target, w, h, internalFormat, format, type, param) {
         1016     if (target.width == w && target.height == h)
         1017         return target;
         1018     target.read = resizeFBO(target.read, w, h, internalFormat, format, type, param);
         1019     target.write = createFBO(w, h, internalFormat, format, type, param);
         1020     target.width = w;
         1021     target.height = h;
         1022     target.texelSizeX = 1.0 / w;
         1023     target.texelSizeY = 1.0 / h;
         1024     return target;
         1025 }
         1026 
         1027 function createTextureAsync (url) {
         1028     let texture = gl.createTexture();
         1029     gl.bindTexture(gl.TEXTURE_2D, texture);
         1030     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
         1031     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
         1032     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
         1033     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
         1034     gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255]));
         1035 
         1036     let obj = {
         1037         texture,
         1038         width: 1,
         1039         height: 1,
         1040         attach (id) {
         1041             gl.activeTexture(gl.TEXTURE0 + id);
         1042             gl.bindTexture(gl.TEXTURE_2D, texture);
         1043             return id;
         1044         }
         1045     };
         1046 
         1047     let image = new Image();
         1048     image.onload = () => {
         1049         obj.width = image.width;
         1050         obj.height = image.height;
         1051         gl.bindTexture(gl.TEXTURE_2D, texture);
         1052         gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
         1053     };
         1054     image.src = url;
         1055 
         1056     return obj;
         1057 }
         1058 
         1059 function updateKeywords () {
         1060     let displayKeywords = [];
         1061     if (config.SHADING) displayKeywords.push("SHADING");
         1062     if (config.BLOOM) displayKeywords.push("BLOOM");
         1063     if (config.SUNRAYS) displayKeywords.push("SUNRAYS");
         1064     displayMaterial.setKeywords(displayKeywords);
         1065 }
         1066 
         1067 updateKeywords();
         1068 initFramebuffers();
         1069 multipleSplats(parseInt(Math.random() * 2) + 2);
         1070 
         1071 let lastUpdateTime = Date.now();
         1072 let colorUpdateTimer = 0.0;
         1073 updateFluid();
         1074 
         1075 function updateFluid () {
         1076     const dt = calcDeltaTime();
         1077     if (resizeCanvas())
         1078         initFramebuffers();
         1079     updateColors(dt);
         1080     applyInputs();
         1081     if (!config.PAUSED)
         1082         step(dt);
         1083     render(null);
         1084     requestAnimationFrame(updateFluid);
         1085 }
         1086 
         1087 function calcDeltaTime () {
         1088     let now = Date.now();
         1089     let dt = (now - lastUpdateTime) / 1000;
         1090     dt = Math.min(dt, 0.016666);
         1091     lastUpdateTime = now;
         1092     return dt;
         1093 }
         1094 
         1095 function resizeCanvas () {
         1096     let width = scaleByPixelRatio(canvas.clientWidth);
         1097     let height = scaleByPixelRatio(canvas.clientHeight);
         1098     if (canvas.width != width || canvas.height != height) {
         1099         canvas.width = width;
         1100         canvas.height = height;
         1101         return true;
         1102     }
         1103     return false;
         1104 }
         1105 
         1106 function updateColors (dt) {
         1107     if (!config.COLORFUL) return;
         1108 
         1109     colorUpdateTimer += dt * config.COLOR_UPDATE_SPEED;
         1110     if (colorUpdateTimer >= 1) {
         1111         colorUpdateTimer = wrap(colorUpdateTimer, 0, 1);
         1112         pointers.forEach(p => {
         1113             p.color = generateColor();
         1114         });
         1115     }
         1116 }
         1117 
         1118 function applyInputs () {
         1119     if (splatStack.length > 0)
         1120         multipleSplats(splatStack.pop());
         1121 
         1122     pointers.forEach(p => {
         1123         if (p.moved) {
         1124             p.moved = false;
         1125             splatPointer(p);
         1126         }
         1127     });
         1128 }
         1129 
         1130 function step (dt) {
         1131     gl.disable(gl.BLEND);
         1132 
         1133     curlProgram.bind();
         1134     gl.uniform2f(curlProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
         1135     gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.read.attach(0));
         1136     blit(curl);
         1137 
         1138     vorticityProgram.bind();
         1139     gl.uniform2f(vorticityProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
         1140     gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.read.attach(0));
         1141     gl.uniform1i(vorticityProgram.uniforms.uCurl, curl.attach(1));
         1142     gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL);
         1143     gl.uniform1f(vorticityProgram.uniforms.dt, dt);
         1144     blit(velocity.write);
         1145     velocity.swap();
         1146 
         1147     divergenceProgram.bind();
         1148     gl.uniform2f(divergenceProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
         1149     gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.read.attach(0));
         1150     blit(divergence);
         1151 
         1152     clearProgram.bind();
         1153     gl.uniform1i(clearProgram.uniforms.uTexture, pressure.read.attach(0));
         1154     gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE);
         1155     blit(pressure.write);
         1156     pressure.swap();
         1157 
         1158     pressureProgram.bind();
         1159     gl.uniform2f(pressureProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
         1160     gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0));
         1161     for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) {
         1162         gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1));
         1163         blit(pressure.write);
         1164         pressure.swap();
         1165     }
         1166 
         1167     gradienSubtractProgram.bind();
         1168     gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
         1169     gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.read.attach(0));
         1170     gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.read.attach(1));
         1171     blit(velocity.write);
         1172     velocity.swap();
         1173 
         1174     advectionProgram.bind();
         1175     gl.uniform2f(advectionProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
         1176     if (!ext.supportLinearFiltering)
         1177         gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, velocity.texelSizeX, velocity.texelSizeY);
         1178     let velocityId = velocity.read.attach(0);
         1179     gl.uniform1i(advectionProgram.uniforms.uVelocity, velocityId);
         1180     gl.uniform1i(advectionProgram.uniforms.uSource, velocityId);
         1181     gl.uniform1f(advectionProgram.uniforms.dt, dt);
         1182     gl.uniform1f(advectionProgram.uniforms.dissipation, config.VELOCITY_DISSIPATION);
         1183     blit(velocity.write);
         1184     velocity.swap();
         1185 
         1186     if (!ext.supportLinearFiltering)
         1187         gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, dye.texelSizeX, dye.texelSizeY);
         1188     gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.read.attach(0));
         1189     gl.uniform1i(advectionProgram.uniforms.uSource, dye.read.attach(1));
         1190     gl.uniform1f(advectionProgram.uniforms.dissipation, config.DENSITY_DISSIPATION);
         1191     blit(dye.write);
         1192     dye.swap();
         1193 }
         1194 
         1195 function render (target) {
         1196     if (config.BLOOM)
         1197         applyBloom(dye.read, bloom);
         1198     if (config.SUNRAYS) {
         1199         applySunrays(dye.read, dye.write, sunrays);
         1200         blur(sunrays, sunraysTemp, 1);
         1201     }
         1202 
         1203     if (target == null || !config.TRANSPARENT) {
         1204         gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
         1205         gl.enable(gl.BLEND);
         1206     }
         1207     else {
         1208         gl.disable(gl.BLEND);
         1209     }
         1210 
         1211     if (!config.TRANSPARENT)
         1212         drawColor(target, normalizeColor(config.BACK_COLOR));
         1213     if (target == null && config.TRANSPARENT)
         1214         drawCheckerboard(target);
         1215     drawDisplay(target);
         1216 }
         1217 
         1218 function drawColor (target, color) {
         1219     colorProgram.bind();
         1220     gl.uniform4f(colorProgram.uniforms.color, color.r, color.g, color.b, 1);
         1221     blit(target);
         1222 }
         1223 
         1224 function drawCheckerboard (target) {
         1225     checkerboardProgram.bind();
         1226     gl.uniform1f(checkerboardProgram.uniforms.aspectRatio, canvas.width / canvas.height);
         1227     blit(target);
         1228 }
         1229 
         1230 function drawDisplay (target) {
         1231     let width = target == null ? gl.drawingBufferWidth : target.width;
         1232     let height = target == null ? gl.drawingBufferHeight : target.height;
         1233 
         1234     displayMaterial.bind();
         1235     if (config.SHADING)
         1236         gl.uniform2f(displayMaterial.uniforms.texelSize, 1.0 / width, 1.0 / height);
         1237     gl.uniform1i(displayMaterial.uniforms.uTexture, dye.read.attach(0));
         1238     if (config.BLOOM) {
         1239         gl.uniform1i(displayMaterial.uniforms.uBloom, bloom.attach(1));
         1240         gl.uniform1i(displayMaterial.uniforms.uDithering, ditheringTexture.attach(2));
         1241         let scale = getTextureScale(ditheringTexture, width, height);
         1242         gl.uniform2f(displayMaterial.uniforms.ditherScale, scale.x, scale.y);
         1243     }
         1244     if (config.SUNRAYS)
         1245         gl.uniform1i(displayMaterial.uniforms.uSunrays, sunrays.attach(3));
         1246     blit(target);
         1247 }
         1248 
         1249 function applyBloom (source, destination) {
         1250     if (bloomFramebuffers.length < 2)
         1251         return;
         1252 
         1253     let last = destination;
         1254 
         1255     gl.disable(gl.BLEND);
         1256     bloomPrefilterProgram.bind();
         1257     let knee = config.BLOOM_THRESHOLD * config.BLOOM_SOFT_KNEE + 0.0001;
         1258     let curve0 = config.BLOOM_THRESHOLD - knee;
         1259     let curve1 = knee * 2;
         1260     let curve2 = 0.25 / knee;
         1261     gl.uniform3f(bloomPrefilterProgram.uniforms.curve, curve0, curve1, curve2);
         1262     gl.uniform1f(bloomPrefilterProgram.uniforms.threshold, config.BLOOM_THRESHOLD);
         1263     gl.uniform1i(bloomPrefilterProgram.uniforms.uTexture, source.attach(0));
         1264     blit(last);
         1265 
         1266     bloomBlurProgram.bind();
         1267     for (let i = 0; i < bloomFramebuffers.length; i++) {
         1268         let dest = bloomFramebuffers[i];
         1269         gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
         1270         gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0));
         1271         blit(dest);
         1272         last = dest;
         1273     }
         1274 
         1275     gl.blendFunc(gl.ONE, gl.ONE);
         1276     gl.enable(gl.BLEND);
         1277 
         1278     for (let i = bloomFramebuffers.length - 2; i >= 0; i--) {
         1279         let baseTex = bloomFramebuffers[i];
         1280         gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
         1281         gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0));
         1282         gl.viewport(0, 0, baseTex.width, baseTex.height);
         1283         blit(baseTex);
         1284         last = baseTex;
         1285     }
         1286 
         1287     gl.disable(gl.BLEND);
         1288     bloomFinalProgram.bind();
         1289     gl.uniform2f(bloomFinalProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
         1290     gl.uniform1i(bloomFinalProgram.uniforms.uTexture, last.attach(0));
         1291     gl.uniform1f(bloomFinalProgram.uniforms.intensity, config.BLOOM_INTENSITY);
         1292     blit(destination);
         1293 }
         1294 
         1295 function applySunrays (source, mask, destination) {
         1296     gl.disable(gl.BLEND);
         1297     sunraysMaskProgram.bind();
         1298     gl.uniform1i(sunraysMaskProgram.uniforms.uTexture, source.attach(0));
         1299     blit(mask);
         1300 
         1301     sunraysProgram.bind();
         1302     gl.uniform1f(sunraysProgram.uniforms.weight, config.SUNRAYS_WEIGHT);
         1303     gl.uniform1i(sunraysProgram.uniforms.uTexture, mask.attach(0));
         1304     blit(destination);
         1305 }
         1306 
         1307 function blur (target, temp, iterations) {
         1308     blurProgram.bind();
         1309     for (let i = 0; i < iterations; i++) {
         1310         gl.uniform2f(blurProgram.uniforms.texelSize, target.texelSizeX, 0.0);
         1311         gl.uniform1i(blurProgram.uniforms.uTexture, target.attach(0));
         1312         blit(temp);
         1313 
         1314         gl.uniform2f(blurProgram.uniforms.texelSize, 0.0, target.texelSizeY);
         1315         gl.uniform1i(blurProgram.uniforms.uTexture, temp.attach(0));
         1316         blit(target);
         1317     }
         1318 }
         1319 
         1320 function splatPointer (pointer) {
         1321     let dx = pointer.deltaX * config.SPLAT_FORCE;
         1322     let dy = pointer.deltaY * config.SPLAT_FORCE;
         1323     splat(pointer.texcoordX, pointer.texcoordY, dx, dy, pointer.color);
         1324 }
         1325 
         1326 function multipleSplats (amount) {
         1327     for (let i = 0; i < amount; i++) {
         1328         const color = generateColor();
         1329         color.r *= 10.0;
         1330         color.g *= 10.0;
         1331         color.b *= 10.0;
         1332         const x = Math.random();
         1333         const y = Math.random();
         1334         const dx = 1000 * (Math.random() - 0.5);
         1335         const dy = 1000 * (Math.random() - 0.5);
         1336         splat(x, y, dx, dy, color);
         1337     }
         1338 }
         1339 
         1340 function splat (x, y, dx, dy, color) {
         1341     splatProgram.bind();
         1342     gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0));
         1343     gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height);
         1344     gl.uniform2f(splatProgram.uniforms.point, x, y);
         1345     gl.uniform3f(splatProgram.uniforms.color, dx, dy, 0.0);
         1346     gl.uniform1f(splatProgram.uniforms.radius, correctRadius(config.SPLAT_RADIUS / 100.0));
         1347     blit(velocity.write);
         1348     velocity.swap();
         1349 
         1350     gl.uniform1i(splatProgram.uniforms.uTarget, dye.read.attach(0));
         1351     gl.uniform3f(splatProgram.uniforms.color, color.r, color.g, color.b);
         1352     blit(dye.write);
         1353     dye.swap();
         1354 }
         1355 
         1356 function correctRadius (radius) {
         1357     let aspectRatio = canvas.width / canvas.height;
         1358     if (aspectRatio > 1)
         1359         radius *= aspectRatio;
         1360     return radius;
         1361 }
         1362 
         1363 canvas.addEventListener('mousedown', e => {
         1364     let posX = scaleByPixelRatio(e.pageX);
         1365     let posY = scaleByPixelRatio(e.pageY);
         1366     let pointer = pointers.find(p => p.id == -1);
         1367     if (pointer == null)
         1368         pointer = new pointerPrototype();
         1369     updatePointerDownData(pointer, -1, posX, posY);
         1370 });
         1371 
         1372 setTimeout(() => {
         1373     window.addEventListener('mousemove', e => {
         1374         let posX = scaleByPixelRatio(e.pageX);
         1375         let posY = scaleByPixelRatio(e.pageY);
         1376         updatePointerMoveData(pointers[0], posX, posY)
         1377     })
         1378   }, 1000)
         1379 
         1380 window.addEventListener('mouseup', () => {
         1381     updatePointerUpData(pointers[0]);
         1382 },{passive: true});
         1383 
         1384 canvas.addEventListener('touchstart', e => {
         1385     e.preventDefault();
         1386     const touches = e.targetTouches;
         1387     while (touches.length >= pointers.length)
         1388         pointers.push(new pointerPrototype());
         1389     for (let i = 0; i < touches.length; i++) {
         1390         let posX = scaleByPixelRatio(touches[i].pageX);
         1391         let posY = scaleByPixelRatio(touches[i].pageY);
         1392         updatePointerDownData(pointers[i + 1], touches[i].identifier, posX, posY);
         1393     }
         1394 },{passive: true});
         1395 
         1396 canvas.addEventListener('touchmove', e => {
         1397     e.preventDefault();
         1398     const touches = e.targetTouches;
         1399     for (let i = 0; i < touches.length; i++) {
         1400         let pointer = pointers[i + 1];
         1401         if (!pointer.down) continue;
         1402         let posX = scaleByPixelRatio(touches[i].pageX);
         1403         let posY = scaleByPixelRatio(touches[i].pageY);
         1404         updatePointerMoveData(pointer, posX, posY);
         1405     }
         1406 },{passive: true});
         1407 
         1408 window.addEventListener('touchend', e => {
         1409     const touches = e.changedTouches;
         1410     for (let i = 0; i < touches.length; i++)
         1411     {
         1412         let pointer = pointers.find(p => p.id == touches[i].identifier);
         1413         if (pointer == null) continue;
         1414         updatePointerUpData(pointer);
         1415     }
         1416 },{passive: true});
         1417 
         1418 /*
         1419 window.addEventListener('keydown', e => {
         1420     if (e.code === 'KeyP')
         1421         config.PAUSED = !config.PAUSED;
         1422     if (e.key === ' ')
         1423         splatStack.push(parseInt(Math.random() * 20) + 5);
         1424 });
         1425 */
         1426 
         1427 function updatePointerDownData (pointer, id, posX, posY) {
         1428     pointer.id = id;
         1429     pointer.down = true;
         1430     pointer.moved = false;
         1431     pointer.texcoordX = posX / canvas.width;
         1432     pointer.texcoordY = 1.0 - posY / canvas.height;
         1433     pointer.prevTexcoordX = pointer.texcoordX;
         1434     pointer.prevTexcoordY = pointer.texcoordY;
         1435     pointer.deltaX = 0;
         1436     pointer.deltaY = 0;
         1437     pointer.color = generateColor();
         1438 }
         1439 
         1440 function updatePointerMoveData (pointer, posX, posY) {
         1441       pointer.prevTexcoordX = pointer.texcoordX
         1442       pointer.prevTexcoordY = pointer.texcoordY
         1443       pointer.texcoordX = posX / canvas.width
         1444       pointer.texcoordY = 1.0 - posY / canvas.height
         1445       pointer.deltaX = correctDeltaX(pointer.texcoordX - pointer.prevTexcoordX)
         1446       pointer.deltaY = correctDeltaY(pointer.texcoordY - pointer.prevTexcoordY)
         1447       pointer.moved = Math.abs(pointer.deltaX) > 0 || Math.abs(pointer.deltaY) > 0
         1448 }
         1449 
         1450 function updatePointerUpData (pointer) {
         1451     pointer.down = false;
         1452 }
         1453 
         1454 function correctDeltaX (delta) {
         1455     let aspectRatio = canvas.width / canvas.height;
         1456     if (aspectRatio < 1) delta *= aspectRatio;
         1457     return delta;
         1458 }
         1459 
         1460 function correctDeltaY (delta) {
         1461     let aspectRatio = canvas.width / canvas.height;
         1462     if (aspectRatio > 1) delta /= aspectRatio;
         1463     return delta;
         1464 }
         1465 
         1466 function generateColor () {
         1467     let c = HSVtoRGB(Math.random(), 1.0, 1.0);
         1468     c.r *= 0.15;
         1469     c.g *= 0.15;
         1470     c.b *= 0.15;
         1471     return c;
         1472 }
         1473 
         1474 function HSVtoRGB (h, s, v) {
         1475     let r, g, b, i, f, p, q, t;
         1476     i = Math.floor(h * 6);
         1477     f = h * 6 - i;
         1478     p = v * (1 - s);
         1479     q = v * (1 - f * s);
         1480     t = v * (1 - (1 - f) * s);
         1481 
         1482     switch (i % 6) {
         1483         case 0: r = v, g = t, b = p; break;
         1484         case 1: r = q, g = v, b = p; break;
         1485         case 2: r = p, g = v, b = t; break;
         1486         case 3: r = p, g = q, b = v; break;
         1487         case 4: r = t, g = p, b = v; break;
         1488         case 5: r = v, g = p, b = q; break;
         1489     }
         1490 
         1491     return {
         1492         r,
         1493         g,
         1494         b
         1495     };
         1496 }
         1497 
         1498 function normalizeColor (input) {
         1499     let output = {
         1500         r: input.r / 255,
         1501         g: input.g / 255,
         1502         b: input.b / 255
         1503     };
         1504     return output;
         1505 }
         1506 
         1507 function wrap (value, min, max) {
         1508     let range = max - min;
         1509     if (range == 0) return min;
         1510     return (value - min) % range + min;
         1511 }
         1512 
         1513 function getResolution (resolution) {
         1514     let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight;
         1515     if (aspectRatio < 1)
         1516         aspectRatio = 1.0 / aspectRatio;
         1517 
         1518     let min = Math.round(resolution);
         1519     let max = Math.round(resolution * aspectRatio);
         1520 
         1521     if (gl.drawingBufferWidth > gl.drawingBufferHeight)
         1522         return { width: max, height: min };
         1523     else
         1524         return { width: min, height: max };
         1525 }
         1526 
         1527 function getTextureScale (texture, width, height) {
         1528     return {
         1529         x: width / texture.width,
         1530         y: height / texture.height
         1531     };
         1532 }
         1533 
         1534 function scaleByPixelRatio (input) {
         1535     let pixelRatio = window.devicePixelRatio || 1;
         1536     return Math.floor(input * pixelRatio);
         1537 }
         1538 
         1539 function hashCode (s) {
         1540     if (s.length == 0) return 0;
         1541     let hash = 0;
         1542     for (let i = 0; i < s.length; i++) {
         1543         hash = (hash << 5) - hash + s.charCodeAt(i);
         1544         hash |= 0; // Convert to 32bit integer
         1545     }
         1546     return hash;
         1547 };