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