2
0

TransformControls.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. /**
  2. * @author arodic / https://github.com/arodic
  3. */
  4. THREE.TransformControls = function ( camera, domElement ) {
  5. // TODO: Make non-uniform scale and rotate play nice in hierarchies
  6. // TODO: prevent infinite horizon translate
  7. THREE.Object3D.call( this );
  8. domElement = ( domElement !== undefined ) ? domElement : document;
  9. this.visible = false;
  10. var _gizmo = new THREE.TransformControlsGizmo();
  11. this.add( _gizmo );
  12. var _plane = new THREE.TransformControlsPlane();
  13. this.add( _plane );
  14. var scope = this;
  15. defineProperty( "camera", camera );
  16. defineProperty( "object", undefined );
  17. defineProperty( "axis", null );
  18. defineProperty( "mode", "translate" );
  19. defineProperty( "translationSnap", null );
  20. defineProperty( "rotationSnap", null );
  21. defineProperty( "space", "world" );
  22. defineProperty( "size", 1 );
  23. scope.dragging = false;
  24. var changeEvent = { type: "change" };
  25. var mouseDownEvent = { type: "mouseDown" };
  26. var mouseUpEvent = { type: "mouseUp", mode: scope.mode };
  27. var objectChangeEvent = { type: "objectChange" };
  28. var ray = new THREE.Raycaster();
  29. var pointerVector = new THREE.Vector2();
  30. var point = new THREE.Vector3();
  31. var offset = new THREE.Vector3();
  32. var rotation = new THREE.Vector3();
  33. var offsetRotation = new THREE.Vector3();
  34. var lookAtMatrix = new THREE.Matrix4();
  35. var _eye = new THREE.Vector3();
  36. var _tempMatrix = new THREE.Matrix4();
  37. var _tempVector = new THREE.Vector3();
  38. var _tempQuaternion = new THREE.Quaternion();
  39. var _unitX = new THREE.Vector3( 1, 0, 0 );
  40. var _unitY = new THREE.Vector3( 0, 1, 0 );
  41. var _unitZ = new THREE.Vector3( 0, 0, 1 );
  42. var _identityEuler = new THREE.Euler();
  43. // var oldPosition = new THREE.Vector3();
  44. // var oldScale = new THREE.Vector3();
  45. // var oldRotationMatrix = new THREE.Matrix4();
  46. var _worldRotationStart = new THREE.Quaternion();
  47. var _worldPositionStart = new THREE.Vector3();
  48. var _worldPointStart = new THREE.Vector3();
  49. var _localPointStart = new THREE.Vector3();
  50. var _localScale = new THREE.Vector3();
  51. var _localPoint = new THREE.Vector3();
  52. var _worldPoint = new THREE.Vector3();
  53. var _worldShift = new THREE.Vector3();
  54. var _localShift = new THREE.Vector3();
  55. var _worldCross = new THREE.Vector3();
  56. var _localCross = new THREE.Vector3();
  57. var _worldQuaternion = new THREE.Quaternion();
  58. var _localQuaternion = new THREE.Quaternion();
  59. var _worldPosition = new THREE.Vector3();
  60. var _worldRotation = new THREE.Euler();
  61. var alignVector = new THREE.Vector3();
  62. var _positionStart = new THREE.Vector3();
  63. var _quaternionStart = new THREE.Quaternion();
  64. var _scaleStart = new THREE.Vector3();
  65. domElement.addEventListener( "mousedown", onPointerDown, false );
  66. domElement.addEventListener( "touchstart", onPointerDown, false );
  67. domElement.addEventListener( "mousemove", onPointerHover, false );
  68. domElement.addEventListener( "touchmove", onPointerHover, false );
  69. domElement.addEventListener( "mousemove", onPointerMove, false );
  70. domElement.addEventListener( "touchmove", onPointerMove, false );
  71. domElement.addEventListener( "mouseup", onPointerUp, false );
  72. domElement.addEventListener( "mouseleave", onPointerUp, false );
  73. domElement.addEventListener( "mouseout", onPointerUp, false );
  74. domElement.addEventListener( "touchend", onPointerUp, false );
  75. domElement.addEventListener( "touchcancel", onPointerUp, false );
  76. domElement.addEventListener( "touchleave", onPointerUp, false );
  77. domElement.addEventListener( "contextmenu", onContext, false );
  78. this.dispose = function () {
  79. domElement.removeEventListener( "mousedown", onPointerDown );
  80. domElement.removeEventListener( "touchstart", onPointerDown );
  81. domElement.removeEventListener( "mousemove", onPointerHover );
  82. domElement.removeEventListener( "touchmove", onPointerHover );
  83. domElement.removeEventListener( "mousemove", onPointerMove );
  84. domElement.removeEventListener( "touchmove", onPointerMove );
  85. domElement.removeEventListener( "mouseup", onPointerUp );
  86. domElement.removeEventListener( "mouseleave", onPointerUp );
  87. domElement.removeEventListener( "mouseout", onPointerUp );
  88. domElement.removeEventListener( "touchend", onPointerUp );
  89. domElement.removeEventListener( "touchcancel", onPointerUp );
  90. domElement.removeEventListener( "touchleave", onPointerUp );
  91. domElement.removeEventListener( "contextmenu", onContext );
  92. };
  93. this.attach = function ( object ) {
  94. this.object = object;
  95. this.visible = true;
  96. };
  97. this.detach = function () {
  98. this.object = undefined;
  99. this.visible = false;
  100. this.axis = null;
  101. };
  102. function defineProperty( propName, defaultValue ) {
  103. var propValue = defaultValue;
  104. Object.defineProperty( scope, propName, {
  105. get: function() {
  106. return propValue !== undefined ? propValue : defaultValue;
  107. },
  108. set: function( value ) {
  109. if ( propValue !== value ) {
  110. propValue = value;
  111. scope.dispatchEvent( changeEvent );
  112. }
  113. }
  114. });
  115. scope[ propName ] = defaultValue;
  116. }
  117. this.updateMatrixWorld = function () {
  118. if ( this.object === undefined ) return;
  119. if ( this.mode === 'scale') this.space = 'local';
  120. // camera.updateMatrixWorld();
  121. // this.object.updateMatrixWorld();
  122. _worldPosition.setFromMatrixPosition( this.object.matrixWorld );
  123. _worldRotation.setFromRotationMatrix( _tempMatrix.extractRotation( this.object.matrixWorld ) );
  124. this.position.copy( _worldPosition );
  125. if ( camera instanceof THREE.PerspectiveCamera ) {
  126. _eye.setFromMatrixPosition( camera.matrixWorld ).sub( _worldPosition ).normalize();
  127. } else if ( camera instanceof THREE.OrthographicCamera ) {
  128. _eye.setFromMatrixPosition( camera.matrixWorld ).normalize();
  129. }
  130. // TODO
  131. this._worldPosition = _worldPosition;
  132. this._worldRotation = _worldRotation;
  133. this._eye = _eye;
  134. var scale = _worldPosition.distanceTo( _tempVector.setFromMatrixPosition( camera.matrixWorld ) ) / 6 * this.size;
  135. this.scale.set( scale, scale, scale );
  136. THREE.Object3D.prototype.updateMatrixWorld.call( this );
  137. };
  138. function onContext( event ) {
  139. event.preventDefault();
  140. }
  141. function onPointerHover( event ) {
  142. if ( scope.object === undefined || scope.dragging === true || ( event.button !== undefined && event.button !== 0 ) ) return;
  143. var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event;
  144. var intersect = intersectObjects( pointer, _gizmo.picker[ scope.mode ].children );
  145. if ( intersect ) {
  146. scope.axis = intersect.object.name;
  147. event.preventDefault();
  148. } else {
  149. scope.axis = null;
  150. }
  151. }
  152. function onPointerDown( event ) {
  153. if ( scope.object === undefined || scope.dragging === true || ( event.button !== undefined && event.button !== 0 ) ) return;
  154. var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event;
  155. if ( pointer.button === 0 || pointer.button === undefined ) {
  156. var intersect = intersectObjects( pointer, _gizmo.picker[ scope.mode ].children );
  157. if ( intersect ) {
  158. event.preventDefault();
  159. event.stopPropagation();
  160. scope.axis = intersect.object.name;
  161. _plane.update( scope.space === "local" ? _worldRotation : _identityEuler, _eye );
  162. var planeIntersect = intersectObjects( pointer, [ _plane ] );
  163. if ( planeIntersect ) {
  164. _positionStart.copy( scope.object.position );
  165. _quaternionStart.copy( scope.object.quaternion );
  166. _scaleStart.copy( scope.object.scale );
  167. _worldRotationStart.setFromRotationMatrix( scope.object.matrixWorld );
  168. _worldPositionStart.setFromMatrixPosition( scope.object.matrixWorld );
  169. _worldPointStart.copy( planeIntersect.point ).sub( _worldPositionStart );
  170. _localPointStart.copy( _worldPointStart ).applyQuaternion( _worldRotationStart.clone().inverse() );
  171. }
  172. } else {
  173. scope.axis = null;
  174. }
  175. }
  176. scope.dragging = true;
  177. }
  178. function onPointerMove( event ) {
  179. var axis = scope.axis;
  180. var mode = scope.mode;
  181. var object = scope.object;
  182. var space = scope.space;
  183. if ( object === undefined || axis === null || scope.dragging === false || ( event.button !== undefined && event.button !== 0 ) ) return;
  184. var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event;
  185. var planeIntersect = intersectObjects( pointer, [ _plane ] );
  186. if ( planeIntersect === false ) return;
  187. event.preventDefault();
  188. event.stopPropagation();
  189. _worldPoint.copy( planeIntersect.point ).sub( _worldPositionStart );
  190. _worldShift.subVectors( _worldPoint, _worldPointStart );
  191. _localPoint.copy( _worldPoint );
  192. _localPoint.applyQuaternion( _worldRotationStart.clone().inverse() );
  193. _localShift.subVectors( _localPoint, _localPointStart );
  194. _worldCross.copy( _worldPoint ).cross( _worldPointStart );
  195. _localCross.copy( _localPoint ).cross( _localPointStart );
  196. if ( mode === 'translate' ) {
  197. if ( axis.search( 'X' ) === -1 ) {
  198. _worldShift.x = 0;
  199. _localShift.x = 0;
  200. }
  201. if ( axis.search( 'Y' ) === -1 ) {
  202. _worldShift.y = 0;
  203. _localShift.y = 0;
  204. }
  205. if ( axis.search( 'Z' ) === -1 ) {
  206. _worldShift.z = 0;
  207. _localShift.z = 0;
  208. }
  209. // Apply translate
  210. if ( space === 'local' ) {
  211. object.position.copy(_localShift).applyQuaternion( _quaternionStart );
  212. } else {
  213. object.position.copy( _worldShift );
  214. }
  215. object.position.add( _positionStart );
  216. if ( scope.translationSnap ) {
  217. if ( space === 'local' ) {
  218. object.position.applyQuaternion(_tempQuaternion.copy( _quaternionStart ).inverse() );
  219. if ( axis.search( 'X' ) !== -1 ) {
  220. object.position.x = Math.round( object.position.x / scope.translationSnap ) * scope.translationSnap;
  221. }
  222. if ( axis.search( 'Y' ) !== -1 ) {
  223. object.position.y = Math.round( object.position.y / scope.translationSnap ) * scope.translationSnap;
  224. }
  225. if ( axis.search( 'Z' ) !== -1 ) {
  226. object.position.z = Math.round( object.position.z / scope.translationSnap ) * scope.translationSnap;
  227. }
  228. object.position.applyQuaternion( _quaternionStart );
  229. }
  230. if ( space === 'world' ) {
  231. if ( object.parent ) {
  232. object.position.add( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
  233. }
  234. if ( axis.search( 'X' ) !== -1 ) {
  235. object.position.x = Math.round( object.position.x / scope.translationSnap ) * scope.translationSnap;
  236. }
  237. if ( axis.search( 'Y' ) !== -1 ) {
  238. object.position.y = Math.round( object.position.y / scope.translationSnap ) * scope.translationSnap;
  239. }
  240. if ( axis.search( 'Z' ) !== -1 ) {
  241. object.position.z = Math.round( object.position.z / scope.translationSnap ) * scope.translationSnap;
  242. }
  243. if ( object.parent ) {
  244. object.position.sub( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
  245. }
  246. }
  247. }
  248. } else if ( mode === 'scale' ) {
  249. if ( axis === 'XYZ' ) {
  250. _localScale.set( _worldShift.y / 50, _worldShift.y / 50, _worldShift.y / 50 ).addScalar( 1 );
  251. } else {
  252. _localScale.set(
  253. axis.search( 'X' ) !== -1 ? _localShift.x / 100 : 0,
  254. axis.search( 'Y' ) !== -1 ? _localShift.y / 100 : 0,
  255. axis.search( 'Z' ) !== -1 ? _localShift.z / 100 : 0
  256. ).addScalar( 1 );
  257. }
  258. // Apply scale
  259. object.scale.copy( _scaleStart ).multiply( _localScale );
  260. } else if ( mode === 'rotate' ) {
  261. if ( axis === 'E' ) {
  262. _localCross.applyQuaternion( _worldRotationStart ).normalize();
  263. direction = _localCross.dot( _eye ) < 0 ? 1 : -1;
  264. _worldQuaternion.setFromAxisAngle( _eye, _localPoint.angleTo( _localPointStart ) * direction );
  265. } else if ( axis === 'XYZE' ) {
  266. // TODO: not working
  267. // _tempVector.copy( _worldShift ).cross( _eye ).normalize();
  268. // _worldQuaternion.setFromAxisAngle( _tempVector, -2 * offset.length() );
  269. } else {
  270. var rotation = scope.space === "local" ? _worldRotation : _identityEuler;
  271. // TODO: make rotation speed relative to pointer movement in view space.
  272. var LINEAR_ROTATION_SPEED = 0.005;
  273. if ( axis === 'X' ) {
  274. alignVector.set( 1, 0, 0 ).applyEuler( rotation );
  275. if ( Math.abs( alignVector.dot( _eye ) ) > 0.3 ) {
  276. _localQuaternion.setFromAxisAngle( _unitX, _localPoint.angleTo( _localPointStart ) * ( _localCross.x > 0 ? -1 : 1 ) );
  277. _worldQuaternion.setFromAxisAngle( _unitX, _worldPoint.angleTo( _worldPointStart ) * ( _worldCross.x > 0 ? -1 : 1 ) );
  278. } else {
  279. _localQuaternion.setFromAxisAngle( _unitX, _worldPoint.sub( _worldPointStart ).dot( _unitX.clone().applyQuaternion( _worldRotationStart ).cross( _eye ) ) * LINEAR_ROTATION_SPEED );
  280. _worldQuaternion.setFromAxisAngle( _unitX, _worldPoint.sub( _worldPointStart ).dot( _unitX.clone().cross( _eye ) ) * LINEAR_ROTATION_SPEED );
  281. }
  282. } else if ( axis === 'Y' ) {
  283. alignVector.set( 0, 1, 0 ).applyEuler( rotation );
  284. if ( Math.abs( alignVector.dot( _eye ) ) > 0.3 ) {
  285. _localQuaternion.setFromAxisAngle( _unitY, _localPoint.angleTo( _localPointStart ) * ( _localCross.y > 0 ? -1 : 1 ) );
  286. _worldQuaternion.setFromAxisAngle( _unitY, _worldPoint.angleTo( _worldPointStart ) * ( _worldCross.y > 0 ? -1 : 1 ) );
  287. } else {
  288. _localQuaternion.setFromAxisAngle( _unitY, _worldPoint.sub( _worldPointStart ).dot( _unitY.clone().applyEuler( rotation ).cross( _eye ) ) * LINEAR_ROTATION_SPEED );
  289. _worldQuaternion.setFromAxisAngle( _unitY, _worldPoint.sub( _worldPointStart ).dot( _unitY.clone().cross( _eye ) ) * LINEAR_ROTATION_SPEED );
  290. }
  291. } else if ( axis === 'Z' ) {
  292. alignVector.set( 0, 0, 1 ).applyEuler( rotation );
  293. if ( Math.abs( alignVector.dot( _eye ) ) > 0.3 ) {
  294. _localQuaternion.setFromAxisAngle( _unitZ, _localPoint.angleTo( _localPointStart ) * ( _localCross.z > 0 ? -1 : 1 ) );
  295. _worldQuaternion.setFromAxisAngle( _unitZ, _worldPoint.angleTo( _worldPointStart ) * ( _worldCross.z > 0 ? -1 : 1 ) );
  296. } else {
  297. _localQuaternion.setFromAxisAngle( _unitZ, _worldPoint.sub( _worldPointStart ).dot( _unitZ.clone().applyEuler( rotation ).cross( _eye ) ) * LINEAR_ROTATION_SPEED );
  298. _worldQuaternion.setFromAxisAngle( _unitZ, _worldPoint.sub( _worldPointStart ).dot( _unitZ.clone().cross( _eye ) ) * LINEAR_ROTATION_SPEED );
  299. }
  300. }
  301. }
  302. // Apply rotate
  303. if ( axis === 'E' || axis === 'XYZE' ) {
  304. space = 'world';
  305. }
  306. if ( space === 'local' ) {
  307. object.quaternion.copy( _quaternionStart );
  308. object.quaternion.multiply( _localQuaternion );
  309. } else {
  310. object.quaternion.copy( _worldQuaternion );
  311. object.quaternion.multiply( _quaternionStart );
  312. }
  313. if ( scope.snapAngle) {
  314. // TODO: implement rotation snap
  315. }
  316. }
  317. scope.dispatchEvent( changeEvent );
  318. scope.dispatchEvent( objectChangeEvent );
  319. }
  320. function onPointerUp( event ) {
  321. event.preventDefault(); // Prevent MouseEvent on mobile
  322. if ( event.button !== undefined && event.button !== 0 ) return;
  323. if ( scope.dragging && ( scope.axis !== null ) ) {
  324. mouseUpEvent.mode = scope.mode;
  325. scope.dispatchEvent( mouseUpEvent );
  326. }
  327. scope.dragging = false;
  328. if ( 'TouchEvent' in window && event instanceof TouchEvent ) {
  329. // Force "rollover"
  330. scope.axis = null;
  331. } else {
  332. onPointerHover( event );
  333. }
  334. }
  335. function intersectObjects( pointer, objects ) {
  336. var rect = domElement.getBoundingClientRect();
  337. var x = ( pointer.clientX - rect.left ) / rect.width;
  338. var y = ( pointer.clientY - rect.top ) / rect.height;
  339. pointerVector.set( ( x * 2 ) - 1, - ( y * 2 ) + 1 );
  340. ray.setFromCamera( pointerVector, camera );
  341. var intersections = ray.intersectObjects( objects, true );
  342. return intersections[ 0 ] ? intersections[ 0 ] : false;
  343. }
  344. // TODO: depricate
  345. this.getMode = function () {
  346. return scope.mode;
  347. console.warn( 'THREE.TransformControls: getMode function has been depricated.' );
  348. };
  349. this.setMode = function ( mode ) {
  350. scope.mode = mode;
  351. console.warn( 'THREE.TransformControls: setMode function has been depricated.' );
  352. };
  353. this.setTranslationSnap = function ( translationSnap ) {
  354. scope.translationSnap = translationSnap;
  355. console.warn( 'THREE.TransformControls: setTranslationSnap function has been depricated.' );
  356. };
  357. this.setRotationSnap = function ( rotationSnap ) {
  358. scope.rotationSnap = rotationSnap;
  359. console.warn( 'THREE.TransformControls: setRotationSnap function has been depricated.' );
  360. };
  361. this.setSize = function ( size ) {
  362. scope.size = size;
  363. console.warn( 'THREE.TransformControls: setSize function has been depricated.' );
  364. };
  365. this.setSpace = function ( space ) {
  366. scope.space = space;
  367. console.warn( 'THREE.TransformControls: setSpace function has been depricated.' );
  368. };
  369. this.update = function () {
  370. console.warn( 'THREE.TransformControls: update function has been depricated.' );
  371. }
  372. };
  373. THREE.TransformControls.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), {
  374. constructor: THREE.TransformControls,
  375. isTransformControls: true
  376. } );