Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 30 additions & 13 deletions demos/webvr/WebVrView.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ var eventEmitter = Marzipano.dependencies.eventEmitter;
var mat4 = Marzipano.dependencies.glMatrix.mat4;
var vec4 = Marzipano.dependencies.glMatrix.vec4;

// A minimal View implementation for use with WebVR.
// A minimal View implementation for use with WebXR.
//
// Note that RectilinearView cannot be used because the WebVR API exposes a view
// Note that RectilinearView cannot be used because the WebXR API exposes a view
// matrix instead of view parameters (yaw, pitch and roll).
//
// Most of the code has been copied verbatim from RectilinearView, but some
// methods are missing (e.g. screenToCoordinates and coordinatesToScreen).
// If we ever graduate this class to the core library, we'll need to figure out
// the best way to share code between the two.
function WebVrView() {
function WebXrView() {
this._width = 0;
this._height = 0;

Expand All @@ -46,13 +46,13 @@ function WebVrView() {
this._tmpVec = vec4.create();
};

eventEmitter(WebVrView);
eventEmitter(WebXrView);

WebVrView.prototype.destroy = function() {
WebXrView.prototype.destroy = function() {
clearOwnProperties(this);
};

WebVrView.prototype.size = function(size) {
WebXrView.prototype.size = function(size) {
size = size || {};
size.width = this._width;
size.height = this._height;
Expand All @@ -61,20 +61,20 @@ WebVrView.prototype.size = function(size) {
return size;
};

WebVrView.prototype.setSize = function(size) {
WebXrView.prototype.setSize = function(size) {
this._width = size.width;
this._height = size.height;
};

WebVrView.prototype.projection = function() {
WebXrView.prototype.projection = function() {
return this._proj;
};

WebVrView.prototype.inverseProjection = function() {
WebXrView.prototype.inverseProjection = function() {
return this._invProj;
};

WebVrView.prototype.setProjection = function(proj) {
WebXrView.prototype.setProjection = function(proj) {
var p = this._proj;
var invp = this._invProj;
var f = this._frustum;
Expand All @@ -93,13 +93,30 @@ WebVrView.prototype.setProjection = function(proj) {
this.emit('change');
};

WebVrView.prototype.selectLevel = function(levelList) {
// Set projection matrix from a WebXR XRView
WebXrView.prototype.setProjectionFromXRView = function(xrView) {
var pose = mat4.create();
mat4.copy(pose, xrView.transform.matrix);
// Clear out translation
pose[12] = 0;
pose[13] = 0;
pose[14] = 0;
mat4.invert(pose, pose);

var proj = mat4.create();
mat4.copy(proj, xrView.projectionMatrix);
mat4.multiply(proj, proj, pose);

this.setProjection(proj);
};

WebXrView.prototype.selectLevel = function(levelList) {
// TODO: Figure out how to determine the most appropriate resolution.
// For now, always default to the highest resolution level.
return levelList[levelList.length-1];
};

WebVrView.prototype.intersects = function(rectangle) {
WebXrView.prototype.intersects = function(rectangle) {
// Check whether the rectangle is on the outer side of any of the frustum
// planes. This is a sufficient condition, though not necessary, for the
// rectangle to be completely outside the frustum.
Expand All @@ -123,4 +140,4 @@ WebVrView.prototype.intersects = function(rectangle) {
};

// Pretend to be a RectilinearView so that an appropriate renderer can be found.
WebVrView.type = WebVrView.prototype.type = 'rectilinear';
WebXrView.type = WebXrView.prototype.type = 'rectilinear';
217 changes: 110 additions & 107 deletions demos/webvr/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,119 +15,122 @@
*/
'use strict';

var mat4 = Marzipano.dependencies.glMatrix.mat4;
var quat = Marzipano.dependencies.glMatrix.quat;
async function main() {

var viewerElement = document.querySelector("#pano");
var enterVrElement = document.querySelector("#enter-vr");
var noVrElement = document.querySelector("#no-vr");

// Create stage and register renderers.
var stage = new Marzipano.WebGlStage();
Marzipano.registerDefaultRenderers(stage);

// Insert stage into the DOM.
viewerElement.appendChild(stage.domElement());

// Create geometry.
var geometry = new Marzipano.CubeGeometry([
{ tileSize: 256, size: 256, fallbackOnly: true },
{ tileSize: 512, size: 512 },
{ tileSize: 512, size: 1024 },
{ tileSize: 512, size: 2048 },
{ tileSize: 512, size: 4096 }
]);

// Create view.
var limiter = Marzipano.RectilinearView.limit.traditional(4096, 110*Math.PI/180);
var viewLeft = new WebXrView();
var viewRight = new WebXrView();

// Create layers.
var layerLeft = createLayer(stage, viewLeft, geometry, 'left',
{ relativeWidth: 0.5, relativeX: 0 });
var layerRight = createLayer(stage, viewRight, geometry, 'right',
{ relativeWidth: 0.5, relativeX: 0.5 });

// Add layers into stage.
stage.addLayer(layerLeft);
stage.addLayer(layerRight);

// WebXR session and rendering logic
let xrRefSpace = null;

let supported = await navigator.xr.isSessionSupported('immersive-vr');
enterVrElement.style.display = supported ? 'block' : 'none';
noVrElement.style.display = supported ? 'none' : 'block';

// Enter WebxR mode when the button is clicked.
enterVrElement.addEventListener('click', function() {
if (!navigator.xr) return;
navigator.xr.requestSession('immersive-vr', { requiredFeatures: ['local-floor'] }).then(onSessionStarted);
});

var degToRad = Marzipano.util.degToRad;
function onSessionStarted(session) {
let xrSession = session;

// Set up XRWebGLLayer with Marzipano's WebGL context
const gl = stage.webGlContext();
gl.makeXRCompatible().then(() => {
xrSession.updateRenderState({ baseLayer: new XRWebGLLayer(xrSession, gl) });
xrSession.requestReferenceSpace('local-floor').then(function(refSpace) {
xrRefSpace = refSpace;
xrSession.requestAnimationFrame(onXRFrame);
});
});
}

var viewerElement = document.querySelector("#pano");
var enterVrElement = document.querySelector("#enter-vr");
var noVrElement = document.querySelector("#no-vr");
function onXRFrame(time, frame) {
let session = frame.session;
let pose = frame.getViewerPose(xrRefSpace);
if (!pose) {
session.requestAnimationFrame(onXRFrame);
return;
}

// Ensure we're rendering to the layer's backbuffer.
let layer = session.renderState.baseLayer;
const gl = stage.webGlContext();
gl.bindFramebuffer(gl.FRAMEBUFFER, layer.framebuffer);

// For stereo, use the first two views (usually left/right)
if (pose.views.length >= 2) {
viewLeft.setProjectionFromXRView(pose.views[0]);
viewRight.setProjectionFromXRView(pose.views[1]);

let layer = session.renderState.baseLayer;
let viewportLeft = layer.getViewport(pose.views[0]);
let viewportRight = layer.getViewport(pose.views[1]);

// Width of stage is the width for the left and right eyes
stage.setSize({
width: viewportLeft.width + viewportRight.width,
height: viewportLeft.height
});

} else if (pose.views.length === 1) {
viewLeft.setProjectionFromXRView(pose.views[0]);
viewRight.setProjectionFromXRView(pose.views[0]);
}

stage.render();

session.requestAnimationFrame(onXRFrame);
}

// Install the WebVR polyfill, which makes the demo functional on "fake" WebVR
// displays such as Google Cardboard.
var polyfill = new WebVRPolyfill();
function createLayer(stage, view, geometry, eye, rect) {
var urlPrefix = "//www.marzipano.net/media/music-room";
var source = new Marzipano.ImageUrlSource.fromString(
urlPrefix + "/" + eye + "/{z}/{f}/{y}/{x}.jpg",
{ cubeMapPreviewUrl: urlPrefix + "/" + eye + "/preview.jpg" });

// Create stage and register renderers.
var stage = new Marzipano.WebGlStage();
Marzipano.registerDefaultRenderers(stage);
var textureStore = new Marzipano.TextureStore(source, stage);
var layer = new Marzipano.Layer(source, geometry, view, textureStore,
{ effects: { rect: rect }});

// Insert stage into the DOM.
viewerElement.appendChild(stage.domElement());
layer.pinFirstLevel();

// Update the stage size whenever the window is resized.
function updateSize() {
stage.setSize({
width: viewerElement.clientWidth,
height: viewerElement.clientHeight
});
}
updateSize();
window.addEventListener('resize', updateSize);

// Create geometry.
var geometry = new Marzipano.CubeGeometry([
{ tileSize: 256, size: 256, fallbackOnly: true },
{ tileSize: 512, size: 512 },
{ tileSize: 512, size: 1024 },
{ tileSize: 512, size: 2048 },
{ tileSize: 512, size: 4096 }
]);

// Create view.
var limiter = Marzipano.RectilinearView.limit.traditional(4096, 110*Math.PI/180);
var viewLeft = new WebVrView();
var viewRight = new WebVrView();

// Create layers.
var layerLeft = createLayer(stage, viewLeft, geometry, 'left',
{ relativeWidth: 0.5, relativeX: 0 });
var layerRight = createLayer(stage, viewRight, geometry, 'right',
{ relativeWidth: 0.5, relativeX: 0.5 });

// Add layers into stage.
stage.addLayer(layerLeft);
stage.addLayer(layerRight);

// Check for an available VR device and initialize accordingly.
var vrDisplay = null;
navigator.getVRDisplays().then(function(vrDisplays) {
if (vrDisplays.length > 0) {
vrDisplay = vrDisplays[0];
vrDisplay.requestAnimationFrame(render);
}
enterVrElement.style.display = vrDisplay ? 'block' : 'none';
noVrElement.style.display = vrDisplay ? 'none' : 'block';
});

// Enter WebVR mode when the button is clicked.
enterVrElement.addEventListener('click', function() {
vrDisplay.requestPresent([{source: stage.domElement()}]);
});

var proj = mat4.create();
var pose = mat4.create();

function render() {
var frameData = new VRFrameData;
vrDisplay.getFrameData(frameData);

// Update the view.
// The panorama demo at https://github.com/toji/webvr.info/tree/master/samples
// recommends computing the view matrix from `frameData.pose.orientation`
// instead of using `frameData.{left,right}ViewMatrix.
if (frameData.pose.orientation) {
mat4.fromQuat(pose, frameData.pose.orientation);
mat4.invert(pose, pose);

mat4.copy(proj, frameData.leftProjectionMatrix);
mat4.multiply(proj, proj, pose);
viewLeft.setProjection(proj);

mat4.copy(proj, frameData.rightProjectionMatrix);
mat4.multiply(proj, proj, pose);
viewRight.setProjection(proj);
return layer;
}

// Render and submit to WebVR display.
stage.render();
vrDisplay.submitFrame();

// Render again on the next frame.
vrDisplay.requestAnimationFrame(render);
}

function createLayer(stage, view, geometry, eye, rect) {
var urlPrefix = "//www.marzipano.net/media/music-room";
var source = new Marzipano.ImageUrlSource.fromString(
urlPrefix + "/" + eye + "/{z}/{f}/{y}/{x}.jpg",
{ cubeMapPreviewUrl: urlPrefix + "/" + eye + "/preview.jpg" });

var textureStore = new Marzipano.TextureStore(source, stage);
var layer = new Marzipano.Layer(source, geometry, view, textureStore,
{ effects: { rect: rect }});

layer.pinFirstLevel();

return layer;
}
main();