@srsergio/taptapp-ar 1.0.79 → 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 (32) 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/simple-ar.d.ts +12 -0
  15. package/dist/compiler/simple-ar.js +46 -19
  16. package/dist/compiler/tracker/tracker.d.ts +10 -0
  17. package/dist/compiler/tracker/tracker.js +6 -4
  18. package/dist/react/TaptappAR.js +26 -2
  19. package/dist/react/use-ar.d.ts +7 -0
  20. package/dist/react/use-ar.js +15 -1
  21. package/package.json +24 -2
  22. package/src/compiler/controller.ts +112 -26
  23. package/src/compiler/controller.worker.js +2 -1
  24. package/src/compiler/estimation/refine-estimate.js +20 -3
  25. package/src/compiler/features/feature-base.ts +1 -1
  26. package/src/compiler/features/feature-manager.ts +2 -2
  27. package/src/compiler/features/one-euro-filter-feature.ts +11 -1
  28. package/src/compiler/matching/matching.js +1 -1
  29. package/src/compiler/simple-ar.ts +62 -20
  30. package/src/compiler/tracker/tracker.js +7 -4
  31. package/src/react/TaptappAR.tsx +38 -1
  32. package/src/react/use-ar.ts +23 -1
@@ -13,7 +13,13 @@ export interface SimpleAROptions {
13
13
  scale?: number;
14
14
  onFound?: ((data: { targetIndex: number }) => void | Promise<void>) | null;
15
15
  onLost?: ((data: { targetIndex: number }) => void | Promise<void>) | null;
16
- onUpdate?: ((data: { targetIndex: number, worldMatrix: number[] }) => void) | null;
16
+ onUpdate?: ((data: {
17
+ targetIndex: number,
18
+ worldMatrix: number[],
19
+ screenCoords?: { x: number, y: number }[],
20
+ reliabilities?: number[],
21
+ stabilities?: number[]
22
+ }) => void) | null;
17
23
  cameraConfig?: MediaStreamConstraints['video'];
18
24
  debug?: boolean;
19
25
  }
@@ -25,7 +31,13 @@ class SimpleAR {
25
31
  scaleMultiplier: number;
26
32
  onFound: ((data: { targetIndex: number }) => void | Promise<void>) | null;
27
33
  onLost: ((data: { targetIndex: number }) => void | Promise<void>) | null;
28
- onUpdateCallback: ((data: { targetIndex: number, worldMatrix: number[] }) => void) | null;
34
+ onUpdateCallback: ((data: {
35
+ targetIndex: number,
36
+ worldMatrix: number[],
37
+ screenCoords?: { x: number, y: number }[],
38
+ reliabilities?: number[],
39
+ stabilities?: number[]
40
+ }) => void) | null;
29
41
  cameraConfig: MediaStreamConstraints['video'];
30
42
  debug: boolean;
31
43
 
@@ -151,7 +163,7 @@ class SimpleAR {
151
163
  if (this.debug) this._updateDebugPanel(this.isTracking);
152
164
  }
153
165
 
154
- const { targetIndex, worldMatrix, modelViewTransform } = data;
166
+ const { targetIndex, worldMatrix, modelViewTransform, screenCoords, reliabilities, stabilities } = data;
155
167
 
156
168
  if (worldMatrix) {
157
169
  if (!this.isTracking) {
@@ -162,31 +174,61 @@ class SimpleAR {
162
174
 
163
175
  this.lastMatrix = worldMatrix;
164
176
 
165
- if (!this.filters[targetIndex]) {
166
- this.filters[targetIndex] = new OneEuroFilter({ minCutOff: 0.8, beta: 0.2 });
177
+ // We use the matrix from the controller directly (it's already filtered there)
178
+ this._positionOverlay(modelViewTransform, targetIndex);
179
+
180
+ // Project points to screen coordinates
181
+ let projectedPoints = [];
182
+ if (screenCoords && screenCoords.length > 0) {
183
+ const containerRect = this.container.getBoundingClientRect();
184
+ const videoW = this.video!.videoWidth;
185
+ const videoH = this.video!.videoHeight;
186
+ const isPortrait = containerRect.height > containerRect.width;
187
+ const isVideoLandscape = videoW > videoH;
188
+ const needsRotation = isPortrait && isVideoLandscape;
189
+ const proj = this.controller!.projectionTransform;
190
+
191
+ const vW = needsRotation ? videoH : videoW;
192
+ const vH = needsRotation ? videoW : videoH;
193
+ const pScale = Math.max(containerRect.width / vW, containerRect.height / vH);
194
+ const dW = vW * pScale;
195
+ const dH = vH * pScale;
196
+ const oX = (containerRect.width - dW) / 2;
197
+ const oY = (containerRect.height - dH) / 2;
198
+
199
+ projectedPoints = screenCoords.map((p: any) => {
200
+ let sx, sy;
201
+ if (needsRotation) {
202
+ sx = oX + (dW / 2) - (p.y - proj[1][2]) * pScale;
203
+ sy = oY + (dH / 2) + (p.x - proj[0][2]) * pScale;
204
+ } else {
205
+ sx = oX + (dW / 2) + (p.x - proj[0][2]) * pScale;
206
+ sy = oY + (dH / 2) + (p.y - proj[1][2]) * pScale;
207
+ }
208
+ return { x: sx, y: sy };
209
+ });
167
210
  }
168
211
 
169
- const flatMVT = [
170
- modelViewTransform[0][0], modelViewTransform[0][1], modelViewTransform[0][2], modelViewTransform[0][3],
171
- modelViewTransform[1][0], modelViewTransform[1][1], modelViewTransform[1][2], modelViewTransform[1][3],
172
- modelViewTransform[2][0], modelViewTransform[2][1], modelViewTransform[2][2], modelViewTransform[2][3]
173
- ];
174
- const smoothedFlat = this.filters[targetIndex].filter(Date.now(), flatMVT);
175
- const smoothedMVT = [
176
- [smoothedFlat[0], smoothedFlat[1], smoothedFlat[2], smoothedFlat[3]],
177
- [smoothedFlat[4], smoothedFlat[5], smoothedFlat[6], smoothedFlat[7]],
178
- [smoothedFlat[8], smoothedFlat[9], smoothedFlat[10], smoothedFlat[11]]
179
- ];
180
-
181
- this._positionOverlay(smoothedMVT, targetIndex);
182
- this.onUpdateCallback && this.onUpdateCallback({ targetIndex, worldMatrix });
212
+ this.onUpdateCallback && this.onUpdateCallback({
213
+ targetIndex,
214
+ worldMatrix,
215
+ screenCoords: projectedPoints,
216
+ reliabilities,
217
+ stabilities
218
+ });
183
219
 
184
220
  } else {
185
221
  if (this.isTracking) {
186
222
  this.isTracking = false;
187
- if (this.filters[targetIndex]) this.filters[targetIndex].reset();
188
223
  this.overlay && (this.overlay.style.opacity = '0');
189
224
  this.onLost && this.onLost({ targetIndex });
225
+ this.onUpdateCallback && this.onUpdateCallback({
226
+ targetIndex,
227
+ worldMatrix: null as any,
228
+ screenCoords: [],
229
+ reliabilities: [],
230
+ stabilities: []
231
+ });
190
232
  }
191
233
  }
192
234
  }
@@ -2,9 +2,9 @@ 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 = 34; // Increased from 18 to 34 for much better fast-motion tracking
5
+ const AR2_SEARCH_SIZE = 25; // Reduced from 34 to 25 to prevent background latching
6
6
  const AR2_SEARCH_GAP = 1;
7
- const AR2_SIM_THRESH = 0.6;
7
+ const AR2_SIM_THRESH = 0.65; // Increased from 0.6 to reduce false positives
8
8
 
9
9
  const TRACKING_KEYFRAME = 0; // 0: 128px (optimized)
10
10
 
@@ -119,6 +119,8 @@ class Tracker {
119
119
 
120
120
  const { px, py, s: scale } = trackingFrame;
121
121
 
122
+ const reliabilities = [];
123
+
122
124
  for (let i = 0; i < matchingPoints.length; i++) {
123
125
  if (sim[i] > AR2_SIM_THRESH && i < px.length) {
124
126
  goodTrack.push(i);
@@ -133,6 +135,7 @@ class Tracker {
133
135
  y: py[i] / scale,
134
136
  z: 0,
135
137
  });
138
+ reliabilities.push(sim[i]);
136
139
  }
137
140
  }
138
141
 
@@ -147,7 +150,7 @@ class Tracker {
147
150
 
148
151
  // If the points cover too little space compared to the screen size of the marker, it's a glitch
149
152
  if (detectedDiagonal < screenW * 0.15) {
150
- return { worldCoords: [], screenCoords: [], debugExtra };
153
+ return { worldCoords: [], screenCoords: [], reliabilities: [], debugExtra };
151
154
  }
152
155
  }
153
156
  if (this.debugMode) {
@@ -160,7 +163,7 @@ class Tracker {
160
163
  };
161
164
  }
162
165
 
163
- return { worldCoords, screenCoords, debugExtra };
166
+ return { worldCoords, screenCoords, reliabilities, indices: goodTrack, octaveIndex, debugExtra };
164
167
  }
165
168
 
166
169
  /**
@@ -15,7 +15,7 @@ export const TaptappAR: React.FC<TaptappARProps> = ({
15
15
  showScanningOverlay = true,
16
16
  showErrorOverlay = true
17
17
  }) => {
18
- const { containerRef, overlayRef, status, toggleVideo } = useAR(config);
18
+ const { containerRef, overlayRef, status, toggleVideo, trackedPoints } = useAR(config);
19
19
 
20
20
  // Simple heuristic to determine if it's a video or image
21
21
  // based on the presence of videoSrc and common extensions
@@ -89,6 +89,25 @@ export const TaptappAR: React.FC<TaptappARProps> = ({
89
89
  )}
90
90
  </div>
91
91
 
92
+ {/* Tracking Points Layer */}
93
+ {status === "tracking" && (
94
+ <div className="taptapp-ar-points-overlay">
95
+ {trackedPoints.filter(p => p.reliability > 0.7).map((point, i) => (
96
+ <div
97
+ key={i}
98
+ className="tracking-point"
99
+ style={{
100
+ left: `${point.x}px`,
101
+ top: `${point.y}px`,
102
+ width: `${(2 + point.reliability * 6) * (0.4 + point.stability * 0.6)}px`,
103
+ height: `${(2 + point.reliability * 6) * (0.4 + point.stability * 0.6)}px`,
104
+ opacity: (0.3 + (point.reliability * 0.4)) * (0.2 + point.stability * 0.8)
105
+ }}
106
+ />
107
+ ))}
108
+ </div>
109
+ )}
110
+
92
111
  <style>{`
93
112
  .taptapp-ar-wrapper {
94
113
  background: #000;
@@ -173,6 +192,24 @@ export const TaptappAR: React.FC<TaptappARProps> = ({
173
192
  pointer-events: none;
174
193
  transition: opacity 0.3s ease;
175
194
  }
195
+ .taptapp-ar-points-overlay {
196
+ position: absolute;
197
+ top: 0;
198
+ left: 0;
199
+ width: 100%;
200
+ height: 100%;
201
+ pointer-events: none;
202
+ z-index: 100; /* High z-index to be above overlay */
203
+ }
204
+ .tracking-point {
205
+ position: absolute;
206
+ background: black;
207
+ border: 1px solid rgba(255,255,255,0.5); /* Better contrast */
208
+ border-radius: 50%;
209
+ transform: translate(-50%, -50%);
210
+ box-shadow: 0 0 2px rgba(255, 255, 255, 0.8);
211
+ pointer-events: none;
212
+ }
176
213
  `}</style>
177
214
  </div>
178
215
  );
@@ -4,12 +4,20 @@ import type { SimpleAR as SimpleARType } from "../compiler/simple-ar.js";
4
4
 
5
5
  export type ARStatus = "scanning" | "tracking" | "error";
6
6
 
7
+ export interface TrackedPoint {
8
+ x: number;
9
+ y: number;
10
+ reliability: number;
11
+ stability: number;
12
+ }
13
+
7
14
  export interface UseARReturn {
8
15
  containerRef: React.RefObject<HTMLDivElement>;
9
16
  overlayRef: React.RefObject<HTMLVideoElement | HTMLImageElement>;
10
17
  status: ARStatus;
11
18
  isPlaying: boolean;
12
19
  toggleVideo: () => Promise<void>;
20
+ trackedPoints: TrackedPoint[];
13
21
  }
14
22
 
15
23
  export const useAR = (config: ARConfig): UseARReturn => {
@@ -17,6 +25,7 @@ export const useAR = (config: ARConfig): UseARReturn => {
17
25
  const overlayRef = useRef<HTMLVideoElement | HTMLImageElement>(null);
18
26
  const [status, setStatus] = useState<ARStatus>("scanning");
19
27
  const [isPlaying, setIsPlaying] = useState(false);
28
+ const [trackedPoints, setTrackedPoints] = useState<TrackedPoint[]>([]);
20
29
  const arInstanceRef = useRef<SimpleARType | null>(null);
21
30
 
22
31
  const toggleVideo = useCallback(async () => {
@@ -53,6 +62,17 @@ export const useAR = (config: ARConfig): UseARReturn => {
53
62
  overlay: overlayRef.current!,
54
63
  scale: config.scale,
55
64
  debug: false,
65
+ onUpdate: ({ screenCoords, reliabilities, stabilities }) => {
66
+ if (screenCoords && reliabilities && stabilities) {
67
+ const points = screenCoords.map((p, i) => ({
68
+ x: p.x,
69
+ y: p.y,
70
+ reliability: reliabilities[i],
71
+ stability: stabilities[i]
72
+ }));
73
+ setTrackedPoints(points);
74
+ }
75
+ },
56
76
  onFound: async ({ targetIndex }) => {
57
77
  console.log(`🎯 Target ${targetIndex} detected!`);
58
78
  if (!isMounted) return;
@@ -72,6 +92,7 @@ export const useAR = (config: ARConfig): UseARReturn => {
72
92
  console.log(`👋 Target ${targetIndex} lost`);
73
93
  if (!isMounted) return;
74
94
  setStatus("scanning");
95
+ setTrackedPoints([]);
75
96
 
76
97
  const overlay = overlayRef.current;
77
98
  if (overlay instanceof HTMLVideoElement) {
@@ -105,6 +126,7 @@ export const useAR = (config: ARConfig): UseARReturn => {
105
126
  overlayRef,
106
127
  status,
107
128
  isPlaying,
108
- toggleVideo
129
+ toggleVideo,
130
+ trackedPoints
109
131
  };
110
132
  };