refactor: factor out animator logic
This commit is contained in:
80
src/App.tsx
80
src/App.tsx
@@ -8,22 +8,33 @@ import {
|
||||
type ThreeEvent,
|
||||
useLoader,
|
||||
} from "@react-three/fiber";
|
||||
import { OrbitControls, useAnimations } from "@react-three/drei";
|
||||
import { OrbitControls } from "@react-three/drei";
|
||||
import { GLTFLoader } from "three/examples/jsm/Addons.js";
|
||||
import { LoopOnce, Vector3 } from "three";
|
||||
import { Vector3 } from "three";
|
||||
|
||||
import "./App.css";
|
||||
import { state, anim } from "./utils/animator";
|
||||
import { useAnimator } from "./hooks/useAnimator";
|
||||
|
||||
const Room = () => {
|
||||
const controls = useRef<ComponentRef<typeof OrbitControls>>(null);
|
||||
const room = useLoader(GLTFLoader, "/room.glb");
|
||||
const { mixer } = useAnimations(room.animations, room.scene);
|
||||
|
||||
const windowAnimator = useAnimator(
|
||||
room.animations,
|
||||
[
|
||||
state(anim("Cube.010Action", false), anim("ShadeHandleAction", false)),
|
||||
state(anim("LowerWindowAction", false)),
|
||||
state(anim("LowerWindowAction", true)),
|
||||
state(anim("Cube.010Action", true), anim("Cube.010Action", true)),
|
||||
],
|
||||
room.scene,
|
||||
);
|
||||
|
||||
const [focus, setFocus] = useState<string | null>(null);
|
||||
|
||||
const cameraPosition = useRef<Vector3 | null>(null);
|
||||
const cameraTarget = useRef<Vector3>(new Vector3(0, 1, 0));
|
||||
const [windowState, setWindowState] = useState(false);
|
||||
const [shadeState, setShadeState] = useState(false);
|
||||
|
||||
const pointerDownCameraPos = useRef(new Vector3());
|
||||
|
||||
@@ -53,64 +64,9 @@ const Room = () => {
|
||||
return;
|
||||
|
||||
e.stopPropagation();
|
||||
if (e.object.name.includes("Window")) {
|
||||
const clip = room.animations.find((x) => x.name === "LowerWindowAction");
|
||||
if (!clip) throw "no animation";
|
||||
|
||||
const action = mixer.clipAction(clip);
|
||||
if (action.isRunning()) return;
|
||||
action.clampWhenFinished = true;
|
||||
action.setLoop(LoopOnce, 1);
|
||||
action.timeScale = (windowState ? -1 : 1) * 1.5;
|
||||
if (windowState) {
|
||||
action.paused = false;
|
||||
action.time = action.getClip().duration;
|
||||
} else {
|
||||
action.reset();
|
||||
}
|
||||
action.play();
|
||||
|
||||
setWindowState(!windowState);
|
||||
}
|
||||
|
||||
if (e.object.name.includes("Shade")) {
|
||||
{
|
||||
const clip = room.animations.find((x) => x.name === "Cube.010Action");
|
||||
if (!clip) throw "no animation";
|
||||
|
||||
const action = mixer.clipAction(clip);
|
||||
if (action.isRunning()) return;
|
||||
action.clampWhenFinished = true;
|
||||
action.setLoop(LoopOnce, 1);
|
||||
action.timeScale = (shadeState ? -1 : 1) * 1.5;
|
||||
if (shadeState) {
|
||||
action.paused = false;
|
||||
action.time = action.getClip().duration;
|
||||
} else {
|
||||
action.reset();
|
||||
}
|
||||
action.play();
|
||||
}
|
||||
{
|
||||
const clip = room.animations.find(
|
||||
(x) => x.name === "ShadeHandleAction",
|
||||
);
|
||||
if (!clip) throw "no animation";
|
||||
|
||||
const action = mixer.clipAction(clip);
|
||||
if (action.isRunning()) return;
|
||||
action.clampWhenFinished = true;
|
||||
action.setLoop(LoopOnce, 1);
|
||||
action.timeScale = (shadeState ? -1 : 1) * 1.5;
|
||||
if (shadeState) {
|
||||
action.paused = false;
|
||||
action.time = action.getClip().duration;
|
||||
} else {
|
||||
action.reset();
|
||||
}
|
||||
action.play();
|
||||
}
|
||||
setShadeState(!shadeState);
|
||||
if (e.object.name === "WindowClickTarget") {
|
||||
windowAnimator.play();
|
||||
}
|
||||
|
||||
if (e.object.name.includes("Poster") && e.object.name !== focus) {
|
||||
|
||||
59
src/hooks/useAnimator.ts
Normal file
59
src/hooks/useAnimator.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useAnimations } from "@react-three/drei";
|
||||
import { LoopOnce, type AnimationClip } from "three";
|
||||
import type { AnimationAction, Object3D } from "three";
|
||||
import type { Animation, AnimatorState } from "../utils/animator";
|
||||
import { useState } from "react";
|
||||
|
||||
// NOTE: maybe root is not necessary
|
||||
|
||||
export const useAnimator = (
|
||||
clips: AnimationClip[],
|
||||
states: AnimatorState[],
|
||||
root?: React.RefObject<Object3D | undefined | null> | Object3D,
|
||||
) => {
|
||||
const [stateIndex, setStateIndex] = useState(0);
|
||||
const { mixer } = useAnimations(clips, root);
|
||||
|
||||
const getAction = (name: string): AnimationAction => {
|
||||
const clip = clips.find((x) => x.name === name);
|
||||
if (!clip) throw `Animation not found: '${name}'`;
|
||||
return mixer.clipAction(clip);
|
||||
};
|
||||
|
||||
const playAnimation = (animation: Animation): void => {
|
||||
const action = getAction(animation.name);
|
||||
|
||||
action.clampWhenFinished = true;
|
||||
action.setLoop(LoopOnce, 1);
|
||||
action.timeScale = (animation.reverse ? -1 : 1) * 1.5;
|
||||
if (animation.reverse) {
|
||||
action.paused = false;
|
||||
action.time = action.getClip().duration;
|
||||
} else {
|
||||
action.reset();
|
||||
}
|
||||
action.play();
|
||||
};
|
||||
|
||||
return {
|
||||
play() {
|
||||
if (this.isPlaying()) return;
|
||||
for (const animation of states[stateIndex]) {
|
||||
playAnimation(animation);
|
||||
}
|
||||
|
||||
setStateIndex((stateIndex + 1) % states.length);
|
||||
},
|
||||
|
||||
isPlaying(): boolean {
|
||||
for (const state of states) {
|
||||
for (const animation of state) {
|
||||
const action = getAction(animation.name);
|
||||
if (action.isRunning()) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
};
|
||||
20
src/utils/animator.ts
Normal file
20
src/utils/animator.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { AnimationAction } from "three";
|
||||
|
||||
export type Animation = {
|
||||
name: string;
|
||||
reverse: boolean;
|
||||
};
|
||||
|
||||
export type AnimatorState = Animation[];
|
||||
|
||||
export type Animator = {
|
||||
actions: Map<string, AnimationAction>;
|
||||
states: AnimatorState[];
|
||||
};
|
||||
|
||||
export const state = (...animations: Animation[]): Animation[] => animations;
|
||||
|
||||
export const anim = (name: string, reverse: boolean): Animation => ({
|
||||
name,
|
||||
reverse,
|
||||
});
|
||||
Reference in New Issue
Block a user