@srsergio/taptapp-ar 1.0.27 → 1.0.28

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.
@@ -76,5 +76,12 @@ export class SimpleAR {
76
76
  _initController(): void;
77
77
  _handleUpdate(data: any): void;
78
78
  _positionOverlay(mVT: any, targetIndex: any): void;
79
+ /**
80
+ * Projects a 3D marker-space point all the way to 2D screen CSS pixels
81
+ */
82
+ _projectToScreen(x: any, y: any, z: any, mVT: any, proj: any, videoW: any, videoH: any, containerRect: any, needsRotation: any): {
83
+ sx: number;
84
+ sy: number;
85
+ };
79
86
  }
80
87
  import { Controller } from "./controller.js";
@@ -145,55 +145,38 @@ class SimpleAR {
145
145
  const containerRect = this.container.getBoundingClientRect();
146
146
  const videoW = this.video.videoWidth;
147
147
  const videoH = this.video.videoHeight;
148
+ // 1. Determine orientation needs
148
149
  const isPortrait = containerRect.height > containerRect.width;
149
150
  const isVideoLandscape = videoW > videoH;
150
151
  const needsRotation = isPortrait && isVideoLandscape;
151
- // Current display dimensions of the video (accounting for rotation)
152
- const vW = needsRotation ? videoH : videoW;
153
- const vH = needsRotation ? videoW : videoH;
154
- // Robust "object-fit: cover" scale calculation
155
- const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
156
- const displayW = vW * perspectiveScale;
157
- const displayH = vH * perspectiveScale;
158
- const offsetX = (containerRect.width - displayW) / 2;
159
- const offsetY = (containerRect.height - displayH) / 2;
160
- // The tracker uses focal length based on height dimension in tracker space
161
- const f = (videoH / 2) / Math.tan((45.0 * Math.PI / 180) / 2);
162
- // Center of the marker in camera space (marker coordinates origin at top-left)
163
- const tx = mVT[0][0] * (markerW / 2) + mVT[0][1] * (markerH / 2) + mVT[0][3];
164
- const ty = mVT[1][0] * (markerW / 2) + mVT[1][1] * (markerH / 2) + mVT[1][3];
165
- const tz = mVT[2][0] * (markerW / 2) + mVT[2][1] * (markerH / 2) + mVT[2][3];
166
- let screenX, screenY, rotation;
167
- if (needsRotation) {
168
- const bufferOffsetX = (tx * f / tz);
169
- const bufferOffsetY = (ty * f / tz);
170
- screenX = offsetX + (displayW / 2) - (bufferOffsetY * perspectiveScale);
171
- screenY = offsetY + (displayH / 2) + (bufferOffsetX * perspectiveScale);
172
- rotation = Math.atan2(mVT[1][0], mVT[0][0]) - Math.PI / 2;
173
- }
174
- else {
175
- const bufferOffsetX = (tx * f / tz);
176
- const bufferOffsetY = (ty * f / tz);
177
- screenX = offsetX + (displayW / 2) + (bufferOffsetX * perspectiveScale);
178
- screenY = offsetY + (displayH / 2) + (bufferOffsetY * perspectiveScale);
179
- rotation = Math.atan2(mVT[1][0], mVT[0][0]);
180
- }
181
- const matrixScale = Math.sqrt(mVT[0][0] ** 2 + mVT[1][0] ** 2);
182
- const finalScale = (f / tz) * perspectiveScale * matrixScale * this.scaleMultiplier;
152
+ // 2. Get intrinsic projection from controller
153
+ const proj = this.controller.projectionTransform;
154
+ // 3. Project 3 points to determine position, scale, and rotation
155
+ // Points in Marker Space: Center, Right-Edge, and Down-Edge
156
+ const pMid = this._projectToScreen(markerW / 2, markerH / 2, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
157
+ const pRight = this._projectToScreen(markerW / 2 + 100, markerH / 2, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
158
+ // 4. Calculate Screen Position
159
+ const screenX = pMid.sx;
160
+ const screenY = pMid.sy;
161
+ // 5. Calculate Rotation and Scale from the projected X-axis vector
162
+ const dx = pRight.sx - pMid.sx;
163
+ const dy = pRight.sy - pMid.sy;
164
+ const rotation = Math.atan2(dy, dx);
165
+ const pixelDistance100 = Math.sqrt(dx * dx + dy * dy);
166
+ // Since we projected 100 units, the scale for the whole markerW is:
167
+ const finalScale = (pixelDistance100 / 100) * this.scaleMultiplier;
183
168
  // DEBUG LOGS
184
169
  if (window.AR_DEBUG) {
185
- console.log('--- AR POSITION DEBUG ---');
186
- console.log('Container:', containerRect.width, 'x', containerRect.height);
170
+ console.log('--- AR POSITION DEBUG (Point Projection) ---');
171
+ console.log('Container:', containerRect.width.toFixed(0), 'x', containerRect.height.toFixed(0));
187
172
  console.log('Video:', videoW, 'x', videoH, 'needsRotation:', needsRotation);
188
- console.log('PerspectiveScale (Cover):', perspectiveScale);
189
- console.log('Display:', displayW, 'x', displayH, 'Offsets:', offsetX, offsetY);
190
- console.log('Projection (tx, ty, tz):', tx.toFixed(2), ty.toFixed(2), tz.toFixed(2));
191
- console.log('Screen Coords:', screenX.toFixed(2), screenY.toFixed(2));
192
- console.log('Final Scale:', finalScale.toFixed(4), '(MatrixScale:', matrixScale.toFixed(4), ')');
173
+ console.log('Screen Pos:', screenX.toFixed(1), screenY.toFixed(1));
174
+ console.log('Rotated Angle:', (rotation * 180 / Math.PI).toFixed(1), 'deg');
175
+ console.log('Final Scale:', finalScale.toFixed(4));
193
176
  }
194
177
  // Apply
195
178
  this.overlay.style.width = `${markerW}px`;
196
- this.overlay.style.height = 'auto';
179
+ this.overlay.style.height = 'auto'; // Maintain aspect ratio of the overlay asset
197
180
  this.overlay.style.position = 'absolute';
198
181
  this.overlay.style.transformOrigin = 'center center';
199
182
  this.overlay.style.left = '0';
@@ -201,9 +184,40 @@ class SimpleAR {
201
184
  this.overlay.style.transform = `
202
185
  translate(${screenX}px, ${screenY}px)
203
186
  translate(-50%, -50%)
204
- scale(${finalScale})
205
187
  rotate(${rotation}rad)
188
+ scale(${finalScale})
206
189
  `;
207
190
  }
191
+ /**
192
+ * Projects a 3D marker-space point all the way to 2D screen CSS pixels
193
+ */
194
+ _projectToScreen(x, y, z, mVT, proj, videoW, videoH, containerRect, needsRotation) {
195
+ // Marker -> Camera Space
196
+ const tx = mVT[0][0] * x + mVT[0][1] * y + mVT[0][2] * z + mVT[0][3];
197
+ const ty = mVT[1][0] * x + mVT[1][1] * y + mVT[1][2] * z + mVT[1][3];
198
+ const tz = mVT[2][0] * x + mVT[2][1] * y + mVT[2][2] * z + mVT[2][3];
199
+ // Camera -> Buffer Pixels (e.g. 1280x720)
200
+ const bx = (proj[0][0] * tx / tz) + proj[0][2];
201
+ const by = (proj[1][1] * ty / tz) + proj[1][2];
202
+ // Buffer -> Screen CSS Pixels
203
+ const vW = needsRotation ? videoH : videoW;
204
+ const vH = needsRotation ? videoW : videoH;
205
+ const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
206
+ const displayW = vW * perspectiveScale;
207
+ const displayH = vH * perspectiveScale;
208
+ const offsetX = (containerRect.width - displayW) / 2;
209
+ const offsetY = (containerRect.height - displayH) / 2;
210
+ let sx, sy;
211
+ if (needsRotation) {
212
+ // Mapping: Camera +X (Right) -> Screen +Y (Down), Camera +Y (Down) -> Screen -X (Left)
213
+ sx = offsetX + (displayW / 2) - (by - proj[1][2]) * perspectiveScale;
214
+ sy = offsetY + (displayH / 2) + (bx - proj[0][2]) * perspectiveScale;
215
+ }
216
+ else {
217
+ sx = offsetX + (displayW / 2) + (bx - proj[0][2]) * perspectiveScale;
218
+ sy = offsetY + (displayH / 2) + (by - proj[1][2]) * perspectiveScale;
219
+ }
220
+ return { sx, sy };
221
+ }
208
222
  }
209
223
  export { SimpleAR };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srsergio/taptapp-ar",
3
- "version": "1.0.27",
3
+ "version": "1.0.28",
4
4
  "description": "AR Compiler for Node.js and Browser",
5
5
  "repository": {
6
6
  "type": "git",
@@ -167,70 +167,49 @@ class SimpleAR {
167
167
 
168
168
  const [markerW, markerH] = this.markerDimensions[targetIndex];
169
169
  const containerRect = this.container.getBoundingClientRect();
170
-
171
170
  const videoW = this.video.videoWidth;
172
171
  const videoH = this.video.videoHeight;
173
172
 
173
+ // 1. Determine orientation needs
174
174
  const isPortrait = containerRect.height > containerRect.width;
175
175
  const isVideoLandscape = videoW > videoH;
176
176
  const needsRotation = isPortrait && isVideoLandscape;
177
177
 
178
- // Current display dimensions of the video (accounting for rotation)
179
- const vW = needsRotation ? videoH : videoW;
180
- const vH = needsRotation ? videoW : videoH;
181
-
182
- // Robust "object-fit: cover" scale calculation
183
- const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
184
-
185
- const displayW = vW * perspectiveScale;
186
- const displayH = vH * perspectiveScale;
187
- const offsetX = (containerRect.width - displayW) / 2;
188
- const offsetY = (containerRect.height - displayH) / 2;
189
-
190
- // The tracker uses focal length based on height dimension in tracker space
191
- const f = (videoH / 2) / Math.tan((45.0 * Math.PI / 180) / 2);
178
+ // 2. Get intrinsic projection from controller
179
+ const proj = this.controller.projectionTransform;
192
180
 
193
- // Center of the marker in camera space (marker coordinates origin at top-left)
194
- const tx = mVT[0][0] * (markerW / 2) + mVT[0][1] * (markerH / 2) + mVT[0][3];
195
- const ty = mVT[1][0] * (markerW / 2) + mVT[1][1] * (markerH / 2) + mVT[1][3];
196
- const tz = mVT[2][0] * (markerW / 2) + mVT[2][1] * (markerH / 2) + mVT[2][3];
181
+ // 3. Project 3 points to determine position, scale, and rotation
182
+ // Points in Marker Space: Center, Right-Edge, and Down-Edge
183
+ const pMid = this._projectToScreen(markerW / 2, markerH / 2, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
184
+ const pRight = this._projectToScreen(markerW / 2 + 100, markerH / 2, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
197
185
 
198
- let screenX, screenY, rotation;
186
+ // 4. Calculate Screen Position
187
+ const screenX = pMid.sx;
188
+ const screenY = pMid.sy;
199
189
 
200
- if (needsRotation) {
201
- const bufferOffsetX = (tx * f / tz);
202
- const bufferOffsetY = (ty * f / tz);
190
+ // 5. Calculate Rotation and Scale from the projected X-axis vector
191
+ const dx = pRight.sx - pMid.sx;
192
+ const dy = pRight.sy - pMid.sy;
203
193
 
204
- screenX = offsetX + (displayW / 2) - (bufferOffsetY * perspectiveScale);
205
- screenY = offsetY + (displayH / 2) + (bufferOffsetX * perspectiveScale);
206
- rotation = Math.atan2(mVT[1][0], mVT[0][0]) - Math.PI / 2;
207
- } else {
208
- const bufferOffsetX = (tx * f / tz);
209
- const bufferOffsetY = (ty * f / tz);
194
+ const rotation = Math.atan2(dy, dx);
195
+ const pixelDistance100 = Math.sqrt(dx * dx + dy * dy);
210
196
 
211
- screenX = offsetX + (displayW / 2) + (bufferOffsetX * perspectiveScale);
212
- screenY = offsetY + (displayH / 2) + (bufferOffsetY * perspectiveScale);
213
- rotation = Math.atan2(mVT[1][0], mVT[0][0]);
214
- }
215
-
216
- const matrixScale = Math.sqrt(mVT[0][0] ** 2 + mVT[1][0] ** 2);
217
- const finalScale = (f / tz) * perspectiveScale * matrixScale * this.scaleMultiplier;
197
+ // Since we projected 100 units, the scale for the whole markerW is:
198
+ const finalScale = (pixelDistance100 / 100) * this.scaleMultiplier;
218
199
 
219
200
  // DEBUG LOGS
220
201
  if (window.AR_DEBUG) {
221
- console.log('--- AR POSITION DEBUG ---');
222
- console.log('Container:', containerRect.width, 'x', containerRect.height);
202
+ console.log('--- AR POSITION DEBUG (Point Projection) ---');
203
+ console.log('Container:', containerRect.width.toFixed(0), 'x', containerRect.height.toFixed(0));
223
204
  console.log('Video:', videoW, 'x', videoH, 'needsRotation:', needsRotation);
224
- console.log('PerspectiveScale (Cover):', perspectiveScale);
225
- console.log('Display:', displayW, 'x', displayH, 'Offsets:', offsetX, offsetY);
226
- console.log('Projection (tx, ty, tz):', tx.toFixed(2), ty.toFixed(2), tz.toFixed(2));
227
- console.log('Screen Coords:', screenX.toFixed(2), screenY.toFixed(2));
228
- console.log('Final Scale:', finalScale.toFixed(4), '(MatrixScale:', matrixScale.toFixed(4), ')');
205
+ console.log('Screen Pos:', screenX.toFixed(1), screenY.toFixed(1));
206
+ console.log('Rotated Angle:', (rotation * 180 / Math.PI).toFixed(1), 'deg');
207
+ console.log('Final Scale:', finalScale.toFixed(4));
229
208
  }
230
209
 
231
210
  // Apply
232
211
  this.overlay.style.width = `${markerW}px`;
233
- this.overlay.style.height = 'auto';
212
+ this.overlay.style.height = 'auto'; // Maintain aspect ratio of the overlay asset
234
213
  this.overlay.style.position = 'absolute';
235
214
  this.overlay.style.transformOrigin = 'center center';
236
215
  this.overlay.style.left = '0';
@@ -238,10 +217,46 @@ class SimpleAR {
238
217
  this.overlay.style.transform = `
239
218
  translate(${screenX}px, ${screenY}px)
240
219
  translate(-50%, -50%)
241
- scale(${finalScale})
242
220
  rotate(${rotation}rad)
221
+ scale(${finalScale})
243
222
  `;
244
223
  }
224
+
225
+ /**
226
+ * Projects a 3D marker-space point all the way to 2D screen CSS pixels
227
+ */
228
+ _projectToScreen(x, y, z, mVT, proj, videoW, videoH, containerRect, needsRotation) {
229
+ // Marker -> Camera Space
230
+ const tx = mVT[0][0] * x + mVT[0][1] * y + mVT[0][2] * z + mVT[0][3];
231
+ const ty = mVT[1][0] * x + mVT[1][1] * y + mVT[1][2] * z + mVT[1][3];
232
+ const tz = mVT[2][0] * x + mVT[2][1] * y + mVT[2][2] * z + mVT[2][3];
233
+
234
+ // Camera -> Buffer Pixels (e.g. 1280x720)
235
+ const bx = (proj[0][0] * tx / tz) + proj[0][2];
236
+ const by = (proj[1][1] * ty / tz) + proj[1][2];
237
+
238
+ // Buffer -> Screen CSS Pixels
239
+ const vW = needsRotation ? videoH : videoW;
240
+ const vH = needsRotation ? videoW : videoH;
241
+ const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
242
+
243
+ const displayW = vW * perspectiveScale;
244
+ const displayH = vH * perspectiveScale;
245
+ const offsetX = (containerRect.width - displayW) / 2;
246
+ const offsetY = (containerRect.height - displayH) / 2;
247
+
248
+ let sx, sy;
249
+ if (needsRotation) {
250
+ // Mapping: Camera +X (Right) -> Screen +Y (Down), Camera +Y (Down) -> Screen -X (Left)
251
+ sx = offsetX + (displayW / 2) - (by - proj[1][2]) * perspectiveScale;
252
+ sy = offsetY + (displayH / 2) + (bx - proj[0][2]) * perspectiveScale;
253
+ } else {
254
+ sx = offsetX + (displayW / 2) + (bx - proj[0][2]) * perspectiveScale;
255
+ sy = offsetY + (displayH / 2) + (by - proj[1][2]) * perspectiveScale;
256
+ }
257
+
258
+ return { sx, sy };
259
+ }
245
260
  }
246
261
 
247
262
  export { SimpleAR };