Add floor anchor API and update the examples to use it.

This commit is contained in:
Trevor F. Smith 2017-10-04 17:49:32 -07:00
Родитель 34e88a32ef
Коммит 0fd8e81c92
6 изменённых файлов: 105 добавлений и 80 удалений

Просмотреть файл

@ -46,7 +46,6 @@
constructor(domElement){ constructor(domElement){
super(domElement, false) super(domElement, false)
this.anchorsToAdd = [] // { node, x, y, z } this.anchorsToAdd = [] // { node, x, y, z }
this.anchoredNodes = [] // { anchorUID, node }
this.addObjectButton = document.createElement('button') this.addObjectButton = document.createElement('button')
this.addObjectButton.setAttribute('class', 'add-object-button') this.addObjectButton.setAttribute('class', 'add-object-button')
@ -87,7 +86,7 @@
const headCoordinateSystem = frame.getCoordinateSystem(XRCoordinateSystem.HEAD_MODEL) const headCoordinateSystem = frame.getCoordinateSystem(XRCoordinateSystem.HEAD_MODEL)
const trackerCoordinateSystem = frame.getCoordinateSystem(XRCoordinateSystem.TRACKER) const trackerCoordinateSystem = frame.getCoordinateSystem(XRCoordinateSystem.TRACKER)
// Create anchors for newly anchored nodes // Create anchors and start tracking them
for(let anchorToAdd of this.anchorsToAdd){ for(let anchorToAdd of this.anchorsToAdd){
// Create an anchor that we'd like to add, relative to the current head position // Create an anchor that we'd like to add, relative to the current head position
const anchorCoordinates = new XRCoordinates( const anchorCoordinates = new XRCoordinates(
@ -97,27 +96,11 @@
) )
const anchor = new XRAnchor(anchorCoordinates) const anchor = new XRAnchor(anchorCoordinates)
// Add and store the anchor UID along with the node that will be updated with changing anchor data // Create the anchor and tell the base class to update the node with its position
const anchorUID = frame.addAnchor(anchor) const anchorUID = frame.addAnchor(anchor)
this.anchoredNodes.push({ this.addAnchoredNode(new XRAnchorOffset(anchorUID), anchorToAdd.node)
anchorUID: anchorUID,
node: anchorToAdd.node
})
this.scene.add(anchorToAdd.node)
} }
this.anchorsToAdd = [] this.anchorsToAdd = []
// Update anchored node positions in the scene graph
for(let anchoredNode of this.anchoredNodes){
const anchor = frame.getAnchor(anchoredNode.anchorUID)
if(anchor === null){
console.error('Unknown anchor ID', anchoredNode.anchorId)
} else {
anchoredNode.node.matrixAutoUpdate = false
anchoredNode.node.matrix.fromArray(anchor.coordinates.getTransformedCoordinates(trackerCoordinateSystem).poseMatrix)
anchoredNode.node.updateMatrixWorld(true)
}
}
} }
} }

Просмотреть файл

@ -55,27 +55,22 @@
}) })
const mesh = new THREE.Mesh(geometry, material) const mesh = new THREE.Mesh(geometry, material)
mesh.position.set(0, 1.4, -1) mesh.position.set(0, 1.4, -1)
this.scene.add(mesh) this.floorGroup.add(mesh)
// Add a box at the scene origin // Add a box on the floor
const box = new THREE.Mesh( const box = new THREE.Mesh(
new THREE.BoxBufferGeometry(0.1, 0.1, 0.1), new THREE.BoxBufferGeometry(0.1, 0.1, 0.1),
new THREE.MeshPhongMaterial({ color: '#DDFFDD' }) new THREE.MeshPhongMaterial({ color: '#DDFFDD' })
) )
box.position.set(0, 0, 0) box.position.set(0, 0, 0)
this.scene.add(box) this.floorGroup.add(box)
// Add some lights to the scene
this.scene.add(new THREE.AmbientLight('#FFF', 0.2)) this.scene.add(new THREE.AmbientLight('#FFF', 0.2))
const directionalLight = new THREE.DirectionalLight('#FFF', 0.6) const directionalLight = new THREE.DirectionalLight('#FFF', 0.6)
directionalLight.position.set(0, 10, 0) directionalLight.position.set(0, 10, 0)
this.scene.add(directionalLight) this.scene.add(directionalLight)
} }
// Called once per frame, before render to give the app a chance to update the this.scene
updateScene(frame){
// Uncomment the next line to spin the teapot
//this.scene.children[0].rotation.y += 0.01
}
} }

Просмотреть файл

@ -1,6 +1,8 @@
/* /*
XRExampleBase holds all of the common XR setup, rendering, and teardown code for a THREE.js based app XRExampleBase holds all of the common XR setup, rendering, and teardown code for a THREE.js based app
Extending classes should be able to focus on rendering their scene It also holds a list of THREE nodes and XRAnchorOffsets which it uses to update the nodes' poses
Extending classes should be able to focus mainly on rendering their scene and handling user input
Parameters: Parameters:
domElement: an element used to show error messages domElement: an element used to show error messages
@ -17,6 +19,8 @@ class XRExampleBase {
this.createVirtualReality = createVirtualReality this.createVirtualReality = createVirtualReality
this.shouldStartPresenting = shouldStartPresenting this.shouldStartPresenting = shouldStartPresenting
this._boundHandleFrame = this._handleFrame.bind(this) // Useful for setting up the requestAnimationFrame callback
// Set during the XR.getDisplays call below // Set during the XR.getDisplays call below
this.displays = null this.displays = null
@ -30,6 +34,12 @@ class XRExampleBase {
this.camera = new THREE.PerspectiveCamera(70, 1024, 1024, 0.1, 1000) // These values will be overwritten by the projection matrix from ARKit or ARCore this.camera = new THREE.PerspectiveCamera(70, 1024, 1024, 0.1, 1000) // These values will be overwritten by the projection matrix from ARKit or ARCore
this.renderer = null // Set in this.handleNewSession this.renderer = null // Set in this.handleNewSession
this.requestedFloor = false
this.floorGroup = new THREE.Group() // This group will eventually be be anchored to the floor (see findFloorAnchor below)
// an array of info that we'll use in _handleFrame to update the nodes using anchors
this.anchoredNodes = [] // { XRAnchorOffset, Three.js Object3D }
// Give extending classes the opportunity to initially populate the scene // Give extending classes the opportunity to initially populate the scene
this.initializeScene() this.initializeScene()
@ -141,7 +151,7 @@ class XRExampleBase {
this.renderer.autoClear = false this.renderer.autoClear = false
this.renderer.setClearColor('#000', 0) this.renderer.setClearColor('#000', 0)
this.session.requestFrame(frame => { this.handleFrame(frame) }) this.session.requestFrame(this._boundHandleFrame)
} }
// Extending classes can react to these events // Extending classes can react to these events
@ -161,9 +171,28 @@ class XRExampleBase {
*/ */
updateScene(frame){} updateScene(frame){}
handleFrame(frame){ _handleFrame(frame){
const nextFrameRequest = this.session.requestFrame(frame => { this.handleFrame(frame) }) const nextFrameRequest = this.session.requestFrame(this._boundHandleFrame)
let headPose = frame.getViewPose(frame.getCoordinateSystem(XRCoordinateSystem.HEAD_MODEL)) const headPose = frame.getViewPose(frame.getCoordinateSystem(XRCoordinateSystem.HEAD_MODEL))
// If we haven't already, request the floor anchor offset
if(this.requestedFloor === false){
this.requestedFloor = true
frame.findFloorAnchor('first-floor-anchor').then(anchorOffset => {
if(anchorOffset === null){
console.error('could not find the floor anchor')
return
}
this.addAnchoredNode(anchorOffset, this.floorGroup)
}).catch(err => {
console.error('error finding the floor anchor', err)
})
}
// Update anchored node positions in the scene graph
for(let anchoredNode of this.anchoredNodes){
this.updateNodeFromAnchorOffset(frame, anchoredNode.node, anchoredNode.anchorOffset)
}
// Let the extending class update the scene before each render // Let the extending class update the scene before each render
this.updateScene(frame) this.updateScene(frame)
@ -190,6 +219,39 @@ class XRExampleBase {
this.renderer.render(this.scene, this.camera) this.renderer.render(this.scene, this.camera)
} }
} }
/*
Add a node to the scene and keep its pose updated using the anchorOffset
*/
addAnchoredNode(anchorOffset, node){
this.anchoredNodes.push({
anchorOffset: anchorOffset,
node: node
})
this.scene.add(node)
}
/*
Get the anchor data from the frame and use it and the anchor offset to update the pose of the node, this must be an Object3D
*/
updateNodeFromAnchorOffset(frame, node, anchorOffset){
const anchor = frame.getAnchor(anchorOffset.anchorUID)
if(anchor === null){
throttledConsoleLog('Unknown anchor uid', anchorOffset.anchorUID)
return
}
node.matrixAutoUpdate = false
const offsetCoordinates = anchorOffset.getTransformedCoordinates(anchor)
if(offsetCoordinates.coordinateSystem.type === XRCoordinateSystem.TRACKER){
node.matrix.fromArray(offsetCoordinates.poseMatrix)
} else {
node.matrix.fromArray(
offsetCoordinates.getTransformedCoordinates(frame.getCoordinateSystem(XRCoordinateSystem.TRACKER)).poseMatrix
)
}
node.updateMatrixWorld(true)
}
} }
function fillInGLTFScene(path, scene, position=[0, 0, -2], scale=[1, 1, 1]){ function fillInGLTFScene(path, scene, position=[0, 0, -2], scale=[1, 1, 1]){

Просмотреть файл

@ -49,8 +49,6 @@
super(domElement, false) super(domElement, false)
this._tapEventData = null // Will be filled in on touch start and used in updateScene this._tapEventData = null // Will be filled in on touch start and used in updateScene
this.anchoredNodes = [] // { XRAnchorOffset, Three.js Object3D }
// A message at the bottom of the screen that shows whether a surface has been found // A message at the bottom of the screen that shows whether a surface has been found
this._messageEl = document.createElement('div') this._messageEl = document.createElement('div')
this.el.appendChild(this._messageEl) this.el.appendChild(this._messageEl)
@ -71,15 +69,7 @@
new THREE.MeshPhongMaterial({ color: '#DDFFDD' }) new THREE.MeshPhongMaterial({ color: '#DDFFDD' })
) )
box.position.set(0, 0, 0) box.position.set(0, 0, 0)
this.scene.add(box) this.floorGroup.add(box)
// Add a box one meter in front of scene origin to show the direction of the Z axis
box = new THREE.Mesh(
new THREE.BoxBufferGeometry(0.1, 0.1, 0.1),
new THREE.MeshPhongMaterial({ color: '#FF0000' })
)
box.position.set(0, 0, -1)
this.scene.add(box)
// Add a few lights // Add a few lights
this.scene.add(new THREE.AmbientLight('#FFF', 0.2)) this.scene.add(new THREE.AmbientLight('#FFF', 0.2))
@ -100,46 +90,13 @@
if(anchorOffset === null){ if(anchorOffset === null){
this._messageEl.innerHTML = 'miss' this._messageEl.innerHTML = 'miss'
} else { } else {
const anchor = frame.getAnchor(anchorOffset.anchorUID) this._messageEl.innerHTML = 'hit'
if(anchor === null){ this.addAnchoredNode(anchorOffset, this._createSceneGraphNode())
console.error('unknown anchor uid', anchorOffset.anchorUID)
return
}
this._messageEl.innerHTML = 'hit: ' + anchor.coordinates.position.join(', ')
// Save the XRAnchorOffset and the node so that we can update the node's position based off of the anchor
let anchorInfo = {
anchorOffset: anchorOffset,
node: this._createSceneGraphNode()
}
this.anchoredNodes.push(anchorInfo)
// Add a block to the scene to indicate the position of the XRAnchorOffset
// Its position will be updated below along with the other anchored nodes
this.scene.add(anchorInfo.node)
} }
}).catch(err => { }).catch(err => {
console.error('Error in hit test', err) console.error('Error in hit test', err)
}) })
} }
const trackerCoordinateSystem = frame.getCoordinateSystem(XRCoordinateSystem.TRACKER)
// Update anchored node positions in the scene graph using updated anchor positions
for(let anchoredNode of this.anchoredNodes){
const anchor = frame.getAnchor(anchoredNode.anchorOffset.anchorUID)
if(anchor === null){
throttledConsoleLog('Unknown anchor uid', anchoredNode.anchorOffset.anchorUID)
} else {
anchoredNode.node.matrixAutoUpdate = false
let offsetCoordinates = anchoredNode.anchorOffset.getTransformedCoordinates(anchor)
if(offsetCoordinates.coordinateSystem.type === XRCoordinateSystem.TRACKER){
anchoredNode.node.matrix.fromArray(offsetCoordinates.poseMatrix)
} else {
anchoredNode.node.matrix.fromArray(offsetCoordinates.getTransformedCoordinates(trackerCoordinateSystem).poseMatrix)
}
anchoredNode.node.updateMatrixWorld(true)
}
}
} }
// Save screen taps as normalized coordinates for use in this.updateScene // Save screen taps as normalized coordinates for use in this.updateScene

Просмотреть файл

@ -60,6 +60,25 @@ export default class Reality extends EventHandlerBase {
throw new Error('Exending classes should implement _findAnchor') throw new Error('Exending classes should implement _findAnchor')
} }
/*
Find an XRAnchorOffset that is at floor level below the current head pose
returns a Promise that resolves either to an AnchorOffset or null if the floor level is unknown
*/
_findFloorAnchor(display, uid=null){
// Copy the head model matrix for the current pose so we have it in the promise below
const headModelMatrix = new Float32Array(display._headPose.poseModelMatrix)
return new Promise((resolve, reject) => {
// For now, just create an anchor at origin level. Maybe in the future search for a surface?
const coordinates = new XRCoordinates(display, display._trackerCoordinateSystem)
headModelMatrix[13] = 0 // Set height to 0
coordinates.poseMatrix = headModelMatrix
const anchor = new XRAnchor(coordinates, uid)
this._addAnchor(anchor, display)
resolve(new XRAnchorOffset(anchor.uid))
})
}
_getAnchor(uid){ _getAnchor(uid){
return this._anchors.get(uid) || null return this._anchors.get(uid) || null
} }

Просмотреть файл

@ -61,6 +61,15 @@ export default class XRPresentationFrame {
return this._session.reality._findAnchor(normalizedScreenX, normalizedScreenY, this._session.display) return this._session.reality._findAnchor(normalizedScreenX, normalizedScreenY, this._session.display)
} }
/*
Find an XRAnchorOffset that is at floor level below the current head pose
uid will be the resulting anchor uid (if any), or if null one will be assigned
*/
findFloorAnchor(uid=null){
// Promise<XRAnchorOffset?> findFloorAnchor();
return this._session.reality._findFloorAnchor(this._session.display, uid)
}
removeAnchor(uid){ removeAnchor(uid){
// void removeAnchor(DOMString uid); // void removeAnchor(DOMString uid);
return this._session.reality._removeAnchor(uid) return this._session.reality._removeAnchor(uid)