Просмотр исходного кода

DragControls: Refactor API. (#29079)

* DragControls: Refactor API.

* DragControls: Clean up.

* DragControls: Make `domElement` optional.

* DragControls: More clean up.

* DragControls: Update docs.

* Examples: Clean up.
Michael Herzog 1 год назад
Родитель
Сommit
b9a14e2dda

+ 17 - 30
docs/examples/en/controls/DragControls.html

@@ -7,7 +7,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:EventDispatcher] &rarr;
+		[page:Controls] &rarr;
 
 		<h1>[name]</h1>
 
@@ -95,25 +95,21 @@
 
 		<h2>Properties</h2>
 
-		<h3>[property:Boolean enabled]</h3>
-		<p>
-			Whether or not the controls are enabled.
-		</p>
+		<p>See the base [page:Controls] class for common properties.</p>
 
-		<h3>[property:Boolean recursive]</h3>
+		<h3>[property:Array objects]</h3>
 		<p>
-			Whether children of draggable objects can be dragged independently from their parent. Default is `true`.
+			An array of draggable 3D objects.
 		</p>
 
-		<h3>[property:Boolean transformGroup]</h3>
+		<h3>[property:Raycaster raycaster]</h3>
 		<p>
-			This option only works if the [page:DragControls.objects] array contains a single draggable group object.
-			If set to `true`, [name] does not transform individual objects but the entire group. Default is `false`.
+			The internal raycaster used for detecting 3D objects.
 		</p>
 
-		<h3>[property:String mode]</h3>
+		<h3>[property:Boolean recursive]</h3>
 		<p>
-			The current transformation mode. Possible values are `translate`, and `rotate`. Default is `translate`.
+			Whether children of draggable objects can be dragged independently from their parent. Default is `true`.
 		</p>
 
 		<h3>[property:Float rotateSpeed]</h3>
@@ -121,16 +117,22 @@
 			The speed at which the object will rotate when dragged in `rotate` mode. The higher the number the faster the rotation. Default is `1`.
 		</p>
 
+		<h3>[property:Boolean transformGroup]</h3>
+		<p>
+			This option only works if the [page:DragControls.objects] array contains a single draggable group object.
+			If set to `true`, [name] does not transform individual objects but the entire group. Default is `false`.
+		</p>
+
 		<h2>Methods</h2>
 
-		<p>See the base [page:EventDispatcher] class for common methods.</p>
+		<p>See the base [page:Controls] class for common methods.</p>
 
-		<h3>[method:undefined activate] ()</h3>
+		<h3>[method:undefined connect] ()</h3>
 		<p>
 			Adds the event listeners of the controls.
 		</p>
 
-		<h3>[method:undefined deactivate] ()</h3>
+		<h3>[method:undefined disconnect] ()</h3>
 		<p>
 			Removes the event listeners of the controls.
 		</p>
@@ -140,21 +142,6 @@
 			Should be called if the controls is no longer required.
 		</p>
 
-		<h3>[method:Array getObjects] ()</h3>
-		<p>
-			Returns the array of draggable objects.
-		</p>
-
-		<h3>[method:Raycaster getRaycaster] ()</h3>
-		<p>
-			Returns the internal [page:Raycaster] instance that is used for intersection tests.
-		</p>
-
-		<h3>[method:undefined setObjects] ( [param:Array objects] )</h3>
-		<p>
-			Sets an array of draggable objects by overwriting the existing one.
-		</p>
-
 		<h2>Source</h2>
 
 		<p>

+ 32 - 0
examples/jsm/controls/Controls.js

@@ -0,0 +1,32 @@
+import { EventDispatcher } from 'three';
+
+class Controls extends EventDispatcher {
+
+	constructor( object, domElement ) {
+
+		super();
+
+		this.object = object;
+		this.domElement = domElement;
+
+		this.enabled = true;
+
+		this.state = - 1;
+
+		this.keys = {};
+		this.mouseButtons = { LEFT: null, MIDDLE: null, RIGHT: null };
+		this.touches = { ONE: null, TWO: null };
+
+	}
+
+	connect() {}
+
+	disconnect() {}
+
+	dispose() {}
+
+	update() {}
+
+}
+
+export { Controls };

+ 260 - 132
examples/jsm/controls/DragControls.js

@@ -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 };

+ 4 - 4
examples/misc_controls_drag.html

@@ -20,7 +20,7 @@
 		<div id="info">
 			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - drag controls<br />
 			Use "Shift+Click" to add/remove objects to/from a group.<br />
-			Use "M" to toggle between rotate and translate mode.<br />
+			Use "M" to toggle between rotate and pan mode (touch only).<br />
 			Grouped objects can be transformed as a union.
 		</div>
 
@@ -143,10 +143,10 @@
 			function onKeyDown( event ) {
 
 				enableSelection = ( event.keyCode === 16 ) ? true : false;
-				
+			
 				if ( event.keyCode === 77 ) {
 
-					controls.mode = ( controls.mode === 'translate' ) ? 'rotate' : 'translate';
+					controls.touches.ONE = ( controls.touches.ONE === THREE.TOUCH.PAN ) ? THREE.TOUCH.ROTATE : THREE.TOUCH.PAN;
 			
 				}
 
@@ -164,7 +164,7 @@
 
 				if ( enableSelection === true ) {
 
-					const draggableObjects = controls.getObjects();
+					const draggableObjects = controls.objects;
 					draggableObjects.length = 0;
 
 					mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;

粤ICP备19079148号