"updated Animation"

This commit is contained in:
SreeNath14
2025-04-10 10:21:24 +05:30
parent 1eec500955
commit e48195db98
15 changed files with 1609 additions and 1169 deletions

View File

@@ -1,134 +1,45 @@
import React, { useRef, useState, useEffect, useMemo } from "react";
import {
useAnimationPlaySpeed,
usePauseButtonStore,
usePlayButtonStore,
useResetButtonStore,
} from "../../../store/usePlayButtonStore";
import { GLTFLoader } from "three-stdlib";
import React, { useRef, useEffect, useMemo } from "react";
import { useLoader, useFrame } from "@react-three/fiber";
import { GLTFLoader } from "three-stdlib";
import * as THREE from "three";
import { GLTF } from "three-stdlib";
import crate from "../../../assets/gltf-glb/crate_box.glb";
import box from "../../../assets/gltf-glb/box.glb";
interface PointAction {
uuid: string;
name: string;
type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap";
objectType: string;
material: string;
delay: string | number;
spawnInterval: string | number;
isUsed: boolean;
import { useProcessAnimation } from "./useProcessAnimations";
import ProcessObject from "./processObject";
import { ProcessData } from "./types";
import { useSimulationStates } from "../../../store/store";
interface ProcessContainerProps {
processes: ProcessData[];
setProcesses: React.Dispatch<React.SetStateAction<any[]>>;
agvRef: any;
}
interface ProcessPoint {
uuid: string;
position: number[];
rotation: number[];
actions: PointAction[];
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: 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;
customMaterials?: Record<string, THREE.Material>;
renderAs?: "box" | "custom";
}
interface AnimationState {
currentIndex: number;
progress: number;
isAnimating: boolean;
speed: number;
isDelaying: boolean;
delayStartTime: number;
currentDelayDuration: number;
delayComplete: boolean;
currentPathIndex: number;
}
interface SpawnedObject {
ref: React.RefObject<THREE.Group | THREE.Mesh>;
state: AnimationState;
visible: boolean;
material: THREE.Material;
spawnTime: number;
currentMaterialType: string;
position: THREE.Vector3;
}
interface ProcessAnimationState {
spawnedObjects: { [objectId: string]: SpawnedObject };
nextSpawnTime: number;
objectIdCounter: number;
isProcessDelaying: boolean;
processDelayStartTime: number;
processDelayDuration: number;
}
const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
const ProcessAnimator: React.FC<ProcessContainerProps> = ({
processes,
setProcesses,
agvRef,
}) => {
const gltf = useLoader(GLTFLoader, crate) as GLTF;
const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { isPaused, setIsPaused } = usePauseButtonStore();
const { isReset, setReset } = useResetButtonStore();
const groupRef = useRef<THREE.Group>(null);
const debugRef = useRef<boolean>(false);
const clockRef = useRef<THREE.Clock>(new THREE.Clock());
const pauseTimeRef = useRef<number>(0);
const elapsedBeforePauseRef = useRef<number>(0);
const animationStatesRef = useRef<Record<string, ProcessAnimationState>>({});
const { speed, setSpeed } = useAnimationPlaySpeed();
const prevIsPlaying = useRef<boolean | null>(null);
const [internalResetFlag, setInternalResetFlag] = useState(false);
const [animationStates, setAnimationStates] = useState<
Record<string, ProcessAnimationState>
>({});
// Store the speed in a ref to access the latest value in animation frames
const speedRef = useRef<number>(speed);
const {
animationStates,
setAnimationStates,
clockRef,
elapsedBeforePauseRef,
speedRef,
debugRef,
findSpawnPoint,
createSpawnedObject,
handlePointActions,
hasNonInheritActions,
getPointDataForAnimationIndex,
processes: processedProcesses,
checkAndCountTriggers,
} = useProcessAnimation(processes, setProcesses, agvRef);
// Update the ref when speed changes
useEffect(() => {
speedRef.current = speed;
}, [speed]);
useEffect(() => {
if (prevIsPlaying.current !== null) {
setAnimationStates({});
}
// Update ref to current isPlaying after effect
prevIsPlaying.current = isPlaying;
// setAnimationStates({});
}, [isPlaying]);
// Sync ref with state
useEffect(() => {
animationStatesRef.current = animationStates;
}, [animationStates]);
// Base materials
const baseMaterials = useMemo(
() => ({
Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
@@ -138,365 +49,22 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
[]
);
// Replace your reset effect with this:
useEffect(() => {
if (isReset) {
// 1. Mark that we're doing an internal reset
setInternalResetFlag(true);
// 2. Pause the animation first
setIsPlaying(false);
setIsPaused(false);
// 3. Reset all animation states
setAnimationStates({});
animationStatesRef.current = {};
// 4. Reset timing references
clockRef.current = new THREE.Clock();
elapsedBeforePauseRef.current = 0;
pauseTimeRef.current = 0;
// 5. Clear the external reset flag
setReset(false);
// 6. After state updates are complete, restart
setTimeout(() => {
setInternalResetFlag(false);
setIsPlaying(true);
}, 0);
}
}, [isReset, setReset, setIsPlaying, setIsPaused]);
// Handle pause state changes
useEffect(() => {
if (isPaused) {
pauseTimeRef.current = clockRef.current.getElapsedTime();
} else if (pauseTimeRef.current > 0) {
const pausedDuration =
clockRef.current.getElapsedTime() - pauseTimeRef.current;
elapsedBeforePauseRef.current += pausedDuration;
}
}, [isPaused]);
// Initialize animation states when processes or play state changes
useEffect(() => {
if (isPlaying && !internalResetFlag) {
const newStates: Record<string, ProcessAnimationState> = {};
processes.forEach((process) => {
newStates[process.id] = {
spawnedObjects: {},
nextSpawnTime: 0,
objectIdCounter: 0,
isProcessDelaying: false,
processDelayStartTime: 0,
processDelayDuration: 0,
};
});
setAnimationStates(newStates);
animationStatesRef.current = newStates;
clockRef.current.start();
}
}, [isPlaying, processes, internalResetFlag]);
const findSpawnPoint = (process: ProcessData): ProcessPoint | null => {
for (const path of process.paths || []) {
for (const point of path.points || []) {
const spawnAction = point.actions?.find(
(a) => a.isUsed && a.type === "Spawn"
);
if (spawnAction) {
return point;
}
}
}
return null;
};
const findAnimationPathPoint = (
process: ProcessData,
spawnPoint: ProcessPoint
): THREE.Vector3 => {
if (process.animationPath && process.animationPath.length > 0) {
let pointIndex = 0;
for (const path of process.paths || []) {
for (let i = 0; i < (path.points?.length || 0); i++) {
const point = path.points?.[i];
if (point && point.uuid === spawnPoint.uuid) {
if (process.animationPath[pointIndex]) {
const p = process.animationPath[pointIndex];
return new THREE.Vector3(p.x, p.y, p.z);
}
}
pointIndex++;
}
}
}
return new THREE.Vector3(
spawnPoint.position[0],
spawnPoint.position[1],
spawnPoint.position[2]
);
};
const createSpawnedObject = (
process: ProcessData,
currentTime: number,
materialType: string,
spawnPoint: ProcessPoint
): SpawnedObject => {
const processMaterials = {
...baseMaterials,
...(process.customMaterials || {}),
};
const spawnPosition = findAnimationPathPoint(process, spawnPoint);
const material =
processMaterials[materialType as keyof typeof processMaterials] ||
baseMaterials.Default;
if (debugRef.current) {
console.log(`Creating object with material: ${materialType}`, material);
}
return {
ref: React.createRef(),
state: {
currentIndex: 0,
progress: 0,
isAnimating: true,
speed: process.speed || 1, // Process base speed (will be multiplied by global speed)
isDelaying: false,
delayStartTime: 0,
currentDelayDuration: 0,
delayComplete: false,
currentPathIndex: 0,
},
visible: true,
material: material,
currentMaterialType: materialType,
spawnTime: currentTime,
position: spawnPosition,
};
};
const handleMaterialSwap = (
processId: string,
objectId: string,
materialType: string
) => {
if (debugRef.current) {
console.log(`Attempting material swap to: ${materialType}`);
}
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState || !processState.spawnedObjects[objectId]) {
if (debugRef.current) console.log("Object not found for swap");
return prev;
}
const process = processes.find((p) => p.id === processId);
if (!process) {
if (debugRef.current) console.log("Process not found");
return prev;
}
const processMaterials = {
...baseMaterials,
...(process.customMaterials || {}),
};
const newMaterial =
processMaterials[materialType as keyof typeof processMaterials];
if (!newMaterial) {
if (debugRef.current) console.log(`Material ${materialType} not found`);
return prev;
}
if (debugRef.current) {
console.log(`Swapping material for ${objectId} to ${materialType}`);
}
return {
...prev,
[processId]: {
...processState,
spawnedObjects: {
...processState.spawnedObjects,
[objectId]: {
...processState.spawnedObjects[objectId],
material: newMaterial,
currentMaterialType: materialType,
},
},
},
};
});
};
const handlePointActions = (
processId: string,
objectId: string,
actions: PointAction[] = [],
currentTime: number
): boolean => {
let shouldStopAnimation = false;
actions.forEach((action) => {
if (!action.isUsed) return;
if (debugRef.current) {
console.log(`Processing action: ${action.type} for ${objectId}`);
}
switch (action.type) {
case "Delay":
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState || processState.isProcessDelaying) {
return prev;
}
const delayDuration =
typeof action.delay === "number"
? action.delay
: parseFloat(action.delay as string) || 0;
if (delayDuration > 0) {
return {
...prev,
[processId]: {
...processState,
isProcessDelaying: true,
processDelayStartTime: currentTime,
processDelayDuration: delayDuration,
spawnedObjects: {
...processState.spawnedObjects,
[objectId]: {
...processState.spawnedObjects[objectId],
state: {
...processState.spawnedObjects[objectId].state,
isAnimating: false,
isDelaying: true,
delayStartTime: currentTime,
currentDelayDuration: delayDuration,
delayComplete: false,
},
},
},
},
};
}
return prev;
});
shouldStopAnimation = true;
break;
case "Despawn":
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState) return prev;
const newSpawnedObjects = { ...processState.spawnedObjects };
delete newSpawnedObjects[objectId];
return {
...prev,
[processId]: {
...processState,
spawnedObjects: newSpawnedObjects,
},
};
});
shouldStopAnimation = true;
break;
case "Swap":
if (action.material) {
handleMaterialSwap(processId, objectId, action.material);
}
break;
default:
break;
}
});
return shouldStopAnimation;
};
const hasNonInheritActions = (actions: PointAction[] = []): boolean => {
return actions.some((action) => action.isUsed && action.type !== "Inherit");
};
const getPointDataForAnimationIndex = (
process: ProcessData,
index: number
): ProcessPoint | null => {
if (!process.paths) return null;
let cumulativePoints = 0;
for (const path of process.paths) {
const pointCount = path.points?.length || 0;
if (index < cumulativePoints + pointCount) {
const pointIndex = index - cumulativePoints;
return path.points?.[pointIndex] || null;
}
cumulativePoints += pointCount;
}
return null;
};
// In processAnimator.tsx - only the relevant spawn logic part that needs fixes
useFrame(() => {
if (!isPlaying || isPaused) return;
// Spawn logic frame
const currentTime =
clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;
setAnimationStates((prev) => {
const newStates = { ...prev };
processes.forEach((process) => {
processedProcesses.forEach((process) => {
const processState = newStates[process.id];
if (!processState) return;
if (processState.isProcessDelaying) {
// Apply global speed to delays (faster speed = shorter delays)
const effectiveDelayTime =
processState.processDelayDuration / speedRef.current;
if (
currentTime - processState.processDelayStartTime >=
effectiveDelayTime
) {
newStates[process.id] = {
...processState,
isProcessDelaying: false,
spawnedObjects: Object.entries(
processState.spawnedObjects
).reduce(
(acc, [id, obj]) => ({
...acc,
[id]: {
...obj,
state: {
...obj.state,
isDelaying: false,
delayComplete: true,
isAnimating: true,
progress:
obj.state.progress === 0 ? 0.001 : obj.state.progress,
},
},
}),
{}
),
};
}
// Existing delay handling logic...
return;
}
@@ -513,7 +81,14 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
? spawnAction.spawnInterval
: parseFloat(spawnAction.spawnInterval as string) || 0;
// Apply global speed to spawn intervals (faster speed = more frequent spawns)
// Check if this is a zero interval spawn and we already spawned an object
if (
spawnInterval === 0 &&
processState.hasSpawnedZeroIntervalObject === true
) {
return; // Don't spawn more objects for zero interval
}
const effectiveSpawnInterval = spawnInterval / speedRef.current;
if (currentTime >= processState.nextSpawnTime) {
@@ -522,9 +97,11 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
process,
currentTime,
spawnAction.material || "Default",
spawnPoint
spawnPoint,
baseMaterials
);
// Update state with the new object and flag for zero interval
newStates[process.id] = {
...processState,
spawnedObjects: {
@@ -533,6 +110,11 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
},
objectIdCounter: processState.objectIdCounter + 1,
nextSpawnTime: currentTime + effectiveSpawnInterval,
// Mark that we've spawned an object for zero interval case
hasSpawnedZeroIntervalObject:
spawnInterval === 0
? true
: processState.hasSpawnedZeroIntervalObject,
};
}
});
@@ -542,20 +124,18 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
});
useFrame((_, delta) => {
if (!isPlaying || isPaused) return;
// Animation logic frame
const currentTime =
clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;
setAnimationStates((prev) => {
const newStates = { ...prev };
processes.forEach((process) => {
processedProcesses.forEach((process) => {
const processState = newStates[process.id];
if (!processState) return;
if (processState.isProcessDelaying) {
// Apply global speed to delays (faster speed = shorter delays)
const effectiveDelayTime =
processState.processDelayDuration / speedRef.current;
@@ -617,7 +197,6 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
const stateRef = obj.state;
if (stateRef.isDelaying) {
// Apply global speed to delays (faster speed = shorter delays)
const effectiveDelayTime =
stateRef.currentDelayDuration / speedRef.current;
@@ -654,18 +233,15 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
stateRef.currentIndex
);
// Handle point actions when first arriving at point
if (stateRef.progress === 0 && currentPointData?.actions) {
if (debugRef.current) {
console.log(
`At point ${stateRef.currentIndex} with actions:`,
currentPointData.actions
);
}
const shouldStop = handlePointActions(
process.id,
objectId,
currentPointData.actions,
currentTime
currentTime,
processedProcesses,
baseMaterials
);
if (shouldStop) {
updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
@@ -691,8 +267,6 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
const nextPoint = path[nextPointIdx];
const distance =
path[stateRef.currentIndex].distanceTo(nextPoint);
// Apply both process-specific speed and global speed multiplier
const effectiveSpeed = stateRef.speed * speedRef.current;
const movement = effectiveSpeed * delta;
@@ -708,17 +282,19 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
stateRef.progress = 0;
currentRef.position.copy(nextPoint);
// TRIGGER CHECK - When object arrives at new point
checkAndCountTriggers(
process.id,
objectId,
stateRef.currentIndex, // The new point index
processedProcesses,
currentTime
);
const newPointData = getPointDataForAnimationIndex(
process,
stateRef.currentIndex
);
if (newPointData?.actions && debugRef.current) {
console.log(
`Reached new point with actions:`,
newPointData.actions
);
}
} else {
currentRef.position.lerpVectors(
path[stateRef.currentIndex],
@@ -742,56 +318,31 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
});
});
if (!processes || processes.length === 0) {
if (!processedProcesses || processedProcesses.length === 0) {
return null;
}
return (
<>
<group ref={groupRef}>
{Object.entries(animationStates).flatMap(([processId, processState]) =>
Object.entries(processState.spawnedObjects)
.filter(([_, obj]) => obj.visible)
.map(([objectId, obj]) => {
const process = processes.find((p) => p.id === processId);
const process = processedProcesses.find((p) => p.id === processId);
const renderAs = process?.renderAs || "custom";
if (renderAs === "box") {
return (
<mesh
key={objectId}
ref={obj.ref as React.RefObject<THREE.Mesh>}
material={obj.material}
position={obj.position}
>
<boxGeometry args={[1, 1, 1]} />
</mesh>
);
}
if (gltf?.scene) {
// Clone the scene and apply the material to all meshes
const clonedScene = gltf.scene.clone();
clonedScene.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.material = obj.material;
}
});
return (
<group
key={objectId}
ref={obj.ref as React.RefObject<THREE.Group>}
position={obj.position}
>
<primitive object={clonedScene} />
</group>
);
}
return null;
return (
<ProcessObject
key={objectId}
objectId={objectId}
obj={obj}
renderAs={renderAs}
gltf={gltf}
/>
);
})
)}
</>
</group>
);
};