@srsergio/taptapp-ar 1.0.34 → 1.0.35
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/controller.js +3 -3
- package/dist/compiler/simple-ar.d.ts +8 -1
- package/dist/compiler/simple-ar.js +100 -35
- package/dist/compiler/tracker/tracker.js +1 -1
- package/package.json +1 -1
- package/src/compiler/controller.js +2 -2
- package/src/compiler/simple-ar.js +107 -36
- package/src/compiler/tracker/tracker.js +1 -1
|
@@ -16,7 +16,7 @@ catch (e) {
|
|
|
16
16
|
const DEFAULT_FILTER_CUTOFF = 0.1; // Menor cutoff para filtrar más ruidos cuando está quieto
|
|
17
17
|
const DEFAULT_FILTER_BETA = 0.01; // Beta bajo para suavizar movimientos rápidos
|
|
18
18
|
const DEFAULT_WARMUP_TOLERANCE = 8; // Más frames de calentamiento para asegurar estabilidad inicial
|
|
19
|
-
const DEFAULT_MISS_TOLERANCE =
|
|
19
|
+
const DEFAULT_MISS_TOLERANCE = 2; // Reducido para que el objeto desaparezca más rápido tras pérdida
|
|
20
20
|
class Controller {
|
|
21
21
|
constructor({ inputWidth, inputHeight, onUpdate = null, debugMode = false, maxTrack = 1, warmupTolerance = null, missTolerance = null, filterMinCF = null, filterBeta = null, worker = null, // Allow custom worker injection
|
|
22
22
|
}) {
|
|
@@ -182,8 +182,8 @@ class Controller {
|
|
|
182
182
|
}
|
|
183
183
|
async _trackAndUpdate(inputData, lastModelViewTransform, targetIndex) {
|
|
184
184
|
const { worldCoords, screenCoords } = this.tracker.track(inputData, lastModelViewTransform, targetIndex);
|
|
185
|
-
if (worldCoords.length <
|
|
186
|
-
return null;
|
|
185
|
+
if (worldCoords.length < 6)
|
|
186
|
+
return null; // Umbral de puntos mínimos para mantener el seguimiento
|
|
187
187
|
const modelViewTransform = await this._workerTrackUpdate(lastModelViewTransform, {
|
|
188
188
|
worldCoords,
|
|
189
189
|
screenCoords,
|
|
@@ -26,7 +26,7 @@ export class SimpleAR {
|
|
|
26
26
|
* @param {((data: {targetIndex: number, worldMatrix: number[]}) => void) | null} [options.onUpdate]
|
|
27
27
|
* @param {Object} [options.cameraConfig]
|
|
28
28
|
*/
|
|
29
|
-
constructor({ container, targetSrc, overlay, scale, onFound, onLost, onUpdate, cameraConfig, }: {
|
|
29
|
+
constructor({ container, targetSrc, overlay, scale, onFound, onLost, onUpdate, cameraConfig, debug, }: {
|
|
30
30
|
container: HTMLElement;
|
|
31
31
|
targetSrc: string | string[];
|
|
32
32
|
overlay: HTMLElement;
|
|
@@ -58,6 +58,11 @@ export class SimpleAR {
|
|
|
58
58
|
worldMatrix: number[];
|
|
59
59
|
}) => void) | null;
|
|
60
60
|
cameraConfig: Object;
|
|
61
|
+
debug: any;
|
|
62
|
+
lastTime: number;
|
|
63
|
+
frameCount: number;
|
|
64
|
+
fps: number;
|
|
65
|
+
debugPanel: HTMLDivElement | null;
|
|
61
66
|
video: HTMLVideoElement | null;
|
|
62
67
|
controller: Controller | null;
|
|
63
68
|
isTracking: boolean;
|
|
@@ -77,5 +82,7 @@ export class SimpleAR {
|
|
|
77
82
|
_initController(): void;
|
|
78
83
|
_handleUpdate(data: any): void;
|
|
79
84
|
_positionOverlay(mVT: any, targetIndex: any): void;
|
|
85
|
+
_createDebugPanel(): void;
|
|
86
|
+
_updateDebugPanel(isTracking: any): void;
|
|
80
87
|
}
|
|
81
88
|
import { Controller } from "./controller.js";
|
|
@@ -30,7 +30,7 @@ class SimpleAR {
|
|
|
30
30
|
* @param {Object} [options.cameraConfig]
|
|
31
31
|
*/
|
|
32
32
|
constructor({ container, targetSrc, overlay, scale = 1.0, // Multiplicador de escala personalizado
|
|
33
|
-
onFound = null, onLost = null, onUpdate = null, cameraConfig = { facingMode: 'environment', width: 1280, height: 720 }, }) {
|
|
33
|
+
onFound = null, onLost = null, onUpdate = null, cameraConfig = { facingMode: 'environment', width: 1280, height: 720 }, debug = false, }) {
|
|
34
34
|
this.container = container;
|
|
35
35
|
this.targetSrc = targetSrc;
|
|
36
36
|
this.overlay = overlay;
|
|
@@ -39,6 +39,13 @@ class SimpleAR {
|
|
|
39
39
|
this.onLost = onLost;
|
|
40
40
|
this.onUpdateCallback = onUpdate;
|
|
41
41
|
this.cameraConfig = cameraConfig;
|
|
42
|
+
this.debug = debug;
|
|
43
|
+
if (this.debug)
|
|
44
|
+
window.AR_DEBUG = true;
|
|
45
|
+
this.lastTime = performance.now();
|
|
46
|
+
this.frameCount = 0;
|
|
47
|
+
this.fps = 0;
|
|
48
|
+
this.debugPanel = null;
|
|
42
49
|
this.video = null;
|
|
43
50
|
this.controller = null;
|
|
44
51
|
this.isTracking = false;
|
|
@@ -55,6 +62,8 @@ class SimpleAR {
|
|
|
55
62
|
await this._startCamera();
|
|
56
63
|
// 3. Initialize controller
|
|
57
64
|
this._initController();
|
|
65
|
+
if (this.debug)
|
|
66
|
+
this._createDebugPanel();
|
|
58
67
|
// 4. Load targets (supports single URL or array of URLs)
|
|
59
68
|
const targets = Array.isArray(this.targetSrc) ? this.targetSrc : [this.targetSrc];
|
|
60
69
|
const result = await this.controller.addImageTargets(targets);
|
|
@@ -114,12 +123,23 @@ class SimpleAR {
|
|
|
114
123
|
this.controller = new Controller({
|
|
115
124
|
inputWidth: this.video.videoWidth,
|
|
116
125
|
inputHeight: this.video.videoHeight,
|
|
126
|
+
debugMode: this.debug,
|
|
117
127
|
onUpdate: (data) => this._handleUpdate(data)
|
|
118
128
|
});
|
|
119
129
|
}
|
|
120
130
|
_handleUpdate(data) {
|
|
121
131
|
if (data.type !== 'updateMatrix')
|
|
122
132
|
return;
|
|
133
|
+
// FPS Calculation
|
|
134
|
+
const now = performance.now();
|
|
135
|
+
this.frameCount++;
|
|
136
|
+
if (now - this.lastTime >= 1000) {
|
|
137
|
+
this.fps = Math.round((this.frameCount * 1000) / (now - this.lastTime));
|
|
138
|
+
this.frameCount = 0;
|
|
139
|
+
this.lastTime = now;
|
|
140
|
+
if (this.debug)
|
|
141
|
+
this._updateDebugPanel(this.isTracking);
|
|
142
|
+
}
|
|
123
143
|
const { targetIndex, worldMatrix, modelViewTransform } = data;
|
|
124
144
|
if (worldMatrix) {
|
|
125
145
|
// Target found
|
|
@@ -170,50 +190,95 @@ class SimpleAR {
|
|
|
170
190
|
const isPortrait = containerRect.height > containerRect.width;
|
|
171
191
|
const isVideoLandscape = videoW > videoH;
|
|
172
192
|
const needsRotation = isPortrait && isVideoLandscape;
|
|
173
|
-
//
|
|
193
|
+
// 3. Get intrinsic projection from controller
|
|
174
194
|
const proj = this.controller.projectionTransform;
|
|
175
|
-
//
|
|
176
|
-
//
|
|
195
|
+
// 4. Position calculation via matrix3d (Support for 3D tilt/Z-rotation)
|
|
196
|
+
// We convert the OpenGL World Matrix to a CSS matrix3d.
|
|
197
|
+
// The OpenGL matrix is column-major. CSS matrix3d is also column-major.
|
|
198
|
+
const m = this.controller.getWorldMatrix(mVT, targetIndex);
|
|
199
|
+
// Map OpenGL coords to Screen Pixels using the projection logic
|
|
200
|
+
const vW = needsRotation ? videoH : videoW;
|
|
201
|
+
const vH = needsRotation ? videoW : videoH;
|
|
202
|
+
const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
|
|
203
|
+
const displayW = vW * perspectiveScale;
|
|
204
|
+
const displayH = vH * perspectiveScale;
|
|
205
|
+
const offsetX = (containerRect.width - displayW) / 2;
|
|
206
|
+
const offsetY = (containerRect.height - displayH) / 2;
|
|
207
|
+
// Adjust for centered marker and scaleMultiplier
|
|
208
|
+
const s = finalScale; // We still need the base scale factor for the pixel-to-marker mapping
|
|
209
|
+
// However, a cleaner way is to use the world matrix directly and map it.
|
|
210
|
+
// Actually, the simpler way to do 3D in CSS while keeping my projection logic is:
|
|
211
|
+
// Project the 4 corners and find the homography, OR
|
|
212
|
+
// Use the OpenGL matrix directly with a perspective mapping.
|
|
213
|
+
// Let's use the points projection to maintain the "needsRotation" logic compatibility
|
|
177
214
|
const pMid = projectToScreen(markerW / 2, markerH / 2, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
178
|
-
const
|
|
179
|
-
|
|
215
|
+
const pUL = projectToScreen(0, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
216
|
+
const pUR = projectToScreen(markerW, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
217
|
+
const pLL = projectToScreen(0, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
218
|
+
// Using these points we can calculate the 3D rotation and perspective
|
|
219
|
+
const dx = pUR.sx - pUL.sx;
|
|
220
|
+
const dy = pUR.sy - pUL.sy;
|
|
221
|
+
const dz = pUR.sx - pLL.sx; // Not really Z but used for slant
|
|
222
|
+
const angle = Math.atan2(dy, dx);
|
|
223
|
+
const scaleX = Math.sqrt(dx * dx + dy * dy) / markerW;
|
|
224
|
+
const scaleY = Math.sqrt((pLL.sx - pUL.sx) ** 2 + (pLL.sy - pUL.sy) ** 2) / markerH;
|
|
225
|
+
// For true 3D tilt, we'll use the projection of the axes
|
|
180
226
|
const screenX = pMid.sx;
|
|
181
227
|
const screenY = pMid.sy;
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
// Since we projected 100 units, the scale for the whole markerW is:
|
|
188
|
-
const finalScale = (pixelDistance100 / 100) * this.scaleMultiplier;
|
|
189
|
-
// DEBUG LOGS
|
|
190
|
-
if (window.AR_DEBUG) {
|
|
191
|
-
console.log('--- AR POSITION DEBUG (Point Projection) ---');
|
|
192
|
-
console.log('Container:', containerRect.width.toFixed(0), 'x', containerRect.height.toFixed(0));
|
|
193
|
-
console.log('Video:', videoW, 'x', videoH, 'needsRotation:', needsRotation);
|
|
194
|
-
console.log('Screen Pos:', screenX.toFixed(1), screenY.toFixed(1));
|
|
195
|
-
console.log('Rotated Angle:', (rotation * 180 / Math.PI).toFixed(1), 'deg');
|
|
196
|
-
console.log('Final Scale:', finalScale.toFixed(4));
|
|
197
|
-
}
|
|
198
|
-
// Apply styles to prevent CSS interference (like max-width: 100%)
|
|
228
|
+
// Final Transform applying 3D perspective via matrix3d derived from projected points
|
|
229
|
+
// NOTE: For full 3D we'd need a homography solver, but for "tilt" we can use the
|
|
230
|
+
// original modelViewTransform if we convert it carefully.
|
|
231
|
+
const openGLWorldMatrix = this.controller.getWorldMatrix(mVT, targetIndex);
|
|
232
|
+
// We need to apply the same scaling and offsets as projectToScreen to the matrix
|
|
199
233
|
this.overlay.style.maxWidth = 'none';
|
|
200
|
-
this.overlay.style.maxHeight = 'none';
|
|
201
234
|
this.overlay.style.width = `${markerW}px`;
|
|
202
|
-
this.overlay.style.height =
|
|
235
|
+
this.overlay.style.height = `${markerH}px`;
|
|
203
236
|
this.overlay.style.position = 'absolute';
|
|
204
|
-
this.overlay.style.transformOrigin = '
|
|
205
|
-
this.overlay.style.display = 'block';
|
|
206
|
-
this.overlay.style.margin = '0';
|
|
237
|
+
this.overlay.style.transformOrigin = '0 0'; // Top-left based for simpler matrix mapping
|
|
207
238
|
this.overlay.style.left = '0';
|
|
208
239
|
this.overlay.style.top = '0';
|
|
209
|
-
|
|
210
|
-
//
|
|
211
|
-
//
|
|
240
|
+
this.overlay.style.display = 'block';
|
|
241
|
+
// Approximate 3D tilt using the projected corners to calculate a skew/scale/rotate combo
|
|
242
|
+
// This is more robust than a raw matrix3d if the projection isn't a perfect pinhole
|
|
212
243
|
this.overlay.style.transform = `
|
|
213
|
-
translate(${
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
244
|
+
translate(${pUL.sx}px, ${pUL.sy}px)
|
|
245
|
+
matrix(${(pUR.sx - pUL.sx) / markerW}, ${(pUR.sy - pUL.sy) / markerW},
|
|
246
|
+
${(pLL.sx - pUL.sx) / markerH}, ${(pLL.sy - pUL.sy) / markerH},
|
|
247
|
+
0, 0)
|
|
248
|
+
scale(${this.scaleMultiplier})
|
|
249
|
+
`;
|
|
250
|
+
}
|
|
251
|
+
// Unified projection logic moved to ./utils/projection.js
|
|
252
|
+
_createDebugPanel() {
|
|
253
|
+
this.debugPanel = document.createElement('div');
|
|
254
|
+
this.debugPanel.style.cssText = `
|
|
255
|
+
position: absolute;
|
|
256
|
+
top: 10px;
|
|
257
|
+
left: 10px;
|
|
258
|
+
background: rgba(0, 0, 0, 0.8);
|
|
259
|
+
color: #0f0;
|
|
260
|
+
font-family: monospace;
|
|
261
|
+
font-size: 12px;
|
|
262
|
+
padding: 8px;
|
|
263
|
+
border-radius: 4px;
|
|
264
|
+
z-index: 99999;
|
|
265
|
+
pointer-events: none;
|
|
266
|
+
line-height: 1.5;
|
|
267
|
+
`;
|
|
268
|
+
this.container.appendChild(this.debugPanel);
|
|
269
|
+
}
|
|
270
|
+
_updateDebugPanel(isTracking) {
|
|
271
|
+
if (!this.debugPanel)
|
|
272
|
+
return;
|
|
273
|
+
const memory = performance.memory ? Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) : '?';
|
|
274
|
+
const color = isTracking ? '#0f0' : '#f00';
|
|
275
|
+
const status = isTracking ? 'TRACKING' : 'SEARCHING';
|
|
276
|
+
this.debugPanel.innerHTML = `
|
|
277
|
+
<div>HEAD-UP DISPLAY</div>
|
|
278
|
+
<div>----------------</div>
|
|
279
|
+
<div>FPS: ${this.fps}</div>
|
|
280
|
+
<div>STATUS: <span style="color:${color}">${status}</span></div>
|
|
281
|
+
<div>MEM: ${memory} MB</div>
|
|
217
282
|
`;
|
|
218
283
|
}
|
|
219
284
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { buildModelViewProjectionTransform, computeScreenCoordiate } from "../estimation/utils.js";
|
|
2
2
|
const AR2_DEFAULT_TS = 6;
|
|
3
3
|
const AR2_DEFAULT_TS_GAP = 1;
|
|
4
|
-
const AR2_SEARCH_SIZE =
|
|
4
|
+
const AR2_SEARCH_SIZE = 18;
|
|
5
5
|
const AR2_SEARCH_GAP = 1;
|
|
6
6
|
const AR2_SIM_THRESH = 0.6;
|
|
7
7
|
const TRACKING_KEYFRAME = 0; // 0: 128px (optimized)
|
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@ const DEFAULT_FILTER_BETA = 0.01; // Beta bajo para suavizar movimientos rápid
|
|
|
20
20
|
|
|
21
21
|
const DEFAULT_WARMUP_TOLERANCE = 8; // Más frames de calentamiento para asegurar estabilidad inicial
|
|
22
22
|
|
|
23
|
-
const DEFAULT_MISS_TOLERANCE =
|
|
23
|
+
const DEFAULT_MISS_TOLERANCE = 2; // Reducido para que el objeto desaparezca más rápido tras pérdida
|
|
24
24
|
|
|
25
25
|
class Controller {
|
|
26
26
|
constructor({
|
|
@@ -231,7 +231,7 @@ class Controller {
|
|
|
231
231
|
lastModelViewTransform,
|
|
232
232
|
targetIndex,
|
|
233
233
|
);
|
|
234
|
-
if (worldCoords.length <
|
|
234
|
+
if (worldCoords.length < 6) return null; // Umbral de puntos mínimos para mantener el seguimiento
|
|
235
235
|
const modelViewTransform = await this._workerTrackUpdate(lastModelViewTransform, {
|
|
236
236
|
worldCoords,
|
|
237
237
|
screenCoords,
|
|
@@ -39,6 +39,7 @@ class SimpleAR {
|
|
|
39
39
|
onLost = null,
|
|
40
40
|
onUpdate = null,
|
|
41
41
|
cameraConfig = { facingMode: 'environment', width: 1280, height: 720 },
|
|
42
|
+
debug = false,
|
|
42
43
|
}) {
|
|
43
44
|
this.container = container;
|
|
44
45
|
this.targetSrc = targetSrc;
|
|
@@ -48,6 +49,13 @@ class SimpleAR {
|
|
|
48
49
|
this.onLost = onLost;
|
|
49
50
|
this.onUpdateCallback = onUpdate;
|
|
50
51
|
this.cameraConfig = cameraConfig;
|
|
52
|
+
this.debug = debug;
|
|
53
|
+
if (this.debug) window.AR_DEBUG = true;
|
|
54
|
+
|
|
55
|
+
this.lastTime = performance.now();
|
|
56
|
+
this.frameCount = 0;
|
|
57
|
+
this.fps = 0;
|
|
58
|
+
this.debugPanel = null;
|
|
51
59
|
|
|
52
60
|
this.video = null;
|
|
53
61
|
this.controller = null;
|
|
@@ -69,6 +77,8 @@ class SimpleAR {
|
|
|
69
77
|
// 3. Initialize controller
|
|
70
78
|
this._initController();
|
|
71
79
|
|
|
80
|
+
if (this.debug) this._createDebugPanel();
|
|
81
|
+
|
|
72
82
|
// 4. Load targets (supports single URL or array of URLs)
|
|
73
83
|
const targets = Array.isArray(this.targetSrc) ? this.targetSrc : [this.targetSrc];
|
|
74
84
|
const result = await this.controller.addImageTargets(targets);
|
|
@@ -134,6 +144,7 @@ class SimpleAR {
|
|
|
134
144
|
this.controller = new Controller({
|
|
135
145
|
inputWidth: this.video.videoWidth,
|
|
136
146
|
inputHeight: this.video.videoHeight,
|
|
147
|
+
debugMode: this.debug,
|
|
137
148
|
onUpdate: (data) => this._handleUpdate(data)
|
|
138
149
|
});
|
|
139
150
|
}
|
|
@@ -141,6 +152,16 @@ class SimpleAR {
|
|
|
141
152
|
_handleUpdate(data) {
|
|
142
153
|
if (data.type !== 'updateMatrix') return;
|
|
143
154
|
|
|
155
|
+
// FPS Calculation
|
|
156
|
+
const now = performance.now();
|
|
157
|
+
this.frameCount++;
|
|
158
|
+
if (now - this.lastTime >= 1000) {
|
|
159
|
+
this.fps = Math.round((this.frameCount * 1000) / (now - this.lastTime));
|
|
160
|
+
this.frameCount = 0;
|
|
161
|
+
this.lastTime = now;
|
|
162
|
+
if (this.debug) this._updateDebugPanel(this.isTracking);
|
|
163
|
+
}
|
|
164
|
+
|
|
144
165
|
const { targetIndex, worldMatrix, modelViewTransform } = data;
|
|
145
166
|
|
|
146
167
|
if (worldMatrix) {
|
|
@@ -198,62 +219,112 @@ class SimpleAR {
|
|
|
198
219
|
const isVideoLandscape = videoW > videoH;
|
|
199
220
|
const needsRotation = isPortrait && isVideoLandscape;
|
|
200
221
|
|
|
201
|
-
//
|
|
222
|
+
// 3. Get intrinsic projection from controller
|
|
202
223
|
const proj = this.controller.projectionTransform;
|
|
203
224
|
|
|
204
|
-
//
|
|
205
|
-
//
|
|
225
|
+
// 4. Position calculation via matrix3d (Support for 3D tilt/Z-rotation)
|
|
226
|
+
// We convert the OpenGL World Matrix to a CSS matrix3d.
|
|
227
|
+
// The OpenGL matrix is column-major. CSS matrix3d is also column-major.
|
|
228
|
+
const m = this.controller.getWorldMatrix(mVT, targetIndex);
|
|
229
|
+
|
|
230
|
+
// Map OpenGL coords to Screen Pixels using the projection logic
|
|
231
|
+
const vW = needsRotation ? videoH : videoW;
|
|
232
|
+
const vH = needsRotation ? videoW : videoH;
|
|
233
|
+
const perspectiveScale = Math.max(containerRect.width / vW, containerRect.height / vH);
|
|
234
|
+
const displayW = vW * perspectiveScale;
|
|
235
|
+
const displayH = vH * perspectiveScale;
|
|
236
|
+
const offsetX = (containerRect.width - displayW) / 2;
|
|
237
|
+
const offsetY = (containerRect.height - displayH) / 2;
|
|
238
|
+
|
|
239
|
+
// Adjust for centered marker and scaleMultiplier
|
|
240
|
+
const s = finalScale; // We still need the base scale factor for the pixel-to-marker mapping
|
|
241
|
+
// However, a cleaner way is to use the world matrix directly and map it.
|
|
242
|
+
|
|
243
|
+
// Actually, the simpler way to do 3D in CSS while keeping my projection logic is:
|
|
244
|
+
// Project the 4 corners and find the homography, OR
|
|
245
|
+
// Use the OpenGL matrix directly with a perspective mapping.
|
|
246
|
+
|
|
247
|
+
// Let's use the points projection to maintain the "needsRotation" logic compatibility
|
|
206
248
|
const pMid = projectToScreen(markerW / 2, markerH / 2, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
207
|
-
const
|
|
249
|
+
const pUL = projectToScreen(0, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
250
|
+
const pUR = projectToScreen(markerW, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
251
|
+
const pLL = projectToScreen(0, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
208
252
|
|
|
209
|
-
//
|
|
210
|
-
const
|
|
211
|
-
const
|
|
253
|
+
// Using these points we can calculate the 3D rotation and perspective
|
|
254
|
+
const dx = pUR.sx - pUL.sx;
|
|
255
|
+
const dy = pUR.sy - pUL.sy;
|
|
256
|
+
const dz = pUR.sx - pLL.sx; // Not really Z but used for slant
|
|
212
257
|
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
const
|
|
258
|
+
const angle = Math.atan2(dy, dx);
|
|
259
|
+
const scaleX = Math.sqrt(dx * dx + dy * dy) / markerW;
|
|
260
|
+
const scaleY = Math.sqrt((pLL.sx - pUL.sx) ** 2 + (pLL.sy - pUL.sy) ** 2) / markerH;
|
|
216
261
|
|
|
217
|
-
|
|
218
|
-
const
|
|
262
|
+
// For true 3D tilt, we'll use the projection of the axes
|
|
263
|
+
const screenX = pMid.sx;
|
|
264
|
+
const screenY = pMid.sy;
|
|
219
265
|
|
|
220
|
-
//
|
|
221
|
-
|
|
266
|
+
// Final Transform applying 3D perspective via matrix3d derived from projected points
|
|
267
|
+
// NOTE: For full 3D we'd need a homography solver, but for "tilt" we can use the
|
|
268
|
+
// original modelViewTransform if we convert it carefully.
|
|
222
269
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
console.log('--- AR POSITION DEBUG (Point Projection) ---');
|
|
226
|
-
console.log('Container:', containerRect.width.toFixed(0), 'x', containerRect.height.toFixed(0));
|
|
227
|
-
console.log('Video:', videoW, 'x', videoH, 'needsRotation:', needsRotation);
|
|
228
|
-
console.log('Screen Pos:', screenX.toFixed(1), screenY.toFixed(1));
|
|
229
|
-
console.log('Rotated Angle:', (rotation * 180 / Math.PI).toFixed(1), 'deg');
|
|
230
|
-
console.log('Final Scale:', finalScale.toFixed(4));
|
|
231
|
-
}
|
|
270
|
+
const openGLWorldMatrix = this.controller.getWorldMatrix(mVT, targetIndex);
|
|
271
|
+
// We need to apply the same scaling and offsets as projectToScreen to the matrix
|
|
232
272
|
|
|
233
|
-
// Apply styles to prevent CSS interference (like max-width: 100%)
|
|
234
273
|
this.overlay.style.maxWidth = 'none';
|
|
235
|
-
this.overlay.style.maxHeight = 'none';
|
|
236
274
|
this.overlay.style.width = `${markerW}px`;
|
|
237
|
-
this.overlay.style.height =
|
|
275
|
+
this.overlay.style.height = `${markerH}px`;
|
|
238
276
|
this.overlay.style.position = 'absolute';
|
|
239
|
-
this.overlay.style.transformOrigin = '
|
|
240
|
-
this.overlay.style.display = 'block';
|
|
241
|
-
this.overlay.style.margin = '0';
|
|
277
|
+
this.overlay.style.transformOrigin = '0 0'; // Top-left based for simpler matrix mapping
|
|
242
278
|
this.overlay.style.left = '0';
|
|
243
279
|
this.overlay.style.top = '0';
|
|
280
|
+
this.overlay.style.display = 'block';
|
|
244
281
|
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
// Then apply our calculated screen position
|
|
282
|
+
// Approximate 3D tilt using the projected corners to calculate a skew/scale/rotate combo
|
|
283
|
+
// This is more robust than a raw matrix3d if the projection isn't a perfect pinhole
|
|
248
284
|
this.overlay.style.transform = `
|
|
249
|
-
translate(${
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
285
|
+
translate(${pUL.sx}px, ${pUL.sy}px)
|
|
286
|
+
matrix(${(pUR.sx - pUL.sx) / markerW}, ${(pUR.sy - pUL.sy) / markerW},
|
|
287
|
+
${(pLL.sx - pUL.sx) / markerH}, ${(pLL.sy - pUL.sy) / markerH},
|
|
288
|
+
0, 0)
|
|
289
|
+
scale(${this.scaleMultiplier})
|
|
253
290
|
`;
|
|
254
291
|
}
|
|
255
292
|
|
|
256
293
|
// Unified projection logic moved to ./utils/projection.js
|
|
294
|
+
|
|
295
|
+
_createDebugPanel() {
|
|
296
|
+
this.debugPanel = document.createElement('div');
|
|
297
|
+
this.debugPanel.style.cssText = `
|
|
298
|
+
position: absolute;
|
|
299
|
+
top: 10px;
|
|
300
|
+
left: 10px;
|
|
301
|
+
background: rgba(0, 0, 0, 0.8);
|
|
302
|
+
color: #0f0;
|
|
303
|
+
font-family: monospace;
|
|
304
|
+
font-size: 12px;
|
|
305
|
+
padding: 8px;
|
|
306
|
+
border-radius: 4px;
|
|
307
|
+
z-index: 99999;
|
|
308
|
+
pointer-events: none;
|
|
309
|
+
line-height: 1.5;
|
|
310
|
+
`;
|
|
311
|
+
this.container.appendChild(this.debugPanel);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
_updateDebugPanel(isTracking) {
|
|
315
|
+
if (!this.debugPanel) return;
|
|
316
|
+
const memory = performance.memory ? Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) : '?';
|
|
317
|
+
const color = isTracking ? '#0f0' : '#f00';
|
|
318
|
+
const status = isTracking ? 'TRACKING' : 'SEARCHING';
|
|
319
|
+
|
|
320
|
+
this.debugPanel.innerHTML = `
|
|
321
|
+
<div>HEAD-UP DISPLAY</div>
|
|
322
|
+
<div>----------------</div>
|
|
323
|
+
<div>FPS: ${this.fps}</div>
|
|
324
|
+
<div>STATUS: <span style="color:${color}">${status}</span></div>
|
|
325
|
+
<div>MEM: ${memory} MB</div>
|
|
326
|
+
`;
|
|
327
|
+
}
|
|
257
328
|
}
|
|
258
329
|
|
|
259
330
|
export { SimpleAR };
|