diff --git a/package.json b/package.json index 1735004d72b5796a91194f8332fc5182b9c37eba..99bbebb66ab9132c268a61a6ab9ebdbc4ca36774 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "react-dom": "^16.1.1", "react-intl": "^2.4.0", "screenfull": "^3.3.2", - "super-hands": "github:mozillareality/aframe-super-hands-component#feature/drawing-work-branch", + "super-hands": "github:mozillareality/aframe-super-hands-component#feature/drawing", "three": "github:mozillareality/three.js#8b1886c384371c3e6305b757d1db7577c5201a9b", "three-to-cannon": "1.3.0", "uuid": "^3.2.1", diff --git a/src/components/grabbable-toggle.js b/src/components/grabbable-toggle.js index 55b022b01fb361c7d6441fc5f008f31ae95092e0..41c042367ddce2647b1943d15afb8fc1117d066a 100644 --- a/src/components/grabbable-toggle.js +++ b/src/components/grabbable-toggle.js @@ -1,46 +1,182 @@ -AFRAME.registerComponent("grabbable-toggle", { - schema: { - primaryReleaseEvents: { default: ["primary_hand_release"] }, - secondaryReleaseEvents: { default: ["secondary_hand_release"] } - }, - - init() { - this.toggle = false; - this.currentHand = null; - - this.onGrabEnd = this.onGrabEnd.bind(this); - this.el.addEventListener("grab-end", this.onGrabEnd); - this.el.classList.add("sticky"); - }, - - remove() { - this.el.removeEventListener("grab-end", this.onGrabEnd); - this.el.classList.remove("sticky"); - }, - - onGrabEnd(e) { - const type = e.detail && e.detail.buttonEvent ? e.detail.buttonEvent.type : null; - - if (this.toggle && this.currentHand !== null && this.currentHand !== e.detail.hand) { - this.toggle = false; - this.currentHand = null; - } +/* global AFRAME, THREE */ +const inherit = AFRAME.utils.extendDeep; +const physicsCore = require("super-hands/reaction_components/prototypes/physics-grab-proto.js"); +const buttonsCore = require("super-hands/reaction_components/prototypes/buttons-proto.js"); +// new object with all core modules +const base = inherit({}, physicsCore, buttonsCore); +AFRAME.registerComponent( + "grabbable-toggle", + inherit(base, { + schema: { + maxGrabbers: { type: "int", default: NaN }, + invert: { default: false }, + suppressY: { default: false }, + primaryReleaseEvents: { default: ["primary_hand_release"] }, + secondaryReleaseEvents: { default: ["secondary_hand_release"] } + }, + init: function() { + this.GRABBED_STATE = "grabbed"; + this.GRAB_EVENT = "grab-start"; + this.UNGRAB_EVENT = "grab-end"; + this.grabbed = false; + this.grabbers = []; + this.constraints = new Map(); + this.deltaPositionIsValid = false; + this.grabDistance = undefined; + this.grabDirection = { x: 0, y: 0, z: -1 }; + this.grabOffset = { x: 0, y: 0, z: 0 }; + // persistent object speeds up repeat setAttribute calls + 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, e => this.start(e)); + this.el.addEventListener(this.UNGRAB_EVENT, e => this.end(e)); + this.el.addEventListener("mouseout", e => this.lostGrabber(e)); - if ((this.isPrimaryRelease(type) && !this.toggle) || this.isSecondaryRelease(type)) { - this.toggle = true; - this.currentHand = e.detail.hand; - e.stopImmediatePropagation(); //prevents grabbable from calling preventDefault - } else if (this.toggle && this.isPrimaryRelease(type)) { this.toggle = false; - this.currentHand = null; - } - }, + this.lastGrabber = null; + }, + 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 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); + } + }; + })(), + remove: function() { + this.el.removeEventListener(this.GRAB_EVENT, this.start); + this.el.removeEventListener(this.UNGRAB_EVENT, this.end); + this.physicsRemove(); + }, + start: function(evt) { + if (evt.defaultPrevented || !this.startButtonOk(evt)) { + return; + } + // room for more grabbers? + let grabAvailable = !Number.isFinite(this.data.maxGrabbers) || this.grabbers.length < this.data.maxGrabbers; + if (Number.isFinite(this.data.maxGrabbers) && !grabAvailable && this.grabbed) { + 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"); + return; + } + this.grabbers.push(evt.detail.hand); + // initiate physics if available, otherwise manual + if (!this.physicsStart(evt) && !this.grabber) { + this.grabber = evt.detail.hand; + this.resetGrabber(); + } + // notify super-hands that the gesture was accepted + if (evt.preventDefault) { + evt.preventDefault(); + } + this.grabbed = true; + this.el.addState(this.GRABBED_STATE); + } + }, + end: function(evt) { + const handIndex = this.grabbers.indexOf(evt.detail.hand); + if (evt.defaultPrevented || !this.endButtonOk(evt)) { + return; + } + + const type = evt.detail && evt.detail.buttonEvent ? evt.detail.buttonEvent.type : null; - isPrimaryRelease(type) { - return this.data.primaryReleaseEvents.indexOf(type) !== -1; - }, + if (this.toggle && this.lastGrabber !== this.grabbers[0]) { + this.toggle = false; + this.lastGrabber = null; + } - isSecondaryRelease(type) { - return this.data.secondaryReleaseEvents.indexOf(type) !== -1; - } -}); + if (handIndex !== -1) { + this.grabbers.splice(handIndex, 1); + this.grabber = this.grabbers[0]; + } + + if ((this.isPrimaryRelease(type) && !this.toggle) || this.isSecondaryRelease(type)) { + this.toggle = true; + this.lastGrabber = this.grabbers[0]; + return; + } else if (this.toggle && this.isPrimaryRelease(type)) { + this.toggle = false; + this.lastGrabber = null; + } + + this.physicsEnd(evt); + if (!this.resetGrabber()) { + this.grabbed = false; + this.el.removeState(this.GRABBED_STATE); + } + if (evt.preventDefault) { + evt.preventDefault(); + } + }, + 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; + }; + })(), + lostGrabber: function(evt) { + let i = this.grabbers.indexOf(evt.relatedTarget); + // if a queued, non-physics grabber leaves the collision zone, forget it + if (i !== -1 && evt.relatedTarget !== this.grabber && !this.physicsIsConstrained(evt.relatedTarget)) { + this.grabbers.splice(i, 1); + } + }, + + isPrimaryRelease(type) { + return this.data.primaryReleaseEvents.indexOf(type) !== -1; + }, + + isSecondaryRelease(type) { + return this.data.secondaryReleaseEvents.indexOf(type) !== -1; + } + }) +); diff --git a/src/components/super-spawner.js b/src/components/super-spawner.js index 720efc4db2cce0a9299fcc5d193d86be6d5b2e6f..60d346f584692b11d4867593fefa6d3b2f2ed2ff 100644 --- a/src/components/super-spawner.js +++ b/src/components/super-spawner.js @@ -40,8 +40,8 @@ AFRAME.registerComponent("super-spawner", { /** * The events to emit for programmatically grabbing and releasing objects */ - grabEvents: { default: ["cursor-grab", "hand_grab"] }, - releaseEvents: { default: ["cursor-release", "hand_release"] }, + grabEvents: { default: ["cursor-grab", "primary_hand_grab"] }, + releaseEvents: { default: ["cursor-release", "primary_hand_release"] }, /** * The spawner will become invisible and ungrabbable for this ammount of time after being grabbed. This can prevent rapidly spawning objects. diff --git a/src/components/tools/pen.js b/src/components/tools/pen.js index 214591f6c7825b8f0b4d3e00ce43f02a9141d673..56bccc46064dc04848f9e3c59ce4df1d714b0d2a 100644 --- a/src/components/tools/pen.js +++ b/src/components/tools/pen.js @@ -17,7 +17,7 @@ AFRAME.registerComponent("pen", { drawingManager: { type: "string" }, color: { type: "color", default: "#FF0000" }, availableColors: { - default: ["#FF0033", "FFFF00", "#00FF33", "#0099FF", "#9900FF", "#FFFFFF", "#000000"] + default: ["#FF0033", "#FFFF00", "#00FF33", "#0099FF", "#9900FF", "#FFFFFF", "#000000"] } }, @@ -125,14 +125,19 @@ AFRAME.registerComponent("pen", { case "colorPrev": this.changeColor(-1); break; + default: + break; } }, stateRemoved(evt) { switch (evt.detail) { case "activated": + case "grabbed": this.endDraw(); break; + default: + break; } } }); diff --git a/src/hub.html b/src/hub.html index 4f583366f64a59d527b9be96fe50faccea476d26..65fa1527c1eada51bef7ab5179ccbca074d76af8 100644 --- a/src/hub.html +++ b/src/hub.html @@ -190,13 +190,12 @@ super-networked-interactable="counter: #pen-counter; mass: 1;" body="type: dynamic; shape: none; mass: 1;" sticky-object="autoLockOnRelease: true; autoLockOnLoad: true;" - grabbable-toggle - grabbable="maxGrabbers: 1; maxGrabBehavior: drop;" + grabbable-toggle="maxGrabbers: 1; maxGrabBehavior: drop;" hoverable activatable__drawHand="buttonStartEvent: secondary_hand_grab; buttonEndEvent: secondary_hand_release;" activatable__drawCursor="buttonStartEvent: secondary-cursor-grab; buttonEndEvent: secondary-cursor-release;" - activatable__colorNext="buttonStartEvent: tertiary_action_north; buttonEndEvent: action_primary_up; activatedState: colorNext;" - activatable__colorPrev="buttonStartEvent: tertiary_action_south; buttonEndEvent: action_primary_up; activatedState: colorPrev;" + activatable__colorNext="buttonStartEvent: tertiary_action_north; buttonEndEvent: secondary_hand_release; activatedState: colorNext;" + activatable__colorPrev="buttonStartEvent: tertiary_action_south; buttonEndEvent: secondary_hand_release; activatedState: colorPrev;" scale="0.5 0.5 0.5" > <a-sphere @@ -248,7 +247,7 @@ dragDropStartButtons: hand_grab, secondary_hand_grab; dragDropEndButtons: hand_release, secondary_hand_release; activateStartButtons: secondary_hand_grab, tertiary_action_north, tertiary_action_south; - activateEndButtons: secondary_hand_release, action_primary_up;" + activateEndButtons: secondary_hand_release;" collision-filter="collisionForces: false" physics-collider ></a-mixin> @@ -288,7 +287,7 @@ dragDropStartButtons: cursor-grab, primary_hand_grab, secondary_hand_grab; dragDropEndButtons: cursor-release, primary_hand_release, secondary_hand_release; activateStartButtons: secondary-cursor-grab, secondary_hand_grab, tertiary_action_north, tertiary_action_south; - activateEndButtons: secondary-cursor-release, secondary_hand_release, action_primary_up;" + activateEndButtons: secondary-cursor-release, secondary_hand_release;" segments-height="9" segments-width="9" event-repeater="events: raycaster-intersection, raycaster-intersection-cleared; eventSource: #cursor-controller" diff --git a/src/input-mappings.js b/src/input-mappings.js index 58f7cdc39608a4787c9678586fd6a93bfbd6b430..80e708d755cc7a389e5a39b30df81738895f754e 100644 --- a/src/input-mappings.js +++ b/src/input-mappings.js @@ -51,7 +51,7 @@ const config = { "trackpad.pressedmove": { left: "move" }, trackpad_dpad4_pressed_west_down: { right: "snap_rotate_left" }, trackpad_dpad4_pressed_east_down: { right: "snap_rotate_right" }, - trackpad_dpad4_pressed_center_down: { right: "action_primary_down" }, + trackpad_dpad4_pressed_center_down: { left: "action_primary_down", right: "action_primary_down" }, trackpad_dpad4_pressed_north_down: { left: "tertiary_action_north", right: "tertiary_action_north" }, trackpad_dpad4_pressed_south_down: { left: "tertiary_action_south", right: "tertiary_action_south" }, trackpadup: { left: "action_primary_up", right: "action_primary_up" }, diff --git a/src/utils/action-event-handler.js b/src/utils/action-event-handler.js index a5d057a448d3520bd05df0e922d027cea2fe5965..0720f7021d427ef7be45dafac52c7b6fe54fe4f6 100644 --- a/src/utils/action-event-handler.js +++ b/src/utils/action-event-handler.js @@ -8,6 +8,8 @@ export default class ActionEventHandler { this.handThatAlsoDrivesCursor = null; this.hovered = false; + this.gotPrimaryDown = false; + this.onPrimaryDown = this.onPrimaryDown.bind(this); this.onPrimaryUp = this.onPrimaryUp.bind(this); this.onSecondaryDown = this.onSecondaryDown.bind(this); @@ -119,7 +121,11 @@ export default class ActionEventHandler { onDown(e, event) { this.onGrab(e, event); - if (this.isHandThatAlsoDrivesCursor(e.target) && !this.isCursorInteracting) { + if ( + this.isHandThatAlsoDrivesCursor(e.target) && + !this.isCursorInteracting && + !this.cursorHand.state.get("grab-start") + ) { this.cursor.setCursorVisibility(false); const button = e.target.components["teleport-controls"].data.button; e.target.emit(button + "down"); @@ -141,10 +147,16 @@ export default class ActionEventHandler { onPrimaryDown(e) { this.onDown(e, "primary_hand_grab"); + this.gotPrimaryDown = true; } onPrimaryUp(e) { - this.onUp(e, "primary_hand_release"); + if (this.gotPrimaryDown) { + this.onUp(e, "primary_hand_release"); + } else { + this.onUp(e, "secondary_hand_release"); + } + this.gotPrimaryDown = false; } onSecondaryDown(e) {