<div id="page_container">
<div id="canvas_container">
<div class="banner"><div class="text">Device is presenting</div></div>
<canvas id="webglCanvas"></canvas>
</div>
<div class="buttons">
<button class="presentation-hide" onclick="tryRequestPresent()">Request Present & Pointerlock</button>
<button class="presentation-show" onclick="tryExitPresent()">Exit Present</button>
<button class="presentation-show" onclick="webglCanvas.requestPointerLock()">Request Pointerlock</button>
</div>
<div class="action-source">Current Action Source: <span id="state"></span></div>
<h3>Log:</h3>
<div id="log"></div>
</div>
.default { color: #999; }
.pointer { color: #F00; }
.keyboard { color: #00F; }
.gamepad { color: #090; }
div.action-source { display: inline-block; padding: 10px;}
body, html {
width: 100%;
height: 100%;
margin: 0;
font-family: segoe ui;
}
h3 {
font-size: 20px;
}
div {
font-size: 12px;
}
.fail {
word-wrap: break-word;
color: red;
font-weight: bold;
}
.note {
word-wrap: break-word;
color: blue;
font-weight: bold;
}
#canvas_container {
height: 200px;
width: 400px;
float: right;
}
#canvas_container canvas {
height: 100%;
width: 100%;
}
#canvas_container .banner {
display: none;
height: 200px;
width: 400px;
background: grey;
position: absolute;
}
#canvas_container .banner .text {
color: white;
font-weight: bold;
padding: 15px;
}
#page_container.presenting.external .banner {
display: inherit;
}
#page_container.presenting .presentation-hide {
display: none;
}
#page_container .presentation-show {
display: none;
}
#page_container.presenting .presentation-show {
display: inline;
}
window.onerror = function (msg, url, lineNo, columnNo, error) {
displayNote(msg); return false;
}
var webglCanvas = document.getElementById("webglCanvas"),
gl = webglCanvas.getContext("experimental-webgl"),
animationFrameId,
vrDisplay = null,
myAction,
pulse = 0;
function init() {
// Defines what happens when user input is detected.
myAction = new Action(
function(source, active) {
// This is executed on any mouse button down, gamepad button down, or keyboard 'space' key down.
setState(active ? source : null);
});
setState('');
// Register for mouse restriction events, so we can request pointerlock.
// See https://codepen.io/ransico/pen/dRXxVY for more details on this pointerlock implementation
window.addEventListener('vrdisplaypointerrestricted', () => {
displayNote('vrdisplaypointerrestricted');
webglCanvas.requestPointerLock();
}, false);
window.addEventListener('vrdisplaypointerunrestricted', () => document.exitPointerLock(), false);
window.addEventListener('vrdisplaypresentchange', () => { if (!vrDisplay.isPresenting) document.exitPointerLock() }, false);
// Register for mouse (any button) event handlers
webglCanvas.addEventListener('pointerdown', () => myAction.activate('pointer'));
webglCanvas.addEventListener('pointerup', () => myAction.deactivate('pointer'));
// Register for keyboard (space bar)
window.addEventListener("keydown", (event) => { if (event.key === ' ') { myAction.activate('keyboard'); event.preventDefault(); }});
window.addEventListener("keyup", (event) => { if (event.key === ' ') { myAction.deactivate('keyboard'); event.preventDefault(); }});
}
// Simple helper function; simply checks if either button at index 0 or 1 is pressed on the given gamepad.
function isSelectButtonPressed(gamepad) {
if (!gamepad.buttons || !gamepad.buttons.length) return false;
if (gamepad.buttons[0].pressed) return true;
if (gamepad.buttons.length > 1 && gamepad.buttons[1].pressed) return true;
return false;
}
// This method is called one per frame, we need to read gamepad state here since the Gamepad API relies on Polling.
function tick() {
// You should make a new call to getGamepads each frame. Here we use feature detection to gracefully handle absence of gamepad support.
var gamepads = navigator.getGamepads ? navigator.getGamepads() : [];
var activate = false;
for (var i = 0; i < gamepads.length; i++) {
if (gamepads[i] && isSelectButtonPressed(gamepads[i])) {
activate = true;
break;
}
}
if (activate) myAction.activate('gamepad');
else myAction.deactivate('gamepad');
}
/*******************************************\
* RENDER LOOP AND UTIL CLASSES BELOW HERE *
\*******************************************/
// A simple representation of an action. This keeps track of which input source 'started' the press, to prevent duplicate events being fired when pressing multiple inputs simultaniously (touch + mouse, for example)
function Action(callback) {
this.callback = callback;
this.activate = function(source) {
if (this.active) { displayNote('ignored activate: ' + source); return };
displayNote('activate: ' + source);
this.active = source;
// Do something in your scene
callback(source, true);
};
this.deactivate = function(source) {
if (source !== this.active) return;
displayNote('deactivate: ' + source);
this.active = null;
// Do something in your scene
callback(null, false);
}
this.active = null;
}
function setState(text) {
text = text || 'deactivated';
document.getElementById('state').innerHTML = "<span class=\"" + text + "\">" + text + "</span>";
}
function tryRequestPresent() {
if (!vrDisplay) return displayRejection(null, "Please plug in an HMD");
// Part 2/2 of Fallback method for browsers that do not raise pointerrestricted events.
if (typeof(window.onvrdisplaypointerrestricted) === 'undefined') {
displayNote('requesting pointerlock in absense of window.vrdisplaypointerrestricted');
webglCanvas.requestPointerLock();
}
vrDisplay.requestPresent([{source: webglCanvas}]).then(() => {
displayNote('Entered VR on ' + vrDisplay.displayName);
}, (e) => {
displayRejection(e, "requestPresent rejected");
});
}
function onAnimationFrame() {
draw();
// Indicate that we are ready to present the rendered frame to the VRDisplay
if (vrDisplay) {
vrDisplay.requestAnimationFrame(onAnimationFrame);
if (vrDisplay.isPresenting) {
vrDisplay.submitFrame();
}
} else {
window.requestAnimationFrame(onAnimationFrame);
}
tick();
}
// Clear the screen color based on the action state.
var stateColors = {
default: [0.5, 0.5, 0.5, 1.0],
pointer: [1.0, 0.0, 0.0, 1.0],
gamepad: [0.0, 1.0, 0.0, 1.0],
keyboard: [0.0, 0.0, 1.0, 1.0]
};
function draw() {
pulse = ++pulse % 100;
let val = Math.sin(pulse * 0.06283) * 0.25 + 0.75,
color = stateColors[myAction.active || 'default'] || stateColors['default'];
gl.clearColor(
color[0] * val,
color[1] * val,
color[2] * val,
color[3]);
gl.clear(gl.COLOR_BUFFER_BIT);
}
function initVR() {
// Handle VRDisplay Detection
if (navigator.getVRDisplays) {
// Connection events
window.addEventListener('vrdisplayconnect', (event) => {
vrDisplay = event.display;
displayNote('VRDisplayConnect: ' + (event&&event.display?event.display.displayName:'Unknown'));
}, false );
window.addEventListener('vrdisplaydisconnect', (event) => vrdisplay = null, false);
// Initial page load, is there a display?
navigator.getVRDisplays().then((displays) => {
if (displays.length !== 1) {
return displayRejection(null, "getVRDisplays.length is 0 - plug in your HMD now.");
}
vrDisplay = displays[0];
}, (e) => {
displayRejection(e, "getVRDisplays rejected");
});
} else {
displayRejection(null, "navigator.getVRDisplays not defined. Does your browser support WebVR?");
}
// Change some styles on presentation change
window.addEventListener('vrdisplaypresentchange', (d) => {
let className = '';
if (vrDisplay.isPresenting) {
className += 'presenting';
className += vrDisplay.capabilities.hasExternalDisplay ? ' external' : ' non-external';
} else {
className = '';
}
document.getElementById('page_container').className = className;
});
window.requestAnimationFrame(onAnimationFrame);
}
function tryExitPresent() {
vrDisplay && vrDisplay.exitPresent().then(() => displayNote('Exited VR.'), displayRejection);
}
function displayNote(message) {
let elem = document.getElementById('log');
elem.innerHTML = '<div class="note">' + message + '</div>' + elem.innerHTML;
}
function displayRejection(e, message) {
let output = message + (e ? (': ' + (e.message || e)) : ''),
elem = document.getElementById('log');
elem.innerHTML = '<div class="fail">' + output + '</div>' + elem.innerHTML;
}
init();
initVR();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.