From e931796699fa559fcac899b18cc14a1d2a9c63e3 Mon Sep 17 00:00:00 2001
From: joni <johnfshaughnessy@gmail.com>
Date: Wed, 23 May 2018 17:41:40 -0700
Subject: [PATCH] Remove look-controls. Simplify cursor-controller for mouse
 and touch events.

---
 src/components/camera-controller.js |  25 +++++
 src/components/cursor-controller.js |  34 ++----
 src/components/look-on-mobile.js    |  27 +++--
 src/hub.js                          |  24 ++---
 src/utils/PrimaryActionHandler.js   |  13 +++
 src/utils/mouse-events-handler.js   | 154 +++++++++++++++++++---------
 src/utils/touch-events-handler.js   |  34 +++---
 7 files changed, 193 insertions(+), 118 deletions(-)
 create mode 100644 src/components/camera-controller.js
 create mode 100644 src/utils/PrimaryActionHandler.js

diff --git a/src/components/camera-controller.js b/src/components/camera-controller.js
new file mode 100644
index 000000000..7e4fe975c
--- /dev/null
+++ b/src/components/camera-controller.js
@@ -0,0 +1,25 @@
+AFRAME.registerComponent("camera-controller", {
+  schema: {
+    lookDownLimit: { default: -50 },
+    lookUpLimit: { default: 50 }
+  },
+
+  init() {
+    this.pitch = 0;
+    this.yaw = 0;
+    this.rotation = { x: 0, y: 0, z: 0 };
+  },
+
+  look(deltaPitch, deltaYaw) {
+    const { lookDownLimit, lookUpLimit } = this.data;
+    this.pitch += deltaPitch;
+    this.pitch = Math.max(lookDownLimit, Math.min(lookUpLimit, this.pitch));
+    this.yaw += deltaYaw;
+  },
+
+  tick() {
+    this.rotation.x = this.pitch;
+    this.rotation.y = this.yaw;
+    this.el.setAttribute("rotation", this.rotation);
+  }
+});
diff --git a/src/components/cursor-controller.js b/src/components/cursor-controller.js
index 61aa0f26a..df930567f 100644
--- a/src/components/cursor-controller.js
+++ b/src/components/cursor-controller.js
@@ -43,14 +43,12 @@ AFRAME.registerComponent("cursor-controller", {
 
     window.APP.touchEventsHandler.registerCursor(this);
     window.APP.touchEventsHandler.registerPinchEmitter(this.el);
-    this.handleTouchStart = this.handleTouchStart.bind(this);
-    this.handleTouchMove = this.handleTouchMove.bind(this);
-    this.handleTouchEnd = this.handleTouchEnd.bind(this);
-
     window.APP.mouseEventsHandler.registerCursor(this);
-    this.handleMouseDown = this.handleMouseDown.bind(this);
-    this.handleMouseMove = this.handleMouseMove.bind(this);
-    this.handleMouseUp = this.handleMouseUp.bind(this);
+
+    this.startInteractionAndForceCursorUpdate = this.startInteractionAndForceCursorUpdate.bind(this);
+    this.startInteraction = this.startInteraction.bind(this);
+    this.moveCursor = this.moveCursor.bind(this);
+    this.endInteraction = this.endInteraction.bind(this);
     this.handleMouseWheel = this.handleMouseWheel.bind(this);
 
     this._handleEnterVR = this._handleEnterVR.bind(this);
@@ -232,7 +230,7 @@ AFRAME.registerComponent("cursor-controller", {
     this._setCursorVisibility(true);
   },
 
-  handleTouchStart: function(touch) {
+  startInteractionAndForceCursorUpdate: function(touch) {
     // Update the ray and cursor positions
     const raycasterComp = this.el.components.raycaster;
     const raycaster = raycasterComp.raycaster;
@@ -253,34 +251,22 @@ AFRAME.registerComponent("cursor-controller", {
     return true;
   },
 
-  handleTouchMove: function(touch) {
-    this.mousePos.set(touch.clientX / window.innerWidth * 2 - 1, -(touch.clientY / window.innerHeight) * 2 + 1);
+  moveCursor: function(e) {
+    this.mousePos.set(e.clientX / window.innerWidth * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1);
   },
 
-  handleTouchEnd: function() {
+  endInteraction: function() {
     this.data.cursor.emit("cursor-release", {});
   },
 
-  handleMouseDown: function() {
+  startInteraction: function() {
     if (this._isTargetOfType(TARGET_TYPE_INTERACTABLE_OR_UI)) {
       this.data.cursor.emit("cursor-grab", {});
       return true;
-    } else if (this.inVR || this.isMobile) {
-      this._startTeleport();
-      return;
     }
     return false;
   },
 
-  handleMouseMove: function(e) {
-    this.mousePos.set(e.clientX / window.innerWidth * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1);
-  },
-
-  handleMouseUp: function() {
-    this.data.cursor.emit("cursor-release", {});
-    this._endTeleport();
-  },
-
   handleMouseWheel: function(e) {
     if (this._isGrabbing()) {
       switch (e.deltaMode) {
diff --git a/src/components/look-on-mobile.js b/src/components/look-on-mobile.js
index 7c99a261c..4c068669b 100644
--- a/src/components/look-on-mobile.js
+++ b/src/components/look-on-mobile.js
@@ -49,35 +49,32 @@ AFRAME.registerComponent("look-on-mobile", {
     this.polyfillObject = new THREE.Object3D();
     this.polyfillControls = new PolyfillControls(this.polyfillObject);
   },
+
   pause() {
     this.el.removeEventListener("rotateX", this.onRotateX);
     this.polyfillControls = null;
     this.polyfillObject = null;
   },
+
   onRotateX(e) {
-    this.pendingLookX = e.detail.value * 0.8;
+    this.pendingLookX = e.detail.value;
   },
 
-  registerLookControls(lookControls) {
-    this.lookControls = lookControls;
-    this.lookControls.data.enabled = false;
-    this.lookControls.polyfillControls.update = () => {};
+  registerCameraController(cameraController) {
+    this.cameraController = cameraController;
   },
 
   tick(t, dt) {
     if (!this.data.enabled) return;
     const scene = this.el.sceneEl;
+    if (scene.is("vr-mode") && scene.checkHeadsetConnected()) return; // TODO: Why would this be ticking if we're in vr-mode?
     const hmdEuler = this.hmdEuler;
-    const pitchObject = this.lookControls.pitchObject;
-    const yawObject = this.lookControls.yawObject;
-    const joystick = this.pendingLookX * dt / 1000;
     const { horizontalLookSpeedRatio, verticalLookSpeedRatio } = this.data;
-    if (scene.is("vr-mode") && scene.checkHeadsetConnected()) return;
     this.polyfillControls.update();
     hmdEuler.setFromQuaternion(this.polyfillObject.quaternion, "YXZ");
 
-    const dX = difference(hmdEuler.x, this.prevX);
-    const dY = difference(hmdEuler.y, this.prevY);
+    const dX = THREE.Math.RAD2DEG * difference(hmdEuler.x, this.prevX);
+    const dY = THREE.Math.RAD2DEG * difference(hmdEuler.y, this.prevY);
 
     this.dXBuffer.push(Math.abs(dX) < 0.001 ? 0 : dX);
     this.dYBuffer.push(Math.abs(dY) < 0.001 ? 0 : dY);
@@ -89,11 +86,11 @@ AFRAME.registerComponent("look-on-mobile", {
       this.dYBuffer.splice(0, 1);
     }
 
-    yawObject.rotation.y += average(this.dYBuffer) * horizontalLookSpeedRatio;
-    pitchObject.rotation.x += average(this.dXBuffer) * verticalLookSpeedRatio + joystick;
-    pitchObject.rotation.x = Math.max(-PI_4, Math.min(PI_4, pitchObject.rotation.x));
+    const deltaYaw = average(this.dYBuffer) * horizontalLookSpeedRatio;
+    const deltaPitch = average(this.dXBuffer) * verticalLookSpeedRatio + this.pendingLookX;
+
+    this.cameraController.look(deltaPitch, deltaYaw);
 
-    this.lookControls.updateOrientation();
     this.prevX = hmdEuler.x;
     this.prevY = hmdEuler.y;
     this.pendingLookX = 0;
diff --git a/src/hub.js b/src/hub.js
index bbd203f7d..2db914273 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -65,6 +65,7 @@ import "./components/scene-shadow";
 import "./components/avatar-replay";
 import "./components/pinch-to-move";
 import "./components/look-on-mobile";
+import "./components/camera-controller";
 
 import ReactDOM from "react-dom";
 import React from "react";
@@ -123,8 +124,8 @@ import { generateDefaultProfile, generateRandomName } from "./utils/identity.js"
 import { getAvailableVREntryTypes, VR_DEVICE_AVAILABILITY } from "./utils/vr-caps-detect.js";
 import ConcurrentLoadDetector from "./utils/concurrent-load-detector.js";
 import TouchEventsHandler from "./utils/touch-events-handler.js";
-import MouseEventsHandler from "./utils/mouse-events-handler.js";
-window.APP.touchEventsHandler = new TouchEventsHandler();
+import { MouseEventsHandler, GearVRMouseEventsHandler } from "./utils/mouse-events-handler.js";
+window.APP.touchEventsHandler = new TouchEventsHandler(); // TODO: Do not create TouchEventsHandler unless on mobile
 window.APP.mouseEventsHandler = new MouseEventsHandler();
 
 function qsTruthy(param) {
@@ -242,22 +243,19 @@ const onReady = async () => {
     AFRAME.registerInputActions(inGameActions, "default");
 
     const camera = document.querySelector("#player-camera");
-    const registerLookControls = e => {
-      if (e.detail.name !== "look-controls") return;
-      camera.removeEventListener("componentinitialized", registerLookControls);
+    const registerCameraController = e => {
+      if (e.detail.name !== "camera-controller") return;
+      camera.removeEventListener("componentinitialized", registerCameraController);
 
-      window.APP.touchEventsHandler.registerLookControls(camera.components["look-controls"]);
-      scene.components["look-on-mobile"].registerLookControls(camera.components["look-controls"]);
+      window.APP.touchEventsHandler.registerCameraController(camera.components["camera-controller"]);
+      scene.components["look-on-mobile"].registerCameraController(camera.components["camera-controller"]);
       scene.setAttribute("look-on-mobile", "enabled", true);
 
-      window.APP.mouseEventsHandler.registerLookControls(camera.components["look-controls"]);
+      window.APP.mouseEventsHandler.registerCameraController(camera.components["camera-controller"]);
       window.APP.mouseEventsHandler.setInverseMouseLook(qsTruthy("invertMouseLook"));
     };
-    camera.addEventListener("componentinitialized", registerLookControls);
-    camera.setAttribute("look-controls", {
-      touchEnabled: false,
-      hmdEnabled: false
-    });
+    camera.addEventListener("componentinitialized", registerCameraController);
+    camera.setAttribute("camera-controller", "foo", "bar");
 
     scene.setAttribute("networked-scene", {
       room: hubId,
diff --git a/src/utils/PrimaryActionHandler.js b/src/utils/PrimaryActionHandler.js
new file mode 100644
index 000000000..2424be991
--- /dev/null
+++ b/src/utils/PrimaryActionHandler.js
@@ -0,0 +1,13 @@
+export default class PrimaryActionHandler {
+  constructor() {
+    this.cursor = null;
+    this.rightTeleporter = null;
+    this.leftTeleporter = null;
+
+    this.registerCursor = this.registerCursor.bind(this);
+  }
+
+  registerCursor(cursor) {
+    this.cursor = cursor;
+  }
+}
diff --git a/src/utils/mouse-events-handler.js b/src/utils/mouse-events-handler.js
index 425b16d52..359098214 100644
--- a/src/utils/mouse-events-handler.js
+++ b/src/utils/mouse-events-handler.js
@@ -1,30 +1,26 @@
-const HORIZONTAL_LOOK_SPEED = 0.0035;
-const VERTICAL_LOOK_SPEED = 0.0021;
-const PI_4 = Math.PI / 4;
+// TODO: Make look speed adjustable by the user
+const HORIZONTAL_LOOK_SPEED = 0.1;
+const VERTICAL_LOOK_SPEED = 0.06;
 
-export default class MouseEventsHandler {
+export class MouseEventsHandler {
   constructor() {
     this.cursor = null;
-    this.lookControls = null;
-    this.isMouseDown = false;
-    this.isMouseDownHandledByCursor = false;
+    this.cameraController = null;
+    this.isLeftButtonDown = false;
+    this.isLeftButtonHandledByCursor = false;
     this.isPointerLocked = false;
-    this.dXBuffer = [];
-    this.dYBuffer = [];
 
     this.registerCursor = this.registerCursor.bind(this);
-    this.registerLookControls = this.registerLookControls.bind(this);
+    this.registerCameraController = this.registerCameraController.bind(this);
     this.isReady = this.isReady.bind(this);
     this.addEventListeners = this.addEventListeners.bind(this);
     this.onMouseDown = this.onMouseDown.bind(this);
+    this.onLeftButtonDown = this.onLeftButtonDown.bind(this);
+    this.onRightButtonDown = this.onRightButtonDown.bind(this);
     this.onMouseMove = this.onMouseMove.bind(this);
     this.onMouseUp = this.onMouseUp.bind(this);
     this.onMouseWheel = this.onMouseWheel.bind(this);
     this.look = this.look.bind(this);
-
-    document.addEventListener("contextmenu", function(e) {
-      e.preventDefault();
-    });
   }
 
   setInverseMouseLook(invert) {
@@ -38,15 +34,15 @@ export default class MouseEventsHandler {
     }
   }
 
-  registerLookControls(lookControls) {
-    this.lookControls = lookControls;
+  registerCameraController(cameraController) {
+    this.cameraController = cameraController;
     if (this.isReady()) {
       this.addEventListeners();
     }
   }
 
   isReady() {
-    return this.cursor && this.lookControls;
+    return this.cursor && this.cameraController;
   }
 
   addEventListeners() {
@@ -54,21 +50,35 @@ export default class MouseEventsHandler {
     document.addEventListener("mousemove", this.onMouseMove);
     document.addEventListener("mouseup", this.onMouseUp);
     document.addEventListener("wheel", this.onMouseWheel);
+    document.addEventListener("contextmenu", e => {
+      e.preventDefault();
+    });
   }
 
   onMouseDown(e) {
     const isLeftButton = e.button === 0;
     if (isLeftButton) {
-      this.isMouseDownHandledByCursor = this.cursor.handleMouseDown();
-      this.isMouseDown = true;
+      this.onLeftButtonDown();
+    } else {
+      this.onRightButtonDown();
+    }
+  }
+
+  onLeftButtonDown() {
+    this.isLeftButtonDown = true;
+    this.isLeftButtonHandledByCursor = this.cursor.startInteraction();
+    if (this.isLeftButtonHandledByCursor) {
+      return;
+    }
+  }
+
+  onRightButtonDown() {
+    if (this.isPointerLocked) {
+      document.exitPointerLock();
+      this.isPointerLocked = false;
     } else {
-      if (this.isPointerLocked) {
-        document.exitPointerLock();
-        this.isPointerLocked = false;
-      } else {
-        document.body.requestPointerLock();
-        this.isPointerLocked = true;
-      }
+      document.body.requestPointerLock();
+      this.isPointerLocked = true;
     }
   }
 
@@ -77,38 +87,90 @@ export default class MouseEventsHandler {
   }
 
   onMouseMove(e) {
-    const shouldLook = (this.isMouseDown && !this.isMouseDownHandledByCursor) || this.isPointerLocked;
+    const shouldLook = this.isPointerLocked || (this.isLeftButtonDown && !this.isLeftButtonHandledByCursor);
     if (shouldLook) {
       this.look(e);
     }
 
-    this.cursor.handleMouseMove(e);
+    this.cursor.moveCursor(e);
   }
 
-  look(e) {
-    const movementX = e.movementX;
-    const movementY = e.movementY;
+  onMouseUp(e) {
+    const isLeftButton = e.button === 0;
+    if (!isLeftButton) return;
 
+    if (this.isLeftButtonHandledByCursor) {
+      this.cursor.endInteraction();
+    }
+    this.isLeftButtonHandledByCursor = false;
+    this.isLeftButtonDown = false;
+  }
+
+  look(e) {
     const sign = this.invertMouseLook ? 1 : -1;
-    this.lookControls.yawObject.rotation.y += sign * movementX * HORIZONTAL_LOOK_SPEED;
-    this.lookControls.pitchObject.rotation.x += sign * movementY * VERTICAL_LOOK_SPEED;
-    this.lookControls.pitchObject.rotation.x = Math.max(
-      -PI_4,
-      Math.min(PI_4, this.lookControls.pitchObject.rotation.x)
-    );
-    this.lookControls.updateOrientation();
+    const deltaPitch = e.movementY * VERTICAL_LOOK_SPEED * sign;
+    const deltaYaw = e.movementX * HORIZONTAL_LOOK_SPEED * sign;
+    this.cameraController.look(deltaPitch, deltaYaw);
+  }
+}
+
+export class GearVRMouseEventsHandler {
+  constructor() {
+    this.cursor = null;
+    this.gazeTeleporter = null;
+    this.isMouseDownHandledByCursor = false;
+    this.isMouseDownHandledByGazeTeleporter = false;
+
+    this.registerCursor = this.registerCursor.bind(this);
+    this.registerGazeTeleporter = this.registerGazeTeleporter.bind(this);
+    this.isReady = this.isReady.bind(this);
+    this.addEventListeners = this.addEventListeners.bind(this);
+    this.onMouseDown = this.onMouseDown.bind(this);
+    this.onMouseUp = this.onMouseUp.bind(this);
+  }
+
+  registerCursor(cursor) {
+    this.cursor = cursor;
+    if (this.isReady()) {
+      this.addEventListeners();
+    }
+  }
+
+  registerGazeTeleporter(gazeTeleporter) {
+    this.gazeTeleporter = gazeTeleporter;
+    if (this.isReady()) {
+      this.addEventListeners();
+    }
+  }
+
+  isReady() {
+    return this.cursor && this.gazeTeleporter;
+  }
+
+  addEventListeners() {
+    document.addEventListener("mousedown", this.onMouseDown);
+    document.addEventListener("mouseup", this.onMouseUp);
+  }
+
+  onMouseDown(e) {
+    this.isMouseDownHandledByCursor = this.cursor.startInteraction();
+    if (this.isMouseDownHandledByCursor) {
+      return;
+    }
+
+    this.gazeTeleporter.startTeleport();
+    this.isMouseDownHandledByGazeTeleporter = true;
   }
 
   onMouseUp(e) {
-    const isLeftButton = e.button === 0;
-    if (isLeftButton) {
-      if (this.isMouseDownHandledByCursor) {
-        this.cursor.handleMouseUp();
-      }
+    if (this.isMouseDownHandledByCursor) {
+      this.cursor.endInteraction();
       this.isMouseDownHandledByCursor = false;
-      this.isMouseDown = false;
-      this.dXBuffer = [];
-      this.dYBuffer = [];
+    }
+
+    if (this.isMouseDownHandledByGazeTeleporter) {
+      this.gazeTeleporter.endTeleport();
+      this.isMouseDownHandledByGazeTeleporter = false;
     }
   }
 }
diff --git a/src/utils/touch-events-handler.js b/src/utils/touch-events-handler.js
index b652dc9b8..bec28edbd 100644
--- a/src/utils/touch-events-handler.js
+++ b/src/utils/touch-events-handler.js
@@ -1,12 +1,12 @@
 const VIRTUAL_JOYSTICK_HEIGHT = 0.8;
-const HORIZONTAL_LOOK_SPEED = 0.006;
-const VERTICAL_LOOK_SPEED = 0.003;
+const HORIZONTAL_LOOK_SPEED = 0.35;
+const VERTICAL_LOOK_SPEED = 0.18;
 const PI_4 = Math.PI / 4;
 
 export default class TouchEventsHandler {
   constructor() {
     this.cursor = null;
-    this.lookControls = null;
+    this.cameraController = null;
     this.pinchEmitter = null;
     this.touches = [];
     this.touchReservedForCursor = null;
@@ -17,7 +17,7 @@ export default class TouchEventsHandler {
     this.pinchTouchId2 = -1;
 
     this.registerCursor = this.registerCursor.bind(this);
-    this.registerLookControls = this.registerLookControls.bind(this);
+    this.registerCameraController = this.registerCameraController.bind(this);
     this.isReady = this.isReady.bind(this);
     this.addEventListeners = this.addEventListeners.bind(this);
     this.handleTouchStart = this.handleTouchStart.bind(this);
@@ -37,8 +37,8 @@ export default class TouchEventsHandler {
     }
   }
 
-  registerLookControls(lookControls) {
-    this.lookControls = lookControls;
+  registerCameraController(cameraController) {
+    this.cameraController = cameraController;
     if (this.isReady()) {
       this.addEventListeners();
     }
@@ -52,7 +52,7 @@ export default class TouchEventsHandler {
   }
 
   isReady() {
-    return this.cursor && this.lookControls && this.pinchEmitter;
+    return this.cursor && this.cameraController && this.pinchEmitter;
   }
 
   addEventListeners() {
@@ -70,7 +70,7 @@ export default class TouchEventsHandler {
     if (touch.clientY / window.innerHeight >= VIRTUAL_JOYSTICK_HEIGHT) {
       return;
     }
-    if (!this.touchReservedForCursor && this.cursor.handleTouchStart(touch)) {
+    if (!this.touchReservedForCursor && this.cursor.startInteractionAndForceCursorUpdate(touch)) {
       this.touchReservedForCursor = touch;
     }
     this.touches.push(touch);
@@ -86,7 +86,7 @@ export default class TouchEventsHandler {
 
   singleTouchMove(touch) {
     if (this.touchReservedForCursor && touch.identifier === this.touchReservedForCursor.identifier) {
-      this.cursor.handleTouchMove(touch);
+      this.cursor.moveCursor(touch);
       return;
     }
     if (touch.clientY / window.innerHeight >= VIRTUAL_JOYSTICK_HEIGHT) return;
@@ -114,7 +114,7 @@ export default class TouchEventsHandler {
     }
     if (touch.identifier === this.touchReservedForLookControls.identifier) {
       if (!this.touchReservedForCursor) {
-        this.cursor.handleTouchMove(touch);
+        this.cursor.moveCursor(touch);
       }
       this.look(this.touchReservedForLookControls, touch);
       this.touchReservedForLookControls = touch;
@@ -133,15 +133,9 @@ export default class TouchEventsHandler {
   }
 
   look(prevTouch, touch) {
-    const dX = touch.clientX - prevTouch.clientX;
-    const dY = touch.clientY - prevTouch.clientY;
-
-    this.lookControls.yawObject.rotation.y += dX * HORIZONTAL_LOOK_SPEED;
-    this.lookControls.pitchObject.rotation.x += dY * VERTICAL_LOOK_SPEED;
-    this.lookControls.pitchObject.rotation.x = Math.max(
-      -PI_4,
-      Math.min(PI_4, this.lookControls.pitchObject.rotation.x)
-    );
+    const deltaPitch = (touch.clientY - prevTouch.clientY) * VERTICAL_LOOK_SPEED;
+    const deltaYaw = (touch.clientX - prevTouch.clientX) * HORIZONTAL_LOOK_SPEED;
+    this.cameraController.look(deltaPitch, deltaYaw);
   }
 
   handleTouchEnd(e) {
@@ -156,7 +150,7 @@ export default class TouchEventsHandler {
     this.touches.splice(touchIndex, 1);
 
     if (this.touchReservedForCursor && touch.identifier === this.touchReservedForCursor.identifier) {
-      this.cursor.handleTouchEnd(touch);
+      this.cursor.endInteraction(touch);
       this.touchReservedForCursor = null;
       return;
     }
-- 
GitLab