From 3ea4f25c5415a9d10f9e1b33354b08fcd95ac2dd Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Fri, 20 Mar 2020 05:05:21 +0200 Subject: [PATCH 01/14] webxr hit test --- build/dependencies.txt | 2 + src/callbacks.js | 7 + src/xr/xr-hit-test-source.js | 117 +++++++++++++ src/xr/xr-hit-test.js | 318 +++++++++++++++++++++++++++++++++++ src/xr/xr-input-source.js | 107 +++++++++++- src/xr/xr-input.js | 6 + src/xr/xr-manager.js | 7 + 7 files changed, 561 insertions(+), 3 deletions(-) create mode 100644 src/xr/xr-hit-test-source.js create mode 100644 src/xr/xr-hit-test.js diff --git a/build/dependencies.txt b/build/dependencies.txt index 248741aa12a..2d20b7c2b5e 100644 --- a/build/dependencies.txt +++ b/build/dependencies.txt @@ -115,6 +115,8 @@ ../src/xr/xr-manager.js ../src/xr/xr-input.js ../src/xr/xr-input-source.js +../src/xr/xr-hit-test.js +../src/xr/xr-hit-test-source.js ../src/net/http.js ../src/script/script.js ../src/script/script-type.js diff --git a/src/callbacks.js b/src/callbacks.js index 3acaa1aff73..fa39502c1bb 100644 --- a/src/callbacks.js +++ b/src/callbacks.js @@ -177,3 +177,10 @@ * @description Callback used by {@link pc.XrManager#endXr} and {@link pc.XrManager#startXr}. * @param {Error|null} err - The Error object or null if operation was successfull. */ + +/** + * @callback pc.callbacks.XrHitTestStart + * @description Callback used by {@link pc.XrHitTest#start} and {@link pc.XrHitTest#startForInputSource}. + * @param {Error|null} err - The Error object if failed to create hit test source or null. + * @param {pc.XrHitTestSource|null} hitTestSource - object that provides access to hit results against real world geometry. + */ diff --git a/src/xr/xr-hit-test-source.js b/src/xr/xr-hit-test-source.js new file mode 100644 index 00000000000..b7e9450b082 --- /dev/null +++ b/src/xr/xr-hit-test-source.js @@ -0,0 +1,117 @@ +Object.assign(pc, function () { + var poolVec3 = [ ]; + var poolQuat = [ ]; + + + /** + * @class + * @name pc.XrHitTestSource + * @augments pc.EventHandler + * @classdesc Represents XR hit test source, which provides access to hit results of real world geometry from AR session. + * @description Represents XR hit test source, which provides access to hit results of real world geometry from AR session. + * @param {pc.XrManager} manager - WebXR Manager. + * @param {object} xrHitTestSource - XRHitTestSource object that is created by WebXR API. + * @param {boolean} transient - True if XRHitTestSource created for input source profile. + * @example + * hitTestSource.on('result', function (position, rotation) { + * target.setPosition(position); + * }); + */ + var XrHitTestSource = function (manager, xrHitTestSource, transient) { + pc.EventHandler.call(this); + + this.manager = manager; + this._xrHitTestSource = xrHitTestSource; + this._transient = transient; + }; + XrHitTestSource.prototype = Object.create(pc.EventHandler.prototype); + XrHitTestSource.prototype.constructor = XrHitTestSource; + + /** + * @event + * @name pc.XrHitTestSource#remove + * @description Fired when {pc.XrHitTestSource} is removed. + * @example + * hitTestSource.once('remove', function () { + * // hit test source has been removed + * }); + */ + + /** + * @event + * @name pc.XrHitTestSource#result + * @description Fired when hit test source receives new results. It provides transform information that tries to match real world picked geometry. + * @param {pc.Vec3} position - Position of hit test + * @param {pc.Quat} rotation - Rotation of hit test + * @param {pc.XrInputSource|null} inputSource - If is transient hit test source, then it will provide related input source + * @example + * hitTestSource.on('result', function (position, rotation, inputSource) { + * target.setPosition(position); + * target.setRotation(rotation); + * }); + */ + + /** + * @function + * @name pc.XrHitTestSource#remove + * @description Stop and remove hit test source. + */ + XrHitTestSource.prototype.remove = function () { + if (! this._xrHitTestSource) + return; + + var hitTestSources = this.manager.hitTest.hitTestSources; + var ind = hitTestSources.indexOf(this); + if (ind !== -1) hitTestSources.splice(ind, 1); + + this.onStop(); + }; + + XrHitTestSource.prototype.onStop = function() { + this._xrHitTestSource.cancel(); + this._xrHitTestSource = null; + + this.fire('remove'); + this.manager.hitTest.fire('remove', this); + }; + + XrHitTestSource.prototype.update = function (frame) { + if (this._transient) { + var transientResults = frame.getHitTestResultsForTransientInput(this._xrHitTestSource); + for(var i = 0; i < transientResults.length; i++) { + var transientResult = transientResults[i]; + var inputSource; + + if (transientResult.inputSource) + inputSource = this.manager.input._getByInputSource(transientResult.inputSource); + + this.updateHitResults(transientResult.results, inputSource); + } + } else { + this.updateHitResults(frame.getHitTestResults(this._xrHitTestSource)); + } + }; + + XrHitTestSource.prototype.updateHitResults = function(results, inputSource) { + for(var i = 0; i < results.length; i++) { + var pose = results[i].getPose(this.manager._referenceSpace); + + var position = poolVec3.pop(); + if (! position) position = new pc.Vec3(); + position.copy(pose.transform.position); + + var rotation = poolQuat.pop(); + if (! rotation) rotation = new pc.Quat(); + rotation.copy(pose.transform.orientation); + + this.fire('result', position, rotation, inputSource); + this.manager.hitTest.fire('result', this, position, rotation, inputSource); + + poolVec3.push(position); + poolQuat.push(rotation); + } + }; + + + return { XrHitTestSource: XrHitTestSource }; +}()); diff --git a/src/xr/xr-hit-test.js b/src/xr/xr-hit-test.js new file mode 100644 index 00000000000..5977372130e --- /dev/null +++ b/src/xr/xr-hit-test.js @@ -0,0 +1,318 @@ +Object.assign(pc, function () { + var hitTestTrackableTypes = { + /** + * @constant + * @type string + * @name pc.XRTRACKABLE_POINT + * @description Point - indicates that the hit test results will be computed based on the feature points detected by the underlying Augmented Reality system. + */ + XRTRACKABLE_POINT: 'point', + + /** + * @constant + * @type string + * @name pc.XRTRACKABLE_PLANE + * @description Plane - indicates that the hit test results will be computed based on the planes detected by the underlying Augmented Reality system. + */ + XRTRACKABLE_PLANE: 'plane', + + /** + * @constant + * @type string + * @name pc.XRTRACKABLE_MESH + * @description Mesh - indicates that the hit test results will be computed based on the meshes detected by the underlying Augmented Reality system. + */ + XRTRACKABLE_MESH: 'mesh' + }; + + + /** + * @class + * @name pc.XrHitTest + * @augments pc.EventHandler + * @classdesc Hit Test provides ability to get position and rotation of ray intersecting point with representation of real world geometry by underlying AR system. + * @description Hit Test provides ability to get position and rotation of ray intersecting point with representation of real world geometry by underlying AR system. + * @param {pc.XrManager} manager - WebXR Manager. + * @property {boolean} supported True if AR Hit Test is supported. + * @property {pc.XrHitTestSource[]} hitTestSources list of active {pc.XrHitTestSource}. + */ + var XrHitTest = function (manager) { + pc.EventHandler.call(this); + + this.manager = manager; + this._supported = !! window.XRSession.prototype.requestHitTestSource; + + this._session = null; + + this.hitTestSources = [ ]; + + if (this._supported) { + this.manager.on('start', this._onSessionStart, this); + this.manager.on('end', this._onSessionEnd, this); + } + }; + XrHitTest.prototype = Object.create(pc.EventHandler.prototype); + XrHitTest.prototype.constructor = XrHitTest; + + /** + * @event + * @name pc.XrHitTest#add + * @description Fired when new {pc.XrHitTestSource} is added to the list. + * @param {pc.XrHitTestSource} hitTestSource - Hit test source that has been added + * @example + * app.xr.hitTest.on('add', function (hitTestSource) { + * // new hit test source is added + * }); + */ + + /** + * @event + * @name pc.XrHitTest#remove + * @description Fired when {pc.XrHitTestSource} is removed to the list. + * @param {pc.XrHitTestSource} hitTestSource - Hit test source that has been removed + * @example + * app.xr.hitTest.on('remove', function (hitTestSource) { + * // hit test source is removed + * }); + */ + + /** + * @event + * @name pc.XrHitTest#result + * @description Fired when hit test source receives new results. It provides transform information that tries to match real world picked geometry. + * @param {pc.XrHitTestSource} hitTestSource - Hit test source that produced the hit result + * @param {pc.Vec3} position - Position of hit test + * @param {pc.Quat} rotation - Rotation of hit test + * @param {pc.XrInputSource|null} inputSource - If is transient hit test source, then it will provide related input source + * @example + * app.xr.hitTest.on('result', function (hitTestSource, position, rotation, inputSource) { + * target.setPosition(position); + * target.setRotation(rotation); + * }); + */ + + XrHitTest.prototype._onSessionStart = function () { + if (this.manager.type !== pc.XRTYPE_AR) + return; + + this._session = this.manager.session; + }; + + XrHitTest.prototype._onSessionEnd = function () { + if (! this._session) + return; + + this._session = null; + + for(var i = 0; i < this.hitTestSources.length; i++) { + this.hitTestSources[i].onStop(); + } + this.hitTestSources = [ ]; + }; + + XrHitTest.prototype.isAvailable = function (callback, fireError) { + var err; + + if (! this._supported) + err = new Error('XR HitTest is not supported'); + + if (! this._session) + err = new Error('XR Session is not started (1)'); + + if (this.manager.type !== pc.XRTYPE_AR) + err = new Error('XR HitTest is available only for AR'); + + if (err) { + if (callback) callback(err); + if (fireError) fireError.fire('error', err); + return false; + } + + return true; + }; + + /** + * @function + * @name pc.XrHitTest#start + * @description Attempts to start hit test with provided reference space. + * @param {string} [spaceType] - Optional reference space type. Defaults to {pc.XRSPACE_VIEWER}. Can be one of the following: + * + * * {@link pc.XRSPACE_VIEWER}: Viewer - hit test will be facing relative to viewers space. + * * {@link pc.XRSPACE_LOCAL}: Local - represents a tracking space with a native origin near the viewer at the time of creation. + * * {@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. + * * {@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 {string[]} [entityTypes] - Optional list of underlying entity tipes against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: + * + * * {@link pc.XRTRACKABLE_POINT}: Point - indicates that the hit test results will be computed based on the feature points detected by the underlying Augmented Reality system. + * * {@link pc.XRTRACKABLE_PLANE}: Plane - indicates that the hit test results will be computed based on the planes detected by the underlying Augmented Reality system. + * * {@link pc.XRTRACKABLE_MESH}: Mesh - indicates that the hit test results will be computed based on the meshes detected by the underlying Augmented Reality system. + * + * @param {pc.Ray} [offsetRay] - Optional ray by which hit test ray can be offset. + * @param {pc.callbacks.XrHitTestStart} [callback] - Optional callback function called once hit test source is created or failed. + * @example + * app.xr.hitTest.start(function (err, hitTestSource) { + * if (err) return; + * hitTestSource.on('result', function (position, rotation) { + * // position and rotation of hit test result + * // based on default Ray facing forward from the Viewer (default space) + * }); + * }); + * @example + * var ray = new pc.Ray(new pc.Vec3(0, 0, 0), new pc.Vec3(0, -1, 0)); + * app.xr.hitTest.start(pc.XRSPACE_LOCAL, [ pc.XRTRACKABLE_PLANE ], ray, function (err, hitTestSource) { + * if (err) return; + * // hit test source that will sample real world geometry straight down + * // from the position where AR session started + * }); + */ + XrHitTest.prototype.start = function (spaceType, entityTypes, offsetRay, callback) { + var self = this; + + if (! spaceType) { + spaceType = pc.XRSPACE_VIEWER; + } else if (typeof(spaceType) === 'function') { + callback = spaceType; + spaceType = pc.XRSPACE_VIEWER; + } else if (typeof(entityTypes) === 'function') { + callback = entityTypes; + entityTypes = undefined; + } else if (typeof(offsetRay) === 'function') { + callback = offsetRay; + offsetRay = undefined; + } + + if (! this.isAvailable(callback, this)) + return; + + this._session.requestReferenceSpace(spaceType).then(function (referenceSpace) { + if (! self._session) { + var err = new Error('XR Session is not started (2)'); + if (callback) callback(err); + self.fire('error', err); + return; + } + + var xrRay = undefined; + if (offsetRay) xrRay = new XRRay(new DOMPoint(offsetRay.origin.x, offsetRay.origin.y, offsetRay.origin.z), new DOMPoint(offsetRay.direction.x, offsetRay.direction.y, offsetRay.direction.z)); + + self._session.requestHitTestSource({ + space: referenceSpace, + entityTypes: entityTypes || undefined, + offsetRay: xrRay + }).then(function (xrHitTestSource) { + if (! self._session) { + xrHitTestSource.cancel(); + var err = new Error('XR Session is not started (3)'); + if (callback) callback(err); + self.fire('error', err); + return; + } + + var hitTestSource = new pc.XrHitTestSource(self.manager, xrHitTestSource); + self.hitTestSources.push(hitTestSource); + + if (callback) callback(null, hitTestSource); + self.fire('add', hitTestSource); + }).catch(function (ex) { + if (callback) callback(ex); + self.fire('error', ex); + }); + }).catch(function (ex) { + if (callback) callback(ex); + self.fire('error', ex); + }); + }; + + /** + * @function + * @name pc.XrHitTest#startForInputSource + * @description Attempts to start hit test with provided input source profile. + * @param {string} profile - name of profile of the {pc.XrInputSource}. + * @param {string[]} [entityTypes] - Optional list of underlying entity tipes against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: + * + * * {@link pc.XRTRACKABLE_POINT}: Point - indicates that the hit test results will be computed based on the feature points detected by the underlying Augmented Reality system. + * * {@link pc.XRTRACKABLE_PLANE}: Plane - indicates that the hit test results will be computed based on the planes detected by the underlying Augmented Reality system. + * * {@link pc.XRTRACKABLE_MESH}: Mesh - indicates that the hit test results will be computed based on the meshes detected by the underlying Augmented Reality system. + * + * @param {pc.Ray} [offsetRay] - Optional ray by which hit test ray can be offset. + * @param {pc.callbacks.XrHitTestStart} [callback] - Optional callback function called once hit test source is created or failed. + * @example + * app.xr.hitTest.start('generic-touchscreen', function (err, hitTestSource) { + * if (err) return; + * hitTestSource.on('result', function (position, rotation, inputSource) { + * // position and rotation of hit test result + * // that will be created from touch on mobile devices + * }); + * }); + */ + XrHitTest.prototype.startForInputSource = function(profile, entityTypes, offsetRay, callback) { + var self = this; + + if (! profile) { + var err = new Error('missing profile'); + if (callback) callback(err); + this.fire('error', err); + return; + } else if (typeof(entityTypes) === 'function') { + callback = entityTypes; + entityTypes = undefined; + } else if (typeof(offsetRay) === 'function') { + callback = offsetRay; + offsetRay = undefined; + } + + if (! this.isAvailable(callback, this)) + return; + + var xrRay = undefined; + if (offsetRay) xrRay = new XRRay(new DOMPoint(offsetRay.origin.x, offsetRay.origin.y, offsetRay.origin.z), new DOMPoint(offsetRay.direction.x, offsetRay.direction.y, offsetRay.direction.z)); + + self._session.requestHitTestSourceForTransientInput({ + profile: profile, + entityTypes: entityTypes || undefined, + offsetRay: xrRay + }).then(function (xrHitTestSource) { + if (! self._session) { + xrHitTestSource.cancel(); + var err = new Error('XR Session is not started (3)'); + if (callback) callback(err); + self.fire('error', err); + return; + } + + var hitTestSource = new pc.XrHitTestSource(self.manager, xrHitTestSource, true); + self.hitTestSources.push(hitTestSource); + + if (callback) callback(null, hitTestSource); + self.fire('add', hitTestSource); + }).catch(function (ex) { + if (callback) callback(ex); + self.fire('error', ex); + }); + }; + + + XrHitTest.prototype.update = function (frame) { + for (var i = 0; i < this.hitTestSources.length; i++) { + this.hitTestSources[i].update(frame); + } + }; + + + Object.defineProperty(XrHitTest.prototype, 'supported', { + get: function () { + return this._supported; + } + }); + + + var obj = { + XrHitTest: XrHitTest + }; + Object.assign(obj, hitTestTrackableTypes); + + + return obj; +}()); diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index e0c453081e2..2575a862bb6 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -58,8 +58,8 @@ Object.assign(pc, function () { * @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. + * @classdesc Represents 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 XR input source, which is any input mechanism which allows the user to perform targeted actions in the same virtual space as the viewer. Example XR input sources include, but are not limited to, handheld controllers, optically tracked hands, and gaze-based input methods that operate on the viewer's pose. * @param {pc.XrManager} manager - WebXR Manager. * @param {object} xrInputSource - XRInputSource object that is created by WebXR API. * @property {object} inputSource XRInputSource object that is associated with this input source. @@ -82,6 +82,7 @@ Object.assign(pc, function () { * @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. + * @property {pc.XrHitTestSource[]} hitTestSources list of active {pc.XrHitTestSource} created by this input source. */ var XrInputSource = function (manager, xrInputSource) { pc.EventHandler.call(this); @@ -94,6 +95,8 @@ Object.assign(pc, function () { this._position = null; this._rotation = null; this._selecting = false; + + this._hitTestSources = [ ]; }; XrInputSource.prototype = Object.create(pc.EventHandler.prototype); XrInputSource.prototype.constructor = XrInputSource; @@ -103,7 +106,7 @@ Object.assign(pc, function () { * @name pc.XrInputSource#remove * @description Fired when {pc.XrInputSource} is removed. * @example - * inputSource.on('remove', function () { + * inputSource.once('remove', function () { * // input source is not available anymore * }); */ @@ -135,6 +138,42 @@ Object.assign(pc, function () { * @param {object} evt - XRInputSourceEvent event data from WebXR API */ + /** + * @event + * @name pc.XrInputSource#hittest:add + * @description Fired when new {pc.XrHitTestSource} is added to the input source. + * @param {pc.XrHitTestSource} hitTestSource - Hit test source that has been added + * @example + * inputSource.on('hittest:add', function (hitTestSource) { + * // new hit test source is added + * }); + */ + + /** + * @event + * @name pc.XrInputSource#hittest:remove + * @description Fired when {pc.XrHitTestSource} is removed to the the input source. + * @param {pc.XrHitTestSource} hitTestSource - Hit test source that has been removed + * @example + * inputSource.on('remove', function (hitTestSource) { + * // hit test source is removed + * }); + */ + + /** + * @event + * @name pc.XrInputSource#hittest:result + * @description Fired when hit test source receives new results. It provides transform information that tries to match real world picked geometry. + * @param {pc.XrHitTestSource} hitTestSource - Hit test source that produced the hit result + * @param {pc.Vec3} position - Position of hit test + * @param {pc.Quat} rotation - Rotation of hit test + * @example + * inputSource.on('hittest:result', function (hitTestSource, position, rotation) { + * target.setPosition(position); + * target.setRotation(rotation); + * }); + */ + XrInputSource.prototype.update = function (frame) { var targetRayPose = frame.getPose(this._xrInputSource.targetRaySpace, this._manager._referenceSpace); if (! targetRayPose) return; @@ -161,6 +200,62 @@ Object.assign(pc, function () { } }; + /** + * @function + * @name pc.XrInputSource#hitTestStart + * @description Attempts to start hit test source based on this input source. + * @param {string[]} [entityTypes] - Optional list of underlying entity tipes against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: + * + * * {@link pc.XRTRACKABLE_POINT}: Point - indicates that the hit test results will be computed based on the feature points detected by the underlying Augmented Reality system. + * * {@link pc.XRTRACKABLE_PLANE}: Plane - indicates that the hit test results will be computed based on the planes detected by the underlying Augmented Reality system. + * * {@link pc.XRTRACKABLE_MESH}: Mesh - indicates that the hit test results will be computed based on the meshes detected by the underlying Augmented Reality system. + * + * @param {pc.Ray} [offsetRay] - Optional ray by which hit test ray can be offset. + * @param {pc.callbacks.XrHitTestStart} [callback] - Optional callback function called once hit test source is created or failed. + * @example + * app.xr.input.on('add', function (inputSource) { + * inputSource.hitTestStart(function (err, hitTestSource) { + * if (err) return; + * hitTestSource.on('result', function (position, rotation) { + * // position and rotation of hit test result + * // that will be created from touch on mobile devices + * }); + * }); + * }); + */ + XrInputSource.prototype.hitTestStart = function (entityTypes, offsetRay, callback) { + var self = this; + + this._manager.hitTest.startForInputSource(this._xrInputSource.profiles[0], entityTypes, offsetRay, function (err, hitTestSource) { + if (hitTestSource) self.onHitTestSourceAdd(hitTestSource); + if (callback) callback(err, hitTestSource); + }); + }; + + XrInputSource.prototype.onHitTestSourceAdd = function (hitTestSource) { + var self = this; + + this._hitTestSources.push(hitTestSource); + + this.fire('hittest:add', hitTestSource); + + hitTestSource.on('result', function(position, rotation, inputSource) { + if (inputSource !== this) + return; + + this.fire('hittest:result', hitTestSource, position, rotation); + }, this); + hitTestSource.once('remove', function() { + this.onHitTestSourceRemove(hitTestSource); + this.fire('hittest:remove', hitTestSource); + }, this); + }; + + XrInputSource.prototype.onHitTestSourceRemove = function (hitTestSource) { + var ind = this._hitTestSources.indexOf(hitTestSource); + if (ind !== -1) this._hitTestSources.splice(ind, 1); + }; + Object.defineProperty(XrInputSource.prototype, 'inputSource', { get: function () { return this._xrInputSource; @@ -221,6 +316,12 @@ Object.assign(pc, function () { } }); + Object.defineProperty(XrInputSource.prototype, 'hitTestSources', { + get: function () { + return this._hitTestSources; + } + }); + var obj = { XrInputSource: XrInputSource diff --git a/src/xr/xr-input.js b/src/xr/xr-input.js index 9ee367667b6..dda066ec5e0 100644 --- a/src/xr/xr-input.js +++ b/src/xr/xr-input.js @@ -166,6 +166,12 @@ Object.assign(pc, function () { var inputSource = this._inputSources[i]; this._inputSources.splice(i, 1); + + var h = inputSource.hitTestSources.length; + while(h--) { + inputSource.hitTestSources[h].remove(); + } + inputSource.fire('remove'); this.fire('remove', inputSource); return; diff --git a/src/xr/xr-manager.js b/src/xr/xr-manager.js index 39a29d120f3..1f8578274aa 100644 --- a/src/xr/xr-manager.js +++ b/src/xr/xr-manager.js @@ -98,6 +98,8 @@ Object.assign(pc, function () { * @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. + * @property {pc.XrInput} input provides access to Input Sources. + * @property {pc.XrHitTest} hitTest provides ability to hit test representation of real world geometry of underlying AR system. */ var XrManager = function (app) { pc.EventHandler.call(this); @@ -118,7 +120,9 @@ Object.assign(pc, function () { this._session = null; this._baseLayer = null; this._referenceSpace = null; + this.input = new pc.XrInput(this); + this.hitTest = new pc.XrHitTest(this); this._camera = null; this._pose = null; @@ -504,6 +508,9 @@ Object.assign(pc, function () { this._camera.camera._node.setLocalRotation(this.rotation); this.input.update(frame); + + if (this._type === pc.XRTYPE_AR && this.hitTest.supported) + this.hitTest.update(frame); }; Object.defineProperty(XrManager.prototype, 'supported', { From 68b740a860cb2abd8ddff1dcdbec7332718b6efc Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Fri, 20 Mar 2020 05:10:28 +0200 Subject: [PATCH 02/14] lint fixes --- src/xr/xr-hit-test-source.js | 12 ++++++------ src/xr/xr-hit-test.js | 15 +++++++-------- src/xr/xr-input-source.js | 8 +++----- src/xr/xr-input.js | 4 ++-- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/xr/xr-hit-test-source.js b/src/xr/xr-hit-test-source.js index b7e9450b082..6a24b256b9e 100644 --- a/src/xr/xr-hit-test-source.js +++ b/src/xr/xr-hit-test-source.js @@ -1,6 +1,6 @@ Object.assign(pc, function () { - var poolVec3 = [ ]; - var poolQuat = [ ]; + var poolVec3 = []; + var poolQuat = []; /** @@ -67,7 +67,7 @@ Object.assign(pc, function () { this.onStop(); }; - XrHitTestSource.prototype.onStop = function() { + XrHitTestSource.prototype.onStop = function () { this._xrHitTestSource.cancel(); this._xrHitTestSource = null; @@ -78,7 +78,7 @@ Object.assign(pc, function () { XrHitTestSource.prototype.update = function (frame) { if (this._transient) { var transientResults = frame.getHitTestResultsForTransientInput(this._xrHitTestSource); - for(var i = 0; i < transientResults.length; i++) { + for (var i = 0; i < transientResults.length; i++) { var transientResult = transientResults[i]; var inputSource; @@ -92,8 +92,8 @@ Object.assign(pc, function () { } }; - XrHitTestSource.prototype.updateHitResults = function(results, inputSource) { - for(var i = 0; i < results.length; i++) { + XrHitTestSource.prototype.updateHitResults = function (results, inputSource) { + for (var i = 0; i < results.length; i++) { var pose = results[i].getPose(this.manager._referenceSpace); var position = poolVec3.pop(); diff --git a/src/xr/xr-hit-test.js b/src/xr/xr-hit-test.js index 5977372130e..e5bba5ae014 100644 --- a/src/xr/xr-hit-test.js +++ b/src/xr/xr-hit-test.js @@ -44,7 +44,7 @@ Object.assign(pc, function () { this._session = null; - this.hitTestSources = [ ]; + this.hitTestSources = []; if (this._supported) { this.manager.on('start', this._onSessionStart, this); @@ -104,10 +104,10 @@ Object.assign(pc, function () { this._session = null; - for(var i = 0; i < this.hitTestSources.length; i++) { + for (var i = 0; i < this.hitTestSources.length; i++) { this.hitTestSources[i].onStop(); } - this.hitTestSources = [ ]; + this.hitTestSources = []; }; XrHitTest.prototype.isAvailable = function (callback, fireError) { @@ -161,8 +161,7 @@ Object.assign(pc, function () { * }); * @example * var ray = new pc.Ray(new pc.Vec3(0, 0, 0), new pc.Vec3(0, -1, 0)); - * app.xr.hitTest.start(pc.XRSPACE_LOCAL, [ pc.XRTRACKABLE_PLANE ], ray, function (err, hitTestSource) { - * if (err) return; + * app.xr.hitTest.start(pc.XRSPACE_LOCAL, [pc.XRTRACKABLE_PLANE], ray, function (err, hitTestSource) { * // hit test source that will sample real world geometry straight down * // from the position where AR session started * }); @@ -194,7 +193,7 @@ Object.assign(pc, function () { return; } - var xrRay = undefined; + var xrRay; if (offsetRay) xrRay = new XRRay(new DOMPoint(offsetRay.origin.x, offsetRay.origin.y, offsetRay.origin.z), new DOMPoint(offsetRay.direction.x, offsetRay.direction.y, offsetRay.direction.z)); self._session.requestHitTestSource({ @@ -247,7 +246,7 @@ Object.assign(pc, function () { * }); * }); */ - XrHitTest.prototype.startForInputSource = function(profile, entityTypes, offsetRay, callback) { + XrHitTest.prototype.startForInputSource = function (profile, entityTypes, offsetRay, callback) { var self = this; if (! profile) { @@ -266,7 +265,7 @@ Object.assign(pc, function () { if (! this.isAvailable(callback, this)) return; - var xrRay = undefined; + var xrRay; if (offsetRay) xrRay = new XRRay(new DOMPoint(offsetRay.origin.x, offsetRay.origin.y, offsetRay.origin.z), new DOMPoint(offsetRay.direction.x, offsetRay.direction.y, offsetRay.direction.z)); self._session.requestHitTestSourceForTransientInput({ diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index 2575a862bb6..78d5d1c8a0e 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -96,7 +96,7 @@ Object.assign(pc, function () { this._rotation = null; this._selecting = false; - this._hitTestSources = [ ]; + this._hitTestSources = []; }; XrInputSource.prototype = Object.create(pc.EventHandler.prototype); XrInputSource.prototype.constructor = XrInputSource; @@ -233,19 +233,17 @@ Object.assign(pc, function () { }; XrInputSource.prototype.onHitTestSourceAdd = function (hitTestSource) { - var self = this; - this._hitTestSources.push(hitTestSource); this.fire('hittest:add', hitTestSource); - hitTestSource.on('result', function(position, rotation, inputSource) { + hitTestSource.on('result', function (position, rotation, inputSource) { if (inputSource !== this) return; this.fire('hittest:result', hitTestSource, position, rotation); }, this); - hitTestSource.once('remove', function() { + hitTestSource.once('remove', function () { this.onHitTestSourceRemove(hitTestSource); this.fire('hittest:remove', hitTestSource); }, this); diff --git a/src/xr/xr-input.js b/src/xr/xr-input.js index dda066ec5e0..d5a9b819bb1 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); @@ -168,7 +168,7 @@ Object.assign(pc, function () { this._inputSources.splice(i, 1); var h = inputSource.hitTestSources.length; - while(h--) { + while (h--) { inputSource.hitTestSources[h].remove(); } From 47632879ed8a019d446e8bf9520a8272eb4164a0 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Fri, 20 Mar 2020 05:23:08 +0200 Subject: [PATCH 03/14] add missing error event --- src/xr/xr-hit-test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/xr/xr-hit-test.js b/src/xr/xr-hit-test.js index e5bba5ae014..d48b1e50872 100644 --- a/src/xr/xr-hit-test.js +++ b/src/xr/xr-hit-test.js @@ -91,6 +91,13 @@ Object.assign(pc, function () { * }); */ + /** + * @event + * @name pc.XrHitTest#error + * @param {Error} error - Error object related to failure of creating hit test source. + * @description Fired when failed create hit test source. + */ + XrHitTest.prototype._onSessionStart = function () { if (this.manager.type !== pc.XRTYPE_AR) return; From 0a6ce7641114d4f304adc14fc12171d0b8a2d1c8 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Fri, 20 Mar 2020 06:30:30 +0200 Subject: [PATCH 04/14] fix build:all --- build/externs.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/externs.js b/build/externs.js index 0e1b86bded7..39d11f748d9 100644 --- a/build/externs.js +++ b/build/externs.js @@ -14,3 +14,5 @@ var WebAssembly = {}; // WebXR var XRWebGLLayer = {}; +var XRRay = {}; +var DOMPoint = {}; From c239ea9865aa202438c0a227a52993372cf16a39 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Fri, 20 Mar 2020 11:06:57 +0000 Subject: [PATCH 05/14] tipes -> types --- src/xr/xr-hit-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xr/xr-hit-test.js b/src/xr/xr-hit-test.js index d48b1e50872..6977df3bbb8 100644 --- a/src/xr/xr-hit-test.js +++ b/src/xr/xr-hit-test.js @@ -150,7 +150,7 @@ Object.assign(pc, function () { * * {@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 {string[]} [entityTypes] - Optional list of underlying entity tipes against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: + * @param {string[]} [entityTypes] - Optional list of underlying entity types against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: * * * {@link pc.XRTRACKABLE_POINT}: Point - indicates that the hit test results will be computed based on the feature points detected by the underlying Augmented Reality system. * * {@link pc.XRTRACKABLE_PLANE}: Plane - indicates that the hit test results will be computed based on the planes detected by the underlying Augmented Reality system. From 40dc1012e97e5be2c13f45d14cfe18c2946e3704 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Fri, 20 Mar 2020 11:15:11 +0000 Subject: [PATCH 06/14] tipes -> types --- src/xr/xr-input-source.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index 78d5d1c8a0e..9f78104a563 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -204,7 +204,7 @@ Object.assign(pc, function () { * @function * @name pc.XrInputSource#hitTestStart * @description Attempts to start hit test source based on this input source. - * @param {string[]} [entityTypes] - Optional list of underlying entity tipes against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: + * @param {string[]} [entityTypes] - Optional list of underlying entity types against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: * * * {@link pc.XRTRACKABLE_POINT}: Point - indicates that the hit test results will be computed based on the feature points detected by the underlying Augmented Reality system. * * {@link pc.XRTRACKABLE_PLANE}: Plane - indicates that the hit test results will be computed based on the planes detected by the underlying Augmented Reality system. From f6c975b74f65e561ba869c91a28f1e28c90139d8 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Fri, 20 Mar 2020 11:18:48 +0000 Subject: [PATCH 07/14] Fix links --- src/xr/xr-input.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/xr/xr-input.js b/src/xr/xr-input.js index d5a9b819bb1..9ad6c2304d2 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 {@link pc.XrInputSource}. */ var XrInput = function (manager) { pc.EventHandler.call(this); @@ -30,7 +30,7 @@ Object.assign(pc, function () { /** * @event * @name pc.XrInput#add - * @description Fired when new {pc.XrInputSource} is added to the list. + * @description Fired when new {@link 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) { @@ -41,7 +41,7 @@ Object.assign(pc, function () { /** * @event * @name pc.XrInput#remove - * @description Fired when {pc.XrInputSource} is removed to the list. + * @description Fired when {@link 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) { @@ -52,7 +52,7 @@ Object.assign(pc, function () { /** * @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. + * @description Fired when {@link pc.XrInputSource} has triggered primary action. This could be pressing a trigger button, or touching a screen. * @param {pc.XrInputSource} inputSource - Input source that triggered select event * @param {object} evt - XRInputSourceEvent event data from WebXR API * @example From 845c749c14bfc11afe6ffefbf4f35a3fb2c56521 Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Fri, 20 Mar 2020 11:21:43 +0000 Subject: [PATCH 08/14] Fix links --- src/xr/xr-input-source.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index 9f78104a563..49ef38b5719 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -76,13 +76,13 @@ 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. Its origin and direction are in local space of XR session. + * @property {pc.Ray} ray Ray that is calculated based on {@link pc.XrInputSource#targetRayMode} that can be used for interacting with virtual objects. Its origin and direction are in local space of XR session. * @property {boolean} grip If input source can be held, then it will have node with its world transformation, that can be used to position and rotate virtual joystics based on it. - * @property {pc.Vec3|null} position If {pc.XrInputSource#grip} is true, then position will represent position of handheld input source in local space of XR session. - * @property {pc.Quat|null} rotation If {pc.XrInputSource#grip} is true, then rotation will represent rotation of handheld input source in local space of XR session. + * @property {pc.Vec3|null} position If {@link 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 {@link 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. - * @property {pc.XrHitTestSource[]} hitTestSources list of active {pc.XrHitTestSource} created by this input source. + * @property {pc.XrHitTestSource[]} hitTestSources list of active {@link pc.XrHitTestSource} created by this input source. */ var XrInputSource = function (manager, xrInputSource) { pc.EventHandler.call(this); @@ -104,7 +104,7 @@ Object.assign(pc, function () { /** * @event * @name pc.XrInputSource#remove - * @description Fired when {pc.XrInputSource} is removed. + * @description Fired when {@link pc.XrInputSource} is removed. * @example * inputSource.once('remove', function () { * // input source is not available anymore @@ -141,7 +141,7 @@ Object.assign(pc, function () { /** * @event * @name pc.XrInputSource#hittest:add - * @description Fired when new {pc.XrHitTestSource} is added to the input source. + * @description Fired when new {@link pc.XrHitTestSource} is added to the input source. * @param {pc.XrHitTestSource} hitTestSource - Hit test source that has been added * @example * inputSource.on('hittest:add', function (hitTestSource) { @@ -152,7 +152,7 @@ Object.assign(pc, function () { /** * @event * @name pc.XrInputSource#hittest:remove - * @description Fired when {pc.XrHitTestSource} is removed to the the input source. + * @description Fired when {@link pc.XrHitTestSource} is removed to the the input source. * @param {pc.XrHitTestSource} hitTestSource - Hit test source that has been removed * @example * inputSource.on('remove', function (hitTestSource) { @@ -204,14 +204,20 @@ Object.assign(pc, function () { * @function * @name pc.XrInputSource#hitTestStart * @description Attempts to start hit test source based on this input source. - * @param {string[]} [entityTypes] - Optional list of underlying entity types against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: + * @param {string[]} [entityTypes] - Optional list of underlying entity types against + * which hit tests will be performed. Defaults to [ {@link pc.XRTRACKABLE_PLANE} ]. Can + * be any combination of the following: * - * * {@link pc.XRTRACKABLE_POINT}: Point - indicates that the hit test results will be computed based on the feature points detected by the underlying Augmented Reality system. - * * {@link pc.XRTRACKABLE_PLANE}: Plane - indicates that the hit test results will be computed based on the planes detected by the underlying Augmented Reality system. - * * {@link pc.XRTRACKABLE_MESH}: Mesh - indicates that the hit test results will be computed based on the meshes detected by the underlying Augmented Reality system. + * * {@link pc.XRTRACKABLE_POINT}: Point - indicates that the hit test results will be + * computed based on the feature points detected by the underlying Augmented Reality system. + * * {@link pc.XRTRACKABLE_PLANE}: Plane - indicates that the hit test results will be + * computed based on the planes detected by the underlying Augmented Reality system. + * * {@link pc.XRTRACKABLE_MESH}: Mesh - indicates that the hit test results will be + * computed based on the meshes detected by the underlying Augmented Reality system. * * @param {pc.Ray} [offsetRay] - Optional ray by which hit test ray can be offset. - * @param {pc.callbacks.XrHitTestStart} [callback] - Optional callback function called once hit test source is created or failed. + * @param {pc.callbacks.XrHitTestStart} [callback] - Optional callback function called + * once hit test source is created or failed. * @example * app.xr.input.on('add', function (inputSource) { * inputSource.hitTestStart(function (err, hitTestSource) { From 85248b63142be72e959251164b37cff9345074cf Mon Sep 17 00:00:00 2001 From: Will Eastcott Date: Fri, 20 Mar 2020 11:23:04 +0000 Subject: [PATCH 09/14] Fix links --- src/xr/xr-hit-test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/xr/xr-hit-test.js b/src/xr/xr-hit-test.js index 6977df3bbb8..c0635acda34 100644 --- a/src/xr/xr-hit-test.js +++ b/src/xr/xr-hit-test.js @@ -34,7 +34,7 @@ Object.assign(pc, function () { * @description Hit Test provides ability to get position and rotation of ray intersecting point with representation of real world geometry by underlying AR system. * @param {pc.XrManager} manager - WebXR Manager. * @property {boolean} supported True if AR Hit Test is supported. - * @property {pc.XrHitTestSource[]} hitTestSources list of active {pc.XrHitTestSource}. + * @property {pc.XrHitTestSource[]} hitTestSources list of active {@link pc.XrHitTestSource}. */ var XrHitTest = function (manager) { pc.EventHandler.call(this); @@ -57,7 +57,7 @@ Object.assign(pc, function () { /** * @event * @name pc.XrHitTest#add - * @description Fired when new {pc.XrHitTestSource} is added to the list. + * @description Fired when new {@link pc.XrHitTestSource} is added to the list. * @param {pc.XrHitTestSource} hitTestSource - Hit test source that has been added * @example * app.xr.hitTest.on('add', function (hitTestSource) { @@ -68,7 +68,7 @@ Object.assign(pc, function () { /** * @event * @name pc.XrHitTest#remove - * @description Fired when {pc.XrHitTestSource} is removed to the list. + * @description Fired when {@link pc.XrHitTestSource} is removed to the list. * @param {pc.XrHitTestSource} hitTestSource - Hit test source that has been removed * @example * app.xr.hitTest.on('remove', function (hitTestSource) { @@ -142,7 +142,7 @@ Object.assign(pc, function () { * @function * @name pc.XrHitTest#start * @description Attempts to start hit test with provided reference space. - * @param {string} [spaceType] - Optional reference space type. Defaults to {pc.XRSPACE_VIEWER}. Can be one of the following: + * @param {string} [spaceType] - Optional reference space type. Defaults to {@link pc.XRSPACE_VIEWER}. Can be one of the following: * * * {@link pc.XRSPACE_VIEWER}: Viewer - hit test will be facing relative to viewers space. * * {@link pc.XRSPACE_LOCAL}: Local - represents a tracking space with a native origin near the viewer at the time of creation. @@ -235,7 +235,7 @@ Object.assign(pc, function () { * @function * @name pc.XrHitTest#startForInputSource * @description Attempts to start hit test with provided input source profile. - * @param {string} profile - name of profile of the {pc.XrInputSource}. + * @param {string} profile - name of profile of the {@link pc.XrInputSource}. * @param {string[]} [entityTypes] - Optional list of underlying entity tipes against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: * * * {@link pc.XRTRACKABLE_POINT}: Point - indicates that the hit test results will be computed based on the feature points detected by the underlying Augmented Reality system. From 48ccc68377c2ad02697f3984a13b238cf9d09754 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Fri, 20 Mar 2020 21:56:22 +0200 Subject: [PATCH 10/14] simplify hit-test start function --- src/xr/xr-hit-test.js | 189 +++++++++++++++----------------------- src/xr/xr-input-source.js | 33 ++++--- 2 files changed, 97 insertions(+), 125 deletions(-) diff --git a/src/xr/xr-hit-test.js b/src/xr/xr-hit-test.js index d48b1e50872..672a99bbebe 100644 --- a/src/xr/xr-hit-test.js +++ b/src/xr/xr-hit-test.js @@ -142,7 +142,8 @@ Object.assign(pc, function () { * @function * @name pc.XrHitTest#start * @description Attempts to start hit test with provided reference space. - * @param {string} [spaceType] - Optional reference space type. Defaults to {pc.XRSPACE_VIEWER}. Can be one of the following: + * @param {object} [options] - Object for passing optional arguments. + * @param {string} [options.spaceType] - Reference space type. Defaults to {pc.XRSPACE_VIEWER}. Can be one of the following: * * * {@link pc.XRSPACE_VIEWER}: Viewer - hit test will be facing relative to viewers space. * * {@link pc.XRSPACE_LOCAL}: Local - represents a tracking space with a native origin near the viewer at the time of creation. @@ -150,156 +151,118 @@ Object.assign(pc, function () { * * {@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 {string[]} [entityTypes] - Optional list of underlying entity tipes against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: + * @param {string} [options.profile] - if hit test source meant to match input source instead of reference space, then name of profile of the {pc.XrInputSource} should be provided. + * @param {string[]} [options.entityTypes] - Optional list of underlying entity tipes against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: * * * {@link pc.XRTRACKABLE_POINT}: Point - indicates that the hit test results will be computed based on the feature points detected by the underlying Augmented Reality system. * * {@link pc.XRTRACKABLE_PLANE}: Plane - indicates that the hit test results will be computed based on the planes detected by the underlying Augmented Reality system. * * {@link pc.XRTRACKABLE_MESH}: Mesh - indicates that the hit test results will be computed based on the meshes detected by the underlying Augmented Reality system. * - * @param {pc.Ray} [offsetRay] - Optional ray by which hit test ray can be offset. - * @param {pc.callbacks.XrHitTestStart} [callback] - Optional callback function called once hit test source is created or failed. + * @param {pc.Ray} [options.offsetRay] - Optional ray by which hit test ray can be offset. + * @param {pc.callbacks.XrHitTestStart} [options.callback] - Optional callback function called once hit test source is created or failed. * @example - * app.xr.hitTest.start(function (err, hitTestSource) { - * if (err) return; - * hitTestSource.on('result', function (position, rotation) { - * // position and rotation of hit test result - * // based on default Ray facing forward from the Viewer (default space) - * }); + * app.xr.hitTest.start({ + * spaceType: pc.XRSPACE_VIEWER, + * callback: function (err, hitTestSource) { + * if (err) return; + * hitTestSource.on('result', function (position, rotation) { + * // position and rotation of hit test result + * // based on Ray facing forward from the Viewer reference space + * }); + * } * }); * @example * var ray = new pc.Ray(new pc.Vec3(0, 0, 0), new pc.Vec3(0, -1, 0)); - * app.xr.hitTest.start(pc.XRSPACE_LOCAL, [pc.XRTRACKABLE_PLANE], ray, function (err, hitTestSource) { - * // hit test source that will sample real world geometry straight down - * // from the position where AR session started + * app.xr.hitTest.start({ + * spaceType: pc.XRSPACE_LOCAL, + * offsetRay: ray, + * callback: function (err, hitTestSource) { + * // hit test source that will sample real world geometry straight down + * // from the position where AR session started + * } + * }); + * @example + * app.xr.hitTest.start({ + * profile: 'generic-touchscreen', + * callback: function (err, hitTestSource) { + * if (err) return; + * hitTestSource.on('result', function (position, rotation, inputSource) { + * // position and rotation of hit test result + * // that will be created from touch on mobile devices + * }); + * } * }); */ - XrHitTest.prototype.start = function (spaceType, entityTypes, offsetRay, callback) { + XrHitTest.prototype.start = function (options) { var self = this; - - if (! spaceType) { - spaceType = pc.XRSPACE_VIEWER; - } else if (typeof(spaceType) === 'function') { - callback = spaceType; - spaceType = pc.XRSPACE_VIEWER; - } else if (typeof(entityTypes) === 'function') { - callback = entityTypes; - entityTypes = undefined; - } else if (typeof(offsetRay) === 'function') { - callback = offsetRay; - offsetRay = undefined; - } + var xrRay; if (! this.isAvailable(callback, this)) return; - this._session.requestReferenceSpace(spaceType).then(function (referenceSpace) { - if (! self._session) { - var err = new Error('XR Session is not started (2)'); - if (callback) callback(err); - self.fire('error', err); - return; - } + options = options || { }; - var xrRay; - if (offsetRay) xrRay = new XRRay(new DOMPoint(offsetRay.origin.x, offsetRay.origin.y, offsetRay.origin.z), new DOMPoint(offsetRay.direction.x, offsetRay.direction.y, offsetRay.direction.z)); + if (! options.profile && ! options.spaceType) + options.spaceType = pc.XRSPACE_VIEWER; - self._session.requestHitTestSource({ - space: referenceSpace, - entityTypes: entityTypes || undefined, - offsetRay: xrRay - }).then(function (xrHitTestSource) { + var offsetRay = options.offsetRay; + if (offsetRay) xrRay = new XRRay(new DOMPoint(offsetRay.origin.x, offsetRay.origin.y, offsetRay.origin.z), new DOMPoint(offsetRay.direction.x, offsetRay.direction.y, offsetRay.direction.z)); + + var callback = options.callback; + + if (options.spaceType) { + this._session.requestReferenceSpace(options.spaceType).then(function (referenceSpace) { if (! self._session) { - xrHitTestSource.cancel(); - var err = new Error('XR Session is not started (3)'); + var err = new Error('XR Session is not started (2)'); if (callback) callback(err); self.fire('error', err); return; } - var hitTestSource = new pc.XrHitTestSource(self.manager, xrHitTestSource); - self.hitTestSources.push(hitTestSource); - - if (callback) callback(null, hitTestSource); - self.fire('add', hitTestSource); + self._session.requestHitTestSource({ + space: referenceSpace, + entityTypes: options.entityTypes || undefined, + offsetRay: xrRay + }).then(function (xrHitTestSource) { + self._onHitTestSource(xrHitTestSource, false, callback); + }).catch(function (ex) { + if (callback) callback(ex); + self.fire('error', ex); + }); }).catch(function (ex) { if (callback) callback(ex); self.fire('error', ex); }); - }).catch(function (ex) { - if (callback) callback(ex); - self.fire('error', ex); - }); + } else { + this._session.requestHitTestSourceForTransientInput({ + profile: options.profile, + entityTypes: options.entityTypes || undefined, + offsetRay: xrRay + }).then(function (xrHitTestSource) { + self._onHitTestSource(xrHitTestSource, true, callback); + }).catch(function (ex) { + if (callback) callback(ex); + self.fire('error', ex); + }); + } }; - /** - * @function - * @name pc.XrHitTest#startForInputSource - * @description Attempts to start hit test with provided input source profile. - * @param {string} profile - name of profile of the {pc.XrInputSource}. - * @param {string[]} [entityTypes] - Optional list of underlying entity tipes against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: - * - * * {@link pc.XRTRACKABLE_POINT}: Point - indicates that the hit test results will be computed based on the feature points detected by the underlying Augmented Reality system. - * * {@link pc.XRTRACKABLE_PLANE}: Plane - indicates that the hit test results will be computed based on the planes detected by the underlying Augmented Reality system. - * * {@link pc.XRTRACKABLE_MESH}: Mesh - indicates that the hit test results will be computed based on the meshes detected by the underlying Augmented Reality system. - * - * @param {pc.Ray} [offsetRay] - Optional ray by which hit test ray can be offset. - * @param {pc.callbacks.XrHitTestStart} [callback] - Optional callback function called once hit test source is created or failed. - * @example - * app.xr.hitTest.start('generic-touchscreen', function (err, hitTestSource) { - * if (err) return; - * hitTestSource.on('result', function (position, rotation, inputSource) { - * // position and rotation of hit test result - * // that will be created from touch on mobile devices - * }); - * }); - */ - XrHitTest.prototype.startForInputSource = function (profile, entityTypes, offsetRay, callback) { - var self = this; - - if (! profile) { - var err = new Error('missing profile'); + XrHitTest.prototype._onHitTestSource = function (xrHitTestSource, transient, callback) { + if (! this._session) { + xrHitTestSource.cancel(); + var err = new Error('XR Session is not started (3)'); if (callback) callback(err); this.fire('error', err); return; - } else if (typeof(entityTypes) === 'function') { - callback = entityTypes; - entityTypes = undefined; - } else if (typeof(offsetRay) === 'function') { - callback = offsetRay; - offsetRay = undefined; } - if (! this.isAvailable(callback, this)) - return; + var hitTestSource = new pc.XrHitTestSource(this.manager, xrHitTestSource, transient); + this.hitTestSources.push(hitTestSource); - var xrRay; - if (offsetRay) xrRay = new XRRay(new DOMPoint(offsetRay.origin.x, offsetRay.origin.y, offsetRay.origin.z), new DOMPoint(offsetRay.direction.x, offsetRay.direction.y, offsetRay.direction.z)); - - self._session.requestHitTestSourceForTransientInput({ - profile: profile, - entityTypes: entityTypes || undefined, - offsetRay: xrRay - }).then(function (xrHitTestSource) { - if (! self._session) { - xrHitTestSource.cancel(); - var err = new Error('XR Session is not started (3)'); - if (callback) callback(err); - self.fire('error', err); - return; - } - - var hitTestSource = new pc.XrHitTestSource(self.manager, xrHitTestSource, true); - self.hitTestSources.push(hitTestSource); - - if (callback) callback(null, hitTestSource); - self.fire('add', hitTestSource); - }).catch(function (ex) { - if (callback) callback(ex); - self.fire('error', ex); - }); + if (callback) callback(null, hitTestSource); + this.fire('add', hitTestSource); }; - XrHitTest.prototype.update = function (frame) { for (var i = 0; i < this.hitTestSources.length; i++) { this.hitTestSources[i].update(frame); diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index 78d5d1c8a0e..0f0be851733 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -204,32 +204,41 @@ Object.assign(pc, function () { * @function * @name pc.XrInputSource#hitTestStart * @description Attempts to start hit test source based on this input source. - * @param {string[]} [entityTypes] - Optional list of underlying entity tipes against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: + * @param {object} [options] - Object for passing optional arguments. + * @param {string[]} [options.entityTypes] - Optional list of underlying entity tipes against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: * * * {@link pc.XRTRACKABLE_POINT}: Point - indicates that the hit test results will be computed based on the feature points detected by the underlying Augmented Reality system. * * {@link pc.XRTRACKABLE_PLANE}: Plane - indicates that the hit test results will be computed based on the planes detected by the underlying Augmented Reality system. * * {@link pc.XRTRACKABLE_MESH}: Mesh - indicates that the hit test results will be computed based on the meshes detected by the underlying Augmented Reality system. * - * @param {pc.Ray} [offsetRay] - Optional ray by which hit test ray can be offset. - * @param {pc.callbacks.XrHitTestStart} [callback] - Optional callback function called once hit test source is created or failed. + * @param {pc.Ray} [options.offsetRay] - Optional ray by which hit test ray can be offset. + * @param {pc.callbacks.XrHitTestStart} [options.callback] - Optional callback function called once hit test source is created or failed. * @example * app.xr.input.on('add', function (inputSource) { - * inputSource.hitTestStart(function (err, hitTestSource) { - * if (err) return; - * hitTestSource.on('result', function (position, rotation) { - * // position and rotation of hit test result - * // that will be created from touch on mobile devices - * }); + * inputSource.hitTestStart({ + * callback: function (err, hitTestSource) { + * if (err) return; + * hitTestSource.on('result', function (position, rotation) { + * // position and rotation of hit test result + * // that will be created from touch on mobile devices + * }); + * } * }); * }); */ - XrInputSource.prototype.hitTestStart = function (entityTypes, offsetRay, callback) { + XrInputSource.prototype.hitTestStart = function (options) { var self = this; - this._manager.hitTest.startForInputSource(this._xrInputSource.profiles[0], entityTypes, offsetRay, function (err, hitTestSource) { + options = options || { }; + options.profile = this._xrInputSource.profiles[0]; + + var callback = options.callback; + options.callback = function (err, hitTestSource) { if (hitTestSource) self.onHitTestSourceAdd(hitTestSource); if (callback) callback(err, hitTestSource); - }); + }; + + this._manager.hitTest.start(options); }; XrInputSource.prototype.onHitTestSourceAdd = function (hitTestSource) { From dab396c57ab7a2a311532f338487b813742f6479 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Fri, 20 Mar 2020 22:08:19 +0200 Subject: [PATCH 11/14] small fixes --- src/xr/xr-hit-test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/xr/xr-hit-test.js b/src/xr/xr-hit-test.js index 567558c8d17..815636e21f3 100644 --- a/src/xr/xr-hit-test.js +++ b/src/xr/xr-hit-test.js @@ -195,16 +195,16 @@ Object.assign(pc, function () { */ XrHitTest.prototype.start = function (options) { var self = this; - var xrRay; - - if (! this.isAvailable(callback, this)) - return; options = options || { }; + if (! this.isAvailable(options.callback, this)) + return; + if (! options.profile && ! options.spaceType) options.spaceType = pc.XRSPACE_VIEWER; + var xrRay; var offsetRay = options.offsetRay; if (offsetRay) xrRay = new XRRay(new DOMPoint(offsetRay.origin.x, offsetRay.origin.y, offsetRay.origin.z), new DOMPoint(offsetRay.direction.x, offsetRay.direction.y, offsetRay.direction.z)); From 03f61d97b5a28154dbb4497b53cf2780ff54ffc3 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Fri, 20 Mar 2020 22:12:27 +0200 Subject: [PATCH 12/14] XrHitTest.hitTestSources > XrHitTest.sources --- src/xr/xr-hit-test-source.js | 6 +++--- src/xr/xr-hit-test.js | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/xr/xr-hit-test-source.js b/src/xr/xr-hit-test-source.js index 6a24b256b9e..794631eab3d 100644 --- a/src/xr/xr-hit-test-source.js +++ b/src/xr/xr-hit-test-source.js @@ -60,9 +60,9 @@ Object.assign(pc, function () { if (! this._xrHitTestSource) return; - var hitTestSources = this.manager.hitTest.hitTestSources; - var ind = hitTestSources.indexOf(this); - if (ind !== -1) hitTestSources.splice(ind, 1); + var sources = this.manager.hitTest.sources; + var ind = sources.indexOf(this); + if (ind !== -1) sources.splice(ind, 1); this.onStop(); }; diff --git a/src/xr/xr-hit-test.js b/src/xr/xr-hit-test.js index 815636e21f3..bd272331b9d 100644 --- a/src/xr/xr-hit-test.js +++ b/src/xr/xr-hit-test.js @@ -34,7 +34,7 @@ Object.assign(pc, function () { * @description Hit Test provides ability to get position and rotation of ray intersecting point with representation of real world geometry by underlying AR system. * @param {pc.XrManager} manager - WebXR Manager. * @property {boolean} supported True if AR Hit Test is supported. - * @property {pc.XrHitTestSource[]} hitTestSources list of active {@link pc.XrHitTestSource}. + * @property {pc.XrHitTestSource[]} sources list of active {@link pc.XrHitTestSource}. */ var XrHitTest = function (manager) { pc.EventHandler.call(this); @@ -44,7 +44,7 @@ Object.assign(pc, function () { this._session = null; - this.hitTestSources = []; + this.sources = []; if (this._supported) { this.manager.on('start', this._onSessionStart, this); @@ -111,10 +111,10 @@ Object.assign(pc, function () { this._session = null; - for (var i = 0; i < this.hitTestSources.length; i++) { - this.hitTestSources[i].onStop(); + for (var i = 0; i < this.sources.length; i++) { + this.sources[i].onStop(); } - this.hitTestSources = []; + this.sources = []; }; XrHitTest.prototype.isAvailable = function (callback, fireError) { @@ -257,15 +257,15 @@ Object.assign(pc, function () { } var hitTestSource = new pc.XrHitTestSource(this.manager, xrHitTestSource, transient); - this.hitTestSources.push(hitTestSource); + this.sources.push(hitTestSource); if (callback) callback(null, hitTestSource); this.fire('add', hitTestSource); }; XrHitTest.prototype.update = function (frame) { - for (var i = 0; i < this.hitTestSources.length; i++) { - this.hitTestSources[i].update(frame); + for (var i = 0; i < this.sources.length; i++) { + this.sources[i].update(frame); } }; From 82239d06929cad4969336581f68eb4810a36bf37 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Fri, 20 Mar 2020 22:33:59 +0200 Subject: [PATCH 13/14] ar hit test example --- examples/examples.js | 1 + examples/xr/ar-basic.html | 2 +- examples/xr/ar-hit-test.html | 191 +++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 examples/xr/ar-hit-test.html diff --git a/examples/examples.js b/examples/examples.js index bb0133af764..9fb6b5dcafd 100644 --- a/examples/examples.js +++ b/examples/examples.js @@ -79,6 +79,7 @@ var categories = [ name: "xr", examples: [ 'ar-basic', + 'ar-hit-test', 'vr-basic', 'vr-controllers', 'vr-movement', diff --git a/examples/xr/ar-basic.html b/examples/xr/ar-basic.html index 9c85d9f777d..d6f33ee825d 100644 --- a/examples/xr/ar-basic.html +++ b/examples/xr/ar-basic.html @@ -99,7 +99,7 @@ var createCube = function(x,y,z) { var cube = new pc.Entity(); cube.addComponent("model", { - type: "box", + type: "box" }); cube.setLocalScale(.5, .5, .5); cube.translate(x * .5, y, z * .5); diff --git a/examples/xr/ar-hit-test.html b/examples/xr/ar-hit-test.html new file mode 100644 index 00000000000..85be19de759 --- /dev/null +++ b/examples/xr/ar-hit-test.html @@ -0,0 +1,191 @@ + + + + PlayCanvas AR Hit Test + + + + + + + + + +
+

+
+ + + + + + From 2e7cef7eb2559ce6a4c4d9bb66ac04bc97af9bd5 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Sat, 21 Mar 2020 21:17:44 +0200 Subject: [PATCH 14/14] fix merge --- src/xr/xr-input-source.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index 32b6c2535b9..28a9da924e3 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -204,14 +204,10 @@ Object.assign(pc, function () { * @function * @name pc.XrInputSource#hitTestStart * @description Attempts to start hit test source based on this input source. -<<<<<<< HEAD * @param {object} [options] - Object for passing optional arguments. - * @param {string[]} [options.entityTypes] - Optional list of underlying entity tipes against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. Can be any combination of the following: -======= - * @param {string[]} [entityTypes] - Optional list of underlying entity types against - * which hit tests will be performed. Defaults to [ {@link pc.XRTRACKABLE_PLANE} ]. Can - * be any combination of the following: ->>>>>>> 85248b63142be72e959251164b37cff9345074cf + * @param {string[]} [options.entityTypes] - Optional list of underlying entity types + * against which hit tests will be performed. Defaults to [ {pc.XRTRACKABLE_PLANE} ]. + * Can be any combination of the following: * * * {@link pc.XRTRACKABLE_POINT}: Point - indicates that the hit test results will be * computed based on the feature points detected by the underlying Augmented Reality system. @@ -220,14 +216,9 @@ Object.assign(pc, function () { * * {@link pc.XRTRACKABLE_MESH}: Mesh - indicates that the hit test results will be * computed based on the meshes detected by the underlying Augmented Reality system. * -<<<<<<< HEAD * @param {pc.Ray} [options.offsetRay] - Optional ray by which hit test ray can be offset. - * @param {pc.callbacks.XrHitTestStart} [options.callback] - Optional callback function called once hit test source is created or failed. -======= - * @param {pc.Ray} [offsetRay] - Optional ray by which hit test ray can be offset. - * @param {pc.callbacks.XrHitTestStart} [callback] - Optional callback function called - * once hit test source is created or failed. ->>>>>>> 85248b63142be72e959251164b37cff9345074cf + * @param {pc.callbacks.XrHitTestStart} [options.callback] - Optional callback function + * called once hit test source is created or failed. * @example * app.xr.input.on('add', function (inputSource) { * inputSource.hitTestStart({