├── 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 |
--------------------------------------------------------------------------------