Spaces:
Running
Running
import { | |
Controls, | |
Matrix4, | |
Plane, | |
Raycaster, | |
Vector2, | |
Vector3, | |
MOUSE, | |
TOUCH | |
} from 'three'; | |
const _plane = new Plane(); | |
const _pointer = new Vector2(); | |
const _offset = new Vector3(); | |
const _diff = new Vector2(); | |
const _previousPointer = new Vector2(); | |
const _intersection = new Vector3(); | |
const _worldPosition = new Vector3(); | |
const _inverseMatrix = new Matrix4(); | |
const _up = new Vector3(); | |
const _right = new Vector3(); | |
let _selected = null, _hovered = null; | |
const _intersections = []; | |
const STATE = { | |
NONE: - 1, | |
PAN: 0, | |
ROTATE: 1 | |
}; | |
class DragControls extends Controls { | |
constructor( objects, camera, domElement = null ) { | |
super( camera, domElement ); | |
this.objects = objects; | |
this.recursive = true; | |
this.transformGroup = false; | |
this.rotateSpeed = 1; | |
this.raycaster = new Raycaster(); | |
// interaction | |
this.mouseButtons = { LEFT: MOUSE.PAN, MIDDLE: MOUSE.PAN, RIGHT: MOUSE.ROTATE }; | |
this.touches = { ONE: TOUCH.PAN }; | |
// event listeners | |
this._onPointerMove = onPointerMove.bind( this ); | |
this._onPointerDown = onPointerDown.bind( this ); | |
this._onPointerCancel = onPointerCancel.bind( this ); | |
this._onContextMenu = onContextMenu.bind( this ); | |
// | |
if ( domElement !== null ) { | |
this.connect(); | |
} | |
} | |
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 ); | |
this.domElement.style.touchAction = 'none'; // disable touch scroll | |
} | |
disconnect() { | |
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 ); | |
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' ) { | |
action = this.touches.ONE; | |
} 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; | |
} | |
} | |
// determine state | |
switch ( action ) { | |
case MOUSE.PAN: | |
case TOUCH.PAN: | |
this.state = STATE.PAN; | |
break; | |
case MOUSE.ROTATE: | |
case TOUCH.ROTATE: | |
this.state = STATE.ROTATE; | |
break; | |
default: | |
this.state = STATE.NONE; | |
} | |
} | |
getRaycaster() { | |
console.warn( 'THREE.DragControls: getRaycaster() has been deprecated. Use controls.raycaster instead.' ); // @deprecated r169 | |
return this.raycaster; | |
} | |
setObjects( objects ) { | |
console.warn( 'THREE.DragControls: setObjects() has been deprecated. Use controls.objects instead.' ); // @deprecated r169 | |
this.objects = objects; | |
} | |
getObjects() { | |
console.warn( 'THREE.DragControls: getObjects() has been deprecated. Use controls.objects instead.' ); // @deprecated r169 | |
return this.objects; | |
} | |
activate() { | |
console.warn( 'THREE.DragControls: activate() has been renamed to connect().' ); // @deprecated r169 | |
this.connect(); | |
} | |
deactivate() { | |
console.warn( 'THREE.DragControls: deactivate() has been renamed to disconnect().' ); // @deprecated r169 | |
this.disconnect(); | |
} | |
set mode( value ) { | |
console.warn( 'THREE.DragControls: The .mode property has been removed. Define the type of transformation via the .mouseButtons or .touches properties.' ); // @deprecated r169 | |
} | |
get mode() { | |
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 ) ); | |
} | |
} else if ( this.state === STATE.ROTATE ) { | |
_diff.subVectors( _pointer, _previousPointer ).multiplyScalar( this.rotateSpeed ); | |
_selected.rotateOnWorldAxis( _up, _diff.x ); | |
_selected.rotateOnWorldAxis( _right.normalize(), - _diff.y ); | |
} | |
this.dispatchEvent( { type: 'drag', object: _selected } ); | |
_previousPointer.copy( _pointer ); | |
} else { | |
// hover support | |
if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) { | |
_intersections.length = 0; | |
raycaster.setFromCamera( _pointer, camera ); | |
raycaster.intersectObjects( this.objects, this.recursive, _intersections ); | |
if ( _intersections.length > 0 ) { | |
const object = _intersections[ 0 ].object; | |
_plane.setFromNormalAndCoplanarPoint( camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) ); | |
if ( _hovered !== object && _hovered !== null ) { | |
this.dispatchEvent( { type: 'hoveroff', object: _hovered } ); | |
domElement.style.cursor = 'auto'; | |
_hovered = null; | |
} | |
if ( _hovered !== object ) { | |
this.dispatchEvent( { type: 'hoveron', object: object } ); | |
domElement.style.cursor = 'pointer'; | |
_hovered = object; | |
} | |
} else { | |
if ( _hovered !== null ) { | |
this.dispatchEvent( { type: 'hoveroff', object: _hovered } ); | |
domElement.style.cursor = 'auto'; | |
_hovered = null; | |
} | |
} | |
} | |
} | |
_previousPointer.copy( _pointer ); | |
} | |
function onPointerDown( event ) { | |
const camera = this.object; | |
const domElement = this.domElement; | |
const raycaster = this.raycaster; | |
if ( this.enabled === false ) return; | |
this._updatePointer( event ); | |
this._updateState( event ); | |
_intersections.length = 0; | |
raycaster.setFromCamera( _pointer, camera ); | |
raycaster.intersectObjects( this.objects, this.recursive, _intersections ); | |
if ( _intersections.length > 0 ) { | |
if ( this.transformGroup === true ) { | |
// look for the outermost group in the object's upper hierarchy | |
_selected = findGroup( _intersections[ 0 ].object ); | |
} else { | |
_selected = _intersections[ 0 ].object; | |
} | |
_plane.setFromNormalAndCoplanarPoint( camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) ); | |
if ( raycaster.ray.intersectPlane( _plane, _intersection ) ) { | |
if ( this.state === STATE.PAN ) { | |
_inverseMatrix.copy( _selected.parent.matrixWorld ).invert(); | |
_offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) ); | |
} else if ( this.state === STATE.ROTATE ) { | |
// the controls only support Y+ up | |
_up.set( 0, 1, 0 ).applyQuaternion( camera.quaternion ).normalize(); | |
_right.set( 1, 0, 0 ).applyQuaternion( camera.quaternion ).normalize(); | |
} | |
} | |
domElement.style.cursor = 'move'; | |
this.dispatchEvent( { type: 'dragstart', object: _selected } ); | |
} | |
_previousPointer.copy( _pointer ); | |
} | |
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 }; | |