@srsergio/taptapp-ar 1.0.101 → 1.1.2

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 (63) hide show
  1. package/dist/compiler/node-worker.js +1 -197
  2. package/dist/compiler/offline-compiler.js +1 -207
  3. package/dist/core/constants.js +1 -38
  4. package/dist/core/detector/crop-detector.js +1 -88
  5. package/dist/core/detector/detector-lite.js +1 -455
  6. package/dist/core/detector/freak.js +1 -89
  7. package/dist/core/estimation/estimate.js +1 -16
  8. package/dist/core/estimation/estimator.js +1 -30
  9. package/dist/core/estimation/morph-refinement.js +1 -116
  10. package/dist/core/estimation/non-rigid-refine.js +1 -70
  11. package/dist/core/estimation/pnp-solver.js +1 -109
  12. package/dist/core/estimation/refine-estimate.js +1 -311
  13. package/dist/core/estimation/utils.js +1 -67
  14. package/dist/core/features/auto-rotation-feature.js +1 -30
  15. package/dist/core/features/crop-detection-feature.js +1 -26
  16. package/dist/core/features/feature-base.js +1 -1
  17. package/dist/core/features/feature-manager.js +1 -55
  18. package/dist/core/features/one-euro-filter-feature.js +1 -44
  19. package/dist/core/features/temporal-filter-feature.js +1 -57
  20. package/dist/core/image-list.js +1 -54
  21. package/dist/core/input-loader.js +1 -87
  22. package/dist/core/matching/hamming-distance.js +1 -66
  23. package/dist/core/matching/hdc.js +1 -102
  24. package/dist/core/matching/hierarchical-clustering.js +1 -130
  25. package/dist/core/matching/hough.js +1 -170
  26. package/dist/core/matching/matcher.js +1 -66
  27. package/dist/core/matching/matching.js +1 -401
  28. package/dist/core/matching/ransacHomography.js +1 -132
  29. package/dist/core/perception/bio-inspired-engine.js +1 -232
  30. package/dist/core/perception/foveal-attention.js +1 -280
  31. package/dist/core/perception/index.js +1 -17
  32. package/dist/core/perception/predictive-coding.js +1 -278
  33. package/dist/core/perception/saccadic-controller.js +1 -269
  34. package/dist/core/perception/saliency-map.js +1 -254
  35. package/dist/core/perception/scale-orchestrator.js +1 -68
  36. package/dist/core/protocol.js +1 -254
  37. package/dist/core/tracker/extract-utils.js +1 -29
  38. package/dist/core/tracker/extract.js +1 -306
  39. package/dist/core/tracker/tracker.js +1 -352
  40. package/dist/core/utils/cumsum.js +1 -37
  41. package/dist/core/utils/delaunay.js +1 -125
  42. package/dist/core/utils/geometry.js +1 -101
  43. package/dist/core/utils/gpu-compute.js +1 -231
  44. package/dist/core/utils/homography.js +1 -138
  45. package/dist/core/utils/images.js +1 -108
  46. package/dist/core/utils/lsh-binarizer.js +1 -37
  47. package/dist/core/utils/lsh-direct.js +1 -76
  48. package/dist/core/utils/projection.js +1 -51
  49. package/dist/core/utils/randomizer.js +1 -25
  50. package/dist/core/utils/worker-pool.js +1 -89
  51. package/dist/index.js +1 -7
  52. package/dist/libs/one-euro-filter.js +1 -70
  53. package/dist/react/TaptappAR.js +1 -151
  54. package/dist/react/types.js +1 -16
  55. package/dist/react/use-ar.js +1 -118
  56. package/dist/runtime/aframe.js +1 -272
  57. package/dist/runtime/bio-inspired-controller.js +1 -358
  58. package/dist/runtime/controller.js +1 -592
  59. package/dist/runtime/controller.worker.js +1 -93
  60. package/dist/runtime/index.js +1 -5
  61. package/dist/runtime/three.js +1 -304
  62. package/dist/runtime/track.js +1 -381
  63. package/package.json +9 -3
@@ -1,306 +1 @@
1
- import { Cumsum } from "../utils/cumsum.js";
2
- import { gpuCompute } from "../utils/gpu-compute.js";
3
- const SEARCH_SIZE1 = 10;
4
- const SEARCH_SIZE2 = 2;
5
- // Template parameters - ajustados para más puntos
6
- const TEMPLATE_SIZE = 6;
7
- const TEMPLATE_SD_THRESH = 4.0; // Reducido de 5.0 para aceptar más candidatos
8
- const MAX_SIM_THRESH = 0.95;
9
- const MAX_THRESH = 0.9;
10
- const MIN_THRESH = 0.2;
11
- const SD_THRESH = 8.0;
12
- const OCCUPANCY_SIZE = 8; // Reduced from 10 to allow more density
13
- // GPU mode flag - set to false to use original JS implementation
14
- let useGPU = true;
15
- /**
16
- * Set GPU mode for extraction
17
- * @param {boolean} enabled - Whether to use GPU acceleration
18
- */
19
- export const setGPUMode = (enabled) => {
20
- useGPU = enabled;
21
- };
22
- /*
23
- * Input image is in grey format. the imageData array size is width * height. value range from 0-255
24
- * pixel value at row r and c = imageData[r * width + c]
25
- *
26
- * @param {Uint8Array} options.imageData
27
- * @param {int} options.width image width
28
- * @param {int} options.height image height
29
- */
30
- const extract = (image) => {
31
- const { data: imageData, width, height } = image;
32
- let dValue, isCandidate;
33
- if (useGPU) {
34
- // GPU-accelerated edge detection
35
- const result = gpuCompute.edgeDetection(imageData, width, height);
36
- dValue = result.dValue;
37
- isCandidate = result.isCandidate;
38
- }
39
- else {
40
- // Original JS implementation
41
- dValue = new Float32Array(imageData.length);
42
- isCandidate = new Uint8Array(imageData.length);
43
- for (let j = 1; j < height - 1; j++) {
44
- const rowOffset = j * width;
45
- const prevRowOffset = (j - 1) * width;
46
- const nextRowOffset = (j + 1) * width;
47
- for (let i = 1; i < width - 1; i++) {
48
- const pos = rowOffset + i;
49
- // dx/dy with tight loops
50
- let dx = (imageData[prevRowOffset + i + 1] - imageData[prevRowOffset + i - 1] +
51
- imageData[rowOffset + i + 1] - imageData[rowOffset + i - 1] +
52
- imageData[nextRowOffset + i + 1] - imageData[nextRowOffset + i - 1]) / 768;
53
- let dy = (imageData[nextRowOffset + i - 1] - imageData[prevRowOffset + i - 1] +
54
- imageData[nextRowOffset + i] - imageData[prevRowOffset + i] +
55
- imageData[nextRowOffset + i + 1] - imageData[prevRowOffset + i + 1]) / 768;
56
- dValue[pos] = Math.sqrt((dx * dx + dy * dy) / 2);
57
- }
58
- }
59
- // Step 1.2 - Local Maxima (for JS path)
60
- for (let j = 1; j < height - 1; j++) {
61
- const rowOffset = j * width;
62
- for (let i = 1; i < width - 1; i++) {
63
- const pos = rowOffset + i;
64
- const val = dValue[pos];
65
- if (val > 0 &&
66
- val >= dValue[pos - 1] && val >= dValue[pos + 1] &&
67
- val >= dValue[pos - width] && val >= dValue[pos + width]) {
68
- isCandidate[pos] = 1;
69
- }
70
- }
71
- }
72
- }
73
- // Step 1.2 - Build Histogram from detected candidates
74
- const dValueHist = new Uint32Array(1000);
75
- let allCount = 0;
76
- for (let j = 1; j < height - 1; j++) {
77
- const rowOffset = j * width;
78
- for (let i = 1; i < width - 1; i++) {
79
- const pos = rowOffset + i;
80
- if (isCandidate[pos]) {
81
- const val = dValue[pos];
82
- let k = Math.floor(val * 1000);
83
- if (k > 999)
84
- k = 999;
85
- dValueHist[k]++;
86
- allCount++;
87
- }
88
- }
89
- }
90
- // Determine dValue threshold for top 5% (aumentado de 2% para más candidatos)
91
- const maxPoints = 0.10 * width * height; // Increased to 10% for more candidates
92
- let kThresh = 999;
93
- let filteredCount = 0;
94
- while (kThresh >= 0) {
95
- filteredCount += dValueHist[kThresh];
96
- if (filteredCount > maxPoints)
97
- break;
98
- kThresh--;
99
- }
100
- const minDValue = kThresh / 1000;
101
- // Step 2
102
- const imageDataSqr = new Float32Array(imageData.length);
103
- for (let i = 0; i < imageData.length; i++) {
104
- imageDataSqr[i] = imageData[i] * imageData[i];
105
- }
106
- const imageDataCumsum = new Cumsum(imageData, width, height);
107
- const imageDataSqrCumsum = new Cumsum(imageDataSqr, width, height);
108
- // Collect candidates above threshold
109
- const candidates = [];
110
- for (let i = 0; i < imageData.length; i++) {
111
- if (isCandidate[i] && dValue[i] >= minDValue) {
112
- candidates.push({
113
- pos: i,
114
- dval: dValue[i],
115
- x: i % width,
116
- y: Math.floor(i / width)
117
- });
118
- }
119
- }
120
- // Sort by dValue DESCENDING
121
- candidates.sort((a, b) => b.dval - a.dval);
122
- // Step 3 - On-Demand Feature Selection (The 10x Win)
123
- const divSize = (TEMPLATE_SIZE * 2 + 1) * 3;
124
- const maxFeatureNum = Math.floor(width / OCCUPANCY_SIZE) * Math.floor(height / OCCUPANCY_SIZE) +
125
- Math.floor(width / divSize) * Math.floor(height / divSize);
126
- const coords = [];
127
- const invalidated = new Uint8Array(width * height);
128
- const templateWidth = 2 * TEMPLATE_SIZE + 1;
129
- const nPixels = templateWidth * templateWidth;
130
- const actualOccSize = Math.floor(Math.min(width, height) / 12); // Reducido de 10 para más densidad
131
- for (let i = 0; i < candidates.length; i++) {
132
- const { x, y, pos } = candidates[i];
133
- if (invalidated[pos])
134
- continue;
135
- // Boundary safety for template
136
- if (x < TEMPLATE_SIZE + SEARCH_SIZE1 || x >= width - TEMPLATE_SIZE - SEARCH_SIZE1 ||
137
- y < TEMPLATE_SIZE + SEARCH_SIZE1 || y >= height - TEMPLATE_SIZE - SEARCH_SIZE1) {
138
- continue;
139
- }
140
- const vlen = _templateVar({
141
- image,
142
- cx: x,
143
- cy: y,
144
- sdThresh: TEMPLATE_SD_THRESH,
145
- imageDataCumsum,
146
- imageDataSqrCumsum,
147
- });
148
- if (vlen === null)
149
- continue;
150
- const templateAvg = imageDataCumsum.query(x - TEMPLATE_SIZE, y - TEMPLATE_SIZE, x + TEMPLATE_SIZE, y + TEMPLATE_SIZE) / nPixels;
151
- // Optimization: Cache template once per candidate
152
- const templateData = new Uint8Array(templateWidth * templateWidth);
153
- let tidx = 0;
154
- const tStart = (y - TEMPLATE_SIZE) * width + (x - TEMPLATE_SIZE);
155
- for (let tj = 0; tj < templateWidth; tj++) {
156
- const rowOffset = tStart + tj * width;
157
- for (let ti = 0; ti < templateWidth; ti++) {
158
- templateData[tidx++] = imageData[rowOffset + ti];
159
- }
160
- }
161
- // Step 2.1: Find max similarity in search area (On demand!)
162
- let max = -1.0;
163
- for (let jj = -SEARCH_SIZE1; jj <= SEARCH_SIZE1; jj++) {
164
- for (let ii = -SEARCH_SIZE1; ii <= SEARCH_SIZE1; ii++) {
165
- if (ii * ii + jj * jj <= SEARCH_SIZE2 * SEARCH_SIZE2)
166
- continue;
167
- const sim = _getSimilarityOptimized({
168
- image,
169
- cx: x + ii,
170
- cy: y + jj,
171
- vlen: vlen,
172
- templateData,
173
- templateAvg,
174
- templateWidth,
175
- imageDataCumsum,
176
- imageDataSqrCumsum,
177
- width,
178
- height
179
- });
180
- if (sim !== null && sim > max) {
181
- max = sim;
182
- if (max > MAX_THRESH)
183
- break;
184
- }
185
- }
186
- if (max > MAX_THRESH)
187
- break;
188
- }
189
- // Now decide if we select it
190
- if (max < MAX_THRESH) {
191
- // Uniqueness check (Step 2.2 sub-loop)
192
- let minUnique = 1.0;
193
- let maxUnique = -1.0;
194
- let failedUnique = false;
195
- for (let jj = -SEARCH_SIZE2; jj <= SEARCH_SIZE2; jj++) {
196
- for (let ii = -SEARCH_SIZE2; ii <= SEARCH_SIZE2; ii++) {
197
- if (ii * ii + jj * jj > SEARCH_SIZE2 * SEARCH_SIZE2)
198
- continue;
199
- if (ii === 0 && jj === 0)
200
- continue;
201
- const sim = _getSimilarityOptimized({
202
- image,
203
- vlen,
204
- cx: x + ii,
205
- cy: y + jj,
206
- templateData,
207
- templateAvg,
208
- templateWidth,
209
- imageDataCumsum,
210
- imageDataSqrCumsum,
211
- width,
212
- height
213
- });
214
- if (sim === null)
215
- continue;
216
- if (sim < minUnique)
217
- minUnique = sim;
218
- if (sim > maxUnique)
219
- maxUnique = sim;
220
- if (minUnique < MIN_THRESH || maxUnique > 0.99) {
221
- failedUnique = true;
222
- break;
223
- }
224
- }
225
- if (failedUnique)
226
- break;
227
- }
228
- if (!failedUnique) {
229
- coords.push({ x, y });
230
- // Invalidate neighbors
231
- for (let jj = -actualOccSize; jj <= actualOccSize; jj++) {
232
- const yy = y + jj;
233
- if (yy < 0 || yy >= height)
234
- continue;
235
- const rowStart = yy * width;
236
- for (let ii = -actualOccSize; ii <= actualOccSize; ii++) {
237
- const xx = x + ii;
238
- if (xx < 0 || xx >= width)
239
- continue;
240
- invalidated[rowStart + xx] = 1;
241
- }
242
- }
243
- }
244
- }
245
- if (coords.length >= maxFeatureNum)
246
- break;
247
- }
248
- return coords;
249
- };
250
- // compute variances of the pixels, centered at (cx, cy)
251
- const _templateVar = ({ image, cx, cy, sdThresh, imageDataCumsum, imageDataSqrCumsum }) => {
252
- if (cx - TEMPLATE_SIZE < 0 || cx + TEMPLATE_SIZE >= image.width)
253
- return null;
254
- if (cy - TEMPLATE_SIZE < 0 || cy + TEMPLATE_SIZE >= image.height)
255
- return null;
256
- const templateWidth = 2 * TEMPLATE_SIZE + 1;
257
- const nPixels = templateWidth * templateWidth;
258
- let average = imageDataCumsum.query(cx - TEMPLATE_SIZE, cy - TEMPLATE_SIZE, cx + TEMPLATE_SIZE, cy + TEMPLATE_SIZE);
259
- average /= nPixels;
260
- //v = sum((pixel_i - avg)^2) for all pixel i within the template
261
- // = sum(pixel_i^2) - sum(2 * avg * pixel_i) + sum(avg^avg)
262
- let vlen = imageDataSqrCumsum.query(cx - TEMPLATE_SIZE, cy - TEMPLATE_SIZE, cx + TEMPLATE_SIZE, cy + TEMPLATE_SIZE);
263
- vlen -=
264
- 2 *
265
- average *
266
- imageDataCumsum.query(cx - TEMPLATE_SIZE, cy - TEMPLATE_SIZE, cx + TEMPLATE_SIZE, cy + TEMPLATE_SIZE);
267
- vlen += nPixels * average * average;
268
- if (vlen / nPixels < sdThresh * sdThresh)
269
- return null;
270
- vlen = Math.sqrt(vlen);
271
- return vlen;
272
- };
273
- const _getSimilarityOptimized = (options) => {
274
- const { cx, cy, vlen, templateData, templateAvg, templateWidth, imageDataCumsum, imageDataSqrCumsum, width, height } = options;
275
- const imageData = options.image.data;
276
- const templateSize = (templateWidth - 1) / 2;
277
- if (cx - templateSize < 0 || cx + templateSize >= width)
278
- return null;
279
- if (cy - templateSize < 0 || cy + templateSize >= height)
280
- return null;
281
- const nP = templateWidth * templateWidth;
282
- const sx = imageDataCumsum.query(cx - templateSize, cy - templateSize, cx + templateSize, cy + templateSize);
283
- const sxx = imageDataSqrCumsum.query(cx - templateSize, cy - templateSize, cx + templateSize, cy + templateSize);
284
- // 🚀 MOONSHOT Early Exit: Check variance (vlen2) before expensive sxy loop
285
- let vlen2 = sxx - (sx * sx) / nP;
286
- if (vlen2 <= 0)
287
- return null;
288
- vlen2 = Math.sqrt(vlen2);
289
- // Full calculation - Optimized with 2x2 sub-sampling for SPEED
290
- let sxy = 0;
291
- const p1_start = (cy - templateSize) * width + (cx - templateSize);
292
- for (let j = 0; j < templateWidth; j++) {
293
- const rowOffset1 = p1_start + j * width;
294
- const rowOffset2 = j * templateWidth;
295
- for (let i = 0; i < templateWidth; i++) {
296
- sxy += imageData[rowOffset1 + i] * templateData[rowOffset2 + i];
297
- }
298
- }
299
- const sampledCount = templateWidth * templateWidth;
300
- const totalCount = templateWidth * templateWidth;
301
- sxy *= (totalCount / sampledCount);
302
- // Covariance check
303
- const sxy_final = sxy - templateAvg * sx;
304
- return (1.0 * sxy_final) / (vlen * vlen2);
305
- };
306
- export { extract };
1
+ import{Cumsum as t}from"../utils/cumsum.js";import{gpuCompute as e}from"../utils/gpu-compute.js";let n=!0;export const setGPUMode=t=>{n=t};const o=o=>{const{data:r,width:i,height:s}=o;let u,m;if(n){const t=e.edgeDetection(r,i,s);u=t.dValue,m=t.isCandidate}else{u=new Float32Array(r.length),m=new Uint8Array(r.length);for(let t=1;t<s-1;t++){const e=t*i,n=(t-1)*i,o=(t+1)*i;for(let t=1;t<i-1;t++){const a=e+t;let l=(r[n+t+1]-r[n+t-1]+r[e+t+1]-r[e+t-1]+r[o+t+1]-r[o+t-1])/768,i=(r[o+t-1]-r[n+t-1]+r[o+t]-r[n+t]+r[o+t+1]-r[n+t+1])/768;u[a]=Math.sqrt((l*l+i*i)/2)}}for(let t=1;t<s-1;t++){const e=t*i;for(let t=1;t<i-1;t++){const n=e+t,o=u[n];o>0&&o>=u[n-1]&&o>=u[n+1]&&o>=u[n-i]&&o>=u[n+i]&&(m[n]=1)}}}const c=new Uint32Array(1e3);for(let t=1;t<s-1;t++){const e=t*i;for(let t=1;t<i-1;t++){const n=e+t;if(m[n]){const t=u[n];let e=Math.floor(1e3*t);e>999&&(e=999),c[e]++}}}const f=.1*i*s;let h=999,g=0;for(;h>=0&&(g+=c[h],!(g>f));)h--;const d=h/1e3,y=new Float32Array(r.length);for(let t=0;t<r.length;t++)y[t]=r[t]*r[t];const p=new t(r,i,s),q=new t(y,i,s),D=[];for(let t=0;t<r.length;t++)m[t]&&u[t]>=d&&D.push({pos:t,dval:u[t],x:t%i,y:Math.floor(t/i)});D.sort((t,e)=>e.dval-t.dval);const w=Math.floor(i/8)*Math.floor(s/8)+Math.floor(i/39)*Math.floor(s/39),M=[],C=new Uint8Array(i*s),x=Math.floor(Math.min(i,s)/12);for(let t=0;t<D.length;t++){const{x:e,y:n,pos:u}=D[t];if(C[u])continue;if(e<16||e>=i-6-10||n<16||n>=s-6-10)continue;const m=a({image:o,cx:e,cy:n,sdThresh:4,imageDataCumsum:p,imageDataSqrCumsum:q});if(null===m)continue;const c=p.query(e-6,n-6,e+6,n+6)/169,f=new Uint8Array(169);let h=0;const g=(n-6)*i+(e-6);for(let t=0;t<13;t++){const e=g+t*i;for(let t=0;t<13;t++)f[h++]=r[e+t]}let d=-1;for(let t=-10;t<=10;t++){for(let a=-10;a<=10;a++){if(a*a+t*t<=4)continue;const r=l({image:o,cx:e+a,cy:n+t,vlen:m,templateData:f,templateAvg:c,templateWidth:13,imageDataCumsum:p,imageDataSqrCumsum:q,width:i,height:s});if(null!==r&&r>d&&(d=r,d>.9))break}if(d>.9)break}if(d<.9){let t=1,a=-1,r=!1;for(let u=-2;u<=2;u++){for(let h=-2;h<=2;h++){if(h*h+u*u>4)continue;if(0===h&&0===u)continue;const g=l({image:o,vlen:m,cx:e+h,cy:n+u,templateData:f,templateAvg:c,templateWidth:13,imageDataCumsum:p,imageDataSqrCumsum:q,width:i,height:s});if(null!==g&&(g<t&&(t=g),g>a&&(a=g),t<.2||a>.99)){r=!0;break}}if(r)break}if(!r){M.push({x:e,y:n});for(let t=-x;t<=x;t++){const o=n+t;if(o<0||o>=s)continue;const a=o*i;for(let t=-x;t<=x;t++){const n=e+t;n<0||n>=i||(C[a+n]=1)}}}}if(M.length>=w)break}return M},a=({image:t,cx:e,cy:n,sdThresh:o,imageDataCumsum:a,imageDataSqrCumsum:l})=>{if(e-6<0||e+6>=t.width)return null;if(n-6<0||n+6>=t.height)return null;let r=a.query(e-6,n-6,e+6,n+6);r/=169;let i=l.query(e-6,n-6,e+6,n+6);return i-=2*r*a.query(e-6,n-6,e+6,n+6),i+=169*r*r,i/169<o*o?null:(i=Math.sqrt(i),i)},l=t=>{const{cx:e,cy:n,vlen:o,templateData:a,templateAvg:l,templateWidth:r,imageDataCumsum:i,imageDataSqrCumsum:s,width:u,height:m}=t,c=t.image.data,f=(r-1)/2;if(e-f<0||e+f>=u)return null;if(n-f<0||n+f>=m)return null;const h=r*r,g=i.query(e-f,n-f,e+f,n+f);let d=s.query(e-f,n-f,e+f,n+f)-g*g/h;if(d<=0)return null;d=Math.sqrt(d);let y=0;const p=(n-f)*u+(e-f);for(let t=0;t<r;t++){const e=p+t*u,n=t*r;for(let t=0;t<r;t++)y+=c[e+t]*a[n+t]}return y*=r*r/(r*r),1*(y-l*g)/(o*d)};export{o as extract};
@@ -1,352 +1 @@
1
- import { buildModelViewProjectionTransform, computeScreenCoordiate } from "../estimation/utils.js";
2
- import { refineNonRigid, projectMesh } from "../estimation/non-rigid-refine.js";
3
- import { AR_CONFIG } from "../constants.js";
4
- const AR2_DEFAULT_TS = AR_CONFIG.TRACKER_TEMPLATE_SIZE;
5
- const AR2_DEFAULT_TS_GAP = 1;
6
- const AR2_SEARCH_SIZE = AR_CONFIG.TRACKER_SEARCH_SIZE;
7
- const AR2_SEARCH_GAP = 1;
8
- const AR2_SIM_THRESH = AR_CONFIG.TRACKER_SIMILARITY_THRESHOLD;
9
- const TRACKING_KEYFRAME = 0; // 0: 128px (optimized)
10
- class Tracker {
11
- constructor(markerDimensions, trackingDataList, projectionTransform, inputWidth, inputHeight, debugMode = false) {
12
- this.markerDimensions = markerDimensions;
13
- this.trackingDataList = trackingDataList;
14
- this.projectionTransform = projectionTransform;
15
- this.inputWidth = inputWidth;
16
- this.inputHeight = inputHeight;
17
- this.debugMode = debugMode;
18
- this.trackingKeyframeList = []; // All octaves for all targets: [targetIndex][octaveIndex]
19
- this.prebuiltData = []; // [targetIndex][octaveIndex]
20
- for (let i = 0; i < trackingDataList.length; i++) {
21
- const targetOctaves = trackingDataList[i];
22
- this.trackingKeyframeList[i] = targetOctaves;
23
- this.prebuiltData[i] = targetOctaves.map(keyframe => ({
24
- px: new Float32Array(keyframe.px),
25
- py: new Float32Array(keyframe.py),
26
- data: new Uint8Array(keyframe.d),
27
- width: keyframe.w,
28
- height: keyframe.h,
29
- scale: keyframe.s,
30
- mesh: keyframe.mesh,
31
- // Recyclable projected image buffer
32
- projectedImage: new Float32Array(keyframe.w * keyframe.h)
33
- }));
34
- }
35
- // Maintain mesh vertices state for temporal continuity
36
- this.meshVerticesState = []; // [targetIndex][octaveIndex]
37
- // Pre-allocate template data buffer to avoid garbage collection
38
- const templateOneSize = AR2_DEFAULT_TS;
39
- const templateSize = templateOneSize * 2 + 1;
40
- this.templateBuffer = new Float32Array(templateSize * templateSize);
41
- }
42
- dummyRun(inputData) {
43
- let transform = [
44
- [1, 0, 0, 0],
45
- [0, 1, 0, 0],
46
- [0, 0, 1, 0],
47
- ];
48
- for (let targetIndex = 0; targetIndex < this.trackingKeyframeList.length; targetIndex++) {
49
- this.track(inputData, transform, targetIndex);
50
- }
51
- }
52
- track(inputData, lastModelViewTransform, targetIndex) {
53
- let debugExtra = {};
54
- // Select the best octave based on current estimated distance/scale
55
- // We want the octave where the marker size is closest to its projected size on screen
56
- const modelViewProjectionTransform = buildModelViewProjectionTransform(this.projectionTransform, lastModelViewTransform);
57
- // Estimate current marker width on screen
58
- const [mW, mH] = this.markerDimensions[targetIndex];
59
- const p0 = computeScreenCoordiate(modelViewProjectionTransform, 0, 0);
60
- const p1 = computeScreenCoordiate(modelViewProjectionTransform, mW, 0);
61
- const screenW = Math.sqrt((p1.x - p0.x) ** 2 + (p1.y - p0.y) ** 2);
62
- // Select octave whose image width is closest to screenW
63
- // Select the best octave based on current estimated distance/scale
64
- // Hysteresis: prevent flip-flopping between octaves
65
- if (!this.lastOctaveIndex)
66
- this.lastOctaveIndex = [];
67
- let octaveIndex = this.lastOctaveIndex[targetIndex] !== undefined ? this.lastOctaveIndex[targetIndex] : 0;
68
- let minDiff = Math.abs(this.prebuiltData[targetIndex][octaveIndex].width - screenW);
69
- // Threshold to switch: only switch if another octave is much better (20% improvement)
70
- const switchThreshold = 0.8;
71
- for (let i = 0; i < this.prebuiltData[targetIndex].length; i++) {
72
- const diff = Math.abs(this.prebuiltData[targetIndex][i].width - screenW);
73
- if (diff < minDiff * switchThreshold) {
74
- minDiff = diff;
75
- octaveIndex = i;
76
- }
77
- }
78
- this.lastOctaveIndex[targetIndex] = octaveIndex;
79
- const prebuilt = this.prebuiltData[targetIndex][octaveIndex];
80
- // 1. Compute Projection (Warping)
81
- this._computeProjection(modelViewProjectionTransform, inputData, prebuilt);
82
- const projectedImage = prebuilt.projectedImage;
83
- // 2. Compute Matching (NCC)
84
- const { matchingPoints, sim } = this._computeMatching(prebuilt, projectedImage);
85
- const trackingFrame = this.trackingKeyframeList[targetIndex][octaveIndex];
86
- const worldCoords = [];
87
- const screenCoords = [];
88
- const goodTrack = [];
89
- const { px, py, s: scale } = trackingFrame;
90
- const reliabilities = [];
91
- for (let i = 0; i < matchingPoints.length; i++) {
92
- const reliability = sim[i];
93
- if (reliability > AR2_SIM_THRESH && i < px.length) {
94
- goodTrack.push(i);
95
- const point = computeScreenCoordiate(modelViewProjectionTransform, matchingPoints[i][0], matchingPoints[i][1]);
96
- screenCoords.push(point);
97
- worldCoords.push({
98
- x: px[i] / scale,
99
- y: py[i] / scale,
100
- z: 0,
101
- });
102
- reliabilities.push(reliability);
103
- }
104
- }
105
- // --- 🚀 MOONSHOT: Non-Rigid Mesh Refinement ---
106
- let deformedMesh = null;
107
- if (prebuilt.mesh && goodTrack.length >= 4) {
108
- if (!this.meshVerticesState[targetIndex])
109
- this.meshVerticesState[targetIndex] = [];
110
- let currentOctaveVertices = this.meshVerticesState[targetIndex][octaveIndex];
111
- // Initial setup: If no state, use the reference points (normalized) as first guess
112
- if (!currentOctaveVertices) {
113
- currentOctaveVertices = new Float32Array(px.length * 2);
114
- for (let i = 0; i < px.length; i++) {
115
- currentOctaveVertices[i * 2] = px[i];
116
- currentOctaveVertices[i * 2 + 1] = py[i];
117
- }
118
- }
119
- // Data fidelity: Prepare tracked targets for mass-spring
120
- const trackedTargets = [];
121
- for (let j = 0; j < goodTrack.length; j++) {
122
- const idx = goodTrack[j];
123
- trackedTargets.push({
124
- meshIndex: idx,
125
- x: matchingPoints[idx][0] * scale, // Convert back to octave space pixels
126
- y: matchingPoints[idx][1] * scale
127
- });
128
- }
129
- // Relax mesh in octave space
130
- const refinedOctaveVertices = refineNonRigid({
131
- mesh: prebuilt.mesh,
132
- trackedPoints: trackedTargets,
133
- currentVertices: currentOctaveVertices,
134
- iterations: 5
135
- });
136
- this.meshVerticesState[targetIndex][octaveIndex] = refinedOctaveVertices;
137
- // Project deformed mesh to screen space
138
- const screenMeshVertices = new Float32Array(refinedOctaveVertices.length);
139
- for (let i = 0; i < refinedOctaveVertices.length; i += 2) {
140
- const p = computeScreenCoordiate(modelViewProjectionTransform, refinedOctaveVertices[i] / scale, refinedOctaveVertices[i + 1] / scale);
141
- screenMeshVertices[i] = p.x;
142
- screenMeshVertices[i + 1] = p.y;
143
- }
144
- deformedMesh = {
145
- vertices: screenMeshVertices,
146
- triangles: prebuilt.mesh.t
147
- };
148
- }
149
- // 2.1 Spatial distribution check: Avoid getting stuck in corners/noise
150
- if (screenCoords.length >= 8) {
151
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
152
- for (const p of screenCoords) {
153
- if (p.x < minX)
154
- minX = p.x;
155
- if (p.y < minY)
156
- minY = p.y;
157
- if (p.x > maxX)
158
- maxX = p.x;
159
- if (p.y > maxY)
160
- maxY = p.y;
161
- }
162
- const detectedDiagonal = Math.sqrt((maxX - minX) ** 2 + (maxY - minY) ** 2);
163
- // If the points cover too little space compared to the screen size of the marker, it's a glitch
164
- if (detectedDiagonal < screenW * 0.15) {
165
- return { worldCoords: [], screenCoords: [], reliabilities: [], debugExtra };
166
- }
167
- }
168
- if (this.debugMode) {
169
- debugExtra = {
170
- octaveIndex,
171
- // Remove Array.from to avoid massive GC pressure
172
- projectedImage: projectedImage,
173
- matchingPoints,
174
- goodTrack,
175
- trackedPoints: screenCoords,
176
- };
177
- }
178
- return { worldCoords, screenCoords, reliabilities, indices: goodTrack, octaveIndex, deformedMesh, debugExtra };
179
- }
180
- /**
181
- * Pure JS implementation of NCC matching
182
- */
183
- _computeMatching(prebuilt, projectedImage) {
184
- const { px, py, scale, data: markerPixels, width: markerWidth, height: markerHeight } = prebuilt;
185
- const featureCount = px.length;
186
- const templateOneSize = AR2_DEFAULT_TS;
187
- const templateSize = templateOneSize * 2 + 1;
188
- const nPixels = templateSize * templateSize;
189
- const oneOverNPixels = 1.0 / nPixels;
190
- const searchOneSize = AR2_SEARCH_SIZE;
191
- const searchGap = AR2_SEARCH_GAP;
192
- const matchingPoints = [];
193
- const sims = new Float32Array(featureCount);
194
- // Reuse shared template buffer
195
- const templateData = this.templateBuffer;
196
- for (let f = 0; f < featureCount; f++) {
197
- const sCenterX = (px[f] + 0.5) | 0; // Faster Math.round
198
- const sCenterY = (py[f] + 0.5) | 0;
199
- let bestSim = -1.0;
200
- let bestX = px[f] / scale;
201
- let bestY = py[f] / scale;
202
- // Pre-calculate template stats for this feature
203
- let sumT = 0;
204
- let sumT2 = 0;
205
- let tidx = 0;
206
- for (let ty = -templateOneSize; ty <= templateOneSize; ty++) {
207
- const fyOffset = (sCenterY + ty) * markerWidth;
208
- for (let tx = -templateOneSize; tx <= templateOneSize; tx++) {
209
- const val = markerPixels[fyOffset + sCenterX + tx];
210
- templateData[tidx++] = val;
211
- sumT += val;
212
- sumT2 += val * val;
213
- }
214
- }
215
- const varT = Math.sqrt(Math.max(0, sumT2 - (sumT * sumT) * oneOverNPixels));
216
- if (varT < 0.0001) {
217
- sims[f] = -1.0;
218
- matchingPoints.push([bestX, bestY]);
219
- continue;
220
- }
221
- // 🚀 MOONSHOT: Coarse-to-Fine Search for MAXIMUM FPS
222
- // Step 1: Coarse search (Gap 4)
223
- const coarseGap = 4;
224
- for (let sy = -searchOneSize; sy <= searchOneSize; sy += coarseGap) {
225
- const cy = sCenterY + sy;
226
- if (cy < templateOneSize || cy >= markerHeight - templateOneSize)
227
- continue;
228
- for (let sx = -searchOneSize; sx <= searchOneSize; sx += coarseGap) {
229
- const cx = sCenterX + sx;
230
- if (cx < templateOneSize || cx >= markerWidth - templateOneSize)
231
- continue;
232
- let sumI = 0, sumI2 = 0, sumIT = 0;
233
- for (let ty = -templateOneSize; ty <= templateOneSize; ty++) {
234
- const rowOffset = (cy + ty) * markerWidth;
235
- const tRowOffset = (ty + templateOneSize) * templateSize;
236
- for (let tx = -templateOneSize; tx <= templateOneSize; tx++) {
237
- const valI = projectedImage[rowOffset + (cx + tx)];
238
- const valT = templateData[tRowOffset + (tx + templateOneSize)];
239
- sumI += valI;
240
- sumI2 += valI * valI;
241
- sumIT += valI * valT;
242
- }
243
- }
244
- const varI = Math.sqrt(Math.max(0, sumI2 - (sumI * sumI) * oneOverNPixels));
245
- if (varI < 0.0001)
246
- continue;
247
- const sim = (sumIT - (sumI * sumT) * oneOverNPixels) / (varI * varT);
248
- if (sim > bestSim) {
249
- bestSim = sim;
250
- bestX = cx / scale;
251
- bestY = cy / scale;
252
- }
253
- }
254
- }
255
- // Step 2: Fine refinement (Gap 1) only around the best coarse match
256
- if (bestSim > AR2_SIM_THRESH) {
257
- const fineCenterX = (bestX * scale) | 0;
258
- const fineCenterY = (bestY * scale) | 0;
259
- const fineSearch = coarseGap;
260
- for (let sy = -fineSearch; sy <= fineSearch; sy++) {
261
- const cy = fineCenterY + sy;
262
- if (cy < templateOneSize || cy >= markerHeight - templateOneSize)
263
- continue;
264
- for (let sx = -fineSearch; sx <= fineSearch; sx++) {
265
- const cx = fineCenterX + sx;
266
- if (cx < templateOneSize || cx >= markerWidth - templateOneSize)
267
- continue;
268
- let sumI = 0, sumI2 = 0, sumIT = 0;
269
- for (let ty = -templateOneSize; ty <= templateOneSize; ty++) {
270
- const rowOffset = (cy + ty) * markerWidth;
271
- const tRowOffset = (ty + templateOneSize) * templateSize;
272
- for (let tx = -templateOneSize; tx <= templateOneSize; tx++) {
273
- const valI = projectedImage[rowOffset + (cx + tx)];
274
- const valT = templateData[tRowOffset + (tx + templateOneSize)];
275
- sumI += valI;
276
- sumI2 += valI * valI;
277
- sumIT += valI * valT;
278
- }
279
- }
280
- const varI = Math.sqrt(Math.max(0, sumI2 - (sumI * sumI) * oneOverNPixels));
281
- if (varI < 0.0001)
282
- continue;
283
- const sim = (sumIT - (sumI * sumT) * oneOverNPixels) / (varI * varT);
284
- if (sim > bestSim) {
285
- bestSim = sim;
286
- bestX = cx / scale;
287
- bestY = cy / scale;
288
- }
289
- }
290
- }
291
- }
292
- sims[f] = bestSim;
293
- matchingPoints.push([bestX, bestY]);
294
- }
295
- return { matchingPoints, sim: sims };
296
- }
297
- /**
298
- * Pure JS implementation of Bilinear Warping
299
- */
300
- _computeProjection(M, inputData, prebuilt) {
301
- const { width: markerWidth, height: markerHeight, scale: markerScale, projectedImage } = prebuilt;
302
- const invScale = 1.0 / markerScale;
303
- const inputW = this.inputWidth;
304
- const inputH = this.inputHeight;
305
- const m00 = M[0][0];
306
- const m01 = M[0][1];
307
- const m03 = M[0][3];
308
- const m10 = M[1][0];
309
- const m11 = M[1][1];
310
- const m13 = M[1][3];
311
- const m20 = M[2][0];
312
- const m21 = M[2][1];
313
- const m23 = M[2][3];
314
- for (let j = 0; j < markerHeight; j++) {
315
- const y = j * invScale;
316
- const jOffset = j * markerWidth;
317
- for (let i = 0; i < markerWidth; i++) {
318
- const x = i * invScale;
319
- const uz = (x * m20) + (y * m21) + m23;
320
- const invZ = 1.0 / uz;
321
- const ux = ((x * m00) + (y * m01) + m03) * invZ;
322
- const uy = ((x * m10) + (y * m11) + m13) * invZ;
323
- // Bilinear interpolation
324
- const x0 = ux | 0; // Faster Math.floor
325
- const y0 = uy | 0;
326
- const x1 = x0 + 1;
327
- const y1 = y0 + 1;
328
- if (x0 >= 0 && x1 < inputW && y0 >= 0 && y1 < inputH) {
329
- const dx = ux - x0;
330
- const dy = uy - y0;
331
- const omDx = 1.0 - dx;
332
- const omDy = 1.0 - dy;
333
- const y0Offset = y0 * inputW;
334
- const y1Offset = y1 * inputW;
335
- const v00 = inputData[y0Offset + x0];
336
- const v10 = inputData[y0Offset + x1];
337
- const v01 = inputData[y1Offset + x0];
338
- const v11 = inputData[y1Offset + x1];
339
- projectedImage[jOffset + i] =
340
- v00 * omDx * omDy +
341
- v10 * dx * omDy +
342
- v01 * omDx * dy +
343
- v11 * dx * dy;
344
- }
345
- else {
346
- projectedImage[jOffset + i] = 0;
347
- }
348
- }
349
- }
350
- }
351
- }
352
- export { Tracker };
1
+ import{buildModelViewProjectionTransform as t,computeScreenCoordiate as e}from"../estimation/utils.js";import{refineNonRigid as s,projectMesh as i}from"../estimation/non-rigid-refine.js";import{AR_CONFIG as o}from"../constants.js";const n=o.TRACKER_TEMPLATE_SIZE,r=o.TRACKER_SEARCH_SIZE,a=o.TRACKER_SIMILARITY_THRESHOLD;class h{constructor(t,e,s,i,o,r=!1){this.markerDimensions=t,this.trackingDataList=e,this.projectionTransform=s,this.inputWidth=i,this.inputHeight=o,this.debugMode=r,this.trackingKeyframeList=[],this.prebuiltData=[];for(let t=0;t<e.length;t++){const s=e[t];this.trackingKeyframeList[t]=s,this.prebuiltData[t]=s.map(t=>({px:new Float32Array(t.px),py:new Float32Array(t.py),data:new Uint8Array(t.d),width:t.w,height:t.h,scale:t.s,mesh:t.mesh,projectedImage:new Float32Array(t.w*t.h)}))}this.meshVerticesState=[];const a=2*n+1;this.templateBuffer=new Float32Array(a*a)}dummyRun(t){let e=[[1,0,0,0],[0,1,0,0],[0,0,1,0]];for(let s=0;s<this.trackingKeyframeList.length;s++)this.track(t,e,s)}track(i,o,n){let r={};const h=t(this.projectionTransform,o),[c,l]=this.markerDimensions[n],m=e(h,0,0),f=e(h,c,0),d=Math.sqrt((f.x-m.x)**2+(f.y-m.y)**2);this.lastOctaveIndex||(this.lastOctaveIndex=[]);let p=void 0!==this.lastOctaveIndex[n]?this.lastOctaveIndex[n]:0,u=Math.abs(this.prebuiltData[n][p].width-d);for(let t=0;t<this.prebuiltData[n].length;t++){const e=Math.abs(this.prebuiltData[n][t].width-d);e<.8*u&&(u=e,p=t)}this.lastOctaveIndex[n]=p;const g=this.prebuiltData[n][p];this._computeProjection(h,i,g);const x=g.projectedImage,{matchingPoints:y,sim:w}=this._computeMatching(g,x),I=this.trackingKeyframeList[n][p],M=[],b=[],A=[],{px:k,py:j,s:E}=I,D=[];for(let t=0;t<y.length;t++){const s=w[t];if(s>a&&t<k.length){A.push(t);const i=e(h,y[t][0],y[t][1]);b.push(i),M.push({x:k[t]/E,y:j[t]/E,z:0}),D.push(s)}}let R=null;if(g.mesh&&A.length>=4){this.meshVerticesState[n]||(this.meshVerticesState[n]=[]);let t=this.meshVerticesState[n][p];if(!t){t=new Float32Array(2*k.length);for(let e=0;e<k.length;e++)t[2*e]=k[e],t[2*e+1]=j[e]}const i=[];for(let t=0;t<A.length;t++){const e=A[t];i.push({meshIndex:e,x:y[e][0]*E,y:y[e][1]*E})}const o=s({mesh:g.mesh,trackedPoints:i,currentVertices:t,iterations:5});this.meshVerticesState[n][p]=o;const r=new Float32Array(o.length);for(let t=0;t<o.length;t+=2){const s=e(h,o[t]/E,o[t+1]/E);r[t]=s.x,r[t+1]=s.y}R={vertices:r,triangles:g.mesh.t}}if(b.length>=8){let t=1/0,e=1/0,s=-1/0,i=-1/0;for(const o of b)o.x<t&&(t=o.x),o.y<e&&(e=o.y),o.x>s&&(s=o.x),o.y>i&&(i=o.y);if(Math.sqrt((s-t)**2+(i-e)**2)<.15*d)return{worldCoords:[],screenCoords:[],reliabilities:[],debugExtra:r}}return this.debugMode&&(r={octaveIndex:p,projectedImage:x,matchingPoints:y,goodTrack:A,trackedPoints:b}),{worldCoords:M,screenCoords:b,reliabilities:D,indices:A,octaveIndex:p,deformedMesh:R,debugExtra:r}}_computeMatching(t,e){const{px:s,py:i,scale:o,data:h,width:c,height:l}=t,m=s.length,f=n,d=2*f+1,p=1/(d*d),u=r,g=[],x=new Float32Array(m),y=this.templateBuffer;for(let t=0;t<m;t++){const n=s[t]+.5|0,r=i[t]+.5|0;let m=-1,w=s[t]/o,I=i[t]/o,M=0,b=0,A=0;for(let t=-f;t<=f;t++){const e=(r+t)*c;for(let t=-f;t<=f;t++){const s=h[e+n+t];y[A++]=s,M+=s,b+=s*s}}const k=Math.sqrt(Math.max(0,b-M*M*p));if(k<1e-4){x[t]=-1,g.push([w,I]);continue}const j=4;for(let t=-u;t<=u;t+=j){const s=r+t;if(!(s<f||s>=l-f))for(let t=-u;t<=u;t+=j){const i=n+t;if(i<f||i>=c-f)continue;let r=0,a=0,h=0;for(let t=-f;t<=f;t++){const o=(s+t)*c,n=(t+f)*d;for(let t=-f;t<=f;t++){const s=e[o+(i+t)];r+=s,a+=s*s,h+=s*y[n+(t+f)]}}const l=Math.sqrt(Math.max(0,a-r*r*p));if(l<1e-4)continue;const u=(h-r*M*p)/(l*k);u>m&&(m=u,w=i/o,I=s/o)}}if(m>a){const t=w*o|0,s=I*o|0,i=j;for(let n=-i;n<=i;n++){const r=s+n;if(!(r<f||r>=l-f))for(let s=-i;s<=i;s++){const i=t+s;if(i<f||i>=c-f)continue;let n=0,a=0,h=0;for(let t=-f;t<=f;t++){const s=(r+t)*c,o=(t+f)*d;for(let t=-f;t<=f;t++){const r=e[s+(i+t)];n+=r,a+=r*r,h+=r*y[o+(t+f)]}}const l=Math.sqrt(Math.max(0,a-n*n*p));if(l<1e-4)continue;const u=(h-n*M*p)/(l*k);u>m&&(m=u,w=i/o,I=r/o)}}}x[t]=m,g.push([w,I])}return{matchingPoints:g,sim:x}}_computeProjection(t,e,s){const{width:i,height:o,scale:n,projectedImage:r}=s,a=1/n,h=this.inputWidth,c=this.inputHeight,l=t[0][0],m=t[0][1],f=t[0][3],d=t[1][0],p=t[1][1],u=t[1][3],g=t[2][0],x=t[2][1],y=t[2][3];for(let t=0;t<o;t++){const s=t*a,o=t*i;for(let t=0;t<i;t++){const i=t*a,n=1/(i*g+s*x+y),w=(i*l+s*m+f)*n,I=(i*d+s*p+u)*n,M=0|w,b=0|I,A=M+1,k=b+1;if(M>=0&&A<h&&b>=0&&k<c){const s=w-M,i=I-b,n=1-s,a=1-i,c=b*h,l=k*h,m=e[c+M],f=e[c+A],d=e[l+M],p=e[l+A];r[o+t]=m*n*a+f*s*a+d*n*i+p*s*i}else r[o+t]=0}}}}export{h as Tracker};