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; }) { + 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 ( - + {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 ; +}; + +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; +// 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; +// 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(null); + +// const [animationStates, setAnimationStates] = useState< +// Record +// >({}); + +// // 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 = {}; +// 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" ? ( +// } +// material={obj.material} +// position={obj.position} // Set position directly in the JSX +// > +// +// +// ) : ( +// gltf?.scene && ( +// } +// position={obj.position} // Set position directly in the JSX +// > +// +// +// ) +// ); +// }) +// )} +// +// ); +// }; + +// 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(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} material={obj.material} + position={obj.position} // Set position directly in the JSX > @@ -507,6 +1288,7 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({ } + position={obj.position} // Set position directly in the JSX > { - // console.log('simulationPaths: ', simulationPaths); + console.log('simulationPaths: ', simulationPaths); }, [simulationPaths]); // useEffect(() => {