Skip to content

Commit

Permalink
Use RelativeOrientationSensor when available, falling back to
Browse files Browse the repository at this point in the history
devicemotion-based FusionPoseSensor when not. Fixes #10.
  • Loading branch information
jsantell committed Jan 22, 2018
1 parent 106e6cb commit 04dde2f
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 7 deletions.
1 change: 1 addition & 0 deletions examples/iframe.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
iframe.src = 'index.html';
iframe.width = '100%';
iframe.height = '100%';
iframe.setAttribute('allow', 'gyroscope; accelerometer');
document.body.appendChild(iframe);

// iOS, cross-origin iframes cannot access devicemotion events, so the parent
Expand Down
15 changes: 8 additions & 7 deletions src/cardboard-vr-display.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import CardboardDistorter from './cardboard-distorter.js';
import CardboardUI from './cardboard-ui.js';
import DeviceInfo from './device-info.js';
import Dpdb from './dpdb.js';
import FusionPoseSensor from './sensor-fusion/fusion-pose-sensor.js';
import PoseSensor from './pose-sensor.js';
import RotateInstructions from './rotate-instructions.js';
import ViewerSelector from './viewer-selector.js';
import { VRDisplay, VRDisplayCapabilities } from './base.js';
Expand Down Expand Up @@ -56,10 +56,7 @@ function CardboardVRDisplay(config) {

// "Private" members.
this.bufferScale_ = this.config.BUFFER_SCALE;
this.poseSensor_ = new FusionPoseSensor(this.config.K_FILTER,
this.config.PREDICTION_TIME_S,
this.config.YAW_ONLY,
this.config.DEBUG);
this.poseSensor_ = new PoseSensor(this.config);
this.distorter_ = null;
this.cardboardUI_ = null;

Expand All @@ -85,7 +82,7 @@ CardboardVRDisplay.prototype = Object.create(VRDisplay.prototype);

CardboardVRDisplay.prototype._getPose = function() {
return {
position: this.poseSensor_.getPosition(),
position: null,
orientation: this.poseSensor_.getOrientation(),
linearVelocity: null,
linearAcceleration: null,
Expand All @@ -95,7 +92,11 @@ CardboardVRDisplay.prototype._getPose = function() {
}

CardboardVRDisplay.prototype._resetPose = function() {
this.poseSensor_.resetPose();
// The non-devicemotion PoseSensor does not have resetPose implemented
// as it has been deprecated from spec.
if (this.poseSensor_.resetPose) {
this.poseSensor_.resetPose();
}
};

CardboardVRDisplay.prototype._getFieldOfView = function(whichEye) {
Expand Down
180 changes: 180 additions & 0 deletions src/pose-sensor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
* Copyright 2015 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import FusionPoseSensor from './sensor-fusion/fusion-pose-sensor.js';
import { Vector3, Quaternion } from './math-util.js';

// Frequency which the Sensors will attempt to fire their
// `reading` functions at. Use 60hz since we generally
// can't get higher without native VR hardware.
const SENSOR_FREQUENCY = 60;

const X_AXIS = new Vector3(1, 0, 0);
const Z_AXIS = new Vector3(0, 0, 1);

let orientation = {};
if (screen.orientation) {
orientation = screen.orientation;
} else if (screen.msOrientation) {
orientation = screen.msOrientation;
} else {
Object.defineProperty(orientation, 'angle', {
get: () => { return (window.orientation || 0); }
});
}

// Quaternion to rotate from sensor coordinates to WebVR coordinates
const SENSOR_TO_VR = new Quaternion();
SENSOR_TO_VR.setFromAxisAngle(X_AXIS, -Math.PI / 2);
SENSOR_TO_VR.multiply(new Quaternion().setFromAxisAngle(Z_AXIS, Math.PI / 2));

/**
* An abstraction class around either using the new RelativeOrientationSensor,
* or `devicemotion` events with complimentary filter via fusion-pose-sensor.js.
*/
export default class PoseSensor {
constructor(config) {
this.config = config;
this.sensor = null;
this.fusionSensor = null;
this._out = new Float32Array(4);

// Can be 'sensor' (using RelativeOrientationSensor) or
// 'devicemotion' (using devicemotion events via FusionPoseSensor),
// or `null` if not yet set.
this.api = null;

// Store any errors from Sensors for debugging purposes
this.errors = [];

// Quaternions for caching transforms
this._sensorQ = new Quaternion();
this._worldToScreenQ = new Quaternion();
this._outQ = new Quaternion();

this._onSensorRead = this._onSensorRead.bind(this);
this._onSensorError = this._onSensorError.bind(this);
this._onOrientationChange = this._onOrientationChange.bind(this);

this._onOrientationChange();
this.init();
}

init() {
// Attempt to use the RelativeOrientationSensor from Generic Sensor APIs.
// First available in Chrome M63, this can fail for several reasons, and attempt
// to fallback to devicemotion. Failure scenarios include:
//
// * Generic Sensor APIs do not exist; fallback to devicemotion.
// * Underlying sensor does not exist; no fallback possible.
// * Feature Policy failure (in an iframe); no fallback.
// https://github.com/immersive-web/webxr/issues/86
// * Permission to sensor data denied; respect user agent; no fallback to devicemotion.
// Browsers are heading towards disabling devicemotion when sensors are denied as well.
// https://www.chromestatus.com/feature/5023919287304192
let sensor = null;
try {
sensor = new RelativeOrientationSensor({ frequency: SENSOR_FREQUENCY });
sensor.addEventListener('error', this._onSensorError);
} catch (error) {
this.errors.push(error);

// Sensors are available in Chrome M63, however the Feature Policy
// integration is not available until Chrome M65, resulting in Sensors
// only being available in main frames.
// https://developers.google.com/web/updates/2017/09/sensors-for-the-web#feature_policy_integration
if (error.name === 'SecurityError') {
console.error('Cannot construct sensors due to the Feature Policy');
console.warn('Attempting to fall back using "devicemotion"; however this will ' +
'fail in the future without correct permissions.');
this.useDeviceMotion();
} else if (error.name === 'ReferenceError') {
// Fall back to devicemotion.
this.useDeviceMotion();
} else {
console.error(error);
}
}

if (sensor) {
this.api = 'sensor';
this.sensor = sensor;
this.sensor.addEventListener('reading', this._onSensorRead);
this.sensor.start();
}

window.addEventListener('orientationchange', this._onOrientationChange);
}

useDeviceMotion() {
this.api = 'devicemotion';
this.fusionSensor = new FusionPoseSensor(this.config.K_FILTER,
this.config.PREDICTION_TIME_S,
this.config.YAW_ONLY,
this.config.DEBUG);
}

getOrientation() {
if (this.fusionSensor) {
return this.fusionSensor.getOrientation();
}

if (!this.sensor || !this.sensor.quaternion) {
this._out[0] = this._out[1] = this._out[2] = 0;
this._out[3] = 1;
return this._out;
}

// Convert to THREE coordinate system: -Z forward, Y up, X right.
const q = this.sensor.quaternion;
this._sensorQ.set(q[0], q[1], q[2], q[3]);

const out = this._outQ;
out.copy(SENSOR_TO_VR);
out.multiply(this._sensorQ);
out.multiply(this._worldToScreenQ);

// Handle the yaw-only case.
if (this.config.YAW_ONLY) {
// Make a quaternion that only turns around the Y-axis.
out.x = out.z = 0;
out.normalize();
}

this._out[0] = out.x;
this._out[1] = out.y;
this._out[2] = out.z;
this._out[3] = out.w;
return this._out;
}

_onSensorError(event) {
this.errors.push(event.error);
if (event.error.name === 'NotAllowedError') {
console.error('Permission to access sensor was denied');
} else if (event.error.name === 'NotReadableError') {
console.error('Sensor could not be read');
} else {
console.error(event.error);
}
}

_onSensorRead() {}

_onOrientationChange() {
const angle = -orientation.angle * Math.PI / 180;
this._worldToScreenQ.setFromAxisAngle(Z_AXIS, angle);
}
}

0 comments on commit 04dde2f

Please sign in to comment.