/ 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 };