| <!doctype html> |
| <!-- |
| Copyright 2016 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| <html> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> |
| <meta name="mobile-web-app-capable" content="yes"> |
| <meta name="apple-mobile-web-app-capable" content="yes"> |
| |
| <!-- Origin Trial Token, feature = WebVR, origin = https://webvr.info, expires = 2017-07-18 --> |
| <meta http-equiv="origin-trial" data-feature="WebVR" data-expires="2017-07-18" content="AmcZQ9UIoWBHR8Q3p/7GHNVux0D2rIfqemrRFSPBaSBb8BViQVPqx3zd8i4mMIf8J70oRhdQqCvafYfBEGAonwkAAABJeyJvcmlnaW4iOiJodHRwczovL3dlYnZyLmluZm86NDQzIiwiZmVhdHVyZSI6IldlYlZSIiwiZXhwaXJ5IjoxNTAwMzM2MDAwfQ=="> |
| <!-- Origin Trial Token, feature = WebVR (For Chrome M59+), origin = https://webvr.info, expires = 2017-07-21 --> |
| <meta http-equiv="origin-trial" data-feature="WebVR (For Chrome M59+)" data-expires="2017-07-21" content="AtJLsI9hT0/XyPU7DEDDxER7jyMU1oNeMk4diF9djsHCkwjulNzizKykf+CKW11B6+0ABoazxbd13jMxBvnUTQIAAABfeyJvcmlnaW4iOiJodHRwczovL3dlYnZyLmluZm86NDQzIiwiZmVhdHVyZSI6IldlYlZSMS4xIiwiZXhwaXJ5IjoxNTAwNjc3MDE3LCJpc1N1YmRvbWFpbiI6dHJ1ZX0="> |
| |
| <title>TEST - VR Links</title> |
| |
| <!-- |
| This test provides a couple of interactive VR elements that link to other |
| pages, both 2D and WebVR-enabled, so that user agents can test their |
| navigation behavior. |
| --> |
| |
| <style> |
| #webgl-canvas, #presenting-message { |
| box-sizing: border-box; |
| height: 100%; |
| left: 0; |
| margin: 0; |
| position: absolute; |
| top: 0; |
| width: 100%; |
| } |
| #presenting-message { |
| color: white; |
| font-family: sans-serif; |
| font-size: 2em; |
| font-weight: bold; |
| z-index: 1; |
| text-align: center; |
| padding: 0.5em; |
| background-color: #444; |
| display: none; |
| } |
| </style> |
| |
| <!-- This entire block in only to facilitate dynamically enabling and |
| disabling the WebVR polyfill, and is not necessary for most WebVR apps. |
| If you want to use the polyfill in your app, just include the js file and |
| everything will work the way you want it to by default. --> |
| <script> |
| var WebVRConfig = { |
| // Prevents the polyfill from initializing automatically. |
| DEFER_INITIALIZATION: true, |
| // Ensures the polyfill is always active when initialized, even if the |
| // native API is available. This is probably NOT what most pages want. |
| ALWAYS_APPEND_POLYFILL_DISPLAY: true, |
| // Polyfill optimizations |
| DIRTY_SUBMIT_FRAME_BINDINGS: true, |
| BUFFER_SCALE: 0.75, |
| }; |
| </script> |
| <script src="js/third-party/webvr-polyfill.js"></script> |
| <script src="js/third-party/wglu/wglu-url.js"></script> |
| <script> |
| // Dynamically turn the polyfill on if requested by the query args. |
| if (WGLUUrl.getBool('polyfill', false)) { |
| InitializeWebVRPolyfill(); |
| } else { |
| // Shim for migration from older version of WebVR. Shouldn't be necessary for very long. |
| InitializeSpecShim(); |
| } |
| </script> |
| <!-- End sample polyfill enabling logic --> |
| |
| <script src="js/third-party/gl-matrix-min.js"></script> |
| |
| <script src="js/third-party/wglu/wglu-debug-geometry.js"></script> |
| <script src="js/third-party/wglu/wglu-program.js"></script> |
| <script src="js/third-party/wglu/wglu-stats.js"></script> |
| <script src="js/third-party/wglu/wglu-texture.js"></script> |
| |
| <script src="js/vr-cube-island.js"></script> |
| <script src="js/vr-samples-util.js"></script> |
| </head> |
| <body> |
| <canvas id="webgl-canvas"></canvas> |
| <div id="presenting-message">Put on your headset now</div> |
| <script> |
| /* global mat4, VRCubeSea, WGLUStats, WGLUTextureLoader, VRSamplesUtil */ |
| (function () { |
| "use strict"; |
| |
| var PLAYER_HEIGHT = 1.65; |
| |
| var vrDisplay = null; |
| var frameData = null; |
| var projectionMat = mat4.create(); |
| var viewMat = mat4.create(); |
| |
| var vrPresentButton = null; |
| |
| // =================================================== |
| // WebGL scene setup. This code is not WebVR specific. |
| // =================================================== |
| |
| // WebGL setup. |
| var webglCanvas = document.getElementById("webgl-canvas"); |
| var glAttribs = { |
| alpha: false, |
| }; |
| var pageId = WGLUUrl.getInt('id', 0); |
| var nextPageId = pageId + 1; |
| var useWebgl2 = WGLUUrl.getBool('webgl2', false); |
| var contextTypes = useWebgl2 ? ["webgl2"] : ["webgl", "experimental-webgl"]; |
| var gl = null; |
| for (var i in contextTypes) { |
| gl = webglCanvas.getContext(contextTypes[i], glAttribs); |
| if (gl) |
| break; |
| } |
| if (!gl) { |
| var webglType = (useWebgl2 ? "WebGL 2" : "WebGL") |
| VRSamplesUtil.addError("Your browser does not support " + webglType + "."); |
| return; |
| } |
| gl.clearColor(0.1, 0.2, 0.3, 1.0); |
| gl.enable(gl.DEPTH_TEST); |
| gl.enable(gl.CULL_FACE); |
| |
| var textureLoader = new WGLUTextureLoader(gl); |
| var texture = textureLoader.loadTexture("media/textures/cube-sea.png"); |
| var cubeIsland = new VRCubeIsland(gl, texture, 2, 2); |
| var debugGeom = new WGLUDebugGeometry(gl); |
| |
| var enablePerformanceMonitoring = WGLUUrl.getBool( |
| 'enablePerformanceMonitoring', false); |
| var stats = new WGLUStats(gl, enablePerformanceMonitoring); |
| |
| var presentingMessage = document.getElementById("presenting-message"); |
| |
| // ================================ |
| // WebVR-specific code begins here. |
| // ================================ |
| |
| function onVRRequestPresent () { |
| // This can only be called in response to a user gesture. |
| vrDisplay.requestPresent([{ source: webglCanvas }]).then(function () { |
| // Nothing to do because we're handling things in onVRPresentChange. |
| }, function (err) { |
| var errMsg = "requestPresent failed."; |
| if (err && err.message) { |
| errMsg += "<br/>" + err.message |
| } |
| VRSamplesUtil.addError(errMsg, 2000); |
| }); |
| } |
| |
| function onVRExitPresent () { |
| if (!vrDisplay.isPresenting) |
| return; |
| |
| vrDisplay.exitPresent().then(function () { |
| // Nothing to do because we're handling things in onVRPresentChange. |
| }, function (err) { |
| var errMsg = "exitPresent failed."; |
| if (err && err.message) { |
| errMsg += "<br/>" + err.message |
| } |
| VRSamplesUtil.addError(errMsg, 2000); |
| }); |
| } |
| |
| function onVRPresentChange () { |
| onResize(); |
| |
| if (vrDisplay.isPresenting) { |
| if (vrDisplay.capabilities.hasExternalDisplay) { |
| presentingMessage.style.display = "block"; |
| |
| VRSamplesUtil.removeButton(vrPresentButton); |
| vrPresentButton = VRSamplesUtil.addButton("Exit VR", "E", "media/icons/cardboard64.png", onVRExitPresent); |
| } |
| } else { |
| if (vrDisplay.capabilities.hasExternalDisplay) { |
| presentingMessage.style.display = ""; |
| |
| VRSamplesUtil.removeButton(vrPresentButton); |
| vrPresentButton = VRSamplesUtil.addButton("Enter VR", "E", "media/icons/cardboard64.png", onVRRequestPresent); |
| } |
| } |
| } |
| |
| if (navigator.getVRDisplays) { |
| frameData = new VRFrameData(); |
| |
| navigator.getVRDisplays().then(function (displays) { |
| if (displays.length > 0) { |
| vrDisplay = displays[displays.length - 1]; |
| |
| vrDisplay.depthNear = 0.1; |
| vrDisplay.depthFar = 1024.0; |
| |
| if (vrDisplay.stageParameters && |
| vrDisplay.stageParameters.sizeX > 0 && |
| vrDisplay.stageParameters.sizeZ > 0) { |
| cubeIsland.resize(vrDisplay.stageParameters.sizeX, vrDisplay.stageParameters.sizeZ); |
| } |
| |
| if (vrDisplay.capabilities.canPresent) |
| vrPresentButton = VRSamplesUtil.addButton("Enter VR", "E", "media/icons/cardboard64.png", onVRRequestPresent); |
| |
| // For the benefit of automated testing. Safe to ignore. |
| if (vrDisplay.capabilities.canPresent && WGLUUrl.getBool('canvasClickPresents', false)) |
| webglCanvas.addEventListener("click", onVRRequestPresent, false); |
| |
| window.addEventListener('vrdisplaypresentchange', onVRPresentChange, false); |
| window.addEventListener('vrdisplayactivate', onVRRequestPresent, false); |
| window.addEventListener('vrdisplaydeactivate', onVRExitPresent, false); |
| } else { |
| VRSamplesUtil.addInfo("WebVR supported, but no VRDisplays found.", 3000); |
| } |
| }); |
| } else if (navigator.getVRDevices) { |
| VRSamplesUtil.addError("Your browser supports WebVR but not the latest version. See <a href='http://webvr.info'>webvr.info</a> for more info."); |
| } else { |
| VRSamplesUtil.addError("Your browser does not support WebVR. See <a href='http://webvr.info'>webvr.info</a> for assistance."); |
| } |
| |
| function onResize () { |
| if (vrDisplay && vrDisplay.isPresenting) { |
| var leftEye = vrDisplay.getEyeParameters("left"); |
| var rightEye = vrDisplay.getEyeParameters("right"); |
| |
| webglCanvas.width = Math.max(leftEye.renderWidth, rightEye.renderWidth) * 2; |
| webglCanvas.height = Math.max(leftEye.renderHeight, rightEye.renderHeight); |
| } else { |
| webglCanvas.width = webglCanvas.offsetWidth * window.devicePixelRatio; |
| webglCanvas.height = webglCanvas.offsetHeight * window.devicePixelRatio; |
| } |
| } |
| window.addEventListener("resize", onResize, false); |
| onResize(); |
| |
| var leftIntersect = false; |
| var rightIntersect = false; |
| |
| var playerPosition = [0, PLAYER_HEIGHT, 0]; |
| var leftPosition = [-0.5, PLAYER_HEIGHT, -1.0]; |
| var rightPosition = [0.5, PLAYER_HEIGHT, -1.0]; |
| |
| function onClick () { |
| if (leftIntersect) { |
| // Navigate back to this page (with a different query string to force |
| // navigation) |
| window.location = "test-vr-links.html?id=" + nextPageId; |
| } else if (rightIntersect) { |
| // Navigate to a 2D site |
| window.location = "index.html" |
| } else { |
| // Reset the background color to a random value |
| gl.clearColor( |
| Math.random() * 0.5, |
| Math.random() * 0.5, |
| Math.random() * 0.5, 1.0); |
| } |
| } |
| webglCanvas.addEventListener("click", onClick, false); |
| |
| function getStandingViewMatrix (out, view) { |
| if (vrDisplay.stageParameters) { |
| // If the headset provides stageParameters use the |
| // sittingToStandingTransform to transform the view matrix into a |
| // space where the floor in the center of the users play space is the |
| // origin. |
| mat4.invert(out, vrDisplay.stageParameters.sittingToStandingTransform); |
| mat4.multiply(out, view, out); |
| } else { |
| // Otherwise you'll want to translate the view to compensate for the |
| // scene floor being at Y=0. Ideally this should match the user's |
| // height (you may want to make it configurable). For this demo we'll |
| // just assume all human beings are 1.65 meters (~5.4ft) tall. |
| mat4.identity(out); |
| mat4.translate(out, out, [0, PLAYER_HEIGHT, 0]); |
| mat4.invert(out, out); |
| mat4.multiply(out, view, out); |
| } |
| } |
| |
| function renderSceneView (projection, view) { |
| cubeIsland.render(projection, view, stats); |
| |
| debugGeom.bind(projection, view); |
| |
| if (leftIntersect) { |
| debugGeom.drawCube(0, leftPosition, 0.35, [0.5, 0, 1, 1]); |
| } else { |
| debugGeom.drawCube(0, leftPosition, 0.3, [0, 0, 1, 1]); |
| } |
| |
| if (rightIntersect) { |
| debugGeom.drawCube(0, rightPosition, 0.35, [0.5, 1, 0, 1]); |
| } else { |
| debugGeom.drawCube(0, rightPosition, 0.3, [0, 1, 0, 1]); |
| } |
| } |
| |
| function lookingAtSphere(position, radius, pose) { |
| var lookVec = vec3.fromValues(0, 0, -1); |
| vec3.transformQuat(lookVec, lookVec, pose.orientation); |
| |
| var m = vec3.create(); |
| vec3.sub(m, playerPosition, position); |
| var b = vec3.dot(m, lookVec); |
| var c = vec3.dot(m, m) - radius * radius; |
| |
| // Exit if player's origin outside of sphere (c > 0) and lookVec is pointing away from sphere (b > 0) |
| if (c > 0.0 && b > 0.0) |
| return false; |
| var discr = b*b - c; |
| |
| // negative discriminant = lookVec misses sphere |
| return (discr >= 0.0); |
| } |
| |
| function onAnimationFrame (t) { |
| stats.begin(); |
| |
| gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); |
| |
| if (vrDisplay) { |
| vrDisplay.requestAnimationFrame(onAnimationFrame); |
| |
| vrDisplay.getFrameData(frameData); |
| |
| leftIntersect = lookingAtSphere(leftPosition, 0.35, frameData.pose); |
| rightIntersect = lookingAtSphere(rightPosition, 0.35, frameData.pose); |
| |
| if (vrDisplay.isPresenting) { |
| gl.viewport(0, 0, webglCanvas.width * 0.5, webglCanvas.height); |
| getStandingViewMatrix(viewMat, frameData.leftViewMatrix); |
| renderSceneView(frameData.leftProjectionMatrix, viewMat); |
| |
| gl.viewport(webglCanvas.width * 0.5, 0, webglCanvas.width * 0.5, webglCanvas.height); |
| getStandingViewMatrix(viewMat, frameData.rightViewMatrix); |
| renderSceneView(frameData.rightProjectionMatrix, viewMat); |
| |
| vrDisplay.submitFrame(); |
| } else { |
| gl.viewport(0, 0, webglCanvas.width, webglCanvas.height); |
| mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0); |
| getStandingViewMatrix(viewMat, frameData.leftViewMatrix); |
| renderSceneView(projectionMat, viewMat); |
| stats.renderOrtho(); |
| } |
| } else { |
| window.requestAnimationFrame(onAnimationFrame); |
| |
| // No VRDisplay found. |
| gl.viewport(0, 0, webglCanvas.width, webglCanvas.height); |
| mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0); |
| mat4.identity(viewMat); |
| mat4.translate(viewMat, viewMat, [0, -PLAYER_HEIGHT, 0]); |
| renderSceneView(projectionMat, viewMat); |
| |
| stats.renderOrtho(); |
| } |
| |
| stats.end(); |
| } |
| window.requestAnimationFrame(onAnimationFrame); |
| })(); |
| </script> |
| </body> |
| </html> |