@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.
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/dist/compiler/controller.d.ts +22 -6
- package/dist/compiler/controller.js +99 -26
- package/dist/compiler/controller.worker.js +2 -1
- package/dist/compiler/estimation/refine-estimate.d.ts +2 -1
- package/dist/compiler/estimation/refine-estimate.js +18 -5
- package/dist/compiler/features/feature-base.d.ts +1 -1
- package/dist/compiler/features/feature-manager.d.ts +1 -1
- package/dist/compiler/features/feature-manager.js +2 -2
- package/dist/compiler/features/one-euro-filter-feature.d.ts +1 -1
- package/dist/compiler/features/one-euro-filter-feature.js +8 -1
- package/dist/compiler/matching/matching.js +1 -1
- package/dist/compiler/offline-compiler.d.ts +92 -8
- package/dist/compiler/offline-compiler.js +3 -86
- package/dist/compiler/simple-ar.d.ts +12 -0
- package/dist/compiler/simple-ar.js +46 -19
- package/dist/compiler/tracker/tracker.d.ts +10 -0
- package/dist/compiler/tracker/tracker.js +6 -4
- package/dist/react/TaptappAR.js +29 -2
- package/dist/react/use-ar.d.ts +7 -0
- package/dist/react/use-ar.js +16 -1
- package/package.json +24 -2
- package/src/compiler/controller.ts +112 -26
- package/src/compiler/controller.worker.js +2 -1
- package/src/compiler/estimation/refine-estimate.js +20 -3
- package/src/compiler/features/feature-base.ts +1 -1
- package/src/compiler/features/feature-manager.ts +2 -2
- package/src/compiler/features/one-euro-filter-feature.ts +11 -1
- package/src/compiler/matching/matching.js +1 -1
- package/src/compiler/offline-compiler.ts +3 -94
- package/src/compiler/simple-ar.ts +62 -20
- package/src/compiler/tracker/tracker.js +7 -4
- package/src/react/TaptappAR.tsx +41 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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 =
|
|
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
|
package/dist/react/TaptappAR.js
CHANGED
|
@@ -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("
|
|
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
|
};
|
package/dist/react/use-ar.d.ts
CHANGED
|
@@ -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;
|
package/dist/react/use-ar.js
CHANGED
|
@@ -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.
|
|
4
|
-
"description": "AR
|
|
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"
|