Add floor anchor API and update the examples to use it.
This commit is contained in:
Родитель
34e88a32ef
Коммит
0fd8e81c92
|
@ -46,7 +46,6 @@
|
|||
constructor(domElement){
|
||||
super(domElement, false)
|
||||
this.anchorsToAdd = [] // { node, x, y, z }
|
||||
this.anchoredNodes = [] // { anchorUID, node }
|
||||
|
||||
this.addObjectButton = document.createElement('button')
|
||||
this.addObjectButton.setAttribute('class', 'add-object-button')
|
||||
|
@ -87,7 +86,7 @@
|
|||
const headCoordinateSystem = frame.getCoordinateSystem(XRCoordinateSystem.HEAD_MODEL)
|
||||
const trackerCoordinateSystem = frame.getCoordinateSystem(XRCoordinateSystem.TRACKER)
|
||||
|
||||
// Create anchors for newly anchored nodes
|
||||
// Create anchors and start tracking them
|
||||
for(let anchorToAdd of this.anchorsToAdd){
|
||||
// Create an anchor that we'd like to add, relative to the current head position
|
||||
const anchorCoordinates = new XRCoordinates(
|
||||
|
@ -97,27 +96,11 @@
|
|||
)
|
||||
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)
|
||||
this.anchoredNodes.push({
|
||||
anchorUID: anchorUID,
|
||||
node: anchorToAdd.node
|
||||
})
|
||||
this.scene.add(anchorToAdd.node)
|
||||
this.addAnchoredNode(new XRAnchorOffset(anchorUID), anchorToAdd.node)
|
||||
}
|
||||
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)
|
||||
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(
|
||||
new THREE.BoxBufferGeometry(0.1, 0.1, 0.1),
|
||||
new THREE.MeshPhongMaterial({ color: '#DDFFDD' })
|
||||
)
|
||||
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))
|
||||
const directionalLight = new THREE.DirectionalLight('#FFF', 0.6)
|
||||
directionalLight.position.set(0, 10, 0)
|
||||
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
|
||||
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:
|
||||
domElement: an element used to show error messages
|
||||
|
@ -17,6 +19,8 @@ class XRExampleBase {
|
|||
this.createVirtualReality = createVirtualReality
|
||||
this.shouldStartPresenting = shouldStartPresenting
|
||||
|
||||
this._boundHandleFrame = this._handleFrame.bind(this) // Useful for setting up the requestAnimationFrame callback
|
||||
|
||||
// Set during the XR.getDisplays call below
|
||||
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.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
|
||||
this.initializeScene()
|
||||
|
||||
|
@ -141,7 +151,7 @@ class XRExampleBase {
|
|||
this.renderer.autoClear = false
|
||||
this.renderer.setClearColor('#000', 0)
|
||||
|
||||
this.session.requestFrame(frame => { this.handleFrame(frame) })
|
||||
this.session.requestFrame(this._boundHandleFrame)
|
||||
}
|
||||
|
||||
// Extending classes can react to these events
|
||||
|
@ -161,9 +171,28 @@ class XRExampleBase {
|
|||
*/
|
||||
updateScene(frame){}
|
||||
|
||||
handleFrame(frame){
|
||||
const nextFrameRequest = this.session.requestFrame(frame => { this.handleFrame(frame) })
|
||||
let headPose = frame.getViewPose(frame.getCoordinateSystem(XRCoordinateSystem.HEAD_MODEL))
|
||||
_handleFrame(frame){
|
||||
const nextFrameRequest = this.session.requestFrame(this._boundHandleFrame)
|
||||
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
|
||||
this.updateScene(frame)
|
||||
|
@ -190,6 +219,39 @@ class XRExampleBase {
|
|||
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]){
|
||||
|
|
|
@ -49,8 +49,6 @@
|
|||
super(domElement, false)
|
||||
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
|
||||
this._messageEl = document.createElement('div')
|
||||
this.el.appendChild(this._messageEl)
|
||||
|
@ -71,15 +69,7 @@
|
|||
new THREE.MeshPhongMaterial({ color: '#DDFFDD' })
|
||||
)
|
||||
box.position.set(0, 0, 0)
|
||||
this.scene.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)
|
||||
this.floorGroup.add(box)
|
||||
|
||||
// Add a few lights
|
||||
this.scene.add(new THREE.AmbientLight('#FFF', 0.2))
|
||||
|
@ -100,46 +90,13 @@
|
|||
if(anchorOffset === null){
|
||||
this._messageEl.innerHTML = 'miss'
|
||||
} else {
|
||||
const anchor = frame.getAnchor(anchorOffset.anchorUID)
|
||||
if(anchor === null){
|
||||
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)
|
||||
this._messageEl.innerHTML = 'hit'
|
||||
this.addAnchoredNode(anchorOffset, this._createSceneGraphNode())
|
||||
}
|
||||
}).catch(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
|
||||
|
|
|
@ -60,6 +60,25 @@ export default class Reality extends EventHandlerBase {
|
|||
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){
|
||||
return this._anchors.get(uid) || null
|
||||
}
|
||||
|
|
|
@ -61,6 +61,15 @@ export default class XRPresentationFrame {
|
|||
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){
|
||||
// void removeAnchor(DOMString uid);
|
||||
return this._session.reality._removeAnchor(uid)
|
||||
|
|
Загрузка…
Ссылка в новой задаче