diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/package.json b/package.json index 96cc1ab..de1dd9b 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,13 @@ "main": "index.js", "dependencies": {}, "scripts": { - "start": "webpack-dev-server --mode development --host 192.168.0.12", + "start": "webpack-dev-server --mode development --host 0.0.0.0", "build": "webpack --mode production --config webpack.config.js" }, "keywords": [], "author": "Arturo Paracuellos", "devDependencies": { + "aframe-aabb-collider-component": "^3.2.0", "aframe-environment-component": "^2.0.0", "aframe-super-hot-loader": "^1.7.0", "aframe-super-hot-html-loader": "^2.1.0", @@ -25,7 +26,6 @@ "css-loader": "^3.4.1", "html-require-loader": "^1.0.1", "json-loader": "^0.5.7", - "super-hands": "^3.0.0", "style-loader": "^0.23.1", "url-loader": "^1.1.2", "webpack": "^4.39.3", diff --git a/src/components/drag-controls.js b/src/components/drag-controls.js new file mode 100644 index 0000000..f7400e5 --- /dev/null +++ b/src/components/drag-controls.js @@ -0,0 +1,19 @@ +AFRAME.registerComponent('drag-controls', { + schema: {objects: {type: 'string', default: 'a-entity'}}, + + init: function () { + var objects = []; + var el = this.el; + var els; + var dragControls; + els = el.sceneEl.querySelectorAll(this.data.objects); + for (var i = 0; i < els.length; i++) { + console.log(i); + objects.push(els[i].object3D); + } + + el.sceneEl.addEventListener('renderstart', function () { + dragControls = new THREE.DragControls(objects, el.sceneEl.camera, el.sceneEl.renderer.domElement); + }); + } +}); \ No newline at end of file diff --git a/src/components/grab.js b/src/components/grab.js new file mode 100644 index 0000000..ca6c028 --- /dev/null +++ b/src/components/grab.js @@ -0,0 +1,89 @@ +/* global AFRAME, THREE */ + +/** +* Handles events coming from the hand-controls. +* Determines if the entity is grabbed or released. +* Updates its position to move along the controller. +*/ +AFRAME.registerComponent('grab', { + init: function () { + this.GRABBED_STATE = 'grabbed'; + // Bind event handlers + this.onHit = this.onHit.bind(this); + this.onGripOpen = this.onGripOpen.bind(this); + this.onGripClose = this.onGripClose.bind(this); + this.currentPosition = new THREE.Vector3(); + }, + + play: function () { + var el = this.el; + el.addEventListener('hit', this.onHit); + el.addEventListener('buttondown', this.onGripClose); + el.addEventListener('buttonup', this.onGripOpen); + }, + + pause: function () { + var el = this.el; + el.removeEventListener('hit', this.onHit); + el.addEventListener('buttondown', this.onGripClose); + el.addEventListener('buttonup', this.onGripOpen); + }, + + onGripClose: function (evt) { + if (this.grabbing) { return; } + this.grabbing = true; + this.pressedButtonId = evt.detail.id; + delete this.previousPosition; + }, + + onGripOpen: function (evt) { + var hitEl = this.hitEl; + if (this.pressedButtonId !== evt.detail.id) { return; } + this.grabbing = false; + if (!hitEl) { return; } + hitEl.removeState(this.GRABBED_STATE); + hitEl.emit('grabend'); + this.hitEl = undefined; + }, + + onHit: function (evt) { + var hitEl = evt.detail.el; + // If the element is already grabbed (it could be grabbed by another controller). + // If the hand is not grabbing the element does not stick. + // If we're already grabbing something you can't grab again. + if (!hitEl || hitEl.is(this.GRABBED_STATE) || !this.grabbing || this.hitEl) { return; } + hitEl.addState(this.GRABBED_STATE); + this.hitEl = hitEl; + }, + + tick: function () { + var hitEl = this.hitEl; + var position; + if (!hitEl) { return; } + this.updateDelta(); + position = hitEl.getAttribute('position'); + hitEl.setAttribute('position', { + x: position.x + this.deltaPosition.x, + y: position.y + this.deltaPosition.y, + z: position.z + this.deltaPosition.z + }); + }, + + updateDelta: function () { + var currentPosition = this.currentPosition; + this.el.object3D.updateMatrixWorld(); + currentPosition.setFromMatrixPosition(this.el.object3D.matrixWorld); + if (!this.previousPosition) { + this.previousPosition = new THREE.Vector3(); + this.previousPosition.copy(currentPosition); + } + var previousPosition = this.previousPosition; + var deltaPosition = { + x: currentPosition.x - previousPosition.x, + y: currentPosition.y - previousPosition.y, + z: currentPosition.z - previousPosition.z + }; + this.previousPosition.copy(currentPosition); + this.deltaPosition = deltaPosition; + } +}); diff --git a/src/home.html b/src/home.html index 985b449..7c9035b 100644 --- a/src/home.html +++ b/src/home.html @@ -1,6 +1,26 @@ - - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/index.js b/src/index.js index ecb4cd2..5ad2cdc 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,10 @@ console.time = () => {}; console.timeEnd = () => {}; require('aframe-environment-component') -require('super-hands') +require('aframe-aabb-collider-component'); +require('./lib/DragControls.js'); +require('./components/grab'); +require('./components/drag-controls'); require('./index.css') diff --git a/src/lib/DragControls.js b/src/lib/DragControls.js new file mode 100644 index 0000000..1d73868 --- /dev/null +++ b/src/lib/DragControls.js @@ -0,0 +1,282 @@ +/* + * @author zz85 / https://github.com/zz85 + * @author mrdoob / http://mrdoob.com + * Running this will allow you to drag three.js objects around the screen. + */ + +THREE.DragControls = function ( _objects, _camera, _domElement ) { + + if ( _objects instanceof THREE.Camera ) { + + console.warn( 'THREE.DragControls: Constructor now expects ( objects, camera, domElement )' ); + var temp = _objects; _objects = _camera; _camera = temp; + + } + + var _plane = new THREE.Plane(); + var _raycaster = new THREE.Raycaster(); + + var _mouse = new THREE.Vector2(); + var _offset = new THREE.Vector3(); + var _intersection = new THREE.Vector3(); + + var _selected = null, _hovered = null; + + // + + var scope = this; + + function activate() { + + _domElement.addEventListener( 'mousemove', onDocumentMouseMove, false ); + _domElement.addEventListener( 'mousedown', onDocumentMouseDown, false ); + _domElement.addEventListener( 'mouseup', onDocumentMouseCancel, false ); + _domElement.addEventListener( 'mouseleave', onDocumentMouseCancel, false ); + _domElement.addEventListener( 'touchmove', onDocumentTouchMove, false ); + _domElement.addEventListener( 'touchstart', onDocumentTouchStart, false ); + _domElement.addEventListener( 'touchend', onDocumentTouchEnd, false ); + + } + + function deactivate() { + + _domElement.removeEventListener( 'mousemove', onDocumentMouseMove, false ); + _domElement.removeEventListener( 'mousedown', onDocumentMouseDown, false ); + _domElement.removeEventListener( 'mouseup', onDocumentMouseCancel, false ); + _domElement.removeEventListener( 'mouseleave', onDocumentMouseCancel, false ); + _domElement.removeEventListener( 'touchmove', onDocumentTouchMove, false ); + _domElement.removeEventListener( 'touchstart', onDocumentTouchStart, false ); + _domElement.removeEventListener( 'touchend', onDocumentTouchEnd, false ); + + } + + function dispose() { + + deactivate(); + + } + + function onDocumentMouseMove( event ) { + + event.preventDefault(); + + var rect = _domElement.getBoundingClientRect(); + + _mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1; + _mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1; + + _raycaster.setFromCamera( _mouse, _camera ); + + if ( _selected && scope.enabled ) { + + if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { + + _selected.position.copy( _intersection.sub( _offset ) ); + + } + + scope.dispatchEvent( { type: 'drag', object: _selected } ); + + return; + + } + + _raycaster.setFromCamera( _mouse, _camera ); + + var intersects = _raycaster.intersectObjects( _objects, true ); + + if ( intersects.length > 0 ) { + + var object = intersects[ 0 ].object.parent; + + _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), object.position ); + + 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; + + } + + } + + } + + function onDocumentMouseDown( event ) { + + event.preventDefault(); + + _raycaster.setFromCamera( _mouse, _camera ); + + var intersects = _raycaster.intersectObjects( _objects, true ); + + if ( intersects.length > 0 ) { + + _selected = intersects[ 0 ].object.parent; + + if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { + + _offset.copy( _intersection ).sub( _selected.position ); + + } + + _domElement.style.cursor = 'move'; + + scope.dispatchEvent( { type: 'dragstart', object: _selected } ); + + } + + + } + + function onDocumentMouseCancel( event ) { + + event.preventDefault(); + + if ( _selected ) { + + scope.dispatchEvent( { type: 'dragend', object: _selected } ); + + _selected = null; + + } + + _domElement.style.cursor = 'auto'; + + } + + function onDocumentTouchMove( event ) { + + event.preventDefault(); + event = event.changedTouches[ 0 ]; + + var rect = _domElement.getBoundingClientRect(); + + _mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1; + _mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1; + + _raycaster.setFromCamera( _mouse, _camera ); + + if ( _selected && scope.enabled ) { + + if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { + + _selected.position.copy( _intersection.sub( _offset ) ); + + } + + scope.dispatchEvent( { type: 'drag', object: _selected } ); + + return; + + } + + } + + function onDocumentTouchStart( event ) { + + event.preventDefault(); + event = event.changedTouches[ 0 ]; + + var rect = _domElement.getBoundingClientRect(); + + _mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1; + _mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1; + + _raycaster.setFromCamera( _mouse, _camera ); + + var intersects = _raycaster.intersectObjects( _objects, true ); + + if ( intersects.length > 0 ) { + + _selected = intersects[ 0 ].object.parent; + + _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _selected.position ); + + if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { + + _offset.copy( _intersection ).sub( _selected.position ); + + } + + _domElement.style.cursor = 'move'; + + scope.dispatchEvent( { type: 'dragstart', object: _selected } ); + + } + + + } + + function onDocumentTouchEnd( event ) { + + event.preventDefault(); + + if ( _selected ) { + + scope.dispatchEvent( { type: 'dragend', object: _selected } ); + + _selected = null; + + } + + _domElement.style.cursor = 'auto'; + + } + + activate(); + + // API + + this.enabled = true; + + this.activate = activate; + this.deactivate = deactivate; + this.dispose = dispose; + + // Backward compatibility + + this.setObjects = function () { + + console.error( 'THREE.DragControls: setObjects() has been removed.' ); + + }; + + this.on = function ( type, listener ) { + + console.warn( 'THREE.DragControls: on() has been deprecated. Use addEventListener() instead.' ); + scope.addEventListener( type, listener ); + + }; + + this.off = function ( type, listener ) { + + console.warn( 'THREE.DragControls: off() has been deprecated. Use removeEventListener() instead.' ); + scope.removeEventListener( type, listener ); + + }; + + this.notify = function ( type ) { + + console.error( 'THREE.DragControls: notify() has been deprecated. Use dispatchEvent() instead.' ); + scope.dispatchEvent( { type: type } ); + + }; + +}; + +THREE.DragControls.prototype = Object.create( THREE.EventDispatcher.prototype ); +THREE.DragControls.prototype.constructor = THREE.DragControls; \ No newline at end of file