Spaces:
Sleeping
Sleeping
| // Landmarks -> performance controls. Every control is normalized [0,1] and | |
| // derived from in-plane geometry normalized by palm size, so it is invariant to | |
| // how close the hand is to the lens. Four INDEPENDENT controls per hand chosen so | |
| // they don't false-trigger each other: | |
| // HEIGHT (wrist y) -> the hero filter sweep | |
| // ROLL (wrist->middle_mcp tilt) -> turntable warp (tempo/pitch) | |
| // PINCH (thumb<->index) -> reverb / delay space [ignores m/r/pinky] | |
| // FIST (middle+ring+pinky curl) -> stutter buildup [ignores thumb/index] | |
| import { H, dist, norm, clamp } from "./math.js"; | |
| const palmSize = (lm) => dist(lm[H.WRIST], lm[H.MIDDLE_MCP]) || 1e-6; | |
| export function controls(lm) { | |
| const ps = palmSize(lm); | |
| // HEIGHT: 1 - wrist.y, mapped so a comfortable raise spans the full range. | |
| const height = norm(1 - lm[H.WRIST].y, 0.18, 0.82); | |
| // PINCH: thumb tip to index tip over palm. ~0.25 pinched … ~1.1 spread. | |
| // Tight pinch => high "space" send. | |
| const pinchRaw = dist(lm[H.THUMB_TIP], lm[H.INDEX_TIP]) / ps; | |
| const pinch = 1 - norm(pinchRaw, 0.28, 0.95); | |
| // FIST: how curled the middle/ring/pinky are (tip distance to wrist over palm). | |
| // open hand ~2.0, tight fist ~1.05 -> fist amount near 1. | |
| const curl = (dist(lm[H.MIDDLE_TIP], lm[H.WRIST]) + | |
| dist(lm[H.RING_TIP], lm[H.WRIST]) + | |
| dist(lm[H.PINKY_TIP], lm[H.WRIST])) / (3 * ps); | |
| const fist = 1 - norm(curl, 1.15, 1.95); | |
| // ROLL: tilt of the wrist->middle_mcp vector. 0=straight up; +right / -left. | |
| const dx = lm[H.MIDDLE_MCP].x - lm[H.WRIST].x; | |
| const dy = lm[H.MIDDLE_MCP].y - lm[H.WRIST].y; | |
| const rollAngle = Math.atan2(dx, -dy); // radians | |
| const roll = norm(rollAngle, -0.7, 0.7); // 0.5 = upright | |
| return { height, pinch, fist, roll, cx: clamp(lm[H.WRIST].x, 0, 1) }; | |
| } | |