@srsergio/taptapp-ar 1.0.42 → 1.0.50
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 +42 -45
- package/dist/compiler/aframe.js +8 -8
- package/dist/compiler/controller.d.ts +50 -76
- package/dist/compiler/controller.js +72 -116
- package/dist/compiler/detector/detector-lite.js +82 -99
- package/dist/compiler/index.js +3 -3
- package/dist/compiler/matching/hamming-distance.d.ts +8 -0
- package/dist/compiler/matching/hamming-distance.js +35 -16
- package/dist/compiler/matching/hierarchical-clustering.d.ts +9 -0
- package/dist/compiler/matching/hierarchical-clustering.js +76 -56
- package/dist/compiler/matching/matching.js +3 -3
- package/dist/compiler/node-worker.js +144 -18
- package/dist/compiler/offline-compiler.d.ts +34 -83
- package/dist/compiler/offline-compiler.js +92 -96
- package/dist/compiler/simple-ar.d.ts +31 -57
- package/dist/compiler/simple-ar.js +32 -73
- package/dist/compiler/three.d.ts +13 -8
- package/dist/compiler/three.js +6 -6
- package/dist/compiler/tracker/extract.js +17 -14
- package/dist/compiler/utils/images.js +11 -16
- package/dist/compiler/utils/lsh-direct.d.ts +12 -0
- package/dist/compiler/utils/lsh-direct.js +76 -0
- package/dist/compiler/utils/worker-pool.js +10 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/react/types.d.ts +1 -1
- package/dist/react/types.js +1 -1
- package/package.json +2 -1
- package/src/compiler/aframe.js +8 -8
- package/src/compiler/controller.ts +512 -0
- package/src/compiler/detector/detector-lite.js +87 -107
- package/src/compiler/index.js +3 -3
- package/src/compiler/matching/hamming-distance.js +39 -16
- package/src/compiler/matching/hierarchical-clustering.js +85 -57
- package/src/compiler/matching/matching.js +3 -3
- package/src/compiler/node-worker.js +163 -18
- package/src/compiler/offline-compiler.ts +513 -0
- package/src/compiler/{simple-ar.js → simple-ar.ts} +64 -91
- package/src/compiler/three.js +6 -6
- package/src/compiler/tracker/extract.js +18 -15
- package/src/compiler/utils/images.js +11 -21
- package/src/compiler/utils/lsh-direct.js +86 -0
- package/src/compiler/utils/worker-pool.js +9 -1
- package/src/index.ts +2 -2
- package/src/react/types.ts +2 -2
- package/src/compiler/controller.js +0 -554
- package/src/compiler/offline-compiler.js +0 -515
|
@@ -4,43 +4,53 @@ import { projectToScreen } from "./utils/projection.js";
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* 🍦 SimpleAR - Dead-simple vanilla AR for image overlays
|
|
7
|
-
*
|
|
8
|
-
* No Three.js. No A-Frame. Just HTML, CSS, and JavaScript.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* const ar = new SimpleAR({
|
|
12
|
-
* container: document.getElementById('ar-container'),
|
|
13
|
-
* targetSrc: './my-target.mind',
|
|
14
|
-
* overlay: document.getElementById('my-overlay'),
|
|
15
|
-
* onFound: () => console.log('Target found!'),
|
|
16
|
-
* onLost: () => console.log('Target lost!')
|
|
17
|
-
* });
|
|
18
|
-
*
|
|
19
|
-
* await ar.start();
|
|
20
7
|
*/
|
|
8
|
+
|
|
9
|
+
export interface SimpleAROptions {
|
|
10
|
+
container: HTMLElement;
|
|
11
|
+
targetSrc: string | string[];
|
|
12
|
+
overlay: HTMLElement;
|
|
13
|
+
scale?: number;
|
|
14
|
+
onFound?: ((data: { targetIndex: number }) => void | Promise<void>) | null;
|
|
15
|
+
onLost?: ((data: { targetIndex: number }) => void | Promise<void>) | null;
|
|
16
|
+
onUpdate?: ((data: { targetIndex: number, worldMatrix: number[] }) => void) | null;
|
|
17
|
+
cameraConfig?: MediaStreamConstraints['video'];
|
|
18
|
+
debug?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
21
|
class SimpleAR {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
container: HTMLElement;
|
|
23
|
+
targetSrc: string | string[];
|
|
24
|
+
overlay: HTMLElement;
|
|
25
|
+
scaleMultiplier: number;
|
|
26
|
+
onFound: ((data: { targetIndex: number }) => void | Promise<void>) | null;
|
|
27
|
+
onLost: ((data: { targetIndex: number }) => void | Promise<void>) | null;
|
|
28
|
+
onUpdateCallback: ((data: { targetIndex: number, worldMatrix: number[] }) => void) | null;
|
|
29
|
+
cameraConfig: MediaStreamConstraints['video'];
|
|
30
|
+
debug: boolean;
|
|
31
|
+
|
|
32
|
+
lastTime: number;
|
|
33
|
+
frameCount: number;
|
|
34
|
+
fps: number;
|
|
35
|
+
debugPanel: HTMLElement | null = null;
|
|
36
|
+
video: HTMLVideoElement | null = null;
|
|
37
|
+
controller: Controller | null = null;
|
|
38
|
+
isTracking: boolean = false;
|
|
39
|
+
lastMatrix: number[] | null = null;
|
|
40
|
+
filters: OneEuroFilter[] = [];
|
|
41
|
+
markerDimensions: number[][] = [];
|
|
42
|
+
|
|
33
43
|
constructor({
|
|
34
44
|
container,
|
|
35
45
|
targetSrc,
|
|
36
46
|
overlay,
|
|
37
|
-
scale = 1.0,
|
|
47
|
+
scale = 1.0,
|
|
38
48
|
onFound = null,
|
|
39
49
|
onLost = null,
|
|
40
50
|
onUpdate = null,
|
|
41
51
|
cameraConfig = { facingMode: 'environment', width: 1280, height: 720 },
|
|
42
52
|
debug = false,
|
|
43
|
-
}) {
|
|
53
|
+
}: SimpleAROptions) {
|
|
44
54
|
this.container = container;
|
|
45
55
|
this.targetSrc = targetSrc;
|
|
46
56
|
this.overlay = overlay;
|
|
@@ -50,56 +60,37 @@ class SimpleAR {
|
|
|
50
60
|
this.onUpdateCallback = onUpdate;
|
|
51
61
|
this.cameraConfig = cameraConfig;
|
|
52
62
|
this.debug = debug;
|
|
63
|
+
// @ts-ignore
|
|
53
64
|
if (this.debug) window.AR_DEBUG = true;
|
|
54
65
|
|
|
55
66
|
this.lastTime = performance.now();
|
|
56
67
|
this.frameCount = 0;
|
|
57
68
|
this.fps = 0;
|
|
58
|
-
this.debugPanel = null;
|
|
59
|
-
|
|
60
|
-
this.video = null;
|
|
61
|
-
this.controller = null;
|
|
62
|
-
this.isTracking = false;
|
|
63
|
-
this.lastMatrix = null;
|
|
64
|
-
this.filters = []; // One filter per target
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
/**
|
|
68
|
-
* Initialize and start AR tracking
|
|
69
|
-
*/
|
|
70
71
|
async start() {
|
|
71
|
-
// 1. Create video element
|
|
72
72
|
this._createVideo();
|
|
73
|
-
|
|
74
|
-
// 2. Start camera
|
|
75
73
|
await this._startCamera();
|
|
76
|
-
|
|
77
|
-
// 3. Initialize controller
|
|
78
74
|
this._initController();
|
|
79
75
|
|
|
80
76
|
if (this.debug) this._createDebugPanel();
|
|
81
77
|
|
|
82
|
-
// 4. Load targets (supports single URL or array of URLs)
|
|
83
78
|
const targets = Array.isArray(this.targetSrc) ? this.targetSrc : [this.targetSrc];
|
|
84
|
-
const result = await this.controller
|
|
85
|
-
this.markerDimensions = result.dimensions;
|
|
79
|
+
const result = await this.controller!.addImageTargets(targets);
|
|
80
|
+
this.markerDimensions = result.dimensions;
|
|
86
81
|
console.log("Targets loaded. Dimensions:", this.markerDimensions);
|
|
87
82
|
|
|
88
|
-
this.controller
|
|
89
|
-
|
|
83
|
+
this.controller!.processVideo(this.video);
|
|
90
84
|
return this;
|
|
91
85
|
}
|
|
92
86
|
|
|
93
|
-
/**
|
|
94
|
-
* Stop AR tracking and release resources
|
|
95
|
-
*/
|
|
96
87
|
stop() {
|
|
97
88
|
if (this.controller) {
|
|
98
89
|
this.controller.dispose();
|
|
99
90
|
this.controller = null;
|
|
100
91
|
}
|
|
101
92
|
if (this.video && this.video.srcObject) {
|
|
102
|
-
this.video.srcObject.getTracks().forEach(track => track.stop());
|
|
93
|
+
(this.video.srcObject as MediaStream).getTracks().forEach(track => track.stop());
|
|
103
94
|
this.video.remove();
|
|
104
95
|
this.video = null;
|
|
105
96
|
}
|
|
@@ -113,14 +104,14 @@ class SimpleAR {
|
|
|
113
104
|
this.video.setAttribute('playsinline', '');
|
|
114
105
|
this.video.setAttribute('muted', '');
|
|
115
106
|
this.video.style.cssText = `
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
107
|
+
position: absolute;
|
|
108
|
+
top: 0;
|
|
109
|
+
left: 0;
|
|
110
|
+
width: 100%;
|
|
111
|
+
height: 100%;
|
|
112
|
+
object-fit: cover;
|
|
113
|
+
z-index: 0;
|
|
114
|
+
`;
|
|
124
115
|
this.container.style.position = 'relative';
|
|
125
116
|
this.container.style.overflow = 'hidden';
|
|
126
117
|
this.container.insertBefore(this.video, this.container.firstChild);
|
|
@@ -130,29 +121,27 @@ class SimpleAR {
|
|
|
130
121
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
131
122
|
video: this.cameraConfig
|
|
132
123
|
});
|
|
133
|
-
this.video
|
|
134
|
-
await this.video
|
|
124
|
+
this.video!.srcObject = stream;
|
|
125
|
+
await this.video!.play();
|
|
135
126
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
this.video.onloadedmetadata = resolve;
|
|
127
|
+
await new Promise<void>(resolve => {
|
|
128
|
+
if (this.video!.videoWidth > 0) return resolve();
|
|
129
|
+
this.video!.onloadedmetadata = () => resolve();
|
|
140
130
|
});
|
|
141
131
|
}
|
|
142
132
|
|
|
143
133
|
_initController() {
|
|
144
134
|
this.controller = new Controller({
|
|
145
|
-
inputWidth: this.video
|
|
146
|
-
inputHeight: this.video
|
|
135
|
+
inputWidth: this.video!.videoWidth,
|
|
136
|
+
inputHeight: this.video!.videoHeight,
|
|
147
137
|
debugMode: this.debug,
|
|
148
138
|
onUpdate: (data) => this._handleUpdate(data)
|
|
149
139
|
});
|
|
150
140
|
}
|
|
151
141
|
|
|
152
|
-
_handleUpdate(data) {
|
|
142
|
+
_handleUpdate(data: any) {
|
|
153
143
|
if (data.type !== 'updateMatrix') return;
|
|
154
144
|
|
|
155
|
-
// FPS Calculation
|
|
156
145
|
const now = performance.now();
|
|
157
146
|
this.frameCount++;
|
|
158
147
|
if (now - this.lastTime >= 1000) {
|
|
@@ -165,7 +154,6 @@ class SimpleAR {
|
|
|
165
154
|
const { targetIndex, worldMatrix, modelViewTransform } = data;
|
|
166
155
|
|
|
167
156
|
if (worldMatrix) {
|
|
168
|
-
// Target found
|
|
169
157
|
if (!this.isTracking) {
|
|
170
158
|
this.isTracking = true;
|
|
171
159
|
this.overlay && (this.overlay.style.opacity = '1');
|
|
@@ -174,12 +162,10 @@ class SimpleAR {
|
|
|
174
162
|
|
|
175
163
|
this.lastMatrix = worldMatrix;
|
|
176
164
|
|
|
177
|
-
// Smooth the tracking data if filters are initialized
|
|
178
165
|
if (!this.filters[targetIndex]) {
|
|
179
166
|
this.filters[targetIndex] = new OneEuroFilter({ minCutOff: 0.1, beta: 0.01 });
|
|
180
167
|
}
|
|
181
168
|
|
|
182
|
-
// Flatten modelViewTransform for filtering (3x4 matrix = 12 values)
|
|
183
169
|
const flatMVT = [
|
|
184
170
|
modelViewTransform[0][0], modelViewTransform[0][1], modelViewTransform[0][2], modelViewTransform[0][3],
|
|
185
171
|
modelViewTransform[1][0], modelViewTransform[1][1], modelViewTransform[1][2], modelViewTransform[1][3],
|
|
@@ -196,7 +182,6 @@ class SimpleAR {
|
|
|
196
182
|
this.onUpdateCallback && this.onUpdateCallback({ targetIndex, worldMatrix });
|
|
197
183
|
|
|
198
184
|
} else {
|
|
199
|
-
// Target lost
|
|
200
185
|
if (this.isTracking) {
|
|
201
186
|
this.isTracking = false;
|
|
202
187
|
if (this.filters[targetIndex]) this.filters[targetIndex].reset();
|
|
@@ -206,30 +191,26 @@ class SimpleAR {
|
|
|
206
191
|
}
|
|
207
192
|
}
|
|
208
193
|
|
|
209
|
-
_positionOverlay(mVT, targetIndex) {
|
|
194
|
+
_positionOverlay(mVT: number[][], targetIndex: number) {
|
|
210
195
|
if (!this.overlay || !this.markerDimensions[targetIndex]) return;
|
|
211
196
|
|
|
212
197
|
const [markerW, markerH] = this.markerDimensions[targetIndex];
|
|
213
198
|
const containerRect = this.container.getBoundingClientRect();
|
|
214
|
-
const videoW = this.video
|
|
215
|
-
const videoH = this.video
|
|
199
|
+
const videoW = this.video!.videoWidth;
|
|
200
|
+
const videoH = this.video!.videoHeight;
|
|
216
201
|
|
|
217
|
-
// 1. Determine orientation needs
|
|
218
202
|
const isPortrait = containerRect.height > containerRect.width;
|
|
219
203
|
const isVideoLandscape = videoW > videoH;
|
|
220
204
|
const needsRotation = isPortrait && isVideoLandscape;
|
|
221
205
|
|
|
222
|
-
|
|
223
|
-
const proj = this.controller.projectionTransform;
|
|
206
|
+
const proj = this.controller!.projectionTransform;
|
|
224
207
|
|
|
225
|
-
// 3. Project 4 corners to determine a full 3D perspective (homography)
|
|
226
208
|
const pUL = projectToScreen(0, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
227
209
|
const pUR = projectToScreen(markerW, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
228
210
|
const pLL = projectToScreen(0, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
229
211
|
const pLR = projectToScreen(markerW, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
230
212
|
|
|
231
|
-
|
|
232
|
-
const solveHomography = (w, h, p1, p2, p3, p4) => {
|
|
213
|
+
const solveHomography = (w: number, h: number, p1: any, p2: any, p3: any, p4: any) => {
|
|
233
214
|
const x1 = p1.sx, y1 = p1.sy;
|
|
234
215
|
const x2 = p2.sx, y2 = p2.sy;
|
|
235
216
|
const x3 = p3.sx, y3 = p3.sy;
|
|
@@ -255,8 +236,6 @@ class SimpleAR {
|
|
|
255
236
|
e = y3 - y1 + h_coeff * y3;
|
|
256
237
|
f = y1;
|
|
257
238
|
}
|
|
258
|
-
// This maps unit square (0..1) to the quadrilateral.
|
|
259
|
-
// We need to scale it by 1/w and 1/h to map (0..w, 0..h)
|
|
260
239
|
return [
|
|
261
240
|
a / w, d / w, 0, g / w,
|
|
262
241
|
b / h, e / h, 0, h_coeff / h,
|
|
@@ -267,7 +246,6 @@ class SimpleAR {
|
|
|
267
246
|
|
|
268
247
|
const matrix = solveHomography(markerW, markerH, pUL, pUR, pLL, pLR);
|
|
269
248
|
|
|
270
|
-
// Apply styles
|
|
271
249
|
this.overlay.style.maxWidth = 'none';
|
|
272
250
|
this.overlay.style.width = `${markerW}px`;
|
|
273
251
|
this.overlay.style.height = `${markerH}px`;
|
|
@@ -277,10 +255,6 @@ class SimpleAR {
|
|
|
277
255
|
this.overlay.style.top = '0';
|
|
278
256
|
this.overlay.style.display = 'block';
|
|
279
257
|
|
|
280
|
-
// Apply 3D transform with matrix3d
|
|
281
|
-
// We also apply the user's custom scaleMultiplier AFTER the perspective transform
|
|
282
|
-
// but since we want to scale around the marker center, we apply it as a prefix/suffix
|
|
283
|
-
// Scale around top-left (0,0) is easy. Scale around center requires offset.
|
|
284
258
|
this.overlay.style.transform = `
|
|
285
259
|
matrix3d(${matrix.join(',')})
|
|
286
260
|
translate(${markerW / 2}px, ${markerH / 2}px)
|
|
@@ -289,8 +263,6 @@ class SimpleAR {
|
|
|
289
263
|
`;
|
|
290
264
|
}
|
|
291
265
|
|
|
292
|
-
// Unified projection logic moved to ./utils/projection.js
|
|
293
|
-
|
|
294
266
|
_createDebugPanel() {
|
|
295
267
|
this.debugPanel = document.createElement('div');
|
|
296
268
|
this.debugPanel.style.cssText = `
|
|
@@ -310,8 +282,9 @@ class SimpleAR {
|
|
|
310
282
|
this.container.appendChild(this.debugPanel);
|
|
311
283
|
}
|
|
312
284
|
|
|
313
|
-
_updateDebugPanel(isTracking) {
|
|
285
|
+
_updateDebugPanel(isTracking: boolean) {
|
|
314
286
|
if (!this.debugPanel) return;
|
|
287
|
+
// @ts-ignore
|
|
315
288
|
const memory = performance.memory ? Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) : '?';
|
|
316
289
|
const color = isTracking ? '#0f0' : '#f00';
|
|
317
290
|
const status = isTracking ? 'TRACKING' : 'SEARCHING';
|
package/src/compiler/three.js
CHANGED
|
@@ -17,7 +17,7 @@ cssScaleDownMatrix.compose(new Vector3(), new Quaternion(), new Vector3(0.001, 0
|
|
|
17
17
|
|
|
18
18
|
const invisibleMatrix = new Matrix4().set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
|
|
19
19
|
|
|
20
|
-
export class
|
|
20
|
+
export class TaarThree {
|
|
21
21
|
constructor({
|
|
22
22
|
container,
|
|
23
23
|
imageTargetSrc,
|
|
@@ -352,11 +352,11 @@ export class MindARThree {
|
|
|
352
352
|
}
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
-
if (!window.
|
|
356
|
-
window.
|
|
355
|
+
if (!window.TAAR) {
|
|
356
|
+
window.TAAR = {};
|
|
357
357
|
}
|
|
358
|
-
if (!window.
|
|
359
|
-
window.
|
|
358
|
+
if (!window.TAAR.IMAGE) {
|
|
359
|
+
window.TAAR.IMAGE = {};
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
window.
|
|
362
|
+
window.TAAR.IMAGE.TaarThree = TaarThree;
|
|
@@ -335,28 +335,31 @@ const _getSimilarityOptimized = (options) => {
|
|
|
335
335
|
cy + templateSize,
|
|
336
336
|
);
|
|
337
337
|
|
|
338
|
-
//
|
|
338
|
+
// 🚀 MOONSHOT Early Exit: Check variance (vlen2) before expensive sxy loop
|
|
339
|
+
let vlen2 = sxx - (sx * sx) / nP;
|
|
340
|
+
if (vlen2 <= 0) return null;
|
|
341
|
+
vlen2 = Math.sqrt(vlen2);
|
|
342
|
+
|
|
343
|
+
// Full calculation - Optimized with 2x2 sub-sampling for SPEED
|
|
339
344
|
let sxy = 0;
|
|
340
|
-
|
|
341
|
-
let p2 = 0;
|
|
342
|
-
const nextRowOffset = width - templateWidth;
|
|
345
|
+
const p1_start = (cy - templateSize) * width + (cx - templateSize);
|
|
343
346
|
|
|
344
|
-
for (let j = 0; j < templateWidth; j
|
|
345
|
-
|
|
346
|
-
|
|
347
|
+
for (let j = 0; j < templateWidth; j += 2) {
|
|
348
|
+
const rowOffset1 = p1_start + j * width;
|
|
349
|
+
const rowOffset2 = j * templateWidth;
|
|
350
|
+
for (let i = 0; i < templateWidth; i += 2) {
|
|
351
|
+
sxy += imageData[rowOffset1 + i] * templateData[rowOffset2 + i];
|
|
347
352
|
}
|
|
348
|
-
p1 += nextRowOffset;
|
|
349
353
|
}
|
|
350
354
|
|
|
355
|
+
// Factor to normalize sxy back to full template area
|
|
356
|
+
// templateWidth is 13, steps of 2 hit 7 points per dim = 49 total points (vs 169)
|
|
357
|
+
const sampledCount = Math.ceil(templateWidth / 2) ** 2;
|
|
358
|
+
const totalCount = templateWidth * templateWidth;
|
|
359
|
+
sxy *= (totalCount / sampledCount);
|
|
360
|
+
|
|
351
361
|
// Covariance check
|
|
352
|
-
// E[(X-EX)(Y-EY)] = E[XY] - EX*EY
|
|
353
|
-
// sum((Xi - avgX)(Yi - avgY)) = sum(XiYi) - avgY * sum(Xi)
|
|
354
362
|
const sxy_final = sxy - templateAvg * sx;
|
|
355
|
-
|
|
356
|
-
let vlen2 = sxx - (sx * sx) / (nP);
|
|
357
|
-
if (vlen2 <= 0) return null;
|
|
358
|
-
vlen2 = Math.sqrt(vlen2);
|
|
359
|
-
|
|
360
363
|
return (1.0 * sxy_final) / (vlen * vlen2);
|
|
361
364
|
};
|
|
362
365
|
|
|
@@ -37,35 +37,25 @@ const upsampleBilinear = ({ image, padOneWidth, padOneHeight }) => {
|
|
|
37
37
|
|
|
38
38
|
const downsampleBilinear = ({ image }) => {
|
|
39
39
|
const { data, width, height } = image;
|
|
40
|
-
const dstWidth = width >>> 1;
|
|
40
|
+
const dstWidth = width >>> 1;
|
|
41
41
|
const dstHeight = height >>> 1;
|
|
42
42
|
|
|
43
43
|
const temp = new Uint8Array(dstWidth * dstHeight);
|
|
44
44
|
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
const srcRowStep = (srcWidth * 2) | 0;
|
|
48
|
-
|
|
49
|
-
let srcRowOffset = 0;
|
|
50
|
-
let dstIndex = 0;
|
|
51
|
-
|
|
45
|
+
// Speed optimization: using Int32 views and manual indexing
|
|
46
|
+
// Also using bitwise operations for color averaging
|
|
52
47
|
for (let j = 0; j < dstHeight; j++) {
|
|
53
|
-
|
|
48
|
+
const row0 = (j * 2) * width;
|
|
49
|
+
const row1 = row0 + width;
|
|
50
|
+
const dstRow = j * dstWidth;
|
|
54
51
|
|
|
55
52
|
for (let i = 0; i < dstWidth; i++) {
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
const
|
|
59
|
-
data[
|
|
60
|
-
|
|
61
|
-
data[srcPos + srcWidth] +
|
|
62
|
-
data[srcPos + srcWidth + 1]
|
|
63
|
-
) * 0.25;
|
|
64
|
-
|
|
65
|
-
temp[dstIndex++] = value | 0; // Fast floor
|
|
66
|
-
srcPos += 2;
|
|
53
|
+
const i2 = i * 2;
|
|
54
|
+
// Efficient Int32 math for blurring
|
|
55
|
+
const val = (data[row0 + i2] + data[row0 + i2 + 1] +
|
|
56
|
+
data[row1 + i2] + data[row1 + i2 + 1]) >> 2;
|
|
57
|
+
temp[dstRow + i] = val & 0xFF;
|
|
67
58
|
}
|
|
68
|
-
srcRowOffset += srcRowStep;
|
|
69
59
|
}
|
|
70
60
|
|
|
71
61
|
return { data: temp, width: dstWidth, height: dstHeight };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { FREAKPOINTS } from "../detector/freak.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 🚀 Moonshot: LSH-Direct Descriptor
|
|
5
|
+
*
|
|
6
|
+
* Instead of computing 672 bits of FREAK and then sampling 64 bits for LSH,
|
|
7
|
+
* we directly compute only the 64 bits we need.
|
|
8
|
+
*
|
|
9
|
+
* Speedup: >10x in descriptor generation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// 1. Pre-calculate the 64 pairs of indices (i, j) that correspond to our LSH sampling
|
|
13
|
+
const LSH_PAIRS = new Int32Array(64 * 2);
|
|
14
|
+
const SAMPLING_INDICES = new Int32Array(64);
|
|
15
|
+
for (let i = 0; i < 64; i++) {
|
|
16
|
+
SAMPLING_INDICES[i] = Math.floor(i * (672 / 64));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Map bit indices to FREAK point pairs
|
|
20
|
+
let currentBit = 0;
|
|
21
|
+
let samplingIdx = 0;
|
|
22
|
+
for (let i = 0; i < FREAKPOINTS.length; i++) {
|
|
23
|
+
for (let j = i + 1; j < FREAKPOINTS.length; j++) {
|
|
24
|
+
if (samplingIdx < 64 && currentBit === SAMPLING_INDICES[samplingIdx]) {
|
|
25
|
+
LSH_PAIRS[samplingIdx * 2] = i;
|
|
26
|
+
LSH_PAIRS[samplingIdx * 2 + 1] = j;
|
|
27
|
+
samplingIdx++;
|
|
28
|
+
}
|
|
29
|
+
currentBit++;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Directly compute 64-bit LSH from FREAK samples
|
|
35
|
+
* @param {Float32Array} samples - Pre-sampled intensities at FREAK positions
|
|
36
|
+
* @returns {Uint32Array} 2-element array (64 bits)
|
|
37
|
+
*/
|
|
38
|
+
export function computeLSH64(samples) {
|
|
39
|
+
const result = new Uint32Array(2);
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < 64; i++) {
|
|
42
|
+
const p1 = LSH_PAIRS[i * 2];
|
|
43
|
+
const p2 = LSH_PAIRS[i * 2 + 1];
|
|
44
|
+
|
|
45
|
+
if (samples[p1] < samples[p2]) {
|
|
46
|
+
const uintIdx = i >> 5; // i / 32
|
|
47
|
+
const uintBitIdx = i & 31; // i % 32
|
|
48
|
+
result[uintIdx] |= (1 << uintBitIdx);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// For backward compatibility if any 84-byte descriptor is still needed
|
|
56
|
+
export function computeFullFREAK(samples) {
|
|
57
|
+
const descriptor = new Uint8Array(84);
|
|
58
|
+
let bitCount = 0;
|
|
59
|
+
let byteIdx = 0;
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < FREAKPOINTS.length; i++) {
|
|
62
|
+
for (let j = i + 1; j < FREAKPOINTS.length; j++) {
|
|
63
|
+
if (samples[i] < samples[j]) {
|
|
64
|
+
descriptor[byteIdx] |= (1 << (7 - bitCount));
|
|
65
|
+
}
|
|
66
|
+
bitCount++;
|
|
67
|
+
if (bitCount === 8) {
|
|
68
|
+
byteIdx++;
|
|
69
|
+
bitCount = 0;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return descriptor;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Super-fast 8-byte (64-bit) dummy descriptor for Protocol V6 compatibility
|
|
78
|
+
* when full descriptors are not required but an object is expected.
|
|
79
|
+
*/
|
|
80
|
+
export function packLSHIntoDescriptor(lsh) {
|
|
81
|
+
const desc = new Uint8Array(8);
|
|
82
|
+
const view = new DataView(desc.buffer);
|
|
83
|
+
view.setUint32(0, lsh[0], true);
|
|
84
|
+
view.setUint32(4, lsh[1], true);
|
|
85
|
+
return desc;
|
|
86
|
+
}
|
|
@@ -34,7 +34,15 @@ export class WorkerPool {
|
|
|
34
34
|
task.taskData.onProgress(msg.percent);
|
|
35
35
|
} else if (msg.type === 'compileDone') {
|
|
36
36
|
cleanup();
|
|
37
|
-
|
|
37
|
+
// If it's the new unified result, return both.
|
|
38
|
+
if (msg.matchingData && msg.trackingData) {
|
|
39
|
+
this._finishTask(worker, task.resolve, {
|
|
40
|
+
matchingData: msg.matchingData,
|
|
41
|
+
trackingData: msg.trackingData
|
|
42
|
+
});
|
|
43
|
+
} else {
|
|
44
|
+
this._finishTask(worker, task.resolve, msg.trackingData);
|
|
45
|
+
}
|
|
38
46
|
} else if (msg.type === 'matchDone') {
|
|
39
47
|
cleanup();
|
|
40
48
|
this._finishTask(worker, task.resolve, msg.matchingData);
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from "./react/types";
|
|
2
|
-
export * from "./compiler/offline-compiler";
|
|
1
|
+
export * from "./react/types.js";
|
|
2
|
+
export * from "./compiler/offline-compiler.js";
|
|
3
3
|
export { Controller } from "./compiler/controller.js";
|
|
4
4
|
export { SimpleAR } from "./compiler/simple-ar.js";
|
package/src/react/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export interface PropsConfig {
|
|
2
2
|
cardId: string;
|
|
3
3
|
targetImageSrc: string;
|
|
4
|
-
|
|
4
|
+
targetTaarSrc: string;
|
|
5
5
|
videoSrc: string;
|
|
6
6
|
videoWidth: number;
|
|
7
7
|
videoHeight: number;
|
|
@@ -26,7 +26,7 @@ export function mapDataToPropsConfig(data: any[]): PropsConfig {
|
|
|
26
26
|
return {
|
|
27
27
|
cardId: photos?.id || "",
|
|
28
28
|
targetImageSrc: photos?.images?.[0]?.image || "",
|
|
29
|
-
|
|
29
|
+
targetTaarSrc: ar?.url || "",
|
|
30
30
|
videoSrc: video?.url || "",
|
|
31
31
|
videoWidth: video?.width || 0,
|
|
32
32
|
videoHeight: video?.height || 0,
|