fix: flickering hands
This commit is contained in:
@@ -18,23 +18,22 @@ const GLOVE_CONFIGS: Record<
|
|||||||
{
|
{
|
||||||
modelPath: string;
|
modelPath: string;
|
||||||
rootNodeName: string;
|
rootNodeName: string;
|
||||||
scale: number;
|
|
||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
left: {
|
left: {
|
||||||
modelPath: "/models/gant_l/model.gltf",
|
modelPath: "/models/gant_l/model.gltf",
|
||||||
rootNodeName: "Armature",
|
rootNodeName: "Armature",
|
||||||
scale: 0.17,
|
|
||||||
},
|
},
|
||||||
right: {
|
right: {
|
||||||
modelPath: "/models/gant_r/model.gltf",
|
modelPath: "/models/gant_r/model.gltf",
|
||||||
rootNodeName: "Hand_r",
|
rootNodeName: "Hand_r",
|
||||||
scale: 0.04,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const HAND_SPACE_DISTANCE = 2.4;
|
const GLOVE_MODEL_SCALE = 0.33;
|
||||||
const HAND_DEPTH_SCALE = 0.45;
|
const HAND_SPACE_DISTANCE = 0.5;
|
||||||
|
const HAND_DEPTH_SCALE = 0.5;
|
||||||
|
const HAND_TRACKING_HIDE_DELAY_MS = 250;
|
||||||
|
|
||||||
const FINGER_LANDMARK_CHAINS = [
|
const FINGER_LANDMARK_CHAINS = [
|
||||||
[0, 1, 2, 3, 4],
|
[0, 1, 2, 3, 4],
|
||||||
@@ -104,7 +103,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_CONFIGS[this.props.handedness].scale,
|
scale: GLOVE_MODEL_SCALE,
|
||||||
},
|
},
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
@@ -252,8 +251,9 @@ function HandTrackingGloveModel({
|
|||||||
const modelPath = config.modelPath;
|
const modelPath = config.modelPath;
|
||||||
const gltf = useLoggedGLTF(modelPath, {
|
const gltf = useLoggedGLTF(modelPath, {
|
||||||
scope: `HandTrackingGlove.${handedness}`,
|
scope: `HandTrackingGlove.${handedness}`,
|
||||||
scale: config.scale,
|
scale: GLOVE_MODEL_SCALE,
|
||||||
});
|
});
|
||||||
|
const lastTrackedAtRef = useRef<number | null>(null);
|
||||||
const gloveScene = useMemo(() => {
|
const gloveScene = useMemo(() => {
|
||||||
const rootNode = gltf.scene.getObjectByName(config.rootNodeName);
|
const rootNode = gltf.scene.getObjectByName(config.rootNodeName);
|
||||||
|
|
||||||
@@ -261,17 +261,16 @@ function HandTrackingGloveModel({
|
|||||||
throw new Error(`Missing glove root node ${config.rootNodeName}`);
|
throw new Error(`Missing glove root node ${config.rootNodeName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return clone(rootNode);
|
const clonedRootNode = clone(rootNode);
|
||||||
|
clonedRootNode.visible = false;
|
||||||
|
|
||||||
|
return clonedRootNode;
|
||||||
}, [config.rootNodeName, gltf.scene]);
|
}, [config.rootNodeName, gltf.scene]);
|
||||||
const fingerPoseChains = useMemo(
|
const fingerPoseChains = useMemo(
|
||||||
() => createFingerPoseChains(gloveScene),
|
() => createFingerPoseChains(gloveScene),
|
||||||
[gloveScene],
|
[gloveScene],
|
||||||
);
|
);
|
||||||
|
|
||||||
const hand = hands.find((candidate) =>
|
|
||||||
matchesHandedness(candidate.handedness, handedness),
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setGloveStatus(handedness, "loaded");
|
setGloveStatus(handedness, "loaded");
|
||||||
}, [handedness, setGloveStatus]);
|
}, [handedness, setGloveStatus]);
|
||||||
@@ -282,12 +281,23 @@ function HandTrackingGloveModel({
|
|||||||
matchesHandedness(candidate.handedness, handedness),
|
matchesHandedness(candidate.handedness, handedness),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!group || !trackedHand || trackedHand.landmarks.length < 21) {
|
if (!group) return;
|
||||||
if (group) group.visible = false;
|
|
||||||
resetFingerPose(fingerPoseChains);
|
if (!trackedHand || trackedHand.landmarks.length < 21) {
|
||||||
|
const lastTrackedAt = lastTrackedAtRef.current;
|
||||||
|
const shouldHide =
|
||||||
|
lastTrackedAt === null ||
|
||||||
|
performance.now() - lastTrackedAt > HAND_TRACKING_HIDE_DELAY_MS;
|
||||||
|
|
||||||
|
if (shouldHide) {
|
||||||
|
group.visible = false;
|
||||||
|
resetFingerPose(fingerPoseChains);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastTrackedAtRef.current = performance.now();
|
||||||
group.visible = true;
|
group.visible = true;
|
||||||
|
|
||||||
const wrist = trackedHand.landmarks[0];
|
const wrist = trackedHand.landmarks[0];
|
||||||
@@ -335,14 +345,12 @@ 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 * config.scale;
|
const scale = palmLength * GLOVE_MODEL_SCALE;
|
||||||
group.scale.setScalar(scale);
|
group.scale.setScalar(scale);
|
||||||
group.updateMatrixWorld(true);
|
group.updateMatrixWorld(true);
|
||||||
applyFingerPose(fingerPoseChains, trackedHand.landmarks, camera);
|
applyFingerPose(fingerPoseChains, trackedHand.landmarks, camera);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!hand) return null;
|
|
||||||
|
|
||||||
return <primitive ref={groupRef} object={gloveScene} />;
|
return <primitive ref={groupRef} object={gloveScene} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot";
|
import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot";
|
||||||
import { useHandTrackingGloveStatus } from "@/hooks/handTracking/useHandTrackingGloveStatus";
|
import { useHandTrackingGloveStatus } from "@/hooks/handTracking/useHandTrackingGloveStatus";
|
||||||
|
import { useDebugStore } from "@/hooks/debug/useDebugStore";
|
||||||
|
|
||||||
const HAND_CONNECTIONS: Array<[number, number]> = [
|
const HAND_CONNECTIONS: Array<[number, number]> = [
|
||||||
[0, 1],
|
[0, 1],
|
||||||
@@ -27,12 +28,19 @@ 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 showHandTrackingSvg = useDebugStore((debug) =>
|
||||||
|
debug.getShowHandTrackingSvg(),
|
||||||
|
);
|
||||||
const gloves = useHandTrackingGloveStatus((state) => state.gloves);
|
const gloves = useHandTrackingGloveStatus((state) => state.gloves);
|
||||||
const hasLoadedGlove = Object.values(gloves).some(
|
const hasLoadedGlove = Object.values(gloves).some(
|
||||||
(gloveStatus) => gloveStatus === "loaded",
|
(gloveStatus) => gloveStatus === "loaded",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (status === "idle" || hands.length === 0 || hasLoadedGlove) {
|
if (
|
||||||
|
status === "idle" ||
|
||||||
|
hands.length === 0 ||
|
||||||
|
(hasLoadedGlove && !showHandTrackingSvg)
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export class Debug {
|
|||||||
private readonly controls: {
|
private readonly controls: {
|
||||||
cameraMode: CameraMode;
|
cameraMode: CameraMode;
|
||||||
showDebugOverlay: boolean;
|
showDebugOverlay: boolean;
|
||||||
|
showHandTrackingSvg: boolean;
|
||||||
showInteractionSpheres: boolean;
|
showInteractionSpheres: boolean;
|
||||||
showPerf: boolean;
|
showPerf: boolean;
|
||||||
sceneMode: SceneMode;
|
sceneMode: SceneMode;
|
||||||
@@ -73,6 +74,7 @@ export class Debug {
|
|||||||
this.controls = {
|
this.controls = {
|
||||||
cameraMode: storedControls.cameraMode ?? "player",
|
cameraMode: storedControls.cameraMode ?? "player",
|
||||||
showDebugOverlay: true,
|
showDebugOverlay: true,
|
||||||
|
showHandTrackingSvg: false,
|
||||||
showInteractionSpheres: false,
|
showInteractionSpheres: false,
|
||||||
showPerf: true,
|
showPerf: true,
|
||||||
sceneMode: storedControls.sceneMode ?? "game",
|
sceneMode: storedControls.sceneMode ?? "game",
|
||||||
@@ -116,6 +118,16 @@ export class Debug {
|
|||||||
this.controls.showDebugOverlay = value;
|
this.controls.showDebugOverlay = value;
|
||||||
this.emit();
|
this.emit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handTrackingFolder = this.createFolder("Hand Tracking");
|
||||||
|
|
||||||
|
handTrackingFolder
|
||||||
|
?.add(this.controls, "showHandTrackingSvg")
|
||||||
|
.name("Afficher SVG")
|
||||||
|
.onChange((value: boolean) => {
|
||||||
|
this.controls.showHandTrackingSvg = value;
|
||||||
|
this.emit();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +191,15 @@ export class Debug {
|
|||||||
return this.controls.showInteractionSpheres;
|
return this.controls.showInteractionSpheres;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getShowHandTrackingSvg(): boolean {
|
||||||
|
return this.controls.showHandTrackingSvg;
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowHandTrackingSvg(value: boolean): void {
|
||||||
|
this.controls.showHandTrackingSvg = value;
|
||||||
|
this.emit();
|
||||||
|
}
|
||||||
|
|
||||||
setShowInteractionSpheres(value: boolean): void {
|
setShowInteractionSpheres(value: boolean): void {
|
||||||
this.controls.showInteractionSpheres = value;
|
this.controls.showInteractionSpheres = value;
|
||||||
this.emit();
|
this.emit();
|
||||||
|
|||||||
Reference in New Issue
Block a user