+
+
+
+
+
+
diff --git a/src/framework/application.js b/src/framework/application.js
index 965e224e15a..11acf29884b 100644
--- a/src/framework/application.js
+++ b/src/framework/application.js
@@ -1852,7 +1852,7 @@ Object.assign(pc, function () {
// #endif
if (frame) {
- app.xr.calculateViews(frame);
+ app.xr.update(frame);
app.graphicsDevice.defaultFramebuffer = frame.session.renderState.baseLayer.framebuffer;
} else {
app.graphicsDevice.defaultFramebuffer = null;
diff --git a/src/framework/components/camera/component.js b/src/framework/components/camera/component.js
index 4d57f31bf43..f780f8618e2 100644
--- a/src/framework/components/camera/component.js
+++ b/src/framework/components/camera/component.js
@@ -564,14 +564,22 @@ Object.assign(pc, function () {
* @description Attempt to start XR session with this camera
* @param {string} type - The type of session. Can be one of the following:
*
- * * {@link pc.XRTYPE_INLINE}: Inline - always available type of session. It has limited features availability and is rendered into HTML element.
- * * {@link pc.XRTYPE_VR}: Immersive VR - session that provides exclusive access to VR device with best available tracking features.
- * * {@link pc.XRTYPE_AR}: Immersive AR - session that provides exclusive access to VR/AR device that is intended to be blended with real-world environment.
+ * * {@link pc.XRTYPE_INLINE}: Inline - always available type of session. It has limited feature availability and is rendered into HTML element.
+ * * {@link pc.XRTYPE_VR}: Immersive VR - session that provides exclusive access to the VR device with the best available tracking features.
+ * * {@link pc.XRTYPE_AR}: Immersive AR - session that provides exclusive access to the VR/AR device that is intended to be blended with the real-world environment.
*
- * @param {pc.callbacks.XrError} [callback] - Optional callback function called once session is started. The callback has one argument Error - it is null if successfully started XR session.
+ * @param {string} spaceType - reference space type. Can be one of the following:
+ *
+ * * {@link pc.XRSPACE_VIEWER}: Viewer - always supported space with some basic tracking capabilities.
+ * * {@link pc.XRSPACE_LOCAL}: Local - represents a tracking space with a native origin near the viewer at the time of creation. It is meant for seated or basic local XR sessions.
+ * * {@link pc.XRSPACE_LOCALFLOOR}: Local Floor - represents a tracking space with a native origin at the floor in a safe position for the user to stand. The y axis equals 0 at floor level. Floor level value might be estimated by the underlying platform. It is meant for seated or basic local XR sessions.
+ * * {@link pc.XRSPACE_BOUNDEDFLOOR}: Bounded Floor - represents a tracking space with its native origin at the floor, where the user is expected to move within a pre-established boundary.
+ * * {@link pc.XRSPACE_UNBOUNDED}: Unbounded - represents a tracking space where the user is expected to move freely around their environment, potentially long distances from their starting point.
+ *
+ * @param {pc.callbacks.XrError} [callback] - Optional callback function called once the session is started. The callback has one argument Error - it is null if the XR session started successfully.
* @example
* // On an entity with a camera component
- * this.entity.camera.startXr(pc.XRTYPE_VR, function (err) {
+ * this.entity.camera.startXr(pc.XRTYPE_VR, pc.XRSPACE_LOCAL, function (err) {
* if (err) {
* // failed to start XR session
* } else {
@@ -579,8 +587,8 @@ Object.assign(pc, function () {
* }
* });
*/
- startXr: function (type, callback) {
- this.system.app.xr.start(this, type, callback);
+ startXr: function (type, spaceType, callback) {
+ this.system.app.xr.start(this, type, spaceType, callback);
},
/**
diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js
new file mode 100644
index 00000000000..e0c453081e2
--- /dev/null
+++ b/src/xr/xr-input-source.js
@@ -0,0 +1,231 @@
+Object.assign(pc, function () {
+ var quat = new pc.Quat();
+
+
+ var targetRayModes = {
+ /**
+ * @constant
+ * @type string
+ * @name pc.XRTARGETRAY_GAZE
+ * @description Gaze - indicates the target ray will originate at the viewer and follow the direction it is facing. (This is commonly referred to as a "gaze input" device in the context of head-mounted displays.)
+ */
+ XRTARGETRAY_GAZE: 'gaze',
+
+ /**
+ * @constant
+ * @type string
+ * @name pc.XRTARGETRAY_SCREEN
+ * @description Screen - indicates that the input source was an interaction with the canvas element associated with an inline session’s output context, such as a mouse click or touch event.
+ */
+ XRTARGETRAY_SCREEN: 'screen',
+
+ /**
+ * @constant
+ * @type string
+ * @name pc.XRTARGETRAY_POINTER
+ * @description Tracked Pointer - indicates that the target ray originates from either a handheld device or other hand-tracking mechanism and represents that the user is using their hands or the held device for pointing.
+ */
+ XRTARGETRAY_POINTER: 'tracked-pointer'
+ };
+
+ var handednessTypes = {
+ /**
+ * @constant
+ * @type string
+ * @name pc.XRHAND_NONE
+ * @description None - input source is not meant to be held in hands.
+ */
+ XRHAND_NONE: 'none',
+
+ /**
+ * @constant
+ * @type string
+ * @name pc.XRHAND_LEFT
+ * @description Left - indicates that input source is meant to be held in left hand.
+ */
+ XRHAND_LEFT: 'left',
+
+ /**
+ * @constant
+ * @type string
+ * @name pc.XRHAND_RIGHT
+ * @description Right - indicates that input source is meant to be held in right hand.
+ */
+ XRHAND_RIGHT: 'right'
+ };
+
+ /**
+ * @class
+ * @name pc.XrInputSource
+ * @augments pc.EventHandler
+ * @classdesc Represents an XR input source, which is any input mechanism which allows the user to perform targeted actions in the same virtual space as the viewer. Example XR input sources include, but are not limited to, handheld controllers, optically tracked hands, and gaze-based input methods that operate on the viewer's pose.
+ * @description Represents an XR input source, which is any input mechanism which allows the user to perform targeted actions in the same virtual space as the viewer. Example XR input sources include, but are not limited to, handheld controllers, optically tracked hands, and gaze-based input methods that operate on the viewer's pose.
+ * @param {pc.XrManager} manager - WebXR Manager.
+ * @param {object} xrInputSource - XRInputSource object that is created by WebXR API.
+ * @property {object} inputSource XRInputSource object that is associated with this input source.
+ * @property {string} targetRayMode Type of ray Input Device is based on. Can be one of the following:
+ *
+ * * {@link pc.XRTARGETRAY_GAZE}: Gaze - indicates the target ray will originate at the viewer and follow the direction it is facing. (This is commonly referred to as a "gaze input" device in the context of head-mounted displays.)
+ * * {@link pc.XRTARGETRAY_SCREEN}: Screen - indicates that the input source was an interaction with the canvas element associated with an inline session’s output context, such as a mouse click or touch event.
+ * * {@link pc.XRTARGETRAY_POINTER}: Tracked Pointer - indicates that the target ray originates from either a handheld device or other hand-tracking mechanism and represents that the user is using their hands or the held device for pointing.
+ *
+ * @property {string} handedness Describes which hand input source is associated with. Can be one of the following:
+ *
+ * * {@link pc.XRHAND_NONE}: None - input source is not meant to be held in hands.
+ * * {@link pc.XRHAND_LEFT}: Left - indicates that input source is meant to be held in left hand.
+ * * {@link pc.XRHAND_RIGHT}: Right - indicates that input source is meant to be held in right hand.
+ *
+ * @property {string[]} profiles List of input profile names indicating both the prefered visual representation and behavior of the input source.
+ * @property {pc.Ray} ray Ray that is calculated based on {pc.XrInputSource#targetRayMode} that can be used for interacting with virtual objects. Its origin and direction are in local space of XR session.
+ * @property {boolean} grip If input source can be held, then it will have node with its world transformation, that can be used to position and rotate virtual joystics based on it.
+ * @property {pc.Vec3|null} position If {pc.XrInputSource#grip} is true, then position will represent position of handheld input source in local space of XR session.
+ * @property {pc.Quat|null} rotation If {pc.XrInputSource#grip} is true, then rotation will represent rotation of handheld input source in local space of XR session.
+ * @property {Gamepad|null} gamepad If input source has buttons, triggers, thumbstick or touchpad, then this object provides access to its states.
+ * @property {boolean} selecting True if input source is in active primary action between selectstart and selectend events.
+ */
+ var XrInputSource = function (manager, xrInputSource) {
+ pc.EventHandler.call(this);
+
+ this._manager = manager;
+ this._xrInputSource = xrInputSource;
+
+ this._ray = new pc.Ray();
+ this._grip = false;
+ this._position = null;
+ this._rotation = null;
+ this._selecting = false;
+ };
+ XrInputSource.prototype = Object.create(pc.EventHandler.prototype);
+ XrInputSource.prototype.constructor = XrInputSource;
+
+ /**
+ * @event
+ * @name pc.XrInputSource#remove
+ * @description Fired when {pc.XrInputSource} is removed.
+ * @example
+ * inputSource.on('remove', function () {
+ * // input source is not available anymore
+ * });
+ */
+
+ /**
+ * @event
+ * @name pc.XrInputSource#select
+ * @description Fired when input source has triggered primary action. This could be pressing a trigger button, or touching a screen.
+ * @param {object} evt - XRInputSourceEvent event data from WebXR API
+ * @example
+ * app.xr.input.on('select', function (evt) {
+ * if (obj.intersectsRay(inputSource.ray)) {
+ * // selected an object with input source
+ * }
+ * });
+ */
+
+ /**
+ * @event
+ * @name pc.XrInputSource#selectstart
+ * @description Fired when input source has started to trigger primary action.
+ * @param {object} evt - XRInputSourceEvent event data from WebXR API
+ */
+
+ /**
+ * @event
+ * @name pc.XrInputSource#selectend
+ * @description Fired when input source has ended triggerring primary action.
+ * @param {object} evt - XRInputSourceEvent event data from WebXR API
+ */
+
+ XrInputSource.prototype.update = function (frame) {
+ var targetRayPose = frame.getPose(this._xrInputSource.targetRaySpace, this._manager._referenceSpace);
+ if (! targetRayPose) return;
+
+ // ray
+ this._ray.origin.copy(targetRayPose.transform.position);
+
+ this._ray.direction.set(0, 0, -1);
+ quat.copy(targetRayPose.transform.orientation);
+ quat.transformVector(this._ray.direction, this._ray.direction);
+
+ // grip
+ if (this._xrInputSource.gripSpace) {
+ var gripPose = frame.getPose(this._xrInputSource.gripSpace, this._manager._referenceSpace);
+ if (gripPose) {
+ if (! this._grip) {
+ this._grip = true;
+ this._position = new pc.Vec3();
+ this._rotation = new pc.Quat();
+ }
+ this._position.copy(gripPose.transform.position);
+ this._rotation.copy(gripPose.transform.orientation);
+ }
+ }
+ };
+
+ Object.defineProperty(XrInputSource.prototype, 'inputSource', {
+ get: function () {
+ return this._xrInputSource;
+ }
+ });
+
+ Object.defineProperty(XrInputSource.prototype, 'targetRayMode', {
+ get: function () {
+ return this._xrInputSource.targetRayMode;
+ }
+ });
+
+ Object.defineProperty(XrInputSource.prototype, 'handedness', {
+ get: function () {
+ return this._xrInputSource.handedness;
+ }
+ });
+
+ Object.defineProperty(XrInputSource.prototype, 'profiles', {
+ get: function () {
+ return this._xrInputSource.profiles;
+ }
+ });
+
+ Object.defineProperty(XrInputSource.prototype, 'ray', {
+ get: function () {
+ return this._ray;
+ }
+ });
+
+ Object.defineProperty(XrInputSource.prototype, 'grip', {
+ get: function () {
+ return this._grip;
+ }
+ });
+
+ Object.defineProperty(XrInputSource.prototype, 'position', {
+ get: function () {
+ return this._position;
+ }
+ });
+
+ Object.defineProperty(XrInputSource.prototype, 'rotation', {
+ get: function () {
+ return this._rotation;
+ }
+ });
+
+ Object.defineProperty(XrInputSource.prototype, 'gamepad', {
+ get: function () {
+ return this._xrInputSource.gamepad || null;
+ }
+ });
+
+ Object.defineProperty(XrInputSource.prototype, 'selecting', {
+ get: function () {
+ return this._selecting;
+ }
+ });
+
+
+ var obj = {
+ XrInputSource: XrInputSource
+ };
+ Object.assign(obj, targetRayModes);
+ Object.assign(obj, handednessTypes);
+ return obj;
+}());
diff --git a/src/xr/xr-input.js b/src/xr/xr-input.js
new file mode 100644
index 00000000000..9ee367667b6
--- /dev/null
+++ b/src/xr/xr-input.js
@@ -0,0 +1,190 @@
+Object.assign(pc, function () {
+ /**
+ * @class
+ * @name pc.XrInput
+ * @augments pc.EventHandler
+ * @classdesc Provides access to input sources for WebXR.
+ * @description Provides access to input sources for WebXR.
+ * @param {pc.XrManager} manager - WebXR Manager.
+ * @property {pc.XrInputSource[]} inputSources List of active {pc.XrInputSource}
+ */
+ var XrInput = function (manager) {
+ pc.EventHandler.call(this);
+
+ var self = this;
+
+ this.manager = manager;
+ this._session = null;
+ this._inputSources = [];
+
+ this._onInputSourcesChangeEvt = function (evt) {
+ self._onInputSourcesChange(evt);
+ };
+
+ this.manager.on('start', this._onSessionStart, this);
+ this.manager.on('end', this._onSessionEnd, this);
+ };
+ XrInput.prototype = Object.create(pc.EventHandler.prototype);
+ XrInput.prototype.constructor = XrInput;
+
+ /**
+ * @event
+ * @name pc.XrInput#add
+ * @description Fired when new {pc.XrInputSource} is added to the list.
+ * @param {pc.XrInputSource} inputSource - Input source that has been added
+ * @example
+ * app.xr.input.on('add', function (inputSource) {
+ * // new input source is added
+ * });
+ */
+
+ /**
+ * @event
+ * @name pc.XrInput#remove
+ * @description Fired when {pc.XrInputSource} is removed to the list.
+ * @param {pc.XrInputSource} inputSource - Input source that has been removed
+ * @example
+ * app.xr.input.on('remove', function (inputSource) {
+ * // input source is removed
+ * });
+ */
+
+ /**
+ * @event
+ * @name pc.XrInput#select
+ * @description Fired when {pc.XrInputSource} has triggered primary action. This could be pressing a trigger button, or touching a screen.
+ * @param {pc.XrInputSource} inputSource - Input source that triggered select event
+ * @param {object} evt - XRInputSourceEvent event data from WebXR API
+ * @example
+ * app.xr.input.on('select', function (inputSource, evt) {
+ * if (obj.intersectsRay(inputSource.ray)) {
+ * // selected an object with input source
+ * }
+ * });
+ */
+
+ /**
+ * @event
+ * @name pc.XrInput#selectstart
+ * @description Fired when {pc.XrInputSource} has started to trigger primary action.
+ * @param {pc.XrInputSource} inputSource - Input source that triggered selectstart event
+ * @param {object} evt - XRInputSourceEvent event data from WebXR API
+ */
+
+ /**
+ * @event
+ * @name pc.XrInput#selectend
+ * @description Fired when {pc.XrInputSource} has ended triggerring primary action.
+ * @param {pc.XrInputSource} inputSource - Input source that triggered selectend event
+ * @param {object} evt - XRInputSourceEvent event data from WebXR API
+ */
+
+ XrInput.prototype._onSessionStart = function () {
+ this._session = this.manager.session;
+ this._session.addEventListener('inputsourceschange', this._onInputSourcesChangeEvt);
+
+ var self = this;
+
+ this._session.addEventListener('select', function (evt) {
+ var inputSource = self._getByInputSource(evt.inputSource);
+ inputSource.update(evt.frame);
+ inputSource.fire('select', evt);
+ self.fire('select', inputSource, evt);
+ });
+ this._session.addEventListener('selectstart', function (evt) {
+ var inputSource = self._getByInputSource(evt.inputSource);
+ inputSource.update(evt.frame);
+ inputSource._selecting = true;
+ inputSource.fire('selectstart', evt);
+ self.fire('selectstart', inputSource, evt);
+ });
+ this._session.addEventListener('selectend', function (evt) {
+ var inputSource = self._getByInputSource(evt.inputSource);
+ inputSource.update(evt.frame);
+ inputSource._selecting = false;
+ inputSource.fire('selectend', evt);
+ self.fire('selectend', inputSource, evt);
+ });
+
+ // add input sources
+ var inputSources = this._session.inputSources;
+ for (var i = 0; i < inputSources.length; i++) {
+ this._addInputSource(inputSources[i]);
+ }
+ };
+
+ XrInput.prototype._onSessionEnd = function () {
+ var i = this._inputSources.length;
+ while (i--) {
+ var inputSource = this._inputSources[i];
+ this._inputSources.splice(i, 1);
+ inputSource.fire('remove');
+ this.fire('remove', inputSource);
+ }
+
+ this._session.removeEventListener('inputsourceschange', this._onInputSourcesChangeEvt);
+ this._session = null;
+ };
+
+ XrInput.prototype._onInputSourcesChange = function (evt) {
+ var i;
+
+ // remove
+ for (i = 0; i < evt.removed.length; i++) {
+ this._removeInputSource(evt.removed[i]);
+ }
+
+ // add
+ for (i = 0; i < evt.added.length; i++) {
+ this._addInputSource(evt.added[i]);
+ }
+ };
+
+ XrInput.prototype._getByInputSource = function (xrInputSource) {
+ for (var i = 0; i < this._inputSources.length; i++) {
+ if (this._inputSources[i].inputSource === xrInputSource) {
+ return this._inputSources[i];
+ }
+ }
+
+ return null;
+ };
+
+ XrInput.prototype._addInputSource = function (xrInputSource) {
+ if (this._getByInputSource(xrInputSource))
+ return;
+
+ var inputSource = new pc.XrInputSource(this.manager, xrInputSource);
+ this._inputSources.push(inputSource);
+ this.fire('add', inputSource);
+ };
+
+ XrInput.prototype._removeInputSource = function (xrInputSource) {
+ for (var i = 0; i < this._inputSources.length; i++) {
+ if (this._inputSources[i].inputSource !== xrInputSource)
+ continue;
+
+ var inputSource = this._inputSources[i];
+ this._inputSources.splice(i, 1);
+ inputSource.fire('remove');
+ this.fire('remove', inputSource);
+ return;
+ }
+ };
+
+ XrInput.prototype.update = function (frame) {
+ for (var i = 0; i < this._inputSources.length; i++) {
+ this._inputSources[i].update(frame);
+ }
+ };
+
+ Object.defineProperty(XrInput.prototype, 'inputSources', {
+ get: function () {
+ return this._inputSources;
+ }
+ });
+
+ return {
+ XrInput: XrInput
+ };
+}());
diff --git a/src/xr/xr-manager.js b/src/xr/xr-manager.js
index 26763f9f340..c84554d376a 100644
--- a/src/xr/xr-manager.js
+++ b/src/xr/xr-manager.js
@@ -4,7 +4,8 @@ Object.assign(pc, function () {
* @constant
* @type string
* @name pc.XRTYPE_INLINE
- * @description Inline - always available type of session. It has limited features availability and is rendered into HTML element.
+ * @description Inline - always available type of session. It has limited features availability and is rendered
+ * into HTML element.
*/
XRTYPE_INLINE: 'inline',
@@ -12,7 +13,8 @@ Object.assign(pc, function () {
* @constant
* @type string
* @name pc.XRTYPE_VR
- * @description Immersive VR - session that provides exclusive access to VR device with best available tracking features.
+ * @description Immersive VR - session that provides exclusive access to VR device with best available tracking
+ * features.
*/
XRTYPE_VR: 'immersive-vr',
@@ -20,11 +22,67 @@ Object.assign(pc, function () {
* @constant
* @type string
* @name pc.XRTYPE_AR
- * @description Immersive AR - session that provides exclusive access to VR/AR device that is intended to be blended with real-world environment.
+ * @description Immersive AR - session that provides exclusive access to VR/AR device that is intended to be blended
+ * with real-world environment.
*/
XRTYPE_AR: 'immersive-ar'
};
+ var spaceTypes = {
+ /**
+ * @constant
+ * @type string
+ * @name pc.XRSPACE_VIEWER
+ * @description Viewer - always supported space with some basic tracking capabilities.
+ */
+ XRSPACE_VIEWER: 'viewer',
+
+ /**
+ * @constant
+ * @type string
+ * @name pc.XRSPACE_LOCAL
+ * @description Local - represents a tracking space with a native origin near the viewer at the time of creation.
+ * The exact position and orientation will be initialized based on the conventions of the underlying platform.
+ * When using this reference space the user is not expected to move beyond their initial position much, if at all,
+ * and tracking is optimized for that purpose. For devices with 6DoF tracking, local reference spaces should
+ * emphasize keeping the origin stable relative to the user’s environment.
+ */
+ XRSPACE_LOCAL: 'local',
+
+ /**
+ * @constant
+ * @type string
+ * @name pc.XRSPACE_LOCALFLOOR
+ * @description Local Floor - represents a tracking space with a native origin at the floor in a safe position for
+ * the user to stand. The y axis equals 0 at floor level, with the x and z position and orientation initialized
+ * based on the conventions of the underlying platform. Floor level value might be estimated by the underlying
+ * platform. When using this reference space, the user is not expected to move beyond their initial position much,
+ * if at all, and tracking is optimized for that purpose. For devices with 6DoF tracking, local-floor reference
+ * spaces should emphasize keeping the origin stable relative to the user’s environment.
+ */
+ XRSPACE_LOCALFLOOR: 'local-floor',
+
+ /**
+ * @constant
+ * @type string
+ * @name pc.XRSPACE_BOUNDEDFLOOR
+ * @description Bounded Floor - represents a tracking space with its native origin at the floor, where the user
+ * is expected to move within a pre-established boundary. Tracking in a bounded-floor reference space is optimized
+ * for keeping the native origin and bounds geometry stable relative to the user’s environment.
+ */
+ XRSPACE_BOUNDEDFLOOR: 'bounded-floor',
+
+ /**
+ * @constant
+ * @type string
+ * @name pc.XRSPACE_UNBOUNDED
+ * @description Unbounded - represents a tracking space where the user is expected to move freely around their
+ * environment, potentially even long distances from their starting point. Tracking in an unbounded reference space
+ * is optimized for stability around the user’s current position, and as such the native origin may drift over time.
+ */
+ XRSPACE_UNBOUNDED: 'unbounded'
+ };
+
/**
* @class
@@ -33,9 +91,13 @@ Object.assign(pc, function () {
* @classdesc Manage and update XR session and its states.
* @description Manage and update XR session and its states.
* @param {pc.Application} app - The main application.
- * @property {boolean} supported Returns true if XR is supported.
- * @property {boolean} active Returns true if XR session is running.
- * @property {string|null} type Returns type of curently running XR session or null if no session is running.
+ * @property {boolean} supported True if XR is supported.
+ * @property {boolean} active True if XR session is running.
+ * @property {string|null} type Returns type of currently running XR session or null if no session is running. Can be
+ * any of pc.XRTYPE_*.
+ * @property {string|null} spaceType Returns reference space type of currently running XR session or null if no session
+ * is running. Can be any of pc.XRSPACE_*.
+ * @property {pc.Entity|null} camera Active camera for which XR session is running or null.
*/
var XrManager = function (app) {
pc.EventHandler.call(this);
@@ -52,10 +114,11 @@ Object.assign(pc, function () {
}
this._type = null;
+ this._spaceType = null;
this._session = null;
this._baseLayer = null;
this._referenceSpace = null;
- this._inputSources = [];
+ this.input = new pc.XrInput(this);
this._camera = null;
this._pose = null;
@@ -128,6 +191,17 @@ Object.assign(pc, function () {
* });
*/
+ /**
+ * @event
+ * @name pc.XrManager#error
+ * @param {Error} error - Error object related to failure of session start.
+ * @description Fired when XR session is failed to start
+ * @example
+ * app.xr.on('error', function (ex) {
+ * // XR session has failed to start
+ * });
+ */
+
/**
* @function
* @name pc.XrManager#start
@@ -139,13 +213,21 @@ Object.assign(pc, function () {
* * {@link pc.XRTYPE_VR}: Immersive VR - session that provides exclusive access to VR device with best available tracking features.
* * {@link pc.XRTYPE_AR}: Immersive AR - session that provides exclusive access to VR/AR device that is intended to be blended with real-world environment.
*
+ * @param {string} spaceType - reference space type. Can be one of the following:
+ *
+ * * {@link pc.XRSPACE_VIEWER}: Viewer - always supported space with some basic tracking capabilities.
+ * * {@link pc.XRSPACE_LOCAL}: Local - represents a tracking space with a native origin near the viewer at the time of creation. It is meant for seated or basic local XR sessions.
+ * * {@link pc.XRSPACE_LOCALFLOOR}: Local Floor - represents a tracking space with a native origin at the floor in a safe position for the user to stand. The y axis equals 0 at floor level. Floor level value might be estimated by the underlying platform. It is meant for seated or basic local XR sessions.
+ * * {@link pc.XRSPACE_BOUNDEDFLOOR}: Bounded Floor - represents a tracking space with its native origin at the floor, where the user is expected to move within a pre-established boundary.
+ * * {@link pc.XRSPACE_UNBOUNDED}: Unbounded - represents a tracking space where the user is expected to move freely around their environment, potentially long distances from their starting point.
+ *
* @example
* button.on('click', function () {
- * app.xr.start(camera, PC.XRTYPE_VR);
+ * app.xr.start(camera, pc.XRTYPE_VR, pc.XRSPACE_LOCAL);
* });
* @param {pc.callbacks.XrError} [callback] - Optional callback function called once session is started. The callback has one argument Error - it is null if successfully started XR session.
*/
- XrManager.prototype.start = function (camera, type, callback) {
+ XrManager.prototype.start = function (camera, type, spaceType, callback) {
if (! this._available[type]) {
if (callback) callback(new Error('XR is not available'));
return;
@@ -161,6 +243,7 @@ Object.assign(pc, function () {
this._camera = camera;
this._camera.camera.xr = this;
this._type = type;
+ this._spaceType = spaceType;
this._setClipPlanes(camera.nearClip, camera.farClip);
@@ -172,8 +255,18 @@ Object.assign(pc, function () {
// 3. probably immersive-vr will fail to be created
// 4. call makeXRCompatible, very likely will lead to context loss
- navigator.xr.requestSession(type).then(function (session) {
- self._onSessionStart(session, callback);
+ navigator.xr.requestSession(type, {
+ requiredFeatures: [spaceType]
+ }).then(function (session) {
+ self._onSessionStart(session, spaceType, callback);
+ }).catch(function (ex) {
+ self._camera.camera.xr = null;
+ self._camera = null;
+ self._type = null;
+ self._spaceType = null;
+
+ if (callback) callback(ex);
+ self.fire('error', ex);
});
};
@@ -239,8 +332,9 @@ Object.assign(pc, function () {
});
};
- XrManager.prototype._onSessionStart = function (session, callback) {
+ XrManager.prototype._onSessionStart = function (session, spaceType, callback) {
var self = this;
+ var failed = false;
this._session = session;
@@ -248,17 +342,6 @@ Object.assign(pc, function () {
self.fire('visibility:change', session.visibilityState);
};
- var onInputSourcesChange = function (evt) {
- var i;
-
- for (i = 0; i < evt.removed.length; i++) {
- self._inputSourceRemove(evt.removed[i]);
- }
- for (i = 0; i < evt.added.length; i++) {
- self._inputSourceAdd(evt.added[i]);
- }
- };
-
var onClipPlanesChange = function () {
self._setClipPlanes(self._camera.nearClip, self._camera.farClip);
};
@@ -267,12 +350,12 @@ Object.assign(pc, function () {
var onEnd = function () {
self._session = null;
self._referenceSpace = null;
- self._inputSources = [];
self._pose = null;
self.views = [];
self._width = 0;
self._height = 0;
self._type = null;
+ self._spaceType = null;
if (self._camera) {
self._camera.off('set_nearClip', onClipPlanesChange);
@@ -284,18 +367,16 @@ Object.assign(pc, function () {
session.removeEventListener('end', onEnd);
session.removeEventListener('visibilitychange', onVisibilityChange);
- session.removeEventListener('inputsourceschange', onInputSourcesChange);
// old requestAnimationFrame will never be triggered,
// so queue up new tick
self.app.tick();
- self.fire('end');
+ if (! failed) self.fire('end');
};
session.addEventListener('end', onEnd);
session.addEventListener('visibilitychange', onVisibilityChange);
- session.addEventListener('inputsourceschange', onInputSourcesChange);
this._camera.on('set_nearClip', onClipPlanesChange);
this._camera.on('set_farClip', onClipPlanesChange);
@@ -309,7 +390,7 @@ Object.assign(pc, function () {
});
// request reference space
- session.requestReferenceSpace('local').then(function (referenceSpace) {
+ session.requestReferenceSpace(spaceType).then(function (referenceSpace) {
self._referenceSpace = referenceSpace;
// old requestAnimationFrame will never be triggered,
@@ -318,21 +399,14 @@ Object.assign(pc, function () {
if (callback) callback(null);
self.fire('start');
+ }).catch(function (ex) {
+ failed = true;
+ session.end();
+ if (callback) callback(ex);
+ self.fire('error', ex);
});
};
- XrManager.prototype._inputSourceAdd = function (inputSource) {
- this._inputSources.push(inputSource);
- this.fire('inputSource:add', inputSource);
- };
-
- XrManager.prototype._inputSourceRemove = function (inputSource) {
- var ind = this._inputSources.indexOf(inputSource);
- if (ind === -1) return;
- this._inputSources.splice(ind, 1);
- this.fire('inputSource:remove', inputSource);
- };
-
XrManager.prototype._setClipPlanes = function (near, far) {
near = Math.min(0.0001, Math.max(0.1, near));
far = Math.max(1000, far);
@@ -354,7 +428,7 @@ Object.assign(pc, function () {
});
};
- XrManager.prototype.calculateViews = function (frame) {
+ XrManager.prototype.update = function (frame) {
if (! this._session) return;
var i, view, viewRaw, layer, viewport;
@@ -401,13 +475,13 @@ Object.assign(pc, function () {
}
}
- // reset position
- var posePosition = this._pose.transform.position;
- var poseOrientation = this._pose.transform.orientation;
- this.position.set(posePosition.x, posePosition.y, posePosition.z);
- this.rotation.set(poseOrientation.x, poseOrientation.y, poseOrientation.z, poseOrientation.w);
-
if (this._pose) {
+ // reset position
+ var posePosition = this._pose.transform.position;
+ var poseOrientation = this._pose.transform.orientation;
+ this.position.set(posePosition.x, posePosition.y, posePosition.z);
+ this.rotation.set(poseOrientation.x, poseOrientation.y, poseOrientation.z, poseOrientation.w);
+
layer = frame.session.renderState.baseLayer;
for (i = 0; i < this._pose.views.length; i++) {
@@ -430,6 +504,8 @@ Object.assign(pc, function () {
// position and rotate camera based on calculated vectors
this._camera.camera._node.setLocalPosition(this.position);
this._camera.camera._node.setLocalRotation(this.rotation);
+
+ this.input.update(frame);
};
Object.defineProperty(XrManager.prototype, 'supported', {
@@ -450,6 +526,12 @@ Object.assign(pc, function () {
}
});
+ Object.defineProperty(XrManager.prototype, 'spaceType', {
+ get: function () {
+ return this._spaceType;
+ }
+ });
+
Object.defineProperty(XrManager.prototype, 'session', {
get: function () {
return this._session;
@@ -465,11 +547,18 @@ Object.assign(pc, function () {
}
});
+ Object.defineProperty(XrManager.prototype, 'camera', {
+ get: function () {
+ return this._camera ? this._camera.entity : null;
+ }
+ });
+
var obj = {
XrManager: XrManager
};
Object.assign(obj, sessionTypes);
+ Object.assign(obj, spaceTypes);
return obj;