Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DragControls: Add support for multiple groups. #27791

Merged
merged 4 commits into from
Feb 26, 2024
Merged

Conversation

narenjoriona
Copy link
Contributor

@narenjoriona narenjoriona commented Feb 21, 2024

Fixed Group drag and drop issue.

Related issue: Multiple Group not able to control using DragControls .

Description

Existing code can drag only one group. In this fix we can drag multiple group in scene.

Fixed Group drag and drop issue.
Existing code can drag only  one group now we can drag multiple group in scene.
@Mugen87
Copy link
Collaborator

Mugen87 commented Feb 21, 2024

Do you mind demonstrating the issue you are fixing with a live example (https://jsfiddle.net/g3atw6k5/)?

@narenjoriona
Copy link
Contributor Author

Do you mind demonstrating the issue you are fixing with a live example (https://jsfiddle.net/g3atw6k5/)?

https://jsfiddle.net/narenjcs/8ret1b9z/13/
Please check here. There is two group in scene but i can move one one group of mesh only. that is the issue

@narenjoriona
Copy link
Contributor Author

narenjoriona commented Feb 21, 2024

Do you mind demonstrating the issue you are fixing with a live example (https://jsfiddle.net/g3atw6k5/)?

My fix is here https://jsfiddle.net/narenjcs/jst15v34/3/

@Mugen87
Copy link
Collaborator

Mugen87 commented Feb 21, 2024

Thanks for sharing the fiddle!

Unfortunately, I can't edit your PR but can you please try it with this updated version of your code?

import {
	EventDispatcher,
	Matrix4,
	Plane,
	Raycaster,
	Vector2,
	Vector3
} from 'three';

const _plane = new Plane();
const _raycaster = new Raycaster();

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();

class DragControls extends EventDispatcher {

	constructor( _objects, _camera, _domElement ) {

		super();

		_domElement.style.touchAction = 'none'; // disable touch scroll

		let _selected = null, _hovered = null;

		const _intersections = [];

		this.mode = 'translate';

		this.rotateSpeed = 1;

		//

		const scope = this;

		function activate() {

			_domElement.addEventListener( 'pointermove', onPointerMove );
			_domElement.addEventListener( 'pointerdown', onPointerDown );
			_domElement.addEventListener( 'pointerup', onPointerCancel );
			_domElement.addEventListener( 'pointerleave', onPointerCancel );

		}

		function deactivate() {

			_domElement.removeEventListener( 'pointermove', onPointerMove );
			_domElement.removeEventListener( 'pointerdown', onPointerDown );
			_domElement.removeEventListener( 'pointerup', onPointerCancel );
			_domElement.removeEventListener( 'pointerleave', onPointerCancel );

			_domElement.style.cursor = '';

		}

		function dispose() {

			deactivate();

		}

		function getObjects() {

			return _objects;

		}

		function getRaycaster() {

			return _raycaster;

		}

		function onPointerMove( event ) {

			if ( scope.enabled === false ) return;

			updatePointer( event );

			_raycaster.setFromCamera( _pointer, _camera );

			if ( _selected ) {

				if ( scope.mode === 'translate' ) {

					if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {

						_selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );

					}

				} else if ( scope.mode === 'rotate' ) {

					_diff.subVectors( _pointer, _previousPointer ).multiplyScalar( scope.rotateSpeed );
					_selected.rotateOnWorldAxis( _up, _diff.x );
					_selected.rotateOnWorldAxis( _right.normalize(), - _diff.y );

				}

				scope.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( _objects, scope.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 ) {

							scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );

							_domElement.style.cursor = 'auto';
							_hovered = null;

						}

						if ( _hovered !== object ) {

							scope.dispatchEvent( { type: 'hoveron', object: object } );

							_domElement.style.cursor = 'pointer';
							_hovered = object;

						}

					} else {

						if ( _hovered !== null ) {

							scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );

							_domElement.style.cursor = 'auto';
							_hovered = null;

						}

					}

				}

			}

			_previousPointer.copy( _pointer );

		}

		function onPointerDown( event ) {

			if ( scope.enabled === false ) return;

			updatePointer( event );

			_intersections.length = 0;

			_raycaster.setFromCamera( _pointer, _camera );
			_raycaster.intersectObjects( _objects, scope.recursive, _intersections );

			if ( _intersections.length > 0 ) {

				if ( scope.transformGroup === true ) {

					// look for a 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 ( scope.mode === 'translate' ) {

						_inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
						_offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );

					} else if ( scope.mode === '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';

				scope.dispatchEvent( { type: 'dragstart', object: _selected } );

			}

			_previousPointer.copy( _pointer );

		}

		function onPointerCancel() {

			if ( scope.enabled === false ) return;

			if ( _selected ) {

				scope.dispatchEvent( { type: 'dragend', object: _selected } );

				_selected = null;

			}

			_domElement.style.cursor = _hovered ? 'pointer' : 'auto';

		}

		function updatePointer( event ) {

			const rect = _domElement.getBoundingClientRect();

			_pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
			_pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1;

		}

		function findGroup( obj ) {

			if ( obj.isGroup ) {

				return obj;

			} else {

				if ( obj.parent === null ) {

					throw new Error( 'DragControls: No group parent found. ' );

				}

				return findGroup( obj.parent );

			}

		}

		activate();

		// API

		this.enabled = true;
		this.recursive = true;
		this.transformGroup = false;

		this.activate = activate;
		this.deactivate = deactivate;
		this.dispose = dispose;
		this.getObjects = getObjects;
		this.getRaycaster = getRaycaster;

	}

}

export { DragControls };

Would this also work for you?

In general, the issue is clear now since DragControls only supports a single group right now. The idea of the PR is to search for the first group object in its ancestor nodes and then apply any transformation to it.

@narenjoriona
Copy link
Contributor Author

Thanks for sharing the fiddle!

Unfortunately, I can't edit your PR but can you please try it with this updated version of your code?

import {
	EventDispatcher,
	Matrix4,
	Plane,
	Raycaster,
	Vector2,
	Vector3
} from 'three';

const _plane = new Plane();
const _raycaster = new Raycaster();

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();

class DragControls extends EventDispatcher {

	constructor( _objects, _camera, _domElement ) {

		super();

		_domElement.style.touchAction = 'none'; // disable touch scroll

		let _selected = null, _hovered = null;

		const _intersections = [];

		this.mode = 'translate';

		this.rotateSpeed = 1;

		//

		const scope = this;

		function activate() {

			_domElement.addEventListener( 'pointermove', onPointerMove );
			_domElement.addEventListener( 'pointerdown', onPointerDown );
			_domElement.addEventListener( 'pointerup', onPointerCancel );
			_domElement.addEventListener( 'pointerleave', onPointerCancel );

		}

		function deactivate() {

			_domElement.removeEventListener( 'pointermove', onPointerMove );
			_domElement.removeEventListener( 'pointerdown', onPointerDown );
			_domElement.removeEventListener( 'pointerup', onPointerCancel );
			_domElement.removeEventListener( 'pointerleave', onPointerCancel );

			_domElement.style.cursor = '';

		}

		function dispose() {

			deactivate();

		}

		function getObjects() {

			return _objects;

		}

		function getRaycaster() {

			return _raycaster;

		}

		function onPointerMove( event ) {

			if ( scope.enabled === false ) return;

			updatePointer( event );

			_raycaster.setFromCamera( _pointer, _camera );

			if ( _selected ) {

				if ( scope.mode === 'translate' ) {

					if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {

						_selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );

					}

				} else if ( scope.mode === 'rotate' ) {

					_diff.subVectors( _pointer, _previousPointer ).multiplyScalar( scope.rotateSpeed );
					_selected.rotateOnWorldAxis( _up, _diff.x );
					_selected.rotateOnWorldAxis( _right.normalize(), - _diff.y );

				}

				scope.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( _objects, scope.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 ) {

							scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );

							_domElement.style.cursor = 'auto';
							_hovered = null;

						}

						if ( _hovered !== object ) {

							scope.dispatchEvent( { type: 'hoveron', object: object } );

							_domElement.style.cursor = 'pointer';
							_hovered = object;

						}

					} else {

						if ( _hovered !== null ) {

							scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );

							_domElement.style.cursor = 'auto';
							_hovered = null;

						}

					}

				}

			}

			_previousPointer.copy( _pointer );

		}

		function onPointerDown( event ) {

			if ( scope.enabled === false ) return;

			updatePointer( event );

			_intersections.length = 0;

			_raycaster.setFromCamera( _pointer, _camera );
			_raycaster.intersectObjects( _objects, scope.recursive, _intersections );

			if ( _intersections.length > 0 ) {

				if ( scope.transformGroup === true ) {

					// look for a 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 ( scope.mode === 'translate' ) {

						_inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
						_offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );

					} else if ( scope.mode === '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';

				scope.dispatchEvent( { type: 'dragstart', object: _selected } );

			}

			_previousPointer.copy( _pointer );

		}

		function onPointerCancel() {

			if ( scope.enabled === false ) return;

			if ( _selected ) {

				scope.dispatchEvent( { type: 'dragend', object: _selected } );

				_selected = null;

			}

			_domElement.style.cursor = _hovered ? 'pointer' : 'auto';

		}

		function updatePointer( event ) {

			const rect = _domElement.getBoundingClientRect();

			_pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
			_pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1;

		}

		function findGroup( obj ) {

			if ( obj.isGroup ) {

				return obj;

			} else {

				if ( obj.parent === null ) {

					throw new Error( 'DragControls: No group parent found. ' );

				}

				return findGroup( obj.parent );

			}

		}

		activate();

		// API

		this.enabled = true;
		this.recursive = true;
		this.transformGroup = false;

		this.activate = activate;
		this.deactivate = deactivate;
		this.dispose = dispose;
		this.getObjects = getObjects;
		this.getRaycaster = getRaycaster;

	}

}

export { DragControls };

Would this also work for you?

In general, the issue is clear now since DragControls only supports a single group right now. The idea of the PR is to search for the first group object in its ancestor nodes and then apply any transformation to it.

https://jsfiddle.net/narenjcs/m0gauchs/8/ In this example there is three cube in one group but that red cube only i can drag it, but i want to move all three cube

@Mugen87
Copy link
Collaborator

Mugen87 commented Feb 21, 2024

How about this: https://jsfiddle.net/w97z2d4q/

Now the controls pick the outermost group.

@narenjoriona
Copy link
Contributor Author

How about this: https://jsfiddle.net/w97z2d4q/

Now the controls pick the outermost group.

Yes Its fine and thanks

@Mugen87
Copy link
Collaborator

Mugen87 commented Feb 21, 2024

Would you like to update your PR with that version of DragControls?

@narenjoriona
Copy link
Contributor Author

Would you like to update your PR with that version of DragControls?

Yes I like to update PR with that version of DragControls

@Mugen87 Mugen87 changed the title Update DragControls.js Feb 21, 2024
@Mugen87
Copy link
Collaborator

Mugen87 commented Feb 21, 2024

The description of DragControls.transformGroup needs an update: https://threejs.org/docs/index.html#examples/en/controls/DragControls.transformGroup

Right now, the property assume only a single group is supported. With this PR, multiple groups can be dragged. This documentation should reflect this change.

@narenjoriona
Copy link
Contributor Author

Hi @Mugen87 Can you add below code for update object dynamically
function updateObject(_newObject){
_objects = _newObject
}

@Mugen87
Copy link
Collaborator

Mugen87 commented Feb 24, 2024

_objects is an array of objects. What is _newObject supposed to be?

I've also seen the PR isn't updated with the suggested changes yet (see #27791 (comment)). Would you mind doing this?

@narenjoriona
Copy link
Contributor Author

_objects is an array of objects. What is _newObject supposed to be?

I've also seen the PR isn't updated with the suggested changes yet (see #27791 (comment)). Would you mind doing this?

Sorry, I have updated latest code and _newObject is array of object(Mesh or Group)

@Mugen87 Mugen87 merged commit c825051 into mrdoob:dev Feb 26, 2024
11 checks passed
@Mugen87 Mugen87 added this to the r162 milestone Feb 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
2 participants