Adding glyphFlip and glyphRotation parameters.

This commit is contained in:
Rezmason
2024-09-10 21:41:59 -07:00
parent f14651c2b2
commit cee10bb1de
7 changed files with 37 additions and 14 deletions

View File

@@ -101,7 +101,7 @@ Some of the [earliest](https://github.com/ppetr/xlockmore/blob/master/modes/matr
You can customize the digital rain quite a bit by stapling "URL variables" to its URL— by putting a '?' at the end of the link above, and then chaining together words, like this: You can customize the digital rain quite a bit by stapling "URL variables" to its URL— by putting a '?' at the end of the link above, and then chaining together words, like this:
[https://rezmason.github.io/matrix/?width=100&fallSpeed=-0.1&effect=none](https://rezmason.github.io/matrix/?width=100&fallSpeed=-0.1&effect=none) [https://rezmason.github.io/matrix/?numColumns=100&fallSpeed=-0.1&slant=200&glyphRotation=180](https://rezmason.github.io/matrix/?numColumns=100&fallSpeed=-0.1&slant=200&glyphRotation=180)
Now you know link fu. Here's a list of customization options: Now you know link fu. Here's a list of customization options:
@@ -116,7 +116,9 @@ Now you know link fu. Here's a list of customization options:
- "palimpsest" is a custom version inspired by the art and sound of [Rob Dougan](https://en.wikipedia.org/wiki/Rob_Dougan)'s [Furious Angels](https://en.wikipedia.org/wiki/Furious_Angels). - "palimpsest" is a custom version inspired by the art and sound of [Rob Dougan](https://en.wikipedia.org/wiki/Rob_Dougan)'s [Furious Angels](https://en.wikipedia.org/wiki/Furious_Angels).
- `skipIntro` - whether or not to start from a blank screen. Can be "true" or "false", default is *true*. - `skipIntro` - whether or not to start from a blank screen. Can be "true" or "false", default is *true*.
- `font` - the set of glyphs to draw. Current options are "matrixcode", "resurrections", "gothic", "coptic", "huberfishA", and "huberfishD". - `font` - the set of glyphs to draw. Current options are "matrixcode", "resurrections", "gothic", "coptic", "huberfishA", and "huberfishD".
- `width` - the number of columns (and rows) to draw. Default is 80. - `numColumns` - the number of columns (and rows) to draw. Default is 80.
- `glyphFlip` - when set to "true", this flips the glyphs. Default is "false".
- `glyphRotation` - the angle to rotate the glyphs in-place, in degrees. Default is 0. I suggest angles that are multiples of 90°.
- `volumetric` - when set to "true", this renders the glyphs with depth, slowly approaching the eye. Default is "false". - `volumetric` - when set to "true", this renders the glyphs with depth, slowly approaching the eye. Default is "false".
- `density` - the number of 3D raindrops to draw, proportional to the default. Default is 1.0. - `density` - the number of 3D raindrops to draw, proportional to the default. Default is 1.0.
- `forwardSpeed` - the rate that the 3D raindrops approach. Default is 1.0. - `forwardSpeed` - the rate that the 3D raindrops approach. Default is 1.0.

View File

@@ -1,10 +1,10 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<title>Matrix digital rain</title> <title>Matrix digital rain</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="apple-mobile-web-app-capable" content="yes" /></meta> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /></meta> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, viewport-fit=cover" /> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, viewport-fit=cover" />
<style> <style>
@supports (padding-top: env(safe-area-inset-top)) { @supports (padding-top: env(safe-area-inset-top)) {

View File

@@ -101,6 +101,8 @@ const defaults = {
glyphEdgeCrop: 0.0, // The border around a glyph in a font texture that should be cropped out glyphEdgeCrop: 0.0, // The border around a glyph in a font texture that should be cropped out
glyphHeightToWidth: 1, // The aspect ratio of glyphs glyphHeightToWidth: 1, // The aspect ratio of glyphs
glyphVerticalSpacing: 1, // The ratio of the vertical distance between glyphs to their height 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 hasThunder: false, // An effect that adds dramatic lightning flashes
isPolar: false, // Whether the glyphs arc across the screen or sit in a standard grid isPolar: false, // Whether the glyphs arc across the screen or sit in a standard grid
rippleTypeName: null, // The variety of the ripple effect rippleTypeName: null, // The variety of the ripple effect
@@ -134,12 +136,12 @@ const versions = {
megacity: { megacity: {
font: "megacity", font: "megacity",
animationSpeed: 0.5, animationSpeed: 0.5,
width: 40, numColumns: 40,
}, },
neomatrixology: { neomatrixology: {
font: "neomatrixology", font: "neomatrixology",
animationSpeed: 0.8, animationSpeed: 0.8,
width: 40, numColumns: 40,
palette: [ palette: [
{ color: hsl(0.15, 0.9, 0.0), at: 0.0 }, { 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.2), at: 0.2 },
@@ -443,7 +445,6 @@ const paramMapping = {
font: { key: "font", parser: (s) => s }, font: { key: "font", parser: (s) => s },
effect: { key: "effect", parser: (s) => s }, effect: { key: "effect", parser: (s) => s },
camera: { key: "useCamera", parser: isTrue }, camera: { key: "useCamera", parser: isTrue },
width: { key: "numColumns", parser: (s) => nullNaN(parseInt(s)) },
numColumns: { key: "numColumns", parser: (s) => nullNaN(parseInt(s)) }, numColumns: { key: "numColumns", parser: (s) => nullNaN(parseInt(s)) },
density: { key: "density", parser: (s) => nullNaN(range(parseFloat(s), 0)) }, density: { key: "density", parser: (s) => nullNaN(range(parseFloat(s), 0)) },
resolution: { key: "resolution", parser: (s) => nullNaN(parseFloat(s)) }, resolution: { key: "resolution", parser: (s) => nullNaN(parseFloat(s)) },
@@ -501,6 +502,11 @@ const paramMapping = {
}, },
volumetric: { key: "volumetric", 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 }, loops: { key: "loops", parser: isTrue },
fps: { key: "fps", parser: (s) => nullNaN(range(parseFloat(s), 0, 60)) }, fps: { key: "fps", parser: (s) => nullNaN(range(parseFloat(s), 0, 60)) },
skipIntro: { key: "skipIntro", parser: isTrue }, skipIntro: { key: "skipIntro", parser: isTrue },
@@ -516,16 +522,17 @@ paramMapping.backgroundRGB = paramMapping.backgroundColor;
paramMapping.cursorRGB = paramMapping.cursorColor; paramMapping.cursorRGB = paramMapping.cursorColor;
paramMapping.glintRGB = paramMapping.glintColor; paramMapping.glintRGB = paramMapping.glintColor;
paramMapping.width = paramMapping.numColumns;
paramMapping.dropLength = paramMapping.raindropLength; paramMapping.dropLength = paramMapping.raindropLength;
paramMapping.angle = paramMapping.slant; paramMapping.angle = paramMapping.slant;
paramMapping.colors = paramMapping.stripeColors; paramMapping.colors = paramMapping.stripeColors;
export default (urlParams) => { export default (urlParams) => {
const validParams = Object.fromEntries( const validParams = Object.fromEntries(
Array.from(Object.entries(urlParams)) Object.entries(urlParams)
.filter(([key]) => key in paramMapping) .filter(([key]) => key in paramMapping)
.map(([key, value]) => [paramMapping[key].key, paramMapping[key].parser(value)]) .map(([key, value]) => [paramMapping[key].key, paramMapping[key].parser(value)])
.filter(([_, value]) => value != null) .filter(([_, value]) => value != null),
); );
if (validParams.effect != null) { if (validParams.effect != null) {

View File

@@ -31,6 +31,8 @@ const brVert = [1, 1];
const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert]; const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert];
export default ({ regl, config, lkg }) => { export default ({ regl, config, lkg }) => {
const { mat2, mat4, vec2, vec3 } = glMatrix;
// The volumetric mode multiplies the number of columns // The volumetric mode multiplies the number of columns
// to reach the desired density, and then overlaps them // to reach the desired density, and then overlaps them
const volumetric = config.volumetric; const volumetric = config.volumetric;
@@ -49,6 +51,9 @@ export default ({ regl, config, lkg }) => {
const slantScale = 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1); const slantScale = 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1);
const showDebugView = config.effect === "none"; const showDebugView = config.effect === "none";
const glyphTransform = mat2.fromScaling(mat2.create(), vec2.fromValues(config.glyphFlip ? -1 : 1, 1));
mat2.rotate(glyphTransform, glyphTransform, (config.glyphRotation * Math.PI) / 180);
const commonUniforms = { const commonUniforms = {
...extractEntries(config, ["animationSpeed", "glyphHeightToWidth", "glyphSequenceLength", "glyphTextureGridSize"]), ...extractEntries(config, ["animationSpeed", "glyphHeightToWidth", "glyphSequenceLength", "glyphTextureGridSize"]),
numColumns, numColumns,
@@ -160,6 +165,7 @@ export default ({ regl, config, lkg }) => {
"glyphEdgeCrop", "glyphEdgeCrop",
"isPolar", "isPolar",
]), ]),
glyphTransform,
density, density,
numQuadColumns, numQuadColumns,
numQuadRows, numQuadRows,
@@ -212,7 +218,6 @@ export default ({ regl, config, lkg }) => {
// Camera and transform math for the volumetric mode // Camera and transform math for the volumetric mode
const screenSize = [1, 1]; const screenSize = [1, 1];
const { mat4, vec3 } = glMatrix;
const transform = mat4.create(); const transform = mat4.create();
if (volumetric && config.isometric) { if (volumetric && config.isometric) {
mat4.rotateX(transform, transform, (Math.PI * 1) / 8); mat4.rotateX(transform, transform, (Math.PI * 1) / 8);

View File

@@ -8,7 +8,7 @@ const rippleTypes = {
const numVerticesPerQuad = 2 * 3; const numVerticesPerQuad = 2 * 3;
const makeConfigBuffer = (device, configUniforms, config, density, gridSize) => { const makeConfigBuffer = (device, configUniforms, config, density, gridSize, glyphTransform) => {
const configData = { const configData = {
...config, ...config,
gridSize, gridSize,
@@ -18,6 +18,7 @@ const makeConfigBuffer = (device, configUniforms, config, density, gridSize) =>
slantScale: 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1), slantScale: 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1),
slantVec: [Math.cos(config.slant), Math.sin(config.slant)], slantVec: [Math.cos(config.slant), Math.sin(config.slant)],
msdfPxRange: 4, msdfPxRange: 4,
glyphTransform,
}; };
// console.table(configData); // console.table(configData);
@@ -25,7 +26,7 @@ const makeConfigBuffer = (device, configUniforms, config, density, gridSize) =>
}; };
export default ({ config, device, timeBuffer }) => { export default ({ config, device, timeBuffer }) => {
const { mat4, vec3 } = glMatrix; const { mat2, mat4, vec2, vec3 } = glMatrix;
const assets = [ const assets = [
loadTexture(device, config.glyphMSDFURL), loadTexture(device, config.glyphMSDFURL),
@@ -45,6 +46,9 @@ export default ({ config, device, timeBuffer }) => {
// rather than a single quad for our geometry // rather than a single quad for our geometry
const numQuads = config.volumetric ? numCells : 1; const numQuads = config.volumetric ? numCells : 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(); const transform = mat4.create();
if (config.volumetric && config.isometric) { if (config.volumetric && config.isometric) {
mat4.rotateX(transform, transform, (Math.PI * 1) / 8); mat4.rotateX(transform, transform, (Math.PI * 1) / 8);
@@ -97,7 +101,7 @@ export default ({ config, device, timeBuffer }) => {
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); const rainShaderUniforms = structs.from(rainShader.code);
configBuffer = makeConfigBuffer(device, rainShaderUniforms.Config, config, density, gridSize); configBuffer = makeConfigBuffer(device, rainShaderUniforms.Config, config, density, gridSize, glyphTransform);
const introCellsBuffer = device.createBuffer({ const introCellsBuffer = device.createBuffer({
size: gridSize[0] * rainShaderUniforms.IntroCell.minSize, size: gridSize[0] * rainShaderUniforms.IntroCell.minSize,

View File

@@ -21,6 +21,8 @@ uniform bool showDebugView;
uniform bool volumetric; uniform bool volumetric;
uniform bool isolateCursor, isolateGlint; uniform bool isolateCursor, isolateGlint;
uniform mat2 glyphTransform;
varying vec2 vUV; varying vec2 vUV;
varying vec4 vRaindrop, vSymbol, vEffect; varying vec4 vRaindrop, vSymbol, vEffect;
varying float vDepth; varying float vDepth;
@@ -110,6 +112,7 @@ vec2 getSymbol(vec2 uv, float index) {
// resolve UV to cropped position of glyph in MSDF texture // resolve UV to cropped position of glyph in MSDF texture
uv = fract(uv * vec2(numColumns, numRows)); uv = fract(uv * vec2(numColumns, numRows));
uv -= 0.5; uv -= 0.5;
uv = glyphTransform * uv;
uv *= clamp(1. - glyphEdgeCrop, 0., 1.); uv *= clamp(1. - glyphEdgeCrop, 0., 1.);
uv += 0.5; uv += 0.5;
uv = (uv + getSymbolUV(index)) / glyphTextureGridSize; uv = (uv + getSymbolUV(index)) / glyphTextureGridSize;

View File

@@ -7,6 +7,7 @@ struct Config {
glyphSequenceLength : f32, glyphSequenceLength : f32,
glyphTextureGridSize : vec2<i32>, glyphTextureGridSize : vec2<i32>,
glyphHeightToWidth : f32, glyphHeightToWidth : f32,
glyphTransform : mat2x2<f32>,
gridSize : vec2<f32>, gridSize : vec2<f32>,
showDebugView : i32, showDebugView : i32,
@@ -493,6 +494,7 @@ fn getSymbol(cellUV : vec2<f32>, index : i32) -> vec2<f32> {
// resolve UV to cropped position of glyph in MSDF texture // resolve UV to cropped position of glyph in MSDF texture
var uv = fract(cellUV * config.gridSize); var uv = fract(cellUV * config.gridSize);
uv -= 0.5; uv -= 0.5;
uv = config.glyphTransform * uv;
uv *= clamp(1.0 - config.glyphEdgeCrop, 0.0, 1.0); uv *= clamp(1.0 - config.glyphEdgeCrop, 0.0, 1.0);
uv += 0.5; uv += 0.5;
uv = (uv + getSymbolUV(index)) / vec2<f32>(config.glyphTextureGridSize); uv = (uv + getSymbolUV(index)) / vec2<f32>(config.glyphTextureGridSize);