Files
La-Fabrik/src/lib/handTracking/handSmoothing.ts
T

65 lines
1.9 KiB
TypeScript

import type {
HandTrackingHand,
HandTrackingLandmark,
} from "@/types/handTracking/handTracking";
function lerp(previous: number, next: number, factor: number): number {
return previous * (1 - factor) + next * factor;
}
function smoothLandmark(
previous: HandTrackingLandmark,
next: HandTrackingLandmark,
factor: number,
): HandTrackingLandmark {
return {
x: lerp(previous.x, next.x, factor),
y: lerp(previous.y, next.y, factor),
z: lerp(previous.z, next.z, factor),
};
}
function smoothHand(
previous: HandTrackingHand,
next: HandTrackingHand,
factor: number,
): HandTrackingHand {
return {
...next,
x: lerp(previous.x, next.x, factor),
y: lerp(previous.y, next.y, factor),
z: lerp(previous.z, next.z, factor),
landmarks: next.landmarks.map((landmark, index) => {
const previousLandmark = previous.landmarks[index];
if (!previousLandmark) return landmark;
return smoothLandmark(previousLandmark, landmark, factor);
}),
};
}
/**
* Apply an exponential moving average to the landmark positions of each
* detected hand. MediaPipe lands per-frame positions with noticeable
* jitter (especially at ~10fps), and feeding those raw values into the
* scene makes both the glove rig and any grabbed object tremble.
*
* `factor` is the weight given to the latest sample (0 = previous frame
* only, 1 = no smoothing). Hands are matched between frames by
* handedness so left/right don't bleed into each other.
*/
export function smoothHands(
previousHands: HandTrackingHand[],
nextHands: HandTrackingHand[],
factor: number,
): HandTrackingHand[] {
if (factor >= 1) return nextHands;
return nextHands.map((next) => {
const previous = previousHands.find(
(candidate) => candidate.handedness === next.handedness,
);
if (!previous) return next;
return smoothHand(previous, next, factor);
});
}