Tecnhotron commited on
Commit
62a7201
·
1 Parent(s): a5d1816

Initial commit

Browse files
Files changed (2) hide show
  1. templates/index.html +1618 -1
  2. templates/script.js +0 -1618
templates/index.html CHANGED
@@ -240,7 +240,1624 @@
240
  </div>
241
  </div>
242
 
243
- <script src="./script.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
 
245
  <script>
246
  const form = document.getElementById('video-form');
 
240
  </div>
241
  </div>
242
 
243
+ <script>/*
244
+ MIT License
245
+
246
+ Copyright (c) 2017 Pavel Dobryakov
247
+
248
+ Permission is hereby granted, free of charge, to any person obtaining a copy
249
+ of this software and associated documentation files (the "Software"), to deal
250
+ in the Software without restriction, including without limitation the rights
251
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
252
+ copies of the Software, and to permit persons to whom the Software is
253
+ furnished to do so, subject to the following conditions:
254
+
255
+ The above copyright notice and this permission notice shall be included in all
256
+ copies or substantial portions of the Software.
257
+
258
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
259
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
260
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
261
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
262
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
263
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
264
+ SOFTWARE.
265
+ */
266
+
267
+ 'use strict';
268
+
269
+ // Simulation section
270
+
271
+ const canvas = document.getElementsByTagName('canvas')[0];
272
+ resizeCanvas();
273
+
274
+ let config = {
275
+ SIM_RESOLUTION: 128,
276
+ DYE_RESOLUTION: 1024,
277
+ CAPTURE_RESOLUTION: 512,
278
+ DENSITY_DISSIPATION: 1,
279
+ VELOCITY_DISSIPATION: 0.2,
280
+ PRESSURE: 0.8,
281
+ PRESSURE_ITERATIONS: 20,
282
+ CURL: 30,
283
+ SPLAT_RADIUS: 0.25,
284
+ SPLAT_FORCE: 6000,
285
+ SHADING: true,
286
+ COLORFUL: true,
287
+ COLOR_UPDATE_SPEED: 10,
288
+ PAUSED: false,
289
+ BACK_COLOR: { r: 0, g: 0, b: 0 },
290
+ TRANSPARENT: false,
291
+ BLOOM: true,
292
+ BLOOM_ITERATIONS: 8,
293
+ BLOOM_RESOLUTION: 256,
294
+ BLOOM_INTENSITY: 0.8,
295
+ BLOOM_THRESHOLD: 0.6,
296
+ BLOOM_SOFT_KNEE: 0.7,
297
+ SUNRAYS: true,
298
+ SUNRAYS_RESOLUTION: 196,
299
+ SUNRAYS_WEIGHT: 1.0,
300
+ }
301
+
302
+ function pointerPrototype () {
303
+ this.id = -1;
304
+ this.texcoordX = 0;
305
+ this.texcoordY = 0;
306
+ this.prevTexcoordX = 0;
307
+ this.prevTexcoordY = 0;
308
+ this.deltaX = 0;
309
+ this.deltaY = 0;
310
+ this.down = false;
311
+ this.moved = false;
312
+ this.color = [30, 0, 300];
313
+ }
314
+
315
+ let pointers = [];
316
+ let splatStack = [];
317
+ pointers.push(new pointerPrototype());
318
+
319
+ const { gl, ext } = getWebGLContext(canvas);
320
+
321
+ if (isMobile()) {
322
+ config.DYE_RESOLUTION = 512;
323
+ }
324
+ if (!ext.supportLinearFiltering) {
325
+ config.DYE_RESOLUTION = 512;
326
+ config.SHADING = false;
327
+ config.BLOOM = false;
328
+ config.SUNRAYS = false;
329
+ }
330
+
331
+ startGUI();
332
+
333
+ function getWebGLContext (canvas) {
334
+ const params = { alpha: true, depth: false, stencil: false, antialias: false, preserveDrawingBuffer: false };
335
+
336
+ let gl = canvas.getContext('webgl2', params);
337
+ const isWebGL2 = !!gl;
338
+ if (!isWebGL2)
339
+ gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params);
340
+
341
+ let halfFloat;
342
+ let supportLinearFiltering;
343
+ if (isWebGL2) {
344
+ gl.getExtension('EXT_color_buffer_float');
345
+ supportLinearFiltering = gl.getExtension('OES_texture_float_linear');
346
+ } else {
347
+ halfFloat = gl.getExtension('OES_texture_half_float');
348
+ supportLinearFiltering = gl.getExtension('OES_texture_half_float_linear');
349
+ }
350
+
351
+ gl.clearColor(0.0, 0.0, 0.0, 1.0);
352
+
353
+ const halfFloatTexType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES;
354
+ let formatRGBA;
355
+ let formatRG;
356
+ let formatR;
357
+
358
+ if (isWebGL2)
359
+ {
360
+ formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType);
361
+ formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType);
362
+ formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType);
363
+ }
364
+ else
365
+ {
366
+ formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
367
+ formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
368
+ formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
369
+ }
370
+
371
+ return {
372
+ gl,
373
+ ext: {
374
+ formatRGBA,
375
+ formatRG,
376
+ formatR,
377
+ halfFloatTexType,
378
+ supportLinearFiltering
379
+ }
380
+ };
381
+ }
382
+
383
+ function getSupportedFormat (gl, internalFormat, format, type)
384
+ {
385
+ if (!supportRenderTextureFormat(gl, internalFormat, format, type))
386
+ {
387
+ switch (internalFormat)
388
+ {
389
+ case gl.R16F:
390
+ return getSupportedFormat(gl, gl.RG16F, gl.RG, type);
391
+ case gl.RG16F:
392
+ return getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, type);
393
+ default:
394
+ return null;
395
+ }
396
+ }
397
+
398
+ return {
399
+ internalFormat,
400
+ format
401
+ }
402
+ }
403
+
404
+ function supportRenderTextureFormat (gl, internalFormat, format, type) {
405
+ let texture = gl.createTexture();
406
+ gl.bindTexture(gl.TEXTURE_2D, texture);
407
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
408
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
409
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
410
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
411
+ gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 4, 4, 0, format, type, null);
412
+
413
+ let fbo = gl.createFramebuffer();
414
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
415
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
416
+
417
+ let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
418
+ return status == gl.FRAMEBUFFER_COMPLETE;
419
+ }
420
+
421
+ function startGUI () {
422
+ }
423
+
424
+ function isMobile () {
425
+ return /Mobi|Android/i.test(navigator.userAgent);
426
+ }
427
+
428
+ function captureScreenshot () {
429
+ let res = getResolution(config.CAPTURE_RESOLUTION);
430
+ let target = createFBO(res.width, res.height, ext.formatRGBA.internalFormat, ext.formatRGBA.format, ext.halfFloatTexType, gl.NEAREST);
431
+ render(target);
432
+
433
+ let texture = framebufferToTexture(target);
434
+ texture = normalizeTexture(texture, target.width, target.height);
435
+
436
+ let captureCanvas = textureToCanvas(texture, target.width, target.height);
437
+ let datauri = captureCanvas.toDataURL();
438
+ downloadURI('fluid.png', datauri);
439
+ URL.revokeObjectURL(datauri);
440
+ }
441
+
442
+ function framebufferToTexture (target) {
443
+ gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);
444
+ let length = target.width * target.height * 4;
445
+ let texture = new Float32Array(length);
446
+ gl.readPixels(0, 0, target.width, target.height, gl.RGBA, gl.FLOAT, texture);
447
+ return texture;
448
+ }
449
+
450
+ function normalizeTexture (texture, width, height) {
451
+ let result = new Uint8Array(texture.length);
452
+ let id = 0;
453
+ for (let i = height - 1; i >= 0; i--) {
454
+ for (let j = 0; j < width; j++) {
455
+ let nid = i * width * 4 + j * 4;
456
+ result[nid + 0] = clamp01(texture[id + 0]) * 255;
457
+ result[nid + 1] = clamp01(texture[id + 1]) * 255;
458
+ result[nid + 2] = clamp01(texture[id + 2]) * 255;
459
+ result[nid + 3] = clamp01(texture[id + 3]) * 255;
460
+ id += 4;
461
+ }
462
+ }
463
+ return result;
464
+ }
465
+
466
+ function clamp01 (input) {
467
+ return Math.min(Math.max(input, 0), 1);
468
+ }
469
+
470
+ function textureToCanvas (texture, width, height) {
471
+ let captureCanvas = document.createElement('canvas');
472
+ let ctx = captureCanvas.getContext('2d');
473
+ captureCanvas.width = width;
474
+ captureCanvas.height = height;
475
+
476
+ let imageData = ctx.createImageData(width, height);
477
+ imageData.data.set(texture);
478
+ ctx.putImageData(imageData, 0, 0);
479
+
480
+ return captureCanvas;
481
+ }
482
+
483
+ function downloadURI (filename, uri) {
484
+ let link = document.createElement('a');
485
+ link.download = filename;
486
+ link.href = uri;
487
+ document.body.appendChild(link);
488
+ link.click();
489
+ document.body.removeChild(link);
490
+ }
491
+
492
+ class Material {
493
+ constructor (vertexShader, fragmentShaderSource) {
494
+ this.vertexShader = vertexShader;
495
+ this.fragmentShaderSource = fragmentShaderSource;
496
+ this.programs = [];
497
+ this.activeProgram = null;
498
+ this.uniforms = [];
499
+ }
500
+
501
+ setKeywords (keywords) {
502
+ let hash = 0;
503
+ for (let i = 0; i < keywords.length; i++)
504
+ hash += hashCode(keywords[i]);
505
+
506
+ let program = this.programs[hash];
507
+ if (program == null)
508
+ {
509
+ let fragmentShader = compileShader(gl.FRAGMENT_SHADER, this.fragmentShaderSource, keywords);
510
+ program = createProgram(this.vertexShader, fragmentShader);
511
+ this.programs[hash] = program;
512
+ }
513
+
514
+ if (program == this.activeProgram) return;
515
+
516
+ this.uniforms = getUniforms(program);
517
+ this.activeProgram = program;
518
+ }
519
+
520
+ bind () {
521
+ gl.useProgram(this.activeProgram);
522
+ }
523
+ }
524
+
525
+ class Program {
526
+ constructor (vertexShader, fragmentShader) {
527
+ this.uniforms = {};
528
+ this.program = createProgram(vertexShader, fragmentShader);
529
+ this.uniforms = getUniforms(this.program);
530
+ }
531
+
532
+ bind () {
533
+ gl.useProgram(this.program);
534
+ }
535
+ }
536
+
537
+ function createProgram (vertexShader, fragmentShader) {
538
+ let program = gl.createProgram();
539
+ gl.attachShader(program, vertexShader);
540
+ gl.attachShader(program, fragmentShader);
541
+ gl.linkProgram(program);
542
+
543
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS))
544
+ console.trace(gl.getProgramInfoLog(program));
545
+
546
+ return program;
547
+ }
548
+
549
+ function getUniforms (program) {
550
+ let uniforms = [];
551
+ let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
552
+ for (let i = 0; i < uniformCount; i++) {
553
+ let uniformName = gl.getActiveUniform(program, i).name;
554
+ uniforms[uniformName] = gl.getUniformLocation(program, uniformName);
555
+ }
556
+ return uniforms;
557
+ }
558
+
559
+ function compileShader (type, source, keywords) {
560
+ source = addKeywords(source, keywords);
561
+
562
+ const shader = gl.createShader(type);
563
+ gl.shaderSource(shader, source);
564
+ gl.compileShader(shader);
565
+
566
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
567
+ console.trace(gl.getShaderInfoLog(shader));
568
+
569
+ return shader;
570
+ };
571
+
572
+ function addKeywords (source, keywords) {
573
+ if (keywords == null) return source;
574
+ let keywordsString = '';
575
+ keywords.forEach(keyword => {
576
+ keywordsString += '#define ' + keyword + '\n';
577
+ });
578
+ return keywordsString + source;
579
+ }
580
+
581
+ const baseVertexShader = compileShader(gl.VERTEX_SHADER, `
582
+ precision highp float;
583
+
584
+ attribute vec2 aPosition;
585
+ varying vec2 vUv;
586
+ varying vec2 vL;
587
+ varying vec2 vR;
588
+ varying vec2 vT;
589
+ varying vec2 vB;
590
+ uniform vec2 texelSize;
591
+
592
+ void main () {
593
+ vUv = aPosition * 0.5 + 0.5;
594
+ vL = vUv - vec2(texelSize.x, 0.0);
595
+ vR = vUv + vec2(texelSize.x, 0.0);
596
+ vT = vUv + vec2(0.0, texelSize.y);
597
+ vB = vUv - vec2(0.0, texelSize.y);
598
+ gl_Position = vec4(aPosition, 0.0, 1.0);
599
+ }
600
+ `);
601
+
602
+ const blurVertexShader = compileShader(gl.VERTEX_SHADER, `
603
+ precision highp float;
604
+
605
+ attribute vec2 aPosition;
606
+ varying vec2 vUv;
607
+ varying vec2 vL;
608
+ varying vec2 vR;
609
+ uniform vec2 texelSize;
610
+
611
+ void main () {
612
+ vUv = aPosition * 0.5 + 0.5;
613
+ float offset = 1.33333333;
614
+ vL = vUv - texelSize * offset;
615
+ vR = vUv + texelSize * offset;
616
+ gl_Position = vec4(aPosition, 0.0, 1.0);
617
+ }
618
+ `);
619
+
620
+ const blurShader = compileShader(gl.FRAGMENT_SHADER, `
621
+ precision mediump float;
622
+ precision mediump sampler2D;
623
+
624
+ varying vec2 vUv;
625
+ varying vec2 vL;
626
+ varying vec2 vR;
627
+ uniform sampler2D uTexture;
628
+
629
+ void main () {
630
+ vec4 sum = texture2D(uTexture, vUv) * 0.29411764;
631
+ sum += texture2D(uTexture, vL) * 0.35294117;
632
+ sum += texture2D(uTexture, vR) * 0.35294117;
633
+ gl_FragColor = sum;
634
+ }
635
+ `);
636
+
637
+ const copyShader = compileShader(gl.FRAGMENT_SHADER, `
638
+ precision mediump float;
639
+ precision mediump sampler2D;
640
+
641
+ varying highp vec2 vUv;
642
+ uniform sampler2D uTexture;
643
+
644
+ void main () {
645
+ gl_FragColor = texture2D(uTexture, vUv);
646
+ }
647
+ `);
648
+
649
+ const clearShader = compileShader(gl.FRAGMENT_SHADER, `
650
+ precision mediump float;
651
+ precision mediump sampler2D;
652
+
653
+ varying highp vec2 vUv;
654
+ uniform sampler2D uTexture;
655
+ uniform float value;
656
+
657
+ void main () {
658
+ gl_FragColor = value * texture2D(uTexture, vUv);
659
+ }
660
+ `);
661
+
662
+ const colorShader = compileShader(gl.FRAGMENT_SHADER, `
663
+ precision mediump float;
664
+
665
+ uniform vec4 color;
666
+
667
+ void main () {
668
+ gl_FragColor = color;
669
+ }
670
+ `);
671
+
672
+ const checkerboardShader = compileShader(gl.FRAGMENT_SHADER, `
673
+ precision highp float;
674
+ precision highp sampler2D;
675
+
676
+ varying vec2 vUv;
677
+ uniform sampler2D uTexture;
678
+ uniform float aspectRatio;
679
+
680
+ #define SCALE 25.0
681
+
682
+ void main () {
683
+ vec2 uv = floor(vUv * SCALE * vec2(aspectRatio, 1.0));
684
+ float v = mod(uv.x + uv.y, 2.0);
685
+ v = v * 0.1 + 0.8;
686
+ gl_FragColor = vec4(vec3(v), 1.0);
687
+ }
688
+ `);
689
+
690
+ const displayShaderSource = `
691
+ precision highp float;
692
+ precision highp sampler2D;
693
+
694
+ varying vec2 vUv;
695
+ varying vec2 vL;
696
+ varying vec2 vR;
697
+ varying vec2 vT;
698
+ varying vec2 vB;
699
+ uniform sampler2D uTexture;
700
+ uniform sampler2D uBloom;
701
+ uniform sampler2D uSunrays;
702
+ uniform sampler2D uDithering;
703
+ uniform vec2 ditherScale;
704
+ uniform vec2 texelSize;
705
+
706
+ vec3 linearToGamma (vec3 color) {
707
+ color = max(color, vec3(0));
708
+ return max(1.055 * pow(color, vec3(0.416666667)) - 0.055, vec3(0));
709
+ }
710
+
711
+ void main () {
712
+ vec3 c = texture2D(uTexture, vUv).rgb;
713
+
714
+ #ifdef SHADING
715
+ vec3 lc = texture2D(uTexture, vL).rgb;
716
+ vec3 rc = texture2D(uTexture, vR).rgb;
717
+ vec3 tc = texture2D(uTexture, vT).rgb;
718
+ vec3 bc = texture2D(uTexture, vB).rgb;
719
+
720
+ float dx = length(rc) - length(lc);
721
+ float dy = length(tc) - length(bc);
722
+
723
+ vec3 n = normalize(vec3(dx, dy, length(texelSize)));
724
+ vec3 l = vec3(0.0, 0.0, 1.0);
725
+
726
+ float diffuse = clamp(dot(n, l) + 0.7, 0.7, 1.0);
727
+ c *= diffuse;
728
+ #endif
729
+
730
+ #ifdef BLOOM
731
+ vec3 bloom = texture2D(uBloom, vUv).rgb;
732
+ #endif
733
+
734
+ #ifdef SUNRAYS
735
+ float sunrays = texture2D(uSunrays, vUv).r;
736
+ c *= sunrays;
737
+ #ifdef BLOOM
738
+ bloom *= sunrays;
739
+ #endif
740
+ #endif
741
+
742
+ #ifdef BLOOM
743
+ float noise = texture2D(uDithering, vUv * ditherScale).r;
744
+ noise = noise * 2.0 - 1.0;
745
+ bloom += noise / 255.0;
746
+ bloom = linearToGamma(bloom);
747
+ c += bloom;
748
+ #endif
749
+
750
+ float a = max(c.r, max(c.g, c.b));
751
+ gl_FragColor = vec4(c, a);
752
+ }
753
+ `;
754
+
755
+ const bloomPrefilterShader = compileShader(gl.FRAGMENT_SHADER, `
756
+ precision mediump float;
757
+ precision mediump sampler2D;
758
+
759
+ varying vec2 vUv;
760
+ uniform sampler2D uTexture;
761
+ uniform vec3 curve;
762
+ uniform float threshold;
763
+
764
+ void main () {
765
+ vec3 c = texture2D(uTexture, vUv).rgb;
766
+ float br = max(c.r, max(c.g, c.b));
767
+ float rq = clamp(br - curve.x, 0.0, curve.y);
768
+ rq = curve.z * rq * rq;
769
+ c *= max(rq, br - threshold) / max(br, 0.0001);
770
+ gl_FragColor = vec4(c, 0.0);
771
+ }
772
+ `);
773
+
774
+ const bloomBlurShader = compileShader(gl.FRAGMENT_SHADER, `
775
+ precision mediump float;
776
+ precision mediump sampler2D;
777
+
778
+ varying vec2 vL;
779
+ varying vec2 vR;
780
+ varying vec2 vT;
781
+ varying vec2 vB;
782
+ uniform sampler2D uTexture;
783
+
784
+ void main () {
785
+ vec4 sum = vec4(0.0);
786
+ sum += texture2D(uTexture, vL);
787
+ sum += texture2D(uTexture, vR);
788
+ sum += texture2D(uTexture, vT);
789
+ sum += texture2D(uTexture, vB);
790
+ sum *= 0.25;
791
+ gl_FragColor = sum;
792
+ }
793
+ `);
794
+
795
+ const bloomFinalShader = compileShader(gl.FRAGMENT_SHADER, `
796
+ precision mediump float;
797
+ precision mediump sampler2D;
798
+
799
+ varying vec2 vL;
800
+ varying vec2 vR;
801
+ varying vec2 vT;
802
+ varying vec2 vB;
803
+ uniform sampler2D uTexture;
804
+ uniform float intensity;
805
+
806
+ void main () {
807
+ vec4 sum = vec4(0.0);
808
+ sum += texture2D(uTexture, vL);
809
+ sum += texture2D(uTexture, vR);
810
+ sum += texture2D(uTexture, vT);
811
+ sum += texture2D(uTexture, vB);
812
+ sum *= 0.25;
813
+ gl_FragColor = sum * intensity;
814
+ }
815
+ `);
816
+
817
+ const sunraysMaskShader = compileShader(gl.FRAGMENT_SHADER, `
818
+ precision highp float;
819
+ precision highp sampler2D;
820
+
821
+ varying vec2 vUv;
822
+ uniform sampler2D uTexture;
823
+
824
+ void main () {
825
+ vec4 c = texture2D(uTexture, vUv);
826
+ float br = max(c.r, max(c.g, c.b));
827
+ c.a = 1.0 - min(max(br * 20.0, 0.0), 0.8);
828
+ gl_FragColor = c;
829
+ }
830
+ `);
831
+
832
+ const sunraysShader = compileShader(gl.FRAGMENT_SHADER, `
833
+ precision highp float;
834
+ precision highp sampler2D;
835
+
836
+ varying vec2 vUv;
837
+ uniform sampler2D uTexture;
838
+ uniform float weight;
839
+
840
+ #define ITERATIONS 16
841
+
842
+ void main () {
843
+ float Density = 0.3;
844
+ float Decay = 0.95;
845
+ float Exposure = 0.7;
846
+
847
+ vec2 coord = vUv;
848
+ vec2 dir = vUv - 0.5;
849
+
850
+ dir *= 1.0 / float(ITERATIONS) * Density;
851
+ float illuminationDecay = 1.0;
852
+
853
+ float color = texture2D(uTexture, vUv).a;
854
+
855
+ for (int i = 0; i < ITERATIONS; i++)
856
+ {
857
+ coord -= dir;
858
+ float col = texture2D(uTexture, coord).a;
859
+ color += col * illuminationDecay * weight;
860
+ illuminationDecay *= Decay;
861
+ }
862
+
863
+ gl_FragColor = vec4(color * Exposure, 0.0, 0.0, 1.0);
864
+ }
865
+ `);
866
+
867
+ const splatShader = compileShader(gl.FRAGMENT_SHADER, `
868
+ precision highp float;
869
+ precision highp sampler2D;
870
+
871
+ varying vec2 vUv;
872
+ uniform sampler2D uTarget;
873
+ uniform float aspectRatio;
874
+ uniform vec3 color;
875
+ uniform vec2 point;
876
+ uniform float radius;
877
+
878
+ void main () {
879
+ vec2 p = vUv - point.xy;
880
+ p.x *= aspectRatio;
881
+ vec3 splat = exp(-dot(p, p) / radius) * color;
882
+ vec3 base = texture2D(uTarget, vUv).xyz;
883
+ gl_FragColor = vec4(base + splat, 1.0);
884
+ }
885
+ `);
886
+
887
+ const advectionShader = compileShader(gl.FRAGMENT_SHADER, `
888
+ precision highp float;
889
+ precision highp sampler2D;
890
+
891
+ varying vec2 vUv;
892
+ uniform sampler2D uVelocity;
893
+ uniform sampler2D uSource;
894
+ uniform vec2 texelSize;
895
+ uniform vec2 dyeTexelSize;
896
+ uniform float dt;
897
+ uniform float dissipation;
898
+
899
+ vec4 bilerp (sampler2D sam, vec2 uv, vec2 tsize) {
900
+ vec2 st = uv / tsize - 0.5;
901
+
902
+ vec2 iuv = floor(st);
903
+ vec2 fuv = fract(st);
904
+
905
+ vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize);
906
+ vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize);
907
+ vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize);
908
+ vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize);
909
+
910
+ return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y);
911
+ }
912
+
913
+ void main () {
914
+ #ifdef MANUAL_FILTERING
915
+ vec2 coord = vUv - dt * bilerp(uVelocity, vUv, texelSize).xy * texelSize;
916
+ vec4 result = bilerp(uSource, coord, dyeTexelSize);
917
+ #else
918
+ vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize;
919
+ vec4 result = texture2D(uSource, coord);
920
+ #endif
921
+ float decay = 1.0 + dissipation * dt;
922
+ gl_FragColor = result / decay;
923
+ }`,
924
+ ext.supportLinearFiltering ? null : ['MANUAL_FILTERING']
925
+ );
926
+
927
+ const divergenceShader = compileShader(gl.FRAGMENT_SHADER, `
928
+ precision mediump float;
929
+ precision mediump sampler2D;
930
+
931
+ varying highp vec2 vUv;
932
+ varying highp vec2 vL;
933
+ varying highp vec2 vR;
934
+ varying highp vec2 vT;
935
+ varying highp vec2 vB;
936
+ uniform sampler2D uVelocity;
937
+
938
+ void main () {
939
+ float L = texture2D(uVelocity, vL).x;
940
+ float R = texture2D(uVelocity, vR).x;
941
+ float T = texture2D(uVelocity, vT).y;
942
+ float B = texture2D(uVelocity, vB).y;
943
+
944
+ vec2 C = texture2D(uVelocity, vUv).xy;
945
+ if (vL.x < 0.0) { L = -C.x; }
946
+ if (vR.x > 1.0) { R = -C.x; }
947
+ if (vT.y > 1.0) { T = -C.y; }
948
+ if (vB.y < 0.0) { B = -C.y; }
949
+
950
+ float div = 0.5 * (R - L + T - B);
951
+ gl_FragColor = vec4(div, 0.0, 0.0, 1.0);
952
+ }
953
+ `);
954
+
955
+ const curlShader = compileShader(gl.FRAGMENT_SHADER, `
956
+ precision mediump float;
957
+ precision mediump sampler2D;
958
+
959
+ varying highp vec2 vUv;
960
+ varying highp vec2 vL;
961
+ varying highp vec2 vR;
962
+ varying highp vec2 vT;
963
+ varying highp vec2 vB;
964
+ uniform sampler2D uVelocity;
965
+
966
+ void main () {
967
+ float L = texture2D(uVelocity, vL).y;
968
+ float R = texture2D(uVelocity, vR).y;
969
+ float T = texture2D(uVelocity, vT).x;
970
+ float B = texture2D(uVelocity, vB).x;
971
+ float vorticity = R - L - T + B;
972
+ gl_FragColor = vec4(0.5 * vorticity, 0.0, 0.0, 1.0);
973
+ }
974
+ `);
975
+
976
+ const vorticityShader = compileShader(gl.FRAGMENT_SHADER, `
977
+ precision highp float;
978
+ precision highp sampler2D;
979
+
980
+ varying vec2 vUv;
981
+ varying vec2 vL;
982
+ varying vec2 vR;
983
+ varying vec2 vT;
984
+ varying vec2 vB;
985
+ uniform sampler2D uVelocity;
986
+ uniform sampler2D uCurl;
987
+ uniform float curl;
988
+ uniform float dt;
989
+
990
+ void main () {
991
+ float L = texture2D(uCurl, vL).x;
992
+ float R = texture2D(uCurl, vR).x;
993
+ float T = texture2D(uCurl, vT).x;
994
+ float B = texture2D(uCurl, vB).x;
995
+ float C = texture2D(uCurl, vUv).x;
996
+
997
+ vec2 force = 0.5 * vec2(abs(T) - abs(B), abs(R) - abs(L));
998
+ force /= length(force) + 0.0001;
999
+ force *= curl * C;
1000
+ force.y *= -1.0;
1001
+
1002
+ vec2 velocity = texture2D(uVelocity, vUv).xy;
1003
+ velocity += force * dt;
1004
+ velocity = min(max(velocity, -1000.0), 1000.0);
1005
+ gl_FragColor = vec4(velocity, 0.0, 1.0);
1006
+ }
1007
+ `);
1008
+
1009
+ const pressureShader = compileShader(gl.FRAGMENT_SHADER, `
1010
+ precision mediump float;
1011
+ precision mediump sampler2D;
1012
+
1013
+ varying highp vec2 vUv;
1014
+ varying highp vec2 vL;
1015
+ varying highp vec2 vR;
1016
+ varying highp vec2 vT;
1017
+ varying highp vec2 vB;
1018
+ uniform sampler2D uPressure;
1019
+ uniform sampler2D uDivergence;
1020
+
1021
+ void main () {
1022
+ float L = texture2D(uPressure, vL).x;
1023
+ float R = texture2D(uPressure, vR).x;
1024
+ float T = texture2D(uPressure, vT).x;
1025
+ float B = texture2D(uPressure, vB).x;
1026
+ float C = texture2D(uPressure, vUv).x;
1027
+ float divergence = texture2D(uDivergence, vUv).x;
1028
+ float pressure = (L + R + B + T - divergence) * 0.25;
1029
+ gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0);
1030
+ }
1031
+ `);
1032
+
1033
+ const gradientSubtractShader = compileShader(gl.FRAGMENT_SHADER, `
1034
+ precision mediump float;
1035
+ precision mediump sampler2D;
1036
+
1037
+ varying highp vec2 vUv;
1038
+ varying highp vec2 vL;
1039
+ varying highp vec2 vR;
1040
+ varying highp vec2 vT;
1041
+ varying highp vec2 vB;
1042
+ uniform sampler2D uPressure;
1043
+ uniform sampler2D uVelocity;
1044
+
1045
+ void main () {
1046
+ float L = texture2D(uPressure, vL).x;
1047
+ float R = texture2D(uPressure, vR).x;
1048
+ float T = texture2D(uPressure, vT).x;
1049
+ float B = texture2D(uPressure, vB).x;
1050
+ vec2 velocity = texture2D(uVelocity, vUv).xy;
1051
+ velocity.xy -= vec2(R - L, T - B);
1052
+ gl_FragColor = vec4(velocity, 0.0, 1.0);
1053
+ }
1054
+ `);
1055
+
1056
+ const blit = (() => {
1057
+ gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
1058
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW);
1059
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
1060
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW);
1061
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
1062
+ gl.enableVertexAttribArray(0);
1063
+
1064
+ return (target, clear = false) => {
1065
+ if (target == null)
1066
+ {
1067
+ gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
1068
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
1069
+ }
1070
+ else
1071
+ {
1072
+ gl.viewport(0, 0, target.width, target.height);
1073
+ gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);
1074
+ }
1075
+ if (clear)
1076
+ {
1077
+ gl.clearColor(0.0, 0.0, 0.0, 1.0);
1078
+ gl.clear(gl.COLOR_BUFFER_BIT);
1079
+ }
1080
+ // CHECK_FRAMEBUFFER_STATUS();
1081
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
1082
+ }
1083
+ })();
1084
+
1085
+ function CHECK_FRAMEBUFFER_STATUS () {
1086
+ let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
1087
+ if (status != gl.FRAMEBUFFER_COMPLETE)
1088
+ console.trace("Framebuffer error: " + status);
1089
+ }
1090
+
1091
+ let dye;
1092
+ let velocity;
1093
+ let divergence;
1094
+ let curl;
1095
+ let pressure;
1096
+ let bloom;
1097
+ let bloomFramebuffers = [];
1098
+ let sunrays;
1099
+ let sunraysTemp;
1100
+
1101
+ let ditheringTexture = createTextureAsync('LDR_LLL1_0.png');
1102
+
1103
+ const blurProgram = new Program(blurVertexShader, blurShader);
1104
+ const copyProgram = new Program(baseVertexShader, copyShader);
1105
+ const clearProgram = new Program(baseVertexShader, clearShader);
1106
+ const colorProgram = new Program(baseVertexShader, colorShader);
1107
+ const checkerboardProgram = new Program(baseVertexShader, checkerboardShader);
1108
+ const bloomPrefilterProgram = new Program(baseVertexShader, bloomPrefilterShader);
1109
+ const bloomBlurProgram = new Program(baseVertexShader, bloomBlurShader);
1110
+ const bloomFinalProgram = new Program(baseVertexShader, bloomFinalShader);
1111
+ const sunraysMaskProgram = new Program(baseVertexShader, sunraysMaskShader);
1112
+ const sunraysProgram = new Program(baseVertexShader, sunraysShader);
1113
+ const splatProgram = new Program(baseVertexShader, splatShader);
1114
+ const advectionProgram = new Program(baseVertexShader, advectionShader);
1115
+ const divergenceProgram = new Program(baseVertexShader, divergenceShader);
1116
+ const curlProgram = new Program(baseVertexShader, curlShader);
1117
+ const vorticityProgram = new Program(baseVertexShader, vorticityShader);
1118
+ const pressureProgram = new Program(baseVertexShader, pressureShader);
1119
+ const gradienSubtractProgram = new Program(baseVertexShader, gradientSubtractShader);
1120
+
1121
+ const displayMaterial = new Material(baseVertexShader, displayShaderSource);
1122
+
1123
+ function initFramebuffers () {
1124
+ let simRes = getResolution(config.SIM_RESOLUTION);
1125
+ let dyeRes = getResolution(config.DYE_RESOLUTION);
1126
+
1127
+ const texType = ext.halfFloatTexType;
1128
+ const rgba = ext.formatRGBA;
1129
+ const rg = ext.formatRG;
1130
+ const r = ext.formatR;
1131
+ const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST;
1132
+
1133
+ gl.disable(gl.BLEND);
1134
+
1135
+ if (dye == null)
1136
+ dye = createDoubleFBO(dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering);
1137
+ else
1138
+ dye = resizeDoubleFBO(dye, dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering);
1139
+
1140
+ if (velocity == null)
1141
+ velocity = createDoubleFBO(simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering);
1142
+ else
1143
+ velocity = resizeDoubleFBO(velocity, simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering);
1144
+
1145
+ divergence = createFBO (simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST);
1146
+ curl = createFBO (simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST);
1147
+ pressure = createDoubleFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST);
1148
+
1149
+ initBloomFramebuffers();
1150
+ initSunraysFramebuffers();
1151
+ }
1152
+
1153
+ function initBloomFramebuffers () {
1154
+ let res = getResolution(config.BLOOM_RESOLUTION);
1155
+
1156
+ const texType = ext.halfFloatTexType;
1157
+ const rgba = ext.formatRGBA;
1158
+ const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST;
1159
+
1160
+ bloom = createFBO(res.width, res.height, rgba.internalFormat, rgba.format, texType, filtering);
1161
+
1162
+ bloomFramebuffers.length = 0;
1163
+ for (let i = 0; i < config.BLOOM_ITERATIONS; i++)
1164
+ {
1165
+ let width = res.width >> (i + 1);
1166
+ let height = res.height >> (i + 1);
1167
+
1168
+ if (width < 2 || height < 2) break;
1169
+
1170
+ let fbo = createFBO(width, height, rgba.internalFormat, rgba.format, texType, filtering);
1171
+ bloomFramebuffers.push(fbo);
1172
+ }
1173
+ }
1174
+
1175
+ function initSunraysFramebuffers () {
1176
+ let res = getResolution(config.SUNRAYS_RESOLUTION);
1177
+
1178
+ const texType = ext.halfFloatTexType;
1179
+ const r = ext.formatR;
1180
+ const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST;
1181
+
1182
+ sunrays = createFBO(res.width, res.height, r.internalFormat, r.format, texType, filtering);
1183
+ sunraysTemp = createFBO(res.width, res.height, r.internalFormat, r.format, texType, filtering);
1184
+ }
1185
+
1186
+ function createFBO (w, h, internalFormat, format, type, param) {
1187
+ gl.activeTexture(gl.TEXTURE0);
1188
+ let texture = gl.createTexture();
1189
+ gl.bindTexture(gl.TEXTURE_2D, texture);
1190
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param);
1191
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param);
1192
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1193
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1194
+ gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null);
1195
+
1196
+ let fbo = gl.createFramebuffer();
1197
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
1198
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
1199
+ gl.viewport(0, 0, w, h);
1200
+ gl.clear(gl.COLOR_BUFFER_BIT);
1201
+
1202
+ let texelSizeX = 1.0 / w;
1203
+ let texelSizeY = 1.0 / h;
1204
+
1205
+ return {
1206
+ texture,
1207
+ fbo,
1208
+ width: w,
1209
+ height: h,
1210
+ texelSizeX,
1211
+ texelSizeY,
1212
+ attach (id) {
1213
+ gl.activeTexture(gl.TEXTURE0 + id);
1214
+ gl.bindTexture(gl.TEXTURE_2D, texture);
1215
+ return id;
1216
+ }
1217
+ };
1218
+ }
1219
+
1220
+ function createDoubleFBO (w, h, internalFormat, format, type, param) {
1221
+ let fbo1 = createFBO(w, h, internalFormat, format, type, param);
1222
+ let fbo2 = createFBO(w, h, internalFormat, format, type, param);
1223
+
1224
+ return {
1225
+ width: w,
1226
+ height: h,
1227
+ texelSizeX: fbo1.texelSizeX,
1228
+ texelSizeY: fbo1.texelSizeY,
1229
+ get read () {
1230
+ return fbo1;
1231
+ },
1232
+ set read (value) {
1233
+ fbo1 = value;
1234
+ },
1235
+ get write () {
1236
+ return fbo2;
1237
+ },
1238
+ set write (value) {
1239
+ fbo2 = value;
1240
+ },
1241
+ swap () {
1242
+ let temp = fbo1;
1243
+ fbo1 = fbo2;
1244
+ fbo2 = temp;
1245
+ }
1246
+ }
1247
+ }
1248
+
1249
+ function resizeFBO (target, w, h, internalFormat, format, type, param) {
1250
+ let newFBO = createFBO(w, h, internalFormat, format, type, param);
1251
+ copyProgram.bind();
1252
+ gl.uniform1i(copyProgram.uniforms.uTexture, target.attach(0));
1253
+ blit(newFBO);
1254
+ return newFBO;
1255
+ }
1256
+
1257
+ function resizeDoubleFBO (target, w, h, internalFormat, format, type, param) {
1258
+ if (target.width == w && target.height == h)
1259
+ return target;
1260
+ target.read = resizeFBO(target.read, w, h, internalFormat, format, type, param);
1261
+ target.write = createFBO(w, h, internalFormat, format, type, param);
1262
+ target.width = w;
1263
+ target.height = h;
1264
+ target.texelSizeX = 1.0 / w;
1265
+ target.texelSizeY = 1.0 / h;
1266
+ return target;
1267
+ }
1268
+
1269
+ function createTextureAsync (url) {
1270
+ let texture = gl.createTexture();
1271
+ gl.bindTexture(gl.TEXTURE_2D, texture);
1272
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1273
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1274
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
1275
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
1276
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255]));
1277
+
1278
+ let obj = {
1279
+ texture,
1280
+ width: 1,
1281
+ height: 1,
1282
+ attach (id) {
1283
+ gl.activeTexture(gl.TEXTURE0 + id);
1284
+ gl.bindTexture(gl.TEXTURE_2D, texture);
1285
+ return id;
1286
+ }
1287
+ };
1288
+
1289
+ let image = new Image();
1290
+ image.onload = () => {
1291
+ obj.width = image.width;
1292
+ obj.height = image.height;
1293
+ gl.bindTexture(gl.TEXTURE_2D, texture);
1294
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
1295
+ };
1296
+ image.src = url;
1297
+
1298
+ return obj;
1299
+ }
1300
+
1301
+ function updateKeywords () {
1302
+ let displayKeywords = [];
1303
+ if (config.SHADING) displayKeywords.push("SHADING");
1304
+ if (config.BLOOM) displayKeywords.push("BLOOM");
1305
+ if (config.SUNRAYS) displayKeywords.push("SUNRAYS");
1306
+ displayMaterial.setKeywords(displayKeywords);
1307
+ }
1308
+
1309
+ updateKeywords();
1310
+ initFramebuffers();
1311
+
1312
+ multipleSplats(parseInt(Math.random() * 10) + 5, 0.8); // Initial burst with slightly
1313
+
1314
+ // Configuration for ambient splats
1315
+ const AMBIENT_INTERVAL = 1000; // Time between ambient splats in milliseconds (e.g., 2 seconds)
1316
+ const MAX_AMBIENT_SPLATS = 5; // Maximum number of random splats per interval (e.g., 1 or 2)
1317
+ const AMBIENT_INTENSITY = 0.3; // How strong the ambient splats are (e.g., 20% of normal)
1318
+
1319
+ setInterval(() => {
1320
+ // Only add ambient splats if the simulation isn't paused by the user (P key)
1321
+ if (!config.PAUSED) {
1322
+ // Generate a small number of splats (1 to MAX_AMBIENT_SPLATS)
1323
+ const numSplats = Math.floor(Math.random() * MAX_AMBIENT_SPLATS) + 1;
1324
+ // Call multipleSplats with the low ambient intensity
1325
+ multipleSplats(numSplats, AMBIENT_INTENSITY);
1326
+ }
1327
+ }, AMBIENT_INTERVAL);
1328
+
1329
+
1330
+ let lastUpdateTime = Date.now();
1331
+ let colorUpdateTimer = 0.0;
1332
+ update();
1333
+
1334
+ function update () {
1335
+ const dt = calcDeltaTime();
1336
+ if (resizeCanvas())
1337
+ initFramebuffers();
1338
+ updateColors(dt);
1339
+ applyInputs();
1340
+ if (!config.PAUSED)
1341
+ step(dt);
1342
+ render(null);
1343
+ requestAnimationFrame(update);
1344
+ }
1345
+
1346
+ function calcDeltaTime () {
1347
+ let now = Date.now();
1348
+ let dt = (now - lastUpdateTime) / 1000;
1349
+ dt = Math.min(dt, 0.016666);
1350
+ lastUpdateTime = now;
1351
+ return dt;
1352
+ }
1353
+
1354
+ function resizeCanvas () {
1355
+ let width = scaleByPixelRatio(canvas.clientWidth);
1356
+ let height = scaleByPixelRatio(canvas.clientHeight);
1357
+ if (canvas.width != width || canvas.height != height) {
1358
+ canvas.width = width;
1359
+ canvas.height = height;
1360
+ return true;
1361
+ }
1362
+ return false;
1363
+ }
1364
+
1365
+ function updateColors (dt) {
1366
+ if (!config.COLORFUL) return;
1367
+
1368
+ colorUpdateTimer += dt * config.COLOR_UPDATE_SPEED;
1369
+ if (colorUpdateTimer >= 1) {
1370
+ colorUpdateTimer = wrap(colorUpdateTimer, 0, 1);
1371
+ pointers.forEach(p => {
1372
+ p.color = generateColor();
1373
+ });
1374
+ }
1375
+ }
1376
+
1377
+ function applyInputs () {
1378
+ if (splatStack.length > 0)
1379
+ multipleSplats(splatStack.pop());
1380
+
1381
+ pointers.forEach(p => {
1382
+ if (p.moved) {
1383
+ p.moved = false;
1384
+ splatPointer(p);
1385
+ }
1386
+ });
1387
+ }
1388
+
1389
+ function step (dt) {
1390
+ gl.disable(gl.BLEND);
1391
+
1392
+ curlProgram.bind();
1393
+ gl.uniform2f(curlProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
1394
+ gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.read.attach(0));
1395
+ blit(curl);
1396
+
1397
+ vorticityProgram.bind();
1398
+ gl.uniform2f(vorticityProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
1399
+ gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.read.attach(0));
1400
+ gl.uniform1i(vorticityProgram.uniforms.uCurl, curl.attach(1));
1401
+ gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL);
1402
+ gl.uniform1f(vorticityProgram.uniforms.dt, dt);
1403
+ blit(velocity.write);
1404
+ velocity.swap();
1405
+
1406
+ divergenceProgram.bind();
1407
+ gl.uniform2f(divergenceProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
1408
+ gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.read.attach(0));
1409
+ blit(divergence);
1410
+
1411
+ clearProgram.bind();
1412
+ gl.uniform1i(clearProgram.uniforms.uTexture, pressure.read.attach(0));
1413
+ gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE);
1414
+ blit(pressure.write);
1415
+ pressure.swap();
1416
+
1417
+ pressureProgram.bind();
1418
+ gl.uniform2f(pressureProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
1419
+ gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0));
1420
+ for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) {
1421
+ gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1));
1422
+ blit(pressure.write);
1423
+ pressure.swap();
1424
+ }
1425
+
1426
+ gradienSubtractProgram.bind();
1427
+ gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
1428
+ gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.read.attach(0));
1429
+ gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.read.attach(1));
1430
+ blit(velocity.write);
1431
+ velocity.swap();
1432
+
1433
+ advectionProgram.bind();
1434
+ gl.uniform2f(advectionProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
1435
+ if (!ext.supportLinearFiltering)
1436
+ gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, velocity.texelSizeX, velocity.texelSizeY);
1437
+ let velocityId = velocity.read.attach(0);
1438
+ gl.uniform1i(advectionProgram.uniforms.uVelocity, velocityId);
1439
+ gl.uniform1i(advectionProgram.uniforms.uSource, velocityId);
1440
+ gl.uniform1f(advectionProgram.uniforms.dt, dt);
1441
+ gl.uniform1f(advectionProgram.uniforms.dissipation, config.VELOCITY_DISSIPATION);
1442
+ blit(velocity.write);
1443
+ velocity.swap();
1444
+
1445
+ if (!ext.supportLinearFiltering)
1446
+ gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, dye.texelSizeX, dye.texelSizeY);
1447
+ gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.read.attach(0));
1448
+ gl.uniform1i(advectionProgram.uniforms.uSource, dye.read.attach(1));
1449
+ gl.uniform1f(advectionProgram.uniforms.dissipation, config.DENSITY_DISSIPATION);
1450
+ blit(dye.write);
1451
+ dye.swap();
1452
+ }
1453
+
1454
+ function render (target) {
1455
+ if (config.BLOOM)
1456
+ applyBloom(dye.read, bloom);
1457
+ if (config.SUNRAYS) {
1458
+ applySunrays(dye.read, dye.write, sunrays);
1459
+ blur(sunrays, sunraysTemp, 1);
1460
+ }
1461
+
1462
+ if (target == null || !config.TRANSPARENT) {
1463
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
1464
+ gl.enable(gl.BLEND);
1465
+ }
1466
+ else {
1467
+ gl.disable(gl.BLEND);
1468
+ }
1469
+
1470
+ if (!config.TRANSPARENT)
1471
+ drawColor(target, normalizeColor(config.BACK_COLOR));
1472
+ if (target == null && config.TRANSPARENT)
1473
+ drawCheckerboard(target);
1474
+ drawDisplay(target);
1475
+ }
1476
+
1477
+ function drawColor (target, color) {
1478
+ colorProgram.bind();
1479
+ gl.uniform4f(colorProgram.uniforms.color, color.r, color.g, color.b, 1);
1480
+ blit(target);
1481
+ }
1482
+
1483
+ function drawCheckerboard (target) {
1484
+ checkerboardProgram.bind();
1485
+ gl.uniform1f(checkerboardProgram.uniforms.aspectRatio, canvas.width / canvas.height);
1486
+ blit(target);
1487
+ }
1488
+
1489
+ function drawDisplay (target) {
1490
+ let width = target == null ? gl.drawingBufferWidth : target.width;
1491
+ let height = target == null ? gl.drawingBufferHeight : target.height;
1492
+
1493
+ displayMaterial.bind();
1494
+ if (config.SHADING)
1495
+ gl.uniform2f(displayMaterial.uniforms.texelSize, 1.0 / width, 1.0 / height);
1496
+ gl.uniform1i(displayMaterial.uniforms.uTexture, dye.read.attach(0));
1497
+ if (config.BLOOM) {
1498
+ gl.uniform1i(displayMaterial.uniforms.uBloom, bloom.attach(1));
1499
+ gl.uniform1i(displayMaterial.uniforms.uDithering, ditheringTexture.attach(2));
1500
+ let scale = getTextureScale(ditheringTexture, width, height);
1501
+ gl.uniform2f(displayMaterial.uniforms.ditherScale, scale.x, scale.y);
1502
+ }
1503
+ if (config.SUNRAYS)
1504
+ gl.uniform1i(displayMaterial.uniforms.uSunrays, sunrays.attach(3));
1505
+ blit(target);
1506
+ }
1507
+
1508
+ function applyBloom (source, destination) {
1509
+ if (bloomFramebuffers.length < 2)
1510
+ return;
1511
+
1512
+ let last = destination;
1513
+
1514
+ gl.disable(gl.BLEND);
1515
+ bloomPrefilterProgram.bind();
1516
+ let knee = config.BLOOM_THRESHOLD * config.BLOOM_SOFT_KNEE + 0.0001;
1517
+ let curve0 = config.BLOOM_THRESHOLD - knee;
1518
+ let curve1 = knee * 2;
1519
+ let curve2 = 0.25 / knee;
1520
+ gl.uniform3f(bloomPrefilterProgram.uniforms.curve, curve0, curve1, curve2);
1521
+ gl.uniform1f(bloomPrefilterProgram.uniforms.threshold, config.BLOOM_THRESHOLD);
1522
+ gl.uniform1i(bloomPrefilterProgram.uniforms.uTexture, source.attach(0));
1523
+ blit(last);
1524
+
1525
+ bloomBlurProgram.bind();
1526
+ for (let i = 0; i < bloomFramebuffers.length; i++) {
1527
+ let dest = bloomFramebuffers[i];
1528
+ gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
1529
+ gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0));
1530
+ blit(dest);
1531
+ last = dest;
1532
+ }
1533
+
1534
+ gl.blendFunc(gl.ONE, gl.ONE);
1535
+ gl.enable(gl.BLEND);
1536
+
1537
+ for (let i = bloomFramebuffers.length - 2; i >= 0; i--) {
1538
+ let baseTex = bloomFramebuffers[i];
1539
+ gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
1540
+ gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0));
1541
+ gl.viewport(0, 0, baseTex.width, baseTex.height);
1542
+ blit(baseTex);
1543
+ last = baseTex;
1544
+ }
1545
+
1546
+ gl.disable(gl.BLEND);
1547
+ bloomFinalProgram.bind();
1548
+ gl.uniform2f(bloomFinalProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
1549
+ gl.uniform1i(bloomFinalProgram.uniforms.uTexture, last.attach(0));
1550
+ gl.uniform1f(bloomFinalProgram.uniforms.intensity, config.BLOOM_INTENSITY);
1551
+ blit(destination);
1552
+ }
1553
+
1554
+ function applySunrays (source, mask, destination) {
1555
+ gl.disable(gl.BLEND);
1556
+ sunraysMaskProgram.bind();
1557
+ gl.uniform1i(sunraysMaskProgram.uniforms.uTexture, source.attach(0));
1558
+ blit(mask);
1559
+
1560
+ sunraysProgram.bind();
1561
+ gl.uniform1f(sunraysProgram.uniforms.weight, config.SUNRAYS_WEIGHT);
1562
+ gl.uniform1i(sunraysProgram.uniforms.uTexture, mask.attach(0));
1563
+ blit(destination);
1564
+ }
1565
+
1566
+ function blur (target, temp, iterations) {
1567
+ blurProgram.bind();
1568
+ for (let i = 0; i < iterations; i++) {
1569
+ gl.uniform2f(blurProgram.uniforms.texelSize, target.texelSizeX, 0.0);
1570
+ gl.uniform1i(blurProgram.uniforms.uTexture, target.attach(0));
1571
+ blit(temp);
1572
+
1573
+ gl.uniform2f(blurProgram.uniforms.texelSize, 0.0, target.texelSizeY);
1574
+ gl.uniform1i(blurProgram.uniforms.uTexture, temp.attach(0));
1575
+ blit(target);
1576
+ }
1577
+ }
1578
+
1579
+ function splatPointer (pointer) {
1580
+ let dx = pointer.deltaX * config.SPLAT_FORCE;
1581
+ let dy = pointer.deltaY * config.SPLAT_FORCE;
1582
+ splat(pointer.texcoordX, pointer.texcoordY, dx, dy, pointer.color);
1583
+ }
1584
+
1585
+ function multipleSplats (amount, intensityMultiplier = 1.0) { // Added intensityMultiplier
1586
+ for (let i = 0; i < amount; i++) {
1587
+ const color = generateColor();
1588
+ // Scale color intensity based on the multiplier
1589
+ color.r *= 10.0 * intensityMultiplier;
1590
+ color.g *= 10.0 * intensityMultiplier;
1591
+ color.b *= 10.0 * intensityMultiplier;
1592
+ const x = Math.random();
1593
+ const y = Math.random();
1594
+ // Scale velocity intensity based on the multiplier
1595
+ const dx = 1000 * (Math.random() - 0.5) * intensityMultiplier;
1596
+ const dy = 1000 * (Math.random() - 0.5) * intensityMultiplier;
1597
+ splat(x, y, dx, dy, color);
1598
+ }
1599
+ }
1600
+
1601
+
1602
+ function splat (x, y, dx, dy, color) {
1603
+ splatProgram.bind();
1604
+ gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0));
1605
+ gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height);
1606
+ gl.uniform2f(splatProgram.uniforms.point, x, y);
1607
+ gl.uniform3f(splatProgram.uniforms.color, dx, dy, 0.0);
1608
+ gl.uniform1f(splatProgram.uniforms.radius, correctRadius(config.SPLAT_RADIUS / 100.0));
1609
+ blit(velocity.write);
1610
+ velocity.swap();
1611
+
1612
+ gl.uniform1i(splatProgram.uniforms.uTarget, dye.read.attach(0));
1613
+ gl.uniform3f(splatProgram.uniforms.color, color.r, color.g, color.b);
1614
+ blit(dye.write);
1615
+ dye.swap();
1616
+ }
1617
+
1618
+ function correctRadius (radius) {
1619
+ let aspectRatio = canvas.width / canvas.height;
1620
+ if (aspectRatio > 1)
1621
+ radius *= aspectRatio;
1622
+ return radius;
1623
+ }
1624
+
1625
+ // Get reference to container to check event targets
1626
+ const containerElement = document.querySelector('.container');
1627
+
1628
+ window.addEventListener('mousedown', e => {
1629
+ // Check if the click started inside the container/form
1630
+ if (containerElement && containerElement.contains(e.target)) {
1631
+ // If the target (or its ancestor) is the container, ignore for fluid
1632
+ return;
1633
+ }
1634
+
1635
+ const rect = canvas.getBoundingClientRect();
1636
+ // Calculate position relative to the canvas origin
1637
+ let posX = scaleByPixelRatio(e.clientX - rect.left);
1638
+ let posY = scaleByPixelRatio(e.clientY - rect.top);
1639
+
1640
+ let pointer = pointers.find(p => p.id == -1);
1641
+ if (pointer == null)
1642
+ pointer = new pointerPrototype();
1643
+
1644
+ // Pass the SCALED PIXEL coordinates relative to the canvas
1645
+ updatePointerDownData(pointer, -1, posX, posY);
1646
+ });
1647
+
1648
+ window.addEventListener('mousemove', e => {
1649
+ let pointer = pointers[0]; // Assuming the first pointer is for mouse
1650
+ if (!pointer.down) return; // Only track if mouse is down
1651
+
1652
+ const rect = canvas.getBoundingClientRect();
1653
+ // Calculate position relative to the canvas origin
1654
+ let posX = scaleByPixelRatio(e.clientX - rect.left);
1655
+ let posY = scaleByPixelRatio(e.clientY - rect.top);
1656
+
1657
+ // Pass the SCALED PIXEL coordinates relative to the canvas
1658
+ updatePointerMoveData(pointer, posX, posY);
1659
+ });
1660
+
1661
+ // window.addEventListener('mouseup', ...) // Keep this listener as is
1662
+
1663
+ window.addEventListener('touchstart', e => {
1664
+ // Note: We generally avoid preventDefault on window touchstart/move
1665
+ // as it can break scrolling. Let's see if it works without it.
1666
+
1667
+ const touches = e.targetTouches;
1668
+ const rect = canvas.getBoundingClientRect();
1669
+ let didProcessTouchOutside = false;
1670
+
1671
+ for (let i = 0; i < touches.length; i++) {
1672
+ // Check if the touch started inside the container/form
1673
+ if (containerElement && containerElement.contains(touches[i].target)) {
1674
+ continue; // Ignore this specific touch for fluid
1675
+ }
1676
+
1677
+ didProcessTouchOutside = true; // Mark that at least one touch outside occurred
1678
+
1679
+ // Ensure pointers array is large enough
1680
+ // Use pointers.length directly, as pointers[0] is mouse
1681
+ while (pointers.length <= touches[i].identifier + 1)
1682
+ pointers.push(new pointerPrototype());
1683
+
1684
+ // Calculate position relative to the canvas origin
1685
+ let relativeX = touches[i].clientX - rect.left;
1686
+ let relativeY = touches[i].clientY - rect.top;
1687
+ let posX = scaleByPixelRatio(relativeX);
1688
+ let posY = scaleByPixelRatio(relativeY);
1689
+
1690
+ // Find the correct pointer slot or reuse an inactive one if needed
1691
+ // For simplicity, let's just assign based on identifier + 1 for now
1692
+ // (assuming identifier 0 is first touch, 1 is second etc.)
1693
+ let pointerIndex = touches[i].identifier + 1;
1694
+ if(pointerIndex >= pointers.length) pointerIndex = pointers.length -1; // Safety check
1695
+
1696
+
1697
+ // Pass the SCALED PIXEL coordinates relative to the canvas
1698
+ updatePointerDownData(pointers[pointerIndex], touches[i].identifier, posX, posY);
1699
+ }
1700
+ // if (didProcessTouchOutside) { e.preventDefault(); } // Avoid if possible
1701
+ });
1702
+
1703
+ window.addEventListener('touchmove', e => {
1704
+ const touches = e.targetTouches;
1705
+ const rect = canvas.getBoundingClientRect();
1706
+
1707
+ for (let i = 0; i < touches.length; i++) {
1708
+ // Find the pointer associated with this touch ID
1709
+ let pointer = pointers.find(p => p.id == touches[i].identifier);
1710
+ if (!pointer || !pointer.down) continue; // Ignore if not tracked or not down
1711
+
1712
+ // Calculate position relative to the canvas origin
1713
+ let relativeX = touches[i].clientX - rect.left;
1714
+ let relativeY = touches[i].clientY - rect.top;
1715
+ let posX = scaleByPixelRatio(relativeX);
1716
+ let posY = scaleByPixelRatio(relativeY);
1717
+
1718
+ // Pass the SCALED PIXEL coordinates relative to the canvas
1719
+ updatePointerMoveData(pointer, posX, posY);
1720
+ }
1721
+ }, false); // UseCapture = false is default, but good to be explicit
1722
+
1723
+ window.addEventListener('touchend', e => {
1724
+ const touches = e.changedTouches;
1725
+ for (let i = 0; i < touches.length; i++)
1726
+ {
1727
+ let pointer = pointers.find(p => p.id == touches[i].identifier);
1728
+ if (pointer == null) continue;
1729
+ updatePointerUpData(pointer);
1730
+ }
1731
+ });
1732
+
1733
+ window.addEventListener('keydown', e => {
1734
+ if (e.code === 'KeyP')
1735
+ config.PAUSED = !config.PAUSED;
1736
+ if (e.key === ' ')
1737
+ splatStack.push(parseInt(Math.random() * 20) + 5);
1738
+ });
1739
+
1740
+ function updatePointerDownData (pointer, id, posX, posY) {
1741
+ pointer.id = id;
1742
+ pointer.down = true;
1743
+ pointer.moved = false;
1744
+ pointer.texcoordX = posX / canvas.width;
1745
+ pointer.texcoordY = 1.0 - posY / canvas.height;
1746
+ pointer.prevTexcoordX = pointer.texcoordX;
1747
+ pointer.prevTexcoordY = pointer.texcoordY;
1748
+ pointer.deltaX = 0;
1749
+ pointer.deltaY = 0;
1750
+ pointer.color = generateColor();
1751
+ }
1752
+
1753
+ function updatePointerMoveData (pointer, posX, posY) {
1754
+ pointer.prevTexcoordX = pointer.texcoordX;
1755
+ pointer.prevTexcoordY = pointer.texcoordY;
1756
+ pointer.texcoordX = posX / canvas.width;
1757
+ pointer.texcoordY = 1.0 - posY / canvas.height;
1758
+ pointer.deltaX = correctDeltaX(pointer.texcoordX - pointer.prevTexcoordX);
1759
+ pointer.deltaY = correctDeltaY(pointer.texcoordY - pointer.prevTexcoordY);
1760
+ pointer.moved = Math.abs(pointer.deltaX) > 0 || Math.abs(pointer.deltaY) > 0;
1761
+ }
1762
+
1763
+ function updatePointerUpData (pointer) {
1764
+ pointer.down = false;
1765
+ }
1766
+
1767
+ function correctDeltaX (delta) {
1768
+ let aspectRatio = canvas.width / canvas.height;
1769
+ if (aspectRatio < 1) delta *= aspectRatio;
1770
+ return delta;
1771
+ }
1772
+
1773
+ function correctDeltaY (delta) {
1774
+ let aspectRatio = canvas.width / canvas.height;
1775
+ if (aspectRatio > 1) delta /= aspectRatio;
1776
+ return delta;
1777
+ }
1778
+
1779
+ function generateColor () {
1780
+ let c = HSVtoRGB(Math.random(), 1.0, 1.0);
1781
+ c.r *= 0.15;
1782
+ c.g *= 0.15;
1783
+ c.b *= 0.15;
1784
+ return c;
1785
+ }
1786
+
1787
+ function HSVtoRGB (h, s, v) {
1788
+ let r, g, b, i, f, p, q, t;
1789
+ i = Math.floor(h * 6);
1790
+ f = h * 6 - i;
1791
+ p = v * (1 - s);
1792
+ q = v * (1 - f * s);
1793
+ t = v * (1 - (1 - f) * s);
1794
+
1795
+ switch (i % 6) {
1796
+ case 0: r = v, g = t, b = p; break;
1797
+ case 1: r = q, g = v, b = p; break;
1798
+ case 2: r = p, g = v, b = t; break;
1799
+ case 3: r = p, g = q, b = v; break;
1800
+ case 4: r = t, g = p, b = v; break;
1801
+ case 5: r = v, g = p, b = q; break;
1802
+ }
1803
+
1804
+ return {
1805
+ r,
1806
+ g,
1807
+ b
1808
+ };
1809
+ }
1810
+
1811
+ function normalizeColor (input) {
1812
+ let output = {
1813
+ r: input.r / 255,
1814
+ g: input.g / 255,
1815
+ b: input.b / 255
1816
+ };
1817
+ return output;
1818
+ }
1819
+
1820
+ function wrap (value, min, max) {
1821
+ let range = max - min;
1822
+ if (range == 0) return min;
1823
+ return (value - min) % range + min;
1824
+ }
1825
+
1826
+ function getResolution (resolution) {
1827
+ let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight;
1828
+ if (aspectRatio < 1)
1829
+ aspectRatio = 1.0 / aspectRatio;
1830
+
1831
+ let min = Math.round(resolution);
1832
+ let max = Math.round(resolution * aspectRatio);
1833
+
1834
+ if (gl.drawingBufferWidth > gl.drawingBufferHeight)
1835
+ return { width: max, height: min };
1836
+ else
1837
+ return { width: min, height: max };
1838
+ }
1839
+
1840
+ function getTextureScale (texture, width, height) {
1841
+ return {
1842
+ x: width / texture.width,
1843
+ y: height / texture.height
1844
+ };
1845
+ }
1846
+
1847
+ function scaleByPixelRatio (input) {
1848
+ let pixelRatio = window.devicePixelRatio || 1;
1849
+ return Math.floor(input * pixelRatio);
1850
+ }
1851
+
1852
+ function hashCode (s) {
1853
+ if (s.length == 0) return 0;
1854
+ let hash = 0;
1855
+ for (let i = 0; i < s.length; i++) {
1856
+ hash = (hash << 5) - hash + s.charCodeAt(i);
1857
+ hash |= 0; // Convert to 32bit integer
1858
+ }
1859
+ return hash;
1860
+ };</script>
1861
 
1862
  <script>
1863
  const form = document.getElementById('video-form');
templates/script.js DELETED
@@ -1,1618 +0,0 @@
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
- // Simulation section
28
-
29
- const canvas = document.getElementsByTagName('canvas')[0];
30
- resizeCanvas();
31
-
32
- let config = {
33
- SIM_RESOLUTION: 128,
34
- DYE_RESOLUTION: 1024,
35
- CAPTURE_RESOLUTION: 512,
36
- DENSITY_DISSIPATION: 1,
37
- VELOCITY_DISSIPATION: 0.2,
38
- PRESSURE: 0.8,
39
- PRESSURE_ITERATIONS: 20,
40
- CURL: 30,
41
- SPLAT_RADIUS: 0.25,
42
- SPLAT_FORCE: 6000,
43
- SHADING: true,
44
- COLORFUL: true,
45
- COLOR_UPDATE_SPEED: 10,
46
- PAUSED: false,
47
- BACK_COLOR: { r: 0, g: 0, b: 0 },
48
- TRANSPARENT: false,
49
- BLOOM: true,
50
- BLOOM_ITERATIONS: 8,
51
- BLOOM_RESOLUTION: 256,
52
- BLOOM_INTENSITY: 0.8,
53
- BLOOM_THRESHOLD: 0.6,
54
- BLOOM_SOFT_KNEE: 0.7,
55
- SUNRAYS: true,
56
- SUNRAYS_RESOLUTION: 196,
57
- SUNRAYS_WEIGHT: 1.0,
58
- }
59
-
60
- function pointerPrototype () {
61
- this.id = -1;
62
- this.texcoordX = 0;
63
- this.texcoordY = 0;
64
- this.prevTexcoordX = 0;
65
- this.prevTexcoordY = 0;
66
- this.deltaX = 0;
67
- this.deltaY = 0;
68
- this.down = false;
69
- this.moved = false;
70
- this.color = [30, 0, 300];
71
- }
72
-
73
- let pointers = [];
74
- let splatStack = [];
75
- pointers.push(new pointerPrototype());
76
-
77
- const { gl, ext } = getWebGLContext(canvas);
78
-
79
- if (isMobile()) {
80
- config.DYE_RESOLUTION = 512;
81
- }
82
- if (!ext.supportLinearFiltering) {
83
- config.DYE_RESOLUTION = 512;
84
- config.SHADING = false;
85
- config.BLOOM = false;
86
- config.SUNRAYS = false;
87
- }
88
-
89
- startGUI();
90
-
91
- function getWebGLContext (canvas) {
92
- const params = { alpha: true, depth: false, stencil: false, antialias: false, preserveDrawingBuffer: false };
93
-
94
- let gl = canvas.getContext('webgl2', params);
95
- const isWebGL2 = !!gl;
96
- if (!isWebGL2)
97
- gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params);
98
-
99
- let halfFloat;
100
- let supportLinearFiltering;
101
- if (isWebGL2) {
102
- gl.getExtension('EXT_color_buffer_float');
103
- supportLinearFiltering = gl.getExtension('OES_texture_float_linear');
104
- } else {
105
- halfFloat = gl.getExtension('OES_texture_half_float');
106
- supportLinearFiltering = gl.getExtension('OES_texture_half_float_linear');
107
- }
108
-
109
- gl.clearColor(0.0, 0.0, 0.0, 1.0);
110
-
111
- const halfFloatTexType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES;
112
- let formatRGBA;
113
- let formatRG;
114
- let formatR;
115
-
116
- if (isWebGL2)
117
- {
118
- formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType);
119
- formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType);
120
- formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType);
121
- }
122
- else
123
- {
124
- formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
125
- formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
126
- formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);
127
- }
128
-
129
- return {
130
- gl,
131
- ext: {
132
- formatRGBA,
133
- formatRG,
134
- formatR,
135
- halfFloatTexType,
136
- supportLinearFiltering
137
- }
138
- };
139
- }
140
-
141
- function getSupportedFormat (gl, internalFormat, format, type)
142
- {
143
- if (!supportRenderTextureFormat(gl, internalFormat, format, type))
144
- {
145
- switch (internalFormat)
146
- {
147
- case gl.R16F:
148
- return getSupportedFormat(gl, gl.RG16F, gl.RG, type);
149
- case gl.RG16F:
150
- return getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, type);
151
- default:
152
- return null;
153
- }
154
- }
155
-
156
- return {
157
- internalFormat,
158
- format
159
- }
160
- }
161
-
162
- function supportRenderTextureFormat (gl, internalFormat, format, type) {
163
- let texture = gl.createTexture();
164
- gl.bindTexture(gl.TEXTURE_2D, texture);
165
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
166
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
167
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
168
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
169
- gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 4, 4, 0, format, type, null);
170
-
171
- let fbo = gl.createFramebuffer();
172
- gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
173
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
174
-
175
- let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
176
- return status == gl.FRAMEBUFFER_COMPLETE;
177
- }
178
-
179
- function startGUI () {
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
-
1070
- multipleSplats(parseInt(Math.random() * 10) + 5, 0.8); // Initial burst with slightly
1071
-
1072
- // Configuration for ambient splats
1073
- const AMBIENT_INTERVAL = 1000; // Time between ambient splats in milliseconds (e.g., 2 seconds)
1074
- const MAX_AMBIENT_SPLATS = 5; // Maximum number of random splats per interval (e.g., 1 or 2)
1075
- const AMBIENT_INTENSITY = 0.3; // How strong the ambient splats are (e.g., 20% of normal)
1076
-
1077
- setInterval(() => {
1078
- // Only add ambient splats if the simulation isn't paused by the user (P key)
1079
- if (!config.PAUSED) {
1080
- // Generate a small number of splats (1 to MAX_AMBIENT_SPLATS)
1081
- const numSplats = Math.floor(Math.random() * MAX_AMBIENT_SPLATS) + 1;
1082
- // Call multipleSplats with the low ambient intensity
1083
- multipleSplats(numSplats, AMBIENT_INTENSITY);
1084
- }
1085
- }, AMBIENT_INTERVAL);
1086
-
1087
-
1088
- let lastUpdateTime = Date.now();
1089
- let colorUpdateTimer = 0.0;
1090
- update();
1091
-
1092
- function update () {
1093
- const dt = calcDeltaTime();
1094
- if (resizeCanvas())
1095
- initFramebuffers();
1096
- updateColors(dt);
1097
- applyInputs();
1098
- if (!config.PAUSED)
1099
- step(dt);
1100
- render(null);
1101
- requestAnimationFrame(update);
1102
- }
1103
-
1104
- function calcDeltaTime () {
1105
- let now = Date.now();
1106
- let dt = (now - lastUpdateTime) / 1000;
1107
- dt = Math.min(dt, 0.016666);
1108
- lastUpdateTime = now;
1109
- return dt;
1110
- }
1111
-
1112
- function resizeCanvas () {
1113
- let width = scaleByPixelRatio(canvas.clientWidth);
1114
- let height = scaleByPixelRatio(canvas.clientHeight);
1115
- if (canvas.width != width || canvas.height != height) {
1116
- canvas.width = width;
1117
- canvas.height = height;
1118
- return true;
1119
- }
1120
- return false;
1121
- }
1122
-
1123
- function updateColors (dt) {
1124
- if (!config.COLORFUL) return;
1125
-
1126
- colorUpdateTimer += dt * config.COLOR_UPDATE_SPEED;
1127
- if (colorUpdateTimer >= 1) {
1128
- colorUpdateTimer = wrap(colorUpdateTimer, 0, 1);
1129
- pointers.forEach(p => {
1130
- p.color = generateColor();
1131
- });
1132
- }
1133
- }
1134
-
1135
- function applyInputs () {
1136
- if (splatStack.length > 0)
1137
- multipleSplats(splatStack.pop());
1138
-
1139
- pointers.forEach(p => {
1140
- if (p.moved) {
1141
- p.moved = false;
1142
- splatPointer(p);
1143
- }
1144
- });
1145
- }
1146
-
1147
- function step (dt) {
1148
- gl.disable(gl.BLEND);
1149
-
1150
- curlProgram.bind();
1151
- gl.uniform2f(curlProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
1152
- gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.read.attach(0));
1153
- blit(curl);
1154
-
1155
- vorticityProgram.bind();
1156
- gl.uniform2f(vorticityProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
1157
- gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.read.attach(0));
1158
- gl.uniform1i(vorticityProgram.uniforms.uCurl, curl.attach(1));
1159
- gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL);
1160
- gl.uniform1f(vorticityProgram.uniforms.dt, dt);
1161
- blit(velocity.write);
1162
- velocity.swap();
1163
-
1164
- divergenceProgram.bind();
1165
- gl.uniform2f(divergenceProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
1166
- gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.read.attach(0));
1167
- blit(divergence);
1168
-
1169
- clearProgram.bind();
1170
- gl.uniform1i(clearProgram.uniforms.uTexture, pressure.read.attach(0));
1171
- gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE);
1172
- blit(pressure.write);
1173
- pressure.swap();
1174
-
1175
- pressureProgram.bind();
1176
- gl.uniform2f(pressureProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
1177
- gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0));
1178
- for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) {
1179
- gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1));
1180
- blit(pressure.write);
1181
- pressure.swap();
1182
- }
1183
-
1184
- gradienSubtractProgram.bind();
1185
- gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
1186
- gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.read.attach(0));
1187
- gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.read.attach(1));
1188
- blit(velocity.write);
1189
- velocity.swap();
1190
-
1191
- advectionProgram.bind();
1192
- gl.uniform2f(advectionProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);
1193
- if (!ext.supportLinearFiltering)
1194
- gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, velocity.texelSizeX, velocity.texelSizeY);
1195
- let velocityId = velocity.read.attach(0);
1196
- gl.uniform1i(advectionProgram.uniforms.uVelocity, velocityId);
1197
- gl.uniform1i(advectionProgram.uniforms.uSource, velocityId);
1198
- gl.uniform1f(advectionProgram.uniforms.dt, dt);
1199
- gl.uniform1f(advectionProgram.uniforms.dissipation, config.VELOCITY_DISSIPATION);
1200
- blit(velocity.write);
1201
- velocity.swap();
1202
-
1203
- if (!ext.supportLinearFiltering)
1204
- gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, dye.texelSizeX, dye.texelSizeY);
1205
- gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.read.attach(0));
1206
- gl.uniform1i(advectionProgram.uniforms.uSource, dye.read.attach(1));
1207
- gl.uniform1f(advectionProgram.uniforms.dissipation, config.DENSITY_DISSIPATION);
1208
- blit(dye.write);
1209
- dye.swap();
1210
- }
1211
-
1212
- function render (target) {
1213
- if (config.BLOOM)
1214
- applyBloom(dye.read, bloom);
1215
- if (config.SUNRAYS) {
1216
- applySunrays(dye.read, dye.write, sunrays);
1217
- blur(sunrays, sunraysTemp, 1);
1218
- }
1219
-
1220
- if (target == null || !config.TRANSPARENT) {
1221
- gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
1222
- gl.enable(gl.BLEND);
1223
- }
1224
- else {
1225
- gl.disable(gl.BLEND);
1226
- }
1227
-
1228
- if (!config.TRANSPARENT)
1229
- drawColor(target, normalizeColor(config.BACK_COLOR));
1230
- if (target == null && config.TRANSPARENT)
1231
- drawCheckerboard(target);
1232
- drawDisplay(target);
1233
- }
1234
-
1235
- function drawColor (target, color) {
1236
- colorProgram.bind();
1237
- gl.uniform4f(colorProgram.uniforms.color, color.r, color.g, color.b, 1);
1238
- blit(target);
1239
- }
1240
-
1241
- function drawCheckerboard (target) {
1242
- checkerboardProgram.bind();
1243
- gl.uniform1f(checkerboardProgram.uniforms.aspectRatio, canvas.width / canvas.height);
1244
- blit(target);
1245
- }
1246
-
1247
- function drawDisplay (target) {
1248
- let width = target == null ? gl.drawingBufferWidth : target.width;
1249
- let height = target == null ? gl.drawingBufferHeight : target.height;
1250
-
1251
- displayMaterial.bind();
1252
- if (config.SHADING)
1253
- gl.uniform2f(displayMaterial.uniforms.texelSize, 1.0 / width, 1.0 / height);
1254
- gl.uniform1i(displayMaterial.uniforms.uTexture, dye.read.attach(0));
1255
- if (config.BLOOM) {
1256
- gl.uniform1i(displayMaterial.uniforms.uBloom, bloom.attach(1));
1257
- gl.uniform1i(displayMaterial.uniforms.uDithering, ditheringTexture.attach(2));
1258
- let scale = getTextureScale(ditheringTexture, width, height);
1259
- gl.uniform2f(displayMaterial.uniforms.ditherScale, scale.x, scale.y);
1260
- }
1261
- if (config.SUNRAYS)
1262
- gl.uniform1i(displayMaterial.uniforms.uSunrays, sunrays.attach(3));
1263
- blit(target);
1264
- }
1265
-
1266
- function applyBloom (source, destination) {
1267
- if (bloomFramebuffers.length < 2)
1268
- return;
1269
-
1270
- let last = destination;
1271
-
1272
- gl.disable(gl.BLEND);
1273
- bloomPrefilterProgram.bind();
1274
- let knee = config.BLOOM_THRESHOLD * config.BLOOM_SOFT_KNEE + 0.0001;
1275
- let curve0 = config.BLOOM_THRESHOLD - knee;
1276
- let curve1 = knee * 2;
1277
- let curve2 = 0.25 / knee;
1278
- gl.uniform3f(bloomPrefilterProgram.uniforms.curve, curve0, curve1, curve2);
1279
- gl.uniform1f(bloomPrefilterProgram.uniforms.threshold, config.BLOOM_THRESHOLD);
1280
- gl.uniform1i(bloomPrefilterProgram.uniforms.uTexture, source.attach(0));
1281
- blit(last);
1282
-
1283
- bloomBlurProgram.bind();
1284
- for (let i = 0; i < bloomFramebuffers.length; i++) {
1285
- let dest = bloomFramebuffers[i];
1286
- gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
1287
- gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0));
1288
- blit(dest);
1289
- last = dest;
1290
- }
1291
-
1292
- gl.blendFunc(gl.ONE, gl.ONE);
1293
- gl.enable(gl.BLEND);
1294
-
1295
- for (let i = bloomFramebuffers.length - 2; i >= 0; i--) {
1296
- let baseTex = bloomFramebuffers[i];
1297
- gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
1298
- gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0));
1299
- gl.viewport(0, 0, baseTex.width, baseTex.height);
1300
- blit(baseTex);
1301
- last = baseTex;
1302
- }
1303
-
1304
- gl.disable(gl.BLEND);
1305
- bloomFinalProgram.bind();
1306
- gl.uniform2f(bloomFinalProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY);
1307
- gl.uniform1i(bloomFinalProgram.uniforms.uTexture, last.attach(0));
1308
- gl.uniform1f(bloomFinalProgram.uniforms.intensity, config.BLOOM_INTENSITY);
1309
- blit(destination);
1310
- }
1311
-
1312
- function applySunrays (source, mask, destination) {
1313
- gl.disable(gl.BLEND);
1314
- sunraysMaskProgram.bind();
1315
- gl.uniform1i(sunraysMaskProgram.uniforms.uTexture, source.attach(0));
1316
- blit(mask);
1317
-
1318
- sunraysProgram.bind();
1319
- gl.uniform1f(sunraysProgram.uniforms.weight, config.SUNRAYS_WEIGHT);
1320
- gl.uniform1i(sunraysProgram.uniforms.uTexture, mask.attach(0));
1321
- blit(destination);
1322
- }
1323
-
1324
- function blur (target, temp, iterations) {
1325
- blurProgram.bind();
1326
- for (let i = 0; i < iterations; i++) {
1327
- gl.uniform2f(blurProgram.uniforms.texelSize, target.texelSizeX, 0.0);
1328
- gl.uniform1i(blurProgram.uniforms.uTexture, target.attach(0));
1329
- blit(temp);
1330
-
1331
- gl.uniform2f(blurProgram.uniforms.texelSize, 0.0, target.texelSizeY);
1332
- gl.uniform1i(blurProgram.uniforms.uTexture, temp.attach(0));
1333
- blit(target);
1334
- }
1335
- }
1336
-
1337
- function splatPointer (pointer) {
1338
- let dx = pointer.deltaX * config.SPLAT_FORCE;
1339
- let dy = pointer.deltaY * config.SPLAT_FORCE;
1340
- splat(pointer.texcoordX, pointer.texcoordY, dx, dy, pointer.color);
1341
- }
1342
-
1343
- function multipleSplats (amount, intensityMultiplier = 1.0) { // Added intensityMultiplier
1344
- for (let i = 0; i < amount; i++) {
1345
- const color = generateColor();
1346
- // Scale color intensity based on the multiplier
1347
- color.r *= 10.0 * intensityMultiplier;
1348
- color.g *= 10.0 * intensityMultiplier;
1349
- color.b *= 10.0 * intensityMultiplier;
1350
- const x = Math.random();
1351
- const y = Math.random();
1352
- // Scale velocity intensity based on the multiplier
1353
- const dx = 1000 * (Math.random() - 0.5) * intensityMultiplier;
1354
- const dy = 1000 * (Math.random() - 0.5) * intensityMultiplier;
1355
- splat(x, y, dx, dy, color);
1356
- }
1357
- }
1358
-
1359
-
1360
- function splat (x, y, dx, dy, color) {
1361
- splatProgram.bind();
1362
- gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0));
1363
- gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height);
1364
- gl.uniform2f(splatProgram.uniforms.point, x, y);
1365
- gl.uniform3f(splatProgram.uniforms.color, dx, dy, 0.0);
1366
- gl.uniform1f(splatProgram.uniforms.radius, correctRadius(config.SPLAT_RADIUS / 100.0));
1367
- blit(velocity.write);
1368
- velocity.swap();
1369
-
1370
- gl.uniform1i(splatProgram.uniforms.uTarget, dye.read.attach(0));
1371
- gl.uniform3f(splatProgram.uniforms.color, color.r, color.g, color.b);
1372
- blit(dye.write);
1373
- dye.swap();
1374
- }
1375
-
1376
- function correctRadius (radius) {
1377
- let aspectRatio = canvas.width / canvas.height;
1378
- if (aspectRatio > 1)
1379
- radius *= aspectRatio;
1380
- return radius;
1381
- }
1382
-
1383
- // Get reference to container to check event targets
1384
- const containerElement = document.querySelector('.container');
1385
-
1386
- window.addEventListener('mousedown', e => {
1387
- // Check if the click started inside the container/form
1388
- if (containerElement && containerElement.contains(e.target)) {
1389
- // If the target (or its ancestor) is the container, ignore for fluid
1390
- return;
1391
- }
1392
-
1393
- const rect = canvas.getBoundingClientRect();
1394
- // Calculate position relative to the canvas origin
1395
- let posX = scaleByPixelRatio(e.clientX - rect.left);
1396
- let posY = scaleByPixelRatio(e.clientY - rect.top);
1397
-
1398
- let pointer = pointers.find(p => p.id == -1);
1399
- if (pointer == null)
1400
- pointer = new pointerPrototype();
1401
-
1402
- // Pass the SCALED PIXEL coordinates relative to the canvas
1403
- updatePointerDownData(pointer, -1, posX, posY);
1404
- });
1405
-
1406
- window.addEventListener('mousemove', e => {
1407
- let pointer = pointers[0]; // Assuming the first pointer is for mouse
1408
- if (!pointer.down) return; // Only track if mouse is down
1409
-
1410
- const rect = canvas.getBoundingClientRect();
1411
- // Calculate position relative to the canvas origin
1412
- let posX = scaleByPixelRatio(e.clientX - rect.left);
1413
- let posY = scaleByPixelRatio(e.clientY - rect.top);
1414
-
1415
- // Pass the SCALED PIXEL coordinates relative to the canvas
1416
- updatePointerMoveData(pointer, posX, posY);
1417
- });
1418
-
1419
- // window.addEventListener('mouseup', ...) // Keep this listener as is
1420
-
1421
- window.addEventListener('touchstart', e => {
1422
- // Note: We generally avoid preventDefault on window touchstart/move
1423
- // as it can break scrolling. Let's see if it works without it.
1424
-
1425
- const touches = e.targetTouches;
1426
- const rect = canvas.getBoundingClientRect();
1427
- let didProcessTouchOutside = false;
1428
-
1429
- for (let i = 0; i < touches.length; i++) {
1430
- // Check if the touch started inside the container/form
1431
- if (containerElement && containerElement.contains(touches[i].target)) {
1432
- continue; // Ignore this specific touch for fluid
1433
- }
1434
-
1435
- didProcessTouchOutside = true; // Mark that at least one touch outside occurred
1436
-
1437
- // Ensure pointers array is large enough
1438
- // Use pointers.length directly, as pointers[0] is mouse
1439
- while (pointers.length <= touches[i].identifier + 1)
1440
- pointers.push(new pointerPrototype());
1441
-
1442
- // Calculate position relative to the canvas origin
1443
- let relativeX = touches[i].clientX - rect.left;
1444
- let relativeY = touches[i].clientY - rect.top;
1445
- let posX = scaleByPixelRatio(relativeX);
1446
- let posY = scaleByPixelRatio(relativeY);
1447
-
1448
- // Find the correct pointer slot or reuse an inactive one if needed
1449
- // For simplicity, let's just assign based on identifier + 1 for now
1450
- // (assuming identifier 0 is first touch, 1 is second etc.)
1451
- let pointerIndex = touches[i].identifier + 1;
1452
- if(pointerIndex >= pointers.length) pointerIndex = pointers.length -1; // Safety check
1453
-
1454
-
1455
- // Pass the SCALED PIXEL coordinates relative to the canvas
1456
- updatePointerDownData(pointers[pointerIndex], touches[i].identifier, posX, posY);
1457
- }
1458
- // if (didProcessTouchOutside) { e.preventDefault(); } // Avoid if possible
1459
- });
1460
-
1461
- window.addEventListener('touchmove', e => {
1462
- const touches = e.targetTouches;
1463
- const rect = canvas.getBoundingClientRect();
1464
-
1465
- for (let i = 0; i < touches.length; i++) {
1466
- // Find the pointer associated with this touch ID
1467
- let pointer = pointers.find(p => p.id == touches[i].identifier);
1468
- if (!pointer || !pointer.down) continue; // Ignore if not tracked or not down
1469
-
1470
- // Calculate position relative to the canvas origin
1471
- let relativeX = touches[i].clientX - rect.left;
1472
- let relativeY = touches[i].clientY - rect.top;
1473
- let posX = scaleByPixelRatio(relativeX);
1474
- let posY = scaleByPixelRatio(relativeY);
1475
-
1476
- // Pass the SCALED PIXEL coordinates relative to the canvas
1477
- updatePointerMoveData(pointer, posX, posY);
1478
- }
1479
- }, false); // UseCapture = false is default, but good to be explicit
1480
-
1481
- window.addEventListener('touchend', e => {
1482
- const touches = e.changedTouches;
1483
- for (let i = 0; i < touches.length; i++)
1484
- {
1485
- let pointer = pointers.find(p => p.id == touches[i].identifier);
1486
- if (pointer == null) continue;
1487
- updatePointerUpData(pointer);
1488
- }
1489
- });
1490
-
1491
- window.addEventListener('keydown', e => {
1492
- if (e.code === 'KeyP')
1493
- config.PAUSED = !config.PAUSED;
1494
- if (e.key === ' ')
1495
- splatStack.push(parseInt(Math.random() * 20) + 5);
1496
- });
1497
-
1498
- function updatePointerDownData (pointer, id, posX, posY) {
1499
- pointer.id = id;
1500
- pointer.down = true;
1501
- pointer.moved = false;
1502
- pointer.texcoordX = posX / canvas.width;
1503
- pointer.texcoordY = 1.0 - posY / canvas.height;
1504
- pointer.prevTexcoordX = pointer.texcoordX;
1505
- pointer.prevTexcoordY = pointer.texcoordY;
1506
- pointer.deltaX = 0;
1507
- pointer.deltaY = 0;
1508
- pointer.color = generateColor();
1509
- }
1510
-
1511
- function updatePointerMoveData (pointer, posX, posY) {
1512
- pointer.prevTexcoordX = pointer.texcoordX;
1513
- pointer.prevTexcoordY = pointer.texcoordY;
1514
- pointer.texcoordX = posX / canvas.width;
1515
- pointer.texcoordY = 1.0 - posY / canvas.height;
1516
- pointer.deltaX = correctDeltaX(pointer.texcoordX - pointer.prevTexcoordX);
1517
- pointer.deltaY = correctDeltaY(pointer.texcoordY - pointer.prevTexcoordY);
1518
- pointer.moved = Math.abs(pointer.deltaX) > 0 || Math.abs(pointer.deltaY) > 0;
1519
- }
1520
-
1521
- function updatePointerUpData (pointer) {
1522
- pointer.down = false;
1523
- }
1524
-
1525
- function correctDeltaX (delta) {
1526
- let aspectRatio = canvas.width / canvas.height;
1527
- if (aspectRatio < 1) delta *= aspectRatio;
1528
- return delta;
1529
- }
1530
-
1531
- function correctDeltaY (delta) {
1532
- let aspectRatio = canvas.width / canvas.height;
1533
- if (aspectRatio > 1) delta /= aspectRatio;
1534
- return delta;
1535
- }
1536
-
1537
- function generateColor () {
1538
- let c = HSVtoRGB(Math.random(), 1.0, 1.0);
1539
- c.r *= 0.15;
1540
- c.g *= 0.15;
1541
- c.b *= 0.15;
1542
- return c;
1543
- }
1544
-
1545
- function HSVtoRGB (h, s, v) {
1546
- let r, g, b, i, f, p, q, t;
1547
- i = Math.floor(h * 6);
1548
- f = h * 6 - i;
1549
- p = v * (1 - s);
1550
- q = v * (1 - f * s);
1551
- t = v * (1 - (1 - f) * s);
1552
-
1553
- switch (i % 6) {
1554
- case 0: r = v, g = t, b = p; break;
1555
- case 1: r = q, g = v, b = p; break;
1556
- case 2: r = p, g = v, b = t; break;
1557
- case 3: r = p, g = q, b = v; break;
1558
- case 4: r = t, g = p, b = v; break;
1559
- case 5: r = v, g = p, b = q; break;
1560
- }
1561
-
1562
- return {
1563
- r,
1564
- g,
1565
- b
1566
- };
1567
- }
1568
-
1569
- function normalizeColor (input) {
1570
- let output = {
1571
- r: input.r / 255,
1572
- g: input.g / 255,
1573
- b: input.b / 255
1574
- };
1575
- return output;
1576
- }
1577
-
1578
- function wrap (value, min, max) {
1579
- let range = max - min;
1580
- if (range == 0) return min;
1581
- return (value - min) % range + min;
1582
- }
1583
-
1584
- function getResolution (resolution) {
1585
- let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight;
1586
- if (aspectRatio < 1)
1587
- aspectRatio = 1.0 / aspectRatio;
1588
-
1589
- let min = Math.round(resolution);
1590
- let max = Math.round(resolution * aspectRatio);
1591
-
1592
- if (gl.drawingBufferWidth > gl.drawingBufferHeight)
1593
- return { width: max, height: min };
1594
- else
1595
- return { width: min, height: max };
1596
- }
1597
-
1598
- function getTextureScale (texture, width, height) {
1599
- return {
1600
- x: width / texture.width,
1601
- y: height / texture.height
1602
- };
1603
- }
1604
-
1605
- function scaleByPixelRatio (input) {
1606
- let pixelRatio = window.devicePixelRatio || 1;
1607
- return Math.floor(input * pixelRatio);
1608
- }
1609
-
1610
- function hashCode (s) {
1611
- if (s.length == 0) return 0;
1612
- let hash = 0;
1613
- for (let i = 0; i < s.length; i++) {
1614
- hash = (hash << 5) - hash + s.charCodeAt(i);
1615
- hash |= 0; // Convert to 32bit integer
1616
- }
1617
- return hash;
1618
- };