/* * RetroArch Menu Shader Pipeline Ribbon * Port for THREE.js * https://github.com/libretro/RetroArch */ let scene, camera, renderer, ribbon const container = document.querySelector('#container') const init = () => { scene = new THREE.Scene() camera = new THREE.PerspectiveCamera( 75, 1, 0.1, 10000 ) camera.position.z = 2 renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }) container.appendChild( renderer.domElement ) ribbon = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1, 128, 128 ), new THREE.ShaderMaterial({ uniforms: { time: { value: 1.0 }, }, vertexShader: ` varying vec3 vEC; uniform float time; float iqhash(float n) { return fract(sin(n) * 43758.5453); } float noise(vec3 x) { vec3 p = floor(x); vec3 f = fract(x); f = f * f * (3.0 - 2.0 * f); float n = p.x + p.y * 57.0 + 113.0 * p.z; return mix(mix(mix(iqhash(n), iqhash(n + 1.0), f.x), mix(iqhash(n + 57.0), iqhash(n + 58.0), f.x), f.y), mix(mix(iqhash(n + 113.0), iqhash(n + 114.0), f.x), mix(iqhash(n + 170.0), iqhash(n + 171.0), f.x), f.y), f.z); } float xmb_noise2(vec3 x) { return cos(x.z * 4.0) * cos(x.z + time / 10.0 + x.x); } void main() { vec4 pos = projectionMatrix * modelViewMatrix * vec4(position, 1.0); vec3 v = vec3(pos.x, 0.0, pos.y); vec3 v2 = v; vec3 v3 = v; v.y = xmb_noise2(v2) / 8.0; v3.x -= time / 5.0; v3.x /= 4.0; v3.z -= time / 10.0; v3.y -= time / 100.0; v.z -= noise(v3 * 7.0) / 15.0; v.y -= noise(v3 * 7.0) / 15.0 + cos(v.x * 2.0 - time / 2.0) / 5.0 - 0.3; vEC = v; gl_Position = vec4(v, 1.0); } `, fragmentShader: ` uniform float time; varying vec3 vEC; void main() { const vec3 up = vec3(0.0, 0.0, 1.0); vec3 x = dFdx(vEC); vec3 y = dFdy(vEC); vec3 normal = normalize(cross(x, y)); float c = 1.0 - dot(normal, up); c = (1.0 - cos(c * c)) / 3.0; gl_FragColor = vec4(1.0, 1.0, 1.0, c * 1.5); } `, extensions: { derivatives: true, fragDepth: false, drawBuffers: false, shaderTextureLOD: false }, side: THREE.DoubleSide, transparent: true, depthTest: false, }) ) scene.add( ribbon ) resize() window.addEventListener( 'resize', resize ) } const resize = () => { const { offsetWidth, offsetHeight } = container renderer.setSize( offsetWidth, offsetHeight ) renderer.setPixelRatio( devicePixelRatio ) camera.aspect = offsetWidth / offsetHeight camera.updateProjectionMatrix() ribbon.scale.set( camera.aspect * 1.55, 0.75, 1 ) } const animate = () => { ribbon.material.uniforms.time.value += 0.01 renderer.render( scene, camera ) requestAnimationFrame( () => animate() ) } init() animate() // Inspired by the loader on the PS4 which randomises the symbols that appear var iconList = ['triangle','square','cross','circle'], icons = document.getElementsByClassName('icon'), ready = 0, i,j; function changeIcon(icon, iconParent) { var randomIcon = '#ps_' + iconList[Math.floor(Math.random() * iconList.length)], iconNum = parseInt(icon.id.substr(3)); if (ready < iconNum) { // We make sure every icon is done animating in the right order ready = iconNum; } iconParent.classList.remove('animate'); icon.setAttribute('xlink:href', randomIcon); if (ready === 4) { // If all symbols are changed, start the animation anew setTimeout(function(){ for (j = 0 ; j < icons.length ; j++) { icons[j].classList.add('animate'); } ready = 0; }, 0); } } for (i = 0 ; i < icons.length ; i++) { // Loop through all the icons icons[i].addEventListener('animationend', function(e) { changeIcon(e.target.querySelector('.svg-icon'), e.target); }); } // Toggler, quick and easy document.querySelector('button').addEventListener('click', function(){ document.querySelector('.loader').classList.toggle('oneline'); });