Skip to content

Commit

Permalink
WebVR prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
georgios-uber committed Mar 9, 2019
1 parent 8bf6cd8 commit cb7813a
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 17 deletions.
39 changes: 33 additions & 6 deletions examples/gltf/app.js
@@ -1,6 +1,6 @@
import {GLTFParser} from '@loaders.gl/gltf';
import {DracoDecoder} from '@loaders.gl/draco';
import {AnimationLoop, setParameters, clear, createGLTFObjects, log} from 'luma.gl';
import {AnimationLoop, setParameters, clear, createGLTFObjects, withParameters, log} from 'luma.gl';
import {Matrix4, radians} from 'math.gl';
import document from 'global/document';

Expand All @@ -10,6 +10,7 @@ const GLTF_MODEL_INDEX = `${GLTF_BASE_URL}model-index.json`;
const INFO_HTML = `
<p><b>glTF</b> rendering.</p>
<p>A luma.gl <code>glTF</code> renderer.</p>
<p><img src="https://img.shields.io/badge/WebVR-Supported-orange.svg" /></p>
<div>
Model
<select id="modelSelector">
Expand Down Expand Up @@ -164,7 +165,9 @@ export class DemoApp {
};
}

onInitialize({gl, canvas}) {
onInitialize({gl, canvas, _loop: animationLoop}) {
animationLoop.enableWebVR();

setParameters(gl, {
depthTest: true,
blend: false,
Expand Down Expand Up @@ -201,23 +204,47 @@ export class DemoApp {
this.initalizeEventHandling(canvas);
}

onRender({gl, time, width, height, aspect}) {
gl.viewport(0, 0, width, height);
clear(gl, {color: [0.2, 0.2, 0.2, 1.0], depth: true});
onRenderFrame(opts) {
const {gl, width, height, _vr: vr} = opts;

if (vr) {
vr.display.getFrameData(vr.frameData);

withParameters(gl, {
viewport: [0, 0, width * 0.5, height],
scissor: [0, 0, width * 0.5, height],
scissorTest: true
}, () => this.onRender(opts));

withParameters(gl, {
viewport: [width * 0.5, 0, width * 0.5, height],
scissor: [width * 0.5, 0, width * 0.5, height],
scissorTest: true
}, () => this.onRender(opts));

vr.display.submitFrame();
} else {
gl.viewport(0, 0, width, height);
this.onRender(opts);
}
}

onRender({gl, time, aspect}) {
clear(gl, {color: [0.2, 0.2, 0.2, 1.0], depth: true});
const [pitch, roll] = this.rotation;
const cameraPos = [
-this.translate * Math.sin(roll) * Math.cos(-pitch),
-this.translate * Math.sin(-pitch),
this.translate * Math.cos(roll) * Math.cos(-pitch)
];

const uView = new Matrix4()
const uView = new Matrix4() //(Array.from(this.frameData[`${eye}ViewMatrix`]))
.translate([0, 0, -this.translate])
.rotateX(pitch)
.rotateY(roll);

const uProjection = new Matrix4().perspective({fov: radians(40), aspect, near: 0.1, far: 9000});
// const uProjection = new Matrix4(Array.from(this.frameData[`${eye}ProjectionMatrix`]));

if (!this.scenes.length) return false;

Expand Down
3 changes: 2 additions & 1 deletion examples/gltf/package.json
Expand Up @@ -6,7 +6,8 @@
],
"scripts": {
"start": "webpack-dev-server --progress --hot --open -d",
"start-local": "webpack-dev-server --env.local --progress --hot --open -d"
"start-local": "webpack-dev-server --env.local --progress --hot --open -d",
"open-vr": "adb reverse tcp:8080 tcp:8080 && adb shell am start -a android.intent.action.VIEW -d http://localhost:8080/"
},
"dependencies": {
"@loaders.gl/draco": "^0.8.0",
Expand Down
89 changes: 85 additions & 4 deletions modules/core/src/core/animation-loop.js
@@ -1,4 +1,4 @@
/* global OffscreenCanvas */
/* global OffscreenCanvas, window, navigator, VRFrameData */

import {
createGLContext,
Expand Down Expand Up @@ -28,6 +28,7 @@ export default class AnimationLoop {
onAddHTML = null,
onInitialize = () => {},
onRender = () => {},
onRenderFrame = null,
onFinalize = () => {},

gl = null,
Expand All @@ -54,6 +55,7 @@ export default class AnimationLoop {
onAddHTML,
onInitialize,
onRender,
onRenderFrame,
onFinalize,

gl,
Expand Down Expand Up @@ -168,7 +170,7 @@ export default class AnimationLoop {
this._updateCallbackData();

// call callback
this.onRender(this.animationProps);
this.onRenderFrame(this.animationProps);
// end callback

// clear needsRedraw flag
Expand Down Expand Up @@ -224,6 +226,10 @@ export default class AnimationLoop {
return this.props.onInitialize(...args);
}

onRenderFrame(...args) {
return this.props.onRenderFrame(...args);
}

onRender(...args) {
return this.props.onRender(...args);
}
Expand All @@ -232,6 +238,77 @@ export default class AnimationLoop {
return this.props.onFinalize(...args);
}

async enableWebVR() {
if (!('getVRDisplays' in navigator)) {
return false;
}

const displays = await navigator.getVRDisplays();
if (displays && displays.length) {
log.info(2, "Found VR Displays", displays)();

this.vrDisplay = displays[0];
this.vrPresenting = false;
this._createEnterVRButton(this.vrDisplay.displayName);

return true;
}

return false;
}

async enterWebVR() {
await this.vrDisplay.requestPresent([{
source: this.gl.canvas
}]);

this.animationProps._vr = {
display: this.vrDisplay,
frameData: new VRFrameData()
};
this.vrPresenting = true;
}

exitWebVR() {
this.animationProps._vr = null;
this.vrPresenting = false;
}

// MOVE TO UTILS
_createEnterVRButton(name) {
const {top, left, width, height} = this.gl.canvas.getBoundingClientRect();

const container = document.createElement('div');
container.style.position = 'absolute';
container.style.top = `${top}px`;
container.style.left = `${left}px`;
container.style.width = `${width}px`;
container.style.height = `${height}px`;
container.style.pointerEvents = 'none';
container.style.zIndex = '999';
document.body.appendChild(container);

const button = document.createElement('button');
button.style.padding = '16px';
button.style.border = '1px solid #fff';
button.style.borderRadius = '8px';
button.style.background = 'rgba(0,0,0,0.5)';
button.style.color = '#fff';
button.style.font = 'normal 20px sans-serif';
button.style.cursor = 'pointer';
button.style.margin = '20px auto';
button.style.display = 'block';
button.style.pointerEvents = 'all';
button.textContent = `ENTER VR (${name})`;
container.appendChild(button);

button.onclick = () => {
button.style.display = 'none';
this.enterWebVR();
};
}
// MOVE TO UTILS

// DEPRECATED/REMOVED METHODS

getHTMLControlValue(id, defaultValue = 1) {
Expand All @@ -253,12 +330,16 @@ export default class AnimationLoop {
return;
}
this.redraw();
this._animationFrameId = requestAnimationFrame(renderFrame);
this._animationFrameId = requestAnimationFrame(renderFrame, this._animationFrameDevice());
};

// cancel any pending renders to ensure only one loop can ever run
cancelAnimationFrame(this._animationFrameId);
this._animationFrameId = requestAnimationFrame(renderFrame);
this._animationFrameId = requestAnimationFrame(renderFrame, this._animationFrameDevice());
}

_animationFrameDevice() {
return this.vrPresenting ? this.vrDisplay : window;
}

_clearNeedsRedraw() {
Expand Down
12 changes: 6 additions & 6 deletions modules/core/src/webgl/utils/request-animation-frame.js
@@ -1,14 +1,14 @@
// Node.js polyfills for requestAnimationFrame and cancelAnimationFrame
/* global window, setTimeout, clearTimeout */

export function requestAnimationFrame(callback) {
return typeof window !== 'undefined' && window.requestAnimationFrame
? window.requestAnimationFrame(callback)
export function requestAnimationFrame(callback, device = window) {
return typeof device !== 'undefined' && device.requestAnimationFrame
? device.requestAnimationFrame(callback)
: setTimeout(callback, 1000 / 60);
}

export function cancelAnimationFrame(timerId) {
return typeof window !== 'undefined' && window.cancelAnimationFrame
? window.cancelAnimationFrame(timerId)
export function cancelAnimationFrame(timerId, device = window) {
return typeof device !== 'undefined' && device.cancelAnimationFrame
? device.cancelAnimationFrame(timerId)
: clearTimeout(timerId);
}

0 comments on commit cb7813a

Please sign in to comment.