@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.
Files changed (47) hide show
  1. package/README.md +42 -45
  2. package/dist/compiler/aframe.js +8 -8
  3. package/dist/compiler/controller.d.ts +50 -76
  4. package/dist/compiler/controller.js +72 -116
  5. package/dist/compiler/detector/detector-lite.js +82 -99
  6. package/dist/compiler/index.js +3 -3
  7. package/dist/compiler/matching/hamming-distance.d.ts +8 -0
  8. package/dist/compiler/matching/hamming-distance.js +35 -16
  9. package/dist/compiler/matching/hierarchical-clustering.d.ts +9 -0
  10. package/dist/compiler/matching/hierarchical-clustering.js +76 -56
  11. package/dist/compiler/matching/matching.js +3 -3
  12. package/dist/compiler/node-worker.js +144 -18
  13. package/dist/compiler/offline-compiler.d.ts +34 -83
  14. package/dist/compiler/offline-compiler.js +92 -96
  15. package/dist/compiler/simple-ar.d.ts +31 -57
  16. package/dist/compiler/simple-ar.js +32 -73
  17. package/dist/compiler/three.d.ts +13 -8
  18. package/dist/compiler/three.js +6 -6
  19. package/dist/compiler/tracker/extract.js +17 -14
  20. package/dist/compiler/utils/images.js +11 -16
  21. package/dist/compiler/utils/lsh-direct.d.ts +12 -0
  22. package/dist/compiler/utils/lsh-direct.js +76 -0
  23. package/dist/compiler/utils/worker-pool.js +10 -1
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.js +2 -2
  26. package/dist/react/types.d.ts +1 -1
  27. package/dist/react/types.js +1 -1
  28. package/package.json +2 -1
  29. package/src/compiler/aframe.js +8 -8
  30. package/src/compiler/controller.ts +512 -0
  31. package/src/compiler/detector/detector-lite.js +87 -107
  32. package/src/compiler/index.js +3 -3
  33. package/src/compiler/matching/hamming-distance.js +39 -16
  34. package/src/compiler/matching/hierarchical-clustering.js +85 -57
  35. package/src/compiler/matching/matching.js +3 -3
  36. package/src/compiler/node-worker.js +163 -18
  37. package/src/compiler/offline-compiler.ts +513 -0
  38. package/src/compiler/{simple-ar.js → simple-ar.ts} +64 -91
  39. package/src/compiler/three.js +6 -6
  40. package/src/compiler/tracker/extract.js +18 -15
  41. package/src/compiler/utils/images.js +11 -21
  42. package/src/compiler/utils/lsh-direct.js +86 -0
  43. package/src/compiler/utils/worker-pool.js +9 -1
  44. package/src/index.ts +2 -2
  45. package/src/react/types.ts +2 -2
  46. package/src/compiler/controller.js +0 -554
  47. package/src/compiler/offline-compiler.js +0 -515
@@ -1,554 +0,0 @@
1
- import { Tracker } from "./tracker/tracker.js";
2
- import { CropDetector } from "./detector/crop-detector.js";
3
- import { OfflineCompiler as Compiler } from "./offline-compiler.js";
4
- import { InputLoader } from "./input-loader.js";
5
- import { OneEuroFilter } from "../libs/one-euro-filter.js";
6
-
7
- let ControllerWorker;
8
-
9
- // Conditional import for worker to avoid crash in non-vite environments
10
- try {
11
- const workerModule = await import("./controller.worker.js?worker&inline");
12
- ControllerWorker = workerModule.default;
13
- } catch (e) {
14
- // Fallback for tests or other environments
15
- ControllerWorker = null;
16
- }
17
-
18
- const DEFAULT_FILTER_CUTOFF = 0.1; // Menor cutoff para filtrar más ruidos cuando está quieto
19
- const DEFAULT_FILTER_BETA = 0.01; // Beta bajo para suavizar movimientos rápidos
20
-
21
- const DEFAULT_WARMUP_TOLERANCE = 8; // Más frames de calentamiento para asegurar estabilidad inicial
22
-
23
- const DEFAULT_MISS_TOLERANCE = 2; // Reducido para que el objeto desaparezca más rápido tras pérdida
24
-
25
- class Controller {
26
- constructor({
27
- inputWidth,
28
- inputHeight,
29
- onUpdate = null,
30
- debugMode = false,
31
- maxTrack = 1,
32
- warmupTolerance = null,
33
- missTolerance = null,
34
- filterMinCF = null,
35
- filterBeta = null,
36
- worker = null, // Allow custom worker injection
37
- }) {
38
- this.inputWidth = inputWidth;
39
- this.inputHeight = inputHeight;
40
- this.maxTrack = maxTrack;
41
- this.filterMinCF = filterMinCF === null ? DEFAULT_FILTER_CUTOFF : filterMinCF;
42
- this.filterBeta = filterBeta === null ? DEFAULT_FILTER_BETA : filterBeta;
43
- this.warmupTolerance = warmupTolerance === null ? DEFAULT_WARMUP_TOLERANCE : warmupTolerance;
44
- this.missTolerance = missTolerance === null ? DEFAULT_MISS_TOLERANCE : missTolerance;
45
- this.cropDetector = new CropDetector(this.inputWidth, this.inputHeight, debugMode, true);
46
- this.inputLoader = new InputLoader(this.inputWidth, this.inputHeight);
47
- this.markerDimensions = null;
48
- this.onUpdate = onUpdate;
49
- this.debugMode = debugMode;
50
- this.processingVideo = false;
51
- this.interestedTargetIndex = -1;
52
- this.trackingStates = [];
53
- this.worker = worker;
54
- if (this.worker) this._setupWorkerListener();
55
-
56
- const near = 10;
57
- const far = 100000;
58
- const fovy = (45.0 * Math.PI) / 180;
59
- const f = this.inputHeight / 2 / Math.tan(fovy / 2);
60
-
61
- this.projectionTransform = [
62
- [f, 0, this.inputWidth / 2],
63
- [0, f, this.inputHeight / 2],
64
- [0, 0, 1],
65
- ];
66
-
67
- this.projectionMatrix = this._glProjectionMatrix({
68
- projectionTransform: this.projectionTransform,
69
- width: this.inputWidth,
70
- height: this.inputHeight,
71
- near: near,
72
- far: far,
73
- });
74
- }
75
-
76
- _setupWorkerListener() {
77
- if (!this.worker) return;
78
- this.worker.onmessage = (e) => {
79
- if (e.data.type === "matchDone" && this.workerMatchDone !== null) {
80
- this.workerMatchDone(e.data);
81
- }
82
- if (e.data.type === "trackUpdateDone" && this.workerTrackDone !== null) {
83
- this.workerTrackDone(e.data);
84
- }
85
- };
86
- }
87
-
88
- _ensureWorker() {
89
- if (this.worker) return;
90
- if (ControllerWorker) {
91
- this.worker = new ControllerWorker();
92
- this._setupWorkerListener();
93
- }
94
- }
95
-
96
- /**
97
- * Load image targets from one or multiple .mind files
98
- * @param {string|string[]} fileURLs - Single URL or array of URLs to .mind files
99
- * @returns {Promise<{dimensions, matchingDataList, trackingDataList}>}
100
- */
101
- async addImageTargets(fileURLs) {
102
- const urls = Array.isArray(fileURLs) ? fileURLs : [fileURLs];
103
-
104
- // Fetch all .mind files in parallel
105
- const buffers = await Promise.all(
106
- urls.map(async (url) => {
107
- const response = await fetch(url);
108
- return response.arrayBuffer();
109
- })
110
- );
111
-
112
- // Combine all buffers into a single target list
113
- return this.addImageTargetsFromBuffers(buffers);
114
- }
115
-
116
- /**
117
- * Load image targets from multiple ArrayBuffers
118
- * @param {ArrayBuffer[]} buffers - Array of .mind file buffers
119
- */
120
- addImageTargetsFromBuffers(buffers) {
121
- const allTrackingData = [];
122
- const allMatchingData = [];
123
- const allDimensions = [];
124
-
125
- for (const buffer of buffers) {
126
- const compiler = new Compiler();
127
- const { dataList } = compiler.importData(buffer);
128
-
129
- for (const item of dataList) {
130
- allMatchingData.push(item.matchingData);
131
- allTrackingData.push(item.trackingData);
132
- allDimensions.push([item.targetImage.width, item.targetImage.height]);
133
- }
134
- }
135
-
136
- this.tracker = new Tracker(
137
- allDimensions,
138
- allTrackingData,
139
- this.projectionTransform,
140
- this.inputWidth,
141
- this.inputHeight,
142
- this.debugMode,
143
- );
144
-
145
- this._ensureWorker();
146
- if (this.worker) {
147
- this.worker.postMessage({
148
- type: "setup",
149
- inputWidth: this.inputWidth,
150
- inputHeight: this.inputHeight,
151
- projectionTransform: this.projectionTransform,
152
- debugMode: this.debugMode,
153
- matchingDataList: allMatchingData,
154
- });
155
- }
156
-
157
- this.markerDimensions = allDimensions;
158
- this.matchingDataList = allMatchingData; // Store for main-thread fallback
159
- return { dimensions: allDimensions, matchingDataList: allMatchingData, trackingDataList: allTrackingData };
160
- }
161
-
162
- /**
163
- * Load image targets from a single ArrayBuffer (backward compatible)
164
- * @param {ArrayBuffer} buffer - Single .mind file buffer
165
- */
166
- addImageTargetsFromBuffer(buffer) {
167
- return this.addImageTargetsFromBuffers([buffer]);
168
- }
169
-
170
- dispose() {
171
- this.stopProcessVideo();
172
- if (this.worker) {
173
- this.worker.postMessage({ type: "dispose" });
174
- this.worker = null;
175
- }
176
- }
177
-
178
- dummyRun(input) {
179
- const inputData = this.inputLoader.loadInput(input);
180
- this.cropDetector.detect(inputData);
181
- this.tracker.dummyRun(inputData);
182
- }
183
-
184
- getProjectionMatrix() {
185
- return this.projectionMatrix;
186
- }
187
-
188
- getRotatedZ90Matrix(m) {
189
- // rotate 90 degree along z-axis
190
- // rotation matrix
191
- // | 0 -1 0 0 |
192
- // | 1 0 0 0 |
193
- // | 0 0 1 0 |
194
- // | 0 0 0 1 |
195
- const rotatedMatrix = [
196
- -m[1],
197
- m[0],
198
- m[2],
199
- m[3],
200
- -m[5],
201
- m[4],
202
- m[6],
203
- m[7],
204
- -m[9],
205
- m[8],
206
- m[10],
207
- m[11],
208
- -m[13],
209
- m[12],
210
- m[14],
211
- m[15],
212
- ];
213
- return rotatedMatrix;
214
- }
215
-
216
- getWorldMatrix(modelViewTransform, targetIndex) {
217
- return this._glModelViewMatrix(modelViewTransform, targetIndex);
218
- }
219
-
220
- async _detectAndMatch(inputData, targetIndexes) {
221
- const { featurePoints } = this.cropDetector.detectMoving(inputData);
222
- const { targetIndex: matchedTargetIndex, modelViewTransform } = await this._workerMatch(
223
- featurePoints,
224
- targetIndexes,
225
- );
226
- return { targetIndex: matchedTargetIndex, modelViewTransform };
227
- }
228
- async _trackAndUpdate(inputData, lastModelViewTransform, targetIndex) {
229
- const { worldCoords, screenCoords } = this.tracker.track(
230
- inputData,
231
- lastModelViewTransform,
232
- targetIndex,
233
- );
234
- if (worldCoords.length < 6) return null; // Umbral de puntos mínimos para mantener el seguimiento
235
- const modelViewTransform = await this._workerTrackUpdate(lastModelViewTransform, {
236
- worldCoords,
237
- screenCoords,
238
- });
239
- return modelViewTransform;
240
- }
241
-
242
- processVideo(input) {
243
- if (this.processingVideo) return;
244
-
245
- this.processingVideo = true;
246
-
247
- this.trackingStates = [];
248
- for (let i = 0; i < this.markerDimensions.length; i++) {
249
- this.trackingStates.push({
250
- showing: false,
251
- isTracking: false,
252
- currentModelViewTransform: null,
253
- trackCount: 0,
254
- trackMiss: 0,
255
- filter: new OneEuroFilter({ minCutOff: this.filterMinCF, beta: this.filterBeta }),
256
- });
257
- }
258
-
259
- const startProcessing = async () => {
260
- while (true) {
261
- if (!this.processingVideo) break;
262
-
263
- const inputData = this.inputLoader.loadInput(input);
264
-
265
- const nTracking = this.trackingStates.reduce((acc, s) => {
266
- return acc + (!!s.isTracking ? 1 : 0);
267
- }, 0);
268
-
269
- // detect and match only if less then maxTrack
270
- if (nTracking < this.maxTrack) {
271
- const matchingIndexes = [];
272
- for (let i = 0; i < this.trackingStates.length; i++) {
273
- const trackingState = this.trackingStates[i];
274
- if (trackingState.isTracking === true) continue;
275
- if (this.interestedTargetIndex !== -1 && this.interestedTargetIndex !== i) continue;
276
-
277
- matchingIndexes.push(i);
278
- }
279
-
280
- const { targetIndex: matchedTargetIndex, modelViewTransform } =
281
- await this._detectAndMatch(inputData, matchingIndexes);
282
-
283
- if (matchedTargetIndex !== -1) {
284
- this.trackingStates[matchedTargetIndex].isTracking = true;
285
- this.trackingStates[matchedTargetIndex].currentModelViewTransform = modelViewTransform;
286
- }
287
- }
288
-
289
- // tracking update
290
- for (let i = 0; i < this.trackingStates.length; i++) {
291
- const trackingState = this.trackingStates[i];
292
-
293
- if (trackingState.isTracking) {
294
- let modelViewTransform = await this._trackAndUpdate(
295
- inputData,
296
- trackingState.currentModelViewTransform,
297
- i,
298
- );
299
- if (modelViewTransform === null) {
300
- trackingState.isTracking = false;
301
- } else {
302
- trackingState.currentModelViewTransform = modelViewTransform;
303
- }
304
- }
305
-
306
- // if not showing, then show it once it reaches warmup number of frames
307
- if (!trackingState.showing) {
308
- if (trackingState.isTracking) {
309
- trackingState.trackMiss = 0;
310
- trackingState.trackCount += 1;
311
- if (trackingState.trackCount > this.warmupTolerance) {
312
- trackingState.showing = true;
313
- trackingState.trackingMatrix = null;
314
- trackingState.filter.reset();
315
- }
316
- }
317
- }
318
-
319
- // if showing, then count miss, and hide it when reaches tolerance
320
- if (trackingState.showing) {
321
- if (!trackingState.isTracking) {
322
- trackingState.trackCount = 0;
323
- trackingState.trackMiss += 1;
324
-
325
- if (trackingState.trackMiss > this.missTolerance) {
326
- trackingState.showing = false;
327
- trackingState.trackingMatrix = null;
328
- this.onUpdate &&
329
- this.onUpdate({ type: "updateMatrix", targetIndex: i, worldMatrix: null });
330
- }
331
- } else {
332
- trackingState.trackMiss = 0;
333
- }
334
- }
335
-
336
- // if showing, then call onUpdate, with world matrix
337
- if (trackingState.showing) {
338
- const worldMatrix = this._glModelViewMatrix(trackingState.currentModelViewTransform, i);
339
- trackingState.trackingMatrix = trackingState.filter.filter(Date.now(), worldMatrix);
340
-
341
- let clone = [];
342
- for (let j = 0; j < trackingState.trackingMatrix.length; j++) {
343
- clone[j] = trackingState.trackingMatrix[j];
344
- }
345
-
346
- const isInputRotated =
347
- input.width === this.inputHeight && input.height === this.inputWidth;
348
- if (isInputRotated) {
349
- clone = this.getRotatedZ90Matrix(clone);
350
- }
351
-
352
- this.onUpdate &&
353
- this.onUpdate({ type: "updateMatrix", targetIndex: i, worldMatrix: clone, modelViewTransform: trackingState.currentModelViewTransform });
354
- }
355
- }
356
-
357
- this.onUpdate && this.onUpdate({ type: "processDone" });
358
-
359
- // Use requestAnimationFrame if available, otherwise just wait briefly
360
- if (typeof requestAnimationFrame !== "undefined") {
361
- await new Promise(requestAnimationFrame);
362
- } else {
363
- await new Promise(resolve => setTimeout(resolve, 16));
364
- }
365
- }
366
- };
367
- startProcessing();
368
- }
369
-
370
- stopProcessVideo() {
371
- this.processingVideo = false;
372
- }
373
-
374
- async detect(input) {
375
- const inputData = this.inputLoader.loadInput(input);
376
- const { featurePoints, debugExtra } = this.cropDetector.detect(inputData);
377
- return { featurePoints, debugExtra };
378
- }
379
-
380
- async match(featurePoints, targetIndex) {
381
- const { targetIndex: matchedTargetIndex, modelViewTransform, screenCoords, worldCoords, debugExtra } = await this._workerMatch(featurePoints, [
382
- targetIndex,
383
- ]);
384
- return { targetIndex: matchedTargetIndex, modelViewTransform, screenCoords, worldCoords, debugExtra };
385
- }
386
-
387
- async track(input, modelViewTransform, targetIndex) {
388
- const inputData = this.inputLoader.loadInput(input);
389
- const result = this.tracker.track(inputData, modelViewTransform, targetIndex);
390
- return result;
391
- }
392
-
393
- async trackUpdate(modelViewTransform, trackFeatures) {
394
- if (trackFeatures.worldCoords.length < 4) return null;
395
- const modelViewTransform2 = await this._workerTrackUpdate(modelViewTransform, trackFeatures);
396
- return modelViewTransform2;
397
- }
398
-
399
- _workerMatch(featurePoints, targetIndexes) {
400
- return new Promise((resolve) => {
401
- // If no worker available, process on main thread
402
- if (!this.worker) {
403
- this._matchOnMainThread(featurePoints, targetIndexes).then(resolve);
404
- return;
405
- }
406
-
407
- this.workerMatchDone = (data) => {
408
- resolve({
409
- targetIndex: data.targetIndex,
410
- modelViewTransform: data.modelViewTransform,
411
- screenCoords: data.screenCoords,
412
- worldCoords: data.worldCoords,
413
- debugExtra: data.debugExtra,
414
- });
415
- };
416
- this.worker.postMessage({ type: "match", featurePoints: featurePoints, targetIndexes });
417
- });
418
- }
419
-
420
- async _matchOnMainThread(featurePoints, targetIndexes) {
421
- // Lazy initialize Matcher and Estimator for main thread
422
- if (!this.mainThreadMatcher) {
423
- const { Matcher } = await import("./matching/matcher.js");
424
- const { Estimator } = await import("./estimation/estimator.js");
425
- this.mainThreadMatcher = new Matcher(this.inputWidth, this.inputHeight, this.debugMode);
426
- this.mainThreadEstimator = new Estimator(this.projectionTransform);
427
- }
428
-
429
- let matchedTargetIndex = -1;
430
- let matchedModelViewTransform = null;
431
- let matchedScreenCoords = null;
432
- let matchedWorldCoords = null;
433
- let matchedDebugExtra = null;
434
-
435
- for (let i = 0; i < targetIndexes.length; i++) {
436
- const matchingIndex = targetIndexes[i];
437
-
438
- const { keyframeIndex, screenCoords, worldCoords, debugExtra } = this.mainThreadMatcher.matchDetection(
439
- this.matchingDataList[matchingIndex],
440
- featurePoints,
441
- );
442
- matchedDebugExtra = debugExtra;
443
-
444
- if (keyframeIndex !== -1) {
445
- const modelViewTransform = this.mainThreadEstimator.estimate({ screenCoords, worldCoords });
446
-
447
- if (modelViewTransform) {
448
- matchedTargetIndex = matchingIndex;
449
- matchedModelViewTransform = modelViewTransform;
450
- matchedScreenCoords = screenCoords;
451
- matchedWorldCoords = worldCoords;
452
- }
453
- break;
454
- }
455
- }
456
-
457
- return {
458
- targetIndex: matchedTargetIndex,
459
- modelViewTransform: matchedModelViewTransform,
460
- screenCoords: matchedScreenCoords,
461
- worldCoords: matchedWorldCoords,
462
- debugExtra: matchedDebugExtra,
463
- };
464
- }
465
-
466
- _workerTrackUpdate(modelViewTransform, trackingFeatures) {
467
- return new Promise((resolve) => {
468
- // If no worker available, process on main thread
469
- if (!this.worker) {
470
- this._trackUpdateOnMainThread(modelViewTransform, trackingFeatures).then(resolve);
471
- return;
472
- }
473
-
474
- this.workerTrackDone = (data) => {
475
- resolve(data.modelViewTransform);
476
- };
477
- const { worldCoords, screenCoords } = trackingFeatures;
478
- this.worker.postMessage({
479
- type: "trackUpdate",
480
- modelViewTransform,
481
- worldCoords,
482
- screenCoords,
483
- });
484
- });
485
- }
486
-
487
- async _trackUpdateOnMainThread(modelViewTransform, trackingFeatures) {
488
- // Lazy initialize Estimator for main thread
489
- if (!this.mainThreadEstimator) {
490
- const { Estimator } = await import("./estimation/estimator.js");
491
- this.mainThreadEstimator = new Estimator(this.projectionTransform);
492
- }
493
-
494
- const { worldCoords, screenCoords } = trackingFeatures;
495
- const finalModelViewTransform = this.mainThreadEstimator.refineEstimate({
496
- initialModelViewTransform: modelViewTransform,
497
- worldCoords,
498
- screenCoords,
499
- });
500
- return finalModelViewTransform;
501
- }
502
-
503
- _glModelViewMatrix(modelViewTransform, targetIndex) {
504
- const height = this.markerDimensions[targetIndex][1];
505
-
506
- const openGLWorldMatrix = [
507
- modelViewTransform[0][0],
508
- -modelViewTransform[1][0],
509
- -modelViewTransform[2][0],
510
- 0,
511
- -modelViewTransform[0][1],
512
- modelViewTransform[1][1],
513
- modelViewTransform[2][1],
514
- 0,
515
- -modelViewTransform[0][2],
516
- modelViewTransform[1][2],
517
- modelViewTransform[2][2],
518
- 0,
519
- modelViewTransform[0][1] * height + modelViewTransform[0][3],
520
- -(modelViewTransform[1][1] * height + modelViewTransform[1][3]),
521
- -(modelViewTransform[2][1] * height + modelViewTransform[2][3]),
522
- 1,
523
- ];
524
- return openGLWorldMatrix;
525
- }
526
-
527
- _glProjectionMatrix({ projectionTransform, width, height, near, far }) {
528
- const proj = [
529
- [
530
- (2 * projectionTransform[0][0]) / width,
531
- 0,
532
- -((2 * projectionTransform[0][2]) / width - 1),
533
- 0,
534
- ],
535
- [
536
- 0,
537
- (2 * projectionTransform[1][1]) / height,
538
- -((2 * projectionTransform[1][2]) / height - 1),
539
- 0,
540
- ],
541
- [0, 0, -(far + near) / (far - near), (-2 * far * near) / (far - near)],
542
- [0, 0, -1, 0],
543
- ];
544
- const projMatrix = [];
545
- for (let i = 0; i < 4; i++) {
546
- for (let j = 0; j < 4; j++) {
547
- projMatrix.push(proj[j][i]);
548
- }
549
- }
550
- return projMatrix;
551
- }
552
- }
553
-
554
- export { Controller };