/ script.js
script.js
   1  window.addEventListener('load', function() {
   2    initFluid();
   3  });
   4  
   5  function initFluid() {
   6  
   7  const canvas = document.getElementById('fluid');
   8  resizeCanvas();
   9  
  10  
  11    
  12  let config = {
  13      SIM_RESOLUTION: 512,
  14      DYE_RESOLUTION: 512,
  15      CAPTURE_RESOLUTION: 512,
  16      DENSITY_DISSIPATION: 2.1, //3.5
  17      VELOCITY_DISSIPATION: 2,
  18      PRESSURE: 10.1, //0.1
  19      PRESSURE_ITERATIONS: 120,
  20      CURL: 12*Math.random(),
  21      SPLAT_RADIUS: 0.305,
  22      SPLAT_FORCE: 10,
  23      SHADING: true,
  24      COLOR_UPDATE_SPEED: 0.006,
  25      BACK_COLOR: { r: 0, g: 0, b: 0 },
  26  }
  27  
  28  function pointerPrototype () {
  29      this.id = -1;
  30      this.texcoordX = 0;
  31      this.texcoordY = 0;
  32      this.prevTexcoordX = 0;
  33      this.prevTexcoordY = 0;
  34      this.deltaX = 0;
  35      this.deltaY = 0;
  36      this.down = false;
  37      this.moved = false;
  38      this.color = [0, 0, 0];
  39  }
  40  
  41  let pointers = [];
  42  pointers.push(new pointerPrototype());
  43  
  44  const { gl, ext } = getWebGLContext(canvas);
  45  
  46  if (!ext.supportLinearFiltering) {
  47      config.DYE_RESOLUTION = 512;
  48      config.SHADING = false;
  49  }
  50  
  51  function getWebGLContext (canvas) {
  52      // canvas.style.backgroundImage = "url('/img/bg.png')";
  53  
  54  
  55   // Create a video element
  56   const video = document.createElement('video');
  57  //  video.src = '/img/bgsv.mp4';
  58   video.autoplay = true;
  59   video.loop = true;
  60   video.muted = true;
  61   video.style.position = 'absolute';
  62   video.style.width = '100%';
  63   video.style.height = '100%';
  64   video.style.objectFit = 'cover';
  65  
  66   // Append the video element to the canvas
  67   canvas.parentNode.insertBefore(video, canvas);
  68  
  69  
  70  
  71      const params = { alpha: true, depth: false, stencil: false, antialias: false, preserveDrawingBuffer: false };
  72  
  73      let gl = canvas.getContext('webgl2', params);
  74      const isWebGL2 = !!gl;
  75      if (!isWebGL2)
  76          gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params);
  77  
  78      let halfFloat;
  79      let supportLinearFiltering;
  80      if (isWebGL2) {
  81          gl.getExtension('EXT_color_buffer_float');
  82          supportLinearFiltering = gl.getExtension('OES_texture_float_linear');
  83      } else {
  84          halfFloat = gl.getExtension('OES_texture_half_float');
  85          supportLinearFiltering = gl.getExtension('OES_texture_half_float_linear');
  86      }
  87  
  88      gl.clearColor(0.0, 0.0, 0.0, 1.0);
  89  
  90      const halfFloatTexType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES;
  91      let formatRGBA;
  92      let formatRG;
  93      let formatR;
  94  
  95      if (isWebGL2)
  96      {
  97          formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType);
  98          formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType);
  99          formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType);
 100      }
 101      else
 102      {
 103          formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
 104          formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
 105          formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
 106      }
 107  
 108      return {
 109          gl,
 110          ext: {
 111              formatRGBA,
 112              formatRG,
 113              formatR,
 114              halfFloatTexType,
 115              supportLinearFiltering
 116          }
 117      };
 118  }
 119  
 120  function getSupportedFormat (gl, internalFormat, format, type)
 121  {
 122      if (!supportRenderTextureFormat(gl, internalFormat, format, type))
 123      {
 124          switch (internalFormat)
 125          {
 126              case gl.R16F:
 127                  return getSupportedFormat(gl, gl.RG16F, gl.RG, type);
 128              case gl.RG16F:
 129                  return getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, type);
 130              default:
 131                  return null;
 132          }
 133      }
 134  
 135      return {
 136          internalFormat,
 137          format
 138      }
 139  }
 140  
 141  function supportRenderTextureFormat (gl, internalFormat, format, type) {
 142      let texture = gl.createTexture();
 143      gl.bindTexture(gl.TEXTURE_2D, texture);
 144      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
 145      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
 146      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
 147      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
 148      gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 4, 4, 0, format, type, null);
 149  
 150      let fbo = gl.createFramebuffer();
 151      gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
 152      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
 153  
 154      let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
 155      return status == gl.FRAMEBUFFER_COMPLETE;
 156  }
 157  
 158  class Material {
 159      constructor (vertexShader, fragmentShaderSource) {
 160          this.vertexShader = vertexShader;
 161          this.fragmentShaderSource = fragmentShaderSource;
 162          this.programs = [];
 163          this.activeProgram = null;
 164          this.uniforms = [];
 165      }
 166  
 167      setKeywords (keywords) {
 168          let hash = 0;
 169          for (let i = 0; i < keywords.length; i++)
 170              hash += hashCode(keywords[i]);
 171  
 172          let program = this.programs[hash];
 173          if (program == null)
 174          {
 175              let fragmentShader = compileShader(gl.FRAGMENT_SHADER, this.fragmentShaderSource, keywords);
 176              program = createProgram(this.vertexShader, fragmentShader);
 177              this.programs[hash] = program;
 178          }
 179  
 180          if (program == this.activeProgram) return;
 181  
 182          this.uniforms = getUniforms(program);
 183          this.activeProgram = program;
 184      }
 185  
 186      bind () {
 187          gl.useProgram(this.activeProgram);
 188      }
 189  }
 190  
 191  class Program {
 192      constructor (vertexShader, fragmentShader) {
 193          this.uniforms = {};
 194          this.program = createProgram(vertexShader, fragmentShader);
 195          this.uniforms = getUniforms(this.program);
 196      }
 197  
 198      bind () {
 199          gl.useProgram(this.program);
 200      }
 201  }
 202  
 203  function createProgram (vertexShader, fragmentShader) {
 204      let program = gl.createProgram();
 205      gl.attachShader(program, vertexShader);
 206      gl.attachShader(program, fragmentShader);
 207      gl.linkProgram(program);
 208  
 209      if (!gl.getProgramParameter(program, gl.LINK_STATUS))
 210          console.trace(gl.getProgramInfoLog(program));
 211  
 212      return program;
 213  }
 214  
 215  function getUniforms (program) {
 216      let uniforms = [];
 217      let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
 218      for (let i = 0; i < uniformCount; i++) {
 219          let uniformName = gl.getActiveUniform(program, i).name;
 220          uniforms[uniformName] = gl.getUniformLocation(program, uniformName);
 221      }
 222      return uniforms;
 223  }
 224  
 225  function compileShader (type, source, keywords) {
 226      source = addKeywords(source, keywords);
 227  
 228      const shader = gl.createShader(type);
 229      gl.shaderSource(shader, source);
 230      gl.compileShader(shader);
 231  
 232      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
 233          console.trace(gl.getShaderInfoLog(shader));
 234          
 235      return shader;
 236  
 237  };
 238  
 239  function addKeywords (source, keywords) {
 240      if (keywords == null) return source;
 241      let keywordsString = '';
 242      keywords.forEach(keyword => {
 243          keywordsString += '#define ' + keyword + '\n';
 244      });
 245  
 246      return keywordsString + source;
 247  }
 248  
 249  const baseVertexShader = compileShader(gl.VERTEX_SHADER, `
 250      precision highp float;
 251  
 252      attribute vec2 aPosition;
 253      varying vec2 vUv;
 254      varying vec2 vL;
 255      varying vec2 vR;
 256      varying vec2 vT;
 257      varying vec2 vB;
 258      uniform vec2 texelSize;
 259  
 260      void main () {
 261          vUv = aPosition * 0.5 + 0.5;
 262          vL = vUv - vec2(texelSize.x, 0.0);
 263          vR = vUv + vec2(texelSize.x, 0.0);
 264          vT = vUv + vec2(0.0, texelSize.y);
 265          vB = vUv - vec2(0.0, texelSize.y);
 266          gl_Position = vec4(aPosition, 0.0, 1.0);
 267      }
 268  `);
 269  
 270  const blurVertexShader = compileShader(gl.VERTEX_SHADER, `
 271      precision highp float;
 272  
 273      attribute vec2 aPosition;
 274      varying vec2 vUv;
 275      varying vec2 vL;
 276      varying vec2 vR;
 277      uniform vec2 texelSize;
 278  
 279      void main () {
 280          vUv = aPosition * 0.5 + 0.5;
 281          float offset = 1.33333333;
 282          vL = vUv - texelSize * offset;
 283          vR = vUv + texelSize * offset;
 284          gl_Position = vec4(aPosition, 0.0, 1.0);
 285      }
 286  `);
 287  
 288  const blurShader = compileShader(gl.FRAGMENT_SHADER, `
 289      precision mediump float;
 290      precision mediump sampler2D;
 291  
 292      varying vec2 vUv;
 293      varying vec2 vL;
 294      varying vec2 vR;
 295      uniform sampler2D uTexture;
 296  
 297      void main () {
 298          vec4 sum = texture2D(uTexture, vUv) * 0.29411764;
 299          sum += texture2D(uTexture, vL) * 0.35294117;
 300          sum += texture2D(uTexture, vR) * 0.35294117;
 301          gl_FragColor = sum;
 302      }
 303  `);
 304  
 305  const copyShader = compileShader(gl.FRAGMENT_SHADER, `
 306      precision mediump float;
 307      precision mediump sampler2D;
 308  
 309      varying highp vec2 vUv;
 310      uniform sampler2D uTexture;
 311  
 312      void main () {
 313          gl_FragColor = texture2D(uTexture, vUv);
 314      }
 315  `);
 316  
 317  const clearShader = compileShader(gl.FRAGMENT_SHADER, `
 318      precision mediump float;
 319      precision mediump sampler2D;
 320  
 321      varying highp vec2 vUv;
 322      uniform sampler2D uTexture;
 323      uniform float value;
 324  
 325      void main () {
 326          gl_FragColor = value * texture2D(uTexture, vUv);
 327      }
 328  `);
 329  
 330  const colorShader = compileShader(gl.FRAGMENT_SHADER, `
 331      precision mediump float;
 332  
 333      uniform vec4 color;
 334  
 335      void main () {
 336          gl_FragColor = color;
 337      }
 338  `);
 339  
 340  
 341  const displayShaderSource = `
 342      precision highp float;
 343      precision highp sampler2D;
 344  
 345      varying vec2 vUv;
 346      varying vec2 vL;
 347      varying vec2 vR;
 348      varying vec2 vT;
 349      varying vec2 vB;
 350      uniform sampler2D uTexture;
 351      uniform sampler2D uDithering;
 352      uniform vec2 ditherScale;
 353      uniform vec2 texelSize;
 354  
 355      vec3 linearToGamma (vec3 color) {
 356          color = max(color, vec3(0));
 357          return max(1.055 * pow(color, vec3(0.416666667)) - 0.055, vec3(0));
 358      }
 359  
 360      void main () {
 361          vec3 c = texture2D(uTexture, vUv).rgb;
 362  
 363      #ifdef SHADING
 364          vec3 lc = texture2D(uTexture, vL).rgb;
 365          vec3 rc = texture2D(uTexture, vR).rgb;
 366          vec3 tc = texture2D(uTexture, vT).rgb;
 367          vec3 bc = texture2D(uTexture, vB).rgb;
 368  
 369          float dx = length(rc) - length(lc);
 370          float dy = length(tc) - length(bc);
 371  
 372          vec3 n = normalize(vec3(dx, dy, length(texelSize)));
 373          vec3 l = vec3(0.0, 0.0, 1.0);
 374  
 375          float diffuse = clamp(dot(n, l) + 0.7, 0.7, 1.0);
 376          c *= diffuse;
 377      #endif
 378  
 379          float a = max(c.r, max(c.g, c.b));
 380          gl_FragColor = vec4(c, a);
 381      }
 382  `;
 383  
 384  const splatShader = compileShader(gl.FRAGMENT_SHADER, `
 385      precision highp float;
 386      precision highp sampler2D;
 387  
 388      varying vec2 vUv;
 389      uniform sampler2D uTarget;
 390      uniform float aspectRatio;
 391      uniform vec3 color;
 392      uniform vec2 point;
 393      uniform float radius;
 394  
 395      void main () {
 396          vec2 p = vUv - point.xy;
 397          p.x *= aspectRatio;
 398          vec3 splat = exp(-dot(p, p) / radius) * color;
 399          vec3 base = texture2D(uTarget, vUv).xyz;
 400          gl_FragColor = vec4(base + splat, 0.97); /*==================================*/
 401      }
 402  `);
 403  
 404  const advectionShader = compileShader(gl.FRAGMENT_SHADER, `
 405      precision highp float;
 406      precision highp sampler2D;
 407  
 408      varying vec2 vUv;
 409      uniform sampler2D uVelocity;
 410      uniform sampler2D uSource;
 411      uniform vec2 texelSize;
 412      uniform vec2 dyeTexelSize;
 413      uniform float dt;
 414      uniform float dissipation;
 415  
 416      vec4 bilerp (sampler2D sam, vec2 uv, vec2 tsize) {
 417          vec2 st = uv / tsize - 0.5;
 418  
 419          vec2 iuv = floor(st);
 420          vec2 fuv = fract(st);
 421  
 422          vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize);
 423          vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize);
 424          vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize);
 425          vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize);
 426  
 427          return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y);
 428      }
 429  
 430      void main () {
 431      #ifdef MANUAL_FILTERING
 432          vec2 coord = vUv - dt * bilerp(uVelocity, vUv, texelSize).xy * texelSize;
 433          vec4 result = bilerp(uSource, coord, dyeTexelSize);
 434      #else
 435          vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize;
 436          vec4 result = texture2D(uSource, coord);
 437      #endif
 438          float decay = 1.0 + dissipation * dt;
 439          gl_FragColor = result / decay;
 440      }`,
 441      ext.supportLinearFiltering ? null : ['MANUAL_FILTERING']
 442  );
 443  
 444  const divergenceShader = compileShader(gl.FRAGMENT_SHADER, `
 445      precision mediump float;
 446      precision mediump sampler2D;
 447  
 448      varying highp vec2 vUv;
 449      varying highp vec2 vL;
 450      varying highp vec2 vR;
 451      varying highp vec2 vT;
 452      varying highp vec2 vB;
 453      uniform sampler2D uVelocity;
 454  
 455      void main () {
 456          float L = texture2D(uVelocity, vL).x;
 457          float R = texture2D(uVelocity, vR).x;
 458          float T = texture2D(uVelocity, vT).y;
 459          float B = texture2D(uVelocity, vB).y;
 460  
 461          vec2 C = texture2D(uVelocity, vUv).xy;
 462          if (vL.x < 0.0) { L = -C.x; }
 463          if (vR.x > 1.0) { R = -C.x; }
 464          if (vT.y > 1.0) { T = -C.y; }
 465          if (vB.y < 0.0) { B = -C.y; }
 466  
 467          float div = 0.5 * (R - L + T - B);
 468          gl_FragColor = vec4(div, 0.0, 0.0, 1.0);
 469      }
 470  `);
 471  
 472  const curlShader = compileShader(gl.FRAGMENT_SHADER, `
 473      precision mediump float;
 474      precision mediump sampler2D;
 475  
 476      varying highp vec2 vUv;
 477      varying highp vec2 vL;
 478      varying highp vec2 vR;
 479      varying highp vec2 vT;
 480      varying highp vec2 vB;
 481      uniform sampler2D uVelocity;
 482  
 483      void main () {
 484          float L = texture2D(uVelocity, vL).y;
 485          float R = texture2D(uVelocity, vR).y;
 486          float T = texture2D(uVelocity, vT).x;
 487          float B = texture2D(uVelocity, vB).x;
 488          float vorticity = R - L - T + B;
 489          gl_FragColor = vec4(0.5 * vorticity, 0.0, 0.0, 1.0);
 490      }
 491  `);
 492  
 493  const vorticityShader = compileShader(gl.FRAGMENT_SHADER, `
 494      precision highp float;
 495      precision highp sampler2D;
 496  
 497      varying vec2 vUv;
 498      varying vec2 vL;
 499      varying vec2 vR;
 500      varying vec2 vT;
 501      varying vec2 vB;
 502      uniform sampler2D uVelocity;
 503      uniform sampler2D uCurl;
 504      uniform float curl;
 505      uniform float dt;
 506  
 507      void main () {
 508          float L = texture2D(uCurl, vL).x;
 509          float R = texture2D(uCurl, vR).x;
 510          float T = texture2D(uCurl, vT).x;
 511          float B = texture2D(uCurl, vB).x;
 512          float C = texture2D(uCurl, vUv).x;
 513  
 514          vec2 force = 0.5 * vec2(abs(T) - abs(B), abs(R) - abs(L));
 515          force /= length(force) + 0.0001;
 516          force *= curl * C;
 517          force.y *= -1.0;
 518  
 519          vec2 velocity = texture2D(uVelocity, vUv).xy;
 520          velocity += force * dt;
 521          velocity = min(max(velocity, -1000.0), 1000.0);
 522          gl_FragColor = vec4(velocity, 0.0, 1.0);
 523      }
 524  `);
 525  
 526  const pressureShader = compileShader(gl.FRAGMENT_SHADER, `
 527      precision mediump float;
 528      precision mediump sampler2D;
 529  
 530      varying highp vec2 vUv;
 531      varying highp vec2 vL;
 532      varying highp vec2 vR;
 533      varying highp vec2 vT;
 534      varying highp vec2 vB;
 535      uniform sampler2D uPressure;
 536      uniform sampler2D uDivergence;
 537  
 538      void main () {
 539          float L = texture2D(uPressure, vL).x;
 540          float R = texture2D(uPressure, vR).x;
 541          float T = texture2D(uPressure, vT).x;
 542          float B = texture2D(uPressure, vB).x;
 543          float C = texture2D(uPressure, vUv).x;
 544          float divergence = texture2D(uDivergence, vUv).x;
 545          float pressure = (L + R + B + T - divergence) * 0.25;
 546          gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0);
 547      }
 548  `);
 549  
 550  const gradientSubtractShader = compileShader(gl.FRAGMENT_SHADER, `
 551      precision mediump float;
 552      precision mediump sampler2D;
 553  
 554      varying highp vec2 vUv;
 555      varying highp vec2 vL;
 556      varying highp vec2 vR;
 557      varying highp vec2 vT;
 558      varying highp vec2 vB;
 559      uniform sampler2D uPressure;
 560      uniform sampler2D uVelocity;
 561  
 562      void main () {
 563          float L = texture2D(uPressure, vL).x;
 564          float R = texture2D(uPressure, vR).x;
 565          float T = texture2D(uPressure, vT).x;
 566          float B = texture2D(uPressure, vB).x;
 567          vec2 velocity = texture2D(uVelocity, vUv).xy;
 568          velocity.xy -= vec2(R - L, T - B);
 569          gl_FragColor = vec4(velocity, 0.0, 1.0);
 570      }
 571  `);
 572  
 573  const blit = (() => {
 574      gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
 575      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW);
 576      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
 577      gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW);
 578      gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
 579      gl.enableVertexAttribArray(0);
 580  
 581      return (target, clear = false) => {
 582          if (target == null)
 583          {
 584              gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
 585              gl.bindFramebuffer(gl.FRAMEBUFFER, null);
 586          }
 587          else
 588          {
 589              gl.viewport(0, 0, target.width, target.height);
 590              gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);
 591          }
 592          if (clear)
 593          {
 594              gl.clearColor(0.0, 0.0, 0.0, 1.0);
 595              gl.clear(gl.COLOR_BUFFER_BIT);
 596          }
 597          // CHECK_FRAMEBUFFER_STATUS();
 598          gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
 599      }
 600  })();
 601  
 602  function CHECK_FRAMEBUFFER_STATUS () {
 603      let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
 604      if (status != gl.FRAMEBUFFER_COMPLETE)
 605          console.trace("Framebuffer error: " + status);
 606  }
 607  
 608  let dye;
 609  let velocity;
 610  let divergence;
 611  let curl;
 612  let pressure;
 613  let ditheringTexture = createTextureAsync('/img/bg.png');
 614  
 615  const blurProgram            = new Program(blurVertexShader, blurShader);
 616  const copyProgram            = new Program(baseVertexShader, copyShader);
 617  const clearProgram           = new Program(baseVertexShader, clearShader);
 618  const colorProgram           = new Program(baseVertexShader, colorShader);
 619  const splatProgram           = new Program(baseVertexShader, splatShader);
 620  const advectionProgram       = new Program(baseVertexShader, advectionShader);
 621  const divergenceProgram      = new Program(baseVertexShader, divergenceShader);
 622  const curlProgram            = new Program(baseVertexShader, curlShader);
 623  const vorticityProgram       = new Program(baseVertexShader, vorticityShader);
 624  const pressureProgram        = new Program(baseVertexShader, pressureShader);
 625  const gradienSubtractProgram = new Program(baseVertexShader, gradientSubtractShader);
 626  
 627  const displayMaterial = new Material(baseVertexShader, displayShaderSource);
 628  
 629  function initFramebuffers () {
 630      let simRes = getResolution(config.SIM_RESOLUTION);
 631      let dyeRes = getResolution(config.DYE_RESOLUTION);
 632  
 633      const texType = ext.halfFloatTexType;
 634      const rgba    = ext.formatRGBA;
 635      const rg      = ext.formatRG;
 636      const r       = ext.formatR;
 637      const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST;
 638  
 639      gl.disable(gl.BLEND);
 640  
 641      if (dye == null)
 642          dye = createDoubleFBO(dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering);
 643      else
 644          dye = resizeDoubleFBO(dye, dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering);
 645  
 646      if (velocity == null)
 647          velocity = createDoubleFBO(simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering);
 648      else
 649          velocity = resizeDoubleFBO(velocity, simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering);
 650  
 651      divergence = createFBO      (simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST);
 652      curl       = createFBO      (simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST);
 653      pressure   = createDoubleFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST);
 654  
 655  }
 656  
 657  function createFBO (w, h, internalFormat, format, type, param) {
 658      gl.activeTexture(gl.TEXTURE0);
 659      let texture = gl.createTexture();
 660      gl.bindTexture(gl.TEXTURE_2D, texture);
 661      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param);
 662      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param);
 663      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
 664      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
 665      gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null);
 666  
 667      let fbo = gl.createFramebuffer();
 668      gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
 669      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
 670      gl.viewport(0, 0, w, h);
 671      gl.clear(gl.COLOR_BUFFER_BIT);
 672  
 673      let texelSizeX = 61.0*Math.random() / w;
 674      let texelSizeY = 1.0*Math.random() / h;
 675  
 676      return {
 677          texture,
 678          fbo,
 679          width: w,
 680          height: h,
 681          texelSizeX,
 682          texelSizeY,
 683          attach (id) {
 684              gl.activeTexture(gl.TEXTURE0 + id);
 685              gl.bindTexture(gl.TEXTURE_2D, texture);
 686              return id;
 687          }
 688      };
 689  }
 690  
 691  function createDoubleFBO (w, h, internalFormat, format, type, param) {
 692      let fbo1 = createFBO(w, h, internalFormat, format, type, param);
 693      let fbo2 = createFBO(w, h, internalFormat, format, type, param);
 694  
 695      return {
 696          width: w,
 697          height: h,
 698          texelSizeX: fbo1.texelSizeX,
 699          texelSizeY: fbo1.texelSizeY,
 700          get read () {
 701              return fbo1;
 702          },
 703          set read (value) {
 704              fbo1 = value;
 705          },
 706          get write () {
 707              return fbo2;
 708          },
 709          set write (value) {
 710              fbo2 = value;
 711          },
 712          swap () {
 713              let temp = fbo1;
 714              fbo1 = fbo2;
 715              fbo2 = temp;
 716          }
 717      }
 718  }
 719  
 720  function resizeFBO (target, w, h, internalFormat, format, type, param) {
 721      let newFBO = createFBO(w, h, internalFormat, format, type, param);
 722      copyProgram.bind();
 723      gl.uniform1i(copyProgram.uniforms.uTexture, target.attach(0));
 724      blit(newFBO);
 725      return newFBO;
 726  }
 727  
 728  function resizeDoubleFBO (target, w, h, internalFormat, format, type, param) {
 729      if (target.width == w && target.height == h)
 730          return target;
 731      target.read = resizeFBO(target.read, w, h, internalFormat, format, type, param);
 732      target.write = createFBO(w, h, internalFormat, format, type, param);
 733      target.width = w;
 734      target.height = h;
 735      target.texelSizeX = 1.0 / w;
 736      target.texelSizeY = 1.0 / h;
 737      return target;
 738  }
 739  
 740  function createTextureAsync (url) {
 741      let texture = gl.createTexture();
 742      gl.bindTexture(gl.TEXTURE_2D, texture);
 743      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
 744      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
 745      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
 746      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
 747      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255]));
 748  
 749      let obj = {
 750          texture,
 751          width: 1,
 752          height: 1,
 753          attach (id) {
 754              gl.activeTexture(gl.TEXTURE0 + id);
 755              gl.bindTexture(gl.TEXTURE_2D, texture);
 756              return id;
 757          }
 758      };
 759  
 760      let image = new Image();
 761      image.onload = () => {
 762          obj.width = image.width;
 763          obj.height = image.height;
 764          gl.bindTexture(gl.TEXTURE_2D, texture);
 765          gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
 766      };
 767      image.src = url;
 768  
 769      return obj;
 770  }
 771  
 772  function updateKeywords () {
 773      let displayKeywords = [];
 774      if (config.SHADING) displayKeywords.push("SHADING");
 775      displayMaterial.setKeywords(displayKeywords);
 776  }
 777  
 778  updateKeywords();
 779  initFramebuffers();
 780  
 781  let lastUpdateTime = Date.now();
 782  let colorUpdateTimer = 0.0;
 783  
 784  function update () {
 785    const dt = calcDeltaTime();
 786  // console.log(dt)
 787      if (resizeCanvas())
 788          initFramebuffers();
 789      updateColors(dt);
 790      applyInputs();
 791      step(dt);
 792      render(null);
 793      requestAnimationFrame(update);   
 794  }
 795  
 796  function calcDeltaTime () {
 797      let now = Date.now();
 798      let dt = (now - lastUpdateTime) / 1000;
 799      dt = Math.min(dt, 0.016666);
 800      lastUpdateTime = now;
 801      return dt;
 802  }
 803  
 804  function resizeCanvas () {
 805      let width = scaleByPixelRatio(canvas.clientWidth);
 806      let height = scaleByPixelRatio(canvas.clientHeight);
 807      if (canvas.width != width || canvas.height != height) {
 808          canvas.width = width;
 809          canvas.height = height;
 810          return true;
 811      }
 812      return false;
 813  }
 814  
 815  function updateColors (dt) {
 816  
 817      colorUpdateTimer += dt * config.COLOR_UPDATE_SPEED;
 818      if (colorUpdateTimer >= 1) {
 819          colorUpdateTimer = wrap(colorUpdateTimer, 0, 1);
 820          pointers.forEach(p => {
 821              p.color = generateColor();
 822          });
 823      }
 824  }
 825  
 826  function applyInputs () {
 827    pointers.forEach(p => {
 828        if (p.moved) {
 829            p.moved = false;
 830            splatPointer(p);
 831        }
 832    });
 833  }
 834  
 835  function step (dt) {
 836      gl.disable(gl.BLEND);
 837  
 838      curlProgram.bind();
 839      gl.uniform2f(curlProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
 840      gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.read.attach(0));
 841      blit(curl);
 842  
 843      vorticityProgram.bind();
 844      gl.uniform2f(vorticityProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
 845      gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.read.attach(0));
 846      gl.uniform1i(vorticityProgram.uniforms.uCurl, curl.attach(1));
 847      gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL);
 848      gl.uniform1f(vorticityProgram.uniforms.dt, dt);
 849      blit(velocity.write);
 850      velocity.swap();
 851  
 852      divergenceProgram.bind();
 853      gl.uniform2f(divergenceProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
 854      gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.read.attach(0));
 855      blit(divergence);
 856  
 857      clearProgram.bind();
 858      gl.uniform1i(clearProgram.uniforms.uTexture, pressure.read.attach(0));
 859      gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE);
 860      blit(pressure.write);
 861      pressure.swap();
 862  
 863      pressureProgram.bind();
 864      gl.uniform2f(pressureProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
 865      gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0));
 866      for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) {
 867          gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1));
 868          blit(pressure.write);
 869          pressure.swap();
 870      }
 871  
 872      gradienSubtractProgram.bind();
 873      gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
 874      gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.read.attach(0));
 875      gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.read.attach(1));
 876      blit(velocity.write);
 877      velocity.swap();
 878  
 879      advectionProgram.bind();
 880      gl.uniform2f(advectionProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
 881      if (!ext.supportLinearFiltering)
 882          gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, velocity.texelSizeX, velocity.texelSizeY);
 883      let velocityId = velocity.read.attach(0);
 884      gl.uniform1i(advectionProgram.uniforms.uVelocity, velocityId);
 885      gl.uniform1i(advectionProgram.uniforms.uSource, velocityId);
 886      gl.uniform1f(advectionProgram.uniforms.dt, dt);
 887      gl.uniform1f(advectionProgram.uniforms.dissipation, config.VELOCITY_DISSIPATION);
 888      blit(velocity.write);
 889      velocity.swap();
 890  
 891      if (!ext.supportLinearFiltering)
 892          gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, dye.texelSizeX, dye.texelSizeY);
 893      gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.read.attach(0));
 894      gl.uniform1i(advectionProgram.uniforms.uSource, dye.read.attach(1));
 895      gl.uniform1f(advectionProgram.uniforms.dissipation, config.DENSITY_DISSIPATION);
 896      blit(dye.write);
 897      dye.swap();
 898  }
 899  
 900  function render (target) {
 901      gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
 902      gl.enable(gl.BLEND);
 903      drawDisplay(target);
 904  }
 905  
 906  function drawDisplay (target) {
 907      let width = target == null ? gl.drawingBufferWidth : target.width;
 908      let height = target == null ? gl.drawingBufferHeight : target.height;
 909  
 910      displayMaterial.bind();
 911      if (config.SHADING)
 912          gl.uniform2f(displayMaterial.uniforms.texelSize, 1.0 / width, 1.0 / height);
 913      gl.uniform1i(displayMaterial.uniforms.uTexture, dye.read.attach(0));
 914      blit(target);
 915  }
 916  
 917  function splatPointer (pointer) {
 918    let dx = pointer.deltaX * config.SPLAT_FORCE;
 919    let dy = pointer.deltaY * config.SPLAT_FORCE;
 920    splat(pointer.texcoordX, pointer.texcoordY, dx, dy, pointer.color);
 921  }
 922  
 923  function clickSplat (pointer) {
 924      const color = generateColor();
 925      color.r *= 10.0;
 926      color.g *= 10.0;
 927      color.b *= 10.0;
 928      let dx = 10 * (Math.random() - 0.5);
 929      let dy = 30 * (Math.random() - 0.5);
 930      splat(pointer.texcoordX, pointer.texcoordY, dx, dy, color);
 931  }
 932  
 933  function splat (x, y, dx, dy, color) {
 934      splatProgram.bind();
 935      gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0));
 936      gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height);
 937      gl.uniform2f(splatProgram.uniforms.point, x, y);
 938      gl.uniform3f(splatProgram.uniforms.color, dx, dy, 0.0);
 939      gl.uniform1f(splatProgram.uniforms.radius, correctRadius(config.SPLAT_RADIUS / 100.0));
 940      blit(velocity.write);
 941      velocity.swap();
 942  
 943      gl.uniform1i(splatProgram.uniforms.uTarget, dye.read.attach(0));
 944      gl.uniform3f(splatProgram.uniforms.color, color.r, color.g, color.b);
 945      blit(dye.write);
 946      dye.swap();
 947  }
 948  
 949  function correctRadius (radius) {
 950      let aspectRatio = canvas.width / canvas.height;
 951      if (aspectRatio > 1)
 952          radius *= aspectRatio;
 953      return radius;
 954  }
 955  
 956  window.addEventListener('mousedown', e => {
 957    let pointer = pointers[0];
 958    let posX = scaleByPixelRatio(e.clientX);
 959    let posY = scaleByPixelRatio(e.clientY);
 960    updatePointerDownData(pointer, -1, posX, posY);
 961    clickSplat(pointer);
 962  });
 963  
 964  $('body').one( 'mousemove', e => {
 965    let pointer = pointers[0];
 966    let posX = scaleByPixelRatio(e.clientX);
 967    let posY = scaleByPixelRatio(e.clientY);
 968    let color = generateColor();
 969    update();
 970    updatePointerMoveData(pointer, posX, posY, color);
 971  });
 972  
 973  window.addEventListener('mousemove', e => {
 974      let pointer = pointers[0];
 975      let posX = scaleByPixelRatio(e.clientX);
 976      let posY = scaleByPixelRatio(e.clientY);
 977      let color = pointer.color;
 978      updatePointerMoveData(pointer, posX, posY, color);
 979  });
 980  
 981  $('body').one( 'touchstart', e => {
 982    const touches = e.targetTouches;
 983    let touch = touches[0]
 984    let pointer = pointers[0];
 985    for (let i = 0; i < touches.length; i++) {
 986      let posX = scaleByPixelRatio(touches[i].clientX);
 987      let posY = scaleByPixelRatio(touches[i].clientY);  
 988      update();
 989      updatePointerDownData(pointer, touches[i].identifier, posX, posY);
 990    }
 991  });  
 992  
 993  window.addEventListener('touchstart', e => {
 994      const touches = e.targetTouches;
 995      let pointer = pointers[0];
 996      for (let i = 0; i < touches.length; i++) {
 997        let posX = scaleByPixelRatio(touches[i].clientX);
 998        let posY = scaleByPixelRatio(touches[i].clientY);  
 999        updatePointerDownData(pointer, touches[i].identifier, posX, posY);
1000      }
1001  });
1002  
1003  window.addEventListener('touchmove', e => {
1004      const touches = e.targetTouches;
1005      let pointer = pointers[0];
1006      for (let i = 0; i < touches.length; i++) {
1007        let posX = scaleByPixelRatio(touches[i].clientX);
1008        let posY = scaleByPixelRatio(touches[i].clientY);
1009        updatePointerMoveData(pointer, posX, posY, pointer.color);
1010    }   
1011  }, false);
1012  
1013  window.addEventListener('touchend', e => {
1014    const touches = e.changedTouches;
1015    let pointer = pointers[0];  
1016  
1017    for (let i = 0; i < touches.length; i++) {
1018      updatePointerUpData(pointer);
1019    }
1020  });
1021   
1022  function updatePointerDownData (pointer, id, posX, posY) {
1023      pointer.id = id;
1024      pointer.down = true;
1025      pointer.moved = false;
1026      pointer.texcoordX = posX / canvas.width;
1027      pointer.texcoordY = 1.0 - posY / canvas.height;
1028      pointer.prevTexcoordX = pointer.texcoordX;
1029      pointer.prevTexcoordY = pointer.texcoordY;
1030      pointer.deltaX = 0;
1031      pointer.deltaY = 0;
1032      pointer.color = generateColor();
1033  }
1034  
1035  function updatePointerMoveData (pointer, posX, posY, color) {
1036      // pointer.down = false;
1037      pointer.prevTexcoordX = pointer.texcoordX;
1038      pointer.prevTexcoordY = pointer.texcoordY;
1039      pointer.texcoordX = posX / canvas.width;
1040      pointer.texcoordY = 1.0 - posY / canvas.height;
1041      pointer.deltaX = correctDeltaX(pointer.texcoordX - pointer.prevTexcoordX);
1042      pointer.deltaY = correctDeltaY(pointer.texcoordY - pointer.prevTexcoordY);
1043      pointer.moved = Math.abs(pointer.deltaX) > 0 || Math.abs(pointer.deltaY) > 0;
1044      pointer.color = color;
1045  }
1046  
1047  function updatePointerUpData (pointer) {
1048    pointer.down = false;
1049  }
1050  
1051  function correctDeltaX (delta) {
1052      let aspectRatio = canvas.width / canvas.height;
1053      if (aspectRatio < 1) delta *= aspectRatio;
1054      return delta;
1055  }
1056  
1057  function correctDeltaY (delta) {
1058      let aspectRatio = canvas.width / canvas.height;
1059      if (aspectRatio > 1) delta /= aspectRatio;
1060      return delta;
1061  }
1062  
1063  function generateColor () {
1064      let c = HSVtoRGB(1.0, 1.0, 1.0);
1065      c.r *= 0.0015;
1066      c.g *= 0.0015;
1067      c.b *= 0.0015;
1068      return c;
1069  }
1070  
1071  function HSVtoRGB (h, s, v) {
1072      let r, g, b, i;
1073      i = Math.floor(h * 3);
1074  
1075      switch (i % 3) {
1076          case 0: r = 104, g = 7, b = 259; break;
1077          case 1: r = 30, g = 227, b = 207; break;
1078          case 2: r = 255, g = 215, b = 57; break;
1079      }
1080  
1081      return {
1082          r,
1083          g,
1084          b
1085      };
1086  }
1087  
1088  function wrap (value, min, max) {
1089      let range = max - min;
1090      if (range == 0) return min;
1091      return (value - min) % range + min;
1092  }
1093  
1094  function getResolution (resolution) {
1095      let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight;
1096      if (aspectRatio < 1)
1097          aspectRatio = 1.0 / aspectRatio;
1098  
1099      let min = Math.round(resolution);
1100      let max = Math.round(resolution * aspectRatio);
1101  
1102      if (gl.drawingBufferWidth > gl.drawingBufferHeight)
1103          return { width: max, height: min };
1104      else
1105          return { width: min, height: max };
1106  }
1107  
1108  function scaleByPixelRatio (input) {
1109      let pixelRatio = window.devicePixelRatio || 1;
1110      return Math.floor(input * pixelRatio);
1111  }
1112  
1113  function hashCode (s) {
1114      if (s.length == 0) return 0;
1115      let hash = 0;
1116      for (let i = 0; i < s.length; i++) {
1117          hash = (hash << 5) - hash + s.charCodeAt(i);
1118          hash |= 0; // Convert to 32bit integer
1119      }
1120      return hash;
1121  };
1122  };