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