Skip to content

Instantly share code, notes, and snippets.

@gabrielflorit
Last active May 29, 2018 23:03
Show Gist options
  • Save gabrielflorit/89e567f3893f365774a8a911fa81ee90 to your computer and use it in GitHub Desktop.
Save gabrielflorit/89e567f3893f365774a8a911fa81ee90 to your computer and use it in GitHub Desktop.
WebXR - first test
license: mit
height: 500
border: no

flow

  • request an xr device
  • if device is available, advertise xr functionality to user
  • on user input, request xr session from device

Made with blockup

import Log from './log.js'
const log = Log()
// XR globals.
let xrButton = document.getElementById('xr-button')
let xrDevice = null
let xrSession = null
let xrFrameOfRef = null
// WebGL scene globals.
let gl = null
// Checks to see if WebXR is available and, if so, requests an XRDevice
// that is connected to the system and tests it to ensure it supports the
// desired session options.
function initXR () {
// Is WebXR available on this UA?
if (navigator.xr) {
// Request an XRDevice connected to the system.
navigator.xr.requestDevice().then(device => {
xrDevice = device
// If the device allows creation of exclusive sessions,
// set it as the target of the 'Enter XR' button.
device.supportsSession({ exclusive: true }).then(() => {
// Updates the button to start an XR session when clicked.
xrButton.addEventListener('click', onButtonClicked)
xrButton.innerHTML = 'Enter XR'
xrButton.disabled = false
})
})
} else {
log.message('This browser does not support the WebXR API.')
}
}
// Called when the user clicks the button to enter XR. If we don't have a
// session already we'll request one, and if we do we'll end it.
function onButtonClicked () {
if (!xrSession) {
xrDevice.requestSession({ exclusive: true }).then(onSessionStarted)
} else {
xrSession.end()
}
}
// Called when we've successfully acquired a XRSession. In response we
// will set up the necessary session state and kick off the frame loop.
function onSessionStarted (session) {
// Save session to global.
xrSession = session
xrButton.innerHTML = 'Exit XR'
// Listen for the sessions 'end' event so we can respond if the user
// or UA ends the session for any reason.
session.addEventListener('end', onSessionEnded)
// Create a WebGL context to render with, initialized to be compatible
// with the XRDisplay we're presenting to.
let canvas = document.createElement('canvas')
gl = canvas.getContext('webgl', {
compatibleXRDevice: session.device
})
// Use the new WebGL context to create a XRWebGLLayer and set it as the
// sessions baseLayer. This allows any content rendered to the layer to
// be displayed on the XRDevice.
session.baseLayer = new window.XRWebGLLayer(session, gl)
// Get a frame of reference, which is required for querying poses. In
// this case an 'eyeLevel' frame of reference means that all poses will
// be relative to the location where the XRDevice was first detected.
session.requestFrameOfReference('eyeLevel').then(frameOfRef => {
xrFrameOfRef = frameOfRef
// Inform the session that we're ready to begin drawing.
session.requestAnimationFrame(onXRFrame)
})
}
// // // Called when the user clicks the 'Exit XR' button. In response we end
// // // the session.
// // function onEndSession (session) {
// // session.end()
// // }
// Called either when the user has explicitly ended the session (like in
// onEndSession()) or when the UA has ended the session for any reason.
// At this point the session object is no longer usable and should be discarded.
function onSessionEnded (event) {
xrSession = null
xrButton.innerHTML = 'Enter VR'
// In this simple case discard the WebGL context too, since we're not
// rendering anything else to the screen with it.
gl = null
}
// Called every time the XRSession requests that a new frame be drawn.
function onXRFrame (t, frame) {
let session = frame.session
// Inform the session that we're ready for the next frame.
session.requestAnimationFrame(onXRFrame)
// Get the XRDevice pose relative to the Frame of Reference we created
// earlier.
let pose = frame.getDevicePose(xrFrameOfRef)
// Getting the pose may fail if, for example, tracking is lost. So we
// have to check to make sure that we got a valid pose before attempting
// to render with it. If not in this case we'll just leave the
// framebuffer cleared, so tracking loss means the scene will simply
// dissapear.
if (pose) {
// If we do have a valid pose, bind the WebGL layer's framebuffer,
// which is where any content to be displayed on the XRDevice must be
// rendered.
gl.bindFramebuffer(gl.FRAMEBUFFER, session.baseLayer.framebuffer)
// Update the clear color so that we can observe the color in the
// headset changing over time.
let time = Date.now()
gl.clearColor(
Math.cos(time / 2000),
Math.cos(time / 4000),
Math.cos(time / 6000),
1.0
)
// Clear the framebuffer
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
// Normally you'd loop through each of the views reported by the frame
// and draw them into the corresponding viewport here, but we're
// keeping this sample slim so we're not bothering to draw any
// geometry.
/* for (let view of frame.views) {
let viewport = session.baseLayer.getViewport(view);
gl.viewport(viewport.x, viewport.y,
viewport.width, viewport.height);
// Draw something.
} */
}
}
// Start the XR application.
initXR()
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>~/Documents/other/blocks/xr/first/demo.js.html</title>
<meta name="Generator" content="Vim/8.0">
<meta name="plugin-version" content="vim7.4_v2">
<meta name="syntax" content="javascript">
<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
<meta name="colorscheme" content="OceanicNext">
<style type="text/css">
<!--
pre { white-space: pre-wrap; font-family: monospace; color: #c6c6c6; background-color: #262626; }
body { font-family: monospace; color: #c6c6c6; background-color: #262626; }
* { font-size: 1em; }
.String { color: #87d787; }
.Boolean { color: #ff875f; }
.Function { color: #5f87d7; }
.Structure { color: #d787d7; }
.javaScriptNumber { color: #ff875f; }
.Normal { color: #c6c6c6; background-color: #262626; padding-bottom: 1px; }
.Comment { color: #767676; }
.Special { color: #5fafaf; }
.Identifier { color: #ff5f5f; }
.Statement { color: #ff5f5f; font-weight: bold; }
.Conditional { color: #d787d7; }
.Label { color: #ffd75f; }
.Keyword { color: #d787d7; }
.Include { color: #5f87d7; }
.javaScriptBraces { color: #c6c6c6; }
-->
</style>
<script type='text/javascript'>
<!--
-->
</script>
</head>
<body>
<pre id='vimCodeElement'>
<span class="Include">import</span> Log <span class="Include">from</span> <span class="String">'./log.js'</span>
<span class="Identifier">const</span> log <span class="Normal">=</span> Log<span class="Normal">()</span>
<span class="Comment">// XR globals.</span>
<span class="Identifier">let</span> xrButton <span class="Normal">=</span> document.<span class="Keyword">getElementById</span><span class="Normal">(</span><span class="String">'xr-button'</span><span class="Normal">)</span>
<span class="Identifier">let</span> xrDevice <span class="Normal">=</span> <span class="Boolean">null</span>
<span class="Identifier">let</span> xrSession <span class="Normal">=</span> <span class="Boolean">null</span>
<span class="Identifier">let</span> xrFrameOfRef <span class="Normal">=</span> <span class="Boolean">null</span>
<span class="Comment">// WebGL scene globals.</span>
<span class="Identifier">let</span> gl <span class="Normal">=</span> <span class="Boolean">null</span>
<span class="Comment">// Checks to see if WebXR is available and, if so, requests an XRDevice</span>
<span class="Comment">// that is connected to the system and tests it to ensure it supports the</span>
<span class="Comment">// desired session options.</span>
<span class="Keyword">function</span> <span class="Function">initXR</span> <span class="Normal">()</span> <span class="javaScriptBraces">{</span>
<span class="Comment">// Is WebXR available on this UA?</span>
<span class="Conditional">if</span> (<span class="Structure">navigator</span>.xr) <span class="javaScriptBraces">{</span>
<span class="Comment">// Request an XRDevice connected to the system.</span>
<span class="Structure">navigator</span>.xr.requestDevice<span class="Normal">()</span>.<span class="Keyword">then</span><span class="Normal">(</span><span class="Special">device</span> <span class="Statement">=&gt;</span> <span class="javaScriptBraces">{</span>
xrDevice <span class="Normal">=</span> device
<span class="Comment">// If the device allows creation of exclusive sessions,</span>
<span class="Comment">// set it as the target of the 'Enter XR' button.</span>
device.supportsSession<span class="Normal">(</span><span class="javaScriptBraces">{</span> <span class="Label">exclusive</span>: <span class="Boolean">true</span> <span class="javaScriptBraces">}</span><span class="Normal">)</span>.<span class="Keyword">then</span><span class="Normal">(()</span> <span class="Statement">=&gt;</span> <span class="javaScriptBraces">{</span>
<span class="Comment">// Updates the button to start an XR session when clicked.</span>
xrButton.<span class="Keyword">addEventListener</span><span class="Normal">(</span><span class="String">'click'</span><span class="Normal">,</span> onButtonClicked<span class="Normal">)</span>
xrButton.innerHTML <span class="Normal">=</span> <span class="String">'Enter XR'</span>
xrButton.disabled <span class="Normal">=</span> <span class="Boolean">false</span>
<span class="javaScriptBraces">}</span><span class="Normal">)</span>
<span class="javaScriptBraces">}</span><span class="Normal">)</span>
<span class="javaScriptBraces">}</span> <span class="Conditional">else</span> <span class="javaScriptBraces">{</span>
log.message<span class="Normal">(</span><span class="String">'This browser does not support the WebXR API.'</span><span class="Normal">)</span>
<span class="javaScriptBraces">}</span>
<span class="javaScriptBraces">}</span>
<span class="Comment">// Called when the user clicks the button to enter XR. If we don't have a</span>
<span class="Comment">// session already we'll request one, and if we do we'll end it.</span>
<span class="Keyword">function</span> <span class="Function">onButtonClicked</span> <span class="Normal">()</span> <span class="javaScriptBraces">{</span>
<span class="Conditional">if</span> (!xrSession) <span class="javaScriptBraces">{</span>
xrDevice.requestSession<span class="Normal">(</span><span class="javaScriptBraces">{</span> <span class="Label">exclusive</span>: <span class="Boolean">true</span> <span class="javaScriptBraces">}</span><span class="Normal">)</span>.<span class="Keyword">then</span><span class="Normal">(</span>onSessionStarted<span class="Normal">)</span>
<span class="javaScriptBraces">}</span> <span class="Conditional">else</span> <span class="javaScriptBraces">{</span>
xrSession.end<span class="Normal">()</span>
<span class="javaScriptBraces">}</span>
<span class="javaScriptBraces">}</span>
<span class="Comment">// Called when we've successfully acquired a XRSession. In response we</span>
<span class="Comment">// will set up the necessary session state and kick off the frame loop.</span>
<span class="Keyword">function</span> <span class="Function">onSessionStarted</span> <span class="Normal">(</span><span class="Special">session</span><span class="Normal">)</span> <span class="javaScriptBraces">{</span>
<span class="Comment">// Save session to global.</span>
xrSession <span class="Normal">=</span> session
xrButton.innerHTML <span class="Normal">=</span> <span class="String">'Exit XR'</span>
<span class="Comment">// Listen for the sessions 'end' event so we can respond if the user</span>
<span class="Comment">// or UA ends the session for any reason.</span>
session.<span class="Keyword">addEventListener</span><span class="Normal">(</span><span class="String">'end'</span><span class="Normal">,</span> onSessionEnded<span class="Normal">)</span>
<span class="Comment">// Create a WebGL context to render with, initialized to be compatible</span>
<span class="Comment">// with the XRDisplay we're presenting to.</span>
<span class="Identifier">let</span> canvas <span class="Normal">=</span> document.<span class="Keyword">createElement</span><span class="Normal">(</span><span class="String">'canvas'</span><span class="Normal">)</span>
gl <span class="Normal">=</span> canvas.getContext<span class="Normal">(</span><span class="String">'webgl'</span><span class="Normal">,</span> <span class="javaScriptBraces">{</span>
<span class="Label">compatibleXRDevice</span>: session.device
<span class="javaScriptBraces">}</span><span class="Normal">)</span>
<span class="Comment">// Use the new WebGL context to create a XRWebGLLayer and set it as the</span>
<span class="Comment">// sessions baseLayer. This allows any content rendered to the layer to</span>
<span class="Comment">// be displayed on the XRDevice.</span>
session.baseLayer <span class="Normal">=</span> <span class="Identifier">new</span> window.XRWebGLLayer<span class="Normal">(</span>session<span class="Normal">,</span> gl<span class="Normal">)</span>
<span class="Comment">// Get a frame of reference, which is required for querying poses. In</span>
<span class="Comment">// this case an 'eyeLevel' frame of reference means that all poses will</span>
<span class="Comment">// be relative to the location where the XRDevice was first detected.</span>
session.requestFrameOfReference<span class="Normal">(</span><span class="String">'eyeLevel'</span><span class="Normal">)</span>.<span class="Keyword">then</span><span class="Normal">(</span><span class="Special">frameOfRef</span> <span class="Statement">=&gt;</span> <span class="javaScriptBraces">{</span>
xrFrameOfRef <span class="Normal">=</span> frameOfRef
<span class="Comment">// Inform the session that we're ready to begin drawing.</span>
session.requestAnimationFrame<span class="Normal">(</span>onXRFrame<span class="Normal">)</span>
<span class="javaScriptBraces">}</span><span class="Normal">)</span>
<span class="javaScriptBraces">}</span>
<span class="Comment">// // // Called when the user clicks the 'Exit XR' button. In response we end</span>
<span class="Comment">// // // the session.</span>
<span class="Comment">// // function onEndSession (session) {</span>
<span class="Comment">// // session.end()</span>
<span class="Comment">// // }</span>
<span class="Comment">// Called either when the user has explicitly ended the session (like in</span>
<span class="Comment">// onEndSession()) or when the UA has ended the session for any reason.</span>
<span class="Comment">// At this point the session object is no longer usable and should be discarded.</span>
<span class="Keyword">function</span> <span class="Function">onSessionEnded</span> <span class="Normal">(</span><span class="Special">event</span><span class="Normal">)</span> <span class="javaScriptBraces">{</span>
xrSession <span class="Normal">=</span> <span class="Boolean">null</span>
xrButton.innerHTML <span class="Normal">=</span> <span class="String">'Enter VR'</span>
<span class="Comment">// In this simple case discard the WebGL context too, since we're not</span>
<span class="Comment">// rendering anything else to the screen with it.</span>
gl <span class="Normal">=</span> <span class="Boolean">null</span>
<span class="javaScriptBraces">}</span>
<span class="Comment">// Called every time the XRSession requests that a new frame be drawn.</span>
<span class="Keyword">function</span> <span class="Function">onXRFrame</span> <span class="Normal">(</span><span class="Special">t</span><span class="Normal">,</span><span class="Special"> frame</span><span class="Normal">)</span> <span class="javaScriptBraces">{</span>
<span class="Identifier">let</span> session <span class="Normal">=</span> frame.session
<span class="Comment">// Inform the session that we're ready for the next frame.</span>
session.requestAnimationFrame<span class="Normal">(</span>onXRFrame<span class="Normal">)</span>
<span class="Comment">// Get the XRDevice pose relative to the Frame of Reference we created</span>
<span class="Comment">// earlier.</span>
<span class="Identifier">let</span> pose <span class="Normal">=</span> frame.getDevicePose<span class="Normal">(</span>xrFrameOfRef<span class="Normal">)</span>
<span class="Comment">// Getting the pose may fail if, for example, tracking is lost. So we</span>
<span class="Comment">// have to check to make sure that we got a valid pose before attempting</span>
<span class="Comment">// to render with it. If not in this case we'll just leave the</span>
<span class="Comment">// framebuffer cleared, so tracking loss means the scene will simply</span>
<span class="Comment">// dissapear.</span>
<span class="Conditional">if</span> (pose) <span class="javaScriptBraces">{</span>
<span class="Comment">// If we do have a valid pose, bind the WebGL layer's framebuffer,</span>
<span class="Comment">// which is where any content to be displayed on the XRDevice must be</span>
<span class="Comment">// rendered.</span>
gl.bindFramebuffer<span class="Normal">(</span>gl.FRAMEBUFFER<span class="Normal">,</span> session.baseLayer.framebuffer<span class="Normal">)</span>
<span class="Comment">// Update the clear color so that we can observe the color in the</span>
<span class="Comment">// headset changing over time.</span>
<span class="Identifier">let</span> time <span class="Normal">=</span> <span class="Structure">Date</span>.<span class="Keyword">now</span><span class="Normal">()</span>
gl.clearColor<span class="Normal">(</span>
<span class="Structure">Math</span>.<span class="Keyword">cos</span><span class="Normal">(</span>time <span class="Normal">/</span> <span class="javaScriptNumber">2000</span><span class="Normal">)</span><span class="Normal">,</span>
<span class="Structure">Math</span>.<span class="Keyword">cos</span><span class="Normal">(</span>time <span class="Normal">/</span> <span class="javaScriptNumber">4000</span><span class="Normal">)</span><span class="Normal">,</span>
<span class="Structure">Math</span>.<span class="Keyword">cos</span><span class="Normal">(</span>time <span class="Normal">/</span> <span class="javaScriptNumber">6000</span><span class="Normal">)</span><span class="Normal">,</span>
<span class="javaScriptNumber">1.0</span>
<span class="Normal">)</span>
<span class="Comment">// Clear the framebuffer</span>
gl.<span class="Keyword">clear</span><span class="Normal">(</span>gl.COLOR_BUFFER_BIT <span class="Normal">|</span> gl.DEPTH_BUFFER_BIT<span class="Normal">)</span>
<span class="Comment">// Normally you'd loop through each of the views reported by the frame</span>
<span class="Comment">// and draw them into the corresponding viewport here, but we're</span>
<span class="Comment">// keeping this sample slim so we're not bothering to draw any</span>
<span class="Comment">// geometry.</span>
<span class="Comment">/* for (let view of frame.views) {</span>
<span class="Comment"> let viewport = session.baseLayer.getViewport(view);</span>
<span class="Comment"> gl.viewport(viewport.x, viewport.y,</span>
<span class="Comment"> viewport.width, viewport.height);</span>
<span class="Comment"> // Draw something.</span>
<span class="Comment"> } */</span>
<span class="javaScriptBraces">}</span>
<span class="javaScriptBraces">}</span>
<span class="Comment">// Start the XR application.</span>
initXR<span class="Normal">()</span>
</pre>
</body>
</html>
<!-- vim: set foldmethod=manual : -->
*{box-sizing:border-box}
!function(e){function n(t){if(l[t])return l[t].exports;var c=l[t]={i:t,l:!1,exports:{}};return e[t].call(c.exports,c,c.exports,n),c.l=!0,c.exports}var l={};n.m=e,n.c=l,n.i=function(e){return e},n.d=function(e,l,t){n.o(e,l)||Object.defineProperty(e,l,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var l=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(l,"a",l),l},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n(n.s=2)}([function(module,exports,__webpack_require__){"use strict";eval("\n\nvar _log = __webpack_require__(1);\n\nvar _log2 = _interopRequireDefault(_log);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nvar log = (0, _log2.default)();\n\n// XR globals.\nvar xrButton = document.getElementById('xr-button');\nvar xrDevice = null;\nvar xrSession = null;\nvar xrFrameOfRef = null;\n\n// WebGL scene globals.\nvar gl = null;\n\n// Checks to see if WebXR is available and, if so, requests an XRDevice\n// that is connected to the system and tests it to ensure it supports the\n// desired session options.\nfunction initXR() {\n // Is WebXR available on this UA?\n if (navigator.xr) {\n // Request an XRDevice connected to the system.\n navigator.xr.requestDevice().then(function (device) {\n xrDevice = device;\n // If the device allows creation of exclusive sessions,\n // set it as the target of the 'Enter XR' button.\n device.supportsSession({ exclusive: true }).then(function () {\n // Updates the button to start an XR session when clicked.\n xrButton.addEventListener('click', onButtonClicked);\n xrButton.innerHTML = 'Enter XR';\n xrButton.disabled = false;\n });\n });\n } else {\n log.message('This browser does not support the WebXR API.');\n }\n}\n\n// Called when the user clicks the button to enter XR. If we don't have a\n// session already we'll request one, and if we do we'll end it.\nfunction onButtonClicked() {\n if (!xrSession) {\n xrDevice.requestSession({ exclusive: true }).then(onSessionStarted);\n } else {\n xrSession.end();\n }\n}\n\n// Called when we've successfully acquired a XRSession. In response we\n// will set up the necessary session state and kick off the frame loop.\nfunction onSessionStarted(session) {\n // Save session to global.\n xrSession = session;\n xrButton.innerHTML = 'Exit XR';\n\n // Listen for the sessions 'end' event so we can respond if the user\n // or UA ends the session for any reason.\n session.addEventListener('end', onSessionEnded);\n\n // Create a WebGL context to render with, initialized to be compatible\n // with the XRDisplay we're presenting to.\n var canvas = document.createElement('canvas');\n gl = canvas.getContext('webgl', {\n compatibleXRDevice: session.device\n });\n\n // Use the new WebGL context to create a XRWebGLLayer and set it as the\n // sessions baseLayer. This allows any content rendered to the layer to\n // be displayed on the XRDevice.\n session.baseLayer = new window.XRWebGLLayer(session, gl);\n\n // Get a frame of reference, which is required for querying poses. In\n // this case an 'eyeLevel' frame of reference means that all poses will\n // be relative to the location where the XRDevice was first detected.\n session.requestFrameOfReference('eyeLevel').then(function (frameOfRef) {\n xrFrameOfRef = frameOfRef;\n // Inform the session that we're ready to begin drawing.\n session.requestAnimationFrame(onXRFrame);\n });\n}\n\n// // // Called when the user clicks the 'Exit XR' button. In response we end\n// // // the session.\n// // function onEndSession (session) {\n// // session.end()\n// // }\n\n// Called either when the user has explicitly ended the session (like in\n// onEndSession()) or when the UA has ended the session for any reason.\n// At this point the session object is no longer usable and should be discarded.\nfunction onSessionEnded(event) {\n xrSession = null;\n xrButton.innerHTML = 'Enter VR';\n // In this simple case discard the WebGL context too, since we're not\n // rendering anything else to the screen with it.\n gl = null;\n}\n\n// Called every time the XRSession requests that a new frame be drawn.\nfunction onXRFrame(t, frame) {\n var session = frame.session;\n\n // Inform the session that we're ready for the next frame.\n session.requestAnimationFrame(onXRFrame);\n\n // Get the XRDevice pose relative to the Frame of Reference we created\n // earlier.\n var pose = frame.getDevicePose(xrFrameOfRef);\n\n // Getting the pose may fail if, for example, tracking is lost. So we\n // have to check to make sure that we got a valid pose before attempting\n // to render with it. If not in this case we'll just leave the\n // framebuffer cleared, so tracking loss means the scene will simply\n // dissapear.\n if (pose) {\n // If we do have a valid pose, bind the WebGL layer's framebuffer,\n // which is where any content to be displayed on the XRDevice must be\n // rendered.\n gl.bindFramebuffer(gl.FRAMEBUFFER, session.baseLayer.framebuffer);\n\n // Update the clear color so that we can observe the color in the\n // headset changing over time.\n var time = Date.now();\n gl.clearColor(Math.cos(time / 2000), Math.cos(time / 4000), Math.cos(time / 6000), 1.0);\n\n // Clear the framebuffer\n gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);\n\n // Normally you'd loop through each of the views reported by the frame\n // and draw them into the corresponding viewport here, but we're\n // keeping this sample slim so we're not bothering to draw any\n // geometry.\n /* for (let view of frame.views) {\n let viewport = session.baseLayer.getViewport(view);\n gl.viewport(viewport.x, viewport.y,\n viewport.width, viewport.height);\n // Draw something.\n } */\n }\n}\n\n// Start the XR application.\ninitXR();//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9kZW1vLmpzPzk2MDAiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IExvZyBmcm9tICcuL2xvZy5qcydcbmNvbnN0IGxvZyA9IExvZygpXG5cbi8vIFhSIGdsb2JhbHMuXG5sZXQgeHJCdXR0b24gPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgneHItYnV0dG9uJylcbmxldCB4ckRldmljZSA9IG51bGxcbmxldCB4clNlc3Npb24gPSBudWxsXG5sZXQgeHJGcmFtZU9mUmVmID0gbnVsbFxuXG4vLyBXZWJHTCBzY2VuZSBnbG9iYWxzLlxubGV0IGdsID0gbnVsbFxuXG4vLyBDaGVja3MgdG8gc2VlIGlmIFdlYlhSIGlzIGF2YWlsYWJsZSBhbmQsIGlmIHNvLCByZXF1ZXN0cyBhbiBYUkRldmljZVxuLy8gdGhhdCBpcyBjb25uZWN0ZWQgdG8gdGhlIHN5c3RlbSBhbmQgdGVzdHMgaXQgdG8gZW5zdXJlIGl0IHN1cHBvcnRzIHRoZVxuLy8gZGVzaXJlZCBzZXNzaW9uIG9wdGlvbnMuXG5mdW5jdGlvbiBpbml0WFIgKCkge1xuICAvLyBJcyBXZWJYUiBhdmFpbGFibGUgb24gdGhpcyBVQT9cbiAgaWYgKG5hdmlnYXRvci54cikge1xuICAgIC8vIFJlcXVlc3QgYW4gWFJEZXZpY2UgY29ubmVjdGVkIHRvIHRoZSBzeXN0ZW0uXG4gICAgbmF2aWdhdG9yLnhyLnJlcXVlc3REZXZpY2UoKS50aGVuKGRldmljZSA9PiB7XG4gICAgICB4ckRldmljZSA9IGRldmljZVxuICAgICAgLy8gSWYgdGhlIGRldmljZSBhbGxvd3MgY3JlYXRpb24gb2YgZXhjbHVzaXZlIHNlc3Npb25zLFxuICAgICAgLy8gc2V0IGl0IGFzIHRoZSB0YXJnZXQgb2YgdGhlICdFbnRlciBYUicgYnV0dG9uLlxuICAgICAgZGV2aWNlLnN1cHBvcnRzU2Vzc2lvbih7IGV4Y2x1c2l2ZTogdHJ1ZSB9KS50aGVuKCgpID0+IHtcbiAgICAgICAgLy8gVXBkYXRlcyB0aGUgYnV0dG9uIHRvIHN0YXJ0IGFuIFhSIHNlc3Npb24gd2hlbiBjbGlja2VkLlxuICAgICAgICB4ckJ1dHRvbi5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIG9uQnV0dG9uQ2xpY2tlZClcbiAgICAgICAgeHJCdXR0b24uaW5uZXJIVE1MID0gJ0VudGVyIFhSJ1xuICAgICAgICB4ckJ1dHRvbi5kaXNhYmxlZCA9IGZhbHNlXG4gICAgICB9KVxuICAgIH0pXG4gIH0gZWxzZSB7XG4gICAgbG9nLm1lc3NhZ2UoJ1RoaXMgYnJvd3NlciBkb2VzIG5vdCBzdXBwb3J0IHRoZSBXZWJYUiBBUEkuJylcbiAgfVxufVxuXG4vLyBDYWxsZWQgd2hlbiB0aGUgdXNlciBjbGlja3MgdGhlIGJ1dHRvbiB0byBlbnRlciBYUi4gSWYgd2UgZG9uJ3QgaGF2ZSBhXG4vLyBzZXNzaW9uIGFscmVhZHkgd2UnbGwgcmVxdWVzdCBvbmUsIGFuZCBpZiB3ZSBkbyB3ZSdsbCBlbmQgaXQuXG5mdW5jdGlvbiBvbkJ1dHRvbkNsaWNrZWQgKCkge1xuICBpZiAoIXhyU2Vzc2lvbikge1xuICAgIHhyRGV2aWNlLnJlcXVlc3RTZXNzaW9uKHsgZXhjbHVzaXZlOiB0cnVlIH0pLnRoZW4ob25TZXNzaW9uU3RhcnRlZClcbiAgfSBlbHNlIHtcbiAgICB4clNlc3Npb24uZW5kKClcbiAgfVxufVxuXG4vLyBDYWxsZWQgd2hlbiB3ZSd2ZSBzdWNjZXNzZnVsbHkgYWNxdWlyZWQgYSBYUlNlc3Npb24uIEluIHJlc3BvbnNlIHdlXG4vLyB3aWxsIHNldCB1cCB0aGUgbmVjZXNzYXJ5IHNlc3Npb24gc3RhdGUgYW5kIGtpY2sgb2ZmIHRoZSBmcmFtZSBsb29wLlxuZnVuY3Rpb24gb25TZXNzaW9uU3RhcnRlZCAoc2Vzc2lvbikge1xuICAvLyBTYXZlIHNlc3Npb24gdG8gZ2xvYmFsLlxuICB4clNlc3Npb24gPSBzZXNzaW9uXG4gIHhyQnV0dG9uLmlubmVySFRNTCA9ICdFeGl0IFhSJ1xuXG4gIC8vIExpc3RlbiBmb3IgdGhlIHNlc3Npb25zICdlbmQnIGV2ZW50IHNvIHdlIGNhbiByZXNwb25kIGlmIHRoZSB1c2VyXG4gIC8vIG9yIFVBIGVuZHMgdGhlIHNlc3Npb24gZm9yIGFueSByZWFzb24uXG4gIHNlc3Npb24uYWRkRXZlbnRMaXN0ZW5lcignZW5kJywgb25TZXNzaW9uRW5kZWQpXG5cbiAgLy8gQ3JlYXRlIGEgV2ViR0wgY29udGV4dCB0byByZW5kZXIgd2l0aCwgaW5pdGlhbGl6ZWQgdG8gYmUgY29tcGF0aWJsZVxuICAvLyB3aXRoIHRoZSBYUkRpc3BsYXkgd2UncmUgcHJlc2VudGluZyB0by5cbiAgbGV0IGNhbnZhcyA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2NhbnZhcycpXG4gIGdsID0gY2FudmFzLmdldENvbnRleHQoJ3dlYmdsJywge1xuICAgIGNvbXBhdGlibGVYUkRldmljZTogc2Vzc2lvbi5kZXZpY2VcbiAgfSlcblxuICAvLyBVc2UgdGhlIG5ldyBXZWJHTCBjb250ZXh0IHRvIGNyZWF0ZSBhIFhSV2ViR0xMYXllciBhbmQgc2V0IGl0IGFzIHRoZVxuICAvLyBzZXNzaW9ucyBiYXNlTGF5ZXIuIFRoaXMgYWxsb3dzIGFueSBjb250ZW50IHJlbmRlcmVkIHRvIHRoZSBsYXllciB0b1xuICAvLyBiZSBkaXNwbGF5ZWQgb24gdGhlIFhSRGV2aWNlLlxuICBzZXNzaW9uLmJhc2VMYXllciA9IG5ldyB3aW5kb3cuWFJXZWJHTExheWVyKHNlc3Npb24sIGdsKVxuXG4gIC8vIEdldCBhIGZyYW1lIG9mIHJlZmVyZW5jZSwgd2hpY2ggaXMgcmVxdWlyZWQgZm9yIHF1ZXJ5aW5nIHBvc2VzLiBJblxuICAvLyB0aGlzIGNhc2UgYW4gJ2V5ZUxldmVsJyBmcmFtZSBvZiByZWZlcmVuY2UgbWVhbnMgdGhhdCBhbGwgcG9zZXMgd2lsbFxuICAvLyBiZSByZWxhdGl2ZSB0byB0aGUgbG9jYXRpb24gd2hlcmUgdGhlIFhSRGV2aWNlIHdhcyBmaXJzdCBkZXRlY3RlZC5cbiAgc2Vzc2lvbi5yZXF1ZXN0RnJhbWVPZlJlZmVyZW5jZSgnZXllTGV2ZWwnKS50aGVuKGZyYW1lT2ZSZWYgPT4ge1xuICAgIHhyRnJhbWVPZlJlZiA9IGZyYW1lT2ZSZWZcbiAgICAvLyBJbmZvcm0gdGhlIHNlc3Npb24gdGhhdCB3ZSdyZSByZWFkeSB0byBiZWdpbiBkcmF3aW5nLlxuICAgIHNlc3Npb24ucmVxdWVzdEFuaW1hdGlvbkZyYW1lKG9uWFJGcmFtZSlcbiAgfSlcbn1cblxuLy8gLy8gLy8gQ2FsbGVkIHdoZW4gdGhlIHVzZXIgY2xpY2tzIHRoZSAnRXhpdCBYUicgYnV0dG9uLiBJbiByZXNwb25zZSB3ZSBlbmRcbi8vIC8vIC8vIHRoZSBzZXNzaW9uLlxuLy8gLy8gZnVuY3Rpb24gb25FbmRTZXNzaW9uIChzZXNzaW9uKSB7XG4vLyAvLyAgIHNlc3Npb24uZW5kKClcbi8vIC8vIH1cblxuLy8gQ2FsbGVkIGVpdGhlciB3aGVuIHRoZSB1c2VyIGhhcyBleHBsaWNpdGx5IGVuZGVkIHRoZSBzZXNzaW9uIChsaWtlIGluXG4vLyBvbkVuZFNlc3Npb24oKSkgb3Igd2hlbiB0aGUgVUEgaGFzIGVuZGVkIHRoZSBzZXNzaW9uIGZvciBhbnkgcmVhc29uLlxuLy8gQXQgdGhpcyBwb2ludCB0aGUgc2Vzc2lvbiBvYmplY3QgaXMgbm8gbG9uZ2VyIHVzYWJsZSBhbmQgc2hvdWxkIGJlIGRpc2NhcmRlZC5cbmZ1bmN0aW9uIG9uU2Vzc2lvbkVuZGVkIChldmVudCkge1xuICB4clNlc3Npb24gPSBudWxsXG4gIHhyQnV0dG9uLmlubmVySFRNTCA9ICdFbnRlciBWUidcbiAgLy8gSW4gdGhpcyBzaW1wbGUgY2FzZSBkaXNjYXJkIHRoZSBXZWJHTCBjb250ZXh0IHRvbywgc2luY2Ugd2UncmUgbm90XG4gIC8vIHJlbmRlcmluZyBhbnl0aGluZyBlbHNlIHRvIHRoZSBzY3JlZW4gd2l0aCBpdC5cbiAgZ2wgPSBudWxsXG59XG5cbi8vIENhbGxlZCBldmVyeSB0aW1lIHRoZSBYUlNlc3Npb24gcmVxdWVzdHMgdGhhdCBhIG5ldyBmcmFtZSBiZSBkcmF3bi5cbmZ1bmN0aW9uIG9uWFJGcmFtZSAodCwgZnJhbWUpIHtcbiAgbGV0IHNlc3Npb24gPSBmcmFtZS5zZXNzaW9uXG5cbiAgLy8gSW5mb3JtIHRoZSBzZXNzaW9uIHRoYXQgd2UncmUgcmVhZHkgZm9yIHRoZSBuZXh0IGZyYW1lLlxuICBzZXNzaW9uLnJlcXVlc3RBbmltYXRpb25GcmFtZShvblhSRnJhbWUpXG5cbiAgLy8gR2V0IHRoZSBYUkRldmljZSBwb3NlIHJlbGF0aXZlIHRvIHRoZSBGcmFtZSBvZiBSZWZlcmVuY2Ugd2UgY3JlYXRlZFxuICAvLyBlYXJsaWVyLlxuICBsZXQgcG9zZSA9IGZyYW1lLmdldERldmljZVBvc2UoeHJGcmFtZU9mUmVmKVxuXG4gIC8vIEdldHRpbmcgdGhlIHBvc2UgbWF5IGZhaWwgaWYsIGZvciBleGFtcGxlLCB0cmFja2luZyBpcyBsb3N0LiBTbyB3ZVxuICAvLyBoYXZlIHRvIGNoZWNrIHRvIG1ha2Ugc3VyZSB0aGF0IHdlIGdvdCBhIHZhbGlkIHBvc2UgYmVmb3JlIGF0dGVtcHRpbmdcbiAgLy8gdG8gcmVuZGVyIHdpdGggaXQuIElmIG5vdCBpbiB0aGlzIGNhc2Ugd2UnbGwganVzdCBsZWF2ZSB0aGVcbiAgLy8gZnJhbWVidWZmZXIgY2xlYXJlZCwgc28gdHJhY2tpbmcgbG9zcyBtZWFucyB0aGUgc2NlbmUgd2lsbCBzaW1wbHlcbiAgLy8gZGlzc2FwZWFyLlxuICBpZiAocG9zZSkge1xuICAgIC8vIElmIHdlIGRvIGhhdmUgYSB2YWxpZCBwb3NlLCBiaW5kIHRoZSBXZWJHTCBsYXllcidzIGZyYW1lYnVmZmVyLFxuICAgIC8vIHdoaWNoIGlzIHdoZXJlIGFueSBjb250ZW50IHRvIGJlIGRpc3BsYXllZCBvbiB0aGUgWFJEZXZpY2UgbXVzdCBiZVxuICAgIC8vIHJlbmRlcmVkLlxuICAgIGdsLmJpbmRGcmFtZWJ1ZmZlcihnbC5GUkFNRUJVRkZFUiwgc2Vzc2lvbi5iYXNlTGF5ZXIuZnJhbWVidWZmZXIpXG5cbiAgICAvLyBVcGRhdGUgdGhlIGNsZWFyIGNvbG9yIHNvIHRoYXQgd2UgY2FuIG9ic2VydmUgdGhlIGNvbG9yIGluIHRoZVxuICAgIC8vIGhlYWRzZXQgY2hhbmdpbmcgb3ZlciB0aW1lLlxuICAgIGxldCB0aW1lID0gRGF0ZS5ub3coKVxuICAgIGdsLmNsZWFyQ29sb3IoXG4gICAgICBNYXRoLmNvcyh0aW1lIC8gMjAwMCksXG4gICAgICBNYXRoLmNvcyh0aW1lIC8gNDAwMCksXG4gICAgICBNYXRoLmNvcyh0aW1lIC8gNjAwMCksXG4gICAgICAxLjBcbiAgICApXG5cbiAgICAvLyBDbGVhciB0aGUgZnJhbWVidWZmZXJcbiAgICBnbC5jbGVhcihnbC5DT0xPUl9CVUZGRVJfQklUIHwgZ2wuREVQVEhfQlVGRkVSX0JJVClcblxuICAgIC8vIE5vcm1hbGx5IHlvdSdkIGxvb3AgdGhyb3VnaCBlYWNoIG9mIHRoZSB2aWV3cyByZXBvcnRlZCBieSB0aGUgZnJhbWVcbiAgICAvLyBhbmQgZHJhdyB0aGVtIGludG8gdGhlIGNvcnJlc3BvbmRpbmcgdmlld3BvcnQgaGVyZSwgYnV0IHdlJ3JlXG4gICAgLy8ga2VlcGluZyB0aGlzIHNhbXBsZSBzbGltIHNvIHdlJ3JlIG5vdCBib3RoZXJpbmcgdG8gZHJhdyBhbnlcbiAgICAvLyBnZW9tZXRyeS5cbiAgICAvKiBmb3IgKGxldCB2aWV3IG9mIGZyYW1lLnZpZXdzKSB7XG4gICAgICAgICAgICBsZXQgdmlld3BvcnQgPSBzZXNzaW9uLmJhc2VMYXllci5nZXRWaWV3cG9ydCh2aWV3KTtcbiAgICAgICAgICAgIGdsLnZpZXdwb3J0KHZpZXdwb3J0LngsIHZpZXdwb3J0LnksXG4gICAgICAgICAgICAgICAgICAgICAgICB2aWV3cG9ydC53aWR0aCwgdmlld3BvcnQuaGVpZ2h0KTtcbiAgICAgICAgICAgIC8vIERyYXcgc29tZXRoaW5nLlxuICAgICAgICAgIH0gKi9cbiAgfVxufVxuXG4vLyBTdGFydCB0aGUgWFIgYXBwbGljYXRpb24uXG5pbml0WFIoKVxuXG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIGRlbW8uanMiXSwibWFwcGluZ3MiOiI7O0FBQUE7QUFDQTs7Ozs7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFEQTtBQUNBO0FBR0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQU1BO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Ozs7OztBQU1BO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///0\n")},function(module,exports,__webpack_require__){"use strict";eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nvar log = function log() {\n window.logMessages = [];\n\n var print = function print() {\n document.querySelector('.logs').innerHTML = window.logMessages.slice(-10).map(function (d) {\n return '<li>' + d.s + ' ' + (d.e ? d.e : '') + '</li>';\n }).join('');\n };\n\n return {\n message: function message(s) {\n window.logMessages.push({ s: s });\n print();\n },\n error: function error(s, e) {\n window.logMessages.push({ s: s, e: e });\n print();\n }\n };\n};\n\nexports.default = log;//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9sb2cuanM/YjgzZiJdLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCBsb2cgPSAoKSA9PiB7XG4gIHdpbmRvdy5sb2dNZXNzYWdlcyA9IFtdXG5cbiAgY29uc3QgcHJpbnQgPSAoKSA9PiB7XG4gICAgZG9jdW1lbnQucXVlcnlTZWxlY3RvcignLmxvZ3MnKS5pbm5lckhUTUwgPSB3aW5kb3cubG9nTWVzc2FnZXNcbiAgICAgIC5zbGljZSgtMTApXG4gICAgICAubWFwKGQgPT4ge1xuICAgICAgICByZXR1cm4gYDxsaT4ke2Quc30gJHtkLmUgPyBkLmUgOiAnJ308L2xpPmBcbiAgICAgIH0pXG4gICAgICAuam9pbignJylcbiAgfVxuXG4gIHJldHVybiB7XG4gICAgbWVzc2FnZSAocykge1xuICAgICAgd2luZG93LmxvZ01lc3NhZ2VzLnB1c2goeyBzIH0pXG4gICAgICBwcmludCgpXG4gICAgfSxcblxuICAgIGVycm9yIChzLCBlKSB7XG4gICAgICB3aW5kb3cubG9nTWVzc2FnZXMucHVzaCh7IHMsIGUgfSlcbiAgICAgIHByaW50KClcbiAgICB9XG4gIH1cbn1cblxuZXhwb3J0IGRlZmF1bHQgbG9nXG5cblxuXG4vLyBXRUJQQUNLIEZPT1RFUiAvL1xuLy8gbG9nLmpzIl0sIm1hcHBpbmdzIjoiOzs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFHQTtBQUNBO0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQVRBO0FBV0E7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///1\n")},function(module,exports,__webpack_require__){"use strict";eval("\n\n__webpack_require__(0);//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMi5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9zY3JpcHQuanM/OWE5NSJdLCJzb3VyY2VzQ29udGVudCI6WyJyZXF1aXJlKCcuL2RlbW8uanMnKVxuXG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIHNjcmlwdC5qcyJdLCJtYXBwaW5ncyI6Ijs7QUFBQSIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///2\n")}]);
<!DOCTYPE html>
<title>blockup</title>
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<link href='dist.css' rel='stylesheet' />
<!-- Origin Trial Token, feature = WebXR Device API, origin = https://ngrok.io, expires = 2018-07-05 -->
<meta http-equiv="origin-trial" data-feature="WebXR Device API" data-expires="2018-07-05" content="An7UhyulDMd1aXHYJY/4aBUXSPnff/LpJhr8H3FW/EKLJb7GyuwH8DWrg+W6AYIxNX6f7vxJySrMcX6OkfgQuAYAAABNeyJvcmlnaW4iOiJodHRwczovL25ncm9rLmlvOjQ0MyIsImZlYXR1cmUiOiJXZWJYUkRldmljZSIsImV4cGlyeSI6MTUzMDgzNDIzNH0=">
<!-- <script src='https://cdn.jsdelivr.net/npm/webxr-polyfill@latest/build/webxr-polyfill.js'></script>
<script>var polyfill = new WebXRPolyfill()</script>
-->
<body>
<h1>hello</h1>
<ul class='logs' />
<button id="xr-button" disabled>XR not found</button>
<script src='dist.js'></script>
</body>
const log = () => {
window.logMessages = []
const print = () => {
document.querySelector('.logs').innerHTML = window.logMessages
.slice(-10)
.map(d => {
return `<li>${d.s} ${d.e ? d.e : ''}</li>`
})
.join('')
}
return {
message (s) {
window.logMessages.push({ s })
print()
},
error (s, e) {
window.logMessages.push({ s, e })
print()
}
}
}
export default log
import Log from './log.js'
import OnDrawFrame from './onDrawFrame.js'
const log = Log()
// TODO: review async functions
// TODO: check correct error handling setup
let device
let session
let frameOfRef
let onDrawFrame
let glCanvas
let gl
// 1 - Request an XR device.
// 2 - If a device is available, application advertises XR functionality to the user.
// 3 - Request an exclusive XR session from the device in response to a user-activation event.
// 4 - Setup necessary session state and kick off the frame loop.
// 5 - Use the session to run a render loop that produces graphical frames to be displayed on the XR device.
// 6 - Continue producing frames until the user indicates that they wish to exit XR mode.
// 7 - End the XR session.
// 3 - Request an exclusive XR session from the device in response to a user-activation event.
const beginXRSession = () => {
device
.requestSession({ exclusive: true })
.then(xrSession => {
log.message('session started')
session = xrSession
onSessionStarted()
// Use a WebGL context as a base layer.
// xrSession.baseLayer = new XRWebGLLayer(session, gl);
// Start the render loop
})
.catch(err => {
log.error('Could not request desired session', err)
})
}
// 4 - Setup necessary session state and kick off the frame loop.
const onSessionStarted = () => {
// Get a frame of reference, which is required for querying poses. In
// this case an 'eyeLevel' frame of reference means that all poses will
// be relative to the location where the XRDevice was first detected.
session
.requestFrameOfReference('eyeLevel')
.then(xrFrameOfRef => {
log.message('frame of ref acquired')
frameOfRef = xrFrameOfRef
})
.then(setupWebGLLayer) // Create a compatible XRWebGLLayer.
.then(() => {
// Start the render loop.
onDrawFrame = OnDrawFrame({ session, frameOfRef, gl, glCanvas, log })
session.requestAnimationFrame(onDrawFrame)
})
.catch(err => {
log.error('Error onSessionStarted', err)
})
}
const setupWebGLLayer = () => {
glCanvas = document.createElement('canvas')
gl = glCanvas.getContext('webgl')
// Make sure the canvas context we want to use is compatible with the device.
return gl
.setCompatibleXRDevice(device)
.then(() => {
log.message('setting baseLayer')
// The content that will be shown on the device
// is defined by the session's baseLayer.
session.baseLayer = new window.XRWebGLLayer(session, gl)
})
.catch(err => {
log.error('Error setupWebGLLayer', err)
})
}
if (navigator.xr) {
//
// 1 - Request an XR device.
//
navigator.xr
.requestDevice()
.then(xrDevice => {
xrDevice
.supportsSession({ exclusive: true })
.then(() => {
//
// 2 - If a device is available, application advertises XR functionality to the user.
//
device = xrDevice
const button = document.createElement('button')
button.innerHTML = 'Enter VR'
button.addEventListener('click', beginXRSession)
document.body.appendChild(button)
})
.catch(err => {
log.error('Could not support desired session', err)
})
})
.catch(err => {
if (err.name === 'NotFoundError') {
// No XRDevices available.
log.error('No XR devices available:', err)
} else {
// An error occurred while requesting an XRDevice.
log.error('Requesting XR device failed:', err)
}
})
} else {
log.message('This browser does not support the WebXR API.')
}
const onDrawFrame = ({ session, frameOfRef, gl, glCanvas, log }) => (
timestamp,
xrFrame
) => {
// Do we have an active session?
if (session) {
log.message('found active session')
let pose = xrFrame.getDevicePose(frameOfRef)
gl.bindFramebuffer(session.baseLayer.framebuffer)
for (let view of xrFrame.views) {
let viewport = session.baseLayer.getViewport(view)
gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height)
log.message('drawing gl viewport')
drawScene(view, pose)
}
// Request the next animation callback
// session.requestAnimationFrame(onDrawFrame)
} else {
// No session available, so render a default mono view.
gl.viewport(0, 0, glCanvas.width, glCanvas.height)
// drawScene()
// Request the next window callback
window.requestAnimationFrame(onDrawFrame)
}
}
const drawScene = (view, pose) => {
let viewMatrix = null
let projectionMatrix = null
if (view) {
viewMatrix = pose.getViewMatrix(view)
projectionMatrix = view.projectionMatrix
} else {
viewMatrix = defaultViewMatrix
projectionMatrix = defaultProjectionMatrix
}
// Set uniforms as appropriate for shaders being used
// Draw Scene
}
export default onDrawFrame
{
"standard": {
"globals": [
]
}
}
require('./demo.js')
*
box-sizing border-box
let xrDevice = null
async function onXRAvailable (device) {
xrDevice = device
// Most (but not all) XRDevices are capable of granting exclusive access to
// the device, which is necessary to show imagery in a headset. If the device
// has that capability the page will want to add an "Enter VR" button (similar
// to "Enter Fullscreen") that triggers the page to begin showing imagery on
// the headset.
xrDevice
.supportsSession({ exclusive: true })
.then(() => {
var enterXrBtn = document.createElement('button')
enterXrBtn.innerHTML = 'Enter VR'
enterXrBtn.addEventListener('click', beginXRSession)
document.body.appendChild(enterVrBtn)
})
.catch(reason => {
console.log('Session not supported: ' + reason)
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment