@srsergio/taptapp-ar 1.0.35 → 1.0.36

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.
@@ -217,9 +217,9 @@ export class OfflineCompiler {
217
217
  }
218
218
  const dataList = this.data.map((item) => {
219
219
  const matchingData = item.matchingData.map((kf) => this._packKeyframe(kf));
220
- const trackingData = [item.trackingData[0]].map((td) => {
220
+ const trackingData = item.trackingData.map((td) => {
221
221
  const count = td.points.length;
222
- // Step 1: Packed Coords - Normalize width/height to 16-bit
222
+ // Packed Coords - Float32 for now as in current import logic
223
223
  const px = new Float32Array(count);
224
224
  const py = new Float32Array(count);
225
225
  for (let i = 0; i < count; i++) {
@@ -192,60 +192,69 @@ class SimpleAR {
192
192
  const needsRotation = isPortrait && isVideoLandscape;
193
193
  // 3. Get intrinsic projection from controller
194
194
  const proj = this.controller.projectionTransform;
195
- // 4. Position calculation via matrix3d (Support for 3D tilt/Z-rotation)
196
- // We convert the OpenGL World Matrix to a CSS matrix3d.
197
- // The OpenGL matrix is column-major. CSS matrix3d is also column-major.
198
- const m = this.controller.getWorldMatrix(mVT, targetIndex);
199
- // Map OpenGL coords to Screen Pixels using the projection logic
200
- const vW = needsRotation ? videoH : videoW;
201
- const vH = needsRotation ? videoW : videoH;
202
- const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
203
- const displayW = vW * perspectiveScale;
204
- const displayH = vH * perspectiveScale;
205
- const offsetX = (containerRect.width - displayW) / 2;
206
- const offsetY = (containerRect.height - displayH) / 2;
207
- // Adjust for centered marker and scaleMultiplier
208
- const s = finalScale; // We still need the base scale factor for the pixel-to-marker mapping
209
- // However, a cleaner way is to use the world matrix directly and map it.
210
- // Actually, the simpler way to do 3D in CSS while keeping my projection logic is:
211
- // Project the 4 corners and find the homography, OR
212
- // Use the OpenGL matrix directly with a perspective mapping.
213
- // Let's use the points projection to maintain the "needsRotation" logic compatibility
214
- const pMid = projectToScreen(markerW / 2, markerH / 2, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
195
+ // 3. Project 4 corners to determine a full 3D perspective (homography)
215
196
  const pUL = projectToScreen(0, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
216
197
  const pUR = projectToScreen(markerW, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
217
198
  const pLL = projectToScreen(0, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
218
- // Using these points we can calculate the 3D rotation and perspective
219
- const dx = pUR.sx - pUL.sx;
220
- const dy = pUR.sy - pUL.sy;
221
- const dz = pUR.sx - pLL.sx; // Not really Z but used for slant
222
- const angle = Math.atan2(dy, dx);
223
- const scaleX = Math.sqrt(dx * dx + dy * dy) / markerW;
224
- const scaleY = Math.sqrt((pLL.sx - pUL.sx) ** 2 + (pLL.sy - pUL.sy) ** 2) / markerH;
225
- // For true 3D tilt, we'll use the projection of the axes
226
- const screenX = pMid.sx;
227
- const screenY = pMid.sy;
228
- // Final Transform applying 3D perspective via matrix3d derived from projected points
229
- // NOTE: For full 3D we'd need a homography solver, but for "tilt" we can use the
230
- // original modelViewTransform if we convert it carefully.
231
- const openGLWorldMatrix = this.controller.getWorldMatrix(mVT, targetIndex);
232
- // We need to apply the same scaling and offsets as projectToScreen to the matrix
199
+ const pLR = projectToScreen(markerW, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
200
+ // Helper to solve for 2D Homography (maps 0..1 square to pUL, pUR, pLL, pLR)
201
+ const solveHomography = (w, h, p1, p2, p3, p4) => {
202
+ const x1 = p1.sx, y1 = p1.sy;
203
+ const x2 = p2.sx, y2 = p2.sy;
204
+ const x3 = p3.sx, y3 = p3.sy;
205
+ const x4 = p4.sx, y4 = p4.sy;
206
+ const dx1 = x2 - x4, dx2 = x3 - x4, dx3 = x1 - x2 + x4 - x3;
207
+ const dy1 = y2 - y4, dy2 = y3 - y4, dy3 = y1 - y2 + y4 - y3;
208
+ let a, b, c, d, e, f, g, h_coeff;
209
+ if (dx3 === 0 && dy3 === 0) {
210
+ a = x2 - x1;
211
+ b = x3 - x1;
212
+ c = x1;
213
+ d = y2 - y1;
214
+ e = y3 - y1;
215
+ f = y1;
216
+ g = 0;
217
+ h_coeff = 0;
218
+ }
219
+ else {
220
+ const det = dx1 * dy2 - dx2 * dy1;
221
+ g = (dx3 * dy2 - dx2 * dy3) / det;
222
+ h_coeff = (dx1 * dy3 - dx3 * dy1) / det;
223
+ a = x2 - x1 + g * x2;
224
+ b = x3 - x1 + h_coeff * x3;
225
+ c = x1;
226
+ d = y2 - y1 + g * y2;
227
+ e = y3 - y1 + h_coeff * y3;
228
+ f = y1;
229
+ }
230
+ // This maps unit square (0..1) to the quadrilateral.
231
+ // We need to scale it by 1/w and 1/h to map (0..w, 0..h)
232
+ return [
233
+ a / w, d / w, 0, g / w,
234
+ b / h, e / h, 0, h_coeff / h,
235
+ 0, 0, 1, 0,
236
+ c, f, 0, 1
237
+ ];
238
+ };
239
+ const matrix = solveHomography(markerW, markerH, pUL, pUR, pLL, pLR);
240
+ // Apply styles
233
241
  this.overlay.style.maxWidth = 'none';
234
242
  this.overlay.style.width = `${markerW}px`;
235
243
  this.overlay.style.height = `${markerH}px`;
236
244
  this.overlay.style.position = 'absolute';
237
- this.overlay.style.transformOrigin = '0 0'; // Top-left based for simpler matrix mapping
245
+ this.overlay.style.transformOrigin = '0 0';
238
246
  this.overlay.style.left = '0';
239
247
  this.overlay.style.top = '0';
240
248
  this.overlay.style.display = 'block';
241
- // Approximate 3D tilt using the projected corners to calculate a skew/scale/rotate combo
242
- // This is more robust than a raw matrix3d if the projection isn't a perfect pinhole
249
+ // Apply 3D transform with matrix3d
250
+ // We also apply the user's custom scaleMultiplier AFTER the perspective transform
251
+ // but since we want to scale around the marker center, we apply it as a prefix/suffix
252
+ // Scale around top-left (0,0) is easy. Scale around center requires offset.
243
253
  this.overlay.style.transform = `
244
- translate(${pUL.sx}px, ${pUL.sy}px)
245
- matrix(${(pUR.sx - pUL.sx) / markerW}, ${(pUR.sy - pUL.sy) / markerW},
246
- ${(pLL.sx - pUL.sx) / markerH}, ${(pLL.sy - pUL.sy) / markerH},
247
- 0, 0)
254
+ matrix3d(${matrix.join(',')})
255
+ translate(${markerW / 2}px, ${markerH / 2}px)
248
256
  scale(${this.scaleMultiplier})
257
+ translate(${-markerW / 2}px, ${-markerH / 2}px)
249
258
  `;
250
259
  }
251
260
  // Unified projection logic moved to ./utils/projection.js
@@ -7,15 +7,7 @@ export class Tracker {
7
7
  inputHeight: any;
8
8
  debugMode: boolean;
9
9
  trackingKeyframeList: any[];
10
- prebuiltData: {
11
- px: Float32Array<any>;
12
- py: Float32Array<any>;
13
- data: Uint8Array<any>;
14
- width: any;
15
- height: any;
16
- scale: any;
17
- projectedImage: Float32Array<ArrayBuffer>;
18
- }[];
10
+ prebuiltData: any[];
19
11
  templateBuffer: Float32Array<ArrayBuffer>;
20
12
  dummyRun(inputData: any): void;
21
13
  track(inputData: any, lastModelViewTransform: any, targetIndex: any): {
@@ -13,15 +13,12 @@ class Tracker {
13
13
  this.inputWidth = inputWidth;
14
14
  this.inputHeight = inputHeight;
15
15
  this.debugMode = debugMode;
16
- this.trackingKeyframeList = [];
16
+ this.trackingKeyframeList = []; // All octaves for all targets: [targetIndex][octaveIndex]
17
+ this.prebuiltData = []; // [targetIndex][octaveIndex]
17
18
  for (let i = 0; i < trackingDataList.length; i++) {
18
- this.trackingKeyframeList.push(trackingDataList[i][TRACKING_KEYFRAME]);
19
- }
20
- // Prebuild TypedArrays for features and pixels
21
- this.prebuiltData = [];
22
- for (let i = 0; i < this.trackingKeyframeList.length; i++) {
23
- const keyframe = this.trackingKeyframeList[i];
24
- this.prebuiltData[i] = {
19
+ const targetOctaves = trackingDataList[i];
20
+ this.trackingKeyframeList[i] = targetOctaves;
21
+ this.prebuiltData[i] = targetOctaves.map(keyframe => ({
25
22
  px: new Float32Array(keyframe.px),
26
23
  py: new Float32Array(keyframe.py),
27
24
  data: new Uint8Array(keyframe.d),
@@ -30,7 +27,7 @@ class Tracker {
30
27
  scale: keyframe.s,
31
28
  // Recyclable projected image buffer
32
29
  projectedImage: new Float32Array(keyframe.w * keyframe.h)
33
- };
30
+ }));
34
31
  }
35
32
  // Pre-allocate template data buffer to avoid garbage collection
36
33
  const templateOneSize = AR2_DEFAULT_TS;
@@ -49,14 +46,31 @@ class Tracker {
49
46
  }
50
47
  track(inputData, lastModelViewTransform, targetIndex) {
51
48
  let debugExtra = {};
49
+ // Select the best octave based on current estimated distance/scale
50
+ // We want the octave where the marker size is closest to its projected size on screen
52
51
  const modelViewProjectionTransform = buildModelViewProjectionTransform(this.projectionTransform, lastModelViewTransform);
53
- const prebuilt = this.prebuiltData[targetIndex];
52
+ // Estimate current marker width on screen
53
+ const [mW, mH] = this.markerDimensions[targetIndex];
54
+ const p0 = computeScreenCoordiate(modelViewProjectionTransform, 0, 0);
55
+ const p1 = computeScreenCoordiate(modelViewProjectionTransform, mW, 0);
56
+ const screenW = Math.sqrt((p1.x - p0.x) ** 2 + (p1.y - p0.y) ** 2);
57
+ // Select octave whose image width is closest to screenW
58
+ let octaveIndex = 0;
59
+ let minDiff = Infinity;
60
+ for (let i = 0; i < this.prebuiltData[targetIndex].length; i++) {
61
+ const diff = Math.abs(this.prebuiltData[targetIndex][i].width - screenW);
62
+ if (diff < minDiff) {
63
+ minDiff = diff;
64
+ octaveIndex = i;
65
+ }
66
+ }
67
+ const prebuilt = this.prebuiltData[targetIndex][octaveIndex];
54
68
  // 1. Compute Projection (Warping)
55
69
  this._computeProjection(modelViewProjectionTransform, inputData, prebuilt);
56
70
  const projectedImage = prebuilt.projectedImage;
57
71
  // 2. Compute Matching (NCC)
58
72
  const { matchingPoints, sim } = this._computeMatching(prebuilt, projectedImage);
59
- const trackingFrame = this.trackingKeyframeList[targetIndex];
73
+ const trackingFrame = this.trackingKeyframeList[targetIndex][octaveIndex];
60
74
  const worldCoords = [];
61
75
  const screenCoords = [];
62
76
  const goodTrack = [];
@@ -75,6 +89,7 @@ class Tracker {
75
89
  }
76
90
  if (this.debugMode) {
77
91
  debugExtra = {
92
+ octaveIndex,
78
93
  projectedImage: Array.from(projectedImage),
79
94
  matchingPoints,
80
95
  goodTrack,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srsergio/taptapp-ar",
3
- "version": "1.0.35",
3
+ "version": "1.0.36",
4
4
  "description": "AR Compiler for Node.js and Browser",
5
5
  "repository": {
6
6
  "type": "git",
@@ -260,9 +260,9 @@ export class OfflineCompiler {
260
260
  const dataList = this.data.map((item) => {
261
261
  const matchingData = item.matchingData.map((kf) => this._packKeyframe(kf));
262
262
 
263
- const trackingData = [item.trackingData[0]].map((td) => {
263
+ const trackingData = item.trackingData.map((td) => {
264
264
  const count = td.points.length;
265
- // Step 1: Packed Coords - Normalize width/height to 16-bit
265
+ // Packed Coords - Float32 for now as in current import logic
266
266
  const px = new Float32Array(count);
267
267
  const py = new Float32Array(count);
268
268
  for (let i = 0; i < count; i++) {
@@ -222,71 +222,70 @@ class SimpleAR {
222
222
  // 3. Get intrinsic projection from controller
223
223
  const proj = this.controller.projectionTransform;
224
224
 
225
- // 4. Position calculation via matrix3d (Support for 3D tilt/Z-rotation)
226
- // We convert the OpenGL World Matrix to a CSS matrix3d.
227
- // The OpenGL matrix is column-major. CSS matrix3d is also column-major.
228
- const m = this.controller.getWorldMatrix(mVT, targetIndex);
229
-
230
- // Map OpenGL coords to Screen Pixels using the projection logic
231
- const vW = needsRotation ? videoH : videoW;
232
- const vH = needsRotation ? videoW : videoH;
233
- const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
234
- const displayW = vW * perspectiveScale;
235
- const displayH = vH * perspectiveScale;
236
- const offsetX = (containerRect.width - displayW) / 2;
237
- const offsetY = (containerRect.height - displayH) / 2;
238
-
239
- // Adjust for centered marker and scaleMultiplier
240
- const s = finalScale; // We still need the base scale factor for the pixel-to-marker mapping
241
- // However, a cleaner way is to use the world matrix directly and map it.
242
-
243
- // Actually, the simpler way to do 3D in CSS while keeping my projection logic is:
244
- // Project the 4 corners and find the homography, OR
245
- // Use the OpenGL matrix directly with a perspective mapping.
246
-
247
- // Let's use the points projection to maintain the "needsRotation" logic compatibility
248
- const pMid = projectToScreen(markerW / 2, markerH / 2, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
225
+ // 3. Project 4 corners to determine a full 3D perspective (homography)
249
226
  const pUL = projectToScreen(0, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
250
227
  const pUR = projectToScreen(markerW, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
251
228
  const pLL = projectToScreen(0, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
229
+ const pLR = projectToScreen(markerW, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
230
+
231
+ // Helper to solve for 2D Homography (maps 0..1 square to pUL, pUR, pLL, pLR)
232
+ const solveHomography = (w, h, p1, p2, p3, p4) => {
233
+ const x1 = p1.sx, y1 = p1.sy;
234
+ const x2 = p2.sx, y2 = p2.sy;
235
+ const x3 = p3.sx, y3 = p3.sy;
236
+ const x4 = p4.sx, y4 = p4.sy;
237
+
238
+ const dx1 = x2 - x4, dx2 = x3 - x4, dx3 = x1 - x2 + x4 - x3;
239
+ const dy1 = y2 - y4, dy2 = y3 - y4, dy3 = y1 - y2 + y4 - y3;
240
+
241
+ let a, b, c, d, e, f, g, h_coeff;
242
+
243
+ if (dx3 === 0 && dy3 === 0) {
244
+ a = x2 - x1; b = x3 - x1; c = x1;
245
+ d = y2 - y1; e = y3 - y1; f = y1;
246
+ g = 0; h_coeff = 0;
247
+ } else {
248
+ const det = dx1 * dy2 - dx2 * dy1;
249
+ g = (dx3 * dy2 - dx2 * dy3) / det;
250
+ h_coeff = (dx1 * dy3 - dx3 * dy1) / det;
251
+ a = x2 - x1 + g * x2;
252
+ b = x3 - x1 + h_coeff * x3;
253
+ c = x1;
254
+ d = y2 - y1 + g * y2;
255
+ e = y3 - y1 + h_coeff * y3;
256
+ f = y1;
257
+ }
258
+ // This maps unit square (0..1) to the quadrilateral.
259
+ // We need to scale it by 1/w and 1/h to map (0..w, 0..h)
260
+ return [
261
+ a / w, d / w, 0, g / w,
262
+ b / h, e / h, 0, h_coeff / h,
263
+ 0, 0, 1, 0,
264
+ c, f, 0, 1
265
+ ];
266
+ };
252
267
 
253
- // Using these points we can calculate the 3D rotation and perspective
254
- const dx = pUR.sx - pUL.sx;
255
- const dy = pUR.sy - pUL.sy;
256
- const dz = pUR.sx - pLL.sx; // Not really Z but used for slant
257
-
258
- const angle = Math.atan2(dy, dx);
259
- const scaleX = Math.sqrt(dx * dx + dy * dy) / markerW;
260
- const scaleY = Math.sqrt((pLL.sx - pUL.sx) ** 2 + (pLL.sy - pUL.sy) ** 2) / markerH;
261
-
262
- // For true 3D tilt, we'll use the projection of the axes
263
- const screenX = pMid.sx;
264
- const screenY = pMid.sy;
265
-
266
- // Final Transform applying 3D perspective via matrix3d derived from projected points
267
- // NOTE: For full 3D we'd need a homography solver, but for "tilt" we can use the
268
- // original modelViewTransform if we convert it carefully.
269
-
270
- const openGLWorldMatrix = this.controller.getWorldMatrix(mVT, targetIndex);
271
- // We need to apply the same scaling and offsets as projectToScreen to the matrix
268
+ const matrix = solveHomography(markerW, markerH, pUL, pUR, pLL, pLR);
272
269
 
270
+ // Apply styles
273
271
  this.overlay.style.maxWidth = 'none';
274
272
  this.overlay.style.width = `${markerW}px`;
275
273
  this.overlay.style.height = `${markerH}px`;
276
274
  this.overlay.style.position = 'absolute';
277
- this.overlay.style.transformOrigin = '0 0'; // Top-left based for simpler matrix mapping
275
+ this.overlay.style.transformOrigin = '0 0';
278
276
  this.overlay.style.left = '0';
279
277
  this.overlay.style.top = '0';
280
278
  this.overlay.style.display = 'block';
281
279
 
282
- // Approximate 3D tilt using the projected corners to calculate a skew/scale/rotate combo
283
- // This is more robust than a raw matrix3d if the projection isn't a perfect pinhole
280
+ // Apply 3D transform with matrix3d
281
+ // We also apply the user's custom scaleMultiplier AFTER the perspective transform
282
+ // but since we want to scale around the marker center, we apply it as a prefix/suffix
283
+ // Scale around top-left (0,0) is easy. Scale around center requires offset.
284
284
  this.overlay.style.transform = `
285
- translate(${pUL.sx}px, ${pUL.sy}px)
286
- matrix(${(pUR.sx - pUL.sx) / markerW}, ${(pUR.sy - pUL.sy) / markerW},
287
- ${(pLL.sx - pUL.sx) / markerH}, ${(pLL.sy - pUL.sy) / markerH},
288
- 0, 0)
285
+ matrix3d(${matrix.join(',')})
286
+ translate(${markerW / 2}px, ${markerH / 2}px)
289
287
  scale(${this.scaleMultiplier})
288
+ translate(${-markerW / 2}px, ${-markerH / 2}px)
290
289
  `;
291
290
  }
292
291
 
@@ -24,16 +24,13 @@ class Tracker {
24
24
  this.inputHeight = inputHeight;
25
25
  this.debugMode = debugMode;
26
26
 
27
- this.trackingKeyframeList = [];
28
- for (let i = 0; i < trackingDataList.length; i++) {
29
- this.trackingKeyframeList.push(trackingDataList[i][TRACKING_KEYFRAME]);
30
- }
27
+ this.trackingKeyframeList = []; // All octaves for all targets: [targetIndex][octaveIndex]
28
+ this.prebuiltData = []; // [targetIndex][octaveIndex]
31
29
 
32
- // Prebuild TypedArrays for features and pixels
33
- this.prebuiltData = [];
34
- for (let i = 0; i < this.trackingKeyframeList.length; i++) {
35
- const keyframe = this.trackingKeyframeList[i];
36
- this.prebuiltData[i] = {
30
+ for (let i = 0; i < trackingDataList.length; i++) {
31
+ const targetOctaves = trackingDataList[i];
32
+ this.trackingKeyframeList[i] = targetOctaves;
33
+ this.prebuiltData[i] = targetOctaves.map(keyframe => ({
37
34
  px: new Float32Array(keyframe.px),
38
35
  py: new Float32Array(keyframe.py),
39
36
  data: new Uint8Array(keyframe.d),
@@ -42,7 +39,7 @@ class Tracker {
42
39
  scale: keyframe.s,
43
40
  // Recyclable projected image buffer
44
41
  projectedImage: new Float32Array(keyframe.w * keyframe.h)
45
- };
42
+ }));
46
43
  }
47
44
 
48
45
  // Pre-allocate template data buffer to avoid garbage collection
@@ -65,12 +62,31 @@ class Tracker {
65
62
  track(inputData, lastModelViewTransform, targetIndex) {
66
63
  let debugExtra = {};
67
64
 
65
+ // Select the best octave based on current estimated distance/scale
66
+ // We want the octave where the marker size is closest to its projected size on screen
68
67
  const modelViewProjectionTransform = buildModelViewProjectionTransform(
69
68
  this.projectionTransform,
70
69
  lastModelViewTransform,
71
70
  );
72
71
 
73
- const prebuilt = this.prebuiltData[targetIndex];
72
+ // Estimate current marker width on screen
73
+ const [mW, mH] = this.markerDimensions[targetIndex];
74
+ const p0 = computeScreenCoordiate(modelViewProjectionTransform, 0, 0);
75
+ const p1 = computeScreenCoordiate(modelViewProjectionTransform, mW, 0);
76
+ const screenW = Math.sqrt((p1.x - p0.x) ** 2 + (p1.y - p0.y) ** 2);
77
+
78
+ // Select octave whose image width is closest to screenW
79
+ let octaveIndex = 0;
80
+ let minDiff = Infinity;
81
+ for (let i = 0; i < this.prebuiltData[targetIndex].length; i++) {
82
+ const diff = Math.abs(this.prebuiltData[targetIndex][i].width - screenW);
83
+ if (diff < minDiff) {
84
+ minDiff = diff;
85
+ octaveIndex = i;
86
+ }
87
+ }
88
+
89
+ const prebuilt = this.prebuiltData[targetIndex][octaveIndex];
74
90
 
75
91
  // 1. Compute Projection (Warping)
76
92
  this._computeProjection(
@@ -87,7 +103,7 @@ class Tracker {
87
103
  projectedImage
88
104
  );
89
105
 
90
- const trackingFrame = this.trackingKeyframeList[targetIndex];
106
+ const trackingFrame = this.trackingKeyframeList[targetIndex][octaveIndex];
91
107
  const worldCoords = [];
92
108
  const screenCoords = [];
93
109
  const goodTrack = [];
@@ -113,6 +129,7 @@ class Tracker {
113
129
 
114
130
  if (this.debugMode) {
115
131
  debugExtra = {
132
+ octaveIndex,
116
133
  projectedImage: Array.from(projectedImage),
117
134
  matchingPoints,
118
135
  goodTrack,