From 5fb911f1a1c1ab969e94edff383c30a384cb1c61 Mon Sep 17 00:00:00 2001
From: SreeNath14 <153710861+SreeNath14@users.noreply.github.com>
Date: Thu, 3 Apr 2025 18:24:06 +0530
Subject: [PATCH] "updated smooth animation"

---
 .../modules/simulation/path/pathCreation.tsx  |   6 +-
 app/src/modules/simulation/process/mesh.tsx   |   7 +
 .../simulation/process/processAnimator.tsx    | 852 +++++++++++++++++-
 app/src/modules/simulation/simulation.tsx     |   2 +-
 4 files changed, 829 insertions(+), 38 deletions(-)
 create mode 100644 app/src/modules/simulation/process/mesh.tsx

diff --git a/app/src/modules/simulation/path/pathCreation.tsx b/app/src/modules/simulation/path/pathCreation.tsx
index 7af4afd..dd531a9 100644
--- a/app/src/modules/simulation/path/pathCreation.tsx
+++ b/app/src/modules/simulation/path/pathCreation.tsx
@@ -14,12 +14,14 @@ import {
 } from "../../../store/store";
 import { useFrame, useThree } from "@react-three/fiber";
 import { useSubModuleStore } from "../../../store/useModuleStore";
+import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
 
 function PathCreation({
   pathsGroupRef,
 }: {
   pathsGroupRef: React.MutableRefObject<THREE.Group>;
 }) {
+  const { isPlaying } = usePlayButtonStore();
   const { renderDistance } = useRenderDistance();
   const { setSubModule } = useSubModuleStore();
   const { setSelectedActionSphere, selectedActionSphere } =
@@ -66,7 +68,7 @@ function PathCreation({
         const distance = new THREE.Vector3(
           ...group.position.toArray()
         ).distanceTo(camera.position);
-        group.visible = distance <= renderDistance;
+        group.visible = ((distance <= renderDistance) && !isPlaying);
       }
     });
   });
@@ -193,7 +195,7 @@ function PathCreation({
   };
 
   return (
-    <group name="simulation-simulationPaths-group" ref={pathsGroupRef}>
+    <group visible={!isPlaying} name="simulation-simulationPaths-group" ref={pathsGroupRef}>
       {simulationPaths.map((path) => {
         if (path.type === "Conveyor") {
           const points = path.points.map(
diff --git a/app/src/modules/simulation/process/mesh.tsx b/app/src/modules/simulation/process/mesh.tsx
new file mode 100644
index 0000000..f6b94bb
--- /dev/null
+++ b/app/src/modules/simulation/process/mesh.tsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+const Mesh: React.FC = () => {
+  return <mesh></mesh>;
+};
+
+export default Mesh;
diff --git a/app/src/modules/simulation/process/processAnimator.tsx b/app/src/modules/simulation/process/processAnimator.tsx
index 45db8be..512b165 100644
--- a/app/src/modules/simulation/process/processAnimator.tsx
+++ b/app/src/modules/simulation/process/processAnimator.tsx
@@ -1,3 +1,582 @@
+// 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";
+
+// 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;
+//   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; // The position of the object
+// }
+
+// interface ProcessAnimationState {
+//   spawnedObjects: { [objectId: string]: SpawnedObject };
+//   nextSpawnTime: number;
+//   objectIdCounter: number;
+// }
+
+// const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
+//   processes,
+// }) => {
+//   
+//   const gltf = useLoader(GLTFLoader, boxGltb) as GLTF;
+//   const { isPlaying } = usePlayButtonStore();
+//   const groupRef = useRef<THREE.Group>(null);
+
+//   const [animationStates, setAnimationStates] = useState<
+//     Record<string, ProcessAnimationState>
+//   >({});
+
+//   // Base materials
+//   const baseMaterials = 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 }),
+//     }),
+//     []
+//   );
+
+//   // Initialize animation states when processes or play state changes
+//   useEffect(() => {
+//     if (!isPlaying) {
+//       setAnimationStates({});
+//       return;
+//     }
+
+//     const newStates: Record<string, ProcessAnimationState> = {};
+//     processes.forEach((process) => {
+//       newStates[process.id] = {
+//         spawnedObjects: {},
+//         nextSpawnTime: 0,
+//         objectIdCounter: 0,
+//       };
+//     });
+//     setAnimationStates(newStates);
+//   }, [isPlaying, processes]);
+
+//   // Find spawn point in a process
+//   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;
+//   };
+
+//   // Find the corresponding animation path point for a spawn point
+//   const findAnimationPathPoint = (process: ProcessData, spawnPoint: ProcessPoint): THREE.Vector3 => {
+//     // If we have an animation path, use the first point
+//     if (process.animationPath && process.animationPath.length > 0) {
+//       // Find the index of this point in the path
+//       let pointIndex = 0;
+
+//       // Try to find the corresponding point in the animation path
+//       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) {
+//             // Found the matching point
+//             if (process.animationPath[pointIndex]) {
+//               const p = process.animationPath[pointIndex];
+//               return new THREE.Vector3(p.x, p.y, p.z);
+//             }
+//           }
+//           pointIndex++;
+//         }
+//       }
+
+//       // Fallback to the spawn point's position
+//       return new THREE.Vector3(
+//         spawnPoint.position[0],
+//         spawnPoint.position[1],
+//         spawnPoint.position[2]
+//       );
+//     }
+
+//     // If no animation path, use the spawn point's position
+//     return new THREE.Vector3(
+//       spawnPoint.position[0],
+//       spawnPoint.position[1],
+//       spawnPoint.position[2]
+//     );
+//   };
+
+//   // Create a new spawned object
+//   const createSpawnedObject = (
+//     process: ProcessData,
+//     currentTime: number,
+//     materialType: string,
+//     spawnPoint: ProcessPoint
+//   ): SpawnedObject => {
+//     const processMaterials = {
+//       ...baseMaterials,
+//       ...(process.customMaterials || {}),
+//     };
+
+//     // Get the position where we should spawn
+//     const spawnPosition = findAnimationPathPoint(process, spawnPoint);
+
+//     return {
+//       ref: React.createRef(),
+//       state: {
+//         currentIndex: 0,
+//         progress: 0,
+//         isAnimating: true,
+//         speed: process.speed || 1,
+//         isDelaying: false,
+//         delayStartTime: 0,
+//         currentDelayDuration: 0,
+//         delayComplete: false,
+//         currentPathIndex: 0,
+//       },
+//       visible: true,
+//       material:
+//         processMaterials[materialType as keyof typeof processMaterials] ||
+//         baseMaterials.Default,
+//       currentMaterialType: materialType,
+//       spawnTime: currentTime,
+//       position: spawnPosition, // Store the position directly
+//     };
+//   };
+
+//   // Handle material swap for an object
+//   const handleMaterialSwap = (
+//     processId: string,
+//     objectId: string,
+//     materialType: string
+//   ) => {
+//     setAnimationStates((prev) => {
+//       const processState = prev[processId];
+//       if (!processState || !processState.spawnedObjects[objectId]) return prev;
+
+//       const process = processes.find((p) => p.id === processId);
+//       const processMaterials = {
+//         ...baseMaterials,
+//         ...(process?.customMaterials || {}),
+//       };
+
+//       const newMaterial =
+//         processMaterials[materialType as keyof typeof processMaterials] ||
+//         baseMaterials.Default;
+
+//       return {
+//         ...prev,
+//         [processId]: {
+//           ...processState,
+//           spawnedObjects: {
+//             ...processState.spawnedObjects,
+//             [objectId]: {
+//               ...processState.spawnedObjects[objectId],
+//               material: newMaterial,
+//               currentMaterialType: materialType,
+//             },
+//           },
+//         },
+//       };
+//     });
+//   };
+
+//   // Handle point actions for an object
+//   const handlePointActions = (
+//     processId: string,
+//     objectId: string,
+//     actions: PointAction[] = [],
+//     currentTime: number
+//   ): boolean => {
+//     let shouldStopAnimation = false;
+
+//     actions.forEach((action) => {
+//       if (!action.isUsed) return;
+
+//       switch (action.type) {
+//         case "Delay":
+//           setAnimationStates((prev) => {
+//             const processState = prev[processId];
+//             if (
+//               !processState ||
+//               !processState.spawnedObjects[objectId] ||
+//               processState.spawnedObjects[objectId].state.isDelaying
+//             ) {
+//               return prev;
+//             }
+
+//             const delayDuration =
+//               typeof action.delay === "number"
+//                 ? action.delay
+//                 : parseFloat(action.delay as string) || 0;
+
+//             if (delayDuration > 0) {
+//               return {
+//                 ...prev,
+//                 [processId]: {
+//                   ...processState,
+//                   spawnedObjects: {
+//                     ...processState.spawnedObjects,
+//                     [objectId]: {
+//                       ...processState.spawnedObjects[objectId],
+//                       state: {
+//                         ...processState.spawnedObjects[objectId].state,
+//                         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;
+//   };
+
+//   // Check if point has non-inherit actions
+//   const hasNonInheritActions = (actions: PointAction[] = []): boolean => {
+//     return actions.some((action) => action.isUsed && action.type !== "Inherit");
+//   };
+
+//   // Get point data for current animation index
+//   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;
+//   };
+
+//   // Spawn objects for all processes
+//   useFrame((state) => {
+//     if (!isPlaying) return;
+
+//     const currentTime = state.clock.getElapsedTime();
+//     setAnimationStates((prev) => {
+//       const newStates = { ...prev };
+
+//       processes.forEach((process) => {
+//         const processState = newStates[process.id];
+//         if (!processState) return;
+
+//         const spawnPoint = findSpawnPoint(process);
+//         if (!spawnPoint || !spawnPoint.actions) return;
+
+//         const spawnAction = spawnPoint.actions.find(
+//           (a) => a.isUsed && a.type === "Spawn"
+//         );
+//         if (!spawnAction) return;
+
+//         const spawnInterval =
+//           typeof spawnAction.spawnInterval === "number"
+//             ? spawnAction.spawnInterval
+//             : parseFloat(spawnAction.spawnInterval as string) || 0;
+
+//         if (currentTime >= processState.nextSpawnTime) {
+//           const objectId = `obj-${process.id}-${processState.objectIdCounter}`;
+
+//           // Create the new object with the spawn point
+//           const newObject = createSpawnedObject(
+//             process,
+//             currentTime,
+//             spawnAction.material || "Default",
+//             spawnPoint
+//           );
+
+//           newStates[process.id] = {
+//             ...processState,
+//             spawnedObjects: {
+//               ...processState.spawnedObjects,
+//               [objectId]: newObject,
+//             },
+//             objectIdCounter: processState.objectIdCounter + 1,
+//             nextSpawnTime: currentTime + spawnInterval,
+//           };
+//         }
+//       });
+
+//       return newStates;
+//     });
+//   });
+
+//   // Animate objects for all processes
+//   useFrame((state, delta) => {
+//     if (!isPlaying) return;
+
+//     const currentTime = state.clock.getElapsedTime();
+//     setAnimationStates((prev) => {
+//       const newStates = { ...prev };
+
+//       processes.forEach((process) => {
+//         const processState = newStates[process.id];
+//         if (!processState) return;
+
+//         const path =
+//           process.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) ||
+//           [];
+//         if (path.length < 2) return;
+
+//         const updatedObjects = { ...processState.spawnedObjects };
+
+//         Object.entries(processState.spawnedObjects).forEach(
+//           ([objectId, obj]) => {
+//             if (!obj.visible || !obj.state.isAnimating) return;
+
+//             const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current;
+//             if (!currentRef) return;
+
+//             // Set the position when the reference is first available
+//             if (obj.position && obj.state.currentIndex === 0 && obj.state.progress === 0) {
+//               currentRef.position.copy(obj.position);
+//             }
+
+//             const stateRef = obj.state;
+
+//             // Get current point data
+//             const currentPointData = getPointDataForAnimationIndex(
+//               process,
+//               stateRef.currentIndex
+//             );
+
+//             // Execute actions when arriving at a new point
+//             if (stateRef.progress === 0 && currentPointData?.actions) {
+//               const shouldStop = handlePointActions(
+//                 process.id,
+//                 objectId,
+//                 currentPointData.actions,
+//                 currentTime
+//               );
+//               if (shouldStop) return;
+//             }
+
+//             // Handle delays
+//             if (stateRef.isDelaying) {
+//               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]);
+//                   delete updatedObjects[objectId];
+//                   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
+//                 );
+//               }
+//             }
+
+//             updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
+//           }
+//         );
+
+//         newStates[process.id] = {
+//           ...processState,
+//           spawnedObjects: updatedObjects,
+//         };
+//       });
+
+//       return newStates;
+//     });
+//   });
+
+//   if (!processes || processes.length === 0) {
+//     return null;
+//   }
+
+//   return (
+//     <>
+//       {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 renderAs = process?.renderAs || "custom";
+
+//             return renderAs === "box" ? (
+//               <mesh
+//                 key={objectId}
+//                 ref={obj.ref as React.RefObject<THREE.Mesh>}
+//                 material={obj.material}
+//                 position={obj.position}  // Set position directly in the JSX
+//               >
+//                 <boxGeometry args={[1, 1, 1]} />
+//               </mesh>
+//             ) : (
+//               gltf?.scene && (
+//                 <group
+//                   key={objectId}
+//                   ref={obj.ref as React.RefObject<THREE.Group>}
+//                   position={obj.position}  // Set position directly in the JSX
+//                 >
+//                   <primitive
+//                     object={gltf.scene.clone()}
+//                     material={obj.material}
+//                   />
+//                 </group>
+//               )
+//             );
+//           })
+//       )}
+//     </>
+//   );
+// };
+
+// export default ProcessAnimator;
+
 import React, { useRef, useState, useEffect, useMemo } from "react";
 import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
 import { GLTFLoader } from "three-stdlib";
@@ -5,6 +584,7 @@ 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;
@@ -66,18 +646,23 @@ interface SpawnedObject {
   material: THREE.Material;
   spawnTime: number;
   currentMaterialType: string;
+  position: THREE.Vector3; // The position of the object
 }
 
 interface ProcessAnimationState {
   spawnedObjects: { [objectId: string]: SpawnedObject };
   nextSpawnTime: number;
   objectIdCounter: number;
+  // New fields for process-wide delay
+  isProcessDelaying: boolean;
+  processDelayStartTime: number;
+  processDelayDuration: number;
 }
 
 const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
   processes,
 }) => {
-  console.log("processes: ", processes);
+  
   const gltf = useLoader(GLTFLoader, boxGltb) as GLTF;
   const { isPlaying } = usePlayButtonStore();
   const groupRef = useRef<THREE.Group>(null);
@@ -90,10 +675,8 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
   const baseMaterials = useMemo(
     () => ({
       Wood: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
-      Box: new THREE.MeshStandardMaterial({
+      Box: new THREE.MeshPhongMaterial({
         color: 0xcccccc,
-        metalness: 0.8,
-        roughness: 0.2,
       }),
       Crate: new THREE.MeshStandardMaterial({
         color: 0x00aaff,
@@ -118,6 +701,10 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
         spawnedObjects: {},
         nextSpawnTime: 0,
         objectIdCounter: 0,
+        // Initialize process-wide delay state
+        isProcessDelaying: false,
+        processDelayStartTime: 0,
+        processDelayDuration: 0,
       };
     });
     setAnimationStates(newStates);
@@ -138,17 +725,62 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
     return null;
   };
 
+  // Find the corresponding animation path point for a spawn point
+  const findAnimationPathPoint = (
+    process: ProcessData,
+    spawnPoint: ProcessPoint
+  ): THREE.Vector3 => {
+    // If we have an animation path, use the first point
+    if (process.animationPath && process.animationPath.length > 0) {
+      // Find the index of this point in the path
+      let pointIndex = 0;
+
+      // Try to find the corresponding point in the animation path
+      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) {
+            // Found the matching point
+            if (process.animationPath[pointIndex]) {
+              const p = process.animationPath[pointIndex];
+              return new THREE.Vector3(p.x, p.y, p.z);
+            }
+          }
+          pointIndex++;
+        }
+      }
+
+      // Fallback to the spawn point's position
+      return new THREE.Vector3(
+        spawnPoint.position[0],
+        spawnPoint.position[1],
+        spawnPoint.position[2]
+      );
+    }
+
+    // If no animation path, use the spawn point's position
+    return new THREE.Vector3(
+      spawnPoint.position[0],
+      spawnPoint.position[1],
+      spawnPoint.position[2]
+    );
+  };
+
   // Create a new spawned object
   const createSpawnedObject = (
     process: ProcessData,
     currentTime: number,
-    materialType: string
+    materialType: string,
+    spawnPoint: ProcessPoint
   ): SpawnedObject => {
     const processMaterials = {
       ...baseMaterials,
       ...(process.customMaterials || {}),
     };
 
+    // Get the position where we should spawn
+    const spawnPosition = findAnimationPathPoint(process, spawnPoint);
+
     return {
       ref: React.createRef(),
       state: {
@@ -168,6 +800,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
         baseMaterials.Default,
       currentMaterialType: materialType,
       spawnTime: currentTime,
+      position: spawnPosition, // Store the position directly
     };
   };
 
@@ -224,11 +857,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
         case "Delay":
           setAnimationStates((prev) => {
             const processState = prev[processId];
-            if (
-              !processState ||
-              !processState.spawnedObjects[objectId] ||
-              processState.spawnedObjects[objectId].state.isDelaying
-            ) {
+            if (!processState || processState.isProcessDelaying) {
               return prev;
             }
 
@@ -242,12 +871,18 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
                 ...prev,
                 [processId]: {
                   ...processState,
+                  // Set process-wide delay instead of object-specific delay
+                  isProcessDelaying: true,
+                  processDelayStartTime: currentTime,
+                  processDelayDuration: delayDuration,
+                  // Update the specific object's state as well
                   spawnedObjects: {
                     ...processState.spawnedObjects,
                     [objectId]: {
                       ...processState.spawnedObjects[objectId],
                       state: {
                         ...processState.spawnedObjects[objectId].state,
+                        isAnimating: false, // Explicitly pause animation during delay
                         isDelaying: true,
                         delayStartTime: currentTime,
                         currentDelayDuration: delayDuration,
@@ -335,6 +970,43 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
         const processState = newStates[process.id];
         if (!processState) return;
 
+        // Skip spawning if the process is currently in a delay
+        if (processState.isProcessDelaying) {
+          // Check if delay is over
+          if (
+            currentTime - processState.processDelayStartTime >=
+            processState.processDelayDuration
+          ) {
+            // Reset process delay state
+            newStates[process.id] = {
+              ...processState,
+              isProcessDelaying: false,
+              // Reset delay state on all objects in this process
+              spawnedObjects: Object.entries(
+                processState.spawnedObjects
+              ).reduce(
+                (acc, [id, obj]) => ({
+                  ...acc,
+                  [id]: {
+                    ...obj,
+                    state: {
+                      ...obj.state,
+                      isDelaying: false,
+                      delayComplete: true,
+                      isAnimating: true, // Ensure animation resumes
+                      // Force a small progress to ensure movement starts
+                      progress:
+                        obj.state.progress === 0 ? 0.001 : obj.state.progress,
+                    },
+                  },
+                }),
+                {}
+              ),
+            };
+          }
+          return; // Skip spawning while delaying
+        }
+
         const spawnPoint = findSpawnPoint(process);
         if (!spawnPoint || !spawnPoint.actions) return;
 
@@ -346,20 +1018,24 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
         const spawnInterval =
           typeof spawnAction.spawnInterval === "number"
             ? spawnAction.spawnInterval
-            : 0;
+            : parseFloat(spawnAction.spawnInterval as string) || 0;
 
         if (currentTime >= processState.nextSpawnTime) {
           const objectId = `obj-${process.id}-${processState.objectIdCounter}`;
 
+          // Create the new object with the spawn point
+          const newObject = createSpawnedObject(
+            process,
+            currentTime,
+            spawnAction.material || "Default",
+            spawnPoint
+          );
+
           newStates[process.id] = {
             ...processState,
             spawnedObjects: {
               ...processState.spawnedObjects,
-              [objectId]: createSpawnedObject(
-                process,
-                currentTime,
-                spawnAction.material || "Default"
-              ),
+              [objectId]: newObject,
             },
             objectIdCounter: processState.objectIdCounter + 1,
             nextSpawnTime: currentTime + spawnInterval,
@@ -383,6 +1059,47 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
         const processState = newStates[process.id];
         if (!processState) return;
 
+        // Check if the process-wide delay is active
+        if (processState.isProcessDelaying) {
+          // Check if the delay has completed
+          if (
+            currentTime - processState.processDelayStartTime >=
+            processState.processDelayDuration
+          ) {
+            // Reset process delay state AND resume animation
+            newStates[process.id] = {
+              ...processState,
+              isProcessDelaying: false,
+              // Reset delay state on all objects in this process AND ensure isAnimating is true
+              spawnedObjects: Object.entries(
+                processState.spawnedObjects
+              ).reduce(
+                (acc, [id, obj]) => ({
+                  ...acc,
+                  [id]: {
+                    ...obj,
+                    state: {
+                      ...obj.state,
+                      isDelaying: false,
+                      delayComplete: true,
+                      isAnimating: true, // Ensure animation resumes
+                      // Important: Force progress to a small positive value to ensure movement
+                      progress:
+                        obj.state.progress === 0 ? 0.005 : obj.state.progress,
+                    },
+                  },
+                }),
+                {}
+              ),
+            };
+            // Skip the rest of the processing for this frame to allow the state update to take effect
+            return newStates;
+          } else {
+            // If we're still in a process-wide delay, don't animate anything
+            return newStates;
+          }
+        }
+
         const path =
           process.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) ||
           [];
@@ -392,13 +1109,63 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
 
         Object.entries(processState.spawnedObjects).forEach(
           ([objectId, obj]) => {
-            if (!obj.visible || !obj.state.isAnimating) return;
+            // Skip objects that are explicitly not visible
+            if (!obj.visible) return;
 
             const currentRef = gltf?.scene ? obj.ref.current : obj.ref.current;
             if (!currentRef) return;
 
+            // Set the position when the reference is first available
+            if (
+              obj.position &&
+              obj.state.currentIndex === 0 &&
+              obj.state.progress === 0
+            ) {
+              currentRef.position.copy(obj.position);
+            }
+
             const stateRef = obj.state;
 
+            // Check if we're delaying at the object level and update accordingly
+            if (stateRef.isDelaying) {
+              if (
+                currentTime - stateRef.delayStartTime >=
+                stateRef.currentDelayDuration
+              ) {
+                // Delay is complete, resume animation
+                stateRef.isDelaying = false;
+                stateRef.delayComplete = true;
+                stateRef.isAnimating = true; // Explicitly resume animation
+
+                // Force movement from the current point by setting progress to a small value
+                // if we're at the start of a segment
+                if (stateRef.progress === 0) {
+                  stateRef.progress = 0.005;
+                }
+
+                // Force an immediate position update to ensure visually accurate position
+                const nextPointIdx = stateRef.currentIndex + 1;
+                if (nextPointIdx < path.length) {
+                  // Calculate the position slightly ahead of the current point
+                  const slightProgress = Math.max(stateRef.progress, 0.005);
+                  currentRef.position.lerpVectors(
+                    path[stateRef.currentIndex],
+                    nextPointIdx < path.length
+                      ? path[nextPointIdx]
+                      : path[stateRef.currentIndex],
+                    slightProgress
+                  );
+                }
+              } else {
+                // Still delaying, don't animate this object
+                updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
+                return;
+              }
+            }
+
+            // Skip animation if the object shouldn't be animating
+            if (!stateRef.isAnimating) return;
+
             // Get current point data
             const currentPointData = getPointDataForAnimationIndex(
               process,
@@ -413,19 +1180,9 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
                 currentPointData.actions,
                 currentTime
               );
-              if (shouldStop) return;
-            }
-
-            // Handle delays
-            if (stateRef.isDelaying) {
-              if (
-                currentTime - stateRef.delayStartTime >=
-                stateRef.currentDelayDuration
-              ) {
-                stateRef.isDelaying = false;
-                stateRef.delayComplete = true;
-              } else {
-                return; // Keep waiting
+              if (shouldStop) {
+                updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
+                return;
               }
             }
 
@@ -438,8 +1195,10 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
                   currentPointData.actions
                 );
                 if (shouldStop) {
-                  currentRef.position.copy(path[stateRef.currentIndex]);
-                  delete updatedObjects[objectId];
+                  // uncomment this or write own logic to handle the object when reaching the last point of the process
+
+                  // currentRef.position.copy(path[stateRef.currentIndex]);
+                  // delete updatedObjects[objectId];
                   return;
                 }
               }
@@ -450,14 +1209,36 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
               const distance =
                 path[stateRef.currentIndex].distanceTo(nextPoint);
               const movement = stateRef.speed * delta;
-              stateRef.progress += movement / distance;
+
+              // If we just resumed from a delay, ensure we make actual progress
+              if (stateRef.delayComplete && stateRef.progress < 0.01) {
+                // Boost initial movement after delay to ensure visible progress
+                stateRef.progress = 0.05; // Small but visible initial progress
+                stateRef.delayComplete = false; // Reset flag so we don't do this again
+              } else {
+                // Normal progress calculation
+                stateRef.progress += movement / distance;
+              }
 
               if (stateRef.progress >= 1) {
+                // We've reached the next point
                 stateRef.currentIndex = nextPointIdx;
                 stateRef.progress = 0;
-                stateRef.delayComplete = false;
                 currentRef.position.copy(nextPoint);
+
+                // Check if we need to execute actions at this new point
+                const newPointData = getPointDataForAnimationIndex(
+                  process,
+                  stateRef.currentIndex
+                );
+
+                if (newPointData?.actions) {
+                  // We've arrived at a new point with actions, handle them in the next frame
+                  // We don't call handlePointActions directly here to avoid state update issues
+                  // The actions will be handled in the next frame when progress is 0
+                }
               } else {
+                // Normal path interpolation
                 currentRef.position.lerpVectors(
                   path[stateRef.currentIndex],
                   nextPoint,
@@ -491,7 +1272,6 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
           .filter(([_, obj]) => obj.visible)
           .map(([objectId, obj]) => {
             const process = processes.find((p) => p.id === processId);
-            console.log("process: ", process);
             const renderAs = process?.renderAs || "custom";
 
             return renderAs === "box" ? (
@@ -499,6 +1279,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
                 key={objectId}
                 ref={obj.ref as React.RefObject<THREE.Mesh>}
                 material={obj.material}
+                position={obj.position} // Set position directly in the JSX
               >
                 <boxGeometry args={[1, 1, 1]} />
               </mesh>
@@ -507,6 +1288,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
                 <group
                   key={objectId}
                   ref={obj.ref as React.RefObject<THREE.Group>}
+                  position={obj.position} // Set position directly in the JSX
                 >
                   <primitive
                     object={gltf.scene.clone()}
diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx
index fd8b520..830a0b1 100644
--- a/app/src/modules/simulation/simulation.tsx
+++ b/app/src/modules/simulation/simulation.tsx
@@ -19,7 +19,7 @@ function Simulation() {
   const [processes, setProcesses] = useState([]);
 
   useEffect(() => {
-    // console.log('simulationPaths: ', simulationPaths);
+    console.log('simulationPaths: ', simulationPaths);
   }, [simulationPaths]);
 
   // useEffect(() => {