UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

186 lines (185 loc) 6.21 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { EventHandler } from "../../core/event-handler.js"; import { platform } from "../../core/platform.js"; import { XrTrackedImage } from "./xr-tracked-image.js"; class XrImageTracking extends EventHandler { /** * Create a new XrImageTracking instance. * * @param {XrManager} manager - WebXR Manager. * @ignore */ constructor(manager) { super(); /** * @type {XrManager} * @private */ __publicField(this, "_manager"); /** * @type {boolean} * @private */ __publicField(this, "_supported", platform.browser && !!window.XRImageTrackingResult); /** @private */ __publicField(this, "_available", false); /** * @type {XrTrackedImage[]} * @private */ __publicField(this, "_images", []); this._manager = manager; if (this._supported) { this._manager.on("start", this._onSessionStart, this); this._manager.on("end", this._onSessionEnd, this); } } /** * Add an image for image tracking. A width can also be provided to help the underlying system * estimate the appropriate transformation. Modifying the tracked images list is only possible * before an AR session is started. * * @param {HTMLCanvasElement|HTMLImageElement|SVGImageElement|HTMLVideoElement|Blob|ImageData|ImageBitmap} image - * Image that is matching real world image as close as possible. Resolution of images should be * at least 300x300. High resolution does _not_ improve tracking performance. The color of the * image is irrelevant, so grayscale images can be used. Images with too many geometric * features or repeating patterns will reduce tracking stability. * @param {number} width - Width (in meters) of image in the real world. Providing this value * as close to the real value will improve tracking quality. * @returns {XrTrackedImage|null} Tracked image object that will contain tracking information. * Returns null if image tracking is not supported or if the XR manager is not active. * @example * // image of a book cover that has width of 20cm (0.2m) * app.xr.imageTracking.add(bookCoverImg, 0.2); */ add(image, width) { if (!this._supported || this._manager.active) return null; const trackedImage = new XrTrackedImage(image, width); this._images.push(trackedImage); return trackedImage; } /** * Remove an image from image tracking. * * @param {XrTrackedImage} trackedImage - Tracked image to be removed. Modifying the tracked * images list is only possible before an AR session is started. */ remove(trackedImage) { if (this._manager.active) return; const ind = this._images.indexOf(trackedImage); if (ind !== -1) { trackedImage.destroy(); this._images.splice(ind, 1); } } /** @private */ _onSessionStart() { this._manager.session.getTrackedImageScores().then((images) => { this._available = true; for (let i = 0; i < images.length; i++) { this._images[i]._trackable = images[i] === "trackable"; } }).catch((err) => { this._available = false; this.fire("error", err); }); } /** @private */ _onSessionEnd() { this._available = false; for (let i = 0; i < this._images.length; i++) { const image = this._images[i]; image._pose = null; image._measuredWidth = 0; if (image._tracking) { image._tracking = false; image.fire("untracked"); } } } /** * @param {Function} callback - Function to call when all images have been prepared as image * bitmaps. * @ignore */ prepareImages(callback) { if (this._images.length) { Promise.all(this._images.map((trackedImage) => { return trackedImage.prepare(); })).then((bitmaps) => { callback(null, bitmaps); }).catch((err) => { callback(err, null); }); } else { callback(null, null); } } /** * @param {XRFrame} frame - XRFrame from requestAnimationFrame callback. * @ignore */ update(frame) { if (!this._available) return; const results = frame.getImageTrackingResults(); const index = {}; for (let i = 0; i < results.length; i++) { index[results[i].index] = results[i]; const trackedImage = this._images[results[i].index]; trackedImage._emulated = results[i].trackingState === "emulated"; trackedImage._measuredWidth = results[i].measuredWidthInMeters; trackedImage._pose = frame.getPose(results[i].imageSpace, this._manager._referenceSpace); } for (let i = 0; i < this._images.length; i++) { if (this._images[i]._tracking && !index[i]) { this._images[i]._tracking = false; this._images[i].fire("untracked"); } else if (!this._images[i]._tracking && index[i]) { this._images[i]._tracking = true; this._images[i].fire("tracked"); } } } /** * True if Image Tracking is supported. * * @type {boolean} */ get supported() { return this._supported; } /** * True if Image Tracking is available. This information is only available when the * XR session has started, and will be true if image tracking is supported and * images were provided and they have been processed successfully. * * @type {boolean} */ get available() { return this._available; } /** * List of {@link XrTrackedImage} that contain tracking information. * * @type {XrTrackedImage[]} */ get images() { return this._images; } } /** * Fired when the XR session is started, but image tracking failed to process the provided * images. The handler is passed the Error object. * * @event * @example * app.xr.imageTracking.on('error', (err) => { * console.error(err.message); * }); */ __publicField(XrImageTracking, "EVENT_ERROR", "error"); export { XrImageTracking };