@srsergio/taptapp-ar 1.0.93 → 1.0.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +16 -14
  2. package/dist/compiler/offline-compiler.d.ts +3 -3
  3. package/dist/compiler/offline-compiler.js +50 -33
  4. package/dist/core/constants.d.ts +2 -0
  5. package/dist/core/constants.js +4 -1
  6. package/dist/core/detector/detector-lite.d.ts +6 -5
  7. package/dist/core/detector/detector-lite.js +46 -16
  8. package/dist/core/matching/matcher.d.ts +1 -1
  9. package/dist/core/matching/matcher.js +7 -4
  10. package/dist/core/matching/matching.d.ts +2 -1
  11. package/dist/core/matching/matching.js +43 -11
  12. package/dist/core/perception/bio-inspired-engine.d.ts +130 -0
  13. package/dist/core/perception/bio-inspired-engine.js +232 -0
  14. package/dist/core/perception/foveal-attention.d.ts +142 -0
  15. package/dist/core/perception/foveal-attention.js +280 -0
  16. package/dist/core/perception/index.d.ts +6 -0
  17. package/dist/core/perception/index.js +17 -0
  18. package/dist/core/perception/predictive-coding.d.ts +92 -0
  19. package/dist/core/perception/predictive-coding.js +278 -0
  20. package/dist/core/perception/saccadic-controller.d.ts +126 -0
  21. package/dist/core/perception/saccadic-controller.js +269 -0
  22. package/dist/core/perception/saliency-map.d.ts +74 -0
  23. package/dist/core/perception/saliency-map.js +254 -0
  24. package/dist/core/perception/scale-orchestrator.d.ts +28 -0
  25. package/dist/core/perception/scale-orchestrator.js +68 -0
  26. package/dist/core/protocol.d.ts +14 -1
  27. package/dist/core/protocol.js +33 -1
  28. package/dist/runtime/bio-inspired-controller.d.ts +135 -0
  29. package/dist/runtime/bio-inspired-controller.js +358 -0
  30. package/dist/runtime/controller.d.ts +11 -2
  31. package/dist/runtime/controller.js +20 -8
  32. package/dist/runtime/controller.worker.js +2 -2
  33. package/dist/runtime/simple-ar.d.ts +24 -20
  34. package/dist/runtime/simple-ar.js +172 -156
  35. package/package.json +1 -1
  36. package/src/compiler/offline-compiler.ts +56 -36
  37. package/src/core/constants.ts +5 -1
  38. package/src/core/detector/detector-lite.js +46 -16
  39. package/src/core/matching/matcher.js +8 -4
  40. package/src/core/matching/matching.js +51 -12
  41. package/src/core/perception/bio-inspired-engine.js +275 -0
  42. package/src/core/perception/foveal-attention.js +306 -0
  43. package/src/core/perception/index.js +18 -0
  44. package/src/core/perception/predictive-coding.js +327 -0
  45. package/src/core/perception/saccadic-controller.js +303 -0
  46. package/src/core/perception/saliency-map.js +296 -0
  47. package/src/core/perception/scale-orchestrator.js +80 -0
  48. package/src/core/protocol.ts +38 -1
  49. package/src/runtime/bio-inspired-controller.ts +448 -0
  50. package/src/runtime/controller.ts +22 -7
  51. package/src/runtime/controller.worker.js +2 -1
  52. package/src/runtime/simple-ar.ts +197 -171
@@ -1,9 +1,9 @@
1
- import { Controller } from "./controller.js";
2
- import { OneEuroFilter } from "../libs/one-euro-filter.js";
1
+ import { BioInspiredController } from "./bio-inspired-controller.js";
3
2
  import { projectToScreen } from "../core/utils/projection.js";
4
3
 
5
4
  /**
6
- * 🍦 SimpleAR - Dead-simple vanilla AR for image overlays
5
+ * 🍦 SimpleAR - High-performance Vanilla AR for image overlays
6
+ * Now powered by Bio-Inspired Perception and Nanite Virtualized Features.
7
7
  */
8
8
 
9
9
  export interface SimpleAROptions {
@@ -25,6 +25,58 @@ export interface SimpleAROptions {
25
25
  debug?: boolean;
26
26
  }
27
27
 
28
+ /**
29
+ * 🕵️ Internal Smoothing Manager
30
+ * Applies Median + Adaptive Alpha filtering for sub-pixel stability.
31
+ */
32
+ class SmoothingManager {
33
+ private history: Map<number, { x: number, y: number }[]> = new Map();
34
+ private lastFiltered: Map<number, { x: number, y: number }> = new Map();
35
+ private medianSize = 3;
36
+ private deadZone = 0.2;
37
+
38
+ smooth(id: number, raw: { x: number, y: number }, reliability: number) {
39
+ if (!this.history.has(id)) this.history.set(id, []);
40
+ const h = this.history.get(id)!;
41
+ h.push(raw);
42
+ if (h.length > this.medianSize) h.shift();
43
+
44
+ // Get median
45
+ const sortedX = [...h].map(p => p.x).sort((a, b) => a - b);
46
+ const sortedY = [...h].map(p => p.y).sort((a, b) => a - b);
47
+ const median = {
48
+ x: sortedX[Math.floor(sortedX.length / 2)],
49
+ y: sortedY[Math.floor(sortedY.length / 2)]
50
+ };
51
+
52
+ // Adaptive Alpha based on reliability
53
+ const baseAlpha = 0.15;
54
+ const alpha = baseAlpha + (reliability * (1.0 - baseAlpha));
55
+ const last = this.lastFiltered.get(id) || median;
56
+
57
+ let filteredX = last.x * (1 - alpha) + median.x * alpha;
58
+ let filteredY = last.y * (1 - alpha) + median.y * alpha;
59
+
60
+ // Dead-zone to kill jitter at rest
61
+ if (Math.abs(filteredX - last.x) < this.deadZone) filteredX = last.x;
62
+ if (Math.abs(filteredY - last.y) < this.deadZone) filteredY = last.y;
63
+
64
+ const result = { x: filteredX, y: filteredY };
65
+ this.lastFiltered.set(id, result);
66
+ return result;
67
+ }
68
+
69
+ reset(id?: number) {
70
+ if (id !== undefined) {
71
+ this.history.delete(id);
72
+ this.lastFiltered.delete(id);
73
+ } else {
74
+ this.history.clear();
75
+ this.lastFiltered.clear();
76
+ }
77
+ }
78
+ }
79
+
28
80
  class SimpleAR {
29
81
  container: HTMLElement;
30
82
  targetSrc: string | string[];
@@ -43,16 +95,18 @@ class SimpleAR {
43
95
  cameraConfig: MediaStreamConstraints['video'];
44
96
  debug: boolean;
45
97
 
46
- lastTime: number;
47
- frameCount: number;
48
- fps: number;
49
- debugPanel: HTMLElement | null = null;
50
- video: HTMLVideoElement | null = null;
51
- controller: Controller | null = null;
52
- isTracking: boolean = false;
53
- lastMatrix: number[] | null = null;
54
- filters: OneEuroFilter[] = [];
55
- markerDimensions: number[][] = [];
98
+ private video: HTMLVideoElement | null = null;
99
+ private controller: BioInspiredController | null = null;
100
+ private smoother = new SmoothingManager();
101
+ private isTracking: boolean = false;
102
+ private markerDimensions: number[][] = [];
103
+ private debugPanel: HTMLElement | null = null;
104
+ private debugCanvas: HTMLCanvasElement | null = null;
105
+ private debugCtx: CanvasRenderingContext2D | null = null;
106
+
107
+ private lastTime = 0;
108
+ private fps = 0;
109
+ private frameCount = 0;
56
110
 
57
111
  constructor({
58
112
  container,
@@ -74,12 +128,6 @@ class SimpleAR {
74
128
  this.onUpdateCallback = onUpdate;
75
129
  this.cameraConfig = cameraConfig;
76
130
  this.debug = debug;
77
- // @ts-ignore
78
- if (this.debug) window.AR_DEBUG = true;
79
-
80
- this.lastTime = performance.now();
81
- this.frameCount = 0;
82
- this.fps = 0;
83
131
  }
84
132
 
85
133
  async start() {
@@ -87,14 +135,17 @@ class SimpleAR {
87
135
  await this._startCamera();
88
136
  this._initController();
89
137
 
90
- if (this.debug) this._createDebugPanel();
138
+ if (this.debug) {
139
+ this._createDebugPanel();
140
+ this._createDebugCanvas();
141
+ }
91
142
 
92
143
  const targets = Array.isArray(this.targetSrc) ? this.targetSrc : [this.targetSrc];
93
144
  const result = await this.controller!.addImageTargets(targets);
94
145
  this.markerDimensions = result.dimensions;
95
- console.log("Targets loaded. Dimensions:", this.markerDimensions);
96
146
 
97
- this.controller!.processVideo(this.video);
147
+ // Kick off loop
148
+ this.controller!.processVideo(this.video!);
98
149
  return this;
99
150
  }
100
151
 
@@ -109,32 +160,26 @@ class SimpleAR {
109
160
  this.video = null;
110
161
  }
111
162
  this.isTracking = false;
112
- this.markerDimensions = [];
163
+ this.smoother.reset();
113
164
  }
114
165
 
115
- _createVideo() {
166
+ private _createVideo() {
116
167
  this.video = document.createElement('video');
117
168
  this.video.setAttribute('autoplay', '');
118
169
  this.video.setAttribute('playsinline', '');
119
170
  this.video.setAttribute('muted', '');
120
171
  this.video.style.cssText = `
121
172
  position: absolute;
122
- top: 0;
123
- left: 0;
124
- width: 100%;
125
- height: 100%;
126
- object-fit: cover;
127
- z-index: 0;
173
+ top: 0; left: 0; width: 100%; height: 100%;
174
+ object-fit: cover; z-index: 0;
128
175
  `;
129
176
  this.container.style.position = 'relative';
130
177
  this.container.style.overflow = 'hidden';
131
178
  this.container.insertBefore(this.video, this.container.firstChild);
132
179
  }
133
180
 
134
- async _startCamera() {
135
- const stream = await navigator.mediaDevices.getUserMedia({
136
- video: this.cameraConfig
137
- });
181
+ private async _startCamera() {
182
+ const stream = await navigator.mediaDevices.getUserMedia({ video: this.cameraConfig });
138
183
  this.video!.srcObject = stream;
139
184
  await this.video!.play();
140
185
 
@@ -144,178 +189,103 @@ class SimpleAR {
144
189
  });
145
190
  }
146
191
 
147
- _initController() {
148
- this.controller = new Controller({
192
+ private _initController() {
193
+ this.controller = new BioInspiredController({
149
194
  inputWidth: this.video!.videoWidth,
150
195
  inputHeight: this.video!.videoHeight,
151
196
  debugMode: this.debug,
152
- warmupTolerance: 3, // 🚀 Faster lock than default
153
- missTolerance: 10, // 🛡️ More resilient to temporary occlusion
197
+ bioInspired: {
198
+ enabled: true,
199
+ aggressiveSkipping: false
200
+ },
154
201
  onUpdate: (data) => this._handleUpdate(data)
155
202
  });
156
203
  }
157
204
 
158
- _handleUpdate(data: any) {
159
- if (data.type !== 'updateMatrix') return;
205
+ private _handleUpdate(data: any) {
206
+ if (data.type !== 'updateMatrix') {
207
+ if (data.type === 'featurePoints' && this.debugCtx) {
208
+ this._drawDebugFeatures(data.featurePoints);
209
+ }
210
+ return;
211
+ }
160
212
 
213
+ // FPS Meter
161
214
  const now = performance.now();
162
215
  this.frameCount++;
163
216
  if (now - this.lastTime >= 1000) {
164
217
  this.fps = Math.round((this.frameCount * 1000) / (now - this.lastTime));
165
218
  this.frameCount = 0;
166
219
  this.lastTime = now;
167
- if (this.debug) this._updateDebugPanel(this.isTracking);
168
220
  }
169
221
 
170
- const { targetIndex, worldMatrix, modelViewTransform, screenCoords, reliabilities, stabilities, detectionPoints } = data;
222
+ const { targetIndex, worldMatrix, modelViewTransform, reliabilities, stabilities, screenCoords, pixelsSaved } = data;
171
223
 
172
- // Project points to screen coordinates
173
- let projectedPoints = [];
224
+ // Apply Smoothing
225
+ let smoothedCoords = screenCoords || [];
174
226
  if (screenCoords && screenCoords.length > 0) {
175
- const containerRect = this.container.getBoundingClientRect();
176
- const videoW = this.video!.videoWidth;
177
- const videoH = this.video!.videoHeight;
178
- const isPortrait = containerRect.height > containerRect.width;
179
- const isVideoLandscape = videoW > videoH;
180
- const needsRotation = isPortrait && isVideoLandscape;
181
- const proj = this.controller!.projectionTransform;
182
-
183
- const vW = needsRotation ? videoH : videoW;
184
- const vH = needsRotation ? videoW : videoH;
185
- const pScale = Math.max(containerRect.width / vW, containerRect.height / vH);
186
- const dW = vW * pScale;
187
- const dH = vH * pScale;
188
- const oX = (containerRect.width - dW) / 2;
189
- const oY = (containerRect.height - dH) / 2;
190
-
191
- projectedPoints = screenCoords.map((p: any) => {
192
- let sx, sy;
193
- if (needsRotation) {
194
- sx = oX + (dW / 2) - (p.y - proj[1][2]) * pScale;
195
- sy = oY + (dH / 2) + (p.x - proj[0][2]) * pScale;
196
- } else {
197
- sx = oX + (dW / 2) + (p.x - proj[0][2]) * pScale;
198
- sy = oY + (dH / 2) + (p.y - proj[1][2]) * pScale;
199
- }
200
- return { x: sx, y: sy };
201
- });
202
- }
203
-
204
- let projectedDetectionPoints = [];
205
- if (detectionPoints && detectionPoints.length > 0) {
206
- const containerRect = this.container.getBoundingClientRect();
207
- const videoW = this.video!.videoWidth;
208
- const videoH = this.video!.videoHeight;
209
- const isPortrait = containerRect.height > containerRect.width;
210
- const isVideoLandscape = videoW > videoH;
211
- const needsRotation = isPortrait && isVideoLandscape;
212
- const proj = this.controller!.projectionTransform;
213
-
214
- const vW = needsRotation ? videoH : videoW;
215
- const vH = needsRotation ? videoW : videoH;
216
- const pScale = Math.max(containerRect.width / vW, containerRect.height / vH);
217
- const dW = vW * pScale;
218
- const dH = vH * pScale;
219
- const oX = (containerRect.width - dW) / 2;
220
- const oY = (containerRect.height - dH) / 2;
221
-
222
- projectedDetectionPoints = detectionPoints.map((p: any) => {
223
- let sx, sy;
224
- if (needsRotation) {
225
- sx = oX + (dW / 2) - (p.y - proj[1][2]) * pScale;
226
- sy = oY + (dH / 2) + (p.x - proj[0][2]) * pScale;
227
- } else {
228
- sx = oX + (dW / 2) + (p.x - proj[0][2]) * pScale;
229
- sy = oY + (dH / 2) + (p.y - proj[1][2]) * pScale;
230
- }
231
- return { x: sx, y: sy };
227
+ smoothedCoords = screenCoords.map((p: any) => {
228
+ const rel = reliabilities ? (reliabilities[p.id] || 0.5) : 0.5;
229
+ const sm = this.smoother.smooth(p.id, p, rel);
230
+ return { ...sm, id: p.id };
232
231
  });
233
232
  }
234
233
 
235
234
  if (worldMatrix) {
236
235
  if (!this.isTracking) {
237
236
  this.isTracking = true;
238
- this.overlay && (this.overlay.style.opacity = '1');
237
+ if (this.overlay) this.overlay.style.opacity = '1';
239
238
  this.onFound && this.onFound({ targetIndex });
240
239
  }
241
-
242
- this.lastMatrix = worldMatrix;
243
240
  this._positionOverlay(modelViewTransform, targetIndex);
244
241
  } else {
245
242
  if (this.isTracking) {
246
243
  this.isTracking = false;
247
- this.overlay && (this.overlay.style.opacity = '0');
244
+ if (this.overlay) this.overlay.style.opacity = '0';
248
245
  this.onLost && this.onLost({ targetIndex });
246
+ this.smoother.reset();
249
247
  }
250
248
  }
251
249
 
252
- // Always notify the callback if we have points, or if we just lost tracking
253
- if (projectedPoints.length > 0 || projectedDetectionPoints.length > 0 || (worldMatrix === null && data.type === 'updateMatrix')) {
254
- this.onUpdateCallback && this.onUpdateCallback({
250
+ // Notify callback
251
+ if (this.onUpdateCallback) {
252
+ this.onUpdateCallback({
255
253
  targetIndex,
256
254
  worldMatrix,
257
- screenCoords: projectedPoints,
255
+ screenCoords: smoothedCoords,
258
256
  reliabilities: reliabilities || [],
259
257
  stabilities: stabilities || [],
260
- detectionPoints: projectedDetectionPoints
258
+ detectionPoints: data.featurePoints
261
259
  });
262
260
  }
261
+
262
+ // Draw Debug UI
263
+ if (this.debug) {
264
+ this._updateHUD(data);
265
+ this._drawDebugPoints(smoothedCoords, stabilities);
266
+ }
263
267
  }
264
268
 
265
- _positionOverlay(mVT: number[][], targetIndex: number) {
269
+ private _positionOverlay(mVT: number[][], targetIndex: number) {
266
270
  if (!this.overlay || !this.markerDimensions[targetIndex]) return;
267
271
 
268
272
  const [markerW, markerH] = this.markerDimensions[targetIndex];
269
273
  const containerRect = this.container.getBoundingClientRect();
270
274
  const videoW = this.video!.videoWidth;
271
275
  const videoH = this.video!.videoHeight;
276
+ const proj = this.controller!.projectionTransform;
272
277
 
278
+ // Handle portrait rotation for mobile
273
279
  const isPortrait = containerRect.height > containerRect.width;
274
280
  const isVideoLandscape = videoW > videoH;
275
281
  const needsRotation = isPortrait && isVideoLandscape;
276
282
 
277
- const proj = this.controller!.projectionTransform;
278
-
279
283
  const pUL = projectToScreen(0, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
280
284
  const pUR = projectToScreen(markerW, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
281
285
  const pLL = projectToScreen(0, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
282
286
  const pLR = projectToScreen(markerW, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
283
287
 
284
- const solveHomography = (w: number, h: number, p1: any, p2: any, p3: any, p4: any) => {
285
- const x1 = p1.sx, y1 = p1.sy;
286
- const x2 = p2.sx, y2 = p2.sy;
287
- const x3 = p3.sx, y3 = p3.sy;
288
- const x4 = p4.sx, y4 = p4.sy;
289
-
290
- const dx1 = x2 - x4, dx2 = x3 - x4, dx3 = x1 - x2 + x4 - x3;
291
- const dy1 = y2 - y4, dy2 = y3 - y4, dy3 = y1 - y2 + y4 - y3;
292
-
293
- let a, b, c, d, e, f, g, h_coeff;
294
-
295
- if (dx3 === 0 && dy3 === 0) {
296
- a = x2 - x1; b = x3 - x1; c = x1;
297
- d = y2 - y1; e = y3 - y1; f = y1;
298
- g = 0; h_coeff = 0;
299
- } else {
300
- const det = dx1 * dy2 - dx2 * dy1;
301
- g = (dx3 * dy2 - dx2 * dy3) / det;
302
- h_coeff = (dx1 * dy3 - dx3 * dy1) / det;
303
- a = x2 - x1 + g * x2;
304
- b = x3 - x1 + h_coeff * x3;
305
- c = x1;
306
- d = y2 - y1 + g * y2;
307
- e = y3 - y1 + h_coeff * y3;
308
- f = y1;
309
- }
310
- return [
311
- a / w, d / w, 0, g / w,
312
- b / h, e / h, 0, h_coeff / h,
313
- 0, 0, 1, 0,
314
- c, f, 0, 1
315
- ];
316
- };
317
-
318
- const matrix = solveHomography(markerW, markerH, pUL, pUR, pLL, pLR);
288
+ const matrix = this._solveHomography(markerW, markerH, pUL, pUR, pLL, pLR);
319
289
 
320
290
  this.overlay.style.maxWidth = 'none';
321
291
  this.overlay.style.width = `${markerW}px`;
@@ -334,40 +304,96 @@ class SimpleAR {
334
304
  `;
335
305
  }
336
306
 
337
- _createDebugPanel() {
307
+ private _solveHomography(w: number, h: number, p1: any, p2: any, p3: any, p4: any) {
308
+ const x1 = p1.sx, y1 = p1.sy;
309
+ const x2 = p2.sx, y2 = p2.sy;
310
+ const x3 = p3.sx, y3 = p3.sy;
311
+ const x4 = p4.sx, y4 = p4.sy;
312
+
313
+ const dx1 = x2 - x4, dx2 = x3 - x4, dx3 = x1 - x2 + x4 - x3;
314
+ const dy1 = y2 - y4, dy2 = y3 - y4, dy3 = y1 - y2 + y4 - y3;
315
+
316
+ const det = dx1 * dy2 - dx2 * dy1;
317
+ const g = (dx3 * dy2 - dx2 * dy3) / det;
318
+ const h_coeff = (dx1 * dy3 - dx3 * dy1) / det;
319
+ const a = x2 - x1 + g * x2;
320
+ const b = x3 - x1 + h_coeff * x3;
321
+ const c = x1;
322
+ const d = y2 - y1 + g * y2;
323
+ const e = y3 - y1 + h_coeff * y3;
324
+ const f = y1;
325
+
326
+ return [
327
+ a / w, d / w, 0, g / w,
328
+ b / h, e / h, 0, h_coeff / h,
329
+ 0, 0, 1, 0,
330
+ c, f, 0, 1
331
+ ];
332
+ }
333
+
334
+ // --- DEBUG METHODS ---
335
+
336
+ private _createDebugPanel() {
338
337
  this.debugPanel = document.createElement('div');
339
338
  this.debugPanel.style.cssText = `
340
- position: absolute;
341
- top: 10px;
342
- left: 10px;
343
- background: rgba(0, 0, 0, 0.8);
344
- color: #0f0;
345
- font-family: monospace;
346
- font-size: 12px;
347
- padding: 8px;
348
- border-radius: 4px;
349
- z-index: 99999;
350
- pointer-events: none;
351
- line-height: 1.5;
339
+ position: absolute; top: 10px; left: 10px;
340
+ background: rgba(0, 0, 0, 0.7); color: #0f0;
341
+ font-family: monospace; font-size: 11px; padding: 10px;
342
+ border-radius: 5px; z-index: 100; pointer-events: none;
343
+ line-height: 1.4; border-left: 3px solid #0f0;
352
344
  `;
353
345
  this.container.appendChild(this.debugPanel);
354
346
  }
355
347
 
356
- _updateDebugPanel(isTracking: boolean) {
348
+ private _createDebugCanvas() {
349
+ this.debugCanvas = document.createElement('canvas');
350
+ this.debugCanvas.width = this.container.clientWidth;
351
+ this.debugCanvas.height = this.container.clientHeight;
352
+ this.debugCanvas.style.cssText = `
353
+ position: absolute; top: 0; left: 0; width: 100%; height: 100%;
354
+ pointer-events: none; z-index: 99;
355
+ `;
356
+ this.container.appendChild(this.debugCanvas);
357
+ this.debugCtx = this.debugCanvas.getContext('2d');
358
+ }
359
+
360
+ private _updateHUD(data: any) {
357
361
  if (!this.debugPanel) return;
358
- // @ts-ignore
359
- const memory = performance.memory ? Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) : '?';
360
- const color = isTracking ? '#0f0' : '#f00';
361
- const status = isTracking ? 'TRACKING' : 'SEARCHING';
362
+ const rel = data.reliabilities ? (data.reliabilities.reduce((a: any, b: any) => a + b, 0) / data.reliabilities.length).toFixed(2) : "0.00";
363
+ const stab = data.stabilities ? (data.stabilities.reduce((a: any, b: any) => a + b, 0) / data.stabilities.length).toFixed(2) : "0.00";
364
+ const savings = data.pixelsSaved ? ((data.pixelsSaved / (this.video!.videoWidth * this.video!.videoHeight)) * 100).toFixed(0) : "0";
362
365
 
363
366
  this.debugPanel.innerHTML = `
364
- <div>HEAD-UP DISPLAY</div>
365
- <div>----------------</div>
366
- <div>FPS: ${this.fps}</div>
367
- <div>STATUS: <span style="color:${color}">${status}</span></div>
368
- <div>MEM: ${memory} MB</div>
367
+ <b>TapTapp AR HUD</b><br>
368
+ ------------------<br>
369
+ STATUS: <span style="color:${this.isTracking ? '#0f0' : '#f00'}">${this.isTracking ? 'TRACKING' : 'SEARCHING'}</span><br>
370
+ FPS: ${this.fps}<br>
371
+ RELIAB: ${rel}<br>
372
+ STABIL: ${stab}<br>
373
+ SAVINGS: ${savings}% Pixels<br>
374
+ POINTS: ${data.screenCoords?.length || 0}
369
375
  `;
370
376
  }
377
+
378
+ private _drawDebugPoints(coords: any[], stabilities: any[]) {
379
+ if (!this.debugCtx) return;
380
+ this.debugCtx.clearRect(0, 0, this.debugCanvas!.width, this.debugCanvas!.height);
381
+
382
+ coords.forEach((p, i) => {
383
+ const s = stabilities ? (stabilities[i] || 0) : 0.5;
384
+ this.debugCtx!.fillStyle = `rgba(0, 255, 0, ${0.4 + s * 0.6})`;
385
+ this.debugCtx!.fillRect(p.x - 1, p.y - 1, 2, 2);
386
+ });
387
+ }
388
+
389
+ private _drawDebugFeatures(points: any[]) {
390
+ if (!this.debugCtx || this.isTracking) return;
391
+ this.debugCtx.clearRect(0, 0, this.debugCanvas!.width, this.debugCanvas!.height);
392
+ this.debugCtx.fillStyle = 'rgba(255, 255, 0, 0.4)';
393
+ points.slice(0, 200).forEach(p => {
394
+ this.debugCtx!.fillRect(p.x - 1, p.y - 1, 2, 2);
395
+ });
396
+ }
371
397
  }
372
398
 
373
399
  export { SimpleAR };