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 Plane Detection #3068

Merged
merged 17 commits into from
Jun 15, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
3 changes: 3 additions & 0 deletions src/framework/components/camera/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,9 @@ class CameraComponent extends Component {
*
* @param {object} [options] - Object with options for XR session initialization.
* @param {string[]} [options.optionalFeatures] - Optional features for XRSession start. It is used for getting access to additional WebXR spec extensions.
* @param {boolean} [options.depthSensing] - Set to true to attempt to enable {@link XrDepthSensing}.
* @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
* the session is started. The callback has one argument Error - it is null if the XR
* session started successfully.
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,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 { XrPlaneDetection } from './xr/xr-plane-detection.js';
export { XrPlane } from './xr/xr-plane.js';

// BACKWARDS COMPATIBILITY
export * from './deprecated.js';
22 changes: 18 additions & 4 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 { XrPlaneDetection } from './xr-plane-detection.js';

/**
* @class
Expand Down Expand Up @@ -57,6 +58,7 @@ class XrManager extends EventHandler {
this.domOverlay = new XrDomOverlay(this);
this.hitTest = new XrHitTest(this);
this.imageTracking = new XrImageTracking(this);
this.planeDetection = new XrPlaneDetection(this);
this.input = new XrInput(this);
this.lightEstimation = new XrLightEstimation(this);

Expand Down Expand Up @@ -171,10 +173,13 @@ class XrManager extends EventHandler {
*
* @example
* button.on('click', function () {
* app.xr.start(camera, pc.XRTYPE_VR, pc.XRSPACE_LOCAL);
* app.xr.start(camera, pc.XRTYPE_VR, pc.XRSPACE_LOCALFLOOR);
* });
* @param {object} [options] - Object with additional options for XR session initialization.
* @param {string[]} [options.optionalFeatures] - Optional features for XRSession start. It is used for getting access to additional WebXR spec extensions.
* @param {boolean} [options.depthSensing] - Set to true to attempt to enable {@link XrDepthSensing}.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplicate of the param definition at line 190

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except that declaration is an object

* @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.
*/
start(camera, type, spaceType, options) {
Expand Down Expand Up @@ -216,10 +221,16 @@ class XrManager extends EventHandler {
if (type === XRTYPE_AR) {
opts.optionalFeatures.push('light-estimation');
opts.optionalFeatures.push('hit-test');
opts.optionalFeatures.push('depth-sensing');

if (options && options.imageTracking) {
opts.optionalFeatures.push('image-tracking');
if (options) {
if (options.depthSensing)
opts.optionalFeatures.push('depth-sensing');

if (options.imageTracking)
opts.optionalFeatures.push('image-tracking');

if (options.planeDetection)
opts.optionalFeatures.push('plane-detection');
}

if (this.domOverlay.root) {
Expand Down Expand Up @@ -505,6 +516,9 @@ class XrManager extends EventHandler {

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

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

this.fire('update', frame);
Expand Down
150 changes: 150 additions & 0 deletions src/xr/xr-plane-detection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { EventHandler } from '../core/event-handler.js';
import { XrPlane } from './xr-plane.js';

/**
* @class
* @name XrPlaneDetection
* @classdesc Plane Detection provides the ability to detect real world surfaces based on estimations of underlying AR system.
* @description Plane Detection provides the ability to detect real world surfaces based on estimations of underlying AR system.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @param {XrManager} manager - WebXR Manager.
* @property {boolean} supported True if Plane Detection is supported.
* @property {boolean} available True if Plane Detection is available. This property can be set to true only during running session.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @property {XrPlane[]|null} planes List of {@link XrPlane} that contain individual plane information, or null if plane detection isn't available.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @example
* // start session with plane detection enabled
* app.xr.start(camera, pc.XRTYPE_VR, pc.XRSPACE_LOCALFLOOR, {
* planeDetection: true
* });
* @example
* app.xr.planeDetection.on('add', function (plane) {
* // new plane been added
* });
*/
class XrPlaneDetection extends EventHandler {
constructor(manager) {
super();

this._manager = manager;
this._supported = !! window.XRPlane;
this._available = false;

// key - XRPlane (native plane does not have ID's)
// value - XrPlane
this._planesIndex = new Map();
Maksims marked this conversation as resolved.
Show resolved Hide resolved

this._planes = null;

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

/**
* @event
* @name XrPlaneDetection#available
* @description Fired when plane detection becomes available.
*/

/**
* @event
* @name XrPlaneDetection#unavailable
* @description Fired when plane detection becomes unavailable.
*/

/**
* @event
* @name XrPlaneDetection#add
* @description Fired when new {@link XrPlane} is added to the list.
* @param {XrPlane} plane - Plane that has been added.
* @example
* app.xr.planeDetection.on('add', function (plane) {
* // new plane is added
* });
*/

/**
* @event
* @name XrPlaneDetection#remove
* @description Fired when a {@link XrPlane} is removed to the list.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @param {XrPlane} plane - Plane that has been removed.
* @example
* app.xr.planeDetection.on('remove', function (plane) {
* // new plane is removed
* });
*/

_onSessionEnd() {
for (let i = 0; i < this._planes.length; i++) {
this._planes[i].destroy();
}
this._planesIndex.clear();
this._planes = null;

if (this._available) {
this._available = false;
this.fire('unavailable');
}
}

update(frame) {
let detectedPlanes;

if (! this._available) {
try {
detectedPlanes = frame.detectedPlanes;
this._planes = [];
this._available = true;
this.fire('available');
} catch (ex) {
return;
}
} else {
detectedPlanes = frame.detectedPlanes;
}

// iterate through indexed planes
for (const [xrPlane, plane] of this._planesIndex) {
Maksims marked this conversation as resolved.
Show resolved Hide resolved
if (detectedPlanes.has(xrPlane))
continue;

// if indexed plane is not listed in detectedPlanes anymore
// then remove it
this._planesIndex.delete(xrPlane);
this._planes.splice(this._planes.indexOf(plane), 1);
plane.destroy();
this.fire('remove', plane);
}

// iterate through detected planes
for (const xrPlane of detectedPlanes) {
Maksims marked this conversation as resolved.
Show resolved Hide resolved
let plane = this._planesIndex.get(xrPlane);

if (! plane) {
// detected plane is not indexed
// then create new XrPlane
plane = new XrPlane(this, xrPlane);
this._planesIndex.set(xrPlane, plane);
this._planes.push(plane);
plane.update(frame);
this.fire('add', plane);
} else {
// if already indexed, just update
plane.update(frame);
}
}
}

get supported() {
return this._supported;
}

get available() {
return this._available;
}

get planes() {
return this._planes;
}
}

export { XrPlaneDetection };
133 changes: 133 additions & 0 deletions src/xr/xr-plane.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { EventHandler } from '../core/event-handler.js';
import { Vec3 } from '../math/vec3.js';
import { Quat } from '../math/quat.js';

let ids = 0;

/**
* @class
* @name XrPlane
* @classdesc Detected Plane instance that provides position, rotation and polygon points. Plane is a subject to change during its lifetime.
* @description Detected Plane instance that provides position, rotation and polygon points. Plane is a subject to change during its lifetime.
* @param {XrPlaneDetection} planeDetection - Plane detection system.
* @param {object} xrPlane - XRPlane that is instantiated by WebXR system.
* @property {number} id Unique identifier of a plane.
* @property {string|null} orientation Plane's pecific orientation (horizontal or vertical) or null if orientation is anything else.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
*/
class XrPlane extends EventHandler {
constructor(planeDetection, xrPlane) {
super();

this._id = ++ids;

this._planeDetection = planeDetection;
this._manager = this._planeDetection._manager;

this._xrPlane = xrPlane;
this._lastChangedTime = this._xrPlane.lastChangedTime;
this._orientation = this._xrPlane.orientation;

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

/**
* @event
* @name XrPlane#remove
* @description Fired when {@link XrPlane} is removed.
* @example
* plane.once('remove', function () {
* // plane is not available anymore
* });
*/

/**
* @event
* @name XrPlane#change
* @description Fired when {@link XrPlane} attributes such as: orientation and/or points have been changed. Position and Rotation can change at any time without triggering `change` event.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @example
* plane.on('change', function () {
* // plane has been changed
* });
*/

destroy() {
this.fire('remove');
}

update(frame) {
const pose = frame.getPose(this._xrPlane.planeSpace, this._manager._referenceSpace);
if (pose) {
this._position.copy(pose.transform.position);
this._rotation.copy(pose.transform.orientation);
}

// has not changed
if (this._lastChangedTime !== this._xrPlane.lastChangedTime) {
this._lastChangedTime = this._xrPlane.lastChangedTime;

// attributes have been changed
this.fire('change');
}
}

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

/**
* @function
* @name XrPlane#getRotation
* @description Get the world space rotation of a plane.
* @returns {Vec3} The world space rotation of a plane.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
*/
getRotation() {
return this._rotation;
}
mvaligursky marked this conversation as resolved.
Show resolved Hide resolved

get id() {
return this.id;
}

get orientation() {
return this._orientation;
}

/**
* @name XrPlane#points
* @type {object[]}
* @description List of DOMPointReadOnly objects that is an object with `x y z` properties that define local point of a planes polygon.
willeastcott marked this conversation as resolved.
Show resolved Hide resolved
* @example
* // prepare reusable objects
* var vecA = new pc.Vec3();
* var vecB = new pc.Vec3();
* var color = new pc.Color(1, 1, 1);
*
* // update Mat4 to plane position and rotation
* transform.setTRS(plane.getPosition(), plane.getRotation(), pc.Vec3.ONE);
*
* // draw lines between points
* for (var i = 0; i < plane.points.length; i++) {
* vecA.copy(plane.points[i]);
* vecB.copy(plane.points[(i + 1) % plane.points.length]);
*
* // transform from planes local to world coords
* transform.transformPoint(vecA, vecA);
* transform.transformPoint(vecB, vecB);
*
* // render line
* app.renderLine(vecA, vecB, color);
* }
*/
get points() {
return this._xrPlane.polygon;
}
}

export { XrPlane };