@srsergio/taptapp-ar 1.0.13 → 1.0.15

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.
@@ -51,6 +51,7 @@ export class Controller {
51
51
  trackingDataList: any[];
52
52
  };
53
53
  tracker: Tracker | undefined;
54
+ matchingDataList: any[] | undefined;
54
55
  /**
55
56
  * Load image targets from a single ArrayBuffer (backward compatible)
56
57
  * @param {ArrayBuffer} buffer - Single .mind file buffer
@@ -100,8 +101,18 @@ export class Controller {
100
101
  trackUpdate(modelViewTransform: any, trackFeatures: any): Promise<any>;
101
102
  _workerMatch(featurePoints: any, targetIndexes: any): Promise<any>;
102
103
  workerMatchDone: ((data: any) => void) | undefined;
104
+ _matchOnMainThread(featurePoints: any, targetIndexes: any): Promise<{
105
+ targetIndex: number;
106
+ modelViewTransform: number[][] | null;
107
+ debugExtra: {
108
+ frames: never[];
109
+ } | null;
110
+ }>;
111
+ mainThreadMatcher: import("./matching/matcher.js").Matcher | undefined;
112
+ mainThreadEstimator: import("./estimation/estimator.js").Estimator | undefined;
103
113
  _workerTrackUpdate(modelViewTransform: any, trackingFeatures: any): Promise<any>;
104
114
  workerTrackDone: ((data: any) => void) | undefined;
115
+ _trackUpdateOnMainThread(modelViewTransform: any, trackingFeatures: any): Promise<never[][] | null>;
105
116
  _glModelViewMatrix(modelViewTransform: any, targetIndex: any): any[];
106
117
  _glProjectionMatrix({ projectionTransform, width, height, near, far }: {
107
118
  projectionTransform: any;
@@ -120,6 +120,7 @@ class Controller {
120
120
  });
121
121
  }
122
122
  this.markerDimensions = allDimensions;
123
+ this.matchingDataList = allMatchingData; // Store for main-thread fallback
123
124
  return { dimensions: allDimensions, matchingDataList: allMatchingData, trackingDataList: allTrackingData };
124
125
  }
125
126
  /**
@@ -324,6 +325,11 @@ class Controller {
324
325
  }
325
326
  _workerMatch(featurePoints, targetIndexes) {
326
327
  return new Promise((resolve) => {
328
+ // If no worker available, process on main thread
329
+ if (!this.worker) {
330
+ this._matchOnMainThread(featurePoints, targetIndexes).then(resolve);
331
+ return;
332
+ }
327
333
  this.workerMatchDone = (data) => {
328
334
  resolve({
329
335
  targetIndex: data.targetIndex,
@@ -331,16 +337,51 @@ class Controller {
331
337
  debugExtra: data.debugExtra,
332
338
  });
333
339
  };
334
- this.worker && this.worker.postMessage({ type: "match", featurePoints: featurePoints, targetIndexes });
340
+ this.worker.postMessage({ type: "match", featurePoints: featurePoints, targetIndexes });
335
341
  });
336
342
  }
343
+ async _matchOnMainThread(featurePoints, targetIndexes) {
344
+ // Lazy initialize Matcher and Estimator for main thread
345
+ if (!this.mainThreadMatcher) {
346
+ const { Matcher } = await import("./matching/matcher.js");
347
+ const { Estimator } = await import("./estimation/estimator.js");
348
+ this.mainThreadMatcher = new Matcher(this.inputWidth, this.inputHeight, this.debugMode);
349
+ this.mainThreadEstimator = new Estimator(this.projectionTransform);
350
+ }
351
+ let matchedTargetIndex = -1;
352
+ let matchedModelViewTransform = null;
353
+ let matchedDebugExtra = null;
354
+ for (let i = 0; i < targetIndexes.length; i++) {
355
+ const matchingIndex = targetIndexes[i];
356
+ const { keyframeIndex, screenCoords, worldCoords, debugExtra } = this.mainThreadMatcher.matchDetection(this.matchingDataList[matchingIndex], featurePoints);
357
+ matchedDebugExtra = debugExtra;
358
+ if (keyframeIndex !== -1) {
359
+ const modelViewTransform = this.mainThreadEstimator.estimate({ screenCoords, worldCoords });
360
+ if (modelViewTransform) {
361
+ matchedTargetIndex = matchingIndex;
362
+ matchedModelViewTransform = modelViewTransform;
363
+ }
364
+ break;
365
+ }
366
+ }
367
+ return {
368
+ targetIndex: matchedTargetIndex,
369
+ modelViewTransform: matchedModelViewTransform,
370
+ debugExtra: matchedDebugExtra,
371
+ };
372
+ }
337
373
  _workerTrackUpdate(modelViewTransform, trackingFeatures) {
338
374
  return new Promise((resolve) => {
375
+ // If no worker available, process on main thread
376
+ if (!this.worker) {
377
+ this._trackUpdateOnMainThread(modelViewTransform, trackingFeatures).then(resolve);
378
+ return;
379
+ }
339
380
  this.workerTrackDone = (data) => {
340
381
  resolve(data.modelViewTransform);
341
382
  };
342
383
  const { worldCoords, screenCoords } = trackingFeatures;
343
- this.worker && this.worker.postMessage({
384
+ this.worker.postMessage({
344
385
  type: "trackUpdate",
345
386
  modelViewTransform,
346
387
  worldCoords,
@@ -348,6 +389,20 @@ class Controller {
348
389
  });
349
390
  });
350
391
  }
392
+ async _trackUpdateOnMainThread(modelViewTransform, trackingFeatures) {
393
+ // Lazy initialize Estimator for main thread
394
+ if (!this.mainThreadEstimator) {
395
+ const { Estimator } = await import("./estimation/estimator.js");
396
+ this.mainThreadEstimator = new Estimator(this.projectionTransform);
397
+ }
398
+ const { worldCoords, screenCoords } = trackingFeatures;
399
+ const finalModelViewTransform = this.mainThreadEstimator.refineEstimate({
400
+ initialModelViewTransform: modelViewTransform,
401
+ worldCoords,
402
+ screenCoords,
403
+ });
404
+ return finalModelViewTransform;
405
+ }
351
406
  _glModelViewMatrix(modelViewTransform, targetIndex) {
352
407
  const height = this.markerDimensions[targetIndex][1];
353
408
  const openGLWorldMatrix = [
@@ -24,11 +24,16 @@ class CropDetector {
24
24
  }
25
25
  detectMoving(input) {
26
26
  const imageData = input;
27
- // loop a few locations around center
28
- const dx = this.lastRandomIndex % 3;
29
- const dy = Math.floor(this.lastRandomIndex / 3);
30
- let startY = Math.floor(this.height / 2 - this.cropSize + (dy * this.cropSize) / 2);
31
- let startX = Math.floor(this.width / 2 - this.cropSize + (dx * this.cropSize) / 2);
27
+ // Expanded to 5x5 grid (25 positions) for better coverage
28
+ const gridSize = 5;
29
+ const dx = this.lastRandomIndex % gridSize;
30
+ const dy = Math.floor(this.lastRandomIndex / gridSize);
31
+ // Calculate offset from center, with overlap for better detection
32
+ const stepX = this.cropSize / 3;
33
+ const stepY = this.cropSize / 3;
34
+ let startY = Math.floor(this.height / 2 - this.cropSize / 2 + (dy - 2) * stepY);
35
+ let startX = Math.floor(this.width / 2 - this.cropSize / 2 + (dx - 2) * stepX);
36
+ // Clamp to valid bounds
32
37
  if (startX < 0)
33
38
  startX = 0;
34
39
  if (startY < 0)
@@ -37,7 +42,7 @@ class CropDetector {
37
42
  startX = this.width - this.cropSize - 1;
38
43
  if (startY >= this.height - this.cropSize)
39
44
  startY = this.height - this.cropSize - 1;
40
- this.lastRandomIndex = (this.lastRandomIndex + 1) % 9;
45
+ this.lastRandomIndex = (this.lastRandomIndex + 1) % (gridSize * gridSize);
41
46
  const result = this._detect(imageData, startX, startY);
42
47
  return result;
43
48
  }
@@ -152,11 +152,14 @@ class SimpleAR {
152
152
  // Matrix is column-major: [m0,m1,m2,m3, m4,m5,m6,m7, m8,m9,m10,m11, m12,m13,m14,m15]
153
153
  const tx = worldMatrix[12];
154
154
  const ty = worldMatrix[13];
155
- const scale = Math.sqrt(worldMatrix[0] ** 2 + worldMatrix[1] ** 2);
155
+ const matrixScale = Math.sqrt(worldMatrix[0] ** 2 + worldMatrix[1] ** 2);
156
156
  const rotation = Math.atan2(worldMatrix[1], worldMatrix[0]);
157
157
  // Convert from normalized coords to screen coords
158
158
  const screenX = offsetX + (videoW / 2 + tx) * scaleX;
159
159
  const screenY = offsetY + (videoH / 2 - ty) * scaleY;
160
+ // Scale factor: use a reasonable multiplier (0.5 instead of 0.01)
161
+ // The matrixScale from the tracking is in image-space coordinates
162
+ const finalScale = matrixScale * scaleX * 0.5;
160
163
  // Apply transform
161
164
  this.overlay.style.position = 'absolute';
162
165
  this.overlay.style.transformOrigin = 'center center';
@@ -165,7 +168,7 @@ class SimpleAR {
165
168
  this.overlay.style.transform = `
166
169
  translate(${screenX}px, ${screenY}px)
167
170
  translate(-50%, -50%)
168
- scale(${scale * scaleX * 0.01})
171
+ scale(${finalScale})
169
172
  rotate(${-rotation}rad)
170
173
  `;
171
174
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srsergio/taptapp-ar",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "AR Compiler for Node.js and Browser",
5
5
  "repository": {
6
6
  "type": "git",
@@ -153,6 +153,7 @@ class Controller {
153
153
  }
154
154
 
155
155
  this.markerDimensions = allDimensions;
156
+ this.matchingDataList = allMatchingData; // Store for main-thread fallback
156
157
  return { dimensions: allDimensions, matchingDataList: allMatchingData, trackingDataList: allTrackingData };
157
158
  }
158
159
 
@@ -395,6 +396,12 @@ class Controller {
395
396
 
396
397
  _workerMatch(featurePoints, targetIndexes) {
397
398
  return new Promise((resolve) => {
399
+ // If no worker available, process on main thread
400
+ if (!this.worker) {
401
+ this._matchOnMainThread(featurePoints, targetIndexes).then(resolve);
402
+ return;
403
+ }
404
+
398
405
  this.workerMatchDone = (data) => {
399
406
  resolve({
400
407
  targetIndex: data.targetIndex,
@@ -402,17 +409,63 @@ class Controller {
402
409
  debugExtra: data.debugExtra,
403
410
  });
404
411
  };
405
- this.worker && this.worker.postMessage({ type: "match", featurePoints: featurePoints, targetIndexes });
412
+ this.worker.postMessage({ type: "match", featurePoints: featurePoints, targetIndexes });
406
413
  });
407
414
  }
408
415
 
416
+ async _matchOnMainThread(featurePoints, targetIndexes) {
417
+ // Lazy initialize Matcher and Estimator for main thread
418
+ if (!this.mainThreadMatcher) {
419
+ const { Matcher } = await import("./matching/matcher.js");
420
+ const { Estimator } = await import("./estimation/estimator.js");
421
+ this.mainThreadMatcher = new Matcher(this.inputWidth, this.inputHeight, this.debugMode);
422
+ this.mainThreadEstimator = new Estimator(this.projectionTransform);
423
+ }
424
+
425
+ let matchedTargetIndex = -1;
426
+ let matchedModelViewTransform = null;
427
+ let matchedDebugExtra = null;
428
+
429
+ for (let i = 0; i < targetIndexes.length; i++) {
430
+ const matchingIndex = targetIndexes[i];
431
+
432
+ const { keyframeIndex, screenCoords, worldCoords, debugExtra } = this.mainThreadMatcher.matchDetection(
433
+ this.matchingDataList[matchingIndex],
434
+ featurePoints,
435
+ );
436
+ matchedDebugExtra = debugExtra;
437
+
438
+ if (keyframeIndex !== -1) {
439
+ const modelViewTransform = this.mainThreadEstimator.estimate({ screenCoords, worldCoords });
440
+
441
+ if (modelViewTransform) {
442
+ matchedTargetIndex = matchingIndex;
443
+ matchedModelViewTransform = modelViewTransform;
444
+ }
445
+ break;
446
+ }
447
+ }
448
+
449
+ return {
450
+ targetIndex: matchedTargetIndex,
451
+ modelViewTransform: matchedModelViewTransform,
452
+ debugExtra: matchedDebugExtra,
453
+ };
454
+ }
455
+
409
456
  _workerTrackUpdate(modelViewTransform, trackingFeatures) {
410
457
  return new Promise((resolve) => {
458
+ // If no worker available, process on main thread
459
+ if (!this.worker) {
460
+ this._trackUpdateOnMainThread(modelViewTransform, trackingFeatures).then(resolve);
461
+ return;
462
+ }
463
+
411
464
  this.workerTrackDone = (data) => {
412
465
  resolve(data.modelViewTransform);
413
466
  };
414
467
  const { worldCoords, screenCoords } = trackingFeatures;
415
- this.worker && this.worker.postMessage({
468
+ this.worker.postMessage({
416
469
  type: "trackUpdate",
417
470
  modelViewTransform,
418
471
  worldCoords,
@@ -421,6 +474,22 @@ class Controller {
421
474
  });
422
475
  }
423
476
 
477
+ async _trackUpdateOnMainThread(modelViewTransform, trackingFeatures) {
478
+ // Lazy initialize Estimator for main thread
479
+ if (!this.mainThreadEstimator) {
480
+ const { Estimator } = await import("./estimation/estimator.js");
481
+ this.mainThreadEstimator = new Estimator(this.projectionTransform);
482
+ }
483
+
484
+ const { worldCoords, screenCoords } = trackingFeatures;
485
+ const finalModelViewTransform = this.mainThreadEstimator.refineEstimate({
486
+ initialModelViewTransform: modelViewTransform,
487
+ worldCoords,
488
+ screenCoords,
489
+ });
490
+ return finalModelViewTransform;
491
+ }
492
+
424
493
  _glModelViewMatrix(modelViewTransform, targetIndex) {
425
494
  const height = this.markerDimensions[targetIndex][1];
426
495
 
@@ -33,19 +33,25 @@ class CropDetector {
33
33
  detectMoving(input) {
34
34
  const imageData = input;
35
35
 
36
- // loop a few locations around center
37
- const dx = this.lastRandomIndex % 3;
38
- const dy = Math.floor(this.lastRandomIndex / 3);
36
+ // Expanded to 5x5 grid (25 positions) for better coverage
37
+ const gridSize = 5;
38
+ const dx = this.lastRandomIndex % gridSize;
39
+ const dy = Math.floor(this.lastRandomIndex / gridSize);
39
40
 
40
- let startY = Math.floor(this.height / 2 - this.cropSize + (dy * this.cropSize) / 2);
41
- let startX = Math.floor(this.width / 2 - this.cropSize + (dx * this.cropSize) / 2);
41
+ // Calculate offset from center, with overlap for better detection
42
+ const stepX = this.cropSize / 3;
43
+ const stepY = this.cropSize / 3;
42
44
 
45
+ let startY = Math.floor(this.height / 2 - this.cropSize / 2 + (dy - 2) * stepY);
46
+ let startX = Math.floor(this.width / 2 - this.cropSize / 2 + (dx - 2) * stepX);
47
+
48
+ // Clamp to valid bounds
43
49
  if (startX < 0) startX = 0;
44
50
  if (startY < 0) startY = 0;
45
51
  if (startX >= this.width - this.cropSize) startX = this.width - this.cropSize - 1;
46
52
  if (startY >= this.height - this.cropSize) startY = this.height - this.cropSize - 1;
47
53
 
48
- this.lastRandomIndex = (this.lastRandomIndex + 1) % 9;
54
+ this.lastRandomIndex = (this.lastRandomIndex + 1) % (gridSize * gridSize);
49
55
 
50
56
  const result = this._detect(imageData, startX, startY);
51
57
  return result;
@@ -179,13 +179,17 @@ class SimpleAR {
179
179
  // Matrix is column-major: [m0,m1,m2,m3, m4,m5,m6,m7, m8,m9,m10,m11, m12,m13,m14,m15]
180
180
  const tx = worldMatrix[12];
181
181
  const ty = worldMatrix[13];
182
- const scale = Math.sqrt(worldMatrix[0] ** 2 + worldMatrix[1] ** 2);
182
+ const matrixScale = Math.sqrt(worldMatrix[0] ** 2 + worldMatrix[1] ** 2);
183
183
  const rotation = Math.atan2(worldMatrix[1], worldMatrix[0]);
184
184
 
185
185
  // Convert from normalized coords to screen coords
186
186
  const screenX = offsetX + (videoW / 2 + tx) * scaleX;
187
187
  const screenY = offsetY + (videoH / 2 - ty) * scaleY;
188
188
 
189
+ // Scale factor: use a reasonable multiplier (0.5 instead of 0.01)
190
+ // The matrixScale from the tracking is in image-space coordinates
191
+ const finalScale = matrixScale * scaleX * 0.5;
192
+
189
193
  // Apply transform
190
194
  this.overlay.style.position = 'absolute';
191
195
  this.overlay.style.transformOrigin = 'center center';
@@ -194,7 +198,7 @@ class SimpleAR {
194
198
  this.overlay.style.transform = `
195
199
  translate(${screenX}px, ${screenY}px)
196
200
  translate(-50%, -50%)
197
- scale(${scale * scaleX * 0.01})
201
+ scale(${finalScale})
198
202
  rotate(${-rotation}rad)
199
203
  `;
200
204
  }