playcanvas
Version:
PlayCanvas WebGL game engine
444 lines (441 loc) • 15.9 kB
JavaScript
import { EventHandler } from '../../core/event-handler.js';
import { platform } from '../../core/platform.js';
import { Mat4 } from '../../core/math/mat4.js';
import { Quat } from '../../core/math/quat.js';
import { Vec3 } from '../../core/math/vec3.js';
import { XRTYPE_AR, XRDEPTHSENSINGUSAGE_GPU, XRDEPTHSENSINGUSAGE_CPU, XRDEPTHSENSINGFORMAT_F32, XRDEPTHSENSINGFORMAT_L8A8, XRDEPTHSENSINGFORMAT_R16U, XRTYPE_INLINE, XRTYPE_VR } from './constants.js';
import { XrDomOverlay } from './xr-dom-overlay.js';
import { XrHitTest } from './xr-hit-test.js';
import { XrImageTracking } from './xr-image-tracking.js';
import { XrInput } from './xr-input.js';
import { XrLightEstimation } from './xr-light-estimation.js';
import { XrPlaneDetection } from './xr-plane-detection.js';
import { XrAnchors } from './xr-anchors.js';
import { XrMeshDetection } from './xr-mesh-detection.js';
import { XrViews } from './xr-views.js';
class XrManager extends EventHandler {
destroy() {}
start(camera, type, spaceType, options) {
var _this_app_graphicsDevice;
var callback = options;
if (typeof options === 'object') {
callback = options.callback;
}
if (!this._available[type]) {
if (callback) callback(new Error('XR is not available'));
return;
}
if (this._session) {
if (callback) callback(new Error('XR session is already started'));
return;
}
this._camera = camera;
this._camera.camera.xr = this;
this._type = type;
this._spaceType = spaceType;
var _options_framebufferScaleFactor;
this._framebufferScaleFactor = (_options_framebufferScaleFactor = options == null ? undefined : options.framebufferScaleFactor) != null ? _options_framebufferScaleFactor : 1.0;
this._setClipPlanes(camera.nearClip, camera.farClip);
var opts = {
requiredFeatures: [
spaceType
],
optionalFeatures: []
};
var webgl = (_this_app_graphicsDevice = this.app.graphicsDevice) == null ? undefined : _this_app_graphicsDevice.isWebGL2;
if (type === XRTYPE_AR) {
opts.optionalFeatures.push('light-estimation');
opts.optionalFeatures.push('hit-test');
if (options) {
if (options.imageTracking && this.imageTracking.supported) {
opts.optionalFeatures.push('image-tracking');
}
if (options.planeDetection) {
opts.optionalFeatures.push('plane-detection');
}
if (options.meshDetection) {
opts.optionalFeatures.push('mesh-detection');
}
}
if (this.domOverlay.supported && this.domOverlay.root) {
opts.optionalFeatures.push('dom-overlay');
opts.domOverlay = {
root: this.domOverlay.root
};
}
if (options && options.anchors && this.anchors.supported) {
opts.optionalFeatures.push('anchors');
}
if (options && options.depthSensing && this.views.supportedDepth) {
opts.optionalFeatures.push('depth-sensing');
var usagePreference = [];
var dataFormatPreference = [];
usagePreference.push(XRDEPTHSENSINGUSAGE_GPU, XRDEPTHSENSINGUSAGE_CPU);
dataFormatPreference.push(XRDEPTHSENSINGFORMAT_F32, XRDEPTHSENSINGFORMAT_L8A8, XRDEPTHSENSINGFORMAT_R16U);
if (options.depthSensing.usagePreference) {
var ind = usagePreference.indexOf(options.depthSensing.usagePreference);
if (ind !== -1) usagePreference.splice(ind, 1);
usagePreference.unshift(options.depthSensing.usagePreference);
}
if (options.depthSensing.dataFormatPreference) {
var ind1 = dataFormatPreference.indexOf(options.depthSensing.dataFormatPreference);
if (ind1 !== -1) dataFormatPreference.splice(ind1, 1);
dataFormatPreference.unshift(options.depthSensing.dataFormatPreference);
}
opts.depthSensing = {
usagePreference: usagePreference,
dataFormatPreference: dataFormatPreference
};
}
if (webgl && options && options.cameraColor && this.views.supportedColor) {
opts.optionalFeatures.push('camera-access');
}
}
opts.optionalFeatures.push('hand-tracking');
if (options && options.optionalFeatures) {
opts.optionalFeatures = opts.optionalFeatures.concat(options.optionalFeatures);
}
if (this.imageTracking.supported && this.imageTracking.images.length) {
this.imageTracking.prepareImages((err, trackedImages)=>{
if (err) {
if (callback) callback(err);
this.fire('error', err);
return;
}
if (trackedImages !== null) {
opts.trackedImages = trackedImages;
}
this._onStartOptionsReady(type, spaceType, opts, callback);
});
} else {
this._onStartOptionsReady(type, spaceType, opts, callback);
}
}
_onStartOptionsReady(type, spaceType, options, callback) {
navigator.xr.requestSession(type, options).then((session)=>{
this._onSessionStart(session, spaceType, callback);
}).catch((ex)=>{
this._camera.camera.xr = null;
this._camera = null;
this._type = null;
this._spaceType = null;
if (callback) callback(ex);
this.fire('error', ex);
});
}
end(callback) {
if (!this._session) {
if (callback) callback(new Error('XR Session is not initialized'));
return;
}
this.webglBinding = null;
if (callback) this.once('end', callback);
this._session.end();
}
isAvailable(type) {
return this._available[type];
}
_deviceAvailabilityCheck() {
for(var key in this._available){
this._sessionSupportCheck(key);
}
}
initiateRoomCapture(callback) {
if (!this._session) {
callback(new Error('Session is not active'));
return;
}
if (!this._session.initiateRoomCapture) {
callback(new Error('Session does not support manual room capture'));
return;
}
this._session.initiateRoomCapture().then(()=>{
if (callback) callback(null);
}).catch((err)=>{
if (callback) callback(err);
});
}
updateTargetFrameRate(frameRate, callback) {
var _this__session;
if (!((_this__session = this._session) == null ? undefined : _this__session.updateTargetFrameRate)) {
callback == null ? undefined : callback(new Error('unable to update frameRate'));
return;
}
this._session.updateTargetFrameRate(frameRate).then(()=>{
callback == null ? undefined : callback();
}).catch((err)=>{
callback == null ? undefined : callback(err);
});
}
_sessionSupportCheck(type) {
navigator.xr.isSessionSupported(type).then((available)=>{
if (this._available[type] === available) {
return;
}
this._available[type] = available;
this.fire('available', type, available);
this.fire("available:" + type, available);
}).catch((ex)=>{
this.fire('error', ex);
});
}
_onSessionStart(session, spaceType, callback) {
var failed = false;
this._session = session;
var onVisibilityChange = ()=>{
this.fire('visibility:change', session.visibilityState);
};
var onClipPlanesChange = ()=>{
this._setClipPlanes(this._camera.nearClip, this._camera.farClip);
};
var onEnd = ()=>{
if (this._camera) {
this._camera.off('set_nearClip', onClipPlanesChange);
this._camera.off('set_farClip', onClipPlanesChange);
this._camera.camera.xr = null;
this._camera = null;
}
session.removeEventListener('end', onEnd);
session.removeEventListener('visibilitychange', onVisibilityChange);
if (!failed) this.fire('end');
this._session = null;
this._referenceSpace = null;
this._width = 0;
this._height = 0;
this._type = null;
this._spaceType = null;
if (this.app.systems) {
this.app.tick();
}
};
session.addEventListener('end', onEnd);
session.addEventListener('visibilitychange', onVisibilityChange);
this._camera.on('set_nearClip', onClipPlanesChange);
this._camera.on('set_farClip', onClipPlanesChange);
this._createBaseLayer();
if (this.session.supportedFrameRates) {
this._supportedFrameRates = Array.from(this.session.supportedFrameRates);
} else {
this._supportedFrameRates = null;
}
this._session.addEventListener('frameratechange', ()=>{
var _this__session;
this.fire('frameratechange', (_this__session = this._session) == null ? undefined : _this__session.frameRate);
});
session.requestReferenceSpace(spaceType).then((referenceSpace)=>{
this._referenceSpace = referenceSpace;
this.app.tick();
if (callback) callback(null);
this.fire('start');
}).catch((ex)=>{
failed = true;
session.end();
if (callback) callback(ex);
this.fire('error', ex);
});
}
_setClipPlanes(near, far) {
if (this._depthNear === near && this._depthFar === far) {
return;
}
this._depthNear = near;
this._depthFar = far;
if (!this._session) {
return;
}
this._session.updateRenderState({
depthNear: this._depthNear,
depthFar: this._depthFar
});
}
_createBaseLayer() {
var device = this.app.graphicsDevice;
var framebufferScaleFactor = device.maxPixelRatio / window.devicePixelRatio * this._framebufferScaleFactor;
this._baseLayer = new XRWebGLLayer(this._session, device.gl, {
alpha: true,
depth: true,
stencil: true,
framebufferScaleFactor: framebufferScaleFactor,
antialias: false
});
if ((device == null ? undefined : device.isWebGL2) && window.XRWebGLBinding) {
try {
this.webglBinding = new XRWebGLBinding(this._session, device.gl);
} catch (ex) {
this.fire('error', ex);
}
}
this._session.updateRenderState({
baseLayer: this._baseLayer,
depthNear: this._depthNear,
depthFar: this._depthFar
});
}
_onDeviceLost() {
if (!this._session) {
return;
}
if (this.webglBinding) {
this.webglBinding = null;
}
this._baseLayer = null;
this._session.updateRenderState({
baseLayer: this._baseLayer,
depthNear: this._depthNear,
depthFar: this._depthFar
});
}
_onDeviceRestored() {
if (!this._session) {
return;
}
setTimeout(()=>{
this.app.graphicsDevice.gl.makeXRCompatible().then(()=>{
this._createBaseLayer();
}).catch((ex)=>{
this.fire('error', ex);
});
}, 0);
}
update(frame) {
if (!this._session) return false;
var width = frame.session.renderState.baseLayer.framebufferWidth;
var height = frame.session.renderState.baseLayer.framebufferHeight;
if (this._width !== width || this._height !== height) {
this._width = width;
this._height = height;
this.app.graphicsDevice.setResolution(width, height);
}
var pose = frame.getViewerPose(this._referenceSpace);
if (!pose) return false;
var lengthOld = this.views.list.length;
this.views.update(frame, pose.views);
var posePosition = pose.transform.position;
var poseOrientation = pose.transform.orientation;
this._localPosition.set(posePosition.x, posePosition.y, posePosition.z);
this._localRotation.set(poseOrientation.x, poseOrientation.y, poseOrientation.z, poseOrientation.w);
if (lengthOld === 0 && this.views.list.length > 0) {
var viewProjMat = new Mat4();
var view = this.views.list[0];
viewProjMat.copy(view.projMat);
var data = viewProjMat.data;
var fov = 2.0 * Math.atan(1.0 / data[5]) * 180.0 / Math.PI;
var aspectRatio = data[5] / data[0];
var farClip = data[14] / (data[10] + 1);
var nearClip = data[14] / (data[10] - 1);
var horizontalFov = false;
var camera = this._camera.camera;
camera.setXrProperties({
aspectRatio,
farClip,
fov,
horizontalFov,
nearClip
});
}
this._camera.camera._node.setLocalPosition(this._localPosition);
this._camera.camera._node.setLocalRotation(this._localRotation);
this.input.update(frame);
if (this._type === XRTYPE_AR) {
if (this.hitTest.supported) {
this.hitTest.update(frame);
}
if (this.lightEstimation.supported) {
this.lightEstimation.update(frame);
}
if (this.imageTracking.supported) {
this.imageTracking.update(frame);
}
if (this.anchors.supported) {
this.anchors.update(frame);
}
if (this.planeDetection.supported) {
this.planeDetection.update(frame);
}
if (this.meshDetection.supported) {
this.meshDetection.update(frame);
}
}
this.fire('update', frame);
return true;
}
get supported() {
return this._supported;
}
get active() {
return !!this._session;
}
get type() {
return this._type;
}
get spaceType() {
return this._spaceType;
}
get session() {
return this._session;
}
get frameRate() {
var _this__session;
var _this__session_frameRate;
return (_this__session_frameRate = (_this__session = this._session) == null ? undefined : _this__session.frameRate) != null ? _this__session_frameRate : null;
}
get supportedFrameRates() {
return this._supportedFrameRates;
}
get framebufferScaleFactor() {
return this._framebufferScaleFactor;
}
set fixedFoveation(value) {
var _this__baseLayer;
var _this__baseLayer_fixedFoveation;
if (((_this__baseLayer_fixedFoveation = (_this__baseLayer = this._baseLayer) == null ? undefined : _this__baseLayer.fixedFoveation) != null ? _this__baseLayer_fixedFoveation : null) !== null) {
if (this.app.graphicsDevice.samples > 1) ;
this._baseLayer.fixedFoveation = value;
}
}
get fixedFoveation() {
var _this__baseLayer;
var _this__baseLayer_fixedFoveation;
return (_this__baseLayer_fixedFoveation = (_this__baseLayer = this._baseLayer) == null ? undefined : _this__baseLayer.fixedFoveation) != null ? _this__baseLayer_fixedFoveation : null;
}
get camera() {
return this._camera ? this._camera.entity : null;
}
get visibilityState() {
if (!this._session) {
return null;
}
return this._session.visibilityState;
}
constructor(app){
super(), this._supported = platform.browser && !!navigator.xr, this._available = {}, this._type = null, this._spaceType = null, this._session = null, this._baseLayer = null, this.webglBinding = null, this._referenceSpace = null, this._camera = null, this._localPosition = new Vec3(), this._localRotation = new Quat(), this._depthNear = 0.1, this._depthFar = 1000, this._supportedFrameRates = null, this._width = 0, this._height = 0, this._framebufferScaleFactor = 1.0;
this.app = app;
this._available[XRTYPE_INLINE] = false;
this._available[XRTYPE_VR] = false;
this._available[XRTYPE_AR] = false;
this.views = new XrViews(this);
this.domOverlay = new XrDomOverlay(this);
this.hitTest = new XrHitTest(this);
this.imageTracking = new XrImageTracking(this);
this.planeDetection = new XrPlaneDetection(this);
this.meshDetection = new XrMeshDetection(this);
this.input = new XrInput(this);
this.lightEstimation = new XrLightEstimation(this);
this.anchors = new XrAnchors(this);
this.views = new XrViews(this);
if (this._supported) {
navigator.xr.addEventListener('devicechange', ()=>{
this._deviceAvailabilityCheck();
});
this._deviceAvailabilityCheck();
this.app.graphicsDevice.on('devicelost', this._onDeviceLost, this);
this.app.graphicsDevice.on('devicerestored', this._onDeviceRestored, this);
}
}
}
XrManager.EVENT_AVAILABLE = 'available';
XrManager.EVENT_START = 'start';
XrManager.EVENT_END = 'end';
XrManager.EVENT_UPDATE = 'update';
XrManager.EVENT_ERROR = 'error';
export { XrManager };