diff --git a/src/components/grabbable-toggle.js b/src/components/grabbable-toggle.js new file mode 100644 index 0000000000000000000000000000000000000000..ef5f41f98acd2284443bf1fdc78a487d51dcb4d0 --- /dev/null +++ b/src/components/grabbable-toggle.js @@ -0,0 +1,36 @@ +AFRAME.registerComponent("grabbable-toggle", { + schema: { + primaryReleaseEvents: { default: ["hand_release", "action_release"] }, + secondaryReleaseEvents: { default: ["secondary_hand_release", "secondary_action_release"] } + }, + + init() { + this.toggle = false; + 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.isPrimaryRelease(type) && !this.toggle) || this.isSecondaryRelease(type)) { + this.toggle = true; + e.stopImmediatePropagation(); //prevents grabbable from calling preventDefault + } else if (this.toggle && this.isPrimaryRelease(type)) { + this.toggle = false; + } + }, + + isPrimaryRelease(type) { + return this.data.primaryReleaseEvents.indexOf(type) !== -1; + }, + + isSecondaryRelease(type) { + return this.data.secondaryReleaseEvents.indexOf(type) !== -1; + } +}); diff --git a/src/components/input-configurator.js b/src/components/input-configurator.js index df36b38d8c4bb81a2f8fd98824cbd3ecf4ef7f15..84daecbf04f0dc728490c8755f340cf3e8bf404d 100644 --- a/src/components/input-configurator.js +++ b/src/components/input-configurator.js @@ -162,7 +162,7 @@ AFRAME.registerComponent("input-configurator", { this.cursor.el.setAttribute("cursor-controller", { rayObject: this.data.gazeCursorRayObject }); } - if (this.actionEventHandler) { + if (this.actionEventHandler && this.controller) { this.actionEventHandler.setHandThatAlsoDrivesCursor(this.controller); } } diff --git a/src/components/tools/pen.js b/src/components/tools/pen.js index d39004c845c25a1ec3c215a5e7ce800b375c9cdd..cbd1407beed3beb32f08e55b283e2c6b461ed4db 100644 --- a/src/components/tools/pen.js +++ b/src/components/tools/pen.js @@ -148,10 +148,12 @@ AFRAME.registerComponent("pen", { }, endDraw() { - this.isDrawing = false; - this.timeSinceLastDraw = 0; - this.el.object3D.getWorldPosition(this.worldPosition); - this.getNormal(this.normal, this.worldPosition, this.direction); - this.currentDrawing.endDraw(this.worldPosition, this.direction, this.normal); + if (this.isDrawing) { + this.isDrawing = false; + this.timeSinceLastDraw = 0; + this.el.object3D.getWorldPosition(this.worldPosition); + this.getNormal(this.normal, this.worldPosition, this.direction); + this.currentDrawing.endDraw(this.worldPosition, this.direction, this.normal); + } } }); diff --git a/src/hub.html b/src/hub.html index 4a638a83c288124e2b9dc3d8d6458a3ad56973e3..2054ba818d9b8d5c1b7725c3d3a2c3d304c8586a 100644 --- a/src/hub.html +++ b/src/hub.html @@ -223,6 +223,7 @@ class="interactable sticky" super-networked-interactable="counter: #counter; mass: 1;" body="type: dynamic; shape: none; mass: 1;" + grabbable-toggle grabbable="maxGrabbers: 1; maxGrabBehavior: drop;" hoverable activatable__hands="buttonStartEvent: secondary_action_grab; buttonEndEvent: secondary_action_release;" @@ -267,12 +268,18 @@ <a-mixin id="controller-super-hands" super-hands=" - colliderEvent: collisions; colliderEventProperty: els; - colliderEndEvent: collisions; colliderEndEventProperty: clearedEls; - grabStartButtons: hand_grab; grabEndButtons: hand_release; - stretchStartButtons: hand_grab; stretchEndButtons: hand_release; - dragDropStartButtons: hand_grab; dragDropEndButtons: hand_release; - activateStartButtons: secondary_action_grab; activateEndButtons: secondary_action_release;" + colliderEvent: collisions; + colliderEventProperty: els; + colliderEndEvent: collisions; + colliderEndEventProperty: clearedEls; + grabStartButtons: hand_grab, secondary_hand_grab; + grabEndButtons: hand_release, secondary_hand_release; + stretchStartButtons: hand_grab, secondary_hand_grab; + stretchEndButtons: hand_release, secondary_hand_release; + dragDropStartButtons: hand_grab, secondary_hand_grab; + dragDropEndButtons: hand_release, secondary_hand_release; + activateStartButtons: secondary_hand_grab, secondary_action_grab; + activateEndButtons: secondary_hand_release, secondary_action_release;" collision-filter="collisionForces: false" physics-collider ></a-mixin> @@ -298,12 +305,18 @@ static-body="shape: sphere;" collision-filter="collisionForces: false" super-hands=" - colliderEvent: raycaster-intersection; colliderEventProperty: els; - colliderEndEvent: raycaster-intersection-cleared; colliderEndEventProperty: clearedEls; - grabStartButtons: cursor-grab; grabEndButtons: cursor-release; - stretchStartButtons: cursor-grab; stretchEndButtons: cursor-release; - dragDropStartButtons: cursor-grab; dragDropEndButtons: cursor-release; - activateStartButtons: secondary_action_grab, secondary-cursor-grab; activateEndButtons: secondary_action_release, secondary-cursor-release;" + colliderEvent: raycaster-intersection; + colliderEventProperty: els; + colliderEndEvent: raycaster-intersection-cleared; + colliderEndEventProperty: clearedEls; + grabStartButtons: cursor-grab, hand_grab, secondary_hand_grab; + grabEndButtons: cursor-release, hand_release, secondary_hand_release; + stretchStartButtons: cursor-grab, hand_grab, secondary_hand_grab; + stretchEndButtons: cursor-release, hand_release, secondary_hand_release; + dragDropStartButtons: cursor-grab, hand_grab, secondary_hand_grab; + dragDropEndButtons: cursor-release, hand_release, secondary_hand_release; + activateStartButtons: secondary_hand_grab, secondary_action_grab, secondary-cursor-grab; + activateEndButtons: secondary_hand_release, secondary_action_release, secondary-cursor-release;" segments-height="9" segments-width="9" event-repeater="events: raycaster-intersection, raycaster-intersection-cleared; eventSource: #cursor-controller" @@ -454,7 +467,7 @@ <a-entity gltf-model-plus="src: #interactable-pen" - class="interactable" + class="interactable sticky" super-spawner="template: #pen-interactable;" position="-1 1.2 -5.5" scale="0.5 0.5 0.5" diff --git a/src/hub.js b/src/hub.js index 2faeb0bf360dab2c37a28ad0ace7f868c1d2cd16..1e72d96dd5074720683c4c4bc290426a1a7251b0 100644 --- a/src/hub.js +++ b/src/hub.js @@ -120,6 +120,7 @@ import "./components/event-repeater"; import "./components/controls-shape-offset"; import "./components/duck"; import "./components/quack"; +import "./components/grabbable-toggle"; import "./components/cardboard-controls"; diff --git a/src/utils/action-event-handler.js b/src/utils/action-event-handler.js index e6c7964ac0e066eea64ff8159d00393634f6c49a..4f00cfa8a33c6c8fed4a15028fa64266e8383c48 100644 --- a/src/utils/action-event-handler.js +++ b/src/utils/action-event-handler.js @@ -2,17 +2,18 @@ export default class ActionEventHandler { constructor(scene, cursor) { this.scene = scene; this.cursor = cursor; + this.cursorHand = this.cursor.data.cursor.components["super-hands"]; this.isCursorInteracting = false; - this.isCursorInteractingOnGrab = false; this.isTeleporting = false; this.handThatAlsoDrivesCursor = null; this.hovered = false; - this.currentlyGrabbingSticky = {}; this.onPrimaryDown = this.onPrimaryDown.bind(this); this.onPrimaryUp = this.onPrimaryUp.bind(this); - this.onGrab = this.onGrab.bind(this); - this.onRelease = this.onRelease.bind(this); + this.onPrimaryGrab = this.onPrimaryGrab.bind(this); + this.onPrimaryRelease = this.onPrimaryRelease.bind(this); + this.onSecondaryGrab = this.onSecondaryGrab.bind(this); + this.onSecondaryRelease = this.onSecondaryRelease.bind(this); this.onCardboardButtonDown = this.onCardboardButtonDown.bind(this); this.onCardboardButtonUp = this.onCardboardButtonUp.bind(this); this.onMoveDuck = this.onMoveDuck.bind(this); @@ -24,10 +25,10 @@ export default class ActionEventHandler { this.scene.addEventListener("action_primary_up", this.onPrimaryUp); this.scene.addEventListener("action_secondary_down", this.onPrimaryDown); this.scene.addEventListener("action_secondary_up", this.onPrimaryUp); - this.scene.addEventListener("action_grab", this.onGrab); - this.scene.addEventListener("action_release", this.onRelease); - this.scene.addEventListener("secondary_action_grab", this.onGrab); - this.scene.addEventListener("secondary_action_release", this.onRelease); + this.scene.addEventListener("action_grab", this.onPrimaryGrab); + this.scene.addEventListener("action_release", this.onPrimaryRelease); + this.scene.addEventListener("secondary_action_grab", this.onSecondaryGrab); + this.scene.addEventListener("secondary_action_release", this.onSecondaryRelease); this.scene.addEventListener("move_duck", this.onMoveDuck); this.scene.addEventListener("cardboardbuttondown", this.onCardboardButtonDown); // TODO: These should be actions this.scene.addEventListener("cardboardbuttonup", this.onCardboardButtonUp); @@ -38,10 +39,10 @@ export default class ActionEventHandler { this.scene.removeEventListener("action_primary_up", this.onPrimaryUp); this.scene.removeEventListener("action_secondary_down", this.onPrimaryDown); this.scene.removeEventListener("action_secondary_up", this.onPrimaryUp); - this.scene.removeEventListener("action_grab", this.onGrab); - this.scene.removeEventListener("action_release", this.onRelease); - this.scene.removeEventListener("secondary_action_grab", this.onGrab); - this.scene.removeEventListener("secondary_action_release", this.onRelease); + this.scene.removeEventListener("action_grab", this.onPrimaryGrab); + this.scene.removeEventListener("action_release", this.onPrimaryRelease); + this.scene.removeEventListener("secondary_action_grab", this.onSecondaryGrab); + this.scene.removeEventListener("secondary_action_release", this.onSecondaryRelease); this.scene.removeEventListener("move_duck", this.onMoveDuck); this.scene.removeEventListener("cardboardbuttondown", this.onCardboardButtonDown); this.scene.removeEventListener("cardboardbuttonup", this.onCardboardButtonUp); @@ -59,85 +60,65 @@ export default class ActionEventHandler { return el && el.matches(".sticky, .sticky *"); } - doGrab(e, primary_event_type) { + isHandThatAlsoDrivesCursor(el) { + return this.handThatAlsoDrivesCursor === el; + } + + onGrab(e, event) { const superHand = e.target.components["super-hands"]; - const cursorHand = - this.isCursorInteractingOnGrab && this.handThatAlsoDrivesCursor === e.target - ? this.cursor.data.cursor.components["super-hands"] - : null; - let grabbed = superHand.state.get("grab-start") || (cursorHand ? cursorHand.state.get("grab-start") : null); - if (this.currentlyGrabbingSticky[e.target.id] && !grabbed) { - this.currentlyGrabbingSticky[e.target.id] = false; - } - const validGrab = - !this.isSticky(grabbed) || !this.currentlyGrabbingSticky[e.target.id] || e.type === primary_event_type; + const isCursorHand = this.isHandThatAlsoDrivesCursor(e.target); - if (this.handThatAlsoDrivesCursor && this.handThatAlsoDrivesCursor === e.target && !this.isCursorInteracting) { - if (superHand.state.has("hover-start") && validGrab) { - e.target.emit("hand_grab"); + if (isCursorHand && !this.isCursorInteracting) { + if (superHand.state.has("hover-start")) { + e.target.emit(event); } else { this.isCursorInteracting = this.cursor.startInteraction(); - if (this.isCursorInteracting) { - this.isCursorInteractingOnGrab = true; - } } - } else if (cursorHand !== null) { - cursorHand.el.emit(e.type); - } else if (!this.isCursorInteractingOnGrab && validGrab) { - e.target.emit("hand_grab"); + } else if (isCursorHand && this.isCursorInteracting) { + this.cursorHand.el.emit(event); + } else { + e.target.emit(event); } - - grabbed = superHand.state.get("grab-start") || (cursorHand ? cursorHand.state.get("grab-start") : null); - return this.isCursorInteractingOnGrab || grabbed !== null; } - doRelease(e, primary_event_type) { + onRelease(e, event) { const superHand = e.target.components["super-hands"]; - const cursorHand = - this.isCursorInteractingOnGrab && this.handThatAlsoDrivesCursor === e.target - ? this.cursor.data.cursor.components["super-hands"] - : null; - let grabbed = superHand.state.get("grab-start") || (cursorHand ? cursorHand.state.get("grab-start") : null); - if ( - (!this.currentlyGrabbingSticky[e.target.id] && this.isSticky(grabbed)) || - (this.currentlyGrabbingSticky[e.target.id] && e.type !== primary_event_type) - ) { - if (this.currentlyGrabbingSticky[e.target.id] && cursorHand !== null) { - cursorHand.el.emit(e.type); + const isCursorHand = this.isHandThatAlsoDrivesCursor(e.target); + + if (this.isCursorInteracting && isCursorHand) { + //need to check both grab-start and hover-start in the case that the spawner is being grabbed this frame + if (this.isSticky(this.cursorHand.state.get("grab-start") || this.cursorHand.state.get("hover-start"))) { + this.cursorHand.el.emit(event); + this.isCursorInteracting = !!this.cursorHand.state.get("grab-start"); + } else { + this.isCursorInteracting = false; + this.cursor.endInteraction(); } - this.currentlyGrabbingSticky[e.target.id] = true; - return true; } else { - this.currentlyGrabbingSticky[e.target.id] = false; + e.target.emit(event); } + } - if ( - this.isCursorInteracting && - this.isCursorInteractingOnGrab && - this.handThatAlsoDrivesCursor && - this.handThatAlsoDrivesCursor === e.target - ) { - this.isCursorInteracting = false; - this.isCursorInteractingOnGrab = false; - this.cursor.endInteraction(); - } else { - e.target.emit("hand_release"); - } + onPrimaryGrab(e) { + this.onGrab(e, "hand_grab"); + } - grabbed = superHand.state.get("grab-start") || (cursorHand ? cursorHand.state.get("grab-start") : null); - return !this.isCursorInteractingOnGrab && grabbed === null; + onPrimaryRelease(e) { + this.onRelease(e, "hand_release"); } - onGrab(e) { - this.doGrab(e, "action_grab"); + onSecondaryGrab(e) { + this.onGrab(e, "secondary_hand_grab"); } - onRelease(e) { - this.doRelease(e, "action_release"); + onSecondaryRelease(e) { + this.onRelease(e, "secondary_hand_release"); } onPrimaryDown(e) { - if (!this.doGrab(e, "action_primary_down")) { + this.onGrab(e, "secondary_hand_grab"); + + if (this.isHandThatAlsoDrivesCursor(e.target) && !this.isCursorInteracting) { this.cursor.setCursorVisibility(false); const button = e.target.components["teleport-controls"].data.button; e.target.emit(button + "down"); @@ -146,13 +127,14 @@ export default class ActionEventHandler { } onPrimaryUp(e) { - if (!this.doRelease(e, "primary_action_up")) { - if (isCursorHand) { - this.cursor.setCursorVisibility(!state.has("hover-start")); - } + if (this.isTeleporting && this.isHandThatAlsoDrivesCursor(e.target)) { + const superHand = e.target.components["super-hands"]; + this.cursor.setCursorVisibility(!superHand.state.has("hover-start")); const button = e.target.components["teleport-controls"].data.button; e.target.emit(button + "up"); this.isTeleporting = false; + } else { + this.onRelease(e, "secondary_hand_release"); } } diff --git a/src/utils/mouse-events-handler.js b/src/utils/mouse-events-handler.js index fdae304a9c2b90a43b2ed1a22df0e687354e39fa..ed9538ea6aee03d476c403b98adadc1eb3b62fbd 100644 --- a/src/utils/mouse-events-handler.js +++ b/src/utils/mouse-events-handler.js @@ -11,7 +11,6 @@ export default class MouseEventsHandler { this.isLeftButtonDown = false; this.isLeftButtonHandledByCursor = false; this.isPointerLocked = false; - this.currentlyGrabbingSticky = false; this.onMouseDown = this.onMouseDown.bind(this); this.onMouseMove = this.onMouseMove.bind(this); @@ -60,12 +59,11 @@ export default class MouseEventsHandler { } onLeftButtonDown() { - if (this.currentlyGrabbingSticky) { + this.isLeftButtonDown = true; + if (this.isSticky(this.superHand.state.get("grab-start"))) { this.superHand.el.emit("secondary-cursor-grab"); - } else { - this.isLeftButtonDown = true; - this.isLeftButtonHandledByCursor = this.cursor.startInteraction(); } + this.isLeftButtonHandledByCursor = this.cursor.startInteraction(); } onRightButtonDown() { @@ -106,34 +104,28 @@ export default class MouseEventsHandler { onMouseUp(e) { switch (e.button) { case 0: //left button - const grabbed = this.superHand.state.get("grab-start"); - if (!this.isSticky(grabbed)) { - this.endCursorInteraction(); - } else if (!this.currentlyGrabbingSticky) { - this.currentlyGrabbingSticky = true; - } else { + if (this.isSticky(this.superHand.state.get("grab-start"))) { this.superHand.el.emit("secondary-cursor-release"); + } else { + this.endInteraction(); } + this.isLeftButtonDown = false; break; case 1: //middle/scroll button break; case 2: //right button - this.endCursorInteraction(); + this.endInteraction(); break; } } - isSticky(el) { - return el && el.matches(".sticky, .sticky *"); + endInteraction() { + this.cursor.endInteraction(); + this.isLeftButtonHandledByCursor = false; } - endCursorInteraction() { - if (this.isLeftButtonHandledByCursor) { - this.cursor.endInteraction(); - } - this.isLeftButtonHandledByCursor = false; - this.isLeftButtonDown = false; - this.currentlyGrabbingSticky = false; + isSticky(el) { + return el && el.matches(".sticky, .sticky *"); } look(e) {