DragControls.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. import {
  2. Controls,
  3. Matrix4,
  4. Plane,
  5. Raycaster,
  6. Vector2,
  7. Vector3,
  8. MOUSE,
  9. TOUCH
  10. } from 'three';
  11. const _plane = new Plane();
  12. const _pointer = new Vector2();
  13. const _offset = new Vector3();
  14. const _diff = new Vector2();
  15. const _previousPointer = new Vector2();
  16. const _intersection = new Vector3();
  17. const _worldPosition = new Vector3();
  18. const _inverseMatrix = new Matrix4();
  19. const _up = new Vector3();
  20. const _right = new Vector3();
  21. let _selected = null, _hovered = null;
  22. const _intersections = [];
  23. const STATE = {
  24. NONE: - 1,
  25. PAN: 0,
  26. ROTATE: 1
  27. };
  28. /**
  29. * This class can be used to provide a drag'n'drop interaction.
  30. *
  31. * ```js
  32. * const controls = new DragControls( objects, camera, renderer.domElement );
  33. *
  34. * // add event listener to highlight dragged objects
  35. * controls.addEventListener( 'dragstart', function ( event ) {
  36. *
  37. * event.object.material.emissive.set( 0xaaaaaa );
  38. *
  39. * } );
  40. *
  41. * controls.addEventListener( 'dragend', function ( event ) {
  42. *
  43. * event.object.material.emissive.set( 0x000000 );
  44. *
  45. * } );
  46. * ```
  47. *
  48. * @augments Controls
  49. */
  50. class DragControls extends Controls {
  51. /**
  52. * Constructs a new controls instance.
  53. *
  54. * @param {Array<Object3D>} objects - An array of draggable 3D objects.
  55. * @param {Camera} camera - The camera of the rendered scene.
  56. * @param {?HTMLDOMElement} [domElement=null] - The HTML DOM element used for event listeners.
  57. */
  58. constructor( objects, camera, domElement = null ) {
  59. super( camera, domElement );
  60. /**
  61. * An array of draggable 3D objects.
  62. *
  63. * @type {Array<Object3D>}
  64. */
  65. this.objects = objects;
  66. /**
  67. * Whether children of draggable objects can be dragged independently from their parent.
  68. *
  69. * @type {boolean}
  70. * @default true
  71. */
  72. this.recursive = true;
  73. /**
  74. * This option only works if the `objects` array contains a single draggable group object.
  75. * If set to `true`, the controls does not transform individual objects but the entire group.
  76. *
  77. * @type {boolean}
  78. * @default false
  79. */
  80. this.transformGroup = false;
  81. /**
  82. * The speed at which the object will rotate when dragged in `rotate` mode.
  83. * The higher the number the faster the rotation.
  84. *
  85. * @type {number}
  86. * @default 1
  87. */
  88. this.rotateSpeed = 1;
  89. /**
  90. * The raycaster used for detecting 3D objects.
  91. *
  92. * @type {Raycaster}
  93. */
  94. this.raycaster = new Raycaster();
  95. // interaction
  96. this.mouseButtons = { LEFT: MOUSE.PAN, MIDDLE: MOUSE.PAN, RIGHT: MOUSE.ROTATE };
  97. this.touches = { ONE: TOUCH.PAN };
  98. // event listeners
  99. this._onPointerMove = onPointerMove.bind( this );
  100. this._onPointerDown = onPointerDown.bind( this );
  101. this._onPointerCancel = onPointerCancel.bind( this );
  102. this._onContextMenu = onContextMenu.bind( this );
  103. //
  104. if ( domElement !== null ) {
  105. this.connect();
  106. }
  107. }
  108. connect() {
  109. this.domElement.addEventListener( 'pointermove', this._onPointerMove );
  110. this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
  111. this.domElement.addEventListener( 'pointerup', this._onPointerCancel );
  112. this.domElement.addEventListener( 'pointerleave', this._onPointerCancel );
  113. this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
  114. this.domElement.style.touchAction = 'none'; // disable touch scroll
  115. }
  116. disconnect() {
  117. this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
  118. this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
  119. this.domElement.removeEventListener( 'pointerup', this._onPointerCancel );
  120. this.domElement.removeEventListener( 'pointerleave', this._onPointerCancel );
  121. this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );
  122. this.domElement.style.touchAction = 'auto';
  123. this.domElement.style.cursor = '';
  124. }
  125. dispose() {
  126. this.disconnect();
  127. }
  128. _updatePointer( event ) {
  129. const rect = this.domElement.getBoundingClientRect();
  130. _pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
  131. _pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1;
  132. }
  133. _updateState( event ) {
  134. // determine action
  135. let action;
  136. if ( event.pointerType === 'touch' ) {
  137. action = this.touches.ONE;
  138. } else {
  139. switch ( event.button ) {
  140. case 0:
  141. action = this.mouseButtons.LEFT;
  142. break;
  143. case 1:
  144. action = this.mouseButtons.MIDDLE;
  145. break;
  146. case 2:
  147. action = this.mouseButtons.RIGHT;
  148. break;
  149. default:
  150. action = null;
  151. }
  152. }
  153. // determine state
  154. switch ( action ) {
  155. case MOUSE.PAN:
  156. case TOUCH.PAN:
  157. this.state = STATE.PAN;
  158. break;
  159. case MOUSE.ROTATE:
  160. case TOUCH.ROTATE:
  161. this.state = STATE.ROTATE;
  162. break;
  163. default:
  164. this.state = STATE.NONE;
  165. }
  166. }
  167. getRaycaster() {
  168. console.warn( 'THREE.DragControls: getRaycaster() has been deprecated. Use controls.raycaster instead.' ); // @deprecated r169
  169. return this.raycaster;
  170. }
  171. setObjects( objects ) {
  172. console.warn( 'THREE.DragControls: setObjects() has been deprecated. Use controls.objects instead.' ); // @deprecated r169
  173. this.objects = objects;
  174. }
  175. getObjects() {
  176. console.warn( 'THREE.DragControls: getObjects() has been deprecated. Use controls.objects instead.' ); // @deprecated r169
  177. return this.objects;
  178. }
  179. activate() {
  180. console.warn( 'THREE.DragControls: activate() has been renamed to connect().' ); // @deprecated r169
  181. this.connect();
  182. }
  183. deactivate() {
  184. console.warn( 'THREE.DragControls: deactivate() has been renamed to disconnect().' ); // @deprecated r169
  185. this.disconnect();
  186. }
  187. set mode( value ) {
  188. console.warn( 'THREE.DragControls: The .mode property has been removed. Define the type of transformation via the .mouseButtons or .touches properties.' ); // @deprecated r169
  189. }
  190. get mode() {
  191. console.warn( 'THREE.DragControls: The .mode property has been removed. Define the type of transformation via the .mouseButtons or .touches properties.' ); // @deprecated r169
  192. }
  193. }
  194. function onPointerMove( event ) {
  195. const camera = this.object;
  196. const domElement = this.domElement;
  197. const raycaster = this.raycaster;
  198. if ( this.enabled === false ) return;
  199. this._updatePointer( event );
  200. raycaster.setFromCamera( _pointer, camera );
  201. if ( _selected ) {
  202. if ( this.state === STATE.PAN ) {
  203. if ( raycaster.ray.intersectPlane( _plane, _intersection ) ) {
  204. _selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
  205. }
  206. } else if ( this.state === STATE.ROTATE ) {
  207. _diff.subVectors( _pointer, _previousPointer ).multiplyScalar( this.rotateSpeed );
  208. _selected.rotateOnWorldAxis( _up, _diff.x );
  209. _selected.rotateOnWorldAxis( _right.normalize(), - _diff.y );
  210. }
  211. this.dispatchEvent( { type: 'drag', object: _selected } );
  212. _previousPointer.copy( _pointer );
  213. } else {
  214. // hover support
  215. if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) {
  216. _intersections.length = 0;
  217. raycaster.setFromCamera( _pointer, camera );
  218. raycaster.intersectObjects( this.objects, this.recursive, _intersections );
  219. if ( _intersections.length > 0 ) {
  220. const object = _intersections[ 0 ].object;
  221. _plane.setFromNormalAndCoplanarPoint( camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) );
  222. if ( _hovered !== object && _hovered !== null ) {
  223. this.dispatchEvent( { type: 'hoveroff', object: _hovered } );
  224. domElement.style.cursor = 'auto';
  225. _hovered = null;
  226. }
  227. if ( _hovered !== object ) {
  228. this.dispatchEvent( { type: 'hoveron', object: object } );
  229. domElement.style.cursor = 'pointer';
  230. _hovered = object;
  231. }
  232. } else {
  233. if ( _hovered !== null ) {
  234. this.dispatchEvent( { type: 'hoveroff', object: _hovered } );
  235. domElement.style.cursor = 'auto';
  236. _hovered = null;
  237. }
  238. }
  239. }
  240. }
  241. _previousPointer.copy( _pointer );
  242. }
  243. function onPointerDown( event ) {
  244. const camera = this.object;
  245. const domElement = this.domElement;
  246. const raycaster = this.raycaster;
  247. if ( this.enabled === false ) return;
  248. this._updatePointer( event );
  249. this._updateState( event );
  250. _intersections.length = 0;
  251. raycaster.setFromCamera( _pointer, camera );
  252. raycaster.intersectObjects( this.objects, this.recursive, _intersections );
  253. if ( _intersections.length > 0 ) {
  254. if ( this.transformGroup === true ) {
  255. // look for the outermost group in the object's upper hierarchy
  256. _selected = findGroup( _intersections[ 0 ].object );
  257. } else {
  258. _selected = _intersections[ 0 ].object;
  259. }
  260. _plane.setFromNormalAndCoplanarPoint( camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
  261. if ( raycaster.ray.intersectPlane( _plane, _intersection ) ) {
  262. if ( this.state === STATE.PAN ) {
  263. _inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
  264. _offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
  265. } else if ( this.state === STATE.ROTATE ) {
  266. // the controls only support Y+ up
  267. _up.set( 0, 1, 0 ).applyQuaternion( camera.quaternion ).normalize();
  268. _right.set( 1, 0, 0 ).applyQuaternion( camera.quaternion ).normalize();
  269. }
  270. }
  271. domElement.style.cursor = 'move';
  272. this.dispatchEvent( { type: 'dragstart', object: _selected } );
  273. }
  274. _previousPointer.copy( _pointer );
  275. }
  276. function onPointerCancel() {
  277. if ( this.enabled === false ) return;
  278. if ( _selected ) {
  279. this.dispatchEvent( { type: 'dragend', object: _selected } );
  280. _selected = null;
  281. }
  282. this.domElement.style.cursor = _hovered ? 'pointer' : 'auto';
  283. this.state = STATE.NONE;
  284. }
  285. function onContextMenu( event ) {
  286. if ( this.enabled === false ) return;
  287. event.preventDefault();
  288. }
  289. function findGroup( obj, group = null ) {
  290. if ( obj.isGroup ) group = obj;
  291. if ( obj.parent === null ) return group;
  292. return findGroup( obj.parent, group );
  293. }
  294. /**
  295. * Fires when the user drags a 3D object.
  296. *
  297. * @event DragControls#drag
  298. * @type {Object}
  299. */
  300. /**
  301. * Fires when the user has finished dragging a 3D object.
  302. *
  303. * @event DragControls#dragend
  304. * @type {Object}
  305. */
  306. /**
  307. * Fires when the pointer is moved onto a 3D object, or onto one of its children.
  308. *
  309. * @event DragControls#hoveron
  310. * @type {Object}
  311. */
  312. /**
  313. * Fires when the pointer is moved out of a 3D object.
  314. *
  315. * @event DragControls#hoveroff
  316. * @type {Object}
  317. */
  318. export { DragControls };
粤ICP备19079148号