Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebXR Anchors #3091

Merged
merged 38 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f94e7f7
WebXR Anchors
Maksims Apr 14, 2021
571f5d5
better wording
Maksims Apr 14, 2021
ab82296
better parameter name
Maksims Apr 14, 2021
7e018ee
fix
Maksims Apr 14, 2021
9060be7
Merge branch 'master' of github.com:playcanvas/engine into webxr-anchors
Maksims Apr 21, 2021
9cd379f
merge
Maksims May 20, 2021
1ce9230
fixes
Maksims May 20, 2021
a5c6059
lint
Maksims May 20, 2021
fc93ac2
Merge branch 'master' into webxr-anchors
Maksims Jun 15, 2021
c46e237
Update xr-manager.js
Maksims Jun 15, 2021
e138780
Update src/xr/xr-anchors.js
willeastcott Jun 15, 2021
fead641
Update src/xr/xr-anchors.js
willeastcott Jun 15, 2021
16d5603
Update src/xr/xr-anchors.js
willeastcott Jun 15, 2021
3d45f16
Update src/xr/xr-anchors.js
willeastcott Jun 15, 2021
ab7d2fa
Update src/xr/xr-anchors.js
willeastcott Jun 15, 2021
1e9106a
Update src/xr/xr-anchors.js
willeastcott Jun 15, 2021
c9a1ee0
Update src/xr/xr-anchors.js
willeastcott Jun 15, 2021
1bf096e
Update src/xr/xr-anchor.js
willeastcott Jun 15, 2021
ca749af
Update src/xr/xr-anchor.js
willeastcott Jun 15, 2021
27a2e0a
Update src/xr/xr-anchor.js
willeastcott Jun 15, 2021
b62c049
Update src/xr/xr-anchor.js
willeastcott Jun 15, 2021
56a1d85
Update src/xr/xr-anchor.js
willeastcott Jun 15, 2021
a5838ba
merge
Maksims Jun 16, 2021
b0d9495
Rename xr-anchors.js to xr-anchor.js
Maksims Jun 16, 2021
3b1f0aa
Update xr-anchors.js
Maksims Jun 16, 2021
47a2af1
Update xr-anchors.js
Maksims Jun 16, 2021
be3dca4
Merge branch 'master' of github.com:playcanvas/engine into webxr-anchors
Maksims Jun 29, 2021
0c73876
Merge branch 'webxr-anchors' of github.com:Maksims/engine-1 into webx…
Maksims Jun 29, 2021
5625bbc
fix warning
Maksims Jun 29, 2021
a907725
hide constructors from docs
Maksims Jul 1, 2021
f820ca4
merge
Maksims Aug 8, 2023
d9974bf
update PR to match recent engine guidlines and some bug fixes
Maksims Aug 8, 2023
2dc0c04
ts..
Maksims Aug 9, 2023
e5d56cd
Merge branch 'main' into webxr-anchors
Maksims Aug 9, 2023
5fad7ed
Update src/framework/xr/xr-anchors.js
Maksims Aug 10, 2023
324a623
Update src/framework/xr/xr-anchors.js
Maksims Aug 10, 2023
e3dafe3
Update src/framework/xr/xr-anchors.js
Maksims Aug 10, 2023
0cb4042
small PR corrections
Maksims Aug 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/framework/components/camera/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,7 @@ class CameraComponent extends Component {
* @param {import('../../xr/xr-manager.js').XrErrorCallback} [options.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.
* @param {boolean} [options.anchors] - Optional boolean to attempt to enable {@link XrAnchors}.
* @param {object} [options.depthSensing] - Optional object with depth sensing parameters to
* attempt to enable {@link XrDepthSensing}.
* @param {string} [options.depthSensing.usagePreference] - Optional usage preference for depth
Expand Down
119 changes: 119 additions & 0 deletions src/framework/xr/xr-anchor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { EventHandler } from '../../core/event-handler.js';

import { Vec3 } from '../../core/math/vec3.js';
import { Quat } from '../../core/math/quat.js';

/**
* An anchor keeps track of a position and rotation that is fixed relative to the real world.
* This allows the application to adjust the location of the virtual objects placed in the
* scene in a way that helps with maintaining the illusion that the placed objects are really
* present in the user’s environment.
*
* @augments EventHandler
* @category XR
*/
class XrAnchor extends EventHandler {
/**
* @type {Vec3}
* @private
*/
_position = new Vec3();

/**
* @type {Quat}
* @private
*/
_rotation = new Quat();

/**
* @param {import('./xr-anchors.js').XrAnchors} anchors - Anchor manager.
* @param {object} xrAnchor - native XRAnchor object that is provided by WebXR API
* @hideconstructor
*/
constructor(anchors, xrAnchor) {
super();

this._anchors = anchors;
this._xrAnchor = xrAnchor;
}

/**
* Fired when an {@link XrAnchor} is destroyed.
*
* @event XrAnchor#destroy
* @example
* // once anchor is destroyed
* anchor.once('destroy', function () {
* // destroy its related entity
* entity.destroy();
* });
*/

/**
* Fired when an {@link XrAnchor}'s position and/or rotation is changed.
*
* @event XrAnchor#change
* @example
* anchor.on('change', function () {
* // anchor has been updated
* entity.setPosition(anchor.getPosition());
* entity.setRotation(anchor.getRotation());
* });
*/

/**
* Destroy an anchor.
*/
destroy() {
if (!this._xrAnchor) return;
this._anchors._index.delete(this._xrAnchor);

const ind = this._anchors._list.indexOf(this);
if (ind !== -1) this._anchors._list.splice(ind, 1);

this._xrAnchor.delete();
this._xrAnchor = null;

this.fire('destroy');
this._anchors.fire('destroy', this);
}

/**
* @param {*} frame - XRFrame from requestAnimationFrame callback.
* @ignore
*/
update(frame) {
if (!this._xrAnchor)
return;

const pose = frame.getPose(this._xrAnchor.anchorSpace, this._anchors.manager._referenceSpace);
if (pose) {
if (this._position.equals(pose.transform.position) && this._rotation.equals(pose.transform.orientation))
return;

this._position.copy(pose.transform.position);
this._rotation.copy(pose.transform.orientation);
this.fire('change');
}
}

/**
* Get the world space position of an anchor.
*
* @returns {Vec3} The world space position of an anchor.
*/
getPosition() {
return this._position;
}

/**
* Get the world space rotation of an anchor.
*
* @returns {Quat} The world space rotation of an anchor.
*/
getRotation() {
return this._rotation;
}
}

export { XrAnchor };
236 changes: 236 additions & 0 deletions src/framework/xr/xr-anchors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import { EventHandler } from '../../core/event-handler.js';
import { platform } from '../../core/platform.js';
import { XrAnchor } from './xr-anchor.js';

/**
* Callback used by {@link XrAnchors#create}.
*
* @callback XrAnchorCreate
* @param {Error|null} err - The Error object if failed to create an anchor or null.
* @param {XrAnchor|null} anchor - The anchor that is tracked against real world geometry.
*/

/**
* Anchors provide an ability to specify a point in the world that needs to be updated to
* correctly reflect the evolving understanding of the world by the underlying AR system,
* such that the anchor remains aligned with the same place in the physical world.
* Anchors tend to persist better relative to the real world, especially during a longer
* session with lots of movement.
*
* ```javascript
* app.xr.start(camera, pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
* anchors: true
* });
* ```
Maksims marked this conversation as resolved.
Show resolved Hide resolved
* @augments EventHandler
* @category XR
*/
class XrAnchors extends EventHandler {
/**
* @type {boolean}
* @private
*/
_supported = platform.browser && !!window.XRAnchor;

/**
* List of anchor creation requests.
*
* @type {Array<object>}
* @private
*/
_creationQueue = [];

/**
* Index of XrAnchors, with XRAnchor (native handle) used as a key.
*
* @type {Map<XRAnchor,XrAnchor>}
* @ignore
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
*/
_index = new Map();

/**
* @type {Array<XrAnchor>}
* @ignore
*/
_list = [];

/**
* Map of callbacks to XRAnchors so that we can call its callback once
* an anchor is updated with a pose for the first time.
*
* @type {Map<XrAnchor,XrAnchorCreate>}
* @private
*/
_callbacksAnchors = new Map();

/**
* @param {import('./xr-manager.js').XrManager} manager - WebXR Manager.
* @hideconstructor
*/
constructor(manager) {
super();

this.manager = manager;

if (this._supported) {
this.manager.on('end', this._onSessionEnd, this);
}
}

/**
* Fired when anchor failed to be created.
*
* @event XrAnchors#error
* @param {Error} error - Error object related to a failure of anchors.
*/

/**
* Fired when a new {@link XrAnchor} is added.
*
* @event XrAnchors#add
* @param {XrAnchor} anchor - Anchor that has been added.
* @example
* app.xr.anchors.on('add', function (anchor) {
* // new anchor is added
* });
*/

/**
* Fired when an {@link XrAnchor} is destroyed.
*
* @event XrAnchors#destroy
* @param {XrAnchor} anchor - Anchor that has been destroyed.
* @example
* app.xr.anchors.on('destroy', function (anchor) {
* // anchor that is destroyed
* });
*/

/** @private */
_onSessionEnd() {
// clear anchor creation queue
for (let i = 0; i < this._creationQueue.length; i++) {
if (!this._creationQueue[i].callback)
continue;

this._creationQueue[i].callback(new Error('session ended'), null);
}
this._creationQueue.length = 0;

// destroy all anchors
if (this._list) {
let i = this._list.length;
while (i--) {
this._list[i].destroy();
}
this._list.length = 0;
}
}

/**
* Create anchor with position, rotation and a callback.
*
* @param {import('../../core/math/vec3.js').Vec3} position - Position for an anchor.
* @param {import('../../core/math/quat.js').Quat} [rotation] - Rotation for an anchor.
* @param {XrAnchorCreate} [callback] - Callback to fire when anchor was created or failed to be created.
* @example
* app.xr.anchors.create(position, rotation, function (err, anchor) {
* if (!err) {
* // new anchor has been created
* }
* });
*/
create(position, rotation, callback) {
this._creationQueue.push({
transform: new XRRigidTransform(position, rotation), // eslint-disable-line no-undef
callback: callback
});
}

/**
* @param {*} frame - XRFrame from requestAnimationFrame callback.
* @ignore
*/
update(frame) {
// check if need to create anchors
if (this._creationQueue.length) {
for (let i = 0; i < this._creationQueue.length; i++) {
const request = this._creationQueue[i];

frame.createAnchor(request.transform, this.manager._referenceSpace)
.then((xrAnchor) => {
if (request.callback)
this._callbacksAnchors.set(xrAnchor, request.callback);
})
.catch((ex) => {
if (request.callback)
request.callback(ex, null);

this.fire('error', ex);
});
}

this._creationQueue.length = 0;
}

// check if destroyed
for (const [xrAnchor, anchor] of this._index) {
if (frame.trackedAnchors.has(xrAnchor))
continue;

anchor.destroy();
}

// update existing anchors
for (let i = 0; i < this._list.length; i++) {
this._list[i].update(frame);
}

// check if added
for (const xrAnchor of frame.trackedAnchors) {
if (this._index.has(xrAnchor))
continue;

try {
const tmp = xrAnchor.anchorSpace; // eslint-disable-line no-unused-vars
} catch (ex) {
// if anchorSpace is not available, then anchor is invalid
// and should not be created
continue;
}

const anchor = new XrAnchor(this, xrAnchor);
this._index.set(xrAnchor, anchor);
this._list.push(anchor);
anchor.update(frame);

const callback = this._callbacksAnchors.get(xrAnchor);
if (callback) {
this._callbacksAnchors.delete(xrAnchor);
callback(null, anchor);
}

this.fire('add', anchor);
}
}

/**
* True if Anchors are supported.
*
* @type {boolean}
*/
get supported() {
return this._supported;
}

/**
* List of available {@link XrAnchor}s.
*
* @type {Array<XrAnchor>}
*/
get list() {
return this._list;
}
}

export { XrAnchors };
Loading