@srsergio/taptapp-ar 1.0.16 → 1.0.18

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.
@@ -283,7 +283,7 @@ class Controller {
283
283
  clone = this.getRotatedZ90Matrix(clone);
284
284
  }
285
285
  this.onUpdate &&
286
- this.onUpdate({ type: "updateMatrix", targetIndex: i, worldMatrix: clone });
286
+ this.onUpdate({ type: "updateMatrix", targetIndex: i, worldMatrix: clone, modelViewTransform: trackingState.currentModelViewTransform });
287
287
  }
288
288
  }
289
289
  this.onUpdate && this.onUpdate({ type: "processDone" });
@@ -47,6 +47,7 @@ export class SimpleAR {
47
47
  * Initialize and start AR tracking
48
48
  */
49
49
  start(): Promise<this>;
50
+ markerDimensions: any;
50
51
  /**
51
52
  * Stop AR tracking and release resources
52
53
  */
@@ -55,6 +56,6 @@ export class SimpleAR {
55
56
  _startCamera(): Promise<void>;
56
57
  _initController(): void;
57
58
  _handleUpdate(data: any): void;
58
- _positionOverlay(worldMatrix: any): void;
59
+ _positionOverlay(mVT: any, targetIndex: any): void;
59
60
  }
60
61
  import { Controller } from "./controller.js";
@@ -41,7 +41,8 @@ class SimpleAR {
41
41
  this._initController();
42
42
  // 4. Load targets (supports single URL or array of URLs)
43
43
  const targets = Array.isArray(this.targetSrc) ? this.targetSrc : [this.targetSrc];
44
- await this.controller.addImageTargets(targets);
44
+ const result = await this.controller.addImageTargets(targets);
45
+ this.markerDimensions = result.dimensions; // [ [w1, h1], [w2, h2], ... ]
45
46
  this.controller.processVideo(this.video);
46
47
  return this;
47
48
  }
@@ -59,6 +60,7 @@ class SimpleAR {
59
60
  this.video = null;
60
61
  }
61
62
  this.isTracking = false;
63
+ this.markerDimensions = [];
62
64
  }
63
65
  _createVideo() {
64
66
  this.video = document.createElement('video');
@@ -101,7 +103,7 @@ class SimpleAR {
101
103
  _handleUpdate(data) {
102
104
  if (data.type !== 'updateMatrix')
103
105
  return;
104
- const { targetIndex, worldMatrix } = data;
106
+ const { targetIndex, worldMatrix, modelViewTransform } = data;
105
107
  if (worldMatrix) {
106
108
  // Target found
107
109
  if (!this.isTracking) {
@@ -110,7 +112,7 @@ class SimpleAR {
110
112
  this.onFound && this.onFound({ targetIndex });
111
113
  }
112
114
  this.lastMatrix = worldMatrix;
113
- this._positionOverlay(worldMatrix);
115
+ this._positionOverlay(modelViewTransform, targetIndex);
114
116
  this.onUpdateCallback && this.onUpdateCallback({ targetIndex, worldMatrix });
115
117
  }
116
118
  else {
@@ -122,9 +124,10 @@ class SimpleAR {
122
124
  }
123
125
  }
124
126
  }
125
- _positionOverlay(worldMatrix) {
126
- if (!this.overlay)
127
+ _positionOverlay(mVT, targetIndex) {
128
+ if (!this.overlay || !this.markerDimensions[targetIndex])
127
129
  return;
130
+ const [markerW, markerH] = this.markerDimensions[targetIndex];
128
131
  const containerRect = this.container.getBoundingClientRect();
129
132
  const videoW = this.video.videoWidth;
130
133
  const videoH = this.video.videoHeight;
@@ -133,14 +136,12 @@ class SimpleAR {
133
136
  const videoAspect = videoW / videoH;
134
137
  let displayW, displayH, offsetX, offsetY;
135
138
  if (containerAspect > videoAspect) {
136
- // Container is wider - video fills width, crops height
137
139
  displayW = containerRect.width;
138
140
  displayH = containerRect.width / videoAspect;
139
141
  offsetX = 0;
140
142
  offsetY = (containerRect.height - displayH) / 2;
141
143
  }
142
144
  else {
143
- // Container is taller - video fills height, crops width
144
145
  displayH = containerRect.height;
145
146
  displayW = containerRect.height * videoAspect;
146
147
  offsetX = (containerRect.width - displayW) / 2;
@@ -148,23 +149,28 @@ class SimpleAR {
148
149
  }
149
150
  const scaleX = displayW / videoW;
150
151
  const scaleY = displayH / videoH;
151
- // Extract position and rotation from world matrix
152
- // Matrix is column-major: [m0,m1,m2,m3, m4,m5,m6,m7, m8,m9,m10,m11, m12,m13,m14,m15]
153
- const tx = worldMatrix[12];
154
- const ty = worldMatrix[13];
155
- const tz = worldMatrix[14];
156
- // focal length (roughly 45 degrees FOV)
152
+ // Project the center of the marker (markerW/2, markerH/2, 0) into camera space
153
+ // Marker coordinates are pixels from top-left.
154
+ const tx = mVT[0][0] * (markerW / 2) + mVT[0][1] * (markerH / 2) + mVT[0][3];
155
+ const ty = mVT[1][0] * (markerW / 2) + mVT[1][1] * (markerH / 2) + mVT[1][3];
156
+ const tz = mVT[2][0] * (markerW / 2) + mVT[2][1] * (markerH / 2) + mVT[2][3];
157
+ // focal length (roughly 45 degrees FOV match Controller.js)
157
158
  const f = videoH / 2 / Math.tan((45.0 * Math.PI / 180) / 2);
158
- // Standard perspective projection to screen space
159
- // tx, ty, tz are in marker units relative to camera
159
+ // Perspective projection to screen space
160
160
  const screenX = offsetX + (videoW / 2 + (tx * f / -tz)) * scaleX;
161
161
  const screenY = offsetY + (videoH / 2 - (ty * f / -tz)) * scaleY;
162
- // Calculate rotation and scale from the matrix
163
- const rotation = Math.atan2(worldMatrix[1], worldMatrix[0]);
164
- const matrixScale = Math.sqrt(worldMatrix[0] ** 2 + worldMatrix[1] ** 2);
162
+ // Use the first row of mVT to determine rotation and base scale component
163
+ // Since marker coordinates are in pixels, mVT[0][0] and mVT[0][1] are unitless scale factors
164
+ const rotation = Math.atan2(mVT[1][0], mVT[0][0]);
165
+ const matrixScale = Math.sqrt(mVT[0][0] ** 2 + mVT[1][0] ** 2);
165
166
  // Perspective scale: how much larger/smaller the object is based on distance (tz)
167
+ // Correct scale should make a marker-width sized element cover the marker.
166
168
  const perspectiveScale = (f / -tz) * scaleX;
167
- const finalScale = matrixScale * perspectiveScale;
169
+ // The overlay element has its own width in CSS pixels (e.g. 200px)
170
+ const overlayCSSWidth = parseFloat(this.overlay.style.width) || 200;
171
+ // Final scale = (Target Width in Pixels on screen) / (Overlay CSS Width)
172
+ // matrixScale is usually ~1.0 if tracking is rigid
173
+ const finalScale = (matrixScale * markerW * perspectiveScale) / overlayCSSWidth;
168
174
  // Apply transform
169
175
  this.overlay.style.position = 'absolute';
170
176
  this.overlay.style.transformOrigin = 'center center';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srsergio/taptapp-ar",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "AR Compiler for Node.js and Browser",
5
5
  "repository": {
6
6
  "type": "git",
@@ -348,7 +348,7 @@ class Controller {
348
348
  }
349
349
 
350
350
  this.onUpdate &&
351
- this.onUpdate({ type: "updateMatrix", targetIndex: i, worldMatrix: clone });
351
+ this.onUpdate({ type: "updateMatrix", targetIndex: i, worldMatrix: clone, modelViewTransform: trackingState.currentModelViewTransform });
352
352
  }
353
353
  }
354
354
 
@@ -55,7 +55,9 @@ class SimpleAR {
55
55
 
56
56
  // 4. Load targets (supports single URL or array of URLs)
57
57
  const targets = Array.isArray(this.targetSrc) ? this.targetSrc : [this.targetSrc];
58
- await this.controller.addImageTargets(targets);
58
+ const result = await this.controller.addImageTargets(targets);
59
+ this.markerDimensions = result.dimensions; // [ [w1, h1], [w2, h2], ... ]
60
+
59
61
  this.controller.processVideo(this.video);
60
62
 
61
63
  return this;
@@ -75,6 +77,7 @@ class SimpleAR {
75
77
  this.video = null;
76
78
  }
77
79
  this.isTracking = false;
80
+ this.markerDimensions = [];
78
81
  }
79
82
 
80
83
  _createVideo() {
@@ -121,7 +124,7 @@ class SimpleAR {
121
124
  _handleUpdate(data) {
122
125
  if (data.type !== 'updateMatrix') return;
123
126
 
124
- const { targetIndex, worldMatrix } = data;
127
+ const { targetIndex, worldMatrix, modelViewTransform } = data;
125
128
 
126
129
  if (worldMatrix) {
127
130
  // Target found
@@ -132,7 +135,7 @@ class SimpleAR {
132
135
  }
133
136
 
134
137
  this.lastMatrix = worldMatrix;
135
- this._positionOverlay(worldMatrix);
138
+ this._positionOverlay(modelViewTransform, targetIndex);
136
139
  this.onUpdateCallback && this.onUpdateCallback({ targetIndex, worldMatrix });
137
140
 
138
141
  } else {
@@ -145,9 +148,10 @@ class SimpleAR {
145
148
  }
146
149
  }
147
150
 
148
- _positionOverlay(worldMatrix) {
149
- if (!this.overlay) return;
151
+ _positionOverlay(mVT, targetIndex) {
152
+ if (!this.overlay || !this.markerDimensions[targetIndex]) return;
150
153
 
154
+ const [markerW, markerH] = this.markerDimensions[targetIndex];
151
155
  const containerRect = this.container.getBoundingClientRect();
152
156
  const videoW = this.video.videoWidth;
153
157
  const videoH = this.video.videoHeight;
@@ -159,13 +163,11 @@ class SimpleAR {
159
163
  let displayW, displayH, offsetX, offsetY;
160
164
 
161
165
  if (containerAspect > videoAspect) {
162
- // Container is wider - video fills width, crops height
163
166
  displayW = containerRect.width;
164
167
  displayH = containerRect.width / videoAspect;
165
168
  offsetX = 0;
166
169
  offsetY = (containerRect.height - displayH) / 2;
167
170
  } else {
168
- // Container is taller - video fills height, crops width
169
171
  displayH = containerRect.height;
170
172
  displayW = containerRect.height * videoAspect;
171
173
  offsetX = (containerRect.width - displayW) / 2;
@@ -175,27 +177,34 @@ class SimpleAR {
175
177
  const scaleX = displayW / videoW;
176
178
  const scaleY = displayH / videoH;
177
179
 
178
- // Extract position and rotation from world matrix
179
- // Matrix is column-major: [m0,m1,m2,m3, m4,m5,m6,m7, m8,m9,m10,m11, m12,m13,m14,m15]
180
- const tx = worldMatrix[12];
181
- const ty = worldMatrix[13];
182
- const tz = worldMatrix[14];
180
+ // Project the center of the marker (markerW/2, markerH/2, 0) into camera space
181
+ // Marker coordinates are pixels from top-left.
182
+ const tx = mVT[0][0] * (markerW / 2) + mVT[0][1] * (markerH / 2) + mVT[0][3];
183
+ const ty = mVT[1][0] * (markerW / 2) + mVT[1][1] * (markerH / 2) + mVT[1][3];
184
+ const tz = mVT[2][0] * (markerW / 2) + mVT[2][1] * (markerH / 2) + mVT[2][3];
183
185
 
184
- // focal length (roughly 45 degrees FOV)
186
+ // focal length (roughly 45 degrees FOV match Controller.js)
185
187
  const f = videoH / 2 / Math.tan((45.0 * Math.PI / 180) / 2);
186
188
 
187
- // Standard perspective projection to screen space
188
- // tx, ty, tz are in marker units relative to camera
189
+ // Perspective projection to screen space
189
190
  const screenX = offsetX + (videoW / 2 + (tx * f / -tz)) * scaleX;
190
191
  const screenY = offsetY + (videoH / 2 - (ty * f / -tz)) * scaleY;
191
192
 
192
- // Calculate rotation and scale from the matrix
193
- const rotation = Math.atan2(worldMatrix[1], worldMatrix[0]);
194
- const matrixScale = Math.sqrt(worldMatrix[0] ** 2 + worldMatrix[1] ** 2);
193
+ // Use the first row of mVT to determine rotation and base scale component
194
+ // Since marker coordinates are in pixels, mVT[0][0] and mVT[0][1] are unitless scale factors
195
+ const rotation = Math.atan2(mVT[1][0], mVT[0][0]);
196
+ const matrixScale = Math.sqrt(mVT[0][0] ** 2 + mVT[1][0] ** 2);
195
197
 
196
198
  // Perspective scale: how much larger/smaller the object is based on distance (tz)
199
+ // Correct scale should make a marker-width sized element cover the marker.
197
200
  const perspectiveScale = (f / -tz) * scaleX;
198
- const finalScale = matrixScale * perspectiveScale;
201
+
202
+ // The overlay element has its own width in CSS pixels (e.g. 200px)
203
+ const overlayCSSWidth = parseFloat(this.overlay.style.width) || 200;
204
+
205
+ // Final scale = (Target Width in Pixels on screen) / (Overlay CSS Width)
206
+ // matrixScale is usually ~1.0 if tracking is rigid
207
+ const finalScale = (matrixScale * markerW * perspectiveScale) / overlayCSSWidth;
199
208
 
200
209
  // Apply transform
201
210
  this.overlay.style.position = 'absolute';