@srsergio/taptapp-ar 1.0.94 → 1.0.96

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.
@@ -1,307 +0,0 @@
1
- import { Controller } from "./controller.js";
2
- import { projectToScreen } from "../core/utils/projection.js";
3
- class SimpleAR {
4
- container;
5
- targetSrc;
6
- overlay;
7
- scaleMultiplier;
8
- onFound;
9
- onLost;
10
- onUpdateCallback;
11
- cameraConfig;
12
- debug;
13
- lastTime;
14
- frameCount;
15
- fps;
16
- debugPanel = null;
17
- video = null;
18
- controller = null;
19
- isTracking = false;
20
- lastMatrix = null;
21
- filters = [];
22
- markerDimensions = [];
23
- constructor({ container, targetSrc, overlay, scale = 1.0, onFound = null, onLost = null, onUpdate = null, cameraConfig = { facingMode: 'environment', width: 1280, height: 720 }, debug = false, }) {
24
- this.container = container;
25
- this.targetSrc = targetSrc;
26
- this.overlay = overlay;
27
- this.scaleMultiplier = scale;
28
- this.onFound = onFound;
29
- this.onLost = onLost;
30
- this.onUpdateCallback = onUpdate;
31
- this.cameraConfig = cameraConfig;
32
- this.debug = debug;
33
- // @ts-ignore
34
- if (this.debug)
35
- window.AR_DEBUG = true;
36
- this.lastTime = performance.now();
37
- this.frameCount = 0;
38
- this.fps = 0;
39
- }
40
- async start() {
41
- this._createVideo();
42
- await this._startCamera();
43
- this._initController();
44
- if (this.debug)
45
- this._createDebugPanel();
46
- const targets = Array.isArray(this.targetSrc) ? this.targetSrc : [this.targetSrc];
47
- const result = await this.controller.addImageTargets(targets);
48
- this.markerDimensions = result.dimensions;
49
- console.log("Targets loaded. Dimensions:", this.markerDimensions);
50
- this.controller.processVideo(this.video);
51
- return this;
52
- }
53
- stop() {
54
- if (this.controller) {
55
- this.controller.dispose();
56
- this.controller = null;
57
- }
58
- if (this.video && this.video.srcObject) {
59
- this.video.srcObject.getTracks().forEach(track => track.stop());
60
- this.video.remove();
61
- this.video = null;
62
- }
63
- this.isTracking = false;
64
- this.markerDimensions = [];
65
- }
66
- _createVideo() {
67
- this.video = document.createElement('video');
68
- this.video.setAttribute('autoplay', '');
69
- this.video.setAttribute('playsinline', '');
70
- this.video.setAttribute('muted', '');
71
- this.video.style.cssText = `
72
- position: absolute;
73
- top: 0;
74
- left: 0;
75
- width: 100%;
76
- height: 100%;
77
- object-fit: cover;
78
- z-index: 0;
79
- `;
80
- this.container.style.position = 'relative';
81
- this.container.style.overflow = 'hidden';
82
- this.container.insertBefore(this.video, this.container.firstChild);
83
- }
84
- async _startCamera() {
85
- const stream = await navigator.mediaDevices.getUserMedia({
86
- video: this.cameraConfig
87
- });
88
- this.video.srcObject = stream;
89
- await this.video.play();
90
- await new Promise(resolve => {
91
- if (this.video.videoWidth > 0)
92
- return resolve();
93
- this.video.onloadedmetadata = () => resolve();
94
- });
95
- }
96
- _initController() {
97
- this.controller = new Controller({
98
- inputWidth: this.video.videoWidth,
99
- inputHeight: this.video.videoHeight,
100
- debugMode: this.debug,
101
- warmupTolerance: 3, // 🚀 Faster lock than default
102
- missTolerance: 10, // 🛡️ More resilient to temporary occlusion
103
- onUpdate: (data) => this._handleUpdate(data)
104
- });
105
- }
106
- _handleUpdate(data) {
107
- if (data.type !== 'updateMatrix')
108
- return;
109
- const now = performance.now();
110
- this.frameCount++;
111
- if (now - this.lastTime >= 1000) {
112
- this.fps = Math.round((this.frameCount * 1000) / (now - this.lastTime));
113
- this.frameCount = 0;
114
- this.lastTime = now;
115
- if (this.debug)
116
- this._updateDebugPanel(this.isTracking);
117
- }
118
- const { targetIndex, worldMatrix, modelViewTransform, screenCoords, reliabilities, stabilities, detectionPoints } = data;
119
- // Project points to screen coordinates
120
- let projectedPoints = [];
121
- if (screenCoords && screenCoords.length > 0) {
122
- const containerRect = this.container.getBoundingClientRect();
123
- const videoW = this.video.videoWidth;
124
- const videoH = this.video.videoHeight;
125
- const isPortrait = containerRect.height > containerRect.width;
126
- const isVideoLandscape = videoW > videoH;
127
- const needsRotation = isPortrait && isVideoLandscape;
128
- const proj = this.controller.projectionTransform;
129
- const vW = needsRotation ? videoH : videoW;
130
- const vH = needsRotation ? videoW : videoH;
131
- const pScale = Math.max(containerRect.width / vW, containerRect.height / vH);
132
- const dW = vW * pScale;
133
- const dH = vH * pScale;
134
- const oX = (containerRect.width - dW) / 2;
135
- const oY = (containerRect.height - dH) / 2;
136
- projectedPoints = screenCoords.map((p) => {
137
- let sx, sy;
138
- if (needsRotation) {
139
- sx = oX + (dW / 2) - (p.y - proj[1][2]) * pScale;
140
- sy = oY + (dH / 2) + (p.x - proj[0][2]) * pScale;
141
- }
142
- else {
143
- sx = oX + (dW / 2) + (p.x - proj[0][2]) * pScale;
144
- sy = oY + (dH / 2) + (p.y - proj[1][2]) * pScale;
145
- }
146
- return { x: sx, y: sy };
147
- });
148
- }
149
- let projectedDetectionPoints = [];
150
- if (detectionPoints && detectionPoints.length > 0) {
151
- const containerRect = this.container.getBoundingClientRect();
152
- const videoW = this.video.videoWidth;
153
- const videoH = this.video.videoHeight;
154
- const isPortrait = containerRect.height > containerRect.width;
155
- const isVideoLandscape = videoW > videoH;
156
- const needsRotation = isPortrait && isVideoLandscape;
157
- const proj = this.controller.projectionTransform;
158
- const vW = needsRotation ? videoH : videoW;
159
- const vH = needsRotation ? videoW : videoH;
160
- const pScale = Math.max(containerRect.width / vW, containerRect.height / vH);
161
- const dW = vW * pScale;
162
- const dH = vH * pScale;
163
- const oX = (containerRect.width - dW) / 2;
164
- const oY = (containerRect.height - dH) / 2;
165
- projectedDetectionPoints = detectionPoints.map((p) => {
166
- let sx, sy;
167
- if (needsRotation) {
168
- sx = oX + (dW / 2) - (p.y - proj[1][2]) * pScale;
169
- sy = oY + (dH / 2) + (p.x - proj[0][2]) * pScale;
170
- }
171
- else {
172
- sx = oX + (dW / 2) + (p.x - proj[0][2]) * pScale;
173
- sy = oY + (dH / 2) + (p.y - proj[1][2]) * pScale;
174
- }
175
- return { x: sx, y: sy };
176
- });
177
- }
178
- if (worldMatrix) {
179
- if (!this.isTracking) {
180
- this.isTracking = true;
181
- this.overlay && (this.overlay.style.opacity = '1');
182
- this.onFound && this.onFound({ targetIndex });
183
- }
184
- this.lastMatrix = worldMatrix;
185
- this._positionOverlay(modelViewTransform, targetIndex);
186
- }
187
- else {
188
- if (this.isTracking) {
189
- this.isTracking = false;
190
- this.overlay && (this.overlay.style.opacity = '0');
191
- this.onLost && this.onLost({ targetIndex });
192
- }
193
- }
194
- // Always notify the callback if we have points, or if we just lost tracking
195
- if (projectedPoints.length > 0 || projectedDetectionPoints.length > 0 || (worldMatrix === null && data.type === 'updateMatrix')) {
196
- this.onUpdateCallback && this.onUpdateCallback({
197
- targetIndex,
198
- worldMatrix,
199
- screenCoords: projectedPoints,
200
- reliabilities: reliabilities || [],
201
- stabilities: stabilities || [],
202
- detectionPoints: projectedDetectionPoints
203
- });
204
- }
205
- }
206
- _positionOverlay(mVT, targetIndex) {
207
- if (!this.overlay || !this.markerDimensions[targetIndex])
208
- return;
209
- const [markerW, markerH] = this.markerDimensions[targetIndex];
210
- const containerRect = this.container.getBoundingClientRect();
211
- const videoW = this.video.videoWidth;
212
- const videoH = this.video.videoHeight;
213
- const isPortrait = containerRect.height > containerRect.width;
214
- const isVideoLandscape = videoW > videoH;
215
- const needsRotation = isPortrait && isVideoLandscape;
216
- const proj = this.controller.projectionTransform;
217
- const pUL = projectToScreen(0, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
218
- const pUR = projectToScreen(markerW, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
219
- const pLL = projectToScreen(0, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
220
- const pLR = projectToScreen(markerW, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
221
- const solveHomography = (w, h, p1, p2, p3, p4) => {
222
- const x1 = p1.sx, y1 = p1.sy;
223
- const x2 = p2.sx, y2 = p2.sy;
224
- const x3 = p3.sx, y3 = p3.sy;
225
- const x4 = p4.sx, y4 = p4.sy;
226
- const dx1 = x2 - x4, dx2 = x3 - x4, dx3 = x1 - x2 + x4 - x3;
227
- const dy1 = y2 - y4, dy2 = y3 - y4, dy3 = y1 - y2 + y4 - y3;
228
- let a, b, c, d, e, f, g, h_coeff;
229
- if (dx3 === 0 && dy3 === 0) {
230
- a = x2 - x1;
231
- b = x3 - x1;
232
- c = x1;
233
- d = y2 - y1;
234
- e = y3 - y1;
235
- f = y1;
236
- g = 0;
237
- h_coeff = 0;
238
- }
239
- else {
240
- const det = dx1 * dy2 - dx2 * dy1;
241
- g = (dx3 * dy2 - dx2 * dy3) / det;
242
- h_coeff = (dx1 * dy3 - dx3 * dy1) / det;
243
- a = x2 - x1 + g * x2;
244
- b = x3 - x1 + h_coeff * x3;
245
- c = x1;
246
- d = y2 - y1 + g * y2;
247
- e = y3 - y1 + h_coeff * y3;
248
- f = y1;
249
- }
250
- return [
251
- a / w, d / w, 0, g / w,
252
- b / h, e / h, 0, h_coeff / h,
253
- 0, 0, 1, 0,
254
- c, f, 0, 1
255
- ];
256
- };
257
- const matrix = solveHomography(markerW, markerH, pUL, pUR, pLL, pLR);
258
- this.overlay.style.maxWidth = 'none';
259
- this.overlay.style.width = `${markerW}px`;
260
- this.overlay.style.height = `${markerH}px`;
261
- this.overlay.style.position = 'absolute';
262
- this.overlay.style.transformOrigin = '0 0';
263
- this.overlay.style.left = '0';
264
- this.overlay.style.top = '0';
265
- this.overlay.style.display = 'block';
266
- this.overlay.style.transform = `
267
- matrix3d(${matrix.join(',')})
268
- translate(${markerW / 2}px, ${markerH / 2}px)
269
- scale(${this.scaleMultiplier})
270
- translate(${-markerW / 2}px, ${-markerH / 2}px)
271
- `;
272
- }
273
- _createDebugPanel() {
274
- this.debugPanel = document.createElement('div');
275
- this.debugPanel.style.cssText = `
276
- position: absolute;
277
- top: 10px;
278
- left: 10px;
279
- background: rgba(0, 0, 0, 0.8);
280
- color: #0f0;
281
- font-family: monospace;
282
- font-size: 12px;
283
- padding: 8px;
284
- border-radius: 4px;
285
- z-index: 99999;
286
- pointer-events: none;
287
- line-height: 1.5;
288
- `;
289
- this.container.appendChild(this.debugPanel);
290
- }
291
- _updateDebugPanel(isTracking) {
292
- if (!this.debugPanel)
293
- return;
294
- // @ts-ignore
295
- const memory = performance.memory ? Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) : '?';
296
- const color = isTracking ? '#0f0' : '#f00';
297
- const status = isTracking ? 'TRACKING' : 'SEARCHING';
298
- this.debugPanel.innerHTML = `
299
- <div>HEAD-UP DISPLAY</div>
300
- <div>----------------</div>
301
- <div>FPS: ${this.fps}</div>
302
- <div>STATUS: <span style="color:${color}">${status}</span></div>
303
- <div>MEM: ${memory} MB</div>
304
- `;
305
- }
306
- }
307
- export { SimpleAR };
@@ -1,373 +0,0 @@
1
- import { Controller } from "./controller.js";
2
- import { OneEuroFilter } from "../libs/one-euro-filter.js";
3
- import { projectToScreen } from "../core/utils/projection.js";
4
-
5
- /**
6
- * 🍦 SimpleAR - Dead-simple vanilla AR for image overlays
7
- */
8
-
9
- export interface SimpleAROptions {
10
- container: HTMLElement;
11
- targetSrc: string | string[];
12
- overlay: HTMLElement;
13
- scale?: number;
14
- onFound?: ((data: { targetIndex: number }) => void | Promise<void>) | null;
15
- onLost?: ((data: { targetIndex: number }) => void | Promise<void>) | null;
16
- onUpdate?: ((data: {
17
- targetIndex: number,
18
- worldMatrix: number[],
19
- screenCoords?: { x: number, y: number }[],
20
- reliabilities?: number[],
21
- stabilities?: number[],
22
- detectionPoints?: { x: number, y: number }[]
23
- }) => void) | null;
24
- cameraConfig?: MediaStreamConstraints['video'];
25
- debug?: boolean;
26
- }
27
-
28
- class SimpleAR {
29
- container: HTMLElement;
30
- targetSrc: string | string[];
31
- overlay: HTMLElement;
32
- scaleMultiplier: number;
33
- onFound: ((data: { targetIndex: number }) => void | Promise<void>) | null;
34
- onLost: ((data: { targetIndex: number }) => void | Promise<void>) | null;
35
- onUpdateCallback: ((data: {
36
- targetIndex: number,
37
- worldMatrix: number[],
38
- screenCoords?: { x: number, y: number }[],
39
- reliabilities?: number[],
40
- stabilities?: number[],
41
- detectionPoints?: { x: number, y: number }[]
42
- }) => void) | null;
43
- cameraConfig: MediaStreamConstraints['video'];
44
- debug: boolean;
45
-
46
- lastTime: number;
47
- frameCount: number;
48
- fps: number;
49
- debugPanel: HTMLElement | null = null;
50
- video: HTMLVideoElement | null = null;
51
- controller: Controller | null = null;
52
- isTracking: boolean = false;
53
- lastMatrix: number[] | null = null;
54
- filters: OneEuroFilter[] = [];
55
- markerDimensions: number[][] = [];
56
-
57
- constructor({
58
- container,
59
- targetSrc,
60
- overlay,
61
- scale = 1.0,
62
- onFound = null,
63
- onLost = null,
64
- onUpdate = null,
65
- cameraConfig = { facingMode: 'environment', width: 1280, height: 720 },
66
- debug = false,
67
- }: SimpleAROptions) {
68
- this.container = container;
69
- this.targetSrc = targetSrc;
70
- this.overlay = overlay;
71
- this.scaleMultiplier = scale;
72
- this.onFound = onFound;
73
- this.onLost = onLost;
74
- this.onUpdateCallback = onUpdate;
75
- this.cameraConfig = cameraConfig;
76
- this.debug = debug;
77
- // @ts-ignore
78
- if (this.debug) window.AR_DEBUG = true;
79
-
80
- this.lastTime = performance.now();
81
- this.frameCount = 0;
82
- this.fps = 0;
83
- }
84
-
85
- async start() {
86
- this._createVideo();
87
- await this._startCamera();
88
- this._initController();
89
-
90
- if (this.debug) this._createDebugPanel();
91
-
92
- const targets = Array.isArray(this.targetSrc) ? this.targetSrc : [this.targetSrc];
93
- const result = await this.controller!.addImageTargets(targets);
94
- this.markerDimensions = result.dimensions;
95
- console.log("Targets loaded. Dimensions:", this.markerDimensions);
96
-
97
- this.controller!.processVideo(this.video);
98
- return this;
99
- }
100
-
101
- stop() {
102
- if (this.controller) {
103
- this.controller.dispose();
104
- this.controller = null;
105
- }
106
- if (this.video && this.video.srcObject) {
107
- (this.video.srcObject as MediaStream).getTracks().forEach(track => track.stop());
108
- this.video.remove();
109
- this.video = null;
110
- }
111
- this.isTracking = false;
112
- this.markerDimensions = [];
113
- }
114
-
115
- _createVideo() {
116
- this.video = document.createElement('video');
117
- this.video.setAttribute('autoplay', '');
118
- this.video.setAttribute('playsinline', '');
119
- this.video.setAttribute('muted', '');
120
- this.video.style.cssText = `
121
- position: absolute;
122
- top: 0;
123
- left: 0;
124
- width: 100%;
125
- height: 100%;
126
- object-fit: cover;
127
- z-index: 0;
128
- `;
129
- this.container.style.position = 'relative';
130
- this.container.style.overflow = 'hidden';
131
- this.container.insertBefore(this.video, this.container.firstChild);
132
- }
133
-
134
- async _startCamera() {
135
- const stream = await navigator.mediaDevices.getUserMedia({
136
- video: this.cameraConfig
137
- });
138
- this.video!.srcObject = stream;
139
- await this.video!.play();
140
-
141
- await new Promise<void>(resolve => {
142
- if (this.video!.videoWidth > 0) return resolve();
143
- this.video!.onloadedmetadata = () => resolve();
144
- });
145
- }
146
-
147
- _initController() {
148
- this.controller = new Controller({
149
- inputWidth: this.video!.videoWidth,
150
- inputHeight: this.video!.videoHeight,
151
- debugMode: this.debug,
152
- warmupTolerance: 3, // 🚀 Faster lock than default
153
- missTolerance: 10, // 🛡️ More resilient to temporary occlusion
154
- onUpdate: (data) => this._handleUpdate(data)
155
- });
156
- }
157
-
158
- _handleUpdate(data: any) {
159
- if (data.type !== 'updateMatrix') return;
160
-
161
- const now = performance.now();
162
- this.frameCount++;
163
- if (now - this.lastTime >= 1000) {
164
- this.fps = Math.round((this.frameCount * 1000) / (now - this.lastTime));
165
- this.frameCount = 0;
166
- this.lastTime = now;
167
- if (this.debug) this._updateDebugPanel(this.isTracking);
168
- }
169
-
170
- const { targetIndex, worldMatrix, modelViewTransform, screenCoords, reliabilities, stabilities, detectionPoints } = data;
171
-
172
- // Project points to screen coordinates
173
- let projectedPoints = [];
174
- if (screenCoords && screenCoords.length > 0) {
175
- const containerRect = this.container.getBoundingClientRect();
176
- const videoW = this.video!.videoWidth;
177
- const videoH = this.video!.videoHeight;
178
- const isPortrait = containerRect.height > containerRect.width;
179
- const isVideoLandscape = videoW > videoH;
180
- const needsRotation = isPortrait && isVideoLandscape;
181
- const proj = this.controller!.projectionTransform;
182
-
183
- const vW = needsRotation ? videoH : videoW;
184
- const vH = needsRotation ? videoW : videoH;
185
- const pScale = Math.max(containerRect.width / vW, containerRect.height / vH);
186
- const dW = vW * pScale;
187
- const dH = vH * pScale;
188
- const oX = (containerRect.width - dW) / 2;
189
- const oY = (containerRect.height - dH) / 2;
190
-
191
- projectedPoints = screenCoords.map((p: any) => {
192
- let sx, sy;
193
- if (needsRotation) {
194
- sx = oX + (dW / 2) - (p.y - proj[1][2]) * pScale;
195
- sy = oY + (dH / 2) + (p.x - proj[0][2]) * pScale;
196
- } else {
197
- sx = oX + (dW / 2) + (p.x - proj[0][2]) * pScale;
198
- sy = oY + (dH / 2) + (p.y - proj[1][2]) * pScale;
199
- }
200
- return { x: sx, y: sy };
201
- });
202
- }
203
-
204
- let projectedDetectionPoints = [];
205
- if (detectionPoints && detectionPoints.length > 0) {
206
- const containerRect = this.container.getBoundingClientRect();
207
- const videoW = this.video!.videoWidth;
208
- const videoH = this.video!.videoHeight;
209
- const isPortrait = containerRect.height > containerRect.width;
210
- const isVideoLandscape = videoW > videoH;
211
- const needsRotation = isPortrait && isVideoLandscape;
212
- const proj = this.controller!.projectionTransform;
213
-
214
- const vW = needsRotation ? videoH : videoW;
215
- const vH = needsRotation ? videoW : videoH;
216
- const pScale = Math.max(containerRect.width / vW, containerRect.height / vH);
217
- const dW = vW * pScale;
218
- const dH = vH * pScale;
219
- const oX = (containerRect.width - dW) / 2;
220
- const oY = (containerRect.height - dH) / 2;
221
-
222
- projectedDetectionPoints = detectionPoints.map((p: any) => {
223
- let sx, sy;
224
- if (needsRotation) {
225
- sx = oX + (dW / 2) - (p.y - proj[1][2]) * pScale;
226
- sy = oY + (dH / 2) + (p.x - proj[0][2]) * pScale;
227
- } else {
228
- sx = oX + (dW / 2) + (p.x - proj[0][2]) * pScale;
229
- sy = oY + (dH / 2) + (p.y - proj[1][2]) * pScale;
230
- }
231
- return { x: sx, y: sy };
232
- });
233
- }
234
-
235
- if (worldMatrix) {
236
- if (!this.isTracking) {
237
- this.isTracking = true;
238
- this.overlay && (this.overlay.style.opacity = '1');
239
- this.onFound && this.onFound({ targetIndex });
240
- }
241
-
242
- this.lastMatrix = worldMatrix;
243
- this._positionOverlay(modelViewTransform, targetIndex);
244
- } else {
245
- if (this.isTracking) {
246
- this.isTracking = false;
247
- this.overlay && (this.overlay.style.opacity = '0');
248
- this.onLost && this.onLost({ targetIndex });
249
- }
250
- }
251
-
252
- // Always notify the callback if we have points, or if we just lost tracking
253
- if (projectedPoints.length > 0 || projectedDetectionPoints.length > 0 || (worldMatrix === null && data.type === 'updateMatrix')) {
254
- this.onUpdateCallback && this.onUpdateCallback({
255
- targetIndex,
256
- worldMatrix,
257
- screenCoords: projectedPoints,
258
- reliabilities: reliabilities || [],
259
- stabilities: stabilities || [],
260
- detectionPoints: projectedDetectionPoints
261
- });
262
- }
263
- }
264
-
265
- _positionOverlay(mVT: number[][], targetIndex: number) {
266
- if (!this.overlay || !this.markerDimensions[targetIndex]) return;
267
-
268
- const [markerW, markerH] = this.markerDimensions[targetIndex];
269
- const containerRect = this.container.getBoundingClientRect();
270
- const videoW = this.video!.videoWidth;
271
- const videoH = this.video!.videoHeight;
272
-
273
- const isPortrait = containerRect.height > containerRect.width;
274
- const isVideoLandscape = videoW > videoH;
275
- const needsRotation = isPortrait && isVideoLandscape;
276
-
277
- const proj = this.controller!.projectionTransform;
278
-
279
- const pUL = projectToScreen(0, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
280
- const pUR = projectToScreen(markerW, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
281
- const pLL = projectToScreen(0, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
282
- const pLR = projectToScreen(markerW, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
283
-
284
- const solveHomography = (w: number, h: number, p1: any, p2: any, p3: any, p4: any) => {
285
- const x1 = p1.sx, y1 = p1.sy;
286
- const x2 = p2.sx, y2 = p2.sy;
287
- const x3 = p3.sx, y3 = p3.sy;
288
- const x4 = p4.sx, y4 = p4.sy;
289
-
290
- const dx1 = x2 - x4, dx2 = x3 - x4, dx3 = x1 - x2 + x4 - x3;
291
- const dy1 = y2 - y4, dy2 = y3 - y4, dy3 = y1 - y2 + y4 - y3;
292
-
293
- let a, b, c, d, e, f, g, h_coeff;
294
-
295
- if (dx3 === 0 && dy3 === 0) {
296
- a = x2 - x1; b = x3 - x1; c = x1;
297
- d = y2 - y1; e = y3 - y1; f = y1;
298
- g = 0; h_coeff = 0;
299
- } else {
300
- const det = dx1 * dy2 - dx2 * dy1;
301
- g = (dx3 * dy2 - dx2 * dy3) / det;
302
- h_coeff = (dx1 * dy3 - dx3 * dy1) / det;
303
- a = x2 - x1 + g * x2;
304
- b = x3 - x1 + h_coeff * x3;
305
- c = x1;
306
- d = y2 - y1 + g * y2;
307
- e = y3 - y1 + h_coeff * y3;
308
- f = y1;
309
- }
310
- return [
311
- a / w, d / w, 0, g / w,
312
- b / h, e / h, 0, h_coeff / h,
313
- 0, 0, 1, 0,
314
- c, f, 0, 1
315
- ];
316
- };
317
-
318
- const matrix = solveHomography(markerW, markerH, pUL, pUR, pLL, pLR);
319
-
320
- this.overlay.style.maxWidth = 'none';
321
- this.overlay.style.width = `${markerW}px`;
322
- this.overlay.style.height = `${markerH}px`;
323
- this.overlay.style.position = 'absolute';
324
- this.overlay.style.transformOrigin = '0 0';
325
- this.overlay.style.left = '0';
326
- this.overlay.style.top = '0';
327
- this.overlay.style.display = 'block';
328
-
329
- this.overlay.style.transform = `
330
- matrix3d(${matrix.join(',')})
331
- translate(${markerW / 2}px, ${markerH / 2}px)
332
- scale(${this.scaleMultiplier})
333
- translate(${-markerW / 2}px, ${-markerH / 2}px)
334
- `;
335
- }
336
-
337
- _createDebugPanel() {
338
- this.debugPanel = document.createElement('div');
339
- this.debugPanel.style.cssText = `
340
- position: absolute;
341
- top: 10px;
342
- left: 10px;
343
- background: rgba(0, 0, 0, 0.8);
344
- color: #0f0;
345
- font-family: monospace;
346
- font-size: 12px;
347
- padding: 8px;
348
- border-radius: 4px;
349
- z-index: 99999;
350
- pointer-events: none;
351
- line-height: 1.5;
352
- `;
353
- this.container.appendChild(this.debugPanel);
354
- }
355
-
356
- _updateDebugPanel(isTracking: boolean) {
357
- if (!this.debugPanel) return;
358
- // @ts-ignore
359
- const memory = performance.memory ? Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) : '?';
360
- const color = isTracking ? '#0f0' : '#f00';
361
- const status = isTracking ? 'TRACKING' : 'SEARCHING';
362
-
363
- this.debugPanel.innerHTML = `
364
- <div>HEAD-UP DISPLAY</div>
365
- <div>----------------</div>
366
- <div>FPS: ${this.fps}</div>
367
- <div>STATUS: <span style="color:${color}">${status}</span></div>
368
- <div>MEM: ${memory} MB</div>
369
- `;
370
- }
371
- }
372
-
373
- export { SimpleAR };