├── img ├── block1.jpg ├── block2.jpg ├── block3.jpg ├── block4.jpg └── attribution.txt ├── js ├── shaders │ ├── sky-vertex.js │ └── sky-fragment.js ├── pointerlock.js ├── example.js ├── vendor │ └── threejs │ │ └── sky │ │ └── SkyShader.js └── FPSControls.js ├── LICENSE.md ├── README.md ├── index.html └── css └── main.css /img/block1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesLMilner/threejs-fps-controls/HEAD/img/block1.jpg -------------------------------------------------------------------------------- /img/block2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesLMilner/threejs-fps-controls/HEAD/img/block2.jpg -------------------------------------------------------------------------------- /img/block3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesLMilner/threejs-fps-controls/HEAD/img/block3.jpg -------------------------------------------------------------------------------- /img/block4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesLMilner/threejs-fps-controls/HEAD/img/block4.jpg -------------------------------------------------------------------------------- /img/attribution.txt: -------------------------------------------------------------------------------- 1 | 2 | Blocks 1-4 3 | Author: rubberduck 4 | Public Domain 5 | http://opengameart.org/content/free-metal-texture-creation-set-03ode/7167 -------------------------------------------------------------------------------- /js/shaders/sky-vertex.js: -------------------------------------------------------------------------------- 1 | varying vec2 vUV; 2 | 3 | void main() { 4 | vUV = uv; 5 | vec4 pos = vec4(position, 1.0); 6 | gl_Position = projectionMatrix * modelViewMatrix * pos; 7 | } 8 | -------------------------------------------------------------------------------- /js/shaders/sky-fragment.js: -------------------------------------------------------------------------------- 1 | uniform sampler2D texture; 2 | varying vec2 vUV; 3 | 4 | void main() { 5 | vec4 sample = texture2D(texture, vUV); 6 | gl_FragColor = vec4(sample.xyz, sample.w); 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 James Milner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FPS Controls for Three.js 2 | A 3D platformer game made with Three.js. The aim of the game is to the get to the top of the crates, at which point they will back to their rightful 3 | place on the moons surface in an orderly fashion. 4 | 5 | [Here is a live demo](https://jamesmilneruk.github.io/threejs-fps-controls) 6 | 7 | # Features 8 | The controls and demo are based on the [PointerLockControls](https://github.com/mrdoob/three.js/blob/master/examples/js/controls/PointerLockControls.js) given in the 9 | three.js examples page/repo. The controls add additional benefits: 10 | 11 | * Double Jump (Optional) 12 | * Crouching 13 | * Walking 14 | * Elementary collision detection with specified objects 15 | 16 | # Future Plans 17 | 18 | * Improved collision detection 19 | * Refactoring to improve code readability and control robustness 20 | * Strafe jumping (long term) 21 | 22 | # How To 23 | 24 | Import three.js, then import FPSControls.js. You can then do something like this: 25 | 26 | ```javascript 27 | camera = new THREE.PerspectiveCamera( 80, window.innerWidth / window.innerHeight, 1, 9000 ); 28 | controls = new THREE.PointerLockControls( camera, 100, 30, true, objects ); 29 | scene.add( controls.getPlayer() ); 30 | ``` 31 | Then in your animation loop you just need to call the update controls method: 32 | 33 | ```javascript 34 | controls.updateControls(); 35 | ``` 36 | 37 | # Contributing 38 | 39 | I would love contributions! Both in the form of issues, feature requests, usage examples and of course pull requests. 40 | 41 | # License 42 | MIT 43 | -------------------------------------------------------------------------------- /js/pointerlock.js: -------------------------------------------------------------------------------- 1 | // http://www.html5rocks.com/en/tutorials/pointerlock/intro/ 2 | function ScreenOverlay(controls, controlsEnabled) { 3 | var blocker = document.getElementById( 'blocker' ); 4 | var instructions = document.getElementById( 'instructions' ); 5 | var havePointerLock = 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document; 6 | 7 | if ( havePointerLock ) { 8 | 9 | var element = document.body; 10 | 11 | var pointerlockchange = function ( event ) { 12 | 13 | if ( document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element ) { 14 | 15 | controls.enabled = true; 16 | 17 | blocker.style.display = 'none'; 18 | 19 | } else { 20 | 21 | controls.enabled = false; 22 | 23 | blocker.style.display = '-webkit-box'; 24 | blocker.style.display = '-moz-box'; 25 | blocker.style.display = 'box'; 26 | 27 | instructions.style.display = ''; 28 | 29 | } 30 | 31 | } 32 | 33 | var pointerlockerror = function ( event ) { 34 | 35 | instructions.style.display = ''; 36 | 37 | } 38 | 39 | // Hook pointer lock state change events 40 | document.addEventListener( 'pointerlockchange', pointerlockchange, false ); 41 | document.addEventListener( 'mozpointerlockchange', pointerlockchange, false ); 42 | document.addEventListener( 'webkitpointerlockchange', pointerlockchange, false ); 43 | 44 | document.addEventListener( 'pointerlockerror', pointerlockerror, false ); 45 | document.addEventListener( 'mozpointerlockerror', pointerlockerror, false ); 46 | document.addEventListener( 'webkitpointerlockerror', pointerlockerror, false ); 47 | 48 | instructions.addEventListener( 'click', function ( event ) { 49 | 50 | instructions.style.display = 'none'; 51 | 52 | // Ask the browser to lock the pointer 53 | element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock; 54 | 55 | if ( /Firefox/i.test( navigator.userAgent ) ) { 56 | 57 | var fullscreenchange = function ( event ) { 58 | 59 | if ( document.fullscreenElement === element || document.mozFullscreenElement === element || document.mozFullScreenElement === element ) { 60 | 61 | document.removeEventListener( 'fullscreenchange', fullscreenchange ); 62 | document.removeEventListener( 'mozfullscreenchange', fullscreenchange ); 63 | 64 | element.requestPointerLock(); 65 | } 66 | 67 | } 68 | 69 | document.addEventListener( 'fullscreenchange', fullscreenchange, false ); 70 | document.addEventListener( 'mozfullscreenchange', fullscreenchange, false ); 71 | 72 | element.requestFullscreen = element.requestFullscreen || element.mozRequestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen; 73 | 74 | element.requestFullscreen(); 75 | 76 | } else { 77 | 78 | element.requestPointerLock(); 79 | 80 | } 81 | 82 | }, false ); 83 | 84 | } else { 85 | 86 | instructions.innerHTML = 'Your browser doesn\'t seem to support Pointer Lock API'; 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | Click to Play 17 |
18 | (W, A, S, D = Move, SPACE = Jump, SPACEx2 = Double Jump, MOUSE = Look around, C = Crouch, SHIFT = Walk) 19 |
20 |
21 |
22 | Timer - 23 | 24 | : 25 | 26 |
27 | 28 | 37 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: 'Roboto Mono'; 3 | } 4 | html, body { 5 | color: #222; 6 | font-size: 1em; 7 | line-height: 1.4; 8 | font-family: 'Roboto Mono'; 9 | } 10 | 11 | ::-moz-selection { 12 | background: #b3d4fc; 13 | text-shadow: none; 14 | } 15 | 16 | ::selection { 17 | background: #b3d4fc; 18 | text-shadow: none; 19 | } 20 | 21 | hr { 22 | display: block; 23 | height: 1px; 24 | border: 0; 25 | border-top: 1px solid #ccc; 26 | margin: 1em 0; 27 | padding: 0; 28 | } 29 | 30 | audio, 31 | canvas, 32 | iframe, 33 | img, 34 | svg, 35 | video { 36 | vertical-align: middle; 37 | } 38 | 39 | fieldset { 40 | border: 0; 41 | margin: 0; 42 | padding: 0; 43 | } 44 | 45 | textarea { 46 | resize: vertical; 47 | } 48 | 49 | .browserupgrade { 50 | margin: 0.2em 0; 51 | background: #ccc; 52 | color: #000; 53 | padding: 0.2em 0; 54 | } 55 | 56 | 57 | /* ========================================================================== 58 | Author's custom styles 59 | ========================================================================== */ 60 | 61 | html, body { 62 | width: 100%; 63 | height: 100%; 64 | } 65 | 66 | body { 67 | background-color: #ffffff; 68 | margin: 0; 69 | overflow: hidden; 70 | font-family: arial; 71 | } 72 | 73 | #blocker { 74 | 75 | position: absolute; 76 | 77 | width: 100%; 78 | height: 100%; 79 | 80 | background-color: rgba(0,0,0,0.80); 81 | 82 | } 83 | 84 | #instructions { 85 | 86 | width: 100%; 87 | height: 100%; 88 | 89 | display: -webkit-box; 90 | display: -moz-box; 91 | display: box; 92 | 93 | -webkit-box-orient: horizontal; 94 | -moz-box-orient: horizontal; 95 | box-orient: horizontal; 96 | 97 | -webkit-box-pack: center; 98 | -moz-box-pack: center; 99 | box-pack: center; 100 | 101 | -webkit-box-align: center; 102 | -moz-box-align: center; 103 | box-align: center; 104 | 105 | color: #ffffff; 106 | text-align: center; 107 | 108 | cursor: pointer; 109 | 110 | } 111 | 112 | .timertext { 113 | font-size: 30px; 114 | margin-left: 20px; 115 | display: inline-block; 116 | color: white; 117 | z-index: 10000; 118 | 119 | } 120 | 121 | #timer { 122 | position: absolute; 123 | bottom:5px; 124 | left:0px; 125 | width:100%; 126 | } 127 | 128 | 129 | /* ========================================================================== 130 | Media Queries 131 | ========================================================================== */ 132 | 133 | @media only screen and (min-width: 35em) { 134 | 135 | } 136 | 137 | @media print, 138 | (-o-min-device-pixel-ratio: 5/4), 139 | (-webkit-min-device-pixel-ratio: 1.25), 140 | (min-resolution: 120dpi) { 141 | 142 | } 143 | 144 | /* ========================================================================== 145 | Helper classes 146 | ========================================================================== */ 147 | 148 | .hidden { 149 | display: none !important; 150 | visibility: hidden; 151 | } 152 | 153 | .visuallyhidden { 154 | border: 0; 155 | clip: rect(0 0 0 0); 156 | height: 1px; 157 | margin: -1px; 158 | overflow: hidden; 159 | padding: 0; 160 | position: absolute; 161 | width: 1px; 162 | } 163 | 164 | .visuallyhidden.focusable:active, 165 | .visuallyhidden.focusable:focus { 166 | clip: auto; 167 | height: auto; 168 | margin: 0; 169 | overflow: visible; 170 | position: static; 171 | width: auto; 172 | } 173 | 174 | .invisible { 175 | visibility: hidden; 176 | } 177 | 178 | .clearfix:before, 179 | .clearfix:after { 180 | content: " "; 181 | display: table; 182 | } 183 | 184 | .clearfix:after { 185 | clear: both; 186 | } 187 | 188 | .clearfix { 189 | *zoom: 1; 190 | } 191 | 192 | /* ========================================================================== 193 | Print styles 194 | ========================================================================== */ 195 | 196 | @media print { 197 | *, 198 | *:before, 199 | *:after { 200 | background: transparent !important; 201 | color: #000 !important; 202 | box-shadow: none !important; 203 | text-shadow: none !important; 204 | } 205 | 206 | a, 207 | a:visited { 208 | text-decoration: underline; 209 | } 210 | 211 | a[href]:after { 212 | content: " (" attr(href) ")"; 213 | } 214 | 215 | abbr[title]:after { 216 | content: " (" attr(title) ")"; 217 | } 218 | 219 | a[href^="#"]:after, 220 | a[href^="javascript:"]:after { 221 | content: ""; 222 | } 223 | 224 | pre, 225 | blockquote { 226 | border: 1px solid #999; 227 | page-break-inside: avoid; 228 | } 229 | 230 | thead { 231 | display: table-header-group; 232 | } 233 | 234 | tr, 235 | img { 236 | page-break-inside: avoid; 237 | } 238 | 239 | img { 240 | max-width: 100% !important; 241 | } 242 | 243 | p, 244 | h2, 245 | h3 { 246 | orphans: 3; 247 | widows: 3; 248 | } 249 | 250 | h2, 251 | h3 { 252 | page-break-after: avoid; 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /js/example.js: -------------------------------------------------------------------------------- 1 | var Harvest = (function () { 2 | 3 | // Instance stores a reference to the Singleton 4 | var instance; 5 | 6 | function startGame() { 7 | 8 | // Singleton 9 | 10 | var camera, scene, renderer; 11 | var geometry, material, mesh; 12 | var controls; 13 | var boxes = []; 14 | var objects = []; 15 | 16 | var WON = false; 17 | var timer; 18 | var fog = 100; 19 | 20 | 21 | init(); 22 | animate(); 23 | 24 | var prevTime = performance.now(); 25 | var velocity = new THREE.Vector3(); 26 | 27 | 28 | function init() { 29 | 30 | initialiseTimer(); 31 | eventHandlers(); 32 | scene = new THREE.Scene(); 33 | scene.fog = new THREE.Fog( 0xffffff, 0, fog + 1000 ); 34 | 35 | // Sky 36 | var pwd = window.location.href.substring(0, window.location.href.indexOf('/')); 37 | var sky = new THREE.SphereGeometry(8000, 32, 32); // radius, widthSegments, heightSegments 38 | 39 | skyBox = new THREE.Mesh(sky); 40 | skyBox.scale.set(-1, 1, 1); 41 | skyBox.eulerOrder = 'XZY'; 42 | skyBox.renderDepth = 1000.0; 43 | scene.add(skyBox); 44 | 45 | // Floor 46 | var floorHeight = 7000; 47 | geometry = new THREE.SphereGeometry(floorHeight, 10, 6, 0, (Math.PI * 2), 0, 0.8); 48 | geometry.applyMatrix( new THREE.Matrix4().makeTranslation(0, -floorHeight, 0) ); 49 | 50 | var material = new THREE.MeshLambertMaterial( ); 51 | 52 | floorMesh = new THREE.Mesh( geometry, material ); 53 | objects.push( floorMesh ); 54 | scene.add( floorMesh ); 55 | 56 | // Boxes 57 | var boxGeometry = new THREE.BoxGeometry( 20, 20, 20 ); 58 | var boxTexture1 = new THREE.ImageUtils.loadTexture("img/block1.jpg"); 59 | var boxTexture2 = new THREE.ImageUtils.loadTexture("img/block2.jpg"); 60 | var boxTexture3 = new THREE.ImageUtils.loadTexture("img/block3.jpg"); 61 | var boxTexture4 = new THREE.ImageUtils.loadTexture("img/block4.jpg"); 62 | var boxMaterial1 = new THREE.MeshBasicMaterial( {map: boxTexture1, reflectivity: 0.8} ); 63 | var boxMaterial2 = new THREE.MeshBasicMaterial( {map: boxTexture2, reflectivity: 0.8} ); 64 | var boxMaterial3 = new THREE.MeshBasicMaterial( {map: boxTexture3, reflectivity: 0.8} ); 65 | var boxMaterial4 = new THREE.MeshBasicMaterial( {map: boxTexture4, reflectivity: 0.8} ); 66 | var items = [boxMaterial1 ,boxMaterial2, boxMaterial3, boxMaterial4]; 67 | var boxZ; 68 | for ( var i = 0; i < 850; i ++ ) { 69 | 70 | var boxmesh = new THREE.Mesh( boxGeometry, items[Math.floor(Math.random()*items.length)] ); 71 | 72 | boxZ = 50; 73 | boxmesh.position.x = Math.floor( Math.random() * 20 - 10 ) * 20; 74 | boxmesh.position.y = Math.floor( Math.random() * 20 ) * boxZ + 10; 75 | boxmesh.position.z = Math.floor( Math.random() * 20 - 10 ) * 20; 76 | 77 | boxes.push( boxmesh ); 78 | objects.push( boxmesh ); 79 | scene.add( boxmesh ); 80 | } 81 | 82 | 83 | camera = new THREE.PerspectiveCamera( 80, window.innerWidth / window.innerHeight, 1, 9000 ); 84 | controls = new THREE.PointerLockControls( camera, 100, 30, true, objects ); 85 | scene.add( controls.getPlayer() ); 86 | 87 | renderer = new THREE.WebGLRenderer({ antialias: true }); //new THREE.WebGLRenderer(); 88 | renderer.setClearColor( 0xffffff ); 89 | renderer.setPixelRatio( window.devicePixelRatio ); 90 | renderer.setSize( window.innerWidth, window.innerHeight ); 91 | ScreenOverlay(controls); // 92 | document.body.appendChild( renderer.domElement ); 93 | 94 | } 95 | 96 | function animate() { 97 | 98 | requestAnimationFrame( animate ); 99 | 100 | if ( controls.enabled ) { 101 | 102 | controls.updateControls(); 103 | 104 | } 105 | renderer.render( scene, camera ); 106 | 107 | } 108 | 109 | function randomTexture(maxTextures) { 110 | return Math.floor(Math.random() * maxTextures) + 1; 111 | } 112 | 113 | function initialiseTimer() { 114 | var sec = 0; 115 | function pad ( val ) { return val > 9 ? val : "0" + val; } 116 | 117 | timer = setInterval( function(){ 118 | document.getElementById("seconds").innerHTML = String(pad(++sec%60)); 119 | document.getElementById("minutes").innerHTML = String(pad(parseInt(sec/60,10))); 120 | }, 1000); 121 | } 122 | 123 | function eventHandlers() { 124 | 125 | // Keyboard press handlers 126 | var onKeyDown = function ( event ) { event.preventDefault(); event.stopPropagation(); handleKeyInteraction(event.keyCode, true); }; 127 | var onKeyUp = function ( event ) { event.preventDefault(); event.stopPropagation(); handleKeyInteraction(event.keyCode, false); }; 128 | document.addEventListener( 'keydown', onKeyDown, false ); 129 | document.addEventListener( 'keyup', onKeyUp, false ); 130 | 131 | // Resize Event 132 | window.addEventListener( 'resize', onWindowResize, false ); 133 | } 134 | 135 | // HANDLE KEY INTERACTION 136 | function handleKeyInteraction(keyCode, boolean) { 137 | var isKeyDown = boolean; 138 | 139 | switch(keyCode) { 140 | case 38: // up 141 | case 87: // w 142 | controls.movements.forward = boolean; 143 | break; 144 | 145 | case 40: // down 146 | case 83: // s 147 | controls.movements.backward = boolean; 148 | break; 149 | 150 | case 37: // left 151 | case 65: // a 152 | controls.movements.left = boolean; 153 | break; 154 | 155 | case 39: // right 156 | case 68: // d 157 | controls.movements.right = boolean; 158 | break; 159 | 160 | case 32: // space 161 | if (!isKeyDown) { 162 | controls.jump(); 163 | } 164 | break; 165 | 166 | case 16: // shift 167 | controls.walk(boolean); 168 | break; 169 | 170 | case 67: // crouch (CTRL + W etc destroys tab in Chrome!) 171 | controls.crouch(boolean); 172 | 173 | } 174 | } 175 | 176 | function onWindowResize() { 177 | 178 | camera.aspect = window.innerWidth / window.innerHeight; 179 | camera.updateProjectionMatrix(); 180 | 181 | renderer.setSize( window.innerWidth, window.innerHeight ); 182 | 183 | } 184 | 185 | 186 | function fallingBoxes(cube, pos, delay) { 187 | //console.log(cube,pos,delay) 188 | setTimeout(function() { cube.position.setY(pos); }, delay); 189 | } 190 | 191 | return { 192 | // Public methods and variables 193 | setFog: function (setFog) { 194 | fog = setFog; 195 | }, 196 | setJumpFactor: function (setJumpFactor) { 197 | jumpFactor = setJumpFactor; 198 | } 199 | 200 | }; 201 | 202 | }; 203 | 204 | return { 205 | 206 | // Get the Singleton instance if one exists 207 | // or create one if it doesn't 208 | getInstance: function () { 209 | 210 | if ( !instance ) { 211 | instance = startGame(); 212 | } 213 | 214 | return instance; 215 | } 216 | 217 | }; 218 | 219 | })(); 220 | 221 | harvest = Harvest.getInstance(); 222 | -------------------------------------------------------------------------------- /js/vendor/threejs/sky/SkyShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author zz85 / https://github.com/zz85 3 | * 4 | * Based on "A Practical Analytic Model for Daylight" 5 | * aka The Preetham Model, the de facto standard analytic skydome model 6 | * http://www.cs.utah.edu/~shirley/papers/sunsky/sunsky.pdf 7 | * 8 | * First implemented by Simon Wallner 9 | * http://www.simonwallner.at/projects/atmospheric-scattering 10 | * 11 | * Improved by Martin Upitis 12 | * http://blenderartists.org/forum/showthread.php?245954-preethams-sky-impementation-HDR 13 | * 14 | * Three.js integration by zz85 http://twitter.com/blurspline 15 | */ 16 | 17 | THREE.ShaderLib['sky'] = { 18 | 19 | uniforms: { 20 | 21 | luminance: { type: "f", value:1 }, 22 | turbidity: { type: "f", value:2 }, 23 | reileigh: { type: "f", value:1 }, 24 | mieCoefficient: { type: "f", value:0.005 }, 25 | mieDirectionalG: { type: "f", value:0.8 }, 26 | sunPosition: { type: "v3", value: new THREE.Vector3() } 27 | 28 | }, 29 | 30 | vertexShader: [ 31 | 32 | "varying vec3 vWorldPosition;", 33 | 34 | "void main() {", 35 | 36 | "vec4 worldPosition = modelMatrix * vec4( position, 1.0 );", 37 | "vWorldPosition = worldPosition.xyz;", 38 | 39 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 40 | 41 | "}", 42 | 43 | ].join("\n"), 44 | 45 | fragmentShader: [ 46 | 47 | "uniform sampler2D skySampler;", 48 | "uniform vec3 sunPosition;", 49 | "varying vec3 vWorldPosition;", 50 | 51 | "vec3 cameraPos = vec3(0., 0., 0.);", 52 | "// uniform sampler2D sDiffuse;", 53 | "// const float turbidity = 10.0; //", 54 | "// const float reileigh = 2.; //", 55 | "// const float luminance = 1.0; //", 56 | "// const float mieCoefficient = 0.005;", 57 | "// const float mieDirectionalG = 0.8;", 58 | 59 | "uniform float luminance;", 60 | "uniform float turbidity;", 61 | "uniform float reileigh;", 62 | "uniform float mieCoefficient;", 63 | "uniform float mieDirectionalG;", 64 | 65 | "vec3 sunDirection = normalize(sunPosition);", 66 | "float reileighCoefficient = reileigh;", 67 | 68 | "// constants for atmospheric scattering", 69 | "const float e = 2.71828182845904523536028747135266249775724709369995957;", 70 | "const float pi = 3.141592653589793238462643383279502884197169;", 71 | 72 | "const float n = 1.0003; // refractive index of air", 73 | "const float N = 2.545E25; // number of molecules per unit volume for air at", 74 | "// 288.15K and 1013mb (sea level -45 celsius)", 75 | "const float pn = 0.035; // depolatization factor for standard air", 76 | 77 | "// wavelength of used primaries, according to preetham", 78 | "const vec3 lambda = vec3(680E-9, 550E-9, 450E-9);", 79 | 80 | "// mie stuff", 81 | "// K coefficient for the primaries", 82 | "const vec3 K = vec3(0.686, 0.678, 0.666);", 83 | "const float v = 4.0;", 84 | 85 | "// optical length at zenith for molecules", 86 | "const float rayleighZenithLength = 8.4E3;", 87 | "const float mieZenithLength = 1.25E3;", 88 | "const vec3 up = vec3(0.0, 1.0, 0.0);", 89 | 90 | "const float EE = 1000.0;", 91 | "const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324;", 92 | "// 66 arc seconds -> degrees, and the cosine of that", 93 | 94 | "// earth shadow hack", 95 | "const float cutoffAngle = pi/1.95;", 96 | "const float steepness = 1.5;", 97 | 98 | 99 | "vec3 totalRayleigh(vec3 lambda)", 100 | "{", 101 | "return (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn));", 102 | "}", 103 | 104 | // see http://blenderartists.org/forum/showthread.php?321110-Shaders-and-Skybox-madness 105 | "// A simplied version of the total Reayleigh scattering to works on browsers that use ANGLE", 106 | "vec3 simplifiedRayleigh()", 107 | "{", 108 | "return 0.0005 / vec3(94, 40, 18);", 109 | // return 0.00054532832366 / (3.0 * 2.545E25 * pow(vec3(680E-9, 550E-9, 450E-9), vec3(4.0)) * 6.245); 110 | "}", 111 | 112 | "float rayleighPhase(float cosTheta)", 113 | "{ ", 114 | "return (3.0 / (16.0*pi)) * (1.0 + pow(cosTheta, 2.0));", 115 | "// return (1.0 / (3.0*pi)) * (1.0 + pow(cosTheta, 2.0));", 116 | "// return (3.0 / 4.0) * (1.0 + pow(cosTheta, 2.0));", 117 | "}", 118 | 119 | "vec3 totalMie(vec3 lambda, vec3 K, float T)", 120 | "{", 121 | "float c = (0.2 * T ) * 10E-18;", 122 | "return 0.434 * c * pi * pow((2.0 * pi) / lambda, vec3(v - 2.0)) * K;", 123 | "}", 124 | 125 | "float hgPhase(float cosTheta, float g)", 126 | "{", 127 | "return (1.0 / (4.0*pi)) * ((1.0 - pow(g, 2.0)) / pow(1.0 - 2.0*g*cosTheta + pow(g, 2.0), 1.5));", 128 | "}", 129 | 130 | "float sunIntensity(float zenithAngleCos)", 131 | "{", 132 | "return EE * max(0.0, 1.0 - exp(-((cutoffAngle - acos(zenithAngleCos))/steepness)));", 133 | "}", 134 | 135 | "// float logLuminance(vec3 c)", 136 | "// {", 137 | "// return log(c.r * 0.2126 + c.g * 0.7152 + c.b * 0.0722);", 138 | "// }", 139 | 140 | "// Filmic ToneMapping http://filmicgames.com/archives/75", 141 | "float A = 0.15;", 142 | "float B = 0.50;", 143 | "float C = 0.10;", 144 | "float D = 0.20;", 145 | "float E = 0.02;", 146 | "float F = 0.30;", 147 | "float W = 1000.0;", 148 | 149 | "vec3 Uncharted2Tonemap(vec3 x)", 150 | "{", 151 | "return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;", 152 | "}", 153 | 154 | 155 | "void main() ", 156 | "{", 157 | "float sunfade = 1.0-clamp(1.0-exp((sunPosition.y/450000.0)),0.0,1.0);", 158 | 159 | "// luminance = 1.0 ;// vWorldPosition.y / 450000. + 0.5; //sunPosition.y / 450000. * 1. + 0.5;", 160 | 161 | "// gl_FragColor = vec4(sunfade, sunfade, sunfade, 1.0);", 162 | 163 | "reileighCoefficient = reileighCoefficient - (1.0* (1.0-sunfade));", 164 | 165 | "float sunE = sunIntensity(dot(sunDirection, up));", 166 | 167 | "// extinction (absorbtion + out scattering) ", 168 | "// rayleigh coefficients", 169 | 170 | // "vec3 betaR = totalRayleigh(lambda) * reileighCoefficient;", 171 | "vec3 betaR = simplifiedRayleigh() * reileighCoefficient;", 172 | 173 | "// mie coefficients", 174 | "vec3 betaM = totalMie(lambda, K, turbidity) * mieCoefficient;", 175 | 176 | "// optical length", 177 | "// cutoff angle at 90 to avoid singularity in next formula.", 178 | "float zenithAngle = acos(max(0.0, dot(up, normalize(vWorldPosition - cameraPos))));", 179 | "float sR = rayleighZenithLength / (cos(zenithAngle) + 0.15 * pow(93.885 - ((zenithAngle * 180.0) / pi), -1.253));", 180 | "float sM = mieZenithLength / (cos(zenithAngle) + 0.15 * pow(93.885 - ((zenithAngle * 180.0) / pi), -1.253));", 181 | 182 | 183 | 184 | "// combined extinction factor ", 185 | "vec3 Fex = exp(-(betaR * sR + betaM * sM));", 186 | 187 | "// in scattering", 188 | "float cosTheta = dot(normalize(vWorldPosition - cameraPos), sunDirection);", 189 | 190 | "float rPhase = rayleighPhase(cosTheta*0.5+0.5);", 191 | "vec3 betaRTheta = betaR * rPhase;", 192 | 193 | "float mPhase = hgPhase(cosTheta, mieDirectionalG);", 194 | "vec3 betaMTheta = betaM * mPhase;", 195 | 196 | 197 | "vec3 Lin = pow(sunE * ((betaRTheta + betaMTheta) / (betaR + betaM)) * (1.0 - Fex),vec3(1.5));", 198 | "Lin *= mix(vec3(1.0),pow(sunE * ((betaRTheta + betaMTheta) / (betaR + betaM)) * Fex,vec3(1.0/2.0)),clamp(pow(1.0-dot(up, sunDirection),5.0),0.0,1.0));", 199 | 200 | "//nightsky", 201 | "vec3 direction = normalize(vWorldPosition - cameraPos);", 202 | "float theta = acos(direction.y); // elevation --> y-axis, [-pi/2, pi/2]", 203 | "float phi = atan(direction.z, direction.x); // azimuth --> x-axis [-pi/2, pi/2]", 204 | "vec2 uv = vec2(phi, theta) / vec2(2.0*pi, pi) + vec2(0.5, 0.0);", 205 | "// vec3 L0 = texture2D(skySampler, uv).rgb+0.1 * Fex;", 206 | "vec3 L0 = vec3(0.1) * Fex;", 207 | 208 | "// composition + solar disc", 209 | "//if (cosTheta > sunAngularDiameterCos)", 210 | "float sundisk = smoothstep(sunAngularDiameterCos,sunAngularDiameterCos+0.00002,cosTheta);", 211 | "// if (normalize(vWorldPosition - cameraPos).y>0.0)", 212 | "L0 += (sunE * 19000.0 * Fex)*sundisk;", 213 | 214 | 215 | "vec3 whiteScale = 1.0/Uncharted2Tonemap(vec3(W));", 216 | 217 | "vec3 texColor = (Lin+L0); ", 218 | "texColor *= 0.04 ;", 219 | "texColor += vec3(0.0,0.001,0.0025)*0.3;", 220 | 221 | "float g_fMaxLuminance = 1.0;", 222 | "float fLumScaled = 0.1 / luminance; ", 223 | "float fLumCompressed = (fLumScaled * (1.0 + (fLumScaled / (g_fMaxLuminance * g_fMaxLuminance)))) / (1.0 + fLumScaled); ", 224 | 225 | "float ExposureBias = fLumCompressed;", 226 | 227 | "vec3 curr = Uncharted2Tonemap((log2(2.0/pow(luminance,4.0)))*texColor);", 228 | "vec3 color = curr*whiteScale;", 229 | 230 | "vec3 retColor = pow(color,vec3(1.0/(1.2+(1.2*sunfade))));", 231 | 232 | 233 | "gl_FragColor.rgb = retColor;", 234 | 235 | "gl_FragColor.a = 1.0;", 236 | "}", 237 | 238 | ].join("\n") 239 | 240 | }; 241 | 242 | THREE.Sky = function () { 243 | 244 | var skyShader = THREE.ShaderLib[ "sky" ]; 245 | var skyUniforms = THREE.UniformsUtils.clone( skyShader.uniforms ); 246 | 247 | var skyMat = new THREE.ShaderMaterial( { 248 | fragmentShader: skyShader.fragmentShader, 249 | vertexShader: skyShader.vertexShader, 250 | uniforms: skyUniforms, 251 | side: THREE.BackSide 252 | } ); 253 | 254 | var skyGeo = new THREE.SphereGeometry( 450000, 32, 15 ); 255 | var skyMesh = new THREE.Mesh( skyGeo, skyMat ); 256 | 257 | 258 | // Expose variables 259 | this.mesh = skyMesh; 260 | this.uniforms = skyUniforms; 261 | 262 | }; 263 | -------------------------------------------------------------------------------- /js/FPSControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.PointerLockControls = function ( camera, mass, playerHeight, doubleJump, worldObjects ) { 6 | 7 | var scope = this; 8 | 9 | scope.worldObjects = worldObjects; 10 | 11 | camera.rotation.set( 0, 0, 0 ); 12 | 13 | var pitchObject = new THREE.Object3D(); 14 | pitchObject.add( camera ); 15 | 16 | var yawObject = new THREE.Object3D(); 17 | yawObject.position.y = playerHeight; 18 | yawObject.add( pitchObject ); 19 | 20 | var PI_2 = Math.PI / 2; 21 | 22 | var onMouseMove = function ( event ) { 23 | 24 | if ( scope.enabled === false ) return; 25 | 26 | var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; 27 | var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; 28 | 29 | yawObject.rotation.y -= movementX * 0.002; 30 | pitchObject.rotation.x -= movementY * 0.002; 31 | 32 | pitchObject.rotation.x = Math.max( - PI_2, Math.min( PI_2, pitchObject.rotation.x ) ); 33 | 34 | }; 35 | 36 | scope.dispose = function() { 37 | 38 | document.removeEventListener( 'mousemove', onMouseMove, false ); 39 | 40 | }; 41 | 42 | document.addEventListener( 'mousemove', onMouseMove, false ); 43 | 44 | scope.enabled = false; 45 | 46 | scope.getPlayer = function () { 47 | 48 | return yawObject; 49 | 50 | }; 51 | 52 | scope.getDirection = function() { 53 | 54 | // assumes the camera itself is not rotated 55 | 56 | var direction = new THREE.Vector3( 0, 0, - 1 ); 57 | var rotation = new THREE.Euler( 0, 0, 0, "YXZ" ); 58 | 59 | return function( v ) { 60 | 61 | rotation.set( pitchObject.rotation.x, yawObject.rotation.y, 0 ); 62 | 63 | v.copy( direction ).applyEuler( rotation ); 64 | 65 | return v; 66 | 67 | }; 68 | 69 | }(); 70 | 71 | // FPS Controls Additions 72 | 73 | scope.updatePlayerHeight = function(height) { 74 | yawObject.position.y = height; 75 | }; 76 | 77 | scope.raycasters = { 78 | 79 | down : new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3( 0, - 1, 0 ), 0, 20 ), 80 | up : new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3( 0, 1, 0 ), 0, 20 ), 81 | forward : new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3(0, 0, -1), 0, 15 ), 82 | backward : new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3(), 0, 15 ), 83 | left : new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3(), 0, 15 ), 84 | right : new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3(), 0, 15 ), 85 | rightStrafe : new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3(), 0, 30 ), 86 | leftStrafe : new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3(), 0, 30 ), 87 | 88 | updateRaycasters : function() { 89 | 90 | this.up.ray.origin.copy(scope.playersPosition); 91 | this.down.ray.origin.copy(scope.playersPosition); 92 | this.forward.ray.set(scope.playersPosition, scope.camDir); 93 | this.backward.ray.set(scope.playersPosition, scope.camDir.negate()); 94 | this.left.ray.set(scope.playersPosition, scope.camDir.applyMatrix4( new THREE.Matrix4().makeRotationY(- (Math.PI / 2) ))); 95 | this.right.ray.set(scope.playersPosition, scope.camDir.applyMatrix4( new THREE.Matrix4().makeRotationY( Math.PI ))); 96 | this.rightStrafe.ray.set(scope.playersPosition, scope.camDir.applyMatrix4( new THREE.Matrix4().makeRotationY( (Math.PI / 4) ))); // Working 97 | this.leftStrafe.ray.set(scope.playersPosition, scope.camDir.applyMatrix4( new THREE.Matrix4().makeRotationY( (Math.PI / 4) ))); 98 | 99 | } 100 | 101 | }; 102 | 103 | scope.intersections = { 104 | 105 | down : scope.raycasters.down.intersectObjects(worldObjects), 106 | up : scope.raycasters.up.intersectObjects(worldObjects ), 107 | forward : scope.raycasters.forward.intersectObjects(worldObjects ), 108 | backward : scope.raycasters.backward.intersectObjects(worldObjects ), 109 | left : scope.raycasters.left.intersectObjects(worldObjects ), 110 | right : scope.raycasters.right.intersectObjects(worldObjects ), 111 | rightStrafe : scope.raycasters.rightStrafe.intersectObjects(worldObjects ), 112 | leftStrafe : scope.raycasters.leftStrafe.intersectObjects(worldObjects ), 113 | 114 | checkIntersections : function() { 115 | 116 | this.down = scope.raycasters.down.intersectObjects(worldObjects); 117 | this.up = scope.raycasters.up.intersectObjects(worldObjects ); 118 | this.forward = scope.raycasters.forward.intersectObjects(worldObjects ); 119 | this.backward = scope.raycasters.backward.intersectObjects(worldObjects ); 120 | this.left = scope.raycasters.left.intersectObjects(worldObjects ); 121 | this.right = scope.raycasters.right.intersectObjects(worldObjects ); 122 | this.rightStrafe = scope.raycasters.rightStrafe.intersectObjects(worldObjects ); 123 | this.leftStrafe = scope.raycasters.leftStrafe.intersectObjects(worldObjects); 124 | 125 | } 126 | 127 | }; 128 | 129 | scope.movements = { 130 | 131 | forward : false, 132 | backward : false, 133 | left : false, 134 | right : false, 135 | 136 | locks : { 137 | forward : true, 138 | backward : true, 139 | left : true, 140 | right : true, 141 | }, 142 | 143 | lock : function() { 144 | var intersections = scope.intersections; 145 | for (var direction in intersections) { 146 | if (intersections[direction].length > 0) { 147 | //console.log(direction, "lock"); 148 | this.locks[direction] = true; 149 | } 150 | } 151 | }, 152 | 153 | unlock : function() { 154 | this.locks.forward = false; 155 | this.locks.backward = false; 156 | this.locks.left = false; 157 | this.locks.right = false; 158 | } 159 | 160 | }; 161 | 162 | scope.doubleJump = doubleJump; 163 | scope.baseHeight = 0; // The minimum plane height 164 | scope.mass = mass || 100; 165 | scope.originalMass = mass; 166 | scope.walkingSpeed = 3000; // Higher = slower 167 | scope.speed = 900; // Movement speed 168 | scope.jumpFactor = 90; // Jump height 169 | scope.velocity = new THREE.Vector3(1, 1, 1); 170 | 171 | scope.jumps = 0; 172 | scope.firstJump = true; 173 | scope.walking = false; 174 | 175 | // Crouched 176 | scope.crouching = false; 177 | var halfHeight; 178 | var fullHeight; 179 | var crouchSmoothing; 180 | var smoothedHeight; 181 | var crouched = false; 182 | 183 | // Jump Variables 184 | scope.jumping = false; 185 | 186 | scope.jump = function() { 187 | scope.jumping = true; 188 | }; 189 | 190 | scope.crouch = function(boolean) { 191 | scope.crouching = boolean; 192 | }; 193 | 194 | scope.walk = function(boolean) { 195 | scope.walking = boolean; 196 | }; 197 | 198 | // So you can update the world objects when they change 199 | scope.updateWorldObjects = function(worldObjects) { 200 | scope.worldObjects = worldObjects; 201 | }; 202 | 203 | scope.updateControls = function() { 204 | 205 | scope.time = performance.now(); 206 | 207 | scope.movements.unlock(); 208 | 209 | // Check change and if Walking? 210 | scope.delta = (scope.walking) ? ( scope.time - scope.prevTime ) / scope.walkingSpeed : ( scope.time - scope.prevTime ) / scope.speed; 211 | var validDelta = isNaN(scope.delta) === false; 212 | if (validDelta) { 213 | 214 | // Velocities 215 | scope.velocity.x -= scope.velocity.x * 8.0 * scope.delta; // Left and right 216 | scope.velocity.z -= scope.velocity.z * 8.0 * scope.delta; // Forward and back 217 | scope.velocity.y -= (scope.walking) ? 9.8 * scope.mass * scope.delta : 5.5 * scope.mass * scope.delta; // Up and Down 218 | 219 | scope.camDir = scope.getPlayer().getWorldDirection().negate(); // 220 | scope.playersPosition = scope.getPlayer().position.clone(); 221 | 222 | scope.raycasters.updateRaycasters(); 223 | scope.intersections.checkIntersections(); 224 | scope.movements.lock(); 225 | 226 | // If your head hits an object, turn your mass up to make you fall back to earth 227 | scope.isBelowObject = scope.intersections.up.length > 0; 228 | scope.mass = (scope.isBelowObject === true ) ? 500 : scope.originalMass; 229 | 230 | scope.isOnObject = scope.intersections.down.length > 0; 231 | if ( scope.isOnObject === true ) { 232 | scope.velocity.y = Math.max( 0, scope.velocity.y ); 233 | scope.jumps = 0; 234 | 235 | //If we start to fall through an object 236 | // if ((this.getPlayer().position.y < playerHeight) && 237 | // scope.downwardsIntersection && 238 | // scope.downwardsIntersection[0].distance < (playerHeight / 2) ) { 239 | // 240 | // this.getPlayer().position.y += 0.1; 241 | // } 242 | 243 | } else { 244 | this.walking = false; 245 | } 246 | 247 | // Crouched 248 | 249 | 250 | 251 | if (!crouched && scope.isOnObject) { 252 | console.log("Not Crouched"); 253 | halfHeight = scope.getPlayer().position.y - (playerHeight * 0.2); 254 | fullHeight = scope.getPlayer().position.y + (playerHeight * 0.2); 255 | } 256 | 257 | if (scope.crouching && scope.isOnObject) { 258 | 259 | scope.walking = true; 260 | if (!crouched && !scope.justCrouched) { 261 | scope.updatePlayerHeight(halfHeight); 262 | crouchSmoothing = 0; 263 | smoothedHeight = 0; 264 | crouched = true; 265 | 266 | // Stop people from crouching through the floor 267 | scope.justCrouched = true; 268 | setTimeout(function() { scope.justCrouched = false; }, 300); 269 | } 270 | 271 | } else if (!scope.crouching && smoothedHeight <= fullHeight ){ 272 | 273 | // Smooth out of crouching 274 | console.log("finished"); 275 | smoothedHeight = halfHeight + crouchSmoothing; 276 | scope.updatePlayerHeight(smoothedHeight); 277 | crouchSmoothing += 2; 278 | //console.log(smoothedHeight) 279 | crouched = false; 280 | scope.walking = false; 281 | 282 | } 283 | 284 | // Jumping - must come after isBelowObject but before isOnObject 285 | if (scope.jumping) { 286 | 287 | scope.walking = false; 288 | scope.crouching = false; 289 | 290 | if (scope.jumps === 0 && !scope.isBelowObject) { 291 | scope.velocity.y += scope.jumpFactor * 2.3; 292 | scope.velocity.z *= 2; // Jumping also increases our forward velocity a little 293 | scope.jumps = 1; 294 | } 295 | else if (scope.doubleJump && scope.jumps === 1 && !scope.isOnObject && !scope.isBelowObject) { 296 | scope.velocity.y += scope.jumpFactor * 1.5; 297 | scope.jumps = 2; 298 | } 299 | 300 | } 301 | 302 | // Movements 303 | 304 | if ( scope.movements.forward && !scope.walking && !scope.movements.locks.forward) scope.velocity.z -= 400.0 * scope.delta; 305 | if ( scope.movements.forward && scope.walking && !scope.movements.locks.forward) scope.velocity.z -= 1000.0 * scope.delta; 306 | if ( scope.movements.backward && !scope.movements.locks.backward ) scope.velocity.z += 400.0 * scope.delta; 307 | if ( scope.movements.left && !scope.movements.locks.left ) scope.velocity.x -= 400.0 * scope.delta; 308 | if ( scope.movements.right && !scope.movements.locks.right ) scope.velocity.x += 400.0 * scope.delta; 309 | 310 | 311 | // Velocity translations 312 | scope.getPlayer().translateX( scope.velocity.x * scope.delta ); 313 | scope.getPlayer().translateY( scope.velocity.y * scope.delta ); 314 | scope.getPlayer().translateZ( scope.velocity.z * scope.delta ); 315 | 316 | scope.jumping = false; 317 | 318 | } 319 | 320 | scope.prevTime = scope.time; // Set the previous time to the time we set at the begining 321 | 322 | }; 323 | 324 | }; 325 | --------------------------------------------------------------------------------