Code viewer for World: Tutorial 3.3 (clone by Mud...
import Stack from './stack'

window.onload = () => {
    return new Promise((resolve, reject) => {
        window.Stack = new Stack(document.querySelector('body'), true);
        resolve();
    })
};

import * as THREE from 'three';
import {Animate} from './animate';

export default class Stack {

    constructor(element, debug) {
        this.tileStack = [];
        this.state = {};
        this.animator = new Animate();

        if (debug) this.DEBUG = true;

        this.setupEnvironment();
        this.setupRenderer();
        this.bind(element);
    }

    initState() {
        this.state.hue = Math.random();
        this.state.lightness = 0.60;
        this.state.tileCount = 0;
    }

    setupEnvironment() {
        this.initState();

        const aspectRatio = window.innerWidth / window.innerHeight;
        const width = 10, height = width / aspectRatio;

        this.scene = new THREE.Scene();

        // Axis
        if (this.DEBUG) this.scene.add(new THREE.AxisHelper(5));

        // Orthographic camera

        this.camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, -10, 1000);
        this.camera.position.set(0, 4, 5);
        this.camera.rotateX(-Math.PI / 6);
        this.scene.add(this.camera);

        // Lighting

        this.ambientLight = new THREE.AmbientLight(0xFFFFFF);
        this.scene.add(this.ambientLight);

        this.directionalLight = new THREE.DirectionalLight(0xFFFFFF, 0.4);
        this.directionalLight.position.set(-1, 1, 0);
        this.scene.add(this.directionalLight);

        if (this.DEBUG) this.scene.add(new THREE.DirectionalLightHelper(this.directionalLight, 5));

        // Base tile
        this.addTile();
    }

    setupRenderer() {
        this.renderer = new THREE.WebGLRenderer();
        this.renderer.setSize(window.innerWidth, window.innerHeight);

        this.render();
    }

    bind(element) {
        element.appendChild(this.renderer.domElement);
        element.addEventListener('keydown', this.onInteraction.bind(this));
    }

    onInteraction(event) {
        if (event.key === " ") {
            this.addTile();
        }
    }

    render(timestamp) {
        requestAnimationFrame(this.render.bind(this));
        this.animator.animate(timestamp);
        this.renderer.render(this.scene, this.camera);
    }

    addTile(options) {

        options = options || {};

        // Compute new state

        this.state.hue += 0.02 + Math.random() * 0.02;
        this.state.lightness = 0.60 + Math.random() * 0.1 - 0.05;
        this.state.metalness = Math.random() * 0.3;
        this.state.roughness = 1 - Math.random() * 0.3;

        // Geometry

        const LENGTH = 1.5, BREADTH = 1.5, HEIGHT = 0.16;
        const geometry = new THREE.BoxGeometry(LENGTH, HEIGHT, BREADTH);

        // Material

        const SATURATION = 0.30;
        const color = new THREE.Color().setHSL(this.state.hue, SATURATION, this.state.lightness);

        const material = new THREE.MeshStandardMaterial({
            color: color,
            metalness: this.state.metalness,
            roughness: this.state.roughness
        });

        // Create the tile

        const tile = new THREE.Mesh(geometry, material);
        this.tileStack.push(tile);
        this.scene.add(tile);

        // Update state parameters

        this.state.tileCount += 1;

        // Transform the tile

        tile.rotateY(Math.PI / 4);
        if (this.tileStack.length > 1) {
            tile.position.set(1, this.state.tileCount * HEIGHT, 0);
        } else {
            tile.position.set(0, this.state.tileCount * HEIGHT, 0)
        }

        // Animations

        if (this.tileStack.length > 1) {
            if(this.state.lastAnimation) this.animator.stopAnimation(this.state.lastAnimation);
            this.state.lastAnimation = this.animator.registerAnimation({
                target: this.tileStack[this.tileStack.length - 1].position,
                property: 'x',
                to: -1,
                duration: 1000,
                loop: Infinity,
                direction: 'alternate'
            });
        }

        this.animator.registerAnimation({
            target: this.camera.position,
            property: 'y',
            to: this.state.tileCount * HEIGHT + 4,
            duration: 1000
        });

        if (this.state.tileCount > 50) {
            this.scene.remove(this.tileStack.shift());
        }

        return tile;
    }
}