Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
430 lines (351 sloc) 13.7 KB
<html>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<head>
<title>Geospatial AR example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<script src="../libs/three/three.js"></script>
<script module src="../libs/webxr.js"></script>
<script src="../libs/Cesium.js"></script>
<script src="../../dist/webxr-geospatial.js"></script>
<link rel="stylesheet" href="../common.css"/>
<script src="../libs/three-web-layer.umd.js"></script>
</head>
<body>
<div id="description">
<h2>Geospatial AR Example</h2>
<h5>(click to dismiss)</h5>
<p>This example displays some cubes to the North, South, East and West from the user's geospatial location. Tapping a cube tells the program to pretend the device is there. Tapping anywhere else resets to using the real geolocation.</p>
</div>
<button type=button id=go-button>Go</button>
<script type=module>
// some dependencies and utilities
import * as mat4 from '../libs/gl-matrix/mat4.js'
import * as vec3 from '../libs/gl-matrix/vec3.js'
import XREngine from "../XREngine.js"
import XRInputManager from "../XRInputManager.js"
let device = null
let session = null
let eyeLevelFrameOfReference = null
let engine = null
// temporary working variables
const workingMatrix = mat4.create()
const workingVec3 = vec3.create()
let textBox = document.createElement('span')
textBox.setAttribute('id', 'text-box')
textBox.setAttribute('class', 'text-box')
//textBox.style.visibility = 'hidden'
textBox.myText = '<p>Waiting to start ...</p>'
textBox.innerHTML = textBox.myText
document.body.appendChild(textBox)
var raycaster = new THREE.Raycaster();
var mouse = null
var boxes = []
////////////
var myGeoNode = null;
var myGeoAnchor = null
var myGeoLoc = null
// for moving DIVs to 3D
function beforeRasterize(webLayer) {
webLayer.element.style.visibility = "visible"
}
function afterRasterize(webLayer) {
webLayer.element.style.visibility = "hidden"
}
let layerOptions = {
onBeforeRasterize: beforeRasterize,
onAfterRasterize: afterRasterize
}
var descLayer = new WebLayer3D(document.getElementById('description'), layerOptions)
var textLayer = new WebLayer3D(textBox, layerOptions)
var clock = new THREE.Clock()
var hudNode = new THREE.Group()
hudNode.add(descLayer)
hudNode.add(textLayer)
hudNode.visible = false
const HUD_Z = 0.2 // 2cm in front of display
updateLoop()
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
XRGeospatialAnchor.useEstimatedElevation(true, 1.3)
async function updateLoop () {
let deviceCarto = await XRGeospatialAnchor.getDeviceCartographic()
let txt = textBox.myText + "<p>device altitude " + deviceCarto.height.toPrecision(6) + "</p>"
let defElev = await XRGeospatialAnchor.getDefaultElevation(deviceCarto)
txt = txt + "<p>estimated altitude " + defElev.toPrecision(6) + "</p>"
textBox.innerHTML = txt
await XRGeospatialAnchor.useEstimatedElevation(true, 1.3)
await sleep(1000)
setTimeout(() => updateLoop(), 1)
}
function updateScene() {
const deltaTime = clock.getDelta()
//const elapsedTime = clock.getElapsedTime()
descLayer.shouldUseTargetLayout = 'always'
textLayer.shouldUseTargetLayout = 'always'
descLayer.update(1) //deltaTime*10)
textLayer.update(1) //deltaTime*10)
if (mouse) {
// update the picking ray with the camera and mouse position
raycaster.setFromCamera( mouse, engine.camera );
mouse = null
if (descLayer.visible && descLayer.hitTest(raycaster.ray)) {
descLayer.visible = false
} else {
// calculate objects intersecting the picking ray
var intersects = raycaster.intersectObjects( boxes );
let intersection = intersects.length ? intersects[0] : null
if (intersection) {
console.log("hit box: " + intersection.object.geoName)
textBox.myText = 'At ' + intersection.object.geoName + " box"
moveGeoOrigin(intersection)
} else {
console.log("miss, going back to device geolocation")
textBox.myText = 'At device location'
clearGeoOrigin()
}
}
}
}
function updateForView() {
let natural = WebLayer3D.computeNaturalDistance(engine.camera, engine.renderer)
let scale = (HUD_Z / natural)
//hudNode.scale.set(scale*window.devicePixelRatio, scale*window.devicePixelRatio, 1)
hudNode.scale.set(scale, scale, 1)
}
async function moveGeoOrigin(intersection) {
if (intersection && intersection.object.parent && intersection.object.parent.anchor) {
let cartesian = intersection.object.parent.anchor.cartesian
let headFrameOfReference = await session.requestFrameOfReference('head-model')
eyeLevelFrameOfReference = await session.requestFrameOfReference('eye-level')
headFrameOfReference.getTransformTo(eyeLevelFrameOfReference, workingMatrix)
let anchor = await session.addAnchor(workingMatrix, eyeLevelFrameOfReference)
if (myGeoAnchor) {
await session.removeAnchor(myGeoAnchor)
}
if (myGeoNode) {
myGeoNode.parent.visible = true
}
myGeoNode = intersection.object
myGeoNode.parent.visible = false
myGeoAnchor = anchor
XRGeospatialAnchor.overrideGeoLocation(cartesian, anchor)
return true
}
}
async function clearGeoOrigin() {
if (myGeoAnchor) {
await session.removeAnchor(myGeoAnchor)
}
if (myGeoNode) {
myGeoNode.parent.visible = true
}
myGeoAnchor = null
myGeoNode = null
XRGeospatialAnchor.useDeviceGeolocation()
}
// samples received from the location API at this location, at one point
let testCartographic = Cesium.Cartographic.fromDegrees(-84.34824477127631, 33.78545836417129, 275.12518310546875)
let testAltitude = 275.12518310546875
// some other pints you could play with.
//
// let streetCorner = Cesium.Cartographic.fromDegrees(-84.348134, 33.785437, 0)
// let upHillEast = Cesium.Cartographic.fromDegrees(-84.347461, 33.785337, 0)
// let yardNortWest = Cesium.Cartographic.fromDegrees(-84.34824477127631, 33.78545836417129, 0)
// let intersectionSouth = Cesium.Cartographic.fromDegrees(-84.348160, 33.783789, 0)
const DIST = 0.001 * THREE.Math.DEG2RAD
function initializeScene() {
engine.camera.add(hudNode)
hudNode.position.set(0,0,-HUD_Z)
// go to where you should go ...
descLayer.update(1)
textLayer.update(1)
engine.addAmbientLight(0xffffff, 0.8)
// Add a box at the origin of our starting coordinate system
// engine.addBox([0, -0.25, 0], [0.025, 0.025, 0.025], 0x44ff44)
// engine.addAxesHelper([0,-0.25,0], [0.2,0.2,0.2])
// some tests
XRGeospatialAnchor.getDeviceCartographic().then(cartographic => {
myGeoLoc = cartographic
console.log("device cartographic: ", myGeoLoc)
let north = Cesium.Cartographic.clone(cartographic)
north.latitude += DIST
XRGeospatialAnchor.createGeoAnchor(north).then(anchor => {
engine.addAnchoredNode(anchor, createSceneGraphNode("north"))
})
let south = Cesium.Cartographic.clone(cartographic)
south.latitude -= DIST
XRGeospatialAnchor.createGeoAnchor(south).then(anchor => {
engine.addAnchoredNode(anchor, createSceneGraphNode("south"))
})
let east = Cesium.Cartographic.clone(cartographic)
east.longitude += DIST
XRGeospatialAnchor.createGeoAnchor(east).then(anchor => {
engine.addAnchoredNode(anchor, createSceneGraphNode("east"))
})
let west = Cesium.Cartographic.clone(cartographic)
west.longitude -= DIST
XRGeospatialAnchor.createGeoAnchor(west).then(anchor => {
engine.addAnchoredNode(anchor, createSceneGraphNode("west"))
})
})
XRGeospatialAnchor.getDeviceElevation().then(altitude => {
console.log("device elevation: ", altitude)
})
XRGeospatialAnchor.getDefaultElevation(testCartographic).then(altitude => {
console.log("recorded work elevation in past: ", testAltitude)
console.log("estimated world elevation: ", altitude)
})
}
// Creates a box used to indicate the location of an anchor offset
function createSceneGraphNode(direction){
let group = new THREE.Group()
let texture = new THREE.TextureLoader().load( '../textures/crate-' + direction + '.gif' );
texture.mapping = THREE.UVMapping;
texture.anisotropy = engine.renderer.capabilities.getMaxAnisotropy();
let material = new THREE.MeshLambertMaterial( { map: texture } );
let geometry = new THREE.BoxBufferGeometry(20, 20, 20)
let mesh = new THREE.Mesh(geometry, material)
mesh.geoName = direction + " box"
boxes.push(mesh)
var outlineMaterial = new THREE.MeshBasicMaterial( { color: 0x00ff00, side: THREE.BackSide } );
var outlineMesh = new THREE.Mesh( geometry, outlineMaterial );
mesh.position.set(0, 0.05, 0)
outlineMesh.position.set(0, 0.05, 0);
outlineMesh.scale.multiplyScalar(1.05);
group.add(mesh)
group.add(outlineMesh)
return group
}
////////////////////////////////////////////////////
////////////////////////////////////////////////////
// BOILER PLATE. Can you feel the plates boiling?
//
// Create the output context where the XRSession will place composited renders
const xrCanvas = document.createElement('canvas')
xrCanvas.setAttribute('class', 'xr-canvas')
const xrContext = xrCanvas.getContext('xrpresent')
if(!xrContext){
console.error('No XR context', xrCanvas)
}
// get the XR Device
navigator.xr.requestDevice().then(xrDevice => {
device = xrDevice
}).catch(err => {
console.error('Error', err)
})
document.getElementById('description').addEventListener('touchstart', hideMe, {capture: true})
function hideMe(event) {
event.target.style.display = 'none'
event.stopPropagation()
}
document.getElementById('go-button').addEventListener('click', handleStartSessionRequest, true)
document.getElementById('go-button').addEventListener('touchstart', handleGoButtonTouch, true)
function handleGoButtonTouch(event) {
event.stopPropagation()
}
// handle input events from the XRInputManager
const inputManager = new XRInputManager(handleXRInput)
function handleXRInput(eventName, details){
switch(eventName){
case 'normalized-touch':
mouse = new THREE.Vector2(...details.normalizedCoordinates)
break
default:
console.error('unknown xr input event', eventName, details)
break
}
}
/////////////////////
// Session startup / shutdown
function handleStartSessionRequest(ev){
if(device === null){
console.error('No xr device')
return
}
if (!session) {
device.requestSession({
outputContext: xrContext,
alignEUS: true,
geolocation: true
}).then(handleSessionStarted).catch(err => {
console.error('Session setup error', err)
})
//document.getElementById('description').style.visibility = 'hidden'
hudNode.visible = true
textBox.myText = 'At device location'
document.getElementById('go-button').innerText = "End"
document.getElementById('go-button').style.display = 'none'
} else {
session.end()
handleSessionEnded();
document.getElementById('description').style.visibility = 'visible'
document.getElementById('description').style.display = 'block'
document.getElementById('go-button').innerText = "Go"
hudNode.visible = false
descLayer.visible = true
}
}
function handleSessionEnded() {
session = null
}
function handleSessionStarted(xrSession){
session = xrSession
document.body.insertBefore(xrCanvas, document.body.firstChild)
// Create the context where we will render our 3D scene
const canvas = document.createElement('canvas')
var glContext = canvas.getContext('webgl', {
compatibleXRDevice: device
})
if(!glContext) throw new Error('Could not create a webgl context')
// Set up the base layer
session.baseLayer = new XRWebGLLayer(session, glContext)
// Create a simple test scene and renderer
// The engine's scene is in the eye-level coordinate system
engine = new XREngine(canvas, glContext, true)
createRootNode().then(() => {
// Kick off rendering
session.requestAnimationFrame(handleAnimationFrame)
})
initializeScene()
}
async function createRootNode() {
let headFrameOfReference = await session.requestFrameOfReference('head-model')
eyeLevelFrameOfReference = await session.requestFrameOfReference('eye-level')
// get the location of the device, and use it to create an
// anchor with the identity orientation
headFrameOfReference.getTransformTo(eyeLevelFrameOfReference, workingMatrix)
mat4.getTranslation(workingVec3, workingMatrix)
mat4.fromTranslation(workingMatrix, workingVec3)
let anchor = await session.addAnchor(workingMatrix, eyeLevelFrameOfReference)
engine.addAnchoredNode(anchor, engine.root)
return true
}
// render loop
function handleAnimationFrame(t, frame){
if(!session || session.ended) return
session.requestAnimationFrame(handleAnimationFrame)
updateScene()
let pose = frame.getDevicePose(eyeLevelFrameOfReference)
if(!pose){
console.log('No pose')
return
}
engine.startFrame()
for (let view of frame.views) {
engine.preRender(
session.baseLayer.getViewport(view),
view.projectionMatrix,
pose.getViewMatrix(view)
)
updateForView()
engine.render()
}
engine.endFrame()
}
</script>
</body>
</html>
You can’t perform that action at this time.