import { useCallback, useEffect, useRef, useState } from 'react'; import * as THREE from 'three'; import { useThree } from '@react-three/fiber'; import { NavMeshQuery } from '@recast-navigation/core'; import { useNavMesh } from '../../../../../store/builder/store'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useProductContext } from '../../../products/productContext'; import HumanAnimator from '../animator/humanAnimator'; import MaterialAnimator from '../animator/materialAnimator'; function HumanInstance({ human }: { human: HumanStatus }) { const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); const { scene } = useThree(); const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext(); const { removeMaterial, setEndTime, setMaterial, setIsVisible } = materialStore(); const { getStorageUnitById } = storageUnitStore(); const { getArmBotById } = armBotStore(); const { getConveyorById } = conveyorStore(); const { getVehicleById } = vehicleStore(); const { getMachineById } = machineStore(); const { triggerPointActions } = useTriggerHandler(); const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore(); const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { setHumanActive, setHumanState, clearCurrentMaterials, setHumanLoad, setHumanScheduled, decrementHumanLoad, removeLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = humanStore(); const [currentPhase, setCurrentPhase] = useState('init'); const [path, setPath] = useState<[number, number, number][]>([]); const pauseTimeRef = useRef(null); const idleTimeRef = useRef(0); const activeTimeRef = useRef(0); const isPausedRef = useRef(false); const isSpeedRef = useRef(0); const { speed } = useAnimationPlaySpeed(); const { isPaused } = usePauseButtonStore(); const previousTimeRef = useRef(null); const animationFrameIdRef = useRef(null); const humanAsset = getAssetById(human.modelUuid); const processStartTimeRef = useRef(null); const processTimeRef = useRef(0); const processAnimationIdRef = useRef(null); const accumulatedPausedTimeRef = useRef(0); const lastPauseTimeRef = useRef(null); const hasLoggedHalfway = useRef(false); const hasLoggedCompleted = useRef(false); useEffect(() => { isPausedRef.current = isPaused; }, [isPaused]); useEffect(() => { isSpeedRef.current = speed; }, [speed]); const computePath = useCallback( (start: any, end: any) => { try { const navMeshQuery = new NavMeshQuery(navMesh); const { path: segmentPath } = navMeshQuery.computePath(start, end); if ( segmentPath.length > 0 && Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(end.x) && Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(end.z) ) { return segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; } else { console.log("There is no path here...Choose valid path") const { path: segmentPaths } = navMeshQuery.computePath(start, start); return segmentPaths.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; } } catch { console.error("Failed to compute path"); return []; } }, [navMesh]); function humanStatus(modelId: string, status: string) { // console.log(`${modelId} , ${status}`); } function reset() { setCurrentPhase('init'); setHumanActive(human.modelUuid, false); setHumanState(human.modelUuid, 'idle'); setHumanScheduled(human.modelUuid, false); setHumanLoad(human.modelUuid, 0); resetAnimation(human.modelUuid); setPath([]); isPausedRef.current = false; pauseTimeRef.current = 0; resetTime(human.modelUuid) activeTimeRef.current = 0 idleTimeRef.current = 0 previousTimeRef.current = null if (animationFrameIdRef.current !== null) { cancelAnimationFrame(animationFrameIdRef.current) animationFrameIdRef.current = null } if (processAnimationIdRef.current) { cancelAnimationFrame(processAnimationIdRef.current); processAnimationIdRef.current = null; } processStartTimeRef.current = null; processTimeRef.current = 0; accumulatedPausedTimeRef.current = 0; lastPauseTimeRef.current = null; hasLoggedHalfway.current = false; hasLoggedCompleted.current = false; const object = scene.getObjectByProperty('uuid', human.modelUuid); if (object && human) { object.position.set(human.position[0], human.position[1], human.position[2]); object.rotation.set(human.rotation[0], human.rotation[1], human.rotation[2]); } } useEffect(() => { if (isPlaying) { const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); if (!action || !(action as HumanAction).assemblyPoint || (action as HumanAction).actionType === 'worker') return; if (!human.isActive && human.state === 'idle' && (currentPhase === 'init' || currentPhase === 'picking')) { const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); if (!humanMesh) return; const toPickupPath = computePath( new THREE.Vector3(...humanMesh.position.toArray()), new THREE.Vector3( (action as HumanAction)?.assemblyPoint?.position?.[0] ?? 0, (action as HumanAction)?.assemblyPoint?.position?.[1] ?? 0, (action as HumanAction)?.assemblyPoint?.position?.[2] ?? 0 ) ); setPath(toPickupPath); setHumanState(human.modelUuid, 'idle'); setCurrentPhase('init_assembly'); setHumanActive(human.modelUuid, false); setCurrentAnimation(human.modelUuid, 'idle', true, true, true); humanStatus(human.modelUuid, 'Human is waiting for material in assembly'); } else if (!human.isActive && human.state === 'idle' && currentPhase === 'waiting') { if (human.currentMaterials.length > 0 && humanAsset && humanAsset.animationState?.current !== 'working_standing') { setCurrentAnimation(human.modelUuid, 'working_standing', true, true, false); setHumanState(human.modelUuid, 'running'); setCurrentPhase('assembling'); setHumanActive(human.modelUuid, true); processStartTimeRef.current = performance.now(); processTimeRef.current = (action as HumanAction).processTime || 0; accumulatedPausedTimeRef.current = 0; lastPauseTimeRef.current = null; hasLoggedHalfway.current = false; hasLoggedCompleted.current = false; if (!processAnimationIdRef.current) { processAnimationIdRef.current = requestAnimationFrame(trackAssemblyProcess); } } } else if (human.isActive && human.state === 'running' && human.currentMaterials.length > 0 && humanAsset && humanAsset.animationState?.current === 'working_standing' && humanAsset.animationState?.isCompleted) { if ((action as HumanAction).assemblyPoint && currentPhase === 'assembling') { setHumanState(human.modelUuid, 'idle'); setCurrentPhase('waiting'); setHumanActive(human.modelUuid, false); setHumanScheduled(human.modelUuid, false); setCurrentAnimation(human.modelUuid, 'idle', true, true, true); humanStatus(human.modelUuid, 'Human is waiting for material in assembly'); decrementHumanLoad(human.modelUuid, 1); const material = removeLastMaterial(human.modelUuid); if (material) { triggerPointActions((action as HumanAction), material.materialId); } } } } else { reset() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [human, currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); const trackAssemblyProcess = useCallback(() => { const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const now = performance.now(); if (!processStartTimeRef.current || !(action as HumanAction).processTime || !action) { return; } if (isPausedRef.current) { if (!lastPauseTimeRef.current) { lastPauseTimeRef.current = now; } processAnimationIdRef.current = requestAnimationFrame(trackAssemblyProcess); return; } else if (lastPauseTimeRef.current) { accumulatedPausedTimeRef.current += now - lastPauseTimeRef.current; lastPauseTimeRef.current = null; } const elapsed = (now - processStartTimeRef.current - accumulatedPausedTimeRef.current) * isSpeedRef.current; const totalProcessTimeMs = ((action as HumanAction).processTime || 1) * 1000; if (elapsed >= totalProcessTimeMs / 2 && !hasLoggedHalfway.current) { hasLoggedHalfway.current = true; if (human.currentMaterials.length > 0) { setMaterial(human.currentMaterials[0].materialId, (action as HumanAction).swapMaterial || 'Default Material'); } humanStatus(human.modelUuid, `🟡 Human ${human.modelUuid} reached halfway in assembly.`); } if (elapsed >= totalProcessTimeMs && !hasLoggedCompleted.current) { hasLoggedCompleted.current = true; setCurrentAnimation(human.modelUuid, 'working_standing', true, true, true); if (processAnimationIdRef.current) { cancelAnimationFrame(processAnimationIdRef.current); processAnimationIdRef.current = null; } humanStatus(human.modelUuid, `✅ Human ${human.modelUuid} completed assembly process.`); return; } processAnimationIdRef.current = requestAnimationFrame(trackAssemblyProcess); }, [human.modelUuid, human.currentMaterials]); useEffect(() => { if (isPlaying) { const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); if (!action || action.actionType !== 'worker' || !action.pickUpPoint || !action.dropPoint) return; if (!human.isActive && human.state === 'idle' && (currentPhase === 'init' || currentPhase === 'waiting')) { const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); if (!humanMesh) return; const toPickupPath = computePath( new THREE.Vector3(...humanMesh.position.toArray()), new THREE.Vector3( action?.pickUpPoint?.position?.[0] ?? 0, action?.pickUpPoint?.position?.[1] ?? 0, action?.pickUpPoint?.position?.[2] ?? 0 ) ); setPath(toPickupPath); setCurrentPhase('init-pickup'); setHumanState(human.modelUuid, 'running'); setHumanActive(human.modelUuid, true); setCurrentAnimation(human.modelUuid, 'walking', true, true, true); humanStatus(human.modelUuid, 'Started from init, heading to pickup'); return; } else if (!human.isActive && human.state === 'idle' && currentPhase === 'picking') { if (humanAsset && human.currentLoad === action.loadCapacity && human.currentMaterials.length > 0 && humanAsset.animationState?.current === 'pickup' && humanAsset.animationState?.isCompleted) { if (action.pickUpPoint && action.dropPoint) { const toDrop = computePath( new THREE.Vector3( action.pickUpPoint.position?.[0] ?? 0, action.pickUpPoint.position?.[1] ?? 0, action.pickUpPoint.position?.[2] ?? 0 ), new THREE.Vector3( action.dropPoint.position?.[0] ?? 0, action.dropPoint.position?.[1] ?? 0, action.dropPoint.position?.[2] ?? 0 ) ); setPath(toDrop); setCurrentPhase('pickup-drop'); setHumanState(human.modelUuid, 'running'); setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true); humanStatus(human.modelUuid, 'Started from pickup point, heading to drop point'); } } else if (human.currentLoad === action.loadCapacity && human.currentMaterials.length > 0 && humanAsset?.animationState?.current !== 'pickup') { if (human.currentMaterials[0]?.materialId) { setIsVisible(human.currentMaterials[0]?.materialId, false); } setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); } } else if (!human.isActive && human.state === 'idle' && currentPhase === 'dropping' && human.currentLoad === 0) { if (action.pickUpPoint && action.dropPoint) { const dropToPickup = computePath( new THREE.Vector3( action.dropPoint.position?.[0] ?? 0, action.dropPoint.position?.[1] ?? 0, action.dropPoint.position?.[2] ?? 0 ), new THREE.Vector3( action.pickUpPoint.position?.[0] ?? 0, action.pickUpPoint.position?.[1] ?? 0, action.pickUpPoint.position?.[2] ?? 0 ) ); setPath(dropToPickup); setCurrentPhase('drop-pickup'); setHumanState(human.modelUuid, 'running'); setHumanActive(human.modelUuid, true); setCurrentAnimation(human.modelUuid, 'walking', true, true, true); humanStatus(human.modelUuid, 'Started from dropping point, heading to pickup point'); } } } else { reset() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [human, currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); function handleCallBack() { if (currentPhase === 'init-pickup') { setCurrentPhase('picking'); setHumanState(human.modelUuid, 'idle'); setHumanActive(human.modelUuid, false); setCurrentAnimation(human.modelUuid, 'idle', true, true, true); humanStatus(human.modelUuid, 'Reached pickup point, waiting for material'); setPath([]); } if (currentPhase === 'init_assembly') { setCurrentPhase('waiting'); setHumanState(human.modelUuid, 'idle'); setHumanActive(human.modelUuid, false); setCurrentAnimation(human.modelUuid, 'idle', true, true, true); humanStatus(human.modelUuid, 'Reached assembly point, waiting for material'); setPath([]); } else if (currentPhase === 'pickup-drop') { setCurrentPhase('dropping'); setHumanState(human.modelUuid, 'idle'); setHumanActive(human.modelUuid, false); setCurrentAnimation(human.modelUuid, 'drop', true, false, false); humanStatus(human.modelUuid, 'Reached drop point'); setPath([]); } else if (currentPhase === 'drop-pickup') { setCurrentPhase('picking'); setHumanState(human.modelUuid, 'idle'); setHumanActive(human.modelUuid, false); setHumanScheduled(human.modelUuid, false); setPath([]); clearCurrentMaterials(human.modelUuid); setCurrentAnimation(human.modelUuid, 'idle', true, true, true); humanStatus(human.modelUuid, 'Reached pickup point again, cycle complete'); } } function animate(currentTime: number) { if (previousTimeRef.current === null) { previousTimeRef.current = currentTime; } const deltaTime = (currentTime - previousTimeRef.current) / 1000; previousTimeRef.current = currentTime; if (human.isActive) { if (!isPausedRef.current) { activeTimeRef.current += deltaTime * isSpeedRef.current; } } else { if (!isPausedRef.current) { idleTimeRef.current += deltaTime * isSpeedRef.current; } } animationFrameIdRef.current = requestAnimationFrame(animate); } useEffect(() => { if (!isPlaying) return if (!human.isActive) { const roundedActiveTime = Math.round(activeTimeRef.current); incrementActiveTime(human.modelUuid, roundedActiveTime); activeTimeRef.current = 0; } else { const roundedIdleTime = Math.round(idleTimeRef.current); incrementIdleTime(human.modelUuid, roundedIdleTime); idleTimeRef.current = 0; } if (animationFrameIdRef.current === null) { animationFrameIdRef.current = requestAnimationFrame(animate); } return () => { if (animationFrameIdRef.current !== null) { cancelAnimationFrame(animationFrameIdRef.current); animationFrameIdRef.current = null; } }; }, [human, isPlaying]); function startUnloadingProcess() { const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { if ((action as HumanAction).triggers.length > 0) { const trigger = getTriggerByUuid(selectedProduct.productUuid, (action as HumanAction).triggers[0]?.triggerUuid); const model = getEventByModelUuid(selectedProduct.productUuid, trigger?.triggeredAsset?.triggeredModel?.modelUuid || ''); if (trigger && model) { if (model.type === 'transfer') { if (action) { handleMaterialDropToConveyor(model); } } else if (model.type === 'machine') { if (action) { handleMaterialDropToMachine(model); } } else if (model.type === 'roboticArm') { if (action) { handleMaterialDropToArmBot(model); } } else if (model.type === 'storageUnit') { if (action) { handleMaterialDropToStorageUnit(model); } } else if (model.type === 'vehicle') { if (action) { handleMaterialDropToVehicle(model); } } } else { const droppedMaterial = human.currentLoad; handleMaterialDropByDefault(droppedMaterial); } } else { const droppedMaterial = human.currentLoad; handleMaterialDropByDefault(droppedMaterial); } } else { requestAnimationFrame(startUnloadingProcess); } } function handleMaterialDropToStorageUnit(model: StorageEventSchema) { const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const humanAsset = getAssetById(human.modelUuid); if (model && humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } const checkAnimation = () => { if (humanAsset?.animationState?.isCompleted) { if (model.point.action.actionType === 'store') { loopMaterialDropToStorage( human.modelUuid, human.currentLoad, model.modelUuid, model.point.action.storageCapacity, (action as HumanAction) ); } } else { requestAnimationFrame(checkAnimation); } }; checkAnimation(); } function loopMaterialDropToStorage( humanId: string, humanCurrentLoad: number, storageUnitId: string, storageMaxCapacity: number, action: HumanAction ) { const storageUnit = getStorageUnitById(storageUnitId); const humanAsset = getAssetById(human.modelUuid); if (!storageUnit || humanCurrentLoad <= 0 || storageUnit.currentLoad >= storageMaxCapacity) { return; } decrementHumanLoad(humanId, 1); humanCurrentLoad -= 1; const material = removeLastMaterial(humanId); if (material) { triggerPointActions(action, material.materialId); } if (humanCurrentLoad > 0 && storageUnit.currentLoad < storageMaxCapacity) { resetAnimation(human.modelUuid); setCurrentAnimation(human.modelUuid, 'drop', true, false, false); const waitForNextDrop = () => { if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { loopMaterialDropToStorage( humanId, humanCurrentLoad, storageUnitId, storageMaxCapacity, action ); } else { requestAnimationFrame(waitForNextDrop); } }; waitForNextDrop(); } } function handleMaterialDropToConveyor(model: ConveyorEventSchema) { const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } const checkAnimation = () => { if (humanAsset?.animationState?.isCompleted) { const conveyor = getConveyorById(model.modelUuid); if (conveyor) { loopMaterialDropToConveyor( human.modelUuid, human.currentLoad, conveyor.modelUuid, (action as HumanAction) ); } } else { requestAnimationFrame(checkAnimation); } }; checkAnimation(); } function loopMaterialDropToConveyor( humanId: string, humanCurrentLoad: number, conveyorId: string, action: HumanAction ) { const conveyor = getConveyorById(conveyorId); const humanAsset = getAssetById(human.modelUuid); if (!conveyor || humanCurrentLoad <= 0) { return; } decrementHumanLoad(humanId, 1); humanCurrentLoad -= 1; const material = removeLastMaterial(humanId); if (material) { triggerPointActions(action, material.materialId); } if (humanCurrentLoad > 0) { resetAnimation(human.modelUuid); setCurrentAnimation(human.modelUuid, 'drop', true, false, false); const waitForNextDrop = () => { if (humanAsset?.animationState?.isCompleted) { loopMaterialDropToConveyor( humanId, humanCurrentLoad, conveyorId, action ); } else { requestAnimationFrame(waitForNextDrop); } }; waitForNextDrop(); } } function handleMaterialDropToArmBot(model: RoboticArmEventSchema) { const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } const checkAnimation = () => { if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { const armBot = getArmBotById(model.modelUuid); if (armBot && armBot.state === 'idle' && !armBot.isActive) { loopMaterialDropToArmBot( human.modelUuid, human.currentLoad, model.modelUuid, (action as HumanAction) ); } } else { requestAnimationFrame(checkAnimation); } }; checkAnimation(); } function loopMaterialDropToArmBot( humanId: string, humanCurrentLoad: number, armBotId: string, action: HumanAction ) { const armBot = getArmBotById(armBotId); const humanAsset = getAssetById(human.modelUuid); if (!armBot || armBot.state !== 'idle' || armBot.isActive || humanCurrentLoad <= 0) { return; } decrementHumanLoad(humanId, 1); humanCurrentLoad -= 1; const material = removeLastMaterial(humanId); if (material) { triggerPointActions(action, material.materialId); } if (humanCurrentLoad > 0) { resetAnimation(human.modelUuid); setCurrentAnimation(human.modelUuid, 'drop', true, false, false); const waitForNextTransfer = () => { const currentArmBot = getArmBotById(armBotId); if (currentArmBot && currentArmBot.state === 'idle' && !currentArmBot.isActive) { if (humanAsset?.animationState?.isCompleted) { loopMaterialDropToArmBot( humanId, humanCurrentLoad, armBotId, action ); } else { requestAnimationFrame(waitForNextTransfer); } } else { requestAnimationFrame(waitForNextTransfer); } }; waitForNextTransfer(); } } function handleMaterialDropToVehicle(model: VehicleEventSchema) { const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } const checkAnimation = () => { if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { const vehicle = getVehicleById(model.modelUuid); if (vehicle && vehicle.state === 'idle' && !vehicle.isActive) { loopMaterialDropToVehicle( human.modelUuid, human.currentLoad, model.modelUuid, (action as HumanAction) ); } } else { requestAnimationFrame(checkAnimation); } }; checkAnimation(); } function loopMaterialDropToVehicle( humanId: string, humanCurrentLoad: number, vehicleId: string, action: HumanAction ) { const vehicle = getVehicleById(vehicleId); const humanAsset = getAssetById(human.modelUuid); if (!vehicle || vehicle.state !== 'idle' || vehicle.isActive || humanCurrentLoad <= 0) { return; } decrementHumanLoad(humanId, 1); humanCurrentLoad -= 1; const material = removeLastMaterial(humanId); if (material) { triggerPointActions(action, material.materialId); } if (humanCurrentLoad > 0) { resetAnimation(human.modelUuid); setCurrentAnimation(human.modelUuid, 'drop', true, false, false); const waitForNextTransfer = () => { const currentVehicle = getVehicleById(vehicleId); if (currentVehicle && currentVehicle.state === 'idle' && !currentVehicle.isActive) { if (humanAsset?.animationState?.isCompleted) { loopMaterialDropToVehicle( humanId, humanCurrentLoad, vehicleId, action ); } else { requestAnimationFrame(waitForNextTransfer); } } else { requestAnimationFrame(waitForNextTransfer); } }; waitForNextTransfer(); } } function handleMaterialDropToMachine(model: MachineEventSchema) { const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } const checkAnimation = () => { if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { const machine = getMachineById(model.modelUuid); if (machine && machine.state === 'idle' && !machine.isActive) { loopMaterialDropToMachine( human.modelUuid, human.currentLoad, model.modelUuid, (action as HumanAction) ); } } else { requestAnimationFrame(checkAnimation); } }; checkAnimation(); } function loopMaterialDropToMachine( humanId: string, humanCurrentLoad: number, machineId: string, action: HumanAction ) { const machine = getMachineById(machineId); const humanAsset = getAssetById(human.modelUuid); if (!machine || machine.state !== 'idle' || machine.isActive || humanCurrentLoad <= 0) { return; } decrementHumanLoad(humanId, 1); humanCurrentLoad -= 1; const material = removeLastMaterial(humanId); if (material) { triggerPointActions(action, material.materialId); } if (humanCurrentLoad > 0) { resetAnimation(human.modelUuid); setCurrentAnimation(human.modelUuid, 'drop', true, false, false); const waitForNextTransfer = () => { const currentMachine = getMachineById(machineId); if (currentMachine && currentMachine.state === 'idle' && !currentMachine.isActive) { if (humanAsset?.animationState?.isCompleted) { loopMaterialDropToMachine( humanId, humanCurrentLoad, machineId, action ); } else { requestAnimationFrame(waitForNextTransfer); } } else { requestAnimationFrame(waitForNextTransfer); } }; waitForNextTransfer(); } } function handleMaterialDropByDefault(droppedMaterial: number) { const humanAsset = getAssetById(human.modelUuid); if (humanAsset?.animationState?.current !== 'drop') { setCurrentAnimation(human.modelUuid, 'drop', true, false, false); } if (humanAsset?.animationState?.isCompleted) { const remainingMaterials = droppedMaterial - 1; decrementHumanLoad(human.modelUuid, 1); const material = removeLastMaterial(human.modelUuid); if (material) { setEndTime(material.materialId, performance.now()); removeMaterial(material.materialId); } if (remainingMaterials > 0) { resetAnimation(human.modelUuid); setCurrentAnimation(human.modelUuid, 'drop', true, false, false); requestAnimationFrame(() => handleMaterialDropByDefault(remainingMaterials)); } return; } requestAnimationFrame(() => handleMaterialDropByDefault(droppedMaterial)); } return ( <> ) } export default HumanInstance