@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 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> &nbsp;|&nbsp;
5
- <a href="./docs/index.html">🌐 <b>Official Website</b></a> &nbsp;|&nbsp;
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. SimpleAR (Recommended) 🍦
87
- The **simplest way** to use ARβ€”no Three.js or A-Frame required. Just overlay an HTML element on the tracked target.
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
- <div id="ar-container" style="width: 100vw; height: 100vh;">
109
- <img id="my-overlay" src="./overlay.png"
110
- style="opacity: 0; z-index: 1; width: 200px; transition: opacity 0.3s;" />
111
- </div>
112
-
113
- <script type="module">
114
- import { SimpleAR } from '@srsergio/taptapp-ar';
115
-
116
- const ar = new SimpleAR({
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
- #### βš™οΈ SimpleAR Options
127
- | Option | Required | Description |
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
- ### 2. React Integration (Vite & SSR Safe) βš›οΈ
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
- ```tsx
147
- import { TaptappAR, mapDataToPropsConfig } from '@srsergio/taptapp-ar';
101
+ const taarThree = new TaarThree({
102
+ container: document.querySelector("#container"),
103
+ imageTargetSrc: './targets.taar',
104
+ });
148
105
 
149
- const MyARComponent = ({ data }) => {
150
- // Use mapDataToPropsConfig to convert your raw data into ARConfig
151
- const config = mapDataToPropsConfig(data);
106
+ const {renderer, scene, camera} = taarThree;
152
107
 
153
- return (
154
- <div style={{ width: '100vw', height: '100vh' }}>
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
- #### πŸ› οΈ Custom UI: `useAR()` Hook
162
- If you want to build your own UI while keeping the powerful tracking logic:
163
-
164
- ```tsx
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
- ### πŸ“š Legacy Usage
250
- For **A-Frame** or **Three.js** wrappers, please refer to the [Advanced Usage Documentation](./docs/advanced-usage.md).
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 & Recognition
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
- ### Acknowledgements
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, maxOctaves: 1 });
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 = this.maxFeaturesPerBucket;
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 = 40; // Increased to 40 to skip extremely small, noisy layers and reduce 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: More aggressive step (pow(2, 0.75) approx 1.68) for smaller exports
20
- c *= Math.pow(2.0, 0.75);
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
- const { buildImageList } = await import('./image-list.js');
68
- const imageList = buildImageList(targetImage);
69
- const percentPerScale = percentPerImage / imageList.length;
70
- const keyframes = [];
71
- for (let i = 0; i < imageList.length; i++) {
72
- const image = imageList[i];
73
- // πŸš€ SMART BITRATE (VBR): Now handled internally by DetectorLite via 'scale'
74
- const detector = new DetectorLite(image.width, image.height, {
75
- useLSH: true,
76
- maxOctaves: 1,
77
- scale: image.scale
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
- const { featurePoints: ps } = detector.detect(image.data);
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: image.width,
91
- height: image.height,
92
- scale: image.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 + (i + 1) * percentPerScale
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
- // Reverted: 'compile-all' combined task was causing issues with pyramid processing
92
- // We go back to sequential match and track for reliability
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
- // πŸš€ SMART BITRATE (VBR): Internalized in DetectorLite
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 { ARConfig, ARDataItem, mapDataToPropsConfig } from "./react/types.js";
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 { mapDataToPropsConfig } from "./react/types.js";
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";
@@ -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" | "imageOverlay";
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: ARDataItem[]): ARConfig;
22
+ export declare function mapDataToPropsConfig(data: any[]): ARConfig;
@@ -1,16 +1,14 @@
1
1
  export function mapDataToPropsConfig(data) {
2
2
  const photos = data.find((item) => item.type === "photos");
3
- const overlay = data.find((item) => item.type === "videoNative" || item.type === "imageOverlay");
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: overlay?.url || "",
10
- overlaySrc: overlay?.url || "",
11
- videoWidth: overlay?.width || 0,
12
- videoHeight: overlay?.height || 0,
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.62",
4
- "author": "Sergio Lazaro <srsergiolazaro@gmail.com>",
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": "^25.0.3",
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, maxOctaves: 1 });
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 = this.maxFeaturesPerBucket;
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 = 40; // Increased to 40 to skip extremely small, noisy layers and reduce 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: More aggressive step (pow(2, 0.75) approx 1.68) for smaller exports
25
- c *= Math.pow(2.0, 0.75);
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
- const { buildImageList } = await import('./image-list.js');
75
- const imageList = buildImageList(targetImage);
76
- const percentPerScale = percentPerImage / imageList.length;
77
- const keyframes = [];
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
- for (let i = 0; i < imageList.length; i++) {
80
- const image = imageList[i];
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
- // πŸš€ SMART BITRATE (VBR): Now handled internally by DetectorLite via 'scale'
83
- const detector = new DetectorLite(image.width, image.height, {
84
- useLSH: true,
85
- maxOctaves: 1,
86
- scale: image.scale
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
- const { featurePoints: ps } = detector.detect(image.data);
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: image.width,
103
- height: image.height,
104
- scale: image.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 + (i + 1) * percentPerScale
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
- // Reverted: 'compile-all' combined task was causing issues with pyramid processing
114
- // We go back to sequential match and track for reliability
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
- // πŸš€ SMART BITRATE (VBR): Internalized in DetectorLite
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 { ARConfig, ARDataItem, mapDataToPropsConfig } from "./react/types.js";
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
-
@@ -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" | "imageOverlay";
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: ARDataItem[]): ARConfig {
21
+ export function mapDataToPropsConfig(data: any[]): ARConfig {
25
22
  const photos = data.find((item) => item.type === "photos");
26
- const overlay = data.find((item) => item.type === "videoNative" || item.type === "imageOverlay");
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: overlay?.url || "",
34
- overlaySrc: overlay?.url || "",
35
- videoWidth: overlay?.width || 0,
36
- videoHeight: overlay?.height || 0,
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
  }