From bb2983e8653c2f5c019608bbe113f7dc236f831e Mon Sep 17 00:00:00 2001 From: joni <johnfshaughnessy@gmail.com> Date: Wed, 9 May 2018 17:20:54 -0700 Subject: [PATCH] Add pinch to move and enable looking up and down. --- src/components/cursor-controller.js | 72 ++++++++-------------- src/hub.js | 12 +++- src/utils/look-controls-toggle.js | 35 +++++++++++ src/utils/pinch-to-move.js | 45 ++++++++++++++ src/utils/pinch.js | 76 +++++++++++++++++++++++ src/utils/pointer-look-controls.js | 96 +++++++++++++++++++++++++++++ 6 files changed, 289 insertions(+), 47 deletions(-) create mode 100644 src/utils/look-controls-toggle.js create mode 100644 src/utils/pinch-to-move.js create mode 100644 src/utils/pinch.js create mode 100644 src/utils/pointer-look-controls.js diff --git a/src/components/cursor-controller.js b/src/components/cursor-controller.js index 6183eb666..7b2c16f77 100644 --- a/src/components/cursor-controller.js +++ b/src/components/cursor-controller.js @@ -43,9 +43,9 @@ AFRAME.registerComponent("cursor-controller", { this.data.cursor.setAttribute("material", { color: this.data.cursorColorUnhovered }); const functionNames = [ - "_handleTouchStart", - "_handleTouchMove", - "_handleTouchEnd", + "_handlePointerDown", + "_handlePointerMove", + "_handlePointerUp", "_handleMouseDown", "_handleMouseMove", "_handleMouseUp", @@ -81,10 +81,10 @@ AFRAME.registerComponent("cursor-controller", { }, play: function() { - document.addEventListener("touchstart", this._handleTouchStart); - document.addEventListener("touchmove", this._handleTouchMove); - document.addEventListener("touchend", this._handleTouchEnd); - document.addEventListener("touchcancel", this._handleTouchEnd); + document.addEventListener("pointerdown", this._handlePointerDown); + document.addEventListener("pointermove", this._handlePointerMove); + document.addEventListener("pointerup", this._handlePointerUp); + document.addEventListener("pointercancel", this._handlePointerUp); document.addEventListener("mousedown", this._handleMouseDown); document.addEventListener("mousemove", this._handleMouseMove); document.addEventListener("mouseup", this._handleMouseUp); @@ -104,10 +104,10 @@ AFRAME.registerComponent("cursor-controller", { }, pause: function() { - document.removeEventListener("touchstart", this._handleTouchStart); - document.removeEventListener("touchmove", this._handleTouchMove); - document.removeEventListener("touchend", this._handleTouchEnd); - document.removeEventListener("touchcancel", this._handleTouchEnd); + document.removeEventListener("pointerdown", this._handlePointerDown); + document.removeEventListener("pointermove", this._handlePointerMove); + document.removeEventListener("pointerup", this._handlePointerUp); + document.removeEventListener("pointercancel", this._handlePointerUp); document.removeEventListener("mousedown", this._handleMouseDown); document.removeEventListener("mousemove", this._handleMouseMove); document.removeEventListener("mouseup", this._handleMouseUp); @@ -229,14 +229,7 @@ AFRAME.registerComponent("cursor-controller", { }, _setLookControlsEnabled(enabled) { - const lookControls = this.data.camera.components["look-controls"]; - if (lookControls) { - if (enabled) { - lookControls.play(); - } else { - lookControls.pause(); - } - } + window.LookControlsToggle.toggle(enabled, this); }, _startTeleport: function() { @@ -257,17 +250,10 @@ AFRAME.registerComponent("cursor-controller", { this._setCursorVisibility(true); }, - _handleTouchStart: function(e) { - if (!this.isMobile || this.hasPointingDevice || this.activeTouch) return; + _handlePointerDown: function(e) { + if (!this.isMobile || this.hasPointingDevice || this.activeTouch || e.clientY / window.innerHeight >= 0.8) return; - for (let i = e.touches.length - 1; i >= 0; i--) { - const touch = e.touches[i]; - if (touch.clientY / window.innerHeight < 0.8) { - this.activeTouch = touch; - break; - } - } - if (!this.activeTouch) return; + this.activeTouch = e; // Update the ray and cursor positions const raycasterComp = this.el.components.raycaster; @@ -289,31 +275,25 @@ AFRAME.registerComponent("cursor-controller", { cursor.object3D.position.copy(intersections[0].point); // Cursor position must be synced to physics before constraint is created cursor.components["static-body"].syncToPhysics(); + this.activeTouch.isUsedByCursor = true; + cursor.emit("touch-used-by-cursor", this.activeTouch); cursor.emit("cursor-grab", {}); }, - _handleTouchMove: function(e) { + _handlePointerMove: function(e) { if (!this.isMobile || this.hasPointingDevice) return; - for (let i = 0; i < e.touches.length; i++) { - const touch = e.touches[i]; - if ( - (!this.activeTouch && touch.clientY / window.innerHeight < 0.8) || - (this.activeTouch && touch.identifier === this.activeTouch.identifier) - ) { - this.mousePos.set(touch.clientX / window.innerWidth * 2 - 1, -(touch.clientY / window.innerHeight) * 2 + 1); - return; - } + if ( + (!this.activeTouch && e.clientY / window.innerHeight < 0.8) || + (this.activeTouch && e.pointerId === this.activeTouch.pointerId) + ) { + this.mousePos.set(e.clientX / window.innerWidth * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1); + return; } }, - _handleTouchEnd: function(e) { - if ( - !this.isMobile || - this.hasPointingDevice || - !this.activeTouch || - this.some(e.touches, touch => touch.identifier === this.activeTouch.identifier) - ) { + _handlePointerUp: function(e) { + if (!this.isMobile || this.hasPointingDevice || !this.activeTouch || e.pointerId !== this.activeTouch.pointerId) { return; } diff --git a/src/hub.js b/src/hub.js index 215965e98..e7b05cc58 100644 --- a/src/hub.js +++ b/src/hub.js @@ -110,6 +110,10 @@ import registerTelemetry from "./telemetry"; 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 Pinch from "./utils/pinch.js"; +import PinchToMove from "./utils/pinch-to-move.js"; +import LookControlsToggle from "./utils/look-controls-toggle.js"; +import PointerLookControls from "./utils/pointer-look-controls.js"; window.RENDER_ORDER = { HUD_BACKGROUND: 1, @@ -210,6 +214,9 @@ const onReady = async () => { const enterScene = async (mediaStream, enterInVR, janusRoomId) => { const scene = document.querySelector("a-scene"); scene.renderer.sortObjects = true; + const pinch = new Pinch(scene); + const pinchToMove = new PinchToMove(scene); + window.p = pinchToMove; const playerRig = document.querySelector("#player-rig"); document.querySelector("a-scene canvas").classList.remove("blurred"); scene.render(); @@ -220,7 +227,10 @@ const onReady = async () => { AFRAME.registerInputActions(inGameActions, "default"); - document.querySelector("#player-camera").setAttribute("look-controls", ""); + const camera = document.querySelector("#player-camera"); + camera.setAttribute("look-controls", "touchEnabled", false); + window.PointerLookControls = new PointerLookControls(camera); + window.LookControlsToggle = new LookControlsToggle(camera, window.PointerLookControls); scene.setAttribute("networked-scene", { room: janusRoomId, diff --git a/src/utils/look-controls-toggle.js b/src/utils/look-controls-toggle.js new file mode 100644 index 000000000..d7f21347e --- /dev/null +++ b/src/utils/look-controls-toggle.js @@ -0,0 +1,35 @@ +export default class LookControlsToggle { + constructor(lookControlsEl, pointerLookControls) { + this.lookControlsEl = lookControlsEl; + this.pointerLookControls = pointerLookControls; + this.toggle = this.toggle.bind(this); + this.allAgreeToEnable = this.allAgreeToEnable.bind(this); + this.requesters = {}; + } + + allAgreeToEnable() { + for (let i in this.requesters) { + if (!this.requesters[i]) { + return false; + } + } + return true; + } + + toggle(enable, requester) { + this.requesters[requester] = enable; + const consensus = this.allAgreeToEnable(); + + if (!this.lookControls) { + this.lookControls = this.lookControlsEl.components["look-controls"]; + } + + if (consensus) { + this.lookControls.play(); + this.pointerLookControls.start(); + } else { + this.lookControls.pause(); + this.pointerLookControls.stop(); + } + } +} diff --git a/src/utils/pinch-to-move.js b/src/utils/pinch-to-move.js new file mode 100644 index 000000000..093fc9ac7 --- /dev/null +++ b/src/utils/pinch-to-move.js @@ -0,0 +1,45 @@ +export default class PinchToMove { + constructor(el) { + this.speed = 0.35; + this.el = el; + this.onPinch = this.onPinch.bind(this); + this.onSpread = this.onSpread.bind(this); + this.decay = this.decay.bind(this); + document.addEventListener("pinch", this.onPinch); + document.addEventListener("spread", this.onSpread); + + this.interval = null; + this.decayingSpeed = 0; + this.dir = 1; + } + + decay() { + if (Math.abs(this.decayingSpeed) < 0.01) { + window.clearInterval(this.interval); + this.interval = null; + } + + this.el.emit("move", { axis: [0, this.dir * this.decayingSpeed] }); + this.decayingSpeed *= 0.93; + } + + onPinch(e) { + const dist = e.detail.distance * this.speed; + this.decayingSpeed = dist; + this.dir = -1; + + if (!this.interval) { + this.interval = window.setInterval(this.decay, 20); + } + } + + onSpread(e) { + const dist = e.detail.distance * this.speed; + this.decayingSpeed = dist; + this.dir = 1; + + if (!this.interval) { + this.interval = window.setInterval(this.decay, 20); + } + } +} diff --git a/src/utils/pinch.js b/src/utils/pinch.js new file mode 100644 index 000000000..cbb2ba503 --- /dev/null +++ b/src/utils/pinch.js @@ -0,0 +1,76 @@ +export default class Pinch { + constructor(el) { + this.el = el; + this.prevDiff = -1; + this.evCache = []; + + this.onPointerMove = this.onPointerMove.bind(this); + this.onPointerDown = this.onPointerDown.bind(this); + this.onPointerUp = this.onPointerUp.bind(this); + this.removeEvent = this.removeEvent.bind(this); + + document.addEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointerdown", this.onPointerDown); + document.addEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointercancel", this.onPointerUp); + document.addEventListener("touch-used-by-cursor", this.onPointerUp); + } + + onPointerUp = ev => { + this.removeEvent(ev); + if (this.evCache.length < 2) { + window.LookControlsToggle.toggle(true, this); + this.prevDiff = -1; + } + }; + + onPointerDown = ev => { + if (ev.isUsedByCursor || ev.clientY / window.innerHeight >= 0.8) { + return; + } + this.evCache.push(ev); + }; + + onPointerMove = ev => { + const cache = this.evCache; + + for (var i = 0; i < cache.length; i++) { + if (ev.pointerId === cache[i].pointerId) { + cache[i] = ev; + break; + } + } + + if (cache.length !== 2) { + return; + } + window.LookControlsToggle.toggle(false, this); + + const diff = Pinch.distance(cache[0].clientX, cache[0].clientY, cache[1].clientX, cache[1].clientY); + + if (this.prevDiff > 0) { + if (diff > this.prevDiff) { + this.el.emit("spread", { distance: diff - this.prevDiff }); + } else if (diff < this.prevDiff) { + this.el.emit("pinch", { distance: this.prevDiff - diff }); + } + } + + this.prevDiff = diff; + }; + + removeEvent = ev => { + for (let i = 0; i < this.evCache.length; i++) { + if (this.evCache[i].pointerId == ev.pointerId) { + this.evCache.splice(i, 1); + break; + } + } + }; + + static distance = (x1, y1, x2, y2) => { + const x = x1 - x2; + const y = y1 - y2; + return Math.sqrt(x * x + y * y); + }; +} diff --git a/src/utils/pointer-look-controls.js b/src/utils/pointer-look-controls.js new file mode 100644 index 000000000..49485c8a1 --- /dev/null +++ b/src/utils/pointer-look-controls.js @@ -0,0 +1,96 @@ +const PI_2 = Math.PI / 2; +export default class PointerLookControls { + constructor(lookControlsEl) { + this.xSpeed = 0.005; + this.ySpeed = 0.003; + this.lookControlsEl = lookControlsEl; + this.onPointerDown = this.onPointerDown.bind(this); + this.onPointerMove = this.onPointerMove.bind(this); + this.onPointerUp = this.onPointerUp.bind(this); + this.getLookControls = this.getLookControls.bind(this); + this.removeEvent = this.removeEvent.bind(this); + document.addEventListener("touch-used-by-cursor", this.onPointerUp); + this.start = this.start.bind(this); + this.stop = this.stop.bind(this); + + this.getLookControls(); + this.cache = []; + } + + getLookControls() { + this.lookControls = this.lookControlsEl.components["look-controls"]; + this.yawObject = this.lookControls.yawObject; + this.pitchObject = this.lookControls.pitchObject; + } + + start() { + document.addEventListener("pointerdown", this.onPointerDown); + document.addEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointercancel", this.onPointerUp); + if (!this.lookControls) { + this.getLookControls(); + } + } + + stop() { + document.removeEventListener("pointerdown", this.onPointerDown); + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.removeEventListener("pointercancel", this.onPointerUp); + this.cache = []; + } + + onPointerDown(ev) { + if (ev.isUsedByCursor || ev.clientY / window.innerHeight >= 0.8) { + return; + } + this.cache.push(ev); + } + + onPointerMove(ev) { + const cache = this.cache; + if (ev.isUsedByCursor || ev.clientY / window.innerHeight >= 0.8) { + return; + } + + let cachedEv = null; + for (var i = 0; i < cache.length; i++) { + if (ev.pointerId === cache[i].pointerId) { + cachedEv = cache[i]; + cache[i] = ev; + break; + } + } + if (!cachedEv) { + return; + } + + const dX = ev.clientX - cachedEv.clientX; + const dY = ev.clientY - cachedEv.clientY; + + this.yawObject.rotation.y -= dX * this.xSpeed; + this.pitchObject.rotation.x -= dY * this.ySpeed; + this.pitchObject.rotation.x = Math.max(-PI_2, Math.min(PI_2, this.pitchObject.rotation.x)); + } + + onPointerUp(ev) { + this.removeEvent(ev); + } + + removeEvent(ev) { + const cache = this.cache; + for (let i = 0; i < cache.length; i++) { + if (cache[i].pointerId == ev.pointerId) { + cache.splice(i, 1); + break; + } + } + } + + static distance = (x1, y1, x2, y2) => { + const x = x1 - x2; + const y = y1 - y2; + return Math.sqrt(x * x + y * y); + }; +} -- GitLab