From da40d65f9e87452623489f3cdb67175913195008 Mon Sep 17 00:00:00 2001
From: Kevin Lee <kevin@infinite-lee.com>
Date: Thu, 26 Jul 2018 16:02:39 -0700
Subject: [PATCH] refactoring input for 6dof hands + cursor

---
 src/components/grabbable-toggle.js   |  36 ++++++++
 src/components/input-configurator.js |   2 +-
 src/components/tools/pen.js          |  12 +--
 src/hub.html                         |  39 +++++---
 src/hub.js                           |   1 +
 src/utils/action-event-handler.js    | 130 ++++++++++++---------------
 src/utils/mouse-events-handler.js    |  34 +++----
 7 files changed, 140 insertions(+), 114 deletions(-)
 create mode 100644 src/components/grabbable-toggle.js

diff --git a/src/components/grabbable-toggle.js b/src/components/grabbable-toggle.js
new file mode 100644
index 000000000..ef5f41f98
--- /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 df36b38d8..84daecbf0 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 d39004c84..cbd1407be 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 4a638a83c..2054ba818 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 2faeb0bf3..1e72d96dd 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 e6c7964ac..4f00cfa8a 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 fdae304a9..ed9538ea6 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) {
-- 
GitLab