@srsergio/taptapp-ar 1.0.26 → 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.
@@ -15,32 +15,49 @@
15
15
  * await ar.start();
16
16
  */
17
17
  export class SimpleAR {
18
+ /**
19
+ * @param {Object} options
20
+ * @param {HTMLElement} options.container
21
+ * @param {string|string[]} options.targetSrc
22
+ * @param {HTMLElement} options.overlay
23
+ * @param {number} [options.scale=1.0]
24
+ * @param {((data: {targetIndex: number}) => void | Promise<void>) | null} [options.onFound]
25
+ * @param {((data: {targetIndex: number}) => void | Promise<void>) | null} [options.onLost]
26
+ * @param {((data: {targetIndex: number, worldMatrix: number[]}) => void) | null} [options.onUpdate]
27
+ * @param {Object} [options.cameraConfig]
28
+ */
18
29
  constructor({ container, targetSrc, overlay, scale, onFound, onLost, onUpdate, cameraConfig, }: {
19
- container: any;
20
- targetSrc: any;
21
- overlay: any;
30
+ container: HTMLElement;
31
+ targetSrc: string | string[];
32
+ overlay: HTMLElement;
22
33
  scale?: number | undefined;
23
- onFound?: null | undefined;
24
- onLost?: null | undefined;
25
- onUpdate?: null | undefined;
26
- cameraConfig?: {
27
- facingMode: string;
28
- width: number;
29
- height: number;
30
- } | undefined;
34
+ onFound?: ((data: {
35
+ targetIndex: number;
36
+ }) => void | Promise<void>) | null | undefined;
37
+ onLost?: ((data: {
38
+ targetIndex: number;
39
+ }) => void | Promise<void>) | null | undefined;
40
+ onUpdate?: ((data: {
41
+ targetIndex: number;
42
+ worldMatrix: number[];
43
+ }) => void) | null | undefined;
44
+ cameraConfig?: Object | undefined;
31
45
  });
32
- container: any;
33
- targetSrc: any;
34
- overlay: any;
46
+ container: HTMLElement;
47
+ targetSrc: string | string[];
48
+ overlay: HTMLElement;
35
49
  scaleMultiplier: number;
36
- onFound: any;
37
- onLost: any;
38
- onUpdateCallback: any;
39
- cameraConfig: {
40
- facingMode: string;
41
- width: number;
42
- height: number;
43
- };
50
+ onFound: ((data: {
51
+ targetIndex: number;
52
+ }) => void | Promise<void>) | null;
53
+ onLost: ((data: {
54
+ targetIndex: number;
55
+ }) => void | Promise<void>) | null;
56
+ onUpdateCallback: ((data: {
57
+ targetIndex: number;
58
+ worldMatrix: number[];
59
+ }) => void) | null;
60
+ cameraConfig: Object;
44
61
  video: HTMLVideoElement | null;
45
62
  controller: Controller | null;
46
63
  isTracking: boolean;
@@ -59,5 +76,12 @@ export class SimpleAR {
59
76
  _initController(): void;
60
77
  _handleUpdate(data: any): void;
61
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
+ };
62
86
  }
63
87
  import { Controller } from "./controller.js";
@@ -16,6 +16,17 @@ import { Controller } from "./controller.js";
16
16
  * await ar.start();
17
17
  */
18
18
  class SimpleAR {
19
+ /**
20
+ * @param {Object} options
21
+ * @param {HTMLElement} options.container
22
+ * @param {string|string[]} options.targetSrc
23
+ * @param {HTMLElement} options.overlay
24
+ * @param {number} [options.scale=1.0]
25
+ * @param {((data: {targetIndex: number}) => void | Promise<void>) | null} [options.onFound]
26
+ * @param {((data: {targetIndex: number}) => void | Promise<void>) | null} [options.onLost]
27
+ * @param {((data: {targetIndex: number, worldMatrix: number[]}) => void) | null} [options.onUpdate]
28
+ * @param {Object} [options.cameraConfig]
29
+ */
19
30
  constructor({ container, targetSrc, overlay, scale = 1.0, // Multiplicador de escala personalizado
20
31
  onFound = null, onLost = null, onUpdate = null, cameraConfig = { facingMode: 'environment', width: 1280, height: 720 }, }) {
21
32
  this.container = container;
@@ -45,6 +56,7 @@ class SimpleAR {
45
56
  const targets = Array.isArray(this.targetSrc) ? this.targetSrc : [this.targetSrc];
46
57
  const result = await this.controller.addImageTargets(targets);
47
58
  this.markerDimensions = result.dimensions; // [ [w1, h1], [w2, h2], ... ]
59
+ console.log("Targets loaded. Dimensions:", this.markerDimensions);
48
60
  this.controller.processVideo(this.video);
49
61
  return this;
50
62
  }
@@ -133,54 +145,38 @@ class SimpleAR {
133
145
  const containerRect = this.container.getBoundingClientRect();
134
146
  const videoW = this.video.videoWidth;
135
147
  const videoH = this.video.videoHeight;
148
+ // 1. Determine orientation needs
136
149
  const isPortrait = containerRect.height > containerRect.width;
137
150
  const isVideoLandscape = videoW > videoH;
138
151
  const needsRotation = isPortrait && isVideoLandscape;
139
- // Current display dimensions of the video (accounting for rotation)
140
- const vW = needsRotation ? videoH : videoW;
141
- const vH = needsRotation ? videoW : videoH;
142
- // Robust "object-fit: cover" scale calculation
143
- const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
144
- const displayW = vW * perspectiveScale;
145
- const displayH = vH * perspectiveScale;
146
- const offsetX = (containerRect.width - displayW) / 2;
147
- const offsetY = (containerRect.height - displayH) / 2;
148
- // The tracker uses focal length based on height dimension in tracker space
149
- const f = (videoH / 2) / Math.tan((45.0 * Math.PI / 180) / 2);
150
- // Center of the marker in camera space (marker coordinates origin at top-left)
151
- const tx = mVT[0][0] * (markerW / 2) + mVT[0][1] * (markerH / 2) + mVT[0][3];
152
- const ty = mVT[1][0] * (markerW / 2) + mVT[1][1] * (markerH / 2) + mVT[1][3];
153
- const tz = mVT[2][0] * (markerW / 2) + mVT[2][1] * (markerH / 2) + mVT[2][3];
154
- let screenX, screenY, rotation;
155
- if (needsRotation) {
156
- // Mapping for 90deg clockwise rotation:
157
- // Buffer X (0..videoW) -> Screen Y (0..displayH)
158
- // Buffer Y (0..videoH) -> Screen X (displayW..0)
159
- const bufferOffsetX = (tx * f / tz);
160
- const bufferOffsetY = (ty * f / tz);
161
- screenX = offsetX + (displayW / 2) - (bufferOffsetY * perspectiveScale);
162
- screenY = offsetY + (displayH / 2) + (bufferOffsetX * perspectiveScale);
163
- rotation = Math.atan2(mVT[1][0], mVT[0][0]) - Math.PI / 2;
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;
168
+ // DEBUG LOGS
169
+ if (window.AR_DEBUG) {
170
+ console.log('--- AR POSITION DEBUG (Point Projection) ---');
171
+ console.log('Container:', containerRect.width.toFixed(0), 'x', containerRect.height.toFixed(0));
172
+ console.log('Video:', videoW, 'x', videoH, 'needsRotation:', needsRotation);
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));
164
176
  }
165
- else {
166
- // Normal mapping:
167
- // Buffer X -> Screen X
168
- // Buffer Y -> Screen Y
169
- const bufferOffsetX = (tx * f / tz);
170
- const bufferOffsetY = (ty * f / tz);
171
- screenX = offsetX + (displayW / 2) + (bufferOffsetX * perspectiveScale);
172
- screenY = offsetY + (displayH / 2) + (bufferOffsetY * perspectiveScale);
173
- rotation = Math.atan2(mVT[1][0], mVT[0][0]);
174
- }
175
- // Final Scale calculation:
176
- // f/tz converts world units to buffer pixels
177
- // perspectiveScale converts buffer pixels to screen pixels
178
- // matrixScale handles the target's relative orientation in 2D
179
- const matrixScale = Math.sqrt(mVT[0][0] ** 2 + mVT[1][0] ** 2);
180
- const finalScale = (f / tz) * perspectiveScale * matrixScale * this.scaleMultiplier;
181
177
  // Apply
182
178
  this.overlay.style.width = `${markerW}px`;
183
- this.overlay.style.height = 'auto';
179
+ this.overlay.style.height = 'auto'; // Maintain aspect ratio of the overlay asset
184
180
  this.overlay.style.position = 'absolute';
185
181
  this.overlay.style.transformOrigin = 'center center';
186
182
  this.overlay.style.left = '0';
@@ -188,9 +184,40 @@ class SimpleAR {
188
184
  this.overlay.style.transform = `
189
185
  translate(${screenX}px, ${screenY}px)
190
186
  translate(-50%, -50%)
191
- scale(${finalScale})
192
187
  rotate(${rotation}rad)
188
+ scale(${finalScale})
193
189
  `;
194
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
+ }
195
222
  }
196
223
  export { SimpleAR };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srsergio/taptapp-ar",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "AR Compiler for Node.js and Browser",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,6 +17,17 @@ import { Controller } from "./controller.js";
17
17
  * await ar.start();
18
18
  */
19
19
  class SimpleAR {
20
+ /**
21
+ * @param {Object} options
22
+ * @param {HTMLElement} options.container
23
+ * @param {string|string[]} options.targetSrc
24
+ * @param {HTMLElement} options.overlay
25
+ * @param {number} [options.scale=1.0]
26
+ * @param {((data: {targetIndex: number}) => void | Promise<void>) | null} [options.onFound]
27
+ * @param {((data: {targetIndex: number}) => void | Promise<void>) | null} [options.onLost]
28
+ * @param {((data: {targetIndex: number, worldMatrix: number[]}) => void) | null} [options.onUpdate]
29
+ * @param {Object} [options.cameraConfig]
30
+ */
20
31
  constructor({
21
32
  container,
22
33
  targetSrc,
@@ -59,6 +70,7 @@ class SimpleAR {
59
70
  const targets = Array.isArray(this.targetSrc) ? this.targetSrc : [this.targetSrc];
60
71
  const result = await this.controller.addImageTargets(targets);
61
72
  this.markerDimensions = result.dimensions; // [ [w1, h1], [w2, h2], ... ]
73
+ console.log("Targets loaded. Dimensions:", this.markerDimensions);
62
74
 
63
75
  this.controller.processVideo(this.video);
64
76
 
@@ -155,68 +167,49 @@ class SimpleAR {
155
167
 
156
168
  const [markerW, markerH] = this.markerDimensions[targetIndex];
157
169
  const containerRect = this.container.getBoundingClientRect();
158
-
159
170
  const videoW = this.video.videoWidth;
160
171
  const videoH = this.video.videoHeight;
161
172
 
173
+ // 1. Determine orientation needs
162
174
  const isPortrait = containerRect.height > containerRect.width;
163
175
  const isVideoLandscape = videoW > videoH;
164
176
  const needsRotation = isPortrait && isVideoLandscape;
165
177
 
166
- // Current display dimensions of the video (accounting for rotation)
167
- const vW = needsRotation ? videoH : videoW;
168
- const vH = needsRotation ? videoW : videoH;
178
+ // 2. Get intrinsic projection from controller
179
+ const proj = this.controller.projectionTransform;
169
180
 
170
- // Robust "object-fit: cover" scale calculation
171
- const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
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);
172
185
 
173
- const displayW = vW * perspectiveScale;
174
- const displayH = vH * perspectiveScale;
175
- const offsetX = (containerRect.width - displayW) / 2;
176
- const offsetY = (containerRect.height - displayH) / 2;
186
+ // 4. Calculate Screen Position
187
+ const screenX = pMid.sx;
188
+ const screenY = pMid.sy;
177
189
 
178
- // The tracker uses focal length based on height dimension in tracker space
179
- const f = (videoH / 2) / Math.tan((45.0 * Math.PI / 180) / 2);
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;
180
193
 
181
- // Center of the marker in camera space (marker coordinates origin at 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];
194
+ const rotation = Math.atan2(dy, dx);
195
+ const pixelDistance100 = Math.sqrt(dx * dx + dy * dy);
185
196
 
186
- let screenX, screenY, rotation;
197
+ // Since we projected 100 units, the scale for the whole markerW is:
198
+ const finalScale = (pixelDistance100 / 100) * this.scaleMultiplier;
187
199
 
188
- if (needsRotation) {
189
- // Mapping for 90deg clockwise rotation:
190
- // Buffer X (0..videoW) -> Screen Y (0..displayH)
191
- // Buffer Y (0..videoH) -> Screen X (displayW..0)
192
- const bufferOffsetX = (tx * f / tz);
193
- const bufferOffsetY = (ty * f / tz);
194
-
195
- screenX = offsetX + (displayW / 2) - (bufferOffsetY * perspectiveScale);
196
- screenY = offsetY + (displayH / 2) + (bufferOffsetX * perspectiveScale);
197
- rotation = Math.atan2(mVT[1][0], mVT[0][0]) - Math.PI / 2;
198
- } else {
199
- // Normal mapping:
200
- // Buffer X -> Screen X
201
- // Buffer Y -> Screen Y
202
- const bufferOffsetX = (tx * f / tz);
203
- const bufferOffsetY = (ty * f / tz);
204
-
205
- screenX = offsetX + (displayW / 2) + (bufferOffsetX * perspectiveScale);
206
- screenY = offsetY + (displayH / 2) + (bufferOffsetY * perspectiveScale);
207
- rotation = Math.atan2(mVT[1][0], mVT[0][0]);
200
+ // DEBUG LOGS
201
+ if (window.AR_DEBUG) {
202
+ console.log('--- AR POSITION DEBUG (Point Projection) ---');
203
+ console.log('Container:', containerRect.width.toFixed(0), 'x', containerRect.height.toFixed(0));
204
+ console.log('Video:', videoW, 'x', videoH, 'needsRotation:', needsRotation);
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));
208
208
  }
209
209
 
210
- // Final Scale calculation:
211
- // f/tz converts world units to buffer pixels
212
- // perspectiveScale converts buffer pixels to screen pixels
213
- // matrixScale handles the target's relative orientation in 2D
214
- const matrixScale = Math.sqrt(mVT[0][0] ** 2 + mVT[1][0] ** 2);
215
- const finalScale = (f / tz) * perspectiveScale * matrixScale * this.scaleMultiplier;
216
-
217
210
  // Apply
218
211
  this.overlay.style.width = `${markerW}px`;
219
- this.overlay.style.height = 'auto';
212
+ this.overlay.style.height = 'auto'; // Maintain aspect ratio of the overlay asset
220
213
  this.overlay.style.position = 'absolute';
221
214
  this.overlay.style.transformOrigin = 'center center';
222
215
  this.overlay.style.left = '0';
@@ -224,10 +217,46 @@ class SimpleAR {
224
217
  this.overlay.style.transform = `
225
218
  translate(${screenX}px, ${screenY}px)
226
219
  translate(-50%, -50%)
227
- scale(${finalScale})
228
220
  rotate(${rotation}rad)
221
+ scale(${finalScale})
229
222
  `;
230
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
+ }
231
260
  }
232
261
 
233
262
  export { SimpleAR };