├── .gitignore
├── README.md
├── index.html
├── js
├── Detector.js
├── OrbitControls.js
├── THREEx.FullScreen.js
├── THREEx.KeyboardState.js
├── THREEx.WindowResize.js
├── index.js
└── three.min.js
├── sound
└── explode.mp3
└── test
├── cube_collision.html
└── move_cube.html
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Example user template template
2 | ### Example user template
3 |
4 | # IntelliJ project files
5 | .idea
6 | *.iml
7 | out
8 | gen
9 | # Created by .ignore support plugin (hsz.mobi)
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # race-game-threejs
2 | A simple race game using three.js
3 |
4 | [click here to play](http://noiron.github.io/race-game-threejs/)
5 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Race car game
6 |
7 |
8 |
9 |
10 |
11 |
12 |
39 |
40 |
41 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/js/Detector.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author alteredq / http://alteredqualia.com/
3 | * @author mr.doob / http://mrdoob.com/
4 | */
5 |
6 | Detector = {
7 |
8 | canvas: !! window.CanvasRenderingContext2D,
9 | webgl: ( function () { try { return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )(),
10 | workers: !! window.Worker,
11 | fileapi: window.File && window.FileReader && window.FileList && window.Blob,
12 |
13 | getWebGLErrorMessage: function () {
14 |
15 | var element = document.createElement( 'div' );
16 | element.id = 'webgl-error-message';
17 | element.style.fontFamily = 'monospace';
18 | element.style.fontSize = '13px';
19 | element.style.fontWeight = 'normal';
20 | element.style.textAlign = 'center';
21 | element.style.background = '#fff';
22 | element.style.color = '#000';
23 | element.style.padding = '1.5em';
24 | element.style.width = '400px';
25 | element.style.margin = '5em auto 0';
26 |
27 | if ( ! this.webgl ) {
28 |
29 | element.innerHTML = window.WebGLRenderingContext ? [
30 | 'Your graphics card does not seem to support WebGL.
',
31 | 'Find out how to get it here.'
32 | ].join( '\n' ) : [
33 | 'Your browser does not seem to support WebGL.
',
34 | 'Find out how to get it here.'
35 | ].join( '\n' );
36 |
37 | }
38 |
39 | return element;
40 |
41 | },
42 |
43 | addGetWebGLMessage: function ( parameters ) {
44 |
45 | var parent, id, element;
46 |
47 | parameters = parameters || {};
48 |
49 | parent = parameters.parent !== undefined ? parameters.parent : document.body;
50 | id = parameters.id !== undefined ? parameters.id : 'oldie';
51 |
52 | element = Detector.getWebGLErrorMessage();
53 | element.id = id;
54 |
55 | parent.appendChild( element );
56 |
57 | }
58 |
59 | };
60 |
--------------------------------------------------------------------------------
/js/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 | */
7 |
8 | THREE.OrbitControls = function ( object, domElement ) {
9 |
10 | this.object = object;
11 | this.domElement = ( domElement !== undefined ) ? domElement : document;
12 |
13 | // API
14 |
15 | this.enabled = true;
16 |
17 | this.center = new THREE.Vector3();
18 |
19 | this.userZoom = true;
20 | this.userZoomSpeed = 1.0;
21 |
22 | this.userRotate = true;
23 | this.userRotateSpeed = 1.0;
24 |
25 | this.userPan = true;
26 | this.userPanSpeed = 2.0;
27 |
28 | this.autoRotate = false;
29 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
30 |
31 | this.minPolarAngle = 0; // radians
32 | this.maxPolarAngle = Math.PI; // radians
33 |
34 | this.minDistance = 0;
35 | this.maxDistance = Infinity;
36 |
37 | // 65 /*A*/, 83 /*S*/, 68 /*D*/
38 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40, ROTATE: 65, ZOOM: 83, PAN: 68 };
39 |
40 | // internals
41 |
42 | var scope = this;
43 |
44 | var EPS = 0.000001;
45 | var PIXELS_PER_ROUND = 1800;
46 |
47 | var rotateStart = new THREE.Vector2();
48 | var rotateEnd = new THREE.Vector2();
49 | var rotateDelta = new THREE.Vector2();
50 |
51 | var zoomStart = new THREE.Vector2();
52 | var zoomEnd = new THREE.Vector2();
53 | var zoomDelta = new THREE.Vector2();
54 |
55 | var phiDelta = 0;
56 | var thetaDelta = 0;
57 | var scale = 1;
58 |
59 | var lastPosition = new THREE.Vector3();
60 |
61 | var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2 };
62 | var state = STATE.NONE;
63 |
64 | // events
65 |
66 | var changeEvent = { type: 'change' };
67 |
68 |
69 | this.rotateLeft = function ( angle ) {
70 |
71 | if ( angle === undefined ) {
72 |
73 | angle = getAutoRotationAngle();
74 |
75 | }
76 |
77 | thetaDelta -= angle;
78 |
79 | };
80 |
81 | this.rotateRight = function ( angle ) {
82 |
83 | if ( angle === undefined ) {
84 |
85 | angle = getAutoRotationAngle();
86 |
87 | }
88 |
89 | thetaDelta += angle;
90 |
91 | };
92 |
93 | this.rotateUp = function ( angle ) {
94 |
95 | if ( angle === undefined ) {
96 |
97 | angle = getAutoRotationAngle();
98 |
99 | }
100 |
101 | phiDelta -= angle;
102 |
103 | };
104 |
105 | this.rotateDown = function ( angle ) {
106 |
107 | if ( angle === undefined ) {
108 |
109 | angle = getAutoRotationAngle();
110 |
111 | }
112 |
113 | phiDelta += angle;
114 |
115 | };
116 |
117 | this.zoomIn = function ( zoomScale ) {
118 |
119 | if ( zoomScale === undefined ) {
120 |
121 | zoomScale = getZoomScale();
122 |
123 | }
124 |
125 | scale /= zoomScale;
126 |
127 | };
128 |
129 | this.zoomOut = function ( zoomScale ) {
130 |
131 | if ( zoomScale === undefined ) {
132 |
133 | zoomScale = getZoomScale();
134 |
135 | }
136 |
137 | scale *= zoomScale;
138 |
139 | };
140 |
141 | this.pan = function ( distance ) {
142 |
143 | distance.transformDirection( this.object.matrix );
144 | distance.multiplyScalar( scope.userPanSpeed );
145 |
146 | this.object.position.add( distance );
147 | this.center.add( distance );
148 |
149 | };
150 |
151 | this.update = function () {
152 |
153 | var position = this.object.position;
154 | var offset = position.clone().sub( this.center );
155 |
156 | // angle from z-axis around y-axis
157 |
158 | var theta = Math.atan2( offset.x, offset.z );
159 |
160 | // angle from y-axis
161 |
162 | var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
163 |
164 | if ( this.autoRotate ) {
165 |
166 | this.rotateLeft( getAutoRotationAngle() );
167 |
168 | }
169 |
170 | theta += thetaDelta;
171 | phi += phiDelta;
172 |
173 | // restrict phi to be between desired limits
174 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
175 |
176 | // restrict phi to be betwee EPS and PI-EPS
177 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
178 |
179 | var radius = offset.length() * scale;
180 |
181 | // restrict radius to be between desired limits
182 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
183 |
184 | offset.x = radius * Math.sin( phi ) * Math.sin( theta );
185 | offset.y = radius * Math.cos( phi );
186 | offset.z = radius * Math.sin( phi ) * Math.cos( theta );
187 |
188 | position.copy( this.center ).add( offset );
189 |
190 | this.object.lookAt( this.center );
191 |
192 | thetaDelta = 0;
193 | phiDelta = 0;
194 | scale = 1;
195 |
196 | if ( lastPosition.distanceTo( this.object.position ) > 0 ) {
197 |
198 | this.dispatchEvent( changeEvent );
199 |
200 | lastPosition.copy( this.object.position );
201 |
202 | }
203 |
204 | };
205 |
206 |
207 | function getAutoRotationAngle() {
208 |
209 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
210 |
211 | }
212 |
213 | function getZoomScale() {
214 |
215 | return Math.pow( 0.95, scope.userZoomSpeed );
216 |
217 | }
218 |
219 | function onMouseDown( event ) {
220 |
221 | if ( scope.enabled === false ) return;
222 | if ( scope.userRotate === false ) return;
223 |
224 | event.preventDefault();
225 |
226 | if ( state === STATE.NONE )
227 | {
228 | if ( event.button === 0 )
229 | state = STATE.ROTATE;
230 | if ( event.button === 1 )
231 | state = STATE.ZOOM;
232 | if ( event.button === 2 )
233 | state = STATE.PAN;
234 | }
235 |
236 |
237 | if ( state === STATE.ROTATE ) {
238 |
239 | //state = STATE.ROTATE;
240 |
241 | rotateStart.set( event.clientX, event.clientY );
242 |
243 | } else if ( state === STATE.ZOOM ) {
244 |
245 | //state = STATE.ZOOM;
246 |
247 | zoomStart.set( event.clientX, event.clientY );
248 |
249 | } else if ( state === STATE.PAN ) {
250 |
251 | //state = STATE.PAN;
252 |
253 | }
254 |
255 | document.addEventListener( 'mousemove', onMouseMove, false );
256 | document.addEventListener( 'mouseup', onMouseUp, false );
257 |
258 | }
259 |
260 | function onMouseMove( event ) {
261 |
262 | if ( scope.enabled === false ) return;
263 |
264 | event.preventDefault();
265 |
266 |
267 |
268 | if ( state === STATE.ROTATE ) {
269 |
270 | rotateEnd.set( event.clientX, event.clientY );
271 | rotateDelta.subVectors( rotateEnd, rotateStart );
272 |
273 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / PIXELS_PER_ROUND * scope.userRotateSpeed );
274 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / PIXELS_PER_ROUND * scope.userRotateSpeed );
275 |
276 | rotateStart.copy( rotateEnd );
277 |
278 | } else if ( state === STATE.ZOOM ) {
279 |
280 | zoomEnd.set( event.clientX, event.clientY );
281 | zoomDelta.subVectors( zoomEnd, zoomStart );
282 |
283 | if ( zoomDelta.y > 0 ) {
284 |
285 | scope.zoomIn();
286 |
287 | } else {
288 |
289 | scope.zoomOut();
290 |
291 | }
292 |
293 | zoomStart.copy( zoomEnd );
294 |
295 | } else if ( state === STATE.PAN ) {
296 |
297 | var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
298 | var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
299 |
300 | scope.pan( new THREE.Vector3( - movementX, movementY, 0 ) );
301 |
302 | }
303 |
304 | }
305 |
306 | function onMouseUp( event ) {
307 |
308 | if ( scope.enabled === false ) return;
309 | if ( scope.userRotate === false ) return;
310 |
311 | document.removeEventListener( 'mousemove', onMouseMove, false );
312 | document.removeEventListener( 'mouseup', onMouseUp, false );
313 |
314 | state = STATE.NONE;
315 |
316 | }
317 |
318 | function onMouseWheel( event ) {
319 |
320 | if ( scope.enabled === false ) return;
321 | if ( scope.userZoom === false ) return;
322 |
323 | var delta = 0;
324 |
325 | if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9
326 |
327 | delta = event.wheelDelta;
328 |
329 | } else if ( event.detail ) { // Firefox
330 |
331 | delta = - event.detail;
332 |
333 | }
334 |
335 | if ( delta > 0 ) {
336 |
337 | scope.zoomOut();
338 |
339 | } else {
340 |
341 | scope.zoomIn();
342 |
343 | }
344 |
345 | }
346 |
347 | function onKeyDown( event ) {
348 |
349 | if ( scope.enabled === false ) return;
350 | if ( scope.userPan === false ) return;
351 |
352 | switch ( event.keyCode ) {
353 |
354 | /*case scope.keys.UP:
355 | scope.pan( new THREE.Vector3( 0, 1, 0 ) );
356 | break;
357 | case scope.keys.BOTTOM:
358 | scope.pan( new THREE.Vector3( 0, - 1, 0 ) );
359 | break;
360 | case scope.keys.LEFT:
361 | scope.pan( new THREE.Vector3( - 1, 0, 0 ) );
362 | break;
363 | case scope.keys.RIGHT:
364 | scope.pan( new THREE.Vector3( 1, 0, 0 ) );
365 | break;
366 | */
367 | case scope.keys.ROTATE:
368 | state = STATE.ROTATE;
369 | break;
370 | case scope.keys.ZOOM:
371 | state = STATE.ZOOM;
372 | break;
373 | case scope.keys.PAN:
374 | state = STATE.PAN;
375 | break;
376 |
377 | }
378 |
379 | }
380 |
381 | function onKeyUp( event ) {
382 |
383 | switch ( event.keyCode ) {
384 |
385 | case scope.keys.ROTATE:
386 | case scope.keys.ZOOM:
387 | case scope.keys.PAN:
388 | state = STATE.NONE;
389 | break;
390 | }
391 |
392 | }
393 |
394 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
395 | this.domElement.addEventListener( 'mousedown', onMouseDown, false );
396 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
397 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
398 | window.addEventListener( 'keydown', onKeyDown, false );
399 | window.addEventListener( 'keyup', onKeyUp, false );
400 |
401 | };
402 |
403 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
404 |
--------------------------------------------------------------------------------
/js/THREEx.FullScreen.js:
--------------------------------------------------------------------------------
1 | // This THREEx helper makes it easy to handle the fullscreen API
2 | // * it hides the prefix for each browser
3 | // * it hides the little discrepencies of the various vendor API
4 | // * at the time of this writing (nov 2011) it is available in
5 | // [firefox nightly](http://blog.pearce.org.nz/2011/11/firefoxs-html-full-screen-api-enabled.html),
6 | // [webkit nightly](http://peter.sh/2011/01/javascript-full-screen-api-navigation-timing-and-repeating-css-gradients/) and
7 | // [chrome stable](http://updates.html5rocks.com/2011/10/Let-Your-Content-Do-the-Talking-Fullscreen-API).
8 |
9 | // # Code
10 |
11 | /** @namespace */
12 | var THREEx = THREEx || {};
13 | THREEx.FullScreen = THREEx.FullScreen || {};
14 |
15 | /**
16 | * test if it is possible to have fullscreen
17 | *
18 | * @returns {Boolean} true if fullscreen API is available, false otherwise
19 | */
20 | THREEx.FullScreen.available = function()
21 | {
22 | return this._hasWebkitFullScreen || this._hasMozFullScreen;
23 | }
24 |
25 | /**
26 | * test if fullscreen is currently activated
27 | *
28 | * @returns {Boolean} true if fullscreen is currently activated, false otherwise
29 | */
30 | THREEx.FullScreen.activated = function()
31 | {
32 | if( this._hasWebkitFullScreen ){
33 | return document.webkitIsFullScreen;
34 | }else if( this._hasMozFullScreen ){
35 | return document.mozFullScreen;
36 | }else{
37 | console.assert(false);
38 | }
39 | }
40 |
41 | /**
42 | * Request fullscreen on a given element
43 | * @param {DomElement} element to make fullscreen. optional. default to document.body
44 | */
45 | THREEx.FullScreen.request = function(element)
46 | {
47 | element = element || document.body;
48 | if( this._hasWebkitFullScreen ){
49 | element.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
50 | }else if( this._hasMozFullScreen ){
51 | element.mozRequestFullScreen();
52 | }else{
53 | console.assert(false);
54 | }
55 | }
56 |
57 | /**
58 | * Cancel fullscreen
59 | */
60 | THREEx.FullScreen.cancel = function()
61 | {
62 | if( this._hasWebkitFullScreen ){
63 | document.webkitCancelFullScreen();
64 | }else if( this._hasMozFullScreen ){
65 | document.mozCancelFullScreen();
66 | }else{
67 | console.assert(false);
68 | }
69 | }
70 |
71 | // internal functions to know which fullscreen API implementation is available
72 | THREEx.FullScreen._hasWebkitFullScreen = 'webkitCancelFullScreen' in document ? true : false;
73 | THREEx.FullScreen._hasMozFullScreen = 'mozCancelFullScreen' in document ? true : false;
74 |
75 | /**
76 | * Bind a key to renderer screenshot
77 | * usage: THREEx.FullScreen.bindKey({ charCode : 'a'.charCodeAt(0) });
78 | */
79 | THREEx.FullScreen.bindKey = function(opts){
80 | opts = opts || {};
81 | var charCode = opts.charCode || 'f'.charCodeAt(0);
82 | var dblclick = opts.dblclick !== undefined ? opts.dblclick : false;
83 | var element = opts.element
84 |
85 | var toggle = function(){
86 | if( THREEx.FullScreen.activated() ){
87 | THREEx.FullScreen.cancel();
88 | }else{
89 | THREEx.FullScreen.request(element);
90 | }
91 | }
92 |
93 | var onKeyPress = function(event){
94 | if( event.which !== charCode ) return;
95 | toggle();
96 | }.bind(this);
97 |
98 | document.addEventListener('keypress', onKeyPress, false);
99 |
100 | dblclick && document.addEventListener('dblclick', toggle, false);
101 |
102 | return {
103 | unbind : function(){
104 | document.removeEventListener('keypress', onKeyPress, false);
105 | dblclick && document.removeEventListener('dblclick', toggle, false);
106 | }
107 | };
108 | }
109 |
--------------------------------------------------------------------------------
/js/THREEx.KeyboardState.js:
--------------------------------------------------------------------------------
1 | // THREEx.KeyboardState.js keep the current state of the keyboard.
2 | // It is possible to query it at any time. No need of an event.
3 | // This is particularly convenient in loop driven case, like in
4 | // 3D demos or games.
5 | //
6 | // # Usage
7 | //
8 | // **Step 1**: Create the object
9 | //
10 | // ```var keyboard = new THREEx.KeyboardState();```
11 | //
12 | // **Step 2**: Query the keyboard state
13 | //
14 | // This will return true if shift and A are pressed, false otherwise
15 | //
16 | // ```keyboard.pressed("shift+A")```
17 | //
18 | // **Step 3**: Stop listening to the keyboard
19 | //
20 | // ```keyboard.destroy()```
21 | //
22 | // NOTE: this library may be nice as standaline. independant from three.js
23 | // - rename it keyboardForGame
24 | //
25 | // # Code
26 | //
27 |
28 | /** @namespace */
29 | var THREEx = THREEx || {};
30 |
31 | /**
32 | * - NOTE: it would be quite easy to push event-driven too
33 | * - microevent.js for events handling
34 | * - in this._onkeyChange, generate a string from the DOM event
35 | * - use this as event name
36 | */
37 | THREEx.KeyboardState = function()
38 | {
39 | // to store the current state
40 | this.keyCodes = {};
41 | this.modifiers = {};
42 |
43 | // create callback to bind/unbind keyboard events
44 | var self = this;
45 | this._onKeyDown = function(event){ self._onKeyChange(event, true); };
46 | this._onKeyUp = function(event){ self._onKeyChange(event, false);};
47 |
48 | // bind keyEvents
49 | document.addEventListener("keydown", this._onKeyDown, false);
50 | document.addEventListener("keyup", this._onKeyUp, false);
51 | }
52 |
53 | /**
54 | * To stop listening of the keyboard events
55 | */
56 | THREEx.KeyboardState.prototype.destroy = function()
57 | {
58 | // unbind keyEvents
59 | document.removeEventListener("keydown", this._onKeyDown, false);
60 | document.removeEventListener("keyup", this._onKeyUp, false);
61 | }
62 |
63 | THREEx.KeyboardState.MODIFIERS = ['shift', 'ctrl', 'alt', 'meta'];
64 | THREEx.KeyboardState.ALIAS = {
65 | 'left' : 37,
66 | 'up' : 38,
67 | 'right' : 39,
68 | 'down' : 40,
69 | 'space' : 32,
70 | 'pageup' : 33,
71 | 'pagedown' : 34,
72 | 'tab' : 9
73 | };
74 |
75 | /**
76 | * to process the keyboard dom event
77 | */
78 | THREEx.KeyboardState.prototype._onKeyChange = function(event, pressed)
79 | {
80 | // log to debug
81 | //console.log("onKeyChange", event, pressed, event.keyCode, event.shiftKey, event.ctrlKey, event.altKey, event.metaKey)
82 |
83 | // update this.keyCodes
84 | var keyCode = event.keyCode;
85 | this.keyCodes[keyCode] = pressed;
86 |
87 | // update this.modifiers
88 | this.modifiers['shift']= event.shiftKey;
89 | this.modifiers['ctrl'] = event.ctrlKey;
90 | this.modifiers['alt'] = event.altKey;
91 | this.modifiers['meta'] = event.metaKey;
92 | }
93 |
94 | /**
95 | * query keyboard state to know if a key is pressed of not
96 | *
97 | * @param {String} keyDesc the description of the key. format : modifiers+key e.g shift+A
98 | * @returns {Boolean} true if the key is pressed, false otherwise
99 | */
100 | THREEx.KeyboardState.prototype.pressed = function(keyDesc)
101 | {
102 | var keys = keyDesc.split("+");
103 | for(var i = 0; i < keys.length; i++){
104 | var key = keys[i];
105 | var pressed;
106 | if( THREEx.KeyboardState.MODIFIERS.indexOf( key ) !== -1 ){
107 | pressed = this.modifiers[key];
108 | }else if( Object.keys(THREEx.KeyboardState.ALIAS).indexOf( key ) != -1 ){
109 | pressed = this.keyCodes[ THREEx.KeyboardState.ALIAS[key] ];
110 | }else {
111 | pressed = this.keyCodes[key.toUpperCase().charCodeAt(0)]
112 | }
113 | if( !pressed) return false;
114 | };
115 | return true;
116 | }
117 |
--------------------------------------------------------------------------------
/js/THREEx.WindowResize.js:
--------------------------------------------------------------------------------
1 | // This THREEx helper makes it easy to handle window resize.
2 | // It will update renderer and camera when window is resized.
3 | //
4 | // # Usage
5 | //
6 | // **Step 1**: Start updating renderer and camera
7 | //
8 | // ```var windowResize = THREEx.WindowResize(aRenderer, aCamera)```
9 | //
10 | // **Step 2**: Start updating renderer and camera
11 | //
12 | // ```windowResize.stop()```
13 | // # Code
14 |
15 | //
16 |
17 | /** @namespace */
18 | var THREEx = THREEx || {};
19 |
20 | /**
21 | * Update renderer and camera when the window is resized
22 | *
23 | * @param {Object} renderer the renderer to update
24 | * @param {Object} Camera the camera to update
25 | */
26 | THREEx.WindowResize = function(renderer, camera){
27 | var callback = function(){
28 | // notify the renderer of the size change
29 | renderer.setSize( window.innerWidth, window.innerHeight );
30 | // update the camera
31 | camera.aspect = window.innerWidth / window.innerHeight;
32 | camera.updateProjectionMatrix();
33 | }
34 | // bind the resize event
35 | window.addEventListener('resize', callback, false);
36 | // return .stop() the function to stop watching window resize
37 | return {
38 | /**
39 | * Stop watching window resize
40 | */
41 | stop : function(){
42 | window.removeEventListener('resize', callback);
43 | }
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/js/index.js:
--------------------------------------------------------------------------------
1 | // 定义全局变量
2 | var container, scene, camera, renderer, controls;
3 | var keyboard = new THREEx.KeyboardState();
4 | var clock = new THREE.Clock;
5 |
6 | var movingCube;
7 | var collideMeshList = [];
8 | var cubes = [];
9 | var message = document.getElementById("message");
10 | var crash = false;
11 | var score = 0;
12 | var scoreText = document.getElementById("score");
13 | var id = 0;
14 | var crashId = " ";
15 | var lastCrashId = " ";
16 |
17 | init();
18 | animate();
19 |
20 | function init() {
21 | // Scene
22 | scene = new THREE.Scene();
23 | // Camera
24 | var screenWidth = window.innerWidth;
25 | var screenHeight = window.innerHeight;
26 | camera = new THREE.PerspectiveCamera(45, screenWidth / screenHeight, 1, 20000);
27 | camera.position.set(0, 170, 400);
28 |
29 | // Renderer
30 | if (Detector.webgl) {
31 | renderer = new THREE.WebGLRenderer({ antialias: true });
32 | } else {
33 | renderer = new THREE.CanvasRenderer();
34 | }
35 | renderer.setSize(screenWidth * 0.85, screenHeight * 0.85);
36 | container = document.getElementById("ThreeJS");
37 | container.appendChild(renderer.domElement);
38 |
39 | THREEx.WindowResize(renderer, camera);
40 | controls = new THREE.OrbitControls(camera, renderer.domElement);
41 |
42 | // 加入两条直线
43 | geometry = new THREE.Geometry();
44 | geometry.vertices.push(new THREE.Vector3(-250, -1, -3000));
45 | geometry.vertices.push(new THREE.Vector3(-300, -1, 200));
46 | material = new THREE.LineBasicMaterial({
47 | color: 0x6699FF, linewidth: 5, fog: true
48 | });
49 | var line1 = new THREE.Line(geometry, material);
50 | scene.add(line1);
51 | geometry = new THREE.Geometry();
52 | geometry.vertices.push(new THREE.Vector3(250, -1, -3000));
53 | geometry.vertices.push(new THREE.Vector3(300, -1, 200));
54 | var line2 = new THREE.Line(geometry, material);
55 | scene.add(line2);
56 |
57 |
58 | // 加入控制的cube
59 | var cubeGeometry = new THREE.CubeGeometry(50, 25, 60, 5, 5, 5);
60 | var wireMaterial = new THREE.MeshBasicMaterial({
61 | color: 0x00ff00,
62 | wireframe: true
63 | });
64 |
65 |
66 | movingCube = new THREE.Mesh(cubeGeometry, wireMaterial);
67 | // movingCube = new THREE.Mesh(cubeGeometry, material);
68 | // movingCube = new THREE.BoxHelper(movingCube);
69 | movingCube.position.set(0, 25, -20);
70 | scene.add(movingCube);
71 |
72 |
73 | }
74 |
75 | function animate() {
76 | requestAnimationFrame(animate);
77 | update();
78 | renderer.render(scene, camera);
79 |
80 | }
81 |
82 | function update() {
83 | var delta = clock.getDelta();
84 | var moveDistance = 200 * delta;
85 | //console.log(moveDistance);
86 | var rotateAngle = Math.PI / 2 * delta;
87 |
88 | // if (keyboard.pressed("A")) {
89 | // camera.rotation.z -= 0.2 * Math.PI / 180;
90 | // console.log("press A")
91 | // }
92 | // if (keyboard.pressed("D")) {
93 | // movingCube.rotation.y += rotateAngle;
94 | // }
95 |
96 | if (keyboard.pressed("left") || keyboard.pressed("A")) {
97 | if (movingCube.position.x > -270)
98 | movingCube.position.x -= moveDistance;
99 | if (camera.position.x > -150) {
100 | camera.position.x -= moveDistance * 0.6;
101 | if (camera.rotation.z > -5 * Math.PI / 180) {
102 | camera.rotation.z -= 0.2 * Math.PI / 180;
103 | }
104 | }
105 | }
106 | if (keyboard.pressed("right") || keyboard.pressed("D")) {
107 | if (movingCube.position.x < 270)
108 | movingCube.position.x += moveDistance;
109 | if (camera.position.x < 150) {
110 | camera.position.x += moveDistance * 0.6;
111 | if (camera.rotation.z < 5 * Math.PI / 180) {
112 | camera.rotation.z += 0.2 * Math.PI / 180;
113 | }
114 | }
115 | }
116 | if (keyboard.pressed("up") || keyboard.pressed("W")) {
117 | movingCube.position.z -= moveDistance;
118 | }
119 | if (keyboard.pressed("down") || keyboard.pressed("S")) {
120 | movingCube.position.z += moveDistance;
121 | }
122 |
123 | if (!(keyboard.pressed("left") || keyboard.pressed("right") ||
124 | keyboard.pressed("A") || keyboard.pressed("D"))) {
125 | delta = camera.rotation.z;
126 | camera.rotation.z -= delta / 10;
127 | }
128 |
129 |
130 | var originPoint = movingCube.position.clone();
131 |
132 | for (var vertexIndex = 0; vertexIndex < movingCube.geometry.vertices.length; vertexIndex++) {
133 | // 顶点原始坐标
134 | var localVertex = movingCube.geometry.vertices[vertexIndex].clone();
135 | // 顶点经过变换后的坐标
136 | var globalVertex = localVertex.applyMatrix4(movingCube.matrix);
137 | var directionVector = globalVertex.sub(movingCube.position);
138 |
139 | var ray = new THREE.Raycaster(originPoint, directionVector.clone().normalize());
140 | var collisionResults = ray.intersectObjects(collideMeshList);
141 | if (collisionResults.length > 0 && collisionResults[0].distance < directionVector.length()) {
142 | crash = true;
143 | crashId = collisionResults[0].object.name;
144 | break;
145 | }
146 | crash = false;
147 | }
148 |
149 | if (crash) {
150 | // message.innerText = "crash";
151 | movingCube.material.color.setHex(0x346386);
152 | console.log("Crash");
153 | if (crashId !== lastCrashId) {
154 | score -= 100;
155 | lastCrashId = crashId;
156 | }
157 |
158 | document.getElementById('explode_sound').play()
159 | } else {
160 | // message.innerText = "Safe";
161 | movingCube.material.color.setHex(0x00ff00);
162 | }
163 |
164 | if (Math.random() < 0.03 && cubes.length < 30) {
165 | makeRandomCube();
166 | }
167 |
168 | for (i = 0; i < cubes.length; i++) {
169 | if (cubes[i].position.z > camera.position.z) {
170 | scene.remove(cubes[i]);
171 | cubes.splice(i, 1);
172 | collideMeshList.splice(i, 1);
173 | } else {
174 | cubes[i].position.z += 10;
175 | }
176 | // renderer.render(scene, camera);
177 | }
178 |
179 | score += 0.1;
180 | scoreText.innerText = "Score:" + Math.floor(score);
181 |
182 | //controls.update();
183 | }
184 |
185 |
186 | // 返回一个介于min和max之间的随机数
187 | function getRandomArbitrary(min, max) {
188 | return Math.random() * (max - min) + min;
189 | }
190 |
191 | // 返回一个介于min和max之间的整型随机数
192 | function getRandomInt(min, max) {
193 | return Math.floor(Math.random() * (max - min + 1) + min);
194 | }
195 |
196 |
197 | function makeRandomCube() {
198 | var a = 1 * 50,
199 | b = getRandomInt(1, 3) * 50,
200 | c = 1 * 50;
201 | var geometry = new THREE.CubeGeometry(a, b, c);
202 | var material = new THREE.MeshBasicMaterial({
203 | color: Math.random() * 0xffffff,
204 | size: 3
205 | });
206 |
207 |
208 | var object = new THREE.Mesh(geometry, material);
209 | var box = new THREE.BoxHelper(object);
210 | // box.material.color.setHex(Math.random() * 0xffffff);
211 | box.material.color.setHex(0xff0000);
212 |
213 | box.position.x = getRandomArbitrary(-250, 250);
214 | box.position.y = 1 + b / 2;
215 | box.position.z = getRandomArbitrary(-800, -1200);
216 | cubes.push(box);
217 | box.name = "box_" + id;
218 | id++;
219 | collideMeshList.push(box);
220 |
221 | scene.add(box);
222 | }
--------------------------------------------------------------------------------
/sound/explode.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noiron/race-game-threejs/7af36f4494ed2919856eb7640ed1df6ae792764e/sound/explode.mp3
--------------------------------------------------------------------------------
/test/cube_collision.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Move a cube
6 |
7 |
8 |
9 |
10 |
11 |
12 |
22 |
23 |
24 |
25 |
26 |
27 |
182 |
183 |
--------------------------------------------------------------------------------
/test/move_cube.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Move a cube
6 |
7 |
8 |
9 |
10 |
11 |
12 |
22 |
23 |
24 |
25 |
115 |
116 |
--------------------------------------------------------------------------------