/// /// /// 'use strict'; import Vector3 = THREE.Vector3; import Material = THREE.Material; import Geometry = THREE.Geometry; Physijs.scripts.worker = "physi_js/physijs_worker.js"; Physijs.scripts.ammo = "physi_js/ammo.js"; class PointerLock { hasLock:boolean = false; constructor(private game:Game, private blocker:HTMLElement, private instructions:HTMLElement) { } gain() { let havePointerLock = 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document; if (!havePointerLock) { return; } document.addEventListener('pointerlockchange', (event) => this.onChange(event), false); document.addEventListener('mozpointerlockchange', (event) => this.onChange(event), false); document.addEventListener('webkitpointerlockchange', (event) => this.onChange(event), false); document.addEventListener('pointerlockerror', (event) => this.onError(event), false); document.addEventListener('mozpointerlockerror', (event) => this.onError(event), false); document.addEventListener('webkitpointerlockerror', (event) => this.onError(event), false); this.blocker.addEventListener("click", (event) => this.onClick(event), false) } onChange(event) { let element = document.body; let doc:any = document; if (doc.pointerLockElement === element || doc.mozPointerLockElement === element || doc.webkitPointerLockElement === element) { //gained this.hasLock = true; this.blocker.style.display = "none"; if (this.game.state == GameState.INITIALIZED || this.game.state == GameState.PAUSED) { this.game.start(); } console.log("gained"); } else { //lost this.hasLock = false; this.blocker.style.display = '-webkit-box'; this.blocker.style.display = '-moz-box'; this.blocker.style.display = 'box'; this.instructions.style.display = ""; if (this.game.state == GameState.STARTED) { this.game.pause(); } console.log("lost"); } } onError(event) { this.instructions.style.display = ""; } onClick(event) { let element:any = document.body; element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock; this.instructions.style.display = "none"; element.requestPointerLock(); } } class Keyboard { static k = { 8: "backspace", 9: "tab", 13: "enter", 16: "shift", 17: "ctrl", 18: "alt", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "delete", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 219: "[", 220: "\\", 221: "]", 222: "'" }; private status = {}; constructor() { } update() { for (var key in this.status) { // insure that every keypress has "down" status exactly once if (!this.status[key].updatedPreviously) { this.status[key].down = true; this.status[key].pressed = true; this.status[key].updatedPreviously = true; } else // updated previously { this.status[key].down = false; } // key has been flagged as "up" since last update if (this.status[key].up) { delete this.status[key]; continue; // move on to next key } if (!this.status[key].pressed) // key released this.status[key].up = true; } } onKeyDown(event) { var key = Keyboard.keyName(event.keyCode); if (!this.status[key]) this.status[key] = {down: false, pressed: false, up: false, updatedPreviously: false}; } onKeyUp(event) { var key = Keyboard.keyName(event.keyCode); if (this.status[key]) this.status[key].pressed = false; } down(key) { return (this.status[key] && this.status[key].down); } pressed(key) { return (this.status[key] && this.status[key].pressed); } up(key) { return (this.status[key] && this.status[key].up); } register() { document.addEventListener("keydown", (event) => this.onKeyDown(event), false); document.addEventListener("keyup", (event) => this.onKeyUp(event), false); } static keyName(keyCode) { return ( Keyboard.k[keyCode] != null ) ? Keyboard.k[keyCode] : String.fromCharCode(keyCode); } } class Morph extends Physijs.SphereMesh { constructor(public faces:number, material?:THREE.Material, mass?:number) { super(Morph.generateGeometry(faces), material, mass); } static generateGeometry(numFaces:number):THREE.Geometry { if (numFaces == 4) { return new THREE.TetrahedronGeometry(); } else if (numFaces == 6) { return new THREE.BoxGeometry(1, 1, 1, 2, 2, 2); } else if (numFaces == 12) { return new THREE.DodecahedronGeometry(1, 0); } else if (numFaces == 20) { return new THREE.IcosahedronGeometry(1, 0); } return null; } private updateGeometry(numFaces:number) { this.faces = numFaces; this.geometry = Morph.generateGeometry(this.faces); } shrink(numFaces:number):void { this.updateGeometry(this.faces - numFaces); } grow(numFaces:number):void { this.updateGeometry(this.faces + numFaces); } } class Enemy extends Morph { constructor() { super(6, Physijs.createMaterial( new THREE.MeshBasicMaterial({ color: 0xb02000 }), .8, .3 ), 2); } /* static getMaterial():Physijs.Material { return Physijs.createMaterial( new THREE.MeshBasicMaterial({ color: 0xb02000 }), .8, .3 ); } */ } class Player extends Morph { constructor() { super(4, Physijs.createMaterial( new THREE.MeshBasicMaterial({ color: 0x00a0b0 }), .8, .3 ), 1); } /* static getMaterial():Physijs.Material { return Physijs.createMaterial( new THREE.MeshBasicMaterial({ color: 0x00a0b0 }), .8, .3 ); } */ } class World { constructor(player:Player, scene:Physijs.Scene, camera:THREE.Camera) { player.position.set(0, 2, 0); scene.add(player); //scene.add(camera); let enemy = new Enemy(); enemy.position.set(0, 5, 0); scene.add(enemy); let light:any = new THREE.DirectionalLight(0xFFFFFF); light.position.set(20, 40, -15); light.target.position.copy(player.position); light.castShadow = true; light.shadow.camera.left = -60; light.shadow.camera.top = -60; light.shadow.camera.right = 60; light.shadow.camera.bottom = 60; light.shadow.camera.near = 20; light.shadow.camera.far = 200; light.shadow.bias = -.0001; //light.shadow.map.width = light.shadow.map.height = 2048; scene.add(light); let groundGeometry = new THREE.PlaneGeometry(100, 100); groundGeometry.rotateX(90); let groundMaterial = Physijs.createMaterial( new THREE.MeshBasicMaterial({color: 0xeaeaea}), .8, .3 ); let ground = new Physijs.PlaneMesh(groundGeometry, groundMaterial); scene.add(ground); ground.receiveShadow = true; } } enum GameState { INITIALIZED, STARTED, PAUSED, STOPPED } class Game { renderer:THREE.WebGLRenderer; camera:THREE.PerspectiveCamera; scene:Physijs.Scene; player:Player; world:World; state:GameState; keyboard:Keyboard; private ticks:number = 0; private delta:number = 0; private lastFrame:number = 0; private timestep:number = 1000 / 30; private maxFPS:number = 30; private keepRunning:boolean; constructor() { this.renderer = new THREE.WebGLRenderer({ antialias: true }); this.renderer.setClearColor(0xffffff); this.renderer.setPixelRatio(window.devicePixelRatio); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.shadowMap.enabled = true; this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.body.appendChild(this.renderer.domElement); window.addEventListener("resize", () => this.onWindowResize(), false); this.camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 1, 1000); this.keyboard = new Keyboard(); this.keyboard.register(); } init():void { //init world this.scene = new Physijs.Scene; this.player = new Player(); this.world = new World(this.player, this.scene, this.camera); //init camera this.camera.position.set(10, 10, 10); this.camera.lookAt(this.player.position); this.state = GameState.INITIALIZED; } onWindowResize() { this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); } /** * Just render the scene. */ render():void { //console.log("render"); this.renderer.render(this.scene, this.camera); } /** * Update logic based on @param delta. * @param delta */ tick(delta):void { //console.log("tick " + delta); this.ticks++; this.keyboard.update(); if (this.keyboard.pressed("W")) { this.player.applyCentralForce(new Vector3(0, 0, -0.5)); } if (this.keyboard.pressed("A")) { this.player.applyCentralForce(new Vector3(0, 0, 0.5)); } if (this.keyboard.pressed("S")) { this.player.applyCentralForce(new Vector3(-0.5, 0, 0)); } if (this.keyboard.pressed("D")) { this.player.applyCentralForce(new Vector3(0.5, 0, 0)); } this.camera.lookAt(this.player.position); this.scene.simulate(); } run(timestamp?):void { if (!timestamp) { timestamp = performance.now(); } if (timestamp < this.lastFrame + (1000 / this.maxFPS)) { if (this.keepRunning) { requestAnimationFrame(() => this.run()); } return; } this.delta += timestamp - this.lastFrame; this.lastFrame = timestamp; var numUpdateSteps = 0; while (this.delta >= this.timestep) { this.tick(this.timestep); this.delta -= this.timestep; if (++numUpdateSteps >= 240) { // panic here, reset delta this.delta = 0; break; } } this.render(); if (this.keepRunning) { requestAnimationFrame(() => this.run()); } } start() { this.state = GameState.STARTED; this.keepRunning = true; this.run(); } pause() { this.state = GameState.PAUSED; this.keepRunning = false; } stop() { this.pause(); this.state = GameState.STOPPED; //end here!! //todo } } if (!Detector.webgl) { Detector.addGetWebGLMessage(); } window.onload = () => { let game = new Game(); game.init(); //make sure we have pointerlock here let block = document.getElementById("block"); let instructions = document.getElementById("instructions"); let plock = new PointerLock(game, block, instructions); plock.gain(); };