@srsergio/taptapp-ar 1.0.27 → 1.0.29
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 +63 -42
- package/package.json +1 -1
- package/src/compiler/simple-ar.js +69 -46
|
@@ -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,65 +145,86 @@ 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
|
-
// Apply
|
|
177
|
+
// Apply styles to prevent CSS interference (like max-width: 100%)
|
|
178
|
+
this.overlay.style.maxWidth = 'none';
|
|
179
|
+
this.overlay.style.maxHeight = 'none';
|
|
195
180
|
this.overlay.style.width = `${markerW}px`;
|
|
196
|
-
this.overlay.style.height = 'auto';
|
|
181
|
+
this.overlay.style.height = 'auto'; // Maintain aspect ratio if user has a custom overlay
|
|
197
182
|
this.overlay.style.position = 'absolute';
|
|
198
183
|
this.overlay.style.transformOrigin = 'center center';
|
|
184
|
+
this.overlay.style.display = 'block';
|
|
185
|
+
this.overlay.style.margin = '0';
|
|
199
186
|
this.overlay.style.left = '0';
|
|
200
187
|
this.overlay.style.top = '0';
|
|
188
|
+
// Apply final transform
|
|
189
|
+
// We use translate to move the center of the elements to 0,0
|
|
190
|
+
// Then apply our calculated screen position
|
|
201
191
|
this.overlay.style.transform = `
|
|
202
192
|
translate(${screenX}px, ${screenY}px)
|
|
203
193
|
translate(-50%, -50%)
|
|
204
|
-
scale(${finalScale})
|
|
205
194
|
rotate(${rotation}rad)
|
|
195
|
+
scale(${finalScale})
|
|
206
196
|
`;
|
|
207
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Projects a 3D marker-space point all the way to 2D screen CSS pixels
|
|
200
|
+
*/
|
|
201
|
+
_projectToScreen(x, y, z, mVT, proj, videoW, videoH, containerRect, needsRotation) {
|
|
202
|
+
// Marker -> Camera Space
|
|
203
|
+
const tx = mVT[0][0] * x + mVT[0][1] * y + mVT[0][2] * z + mVT[0][3];
|
|
204
|
+
const ty = mVT[1][0] * x + mVT[1][1] * y + mVT[1][2] * z + mVT[1][3];
|
|
205
|
+
const tz = mVT[2][0] * x + mVT[2][1] * y + mVT[2][2] * z + mVT[2][3];
|
|
206
|
+
// Camera -> Buffer Pixels (e.g. 1280x720)
|
|
207
|
+
const bx = (proj[0][0] * tx / tz) + proj[0][2];
|
|
208
|
+
const by = (proj[1][1] * ty / tz) + proj[1][2];
|
|
209
|
+
// Buffer -> Screen CSS Pixels
|
|
210
|
+
const vW = needsRotation ? videoH : videoW;
|
|
211
|
+
const vH = needsRotation ? videoW : videoH;
|
|
212
|
+
const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
|
|
213
|
+
const displayW = vW * perspectiveScale;
|
|
214
|
+
const displayH = vH * perspectiveScale;
|
|
215
|
+
const offsetX = (containerRect.width - displayW) / 2;
|
|
216
|
+
const offsetY = (containerRect.height - displayH) / 2;
|
|
217
|
+
let sx, sy;
|
|
218
|
+
if (needsRotation) {
|
|
219
|
+
// Mapping: Camera +X (Right) -> Screen +Y (Down), Camera +Y (Down) -> Screen -X (Left)
|
|
220
|
+
sx = offsetX + (displayW / 2) - (by - proj[1][2]) * perspectiveScale;
|
|
221
|
+
sy = offsetY + (displayH / 2) + (bx - proj[0][2]) * perspectiveScale;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
sx = offsetX + (displayW / 2) + (bx - proj[0][2]) * perspectiveScale;
|
|
225
|
+
sy = offsetY + (displayH / 2) + (by - proj[1][2]) * perspectiveScale;
|
|
226
|
+
}
|
|
227
|
+
return { sx, sy };
|
|
228
|
+
}
|
|
208
229
|
}
|
|
209
230
|
export { SimpleAR };
|
package/package.json
CHANGED
|
@@ -167,81 +167,104 @@ 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);
|
|
178
|
+
// 2. Get intrinsic projection from controller
|
|
179
|
+
const proj = this.controller.projectionTransform;
|
|
184
180
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
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);
|
|
189
185
|
|
|
190
|
-
//
|
|
191
|
-
const
|
|
186
|
+
// 4. Calculate Screen Position
|
|
187
|
+
const screenX = pMid.sx;
|
|
188
|
+
const screenY = pMid.sy;
|
|
192
189
|
|
|
193
|
-
//
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
const tz = mVT[2][0] * (markerW / 2) + mVT[2][1] * (markerH / 2) + mVT[2][3];
|
|
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;
|
|
197
193
|
|
|
198
|
-
|
|
194
|
+
const rotation = Math.atan2(dy, dx);
|
|
195
|
+
const pixelDistance100 = Math.sqrt(dx * dx + dy * dy);
|
|
199
196
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const bufferOffsetY = (ty * f / tz);
|
|
203
|
-
|
|
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);
|
|
210
|
-
|
|
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('
|
|
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
|
-
// Apply
|
|
210
|
+
// Apply styles to prevent CSS interference (like max-width: 100%)
|
|
211
|
+
this.overlay.style.maxWidth = 'none';
|
|
212
|
+
this.overlay.style.maxHeight = 'none';
|
|
232
213
|
this.overlay.style.width = `${markerW}px`;
|
|
233
|
-
this.overlay.style.height = 'auto';
|
|
214
|
+
this.overlay.style.height = 'auto'; // Maintain aspect ratio if user has a custom overlay
|
|
234
215
|
this.overlay.style.position = 'absolute';
|
|
235
216
|
this.overlay.style.transformOrigin = 'center center';
|
|
217
|
+
this.overlay.style.display = 'block';
|
|
218
|
+
this.overlay.style.margin = '0';
|
|
236
219
|
this.overlay.style.left = '0';
|
|
237
220
|
this.overlay.style.top = '0';
|
|
221
|
+
|
|
222
|
+
// Apply final transform
|
|
223
|
+
// We use translate to move the center of the elements to 0,0
|
|
224
|
+
// Then apply our calculated screen position
|
|
238
225
|
this.overlay.style.transform = `
|
|
239
226
|
translate(${screenX}px, ${screenY}px)
|
|
240
227
|
translate(-50%, -50%)
|
|
241
|
-
scale(${finalScale})
|
|
242
228
|
rotate(${rotation}rad)
|
|
229
|
+
scale(${finalScale})
|
|
243
230
|
`;
|
|
244
231
|
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Projects a 3D marker-space point all the way to 2D screen CSS pixels
|
|
235
|
+
*/
|
|
236
|
+
_projectToScreen(x, y, z, mVT, proj, videoW, videoH, containerRect, needsRotation) {
|
|
237
|
+
// Marker -> Camera Space
|
|
238
|
+
const tx = mVT[0][0] * x + mVT[0][1] * y + mVT[0][2] * z + mVT[0][3];
|
|
239
|
+
const ty = mVT[1][0] * x + mVT[1][1] * y + mVT[1][2] * z + mVT[1][3];
|
|
240
|
+
const tz = mVT[2][0] * x + mVT[2][1] * y + mVT[2][2] * z + mVT[2][3];
|
|
241
|
+
|
|
242
|
+
// Camera -> Buffer Pixels (e.g. 1280x720)
|
|
243
|
+
const bx = (proj[0][0] * tx / tz) + proj[0][2];
|
|
244
|
+
const by = (proj[1][1] * ty / tz) + proj[1][2];
|
|
245
|
+
|
|
246
|
+
// Buffer -> Screen CSS Pixels
|
|
247
|
+
const vW = needsRotation ? videoH : videoW;
|
|
248
|
+
const vH = needsRotation ? videoW : videoH;
|
|
249
|
+
const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
|
|
250
|
+
|
|
251
|
+
const displayW = vW * perspectiveScale;
|
|
252
|
+
const displayH = vH * perspectiveScale;
|
|
253
|
+
const offsetX = (containerRect.width - displayW) / 2;
|
|
254
|
+
const offsetY = (containerRect.height - displayH) / 2;
|
|
255
|
+
|
|
256
|
+
let sx, sy;
|
|
257
|
+
if (needsRotation) {
|
|
258
|
+
// Mapping: Camera +X (Right) -> Screen +Y (Down), Camera +Y (Down) -> Screen -X (Left)
|
|
259
|
+
sx = offsetX + (displayW / 2) - (by - proj[1][2]) * perspectiveScale;
|
|
260
|
+
sy = offsetY + (displayH / 2) + (bx - proj[0][2]) * perspectiveScale;
|
|
261
|
+
} else {
|
|
262
|
+
sx = offsetX + (displayW / 2) + (bx - proj[0][2]) * perspectiveScale;
|
|
263
|
+
sy = offsetY + (displayH / 2) + (by - proj[1][2]) * perspectiveScale;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return { sx, sy };
|
|
267
|
+
}
|
|
245
268
|
}
|
|
246
269
|
|
|
247
270
|
export { SimpleAR };
|