@srsergio/taptapp-ar 1.0.88 → 1.0.90

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.
Files changed (41) hide show
  1. package/README.md +17 -10
  2. package/dist/compiler/offline-compiler.js +16 -4
  3. package/dist/core/detector/detector-lite.d.ts +1 -0
  4. package/dist/core/detector/detector-lite.js +31 -15
  5. package/dist/core/estimation/estimate.d.ts +7 -0
  6. package/dist/core/estimation/estimate.js +13 -48
  7. package/dist/core/estimation/morph-refinement.d.ts +8 -0
  8. package/dist/core/estimation/morph-refinement.js +116 -0
  9. package/dist/core/estimation/pnp-solver.d.ts +5 -0
  10. package/dist/core/estimation/pnp-solver.js +109 -0
  11. package/dist/core/input-loader.js +19 -2
  12. package/dist/core/matching/hdc.d.ts +27 -0
  13. package/dist/core/matching/hdc.js +102 -0
  14. package/dist/core/matching/hierarchical-clustering.d.ts +1 -3
  15. package/dist/core/matching/hierarchical-clustering.js +30 -29
  16. package/dist/core/matching/hough.js +12 -11
  17. package/dist/core/matching/matcher.d.ts +4 -0
  18. package/dist/core/matching/matcher.js +23 -8
  19. package/dist/core/matching/matching.d.ts +22 -2
  20. package/dist/core/matching/matching.js +169 -39
  21. package/dist/core/matching/ransacHomography.js +3 -6
  22. package/dist/core/protocol.d.ts +5 -3
  23. package/dist/core/protocol.js +28 -6
  24. package/dist/runtime/controller.js +19 -14
  25. package/dist/runtime/controller.worker.js +4 -1
  26. package/package.json +3 -2
  27. package/src/compiler/offline-compiler.ts +17 -4
  28. package/src/core/detector/detector-lite.js +32 -15
  29. package/src/core/estimation/estimate.js +14 -63
  30. package/src/core/estimation/morph-refinement.js +139 -0
  31. package/src/core/estimation/pnp-solver.js +131 -0
  32. package/src/core/input-loader.js +21 -2
  33. package/src/core/matching/hdc.ts +117 -0
  34. package/src/core/matching/hierarchical-clustering.js +30 -29
  35. package/src/core/matching/hough.js +12 -11
  36. package/src/core/matching/matcher.js +27 -9
  37. package/src/core/matching/matching.js +192 -39
  38. package/src/core/matching/ransacHomography.js +3 -6
  39. package/src/core/protocol.ts +26 -6
  40. package/src/runtime/controller.ts +20 -14
  41. package/src/runtime/controller.worker.js +4 -1
@@ -3,18 +3,28 @@ import { compute as hammingCompute } from "./hamming-distance.js";
3
3
  import { computeHoughMatches } from "./hough.js";
4
4
  import { computeHomography } from "./ransacHomography.js";
5
5
  import { multiplyPointHomographyInhomogenous, matrixInverse33 } from "../utils/geometry.js";
6
- const INLIER_THRESHOLD = 5.0; // Tightened from 10 to 5 for better precision
7
- const MIN_NUM_INLIERS = 8; // Restored to 8
8
- const CLUSTER_MAX_POP = 20;
9
- const HAMMING_THRESHOLD = 0.85; // Relaxed from 0.8 back to 0.85
6
+ import { refineWithMorphology } from "../estimation/morph-refinement.js";
7
+ import { popcount32 } from "./hierarchical-clustering.js";
8
+ const INLIER_THRESHOLD = 15.0; // 🚀 More tolerance for initial noise
9
+ const MIN_NUM_INLIERS = 6; // 🎯 Lowered from 8 to catch the marker earlier
10
+ const CLUSTER_MAX_POP = 25;
11
+ const HAMMING_THRESHOLD = 0.85;
12
+ const HDC_RATIO_THRESHOLD = 0.85;
13
+ const MAX_MATCH_QUERY_POINTS = 800; // 🚀 Increased to handle 3000+ points detected
10
14
  // match list of querpoints against pre-built list of keyframes
11
- const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) => {
15
+ const match = ({ keyframe, querypoints: rawQuerypoints, querywidth, queryheight, debugMode }) => {
12
16
  let debugExtra = {};
17
+ // 🎯 Performance Optimizer: Use only the most "salient" points (highest response)
18
+ const querypoints = rawQuerypoints.length > MAX_MATCH_QUERY_POINTS
19
+ ? [...rawQuerypoints].sort((a, b) => (b.score || b.response || 0) - (a.score || a.response || 0)).slice(0, MAX_MATCH_QUERY_POINTS)
20
+ : rawQuerypoints;
13
21
  const matches = [];
14
22
  const qlen = querypoints.length;
15
23
  const kmax = keyframe.max;
16
24
  const kmin = keyframe.min;
17
- const descSize = 2; // Protocol V6: 64-bit LSH (2 x 32-bit)
25
+ const isHDC = keyframe.hdc === true || (kmax && kmax.hdc === 1);
26
+ const descSize = isHDC ? 1 : 2;
27
+ const currentRatioThreshold = isHDC ? HDC_RATIO_THRESHOLD : HAMMING_THRESHOLD;
18
28
  for (let j = 0; j < qlen; j++) {
19
29
  const querypoint = querypoints[j];
20
30
  const col = querypoint.maxima ? kmax : kmin;
@@ -23,14 +33,15 @@ const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) =>
23
33
  const rootNode = col.t;
24
34
  const keypointIndexes = [];
25
35
  const queue = new TinyQueue([], (a1, a2) => a1.d - a2.d);
26
- // query potential candidates from the columnar tree
27
36
  _query({
28
37
  node: rootNode,
29
38
  descriptors: col.d,
30
39
  querypoint,
31
40
  queue,
32
41
  keypointIndexes,
33
- numPop: 0
42
+ numPop: 0,
43
+ isHDC,
44
+ descSize
34
45
  });
35
46
  let bestIndex = -1;
36
47
  let bestD1 = Number.MAX_SAFE_INTEGER;
@@ -39,8 +50,13 @@ const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) =>
39
50
  const cDesc = col.d;
40
51
  for (let k = 0; k < keypointIndexes.length; k++) {
41
52
  const idx = keypointIndexes[k];
42
- // Use offsets based on detected descriptor size
43
- const d = hammingCompute({ v1: cDesc, v1Offset: idx * descSize, v2: qDesc });
53
+ let d;
54
+ if (isHDC) {
55
+ d = popcount32(cDesc[idx] ^ querypoint.hdcSignature);
56
+ }
57
+ else {
58
+ d = hammingCompute({ v1: cDesc, v1Offset: idx * descSize, v2: qDesc });
59
+ }
44
60
  if (d < bestD1) {
45
61
  bestD2 = bestD1;
46
62
  bestD1 = d;
@@ -51,7 +67,7 @@ const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) =>
51
67
  }
52
68
  }
53
69
  if (bestIndex !== -1) {
54
- if (bestD2 === Number.MAX_SAFE_INTEGER || (bestD1 / bestD2) < HAMMING_THRESHOLD) {
70
+ if (bestD2 === Number.MAX_SAFE_INTEGER || (bestD1 / bestD2) < currentRatioThreshold) {
55
71
  matches.push({
56
72
  querypoint,
57
73
  keypoint: {
@@ -59,26 +75,39 @@ const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) =>
59
75
  y: col.y[bestIndex],
60
76
  angle: col.a[bestIndex],
61
77
  scale: col.s ? col.s[bestIndex] : keyframe.s
62
- }
78
+ },
79
+ d: bestD1
63
80
  });
64
81
  }
65
82
  }
66
83
  }
67
- if (matches.length < MIN_NUM_INLIERS)
84
+ if (matches.length < MIN_NUM_INLIERS) {
68
85
  return { debugExtra };
86
+ }
87
+ // Debug: Log Hamming results
88
+ if (Math.random() < 0.1 && debugMode) {
89
+ console.log(`MATCH_DL: Hamming found ${matches.length} initial matches`);
90
+ }
91
+ // 🌌 Moonshot: Constellation matching disabled for performance calibration
92
+ const constellationMatches = matches;
93
+ if (debugMode)
94
+ debugExtra.constellationMatches = constellationMatches;
69
95
  const houghMatches = computeHoughMatches({
70
- keywidth: keyframe.w,
71
- keyheight: keyframe.h,
96
+ keywidth: keyframe.w || keyframe.width,
97
+ keyheight: keyframe.h || keyframe.height,
72
98
  querywidth,
73
99
  queryheight,
74
- matches,
100
+ matches: constellationMatches,
75
101
  });
76
102
  if (debugMode)
77
103
  debugExtra.houghMatches = houghMatches;
104
+ if (houghMatches.length < MIN_NUM_INLIERS) {
105
+ return { debugExtra };
106
+ }
78
107
  const H = computeHomography({
79
108
  srcPoints: houghMatches.map((m) => [m.keypoint.x, m.keypoint.y]),
80
109
  dstPoints: houghMatches.map((m) => [m.querypoint.x, m.querypoint.y]),
81
- keyframe: { width: keyframe.w, height: keyframe.h },
110
+ keyframe: { width: keyframe.w || keyframe.width, height: keyframe.h || keyframe.height },
82
111
  });
83
112
  if (H === null) {
84
113
  return { debugExtra };
@@ -90,11 +119,14 @@ const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) =>
90
119
  });
91
120
  if (debugMode)
92
121
  debugExtra.inlierMatches = inlierMatches;
93
- if (inlierMatches.length < MIN_NUM_INLIERS)
122
+ if (inlierMatches.length < MIN_NUM_INLIERS) {
94
123
  return { debugExtra };
95
- // Second pass with homography guided matching
124
+ }
125
+ if (debugMode && Math.random() < 0.02) {
126
+ console.log(`MATCH: Homography success with ${inlierMatches.length} inliers`);
127
+ }
96
128
  const HInv = matrixInverse33(H, 0.00001);
97
- const dThreshold2 = 100; // 10 * 10
129
+ const dThreshold2 = 100;
98
130
  const matches2 = [];
99
131
  const hi00 = HInv[0], hi01 = HInv[1], hi02 = HInv[2];
100
132
  const hi10 = HInv[3], hi11 = HInv[4], hi12 = HInv[5];
@@ -102,7 +134,6 @@ const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) =>
102
134
  for (let j = 0; j < qlen; j++) {
103
135
  const querypoint = querypoints[j];
104
136
  const qx = querypoint.x, qy = querypoint.y;
105
- // Inline multiplyPointHomographyInhomogenous
106
137
  const uz = (qx * hi20) + (qy * hi21) + hi22;
107
138
  const invZ = 1.0 / uz;
108
139
  const mapX = ((qx * hi00) + (qy * hi01) + hi02) * invZ;
@@ -121,7 +152,13 @@ const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) =>
121
152
  const d2 = dx * dx + dy * dy;
122
153
  if (d2 > dThreshold2)
123
154
  continue;
124
- const d = hammingCompute({ v1: cd, v1Offset: k * descSize, v2: qDesc });
155
+ let d;
156
+ if (isHDC) {
157
+ d = popcount32(cd[k] ^ querypoint.hdcSignature);
158
+ }
159
+ else {
160
+ d = hammingCompute({ v1: cd, v1Offset: k * descSize, v2: qDesc });
161
+ }
125
162
  if (d < bestD1) {
126
163
  bestD2 = bestD1;
127
164
  bestD1 = d;
@@ -132,7 +169,7 @@ const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) =>
132
169
  }
133
170
  }
134
171
  if (bestIndex !== -1 &&
135
- (bestD2 === Number.MAX_SAFE_INTEGER || (bestD1 / bestD2) < HAMMING_THRESHOLD)) {
172
+ (bestD2 === Number.MAX_SAFE_INTEGER || (bestD1 / bestD2) < currentRatioThreshold)) {
136
173
  matches2.push({
137
174
  querypoint,
138
175
  keypoint: {
@@ -147,8 +184,8 @@ const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) =>
147
184
  if (debugMode)
148
185
  debugExtra.matches2 = matches2;
149
186
  const houghMatches2 = computeHoughMatches({
150
- keywidth: keyframe.w,
151
- keyheight: keyframe.h,
187
+ keywidth: keyframe.w || keyframe.width,
188
+ keyheight: keyframe.h || keyframe.height,
152
189
  querywidth,
153
190
  queryheight,
154
191
  matches: matches2,
@@ -158,7 +195,7 @@ const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) =>
158
195
  const H2 = computeHomography({
159
196
  srcPoints: houghMatches2.map((m) => [m.keypoint.x, m.keypoint.y]),
160
197
  dstPoints: houghMatches2.map((m) => [m.querypoint.x, m.querypoint.y]),
161
- keyframe: { width: keyframe.w, height: keyframe.h },
198
+ keyframe: { width: keyframe.w || keyframe.width, height: keyframe.h || keyframe.height },
162
199
  });
163
200
  if (H2 === null)
164
201
  return { debugExtra };
@@ -169,10 +206,17 @@ const match = ({ keyframe, querypoints, querywidth, queryheight, debugMode }) =>
169
206
  });
170
207
  if (debugMode)
171
208
  debugExtra.inlierMatches2 = inlierMatches2;
172
- return { H: H2, matches: inlierMatches2, debugExtra };
209
+ const refinedH = refineWithMorphology({
210
+ imageData: rawQuerypoints[0].imageData,
211
+ width: querywidth,
212
+ height: queryheight,
213
+ targetData: { w: keyframe.w || keyframe.width, h: keyframe.h || keyframe.height },
214
+ initialH: H2,
215
+ iterations: 3
216
+ });
217
+ return { H: refinedH || H2, matches: inlierMatches2, debugExtra };
173
218
  };
174
- const _query = ({ node, descriptors, querypoint, queue, keypointIndexes, numPop }) => {
175
- const descSize = 2;
219
+ const _query = ({ node, descriptors, querypoint, queue, keypointIndexes, numPop, isHDC, descSize }) => {
176
220
  const isLeaf = node[0] === 1;
177
221
  const childrenOrIndices = node[2];
178
222
  if (isLeaf) {
@@ -188,27 +232,33 @@ const _query = ({ node, descriptors, querypoint, queue, keypointIndexes, numPop
188
232
  for (let i = 0; i < clen; i++) {
189
233
  const childNode = childrenOrIndices[i];
190
234
  const cIdx = childNode[1];
191
- const d = hammingCompute({
192
- v1: descriptors,
193
- v1Offset: cIdx * descSize,
194
- v2: qDesc,
195
- });
235
+ let d;
236
+ if (isHDC) {
237
+ d = popcount32(descriptors[cIdx] ^ querypoint.hdcSignature);
238
+ }
239
+ else {
240
+ d = hammingCompute({
241
+ v1: descriptors,
242
+ v1Offset: cIdx * descSize,
243
+ v2: qDesc,
244
+ });
245
+ }
196
246
  distances[i] = d;
197
247
  if (d < minD)
198
248
  minD = d;
199
249
  }
200
250
  for (let i = 0; i < clen; i++) {
201
251
  const dist = distances[i];
202
- if (dist !== minD) {
203
- queue.push({ node: childrenOrIndices[i], d: dist });
252
+ if (dist <= minD) {
253
+ _query({ node: childrenOrIndices[i], descriptors, querypoint, queue, keypointIndexes, numPop: numPop + 1, isHDC, descSize });
204
254
  }
205
255
  else {
206
- _query({ node: childrenOrIndices[i], descriptors, querypoint, queue, keypointIndexes, numPop: numPop + 1 });
256
+ queue.push({ node: childrenOrIndices[i], d: dist });
207
257
  }
208
258
  }
209
259
  if (numPop < CLUSTER_MAX_POP && queue.length > 0) {
210
260
  const { node } = queue.pop();
211
- _query({ node, descriptors, querypoint, queue, keypointIndexes, numPop: numPop + 1 });
261
+ _query({ node, descriptors, querypoint, queue, keypointIndexes, numPop: numPop + 1, isHDC, descSize });
212
262
  }
213
263
  };
214
264
  const _findInlierMatches = (options) => {
@@ -222,7 +272,6 @@ const _findInlierMatches = (options) => {
222
272
  const m = matches[i];
223
273
  const qp = m.querypoint;
224
274
  const kp = m.keypoint;
225
- // Inline multiplyPointHomographyInhomogenous
226
275
  const uz = (kp.x * h20) + (kp.y * h21) + h22;
227
276
  const invZ = 1.0 / uz;
228
277
  const mx = ((kp.x * h00) + (kp.y * h01) + h02) * invZ;
@@ -235,4 +284,85 @@ const _findInlierMatches = (options) => {
235
284
  }
236
285
  return goodMatches;
237
286
  };
287
+ const _applyConstellationFilter = (matches) => {
288
+ const len = matches.length;
289
+ if (len < 3)
290
+ return matches;
291
+ const pool = matches.slice().sort((a, b) => a.d - b.d).slice(0, 1500);
292
+ const RATIO_TOLERANCE = 0.25;
293
+ const COSINE_TOLERANCE = 0.2;
294
+ const MAX_NEIGHBORS = 6;
295
+ const MIN_VERIFICATIONS = 1;
296
+ const gridSize = 50;
297
+ const grid = new Map();
298
+ const getGridKey = (x, y) => `${Math.floor(x / gridSize)},${Math.floor(y / gridSize)}`;
299
+ for (let i = 0; i < pool.length; i++) {
300
+ const qp = pool[i].querypoint;
301
+ const key = getGridKey(qp.x, qp.y);
302
+ if (!grid.has(key))
303
+ grid.set(key, []);
304
+ grid.get(key).push(i);
305
+ }
306
+ const scores = new Int32Array(pool.length);
307
+ for (let i = 0; i < pool.length; i++) {
308
+ const m1 = pool[i];
309
+ const qp1 = m1.querypoint;
310
+ const neighbors = [];
311
+ const gx = Math.floor(qp1.x / gridSize);
312
+ const gy = Math.floor(qp1.y / gridSize);
313
+ for (let dx = -1; dx <= 1; dx++) {
314
+ for (let dy = -1; dy <= 1; dy++) {
315
+ const cell = grid.get(`${gx + dx},${gy + dy}`);
316
+ if (!cell)
317
+ continue;
318
+ for (const idx of cell) {
319
+ if (idx === i)
320
+ continue;
321
+ const m2 = pool[idx];
322
+ const distSq = Math.pow(m2.querypoint.x - qp1.x, 2) + Math.pow(m2.querypoint.y - qp1.y, 2);
323
+ neighbors.push({ index: idx, d2: distSq });
324
+ }
325
+ }
326
+ }
327
+ }
328
+ const filtered = [];
329
+ // Dummy logic just to keep the structure
330
+ for (let i = 0; i < pool.length; i++) {
331
+ filtered.push(pool[i]);
332
+ }
333
+ return filtered;
334
+ };
335
+ const _checkTriadConsistency = (m1, m2, m3, ratioTol, cosTol) => {
336
+ const q1x = m1.querypoint.x, q1y = m1.querypoint.y;
337
+ const q2x = m2.querypoint.x, q2y = m2.querypoint.y;
338
+ const q3x = m3.querypoint.x, q3y = m3.querypoint.y;
339
+ const v21q = [q2x - q1x, q2y - q1y];
340
+ const v31q = [q3x - q1x, q3y - q1y];
341
+ const k1x = m1.keypoint.x, k1y = m1.keypoint.y;
342
+ const k2x = m2.keypoint.x, k2y = m2.keypoint.y;
343
+ const k3x = m3.keypoint.x, k3y = m3.keypoint.y;
344
+ const v21k = [k2x - k1x, k2y - k1y];
345
+ const v31k = [k3x - k1x, k3y - k1y];
346
+ const d21q2 = v21q[0] * v21q[0] + v21q[1] * v21q[1];
347
+ const d31q2 = v31q[0] * v31q[0] + v31q[1] * v31q[1];
348
+ const d21k2 = v21k[0] * v21k[0] + v21k[1] * v21k[1];
349
+ const d31k2 = v31k[0] * v31k[0] + v31k[1] * v31k[1];
350
+ if (d31q2 < 1e-4 || d31k2 < 1e-4)
351
+ return false;
352
+ const ratioQ = d21q2 / d31q2;
353
+ const ratioK = d21k2 / d31k2;
354
+ if (Math.abs(ratioQ - ratioK) / (ratioK + 1e-6) > ratioTol * 2)
355
+ return false;
356
+ const dotQ = v21q[0] * v31q[0] + v21q[1] * v31q[1];
357
+ const cosQ = dotQ / Math.sqrt(d21q2 * d31q2);
358
+ const dotK = v21k[0] * v31k[0] + v21k[1] * v31k[1];
359
+ const cosK = dotK / Math.sqrt(d21k2 * d31k2);
360
+ if (Math.abs(cosQ - cosK) > cosTol)
361
+ return false;
362
+ const crossQ = v21q[0] * v31q[1] - v21q[1] * v31q[0];
363
+ const crossK = v21k[0] * v31k[1] - v21k[1] * v31k[0];
364
+ if ((crossQ > 0) !== (crossK > 0))
365
+ return false;
366
+ return true;
367
+ };
238
368
  export { match };
@@ -3,8 +3,8 @@ import { quadrilateralConvex, matrixInverse33, smallestTriangleArea, multiplyPoi
3
3
  import { solveHomography } from "../utils/homography.js";
4
4
  const CAUCHY_SCALE = 0.01;
5
5
  const CHUNK_SIZE = 10;
6
- const NUM_HYPOTHESES = 20;
7
- const NUM_HYPOTHESES_QUICK = 10;
6
+ const NUM_HYPOTHESES = 100;
7
+ const NUM_HYPOTHESES_QUICK = 50;
8
8
  // Using RANSAC to estimate homography
9
9
  const computeHomography = (options) => {
10
10
  const { srcPoints, dstPoints, keyframe, quickMode } = options;
@@ -89,13 +89,10 @@ const computeHomography = (options) => {
89
89
  return finalH;
90
90
  };
91
91
  const _checkHeuristics = ({ H, testPoints, keyframe }) => {
92
- const HInv = matrixInverse33(H, 0.00001);
93
- if (HInv === null)
94
- return false;
95
92
  const mp = [];
96
93
  for (let i = 0; i < testPoints.length; i++) {
97
94
  // 4 test points, corner of keyframe
98
- mp.push(multiplyPointHomographyInhomogenous(testPoints[i], HInv));
95
+ mp.push(multiplyPointHomographyInhomogenous(testPoints[i], H));
99
96
  }
100
97
  const smallArea = smallestTriangleArea(mp[0], mp[1], mp[2], mp[3]);
101
98
  if (smallArea < keyframe.width * keyframe.height * 0.0001)
@@ -1,4 +1,5 @@
1
- export declare const CURRENT_VERSION = 8;
1
+ export declare const CURRENT_VERSION = 9;
2
+ export declare const HDC_SEED = 322420463;
2
3
  /**
3
4
  * Morton Order calculation for spatial sorting
4
5
  */
@@ -14,12 +15,13 @@ export declare function unpack4Bit(packed: Uint8Array, width: number, height: nu
14
15
  /**
15
16
  * Columnarizes point data for efficient storage and transfer
16
17
  */
17
- export declare function columnarize(points: any[], tree: any, width: number, height: number): {
18
+ export declare function columnarize(points: any[], tree: any, width: number, height: number, useHDC?: boolean): {
18
19
  x: Uint16Array<ArrayBuffer>;
19
20
  y: Uint16Array<ArrayBuffer>;
20
21
  a: Int16Array<ArrayBuffer>;
21
22
  s: Uint8Array<ArrayBuffer>;
22
- d: Uint32Array<ArrayBuffer>;
23
+ d: any;
24
+ hdc: number;
23
25
  t: any;
24
26
  };
25
27
  /**
@@ -1,5 +1,6 @@
1
1
  import * as msgpack from "@msgpack/msgpack";
2
- export const CURRENT_VERSION = 8;
2
+ export const CURRENT_VERSION = 9; // Bumped for HDC support
3
+ export const HDC_SEED = 0x1337BEEF; // Default system seed
3
4
  /**
4
5
  * Morton Order calculation for spatial sorting
5
6
  */
@@ -49,21 +50,35 @@ export function unpack4Bit(packed, width, height) {
49
50
  /**
50
51
  * Columnarizes point data for efficient storage and transfer
51
52
  */
52
- export function columnarize(points, tree, width, height) {
53
+ export function columnarize(points, tree, width, height, useHDC = false) {
53
54
  const count = points.length;
54
55
  const x = new Uint16Array(count);
55
56
  const y = new Uint16Array(count);
56
57
  const angle = new Int16Array(count);
57
58
  const scale = new Uint8Array(count);
58
- const descriptors = new Uint32Array(count * 2);
59
+ let descriptors;
60
+ if (useHDC) {
61
+ descriptors = new Uint32Array(count); // HDC Signatures (32-bit)
62
+ }
63
+ else {
64
+ descriptors = new Uint32Array(count * 2); // Raw Descriptors (64-bit)
65
+ }
59
66
  for (let i = 0; i < count; i++) {
60
67
  x[i] = Math.round((points[i].x / width) * 65535);
61
68
  y[i] = Math.round((points[i].y / height) * 65535);
62
69
  angle[i] = Math.round((points[i].angle / Math.PI) * 32767);
63
70
  scale[i] = Math.round(Math.log2(points[i].scale || 1));
64
71
  if (points[i].descriptors && points[i].descriptors.length >= 2) {
65
- descriptors[i * 2] = points[i].descriptors[0];
66
- descriptors[(i * 2) + 1] = points[i].descriptors[1];
72
+ if (useHDC) {
73
+ // For HDC, we'd normally call project + compress here
74
+ // But protocol.ts should be agnostic of the generator.
75
+ // We'll assume points[i].hdcSignature exists if pre-calculated
76
+ descriptors[i] = points[i].hdcSignature || 0;
77
+ }
78
+ else {
79
+ descriptors[i * 2] = points[i].descriptors[0];
80
+ descriptors[(i * 2) + 1] = points[i].descriptors[1];
81
+ }
67
82
  }
68
83
  }
69
84
  return {
@@ -72,6 +87,7 @@ export function columnarize(points, tree, width, height) {
72
87
  a: angle,
73
88
  s: scale,
74
89
  d: descriptors,
90
+ hdc: useHDC ? 1 : 0, // HDC Flag (renamed from h to avoid collision with height)
75
91
  t: compactTree(tree.rootNode),
76
92
  };
77
93
  }
@@ -182,7 +198,13 @@ export function decodeTaar(buffer) {
182
198
  col.s = s;
183
199
  }
184
200
  if (col.d instanceof Uint8Array) {
185
- col.d = new Uint32Array(col.d.buffer.slice(col.d.byteOffset, col.d.byteOffset + col.d.byteLength));
201
+ // Check if it's HDC (Uint32) or Raw (Uint32 x 2)
202
+ if (col.hdc === 1) {
203
+ col.d = new Uint32Array(col.d.buffer.slice(col.d.byteOffset, col.d.byteOffset + col.d.byteLength));
204
+ }
205
+ else {
206
+ col.d = new Uint32Array(col.d.buffer.slice(col.d.byteOffset, col.d.byteOffset + col.d.byteLength));
207
+ }
186
208
  }
187
209
  }
188
210
  }
@@ -66,16 +66,19 @@ class Controller {
66
66
  if (this.worker)
67
67
  this._setupWorkerListener();
68
68
  // Moonshot: Full frame detector for better sensitivity
69
- this.fullDetector = new DetectorLite(this.inputWidth, this.inputHeight, { useLSH: true });
69
+ this.fullDetector = new DetectorLite(this.inputWidth, this.inputHeight, {
70
+ useLSH: true,
71
+ maxFeaturesPerBucket: 24 // Increased from 12 for better small target density
72
+ });
70
73
  this.featureManager.init({
71
74
  inputWidth: this.inputWidth,
72
75
  inputHeight: this.inputHeight,
73
76
  projectionTransform: [], // Will be set below
74
77
  debugMode: this.debugMode
75
78
  });
76
- const near = 10;
77
- const far = 100000;
78
- const fovy = (45.0 * Math.PI) / 180;
79
+ const near = 1.0;
80
+ const far = 10000;
81
+ const fovy = (60.0 * Math.PI) / 180;
79
82
  const f = this.inputHeight / 2 / Math.tan(fovy / 2);
80
83
  this.projectionTransform = [
81
84
  [f, 0, this.inputWidth / 2],
@@ -245,9 +248,11 @@ class Controller {
245
248
  }
246
249
  }
247
250
  }
248
- // STRICT QUALITY CHECK: We only update the transform if we have enough HIGH CONFIDENCE points
249
- const stableAndReliable = reliabilities.filter((r, idx) => r > 0.8 && stabilities[indices[idx]] > 0.6).length;
250
- if (stableAndReliable < 6 || finalWorldCoords.length < 8) {
251
+ // 🚀 WARMUP FIX: If we just started tracking (less than 15 frames), we are much more relaxed
252
+ const isWarmup = state.trackCount < 15;
253
+ const numTracked = finalWorldCoords.length;
254
+ const minPoints = isWarmup ? 4 : 5; // Start with 4, then require 5
255
+ if (numTracked < minPoints) {
251
256
  return {
252
257
  modelViewTransform: null,
253
258
  screenCoords: finalScreenCoords,
@@ -255,6 +260,7 @@ class Controller {
255
260
  stabilities: finalStabilities
256
261
  };
257
262
  }
263
+ state.trackCount++;
258
264
  const modelViewTransform = await this._workerTrackUpdate(lastModelViewTransform, {
259
265
  worldCoords: finalWorldCoords,
260
266
  screenCoords: finalWorldCoords.map((_, i) => {
@@ -544,15 +550,14 @@ class Controller {
544
550
  });
545
551
  }
546
552
  _glModelViewMatrix(modelViewTransform, targetIndex) {
547
- const height = this.markerDimensions[targetIndex][1];
553
+ // Transformation to map Computer Vision coordinates (Y-down, Z-forward)
554
+ // to OpenGL coordinates (Y-up, Z-backward).
555
+ // We negate the 2nd and 3rd rows of the pose matrix.
548
556
  return [
549
557
  modelViewTransform[0][0], -modelViewTransform[1][0], -modelViewTransform[2][0], 0,
550
- -modelViewTransform[0][1], modelViewTransform[1][1], modelViewTransform[2][1], 0,
551
- -modelViewTransform[0][2], modelViewTransform[1][2], modelViewTransform[2][2], 0,
552
- modelViewTransform[0][1] * height + modelViewTransform[0][3],
553
- -(modelViewTransform[1][1] * height + modelViewTransform[1][3]),
554
- -(modelViewTransform[2][1] * height + modelViewTransform[2][3]),
555
- 1,
558
+ modelViewTransform[0][1], -modelViewTransform[1][1], -modelViewTransform[2][1], 0,
559
+ modelViewTransform[0][2], -modelViewTransform[1][2], -modelViewTransform[2][2], 0,
560
+ modelViewTransform[0][3], -modelViewTransform[1][3], -modelViewTransform[2][3], 1,
556
561
  ];
557
562
  }
558
563
  _glProjectionMatrix({ projectionTransform, width, height, near, far }) {
@@ -19,7 +19,10 @@ onmessage = (msg) => {
19
19
  if (data.trackingDataList && data.markerDimensions) {
20
20
  tracker = new Tracker(data.markerDimensions, data.trackingDataList, data.projectionTransform, data.inputWidth, data.inputHeight, debugMode);
21
21
  }
22
- detector = new DetectorLite(data.inputWidth, data.inputHeight, { useLSH: true });
22
+ detector = new DetectorLite(data.inputWidth, data.inputHeight, {
23
+ useLSH: true,
24
+ maxFeaturesPerBucket: 24
25
+ });
23
26
  break;
24
27
  case "match":
25
28
  const interestedTargetIndexes = data.targetIndexes;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srsergio/taptapp-ar",
3
- "version": "1.0.88",
3
+ "version": "1.0.90",
4
4
  "description": "Ultra-fast Augmented Reality (AR) SDK for Node.js and Browser. Image tracking with 100% pure JavaScript, zero-dependencies, and high-performance compilation.",
5
5
  "keywords": [
6
6
  "augmented reality",
@@ -52,7 +52,8 @@
52
52
  "build": "tsc",
53
53
  "prepublishOnly": "npm run build",
54
54
  "test": "vitest",
55
- "test:react": "vite --port 4321 --open tests/react-test.html"
55
+ "test:react": "vite --port 4321 --open tests/reliability-test.html",
56
+ "test:pnp": "pnpm build && node scripts/pnp-verify-cli.js"
56
57
  },
57
58
  "peerDependencies": {
58
59
  "aframe": ">=1.5.0",
@@ -12,6 +12,7 @@ import { DetectorLite } from "../core/detector/detector-lite.js";
12
12
  import { build as hierarchicalClusteringBuild } from "../core/matching/hierarchical-clustering.js";
13
13
  import * as protocol from "../core/protocol.js";
14
14
  import { triangulate, getEdges } from "../core/utils/delaunay.js";
15
+ import { generateBasis, projectDescriptor, compressToSignature } from "../core/matching/hdc.js";
15
16
 
16
17
  // Detect environment
17
18
  const isNode = typeof process !== "undefined" &&
@@ -92,15 +93,26 @@ export class OfflineCompiler {
92
93
  const results = [];
93
94
  for (let i = 0; i < targetImages.length; i++) {
94
95
  const targetImage = targetImages[i];
95
- const imageList = buildImageList(targetImage);
96
+ const fullImageList = buildImageList(targetImage);
97
+ // 🚀 MOONSHOT: Keep many scales for better robustness
98
+ const imageList = fullImageList;
96
99
  const percentPerImageScale = percentPerImage / imageList.length;
97
100
 
98
101
  const keyframes = [];
99
102
 
100
103
  for (const image of imageList as any[]) {
101
- const detector = new DetectorLite(image.width, image.height, { useLSH: true, maxFeaturesPerBucket: 20 });
104
+ const detector = new DetectorLite(image.width, image.height, { useLSH: true, maxFeaturesPerBucket: 40 });
102
105
  const { featurePoints: ps } = detector.detect(image.data);
103
106
 
107
+ // HDC Pre-calculation
108
+ const hdcBasis = generateBasis(protocol.HDC_SEED, 1024);
109
+ for (const p of ps) {
110
+ if (p.descriptors) {
111
+ const hv = projectDescriptor(p.descriptors, hdcBasis);
112
+ p.hdcSignature = compressToSignature(hv);
113
+ }
114
+ }
115
+
104
116
  const maximaPoints = ps.filter((p: any) => p.maxima);
105
117
  const minimaPoints = ps.filter((p: any) => !p.maxima);
106
118
  const maximaPointsCluster = hierarchicalClusteringBuild({ points: maximaPoints });
@@ -204,8 +216,9 @@ export class OfflineCompiler {
204
216
  w: kf.width,
205
217
  h: kf.height,
206
218
  s: kf.scale,
207
- max: protocol.columnarize(kf.maximaPoints, kf.maximaPointsCluster, kf.width, kf.height),
208
- min: protocol.columnarize(kf.minimaPoints, kf.minimaPointsCluster, kf.width, kf.height),
219
+ hdc: false,
220
+ max: protocol.columnarize(kf.maximaPoints, kf.maximaPointsCluster, kf.width, kf.height, false),
221
+ min: protocol.columnarize(kf.minimaPoints, kf.minimaPointsCluster, kf.width, kf.height, false),
209
222
  })),
210
223
  };
211
224
  });