From d9e6d2234f659437cc556d71678604cc2797c023 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Mon, 10 Feb 2020 20:34:48 +0200 Subject: [PATCH 01/18] initial work in progress for webxr input sources --- build/dependencies.txt | 1 + src/framework/application.js | 2 +- src/input/xr.js | 178 +++++++++++++++++++++++++++++++++++ src/xr/xr-manager.js | 44 ++------- 4 files changed, 190 insertions(+), 35 deletions(-) create mode 100644 src/input/xr.js diff --git a/build/dependencies.txt b/build/dependencies.txt index 89e0193954b..765030e7979 100644 --- a/build/dependencies.txt +++ b/build/dependencies.txt @@ -109,6 +109,7 @@ ../src/input/touch.js ../src/input/controller.js ../src/input/element-input.js +../src/input/xr.js ../src/vr/vr-manager.js ../src/vr/vr-display.js ../src/xr/xr-manager.js 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/input/xr.js b/src/input/xr.js new file mode 100644 index 00000000000..5ae4736fef1 --- /dev/null +++ b/src/input/xr.js @@ -0,0 +1,178 @@ +Object.assign(pc, function () { + var inputSourceId = 0; + var quatA = new pc.Quat(); + + var XrInputSource = function(inputSource) { + pc.EventHandler.call(this); + + this.id = ++inputSourceId; + this._inputSource = inputSource; + + this.type = ''; + + this.ray = new pc.Ray(); + this.grip = null; + }; + XrInputSource.prototype = Object.create(pc.EventHandler.prototype); + XrInputSource.prototype.constructor = XrInputSource; + + + var XrInputSources = function (manager) { + pc.EventHandler.call(this); + + var self = this; + + this.manager = manager; + this._session = null; + this._sources = [ ]; + + this._onInputSourcesChangeEvt = function(evt) { + self._onInputSourcesChange(evt); + }; + + this.manager.on('start', this._onSessionStart, this); + this.manager.on('end', this._onSessionEnd, this); + + this.on('add', function(inputSource) { + console.log('add', inputSource); + }); + + this.on('remove', function(inputSource) { + console.log('remove', inputSource); + }); + }; + XrInputSources.prototype = Object.create(pc.EventHandler.prototype); + XrInputSources.prototype.constructor = XrInputSources; + + // events + // add + // remove + + XrInputSources.prototype._onSessionStart = function () { + this._session = this.manager.session; + this._session.addEventListener('inputsourceschange', this._onInputSourcesChangeEvt); + + var self = this; + + this._session.addEventListener("select", function(evt) { + console.log('select', evt); + + var inputSourcePose = evt.frame.getPose(evt.inputSource.targetRaySpace, self.manager._referenceSpace); + if (inputSourcePose) { + console.log(inputSourcePose); + } + }); + this._session.addEventListener("selectstart", function(evt) { + console.log('selectstart', evt); + }); + this._session.addEventListener("selectend", function(evt) { + console.log('selectend', evt); + }); + + // add input sources + var sources = this._session.inputSources; + for(var i = 0; i < sources.length; i++) { + this._addInputSource(sources[i]); + } + }; + + XrInputSources.prototype._onSessionEnd = function () { + // todo + // fire remove event on all input sources + var i = this._sources.length; + while(i--) { + var source = this._sources[i]; + this._sources.splice(i, 1); + source.fire('remove'); + this.fire('remove', source); + } + + this._session.removeEventListener('inputsourceschange', this._onInputSourcesChangeEvt); + this._session = null; + }; + + XrInputSources.prototype._onInputSourcesChange = function (evt) { + console.log('inputsourceschange', evt); + + // remove + for(var i = 0; i < evt.removed.length; i++) { + this._removeInputSource(evt.removed[i]); + } + + // add + for(var i = 0; i < evt.added.length; i++) { + this._addInputSource(evt.added[i]); + } + }; + + XrInputSources.prototype._addInputSource = function (inputSource) { + var source = new XrInputSource(inputSource); + this._sources.push(source); + this.fire('add', source); + }; + + XrInputSources.prototype._removeInputSource = function (inputSource) { + for(var i = 0; i < this._sources.length; i++) { + if (this._sources[i]._inputSource !== inputSource) + continue; + + var source = this._sources[i]; + this._sources.splice(i, 1); + source.fire('remove'); + this.fire('remove', source); + return; + } + }; + + XrInputSources.prototype.update = function(frame) { + for(var i = 0; i < this._sources.length; i++) { + var source = this._sources[i]; + var inputSource = source._inputSource; + + var targetRayPose = frame.getPose(inputSource.targetRaySpace, this.manager._referenceSpace); + + if (! targetRayPose) { + console.log('no targetRayPose', i); + continue; + } + + // if (inputSource.targetRayMode === 'gaze') { + // if (! source.ray) source.ray = new pc.Ray(); + + source.ray.origin.copy(targetRayPose.transform.position); + + source.ray.direction.set(0, 0, -1); + quatA.copy(targetRayPose.transform.orientation); + quatA.transformVector(source.ray.direction, source.ray.direction); + + // } else if (inputSource.targetRayMode === 'tracked-pointer') { + // if (! source.ray) source.ray = new pc.Ray(); + // + // source.ray.origin.copy(targetRayPose.transform.position); + // + // source.ray.direction.set(0, 0, -1); + // quatA.copy(targetRayPose.transform.orientation); + // quatA.transformVector(source.ray.direction, source.ray.direction); + // } + + if (inputSource.gripSpace) { + var gripPose = frame.getPose(inputSource.gripSpace, this.manager._referenceSpace); + if (gripPose) { + if (! source.grip) { + source.grip = { + position: new pc.Vec3(), + rotation: new pc.Quat() + }; + }; + + source.grip.position.copy(gripPose.transform.position); + source.grip.rotation.copy(gripPose.transform.orientation); + } + } + } + }; + + return { + XrInputSources: XrInputSources + }; +}()); diff --git a/src/xr/xr-manager.js b/src/xr/xr-manager.js index 26763f9f340..f1bef012bba 100644 --- a/src/xr/xr-manager.js +++ b/src/xr/xr-manager.js @@ -55,7 +55,7 @@ Object.assign(pc, function () { this._session = null; this._baseLayer = null; this._referenceSpace = null; - this._inputSources = []; + this.input = new pc.XrInputSources(this); this._camera = null; this._pose = null; @@ -248,17 +248,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,7 +256,6 @@ Object.assign(pc, function () { var onEnd = function () { self._session = null; self._referenceSpace = null; - self._inputSources = []; self._pose = null; self.views = []; self._width = 0; @@ -284,7 +272,6 @@ 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 @@ -295,7 +282,6 @@ Object.assign(pc, function () { session.addEventListener('end', onEnd); session.addEventListener('visibilitychange', onVisibilityChange); - session.addEventListener('inputsourceschange', onInputSourcesChange); this._camera.on('set_nearClip', onClipPlanesChange); this._camera.on('set_farClip', onClipPlanesChange); @@ -321,18 +307,6 @@ Object.assign(pc, function () { }); }; - 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 +328,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 +375,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 +404,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', { From 47653a3f7555eff16a687c0975e4b2c852080b0a Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Thu, 13 Feb 2020 11:38:25 +0200 Subject: [PATCH 02/18] initial XR input sources PR --- build/dependencies.txt | 3 +- src/input/xr.js | 178 -------------------------------------- src/xr/xr-input-source.js | 100 +++++++++++++++++++++ src/xr/xr-input.js | 129 +++++++++++++++++++++++++++ src/xr/xr-manager.js | 2 +- 5 files changed, 232 insertions(+), 180 deletions(-) delete mode 100644 src/input/xr.js create mode 100644 src/xr/xr-input-source.js create mode 100644 src/xr/xr-input.js diff --git a/build/dependencies.txt b/build/dependencies.txt index 765030e7979..32b4424b81a 100644 --- a/build/dependencies.txt +++ b/build/dependencies.txt @@ -109,10 +109,11 @@ ../src/input/touch.js ../src/input/controller.js ../src/input/element-input.js -../src/input/xr.js ../src/vr/vr-manager.js ../src/vr/vr-display.js ../src/xr/xr-manager.js +../src/xr/xr-input.js +../src/xr/xr-input-source.js ../src/net/http.js ../src/script/script-registry.js ../src/script/script.js diff --git a/src/input/xr.js b/src/input/xr.js deleted file mode 100644 index 5ae4736fef1..00000000000 --- a/src/input/xr.js +++ /dev/null @@ -1,178 +0,0 @@ -Object.assign(pc, function () { - var inputSourceId = 0; - var quatA = new pc.Quat(); - - var XrInputSource = function(inputSource) { - pc.EventHandler.call(this); - - this.id = ++inputSourceId; - this._inputSource = inputSource; - - this.type = ''; - - this.ray = new pc.Ray(); - this.grip = null; - }; - XrInputSource.prototype = Object.create(pc.EventHandler.prototype); - XrInputSource.prototype.constructor = XrInputSource; - - - var XrInputSources = function (manager) { - pc.EventHandler.call(this); - - var self = this; - - this.manager = manager; - this._session = null; - this._sources = [ ]; - - this._onInputSourcesChangeEvt = function(evt) { - self._onInputSourcesChange(evt); - }; - - this.manager.on('start', this._onSessionStart, this); - this.manager.on('end', this._onSessionEnd, this); - - this.on('add', function(inputSource) { - console.log('add', inputSource); - }); - - this.on('remove', function(inputSource) { - console.log('remove', inputSource); - }); - }; - XrInputSources.prototype = Object.create(pc.EventHandler.prototype); - XrInputSources.prototype.constructor = XrInputSources; - - // events - // add - // remove - - XrInputSources.prototype._onSessionStart = function () { - this._session = this.manager.session; - this._session.addEventListener('inputsourceschange', this._onInputSourcesChangeEvt); - - var self = this; - - this._session.addEventListener("select", function(evt) { - console.log('select', evt); - - var inputSourcePose = evt.frame.getPose(evt.inputSource.targetRaySpace, self.manager._referenceSpace); - if (inputSourcePose) { - console.log(inputSourcePose); - } - }); - this._session.addEventListener("selectstart", function(evt) { - console.log('selectstart', evt); - }); - this._session.addEventListener("selectend", function(evt) { - console.log('selectend', evt); - }); - - // add input sources - var sources = this._session.inputSources; - for(var i = 0; i < sources.length; i++) { - this._addInputSource(sources[i]); - } - }; - - XrInputSources.prototype._onSessionEnd = function () { - // todo - // fire remove event on all input sources - var i = this._sources.length; - while(i--) { - var source = this._sources[i]; - this._sources.splice(i, 1); - source.fire('remove'); - this.fire('remove', source); - } - - this._session.removeEventListener('inputsourceschange', this._onInputSourcesChangeEvt); - this._session = null; - }; - - XrInputSources.prototype._onInputSourcesChange = function (evt) { - console.log('inputsourceschange', evt); - - // remove - for(var i = 0; i < evt.removed.length; i++) { - this._removeInputSource(evt.removed[i]); - } - - // add - for(var i = 0; i < evt.added.length; i++) { - this._addInputSource(evt.added[i]); - } - }; - - XrInputSources.prototype._addInputSource = function (inputSource) { - var source = new XrInputSource(inputSource); - this._sources.push(source); - this.fire('add', source); - }; - - XrInputSources.prototype._removeInputSource = function (inputSource) { - for(var i = 0; i < this._sources.length; i++) { - if (this._sources[i]._inputSource !== inputSource) - continue; - - var source = this._sources[i]; - this._sources.splice(i, 1); - source.fire('remove'); - this.fire('remove', source); - return; - } - }; - - XrInputSources.prototype.update = function(frame) { - for(var i = 0; i < this._sources.length; i++) { - var source = this._sources[i]; - var inputSource = source._inputSource; - - var targetRayPose = frame.getPose(inputSource.targetRaySpace, this.manager._referenceSpace); - - if (! targetRayPose) { - console.log('no targetRayPose', i); - continue; - } - - // if (inputSource.targetRayMode === 'gaze') { - // if (! source.ray) source.ray = new pc.Ray(); - - source.ray.origin.copy(targetRayPose.transform.position); - - source.ray.direction.set(0, 0, -1); - quatA.copy(targetRayPose.transform.orientation); - quatA.transformVector(source.ray.direction, source.ray.direction); - - // } else if (inputSource.targetRayMode === 'tracked-pointer') { - // if (! source.ray) source.ray = new pc.Ray(); - // - // source.ray.origin.copy(targetRayPose.transform.position); - // - // source.ray.direction.set(0, 0, -1); - // quatA.copy(targetRayPose.transform.orientation); - // quatA.transformVector(source.ray.direction, source.ray.direction); - // } - - if (inputSource.gripSpace) { - var gripPose = frame.getPose(inputSource.gripSpace, this.manager._referenceSpace); - if (gripPose) { - if (! source.grip) { - source.grip = { - position: new pc.Vec3(), - rotation: new pc.Quat() - }; - }; - - source.grip.position.copy(gripPose.transform.position); - source.grip.rotation.copy(gripPose.transform.orientation); - } - } - } - }; - - return { - XrInputSources: XrInputSources - }; -}()); diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js new file mode 100644 index 00000000000..2e4ac46de25 --- /dev/null +++ b/src/xr/xr-input-source.js @@ -0,0 +1,100 @@ +Object.assign(pc, function () { + var inputSourceId = 0; + var quatA = new pc.Quat(); + + var XrInputSource = function(manager, inputSource) { + pc.EventHandler.call(this); + + this.id = ++inputSourceId; + this.manager = manager; + this._inputSource = inputSource; + + this._ray = new pc.Ray(); + this._grip = null; + }; + XrInputSource.prototype = Object.create(pc.EventHandler.prototype); + XrInputSource.prototype.constructor = XrInputSource; + + // EVENTS: + // remove + // select + // selectstart + // selectend + + XrInputSource.prototype.update = function(frame) { + var targetRayPose = frame.getPose(this._inputSource.targetRaySpace, this.manager._referenceSpace); + if (! targetRayPose) { + console.log('no targetRayPose', i); + return; + } + + var camera = this.manager._camera; + var parent = (camera.entity && camera.entity.parent) || null; + var parentPosition = parent && parent.getPosition(); + + // ray + this._ray.origin.copy(targetRayPose.transform.position); + // relative to XR camera parent + if (parentPosition) this._ray.origin.add(parentPosition); + + this._ray.direction.set(0, 0, -1); + quatA.copy(targetRayPose.transform.orientation); + quatA.transformVector(this._ray.direction, this._ray.direction); + + // grip + if (this._inputSource.gripSpace) { + var gripPose = frame.getPose(this._inputSource.gripSpace, this.manager._referenceSpace); + if (gripPose) { + if (! this._grip) { + this._grip = { + position: new pc.Vec3(), + rotation: new pc.Quat() + }; + }; + + this._grip.position.copy(gripPose.transform.position); + this._grip.rotation.copy(gripPose.transform.orientation); + } + } + }; + + Object.defineProperty(XrInputSource.prototype, 'inputSource', { + get: function() { + return this._inputSource; + } + }); + + Object.defineProperty(XrInputSource.prototype, 'targetRayMode', { + get: function() { + return this._inputSource.targetRayMode; + } + }); + + Object.defineProperty(XrInputSource.prototype, 'handedness', { + get: function() { + return this._inputSource.handedness; + } + }); + + Object.defineProperty(XrInputSource.prototype, 'profiles', { + get: function() { + return this._inputSource.profiles; + } + }); + + Object.defineProperty(XrInputSource.prototype, 'ray', { + get: function() { + return this._ray; + } + }); + + Object.defineProperty(XrInputSource.prototype, 'grip', { + get: function() { + return this._grip; + } + }); + + return { + XrInputSource: XrInputSource + }; +}()); diff --git a/src/xr/xr-input.js b/src/xr/xr-input.js new file mode 100644 index 00000000000..2adab73fdee --- /dev/null +++ b/src/xr/xr-input.js @@ -0,0 +1,129 @@ +Object.assign(pc, function () { + 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; + + // EVENTS: + // add + // remove + // select + // selectstart + // selectend + + 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.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.fire('selectend', evt); + self.fire('selectend', inputSource, evt); + }); + + // add input sources + var sources = this._session.inputSources; + for(var i = 0; i < sources.length; i++) { + this._addInputSource(sources[i]); + } + }; + + XrInput.prototype._onSessionEnd = function () { + var i = this._inputSources.length; + while(i--) { + var source = this._inputSources[i]; + this._inputSources.splice(i, 1); + source.fire('remove'); + this.fire('remove', source); + } + + this._session.removeEventListener('inputsourceschange', this._onInputSourcesChangeEvt); + this._session = null; + }; + + XrInput.prototype._onInputSourcesChange = function (evt) { + // remove + for(var i = 0; i < evt.removed.length; i++) { + this._removeInputSource(evt.removed[i]); + } + + // add + for(var i = 0; i < evt.added.length; i++) { + this._addInputSource(evt.added[i]); + } + }; + + XrInput.prototype._getByInputSource = function(inputSource) { + for(var i = 0; i < this._inputSources.length; i++) { + if (this._inputSources[i]._inputSource === inputSource) { + return this._inputSources[i]; + } + } + + return null; + }; + + XrInput.prototype._addInputSource = function (inputSource) { + var source = new XrInputSource(this.manager, inputSource); + this._inputSources.push(source); + this.fire('add', source); + }; + + XrInput.prototype._removeInputSource = function (inputSource) { + for(var i = 0; i < this._inputSources.length; i++) { + if (this._inputSources[i]._inputSource !== inputSource) + continue; + + var source = this._inputSources[i]; + this._inputSources.splice(i, 1); + source.fire('remove'); + this.fire('remove', source); + 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 f1bef012bba..a484cda0b44 100644 --- a/src/xr/xr-manager.js +++ b/src/xr/xr-manager.js @@ -55,7 +55,7 @@ Object.assign(pc, function () { this._session = null; this._baseLayer = null; this._referenceSpace = null; - this.input = new pc.XrInputSources(this); + this.input = new pc.XrInput(this); this._camera = null; this._pose = null; From 16e0530bc4bfb23683889187a8c68199424d3c3f Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Thu, 13 Feb 2020 12:06:02 +0200 Subject: [PATCH 03/18] added WebXR Input Sources constants --- src/xr/xr-input-source.js | 84 ++++++++++++++++++++++++++++++++++----- src/xr/xr-input.js | 30 +++++++------- src/xr/xr-manager.js | 2 - 3 files changed, 90 insertions(+), 26 deletions(-) diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index 2e4ac46de25..0e254bc59e2 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -2,7 +2,61 @@ Object.assign(pc, function () { var inputSourceId = 0; var quatA = new pc.Quat(); - var XrInputSource = function(manager, inputSource) { + + 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 - inputSource is not meant to be held in hands. + */ + XRHAND_NONE: 'none', + + /** + * @constant + * @type string + * @name pc.XRHAND_LEFT + * @description Left - indicates that InputSource is meant to be held in left hand. + */ + XRHAND_LEFT: 'left', + + /** + * @constant + * @type string + * @name pc.XRHAND_RIGHT + * @description Right - indicates that InputSource is meant to be held in right hand. + */ + XRHAND_RIGHT: 'right' + }; + + + var XrInputSource = function (manager, inputSource) { pc.EventHandler.call(this); this.id = ++inputSourceId; @@ -21,7 +75,7 @@ Object.assign(pc, function () { // selectstart // selectend - XrInputSource.prototype.update = function(frame) { + XrInputSource.prototype.update = function (frame) { var targetRayPose = frame.getPose(this._inputSource.targetRaySpace, this.manager._referenceSpace); if (! targetRayPose) { console.log('no targetRayPose', i); @@ -50,7 +104,7 @@ Object.assign(pc, function () { position: new pc.Vec3(), rotation: new pc.Quat() }; - }; + } this._grip.position.copy(gripPose.transform.position); this._grip.rotation.copy(gripPose.transform.orientation); @@ -59,42 +113,52 @@ Object.assign(pc, function () { }; Object.defineProperty(XrInputSource.prototype, 'inputSource', { - get: function() { + get: function () { return this._inputSource; } }); Object.defineProperty(XrInputSource.prototype, 'targetRayMode', { - get: function() { + get: function () { return this._inputSource.targetRayMode; } }); Object.defineProperty(XrInputSource.prototype, 'handedness', { - get: function() { + get: function () { return this._inputSource.handedness; } }); Object.defineProperty(XrInputSource.prototype, 'profiles', { - get: function() { + get: function () { return this._inputSource.profiles; } }); Object.defineProperty(XrInputSource.prototype, 'ray', { - get: function() { + get: function () { return this._ray; } }); Object.defineProperty(XrInputSource.prototype, 'grip', { - get: function() { + get: function () { return this._grip; } }); - return { + Object.defineProperty(XrInputSource.prototype, 'gamepad', { + get: function () { + return this._inputSource.gamepad || null; + } + }); + + + 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 index 2adab73fdee..1ecf0881cce 100644 --- a/src/xr/xr-input.js +++ b/src/xr/xr-input.js @@ -6,9 +6,9 @@ Object.assign(pc, function () { this.manager = manager; this._session = null; - this._inputSources = [ ]; + this._inputSources = []; - this._onInputSourcesChangeEvt = function(evt) { + this._onInputSourcesChangeEvt = function (evt) { self._onInputSourcesChange(evt); }; @@ -31,19 +31,19 @@ Object.assign(pc, function () { var self = this; - this._session.addEventListener('select', function(evt) { + 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) { + this._session.addEventListener('selectstart', function (evt) { var inputSource = self._getByInputSource(evt.inputSource); inputSource.update(evt.frame); inputSource.fire('selectstart', evt); self.fire('selectstart', inputSource, evt); }); - this._session.addEventListener('selectend', function(evt) { + this._session.addEventListener('selectend', function (evt) { var inputSource = self._getByInputSource(evt.inputSource); inputSource.update(evt.frame); inputSource.fire('selectend', evt); @@ -52,14 +52,14 @@ Object.assign(pc, function () { // add input sources var sources = this._session.inputSources; - for(var i = 0; i < sources.length; i++) { + for (var i = 0; i < sources.length; i++) { this._addInputSource(sources[i]); } }; XrInput.prototype._onSessionEnd = function () { var i = this._inputSources.length; - while(i--) { + while (i--) { var source = this._inputSources[i]; this._inputSources.splice(i, 1); source.fire('remove'); @@ -71,19 +71,21 @@ Object.assign(pc, function () { }; XrInput.prototype._onInputSourcesChange = function (evt) { + var i; + // remove - for(var i = 0; i < evt.removed.length; i++) { + for (i = 0; i < evt.removed.length; i++) { this._removeInputSource(evt.removed[i]); } // add - for(var i = 0; i < evt.added.length; i++) { + for (i = 0; i < evt.added.length; i++) { this._addInputSource(evt.added[i]); } }; - XrInput.prototype._getByInputSource = function(inputSource) { - for(var i = 0; i < this._inputSources.length; i++) { + XrInput.prototype._getByInputSource = function (inputSource) { + for (var i = 0; i < this._inputSources.length; i++) { if (this._inputSources[i]._inputSource === inputSource) { return this._inputSources[i]; } @@ -99,7 +101,7 @@ Object.assign(pc, function () { }; XrInput.prototype._removeInputSource = function (inputSource) { - for(var i = 0; i < this._inputSources.length; i++) { + for (var i = 0; i < this._inputSources.length; i++) { if (this._inputSources[i]._inputSource !== inputSource) continue; @@ -111,8 +113,8 @@ Object.assign(pc, function () { } }; - XrInput.prototype.update = function(frame) { - for(var i = 0; i < this._inputSources.length; i++) { + XrInput.prototype.update = function (frame) { + for (var i = 0; i < this._inputSources.length; i++) { this._inputSources[i].update(frame); } }; diff --git a/src/xr/xr-manager.js b/src/xr/xr-manager.js index a484cda0b44..a2ffd892d14 100644 --- a/src/xr/xr-manager.js +++ b/src/xr/xr-manager.js @@ -446,7 +446,5 @@ Object.assign(pc, function () { XrManager: XrManager }; Object.assign(obj, sessionTypes); - - return obj; }()); From 016674fd5f04eadd3096a75d83a0af212dd65b88 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Thu, 13 Feb 2020 12:12:35 +0200 Subject: [PATCH 04/18] fix --- src/xr/xr-input.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xr/xr-input.js b/src/xr/xr-input.js index 1ecf0881cce..9e051810f72 100644 --- a/src/xr/xr-input.js +++ b/src/xr/xr-input.js @@ -95,7 +95,7 @@ Object.assign(pc, function () { }; XrInput.prototype._addInputSource = function (inputSource) { - var source = new XrInputSource(this.manager, inputSource); + var source = new pc.XrInputSource(this.manager, inputSource); this._inputSources.push(source); this.fire('add', source); }; From 4e9efa47791058fa04eac21a35a92bc429ba5933 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Thu, 13 Feb 2020 14:56:43 +0200 Subject: [PATCH 05/18] document WebXR Input Sources --- src/xr/xr-input-source.js | 144 +++++++++++++++++++++++++++++--------- src/xr/xr-input.js | 102 +++++++++++++++++++++------ 2 files changed, 189 insertions(+), 57 deletions(-) diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index 0e254bc59e2..85067346239 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -1,6 +1,5 @@ Object.assign(pc, function () { - var inputSourceId = 0; - var quatA = new pc.Quat(); + var quat = new pc.Quat(); var targetRayModes = { @@ -34,7 +33,7 @@ Object.assign(pc, function () { * @constant * @type string * @name pc.XRHAND_NONE - * @description None - inputSource is not meant to be held in hands. + * @description None - input source is not meant to be held in hands. */ XRHAND_NONE: 'none', @@ -42,7 +41,7 @@ Object.assign(pc, function () { * @constant * @type string * @name pc.XRHAND_LEFT - * @description Left - indicates that InputSource is meant to be held in left hand. + * @description Left - indicates that input source is meant to be held in left hand. */ XRHAND_LEFT: 'left', @@ -50,39 +49,100 @@ Object.assign(pc, function () { * @constant * @type string * @name pc.XRHAND_RIGHT - * @description Right - indicates that InputSource is meant to be held in right hand. + * @description Right - indicates that input source is meant to be held in right hand. */ XRHAND_RIGHT: 'right' }; - - var XrInputSource = function (manager, inputSource) { + /** + * @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 {XRInputSource} xrInputSource - WebXR object that represent input source. + * @property {XRInputSource} inputSource WebXR object that represent 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. + * @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 world space position of handheld input source. + * @property {pc.Quat|null} rotation If {pc.XrInputSource#grip} is true, then rotation will represent world space rotation of handheld input source. + * @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.id = ++inputSourceId; - this.manager = manager; - this._inputSource = inputSource; + this._manager = manager; + this._xrInputSource = xrInputSource; this._ray = new pc.Ray(); - this._grip = null; + this._grip = false; + this._position = null; + this._rotation = null; + this._selecting = false; }; XrInputSource.prototype = Object.create(pc.EventHandler.prototype); XrInputSource.prototype.constructor = XrInputSource; - // EVENTS: - // remove - // select - // selectstart - // selectend + /** + * @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 {XRInputSourceEvent} evt - Raw 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 {XRInputSourceEvent} evt - Raw event data from WebXR API + */ + + /** + * @event + * @name pc.XrInputSource#selectend + * @description Fired when input source has ended triggerring primary action. + * @param {XRInputSourceEvent} evt - Raw event data from WebXR API + */ XrInputSource.prototype.update = function (frame) { - var targetRayPose = frame.getPose(this._inputSource.targetRaySpace, this.manager._referenceSpace); + var targetRayPose = frame.getPose(this._xrInputSource.targetRaySpace, this._manager._referenceSpace); if (! targetRayPose) { console.log('no targetRayPose', i); return; } - var camera = this.manager._camera; + var camera = this._manager._camera; var parent = (camera.entity && camera.entity.parent) || null; var parentPosition = parent && parent.getPosition(); @@ -92,47 +152,45 @@ Object.assign(pc, function () { if (parentPosition) this._ray.origin.add(parentPosition); this._ray.direction.set(0, 0, -1); - quatA.copy(targetRayPose.transform.orientation); - quatA.transformVector(this._ray.direction, this._ray.direction); + quat.copy(targetRayPose.transform.orientation); + quat.transformVector(this._ray.direction, this._ray.direction); // grip - if (this._inputSource.gripSpace) { - var gripPose = frame.getPose(this._inputSource.gripSpace, this.manager._referenceSpace); + if (this._xrInputSource.gripSpace) { + var gripPose = frame.getPose(this._xrInputSource.gripSpace, this._manager._referenceSpace); if (gripPose) { if (! this._grip) { - this._grip = { - position: new pc.Vec3(), - rotation: new pc.Quat() - }; + this._grip = true; + this._position = new pc.Vec3(); + this._rotation = new pc.Quat(); } - - this._grip.position.copy(gripPose.transform.position); - this._grip.rotation.copy(gripPose.transform.orientation); + this._position.copy(gripPose.transform.position); + this._rotation.copy(gripPose.transform.orientation); } } }; Object.defineProperty(XrInputSource.prototype, 'inputSource', { get: function () { - return this._inputSource; + return this._xrInputSource; } }); Object.defineProperty(XrInputSource.prototype, 'targetRayMode', { get: function () { - return this._inputSource.targetRayMode; + return this._xrInputSource.targetRayMode; } }); Object.defineProperty(XrInputSource.prototype, 'handedness', { get: function () { - return this._inputSource.handedness; + return this._xrInputSource.handedness; } }); Object.defineProperty(XrInputSource.prototype, 'profiles', { get: function () { - return this._inputSource.profiles; + return this._xrInputSource.profiles; } }); @@ -148,9 +206,27 @@ Object.assign(pc, function () { } }); + 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._inputSource.gamepad || null; + return this._xrInputSource.gamepad || null; + } + }); + + Object.defineProperty(XrInputSource.prototype, 'selecting', { + get: function () { + return this._selecting; } }); diff --git a/src/xr/xr-input.js b/src/xr/xr-input.js index 9e051810f72..a06f3a9134f 100644 --- a/src/xr/xr-input.js +++ b/src/xr/xr-input.js @@ -1,4 +1,13 @@ 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); @@ -18,12 +27,57 @@ Object.assign(pc, function () { XrInput.prototype = Object.create(pc.EventHandler.prototype); XrInput.prototype.constructor = XrInput; - // EVENTS: - // add - // remove - // select - // selectstart - // selectend + /** + * @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 {XRInputSourceEvent} evt - Raw 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 {XRInputSourceEvent} evt - Raw 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 {XRInputSourceEvent} evt - Raw event data from WebXR API + */ XrInput.prototype._onSessionStart = function () { this._session = this.manager.session; @@ -40,30 +94,32 @@ Object.assign(pc, function () { 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 sources = this._session.inputSources; - for (var i = 0; i < sources.length; i++) { - this._addInputSource(sources[i]); + 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 source = this._inputSources[i]; + var inputSource = this._inputSources[i]; this._inputSources.splice(i, 1); - source.fire('remove'); - this.fire('remove', source); + inputSource.fire('remove'); + this.fire('remove', inputSource); } this._session.removeEventListener('inputsourceschange', this._onInputSourcesChangeEvt); @@ -84,9 +140,9 @@ Object.assign(pc, function () { } }; - XrInput.prototype._getByInputSource = function (inputSource) { + XrInput.prototype._getByInputSource = function (xrInputSource) { for (var i = 0; i < this._inputSources.length; i++) { - if (this._inputSources[i]._inputSource === inputSource) { + if (this._inputSources[i].inputSource === xrInputSource) { return this._inputSources[i]; } } @@ -94,21 +150,21 @@ Object.assign(pc, function () { return null; }; - XrInput.prototype._addInputSource = function (inputSource) { - var source = new pc.XrInputSource(this.manager, inputSource); - this._inputSources.push(source); - this.fire('add', source); + XrInput.prototype._addInputSource = function (xrInputSource) { + var inputSource = new pc.XrInputSource(this.manager, xrInputSource); + this._inputSources.push(inputSource); + this.fire('add', inputSource); }; - XrInput.prototype._removeInputSource = function (inputSource) { + XrInput.prototype._removeInputSource = function (xrInputSource) { for (var i = 0; i < this._inputSources.length; i++) { - if (this._inputSources[i]._inputSource !== inputSource) + if (this._inputSources[i].inputSource !== xrInputSource) continue; - var source = this._inputSources[i]; + var inputSource = this._inputSources[i]; this._inputSources.splice(i, 1); - source.fire('remove'); - this.fire('remove', source); + inputSource.fire('remove'); + this.fire('remove', inputSource); return; } }; From f0f7fd90855c2e7bc0faa8c8db4e75bea2b23d94 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Thu, 13 Feb 2020 15:05:33 +0200 Subject: [PATCH 06/18] fix lint and build issues --- src/xr/xr-input-source.js | 15 ++++++--------- src/xr/xr-input.js | 6 +++--- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index 85067346239..b06b26cfb8d 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -61,8 +61,8 @@ Object.assign(pc, function () { * @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 {XRInputSource} xrInputSource - WebXR object that represent input source. - * @property {XRInputSource} inputSource WebXR object that represent input source. + * @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.) @@ -112,7 +112,7 @@ Object.assign(pc, function () { * @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 {XRInputSourceEvent} evt - Raw event data from WebXR API + * @param {object} evt - XRInputSourceEvent event data from WebXR API * @example * app.xr.input.on('select', function (evt) { * if (obj.intersectsRay(inputSource.ray)) { @@ -125,22 +125,19 @@ Object.assign(pc, function () { * @event * @name pc.XrInputSource#selectstart * @description Fired when input source has started to trigger primary action. - * @param {XRInputSourceEvent} evt - Raw event data from WebXR API + * @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 {XRInputSourceEvent} evt - Raw event data from WebXR API + * @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) { - console.log('no targetRayPose', i); - return; - } + if (! targetRayPose) return; var camera = this._manager._camera; var parent = (camera.entity && camera.entity.parent) || null; diff --git a/src/xr/xr-input.js b/src/xr/xr-input.js index a06f3a9134f..1924dee04a8 100644 --- a/src/xr/xr-input.js +++ b/src/xr/xr-input.js @@ -54,7 +54,7 @@ Object.assign(pc, function () { * @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 {XRInputSourceEvent} evt - Raw event data from WebXR API + * @param {object} evt - XRInputSourceEvent event data from WebXR API * @example * app.xr.input.on('select', function (inputSource, evt) { * if (obj.intersectsRay(inputSource.ray)) { @@ -68,7 +68,7 @@ Object.assign(pc, function () { * @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 {XRInputSourceEvent} evt - Raw event data from WebXR API + * @param {object} evt - XRInputSourceEvent event data from WebXR API */ /** @@ -76,7 +76,7 @@ Object.assign(pc, function () { * @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 {XRInputSourceEvent} evt - Raw event data from WebXR API + * @param {object} evt - XRInputSourceEvent event data from WebXR API */ XrInput.prototype._onSessionStart = function () { From 56e8e6f0ca24b519cb86de66959e59a9781aa5a8 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Thu, 13 Feb 2020 15:25:54 +0200 Subject: [PATCH 07/18] fix typo --- src/xr/xr-input.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xr/xr-input.js b/src/xr/xr-input.js index 1924dee04a8..f3e873a13c5 100644 --- a/src/xr/xr-input.js +++ b/src/xr/xr-input.js @@ -6,7 +6,7 @@ Object.assign(pc, function () { * @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} + * @property {pc.XrInputSource[]} inputSources List of active {pc.XrInputSource} */ var XrInput = function (manager) { pc.EventHandler.call(this); From a7db34ebdcada32d8d1d0e54d40bd93e87620ef9 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Thu, 13 Feb 2020 19:39:13 +0200 Subject: [PATCH 08/18] add xr-picking example --- examples/examples.js | 3 +- examples/xr/augmented-reality-basic.html | 12 +- examples/xr/virtual-reality-basic.html | 12 +- examples/xr/xr-picking.html | 221 +++++++++++++++++++++++ 4 files changed, 235 insertions(+), 13 deletions(-) create mode 100644 examples/xr/xr-picking.html diff --git a/examples/examples.js b/examples/examples.js index acc68380fe2..cfbd0812e26 100644 --- a/examples/examples.js +++ b/examples/examples.js @@ -77,7 +77,8 @@ var categories = [ name: "xr", examples: [ 'augmented-reality-basic', - 'virtual-reality-basic' + 'virtual-reality-basic', + 'xr-picking' ] } ]; diff --git a/examples/xr/augmented-reality-basic.html b/examples/xr/augmented-reality-basic.html index 87638986649..43c88dc5fcd 100644 --- a/examples/xr/augmented-reality-basic.html +++ b/examples/xr/augmented-reality-basic.html @@ -118,10 +118,10 @@ var activate = function () { if (app.xr.isAvailable(pc.XRTYPE_AR)) { c.camera.startXr(pc.XRTYPE_AR, function (err) { - if (err) message("WebXR Immersive AR failed to start: " + err.message); + if (err) message("Immersive AR failed to start: " + err.message); }); } else { - message("WebXR Immersive AR is not available"); + message("Immersive AR is not available"); } }; @@ -150,17 +150,17 @@ }); app.xr.on('start', function () { - message("WebXR Immersive AR session has started"); + message("Immersive AR session has started"); }); app.xr.on('end', function () { - message("WebXR Immersive AR session has ended"); + message("Immersive AR session has ended"); }); app.xr.on('available:' + pc.XRTYPE_AR, function (available) { - message("WebXR Immersive AR is now " + (available ? 'available' : 'unavailable')); + message("Immersive AR is " + (available ? 'available' : 'unavailable')); }); if (! app.xr.isAvailable(pc.XRTYPE_AR)) { - message("WebXR Immersive AR is not available"); + message("Immersive AR is not available"); } } else { message("WebXR is not supported"); diff --git a/examples/xr/virtual-reality-basic.html b/examples/xr/virtual-reality-basic.html index 7d6d34b2cb5..473cb8043d4 100644 --- a/examples/xr/virtual-reality-basic.html +++ b/examples/xr/virtual-reality-basic.html @@ -118,10 +118,10 @@ var activate = function () { if (app.xr.isAvailable(pc.XRTYPE_VR)) { c.camera.startXr(pc.XRTYPE_VR, function (err) { - if (err) message("WebXR Immersive VR failed to start: " + err.message); + if (err) message("Immersive VR failed to start: " + err.message); }); } else { - message("WebXR Immersive VR is not available"); + message("Immersive VR is not available"); } }; @@ -150,17 +150,17 @@ }); app.xr.on('start', function () { - message("WebXR Immersive VR session has started"); + message("Immersive VR session has started"); }); app.xr.on('end', function () { - message("WebXR Immersive VR session has ended"); + message("Immersive VR session has ended"); }); app.xr.on('available:' + pc.XRTYPE_VR, function (available) { - message("WebXR Immersive VR is now " + (available ? 'available' : 'unavailable')); + message("Immersive VR is " + (available ? 'available' : 'unavailable')); }); if (! app.xr.isAvailable(pc.XRTYPE_VR)) { - message("WebXR Immersive VR is not available"); + message("Immersive VR is not available"); } } else { message("WebXR is not supported"); diff --git a/examples/xr/xr-picking.html b/examples/xr/xr-picking.html new file mode 100644 index 00000000000..4b5a101e5b7 --- /dev/null +++ b/examples/xr/xr-picking.html @@ -0,0 +1,221 @@ + + + + PlayCanvas Virtual Reality + + + + + + + + + +
+

+
+ + + + + + From 4a93e441ea39cc8b8c16ae05b4d54187456ea108 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Thu, 13 Feb 2020 20:03:32 +0200 Subject: [PATCH 09/18] add vr controller example --- examples/examples.js | 3 +- examples/xr/vr-controllers.html | 209 ++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 examples/xr/vr-controllers.html diff --git a/examples/examples.js b/examples/examples.js index cfbd0812e26..f8091ee0ee4 100644 --- a/examples/examples.js +++ b/examples/examples.js @@ -78,7 +78,8 @@ var categories = [ examples: [ 'augmented-reality-basic', 'virtual-reality-basic', - 'xr-picking' + 'xr-picking', + 'vr-controllers' ] } ]; diff --git a/examples/xr/vr-controllers.html b/examples/xr/vr-controllers.html new file mode 100644 index 00000000000..9ab849a4563 --- /dev/null +++ b/examples/xr/vr-controllers.html @@ -0,0 +1,209 @@ + + + + PlayCanvas Virtual Reality + + + + + + + + + +
+

+
+ + + + + + From 4ab1525559fa79746e13a5872c0a6dc96baf03f7 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Thu, 20 Feb 2020 21:41:26 +0200 Subject: [PATCH 10/18] make XR controllers in XR local space --- src/xr/xr-input-source.js | 12 +++--------- src/xr/xr-input.js | 3 +++ src/xr/xr-manager.js | 19 +++++++++++++++---- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index b06b26cfb8d..e0c453081e2 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -76,10 +76,10 @@ Object.assign(pc, function () { * * {@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. + * @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 world space position of handheld input source. - * @property {pc.Quat|null} rotation If {pc.XrInputSource#grip} is true, then rotation will represent world space rotation of handheld input source. + * @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. */ @@ -139,14 +139,8 @@ Object.assign(pc, function () { var targetRayPose = frame.getPose(this._xrInputSource.targetRaySpace, this._manager._referenceSpace); if (! targetRayPose) return; - var camera = this._manager._camera; - var parent = (camera.entity && camera.entity.parent) || null; - var parentPosition = parent && parent.getPosition(); - // ray this._ray.origin.copy(targetRayPose.transform.position); - // relative to XR camera parent - if (parentPosition) this._ray.origin.add(parentPosition); this._ray.direction.set(0, 0, -1); quat.copy(targetRayPose.transform.orientation); diff --git a/src/xr/xr-input.js b/src/xr/xr-input.js index f3e873a13c5..9ee367667b6 100644 --- a/src/xr/xr-input.js +++ b/src/xr/xr-input.js @@ -151,6 +151,9 @@ Object.assign(pc, function () { }; 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); diff --git a/src/xr/xr-manager.js b/src/xr/xr-manager.js index a2ffd892d14..f245b9c8e2f 100644 --- a/src/xr/xr-manager.js +++ b/src/xr/xr-manager.js @@ -33,9 +33,10 @@ 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 Type of curently running XR session or null if no session is running. + * @property {pc.Entity|null} camera Active camera for which XR session is running or null. */ var XrManager = function (app) { pc.EventHandler.call(this); @@ -295,7 +296,11 @@ Object.assign(pc, function () { }); // request reference space - session.requestReferenceSpace('local').then(function (referenceSpace) { + var spaceType = 'local'; + if (this._type === pc.XRTYPE_INLINE) + spaceType = 'viewer'; + + session.requestReferenceSpace(spaceType).then(function (referenceSpace) { self._referenceSpace = referenceSpace; // old requestAnimationFrame will never be triggered, @@ -441,6 +446,12 @@ Object.assign(pc, function () { } }); + Object.defineProperty(XrManager.prototype, 'camera', { + get: function () { + return this._camera ? this._camera.entity : null; + } + }); + var obj = { XrManager: XrManager From 0809ae1fc0dcf2fe8a4f3f7323db3209358a5772 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Thu, 20 Feb 2020 22:39:26 +0200 Subject: [PATCH 11/18] updated XR examples, added VR Movement example --- examples/examples.js | 3 +- examples/xr/augmented-reality-basic.html | 2 +- examples/xr/vr-controllers.html | 15 +- examples/xr/vr-movement.html | 302 +++++++++++++++++++++++ examples/xr/xr-picking.html | 16 +- 5 files changed, 310 insertions(+), 28 deletions(-) create mode 100644 examples/xr/vr-movement.html diff --git a/examples/examples.js b/examples/examples.js index f8091ee0ee4..e35ecfadbd6 100644 --- a/examples/examples.js +++ b/examples/examples.js @@ -79,7 +79,8 @@ var categories = [ 'augmented-reality-basic', 'virtual-reality-basic', 'xr-picking', - 'vr-controllers' + 'vr-controllers', + 'vr-movement' ] } ]; diff --git a/examples/xr/augmented-reality-basic.html b/examples/xr/augmented-reality-basic.html index 43c88dc5fcd..6ac8e464d34 100644 --- a/examples/xr/augmented-reality-basic.html +++ b/examples/xr/augmented-reality-basic.html @@ -1,7 +1,7 @@ - PlayCanvas Virtual Reality + PlayCanvas Augmented Reality diff --git a/examples/xr/vr-controllers.html b/examples/xr/vr-controllers.html index 9ab849a4563..f499f56db6a 100644 --- a/examples/xr/vr-controllers.html +++ b/examples/xr/vr-controllers.html @@ -1,7 +1,7 @@ - PlayCanvas Virtual Reality + PlayCanvas VR Controllers @@ -169,23 +169,14 @@ } }); - app.xr.on('end', function () { - message("Immersive VR session has ended"); - }); - app.xr.on('available:' + pc.XRTYPE_VR, function (available) { - message("Immersive VR is " + (available ? 'available' : 'unavailable')); - }); - - if (! app.xr.isAvailable(pc.XRTYPE_VR)) { - message("Immersive VR is not available"); - } - // when new input source added app.xr.input.on('add', function(inputSource) { message("Controller Added"); createController(inputSource); }); + message("Tap on screen to enter VR, and see controllers"); + // update position and rotation for each controller app.on('update', function() { for(var i = 0; i < controllers.length; i++) { diff --git a/examples/xr/vr-movement.html b/examples/xr/vr-movement.html new file mode 100644 index 00000000000..6a99253b6c1 --- /dev/null +++ b/examples/xr/vr-movement.html @@ -0,0 +1,302 @@ + + + + PlayCanvas VR Movement + + + + + + + + + +
+

+
+ + + + + + diff --git a/examples/xr/xr-picking.html b/examples/xr/xr-picking.html index 4b5a101e5b7..493f869db55 100644 --- a/examples/xr/xr-picking.html +++ b/examples/xr/xr-picking.html @@ -1,7 +1,7 @@ - PlayCanvas Virtual Reality + PlayCanvas XR Picking @@ -152,19 +152,7 @@ } }); - app.xr.on('start', function () { - message("Pick any box"); - }); - app.xr.on('end', function () { - message("Immersive VR session has ended"); - }); - app.xr.on('available:' + pc.XRTYPE_VR, function (available) { - message("Immersive VR is " + (available ? 'available' : 'unavailable')); - }); - - if (! app.xr.isAvailable(pc.XRTYPE_VR)) { - message("Immersive VR is not available"); - } + message("Tap on screen to enter VR, and then pick objects"); // when input source is triggers select // pick closest box and change its color From 0a92274d0b6461ecd978e0db7c8cb4a13861afbc Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Fri, 21 Feb 2020 13:24:12 +0200 Subject: [PATCH 12/18] add space type to start XR session --- examples/xr/augmented-reality-basic.html | 4 +- examples/xr/virtual-reality-basic.html | 9 +- examples/xr/vr-controllers.html | 2 +- examples/xr/vr-movement.html | 2 +- examples/xr/xr-picking.html | 2 +- src/framework/components/camera/component.js | 14 ++- src/xr/xr-manager.js | 108 +++++++++++++++++-- 7 files changed, 119 insertions(+), 22 deletions(-) diff --git a/examples/xr/augmented-reality-basic.html b/examples/xr/augmented-reality-basic.html index 6ac8e464d34..6c820103d13 100644 --- a/examples/xr/augmented-reality-basic.html +++ b/examples/xr/augmented-reality-basic.html @@ -117,8 +117,8 @@ if (app.xr.supported) { var activate = function () { if (app.xr.isAvailable(pc.XRTYPE_AR)) { - c.camera.startXr(pc.XRTYPE_AR, function (err) { - if (err) message("Immersive AR failed to start: " + err.message); + c.camera.startXr(pc.XRTYPE_AR, pc.XRSPACE_VIEWER, function (err) { + if (err) message("WebXR Immersive AR failed to start: " + err.message); }); } else { message("Immersive AR is not available"); diff --git a/examples/xr/virtual-reality-basic.html b/examples/xr/virtual-reality-basic.html index 473cb8043d4..f66d08df749 100644 --- a/examples/xr/virtual-reality-basic.html +++ b/examples/xr/virtual-reality-basic.html @@ -117,8 +117,8 @@ if (app.xr.supported) { var activate = function () { if (app.xr.isAvailable(pc.XRTYPE_VR)) { - c.camera.startXr(pc.XRTYPE_VR, function (err) { - if (err) message("Immersive VR failed to start: " + err.message); + c.camera.startXr(pc.XRTYPE_VR, pc.XRSPACE_LOCAL, function (err) { + if (err) message("WebXR Immersive VR failed to start: " + err.message); }); } else { message("Immersive VR is not available"); @@ -131,7 +131,7 @@ }); if (app.touch) { - app.touch.on("touchend", function () { + app.touch.on("touchend", function (evt) { if (! app.xr.active) { // if not in VR, activate activate(); @@ -139,6 +139,9 @@ // otherwise reset camera c.camera.endXr(); } + + evt.preventDefault(); + evt.stopPropagation(); }); } diff --git a/examples/xr/vr-controllers.html b/examples/xr/vr-controllers.html index f499f56db6a..c46fd9587b4 100644 --- a/examples/xr/vr-controllers.html +++ b/examples/xr/vr-controllers.html @@ -137,7 +137,7 @@ if (app.xr.supported) { var activate = function () { if (app.xr.isAvailable(pc.XRTYPE_VR)) { - c.camera.startXr(pc.XRTYPE_VR, function (err) { + c.camera.startXr(pc.XRTYPE_VR, pc.XRSPACE_LOCAL, function (err) { if (err) message("Immersive VR failed to start: " + err.message); }); } else { diff --git a/examples/xr/vr-movement.html b/examples/xr/vr-movement.html index 6a99253b6c1..5543540b7ab 100644 --- a/examples/xr/vr-movement.html +++ b/examples/xr/vr-movement.html @@ -141,7 +141,7 @@ if (app.xr.supported) { var activate = function () { if (app.xr.isAvailable(pc.XRTYPE_VR)) { - c.camera.startXr(pc.XRTYPE_VR, function (err) { + c.camera.startXr(pc.XRTYPE_VR, pc.XRSPACE_LOCAL, function (err) { if (err) message("Immersive VR failed to start: " + err.message); }); } else { diff --git a/examples/xr/xr-picking.html b/examples/xr/xr-picking.html index 493f869db55..55ee16a7385 100644 --- a/examples/xr/xr-picking.html +++ b/examples/xr/xr-picking.html @@ -120,7 +120,7 @@ if (app.xr.supported) { var activate = function () { if (app.xr.isAvailable(pc.XRTYPE_VR)) { - c.camera.startXr(pc.XRTYPE_VR, function (err) { + c.camera.startXr(pc.XRTYPE_VR, pc.XRSPACE_LOCAL, function (err) { if (err) message("Immersive VR failed to start: " + err.message); }); } else { diff --git a/src/framework/components/camera/component.js b/src/framework/components/camera/component.js index 4d57f31bf43..99121dbfb78 100644 --- a/src/framework/components/camera/component.js +++ b/src/framework/components/camera/component.js @@ -568,10 +568,18 @@ 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 - awlays supported space with some basic tracking capabilities. + * * {@link pc.XRSPACE_LOCAL}: Local - it 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 - it 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 underlying platform. It is meant for seated or basic local XR sessions. + * * {@link pc.XRSPACE_BOUNDEDFLOOR}: Bounded Floor - it represents a tracking space with it’s native origin at the floor, where the user is expected to move within a pre-established boundary. + * * {@link pc.XRSPACE_UNBOUNDED}: Unbounded - it represents a tracking space where the user is expected to move freely around their environment, potentially even long distances from their starting point. + * * @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. * @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-manager.js b/src/xr/xr-manager.js index f245b9c8e2f..cd89c2b97e6 100644 --- a/src/xr/xr-manager.js +++ b/src/xr/xr-manager.js @@ -25,6 +25,48 @@ Object.assign(pc, function () { XRTYPE_AR: 'immersive-ar' }; + var spaceTypes = { + /** + * @constant + * @type string + * @name pc.XRSPACE_VIEWER + * @description Viewer - awlays supported space with some basic tracking capabilities. + */ + XRSPACE_VIEWER: 'viewer', + + /** + * @constant + * @type string + * @name pc.XRSPACE_LOCAL + * @description Local - it 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 - it 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 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 - it represents a tracking space with it’s 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 boundsGeometry stable relative to the user’s environment. + */ + XRSPACE_BOUNDEDFLOOR: 'bounded-floor', + + /** + * @constant + * @type string + * @name pc.XRSPACE_UNBOUNDED + * @description Unbounded - it 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 @@ -35,7 +77,8 @@ Object.assign(pc, function () { * @param {pc.Application} app - The main application. * @property {boolean} supported True if XR is supported. * @property {boolean} active True if XR session is running. - * @property {string|null} type Type of curently running XR session or null if no session is running. + * @property {string|null} type Returns type of curently 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 curently 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) { @@ -53,6 +96,7 @@ Object.assign(pc, function () { } this._type = null; + this._spaceType = null; this._session = null; this._baseLayer = null; this._referenceSpace = null; @@ -129,6 +173,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 @@ -140,13 +195,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 - awlays supported space with some basic tracking capabilities. + * * {@link pc.XRSPACE_LOCAL}: Local - it 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 - it 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 underlying platform. It is meant for seated or basic local XR sessions. + * * {@link pc.XRSPACE_BOUNDEDFLOOR}: Bounded Floor - it represents a tracking space with it’s native origin at the floor, where the user is expected to move within a pre-established boundary. + * * {@link pc.XRSPACE_UNBOUNDED}: Unbounded - it represents a tracking space where the user is expected to move freely around their environment, potentially even 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; @@ -162,6 +225,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); @@ -173,8 +237,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); }); }; @@ -240,8 +314,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; @@ -262,6 +337,7 @@ Object.assign(pc, function () { self._width = 0; self._height = 0; self._type = null; + self._spaceType = null; if (self._camera) { self._camera.off('set_nearClip', onClipPlanesChange); @@ -278,7 +354,7 @@ Object.assign(pc, function () { // so queue up new tick self.app.tick(); - self.fire('end'); + if (! failed) self.fire('end'); }; session.addEventListener('end', onEnd); @@ -296,10 +372,6 @@ Object.assign(pc, function () { }); // request reference space - var spaceType = 'local'; - if (this._type === pc.XRTYPE_INLINE) - spaceType = 'viewer'; - session.requestReferenceSpace(spaceType).then(function (referenceSpace) { self._referenceSpace = referenceSpace; @@ -309,6 +381,11 @@ 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); }); }; @@ -431,6 +508,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; @@ -457,5 +540,8 @@ Object.assign(pc, function () { XrManager: XrManager }; Object.assign(obj, sessionTypes); + Object.assign(obj, spaceTypes); + + return obj; }()); From f5dd517ae30d74ffa48b694fa7b871dd7cfae457 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Fri, 21 Feb 2020 14:54:55 +0200 Subject: [PATCH 13/18] lint fixes --- src/xr/xr-manager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/xr/xr-manager.js b/src/xr/xr-manager.js index cd89c2b97e6..a7f00d6b7fe 100644 --- a/src/xr/xr-manager.js +++ b/src/xr/xr-manager.js @@ -64,7 +64,7 @@ Object.assign(pc, function () { * @name pc.XRSPACE_UNBOUNDED * @description Unbounded - it 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', + XRSPACE_UNBOUNDED: 'unbounded' }; @@ -238,7 +238,7 @@ Object.assign(pc, function () { // 4. call makeXRCompatible, very likely will lead to context loss navigator.xr.requestSession(type, { - requiredFeatures: [ spaceType ] + requiredFeatures: [spaceType] }).then(function (session) { self._onSessionStart(session, spaceType, callback); }).catch(function (ex) { From 26cc88ed87f2e0ea9aca8befc7d699b2058abb44 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Fri, 21 Feb 2020 13:24:52 +0000 Subject: [PATCH 14/18] Doc tweaks. --- src/framework/components/camera/component.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/framework/components/camera/component.js b/src/framework/components/camera/component.js index 99121dbfb78..f780f8618e2 100644 --- a/src/framework/components/camera/component.js +++ b/src/framework/components/camera/component.js @@ -564,19 +564,19 @@ 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 {string} spaceType - reference space type. Can be one of the following: * - * * {@link pc.XRSPACE_VIEWER}: Viewer - awlays supported space with some basic tracking capabilities. - * * {@link pc.XRSPACE_LOCAL}: Local - it 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 - it 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 underlying platform. It is meant for seated or basic local XR sessions. - * * {@link pc.XRSPACE_BOUNDEDFLOOR}: Bounded Floor - it represents a tracking space with it’s native origin at the floor, where the user is expected to move within a pre-established boundary. - * * {@link pc.XRSPACE_UNBOUNDED}: Unbounded - it represents a tracking space where the user is expected to move freely around their environment, potentially even long distances from their starting point. + * * {@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 session is started. The callback has one argument Error - it is null if successfully started XR session. + * @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, pc.XRSPACE_LOCAL, function (err) { From 1a8be187b23f40d4a7d203044edd8ff88c68034d Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Fri, 21 Feb 2020 13:25:57 +0000 Subject: [PATCH 15/18] Spelling correction --- examples/xr/vr-movement.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/xr/vr-movement.html b/examples/xr/vr-movement.html index 5543540b7ab..d8955278585 100644 --- a/examples/xr/vr-movement.html +++ b/examples/xr/vr-movement.html @@ -261,7 +261,7 @@ // after movement and rotation is done // we can get camera parent position and rotation - // to affset controller ray and model + // to offset controller ray and model var parentPosition = cameraParent.getPosition(); var parentRotation = cameraParent.getRotation(); From e2ce4f386e7439a7c7ff9a9f062c74f4fdc3a079 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Fri, 21 Feb 2020 13:36:41 +0000 Subject: [PATCH 16/18] Doc tweaks --- src/xr/xr-manager.js | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/xr/xr-manager.js b/src/xr/xr-manager.js index a7f00d6b7fe..5b7f3dd7962 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,7 +22,8 @@ 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' }; @@ -30,7 +33,7 @@ Object.assign(pc, function () { * @constant * @type string * @name pc.XRSPACE_VIEWER - * @description Viewer - awlays supported space with some basic tracking capabilities. + * @description Viewer - always supported space with some basic tracking capabilities. */ XRSPACE_VIEWER: 'viewer', @@ -38,7 +41,11 @@ Object.assign(pc, function () { * @constant * @type string * @name pc.XRSPACE_LOCAL - * @description Local - it 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. + * @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', @@ -46,7 +53,12 @@ Object.assign(pc, function () { * @constant * @type string * @name pc.XRSPACE_LOCALFLOOR - * @description Local Floor - it 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 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. + * @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', @@ -54,7 +66,9 @@ Object.assign(pc, function () { * @constant * @type string * @name pc.XRSPACE_BOUNDEDFLOOR - * @description Bounded Floor - it represents a tracking space with it’s 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 boundsGeometry stable relative to the user’s environment. + * @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', @@ -62,7 +76,9 @@ Object.assign(pc, function () { * @constant * @type string * @name pc.XRSPACE_UNBOUNDED - * @description Unbounded - it 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. + * @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' }; From 54a45e3482529db77f752c47ff6cf2bc5c9a0506 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Fri, 21 Feb 2020 13:38:36 +0000 Subject: [PATCH 17/18] curently -> currently --- src/xr/xr-manager.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/xr/xr-manager.js b/src/xr/xr-manager.js index 5b7f3dd7962..176f11b24ed 100644 --- a/src/xr/xr-manager.js +++ b/src/xr/xr-manager.js @@ -93,8 +93,10 @@ Object.assign(pc, function () { * @param {pc.Application} app - The main application. * @property {boolean} supported True if XR is supported. * @property {boolean} active True if XR session is running. - * @property {string|null} type Returns type of curently 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 curently running XR session or null if no session is running. Can be any of pc.XRSPACE_*. + * @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) { From 79dffc9518255c155d9b0ea7cdd1079c49e1e514 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Fri, 21 Feb 2020 13:41:09 +0000 Subject: [PATCH 18/18] Docs tweaks --- src/xr/xr-manager.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/xr/xr-manager.js b/src/xr/xr-manager.js index 176f11b24ed..c84554d376a 100644 --- a/src/xr/xr-manager.js +++ b/src/xr/xr-manager.js @@ -215,11 +215,11 @@ Object.assign(pc, function () { * * @param {string} spaceType - reference space type. Can be one of the following: * - * * {@link pc.XRSPACE_VIEWER}: Viewer - awlays supported space with some basic tracking capabilities. - * * {@link pc.XRSPACE_LOCAL}: Local - it 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 - it 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 underlying platform. It is meant for seated or basic local XR sessions. - * * {@link pc.XRSPACE_BOUNDEDFLOOR}: Bounded Floor - it represents a tracking space with it’s native origin at the floor, where the user is expected to move within a pre-established boundary. - * * {@link pc.XRSPACE_UNBOUNDED}: Unbounded - it represents a tracking space where the user is expected to move freely around their environment, potentially even long distances from their starting point. + * * {@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 () {