webxr-polyfill/polyfill/XRSession.js

355 строки
9.9 KiB
JavaScript

import EventHandlerBase from './fill/EventHandlerBase.js'
import MatrixMath from './fill/MatrixMath.js'
import XRDisplay from './XRDisplay.js'
import XRFaceAnchor from './XRFaceAnchor.js'
import XRImageAnchor from './XRImageAnchor.js'
import XRAnchor from './XRAnchor.js'
import ARKitWrapper from './platform/ARKitWrapper.js'
import XRPlaneAnchor from './XRPlaneAnchor.js'
/*
A script that wishes to make use of an XRDisplay can request an XRSession.
An XRSession provides a list of the available Reality instances that the script may request as well as make a request for an animation frame.
*/
export default class XRSession extends EventHandlerBase {
constructor(xr, display, createParameters){
super(xr)
this._xr = xr
this._display = display
this._createParameters = createParameters
this._ended = false
this._baseLayer = null
this._stageBounds = null
this._skip = false;
this._frameAnchors = []
this._tempMatrix = MatrixMath.mat4_generateIdentity()
this._tempMatrix2 = MatrixMath.mat4_generateIdentity()
this._display.addEventListener(XRDisplay.TRACKING_CHANGED, this._handleTrackingChanged.bind(this))
this._display.addEventListener(XRDisplay.NEW_WORLD_ANCHOR, this._handleNewWorldAnchor.bind(this))
this._display.addEventListener(XRDisplay.REMOVE_WORLD_ANCHOR, this._handleRemoveWorldAnchor.bind(this))
this._display.addEventListener(XRDisplay.UPDATE_WORLD_ANCHOR, this._handleUpdateWorldAnchor.bind(this))
}
get display(){ return this._display }
get createParameters(){ return this._parameters }
get realities(){ return this._xr._sharedRealities }
get reality(){ return this._display._reality }
get baseLayer(){
return this._baseLayer
}
set baseLayer(value){
this._baseLayer = value
this._display._handleNewBaseLayer(this._baseLayer)
}
get depthNear(){ this._display._depthNear }
set depthNear(value){ this._display._depthNear = value }
get depthFar(){ this._display._depthFar }
set depthFar(value){ this._display._depthFar = value }
get hasStageBounds(){ this._stageBounds !== null }
get stageBounds(){ return this._stageBounds }
requestFrame(callback){
if(this._ended) return null
if(typeof callback !== 'function'){
throw 'Invalid callback'
}
return this._handleRequestFrame(callback)
}
_handleRequestFrame(callback) {
return this._display._requestAnimationFrame((timestamp) => {
if (this._skip) {
this._skip = false;
return this._handleRequestFrame(callback)
}
//this._skip = true; // try skipping every second raf
const frame = this._createPresentationFrame(timestamp)
this._updateCameraAnchor(frame)
this._display._reality._handleNewFrame(frame)
this._display._handleNewFrame(frame)
callback(frame)
this._display._handleAfterFrame(frame)
})
}
cancelFrame(handle){
return this._display._cancelAnimationFrame(handle)
}
end(){
if(this._ended) return
for (var i = 0; i< this._frameAnchors.length; i++) {
this._display._reality._removeAnchor(this._frameAnchors[i].uid)
}
this._frameAnchors = [];
this._ended = true
this._display._stop()
return new Promise((resolve, reject) => {
resolve()
})
}
_updateCameraAnchor(frame) {
// new anchor each minute
if (this._frameAnchors.length == 0 || (this._frameAnchors[0].timestamp + 1000) < frame.timestamp) {
const headCoordinateSystem = frame.getCoordinateSystem(XRCoordinateSystem.EYE_LEVEL)
const anchorUID = frame.addAnchor(headCoordinateSystem, [0,-1,0], [0,0,0,1],
'cameraAnchor-' + new Date().getTime() + '-' + Math.floor((Math.random() * Number.MAX_SAFE_INTEGER)));
const anchor = frame.getAnchor(anchorUID)
anchor.timestamp = frame.timestamp;
this._frameAnchors.unshift(anchor)
if (this._frameAnchors.length > 5) {
var oldAnchor = this._frameAnchors.pop()
this._display._reality._removeAnchor(oldAnchor.uid)
}
return anchor;
} else {
return this._frameAnchors[0]
}
}
_transformToCameraAnchor(camera) {
if (this._frameAnchors.length == 0) return camera.viewMatrix
var matrix = camera.viewMatrix
camera._anchorUid = this._frameAnchors[0].uid
const anchorCoords = this._frameAnchors[0].coordinateSystem
// should only have to invert anchor coords, but we're sending in the inverse
// of the camera pose ...
// get world to anchor by inverting anchor to world
MatrixMath.mat4_invert(this._tempMatrix, anchorCoords._poseModelMatrix)
// get camera to world by inverting world to camera
// MatrixMath.mat4_invert(this._tempMatrix2, matrix)
// MatrixMath.mat4_multiply(camera.viewMatrix, this._tempMatrix, this._tempMatrix2)
MatrixMath.mat4_multiply(camera.viewMatrix, this._tempMatrix, matrix)
}
setVideoFrameHandler(callback) {
if (callback instanceof Worker) {
var worker = callback;
callback = (ev => {
// var cv = ev.detail
// var buffers = cv.frame.buffers
// var buffs = []
// for (var i = 0; i < buffers.length; i++) {
// buffs.push(buffers[i].buffer)
// }
// worker.postMessage(cv, buffs);
this._transformToCameraAnchor(ev.detail.camera)
ev.detail.postMessageToWorker(worker, {type: "newVideoFrame"})
ev.detail.release()
})
} else {
var originalCallback = callback;
callback = (ev => {
this._transformToCameraAnchor(ev.detail.camera)
originalCallback(ev)
})
}
this._display.addEventListener("videoFrame", callback)
}
getVideoFramePose(videoFrame, poseOut)
{
if (!videoFrame.camera._anchorUid) return
var anchorPose;
var anchor = this.reality._getAnchor(videoFrame.camera._anchorUid)
if (anchor) {
anchorPose = anchor.coordinateSystem._poseModelMatrix
} else {
var i =0;
for (; i < this._frameAnchors.length; i++) {
if (videoFrame.camera._anchorUid == this._frameAnchors[i].uid) {
anchorPose = this._frameAnchors[i].coordinateSystem._poseModelMatrix;
break;
}
}
if (i == this._frameAnchors.length) {
// shouldn't happen!
console.warn("should never get here: session.getVideoFramePose can't find anchor")
return;
}
}
MatrixMath.mat4_multiply(poseOut, anchorPose, videoFrame.camera.viewMatrix )
}
// normalized screen x and y are in range 0..1, with 0,0 at top left and 1,1 at bottom right
hitTest(normalizedScreenX, normalizedScreenY, options=null){
// Promise<XRAnchorOffset?> findAnchor(float32, float32); // cast a ray to find or create an anchor at the first intersection in the Reality
return this.reality._findAnchor(normalizedScreenX, normalizedScreenY, this.display, options)
}
requestVideoFrame() {
this._display._requestVideoFrame();
}
stopVideoFrames() {
this._display._stopVideoFrames();
}
startVideoFrames() {
this._display._startVideoFrames();
}
_createPresentationFrame(timestamp){
return new XRPresentationFrame(this, timestamp)
}
_getCoordinateSystem(...types){
for(let type of types){
switch(type){
case XRCoordinateSystem.HEAD_MODEL:
return this._display._headModelCoordinateSystem
case XRCoordinateSystem.EYE_LEVEL:
return this._display._eyeLevelCoordinateSystem
case XRCoordinateSystem.TRACKER:
return this._display._trackerCoordinateSystem
case XRCoordinateSystem.GEOSPATIAL:
// Not supported yet
default:
continue
}
}
return null
}
createImageAnchor(uid, buffer, width, height, physicalWidthInMeters) {
return this.reality._createImageAnchor(uid, buffer, width, height, physicalWidthInMeters)
}
activateDetectionImage(uid) {
return this.reality._activateDetectionImage(uid, this._display)
}
_handleNewWorldAnchor(event) {
let xrAnchor = event.detail
//console.log(`New world anchor: ${JSON.stringify(xrAnchor)}`)
if (!xrAnchor.uid.startsWith('cameraAnchor-')) {
try {
this.dispatchEvent(
new CustomEvent(
XRSession.NEW_WORLD_ANCHOR,
{
source: this,
detail: xrAnchor
}
)
)
} catch(e) {
console.error('NEW_WORLD_ANCHOR event error', e)
}
} else {
// console.log('not passing NEW_WORLD_ANCHOR event to app for ', xrAnchor.uid)
}
}
_handleUpdateWorldAnchor(event) {
let xrAnchor = event.detail
//console.log(`New world anchor: ${JSON.stringify(xrAnchor)}`)
try {
this.dispatchEvent(
new CustomEvent(
XRSession.UPDATE_WORLD_ANCHOR,
{
source: this,
detail: xrAnchor
}
)
)
} catch(e) {
console.error('UPDATE_WORLD_ANCHOR event error', e)
}
}
_handleRemoveWorldAnchor(event) {
let xrAnchor = event.detail
//console.log(`Remove world anchor: ${JSON.stringify(xrAnchor)}`)
try {
this.dispatchEvent(
new CustomEvent(
XRSession.REMOVE_WORLD_ANCHOR,
{
source: this,
detail: xrAnchor
}
)
)
} catch(e) {
console.error('REMOVE_WORLD_ANCHOR event error', e)
}
}
_handleTrackingChanged(event) {
try {
this.dispatchEvent(
new CustomEvent(
XRSession.TRACKING_CHANGED,
{
source: this,
detail: event.detail
}
)
)
} catch(e) {
console.error('TRACKING_CHANGED event error', e)
}
}
getWorldMap() {
return this.reality._getWorldMap()
}
setWorldMap(worldMap) {
return this.reality._setWorldMap(worldMap)
}
getWorldMappingStatus() {
return this.reality._getWorldMappingStatus()
}
/*
attribute EventHandler onblur;
attribute EventHandler onfocus;
attribute EventHandler onresetpose;
attribute EventHandler onrealitychanged;
attribute EventHandler onrealityconnect;
attribute EventHandler onrealitydisconnect;
attribute EventHandler onboundschange;
attribute EventHandler onended;
*/
}
XRSession.REALITY = 'reality'
XRSession.AUGMENTATION = 'augmentation'
XRSession.TYPES = [XRSession.REALITY, XRSession.AUGMENTATION]
XRSession.TRACKING_CHANGED = 'tracking-changed'
XRSession.NEW_WORLD_ANCHOR = 'world-anchor'
XRSession.UPDATE_WORLD_ANCHOR = 'update-world-anchor'
XRSession.REMOVE_WORLD_ANCHOR = 'remove-world-anchor'