mirror of
https://github.com/xemu-project/xemu-website.git
synced 2024-11-26 21:00:33 +00:00
Animate logo
This commit is contained in:
parent
f846be7704
commit
495a4460a6
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -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
|
||||
|
6
build.sh
6
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/
|
||||
|
@ -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__':
|
||||
|
BIN
resources/logo_sdf.png
Normal file
BIN
resources/logo_sdf.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
164
templates/gl_logo.js
Normal file
164
templates/gl_logo.js
Normal file
@ -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;
|
||||
}
|
||||
})();
|
141
templates/gl_logo_frag.glsl
Normal file
141
templates/gl_logo_frag.glsl
Normal file
@ -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);
|
||||
}
|
8
templates/gl_logo_vert.glsl
Normal file
8
templates/gl_logo_vert.glsl
Normal file
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="x-shader/x-vertex" id="vertex-shader">
|
||||
{% include "gl_logo_vert.glsl" %}
|
||||
</script>
|
||||
<script type="x-shader/x-fragment" id="fragment-shader">
|
||||
{% include "gl_logo_frag.glsl" %}
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
{% include "gl_logo.js" %}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block jumbotron %}
|
||||
@ -95,7 +117,10 @@
|
||||
<img src="xbox_logo.png" class="img-fluid" width=450 />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h1 class="display-3"><img alt="xemu logo" src="/logo-green.svg" style="height: 0.8em"></h1>
|
||||
<h1 class="display-3">
|
||||
<canvas id="logo-canvas" class="gl-logo"></canvas>
|
||||
<img id="logo-fallback" alt="xemu logo" src="/logo-green.svg" style="height: 1.03em;">
|
||||
</h1>
|
||||
<h4 class="card-subtitle mb-2 text-muted">Original Xbox Emulator</h4>
|
||||
<p>
|
||||
A free and open-source application that emulates the original Microsoft
|
||||
|
Loading…
Reference in New Issue
Block a user