You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					565 lines
				
				15 KiB
			
		
		
			
		
	
	
					565 lines
				
				15 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								<!-- Licensed under a BSD license. See license.html for license -->
							 | 
						||
| 
								 | 
							
								<!DOCTYPE html>
							 | 
						||
| 
								 | 
							
								<html>
							 | 
						||
| 
								 | 
							
								  <head>
							 | 
						||
| 
								 | 
							
								    <meta charset="utf-8">
							 | 
						||
| 
								 | 
							
								    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
							 | 
						||
| 
								 | 
							
								    <title>Three.js - Game - Player Input</title>
							 | 
						||
| 
								 | 
							
								    <style>
							 | 
						||
| 
								 | 
							
								    html {
							 | 
						||
| 
								 | 
							
								      box-sizing: border-box;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    *, *:before, *:after {
							 | 
						||
| 
								 | 
							
								      box-sizing: inherit;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    html, body {
							 | 
						||
| 
								 | 
							
								        margin: 0;
							 | 
						||
| 
								 | 
							
								        height: 100%;
							 | 
						||
| 
								 | 
							
								        user-select: none;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    img, canvas {
							 | 
						||
| 
								 | 
							
								        /* prevent the save-image on long press on mobile */
							 | 
						||
| 
								 | 
							
								        pointer-events: none;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    #c {
							 | 
						||
| 
								 | 
							
								        width: 100%;
							 | 
						||
| 
								 | 
							
								        height: 100%;
							 | 
						||
| 
								 | 
							
								        display: block;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    #ui {
							 | 
						||
| 
								 | 
							
								      position: absolute;
							 | 
						||
| 
								 | 
							
								      left: 0;
							 | 
						||
| 
								 | 
							
								      top: 0;
							 | 
						||
| 
								 | 
							
								      width: 100%;
							 | 
						||
| 
								 | 
							
								      height: 100%;
							 | 
						||
| 
								 | 
							
								      display: flex;
							 | 
						||
| 
								 | 
							
								      justify-items: center;
							 | 
						||
| 
								 | 
							
								      align-content: stretch;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    #ui>div {
							 | 
						||
| 
								 | 
							
								      display: flex;
							 | 
						||
| 
								 | 
							
								      align-items: flex-end;
							 | 
						||
| 
								 | 
							
								      flex: 1 1 auto;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    .bright {
							 | 
						||
| 
								 | 
							
								      filter: brightness(2);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    #left {
							 | 
						||
| 
								 | 
							
								      justify-content: flex-end;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    #right {
							 | 
						||
| 
								 | 
							
								      justify-content: flex-start;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    #ui img {
							 | 
						||
| 
								 | 
							
								      padding: 10px;
							 | 
						||
| 
								 | 
							
								      width: 80px;
							 | 
						||
| 
								 | 
							
								      height: 80px;
							 | 
						||
| 
								 | 
							
								      display: block;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    #loading {
							 | 
						||
| 
								 | 
							
								      position: absolute;
							 | 
						||
| 
								 | 
							
								      left: 0;
							 | 
						||
| 
								 | 
							
								      top: 0;
							 | 
						||
| 
								 | 
							
								      width: 100%;
							 | 
						||
| 
								 | 
							
								      height: 100%;
							 | 
						||
| 
								 | 
							
								      display: flex;
							 | 
						||
| 
								 | 
							
								      align-items: center;
							 | 
						||
| 
								 | 
							
								      justify-content: center;
							 | 
						||
| 
								 | 
							
								      text-align: center;
							 | 
						||
| 
								 | 
							
								      font-size: xx-large;
							 | 
						||
| 
								 | 
							
								      font-family: sans-serif;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    #loading>div>div {
							 | 
						||
| 
								 | 
							
								      padding: 2px;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    .progress {
							 | 
						||
| 
								 | 
							
								      width: 50vw;
							 | 
						||
| 
								 | 
							
								      border: 1px solid black;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    #progressbar {
							 | 
						||
| 
								 | 
							
								      width: 0%;
							 | 
						||
| 
								 | 
							
								      transition: width ease-out .5s;
							 | 
						||
| 
								 | 
							
								      height: 1em;
							 | 
						||
| 
								 | 
							
								      background-color: #888;
							 | 
						||
| 
								 | 
							
								      background-image: linear-gradient(
							 | 
						||
| 
								 | 
							
								        -45deg,
							 | 
						||
| 
								 | 
							
								        rgba(255, 255, 255, .5) 25%,
							 | 
						||
| 
								 | 
							
								        transparent 25%,
							 | 
						||
| 
								 | 
							
								        transparent 50%,
							 | 
						||
| 
								 | 
							
								        rgba(255, 255, 255, .5) 50%,
							 | 
						||
| 
								 | 
							
								        rgba(255, 255, 255, .5) 75%,
							 | 
						||
| 
								 | 
							
								        transparent 75%,
							 | 
						||
| 
								 | 
							
								        transparent
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								      background-size: 50px 50px;
							 | 
						||
| 
								 | 
							
								      animation: progressanim 2s linear infinite;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @keyframes progressanim {
							 | 
						||
| 
								 | 
							
								      0% {
							 | 
						||
| 
								 | 
							
								        background-position: 50px 50px;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      100% {
							 | 
						||
| 
								 | 
							
								        background-position: 0 0;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  </style>
							 | 
						||
| 
								 | 
							
								  </head>
							 | 
						||
| 
								 | 
							
								  <body>
							 | 
						||
| 
								 | 
							
								    <canvas id="c" tabindex="1"></canvas>
							 | 
						||
| 
								 | 
							
								    <div id="ui">
							 | 
						||
| 
								 | 
							
								      <div id="left"><img src="resources/images/left.svg"></div>
							 | 
						||
| 
								 | 
							
								      <div style="flex: 0 0 40px;"></div>
							 | 
						||
| 
								 | 
							
								      <div id="right"><img src="resources/images/right.svg"></div>
							 | 
						||
| 
								 | 
							
								    </div>
							 | 
						||
| 
								 | 
							
								    <div id="loading">
							 | 
						||
| 
								 | 
							
								      <div>
							 | 
						||
| 
								 | 
							
								        <div>...loading...</div>
							 | 
						||
| 
								 | 
							
								        <div class="progress"><div id="progressbar"></div></div>
							 | 
						||
| 
								 | 
							
								      </div>
							 | 
						||
| 
								 | 
							
								    </div>
							 | 
						||
| 
								 | 
							
								  </body>
							 | 
						||
| 
								 | 
							
								<!-- Import maps polyfill -->
							 | 
						||
| 
								 | 
							
								<!-- Remove this when import maps will be widely supported -->
							 | 
						||
| 
								 | 
							
								<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<script type="importmap">
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								  "imports": {
							 | 
						||
| 
								 | 
							
								    "three": "../../build/three.module.js",
							 | 
						||
| 
								 | 
							
								    "three/addons/": "../../examples/jsm/"
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								</script>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<script type="module">
							 | 
						||
| 
								 | 
							
								import * as THREE from 'three';
							 | 
						||
| 
								 | 
							
								import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
							 | 
						||
| 
								 | 
							
								import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';
							 | 
						||
| 
								 | 
							
								import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function main() {
							 | 
						||
| 
								 | 
							
								  const canvas = document.querySelector('#c');
							 | 
						||
| 
								 | 
							
								  const renderer = new THREE.WebGLRenderer({canvas});
							 | 
						||
| 
								 | 
							
								  renderer.outputEncoding = THREE.sRGBEncoding;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const fov = 45;
							 | 
						||
| 
								 | 
							
								  const aspect = 2;  // the canvas default
							 | 
						||
| 
								 | 
							
								  const near = 0.1;
							 | 
						||
| 
								 | 
							
								  const far = 1000;
							 | 
						||
| 
								 | 
							
								  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
							 | 
						||
| 
								 | 
							
								  camera.position.set(0, 40, 80);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const controls = new OrbitControls(camera, canvas);
							 | 
						||
| 
								 | 
							
								  controls.target.set(0, 5, 0);
							 | 
						||
| 
								 | 
							
								  controls.update();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const scene = new THREE.Scene();
							 | 
						||
| 
								 | 
							
								  scene.background = new THREE.Color('white');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function addLight(...pos) {
							 | 
						||
| 
								 | 
							
								    const color = 0xFFFFFF;
							 | 
						||
| 
								 | 
							
								    const intensity = 0.8;
							 | 
						||
| 
								 | 
							
								    const light = new THREE.DirectionalLight(color, intensity);
							 | 
						||
| 
								 | 
							
								    light.position.set(...pos);
							 | 
						||
| 
								 | 
							
								    scene.add(light);
							 | 
						||
| 
								 | 
							
								    scene.add(light.target);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  addLight(5, 5, 2);
							 | 
						||
| 
								 | 
							
								  addLight(-5, 5, 5);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const manager = new THREE.LoadingManager();
							 | 
						||
| 
								 | 
							
								  manager.onLoad = init;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const progressbarElem = document.querySelector('#progressbar');
							 | 
						||
| 
								 | 
							
								  manager.onProgress = (url, itemsLoaded, itemsTotal) => {
							 | 
						||
| 
								 | 
							
								    progressbarElem.style.width = `${itemsLoaded / itemsTotal * 100 | 0}%`;
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const models = {
							 | 
						||
| 
								 | 
							
								    pig:    { url: 'resources/models/animals/Pig.gltf' },
							 | 
						||
| 
								 | 
							
								    cow:    { url: 'resources/models/animals/Cow.gltf' },
							 | 
						||
| 
								 | 
							
								    llama:  { url: 'resources/models/animals/Llama.gltf' },
							 | 
						||
| 
								 | 
							
								    pug:    { url: 'resources/models/animals/Pug.gltf' },
							 | 
						||
| 
								 | 
							
								    sheep:  { url: 'resources/models/animals/Sheep.gltf' },
							 | 
						||
| 
								 | 
							
								    zebra:  { url: 'resources/models/animals/Zebra.gltf' },
							 | 
						||
| 
								 | 
							
								    horse:  { url: 'resources/models/animals/Horse.gltf' },
							 | 
						||
| 
								 | 
							
								    knight: { url: 'resources/models/knight/KnightCharacter.gltf' },
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								  {
							 | 
						||
| 
								 | 
							
								    const gltfLoader = new GLTFLoader(manager);
							 | 
						||
| 
								 | 
							
								    for (const model of Object.values(models)) {
							 | 
						||
| 
								 | 
							
								      gltfLoader.load(model.url, (gltf) => {
							 | 
						||
| 
								 | 
							
								        model.gltf = gltf;
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function prepModelsAndAnimations() {
							 | 
						||
| 
								 | 
							
								    Object.values(models).forEach(model => {
							 | 
						||
| 
								 | 
							
								      const animsByName = {};
							 | 
						||
| 
								 | 
							
								      model.gltf.animations.forEach((clip) => {
							 | 
						||
| 
								 | 
							
								        animsByName[clip.name] = clip;
							 | 
						||
| 
								 | 
							
								        // Should really fix this in .blend file
							 | 
						||
| 
								 | 
							
								        if (clip.name === 'Walk') {
							 | 
						||
| 
								 | 
							
								          clip.duration /= 2;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      model.animations = animsByName;
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Keeps the state of keys/buttons
							 | 
						||
| 
								 | 
							
								  //
							 | 
						||
| 
								 | 
							
								  // You can check
							 | 
						||
| 
								 | 
							
								  //
							 | 
						||
| 
								 | 
							
								  //   inputManager.keys.left.down
							 | 
						||
| 
								 | 
							
								  //
							 | 
						||
| 
								 | 
							
								  // to see if the left key is currently held down
							 | 
						||
| 
								 | 
							
								  // and you can check
							 | 
						||
| 
								 | 
							
								  //
							 | 
						||
| 
								 | 
							
								  //   inputManager.keys.left.justPressed
							 | 
						||
| 
								 | 
							
								  //
							 | 
						||
| 
								 | 
							
								  // To see if the left key was pressed this frame
							 | 
						||
| 
								 | 
							
								  //
							 | 
						||
| 
								 | 
							
								  // Keys are 'left', 'right', 'a', 'b', 'up', 'down'
							 | 
						||
| 
								 | 
							
								  class InputManager {
							 | 
						||
| 
								 | 
							
								    constructor() {
							 | 
						||
| 
								 | 
							
								      this.keys = {};
							 | 
						||
| 
								 | 
							
								      const keyMap = new Map();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const setKey = (keyName, pressed) => {
							 | 
						||
| 
								 | 
							
								        const keyState = this.keys[keyName];
							 | 
						||
| 
								 | 
							
								        keyState.justPressed = pressed && !keyState.down;
							 | 
						||
| 
								 | 
							
								        keyState.down = pressed;
							 | 
						||
| 
								 | 
							
								      };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const addKey = (keyCode, name) => {
							 | 
						||
| 
								 | 
							
								        this.keys[name] = { down: false, justPressed: false };
							 | 
						||
| 
								 | 
							
								        keyMap.set(keyCode, name);
							 | 
						||
| 
								 | 
							
								      };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const setKeyFromKeyCode = (keyCode, pressed) => {
							 | 
						||
| 
								 | 
							
								        const keyName = keyMap.get(keyCode);
							 | 
						||
| 
								 | 
							
								        if (!keyName) {
							 | 
						||
| 
								 | 
							
								          return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        setKey(keyName, pressed);
							 | 
						||
| 
								 | 
							
								      };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      addKey(37, 'left');
							 | 
						||
| 
								 | 
							
								      addKey(39, 'right');
							 | 
						||
| 
								 | 
							
								      addKey(38, 'up');
							 | 
						||
| 
								 | 
							
								      addKey(40, 'down');
							 | 
						||
| 
								 | 
							
								      addKey(90, 'a');
							 | 
						||
| 
								 | 
							
								      addKey(88, 'b');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      window.addEventListener('keydown', (e) => {
							 | 
						||
| 
								 | 
							
								        setKeyFromKeyCode(e.keyCode, true);
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      window.addEventListener('keyup', (e) => {
							 | 
						||
| 
								 | 
							
								        setKeyFromKeyCode(e.keyCode, false);
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const sides = [
							 | 
						||
| 
								 | 
							
								        { elem: document.querySelector('#left'),  key: 'left'  },
							 | 
						||
| 
								 | 
							
								        { elem: document.querySelector('#right'), key: 'right' },
							 | 
						||
| 
								 | 
							
								      ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // note: not a good design?
							 | 
						||
| 
								 | 
							
								      // The last direction the user presses should take
							 | 
						||
| 
								 | 
							
								      // precedence. Example: User presses L, without letting go of
							 | 
						||
| 
								 | 
							
								      // L user presses R. Input should now be R. User lets off R
							 | 
						||
| 
								 | 
							
								      // Input should now be L.
							 | 
						||
| 
								 | 
							
								      // With this code if user pressed both L and R result is nothing
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const clearKeys = () => {
							 | 
						||
| 
								 | 
							
								        for (const {key} of sides) {
							 | 
						||
| 
								 | 
							
								            setKey(key, false);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const handleMouseMove = (e) => {
							 | 
						||
| 
								 | 
							
								        e.preventDefault();
							 | 
						||
| 
								 | 
							
								        // this is needed because we call preventDefault();
							 | 
						||
| 
								 | 
							
								        // we also gave the canvas a tabindex so it can
							 | 
						||
| 
								 | 
							
								        // become the focus
							 | 
						||
| 
								 | 
							
								        canvas.focus();
							 | 
						||
| 
								 | 
							
								        window.addEventListener('pointermove', handleMouseMove);
							 | 
						||
| 
								 | 
							
								        window.addEventListener('pointerup', handleMouseUp);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for (const {elem, key} of sides) {
							 | 
						||
| 
								 | 
							
								          let pressed = false;
							 | 
						||
| 
								 | 
							
								          const rect = elem.getBoundingClientRect();
							 | 
						||
| 
								 | 
							
								          const x = e.clientX;
							 | 
						||
| 
								 | 
							
								          const y = e.clientY;
							 | 
						||
| 
								 | 
							
								          const inRect = x >= rect.left && x < rect.right &&
							 | 
						||
| 
								 | 
							
								                         y >= rect.top && y < rect.bottom;
							 | 
						||
| 
								 | 
							
								          if (inRect) {
							 | 
						||
| 
								 | 
							
								            pressed = true;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          setKey(key, pressed);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      function handleMouseUp() {
							 | 
						||
| 
								 | 
							
								        clearKeys();
							 | 
						||
| 
								 | 
							
								        window.removeEventListener('pointermove', handleMouseMove, {passive: false});
							 | 
						||
| 
								 | 
							
								        window.removeEventListener('pointerup', handleMouseUp);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const uiElem = document.querySelector('#ui');
							 | 
						||
| 
								 | 
							
								      uiElem.addEventListener('pointerdown', handleMouseMove, {passive: false});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      uiElem.addEventListener('touchstart', (e) => {
							 | 
						||
| 
								 | 
							
								        // prevent scrolling
							 | 
						||
| 
								 | 
							
								        e.preventDefault();
							 | 
						||
| 
								 | 
							
								      }, {passive: false});
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    update() {
							 | 
						||
| 
								 | 
							
								      for (const keyState of Object.values(this.keys)) {
							 | 
						||
| 
								 | 
							
								        if (keyState.justPressed) {
							 | 
						||
| 
								 | 
							
								          keyState.justPressed = false;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function removeArrayElement(array, element) {
							 | 
						||
| 
								 | 
							
								    const ndx = array.indexOf(element);
							 | 
						||
| 
								 | 
							
								    if (ndx >= 0) {
							 | 
						||
| 
								 | 
							
								      array.splice(ndx, 1);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  class SafeArray {
							 | 
						||
| 
								 | 
							
								    constructor() {
							 | 
						||
| 
								 | 
							
								      this.array = [];
							 | 
						||
| 
								 | 
							
								      this.addQueue = [];
							 | 
						||
| 
								 | 
							
								      this.removeQueue = new Set();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    get isEmpty() {
							 | 
						||
| 
								 | 
							
								      return this.addQueue.length + this.array.length > 0;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    add(element) {
							 | 
						||
| 
								 | 
							
								      this.addQueue.push(element);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    remove(element) {
							 | 
						||
| 
								 | 
							
								      this.removeQueue.add(element);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    forEach(fn) {
							 | 
						||
| 
								 | 
							
								      this._addQueued();
							 | 
						||
| 
								 | 
							
								      this._removeQueued();
							 | 
						||
| 
								 | 
							
								      for (const element of this.array) {
							 | 
						||
| 
								 | 
							
								        if (this.removeQueue.has(element)) {
							 | 
						||
| 
								 | 
							
								          continue;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        fn(element);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      this._removeQueued();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    _addQueued() {
							 | 
						||
| 
								 | 
							
								      if (this.addQueue.length) {
							 | 
						||
| 
								 | 
							
								        this.array.splice(this.array.length, 0, ...this.addQueue);
							 | 
						||
| 
								 | 
							
								        this.addQueue = [];
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    _removeQueued() {
							 | 
						||
| 
								 | 
							
								      if (this.removeQueue.size) {
							 | 
						||
| 
								 | 
							
								        this.array = this.array.filter(element => !this.removeQueue.has(element));
							 | 
						||
| 
								 | 
							
								        this.removeQueue.clear();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  class GameObjectManager {
							 | 
						||
| 
								 | 
							
								    constructor() {
							 | 
						||
| 
								 | 
							
								      this.gameObjects = new SafeArray();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    createGameObject(parent, name) {
							 | 
						||
| 
								 | 
							
								      const gameObject = new GameObject(parent, name);
							 | 
						||
| 
								 | 
							
								      this.gameObjects.add(gameObject);
							 | 
						||
| 
								 | 
							
								      return gameObject;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    removeGameObject(gameObject) {
							 | 
						||
| 
								 | 
							
								      this.gameObjects.remove(gameObject);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    update() {
							 | 
						||
| 
								 | 
							
								      this.gameObjects.forEach(gameObject => gameObject.update());
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const kForward = new THREE.Vector3(0, 0, 1);
							 | 
						||
| 
								 | 
							
								  const globals = {
							 | 
						||
| 
								 | 
							
								    time: 0,
							 | 
						||
| 
								 | 
							
								    deltaTime: 0,
							 | 
						||
| 
								 | 
							
								    moveSpeed: 16,
							 | 
						||
| 
								 | 
							
								    camera,
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								  const gameObjectManager = new GameObjectManager();
							 | 
						||
| 
								 | 
							
								  const inputManager = new InputManager();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  class GameObject {
							 | 
						||
| 
								 | 
							
								    constructor(parent, name) {
							 | 
						||
| 
								 | 
							
								      this.name = name;
							 | 
						||
| 
								 | 
							
								      this.components = [];
							 | 
						||
| 
								 | 
							
								      this.transform = new THREE.Object3D();
							 | 
						||
| 
								 | 
							
								      parent.add(this.transform);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    addComponent(ComponentType, ...args) {
							 | 
						||
| 
								 | 
							
								      const component = new ComponentType(this, ...args);
							 | 
						||
| 
								 | 
							
								      this.components.push(component);
							 | 
						||
| 
								 | 
							
								      return component;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    removeComponent(component) {
							 | 
						||
| 
								 | 
							
								      removeArrayElement(this.components, component);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    getComponent(ComponentType) {
							 | 
						||
| 
								 | 
							
								      return this.components.find(c => c instanceof ComponentType);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    update() {
							 | 
						||
| 
								 | 
							
								      for (const component of this.components) {
							 | 
						||
| 
								 | 
							
								        component.update();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Base for all components
							 | 
						||
| 
								 | 
							
								  class Component {
							 | 
						||
| 
								 | 
							
								    constructor(gameObject) {
							 | 
						||
| 
								 | 
							
								      this.gameObject = gameObject;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    update() {
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  class CameraInfo extends Component {
							 | 
						||
| 
								 | 
							
								    constructor(gameObject) {
							 | 
						||
| 
								 | 
							
								      super(gameObject);
							 | 
						||
| 
								 | 
							
								      this.projScreenMatrix = new THREE.Matrix4();
							 | 
						||
| 
								 | 
							
								      this.frustum = new THREE.Frustum();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    update() {
							 | 
						||
| 
								 | 
							
								      const {camera} = globals;
							 | 
						||
| 
								 | 
							
								      this.projScreenMatrix.multiplyMatrices(
							 | 
						||
| 
								 | 
							
								          camera.projectionMatrix,
							 | 
						||
| 
								 | 
							
								          camera.matrixWorldInverse);
							 | 
						||
| 
								 | 
							
								      this.frustum.setFromProjectionMatrix(this.projScreenMatrix);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  class SkinInstance extends Component {
							 | 
						||
| 
								 | 
							
								    constructor(gameObject, model) {
							 | 
						||
| 
								 | 
							
								      super(gameObject);
							 | 
						||
| 
								 | 
							
								      this.model = model;
							 | 
						||
| 
								 | 
							
								      this.animRoot = SkeletonUtils.clone(this.model.gltf.scene);
							 | 
						||
| 
								 | 
							
								      this.mixer = new THREE.AnimationMixer(this.animRoot);
							 | 
						||
| 
								 | 
							
								      gameObject.transform.add(this.animRoot);
							 | 
						||
| 
								 | 
							
								      this.actions = {};
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    setAnimation(animName) {
							 | 
						||
| 
								 | 
							
								      const clip = this.model.animations[animName];
							 | 
						||
| 
								 | 
							
								      // turn off all current actions
							 | 
						||
| 
								 | 
							
								      for (const action of Object.values(this.actions)) {
							 | 
						||
| 
								 | 
							
								        action.enabled = false;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      // get or create existing action for clip
							 | 
						||
| 
								 | 
							
								      const action = this.mixer.clipAction(clip);
							 | 
						||
| 
								 | 
							
								      action.enabled = true;
							 | 
						||
| 
								 | 
							
								      action.reset();
							 | 
						||
| 
								 | 
							
								      action.play();
							 | 
						||
| 
								 | 
							
								      this.actions[animName] = action;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    update() {
							 | 
						||
| 
								 | 
							
								      this.mixer.update(globals.deltaTime);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  class Player extends Component {
							 | 
						||
| 
								 | 
							
								    constructor(gameObject) {
							 | 
						||
| 
								 | 
							
								      super(gameObject);
							 | 
						||
| 
								 | 
							
								      const model = models.knight;
							 | 
						||
| 
								 | 
							
								      this.skinInstance = gameObject.addComponent(SkinInstance, model);
							 | 
						||
| 
								 | 
							
								      this.skinInstance.setAnimation('Run');
							 | 
						||
| 
								 | 
							
								      this.turnSpeed = globals.moveSpeed / 4;
							 | 
						||
| 
								 | 
							
								      this.offscreenTimer = 0;
							 | 
						||
| 
								 | 
							
								      this.maxTimeOffScreen = 3;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    update() {
							 | 
						||
| 
								 | 
							
								      const {deltaTime, moveSpeed} = globals;
							 | 
						||
| 
								 | 
							
								      const {transform} = this.gameObject;
							 | 
						||
| 
								 | 
							
								      const delta = (inputManager.keys.left.down  ?  1 : 0) +
							 | 
						||
| 
								 | 
							
								                    (inputManager.keys.right.down ? -1 : 0);
							 | 
						||
| 
								 | 
							
								      transform.rotation.y += this.turnSpeed * delta * deltaTime;
							 | 
						||
| 
								 | 
							
								      transform.translateOnAxis(kForward, moveSpeed * deltaTime);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const {frustum} = globals.cameraInfo;
							 | 
						||
| 
								 | 
							
								      if (frustum.containsPoint(transform.position)) {
							 | 
						||
| 
								 | 
							
								        this.offscreenTimer = 0;
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        this.offscreenTimer += deltaTime;
							 | 
						||
| 
								 | 
							
								        if (this.offscreenTimer >= this.maxTimeOffScreen) {
							 | 
						||
| 
								 | 
							
								          transform.position.set(0, 0, 0);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function init() {
							 | 
						||
| 
								 | 
							
								    // hide the loading bar
							 | 
						||
| 
								 | 
							
								    const loadingElem = document.querySelector('#loading');
							 | 
						||
| 
								 | 
							
								    loadingElem.style.display = 'none';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    prepModelsAndAnimations();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								      const gameObject = gameObjectManager.createGameObject(camera, 'camera');
							 | 
						||
| 
								 | 
							
								      globals.cameraInfo = gameObject.addComponent(CameraInfo);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								      const gameObject = gameObjectManager.createGameObject(scene, 'player');
							 | 
						||
| 
								 | 
							
								      gameObject.addComponent(Player);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function resizeRendererToDisplaySize(renderer) {
							 | 
						||
| 
								 | 
							
								    const canvas = renderer.domElement;
							 | 
						||
| 
								 | 
							
								    const width = canvas.clientWidth;
							 | 
						||
| 
								 | 
							
								    const height = canvas.clientHeight;
							 | 
						||
| 
								 | 
							
								    const needResize = canvas.width !== width || canvas.height !== height;
							 | 
						||
| 
								 | 
							
								    if (needResize) {
							 | 
						||
| 
								 | 
							
								      renderer.setSize(width, height, false);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return needResize;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  let then = 0;
							 | 
						||
| 
								 | 
							
								  function render(now) {
							 | 
						||
| 
								 | 
							
								    // convert to seconds
							 | 
						||
| 
								 | 
							
								    globals.time = now * 0.001;
							 | 
						||
| 
								 | 
							
								    // make sure delta time isn't too big.
							 | 
						||
| 
								 | 
							
								    globals.deltaTime = Math.min(globals.time - then, 1 / 20);
							 | 
						||
| 
								 | 
							
								    then = globals.time;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (resizeRendererToDisplaySize(renderer)) {
							 | 
						||
| 
								 | 
							
								      const canvas = renderer.domElement;
							 | 
						||
| 
								 | 
							
								      camera.aspect = canvas.clientWidth / canvas.clientHeight;
							 | 
						||
| 
								 | 
							
								      camera.updateProjectionMatrix();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    gameObjectManager.update();
							 | 
						||
| 
								 | 
							
								    inputManager.update();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    renderer.render(scene, camera);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    requestAnimationFrame(render);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  requestAnimationFrame(render);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								main();
							 | 
						||
| 
								 | 
							
								</script>
							 | 
						||
| 
								 | 
							
								</html>
							 |