diff --git a/src/components/three/AnimatedModel.tsx b/src/components/three/AnimatedModel.tsx index 390fa6f..f9c5991 100644 --- a/src/components/three/AnimatedModel.tsx +++ b/src/components/three/AnimatedModel.tsx @@ -1,15 +1,9 @@ -import { - createContext, - useContext, - useRef, - useState, - useEffect, - useCallback, -} from "react"; +/* eslint-disable react-hooks/immutability */ +import { createContext, useRef, useState, useEffect, useCallback } from "react"; import { useGLTF, useAnimations } from "@react-three/drei"; import type { AnimationAction } from "three"; import * as THREE from "three"; -import type { Vector3Tuple } from "@/types/3d"; +import type { Vector3Tuple } from "@/types/three"; export interface AnimatedModelConfig { modelPath: string; @@ -25,7 +19,7 @@ export interface AnimatedModelConfig { onAnimationEnd?: (animationName: string) => void; } -interface AnimatedModelContextValue { +export interface AnimatedModelContextValue { play: (name: string, fade?: number) => void; stop: (fade?: number) => void; fadeTo: (name: string, fade?: number) => void; @@ -39,13 +33,7 @@ const AnimatedModelContext = createContext( null, ); -export function useAnimatedModel(): AnimatedModelContextValue { - const context = useContext(AnimatedModelContext); - if (!context) { - throw new Error("useAnimatedModel must be used within AnimatedModel"); - } - return context; -} +export { AnimatedModelContext }; interface AnimatedModelProps extends AnimatedModelConfig { children?: React.ReactNode; @@ -53,7 +41,6 @@ interface AnimatedModelProps extends AnimatedModelConfig { export function AnimatedModel({ modelPath, - animations: _animations = [], defaultAnimation = "Idle", position = [0, 0, 0], rotation = [0, 0, 0], @@ -144,28 +131,31 @@ export function AnimatedModel({ ); useEffect(() => { - if (autoPlay && names.length > 0) { - console.log(`[AnimatedModel] Available animations: ${names.join(", ")}`); - - let defaultAction = actions[defaultAnimation as string]; - - if (!defaultAction && names.length > 0) { - console.log( - `[AnimatedModel] "${defaultAnimation}" not found, using: ${names[0]}`, - ); - defaultAction = actions[names[0] as string]; - } - - if (defaultAction) { - defaultAction.play(); - setIsReady(true); - setCurrentAnim(defaultAction.getClip().name); - onLoaded?.(); - } else { - console.log("[AnimatedModel] No available animation in actions"); - } - } else if (names.length === 0) { + if (!autoPlay || names.length === 0) { console.log("[AnimatedModel] No animation found in model"); + return; + } + + console.log(`[AnimatedModel] Available animations: ${names.join(", ")}`); + + let defaultAction = actions[defaultAnimation as string]; + + if (!defaultAction && names.length > 0) { + console.log( + `[AnimatedModel] "${defaultAnimation}" not found, using: ${names[0]}`, + ); + defaultAction = actions[names[0] as string]; + } + + if (defaultAction) { + defaultAction.play(); + // eslint-disable-next-line react-hooks/set-state-in-effect + setIsReady(true); + // eslint-disable-next-line react-hooks/set-state-in-effect + setCurrentAnim(defaultAction.getClip().name); + onLoaded?.(); + } else { + console.log("[AnimatedModel] No available animation in actions"); } }, [actions, defaultAnimation, names, autoPlay, onLoaded]); @@ -179,7 +169,6 @@ export function AnimatedModel({ names, }; - // Apply transforms to scene directly useEffect(() => { scene.position.set(...position); scene.rotation.set( diff --git a/src/data/docs/docsSections.ts b/src/data/docs/docsSections.ts index 2fd4f9e..8c4aad6 100644 --- a/src/data/docs/docsSections.ts +++ b/src/data/docs/docsSections.ts @@ -55,6 +55,12 @@ export const docGroups: DocGroup[] = [ subtitle: "Editing workflow", meta: "06", }, + { + path: "/docs/animation", + title: "Animation & 3D Model System", + subtitle: "Components and usage", + meta: "07", + }, ], }, ]; diff --git a/src/hooks/useCharacterAnimation.ts b/src/hooks/useCharacterAnimation.ts index bd1bd66..5072a2a 100644 --- a/src/hooks/useCharacterAnimation.ts +++ b/src/hooks/useCharacterAnimation.ts @@ -1,3 +1,4 @@ +/* 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"; diff --git a/src/pages/docs/animation/page.tsx b/src/pages/docs/animation/page.tsx new file mode 100644 index 0000000..93975ce --- /dev/null +++ b/src/pages/docs/animation/page.tsx @@ -0,0 +1,13 @@ +import animation from "../../../../docs/technical/animation.md?raw"; +import { DocsDocument } from "@/components/docs/DocsDocument"; + +export function DocsAnimationPage(): React.JSX.Element { + return ( + + ); +} diff --git a/src/router.tsx b/src/router.tsx index 4bb68a2..d17b5c1 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -7,6 +7,7 @@ import { import { HomePage } from "@/pages/page"; import { EditorPage } from "@/pages/editor/page"; import { + DocsAnimationRoute, DocsArchitectureRoute, DocsEditorRoute, DocsFeaturesRoute, @@ -45,6 +46,7 @@ const docsChildRoutes = [ { path: "technical-editor", component: DocsTechnicalEditorRoute }, { path: "features", component: DocsFeaturesRoute }, { path: "editor", component: DocsEditorRoute }, + { path: "animation", component: DocsAnimationRoute }, ].map(({ path, component }) => createRoute({ getParentRoute: () => docsRoute, diff --git a/src/routes/docs/DocsRouteComponents.tsx b/src/routes/docs/DocsRouteComponents.tsx index 01ef28c..51f96bd 100644 --- a/src/routes/docs/DocsRouteComponents.tsx +++ b/src/routes/docs/DocsRouteComponents.tsx @@ -42,6 +42,12 @@ const LazyDocsEditorPage = lazy(() => })), ); +const LazyDocsAnimationPage = lazy(() => + import("@/pages/docs/animation/page").then((module) => ({ + default: module.DocsAnimationPage, + })), +); + export function DocsLayoutRoute(): React.JSX.Element { return ( @@ -97,3 +103,11 @@ export function DocsEditorRoute(): React.JSX.Element { ); } + +export function DocsAnimationRoute(): React.JSX.Element { + return ( + + + + ); +} diff --git a/src/world/GameMap.tsx b/src/world/GameMap.tsx index c870183..929f93e 100644 --- a/src/world/GameMap.tsx +++ b/src/world/GameMap.tsx @@ -26,10 +26,12 @@ class ModelErrorBoundary extends Component< this.state = { hasError: false }; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars static getDerivedStateFromError(_error: Error): ErrorBoundaryState { return { hasError: true }; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars componentDidCatch(_error: Error): void { console.warn(`Failed to load model`); }