handspew / utils /handUtils.js
Tina Tarighian
initial
065d164
/**
* Utility functions for hand detection and drawing
*/
/**
* Draw hand landmarks on canvas
* @param {CanvasRenderingContext2D} ctx - Canvas context
* @param {Array} landmarks - Hand landmarks from MediaPipe
* @param {HTMLCanvasElement} canvas - Canvas element
* @param {boolean} isMobile - Whether the device is mobile
*/
export const drawLandmarks = (ctx, landmarks, canvas, isMobile) => {
// Draw connections
const connections = [
// Thumb
[0, 1], [1, 2], [2, 3], [3, 4],
// Index finger
[0, 5], [5, 6], [6, 7], [7, 8],
// Middle finger
[9, 10], [10, 11], [11, 12], [5, 9],
// Ring finger
[9, 13], [13, 14], [14, 15], [15, 16],
// Pinky
[13, 17], [17, 18], [18, 19], [19, 20],
// Palm
[0, 17]
];
// Use the requested colors
ctx.lineWidth = isMobile ? 2 : 3; // Thinner lines on mobile
ctx.strokeStyle = '#217BFE'; // Primary hand color
for (const [start, end] of connections) {
ctx.beginPath();
ctx.moveTo(landmarks[start].x * canvas.width, landmarks[start].y * canvas.height);
ctx.lineTo(landmarks[end].x * canvas.width, landmarks[end].y * canvas.height);
ctx.stroke();
}
// Draw landmarks with secondary color
ctx.fillStyle = '#AC87EB';
landmarks.forEach(landmark => {
ctx.beginPath();
ctx.arc(landmark.x * canvas.width, landmark.y * canvas.height, isMobile ? 3 : 4, 0, 2 * Math.PI);
ctx.fill();
});
// Highlight thumb and index finger
const thumbTip = landmarks[4];
const indexTip = landmarks[8];
ctx.fillStyle = '#217BFE';
[thumbTip, indexTip].forEach(tip => {
ctx.beginPath();
ctx.arc(tip.x * canvas.width, tip.y * canvas.height, isMobile ? 4 : 6, 0, 2 * Math.PI);
ctx.fill();
});
};
/**
* Check if hand is open (thumb away from index finger)
* @param {Array} landmarks - Hand landmarks from MediaPipe
* @returns {Object} - Object containing isOpen and other hand information
*/
export const analyzeHandGesture = (landmarks) => {
const thumbTip = landmarks[4]; // Thumb tip
const indexTip = landmarks[8]; // Index finger tip
const wrist = landmarks[0]; // Wrist
// Determine if it's a left or right hand
// If thumb is to the left of the wrist, it's likely a right hand
const isLeftHand = !(thumbTip.x < wrist.x);
// Calculate distance between thumb and index finger
const distance = Math.sqrt(
Math.pow(thumbTip.x - indexTip.x, 2) +
Math.pow(thumbTip.y - indexTip.y, 2) +
Math.pow(thumbTip.z - indexTip.z, 2)
);
// Hand is open when distance is greater than threshold
const isOpen = distance > 0.1;
return {
isOpen,
isLeftHand,
thumbPosition: {
x: thumbTip.x,
y: thumbTip.y
}
};
};
/**
* Create animation keyframes for hand detection animations
* @returns {string} - CSS keyframes string
*/
export const getAnimationKeyframes = () => {
return `
@keyframes thinking-blink {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(0.95); }
}
@keyframes spring-out {
0% {
transform: scale(0) translateY(0);
opacity: 0;
}
20% {
transform: scale(0.3) translateY(-5px);
opacity: 0.7;
}
40% {
transform: scale(1.5) translateY(-30px);
}
60% {
transform: scale(0.8) translateY(15px);
}
75% {
transform: scale(1.2) translateY(-10px);
}
90% {
transform: scale(0.95) translateY(5px);
}
100% {
transform: scale(1) translateY(0);
opacity: 1;
}
}
@keyframes wiggle {
0% { transform: rotate(0deg); }
15% { transform: rotate(-15deg); }
30% { transform: rotate(12deg); }
45% { transform: rotate(-8deg); }
60% { transform: rotate(5deg); }
75% { transform: rotate(-2deg); }
100% { transform: rotate(0deg); }
}
/* Combined animation that handles both scale and rotation */
@keyframes spring-wiggle {
0% {
transform: scale(0) rotate(0deg) translateY(0);
opacity: 0;
}
15% {
transform: scale(0.2) rotate(-5deg) translateY(-5px);
opacity: 0.5;
}
30% {
transform: scale(1.5) rotate(12deg) translateY(-30px);
opacity: 1;
}
45% {
transform: scale(0.8) rotate(-8deg) translateY(15px);
}
60% {
transform: scale(1.2) rotate(5deg) translateY(-10px);
}
75% {
transform: scale(0.95) rotate(-2deg) translateY(5px);
}
90% {
transform: scale(1.05) rotate(1deg) translateY(-2px);
}
100% {
transform: scale(1) rotate(0deg) translateY(0);
}
}
/* Add new animations for particles and popping */
@keyframes float-particle {
0% {
transform: translate(0, 0) rotate(0deg);
opacity: 1;
}
100% {
transform: translate(var(--tx), var(--ty)) rotate(var(--r));
opacity: 0;
}
}
@keyframes pop-out {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(0);
opacity: 0;
}
}
`;
};