diff --git a/dist/super-hands.js b/dist/super-hands.js index c95a679..c1f4310 100644 --- a/dist/super-hands.js +++ b/dist/super-hands.js @@ -155,11 +155,16 @@ AFRAME.registerComponent('super-hands', { let carried = this.state.get(this.GRAB_EVENT); this.dispatchMouseEventAll('mousedown', this.el); this.gehClicking = new Set(this.hoverEls); + const detail = { + hand: this.el, + buttonEvent: evt + }; if (!carried) { - carried = this.findTarget(this.GRAB_EVENT, { - hand: this.el, - buttonEvent: evt - }); + if (evt.detail && evt.detail.targetEntity && !this.emitCancelable(evt.detail.targetEntity, this.GRAB_EVENT, detail)) { + carried = evt.detail.targetEntity; + } else { + carried = this.findTarget(this.GRAB_EVENT, detail); + } if (carried) { this.state.set(this.GRAB_EVENT, carried); this._unHover(carried); @@ -844,25 +849,30 @@ AFRAME.registerComponent('grabbable', inherit(base, { this.yFactor = (this.data.invert ? -1 : 1) * !this.data.suppressY; }, tick: function () { - var entityPosition; - if (this.grabber) { - // reflect on z-axis to point in same direction as the laser - this.targetPosition.copy(this.grabDirection); - this.targetPosition.applyQuaternion(this.grabber.object3D.getWorldQuaternion()).setLength(this.grabDistance).add(this.grabber.object3D.getWorldPosition()).add(this.grabOffset); - if (this.deltaPositionIsValid) { - // relative position changes work better with nested entities - this.deltaPosition.sub(this.targetPosition); - entityPosition = this.el.getAttribute('position'); - this.destPosition.x = entityPosition.x - this.deltaPosition.x * this.xFactor; - this.destPosition.y = entityPosition.y - this.deltaPosition.y * this.yFactor; - this.destPosition.z = entityPosition.z - this.deltaPosition.z * this.zFactor; - this.el.setAttribute('position', this.destPosition); - } else { - this.deltaPositionIsValid = true; + var q = new THREE.Quaternion(); + var v = new THREE.Vector3(); + + return function () { + var entityPosition; + if (this.grabber) { + // reflect on z-axis to point in same direction as the laser + this.targetPosition.copy(this.grabDirection); + this.targetPosition.applyQuaternion(this.grabber.object3D.getWorldQuaternion(q)).setLength(this.grabDistance).add(this.grabber.object3D.getWorldPosition(v)).add(this.grabOffset); + if (this.deltaPositionIsValid) { + // relative position changes work better with nested entities + this.deltaPosition.sub(this.targetPosition); + entityPosition = this.el.getAttribute('position'); + this.destPosition.x = entityPosition.x - this.deltaPosition.x * this.xFactor; + this.destPosition.y = entityPosition.y - this.deltaPosition.y * this.yFactor; + this.destPosition.z = entityPosition.z - this.deltaPosition.z * this.zFactor; + this.el.setAttribute('position', this.destPosition); + } else { + this.deltaPositionIsValid = true; + } + this.deltaPosition.copy(this.targetPosition); } - this.deltaPosition.copy(this.targetPosition); - } - }, + }; + }(), remove: function () { this.el.removeEventListener(this.GRAB_EVENT, this.start); this.el.removeEventListener(this.UNGRAB_EVENT, this.end); @@ -913,19 +923,23 @@ AFRAME.registerComponent('grabbable', inherit(base, { } }, resetGrabber: function () { - let raycaster; - if (!this.grabber) { - return false; - } - raycaster = this.grabber.getAttribute('raycaster'); - this.deltaPositionIsValid = false; - this.grabDistance = this.el.object3D.getWorldPosition().distanceTo(this.grabber.object3D.getWorldPosition()); - if (raycaster) { - this.grabDirection = raycaster.direction; - this.grabOffset = raycaster.origin; - } - return true; - }, + var objPos = new THREE.Vector3(); + var grabPos = new THREE.Vector3(); + return function () { + let raycaster; + if (!this.grabber) { + return false; + } + raycaster = this.grabber.getAttribute('raycaster'); + this.deltaPositionIsValid = false; + this.grabDistance = this.el.object3D.getWorldPosition(objPos).distanceTo(this.grabber.object3D.getWorldPosition(grabPos)); + if (raycaster) { + this.grabDirection = raycaster.direction; + this.grabOffset = raycaster.origin; + } + return true; + }; + }(), lostGrabber: function (evt) { let i = this.grabbers.indexOf(evt.relatedTarget); // if a queued, non-physics grabber leaves the collision zone, forget it @@ -1075,7 +1089,8 @@ AFRAME.registerComponent('stretchable', inherit(base, { schema: { usePhysics: { default: 'ifavailable' }, invert: { default: false }, - physicsUpdateRate: { default: 100 } + physicsUpdateRate: { default: 100 }, + useWorldPosition: { default: false } }, init: function () { this.STRETCHED_STATE = 'stretched'; @@ -1102,8 +1117,13 @@ AFRAME.registerComponent('stretchable', inherit(base, { return; } this.scale.copy(this.el.getAttribute('scale')); - this.handPos.copy(this.stretchers[0].getAttribute('position')); - this.otherHandPos.copy(this.stretchers[1].getAttribute('position')); + if (this.data.useWorldPosition) { + this.stretchers[0].object3D.getWorldPosition(this.handPos); + this.stretchers[1].object3D.getWorldPosition(this.otherHandPos); + } else { + this.handPos.copy(this.stretchers[0].getAttribute('position')); + this.otherHandPos.copy(this.stretchers[1].getAttribute('position')); + } const currentStretch = this.handPos.distanceTo(this.otherHandPos); let deltaStretch = 1; if (this.previousStretch !== null && currentStretch !== 0) { diff --git a/dist/super-hands.min.js b/dist/super-hands.min.js index e93e800..d45977e 100644 --- a/dist/super-hands.min.js +++ b/dist/super-hands.min.js @@ -1,5 +1,5 @@ (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;ithis.gehClicking.has(a)),c=this.state.get(this.GRAB_EVENT),d={hand:this.el,buttonEvent:a};this.dispatchMouseEventAll('mouseup',this.el);for(let c=0;c{this.dispatchMouseEvent(a,'dragend',this.el),this.dispatchMouseEventAll('drop',a,!0,!0),this.dispatchMouseEventAll('dragleave',a,!0,!0)}),this.gehDragged.clear(),b){const c={hand:this.el,dropped:b,on:null,buttonEvent:a},d={hand:this.el,buttonEvent:a},e=this.findTarget(this.DRAGDROP_EVENT,c,!0);e&&(c.on=e,this.emitCancelable(b,this.DRAGDROP_EVENT,c),this._unHover(e)),this.emitCancelable(b,this.UNDRAG_EVENT,d)||(this.promoteHoveredEl(b),this.state.delete(this.DRAG_EVENT),this.hover())}},processHitEl:function(a,b){const c=b&&b.distance,d=this.hoverElsIntersections,e=this.hoverEls,f=this.hoverEls.indexOf(a);let g=!1;if(-1===f){if(g=!0,null!=c){let f=0;for(;f{this.dispatchMouseEventAll('dragenter',a,!0,!0)})}return g},onHit:function(a){const b=a.detail[this.data.colliderEventProperty];let c=0;if(b){if(Array.isArray(b))for(let d,e=0;ethis._unHover(a)):this._unHover(b))},_unHover:function(a,b){let c,d=!1;a===this.state.get(this.DRAGOVER_EVENT)&&(this.state.delete(this.DRAGOVER_EVENT),d=!0,c={hand:this.el,hovered:a,carried:this.state.get(this.DRAG_EVENT)},this.emitCancelable(a,this.UNDRAGOVER_EVENT,c),this.state.has(this.DRAG_EVENT)&&this.emitCancelable(this.state.get(this.DRAG_EVENT),this.UNDRAGOVER_EVENT,c)),a===this.state.get(this.HOVER_EVENT)&&(this.state.delete(this.HOVER_EVENT),d=!0,this.emitCancelable(a,this.UNHOVER_EVENT,{hand:this.el})),d&&!b&&this.hover()},unWatch:function(a){const b=a.detail[this.data.colliderEndEventProperty];b&&(Array.isArray(b)?b.forEach((a)=>this._unWatch(a)):this._unWatch(b))},_unWatch:function(a){var b=this.hoverEls.indexOf(a);-1!==b&&(this.hoverEls.splice(b,1),this.hoverElsIntersections.splice(b,1)),this.gehDragged.forEach((b)=>{this.dispatchMouseEvent(a,'dragleave',b),this.dispatchMouseEvent(b,'dragleave',a)}),this.dispatchMouseEvent(a,'mouseout',this.el)},registerListeners:function(){this.el.addEventListener(this.data.colliderEvent,this.onHit),this.el.addEventListener(this.data.colliderEndEvent,this.unWatch),this.el.addEventListener(this.data.colliderEndEvent,this.unHover),this.data.grabStartButtons.forEach((a)=>{this.el.addEventListener(a,this.onGrabStartButton)}),this.data.stretchStartButtons.forEach((a)=>{this.el.addEventListener(a,this.onStretchStartButton)}),this.data.dragDropStartButtons.forEach((a)=>{this.el.addEventListener(a,this.onDragDropStartButton)}),this.data.dragDropEndButtons.forEach((a)=>{this.el.addEventListener(a,this.onDragDropEndButton)}),this.data.stretchEndButtons.forEach((a)=>{this.el.addEventListener(a,this.onStretchEndButton)}),this.data.grabEndButtons.forEach((a)=>{this.el.addEventListener(a,this.onGrabEndButton)})},unRegisterListeners:function(a){a=a||this.data;0===Object.keys(a).length||(this.el.removeEventListener(a.colliderEvent,this.onHit),this.el.removeEventListener(a.colliderEndEvent,this.unHover),this.el.removeEventListener(a.colliderEndEvent,this.unWatch),a.grabStartButtons.forEach((a)=>{this.el.removeEventListener(a,this.onGrabStartButton)}),a.grabEndButtons.forEach((a)=>{this.el.removeEventListener(a,this.onGrabEndButton)}),a.stretchStartButtons.forEach((a)=>{this.el.removeEventListener(a,this.onStretchStartButton)}),a.stretchEndButtons.forEach((a)=>{this.el.removeEventListener(a,this.onStretchEndButton)}),a.dragDropStartButtons.forEach((a)=>{this.el.removeEventListener(a,this.onDragDropStartButton)}),a.dragDropEndButtons.forEach((a)=>{this.el.removeEventListener(a,this.onDragDropEndButton)}))},emitCancelable:function(a,b,c){var d,e;return c=c||{},d={bubbles:!0,cancelable:!0,detail:c},d.detail.target=d.detail.target||a,e=new window.CustomEvent(b,d),a.dispatchEvent(e)},dispatchMouseEvent:function(a,b,c){var d=new window.MouseEvent(b,{relatedTarget:c});a.dispatchEvent(d)},dispatchMouseEventAll:function(a,b,c,d){let e=this.hoverEls;if(c&&(e=e.filter((a)=>a!==this.state.get(this.GRAB_EVENT)&&a!==this.state.get(this.DRAG_EVENT)&&a!==this.state.get(this.STRETCH_EVENT)&&!this.gehDragged.has(a))),d)for(let c=0;ca!==this.state.get(this.GRAB_EVENT)&&a!==this.state.get(this.DRAG_EVENT)&&a!==this.state.get(this.STRETCH_EVENT))),d=e.length-1;0<=d;d--)if(!this.emitCancelable(e[d],a,b))return e[d];return null},promoteHoveredEl:function(a){var b=this.hoverEls.indexOf(a);if(-1!==b&&null==this.hoverElsIntersections[b].distance){this.hoverEls.splice(b,1);const c=this.hoverElsIntersections.splice(b,1);this.hoverEls.push(a),this.hoverElsIntersections.push(c)}}}); +'use strict';if('undefined'==typeof AFRAME)throw new Error('Component attempted to register before AFRAME was available.');require('./systems/super-hands-system.js'),require('./reaction_components/hoverable.js'),require('./reaction_components/grabbable.js'),require('./reaction_components/stretchable.js'),require('./reaction_components/drag-droppable.js'),require('./reaction_components/draggable.js'),require('./reaction_components/droppable.js'),require('./reaction_components/clickable.js'),AFRAME.registerComponent('super-hands',{schema:{colliderEvent:{default:'hit'},colliderEventProperty:{default:'el'},colliderEndEvent:{default:'hitend'},colliderEndEventProperty:{default:'el'},grabStartButtons:{default:['gripdown','trackpaddown','triggerdown','gripclose','abuttondown','bbuttondown','xbuttondown','ybuttondown','pointup','thumbup','pointingstart','pistolstart','thumbstickdown','mousedown','touchstart']},grabEndButtons:{default:['gripup','trackpadup','triggerup','gripopen','abuttonup','bbuttonup','xbuttonup','ybuttonup','pointdown','thumbdown','pointingend','pistolend','thumbstickup','mouseup','touchend']},stretchStartButtons:{default:['gripdown','trackpaddown','triggerdown','gripclose','abuttondown','bbuttondown','xbuttondown','ybuttondown','pointup','thumbup','pointingstart','pistolstart','thumbstickdown','mousedown','touchstart']},stretchEndButtons:{default:['gripup','trackpadup','triggerup','gripopen','abuttonup','bbuttonup','xbuttonup','ybuttonup','pointdown','thumbdown','pointingend','pistolend','thumbstickup','mouseup','touchend']},dragDropStartButtons:{default:['gripdown','trackpaddown','triggerdown','gripclose','abuttondown','bbuttondown','xbuttondown','ybuttondown','pointup','thumbup','pointingstart','pistolstart','thumbstickdown','mousedown','touchstart']},dragDropEndButtons:{default:['gripup','trackpadup','triggerup','gripopen','abuttonup','bbuttonup','xbuttonup','ybuttonup','pointdown','thumbdown','pointingend','pistolend','thumbstickup','mouseup','touchend']},interval:{default:0}},multiple:!1,init:function(){this.HOVER_EVENT='hover-start',this.UNHOVER_EVENT='hover-end',this.GRAB_EVENT='grab-start',this.UNGRAB_EVENT='grab-end',this.STRETCH_EVENT='stretch-start',this.UNSTRETCH_EVENT='stretch-end',this.DRAG_EVENT='drag-start',this.UNDRAG_EVENT='drag-end',this.DRAGOVER_EVENT='dragover-start',this.UNDRAGOVER_EVENT='dragover-end',this.DRAGDROP_EVENT='drag-drop',this.otherSuperHand=null,this.gehDragged=new Set,this.gehClicking=new Set,this.hoverEls=[],this.hoverElsIntersections=[],this.prevCheckTime=null,this.state=new Map,this.dragging=!1,this.unHover=this.unHover.bind(this),this.unWatch=this.unWatch.bind(this),this.onHit=this.onHit.bind(this),this.onGrabStartButton=this.onGrabStartButton.bind(this),this.onGrabEndButton=this.onGrabEndButton.bind(this),this.onStretchStartButton=this.onStretchStartButton.bind(this),this.onStretchEndButton=this.onStretchEndButton.bind(this),this.onDragDropStartButton=this.onDragDropStartButton.bind(this),this.onDragDropEndButton=this.onDragDropEndButton.bind(this),this.system.registerMe(this)},update:function(a){this.unRegisterListeners(a),this.registerListeners()},remove:function(){this.system.unregisterMe(this),this.unRegisterListeners(),this.hoverEls.length=0,this.state.get(this.HOVER_EVENT)&&this._unHover(this.state.get(this.HOVER_EVENT)),this.onGrabEndButton(),this.onStretchEndButton(),this.onDragDropEndButton()},tick:function(){function a(d,a){const b=null==d.distance?-1:d.distance,e=null==a.distance?-1:a.distance;return bthis.gehClicking.has(a)),c=this.state.get(this.GRAB_EVENT),d={hand:this.el,buttonEvent:a};this.dispatchMouseEventAll('mouseup',this.el);for(let c=0;c{this.dispatchMouseEvent(a,'dragend',this.el),this.dispatchMouseEventAll('drop',a,!0,!0),this.dispatchMouseEventAll('dragleave',a,!0,!0)}),this.gehDragged.clear(),b){const c={hand:this.el,dropped:b,on:null,buttonEvent:a},d={hand:this.el,buttonEvent:a},e=this.findTarget(this.DRAGDROP_EVENT,c,!0);e&&(c.on=e,this.emitCancelable(b,this.DRAGDROP_EVENT,c),this._unHover(e)),this.emitCancelable(b,this.UNDRAG_EVENT,d)||(this.promoteHoveredEl(b),this.state.delete(this.DRAG_EVENT),this.hover())}},processHitEl:function(a,b){const c=b&&b.distance,d=this.hoverElsIntersections,e=this.hoverEls,f=this.hoverEls.indexOf(a);let g=!1;if(-1===f){if(g=!0,null!=c){let f=0;for(;f{this.dispatchMouseEventAll('dragenter',a,!0,!0)})}return g},onHit:function(a){const b=a.detail[this.data.colliderEventProperty];let c=0;if(b){if(Array.isArray(b))for(let d,e=0;ethis._unHover(a)):this._unHover(b))},_unHover:function(a,b){let c,d=!1;a===this.state.get(this.DRAGOVER_EVENT)&&(this.state.delete(this.DRAGOVER_EVENT),d=!0,c={hand:this.el,hovered:a,carried:this.state.get(this.DRAG_EVENT)},this.emitCancelable(a,this.UNDRAGOVER_EVENT,c),this.state.has(this.DRAG_EVENT)&&this.emitCancelable(this.state.get(this.DRAG_EVENT),this.UNDRAGOVER_EVENT,c)),a===this.state.get(this.HOVER_EVENT)&&(this.state.delete(this.HOVER_EVENT),d=!0,this.emitCancelable(a,this.UNHOVER_EVENT,{hand:this.el})),d&&!b&&this.hover()},unWatch:function(a){const b=a.detail[this.data.colliderEndEventProperty];b&&(Array.isArray(b)?b.forEach((a)=>this._unWatch(a)):this._unWatch(b))},_unWatch:function(a){var b=this.hoverEls.indexOf(a);-1!==b&&(this.hoverEls.splice(b,1),this.hoverElsIntersections.splice(b,1)),this.gehDragged.forEach((b)=>{this.dispatchMouseEvent(a,'dragleave',b),this.dispatchMouseEvent(b,'dragleave',a)}),this.dispatchMouseEvent(a,'mouseout',this.el)},registerListeners:function(){this.el.addEventListener(this.data.colliderEvent,this.onHit),this.el.addEventListener(this.data.colliderEndEvent,this.unWatch),this.el.addEventListener(this.data.colliderEndEvent,this.unHover),this.data.grabStartButtons.forEach((a)=>{this.el.addEventListener(a,this.onGrabStartButton)}),this.data.stretchStartButtons.forEach((a)=>{this.el.addEventListener(a,this.onStretchStartButton)}),this.data.dragDropStartButtons.forEach((a)=>{this.el.addEventListener(a,this.onDragDropStartButton)}),this.data.dragDropEndButtons.forEach((a)=>{this.el.addEventListener(a,this.onDragDropEndButton)}),this.data.stretchEndButtons.forEach((a)=>{this.el.addEventListener(a,this.onStretchEndButton)}),this.data.grabEndButtons.forEach((a)=>{this.el.addEventListener(a,this.onGrabEndButton)})},unRegisterListeners:function(a){a=a||this.data;0===Object.keys(a).length||(this.el.removeEventListener(a.colliderEvent,this.onHit),this.el.removeEventListener(a.colliderEndEvent,this.unHover),this.el.removeEventListener(a.colliderEndEvent,this.unWatch),a.grabStartButtons.forEach((a)=>{this.el.removeEventListener(a,this.onGrabStartButton)}),a.grabEndButtons.forEach((a)=>{this.el.removeEventListener(a,this.onGrabEndButton)}),a.stretchStartButtons.forEach((a)=>{this.el.removeEventListener(a,this.onStretchStartButton)}),a.stretchEndButtons.forEach((a)=>{this.el.removeEventListener(a,this.onStretchEndButton)}),a.dragDropStartButtons.forEach((a)=>{this.el.removeEventListener(a,this.onDragDropStartButton)}),a.dragDropEndButtons.forEach((a)=>{this.el.removeEventListener(a,this.onDragDropEndButton)}))},emitCancelable:function(a,b,c){var d,e;return c=c||{},d={bubbles:!0,cancelable:!0,detail:c},d.detail.target=d.detail.target||a,e=new window.CustomEvent(b,d),a.dispatchEvent(e)},dispatchMouseEvent:function(a,b,c){var d=new window.MouseEvent(b,{relatedTarget:c});a.dispatchEvent(d)},dispatchMouseEventAll:function(a,b,c,d){let e=this.hoverEls;if(c&&(e=e.filter((a)=>a!==this.state.get(this.GRAB_EVENT)&&a!==this.state.get(this.DRAG_EVENT)&&a!==this.state.get(this.STRETCH_EVENT)&&!this.gehDragged.has(a))),d)for(let c=0;ca!==this.state.get(this.GRAB_EVENT)&&a!==this.state.get(this.DRAG_EVENT)&&a!==this.state.get(this.STRETCH_EVENT))),d=e.length-1;0<=d;d--)if(!this.emitCancelable(e[d],a,b))return e[d];return null},promoteHoveredEl:function(a){var b=this.hoverEls.indexOf(a);if(-1!==b&&null==this.hoverElsIntersections[b].distance){this.hoverEls.splice(b,1);const c=this.hoverElsIntersections.splice(b,1);this.hoverEls.push(a),this.hoverElsIntersections.push(c)}}}); },{"./reaction_components/clickable.js":2,"./reaction_components/drag-droppable.js":3,"./reaction_components/draggable.js":4,"./reaction_components/droppable.js":5,"./reaction_components/grabbable.js":6,"./reaction_components/hoverable.js":7,"./reaction_components/stretchable.js":10,"./systems/super-hands-system.js":11}],2:[function(require,module,exports){ 'use strict';const buttonCore=require('./prototypes/buttons-proto.js');AFRAME.registerComponent('clickable',AFRAME.utils.extendDeep({},buttonCore,{schema:{onclick:{type:'string'}},init:function(){this.CLICKED_STATE='clicked',this.CLICK_EVENT='grab-start',this.UNCLICK_EVENT='grab-end',this.clickers=[],this.start=this.start.bind(this),this.end=this.end.bind(this),this.el.addEventListener(this.CLICK_EVENT,this.start),this.el.addEventListener(this.UNCLICK_EVENT,this.end)},remove:function(){this.el.removeEventListener(this.CLICK_EVENT,this.start),this.el.removeEventListener(this.UNCLICK_EVENT,this.end)},start:function(a){a.defaultPrevented||!this.startButtonOk(a)||(this.el.addState(this.CLICKED_STATE),-1===this.clickers.indexOf(a.detail.hand)&&(this.clickers.push(a.detail.hand),a.preventDefault&&a.preventDefault()))},end:function(a){const b=this.clickers.indexOf(a.detail.hand);a.defaultPrevented||!this.endButtonOk(a)||(-1!==b&&this.clickers.splice(b,1),1>this.clickers.length&&this.el.removeState(this.CLICKED_STATE),a.preventDefault&&a.preventDefault())}})); @@ -14,7 +14,7 @@ 'use strict';function elementMatches(a,b){return a.matches?a.matches(b):a.msMatchesSelector?a.msMatchesSelector(b):a.webkitMatchesSelector?a.webkitMatchesSelector(b):void 0}AFRAME.registerComponent('droppable',{schema:{accepts:{default:''},autoUpdate:{default:!0},acceptEvent:{default:''},rejectEvent:{default:''}},multiple:!0,init:function(){this.HOVERED_STATE='dragover',this.HOVER_EVENT='dragover-start',this.UNHOVER_EVENT='dragover-end',this.DRAGDROP_EVENT='drag-drop',this.hoverStartBound=this.hoverStart.bind(this),this.hoverEndBound=this.hoverEnd.bind(this),this.dragDropBound=this.dragDrop.bind(this),this.mutateAcceptsBound=this.mutateAccepts.bind(this),this.acceptableEntities=[],this.observer=new window.MutationObserver(this.mutateAcceptsBound),this.observerOpts={childList:!0,subtree:!0},this.el.addEventListener(this.HOVER_EVENT,this.hoverStartBound),this.el.addEventListener(this.UNHOVER_EVENT,this.hoverEndBound),this.el.addEventListener(this.DRAGDROP_EVENT,this.dragDropBound)},update:function(){this.acceptableEntities=this.data.accepts.length?Array.prototype.slice.call(this.el.sceneEl.querySelectorAll(this.data.accepts)):null,this.data.autoUpdate&&null!=this.acceptableEntities?this.observer.observe(this.el.sceneEl,this.observerOpts):this.observer.disconnect()},remove:function(){this.el.removeEventListener(this.HOVER_EVENT,this.hoverStartBound),this.el.removeEventListener(this.UNHOVER_EVENT,this.hoverEndBound),this.el.removeEventListener(this.DRAGDROP_EVENT,this.dragDropBound),this.observer.disconnect()},mutateAccepts:function(a){const b=this.data.accepts;a.forEach((a)=>{a.addedNodes.forEach((a)=>{elementMatches(a,b)&&this.acceptableEntities.push(a)})})},entityAcceptable:function(a){const b=this.acceptableEntities;if(null==b)return!0;for(let c of b)if(c===a)return!0;return!1},hoverStart:function(a){a.defaultPrevented||!this.entityAcceptable(a.detail.carried)||(this.el.addState(this.HOVERED_STATE),a.preventDefault&&a.preventDefault())},hoverEnd:function(a){a.defaultPrevented||this.el.removeState(this.HOVERED_STATE)},dragDrop:function(a){if(!a.defaultPrevented){const b=a.detail.dropped;return this.entityAcceptable(b)?void(this.data.acceptEvent.length&&this.el.emit(this.data.acceptEvent,{el:b}),a.preventDefault&&a.preventDefault()):void(this.data.rejectEvent.length&&this.el.emit(this.data.rejectEvent,{el:b}))}}}); },{}],6:[function(require,module,exports){ -'use strict';const inherit=AFRAME.utils.extendDeep,physicsCore=require('./prototypes/physics-grab-proto.js'),buttonsCore=require('./prototypes/buttons-proto.js'),base=inherit({},physicsCore,buttonsCore);AFRAME.registerComponent('grabbable',inherit(base,{schema:{maxGrabbers:{type:'int',default:NaN},invert:{default:!1},suppressY:{default:!1}},init:function(){this.GRABBED_STATE='grabbed',this.GRAB_EVENT='grab-start',this.UNGRAB_EVENT='grab-end',this.grabbed=!1,this.grabbers=[],this.constraints=new Map,this.deltaPositionIsValid=!1,this.grabDistance=void 0,this.grabDirection={x:0,y:0,z:-1},this.grabOffset={x:0,y:0,z:0},this.destPosition={x:0,y:0,z:0},this.deltaPosition=new THREE.Vector3,this.targetPosition=new THREE.Vector3,this.physicsInit(),this.el.addEventListener(this.GRAB_EVENT,(a)=>this.start(a)),this.el.addEventListener(this.UNGRAB_EVENT,(a)=>this.end(a)),this.el.addEventListener('mouseout',(a)=>this.lostGrabber(a))},update:function(){this.physicsUpdate(),this.xFactor=this.data.invert?-1:1,this.zFactor=this.data.invert?-1:1,this.yFactor=(this.data.invert?-1:1)*!this.data.suppressY},tick:function(){var a;this.grabber&&(this.targetPosition.copy(this.grabDirection),this.targetPosition.applyQuaternion(this.grabber.object3D.getWorldQuaternion()).setLength(this.grabDistance).add(this.grabber.object3D.getWorldPosition()).add(this.grabOffset),this.deltaPositionIsValid?(this.deltaPosition.sub(this.targetPosition),a=this.el.getAttribute('position'),this.destPosition.x=a.x-this.deltaPosition.x*this.xFactor,this.destPosition.y=a.y-this.deltaPosition.y*this.yFactor,this.destPosition.z=a.z-this.deltaPosition.z*this.zFactor,this.el.setAttribute('position',this.destPosition)):this.deltaPositionIsValid=!0,this.deltaPosition.copy(this.targetPosition))},remove:function(){this.el.removeEventListener(this.GRAB_EVENT,this.start),this.el.removeEventListener(this.UNGRAB_EVENT,this.end),this.physicsRemove()},start:function(a){if(!a.defaultPrevented&&this.startButtonOk(a)){const b=!Number.isFinite(this.data.maxGrabbers)||this.grabbers.lengththis.start(a)),this.el.addEventListener(this.UNGRAB_EVENT,(a)=>this.end(a)),this.el.addEventListener('mouseout',(a)=>this.lostGrabber(a))},update:function(){this.physicsUpdate(),this.xFactor=this.data.invert?-1:1,this.zFactor=this.data.invert?-1:1,this.yFactor=(this.data.invert?-1:1)*!this.data.suppressY},tick:function(){var a=new THREE.Quaternion,b=new THREE.Vector3;return function(){var c;this.grabber&&(this.targetPosition.copy(this.grabDirection),this.targetPosition.applyQuaternion(this.grabber.object3D.getWorldQuaternion(a)).setLength(this.grabDistance).add(this.grabber.object3D.getWorldPosition(b)).add(this.grabOffset),this.deltaPositionIsValid?(this.deltaPosition.sub(this.targetPosition),c=this.el.getAttribute('position'),this.destPosition.x=c.x-this.deltaPosition.x*this.xFactor,this.destPosition.y=c.y-this.deltaPosition.y*this.yFactor,this.destPosition.z=c.z-this.deltaPosition.z*this.zFactor,this.el.setAttribute('position',this.destPosition)):this.deltaPositionIsValid=!0,this.deltaPosition.copy(this.targetPosition))}}(),remove:function(){this.el.removeEventListener(this.GRAB_EVENT,this.start),this.el.removeEventListener(this.UNGRAB_EVENT,this.end),this.physicsRemove()},start:function(a){if(!a.defaultPrevented&&this.startButtonOk(a)){const b=!Number.isFinite(this.data.maxGrabbers)||this.grabbers.lengththis.hoverers.length&&this.el.removeState(this.HOVERED_STATE)}}}); @@ -26,7 +26,7 @@ 'use strict';module.exports={schema:{usePhysics:{default:'ifavailable'}},physicsInit:function(){this.constraints=new Map},physicsUpdate:function(){'never'===this.data.usePhysics&&this.constraints.size&&this.physicsClear()},physicsRemove:function(){this.physicsClear()},physicsStart:function(a){if('never'!==this.data.usePhysics&&this.el.body&&a.detail.hand.body&&!this.constraints.has(a.detail.hand)){const b=Math.random().toString(36).substr(2,9);return this.el.setAttribute('constraint__'+b,{target:a.detail.hand}),this.constraints.set(a.detail.hand,b),!0}return'only'===this.data.usePhysics},physicsEnd:function(a){let b=this.constraints.get(a.detail.hand);b&&(this.el.removeAttribute('constraint__'+b),this.constraints.delete(a.detail.hand))},physicsClear:function(){if(this.el.body)for(let a of this.constraints.values())this.el.body.world.removeConstraint(a);this.constraints.clear()},physicsIsConstrained:function(a){return this.constraints.has(a)},physicsIsGrabbing(){return 0{b.otherSuperHand===a&&(b.otherSuperHand=null)})}}); diff --git a/index.js b/index.js index e14068d..fe2b5d3 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ require('./reaction_components/drag-droppable.js') require('./reaction_components/draggable.js') require('./reaction_components/droppable.js') require('./reaction_components/clickable.js') +require('./reaction_components/activatable.js') /** * Super Hands component for A-Frame. @@ -58,6 +59,18 @@ AFRAME.registerComponent('super-hands', { 'pointdown', 'thumbdown', 'pointingend', 'pistolend', 'thumbstickup', 'mouseup', 'touchend'] }, + activateStartButtons: { + default: ['gripdown', 'trackpaddown', 'triggerdown', 'gripclose', + 'abuttondown', 'bbuttondown', 'xbuttondown', 'ybuttondown', + 'pointup', 'thumbup', 'pointingstart', 'pistolstart', + 'thumbstickdown', 'mousedown', 'touchstart'] + }, + activateEndButtons: { + default: ['gripup', 'trackpadup', 'triggerup', 'gripopen', + 'abuttonup', 'bbuttonup', 'xbuttonup', 'ybuttonup', + 'pointdown', 'thumbdown', 'pointingend', 'pistolend', + 'thumbstickup', 'mouseup', 'touchend'] + }, interval: { default: 0 } }, @@ -82,6 +95,8 @@ AFRAME.registerComponent('super-hands', { this.DRAGOVER_EVENT = 'dragover-start' this.UNDRAGOVER_EVENT = 'dragover-end' this.DRAGDROP_EVENT = 'drag-drop' + this.ACTIVATE_EVENT = 'activate-start' + this.DEACTIVATE_EVENT = 'activate-end' // links to other systems/components this.otherSuperHand = null @@ -106,6 +121,8 @@ AFRAME.registerComponent('super-hands', { this.onStretchEndButton = this.onStretchEndButton.bind(this) this.onDragDropStartButton = this.onDragDropStartButton.bind(this) this.onDragDropEndButton = this.onDragDropEndButton.bind(this) + this.onActivateStartButton = this.onActivateStartButton.bind(this) + this.onActivateEndButton = this.onActivateEndButton.bind(this) this.system.registerMe(this) }, @@ -168,11 +185,16 @@ AFRAME.registerComponent('super-hands', { let carried = this.state.get(this.GRAB_EVENT) this.dispatchMouseEventAll('mousedown', this.el) this.gehClicking = new Set(this.hoverEls) + const detail = { + hand: this.el, + buttonEvent: evt + } if (!carried) { - carried = this.findTarget(this.GRAB_EVENT, { - hand: this.el, - buttonEvent: evt - }) + if (evt.detail && evt.detail.targetEntity && !this.emitCancelable(evt.detail.targetEntity, this.GRAB_EVENT, detail)) { + carried = evt.detail.targetEntity + } else { + carried = this.findTarget(this.GRAB_EVENT, detail) + } if (carried) { this.state.set(this.GRAB_EVENT, carried) this._unHover(carried) @@ -281,6 +303,30 @@ AFRAME.registerComponent('super-hands', { } } }, + onActivateStartButton: function (evt) { + const carried = this.state.get(this.GRAB_EVENT) + let activated = this.state.get(this.ACTIVATE_EVENT) + if (carried) { + if (!activated && !this.emitCancelable(carried, this.ACTIVATE_EVENT, {hand: this.el, buttonEvent: evt})) { + activated = this.state.get(this.ACTIVATE_EVENT) + } + if (activated) { + this.state.set(this.ACTIVATE_EVENT, activated) + } + } + }, + onActivateEndButton: function (evt) { + const carried = this.state.get(this.GRAB_EVENT) + let deactivated = this.state.get(this.DEACTIVATE_EVENT) + if (carried) { + if (!deactivated && !this.emitCancelable(carried, this.DEACTIVATE_EVENT, {hand: this.el, buttonEvent: evt})) { + deactivated = this.state.get(this.DEACTIVATE_EVENT) + } + if (deactivated) { + this.state.set(this.DEACTIVATE_EVENT, deactivated) + } + } + }, processHitEl: function (hitEl, intersection) { const dist = intersection && intersection.distance const sects = this.hoverElsIntersections @@ -434,6 +480,9 @@ AFRAME.registerComponent('super-hands', { this.data.dragDropStartButtons.forEach(b => { this.el.addEventListener(b, this.onDragDropStartButton) }) + this.data.activateStartButtons.forEach(b => { + this.el.addEventListener(b, this.onActivateStartButton) + }) this.data.dragDropEndButtons.forEach(b => { this.el.addEventListener(b, this.onDragDropEndButton) }) @@ -443,6 +492,9 @@ AFRAME.registerComponent('super-hands', { this.data.grabEndButtons.forEach(b => { this.el.addEventListener(b, this.onGrabEndButton) }) + this.data.activateEndButtons.forEach(b => { + this.el.addEventListener(b, this.onActivateEndButton) + }) }, unRegisterListeners: function (data) { data = data || this.data @@ -463,6 +515,9 @@ AFRAME.registerComponent('super-hands', { data.stretchStartButtons.forEach(b => { this.el.removeEventListener(b, this.onStretchStartButton) }) + data.activateStartButtons.forEach(b => { + this.el.removeEventListener(b, this.onActivateStartButton) + }) data.stretchEndButtons.forEach(b => { this.el.removeEventListener(b, this.onStretchEndButton) }) @@ -472,6 +527,9 @@ AFRAME.registerComponent('super-hands', { data.dragDropEndButtons.forEach(b => { this.el.removeEventListener(b, this.onDragDropEndButton) }) + data.activateEndButtons.forEach(b => { + this.el.removeEventListener(b, this.onActivateEndButton) + }) }, emitCancelable: function (target, name, detail) { var data, evt diff --git a/reaction_components/activatable.js b/reaction_components/activatable.js new file mode 100644 index 0000000..f82485d --- /dev/null +++ b/reaction_components/activatable.js @@ -0,0 +1,38 @@ +/* global AFRAME */ +const inherit = AFRAME.utils.extendDeep +const buttonCore = require('./prototypes/buttons-proto.js') + +AFRAME.registerComponent('activatable', inherit({}, buttonCore, { + multiple: true, + schema: { + buttonStartEvent: {default: ''}, + buttonEndEvent: {default: ''}, + activatedState: {default: 'activated'} + }, + init: function () { + this.ACTIVATE_EVENT = 'activate-start' + this.DEACTIVATE_EVENT = 'activate-end' + + this.activateStart = this.activateStart.bind(this) + this.activateEnd = this.activateEnd.bind(this) + + this.el.addEventListener(this.ACTIVATE_EVENT, this.activateStart) + this.el.addEventListener(this.DEACTIVATE_EVENT, this.activateEnd) + }, + remove: function () { + this.el.removeEventListener(this.ACTIVATE_EVENT, this.activateStart) + this.el.removeEventListener(this.DEACTIVATE_EVENT, this.activateEnd) + }, + activateStart: function (evt) { + if (evt.defaultPrevented || !this.startButtonOk(evt)) { return } + if (evt.detail.buttonEvent.type !== this.data.buttonStartEvent) { return } + this.el.addState(this.data.activatedState) + if (evt.preventDefault) { evt.preventDefault() } + }, + activateEnd: function (evt) { + if (evt.defaultPrevented || !this.endButtonOk(evt)) { return } + if (evt.detail.buttonEvent.type !== this.data.buttonEndEvent) { return } + this.el.removeState(this.data.activatedState) + if (evt.preventDefault) { evt.preventDefault() } + } +})) diff --git a/reaction_components/grabbable.js b/reaction_components/grabbable.js index 157ba3a..6bb74d9 100644 --- a/reaction_components/grabbable.js +++ b/reaction_components/grabbable.js @@ -7,6 +7,7 @@ const base = inherit({}, physicsCore, buttonsCore) AFRAME.registerComponent('grabbable', inherit(base, { schema: { maxGrabbers: {type: 'int', default: NaN}, + maxGrabBehavior: {default: 'nothing', oneOf: ['nothing', 'drop']}, invert: {default: false}, suppressY: {default: false} }, @@ -37,33 +38,38 @@ AFRAME.registerComponent('grabbable', inherit(base, { this.zFactor = (this.data.invert) ? -1 : 1 this.yFactor = ((this.data.invert) ? -1 : 1) * !this.data.suppressY }, - tick: function () { - var entityPosition - if (this.grabber) { - // reflect on z-axis to point in same direction as the laser - this.targetPosition.copy(this.grabDirection) - this.targetPosition - .applyQuaternion(this.grabber.object3D.getWorldQuaternion()) - .setLength(this.grabDistance) - .add(this.grabber.object3D.getWorldPosition()) - .add(this.grabOffset) - if (this.deltaPositionIsValid) { - // relative position changes work better with nested entities - this.deltaPosition.sub(this.targetPosition) - entityPosition = this.el.getAttribute('position') - this.destPosition.x = - entityPosition.x - this.deltaPosition.x * this.xFactor - this.destPosition.y = - entityPosition.y - this.deltaPosition.y * this.yFactor - this.destPosition.z = - entityPosition.z - this.deltaPosition.z * this.zFactor - this.el.setAttribute('position', this.destPosition) - } else { - this.deltaPositionIsValid = true + tick: (function () { + var q = new THREE.Quaternion() + var v = new THREE.Vector3() + + return function(){ + var entityPosition + if (this.grabber) { + // reflect on z-axis to point in same direction as the laser + this.targetPosition.copy(this.grabDirection) + this.targetPosition + .applyQuaternion(this.grabber.object3D.getWorldQuaternion(q)) + .setLength(this.grabDistance) + .add(this.grabber.object3D.getWorldPosition(v)) + .add(this.grabOffset) + if (this.deltaPositionIsValid) { + // relative position changes work better with nested entities + this.deltaPosition.sub(this.targetPosition) + entityPosition = this.el.getAttribute('position') + this.destPosition.x = + entityPosition.x - this.deltaPosition.x * this.xFactor + this.destPosition.y = + entityPosition.y - this.deltaPosition.y * this.yFactor + this.destPosition.z = + entityPosition.z - this.deltaPosition.z * this.zFactor + this.el.setAttribute('position', this.destPosition) + } else { + this.deltaPositionIsValid = true + } + this.deltaPosition.copy(this.targetPosition) } - this.deltaPosition.copy(this.targetPosition) } - }, + })(), remove: function () { this.el.removeEventListener(this.GRAB_EVENT, this.start) this.el.removeEventListener(this.UNGRAB_EVENT, this.end) @@ -74,9 +80,13 @@ AFRAME.registerComponent('grabbable', inherit(base, { return } // room for more grabbers? - const grabAvailable = !Number.isFinite(this.data.maxGrabbers) || + let grabAvailable = !Number.isFinite(this.data.maxGrabbers) || this.grabbers.length < this.data.maxGrabbers - + if (Number.isFinite(this.data.maxGrabbers) && !grabAvailable && + this.grabbed && this.data.maxGrabBehavior === 'drop') { + this.grabbers[0].components['super-hands'].onGrabEndButton() + grabAvailable = true + } if (this.grabbers.indexOf(evt.detail.hand) === -1 && grabAvailable) { if (!evt.detail.hand.object3D) { console.warn('grabbable entities must have an object3D') @@ -108,21 +118,25 @@ AFRAME.registerComponent('grabbable', inherit(base, { } if (evt.preventDefault) { evt.preventDefault() } }, - resetGrabber: function () { - let raycaster - if (!this.grabber) { - return false - } - raycaster = this.grabber.getAttribute('raycaster') - this.deltaPositionIsValid = false - this.grabDistance = this.el.object3D.getWorldPosition() - .distanceTo(this.grabber.object3D.getWorldPosition()) - if (raycaster) { - this.grabDirection = raycaster.direction - this.grabOffset = raycaster.origin + resetGrabber: (function () { + var objPos = new THREE.Vector3() + var grabPos = new THREE.Vector3() + return function(){ + let raycaster + if (!this.grabber) { + return false + } + raycaster = this.grabber.getAttribute('raycaster') + this.deltaPositionIsValid = false + this.grabDistance = this.el.object3D.getWorldPosition(objPos) + .distanceTo(this.grabber.object3D.getWorldPosition(grabPos)) + if (raycaster) { + this.grabDirection = raycaster.direction + this.grabOffset = raycaster.origin + } + return true } - return true - }, + })(), lostGrabber: function (evt) { let i = this.grabbers.indexOf(evt.relatedTarget) // if a queued, non-physics grabber leaves the collision zone, forget it diff --git a/reaction_components/stretchable.js b/reaction_components/stretchable.js index 7b34bd2..cc86361 100644 --- a/reaction_components/stretchable.js +++ b/reaction_components/stretchable.js @@ -7,7 +7,8 @@ AFRAME.registerComponent('stretchable', inherit(base, { schema: { usePhysics: {default: 'ifavailable'}, invert: {default: false}, - physicsUpdateRate: {default: 100} + physicsUpdateRate: {default: 100}, + useWorldPosition: {default: false} }, init: function () { this.STRETCHED_STATE = 'stretched' @@ -36,8 +37,13 @@ AFRAME.registerComponent('stretchable', inherit(base, { tick: function (time, timeDelta) { if (!this.stretched) { return } this.scale.copy(this.el.getAttribute('scale')) - this.handPos.copy(this.stretchers[0].getAttribute('position')) - this.otherHandPos.copy(this.stretchers[1].getAttribute('position')) + if (this.data.useWorldPosition) { + this.stretchers[0].object3D.getWorldPosition(this.handPos) + this.stretchers[1].object3D.getWorldPosition(this.otherHandPos) + } else { + this.handPos.copy(this.stretchers[0].getAttribute('position')) + this.otherHandPos.copy(this.stretchers[1].getAttribute('position')) + } const currentStretch = this.handPos.distanceTo(this.otherHandPos) let deltaStretch = 1 if (this.previousStretch !== null && currentStretch !== 0) {