forked from fofr/face_looker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathface-tracker.js
More file actions
89 lines (71 loc) · 2.64 KB
/
face-tracker.js
File metadata and controls
89 lines (71 loc) · 2.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// Grid configuration (must match your generated images)
const P_MIN = -15;
const P_MAX = 15;
const STEP = 3;
const SIZE = 256;
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function quantizeToGrid(val) {
const raw = P_MIN + (val + 1) * (P_MAX - P_MIN) / 2; // [-1,1] -> [-15,15]
const snapped = Math.round(raw / STEP) * STEP;
return clamp(snapped, P_MIN, P_MAX);
}
function sanitize(val) {
const str = Number(val).toFixed(1); // force one decimal, e.g. 0 -> 0.0
return str.replace('-', 'm').replace('.', 'p');
}
function gridToFilename(px, py) {
return `gaze_px${sanitize(px)}_py${sanitize(py)}_${SIZE}.webp`;
}
function updateDebug(debugEl, x, y, filename) {
if (!debugEl) return;
debugEl.innerHTML = `Mouse: (${Math.round(x)}, ${Math.round(y)})<br/>Image: ${filename}`;
}
function initializeFaceTracker(container) {
const basePath = container.dataset.basePath || '/faces/';
const showDebug = String(container.dataset.debug || 'false') === 'true';
const img = document.createElement('img');
img.className = 'face-image';
img.alt = 'Face following gaze';
container.appendChild(img);
let debugEl = null;
if (showDebug) {
debugEl = document.createElement('div');
debugEl.className = 'face-debug';
container.appendChild(debugEl);
}
function setFromClient(clientX, clientY) {
const rect = container.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const nx = (clientX - centerX) / (rect.width / 2);
const ny = (centerY - clientY) / (rect.height / 2);
const clampedX = clamp(nx, -1, 1);
const clampedY = clamp(ny, -1, 1);
const px = quantizeToGrid(clampedX);
const py = quantizeToGrid(clampedY);
const filename = gridToFilename(px, py);
const imagePath = `${basePath}${filename}`;
img.src = imagePath;
updateDebug(debugEl, clientX - rect.left, clientY - rect.top, filename);
}
function handleMouseMove(e) {
setFromClient(e.clientX, e.clientY);
}
function handleTouchMove(e) {
if (e.touches && e.touches.length > 0) {
const t = e.touches[0];
setFromClient(t.clientX, t.clientY);
}
}
// Track pointer anywhere on the page
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('touchmove', handleTouchMove, { passive: true });
// Initialize at center
const rect = container.getBoundingClientRect();
setFromClient(rect.left + rect.width / 2, rect.top + rect.height / 2);
}
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.face-tracker').forEach((el) => initializeFaceTracker(el));
});