├── .gitignore
├── Makefile
├── README.md
├── TODO.md
├── examples
├── basic.html
├── demo.html
├── images
│ └── screenshot-threex-colliders-512x512.jpg
├── manual-debug.html
├── manual-object.html
└── vendor
│ └── three.js
│ ├── build
│ ├── three.js
│ └── three.min.js
│ └── examples
│ └── js
│ └── controls
│ └── OrbitControls.js
├── package.require.js
├── threex.collider.js
├── threex.colliderhelper.js
└── threex.collidersystem.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .betterjs
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # makefile to automatize simple operations
2 |
3 | server:
4 | python -m SimpleHTTPServer
5 |
6 | deploy:
7 | # assume there is something to commit
8 | # use "git diff --exit-code HEAD" to know if there is something to commit
9 | # so two lines: one if no commit, one if something to commit
10 | git commit -a -m "New deploy" && git push -f origin HEAD:gh-pages && git reset HEAD~
11 |
12 |
13 | ###################################################
14 | # Support betterjs cache dir - http://betterjs.org
15 | buildBetterjs:
16 | jsdoc2betterjs -s -p -d .betterjs *.js
17 |
18 | watchBetterjs: buildBetterjs
19 | # fswatch is available at https://github.com/emcrisostomo/fswatch
20 | fswatch *.js | xargs -n1 jsdoc2betterjs -s -p -d .betterjs
21 |
22 | cleanBetterjs:
23 | rm -rf .betterjs
24 |
25 | serverBetterjs: buildBetterjs
26 | jsdoc2betterjs servecachedir .betterjs
27 |
28 | ###################################################
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 3threex.colliders
2 | =============
3 |
4 | threex.colliders is a
5 | [threex game extension for three.js](http://www.threejsgames.com/extensions/).
6 | It provides an collider system. Each ```THREE.Object3D``` may be attached to a ```THREEx.Collider``` for AABB. Sphere will be added when time allow.
7 | Then you add those in a ```THREEx.ColliderSystem``` and ```.computeAndNotify()``` all the collisions at this time.
8 | When 2 colliders start colliding with each other, the event 'contactEnter' is sent to each listener. When those colliders keep colliding, the event 'contactStay' is sent. When those colliders are no more colliding, the event sent is 'contactExit'.
9 |
10 | Show Don't Tell
11 | ===============
12 | * [examples/basic.html](http://jeromeetienne.github.io/threex.colliders/examples/basic.html)
13 | \[[view source](https://github.com/jeromeetienne/threex.colliders/blob/master/examples/basic.html)\] :
14 | It shows a basic usage of this extension.
15 | * [examples/demo.html](http://jeromeetienne.github.io/threex.colliders/examples/demo.html)
16 | \[[view source](https://github.com/jeromeetienne/threex.colliders/blob/master/examples/demo.html)\] :
17 | It shows all the cases of collisions.
18 |
19 | A Screenshot
20 | ============
21 | [](http://jeromeetienne.github.io/threex.colliders/examples/basic.html)
22 |
23 | How To Install It
24 | =================
25 |
26 | You can install it via script tag
27 |
28 | ```html
29 |
30 | ```
31 |
32 | Or you can install with [bower](http://bower.io/), as you wish.
33 |
34 | ```bash
35 | bower install threex.colliders
36 | ```
37 |
38 | How To Use It
39 | =============
40 |
41 | First you need to create a ```THREEx.ColliderSystem```. It gonna handle the whole thing for you
42 |
43 | ```
44 | var colliderSystem = new THREEx.ColliderSystem()
45 | ```
46 |
47 | Every time you wish to compute collision and notify associated events among colliders, just do the following
48 |
49 | ```
50 | colliderSystem.computeAndNotify(colliders)
51 | ````
52 |
53 | ### How To Add Box3 Collider ? (or call it [AABB](http://en.wikipedia.org/wiki/Axis-aligned_bounding_box#Axis-aligned_minimum_bounding_box))
54 |
55 | You need a ```THREE.Box3``` to define the shape of your collider.
56 | Say you take default boundingBox from your object geometry, Or another it is all up to you.
57 |
58 | ```
59 | object3d.geometry.computeBoundingBox()
60 | var box3 = object3d.geometry.boundingBox.clone()
61 | ```
62 |
63 | Now with your ```THREEx.Box3``` you create your controller
64 |
65 | ```
66 | var collider = new THREEx.ColliderBox3(object3d, box3)
67 | ```
68 |
69 | ### Helpers for easier creation
70 |
71 | If you dont want to handle all those cases yourself, i create a small helper
72 |
73 | ```
74 | var collider = THREEx.Collider.createFromObject3d(object3d)
75 | ```
76 |
77 | ### How to receive event from colliders ?
78 |
79 | There are 3 kind of events
80 | - **contactEnter(otherCollider)** which is triggered when an object start colliding with another
81 | - **contactExit(otherCollider)** which is notified when the object is no more colliding with another
82 | - **contactStay(otherCollider)** which is notified when the object is still colliding with another
83 | - **contactRemoved(otherColliderId)** which is notified when the other collider has been removed
84 |
85 | To start listening on a event, just do
86 |
87 | ```
88 | var onCollideEnter = collider.addEventListener('contactEnter', function(otherCollider){
89 | console.log('contactEnter with', otherCollider.id)
90 | })
91 | ```
92 |
93 | To remove the event listener, do the following
94 |
95 | ```
96 | collider.removeEventListener(onColliderEnter)
97 | ```
98 |
99 | TODO
100 | ====
101 | * a THREEx.ColliderGroup. it is a group of other colliders shape
102 | * a collider for sphere, oriented bounding box etc...
103 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | * to a nice visual test demo
2 |
3 | ### File hierachy
4 | threex.colliderSystem.js
5 | threex.collider.js
6 |
7 |
8 | ### What need to be tested
9 | * creation during contact
10 | * removal during contact
11 | * during non contact
12 |
13 |
14 | - do a static object in the middle
15 | - a moving object from left to right
16 | - by tunning where the moving object start and finish
17 | - you can recreate all the case
18 | - there is like 4 of theml
--------------------------------------------------------------------------------
/examples/basic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
201 |
--------------------------------------------------------------------------------
/examples/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
19 |
20 | - Received event change the object's bounding box:
21 | -
22 | - Event initial state : set color to green
23 | - Event contactEnter : set color to red
24 | - Event contactExit : set color to green
25 | - Event contactRemoved : set color to blue
26 | - Event contactStay : increase line width
27 |
28 |
251 |
--------------------------------------------------------------------------------
/examples/images/screenshot-threex-colliders-512x512.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeromeetienne/threex.colliders/725591b2769916bf96a2e5663191a8aba27e1a93/examples/images/screenshot-threex-colliders-512x512.jpg
--------------------------------------------------------------------------------
/examples/manual-debug.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
19 |
20 | - Received event change the object's bounding box:
21 | -
22 | - Event initial state : set color to green
23 | - Event contactEnter : set color to red
24 | - Event contactExit : set color to green
25 | - Event contactRemoved : set color to blue
26 | - Event contactStay : increase line width
27 |
28 |
258 |
--------------------------------------------------------------------------------
/examples/manual-object.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
246 |
--------------------------------------------------------------------------------
/examples/vendor/three.js/examples/js/controls/OrbitControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author qiao / https://github.com/qiao
3 | * @author mrdoob / http://mrdoob.com
4 | * @author alteredq / http://alteredqualia.com/
5 | * @author WestLangley / http://github.com/WestLangley
6 | * @author erich666 / http://erichaines.com
7 | */
8 | /*global THREE, console */
9 |
10 | // This set of controls performs orbiting, dollying (zooming), and panning. It maintains
11 | // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is
12 | // supported.
13 | //
14 | // Orbit - left mouse / touch: one finger move
15 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
16 | // Pan - right mouse, or arrow keys / touch: three finter swipe
17 | //
18 | // This is a drop-in replacement for (most) TrackballControls used in examples.
19 | // That is, include this js file and wherever you see:
20 | // controls = new THREE.TrackballControls( camera );
21 | // controls.target.z = 150;
22 | // Simple substitute "OrbitControls" and the control should work as-is.
23 |
24 | THREE.OrbitControls = function ( object, domElement ) {
25 |
26 | this.object = object;
27 | this.domElement = ( domElement !== undefined ) ? domElement : document;
28 |
29 | // API
30 |
31 | // Set to false to disable this control
32 | this.enabled = true;
33 |
34 | // "target" sets the location of focus, where the control orbits around
35 | // and where it pans with respect to.
36 | this.target = new THREE.Vector3();
37 |
38 | // center is old, deprecated; use "target" instead
39 | this.center = this.target;
40 |
41 | // This option actually enables dollying in and out; left as "zoom" for
42 | // backwards compatibility
43 | this.noZoom = false;
44 | this.zoomSpeed = 1.0;
45 |
46 | // Limits to how far you can dolly in and out
47 | this.minDistance = 0;
48 | this.maxDistance = Infinity;
49 |
50 | // Set to true to disable this control
51 | this.noRotate = false;
52 | this.rotateSpeed = 1.0;
53 |
54 | // Set to true to disable this control
55 | this.noPan = false;
56 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push
57 |
58 | // Set to true to automatically rotate around the target
59 | this.autoRotate = false;
60 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
61 |
62 | // How far you can orbit vertically, upper and lower limits.
63 | // Range is 0 to Math.PI radians.
64 | this.minPolarAngle = 0; // radians
65 | this.maxPolarAngle = Math.PI; // radians
66 |
67 | // Set to true to disable use of the keys
68 | this.noKeys = false;
69 |
70 | // The four arrow keys
71 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
72 |
73 | ////////////
74 | // internals
75 |
76 | var scope = this;
77 |
78 | var EPS = 0.000001;
79 |
80 | var rotateStart = new THREE.Vector2();
81 | var rotateEnd = new THREE.Vector2();
82 | var rotateDelta = new THREE.Vector2();
83 |
84 | var panStart = new THREE.Vector2();
85 | var panEnd = new THREE.Vector2();
86 | var panDelta = new THREE.Vector2();
87 | var panOffset = new THREE.Vector3();
88 |
89 | var offset = new THREE.Vector3();
90 |
91 | var dollyStart = new THREE.Vector2();
92 | var dollyEnd = new THREE.Vector2();
93 | var dollyDelta = new THREE.Vector2();
94 |
95 | var phiDelta = 0;
96 | var thetaDelta = 0;
97 | var scale = 1;
98 | var pan = new THREE.Vector3();
99 |
100 | var lastPosition = new THREE.Vector3();
101 |
102 | var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
103 |
104 | var state = STATE.NONE;
105 |
106 | // for reset
107 |
108 | this.target0 = this.target.clone();
109 | this.position0 = this.object.position.clone();
110 |
111 | // so camera.up is the orbit axis
112 |
113 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
114 | var quatInverse = quat.clone().inverse();
115 |
116 | // events
117 |
118 | var changeEvent = { type: 'change' };
119 | var startEvent = { type: 'start'};
120 | var endEvent = { type: 'end'};
121 |
122 | this.rotateLeft = function ( angle ) {
123 |
124 | if ( angle === undefined ) {
125 |
126 | angle = getAutoRotationAngle();
127 |
128 | }
129 |
130 | thetaDelta -= angle;
131 |
132 | };
133 |
134 | this.rotateUp = function ( angle ) {
135 |
136 | if ( angle === undefined ) {
137 |
138 | angle = getAutoRotationAngle();
139 |
140 | }
141 |
142 | phiDelta -= angle;
143 |
144 | };
145 |
146 | // pass in distance in world space to move left
147 | this.panLeft = function ( distance ) {
148 |
149 | var te = this.object.matrix.elements;
150 |
151 | // get X column of matrix
152 | panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] );
153 | panOffset.multiplyScalar( - distance );
154 |
155 | pan.add( panOffset );
156 |
157 | };
158 |
159 | // pass in distance in world space to move up
160 | this.panUp = function ( distance ) {
161 |
162 | var te = this.object.matrix.elements;
163 |
164 | // get Y column of matrix
165 | panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] );
166 | panOffset.multiplyScalar( distance );
167 |
168 | pan.add( panOffset );
169 |
170 | };
171 |
172 | // pass in x,y of change desired in pixel space,
173 | // right and down are positive
174 | this.pan = function ( deltaX, deltaY ) {
175 |
176 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
177 |
178 | if ( scope.object.fov !== undefined ) {
179 |
180 | // perspective
181 | var position = scope.object.position;
182 | var offset = position.clone().sub( scope.target );
183 | var targetDistance = offset.length();
184 |
185 | // half of the fov is center to top of screen
186 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
187 |
188 | // we actually don't use screenWidth, since perspective camera is fixed to screen height
189 | scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight );
190 | scope.panUp( 2 * deltaY * targetDistance / element.clientHeight );
191 |
192 | } else if ( scope.object.top !== undefined ) {
193 |
194 | // orthographic
195 | scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth );
196 | scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight );
197 |
198 | } else {
199 |
200 | // camera neither orthographic or perspective
201 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
202 |
203 | }
204 |
205 | };
206 |
207 | this.dollyIn = function ( dollyScale ) {
208 |
209 | if ( dollyScale === undefined ) {
210 |
211 | dollyScale = getZoomScale();
212 |
213 | }
214 |
215 | scale /= dollyScale;
216 |
217 | };
218 |
219 | this.dollyOut = function ( dollyScale ) {
220 |
221 | if ( dollyScale === undefined ) {
222 |
223 | dollyScale = getZoomScale();
224 |
225 | }
226 |
227 | scale *= dollyScale;
228 |
229 | };
230 |
231 | this.update = function () {
232 |
233 | var position = this.object.position;
234 |
235 | offset.copy( position ).sub( this.target );
236 |
237 | // rotate offset to "y-axis-is-up" space
238 | offset.applyQuaternion( quat );
239 |
240 | // angle from z-axis around y-axis
241 |
242 | var theta = Math.atan2( offset.x, offset.z );
243 |
244 | // angle from y-axis
245 |
246 | var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
247 |
248 | if ( this.autoRotate ) {
249 |
250 | this.rotateLeft( getAutoRotationAngle() );
251 |
252 | }
253 |
254 | theta += thetaDelta;
255 | phi += phiDelta;
256 |
257 | // restrict phi to be between desired limits
258 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
259 |
260 | // restrict phi to be betwee EPS and PI-EPS
261 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
262 |
263 | var radius = offset.length() * scale;
264 |
265 | // restrict radius to be between desired limits
266 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
267 |
268 | // move target to panned location
269 | this.target.add( pan );
270 |
271 | offset.x = radius * Math.sin( phi ) * Math.sin( theta );
272 | offset.y = radius * Math.cos( phi );
273 | offset.z = radius * Math.sin( phi ) * Math.cos( theta );
274 |
275 | // rotate offset back to "camera-up-vector-is-up" space
276 | offset.applyQuaternion( quatInverse );
277 |
278 | position.copy( this.target ).add( offset );
279 |
280 | this.object.lookAt( this.target );
281 |
282 | thetaDelta = 0;
283 | phiDelta = 0;
284 | scale = 1;
285 | pan.set( 0, 0, 0 );
286 |
287 | if ( lastPosition.distanceToSquared( this.object.position ) > EPS ) {
288 |
289 | this.dispatchEvent( changeEvent );
290 |
291 | lastPosition.copy( this.object.position );
292 |
293 | }
294 |
295 | };
296 |
297 |
298 | this.reset = function () {
299 |
300 | state = STATE.NONE;
301 |
302 | this.target.copy( this.target0 );
303 | this.object.position.copy( this.position0 );
304 |
305 | this.update();
306 |
307 | };
308 |
309 | function getAutoRotationAngle() {
310 |
311 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
312 |
313 | }
314 |
315 | function getZoomScale() {
316 |
317 | return Math.pow( 0.95, scope.zoomSpeed );
318 |
319 | }
320 |
321 | function onMouseDown( event ) {
322 |
323 | if ( scope.enabled === false ) return;
324 | event.preventDefault();
325 |
326 | if ( event.button === 0 ) {
327 | if ( scope.noRotate === true ) return;
328 |
329 | state = STATE.ROTATE;
330 |
331 | rotateStart.set( event.clientX, event.clientY );
332 |
333 | } else if ( event.button === 1 ) {
334 | if ( scope.noZoom === true ) return;
335 |
336 | state = STATE.DOLLY;
337 |
338 | dollyStart.set( event.clientX, event.clientY );
339 |
340 | } else if ( event.button === 2 ) {
341 | if ( scope.noPan === true ) return;
342 |
343 | state = STATE.PAN;
344 |
345 | panStart.set( event.clientX, event.clientY );
346 |
347 | }
348 |
349 | scope.domElement.addEventListener( 'mousemove', onMouseMove, false );
350 | scope.domElement.addEventListener( 'mouseup', onMouseUp, false );
351 | scope.dispatchEvent( startEvent );
352 |
353 | }
354 |
355 | function onMouseMove( event ) {
356 |
357 | if ( scope.enabled === false ) return;
358 |
359 | event.preventDefault();
360 |
361 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
362 |
363 | if ( state === STATE.ROTATE ) {
364 |
365 | if ( scope.noRotate === true ) return;
366 |
367 | rotateEnd.set( event.clientX, event.clientY );
368 | rotateDelta.subVectors( rotateEnd, rotateStart );
369 |
370 | // rotating across whole screen goes 360 degrees around
371 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
372 |
373 | // rotating up and down along whole screen attempts to go 360, but limited to 180
374 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
375 |
376 | rotateStart.copy( rotateEnd );
377 |
378 | } else if ( state === STATE.DOLLY ) {
379 |
380 | if ( scope.noZoom === true ) return;
381 |
382 | dollyEnd.set( event.clientX, event.clientY );
383 | dollyDelta.subVectors( dollyEnd, dollyStart );
384 |
385 | if ( dollyDelta.y > 0 ) {
386 |
387 | scope.dollyIn();
388 |
389 | } else {
390 |
391 | scope.dollyOut();
392 |
393 | }
394 |
395 | dollyStart.copy( dollyEnd );
396 |
397 | } else if ( state === STATE.PAN ) {
398 |
399 | if ( scope.noPan === true ) return;
400 |
401 | panEnd.set( event.clientX, event.clientY );
402 | panDelta.subVectors( panEnd, panStart );
403 |
404 | scope.pan( panDelta.x, panDelta.y );
405 |
406 | panStart.copy( panEnd );
407 |
408 | }
409 |
410 | scope.update();
411 |
412 | }
413 |
414 | function onMouseUp( /* event */ ) {
415 |
416 | if ( scope.enabled === false ) return;
417 |
418 | scope.domElement.removeEventListener( 'mousemove', onMouseMove, false );
419 | scope.domElement.removeEventListener( 'mouseup', onMouseUp, false );
420 | scope.dispatchEvent( endEvent );
421 | state = STATE.NONE;
422 |
423 | }
424 |
425 | function onMouseWheel( event ) {
426 |
427 | if ( scope.enabled === false || scope.noZoom === true ) return;
428 |
429 | event.preventDefault();
430 | event.stopPropagation();
431 |
432 | var delta = 0;
433 |
434 | if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9
435 |
436 | delta = event.wheelDelta;
437 |
438 | } else if ( event.detail !== undefined ) { // Firefox
439 |
440 | delta = - event.detail;
441 |
442 | }
443 |
444 | if ( delta > 0 ) {
445 |
446 | scope.dollyOut();
447 |
448 | } else {
449 |
450 | scope.dollyIn();
451 |
452 | }
453 |
454 | scope.update();
455 | scope.dispatchEvent( startEvent );
456 | scope.dispatchEvent( endEvent );
457 |
458 | }
459 |
460 | function onKeyDown( event ) {
461 |
462 | if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return;
463 |
464 | switch ( event.keyCode ) {
465 |
466 | case scope.keys.UP:
467 | scope.pan( 0, scope.keyPanSpeed );
468 | scope.update();
469 | break;
470 |
471 | case scope.keys.BOTTOM:
472 | scope.pan( 0, - scope.keyPanSpeed );
473 | scope.update();
474 | break;
475 |
476 | case scope.keys.LEFT:
477 | scope.pan( scope.keyPanSpeed, 0 );
478 | scope.update();
479 | break;
480 |
481 | case scope.keys.RIGHT:
482 | scope.pan( - scope.keyPanSpeed, 0 );
483 | scope.update();
484 | break;
485 |
486 | }
487 |
488 | }
489 |
490 | function touchstart( event ) {
491 |
492 | if ( scope.enabled === false ) return;
493 |
494 | switch ( event.touches.length ) {
495 |
496 | case 1: // one-fingered touch: rotate
497 |
498 | if ( scope.noRotate === true ) return;
499 |
500 | state = STATE.TOUCH_ROTATE;
501 |
502 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
503 | break;
504 |
505 | case 2: // two-fingered touch: dolly
506 |
507 | if ( scope.noZoom === true ) return;
508 |
509 | state = STATE.TOUCH_DOLLY;
510 |
511 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
512 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
513 | var distance = Math.sqrt( dx * dx + dy * dy );
514 | dollyStart.set( 0, distance );
515 | break;
516 |
517 | case 3: // three-fingered touch: pan
518 |
519 | if ( scope.noPan === true ) return;
520 |
521 | state = STATE.TOUCH_PAN;
522 |
523 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
524 | break;
525 |
526 | default:
527 |
528 | state = STATE.NONE;
529 |
530 | }
531 |
532 | scope.dispatchEvent( startEvent );
533 |
534 | }
535 |
536 | function touchmove( event ) {
537 |
538 | if ( scope.enabled === false ) return;
539 |
540 | event.preventDefault();
541 | event.stopPropagation();
542 |
543 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
544 |
545 | switch ( event.touches.length ) {
546 |
547 | case 1: // one-fingered touch: rotate
548 |
549 | if ( scope.noRotate === true ) return;
550 | if ( state !== STATE.TOUCH_ROTATE ) return;
551 |
552 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
553 | rotateDelta.subVectors( rotateEnd, rotateStart );
554 |
555 | // rotating across whole screen goes 360 degrees around
556 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
557 | // rotating up and down along whole screen attempts to go 360, but limited to 180
558 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
559 |
560 | rotateStart.copy( rotateEnd );
561 |
562 | scope.update();
563 | break;
564 |
565 | case 2: // two-fingered touch: dolly
566 |
567 | if ( scope.noZoom === true ) return;
568 | if ( state !== STATE.TOUCH_DOLLY ) return;
569 |
570 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
571 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
572 | var distance = Math.sqrt( dx * dx + dy * dy );
573 |
574 | dollyEnd.set( 0, distance );
575 | dollyDelta.subVectors( dollyEnd, dollyStart );
576 |
577 | if ( dollyDelta.y > 0 ) {
578 |
579 | scope.dollyOut();
580 |
581 | } else {
582 |
583 | scope.dollyIn();
584 |
585 | }
586 |
587 | dollyStart.copy( dollyEnd );
588 |
589 | scope.update();
590 | break;
591 |
592 | case 3: // three-fingered touch: pan
593 |
594 | if ( scope.noPan === true ) return;
595 | if ( state !== STATE.TOUCH_PAN ) return;
596 |
597 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
598 | panDelta.subVectors( panEnd, panStart );
599 |
600 | scope.pan( panDelta.x, panDelta.y );
601 |
602 | panStart.copy( panEnd );
603 |
604 | scope.update();
605 | break;
606 |
607 | default:
608 |
609 | state = STATE.NONE;
610 |
611 | }
612 |
613 | }
614 |
615 | function touchend( /* event */ ) {
616 |
617 | if ( scope.enabled === false ) return;
618 |
619 | scope.dispatchEvent( endEvent );
620 | state = STATE.NONE;
621 |
622 | }
623 |
624 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
625 | this.domElement.addEventListener( 'mousedown', onMouseDown, false );
626 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
627 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
628 |
629 | this.domElement.addEventListener( 'touchstart', touchstart, false );
630 | this.domElement.addEventListener( 'touchend', touchend, false );
631 | this.domElement.addEventListener( 'touchmove', touchmove, false );
632 |
633 | window.addEventListener( 'keydown', onKeyDown, false );
634 |
635 | // force an update at start
636 | this.update();
637 |
638 | };
639 |
640 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
--------------------------------------------------------------------------------
/package.require.js:
--------------------------------------------------------------------------------
1 | define( [ './threex.collider.js'
2 | , './threex.colliderhelper.js'
3 | , './threex.collidersystem.js'
4 | ], function(module){
5 | });
--------------------------------------------------------------------------------
/threex.collider.js:
--------------------------------------------------------------------------------
1 | var THREEx = THREEx || {}
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // THREEx.Collider
5 | //////////////////////////////////////////////////////////////////////////////////
6 |
7 | /**
8 | * collider base class
9 | *
10 | * @param {THREE.Object3D} object3d - the object
11 | */
12 | THREEx.Collider = function(object3d){
13 | this.id = THREEx.Collider.idCount++
14 | this.object3d = object3d
15 | this.userData = {}
16 | }
17 |
18 |
19 | THREEx.Collider.idCount = 0;
20 |
21 |
22 | /**
23 | * microevents.js - https://github.com/jeromeetienne/microevent.js
24 | *
25 | * @param {Object} destObj - the destination object
26 | */
27 | THREEx.Collider.MicroeventMixin = function(destObj){
28 | destObj.addEventListener = function(event, fct){
29 | if(this._events === undefined) this._events = {};
30 | this._events[event] = this._events[event] || [];
31 | this._events[event].push(fct);
32 | return fct;
33 | };
34 | destObj.removeEventListener = function(event, fct){
35 | if(this._events === undefined) this._events = {};
36 | if( event in this._events === false ) return;
37 | this._events[event].splice(this._events[event].indexOf(fct), 1);
38 | };
39 | destObj.dispatchEvent = function(event /* , args... */){
40 | if(this._events === undefined) this._events = {};
41 | if( this._events[event] === undefined ) return;
42 | var tmpArray = this._events[event].slice();
43 | for(var i = 0; i < tmpArray.length; i++){
44 | var result = tmpArray[i].apply(this, Array.prototype.slice.call(arguments, 1))
45 | if( result !== undefined ) return result;
46 | }
47 | return undefined;
48 | };
49 | };
50 |
51 | THREEx.Collider.MicroeventMixin(THREEx.Collider.prototype)
52 |
53 | //////////////////////////////////////////////////////////////////////////////////
54 | // Comment //
55 | //////////////////////////////////////////////////////////////////////////////////
56 |
57 | /**
58 | * Easy create a collider from a object3d
59 | *
60 | * @param {THREE.Object3D} object3d - the object
61 | * @param {String=} hint - hint on how to create it
62 | * @return {THREE.Collider} - the create collider
63 | */
64 | THREEx.Collider.createFromObject3d = function(object3d, hint){
65 | hint = hint || 'default'
66 |
67 | if( hint === 'accurate' ){
68 | var box3 = new THREE.Box3()
69 | var collider = new THREEx.ColliderBox3(object3d, box3, 'vertices')
70 | }else if( hint === 'fast' || hint === 'default' ){
71 | // set it from object3d
72 | var box3 = new THREE.Box3()
73 | box3.setFromObject( object3d );
74 |
75 | // cancel the effect of object3d.position
76 | var center = box3.center()
77 | center.sub(object3d.position)
78 | // cancel the effect of object3d.scale
79 | var size = box3.size()
80 | size.divide(object3d.scale)
81 | // update box3
82 | box3.setFromCenterAndSize(center, size)
83 | // actually create the collider
84 | var collider = new THREEx.ColliderBox3(object3d, box3, 'positionScaleOnly')
85 | }else console.assert(false)
86 |
87 | return collider
88 | }
89 |
90 |
91 | //////////////////////////////////////////////////////////////////////////////////
92 | //////////////////////////////////////////////////////////////////////////////////
93 | //////////////////////////////////////////////////////////////////////////////////
94 | //////////////////////////////////////////////////////////////////////////////////
95 | //////////////////////////////////////////////////////////////////////////////////
96 | //////////////////////////////////////////////////////////////////////////////////
97 | //////////////////////////////////////////////////////////////////////////////////
98 | //////////////////////////////////////////////////////////////////////////////////
99 | // THREEx.ColliderBox3
100 | //////////////////////////////////////////////////////////////////////////////////
101 | //////////////////////////////////////////////////////////////////////////////////
102 | //////////////////////////////////////////////////////////////////////////////////
103 | //////////////////////////////////////////////////////////////////////////////////
104 | //////////////////////////////////////////////////////////////////////////////////
105 | //////////////////////////////////////////////////////////////////////////////////
106 | //////////////////////////////////////////////////////////////////////////////////
107 | //////////////////////////////////////////////////////////////////////////////////
108 |
109 | THREEx.ColliderBox3 = function(object3d, shape, updateMode){
110 | console.assert(shape instanceof THREE.Box3 )
111 |
112 | THREEx.Collider.call( this, object3d )
113 |
114 | this.shape = shape
115 | this.updatedBox3= shape.clone()
116 |
117 | this.updateMode = updateMode || 'vertices'
118 | }
119 |
120 | THREEx.ColliderBox3.prototype = Object.create( THREEx.Collider.prototype );
121 |
122 | //////////////////////////////////////////////////////////////////////////////////
123 | // .update
124 | //////////////////////////////////////////////////////////////////////////////////
125 |
126 | /**
127 | * update this Collider
128 | *
129 | * @param {String=} updateMode - the update mode to use. default to this.updateMode
130 | */
131 | THREEx.ColliderBox3.prototype.update = function(updateMode){
132 | // default arguments
133 | updateMode = updateMode || this.updateMode
134 | var newBox3 = this.shape.clone()
135 | // init newBox3 based on updateMode
136 | if( updateMode === 'vertices' ){
137 | // full recomputation of the box3 for each vertice, of geometry, of each child
138 | // - it is quite expensive
139 | newBox3.setFromObject(this.object3d)
140 | }else if( updateMode === 'transform' ){
141 | // TODO should i do that .updateMatrixWorld ?
142 | this.object3d.updateMatrixWorld( true );
143 | newBox3.applyMatrix4(this.object3d.matrixWorld)
144 | }else if( updateMode === 'none' ){
145 | // may be useful if the object3d never moves
146 | // - thus you do a collider.update('vertices') on init and collide.updateMode = 'none'
147 | }else if( updateMode === 'positionScaleOnly' ){
148 | // get matrix in world coordinate
149 | this.object3d.updateMatrixWorld( true )
150 | var matrix = this.object3d.matrixWorld
151 | // update scale
152 | var scale = new THREE.Vector3().setFromMatrixScale( matrix );
153 | newBox3.min.multiply(scale)
154 | newBox3.max.multiply(scale)
155 | // update position
156 | var position = new THREE.Vector3().setFromMatrixPosition( matrix );
157 | newBox3.translate(position)
158 | }else console.assert(false)
159 |
160 | // save this.updatedBox3
161 | this.updatedBox3 = newBox3
162 | }
163 |
164 | //////////////////////////////////////////////////////////////////////////////////
165 | // .collideWith
166 | //////////////////////////////////////////////////////////////////////////////////
167 |
168 | /**
169 | * test if this collider collides with the otherCollider
170 | *
171 | * @param {THREEx.Collider} otherCollider - the other collider
172 | * @return {Boolean} - true if they are in contact, false otherwise
173 | */
174 | THREEx.ColliderBox3.prototype.collideWith = function(otherCollider){
175 | if( otherCollider instanceof THREEx.ColliderBox3 ){
176 | return this.collideWithBox3(otherCollider)
177 | }else console.assert(false)
178 | }
179 |
180 | /**
181 | * test if this collider collides with the otherCollider
182 | *
183 | * @param {THREEx.ColliderBox3} otherCollider - the other collider
184 | * @return {Boolean} - true if they are in contact, false otherwise
185 | */
186 | THREEx.ColliderBox3.prototype.collideWithBox3 = function(otherCollider){
187 | console.assert( otherCollider instanceof THREEx.ColliderBox3 )
188 |
189 | var doCollide = this.updatedBox3.isIntersectionBox(otherCollider.updatedBox3)
190 |
191 | return doCollide ? true : false
192 | }
193 |
--------------------------------------------------------------------------------
/threex.colliderhelper.js:
--------------------------------------------------------------------------------
1 | var THREEx = THREEx || {}
2 |
3 | /**
4 | * An helper object to help visualize your colilder
5 | *
6 | * @param {THREE.Collider} collider - the collider to monitor
7 | */
8 | THREEx.ColliderHelper = function( collider ){
9 | if( collider instanceof THREEx.ColliderBox3 ){
10 | return new THREEx.ColliderBox3Helper(collider)
11 | }else console.assert(false)
12 | }
13 |
14 |
15 |
16 | //////////////////////////////////////////////////////////////////////////////////
17 | // THREEx.ColliderBox3Helper
18 | //////////////////////////////////////////////////////////////////////////////////
19 |
20 | /**
21 | * An helper object to help visualize your colilder
22 | *
23 | * @param {THREE.Collider} collider - the collider to monitor
24 | */
25 | THREEx.ColliderBox3Helper = function( collider ){
26 | // check argument
27 | console.assert( collider instanceof THREEx.ColliderBox3 )
28 | // setup geometry/material
29 | var geometry = new THREE.BoxGeometry(1,1,1)
30 | var material = new THREE.MeshBasicMaterial({
31 | wireframe : true
32 | })
33 |
34 | // create the mesh
35 | THREE.Mesh.call(this, geometry, material)
36 |
37 | /**
38 | * make the helper match the collider shape. used the .updatedBox3
39 | */
40 | this.update = function(){
41 | var box3 = collider.updatedBox3
42 | this.scale.copy( box3.size() )
43 | this.position.copy( box3.center() )
44 | }
45 |
46 | /**
47 | * free webgl memory
48 | */
49 | this.dispose = function(){
50 | geometry.dispose()
51 | material.dispose()
52 | }
53 | }
54 |
55 | THREEx.ColliderBox3Helper.prototype = Object.create( THREE.Mesh.prototype );
56 |
--------------------------------------------------------------------------------
/threex.collidersystem.js:
--------------------------------------------------------------------------------
1 | var THREEx = THREEx || {}
2 |
3 | //////////////////////////////////////////////////////////////////////////////////
4 | // Comment //
5 | //////////////////////////////////////////////////////////////////////////////////
6 | THREEx.ColliderSystem = function(){
7 | //////////////////////////////////////////////////////////////////////////////////
8 | // Comment //
9 | //////////////////////////////////////////////////////////////////////////////////
10 |
11 | /**
12 | * compute collisions states and notify events appropriatly
13 | * @param {THREEx.Collider[]} colliders - array of colliders
14 | */
15 | this.computeAndNotify = function(colliders){
16 | // purge states from the colliders which are no more there
17 | purgeState(colliders)
18 | // compute and notify contacts between colliders
19 | notifyContacts(colliders)
20 | }
21 |
22 | //////////////////////////////////////////////////////////////////////////////////
23 | // handle colliding states
24 | //////////////////////////////////////////////////////////////////////////////////
25 | var states = {}
26 | this._states = states
27 | function getStateLabel(collider1, collider2){
28 | if( collider1.id < collider2.id )
29 | var stateLabel = collider1.id + '-' + collider2.id
30 | else
31 | var stateLabel = collider2.id + '-' + collider1.id
32 | return stateLabel
33 |
34 | }
35 |
36 |
37 |
38 | //////////////////////////////////////////////////////////////////////////////////
39 | // Comment //
40 | //////////////////////////////////////////////////////////////////////////////////
41 |
42 | /**
43 | * purge states
44 | * - go thru all states
45 | * - any states which isnt both in colliders, remove it
46 | * - if only one of both colliders is still present, notify contactRemoved(contactId)
47 | *
48 | * @param {THREE.Collider[]} colliders - base to purge state
49 | */
50 | function purgeState(colliders){
51 | // remove pending states for removed collider
52 | Object.keys(states).forEach(function(stateLabel){
53 | // get leftColliderId
54 | var leftColliderId = parseInt(stateLabel.match(/^([0-9]+)-/)[1])
55 | var rightColliderId = parseInt(stateLabel.match(/-([0-9]+)$/)[1])
56 |
57 | // get colliders based on their id
58 | var leftCollider = findById(colliders, leftColliderId)
59 | var rightCollider = findById(colliders, rightColliderId)
60 |
61 | // handle differently depending on their presence
62 | if( leftCollider !== null && rightCollider !== null ){
63 | // both still present, do nothing
64 | return
65 | }else if( leftCollider !== null && rightCollider === null ){
66 | // right collider got removed
67 | leftCollider.dispatchEvent('contactRemoved', rightColliderId)
68 | }else if( leftCollider === null && rightCollider !== null ){
69 | // left collider got removed
70 | rightCollider.dispatchEvent('contactRemoved', leftColliderId)
71 | }else{
72 | // both got removed
73 | }
74 |
75 | // update states
76 | delete states[stateLabel]
77 | })
78 |
79 | return
80 |
81 | function findById(colliders, colliderId){
82 | for( var i = 0; i < colliders.length; i++ ){
83 | if( colliders[i].id === colliderId ){
84 | return colliders[i]
85 | }
86 | }
87 | return null
88 | }
89 | }
90 |
91 | //////////////////////////////////////////////////////////////////////////////////
92 | // Comment //
93 | //////////////////////////////////////////////////////////////////////////////////
94 |
95 |
96 | /**
97 | * Compute the collision and immediatly notify the listener
98 | *
99 | * @param {THREE.Collider[]} colliders - base to purge state
100 | */
101 | function notifyContacts(colliders){
102 | for(var i = 0; i < colliders.length; i++){
103 | var collider1 = colliders[i]
104 | for(var j = i+1; j < colliders.length; j++){
105 | var collider2 = colliders[j]
106 | // stay if they do collide
107 | var doCollide = collider1.collideWith(collider2)
108 | // get previous state
109 | var stateLabel = getStateLabel(collider1, collider2)
110 | var stateExisted= states[stateLabel] ? true : false
111 | // process depending do Collide
112 | if( doCollide ){
113 | // notify proper events
114 | if( stateExisted === true ){
115 | dispatchEvent(collider1, collider2, 'contactStay')
116 | }else{
117 | dispatchEvent(collider1, collider2, 'contactEnter')
118 | }
119 | // update states
120 | states[stateLabel] = 'dummy'
121 | }else{
122 | // notify proper events
123 | if( stateExisted === true ){
124 | dispatchEvent(collider1, collider2, 'contactExit')
125 | }
126 | // update states
127 | delete states[stateLabel]
128 | }
129 | }
130 | }
131 | // console.log('post notify states', Object.keys(states).length)
132 | return
133 |
134 | function dispatchEvent(collider1, collider2, eventName){
135 | // console.log('dispatchEvent', eventName, 'between', collider1.id, 'and', collider2.id)
136 | // send event to collider1
137 | collider1.dispatchEvent(eventName, collider2, collider1)
138 | // send event to collider2
139 | collider2.dispatchEvent(eventName, collider1, collider2)
140 | }
141 | }
142 |
143 | /**
144 | * reset the events states
145 | */
146 | this.reset = function(){
147 | states = {}
148 | }
149 | }
150 |
151 |
--------------------------------------------------------------------------------