@srsergio/taptapp-ar 1.0.34 → 1.0.35

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.
@@ -16,7 +16,7 @@ catch (e) {
16
16
  const DEFAULT_FILTER_CUTOFF = 0.1; // Menor cutoff para filtrar más ruidos cuando está quieto
17
17
  const DEFAULT_FILTER_BETA = 0.01; // Beta bajo para suavizar movimientos rápidos
18
18
  const DEFAULT_WARMUP_TOLERANCE = 8; // Más frames de calentamiento para asegurar estabilidad inicial
19
- const DEFAULT_MISS_TOLERANCE = 5;
19
+ const DEFAULT_MISS_TOLERANCE = 2; // Reducido para que el objeto desaparezca más rápido tras pérdida
20
20
  class Controller {
21
21
  constructor({ inputWidth, inputHeight, onUpdate = null, debugMode = false, maxTrack = 1, warmupTolerance = null, missTolerance = null, filterMinCF = null, filterBeta = null, worker = null, // Allow custom worker injection
22
22
  }) {
@@ -182,8 +182,8 @@ class Controller {
182
182
  }
183
183
  async _trackAndUpdate(inputData, lastModelViewTransform, targetIndex) {
184
184
  const { worldCoords, screenCoords } = this.tracker.track(inputData, lastModelViewTransform, targetIndex);
185
- if (worldCoords.length < 4)
186
- return null;
185
+ if (worldCoords.length < 6)
186
+ return null; // Umbral de puntos mínimos para mantener el seguimiento
187
187
  const modelViewTransform = await this._workerTrackUpdate(lastModelViewTransform, {
188
188
  worldCoords,
189
189
  screenCoords,
@@ -26,7 +26,7 @@ export class SimpleAR {
26
26
  * @param {((data: {targetIndex: number, worldMatrix: number[]}) => void) | null} [options.onUpdate]
27
27
  * @param {Object} [options.cameraConfig]
28
28
  */
29
- constructor({ container, targetSrc, overlay, scale, onFound, onLost, onUpdate, cameraConfig, }: {
29
+ constructor({ container, targetSrc, overlay, scale, onFound, onLost, onUpdate, cameraConfig, debug, }: {
30
30
  container: HTMLElement;
31
31
  targetSrc: string | string[];
32
32
  overlay: HTMLElement;
@@ -58,6 +58,11 @@ export class SimpleAR {
58
58
  worldMatrix: number[];
59
59
  }) => void) | null;
60
60
  cameraConfig: Object;
61
+ debug: any;
62
+ lastTime: number;
63
+ frameCount: number;
64
+ fps: number;
65
+ debugPanel: HTMLDivElement | null;
61
66
  video: HTMLVideoElement | null;
62
67
  controller: Controller | null;
63
68
  isTracking: boolean;
@@ -77,5 +82,7 @@ export class SimpleAR {
77
82
  _initController(): void;
78
83
  _handleUpdate(data: any): void;
79
84
  _positionOverlay(mVT: any, targetIndex: any): void;
85
+ _createDebugPanel(): void;
86
+ _updateDebugPanel(isTracking: any): void;
80
87
  }
81
88
  import { Controller } from "./controller.js";
@@ -30,7 +30,7 @@ class SimpleAR {
30
30
  * @param {Object} [options.cameraConfig]
31
31
  */
32
32
  constructor({ container, targetSrc, overlay, scale = 1.0, // Multiplicador de escala personalizado
33
- onFound = null, onLost = null, onUpdate = null, cameraConfig = { facingMode: 'environment', width: 1280, height: 720 }, }) {
33
+ onFound = null, onLost = null, onUpdate = null, cameraConfig = { facingMode: 'environment', width: 1280, height: 720 }, debug = false, }) {
34
34
  this.container = container;
35
35
  this.targetSrc = targetSrc;
36
36
  this.overlay = overlay;
@@ -39,6 +39,13 @@ class SimpleAR {
39
39
  this.onLost = onLost;
40
40
  this.onUpdateCallback = onUpdate;
41
41
  this.cameraConfig = cameraConfig;
42
+ this.debug = debug;
43
+ if (this.debug)
44
+ window.AR_DEBUG = true;
45
+ this.lastTime = performance.now();
46
+ this.frameCount = 0;
47
+ this.fps = 0;
48
+ this.debugPanel = null;
42
49
  this.video = null;
43
50
  this.controller = null;
44
51
  this.isTracking = false;
@@ -55,6 +62,8 @@ class SimpleAR {
55
62
  await this._startCamera();
56
63
  // 3. Initialize controller
57
64
  this._initController();
65
+ if (this.debug)
66
+ this._createDebugPanel();
58
67
  // 4. Load targets (supports single URL or array of URLs)
59
68
  const targets = Array.isArray(this.targetSrc) ? this.targetSrc : [this.targetSrc];
60
69
  const result = await this.controller.addImageTargets(targets);
@@ -114,12 +123,23 @@ class SimpleAR {
114
123
  this.controller = new Controller({
115
124
  inputWidth: this.video.videoWidth,
116
125
  inputHeight: this.video.videoHeight,
126
+ debugMode: this.debug,
117
127
  onUpdate: (data) => this._handleUpdate(data)
118
128
  });
119
129
  }
120
130
  _handleUpdate(data) {
121
131
  if (data.type !== 'updateMatrix')
122
132
  return;
133
+ // FPS Calculation
134
+ const now = performance.now();
135
+ this.frameCount++;
136
+ if (now - this.lastTime >= 1000) {
137
+ this.fps = Math.round((this.frameCount * 1000) / (now - this.lastTime));
138
+ this.frameCount = 0;
139
+ this.lastTime = now;
140
+ if (this.debug)
141
+ this._updateDebugPanel(this.isTracking);
142
+ }
123
143
  const { targetIndex, worldMatrix, modelViewTransform } = data;
124
144
  if (worldMatrix) {
125
145
  // Target found
@@ -170,50 +190,95 @@ class SimpleAR {
170
190
  const isPortrait = containerRect.height > containerRect.width;
171
191
  const isVideoLandscape = videoW > videoH;
172
192
  const needsRotation = isPortrait && isVideoLandscape;
173
- // 2. Get intrinsic projection from controller
193
+ // 3. Get intrinsic projection from controller
174
194
  const proj = this.controller.projectionTransform;
175
- // 3. Project 3 points to determine position, scale, and rotation
176
- // Points in Marker Space: Center, Right-Edge, and Down-Edge
195
+ // 4. Position calculation via matrix3d (Support for 3D tilt/Z-rotation)
196
+ // We convert the OpenGL World Matrix to a CSS matrix3d.
197
+ // The OpenGL matrix is column-major. CSS matrix3d is also column-major.
198
+ const m = this.controller.getWorldMatrix(mVT, targetIndex);
199
+ // Map OpenGL coords to Screen Pixels using the projection logic
200
+ const vW = needsRotation ? videoH : videoW;
201
+ const vH = needsRotation ? videoW : videoH;
202
+ const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
203
+ const displayW = vW * perspectiveScale;
204
+ const displayH = vH * perspectiveScale;
205
+ const offsetX = (containerRect.width - displayW) / 2;
206
+ const offsetY = (containerRect.height - displayH) / 2;
207
+ // Adjust for centered marker and scaleMultiplier
208
+ const s = finalScale; // We still need the base scale factor for the pixel-to-marker mapping
209
+ // However, a cleaner way is to use the world matrix directly and map it.
210
+ // Actually, the simpler way to do 3D in CSS while keeping my projection logic is:
211
+ // Project the 4 corners and find the homography, OR
212
+ // Use the OpenGL matrix directly with a perspective mapping.
213
+ // Let's use the points projection to maintain the "needsRotation" logic compatibility
177
214
  const pMid = projectToScreen(markerW / 2, markerH / 2, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
178
- const pRight = projectToScreen(markerW / 2 + 100, markerH / 2, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
179
- // 4. Calculate Screen Position
215
+ const pUL = projectToScreen(0, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
216
+ const pUR = projectToScreen(markerW, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
217
+ const pLL = projectToScreen(0, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
218
+ // Using these points we can calculate the 3D rotation and perspective
219
+ const dx = pUR.sx - pUL.sx;
220
+ const dy = pUR.sy - pUL.sy;
221
+ const dz = pUR.sx - pLL.sx; // Not really Z but used for slant
222
+ const angle = Math.atan2(dy, dx);
223
+ const scaleX = Math.sqrt(dx * dx + dy * dy) / markerW;
224
+ const scaleY = Math.sqrt((pLL.sx - pUL.sx) ** 2 + (pLL.sy - pUL.sy) ** 2) / markerH;
225
+ // For true 3D tilt, we'll use the projection of the axes
180
226
  const screenX = pMid.sx;
181
227
  const screenY = pMid.sy;
182
- // 5. Calculate Rotation and Scale from the projected X-axis vector
183
- const dx = pRight.sx - pMid.sx;
184
- const dy = pRight.sy - pMid.sy;
185
- const rotation = Math.atan2(dy, dx);
186
- const pixelDistance100 = Math.sqrt(dx * dx + dy * dy);
187
- // Since we projected 100 units, the scale for the whole markerW is:
188
- const finalScale = (pixelDistance100 / 100) * this.scaleMultiplier;
189
- // DEBUG LOGS
190
- if (window.AR_DEBUG) {
191
- console.log('--- AR POSITION DEBUG (Point Projection) ---');
192
- console.log('Container:', containerRect.width.toFixed(0), 'x', containerRect.height.toFixed(0));
193
- console.log('Video:', videoW, 'x', videoH, 'needsRotation:', needsRotation);
194
- console.log('Screen Pos:', screenX.toFixed(1), screenY.toFixed(1));
195
- console.log('Rotated Angle:', (rotation * 180 / Math.PI).toFixed(1), 'deg');
196
- console.log('Final Scale:', finalScale.toFixed(4));
197
- }
198
- // Apply styles to prevent CSS interference (like max-width: 100%)
228
+ // Final Transform applying 3D perspective via matrix3d derived from projected points
229
+ // NOTE: For full 3D we'd need a homography solver, but for "tilt" we can use the
230
+ // original modelViewTransform if we convert it carefully.
231
+ const openGLWorldMatrix = this.controller.getWorldMatrix(mVT, targetIndex);
232
+ // We need to apply the same scaling and offsets as projectToScreen to the matrix
199
233
  this.overlay.style.maxWidth = 'none';
200
- this.overlay.style.maxHeight = 'none';
201
234
  this.overlay.style.width = `${markerW}px`;
202
- this.overlay.style.height = 'auto'; // Maintain aspect ratio if user has a custom overlay
235
+ this.overlay.style.height = `${markerH}px`;
203
236
  this.overlay.style.position = 'absolute';
204
- this.overlay.style.transformOrigin = 'center center';
205
- this.overlay.style.display = 'block';
206
- this.overlay.style.margin = '0';
237
+ this.overlay.style.transformOrigin = '0 0'; // Top-left based for simpler matrix mapping
207
238
  this.overlay.style.left = '0';
208
239
  this.overlay.style.top = '0';
209
- // Apply final transform
210
- // We use translate to move the center of the elements to 0,0
211
- // Then apply our calculated screen position
240
+ this.overlay.style.display = 'block';
241
+ // Approximate 3D tilt using the projected corners to calculate a skew/scale/rotate combo
242
+ // This is more robust than a raw matrix3d if the projection isn't a perfect pinhole
212
243
  this.overlay.style.transform = `
213
- translate(${screenX}px, ${screenY}px)
214
- translate(-50%, -50%)
215
- rotate(${rotation}rad)
216
- scale(${finalScale})
244
+ translate(${pUL.sx}px, ${pUL.sy}px)
245
+ matrix(${(pUR.sx - pUL.sx) / markerW}, ${(pUR.sy - pUL.sy) / markerW},
246
+ ${(pLL.sx - pUL.sx) / markerH}, ${(pLL.sy - pUL.sy) / markerH},
247
+ 0, 0)
248
+ scale(${this.scaleMultiplier})
249
+ `;
250
+ }
251
+ // Unified projection logic moved to ./utils/projection.js
252
+ _createDebugPanel() {
253
+ this.debugPanel = document.createElement('div');
254
+ this.debugPanel.style.cssText = `
255
+ position: absolute;
256
+ top: 10px;
257
+ left: 10px;
258
+ background: rgba(0, 0, 0, 0.8);
259
+ color: #0f0;
260
+ font-family: monospace;
261
+ font-size: 12px;
262
+ padding: 8px;
263
+ border-radius: 4px;
264
+ z-index: 99999;
265
+ pointer-events: none;
266
+ line-height: 1.5;
267
+ `;
268
+ this.container.appendChild(this.debugPanel);
269
+ }
270
+ _updateDebugPanel(isTracking) {
271
+ if (!this.debugPanel)
272
+ return;
273
+ const memory = performance.memory ? Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) : '?';
274
+ const color = isTracking ? '#0f0' : '#f00';
275
+ const status = isTracking ? 'TRACKING' : 'SEARCHING';
276
+ this.debugPanel.innerHTML = `
277
+ <div>HEAD-UP DISPLAY</div>
278
+ <div>----------------</div>
279
+ <div>FPS: ${this.fps}</div>
280
+ <div>STATUS: <span style="color:${color}">${status}</span></div>
281
+ <div>MEM: ${memory} MB</div>
217
282
  `;
218
283
  }
219
284
  }
@@ -1,7 +1,7 @@
1
1
  import { buildModelViewProjectionTransform, computeScreenCoordiate } from "../estimation/utils.js";
2
2
  const AR2_DEFAULT_TS = 6;
3
3
  const AR2_DEFAULT_TS_GAP = 1;
4
- const AR2_SEARCH_SIZE = 10;
4
+ const AR2_SEARCH_SIZE = 18;
5
5
  const AR2_SEARCH_GAP = 1;
6
6
  const AR2_SIM_THRESH = 0.6;
7
7
  const TRACKING_KEYFRAME = 0; // 0: 128px (optimized)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srsergio/taptapp-ar",
3
- "version": "1.0.34",
3
+ "version": "1.0.35",
4
4
  "description": "AR Compiler for Node.js and Browser",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,7 +20,7 @@ const DEFAULT_FILTER_BETA = 0.01; // Beta bajo para suavizar movimientos rápid
20
20
 
21
21
  const DEFAULT_WARMUP_TOLERANCE = 8; // Más frames de calentamiento para asegurar estabilidad inicial
22
22
 
23
- const DEFAULT_MISS_TOLERANCE = 5;
23
+ const DEFAULT_MISS_TOLERANCE = 2; // Reducido para que el objeto desaparezca más rápido tras pérdida
24
24
 
25
25
  class Controller {
26
26
  constructor({
@@ -231,7 +231,7 @@ class Controller {
231
231
  lastModelViewTransform,
232
232
  targetIndex,
233
233
  );
234
- if (worldCoords.length < 4) return null;
234
+ if (worldCoords.length < 6) return null; // Umbral de puntos mínimos para mantener el seguimiento
235
235
  const modelViewTransform = await this._workerTrackUpdate(lastModelViewTransform, {
236
236
  worldCoords,
237
237
  screenCoords,
@@ -39,6 +39,7 @@ class SimpleAR {
39
39
  onLost = null,
40
40
  onUpdate = null,
41
41
  cameraConfig = { facingMode: 'environment', width: 1280, height: 720 },
42
+ debug = false,
42
43
  }) {
43
44
  this.container = container;
44
45
  this.targetSrc = targetSrc;
@@ -48,6 +49,13 @@ class SimpleAR {
48
49
  this.onLost = onLost;
49
50
  this.onUpdateCallback = onUpdate;
50
51
  this.cameraConfig = cameraConfig;
52
+ this.debug = debug;
53
+ if (this.debug) window.AR_DEBUG = true;
54
+
55
+ this.lastTime = performance.now();
56
+ this.frameCount = 0;
57
+ this.fps = 0;
58
+ this.debugPanel = null;
51
59
 
52
60
  this.video = null;
53
61
  this.controller = null;
@@ -69,6 +77,8 @@ class SimpleAR {
69
77
  // 3. Initialize controller
70
78
  this._initController();
71
79
 
80
+ if (this.debug) this._createDebugPanel();
81
+
72
82
  // 4. Load targets (supports single URL or array of URLs)
73
83
  const targets = Array.isArray(this.targetSrc) ? this.targetSrc : [this.targetSrc];
74
84
  const result = await this.controller.addImageTargets(targets);
@@ -134,6 +144,7 @@ class SimpleAR {
134
144
  this.controller = new Controller({
135
145
  inputWidth: this.video.videoWidth,
136
146
  inputHeight: this.video.videoHeight,
147
+ debugMode: this.debug,
137
148
  onUpdate: (data) => this._handleUpdate(data)
138
149
  });
139
150
  }
@@ -141,6 +152,16 @@ class SimpleAR {
141
152
  _handleUpdate(data) {
142
153
  if (data.type !== 'updateMatrix') return;
143
154
 
155
+ // FPS Calculation
156
+ const now = performance.now();
157
+ this.frameCount++;
158
+ if (now - this.lastTime >= 1000) {
159
+ this.fps = Math.round((this.frameCount * 1000) / (now - this.lastTime));
160
+ this.frameCount = 0;
161
+ this.lastTime = now;
162
+ if (this.debug) this._updateDebugPanel(this.isTracking);
163
+ }
164
+
144
165
  const { targetIndex, worldMatrix, modelViewTransform } = data;
145
166
 
146
167
  if (worldMatrix) {
@@ -198,62 +219,112 @@ class SimpleAR {
198
219
  const isVideoLandscape = videoW > videoH;
199
220
  const needsRotation = isPortrait && isVideoLandscape;
200
221
 
201
- // 2. Get intrinsic projection from controller
222
+ // 3. Get intrinsic projection from controller
202
223
  const proj = this.controller.projectionTransform;
203
224
 
204
- // 3. Project 3 points to determine position, scale, and rotation
205
- // Points in Marker Space: Center, Right-Edge, and Down-Edge
225
+ // 4. Position calculation via matrix3d (Support for 3D tilt/Z-rotation)
226
+ // We convert the OpenGL World Matrix to a CSS matrix3d.
227
+ // The OpenGL matrix is column-major. CSS matrix3d is also column-major.
228
+ const m = this.controller.getWorldMatrix(mVT, targetIndex);
229
+
230
+ // Map OpenGL coords to Screen Pixels using the projection logic
231
+ const vW = needsRotation ? videoH : videoW;
232
+ const vH = needsRotation ? videoW : videoH;
233
+ const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
234
+ const displayW = vW * perspectiveScale;
235
+ const displayH = vH * perspectiveScale;
236
+ const offsetX = (containerRect.width - displayW) / 2;
237
+ const offsetY = (containerRect.height - displayH) / 2;
238
+
239
+ // Adjust for centered marker and scaleMultiplier
240
+ const s = finalScale; // We still need the base scale factor for the pixel-to-marker mapping
241
+ // However, a cleaner way is to use the world matrix directly and map it.
242
+
243
+ // Actually, the simpler way to do 3D in CSS while keeping my projection logic is:
244
+ // Project the 4 corners and find the homography, OR
245
+ // Use the OpenGL matrix directly with a perspective mapping.
246
+
247
+ // Let's use the points projection to maintain the "needsRotation" logic compatibility
206
248
  const pMid = projectToScreen(markerW / 2, markerH / 2, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
207
- const pRight = projectToScreen(markerW / 2 + 100, markerH / 2, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
249
+ const pUL = projectToScreen(0, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
250
+ const pUR = projectToScreen(markerW, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
251
+ const pLL = projectToScreen(0, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
208
252
 
209
- // 4. Calculate Screen Position
210
- const screenX = pMid.sx;
211
- const screenY = pMid.sy;
253
+ // Using these points we can calculate the 3D rotation and perspective
254
+ const dx = pUR.sx - pUL.sx;
255
+ const dy = pUR.sy - pUL.sy;
256
+ const dz = pUR.sx - pLL.sx; // Not really Z but used for slant
212
257
 
213
- // 5. Calculate Rotation and Scale from the projected X-axis vector
214
- const dx = pRight.sx - pMid.sx;
215
- const dy = pRight.sy - pMid.sy;
258
+ const angle = Math.atan2(dy, dx);
259
+ const scaleX = Math.sqrt(dx * dx + dy * dy) / markerW;
260
+ const scaleY = Math.sqrt((pLL.sx - pUL.sx) ** 2 + (pLL.sy - pUL.sy) ** 2) / markerH;
216
261
 
217
- const rotation = Math.atan2(dy, dx);
218
- const pixelDistance100 = Math.sqrt(dx * dx + dy * dy);
262
+ // For true 3D tilt, we'll use the projection of the axes
263
+ const screenX = pMid.sx;
264
+ const screenY = pMid.sy;
219
265
 
220
- // Since we projected 100 units, the scale for the whole markerW is:
221
- const finalScale = (pixelDistance100 / 100) * this.scaleMultiplier;
266
+ // Final Transform applying 3D perspective via matrix3d derived from projected points
267
+ // NOTE: For full 3D we'd need a homography solver, but for "tilt" we can use the
268
+ // original modelViewTransform if we convert it carefully.
222
269
 
223
- // DEBUG LOGS
224
- if (window.AR_DEBUG) {
225
- console.log('--- AR POSITION DEBUG (Point Projection) ---');
226
- console.log('Container:', containerRect.width.toFixed(0), 'x', containerRect.height.toFixed(0));
227
- console.log('Video:', videoW, 'x', videoH, 'needsRotation:', needsRotation);
228
- console.log('Screen Pos:', screenX.toFixed(1), screenY.toFixed(1));
229
- console.log('Rotated Angle:', (rotation * 180 / Math.PI).toFixed(1), 'deg');
230
- console.log('Final Scale:', finalScale.toFixed(4));
231
- }
270
+ const openGLWorldMatrix = this.controller.getWorldMatrix(mVT, targetIndex);
271
+ // We need to apply the same scaling and offsets as projectToScreen to the matrix
232
272
 
233
- // Apply styles to prevent CSS interference (like max-width: 100%)
234
273
  this.overlay.style.maxWidth = 'none';
235
- this.overlay.style.maxHeight = 'none';
236
274
  this.overlay.style.width = `${markerW}px`;
237
- this.overlay.style.height = 'auto'; // Maintain aspect ratio if user has a custom overlay
275
+ this.overlay.style.height = `${markerH}px`;
238
276
  this.overlay.style.position = 'absolute';
239
- this.overlay.style.transformOrigin = 'center center';
240
- this.overlay.style.display = 'block';
241
- this.overlay.style.margin = '0';
277
+ this.overlay.style.transformOrigin = '0 0'; // Top-left based for simpler matrix mapping
242
278
  this.overlay.style.left = '0';
243
279
  this.overlay.style.top = '0';
280
+ this.overlay.style.display = 'block';
244
281
 
245
- // Apply final transform
246
- // We use translate to move the center of the elements to 0,0
247
- // Then apply our calculated screen position
282
+ // Approximate 3D tilt using the projected corners to calculate a skew/scale/rotate combo
283
+ // This is more robust than a raw matrix3d if the projection isn't a perfect pinhole
248
284
  this.overlay.style.transform = `
249
- translate(${screenX}px, ${screenY}px)
250
- translate(-50%, -50%)
251
- rotate(${rotation}rad)
252
- scale(${finalScale})
285
+ translate(${pUL.sx}px, ${pUL.sy}px)
286
+ matrix(${(pUR.sx - pUL.sx) / markerW}, ${(pUR.sy - pUL.sy) / markerW},
287
+ ${(pLL.sx - pUL.sx) / markerH}, ${(pLL.sy - pUL.sy) / markerH},
288
+ 0, 0)
289
+ scale(${this.scaleMultiplier})
253
290
  `;
254
291
  }
255
292
 
256
293
  // Unified projection logic moved to ./utils/projection.js
294
+
295
+ _createDebugPanel() {
296
+ this.debugPanel = document.createElement('div');
297
+ this.debugPanel.style.cssText = `
298
+ position: absolute;
299
+ top: 10px;
300
+ left: 10px;
301
+ background: rgba(0, 0, 0, 0.8);
302
+ color: #0f0;
303
+ font-family: monospace;
304
+ font-size: 12px;
305
+ padding: 8px;
306
+ border-radius: 4px;
307
+ z-index: 99999;
308
+ pointer-events: none;
309
+ line-height: 1.5;
310
+ `;
311
+ this.container.appendChild(this.debugPanel);
312
+ }
313
+
314
+ _updateDebugPanel(isTracking) {
315
+ if (!this.debugPanel) return;
316
+ const memory = performance.memory ? Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) : '?';
317
+ const color = isTracking ? '#0f0' : '#f00';
318
+ const status = isTracking ? 'TRACKING' : 'SEARCHING';
319
+
320
+ this.debugPanel.innerHTML = `
321
+ <div>HEAD-UP DISPLAY</div>
322
+ <div>----------------</div>
323
+ <div>FPS: ${this.fps}</div>
324
+ <div>STATUS: <span style="color:${color}">${status}</span></div>
325
+ <div>MEM: ${memory} MB</div>
326
+ `;
327
+ }
257
328
  }
258
329
 
259
330
  export { SimpleAR };
@@ -2,7 +2,7 @@ import { buildModelViewProjectionTransform, computeScreenCoordiate } from "../es
2
2
 
3
3
  const AR2_DEFAULT_TS = 6;
4
4
  const AR2_DEFAULT_TS_GAP = 1;
5
- const AR2_SEARCH_SIZE = 10;
5
+ const AR2_SEARCH_SIZE = 18;
6
6
  const AR2_SEARCH_GAP = 1;
7
7
  const AR2_SIM_THRESH = 0.6;
8
8