refactor: replace pinch gesture with fist gesture

This commit is contained in:
Tom Boullay
2026-04-29 10:34:11 +02:00
parent 28e3ac4c06
commit cc4c11f934
9 changed files with 69 additions and 26 deletions
+5 -5
View File
@@ -92,14 +92,14 @@ export function GrabbableObject({
useFrame(() => {
if (!rbRef.current) return;
const pinchingHand = handControlled
? hands.find((hand) => hand.isPinch)
const fistHand = handControlled
? hands.find((hand) => hand.isFist)
: undefined;
if (!isHolding.current && !pinchingHand) return;
if (!isHolding.current && !fistHand) return;
if (pinchingHand) {
_handNdc.set((1 - pinchingHand.x) * 2 - 1, -pinchingHand.y * 2 + 1, 0.5);
if (fistHand) {
_handNdc.set((1 - fistHand.x) * 2 - 1, -fistHand.y * 2 + 1, 0.5);
_handNdc.unproject(camera);
_handDirection.subVectors(_handNdc, camera.position).normalize();
_holdTarget
+5 -3
View File
@@ -13,21 +13,23 @@ const STATUS_LABELS: Record<HandTrackingStatus, string> = {
};
export function HandTrackingOverlay(): React.JSX.Element | null {
const { hands, status, serverStatus, error } = useHandTrackingSnapshot();
const { hands, status, usageStatus, serverStatus, error } =
useHandTrackingSnapshot();
if (status === "idle") {
return null;
}
const pinching = hands.some((hand) => hand.isPinch);
const fist = hands.some((hand) => hand.isFist);
return (
<aside className="hand-tracking-overlay" aria-label="Hand tracking status">
<strong>Hand tracking</strong>
<span>Status: {STATUS_LABELS[status]}</span>
<span>Usage: {usageStatus}</span>
{serverStatus ? <span>Server: {serverStatus}</span> : null}
<span>Hands: {hands.length}</span>
<span>Pinch: {pinching ? "yes" : "no"}</span>
<span>Fist: {fist ? "yes" : "no"}</span>
{error ? (
<span className="hand-tracking-overlay__error">{error}</span>
) : null}
+5 -1
View File
@@ -1,5 +1,6 @@
import type { ReactNode } from "react";
import { useSceneMode } from "@/hooks/debug/useSceneMode";
import { useInteraction } from "@/hooks/useInteraction";
import {
HAND_TRACKING_IDLE_SNAPSHOT,
HandTrackingContext,
@@ -13,7 +14,10 @@ export function HandTrackingProvider({
children: ReactNode;
}): React.JSX.Element {
const sceneMode = useSceneMode();
const enabled = isDebugEnabled() && sceneMode === "physics";
const { focused, holding } = useInteraction();
const isInInteractionZone = focused !== null || holding;
const enabled =
isDebugEnabled() && sceneMode === "physics" && isInInteractionZone;
const snapshot = useRemoteHandTracking({ enabled });
return (
+1 -1
View File
@@ -37,7 +37,7 @@ export function HandTrackingVisualizer(): React.JSX.Element | null {
const landmarks = hand.landmarks ?? [];
if (landmarks.length === 0) return null;
const color = hand.isPinch ? "#facc15" : "#38bdf8";
const color = hand.isFist ? "#facc15" : "#38bdf8";
return (
<g key={`${hand.handedness}-${handIndex}`}>
+1
View File
@@ -4,6 +4,7 @@ import type { HandTrackingSnapshot } from "@/types/handTracking";
export const HAND_TRACKING_IDLE_SNAPSHOT: HandTrackingSnapshot = {
hands: [],
status: "idle",
usageStatus: "inactive",
serverStatus: null,
error: null,
};
+8
View File
@@ -22,6 +22,7 @@ interface UseRemoteHandTrackingOptions {
const INITIAL_SNAPSHOT: HandTrackingSnapshot = {
hands: [],
status: "idle",
usageStatus: "inactive",
serverStatus: null,
error: null,
};
@@ -144,6 +145,7 @@ export function useRemoteHandTracking({
setSnapshot({
hands: [],
status: "requesting_camera",
usageStatus: "available",
serverStatus: null,
error: null,
});
@@ -193,6 +195,7 @@ export function useRemoteHandTracking({
setSnapshot((current) => ({
...current,
status: "connected",
usageStatus: "available",
error: null,
}));
};
@@ -204,6 +207,9 @@ export function useRemoteHandTracking({
setSnapshot((current) => ({
...current,
hands: data.hands,
usageStatus: data.hands.some((hand) => hand.isFist)
? "active"
: "available",
serverStatus: null,
error: null,
}));
@@ -222,6 +228,7 @@ export function useRemoteHandTracking({
...current,
hands: [],
status: "error",
usageStatus: "inactive",
error: data.message,
}));
};
@@ -254,6 +261,7 @@ export function useRemoteHandTracking({
setSnapshot({
hands: [],
status: "error",
usageStatus: "inactive",
serverStatus: null,
error:
error instanceof Error ? error.message : "Hand tracking failed",
+4 -2
View File
@@ -10,11 +10,12 @@ export interface HandTrackingHand {
z: number;
landmarks: HandTrackingLandmark[];
handedness: string;
isPinch: boolean;
pinchDistance: number;
isFist: boolean;
score: number;
}
export type HandTrackingUsageStatus = "inactive" | "available" | "active";
export type HandTrackingStatus =
| "idle"
| "requesting_camera"
@@ -28,6 +29,7 @@ export type HandTrackingStatus =
export interface HandTrackingSnapshot {
hands: HandTrackingHand[];
status: HandTrackingStatus;
usageStatus: HandTrackingUsageStatus;
serverStatus: string | null;
error: string | null;
}