@srsergio/taptapp-ar 1.0.93 → 1.0.95
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/README.md +16 -14
- package/dist/compiler/offline-compiler.d.ts +3 -3
- package/dist/compiler/offline-compiler.js +50 -33
- package/dist/core/constants.d.ts +2 -0
- package/dist/core/constants.js +4 -1
- package/dist/core/detector/detector-lite.d.ts +6 -5
- package/dist/core/detector/detector-lite.js +46 -16
- package/dist/core/matching/matcher.d.ts +1 -1
- package/dist/core/matching/matcher.js +7 -4
- package/dist/core/matching/matching.d.ts +2 -1
- package/dist/core/matching/matching.js +43 -11
- package/dist/core/perception/bio-inspired-engine.d.ts +130 -0
- package/dist/core/perception/bio-inspired-engine.js +232 -0
- package/dist/core/perception/foveal-attention.d.ts +142 -0
- package/dist/core/perception/foveal-attention.js +280 -0
- package/dist/core/perception/index.d.ts +6 -0
- package/dist/core/perception/index.js +17 -0
- package/dist/core/perception/predictive-coding.d.ts +92 -0
- package/dist/core/perception/predictive-coding.js +278 -0
- package/dist/core/perception/saccadic-controller.d.ts +126 -0
- package/dist/core/perception/saccadic-controller.js +269 -0
- package/dist/core/perception/saliency-map.d.ts +74 -0
- package/dist/core/perception/saliency-map.js +254 -0
- package/dist/core/perception/scale-orchestrator.d.ts +28 -0
- package/dist/core/perception/scale-orchestrator.js +68 -0
- package/dist/core/protocol.d.ts +14 -1
- package/dist/core/protocol.js +33 -1
- package/dist/runtime/bio-inspired-controller.d.ts +135 -0
- package/dist/runtime/bio-inspired-controller.js +358 -0
- package/dist/runtime/controller.d.ts +11 -2
- package/dist/runtime/controller.js +20 -8
- package/dist/runtime/controller.worker.js +2 -2
- package/dist/runtime/simple-ar.d.ts +24 -20
- package/dist/runtime/simple-ar.js +172 -156
- package/package.json +1 -1
- package/src/compiler/offline-compiler.ts +56 -36
- package/src/core/constants.ts +5 -1
- package/src/core/detector/detector-lite.js +46 -16
- package/src/core/matching/matcher.js +8 -4
- package/src/core/matching/matching.js +51 -12
- package/src/core/perception/bio-inspired-engine.js +275 -0
- package/src/core/perception/foveal-attention.js +306 -0
- package/src/core/perception/index.js +18 -0
- package/src/core/perception/predictive-coding.js +327 -0
- package/src/core/perception/saccadic-controller.js +303 -0
- package/src/core/perception/saliency-map.js +296 -0
- package/src/core/perception/scale-orchestrator.js +80 -0
- package/src/core/protocol.ts +38 -1
- package/src/runtime/bio-inspired-controller.ts +448 -0
- package/src/runtime/controller.ts +22 -7
- package/src/runtime/controller.worker.js +2 -1
- package/src/runtime/simple-ar.ts +197 -171
package/src/runtime/simple-ar.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { OneEuroFilter } from "../libs/one-euro-filter.js";
|
|
1
|
+
import { BioInspiredController } from "./bio-inspired-controller.js";
|
|
3
2
|
import { projectToScreen } from "../core/utils/projection.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
|
-
* 🍦 SimpleAR -
|
|
5
|
+
* 🍦 SimpleAR - High-performance Vanilla AR for image overlays
|
|
6
|
+
* Now powered by Bio-Inspired Perception and Nanite Virtualized Features.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
export interface SimpleAROptions {
|
|
@@ -25,6 +25,58 @@ export interface SimpleAROptions {
|
|
|
25
25
|
debug?: boolean;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* 🕵️ Internal Smoothing Manager
|
|
30
|
+
* Applies Median + Adaptive Alpha filtering for sub-pixel stability.
|
|
31
|
+
*/
|
|
32
|
+
class SmoothingManager {
|
|
33
|
+
private history: Map<number, { x: number, y: number }[]> = new Map();
|
|
34
|
+
private lastFiltered: Map<number, { x: number, y: number }> = new Map();
|
|
35
|
+
private medianSize = 3;
|
|
36
|
+
private deadZone = 0.2;
|
|
37
|
+
|
|
38
|
+
smooth(id: number, raw: { x: number, y: number }, reliability: number) {
|
|
39
|
+
if (!this.history.has(id)) this.history.set(id, []);
|
|
40
|
+
const h = this.history.get(id)!;
|
|
41
|
+
h.push(raw);
|
|
42
|
+
if (h.length > this.medianSize) h.shift();
|
|
43
|
+
|
|
44
|
+
// Get median
|
|
45
|
+
const sortedX = [...h].map(p => p.x).sort((a, b) => a - b);
|
|
46
|
+
const sortedY = [...h].map(p => p.y).sort((a, b) => a - b);
|
|
47
|
+
const median = {
|
|
48
|
+
x: sortedX[Math.floor(sortedX.length / 2)],
|
|
49
|
+
y: sortedY[Math.floor(sortedY.length / 2)]
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Adaptive Alpha based on reliability
|
|
53
|
+
const baseAlpha = 0.15;
|
|
54
|
+
const alpha = baseAlpha + (reliability * (1.0 - baseAlpha));
|
|
55
|
+
const last = this.lastFiltered.get(id) || median;
|
|
56
|
+
|
|
57
|
+
let filteredX = last.x * (1 - alpha) + median.x * alpha;
|
|
58
|
+
let filteredY = last.y * (1 - alpha) + median.y * alpha;
|
|
59
|
+
|
|
60
|
+
// Dead-zone to kill jitter at rest
|
|
61
|
+
if (Math.abs(filteredX - last.x) < this.deadZone) filteredX = last.x;
|
|
62
|
+
if (Math.abs(filteredY - last.y) < this.deadZone) filteredY = last.y;
|
|
63
|
+
|
|
64
|
+
const result = { x: filteredX, y: filteredY };
|
|
65
|
+
this.lastFiltered.set(id, result);
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
reset(id?: number) {
|
|
70
|
+
if (id !== undefined) {
|
|
71
|
+
this.history.delete(id);
|
|
72
|
+
this.lastFiltered.delete(id);
|
|
73
|
+
} else {
|
|
74
|
+
this.history.clear();
|
|
75
|
+
this.lastFiltered.clear();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
28
80
|
class SimpleAR {
|
|
29
81
|
container: HTMLElement;
|
|
30
82
|
targetSrc: string | string[];
|
|
@@ -43,16 +95,18 @@ class SimpleAR {
|
|
|
43
95
|
cameraConfig: MediaStreamConstraints['video'];
|
|
44
96
|
debug: boolean;
|
|
45
97
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
98
|
+
private video: HTMLVideoElement | null = null;
|
|
99
|
+
private controller: BioInspiredController | null = null;
|
|
100
|
+
private smoother = new SmoothingManager();
|
|
101
|
+
private isTracking: boolean = false;
|
|
102
|
+
private markerDimensions: number[][] = [];
|
|
103
|
+
private debugPanel: HTMLElement | null = null;
|
|
104
|
+
private debugCanvas: HTMLCanvasElement | null = null;
|
|
105
|
+
private debugCtx: CanvasRenderingContext2D | null = null;
|
|
106
|
+
|
|
107
|
+
private lastTime = 0;
|
|
108
|
+
private fps = 0;
|
|
109
|
+
private frameCount = 0;
|
|
56
110
|
|
|
57
111
|
constructor({
|
|
58
112
|
container,
|
|
@@ -74,12 +128,6 @@ class SimpleAR {
|
|
|
74
128
|
this.onUpdateCallback = onUpdate;
|
|
75
129
|
this.cameraConfig = cameraConfig;
|
|
76
130
|
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
131
|
}
|
|
84
132
|
|
|
85
133
|
async start() {
|
|
@@ -87,14 +135,17 @@ class SimpleAR {
|
|
|
87
135
|
await this._startCamera();
|
|
88
136
|
this._initController();
|
|
89
137
|
|
|
90
|
-
if (this.debug)
|
|
138
|
+
if (this.debug) {
|
|
139
|
+
this._createDebugPanel();
|
|
140
|
+
this._createDebugCanvas();
|
|
141
|
+
}
|
|
91
142
|
|
|
92
143
|
const targets = Array.isArray(this.targetSrc) ? this.targetSrc : [this.targetSrc];
|
|
93
144
|
const result = await this.controller!.addImageTargets(targets);
|
|
94
145
|
this.markerDimensions = result.dimensions;
|
|
95
|
-
console.log("Targets loaded. Dimensions:", this.markerDimensions);
|
|
96
146
|
|
|
97
|
-
|
|
147
|
+
// Kick off loop
|
|
148
|
+
this.controller!.processVideo(this.video!);
|
|
98
149
|
return this;
|
|
99
150
|
}
|
|
100
151
|
|
|
@@ -109,32 +160,26 @@ class SimpleAR {
|
|
|
109
160
|
this.video = null;
|
|
110
161
|
}
|
|
111
162
|
this.isTracking = false;
|
|
112
|
-
this.
|
|
163
|
+
this.smoother.reset();
|
|
113
164
|
}
|
|
114
165
|
|
|
115
|
-
_createVideo() {
|
|
166
|
+
private _createVideo() {
|
|
116
167
|
this.video = document.createElement('video');
|
|
117
168
|
this.video.setAttribute('autoplay', '');
|
|
118
169
|
this.video.setAttribute('playsinline', '');
|
|
119
170
|
this.video.setAttribute('muted', '');
|
|
120
171
|
this.video.style.cssText = `
|
|
121
172
|
position: absolute;
|
|
122
|
-
top: 0;
|
|
123
|
-
|
|
124
|
-
width: 100%;
|
|
125
|
-
height: 100%;
|
|
126
|
-
object-fit: cover;
|
|
127
|
-
z-index: 0;
|
|
173
|
+
top: 0; left: 0; width: 100%; height: 100%;
|
|
174
|
+
object-fit: cover; z-index: 0;
|
|
128
175
|
`;
|
|
129
176
|
this.container.style.position = 'relative';
|
|
130
177
|
this.container.style.overflow = 'hidden';
|
|
131
178
|
this.container.insertBefore(this.video, this.container.firstChild);
|
|
132
179
|
}
|
|
133
180
|
|
|
134
|
-
async _startCamera() {
|
|
135
|
-
const stream = await navigator.mediaDevices.getUserMedia({
|
|
136
|
-
video: this.cameraConfig
|
|
137
|
-
});
|
|
181
|
+
private async _startCamera() {
|
|
182
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: this.cameraConfig });
|
|
138
183
|
this.video!.srcObject = stream;
|
|
139
184
|
await this.video!.play();
|
|
140
185
|
|
|
@@ -144,178 +189,103 @@ class SimpleAR {
|
|
|
144
189
|
});
|
|
145
190
|
}
|
|
146
191
|
|
|
147
|
-
_initController() {
|
|
148
|
-
this.controller = new
|
|
192
|
+
private _initController() {
|
|
193
|
+
this.controller = new BioInspiredController({
|
|
149
194
|
inputWidth: this.video!.videoWidth,
|
|
150
195
|
inputHeight: this.video!.videoHeight,
|
|
151
196
|
debugMode: this.debug,
|
|
152
|
-
|
|
153
|
-
|
|
197
|
+
bioInspired: {
|
|
198
|
+
enabled: true,
|
|
199
|
+
aggressiveSkipping: false
|
|
200
|
+
},
|
|
154
201
|
onUpdate: (data) => this._handleUpdate(data)
|
|
155
202
|
});
|
|
156
203
|
}
|
|
157
204
|
|
|
158
|
-
_handleUpdate(data: any) {
|
|
159
|
-
if (data.type !== 'updateMatrix')
|
|
205
|
+
private _handleUpdate(data: any) {
|
|
206
|
+
if (data.type !== 'updateMatrix') {
|
|
207
|
+
if (data.type === 'featurePoints' && this.debugCtx) {
|
|
208
|
+
this._drawDebugFeatures(data.featurePoints);
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
160
212
|
|
|
213
|
+
// FPS Meter
|
|
161
214
|
const now = performance.now();
|
|
162
215
|
this.frameCount++;
|
|
163
216
|
if (now - this.lastTime >= 1000) {
|
|
164
217
|
this.fps = Math.round((this.frameCount * 1000) / (now - this.lastTime));
|
|
165
218
|
this.frameCount = 0;
|
|
166
219
|
this.lastTime = now;
|
|
167
|
-
if (this.debug) this._updateDebugPanel(this.isTracking);
|
|
168
220
|
}
|
|
169
221
|
|
|
170
|
-
const { targetIndex, worldMatrix, modelViewTransform,
|
|
222
|
+
const { targetIndex, worldMatrix, modelViewTransform, reliabilities, stabilities, screenCoords, pixelsSaved } = data;
|
|
171
223
|
|
|
172
|
-
//
|
|
173
|
-
let
|
|
224
|
+
// Apply Smoothing
|
|
225
|
+
let smoothedCoords = screenCoords || [];
|
|
174
226
|
if (screenCoords && screenCoords.length > 0) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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 };
|
|
227
|
+
smoothedCoords = screenCoords.map((p: any) => {
|
|
228
|
+
const rel = reliabilities ? (reliabilities[p.id] || 0.5) : 0.5;
|
|
229
|
+
const sm = this.smoother.smooth(p.id, p, rel);
|
|
230
|
+
return { ...sm, id: p.id };
|
|
232
231
|
});
|
|
233
232
|
}
|
|
234
233
|
|
|
235
234
|
if (worldMatrix) {
|
|
236
235
|
if (!this.isTracking) {
|
|
237
236
|
this.isTracking = true;
|
|
238
|
-
this.overlay
|
|
237
|
+
if (this.overlay) this.overlay.style.opacity = '1';
|
|
239
238
|
this.onFound && this.onFound({ targetIndex });
|
|
240
239
|
}
|
|
241
|
-
|
|
242
|
-
this.lastMatrix = worldMatrix;
|
|
243
240
|
this._positionOverlay(modelViewTransform, targetIndex);
|
|
244
241
|
} else {
|
|
245
242
|
if (this.isTracking) {
|
|
246
243
|
this.isTracking = false;
|
|
247
|
-
this.overlay
|
|
244
|
+
if (this.overlay) this.overlay.style.opacity = '0';
|
|
248
245
|
this.onLost && this.onLost({ targetIndex });
|
|
246
|
+
this.smoother.reset();
|
|
249
247
|
}
|
|
250
248
|
}
|
|
251
249
|
|
|
252
|
-
//
|
|
253
|
-
if (
|
|
254
|
-
this.onUpdateCallback
|
|
250
|
+
// Notify callback
|
|
251
|
+
if (this.onUpdateCallback) {
|
|
252
|
+
this.onUpdateCallback({
|
|
255
253
|
targetIndex,
|
|
256
254
|
worldMatrix,
|
|
257
|
-
screenCoords:
|
|
255
|
+
screenCoords: smoothedCoords,
|
|
258
256
|
reliabilities: reliabilities || [],
|
|
259
257
|
stabilities: stabilities || [],
|
|
260
|
-
detectionPoints:
|
|
258
|
+
detectionPoints: data.featurePoints
|
|
261
259
|
});
|
|
262
260
|
}
|
|
261
|
+
|
|
262
|
+
// Draw Debug UI
|
|
263
|
+
if (this.debug) {
|
|
264
|
+
this._updateHUD(data);
|
|
265
|
+
this._drawDebugPoints(smoothedCoords, stabilities);
|
|
266
|
+
}
|
|
263
267
|
}
|
|
264
268
|
|
|
265
|
-
_positionOverlay(mVT: number[][], targetIndex: number) {
|
|
269
|
+
private _positionOverlay(mVT: number[][], targetIndex: number) {
|
|
266
270
|
if (!this.overlay || !this.markerDimensions[targetIndex]) return;
|
|
267
271
|
|
|
268
272
|
const [markerW, markerH] = this.markerDimensions[targetIndex];
|
|
269
273
|
const containerRect = this.container.getBoundingClientRect();
|
|
270
274
|
const videoW = this.video!.videoWidth;
|
|
271
275
|
const videoH = this.video!.videoHeight;
|
|
276
|
+
const proj = this.controller!.projectionTransform;
|
|
272
277
|
|
|
278
|
+
// Handle portrait rotation for mobile
|
|
273
279
|
const isPortrait = containerRect.height > containerRect.width;
|
|
274
280
|
const isVideoLandscape = videoW > videoH;
|
|
275
281
|
const needsRotation = isPortrait && isVideoLandscape;
|
|
276
282
|
|
|
277
|
-
const proj = this.controller!.projectionTransform;
|
|
278
|
-
|
|
279
283
|
const pUL = projectToScreen(0, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
280
284
|
const pUR = projectToScreen(markerW, 0, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
281
285
|
const pLL = projectToScreen(0, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
282
286
|
const pLR = projectToScreen(markerW, markerH, 0, mVT, proj, videoW, videoH, containerRect, needsRotation);
|
|
283
287
|
|
|
284
|
-
const
|
|
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);
|
|
288
|
+
const matrix = this._solveHomography(markerW, markerH, pUL, pUR, pLL, pLR);
|
|
319
289
|
|
|
320
290
|
this.overlay.style.maxWidth = 'none';
|
|
321
291
|
this.overlay.style.width = `${markerW}px`;
|
|
@@ -334,40 +304,96 @@ class SimpleAR {
|
|
|
334
304
|
`;
|
|
335
305
|
}
|
|
336
306
|
|
|
337
|
-
|
|
307
|
+
private _solveHomography(w: number, h: number, p1: any, p2: any, p3: any, p4: any) {
|
|
308
|
+
const x1 = p1.sx, y1 = p1.sy;
|
|
309
|
+
const x2 = p2.sx, y2 = p2.sy;
|
|
310
|
+
const x3 = p3.sx, y3 = p3.sy;
|
|
311
|
+
const x4 = p4.sx, y4 = p4.sy;
|
|
312
|
+
|
|
313
|
+
const dx1 = x2 - x4, dx2 = x3 - x4, dx3 = x1 - x2 + x4 - x3;
|
|
314
|
+
const dy1 = y2 - y4, dy2 = y3 - y4, dy3 = y1 - y2 + y4 - y3;
|
|
315
|
+
|
|
316
|
+
const det = dx1 * dy2 - dx2 * dy1;
|
|
317
|
+
const g = (dx3 * dy2 - dx2 * dy3) / det;
|
|
318
|
+
const h_coeff = (dx1 * dy3 - dx3 * dy1) / det;
|
|
319
|
+
const a = x2 - x1 + g * x2;
|
|
320
|
+
const b = x3 - x1 + h_coeff * x3;
|
|
321
|
+
const c = x1;
|
|
322
|
+
const d = y2 - y1 + g * y2;
|
|
323
|
+
const e = y3 - y1 + h_coeff * y3;
|
|
324
|
+
const f = y1;
|
|
325
|
+
|
|
326
|
+
return [
|
|
327
|
+
a / w, d / w, 0, g / w,
|
|
328
|
+
b / h, e / h, 0, h_coeff / h,
|
|
329
|
+
0, 0, 1, 0,
|
|
330
|
+
c, f, 0, 1
|
|
331
|
+
];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// --- DEBUG METHODS ---
|
|
335
|
+
|
|
336
|
+
private _createDebugPanel() {
|
|
338
337
|
this.debugPanel = document.createElement('div');
|
|
339
338
|
this.debugPanel.style.cssText = `
|
|
340
|
-
position: absolute;
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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;
|
|
339
|
+
position: absolute; top: 10px; left: 10px;
|
|
340
|
+
background: rgba(0, 0, 0, 0.7); color: #0f0;
|
|
341
|
+
font-family: monospace; font-size: 11px; padding: 10px;
|
|
342
|
+
border-radius: 5px; z-index: 100; pointer-events: none;
|
|
343
|
+
line-height: 1.4; border-left: 3px solid #0f0;
|
|
352
344
|
`;
|
|
353
345
|
this.container.appendChild(this.debugPanel);
|
|
354
346
|
}
|
|
355
347
|
|
|
356
|
-
|
|
348
|
+
private _createDebugCanvas() {
|
|
349
|
+
this.debugCanvas = document.createElement('canvas');
|
|
350
|
+
this.debugCanvas.width = this.container.clientWidth;
|
|
351
|
+
this.debugCanvas.height = this.container.clientHeight;
|
|
352
|
+
this.debugCanvas.style.cssText = `
|
|
353
|
+
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
|
354
|
+
pointer-events: none; z-index: 99;
|
|
355
|
+
`;
|
|
356
|
+
this.container.appendChild(this.debugCanvas);
|
|
357
|
+
this.debugCtx = this.debugCanvas.getContext('2d');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private _updateHUD(data: any) {
|
|
357
361
|
if (!this.debugPanel) return;
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
const status = isTracking ? 'TRACKING' : 'SEARCHING';
|
|
362
|
+
const rel = data.reliabilities ? (data.reliabilities.reduce((a: any, b: any) => a + b, 0) / data.reliabilities.length).toFixed(2) : "0.00";
|
|
363
|
+
const stab = data.stabilities ? (data.stabilities.reduce((a: any, b: any) => a + b, 0) / data.stabilities.length).toFixed(2) : "0.00";
|
|
364
|
+
const savings = data.pixelsSaved ? ((data.pixelsSaved / (this.video!.videoWidth * this.video!.videoHeight)) * 100).toFixed(0) : "0";
|
|
362
365
|
|
|
363
366
|
this.debugPanel.innerHTML = `
|
|
364
|
-
<
|
|
365
|
-
|
|
366
|
-
<
|
|
367
|
-
|
|
368
|
-
|
|
367
|
+
<b>TapTapp AR HUD</b><br>
|
|
368
|
+
------------------<br>
|
|
369
|
+
STATUS: <span style="color:${this.isTracking ? '#0f0' : '#f00'}">${this.isTracking ? 'TRACKING' : 'SEARCHING'}</span><br>
|
|
370
|
+
FPS: ${this.fps}<br>
|
|
371
|
+
RELIAB: ${rel}<br>
|
|
372
|
+
STABIL: ${stab}<br>
|
|
373
|
+
SAVINGS: ${savings}% Pixels<br>
|
|
374
|
+
POINTS: ${data.screenCoords?.length || 0}
|
|
369
375
|
`;
|
|
370
376
|
}
|
|
377
|
+
|
|
378
|
+
private _drawDebugPoints(coords: any[], stabilities: any[]) {
|
|
379
|
+
if (!this.debugCtx) return;
|
|
380
|
+
this.debugCtx.clearRect(0, 0, this.debugCanvas!.width, this.debugCanvas!.height);
|
|
381
|
+
|
|
382
|
+
coords.forEach((p, i) => {
|
|
383
|
+
const s = stabilities ? (stabilities[i] || 0) : 0.5;
|
|
384
|
+
this.debugCtx!.fillStyle = `rgba(0, 255, 0, ${0.4 + s * 0.6})`;
|
|
385
|
+
this.debugCtx!.fillRect(p.x - 1, p.y - 1, 2, 2);
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private _drawDebugFeatures(points: any[]) {
|
|
390
|
+
if (!this.debugCtx || this.isTracking) return;
|
|
391
|
+
this.debugCtx.clearRect(0, 0, this.debugCanvas!.width, this.debugCanvas!.height);
|
|
392
|
+
this.debugCtx.fillStyle = 'rgba(255, 255, 0, 0.4)';
|
|
393
|
+
points.slice(0, 200).forEach(p => {
|
|
394
|
+
this.debugCtx!.fillRect(p.x - 1, p.y - 1, 2, 2);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
371
397
|
}
|
|
372
398
|
|
|
373
399
|
export { SimpleAR };
|