|
|
@@ -1,14 +1,15 @@
|
|
|
import {
|
|
|
- EventDispatcher,
|
|
|
Matrix4,
|
|
|
Plane,
|
|
|
Raycaster,
|
|
|
Vector2,
|
|
|
- Vector3
|
|
|
+ Vector3,
|
|
|
+ MOUSE,
|
|
|
+ TOUCH
|
|
|
} from 'three';
|
|
|
+import { Controls } from './Controls.js';
|
|
|
|
|
|
const _plane = new Plane();
|
|
|
-const _raycaster = new Raycaster();
|
|
|
|
|
|
const _pointer = new Vector2();
|
|
|
const _offset = new Vector3();
|
|
|
@@ -21,262 +22,389 @@ const _inverseMatrix = new Matrix4();
|
|
|
const _up = new Vector3();
|
|
|
const _right = new Vector3();
|
|
|
|
|
|
-class DragControls extends EventDispatcher {
|
|
|
+let _selected = null, _hovered = null;
|
|
|
+const _intersections = [];
|
|
|
|
|
|
- constructor( _objects, _camera, _domElement ) {
|
|
|
+const STATE = {
|
|
|
+ NONE: - 1,
|
|
|
+ PAN: 0,
|
|
|
+ ROTATE: 1
|
|
|
+};
|
|
|
|
|
|
- super();
|
|
|
+class DragControls extends Controls {
|
|
|
|
|
|
- _domElement.style.touchAction = 'none'; // disable touch scroll
|
|
|
+ constructor( objects, camera, domElement = null ) {
|
|
|
|
|
|
- let _selected = null, _hovered = null;
|
|
|
+ super( camera, domElement );
|
|
|
|
|
|
- const _intersections = [];
|
|
|
-
|
|
|
- this.mode = 'translate';
|
|
|
+ this.objects = objects;
|
|
|
|
|
|
+ this.recursive = true;
|
|
|
+ this.transformGroup = false;
|
|
|
this.rotateSpeed = 1;
|
|
|
|
|
|
- //
|
|
|
+ this.raycaster = new Raycaster();
|
|
|
|
|
|
- const scope = this;
|
|
|
+ // interaction
|
|
|
|
|
|
- function activate() {
|
|
|
+ this.mouseButtons = { LEFT: MOUSE.PAN, MIDDLE: MOUSE.PAN, RIGHT: MOUSE.ROTATE };
|
|
|
+ this.touches = { ONE: TOUCH.PAN };
|
|
|
|
|
|
- _domElement.addEventListener( 'pointermove', onPointerMove );
|
|
|
- _domElement.addEventListener( 'pointerdown', onPointerDown );
|
|
|
- _domElement.addEventListener( 'pointerup', onPointerCancel );
|
|
|
- _domElement.addEventListener( 'pointerleave', onPointerCancel );
|
|
|
+ // event listeners
|
|
|
|
|
|
- }
|
|
|
+ this._onPointerMove = onPointerMove.bind( this );
|
|
|
+ this._onPointerDown = onPointerDown.bind( this );
|
|
|
+ this._onPointerCancel = onPointerCancel.bind( this );
|
|
|
+ this._onContextMenu = onContextMenu.bind( this );
|
|
|
|
|
|
- function deactivate() {
|
|
|
+ //
|
|
|
|
|
|
- _domElement.removeEventListener( 'pointermove', onPointerMove );
|
|
|
- _domElement.removeEventListener( 'pointerdown', onPointerDown );
|
|
|
- _domElement.removeEventListener( 'pointerup', onPointerCancel );
|
|
|
- _domElement.removeEventListener( 'pointerleave', onPointerCancel );
|
|
|
+ if ( domElement !== null ) {
|
|
|
|
|
|
- _domElement.style.cursor = '';
|
|
|
+ this.connect();
|
|
|
|
|
|
}
|
|
|
|
|
|
- function dispose() {
|
|
|
+ }
|
|
|
|
|
|
- deactivate();
|
|
|
+ connect() {
|
|
|
|
|
|
- }
|
|
|
+ this.domElement.addEventListener( 'pointermove', this._onPointerMove );
|
|
|
+ this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
|
|
|
+ this.domElement.addEventListener( 'pointerup', this._onPointerCancel );
|
|
|
+ this.domElement.addEventListener( 'pointerleave', this._onPointerCancel );
|
|
|
+ this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
|
|
|
|
|
|
- function getObjects() {
|
|
|
+ this.domElement.style.touchAction = 'none'; // disable touch scroll
|
|
|
|
|
|
- return _objects;
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
+ disconnect() {
|
|
|
|
|
|
- function setObjects( objects ) {
|
|
|
+ this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
|
|
|
+ this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
|
|
|
+ this.domElement.removeEventListener( 'pointerup', this._onPointerCancel );
|
|
|
+ this.domElement.removeEventListener( 'pointerleave', this._onPointerCancel );
|
|
|
+ this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );
|
|
|
|
|
|
- _objects = objects;
|
|
|
+ this.domElement.style.touchAction = 'auto';
|
|
|
+ this.domElement.style.cursor = '';
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
+
|
|
|
+ dispose() {
|
|
|
+
|
|
|
+ this.disconnect();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ _updatePointer( event ) {
|
|
|
+
|
|
|
+ const rect = this.domElement.getBoundingClientRect();
|
|
|
+
|
|
|
+ _pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
|
|
|
+ _pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ _updateState( event ) {
|
|
|
+
|
|
|
+ // determine action
|
|
|
+
|
|
|
+ let action;
|
|
|
+
|
|
|
+ if ( event.pointerType === 'touch' ) {
|
|
|
|
|
|
- function getRaycaster() {
|
|
|
+ action = this.touches.ONE;
|
|
|
|
|
|
- return _raycaster;
|
|
|
+ } else {
|
|
|
+
|
|
|
+ switch ( event.button ) {
|
|
|
+
|
|
|
+ case 0:
|
|
|
+
|
|
|
+ action = this.mouseButtons.LEFT;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1:
|
|
|
+
|
|
|
+ action = this.mouseButtons.MIDDLE;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 2:
|
|
|
+
|
|
|
+ action = this.mouseButtons.RIGHT;
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+
|
|
|
+ action = null;
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
|
- function onPointerMove( event ) {
|
|
|
+ // determine state
|
|
|
|
|
|
- if ( scope.enabled === false ) return;
|
|
|
+ switch ( action ) {
|
|
|
|
|
|
- updatePointer( event );
|
|
|
+ case MOUSE.PAN:
|
|
|
+ case TOUCH.PAN:
|
|
|
|
|
|
- _raycaster.setFromCamera( _pointer, _camera );
|
|
|
+ this.state = STATE.PAN;
|
|
|
|
|
|
- if ( _selected ) {
|
|
|
+ break;
|
|
|
|
|
|
- if ( scope.mode === 'translate' ) {
|
|
|
+ case MOUSE.ROTATE:
|
|
|
+ case TOUCH.ROTATE:
|
|
|
|
|
|
- if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
|
|
|
+ this.state = STATE.ROTATE;
|
|
|
|
|
|
- _selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
|
|
|
+ break;
|
|
|
|
|
|
- }
|
|
|
+ default:
|
|
|
|
|
|
- } else if ( scope.mode === 'rotate' ) {
|
|
|
+ this.state = STATE.NONE;
|
|
|
|
|
|
- _diff.subVectors( _pointer, _previousPointer ).multiplyScalar( scope.rotateSpeed );
|
|
|
- _selected.rotateOnWorldAxis( _up, _diff.x );
|
|
|
- _selected.rotateOnWorldAxis( _right.normalize(), - _diff.y );
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- scope.dispatchEvent( { type: 'drag', object: _selected } );
|
|
|
+ getRaycaster() {
|
|
|
|
|
|
- _previousPointer.copy( _pointer );
|
|
|
+ console.warn( 'THREE.DragControls: getRaycaster() has been deprecated. Use controls.raycaster instead.' ); // @deprecated r169
|
|
|
|
|
|
- } else {
|
|
|
+ return this.raycaster;
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
- // hover support
|
|
|
+ setObjects( objects ) {
|
|
|
|
|
|
- if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) {
|
|
|
+ console.warn( 'THREE.DragControls: setObjects() has been deprecated. Use controls.objects instead.' ); // @deprecated r169
|
|
|
|
|
|
- _intersections.length = 0;
|
|
|
+ this.objects = objects;
|
|
|
|
|
|
- _raycaster.setFromCamera( _pointer, _camera );
|
|
|
- _raycaster.intersectObjects( _objects, scope.recursive, _intersections );
|
|
|
+ }
|
|
|
|
|
|
- if ( _intersections.length > 0 ) {
|
|
|
+ getObjects() {
|
|
|
|
|
|
- const object = _intersections[ 0 ].object;
|
|
|
+ console.warn( 'THREE.DragControls: getObjects() has been deprecated. Use controls.objects instead.' ); // @deprecated r169
|
|
|
|
|
|
- _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) );
|
|
|
+ return this.objects;
|
|
|
|
|
|
- if ( _hovered !== object && _hovered !== null ) {
|
|
|
+ }
|
|
|
|
|
|
- scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );
|
|
|
+ activate() {
|
|
|
|
|
|
- _domElement.style.cursor = 'auto';
|
|
|
- _hovered = null;
|
|
|
+ console.warn( 'THREE.DragControls: activate() has been renamed to connect().' ); // @deprecated r169
|
|
|
+ this.connect();
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- if ( _hovered !== object ) {
|
|
|
+ deactivate() {
|
|
|
|
|
|
- scope.dispatchEvent( { type: 'hoveron', object: object } );
|
|
|
+ console.warn( 'THREE.DragControls: deactivate() has been renamed to disconnect().' ); // @deprecated r169
|
|
|
+ this.disconnect();
|
|
|
|
|
|
- _domElement.style.cursor = 'pointer';
|
|
|
- _hovered = object;
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
+ set mode( value ) {
|
|
|
|
|
|
- } else {
|
|
|
+ console.warn( 'THREE.DragControls: The .mode property has been removed. Define the type of transformation via the .mouseButtons or .touches properties.' ); // @deprecated r169
|
|
|
|
|
|
- if ( _hovered !== null ) {
|
|
|
+ }
|
|
|
|
|
|
- scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );
|
|
|
+ get mode() {
|
|
|
|
|
|
- _domElement.style.cursor = 'auto';
|
|
|
- _hovered = null;
|
|
|
+ console.warn( 'THREE.DragControls: The .mode property has been removed. Define the type of transformation via the .mouseButtons or .touches properties.' ); // @deprecated r169
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
+}
|
|
|
|
|
|
- }
|
|
|
+function onPointerMove( event ) {
|
|
|
+
|
|
|
+ const camera = this.object;
|
|
|
+ const domElement = this.domElement;
|
|
|
+ const raycaster = this.raycaster;
|
|
|
+
|
|
|
+ if ( this.enabled === false ) return;
|
|
|
+
|
|
|
+ this._updatePointer( event );
|
|
|
+
|
|
|
+ raycaster.setFromCamera( _pointer, camera );
|
|
|
+
|
|
|
+ if ( _selected ) {
|
|
|
+
|
|
|
+ if ( this.state === STATE.PAN ) {
|
|
|
+
|
|
|
+ if ( raycaster.ray.intersectPlane( _plane, _intersection ) ) {
|
|
|
+
|
|
|
+ _selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
- _previousPointer.copy( _pointer );
|
|
|
+ } else if ( this.state === STATE.ROTATE ) {
|
|
|
+
|
|
|
+ _diff.subVectors( _pointer, _previousPointer ).multiplyScalar( this.rotateSpeed );
|
|
|
+ _selected.rotateOnWorldAxis( _up, _diff.x );
|
|
|
+ _selected.rotateOnWorldAxis( _right.normalize(), - _diff.y );
|
|
|
|
|
|
}
|
|
|
|
|
|
- function onPointerDown( event ) {
|
|
|
+ this.dispatchEvent( { type: 'drag', object: _selected } );
|
|
|
+
|
|
|
+ _previousPointer.copy( _pointer );
|
|
|
|
|
|
- if ( scope.enabled === false ) return;
|
|
|
+ } else {
|
|
|
|
|
|
- updatePointer( event );
|
|
|
+ // hover support
|
|
|
+
|
|
|
+ if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) {
|
|
|
|
|
|
_intersections.length = 0;
|
|
|
|
|
|
- _raycaster.setFromCamera( _pointer, _camera );
|
|
|
- _raycaster.intersectObjects( _objects, scope.recursive, _intersections );
|
|
|
+ raycaster.setFromCamera( _pointer, camera );
|
|
|
+ raycaster.intersectObjects( this.objects, this.recursive, _intersections );
|
|
|
|
|
|
if ( _intersections.length > 0 ) {
|
|
|
|
|
|
- if ( scope.transformGroup === true ) {
|
|
|
+ const object = _intersections[ 0 ].object;
|
|
|
|
|
|
- // look for the outermost group in the object's upper hierarchy
|
|
|
+ _plane.setFromNormalAndCoplanarPoint( camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) );
|
|
|
|
|
|
- _selected = findGroup( _intersections[ 0 ].object );
|
|
|
+ if ( _hovered !== object && _hovered !== null ) {
|
|
|
|
|
|
- } else {
|
|
|
+ this.dispatchEvent( { type: 'hoveroff', object: _hovered } );
|
|
|
|
|
|
- _selected = _intersections[ 0 ].object;
|
|
|
+ domElement.style.cursor = 'auto';
|
|
|
+ _hovered = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
- _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
|
|
|
+ if ( _hovered !== object ) {
|
|
|
|
|
|
- if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
|
|
|
+ this.dispatchEvent( { type: 'hoveron', object: object } );
|
|
|
|
|
|
- if ( scope.mode === 'translate' ) {
|
|
|
+ domElement.style.cursor = 'pointer';
|
|
|
+ _hovered = object;
|
|
|
|
|
|
- _inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
|
|
|
- _offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
|
|
|
+ }
|
|
|
|
|
|
- } else if ( scope.mode === 'rotate' ) {
|
|
|
+ } else {
|
|
|
|
|
|
- // the controls only support Y+ up
|
|
|
- _up.set( 0, 1, 0 ).applyQuaternion( _camera.quaternion ).normalize();
|
|
|
- _right.set( 1, 0, 0 ).applyQuaternion( _camera.quaternion ).normalize();
|
|
|
+ if ( _hovered !== null ) {
|
|
|
|
|
|
- }
|
|
|
+ this.dispatchEvent( { type: 'hoveroff', object: _hovered } );
|
|
|
+
|
|
|
+ domElement.style.cursor = 'auto';
|
|
|
+ _hovered = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
- _domElement.style.cursor = 'move';
|
|
|
+ }
|
|
|
|
|
|
- scope.dispatchEvent( { type: 'dragstart', object: _selected } );
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- _previousPointer.copy( _pointer );
|
|
|
+ _previousPointer.copy( _pointer );
|
|
|
|
|
|
- }
|
|
|
+}
|
|
|
|
|
|
- function onPointerCancel() {
|
|
|
+function onPointerDown( event ) {
|
|
|
|
|
|
- if ( scope.enabled === false ) return;
|
|
|
+ const camera = this.object;
|
|
|
+ const domElement = this.domElement;
|
|
|
+ const raycaster = this.raycaster;
|
|
|
|
|
|
- if ( _selected ) {
|
|
|
+ if ( this.enabled === false ) return;
|
|
|
|
|
|
- scope.dispatchEvent( { type: 'dragend', object: _selected } );
|
|
|
+ this._updatePointer( event );
|
|
|
+ this._updateState( event );
|
|
|
|
|
|
- _selected = null;
|
|
|
+ _intersections.length = 0;
|
|
|
|
|
|
- }
|
|
|
+ raycaster.setFromCamera( _pointer, camera );
|
|
|
+ raycaster.intersectObjects( this.objects, this.recursive, _intersections );
|
|
|
|
|
|
- _domElement.style.cursor = _hovered ? 'pointer' : 'auto';
|
|
|
+ if ( _intersections.length > 0 ) {
|
|
|
|
|
|
- }
|
|
|
+ if ( this.transformGroup === true ) {
|
|
|
|
|
|
- function updatePointer( event ) {
|
|
|
+ // look for the outermost group in the object's upper hierarchy
|
|
|
|
|
|
- const rect = _domElement.getBoundingClientRect();
|
|
|
+ _selected = findGroup( _intersections[ 0 ].object );
|
|
|
|
|
|
- _pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
|
|
|
- _pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1;
|
|
|
+ } else {
|
|
|
+
|
|
|
+ _selected = _intersections[ 0 ].object;
|
|
|
|
|
|
}
|
|
|
|
|
|
- function findGroup( obj, group = null ) {
|
|
|
+ _plane.setFromNormalAndCoplanarPoint( camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
|
|
|
+
|
|
|
+ if ( raycaster.ray.intersectPlane( _plane, _intersection ) ) {
|
|
|
+
|
|
|
+ if ( this.state === STATE.PAN ) {
|
|
|
|
|
|
- if ( obj.isGroup ) group = obj;
|
|
|
+ _inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
|
|
|
+ _offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
|
|
|
|
|
|
- if ( obj.parent === null ) return group;
|
|
|
+ } else if ( this.state === STATE.ROTATE ) {
|
|
|
|
|
|
- return findGroup( obj.parent, group );
|
|
|
+ // the controls only support Y+ up
|
|
|
+ _up.set( 0, 1, 0 ).applyQuaternion( camera.quaternion ).normalize();
|
|
|
+ _right.set( 1, 0, 0 ).applyQuaternion( camera.quaternion ).normalize();
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
|
- activate();
|
|
|
+ domElement.style.cursor = 'move';
|
|
|
|
|
|
- // API
|
|
|
+ this.dispatchEvent( { type: 'dragstart', object: _selected } );
|
|
|
|
|
|
- this.enabled = true;
|
|
|
- this.recursive = true;
|
|
|
- this.transformGroup = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ _previousPointer.copy( _pointer );
|
|
|
|
|
|
- this.activate = activate;
|
|
|
- this.deactivate = deactivate;
|
|
|
- this.dispose = dispose;
|
|
|
- this.getObjects = getObjects;
|
|
|
- this.getRaycaster = getRaycaster;
|
|
|
- this.setObjects = setObjects;
|
|
|
+}
|
|
|
+
|
|
|
+function onPointerCancel() {
|
|
|
+
|
|
|
+ if ( this.enabled === false ) return;
|
|
|
+
|
|
|
+ if ( _selected ) {
|
|
|
+
|
|
|
+ this.dispatchEvent( { type: 'dragend', object: _selected } );
|
|
|
+
|
|
|
+ _selected = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
+ this.domElement.style.cursor = _hovered ? 'pointer' : 'auto';
|
|
|
+
|
|
|
+ this.state = STATE.NONE;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+function onContextMenu( event ) {
|
|
|
+
|
|
|
+ if ( this.enabled === false ) return;
|
|
|
+
|
|
|
+ event.preventDefault();
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+function findGroup( obj, group = null ) {
|
|
|
+
|
|
|
+ if ( obj.isGroup ) group = obj;
|
|
|
+
|
|
|
+ if ( obj.parent === null ) return group;
|
|
|
+
|
|
|
+ return findGroup( obj.parent, group );
|
|
|
+
|
|
|
}
|
|
|
|
|
|
export { DragControls };
|