Add floor anchor API and update the examples to use it.
This commit is contained in:
Родитель
34e88a32ef
Коммит
0fd8e81c92
|
@ -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)
|
||||||
|
|
Загрузка…
Ссылка в новой задаче