diff --git a/src/components/pitch-yaw-rotator.js b/src/components/pitch-yaw-rotator.js index f4c5f684ef5bd42cdb4d006464b1160f104de244..297bf4f81b5105ca229a5d34bdf527b2389725bb 100644 --- a/src/components/pitch-yaw-rotator.js +++ b/src/components/pitch-yaw-rotator.js @@ -12,6 +12,13 @@ AFRAME.registerComponent("pitch-yaw-rotator", { init() { this.pitch = 0; this.yaw = 0; + this.onRotateX = this.onRotateX.bind(this); + this.el.sceneEl.addEventListener("rotateX", this.onRotateX); + this.pendingXRotation = 0; + }, + + onRotateX(e) { + this.pendingXRotation += e.detail.value; }, look(deltaPitch, deltaYaw) { @@ -30,10 +37,17 @@ AFRAME.registerComponent("pitch-yaw-rotator", { tick() { const userinput = AFRAME.scenes[0].systems.userinput; const cameraDelta = userinput.readFrameValueAtPath(paths.actions.cameraDelta); + let lookX = this.pendingXRotation; + let lookY = 0; if (cameraDelta) { - this.look(cameraDelta[1], cameraDelta[0]); + lookY += cameraDelta[0]; + lookX += cameraDelta[1]; + } + if (lookX !== 0 || lookY !== 0) { + this.look(lookX, lookY); this.el.object3D.rotation.set(degToRad(this.pitch), degToRad(this.yaw), 0); this.el.object3D.rotation.order = "YXZ"; } + this.pendingXRotation = 0; } }); diff --git a/src/systems/userinput/bindings/touchscreen-user.js b/src/systems/userinput/bindings/touchscreen-user.js index 92ef99eab630a7677d94a8df7eebe1ca10b22698..899781f40fb41c51ec66daf238296cc2219d1f21 100644 --- a/src/systems/userinput/bindings/touchscreen-user.js +++ b/src/systems/userinput/bindings/touchscreen-user.js @@ -4,6 +4,14 @@ import { xforms } from "./xforms"; const zero = "/vars/touchscreen/zero"; const forward = "/vars/touchscreen/pinchDeltaForward"; +const touchCamDelta = "vars/touchscreen/touchCameraDelta"; +const touchCamDeltaX = "vars/touchscreen/touchCameraDelta/x"; +const touchCamDeltaY = "vars/touchscreen/touchCameraDelta/y"; +const touchCamDeltaXScaled = "vars/touchscreen/touchCameraDelta/x/scaled"; +const touchCamDeltaYScaled = "vars/touchscreen/touchCameraDelta/y/scaled"; +const gyroCamDelta = "vars/gyro/gyroCameraDelta"; +const gyroCamDeltaXScaled = "vars/gyro/gyroCameraDelta/x/scaled"; +const gyroCamDeltaYScaled = "vars/gyro/gyroCameraDelta/y/scaled"; export const touchscreenUserBindings = { [sets.global]: [ @@ -27,25 +35,48 @@ export const touchscreenUserBindings = { xform: xforms.copy }, { - src: { value: paths.device.touchscreen.cameraDelta }, - dest: { x: "/var/touchscreenCamDeltaX", y: "/var/touchscreenCamDeltaY" }, + src: { value: paths.device.touchscreen.touchCameraDelta }, + dest: { x: touchCamDeltaX, y: touchCamDeltaY }, xform: xforms.split_vec2 }, { - src: { value: "/var/touchscreenCamDeltaX" }, - dest: { value: "/var/touchscreenCamDeltaXScaled" }, + src: { value: touchCamDeltaX }, + dest: { value: touchCamDeltaXScaled }, xform: xforms.scale(0.18) }, { - src: { value: "/var/touchscreenCamDeltaY" }, - dest: { value: "/var/touchscreenCamDeltaYScaled" }, + src: { value: touchCamDeltaY }, + dest: { value: touchCamDeltaYScaled }, xform: xforms.scale(0.35) }, { - src: { x: "/var/touchscreenCamDeltaXScaled", y: "/var/touchscreenCamDeltaYScaled" }, - dest: { value: paths.actions.cameraDelta }, + src: { x: touchCamDeltaXScaled, y: touchCamDeltaYScaled }, + dest: { value: touchCamDelta }, xform: xforms.compose_vec2 }, + { + src: { value: paths.device.gyro.averageDeltaX }, + dest: { value: gyroCamDeltaXScaled }, + xform: xforms.scale(1.00) + }, + { + src: { value: paths.device.gyro.averageDeltaY }, + dest: { value: gyroCamDeltaYScaled }, + xform: xforms.scale(1.00) + }, + { + src: { x: gyroCamDeltaYScaled, y: gyroCamDeltaXScaled }, + dest: { value: gyroCamDelta }, + xform: xforms.compose_vec2 + }, + { + src: { + first: touchCamDelta, + second: gyroCamDelta + }, + dest: { value: paths.actions.cameraDelta }, + xform: xforms.add_vec2 + }, { src: { value: paths.device.touchscreen.isTouchingGrabbable }, dest: { value: paths.actions.cursor.grab }, diff --git a/src/systems/userinput/bindings/xforms.js b/src/systems/userinput/bindings/xforms.js index 8fe0d49d22ed58513bd50cf65fb8e7a104905b41..444134675a842316f0f5b52f55ed8ff30083ca15 100644 --- a/src/systems/userinput/bindings/xforms.js +++ b/src/systems/userinput/bindings/xforms.js @@ -98,6 +98,10 @@ export const xforms = { const second = frame[src.second]; if (first && second) { frame[dest.value] = [first[0] + second[0], first[1] + second[1]]; + } else if (second) { + frame[dest.value] = second; + } else if (first) { + frame[dest.value] = first; } }, any: function(frame, src, dest) { diff --git a/src/systems/userinput/devices/app-aware-touchscreen.js b/src/systems/userinput/devices/app-aware-touchscreen.js index ab30d2e2528f3f88f5596abcce207c2c276db353..3183ea6c053ab26d225f68eddd820b0a4be44b0c 100644 --- a/src/systems/userinput/devices/app-aware-touchscreen.js +++ b/src/systems/userinput/devices/app-aware-touchscreen.js @@ -94,7 +94,9 @@ export class AppAwareTouchscreenDevice { move(touch) { if (!touchIsAssigned(touch, this.assignments)) { - console.warn("touch does not have job", touch); + if (!touch.target.classList[0] || !touch.target.classList[0].startsWith("virtual-gamepad-controls")) { + console.warn("touch does not have job", touch); + } return; } @@ -232,7 +234,7 @@ export class AppAwareTouchscreenDevice { } if (jobIsAssigned(MOVE_CAMERA_JOB, this.assignments)) { - frame[path.cameraDelta] = findByJob(MOVE_CAMERA_JOB, this.assignments).delta; + frame[path.touchCameraDelta] = findByJob(MOVE_CAMERA_JOB, this.assignments).delta; } frame[path.pinch.delta] = this.pinch.delta; diff --git a/src/systems/userinput/devices/gyro.js b/src/systems/userinput/devices/gyro.js new file mode 100644 index 0000000000000000000000000000000000000000..9e36ad5c6628d0fe53c35968d1fcb645a6ccd34e --- /dev/null +++ b/src/systems/userinput/devices/gyro.js @@ -0,0 +1,79 @@ +import { paths } from "../paths"; + +const TWOPI = Math.PI * 2; + +class CircularBuffer { + constructor(length) { + this.items = new Array(length).fill(0); + this.writePtr = 0; + } + + push(item) { + this.items[this.writePtr] = item; + this.writePtr = (this.writePtr + 1) % this.items.length; + } +} + +const abs = Math.abs; +// Input: two numbers between [-Math.PI, Math.PI] +// Output: difference between them, where -Math.PI === Math.PI +const difference = (curr, prev) => { + const a = curr - prev; + const b = curr + TWOPI - prev; + const c = curr - (prev + TWOPI); + if (abs(a) < abs(b)) { + if (abs(a) < abs(c)) { + return a; + } + } + if (abs(b) < abs(c)) { + return b; + } + + return c; +}; + +const average = a => { + let sum = 0; + for (let i = 0; i < a.length; i++) { + const n = a[i]; + sum += n; + } + return sum / a.length; +}; + +export class GyroDevice { + constructor() { + this.hmdEuler = new THREE.Euler(); + this.hmdQuaternion = new THREE.Quaternion(); + this.prevX = this.hmdEuler.x; + this.prevY = this.hmdEuler.y; + this.dXBuffer = new CircularBuffer(6); + this.dYBuffer = new CircularBuffer(6); + this.vrDisplay = window.webvrpolyfill.getPolyfillDisplays()[0]; + this.frameData = new window.webvrpolyfill.constructor.VRFrameData(); + } + + write(frame) { + const hmdEuler = this.hmdEuler; + this.vrDisplay.getFrameData(this.frameData); + if (this.frameData.pose.orientation !== null) { + this.hmdQuaternion.fromArray(this.frameData.pose.orientation); + hmdEuler.setFromQuaternion(this.hmdQuaternion, "YXZ"); + } + + 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); + + this.averageDeltaX = average(this.dXBuffer.items); + this.averageDeltaY = average(this.dYBuffer.items); + + this.prevX = hmdEuler.x; + this.prevY = hmdEuler.y; + frame[paths.device.gyro.averageDeltaX] = this.averageDeltaX; + frame[paths.device.gyro.averageDeltaY] = this.averageDeltaY; + } +} diff --git a/src/systems/userinput/paths.js b/src/systems/userinput/paths.js index 3acb4a740b21462368e08c2674f273b3b78ff849..4afc265bf84f1e89c232399143528815a3770890 100644 --- a/src/systems/userinput/paths.js +++ b/src/systems/userinput/paths.js @@ -69,12 +69,17 @@ paths.device.smartMouse.cursorPose = "/device/smartMouse/cursorPose"; paths.device.smartMouse.cameraDelta = "/device/smartMouse/cameraDelta"; paths.device.touchscreen = {}; paths.device.touchscreen.cursorPose = "/device/touchscreen/cursorPose"; +paths.device.touchscreen.touchCameraDelta = "/device/touchscreen/touchCameraDelta"; +paths.device.touchscreen.gyroCameraDelta = "/device/touchscreen/gyroCameraDelta"; paths.device.touchscreen.cameraDelta = "/device/touchscreen/cameraDelta"; paths.device.touchscreen.pinch = {}; paths.device.touchscreen.pinch.delta = "/device/touchscreen/pinch/delta"; paths.device.touchscreen.pinch.initialDistance = "/device/touchscreen/pinch/initialDistance"; paths.device.touchscreen.pinch.currentDistance = "/device/touchscreen/pinch/currentDistance"; paths.device.touchscreen.isTouchingGrabbable = "/device/touchscreen/isTouchingGrabbable"; +paths.device.gyro = {}; +paths.device.gyro.averageDeltaX = "/device/gyro/averageDeltaX"; +paths.device.gyro.averageDeltaY = "/device/gyro/averageDeltaY"; paths.device.hud = {}; paths.device.hud.penButton = "/device/hud/penButton"; diff --git a/src/systems/userinput/userinput.js b/src/systems/userinput/userinput.js index de08bea49ed594bf270f4d419436cdf40b9442cd..e7b6abc147704604e038e1af721d9b73f6d37f0b 100644 --- a/src/systems/userinput/userinput.js +++ b/src/systems/userinput/userinput.js @@ -9,6 +9,7 @@ import { OculusGoControllerDevice } from "./devices/oculus-go-controller"; import { OculusTouchControllerDevice } from "./devices/oculus-touch-controller"; import { DaydreamControllerDevice } from "./devices/daydream-controller"; import { ViveControllerDevice } from "./devices/vive-controller"; +import { GyroDevice } from "./devices/gyro"; import { AppAwareMouseDevice } from "./devices/app-aware-mouse"; import { AppAwareTouchscreenDevice } from "./devices/app-aware-touchscreen"; @@ -101,14 +102,17 @@ AFRAME.registerSystem("userinput", { this.gamepads = []; const appAwareTouchscreenDevice = new AppAwareTouchscreenDevice(); + const gyroDevice = new GyroDevice(); const updateBindingsForVRMode = () => { const inVRMode = this.el.sceneEl.is("vr-mode"); if (AFRAME.utils.device.isMobile()) { if (inVRMode) { this.activeDevices.delete(appAwareTouchscreenDevice); + this.activeDevices.delete(gyroDevice); this.registeredMappings.delete(touchscreenUserBindings); } else { this.activeDevices.add(appAwareTouchscreenDevice); + this.activeDevices.add(gyroDevice); this.registeredMappings.add(touchscreenUserBindings); } } else {