diff --git a/js/colorToRGB.js b/js/colorToRGB.js new file mode 100644 index 0000000..54f3f16 --- /dev/null +++ b/js/colorToRGB.js @@ -0,0 +1,12 @@ +export default ({ space, values }) => { + if (space === "rgb") { + return values; + } + const [hue, saturation, lightness] = values; + const a = saturation * Math.min(lightness, 1 - lightness); + const f = (n) => { + const k = (n + hue * 12) % 12; + return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)); + }; + return [f(0), f(8), f(4)]; +}; diff --git a/js/config.js b/js/config.js index fd0dcce..afd814f 100644 --- a/js/config.js +++ b/js/config.js @@ -59,6 +59,9 @@ const textureURLs = { metal: "assets/metal.png", }; +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 @@ -100,10 +103,10 @@ const defaults = { 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 - { hsl: [0.3, 0.9, 0.0], at: 0.0 }, - { hsl: [0.3, 0.9, 0.2], at: 0.2 }, - { hsl: [0.3, 0.9, 0.7], at: 0.7 }, - { hsl: [0.3, 0.9, 0.8], at: 0.8 }, + { 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 @@ -138,9 +141,9 @@ const versions = { rippleTypeName: "box", numColumns: 108, palette: [ - { hsl: [0.4, 0.8, 0.0], at: 0.0 }, - { hsl: [0.4, 0.8, 0.5], at: 0.5 }, - { hsl: [0.4, 0.8, 1.0], at: 1.0 }, + { 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, }, @@ -155,11 +158,11 @@ const versions = { numColumns: 60, cycleSpeed: 0.35, palette: [ - { hsl: [0.0, 1.0, 0.0], at: 0.0 }, - { hsl: [0.0, 1.0, 0.2], at: 0.2 }, - { hsl: [0.0, 1.0, 0.4], at: 0.4 }, - { hsl: [0.1, 1.0, 0.7], at: 0.7 }, - { hsl: [0.2, 1.0, 1.0], at: 1.0 }, + { 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, @@ -179,11 +182,11 @@ const versions = { rippleSpeed: 0.1, numColumns: 40, palette: [ - { hsl: [0.0, 0.0, 0.0], at: 0.0 }, - { hsl: [0.0, 0.8, 0.3], at: 0.3 }, - { hsl: [0.1, 0.8, 0.5], at: 0.5 }, - { hsl: [0.1, 1.0, 0.6], at: 0.6 }, - { hsl: [0.1, 1.0, 0.9], at: 0.9 }, + { 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, }, @@ -199,9 +202,9 @@ const versions = { bloomStrength: 0.7, fallSpeed: 0.3, palette: [ - { hsl: [0.375, 0.9, 0.0], at: 0.0 }, - { hsl: [0.375, 1.0, 0.6], at: 0.92 }, - { hsl: [0.375, 1.0, 1.0], at: 1.0 }, + { 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: { @@ -222,8 +225,8 @@ const versions = { bloomStrength: 0.7, fallSpeed: 0.3, palette: [ - { hsl: [0.37, 0.6, 0.0], at: 0.0 }, - { hsl: [0.37, 0.6, 0.5], at: 1.0 }, + { 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, @@ -249,8 +252,8 @@ const versions = { bloomStrength: 0.7, fallSpeed: 0.3, palette: [ - { hsl: [0.97, 0.6, 0.0], at: 0.0 }, - { hsl: [0.97, 0.6, 0.5], at: 1.0 }, + { 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, @@ -276,8 +279,8 @@ const versions = { bloomStrength: 0.7, fallSpeed: 0.3, palette: [ - { hsl: [0.12, 0.6, 0.0], at: 0.0 }, - { hsl: [0.14, 0.6, 0.5], at: 1.0 }, + { 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, @@ -295,8 +298,8 @@ const versions = { fallSpeed: 0.5, slant: Math.PI * -0.0625, palette: [ - { hsl: [0.15, 0.25, 0.9], at: 0.0 }, - { hsl: [0.6, 0.8, 0.1], at: 0.4 }, + { color: hsl(0.15, 0.25, 0.9), at: 0.0 }, + { color: hsl(0.6, 0.8, 0.1), at: 0.4 }, ], }, twilight: { @@ -308,11 +311,11 @@ const versions = { fallSpeed: 0.1, highPassThreshold: 0.0, palette: [ - { hsl: [0.6, 1.0, 0.05], at: 0.0 }, - { hsl: [0.6, 0.8, 0.1], at: 0.1 }, - { hsl: [0.88, 0.8, 0.5], at: 0.5 }, - { hsl: [0.15, 1.0, 0.6], at: 0.8 }, - // { hsl: [0.1, 1.0, 0.9], at: 1.0 }, + { 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 }, ], }, @@ -332,8 +335,8 @@ const versions = { bloomStrength: 0.7, fallSpeed: 0.3, palette: [ - { hsl: [0.37, 0.6, 0.0], at: 0.0 }, - { hsl: [0.37, 0.6, 0.5], at: 1.0 }, + { 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, @@ -366,6 +369,19 @@ versions["2021"] = versions.resurrections; const range = (f, min = -Infinity, max = Infinity) => Math.max(min, Math.min(max, f)); const nullNaN = (f) => (isNaN(f) ? null : f); +const clumpArray = (array, clumpSize) => + array.reduce((result, value) => { + if (result.length > 0) { + const last = result[result.length - 1]; + if (last.length < clumpSize) { + last.push(value); + return result; + } + } + result.push([value]); + return result; + }, []); + const paramMapping = { version: { key: "version", parser: (s) => s }, font: { key: "font", parser: (s) => s }, diff --git a/js/regl/palettePass.js b/js/regl/palettePass.js index 1d25620..385134a 100644 --- a/js/regl/palettePass.js +++ b/js/regl/palettePass.js @@ -1,3 +1,4 @@ +import colorToRGB from "../colorToRGB.js"; import { loadText, make1DTexture, makePassFBO, makePass } from "./utils.js"; // Maps the brightness of the rendered rain and bloom to colors @@ -5,15 +6,6 @@ import { loadText, make1DTexture, makePassFBO, makePass } from "./utils.js"; // This shader introduces noise into the renders, to avoid banding -const colorToRGB = ([hue, saturation, lightness]) => { - const a = saturation * Math.min(lightness, 1 - lightness); - const f = (n) => { - const k = (n + hue * 12) % 12; - return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)); - }; - return [f(0), f(8), f(4)]; -}; - const makePalette = (regl, entries) => { const PALETTE_SIZE = 2048; const paletteColors = Array(PALETTE_SIZE); @@ -23,7 +15,7 @@ const makePalette = (regl, entries) => { .slice() .sort((e1, e2) => e1.at - e2.at) .map((entry) => ({ - rgb: colorToRGB(entry.hsl), + 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 }); diff --git a/js/webgpu/palettePass.js b/js/webgpu/palettePass.js index dcc6a3e..c1984f6 100644 --- a/js/webgpu/palettePass.js +++ b/js/webgpu/palettePass.js @@ -1,3 +1,4 @@ +import colorToRGB from "../colorToRGB.js"; import { structs } from "../../lib/gpu-buffer.js"; import { loadShader, makeUniformBuffer, makeBindGroup, makeComputeTarget, makePass } from "./utils.js"; @@ -6,15 +7,6 @@ import { loadShader, makeUniformBuffer, makeBindGroup, makeComputeTarget, makePa // This shader introduces noise into the renders, to avoid banding -const colorToRGB = ([hue, saturation, lightness]) => { - const a = saturation * Math.min(lightness, 1 - lightness); - const f = (n) => { - const k = (n + hue * 12) % 12; - return lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)); - }; - return [f(0), f(8), f(4)]; -}; - const makePalette = (device, paletteUniforms, entries) => { const PALETTE_SIZE = 512; const paletteColors = Array(PALETTE_SIZE); @@ -24,7 +16,7 @@ const makePalette = (device, paletteUniforms, entries) => { .slice() .sort((e1, e2) => e1.at - e2.at) .map((entry) => ({ - rgb: colorToRGB(entry.hsl), + 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 });