diff --git a/public/fluid.js b/public/fluid.js index e2a4c59..b8d75ea 100644 --- a/public/fluid.js +++ b/public/fluid.js @@ -23,53 +23,53 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -'use strict'; +"use strict"; // Simulation section -const canvas = document.getElementsByTagName('canvas')[0]; +const canvas = document.getElementsByTagName("canvas")[0]; resizeCanvas(); let config = { - SIM_RESOLUTION: 128, - DYE_RESOLUTION: 1024, - CAPTURE_RESOLUTION: 512, - DENSITY_DISSIPATION: 1, - VELOCITY_DISSIPATION: 0.2, - PRESSURE: 0.8, - PRESSURE_ITERATIONS: 20, - CURL: 30, - SPLAT_RADIUS: 0.25, - SPLAT_FORCE: 6000, - SHADING: true, - COLORFUL: true, - COLOR_UPDATE_SPEED: 10, - PAUSED: false, - BACK_COLOR: { r: 0, g: 0, b: 0 }, - TRANSPARENT: false, - BLOOM: true, - BLOOM_ITERATIONS: 8, - BLOOM_RESOLUTION: 256, - BLOOM_INTENSITY: 0.3, - BLOOM_THRESHOLD: 0.6, - BLOOM_SOFT_KNEE: 0.7, - SUNRAYS: true, - SUNRAYS_RESOLUTION: 196, - SUNRAYS_WEIGHT: 1.0, + SIM_RESOLUTION: 128, + DYE_RESOLUTION: 1024, + CAPTURE_RESOLUTION: 512, + DENSITY_DISSIPATION: 1, + VELOCITY_DISSIPATION: 0.2, + PRESSURE: 0.8, + PRESSURE_ITERATIONS: 20, + CURL: 30, + SPLAT_RADIUS: 0.25, + SPLAT_FORCE: 6000, + SHADING: true, + COLORFUL: true, + COLOR_UPDATE_SPEED: 10, + PAUSED: false, + BACK_COLOR: { r: 0, g: 0, b: 0 }, + TRANSPARENT: false, + BLOOM: true, + BLOOM_ITERATIONS: 8, + BLOOM_RESOLUTION: 256, + BLOOM_INTENSITY: 0.3, + BLOOM_THRESHOLD: 0.6, + BLOOM_SOFT_KNEE: 0.7, + SUNRAYS: true, + SUNRAYS_RESOLUTION: 196, + SUNRAYS_WEIGHT: 1.0, }; class pointerPrototype { - constructor() { - this.id = -1; - this.texcoordX = 0; - this.texcoordY = 0; - this.prevTexcoordX = 0; - this.prevTexcoordY = 0; - this.deltaX = 0; - this.deltaY = 0; - this.down = false; - this.moved = false; - this.color = [30, 0, 300]; - } + constructor() { + this.id = -1; + this.texcoordX = 0; + this.texcoordY = 0; + this.prevTexcoordX = 0; + this.prevTexcoordY = 0; + this.deltaX = 0; + this.deltaY = 0; + this.down = false; + this.moved = false; + this.color = [30, 0, 300]; + } } let pointers = []; @@ -79,253 +79,278 @@ pointers.push(new pointerPrototype()); const { gl, ext } = getWebGLContext(canvas); if (isMobile()) { - config.DYE_RESOLUTION = 512; + config.DYE_RESOLUTION = 512; } if (window.screen.availHeight >= 1400) { - config.SIM_RESOLUTION = 256; - config.DYE_RESOLUTION = 4096; - config.BLOOM_RESOLUTION = 512; + config.SIM_RESOLUTION = 256; + config.DYE_RESOLUTION = 4096; + config.BLOOM_RESOLUTION = 512; } if (!ext.supportLinearFiltering) { - config.DYE_RESOLUTION = 512; - config.SHADING = false; - config.BLOOM = false; - config.SUNRAYS = false; + config.DYE_RESOLUTION = 512; + config.SHADING = false; + config.BLOOM = false; + config.SUNRAYS = false; } function getWebGLContext(canvas) { - const params = { alpha: true, depth: false, stencil: false, antialias: false, preserveDrawingBuffer: false }; + const params = { + alpha: true, + depth: false, + stencil: false, + antialias: false, + preserveDrawingBuffer: false, + }; - let gl = canvas.getContext('webgl2', params); - const isWebGL2 = !!gl; - if (!isWebGL2) - gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params); + let gl = canvas.getContext("webgl2", params); + const isWebGL2 = !!gl; + if (!isWebGL2) + gl = + canvas.getContext("webgl", params) || + canvas.getContext("experimental-webgl", params); - let halfFloat; - let supportLinearFiltering; - if (isWebGL2) { - gl.getExtension('EXT_color_buffer_float'); - supportLinearFiltering = gl.getExtension('OES_texture_float_linear'); - } else { - halfFloat = gl.getExtension('OES_texture_half_float'); - supportLinearFiltering = gl.getExtension('OES_texture_half_float_linear'); - } + let halfFloat; + let supportLinearFiltering; + if (isWebGL2) { + gl.getExtension("EXT_color_buffer_float"); + supportLinearFiltering = gl.getExtension("OES_texture_float_linear"); + } else { + halfFloat = gl.getExtension("OES_texture_half_float"); + supportLinearFiltering = gl.getExtension("OES_texture_half_float_linear"); + } - gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.clearColor(0.0, 0.0, 0.0, 1.0); - const halfFloatTexType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES; - let formatRGBA; - let formatRG; - let formatR; + const halfFloatTexType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES; + let formatRGBA; + let formatRG; + let formatR; - if (isWebGL2) { - formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType); - formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType); - formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType); - } - else { - formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); - formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); - formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); - } + if (isWebGL2) { + formatRGBA = getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, halfFloatTexType); + formatRG = getSupportedFormat(gl, gl.RG16F, gl.RG, halfFloatTexType); + formatR = getSupportedFormat(gl, gl.R16F, gl.RED, halfFloatTexType); + } else { + formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); + formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); + formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType); + } - return { - gl, - ext: { - formatRGBA, - formatRG, - formatR, - halfFloatTexType, - supportLinearFiltering - } - }; + return { + gl, + ext: { + formatRGBA, + formatRG, + formatR, + halfFloatTexType, + supportLinearFiltering, + }, + }; } function getSupportedFormat(gl, internalFormat, format, type) { - if (!supportRenderTextureFormat(gl, internalFormat, format, type)) { - switch (internalFormat) { - case gl.R16F: - return getSupportedFormat(gl, gl.RG16F, gl.RG, type); - case gl.RG16F: - return getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, type); - default: - return null; - } - } + if (!supportRenderTextureFormat(gl, internalFormat, format, type)) { + switch (internalFormat) { + case gl.R16F: + return getSupportedFormat(gl, gl.RG16F, gl.RG, type); + case gl.RG16F: + return getSupportedFormat(gl, gl.RGBA16F, gl.RGBA, type); + default: + return null; + } + } - return { - internalFormat, - format - }; + return { + internalFormat, + format, + }; } function supportRenderTextureFormat(gl, internalFormat, format, type) { - let texture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 4, 4, 0, format, type, null); + let texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 4, 4, 0, format, type, null); - let fbo = gl.createFramebuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + let fbo = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + texture, + 0 + ); - let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); - return status == gl.FRAMEBUFFER_COMPLETE; + let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + return status == gl.FRAMEBUFFER_COMPLETE; } function isMobile() { - return /Mobi|Android/i.test(navigator.userAgent); + return /Mobi|Android/i.test(navigator.userAgent); } function captureScreenshot() { - let res = getResolution(config.CAPTURE_RESOLUTION); - let target = createFBO(res.width, res.height, ext.formatRGBA.internalFormat, ext.formatRGBA.format, ext.halfFloatTexType, gl.NEAREST); - render(target); + let res = getResolution(config.CAPTURE_RESOLUTION); + let target = createFBO( + res.width, + res.height, + ext.formatRGBA.internalFormat, + ext.formatRGBA.format, + ext.halfFloatTexType, + gl.NEAREST + ); + render(target); - let texture = framebufferToTexture(target); - texture = normalizeTexture(texture, target.width, target.height); + let texture = framebufferToTexture(target); + texture = normalizeTexture(texture, target.width, target.height); - let captureCanvas = textureToCanvas(texture, target.width, target.height); - let datauri = captureCanvas.toDataURL(); - downloadURI('fluid.png', datauri); - URL.revokeObjectURL(datauri); + let captureCanvas = textureToCanvas(texture, target.width, target.height); + let datauri = captureCanvas.toDataURL(); + downloadURI("fluid.png", datauri); + URL.revokeObjectURL(datauri); } function framebufferToTexture(target) { - gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo); - let length = target.width * target.height * 4; - let texture = new Float32Array(length); - gl.readPixels(0, 0, target.width, target.height, gl.RGBA, gl.FLOAT, texture); - return texture; + gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo); + let length = target.width * target.height * 4; + let texture = new Float32Array(length); + gl.readPixels(0, 0, target.width, target.height, gl.RGBA, gl.FLOAT, texture); + return texture; } function normalizeTexture(texture, width, height) { - let result = new Uint8Array(texture.length); - let id = 0; - for (let i = height - 1; i >= 0; i--) { - for (let j = 0; j < width; j++) { - let nid = i * width * 4 + j * 4; - result[nid + 0] = clamp01(texture[id + 0]) * 255; - result[nid + 1] = clamp01(texture[id + 1]) * 255; - result[nid + 2] = clamp01(texture[id + 2]) * 255; - result[nid + 3] = clamp01(texture[id + 3]) * 255; - id += 4; - } - } - return result; + let result = new Uint8Array(texture.length); + let id = 0; + for (let i = height - 1; i >= 0; i--) { + for (let j = 0; j < width; j++) { + let nid = i * width * 4 + j * 4; + result[nid + 0] = clamp01(texture[id + 0]) * 255; + result[nid + 1] = clamp01(texture[id + 1]) * 255; + result[nid + 2] = clamp01(texture[id + 2]) * 255; + result[nid + 3] = clamp01(texture[id + 3]) * 255; + id += 4; + } + } + return result; } function clamp01(input) { - return Math.min(Math.max(input, 0), 1); + return Math.min(Math.max(input, 0), 1); } function textureToCanvas(texture, width, height) { - let captureCanvas = document.createElement('canvas'); - let ctx = captureCanvas.getContext('2d'); - captureCanvas.width = width; - captureCanvas.height = height; + let captureCanvas = document.createElement("canvas"); + let ctx = captureCanvas.getContext("2d"); + captureCanvas.width = width; + captureCanvas.height = height; - let imageData = ctx.createImageData(width, height); - imageData.data.set(texture); - ctx.putImageData(imageData, 0, 0); + let imageData = ctx.createImageData(width, height); + imageData.data.set(texture); + ctx.putImageData(imageData, 0, 0); - return captureCanvas; + return captureCanvas; } class Material { - constructor(vertexShader, fragmentShaderSource) { - this.vertexShader = vertexShader; - this.fragmentShaderSource = fragmentShaderSource; - this.programs = []; - this.activeProgram = null; - this.uniforms = []; - } + constructor(vertexShader, fragmentShaderSource) { + this.vertexShader = vertexShader; + this.fragmentShaderSource = fragmentShaderSource; + this.programs = []; + this.activeProgram = null; + this.uniforms = []; + } - setKeywords(keywords) { - let hash = 0; - for (let i = 0; i < keywords.length; i++) - hash += hashCode(keywords[i]); + setKeywords(keywords) { + let hash = 0; + for (let i = 0; i < keywords.length; i++) hash += hashCode(keywords[i]); - let program = this.programs[hash]; - if (program == null) { - let fragmentShader = compileShader(gl.FRAGMENT_SHADER, this.fragmentShaderSource, keywords); - program = createProgram(this.vertexShader, fragmentShader); - this.programs[hash] = program; - } + let program = this.programs[hash]; + if (program == null) { + let fragmentShader = compileShader( + gl.FRAGMENT_SHADER, + this.fragmentShaderSource, + keywords + ); + program = createProgram(this.vertexShader, fragmentShader); + this.programs[hash] = program; + } - if (program == this.activeProgram) return; + if (program == this.activeProgram) return; - this.uniforms = getUniforms(program); - this.activeProgram = program; - } + this.uniforms = getUniforms(program); + this.activeProgram = program; + } - bind() { - gl.useProgram(this.activeProgram); - } + bind() { + gl.useProgram(this.activeProgram); + } } class Program { - constructor(vertexShader, fragmentShader) { - this.uniforms = {}; - this.program = createProgram(vertexShader, fragmentShader); - this.uniforms = getUniforms(this.program); - } + constructor(vertexShader, fragmentShader) { + this.uniforms = {}; + this.program = createProgram(vertexShader, fragmentShader); + this.uniforms = getUniforms(this.program); + } - bind() { - gl.useProgram(this.program); - } + bind() { + gl.useProgram(this.program); + } } function createProgram(vertexShader, fragmentShader) { - let program = gl.createProgram(); - gl.attachShader(program, vertexShader); - gl.attachShader(program, fragmentShader); - gl.linkProgram(program); + let program = gl.createProgram(); + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + gl.linkProgram(program); - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) - console.trace(gl.getProgramInfoLog(program)); + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) + console.trace(gl.getProgramInfoLog(program)); - return program; + return program; } function getUniforms(program) { - let uniforms = []; - let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); - for (let i = 0; i < uniformCount; i++) { - let uniformName = gl.getActiveUniform(program, i).name; - uniforms[uniformName] = gl.getUniformLocation(program, uniformName); - } - return uniforms; + let uniforms = []; + let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + for (let i = 0; i < uniformCount; i++) { + let uniformName = gl.getActiveUniform(program, i).name; + uniforms[uniformName] = gl.getUniformLocation(program, uniformName); + } + return uniforms; } function compileShader(type, source, keywords) { - source = addKeywords(source, keywords); + source = addKeywords(source, keywords); - const shader = gl.createShader(type); - gl.shaderSource(shader, source); - gl.compileShader(shader); + const shader = gl.createShader(type); + gl.shaderSource(shader, source); + gl.compileShader(shader); - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) - console.trace(gl.getShaderInfoLog(shader)); + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) + console.trace(gl.getShaderInfoLog(shader)); - return shader; -}; - -function addKeywords(source, keywords) { - if (keywords == null) return source; - let keywordsString = ''; - keywords.forEach(keyword => { - keywordsString += '#define ' + keyword + '\n'; - }); - return keywordsString + source; + return shader; } -const baseVertexShader = compileShader(gl.VERTEX_SHADER, ` +function addKeywords(source, keywords) { + if (keywords == null) return source; + let keywordsString = ""; + keywords.forEach((keyword) => { + keywordsString += "#define " + keyword + "\n"; + }); + return keywordsString + source; +} + +const baseVertexShader = compileShader( + gl.VERTEX_SHADER, + ` precision highp float; attribute vec2 aPosition; @@ -344,9 +369,12 @@ const baseVertexShader = compileShader(gl.VERTEX_SHADER, ` vB = vUv - vec2(0.0, texelSize.y); gl_Position = vec4(aPosition, 0.0, 1.0); } -`); +` +); -const blurVertexShader = compileShader(gl.VERTEX_SHADER, ` +const blurVertexShader = compileShader( + gl.VERTEX_SHADER, + ` precision highp float; attribute vec2 aPosition; @@ -362,9 +390,12 @@ const blurVertexShader = compileShader(gl.VERTEX_SHADER, ` vR = vUv + texelSize * offset; gl_Position = vec4(aPosition, 0.0, 1.0); } -`); +` +); -const blurShader = compileShader(gl.FRAGMENT_SHADER, ` +const blurShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision mediump float; precision mediump sampler2D; @@ -379,9 +410,12 @@ const blurShader = compileShader(gl.FRAGMENT_SHADER, ` sum += texture2D(uTexture, vR) * 0.35294117; gl_FragColor = sum; } -`); +` +); -const copyShader = compileShader(gl.FRAGMENT_SHADER, ` +const copyShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision mediump float; precision mediump sampler2D; @@ -391,9 +425,12 @@ const copyShader = compileShader(gl.FRAGMENT_SHADER, ` void main () { gl_FragColor = texture2D(uTexture, vUv); } -`); +` +); -const clearShader = compileShader(gl.FRAGMENT_SHADER, ` +const clearShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision mediump float; precision mediump sampler2D; @@ -404,9 +441,12 @@ const clearShader = compileShader(gl.FRAGMENT_SHADER, ` void main () { gl_FragColor = value * texture2D(uTexture, vUv); } -`); +` +); -const colorShader = compileShader(gl.FRAGMENT_SHADER, ` +const colorShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision mediump float; uniform vec4 color; @@ -414,9 +454,12 @@ const colorShader = compileShader(gl.FRAGMENT_SHADER, ` void main () { gl_FragColor = color; } -`); +` +); -const checkerboardShader = compileShader(gl.FRAGMENT_SHADER, ` +const checkerboardShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision highp float; precision highp sampler2D; @@ -432,7 +475,8 @@ const checkerboardShader = compileShader(gl.FRAGMENT_SHADER, ` v = v * 0.1 + 0.8; gl_FragColor = vec4(vec3(v), 1.0); } -`); +` +); const displayShaderSource = ` precision highp float; @@ -499,7 +543,9 @@ const displayShaderSource = ` } `; -const bloomPrefilterShader = compileShader(gl.FRAGMENT_SHADER, ` +const bloomPrefilterShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision mediump float; precision mediump sampler2D; @@ -516,9 +562,12 @@ const bloomPrefilterShader = compileShader(gl.FRAGMENT_SHADER, ` c *= max(rq, br - threshold) / max(br, 0.0001); gl_FragColor = vec4(c, 0.0); } -`); +` +); -const bloomBlurShader = compileShader(gl.FRAGMENT_SHADER, ` +const bloomBlurShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision mediump float; precision mediump sampler2D; @@ -537,9 +586,12 @@ const bloomBlurShader = compileShader(gl.FRAGMENT_SHADER, ` sum *= 0.25; gl_FragColor = sum; } -`); +` +); -const bloomFinalShader = compileShader(gl.FRAGMENT_SHADER, ` +const bloomFinalShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision mediump float; precision mediump sampler2D; @@ -559,9 +611,12 @@ const bloomFinalShader = compileShader(gl.FRAGMENT_SHADER, ` sum *= 0.25; gl_FragColor = sum * intensity; } -`); +` +); -const sunraysMaskShader = compileShader(gl.FRAGMENT_SHADER, ` +const sunraysMaskShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision highp float; precision highp sampler2D; @@ -574,9 +629,12 @@ const sunraysMaskShader = compileShader(gl.FRAGMENT_SHADER, ` c.a = 1.0 - min(max(br * 20.0, 0.0), 0.8); gl_FragColor = c; } -`); +` +); -const sunraysShader = compileShader(gl.FRAGMENT_SHADER, ` +const sunraysShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision highp float; precision highp sampler2D; @@ -609,9 +667,12 @@ const sunraysShader = compileShader(gl.FRAGMENT_SHADER, ` gl_FragColor = vec4(color * Exposure, 0.0, 0.0, 1.0); } -`); +` +); -const splatShader = compileShader(gl.FRAGMENT_SHADER, ` +const splatShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision highp float; precision highp sampler2D; @@ -629,9 +690,12 @@ const splatShader = compileShader(gl.FRAGMENT_SHADER, ` vec3 base = texture2D(uTarget, vUv).xyz; gl_FragColor = vec4(base + splat, 1.0); } -`); +` +); -const advectionShader = compileShader(gl.FRAGMENT_SHADER, ` +const advectionShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision highp float; precision highp sampler2D; @@ -668,10 +732,12 @@ const advectionShader = compileShader(gl.FRAGMENT_SHADER, ` float decay = 1.0 + dissipation * dt; gl_FragColor = result / decay; }`, - ext.supportLinearFiltering ? null : ['MANUAL_FILTERING'] + ext.supportLinearFiltering ? null : ["MANUAL_FILTERING"] ); -const divergenceShader = compileShader(gl.FRAGMENT_SHADER, ` +const divergenceShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision mediump float; precision mediump sampler2D; @@ -697,9 +763,12 @@ const divergenceShader = compileShader(gl.FRAGMENT_SHADER, ` float div = 0.5 * (R - L + T - B); gl_FragColor = vec4(div, 0.0, 0.0, 1.0); } -`); +` +); -const curlShader = compileShader(gl.FRAGMENT_SHADER, ` +const curlShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision mediump float; precision mediump sampler2D; @@ -718,9 +787,12 @@ const curlShader = compileShader(gl.FRAGMENT_SHADER, ` float vorticity = R - L - T + B; gl_FragColor = vec4(0.5 * vorticity, 0.0, 0.0, 1.0); } -`); +` +); -const vorticityShader = compileShader(gl.FRAGMENT_SHADER, ` +const vorticityShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision highp float; precision highp sampler2D; @@ -751,9 +823,12 @@ const vorticityShader = compileShader(gl.FRAGMENT_SHADER, ` velocity = min(max(velocity, -1000.0), 1000.0); gl_FragColor = vec4(velocity, 0.0, 1.0); } -`); +` +); -const pressureShader = compileShader(gl.FRAGMENT_SHADER, ` +const pressureShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision mediump float; precision mediump sampler2D; @@ -775,9 +850,12 @@ const pressureShader = compileShader(gl.FRAGMENT_SHADER, ` float pressure = (L + R + B + T - divergence) * 0.25; gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0); } -`); +` +); -const gradientSubtractShader = compileShader(gl.FRAGMENT_SHADER, ` +const gradientSubtractShader = compileShader( + gl.FRAGMENT_SHADER, + ` precision mediump float; precision mediump sampler2D; @@ -798,38 +876,46 @@ const gradientSubtractShader = compileShader(gl.FRAGMENT_SHADER, ` velocity.xy -= vec2(R - L, T - B); gl_FragColor = vec4(velocity, 0.0, 1.0); } -`); +` +); const blit = (() => { - gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer()); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW); - gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(0); + gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), + gl.STATIC_DRAW + ); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer()); + gl.bufferData( + gl.ELEMENT_ARRAY_BUFFER, + new Uint16Array([0, 1, 2, 0, 2, 3]), + gl.STATIC_DRAW + ); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + gl.enableVertexAttribArray(0); - return (target, clear = false) => { - if (target == null) { - gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - } - else { - gl.viewport(0, 0, target.width, target.height); - gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo); - } - if (clear) { - gl.clearColor(0.0, 0.0, 0.0, 1.0); - gl.clear(gl.COLOR_BUFFER_BIT); - } - // CHECK_FRAMEBUFFER_STATUS(); - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); - }; + return (target, clear = false) => { + if (target == null) { + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } else { + gl.viewport(0, 0, target.width, target.height); + gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo); + } + if (clear) { + gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT); + } + // CHECK_FRAMEBUFFER_STATUS(); + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); + }; })(); function CHECK_FRAMEBUFFER_STATUS() { - let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); - if (status != gl.FRAMEBUFFER_COMPLETE) - console.trace("Framebuffer error: " + status); + let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + if (status != gl.FRAMEBUFFER_COMPLETE) + console.trace("Framebuffer error: " + status); } let dye; @@ -842,14 +928,17 @@ let bloomFramebuffers = []; let sunrays; let sunraysTemp; -let ditheringTexture = createTextureAsync('/LDR_LLL1_0.png'); +let ditheringTexture = createTextureAsync("/LDR_LLL1_0.png"); const blurProgram = new Program(blurVertexShader, blurShader); const copyProgram = new Program(baseVertexShader, copyShader); const clearProgram = new Program(baseVertexShader, clearShader); const colorProgram = new Program(baseVertexShader, colorShader); const checkerboardProgram = new Program(baseVertexShader, checkerboardShader); -const bloomPrefilterProgram = new Program(baseVertexShader, bloomPrefilterShader); +const bloomPrefilterProgram = new Program( + baseVertexShader, + bloomPrefilterShader +); const bloomBlurProgram = new Program(baseVertexShader, bloomBlurShader); const bloomFinalProgram = new Program(baseVertexShader, bloomFinalShader); const sunraysMaskProgram = new Program(baseVertexShader, sunraysMaskShader); @@ -860,193 +949,298 @@ const divergenceProgram = new Program(baseVertexShader, divergenceShader); const curlProgram = new Program(baseVertexShader, curlShader); const vorticityProgram = new Program(baseVertexShader, vorticityShader); const pressureProgram = new Program(baseVertexShader, pressureShader); -const gradienSubtractProgram = new Program(baseVertexShader, gradientSubtractShader); +const gradienSubtractProgram = new Program( + baseVertexShader, + gradientSubtractShader +); const displayMaterial = new Material(baseVertexShader, displayShaderSource); function initFramebuffers() { - let simRes = getResolution(config.SIM_RESOLUTION); - let dyeRes = getResolution(config.DYE_RESOLUTION); + let simRes = getResolution(config.SIM_RESOLUTION); + let dyeRes = getResolution(config.DYE_RESOLUTION); - const texType = ext.halfFloatTexType; - const rgba = ext.formatRGBA; - const rg = ext.formatRG; - const r = ext.formatR; - const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST; + const texType = ext.halfFloatTexType; + const rgba = ext.formatRGBA; + const rg = ext.formatRG; + const r = ext.formatR; + const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST; - gl.disable(gl.BLEND); + gl.disable(gl.BLEND); - if (dye == null) - dye = createDoubleFBO(dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering); - else - dye = resizeDoubleFBO(dye, dyeRes.width, dyeRes.height, rgba.internalFormat, rgba.format, texType, filtering); + if (dye == null) + dye = createDoubleFBO( + dyeRes.width, + dyeRes.height, + rgba.internalFormat, + rgba.format, + texType, + filtering + ); + else + dye = resizeDoubleFBO( + dye, + dyeRes.width, + dyeRes.height, + rgba.internalFormat, + rgba.format, + texType, + filtering + ); - if (velocity == null) - velocity = createDoubleFBO(simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering); - else - velocity = resizeDoubleFBO(velocity, simRes.width, simRes.height, rg.internalFormat, rg.format, texType, filtering); + if (velocity == null) + velocity = createDoubleFBO( + simRes.width, + simRes.height, + rg.internalFormat, + rg.format, + texType, + filtering + ); + else + velocity = resizeDoubleFBO( + velocity, + simRes.width, + simRes.height, + rg.internalFormat, + rg.format, + texType, + filtering + ); - divergence = createFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST); - curl = createFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST); - pressure = createDoubleFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST); + divergence = createFBO( + simRes.width, + simRes.height, + r.internalFormat, + r.format, + texType, + gl.NEAREST + ); + curl = createFBO( + simRes.width, + simRes.height, + r.internalFormat, + r.format, + texType, + gl.NEAREST + ); + pressure = createDoubleFBO( + simRes.width, + simRes.height, + r.internalFormat, + r.format, + texType, + gl.NEAREST + ); - initBloomFramebuffers(); - initSunraysFramebuffers(); + initBloomFramebuffers(); + initSunraysFramebuffers(); } function initBloomFramebuffers() { - let res = getResolution(config.BLOOM_RESOLUTION); + let res = getResolution(config.BLOOM_RESOLUTION); - const texType = ext.halfFloatTexType; - const rgba = ext.formatRGBA; - const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST; + const texType = ext.halfFloatTexType; + const rgba = ext.formatRGBA; + const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST; - bloom = createFBO(res.width, res.height, rgba.internalFormat, rgba.format, texType, filtering); + bloom = createFBO( + res.width, + res.height, + rgba.internalFormat, + rgba.format, + texType, + filtering + ); - bloomFramebuffers.length = 0; - for (let i = 0; i < config.BLOOM_ITERATIONS; i++) { - let width = res.width >> (i + 1); - let height = res.height >> (i + 1); + bloomFramebuffers.length = 0; + for (let i = 0; i < config.BLOOM_ITERATIONS; i++) { + let width = res.width >> (i + 1); + let height = res.height >> (i + 1); - if (width < 2 || height < 2) break; + if (width < 2 || height < 2) break; - let fbo = createFBO(width, height, rgba.internalFormat, rgba.format, texType, filtering); - bloomFramebuffers.push(fbo); - } + let fbo = createFBO( + width, + height, + rgba.internalFormat, + rgba.format, + texType, + filtering + ); + bloomFramebuffers.push(fbo); + } } function initSunraysFramebuffers() { - let res = getResolution(config.SUNRAYS_RESOLUTION); + let res = getResolution(config.SUNRAYS_RESOLUTION); - const texType = ext.halfFloatTexType; - const r = ext.formatR; - const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST; + const texType = ext.halfFloatTexType; + const r = ext.formatR; + const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST; - sunrays = createFBO(res.width, res.height, r.internalFormat, r.format, texType, filtering); - sunraysTemp = createFBO(res.width, res.height, r.internalFormat, r.format, texType, filtering); + sunrays = createFBO( + res.width, + res.height, + r.internalFormat, + r.format, + texType, + filtering + ); + sunraysTemp = createFBO( + res.width, + res.height, + r.internalFormat, + r.format, + texType, + filtering + ); } function createFBO(w, h, internalFormat, format, type, param) { - gl.activeTexture(gl.TEXTURE0); - let texture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null); + gl.activeTexture(gl.TEXTURE0); + let texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null); - let fbo = gl.createFramebuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); - gl.viewport(0, 0, w, h); - gl.clear(gl.COLOR_BUFFER_BIT); + let fbo = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + texture, + 0 + ); + gl.viewport(0, 0, w, h); + gl.clear(gl.COLOR_BUFFER_BIT); - let texelSizeX = 1.0 / w; - let texelSizeY = 1.0 / h; + let texelSizeX = 1.0 / w; + let texelSizeY = 1.0 / h; - return { - texture, - fbo, - width: w, - height: h, - texelSizeX, - texelSizeY, - attach(id) { - gl.activeTexture(gl.TEXTURE0 + id); - gl.bindTexture(gl.TEXTURE_2D, texture); - return id; - } - }; + return { + texture, + fbo, + width: w, + height: h, + texelSizeX, + texelSizeY, + attach(id) { + gl.activeTexture(gl.TEXTURE0 + id); + gl.bindTexture(gl.TEXTURE_2D, texture); + return id; + }, + }; } function createDoubleFBO(w, h, internalFormat, format, type, param) { - let fbo1 = createFBO(w, h, internalFormat, format, type, param); - let fbo2 = createFBO(w, h, internalFormat, format, type, param); + let fbo1 = createFBO(w, h, internalFormat, format, type, param); + let fbo2 = createFBO(w, h, internalFormat, format, type, param); - return { - width: w, - height: h, - texelSizeX: fbo1.texelSizeX, - texelSizeY: fbo1.texelSizeY, - get read() { - return fbo1; - }, - set read(value) { - fbo1 = value; - }, - get write() { - return fbo2; - }, - set write(value) { - fbo2 = value; - }, - swap() { - let temp = fbo1; - fbo1 = fbo2; - fbo2 = temp; - } - }; + return { + width: w, + height: h, + texelSizeX: fbo1.texelSizeX, + texelSizeY: fbo1.texelSizeY, + get read() { + return fbo1; + }, + set read(value) { + fbo1 = value; + }, + get write() { + return fbo2; + }, + set write(value) { + fbo2 = value; + }, + swap() { + let temp = fbo1; + fbo1 = fbo2; + fbo2 = temp; + }, + }; } function resizeFBO(target, w, h, internalFormat, format, type, param) { - let newFBO = createFBO(w, h, internalFormat, format, type, param); - copyProgram.bind(); - gl.uniform1i(copyProgram.uniforms.uTexture, target.attach(0)); - blit(newFBO); - return newFBO; + let newFBO = createFBO(w, h, internalFormat, format, type, param); + copyProgram.bind(); + gl.uniform1i(copyProgram.uniforms.uTexture, target.attach(0)); + blit(newFBO); + return newFBO; } function resizeDoubleFBO(target, w, h, internalFormat, format, type, param) { - if (target.width == w && target.height == h) - return target; - target.read = resizeFBO(target.read, w, h, internalFormat, format, type, param); - target.write = createFBO(w, h, internalFormat, format, type, param); - target.width = w; - target.height = h; - target.texelSizeX = 1.0 / w; - target.texelSizeY = 1.0 / h; - return target; + if (target.width == w && target.height == h) return target; + target.read = resizeFBO( + target.read, + w, + h, + internalFormat, + format, + type, + param + ); + target.write = createFBO(w, h, internalFormat, format, type, param); + target.width = w; + target.height = h; + target.texelSizeX = 1.0 / w; + target.texelSizeY = 1.0 / h; + return target; } function createTextureAsync(url) { - let texture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255])); + let texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGB, + 1, + 1, + 0, + gl.RGB, + gl.UNSIGNED_BYTE, + new Uint8Array([255, 255, 255]) + ); - let obj = { - texture, - width: 1, - height: 1, - attach(id) { - gl.activeTexture(gl.TEXTURE0 + id); - gl.bindTexture(gl.TEXTURE_2D, texture); - return id; - } - }; + let obj = { + texture, + width: 1, + height: 1, + attach(id) { + gl.activeTexture(gl.TEXTURE0 + id); + gl.bindTexture(gl.TEXTURE_2D, texture); + return id; + }, + }; - let image = new Image(); - image.onload = () => { - obj.width = image.width; - obj.height = image.height; - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); - }; - image.src = url; + let image = new Image(); + image.onload = () => { + obj.width = image.width; + obj.height = image.height; + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); + }; + image.src = url; - return obj; + return obj; } function updateKeywords() { - let displayKeywords = []; - if (config.SHADING) displayKeywords.push("SHADING"); - if (config.BLOOM) displayKeywords.push("BLOOM"); - if (config.SUNRAYS) displayKeywords.push("SUNRAYS"); - displayMaterial.setKeywords(displayKeywords); + let displayKeywords = []; + if (config.SHADING) displayKeywords.push("SHADING"); + if (config.BLOOM) displayKeywords.push("BLOOM"); + if (config.SUNRAYS) displayKeywords.push("SUNRAYS"); + displayMaterial.setKeywords(displayKeywords); } updateKeywords(); @@ -1058,472 +1252,542 @@ let colorUpdateTimer = 0.0; update(); function update() { - const dt = calcDeltaTime(); - if (resizeCanvas()) - initFramebuffers(); - updateColors(dt); - applyInputs(); - if (!config.PAUSED) - step(dt); - render(null); - requestAnimationFrame(update); + const dt = calcDeltaTime(); + if (resizeCanvas()) initFramebuffers(); + updateColors(dt); + applyInputs(); + if (!config.PAUSED) step(dt); + render(null); + requestAnimationFrame(update); } function calcDeltaTime() { - let now = Date.now(); - let dt = (now - lastUpdateTime) / 1000; - dt = Math.min(dt, 0.016666); - lastUpdateTime = now; - return dt; + let now = Date.now(); + let dt = (now - lastUpdateTime) / 1000; + dt = Math.min(dt, 0.016666); + lastUpdateTime = now; + return dt; } function resizeCanvas() { - let width = scaleByPixelRatio(canvas.clientWidth); - let height = scaleByPixelRatio(canvas.clientHeight); - if (canvas.width != width || canvas.height != height) { - canvas.width = width; - canvas.height = height; - return true; - } - return false; + let width = scaleByPixelRatio(canvas.clientWidth); + let height = scaleByPixelRatio(canvas.clientHeight); + if (canvas.width != width || canvas.height != height) { + canvas.width = width; + canvas.height = height; + return true; + } + return false; } function updateColors(dt) { - if (!config.COLORFUL) return; + if (!config.COLORFUL) return; - colorUpdateTimer += dt * config.COLOR_UPDATE_SPEED; - if (colorUpdateTimer >= 1) { - colorUpdateTimer = wrap(colorUpdateTimer, 0, 1); - pointers.forEach(p => { - p.color = generateColor(); - }); - } + colorUpdateTimer += dt * config.COLOR_UPDATE_SPEED; + if (colorUpdateTimer >= 1) { + colorUpdateTimer = wrap(colorUpdateTimer, 0, 1); + pointers.forEach((p) => { + p.color = generateColor(); + }); + } } function applyInputs() { - if (splatStack.length > 0) - multipleSplats(splatStack.pop()); + if (splatStack.length > 0) multipleSplats(splatStack.pop()); - pointers.forEach(p => { - if (p.moved) { - p.moved = false; - splatPointer(p); - } - }); + pointers.forEach((p) => { + if (p.moved) { + p.moved = false; + splatPointer(p); + } + }); } function step(dt) { - gl.disable(gl.BLEND); + gl.disable(gl.BLEND); - curlProgram.bind(); - gl.uniform2f(curlProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); - gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.read.attach(0)); - blit(curl); + curlProgram.bind(); + gl.uniform2f( + curlProgram.uniforms.texelSize, + velocity.texelSizeX, + velocity.texelSizeY + ); + gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.read.attach(0)); + blit(curl); - vorticityProgram.bind(); - gl.uniform2f(vorticityProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); - gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.read.attach(0)); - gl.uniform1i(vorticityProgram.uniforms.uCurl, curl.attach(1)); - gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL); - gl.uniform1f(vorticityProgram.uniforms.dt, dt); - blit(velocity.write); - velocity.swap(); + vorticityProgram.bind(); + gl.uniform2f( + vorticityProgram.uniforms.texelSize, + velocity.texelSizeX, + velocity.texelSizeY + ); + gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.read.attach(0)); + gl.uniform1i(vorticityProgram.uniforms.uCurl, curl.attach(1)); + gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL); + gl.uniform1f(vorticityProgram.uniforms.dt, dt); + blit(velocity.write); + velocity.swap(); - divergenceProgram.bind(); - gl.uniform2f(divergenceProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); - gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.read.attach(0)); - blit(divergence); + divergenceProgram.bind(); + gl.uniform2f( + divergenceProgram.uniforms.texelSize, + velocity.texelSizeX, + velocity.texelSizeY + ); + gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.read.attach(0)); + blit(divergence); - clearProgram.bind(); - gl.uniform1i(clearProgram.uniforms.uTexture, pressure.read.attach(0)); - gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE); - blit(pressure.write); - pressure.swap(); + clearProgram.bind(); + gl.uniform1i(clearProgram.uniforms.uTexture, pressure.read.attach(0)); + gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE); + blit(pressure.write); + pressure.swap(); - pressureProgram.bind(); - gl.uniform2f(pressureProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); - gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0)); - for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) { - gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1)); - blit(pressure.write); - pressure.swap(); - } + pressureProgram.bind(); + gl.uniform2f( + pressureProgram.uniforms.texelSize, + velocity.texelSizeX, + velocity.texelSizeY + ); + gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0)); + for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) { + gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1)); + blit(pressure.write); + pressure.swap(); + } - gradienSubtractProgram.bind(); - gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); - gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.read.attach(0)); - gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.read.attach(1)); - blit(velocity.write); - velocity.swap(); + gradienSubtractProgram.bind(); + gl.uniform2f( + gradienSubtractProgram.uniforms.texelSize, + velocity.texelSizeX, + velocity.texelSizeY + ); + gl.uniform1i( + gradienSubtractProgram.uniforms.uPressure, + pressure.read.attach(0) + ); + gl.uniform1i( + gradienSubtractProgram.uniforms.uVelocity, + velocity.read.attach(1) + ); + blit(velocity.write); + velocity.swap(); - advectionProgram.bind(); - gl.uniform2f(advectionProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY); - if (!ext.supportLinearFiltering) - gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, velocity.texelSizeX, velocity.texelSizeY); - let velocityId = velocity.read.attach(0); - gl.uniform1i(advectionProgram.uniforms.uVelocity, velocityId); - gl.uniform1i(advectionProgram.uniforms.uSource, velocityId); - gl.uniform1f(advectionProgram.uniforms.dt, dt); - gl.uniform1f(advectionProgram.uniforms.dissipation, config.VELOCITY_DISSIPATION); - blit(velocity.write); - velocity.swap(); + advectionProgram.bind(); + gl.uniform2f( + advectionProgram.uniforms.texelSize, + velocity.texelSizeX, + velocity.texelSizeY + ); + if (!ext.supportLinearFiltering) + gl.uniform2f( + advectionProgram.uniforms.dyeTexelSize, + velocity.texelSizeX, + velocity.texelSizeY + ); + let velocityId = velocity.read.attach(0); + gl.uniform1i(advectionProgram.uniforms.uVelocity, velocityId); + gl.uniform1i(advectionProgram.uniforms.uSource, velocityId); + gl.uniform1f(advectionProgram.uniforms.dt, dt); + gl.uniform1f( + advectionProgram.uniforms.dissipation, + config.VELOCITY_DISSIPATION + ); + blit(velocity.write); + velocity.swap(); - if (!ext.supportLinearFiltering) - gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, dye.texelSizeX, dye.texelSizeY); - gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.read.attach(0)); - gl.uniform1i(advectionProgram.uniforms.uSource, dye.read.attach(1)); - gl.uniform1f(advectionProgram.uniforms.dissipation, config.DENSITY_DISSIPATION); - blit(dye.write); - dye.swap(); + if (!ext.supportLinearFiltering) + gl.uniform2f( + advectionProgram.uniforms.dyeTexelSize, + dye.texelSizeX, + dye.texelSizeY + ); + gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.read.attach(0)); + gl.uniform1i(advectionProgram.uniforms.uSource, dye.read.attach(1)); + gl.uniform1f( + advectionProgram.uniforms.dissipation, + config.DENSITY_DISSIPATION + ); + blit(dye.write); + dye.swap(); } function render(target) { - if (config.BLOOM) - applyBloom(dye.read, bloom); - if (config.SUNRAYS) { - applySunrays(dye.read, dye.write, sunrays); - blur(sunrays, sunraysTemp, 1); - } + if (config.BLOOM) applyBloom(dye.read, bloom); + if (config.SUNRAYS) { + applySunrays(dye.read, dye.write, sunrays); + blur(sunrays, sunraysTemp, 1); + } - if (target == null || !config.TRANSPARENT) { - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); - gl.enable(gl.BLEND); - } - else { - gl.disable(gl.BLEND); - } + if (target == null || !config.TRANSPARENT) { + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.enable(gl.BLEND); + } else { + gl.disable(gl.BLEND); + } - if (!config.TRANSPARENT) - drawColor(target, normalizeColor(config.BACK_COLOR)); - if (target == null && config.TRANSPARENT) - drawCheckerboard(target); - drawDisplay(target); + if (!config.TRANSPARENT) drawColor(target, normalizeColor(config.BACK_COLOR)); + if (target == null && config.TRANSPARENT) drawCheckerboard(target); + drawDisplay(target); } function drawColor(target, color) { - colorProgram.bind(); - gl.uniform4f(colorProgram.uniforms.color, color.r, color.g, color.b, 1); - blit(target); + colorProgram.bind(); + gl.uniform4f(colorProgram.uniforms.color, color.r, color.g, color.b, 1); + blit(target); } function drawCheckerboard(target) { - checkerboardProgram.bind(); - gl.uniform1f(checkerboardProgram.uniforms.aspectRatio, canvas.width / canvas.height); - blit(target); + checkerboardProgram.bind(); + gl.uniform1f( + checkerboardProgram.uniforms.aspectRatio, + canvas.width / canvas.height + ); + blit(target); } function drawDisplay(target) { - let width = target == null ? gl.drawingBufferWidth : target.width; - let height = target == null ? gl.drawingBufferHeight : target.height; + let width = target == null ? gl.drawingBufferWidth : target.width; + let height = target == null ? gl.drawingBufferHeight : target.height; - displayMaterial.bind(); - if (config.SHADING) - gl.uniform2f(displayMaterial.uniforms.texelSize, 1.0 / width, 1.0 / height); - gl.uniform1i(displayMaterial.uniforms.uTexture, dye.read.attach(0)); - if (config.BLOOM) { - gl.uniform1i(displayMaterial.uniforms.uBloom, bloom.attach(1)); - gl.uniform1i(displayMaterial.uniforms.uDithering, ditheringTexture.attach(2)); - let scale = getTextureScale(ditheringTexture, width, height); - gl.uniform2f(displayMaterial.uniforms.ditherScale, scale.x, scale.y); - } - if (config.SUNRAYS) - gl.uniform1i(displayMaterial.uniforms.uSunrays, sunrays.attach(3)); - blit(target); + displayMaterial.bind(); + if (config.SHADING) + gl.uniform2f(displayMaterial.uniforms.texelSize, 1.0 / width, 1.0 / height); + gl.uniform1i(displayMaterial.uniforms.uTexture, dye.read.attach(0)); + if (config.BLOOM) { + gl.uniform1i(displayMaterial.uniforms.uBloom, bloom.attach(1)); + gl.uniform1i( + displayMaterial.uniforms.uDithering, + ditheringTexture.attach(2) + ); + let scale = getTextureScale(ditheringTexture, width, height); + gl.uniform2f(displayMaterial.uniforms.ditherScale, scale.x, scale.y); + } + if (config.SUNRAYS) + gl.uniform1i(displayMaterial.uniforms.uSunrays, sunrays.attach(3)); + blit(target); } function applyBloom(source, destination) { - if (bloomFramebuffers.length < 2) - return; + if (bloomFramebuffers.length < 2) return; - let last = destination; + let last = destination; - gl.disable(gl.BLEND); - bloomPrefilterProgram.bind(); - let knee = config.BLOOM_THRESHOLD * config.BLOOM_SOFT_KNEE + 0.0001; - let curve0 = config.BLOOM_THRESHOLD - knee; - let curve1 = knee * 2; - let curve2 = 0.25 / knee; - gl.uniform3f(bloomPrefilterProgram.uniforms.curve, curve0, curve1, curve2); - gl.uniform1f(bloomPrefilterProgram.uniforms.threshold, config.BLOOM_THRESHOLD); - gl.uniform1i(bloomPrefilterProgram.uniforms.uTexture, source.attach(0)); - blit(last); + gl.disable(gl.BLEND); + bloomPrefilterProgram.bind(); + let knee = config.BLOOM_THRESHOLD * config.BLOOM_SOFT_KNEE + 0.0001; + let curve0 = config.BLOOM_THRESHOLD - knee; + let curve1 = knee * 2; + let curve2 = 0.25 / knee; + gl.uniform3f(bloomPrefilterProgram.uniforms.curve, curve0, curve1, curve2); + gl.uniform1f( + bloomPrefilterProgram.uniforms.threshold, + config.BLOOM_THRESHOLD + ); + gl.uniform1i(bloomPrefilterProgram.uniforms.uTexture, source.attach(0)); + blit(last); - bloomBlurProgram.bind(); - for (let i = 0; i < bloomFramebuffers.length; i++) { - let dest = bloomFramebuffers[i]; - gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY); - gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0)); - blit(dest); - last = dest; - } + bloomBlurProgram.bind(); + for (let i = 0; i < bloomFramebuffers.length; i++) { + let dest = bloomFramebuffers[i]; + gl.uniform2f( + bloomBlurProgram.uniforms.texelSize, + last.texelSizeX, + last.texelSizeY + ); + gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0)); + blit(dest); + last = dest; + } - gl.blendFunc(gl.ONE, gl.ONE); - gl.enable(gl.BLEND); + gl.blendFunc(gl.ONE, gl.ONE); + gl.enable(gl.BLEND); - for (let i = bloomFramebuffers.length - 2; i >= 0; i--) { - let baseTex = bloomFramebuffers[i]; - gl.uniform2f(bloomBlurProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY); - gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0)); - gl.viewport(0, 0, baseTex.width, baseTex.height); - blit(baseTex); - last = baseTex; - } + for (let i = bloomFramebuffers.length - 2; i >= 0; i--) { + let baseTex = bloomFramebuffers[i]; + gl.uniform2f( + bloomBlurProgram.uniforms.texelSize, + last.texelSizeX, + last.texelSizeY + ); + gl.uniform1i(bloomBlurProgram.uniforms.uTexture, last.attach(0)); + gl.viewport(0, 0, baseTex.width, baseTex.height); + blit(baseTex); + last = baseTex; + } - gl.disable(gl.BLEND); - bloomFinalProgram.bind(); - gl.uniform2f(bloomFinalProgram.uniforms.texelSize, last.texelSizeX, last.texelSizeY); - gl.uniform1i(bloomFinalProgram.uniforms.uTexture, last.attach(0)); - gl.uniform1f(bloomFinalProgram.uniforms.intensity, config.BLOOM_INTENSITY); - blit(destination); + gl.disable(gl.BLEND); + bloomFinalProgram.bind(); + gl.uniform2f( + bloomFinalProgram.uniforms.texelSize, + last.texelSizeX, + last.texelSizeY + ); + gl.uniform1i(bloomFinalProgram.uniforms.uTexture, last.attach(0)); + gl.uniform1f(bloomFinalProgram.uniforms.intensity, config.BLOOM_INTENSITY); + blit(destination); } function applySunrays(source, mask, destination) { - gl.disable(gl.BLEND); - sunraysMaskProgram.bind(); - gl.uniform1i(sunraysMaskProgram.uniforms.uTexture, source.attach(0)); - blit(mask); + gl.disable(gl.BLEND); + sunraysMaskProgram.bind(); + gl.uniform1i(sunraysMaskProgram.uniforms.uTexture, source.attach(0)); + blit(mask); - sunraysProgram.bind(); - gl.uniform1f(sunraysProgram.uniforms.weight, config.SUNRAYS_WEIGHT); - gl.uniform1i(sunraysProgram.uniforms.uTexture, mask.attach(0)); - blit(destination); + sunraysProgram.bind(); + gl.uniform1f(sunraysProgram.uniforms.weight, config.SUNRAYS_WEIGHT); + gl.uniform1i(sunraysProgram.uniforms.uTexture, mask.attach(0)); + blit(destination); } function blur(target, temp, iterations) { - blurProgram.bind(); - for (let i = 0; i < iterations; i++) { - gl.uniform2f(blurProgram.uniforms.texelSize, target.texelSizeX, 0.0); - gl.uniform1i(blurProgram.uniforms.uTexture, target.attach(0)); - blit(temp); + blurProgram.bind(); + for (let i = 0; i < iterations; i++) { + gl.uniform2f(blurProgram.uniforms.texelSize, target.texelSizeX, 0.0); + gl.uniform1i(blurProgram.uniforms.uTexture, target.attach(0)); + blit(temp); - gl.uniform2f(blurProgram.uniforms.texelSize, 0.0, target.texelSizeY); - gl.uniform1i(blurProgram.uniforms.uTexture, temp.attach(0)); - blit(target); - } + gl.uniform2f(blurProgram.uniforms.texelSize, 0.0, target.texelSizeY); + gl.uniform1i(blurProgram.uniforms.uTexture, temp.attach(0)); + blit(target); + } } function splatPointer(pointer) { - let dx = pointer.deltaX * config.SPLAT_FORCE; - let dy = pointer.deltaY * config.SPLAT_FORCE; - splat(pointer.texcoordX, pointer.texcoordY, dx, dy, pointer.color); + let dx = pointer.deltaX * config.SPLAT_FORCE; + let dy = pointer.deltaY * config.SPLAT_FORCE; + splat(pointer.texcoordX, pointer.texcoordY, dx, dy, pointer.color); } function multipleSplats(amount) { - for (let i = 0; i < amount; i++) { - const color = generateColor(); - color.r *= 10.0; - color.g *= 10.0; - color.b *= 10.0; - const x = Math.random(); - const y = Math.random(); - const dx = 1000 * (Math.random() - 0.5); - const dy = 1000 * (Math.random() - 0.5); - splat(x, y, dx, dy, color); - } + for (let i = 0; i < amount; i++) { + const color = generateColor(); + color.r *= 10.0; + color.g *= 10.0; + color.b *= 10.0; + const x = Math.random(); + const y = Math.random(); + const dx = 1000 * (Math.random() - 0.5); + const dy = 1000 * (Math.random() - 0.5); + splat(x, y, dx, dy, color); + } } function splat(x, y, dx, dy, color) { - splatProgram.bind(); - gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0)); - gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height); - gl.uniform2f(splatProgram.uniforms.point, x, y); - gl.uniform3f(splatProgram.uniforms.color, dx, dy, 0.0); - gl.uniform1f(splatProgram.uniforms.radius, correctRadius(config.SPLAT_RADIUS / 100.0)); - blit(velocity.write); - velocity.swap(); + splatProgram.bind(); + gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0)); + gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height); + gl.uniform2f(splatProgram.uniforms.point, x, y); + gl.uniform3f(splatProgram.uniforms.color, dx, dy, 0.0); + gl.uniform1f( + splatProgram.uniforms.radius, + correctRadius(config.SPLAT_RADIUS / 100.0) + ); + blit(velocity.write); + velocity.swap(); - gl.uniform1i(splatProgram.uniforms.uTarget, dye.read.attach(0)); - gl.uniform3f(splatProgram.uniforms.color, color.r, color.g, color.b); - blit(dye.write); - dye.swap(); + gl.uniform1i(splatProgram.uniforms.uTarget, dye.read.attach(0)); + gl.uniform3f(splatProgram.uniforms.color, color.r, color.g, color.b); + blit(dye.write); + dye.swap(); } function correctRadius(radius) { - let aspectRatio = canvas.width / canvas.height; - if (aspectRatio > 1) - radius *= aspectRatio; - return radius; + let aspectRatio = canvas.width / canvas.height; + if (aspectRatio > 1) radius *= aspectRatio; + return radius; } -canvas.addEventListener('mousedown', e => { - let posX = scaleByPixelRatio(e.offsetX); - let posY = scaleByPixelRatio(e.offsetY); - let pointer = pointers.find(p => p.id == -1); - if (pointer == null) - pointer = new pointerPrototype(); - updatePointerDownData(pointer, -1, posX, posY); +canvas.addEventListener("mousedown", (e) => { + let posX = scaleByPixelRatio(e.offsetX); + let posY = scaleByPixelRatio(e.offsetY); + let pointer = pointers.find((p) => p.id == -1); + if (pointer == null) pointer = new pointerPrototype(); + updatePointerDownData(pointer, -1, posX, posY); }); setTimeout(() => { - canvas.addEventListener('mousemove', e => { - let posX = scaleByPixelRatio(e.offsetX); - let posY = scaleByPixelRatio(e.offsetY); - updatePointerMoveData(pointers[0], posX, posY); - }); + canvas.addEventListener("mousemove", (e) => { + let posX = scaleByPixelRatio(e.offsetX); + let posY = scaleByPixelRatio(e.offsetY); + updatePointerMoveData(pointers[0], posX, posY); + }); }, 500); -window.addEventListener('mouseup', () => { - updatePointerUpData(pointers[0]); +window.addEventListener("mouseup", () => { + updatePointerUpData(pointers[0]); }); -canvas.addEventListener('touchstart', e => { - e.preventDefault(); - const touches = e.targetTouches; - while (touches.length >= pointers.length) - pointers.push(new pointerPrototype()); - for (let i = 0; i < touches.length; i++) { - let posX = scaleByPixelRatio(touches[i].pageX); - let posY = scaleByPixelRatio(touches[i].pageY); - updatePointerDownData(pointers[i + 1], touches[i].identifier, posX, posY); - } +canvas.addEventListener("touchstart", (e) => { + e.preventDefault(); + const touches = e.targetTouches; + while (touches.length >= pointers.length) + pointers.push(new pointerPrototype()); + for (let i = 0; i < touches.length; i++) { + let posX = scaleByPixelRatio(touches[i].pageX); + let posY = scaleByPixelRatio(touches[i].pageY); + updatePointerDownData(pointers[i + 1], touches[i].identifier, posX, posY); + } }); -canvas.addEventListener('touchmove', e => { - e.preventDefault(); - const touches = e.targetTouches; - for (let i = 0; i < touches.length; i++) { - let pointer = pointers[i + 1]; - if (!pointer.down) continue; - let posX = scaleByPixelRatio(touches[i].pageX); - let posY = scaleByPixelRatio(touches[i].pageY); - updatePointerMoveData(pointer, posX, posY); - } -}, false); +canvas.addEventListener( + "touchmove", + (e) => { + e.preventDefault(); + const touches = e.targetTouches; + for (let i = 0; i < touches.length; i++) { + let pointer = pointers[i + 1]; + if (!pointer.down) continue; + let posX = scaleByPixelRatio(touches[i].pageX); + let posY = scaleByPixelRatio(touches[i].pageY); + updatePointerMoveData(pointer, posX, posY); + } + }, + false +); -window.addEventListener('touchend', e => { - const touches = e.changedTouches; - for (let i = 0; i < touches.length; i++) { - let pointer = pointers.find(p => p.id == touches[i].identifier); - if (pointer == null) continue; - updatePointerUpData(pointer); - } +window.addEventListener("touchend", (e) => { + const touches = e.changedTouches; + for (let i = 0; i < touches.length; i++) { + let pointer = pointers.find((p) => p.id == touches[i].identifier); + if (pointer == null) continue; + updatePointerUpData(pointer); + } }); -window.addEventListener('keydown', e => { - if (e.code === 'KeyP') - config.PAUSED = !config.PAUSED; - if (e.key === ' ') - splatStack.push(parseInt(Math.random() * 20) + 5); +window.addEventListener("keydown", (e) => { + if (e.code === "KeyP") config.PAUSED = !config.PAUSED; + if (e.key === " ") splatStack.push(parseInt(Math.random() * 20) + 5); }); function updatePointerDownData(pointer, id, posX, posY) { - pointer.id = id; - pointer.down = true; - pointer.moved = false; - pointer.texcoordX = posX / canvas.width; - pointer.texcoordY = 1.0 - posY / canvas.height; - pointer.prevTexcoordX = pointer.texcoordX; - pointer.prevTexcoordY = pointer.texcoordY; - pointer.deltaX = 0; - pointer.deltaY = 0; - pointer.color = generateColor(); + pointer.id = id; + pointer.down = true; + pointer.moved = false; + pointer.texcoordX = posX / canvas.width; + pointer.texcoordY = 1.0 - posY / canvas.height; + pointer.prevTexcoordX = pointer.texcoordX; + pointer.prevTexcoordY = pointer.texcoordY; + pointer.deltaX = 0; + pointer.deltaY = 0; + pointer.color = generateColor(); } function updatePointerMoveData(pointer, posX, posY) { - pointer.prevTexcoordX = pointer.texcoordX; - pointer.prevTexcoordY = pointer.texcoordY; - pointer.texcoordX = posX / canvas.width; - pointer.texcoordY = 1.0 - posY / canvas.height; - pointer.deltaX = correctDeltaX(pointer.texcoordX - pointer.prevTexcoordX); - pointer.deltaY = correctDeltaY(pointer.texcoordY - pointer.prevTexcoordY); - pointer.moved = Math.abs(pointer.deltaX) > 0 || Math.abs(pointer.deltaY) > 0; + pointer.prevTexcoordX = pointer.texcoordX; + pointer.prevTexcoordY = pointer.texcoordY; + pointer.texcoordX = posX / canvas.width; + pointer.texcoordY = 1.0 - posY / canvas.height; + pointer.deltaX = correctDeltaX(pointer.texcoordX - pointer.prevTexcoordX); + pointer.deltaY = correctDeltaY(pointer.texcoordY - pointer.prevTexcoordY); + pointer.moved = Math.abs(pointer.deltaX) > 0 || Math.abs(pointer.deltaY) > 0; } function updatePointerUpData(pointer) { - pointer.down = false; + pointer.down = false; } function correctDeltaX(delta) { - let aspectRatio = canvas.width / canvas.height; - if (aspectRatio < 1) delta *= aspectRatio; - return delta; + let aspectRatio = canvas.width / canvas.height; + if (aspectRatio < 1) delta *= aspectRatio; + return delta; } function correctDeltaY(delta) { - let aspectRatio = canvas.width / canvas.height; - if (aspectRatio > 1) delta /= aspectRatio; - return delta; + let aspectRatio = canvas.width / canvas.height; + if (aspectRatio > 1) delta /= aspectRatio; + return delta; } function generateColor() { - let c = HSVtoRGB(Math.random(), 1.0, 1.0); - c.r *= 0.15; - c.g *= 0.15; - c.b *= 0.15; - return c; + let c = HSVtoRGB(Math.random(), 1.0, 1.0); + c.r *= 0.15; + c.g *= 0.15; + c.b *= 0.15; + return c; } function HSVtoRGB(h, s, v) { - let r, g, b, i, f, p, q, t; - i = Math.floor(h * 6); - f = h * 6 - i; - p = v * (1 - s); - q = v * (1 - f * s); - t = v * (1 - (1 - f) * s); + let r, g, b, i, f, p, q, t; + i = Math.floor(h * 6); + f = h * 6 - i; + p = v * (1 - s); + q = v * (1 - f * s); + t = v * (1 - (1 - f) * s); - switch (i % 6) { - case 0: r = v, g = t, b = p; break; - case 1: r = q, g = v, b = p; break; - case 2: r = p, g = v, b = t; break; - case 3: r = p, g = q, b = v; break; - case 4: r = t, g = p, b = v; break; - case 5: r = v, g = p, b = q; break; - } + switch (i % 6) { + case 0: + (r = v), (g = t), (b = p); + break; + case 1: + (r = q), (g = v), (b = p); + break; + case 2: + (r = p), (g = v), (b = t); + break; + case 3: + (r = p), (g = q), (b = v); + break; + case 4: + (r = t), (g = p), (b = v); + break; + case 5: + (r = v), (g = p), (b = q); + break; + } - return { - r, - g, - b - }; + return { + r, + g, + b, + }; } function normalizeColor(input) { - let output = { - r: input.r / 255, - g: input.g / 255, - b: input.b / 255 - }; - return output; + let output = { + r: input.r / 255, + g: input.g / 255, + b: input.b / 255, + }; + return output; } function wrap(value, min, max) { - let range = max - min; - if (range == 0) return min; - return (value - min) % range + min; + let range = max - min; + if (range == 0) return min; + return ((value - min) % range) + min; } function getResolution(resolution) { - let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight; - if (aspectRatio < 1) - aspectRatio = 1.0 / aspectRatio; + let aspectRatio = gl.drawingBufferWidth / gl.drawingBufferHeight; + if (aspectRatio < 1) aspectRatio = 1.0 / aspectRatio; - let min = Math.round(resolution); - let max = Math.round(resolution * aspectRatio); + let min = Math.round(resolution); + let max = Math.round(resolution * aspectRatio); - if (gl.drawingBufferWidth > gl.drawingBufferHeight) - return { width: max, height: min }; - else - return { width: min, height: max }; + if (gl.drawingBufferWidth > gl.drawingBufferHeight) + return { width: max, height: min }; + else return { width: min, height: max }; } function getTextureScale(texture, width, height) { - return { - x: width / texture.width, - y: height / texture.height - }; + return { + x: width / texture.width, + y: height / texture.height, + }; } function scaleByPixelRatio(input) { - let pixelRatio = window.devicePixelRatio || 1; - return Math.floor(input * pixelRatio); + let pixelRatio = window.devicePixelRatio || 1; + return Math.floor(input * pixelRatio); } function hashCode(s) { - if (s.length == 0) return 0; - let hash = 0; - for (let i = 0; i < s.length; i++) { - hash = (hash << 5) - hash + s.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } - return hash; -}; \ No newline at end of file + if (s.length == 0) return 0; + let hash = 0; + for (let i = 0; i < s.length; i++) { + hash = (hash << 5) - hash + s.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + return hash; +} diff --git a/src/app.js b/src/app.js index 992e15b..a8b2129 100644 --- a/src/app.js +++ b/src/app.js @@ -1,98 +1,135 @@ -import { event_listener_array, calc_delta_time } from './utils'; +import { addEventListeners, calcDeltaTime } from "./utils"; -export function perspective_3d(class_name) { - const elements = document.body.getElementsByClassName(class_name); +/** + * Adds 3D perspective rotation effect to elements with the specified class name. + * The rotation is controlled by mouse/touch movement and uses spring physics for smooth animation. + * @param {string} className - The class name of elements to apply the 3D effect to + */ +export const add3DRotationEffect = (className) => { + const elements = document.body.getElementsByClassName(className); - let velocity_x = 0; - let velocity_y = 0; - let rotation_x = 0; - let rotation_y = 0; - let target_rotation_x = 0; - let target_rotation_y = 0; + const state = { + velocity: { x: 0, y: 0 }, + rotation: { x: 0, y: 0 }, + targetRotation: { x: 0, y: 0 }, + }; - (function update() { - let dt = calc_delta_time(); - const ss = .08; + /** + * Calculates spring physics for smooth animation + * @param {number} position - Current position + * @param {number} target - Target position + * @param {number} velocity - Current velocity + * @param {number} omega - Angular frequency + * @param {number} dt - Delta time + * @returns {number} New velocity + */ + const spring = (position, target, velocity, omega, dt) => { + const n1 = velocity - (position - target) * (omega ** 2 * dt); + const n2 = 1 + omega * dt; + return n1 / n2 ** 2; + }; - velocity_x = spring(rotation_x, target_rotation_x, velocity_x, 10, dt); - velocity_y = spring(rotation_y, target_rotation_y, velocity_y, 10, dt); - rotation_x += velocity_x * dt * ss; - rotation_y += velocity_y * dt * ss; + /** + * Clamps a value between min and max + * @param {number} value - Value to clamp + * @param {number} min - Minimum value + * @param {number} max - Maximum value + * @returns {number} Clamped value + */ + const clamp = (value, min, max) => Math.min(Math.max(value, min), max); - const style = `perspective(700px) rotateX(${rotation_y}rad) rotateY(${rotation_x}rad)`; + /** + * Updates rotation values and applies transform styles in animation frame + */ + const updateRotation = () => { + const dt = calcDeltaTime(); + const springConstant = 0.08; - for (const el of elements) { - el.style.transform = style; - } + state.velocity.x = spring( + state.rotation.x, + state.targetRotation.x, + state.velocity.x, + 10, + dt + ); + state.velocity.y = spring( + state.rotation.y, + state.targetRotation.y, + state.velocity.y, + 10, + dt + ); - requestAnimationFrame(update); - })(); + state.rotation.x += state.velocity.x * dt * springConstant; + state.rotation.y += state.velocity.y * dt * springConstant; - event_listener_array(window, ["mousemove", "touchmove"], (e) => { - if (e.changedTouches && e.changedTouches[0]) { - e = e.changedTouches[0]; - } + const style = `perspective(700px) rotateX(${state.rotation.y}rad) rotateY(${state.rotation.x}rad)`; + Array.from(elements).forEach((el) => (el.style.transform = style)); - target_rotation_x = (e.clientX / window.innerWidth) * 2 - 1; - target_rotation_y = -(e.clientY / window.innerHeight) * 2 + 1; + requestAnimationFrame(updateRotation); + }; - target_rotation_x = clamp(target_rotation_x, -0.5, 0.5); - target_rotation_y = clamp(target_rotation_y, -0.5, 0.5); - }, false); + /** + * Handles mouse/touch movement to update target rotation + * @param {(MouseEvent|TouchEvent)} e - Mouse or touch event + */ + const handleMovement = (e) => { + const event = e.changedTouches?.[0] || e; - function spring(position, target, velocity, omega, dt) { - let n1 = velocity - (position - target) * (Math.pow(omega, 2) * dt); - let n2 = 1 + omega * dt; - return n1 / Math.pow(n2, 2); - } - function clamp(value, min, max) { - return Math.min(Math.max(value, min), max); - } -} + state.targetRotation.x = (event.clientX / window.innerWidth) * 2 - 1; + state.targetRotation.y = -(event.clientY / window.innerHeight) * 2 + 1; -(function pick_greeting() { - const hours = new Date().getHours(); - const greeing_el = document.querySelector(".greeting"); + state.targetRotation.x = clamp(state.targetRotation.x, -0.5, 0.5); + state.targetRotation.y = clamp(state.targetRotation.y, -0.5, 0.5); + }; - if (hours < 6) { - const data = "Good night"; - greeing_el.textContent = data; - greeing_el.innerText = data; - } else if (hours >= 6 && hours < 12) { - const data = "Good morning"; - greeing_el.textContent = data; - greeing_el.innerText = data; - } else if (hours >= 12 && hours < 16) { - const data = "Good afternoon"; - greeing_el.textContent = data; - greeing_el.innerText = data; - } else if (hours >= 16 && hours <= 23) { - const data = "Good evening"; - greeing_el.textContent = data; - greeing_el.innerText = data; - } else { - const data = "Hello"; - greeing_el.textContent = data; - greeing_el.innerText = data; - } + addEventListeners(window, ["mousemove", "touchmove"], handleMovement, false); - setTimeout(pick_greeting, 6e4); -})(); + updateRotation(); +}; -(function render_time() { - const time = new Date(); - let h = time.getHours(); +/** + * Updates greeting text based on time of day. + * Updates every minute. + */ +export const updateGreeting = () => { + const hours = new Date().getHours(); + const greetingEl = document.querySelector(".greeting"); + if (!greetingEl) return; - h = h.toString().padStart(2, 0); - let m = time.getMinutes().toString().padStart(2, 0); - let s = time.getSeconds().toString().padStart(2, 0); + /** + * Gets appropriate greeting based on hour of day + * @param {number} hours - Hour in 24-hour format + * @returns {string} Greeting text + */ + const getGreeting = (hours) => { + if (hours < 6) return "Good night"; + if (hours < 12) return "Good morning"; + if (hours < 16) return "Good afternoon"; + if (hours <= 23) return "Good evening"; + return "Hello"; + }; - const clock_el = document.querySelector('.clock'); - if (!clock_el) return; + const greeting = getGreeting(hours); + greetingEl.textContent = greeting; - const formatted_date = `${h}:${m}:${s}`; - clock_el.textContent = formatted_date; - clock_el.innerText = formatted_date; + setTimeout(updateGreeting, 60_000); // 60 seconds +}; - setTimeout(render_time, 1e3); -})(); \ No newline at end of file +/** + * Updates clock display with current time in HH:MM:SS format. + * Updates every second. + */ +export const updateTime = () => { + const clockEl = document.querySelector(".clock"); + if (!clockEl) return; + + const time = new Date(); + const formatted_date = [time.getHours(), time.getMinutes(), time.getSeconds()] + .map((num) => num.toString().padStart(2, "0")) + .join(":"); + + clockEl.textContent = formatted_date; + + setTimeout(updateTime, 1000); +}; diff --git a/src/main.js b/src/main.js index 8f13220..f070ad7 100644 --- a/src/main.js +++ b/src/main.js @@ -1,18 +1,17 @@ -// CSS Import -import './css/style.css'; -import './css/font.css'; -import './css/animation.css'; -import './css/button.css'; +import "./css/style.css"; +import "./css/font.css"; +import "./css/animation.css"; +import "./css/button.css"; -// JS Import -import './app'; +import "./app"; +import { add3DRotationEffect, updateGreeting, updateTime } from "./app"; +import { importJsAsModule } from "./utils"; -import { perspective_3d } from './app'; -import { import_js_as_module } from './utils'; +const initialize = async () => { + await importJsAsModule("/fluid.js"); + add3DRotationEffect("perspective"); + updateGreeting(); + updateTime(); +}; -window.onload = start; - -async function start() { - await import_js_as_module('/fluid.js'); - perspective_3d('perspective'); -} \ No newline at end of file +window.addEventListener("load", initialize); diff --git a/src/utils.js b/src/utils.js index 4c18e2b..4823616 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,31 +1,57 @@ +/** Timestamp of the last update used for delta time calculation */ export let last_update = Date.now(); -export function calc_delta_time() { - let now = Date.now(); - let dt = (now - last_update) / 1e3; - dt = Math.min(dt, 0.016); - last_update = now; - return dt; +/** + * Calculates the time elapsed since the last update, capped at 60fps + * @returns {number} Delta time in seconds + */ +export function calcDeltaTime() { + const now = Date.now(); + const MAX_DELTA = 0.016; // 60fps cap + let dt = (now - last_update) / 1000; + dt = Math.min(dt, MAX_DELTA); + last_update = now; + return dt; } -export function event_listener_array(whos, event_names, callback, options = null) { - if (!Array.isArray(whos)) { - whos = [whos]; - } - for (const name of event_names) { - for (const who of whos) { - who.addEventListener(name, callback, options); - } - } +/** + * Adds event listeners to one or more elements for multiple event types + * @param {HTMLElement|HTMLElement[]} elements - Single element or array of elements + * @param {string[]} eventNames - Array of event names to listen for + * @param {Function} callback - Event handler function + * @param {(boolean|AddEventListenerOptions|null)} [options=null] - Event listener options + */ +export function addEventListeners( + elements, + eventNames, + callback, + options = null +) { + // Convert single element to array if needed + const elementArray = Array.isArray(elements) ? elements : [elements]; + + // Add event listeners to each element for each event name + eventNames.forEach((eventName) => { + elementArray.forEach((element) => { + element.addEventListener(eventName, callback, options); + }); + }); } -export async function import_js_as_module(url) { - return new Promise((resole, reject) => { - const body = document.getElementsByTagName('body')[0]; - const script = document.createElement('script'); - script.type = 'module'; - script.src = url; - script.onload = resole; - body.appendChild(script); - }); -} \ No newline at end of file +/** + * Imports a JavaScript file as an ES module by injecting a script tag + * @param {string} url - URL of the JavaScript file to import + * @returns {Promise} Resolves when the script has loaded + */ +export const importJsAsModule = async (url) => { + return new Promise((resolve, reject) => { + const body = document.querySelector("body"); + const script = document.createElement("script"); + + script.type = "module"; + script.src = url; + script.onload = resolve; + + body.appendChild(script); + }); +};