From 7dd8003dc00b56401ceaa082ea944f190f901bfa Mon Sep 17 00:00:00 2001 From: Kevin Lee <kevin@infinite-lee.com> Date: Mon, 13 Aug 2018 18:05:33 -0700 Subject: [PATCH] change grabbable-toggle into a full replacement for grabbable reaction component; fix various bugs --- package.json | 2 +- src/components/grabbable-toggle.js | 220 +++++++++++++++++++++++------ src/components/super-spawner.js | 4 +- src/components/tools/pen.js | 7 +- src/hub.html | 11 +- src/input-mappings.js | 2 +- src/utils/action-event-handler.js | 16 ++- 7 files changed, 207 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index 1735004d7..99bbebb66 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 55b022b01..41c042367 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 720efc4db..60d346f58 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 214591f6c..56bccc460 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 4f583366f..65fa1527c 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 58f7cdc39..80e708d75 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 a5d057a44..0720f7021 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) { -- GitLab