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; }; AFRAME.registerComponent("look-on-mobile", { schema: { horizontalLookSpeedRatio: { default: 1.0 }, // motion applied to camera / motion of polyfill object verticalLookSpeedRatio: { default: 1.0 }, // motion applied to camera / motion of polyfill object camera: { type: "selector" } }, init() { this.hmdEuler = new THREE.Euler(); this.hmdQuaternion = new THREE.Quaternion(); this.prevX = this.hmdEuler.x; this.prevY = this.hmdEuler.y; this.pendingLookX = 0; this.onRotateX = this.onRotateX.bind(this); this.dXBuffer = new CircularBuffer(6); this.dYBuffer = new CircularBuffer(6); this.vrDisplay = window.webvrpolyfill.getPolyfillDisplays()[0]; this.frameData = new window.webvrpolyfill.constructor.VRFrameData(); }, play() { this.el.addEventListener("rotateX", this.onRotateX); }, pause() { this.el.removeEventListener("rotateX", this.onRotateX); }, update() { this.cameraController = this.data.camera.components["pitch-yaw-rotator"]; }, onRotateX(e) { this.pendingLookX = e.detail.value; }, tick() { const hmdEuler = this.hmdEuler; const { horizontalLookSpeedRatio, verticalLookSpeedRatio } = this.data; 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); const deltaYaw = average(this.dYBuffer.items) * horizontalLookSpeedRatio; const deltaPitch = average(this.dXBuffer.items) * verticalLookSpeedRatio + this.pendingLookX; this.cameraController.look(deltaPitch, deltaYaw); this.prevX = hmdEuler.x; this.prevY = hmdEuler.y; this.pendingLookX = 0; } });