@srsergio/taptapp-ar 1.0.62 β 1.0.67
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/README.md +79 -105
- package/dist/compiler/detector/crop-detector.js +1 -1
- package/dist/compiler/detector/detector-lite.d.ts +0 -1
- package/dist/compiler/detector/detector-lite.js +2 -4
- package/dist/compiler/image-list.js +4 -3
- package/dist/compiler/node-worker.js +37 -16
- package/dist/compiler/offline-compiler.d.ts +1 -4
- package/dist/compiler/offline-compiler.js +17 -8
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -3
- package/dist/react/types.d.ts +2 -5
- package/dist/react/types.js +5 -7
- package/package.json +5 -20
- package/src/compiler/detector/crop-detector.js +1 -1
- package/src/compiler/detector/detector-lite.js +2 -5
- package/src/compiler/image-list.js +4 -3
- package/src/compiler/node-worker.js +43 -16
- package/src/compiler/offline-compiler.ts +18 -8
- package/src/index.ts +1 -4
- package/src/react/types.ts +7 -12
package/README.md
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
# @srsergio/taptapp-ar
|
|
2
2
|
|
|
3
|
-
<p align="center">
|
|
4
|
-
<a href="./docs/technical-paper.pdf">π <b>Technical Paper (PDF)</b></a> |
|
|
5
|
-
<a href="./docs/index.html">π <b>Official Website</b></a> |
|
|
6
|
-
<a href="./analysis/INDEX.md">π <b>Analysis Report</b></a>
|
|
7
|
-
</p>
|
|
8
|
-
|
|
9
3
|
π **TapTapp AR** is a high-performance Augmented Reality (AR) toolkit for **Node.js** and **Browser** environments. It provides an ultra-fast offline compiler and a lightweight runtime for image tracking.
|
|
10
4
|
|
|
11
5
|
**100% Pure JavaScript**: This package is now completely independent of **TensorFlow.js** for both compilation and real-time tracking, resulting in massive performance gains and zero-latency initialization.
|
|
@@ -83,104 +77,43 @@ const binaryBuffer = compiler.exportData();
|
|
|
83
77
|
|
|
84
78
|
## π₯ Runtime Usage (AR Tracking)
|
|
85
79
|
|
|
86
|
-
### 1.
|
|
87
|
-
The
|
|
88
|
-
|
|
89
|
-
```javascript
|
|
90
|
-
import { SimpleAR } from '@srsergio/taptapp-ar';
|
|
80
|
+
### 1. Simple A-Frame Integration
|
|
81
|
+
The easiest way to use TapTapp AR in a web app:
|
|
91
82
|
|
|
92
|
-
const ar = new SimpleAR({
|
|
93
|
-
container: document.getElementById('ar-container'),
|
|
94
|
-
targetSrc: './my-target.taar', // Single URL or array: ['./a.taar', './b.taar']
|
|
95
|
-
overlay: document.getElementById('my-overlay'),
|
|
96
|
-
onFound: ({ targetIndex }) => console.log(`Target ${targetIndex} detected! π―`),
|
|
97
|
-
onLost: ({ targetIndex }) => console.log(`Target ${targetIndex} lost π`)
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
await ar.start();
|
|
101
|
-
|
|
102
|
-
// When done:
|
|
103
|
-
ar.stop();
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
#### π Minimal HTML Example
|
|
107
83
|
```html
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
container: document.getElementById('ar-container'),
|
|
118
|
-
targetSrc: './targets.taar',
|
|
119
|
-
overlay: document.getElementById('my-overlay'),
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
ar.start();
|
|
123
|
-
</script>
|
|
84
|
+
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
|
|
85
|
+
<script src="path/to/@srsergio/taptapp-ar/dist/index.js"></script>
|
|
86
|
+
|
|
87
|
+
<a-scene taar-image="imageTargetSrc: ./targets.taar;">
|
|
88
|
+
<a-camera position="0 0 0" look-controls="enabled: false"></a-camera>
|
|
89
|
+
<a-entity taar-image-target="targetIndex: 0">
|
|
90
|
+
<a-plane position="0 0 0" height="0.552" width="1"></a-plane>
|
|
91
|
+
</a-entity>
|
|
92
|
+
</a-scene>
|
|
124
93
|
```
|
|
125
94
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
| :--- | :--- | :--- |
|
|
129
|
-
| `container` | β
| DOM element where video + overlay render |
|
|
130
|
-
| `targetSrc` | β
| URL to your `.taar` file |
|
|
131
|
-
| `overlay` | β
| DOM element to position on the target |
|
|
132
|
-
| `onFound` | β | Callback when target is detected |
|
|
133
|
-
| `onLost` | β | Callback when target is lost |
|
|
134
|
-
| `onUpdate` | β | Called cada frame con `{ targetIndex, worldMatrix }` |
|
|
135
|
-
| `cameraConfig` | β | Config de cΓ‘mara (por defecto: `{ facingMode: 'environment', width: 1280, height: 720 }`) |
|
|
136
|
-
|
|
137
|
-
---
|
|
95
|
+
### 2. High-Performance Three.js Wrapper
|
|
96
|
+
For custom Three.js applications:
|
|
138
97
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
The fastest and most modern way to build AR apps with React. It supports **Code Splitting** and is **100% SSR-Safe** (Next.js, Astro, Remix).
|
|
142
|
-
|
|
143
|
-
#### π Quick Start: `<TaptappAR />`
|
|
144
|
-
Drop the component into your app. It handles camera permissions, scanning animations, and video/image overlays automatically.
|
|
98
|
+
```javascript
|
|
99
|
+
import { TaarThree } from '@srsergio/taptapp-ar';
|
|
145
100
|
|
|
146
|
-
|
|
147
|
-
|
|
101
|
+
const taarThree = new TaarThree({
|
|
102
|
+
container: document.querySelector("#container"),
|
|
103
|
+
imageTargetSrc: './targets.taar',
|
|
104
|
+
});
|
|
148
105
|
|
|
149
|
-
const
|
|
150
|
-
// Use mapDataToPropsConfig to convert your raw data into ARConfig
|
|
151
|
-
const config = mapDataToPropsConfig(data);
|
|
106
|
+
const {renderer, scene, camera} = taarThree;
|
|
152
107
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
<TaptappAR config={config} />
|
|
156
|
-
</div>
|
|
157
|
-
);
|
|
158
|
-
};
|
|
159
|
-
```
|
|
108
|
+
const anchor = taarThree.addAnchor(0);
|
|
109
|
+
// Add your 3D models to anchor.group
|
|
160
110
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
import { useAR } from '@srsergio/taptapp-ar';
|
|
166
|
-
|
|
167
|
-
const CustomAR = ({ config }) => {
|
|
168
|
-
const { containerRef, overlayRef, status, toggleVideo } = useAR(config);
|
|
169
|
-
|
|
170
|
-
return (
|
|
171
|
-
<div ref={containerRef} style={{ position: 'relative' }} onClick={toggleVideo}>
|
|
172
|
-
{/* Custom Scanning UI */}
|
|
173
|
-
{status === 'scanning' && <div className="my-loader">Scanning...</div>}
|
|
174
|
-
|
|
175
|
-
{/* Video Overlay */}
|
|
176
|
-
<video ref={overlayRef} src={config.videoSrc} loop muted playsInline />
|
|
177
|
-
</div>
|
|
178
|
-
);
|
|
179
|
-
};
|
|
111
|
+
await taarThree.start();
|
|
112
|
+
renderer.setAnimationLoop(() => {
|
|
113
|
+
renderer.render(scene, camera);
|
|
114
|
+
});
|
|
180
115
|
```
|
|
181
116
|
|
|
182
|
-
---
|
|
183
|
-
|
|
184
117
|
### 3. Raw Controller (Advanced & Custom Engines)
|
|
185
118
|
The `Controller` is the core engine of TapTapp AR. You can use it to build your own AR components or integrate tracking into custom 3D engines.
|
|
186
119
|
|
|
@@ -246,8 +179,56 @@ if (targetIndex !== -1) {
|
|
|
246
179
|
}
|
|
247
180
|
```
|
|
248
181
|
|
|
249
|
-
###
|
|
250
|
-
|
|
182
|
+
### 4. Vanilla JS (No Framework) π¦
|
|
183
|
+
The **simplest way** to use ARβno Three.js, no A-Frame. Just overlay an image on the tracked target.
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
import { SimpleAR } from '@srsergio/taptapp-ar';
|
|
187
|
+
|
|
188
|
+
const ar = new SimpleAR({
|
|
189
|
+
container: document.getElementById('ar-container'),
|
|
190
|
+
targetSrc: './my-target.taar', // Single URL or array: ['./a.taar', './b.taar']
|
|
191
|
+
overlay: document.getElementById('my-overlay'),
|
|
192
|
+
onFound: ({ targetIndex }) => console.log(`Target ${targetIndex} detected! π―`),
|
|
193
|
+
onLost: ({ targetIndex }) => console.log(`Target ${targetIndex} lost π`)
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await ar.start();
|
|
197
|
+
|
|
198
|
+
// When done:
|
|
199
|
+
ar.stop();
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### π Minimal HTML
|
|
203
|
+
```html
|
|
204
|
+
<div id="ar-container" style="width: 100vw; height: 100vh;">
|
|
205
|
+
<img id="my-overlay" src="./overlay.png"
|
|
206
|
+
style="opacity: 0; z-index: 1; width: 200px; transition: opacity 0.3s;" />
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<script type="module">
|
|
210
|
+
import { SimpleAR } from '@srsergio/taptapp-ar';
|
|
211
|
+
|
|
212
|
+
const ar = new SimpleAR({
|
|
213
|
+
container: document.getElementById('ar-container'),
|
|
214
|
+
targetSrc: './targets.taar',
|
|
215
|
+
overlay: document.getElementById('my-overlay'),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
ar.start();
|
|
219
|
+
</script>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### βοΈ SimpleAR Options
|
|
223
|
+
| Option | Required | Description |
|
|
224
|
+
| :--- | :--- | :--- |
|
|
225
|
+
| `container` | β
| DOM element where video + overlay render |
|
|
226
|
+
| `targetSrc` | β
| URL to your `.taar` file |
|
|
227
|
+
| `overlay` | β
| DOM element to position on the target |
|
|
228
|
+
| `onFound` | β | Callback when target is detected |
|
|
229
|
+
| `onLost` | β | Callback when target is lost |
|
|
230
|
+
| `onUpdate` | β | Called each frame with `{ targetIndex, worldMatrix }` |
|
|
231
|
+
| `cameraConfig` | β | Camera constraints (default: `{ facingMode: 'environment', width: 1280, height: 720 }`) |
|
|
251
232
|
|
|
252
233
|
---
|
|
253
234
|
|
|
@@ -261,15 +242,8 @@ TapTapp AR uses a proprietary **Moonshot Vision Codec** that is significantly mo
|
|
|
261
242
|
|
|
262
243
|
---
|
|
263
244
|
|
|
264
|
-
## π License &
|
|
265
|
-
|
|
266
|
-
**Taptapp AR** is created and maintained by **Sergio Lazaro**.
|
|
267
|
-
|
|
268
|
-
This project is licensed under the **GPL-3.0 License**.
|
|
269
|
-
This ensures that the project remains open and free, and that authorship is properly recognized. No "closed-source" usage is allowed without a commercial agreement.
|
|
270
|
-
|
|
271
|
-
Commercial licenses are available for proprietary applications. Please contact the author for details.
|
|
245
|
+
## π License & Credits
|
|
272
246
|
|
|
273
|
-
|
|
274
|
-
This project evolved from the incredible work of [MindAR](https://github.com/hiukim/mind-ar-js). While the codebase has been extensively rewritten and optimized for performance, we gratefully acknowledge the foundation laid by the original authors.
|
|
247
|
+
MIT Β© [srsergiolazaro](https://github.com/srsergiolazaro)
|
|
275
248
|
|
|
249
|
+
Based on the core research of MindAR, but completely re-written for high-performance binary processing and JS-only execution.
|
|
@@ -8,7 +8,7 @@ class CropDetector {
|
|
|
8
8
|
let minDimension = Math.min(width, height) / 2;
|
|
9
9
|
let cropSize = Math.pow(2, Math.round(Math.log(minDimension) / Math.log(2)));
|
|
10
10
|
this.cropSize = cropSize;
|
|
11
|
-
this.detector = new DetectorLite(cropSize, cropSize, { useLSH: true
|
|
11
|
+
this.detector = new DetectorLite(cropSize, cropSize, { useLSH: true });
|
|
12
12
|
this.lastRandomIndex = 4;
|
|
13
13
|
}
|
|
14
14
|
detect(input) {
|
|
@@ -9,7 +9,6 @@ export class DetectorLite {
|
|
|
9
9
|
useGPU: any;
|
|
10
10
|
useLSH: any;
|
|
11
11
|
numOctaves: number;
|
|
12
|
-
maxFeaturesPerBucket: any;
|
|
13
12
|
/**
|
|
14
13
|
* Detecta caracterΓsticas en una imagen en escala de grises
|
|
15
14
|
* @param {Float32Array|Uint8Array} imageData - Datos de imagen (width * height)
|
|
@@ -15,6 +15,7 @@ import { computeLSH64, computeFullFREAK, packLSHIntoDescriptor } from "../utils/
|
|
|
15
15
|
const PYRAMID_MIN_SIZE = 4; // Restored to 4 for better small-scale detection
|
|
16
16
|
// PYRAMID_MAX_OCTAVE ya no es necesario, el lΓmite lo da PYRAMID_MIN_SIZE
|
|
17
17
|
const NUM_BUCKETS_PER_DIMENSION = 10;
|
|
18
|
+
const MAX_FEATURES_PER_BUCKET = 30; // Maximized to ensure robustness in Moonshot mode
|
|
18
19
|
const ORIENTATION_NUM_BINS = 36;
|
|
19
20
|
const FREAK_EXPANSION_FACTOR = 7.0;
|
|
20
21
|
// Global GPU mode flag
|
|
@@ -47,9 +48,6 @@ export class DetectorLite {
|
|
|
47
48
|
break;
|
|
48
49
|
}
|
|
49
50
|
this.numOctaves = options.maxOctaves !== undefined ? Math.min(numOctaves, options.maxOctaves) : numOctaves;
|
|
50
|
-
// π SMART BITRATE (VBR): Internal logic to decide feature density based on scale
|
|
51
|
-
const scale = options.scale !== undefined ? options.scale : 1.0;
|
|
52
|
-
this.maxFeaturesPerBucket = options.maxFeaturesPerBucket || Math.max(4, Math.floor(12 * Math.sqrt(scale)));
|
|
53
51
|
}
|
|
54
52
|
/**
|
|
55
53
|
* Detecta caracterΓsticas en una imagen en escala de grises
|
|
@@ -307,7 +305,7 @@ export class DetectorLite {
|
|
|
307
305
|
*/
|
|
308
306
|
_applyPrune(extremas) {
|
|
309
307
|
const nBuckets = NUM_BUCKETS_PER_DIMENSION;
|
|
310
|
-
const nFeatures =
|
|
308
|
+
const nFeatures = MAX_FEATURES_PER_BUCKET;
|
|
311
309
|
// Agrupar por buckets
|
|
312
310
|
const buckets = [];
|
|
313
311
|
for (let i = 0; i < nBuckets * nBuckets; i++) {
|
|
@@ -4,7 +4,7 @@ import { resize } from "./utils/images.js";
|
|
|
4
4
|
* Un valor mΓ‘s bajo permite detectar imΓ‘genes mΓ‘s pequeΓ±as pero aumenta el tiempo de procesamiento
|
|
5
5
|
* @constant {number}
|
|
6
6
|
*/
|
|
7
|
-
const MIN_IMAGE_PIXEL_SIZE =
|
|
7
|
+
const MIN_IMAGE_PIXEL_SIZE = 32;
|
|
8
8
|
/**
|
|
9
9
|
* Construye una lista de imΓ‘genes con diferentes escalas para detecciΓ³n de caracterΓsticas
|
|
10
10
|
* @param {Object} inputImage - Imagen de entrada con propiedades width, height y data
|
|
@@ -16,8 +16,9 @@ const buildImageList = (inputImage) => {
|
|
|
16
16
|
let c = minScale;
|
|
17
17
|
while (true) {
|
|
18
18
|
scaleList.push(c);
|
|
19
|
-
// Optimization:
|
|
20
|
-
|
|
19
|
+
// Optimization: Paso balanceado (aprox 1.5)
|
|
20
|
+
// Mejor cobertura que 2.0, pero mucho mΓ‘s ligero que 1.41 o 1.26
|
|
21
|
+
c *= Math.pow(2.0, 0.6);
|
|
21
22
|
if (c >= 0.95) {
|
|
22
23
|
c = 1;
|
|
23
24
|
break;
|
|
@@ -64,19 +64,40 @@ parentPort.on('message', async (msg) => {
|
|
|
64
64
|
else if (msg.type === 'match') {
|
|
65
65
|
const { targetImage, percentPerImage, basePercent } = msg;
|
|
66
66
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
// π MOONSHOT: Only run detector ONCE on full-res image.
|
|
68
|
+
// DetectorLite internally builds a pyramid (octaves 1.0, 0.5, 0.25, etc.)
|
|
69
|
+
const detector = new DetectorLite(targetImage.width, targetImage.height, {
|
|
70
|
+
useLSH: true
|
|
71
|
+
});
|
|
72
|
+
parentPort.postMessage({ type: 'progress', percent: basePercent + percentPerImage * 0.1 });
|
|
73
|
+
const { featurePoints: allPoints } = detector.detect(targetImage.data);
|
|
74
|
+
parentPort.postMessage({ type: 'progress', percent: basePercent + percentPerImage * 0.5 });
|
|
75
|
+
// Group points by their scale (octave)
|
|
76
|
+
const scalesMap = new Map();
|
|
77
|
+
for (const p of allPoints) {
|
|
78
|
+
const octaveScale = p.scale;
|
|
79
|
+
let list = scalesMap.get(octaveScale);
|
|
80
|
+
if (!list) {
|
|
81
|
+
list = [];
|
|
82
|
+
scalesMap.set(octaveScale, list);
|
|
83
|
+
}
|
|
84
|
+
// Coordinates in p are already full-res.
|
|
85
|
+
// We need them relative to the scaled image for the keyframe.
|
|
86
|
+
list.push({
|
|
87
|
+
...p,
|
|
88
|
+
x: p.x / octaveScale,
|
|
89
|
+
y: p.y / octaveScale,
|
|
90
|
+
scale: 1.0 // Keypoint scale is always 1.0 relative to its own keyframe image
|
|
78
91
|
});
|
|
79
|
-
|
|
92
|
+
}
|
|
93
|
+
// Optional: Run another detector pass at an intermediate scale to improve coverage
|
|
94
|
+
// (e.g. at 1/1.41 ratio) if tracking robustness suffers.
|
|
95
|
+
// For now, let's stick to octaves for MAXIMUM speed.
|
|
96
|
+
const keyframes = [];
|
|
97
|
+
const sortedScales = Array.from(scalesMap.keys()).sort((a, b) => a - b);
|
|
98
|
+
const percentPerScale = (percentPerImage * 0.4) / sortedScales.length;
|
|
99
|
+
for (const s of sortedScales) {
|
|
100
|
+
const ps = scalesMap.get(s);
|
|
80
101
|
const sortedPs = sortPoints(ps);
|
|
81
102
|
const maximaPoints = sortedPs.filter((p) => p.maxima);
|
|
82
103
|
const minimaPoints = sortedPs.filter((p) => !p.maxima);
|
|
@@ -87,13 +108,13 @@ parentPort.on('message', async (msg) => {
|
|
|
87
108
|
minimaPoints,
|
|
88
109
|
maximaPointsCluster,
|
|
89
110
|
minimaPointsCluster,
|
|
90
|
-
width:
|
|
91
|
-
height:
|
|
92
|
-
scale:
|
|
111
|
+
width: Math.round(targetImage.width / s),
|
|
112
|
+
height: Math.round(targetImage.height / s),
|
|
113
|
+
scale: 1.0 / s, // keyframe.scale is relative to full target image
|
|
93
114
|
});
|
|
94
115
|
parentPort.postMessage({
|
|
95
116
|
type: 'progress',
|
|
96
|
-
percent: basePercent +
|
|
117
|
+
percent: basePercent + percentPerImage * 0.6 + keyframes.length * percentPerScale
|
|
97
118
|
});
|
|
98
119
|
}
|
|
99
120
|
parentPort.postMessage({
|
|
@@ -12,10 +12,7 @@ export declare class OfflineCompiler {
|
|
|
12
12
|
constructor();
|
|
13
13
|
_initNodeWorkers(): Promise<void>;
|
|
14
14
|
compileImageTargets(images: any[], progressCallback: (p: number) => void): Promise<any>;
|
|
15
|
-
_compileTarget(targetImages: any[], progressCallback: (p: number) => void): Promise<
|
|
16
|
-
matchingData: any;
|
|
17
|
-
trackingData: any;
|
|
18
|
-
}[]>;
|
|
15
|
+
_compileTarget(targetImages: any[], progressCallback: (p: number) => void): Promise<any[]>;
|
|
19
16
|
_compileMatch(targetImages: any[], progressCallback: (p: number) => void): Promise<any[]>;
|
|
20
17
|
_compileTrack(targetImages: any[], progressCallback: (p: number) => void): Promise<any[]>;
|
|
21
18
|
compileTrack({ progressCallback, targetImages, basePercent }: {
|
|
@@ -88,8 +88,22 @@ export class OfflineCompiler {
|
|
|
88
88
|
async _compileTarget(targetImages, progressCallback) {
|
|
89
89
|
if (isNode)
|
|
90
90
|
await this._initNodeWorkers();
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
93
107
|
const matchingResults = await this._compileMatch(targetImages, (p) => progressCallback(p * 0.5));
|
|
94
108
|
const trackingResults = await this._compileTrack(targetImages, (p) => progressCallback(50 + p * 0.5));
|
|
95
109
|
return targetImages.map((_, i) => ({
|
|
@@ -126,12 +140,7 @@ export class OfflineCompiler {
|
|
|
126
140
|
const percentPerImageScale = percentPerImage / imageList.length;
|
|
127
141
|
const keyframes = [];
|
|
128
142
|
for (const image of imageList) {
|
|
129
|
-
|
|
130
|
-
const detector = new DetectorLite(image.width, image.height, {
|
|
131
|
-
useLSH: true,
|
|
132
|
-
maxOctaves: 1,
|
|
133
|
-
scale: image.scale
|
|
134
|
-
});
|
|
143
|
+
const detector = new DetectorLite(image.width, image.height, { useLSH: true });
|
|
135
144
|
const { featurePoints: ps } = detector.detect(image.data);
|
|
136
145
|
const maximaPoints = ps.filter((p) => p.maxima);
|
|
137
146
|
const minimaPoints = ps.filter((p) => !p.maxima);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from "./react/use-ar.js";
|
|
3
|
-
export * from "./react/TaptappAR.js";
|
|
1
|
+
export * from "./react/types";
|
|
4
2
|
export * from "./compiler/offline-compiler.js";
|
|
5
3
|
export { Controller } from "./compiler/controller.js";
|
|
6
4
|
export { SimpleAR } from "./compiler/simple-ar.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from "./react/use-ar.js";
|
|
3
|
-
export * from "./react/TaptappAR.js";
|
|
1
|
+
export * from "./react/types";
|
|
4
2
|
export * from "./compiler/offline-compiler.js";
|
|
5
3
|
export { Controller } from "./compiler/controller.js";
|
|
6
4
|
export { SimpleAR } from "./compiler/simple-ar.js";
|
package/dist/react/types.d.ts
CHANGED
|
@@ -3,23 +3,20 @@ export interface ARConfig {
|
|
|
3
3
|
targetImageSrc: string;
|
|
4
4
|
targetTaarSrc: string;
|
|
5
5
|
videoSrc: string;
|
|
6
|
-
overlaySrc: string;
|
|
7
6
|
videoWidth: number;
|
|
8
7
|
videoHeight: number;
|
|
9
8
|
scale: number;
|
|
10
|
-
overlayType: "video" | "image";
|
|
11
9
|
}
|
|
12
10
|
export interface ARDataItem {
|
|
13
11
|
id: string;
|
|
14
|
-
type: "photos" | "videoNative" | "ar"
|
|
12
|
+
type: "photos" | "videoNative" | "ar";
|
|
15
13
|
images?: {
|
|
16
14
|
image: string;
|
|
17
15
|
fileId: string;
|
|
18
16
|
}[];
|
|
19
17
|
url?: string;
|
|
20
|
-
fileId?: string;
|
|
21
18
|
scale?: number;
|
|
22
19
|
width?: number;
|
|
23
20
|
height?: number;
|
|
24
21
|
}
|
|
25
|
-
export declare function mapDataToPropsConfig(data:
|
|
22
|
+
export declare function mapDataToPropsConfig(data: any[]): ARConfig;
|
package/dist/react/types.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
export function mapDataToPropsConfig(data) {
|
|
2
2
|
const photos = data.find((item) => item.type === "photos");
|
|
3
|
-
const
|
|
3
|
+
const video = data.find((item) => item.type === "videoNative");
|
|
4
4
|
const ar = data.find((item) => item.type === "ar");
|
|
5
5
|
return {
|
|
6
6
|
cardId: photos?.id || "",
|
|
7
7
|
targetImageSrc: photos?.images?.[0]?.image || "",
|
|
8
8
|
targetTaarSrc: ar?.url || "",
|
|
9
|
-
videoSrc:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
scale: overlay?.scale || 1,
|
|
14
|
-
overlayType: overlay?.type === "videoNative" ? "video" : "image",
|
|
9
|
+
videoSrc: video?.url || "",
|
|
10
|
+
videoWidth: video?.width || 0,
|
|
11
|
+
videoHeight: video?.height || 0,
|
|
12
|
+
scale: video?.scale || 1,
|
|
15
13
|
};
|
|
16
14
|
}
|
package/package.json
CHANGED
|
@@ -1,24 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@srsergio/taptapp-ar",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"
|
|
5
|
-
"license": "GPL-3.0",
|
|
6
|
-
"description": "Ultra-fast, lightweight Augmented Reality Image Tracking SDK for the web. Features an optimized offline compiler, React components, and compatibility with Three.js/A-Frame. No heavy ML frameworks required.",
|
|
7
|
-
"keywords": [
|
|
8
|
-
"web-ar",
|
|
9
|
-
"augmented-reality",
|
|
10
|
-
"image-tracking",
|
|
11
|
-
"ar-compiler",
|
|
12
|
-
"computer-vision",
|
|
13
|
-
"three-js",
|
|
14
|
-
"react-ar",
|
|
15
|
-
"a-frame",
|
|
16
|
-
"mind-ar",
|
|
17
|
-
"offline-ar",
|
|
18
|
-
"webgl",
|
|
19
|
-
"markerless-tracking",
|
|
20
|
-
"taptapp"
|
|
21
|
-
],
|
|
3
|
+
"version": "1.0.67",
|
|
4
|
+
"description": "AR Compiler for Node.js and Browser",
|
|
22
5
|
"repository": {
|
|
23
6
|
"type": "git",
|
|
24
7
|
"url": "git+https://github.com/srsergiolazaro/taptapp-ar.git"
|
|
@@ -74,11 +57,13 @@
|
|
|
74
57
|
"tinyqueue": "^2.0.3"
|
|
75
58
|
},
|
|
76
59
|
"devDependencies": {
|
|
77
|
-
"@types/node": "^
|
|
60
|
+
"@types/node": "^20.14.2",
|
|
78
61
|
"@types/react": "^18.3.3",
|
|
79
62
|
"@types/react-dom": "^18.3.0",
|
|
80
63
|
"@types/three": "^0.170.0",
|
|
81
64
|
"jimp": "^1.6.0",
|
|
65
|
+
"react": "^18.3.1",
|
|
66
|
+
"react-dom": "^18.3.1",
|
|
82
67
|
"typescript": "^5.4.5",
|
|
83
68
|
"vitest": "^4.0.16"
|
|
84
69
|
},
|
|
@@ -11,7 +11,7 @@ class CropDetector {
|
|
|
11
11
|
let cropSize = Math.pow(2, Math.round(Math.log(minDimension) / Math.log(2)));
|
|
12
12
|
this.cropSize = cropSize;
|
|
13
13
|
|
|
14
|
-
this.detector = new DetectorLite(cropSize, cropSize, { useLSH: true
|
|
14
|
+
this.detector = new DetectorLite(cropSize, cropSize, { useLSH: true });
|
|
15
15
|
|
|
16
16
|
this.lastRandomIndex = 4;
|
|
17
17
|
}
|
|
@@ -19,6 +19,7 @@ const PYRAMID_MIN_SIZE = 4; // Restored to 4 for better small-scale detection
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
const NUM_BUCKETS_PER_DIMENSION = 10;
|
|
22
|
+
const MAX_FEATURES_PER_BUCKET = 30; // Maximized to ensure robustness in Moonshot mode
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
const ORIENTATION_NUM_BINS = 36;
|
|
@@ -57,10 +58,6 @@ export class DetectorLite {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
this.numOctaves = options.maxOctaves !== undefined ? Math.min(numOctaves, options.maxOctaves) : numOctaves;
|
|
60
|
-
|
|
61
|
-
// π SMART BITRATE (VBR): Internal logic to decide feature density based on scale
|
|
62
|
-
const scale = options.scale !== undefined ? options.scale : 1.0;
|
|
63
|
-
this.maxFeaturesPerBucket = options.maxFeaturesPerBucket || Math.max(4, Math.floor(12 * Math.sqrt(scale)));
|
|
64
61
|
}
|
|
65
62
|
|
|
66
63
|
/**
|
|
@@ -353,7 +350,7 @@ export class DetectorLite {
|
|
|
353
350
|
*/
|
|
354
351
|
_applyPrune(extremas) {
|
|
355
352
|
const nBuckets = NUM_BUCKETS_PER_DIMENSION;
|
|
356
|
-
const nFeatures =
|
|
353
|
+
const nFeatures = MAX_FEATURES_PER_BUCKET;
|
|
357
354
|
|
|
358
355
|
// Agrupar por buckets
|
|
359
356
|
const buckets = [];
|
|
@@ -5,7 +5,7 @@ import { resize } from "./utils/images.js";
|
|
|
5
5
|
* Un valor mΓ‘s bajo permite detectar imΓ‘genes mΓ‘s pequeΓ±as pero aumenta el tiempo de procesamiento
|
|
6
6
|
* @constant {number}
|
|
7
7
|
*/
|
|
8
|
-
const MIN_IMAGE_PIXEL_SIZE =
|
|
8
|
+
const MIN_IMAGE_PIXEL_SIZE = 32;
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
@@ -21,8 +21,9 @@ const buildImageList = (inputImage) => {
|
|
|
21
21
|
let c = minScale;
|
|
22
22
|
while (true) {
|
|
23
23
|
scaleList.push(c);
|
|
24
|
-
// Optimization:
|
|
25
|
-
|
|
24
|
+
// Optimization: Paso balanceado (aprox 1.5)
|
|
25
|
+
// Mejor cobertura que 2.0, pero mucho mΓ‘s ligero que 1.41 o 1.26
|
|
26
|
+
c *= Math.pow(2.0, 0.6);
|
|
26
27
|
if (c >= 0.95) {
|
|
27
28
|
c = 1;
|
|
28
29
|
break;
|
|
@@ -71,22 +71,49 @@ parentPort.on('message', async (msg) => {
|
|
|
71
71
|
const { targetImage, percentPerImage, basePercent } = msg;
|
|
72
72
|
|
|
73
73
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
74
|
+
// π MOONSHOT: Only run detector ONCE on full-res image.
|
|
75
|
+
// DetectorLite internally builds a pyramid (octaves 1.0, 0.5, 0.25, etc.)
|
|
76
|
+
const detector = new DetectorLite(targetImage.width, targetImage.height, {
|
|
77
|
+
useLSH: true
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
parentPort.postMessage({ type: 'progress', percent: basePercent + percentPerImage * 0.1 });
|
|
81
|
+
|
|
82
|
+
const { featurePoints: allPoints } = detector.detect(targetImage.data);
|
|
83
|
+
|
|
84
|
+
parentPort.postMessage({ type: 'progress', percent: basePercent + percentPerImage * 0.5 });
|
|
78
85
|
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
// Group points by their scale (octave)
|
|
87
|
+
const scalesMap = new Map();
|
|
88
|
+
for (const p of allPoints) {
|
|
89
|
+
const octaveScale = p.scale;
|
|
90
|
+
let list = scalesMap.get(octaveScale);
|
|
91
|
+
if (!list) {
|
|
92
|
+
list = [];
|
|
93
|
+
scalesMap.set(octaveScale, list);
|
|
94
|
+
}
|
|
81
95
|
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
// Coordinates in p are already full-res.
|
|
97
|
+
// We need them relative to the scaled image for the keyframe.
|
|
98
|
+
list.push({
|
|
99
|
+
...p,
|
|
100
|
+
x: p.x / octaveScale,
|
|
101
|
+
y: p.y / octaveScale,
|
|
102
|
+
scale: 1.0 // Keypoint scale is always 1.0 relative to its own keyframe image
|
|
87
103
|
});
|
|
88
|
-
|
|
104
|
+
}
|
|
89
105
|
|
|
106
|
+
// Optional: Run another detector pass at an intermediate scale to improve coverage
|
|
107
|
+
// (e.g. at 1/1.41 ratio) if tracking robustness suffers.
|
|
108
|
+
// For now, let's stick to octaves for MAXIMUM speed.
|
|
109
|
+
|
|
110
|
+
const keyframes = [];
|
|
111
|
+
const sortedScales = Array.from(scalesMap.keys()).sort((a, b) => a - b);
|
|
112
|
+
|
|
113
|
+
const percentPerScale = (percentPerImage * 0.4) / sortedScales.length;
|
|
114
|
+
|
|
115
|
+
for (const s of sortedScales) {
|
|
116
|
+
const ps = scalesMap.get(s);
|
|
90
117
|
const sortedPs = sortPoints(ps);
|
|
91
118
|
const maximaPoints = sortedPs.filter((p) => p.maxima);
|
|
92
119
|
const minimaPoints = sortedPs.filter((p) => !p.maxima);
|
|
@@ -99,14 +126,14 @@ parentPort.on('message', async (msg) => {
|
|
|
99
126
|
minimaPoints,
|
|
100
127
|
maximaPointsCluster,
|
|
101
128
|
minimaPointsCluster,
|
|
102
|
-
width:
|
|
103
|
-
height:
|
|
104
|
-
scale:
|
|
129
|
+
width: Math.round(targetImage.width / s),
|
|
130
|
+
height: Math.round(targetImage.height / s),
|
|
131
|
+
scale: 1.0 / s, // keyframe.scale is relative to full target image
|
|
105
132
|
});
|
|
106
133
|
|
|
107
134
|
parentPort.postMessage({
|
|
108
135
|
type: 'progress',
|
|
109
|
-
percent: basePercent +
|
|
136
|
+
percent: basePercent + percentPerImage * 0.6 + keyframes.length * percentPerScale
|
|
110
137
|
});
|
|
111
138
|
}
|
|
112
139
|
|
|
@@ -110,8 +110,23 @@ export class OfflineCompiler {
|
|
|
110
110
|
async _compileTarget(targetImages: any[], progressCallback: (p: number) => void) {
|
|
111
111
|
if (isNode) await this._initNodeWorkers();
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
if (this.workerPool) {
|
|
114
|
+
const progressMap = new Float32Array(targetImages.length);
|
|
115
|
+
const wrappedPromises = targetImages.map((targetImage: any, index: number) => {
|
|
116
|
+
return this.workerPool!.runTask({
|
|
117
|
+
type: 'compile-all', // π MOONSHOT: Combined task
|
|
118
|
+
targetImage,
|
|
119
|
+
onProgress: (p: number) => {
|
|
120
|
+
progressMap[index] = p;
|
|
121
|
+
const sum = progressMap.reduce((a, b) => a + b, 0);
|
|
122
|
+
progressCallback(sum / targetImages.length);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
return Promise.all(wrappedPromises);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Fallback or non-worker implementation: run match and track sequentially
|
|
115
130
|
const matchingResults = await this._compileMatch(targetImages, (p) => progressCallback(p * 0.5));
|
|
116
131
|
const trackingResults = await this._compileTrack(targetImages, (p) => progressCallback(50 + p * 0.5));
|
|
117
132
|
|
|
@@ -155,12 +170,7 @@ export class OfflineCompiler {
|
|
|
155
170
|
const keyframes = [];
|
|
156
171
|
|
|
157
172
|
for (const image of imageList as any[]) {
|
|
158
|
-
|
|
159
|
-
const detector = new DetectorLite(image.width, image.height, {
|
|
160
|
-
useLSH: true,
|
|
161
|
-
maxOctaves: 1,
|
|
162
|
-
scale: image.scale
|
|
163
|
-
});
|
|
173
|
+
const detector = new DetectorLite(image.width, image.height, { useLSH: true });
|
|
164
174
|
const { featurePoints: ps } = detector.detect(image.data);
|
|
165
175
|
|
|
166
176
|
const maximaPoints = ps.filter((p: any) => p.maxima);
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from "./react/use-ar.js";
|
|
3
|
-
export * from "./react/TaptappAR.js";
|
|
1
|
+
export * from "./react/types";
|
|
4
2
|
export * from "./compiler/offline-compiler.js";
|
|
5
3
|
export { Controller } from "./compiler/controller.js";
|
|
6
4
|
export { SimpleAR } from "./compiler/simple-ar.js";
|
|
7
|
-
|
package/src/react/types.ts
CHANGED
|
@@ -3,38 +3,33 @@ export interface ARConfig {
|
|
|
3
3
|
targetImageSrc: string;
|
|
4
4
|
targetTaarSrc: string;
|
|
5
5
|
videoSrc: string;
|
|
6
|
-
overlaySrc: string; // Alias for preloading/compatibility
|
|
7
6
|
videoWidth: number;
|
|
8
7
|
videoHeight: number;
|
|
9
8
|
scale: number;
|
|
10
|
-
overlayType: "video" | "image";
|
|
11
9
|
}
|
|
12
10
|
|
|
13
11
|
export interface ARDataItem {
|
|
14
12
|
id: string;
|
|
15
|
-
type: "photos" | "videoNative" | "ar"
|
|
13
|
+
type: "photos" | "videoNative" | "ar";
|
|
16
14
|
images?: { image: string; fileId: string }[];
|
|
17
15
|
url?: string;
|
|
18
|
-
fileId?: string;
|
|
19
16
|
scale?: number;
|
|
20
17
|
width?: number;
|
|
21
18
|
height?: number;
|
|
22
19
|
}
|
|
23
20
|
|
|
24
|
-
export function mapDataToPropsConfig(data:
|
|
21
|
+
export function mapDataToPropsConfig(data: any[]): ARConfig {
|
|
25
22
|
const photos = data.find((item) => item.type === "photos");
|
|
26
|
-
const
|
|
23
|
+
const video = data.find((item) => item.type === "videoNative");
|
|
27
24
|
const ar = data.find((item) => item.type === "ar");
|
|
28
25
|
|
|
29
26
|
return {
|
|
30
27
|
cardId: photos?.id || "",
|
|
31
28
|
targetImageSrc: photos?.images?.[0]?.image || "",
|
|
32
29
|
targetTaarSrc: ar?.url || "",
|
|
33
|
-
videoSrc:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
scale: overlay?.scale || 1,
|
|
38
|
-
overlayType: overlay?.type === "videoNative" ? "video" : "image",
|
|
30
|
+
videoSrc: video?.url || "",
|
|
31
|
+
videoWidth: video?.width || 0,
|
|
32
|
+
videoHeight: video?.height || 0,
|
|
33
|
+
scale: video?.scale || 1,
|
|
39
34
|
};
|
|
40
35
|
}
|