Matthias Mullie has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/390437 )

Change subject: Replace OrbitControls with TrackballControls
......................................................................

Replace OrbitControls with TrackballControls

OrbitControls limits rotation around the poles, but
TrackballControls will let you rotate around the
object any way you want.

I also had to move some code around to get TrackballControls
working. Not all these changes are necessary, but I also
find it a bit clearer.

Bug: T179823
Change-Id: I4bcf6516b7438381ff347a61c05139be3edfe07c
---
M extension.json
M modules/mmv.3d.js
D modules/three/OrbitControls.js
A modules/three/TrackballControls.js
4 files changed, 713 insertions(+), 1,129 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/3D 
refs/changes/37/390437/1

diff --git a/extension.json b/extension.json
index c6cf346..cb7e571 100644
--- a/extension.json
+++ b/extension.json
@@ -38,7 +38,7 @@
                                "mmv.3d.js",
                                "three/three.js",
                                "three/STLLoader.js",
-                               "three/OrbitControls.js"
+                               "three/TrackballControls.js"
                        ],
                        "styles": [
                                "mmv.3d.less"
diff --git a/modules/mmv.3d.js b/modules/mmv.3d.js
index 4d2e0ec..d810113 100644
--- a/modules/mmv.3d.js
+++ b/modules/mmv.3d.js
@@ -28,40 +28,43 @@
 
        TD = ThreeD.prototype;
 
-       TD.createScene = function () {
-               var threed = this,
-                       dimensions = this.dimensionsFunc(),
-                       ambient = new THREE.AmbientLight( 0x666666 ),
-                       dlight = new THREE.DirectionalLight( 0x999999 );
+       TD.init = function () {
+               var dimensions = this.getDimensions(),
+                       directionalLight;
 
-               dlight.position.set( 0, 0, 1 );
-               dlight.castShadow = true;
+               this.renderer = new THREE.WebGLRenderer();
+               this.renderer.setClearColor( 0x222222 );
+               this.renderer.setPixelRatio( window.devicePixelRatio );
+               this.renderer.setSize( dimensions.width, dimensions.height );
+               this.$container.html( this.renderer.domElement );
 
-               this.scene = new THREE.Scene();
-               this.camera = new THREE.PerspectiveCamera( 60, 
dimensions.ratio, 1, 5000 );
                this.manager = new THREE.LoadingManager();
 
+               this.camera = new THREE.PerspectiveCamera( 60, 
dimensions.ratio, 1, 5000 );
                this.camera.up.set( 0, 0, 1 );
                this.camera.add( new THREE.PointLight( 0xffffff, 0.4 ) );
 
-               this.scene.add( ambient );
-               this.scene.add( dlight );
+               this.controls = new THREE.TrackballControls( this.camera, 
this.renderer.domElement );
+               this.controls.rotateSpeed = 4;
+               this.controls.zoomSpeed = 4;
+               this.controls.panSpeed = 4;
+               this.controls.addEventListener( 'change', this.render.bind( 
this ) );
+               this.controls.addEventListener( 'start', 
this.controlsStart.bind( this ) );
+               this.controls.addEventListener( 'end', this.controlsEnd.bind( 
this ) );
+
+               this.scene = new THREE.Scene();
                this.scene.add( this.camera );
 
-               this.renderer = new THREE.WebGLRenderer();
+               this.scene.add( new THREE.AmbientLight( 0x666666 ) );
 
-               this.renderer.setClearColor( 0x222222 );
+               directionalLight = new THREE.DirectionalLight( 0x999999 );
+               directionalLight.position.set( 0, 0, 1 );
+               directionalLight.castShadow = true;
+               this.scene.add( directionalLight );
 
-               this.controls = new THREE.OrbitControls( this.camera, 
this.renderer.domElement );
-               this.controls.addEventListener( 'change', $.proxy( function () 
{ threed.render(); }, threed ) );
-               this.controls.addEventListener( 'start', $.proxy( function () { 
threed.controlsStart(); }, threed ) );
-               this.controls.addEventListener( 'end', $.proxy( function () { 
threed.controlsEnd(); }, threed ) );
-               this.controls.enableKeys = false;
-               this.controls.update();
+               $( window ).on( 'resize.3d', $.debounce( 100, 
this.onWindowResize.bind( this ) ) );
 
-               $( window ).on( 'resize.3d', $.debounce( 100, $.proxy( function 
() { threed.onWindowResize(); }, threed ) ) );
-
-               this.animate();
+               this.render();
        };
 
        TD.center = function ( object ) {
@@ -91,6 +94,63 @@
                return new THREE.Mesh( geometry, material );
        };
 
+       TD.render = function () {
+               this.renderer.render( this.scene, this.camera );
+       };
+
+       TD.animate = function () {
+               requestAnimationFrame( this.animate.bind( this ) );
+               this.controls.update();
+       };
+
+       TD.onWindowResize = function () {
+               var dimensions = this.getDimensions();
+
+               this.camera.aspect = dimensions.width / dimensions.height;
+               this.camera.updateProjectionMatrix();
+
+               this.renderer.setSize( dimensions.width, dimensions.height );
+
+               this.controls.handleResize();
+
+               this.render( this.renderer, this.scene, this.camera );
+       };
+
+       TD.load = function ( extension, url ) {
+               var threed = this;
+
+               // Abort any loading that might still be happening
+               if ( this.promise ) {
+                       this.promise.reject();
+               }
+
+               this.promise = this.loadFile( extension, url );
+
+               this.progressBar.jumpTo( 0 );
+               this.progressBar.animateTo( 5 );
+
+               this.promise.then( function ( object ) {
+                       delete threed.promise;
+
+                       threed.progressBar.hide();
+
+                       object.castShadow = true;
+
+                       threed.center( object );
+                       threed.scene.add( object );
+
+                       threed.camera.lookAt( threed.scene.position );
+                       threed.render( threed.renderer, threed.scene, 
threed.camera );
+
+                       mw.threed.attachBadge( threed.$container );
+               } ).progress( function ( progress ) {
+                       threed.progressBar.animateTo( progress );
+               } ).fail( function ( /* error */ ) {
+                       threed.progressBar.hide();
+                       delete threed.promise;
+               } );
+       };
+
        TD.loadFile = function ( extension, url ) {
                var threed = this,
                        deferred = $.Deferred(),
@@ -108,12 +168,7 @@
                                object = threed.geometryToObject( data );
                        }
 
-                       object.castShadow = true;
-
-                       threed.center( object );
-                       threed.scene.add( object );
-
-                       deferred.resolve();
+                       deferred.resolve( object );
                }, function ( progress ) {
                        deferred.notify( ( progress.loaded / progress.total ) * 
100 );
                }, function ( error ) {
@@ -126,64 +181,7 @@
                        }
                } );
 
-               return deferred;
-       };
-
-       TD.render = function () {
-               this.renderer.render( this.scene, this.camera );
-       };
-
-       TD.animate = function () {
-               if ( this.renderer && this.scene && this.camera ) {
-                       this.render( this.renderer, this.scene, this.camera );
-               }
-
-               requestAnimationFrame( $.proxy( function () { this.animate(); 
}, this ) );
-       };
-
-       TD.onWindowResize = function () {
-               var dimensions = this.dimensionsFunc();
-
-               if ( !this.camera || !this.renderer || !this.scene ) {
-                       return;
-               }
-
-               this.camera.aspect = dimensions.width / dimensions.height;
-               this.camera.updateProjectionMatrix();
-               this.renderer.setSize( dimensions.width, dimensions.height );
-               this.render( this.renderer, this.scene, this.camera );
-       };
-
-       TD.load = function ( extension, url ) {
-               var threed = this;
-
-               // Abort any loading that might still be happening
-               if ( this.promise ) {
-                       this.promise.reject();
-               }
-
-               this.promise = this.loadFile( extension, url );
-
-               this.progressBar.jumpTo( 0 );
-               this.progressBar.animateTo( 5 );
-
-               this.promise.then( function () {
-                       var dimensions = threed.dimensionsFunc();
-                       delete threed.promise;
-
-                       threed.progressBar.hide();
-                       threed.renderer.setSize( dimensions.width, 
dimensions.height );
-                       threed.$container.html( threed.renderer.domElement );
-                       threed.camera.lookAt( threed.scene.position );
-                       threed.render( threed.renderer, threed.scene, 
threed.camera );
-
-                       mw.threed.attachBadge( threed.$container );
-               } ).progress( function ( progress ) {
-                       threed.progressBar.animateTo( progress );
-               } ).fail( function ( /* error */ ) {
-                       threed.progressBar.hide();
-                       delete threed.promise;
-               } );
+               return deferred.promise();
        };
 
        TD.controlsStart = function () {
@@ -194,7 +192,7 @@
                $( this.renderer.domElement ).removeClass( 'mousedown' );
        };
 
-       TD.dimensionsFunc = function () {
+       TD.getDimensions = function () {
                var width = $( window ).width(),
                        height = this.viewer.ui.canvas.$imageWrapper.height();
 
@@ -213,10 +211,8 @@
                        singleton = new ThreeD( e.viewer );
                }
 
-               // Clear any state, create objects for render.
-               singleton.createScene();
-
-               // Complete load.
+               singleton.init();
+               singleton.animate();
                singleton.load( extension, e.imageInfo.url );
        } );
 
diff --git a/modules/three/OrbitControls.js b/modules/three/OrbitControls.js
deleted file mode 100644
index 45d1b41..0000000
--- a/modules/three/OrbitControls.js
+++ /dev/null
@@ -1,1037 +0,0 @@
-/**
- * @author qiao / https://github.com/qiao
- * @author mrdoob / http://mrdoob.com
- * @author alteredq / http://alteredqualia.com/
- * @author WestLangley / http://github.com/WestLangley
- * @author erich666 / http://erichaines.com
- */
-
-// This set of controls performs orbiting, dollying (zooming), and panning.
-// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by 
default).
-//
-//    Orbit - left mouse / touch: one finger move
-//    Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
-//    Pan - right mouse, or arrow keys / touch: three finter swipe
-
-THREE.OrbitControls = function ( object, domElement ) {
-
-       this.object = object;
-
-       this.domElement = ( domElement !== undefined ) ? domElement : document;
-
-       // Set to false to disable this control
-       this.enabled = true;
-
-       // "target" sets the location of focus, where the object orbits around
-       this.target = new THREE.Vector3();
-
-       // How far you can dolly in and out ( PerspectiveCamera only )
-       this.minDistance = 0;
-       this.maxDistance = Infinity;
-
-       // How far you can zoom in and out ( OrthographicCamera only )
-       this.minZoom = 0;
-       this.maxZoom = Infinity;
-
-       // How far you can orbit vertically, upper and lower limits.
-       // Range is 0 to Math.PI radians.
-       this.minPolarAngle = 0; // radians
-       this.maxPolarAngle = Math.PI; // radians
-
-       // How far you can orbit horizontally, upper and lower limits.
-       // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI 
].
-       this.minAzimuthAngle = - Infinity; // radians
-       this.maxAzimuthAngle = Infinity; // radians
-
-       // Set to true to enable damping (inertia)
-       // If damping is enabled, you must call controls.update() in your 
animation loop
-       this.enableDamping = false;
-       this.dampingFactor = 0.25;
-
-       // This option actually enables dollying in and out; left as "zoom" for 
backwards compatibility.
-       // Set to false to disable zooming
-       this.enableZoom = true;
-       this.zoomSpeed = 1.0;
-
-       // Set to false to disable rotating
-       this.enableRotate = true;
-       this.rotateSpeed = 1.0;
-
-       // Set to false to disable panning
-       this.enablePan = true;
-       this.keyPanSpeed = 7.0; // pixels moved per arrow key push
-
-       // Set to true to automatically rotate around the target
-       // If auto-rotate is enabled, you must call controls.update() in your 
animation loop
-       this.autoRotate = false;
-       this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
-
-       // Set to false to disable use of the keys
-       this.enableKeys = true;
-
-       // The four arrow keys
-       this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
-
-       // Mouse buttons
-       this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: 
THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
-
-       // for reset
-       this.target0 = this.target.clone();
-       this.position0 = this.object.position.clone();
-       this.zoom0 = this.object.zoom;
-
-       //
-       // public methods
-       //
-
-       this.getPolarAngle = function () {
-
-               return phi;
-
-       };
-
-       this.getAzimuthalAngle = function () {
-
-               return theta;
-
-       };
-
-       this.reset = function () {
-
-               scope.target.copy( scope.target0 );
-               scope.object.position.copy( scope.position0 );
-               scope.object.zoom = scope.zoom0;
-
-               scope.object.updateProjectionMatrix();
-               scope.dispatchEvent( changeEvent );
-
-               scope.update();
-
-               state = STATE.NONE;
-
-       };
-
-       // this method is exposed, but perhaps it would be better if we can 
make it private...
-       this.update = function() {
-
-               var offset = new THREE.Vector3();
-
-               // so camera.up is the orbit axis
-               var quat = new THREE.Quaternion().setFromUnitVectors( 
object.up, new THREE.Vector3( 0, 1, 0 ) );
-               var quatInverse = quat.clone().inverse();
-
-               var lastPosition = new THREE.Vector3();
-               var lastQuaternion = new THREE.Quaternion();
-
-               return function () {
-
-                       var position = scope.object.position;
-
-                       offset.copy( position ).sub( scope.target );
-
-                       // rotate offset to "y-axis-is-up" space
-                       offset.applyQuaternion( quat );
-
-                       // angle from z-axis around y-axis
-                       spherical.setFromVector3( offset );
-
-                       if ( scope.autoRotate && state === STATE.NONE ) {
-
-                               rotateLeft( getAutoRotationAngle() );
-
-                       }
-
-                       spherical.theta += sphericalDelta.theta;
-                       spherical.phi += sphericalDelta.phi;
-
-                       // restrict theta to be between desired limits
-                       spherical.theta = Math.max( scope.minAzimuthAngle, 
Math.min( scope.maxAzimuthAngle, spherical.theta ) );
-
-                       // restrict phi to be between desired limits
-                       spherical.phi = Math.max( scope.minPolarAngle, 
Math.min( scope.maxPolarAngle, spherical.phi ) );
-
-                       spherical.makeSafe();
-
-
-                       spherical.radius *= scale;
-
-                       // restrict radius to be between desired limits
-                       spherical.radius = Math.max( scope.minDistance, 
Math.min( scope.maxDistance, spherical.radius ) );
-
-                       // move target to panned location
-                       scope.target.add( panOffset );
-
-                       offset.setFromSpherical( spherical );
-
-                       // rotate offset back to "camera-up-vector-is-up" space
-                       offset.applyQuaternion( quatInverse );
-
-                       position.copy( scope.target ).add( offset );
-
-                       scope.object.lookAt( scope.target );
-
-                       if ( scope.enableDamping === true ) {
-
-                               sphericalDelta.theta *= ( 1 - 
scope.dampingFactor );
-                               sphericalDelta.phi *= ( 1 - scope.dampingFactor 
);
-
-                       } else {
-
-                               sphericalDelta.set( 0, 0, 0 );
-
-                       }
-
-                       scale = 1;
-                       panOffset.set( 0, 0, 0 );
-
-                       // update condition is:
-                       // min(camera displacement, camera rotation in 
radians)^2 > EPS
-                       // using small-angle approximation cos(x/2) = 1 - x^2 / 
8
-
-                       if ( zoomChanged ||
-                               lastPosition.distanceToSquared( 
scope.object.position ) > EPS ||
-                               8 * ( 1 - lastQuaternion.dot( 
scope.object.quaternion ) ) > EPS ) {
-
-                               scope.dispatchEvent( changeEvent );
-
-                               lastPosition.copy( scope.object.position );
-                               lastQuaternion.copy( scope.object.quaternion );
-                               zoomChanged = false;
-
-                               return true;
-
-                       }
-
-                       return false;
-
-               };
-
-       }();
-
-       this.dispose = function() {
-
-               scope.domElement.removeEventListener( 'contextmenu', 
onContextMenu, false );
-               scope.domElement.removeEventListener( 'mousedown', onMouseDown, 
false );
-               scope.domElement.removeEventListener( 'mousewheel', 
onMouseWheel, false );
-               scope.domElement.removeEventListener( 'MozMousePixelScroll', 
onMouseWheel, false ); // firefox
-
-               scope.domElement.removeEventListener( 'touchstart', 
onTouchStart, false );
-               scope.domElement.removeEventListener( 'touchend', onTouchEnd, 
false );
-               scope.domElement.removeEventListener( 'touchmove', onTouchMove, 
false );
-
-               document.removeEventListener( 'mousemove', onMouseMove, false );
-               document.removeEventListener( 'mouseup', onMouseUp, false );
-               document.removeEventListener( 'mouseout', onMouseUp, false );
-
-               window.removeEventListener( 'keydown', onKeyDown, false );
-
-               //scope.dispatchEvent( { type: 'dispose' } ); // should this be 
added here?
-
-       };
-
-       //
-       // internals
-       //
-
-       var scope = this;
-
-       var changeEvent = { type: 'change' };
-       var startEvent = { type: 'start' };
-       var endEvent = { type: 'end' };
-
-       var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE 
: 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
-
-       var state = STATE.NONE;
-
-       var EPS = 0.000001;
-
-       // current position in spherical coordinates
-       var spherical = new THREE.Spherical();
-       var sphericalDelta = new THREE.Spherical();
-
-       var scale = 1;
-       var panOffset = new THREE.Vector3();
-       var zoomChanged = false;
-
-       var rotateStart = new THREE.Vector2();
-       var rotateEnd = new THREE.Vector2();
-       var rotateDelta = new THREE.Vector2();
-
-       var panStart = new THREE.Vector2();
-       var panEnd = new THREE.Vector2();
-       var panDelta = new THREE.Vector2();
-
-       var dollyStart = new THREE.Vector2();
-       var dollyEnd = new THREE.Vector2();
-       var dollyDelta = new THREE.Vector2();
-
-       function getAutoRotationAngle() {
-
-               return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
-
-       }
-
-       function getZoomScale() {
-
-               return Math.pow( 0.95, scope.zoomSpeed );
-
-       }
-
-       function rotateLeft( angle ) {
-
-               sphericalDelta.theta -= angle;
-
-       }
-
-       function rotateUp( angle ) {
-
-               sphericalDelta.phi -= angle;
-
-       }
-
-       var panLeft = function() {
-
-               var v = new THREE.Vector3();
-
-               return function panLeft( distance, objectMatrix ) {
-
-                       v.setFromMatrixColumn( objectMatrix, 0 ); // get X 
column of objectMatrix
-                       v.multiplyScalar( - distance );
-
-                       panOffset.add( v );
-
-               };
-
-       }();
-
-       var panUp = function() {
-
-               var v = new THREE.Vector3();
-
-               return function panUp( distance, objectMatrix ) {
-
-                       v.setFromMatrixColumn( objectMatrix, 1 ); // get Y 
column of objectMatrix
-                       v.multiplyScalar( distance );
-
-                       panOffset.add( v );
-
-               };
-
-       }();
-
-       // deltaX and deltaY are in pixels; right and down are positive
-       var pan = function() {
-
-               var offset = new THREE.Vector3();
-
-               return function( deltaX, deltaY ) {
-
-                       var element = scope.domElement === document ? 
scope.domElement.body : scope.domElement;
-
-                       if ( scope.object instanceof THREE.PerspectiveCamera ) {
-
-                               // perspective
-                               var position = scope.object.position;
-                               offset.copy( position ).sub( scope.target );
-                               var targetDistance = offset.length();
-
-                               // half of the fov is center to top of screen
-                               targetDistance *= Math.tan( ( scope.object.fov 
/ 2 ) * Math.PI / 180.0 );
-
-                               // we actually don't use screenWidth, since 
perspective camera is fixed to screen height
-                               panLeft( 2 * deltaX * targetDistance / 
element.clientHeight, scope.object.matrix );
-                               panUp( 2 * deltaY * targetDistance / 
element.clientHeight, scope.object.matrix );
-
-                       } else if ( scope.object instanceof 
THREE.OrthographicCamera ) {
-
-                               // orthographic
-                               panLeft( deltaX * ( scope.object.right - 
scope.object.left ) / scope.object.zoom / element.clientWidth, 
scope.object.matrix );
-                               panUp( deltaY * ( scope.object.top - 
scope.object.bottom ) / scope.object.zoom / element.clientHeight, 
scope.object.matrix );
-
-                       } else {
-
-                               // camera neither orthographic nor perspective
-                               console.warn( 'WARNING: OrbitControls.js 
encountered an unknown camera type - pan disabled.' );
-                               scope.enablePan = false;
-
-                       }
-
-               };
-
-       }();
-
-       function dollyIn( dollyScale ) {
-
-               if ( scope.object instanceof THREE.PerspectiveCamera ) {
-
-                       scale /= dollyScale;
-
-               } else if ( scope.object instanceof THREE.OrthographicCamera ) {
-
-                       scope.object.zoom = Math.max( scope.minZoom, Math.min( 
scope.maxZoom, scope.object.zoom * dollyScale ) );
-                       scope.object.updateProjectionMatrix();
-                       zoomChanged = true;
-
-               } else {
-
-                       console.warn( 'WARNING: OrbitControls.js encountered an 
unknown camera type - dolly/zoom disabled.' );
-                       scope.enableZoom = false;
-
-               }
-
-       }
-
-       function dollyOut( dollyScale ) {
-
-               if ( scope.object instanceof THREE.PerspectiveCamera ) {
-
-                       scale *= dollyScale;
-
-               } else if ( scope.object instanceof THREE.OrthographicCamera ) {
-
-                       scope.object.zoom = Math.max( scope.minZoom, Math.min( 
scope.maxZoom, scope.object.zoom / dollyScale ) );
-                       scope.object.updateProjectionMatrix();
-                       zoomChanged = true;
-
-               } else {
-
-                       console.warn( 'WARNING: OrbitControls.js encountered an 
unknown camera type - dolly/zoom disabled.' );
-                       scope.enableZoom = false;
-
-               }
-
-       }
-
-       //
-       // event callbacks - update the object state
-       //
-
-       function handleMouseDownRotate( event ) {
-
-               //console.log( 'handleMouseDownRotate' );
-
-               rotateStart.set( event.clientX, event.clientY );
-
-       }
-
-       function handleMouseDownDolly( event ) {
-
-               //console.log( 'handleMouseDownDolly' );
-
-               dollyStart.set( event.clientX, event.clientY );
-
-       }
-
-       function handleMouseDownPan( event ) {
-
-               //console.log( 'handleMouseDownPan' );
-
-               panStart.set( event.clientX, event.clientY );
-
-       }
-
-       function handleMouseMoveRotate( event ) {
-
-               //console.log( 'handleMouseMoveRotate' );
-
-               rotateEnd.set( event.clientX, event.clientY );
-               rotateDelta.subVectors( rotateEnd, rotateStart );
-
-               var element = scope.domElement === document ? 
scope.domElement.body : scope.domElement;
-
-               // rotating across whole screen goes 360 degrees around
-               rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * 
scope.rotateSpeed );
-
-               // rotating up and down along whole screen attempts to go 360, 
but limited to 180
-               rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * 
scope.rotateSpeed );
-
-               rotateStart.copy( rotateEnd );
-
-               scope.update();
-
-       }
-
-       function handleMouseMoveDolly( event ) {
-
-               //console.log( 'handleMouseMoveDolly' );
-
-               dollyEnd.set( event.clientX, event.clientY );
-
-               dollyDelta.subVectors( dollyEnd, dollyStart );
-
-               if ( dollyDelta.y > 0 ) {
-
-                       dollyIn( getZoomScale() );
-
-               } else if ( dollyDelta.y < 0 ) {
-
-                       dollyOut( getZoomScale() );
-
-               }
-
-               dollyStart.copy( dollyEnd );
-
-               scope.update();
-
-       }
-
-       function handleMouseMovePan( event ) {
-
-               //console.log( 'handleMouseMovePan' );
-
-               panEnd.set( event.clientX, event.clientY );
-
-               panDelta.subVectors( panEnd, panStart );
-
-               pan( panDelta.x, panDelta.y );
-
-               panStart.copy( panEnd );
-
-               scope.update();
-
-       }
-
-       function handleMouseUp( event ) {
-
-               //console.log( 'handleMouseUp' );
-
-       }
-
-       function handleMouseWheel( event ) {
-
-               //console.log( 'handleMouseWheel' );
-
-               var delta = 0;
-
-               if ( event.wheelDelta !== undefined ) {
-
-                       // WebKit / Opera / Explorer 9
-
-                       delta = event.wheelDelta;
-
-               } else if ( event.detail !== undefined ) {
-
-                       // Firefox
-
-                       delta = - event.detail;
-
-               }
-
-               if ( delta > 0 ) {
-
-                       dollyOut( getZoomScale() );
-
-               } else if ( delta < 0 ) {
-
-                       dollyIn( getZoomScale() );
-
-               }
-
-               scope.update();
-
-       }
-
-       function handleKeyDown( event ) {
-
-               //console.log( 'handleKeyDown' );
-
-               switch ( event.keyCode ) {
-
-                       case scope.keys.UP:
-                               pan( 0, scope.keyPanSpeed );
-                               scope.update();
-                               break;
-
-                       case scope.keys.BOTTOM:
-                               pan( 0, - scope.keyPanSpeed );
-                               scope.update();
-                               break;
-
-                       case scope.keys.LEFT:
-                               pan( scope.keyPanSpeed, 0 );
-                               scope.update();
-                               break;
-
-                       case scope.keys.RIGHT:
-                               pan( - scope.keyPanSpeed, 0 );
-                               scope.update();
-                               break;
-
-               }
-
-       }
-
-       function handleTouchStartRotate( event ) {
-
-               //console.log( 'handleTouchStartRotate' );
-
-               rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 
].pageY );
-
-       }
-
-       function handleTouchStartDolly( event ) {
-
-               //console.log( 'handleTouchStartDolly' );
-
-               var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
-               var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-
-               var distance = Math.sqrt( dx * dx + dy * dy );
-
-               dollyStart.set( 0, distance );
-
-       }
-
-       function handleTouchStartPan( event ) {
-
-               //console.log( 'handleTouchStartPan' );
-
-               panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 
].pageY );
-
-       }
-
-       function handleTouchMoveRotate( event ) {
-
-               //console.log( 'handleTouchMoveRotate' );
-
-               rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 
].pageY );
-               rotateDelta.subVectors( rotateEnd, rotateStart );
-
-               var element = scope.domElement === document ? 
scope.domElement.body : scope.domElement;
-
-               // rotating across whole screen goes 360 degrees around
-               rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * 
scope.rotateSpeed );
-
-               // rotating up and down along whole screen attempts to go 360, 
but limited to 180
-               rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * 
scope.rotateSpeed );
-
-               rotateStart.copy( rotateEnd );
-
-               scope.update();
-
-       }
-
-       function handleTouchMoveDolly( event ) {
-
-               //console.log( 'handleTouchMoveDolly' );
-
-               var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
-               var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-
-               var distance = Math.sqrt( dx * dx + dy * dy );
-
-               dollyEnd.set( 0, distance );
-
-               dollyDelta.subVectors( dollyEnd, dollyStart );
-
-               if ( dollyDelta.y > 0 ) {
-
-                       dollyOut( getZoomScale() );
-
-               } else if ( dollyDelta.y < 0 ) {
-
-                       dollyIn( getZoomScale() );
-
-               }
-
-               dollyStart.copy( dollyEnd );
-
-               scope.update();
-
-       }
-
-       function handleTouchMovePan( event ) {
-
-               //console.log( 'handleTouchMovePan' );
-
-               panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY 
);
-
-               panDelta.subVectors( panEnd, panStart );
-
-               pan( panDelta.x, panDelta.y );
-
-               panStart.copy( panEnd );
-
-               scope.update();
-
-       }
-
-       function handleTouchEnd( event ) {
-
-               //console.log( 'handleTouchEnd' );
-
-       }
-
-       //
-       // event handlers - FSM: listen for events and reset state
-       //
-
-       function onMouseDown( event ) {
-
-               if ( scope.enabled === false ) return;
-
-               event.preventDefault();
-
-               if ( event.button === scope.mouseButtons.ORBIT ) {
-
-                       if ( scope.enableRotate === false ) return;
-
-                       handleMouseDownRotate( event );
-
-                       state = STATE.ROTATE;
-
-               } else if ( event.button === scope.mouseButtons.ZOOM ) {
-
-                       if ( scope.enableZoom === false ) return;
-
-                       handleMouseDownDolly( event );
-
-                       state = STATE.DOLLY;
-
-               } else if ( event.button === scope.mouseButtons.PAN ) {
-
-                       if ( scope.enablePan === false ) return;
-
-                       handleMouseDownPan( event );
-
-                       state = STATE.PAN;
-
-               }
-
-               if ( state !== STATE.NONE ) {
-
-                       document.addEventListener( 'mousemove', onMouseMove, 
false );
-                       document.addEventListener( 'mouseup', onMouseUp, false 
);
-                       document.addEventListener( 'mouseout', onMouseUp, false 
);
-
-                       scope.dispatchEvent( startEvent );
-
-               }
-
-       }
-
-       function onMouseMove( event ) {
-
-               if ( scope.enabled === false ) return;
-
-               event.preventDefault();
-
-               if ( state === STATE.ROTATE ) {
-
-                       if ( scope.enableRotate === false ) return;
-
-                       handleMouseMoveRotate( event );
-
-               } else if ( state === STATE.DOLLY ) {
-
-                       if ( scope.enableZoom === false ) return;
-
-                       handleMouseMoveDolly( event );
-
-               } else if ( state === STATE.PAN ) {
-
-                       if ( scope.enablePan === false ) return;
-
-                       handleMouseMovePan( event );
-
-               }
-
-       }
-
-       function onMouseUp( event ) {
-
-               if ( scope.enabled === false ) return;
-
-               handleMouseUp( event );
-
-               document.removeEventListener( 'mousemove', onMouseMove, false );
-               document.removeEventListener( 'mouseup', onMouseUp, false );
-               document.removeEventListener( 'mouseout', onMouseUp, false );
-
-               scope.dispatchEvent( endEvent );
-
-               state = STATE.NONE;
-
-       }
-
-       function onMouseWheel( event ) {
-
-               if ( scope.enabled === false || scope.enableZoom === false || 
state !== STATE.NONE ) return;
-
-               event.preventDefault();
-               event.stopPropagation();
-
-               handleMouseWheel( event );
-
-               scope.dispatchEvent( startEvent ); // not sure why these are 
here...
-               scope.dispatchEvent( endEvent );
-
-       }
-
-       function onKeyDown( event ) {
-
-               if ( scope.enabled === false || scope.enableKeys === false || 
scope.enablePan === false ) return;
-
-               handleKeyDown( event );
-
-       }
-
-       function onTouchStart( event ) {
-
-               if ( scope.enabled === false ) return;
-
-               switch ( event.touches.length ) {
-
-                       case 1: // one-fingered touch: rotate
-
-                               if ( scope.enableRotate === false ) return;
-
-                               handleTouchStartRotate( event );
-
-                               state = STATE.TOUCH_ROTATE;
-
-                               break;
-
-                       case 2: // two-fingered touch: dolly
-
-                               if ( scope.enableZoom === false ) return;
-
-                               handleTouchStartDolly( event );
-
-                               state = STATE.TOUCH_DOLLY;
-
-                               break;
-
-                       case 3: // three-fingered touch: pan
-
-                               if ( scope.enablePan === false ) return;
-
-                               handleTouchStartPan( event );
-
-                               state = STATE.TOUCH_PAN;
-
-                               break;
-
-                       default:
-
-                               state = STATE.NONE;
-
-               }
-
-               if ( state !== STATE.NONE ) {
-
-                       scope.dispatchEvent( startEvent );
-
-               }
-
-       }
-
-       function onTouchMove( event ) {
-
-               if ( scope.enabled === false ) return;
-
-               event.preventDefault();
-               event.stopPropagation();
-
-               switch ( event.touches.length ) {
-
-                       case 1: // one-fingered touch: rotate
-
-                               if ( scope.enableRotate === false ) return;
-                               if ( state !== STATE.TOUCH_ROTATE ) return; // 
is this needed?...
-
-                               handleTouchMoveRotate( event );
-
-                               break;
-
-                       case 2: // two-fingered touch: dolly
-
-                               if ( scope.enableZoom === false ) return;
-                               if ( state !== STATE.TOUCH_DOLLY ) return; // 
is this needed?...
-
-                               handleTouchMoveDolly( event );
-
-                               break;
-
-                       case 3: // three-fingered touch: pan
-
-                               if ( scope.enablePan === false ) return;
-                               if ( state !== STATE.TOUCH_PAN ) return; // is 
this needed?...
-
-                               handleTouchMovePan( event );
-
-                               break;
-
-                       default:
-
-                               state = STATE.NONE;
-
-               }
-
-       }
-
-       function onTouchEnd( event ) {
-
-               if ( scope.enabled === false ) return;
-
-               handleTouchEnd( event );
-
-               scope.dispatchEvent( endEvent );
-
-               state = STATE.NONE;
-
-       }
-
-       function onContextMenu( event ) {
-
-               event.preventDefault();
-
-       }
-
-       //
-
-       scope.domElement.addEventListener( 'contextmenu', onContextMenu, false 
);
-
-       scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
-       scope.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
-       scope.domElement.addEventListener( 'MozMousePixelScroll', onMouseWheel, 
false ); // firefox
-
-       scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
-       scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
-       scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
-
-       window.addEventListener( 'keydown', onKeyDown, false );
-
-       // force an update at start
-
-       this.update();
-
-};
-
-THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype 
);
-THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
-
-Object.defineProperties( THREE.OrbitControls.prototype, {
-
-       center: {
-
-               get: function () {
-
-                       console.warn( 'THREE.OrbitControls: .center has been 
renamed to .target' );
-                       return this.target;
-
-               }
-
-       },
-
-       // backward compatibility
-
-       noZoom: {
-
-               get: function () {
-
-                       console.warn( 'THREE.OrbitControls: .noZoom has been 
deprecated. Use .enableZoom instead.' );
-                       return ! this.enableZoom;
-
-               },
-
-               set: function ( value ) {
-
-                       console.warn( 'THREE.OrbitControls: .noZoom has been 
deprecated. Use .enableZoom instead.' );
-                       this.enableZoom = ! value;
-
-               }
-
-       },
-
-       noRotate: {
-
-               get: function () {
-
-                       console.warn( 'THREE.OrbitControls: .noRotate has been 
deprecated. Use .enableRotate instead.' );
-                       return ! this.enableRotate;
-
-               },
-
-               set: function ( value ) {
-
-                       console.warn( 'THREE.OrbitControls: .noRotate has been 
deprecated. Use .enableRotate instead.' );
-                       this.enableRotate = ! value;
-
-               }
-
-       },
-
-       noPan: {
-
-               get: function () {
-
-                       console.warn( 'THREE.OrbitControls: .noPan has been 
deprecated. Use .enablePan instead.' );
-                       return ! this.enablePan;
-
-               },
-
-               set: function ( value ) {
-
-                       console.warn( 'THREE.OrbitControls: .noPan has been 
deprecated. Use .enablePan instead.' );
-                       this.enablePan = ! value;
-
-               }
-
-       },
-
-       noKeys: {
-
-               get: function () {
-
-                       console.warn( 'THREE.OrbitControls: .noKeys has been 
deprecated. Use .enableKeys instead.' );
-                       return ! this.enableKeys;
-
-               },
-
-               set: function ( value ) {
-
-                       console.warn( 'THREE.OrbitControls: .noKeys has been 
deprecated. Use .enableKeys instead.' );
-                       this.enableKeys = ! value;
-
-               }
-
-       },
-
-       staticMoving : {
-
-               get: function () {
-
-                       console.warn( 'THREE.OrbitControls: .staticMoving has 
been deprecated. Use .enableDamping instead.' );
-                       return ! this.constraint.enableDamping;
-
-               },
-
-               set: function ( value ) {
-
-                       console.warn( 'THREE.OrbitControls: .staticMoving has 
been deprecated. Use .enableDamping instead.' );
-                       this.constraint.enableDamping = ! value;
-
-               }
-
-       },
-
-       dynamicDampingFactor : {
-
-               get: function () {
-
-                       console.warn( 'THREE.OrbitControls: 
.dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
-                       return this.constraint.dampingFactor;
-
-               },
-
-               set: function ( value ) {
-
-                       console.warn( 'THREE.OrbitControls: 
.dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
-                       this.constraint.dampingFactor = value;
-
-               }
-
-       }
-
-} );
diff --git a/modules/three/TrackballControls.js 
b/modules/three/TrackballControls.js
new file mode 100755
index 0000000..5d45829
--- /dev/null
+++ b/modules/three/TrackballControls.js
@@ -0,0 +1,625 @@
+/**
+ * @author Eberhard Graether / http://egraether.com/
+ * @author Mark Lundin         / http://mark-lundin.com
+ * @author Simone Manini / http://daron1337.github.io
+ * @author Luca Antiga         / http://lantiga.github.io
+ */
+
+THREE.TrackballControls = function ( object, domElement ) {
+
+       var _this = this;
+       var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, 
TOUCH_ZOOM_PAN: 4 };
+
+       this.object = object;
+       this.domElement = ( domElement !== undefined ) ? domElement : document;
+
+       // API
+
+       this.enabled = true;
+
+       this.screen = { left: 0, top: 0, width: 0, height: 0 };
+
+       this.rotateSpeed = 1.0;
+       this.zoomSpeed = 1.2;
+       this.panSpeed = 0.3;
+
+       this.noRotate = false;
+       this.noZoom = false;
+       this.noPan = false;
+
+       this.staticMoving = false;
+       this.dynamicDampingFactor = 0.2;
+
+       this.minDistance = 0;
+       this.maxDistance = Infinity;
+
+       this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
+
+       // internals
+
+       this.target = new THREE.Vector3();
+
+       var EPS = 0.000001;
+
+       var lastPosition = new THREE.Vector3();
+
+       var _state = STATE.NONE,
+       _prevState = STATE.NONE,
+
+       _eye = new THREE.Vector3(),
+
+       _movePrev = new THREE.Vector2(),
+       _moveCurr = new THREE.Vector2(),
+
+       _lastAxis = new THREE.Vector3(),
+       _lastAngle = 0,
+
+       _zoomStart = new THREE.Vector2(),
+       _zoomEnd = new THREE.Vector2(),
+
+       _touchZoomDistanceStart = 0,
+       _touchZoomDistanceEnd = 0,
+
+       _panStart = new THREE.Vector2(),
+       _panEnd = new THREE.Vector2();
+
+       // for reset
+
+       this.target0 = this.target.clone();
+       this.position0 = this.object.position.clone();
+       this.up0 = this.object.up.clone();
+
+       // events
+
+       var changeEvent = { type: 'change' };
+       var startEvent = { type: 'start' };
+       var endEvent = { type: 'end' };
+
+
+       // methods
+
+       this.handleResize = function () {
+
+               if ( this.domElement === document ) {
+
+                       this.screen.left = 0;
+                       this.screen.top = 0;
+                       this.screen.width = window.innerWidth;
+                       this.screen.height = window.innerHeight;
+
+               } else {
+
+                       var box = this.domElement.getBoundingClientRect();
+                       // adjustments come from similar code in the jquery 
offset() function
+                       var d = this.domElement.ownerDocument.documentElement;
+                       this.screen.left = box.left + window.pageXOffset - 
d.clientLeft;
+                       this.screen.top = box.top + window.pageYOffset - 
d.clientTop;
+                       this.screen.width = box.width;
+                       this.screen.height = box.height;
+
+               }
+
+       };
+
+       this.handleEvent = function ( event ) {
+
+               if ( typeof this[ event.type ] == 'function' ) {
+
+                       this[ event.type ]( event );
+
+               }
+
+       };
+
+       var getMouseOnScreen = ( function () {
+
+               var vector = new THREE.Vector2();
+
+               return function getMouseOnScreen( pageX, pageY ) {
+
+                       vector.set(
+                               ( pageX - _this.screen.left ) / 
_this.screen.width,
+                               ( pageY - _this.screen.top ) / 
_this.screen.height
+                       );
+
+                       return vector;
+
+               };
+
+       }() );
+
+       var getMouseOnCircle = ( function () {
+
+               var vector = new THREE.Vector2();
+
+               return function getMouseOnCircle( pageX, pageY ) {
+
+                       vector.set(
+                               ( ( pageX - _this.screen.width * 0.5 - 
_this.screen.left ) / ( _this.screen.width * 0.5 ) ),
+                               ( ( _this.screen.height + 2 * ( 
_this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional
+                       );
+
+                       return vector;
+
+               };
+
+       }() );
+
+       this.rotateCamera = ( function() {
+
+               var axis = new THREE.Vector3(),
+                       quaternion = new THREE.Quaternion(),
+                       eyeDirection = new THREE.Vector3(),
+                       objectUpDirection = new THREE.Vector3(),
+                       objectSidewaysDirection = new THREE.Vector3(),
+                       moveDirection = new THREE.Vector3(),
+                       angle;
+
+               return function rotateCamera() {
+
+                       moveDirection.set( _moveCurr.x - _movePrev.x, 
_moveCurr.y - _movePrev.y, 0 );
+                       angle = moveDirection.length();
+
+                       if ( angle ) {
+
+                               _eye.copy( _this.object.position ).sub( 
_this.target );
+
+                               eyeDirection.copy( _eye ).normalize();
+                               objectUpDirection.copy( _this.object.up 
).normalize();
+                               objectSidewaysDirection.crossVectors( 
objectUpDirection, eyeDirection ).normalize();
+
+                               objectUpDirection.setLength( _moveCurr.y - 
_movePrev.y );
+                               objectSidewaysDirection.setLength( _moveCurr.x 
- _movePrev.x );
+
+                               moveDirection.copy( objectUpDirection.add( 
objectSidewaysDirection ) );
+
+                               axis.crossVectors( moveDirection, _eye 
).normalize();
+
+                               angle *= _this.rotateSpeed;
+                               quaternion.setFromAxisAngle( axis, angle );
+
+                               _eye.applyQuaternion( quaternion );
+                               _this.object.up.applyQuaternion( quaternion );
+
+                               _lastAxis.copy( axis );
+                               _lastAngle = angle;
+
+                       } else if ( ! _this.staticMoving && _lastAngle ) {
+
+                               _lastAngle *= Math.sqrt( 1.0 - 
_this.dynamicDampingFactor );
+                               _eye.copy( _this.object.position ).sub( 
_this.target );
+                               quaternion.setFromAxisAngle( _lastAxis, 
_lastAngle );
+                               _eye.applyQuaternion( quaternion );
+                               _this.object.up.applyQuaternion( quaternion );
+
+                       }
+
+                       _movePrev.copy( _moveCurr );
+
+               };
+
+       }() );
+
+
+       this.zoomCamera = function () {
+
+               var factor;
+
+               if ( _state === STATE.TOUCH_ZOOM_PAN ) {
+
+                       factor = _touchZoomDistanceStart / 
_touchZoomDistanceEnd;
+                       _touchZoomDistanceStart = _touchZoomDistanceEnd;
+                       _eye.multiplyScalar( factor );
+
+               } else {
+
+                       factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * 
_this.zoomSpeed;
+
+                       if ( factor !== 1.0 && factor > 0.0 ) {
+
+                               _eye.multiplyScalar( factor );
+
+                       }
+
+                       if ( _this.staticMoving ) {
+
+                               _zoomStart.copy( _zoomEnd );
+
+                       } else {
+
+                               _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * 
this.dynamicDampingFactor;
+
+                       }
+
+               }
+
+       };
+
+       this.panCamera = ( function() {
+
+               var mouseChange = new THREE.Vector2(),
+                       objectUp = new THREE.Vector3(),
+                       pan = new THREE.Vector3();
+
+               return function panCamera() {
+
+                       mouseChange.copy( _panEnd ).sub( _panStart );
+
+                       if ( mouseChange.lengthSq() ) {
+
+                               mouseChange.multiplyScalar( _eye.length() * 
_this.panSpeed );
+
+                               pan.copy( _eye ).cross( _this.object.up 
).setLength( mouseChange.x );
+                               pan.add( objectUp.copy( _this.object.up 
).setLength( mouseChange.y ) );
+
+                               _this.object.position.add( pan );
+                               _this.target.add( pan );
+
+                               if ( _this.staticMoving ) {
+
+                                       _panStart.copy( _panEnd );
+
+                               } else {
+
+                                       _panStart.add( mouseChange.subVectors( 
_panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
+
+                               }
+
+                       }
+
+               };
+
+       }() );
+
+       this.checkDistances = function () {
+
+               if ( ! _this.noZoom || ! _this.noPan ) {
+
+                       if ( _eye.lengthSq() > _this.maxDistance * 
_this.maxDistance ) {
+
+                               _this.object.position.addVectors( _this.target, 
_eye.setLength( _this.maxDistance ) );
+                               _zoomStart.copy( _zoomEnd );
+
+                       }
+
+                       if ( _eye.lengthSq() < _this.minDistance * 
_this.minDistance ) {
+
+                               _this.object.position.addVectors( _this.target, 
_eye.setLength( _this.minDistance ) );
+                               _zoomStart.copy( _zoomEnd );
+
+                       }
+
+               }
+
+       };
+
+       this.update = function () {
+
+               _eye.subVectors( _this.object.position, _this.target );
+
+               if ( ! _this.noRotate ) {
+
+                       _this.rotateCamera();
+
+               }
+
+               if ( ! _this.noZoom ) {
+
+                       _this.zoomCamera();
+
+               }
+
+               if ( ! _this.noPan ) {
+
+                       _this.panCamera();
+
+               }
+
+               _this.object.position.addVectors( _this.target, _eye );
+
+               _this.checkDistances();
+
+               _this.object.lookAt( _this.target );
+
+               if ( lastPosition.distanceToSquared( _this.object.position ) > 
EPS ) {
+
+                       _this.dispatchEvent( changeEvent );
+
+                       lastPosition.copy( _this.object.position );
+
+               }
+
+       };
+
+       this.reset = function () {
+
+               _state = STATE.NONE;
+               _prevState = STATE.NONE;
+
+               _this.target.copy( _this.target0 );
+               _this.object.position.copy( _this.position0 );
+               _this.object.up.copy( _this.up0 );
+
+               _eye.subVectors( _this.object.position, _this.target );
+
+               _this.object.lookAt( _this.target );
+
+               _this.dispatchEvent( changeEvent );
+
+               lastPosition.copy( _this.object.position );
+
+       };
+
+       // listeners
+
+       function keydown( event ) {
+
+               if ( _this.enabled === false ) return;
+
+               window.removeEventListener( 'keydown', keydown );
+
+               _prevState = _state;
+
+               if ( _state !== STATE.NONE ) {
+
+                       return;
+
+               } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! 
_this.noRotate ) {
+
+                       _state = STATE.ROTATE;
+
+               } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! 
_this.noZoom ) {
+
+                       _state = STATE.ZOOM;
+
+               } else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! 
_this.noPan ) {
+
+                       _state = STATE.PAN;
+
+               }
+
+       }
+
+       function keyup( event ) {
+
+               if ( _this.enabled === false ) return;
+
+               _state = _prevState;
+
+               window.addEventListener( 'keydown', keydown, false );
+
+       }
+
+       function mousedown( event ) {
+
+               if ( _this.enabled === false ) return;
+
+               event.preventDefault();
+               event.stopPropagation();
+
+               if ( _state === STATE.NONE ) {
+
+                       _state = event.button;
+
+               }
+
+               if ( _state === STATE.ROTATE && ! _this.noRotate ) {
+
+                       _moveCurr.copy( getMouseOnCircle( event.pageX, 
event.pageY ) );
+                       _movePrev.copy( _moveCurr );
+
+               } else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
+
+                       _zoomStart.copy( getMouseOnScreen( event.pageX, 
event.pageY ) );
+                       _zoomEnd.copy( _zoomStart );
+
+               } else if ( _state === STATE.PAN && ! _this.noPan ) {
+
+                       _panStart.copy( getMouseOnScreen( event.pageX, 
event.pageY ) );
+                       _panEnd.copy( _panStart );
+
+               }
+
+               document.addEventListener( 'mousemove', mousemove, false );
+               document.addEventListener( 'mouseup', mouseup, false );
+
+               _this.dispatchEvent( startEvent );
+
+       }
+
+       function mousemove( event ) {
+
+               if ( _this.enabled === false ) return;
+
+               event.preventDefault();
+               event.stopPropagation();
+
+               if ( _state === STATE.ROTATE && ! _this.noRotate ) {
+
+                       _movePrev.copy( _moveCurr );
+                       _moveCurr.copy( getMouseOnCircle( event.pageX, 
event.pageY ) );
+
+               } else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
+
+                       _zoomEnd.copy( getMouseOnScreen( event.pageX, 
event.pageY ) );
+
+               } else if ( _state === STATE.PAN && ! _this.noPan ) {
+
+                       _panEnd.copy( getMouseOnScreen( event.pageX, 
event.pageY ) );
+
+               }
+
+       }
+
+       function mouseup( event ) {
+
+               if ( _this.enabled === false ) return;
+
+               event.preventDefault();
+               event.stopPropagation();
+
+               _state = STATE.NONE;
+
+               document.removeEventListener( 'mousemove', mousemove );
+               document.removeEventListener( 'mouseup', mouseup );
+               _this.dispatchEvent( endEvent );
+
+       }
+
+       function mousewheel( event ) {
+
+               if ( _this.enabled === false ) return;
+
+               event.preventDefault();
+               event.stopPropagation();
+
+               switch ( event.deltaMode ) {
+
+                       case 2:
+                               // Zoom in pages
+                               _zoomStart.y -= event.deltaY * 0.025;
+                               break;
+
+                       case 1:
+                               // Zoom in lines
+                               _zoomStart.y -= event.deltaY * 0.01;
+                               break;
+
+                       default:
+                               // undefined, 0, assume pixels
+                               _zoomStart.y -= event.deltaY * 0.00025;
+                               break;
+
+               }
+
+               _this.dispatchEvent( startEvent );
+               _this.dispatchEvent( endEvent );
+
+       }
+
+       function touchstart( event ) {
+
+               if ( _this.enabled === false ) return;
+
+               switch ( event.touches.length ) {
+
+                       case 1:
+                               _state = STATE.TOUCH_ROTATE;
+                               _moveCurr.copy( getMouseOnCircle( 
event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
+                               _movePrev.copy( _moveCurr );
+                               break;
+
+                       default: // 2 or more
+                               _state = STATE.TOUCH_ZOOM_PAN;
+                               var dx = event.touches[ 0 ].pageX - 
event.touches[ 1 ].pageX;
+                               var dy = event.touches[ 0 ].pageY - 
event.touches[ 1 ].pageY;
+                               _touchZoomDistanceEnd = _touchZoomDistanceStart 
= Math.sqrt( dx * dx + dy * dy );
+
+                               var x = ( event.touches[ 0 ].pageX + 
event.touches[ 1 ].pageX ) / 2;
+                               var y = ( event.touches[ 0 ].pageY + 
event.touches[ 1 ].pageY ) / 2;
+                               _panStart.copy( getMouseOnScreen( x, y ) );
+                               _panEnd.copy( _panStart );
+                               break;
+
+               }
+
+               _this.dispatchEvent( startEvent );
+
+       }
+
+       function touchmove( event ) {
+
+               if ( _this.enabled === false ) return;
+
+               event.preventDefault();
+               event.stopPropagation();
+
+               switch ( event.touches.length ) {
+
+                       case 1:
+                               _movePrev.copy( _moveCurr );
+                               _moveCurr.copy( getMouseOnCircle( 
event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
+                               break;
+
+                       default: // 2 or more
+                               var dx = event.touches[ 0 ].pageX - 
event.touches[ 1 ].pageX;
+                               var dy = event.touches[ 0 ].pageY - 
event.touches[ 1 ].pageY;
+                               _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy 
* dy );
+
+                               var x = ( event.touches[ 0 ].pageX + 
event.touches[ 1 ].pageX ) / 2;
+                               var y = ( event.touches[ 0 ].pageY + 
event.touches[ 1 ].pageY ) / 2;
+                               _panEnd.copy( getMouseOnScreen( x, y ) );
+                               break;
+
+               }
+
+       }
+
+       function touchend( event ) {
+
+               if ( _this.enabled === false ) return;
+
+               switch ( event.touches.length ) {
+
+                       case 0:
+                               _state = STATE.NONE;
+                               break;
+
+                       case 1:
+                               _state = STATE.TOUCH_ROTATE;
+                               _moveCurr.copy( getMouseOnCircle( 
event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
+                               _movePrev.copy( _moveCurr );
+                               break;
+
+               }
+
+               _this.dispatchEvent( endEvent );
+
+       }
+
+       function contextmenu( event ) {
+
+               if ( _this.enabled === false ) return;
+
+               event.preventDefault();
+
+       }
+
+       this.dispose = function() {
+
+               this.domElement.removeEventListener( 'contextmenu', 
contextmenu, false );
+               this.domElement.removeEventListener( 'mousedown', mousedown, 
false );
+               this.domElement.removeEventListener( 'wheel', mousewheel, false 
);
+
+               this.domElement.removeEventListener( 'touchstart', touchstart, 
false );
+               this.domElement.removeEventListener( 'touchend', touchend, 
false );
+               this.domElement.removeEventListener( 'touchmove', touchmove, 
false );
+
+               document.removeEventListener( 'mousemove', mousemove, false );
+               document.removeEventListener( 'mouseup', mouseup, false );
+
+               window.removeEventListener( 'keydown', keydown, false );
+               window.removeEventListener( 'keyup', keyup, false );
+
+       };
+
+       this.domElement.addEventListener( 'contextmenu', contextmenu, false );
+       this.domElement.addEventListener( 'mousedown', mousedown, false );
+       this.domElement.addEventListener( 'wheel', mousewheel, false );
+
+       this.domElement.addEventListener( 'touchstart', touchstart, false );
+       this.domElement.addEventListener( 'touchend', touchend, false );
+       this.domElement.addEventListener( 'touchmove', touchmove, false );
+
+       window.addEventListener( 'keydown', keydown, false );
+       window.addEventListener( 'keyup', keyup, false );
+
+       this.handleResize();
+
+       // force an update at start
+       this.update();
+
+};
+
+THREE.TrackballControls.prototype = Object.create( 
THREE.EventDispatcher.prototype );
+THREE.TrackballControls.prototype.constructor = THREE.TrackballControls;

-- 
To view, visit https://gerrit.wikimedia.org/r/390437
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I4bcf6516b7438381ff347a61c05139be3edfe07c
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/3D
Gerrit-Branch: master
Gerrit-Owner: Matthias Mullie <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to