UNPKG

@ar-js-org/ar.js-threejs

Version:

AR.js (THREEx, ARjs) modular package - Typescript version

385 lines (340 loc) 15.8 kB
import { EventDispatcher, EventListener, PerspectiveCamera, Matrix4 } from 'three' import { IArToolkitContext, IContextParameters } from './CommonInterfaces/THREEx-interfaces'; import { ArMarkerControls } from "./ArMarkerControls"; import { setParameters } from './common-functions/utilityFunctions'; import jsartoolkit from "@ar-js-org/artoolkit5-js"; // TODO comment explanation const { ARController } = jsartoolkit; declare global { var arToolkitContext: IArToolkitContext; } /** * @class ArToolkitContext */ export class ArToolkitContext implements IArToolkitContext { private _updatedAt: any; public parameters: IContextParameters; public arController: any; private initialized: boolean; private _arMarkersControls: any; public _artoolkitProjectionAxisTransformMatrix: any; private className: string; /** * Create a new instance of the ARToolkitContext, provide some valid paramters to it. * @param {IContextParameters} parameters * @see {IContextParameters} */ constructor(parameters: IContextParameters) { this.className = "ArToolkitContext"; this._updatedAt = null; // handle default parameters this.parameters = { // AR backend - ['artoolkit'] trackingBackend: 'artoolkit', // debug - true if one should display artoolkit debug canvas, false otherwise debug: false, // the mode of detection - ['color', 'color_and_matrix', 'mono', 'mono_and_matrix'] detectionMode: 'mono', // type of matrix code - valid iif detectionMode end with 'matrix' - [3x3, 3x3_HAMMING63, 3x3_PARITY65, 4x4, 4x4_BCH_13_9_3, 4x4_BCH_13_5_5] matrixCodeType: '3x3', // url of the camera parameters cameraParametersUrl: ArToolkitContext.baseURL + '../../data/data/camera_para.dat', // tune the maximum rate of pose detection in the source image maxDetectionRate: 60, // resolution of at which we detect pose in the source image canvasWidth: 640, canvasHeight: 480, // the patternRatio inside the artoolkit marker - artoolkit only patternRatio: 0.5, // Labeling mode for markers - ['black_region', 'white_region'] // black_region: Black bordered markers on a white background, white_region: White bordered markers on a black background labelingMode: "black_region", // enable image smoothing or not for canvas copy - default to true // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled imageSmoothingEnabled: false, } // parameters sanity check console.assert( ['artoolkit'].indexOf(String(this.parameters.trackingBackend)) !== -1, 'invalid parameter trackingBackend', this.parameters.trackingBackend) console.assert( ['color', 'color_and_matrix', 'mono', 'mono_and_matrix'].indexOf(this.parameters.detectionMode) !== -1, 'invalid parameter detectionMode', this.parameters.detectionMode) console.assert( ["black_region", "white_region"].indexOf(this.parameters.labelingMode) !== -1, "invalid parameter labelingMode", this.parameters.labelingMode ); this.arController = null; this.initialized = false this._arMarkersControls = []; this._artoolkitProjectionAxisTransformMatrix = null; ////////////////////////////////////////////////////////////////////////////// // setParameters ////////////////////////////////////////////////////////////////////////////// setParameters(parameters, this) } /** * dispatch an Event as a THREE.EventDispatcher * @param {any} event */ dispatchEvent = EventDispatcher.prototype.dispatchEvent; /** * Add an Event listeners as a THREE.EventDispatcher * @param {T extends any} type * @param {EventListener} listener */ addEventListener = EventDispatcher.prototype.addEventListener; /** * Check if has an Event listeners as a THREE.EventDispatcher * @param {T extends any} type * @param {EventListener} listener */ hasEventListener = EventDispatcher.prototype.hasEventListener; /** * Remove an Event listeners as a THREE.EventDispatcher * @param {T extends any} type * @param {EventListener} listener */ removeEventListener = EventDispatcher.prototype.removeEventListener; static baseURL = "https://ar-js-org.github.io/AR.js/three.js/"; static REVISION = "3.4.3-threejs-0.3.2"; /** * Create a default Camera * @param {string} trackingBackend * @returns {Camera} * @deprecated use ARjs.Utils.createDefaultCamera instead */ createDefaultCamera(trackingBackend: string) { console.assert(false, 'use ARjs.Utils.createDefaultCamera instead') // Create a camera let camera; if (trackingBackend === 'artoolkit') { camera = new PerspectiveCamera(); } else console.assert(false); return camera } /** * Init the artoolkit backend. This is one of the first steps in your app. * @param {Function} onCompleted * @returns {void} */ init(onCompleted: Function): void { var _this = this; if (this.parameters.trackingBackend === "artoolkit") { this._initArtoolkit(done); } else console.assert(false); return; function done() { // dispatch event _this.dispatchEvent({ type: "initialized", }); _this.initialized = true; onCompleted && onCompleted(); } }; /** * update is where the data from an image or video stream is processed. * This is mandatory otherwise the marker can not be detected. * @param {HTMLImageElement | HTMLVideoElement} srcElement * @returns {boolean} */ update(srcElement: HTMLImageElement | HTMLVideoElement) { // be sure arController is fully initialized if ( this.parameters.trackingBackend === "artoolkit" && this.arController === null ) return false; // honor this.parameters.maxDetectionRate var present = performance.now(); if ( this._updatedAt !== null && present - this._updatedAt < 1000 / this.parameters.maxDetectionRate ) { return false; } this._updatedAt = present; var prevVisibleMarkers: any[] = []; // mark all markers to invisible before processing this frame this._arMarkersControls.forEach(function (markerControls: any) { if (markerControls.object3d.visible) { prevVisibleMarkers.push(markerControls); } if (!markerControls.context.arController.showObject) { markerControls.object3d.visible = false; } }); // process this frame if (this.parameters.trackingBackend === "artoolkit") { this._updateArtoolkit(srcElement); } else { console.assert(false); } // dispatch event this.dispatchEvent({ type: "sourceProcessed", }); // After frame is processed, check visibility of each marker to determine if it was found or lost this._arMarkersControls.forEach(function (markerControls: any) { var wasVisible = prevVisibleMarkers.includes(markerControls); var isVisible = markerControls.object3d.visible; if (isVisible === true && wasVisible === false) { window.dispatchEvent( new CustomEvent("markerFound", { detail: markerControls, }) ); } else if (isVisible === false && wasVisible === true) { window.dispatchEvent( new CustomEvent("markerLost", { detail: markerControls, }) ); } }); // return true as we processed the frame return true; }; //////////////////////////////////////////////////////////////////////////////// // Add/Remove markerControls //////////////////////////////////////////////////////////////////////////////// /** * Add a marker to be detected. * @param {ArMarkerControls} arMarkerControls * @returns {void} */ addMarker(arMarkerControls: ArMarkerControls): void { console.assert(arMarkerControls instanceof ArMarkerControls); this._arMarkersControls.push(arMarkerControls); }; /** * Remove a marker from the class. * @param {ArMarkerControls} arMarkerControls * @returns {void} */ removeMarker(arMarkerControls: ArMarkerControls): void { console.assert(arMarkerControls instanceof ArMarkerControls); var index = this._arMarkersControls.indexOf(arMarkerControls); if (index < 0) { return; } this._arMarkersControls.splice(index, 1); }; private _initArtoolkit(onCompleted: Function) { var _this = this; // set this._artoolkitProjectionAxisTransformMatrix to change artoolkit projection matrix axis to match usual webgl one this._artoolkitProjectionAxisTransformMatrix = new Matrix4(); this._artoolkitProjectionAxisTransformMatrix.multiply( new Matrix4().makeRotationY(Math.PI) ); this._artoolkitProjectionAxisTransformMatrix.multiply( new Matrix4().makeRotationZ(Math.PI) ); // init controller ARController.initWithDimensions( _this.parameters.canvasWidth, _this.parameters.canvasHeight, _this.parameters.cameraParametersUrl, {} ) .then((arController: any) => { _this.arController = arController; // honor this.parameters.imageSmoothingEnabled arController.ctx.mozImageSmoothingEnabled = _this.parameters.imageSmoothingEnabled; arController.ctx.webkitImageSmoothingEnabled = _this.parameters.imageSmoothingEnabled; arController.ctx.msImageSmoothingEnabled = _this.parameters.imageSmoothingEnabled; arController.ctx.imageSmoothingEnabled = _this.parameters.imageSmoothingEnabled; // honor this.parameters.debug if (_this.parameters.debug === true) { arController.debugSetup(); arController.canvas.style.position = "absolute"; arController.canvas.style.top = "0px"; arController.canvas.style.opacity = "0.6"; arController.canvas.style.pointerEvents = "none"; arController.canvas.style.zIndex = "-1"; } // setPatternDetectionMode var detectionModes = { color: arController.artoolkit.AR_TEMPLATE_MATCHING_COLOR, color_and_matrix: arController.artoolkit.AR_TEMPLATE_MATCHING_COLOR_AND_MATRIX, mono: arController.artoolkit.AR_TEMPLATE_MATCHING_MONO, mono_and_matrix: arController.artoolkit.AR_TEMPLATE_MATCHING_MONO_AND_MATRIX, }; //@ts-ignore var detectionMode = detectionModes[_this.parameters.detectionMode]; console.assert(detectionMode !== undefined); arController.setPatternDetectionMode(detectionMode); // setMatrixCodeType var matrixCodeTypes = { "3x3": arController.artoolkit.AR_MATRIX_CODE_3x3, "3x3_HAMMING63": arController.artoolkit.AR_MATRIX_CODE_3x3_HAMMING63, "3x3_PARITY65": arController.artoolkit.AR_MATRIX_CODE_3x3_PARITY65, "4x4": arController.artoolkit.AR_MATRIX_CODE_4x4, "4x4_BCH_13_9_3": arController.artoolkit.AR_MATRIX_CODE_4x4_BCH_13_9_3, "4x4_BCH_13_5_5": arController.artoolkit.AR_MATRIX_CODE_4x4_BCH_13_5_5, "5x5_BCH_22_12_5": arController.artoolkit.AR_MATRIX_CODE_5x5_BCH_22_12_5, "5x5_BCH_22_7_7": arController.artoolkit.AR_MATRIX_CODE_5x5_BCH_22_7_7, "5x5": arController.artoolkit.AR_MATRIX_CODE_5x5, "6x6": arController.artoolkit.AR_MATRIX_CODE_6x6, }; //@ts-ignore var matrixCodeType = matrixCodeTypes[_this.parameters.matrixCodeType]; console.assert(matrixCodeType !== undefined); arController.setMatrixCodeType(matrixCodeType); // set the patternRatio for artoolkit arController.setPattRatio(_this.parameters.patternRatio); // set the labelingMode for artoolkit var labelingModeTypes = { black_region: arController.artoolkit.AR_LABELING_BLACK_REGION, white_region: arController.artoolkit.AR_LABELING_WHITE_REGION, }; //@ts-ignore var labelingModeType = labelingModeTypes[_this.parameters.labelingMode]; console.assert(labelingModeType !== undefined); arController.setLabelingMode(labelingModeType); // set thresholding in artoolkit // this seems to be the default // arController.setThresholdMode(artoolkit.AR_LABELING_THRESH_MODE_MANUAL) // adatative consume a LOT of cpu... // arController.setThresholdMode(artoolkit.AR_LABELING_THRESH_MODE_AUTO_ADAPTIVE) // arController.setThresholdMode(artoolkit.AR_LABELING_THRESH_MODE_AUTO_OTSU) // notify onCompleted(); }); return this; }; /** * getProjectionMatrix return the Camera Projection Matrix from the ARController camera. * @returns {Matrix4} */ getProjectionMatrix(): Matrix4 { // FIXME rename this function to say it is artoolkit specific - getArtoolkitProjectMatrix // keep a backward compatibility with a console.warn console.assert(this.parameters.trackingBackend === "artoolkit"); console.assert( this.arController, "arController MUST be initialized to call this function" ); // get projectionMatrixArr from artoolkit var projectionMatrixArr = this.arController.getCameraMatrix(); var projectionMatrix = new Matrix4().fromArray(projectionMatrixArr); // projectionMatrix.multiply(this._artoolkitProjectionAxisTransformMatrix) // return the result return projectionMatrix; }; private _updateArtoolkit(srcElement: HTMLImageElement | HTMLVideoElement) { this.arController.process(srcElement); }; }