├── .env ├── Procfile ├── public ├── assets │ ├── gem.png │ ├── walls.png │ ├── controls.png │ ├── pharaoh.png │ ├── tentacles.png │ ├── Golden_Age.ttf │ ├── perlin-512.png │ ├── spr_character.png │ ├── title_screen.png │ └── Golden_Age_Shad.ttf ├── js │ ├── SquareHelper.js │ ├── AtlasHelper.js │ ├── Keys.js │ ├── libs │ │ ├── stats.min.js │ │ ├── SpriteMixer.js │ │ └── OrbitControls.js │ ├── Chaser.js │ ├── Game.js │ ├── Treasures.js │ ├── SprControler.js │ ├── SceneGenerator.js │ ├── Controler.js │ └── Atlas.js └── html │ └── game.html ├── app.js ├── package.json └── README.md /.env: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node app.js -------------------------------------------------------------------------------- /public/assets/gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixmariotto/Temple_Of_Doom/HEAD/public/assets/gem.png -------------------------------------------------------------------------------- /public/assets/walls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixmariotto/Temple_Of_Doom/HEAD/public/assets/walls.png -------------------------------------------------------------------------------- /public/assets/controls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixmariotto/Temple_Of_Doom/HEAD/public/assets/controls.png -------------------------------------------------------------------------------- /public/assets/pharaoh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixmariotto/Temple_Of_Doom/HEAD/public/assets/pharaoh.png -------------------------------------------------------------------------------- /public/assets/tentacles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixmariotto/Temple_Of_Doom/HEAD/public/assets/tentacles.png -------------------------------------------------------------------------------- /public/assets/Golden_Age.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixmariotto/Temple_Of_Doom/HEAD/public/assets/Golden_Age.ttf -------------------------------------------------------------------------------- /public/assets/perlin-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixmariotto/Temple_Of_Doom/HEAD/public/assets/perlin-512.png -------------------------------------------------------------------------------- /public/assets/spr_character.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixmariotto/Temple_Of_Doom/HEAD/public/assets/spr_character.png -------------------------------------------------------------------------------- /public/assets/title_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixmariotto/Temple_Of_Doom/HEAD/public/assets/title_screen.png -------------------------------------------------------------------------------- /public/assets/Golden_Age_Shad.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/felixmariotto/Temple_Of_Doom/HEAD/public/assets/Golden_Age_Shad.ttf -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require( 'express' ); 3 | const path = require( 'path' ); 4 | const app = new express(); 5 | const PORT = process.env.PORT || 5000; 6 | 7 | 8 | app.use(express.static('public')); 9 | 10 | app 11 | .get('/', (req, res)=> { 12 | res.sendFile(path.join(__dirname + '/public/html/game.html')); 13 | }) 14 | 15 | .listen(PORT, ()=> { 16 | console.log('App listening on port ' + PORT); 17 | }) -------------------------------------------------------------------------------- /public/js/SquareHelper.js: -------------------------------------------------------------------------------- 1 | 2 | function SquareHelper( logicSquare ) { 3 | 4 | let group = new THREE.Group(); 5 | group.position.copy( logicSquare.position ); 6 | 7 | var material = new THREE.LineBasicMaterial({ 8 | color: 0xffffff 9 | }); 10 | 11 | var geometry = new THREE.Geometry(); 12 | 13 | geometry.vertices.push( 14 | new THREE.Vector3( logicSquare.width, 0, 0 ), 15 | new THREE.Vector3( logicSquare.width, logicSquare.height, 0 ), 16 | new THREE.Vector3( 0, logicSquare.height, 0 ), 17 | new THREE.Vector3( 0, 0, 0 ), 18 | new THREE.Vector3( logicSquare.width, 0, 0 ) 19 | ); 20 | 21 | var line = new THREE.Line( geometry, material ); 22 | line.position.z += 0.5 ; 23 | group.add( line ); 24 | 25 | return group; 26 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "temple_of_doom", 3 | "version": "1.0.0", 4 | "description": "2.5D runner game", 5 | "engines": { 6 | "node": "10.x" 7 | }, 8 | "main": "app.js", 9 | "scripts": { 10 | "start": "node app.js", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/felixmariotto/Temple_Of_Doom.git" 16 | }, 17 | "keywords": [ 18 | "runner", 19 | "game", 20 | "2.5D", 21 | "three.js", 22 | "felix", 23 | "mariotto" 24 | ], 25 | "author": "felix mariotto", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/felixmariotto/Temple_Of_Doom/issues" 29 | }, 30 | "homepage": "https://github.com/felixmariotto/Temple_Of_Doom#readme", 31 | "dependencies": { 32 | "express": "^4.17.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/js/AtlasHelper.js: -------------------------------------------------------------------------------- 1 | 2 | // AtlasHelper returns a group containing collections of THREE.Line 3 | // to help visualise the world atlas. 4 | function AtlasHelper( pointsArrays ) { 5 | 6 | let group = new THREE.Group(); 7 | let scaleVec = new THREE.Vector3( 1, 0, 0 ); 8 | 9 | 10 | 11 | pointsArrays.forEach( (points, i)=> { 12 | 13 | switch (i) { 14 | case 0 : color = 'red'; break; 15 | case 1 : color = 'blue'; break; 16 | case 2 : color = 'green'; break; 17 | }; 18 | 19 | var material = new THREE.LineBasicMaterial({ 20 | color: color 21 | }); 22 | 23 | var geometry = new THREE.Geometry(); 24 | 25 | points.forEach( (vec)=> { 26 | 27 | geometry.vertices.push( 28 | vec, 29 | new THREE.Vector3() 30 | .copy(vec) 31 | .add(scaleVec) 32 | ); 33 | 34 | }) 35 | 36 | 37 | var line = new THREE.Line( geometry, material ); 38 | line.position.z = 1 ; 39 | group.add( line ); 40 | 41 | }) 42 | 43 | return group ; 44 | }; -------------------------------------------------------------------------------- /public/js/Keys.js: -------------------------------------------------------------------------------- 1 | 2 | function Keys() { 3 | 4 | var isPressed = { 5 | 'up': false, 6 | 'down': false, 7 | 'left': false, 8 | 'right': false, 9 | 'space': false 10 | }; 11 | 12 | 13 | 14 | function init() { 15 | document.addEventListener('keydown', (e)=> { 16 | onKeyChange( e, true ); 17 | }); 18 | document.addEventListener('keyup', (e)=> { 19 | onKeyChange( e, false ); 20 | }); 21 | }; 22 | 23 | 24 | function onKeyChange( e, bool ) { 25 | 26 | switch ( e.key ) { 27 | 28 | case "ArrowRight" : 29 | isPressed.right = bool ; 30 | break; 31 | 32 | case "ArrowLeft" : 33 | isPressed.left = bool ; 34 | break; 35 | 36 | case "ArrowUp" : 37 | isPressed.up = bool ; 38 | break; 39 | 40 | case "ArrowDown" : 41 | isPressed.down = bool ; 42 | break; 43 | 44 | case " " : 45 | isPressed.space = bool ; 46 | break; 47 | }; 48 | }; 49 | 50 | 51 | 52 | return { 53 | init, 54 | isPressed 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # the Temple of Doom 2 | Runner game 2.5D with pixels and sprites 3 | 4 | ## Play online here : https://temple-of-doom.herokuapp.com ## 5 | 6 | This simple game is inspired from adventure works like Indiana Jones, the Mummy, Tomb Rider, Uncharted, Temple Run or Uncle Scrooge. (yes, never read The Life and Times of Scrooge McDuck, with the zombie ?). 7 | It's almost a rule that when an adventurer take a treasure from a temple, a monster or something really bad immediately shows up to destroy them/the world. 8 | 9 | I've always liked this simple illustration of the "Greed is Vain" concept, and I've always thought it's a pitty that there is not more works inspired from this genre. So when the time came for me to make a sample game project using Three.js with SpriteMixer for sprite animations, I chose to make this. 10 | 11 | Have fun playing it ! 12 | 13 | ## Screenshots ## 14 | ![screenshot of the Temple of Doom game 1](https://felixmariotto.s3.eu-west-3.amazonaws.com/temple-of-doom-1.png) 15 | ![screenshot of the Temple of Doom game 2](https://felixmariotto.s3.eu-west-3.amazonaws.com/temple-of-doom-2.png) 16 | 17 | -------------------------------------------------------------------------------- /public/js/libs/stats.min.js: -------------------------------------------------------------------------------- 1 | // stats.js - http://github.com/mrdoob/stats.js 2 | var Stats=function(){function h(a){c.appendChild(a.dom);return a}function k(a){for(var d=0;de+1E3&&(r.update(1E3*a/(c-e),100),e=c,a=0,t)){var d=performance.memory;t.update(d.usedJSHeapSize/1048576,d.jsHeapSizeLimit/1048576)}return c},update:function(){g=this.end()},domElement:c,setMode:k}}; 4 | Stats.Panel=function(h,k,l){var c=Infinity,g=0,e=Math.round,a=e(window.devicePixelRatio||1),r=80*a,f=48*a,t=3*a,u=2*a,d=3*a,m=15*a,n=74*a,p=30*a,q=document.createElement("canvas");q.width=r;q.height=f;q.style.cssText="width:80px;height:48px";var b=q.getContext("2d");b.font="bold "+9*a+"px Helvetica,Arial,sans-serif";b.textBaseline="top";b.fillStyle=l;b.fillRect(0,0,r,f);b.fillStyle=k;b.fillText(h,t,u);b.fillRect(d,m,n,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d,m,n,p);return{dom:q,update:function(f, 5 | v){c=Math.min(c,f);g=Math.max(g,f);b.fillStyle=l;b.globalAlpha=1;b.fillRect(0,0,r,m);b.fillStyle=k;b.fillText(e(f)+" "+h+" ("+e(c)+"-"+e(g)+")",t,u);b.drawImage(q,d+a,m,n-a,p,d,m,n-a,p);b.fillRect(d+n-a,m,a,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d+n-a,m,a,e((1-f/v)*p))}}};"object"===typeof module&&(module.exports=Stats); 6 | -------------------------------------------------------------------------------- /public/js/Chaser.js: -------------------------------------------------------------------------------- 1 | 2 | function Chaser( logicCube, needHelper ) { 3 | 4 | 5 | // const SPEED = 0.031 ; 6 | const SPEED = 0.028; 7 | const STARTVEC = new THREE.Vector3( 2.5, 2, 1.5); // was ( 5, 2, 1) 8 | 9 | var group = new THREE.Group(); 10 | group.position.copy( STARTVEC ); 11 | scene.add( group ); 12 | 13 | var pharaohFollows; 14 | var pharaohVecTarget = new THREE.Vector3(); 15 | 16 | var params = { 17 | isRunning: false, 18 | startVec: STARTVEC 19 | }; 20 | 21 | 22 | if ( needHelper ) { 23 | var geometry = new THREE.BoxBufferGeometry( 40, 50, 2.5 ); 24 | var material = new THREE.MeshBasicMaterial( {color: 0x000000} ); 25 | var cube = new THREE.Mesh( geometry, material ); 26 | cube.position.x = -20 ; 27 | group.add( cube ); 28 | }; 29 | 30 | 31 | 32 | function update() { 33 | 34 | if ( params.isRunning ) { 35 | 36 | // Update the monster 37 | 38 | group.position.x += SPEED ; 39 | group.position.y = getTrackInterpol( group.position.x ) 40 | 41 | // check for collision with the player 42 | if ( logicCube.position.x < group.position.x ) { 43 | game.fail(); 44 | }; 45 | 46 | sprControler.tentaclesSprites[0].position.y = Math.sin( Date.now() / 350 ) / 2 ; 47 | sprControler.tentaclesSprites[1].position.y = Math.sin( Date.now() / 300 ) / 2 ; 48 | sprControler.tentaclesSprites[2].position.y = Math.sin( Date.now() / 250 ) / 2 ; 49 | 50 | // Update the pharaoh 51 | 52 | if ( pharaohFollows ) { 53 | pharaohVecTarget.set( 54 | THREE.Math.lerp( group.position.x, logicCube.position.x, 0.7 ), 55 | getTrackInterpol( THREE.Math.lerp( group.position.x, logicCube.position.x, 0.7 ) ), 56 | logicCube.position.z 57 | ); 58 | pharaohVecTarget.y += Math.sin( Date.now() / 300 ) / 3 ; 59 | pharaohVecTarget.z += 0.5 ; 60 | sprControler.getPharaoh().position.lerp( pharaohVecTarget, 0.05 ); 61 | }; 62 | 63 | }; 64 | 65 | }; 66 | 67 | 68 | 69 | 70 | // This function return a linear interpolation between two points 71 | // in the chaser track, so we get a Y position varying smoothly 72 | function getTrackInterpol( xValue ) { 73 | return THREE.Math.lerp( 74 | atlas.chaserTrack[ Math.floor( xValue ) ], 75 | atlas.chaserTrack[ Math.ceil( xValue ) ], 76 | xValue - Math.floor( xValue ) ); 77 | }; 78 | 79 | 80 | 81 | 82 | function start() { 83 | params.isRunning = true; 84 | group.add( sprControler.tentaclesGroup ); 85 | sprControler.enablePharaoh(); 86 | 87 | // Make the pharaoh head levitate for a second or two, 88 | // then follow the player 89 | let token = setInterval( ()=> { 90 | sprControler.getPharaoh().position.y += 0.01 ; 91 | if ( sprControler.getPharaoh().position.y > 4.2 ) { 92 | clearInterval( token ); 93 | // follow player 94 | setTimeout( ()=> { 95 | pharaohFollows = true ; 96 | }, 500); 97 | }; 98 | }, 20); 99 | 100 | }; 101 | 102 | function stop() { 103 | params.isRunning = false; 104 | pharaohFollows = false ; 105 | group.remove( sprControler.tentaclesGroup ); 106 | group.position.copy( chaser.params.startVec ); 107 | sprControler.disablePharaoh(); 108 | sprControler.getPharaoh().position.set( 7.5, 3.8, 1.5 ); 109 | }; 110 | 111 | 112 | 113 | return { 114 | update, 115 | start, 116 | stop, 117 | group, 118 | params 119 | }; 120 | 121 | }; -------------------------------------------------------------------------------- /public/js/Game.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function Game() { 4 | 5 | 6 | const PLAYERSPAWNVEC = new THREE.Vector3( 10, 4, 1 ) ; 7 | // const PLAYERSPAWNVEC = new THREE.Vector3(57, 9, 1) 8 | 9 | // OLD STARTS 10 | // const PLAYERSTARTVEC = new THREE.Vector3( 26, 5.5, 1 ) ; 11 | // const PLAYERSTARTVEC = new THREE.Vector3( 135, 30, 1 ) ; 12 | 13 | 14 | // const PLAYERSTARTVEC = new THREE.Vector3( 143, 15, 1 ) ; // end 15 | const PLAYERSTARTVEC = new THREE.Vector3( 41.75, 15, 1 ) ; // TRUE START 16 | 17 | const STARTBUTTONPOS = 9 ; 18 | 19 | var domGameoverOverlay = document.getElementById('gameover-overlay'); 20 | var domFailMenu = document.getElementById('fail-menu'); 21 | var domWinMenu = document.getElementById('win-menu'); 22 | var domInventory = document.getElementById('inventory'); 23 | 24 | 25 | var light = new THREE.PointLight( 0xf5ffff, 2, 6, 2 ); 26 | light.position.set( 145, 14, 1.5 ); 27 | scene.add( light ); 28 | 29 | 30 | 31 | square.moveTo( PLAYERSTARTVEC ); 32 | 33 | createChaserWall(); 34 | createInitWall(); 35 | 36 | 37 | 38 | 39 | 40 | function createChaserWall() { 41 | atlas.TempObstacle( 42 | "chaser_wall", 43 | new THREE.Vector3( 6, 15, 4 ), 44 | true 45 | ); 46 | }; 47 | 48 | function createInitWall() { 49 | atlas.TempObstacle( 50 | "init_wall", 51 | new THREE.Vector3( 44, 15, 4 ), 52 | true 53 | ); 54 | }; 55 | 56 | 57 | 58 | 59 | 60 | function fail() { 61 | 62 | gamePaused = true ; 63 | gameFinished = true ; 64 | 65 | domInventory.style.display = "none" ; 66 | domGameoverOverlay.style.display = "inherit"; 67 | domWinMenu.style.display = "none"; 68 | domFailMenu.style.display = "inherit"; 69 | 70 | document.getElementById('fail-time-text').innerHTML = 71 | "You survived " + survivalTimeCounter.toFixed(1) + " seconds"; 72 | survivalTimeCounter = 0 ; 73 | }; 74 | 75 | 76 | 77 | function win() { 78 | 79 | gamePaused = true ; 80 | gameFinished = true ; 81 | 82 | domInventory.style.display = "none" ; 83 | domGameoverOverlay.style.display = "inherit"; 84 | domWinMenu.style.display = "inherit"; 85 | domFailMenu.style.display = "none"; 86 | 87 | document.getElementById('win-time-text').innerHTML = 88 | "You reached the exit in " + survivalTimeCounter.toFixed(1) + " seconds"; 89 | survivalTimeCounter = 0 ; 90 | 91 | document.getElementById('loot-text').innerHTML = 92 | 'You looted ' + treasures.getFoundTreasures().length + 93 | " / " + treasures.treasures.length + ' gems' ; 94 | }; 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | function start() { 103 | 104 | survivalTimeCounter = 0.01 ; 105 | 106 | chaser.start(); 107 | atlas.removeTempObstacle( 'init_wall' ); 108 | controler.movementEnabled = false ; 109 | 110 | // wall opens 111 | setTimeout( ()=> { 112 | atlas.removeTempObstacle( 'chaser_wall' ); 113 | }, 500); 114 | 115 | // player can run 116 | setTimeout( ()=> { 117 | controler.movementEnabled = true ; 118 | }, 0); // was 2500 119 | }; 120 | 121 | 122 | function restartGame() { 123 | treasures.clearInventory(); 124 | chaser.stop(); 125 | createChaserWall(); 126 | createInitWall(); 127 | square.moveTo( PLAYERSPAWNVEC ); 128 | }; 129 | 130 | 131 | return { 132 | fail, 133 | win, 134 | start, 135 | restartGame, 136 | startButtonPos: STARTBUTTONPOS, 137 | createChaserWall, 138 | createInitWall 139 | }; 140 | }; -------------------------------------------------------------------------------- /public/js/Treasures.js: -------------------------------------------------------------------------------- 1 | 2 | function Treasures() { 3 | 4 | var treasures = []; 5 | 6 | var domInventory = document.getElementById('inventory'); 7 | var domCount = document.getElementById('inventory-count'); 8 | 9 | 10 | 11 | 12 | initTreasure( './assets/gem.png', 'purple_gem', [8.5, 2.6, 1.5], 0.7 ); // start 13 | initTreasure( './assets/gem.png', 'thing', [47.5, 2.5, 0.5], 0.7 ); // between the two walls 14 | initTreasure( './assets/gem.png', 'truc', [59.5, 5.5, 0.5], 0.7 ); // in small stairs 15 | initTreasure( './assets/gem.png', 'red_gem', [69.5, 8.7, 0.5], 0.7 ); // bottom big stairs 16 | initTreasure( './assets/gem.png', 'green_gem', [95.5, 12.3, 2.5], 0.7 ); // first chamber 17 | initTreasure( './assets/gem.png', 'yellow_gem', [109.5, 10, 2.5], 0.7 ); // second chamber 18 | initTreasure( './assets/gem.png', 'diamond', [131.5, 13.8, 0.5], 0.7 ); // on column 19 | 20 | 21 | 22 | function initTreasure( url, name, arrPos, scale ) { 23 | 24 | textureLoader.load( url, (texture)=> { 25 | 26 | texture.magFilter = THREE.NearestFilter; 27 | 28 | let spriteMaterial = new THREE.SpriteMaterial({ map:texture, color:0xffffff }); 29 | sprite = new THREE.Sprite( spriteMaterial ); 30 | 31 | sprite.position.set( arrPos[0], arrPos[1], arrPos[2] ); 32 | sprite.scale.set( scale, scale, scale ); 33 | 34 | scene.add( sprite ); 35 | 36 | treasures.push({ 37 | url, 38 | sprite, 39 | name, 40 | track: sprite.position.z - 0.5, 41 | found: false 42 | }); 43 | 44 | updateInventoryCounter(); 45 | 46 | }); 47 | }; 48 | 49 | 50 | 51 | 52 | function findTreasure( name ) { 53 | return treasures.find( (treasure)=> { 54 | return (treasure.name == name) ; 55 | }); 56 | }; 57 | 58 | 59 | 60 | 61 | 62 | function testCollision( logicCube ) { 63 | 64 | treasures.forEach( (treasure)=> { 65 | 66 | // check if the player is on the same track as the treasure 67 | // and if the treasure is not already found 68 | if ( treasure.track == logicCube.position.z && 69 | !treasure.found ) { 70 | 71 | let cubeLeftPoint = logicCube.position ; 72 | let treasurePoint = treasure.sprite.position ; 73 | 74 | // check if collision 75 | if ( cubeLeftPoint.x < treasurePoint.x && 76 | cubeLeftPoint.x + logicCube.width > treasurePoint.x ) { 77 | 78 | // treasure disappears and it's added to the inventory 79 | addToInventory( treasure ); 80 | 81 | // start the game if the treasure is the purple gem 82 | if ( treasure.name == 'purple_gem' ) game.start(); 83 | }; 84 | 85 | }; 86 | 87 | }); 88 | 89 | }; 90 | 91 | 92 | 93 | 94 | function addToInventory( treasure ) { 95 | 96 | treasure.sprite.visible = false ; 97 | treasure.found = true ; 98 | 99 | let domIMG = document.createElement( 'IMG' ); 100 | domIMG.classList.add( 'inventory-item' ); 101 | domIMG.classList.add( 'add-inventory' ); 102 | domIMG.src = treasure.url ; 103 | domInventory.appendChild( domIMG ); 104 | 105 | updateInventoryCounter(); 106 | }; 107 | 108 | 109 | 110 | function clearInventory() { 111 | 112 | /// Reseting of treasures attributes 113 | 114 | treasures.forEach( (treasure)=> { 115 | 116 | if ( treasure.found ) { 117 | treasure.found = false; 118 | }; 119 | 120 | treasure.sprite.visible = true ; 121 | }); 122 | 123 | 124 | updateInventoryCounter(); 125 | 126 | 127 | /// Removal from the html inventory UI 128 | 129 | let domElements = document.getElementsByClassName('inventory-item'); 130 | 131 | for ( let i=domElements.length ; i>0 ; i-- ) { 132 | 133 | if ( domElements[i] && domElements[i].tagName == 'IMG' ) { 134 | domInventory.removeChild( domElements[i] ); 135 | }; 136 | 137 | }; 138 | 139 | }; 140 | 141 | 142 | 143 | 144 | function updateInventoryCounter() { 145 | domCount.innerHTML = getFoundTreasures().length + " / " + treasures.length ; 146 | }; 147 | 148 | 149 | 150 | function getFoundTreasures() { 151 | return treasures.reduce( (accu, treasure)=> { 152 | if ( treasure.found == true ) accu.push( treasure ); 153 | return accu ; 154 | }, []) 155 | }; 156 | 157 | 158 | 159 | 160 | 161 | return { 162 | testCollision, 163 | findTreasure, 164 | clearInventory, 165 | treasures, 166 | getFoundTreasures 167 | }; 168 | 169 | 170 | }; -------------------------------------------------------------------------------- /public/js/SprControler.js: -------------------------------------------------------------------------------- 1 | 2 | function SprControler() { 3 | 4 | spriteMixer = SpriteMixer(); 5 | 6 | var currentAction, currentMovement; 7 | 8 | var charaSprite, pharaohSprite; 9 | 10 | var walkRight, walkLeft, idleRight, idleLeft; 11 | var pharaohShine; 12 | 13 | var tentaclesSprites = []; 14 | var tentaclesActions = []; 15 | var tentaclesGroup = new THREE.Group(); 16 | 17 | 18 | 19 | createTentacles(); 20 | 21 | 22 | 23 | textureLoader.load( "./assets/spr_character.png", (texture)=> { 24 | 25 | texture.magFilter = THREE.NearestFilter; 26 | 27 | charaSprite = spriteMixer.ActionSprite( texture, 4, 8 ); 28 | charaSprite.setFrame( 0 ); 29 | 30 | charaSprite.scale.set( 0.8, 0.8, 0.8 ); 31 | charaSprite.position.copy( square.position ); 32 | charaSprite.position.z += 0.5; 33 | charaSprite.position.y += 0.4; 34 | charaSprite.position.x += 0.25; 35 | square.sprite = charaSprite ; 36 | 37 | 38 | 39 | // Actions 40 | 41 | walkLeft = spriteMixer.Action( charaSprite, 0, 3, 85 ); 42 | walkRight = spriteMixer.Action( charaSprite, 4, 7, 85 ); 43 | idleRight = spriteMixer.Action( charaSprite, 8, 9, 185 ); 44 | idleLeft = spriteMixer.Action( charaSprite, 10, 11, 185 ); 45 | 46 | 47 | 48 | scene.add( charaSprite ); 49 | 50 | }); 51 | 52 | 53 | 54 | 55 | textureLoader.load( './assets/pharaoh.png', (texture)=> { 56 | 57 | texture.magFilter = THREE.NearestFilter; 58 | 59 | pharaohSprite = spriteMixer.ActionSprite( texture, 4, 2 ); 60 | pharaohSprite.setFrame( 0 ); 61 | 62 | pharaohSprite.scale.set( 0.75, 0.75, 0.75 ); 63 | pharaohSprite.position.set( 7.5, 3.8, 1.5 ); 64 | 65 | pharaohShine = spriteMixer.Action( pharaohSprite, 1, 4, 70 ); 66 | 67 | scene.add( pharaohSprite ); 68 | 69 | }) 70 | 71 | 72 | 73 | 74 | 75 | 76 | function createTentacles() { 77 | 78 | for (let i=0 ; i<3 ; i++) { 79 | 80 | textureLoader.load( './assets/tentacles.png', (texture)=> { 81 | 82 | texture.magFilter = THREE.NearestFilter; 83 | 84 | tentaclesSprites[i] = spriteMixer.ActionSprite( texture, 1, 2 ); 85 | 86 | tentaclesSprites[i].scale.set( 7, 7, 7 ); 87 | tentaclesSprites[i].position.set( 0, 0, (0.5 + i) / 2 ); 88 | 89 | tentaclesActions[i] = spriteMixer.Action( tentaclesSprites[i], 0, 1, 200 ); 90 | tentaclesActions[i].playLoop(); 91 | 92 | tentaclesGroup.add( tentaclesSprites[i] ); 93 | 94 | }); 95 | }; 96 | 97 | }; 98 | 99 | 100 | 101 | 102 | 103 | 104 | function enablePharaoh() { 105 | pharaohShine.playLoop(); 106 | }; 107 | 108 | 109 | function disablePharaoh() { 110 | pharaohSprite.setFrame( 0 ); 111 | }; 112 | 113 | 114 | function getPharaoh() { 115 | return pharaohSprite; 116 | }; 117 | 118 | 119 | 120 | 121 | 122 | function setAction( name ) { 123 | 124 | // stop function is requested movement is the currently 125 | // played one. 126 | if ( this.currentMovement == name ) return 127 | 128 | 129 | switch( name ) { 130 | case 'walkLeft' : 131 | walkLeft.playLoop(); 132 | currentAction = walkLeft ; 133 | this.currentMovement = 'walkLeft'; 134 | break; 135 | case 'walkRight' : 136 | walkRight.playLoop(); 137 | currentAction = walkRight ; 138 | this.currentMovement = 'walkRight'; 139 | break; 140 | case 'idleLeft' : 141 | idleLeft.playLoop(); 142 | currentAction = idleLeft ; 143 | this.currentMovement = 'idleLeft'; 144 | break; 145 | case 'idleRight' : 146 | idleRight.playLoop(); 147 | currentAction = idleRight ; 148 | this.currentMovement = 'idleRight'; 149 | break; 150 | case 'jumpRight' : 151 | if (currentAction) currentAction.stop(); 152 | charaSprite.setFrame( 14 ) ; 153 | setTimeout( ()=> { 154 | charaSprite.setFrame( 12 ) ; 155 | }, 90); 156 | this.currentMovement = 'jumpRight'; 157 | break; 158 | case 'jumpLeft' : 159 | if (currentAction) currentAction.stop(); 160 | charaSprite.setFrame( 15 ) ; 161 | setTimeout( ()=> { 162 | charaSprite.setFrame( 13 ) ; 163 | }, 90); 164 | this.currentMovement = 'jumpLeft'; 165 | break; 166 | case 'faceBack' : 167 | if (currentAction) currentAction.stop(); 168 | charaSprite.setFrame( 16 ); 169 | this.currentMovement = 'faceBack'; 170 | break; 171 | case 'faceFront' : 172 | if (currentAction) currentAction.stop(); 173 | charaSprite.setFrame( 17 ); 174 | this.currentMovement = 'faceFront'; 175 | break; 176 | }; 177 | 178 | }; 179 | 180 | 181 | 182 | 183 | 184 | return { 185 | setAction, 186 | currentMovement, 187 | tentaclesGroup, 188 | enablePharaoh, 189 | disablePharaoh, 190 | getPharaoh, 191 | tentaclesSprites 192 | }; 193 | 194 | }; -------------------------------------------------------------------------------- /public/js/SceneGenerator.js: -------------------------------------------------------------------------------- 1 | 2 | function SceneGenerator( pointsArrays ) { 3 | 4 | 5 | const TRACKSCOLORS = [ "red", "blue", "green" ]; 6 | 7 | const extrudeSettings = { 8 | steps: 1, 9 | extrudePath: new THREE.LineCurve3( 10 | new THREE.Vector3(0, 0, 0), 11 | new THREE.Vector3(0, 0, 1) 12 | ) 13 | }; 14 | 15 | 16 | 17 | var shapes = []; 18 | 19 | var shiftLeftVec = new THREE.Vector3( 1, 0, 0 ); 20 | var shiftBottomVec = new THREE.Vector3( 0, -80, 0 ); 21 | var shiftTopVec = new THREE.Vector3( 0, 80, 0 ); 22 | 23 | 24 | var wallsMaterial = new THREE.MeshLambertMaterial({ color: 0xf5dd90 }); 25 | 26 | var sliceMaterial = new THREE.MeshBasicMaterial( { color:0x292417 } ); 27 | 28 | 29 | 30 | /// create water bellow y = 0 ; 31 | let mesh = new THREE.Mesh( new THREE.BoxBufferGeometry(500, 100, 3), 32 | new THREE.MeshBasicMaterial({color:0x07004a}) ); 33 | mesh.position.y = -50 ; 34 | mesh.position.z = 1.5 ; 35 | scene.add( mesh ); 36 | 37 | 38 | 39 | /* 40 | textureLoader.load( './assets/perlin-512.png', (texture)=> { 41 | 42 | texture.wrapS = THREE.RepeatWrapping; 43 | texture.wrapT = THREE.RepeatWrapping; 44 | texture.repeat.set( 0.008, 0.008 ); 45 | texture.magFilter = THREE.NearestFilter; 46 | 47 | wallsMaterial.map = texture ; 48 | wallsMaterial.color = new THREE.Color('blue') ; 49 | }, null, (err)=> { 50 | throw err ; 51 | }); 52 | */ 53 | 54 | 55 | // This create new points between each existing point to create the steps, 56 | // and add two points very low to make the shape visible on the bottom 57 | // of the atlas. 58 | pointsArrays.forEach( (points)=> { 59 | 60 | let newArr = points.reduce( (accu, value, i, arr)=> { 61 | 62 | accu.push(value); 63 | accu.push( new THREE.Vector3() 64 | .copy( value ) 65 | .add( shiftLeftVec ) ); 66 | 67 | 68 | // If this is the end of the array, we add the two points 69 | // far bellow the scene, to make the shape extend toward 70 | // the bottom of the screen 71 | if ( i == arr.length -1 ) { 72 | 73 | accu.push( new THREE.Vector3() 74 | .copy( arr[i] ) 75 | .add( shiftBottomVec ) ); 76 | 77 | accu.push( new THREE.Vector3() 78 | .copy( arr[0] ) 79 | .add( shiftBottomVec ) ); 80 | }; 81 | 82 | return accu ; 83 | 84 | }, []); 85 | 86 | shapes.push( new THREE.Shape( newArr ) ); 87 | 88 | }); 89 | 90 | 91 | 92 | 93 | 94 | // createShape() takes the array of points representing one dimension 95 | // in the game, and one color to apply to the shape. 96 | function generateShapes() { 97 | 98 | shapes.forEach( (shape, i)=> { 99 | var geometry = new THREE.ShapeGeometry( shape ); 100 | var material = new THREE.MeshBasicMaterial( { color:TRACKSCOLORS[i] } ); 101 | var mesh = new THREE.Mesh( geometry, material ) ; 102 | mesh.position.z = i ; 103 | scene.add( mesh ); 104 | }); 105 | }; 106 | 107 | 108 | 109 | 110 | 111 | function generateExtrusions( isTexturedMat ) { 112 | 113 | shapes.forEach( (shape, i)=> { 114 | 115 | let geometry = new THREE.ExtrudeBufferGeometry( shape, extrudeSettings ); 116 | let material = isTexturedMat ? wallsMaterial : new THREE.MeshLambertMaterial( { color:TRACKSCOLORS[i] } ); 117 | let mesh = new THREE.Mesh( geometry, material ) ; 118 | mesh.position.z = i ; 119 | mesh.rotation.z = Math.PI / 2 ; 120 | scene.add( mesh ); 121 | 122 | if ( i == 2 ) { 123 | var shapeGeom = new THREE.ShapeGeometry( shape ); 124 | var shapeMesh = new THREE.Mesh( shapeGeom, sliceMaterial ) ; 125 | shapeMesh.position.z = 3.001 ; 126 | scene.add( shapeMesh ); 127 | }; 128 | 129 | }); 130 | 131 | }; 132 | 133 | 134 | 135 | 136 | function generateRoof( roofLevels ) { 137 | 138 | var points = roofLevels.reduce( (accu, value, i, arr)=> { 139 | 140 | let point = new THREE.Vector3( i, value, 0 ); 141 | 142 | accu.push(point); 143 | accu.push( new THREE.Vector3() 144 | .copy( point ) 145 | .add( shiftLeftVec ) ); 146 | 147 | if ( i == arr.length -1 ) { 148 | 149 | accu.push( new THREE.Vector3() 150 | .copy( accu[i*2] ) 151 | .add( shiftTopVec ) ); 152 | 153 | accu.push( new THREE.Vector3() 154 | .copy( accu[0] ) 155 | .add( shiftTopVec ) ); 156 | }; 157 | 158 | return accu ; 159 | 160 | }, []); 161 | 162 | let shape = new THREE.Shape( points ); 163 | 164 | 165 | var settings = { 166 | steps: 1, 167 | extrudePath: new THREE.LineCurve3( 168 | new THREE.Vector3(0, 0, 0), 169 | new THREE.Vector3(0, 0, 3) 170 | ) 171 | }; 172 | 173 | let geometry = new THREE.ExtrudeBufferGeometry( shape , settings ); 174 | let mesh = new THREE.Mesh( geometry, wallsMaterial ) ; 175 | 176 | mesh.rotation.z = Math.PI / 2 ; 177 | scene.add( mesh ); 178 | 179 | 180 | var shapeGeom = new THREE.ShapeGeometry( shape ); 181 | var shapeMesh = new THREE.Mesh( shapeGeom, sliceMaterial ) ; 182 | shapeMesh.position.z = 3.001 ; 183 | scene.add( shapeMesh ); 184 | 185 | }; 186 | 187 | 188 | 189 | 190 | 191 | return { 192 | generateShapes, 193 | generateExtrusions, 194 | generateRoof 195 | }; 196 | 197 | }; -------------------------------------------------------------------------------- /public/js/Controler.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | function Controler( logicSquare ) { 5 | 6 | const square = logicSquare; 7 | 8 | const RUNSPEED = 0.035 ; 9 | const LEAPSPEED = 0.1 ; 10 | const LEAPPOWER = 0.3 ; 11 | const FALLSPEED = -0.2 ; 12 | const STEPPINGSPEED = 0.3 ; 13 | 14 | var run = 0 ; 15 | var leap = 0 ; 16 | var leapLevel = 0 ; 17 | 18 | var hasShifted = false ; 19 | var hasJumped = false ; 20 | 21 | var mustStep = false ; 22 | var steppingTimeout = 0 ; 23 | var stepDirection; 24 | 25 | var fallRatio = 0 ; 26 | 27 | var lastDirection = 'left' ; 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | function update( delta ) { 37 | 38 | 39 | ///// Actions starting 40 | 41 | if ( this.movementEnabled ) { 42 | 43 | // If both right and left arrows are pressed, character is stilled, 44 | // but if right or left arrow is pressed alone, 'run' variable is 45 | // set, so the character start moving next time 'run' is checked. 46 | // If neither right nor left arrow is pressed, character is stilled. 47 | if ( (keys.isPressed.left && keys.isPressed.right) || 48 | (!keys.isPressed.left && !keys.isPressed.right) ) { 49 | // make the character still 50 | run = 0 ; 51 | // If the player does not push an arrow, the character 52 | // will not step over a step. 53 | mustStep = false ; 54 | } else if ( keys.isPressed.left ) { 55 | run = -RUNSPEED ; 56 | } else if ( keys.isPressed.right ) { 57 | run = RUNSPEED ; 58 | }; 59 | 60 | 61 | // If both up en down arrows are either not pressed or both pressed, 62 | // 'hasShifted' variable is set to false, so the character can shift 63 | // once again. If one of these arrow is pressed, and 'hasShifted' is 64 | // true, then the character is shifted. This is intended to keep the 65 | // user from shifting too much by mistake. 66 | if ( (keys.isPressed.up && keys.isPressed.down) || 67 | (!keys.isPressed.up && !keys.isPressed.down) ) { 68 | 69 | hasShifted = false ; 70 | 71 | } else if ( hasShifted == false && keys.isPressed.up ) { 72 | 73 | if ( square.position.z > 0 ) { 74 | hasShifted = square.shift( -1 ) ; 75 | }; 76 | 77 | } else if ( hasShifted == false && keys.isPressed.down ) { 78 | 79 | if ( square.position.z < 2 ) { 80 | hasShifted = square.shift( 1 ) ; 81 | }; 82 | }; 83 | 84 | 85 | 86 | if ( keys.isPressed.space ) { 87 | 88 | if ( hasJumped == false && leap == 0 && 89 | !square.isFlying() ) { 90 | 91 | leap = LEAPSPEED; 92 | hasJumped = true ; 93 | }; 94 | 95 | } else { 96 | hasJumped = false ; 97 | }; 98 | 99 | }; 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | ///// Actions handling 109 | 110 | 111 | // This statement occur after the player has walked against a steppable 112 | // ostacle during some time, represented by steppingTimeout. 113 | if ( steppingTimeout >= 1 ) { 114 | 115 | // steppingTimeout is reused to play the stepping action. 116 | // mustStep is re-set to true, is case the player stopped 117 | // walking, to make this action unavoidable once started. 118 | if ( steppingTimeout - 1 < 2 ) { 119 | square.step( (steppingTimeout - 1) / 2, stepDirection ); 120 | mustStep = true ; 121 | 122 | // reset all the stepping variable, the action is finished 123 | } else { 124 | steppingTimeout = 0 ; 125 | mustStep = false ; 126 | }; 127 | }; 128 | 129 | 130 | // if mustStep is true, it means that the player is walking against a 131 | // steppable obstacle. steppingTimeout in incremented, so that when it 132 | // reach a given value, the stepping action will occur. 133 | // If mustStep is false, steppingTimeout is reset. 134 | if ( mustStep ) { 135 | steppingTimeout += STEPPINGSPEED ; 136 | } else { 137 | steppingTimeout = 0 ; 138 | }; 139 | 140 | 141 | 142 | 143 | 144 | // we check if steppingTimeout < 1 because running 145 | // must not occur when the character is stepping 146 | if ( run != 0 && steppingTimeout < 1 ) { 147 | 148 | if ( !logicSquare.isFlying() ) { 149 | sprControler.setAction( (run > 0 ? 'walkRight' : 'walkLeft') ); 150 | }; 151 | 152 | walk( run ); 153 | 154 | } else if ( !logicSquare.isFlying() ) { 155 | 156 | if ( lastDirection == 'right' ) { 157 | 158 | sprControler.setAction( 'idleRight' ); 159 | 160 | } else { 161 | 162 | sprControler.setAction( 'idleLeft' ); 163 | }; 164 | }; 165 | 166 | 167 | 168 | 169 | 170 | 171 | // handle the leaps 172 | if ( leap > 0 ) { 173 | 174 | leap += LEAPSPEED ; 175 | leapLevel = Math.sin( leap ); 176 | 177 | if ( leap == 0.2 ) { 178 | sprControler.setAction( lastDirection == 'left' ? 'jumpLeft' : 'jumpRight' ); 179 | }; 180 | 181 | if ( leap < 1.5 ) { 182 | leapOffset( 1 - leapLevel ); 183 | } else { 184 | leap = 0 ; 185 | }; 186 | }; 187 | 188 | 189 | 190 | 191 | if ( leap == 0 && 192 | square.isFlying() && 193 | steppingTimeout < 1 ) { 194 | 195 | fallRatio = fallRatio >= 1 ? 1 : fallRatio + 0.1 ; 196 | fall(); 197 | 198 | } else { 199 | fallRatio = 0 ; 200 | }; 201 | 202 | 203 | }; 204 | 205 | 206 | 207 | function walk( offset ) { 208 | 209 | square.move( offset, 0, 0 ); 210 | 211 | lastDirection = offset > 0 ? 'right' : 'left' ; 212 | 213 | // set mustStep to its default, it will be set again to true in the 214 | // next statement if needed 215 | mustStep = false ; 216 | 217 | // keep the cube from entering a wall 218 | if ( (offset > 0 && square.collision.right > 0) || 219 | (offset < 0 && square.collision.left > 0) ) { 220 | 221 | // If the step facing the square is smaller than half or equal to 222 | // the half of its height, then the square is set to step this step 223 | if ( (square.height - square.collision.right) >= square.height/2 && 224 | (square.height - square.collision.left) >= square.height/2 ) { 225 | 226 | mustStep = true ; 227 | stepDirection = square.collision.right > 0 ? 'right' : 'left'; 228 | }; 229 | 230 | square.move( - offset, 0, 0 ); 231 | 232 | // start the stepping action whatever steppingTimeout hold, 233 | // to act as if the character catch an edge after jumbing 234 | if ( square.isFlying() && mustStep && steppingTimeout < 1 ) { 235 | steppingTimeout = 1 ; 236 | }; 237 | }; 238 | }; 239 | 240 | 241 | 242 | 243 | function leapOffset( offset ) { 244 | square.move( 0, offset * LEAPPOWER , 0 ); 245 | 246 | }; 247 | 248 | 249 | function fall() { 250 | 251 | square.move( 0, FALLSPEED * fallRatio, 0 ); 252 | 253 | // keep the cube from entering the ground 254 | if ( square.collision.right > 0 ) { 255 | square.move( 0, square.collision.right, 0 ); 256 | } else if ( square.collision.left > 0 ) { 257 | square.move( 0, square.collision.left, 0 ); 258 | }; 259 | }; 260 | 261 | 262 | 263 | return { 264 | update, 265 | movementEnabled: true, 266 | }; 267 | 268 | }; 269 | -------------------------------------------------------------------------------- /public/js/libs/SpriteMixer.js: -------------------------------------------------------------------------------- 1 | 2 | // Author: Felix Mariotto 3 | 4 | // Based on Lee Stemkoski's work who coded the core texture offsetting part : 5 | // http://stemkoski.github.io/Three.js/Texture-Animation.html 6 | 7 | function SpriteMixer() { 8 | 9 | var actionSprites = []; // Will store every new actionSprite. 10 | var listeners = []; // Will store the user callbacks to call upon loop, finished, etc.. 11 | 12 | 13 | var api = { 14 | actionSprites: actionSprites, 15 | update: update, 16 | ActionSprite: ActionSprite, 17 | Action: Action, 18 | addEventListener: addEventListener 19 | }; 20 | 21 | 22 | 23 | // It can be used to make SpriteMixer call a callback function 24 | // when an action is finished or looping. eventName argument can 25 | // be either "finished" or "loop". 26 | function addEventListener( eventName, callback ) { 27 | if ( eventName && callback ) { 28 | listeners.push( { eventName, callback } ); 29 | } else { 30 | throw 'Error : an argument is missing'; 31 | }; 32 | }; 33 | 34 | 35 | // Update every stored actionSprite if needed. 36 | function update( delta ) { 37 | actionSprites.forEach( (actionSprite)=> { 38 | if ( actionSprite.paused == false ) { 39 | updateAction( actionSprite.currentAction, delta * 1000 ); 40 | }; 41 | }); 42 | }; 43 | 44 | 45 | 46 | // This offsets the texture to make the next frame of the animation appear. 47 | function offsetTexture( actionSprite ) { 48 | actionSprite.material.map.offset.x = actionSprite.getColumn() / actionSprite.tilesHoriz; 49 | actionSprite.material.map.offset.y = (actionSprite.tilesVert - actionSprite.getRow() -1 ) / actionSprite.tilesVert; 50 | }; 51 | 52 | 53 | 54 | // This is called during the loop, it first check if the animation must 55 | // be updated, then increment actionSprite.currentTile, then call offsetTexture(). 56 | // Various operations are made depending on clampWhenFinished and hideWhenFinished 57 | // options. 58 | function updateAction( action, milliSec ) { 59 | 60 | action.actionSprite.currentDisplayTime += milliSec; 61 | 62 | while (action.actionSprite.currentDisplayTime > action.tileDisplayDuration) { 63 | 64 | action.actionSprite.currentDisplayTime -= action.tileDisplayDuration; 65 | action.actionSprite.currentTile = (action.actionSprite.currentTile + 1) ; 66 | 67 | // Restarts the animation if the last frame was reached at last call. 68 | if (action.actionSprite.currentTile > action.indexEnd) { 69 | 70 | action.actionSprite.currentTile = action.indexStart ; 71 | 72 | // Call the user callbacks on the event 'loop' 73 | if ( action.mustLoop == true ) { 74 | 75 | listeners.forEach( (listener)=> { 76 | if ( listener.eventName == 'loop' ) { 77 | listener.callback({ 78 | type:'loop', 79 | action: action 80 | }); 81 | }; 82 | }); 83 | 84 | } else { // action must not loop 85 | 86 | if ( action.clampWhenFinished == true ) { 87 | 88 | action.actionSprite.paused = true ; 89 | 90 | if (action.hideWhenFinished == true) { 91 | action.actionSprite.visible = false ; 92 | }; 93 | 94 | callFinishedListeners( action ); 95 | 96 | } else { // must restart the animation before to stop 97 | 98 | action.actionSprite.paused = true ; 99 | 100 | if (action.hideWhenFinished == true) { 101 | action.actionSprite.visible = false ; 102 | }; 103 | 104 | // Call updateAction() a last time after a frame duration, 105 | // even if the action is actually paused before, in order to restart 106 | // the animation. 107 | setTimeout( ()=> { 108 | updateAction( action, action.tileDisplayDuration ); 109 | callFinishedListeners( action ); 110 | }, action.tileDisplayDuration); 111 | 112 | }; 113 | }; 114 | }; 115 | 116 | 117 | offsetTexture( action.actionSprite ); 118 | 119 | 120 | // Call the user callbacks on the event 'finished'. 121 | function callFinishedListeners( action ) { 122 | listeners.forEach( (listener)=> { 123 | if ( listener.eventName == 'finished' ) { 124 | listener.callback({ 125 | type:'finished', 126 | action: action 127 | }); 128 | }; 129 | }, action.tileDisplayDuration ); 130 | }; 131 | 132 | 133 | }; 134 | 135 | }; 136 | 137 | 138 | 139 | // reveal the sprite and play the action only once 140 | function playOnce() { 141 | this.mustLoop = false ; 142 | this.actionSprite.currentAction = this ; 143 | this.actionSprite.currentTile = this.indexStart ; 144 | offsetTexture( this.actionSprite ); 145 | this.actionSprite.paused = false ; 146 | this.actionSprite.visible = true ; 147 | }; 148 | 149 | // resume the action if it was paused 150 | function resume() { 151 | // this is in case setFrame was used to set a frame outside of the 152 | // animation range, which would lead to bugs. 153 | if ( this.currentTile > this.indexStart && 154 | this.currentTile < this.indexEnd ) { 155 | this.currentTile = this.indexStart; 156 | }; 157 | this.actionSprite.paused = false ; 158 | this.actionSprite.visible = true ; 159 | }; 160 | 161 | // reveal the sprite and play it in a loop 162 | function playLoop() { 163 | this.mustLoop = true ; 164 | this.actionSprite.currentAction = this ; 165 | this.actionSprite.currentTile = this.indexStart ; 166 | offsetTexture( this.actionSprite ); 167 | this.actionSprite.paused = false ; 168 | this.actionSprite.visible = true ; 169 | }; 170 | 171 | // pause the action when it reach the last frame 172 | function pauseNextEnd() { 173 | this.mustLoop = false; 174 | }; 175 | 176 | // pause the action on the current frame 177 | function pause() { 178 | this.actionSprite.paused = true ; 179 | }; 180 | 181 | // pause and reset the action 182 | function stop() { 183 | this.actionSprite.currentDisplayTime = 0; 184 | this.actionSprite.currentTile = this.indexStart; 185 | this.actionSprite.paused = true ; 186 | if (this.hideWhenFinished == true) { 187 | this.actionSprite.visible = false ; 188 | }; 189 | offsetTexture( this.actionSprite ); 190 | }; 191 | 192 | // Set manually a frame of the animation. Frame indexing starts at 0. 193 | function setFrame( frameID ) { 194 | this.paused = true ; 195 | this.currentTile = frameID; 196 | offsetTexture( this ); 197 | }; 198 | 199 | // returns the row of the current tile. 200 | function getRow() { 201 | return Math.floor(this.currentTile / this.tilesHoriz); 202 | }; 203 | 204 | // returns the column of the current tile. 205 | function getColumn() { 206 | return this.currentTile % this.tilesHoriz; 207 | }; 208 | 209 | 210 | 211 | 212 | 213 | function ActionSprite( texture, tilesHoriz, tilesVert ) { 214 | 215 | texture.wrapS = texture.wrapT = THREE.RepeatWrapping; 216 | texture.repeat.set( 1/tilesHoriz, 1/tilesVert ); 217 | 218 | let spriteMaterial = new THREE.SpriteMaterial({ 219 | map:texture, color:0xffffff}); 220 | 221 | let actionSprite = new THREE.Sprite(spriteMaterial); 222 | actionSprite.isIndexedSprite = true ; 223 | 224 | actionSprite.tilesHoriz = tilesHoriz ; 225 | actionSprite.tilesVert = tilesVert ; 226 | actionSprite.tiles = (tilesHoriz * tilesVert) ; 227 | actionSprite.currentDisplayTime = 0 ; 228 | actionSprite.currentTile = 0 ; 229 | actionSprite.paused = true ; 230 | actionSprite.currentAction; 231 | 232 | actionSprite.setFrame = setFrame ; 233 | actionSprite.getRow = getRow; 234 | actionSprite.getColumn = getColumn; 235 | 236 | offsetTexture( actionSprite ) ; 237 | 238 | actionSprites.push( actionSprite ); 239 | 240 | return actionSprite ; 241 | 242 | }; 243 | 244 | 245 | 246 | 247 | 248 | 249 | /* 250 | ActionSprite(textureURL:string, tilesHoriz:integer, tilesVert:integer, numTiles:integer, tileDispDuration:integer) 251 | - texture : texture containing all the frames in a grid. 252 | - tilesHoriz : number of frames on the horizontal direction. 253 | - tilesVert : number of frames on the vertical direction. 254 | - numTiles : total number of frames. As you can see in the exemples, 255 | it does not necessarily equal tilesHoriz*tilesVert, for instance 256 | if the last frames are empty. 257 | - tileDispDuration : display duration of ONE FRAME, un milliseconds. 258 | 259 | spriteMixer.ActionSprite() returns a extended THREE.Sprite. 260 | All the parameters necessary for the animation are stored inside, 261 | but you can still use it as any THREE.Sprite, like scale it etc.. 262 | */ 263 | function Action( actionSprite, indexStart, indexEnd, tileDisplayDuration ) { 264 | 265 | if ( !actionSprite.isIndexedSprite ) { 266 | throw 'Error : "texture" argument must be an indexedTexture.' ; 267 | return 268 | }; 269 | 270 | 271 | return { 272 | type: "spriteAction", 273 | playOnce, 274 | playLoop, 275 | resume, 276 | pause, 277 | pauseNextEnd, 278 | stop, 279 | clampWhenFinished: true, 280 | hideWhenFinished: false, 281 | mustLoop: true, 282 | indexStart, 283 | indexEnd, 284 | tileDisplayDuration, 285 | actionSprite 286 | }; 287 | 288 | }; 289 | 290 | 291 | 292 | 293 | 294 | return api; 295 | 296 | }; -------------------------------------------------------------------------------- /public/html/game.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Temple Of Doom 5 | 6 | 7 | 8 | 9 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 |
237 |
238 | 239 |

the Temple of Doom

240 | 241 | 242 |
243 | 244 |

245 |

< back

246 |

247 | 248 | 253 | 254 | 258 | 259 | 262 | 263 |
264 | 265 |
266 |
267 | 268 | 269 | 270 | 271 | 272 |
273 |
274 | 275 | 279 | 280 | 285 | 286 |

restart (press space) >

287 | 288 |
289 |
290 | 291 | 292 | 293 | 294 | 295 |
296 |
297 |

0 / 10

298 |
299 |
300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 502 | 503 | 504 | -------------------------------------------------------------------------------- /public/js/Atlas.js: -------------------------------------------------------------------------------- 1 | 2 | function Atlas() { 3 | 4 | 5 | // ATLAS ARRAYS 6 | // these arrays hold the height of individual steps in the tracks 7 | 8 | 9 | /* ORIGINAL MAP // trap hole // trap little hole // columns slalom // climbing backward // big climbing // leap /// Chamber 10 | const d0 = [ 10, 8, 8, 8, 8, 8, 2, 2, 2, 2, 2, 2, 2, 1.75, 1.5, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 3.5, 4, 4, 4, 4, 4, 10, 4, 10, 4, 10, 6, 5.5, 5, 4.5, 3, 2.5, 2.5, 2.5, 2.5, 15, 2.5, 2.5, 2.5, 15, 2.5, 2.5, 2.5, 15, 3, 6, 6, 3.5, 6, 6, 6, 6, 30, 6.5, 30, 8, 30, 11.5, 11.5, 11, 30, 12.5, 14, 14, 14, 0, 0, 0, 0, 0, 0, 0, 14, 35, 14, 14, 14, 35, 14, 14, 35, 14, 14, 35, 14, 14, 14, 14, 14, 0, 14, 0, 0, 14, 14, 14, 14, 14, 35, 14, 14, 35, 14, 14, 35, 14, 14, 14, 17.5, 14, 17.5, 18.5, 17, 19, 16.5, 19.5, 20, 20, 20, 19.5, 35, 20, 35, 0, 20, 20, 0, 0, 0, 35, 35, 35, 35, 35, 35, 35, 35 ]; // RED 11 | const d1 = [ 10, 2, 2, 2, 2, 2, 2, 3.5, 2.25, 2, 2, 2, 1.75, 1.5, 1.25, 0.5, 0.5, 1, 2, 3, 0.5, 2, 2.5, 3, 3.5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4.5, 4.5, 4.5, 4, 0, 0, 2.5, 2.5, 2.5, 2.5, 2.5, 15, 2.5, 2.5, 2.5, 15, 2.5, 2.5, 2.5, 2.5, 5, 3.5, 3, 5.5, 6, 6, 6.5, 7, 8, 8, 9.5, 9.5, 30, 11, 12.5, 12.5, 35, 14, 14, 14, 0, 14, 0, 14, 0, 14, 14, 14, 14, 35, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 35, 14, 14, 0, 14, 14, 0, 14, 14, 35, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 35, 14, 35, 14, 17.5, 14.5, 17, 15, 16.5, 16, 15, 20, 20, 19.5, 20, 20, 20, 0, 35, 20, 0, 20, 20, 20, 35, 35, 35, 35, 35, 35, 35 ]; // BLUE 12 | const d2 = [ 10, 8, 8, 8, 8, 8, 2, 2, 2, 2, 2, 2, 1, 1, 0.5, 0.5, 0.5, 0.5, 0, 0, 0.5, 1, 1, 1, 2.25, 3, 4, 4, 3.5, 3, 2.5, 2, 1.5, 1, 4, 3.5, 3.5, 3, 0, 0, 2.5, 2.5, 2.5, 15, 2.5, 2.5, 2.5, 15, 2.5, 2.5, 2.5, 15, 2.5, 2.5, 2.5, 2.5, 3, 3.5, 3.5, 5.5, 6, 6.5, 8, 8, 30, 9.5, 11, 11, 30, 12.5, 14, 14, 14, 0, 0, 0, 0, 0, 0, 11, 11, 11, 12, 13, 14, 35, 14, 14, 35, 14, 14, 35, 14, 14, 14, 14, 14, 0, 0, 13.5, 0, 14, 14, 14, 14, 14, 35, 14, 14, 35, 14, 14, 35, 14, 14, 14, 14, 14, 14, 14.5, 14, 15, 14, 15.5, 14, 20, 19.75, 19.5, 19.5, 19.5, 19.5, 0, 19.5, 20, 0, 0, 0, 35, 35, 35, 35, 35, 35, 35, 35 ]; // GREEN 13 | */ 14 | 15 | // flags //mask // tuto Chamber // start hole // tuto stairs // big stairs // chamber 1 // chamber 2 16 | const d0 = [ 10, 8, 8, 8, 8, 8, 2, 2, 2, 2, 2, 10, 2, 10, 2, 1.75, 1.5, 1.25, 1, 1, 1, 1, 1, 15, 1, 1.5, 1, 15, 1, 1, 1, 1, 1, 1.25, 1.5, 1.75, 2, 10, 2, 10, 2, 2, 2, 2, 2, 10, 10, 2, 10, 10, 2, 2, 2.5, 2.5, 2.5, 3.75, 3.75, 3.75, 3.75, 5.25, 5.25, 5, 5, 5, 5, 10, 5, 10, 5, 8.25, 8.25, 8.5, 8, 9, 9.25, 9.25, 11.75, 9.75, 12.5, 12.5, 10.5, 10.5, 10.5, 10.5, 10.5, 10.5, 20, 12.5, 20, 12.5, 12.5, 12.5, 20, 12.5, -3, -3, -3, 12.5, 20, 12.5, 12.5, 12.5, 20, 12.5, 12.5, 12.5, -3, 12.5, -3, -3, 20, 12.5, 12.5, 12.5, 20, 12.5, 20, 11.5, 12, -3, 30, -3, 30, -3, -3, -3, -3, -3, 12.5, -3, -3, 13.25, -3, 12.5, -3, -3, 15, -3, 13.5, -3, 14.25, -3, 13, -3, -3, 30, 30, 30, 30, 30 ]; // RED 17 | const d1 = [ 10, 2, 2, 2, 2, 2, 2, 3.5, 2.25, 2, 2, 2, 2, 2, 2, 1.75, 1.5, 1.25, 1, 1, 1, 15, 1, 1, 1, 1.5, 1, 1, 1, 15, 1, 1, 1, 1.25, 1.5, 1.75, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 4.5, 3.75, 3.25, 7, 6.25, 4.5, 5.5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 8, 7, 9.25, 11, 9.25, 10.25, 12.5, 12.5, 10.5, 12.5, 10.5, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, 20, 12.5, 12.5, 12.5, -3, 12.5, -3, 12.5, 12.5, 12.5, 20, 12.5, 12.5, 12.5, -3, -3, -3, -3, 12.5, 12.5, 12.5, 12.5, 20, 12.5, 12.5, 12.5, 12.5, 12.5, 12.5, -3, 11.5, -3, 12.5, -3, 12.5, -3, 12.5, 12.5, -3, 11.5, -3, 11, 11.25, -3, -3, 11.5, -3, -3, -3, 12.5, -3, -3, -3, 12.5, 12.5, 12.5, 12.5, 12.5, 30, 30 ]; // BLUE 18 | const d2 = [ 10, 8, 8, 8, 8, 8, 2, 2, 2, 2, 2, 10, 2, 10, 2, 1.75, 1.5, 1.25, 1, 1, 1, 1, 1, 15, 1, 1.5, 1, 15, 1, 1, 1, 1, 1, 1.25, 1.5, 1.75, 2, 10, 2, 10, 2, 2, 2, 2, 2, 10, 10, 2, 10, 10, 2, 2, 2, 2.5, 2.75, 2, 2.5, 3.75, 3.25, 4, 4.25, 4.5, 4.75, 5, 5, 10, 5, 10, 5, 5, 5, 5, 5, 6, 7, 9 , 9.25, 9.25, 9.25, 9.5, 10.25, 10.5, 10.5, 10.5, 10.5, 10.5, 20, 12.5, 20, 12.5, 12.5, 12.5, 20, 12.5, -3, 11.75, -3, 12.5, 20, 12.5, 12.5, 12.5, 20, -3, -3, 12.5, -3, 12.5, -3, 9.5, 10.5, 11.5, 12.5, 12.5, 20, 12.5, 13, 11.5, 12, -3, 12.5, -3, 11.5, -3, 11.5, -3, -3, -3, -3, -3, -3, 10.5, -3, -3, -3, -3, 11, -3, 11.5, -3, -3, -3, -3, -3, -3, 30, 30, 30, 30, 30 ]; // GREEN 19 | 20 | const roof = [ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5.5, 5.5, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 5.5, 5.5, 5, 5, 30, 30, 5, 5, 5, 5, 4.5, 4.5, 4.5, 4.5, 4.5, 4.75, 5, 5.25, 5.5, 5.75, 6, 6.25, 6.5, 6.75, 7, 7.25, 7.25, 7.25, 7.25, 9, 9, 17, 17, 17, 17, 17, 17, 17, 17, 14, 17, 14.5, 17, 17, 17, 17, 17, 17, 17, 16, 16, 15, 15, 15, 15, 15, 15, 30, 30, 30, 15, 15, 15, 15, 15, 15, 15, 30, 30, 30, 30, 30, 15, 15, 15, 15, 15, 15, 16.5, 16.5, 16.5, 16.5, 30, 16.5, 30, 16.5, 30, 16.5, 30, 16.5, 16.5, 16.5, 16.5, 30, 16.5, 16.5, 16.5, 30, 16.5, 16.5, 30, 16.5, 16.5, 16.5, 30, 16.5, 16.5, 16.5, 15.5, 15.5, 15.5, 15.5, 15.5 ]; 21 | 22 | const chaserTrack = [ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2.75, 2.5, 2.25, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2.25, 2.5, 2.75, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3.25, 3.5, 3.75, 4, 4.25, 4.5, 4.75, 5, 5.25, 5.5, 5.75, 6, 6, 6, 7, 8.5, 9.5, 9.5, 9.5, 10, 10, 10.5, 13, 13, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5, 13.5 ] 23 | 24 | 25 | 26 | const tracks = [ d0, d1, d2 ]; 27 | 28 | const ENDPOINT = 145 //OLD : 137 ; 29 | 30 | const CAMERAVEC = new THREE.Vector3( -1, 2, 19 ); 31 | const SPRITEVEC = new THREE.Vector3( 0.25, 0.4, 0.5 ); 32 | 33 | var pointsArrays = []; 34 | var isFlying ; 35 | var tempObstacles = []; 36 | 37 | 38 | var wallsMaterial = new THREE.MeshLambertMaterial({ color: 0x292417 }); 39 | 40 | 41 | 42 | 43 | // TempObstacle creates a temporary obstacle against which 44 | // logicSquare.move() check collision, in addition to the atlas. 45 | // If vec.z is higher than 2, the tempObstacle is considered 46 | // spanning over the 3 tracks 47 | function TempObstacle( name, vec, needHelper ) { 48 | 49 | let tempObstacle = { 50 | name, 51 | vec 52 | }; 53 | 54 | tempObstacles.push( tempObstacle ); 55 | 56 | let helper = needHelper ? new THREE.Group() : undefined ; 57 | 58 | if ( helper ) { 59 | 60 | const offsetVec = new THREE.Vector3( 0.5, 0, 0.5 ); 61 | 62 | let isSpanning = vec.z > 2 ; 63 | let depth = isSpanning ? 2.9 : 1 ; 64 | 65 | let geom = new THREE.BoxBufferGeometry( 1, 80, depth ); 66 | let mesh = new THREE.Mesh( geom, wallsMaterial ); 67 | 68 | mesh.position.copy(vec) ; 69 | if ( isSpanning ) { 70 | mesh.position.z = 1 ; 71 | }; 72 | mesh.position.add( offsetVec ); 73 | mesh.position.y -= 40 ; 74 | 75 | scene.add( mesh ); 76 | 77 | tempObstacle.helper = mesh ; 78 | }; 79 | 80 | return tempObstacle ; 81 | }; 82 | 83 | 84 | 85 | 86 | // remove a tempObstacle from tempsObstacles array and from the scene, 87 | // and free memory. 88 | function removeTempObstacle( name ) { 89 | 90 | let tempObstacle = tempObstacles.find( (tempObstacle)=> { 91 | return tempObstacle.name == name ; 92 | }); 93 | 94 | if ( tempObstacle ) { 95 | tempObstacles.splice( tempObstacles.indexOf( tempObstacle ), 1 ); 96 | if ( tempObstacle.helper ) { 97 | scene.remove( tempObstacle.helper ); 98 | tempObstacle.helper.geometry.dispose(); 99 | tempObstacle.helper.material.dispose(); 100 | }; 101 | }; 102 | }; 103 | 104 | 105 | 106 | 107 | 108 | // initVectors() transforms the atlas arrays (d0, d1 and d2), 109 | // into arrays of THREE.Vector3. 110 | function initVectors() { 111 | 112 | [ d0, d1, d2 ].forEach( (dimension, i)=> { 113 | 114 | pointsArrays.push([]); 115 | 116 | // transform the atlas references into pointsArrays, 117 | // the atlas hold only the height, the horizontal offset 118 | // is equal to the position in the array. 119 | dimension.forEach( (height, index)=> { 120 | pointsArrays[i].push( new THREE.Vector3( index, height, i ) ); 121 | }); 122 | 123 | }); 124 | }; 125 | 126 | 127 | 128 | 129 | 130 | 131 | // LogicCube is an abstrac class, used to compute collisions 132 | // with the atlas. 133 | function LogicSquare( width, height, position ) { 134 | 135 | position = position || new THREE.Vector3(); 136 | let collision = { left:0, right:0 }; 137 | 138 | 139 | function move( x, y, z ) { 140 | 141 | this.position.x += x ; 142 | this.position.y += y ; 143 | this.position.z += z ; 144 | 145 | // log current player position 146 | // console.log( 'x : ' + this.position.x.toFixed(2) + ' / y : ' + this.position.y.toFixed(2) ); 147 | 148 | if ( this.helper ) { 149 | this.helper.position.x += x ; 150 | this.helper.position.y += y ; 151 | this.helper.position.z += z ; 152 | }; 153 | 154 | if ( this.camera ) { 155 | this.camera.position.x += x ; 156 | this.camera.position.y += y ; 157 | this.camera.lookAt( 158 | this.position.x, 159 | this.position.y + 2, 160 | this.position.z 161 | ); 162 | }; 163 | 164 | if ( this.sprite ) { 165 | this.sprite.position.x += x ; 166 | this.sprite.position.y += y ; 167 | this.sprite.position.z += z ; 168 | }; 169 | 170 | // collide with the atlas 171 | testCollision( this, tracks[ this.position.z ] ); 172 | 173 | // collide with the temporary obstacles 174 | tempObstacles.forEach( (tempObstacle)=> { 175 | testCollisionTemp( this, tempObstacle ); 176 | }); 177 | 178 | // test collision with treasures 179 | treasures.testCollision( this ); 180 | 181 | // test if the player reached the end 182 | if ( this.position.x >= ENDPOINT ) { 183 | game.win(); 184 | }; 185 | 186 | }; 187 | 188 | 189 | 190 | 191 | function moveTo( vec ) { 192 | this.position.copy( vec ); 193 | if (this.helper) this.helper.position.copy( vec ); 194 | if (this.sprite) this.sprite.position 195 | .copy( SPRITEVEC ) 196 | .add( vec ); 197 | if (this.camera) this.camera.position 198 | .copy( CAMERAVEC ) 199 | .add( vec ); 200 | }; 201 | 202 | 203 | 204 | 205 | // step() make the character step over an obstacle, 206 | // it is called by the Controler loop with a t argument 207 | // representing the time on the animation, between 0 and 1; 208 | function step( t, direction ) { 209 | this.move( 0.01 * ( direction == 'right' ? 1 : -1 ) , ( collision[ direction ] * t ) + 0.01, 0 ); 210 | }; 211 | 212 | 213 | 214 | function isFlying() { 215 | 216 | /* 217 | let track = tracks[ this.position.z ] 218 | 219 | // Quelle est la hauteur de la marche sur laquelle 220 | // l'angle bas-gauche se trouve ? 221 | // La hauteur de l'angle bas-gauche est-elle 222 | // inférieur a la hauteur de l'angle + n ? 223 | let heightCurrentStepLeft = track[ Math.floor(this.position.x) ]; 224 | let heightSquare = this.position.y ; 225 | let isFlying = ( heightCurrentStepLeft + 0.02 ) < heightSquare ; 226 | 227 | if ( isFlying == true ) { 228 | 229 | let heightCurrentStepRight = track[ Math.floor(this.position.x + this.width) ]; 230 | isFlying = ( heightCurrentStepRight + 0.02 ) < heightSquare ; 231 | 232 | return isFlying ; 233 | 234 | } else { 235 | return false ; 236 | }; 237 | */ 238 | 239 | 240 | isFlying = ( tracks[ this.position.z ][ Math.floor(this.position.x) ] + 0.02 ) < this.position.y ; 241 | 242 | if ( isFlying == true ) { 243 | 244 | isFlying = ( tracks[ this.position.z ][ Math.floor(this.position.x + this.width) ] + 0.02 ) < this.position.y ; 245 | 246 | return isFlying ; 247 | 248 | } else { 249 | return false ; 250 | }; 251 | 252 | }; 253 | 254 | 255 | // shit() make the square change of atlas track 256 | function shift( increment ) { 257 | 258 | if ( !isInside( this, tracks[ this.position.z + increment ] ) ) { 259 | this.move( 0, 0, increment ); 260 | return true ; 261 | } else { 262 | return false ; 263 | } 264 | 265 | // isInside returns true if the square would be inside the passed track 266 | // if it was transposed to it without any more movement. 267 | function isInside( square, track ) { 268 | return square.position.y < track[ Math.floor(square.position.x) ] || 269 | square.position.y < track[ Math.floor(square.position.x + square.width) ] ; 270 | }; 271 | 272 | }; 273 | 274 | 275 | return { 276 | width, 277 | height, 278 | position, 279 | collision, 280 | move, 281 | moveTo, 282 | shift, 283 | step, 284 | isFlying, 285 | helper: undefined, 286 | camera: undefined, 287 | sprite: undefined 288 | }; 289 | }; 290 | 291 | 292 | 293 | 294 | 295 | function testCollision( logicSquare, track ) { 296 | 297 | /* 298 | // Quelle est la hauteur de la marche sur laquelle 299 | // l'angle bas-gauche se trouve ? 300 | // La hauteur de l'angle bas-gauche est-elle moindre 301 | // que cette hauteur ? 302 | let heightCurrentStepLeft = track[ Math.floor(logicSquare.position.x) ]; 303 | let heightSquare = logicSquare.position.y ; 304 | logicSquare.collision.left = heightCurrentStepLeft - heightSquare ; 305 | 306 | // Comment savoir, en fonction de logicSquare.width, si l'angle 307 | // droit du carré est au-dessus de la prochaine marche ? 308 | let heightCurrentStepRight = track[ Math.floor(logicSquare.position.x + logicSquare.width) ]; 309 | logicSquare.collision.right = heightCurrentStepRight - heightSquare ; 310 | 311 | console.log( logicSquare.collision ) 312 | */ 313 | 314 | 315 | // VERSION WITHOUT VARIABLE DECLARATION 316 | logicSquare.collision.left = track[ Math.floor(logicSquare.position.x) ] - 317 | logicSquare.position.y ; 318 | 319 | logicSquare.collision.right = track[ Math.floor(logicSquare.position.x + 320 | logicSquare.width) ] - 321 | logicSquare.position.y ; 322 | 323 | }; 324 | 325 | 326 | 327 | 328 | // testCollisionTemp update logicSquare.collision if the square traverse 329 | // a temporary obstacle bigger than the atlas 330 | function testCollisionTemp( logicSquare, tempObstacle ) { 331 | 332 | // Angle droit de trouve sur l'obstacle 333 | if ( Math.floor(logicSquare.position.x + logicSquare.width) == tempObstacle.vec.x ) { 334 | 335 | if ( logicSquare.collision.right < ( tempObstacle.vec.y - logicSquare.position.y ) ) { 336 | logicSquare.collision.right = ( tempObstacle.vec.y - logicSquare.position.y ) 337 | }; 338 | 339 | // Angle gauche de trouve sur l'obstacle 340 | } else if ( Math.floor(logicSquare.position.x) == tempObstacle.vec.x ) { 341 | 342 | if ( logicSquare.collision.left < ( tempObstacle.vec.y - logicSquare.position.y ) ) { 343 | logicSquare.collision.left = ( tempObstacle.vec.y - logicSquare.position.y ) 344 | }; 345 | }; 346 | }; 347 | 348 | 349 | 350 | 351 | 352 | 353 | return { 354 | initVectors, 355 | pointsArrays, 356 | LogicSquare, 357 | chaserTrack, 358 | roof, 359 | TempObstacle, 360 | removeTempObstacle 361 | }; 362 | 363 | }; -------------------------------------------------------------------------------- /public/js/libs/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | 9 | // This set of controls performs orbiting, dollying (zooming), and panning. 10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 11 | // 12 | // Orbit - left mouse / touch: one-finger move 13 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 14 | // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move 15 | 16 | THREE.OrbitControls = function ( object, domElement ) { 17 | 18 | this.object = object; 19 | 20 | this.domElement = ( domElement !== undefined ) ? domElement : document; 21 | 22 | // Set to false to disable this control 23 | this.enabled = true; 24 | 25 | // "target" sets the location of focus, where the object orbits around 26 | this.target = new THREE.Vector3(); 27 | 28 | // How far you can dolly in and out ( PerspectiveCamera only ) 29 | this.minDistance = 0; 30 | this.maxDistance = Infinity; 31 | 32 | // How far you can zoom in and out ( OrthographicCamera only ) 33 | this.minZoom = 0; 34 | this.maxZoom = Infinity; 35 | 36 | // How far you can orbit vertically, upper and lower limits. 37 | // Range is 0 to Math.PI radians. 38 | this.minPolarAngle = 0; // radians 39 | this.maxPolarAngle = Math.PI; // radians 40 | 41 | // How far you can orbit horizontally, upper and lower limits. 42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 43 | this.minAzimuthAngle = - Infinity; // radians 44 | this.maxAzimuthAngle = Infinity; // radians 45 | 46 | // Set to true to enable damping (inertia) 47 | // If damping is enabled, you must call controls.update() in your animation loop 48 | this.enableDamping = false; 49 | this.dampingFactor = 0.25; 50 | 51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 52 | // Set to false to disable zooming 53 | this.enableZoom = true; 54 | this.zoomSpeed = 1.0; 55 | 56 | // Set to false to disable rotating 57 | this.enableRotate = true; 58 | this.rotateSpeed = 1.0; 59 | 60 | // Set to false to disable panning 61 | this.enablePan = true; 62 | this.panSpeed = 1.0; 63 | this.screenSpacePanning = false; // if true, pan in screen-space 64 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 65 | 66 | // Set to true to automatically rotate around the target 67 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 68 | this.autoRotate = false; 69 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 70 | 71 | // Set to false to disable use of the keys 72 | this.enableKeys = true; 73 | 74 | // The four arrow keys 75 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 76 | 77 | // Mouse buttons 78 | this.mouseButtons = { LEFT: THREE.MOUSE.LEFT, MIDDLE: THREE.MOUSE.MIDDLE, RIGHT: THREE.MOUSE.RIGHT }; 79 | 80 | // for reset 81 | this.target0 = this.target.clone(); 82 | this.position0 = this.object.position.clone(); 83 | this.zoom0 = this.object.zoom; 84 | 85 | // 86 | // public methods 87 | // 88 | 89 | this.getPolarAngle = function () { 90 | 91 | return spherical.phi; 92 | 93 | }; 94 | 95 | this.getAzimuthalAngle = function () { 96 | 97 | return spherical.theta; 98 | 99 | }; 100 | 101 | this.saveState = function () { 102 | 103 | scope.target0.copy( scope.target ); 104 | scope.position0.copy( scope.object.position ); 105 | scope.zoom0 = scope.object.zoom; 106 | 107 | }; 108 | 109 | this.reset = function () { 110 | 111 | scope.target.copy( scope.target0 ); 112 | scope.object.position.copy( scope.position0 ); 113 | scope.object.zoom = scope.zoom0; 114 | 115 | scope.object.updateProjectionMatrix(); 116 | scope.dispatchEvent( changeEvent ); 117 | 118 | scope.update(); 119 | 120 | state = STATE.NONE; 121 | 122 | }; 123 | 124 | // this method is exposed, but perhaps it would be better if we can make it private... 125 | this.update = function () { 126 | 127 | var offset = new THREE.Vector3(); 128 | 129 | // so camera.up is the orbit axis 130 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 131 | var quatInverse = quat.clone().inverse(); 132 | 133 | var lastPosition = new THREE.Vector3(); 134 | var lastQuaternion = new THREE.Quaternion(); 135 | 136 | return function update() { 137 | 138 | var position = scope.object.position; 139 | 140 | offset.copy( position ).sub( scope.target ); 141 | 142 | // rotate offset to "y-axis-is-up" space 143 | offset.applyQuaternion( quat ); 144 | 145 | // angle from z-axis around y-axis 146 | spherical.setFromVector3( offset ); 147 | 148 | if ( scope.autoRotate && state === STATE.NONE ) { 149 | 150 | rotateLeft( getAutoRotationAngle() ); 151 | 152 | } 153 | 154 | spherical.theta += sphericalDelta.theta; 155 | spherical.phi += sphericalDelta.phi; 156 | 157 | // restrict theta to be between desired limits 158 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 159 | 160 | // restrict phi to be between desired limits 161 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 162 | 163 | spherical.makeSafe(); 164 | 165 | 166 | spherical.radius *= scale; 167 | 168 | // restrict radius to be between desired limits 169 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 170 | 171 | // move target to panned location 172 | scope.target.add( panOffset ); 173 | 174 | offset.setFromSpherical( spherical ); 175 | 176 | // rotate offset back to "camera-up-vector-is-up" space 177 | offset.applyQuaternion( quatInverse ); 178 | 179 | position.copy( scope.target ).add( offset ); 180 | 181 | scope.object.lookAt( scope.target ); 182 | 183 | if ( scope.enableDamping === true ) { 184 | 185 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 186 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 187 | 188 | panOffset.multiplyScalar( 1 - scope.dampingFactor ); 189 | 190 | } else { 191 | 192 | sphericalDelta.set( 0, 0, 0 ); 193 | 194 | panOffset.set( 0, 0, 0 ); 195 | 196 | } 197 | 198 | scale = 1; 199 | 200 | // update condition is: 201 | // min(camera displacement, camera rotation in radians)^2 > EPS 202 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 203 | 204 | if ( zoomChanged || 205 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 206 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 207 | 208 | scope.dispatchEvent( changeEvent ); 209 | 210 | lastPosition.copy( scope.object.position ); 211 | lastQuaternion.copy( scope.object.quaternion ); 212 | zoomChanged = false; 213 | 214 | return true; 215 | 216 | } 217 | 218 | return false; 219 | 220 | }; 221 | 222 | }(); 223 | 224 | this.dispose = function () { 225 | 226 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 227 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 228 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 229 | 230 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 231 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 232 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 233 | 234 | document.removeEventListener( 'mousemove', onMouseMove, false ); 235 | document.removeEventListener( 'mouseup', onMouseUp, false ); 236 | 237 | window.removeEventListener( 'keydown', onKeyDown, false ); 238 | 239 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 240 | 241 | }; 242 | 243 | // 244 | // internals 245 | // 246 | 247 | var scope = this; 248 | 249 | var changeEvent = { type: 'change' }; 250 | var startEvent = { type: 'start' }; 251 | var endEvent = { type: 'end' }; 252 | 253 | var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY_PAN: 4 }; 254 | 255 | var state = STATE.NONE; 256 | 257 | var EPS = 0.000001; 258 | 259 | // current position in spherical coordinates 260 | var spherical = new THREE.Spherical(); 261 | var sphericalDelta = new THREE.Spherical(); 262 | 263 | var scale = 1; 264 | var panOffset = new THREE.Vector3(); 265 | var zoomChanged = false; 266 | 267 | var rotateStart = new THREE.Vector2(); 268 | var rotateEnd = new THREE.Vector2(); 269 | var rotateDelta = new THREE.Vector2(); 270 | 271 | var panStart = new THREE.Vector2(); 272 | var panEnd = new THREE.Vector2(); 273 | var panDelta = new THREE.Vector2(); 274 | 275 | var dollyStart = new THREE.Vector2(); 276 | var dollyEnd = new THREE.Vector2(); 277 | var dollyDelta = new THREE.Vector2(); 278 | 279 | function getAutoRotationAngle() { 280 | 281 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 282 | 283 | } 284 | 285 | function getZoomScale() { 286 | 287 | return Math.pow( 0.95, scope.zoomSpeed ); 288 | 289 | } 290 | 291 | function rotateLeft( angle ) { 292 | 293 | sphericalDelta.theta -= angle; 294 | 295 | } 296 | 297 | function rotateUp( angle ) { 298 | 299 | sphericalDelta.phi -= angle; 300 | 301 | } 302 | 303 | var panLeft = function () { 304 | 305 | var v = new THREE.Vector3(); 306 | 307 | return function panLeft( distance, objectMatrix ) { 308 | 309 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 310 | v.multiplyScalar( - distance ); 311 | 312 | panOffset.add( v ); 313 | 314 | }; 315 | 316 | }(); 317 | 318 | var panUp = function () { 319 | 320 | var v = new THREE.Vector3(); 321 | 322 | return function panUp( distance, objectMatrix ) { 323 | 324 | if ( scope.screenSpacePanning === true ) { 325 | 326 | v.setFromMatrixColumn( objectMatrix, 1 ); 327 | 328 | } else { 329 | 330 | v.setFromMatrixColumn( objectMatrix, 0 ); 331 | v.crossVectors( scope.object.up, v ); 332 | 333 | } 334 | 335 | v.multiplyScalar( distance ); 336 | 337 | panOffset.add( v ); 338 | 339 | }; 340 | 341 | }(); 342 | 343 | // deltaX and deltaY are in pixels; right and down are positive 344 | var pan = function () { 345 | 346 | var offset = new THREE.Vector3(); 347 | 348 | return function pan( deltaX, deltaY ) { 349 | 350 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 351 | 352 | if ( scope.object.isPerspectiveCamera ) { 353 | 354 | // perspective 355 | var position = scope.object.position; 356 | offset.copy( position ).sub( scope.target ); 357 | var targetDistance = offset.length(); 358 | 359 | // half of the fov is center to top of screen 360 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 361 | 362 | // we use only clientHeight here so aspect ratio does not distort speed 363 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 364 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 365 | 366 | } else if ( scope.object.isOrthographicCamera ) { 367 | 368 | // orthographic 369 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 370 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 371 | 372 | } else { 373 | 374 | // camera neither orthographic nor perspective 375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 376 | scope.enablePan = false; 377 | 378 | } 379 | 380 | }; 381 | 382 | }(); 383 | 384 | function dollyIn( dollyScale ) { 385 | 386 | if ( scope.object.isPerspectiveCamera ) { 387 | 388 | scale /= dollyScale; 389 | 390 | } else if ( scope.object.isOrthographicCamera ) { 391 | 392 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 393 | scope.object.updateProjectionMatrix(); 394 | zoomChanged = true; 395 | 396 | } else { 397 | 398 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 399 | scope.enableZoom = false; 400 | 401 | } 402 | 403 | } 404 | 405 | function dollyOut( dollyScale ) { 406 | 407 | if ( scope.object.isPerspectiveCamera ) { 408 | 409 | scale *= dollyScale; 410 | 411 | } else if ( scope.object.isOrthographicCamera ) { 412 | 413 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 414 | scope.object.updateProjectionMatrix(); 415 | zoomChanged = true; 416 | 417 | } else { 418 | 419 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 420 | scope.enableZoom = false; 421 | 422 | } 423 | 424 | } 425 | 426 | // 427 | // event callbacks - update the object state 428 | // 429 | 430 | function handleMouseDownRotate( event ) { 431 | 432 | //console.log( 'handleMouseDownRotate' ); 433 | 434 | rotateStart.set( event.clientX, event.clientY ); 435 | 436 | } 437 | 438 | function handleMouseDownDolly( event ) { 439 | 440 | //console.log( 'handleMouseDownDolly' ); 441 | 442 | dollyStart.set( event.clientX, event.clientY ); 443 | 444 | } 445 | 446 | function handleMouseDownPan( event ) { 447 | 448 | //console.log( 'handleMouseDownPan' ); 449 | 450 | panStart.set( event.clientX, event.clientY ); 451 | 452 | } 453 | 454 | function handleMouseMoveRotate( event ) { 455 | 456 | //console.log( 'handleMouseMoveRotate' ); 457 | 458 | rotateEnd.set( event.clientX, event.clientY ); 459 | 460 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 461 | 462 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 463 | 464 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 465 | 466 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 467 | 468 | rotateStart.copy( rotateEnd ); 469 | 470 | scope.update(); 471 | 472 | } 473 | 474 | function handleMouseMoveDolly( event ) { 475 | 476 | //console.log( 'handleMouseMoveDolly' ); 477 | 478 | dollyEnd.set( event.clientX, event.clientY ); 479 | 480 | dollyDelta.subVectors( dollyEnd, dollyStart ); 481 | 482 | if ( dollyDelta.y > 0 ) { 483 | 484 | dollyIn( getZoomScale() ); 485 | 486 | } else if ( dollyDelta.y < 0 ) { 487 | 488 | dollyOut( getZoomScale() ); 489 | 490 | } 491 | 492 | dollyStart.copy( dollyEnd ); 493 | 494 | scope.update(); 495 | 496 | } 497 | 498 | function handleMouseMovePan( event ) { 499 | 500 | //console.log( 'handleMouseMovePan' ); 501 | 502 | panEnd.set( event.clientX, event.clientY ); 503 | 504 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 505 | 506 | pan( panDelta.x, panDelta.y ); 507 | 508 | panStart.copy( panEnd ); 509 | 510 | scope.update(); 511 | 512 | } 513 | 514 | function handleMouseUp( event ) { 515 | 516 | // console.log( 'handleMouseUp' ); 517 | 518 | } 519 | 520 | function handleMouseWheel( event ) { 521 | 522 | // console.log( 'handleMouseWheel' ); 523 | 524 | if ( event.deltaY < 0 ) { 525 | 526 | dollyOut( getZoomScale() ); 527 | 528 | } else if ( event.deltaY > 0 ) { 529 | 530 | dollyIn( getZoomScale() ); 531 | 532 | } 533 | 534 | scope.update(); 535 | 536 | } 537 | 538 | function handleKeyDown( event ) { 539 | 540 | // console.log( 'handleKeyDown' ); 541 | 542 | var needsUpdate = false; 543 | 544 | switch ( event.keyCode ) { 545 | 546 | case scope.keys.UP: 547 | pan( 0, scope.keyPanSpeed ); 548 | needsUpdate = true; 549 | break; 550 | 551 | case scope.keys.BOTTOM: 552 | pan( 0, - scope.keyPanSpeed ); 553 | needsUpdate = true; 554 | break; 555 | 556 | case scope.keys.LEFT: 557 | pan( scope.keyPanSpeed, 0 ); 558 | needsUpdate = true; 559 | break; 560 | 561 | case scope.keys.RIGHT: 562 | pan( - scope.keyPanSpeed, 0 ); 563 | needsUpdate = true; 564 | break; 565 | 566 | } 567 | 568 | if ( needsUpdate ) { 569 | 570 | // prevent the browser from scrolling on cursor keys 571 | event.preventDefault(); 572 | 573 | scope.update(); 574 | 575 | } 576 | 577 | 578 | } 579 | 580 | function handleTouchStartRotate( event ) { 581 | 582 | //console.log( 'handleTouchStartRotate' ); 583 | 584 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 585 | 586 | } 587 | 588 | function handleTouchStartDollyPan( event ) { 589 | 590 | //console.log( 'handleTouchStartDollyPan' ); 591 | 592 | if ( scope.enableZoom ) { 593 | 594 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 595 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 596 | 597 | var distance = Math.sqrt( dx * dx + dy * dy ); 598 | 599 | dollyStart.set( 0, distance ); 600 | 601 | } 602 | 603 | if ( scope.enablePan ) { 604 | 605 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 606 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 607 | 608 | panStart.set( x, y ); 609 | 610 | } 611 | 612 | } 613 | 614 | function handleTouchMoveRotate( event ) { 615 | 616 | //console.log( 'handleTouchMoveRotate' ); 617 | 618 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 619 | 620 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 621 | 622 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 623 | 624 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 625 | 626 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 627 | 628 | rotateStart.copy( rotateEnd ); 629 | 630 | scope.update(); 631 | 632 | } 633 | 634 | function handleTouchMoveDollyPan( event ) { 635 | 636 | //console.log( 'handleTouchMoveDollyPan' ); 637 | 638 | if ( scope.enableZoom ) { 639 | 640 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 641 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 642 | 643 | var distance = Math.sqrt( dx * dx + dy * dy ); 644 | 645 | dollyEnd.set( 0, distance ); 646 | 647 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 648 | 649 | dollyIn( dollyDelta.y ); 650 | 651 | dollyStart.copy( dollyEnd ); 652 | 653 | } 654 | 655 | if ( scope.enablePan ) { 656 | 657 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 658 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 659 | 660 | panEnd.set( x, y ); 661 | 662 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 663 | 664 | pan( panDelta.x, panDelta.y ); 665 | 666 | panStart.copy( panEnd ); 667 | 668 | } 669 | 670 | scope.update(); 671 | 672 | } 673 | 674 | function handleTouchEnd( event ) { 675 | 676 | //console.log( 'handleTouchEnd' ); 677 | 678 | } 679 | 680 | // 681 | // event handlers - FSM: listen for events and reset state 682 | // 683 | 684 | function onMouseDown( event ) { 685 | 686 | if ( scope.enabled === false ) return; 687 | 688 | // Prevent the browser from scrolling. 689 | 690 | event.preventDefault(); 691 | 692 | // Manually set the focus since calling preventDefault above 693 | // prevents the browser from setting it automatically. 694 | 695 | scope.domElement.focus ? scope.domElement.focus() : window.focus(); 696 | 697 | switch ( event.button ) { 698 | 699 | case scope.mouseButtons.LEFT: 700 | 701 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 702 | 703 | if ( scope.enablePan === false ) return; 704 | 705 | handleMouseDownPan( event ); 706 | 707 | state = STATE.PAN; 708 | 709 | } else { 710 | 711 | if ( scope.enableRotate === false ) return; 712 | 713 | handleMouseDownRotate( event ); 714 | 715 | state = STATE.ROTATE; 716 | 717 | } 718 | 719 | break; 720 | 721 | case scope.mouseButtons.MIDDLE: 722 | 723 | if ( scope.enableZoom === false ) return; 724 | 725 | handleMouseDownDolly( event ); 726 | 727 | state = STATE.DOLLY; 728 | 729 | break; 730 | 731 | case scope.mouseButtons.RIGHT: 732 | 733 | if ( scope.enablePan === false ) return; 734 | 735 | handleMouseDownPan( event ); 736 | 737 | state = STATE.PAN; 738 | 739 | break; 740 | 741 | } 742 | 743 | if ( state !== STATE.NONE ) { 744 | 745 | document.addEventListener( 'mousemove', onMouseMove, false ); 746 | document.addEventListener( 'mouseup', onMouseUp, false ); 747 | 748 | scope.dispatchEvent( startEvent ); 749 | 750 | } 751 | 752 | } 753 | 754 | function onMouseMove( event ) { 755 | 756 | if ( scope.enabled === false ) return; 757 | 758 | event.preventDefault(); 759 | 760 | switch ( state ) { 761 | 762 | case STATE.ROTATE: 763 | 764 | if ( scope.enableRotate === false ) return; 765 | 766 | handleMouseMoveRotate( event ); 767 | 768 | break; 769 | 770 | case STATE.DOLLY: 771 | 772 | if ( scope.enableZoom === false ) return; 773 | 774 | handleMouseMoveDolly( event ); 775 | 776 | break; 777 | 778 | case STATE.PAN: 779 | 780 | if ( scope.enablePan === false ) return; 781 | 782 | handleMouseMovePan( event ); 783 | 784 | break; 785 | 786 | } 787 | 788 | } 789 | 790 | function onMouseUp( event ) { 791 | 792 | if ( scope.enabled === false ) return; 793 | 794 | handleMouseUp( event ); 795 | 796 | document.removeEventListener( 'mousemove', onMouseMove, false ); 797 | document.removeEventListener( 'mouseup', onMouseUp, false ); 798 | 799 | scope.dispatchEvent( endEvent ); 800 | 801 | state = STATE.NONE; 802 | 803 | } 804 | 805 | function onMouseWheel( event ) { 806 | 807 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 808 | 809 | event.preventDefault(); 810 | event.stopPropagation(); 811 | 812 | scope.dispatchEvent( startEvent ); 813 | 814 | handleMouseWheel( event ); 815 | 816 | scope.dispatchEvent( endEvent ); 817 | 818 | } 819 | 820 | function onKeyDown( event ) { 821 | 822 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 823 | 824 | handleKeyDown( event ); 825 | 826 | } 827 | 828 | function onTouchStart( event ) { 829 | 830 | if ( scope.enabled === false ) return; 831 | 832 | event.preventDefault(); 833 | 834 | switch ( event.touches.length ) { 835 | 836 | case 1: // one-fingered touch: rotate 837 | 838 | if ( scope.enableRotate === false ) return; 839 | 840 | handleTouchStartRotate( event ); 841 | 842 | state = STATE.TOUCH_ROTATE; 843 | 844 | break; 845 | 846 | case 2: // two-fingered touch: dolly-pan 847 | 848 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 849 | 850 | handleTouchStartDollyPan( event ); 851 | 852 | state = STATE.TOUCH_DOLLY_PAN; 853 | 854 | break; 855 | 856 | default: 857 | 858 | state = STATE.NONE; 859 | 860 | } 861 | 862 | if ( state !== STATE.NONE ) { 863 | 864 | scope.dispatchEvent( startEvent ); 865 | 866 | } 867 | 868 | } 869 | 870 | function onTouchMove( event ) { 871 | 872 | if ( scope.enabled === false ) return; 873 | 874 | event.preventDefault(); 875 | event.stopPropagation(); 876 | 877 | switch ( event.touches.length ) { 878 | 879 | case 1: // one-fingered touch: rotate 880 | 881 | if ( scope.enableRotate === false ) return; 882 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed? 883 | 884 | handleTouchMoveRotate( event ); 885 | 886 | break; 887 | 888 | case 2: // two-fingered touch: dolly-pan 889 | 890 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 891 | if ( state !== STATE.TOUCH_DOLLY_PAN ) return; // is this needed? 892 | 893 | handleTouchMoveDollyPan( event ); 894 | 895 | break; 896 | 897 | default: 898 | 899 | state = STATE.NONE; 900 | 901 | } 902 | 903 | } 904 | 905 | function onTouchEnd( event ) { 906 | 907 | if ( scope.enabled === false ) return; 908 | 909 | handleTouchEnd( event ); 910 | 911 | scope.dispatchEvent( endEvent ); 912 | 913 | state = STATE.NONE; 914 | 915 | } 916 | 917 | function onContextMenu( event ) { 918 | 919 | if ( scope.enabled === false ) return; 920 | 921 | event.preventDefault(); 922 | 923 | } 924 | 925 | // 926 | 927 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 928 | 929 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 930 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 931 | 932 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 933 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 934 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 935 | 936 | window.addEventListener( 'keydown', onKeyDown, false ); 937 | 938 | // force an update at start 939 | 940 | this.update(); 941 | 942 | }; 943 | 944 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 945 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 946 | 947 | Object.defineProperties( THREE.OrbitControls.prototype, { 948 | 949 | center: { 950 | 951 | get: function () { 952 | 953 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 954 | return this.target; 955 | 956 | } 957 | 958 | }, 959 | 960 | // backward compatibility 961 | 962 | noZoom: { 963 | 964 | get: function () { 965 | 966 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 967 | return ! this.enableZoom; 968 | 969 | }, 970 | 971 | set: function ( value ) { 972 | 973 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 974 | this.enableZoom = ! value; 975 | 976 | } 977 | 978 | }, 979 | 980 | noRotate: { 981 | 982 | get: function () { 983 | 984 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 985 | return ! this.enableRotate; 986 | 987 | }, 988 | 989 | set: function ( value ) { 990 | 991 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 992 | this.enableRotate = ! value; 993 | 994 | } 995 | 996 | }, 997 | 998 | noPan: { 999 | 1000 | get: function () { 1001 | 1002 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 1003 | return ! this.enablePan; 1004 | 1005 | }, 1006 | 1007 | set: function ( value ) { 1008 | 1009 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 1010 | this.enablePan = ! value; 1011 | 1012 | } 1013 | 1014 | }, 1015 | 1016 | noKeys: { 1017 | 1018 | get: function () { 1019 | 1020 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1021 | return ! this.enableKeys; 1022 | 1023 | }, 1024 | 1025 | set: function ( value ) { 1026 | 1027 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1028 | this.enableKeys = ! value; 1029 | 1030 | } 1031 | 1032 | }, 1033 | 1034 | staticMoving: { 1035 | 1036 | get: function () { 1037 | 1038 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1039 | return ! this.enableDamping; 1040 | 1041 | }, 1042 | 1043 | set: function ( value ) { 1044 | 1045 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1046 | this.enableDamping = ! value; 1047 | 1048 | } 1049 | 1050 | }, 1051 | 1052 | dynamicDampingFactor: { 1053 | 1054 | get: function () { 1055 | 1056 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1057 | return this.dampingFactor; 1058 | 1059 | }, 1060 | 1061 | set: function ( value ) { 1062 | 1063 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1064 | this.dampingFactor = value; 1065 | 1066 | } 1067 | 1068 | } 1069 | 1070 | } ); 1071 | --------------------------------------------------------------------------------