@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.
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
@@ -0,0 +1,512 @@
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: any;
8
+
9
+ // Conditional import for worker to avoid crash in non-vite environments
10
+ const getControllerWorker = async () => {
11
+ if (typeof Worker === 'undefined') return null;
12
+ try {
13
+ // @ts-ignore
14
+ const workerModule = await import("./controller.worker.js?worker&inline");
15
+ return workerModule.default;
16
+ } catch (e) {
17
+ return null;
18
+ }
19
+ };
20
+ ControllerWorker = await getControllerWorker();
21
+
22
+ const DEFAULT_FILTER_CUTOFF = 0.1;
23
+ const DEFAULT_FILTER_BETA = 0.01;
24
+ const DEFAULT_WARMUP_TOLERANCE = 8;
25
+ const DEFAULT_MISS_TOLERANCE = 2;
26
+
27
+ export interface ControllerOptions {
28
+ inputWidth: number;
29
+ inputHeight: number;
30
+ onUpdate?: ((data: any) => void) | null;
31
+ debugMode?: boolean;
32
+ maxTrack?: number;
33
+ warmupTolerance?: number | null;
34
+ missTolerance?: number | null;
35
+ filterMinCF?: number | null;
36
+ filterBeta?: number | null;
37
+ worker?: any;
38
+ }
39
+
40
+ class Controller {
41
+ inputWidth: number;
42
+ inputHeight: number;
43
+ maxTrack: number;
44
+ filterMinCF: number;
45
+ filterBeta: number;
46
+ warmupTolerance: number;
47
+ missTolerance: number;
48
+ cropDetector: CropDetector;
49
+ inputLoader: InputLoader;
50
+ markerDimensions: any[] | null = null;
51
+ onUpdate: ((data: any) => void) | null;
52
+ debugMode: boolean;
53
+ processingVideo: boolean = false;
54
+ interestedTargetIndex: number = -1;
55
+ trackingStates: any[] = [];
56
+ worker: any;
57
+ projectionTransform: number[][];
58
+ projectionMatrix: number[];
59
+ tracker: Tracker | null = null;
60
+ matchingDataList: any;
61
+ workerMatchDone: ((data: any) => void) | null = null;
62
+ workerTrackDone: ((data: any) => void) | null = null;
63
+ mainThreadMatcher: any;
64
+ mainThreadEstimator: any;
65
+
66
+ constructor({
67
+ inputWidth,
68
+ inputHeight,
69
+ onUpdate = null,
70
+ debugMode = false,
71
+ maxTrack = 1,
72
+ warmupTolerance = null,
73
+ missTolerance = null,
74
+ filterMinCF = null,
75
+ filterBeta = null,
76
+ worker = null,
77
+ }: ControllerOptions) {
78
+ this.inputWidth = inputWidth;
79
+ this.inputHeight = inputHeight;
80
+ this.maxTrack = maxTrack;
81
+ this.filterMinCF = filterMinCF === null ? DEFAULT_FILTER_CUTOFF : filterMinCF;
82
+ this.filterBeta = filterBeta === null ? DEFAULT_FILTER_BETA : filterBeta;
83
+ this.warmupTolerance = warmupTolerance === null ? DEFAULT_WARMUP_TOLERANCE : warmupTolerance;
84
+ this.missTolerance = missTolerance === null ? DEFAULT_MISS_TOLERANCE : missTolerance;
85
+ this.cropDetector = new CropDetector(this.inputWidth, this.inputHeight, debugMode);
86
+ this.inputLoader = new InputLoader(this.inputWidth, this.inputHeight);
87
+ this.onUpdate = onUpdate;
88
+ this.debugMode = debugMode;
89
+ this.worker = worker;
90
+ if (this.worker) this._setupWorkerListener();
91
+
92
+ const near = 10;
93
+ const far = 100000;
94
+ const fovy = (45.0 * Math.PI) / 180;
95
+ const f = this.inputHeight / 2 / Math.tan(fovy / 2);
96
+
97
+ this.projectionTransform = [
98
+ [f, 0, this.inputWidth / 2],
99
+ [0, f, this.inputHeight / 2],
100
+ [0, 0, 1],
101
+ ];
102
+
103
+ this.projectionMatrix = this._glProjectionMatrix({
104
+ projectionTransform: this.projectionTransform,
105
+ width: this.inputWidth,
106
+ height: this.inputHeight,
107
+ near: near,
108
+ far: far,
109
+ });
110
+ }
111
+
112
+ _setupWorkerListener() {
113
+ if (!this.worker) return;
114
+ this.worker.onmessage = (e: any) => {
115
+ if (e.data.type === "matchDone" && this.workerMatchDone !== null) {
116
+ this.workerMatchDone(e.data);
117
+ }
118
+ if (e.data.type === "trackUpdateDone" && this.workerTrackDone !== null) {
119
+ this.workerTrackDone(e.data);
120
+ }
121
+ };
122
+ }
123
+
124
+ _ensureWorker() {
125
+ if (this.worker) return;
126
+ if (ControllerWorker && typeof Worker !== 'undefined') {
127
+ this.worker = new ControllerWorker();
128
+ this._setupWorkerListener();
129
+ }
130
+ }
131
+
132
+ async addImageTargets(fileURLs: string | string[]) {
133
+ const urls = Array.isArray(fileURLs) ? fileURLs : [fileURLs];
134
+ const buffers = await Promise.all(
135
+ urls.map(async (url) => {
136
+ const response = await fetch(url);
137
+ return response.arrayBuffer();
138
+ })
139
+ );
140
+ return this.addImageTargetsFromBuffers(buffers);
141
+ }
142
+
143
+ addImageTargetsFromBuffers(buffers: ArrayBuffer[]) {
144
+ const allTrackingData: any[] = [];
145
+ const allMatchingData: any[] = [];
146
+ const allDimensions: any[] = [];
147
+
148
+ for (const buffer of buffers) {
149
+ const compiler = new Compiler();
150
+ const result = compiler.importData(buffer);
151
+ const dataList = (result as any).dataList || [];
152
+
153
+ for (const item of dataList) {
154
+ allMatchingData.push(item.matchingData);
155
+ allTrackingData.push(item.trackingData);
156
+ allDimensions.push([item.targetImage.width, item.targetImage.height]);
157
+ }
158
+ }
159
+
160
+ this.tracker = new Tracker(
161
+ allDimensions,
162
+ allTrackingData,
163
+ this.projectionTransform,
164
+ this.inputWidth,
165
+ this.inputHeight,
166
+ this.debugMode,
167
+ );
168
+
169
+ this._ensureWorker();
170
+ if (this.worker) {
171
+ this.worker.postMessage({
172
+ type: "setup",
173
+ inputWidth: this.inputWidth,
174
+ inputHeight: this.inputHeight,
175
+ projectionTransform: this.projectionTransform,
176
+ debugMode: this.debugMode,
177
+ matchingDataList: allMatchingData,
178
+ });
179
+ }
180
+
181
+ this.markerDimensions = allDimensions;
182
+ this.matchingDataList = allMatchingData;
183
+ return { dimensions: allDimensions, matchingDataList: allMatchingData, trackingDataList: allTrackingData };
184
+ }
185
+
186
+ addImageTargetsFromBuffer(buffer: ArrayBuffer) {
187
+ return this.addImageTargetsFromBuffers([buffer]);
188
+ }
189
+
190
+ dispose() {
191
+ this.stopProcessVideo();
192
+ if (this.worker) {
193
+ this.worker.postMessage({ type: "dispose" });
194
+ this.worker = null;
195
+ }
196
+ }
197
+
198
+ dummyRun(input: any) {
199
+ const inputData = this.inputLoader.loadInput(input);
200
+ this.cropDetector.detect(inputData);
201
+ this.tracker!.dummyRun(inputData);
202
+ }
203
+
204
+ getProjectionMatrix() {
205
+ return this.projectionMatrix;
206
+ }
207
+
208
+ getRotatedZ90Matrix(m: number[]) {
209
+ return [
210
+ -m[1], m[0], m[2], m[3],
211
+ -m[5], m[4], m[6], m[7],
212
+ -m[9], m[8], m[10], m[11],
213
+ -m[13], m[12], m[14], m[15],
214
+ ];
215
+ }
216
+
217
+ getWorldMatrix(modelViewTransform: number[][], targetIndex: number) {
218
+ return this._glModelViewMatrix(modelViewTransform, targetIndex);
219
+ }
220
+
221
+ async _detectAndMatch(inputData: any, targetIndexes: number[]) {
222
+ const { featurePoints } = this.cropDetector.detectMoving(inputData);
223
+ const { targetIndex: matchedTargetIndex, modelViewTransform } = await this._workerMatch(
224
+ featurePoints,
225
+ targetIndexes,
226
+ );
227
+ return { targetIndex: matchedTargetIndex, modelViewTransform };
228
+ }
229
+
230
+ async _trackAndUpdate(inputData: any, lastModelViewTransform: number[][], targetIndex: number) {
231
+ const { worldCoords, screenCoords } = this.tracker!.track(
232
+ inputData,
233
+ lastModelViewTransform,
234
+ targetIndex,
235
+ );
236
+ if (worldCoords.length < 6) return null;
237
+ const modelViewTransform = await this._workerTrackUpdate(lastModelViewTransform, {
238
+ worldCoords,
239
+ screenCoords,
240
+ });
241
+ return modelViewTransform;
242
+ }
243
+
244
+ processVideo(input: any) {
245
+ if (this.processingVideo) return;
246
+ this.processingVideo = true;
247
+
248
+ this.trackingStates = [];
249
+ for (let i = 0; i < (this.markerDimensions?.length || 0); i++) {
250
+ this.trackingStates.push({
251
+ showing: false,
252
+ isTracking: false,
253
+ currentModelViewTransform: null,
254
+ trackCount: 0,
255
+ trackMiss: 0,
256
+ filter: new OneEuroFilter({ minCutOff: this.filterMinCF, beta: this.filterBeta }),
257
+ });
258
+ }
259
+
260
+ const startProcessing = async () => {
261
+ while (true) {
262
+ if (!this.processingVideo) break;
263
+
264
+ const inputData = this.inputLoader.loadInput(input);
265
+ const nTracking = this.trackingStates.reduce((acc, s) => acc + (!!s.isTracking ? 1 : 0), 0);
266
+
267
+ if (nTracking < this.maxTrack) {
268
+ const matchingIndexes = [];
269
+ for (let i = 0; i < this.trackingStates.length; i++) {
270
+ const trackingState = this.trackingStates[i];
271
+ if (trackingState.isTracking === true) continue;
272
+ if (this.interestedTargetIndex !== -1 && this.interestedTargetIndex !== i) continue;
273
+ matchingIndexes.push(i);
274
+ }
275
+
276
+ const { targetIndex: matchedTargetIndex, modelViewTransform } =
277
+ await this._detectAndMatch(inputData, matchingIndexes);
278
+
279
+ if (matchedTargetIndex !== -1) {
280
+ this.trackingStates[matchedTargetIndex].isTracking = true;
281
+ this.trackingStates[matchedTargetIndex].currentModelViewTransform = modelViewTransform;
282
+ }
283
+ }
284
+
285
+ for (let i = 0; i < this.trackingStates.length; i++) {
286
+ const trackingState = this.trackingStates[i];
287
+
288
+ if (trackingState.isTracking) {
289
+ let modelViewTransform = await this._trackAndUpdate(
290
+ inputData,
291
+ trackingState.currentModelViewTransform,
292
+ i,
293
+ );
294
+ if (modelViewTransform === null) {
295
+ trackingState.isTracking = false;
296
+ } else {
297
+ trackingState.currentModelViewTransform = modelViewTransform;
298
+ }
299
+ }
300
+
301
+ if (!trackingState.showing) {
302
+ if (trackingState.isTracking) {
303
+ trackingState.trackMiss = 0;
304
+ trackingState.trackCount += 1;
305
+ if (trackingState.trackCount > this.warmupTolerance) {
306
+ trackingState.showing = true;
307
+ trackingState.trackingMatrix = null;
308
+ trackingState.filter.reset();
309
+ }
310
+ }
311
+ }
312
+
313
+ if (trackingState.showing) {
314
+ if (!trackingState.isTracking) {
315
+ trackingState.trackCount = 0;
316
+ trackingState.trackMiss += 1;
317
+ if (trackingState.trackMiss > this.missTolerance) {
318
+ trackingState.showing = false;
319
+ trackingState.trackingMatrix = null;
320
+ this.onUpdate && this.onUpdate({ type: "updateMatrix", targetIndex: i, worldMatrix: null });
321
+ }
322
+ } else {
323
+ trackingState.trackMiss = 0;
324
+ }
325
+ }
326
+
327
+ if (trackingState.showing) {
328
+ const worldMatrix = this._glModelViewMatrix(trackingState.currentModelViewTransform, i);
329
+ trackingState.trackingMatrix = trackingState.filter.filter(Date.now(), worldMatrix);
330
+ let clone = [...trackingState.trackingMatrix];
331
+
332
+ const isInputRotated = input.width === this.inputHeight && input.height === this.inputWidth;
333
+ if (isInputRotated) {
334
+ clone = this.getRotatedZ90Matrix(clone);
335
+ }
336
+
337
+ this.onUpdate && this.onUpdate({
338
+ type: "updateMatrix",
339
+ targetIndex: i,
340
+ worldMatrix: clone,
341
+ modelViewTransform: trackingState.currentModelViewTransform
342
+ });
343
+ }
344
+ }
345
+
346
+ this.onUpdate && this.onUpdate({ type: "processDone" });
347
+
348
+ if (typeof requestAnimationFrame !== "undefined") {
349
+ await new Promise(requestAnimationFrame);
350
+ } else {
351
+ await new Promise(resolve => setTimeout(resolve, 16));
352
+ }
353
+ }
354
+ };
355
+ startProcessing();
356
+ }
357
+
358
+ stopProcessVideo() {
359
+ this.processingVideo = false;
360
+ }
361
+
362
+ async detect(input: any) {
363
+ const inputData = this.inputLoader.loadInput(input);
364
+ const { featurePoints, debugExtra } = this.cropDetector.detect(inputData);
365
+ return { featurePoints, debugExtra };
366
+ }
367
+
368
+ async match(featurePoints: any, targetIndex: number) {
369
+ const { targetIndex: matchedTargetIndex, modelViewTransform, screenCoords, worldCoords, debugExtra } = await this._workerMatch(featurePoints, [
370
+ targetIndex,
371
+ ]);
372
+ return { targetIndex: matchedTargetIndex, modelViewTransform, screenCoords, worldCoords, debugExtra };
373
+ }
374
+
375
+ async track(input: any, modelViewTransform: number[][], targetIndex: number) {
376
+ const inputData = this.inputLoader.loadInput(input);
377
+ return this.tracker!.track(inputData, modelViewTransform, targetIndex);
378
+ }
379
+
380
+ async trackUpdate(modelViewTransform: number[][], trackFeatures: any) {
381
+ if (trackFeatures.worldCoords.length < 4) return null;
382
+ return this._workerTrackUpdate(modelViewTransform, trackFeatures);
383
+ }
384
+
385
+ _workerMatch(featurePoints: any, targetIndexes: number[]): Promise<any> {
386
+ return new Promise((resolve) => {
387
+ if (!this.worker) {
388
+ this._matchOnMainThread(featurePoints, targetIndexes).then(resolve);
389
+ return;
390
+ }
391
+
392
+ this.workerMatchDone = (data: any) => {
393
+ resolve({
394
+ targetIndex: data.targetIndex,
395
+ modelViewTransform: data.modelViewTransform,
396
+ screenCoords: data.screenCoords,
397
+ worldCoords: data.worldCoords,
398
+ debugExtra: data.debugExtra,
399
+ });
400
+ };
401
+ this.worker.postMessage({ type: "match", featurePoints: featurePoints, targetIndexes });
402
+ });
403
+ }
404
+
405
+ async _matchOnMainThread(featurePoints: any, targetIndexes: number[]) {
406
+ if (!this.mainThreadMatcher) {
407
+ const { Matcher } = await import("./matching/matcher.js");
408
+ const { Estimator } = await import("./estimation/estimator.js");
409
+ this.mainThreadMatcher = new Matcher(this.inputWidth, this.inputHeight, this.debugMode);
410
+ this.mainThreadEstimator = new Estimator(this.projectionTransform);
411
+ }
412
+
413
+ let matchedTargetIndex = -1;
414
+ let matchedModelViewTransform = null;
415
+ let matchedScreenCoords = null;
416
+ let matchedWorldCoords = null;
417
+ let matchedDebugExtra = null;
418
+
419
+ for (let i = 0; i < targetIndexes.length; i++) {
420
+ const matchingIndex = targetIndexes[i];
421
+ const { keyframeIndex, screenCoords, worldCoords, debugExtra } = this.mainThreadMatcher.matchDetection(
422
+ this.matchingDataList[matchingIndex],
423
+ featurePoints,
424
+ );
425
+ matchedDebugExtra = debugExtra;
426
+
427
+ if (keyframeIndex !== -1) {
428
+ const modelViewTransform = this.mainThreadEstimator.estimate({ screenCoords, worldCoords });
429
+ if (modelViewTransform) {
430
+ matchedTargetIndex = matchingIndex;
431
+ matchedModelViewTransform = modelViewTransform;
432
+ matchedScreenCoords = screenCoords;
433
+ matchedWorldCoords = worldCoords;
434
+ }
435
+ break;
436
+ }
437
+ }
438
+
439
+ return {
440
+ targetIndex: matchedTargetIndex,
441
+ modelViewTransform: matchedModelViewTransform,
442
+ screenCoords: matchedScreenCoords,
443
+ worldCoords: matchedWorldCoords,
444
+ debugExtra: matchedDebugExtra,
445
+ };
446
+ }
447
+
448
+ _workerTrackUpdate(modelViewTransform: number[][], trackingFeatures: any): Promise<any> {
449
+ return new Promise((resolve) => {
450
+ if (!this.worker) {
451
+ this._trackUpdateOnMainThread(modelViewTransform, trackingFeatures).then(resolve);
452
+ return;
453
+ }
454
+
455
+ this.workerTrackDone = (data: any) => {
456
+ resolve(data.modelViewTransform);
457
+ };
458
+ const { worldCoords, screenCoords } = trackingFeatures;
459
+ this.worker.postMessage({
460
+ type: "trackUpdate",
461
+ modelViewTransform,
462
+ worldCoords,
463
+ screenCoords,
464
+ });
465
+ });
466
+ }
467
+
468
+ async _trackUpdateOnMainThread(modelViewTransform: number[][], trackingFeatures: any) {
469
+ if (!this.mainThreadEstimator) {
470
+ const { Estimator } = await import("./estimation/estimator.js");
471
+ this.mainThreadEstimator = new Estimator(this.projectionTransform);
472
+ }
473
+
474
+ const { worldCoords, screenCoords } = trackingFeatures;
475
+ return this.mainThreadEstimator.refineEstimate({
476
+ initialModelViewTransform: modelViewTransform,
477
+ worldCoords,
478
+ screenCoords,
479
+ });
480
+ }
481
+
482
+ _glModelViewMatrix(modelViewTransform: number[][], targetIndex: number) {
483
+ const height = this.markerDimensions![targetIndex][1];
484
+ return [
485
+ modelViewTransform[0][0], -modelViewTransform[1][0], -modelViewTransform[2][0], 0,
486
+ -modelViewTransform[0][1], modelViewTransform[1][1], modelViewTransform[2][1], 0,
487
+ -modelViewTransform[0][2], modelViewTransform[1][2], modelViewTransform[2][2], 0,
488
+ modelViewTransform[0][1] * height + modelViewTransform[0][3],
489
+ -(modelViewTransform[1][1] * height + modelViewTransform[1][3]),
490
+ -(modelViewTransform[2][1] * height + modelViewTransform[2][3]),
491
+ 1,
492
+ ];
493
+ }
494
+
495
+ _glProjectionMatrix({ projectionTransform, width, height, near, far }: any) {
496
+ const proj = [
497
+ [(2 * projectionTransform[0][0]) / width, 0, -((2 * projectionTransform[0][2]) / width - 1), 0],
498
+ [0, (2 * projectionTransform[1][1]) / height, -((2 * projectionTransform[1][2]) / height - 1), 0],
499
+ [0, 0, -(far + near) / (far - near), (-2 * far * near) / (far - near)],
500
+ [0, 0, -1, 0],
501
+ ];
502
+ const projMatrix = [];
503
+ for (let i = 0; i < 4; i++) {
504
+ for (let j = 0; j < 4; j++) {
505
+ projMatrix.push(proj[j][i]);
506
+ }
507
+ }
508
+ return projMatrix;
509
+ }
510
+ }
511
+
512
+ export { Controller };