@srsergio/taptapp-ar 1.0.43 → 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
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { parentPort } from 'node:worker_threads';
|
|
8
8
|
import { extractTrackingFeatures } from './tracker/extract-utils.js';
|
|
9
|
-
import { buildTrackingImageList
|
|
9
|
+
import { buildTrackingImageList } from './image-list.js';
|
|
10
10
|
import { DetectorLite } from './detector/detector-lite.js';
|
|
11
11
|
import { build as hierarchicalClusteringBuild } from './matching/hierarchical-clustering.js';
|
|
12
12
|
|
|
@@ -14,6 +14,32 @@ if (!parentPort) {
|
|
|
14
14
|
throw new Error('This file must be run as a worker thread.');
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// Helper for Morton Order sorting inside worker
|
|
18
|
+
function getMorton(x, y) {
|
|
19
|
+
let x_int = x | 0;
|
|
20
|
+
let y_int = y | 0;
|
|
21
|
+
x_int = (x_int | (x_int << 8)) & 0x00FF00FF;
|
|
22
|
+
x_int = (x_int | (x_int << 4)) & 0x0F0F0F0F;
|
|
23
|
+
x_int = (x_int | (x_int << 2)) & 0x33333333;
|
|
24
|
+
x_int = (x_int | (x_int << 1)) & 0x55555555;
|
|
25
|
+
y_int = (y_int | (y_int << 8)) & 0x00FF00FF;
|
|
26
|
+
y_int = (y_int | (y_int << 4)) & 0x0F0F0F0F;
|
|
27
|
+
y_int = (y_int | (y_int << 2)) & 0x33333333;
|
|
28
|
+
y_int = (y_int | (y_int << 1)) & 0x55555555;
|
|
29
|
+
return x_int | (y_int << 1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const mortonCache = new Int32Array(2048); // Cache for sorting stability
|
|
33
|
+
|
|
34
|
+
function sortPoints(points) {
|
|
35
|
+
if (points.length <= 1) return points;
|
|
36
|
+
|
|
37
|
+
// Sort in-place to avoid allocations
|
|
38
|
+
return points.sort((a, b) => {
|
|
39
|
+
return getMorton(a.x, a.y) - getMorton(b.x, b.y);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
17
43
|
parentPort.on('message', async (msg) => {
|
|
18
44
|
if (msg.type === 'compile') {
|
|
19
45
|
const { targetImage, percentPerImage, basePercent } = msg;
|
|
@@ -38,27 +64,60 @@ parentPort.on('message', async (msg) => {
|
|
|
38
64
|
} catch (error) {
|
|
39
65
|
parentPort.postMessage({
|
|
40
66
|
type: 'error',
|
|
41
|
-
error: error.message
|
|
67
|
+
error: error.message + '\n' + error.stack
|
|
42
68
|
});
|
|
43
69
|
}
|
|
44
70
|
} else if (msg.type === 'match') {
|
|
45
71
|
const { targetImage, percentPerImage, basePercent } = msg;
|
|
46
72
|
|
|
47
73
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 });
|
|
85
|
+
|
|
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
|
+
}
|
|
95
|
+
|
|
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
|
|
103
|
+
});
|
|
104
|
+
}
|
|
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.
|
|
51
109
|
|
|
52
110
|
const keyframes = [];
|
|
53
|
-
|
|
54
|
-
const image = imageList[i];
|
|
55
|
-
const detector = new DetectorLite(image.width, image.height, { useLSH: true });
|
|
111
|
+
const sortedScales = Array.from(scalesMap.keys()).sort((a, b) => a - b);
|
|
56
112
|
|
|
57
|
-
|
|
58
|
-
|
|
113
|
+
const percentPerScale = (percentPerImage * 0.4) / sortedScales.length;
|
|
114
|
+
|
|
115
|
+
for (const s of sortedScales) {
|
|
116
|
+
const ps = scalesMap.get(s);
|
|
117
|
+
const sortedPs = sortPoints(ps);
|
|
118
|
+
const maximaPoints = sortedPs.filter((p) => p.maxima);
|
|
119
|
+
const minimaPoints = sortedPs.filter((p) => !p.maxima);
|
|
59
120
|
|
|
60
|
-
const maximaPoints = ps.filter((p) => p.maxima);
|
|
61
|
-
const minimaPoints = ps.filter((p) => !p.maxima);
|
|
62
121
|
const maximaPointsCluster = hierarchicalClusteringBuild({ points: maximaPoints });
|
|
63
122
|
const minimaPointsCluster = hierarchicalClusteringBuild({ points: minimaPoints });
|
|
64
123
|
|
|
@@ -67,15 +126,14 @@ parentPort.on('message', async (msg) => {
|
|
|
67
126
|
minimaPoints,
|
|
68
127
|
maximaPointsCluster,
|
|
69
128
|
minimaPointsCluster,
|
|
70
|
-
width:
|
|
71
|
-
height:
|
|
72
|
-
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
|
|
73
132
|
});
|
|
74
133
|
|
|
75
|
-
localPercent += percentPerAction;
|
|
76
134
|
parentPort.postMessage({
|
|
77
135
|
type: 'progress',
|
|
78
|
-
percent: basePercent +
|
|
136
|
+
percent: basePercent + percentPerImage * 0.6 + keyframes.length * percentPerScale
|
|
79
137
|
});
|
|
80
138
|
}
|
|
81
139
|
|
|
@@ -86,7 +144,94 @@ parentPort.on('message', async (msg) => {
|
|
|
86
144
|
} catch (error) {
|
|
87
145
|
parentPort.postMessage({
|
|
88
146
|
type: 'error',
|
|
89
|
-
error: error.message
|
|
147
|
+
error: error.message + '\n' + error.stack
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} else if (msg.type === 'compile-all') {
|
|
151
|
+
const { targetImage } = msg;
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
// 1. Single Pass Detection + Pyramid Generation
|
|
155
|
+
const detector = new DetectorLite(targetImage.width, targetImage.height, { useLSH: true });
|
|
156
|
+
parentPort.postMessage({ type: 'progress', percent: 10 });
|
|
157
|
+
|
|
158
|
+
const { featurePoints, pyramid } = detector.detect(targetImage.data);
|
|
159
|
+
parentPort.postMessage({ type: 'progress', percent: 40 });
|
|
160
|
+
|
|
161
|
+
// 2. Extract Tracking Data using the ALREADY BLURRED pyramid
|
|
162
|
+
// We need 2 levels closest to 256 and 128
|
|
163
|
+
const trackingImageList = [];
|
|
164
|
+
|
|
165
|
+
// Octave 0 is Original blured. Octave 1 is 0.5x. Octave 2 is 0.25x.
|
|
166
|
+
// We'll pick the best ones.
|
|
167
|
+
const targetSizes = [256, 128];
|
|
168
|
+
for (const targetSize of targetSizes) {
|
|
169
|
+
let bestLevel = 0;
|
|
170
|
+
let minDiff = Math.abs(Math.min(targetImage.width, targetImage.height) - targetSize);
|
|
171
|
+
|
|
172
|
+
for (let l = 1; l < pyramid.length; l++) {
|
|
173
|
+
const img = pyramid[l][0];
|
|
174
|
+
const diff = Math.abs(Math.min(img.width, img.height) - targetSize);
|
|
175
|
+
if (diff < minDiff) {
|
|
176
|
+
minDiff = diff;
|
|
177
|
+
bestLevel = l;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const levelImg = pyramid[bestLevel][0];
|
|
182
|
+
trackingImageList.push({
|
|
183
|
+
data: levelImg.data,
|
|
184
|
+
width: levelImg.width,
|
|
185
|
+
height: levelImg.height,
|
|
186
|
+
scale: levelImg.width / targetImage.width
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const trackingData = extractTrackingFeatures(trackingImageList, () => { });
|
|
191
|
+
parentPort.postMessage({ type: 'progress', percent: 60 });
|
|
192
|
+
|
|
193
|
+
// 3. Build Keyframes for Matching
|
|
194
|
+
const scalesMap = new Map();
|
|
195
|
+
for (const p of featurePoints) {
|
|
196
|
+
const s = p.scale;
|
|
197
|
+
let list = scalesMap.get(s);
|
|
198
|
+
if (!list) {
|
|
199
|
+
list = [];
|
|
200
|
+
scalesMap.set(s, list);
|
|
201
|
+
}
|
|
202
|
+
list.push({ ...p, x: p.x / s, y: p.y / s, scale: 1.0 });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const keyframes = [];
|
|
206
|
+
const sortedScales = Array.from(scalesMap.keys()).sort((a, b) => a - b);
|
|
207
|
+
for (const s of sortedScales) {
|
|
208
|
+
const ps = scalesMap.get(s);
|
|
209
|
+
const sortedPs = sortPoints(ps);
|
|
210
|
+
const maximaPoints = sortedPs.filter((p) => p.maxima);
|
|
211
|
+
const minimaPoints = sortedPs.filter((p) => !p.maxima);
|
|
212
|
+
const maximaPointsCluster = hierarchicalClusteringBuild({ points: maximaPoints });
|
|
213
|
+
const minimaPointsCluster = hierarchicalClusteringBuild({ points: minimaPoints });
|
|
214
|
+
|
|
215
|
+
keyframes.push({
|
|
216
|
+
maximaPoints,
|
|
217
|
+
minimaPoints,
|
|
218
|
+
maximaPointsCluster,
|
|
219
|
+
minimaPointsCluster,
|
|
220
|
+
width: Math.round(targetImage.width / s),
|
|
221
|
+
height: Math.round(targetImage.height / s),
|
|
222
|
+
scale: 1.0 / s,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
parentPort.postMessage({
|
|
227
|
+
type: 'compileDone', // Reusing message type for compatibility with WorkerPool
|
|
228
|
+
matchingData: keyframes,
|
|
229
|
+
trackingData: trackingData
|
|
230
|
+
});
|
|
231
|
+
} catch (error) {
|
|
232
|
+
parentPort.postMessage({
|
|
233
|
+
type: 'error',
|
|
234
|
+
error: error.message + '\n' + error.stack
|
|
90
235
|
});
|
|
91
236
|
}
|
|
92
237
|
}
|