From 237990b44c93cbfbb7d3ea88a3353c0a0d28216b Mon Sep 17 00:00:00 2001 From: Rezmason Date: Mon, 5 May 2025 08:52:35 -0700 Subject: [PATCH] Ran the format script --- js/Matrix.js | 50 +- js/index.js | 70 +-- js/main.js | 6 +- js/regl/bloomPass.js | 18 +- js/regl/imagePass.js | 5 +- js/regl/lkgHelper.js | 161 +++--- js/regl/main.js | 236 ++++----- js/regl/mirrorPass.js | 2 +- js/regl/palettePass.js | 148 +++--- js/regl/quiltPass.js | 2 +- js/regl/rainPass.js | 606 ++++++++++------------ js/regl/stripePass.js | 124 ++--- js/regl/utils.js | 24 +- js/utils/config.js | 1056 +++++++++++++++++++------------------- js/webgpu/bloomPass.js | 44 +- js/webgpu/endPass.js | 5 +- js/webgpu/imagePass.js | 12 +- js/webgpu/main.js | 27 +- js/webgpu/mirrorPass.js | 14 +- js/webgpu/palettePass.js | 8 +- js/webgpu/rainPass.js | 45 +- js/webgpu/stripePass.js | 18 +- js/webgpu/utils.js | 34 +- rollup.config.mjs | 66 +-- webpack.config.js | 96 ++-- 25 files changed, 1474 insertions(+), 1403 deletions(-) diff --git a/js/Matrix.js b/js/Matrix.js index 11979c9..653ac90 100644 --- a/js/Matrix.js +++ b/js/Matrix.js @@ -107,34 +107,32 @@ import makeConfig from "./utils/config"; /** @param {MatrixProps} props */ export const Matrix = memo((props) => { - const { style, className, ...rest } = props; - const elProps = { style, className }; - const matrix = useRef(null); - const rainRef = useRef(null); - const canvasRef = useRef(null); + const { style, className, ...rest } = props; + const elProps = { style, className }; + const matrix = useRef(null); + const rainRef = useRef(null); + const canvasRef = useRef(null); - useEffect(() => { - const canvas = document.createElement("canvas"); - canvas.style.width = "100%"; - canvas.style.height = "100%"; - canvasRef.current = canvas; - }, []); + useEffect(() => { + const canvas = document.createElement("canvas"); + canvas.style.width = "100%"; + canvas.style.height = "100%"; + canvasRef.current = canvas; + }, []); - useEffect(() => { - matrix.current.appendChild(canvasRef.current); - const gl = canvasRef.current.getContext("webgl"); - createRain(canvasRef.current, makeConfig({ ...rest }), gl).then( - (handles) => { - rainRef.current = handles; - } - ); + useEffect(() => { + matrix.current.appendChild(canvasRef.current); + const gl = canvasRef.current.getContext("webgl"); + createRain(canvasRef.current, makeConfig({ ...rest }), gl).then((handles) => { + rainRef.current = handles; + }); - return () => { - if (rainRef.current) { - destroyRain(rainRef.current); - } - }; - }, [props]); + return () => { + if (rainRef.current) { + destroyRain(rainRef.current); + } + }; + }, [props]); - return
; + return
; }); diff --git a/js/index.js b/js/index.js index 2a5be3b..67e6a5a 100644 --- a/js/index.js +++ b/js/index.js @@ -6,43 +6,43 @@ import { Matrix } from "./Matrix"; const root = createRoot(document.getElementById("root")); let idx = 1; const versions = [ - "3d", - "trinity", - "bugs", - "megacity", - "nightmare", - "paradise", - "resurrections", - "operator", - "holoplay", - "throwback", - "updated", - "1999", - "2003", - "2021", + "3d", + "trinity", + "bugs", + "megacity", + "nightmare", + "paradise", + "resurrections", + "operator", + "holoplay", + "throwback", + "updated", + "1999", + "2003", + "2021", ]; const App = () => { - const [version, setVersion] = React.useState(versions[0]); - // const [number, setNumber] = React.useState(0); - const onButtonClick = () => { - setVersion((s) => { - const newVersion = versions[idx]; - idx = (idx + 1) % versions.length; - console.log(newVersion); - return newVersion; - }); - }; - // const newNum = () => setNumber((n) => n + 1); - console.log("version", version); - // console.log("num", number); + const [version, setVersion] = React.useState(versions[0]); + // const [number, setNumber] = React.useState(0); + const onButtonClick = () => { + setVersion((s) => { + const newVersion = versions[idx]; + idx = (idx + 1) % versions.length; + console.log(newVersion); + return newVersion; + }); + }; + // const newNum = () => setNumber((n) => n + 1); + console.log("version", version); + // console.log("num", number); - return ( -
-

Rain

- - {/* */} - -
- ); + return ( +
+

Rain

+ + {/* */} + +
+ ); }; root.render(); diff --git a/js/main.js b/js/main.js index 3f6809b..5001a9f 100644 --- a/js/main.js +++ b/js/main.js @@ -7,7 +7,11 @@ document.addEventListener("touchmove", (e) => e.preventDefault(), { }); const supportsWebGPU = async () => { - return window.GPUQueue != null && navigator.gpu != null && navigator.gpu.getPreferredCanvasFormat != null; + return ( + window.GPUQueue != null && + navigator.gpu != null && + navigator.gpu.getPreferredCanvasFormat != null + ); }; const isRunningSwiftShader = () => { diff --git a/js/regl/bloomPass.js b/js/regl/bloomPass.js index a2c09d8..d56a335 100644 --- a/js/regl/bloomPass.js +++ b/js/regl/bloomPass.js @@ -1,7 +1,7 @@ import { makePassFBO, makePass } from "./utils"; -import highPassFrag from '../../shaders/glsl/bloomPass.highPass.frag.glsl'; -import blurFrag from '../../shaders/glsl/bloomPass.blur.frag.glsl'; -import combineFrag from '../../shaders/glsl/bloomPass.combine.frag.glsl'; +import highPassFrag from "../../shaders/glsl/bloomPass.highPass.frag.glsl"; +import blurFrag from "../../shaders/glsl/bloomPass.blur.frag.glsl"; +import combineFrag from "../../shaders/glsl/bloomPass.combine.frag.glsl"; // The bloom pass is basically an added high-pass blur. // The blur approximation is the sum of a pyramid of downscaled, blurred textures. @@ -16,7 +16,9 @@ const makePyramid = (regl, height, halfFloat) => .map((_) => makePassFBO(regl, halfFloat)); 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)), + ); export default ({ regl, config }, inputs) => { const { bloomStrength, bloomSize, highPassThreshold } = config; @@ -94,12 +96,16 @@ export default ({ regl, config }, inputs) => { const highPassFBO = highPassPyramid[i]; const hBlurFBO = hBlurPyramid[i]; const vBlurFBO = vBlurPyramid[i]; - highPass({ fbo: highPassFBO, frag: highPassFrag, tex: i === 0 ? inputs.primary : highPassPyramid[i - 1] }); + highPass({ + fbo: highPassFBO, + frag: highPassFrag, + tex: i === 0 ? inputs.primary : highPassPyramid[i - 1], + }); blur({ fbo: hBlurFBO, frag: blurFrag, tex: highPassFBO, direction: [1, 0] }); blur({ fbo: vBlurFBO, frag: blurFrag, tex: hBlurFBO, direction: [0, 1] }); } combine({ frag: combineFrag }); - } + }, ); }; diff --git a/js/regl/imagePass.js b/js/regl/imagePass.js index d12b017..f66dcd0 100644 --- a/js/regl/imagePass.js +++ b/js/regl/imagePass.js @@ -3,7 +3,8 @@ import imagePassFrag from "../../shaders/glsl/imagePass.frag.glsl"; // Multiplies the rendered rain and bloom by a loaded in image -const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg"; +const defaultBGURL = + "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg"; export default ({ regl, config }, inputs) => { const output = makePassFBO(regl, config.useHalfFloat); @@ -28,6 +29,6 @@ export default ({ regl, config }, inputs) => { if (shouldRender) { render({ frag: imagePassFrag }); } - } + }, ); }; diff --git a/js/regl/lkgHelper.js b/js/regl/lkgHelper.js index 98d5ecc..874b336 100644 --- a/js/regl/lkgHelper.js +++ b/js/regl/lkgHelper.js @@ -2,100 +2,95 @@ const HoloPlayCore = require("holoplay-core"); const recordedDevice = { - buttons: [0, 0, 0, 0], - calibration: { - DPI: { value: 324 }, - center: { value: 0.15018756687641144 }, - configVersion: "3.0", - flipImageX: { value: 0 }, - flipImageY: { value: 0 }, - flipSubp: { value: 0 }, - fringe: { value: 0 }, - invView: { value: 1 }, - pitch: { value: 52.58013153076172 }, - screenH: { value: 2048 }, - screenW: { value: 1536 }, - slope: { value: -7.145165920257568 }, - verticalAngle: { value: 0 }, - viewCone: { value: 40 }, - }, - defaultQuilt: { - quiltAspect: 0.75, - quiltX: 3840, - quiltY: 3840, - tileX: 8, - tileY: 6, - }, - hardwareVersion: "portrait", - hwid: "LKG-P11063", - index: 0, - joystickIndex: -1, - state: "ok", - unityIndex: 1, - windowCoords: [1440, 900], + buttons: [0, 0, 0, 0], + calibration: { + DPI: { value: 324 }, + center: { value: 0.15018756687641144 }, + configVersion: "3.0", + flipImageX: { value: 0 }, + flipImageY: { value: 0 }, + flipSubp: { value: 0 }, + fringe: { value: 0 }, + invView: { value: 1 }, + pitch: { value: 52.58013153076172 }, + screenH: { value: 2048 }, + screenW: { value: 1536 }, + slope: { value: -7.145165920257568 }, + verticalAngle: { value: 0 }, + viewCone: { value: 40 }, + }, + defaultQuilt: { + quiltAspect: 0.75, + quiltX: 3840, + quiltY: 3840, + tileX: 8, + tileY: 6, + }, + hardwareVersion: "portrait", + hwid: "LKG-P11063", + index: 0, + joystickIndex: -1, + state: "ok", + unityIndex: 1, + windowCoords: [1440, 900], }; const interpretDevice = (device) => { - if (device == null) { - return { enabled: false, tileX: 1, tileY: 1 }; - } + if (device == null) { + return { enabled: false, tileX: 1, tileY: 1 }; + } - const fov = 15; + const fov = 15; - const calibration = Object.fromEntries( - Object.entries(device.calibration) - .map(([key, value]) => [key, value.value]) - .filter(([key, value]) => value != null) - ); + const calibration = Object.fromEntries( + Object.entries(device.calibration) + .map(([key, value]) => [key, value.value]) + .filter(([key, value]) => value != null), + ); - const screenInches = calibration.screenW / calibration.DPI; - const pitch = - calibration.pitch * - screenInches * - Math.cos(Math.atan(1.0 / calibration.slope)); - const tilt = - (calibration.screenH / (calibration.screenW * calibration.slope)) * - -(calibration.flipImageX * 2 - 1); - const subp = 1 / (calibration.screenW * 3); + const screenInches = calibration.screenW / calibration.DPI; + const pitch = calibration.pitch * screenInches * Math.cos(Math.atan(1.0 / calibration.slope)); + const tilt = + (calibration.screenH / (calibration.screenW * calibration.slope)) * + -(calibration.flipImageX * 2 - 1); + const subp = 1 / (calibration.screenW * 3); - const defaultQuilt = device.defaultQuilt; + const defaultQuilt = device.defaultQuilt; - const quiltViewPortion = [ - (Math.floor(defaultQuilt.quiltX / defaultQuilt.tileX) * - defaultQuilt.tileX) / - defaultQuilt.quiltX, - (Math.floor(defaultQuilt.quiltY / defaultQuilt.tileY) * - defaultQuilt.tileY) / - defaultQuilt.quiltY, - ]; + const quiltViewPortion = [ + (Math.floor(defaultQuilt.quiltX / defaultQuilt.tileX) * defaultQuilt.tileX) / + defaultQuilt.quiltX, + (Math.floor(defaultQuilt.quiltY / defaultQuilt.tileY) * defaultQuilt.tileY) / + defaultQuilt.quiltY, + ]; - return { - ...defaultQuilt, - ...calibration, - pitch, - tilt, - subp, + return { + ...defaultQuilt, + ...calibration, + pitch, + tilt, + subp, - quiltViewPortion, - fov, - enabled: true, - }; + quiltViewPortion, + fov, + enabled: true, + }; }; export default async (useHoloplay = false, useRecordedDevice = false) => { - if (!useHoloplay) { - return interpretDevice(null); - } - // const HoloPlayCore = await import("../../lib/holoplaycore.module.js"); - const device = await new Promise( - (resolve, reject) => - new HoloPlayCore.Client( - (data) => resolve(data.devices?.[0]), - (error) => resolve(null) - ) - ); - if (device == null && useRecordedDevice) { - return interpretDevice(recordedDevice); - } - return interpretDevice(device); + if (!useHoloplay) { + return interpretDevice(null); + } + // const HoloPlayCore = await import("../../lib/holoplaycore.module.js"); + const device = await new Promise( + (resolve, reject) => + new HoloPlayCore.Client( + (data) => resolve(data.devices?.[0]), + (error) => resolve(null), + ), + ); + if (device == null && useRecordedDevice) { + return interpretDevice(recordedDevice); + } + return interpretDevice(device); }; diff --git a/js/regl/main.js b/js/regl/main.js index fb3cc55..3e7ef6d 100644 --- a/js/regl/main.js +++ b/js/regl/main.js @@ -7,24 +7,20 @@ import makeStripePass from "./stripePass.js"; import makeImagePass from "./imagePass.js"; import makeQuiltPass from "./quiltPass.js"; import makeMirrorPass from "./mirrorPass.js"; -import { - setupCamera, - cameraCanvas, - cameraAspectRatio, -} from "../utils/camera.js"; +import { setupCamera, cameraCanvas, cameraAspectRatio } from "../utils/camera.js"; import getLKG from "./lkgHelper.js"; const effects = { - none: null, - plain: makePalettePass, - palette: makePalettePass, - customStripes: makeStripePass, - stripes: makeStripePass, - pride: makeStripePass, - transPride: makeStripePass, - trans: makeStripePass, - image: makeImagePass, - mirror: makeMirrorPass, + none: null, + plain: makePalettePass, + palette: makePalettePass, + customStripes: makeStripePass, + stripes: makeStripePass, + pride: makeStripePass, + transPride: makeStripePass, + trans: makeStripePass, + image: makeImagePass, + mirror: makeMirrorPass, }; const dimensions = { width: 1, height: 1 }; @@ -41,132 +37,124 @@ const dimensions = { width: 1, height: 1 }; // Promise.all([loadJS("lib/regl.min.js"), loadJS("lib/gl-matrix.js")]); export const createRain = async (canvas, config, gl) => { - const resize = () => { - const dpr = window.devicePixelRatio || 1; - canvas.width = Math.ceil(window.innerWidth * dpr * config.resolution); - canvas.height = Math.ceil(window.innerHeight * dpr * config.resolution); - }; + const resize = () => { + const dpr = window.devicePixelRatio || 1; + canvas.width = Math.ceil(window.innerWidth * dpr * config.resolution); + canvas.height = Math.ceil(window.innerHeight * dpr * config.resolution); + }; - window.onresize = resize; - if (document.fullscreenEnabled || document.webkitFullscreenEnabled) { - window.ondblclick = () => { - if (document.fullscreenElement == null) { - if (canvas.webkitRequestFullscreen != null) { - canvas.webkitRequestFullscreen(); - } else { - canvas.requestFullscreen(); - } - } else { - document.exitFullscreen(); - } - }; - } - resize(); + window.onresize = resize; + if (document.fullscreenEnabled || document.webkitFullscreenEnabled) { + window.ondblclick = () => { + if (document.fullscreenElement == null) { + if (canvas.webkitRequestFullscreen != null) { + canvas.webkitRequestFullscreen(); + } else { + canvas.requestFullscreen(); + } + } else { + document.exitFullscreen(); + } + }; + } + resize(); - if (config.useCamera) { - await setupCamera(); - } + if (config.useCamera) { + await setupCamera(); + } - const extensions = [ - "OES_texture_half_float", - "OES_texture_half_float_linear", - ]; - // These extensions are also needed, but Safari misreports that they are missing - const optionalExtensions = [ - "EXT_color_buffer_half_float", - "WEBGL_color_buffer_float", - "OES_standard_derivatives", - ]; + const extensions = ["OES_texture_half_float", "OES_texture_half_float_linear"]; + // These extensions are also needed, but Safari misreports that they are missing + const optionalExtensions = [ + "EXT_color_buffer_half_float", + "WEBGL_color_buffer_float", + "OES_standard_derivatives", + ]; - switch (config.testFix) { - case "fwidth_10_1_2022_A": - extensions.push("OES_standard_derivatives"); - break; - case "fwidth_10_1_2022_B": - optionalExtensions.forEach((ext) => extensions.push(ext)); - extensions.length = 0; - break; - } + switch (config.testFix) { + case "fwidth_10_1_2022_A": + extensions.push("OES_standard_derivatives"); + break; + case "fwidth_10_1_2022_B": + optionalExtensions.forEach((ext) => extensions.push(ext)); + extensions.length = 0; + break; + } - const regl = createREGL({ - gl, - pixelRatio: 1, - extensions, - optionalExtensions, - }); + const regl = createREGL({ + gl, + pixelRatio: 1, + extensions, + optionalExtensions, + }); - const cameraTex = regl.texture(cameraCanvas); - const lkg = await getLKG(config.useHoloplay, true); + const cameraTex = regl.texture(cameraCanvas); + const lkg = await getLKG(config.useHoloplay, true); - // All this takes place in a full screen quad. - const fullScreenQuad = makeFullScreenQuad(regl); - const effectName = config.effect in effects ? config.effect : "palette"; - const context = { regl, config, lkg, cameraTex, cameraAspectRatio }; - const pipeline = makePipeline(context, [ - makeRain, - makeBloomPass, - effects[effectName], - makeQuiltPass, - ]); + // All this takes place in a full screen quad. + const fullScreenQuad = makeFullScreenQuad(regl); + const effectName = config.effect in effects ? config.effect : "palette"; + const context = { regl, config, lkg, cameraTex, cameraAspectRatio }; + const pipeline = makePipeline(context, [ + makeRain, + makeBloomPass, + effects[effectName], + makeQuiltPass, + ]); - const screenUniforms = { tex: pipeline[pipeline.length - 1].outputs.primary }; - const drawToScreen = regl({ uniforms: screenUniforms }); - await Promise.all(pipeline.map((step) => step.ready)); - pipeline.forEach((step) => step.setSize(canvas.width, canvas.height)); - dimensions.width = canvas.width; - dimensions.height = canvas.height; + const screenUniforms = { tex: pipeline[pipeline.length - 1].outputs.primary }; + const drawToScreen = regl({ uniforms: screenUniforms }); + await Promise.all(pipeline.map((step) => step.ready)); + pipeline.forEach((step) => step.setSize(canvas.width, canvas.height)); + dimensions.width = canvas.width; + dimensions.height = canvas.height; - const targetFrameTimeMilliseconds = 1000 / config.fps; - let last = NaN; + const targetFrameTimeMilliseconds = 1000 / config.fps; + let last = NaN; - const tick = regl.frame(({ viewportWidth, viewportHeight }) => { - if (config.once) { - tick.cancel(); - } + const tick = regl.frame(({ viewportWidth, viewportHeight }) => { + if (config.once) { + tick.cancel(); + } - const now = regl.now() * 1000; + const now = regl.now() * 1000; - if (isNaN(last)) { - last = now; - } + if (isNaN(last)) { + last = now; + } - const shouldRender = - config.fps >= 60 || - now - last >= targetFrameTimeMilliseconds || - config.once == true; + const shouldRender = + config.fps >= 60 || now - last >= targetFrameTimeMilliseconds || config.once == true; - if (shouldRender) { - while (now - targetFrameTimeMilliseconds > last) { - last += targetFrameTimeMilliseconds; - } - } + if (shouldRender) { + while (now - targetFrameTimeMilliseconds > last) { + last += targetFrameTimeMilliseconds; + } + } - if (config.useCamera) { - cameraTex(cameraCanvas); - } - if ( - dimensions.width !== viewportWidth || - dimensions.height !== viewportHeight - ) { - dimensions.width = viewportWidth; - dimensions.height = viewportHeight; - for (const step of pipeline) { - step.setSize(viewportWidth, viewportHeight); - } - } - fullScreenQuad(() => { - for (const step of pipeline) { - step.execute(shouldRender); - } - drawToScreen(); - }); - }); + if (config.useCamera) { + cameraTex(cameraCanvas); + } + if (dimensions.width !== viewportWidth || dimensions.height !== viewportHeight) { + dimensions.width = viewportWidth; + dimensions.height = viewportHeight; + for (const step of pipeline) { + step.setSize(viewportWidth, viewportHeight); + } + } + fullScreenQuad(() => { + for (const step of pipeline) { + step.execute(shouldRender); + } + drawToScreen(); + }); + }); - return { regl, tick, canvas }; + return { regl, tick, canvas }; }; export const destroyRain = ({ regl, tick, canvas }) => { - tick.cancel(); // stop RAF - regl.destroy(); // release all GPU resources & event listeners - //canvas.remove(); // drop from the DOM + tick.cancel(); // stop RAF + regl.destroy(); // release all GPU resources & event listeners + //canvas.remove(); // drop from the DOM }; diff --git a/js/regl/mirrorPass.js b/js/regl/mirrorPass.js index dc52114..f225825 100644 --- a/js/regl/mirrorPass.js +++ b/js/regl/mirrorPass.js @@ -45,6 +45,6 @@ export default ({ regl, config, cameraTex, cameraAspectRatio }, inputs) => { if (shouldRender) { render({ frag: mirrorPassFrag }); } - } + }, ); }; diff --git a/js/regl/palettePass.js b/js/regl/palettePass.js index ed87a55..228e8bb 100644 --- a/js/regl/palettePass.js +++ b/js/regl/palettePass.js @@ -8,47 +8,45 @@ import palettePassFrag from "../../shaders/glsl/palettePass.frag.glsl"; // This shader introduces noise into the renders, to avoid banding const makePalette = (regl, entries) => { - const PALETTE_SIZE = 2048; - const paletteColors = Array(PALETTE_SIZE); + const PALETTE_SIZE = 2048; + const paletteColors = Array(PALETTE_SIZE); - // Convert HSL gradient into sorted RGB gradient, capping the ends - const sortedEntries = entries - .slice() - .sort((e1, e2) => e1.at - e2.at) - .map((entry) => ({ - rgb: colorToRGB(entry.color), - 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, - }); + // Convert HSL gradient into sorted RGB gradient, capping the ends + const sortedEntries = entries + .slice() + .sort((e1, e2) => e1.at - e2.at) + .map((entry) => ({ + rgb: colorToRGB(entry.color), + 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, + }); - // Interpolate between the sorted RGB entries to generate - // the palette texture data - 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, - ]; - } - } - }); + // Interpolate between the sorted RGB entries to generate + // the palette texture data + 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.map((rgb) => [...rgb, 1]) - ); + return make1DTexture( + regl, + paletteColors.map((rgb) => [...rgb, 1]), + ); }; // The rendered texture's values are mapped to colors in a palette texture. @@ -58,44 +56,44 @@ const makePalette = (regl, entries) => { // in screen space. export default ({ regl, config }, inputs) => { - const output = makePassFBO(regl, config.useHalfFloat); - const paletteTex = makePalette(regl, config.palette); - const { - backgroundColor, - cursorColor, - glintColor, - cursorIntensity, - glintIntensity, - ditherMagnitude, - } = config; + const output = makePassFBO(regl, config.useHalfFloat); + const paletteTex = makePalette(regl, config.palette); + const { + backgroundColor, + cursorColor, + glintColor, + cursorIntensity, + glintIntensity, + ditherMagnitude, + } = config; - const render = regl({ - frag: regl.prop("frag"), + const render = regl({ + frag: regl.prop("frag"), - uniforms: { - backgroundColor: colorToRGB(backgroundColor), - cursorColor: colorToRGB(cursorColor), - glintColor: colorToRGB(glintColor), - cursorIntensity, - glintIntensity, - ditherMagnitude, - tex: inputs.primary, - bloomTex: inputs.bloom, - paletteTex, - }, - framebuffer: output, - }); + uniforms: { + backgroundColor: colorToRGB(backgroundColor), + cursorColor: colorToRGB(cursorColor), + glintColor: colorToRGB(glintColor), + cursorIntensity, + glintIntensity, + ditherMagnitude, + tex: inputs.primary, + bloomTex: inputs.bloom, + paletteTex, + }, + framebuffer: output, + }); - return makePass( - { - primary: output, - }, - palettePassFrag.loaded, - (w, h) => output.resize(w, h), - (shouldRender) => { - if (shouldRender) { - render({ frag: palettePassFrag }); - } - } - ); + return makePass( + { + primary: output, + }, + palettePassFrag.loaded, + (w, h) => output.resize(w, h), + (shouldRender) => { + if (shouldRender) { + render({ frag: palettePassFrag }); + } + }, + ); }; diff --git a/js/regl/quiltPass.js b/js/regl/quiltPass.js index 917c120..7897a48 100644 --- a/js/regl/quiltPass.js +++ b/js/regl/quiltPass.js @@ -30,6 +30,6 @@ export default ({ regl, config, lkg }, inputs) => { if (shouldRender) { render({ frag: quiltPassFrag }); } - } + }, ); }; diff --git a/js/regl/rainPass.js b/js/regl/rainPass.js index e44950b..a41dc10 100644 --- a/js/regl/rainPass.js +++ b/js/regl/rainPass.js @@ -1,9 +1,4 @@ -import { - loadImage, - makePassFBO, - makeDoubleBuffer, - makePass, -} from "./utils.js"; +import { loadImage, makePassFBO, makeDoubleBuffer, makePass } from "./utils.js"; import { mat4, vec3 } from "gl-matrix"; import rainPassIntro from "../../shaders/glsl/rainPass.intro.frag.glsl"; import rainPassRaindrop from "../../shaders/glsl/rainPass.raindrop.frag.glsl"; @@ -13,13 +8,11 @@ import rainPassVert from "../../shaders/glsl/rainPass.vert.glsl"; import rainPassFrag from "../../shaders/glsl/rainPass.frag.glsl"; const extractEntries = (src, keys) => - Object.fromEntries( - Array.from(Object.entries(src)).filter(([key]) => keys.includes(key)) - ); + Object.fromEntries(Array.from(Object.entries(src)).filter(([key]) => keys.includes(key))); const rippleTypes = { - box: 0, - circle: 1, + box: 0, + circle: 1, }; // These compute buffers are used to compute the properties of cells in the grid. @@ -30,12 +23,12 @@ const rippleTypes = { // These double buffers are smaller than the screen, because their pixels correspond // with cells in the grid, and the cells' glyphs are much larger than a pixel. const makeComputeDoubleBuffer = (regl, height, width) => - makeDoubleBuffer(regl, { - width, - height, - wrapT: "clamp", - type: "half float", - }); + makeDoubleBuffer(regl, { + width, + height, + wrapT: "clamp", + type: "half float", + }); const numVerticesPerQuad = 2 * 3; const tlVert = [0, 0]; @@ -45,352 +38,301 @@ const brVert = [1, 1]; const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert]; export default ({ regl, config, lkg }) => { - // The volumetric mode multiplies the number of columns - // to reach the desired density, and then overlaps them - const volumetric = config.volumetric; - const density = volumetric && config.effect !== "none" ? config.density : 1; - const [numRows, numColumns] = [ - config.numColumns, - Math.floor(config.numColumns * density), - ]; + // The volumetric mode multiplies the number of columns + // to reach the desired density, and then overlaps them + const volumetric = config.volumetric; + const density = volumetric && config.effect !== "none" ? config.density : 1; + const [numRows, numColumns] = [config.numColumns, Math.floor(config.numColumns * density)]; - // The volumetric mode requires us to create a grid of quads, - // rather than a single quad for our geometry - const [numQuadRows, numQuadColumns] = volumetric - ? [numRows, numColumns] - : [1, 1]; - const numQuads = numQuadRows * numQuadColumns; - const quadSize = [1 / numQuadColumns, 1 / numQuadRows]; + // The volumetric mode requires us to create a grid of quads, + // rather than a single quad for our geometry + const [numQuadRows, numQuadColumns] = volumetric ? [numRows, numColumns] : [1, 1]; + const numQuads = numQuadRows * numQuadColumns; + const quadSize = [1 / numQuadColumns, 1 / numQuadRows]; - // Various effect-related values - const rippleType = - config.rippleTypeName in rippleTypes - ? rippleTypes[config.rippleTypeName] - : -1; - const slantVec = [Math.cos(config.slant), Math.sin(config.slant)]; - const slantScale = - 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1); - const showDebugView = config.effect === "none"; + // Various effect-related values + const rippleType = config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1; + const slantVec = [Math.cos(config.slant), Math.sin(config.slant)]; + const slantScale = 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1); + const showDebugView = config.effect === "none"; - const commonUniforms = { - ...extractEntries(config, [ - "animationSpeed", - "glyphHeightToWidth", - "glyphSequenceLength", - "glyphTextureGridSize", - ]), - numColumns, - numRows, - showDebugView, - }; + const commonUniforms = { + ...extractEntries(config, [ + "animationSpeed", + "glyphHeightToWidth", + "glyphSequenceLength", + "glyphTextureGridSize", + ]), + numColumns, + numRows, + showDebugView, + }; - const introDoubleBuffer = makeComputeDoubleBuffer(regl, 1, numColumns); + const introDoubleBuffer = makeComputeDoubleBuffer(regl, 1, numColumns); - const introUniforms = { - ...commonUniforms, - ...extractEntries(config, ["fallSpeed", "skipIntro"]), - }; - const intro = regl({ - frag: regl.prop("frag"), - uniforms: { - ...introUniforms, - previousIntroState: introDoubleBuffer.back, - }, + const introUniforms = { + ...commonUniforms, + ...extractEntries(config, ["fallSpeed", "skipIntro"]), + }; + const intro = regl({ + frag: regl.prop("frag"), + uniforms: { + ...introUniforms, + previousIntroState: introDoubleBuffer.back, + }, - framebuffer: introDoubleBuffer.front, - }); + framebuffer: introDoubleBuffer.front, + }); - const raindropDoubleBuffer = makeComputeDoubleBuffer( - regl, - numRows, - numColumns - ); - - const raindropUniforms = { - ...commonUniforms, - ...extractEntries(config, [ - "brightnessDecay", - "fallSpeed", - "raindropLength", - "loops", - "skipIntro", - ]), - }; - const raindrop = regl({ - frag: regl.prop("frag"), - uniforms: { - ...raindropUniforms, - introState: introDoubleBuffer.front, - previousRaindropState: raindropDoubleBuffer.back, - }, + const raindropDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns); - framebuffer: raindropDoubleBuffer.front, - }); + const raindropUniforms = { + ...commonUniforms, + ...extractEntries(config, [ + "brightnessDecay", + "fallSpeed", + "raindropLength", + "loops", + "skipIntro", + ]), + }; + const raindrop = regl({ + frag: regl.prop("frag"), + uniforms: { + ...raindropUniforms, + introState: introDoubleBuffer.front, + previousRaindropState: raindropDoubleBuffer.back, + }, - const symbolDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns); + framebuffer: raindropDoubleBuffer.front, + }); - const symbolUniforms = { - ...commonUniforms, - ...extractEntries(config, ["cycleSpeed", "cycleFrameSkip", "loops"]), - }; - const symbol = regl({ - frag: regl.prop("frag"), - uniforms: { - ...symbolUniforms, - raindropState: raindropDoubleBuffer.front, - previousSymbolState: symbolDoubleBuffer.back, - }, + const symbolDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns); - framebuffer: symbolDoubleBuffer.front, - }); + const symbolUniforms = { + ...commonUniforms, + ...extractEntries(config, ["cycleSpeed", "cycleFrameSkip", "loops"]), + }; + const symbol = regl({ + frag: regl.prop("frag"), + uniforms: { + ...symbolUniforms, + raindropState: raindropDoubleBuffer.front, + previousSymbolState: symbolDoubleBuffer.back, + }, - const effectDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns); + framebuffer: symbolDoubleBuffer.front, + }); - const effectUniforms = { - ...commonUniforms, - ...extractEntries(config, [ - "hasThunder", - "rippleScale", - "rippleSpeed", - "rippleThickness", - "loops", - ]), - rippleType, - }; - const effect = regl({ - frag: regl.prop("frag"), - uniforms: { - ...effectUniforms, - raindropState: raindropDoubleBuffer.front, - previousEffectState: effectDoubleBuffer.back, - }, + const effectDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns); - framebuffer: effectDoubleBuffer.front, - }); + const effectUniforms = { + ...commonUniforms, + ...extractEntries(config, [ + "hasThunder", + "rippleScale", + "rippleSpeed", + "rippleThickness", + "loops", + ]), + rippleType, + }; + const effect = regl({ + frag: regl.prop("frag"), + uniforms: { + ...effectUniforms, + raindropState: raindropDoubleBuffer.front, + previousEffectState: effectDoubleBuffer.back, + }, - const quadPositions = Array(numQuadRows) - .fill() - .map((_, y) => - Array(numQuadColumns) - .fill() - .map((_, x) => Array(numVerticesPerQuad).fill([x, y])) - ); + framebuffer: effectDoubleBuffer.front, + }); - // We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen - const glyphMSDF = loadImage(regl, config.glyphMSDFURL); - const glintMSDF = loadImage(regl, config.glintMSDFURL); - const baseTexture = loadImage(regl, config.baseTextureURL, true); - const glintTexture = loadImage(regl, config.glintTextureURL, true); - const output = makePassFBO(regl, config.useHalfFloat); - const renderUniforms = { - ...commonUniforms, - ...extractEntries(config, [ - // vertex - "forwardSpeed", - "glyphVerticalSpacing", - // fragment - "baseBrightness", - "baseContrast", - "glintBrightness", - "glintContrast", - "hasBaseTexture", - "hasGlintTexture", - "brightnessThreshold", - "brightnessOverride", - "isolateCursor", - "isolateGlint", - "glyphEdgeCrop", - "isPolar", - ]), - density, - numQuadColumns, - numQuadRows, - quadSize, - slantScale, - slantVec, - volumetric, - }; - const render = regl({ - blend: { - enable: true, - func: { - src: "one", - dst: "one", - }, - }, - vert: regl.prop("vert"), - frag: regl.prop("frag"), + const quadPositions = Array(numQuadRows) + .fill() + .map((_, y) => + Array(numQuadColumns) + .fill() + .map((_, x) => Array(numVerticesPerQuad).fill([x, y])), + ); - uniforms: { - ...renderUniforms, + // We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen + const glyphMSDF = loadImage(regl, config.glyphMSDFURL); + const glintMSDF = loadImage(regl, config.glintMSDFURL); + const baseTexture = loadImage(regl, config.baseTextureURL, true); + const glintTexture = loadImage(regl, config.glintTextureURL, true); + const output = makePassFBO(regl, config.useHalfFloat); + const renderUniforms = { + ...commonUniforms, + ...extractEntries(config, [ + // vertex + "forwardSpeed", + "glyphVerticalSpacing", + // fragment + "baseBrightness", + "baseContrast", + "glintBrightness", + "glintContrast", + "hasBaseTexture", + "hasGlintTexture", + "brightnessThreshold", + "brightnessOverride", + "isolateCursor", + "isolateGlint", + "glyphEdgeCrop", + "isPolar", + ]), + density, + numQuadColumns, + numQuadRows, + quadSize, + slantScale, + slantVec, + volumetric, + }; + const render = regl({ + blend: { + enable: true, + func: { + src: "one", + dst: "one", + }, + }, + vert: regl.prop("vert"), + frag: regl.prop("frag"), - raindropState: raindropDoubleBuffer.front, - symbolState: symbolDoubleBuffer.front, - effectState: effectDoubleBuffer.front, - glyphMSDF: glyphMSDF.texture, - glintMSDF: glintMSDF.texture, - baseTexture: baseTexture.texture, - glintTexture: glintTexture.texture, - glyphTransform: regl.prop('glyphTransform'), + uniforms: { + ...renderUniforms, - msdfPxRange: 4.0, - glyphMSDFSize: () => [glyphMSDF.width(), glyphMSDF.height()], - glintMSDFSize: () => [glintMSDF.width(), glintMSDF.height()], + raindropState: raindropDoubleBuffer.front, + symbolState: symbolDoubleBuffer.front, + effectState: effectDoubleBuffer.front, + glyphMSDF: glyphMSDF.texture, + glintMSDF: glintMSDF.texture, + baseTexture: baseTexture.texture, + glintTexture: glintTexture.texture, + glyphTransform: regl.prop("glyphTransform"), - camera: regl.prop("camera"), - transform: regl.prop("transform"), - screenSize: regl.prop("screenSize"), - }, + msdfPxRange: 4.0, + glyphMSDFSize: () => [glyphMSDF.width(), glyphMSDF.height()], + glintMSDFSize: () => [glintMSDF.width(), glintMSDF.height()], - viewport: regl.prop("viewport"), + camera: regl.prop("camera"), + transform: regl.prop("transform"), + screenSize: regl.prop("screenSize"), + }, - attributes: { - aPosition: quadPositions, - aCorner: Array(numQuads).fill(quadVertices), - }, - count: numQuads * numVerticesPerQuad, + viewport: regl.prop("viewport"), - framebuffer: output, - }); + attributes: { + aPosition: quadPositions, + aCorner: Array(numQuads).fill(quadVertices), + }, + count: numQuads * numVerticesPerQuad, - // Camera and transform math for the volumetric mode - const screenSize = [1, 1]; - //const { mat4, vec3 } = glMatrix; - const transform = mat4.create(); - if (volumetric && config.isometric) { - mat4.rotateX(transform, transform, (Math.PI * 1) / 8); - mat4.rotateY(transform, transform, (Math.PI * 1) / 4); - mat4.translate(transform, transform, vec3.fromValues(0, 0, -1)); - mat4.scale(transform, transform, vec3.fromValues(1, 1, 2)); - } else if (lkg.enabled) { - mat4.translate(transform, transform, vec3.fromValues(0, 0, -1.1)); - mat4.scale(transform, transform, vec3.fromValues(1, 1, 1)); - mat4.scale(transform, transform, vec3.fromValues(0.15, 0.15, 0.15)); - } else { - mat4.translate(transform, transform, vec3.fromValues(0, 0, -1)); - } - const camera = mat4.create(); + framebuffer: output, + }); - const vantagePoints = []; + // Camera and transform math for the volumetric mode + const screenSize = [1, 1]; + //const { mat4, vec3 } = glMatrix; + const transform = mat4.create(); + if (volumetric && config.isometric) { + mat4.rotateX(transform, transform, (Math.PI * 1) / 8); + mat4.rotateY(transform, transform, (Math.PI * 1) / 4); + mat4.translate(transform, transform, vec3.fromValues(0, 0, -1)); + mat4.scale(transform, transform, vec3.fromValues(1, 1, 2)); + } else if (lkg.enabled) { + mat4.translate(transform, transform, vec3.fromValues(0, 0, -1.1)); + mat4.scale(transform, transform, vec3.fromValues(1, 1, 1)); + mat4.scale(transform, transform, vec3.fromValues(0.15, 0.15, 0.15)); + } else { + mat4.translate(transform, transform, vec3.fromValues(0, 0, -1)); + } + const camera = mat4.create(); - return makePass( - { - primary: output, - }, - Promise.all([ - glyphMSDF.loaded, - glintMSDF.loaded, - baseTexture.loaded, - glintTexture.loaded, - ]), - (w, h) => { - output.resize(w, h); - const aspectRatio = w / h; + const vantagePoints = []; - const [numTileColumns, numTileRows] = [lkg.tileX, lkg.tileY]; - const numVantagePoints = numTileRows * numTileColumns; - const tileWidth = Math.floor(w / numTileColumns); - const tileHeight = Math.floor(h / numTileRows); - vantagePoints.length = 0; - for (let row = 0; row < numTileRows; row++) { - for (let column = 0; column < numTileColumns; column++) { - const index = column + row * numTileColumns; - const camera = mat4.create(); + return makePass( + { + primary: output, + }, + Promise.all([glyphMSDF.loaded, glintMSDF.loaded, baseTexture.loaded, glintTexture.loaded]), + (w, h) => { + output.resize(w, h); + const aspectRatio = w / h; - if (volumetric && config.isometric) { - if (aspectRatio > 1) { - mat4.ortho( - camera, - -1.5 * aspectRatio, - 1.5 * aspectRatio, - -1.5, - 1.5, - -1000, - 1000 - ); - } else { - mat4.ortho( - camera, - -1.5, - 1.5, - -1.5 / aspectRatio, - 1.5 / aspectRatio, - -1000, - 1000 - ); - } - } else if (lkg.enabled) { - mat4.perspective( - camera, - (Math.PI / 180) * lkg.fov, - lkg.quiltAspect, - 0.0001, - 1000 - ); + const [numTileColumns, numTileRows] = [lkg.tileX, lkg.tileY]; + const numVantagePoints = numTileRows * numTileColumns; + const tileWidth = Math.floor(w / numTileColumns); + const tileHeight = Math.floor(h / numTileRows); + vantagePoints.length = 0; + for (let row = 0; row < numTileRows; row++) { + for (let column = 0; column < numTileColumns; column++) { + const index = column + row * numTileColumns; + const camera = mat4.create(); - const distanceToTarget = -1; // TODO: Get from somewhere else - let vantagePointAngle = - (Math.PI / 180) * - lkg.viewCone * - (index / (numVantagePoints - 1) - 0.5); - if (isNaN(vantagePointAngle)) { - vantagePointAngle = 0; - } - const xOffset = distanceToTarget * Math.tan(vantagePointAngle); + if (volumetric && config.isometric) { + if (aspectRatio > 1) { + mat4.ortho(camera, -1.5 * aspectRatio, 1.5 * aspectRatio, -1.5, 1.5, -1000, 1000); + } else { + mat4.ortho(camera, -1.5, 1.5, -1.5 / aspectRatio, 1.5 / aspectRatio, -1000, 1000); + } + } else if (lkg.enabled) { + mat4.perspective(camera, (Math.PI / 180) * lkg.fov, lkg.quiltAspect, 0.0001, 1000); - mat4.translate(camera, camera, vec3.fromValues(xOffset, 0, 0)); + const distanceToTarget = -1; // TODO: Get from somewhere else + let vantagePointAngle = + (Math.PI / 180) * lkg.viewCone * (index / (numVantagePoints - 1) - 0.5); + if (isNaN(vantagePointAngle)) { + vantagePointAngle = 0; + } + const xOffset = distanceToTarget * Math.tan(vantagePointAngle); - camera[8] = - -xOffset / - (distanceToTarget * - Math.tan((Math.PI / 180) * 0.5 * lkg.fov) * - lkg.quiltAspect); // Is this right?? - } else { - mat4.perspective( - camera, - (Math.PI / 180) * 90, - aspectRatio, - 0.0001, - 1000 - ); - } + mat4.translate(camera, camera, vec3.fromValues(xOffset, 0, 0)); - const viewport = { - x: column * tileWidth, - y: row * tileHeight, - width: tileWidth, - height: tileHeight, - }; - vantagePoints.push({ camera, viewport }); - } - } - [screenSize[0], screenSize[1]] = - aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1]; - }, - (shouldRender) => { - intro({ frag: rainPassIntro }); - raindrop({ frag: rainPassRaindrop }); - symbol({ frag: rainPassSymbol }); - effect({ frag: rainPassEffect }); + camera[8] = + -xOffset / + (distanceToTarget * Math.tan((Math.PI / 180) * 0.5 * lkg.fov) * lkg.quiltAspect); // Is this right?? + } else { + mat4.perspective(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000); + } - if (shouldRender) { - regl.clear({ - depth: 1, - color: [0, 0, 0, 1], - framebuffer: output, - }); - - for (const vantagePoint of vantagePoints) { - render({ - ...vantagePoint, - transform, - screenSize, - vert: rainPassVert, - frag: rainPassFrag, - glyphTransform: [1, 0, 0, 1] - }); - } - } - } - ); + const viewport = { + x: column * tileWidth, + y: row * tileHeight, + width: tileWidth, + height: tileHeight, + }; + vantagePoints.push({ camera, viewport }); + } + } + [screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1]; + }, + (shouldRender) => { + intro({ frag: rainPassIntro }); + raindrop({ frag: rainPassRaindrop }); + symbol({ frag: rainPassSymbol }); + effect({ frag: rainPassEffect }); + + if (shouldRender) { + regl.clear({ + depth: 1, + color: [0, 0, 0, 1], + framebuffer: output, + }); + + for (const vantagePoint of vantagePoints) { + render({ + ...vantagePoint, + transform, + screenSize, + vert: rainPassVert, + frag: rainPassFrag, + glyphTransform: [1, 0, 0, 1], + }); + } + } + }, + ); }; diff --git a/js/regl/stripePass.js b/js/regl/stripePass.js index f962a17..ebde1e7 100644 --- a/js/regl/stripePass.js +++ b/js/regl/stripePass.js @@ -8,77 +8,77 @@ import stripePassFrag from "../../shaders/glsl/stripePass.frag.glsl"; // This shader introduces noise into the renders, to avoid banding const transPrideStripeColors = [ - { space: "rgb", values: [0.36, 0.81, 0.98] }, - { space: "rgb", values: [0.96, 0.66, 0.72] }, - { space: "rgb", values: [1.0, 1.0, 1.0] }, - { space: "rgb", values: [0.96, 0.66, 0.72] }, - { space: "rgb", values: [0.36, 0.81, 0.98] }, + { space: "rgb", values: [0.36, 0.81, 0.98] }, + { space: "rgb", values: [0.96, 0.66, 0.72] }, + { space: "rgb", values: [1.0, 1.0, 1.0] }, + { space: "rgb", values: [0.96, 0.66, 0.72] }, + { space: "rgb", values: [0.36, 0.81, 0.98] }, ] - .map((color) => Array(3).fill(color)) - .flat(); + .map((color) => Array(3).fill(color)) + .flat(); const prideStripeColors = [ - { space: "rgb", values: [0.89, 0.01, 0.01] }, - { space: "rgb", values: [1.0, 0.55, 0.0] }, - { space: "rgb", values: [1.0, 0.93, 0.0] }, - { space: "rgb", values: [0.0, 0.5, 0.15] }, - { space: "rgb", values: [0.0, 0.3, 1.0] }, - { space: "rgb", values: [0.46, 0.03, 0.53] }, + { space: "rgb", values: [0.89, 0.01, 0.01] }, + { space: "rgb", values: [1.0, 0.55, 0.0] }, + { space: "rgb", values: [1.0, 0.93, 0.0] }, + { space: "rgb", values: [0.0, 0.5, 0.15] }, + { space: "rgb", values: [0.0, 0.3, 1.0] }, + { space: "rgb", values: [0.46, 0.03, 0.53] }, ] - .map((color) => Array(2).fill(color)) - .flat(); + .map((color) => Array(2).fill(color)) + .flat(); export default ({ regl, config }, inputs) => { - const output = makePassFBO(regl, config.useHalfFloat); + const output = makePassFBO(regl, config.useHalfFloat); - const { - backgroundColor, - cursorColor, - glintColor, - cursorIntensity, - glintIntensity, - ditherMagnitude, - } = config; + const { + backgroundColor, + cursorColor, + glintColor, + cursorIntensity, + glintIntensity, + ditherMagnitude, + } = config; - // Expand and convert stripe colors into 1D texture data - const stripeColors = - "stripeColors" in config - ? config.stripeColors - : config.effect === "pride" - ? prideStripeColors - : transPrideStripeColors; - const stripeTex = make1DTexture( - regl, - stripeColors.map((color) => [...colorToRGB(color), 1]) - ); + // Expand and convert stripe colors into 1D texture data + const stripeColors = + "stripeColors" in config + ? config.stripeColors + : config.effect === "pride" + ? prideStripeColors + : transPrideStripeColors; + const stripeTex = make1DTexture( + regl, + stripeColors.map((color) => [...colorToRGB(color), 1]), + ); - const render = regl({ - frag: regl.prop("frag"), + const render = regl({ + frag: regl.prop("frag"), - uniforms: { - backgroundColor: colorToRGB(backgroundColor), - cursorColor: colorToRGB(cursorColor), - glintColor: colorToRGB(glintColor), - cursorIntensity, - glintIntensity, - ditherMagnitude, - tex: inputs.primary, - bloomTex: inputs.bloom, - stripeTex, - }, - framebuffer: output, - }); + uniforms: { + backgroundColor: colorToRGB(backgroundColor), + cursorColor: colorToRGB(cursorColor), + glintColor: colorToRGB(glintColor), + cursorIntensity, + glintIntensity, + ditherMagnitude, + tex: inputs.primary, + bloomTex: inputs.bloom, + stripeTex, + }, + framebuffer: output, + }); - return makePass( - { - primary: output, - }, - null, - (w, h) => output.resize(w, h), - (shouldRender) => { - if (shouldRender) { - render({ frag: stripePassFrag }); - } - } - ); + return makePass( + { + primary: output, + }, + null, + (w, h) => output.resize(w, h), + (shouldRender) => { + if (shouldRender) { + render({ frag: stripePassFrag }); + } + }, + ); }; diff --git a/js/regl/utils.js b/js/regl/utils.js index efbb284..5678acf 100644 --- a/js/regl/utils.js +++ b/js/regl/utils.js @@ -8,7 +8,8 @@ const makePassTexture = (regl, halfFloat) => mag: "linear", }); -const makePassFBO = (regl, halfFloat) => regl.framebuffer({ color: makePassTexture(regl, halfFloat) }); +const makePassFBO = (regl, halfFloat) => + regl.framebuffer({ color: makePassTexture(regl, halfFloat) }); const makeDoubleBuffer = (regl, props) => { const state = Array(2) @@ -17,7 +18,7 @@ const makeDoubleBuffer = (regl, props) => { regl.framebuffer({ color: regl.texture(props), depthStencil: false, - }) + }), ); return { front: ({ tick }) => state[tick % 2], @@ -149,6 +150,21 @@ const makePass = (outputs, ready, setSize, execute) => ({ }); const makePipeline = (context, steps) => - steps.filter((f) => f != null).reduce((pipeline, f, i) => [...pipeline, f(context, i == 0 ? null : pipeline[i - 1].outputs)], []); + steps + .filter((f) => f != null) + .reduce( + (pipeline, f, i) => [...pipeline, f(context, i == 0 ? null : pipeline[i - 1].outputs)], + [], + ); -export { makePassTexture, makePassFBO, makeDoubleBuffer, loadImage, loadText, makeFullScreenQuad, make1DTexture, makePass, makePipeline }; +export { + makePassTexture, + makePassFBO, + makeDoubleBuffer, + loadImage, + loadText, + makeFullScreenQuad, + make1DTexture, + makePass, + makePipeline, +}; diff --git a/js/utils/config.js b/js/utils/config.js index e1c16fb..6df82ae 100644 --- a/js/utils/config.js +++ b/js/utils/config.js @@ -16,402 +16,402 @@ import texMesh from "../../assets/mesh.png"; import texMetal from "../../assets/metal.png"; const fonts = { - coptic: { - // The script the Gnostic codices were written in - glyphMSDFURL: msdfCoptic, - glyphSequenceLength: 32, - glyphTextureGridSize: [8, 8], - }, - gothic: { - // The script the Codex Argenteus was written in - glyphMSDFURL: msdfGothic, - glyphSequenceLength: 27, - glyphTextureGridSize: [8, 8], - }, - matrixcode: { - // The glyphs seen in the film trilogy - glyphMSDFURL: msdfMatrixCode, - glyphSequenceLength: 57, - glyphTextureGridSize: [8, 8], - }, - megacity: { - // The glyphs seen in the film trilogy - glyphMSDFURL: megacity, - glyphSequenceLength: 64, - glyphTextureGridSize: [8, 8], - }, - resurrections: { - // The glyphs seen in the film trilogy - glyphMSDFURL: msdfRes, - glintMSDFURL: msdfResGlint, - glyphSequenceLength: 135, - glyphTextureGridSize: [13, 12], - }, - huberfishA: { - glyphMSDFURL: msdfHuberfishA, - glyphSequenceLength: 34, - glyphTextureGridSize: [6, 6], - }, - huberfishD: { - glyphMSDFURL: msdfHuberfishD, - glyphSequenceLength: 34, - glyphTextureGridSize: [6, 6], - }, - gtarg_tenretniolleh: { - glyphMSDFURL: msdfGtargTenretni, - glyphSequenceLength: 36, - glyphTextureGridSize: [6, 6], - }, - gtarg_alientext: { - glyphMSDFURL: msdfGtargAlienText, - glyphSequenceLength: 38, - glyphTextureGridSize: [8, 5], - }, - neomatrixology: { - glyphMSDFURL: msdfNeoMatrixology, - glyphSequenceLength: 12, - glyphTextureGridSize: [4, 4], - }, + coptic: { + // The script the Gnostic codices were written in + glyphMSDFURL: msdfCoptic, + glyphSequenceLength: 32, + glyphTextureGridSize: [8, 8], + }, + gothic: { + // The script the Codex Argenteus was written in + glyphMSDFURL: msdfGothic, + glyphSequenceLength: 27, + glyphTextureGridSize: [8, 8], + }, + matrixcode: { + // The glyphs seen in the film trilogy + glyphMSDFURL: msdfMatrixCode, + glyphSequenceLength: 57, + glyphTextureGridSize: [8, 8], + }, + megacity: { + // The glyphs seen in the film trilogy + glyphMSDFURL: megacity, + glyphSequenceLength: 64, + glyphTextureGridSize: [8, 8], + }, + resurrections: { + // The glyphs seen in the film trilogy + glyphMSDFURL: msdfRes, + glintMSDFURL: msdfResGlint, + glyphSequenceLength: 135, + glyphTextureGridSize: [13, 12], + }, + huberfishA: { + glyphMSDFURL: msdfHuberfishA, + glyphSequenceLength: 34, + glyphTextureGridSize: [6, 6], + }, + huberfishD: { + glyphMSDFURL: msdfHuberfishD, + glyphSequenceLength: 34, + glyphTextureGridSize: [6, 6], + }, + gtarg_tenretniolleh: { + glyphMSDFURL: msdfGtargTenretni, + glyphSequenceLength: 36, + glyphTextureGridSize: [6, 6], + }, + gtarg_alientext: { + glyphMSDFURL: msdfGtargAlienText, + glyphSequenceLength: 38, + glyphTextureGridSize: [8, 5], + }, + neomatrixology: { + glyphMSDFURL: msdfNeoMatrixology, + glyphSequenceLength: 12, + glyphTextureGridSize: [4, 4], + }, }; const textureURLs = { - sand: texSand, - pixels: texPixels, - mesh: texMesh, - metal: texMetal, + sand: texSand, + pixels: texPixels, + mesh: texMesh, + metal: texMetal, }; const hsl = (...values) => ({ space: "hsl", values }); const rgb = (...values) => ({ space: "rgb", values }); const defaults = { - font: "matrixcode", - effect: "palette", // The name of the effect to apply at the end of the process— mainly handles coloration - baseTexture: null, // The name of the texture to apply to the base layer of the glyphs - glintTexture: null, // The name of the texture to apply to the glint layer of the glyphs - useCamera: false, - backgroundColor: hsl(0, 0, 0), // The color "behind" the glyphs - isolateCursor: true, // Whether the "cursor"— the brightest glyph at the bottom of a raindrop— has its own color - cursorColor: hsl(0.242, 1, 0.73), // The color of the cursor - cursorIntensity: 2, // The intensity of the cursor - isolateGlint: false, // Whether the "glint"— highlights on certain symbols in the font— should appear - glintColor: hsl(0, 0, 1), // The color of the glint - glintIntensity: 1, // The intensity of the glint - volumetric: false, // A mode where the raindrops appear in perspective - animationSpeed: 1, // The global rate that all animations progress - fps: 60, // The target frame rate (frames per second) of the effect - forwardSpeed: 0.25, // The speed volumetric rain approaches the eye - bloomStrength: 0.7, // The intensity of the bloom - bloomSize: 0.4, // The amount the bloom calculation is scaled - highPassThreshold: 0.1, // The minimum brightness that is still blurred - cycleSpeed: 0.03, // The speed glyphs change - cycleFrameSkip: 1, // The global minimum number of frames between glyphs cycling - baseBrightness: -0.5, // The brightness of the glyphs, before any effects are applied - baseContrast: 1.1, // The contrast of the glyphs, before any effects are applied - glintBrightness: -1.5, // The brightness of the glints, before any effects are applied - glintContrast: 2.5, // The contrast of the glints, before any effects are applied - brightnessOverride: 0.0, // A global override to the brightness of displayed glyphs. Only used if it is > 0. - brightnessThreshold: 0, // The minimum brightness for a glyph to still be considered visible - brightnessDecay: 1.0, // The rate at which glyphs light up and dim - ditherMagnitude: 0.05, // The magnitude of the random per-pixel dimming - fallSpeed: 0.3, // The speed the raindrops progress downwards - glyphEdgeCrop: 0.0, // The border around a glyph in a font texture that should be cropped out - glyphHeightToWidth: 1, // The aspect ratio of glyphs - glyphVerticalSpacing: 1, // The ratio of the vertical distance between glyphs to their height - glyphFlip: false, // Whether to horizontally reflect the glyphs - glyphRotation: 0, // An angle to rotate the glyphs. Currently limited to 90° increments - hasThunder: false, // An effect that adds dramatic lightning flashes - isPolar: false, // Whether the glyphs arc across the screen or sit in a standard grid - rippleTypeName: null, // The variety of the ripple effect - rippleThickness: 0.2, // The thickness of the ripple effect - rippleScale: 30, // The size of the ripple effect - rippleSpeed: 0.2, // The rate at which the ripple effect progresses - numColumns: 80, // The maximum dimension of the glyph grid - density: 1, // In volumetric mode, the number of actual columns compared to the grid - palette: [ - // The color palette that glyph brightness is color mapped to - { color: hsl(0.3, 0.9, 0.0), at: 0.0 }, - { color: hsl(0.3, 0.9, 0.2), at: 0.2 }, - { color: hsl(0.3, 0.9, 0.7), at: 0.7 }, - { color: hsl(0.3, 0.9, 0.8), at: 0.8 }, - ], - raindropLength: 0.75, // Adjusts the frequency of raindrops (and their length) in a column - slant: 0, // The angle at which rain falls; the orientation of the glyph grid - resolution: 0.75, // An overall scale multiplier - useHalfFloat: false, - renderer: "regl", // The preferred web graphics API - suppressWarnings: false, // Whether to show warnings to visitors on load - isometric: false, - useHoloplay: false, - loops: false, - skipIntro: true, - testFix: null, + font: "matrixcode", + effect: "palette", // The name of the effect to apply at the end of the process— mainly handles coloration + baseTexture: null, // The name of the texture to apply to the base layer of the glyphs + glintTexture: null, // The name of the texture to apply to the glint layer of the glyphs + useCamera: false, + backgroundColor: hsl(0, 0, 0), // The color "behind" the glyphs + isolateCursor: true, // Whether the "cursor"— the brightest glyph at the bottom of a raindrop— has its own color + cursorColor: hsl(0.242, 1, 0.73), // The color of the cursor + cursorIntensity: 2, // The intensity of the cursor + isolateGlint: false, // Whether the "glint"— highlights on certain symbols in the font— should appear + glintColor: hsl(0, 0, 1), // The color of the glint + glintIntensity: 1, // The intensity of the glint + volumetric: false, // A mode where the raindrops appear in perspective + animationSpeed: 1, // The global rate that all animations progress + fps: 60, // The target frame rate (frames per second) of the effect + forwardSpeed: 0.25, // The speed volumetric rain approaches the eye + bloomStrength: 0.7, // The intensity of the bloom + bloomSize: 0.4, // The amount the bloom calculation is scaled + highPassThreshold: 0.1, // The minimum brightness that is still blurred + cycleSpeed: 0.03, // The speed glyphs change + cycleFrameSkip: 1, // The global minimum number of frames between glyphs cycling + baseBrightness: -0.5, // The brightness of the glyphs, before any effects are applied + baseContrast: 1.1, // The contrast of the glyphs, before any effects are applied + glintBrightness: -1.5, // The brightness of the glints, before any effects are applied + glintContrast: 2.5, // The contrast of the glints, before any effects are applied + brightnessOverride: 0.0, // A global override to the brightness of displayed glyphs. Only used if it is > 0. + brightnessThreshold: 0, // The minimum brightness for a glyph to still be considered visible + brightnessDecay: 1.0, // The rate at which glyphs light up and dim + ditherMagnitude: 0.05, // The magnitude of the random per-pixel dimming + fallSpeed: 0.3, // The speed the raindrops progress downwards + glyphEdgeCrop: 0.0, // The border around a glyph in a font texture that should be cropped out + glyphHeightToWidth: 1, // The aspect ratio of glyphs + glyphVerticalSpacing: 1, // The ratio of the vertical distance between glyphs to their height + glyphFlip: false, // Whether to horizontally reflect the glyphs + glyphRotation: 0, // An angle to rotate the glyphs. Currently limited to 90° increments + hasThunder: false, // An effect that adds dramatic lightning flashes + isPolar: false, // Whether the glyphs arc across the screen or sit in a standard grid + rippleTypeName: null, // The variety of the ripple effect + rippleThickness: 0.2, // The thickness of the ripple effect + rippleScale: 30, // The size of the ripple effect + rippleSpeed: 0.2, // The rate at which the ripple effect progresses + numColumns: 80, // The maximum dimension of the glyph grid + density: 1, // In volumetric mode, the number of actual columns compared to the grid + palette: [ + // The color palette that glyph brightness is color mapped to + { color: hsl(0.3, 0.9, 0.0), at: 0.0 }, + { color: hsl(0.3, 0.9, 0.2), at: 0.2 }, + { color: hsl(0.3, 0.9, 0.7), at: 0.7 }, + { color: hsl(0.3, 0.9, 0.8), at: 0.8 }, + ], + raindropLength: 0.75, // Adjusts the frequency of raindrops (and their length) in a column + slant: 0, // The angle at which rain falls; the orientation of the glyph grid + resolution: 0.75, // An overall scale multiplier + useHalfFloat: false, + renderer: "regl", // The preferred web graphics API + suppressWarnings: false, // Whether to show warnings to visitors on load + isometric: false, + useHoloplay: false, + loops: false, + skipIntro: true, + testFix: null, }; const versions = { - classic: {}, - megacity: { - font: "megacity", - animationSpeed: 0.5, - numColumns: 40, - }, - neomatrixology: { - font: "neomatrixology", - animationSpeed: 0.8, - numColumns: 40, - palette: [ - { color: hsl(0.15, 0.9, 0.0), at: 0.0 }, - { color: hsl(0.15, 0.9, 0.2), at: 0.2 }, - { color: hsl(0.15, 0.9, 0.7), at: 0.7 }, - { color: hsl(0.15, 0.9, 0.8), at: 0.8 }, - ], - cursorColor: hsl(0.167, 1, 0.75), - cursorIntensity: 2, - }, - operator: { - cursorColor: hsl(0.375, 1, 0.66), - cursorIntensity: 3, - bloomSize: 0.6, - bloomStrength: 0.75, - highPassThreshold: 0.0, - cycleSpeed: 0.01, - cycleFrameSkip: 8, - brightnessOverride: 0.22, - brightnessThreshold: 0, - fallSpeed: 0.6, - glyphEdgeCrop: 0.15, - glyphHeightToWidth: 1.35, - rippleTypeName: "box", - numColumns: 108, - palette: [ - { color: hsl(0.4, 0.8, 0.0), at: 0.0 }, - { color: hsl(0.4, 0.8, 0.5), at: 0.5 }, - { color: hsl(0.4, 0.8, 1.0), at: 1.0 }, - ], - raindropLength: 1.5, - }, - nightmare: { - font: "gothic", - isolateCursor: false, - highPassThreshold: 0.7, - baseBrightness: -0.8, - brightnessDecay: 0.75, - fallSpeed: 1.2, - hasThunder: true, - numColumns: 60, - cycleSpeed: 0.35, - palette: [ - { color: hsl(0.0, 1.0, 0.0), at: 0.0 }, - { color: hsl(0.0, 1.0, 0.2), at: 0.2 }, - { color: hsl(0.0, 1.0, 0.4), at: 0.4 }, - { color: hsl(0.1, 1.0, 0.7), at: 0.7 }, - { color: hsl(0.2, 1.0, 1.0), at: 1.0 }, - ], - raindropLength: 0.5, - slant: (22.5 * Math.PI) / 180, - }, - paradise: { - font: "coptic", - isolateCursor: false, - bloomStrength: 1, - highPassThreshold: 0, - cycleSpeed: 0.005, - baseBrightness: -1.3, - baseContrast: 2, - brightnessDecay: 0.05, - fallSpeed: 0.02, - isPolar: true, - rippleTypeName: "circle", - rippleSpeed: 0.1, - numColumns: 40, - palette: [ - { color: hsl(0.0, 0.0, 0.0), at: 0.0 }, - { color: hsl(0.0, 0.8, 0.3), at: 0.3 }, - { color: hsl(0.1, 0.8, 0.5), at: 0.5 }, - { color: hsl(0.1, 1.0, 0.6), at: 0.6 }, - { color: hsl(0.1, 1.0, 0.9), at: 0.9 }, - ], - raindropLength: 0.4, - }, - resurrections: { - font: "resurrections", - glyphEdgeCrop: 0.1, - cursorColor: hsl(0.292, 1, 0.8), - cursorIntensity: 2, - baseBrightness: -0.7, - baseContrast: 1.17, - highPassThreshold: 0, - numColumns: 70, - cycleSpeed: 0.03, - bloomStrength: 0.7, - fallSpeed: 0.3, - palette: [ - { color: hsl(0.375, 0.9, 0.0), at: 0.0 }, - { color: hsl(0.375, 1.0, 0.6), at: 0.92 }, - { color: hsl(0.375, 1.0, 1.0), at: 1.0 }, - ], - }, - trinity: { - font: "resurrections", - glintTexture: "metal", - baseTexture: "pixels", - glyphEdgeCrop: 0.1, - cursorColor: hsl(0.292, 1, 0.8), - cursorIntensity: 2, - isolateGlint: true, - glintColor: hsl(0.131, 1, 0.6), - glintIntensity: 3, - glintBrightness: -0.5, - glintContrast: 1.5, - baseBrightness: -0.4, - baseContrast: 1.5, - highPassThreshold: 0, - numColumns: 60, - cycleSpeed: 0.03, - bloomStrength: 0.7, - fallSpeed: 0.3, - palette: [ - { color: hsl(0.37, 0.6, 0.0), at: 0.0 }, - { color: hsl(0.37, 0.6, 0.5), at: 1.0 }, - ], - cycleSpeed: 0.01, - volumetric: true, - forwardSpeed: 0.2, - raindropLength: 0.3, - density: 0.75, - }, - morpheus: { - font: "resurrections", - glintTexture: "mesh", - baseTexture: "metal", - glyphEdgeCrop: 0.1, - cursorColor: hsl(0.333, 1, 0.85), - cursorIntensity: 2, - isolateGlint: true, - glintColor: hsl(0.4, 1, 0.5), - glintIntensity: 2, - glintBrightness: -1.5, - glintContrast: 3, - baseBrightness: -0.3, - baseContrast: 1.5, - highPassThreshold: 0, - numColumns: 60, - cycleSpeed: 0.03, - bloomStrength: 0.7, - fallSpeed: 0.3, - palette: [ - { color: hsl(0.97, 0.6, 0.0), at: 0.0 }, - { color: hsl(0.97, 0.6, 0.5), at: 1.0 }, - ], - cycleSpeed: 0.015, - volumetric: true, - forwardSpeed: 0.1, - raindropLength: 0.4, - density: 0.75, - }, - bugs: { - font: "resurrections", - glintTexture: "sand", - baseTexture: "metal", - glyphEdgeCrop: 0.1, - cursorColor: hsl(0.619, 1, 0.65), - cursorIntensity: 2, - isolateGlint: true, - glintColor: hsl(0.625, 1, 0.6), - glintIntensity: 3, - glintBrightness: -1, - glintContrast: 3, - baseBrightness: -0.3, - baseContrast: 1.5, - highPassThreshold: 0, - numColumns: 60, - cycleSpeed: 0.03, - bloomStrength: 0.7, - fallSpeed: 0.3, - palette: [ - { color: hsl(0.12, 0.6, 0.0), at: 0.0 }, - { color: hsl(0.14, 0.6, 0.5), at: 1.0 }, - ], - cycleSpeed: 0.01, - volumetric: true, - forwardSpeed: 0.4, - raindropLength: 0.3, - density: 0.75, - }, - palimpsest: { - font: "huberfishA", - isolateCursor: false, - bloomStrength: 0.2, - numColumns: 40, - raindropLength: 1.2, - cycleFrameSkip: 3, - fallSpeed: 0.5, - slant: Math.PI * -0.0625, - palette: [ - { color: hsl(0.15, 0.25, 0.9), at: 0.0 }, - { color: hsl(0.6, 0.8, 0.1), at: 0.4 }, - ], - }, - twilight: { - font: "huberfishD", - cursorColor: hsl(0.167, 1, 0.8), - cursorIntensity: 1.5, - bloomStrength: 0.1, - numColumns: 50, - raindropLength: 0.9, - fallSpeed: 0.1, - highPassThreshold: 0.0, - palette: [ - { color: hsl(0.6, 1.0, 0.05), at: 0.0 }, - { color: hsl(0.6, 0.8, 0.1), at: 0.1 }, - { color: hsl(0.88, 0.8, 0.5), at: 0.5 }, - { color: hsl(0.15, 1.0, 0.6), at: 0.8 }, - // { color: hsl(0.1, 1.0, 0.9), at: 1.0 }, - ], - }, + classic: {}, + megacity: { + font: "megacity", + animationSpeed: 0.5, + numColumns: 40, + }, + neomatrixology: { + font: "neomatrixology", + animationSpeed: 0.8, + numColumns: 40, + palette: [ + { color: hsl(0.15, 0.9, 0.0), at: 0.0 }, + { color: hsl(0.15, 0.9, 0.2), at: 0.2 }, + { color: hsl(0.15, 0.9, 0.7), at: 0.7 }, + { color: hsl(0.15, 0.9, 0.8), at: 0.8 }, + ], + cursorColor: hsl(0.167, 1, 0.75), + cursorIntensity: 2, + }, + operator: { + cursorColor: hsl(0.375, 1, 0.66), + cursorIntensity: 3, + bloomSize: 0.6, + bloomStrength: 0.75, + highPassThreshold: 0.0, + cycleSpeed: 0.01, + cycleFrameSkip: 8, + brightnessOverride: 0.22, + brightnessThreshold: 0, + fallSpeed: 0.6, + glyphEdgeCrop: 0.15, + glyphHeightToWidth: 1.35, + rippleTypeName: "box", + numColumns: 108, + palette: [ + { color: hsl(0.4, 0.8, 0.0), at: 0.0 }, + { color: hsl(0.4, 0.8, 0.5), at: 0.5 }, + { color: hsl(0.4, 0.8, 1.0), at: 1.0 }, + ], + raindropLength: 1.5, + }, + nightmare: { + font: "gothic", + isolateCursor: false, + highPassThreshold: 0.7, + baseBrightness: -0.8, + brightnessDecay: 0.75, + fallSpeed: 1.2, + hasThunder: true, + numColumns: 60, + cycleSpeed: 0.35, + palette: [ + { color: hsl(0.0, 1.0, 0.0), at: 0.0 }, + { color: hsl(0.0, 1.0, 0.2), at: 0.2 }, + { color: hsl(0.0, 1.0, 0.4), at: 0.4 }, + { color: hsl(0.1, 1.0, 0.7), at: 0.7 }, + { color: hsl(0.2, 1.0, 1.0), at: 1.0 }, + ], + raindropLength: 0.5, + slant: (22.5 * Math.PI) / 180, + }, + paradise: { + font: "coptic", + isolateCursor: false, + bloomStrength: 1, + highPassThreshold: 0, + cycleSpeed: 0.005, + baseBrightness: -1.3, + baseContrast: 2, + brightnessDecay: 0.05, + fallSpeed: 0.02, + isPolar: true, + rippleTypeName: "circle", + rippleSpeed: 0.1, + numColumns: 40, + palette: [ + { color: hsl(0.0, 0.0, 0.0), at: 0.0 }, + { color: hsl(0.0, 0.8, 0.3), at: 0.3 }, + { color: hsl(0.1, 0.8, 0.5), at: 0.5 }, + { color: hsl(0.1, 1.0, 0.6), at: 0.6 }, + { color: hsl(0.1, 1.0, 0.9), at: 0.9 }, + ], + raindropLength: 0.4, + }, + resurrections: { + font: "resurrections", + glyphEdgeCrop: 0.1, + cursorColor: hsl(0.292, 1, 0.8), + cursorIntensity: 2, + baseBrightness: -0.7, + baseContrast: 1.17, + highPassThreshold: 0, + numColumns: 70, + cycleSpeed: 0.03, + bloomStrength: 0.7, + fallSpeed: 0.3, + palette: [ + { color: hsl(0.375, 0.9, 0.0), at: 0.0 }, + { color: hsl(0.375, 1.0, 0.6), at: 0.92 }, + { color: hsl(0.375, 1.0, 1.0), at: 1.0 }, + ], + }, + trinity: { + font: "resurrections", + glintTexture: "metal", + baseTexture: "pixels", + glyphEdgeCrop: 0.1, + cursorColor: hsl(0.292, 1, 0.8), + cursorIntensity: 2, + isolateGlint: true, + glintColor: hsl(0.131, 1, 0.6), + glintIntensity: 3, + glintBrightness: -0.5, + glintContrast: 1.5, + baseBrightness: -0.4, + baseContrast: 1.5, + highPassThreshold: 0, + numColumns: 60, + cycleSpeed: 0.03, + bloomStrength: 0.7, + fallSpeed: 0.3, + palette: [ + { color: hsl(0.37, 0.6, 0.0), at: 0.0 }, + { color: hsl(0.37, 0.6, 0.5), at: 1.0 }, + ], + cycleSpeed: 0.01, + volumetric: true, + forwardSpeed: 0.2, + raindropLength: 0.3, + density: 0.75, + }, + morpheus: { + font: "resurrections", + glintTexture: "mesh", + baseTexture: "metal", + glyphEdgeCrop: 0.1, + cursorColor: hsl(0.333, 1, 0.85), + cursorIntensity: 2, + isolateGlint: true, + glintColor: hsl(0.4, 1, 0.5), + glintIntensity: 2, + glintBrightness: -1.5, + glintContrast: 3, + baseBrightness: -0.3, + baseContrast: 1.5, + highPassThreshold: 0, + numColumns: 60, + cycleSpeed: 0.03, + bloomStrength: 0.7, + fallSpeed: 0.3, + palette: [ + { color: hsl(0.97, 0.6, 0.0), at: 0.0 }, + { color: hsl(0.97, 0.6, 0.5), at: 1.0 }, + ], + cycleSpeed: 0.015, + volumetric: true, + forwardSpeed: 0.1, + raindropLength: 0.4, + density: 0.75, + }, + bugs: { + font: "resurrections", + glintTexture: "sand", + baseTexture: "metal", + glyphEdgeCrop: 0.1, + cursorColor: hsl(0.619, 1, 0.65), + cursorIntensity: 2, + isolateGlint: true, + glintColor: hsl(0.625, 1, 0.6), + glintIntensity: 3, + glintBrightness: -1, + glintContrast: 3, + baseBrightness: -0.3, + baseContrast: 1.5, + highPassThreshold: 0, + numColumns: 60, + cycleSpeed: 0.03, + bloomStrength: 0.7, + fallSpeed: 0.3, + palette: [ + { color: hsl(0.12, 0.6, 0.0), at: 0.0 }, + { color: hsl(0.14, 0.6, 0.5), at: 1.0 }, + ], + cycleSpeed: 0.01, + volumetric: true, + forwardSpeed: 0.4, + raindropLength: 0.3, + density: 0.75, + }, + palimpsest: { + font: "huberfishA", + isolateCursor: false, + bloomStrength: 0.2, + numColumns: 40, + raindropLength: 1.2, + cycleFrameSkip: 3, + fallSpeed: 0.5, + slant: Math.PI * -0.0625, + palette: [ + { color: hsl(0.15, 0.25, 0.9), at: 0.0 }, + { color: hsl(0.6, 0.8, 0.1), at: 0.4 }, + ], + }, + twilight: { + font: "huberfishD", + cursorColor: hsl(0.167, 1, 0.8), + cursorIntensity: 1.5, + bloomStrength: 0.1, + numColumns: 50, + raindropLength: 0.9, + fallSpeed: 0.1, + highPassThreshold: 0.0, + palette: [ + { color: hsl(0.6, 1.0, 0.05), at: 0.0 }, + { color: hsl(0.6, 0.8, 0.1), at: 0.1 }, + { color: hsl(0.88, 0.8, 0.5), at: 0.5 }, + { color: hsl(0.15, 1.0, 0.6), at: 0.8 }, + // { color: hsl(0.1, 1.0, 0.9), at: 1.0 }, + ], + }, - holoplay: { - font: "resurrections", - glintTexture: "metal", - glyphEdgeCrop: 0.1, - cursorColor: hsl(0.292, 1, 0.8), - cursorIntensity: 2, - isolateGlint: true, - glintColor: hsl(0.131, 1, 0.6), - glintIntensity: 3, - glintBrightness: -0.5, - glintContrast: 1.5, - baseBrightness: -0.4, - baseContrast: 1.5, - highPassThreshold: 0, - cycleSpeed: 0.03, - bloomStrength: 0.7, - fallSpeed: 0.3, - palette: [ - { color: hsl(0.37, 0.6, 0.0), at: 0.0 }, - { color: hsl(0.37, 0.6, 0.5), at: 1.0 }, - ], - cycleSpeed: 0.01, - raindropLength: 0.3, + holoplay: { + font: "resurrections", + glintTexture: "metal", + glyphEdgeCrop: 0.1, + cursorColor: hsl(0.292, 1, 0.8), + cursorIntensity: 2, + isolateGlint: true, + glintColor: hsl(0.131, 1, 0.6), + glintIntensity: 3, + glintBrightness: -0.5, + glintContrast: 1.5, + baseBrightness: -0.4, + baseContrast: 1.5, + highPassThreshold: 0, + cycleSpeed: 0.03, + bloomStrength: 0.7, + fallSpeed: 0.3, + palette: [ + { color: hsl(0.37, 0.6, 0.0), at: 0.0 }, + { color: hsl(0.37, 0.6, 0.5), at: 1.0 }, + ], + cycleSpeed: 0.01, + raindropLength: 0.3, - renderer: "regl", - numColumns: 20, - ditherMagnitude: 0, - bloomStrength: 0, - volumetric: true, - forwardSpeed: 0, - density: 3, - useHoloplay: true, - }, + renderer: "regl", + numColumns: 20, + ditherMagnitude: 0, + bloomStrength: 0, + volumetric: true, + forwardSpeed: 0, + density: 3, + useHoloplay: true, + }, - ["3d"]: { - volumetric: true, - fallSpeed: 0.5, - cycleSpeed: 0.03, - baseBrightness: -0.9, - baseContrast: 1.5, - raindropLength: 0.3, - }, + ["3d"]: { + volumetric: true, + fallSpeed: 0.5, + cycleSpeed: 0.03, + baseBrightness: -0.9, + baseContrast: 1.5, + raindropLength: 0.3, + }, }; versions.throwback = versions.operator; versions.updated = versions.resurrections; @@ -419,119 +419,118 @@ versions["1999"] = versions.operator; versions["2003"] = versions.classic; versions["2021"] = versions.resurrections; -const range = (f, min = -Infinity, max = Infinity) => - Math.max(min, Math.min(max, f)); +const range = (f, min = -Infinity, max = Infinity) => Math.max(min, Math.min(max, f)); const nullNaN = (f) => (isNaN(f) ? null : f); const isTrue = (s) => s.toLowerCase().includes("true"); const parseColor = (isHSL) => (s) => ({ - space: isHSL ? "hsl" : "rgb", - values: s.split(",").map(parseFloat), + space: isHSL ? "hsl" : "rgb", + values: s.split(",").map(parseFloat), }); const parseColors = (isHSL) => (s) => { - const values = s.split(",").map(parseFloat); - const space = isHSL ? "hsl" : "rgb"; - return Array(Math.floor(values.length / 3)) - .fill() - .map((_, index) => ({ - space, - values: values.slice(index * 3, (index + 1) * 3), - })); + const values = s.split(",").map(parseFloat); + const space = isHSL ? "hsl" : "rgb"; + return Array(Math.floor(values.length / 3)) + .fill() + .map((_, index) => ({ + space, + values: values.slice(index * 3, (index + 1) * 3), + })); }; const parsePalette = (isHSL) => (s) => { - const values = s.split(",").map(parseFloat); - const space = isHSL ? "hsl" : "rgb"; - return Array(Math.floor(values.length / 4)) - .fill() - .map((_, index) => { - const colorValues = values.slice(index * 4, (index + 1) * 4); - return { - color: { - space, - values: colorValues.slice(0, 3), - }, - at: colorValues[3], - }; - }); + const values = s.split(",").map(parseFloat); + const space = isHSL ? "hsl" : "rgb"; + return Array(Math.floor(values.length / 4)) + .fill() + .map((_, index) => { + const colorValues = values.slice(index * 4, (index + 1) * 4); + return { + color: { + space, + values: colorValues.slice(0, 3), + }, + at: colorValues[3], + }; + }); }; const paramMapping = { - testFix: { key: "testFix", parser: (s) => s }, - version: { key: "version", parser: (s) => s }, - font: { key: "font", parser: (s) => s }, - effect: { key: "effect", parser: (s) => s }, - camera: { key: "useCamera", parser: isTrue }, - 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)), - }, - bloomStrength: { - key: "bloomStrength", - parser: (s) => nullNaN(range(parseFloat(s), 0, 1)), - }, - ditherMagnitude: { - key: "ditherMagnitude", - parser: (s) => nullNaN(range(parseFloat(s), 0, 1)), - }, - url: { key: "bgURL", parser: (s) => s }, - palette: { key: "palette", parser: parsePalette(false) }, - stripeColors: { key: "stripeColors", parser: parseColors(false) }, - backgroundColor: { key: "backgroundColor", parser: parseColor(false) }, - cursorColor: { key: "cursorColor", parser: parseColor(false) }, - glintColor: { key: "glintColor", parser: parseColor(false) }, + testFix: { key: "testFix", parser: (s) => s }, + version: { key: "version", parser: (s) => s }, + font: { key: "font", parser: (s) => s }, + effect: { key: "effect", parser: (s) => s }, + camera: { key: "useCamera", parser: isTrue }, + 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)), + }, + bloomStrength: { + key: "bloomStrength", + parser: (s) => nullNaN(range(parseFloat(s), 0, 1)), + }, + ditherMagnitude: { + key: "ditherMagnitude", + parser: (s) => nullNaN(range(parseFloat(s), 0, 1)), + }, + url: { key: "bgURL", parser: (s) => s }, + palette: { key: "palette", parser: parsePalette(false) }, + stripeColors: { key: "stripeColors", parser: parseColors(false) }, + backgroundColor: { key: "backgroundColor", parser: parseColor(false) }, + cursorColor: { key: "cursorColor", parser: parseColor(false) }, + glintColor: { key: "glintColor", parser: parseColor(false) }, - paletteHSL: { key: "palette", parser: parsePalette(true) }, - stripeHSL: { key: "stripeColors", parser: parseColors(true) }, - backgroundHSL: { key: "backgroundColor", parser: parseColor(true) }, - cursorHSL: { key: "cursorColor", parser: parseColor(true) }, - glintHSL: { key: "glintColor", parser: parseColor(true) }, + paletteHSL: { key: "palette", parser: parsePalette(true) }, + stripeHSL: { key: "stripeColors", parser: parseColors(true) }, + backgroundHSL: { key: "backgroundColor", parser: parseColor(true) }, + cursorHSL: { key: "cursorColor", parser: parseColor(true) }, + glintHSL: { key: "glintColor", parser: parseColor(true) }, - cursorIntensity: { - key: "cursorIntensity", - parser: (s) => nullNaN(range(parseFloat(s), 0, Infinity)), - }, + cursorIntensity: { + key: "cursorIntensity", + parser: (s) => nullNaN(range(parseFloat(s), 0, Infinity)), + }, - glyphIntensity: { - key: "glyphIntensity", - parser: (s) => nullNaN(range(parseFloat(s), 0, Infinity)), - }, + glyphIntensity: { + key: "glyphIntensity", + parser: (s) => nullNaN(range(parseFloat(s), 0, Infinity)), + }, - volumetric: { key: "volumetric", parser: isTrue }, - glyphFlip: { key: "glyphFlip", parser: isTrue }, - glyphRotation: { - key: "glyphRotation", - parser: (s) => nullNaN(range(parseFloat(s), 0, Infinity)), - }, - loops: { key: "loops", parser: isTrue }, - fps: { key: "fps", parser: (s) => nullNaN(range(parseFloat(s), 0, 60)) }, - skipIntro: { key: "skipIntro", parser: isTrue }, - renderer: { key: "renderer", parser: (s) => s }, - suppressWarnings: { key: "suppressWarnings", parser: isTrue }, - once: { key: "once", parser: isTrue }, - isometric: { key: "isometric", parser: isTrue }, + volumetric: { key: "volumetric", parser: isTrue }, + glyphFlip: { key: "glyphFlip", parser: isTrue }, + glyphRotation: { + key: "glyphRotation", + parser: (s) => nullNaN(range(parseFloat(s), 0, Infinity)), + }, + loops: { key: "loops", parser: isTrue }, + fps: { key: "fps", parser: (s) => nullNaN(range(parseFloat(s), 0, 60)) }, + skipIntro: { key: "skipIntro", parser: isTrue }, + renderer: { key: "renderer", parser: (s) => s }, + suppressWarnings: { key: "suppressWarnings", parser: isTrue }, + once: { key: "once", parser: isTrue }, + isometric: { key: "isometric", parser: isTrue }, }; paramMapping.paletteRGB = paramMapping.palette; @@ -546,72 +545,57 @@ paramMapping.angle = paramMapping.slant; paramMapping.colors = paramMapping.stripeColors; export default (urlParams) => { - const validParams = Object.fromEntries( - Object.entries(urlParams) - .filter(([key]) => key in paramMapping) - .map(([key, value]) => [ - paramMapping[key].key, - paramMapping[key].parser(value), - ]) - .filter(([_, value]) => value != null) - ); + const validParams = Object.fromEntries( + Object.entries(urlParams) + .filter(([key]) => key in paramMapping) + .map(([key, value]) => [paramMapping[key].key, paramMapping[key].parser(value)]) + .filter(([_, value]) => value != null), + ); - if (validParams.effect != null) { - if (validParams.cursorColor == null) { - validParams.cursorColor = hsl(0, 0, 1); - } + if (validParams.effect != null) { + if (validParams.cursorColor == null) { + validParams.cursorColor = hsl(0, 0, 1); + } - if (validParams.cursorIntensity == null) { - validParams.cursorIntensity = 2; - } + if (validParams.cursorIntensity == null) { + validParams.cursorIntensity = 2; + } - if (validParams.glintColor == null) { - validParams.glintColor = hsl(0, 0, 1); - } + if (validParams.glintColor == null) { + validParams.glintColor = hsl(0, 0, 1); + } - if (validParams.glyphIntensity == null) { - validParams.glyphIntensity = 1; - } - } + if (validParams.glyphIntensity == null) { + validParams.glyphIntensity = 1; + } + } - const version = - validParams.version in versions - ? versions[validParams.version] - : versions.classic; - const fontName = [validParams.font, version.font, defaults.font].find( - (name) => name in fonts - ); - const font = fonts[fontName]; + const version = + validParams.version in versions ? versions[validParams.version] : versions.classic; + const fontName = [validParams.font, version.font, defaults.font].find((name) => name in fonts); + const font = fonts[fontName]; - const baseTextureURL = - textureURLs[ - [version.baseTexture, defaults.baseTexture].find( - (name) => name in textureURLs - ) - ]; - const hasBaseTexture = baseTextureURL != null; - const glintTextureURL = - textureURLs[ - [version.glintTexture, defaults.glintTexture].find( - (name) => name in textureURLs - ) - ]; - const hasGlintTexture = glintTextureURL != null; + const baseTextureURL = + textureURLs[[version.baseTexture, defaults.baseTexture].find((name) => name in textureURLs)]; + const hasBaseTexture = baseTextureURL != null; + const glintTextureURL = + textureURLs[[version.glintTexture, defaults.glintTexture].find((name) => name in textureURLs)]; + const hasGlintTexture = glintTextureURL != null; - const config = { - ...defaults, - ...version, - ...font, - ...validParams, - baseTextureURL, - glintTextureURL, - hasBaseTexture, - hasGlintTexture, - }; + const config = { + ...defaults, + ...version, + ...font, + ...validParams, + baseTextureURL, + glintTextureURL, + hasBaseTexture, + hasGlintTexture, + }; - if (config.bloomSize <= 0) { - config.bloomStrength = 0; - } + if (config.bloomSize <= 0) { + config.bloomStrength = 0; + } - return config; + return config; }; diff --git a/js/webgpu/bloomPass.js b/js/webgpu/bloomPass.js index 78c3a4e..a0767fb 100644 --- a/js/webgpu/bloomPass.js +++ b/js/webgpu/bloomPass.js @@ -1,5 +1,11 @@ import { structs } from "../../lib/gpu-buffer.js"; -import { makeComputeTarget, loadShader, makeUniformBuffer, makeBindGroup, makePass } from "./utils.js"; +import { + makeComputeTarget, + loadShader, + makeUniformBuffer, + makeBindGroup, + makePass, +} from "./utils.js"; // const makePyramid = makeComputeTarget; @@ -20,8 +26,8 @@ const makePyramid = (device, size, pyramidHeight) => .map((_, index) => makeComputeTarget( device, - size.map((x) => Math.floor(x * 2 ** -index)) - ) + size.map((x) => Math.floor(x * 2 ** -index)), + ), ); const destroyPyramid = (pyramid) => pyramid?.forEach((texture) => texture.destroy()); @@ -47,7 +53,10 @@ export default ({ config, device }) => { return makePass("No Bloom", null, (size, inputs) => ({ ...inputs, bloom: emptyTexture })); } - const assets = [loadShader(device, "shaders/wgsl/bloomBlur.wgsl"), loadShader(device, "shaders/wgsl/bloomCombine.wgsl")]; + const assets = [ + loadShader(device, "shaders/wgsl/bloomBlur.wgsl"), + loadShader(device, "shaders/wgsl/bloomCombine.wgsl"), + ]; const linearSampler = device.createSampler({ magFilter: "linear", @@ -122,12 +131,27 @@ export default ({ config, device }) => { for (let i = 0; i < pyramidHeight; i++) { const hBlurPyramidView = makePyramidLevelView(hBlurPyramid, i); const vBlurPyramidView = makePyramidLevelView(vBlurPyramid, i); - hBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [hBlurBuffer, linearSampler, srcView, hBlurPyramidView]); - vBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [vBlurBuffer, linearSampler, hBlurPyramidView, vBlurPyramidView]); + hBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [ + hBlurBuffer, + linearSampler, + srcView, + hBlurPyramidView, + ]); + vBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [ + vBlurBuffer, + linearSampler, + hBlurPyramidView, + vBlurPyramidView, + ]); srcView = hBlurPyramidView; } - combineBindGroup = makeBindGroup(device, combinePipeline, 0, [combineBuffer, linearSampler, ...makePyramidViews(vBlurPyramid), output.createView()]); + combineBindGroup = makeBindGroup(device, combinePipeline, 0, [ + combineBuffer, + linearSampler, + ...makePyramidViews(vBlurPyramid), + output.createView(), + ]); return { ...inputs, @@ -144,7 +168,11 @@ export default ({ config, device }) => { computePass.setPipeline(blurPipeline); for (let i = 0; i < pyramidHeight; i++) { - const dispatchSize = [Math.ceil(Math.floor(scaledScreenSize[0] * 2 ** -i) / 32), Math.floor(Math.floor(scaledScreenSize[1] * 2 ** -i)), 1]; + const dispatchSize = [ + Math.ceil(Math.floor(scaledScreenSize[0] * 2 ** -i) / 32), + Math.floor(Math.floor(scaledScreenSize[1] * 2 ** -i)), + 1, + ]; computePass.setBindGroup(0, hBlurBindGroups[i]); computePass.dispatchWorkgroups(...dispatchSize); computePass.setBindGroup(0, vBlurBindGroups[i]); diff --git a/js/webgpu/endPass.js b/js/webgpu/endPass.js index 5030aad..0f25241 100644 --- a/js/webgpu/endPass.js +++ b/js/webgpu/endPass.js @@ -45,7 +45,10 @@ export default ({ device, canvasFormat, canvasContext }) => { })(); const build = (size, inputs) => { - renderBindGroup = makeBindGroup(device, renderPipeline, 0, [nearestSampler, inputs.primary.createView()]); + renderBindGroup = makeBindGroup(device, renderPipeline, 0, [ + nearestSampler, + inputs.primary.createView(), + ]); return null; }; diff --git a/js/webgpu/imagePass.js b/js/webgpu/imagePass.js index ee859de..1f21005 100644 --- a/js/webgpu/imagePass.js +++ b/js/webgpu/imagePass.js @@ -1,9 +1,17 @@ import { structs } from "../../lib/gpu-buffer.js"; -import { makeComputeTarget, makeUniformBuffer, loadTexture, loadShader, makeBindGroup, makePass } from "./utils.js"; +import { + makeComputeTarget, + makeUniformBuffer, + loadTexture, + loadShader, + makeBindGroup, + makePass, +} from "./utils.js"; // Multiplies the rendered rain and bloom by a loaded in image -const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg"; +const defaultBGURL = + "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg"; export default ({ config, device }) => { const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL; diff --git a/js/webgpu/main.js b/js/webgpu/main.js index aa5ce42..43c2699 100644 --- a/js/webgpu/main.js +++ b/js/webgpu/main.js @@ -74,7 +74,10 @@ export default async (canvas, config) => { const cameraTex = device.createTexture({ size: cameraSize, format: "rgba8unorm", - usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, }); const context = { @@ -90,7 +93,12 @@ export default async (canvas, config) => { }; const effectName = config.effect in effects ? config.effect : "palette"; - const pipeline = await makePipeline(context, [makeRain, makeBloomPass, effects[effectName], makeEndPass]); + const pipeline = await makePipeline(context, [ + makeRain, + makeBloomPass, + effects[effectName], + makeEndPass, + ]); const targetFrameTimeMilliseconds = 1000 / config.fps; let frames = 0; @@ -107,7 +115,8 @@ export default async (canvas, config) => { last = start; } - const shouldRender = config.fps >= 60 || now - last >= targetFrameTimeMilliseconds || config.once; + const shouldRender = + config.fps >= 60 || now - last >= targetFrameTimeMilliseconds || config.once; if (shouldRender) { while (now - targetFrameTimeMilliseconds > last) { last += targetFrameTimeMilliseconds; @@ -125,10 +134,18 @@ export default async (canvas, config) => { } if (config.useCamera) { - device.queue.copyExternalImageToTexture({ source: cameraCanvas }, { texture: cameraTex }, cameraSize); + device.queue.copyExternalImageToTexture( + { source: cameraCanvas }, + { texture: cameraTex }, + cameraSize, + ); } - device.queue.writeBuffer(timeBuffer, 0, timeUniforms.toBuffer({ seconds: (now - start) / 1000, frames })); + device.queue.writeBuffer( + timeBuffer, + 0, + timeUniforms.toBuffer({ seconds: (now - start) / 1000, frames }), + ); frames++; const encoder = device.createCommandEncoder(); diff --git a/js/webgpu/mirrorPass.js b/js/webgpu/mirrorPass.js index 9de12af..e5ec465 100644 --- a/js/webgpu/mirrorPass.js +++ b/js/webgpu/mirrorPass.js @@ -1,5 +1,11 @@ import { structs } from "../../lib/gpu-buffer.js"; -import { makeComputeTarget, makeUniformBuffer, loadShader, makeBindGroup, makePass } from "./utils.js"; +import { + makeComputeTarget, + makeUniformBuffer, + loadShader, + makeBindGroup, + makePass, +} from "./utils.js"; let start; const numTouches = 5; @@ -77,7 +83,11 @@ export default ({ config, device, cameraTex, cameraAspectRatio, timeBuffer }) => ]); const screenAspectRatio = size[0] / size[1]; - device.queue.writeBuffer(sceneBuffer, 0, sceneUniforms.toBuffer({ screenAspectRatio, cameraAspectRatio })); + device.queue.writeBuffer( + sceneBuffer, + 0, + sceneUniforms.toBuffer({ screenAspectRatio, cameraAspectRatio }), + ); return { primary: output }; }; diff --git a/js/webgpu/palettePass.js b/js/webgpu/palettePass.js index 7e2b479..0f19caa 100644 --- a/js/webgpu/palettePass.js +++ b/js/webgpu/palettePass.js @@ -1,6 +1,12 @@ import colorToRGB from "../colorToRGB.js"; import { structs } from "../../lib/gpu-buffer.js"; -import { loadShader, makeUniformBuffer, makeBindGroup, makeComputeTarget, makePass } from "./utils.js"; +import { + loadShader, + makeUniformBuffer, + makeBindGroup, + makeComputeTarget, + makePass, +} from "./utils.js"; // Maps the brightness of the rendered rain and bloom to colors // in a linear gradient buffer generated from the passed-in color sequence diff --git a/js/webgpu/rainPass.js b/js/webgpu/rainPass.js index da0f9e3..67bed55 100644 --- a/js/webgpu/rainPass.js +++ b/js/webgpu/rainPass.js @@ -1,5 +1,12 @@ import { structs } from "../../lib/gpu-buffer.js"; -import { makeRenderTarget, loadTexture, loadShader, makeUniformBuffer, makeBindGroup, makePass } from "./utils.js"; +import { + makeRenderTarget, + loadTexture, + loadShader, + makeUniformBuffer, + makeBindGroup, + makePass, +} from "./utils.js"; const rippleTypes = { box: 0, @@ -46,7 +53,10 @@ export default ({ config, device, timeBuffer }) => { // rather than a single quad for our geometry const numQuads = config.volumetric ? numCells : 1; - const glyphTransform = mat2.fromScaling(mat2.create(), vec2.fromValues(config.glyphFlip ? -1 : 1, 1)); + const glyphTransform = mat2.fromScaling( + mat2.create(), + vec2.fromValues(config.glyphFlip ? -1 : 1, 1), + ); mat2.rotate(glyphTransform, glyphTransform, (config.glyphRotation * Math.PI) / 180); const transform = mat4.create(); @@ -98,10 +108,18 @@ export default ({ config, device, timeBuffer }) => { let highPassOutput; const loaded = (async () => { - const [glyphMSDFTexture, glintMSDFTexture, baseTexture, glintTexture, rainShader] = await Promise.all(assets); + const [glyphMSDFTexture, glintMSDFTexture, baseTexture, glintTexture, rainShader] = + await Promise.all(assets); const rainShaderUniforms = structs.from(rainShader.code); - configBuffer = makeConfigBuffer(device, rainShaderUniforms.Config, config, density, gridSize, glyphTransform); + configBuffer = makeConfigBuffer( + device, + rainShaderUniforms.Config, + config, + density, + gridSize, + glyphTransform, + ); const introCellsBuffer = device.createBuffer({ size: gridSize[0] * rainShaderUniforms.IntroCell.minSize, @@ -168,8 +186,17 @@ export default ({ config, device, timeBuffer }) => { }), ]); - introBindGroup = makeBindGroup(device, introPipeline, 0, [configBuffer, timeBuffer, introCellsBuffer]); - computeBindGroup = makeBindGroup(device, computePipeline, 0, [configBuffer, timeBuffer, cellsBuffer, introCellsBuffer]); + introBindGroup = makeBindGroup(device, introPipeline, 0, [ + configBuffer, + timeBuffer, + introCellsBuffer, + ]); + computeBindGroup = makeBindGroup(device, computePipeline, 0, [ + configBuffer, + timeBuffer, + cellsBuffer, + introCellsBuffer, + ]); renderBindGroup = makeBindGroup(device, renderPipeline, 0, [ configBuffer, timeBuffer, @@ -196,7 +223,11 @@ export default ({ config, device, timeBuffer }) => { mat4.perspectiveZO(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000); } const screenSize = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1]; - device.queue.writeBuffer(sceneBuffer, 0, sceneUniforms.toBuffer({ screenSize, camera, transform })); + device.queue.writeBuffer( + sceneBuffer, + 0, + sceneUniforms.toBuffer({ screenSize, camera, transform }), + ); // Update output?.destroy(); diff --git a/js/webgpu/stripePass.js b/js/webgpu/stripePass.js index 3f709ce..c168c51 100644 --- a/js/webgpu/stripePass.js +++ b/js/webgpu/stripePass.js @@ -1,6 +1,13 @@ import colorToRGB from "../colorToRGB.js"; import { structs } from "../../lib/gpu-buffer.js"; -import { loadShader, make1DTexture, makeUniformBuffer, makeBindGroup, makeComputeTarget, makePass } from "./utils.js"; +import { + loadShader, + make1DTexture, + makeUniformBuffer, + makeBindGroup, + makeComputeTarget, + makePass, +} from "./utils.js"; // Multiplies the rendered rain and bloom by a 1D gradient texture // generated from the passed-in color sequence @@ -38,10 +45,15 @@ const numVerticesPerQuad = 2 * 3; export default ({ config, device, timeBuffer }) => { // Expand and convert stripe colors into 1D texture data - const stripeColors = "stripeColors" in config ? config.stripeColors : config.effect === "pride" ? prideStripeColors : transPrideStripeColors; + const stripeColors = + "stripeColors" in config + ? config.stripeColors + : config.effect === "pride" + ? prideStripeColors + : transPrideStripeColors; const stripeTex = make1DTexture( device, - stripeColors.map((color) => [...colorToRGB(color), 1]) + stripeColors.map((color) => [...colorToRGB(color), 1]), ); const linearSampler = device.createSampler({ diff --git a/js/webgpu/utils.js b/js/webgpu/utils.js index f0b63d7..bca4885 100644 --- a/js/webgpu/utils.js +++ b/js/webgpu/utils.js @@ -3,7 +3,10 @@ const loadTexture = async (device, url) => { return device.createTexture({ size: [1, 1, 1], format: "rgba8unorm", - usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, }); } @@ -15,7 +18,10 @@ const loadTexture = async (device, url) => { const texture = device.createTexture({ size, format: "rgba8unorm", - usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, }); device.queue.copyExternalImageToTexture({ source, flipY: true }, { texture }, size); @@ -28,7 +34,11 @@ const makeRenderTarget = (device, size, format, mipLevelCount = 1) => size: [...size, 1], mipLevelCount, format, - usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_SRC | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, }); const makeComputeTarget = (device, size, mipLevelCount = 1) => @@ -36,7 +46,11 @@ const makeComputeTarget = (device, size, mipLevelCount = 1) => size: [...size, 1], mipLevelCount, format: "rgba8unorm", - usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING, + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_SRC | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.STORAGE_BINDING, }); const loadShader = async (device, url) => { @@ -105,4 +119,14 @@ const makePipeline = async (context, steps) => { }; }; -export { makeRenderTarget, makeComputeTarget, make1DTexture, loadTexture, loadShader, makeUniformBuffer, makePass, makePipeline, makeBindGroup }; +export { + makeRenderTarget, + makeComputeTarget, + make1DTexture, + loadTexture, + loadShader, + makeUniformBuffer, + makePass, + makePipeline, + makeBindGroup, +}; diff --git a/rollup.config.mjs b/rollup.config.mjs index f9b8e96..8293306 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -8,37 +8,37 @@ import terser from "@rollup/plugin-terser"; import { string } from "rollup-plugin-string"; export default { - input: "js/Matrix.js", - external: ["react", "react-dom"], // keep them out of your bundle - plugins: [ - peerDepsExternal(), // auto-exclude peerDeps - nodeResolve(), // so Rollup can find deps in node_modules - string({ include: ["**/*.glsl"] }), - url({ include: ["**/*.png"], limit: 0 }), - babel({ - exclude: "node_modules/**", // transpile JSX - babelHelpers: "bundled", - presets: ["@babel/preset-react", "@babel/preset-env"], - }), - commonjs(), // turn CJS deps into ES - terser({ - sourceMap: false, // <- suppress .map generation - format: { comments: false }, - }), - visualizer({ - filename: "dist/stats.html", - gzipSize: true, - brotliSize: true, - includeAssets: true, - }), // bundle-size treemap - ], - output: [ - { - file: "dist/index.cjs.js", - format: "cjs", - exports: "named", - sourcemap: false, - }, - // { file: 'dist/index.esm.js', format: 'es' } // optional ESM build - ], + input: "js/Matrix.js", + external: ["react", "react-dom"], // keep them out of your bundle + plugins: [ + peerDepsExternal(), // auto-exclude peerDeps + nodeResolve(), // so Rollup can find deps in node_modules + string({ include: ["**/*.glsl"] }), + url({ include: ["**/*.png"], limit: 0 }), + babel({ + exclude: "node_modules/**", // transpile JSX + babelHelpers: "bundled", + presets: ["@babel/preset-react", "@babel/preset-env"], + }), + commonjs(), // turn CJS deps into ES + terser({ + sourceMap: false, // <- suppress .map generation + format: { comments: false }, + }), + visualizer({ + filename: "dist/stats.html", + gzipSize: true, + brotliSize: true, + includeAssets: true, + }), // bundle-size treemap + ], + output: [ + { + file: "dist/index.cjs.js", + format: "cjs", + exports: "named", + sourcemap: false, + }, + // { file: 'dist/index.esm.js', format: 'es' } // optional ESM build + ], }; diff --git a/webpack.config.js b/webpack.config.js index 29a593c..ebafb05 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,52 +3,52 @@ const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { - mode: "development", - entry: path.resolve(__dirname, "./js/index.js"), - module: { - rules: [ - { - test: /\.(js|jsx)$/, - exclude: /node_modules/, - use: ["babel-loader"], - }, - { - test: /\.css$/, - use: ["style-loader", "css-loader"], - }, - { - test: /\.(png|j?g|svg|gif)?$/, - type: "asset/resource", - }, - { - test: /\.(glsl|frag|vert)$/i, - exclude: /node_modules/, - use: ["raw-loader"], - }, - ], - }, - resolve: { - extensions: ["*", ".js", ".jsx"], - }, - output: { - path: path.resolve(__dirname, "./dist"), - filename: "[name].bundle.js", - clean: true, - }, - devtool: "inline-source-map", - plugins: [ - new HtmlWebpackPlugin({ - template: path.resolve(__dirname, "public/index.html"), - filename: "index.html", - }), - new webpack.HotModuleReplacementPlugin(), - ], - devServer: { - historyApiFallback: true, - static: path.resolve(__dirname, "./dist"), - compress: true, - hot: true, - open: true, - port: 3000, - }, + mode: "development", + entry: path.resolve(__dirname, "./js/index.js"), + module: { + rules: [ + { + test: /\.(js|jsx)$/, + exclude: /node_modules/, + use: ["babel-loader"], + }, + { + test: /\.css$/, + use: ["style-loader", "css-loader"], + }, + { + test: /\.(png|j?g|svg|gif)?$/, + type: "asset/resource", + }, + { + test: /\.(glsl|frag|vert)$/i, + exclude: /node_modules/, + use: ["raw-loader"], + }, + ], + }, + resolve: { + extensions: ["*", ".js", ".jsx"], + }, + output: { + path: path.resolve(__dirname, "./dist"), + filename: "[name].bundle.js", + clean: true, + }, + devtool: "inline-source-map", + plugins: [ + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, "public/index.html"), + filename: "index.html", + }), + new webpack.HotModuleReplacementPlugin(), + ], + devServer: { + historyApiFallback: true, + static: path.resolve(__dirname, "./dist"), + compress: true, + hot: true, + open: true, + port: 3000, + }, };