Skip to content

Commit 9616a60

Browse files
committed
scaffold ARToolKit plugin with initial setup and basic functionality
- missed plugin.js!
1 parent adcdff8 commit 9616a60

File tree

1 file changed

+162
-27
lines changed

1 file changed

+162
-27
lines changed

src/plugin.js

Lines changed: 162 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,170 @@
1-
// Assuming existing imports and setup in src/plugin.js
1+
// src/plugin.js
2+
/**
3+
* ArtoolkitPlugin
4+
* - maintains plugin lifecycle (init, enable, disable, dispose)
5+
* - optionally runs detection inside a Worker (src/worker/worker.js)
6+
* - subscribes to engine:update to send frames (by id) to the worker
7+
* - emits ar:markerFound / ar:markerUpdated / ar:markerLost on the engine eventBus
8+
*
9+
* Note: the worker stub is intentionally simple and returns a fake detection.
10+
*/
11+
export class ArtoolkitPlugin {
12+
constructor(options = {}) {
13+
this.options = options;
14+
this.core = null;
15+
this.enabled = false;
216

3-
let detectionWorker;
17+
// Worker and handlers
18+
this._worker = null;
19+
this._onWorkerMessage = this._onWorkerMessage.bind(this);
420

5-
function enableWorker() {
6-
detectionWorker = new Worker(new URL('./worker/worker.js', import.meta.url));
21+
// Engine update subscription
22+
this._onEngineUpdate = this._onEngineUpdate.bind(this);
723

8-
detectionWorker.postMessage({ type: 'init' });
24+
// Marker state tracking: Map<id, { lastSeen: number, visible: boolean }>
25+
this._markers = new Map();
926

10-
// Capture engine:update event to post processFrame messages
11-
core.eventBus.on('engine:update', (frame) => {
12-
if (frame) {
13-
detectionWorker.postMessage({ type: 'processFrame', payload: { frameId: frame.id } });
27+
// configuration
28+
this.workerEnabled = options.worker !== false; // default true
29+
this.lostThreshold = options.lostThreshold ?? 5; // frames to consider lost
1430
}
15-
});
1631

17-
detectionWorker.addEventListener('message', (ev) => {
18-
const { type, payload } = ev.data || {};
19-
if (type === 'ready') {
20-
console.log('Worker is ready');
21-
} else if (type === 'detectionResult') {
22-
payload.detections.forEach(detection => {
23-
core.eventBus.emit('ar:markerUpdated', {
24-
id: detection.id,
25-
poseMatrix: new Float32Array(detection.poseMatrix),
26-
confidence: detection.confidence,
27-
corners: detection.corners
28-
});
29-
});
32+
async init(core) {
33+
this.core = core;
34+
// Nothing heavy here; defer worker setup to enable()
35+
return this;
3036
}
31-
});
32-
}
3337

34-
// Call enableWorker when you want to start the worker
35-
// Example: enableWorker();
38+
async enable() {
39+
if (!this.core) throw new Error('Plugin not initialized');
40+
if (this.enabled) return this;
41+
this.enabled = true;
42+
43+
// subscribe to engine update to send frames to worker
44+
this.core.eventBus.on('engine:update', this._onEngineUpdate);
45+
46+
// start worker if configured
47+
if (this.workerEnabled) {
48+
this._startWorker();
49+
}
50+
51+
// start a simple interval to sweep lost markers by frame count (optional)
52+
this._sweepInterval = setInterval(() => this._sweepMarkers(), 100); // adjust as needed
53+
return this;
54+
}
55+
56+
async disable() {
57+
if (!this.enabled) return this;
58+
this.enabled = false;
59+
60+
this.core.eventBus.off('engine:update', this._onEngineUpdate);
61+
62+
if (this._worker) {
63+
this._stopWorker();
64+
}
65+
66+
if (this._sweepInterval) {
67+
clearInterval(this._sweepInterval);
68+
this._sweepInterval = null;
69+
}
70+
71+
return this;
72+
}
73+
74+
dispose() {
75+
return this.disable();
76+
}
77+
78+
// Engine frame handler: forward frame info to the worker
79+
_onEngineUpdate(frame) {
80+
// frame is expected to be an object provided by the capture system, e.g.:
81+
// { id: number, timestamp, imageBitmap?, width, height, sourceRef }
82+
if (!frame) return;
83+
84+
// Send lightweight message to worker (worker may accept ImageBitmap later)
85+
if (this._worker) {
86+
try {
87+
this._worker.postMessage({ type: 'processFrame', payload: { frameId: frame.id } });
88+
} catch (err) {
89+
// worker may be terminated; ignore
90+
console.warn('Artoolkit worker postMessage failed', err);
91+
}
92+
} else {
93+
// No worker: we could run detection inline in future
94+
}
95+
}
96+
97+
// Worker lifecycle
98+
_startWorker() {
99+
if (this._worker) return;
100+
// spawn worker relative to this module
101+
this._worker = new Worker(new URL('./worker/worker.js', import.meta.url));
102+
this._worker.addEventListener('message', this._onWorkerMessage);
103+
this._worker.postMessage({ type: 'init' });
104+
}
105+
106+
_stopWorker() {
107+
if (!this._worker) return;
108+
this._worker.removeEventListener('message', this._onWorkerMessage);
109+
try {
110+
this._worker.terminate();
111+
} catch (e) {
112+
// ignore
113+
}
114+
this._worker = null;
115+
}
116+
117+
_onWorkerMessage(ev) {
118+
const { type, payload } = ev.data || {};
119+
if (type === 'ready') {
120+
// Worker initialized
121+
this.core?.eventBus?.emit('ar:workerReady', {});
122+
} else if (type === 'detectionResult') {
123+
// payload: { detections: [ { id, confidence, poseMatrix (array), corners, frameId } ] }
124+
if (!payload || !Array.isArray(payload.detections)) return;
125+
for (const d of payload.detections) {
126+
const id = d.id;
127+
const now = Date.now();
128+
const poseMatrix = new Float32Array(d.poseMatrix || []);
129+
const confidence = d.confidence ?? 0;
130+
const corners = d.corners ?? [];
131+
132+
const prev = this._markers.get(id);
133+
if (!prev || !prev.visible) {
134+
// newly found
135+
this._markers.set(id, { lastSeen: now, visible: true, lostCount: 0 });
136+
this.core.eventBus.emit('ar:markerFound', { id, poseMatrix, confidence, corners, timestamp: now });
137+
} else {
138+
// updated
139+
prev.lastSeen = now;
140+
prev.lostCount = 0;
141+
this._markers.set(id, prev);
142+
this.core.eventBus.emit('ar:markerUpdated', { id, poseMatrix, confidence, corners, timestamp: now });
143+
}
144+
}
145+
} else if (type === 'error') {
146+
console.error('Artoolkit worker error', payload);
147+
this.core?.eventBus?.emit('ar:workerError', payload);
148+
}
149+
}
150+
151+
// sweep markers and emit lost events for markers not seen recently
152+
_sweepMarkers() {
153+
const now = Date.now();
154+
for (const [id, state] of this._markers.entries()) {
155+
const deltaMs = now - (state.lastSeen || 0);
156+
// converted threshold: if not seen within lostThreshold * frameInterval (~100ms here) mark lost
157+
// simple heuristic: if lastSeen is older than lostThreshold * 200ms mark lost
158+
if (deltaMs > (this.lostThreshold * 200)) {
159+
// emit lost
160+
this._markers.delete(id);
161+
this.core.eventBus.emit('ar:markerLost', { id, timestamp: now });
162+
}
163+
}
164+
}
165+
166+
// public helper to get marker state
167+
getMarkerState(markerId) {
168+
return this._markers.get(markerId) || null;
169+
}
170+
}

0 commit comments

Comments
 (0)