@webarkit/ar-nft
Version:
WebAR Javscript library for markerless AR
267 lines (242 loc) • 9.99 kB
text/typescript
/*
* NFTWorker.simd.ts
* ARnft
*
* This file is part of ARnft - WebARKit.
*
* ARnft is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARnft is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ARnft. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent modules, and to
* copy and distribute the resulting executable under terms of your choice,
* provided that you also meet, for each linked independent module, the terms and
* conditions of the license of that module. An independent module is a module
* which is neither derived from nor based on this library. If you modify this
* library, you may extend this exception to your version of the library, but you
* are not obligated to do so. If you do not wish to do so, delete this exception
* statement from your version.
*
* Copyright 2021-2025 WebARKit.
*
* Author(s): Walter Perdan @kalwalt https://github.com/kalwalt
*
*/
import Worker from "worker-loader?inline=no-fallback!./Worker.simd";
import { getWindowSize } from "./utils/ARnftUtils";
export default class NFTWorker {
private worker: Worker;
private markerURL: Array<string>;
private _processing: boolean = false;
private vw: number;
private vh: number;
private target: EventTarget;
private uuid: string;
private name: string;
private addPath: string;
protected ready: boolean;
/**
* The NFTWorker constructor, to create a new instance of the NFTWorker class.
* @param markerURL An array of strings representing the URLs of the NFT markers.
* @param w the width of the camera.
* @param h the height of the camera.
* @param uuid the UUID of the marker assigned by the ARnft constructor.
* @param name the name of the marker.
* @param addPath the additional path for the marker.
*/
constructor(markerURL: Array<string>, w: number, h: number, uuid: string, name: string, addPath: string) {
this.markerURL = markerURL;
this.vw = w;
this.vh = h;
this.target = window || global;
this.uuid = uuid;
this.name = name;
this.ready = false;
this.addPath = addPath;
}
/**
* Initialize the NFTWorker instance. You need to provide the camera\_para.dat URL,
* the renderUpdate and trackUpdate functions for the stats, and a boolean for filtering the matrix.
* @param cameraURL The URL of the camera\_para.dat file.
* @param renderUpdate The function to call for rendering updates.
* @param trackUpdate The function to call for tracking updates.
* @param oef A boolean for filtering the matrix.
* @returns A promise that resolves to true if successful.
*/
public async initialize(
cameraURL: string,
renderUpdate: () => void,
trackUpdate: () => void,
oef: boolean
): Promise<boolean> {
this.worker = new Worker();
const worker = this.worker;
this.target.addEventListener("terminateWorker-" + this.name, function () {
worker.postMessage({ type: "stop" });
worker.terminate();
});
return await this.load(cameraURL, renderUpdate, trackUpdate, oef);
}
/**
* Passes the video stream to the worker for processing.
* @param imagedata The image data from the video stream.
* @param frame The current frame number.
* @returns void
*/
public process(imagedata: ImageData, frame: number) {
if (this._processing) {
return;
}
this._processing = true;
this.worker.postMessage({ type: "process", imagedata, frame }, [imagedata.data.buffer]);
}
/**
* Load the resources from the ARnft instance into the worker.
* @param cameraURL The URL of the camera\_para.dat file.
* @param renderUpdate The function to call for rendering updates.
* @param trackUpdate The function to call for tracking updates.
* @param oef A boolean for filtering the matrix.
* @returns A promise that resolves to true if successful.
*/
protected load(
cameraURL: string,
renderUpdate: () => void,
trackUpdate: () => void,
oef: boolean
): Promise<boolean> {
let [sw, sh, pw, ph, w, h] = getWindowSize(this.vw, this.vh);
const setWindowSizeEvent = new CustomEvent<object>("getWindowSize", { detail: { sw: sw, sh: sh } });
this.target.dispatchEvent(setWindowSizeEvent);
this.worker.postMessage({
type: "load",
pw: pw,
ph: ph,
camera_para: cameraURL,
marker: this.markerURL,
addPath: this.addPath,
oef: oef,
});
this.worker.onmessage = (ev: any) => {
const msg = ev.data;
switch (msg.type) {
case "loaded": {
const proj = JSON.parse(msg.proj);
const ratioW = pw / w;
const ratioH = ph / h;
proj[0] *= ratioW;
proj[4] *= ratioW;
proj[8] *= ratioW;
proj[12] *= ratioW;
proj[1] *= ratioH;
proj[5] *= ratioH;
proj[9] *= ratioH;
proj[13] *= ratioH;
const projectionMatrixEvent = new CustomEvent<object>("getProjectionMatrix", {
detail: { proj: proj },
});
this.target.dispatchEvent(projectionMatrixEvent);
break;
}
case "endLoading": {
if (msg.end == true) {
// removing loader page if present
const loader = document.getElementById("loading");
if (loader) {
loader.querySelector<HTMLElement>(".loading-text").innerText = "Start the tracking!";
setTimeout(function () {
if (loader.parentElement == null) {
return;
}
if (loader) {
loader.parentElement.removeChild(loader);
}
}, 2000);
}
}
this.ready = true;
this.target.dispatchEvent(new CustomEvent<object>("nftLoaded-" + this.uuid));
break;
}
case "markerInfos": {
const marker = msg.marker;
const nftEvent = new CustomEvent<object>("getNFTData-" + this.uuid + "-" + this.name, {
detail: { dpi: marker.dpi, width: marker.width, height: marker.height },
});
this.target.dispatchEvent(nftEvent);
break;
}
case "found": {
this.found(msg);
break;
}
case "not found": {
this.found(null);
break;
}
}
this._processing = false;
trackUpdate();
};
this.worker.onerror = (err: any) => {
console.error("Worker error from NFTWorker: ", err);
};
let renderU = () => {
renderUpdate();
window.requestAnimationFrame(renderU);
};
renderU();
return Promise.resolve(true);
}
/**
* Dispatch an event listener if the marker is lost or the matrix of the marker
* if found.
* @param msg message from the worker.
*/
public found(msg: any) {
let world: Float64Array;
if (!msg) {
// commenting out this routine see https://github.com/webarkit/ARnft/pull/184#issuecomment-853400903
//if (world) {
world = null;
const nftTrackingLostEvent = new CustomEvent<object>("nftTrackingLost-" + this.uuid + "-" + this.name, {
detail: { name: this.name },
});
this.target.dispatchEvent(nftTrackingLostEvent);
//}
} else {
world = JSON.parse(msg.matrixGL_RH);
const matrixGLrhEvent = new CustomEvent<object>("getMatrixGL_RH-" + this.uuid + "-" + this.name, {
detail: { matrixGL_RH: world, name: this.name },
});
this.target.dispatchEvent(matrixGLrhEvent);
}
}
public isReady() {
return this.ready;
}
public getUuid(): string {
return this.uuid;
}
public getName(): string {
return this.name;
}
public getMarkerUrl(): Array<string> {
return this.markerURL;
}
public getEventTarget(): EventTarget {
return this.target;
}
public destroy(): void {}
}
//export default null as any;