@srsergio/taptapp-ar 1.0.78 → 1.0.80

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 (35) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/dist/compiler/controller.d.ts +22 -6
  4. package/dist/compiler/controller.js +99 -26
  5. package/dist/compiler/controller.worker.js +2 -1
  6. package/dist/compiler/estimation/refine-estimate.d.ts +2 -1
  7. package/dist/compiler/estimation/refine-estimate.js +18 -5
  8. package/dist/compiler/features/feature-base.d.ts +1 -1
  9. package/dist/compiler/features/feature-manager.d.ts +1 -1
  10. package/dist/compiler/features/feature-manager.js +2 -2
  11. package/dist/compiler/features/one-euro-filter-feature.d.ts +1 -1
  12. package/dist/compiler/features/one-euro-filter-feature.js +8 -1
  13. package/dist/compiler/matching/matching.js +1 -1
  14. package/dist/compiler/offline-compiler.d.ts +92 -8
  15. package/dist/compiler/offline-compiler.js +3 -86
  16. package/dist/compiler/simple-ar.d.ts +12 -0
  17. package/dist/compiler/simple-ar.js +46 -19
  18. package/dist/compiler/tracker/tracker.d.ts +10 -0
  19. package/dist/compiler/tracker/tracker.js +6 -4
  20. package/dist/react/TaptappAR.js +29 -2
  21. package/dist/react/use-ar.d.ts +7 -0
  22. package/dist/react/use-ar.js +16 -1
  23. package/package.json +24 -2
  24. package/src/compiler/controller.ts +112 -26
  25. package/src/compiler/controller.worker.js +2 -1
  26. package/src/compiler/estimation/refine-estimate.js +20 -3
  27. package/src/compiler/features/feature-base.ts +1 -1
  28. package/src/compiler/features/feature-manager.ts +2 -2
  29. package/src/compiler/features/one-euro-filter-feature.ts +11 -1
  30. package/src/compiler/matching/matching.js +1 -1
  31. package/src/compiler/offline-compiler.ts +3 -94
  32. package/src/compiler/simple-ar.ts +62 -20
  33. package/src/compiler/tracker/tracker.js +7 -4
  34. package/src/react/TaptappAR.tsx +41 -1
  35. package/src/react/use-ar.ts +24 -1
@@ -10,7 +10,6 @@ import { extractTrackingFeatures } from "./tracker/extract-utils.js";
10
10
  import { DetectorLite } from "./detector/detector-lite.js";
11
11
  import { build as hierarchicalClusteringBuild } from "./matching/hierarchical-clustering.js";
12
12
  import * as msgpack from "@msgpack/msgpack";
13
- import { WorkerPool } from "./utils/worker-pool.js";
14
13
  // Detect environment
15
14
  const isNode = typeof process !== "undefined" &&
16
15
  process.versions != null &&
@@ -18,35 +17,8 @@ const isNode = typeof process !== "undefined" &&
18
17
  const CURRENT_VERSION = 7; // Protocol v7: Moonshot - 4-bit Packed Tracking Data
19
18
  export class OfflineCompiler {
20
19
  data = null;
21
- workerPool = null;
22
20
  constructor() {
23
- // Workers only in Node.js
24
- if (!isNode) {
25
- console.log("🌐 OfflineCompiler: Browser mode (no workers)");
26
- }
27
- }
28
- async _initNodeWorkers() {
29
- try {
30
- const pathModule = "path";
31
- const urlModule = "url";
32
- const osModule = "os";
33
- const workerThreadsModule = "node:worker_threads";
34
- const [path, url, os, { Worker }] = await Promise.all([
35
- import(/* @vite-ignore */ pathModule),
36
- import(/* @vite-ignore */ urlModule),
37
- import(/* @vite-ignore */ osModule),
38
- import(/* @vite-ignore */ workerThreadsModule)
39
- ]);
40
- const __filename = url.fileURLToPath(import.meta.url);
41
- const __dirname = path.dirname(__filename);
42
- const workerPath = path.join(__dirname, "node-worker.js");
43
- // Limit workers to avoid freezing system
44
- const numWorkers = Math.min(os.cpus().length, 4);
45
- this.workerPool = new WorkerPool(workerPath, numWorkers, Worker);
46
- }
47
- catch (e) {
48
- console.log("⚡ OfflineCompiler: Running without workers (initialization failed)", e);
49
- }
21
+ console.log("⚡ OfflineCompiler: Main thread mode (no workers)");
50
22
  }
51
23
  async compileImageTargets(images, progressCallback) {
52
24
  console.time("⏱️ Compilación total");
@@ -86,24 +58,7 @@ export class OfflineCompiler {
86
58
  return this.data;
87
59
  }
88
60
  async _compileTarget(targetImages, progressCallback) {
89
- if (isNode)
90
- await this._initNodeWorkers();
91
- if (this.workerPool) {
92
- const progressMap = new Float32Array(targetImages.length);
93
- const wrappedPromises = targetImages.map((targetImage, index) => {
94
- return this.workerPool.runTask({
95
- type: 'compile-all', // 🚀 MOONSHOT: Combined task
96
- targetImage,
97
- onProgress: (p) => {
98
- progressMap[index] = p;
99
- const sum = progressMap.reduce((a, b) => a + b, 0);
100
- progressCallback(sum / targetImages.length);
101
- }
102
- });
103
- });
104
- return Promise.all(wrappedPromises);
105
- }
106
- // Fallback or non-worker implementation: run match and track sequentially
61
+ // Run match and track sequentially to match browser behavior exactly
107
62
  const matchingResults = await this._compileMatch(targetImages, (p) => progressCallback(p * 0.5));
108
63
  const trackingResults = await this._compileTrack(targetImages, (p) => progressCallback(50 + p * 0.5));
109
64
  return targetImages.map((_, i) => ({
@@ -114,25 +69,6 @@ export class OfflineCompiler {
114
69
  async _compileMatch(targetImages, progressCallback) {
115
70
  const percentPerImage = 100 / targetImages.length;
116
71
  let currentPercent = 0;
117
- if (isNode)
118
- await this._initNodeWorkers();
119
- if (this.workerPool) {
120
- const progressMap = new Float32Array(targetImages.length);
121
- const wrappedPromises = targetImages.map((targetImage, index) => {
122
- return this.workerPool.runTask({
123
- type: 'match',
124
- targetImage,
125
- percentPerImage,
126
- basePercent: 0,
127
- onProgress: (p) => {
128
- progressMap[index] = p;
129
- const sum = progressMap.reduce((a, b) => a + b, 0);
130
- progressCallback(sum);
131
- }
132
- });
133
- });
134
- return Promise.all(wrappedPromises);
135
- }
136
72
  const results = [];
137
73
  for (let i = 0; i < targetImages.length; i++) {
138
74
  const targetImage = targetImages[i];
@@ -165,23 +101,6 @@ export class OfflineCompiler {
165
101
  async _compileTrack(targetImages, progressCallback) {
166
102
  const percentPerImage = 100 / targetImages.length;
167
103
  let currentPercent = 0;
168
- if (this.workerPool) {
169
- const progressMap = new Float32Array(targetImages.length);
170
- const wrappedPromises = targetImages.map((targetImage, index) => {
171
- return this.workerPool.runTask({
172
- type: 'compile',
173
- targetImage,
174
- percentPerImage,
175
- basePercent: 0,
176
- onProgress: (p) => {
177
- progressMap[index] = p;
178
- const sum = progressMap.reduce((a, b) => a + b, 0);
179
- progressCallback(sum);
180
- }
181
- });
182
- });
183
- return Promise.all(wrappedPromises);
184
- }
185
104
  const results = [];
186
105
  for (let i = 0; i < targetImages.length; i++) {
187
106
  const targetImage = targetImages[i];
@@ -407,9 +326,7 @@ export class OfflineCompiler {
407
326
  };
408
327
  }
409
328
  async destroy() {
410
- if (this.workerPool) {
411
- await this.workerPool.destroy();
412
- }
329
+ // No workers to destroy
413
330
  }
414
331
  _pack4Bit(data) {
415
332
  const length = data.length;
@@ -17,6 +17,12 @@ export interface SimpleAROptions {
17
17
  onUpdate?: ((data: {
18
18
  targetIndex: number;
19
19
  worldMatrix: number[];
20
+ screenCoords?: {
21
+ x: number;
22
+ y: number;
23
+ }[];
24
+ reliabilities?: number[];
25
+ stabilities?: number[];
20
26
  }) => void) | null;
21
27
  cameraConfig?: MediaStreamConstraints['video'];
22
28
  debug?: boolean;
@@ -35,6 +41,12 @@ declare class SimpleAR {
35
41
  onUpdateCallback: ((data: {
36
42
  targetIndex: number;
37
43
  worldMatrix: number[];
44
+ screenCoords?: {
45
+ x: number;
46
+ y: number;
47
+ }[];
48
+ reliabilities?: number[];
49
+ stabilities?: number[];
38
50
  }) => void) | null;
39
51
  cameraConfig: MediaStreamConstraints['video'];
40
52
  debug: boolean;
@@ -1,5 +1,4 @@
1
1
  import { Controller } from "./controller.js";
2
- import { OneEuroFilter } from "../libs/one-euro-filter.js";
3
2
  import { projectToScreen } from "./utils/projection.js";
4
3
  class SimpleAR {
5
4
  container;
@@ -114,7 +113,7 @@ class SimpleAR {
114
113
  if (this.debug)
115
114
  this._updateDebugPanel(this.isTracking);
116
115
  }
117
- const { targetIndex, worldMatrix, modelViewTransform } = data;
116
+ const { targetIndex, worldMatrix, modelViewTransform, screenCoords, reliabilities, stabilities } = data;
118
117
  if (worldMatrix) {
119
118
  if (!this.isTracking) {
120
119
  this.isTracking = true;
@@ -122,30 +121,58 @@ class SimpleAR {
122
121
  this.onFound && this.onFound({ targetIndex });
123
122
  }
124
123
  this.lastMatrix = worldMatrix;
125
- if (!this.filters[targetIndex]) {
126
- this.filters[targetIndex] = new OneEuroFilter({ minCutOff: 0.8, beta: 0.2 });
124
+ // We use the matrix from the controller directly (it's already filtered there)
125
+ this._positionOverlay(modelViewTransform, targetIndex);
126
+ // Project points to screen coordinates
127
+ let projectedPoints = [];
128
+ if (screenCoords && screenCoords.length > 0) {
129
+ const containerRect = this.container.getBoundingClientRect();
130
+ const videoW = this.video.videoWidth;
131
+ const videoH = this.video.videoHeight;
132
+ const isPortrait = containerRect.height > containerRect.width;
133
+ const isVideoLandscape = videoW > videoH;
134
+ const needsRotation = isPortrait && isVideoLandscape;
135
+ const proj = this.controller.projectionTransform;
136
+ const vW = needsRotation ? videoH : videoW;
137
+ const vH = needsRotation ? videoW : videoH;
138
+ const pScale = Math.max(containerRect.width / vW, containerRect.height / vH);
139
+ const dW = vW * pScale;
140
+ const dH = vH * pScale;
141
+ const oX = (containerRect.width - dW) / 2;
142
+ const oY = (containerRect.height - dH) / 2;
143
+ projectedPoints = screenCoords.map((p) => {
144
+ let sx, sy;
145
+ if (needsRotation) {
146
+ sx = oX + (dW / 2) - (p.y - proj[1][2]) * pScale;
147
+ sy = oY + (dH / 2) + (p.x - proj[0][2]) * pScale;
148
+ }
149
+ else {
150
+ sx = oX + (dW / 2) + (p.x - proj[0][2]) * pScale;
151
+ sy = oY + (dH / 2) + (p.y - proj[1][2]) * pScale;
152
+ }
153
+ return { x: sx, y: sy };
154
+ });
127
155
  }
128
- const flatMVT = [
129
- modelViewTransform[0][0], modelViewTransform[0][1], modelViewTransform[0][2], modelViewTransform[0][3],
130
- modelViewTransform[1][0], modelViewTransform[1][1], modelViewTransform[1][2], modelViewTransform[1][3],
131
- modelViewTransform[2][0], modelViewTransform[2][1], modelViewTransform[2][2], modelViewTransform[2][3]
132
- ];
133
- const smoothedFlat = this.filters[targetIndex].filter(Date.now(), flatMVT);
134
- const smoothedMVT = [
135
- [smoothedFlat[0], smoothedFlat[1], smoothedFlat[2], smoothedFlat[3]],
136
- [smoothedFlat[4], smoothedFlat[5], smoothedFlat[6], smoothedFlat[7]],
137
- [smoothedFlat[8], smoothedFlat[9], smoothedFlat[10], smoothedFlat[11]]
138
- ];
139
- this._positionOverlay(smoothedMVT, targetIndex);
140
- this.onUpdateCallback && this.onUpdateCallback({ targetIndex, worldMatrix });
156
+ this.onUpdateCallback && this.onUpdateCallback({
157
+ targetIndex,
158
+ worldMatrix,
159
+ screenCoords: projectedPoints,
160
+ reliabilities,
161
+ stabilities
162
+ });
141
163
  }
142
164
  else {
143
165
  if (this.isTracking) {
144
166
  this.isTracking = false;
145
- if (this.filters[targetIndex])
146
- this.filters[targetIndex].reset();
147
167
  this.overlay && (this.overlay.style.opacity = '0');
148
168
  this.onLost && this.onLost({ targetIndex });
169
+ this.onUpdateCallback && this.onUpdateCallback({
170
+ targetIndex,
171
+ worldMatrix: null,
172
+ screenCoords: [],
173
+ reliabilities: [],
174
+ stabilities: []
175
+ });
149
176
  }
150
177
  }
151
178
  }
@@ -11,6 +11,13 @@ export class Tracker {
11
11
  templateBuffer: Float32Array<ArrayBuffer>;
12
12
  dummyRun(inputData: any): void;
13
13
  track(inputData: any, lastModelViewTransform: any, targetIndex: any): {
14
+ worldCoords: never[];
15
+ screenCoords: never[];
16
+ reliabilities: never[];
17
+ debugExtra: {};
18
+ indices?: undefined;
19
+ octaveIndex?: undefined;
20
+ } | {
14
21
  worldCoords: {
15
22
  x: number;
16
23
  y: number;
@@ -20,6 +27,9 @@ export class Tracker {
20
27
  x: number;
21
28
  y: number;
22
29
  }[];
30
+ reliabilities: number[];
31
+ indices: number[];
32
+ octaveIndex: any;
23
33
  debugExtra: {};
24
34
  };
25
35
  lastOctaveIndex: any[] | undefined;
@@ -1,9 +1,9 @@
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 = 34; // Increased from 18 to 34 for much better fast-motion tracking
4
+ const AR2_SEARCH_SIZE = 25; // Reduced from 34 to 25 to prevent background latching
5
5
  const AR2_SEARCH_GAP = 1;
6
- const AR2_SIM_THRESH = 0.6;
6
+ const AR2_SIM_THRESH = 0.65; // Increased from 0.6 to reduce false positives
7
7
  const TRACKING_KEYFRAME = 0; // 0: 128px (optimized)
8
8
  class Tracker {
9
9
  constructor(markerDimensions, trackingDataList, projectionTransform, inputWidth, inputHeight, debugMode = false) {
@@ -82,6 +82,7 @@ class Tracker {
82
82
  const screenCoords = [];
83
83
  const goodTrack = [];
84
84
  const { px, py, s: scale } = trackingFrame;
85
+ const reliabilities = [];
85
86
  for (let i = 0; i < matchingPoints.length; i++) {
86
87
  if (sim[i] > AR2_SIM_THRESH && i < px.length) {
87
88
  goodTrack.push(i);
@@ -92,6 +93,7 @@ class Tracker {
92
93
  y: py[i] / scale,
93
94
  z: 0,
94
95
  });
96
+ reliabilities.push(sim[i]);
95
97
  }
96
98
  }
97
99
  // 2.1 Spatial distribution check: Avoid getting stuck in corners/noise
@@ -110,7 +112,7 @@ class Tracker {
110
112
  const detectedDiagonal = Math.sqrt((maxX - minX) ** 2 + (maxY - minY) ** 2);
111
113
  // If the points cover too little space compared to the screen size of the marker, it's a glitch
112
114
  if (detectedDiagonal < screenW * 0.15) {
113
- return { worldCoords: [], screenCoords: [], debugExtra };
115
+ return { worldCoords: [], screenCoords: [], reliabilities: [], debugExtra };
114
116
  }
115
117
  }
116
118
  if (this.debugMode) {
@@ -122,7 +124,7 @@ class Tracker {
122
124
  trackedPoints: screenCoords,
123
125
  };
124
126
  }
125
- return { worldCoords, screenCoords, debugExtra };
127
+ return { worldCoords, screenCoords, reliabilities, indices: goodTrack, octaveIndex, debugExtra };
126
128
  }
127
129
  /**
128
130
  * Pure JS implementation of NCC matching
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo } from "react";
3
3
  import { useAR } from "./use-ar.js";
4
4
  export const TaptappAR = ({ config, className = "", showScanningOverlay = true, showErrorOverlay = true }) => {
5
- const { containerRef, overlayRef, status, toggleVideo } = useAR(config);
5
+ const { containerRef, overlayRef, status, toggleVideo, trackedPoints } = useAR(config);
6
6
  // Simple heuristic to determine if it's a video or image
7
7
  // based on the presence of videoSrc and common extensions
8
8
  const isVideo = useMemo(() => {
@@ -12,7 +12,13 @@ export const TaptappAR = ({ config, className = "", showScanningOverlay = true,
12
12
  const url = config.videoSrc.toLowerCase().split('?')[0];
13
13
  return videoExtensions.some(ext => url.endsWith(ext)) || config.videoSrc.includes('video');
14
14
  }, [config.videoSrc]);
15
- return (_jsxs("div", { className: `taptapp-ar-wrapper ${className} ${status}`, style: { position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }, children: [showScanningOverlay && status === "scanning" && (_jsx("div", { className: "taptapp-ar-overlay taptapp-ar-scanning", children: _jsxs("div", { className: "scanning-content", children: [_jsxs("div", { className: "scanning-frame", children: [_jsx("img", { className: "target-preview", src: config.targetImageSrc, alt: "Target", crossOrigin: "anonymous" }), _jsx("div", { className: "scanning-line" })] }), _jsx("p", { className: "scanning-text", children: "Apunta a la imagen para comenzar" })] }) })), showErrorOverlay && status === "error" && (_jsx("div", { className: "taptapp-ar-overlay taptapp-ar-error", children: _jsxs("div", { className: "error-content", children: [_jsx("span", { className: "error-icon", children: "\u26A0\uFE0F" }), _jsx("p", { className: "error-title", children: "No se pudo iniciar AR" }), _jsx("p", { className: "error-text", children: "Verifica los permisos de c\u00E1mara" }), _jsx("button", { className: "retry-btn", onClick: () => window.location.reload(), children: "Reintentar" })] }) })), _jsx("div", { ref: containerRef, className: "taptapp-ar-container", onClick: toggleVideo, style: { width: '100%', height: '100%' }, children: isVideo ? (_jsx("video", { ref: overlayRef, className: "taptapp-ar-overlay-element", src: config.videoSrc, preload: "auto", loop: true, playsInline: true, muted: true, crossOrigin: "anonymous" })) : (_jsx("img", { ref: overlayRef, className: "taptapp-ar-overlay-element", src: config.videoSrc || config.targetImageSrc, crossOrigin: "anonymous", alt: "AR Overlay" })) }), _jsx("style", { children: `
15
+ return (_jsxs("div", { className: `taptapp-ar-wrapper ${className} ${status}`, style: { position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }, children: [showScanningOverlay && status === "scanning" && (_jsx("div", { className: "taptapp-ar-overlay taptapp-ar-scanning", children: _jsxs("div", { className: "scanning-content", children: [_jsxs("div", { className: "scanning-frame", children: [_jsx("img", { className: "target-preview", src: config.targetImageSrc, alt: "Target", crossOrigin: "anonymous" }), _jsx("div", { className: "scanning-line" })] }), _jsx("p", { className: "scanning-text", children: "Apunta a la imagen para comenzar" })] }) })), showErrorOverlay && status === "error" && (_jsx("div", { className: "taptapp-ar-overlay taptapp-ar-error", children: _jsxs("div", { className: "error-content", children: [_jsx("span", { className: "error-icon", children: "\u26A0\uFE0F" }), _jsx("p", { className: "error-title", children: "No se pudo iniciar AR" }), _jsx("p", { className: "error-text", children: "Verifica los permisos de c\u00E1mara" }), _jsx("button", { className: "retry-btn", onClick: () => window.location.reload(), children: "Reintentar" })] }) })), _jsx("div", { ref: containerRef, className: "taptapp-ar-container", onClick: toggleVideo, style: { width: '100%', height: '100%' }, children: isVideo ? (_jsx("video", { ref: overlayRef, className: "taptapp-ar-overlay-element", src: config.videoSrc, preload: "auto", loop: true, playsInline: true, muted: true, crossOrigin: "anonymous" })) : (_jsx("img", { ref: overlayRef, className: "taptapp-ar-overlay-element", src: config.videoSrc || config.targetImageSrc, crossOrigin: "anonymous", alt: "AR Overlay" })) }), status === "tracking" && (_jsx("div", { className: "taptapp-ar-points-overlay", children: trackedPoints.filter(p => p.reliability > 0.7).map((point, i) => (_jsx("div", { className: "tracking-point", style: {
16
+ left: `${point.x}px`,
17
+ top: `${point.y}px`,
18
+ width: `${(2 + point.reliability * 6) * (0.4 + point.stability * 0.6)}px`,
19
+ height: `${(2 + point.reliability * 6) * (0.4 + point.stability * 0.6)}px`,
20
+ opacity: (0.3 + (point.reliability * 0.4)) * (0.2 + point.stability * 0.8)
21
+ } }, i))) })), _jsx("style", { children: `
16
22
  .taptapp-ar-wrapper {
17
23
  background: #000;
18
24
  color: white;
@@ -92,6 +98,27 @@ export const TaptappAR = ({ config, className = "", showScanningOverlay = true,
92
98
  display: block;
93
99
  width: 100%;
94
100
  height: auto;
101
+ opacity: 0;
102
+ pointer-events: none;
103
+ transition: opacity 0.3s ease;
104
+ }
105
+ .taptapp-ar-points-overlay {
106
+ position: absolute;
107
+ top: 0;
108
+ left: 0;
109
+ width: 100%;
110
+ height: 100%;
111
+ pointer-events: none;
112
+ z-index: 100; /* High z-index to be above overlay */
113
+ }
114
+ .tracking-point {
115
+ position: absolute;
116
+ background: black;
117
+ border: 1px solid rgba(255,255,255,0.5); /* Better contrast */
118
+ border-radius: 50%;
119
+ transform: translate(-50%, -50%);
120
+ box-shadow: 0 0 2px rgba(255, 255, 255, 0.8);
121
+ pointer-events: none;
95
122
  }
96
123
  ` })] }));
97
124
  };
@@ -1,10 +1,17 @@
1
1
  import type { ARConfig } from "./types.js";
2
2
  export type ARStatus = "scanning" | "tracking" | "error";
3
+ export interface TrackedPoint {
4
+ x: number;
5
+ y: number;
6
+ reliability: number;
7
+ stability: number;
8
+ }
3
9
  export interface UseARReturn {
4
10
  containerRef: React.RefObject<HTMLDivElement>;
5
11
  overlayRef: React.RefObject<HTMLVideoElement | HTMLImageElement>;
6
12
  status: ARStatus;
7
13
  isPlaying: boolean;
8
14
  toggleVideo: () => Promise<void>;
15
+ trackedPoints: TrackedPoint[];
9
16
  }
10
17
  export declare const useAR: (config: ARConfig) => UseARReturn;
@@ -4,6 +4,7 @@ export const useAR = (config) => {
4
4
  const overlayRef = useRef(null);
5
5
  const [status, setStatus] = useState("scanning");
6
6
  const [isPlaying, setIsPlaying] = useState(false);
7
+ const [trackedPoints, setTrackedPoints] = useState([]);
7
8
  const arInstanceRef = useRef(null);
8
9
  const toggleVideo = useCallback(async () => {
9
10
  const overlay = overlayRef.current;
@@ -38,6 +39,18 @@ export const useAR = (config) => {
38
39
  targetSrc: config.targetTaarSrc,
39
40
  overlay: overlayRef.current,
40
41
  scale: config.scale,
42
+ debug: false,
43
+ onUpdate: ({ screenCoords, reliabilities, stabilities }) => {
44
+ if (screenCoords && reliabilities && stabilities) {
45
+ const points = screenCoords.map((p, i) => ({
46
+ x: p.x,
47
+ y: p.y,
48
+ reliability: reliabilities[i],
49
+ stability: stabilities[i]
50
+ }));
51
+ setTrackedPoints(points);
52
+ }
53
+ },
41
54
  onFound: async ({ targetIndex }) => {
42
55
  console.log(`🎯 Target ${targetIndex} detected!`);
43
56
  if (!isMounted)
@@ -59,6 +72,7 @@ export const useAR = (config) => {
59
72
  if (!isMounted)
60
73
  return;
61
74
  setStatus("scanning");
75
+ setTrackedPoints([]);
62
76
  const overlay = overlayRef.current;
63
77
  if (overlay instanceof HTMLVideoElement) {
64
78
  overlay.pause();
@@ -89,6 +103,7 @@ export const useAR = (config) => {
89
103
  overlayRef,
90
104
  status,
91
105
  isPlaying,
92
- toggleVideo
106
+ toggleVideo,
107
+ trackedPoints
93
108
  };
94
109
  };
package/package.json CHANGED
@@ -1,7 +1,29 @@
1
1
  {
2
2
  "name": "@srsergio/taptapp-ar",
3
- "version": "1.0.78",
4
- "description": "AR Compiler for Node.js and Browser",
3
+ "version": "1.0.80",
4
+ "description": "Ultra-fast Augmented Reality (AR) SDK for Node.js and Browser. Image tracking with 100% pure JavaScript, zero-dependencies, and high-performance compilation.",
5
+ "keywords": [
6
+ "augmented reality",
7
+ "ar",
8
+ "image tracking",
9
+ "computer vision",
10
+ "webar",
11
+ "a-frame",
12
+ "three.js",
13
+ "ar.js",
14
+ "mindar",
15
+ "tracking",
16
+ "opencv",
17
+ "javascript",
18
+ "webgl",
19
+ "cross-platform",
20
+ "no-wasm",
21
+ "no-tfjs",
22
+ "webxr",
23
+ "natural-feature-tracking"
24
+ ],
25
+ "author": "Sergio Lázaro <slazaro.dev@gmail.com>",
26
+ "license": "MIT",
5
27
  "repository": {
6
28
  "type": "git",
7
29
  "url": "git+https://github.com/srsergiolazaro/taptapp-ar.git"