fix hand tracking glove fallback and loading

This commit is contained in:
Tom Boullay
2026-05-02 11:35:28 +02:00
parent fe662ebe7d
commit ac7f60060c
4 changed files with 35 additions and 29 deletions
Binary file not shown.
@@ -13,19 +13,28 @@ import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
import type { HandTrackingLandmark } from "@/types/handTracking/handTracking"; import type { HandTrackingLandmark } from "@/types/handTracking/handTracking";
import { logModelLoadError } from "@/utils/three/modelLoadLogger"; import { logModelLoadError } from "@/utils/three/modelLoadLogger";
const GLOVE_MODEL_PATHS: Record<HandTrackingGloveHandedness, string> = { const GLOVE_CONFIGS: Record<
left: "/models/gant_l/model.gltf", HandTrackingGloveHandedness,
right: "/models/gant_r/model.gltf", {
}; modelPath: string;
rootNodeName: string;
const GLOVE_ROOT_NODE_NAMES: Record<HandTrackingGloveHandedness, string> = { scale: number;
left: "Hand_l", }
right: "Hand_r", > = {
left: {
modelPath: "/models/gant_l/model.gltf",
rootNodeName: "Armature",
scale: 0.17,
},
right: {
modelPath: "/models/gant_r/model.gltf",
rootNodeName: "Hand_r",
scale: 0.04,
},
}; };
const HAND_SPACE_DISTANCE = 2.4; const HAND_SPACE_DISTANCE = 2.4;
const HAND_DEPTH_SCALE = 0.45; const HAND_DEPTH_SCALE = 0.45;
const GLOVE_SCALE = 0.17;
const _cameraPosition = new THREE.Vector3(); const _cameraPosition = new THREE.Vector3();
const _direction = new THREE.Vector3(); const _direction = new THREE.Vector3();
@@ -73,7 +82,7 @@ class HandTrackingGloveErrorBoundary extends Component<
{ {
modelPath: this.props.modelPath, modelPath: this.props.modelPath,
scope: `HandTrackingGlove.${this.props.handedness}`, scope: `HandTrackingGlove.${this.props.handedness}`,
scale: GLOVE_SCALE, scale: GLOVE_CONFIGS[this.props.handedness].scale,
}, },
error, error,
); );
@@ -122,24 +131,21 @@ function HandTrackingGloveModel({
const setGloveStatus = useHandTrackingGloveStatus( const setGloveStatus = useHandTrackingGloveStatus(
(state) => state.setGloveStatus, (state) => state.setGloveStatus,
); );
const modelPath = GLOVE_MODEL_PATHS[handedness]; const config = GLOVE_CONFIGS[handedness];
const modelPath = config.modelPath;
const gltf = useLoggedGLTF(modelPath, { const gltf = useLoggedGLTF(modelPath, {
scope: `HandTrackingGlove.${handedness}`, scope: `HandTrackingGlove.${handedness}`,
scale: GLOVE_SCALE, scale: config.scale,
}); });
const gloveScene = useMemo(() => { const gloveScene = useMemo(() => {
const rootNode = gltf.scene.getObjectByName( const rootNode = gltf.scene.getObjectByName(config.rootNodeName);
GLOVE_ROOT_NODE_NAMES[handedness],
);
if (!rootNode) { if (!rootNode) {
throw new Error( throw new Error(`Missing glove root node ${config.rootNodeName}`);
`Missing glove root node ${GLOVE_ROOT_NODE_NAMES[handedness]}`,
);
} }
return clone(rootNode); return clone(rootNode);
}, [gltf.scene, handedness]); }, [config.rootNodeName, gltf.scene]);
const hand = hands.find((candidate) => const hand = hands.find((candidate) =>
matchesHandedness(candidate.handedness, handedness), matchesHandedness(candidate.handedness, handedness),
@@ -207,7 +213,7 @@ function HandTrackingGloveModel({
group.quaternion.slerp(_targetQuaternion, Math.min(1, delta * 18)); group.quaternion.slerp(_targetQuaternion, Math.min(1, delta * 18));
const palmLength = _wristPosition.distanceTo(_middlePosition); const palmLength = _wristPosition.distanceTo(_middlePosition);
const scale = palmLength * GLOVE_SCALE; const scale = palmLength * config.scale;
group.scale.setScalar(scale); group.scale.setScalar(scale);
}); });
@@ -219,7 +225,7 @@ function HandTrackingGloveModel({
export function HandTrackingGlove({ export function HandTrackingGlove({
handedness, handedness,
}: HandTrackingGloveProps): React.JSX.Element { }: HandTrackingGloveProps): React.JSX.Element {
const modelPath = GLOVE_MODEL_PATHS[handedness]; const modelPath = GLOVE_CONFIGS[handedness].modelPath;
return ( return (
<HandTrackingGloveErrorBoundary <HandTrackingGloveErrorBoundary
@@ -231,5 +237,5 @@ export function HandTrackingGlove({
); );
} }
useGLTF.preload(GLOVE_MODEL_PATHS.left); useGLTF.preload(GLOVE_CONFIGS.left.modelPath);
useGLTF.preload(GLOVE_MODEL_PATHS.right); useGLTF.preload(GLOVE_CONFIGS.right.modelPath);
+3 -3
View File
@@ -28,11 +28,11 @@ const HAND_CONNECTIONS: Array<[number, number]> = [
export function HandTrackingVisualizer(): React.JSX.Element | null { export function HandTrackingVisualizer(): React.JSX.Element | null {
const { hands, status } = useHandTrackingSnapshot(); const { hands, status } = useHandTrackingSnapshot();
const gloves = useHandTrackingGloveStatus((state) => state.gloves); const gloves = useHandTrackingGloveStatus((state) => state.gloves);
const shouldShowSvgFallback = Object.values(gloves).some( const hasLoadedGlove = Object.values(gloves).some(
(gloveStatus) => gloveStatus === "error" || gloveStatus === "idle", (gloveStatus) => gloveStatus === "loaded",
); );
if (status === "idle" || hands.length === 0 || !shouldShowSvgFallback) { if (status === "idle" || hands.length === 0 || hasLoadedGlove) {
return null; return null;
} }
@@ -30,8 +30,8 @@ export function HandTrackingDebugPanel(): React.JSX.Element | null {
] ]
.filter(Boolean) .filter(Boolean)
.join(", ") || "none"; .join(", ") || "none";
const modelFallback = Object.values(gloves).some( const modelFallback = !Object.values(gloves).some(
(gloveStatus) => gloveStatus === "error", (gloveStatus) => gloveStatus === "loaded",
); );
return ( return (