From 495a4460a6dea1c6ca33c046545eb53c1cba0b9c Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Mon, 14 Jun 2021 21:56:36 -0700 Subject: [PATCH] Animate logo --- .github/workflows/build.yml | 2 +- build.sh | 6 +- generate.py | 9 +- resources/logo_sdf.png | Bin 0 -> 4341 bytes templates/gl_logo.js | 164 ++++++++++++++++++++++++++++++++++ templates/gl_logo_frag.glsl | 141 +++++++++++++++++++++++++++++ templates/gl_logo_vert.glsl | 8 ++ templates/template_index.html | 27 +++++- 8 files changed, 346 insertions(+), 11 deletions(-) create mode 100644 resources/logo_sdf.png create mode 100644 templates/gl_logo.js create mode 100644 templates/gl_logo_frag.glsl create mode 100644 templates/gl_logo_vert.glsl diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 065428c7..ea9b9e1c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: mkdir env python3 -m venv env source env/bin/activate - python -m pip install tqdm jinja2==2.11.3 PyGithub + python -m pip install tqdm jinja2==2.11.3 PyGithub minify-html ./build.sh cssmin > dist/theme.css.min < dist/theme.css mv dist/theme.css.min dist/theme.css diff --git a/build.sh b/build.sh index f18ddd00..8fcc2456 100755 --- a/build.sh +++ b/build.sh @@ -19,11 +19,7 @@ echo "Compiling stylesheets" echo "Generating HTML pages" ./generate.py -cp resources/cover_front_default.png \ - resources/xbox_logo.png \ - resources/xbox_duke.png \ - resources/logo-green.svg \ - dist +cp resources/* dist rm dist/theme.css.map mkdir -p dist/webfonts/ diff --git a/generate.py b/generate.py index 1e183fc8..3647b96c 100755 --- a/generate.py +++ b/generate.py @@ -15,6 +15,7 @@ from functools import reduce from github import Github from jinja2 import Environment, FileSystemLoader from tqdm import tqdm +from minify_html import minify as minify_html output_dir = 'dist' repo_url_base = 'https://raw.githubusercontent.com/mborgerson/xemu-website/master/' @@ -227,10 +228,10 @@ def main(): os.makedirs(title_dir, exist_ok=True) title = title_lookup[title_id] with open(os.path.join(title_dir, 'index.html'), 'w') as f: - f.write(template.render( + f.write(minify_html(template.render( title=title, title_status_descriptions=title_status_descriptions - )) + ))) count += 1 print(' - Created %d title pages' % count) @@ -265,13 +266,13 @@ def main(): dorder.extend(sorted(tmap.values(),key=lambda title:title.title_name)) with open(os.path.join(output_dir, 'index.html'), 'w') as f: - f.write(template.render( + f.write(minify_html(template.render( titles=dorder, title_status_descriptions=title_status_descriptions, game_status_counts=game_status_counts, xemu_build_version=xemu_build_version, xemu_build_date=xemu_build_date - )) + ), minify_js=True, minify_css=True)) print(' - Ok') if __name__ == '__main__': diff --git a/resources/logo_sdf.png b/resources/logo_sdf.png new file mode 100644 index 0000000000000000000000000000000000000000..5a8f1a20574245a585af39dd26c837a5e90f8c09 GIT binary patch literal 4341 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 Xr!V#`w@bs{cLt!NVW{4q>KOMw_hITp literal 0 HcmV?d00001 diff --git a/templates/gl_logo.js b/templates/gl_logo.js new file mode 100644 index 00000000..786693ef --- /dev/null +++ b/templates/gl_logo.js @@ -0,0 +1,164 @@ +;(function(){ +"use strict" +window.addEventListener("load", setupWebGL, false); + +var gl, program; +var time_loc; +var buffer; +var ebuffer; +var pending_disable_fallback = true; +var restart_anim = false; +var last_started = 0; +var loaded = false; +function reset_time() { + restart_anim = true; +} + +// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL +function loadTexture(gl, url) { + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + const level = 0; + const internalFormat = gl.RGBA; + const width = 1; + const height = 1; + const border = 0; + const srcFormat = gl.RGBA; + const srcType = gl.UNSIGNED_BYTE; + const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue + gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, + width, height, border, srcFormat, srcType, + pixel); + + const image = new Image(); + image.onload = function() { + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, + srcFormat, srcType, image); + + // WebGL1 has different requirements for power of 2 images + // vs non power of 2 images so check if the image is a + // power of 2 in both dimensions. + if (isPowerOf2(image.width) && isPowerOf2(image.height)) { + // Yes, it's a power of 2. Generate mips. + gl.generateMipmap(gl.TEXTURE_2D); + } else { + // No, it's not a power of 2. Turn off mips and set + // wrapping to clamp to edge + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + } + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + loaded = true; + }; + image.src = url; + return texture; +} + +function isPowerOf2(value) { + return (value & (value - 1)) == 0; +} + +function setupWebGL (evt) { + window.removeEventListener(evt.type, setupWebGL, false); + if (!(gl = getRenderingContext())) + return; + + var source = document.querySelector("#vertex-shader").innerHTML; + var vertexShader = gl.createShader(gl.VERTEX_SHADER); + gl.shaderSource(vertexShader,source); + gl.compileShader(vertexShader); + if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { + var err_str = gl.getShaderInfoLog(vertexShader); + console.log("Vertex shader compilation failed: " + err_str); + return; + } + + source = document.querySelector("#fragment-shader").innerHTML + var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragmentShader,source); + gl.compileShader(fragmentShader); + if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { + var err_str = gl.getShaderInfoLog(fragmentShader); + console.log("Fragment shader compilation failed: " + err_str); + return; + } + + program = gl.createProgram(); + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + gl.linkProgram(program); + gl.detachShader(program, vertexShader); + gl.detachShader(program, fragmentShader); + gl.deleteShader(vertexShader); + gl.deleteShader(fragmentShader); + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + var err_str = gl.getProgramInfoLog(program); + console.log("Shader linking failed: " + err_str); + return; + } + + buffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ + -1.0, -1.0, 0.0, 0.0, + -1.0, 1.0, 0.0, 1.0, + 1.0, 1.0, 1.0, 1.0, + 1.0, -1.0, 1.0, 0.0]), gl.STATIC_DRAW); + + ebuffer = gl.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int8Array([ 0, 1, 2, 3 ]), gl.STATIC_DRAW); + + var pos_attr_loc = gl.getAttribLocation(program, "in_Position"); + gl.vertexAttribPointer(pos_attr_loc, 2, gl.FLOAT, false, 4*4, 0); + gl.enableVertexAttribArray(pos_attr_loc); + var tex_attr_loc = gl.getAttribLocation(program, "in_Texcoord"); + gl.vertexAttribPointer(tex_attr_loc, 2, gl.FLOAT, false, 4*4, 2*4); + gl.enableVertexAttribArray(tex_attr_loc); + + var tex = loadTexture(gl, "logo_sdf.png") + var tex_loc = gl.getUniformLocation(program, "tex"); + time_loc = gl.getUniformLocation(program, "iTime"); + + gl.useProgram(program); + + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); + requestAnimationFrame(render); +} + +function render(ts) { + if (!loaded) { + requestAnimationFrame(render); + return; + } + if (pending_disable_fallback) { + document.getElementById("logo-canvas").style.visibility = "visible"; + document.getElementById("logo-fallback").style.visibility = "hidden"; + document.getElementById("logo-canvas").onclick = reset_time; + pending_disable_fallback = false; + } + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + var offset = 6000.0; + if (restart_anim) { + last_started = ts+offset; + restart_anim = false; + } + gl.uniform1f(time_loc, (ts-last_started+offset)/1000.0); + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_BYTE, 0); + requestAnimationFrame(render); +} + +function getRenderingContext() { + var canvas = document.querySelector("canvas"); + canvas.width = 512; + canvas.height = 512; + var gl = canvas.getContext("webgl"); + if (!gl) { + return null; + } + return gl; +} +})(); diff --git a/templates/gl_logo_frag.glsl b/templates/gl_logo_frag.glsl new file mode 100644 index 00000000..ed2b5f7a --- /dev/null +++ b/templates/gl_logo_frag.glsl @@ -0,0 +1,141 @@ +#version 100 +precision highp float; +uniform sampler2D tex; +uniform float iTime; +varying vec2 Texcoord; +const float scale = 1.4; +const vec2 texSize = vec2(128.0,128.0); +float pxRange = 6.0; +const vec4 bgColor = vec4(0.); +const vec4 fgColor = vec4(0.259, 0.890, 0.208, 1.); +const vec4 particleColor = vec4(0.259, 0.890, 0.208, 1.); +const int numParticles = 40; +const float duration = 1.0; +const float pause = 8.0; +const vec4 textPos = vec4(0.01, 0, 0.98, 0.125); +const float lineWidth = 0.15; + +// Thanks to: https://www.shadertoy.com/view/Xl2SRR +float random(float co) +{ + return fract(sin(co*12.989) * 43758.545); +} + +float median(float r, float g, float b) +{ + return max(min(r, g), min(max(r, g), b)); +} + +// Thanks to: https://www.iquilezles.org/www/articles/functions/functions.htm +float quaImpulse( float k, float x ) +{ + return 2.0*sqrt(k)*x/(1.0+k*x*x); +} + +float getCurrentTime() +{ + return mod(iTime, duration+pause)/duration; +} + +float getBox(vec2 uv, float x, float width) +{ + float rhs = sign(clamp(x-uv.x+width, 0., 1.)); + float lhs = sign(clamp(x-uv.x, 0., 1.)); + return rhs-lhs; +} + +float getSweepingLinePos() +{ + return getCurrentTime()-lineWidth+textPos.x; +} + +float getSweepingLine(vec2 uv) +{ + return getBox(uv, getSweepingLinePos(), lineWidth); +} + +float getGradients(vec2 uv) +{ + float t = getCurrentTime(); + float gw = lineWidth/2.; + float left = getBox(uv, getSweepingLinePos() - gw, gw)*smoothstep(0., 1., (gw + lineWidth - (t - uv.x + textPos.x))/lineWidth); + float right = getBox(uv, getSweepingLinePos() + lineWidth, gw)*smoothstep(0., 1., (gw + (t - uv.x + textPos.x))/lineWidth); + float gradient_y = smoothstep(0.8, 1., 1.-abs(0.5-uv.y)); + return (left + right) * gradient_y; +} + +// Note: Does not include offset, added in getParticlePosition +vec2 getParticleInitialPosition(int i) +{ + vec2 pos; + pos.x = float(i)/float(numParticles-1); // Even + pos.y = sign(random(float(i)) - 0.1); // Top biased + return pos*textPos.zw; +} + +float getParticleTime(int i) +{ + // Compute based on initial x due to sweeping reveal + return getCurrentTime()-getParticleInitialPosition(i).x; +} + +float getParticleIntensity(int i) +{ + float lifespan = 1.0 + 0.4*(random(float(i*44))-0.5); + float alive = clamp(sign(getParticleTime(i)), 0., 1.); + return alive*clamp(lifespan-getParticleTime(i), 0., 1.); +} + +vec2 getParticlePosition(int i) +{ + float pt = getParticleTime(i); + float alive = clamp(sign(pt), 0., 1.); + float falloff = 10.; + float impulse = quaImpulse(falloff, pt+0.8)+0.2; + vec2 pos = getParticleInitialPosition(i); + + vec2 velocity; + // Move mostly right, but sometimes left + velocity.x = sign(random(float(i+3000))-0.2); + velocity.x *= impulse*1.25*(0.00 + random(float(i+62))); + // Move vertically in whatever direction we spawned in + velocity.y = sign(pos.y); + velocity.y *= impulse*1.40*(0.25 + 1.2*random(float(i+62))); + return pos + alive * velocity * pt + vec2(textPos.x, 0.5); // Offset to center +} + +float getParticles(vec2 uv) +{ + // Compute contribution from all particles to this frag + float c = 0.; + for (int j = 0; j < numParticles; j++) { + vec2 pos = getParticlePosition(j); + float d = distance(uv, pos); + c += (1.-smoothstep(0.01, 0.01035,d))*getParticleIntensity(j); + } + + return c; +} + +void main() +{ + vec2 uv = gl_FragCoord.xy/vec2(512,512); + float scale = 1.4; + uv -= 0.5 * (1.-1./scale); + uv *= scale; + vec2 pos = uv; + + vec3 msd = texture2D(tex, vec2(pos.x, 1.-pos.y)).rgb; + float sd = median(msd.r, msd.g, msd.b); + float screenPxDistance = pxRange*(sd - 0.5); + float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); + vec4 fill_color = mix(bgColor, fgColor, opacity); + float outline = clamp(screenPxDistance + 1.6, 0., 1.); + outline -= clamp(screenPxDistance - 1.6, 0., 1.); + outline = smoothstep(0.5, 1., outline); + + vec4 line_color = mix(bgColor, fgColor, outline); + gl_FragColor = mix(fill_color, line_color, getSweepingLine(uv)); + gl_FragColor += mix(vec4(0), particleColor, getParticles(uv)); + gl_FragColor += 2.*vec4(1.)*getBox(uv, textPos.x, textPos.z)*getGradients(uv); +} diff --git a/templates/gl_logo_vert.glsl b/templates/gl_logo_vert.glsl new file mode 100644 index 00000000..88f824e5 --- /dev/null +++ b/templates/gl_logo_vert.glsl @@ -0,0 +1,8 @@ +#version 100 +attribute vec2 in_Position; +attribute vec2 in_Texcoord; +varying vec2 Texcoord; +void main() { + Texcoord = in_Texcoord; + gl_Position = vec4(in_Position, 0.0, 1.0); +} diff --git a/templates/template_index.html b/templates/template_index.html index 1f78b357..c5c85f67 100644 --- a/templates/template_index.html +++ b/templates/template_index.html @@ -83,8 +83,30 @@ display: inline-block; margin-right: 1em; } + +canvas { + display: block; + visibility: hidden; + width: 256px; + height: 256px; + margin-left: -38px; + margin-bottom: -177px; + margin-top: -80px; + padding: 0; + border: none; +} + + + + {% endblock %} {% block jumbotron %} @@ -95,7 +117,10 @@
-

xemu logo

+

+ + xemu logo +

Original Xbox Emulator

A free and open-source application that emulates the original Microsoft