ARToolKit detection plugin for AR.js core (ECS plugin)
npm install @ar-js-org/arjs-plugin-artoolkit








Lightweight WebWorker ARToolKit plugin for AR.js that detects square markers using WebAssembly and ImageBitmap zero-copy transfers, offering an event-driven API for realtime camera input, fast detection, and easy integration. ππ―πβ‘π§©
- Features
- Version
- Installation
- Using the ESM build (recommended)
- Using source (development mode)
- Usage
- Quick Start (copy-paste)
- Register and enable
- Events
- Sending frames
- Loading a pattern marker
- Examples
- API Reference
- Troubleshooting
- π§ Web Worker-based detection β marker detection runs off the main thread (Browser Module Worker)
- πΌοΈ ImageBitmap support β zero-copy frame transfer for efficient camera frames
- π§© ARToolKit integration β square pattern markers (patt files)
- β‘ Event-driven API β markerFound / markerUpdated / markerLost + raw getMarker events
- π Confidence filtering β only forwards PATTERN_MARKER events above minConfidence
The plugin exposes its build-time version both as a constant and on each instance:
``js
import {
ArtoolkitPlugin,
ARTOOLKIT_PLUGIN_VERSION,
} from "@ar-js-org/arjs-plugin-artoolkit";
console.log("Build version:", ARTOOLKIT_PLUGIN_VERSION); // e.g. 0.1.0 or 'unknown'
const plugin = new ArtoolkitPlugin();
console.log("Instance version:", plugin.version);
`
If the build-time define is missing (for example when using raw source or some test runners), the version falls back to 'unknown'.
`bash`Attention: package may not be published yet
npm install @ar-js-org/arjs-plugin-artoolkit
When you import the built ESM bundle from dist/, the worker and ARToolKit are already bundled and referenced correctly. You do NOT need to pass artoolkitModuleUrl.
Example:
`html`
Serving notes:
- Serve from a web server so /dist assets resolve. The build is configured with base: './', so the worker asset is referenced relative to the ESM file (e.g., /dist/assets/worker-*.js).dist/
- In your own apps, place where you serve static assets and import the ESM with the appropriate path (absolute or relative).
If you develop against src/ (not the built dist/), the worker will attempt to dynamically import ARToolKit. In that case, you must provide a valid artoolkitModuleUrl (for example a direct path to the UMD or ESM build) or ensure your dev server can resolve @ar-js-org/artoolkit5-js as an ES module. Browser module loading issues may occur if the module is not properly served or is not an ES module.
`js`
const plugin = new ArtoolkitPlugin({
worker: true,
artoolkitModuleUrl: '/node_modules/@ar-js-org/artoolkit5-js/dist/ARToolkit.js', // provide when using src/
cameraParametersUrl: '/path/to/camera_para.dat',
wasmBaseUrl: '/node_modules/@ar-js-org/artoolkit5-js/dist/', // optional; if your build requires it
minConfidence: 0.6,
});
console.log('Plugin version:', plugin.version);
CDN fallback (for source/dev):
- Set artoolkitModuleUrl to a CDN ESM endpoint (e.g., jsDelivr/UNPKG) for @ar-js-org/artoolkit5-js.
Notes:
- The previous loader.js and manual WASM placement flow is no longer used.
- In the dist/ build, ARToolKit is bundled and artoolkitModuleUrl is NOT needed.
`js
import { ArtoolkitPlugin } from "@ar-js-org/arjs-plugin-artoolkit";
// Minimal event bus stub
const eventBus = {
_h: new Map(),
on(e, h) {
if (!this._h.has(e)) this._h.set(e, []);
this._h.get(e).push(h);
},
emit(e, p) {
(this._h.get(e) || []).forEach((fn) => {
try {
fn(p);
} catch (err) {
console.error(err);
}
});
},
};
const engine = { eventBus };
const plugin = new ArtoolkitPlugin({ worker: true, minConfidence: 0.6 });
await plugin.init(engine);
await plugin.enable();
console.log("Version:", plugin.version);
// Load a marker (size is world units)
await plugin.loadMarker("/examples/simple-marker/data/patt.hiro", 1);
eventBus.on("ar:markerFound", (m) => console.log("FOUND", m.id));
eventBus.on("ar:markerUpdated", (m) => console.log("UPDATED", m.id));
eventBus.on("ar:markerLost", (m) => console.log("LOST", m.id));
`
`js
import { ArtoolkitPlugin } from "@ar-js-org/arjs-plugin-artoolkit";
const plugin = new ArtoolkitPlugin({
worker: true,
lostThreshold: 5, // frames before a marker is considered lost
frameDurationMs: 100, // expected ms per frame (affects lost timing)
// artoolkitModuleUrl: '/node_modules/@ar-js-org/artoolkit5-js/dist/ARToolkit.js', // Only for src/dev
cameraParametersUrl: "/data/camera_para.dat",
minConfidence: 0.6,
});
engine.pluginManager.register("artoolkit", plugin);
await engine.pluginManager.enable("artoolkit");
`
The plugin emits the following events on your engineβs event bus:
`js
// Marker first detected
engine.eventBus.on(
"ar:markerFound",
({ id, poseMatrix, confidence, corners }) => {
// poseMatrix is Float32Array(16)
},
);
// Marker updated (tracking)
engine.eventBus.on("ar:markerUpdated", (data) => {
// same shape as markerFound
});
// Marker lost
engine.eventBus.on("ar:markerLost", ({ id }) => {});
// Worker lifecycle
engine.eventBus.on("ar:workerReady", () => {});
engine.eventBus.on("ar:workerError", (error) => {});
// Raw ARToolKit getMarker (filtered: PATTERN_MARKER only, above minConfidence)
engine.eventBus.on("ar:getMarker", (payload) => {
// payload = { type, matrix: number[16], marker: { idPatt, cfPatt, idMatrix?, cfMatrix?, vertex? } }
});
`
`js
// Create ImageBitmap from a
// Emit an engine update; the plugin transfers the ImageBitmap to the worker
engine.eventBus.emit("engine:update", {
id: frameId,
timestamp: Date.now(),
imageBitmap,
width: imageBitmap.width,
height: imageBitmap.height,
});
// The ImageBitmap is transferred and cannot be reused; the worker will close it.
`
`js`
const { markerId, size } = await plugin.loadMarker(
"/examples/simple-marker/data/patt.hiro",
1,
);
A complete webcam-based example is available under examples/simple-marker/.
Serve from the repository root so that dist/ and example paths resolve:
`bash`From repository root
npx http-server -p 8080or
python3 -m http.server 8080
Open:
- http://localhost:8080/examples/simple-marker/index.html
The example demonstrates:
- Webcam capture with getUserMedia
- ImageBitmap creation and frame submission
- Event handling and console output
- Raw ar:getMarker payloads for debugging
`text`
{
worker?: boolean; // Enable worker (default: true)
lostThreshold?: number; // Frames before 'lost' (default: 5)
frameDurationMs?: number; // ms per frame used with lostThreshold (default: 200)
sweepIntervalMs?: number; // Lost-sweep interval (default: 100)
artoolkitModuleUrl?: string; // Only needed when using source/dev; not needed for dist build
cameraParametersUrl?: string;// Camera params file URL (required unless you rely on a remote default)
wasmBaseUrl?: string; // Base URL for ARToolKit assets (optional)
minConfidence?: number; // Minimum confidence to forward getMarker (default: 0.6)
}
- async init(core) β initialize with engine coreasync enable()
- β start worker and subscribe to framesasync disable()
- β stop worker and timersdispose()
- β alias for disablegetMarkerState(markerId)
- β current tracked stateasync loadMarker(patternUrl: string, size = 1)
- β load and track a pattern
- Worker asset 404:
- Ensure you import the ESM from /dist/arjs-plugin-artoolkit.es.js and that /dist/assets/worker-*.js is served.base: './'
- The build uses , so worker URLs are relative to the ESM file location.artoolkitModuleUrl
- βFailed to resolve module specifierβ in the Worker (source/dev only):
- Provide or serve /node_modules from your dev serverminConfidence
- Worker not starting:
- Serve via HTTP/HTTPS; ensure ES modules and Workers are supported
- No detections:
- Confirm camera started, correct marker pattern, sufficient lighting
- Adjust to reduce/raise filtering
- Check plugin.version (if 'unknown', ensure build-time define is configured)
- Sourcemap files (.map) generated in dist/ and types/ are excluded from the repository and the npm package to reduce package size and avoid shipping debug artifacts..gitignore
- See and .npmignore for details.npm install @ar-js-org/arjs-plugin-artoolkit
- When installing the package from npm (), all required built files are included and ready to use.npm run build
- If you install from source (e.g., cloning the repository), you must run the build manually: .
- Built files (dist/) and TypeScript declarations (types/) are NOT committed to the repository. They are generated by the CI/build process and attached to GitHub Releases as downloadable assets.v1.2.3
- To download the built files for a given release tag (example ), use the Releases download URL:
https://github.com/AR-js-org/arjs-plugin-artoolkit/releases/download/v1.2.3/dist/arjs-plugin-artoolkit.es.js
- If/when the package is published to npm, you can use jsDelivr to serve files from the npm package:
https://cdn.jsdelivr.net/npm/@ar-js-org/arjs-plugin-artoolkit@1.2.3/dist/arjs-plugin-artoolkit.es.js
- Note: jsDelivr serves files from npm or from the repository tree at a tag/branch. Because dist/ and types/` are not committed to the repo, the npm package must contain the built files for jsDelivr to serve them.
If you want me to add publish instructions or a small note showing how to use the Release assets or jsDelivr URLs in your project, I can add examples.