From 91deea34d6ec6c248e6e3fc5fc4c000e04dcffd4 Mon Sep 17 00:00:00 2001 From: Rezmason Date: Wed, 20 Oct 2021 03:25:04 -0700 Subject: [PATCH] Ran all the JS through prettier. --- index.html | 73 ++++----- js/bloomPass.js | 194 ++++++++++------------ js/config.js | 357 ++++++++++++++++++++--------------------- js/imagePass.js | 45 +++--- js/main.js | 98 +++++------ js/palettePass.js | 126 +++++++-------- js/renderer.js | 307 +++++++++++++++++------------------ js/resurrectionPass.js | 50 +++--- js/stripePass.js | 86 +++++----- js/utils.js | 304 +++++++++++++++++------------------ 10 files changed, 776 insertions(+), 864 deletions(-) diff --git a/index.html b/index.html index 5ead984..6735f54 100644 --- a/index.html +++ b/index.html @@ -1,42 +1,39 @@ - - Matrix digital rain - - - - - - - - - - - + For more information, please visit: https://github.com/Rezmason/matrix + --> + + + + + diff --git a/js/bloomPass.js b/js/bloomPass.js index 2ece3b2..323df2a 100644 --- a/js/bloomPass.js +++ b/js/bloomPass.js @@ -1,127 +1,103 @@ -import { - loadText, - extractEntries, - makePassFBO, - makePyramid, - resizePyramid, - makePass -} from "./utils.js"; +import { loadText, extractEntries, makePassFBO, makePyramid, resizePyramid, makePass } from "./utils.js"; // The bloom pass is basically an added high-pass blur. const pyramidHeight = 5; const levelStrengths = Array(pyramidHeight) - .fill() - .map((_, index) => - Math.pow(index / (pyramidHeight * 2) + 0.5, 1 / 3).toPrecision(5) - ) - .reverse(); + .fill() + .map((_, index) => Math.pow(index / (pyramidHeight * 2) + 0.5, 1 / 3).toPrecision(5)) + .reverse(); export default (regl, config, inputs) => { + const enabled = config.bloomSize > 0 && config.bloomStrength > 0; - const enabled = config.bloomSize > 0 && config.bloomStrength > 0; + if (!enabled) { + return makePass({ + primary: inputs.primary, + bloom: makePassFBO(regl), + }); + } - if (!enabled) { - return makePass( - { - primary: inputs.primary, - bloom: makePassFBO(regl) - } - ); - } + const uniforms = extractEntries(config, ["bloomStrength", "highPassThreshold"]); - const uniforms = extractEntries(config, [ - "bloomStrength", - "highPassThreshold" - ]); + const highPassPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat); + const hBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat); + const vBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat); + const output = makePassFBO(regl, config.useHalfFloat); - const highPassPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat); - const hBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat); - const vBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat); - const output = makePassFBO(regl, config.useHalfFloat); + const highPassFrag = loadText("../shaders/highPass.frag"); - const highPassFrag = loadText("../shaders/highPass.frag"); + // The high pass restricts the blur to bright things in our input texture. + const highPass = regl({ + frag: regl.prop("frag"), + uniforms: { + ...uniforms, + tex: regl.prop("tex"), + }, + framebuffer: regl.prop("fbo"), + }); - // The high pass restricts the blur to bright things in our input texture. - const highPass = regl({ - frag: regl.prop("frag"), - uniforms: { - ...uniforms, - tex: regl.prop("tex") - }, - framebuffer: regl.prop("fbo") - }); + // A 2D gaussian blur is just a 1D blur done horizontally, then done vertically. + // The FBO pyramid's levels represent separate levels of detail; + // by blurring them all, this 3x1 blur approximates a more complex gaussian. - // A 2D gaussian blur is just a 1D blur done horizontally, then done vertically. - // The FBO pyramid's levels represent separate levels of detail; - // by blurring them all, this 3x1 blur approximates a more complex gaussian. + const blurFrag = loadText("../shaders/blur.frag"); + const blur = regl({ + frag: regl.prop("frag"), + uniforms: { + ...uniforms, + tex: regl.prop("tex"), + direction: regl.prop("direction"), + height: regl.context("viewportWidth"), + width: regl.context("viewportHeight"), + }, + framebuffer: regl.prop("fbo"), + }); - const blurFrag = loadText("../shaders/blur.frag"); - const blur = regl({ - frag: regl.prop("frag"), - uniforms: { - ...uniforms, - tex: regl.prop("tex"), - direction: regl.prop("direction"), - height: regl.context("viewportWidth"), - width: regl.context("viewportHeight") - }, - framebuffer: regl.prop("fbo") - }); + // The pyramid of textures gets flattened onto the source texture. + const flattenPyramid = regl({ + frag: ` + precision mediump float; + varying vec2 vUV; + ${vBlurPyramid.map((_, index) => `uniform sampler2D pyr_${index};`).join("\n")} + uniform float bloomStrength; + void main() { + vec4 total = vec4(0.); + ${vBlurPyramid.map((_, index) => `total += texture2D(pyr_${index}, vUV) * ${levelStrengths[index]};`).join("\n")} + gl_FragColor = total * bloomStrength; + } + `, + uniforms: { + ...uniforms, + ...Object.fromEntries(vBlurPyramid.map((fbo, index) => [`pyr_${index}`, fbo])), + }, + framebuffer: output, + }); - // The pyramid of textures gets flattened onto the source texture. - const flattenPyramid = regl({ - frag: ` - precision mediump float; - varying vec2 vUV; - ${vBlurPyramid - .map((_, index) => `uniform sampler2D pyr_${index};`) - .join("\n")} - uniform float bloomStrength; - void main() { - vec4 total = vec4(0.); - ${vBlurPyramid - .map( - (_, index) => - `total += texture2D(pyr_${index}, vUV) * ${levelStrengths[index]};` - ) - .join("\n")} - gl_FragColor = total * bloomStrength; - } - `, - uniforms: { - ...uniforms, - ...Object.fromEntries( - vBlurPyramid.map((fbo, index) => [`pyr_${index}`, fbo]) - ) - }, - framebuffer: output - }); + return makePass( + { + primary: inputs.primary, + bloom: output, + }, + () => { + for (let i = 0; i < pyramidHeight; i++) { + const highPassFBO = highPassPyramid[i]; + const hBlurFBO = hBlurPyramid[i]; + const vBlurFBO = vBlurPyramid[i]; + highPass({ fbo: highPassFBO, frag: highPassFrag.text(), tex: inputs.primary }); + blur({ fbo: hBlurFBO, frag: blurFrag.text(), tex: highPassFBO, direction: [1, 0] }); + blur({ fbo: vBlurFBO, frag: blurFrag.text(), tex: hBlurFBO, direction: [0, 1] }); + } - return makePass( - { - primary: inputs.primary, - bloom: output - }, - () => { - for (let i = 0; i < pyramidHeight; i++) { - const highPassFBO = highPassPyramid[i]; - const hBlurFBO = hBlurPyramid[i]; - const vBlurFBO = vBlurPyramid[i]; - highPass({ fbo: highPassFBO, frag: highPassFrag.text(), tex: inputs.primary }); - blur({ fbo: hBlurFBO, frag: blurFrag.text(), tex: highPassFBO, direction: [1, 0] }); - blur({ fbo: vBlurFBO, frag: blurFrag.text(), tex: hBlurFBO, direction: [0, 1] }); - } - - flattenPyramid(); - }, - (w, h) => { - // The blur pyramids can be lower resolution than the screen. - resizePyramid(highPassPyramid, w, h, config.bloomSize); - resizePyramid(hBlurPyramid, w, h, config.bloomSize); - resizePyramid(vBlurPyramid, w, h, config.bloomSize); - output.resize(w, h); - }, - [highPassFrag.laoded, blurFrag.loaded] - ); + flattenPyramid(); + }, + (w, h) => { + // The blur pyramids can be lower resolution than the screen. + resizePyramid(highPassPyramid, w, h, config.bloomSize); + resizePyramid(hBlurPyramid, w, h, config.bloomSize); + resizePyramid(vBlurPyramid, w, h, config.bloomSize); + output.resize(w, h); + }, + [highPassFrag.laoded, blurFrag.loaded] + ); }; diff --git a/js/config.js b/js/config.js index 5c0156c..d646ff1 100644 --- a/js/config.js +++ b/js/config.js @@ -1,204 +1,197 @@ const fonts = { - coptic: { - glyphTexURL: "coptic_msdf.png", - glyphSequenceLength: 32, - glyphTextureColumns: 8 - }, - gothic: { - glyphTexURL: "gothic_msdf.png", - glyphSequenceLength: 27, - glyphTextureColumns: 8 - }, - matrixcode: { - glyphTexURL: "matrixcode_msdf.png", - glyphSequenceLength: 57, - glyphTextureColumns: 8 - } + coptic: { + glyphTexURL: "coptic_msdf.png", + glyphSequenceLength: 32, + glyphTextureColumns: 8, + }, + gothic: { + glyphTexURL: "gothic_msdf.png", + glyphSequenceLength: 27, + glyphTextureColumns: 8, + }, + matrixcode: { + glyphTexURL: "matrixcode_msdf.png", + glyphSequenceLength: 57, + glyphTextureColumns: 8, + }, }; const defaults = { - backgroundColor: [0, 0, 0], - volumetric: false, - resurrectingCodeRatio: 0, - animationSpeed: 1, - forwardSpeed: 0.25, - bloomStrength: 1, - bloomSize: 0.6, - highPassThreshold: 0.1, - cycleSpeed: 1, - cycleStyleName: "cycleFasterWhenDimmed", - cursorEffectThreshold: 1, - brightnessOffset: 0.0, - brightnessMultiplier: 1.0, - brightnessMix: 1.0, - brightnessMinimum: 0, - fallSpeed: 1, - glyphEdgeCrop: 0.0, - glyphHeightToWidth: 1, - hasSun: false, - hasThunder: false, - isPolar: false, - rippleTypeName: null, - rippleThickness: 0.2, - rippleScale: 30, - rippleSpeed: 0.2, - numColumns: 80, - density: 1, - paletteEntries: [ - { hsl: [0.3, 0.9, 0.0], at: 0.0 }, - { hsl: [0.3, 0.9, 0.2], at: 0.2 }, - { hsl: [0.3, 0.9, 0.7], at: 0.7 }, - { hsl: [0.3, 0.9, 0.8], at: 0.8 } - ], - raindropLength: 1, - slant: 0, - resolution: 1, - useHalfFloat: false, + backgroundColor: [0, 0, 0], + volumetric: false, + resurrectingCodeRatio: 0, + animationSpeed: 1, + forwardSpeed: 0.25, + bloomStrength: 1, + bloomSize: 0.6, + highPassThreshold: 0.1, + cycleSpeed: 1, + cycleStyleName: "cycleFasterWhenDimmed", + cursorEffectThreshold: 1, + brightnessOffset: 0.0, + brightnessMultiplier: 1.0, + brightnessMix: 1.0, + brightnessMinimum: 0, + fallSpeed: 1, + glyphEdgeCrop: 0.0, + glyphHeightToWidth: 1, + hasSun: false, + hasThunder: false, + isPolar: false, + rippleTypeName: null, + rippleThickness: 0.2, + rippleScale: 30, + rippleSpeed: 0.2, + numColumns: 80, + density: 1, + paletteEntries: [ + { hsl: [0.3, 0.9, 0.0], at: 0.0 }, + { hsl: [0.3, 0.9, 0.2], at: 0.2 }, + { hsl: [0.3, 0.9, 0.7], at: 0.7 }, + { hsl: [0.3, 0.9, 0.8], at: 0.8 }, + ], + raindropLength: 1, + slant: 0, + resolution: 1, + useHalfFloat: false, }; const versions = { - classic: { - ...defaults, - ...fonts.matrixcode - }, - operator: { - ...defaults, - ...fonts.matrixcode, - bloomStrength: 0.75, - highPassThreshold: 0.0, - cycleSpeed: 0.05, - cycleStyleName: "cycleRandomly", - cursorEffectThreshold: 0.64, - brightnessOffset: 0.25, - brightnessMultiplier: 0.0, - brightnessMinimum: -1.0, - fallSpeed: 0.65, - glyphEdgeCrop: 0.15, - glyphHeightToWidth: 1.35, - rippleTypeName: "box", - numColumns: 108, - paletteEntries: [ - { hsl: [0.4, 0.8, 0.0], at: 0.0 }, - { hsl: [0.4, 0.8, 0.5], at: 0.5 }, - { hsl: [0.4, 0.8, 1.0], at: 1.0 } - ], - raindropLength: 1.5 - }, - nightmare: { - ...defaults, - ...fonts.gothic, - highPassThreshold: 0.7, - brightnessMix: 0.75, - fallSpeed: 2.0, - hasThunder: true, - numColumns: 60, - paletteEntries: [ - { hsl: [0.0, 1.0, 0.0], at: 0.0 }, - { hsl: [0.0, 1.0, 0.2], at: 0.2 }, - { hsl: [0.0, 1.0, 0.4], at: 0.4 }, - { hsl: [0.1, 1.0, 0.7], at: 0.7 }, - { hsl: [0.2, 1.0, 1.0], at: 1.0 } - ], - raindropLength: 0.6, - slant: (22.5 * Math.PI) / 180 - }, - paradise: { - ...defaults, - ...fonts.coptic, - bloomStrength: 1.75, - highPassThreshold: 0, - cycleSpeed: 0.1, - brightnessMix: 0.05, - fallSpeed: 0.08, - hasSun: true, - isPolar: true, - rippleTypeName: "circle", - rippleSpeed: 0.1, - numColumns: 30, - paletteEntries: [ - { hsl: [0.0, 0.0, 0.0], at: 0.0 }, - { hsl: [0.0, 0.8, 0.3], at: 0.3 }, - { hsl: [0.1, 0.8, 0.5], at: 0.5 }, - { hsl: [0.1, 1.0, 0.6], at: 0.6 }, - { hsl: [0.1, 1.0, 0.9], at: 0.9 } - ], - raindropLength: 0.4 - }, - resurrections: { - ...defaults, - ...fonts.matrixcode, - resurrectingCodeRatio: 0.25, - effect:"resurrections", - width:100, - volumetric:true, - density:1.5, - fallSpeed:1.2, - raindropLength:1.25 - } + classic: { + ...defaults, + ...fonts.matrixcode, + }, + operator: { + ...defaults, + ...fonts.matrixcode, + bloomStrength: 0.75, + highPassThreshold: 0.0, + cycleSpeed: 0.05, + cycleStyleName: "cycleRandomly", + cursorEffectThreshold: 0.64, + brightnessOffset: 0.25, + brightnessMultiplier: 0.0, + brightnessMinimum: -1.0, + fallSpeed: 0.65, + glyphEdgeCrop: 0.15, + glyphHeightToWidth: 1.35, + rippleTypeName: "box", + numColumns: 108, + paletteEntries: [ + { hsl: [0.4, 0.8, 0.0], at: 0.0 }, + { hsl: [0.4, 0.8, 0.5], at: 0.5 }, + { hsl: [0.4, 0.8, 1.0], at: 1.0 }, + ], + raindropLength: 1.5, + }, + nightmare: { + ...defaults, + ...fonts.gothic, + highPassThreshold: 0.7, + brightnessMix: 0.75, + fallSpeed: 2.0, + hasThunder: true, + numColumns: 60, + paletteEntries: [ + { hsl: [0.0, 1.0, 0.0], at: 0.0 }, + { hsl: [0.0, 1.0, 0.2], at: 0.2 }, + { hsl: [0.0, 1.0, 0.4], at: 0.4 }, + { hsl: [0.1, 1.0, 0.7], at: 0.7 }, + { hsl: [0.2, 1.0, 1.0], at: 1.0 }, + ], + raindropLength: 0.6, + slant: (22.5 * Math.PI) / 180, + }, + paradise: { + ...defaults, + ...fonts.coptic, + bloomStrength: 1.75, + highPassThreshold: 0, + cycleSpeed: 0.1, + brightnessMix: 0.05, + fallSpeed: 0.08, + hasSun: true, + isPolar: true, + rippleTypeName: "circle", + rippleSpeed: 0.1, + numColumns: 30, + paletteEntries: [ + { hsl: [0.0, 0.0, 0.0], at: 0.0 }, + { hsl: [0.0, 0.8, 0.3], at: 0.3 }, + { hsl: [0.1, 0.8, 0.5], at: 0.5 }, + { hsl: [0.1, 1.0, 0.6], at: 0.6 }, + { hsl: [0.1, 1.0, 0.9], at: 0.9 }, + ], + raindropLength: 0.4, + }, + resurrections: { + ...defaults, + ...fonts.matrixcode, + resurrectingCodeRatio: 0.25, + effect: "resurrections", + width: 100, + volumetric: true, + density: 1.5, + fallSpeed: 1.2, + raindropLength: 1.25, + }, }; versions.throwback = versions.operator; versions["1999"] = versions.classic; -const range = (f, min = -Infinity, max = Infinity) => - Math.max(min, Math.min(max, f)); -const nullNaN = f => (isNaN(f) ? null : f); +const range = (f, min = -Infinity, max = Infinity) => Math.max(min, Math.min(max, f)); +const nullNaN = (f) => (isNaN(f) ? null : f); const paramMapping = { - version: { key: "version", parser: s => s }, - effect: { key: "effect", parser: s => s }, - width: { key: "numColumns", parser: s => nullNaN(parseInt(s)) }, - numColumns: { key: "numColumns", parser: s => nullNaN(parseInt(s)) }, - density: { key: "density", parser: s => nullNaN(range(parseFloat(s), 0)) }, - resolution: { key: "resolution", parser: s => nullNaN(parseFloat(s)) }, - animationSpeed: { - key: "animationSpeed", - parser: s => nullNaN(parseFloat(s)) - }, - forwardSpeed: { - key: "forwardSpeed", - parser: s => nullNaN(parseFloat(s)) - }, - cycleSpeed: { key: "cycleSpeed", parser: s => nullNaN(parseFloat(s)) }, - fallSpeed: { key: "fallSpeed", parser: s => nullNaN(parseFloat(s)) }, - raindropLength: { - key: "raindropLength", - parser: s => nullNaN(parseFloat(s)) - }, - slant: { - key: "slant", - parser: s => nullNaN((parseFloat(s) * Math.PI) / 180) - }, - bloomSize: { - key: "bloomSize", - parser: s => nullNaN(range(parseFloat(s), 0, 1)) - }, - url: { key: "bgURL", parser: s => s }, - stripeColors: { key: "stripeColors", parser: s => s }, - backgroundColor: { key: "backgroundColor", parser: s => s.split(",").map(parseFloat) }, - volumetric: { key: "volumetric", parser: s => s.toLowerCase().includes("true") } + version: { key: "version", parser: (s) => s }, + effect: { key: "effect", parser: (s) => s }, + width: { key: "numColumns", parser: (s) => nullNaN(parseInt(s)) }, + numColumns: { key: "numColumns", parser: (s) => nullNaN(parseInt(s)) }, + density: { key: "density", parser: (s) => nullNaN(range(parseFloat(s), 0)) }, + resolution: { key: "resolution", parser: (s) => nullNaN(parseFloat(s)) }, + animationSpeed: { + key: "animationSpeed", + parser: (s) => nullNaN(parseFloat(s)), + }, + forwardSpeed: { + key: "forwardSpeed", + parser: (s) => nullNaN(parseFloat(s)), + }, + cycleSpeed: { key: "cycleSpeed", parser: (s) => nullNaN(parseFloat(s)) }, + fallSpeed: { key: "fallSpeed", parser: (s) => nullNaN(parseFloat(s)) }, + raindropLength: { + key: "raindropLength", + parser: (s) => nullNaN(parseFloat(s)), + }, + slant: { + key: "slant", + parser: (s) => nullNaN((parseFloat(s) * Math.PI) / 180), + }, + bloomSize: { + key: "bloomSize", + parser: (s) => nullNaN(range(parseFloat(s), 0, 1)), + }, + url: { key: "bgURL", parser: (s) => s }, + stripeColors: { key: "stripeColors", parser: (s) => s }, + backgroundColor: { key: "backgroundColor", parser: (s) => s.split(",").map(parseFloat) }, + volumetric: { key: "volumetric", parser: (s) => s.toLowerCase().includes("true") }, }; paramMapping.dropLength = paramMapping.raindropLength; paramMapping.angle = paramMapping.slant; paramMapping.colors = paramMapping.stripeColors; export default (searchString, make1DTexture) => { - const urlParams = Object.fromEntries( - Array.from(new URLSearchParams(searchString).entries()) - .filter(([key]) => key in paramMapping) - .map(([key, value]) => [ - paramMapping[key].key, - paramMapping[key].parser(value) - ]) - .filter(([_, value]) => value != null) - ); + const urlParams = Object.fromEntries( + Array.from(new URLSearchParams(searchString).entries()) + .filter(([key]) => key in paramMapping) + .map(([key, value]) => [paramMapping[key].key, paramMapping[key].parser(value)]) + .filter(([_, value]) => value != null) + ); - const version = - urlParams.version in versions - ? versions[urlParams.version] - : versions.classic; + const version = urlParams.version in versions ? versions[urlParams.version] : versions.classic; - return { - ...version, - ...urlParams - }; + return { + ...version, + ...urlParams, + }; }; diff --git a/js/imagePass.js b/js/imagePass.js index 749c64a..f40772d 100644 --- a/js/imagePass.js +++ b/js/imagePass.js @@ -1,28 +1,27 @@ import { loadImage, loadText, makePassFBO, makePass } from "./utils.js"; -const defaultBGURL = - "https://upload.wikimedia.org/wikipedia/commons/0/0a/Flammarion_Colored.jpg"; +const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/0/0a/Flammarion_Colored.jpg"; export default (regl, config, inputs) => { - const output = makePassFBO(regl, config.useHalfFloat); - const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL; - const background = loadImage(regl, bgURL); - const imagePassFrag = loadText("../shaders/imagePass.frag"); - const render = regl({ - frag: regl.prop("frag"), - uniforms: { - backgroundTex: background.texture, - tex: inputs.primary, - bloomTex: inputs.bloom - }, - framebuffer: output - }); - return makePass( - { - primary: output - }, - () => render({frag: imagePassFrag.text()}), - null, - [background.loaded, imagePassFrag.loaded] - ); + const output = makePassFBO(regl, config.useHalfFloat); + const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL; + const background = loadImage(regl, bgURL); + const imagePassFrag = loadText("../shaders/imagePass.frag"); + const render = regl({ + frag: regl.prop("frag"), + uniforms: { + backgroundTex: background.texture, + tex: inputs.primary, + bloomTex: inputs.bloom, + }, + framebuffer: output, + }); + return makePass( + { + primary: output, + }, + () => render({ frag: imagePassFrag.text() }), + null, + [background.loaded, imagePassFrag.loaded] + ); }; diff --git a/js/main.js b/js/main.js index b19a4a3..7915138 100644 --- a/js/main.js +++ b/js/main.js @@ -9,30 +9,26 @@ import makeResurrectionPass from "./resurrectionPass.js"; const canvas = document.createElement("canvas"); document.body.appendChild(canvas); -document.addEventListener("touchmove", e => e.preventDefault(), { - passive: false +document.addEventListener("touchmove", (e) => e.preventDefault(), { + passive: false, }); const regl = createREGL({ - canvas, - extensions: ["OES_texture_half_float", "OES_texture_half_float_linear"], - // These extensions are also needed, but Safari misreports that they are missing - optionalExtensions: [ - "EXT_color_buffer_half_float", - "WEBGL_color_buffer_float", - "OES_standard_derivatives" - ] + canvas, + extensions: ["OES_texture_half_float", "OES_texture_half_float_linear"], + // These extensions are also needed, but Safari misreports that they are missing + optionalExtensions: ["EXT_color_buffer_half_float", "WEBGL_color_buffer_float", "OES_standard_derivatives"], }); const effects = { - none: null, - plain: makePalettePass, - customStripes: makeStripePass, - stripes: makeStripePass, - pride: makeStripePass, - image: makeImagePass, - resurrection: makeResurrectionPass, - resurrections: makeResurrectionPass + none: null, + plain: makePalettePass, + customStripes: makeStripePass, + stripes: makeStripePass, + pride: makeStripePass, + image: makeImagePass, + resurrection: makeResurrectionPass, + resurrections: makeResurrectionPass, }; const config = makeConfig(window.location.search); @@ -40,8 +36,8 @@ const resolution = config.resolution; const effect = config.effect in effects ? config.effect : "plain"; const resize = () => { - canvas.width = Math.ceil(canvas.clientWidth * resolution); - canvas.height = Math.ceil(canvas.clientHeight * resolution); + canvas.width = Math.ceil(canvas.clientWidth * resolution); + canvas.height = Math.ceil(canvas.clientHeight * resolution); }; window.onresize = resize; resize(); @@ -49,41 +45,29 @@ resize(); const dimensions = { width: 1, height: 1 }; document.body.onload = async () => { - // All this takes place in a full screen quad. - const fullScreenQuad = makeFullScreenQuad(regl); - const pipeline = makePipeline( - [ - makeMatrixRenderer, - effect === "none" ? null : makeBloomPass, - effects[effect] - ], - p => p.outputs, - regl, - config - ); - const drawToScreen = regl({ - uniforms: { - tex: pipeline[pipeline.length - 1].outputs.primary - } - }); - await Promise.all(pipeline.map(({ ready }) => ready)); - const tick = regl.frame(({ viewportWidth, viewportHeight }) => { - // tick.cancel(); - if ( - dimensions.width !== viewportWidth || - dimensions.height !== viewportHeight - ) { - dimensions.width = viewportWidth; - dimensions.height = viewportHeight; - for (const step of pipeline) { - step.resize(viewportWidth, viewportHeight); - } - } - fullScreenQuad(() => { - for (const step of pipeline) { - step.render(); - } - drawToScreen(); - }); - }); + // All this takes place in a full screen quad. + const fullScreenQuad = makeFullScreenQuad(regl); + const pipeline = makePipeline([makeMatrixRenderer, effect === "none" ? null : makeBloomPass, effects[effect]], (p) => p.outputs, regl, config); + const drawToScreen = regl({ + uniforms: { + tex: pipeline[pipeline.length - 1].outputs.primary, + }, + }); + await Promise.all(pipeline.map(({ ready }) => ready)); + const tick = regl.frame(({ viewportWidth, viewportHeight }) => { + // tick.cancel(); + if (dimensions.width !== viewportWidth || dimensions.height !== viewportHeight) { + dimensions.width = viewportWidth; + dimensions.height = viewportHeight; + for (const step of pipeline) { + step.resize(viewportWidth, viewportHeight); + } + } + fullScreenQuad(() => { + for (const step of pipeline) { + step.render(); + } + drawToScreen(); + }); + }); }; diff --git a/js/palettePass.js b/js/palettePass.js index 8a330ba..d708997 100644 --- a/js/palettePass.js +++ b/js/palettePass.js @@ -1,51 +1,49 @@ import { loadText, extractEntries, make1DTexture, makePassFBO, makePass } from "./utils.js"; const colorToRGB = ([hue, saturation, lightness]) => { - const a = saturation * Math.min(lightness, 1 - lightness); - const f = (n) => { - const k = (n + hue * 12) % 12; - return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)); - }; - return [f(0), f(8), f(4)]; + const a = saturation * Math.min(lightness, 1 - lightness); + const f = (n) => { + const k = (n + hue * 12) % 12; + return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)); + }; + return [f(0), f(8), f(4)]; }; const makePalette = (regl, entries) => { - const PALETTE_SIZE = 2048; - const paletteColors = Array(PALETTE_SIZE); - const sortedEntries = entries - .slice() - .sort((e1, e2) => e1.at - e2.at) - .map(entry => ({ - rgb: colorToRGB(entry.hsl), - arrayIndex: Math.floor( - Math.max(Math.min(1, entry.at), 0) * (PALETTE_SIZE - 1) - ) - })); - sortedEntries.unshift({ rgb: sortedEntries[0].rgb, arrayIndex: 0 }); - sortedEntries.push({ - rgb: sortedEntries[sortedEntries.length - 1].rgb, - arrayIndex: PALETTE_SIZE - 1 - }); - sortedEntries.forEach((entry, index) => { - paletteColors[entry.arrayIndex] = entry.rgb.slice(); - if (index + 1 < sortedEntries.length) { - const nextEntry = sortedEntries[index + 1]; - const diff = nextEntry.arrayIndex - entry.arrayIndex; - for (let i = 0; i < diff; i++) { - const ratio = i / diff; - paletteColors[entry.arrayIndex + i] = [ - entry.rgb[0] * (1 - ratio) + nextEntry.rgb[0] * ratio, - entry.rgb[1] * (1 - ratio) + nextEntry.rgb[1] * ratio, - entry.rgb[2] * (1 - ratio) + nextEntry.rgb[2] * ratio - ]; - } - } - }); + const PALETTE_SIZE = 2048; + const paletteColors = Array(PALETTE_SIZE); + const sortedEntries = entries + .slice() + .sort((e1, e2) => e1.at - e2.at) + .map((entry) => ({ + rgb: colorToRGB(entry.hsl), + arrayIndex: Math.floor(Math.max(Math.min(1, entry.at), 0) * (PALETTE_SIZE - 1)), + })); + sortedEntries.unshift({ rgb: sortedEntries[0].rgb, arrayIndex: 0 }); + sortedEntries.push({ + rgb: sortedEntries[sortedEntries.length - 1].rgb, + arrayIndex: PALETTE_SIZE - 1, + }); + sortedEntries.forEach((entry, index) => { + paletteColors[entry.arrayIndex] = entry.rgb.slice(); + if (index + 1 < sortedEntries.length) { + const nextEntry = sortedEntries[index + 1]; + const diff = nextEntry.arrayIndex - entry.arrayIndex; + for (let i = 0; i < diff; i++) { + const ratio = i / diff; + paletteColors[entry.arrayIndex + i] = [ + entry.rgb[0] * (1 - ratio) + nextEntry.rgb[0] * ratio, + entry.rgb[1] * (1 - ratio) + nextEntry.rgb[1] * ratio, + entry.rgb[2] * (1 - ratio) + nextEntry.rgb[2] * ratio, + ]; + } + } + }); - return make1DTexture( - regl, - paletteColors.flat().map(i => i * 0xff) - ); + return make1DTexture( + regl, + paletteColors.flat().map((i) => i * 0xff) + ); }; // The rendered texture's values are mapped to colors in a palette texture. @@ -55,32 +53,30 @@ const makePalette = (regl, entries) => { // in screen space. export default (regl, config, inputs) => { - const output = makePassFBO(regl, config.useHalfFloat); - const palette = makePalette(regl, config.paletteEntries); + const output = makePassFBO(regl, config.useHalfFloat); + const palette = makePalette(regl, config.paletteEntries); - const palettePassFrag = loadText("../shaders/palettePass.frag"); + const palettePassFrag = loadText("../shaders/palettePass.frag"); - const render = regl({ - frag: regl.prop("frag"), + const render = regl({ + frag: regl.prop("frag"), - uniforms: { - ...extractEntries(config, [ - "backgroundColor", - ]), - tex: inputs.primary, - bloomTex: inputs.bloom, - palette, - ditherMagnitude: 0.05 - }, - framebuffer: output - }); + uniforms: { + ...extractEntries(config, ["backgroundColor"]), + tex: inputs.primary, + bloomTex: inputs.bloom, + palette, + ditherMagnitude: 0.05, + }, + framebuffer: output, + }); - return makePass( - { - primary: output - }, - () => render({ frag: palettePassFrag.text() }), - null, - palettePassFrag.loaded - ); + return makePass( + { + primary: output, + }, + () => render({ frag: palettePassFrag.text() }), + null, + palettePassFrag.loaded + ); }; diff --git a/js/renderer.js b/js/renderer.js index b6289bc..002cccd 100644 --- a/js/renderer.js +++ b/js/renderer.js @@ -1,187 +1,180 @@ -import { - extractEntries, - loadImage, - loadText, - makePassFBO, - makeDoubleBuffer, - makePass -} from "./utils.js"; +import { extractEntries, loadImage, loadText, makePassFBO, makeDoubleBuffer, makePass } from "./utils.js"; const rippleTypes = { - box: 0, - circle: 1 + box: 0, + circle: 1, }; const cycleStyles = { - cycleFasterWhenDimmed: 0, - cycleRandomly: 1 + cycleFasterWhenDimmed: 0, + cycleRandomly: 1, }; const numVerticesPerQuad = 2 * 3; export default (regl, config) => { + const volumetric = config.volumetric; + const density = volumetric && config.effect !== "none" ? config.density : 1; + const [numRows, numColumns] = [config.numColumns, config.numColumns * density]; + const [numQuadRows, numQuadColumns] = volumetric ? [numRows, numColumns] : [1, 1]; + const numQuads = numQuadRows * numQuadColumns; + const quadSize = [1 / numQuadColumns, 1 / numQuadRows]; - const volumetric = config.volumetric; - const density = volumetric && config.effect !== "none" ? config.density : 1; - const [numRows, numColumns] = [config.numColumns, config.numColumns * density]; - const [numQuadRows, numQuadColumns] = volumetric ? [numRows, numColumns] : [1, 1]; - const numQuads = numQuadRows * numQuadColumns; - const quadSize = [1 / numQuadColumns, 1 / numQuadRows]; + // These two framebuffers are used to compute the raining code. + // they take turns being the source and destination of the "compute" shader. + // The half float data type is crucial! It lets us store almost any real number, + // whereas the default type limits us to integers between 0 and 255. - // These two framebuffers are used to compute the raining code. - // they take turns being the source and destination of the "compute" shader. - // The half float data type is crucial! It lets us store almost any real number, - // whereas the default type limits us to integers between 0 and 255. + // This double buffer is smaller than the screen, because its pixels correspond + // with glyphs in the final image, and the glyphs are much larger than a pixel. + const doubleBuffer = makeDoubleBuffer(regl, { + width: numColumns, + height: numRows, + wrapT: "clamp", + type: "half float", + }); - // This double buffer is smaller than the screen, because its pixels correspond - // with glyphs in the final image, and the glyphs are much larger than a pixel. - const doubleBuffer = makeDoubleBuffer(regl, { - width: numColumns, - height: numRows, - wrapT: "clamp", - type: "half float" - }); + const output = makePassFBO(regl, config.useHalfFloat); + const uniforms = { + ...extractEntries(config, [ + // rain general + "glyphHeightToWidth", + "glyphTextureColumns", + // rain update + "animationSpeed", + "brightnessMinimum", + "brightnessMix", + "brightnessMultiplier", + "brightnessOffset", + "cursorEffectThreshold", + "cycleSpeed", + "fallSpeed", + "glyphSequenceLength", + "hasSun", + "hasThunder", + "raindropLength", + "rippleScale", + "rippleSpeed", + "rippleThickness", + "resurrectingCodeRatio", + // rain vertex + "forwardSpeed", + // rain render + "glyphEdgeCrop", + "isPolar", + ]), + density, + numRows, + numColumns, + numQuadRows, + numQuadColumns, + quadSize, + volumetric, + }; - const output = makePassFBO(regl, config.useHalfFloat); + uniforms.rippleType = config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1; + uniforms.cycleStyle = config.cycleStyleName in cycleStyles ? cycleStyles[config.cycleStyleName] : 0; + uniforms.slantVec = [Math.cos(config.slant), Math.sin(config.slant)]; + uniforms.slantScale = 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1); + uniforms.showComputationTexture = config.effect === "none"; - const uniforms = { - ...extractEntries(config, [ - // rain general - "glyphHeightToWidth", - "glyphTextureColumns", - // rain update - "animationSpeed", - "brightnessMinimum", - "brightnessMix", - "brightnessMultiplier", - "brightnessOffset", - "cursorEffectThreshold", - "cycleSpeed", - "fallSpeed", - "glyphSequenceLength", - "hasSun", - "hasThunder", - "raindropLength", - "rippleScale", - "rippleSpeed", - "rippleThickness", - "resurrectingCodeRatio", - // rain vertex - "forwardSpeed", - // rain render - "glyphEdgeCrop", - "isPolar", - ]), - density, - numRows, - numColumns, - numQuadRows, - numQuadColumns, - quadSize, - volumetric - }; + const msdf = loadImage(regl, config.glyphTexURL); - uniforms.rippleType = - config.rippleTypeName in rippleTypes - ? rippleTypes[config.rippleTypeName] - : -1; - uniforms.cycleStyle = - config.cycleStyleName in cycleStyles - ? cycleStyles[config.cycleStyleName] - : 0; - uniforms.slantVec = [Math.cos(config.slant), Math.sin(config.slant)]; - uniforms.slantScale = - 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1); - uniforms.showComputationTexture = config.effect === "none"; + const updateFrag = loadText("../shaders/update.frag"); + const update = regl({ + frag: regl.prop("frag"), + uniforms: { + ...uniforms, + lastState: doubleBuffer.back, + }, - const msdf = loadImage(regl, config.glyphTexURL); + framebuffer: doubleBuffer.front, + }); - const updateFrag = loadText("../shaders/update.frag"); - const update = regl({ - frag: regl.prop("frag"), - uniforms: { - ...uniforms, - lastState: doubleBuffer.back - }, + const quadPositions = Array(numQuadRows) + .fill() + .map((_, y) => + Array(numQuadColumns) + .fill() + .map((_, x) => Array(numVerticesPerQuad).fill([x, y])) + ); - framebuffer: doubleBuffer.front - }); + const quadCorners = Array(numQuads).fill([ + [0, 0], + [0, 1], + [1, 1], + [0, 0], + [1, 1], + [1, 0], + ]); - const quadPositions = Array(numQuadRows).fill().map((_, y) => - Array(numQuadColumns).fill().map((_, x) => - Array(numVerticesPerQuad).fill([x, y]) - ) - ); + // We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen + const renderVert = loadText("../shaders/render.vert"); + const renderFrag = loadText("../shaders/render.frag"); + const render = regl({ + blend: { + enable: true, + func: { + srcRGB: "src alpha", + srcAlpha: 1, + dstRGB: "dst alpha", + dstAlpha: 1, + }, + }, + vert: regl.prop("vert"), + frag: regl.prop("frag"), - const quadCorners = Array(numQuads).fill([[0, 0], [0, 1], [1, 1], [0, 0], [1, 1], [1, 0]]); + uniforms: { + ...uniforms, - // We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen - const renderVert = loadText("../shaders/render.vert"); - const renderFrag = loadText("../shaders/render.frag"); - const render = regl({ - blend: { - enable: true, - func: { - srcRGB: "src alpha", - srcAlpha: 1, - dstRGB: "dst alpha", - dstAlpha: 1 - } - }, - vert: regl.prop("vert"), - frag: regl.prop("frag"), + lastState: doubleBuffer.front, + glyphTex: msdf.texture, - uniforms: { - ...uniforms, + camera: regl.prop("camera"), + transform: regl.prop("transform"), + screenSize: regl.prop("screenSize"), + }, - lastState: doubleBuffer.front, - glyphTex: msdf.texture, + attributes: { + aPosition: quadPositions, + aCorner: quadCorners, + }, + count: numQuads * numVerticesPerQuad, - camera: regl.prop("camera"), - transform: regl.prop("transform"), - screenSize: regl.prop("screenSize") - }, + framebuffer: output, + }); - attributes: { - aPosition: quadPositions, - aCorner: quadCorners - }, - count: numQuads * numVerticesPerQuad, + const screenSize = [1, 1]; + const { mat4, vec3 } = glMatrix; + const camera = mat4.create(); + const translation = vec3.set(vec3.create(), 0, 0.5 / numRows, -1); + const scale = vec3.set(vec3.create(), 1, 1, 1); + const transform = mat4.create(); + mat4.translate(transform, transform, translation); + mat4.scale(transform, transform, scale); - framebuffer: output - }); + return makePass( + { + primary: output, + }, + () => { + const time = Date.now(); - const screenSize = [1, 1]; - const {mat4, vec3} = glMatrix; - const camera = mat4.create(); - const translation = vec3.set(vec3.create(), 0, 0.5 / numRows, -1); - const scale = vec3.set(vec3.create(), 1, 1, 1); - const transform = mat4.create(); - mat4.translate(transform, transform, translation); - mat4.scale(transform, transform, scale); - - return makePass( - { - primary: output - }, - () => { - const time = Date.now(); - - update({frag: updateFrag.text()}); - regl.clear({ - depth: 1, - color: [0, 0, 0, 1], - framebuffer: output - }); - render({camera, transform, screenSize, vert: renderVert.text(), frag: renderFrag.text()}); - }, - (w, h) => { - output.resize(w, h); - const aspectRatio = w / h; - glMatrix.mat4.perspective(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000); - [screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1]; - }, - [msdf.loaded, updateFrag.loaded, renderVert.loaded, renderFrag.loaded] - ); + update({ frag: updateFrag.text() }); + regl.clear({ + depth: 1, + color: [0, 0, 0, 1], + framebuffer: output, + }); + render({ camera, transform, screenSize, vert: renderVert.text(), frag: renderFrag.text() }); + }, + (w, h) => { + output.resize(w, h); + const aspectRatio = w / h; + glMatrix.mat4.perspective(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000); + [screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1]; + }, + [msdf.loaded, updateFrag.loaded, renderVert.loaded, renderFrag.loaded] + ); }; diff --git a/js/resurrectionPass.js b/js/resurrectionPass.js index fd84d10..b8ec04f 100644 --- a/js/resurrectionPass.js +++ b/js/resurrectionPass.js @@ -1,37 +1,35 @@ import { loadText, extractEntries, make1DTexture, makePassFBO, makePass } from "./utils.js"; const colorToRGB = ([hue, saturation, lightness]) => { - const a = saturation * Math.min(lightness, 1 - lightness); - const f = (n) => { - const k = (n + hue * 12) % 12; - return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)); - }; - return [f(0), f(8), f(4)]; + const a = saturation * Math.min(lightness, 1 - lightness); + const f = (n) => { + const k = (n + hue * 12) % 12; + return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)); + }; + return [f(0), f(8), f(4)]; }; export default (regl, config, inputs) => { - const output = makePassFBO(regl, config.useHalfFloat); + const output = makePassFBO(regl, config.useHalfFloat); - const resurrectionPassFrag = loadText("../shaders/resurrectionPass.frag"); + const resurrectionPassFrag = loadText("../shaders/resurrectionPass.frag"); - const render = regl({ - frag: regl.prop("frag"), + const render = regl({ + frag: regl.prop("frag"), - uniforms: { - ...extractEntries(config, [ - "backgroundColor", - ]), - tex: inputs.primary, - bloomTex: inputs.bloom, - ditherMagnitude: 0.05 - }, - framebuffer: output - }); + uniforms: { + ...extractEntries(config, ["backgroundColor"]), + tex: inputs.primary, + bloomTex: inputs.bloom, + ditherMagnitude: 0.05, + }, + framebuffer: output, + }); - return makePass( - { - primary: output - }, - () => render({frag: resurrectionPassFrag.text() }) - ); + return makePass( + { + primary: output, + }, + () => render({ frag: resurrectionPassFrag.text() }) + ); }; diff --git a/js/stripePass.js b/js/stripePass.js index d68d57f..0ec73ea 100644 --- a/js/stripePass.js +++ b/js/stripePass.js @@ -1,61 +1,55 @@ import { loadText, extractEntries, make1DTexture, makePassFBO, makePass } from "./utils.js"; const neapolitanStripeColors = [ - [0.4, 0.15, 0.1], - [0.4, 0.15, 0.1], - [0.8, 0.8, 0.6], - [0.8, 0.8, 0.6], - [1.0, 0.7, 0.8], - [1.0, 0.7, 0.8] + [0.4, 0.15, 0.1], + [0.4, 0.15, 0.1], + [0.8, 0.8, 0.6], + [0.8, 0.8, 0.6], + [1.0, 0.7, 0.8], + [1.0, 0.7, 0.8], ].flat(); const prideStripeColors = [ - [1, 0, 0], - [1, 0.5, 0], - [1, 1, 0], - [0, 1, 0], - [0, 0, 1], - [0.8, 0, 1] + [1, 0, 0], + [1, 0.5, 0], + [1, 1, 0], + [0, 1, 0], + [0, 0, 1], + [0.8, 0, 1], ].flat(); export default (regl, config, inputs) => { - const output = makePassFBO(regl, config.useHalfFloat); + const output = makePassFBO(regl, config.useHalfFloat); - const stripeColors = - "stripeColors" in config - ? config.stripeColors.split(",").map(parseFloat) - : config.effect === "pride" - ? prideStripeColors - : neapolitanStripeColors; - const numStripeColors = Math.floor(stripeColors.length / 3); - const stripes = make1DTexture( - regl, - stripeColors.slice(0, numStripeColors * 3).map(f => Math.floor(f * 0xff)) - ); + const stripeColors = + "stripeColors" in config ? config.stripeColors.split(",").map(parseFloat) : config.effect === "pride" ? prideStripeColors : neapolitanStripeColors; + const numStripeColors = Math.floor(stripeColors.length / 3); + const stripes = make1DTexture( + regl, + stripeColors.slice(0, numStripeColors * 3).map((f) => Math.floor(f * 0xff)) + ); - const stripePassFrag = loadText("../shaders/stripePass.frag"); + const stripePassFrag = loadText("../shaders/stripePass.frag"); - const render = regl({ - frag: regl.prop("frag"), + const render = regl({ + frag: regl.prop("frag"), - uniforms: { - ...extractEntries(config, [ - "backgroundColor", - ]), - tex: inputs.primary, - bloomTex: inputs.bloom, - stripes, - ditherMagnitude: 0.05 - }, - framebuffer: output - }); + uniforms: { + ...extractEntries(config, ["backgroundColor"]), + tex: inputs.primary, + bloomTex: inputs.bloom, + stripes, + ditherMagnitude: 0.05, + }, + framebuffer: output, + }); - return makePass( - { - primary: output - }, - () => render({frag: stripePassFrag.text()}), - null, - stripePassFrag.loaded - ); + return makePass( + { + primary: output, + }, + () => render({ frag: stripePassFrag.text() }), + null, + stripePassFrag.loaded + ); }; diff --git a/js/utils.js b/js/utils.js index 288a69d..5def554 100644 --- a/js/utils.js +++ b/js/utils.js @@ -1,128 +1,120 @@ -const extractEntries = (src, keys) => - Object.fromEntries( - Array.from(Object.entries(src)).filter(([key]) => keys.includes(key)) - ); +const extractEntries = (src, keys) => Object.fromEntries(Array.from(Object.entries(src)).filter(([key]) => keys.includes(key))); const makePassTexture = (regl, halfFloat) => - regl.texture({ - width: 1, - height: 1, - type: halfFloat ? "half float" : "uint8", - wrap: "clamp", - min: "linear", - mag: "linear" - }); + regl.texture({ + width: 1, + height: 1, + type: halfFloat ? "half float" : "uint8", + wrap: "clamp", + min: "linear", + mag: "linear", + }); const makePassFBO = (regl, halfFloat) => regl.framebuffer({ color: makePassTexture(regl, halfFloat) }); // A pyramid is just an array of FBOs, where each FBO is half the width // and half the height of the FBO below it. const makePyramid = (regl, height, halfFloat) => - Array(height) - .fill() - .map(_ => makePassFBO(regl, halfFloat)); + Array(height) + .fill() + .map((_) => makePassFBO(regl, halfFloat)); const makeDoubleBuffer = (regl, props) => { - const state = Array(2) - .fill() - .map(() => - regl.framebuffer({ - color: regl.texture(props), - depthStencil: false - }) - ); - return { - front: ({ tick }) => state[tick % 2], - back: ({ tick }) => state[(tick + 1) % 2] - }; + const state = Array(2) + .fill() + .map(() => + regl.framebuffer({ + color: regl.texture(props), + depthStencil: false, + }) + ); + return { + front: ({ tick }) => state[tick % 2], + back: ({ tick }) => state[(tick + 1) % 2], + }; }; const resizePyramid = (pyramid, vw, vh, scale) => - pyramid.forEach((fbo, index) => - fbo.resize( - Math.floor((vw * scale) / 2 ** index), - Math.floor((vh * scale) / 2 ** index) - ) - ); + pyramid.forEach((fbo, index) => fbo.resize(Math.floor((vw * scale) / 2 ** index), Math.floor((vh * scale) / 2 ** index))); const loadImage = (regl, url) => { - let texture = regl.texture([[0]]); - let loaded = false; - return { - texture: () => { - if (!loaded) { - console.warn(`texture still loading: ${url}`); - } - return texture; - }, - loaded: (async () => { - if (url != null) { - const data = new Image(); - data.crossOrigin = "anonymous"; - data.src = url; - await data.decode(); - loaded = true; - texture = regl.texture({ - data, - mag: "linear", - min: "linear", - flipY: true - }); - } - })() - }; + let texture = regl.texture([[0]]); + let loaded = false; + return { + texture: () => { + if (!loaded) { + console.warn(`texture still loading: ${url}`); + } + return texture; + }, + loaded: (async () => { + if (url != null) { + const data = new Image(); + data.crossOrigin = "anonymous"; + data.src = url; + await data.decode(); + loaded = true; + texture = regl.texture({ + data, + mag: "linear", + min: "linear", + flipY: true, + }); + } + })(), + }; }; const loadShader = (regl, url) => { - let texture = regl.texture([[0]]); - let loaded = false; - return { - texture: () => { - if (!loaded) { - console.warn(`texture still loading: ${url}`); - } - return texture; - }, - loaded: (async () => { - if (url != null) { - const data = new Image(); - data.crossOrigin = "anonymous"; - data.src = url; - await data.decode(); - loaded = true; - texture = regl.texture({ - data, - mag: "linear", - min: "linear", - flipY: true - }); - } - })() - }; + let texture = regl.texture([[0]]); + let loaded = false; + return { + texture: () => { + if (!loaded) { + console.warn(`texture still loading: ${url}`); + } + return texture; + }, + loaded: (async () => { + if (url != null) { + const data = new Image(); + data.crossOrigin = "anonymous"; + data.src = url; + await data.decode(); + loaded = true; + texture = regl.texture({ + data, + mag: "linear", + min: "linear", + flipY: true, + }); + } + })(), + }; }; const loadText = (url) => { - let text = ""; - let loaded = false; - return { - text: () => { - if (!loaded) { - console.warn(`text still loading: ${url}`); - } - return text; - }, - loaded: (async () => { - if (url != null) { - text = await (await fetch(url)).text(); - loaded = true; - } - })() - }; + let text = ""; + let loaded = false; + return { + text: () => { + if (!loaded) { + console.warn(`text still loading: ${url}`); + } + return text; + }, + loaded: (async () => { + if (url != null) { + text = await (await fetch(url)).text(); + loaded = true; + } + })(), + }; }; const makeFullScreenQuad = (regl, uniforms = {}, context = {}) => - regl({ - vert: ` + regl({ + vert: ` precision mediump float; attribute vec2 aPosition; varying vec2 vUV; @@ -132,7 +124,7 @@ const makeFullScreenQuad = (regl, uniforms = {}, context = {}) => } `, - frag: ` + frag: ` precision mediump float; varying vec2 vUV; uniform sampler2D tex; @@ -141,75 +133,65 @@ const makeFullScreenQuad = (regl, uniforms = {}, context = {}) => } `, - attributes: { - aPosition: [-4, -4, 4, -4, 0, 4] - }, - count: 3, + attributes: { + aPosition: [-4, -4, 4, -4, 0, 4], + }, + count: 3, - uniforms: { - ...uniforms, - time: regl.context("time") - }, + uniforms: { + ...uniforms, + time: regl.context("time"), + }, - context, + context, - depth: { enable: false }, - - }); + depth: { enable: false }, + }); const make1DTexture = (regl, data) => - regl.texture({ - data, - width: data.length / 3, - height: 1, - format: "rgb", - mag: "linear", - min: "linear" - }); + regl.texture({ + data, + width: data.length / 3, + height: 1, + format: "rgb", + mag: "linear", + min: "linear", + }); const makePass = (outputs, render, resize, ready) => { - if (render == null) { - render = () => {}; - } - if (resize == null) { - resize = (w, h) => - Object.values(outputs).forEach(output => output.resize(w, h)); - } - if (ready == null) { - ready = Promise.resolve(); - } else if (ready instanceof Array) { - ready = Promise.all(ready); - } - return { - outputs, - render, - resize, - ready - }; + if (render == null) { + render = () => {}; + } + if (resize == null) { + resize = (w, h) => Object.values(outputs).forEach((output) => output.resize(w, h)); + } + if (ready == null) { + ready = Promise.resolve(); + } else if (ready instanceof Array) { + ready = Promise.all(ready); + } + return { + outputs, + render, + resize, + ready, + }; }; const makePipeline = (steps, getInputs, ...params) => - steps - .filter(f => f != null) - .reduce( - (pipeline, f, i) => [ - ...pipeline, - f(...params, i == 0 ? null : getInputs(pipeline[i - 1])) - ], - [] - ); + steps.filter((f) => f != null).reduce((pipeline, f, i) => [...pipeline, f(...params, i == 0 ? null : getInputs(pipeline[i - 1]))], []); export { - extractEntries, - makePassTexture, - makePassFBO, - makeDoubleBuffer, - makePyramid, - resizePyramid, - loadImage, - loadText, - makeFullScreenQuad, - make1DTexture, - makePass, - makePipeline + extractEntries, + makePassTexture, + makePassFBO, + makeDoubleBuffer, + makePyramid, + resizePyramid, + loadImage, + loadText, + makeFullScreenQuad, + make1DTexture, + makePass, + makePipeline, };