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"; import camera from "../../../assets/gltf-glb/camera face 2.gltf"; interface PointAction { uuid: string; name: string; type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap"; objectType: string; 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(null); const meshRef = useRef(null); const [visible, setVisible] = useState(false); const [currentPathIndex, setCurrentPathIndex] = useState(0); 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( 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({ 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; 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; } return null; }; 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) => { setCurrentMaterial( materials[materialType as keyof typeof materials] || materials.Default ); }; const hasNonInheritActions = (actions: PointAction[] = []) => { return actions.some((action) => action.isUsed && action.type !== "Inherit"); }; const handlePointActions = ( actions: PointAction[] = [], currentTime: number ) => { 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": setVisible(true); break; case "Swap": if (action.material) { handleMaterialSwap(action.material); } break; case "Inherit": // Handle inherit logic if needed 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; // Check if we need to switch paths (specific to your structure) if (stateRef.currentIndex === 3 && stateRef.currentPathIndex === 0) { setCurrentPathIndex(1); stateRef.currentPathIndex = 1; } // Get the current point data const currentPointData = getPointDataForAnimationIndex( stateRef.currentIndex ); // Execute actions when we first arrive at a point if (stateRef.progress === 0 && currentPointData?.actions) { console.log( `Processing actions at point ${stateRef.currentIndex}`, currentPointData.actions ); const shouldStop = handlePointActions( currentPointData.actions, currentTime ); if (shouldStop) return; } // Handle delays if (stateRef.isDelaying) { console.log( `Delaying... ${currentTime - stateRef.delayStartTime}/${ stateRef.currentDelayDuration }` ); if ( currentTime - stateRef.delayStartTime >= stateRef.currentDelayDuration ) { stateRef.isDelaying = false; stateRef.delayComplete = true; } else { return; // Keep waiting } } 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 ? ( ) : null; } return visible ? ( ) : null; }; export default ProcessAnimator;