/* * Copyright 2016 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. */ var CardboardDistorter = require('./cardboard-distorter.js'); var CardboardUI = require('./cardboard-ui.js'); var DeviceInfo = require('./device-info.js'); var Dpdb = require('./dpdb/dpdb.js'); var FusionPoseSensor = require('./sensor-fusion/fusion-pose-sensor.js'); var RotateInstructions = require('./rotate-instructions.js'); var ViewerSelector = require('./viewer-selector.js'); var VRDisplay = require('./base.js').VRDisplay; var Util = require('./util.js'); var Eye = { LEFT: 'left', RIGHT: 'right' }; /** * VRDisplay based on mobile device parameters and DeviceMotion APIs. */ function CardboardVRDisplay() { this.displayName = 'Cardboard VRDisplay (webvr-polyfill)'; this.capabilities.hasOrientation = true; this.capabilities.canPresent = true; // "Private" members. this.bufferScale_ = WebVRConfig.BUFFER_SCALE; this.poseSensor_ = new FusionPoseSensor(); this.distorter_ = null; this.cardboardUI_ = null; this.dpdb_ = new Dpdb(true, this.onDeviceParamsUpdated_.bind(this)); this.deviceInfo_ = new DeviceInfo(this.dpdb_.getDeviceParams()); this.viewerSelector_ = new ViewerSelector(); this.viewerSelector_.on('change', this.onViewerChanged_.bind(this)); // Set the correct initial viewer. this.deviceInfo_.setViewer(this.viewerSelector_.getCurrentViewer()); if (!WebVRConfig.ROTATE_INSTRUCTIONS_DISABLED) { this.rotateInstructions_ = new RotateInstructions(); } if (Util.isIOS()) { // Listen for resize events to workaround this awful Safari bug. window.addEventListener('resize', this.onResize_.bind(this)); } } CardboardVRDisplay.prototype = new VRDisplay(); CardboardVRDisplay.prototype.getImmediatePose = function() { return { position: this.poseSensor_.getPosition(), orientation: this.poseSensor_.getOrientation(), linearVelocity: null, linearAcceleration: null, angularVelocity: null, angularAcceleration: null }; }; CardboardVRDisplay.prototype.resetPose = function() { this.poseSensor_.resetPose(); }; CardboardVRDisplay.prototype.getEyeParameters = function(whichEye) { var offset = [this.deviceInfo_.viewer.interLensDistance * 0.5, 0.0, 0.0]; var fieldOfView; // TODO: FoV can be a little expensive to compute. Cache when device params change. if (whichEye == Eye.LEFT) { offset[0] *= -1.0; fieldOfView = this.deviceInfo_.getFieldOfViewLeftEye(); } else if (whichEye == Eye.RIGHT) { fieldOfView = this.deviceInfo_.getFieldOfViewRightEye(); } else { console.error('Invalid eye provided: %s', whichEye); return null; } return { fieldOfView: fieldOfView, offset: offset, // TODO: Should be able to provide better values than these. renderWidth: this.deviceInfo_.device.width * 0.5 * this.bufferScale_, renderHeight: this.deviceInfo_.device.height * this.bufferScale_, }; }; CardboardVRDisplay.prototype.onDeviceParamsUpdated_ = function(newParams) { if (Util.isDebug()) { console.log('DPDB reported that device params were updated.'); } this.deviceInfo_.updateDeviceParams(newParams); if (this.distorter_) { this.distorter_.updateDeviceInfo(this.deviceInfo_); } }; CardboardVRDisplay.prototype.updateBounds_ = function () { if (this.layer_ && this.distorter_ && (this.layer_.leftBounds || this.layer_.rightBounds)) { this.distorter_.setTextureBounds(this.layer_.leftBounds, this.layer_.rightBounds); } }; CardboardVRDisplay.prototype.beginPresent_ = function() { var gl = this.layer_.source.getContext('webgl'); if (!gl) gl = this.layer_.source.getContext('experimental-webgl'); if (!gl) gl = this.layer_.source.getContext('webgl2'); if (!gl) return; // Can't do distortion without a WebGL context. // Provides a way to opt out of distortion if (this.layer_.predistorted) { if (!WebVRConfig.CARDBOARD_UI_DISABLED) { gl.canvas.width = Util.getScreenWidth() * this.bufferScale_; gl.canvas.height = Util.getScreenHeight() * this.bufferScale_; this.cardboardUI_ = new CardboardUI(gl); } } else { // Create a new distorter for the target context this.distorter_ = new CardboardDistorter(gl); this.distorter_.updateDeviceInfo(this.deviceInfo_); this.cardboardUI_ = this.distorter_.cardboardUI; } if (this.cardboardUI_) { this.cardboardUI_.listen(function(e) { // Options clicked. this.viewerSelector_.show(this.layer_.source.parentElement); e.stopPropagation(); e.preventDefault(); }.bind(this), function(e) { // Back clicked. this.exitPresent(); e.stopPropagation(); e.preventDefault(); }.bind(this)); } if (this.rotateInstructions_) { if (Util.isLandscapeMode() && Util.isMobile()) { // In landscape mode, temporarily show the "put into Cardboard" // interstitial. Otherwise, do the default thing. this.rotateInstructions_.showTemporarily(3000, this.layer_.source.parentElement); } else { this.rotateInstructions_.update(); } } // Listen for orientation change events in order to show interstitial. this.orientationHandler = this.onOrientationChange_.bind(this); window.addEventListener('orientationchange', this.orientationHandler); // Listen for present display change events in order to update distorter dimensions this.vrdisplaypresentchangeHandler = this.updateBounds_.bind(this); window.addEventListener('vrdisplaypresentchange', this.vrdisplaypresentchangeHandler); // Fire this event initially, to give geometry-distortion clients the chance // to do something custom. this.fireVRDisplayDeviceParamsChange_(); }; CardboardVRDisplay.prototype.endPresent_ = function() { if (this.distorter_) { this.distorter_.destroy(); this.distorter_ = null; } if (this.cardboardUI_) { this.cardboardUI_.destroy(); this.cardboardUI_ = null; } if (this.rotateInstructions_) { this.rotateInstructions_.hide(); } this.viewerSelector_.hide(); window.removeEventListener('orientationchange', this.orientationHandler); window.removeEventListener('vrdisplaypresentchange', this.vrdisplaypresentchangeHandler); }; CardboardVRDisplay.prototype.submitFrame = function(pose) { if (this.distorter_) { this.distorter_.submitFrame(); } else if (this.cardboardUI_ && this.layer_) { // Hack for predistorted: true. var canvas = this.layer_.source.getContext('webgl').canvas; if (canvas.width != this.lastWidth || canvas.height != this.lastHeight) { this.cardboardUI_.onResize(); } this.lastWidth = canvas.width; this.lastHeight = canvas.height; // Render the Cardboard UI. this.cardboardUI_.render(); } }; CardboardVRDisplay.prototype.onOrientationChange_ = function(e) { // Hide the viewer selector. this.viewerSelector_.hide(); // Update the rotate instructions. if (this.rotateInstructions_) { this.rotateInstructions_.update(); } this.onResize_(); }; CardboardVRDisplay.prototype.onResize_ = function(e) { if (this.layer_) { var gl = this.layer_.source.getContext('webgl'); // Size the CSS canvas. // Added padding on right and bottom because iPhone 5 will not // hide the URL bar unless content is bigger than the screen. // This will not be visible as long as the container element (e.g. body) // is set to 'overflow: hidden'. var cssProperties = [ 'position: absolute', 'top: 0', 'left: 0', 'width: ' + Math.max(screen.width, screen.height) + 'px', 'height: ' + Math.min(screen.height, screen.width) + 'px', 'border: 0', 'margin: 0', 'padding: 0 10px 10px 0', ]; gl.canvas.setAttribute('style', cssProperties.join('; ') + ';'); Util.safariCssSizeWorkaround(gl.canvas); } }; CardboardVRDisplay.prototype.onViewerChanged_ = function(viewer) { this.deviceInfo_.setViewer(viewer); if (this.distorter_) { // Update the distortion appropriately. this.distorter_.updateDeviceInfo(this.deviceInfo_); } // Fire a new event containing viewer and device parameters for clients that // want to implement their own geometry-based distortion. this.fireVRDisplayDeviceParamsChange_(); }; CardboardVRDisplay.prototype.fireVRDisplayDeviceParamsChange_ = function() { var event = new CustomEvent('vrdisplaydeviceparamschange', { detail: { vrdisplay: this, deviceInfo: this.deviceInfo_, } }); window.dispatchEvent(event); }; module.exports = CardboardVRDisplay;