Skip to content

Commit 8844dd4

Browse files
committed
CW_temp
1 parent a911ab7 commit 8844dd4

1 file changed

Lines changed: 292 additions & 0 deletions

File tree

hci.html

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Air Gesture Button Control</title>
8+
9+
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js"></script>
10+
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"></script>
11+
12+
<style>
13+
body {
14+
margin: 0;
15+
font-family: Arial, sans-serif;
16+
background: #212121;
17+
color: white;
18+
overflow: hidden;
19+
}
20+
21+
#video {
22+
position: absolute;
23+
transform: scaleX(-1);
24+
opacity: 0.25;
25+
max-width: 100%;
26+
height: 100%;
27+
}
28+
29+
#canvas {
30+
position: absolute;
31+
left: 0;
32+
top: 0;
33+
}
34+
35+
#cursor {
36+
position: absolute;
37+
width: 18px;
38+
height: 18px;
39+
border-radius: 50%;
40+
background: #df0000;
41+
pointer-events: none;
42+
}
43+
44+
#ui {
45+
position: fixed;
46+
top: 20px;
47+
left: 50%;
48+
transform: translateX(-50%);
49+
display: flex;
50+
gap: 20px;
51+
}
52+
53+
.button {
54+
padding: 15px 25px;
55+
background: #fff;
56+
color: #212121;
57+
border-radius: 5px;
58+
font-size: 18px;
59+
cursor: pointer;
60+
transition: background 0.3s ease;
61+
}
62+
63+
.button:hover {
64+
background: #f0f0f0;
65+
}
66+
67+
.active {
68+
background: #00df00;
69+
}
70+
71+
#status {
72+
position: fixed;
73+
bottom: 20px;
74+
left: 20px;
75+
background: rgba(0, 0, 0, 0.5);
76+
padding: 10px 15px;
77+
border-radius: 8px;
78+
font-size: 14px;
79+
}
80+
</style>
81+
</head>
82+
83+
<body>
84+
<video id="video" autoplay playsinline></video>
85+
<canvas id="canvas"></canvas>
86+
<div id="cursor"></div>
87+
88+
<div id="ui">
89+
<div id="btnBack" class="button">Back</div>
90+
<div id="btnForward" class="button">Forward</div>
91+
<div id="btnUp" class="button">Scroll Up</div>
92+
<div id="btnDown" class="button">Scroll Down</div>
93+
<div id="btnConfirm" class="button">Confirm</div>
94+
</div>
95+
96+
<div id="status">Status: Idle</div>
97+
98+
<script>
99+
const video = document.getElementById("video");
100+
const canvas = document.getElementById("canvas");
101+
const ctx = canvas.getContext("2d");
102+
const cursor = document.getElementById("cursor");
103+
const status = document.getElementById("status");
104+
105+
canvas.width = window.innerWidth;
106+
canvas.height = window.innerHeight;
107+
108+
let drawing = false;
109+
let path = [];
110+
let prevX = 0, prevY = 0;
111+
let idleTimeout = null;
112+
113+
const distance = (a, b) => Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
114+
115+
const setActiveButton = id => {
116+
document.querySelectorAll(".button").forEach(b => b.classList.remove("active"));
117+
document.getElementById(id).classList.add("active");
118+
};
119+
120+
const showToast = message => {
121+
const toast = document.createElement("div");
122+
toast.className = "toast";
123+
toast.innerText = message;
124+
toastContainer.appendChild(toast);
125+
126+
setTimeout(() => {
127+
toast.remove();
128+
}, 1000);
129+
};
130+
131+
const resetToIdle = () => {
132+
status.innerText = "Status: Idle";
133+
document.querySelectorAll(".button").forEach(b => b.classList.remove("active"));
134+
};
135+
136+
function recognizeGesture() {
137+
if (path.length < 8) {
138+
status.innerText = "Status: Not Recognized";
139+
return;
140+
}
141+
142+
const start = path[0];
143+
const end = path[path.length - 1];
144+
const dx = end.x - start.x;
145+
const dy = end.y - start.y;
146+
147+
// Horizontal
148+
if (Math.abs(dx) > 150 && Math.abs(dy) < 80) {
149+
if (dx > 0) {
150+
status.innerText = "Gesture: Forward";
151+
setActiveButton("btnForward");
152+
} else {
153+
status.innerText = "Gesture: Back";
154+
setActiveButton("btnBack");
155+
}
156+
return;
157+
}
158+
159+
// Vertical
160+
if (Math.abs(dy) > 150 && Math.abs(dx) < 80) {
161+
if (dy > 0) {
162+
status.innerText = "Gesture: Scroll Down";
163+
setActiveButton("btnDown");
164+
window.scrollBy(0, 300);
165+
} else {
166+
status.innerText = "Gesture: Scroll Up";
167+
setActiveButton("btnUp");
168+
window.scrollBy(0, -300);
169+
}
170+
return;
171+
}
172+
173+
// Circle
174+
if (isCircle(path)) {
175+
status.innerText = "Gesture: Confirm";
176+
setActiveButton("btnConfirm");
177+
return;
178+
}
179+
180+
status.innerText = "Status: Not Recognized";
181+
}
182+
183+
function isCircle(path) {
184+
let minX = Infinity, maxX = -Infinity;
185+
let minY = Infinity, maxY = -Infinity;
186+
187+
path.forEach(p => {
188+
minX = Math.min(minX, p.x);
189+
maxX = Math.max(maxX, p.x);
190+
minY = Math.min(minY, p.y);
191+
maxY = Math.max(maxY, p.y);
192+
});
193+
194+
const width = maxX - minX;
195+
const height = maxY - minY;
196+
197+
const start = path[0];
198+
const end = path[path.length - 1];
199+
const closeDist = Math.hypot(end.x - start.x, end.y - start.y);
200+
201+
if (closeDist > 100) return false;
202+
203+
let length = 0;
204+
for (let i = 1; i < path.length; i++) {
205+
length += Math.hypot(path[i].x - path[i - 1].x, path[i].y - path[i - 1].y);
206+
}
207+
208+
return true;
209+
}
210+
211+
function drawPath() {
212+
ctx.clearRect(0, 0, canvas.width, canvas.height);
213+
if (path.length < 2) return;
214+
215+
ctx.beginPath();
216+
ctx.moveTo(path[0].x, path[0].y);
217+
for (let i = 1; i < path.length; i++) {
218+
ctx.lineTo(path[i].x, path[i].y);
219+
}
220+
ctx.strokeStyle = "#00df00";
221+
ctx.lineWidth = 4;
222+
ctx.stroke();
223+
}
224+
225+
const hands = new Hands({
226+
locateFile: file => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`
227+
});
228+
229+
hands.setOptions({
230+
maxNumHands: 1,
231+
modelComplexity: 1,
232+
minDetectionConfidence: 0.7,
233+
minTrackingConfidence: 0.7
234+
});
235+
236+
hands.onResults(results => {
237+
if (!results.multiHandLandmarks.length) return;
238+
239+
const hand = results.multiHandLandmarks[0];
240+
const index = hand[8];
241+
const thumb = hand[4];
242+
243+
const x = (1 - index.x) * window.innerWidth;
244+
const y = index.y * window.innerHeight;
245+
246+
const smoothX = prevX * 0.7 + x * 0.3;
247+
const smoothY = prevY * 0.7 + y * 0.3;
248+
249+
prevX = smoothX;
250+
prevY = smoothY;
251+
252+
cursor.style.left = `${smoothX}px`;
253+
cursor.style.top = `${smoothY}px`;
254+
255+
const pinch = distance(index, thumb);
256+
257+
if (pinch < 0.05) {
258+
if (!drawing) {
259+
drawing = true;
260+
path = [];
261+
clearTimeout(idleTimeout);
262+
status.innerText = "Drawing gesture...";
263+
}
264+
path.push({ x: smoothX, y: smoothY });
265+
} else {
266+
if (drawing) {
267+
drawing = false;
268+
recognizeGesture();
269+
path = [];
270+
idleTimeout = setTimeout(resetToIdle, 1000);
271+
}
272+
}
273+
274+
drawPath();
275+
});
276+
277+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
278+
alert("Camera API not supported");
279+
} else {
280+
const camera = new Camera(video, {
281+
onFrame: async () => {
282+
await hands.send({ image: video });
283+
},
284+
width: 640,
285+
height: 480
286+
});
287+
camera.start();
288+
}
289+
</script>
290+
</body>
291+
292+
</html>

0 commit comments

Comments
 (0)