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 10 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
7 changes: 7 additions & 0 deletions src/callbacks.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,10 @@
* @param {Error|null} err - The Error object if failed to create hit test source or null.
* @param {XrHitTestSource|null} hitTestSource - Object that provides access to hit results against real world geometry.
*/

/**
* @callback callbacks.XrAnchorCreate
* @description Callback used by {@link XrAnchors#create}.
* @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.
*/
1 change: 1 addition & 0 deletions src/framework/components/camera/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ class CameraComponent extends Component {
* @param {callbacks.XrError} [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 sensing, can be 'cpu-optimized' or 'gpu-optimized' (XRDEPTHSENSINGUSAGE_*), defaults to 'cpu-optimized'. Most preferred and supported will be chosen by the underlying depth sensing system.
* @param {string} [options.depthSensing.dataFormatPreference] - Optional data format preference for depth sensing, can be 'luminance-alpha' or 'float32' (XRDEPTHSENSINGFORMAT_*), defaults to 'luminance-alpha'. Most preferred and supported will be chosen by the underlying depth sensing system.
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ export { XrHitTestSource } from './xr/xr-hit-test-source.js';
export { XrImageTracking } from './xr/xr-image-tracking.js';
export { XrTrackedImage } from './xr/xr-tracked-image.js';
export { XrDomOverlay } from './xr/xr-dom-overlay.js';
export { XrAnchors } from './xr/xr-anchors.js';
export { XrAnchor } from './xr/xr-anchor.js';
export { XrPlaneDetection } from './xr/xr-plane-detection.js';
export { XrPlane } from './xr/xr-plane.js';

Expand Down
104 changes: 104 additions & 0 deletions src/xr/xr-anchor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { EventHandler } from '../core/event-handler.js';

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

/**
* @class
* @name XrAnchor
* @classdesc Anchor provides position and rotation, that is updated by underlying AR system, that tries to better persist an anchor relative to evolving understanding of a real world.
* @description Anchor provides position and rotation, that is updated by underlying AR system, that tries to better persist an anchor relative to evolving understanding of a real world.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @param {XrAnchors} anchors - Anchors manager.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @property {object} xrAndhor native XRAnchor object that is provided by WebXR API.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
*/
class XrAnchor extends EventHandler {
constructor(anchors, xrAnchor) {
super();

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

this._position = new Vec3();
this._rotation = new Quat();
}

/**
* @event
* @name XrAnchor#remove
* @description Fired when {@link XrAnchor} is removed.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @example
* // once anchor is removed
* anchor.once('remove', function () {
* // destroy its related entity
* entity.destroy();
* });
*/

/**
* @event
* @name XrAnchor#change
* @description Fired when {@link XrAnchor}'s position and/or rotation is changed.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @example
* anchor.on('change', function () {
* // anchor has been updated
* entity.setPosition(anchor.getPosition());
* entity.setRotation(anchor.getRotation());
* });
*/

/**
* @function
* @name XrAnchor#remove
Maksims marked this conversation as resolved.
Show resolved Hide resolved
* @description Remove an anchor from tracking.
*/
remove() {
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('remove');
this._anchors.fire('remove', this);
}

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');
}
}

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

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

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

/**
* @class
* @name XrAnchors
* @classdesc Anchors provide an ability to specify a point in the world that need 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.
* @description Anchors provide an ability to specify a point in the world that need 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.
* @param {XrManager} manager - WebXR Manager.
* @property {boolean} supported True if Anchors are supported.
* @property {XrAnchor[]} list List of active {@link XrAnchor}'s.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @example
* app.xr.start(camera, pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
* anchors: true
* });
*/
class XrAnchors extends EventHandler {
constructor(manager) {
super();

this.manager = manager;
this._supported = !! window.XRAnchor;

// list of anchor creation requests
this._creationQueue = [];

// key - XRAnchor (native anchor does not have ID's)
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
// value - XrAnchor
this._index = new Map();
this._list = null;

// map of callbacks to XRAnchor's,
// so that we can call callback
// once anchor is first time updated with a pose
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
//
// key - XRAnchor
// value - function
this._callbacks = new Map();

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

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

/**
* @event
* @name XrAnchors#add
* @description Fired when new {@link XrAnchor} is added.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @param {XrAnchor} anchor - Anchor that has been added.
* @example
* app.xr.anchors.on('add', function (anchor) {
* // new anchor is added
* });
*/

/**
* @event
* @name XrAnchors#remove
* @description Fired when {@link XrAnchor} is removed.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @param {XrAnchor} anchor - Anchor that has been removed.
* @example
* app.xr.anchors.on('remove', function (anchor) {
* // anchor that is removed
* });
*/

_onSessionStart() {
this._list = [];
}

_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;

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

/**
* @function
* @name XrAnchors#create
* @description Create anchor with position and rotation, with a callback.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @param {Vec3} position - Position for an anchor
* @param {Quat} [rotation] - Rotastion for an anchor
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @param {callbacks.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),
callback: callback
});
}

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._callbacks.set(xrAnchor, request.callback);
})
.catch((ex) => {
if (request.callback)
request.callback(ex, null);

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

this._creationQueue.length = 0;
}

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

anchor.remove();
}

// 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;

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

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

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

get supported() {
return this._supported;
}

get list() {
return this._list;
}
}

export { XrAnchors };
12 changes: 12 additions & 0 deletions src/xr/xr-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { XrLightEstimation } from './xr-light-estimation.js';
import { XrImageTracking } from './xr-image-tracking.js';
import { XrDomOverlay } from './xr-dom-overlay.js';
import { XrDepthSensing } from './xr-depth-sensing.js';
import { XrAnchors } from './xr-anchors.js';
import { XrPlaneDetection } from './xr-plane-detection.js';

/**
Expand Down Expand Up @@ -61,6 +62,7 @@ class XrManager extends EventHandler {
this.planeDetection = new XrPlaneDetection(this);
this.input = new XrInput(this);
this.lightEstimation = new XrLightEstimation(this);
this.anchors = new XrAnchors(this);

this._camera = null;
this.views = [];
Expand Down Expand Up @@ -178,6 +180,8 @@ class XrManager extends EventHandler {
* @example
* button.on('click', function () {
* app.xr.start(camera, pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
* anchors: true,
* imageTracking: true,
* depthSensing: { }
* });
* });
Expand All @@ -186,6 +190,7 @@ class XrManager extends EventHandler {
* @param {boolean} [options.imageTracking] - Set to true to attempt to enable {@link XrImageTracking}.
* @param {boolean} [options.planeDetection] - Set to true to attempt to enable {@link XrPlaneDetection}.
* @param {callbacks.XrError} [options.callback] - Optional callback function called once session is started. The callback has one argument Error - it is null if successfully started XR session.
* @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 sensing, can be 'cpu-optimized' or 'gpu-optimized' (XRDEPTHSENSINGUSAGE_*), defaults to 'cpu-optimized'. Most preferred and supported will be chosen by the underlying depth sensing system.
* @param {string} [options.depthSensing.dataFormatPreference] - Optional data format preference for depth sensing, can be 'luminance-alpha' or 'float32' (XRDEPTHSENSINGFORMAT_*), defaults to 'luminance-alpha'. Most preferred and supported will be chosen by the underlying depth sensing system.
Expand Down Expand Up @@ -243,6 +248,10 @@ class XrManager extends EventHandler {
opts.domOverlay = { root: this.domOverlay.root };
}

if (options && options.anchors && this.anchors.supported) {
opts.optionalFeatures.push('anchors');
}

if (options && options.depthSensing && this.depthSensing.supported) {
opts.optionalFeatures.push('depth-sensing');

Expand Down Expand Up @@ -546,6 +555,9 @@ class XrManager extends EventHandler {
if (this.imageTracking.supported)
this.imageTracking.update(frame);

if (this.anchors.supported)
this.anchors.update(frame);

if (this.planeDetection.supported)
this.planeDetection.update(frame);
}
Expand Down