Dwinzo_dev/app/src/modules/simulation/process/processAnimator.tsx

348 lines
9.0 KiB
TypeScript
Raw Normal View History

2025-04-01 13:24:04 +00:00
import React, { useRef, useState, useEffect, useMemo } from "react";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
import { GLTFLoader } from "three-stdlib";
import { useLoader, useFrame } from "@react-three/fiber";
import * as THREE from "three";
import { GLTF } from "three-stdlib";
import boxGltb from "../../../assets/gltf-glb/crate_box.glb";
2025-04-02 05:40:44 +00:00
import camera from "../../../assets/gltf-glb/camera face 2.gltf";
2025-04-01 13:24:04 +00:00
interface PointAction {
uuid: string;
name: string;
type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap";
2025-04-02 05:40:44 +00:00
objectType: string;
2025-04-01 13:24:04 +00:00
material: string;
delay: string | number;
spawnInterval: string | number;
isUsed: boolean;
}
interface ProcessPoint {
uuid: string;
position: number[];
rotation: number[];
actions: PointAction[];
connections: {
source: { pathUUID: string; pointUUID: string };
targets: { pathUUID: string; pointUUID: string }[];
};
}
interface ProcessPath {
modeluuid: string;
modelName: string;
points: ProcessPoint[];
pathPosition: number[];
pathRotation: number[];
speed: number;
}
interface ProcessData {
id: string;
paths: ProcessPath[];
animationPath: { x: number; y: number; z: number }[];
pointActions: PointAction[][];
speed: number;
}
interface AnimationState {
currentIndex: number;
progress: number;
isAnimating: boolean;
speed: number;
isDelaying: boolean;
delayStartTime: number;
currentDelayDuration: number;
delayComplete: boolean;
currentPathIndex: number;
}
const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
processes,
}) => {
console.log("processes: ", processes);
const gltf = useLoader(GLTFLoader, boxGltb) as GLTF;
const { isPlaying, setIsPlaying } = usePlayButtonStore();
const groupRef = useRef<THREE.Group>(null);
const meshRef = useRef<THREE.Mesh>(null);
const [visible, setVisible] = useState(false);
2025-04-02 05:40:44 +00:00
const [currentPathIndex, setCurrentPathIndex] = useState(0);
2025-04-01 13:24:04 +00:00
const materials = useMemo(
() => ({
Wood: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
Box: new THREE.MeshStandardMaterial({
color: 0xcccccc,
metalness: 0.8,
roughness: 0.2,
}),
Crate: new THREE.MeshStandardMaterial({
color: 0x00aaff,
metalness: 0.1,
roughness: 0.5,
}),
Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
}),
[]
);
const [currentMaterial, setCurrentMaterial] = useState<THREE.Material>(
materials.Default
);
const { animationPath, currentProcess } = useMemo(() => {
const defaultProcess = {
animationPath: [],
pointActions: [],
speed: 1,
paths: [],
};
const cp = processes?.[0] || defaultProcess;
return {
animationPath:
cp.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) || [],
currentProcess: cp,
};
}, [processes]);
const animationStateRef = useRef<AnimationState>({
currentIndex: 0,
progress: 0,
isAnimating: false,
speed: currentProcess.speed,
isDelaying: false,
delayStartTime: 0,
currentDelayDuration: 0,
delayComplete: false,
currentPathIndex: 0,
});
const getPointDataForAnimationIndex = (index: number) => {
if (!processes[0]?.paths) return null;
2025-04-02 05:40:44 +00:00
let cumulativePoints = 0;
console.log("cumulativePoints: ", cumulativePoints);
for (const path of processes[0].paths) {
const pointCount = path.points?.length || 0;
if (index < cumulativePoints + pointCount) {
const pointIndex = index - cumulativePoints;
return path.points?.[pointIndex] || null;
}
cumulativePoints += pointCount;
2025-04-01 13:24:04 +00:00
}
2025-04-02 05:40:44 +00:00
return null;
2025-04-01 13:24:04 +00:00
};
useEffect(() => {
if (isPlaying) {
setVisible(true);
animationStateRef.current = {
currentIndex: 0,
progress: 0,
isAnimating: true,
speed: currentProcess.speed,
isDelaying: false,
delayStartTime: 0,
currentDelayDuration: 0,
delayComplete: false,
currentPathIndex: 0,
};
const currentRef = gltf?.scene ? groupRef.current : meshRef.current;
if (currentRef && animationPath.length > 0) {
currentRef.position.copy(animationPath[0]);
}
} else {
animationStateRef.current.isAnimating = false;
}
}, [isPlaying, currentProcess, animationPath]);
const handleMaterialSwap = (materialType: string) => {
2025-04-02 05:40:44 +00:00
setCurrentMaterial(
materials[materialType as keyof typeof materials] || materials.Default
);
2025-04-01 13:24:04 +00:00
};
const hasNonInheritActions = (actions: PointAction[] = []) => {
return actions.some((action) => action.isUsed && action.type !== "Inherit");
};
const handlePointActions = (
actions: PointAction[] = [],
2025-04-02 05:40:44 +00:00
currentTime: number
2025-04-01 13:24:04 +00:00
) => {
let shouldStopAnimation = false;
actions.forEach((action) => {
if (!action.isUsed) return;
switch (action.type) {
case "Delay":
if (
!animationStateRef.current.isDelaying &&
!animationStateRef.current.delayComplete
) {
const delayDuration =
typeof action.delay === "number"
? action.delay
: parseFloat(action.delay as string) || 0;
if (delayDuration > 0) {
animationStateRef.current.isDelaying = true;
animationStateRef.current.delayStartTime = currentTime;
animationStateRef.current.currentDelayDuration = delayDuration;
shouldStopAnimation = true;
}
}
break;
case "Despawn":
setVisible(false);
setIsPlaying(false);
animationStateRef.current.isAnimating = false;
shouldStopAnimation = true;
break;
case "Spawn":
2025-04-02 05:40:44 +00:00
setVisible(true);
2025-04-01 13:24:04 +00:00
break;
case "Swap":
if (action.material) {
handleMaterialSwap(action.material);
}
break;
case "Inherit":
2025-04-02 05:40:44 +00:00
// Handle inherit logic if needed
2025-04-01 13:24:04 +00:00
break;
}
});
return shouldStopAnimation;
};
useFrame((state, delta) => {
const currentRef = gltf?.scene ? groupRef.current : meshRef.current;
if (
!currentRef ||
!animationStateRef.current.isAnimating ||
animationPath.length < 2
) {
return;
}
const currentTime = state.clock.getElapsedTime();
const path = animationPath;
const stateRef = animationStateRef.current;
2025-04-02 05:40:44 +00:00
// Check if we need to switch paths (specific to your structure)
2025-04-01 13:24:04 +00:00
if (stateRef.currentIndex === 3 && stateRef.currentPathIndex === 0) {
2025-04-02 05:40:44 +00:00
setCurrentPathIndex(1);
2025-04-01 13:24:04 +00:00
stateRef.currentPathIndex = 1;
}
2025-04-02 05:40:44 +00:00
// Get the current point data
2025-04-01 13:24:04 +00:00
const currentPointData = getPointDataForAnimationIndex(
stateRef.currentIndex
);
2025-04-02 05:40:44 +00:00
// Execute actions when we first arrive at a point
2025-04-01 13:24:04 +00:00
if (stateRef.progress === 0 && currentPointData?.actions) {
2025-04-02 05:40:44 +00:00
console.log(
`Processing actions at point ${stateRef.currentIndex}`,
currentPointData.actions
);
2025-04-01 13:24:04 +00:00
const shouldStop = handlePointActions(
currentPointData.actions,
2025-04-02 05:40:44 +00:00
currentTime
2025-04-01 13:24:04 +00:00
);
if (shouldStop) return;
}
2025-04-02 05:40:44 +00:00
// Handle delays
2025-04-01 13:24:04 +00:00
if (stateRef.isDelaying) {
2025-04-02 05:40:44 +00:00
console.log(
`Delaying... ${currentTime - stateRef.delayStartTime}/${
stateRef.currentDelayDuration
}`
);
2025-04-01 13:24:04 +00:00
if (
currentTime - stateRef.delayStartTime >=
stateRef.currentDelayDuration
) {
stateRef.isDelaying = false;
stateRef.delayComplete = true;
} else {
2025-04-02 05:40:44 +00:00
return; // Keep waiting
2025-04-01 13:24:04 +00:00
}
}
const nextPointIdx = stateRef.currentIndex + 1;
const isLastPoint = nextPointIdx >= path.length;
if (isLastPoint) {
if (currentPointData?.actions) {
const shouldStop = !hasNonInheritActions(currentPointData.actions);
if (shouldStop) {
currentRef.position.copy(path[stateRef.currentIndex]);
setIsPlaying(false);
stateRef.isAnimating = false;
return;
}
}
}
if (!isLastPoint) {
const nextPoint = path[nextPointIdx];
const distance = path[stateRef.currentIndex].distanceTo(nextPoint);
const movement = stateRef.speed * delta;
stateRef.progress += movement / distance;
if (stateRef.progress >= 1) {
stateRef.currentIndex = nextPointIdx;
stateRef.progress = 0;
stateRef.delayComplete = false;
currentRef.position.copy(nextPoint);
} else {
currentRef.position.lerpVectors(
path[stateRef.currentIndex],
nextPoint,
stateRef.progress
);
}
}
});
if (!processes || processes.length === 0) {
return null;
}
if (!gltf?.scene) {
return visible ? (
<mesh ref={meshRef} material={currentMaterial}>
<boxGeometry args={[1, 1, 1]} />
</mesh>
) : null;
}
return visible ? (
<group ref={groupRef}>
<primitive
object={gltf.scene.clone()}
scale={[1, 1, 1]}
material={currentMaterial}
/>
</group>
) : null;
};
export default ProcessAnimator;