import React, { useCallback, useEffect, useRef, useState } from 'react'; import VehicleAnimator from '../animator/vehicleAnimator'; import * as THREE from 'three'; import { NavMeshQuery } from '@recast-navigation/core'; import { useNavMesh } from '../../../../../store/builder/store'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore'; import { useStorageUnitStore } from '../../../../../store/simulation/useStorageUnitStore'; import { useMaterialStore } from '../../../../../store/simulation/useMaterialStore'; import { useProductStore } from '../../../../../store/simulation/useProductStore'; import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore'; import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; import MaterialAnimator from '../animator/materialAnimator'; type Timer = { start: number | null; active: boolean; }; function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) { const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); const { removeMaterial, setEndTime } = useMaterialStore(); const { getStorageUnitById } = useStorageUnitStore(); const { triggerPointActions } = useTriggerHandler(); const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = useProductStore(); const { selectedProduct } = useSelectedProduct(); const { vehicles, setVehicleActive, setVehicleState, setVehiclePicking, clearCurrentMaterials, setVehicleLoad, decrementVehicleLoad, removeLastMaterial } = useVehicleStore(); const [currentPhase, setCurrentPhase] = useState('stationed'); const [path, setPath] = useState<[number, number, number][]>([]); const pauseTimeRef = useRef(null); const isPausedRef = useRef(false); let startTime: number; let fixedInterval: number; const { speed } = useAnimationPlaySpeed(); const { isPaused } = usePauseButtonStore(); useEffect(() => { isPausedRef.current = isPaused; }, [isPaused]); const computePath = useCallback( (start: any, end: any) => { try { const navMeshQuery = new NavMeshQuery(navMesh); const { path: segmentPath } = navMeshQuery.computePath(start, end); return ( segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || [] ); } catch { echo.error("Failed to compute path"); return []; } }, [navMesh] ); function vehicleStatus(modelId: string, status: string) { // } // Function to reset everything function reset() { setCurrentPhase('stationed'); setVehicleActive(agvDetail.modelUuid, false); setVehiclePicking(agvDetail.modelUuid, false); setVehicleState(agvDetail.modelUuid, 'idle'); setVehicleLoad(agvDetail.modelUuid, 0); setPath([]); startTime = 0; isPausedRef.current = false; pauseTimeRef.current = 0; } useEffect(() => { if (isPlaying) { if (!agvDetail.point.action.unLoadPoint || !agvDetail.point.action.pickUpPoint) return; if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'stationed') { const toPickupPath = computePath( new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]), agvDetail?.point?.action?.pickUpPoint?.position ); setPath(toPickupPath); setCurrentPhase('stationed-pickup'); setVehicleState(agvDetail.modelUuid, 'running'); setVehiclePicking(agvDetail.modelUuid, false); setVehicleActive(agvDetail.modelUuid, true); vehicleStatus(agvDetail.modelUuid, 'Started from station, heading to pickup'); return; } else if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'picking') { if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity && agvDetail.currentMaterials.length > 0) { if (agvDetail.point.action.pickUpPoint && agvDetail.point.action.unLoadPoint) { const toDrop = computePath( agvDetail.point.action.pickUpPoint.position, agvDetail.point.action.unLoadPoint.position ); setPath(toDrop); setCurrentPhase('pickup-drop'); setVehicleState(agvDetail.modelUuid, 'running'); setVehiclePicking(agvDetail.modelUuid, false); setVehicleActive(agvDetail.modelUuid, true); vehicleStatus(agvDetail.modelUuid, 'Started from pickup point, heading to drop point'); } } } else if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'dropping' && agvDetail.currentLoad === 0) { if (agvDetail.point.action.pickUpPoint && agvDetail.point.action.unLoadPoint) { const dropToPickup = computePath( agvDetail.point.action.unLoadPoint.position, agvDetail.point.action.pickUpPoint.position ); setPath(dropToPickup); setCurrentPhase('drop-pickup'); setVehicleState(agvDetail.modelUuid, 'running'); setVehiclePicking(agvDetail.modelUuid, false); setVehicleActive(agvDetail.modelUuid, true); vehicleStatus(agvDetail.modelUuid, 'Started from dropping point, heading to pickup point'); } } } else { reset() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [vehicles, currentPhase, path, isPlaying]); function handleCallBack() { if (currentPhase === 'stationed-pickup') { setCurrentPhase('picking'); setVehicleState(agvDetail.modelUuid, 'idle'); setVehiclePicking(agvDetail.modelUuid, true); setVehicleActive(agvDetail.modelUuid, false); vehicleStatus(agvDetail.modelUuid, 'Reached pickup point, waiting for material'); setPath([]); } else if (currentPhase === 'pickup-drop') { setCurrentPhase('dropping'); setVehicleState(agvDetail.modelUuid, 'idle'); setVehiclePicking(agvDetail.modelUuid, false); setVehicleActive(agvDetail.modelUuid, false); vehicleStatus(agvDetail.modelUuid, 'Reached drop point'); setPath([]); } else if (currentPhase === 'drop-pickup') { setCurrentPhase('picking'); setVehicleState(agvDetail.modelUuid, 'idle'); setVehiclePicking(agvDetail.modelUuid, true); setVehicleActive(agvDetail.modelUuid, false); setPath([]); clearCurrentMaterials(agvDetail.modelUuid) vehicleStatus(agvDetail.modelUuid, 'Reached pickup point again, cycle complete'); } } function startUnloadingProcess() { if (agvDetail.point.action.triggers.length > 0) { const trigger = getTriggerByUuid(selectedProduct.productId, agvDetail.point.action.triggers[0].triggerUuid); const model = getEventByModelUuid(selectedProduct.productId, trigger?.triggeredAsset?.triggeredModel?.modelUuid || ''); if (trigger && model) { if (model.type === 'transfer') { const action = getActionByUuid(selectedProduct.productId, agvDetail.point.action.actionUuid); if (action) { handleMaterialDropToConveyor(action); } } else if (model.type === 'machine') { // } else if (model.type === 'roboticArm') { const action = getActionByUuid(selectedProduct.productId, agvDetail.point.action.actionUuid); if (action) { handleMaterialDropToArmBot(action); } } else if (model.type === 'storageUnit') { const action = getActionByUuid(selectedProduct.productId, agvDetail.point.action.actionUuid); if (action) { handleMaterialDropToStorageUnit(action); } } } else { const droppedMaterial = agvDetail.currentLoad; startTime = performance.now(); handleMaterialDropByDefault(droppedMaterial); } } else { const droppedMaterial = agvDetail.currentLoad; startTime = performance.now(); handleMaterialDropByDefault(droppedMaterial); } } function handleMaterialDropToStorageUnit(action: Action) { if (action.triggers.length > 0 && action.triggers[0].triggeredAsset?.triggeredModel.modelUuid) { const storageUnit = getStorageUnitById(action.triggers[0].triggeredAsset?.triggeredModel.modelUuid); if (storageUnit) { if (storageUnit.point.action.actionType === 'store') { handleMaterialDropToStorage( agvDetail.modelUuid, agvDetail.currentLoad, agvDetail.point.action.unLoadDuration, storageUnit.modelUuid, storageUnit.point.action.storageCapacity, agvDetail.point.action ); } } } } function handleMaterialDropToStorage( vehicleId: string, vehicleCurrentLoad: number, unLoadDuration: number, storageUnitId: string, storageMaxCapacity: number, action: VehicleAction ) { startTime = performance.now(); const fixedInterval = ((unLoadDuration / vehicleCurrentLoad) * (1000 / speed)); const unloadLoop = () => { if (isPausedRef.current) { pauseTimeRef.current ??= performance.now(); requestAnimationFrame(unloadLoop); return; } if (pauseTimeRef.current) { const pauseDuration = performance.now() - pauseTimeRef.current; startTime += pauseDuration; pauseTimeRef.current = null; } const elapsedTime = performance.now() - startTime; const storageUnit = getStorageUnitById(storageUnitId); if (elapsedTime >= fixedInterval) { if (storageUnit && agvDetail && storageUnit.currentLoad < storageMaxCapacity && vehicleCurrentLoad > 0) { decrementVehicleLoad(vehicleId, 1); vehicleCurrentLoad -= 1; const material = removeLastMaterial(vehicleId); if (material) { triggerPointActions(action, material.materialId); } if (vehicleCurrentLoad > 0 && storageUnit.currentLoad < storageMaxCapacity) { startTime = performance.now(); requestAnimationFrame(unloadLoop); } } } else { requestAnimationFrame(unloadLoop); } }; const storageUnit = getStorageUnitById(storageUnitId); if (storageUnit && vehicleCurrentLoad > 0 && storageUnit?.currentLoad < storageMaxCapacity) { unloadLoop(); } } function handleMaterialDropToConveyor(action: Action) { if (agvDetail.currentLoad > 1) { // } else if (agvDetail.currentLoad === 1 && agvDetail.currentMaterials.length === 1) { triggerPointActions(action, agvDetail.currentMaterials[0].materialId); decrementVehicleLoad(agvDetail.modelUuid, 1); removeLastMaterial(agvDetail.modelUuid); } } function handleMaterialDropToArmBot(action: Action) { if (agvDetail.currentLoad > 1) { // } else if (agvDetail.currentLoad === 1 && agvDetail.currentMaterials.length === 1) { triggerPointActions(action, agvDetail.currentMaterials[0].materialId); } } function handleMaterialDropByDefault(droppedMaterial: number) { if (isPausedRef.current) { pauseTimeRef.current ??= performance.now(); requestAnimationFrame(() => handleMaterialDropByDefault(droppedMaterial)); return; } if (pauseTimeRef.current) { const pauseDuration = performance.now() - pauseTimeRef.current; startTime += pauseDuration; pauseTimeRef.current = null; } const elapsedTime = performance.now() - startTime; const unLoadDuration = agvDetail.point.action.unLoadDuration; fixedInterval = ((unLoadDuration / agvDetail.currentLoad) * (1000 / speed)); if (elapsedTime >= fixedInterval) { let droppedMat = droppedMaterial - 1; decrementVehicleLoad(agvDetail.modelUuid, 1); const material = removeLastMaterial(agvDetail.modelUuid); if (material) { setEndTime(material.materialId, performance.now()); removeMaterial(material.materialId); } if (droppedMat > 0) { startTime = performance.now(); requestAnimationFrame(() => handleMaterialDropByDefault(droppedMat)); } else { return; } } else { requestAnimationFrame(() => handleMaterialDropByDefault(droppedMaterial)); } } return ( <> ); } export default VehicleInstance;