fix: guard hand landmark visualization
This commit is contained in:
@@ -68,6 +68,13 @@ Server responds with detected hands:
|
|||||||
"x": 0.5,
|
"x": 0.5,
|
||||||
"y": 0.3,
|
"y": 0.3,
|
||||||
"z": 0.1,
|
"z": 0.1,
|
||||||
|
"landmarks": [
|
||||||
|
{
|
||||||
|
"x": 0.48,
|
||||||
|
"y": 0.32,
|
||||||
|
"z": 0.02
|
||||||
|
}
|
||||||
|
],
|
||||||
"handedness": "Right",
|
"handedness": "Right",
|
||||||
"isPinch": true,
|
"isPinch": true,
|
||||||
"pinchDistance": 0.05,
|
"pinchDistance": 0.05,
|
||||||
|
|||||||
@@ -19,16 +19,18 @@ class HandData:
|
|||||||
x: float
|
x: float
|
||||||
y: float
|
y: float
|
||||||
z: float
|
z: float
|
||||||
|
landmarks: list[dict[str, float]]
|
||||||
handedness: str
|
handedness: str
|
||||||
is_pinch: bool
|
is_pinch: bool
|
||||||
pinch_distance: float
|
pinch_distance: float
|
||||||
score: float
|
score: float
|
||||||
|
|
||||||
def to_payload(self) -> dict[str, float | str | bool]:
|
def to_payload(self) -> dict[str, float | str | bool | list[dict[str, float]]]:
|
||||||
return {
|
return {
|
||||||
"x": self.x,
|
"x": self.x,
|
||||||
"y": self.y,
|
"y": self.y,
|
||||||
"z": self.z,
|
"z": self.z,
|
||||||
|
"landmarks": self.landmarks,
|
||||||
"handedness": self.handedness,
|
"handedness": self.handedness,
|
||||||
"isPinch": self.is_pinch,
|
"isPinch": self.is_pinch,
|
||||||
"pinchDistance": self.pinch_distance,
|
"pinchDistance": self.pinch_distance,
|
||||||
@@ -86,6 +88,10 @@ class HandTracker:
|
|||||||
x=index_tip.x,
|
x=index_tip.x,
|
||||||
y=index_tip.y,
|
y=index_tip.y,
|
||||||
z=index_tip.z,
|
z=index_tip.z,
|
||||||
|
landmarks=[
|
||||||
|
{"x": point.x, "y": point.y, "z": point.z}
|
||||||
|
for point in landmarks
|
||||||
|
],
|
||||||
handedness=handedness.category_name,
|
handedness=handedness.category_name,
|
||||||
is_pinch=pinch_distance < 0.07,
|
is_pinch=pinch_distance < 0.07,
|
||||||
pinch_distance=pinch_distance,
|
pinch_distance=pinch_distance,
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { useHandTrackingSnapshot } from "@/hooks/useHandTrackingSnapshot";
|
||||||
|
|
||||||
|
const HAND_CONNECTIONS: Array<[number, number]> = [
|
||||||
|
[0, 1],
|
||||||
|
[1, 2],
|
||||||
|
[2, 3],
|
||||||
|
[3, 4],
|
||||||
|
[0, 5],
|
||||||
|
[5, 6],
|
||||||
|
[6, 7],
|
||||||
|
[7, 8],
|
||||||
|
[5, 9],
|
||||||
|
[9, 10],
|
||||||
|
[10, 11],
|
||||||
|
[11, 12],
|
||||||
|
[9, 13],
|
||||||
|
[13, 14],
|
||||||
|
[14, 15],
|
||||||
|
[15, 16],
|
||||||
|
[13, 17],
|
||||||
|
[17, 18],
|
||||||
|
[18, 19],
|
||||||
|
[19, 20],
|
||||||
|
[0, 17],
|
||||||
|
];
|
||||||
|
|
||||||
|
export function HandTrackingVisualizer(): React.JSX.Element | null {
|
||||||
|
const { hands, status } = useHandTrackingSnapshot();
|
||||||
|
|
||||||
|
if (status === "idle" || hands.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg className="hand-tracking-visualizer" aria-hidden="true">
|
||||||
|
{hands.map((hand, handIndex) => {
|
||||||
|
const landmarks = hand.landmarks ?? [];
|
||||||
|
if (landmarks.length === 0) return null;
|
||||||
|
|
||||||
|
const color = hand.isPinch ? "#facc15" : "#38bdf8";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g key={`${hand.handedness}-${handIndex}`}>
|
||||||
|
{HAND_CONNECTIONS.map(([from, to]) => {
|
||||||
|
const fromPoint = landmarks[from];
|
||||||
|
const toPoint = landmarks[to];
|
||||||
|
if (!fromPoint || !toPoint) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<line
|
||||||
|
key={`${from}-${to}`}
|
||||||
|
x1={`${(1 - fromPoint.x) * 100}%`}
|
||||||
|
y1={`${fromPoint.y * 100}%`}
|
||||||
|
x2={`${(1 - toPoint.x) * 100}%`}
|
||||||
|
y2={`${toPoint.y * 100}%`}
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{landmarks.map((landmark, landmarkIndex) => (
|
||||||
|
<circle
|
||||||
|
key={landmarkIndex}
|
||||||
|
cx={`${(1 - landmark.x) * 100}%`}
|
||||||
|
cy={`${landmark.y * 100}%`}
|
||||||
|
r={landmarkIndex === 8 ? 5 : 3}
|
||||||
|
fill={landmarkIndex === 8 ? "#ffffff" : color}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -418,6 +418,16 @@ canvas {
|
|||||||
color: #fca5a5;
|
color: #fca5a5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hand-tracking-visualizer {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 15;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
pointer-events: none;
|
||||||
|
filter: drop-shadow(0 0 8px rgba(56, 189, 248, 0.55));
|
||||||
|
}
|
||||||
|
|
||||||
/* Editor page */
|
/* Editor page */
|
||||||
.editor-container {
|
.editor-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Canvas } from "@react-three/fiber";
|
|||||||
import { Crosshair } from "@/components/ui/Crosshair";
|
import { Crosshair } from "@/components/ui/Crosshair";
|
||||||
import { HandTrackingOverlay } from "@/components/ui/HandTrackingOverlay";
|
import { HandTrackingOverlay } from "@/components/ui/HandTrackingOverlay";
|
||||||
import { HandTrackingProvider } from "@/components/ui/HandTrackingProvider";
|
import { HandTrackingProvider } from "@/components/ui/HandTrackingProvider";
|
||||||
|
import { HandTrackingVisualizer } from "@/components/ui/HandTrackingVisualizer";
|
||||||
import { InteractPrompt } from "@/components/ui/InteractPrompt";
|
import { InteractPrompt } from "@/components/ui/InteractPrompt";
|
||||||
import { DebugPerf } from "@/components/debug/DebugPerf";
|
import { DebugPerf } from "@/components/debug/DebugPerf";
|
||||||
import { World } from "@/world/World";
|
import { World } from "@/world/World";
|
||||||
@@ -18,6 +19,7 @@ export function HomePage(): React.JSX.Element {
|
|||||||
</Canvas>
|
</Canvas>
|
||||||
<Crosshair />
|
<Crosshair />
|
||||||
<InteractPrompt />
|
<InteractPrompt />
|
||||||
|
<HandTrackingVisualizer />
|
||||||
<HandTrackingOverlay />
|
<HandTrackingOverlay />
|
||||||
</HandTrackingProvider>
|
</HandTrackingProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
export interface HandTrackingLandmark {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface HandTrackingHand {
|
export interface HandTrackingHand {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
z: number;
|
z: number;
|
||||||
|
landmarks: HandTrackingLandmark[];
|
||||||
handedness: string;
|
handedness: string;
|
||||||
isPinch: boolean;
|
isPinch: boolean;
|
||||||
pinchDistance: number;
|
pinchDistance: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user