@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.
- package/dist/compiler/simple-ar.d.ts +7 -0
- package/dist/compiler/simple-ar.js +55 -41
- package/package.json +1 -1
- package/src/compiler/simple-ar.js +60 -45
|
@@ -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
|
-
//
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
//
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
//
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
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('
|
|
189
|
-
console.log('
|
|
190
|
-
console.log('
|
|
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
|
@@ -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
|
-
//
|
|
179
|
-
const
|
|
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
|
-
//
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
const
|
|
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
|
-
|
|
186
|
+
// 4. Calculate Screen Position
|
|
187
|
+
const screenX = pMid.sx;
|
|
188
|
+
const screenY = pMid.sy;
|
|
199
189
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
205
|
-
|
|
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
|
-
|
|
212
|
-
|
|
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('
|
|
225
|
-
console.log('
|
|
226
|
-
console.log('
|
|
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 };
|