Facebook
From KaoTD, 3 Years ago, written in HTML5.
Embed
Download Paste or View Raw
Hits: 142
  1. <!DOCTYPE html>
  2.     <head>
  3.         <meta charset="utf-8">
  4.         <meta http-equiv="Cache-Control" content="no-cache">
  5.  
  6.         <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  7.         <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
  8.         <meta name="apple-mobile-web-app-capable" content="yes">
  9.         <meta name="mobile-web-app-capable" content="yes">
  10.  
  11.         <title>KaoTD</title>
  12.         <meta name="description">
  13.  
  14.         <meta property="og:type">
  15.         <meta property="og:title">
  16.         <meta property="og:description">
  17.         <meta property="og:url">
  18.  
  19.         <script type="text/javascript" src="dat.gui.min.js"></script>
  20.         <style>
  21.             * {
  22.                 user-select: none;
  23.             }
  24.  
  25.             html, body {
  26.                 overflow: hidden;
  27.             }
  28.  
  29.             body {
  30.                 margin: 0;
  31.                 position: fixed;
  32.                 width: 100%;
  33.                 height: 100%;
  34.             }
  35.  
  36.             canvas {
  37.                 width: 100%;
  38.                 height: 100%;
  39.                 background-size: cover;
  40.                 background-repeat: no-repeat;
  41.                 background-position: center;
  42.             }
  43.  
  44.             .dg {
  45.                 opacity: 0.9;
  46.             }
  47.  
  48.             .dg .property-name {
  49.                 overflow: visible;
  50.             }
  51.  
  52.             .bigFont {
  53.                 font-size: 150%;
  54.                 color: #8C8C8C;
  55.             }
  56.  
  57.             .cr.function.appBigFont {
  58.                 font-size: 150%;
  59.                 line-height: 27px;
  60.                 background-color: #2F4F4F;
  61.             }
  62.  
  63.             .cr.function.appBigFont .property-name {
  64.                 float: none;
  65.             }
  66.  
  67.             .cr.function.appBigFont .icon {
  68.                 position: sticky;
  69.                 bottom: 27px;
  70.             }
  71.         </style>
  72.     </head>
  73.     <body>
  74.         <canvas></canvas>
  75.         <!-- <script src="./script.js"></script> -->
  76.         <script type="text/javascript">
  77.             'use strict';
  78.  
  79. const canvas = document.getElementsByTagName('canvas')[0];
  80. canvas.width = canvas.clientWidth;
  81. canvas.height = canvas.clientHeight;
  82.  
  83. Array.prototype.getRandom = function() {
  84.     return this[Math.floor(Math.random() * this.length)];
  85. };
  86.  
  87. let splatColors = [{ r: 0, g: 0.15, b: 0 }];
  88.  
  89. let config = {
  90.     SIM_RESOLUTION: 256,
  91.     DYE_RESOLUTION: 1024,
  92.     DENSITY_DISSIPATION: 0.97,
  93.     VELOCITY_DISSIPATION: 0.98,
  94.     PRESSURE_DISSIPATION: 0.8,
  95.     PRESSURE_ITERATIONS: 20,
  96.     CURL: 30,
  97.     SPLAT_RADIUS: 0.3,
  98.     SHADING: true,
  99.     COLORFUL: true,
  100.     PAUSED: false,
  101.     BACK_COLOR: { r: 0, g: 0, b: 0 },
  102.     TRANSPARENT: false,
  103.     BLOOM: true,
  104.     BLOOM_ITERATIONS: 8,
  105.     BLOOM_RESOLUTION: 256,
  106.     BLOOM_INTENSITY: 0.8,
  107.     BLOOM_THRESHOLD: 0.6,
  108.     BLOOM_SOFT_KNEE: 0.7,
  109.     POINTER_COLOR: [{ r: 0, g: 0.15, b: 0 }],
  110.     SOUND_SENSITIVITY: 0.25,
  111.     AUDIO_RESPONSIVE: true,
  112.     FREQ_RANGE: 8,
  113.     FREQ_RANGE_START: 0
  114. };
  115.  
  116. document.addEventListener("DOMContentLoaded", () => {  
  117.     window.wallpaperPropertyListener = {
  118.         applyUserProperties: (properties) => {
  119.             if (properties.bloom_intensity) config.BLOOM_INTENSITY = properties.bloom_intensity.value;
  120.             if (properties.bloom_threshold) config.BLOOM_THRESHOLD = properties.bloom_threshold.value;
  121.             if (properties.colorful) config.COLORFUL = properties.colorful.value;
  122.             if (properties.density_diffusion) config.DENSITY_DISSIPATION = properties.density_diffusion.value;
  123.             if (properties.enable_bloom) config.BLOOM = properties.enable_bloom.value;
  124.             if (properties.paused) config.PAUSED = properties.paused.value;
  125.             if (properties.pressure_diffusion) config.PRESSURE_DISSIPATION = properties.pressure_diffusion.value;
  126.             if (properties.shading) config.SHADING = properties.shading.value;
  127.             if (properties.splat_radius) config.SPLAT_RADIUS = properties.splat_radius.value;
  128.             if (properties.velocity_diffusion) config.VELOCITY_DISSIPATION = properties.velocity_diffusion.value;
  129.             if (properties.vorticity) config.CURL = properties.vorticity.value;
  130.             if (properties.sound_sensitivity) config.SOUND_SENSITIVITY = properties.sound_sensitivity.value;
  131.             if (properties.audio_responsive) config.AUDIO_RESPONSIVE = properties.audio_responsive.value;
  132.             if (properties.simulation_resolution) {
  133.                 config.SIM_RESOLUTION = properties.simulation_resolution.value;
  134.                 initFramebuffers();
  135.             }
  136.             if (properties.dye_resolution) {
  137.                 config.DYE_RESOLUTION = properties.dye_resolution.value;
  138.                 initFramebuffers();
  139.             }
  140.             if (properties.splat_color) splatColors[0] = rgbToPointerColor(properties.splat_color.value);
  141.             if (properties.splat_color_2) splatColors[1] = rgbToPointerColor(properties.splat_color_2.value);
  142.             if (properties.splat_color_3) splatColors[2] = rgbToPointerColor(properties.splat_color_3.value);
  143.             if (properties.splat_color_4) splatColors[3] = rgbToPointerColor(properties.splat_color_4.value);
  144.             if (properties.splat_color_5) splatColors[4] = rgbToPointerColor(properties.splat_color_5.value);
  145.             if (properties.background_color) {
  146.                 let c = properties.background_color.value.split(" "),
  147.                 r = Math.floor(c[0]*255),
  148.                 g = Math.floor(c[1]*255),
  149.                 b = Math.floor(c[2]*255);
  150.                 document.body.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
  151.                 config.BACK_COLOR.r = r;
  152.                 config.BACK_COLOR.g = g;
  153.                 config.BACK_COLOR.b = b;
  154.             }
  155.             if (properties.more_colors && !properties.more_colors.value) {
  156.                config.POINTER_COLOR = [splatColors[0]];
  157.             } else if (properties.more_colors && properties.more_colors.value) {
  158.                config.POINTER_COLOR = splatColors;
  159.             }
  160.             if (properties.use_background_image) config.TRANSPARENT = properties.use_background_image.value;
  161.             if (properties.background_image) canvas.style.backgroundImage = `url("file:///${properties.background_image.value}")`;
  162.             if (properties.repeat_background) canvas.style.backgroundRepeat = properties.repeat_background.value ? "repeat" : "no-repeat";
  163.             if (properties.background_image_size) canvas.style.backgroundSize = properties.background_image_size.value;
  164.             if (properties.frequency_range) {
  165.                 config.FREQ_RANGE = properties.frequency_range.value;
  166.  
  167.                 if (config.FREQ_RANGE + config.FREQ_RANGE_START > 61) {
  168.                     config.FREQ_RANGE_START = 62 - config.FREQ_RANGE;
  169.                 }
  170.             }
  171.             if (properties.frequency_range_start) {
  172.                 if (config.FREQ_RANGE + properties.frequency_range_start.value > 61) {
  173.                     config.FREQ_RANGE_START = 62 - config.FREQ_RANGE;
  174.                 } else {
  175.                     config.FREQ_RANGE_START = properties.frequency_range_start.value;
  176.                 }
  177.             };
  178.         }
  179.     };
  180.  
  181.     window.wallpaperRegisterAudioListener((audioArray) => {
  182.         if (!config.AUDIO_RESPONSIVE) return;
  183.         if (audioArray[0] > 5) return;
  184.  
  185.         let bass = 0.0;
  186.         let half = Math.floor(audioArray.length / 2);
  187.  
  188.         for (let i = 0; i <= config.FREQ_RANGE; i++) {
  189.            bass += audioArray[i + config.FREQ_RANGE_START];
  190.            bass += audioArray[half + (i + config.FREQ_RANGE_START)];
  191.        }
  192.        bass /= (config.FREQ_RANGE * 2);
  193.        multipleSplats(parseInt((bass * config.SOUND_SENSITIVITY) * 10));
  194.    });
  195. });
  196.  
  197. function indexOfMax(arr) {
  198.    if (arr.length === 0) {
  199.        return -1;
  200.    }
  201.  
  202.    var max = arr[0];
  203.    var maxIndex = 0;
  204.  
  205.    for (var i = 1; i < arr.length; i++) {
  206.        if (arr[i] > max) {
  207.             maxIndex = i;
  208.             max = arr[i];
  209.         }
  210.     }
  211.  
  212.     return maxIndex;
  213. }
  214.  
  215. function pointerPrototype () {
  216.     this.id = -1;
  217.     this.x = 0;
  218.     this.y = 0;
  219.     this.dx = 0;
  220.     this.dy = 0;
  221.     this.down = false;
  222.     this.moved = false;
  223.     this.color = [30, 0, 300];
  224. }
  225.  
  226. let pointers = [];
  227. let splatStack = [];
  228. let bloomFramebuffers = [];
  229. pointers.push(new pointerPrototype());
  230.  
  231. const { gl, ext } = getWebGLContext(canvas);
  232.  
  233. if (isMobile())
  234.     config.SHADING = false;
  235. if (!ext.supportLinearFiltering)
  236. {
  237.     config.SHADING = false;
  238.     config.BLOOM = false;
  239. }
  240.  
  241. // startGUI();
  242.  
  243. function getWebGLContext (canvas) {
  244.     const params = { alpha: true, depth: false, stencil: false, antialias: false, preserveDrawingBuffer: false };
  245.  
  246.     let gl = canvas.getContext('webgl2', params);
  247.     const isWebGL2 = !!gl;
  248.     if (!isWebGL2)
  249.         gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params);
  250.  
  251.     let halfFloat;
  252.     let supportLinearFiltering;
  253.     if (isWebGL2) {
  254.         gl.getExtension('EXT_color_buffer_float');
  255.         supportLinearFiltering = gl.getExtension('OES_texture_float_linear');
  256.     } else {
  257.         halfFloat = gl.getExtension('OES_texture_half_float');
  258.         supportLinearFiltering = gl.getExtension('OES_texture_half_float_linear');
  259.     }
  260.  
  261.     gl.clearColor(0.0, 0.0, 0.0, 1.0);
  262.  
  263.     const halfFloatTexType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES;
  264.     let formatRGBA;
  265.     let formatRG;
  266.     let formatR;
  267.  
  268.     if (isWebGL2)
  269.     {
  270.         formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType);
  271.         formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType);
  272.         formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType);
  273.     }
  274.     else
  275.     {
  276.         formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
  277.         formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
  278.         formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
  279.     }
  280.  
  281.     return {
  282.         gl,
  283.         ext: {
  284.             formatRGBA,
  285.             formatRG,
  286.             formatR,
  287.             halfFloatTexType,
  288.             supportLinearFiltering
  289.         }
  290.     };
  291. }
  292.  
  293. function getSupportedFormat (gl, internalFormat, format, type)
  294. {
  295.     if (!supportRenderTextureFormat(gl, internalFormat, format, type))
  296.     {
  297.         switch (internalFormat)
  298.         {
  299.             case gl.R16F:
  300.                 return getSupportedFormat(gl, gl.RG16F, gl.RG, type);
  301.             case gl.RG16F:
  302.                 return getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, type);
  303.             default:
  304.                 return null;
  305.         }
  306.     }
  307.  
  308.     return {
  309.         internalFormat,
  310.         format
  311.     }
  312. }
  313.  
  314. function supportRenderTextureFormat (gl, internalFormat, format, type) {
  315.     let texture = gl.createTexture();
  316.     gl.bindTexture(gl.TEXTURE_2D, texture);
  317.     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  318.     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  319.     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  320.     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  321.     gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 4, 4, 0, format, type, null);
  322.  
  323.     let fbo = gl.createFramebuffer();
  324.     gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
  325.     gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
  326.  
  327.     const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
  328.     if (status != gl.FRAMEBUFFER_COMPLETE)
  329.         return false;
  330.     return true;
  331. }
  332.  
  333. function startGUI () {
  334.     var gui = new dat.GUI({ width: 300 });
  335.     gui.add(config, 'SIM_RESOLUTION', { '32': 32, '64': 64, '128': 128, '256': 256 }).name('sim resolution').onFinishChange(initFramebuffers);
  336.     gui.add(config, 'DYE_RESOLUTION', { '128': 128, '256': 256, '512': 512, '1024': 1024 }).name('dye resolution').onFinishChange(initFramebuffers);
  337.     gui.add(config, 'DENSITY_DISSIPATION', 0.9, 1.0).name('density diffusion');
  338.     gui.add(config, 'VELOCITY_DISSIPATION', 0.9, 1.0).name('velocity diffusion');
  339.     gui.add(config, 'PRESSURE_DISSIPATION', 0.0, 1.0).name('pressure diffusion');
  340.     gui.add(config, 'CURL', 0, 50).name('vorticity').step(1);
  341.     gui.add(config, 'SPLAT_RADIUS', 0.01, 1.0).name('splat radius');
  342.     gui.add(config, 'SHADING').name('shading');
  343.     gui.add(config, 'COLORFUL').name('colorful');
  344.     gui.add(config, 'PAUSED').name('paused').listen();
  345.  
  346.     gui.add({ fun: () => {
  347.         splatStack.push(parseInt(Math.random() * 20) + 5);
  348.     } }, 'fun').name('Random splats');
  349.  
  350.     let bloomFolder = gui.addFolder('Bloom');
  351.     bloomFolder.add(config, 'BLOOM').name('enabled');
  352.     bloomFolder.add(config, 'BLOOM_INTENSITY', 0.1, 2.0).name('intensity');
  353.     bloomFolder.add(config, 'BLOOM_THRESHOLD', 0.0, 1.0).name('threshold');
  354.  
  355.     let captureFolder = gui.addFolder('Capture');
  356.     captureFolder.addColor(config, 'BACK_COLOR').name('background color');
  357.     captureFolder.add(config, 'TRANSPARENT').name('transparent');
  358.     captureFolder.add({ fun: captureScreenshot }, 'fun').name('take screenshot');
  359.  
  360.     let github = gui.add({ fun : () => {
  361.         window.open('https://github.com/PavelDoGreat/WebGL-Fluid-Simulation');
  362.     } }, 'fun').name('Github');
  363.     github.__li.className = 'cr function bigFont';
  364.     github.__li.style.borderLeft = '3px solid #8C8C8C';
  365.     let githubIcon = document.createElement('span');
  366.     github.domElement.parentElement.appendChild(githubIcon);
  367.     githubIcon.className = 'icon github';
  368.  
  369.     let twitter = gui.add({ fun : () => {
  370.         window.open('https://twitter.com/PavelDoGreat');
  371.     } }, 'fun').name('Twitter');
  372.     twitter.__li.className = 'cr function bigFont';
  373.     twitter.__li.style.borderLeft = '3px solid #8C8C8C';
  374.     let twitterIcon = document.createElement('span');
  375.     twitter.domElement.parentElement.appendChild(twitterIcon);
  376.     twitterIcon.className = 'icon twitter';
  377.  
  378.     let discord = gui.add({ fun : () => {
  379.         window.open('https://discordapp.com/invite/CeqZDDE');
  380.     } }, 'fun').name('Discord');
  381.     discord.__li.className = 'cr function bigFont';
  382.     discord.__li.style.borderLeft = '3px solid #8C8C8C';
  383.     let discordIcon = document.createElement('span');
  384.     discord.domElement.parentElement.appendChild(discordIcon);
  385.     discordIcon.className = 'icon discord';
  386.  
  387.     let app = gui.add({ fun : () => {
  388.         window.open('http://onelink.to/5b58bn');
  389.     } }, 'fun').name('Check out new improved version');
  390.     app.__li.className = 'cr function appBigFont';
  391.     app.__li.style.borderLeft = '3px solid #00FF7F';
  392.     let appIcon = document.createElement('span');
  393.     app.domElement.parentElement.appendChild(appIcon);
  394.     appIcon.className = 'icon app';
  395.  
  396.     if (isMobile())
  397.         gui.close();
  398. }
  399.  
  400. function captureScreenshot () {
  401.     colorProgram.bind();
  402.     gl.uniform4f(colorProgram.uniforms.color, 0, 0, 0, 1);
  403.     blit(density.write.fbo);
  404.  
  405.     render(density.write.fbo);
  406.     gl.bindFramebuffer(gl.FRAMEBUFFER, density.write.fbo);
  407.  
  408.     let length = dyeWidth * dyeHeight * 4;
  409.     let pixels = new Float32Array(length);
  410.     gl.readPixels(0, 0, dyeWidth, dyeHeight, gl.RGBA, gl.FLOAT, pixels);
  411.  
  412.     let newPixels = new Uint8Array(length);
  413.  
  414.     let id = 0;
  415.     for (let i = dyeHeight - 1; i >= 0; i--) {
  416.         for (let j = 0; j < dyeWidth; j++) {
  417.            let nid = i * dyeWidth * 4 + j * 4;
  418.            newPixels[nid + 0] = clamp01(pixels[id + 0]) * 255;
  419.            newPixels[nid + 1] = clamp01(pixels[id + 1]) * 255;
  420.            newPixels[nid + 2] = clamp01(pixels[id + 2]) * 255;
  421.            newPixels[nid + 3] = clamp01(pixels[id + 3]) * 255;
  422.            id += 4;
  423.        }
  424.    }
  425.  
  426.    let captureCanvas = document.createElement('canvas');
  427.    let ctx = captureCanvas.getContext('2d');
  428.    captureCanvas.width = dyeWidth;
  429.    captureCanvas.height = dyeHeight;
  430.  
  431.    let imageData = ctx.createImageData(dyeWidth, dyeHeight);
  432.    imageData.data.set(newPixels);
  433.    ctx.putImageData(imageData, 0, 0);
  434.    let datauri = captureCanvas.toDataURL();
  435.  
  436.    downloadURI("fluid.png", datauri);
  437.  
  438.    URL.revokeObjectURL(datauri);
  439. }
  440.  
  441. function clamp01 (input) {
  442.    return Math.min(Math.max(input, 0), 1);
  443. }
  444.  
  445. function downloadURI (filename, uri) {
  446.    let link = document.createElement("a");
  447.    link.download = filename;
  448.    link.href = uri;
  449.    document.body.appendChild(link);
  450.    link.click();
  451.    document.body.removeChild(link);
  452. }
  453.  
  454. function isMobile () {
  455.    return /Mobi|Android/i.test(navigator.userAgent);
  456. }
  457.  
  458. class GLProgram {
  459.    constructor (vertexShader, fragmentShader) {
  460.        this.uniforms = {};
  461.        this.program = gl.createProgram();
  462.  
  463.        gl.attachShader(this.program, vertexShader);
  464.        gl.attachShader(this.program, fragmentShader);
  465.        gl.linkProgram(this.program);
  466.  
  467.        if (!gl.getProgramParameter(this.program, gl.LINK_STATUS))
  468.            throw gl.getProgramInfoLog(this.program);
  469.  
  470.        const uniformCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
  471.        for (let i = 0; i < uniformCount; i++) {
  472.            const uniformName = gl.getActiveUniform(this.program, i).name;
  473.            this.uniforms[uniformName] = gl.getUniformLocation(this.program, uniformName);
  474.        }
  475.    }
  476.  
  477.    bind () {
  478.        gl.useProgram(this.program);
  479.    }
  480. }
  481.  
  482. function compileShader (type, source) {
  483.    const shader = gl.createShader(type);
  484.    gl.shaderSource(shader, source);
  485.    gl.compileShader(shader);
  486.  
  487.    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
  488.        throw gl.getShaderInfoLog(shader);
  489.  
  490.    return shader;
  491. };
  492.  
  493. const baseVertexShader = compileShader(gl.VERTEX_SHADER, `
  494.    precision highp float;
  495.  
  496.    attribute vec2 aPosition;
  497.    varying vec2 vUv;
  498.    varying vec2 vL;
  499.    varying vec2 vR;
  500.    varying vec2 vT;
  501.    varying vec2 vB;
  502.    uniform vec2 texelSize;
  503.  
  504.    void main () {
  505.        vUv = aPosition * 0.5 + 0.5;
  506.        vL = vUv - vec2(texelSize.x, 0.0);
  507.        vR = vUv + vec2(texelSize.x, 0.0);
  508.        vT = vUv + vec2(0.0, texelSize.y);
  509.        vB = vUv - vec2(0.0, texelSize.y);
  510.        gl_Position = vec4(aPosition, 0.0, 1.0);
  511.    }
  512. `);
  513.  
  514. const clearShader = compileShader(gl.FRAGMENT_SHADER, `
  515.    precision mediump float;
  516.    precision mediump sampler2D;
  517.  
  518.    varying highp vec2 vUv;
  519.    uniform sampler2D uTexture;
  520.    uniform float value;
  521.  
  522.    void main () {
  523.        gl_FragColor = value * texture2D(uTexture, vUv);
  524.    }
  525. `);
  526.  
  527. const colorShader = compileShader(gl.FRAGMENT_SHADER, `
  528.    precision mediump float;
  529.  
  530.    uniform vec4 color;
  531.  
  532.    void main () {
  533.        gl_FragColor = color;
  534.    }
  535. `);
  536.  
  537. const backgroundShader = compileShader(gl.FRAGMENT_SHADER, `
  538.    void main () {
  539.        gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
  540.    }
  541. `);
  542.  
  543. const displayShader = compileShader(gl.FRAGMENT_SHADER, `
  544.    precision highp float;
  545.    precision highp sampler2D;
  546.  
  547.    varying vec2 vUv;
  548.    uniform sampler2D uTexture;
  549.  
  550.    void main () {
  551.        vec3 C = texture2D(uTexture, vUv).rgb;
  552.        float a = max(C.r, max(C.g, C.b));
  553.        gl_FragColor = vec4(C, a);
  554.    }
  555. `);
  556.  
  557. const displayBloomShader = compileShader(gl.FRAGMENT_SHADER, `
  558.    precision highp float;
  559.    precision highp sampler2D;
  560.  
  561.    varying vec2 vUv;
  562.    uniform sampler2D uTexture;
  563.    uniform sampler2D uBloom;
  564.    uniform sampler2D uDithering;
  565.    uniform vec2 ditherScale;
  566.  
  567.    void main () {
  568.        vec3 C = texture2D(uTexture, vUv).rgb;
  569.        vec3 bloom = texture2D(uBloom, vUv).rgb;
  570.        vec3 noise = texture2D(uDithering, vUv * ditherScale).rgb;
  571.        noise = noise * 2.0 - 1.0;
  572.        bloom += noise / 800.0;
  573.        bloom = pow(bloom.rgb, vec3(1.0 / 2.2));
  574.        C += bloom;
  575.        float a = max(C.r, max(C.g, C.b));
  576.        gl_FragColor = vec4(C, a);
  577.    }
  578. `);
  579.  
  580. const displayShadingShader = compileShader(gl.FRAGMENT_SHADER, `
  581.    precision highp float;
  582.    precision highp sampler2D;
  583.  
  584.    varying vec2 vUv;
  585.    varying vec2 vL;
  586.    varying vec2 vR;
  587.    varying vec2 vT;
  588.    varying vec2 vB;
  589.    uniform sampler2D uTexture;
  590.    uniform vec2 texelSize;
  591.  
  592.    void main () {
  593.        vec3 L = texture2D(uTexture, vL).rgb;
  594.        vec3 R = texture2D(uTexture, vR).rgb;
  595.        vec3 T = texture2D(uTexture, vT).rgb;
  596.        vec3 B = texture2D(uTexture, vB).rgb;
  597.        vec3 C = texture2D(uTexture, vUv).rgb;
  598.  
  599.        float dx = length(R) - length(L);
  600.        float dy = length(T) - length(B);
  601.  
  602.        vec3 n = normalize(vec3(dx, dy, length(texelSize)));
  603.        vec3 l = vec3(0.0, 0.0, 1.0);
  604.  
  605.        float diffuse = clamp(dot(n, l) + 0.7, 0.7, 1.0);
  606.        C.rgb *= diffuse;
  607.  
  608.        float a = max(C.r, max(C.g, C.b));
  609.        gl_FragColor = vec4(C, a);
  610.    }
  611. `);
  612.  
  613. const displayBloomShadingShader = compileShader(gl.FRAGMENT_SHADER, `
  614.    precision highp float;
  615.    precision highp sampler2D;
  616.  
  617.    varying vec2 vUv;
  618.    varying vec2 vL;
  619.    varying vec2 vR;
  620.    varying vec2 vT;
  621.    varying vec2 vB;
  622.    uniform sampler2D uTexture;
  623.    uniform sampler2D uBloom;
  624.    uniform sampler2D uDithering;
  625.    uniform vec2 ditherScale;
  626.    uniform vec2 texelSize;
  627.  
  628.    void main () {
  629.        vec3 L = texture2D(uTexture, vL).rgb;
  630.        vec3 R = texture2D(uTexture, vR).rgb;
  631.        vec3 T = texture2D(uTexture, vT).rgb;
  632.        vec3 B = texture2D(uTexture, vB).rgb;
  633.        vec3 C = texture2D(uTexture, vUv).rgb;
  634.  
  635.        float dx = length(R) - length(L);
  636.        float dy = length(T) - length(B);
  637.  
  638.        vec3 n = normalize(vec3(dx, dy, length(texelSize)));
  639.        vec3 l = vec3(0.0, 0.0, 1.0);
  640.  
  641.        float diffuse = clamp(dot(n, l) + 0.7, 0.7, 1.0);
  642.        C *= diffuse;
  643.  
  644.        vec3 bloom = texture2D(uBloom, vUv).rgb;
  645.        vec3 noise = texture2D(uDithering, vUv * ditherScale).rgb;
  646.        noise = noise * 2.0 - 1.0;
  647.        bloom += noise / 800.0;
  648.        bloom = pow(bloom.rgb, vec3(1.0 / 2.2));
  649.        C += bloom;
  650.  
  651.        float a = max(C.r, max(C.g, C.b));
  652.        gl_FragColor = vec4(C, a);
  653.    }
  654. `);
  655.  
  656. const bloomPrefilterShader = compileShader(gl.FRAGMENT_SHADER, `
  657.    precision mediump float;
  658.    precision mediump sampler2D;
  659.  
  660.    varying vec2 vUv;
  661.    uniform sampler2D uTexture;
  662.    uniform vec3 curve;
  663.    uniform float threshold;
  664.  
  665.    void main () {
  666.        vec3 c = texture2D(uTexture, vUv).rgb;
  667.        float br = max(c.r, max(c.g, c.b));
  668.        float rq = clamp(br - curve.x, 0.0, curve.y);
  669.        rq = curve.z * rq * rq;
  670.        c *= max(rq, br - threshold) / max(br, 0.0001);
  671.        gl_FragColor = vec4(c, 0.0);
  672.    }
  673. `);
  674.  
  675. const bloomBlurShader = compileShader(gl.FRAGMENT_SHADER, `
  676.    precision mediump float;
  677.    precision mediump sampler2D;
  678.  
  679.    varying vec2 vL;
  680.    varying vec2 vR;
  681.    varying vec2 vT;
  682.    varying vec2 vB;
  683.    uniform sampler2D uTexture;
  684.  
  685.    void main () {
  686.        vec4 sum = vec4(0.0);
  687.        sum += texture2D(uTexture, vL);
  688.        sum += texture2D(uTexture, vR);
  689.        sum += texture2D(uTexture, vT);
  690.        sum += texture2D(uTexture, vB);
  691.        sum *= 0.25;
  692.        gl_FragColor = sum;
  693.    }
  694. `);
  695.  
  696. const bloomFinalShader = compileShader(gl.FRAGMENT_SHADER, `
  697.    precision mediump float;
  698.    precision mediump sampler2D;
  699.  
  700.    varying vec2 vL;
  701.    varying vec2 vR;
  702.    varying vec2 vT;
  703.    varying vec2 vB;
  704.    uniform sampler2D uTexture;
  705.    uniform float intensity;
  706.  
  707.    void main () {
  708.        vec4 sum = vec4(0.0);
  709.        sum += texture2D(uTexture, vL);
  710.        sum += texture2D(uTexture, vR);
  711.        sum += texture2D(uTexture, vT);
  712.        sum += texture2D(uTexture, vB);
  713.        sum *= 0.25;
  714.        gl_FragColor = sum * intensity;
  715.    }
  716. `);
  717.  
  718. const splatShader = compileShader(gl.FRAGMENT_SHADER, `
  719.    precision highp float;
  720.    precision highp sampler2D;
  721.  
  722.    varying vec2 vUv;
  723.    uniform sampler2D uTarget;
  724.    uniform float aspectRatio;
  725.    uniform vec3 color;
  726.    uniform vec2 point;
  727.    uniform float radius;
  728.  
  729.    void main () {
  730.        vec2 p = vUv - point.xy;
  731.        p.x *= aspectRatio;
  732.        vec3 splat = exp(-dot(p, p) / radius) * color;
  733.        vec3 base = texture2D(uTarget, vUv).xyz;
  734.        gl_FragColor = vec4(base + splat, 1.0);
  735.    }
  736. `);
  737.  
  738. const advectionManualFilteringShader = compileShader(gl.FRAGMENT_SHADER, `
  739.    precision highp float;
  740.    precision highp sampler2D;
  741.  
  742.    varying vec2 vUv;
  743.    uniform sampler2D uVelocity;
  744.    uniform sampler2D uSource;
  745.    uniform vec2 texelSize;
  746.    uniform vec2 dyeTexelSize;
  747.    uniform float dt;
  748.    uniform float dissipation;
  749.  
  750.    vec4 bilerp (sampler2D sam, vec2 uv, vec2 tsize) {
  751.        vec2 st = uv / tsize - 0.5;
  752.  
  753.        vec2 iuv = floor(st);
  754.        vec2 fuv = fract(st);
  755.  
  756.        vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize);
  757.        vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize);
  758.        vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize);
  759.        vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize);
  760.  
  761.        return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y);
  762.    }
  763.  
  764.    void main () {
  765.        vec2 coord = vUv - dt * bilerp(uVelocity, vUv, texelSize).xy * texelSize;
  766.        gl_FragColor = dissipation * bilerp(uSource, coord, dyeTexelSize);
  767.        gl_FragColor.a = 1.0;
  768.    }
  769. `);
  770.  
  771. const advectionShader = compileShader(gl.FRAGMENT_SHADER, `
  772.    precision highp float;
  773.    precision highp sampler2D;
  774.  
  775.    varying vec2 vUv;
  776.    uniform sampler2D uVelocity;
  777.    uniform sampler2D uSource;
  778.    uniform vec2 texelSize;
  779.    uniform float dt;
  780.    uniform float dissipation;
  781.  
  782.    void main () {
  783.        vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize;
  784.        gl_FragColor = dissipation * texture2D(uSource, coord);
  785.        gl_FragColor.a = 1.0;
  786.    }
  787. `);
  788.  
  789. const divergenceShader = compileShader(gl.FRAGMENT_SHADER, `
  790.    precision mediump float;
  791.    precision mediump sampler2D;
  792.  
  793.    varying highp vec2 vUv;
  794.    varying highp vec2 vL;
  795.    varying highp vec2 vR;
  796.    varying highp vec2 vT;
  797.    varying highp vec2 vB;
  798.    uniform sampler2D uVelocity;
  799.  
  800.    void main () {
  801.        float L = texture2D(uVelocity, vL).x;
  802.        float R = texture2D(uVelocity, vR).x;
  803.        float T = texture2D(uVelocity, vT).y;
  804.        float B = texture2D(uVelocity, vB).y;
  805.  
  806.        vec2 C = texture2D(uVelocity, vUv).xy;
  807.        if (vL.x < 0.0) { L = -C.x; }
  808.        if (vR.x > 1.0) { R = -C.x; }
  809.         if (vT.y > 1.0) { T = -C.y; }
  810.         if (vB.y < 0.0) { B = -C.y; }
  811.  
  812.        float div = 0.5 * (R - L + T - B);
  813.        gl_FragColor = vec4(div, 0.0, 0.0, 1.0);
  814.    }
  815. `);
  816.  
  817. const curlShader = compileShader(gl.FRAGMENT_SHADER, `
  818.    precision mediump float;
  819.    precision mediump sampler2D;
  820.  
  821.    varying highp vec2 vUv;
  822.    varying highp vec2 vL;
  823.    varying highp vec2 vR;
  824.    varying highp vec2 vT;
  825.    varying highp vec2 vB;
  826.    uniform sampler2D uVelocity;
  827.  
  828.    void main () {
  829.        float L = texture2D(uVelocity, vL).y;
  830.        float R = texture2D(uVelocity, vR).y;
  831.        float T = texture2D(uVelocity, vT).x;
  832.        float B = texture2D(uVelocity, vB).x;
  833.        float vorticity = R - L - T + B;
  834.        gl_FragColor = vec4(0.5 * vorticity, 0.0, 0.0, 1.0);
  835.    }
  836. `);
  837.  
  838. const vorticityShader = compileShader(gl.FRAGMENT_SHADER, `
  839.    precision highp float;
  840.    precision highp sampler2D;
  841.  
  842.    varying vec2 vUv;
  843.    varying vec2 vL;
  844.    varying vec2 vR;
  845.    varying vec2 vT;
  846.    varying vec2 vB;
  847.    uniform sampler2D uVelocity;
  848.    uniform sampler2D uCurl;
  849.    uniform float curl;
  850.    uniform float dt;
  851.  
  852.    void main () {
  853.        float L = texture2D(uCurl, vL).x;
  854.        float R = texture2D(uCurl, vR).x;
  855.        float T = texture2D(uCurl, vT).x;
  856.        float B = texture2D(uCurl, vB).x;
  857.        float C = texture2D(uCurl, vUv).x;
  858.  
  859.        vec2 force = 0.5 * vec2(abs(T) - abs(B), abs(R) - abs(L));
  860.        force /= length(force) + 0.0001;
  861.        force *= curl * C;
  862.        force.y *= -1.0;
  863.  
  864.        vec2 vel = texture2D(uVelocity, vUv).xy;
  865.        gl_FragColor = vec4(vel + force * dt, 0.0, 1.0);
  866.    }
  867. `);
  868.  
  869. const pressureShader = compileShader(gl.FRAGMENT_SHADER, `
  870.    precision mediump float;
  871.    precision mediump sampler2D;
  872.  
  873.    varying highp vec2 vUv;
  874.    varying highp vec2 vL;
  875.    varying highp vec2 vR;
  876.    varying highp vec2 vT;
  877.    varying highp vec2 vB;
  878.    uniform sampler2D uPressure;
  879.    uniform sampler2D uDivergence;
  880.  
  881.    vec2 boundary (vec2 uv) {
  882.        return uv;
  883.        // uncomment if you use wrap or repeat texture mode
  884.        // uv = min(max(uv, 0.0), 1.0);
  885.        // return uv;
  886.    }
  887.  
  888.    void main () {
  889.        float L = texture2D(uPressure, boundary(vL)).x;
  890.        float R = texture2D(uPressure, boundary(vR)).x;
  891.        float T = texture2D(uPressure, boundary(vT)).x;
  892.        float B = texture2D(uPressure, boundary(vB)).x;
  893.        float C = texture2D(uPressure, vUv).x;
  894.        float divergence = texture2D(uDivergence, vUv).x;
  895.        float pressure = (L + R + B + T - divergence) * 0.25;
  896.        gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0);
  897.    }
  898. `);
  899.  
  900. const gradientSubtractShader = compileShader(gl.FRAGMENT_SHADER, `
  901.    precision mediump float;
  902.    precision mediump sampler2D;
  903.  
  904.    varying highp vec2 vUv;
  905.    varying highp vec2 vL;
  906.    varying highp vec2 vR;
  907.    varying highp vec2 vT;
  908.    varying highp vec2 vB;
  909.    uniform sampler2D uPressure;
  910.    uniform sampler2D uVelocity;
  911.  
  912.    vec2 boundary (vec2 uv) {
  913.        return uv;
  914.        // uv = min(max(uv, 0.0), 1.0);
  915.        // return uv;
  916.    }
  917.  
  918.    void main () {
  919.        float L = texture2D(uPressure, boundary(vL)).x;
  920.        float R = texture2D(uPressure, boundary(vR)).x;
  921.        float T = texture2D(uPressure, boundary(vT)).x;
  922.        float B = texture2D(uPressure, boundary(vB)).x;
  923.        vec2 velocity = texture2D(uVelocity, vUv).xy;
  924.        velocity.xy -= vec2(R - L, T - B);
  925.        gl_FragColor = vec4(velocity, 0.0, 1.0);
  926.    }
  927. `);
  928.  
  929. const blit = (() => {
  930.     gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
  931.     gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW);
  932.     gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
  933.     gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW);
  934.     gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
  935.     gl.enableVertexAttribArray(0);
  936.  
  937.     return (destination) => {
  938.         gl.bindFramebuffer(gl.FRAMEBUFFER, destination);
  939.         gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
  940.     }
  941. })();
  942.  
  943. let simWidth;
  944. let simHeight;
  945. let dyeWidth;
  946. let dyeHeight;
  947. let density;
  948. let velocity;
  949. let divergence;
  950. let curl;
  951. let pressure;
  952. let bloom;
  953.  
  954. let ditheringTexture = createTextureAsync('LDR_RGB1_0.png');
  955.  
  956. const clearProgram               = new GLProgram(baseVertexShader, clearShader);
  957. const colorProgram               = new GLProgram(baseVertexShader, colorShader);
  958. const backgroundProgram          = new GLProgram(baseVertexShader, backgroundShader);
  959. const displayProgram             = new GLProgram(baseVertexShader, displayShader);
  960. const displayBloomProgram        = new GLProgram(baseVertexShader, displayBloomShader);
  961. const displayShadingProgram      = new GLProgram(baseVertexShader, displayShadingShader);
  962. const displayBloomShadingProgram = new GLProgram(baseVertexShader, displayBloomShadingShader);
  963. const bloomPrefilterProgram      = new GLProgram(baseVertexShader, bloomPrefilterShader);
  964. const bloomBlurProgram           = new GLProgram(baseVertexShader, bloomBlurShader);
  965. const bloomFinalProgram          = new GLProgram(baseVertexShader, bloomFinalShader);
  966. const splatProgram               = new GLProgram(baseVertexShader, splatShader);
  967. const advectionProgram           = new GLProgram(baseVertexShader, ext.supportLinearFiltering ? advectionShader : advectionManualFilteringShader);
  968. const divergenceProgram          = new GLProgram(baseVertexShader, divergenceShader);
  969. const curlProgram                = new GLProgram(baseVertexShader, curlShader);
  970. const vorticityProgram           = new GLProgram(baseVertexShader, vorticityShader);
  971. const pressureProgram            = new GLProgram(baseVertexShader, pressureShader);
  972. const gradienSubtractProgram     = new GLProgram(baseVertexShader, gradientSubtractShader);
  973.  
  974. function initFramebuffers () {
  975.     let simRes = getResolution(config.SIM_RESOLUTION);
  976.     let dyeRes = getResolution(config.DYE_RESOLUTION);
  977.  
  978.     simWidth  = simRes.width;
  979.     simHeight = simRes.height;
  980.     dyeWidth  = dyeRes.width;
  981.     dyeHeight = dyeRes.height;
  982.  
  983.     const texType = ext.halfFloatTexType;
  984.     const rgba    = ext.formatRGBA;
  985.     const rg      = ext.formatRG;
  986.     const r       = ext.formatR;
  987.     const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST;
  988.  
  989.     if (density == null)
  990.         density = createDoubleFBO(dyeWidth, dyeHeight, rgba.internalFormat, rgba.format, texType, filtering);
  991.     else
  992.         density = resizeDoubleFBO(density, dyeWidth, dyeHeight, rgba.internalFormat, rgba.format, texType, filtering);
  993.  
  994.     if (velocity == null)
  995.         velocity = createDoubleFBO(simWidth, simHeight, rg.internalFormat, rg.format, texType, filtering);
  996.     else
  997.         velocity = resizeDoubleFBO(velocity, simWidth, simHeight, rg.internalFormat, rg.format, texType, filtering);
  998.  
  999.     divergence = createFBO      (simWidth, simHeight, r.internalFormat, r.format, texType, gl.NEAREST);
  1000.     curl       = createFBO      (simWidth, simHeight, r.internalFormat, r.format, texType, gl.NEAREST);
  1001.     pressure   = createDoubleFBO(simWidth, simHeight, r.internalFormat, r.format, texType, gl.NEAREST);
  1002.  
  1003.     initBloomFramebuffers();
  1004. }
  1005.  
  1006. function initBloomFramebuffers () {
  1007.     let res = getResolution(config.BLOOM_RESOLUTION);
  1008.  
  1009.     const texType = ext.halfFloatTexType;
  1010.     const rgba = ext.formatRGBA;
  1011.     const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST;
  1012.  
  1013.     bloom = createFBO(res.width, res.height, rgba.internalFormat, rgba.format, texType, filtering);
  1014.  
  1015.     bloomFramebuffers.length = 0;
  1016.     for (let i = 0; i < config.BLOOM_ITERATIONS; i++)
  1017.    {
  1018.        let width = res.width >> (i + 1);
  1019.         let height = res.height >> (i + 1);
  1020.  
  1021.         if (width < 2 || height < 2) break;
  1022.  
  1023.        let fbo = createFBO(width, height, rgba.internalFormat, rgba.format, texType, filtering);
  1024.        bloomFramebuffers.push(fbo);
  1025.    }
  1026. }
  1027.  
  1028. function createFBO (w, h, internalFormat, format, type, param) {
  1029.    gl.activeTexture(gl.TEXTURE0);
  1030.    let texture = gl.createTexture();
  1031.    gl.bindTexture(gl.TEXTURE_2D, texture);
  1032.    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param);
  1033.    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param);
  1034.    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  1035.    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  1036.    gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null);
  1037.  
  1038.    let fbo = gl.createFramebuffer();
  1039.    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
  1040.    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
  1041.    gl.viewport(0, 0, w, h);
  1042.    gl.clear(gl.COLOR_BUFFER_BIT);
  1043.  
  1044.    return {
  1045.        texture,
  1046.        fbo,
  1047.        width: w,
  1048.        height: h,
  1049.        attach (id) {
  1050.            gl.activeTexture(gl.TEXTURE0 + id);
  1051.            gl.bindTexture(gl.TEXTURE_2D, texture);
  1052.            return id;
  1053.        }
  1054.    };
  1055. }
  1056.  
  1057. function createDoubleFBO (w, h, internalFormat, format, type, param) {
  1058.    let fbo1 = createFBO(w, h, internalFormat, format, type, param);
  1059.    let fbo2 = createFBO(w, h, internalFormat, format, type, param);
  1060.  
  1061.    return {
  1062.        get read () {
  1063.            return fbo1;
  1064.        },
  1065.        set read (value) {
  1066.            fbo1 = value;
  1067.        },
  1068.        get write () {
  1069.            return fbo2;
  1070.        },
  1071.        set write (value) {
  1072.            fbo2 = value;
  1073.        },
  1074.        swap () {
  1075.            let temp = fbo1;
  1076.            fbo1 = fbo2;
  1077.            fbo2 = temp;
  1078.        }
  1079.    }
  1080. }
  1081.  
  1082. function resizeFBO (target, w, h, internalFormat, format, type, param) {
  1083.    let newFBO = createFBO(w, h, internalFormat, format, type, param);
  1084.    clearProgram.bind();
  1085.    gl.uniform1i(clearProgram.uniforms.uTexture, target.attach(0));
  1086.    gl.uniform1f(clearProgram.uniforms.value, 1);
  1087.    blit(newFBO.fbo);
  1088.    return newFBO;
  1089. }
  1090.  
  1091. function resizeDoubleFBO (target, w, h, internalFormat, format, type, param) {
  1092.    target.read = resizeFBO(target.read, w, h, internalFormat, format, type, param);
  1093.    target.write = createFBO(w, h, internalFormat, format, type, param);
  1094.    return target;
  1095. }
  1096.  
  1097. function createTextureAsync (url) {
  1098.    let texture = gl.createTexture();
  1099.    gl.bindTexture(gl.TEXTURE_2D, texture);
  1100.    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  1101.    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  1102.    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
  1103.    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
  1104.    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255]));
  1105.  
  1106.    let obj = {
  1107.        texture,
  1108.        width: 1,
  1109.        height: 1,
  1110.        attach (id) {
  1111.            gl.activeTexture(gl.TEXTURE0 + id);
  1112.            gl.bindTexture(gl.TEXTURE_2D, texture);
  1113.            return id;
  1114.        }
  1115.    };
  1116.  
  1117.    let image = new Image();
  1118.    image.onload = () => {
  1119.         obj.width = image.width;
  1120.         obj.height = image.height;
  1121.         gl.bindTexture(gl.TEXTURE_2D, texture);
  1122.         gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
  1123.     };
  1124.     image.src = url;
  1125.  
  1126.     return obj;
  1127. }
  1128.  
  1129. initFramebuffers();
  1130. multipleSplats(parseInt(Math.random() * 20) + 3);
  1131.  
  1132. let lastColorChangeTime = Date.now();
  1133.  
  1134. update();
  1135.  
  1136. function update () {
  1137.     resizeCanvas();
  1138.     input();
  1139.     if (!config.PAUSED)
  1140.         step(0.016);
  1141.     render(null);
  1142.     requestAnimationFrame(update);
  1143. }
  1144.  
  1145. function input () {
  1146.     if (splatStack.length > 0)
  1147.         multipleSplats(splatStack.pop());
  1148.  
  1149.     for (let i = 0; i < pointers.length; i++) {
  1150.        const p = pointers[i];
  1151.        if (p.moved) {
  1152.            splat(p.x, p.y, p.dx, p.dy, p.color);
  1153.            p.moved = false;
  1154.        }
  1155.    }
  1156.  
  1157.    if (lastColorChangeTime + 100 < Date.now())
  1158.    {
  1159.        lastColorChangeTime = Date.now();
  1160.        for (let i = 0; i < pointers.length; i++) {
  1161.            const p = pointers[i];
  1162.            p.color = config.COLORFUL ? generateColor() : config.POINTER_COLOR.getRandom();
  1163.        }
  1164.    }
  1165. }
  1166.  
  1167. function step (dt) {
  1168.    gl.disable(gl.BLEND);
  1169.    gl.viewport(0, 0, simWidth, simHeight);
  1170.  
  1171.    curlProgram.bind();
  1172.    gl.uniform2f(curlProgram.uniforms.texelSize, 1.0 / simWidth, 1.0 / simHeight);
  1173.    gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.read.attach(0));
  1174.    blit(curl.fbo);
  1175.  
  1176.    vorticityProgram.bind();
  1177.    gl.uniform2f(vorticityProgram.uniforms.texelSize, 1.0 / simWidth, 1.0 / simHeight);
  1178.    gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.read.attach(0));
  1179.    gl.uniform1i(vorticityProgram.uniforms.uCurl, curl.attach(1));
  1180.    gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL);
  1181.    gl.uniform1f(vorticityProgram.uniforms.dt, dt);
  1182.    blit(velocity.write.fbo);
  1183.    velocity.swap();
  1184.  
  1185.    divergenceProgram.bind();
  1186.    gl.uniform2f(divergenceProgram.uniforms.texelSize, 1.0 / simWidth, 1.0 / simHeight);
  1187.    gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.read.attach(0));
  1188.    blit(divergence.fbo);
  1189.  
  1190.    clearProgram.bind();
  1191.    gl.uniform1i(clearProgram.uniforms.uTexture, pressure.read.attach(0));
  1192.    gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE_DISSIPATION);
  1193.    blit(pressure.write.fbo);
  1194.    pressure.swap();
  1195.  
  1196.    pressureProgram.bind();
  1197.    gl.uniform2f(pressureProgram.uniforms.texelSize, 1.0 / simWidth, 1.0 / simHeight);
  1198.    gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0));
  1199.    for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) {
  1200.        gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1));
  1201.        blit(pressure.write.fbo);
  1202.        pressure.swap();
  1203.    }
  1204.  
  1205.    gradienSubtractProgram.bind();
  1206.    gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, 1.0 / simWidth, 1.0 / simHeight);
  1207.    gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.read.attach(0));
  1208.    gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.read.attach(1));
  1209.    blit(velocity.write.fbo);
  1210.    velocity.swap();
  1211.  
  1212.    advectionProgram.bind();
  1213.    gl.uniform2f(advectionProgram.uniforms.texelSize, 1.0 / simWidth, 1.0 / simHeight);
  1214.    if (!ext.supportLinearFiltering)
  1215.        gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, 1.0 / simWidth, 1.0 / simHeight);
  1216.    let velocityId = velocity.read.attach(0);
  1217.    gl.uniform1i(advectionProgram.uniforms.uVelocity, velocityId);
  1218.    gl.uniform1i(advectionProgram.uniforms.uSource, velocityId);
  1219.    gl.uniform1f(advectionProgram.uniforms.dt, dt);
  1220.    gl.uniform1f(advectionProgram.uniforms.dissipation, config.VELOCITY_DISSIPATION);
  1221.    blit(velocity.write.fbo);
  1222.    velocity.swap();
  1223.  
  1224.    gl.viewport(0, 0, dyeWidth, dyeHeight);
  1225.  
  1226.    if (!ext.supportLinearFiltering)
  1227.        gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, 1.0 / dyeWidth, 1.0 / dyeHeight);
  1228.    gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.read.attach(0));
  1229.    gl.uniform1i(advectionProgram.uniforms.uSource, density.read.attach(1));
  1230.    gl.uniform1f(advectionProgram.uniforms.dissipation, config.DENSITY_DISSIPATION);
  1231.    blit(density.write.fbo);
  1232.    density.swap();
  1233. }
  1234.  
  1235. function render (target) {
  1236.    if (config.BLOOM)
  1237.        applyBloom(density.read, bloom);
  1238.  
  1239.    if (target == null || !config.TRANSPARENT) {
  1240.        gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
  1241.        gl.enable(gl.BLEND);
  1242.    }
  1243.    else {
  1244.        gl.disable(gl.BLEND);
  1245.    }
  1246.  
  1247.    let width  = target == null ? gl.drawingBufferWidth : dyeWidth;
  1248.    let height = target == null ? gl.drawingBufferHeight : dyeHeight;
  1249.  
  1250.    gl.viewport(0, 0, width, height);
  1251.  
  1252.    if (!config.TRANSPARENT) {
  1253.        colorProgram.bind();
  1254.        let bc = config.BACK_COLOR;
  1255.        gl.uniform4f(colorProgram.uniforms.color, bc.r / 255, bc.g / 255, bc.b / 255, 1);
  1256.        blit(target);
  1257.    }
  1258.  
  1259.    if (target == null && config.TRANSPARENT) {
  1260.        backgroundProgram.bind();
  1261.        gl.uniform1f(backgroundProgram.uniforms.aspectRatio, canvas.width / canvas.height);
  1262.        blit(null);
  1263.    }
  1264.  
  1265.    if (config.SHADING) {
  1266.        let program = config.BLOOM ? displayBloomShadingProgram : displayShadingProgram;
  1267.        program.bind();
  1268.        gl.uniform2f(program.uniforms.texelSize, 1.0 / width, 1.0 / height);
  1269.        gl.uniform1i(program.uniforms.uTexture, density.read.attach(0));
  1270.        if (config.BLOOM) {
  1271.            gl.uniform1i(program.uniforms.uBloom, bloom.attach(1));
  1272.            gl.uniform1i(program.uniforms.uDithering, ditheringTexture.attach(2));
  1273.            let scale = getTextureScale(ditheringTexture, width, height);
  1274.            gl.uniform2f(program.uniforms.ditherScale, scale.x, scale.y);
  1275.        }
  1276.    }
  1277.    else {
  1278.        let program = config.BLOOM ? displayBloomProgram : displayProgram;
  1279.        program.bind();
  1280.        gl.uniform1i(program.uniforms.uTexture, density.read.attach(0));
  1281.        if (config.BLOOM) {
  1282.            gl.uniform1i(program.uniforms.uBloom, bloom.attach(1));
  1283.            gl.uniform1i(program.uniforms.uDithering, ditheringTexture.attach(2));
  1284.            let scale = getTextureScale(ditheringTexture, width, height);
  1285.            gl.uniform2f(program.uniforms.ditherScale, scale.x, scale.y);
  1286.        }
  1287.    }
  1288.  
  1289.    blit(target);
  1290. }
  1291.  
  1292. function applyBloom (source, destination) {
  1293.    if (bloomFramebuffers.length < 2)
  1294.        return;
  1295.  
  1296.    let last = destination;
  1297.  
  1298.    gl.disable(gl.BLEND);
  1299.    bloomPrefilterProgram.bind();
  1300.    let knee = config.BLOOM_THRESHOLD * config.BLOOM_SOFT_KNEE + 0.0001;
  1301.    let curve0 = config.BLOOM_THRESHOLD - knee;
  1302.    let curve1 = knee * 2;
  1303.    let curve2 = 0.25 / knee;
  1304.    gl.uniform3f(bloomPrefilterProgram.uniforms.curve, curve0, curve1, curve2);
  1305.    gl.uniform1f(bloomPrefilterProgram.uniforms.threshold, config.BLOOM_THRESHOLD);
  1306.    gl.uniform1i(bloomPrefilterProgram.uniforms.uTexture, source.attach(0));
  1307.    gl.viewport(0, 0, last.width, last.height);
  1308.    blit(last.fbo);
  1309.  
  1310.    bloomBlurProgram.bind();
  1311.    for (let i = 0; i < bloomFramebuffers.length; i++) {
  1312.        let dest = bloomFramebuffers[i];
  1313.        gl.uniform2f(bloomBlurProgram.uniforms.texelSize, 1.0 / last.width, 1.0 / last.height);
  1314.        gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0));
  1315.        gl.viewport(0, 0, dest.width, dest.height);
  1316.        blit(dest.fbo);
  1317.        last = dest;
  1318.    }
  1319.  
  1320.    gl.blendFunc(gl.ONE, gl.ONE);
  1321.    gl.enable(gl.BLEND);
  1322.  
  1323.    for (let i = bloomFramebuffers.length - 2; i >= 0; i--) {
  1324.         let baseTex = bloomFramebuffers[i];
  1325.         gl.uniform2f(bloomBlurProgram.uniforms.texelSize, 1.0 / last.width, 1.0 / last.height);
  1326.         gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0));
  1327.         gl.viewport(0, 0, baseTex.width, baseTex.height);
  1328.         blit(baseTex.fbo);
  1329.         last = baseTex;
  1330.     }
  1331.  
  1332.     gl.disable(gl.BLEND);
  1333.     bloomFinalProgram.bind();
  1334.     gl.uniform2f(bloomFinalProgram.uniforms.texelSize, 1.0 / last.width, 1.0 / last.height);
  1335.     gl.uniform1i(bloomFinalProgram.uniforms.uTexture, last.attach(0));
  1336.     gl.uniform1f(bloomFinalProgram.uniforms.intensity, config.BLOOM_INTENSITY);
  1337.     gl.viewport(0, 0, destination.width, destination.height);
  1338.     blit(destination.fbo);
  1339. }
  1340.  
  1341. function splat (x, y, dx, dy, color) {
  1342.     gl.viewport(0, 0, simWidth, simHeight);
  1343.     splatProgram.bind();
  1344.     gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0));
  1345.     gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height);
  1346.     gl.uniform2f(splatProgram.uniforms.point, x / canvas.width, 1.0 - y / canvas.height);
  1347.     gl.uniform3f(splatProgram.uniforms.color, dx, -dy, 1.0);
  1348.     gl.uniform1f(splatProgram.uniforms.radius, config.SPLAT_RADIUS / 100.0);
  1349.     blit(velocity.write.fbo);
  1350.     velocity.swap();
  1351.  
  1352.     gl.viewport(0, 0, dyeWidth, dyeHeight);
  1353.     gl.uniform1i(splatProgram.uniforms.uTarget, density.read.attach(0));
  1354.     gl.uniform3f(splatProgram.uniforms.color, color.r, color.g, color.b);
  1355.     blit(density.write.fbo);
  1356.     density.swap();
  1357. }
  1358.  
  1359. function multipleSplats (amount) {
  1360.     for (let i = 0; i < amount; i++) {
  1361.        const color = config.COLORFUL ? generateColor() : Object.assign({}, config.POINTER_COLOR.getRandom());
  1362.        color.r *= 10.0;
  1363.        color.g *= 10.0;
  1364.        color.b *= 10.0;
  1365.        const x = canvas.width * Math.random();
  1366.        const y = canvas.height * Math.random();
  1367.        const dx = 1000 * (Math.random() - 0.5);
  1368.        const dy = 1000 * (Math.random() - 0.5);
  1369.        splat(x, y, dx, dy, color);
  1370.    }
  1371. }
  1372.  
  1373. function resizeCanvas () {
  1374.    if (canvas.width != canvas.clientWidth || canvas.height != canvas.clientHeight) {
  1375.        canvas.width = canvas.clientWidth;
  1376.        canvas.height = canvas.clientHeight;
  1377.        initFramebuffers();
  1378.    }
  1379. }
  1380.  
  1381. canvas.addEventListener('mousemove', e => {
  1382.     pointers[0].moved = pointers[0].down;
  1383.     pointers[0].dx = (e.offsetX - pointers[0].x) * 5.0;
  1384.     pointers[0].dy = (e.offsetY - pointers[0].y) * 5.0;
  1385.     pointers[0].x = e.offsetX;
  1386.     pointers[0].y = e.offsetY;
  1387. });
  1388.  
  1389. canvas.addEventListener('touchmove', e => {
  1390.     e.preventDefault();
  1391.     const touches = e.targetTouches;
  1392.     for (let i = 0; i < touches.length; i++) {
  1393.        let pointer = pointers[i];
  1394.        pointer.moved = pointer.down;
  1395.        pointer.dx = (touches[i].pageX - pointer.x) * 8.0;
  1396.        pointer.dy = (touches[i].pageY - pointer.y) * 8.0;
  1397.        pointer.x = touches[i].pageX;
  1398.        pointer.y = touches[i].pageY;
  1399.    }
  1400. }, false);
  1401.  
  1402. canvas.addEventListener('mouseenter', () => {
  1403.     pointers[0].down = true;
  1404.     pointers[0].color = config.POINTER_COLOR.getRandom();
  1405. });
  1406.  
  1407. canvas.addEventListener('touchstart', e => {
  1408.     e.preventDefault();
  1409.     const touches = e.targetTouches;
  1410.     for (let i = 0; i < touches.length; i++) {
  1411.        if (i >= pointers.length)
  1412.             pointers.push(new pointerPrototype());
  1413.  
  1414.         pointers[i].id = touches[i].identifier;
  1415.         pointers[i].down = true;
  1416.         pointers[i].x = touches[i].pageX;
  1417.         pointers[i].y = touches[i].pageY;
  1418.         pointers[i].color = config.POINTER_COLOR.getRandom();
  1419.     }
  1420. });
  1421.  
  1422. canvas.addEventListener("mousedown", () => {
  1423.     multipleSplats(parseInt(Math.random() * 20) + 5);
  1424. });
  1425.  
  1426. window.addEventListener('mouseleave', () => {
  1427.     pointers[0].down = false;
  1428. });
  1429.  
  1430. window.addEventListener('touchend', e => {
  1431.     const touches = e.changedTouches;
  1432.     for (let i = 0; i < touches.length; i++)
  1433.        for (let j = 0; j < pointers.length; j++)
  1434.            if (touches[i].identifier == pointers[j].id)
  1435.                pointers[j].down = false;
  1436. });
  1437.  
  1438. window.addEventListener('keydown', e => {
  1439.     if (e.code === 'KeyP')
  1440.         config.PAUSED = !config.PAUSED;
  1441.     if (e.key === ' ')
  1442.         splatStack.push(parseInt(Math.random() * 20) + 5);
  1443. });
  1444.  
  1445. function generateColor () {
  1446.     let c = HSVtoRGB(Math.random(), 1.0, 1.0);
  1447.     c.r *= 0.15;
  1448.     c.g *= 0.15;
  1449.     c.b *= 0.15;
  1450.     return c;
  1451. }
  1452.  
  1453. function HSVtoRGB (h, s, v) {
  1454.     let r, g, b, i, f, p, q, t;
  1455.     i = Math.floor(h * 6);
  1456.     f = h * 6 - i;
  1457.     p = v * (1 - s);
  1458.     q = v * (1 - f * s);
  1459.     t = v * (1 - (1 - f) * s);
  1460.  
  1461.     switch (i % 6) {
  1462.         case 0: r = v, g = t, b = p; break;
  1463.         case 1: r = q, g = v, b = p; break;
  1464.         case 2: r = p, g = v, b = t; break;
  1465.         case 3: r = p, g = q, b = v; break;
  1466.         case 4: r = t, g = p, b = v; break;
  1467.         case 5: r = v, g = p, b = q; break;
  1468.     }
  1469.  
  1470.     return {
  1471.         r,
  1472.         g,
  1473.         b
  1474.     };
  1475. }
  1476.  
  1477. function RGBToHue(r, g, b) {
  1478.   // Find greatest and smallest channel values
  1479.   let cmin = Math.min(r,g,b),
  1480.       cmax = Math.max(r,g,b),
  1481.       delta = cmax - cmin,
  1482.       h = 0,
  1483.       s = 0,
  1484.       l = 0;
  1485.  
  1486.   // Calculate hue
  1487.   // No difference
  1488.   if (delta == 0)
  1489.     h = 0;
  1490.   // Red is max
  1491.   else if (cmax == r)
  1492.     h = ((g - b) / delta) % 6;
  1493.   // Green is max
  1494.   else if (cmax == g)
  1495.     h = (b - r) / delta + 2;
  1496.   // Blue is max
  1497.   else
  1498.     h = (r - g) / delta + 4;
  1499.  
  1500.   h = Math.round(h * 60);
  1501.    
  1502.   // Make negative hues positive behind 360°
  1503.   if (h < 0)
  1504.      h += 360;
  1505.  
  1506.  return h;
  1507. }
  1508.  
  1509. function getResolution (resolution) {
  1510.    let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight;
  1511.    if (aspectRatio < 1)
  1512.        aspectRatio = 1.0 / aspectRatio;
  1513.  
  1514.    let max = Math.round(resolution * aspectRatio);
  1515.    let min = Math.round(resolution);
  1516.  
  1517.    if (gl.drawingBufferWidth > gl.drawingBufferHeight)
  1518.         return { width: max, height: min };
  1519.     else
  1520.         return { width: min, height: max };
  1521. }
  1522.  
  1523. function getTextureScale (texture, width, height) {
  1524.     return {
  1525.         x: width / texture.width,
  1526.         y: height / texture.height
  1527.     };
  1528. }
  1529.  
  1530. function rgbToPointerColor(color) {
  1531.     let c = color.split(" ");
  1532.     let hue = RGBToHue(c[0], c[1], c[2]);
  1533.     let c2 = HSVtoRGB(hue/360, 1.0, 1.0);
  1534.     c2.r *= 0.15;
  1535.     c2.g *= 0.15;
  1536.     c2.b *= 0.15;
  1537.     return c2;
  1538. }
  1539.         </script>
  1540.     </body>
  1541. </html>