/* eslint-disable react-hooks/immutability */ import { useRef, useEffect, useState, useCallback } from "react"; import { useGLTF, useAnimations } from "@react-three/drei"; import type { AnimationAction, AnimationMixer } from "three"; import * as THREE from "three"; export interface CharacterAnimationConfig { modelPath: string; initialAnimation?: string; fadeDuration?: number; } interface UseCharacterAnimationReturn { scene: THREE.Group; actions: { [key: string]: AnimationAction | null }; names: string[]; mixer: AnimationMixer; groupRef: React.MutableRefObject; currentAnimation: string; play: (name: string) => void; stop: () => void; fadeTo: (name: string, duration?: number) => void; setAnimationSpeed: (speed: number) => void; } const DEFAULT_FADE_DURATION = 0.3; export function useCharacterAnimation( config: CharacterAnimationConfig, ): UseCharacterAnimationReturn { const { modelPath, initialAnimation = "Idle", fadeDuration = DEFAULT_FADE_DURATION, } = config; const groupRef = useRef(null); const { scene, animations } = useGLTF(modelPath); const { actions, names, mixer } = useAnimations(animations, groupRef); const [currentAnimation, setCurrentAnimation] = useState(initialAnimation); const play = useCallback( (name: string) => { const action = actions[name]; if (action) { Object.values(actions).forEach((a) => { if (a && a !== action) a.fadeOut(fadeDuration); }); action.reset().fadeIn(fadeDuration).play(); setCurrentAnimation(name); } }, [actions, fadeDuration], ); const stop = useCallback(() => { Object.values(actions).forEach((a) => a?.fadeOut(fadeDuration)); const defaultAction = actions[initialAnimation as string]; if (defaultAction) { defaultAction.reset().fadeIn(fadeDuration).play(); setCurrentAnimation(initialAnimation); } }, [actions, initialAnimation, fadeDuration]); const fadeTo = useCallback( (name: string, duration = fadeDuration) => { const targetAction = actions[name]; if (targetAction) { Object.values(actions).forEach((a) => { if (a && a !== targetAction) a.fadeOut(duration); }); targetAction.reset().fadeIn(duration).play(); setCurrentAnimation(name); } }, [actions, fadeDuration], ); const setAnimationSpeed = useCallback( (speed: number) => { if (mixer) { mixer.timeScale = speed; } }, [mixer], ); useEffect(() => { const defaultAction = actions[initialAnimation as string]; if (defaultAction) { defaultAction.play(); } }, [actions, initialAnimation]); return { scene, actions, names, mixer, groupRef, currentAnimation, play, stop, fadeTo, setAnimationSpeed, }; }