Skip to content

Commit dcd21ca

Browse files
WebXR Anchors (#3091)
* WebXR Anchors * better wording * better parameter name * fix * fixes * lint * Update xr-manager.js * Update src/xr/xr-anchors.js * Update src/xr/xr-anchors.js * Update src/xr/xr-anchors.js * Update src/xr/xr-anchors.js * Update src/xr/xr-anchors.js * Update src/xr/xr-anchors.js * Update src/xr/xr-anchors.js * Update src/xr/xr-anchor.js * Update src/xr/xr-anchor.js * Update src/xr/xr-anchor.js * Update src/xr/xr-anchor.js * Update src/xr/xr-anchor.js * Rename xr-anchors.js to xr-anchor.js * Update xr-anchors.js * Update xr-anchors.js * fix warning * hide constructors from docs * update PR to match recent engine guidlines and some bug fixes * ts.. * Update src/framework/xr/xr-anchors.js Co-authored-by: Will Eastcott <[email protected]> * Update src/framework/xr/xr-anchors.js Co-authored-by: Will Eastcott <[email protected]> * Update src/framework/xr/xr-anchors.js Co-authored-by: Will Eastcott <[email protected]> * small PR corrections --------- Co-authored-by: Will Eastcott <[email protected]>
1 parent f951576 commit dcd21ca

File tree

5 files changed

+372
-1
lines changed

5 files changed

+372
-1
lines changed

src/framework/components/camera/component.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,7 @@ class CameraComponent extends Component {
946946
* @param {import('../../xr/xr-manager.js').XrErrorCallback} [options.callback] - Optional
947947
* callback function called once the session is started. The callback has one argument Error -
948948
* it is null if the XR session started successfully.
949+
* @param {boolean} [options.anchors] - Optional boolean to attempt to enable {@link XrAnchors}.
949950
* @param {object} [options.depthSensing] - Optional object with depth sensing parameters to
950951
* attempt to enable {@link XrDepthSensing}.
951952
* @param {string} [options.depthSensing.usagePreference] - Optional usage preference for depth

src/framework/xr/xr-anchor.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { EventHandler } from '../../core/event-handler.js';
2+
3+
import { Vec3 } from '../../core/math/vec3.js';
4+
import { Quat } from '../../core/math/quat.js';
5+
6+
/**
7+
* An anchor keeps track of a position and rotation that is fixed relative to the real world.
8+
* This allows the application to adjust the location of the virtual objects placed in the
9+
* scene in a way that helps with maintaining the illusion that the placed objects are really
10+
* present in the user’s environment.
11+
*
12+
* @augments EventHandler
13+
* @category XR
14+
*/
15+
class XrAnchor extends EventHandler {
16+
/**
17+
* @type {Vec3}
18+
* @private
19+
*/
20+
_position = new Vec3();
21+
22+
/**
23+
* @type {Quat}
24+
* @private
25+
*/
26+
_rotation = new Quat();
27+
28+
/**
29+
* @param {import('./xr-anchors.js').XrAnchors} anchors - Anchor manager.
30+
* @param {object} xrAnchor - native XRAnchor object that is provided by WebXR API
31+
* @hideconstructor
32+
*/
33+
constructor(anchors, xrAnchor) {
34+
super();
35+
36+
this._anchors = anchors;
37+
this._xrAnchor = xrAnchor;
38+
}
39+
40+
/**
41+
* Fired when an {@link XrAnchor} is destroyed.
42+
*
43+
* @event XrAnchor#destroy
44+
* @example
45+
* // once anchor is destroyed
46+
* anchor.once('destroy', function () {
47+
* // destroy its related entity
48+
* entity.destroy();
49+
* });
50+
*/
51+
52+
/**
53+
* Fired when an {@link XrAnchor}'s position and/or rotation is changed.
54+
*
55+
* @event XrAnchor#change
56+
* @example
57+
* anchor.on('change', function () {
58+
* // anchor has been updated
59+
* entity.setPosition(anchor.getPosition());
60+
* entity.setRotation(anchor.getRotation());
61+
* });
62+
*/
63+
64+
/**
65+
* Destroy an anchor.
66+
*/
67+
destroy() {
68+
if (!this._xrAnchor) return;
69+
this._anchors._index.delete(this._xrAnchor);
70+
71+
const ind = this._anchors._list.indexOf(this);
72+
if (ind !== -1) this._anchors._list.splice(ind, 1);
73+
74+
this._xrAnchor.delete();
75+
this._xrAnchor = null;
76+
77+
this.fire('destroy');
78+
this._anchors.fire('destroy', this);
79+
}
80+
81+
/**
82+
* @param {*} frame - XRFrame from requestAnimationFrame callback.
83+
* @ignore
84+
*/
85+
update(frame) {
86+
if (!this._xrAnchor)
87+
return;
88+
89+
const pose = frame.getPose(this._xrAnchor.anchorSpace, this._anchors.manager._referenceSpace);
90+
if (pose) {
91+
if (this._position.equals(pose.transform.position) && this._rotation.equals(pose.transform.orientation))
92+
return;
93+
94+
this._position.copy(pose.transform.position);
95+
this._rotation.copy(pose.transform.orientation);
96+
this.fire('change');
97+
}
98+
}
99+
100+
/**
101+
* Get the world space position of an anchor.
102+
*
103+
* @returns {Vec3} The world space position of an anchor.
104+
*/
105+
getPosition() {
106+
return this._position;
107+
}
108+
109+
/**
110+
* Get the world space rotation of an anchor.
111+
*
112+
* @returns {Quat} The world space rotation of an anchor.
113+
*/
114+
getRotation() {
115+
return this._rotation;
116+
}
117+
}
118+
119+
export { XrAnchor };

src/framework/xr/xr-anchors.js

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
import { EventHandler } from '../../core/event-handler.js';
2+
import { platform } from '../../core/platform.js';
3+
import { XrAnchor } from './xr-anchor.js';
4+
5+
/**
6+
* Callback used by {@link XrAnchors#create}.
7+
*
8+
* @callback XrAnchorCreate
9+
* @param {Error|null} err - The Error object if failed to create an anchor or null.
10+
* @param {XrAnchor|null} anchor - The anchor that is tracked against real world geometry.
11+
*/
12+
13+
/**
14+
* Anchors provide an ability to specify a point in the world that needs to be updated to
15+
* correctly reflect the evolving understanding of the world by the underlying AR system,
16+
* such that the anchor remains aligned with the same place in the physical world.
17+
* Anchors tend to persist better relative to the real world, especially during a longer
18+
* session with lots of movement.
19+
*
20+
* ```javascript
21+
* app.xr.start(camera, pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
22+
* anchors: true
23+
* });
24+
* ```
25+
* @augments EventHandler
26+
* @category XR
27+
*/
28+
class XrAnchors extends EventHandler {
29+
/**
30+
* @type {boolean}
31+
* @private
32+
*/
33+
_supported = platform.browser && !!window.XRAnchor;
34+
35+
/**
36+
* List of anchor creation requests.
37+
*
38+
* @type {Array<object>}
39+
* @private
40+
*/
41+
_creationQueue = [];
42+
43+
/**
44+
* Index of XrAnchors, with XRAnchor (native handle) used as a key.
45+
*
46+
* @type {Map<XRAnchor,XrAnchor>}
47+
* @ignore
48+
*/
49+
_index = new Map();
50+
51+
/**
52+
* @type {Array<XrAnchor>}
53+
* @ignore
54+
*/
55+
_list = [];
56+
57+
/**
58+
* Map of callbacks to XRAnchors so that we can call its callback once
59+
* an anchor is updated with a pose for the first time.
60+
*
61+
* @type {Map<XrAnchor,XrAnchorCreate>}
62+
* @private
63+
*/
64+
_callbacksAnchors = new Map();
65+
66+
/**
67+
* @param {import('./xr-manager.js').XrManager} manager - WebXR Manager.
68+
* @hideconstructor
69+
*/
70+
constructor(manager) {
71+
super();
72+
73+
this.manager = manager;
74+
75+
if (this._supported) {
76+
this.manager.on('end', this._onSessionEnd, this);
77+
}
78+
}
79+
80+
/**
81+
* Fired when anchor failed to be created.
82+
*
83+
* @event XrAnchors#error
84+
* @param {Error} error - Error object related to a failure of anchors.
85+
*/
86+
87+
/**
88+
* Fired when a new {@link XrAnchor} is added.
89+
*
90+
* @event XrAnchors#add
91+
* @param {XrAnchor} anchor - Anchor that has been added.
92+
* @example
93+
* app.xr.anchors.on('add', function (anchor) {
94+
* // new anchor is added
95+
* });
96+
*/
97+
98+
/**
99+
* Fired when an {@link XrAnchor} is destroyed.
100+
*
101+
* @event XrAnchors#destroy
102+
* @param {XrAnchor} anchor - Anchor that has been destroyed.
103+
* @example
104+
* app.xr.anchors.on('destroy', function (anchor) {
105+
* // anchor that is destroyed
106+
* });
107+
*/
108+
109+
/** @private */
110+
_onSessionEnd() {
111+
// clear anchor creation queue
112+
for (let i = 0; i < this._creationQueue.length; i++) {
113+
if (!this._creationQueue[i].callback)
114+
continue;
115+
116+
this._creationQueue[i].callback(new Error('session ended'), null);
117+
}
118+
this._creationQueue.length = 0;
119+
120+
// destroy all anchors
121+
if (this._list) {
122+
let i = this._list.length;
123+
while (i--) {
124+
this._list[i].destroy();
125+
}
126+
this._list.length = 0;
127+
}
128+
}
129+
130+
/**
131+
* Create anchor with position, rotation and a callback.
132+
*
133+
* @param {import('../../core/math/vec3.js').Vec3} position - Position for an anchor.
134+
* @param {import('../../core/math/quat.js').Quat} [rotation] - Rotation for an anchor.
135+
* @param {XrAnchorCreate} [callback] - Callback to fire when anchor was created or failed to be created.
136+
* @example
137+
* app.xr.anchors.create(position, rotation, function (err, anchor) {
138+
* if (!err) {
139+
* // new anchor has been created
140+
* }
141+
* });
142+
*/
143+
create(position, rotation, callback) {
144+
this._creationQueue.push({
145+
transform: new XRRigidTransform(position, rotation), // eslint-disable-line no-undef
146+
callback: callback
147+
});
148+
}
149+
150+
/**
151+
* @param {*} frame - XRFrame from requestAnimationFrame callback.
152+
* @ignore
153+
*/
154+
update(frame) {
155+
// check if need to create anchors
156+
if (this._creationQueue.length) {
157+
for (let i = 0; i < this._creationQueue.length; i++) {
158+
const request = this._creationQueue[i];
159+
160+
frame.createAnchor(request.transform, this.manager._referenceSpace)
161+
.then((xrAnchor) => {
162+
if (request.callback)
163+
this._callbacksAnchors.set(xrAnchor, request.callback);
164+
})
165+
.catch((ex) => {
166+
if (request.callback)
167+
request.callback(ex, null);
168+
169+
this.fire('error', ex);
170+
});
171+
}
172+
173+
this._creationQueue.length = 0;
174+
}
175+
176+
// check if destroyed
177+
for (const [xrAnchor, anchor] of this._index) {
178+
if (frame.trackedAnchors.has(xrAnchor))
179+
continue;
180+
181+
anchor.destroy();
182+
}
183+
184+
// update existing anchors
185+
for (let i = 0; i < this._list.length; i++) {
186+
this._list[i].update(frame);
187+
}
188+
189+
// check if added
190+
for (const xrAnchor of frame.trackedAnchors) {
191+
if (this._index.has(xrAnchor))
192+
continue;
193+
194+
try {
195+
const tmp = xrAnchor.anchorSpace; // eslint-disable-line no-unused-vars
196+
} catch (ex) {
197+
// if anchorSpace is not available, then anchor is invalid
198+
// and should not be created
199+
continue;
200+
}
201+
202+
const anchor = new XrAnchor(this, xrAnchor);
203+
this._index.set(xrAnchor, anchor);
204+
this._list.push(anchor);
205+
anchor.update(frame);
206+
207+
const callback = this._callbacksAnchors.get(xrAnchor);
208+
if (callback) {
209+
this._callbacksAnchors.delete(xrAnchor);
210+
callback(null, anchor);
211+
}
212+
213+
this.fire('add', anchor);
214+
}
215+
}
216+
217+
/**
218+
* True if Anchors are supported.
219+
*
220+
* @type {boolean}
221+
*/
222+
get supported() {
223+
return this._supported;
224+
}
225+
226+
/**
227+
* List of available {@link XrAnchor}s.
228+
*
229+
* @type {Array<XrAnchor>}
230+
*/
231+
get list() {
232+
return this._list;
233+
}
234+
}
235+
236+
export { XrAnchors };

0 commit comments

Comments
 (0)