Bladeren bron

added transform controls
replaced axis geometry with code
added rotation and scale controls
refactored
added local modes
changed default settings
added todo list

optimized and bug fixes

changed default settings

typo

hide intersection planes

refactored transformControls
added half-rings to rotation handles

Aleksandar Rodic 12 jaren geleden
bovenliggende
commit
459769d056
3 gewijzigde bestanden met toevoegingen van 839 en 86 verwijderingen
  1. 1 0
      editor/index.html
  2. 33 86
      editor/js/ui/Viewport.js
  3. 805 0
      examples/js/controls/TransformControls.js

+ 1 - 0
editor/index.html

@@ -77,6 +77,7 @@
 		<script src="../examples/js/libs/system.min.js"></script>
 
 		<script src="../examples/js/controls/EditorControls.js"></script>
+		<script src="../examples/js/controls/TransformControls.js"></script>
 		<script src="../examples/js/loaders/BinaryLoader.js"></script>
 		<script src="../examples/js/loaders/ColladaLoader.js"></script>
 		<script src="../examples/js/loaders/OBJLoader.js"></script>

+ 33 - 86
editor/js/ui/Viewport.js

@@ -25,8 +25,15 @@ var Viewport = function ( signals ) {
 	var grid = new THREE.GridHelper( 500, 25 );
 	sceneHelpers.add( grid );
 
-	var modifierAxis = new THREE.Vector3( 1, 1, 1 );
-	var snapDist = null;
+	//
+
+	var scene = new THREE.Scene();
+
+	var camera = new THREE.PerspectiveCamera( 50, container.dom.offsetWidth / container.dom.offsetHeight, 1, 5000 );
+	camera.position.set( 500, 250, 500 );
+	camera.lookAt( scene.position );
+
+	//
 
 	var selectionBox = new THREE.BoxHelper();
 	selectionBox.material.color.setHex( 0xffff00 );
@@ -35,20 +42,9 @@ var Viewport = function ( signals ) {
 	selectionBox.visible = false;
 	sceneHelpers.add( selectionBox );
 
-	var selectionAxis = new THREE.AxisHelper( 100 );
-	selectionAxis.material.depthTest = false;
-	selectionAxis.material.transparent = true;
-	selectionAxis.matrixAutoUpdate = false;
-	selectionAxis.visible = false;
-	sceneHelpers.add( selectionAxis );
-
-	//
-
-	var scene = new THREE.Scene();
-
-	var camera = new THREE.PerspectiveCamera( 50, container.dom.offsetWidth / container.dom.offsetHeight, 1, 5000 );
-	camera.position.set( 500, 250, 500 );
-	camera.lookAt( scene.position );
+	var transformControls = new THREE.TransformControls( camera, container.dom );
+	sceneHelpers.add( transformControls.gizmo );
+	transformControls.hide();
 
 	// fog
 
@@ -60,13 +56,8 @@ var Viewport = function ( signals ) {
 
 	// object picking
 
-	var intersectionPlane = new THREE.Mesh( new THREE.PlaneGeometry( 5000, 5000 ) );
-	intersectionPlane.visible = false;
-	sceneHelpers.add( intersectionPlane );
-
 	var ray = new THREE.Raycaster();
 	var projector = new THREE.Projector();
-	var offset = new THREE.Vector3();
 
 	var selected = camera;
 
@@ -106,72 +97,21 @@ var Viewport = function ( signals ) {
 
 		onMouseDownPosition.set( event.layerX, event.layerY );
 
-		if ( event.button === 0 ) {
-
-			var intersects = getIntersects( event, objects );
-
-			if ( intersects.length > 0 ) {
-
-				var object = intersects[ 0 ].object;
-
-				if ( selected === object || selected === helpersToObjects[ object.id ] ) {
-
-					intersectionPlane.position.copy( selected.position );
-					intersectionPlane.lookAt( camera.position );
-					intersectionPlane.updateMatrixWorld();
-
-					var intersects = ray.intersectObject( intersectionPlane );
-
-					offset.copy( intersects[ 0 ].point ).sub( intersectionPlane.position );
-
-					document.addEventListener( 'mousemove', onMouseMove, false );
-
-					controls.enabled = false;
-
-				}
-
-			} else {
-
-				controls.enabled = true;
-
-			}
+		setTimeout( function(){ 
+			if ( transformControls.active ) controls.enabled = false;
+			else controls.enabled = true;
+		}, 10 );
 
-			document.addEventListener( 'mouseup', onMouseUp, false );
+		document.addEventListener( 'mousemove', onMouseMove, false );
+		document.addEventListener( 'mouseup', onMouseUp, false );
 
-		}
+		render();
 
 	};
 
 	var onMouseMove = function ( event ) {
 
-		onMouseMovePosition.set( event.layerX, event.layerY );
-
-		if ( onMouseDownPosition.distanceTo( onMouseUpPosition ) > 1 ) {
-
-			var intersects = getIntersects( event, intersectionPlane );
-
-			if ( intersects.length > 0 ) {
-
-				var point = intersects[ 0 ].point.sub( offset );
-
-				if (snapDist) {
-					point.x = Math.round( point.x / snapDist ) * snapDist;
-					point.y = Math.round( point.y / snapDist ) * snapDist;
-					point.z = Math.round( point.z / snapDist ) * snapDist;
-				}
-
-				selected.position.x = modifierAxis.x === 1 ? point.x : intersectionPlane.position.x;
-				selected.position.y = modifierAxis.y === 1 ? point.y : intersectionPlane.position.y;
-				selected.position.z = modifierAxis.z === 1 ? point.z : intersectionPlane.position.z;
-
-
-				signals.objectChanged.dispatch( selected );
-
-				render();
-
-			}
-
-		}
+		signals.objectChanged.dispatch( selected );
 
 	};
 
@@ -205,6 +145,8 @@ var Viewport = function ( signals ) {
 
 			}
 
+			controls.enabled = false;
+
 			render();
 
 		}
@@ -244,7 +186,13 @@ var Viewport = function ( signals ) {
 
 	signals.modifierAxisChanged.add( function ( axis ) {
 
-		modifierAxis.copy( axis );
+		transformControls.modifierAxis.copy( axis );
+
+	} );
+
+	signals.snapChanged.add( function ( dist ) {
+
+		transformControls.snapDist = dist;
 
 	} );
 
@@ -348,7 +296,7 @@ var Viewport = function ( signals ) {
 	signals.objectSelected.add( function ( object ) {
 
 		selectionBox.visible = false;
-		selectionAxis.visible = false;
+		transformControls.detatch();
 
 		if ( object !== null ) {
 
@@ -359,11 +307,10 @@ var Viewport = function ( signals ) {
 
 			}
 
-			selectionAxis.matrixWorld = object.matrixWorld;
-			selectionAxis.visible = true;
-
 			selected = object;
 
+			if ( !(selected instanceof THREE.PerspectiveCamera) ) transformControls.attatch(object);
+
 		}
 
 		render();
@@ -636,4 +583,4 @@ var Viewport = function ( signals ) {
 
 	return container;
 
-}
+}

+ 805 - 0
examples/js/controls/TransformControls.js

@@ -0,0 +1,805 @@
+/**
+ * @author arodic / https://github.com/arodic
+ */
+
+ // "use strict";
+
+THREE.TransformControls = function ( camera, domElement ) {
+
+	// TODO: Choose a better fitting intersection plane when looking at grazing angles
+	// TODO: Make better mapping for scale
+	// TODO: ADD RXYZ contol
+	// TODO: fix flickering
+	// TODO: make everything work with hierarchies
+
+	this.camera = camera;
+	this.domElement = ( domElement !== undefined ) ? domElement : document;
+
+	this.active = false;
+	this.mode = 'rotate';
+	this.space = 'local';
+
+	this.snapDist = null;
+  this.modifierAxis = new THREE.Vector3( 1, 1, 1 );
+	
+	var scope = this;
+
+	var showPickers = false; // debug
+
+	var ray = new THREE.Raycaster();
+	var projector = new THREE.Projector();
+	var pointerVector = new THREE.Vector3();
+	var intersect, planeIntersect;
+
+	var offset = new THREE.Vector3();
+	var localOffset = new THREE.Vector3();
+	var point = new THREE.Vector3();
+	var localPoint = new THREE.Vector3();
+	var rotation = new THREE.Vector3();
+	var scale = 1;
+	var offsetRotation = new THREE.Vector3();
+	var rotationMatrix = new THREE.Matrix4();
+	var lookAtMatrix = new THREE.Matrix4();
+	
+	var tempMatrix = new THREE.Matrix4();
+	var tempVector = new THREE.Vector3();
+	var tempQuaternion = new THREE.Quaternion();
+	var unitX = new THREE.Vector3( 1, 0, 0 );
+	var unitY = new THREE.Vector3( 0, 1, 0 );
+	var unitZ = new THREE.Vector3( 0, 0, 1 );
+
+	var quaternionXYZ = new THREE.Quaternion();
+	var quaternionX = new THREE.Quaternion();
+	var quaternionY = new THREE.Quaternion();
+	var quaternionZ = new THREE.Quaternion();
+
+	var oldMatrix = new THREE.Matrix4();
+	var oldPosition = new THREE.Vector3();
+	var oldRotation = new THREE.Vector3();
+	var oldScale = new THREE.Vector3();
+
+	var objPosition = new THREE.Vector3();
+	var objRotation = new THREE.Vector3();
+	var objScale = new THREE.Vector3();
+	var camPosition = new THREE.Vector3();
+	var camRotation = new THREE.Vector3();
+	var camDistance;
+
+	var displayAxes = {};
+	var pickerAxes = {};
+	var intersectionPlanes = {};
+	var intersectionPlaneList = ['XY','YZ','XZ','XYZE'];
+	var currentPlane = 'XY';
+
+	var object, name;
+
+	// gizmo geometry
+	{
+
+		this.gizmo = new THREE.Object3D();
+
+		displayAxes["translate"] = new THREE.Object3D();
+		displayAxes["rotate"] = new THREE.Object3D();
+		displayAxes["scale"] = new THREE.Object3D();
+		this.gizmo.add( displayAxes["translate"] );
+		this.gizmo.add( displayAxes["rotate"] );
+		this.gizmo.add( displayAxes["scale"] );
+		
+		pickerAxes["translate"] = new THREE.Object3D();
+		pickerAxes["rotate"] = new THREE.Object3D();
+		pickerAxes["scale"] = new THREE.Object3D();
+		this.gizmo.add( pickerAxes["translate"] );
+		this.gizmo.add( pickerAxes["rotate"] );
+		this.gizmo.add( pickerAxes["scale"] );
+
+		var HandleMaterial = function ( color ) {
+			var material = new THREE.MeshBasicMaterial();
+			material.side = THREE.DoubleSide;
+			material.transparent = true;
+			material.depthTest = false;
+			material.depthWrite = false;
+			material.color.setRGB( color[0], color[1], color[2] );
+			material.opacity = color[3];
+			return material;
+		}
+
+		var LineMaterial = function ( color ) {
+			var material = new THREE.LineBasicMaterial();
+			material.side = THREE.DoubleSide;
+			material.transparent = true;
+			material.depthTest = false;
+			material.depthWrite = false;
+			material.color.setRGB( color[0], color[1], color[2] );
+			material.opacity = color[3];
+			return material;
+		}
+
+		// var CutoffMaterial = function () {
+		// 	var material = new THREE.MeshBasicMaterial();
+		// 	material.side = THREE.DoubleSide;
+		// 	material.transparent = true;
+		// 	material.depthTest = false;
+		// 	material.depthWrite = true;
+		// 	material.color.setRGB( 0 ,0 ,0 );
+		// 	material.opacity = 0.1;
+		// 	return material;
+		// }
+
+		// materials by color
+		var white = [1,1,1,0.2];
+		var gray = [0.5,0.5,0.5,1];
+		var red = [1,0,0,1];
+		var green = [0,1,0,1];
+		var blue = [0,0,1,1];
+		var cyan = [0,1,1,0.2];
+		var magenta = [1,0,1,0.2];
+		var yellow = [1,1,0,0.2];
+
+		var mesh;
+
+		// line axes
+
+		mesh = new THREE.Line( new THREE.Geometry(), LineMaterial( red ) );
+		mesh.geometry.vertices = [ new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 1, 0, 0 ) ];
+		displayAxes['translate'].add( mesh );
+		displayAxes['scale'].add( mesh.clone() );
+
+		mesh = new THREE.Line( new THREE.Geometry(), LineMaterial( green ) );
+		mesh.geometry.vertices = [ new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 1, 0 ) ];
+		displayAxes['translate'].add( mesh );
+		displayAxes['scale'].add( mesh.clone() );
+
+		mesh = new THREE.Line( new THREE.Geometry(), LineMaterial( blue ) );
+		mesh.geometry.vertices = [ new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, 1 ) ];
+		displayAxes['translate'].add( mesh );
+		displayAxes['scale'].add( mesh.clone() );
+
+		// Translate handles
+
+		mesh = new THREE.Mesh( new THREE.OctahedronGeometry( 0.1, 0 ), HandleMaterial( white ) );
+		mesh.name = 'TXYZ';
+		displayAxes['translate'].add( mesh );
+		pickerAxes['translate'].add( mesh.clone() );
+
+		mesh = new THREE.Mesh( new THREE.PlaneGeometry( 0.2, 0.2 ), HandleMaterial( yellow ) );
+		mesh.position.set( 0.1, 0.1, 0 );
+		bakeTransformations( mesh );
+		mesh.name = 'TXY';
+		displayAxes['translate'].add( mesh );
+		pickerAxes['translate'].add( mesh.clone() );
+
+		mesh = new THREE.Mesh( new THREE.PlaneGeometry( 0.2, 0.2 ), HandleMaterial( cyan ) );
+		mesh.position.set( 0, 0.1, 0.1 );
+		mesh.rotation.y = Math.PI/2;
+		bakeTransformations( mesh );
+		mesh.name = 'TYZ';
+		displayAxes['translate'].add( mesh );
+		pickerAxes['translate'].add( mesh.clone() );
+		
+		mesh = new THREE.Mesh( new THREE.PlaneGeometry( 0.2, 0.2 ), HandleMaterial( magenta ) );
+		mesh.position.set( 0.1, 0, 0.1 );
+		mesh.rotation.x = Math.PI/2;
+		bakeTransformations( mesh );
+		mesh.name = 'TXZ';
+		displayAxes['translate'].add( mesh );
+		pickerAxes['translate'].add( mesh.clone() );
+
+		mesh = new THREE.Mesh( new THREE.CylinderGeometry( 0, 0.05, 0.2, 4, 1, true ), HandleMaterial( red ) );
+		mesh.position.x = 0.9;
+		mesh.rotation.z = -Math.PI/2;
+		bakeTransformations( mesh );
+		mesh.name = 'TX';
+		displayAxes['translate'].add( mesh );
+
+		mesh = new THREE.Mesh( new THREE.CylinderGeometry( 0, 0.05, 0.2, 4, 1, true ), HandleMaterial( green ) );
+		mesh.position.y = 0.9;
+		bakeTransformations( mesh );
+		mesh.name = 'TY';
+		displayAxes['translate'].add( mesh );
+
+		mesh = new THREE.Mesh( new THREE.CylinderGeometry( 0, 0.05, 0.2, 4, 1, true ), HandleMaterial( blue ) );
+		mesh.position.z = 0.9;
+		mesh.rotation.x = Math.PI/2;
+		bakeTransformations( mesh );
+		mesh.name = 'TZ';
+		displayAxes['translate'].add( mesh );
+
+		mesh = new THREE.Mesh( new THREE.CylinderGeometry( 0.04, 0.04, 0.8, 4, 1, false ), HandleMaterial( red ) );
+		mesh.position.x = 0.6;
+		mesh.rotation.z = -Math.PI/2;
+		bakeTransformations( mesh );
+		mesh.name = 'TX';
+		pickerAxes['translate'].add( mesh );
+
+		mesh = new THREE.Mesh( new THREE.CylinderGeometry( 0.04, 0.04, 0.8, 4, 1, false ), HandleMaterial( green ) );
+		mesh.position.y = 0.6;
+		bakeTransformations( mesh );
+		mesh.name = 'TY';
+		pickerAxes['translate'].add( mesh );
+
+		mesh = new THREE.Mesh( new THREE.CylinderGeometry( 0.04, 0.04, 0.8, 4, 1, false ), HandleMaterial( blue ) );
+		mesh.position.z = 0.6;
+		mesh.rotation.x = Math.PI/2;
+		bakeTransformations( mesh );
+		mesh.name = 'TZ';
+		pickerAxes['translate'].add( mesh );
+
+		// scale manipulators
+
+		mesh = new THREE.Mesh( new THREE.CubeGeometry( 0.1, 0.1, 0.1 ), HandleMaterial( white ) );
+		mesh.name = 'SXYZ';
+		displayAxes['scale'].add( mesh );
+		pickerAxes['scale'].add( mesh.clone() );
+
+		mesh = new THREE.Mesh( new THREE.CubeGeometry( 0.1, 0.1, 0.1 ), HandleMaterial( red ) );
+		mesh.position.set( 1, 0, 0 );
+		bakeTransformations( mesh );
+		mesh.name = 'SX';
+		displayAxes['scale'].add( mesh );
+		pickerAxes['scale'].add( mesh.clone() );
+
+		mesh = new THREE.Mesh( new THREE.CubeGeometry( 0.1, 0.1, 0.1 ), HandleMaterial( green ) );
+		mesh.position.set( 0, 1, 0 );
+		bakeTransformations( mesh );
+		mesh.name = 'SY';
+		displayAxes['scale'].add( mesh );
+		pickerAxes['scale'].add( mesh.clone() );
+
+		mesh = new THREE.Mesh( new THREE.CubeGeometry( 0.1, 0.1, 0.1 ), HandleMaterial( blue ) );
+		mesh.position.set( 0, 0, 1 );
+		bakeTransformations( mesh );
+		mesh.name = 'SZ';
+		displayAxes['scale'].add( mesh );
+		pickerAxes['scale'].add( mesh.clone() );
+
+		// rotate manipulators
+
+		// mesh = new THREE.Mesh( new THREE.PlaneGeometry( 2.3, 2.3, 5, 5 ), CutoffMaterial() );
+		// mesh.name = 'CUTOFFE';
+		// displayAxes['rotate'].add( mesh );
+
+		var Circle = function( radius, facing, arc ) {
+			
+			var geometry = new THREE.Geometry();
+			arc = arc ? arc : 1;
+			for ( var i = 0; i <= 64 * arc; ++i ) {
+				if ( facing == 'x' ) geometry.vertices.push( new THREE.Vector3( 0, Math.cos( i / 32 * Math.PI ), Math.sin( i / 32 * Math.PI ) ).multiplyScalar(radius) );
+				if ( facing == 'y' ) geometry.vertices.push( new THREE.Vector3( Math.cos( i / 32 * Math.PI ), 0, Math.sin( i / 32 * Math.PI ) ).multiplyScalar(radius) );
+				if ( facing == 'z' ) geometry.vertices.push( new THREE.Vector3( Math.sin( i / 32 * Math.PI ), Math.cos( i / 32 * Math.PI ), 0 ).multiplyScalar(radius) );
+			}
+
+			return geometry;
+		}
+
+		mesh = new THREE.Line( Circle( 1, 'x', 0.5 ), LineMaterial( red ) );
+		mesh.name = 'RX';
+		displayAxes['rotate'].add( mesh );
+
+		mesh = new THREE.Line( Circle( 1, 'y', 0.5 ), LineMaterial( green ) );
+		mesh.name = 'RY';
+		displayAxes['rotate'].add( mesh );
+
+		mesh = new THREE.Line( Circle( 1, 'z', 0.5 ), LineMaterial( blue ) );
+		mesh.name = 'RZ';
+		displayAxes['rotate'].add( mesh );
+
+		mesh = new THREE.Line( Circle( 1, 'z' ), LineMaterial( gray ) );
+		mesh.name = 'RXYZE';
+		displayAxes['rotate'].add( mesh );
+
+		mesh = new THREE.Line( Circle( 1.1, 'z' ), LineMaterial( [1,1,0,1] ) );
+		mesh.name = 'RE';
+		displayAxes['rotate'].add( mesh );
+
+		mesh = new THREE.Mesh( new THREE.TorusGeometry( 1, 0.05, 4, 12 ), HandleMaterial( cyan ) );
+		mesh.rotation.y = -Math.PI/2;
+		bakeTransformations( mesh );
+		mesh.name = 'RX';
+		pickerAxes['rotate'].add( mesh );
+
+		mesh = new THREE.Mesh( new THREE.TorusGeometry( 1, 0.05, 4, 12 ), HandleMaterial( magenta ) );
+		mesh.rotation.x = -Math.PI/2;
+		bakeTransformations( mesh );
+		mesh.name = 'RY';
+		pickerAxes['rotate'].add( mesh );
+
+		mesh = new THREE.Mesh( new THREE.TorusGeometry( 1, 0.05, 4, 12 ), HandleMaterial( yellow ) );
+		mesh.name = 'RZ';
+		pickerAxes['rotate'].add( mesh );
+
+		mesh = new THREE.Mesh( new THREE.SphereGeometry( 0.95, 12, 12 ), HandleMaterial( white ) );
+		mesh.name = 'RXYZ';
+		pickerAxes['rotate'].add( mesh );
+
+		mesh = new THREE.Mesh( new THREE.TorusGeometry( 1.12, 0.07, 4, 12 ), HandleMaterial( yellow ) );
+		mesh.name = 'RE';
+		pickerAxes['rotate'].add( mesh );
+
+		mesh = null;
+
+	}
+
+	// intersection planes
+	{
+		
+		var planes = new THREE.Object3D();
+		this.gizmo.add(planes);
+
+		for ( var i in intersectionPlaneList ){
+
+			intersectionPlanes[intersectionPlaneList[i]] = new THREE.Mesh( new THREE.PlaneGeometry( 500, 500, 50, 50 ), new THREE.MeshBasicMaterial( { wireframe: true } ) );
+			intersectionPlanes[intersectionPlaneList[i]].material.side = THREE.DoubleSide;
+			intersectionPlanes[intersectionPlaneList[i]].name = intersectionPlaneList[i];
+			intersectionPlanes[intersectionPlaneList[i]].visible = false;
+			planes.add(intersectionPlanes[intersectionPlaneList[i]]);
+
+		}
+
+		intersectionPlanes['YZ'].rotation.set( 0, Math.PI/2, 0 );
+		intersectionPlanes['XZ'].rotation.set( -Math.PI/2, 0, 0 );
+		bakeTransformations(intersectionPlanes['YZ']);
+		bakeTransformations(intersectionPlanes['XZ']);
+
+	}
+
+
+  this.attatch = function ( object ) {
+
+  	this.object = object;
+	 	this.updateGizmo();
+	 	this.updateMode();
+		
+		this.domElement.addEventListener( 'mousedown', onMouseDown, false );
+		document.addEventListener( 'keydown', onKeyDown, false );
+  
+  }
+
+  this.detatch = function ( object ) {
+
+	 	this.hide();
+
+		this.domElement.removeEventListener( 'mousedown', onMouseDown, false );
+		document.removeEventListener( 'keydown', onKeyDown, false );
+  
+  }
+
+  this.updateGizmo = function() {
+
+		objPosition.getPositionFromMatrix(this.object.matrixWorld);
+		objRotation.setEulerFromRotationMatrix(tempMatrix.extractRotation(this.object.matrixWorld));
+		objScale.getScaleFromMatrix(this.object.matrixWorld);
+
+		camPosition.getPositionFromMatrix(this.camera.matrixWorld);
+		camRotation.setEulerFromRotationMatrix(tempMatrix.extractRotation(this.camera.matrixWorld));
+
+		camDistance = objPosition.distanceTo( camPosition );
+		this.gizmo.position.copy(objPosition)
+		this.gizmo.scale.set( camDistance/6, camDistance/6, camDistance/6 );
+
+
+		for ( i in this.gizmo.children ) {
+			for ( j in this.gizmo.children[i].children ) {
+
+				object = this.gizmo.children[i].children[j];
+				name = object.name;
+
+				if ( name.search('E') != -1 ){
+					
+					lookAtMatrix.lookAt( camPosition, objPosition, tempVector.set( 0, 1, 0 ));
+					object.rotation.setEulerFromRotationMatrix( lookAtMatrix );
+
+				} else {
+
+					var eye = new THREE.Vector3().copy(camPosition).sub(objPosition).normalize();
+
+					if ( this.space == 'local' ) {
+
+						tempQuaternion = new THREE.Quaternion().setFromEuler(objRotation);
+
+						tempMatrix.makeRotationFromQuaternion( tempQuaternion ).getInverse( tempMatrix );
+						eye.applyProjection( tempMatrix );
+
+						if ( name == 'RX' ) {
+							quaternionX.setFromAxisAngle( unitX, Math.atan2( -eye.y, eye.z ) );
+						  tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX );
+						}
+
+						if ( name == 'RY' ) {
+							quaternionY.setFromAxisAngle( unitY, Math.atan2( eye.x, eye.z ) );
+						  tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionY );
+						}
+
+						if ( name == 'RZ' ) {
+							quaternionZ.setFromAxisAngle( unitZ, Math.atan2( eye.y, eye.x ) );
+						  tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionZ );
+						}
+						
+						object.rotation.setEulerFromQuaternion( tempQuaternion );
+
+					} else if ( this.space == 'world' ) {
+
+						object.rotation.set( 0, 0, 0 );
+
+						if ( name == 'RX' ) {
+							object.rotation.setX( Math.atan2( -eye.y, eye.z ) );
+						}
+
+						if ( name == 'RY' ) {
+							object.rotation.setY( Math.atan2( eye.x, eye.z ) );
+						}
+
+						if ( name == 'RZ' ) {
+							object.rotation.setZ( Math.atan2( eye.y, eye.x ) );
+						}
+
+					}
+
+				}
+
+			}
+		}
+
+		signals.objectChanged.dispatch( this.object );
+ 
+  }
+
+  this.hide = function () {
+
+	 	for ( i in displayAxes ) {
+
+		 	for ( j in displayAxes[i].children ) {
+
+		 		displayAxes[i].children[j].visible = false;
+
+		 	}
+
+	 	}
+
+	 	for ( i in pickerAxes ) {
+
+		 	for ( j in pickerAxes[i].children ) {
+
+		 		pickerAxes[i].children[j].visible = false;
+
+		 	}
+
+	 	}
+
+  }
+
+  this.updateMode = function() {
+
+  	this.hide();
+
+  	if ( scope.mode == 'scale' ) scope.space = 'local';
+
+	 	for ( i in displayAxes[this.mode].children ) {
+
+ 			displayAxes[this.mode].children[i].visible = true;
+	 		
+	 	}
+
+	 	for ( i in pickerAxes[this.mode].children ) {
+
+ 			pickerAxes[this.mode].children[i].visible = showPickers;
+	 		
+	 	}
+
+	 	scope.updateGizmo();
+
+  }
+
+  this.setIntersectionPlane = function () {
+
+  	if ( this.active.search("X") != -1 || this.active.search("Y") != -1 ) {
+
+  		currentPlane = 'XY';
+
+  	} 
+
+  	if ( this.active.search("Z") != -1 ) {
+
+  		currentPlane = 'YZ';
+
+  	}
+
+  	if ( this.active.search("XZ") != -1 ) {
+
+  		currentPlane = 'XZ';
+
+  	} 
+
+  	if ( this.active.search("XYZ") != -1 ) {
+
+  		currentPlane = 'XYZE';
+
+  	}
+
+   	if ( this.active.search("RX") != -1 ) {
+
+  		currentPlane = 'YZ';
+
+  	}
+
+  	if ( this.active.search("RY") != -1 ) {
+
+  		currentPlane = 'XZ';
+
+  	} 
+
+  	if ( this.active.search("RZ") != -1 ) {
+
+  		currentPlane = 'XY';
+
+  	}
+
+  	// intersectionPlanes[currentPlane].visible = true;
+  	scope.updateGizmo();
+
+  }
+
+  function onMouseDown( event ) {
+
+		event.preventDefault();
+
+		scope.domElement.focus();
+
+		scope.updateGizmo();
+
+		if ( event.button === 0 ) {
+
+			scope.updateGizmo();
+
+			intersect = intersectObjects( pickerAxes[scope.mode].children );
+
+			if ( intersect[ 0 ] ) scope.active = intersect[ 0 ].object.name;
+
+			if ( scope.active ) {
+
+				scope.setIntersectionPlane();
+
+				oldMatrix.copy( scope.object.matrixWorld );
+			  rotationMatrix.extractRotation( oldMatrix );
+
+				planeIntersect = intersectObjects( [intersectionPlanes[currentPlane]] );
+
+				offset.copy( planeIntersect[ 0 ].point );
+
+			  oldPosition.copy(scope.object.position);
+			  oldRotation.copy(objRotation);
+			  oldScale.copy(objScale);
+
+				if ( scope.mode == 'rotate' && scope.space == 'world' ) offset.sub( objPosition );
+
+			}
+
+		}
+
+		document.addEventListener( 'mousemove', onMouseMove, false );
+		document.addEventListener( 'mouseup', onMouseUp, false );
+		document.addEventListener( 'mouseout', onMouseUp, false );
+
+	};
+
+	function onMouseMove( event ) {
+
+		if ( scope.active ) {
+
+			planeIntersect = intersectObjects( [intersectionPlanes[currentPlane]] );
+
+			if ( planeIntersect[ 0 ] ) point.copy( planeIntersect[ 0 ].point );
+
+			if ( point ) {
+
+				localPoint = worldToLocal( point, oldMatrix );
+				localOffset = worldToLocal( offset, oldMatrix );
+
+				if ( ( scope.mode == 'translate' ) && scope.active.search("T") != -1 ) {
+
+					if ( scope.space == 'local' ) {
+
+						localPoint.multiply(objScale);
+						localOffset.multiply(objScale);
+						localPoint.sub( localOffset );
+
+						if ( scope.active.search("X") == -1 || scope.modifierAxis.x != 1 ) localPoint.x = 0;
+						if ( scope.active.search("Y") == -1 || scope.modifierAxis.y != 1 ) localPoint.y = 0;
+						if ( scope.active.search("Z") == -1 || scope.modifierAxis.z != 1 ) localPoint.z = 0;
+						if ( scope.active.search("XYZ") != -1 ) localPoint.set( 0, 0, 0 );
+
+						localPoint.applyMatrix4( rotationMatrix );
+						scope.object.position.copy( oldPosition );
+						scope.object.position.add( localPoint );
+
+					} 
+
+					if ( scope.space == 'world' || scope.active.search("XYZ") != -1 ) {
+
+						point.sub( offset );
+
+						if ( scope.active.search("X") == -1 || scope.modifierAxis.x != 1 ) point.x = 0;
+						if ( scope.active.search("Y") == -1 || scope.modifierAxis.y != 1 ) point.y = 0;
+						if ( scope.active.search("Z") == -1 || scope.modifierAxis.z != 1 ) point.z = 0;
+
+						scope.object.position.copy( oldPosition );
+						scope.object.position.add( point );
+						
+						if ( scope.snapDist ) {
+							if ( scope.active.search("X") != -1 ) scope.object.position.x = Math.round( scope.object.position.x / scope.snapDist ) * scope.snapDist;
+			        if ( scope.active.search("Y") != -1 ) scope.object.position.y = Math.round( scope.object.position.y / scope.snapDist ) * scope.snapDist;
+			        if ( scope.active.search("Z") != -1 ) scope.object.position.z = Math.round( scope.object.position.z / scope.snapDist ) * scope.snapDist;
+						}
+
+					}
+
+				}
+
+				if ( ( scope.mode == 'scale') && scope.active.search("S") != -1 ) {
+
+					if ( scope.space == 'local' ) {
+
+						point.sub( offset );
+
+						if ( scope.active.search("X") == -1 || scope.modifierAxis.x != 1 ) point.x = 0;
+						if ( scope.active.search("Y") == -1 || scope.modifierAxis.y != 1 ) point.y = 0;
+						if ( scope.active.search("Z") == -1 || scope.modifierAxis.z != 1 ) point.z = 0;
+
+						localPoint.sub( localOffset );
+
+						if ( scope.active.search("X") == -1 || scope.modifierAxis.x != 1 ) localPoint.x = 0;
+						if ( scope.active.search("Y") == -1 || scope.modifierAxis.y != 1 ) localPoint.y = 0;
+						if ( scope.active.search("Z") == -1 || scope.modifierAxis.z != 1 ) localPoint.z = 0;
+
+						if ( scope.active.search("XYZ") != -1) {
+							
+							scale = 1 + ( ( point.x + point.y ) / 10 );
+
+							scope.object.scale.x = oldScale.x * scale;
+							scope.object.scale.y = oldScale.y * scale;
+							scope.object.scale.z = oldScale.z * scale;
+
+						} else {
+
+							// TODO: add more intuitive mapping
+							scope.object.scale.x = oldScale.x + localPoint.x/30;
+							scope.object.scale.y = oldScale.y + localPoint.y/30;
+							scope.object.scale.z = oldScale.z + localPoint.z/30;
+
+						}
+
+					} else if ( scope.space == 'world' ) {
+
+						// Cannot scale in world space. This would require geometry manipulation or another transformation matrix.
+
+					}
+
+				}
+
+				if ( ( scope.mode == 'rotate' ) && scope.active.search("R") != -1 ) {
+
+					if ( scope.space == 'local' ) {
+
+						offsetRotation.set( Math.atan2( localOffset.z, localOffset.y ), Math.atan2( localOffset.x, localOffset.z ), Math.atan2( localOffset.y, localOffset.x ) );
+						rotation.set( Math.atan2( localPoint.z, localPoint.y ), Math.atan2( localPoint.x, localPoint.z ), Math.atan2( localPoint.y, localPoint.x ) );
+
+						quaternionXYZ.setFromRotationMatrix( rotationMatrix );
+						quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x );
+						quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y );
+						quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z );
+
+						if ( scope.active.search("X") != -1 && scope.modifierAxis.x === 1 ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionX );
+						if ( scope.active.search("Y") != -1 && scope.modifierAxis.y === 1 ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionY );
+						if ( scope.active.search("Z") != -1 && scope.modifierAxis.z === 1 ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionZ );
+						
+						scope.object.rotation.setEulerFromQuaternion( quaternionXYZ );
+
+					}  else if ( scope.space == 'world' ) {
+
+						point.sub( objPosition );
+
+						offsetRotation.set( Math.atan2( offset.z, offset.y ), Math.atan2( offset.x, offset.z ), Math.atan2( offset.y, offset.x ) );
+						rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) );
+
+						quaternionXYZ.setFromRotationMatrix( rotationMatrix );
+						
+						tempQuaternion = new THREE.Quaternion().setFromEuler( new THREE.Vector3( 0, 1, 0 ), 0 );
+						
+						quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x );
+						quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y );
+						quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z );
+
+						if ( scope.active.search("X") != -1 && scope.modifierAxis.x === 1 ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX );
+						if ( scope.active.search("Y") != -1 && scope.modifierAxis.y === 1 ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionY );
+						if ( scope.active.search("Z") != -1 && scope.modifierAxis.z === 1 ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionZ );
+
+						tempQuaternion.multiplyQuaternions( tempQuaternion,quaternionXYZ);
+						
+						scope.object.rotation.setEulerFromQuaternion( tempQuaternion);
+
+					}
+
+				}
+
+			}
+
+		}
+
+		signals.objectChanged.dispatch( scope.object );
+		scope.updateGizmo();
+
+	}
+
+	function onMouseUp( event ) {
+
+		scope.active = false;
+
+		document.removeEventListener( 'mousemove', onMouseMove, false );
+		document.removeEventListener( 'mouseup', onMouseUp, false );
+
+	}
+
+	function onKeyDown( event ) {
+
+ 
+		if ( event.keyCode == 87 ) { // W
+
+			if ( scope.mode == 'translate' ) scope.space = ( scope.space == 'world' ) ? 'local' : 'world';
+			scope.mode = 'translate';
+
+		}
+
+		if ( event.keyCode == 69 ) { // E
+
+			if ( scope.mode == 'rotate' ) scope.space = ( scope.space == 'world' ) ? 'local' : 'world';			
+			scope.mode = 'rotate';
+
+		}
+
+		if ( event.keyCode == 82 ) { // R
+			
+			scope.mode = 'scale';
+			scope.space = 'local';
+
+		}
+
+		scope.updateMode();
+
+	}
+
+	function intersectObjects( objects ) {
+
+		pointerVector.set(
+			( event.layerX / scope.domElement.offsetWidth ) * 2 - 1,
+			- ( event.layerY / scope.domElement.offsetHeight ) * 2 + 1,
+			0.5
+		);
+
+		projector.unprojectVector( pointerVector, scope.camera );
+		ray.set( camPosition, pointerVector.sub( camPosition ).normalize() );
+	
+		return ray.intersectObjects( objects, true );
+	
+	}
+
+	function worldToLocal( point, objectMatrix ) {
+
+		tempMatrix.getInverse( objectMatrix );
+		return point.clone().applyMatrix4( tempMatrix );
+
+	}
+
+	function bakeTransformations( object ) {
+
+		var tempGeometry = new THREE.Geometry();
+		var tempMatrix = new THREE.Matrix4().identity();
+		THREE.GeometryUtils.merge( tempGeometry, object );
+		object.setGeometry( tempGeometry );
+		object.position.set( 0, 0, 0 );
+		object.rotation.set( 0, 0, 0 );
+		object.scale.set( 1, 1, 1 );
+
+	}
+
+};
+
+THREE.TransformControls.prototype = Object.create( THREE.EventDispatcher.prototype );