diff --git a/examples/iframe.html b/examples/iframe.html
index 4bf3220..a18deab 100644
--- a/examples/iframe.html
+++ b/examples/iframe.html
@@ -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
diff --git a/src/cardboard-vr-display.js b/src/cardboard-vr-display.js
index aafef1e..57efea6 100644
--- a/src/cardboard-vr-display.js
+++ b/src/cardboard-vr-display.js
@@ -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';
@@ -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;
@@ -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,
@@ -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) {
diff --git a/src/pose-sensor.js b/src/pose-sensor.js
new file mode 100644
index 0000000..23e2489
--- /dev/null
+++ b/src/pose-sensor.js
@@ -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);
+ }
+}