From d03cd685e111678e704571d15307a388fd88cbfc Mon Sep 17 00:00:00 2001 From: Rezmason Date: Sun, 17 Feb 2019 18:42:08 -0800 Subject: [PATCH] - Added "slant" param to readme - getParam now supports synonyms - Renamed "dropLength" URL param to "raindropLength", keeping around support for dropLength URL param - Renamed "texture" to "fontTexture" - Changed "isSlanted" boolean to "slant" angle, adding slant/angle URL param, converted from degrees to radians - Renamed "numGlyphColumns" to "numFontColumns" - Renamed "showRTT" to "showComputationTexture" - Random glyphs are now based on a simple sine scramble - Renaming "now" and "delta" to "time" and "deltaTime" - glyphCycleSpeed is no longer premultiplied; it is now a number between 0 and 1, much easier to visualize --- README.md | 3 +- index.html | 46 +++++++++++++-------- js/MatrixRenderer.js | 98 ++++++++++++++++++++++---------------------- 3 files changed, 79 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index fde69ea..dd33422 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,8 @@ Now you know link fu. Here's a list of customization options: - **version** - the version of the Matrix to simulate. Can be "paradise", "nightmare" or "1999" (default). - **width** - the number of columns (and rows) to draw. Default is 80. -- **dropLength** - the vertical scale of "raindrops" in the columns. Can be any number, even negative! Default is 1.0. +- **slant** - which angle is up, in degrees. Default is 0. +- **raindropLength** - the vertical scale of "raindrops" in the columns. Can be any number, even negative! Default is 1.0. - **animationSpeed** - the overall speed of the animation. Can be any number, even negative! Default is 1.0. - **fallSpeed** - the speed of the rain. Can be any number, even negative! Default is 1.0. - **cycleSpeed** - the speed that the glyphs change their symbol. Can be any number, even negative! Default is 1.0. diff --git a/index.html b/index.html index 0f1b5fc..35e4163 100644 --- a/index.html +++ b/index.html @@ -26,8 +26,8 @@ const versions = { paradise: { - texture: './coptic_msdf.png', - dropLength: 0.5, + fontTexture: './coptic_msdf.png', + raindropLength: 0.5, glyphSequenceLength: 32, bloom: { strength: 4, @@ -45,13 +45,13 @@ cycleSpeed: 0.05, hasThunder: false, hasSun: true, - isSlanted: false, + slant: 0, isPolar: true, numColumns: 50 }, nightmare: { - texture: './gothic_msdf.png', - dropLength: 0.6, + fontTexture: './gothic_msdf.png', + raindropLength: 0.6, glyphSequenceLength: 27, bloom: { strength: 2, @@ -69,13 +69,13 @@ cycleSpeed: 0.02, hasThunder: true, hasSun: false, - isSlanted: true, + slant: 360 / 16, isPolar: false, numColumns: 60 }, ["1999"]: { - texture: './matrixcode_msdf.png', - dropLength: 1, + fontTexture: './matrixcode_msdf.png', + raindropLength: 1, glyphSequenceLength: 57, bloom: { strength: 2, @@ -110,14 +110,23 @@ cycleSpeed: 1, hasThunder: false, hasSun: false, - isSlanted: false, + slant: 0, isPolar: false, numColumns: 80 } }; const urlParams = new Map(window.location.href.replace(/^[^\?]+\?/, "").split("&").map(pair => pair.split("="))); - const getParam = (key, defaultValue) => urlParams.has(key) ? urlParams.get(key) : defaultValue; + const getParam = (keyOrKeys, defaultValue) => { + if (Array.isArray(keyOrKeys)) { + const keys = keyOrKeys; + const key = keys.find(key => urlParams.has(key)); + return key != null ? urlParams.get(key) : defaultValue; + } else { + const key = keyOrKeys; + return urlParams.has(key) ? urlParams.get(key) : defaultValue; + } + }; const version = versions[getParam("version", "1999")] || versions["1999"]; @@ -126,9 +135,10 @@ const fallSpeed = parseFloat(getParam("fallSpeed", 1)) * version.fallSpeed; const cycleSpeed = parseFloat(getParam("cycleSpeed", 1)) * version.cycleSpeed; const numColumns = parseInt(getParam("width", version.numColumns)); - const dropLength = parseFloat(getParam("dropLength", version.dropLength)); - const numGlyphColumns = 8; + const raindropLength = parseFloat(getParam(["raindropLength", "dropLength"], version.raindropLength)); + const numFontColumns = 8; const glyphSequenceLength = version.glyphSequenceLength; + const slant = parseFloat(getParam(["slant", "angle"], version.slant)) * Math.PI / 180; const effect = getParam("effect", "plain"); @@ -142,20 +152,20 @@ element.appendChild(renderer.domElement); const composer = new THREE.EffectComposer( renderer ); - const texture = new THREE.TextureLoader().load( version.texture ); + const fontTexture = new THREE.TextureLoader().load( version.fontTexture ); - const matrixRenderer = makeMatrixRenderer(renderer, texture, { + const matrixRenderer = makeMatrixRenderer(renderer, fontTexture, { sharpness, numColumns, animationSpeed, fallSpeed, cycleSpeed, glyphSequenceLength, - numGlyphColumns, + numFontColumns, hasThunder: version.hasThunder, hasSun: version.hasSun, isPolar: version.isPolar, - isSlanted: version.isSlanted, - showRTT: effect === "none", - dropLength + slant, + showComputationTexture: effect === "none", + raindropLength }); matrixRenderer.pass.renderToScreen = false; diff --git a/js/MatrixRenderer.js b/js/MatrixRenderer.js index 0e34480..1f3d1d9 100644 --- a/js/MatrixRenderer.js +++ b/js/MatrixRenderer.js @@ -3,13 +3,13 @@ const makeMatrixRenderer = (renderer, texture, { numColumns, animationSpeed, fallSpeed, cycleSpeed, glyphSequenceLength, - numGlyphColumns, + numFontColumns, hasThunder, hasSun, isPolar, - isSlanted, - showRTT, - dropLength + slant, + showComputationTexture, + raindropLength }) => { const matrixRenderer = {}; const camera = new THREE.OrthographicCamera( -0.5, 0.5, 0.5, -0.5, 0.0001, 10000 ); @@ -17,9 +17,12 @@ const makeMatrixRenderer = (renderer, texture, { const gpuCompute = new GPUComputationRenderer( numColumns, numColumns, renderer ); const glyphValue = gpuCompute.createTexture(); const pixels = glyphValue.image.data; + + const scramble = i => Math.sin(i) * 0.5 + 0.5; + for (let i = 0; i < numColumns * numColumns; i++) { pixels[i * 4 + 0] = 0; - pixels[i * 4 + 1] = Math.random(); + pixels[i * 4 + 1] = showComputationTexture ? 0 : scramble(i); pixels[i * 4 + 2] = 0; pixels[i * 4 + 3] = 0; } @@ -31,15 +34,15 @@ const glyphVariable = gpuCompute.addVariable( #define PI 3.14159265359 #define SQRT_2 1.4142135623730951 #define SQRT_5 2.23606797749979 - uniform float now; - uniform float delta; + uniform float time; + uniform float deltaTime; uniform float animationSpeed; uniform float fallSpeed; uniform float cycleSpeed; uniform float brightnessChangeBias; uniform float glyphSequenceLength; - uniform float numGlyphColumns; - uniform float dropLength; + uniform float numFontColumns; + uniform float raindropLength; highp float rand( const in vec2 uv ) { const highp float a = 12.9898, b = 78.233, c = 43758.5453; @@ -62,11 +65,11 @@ const glyphVariable = gpuCompute.addVariable( vec4 data = texture2D( glyph, uv ); float brightness = data.r; - float cycle = data.g; + float glyphCycle = data.g; - float simTime = now * 0.0005 * animationSpeed; + float simTime = time * 0.0005 * animationSpeed; float columnTime = (columnTimeOffset * 1000.0 + simTime * fallSpeed) * (0.5 + columnSpeedOffset * 0.5) + (sin(simTime * fallSpeed * 2.0 * columnSpeedOffset) * 0.2); - float glyphTime = (gl_FragCoord.y * 0.01 + columnTime) / dropLength; + float glyphTime = (gl_FragCoord.y * 0.01 + columnTime) / raindropLength; float value = 1.0 - fract((glyphTime + 0.3 * sin(SQRT_2 * glyphTime) + 0.2 * sin(SQRT_5 * glyphTime))); @@ -92,23 +95,23 @@ const glyphVariable = gpuCompute.addVariable( brightness = mix(brightness, newBrightness, brightnessChangeBias); #endif - float glyphCycleSpeed = (brightness < 0.0) ? 0.0 : delta * cycleSpeed * 0.2 * pow(1.0 - brightness, 4.0); - cycle = fract(cycle + glyphCycleSpeed); - float symbol = floor(glyphSequenceLength * cycle); - float symbolX = mod(symbol, numGlyphColumns); - float symbolY = ((numGlyphColumns - 1.0) - (symbol - symbolX) / numGlyphColumns); + float glyphCycleSpeed = (brightness <= 0.0) ? 0.0 : pow(1.0 - brightness, 4.0); + glyphCycle = fract(glyphCycle + deltaTime * cycleSpeed * 0.2 * glyphCycleSpeed); + float symbol = floor(glyphSequenceLength * glyphCycle); + float symbolX = mod(symbol, numFontColumns); + float symbolY = ((numFontColumns - 1.0) - (symbol - symbolX) / numFontColumns); gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); gl_FragColor.r = brightness; - gl_FragColor.g = cycle; + gl_FragColor.g = glyphCycle; - #ifdef showRTT + #ifdef showComputationTexture // Better use of the blue channel, for show and tell - gl_FragColor.b = min(1.0, glyphCycleSpeed * 500.0); + gl_FragColor.b = min(1.0, glyphCycleSpeed); gl_FragColor.a = 1.0; #else - gl_FragColor.b = symbolX / numGlyphColumns; - gl_FragColor.a = symbolY / numGlyphColumns; + gl_FragColor.b = symbolX / numFontColumns; + gl_FragColor.a = symbolY / numFontColumns; #endif } ` @@ -119,14 +122,14 @@ const glyphVariable = gpuCompute.addVariable( const brightnessChangeBias = (animationSpeed * fallSpeed) == 0 ? 1 : Math.min(1, Math.abs(animationSpeed * fallSpeed)); Object.assign(glyphVariable.material.uniforms, { - now: { type: "f", value: 0 }, - delta: { type: "f", value: 0.01 }, + time: { type: "f", value: 0 }, + deltaTime: { type: "f", value: 0.01 }, animationSpeed: { type: "f", value: animationSpeed }, fallSpeed: { type: "f", value: fallSpeed }, cycleSpeed: {type: "f", value: cycleSpeed }, glyphSequenceLength: { type: "f", value: glyphSequenceLength }, - numGlyphColumns: {type: "f", value: numGlyphColumns }, - dropLength: {type: "f", value: dropLength }, + numFontColumns: {type: "f", value: numFontColumns }, + raindropLength: {type: "f", value: raindropLength }, brightnessChangeBias: { type: "f", value: brightnessChangeBias }, }); if (hasThunder) { @@ -135,8 +138,8 @@ const glyphVariable = gpuCompute.addVariable( if (hasSun) { glyphVariable.material.defines.hasSun = 1.0; } - if (showRTT) { - glyphVariable.material.defines.showRTT = 1.0; + if (showComputationTexture) { + glyphVariable.material.defines.showComputationTexture = 1.0; } const error = gpuCompute.init(); @@ -154,8 +157,9 @@ const glyphVariable = gpuCompute.addVariable( msdf: { type: "t", value: texture }, numColumns: {type: "f", value: numColumns}, sharpness: { type: "f", value: sharpness }, - numGlyphColumns: {type: "f", value: numGlyphColumns}, + numFontColumns: {type: "f", value: numFontColumns}, resolution: {type: "v2", value: new THREE.Vector2() }, + slant: {type: "v2", value: new THREE.Vector2(Math.cos(slant), Math.sin(slant)) } }, vertexShader: ` attribute vec2 uv; @@ -179,7 +183,8 @@ const glyphVariable = gpuCompute.addVariable( uniform sampler2D msdf; uniform sampler2D glyphs; uniform float numColumns; - uniform float numGlyphColumns; + uniform float numFontColumns; + uniform vec2 slant; varying vec2 vUV; float median(float r, float g, float b) { @@ -189,23 +194,22 @@ const glyphVariable = gpuCompute.addVariable( void main() { vec2 uv = vUV; + + uv = vec2( + (uv.x - 0.5) * slant.x + (uv.y - 0.5) * slant.y, + (uv.y - 0.5) * slant.x - (uv.x - 0.5) * slant.y + ) * 0.75 + 0.5; + #ifdef isPolar - vec2 diff = vUV - vec2(0.5, 1.25); + vec2 diff = uv - vec2(0.5, 1.25); float radius = length(diff); float angle = atan(diff.y, diff.x) + PI; uv = vec2(angle / PI, 1.0 - pow(radius * 0.75, 0.6)); #endif - #ifdef isSlanted - float angle = PI * 0.125; - vec2 rotation = vec2(cos(angle), sin(angle)); - uv = vec2( - (vUV.x - 0.5) * rotation.x + (vUV.y - 0.5) * rotation.y, - (vUV.y - 0.5) * rotation.x - (vUV.x - 0.5) * rotation.y) * 0.75 + 0.5; - #endif vec4 glyph = texture2D(glyphs, uv); - #ifdef showRTT + #ifdef showComputationTexture gl_FragColor = glyph; return; #endif @@ -213,7 +217,7 @@ const glyphVariable = gpuCompute.addVariable( // Unpack the values from the glyph texture float brightness = glyph.r; vec2 symbolUV = glyph.ba; - vec4 sample = texture2D(msdf, fract(uv * numColumns) / numGlyphColumns + symbolUV); + vec4 sample = texture2D(msdf, fract(uv * numColumns) / numFontColumns + symbolUV); // The rest is straight up MSDF float sigDist = median(sample.r, sample.g, sample.b) - 0.5; @@ -239,12 +243,8 @@ const glyphVariable = gpuCompute.addVariable( mesh.material.defines.isPolar = 1.0; } - if (isSlanted) { - mesh.material.defines.isSlanted = 1.0; - } - - if (showRTT) { - mesh.material.defines.showRTT = 1.0; + if (showComputationTexture) { + mesh.material.defines.showComputationTexture = 1.0; } scene.add( mesh ); @@ -266,11 +266,11 @@ const glyphVariable = gpuCompute.addVariable( return; } - const delta = ((now - last > 1000) ? 0 : now - last) / 1000 * animationSpeed; + const deltaTime = ((now - last > 1000) ? 0 : now - last) / 1000 * animationSpeed; last = now; - glyphVariable.material.uniforms.now.value = now; - glyphVariable.material.uniforms.delta.value = delta; + glyphVariable.material.uniforms.time.value = now; + glyphVariable.material.uniforms.deltaTime.value = deltaTime; gpuCompute.compute(); renderer.render( scene, camera );