Particle3d_v004 / index.html
JamesToth's picture
Update index.html
c754822 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Omni-Hand Diagnostic Mode</title>
<style>
body {
margin: 0; overflow: hidden; background-color: #050505;
font-family: 'Segoe UI', monospace; user-select: none;
}
/* HUD - Top Left */
#hud {
position: absolute; top: 20px; left: 20px;
width: 280px; padding: 20px;
background: rgba(20, 20, 20, 0.9);
border-left: 4px solid #00ff88;
border-radius: 4px;
color: #fff; z-index: 10;
}
.row { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 8px; color: #ccc; }
.val { font-weight: bold; color: #fff; }
.highlight { color: #00ff88; text-shadow: 0 0 10px rgba(0,255,136,0.5); }
#gesture-display {
font-size: 20px; text-align: center; margin-top: 15px;
padding-top: 15px; border-top: 1px solid #333;
color: #666; font-weight: 300;
}
/* DEBUG VIEW - Bottom Right */
#debug-container {
position: absolute; bottom: 20px; right: 20px;
width: 320px; height: 240px;
background: #000;
border: 2px solid #333;
z-index: 100;
}
#debug-video { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); opacity: 0.6; }
#debug-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; transform: scaleX(-1); }
#debug-label {
position: absolute; top: 0; left: 0; background: red; color: white;
font-size: 10px; padding: 2px 5px;
}
#loading {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
color: #00ff88; font-size: 24px; text-transform: uppercase; letter-spacing: 4px;
text-shadow: 0 0 20px #00ff88;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js"></script>
</head>
<body>
<div id="loading">Loading AI Engine...</div>
<div id="hud">
<div style="font-size: 10px; color: #888; margin-bottom: 10px;">DIAGNOSTIC MODE</div>
<div class="row"><span>HAND TRACKING</span><span id="ui-track" class="val" style="color:red">OFFLINE</span></div>
<div class="row"><span>SHAPE</span><span id="ui-shape" class="val">GALAXY</span></div>
<div class="row"><span>PARTICLES</span><span class="val">12,000</span></div>
<div id="gesture-display">WAITING...</div>
</div>
<div id="debug-container">
<div id="debug-label">CAMERA FEED</div>
<video id="debug-video" playsinline></video>
<canvas id="debug-canvas" width="320" height="240"></canvas>
</div>
<script>
// --- CONFIG ---
const CONFIG = {
count: 12000,
camWidth: 640,
camHeight: 480
};
const State = {
active: false,
hand: { x: 0, y: 0, z: 0 },
gesture: 'NONE',
shapeIdx: 0,
lastGestureTime: 0
};
// --- THREE.JS SETUP ---
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x050505, 0.04);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 100);
camera.position.z = 10;
const renderer = new THREE.WebGLRenderer({ antialias: false });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Particles
const geo = new THREE.BufferGeometry();
const pos = new Float32Array(CONFIG.count * 3);
const tar = new Float32Array(CONFIG.count * 3);
const vel = new Float32Array(CONFIG.count * 3); // Velocity
for(let i=0; i<CONFIG.count*3; i++) {
pos[i] = (Math.random()-0.5)*30;
tar[i] = pos[i];
vel[i] = 0;
}
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
// Texture
const cvs = document.createElement('canvas'); cvs.width=32; cvs.height=32;
const ctx = cvs.getContext('2d');
const grd = ctx.createRadialGradient(16,16,0,16,16,16);
grd.addColorStop(0,'white'); grd.addColorStop(1,'transparent');
ctx.fillStyle = grd; ctx.fillRect(0,0,32,32);
const tex = new THREE.CanvasTexture(cvs);
const mat = new THREE.PointsMaterial({
size: 0.15, map: tex, color: 0x00ff88,
transparent: true, opacity: 0.8, blending: THREE.AdditiveBlending, depthWrite: false
});
const particles = new THREE.Points(geo, mat);
scene.add(particles);
// --- SHAPES ---
const Shapes = [
(i) => { // Galaxy
const r = (i/CONFIG.count)*8; const a = (i/CONFIG.count)*20;
return {x: Math.cos(a)*r, y: (Math.random()-0.5), z: Math.sin(a)*r};
},
(i) => { // Sphere
const p = Math.acos(-1+(2*i)/CONFIG.count); const t = Math.sqrt(CONFIG.count*Math.PI)*p;
return {x: 4*Math.sin(p)*Math.cos(t), y: 4*Math.sin(p)*Math.sin(t), z: 4*Math.cos(p)};
}
];
function updateTargets() {
const func = Shapes[State.shapeIdx];
for(let i=0; i<CONFIG.count; i++) {
const p = func(i);
tar[i*3] = p.x; tar[i*3+1] = p.y; tar[i*3+2] = p.z;
}
}
updateTargets();
// --- GESTURE LOGIC (RELAXED) ---
function detectGesture(lm) {
// Tips: 8(Idx), 12(Mid), 16(Rng), 20(Pnk) | PIPs: 6, 10, 14, 18
// 0 = Wrist
// Helper: Is finger extended? (Tip higher than PIP)
const idxUp = lm[8].y < lm[6].y;
const midUp = lm[12].y < lm[10].y;
const rngUp = lm[16].y < lm[14].y;
const pnkUp = lm[20].y < lm[18].y;
// Helper: Distance
const pinchDist = Math.hypot(lm[4].x - lm[8].x, lm[4].y - lm[8].y);
const fistDist = Math.hypot(lm[0].x - lm[12].x, lm[0].y - lm[12].y); // Wrist to mid-tip
let g = "UNKNOWN";
// Logic Tree
if (fistDist < 0.25 && !idxUp) g = "FIST (GRAVITY)"; // Relaxed threshold
else if (pinchDist < 0.08) g = "PINCH (ZOOM)";
else if (idxUp && pnkUp && !midUp) g = "ROCK (TRAILS)";
else if (idxUp && midUp && !rngUp) g = "PEACE (CHAOS)";
else if (idxUp && !midUp && !pnkUp) g = "INDEX (CURSOR)";
else if (!idxUp && !midUp && pnkUp) g = "PINKY (VORTEX)";
else if (idxUp && midUp && rngUp && pnkUp) g = "OPEN (SHIELD)";
return g;
}
// --- MEDIAPIPE SETUP ---
const videoElem = document.getElementById('debug-video');
const debugCanvas = document.getElementById('debug-canvas');
const debugCtx = debugCanvas.getContext('2d');
const uiTrack = document.getElementById('ui-track');
const uiGesture = document.getElementById('gesture-display');
function onResults(results) {
document.getElementById('loading').style.display = 'none';
// Debug Draw
debugCtx.save();
debugCtx.clearRect(0, 0, debugCanvas.width, debugCanvas.height);
if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
State.active = true;
uiTrack.innerText = "ONLINE";
uiTrack.style.color = "#00ff88";
const lm = results.multiHandLandmarks[0];
// DRAW SKELETON (Crucial for Debug)
drawConnectors(debugCtx, lm, HAND_CONNECTIONS, {color: '#00ff00', lineWidth: 2});
drawLandmarks(debugCtx, lm, {color: '#ff0000', lineWidth: 1, radius: 2});
// Map Position
const x = (1 - lm[9].x) * 16 - 8;
const y = -(lm[9].y - 0.5) * 12;
State.hand.x += (x - State.hand.x) * 0.2;
State.hand.y += (y - State.hand.y) * 0.2;
// Detect
const g = detectGesture(lm);
if(g !== State.gesture) {
State.gesture = g;
uiGesture.innerHTML = `<span class="highlight">${g}</span>`;
// Swipe Logic
if(g === "INDEX (CURSOR)" && Math.abs(x - State.hand.x) > 0.5) {
// Simple logic for swipe
}
}
} else {
State.active = false;
uiTrack.innerText = "NO HAND";
uiTrack.style.color = "red";
uiGesture.innerText = "SHOW HAND";
}
debugCtx.restore();
}
const hands = new Hands({locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`});
hands.setOptions({maxNumHands: 1, minDetectionConfidence: 0.5, minTrackingConfidence: 0.5});
hands.onResults(onResults);
const cam = new Camera(videoElem, {
onFrame: async () => { await hands.send({image: videoElem}); },
width: 320, height: 240
});
cam.start();
// --- ANIMATION LOOP ---
function animate() {
requestAnimationFrame(animate);
// Physics
for(let i=0; i<CONFIG.count; i++) {
const i3 = i*3;
let tx = tar[i3]; let ty = tar[i3+1]; let tz = tar[i3+2];
if(State.active) {
const dx = pos[i3] - State.hand.x;
const dy = pos[i3+1] - State.hand.y;
const dist = Math.sqrt(dx*dx + dy*dy);
if(State.gesture.includes("FIST") && dist < 10) {
tx -= dx*0.5; ty -= dy*0.5; // Suck
} else if(State.gesture.includes("SHIELD") && dist < 6) {
tx += dx*0.5; ty += dy*0.5; // Push
}
}
vel[i3] += (tx - pos[i3])*0.05;
vel[i3+1] += (ty - pos[i3+1])*0.05;
vel[i3+2] += (tz - pos[i3+2])*0.05;
vel[i3] *= 0.9; vel[i3+1] *= 0.9; vel[i3+2] *= 0.9;
pos[i3] += vel[i3]; pos[i3+1] += vel[i3+1]; pos[i3+2] += vel[i3+2];
}
geo.attributes.position.needsUpdate = true;
renderer.render(scene, camera);
}
animate();
window.onresize = () => {
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
};
</script>
</body>
</html>