diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx index ba74c7c..e371f6f 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/assemblyAction.tsx @@ -1,5 +1,6 @@ import React from "react"; import InputRange from "../../../../../ui/inputs/InputRange"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import SwapAction from "./SwapAction"; interface AssemblyActionProps { @@ -10,6 +11,15 @@ interface AssemblyActionProps { disabled?: boolean, onChange: (value: number) => void; }; + assemblyCount: { + value: number; + min: number; + max: number; + step: number; + defaultValue: string, + disabled: false, + onChange: (value: number) => void; + } swapOptions: string[]; swapDefaultOption: string; onSwapSelect: (value: string) => void; @@ -18,6 +28,7 @@ interface AssemblyActionProps { const AssemblyAction: React.FC = ({ processTime, + assemblyCount, swapOptions, swapDefaultOption, onSwapSelect, @@ -34,6 +45,21 @@ const AssemblyAction: React.FC = ({ onClick={() => { }} onChange={processTime.onChange} /> + + {assemblyCount && ( + { }} + onChange={(value) => assemblyCount.onChange(parseInt(value))} + /> + )} void; }; - loadCount?: { + loadCount: { value: number; min: number; max: number; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx index 6d91d1c..6109361 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -20,6 +20,7 @@ function HumanMechanics() { const [activeOption, setActiveOption] = useState<"worker" | "assembly">("worker"); const [speed, setSpeed] = useState("0.5"); const [loadCount, setLoadCount] = useState(0); + const [assemblyCount, setAssemblyCount] = useState(0); const [loadCapacity, setLoadCapacity] = useState("1"); const [processTime, setProcessTime] = useState(10); const [swappedMaterial, setSwappedMaterial] = useState("Default material"); @@ -56,6 +57,7 @@ function HumanMechanics() { setLoadCapacity(firstAction.loadCapacity.toString()); setActiveOption(firstAction.actionType); setLoadCount(firstAction.loadCount || 0); + setAssemblyCount(firstAction.assemblyCount || 0); setProcessTime(firstAction.processTime || 10); setSwappedMaterial(firstAction.swapMaterial || "Default material"); } @@ -84,6 +86,7 @@ function HumanMechanics() { setActiveOption(newCurrentAction.actionType); setLoadCapacity(newCurrentAction.loadCapacity.toString()); setLoadCount(newCurrentAction.loadCount || 0); + setAssemblyCount(newCurrentAction.assemblyCount || 0); if (newCurrentAction.actionType === 'assembly') { setProcessTime(newCurrentAction.processTime || 10); @@ -200,6 +203,28 @@ function HumanMechanics() { setLoadCount(value); }; + const handleAssemblyCountChange = (value: number) => { + if (!currentAction || !selectedPointData || !selectedAction.actionId) return; + + const updatedAction = { ...currentAction, assemblyCount: value }; + const updatedActions = selectedPointData.actions.map(action => action.actionUuid === updatedAction.actionUuid ? updatedAction : action); + const updatedPoint = { ...selectedPointData, actions: updatedActions }; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + updatedAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setCurrentAction(updatedAction); + setSelectedPointData(updatedPoint); + setAssemblyCount(value); + }; + const handleProcessTimeChange = (value: number) => { if (!currentAction || !selectedPointData || !selectedAction.actionId) return; @@ -281,6 +306,7 @@ function HumanMechanics() { actionName: `Action ${selectedPointData.actions.length + 1}`, actionType: "worker", loadCount: 1, + assemblyCount: 1, loadCapacity: 1, processTime: 10, triggers: [], @@ -389,7 +415,7 @@ function HumanMechanics() { }} loadCount={{ value: loadCount, - min: 0, + min: 1, max: 20, step: 1, defaultValue: "1", @@ -407,6 +433,15 @@ function HumanMechanics() { max: 60, onChange: handleProcessTimeChange, }} + assemblyCount={{ + value: assemblyCount, + min: 1, + max: 20, + step: 1, + defaultValue: "1", + disabled: false, + onChange: handleAssemblyCountChange, + }} swapOptions={["Default material", "Material 1", "Material 2", "Material 3"]} swapDefaultOption={swappedMaterial} onSwapSelect={handleMaterialChange} diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index 85c0f88..deda3d8 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -265,6 +265,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { actionName: "Action 1", actionType: "worker", loadCount: 1, + assemblyCount: 1, loadCapacity: 1, processTime: 10, triggers: [] diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 4b72cd3..80d8b0a 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -384,6 +384,7 @@ async function handleModelLoad( actionName: "Action 1", actionType: "worker", loadCount: 1, + assemblyCount: 1, loadCapacity: 1, processTime: 10, triggers: [] diff --git a/app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx b/app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx new file mode 100644 index 0000000..d2c76eb --- /dev/null +++ b/app/src/modules/builder/asset/models/model/animator/modelAnimator.tsx @@ -0,0 +1,94 @@ +import { useEffect, useRef, useCallback, useState } from 'react'; +import * as THREE from 'three'; +import { useFrame } from '@react-three/fiber'; +import { useSceneContext } from '../../../../../scene/sceneContext'; +import useModuleStore from '../../../../../../store/useModuleStore'; +import { usePauseButtonStore, useAnimationPlaySpeed } from '../../../../../../store/usePlayButtonStore'; + +interface ModelAnimatorProps { + asset: Asset; + gltfScene: THREE.Object3D; +} + +export function ModelAnimator({ + asset, + gltfScene, +}: ModelAnimatorProps) { + const mixerRef = useRef(); + const actions = useRef<{ [name: string]: THREE.AnimationAction }>({}); + const [previousAnimation, setPreviousAnimation] = useState(null); + + const blendFactor = useRef(0); + const blendDuration = 0.5; + + const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); + const { assetStore } = useSceneContext(); + const { activeModule } = useModuleStore(); + const { setAnimations, setAnimationComplete } = assetStore(); + + const handleAnimationComplete = useCallback(() => { + if (asset.animationState) { + setAnimationComplete(asset.modelUuid, true); + } + }, [asset.animationState]); + + useEffect(() => { + if (!gltfScene || !gltfScene.animations || gltfScene.animations.length === 0) return; + + mixerRef.current = new THREE.AnimationMixer(gltfScene); + + gltfScene.animations.forEach((clip: THREE.AnimationClip) => { + const action = mixerRef.current!.clipAction(clip); + actions.current[clip.name] = action; + }); + + const animationNames = gltfScene.animations.map(clip => clip.name); + setAnimations(asset.modelUuid, animationNames); + + return () => { + mixerRef.current?.stopAllAction(); + mixerRef.current = undefined; + }; + }, [gltfScene]); + + useEffect(() => { + if (!asset.animationState || !mixerRef.current) return; + + const { current, loopAnimation, isPlaying } = asset.animationState; + const currentAction = actions.current[current]; + const previousAction = previousAnimation ? actions.current[previousAnimation] : null; + + if (isPlaying && currentAction && !isPaused) { + blendFactor.current = 0; + + currentAction.reset(); + currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1); + currentAction.clampWhenFinished = true; + + if (previousAction && previousAction !== currentAction) { + previousAction.crossFadeTo(currentAction, blendDuration, false); + } + + currentAction.play(); + mixerRef.current.addEventListener('finished', handleAnimationComplete); + setPreviousAnimation(current); + } else { + Object.values(actions.current).forEach((action) => action.stop()); + } + + return () => { + if (mixerRef.current) { + mixerRef.current.removeEventListener('finished', handleAnimationComplete); + } + }; + }, [asset.animationState?.current, asset.animationState?.isCompleted, asset.animationState?.isPlaying, isPaused, activeModule]); + + useFrame((_, delta) => { + if (mixerRef.current) { + mixerRef.current.update(delta * (activeModule === 'simulation' ? speed : 1)); + } + }); + + return null; +} diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx index 0d7cf7e..8fd29e5 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx @@ -404,6 +404,7 @@ const CopyPasteControls3D = ({ actionName: "Action 1", actionType: "worker", loadCapacity: 1, + assemblyCount: 1, loadCount: 1, processTime: 10, triggers: [] diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index f4f5048..d32468a 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -405,6 +405,7 @@ const DuplicationControls3D = ({ actionName: "Action 1", actionType: "worker", loadCapacity: 1, + assemblyCount: 1, loadCount: 1, processTime: 10, triggers: [] diff --git a/app/src/modules/scene/sceneContext.tsx b/app/src/modules/scene/sceneContext.tsx index 67811c4..187d66a 100644 --- a/app/src/modules/scene/sceneContext.tsx +++ b/app/src/modules/scene/sceneContext.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useMemo } from 'react'; +import { createContext, useContext, useMemo, useRef } from 'react'; import { createAssetStore, AssetStoreType } from '../../store/builder/useAssetStore'; import { createWallAssetStore, WallAssetStoreType } from '../../store/builder/useWallAssetStore'; @@ -38,6 +38,8 @@ type SceneContextValue = { storageUnitStore: StorageUnitStoreType; humanStore: HumanStoreType; + humanEventManagerRef: React.RefObject; + clearStores: () => void; layout: 'Main Layout' | 'Comparison Layout'; @@ -71,6 +73,8 @@ export function SceneProvider({ const storageUnitStore = useMemo(() => createStorageUnitStore(), []); const humanStore = useMemo(() => createHumanStore(), []); + const humanEventManagerRef = useRef({ humanStates: [] }); + const clearStores = useMemo(() => () => { assetStore.getState().clearAssets(); wallAssetStore.getState().clearWallAssets(); @@ -87,6 +91,7 @@ export function SceneProvider({ vehicleStore.getState().clearVehicles(); storageUnitStore.getState().clearStorageUnits(); humanStore.getState().clearHumans(); + humanEventManagerRef.current.humanStates = []; }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]); const contextValue = useMemo(() => ( @@ -106,6 +111,7 @@ export function SceneProvider({ vehicleStore, storageUnitStore, humanStore, + humanEventManagerRef, clearStores, layout } diff --git a/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx b/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx index 120058b..148c59e 100644 --- a/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx +++ b/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx @@ -1,24 +1,24 @@ import { useEffect } from 'react' -import { useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useFrame } from '@react-three/fiber' import { useSceneContext } from '../../../../scene/sceneContext'; import { useProductContext } from '../../../products/productContext'; // import { findConveyorSubsequence } from '../../../simulator/functions/getConveyorSequencesInProduct'; function ConveyorInstance({ conveyor }: { readonly conveyor: ConveyorStatus }) { + const { materialStore, conveyorStore, productStore } = useSceneContext(); const { getProductById } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const { getMaterialsByCurrentModelUuid, materials } = materialStore(); - const { isReset } = useResetButtonStore(); + const { getMaterialsByCurrentModelUuid } = materialStore(); const { setConveyorPaused } = conveyorStore(); - useEffect(() => { + useFrame(() => { const product = getProductById(selectedProduct.productUuid); if (!product) return; const conveyorMaterials = getMaterialsByCurrentModelUuid(conveyor.modelUuid); - if (conveyorMaterials && conveyorMaterials?.length > 0) { + if (conveyorMaterials && conveyorMaterials.length > 0) { const hasPausedMaterials = conveyorMaterials.some(material => material.isPaused); @@ -52,7 +52,7 @@ function ConveyorInstance({ conveyor }: { readonly conveyor: ConveyorStatus }) { // } // }); - }, [materials, conveyor.modelUuid, getMaterialsByCurrentModelUuid, setConveyorPaused, isReset, selectedProduct.productUuid, getProductById]); + }); useEffect(() => { // console.log('conveyor: ', conveyor); @@ -61,7 +61,7 @@ function ConveyorInstance({ conveyor }: { readonly conveyor: ConveyorStatus }) { return ( <> - ) -}; + ); +} export default ConveyorInstance \ No newline at end of file diff --git a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts index ac5a478..f023852 100644 --- a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts +++ b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts @@ -1,195 +1,142 @@ -import { useEffect, useRef } from 'react'; +import { useEffect } from 'react'; import { useFrame } from '@react-three/fiber'; import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../store/usePlayButtonStore'; import { useSceneContext } from '../../../scene/sceneContext'; import { useProductContext } from '../../products/productContext'; export function useHumanEventManager() { - const { humanStore, productStore, assetStore } = useSceneContext(); - const { getHumanById, setCurrentPhase } = humanStore(); + const { humanStore, productStore, assetStore, humanEventManagerRef } = useSceneContext(); + const { getHumanById, setCurrentPhase, removeCurrentAction } = humanStore(); const { getAssetById } = assetStore(); const { getActionByUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const stateRef = useRef({ - humanStates: new Map void)[], - actionQueue: { actionType: "worker" | "assembly", actionUuid: string, actionName: string }[], - isCooldown: boolean - }>(), - callbackCounts: new Map>(), - isMonitoring: false - }); - const { isPlaying } = usePlayButtonStore(); const { isPaused } = usePauseButtonStore(); const { isReset } = useResetButtonStore(); useEffect(() => { - if (isReset) { - stateRef.current.humanStates.clear(); - stateRef.current.callbackCounts.clear(); - stateRef.current.isMonitoring = false; + if ((isReset || !isPlaying) && humanEventManagerRef.current) { + humanEventManagerRef.current.humanStates = []; } - }, [isReset]); + }, [isReset, isPlaying]); const addHumanToMonitor = (humanId: string, callback: () => void, actionUuid: string) => { - const action = getActionByUuid(selectedProduct.productUuid, actionUuid || '') as HumanAction | undefined; + const human = getHumanById(humanId); + const action = getActionByUuid(selectedProduct.productUuid, actionUuid); + if (!human || !action || (action.actionType !== 'assembly' && action.actionType !== 'worker') || !humanEventManagerRef.current) return; - if (!action) return; - - const actionType = action.actionType; - if (actionType !== "worker" && actionType !== "assembly") return; - - if (!stateRef.current.callbackCounts.has(humanId)) { - stateRef.current.callbackCounts.set(humanId, new Map()); + let state = humanEventManagerRef.current.humanStates.find(h => h.humanId === humanId); + if (!state) { + state = { humanId, actionQueue: [], isCooldown: false }; + humanEventManagerRef.current.humanStates.push(state); } - const actionCounts = stateRef.current.callbackCounts.get(humanId)!; - if (!actionCounts.has(actionUuid)) { - actionCounts.set(actionUuid, 0); - } - - const currentCount = actionCounts.get(actionUuid)!; - if (actionType === 'worker' && currentCount >= action.loadCount) { + const existingAction = state.actionQueue.find(a => a.actionUuid === actionUuid); + if (existingAction) { + const currentCount = existingAction.count ?? 0; + if (existingAction.actionType === 'worker') { + if (currentCount < existingAction.maxLoadCount) { + existingAction.callback = callback; + existingAction.isMonitored = true; + existingAction.isCompleted = false; + } + } else if (existingAction.actionType === 'assembly') { + if (currentCount < existingAction.maxAssemblyCount) { + existingAction.callback = callback; + existingAction.isMonitored = true; + existingAction.isCompleted = false; + } + } return; } - if (!stateRef.current.humanStates.has(humanId)) { - stateRef.current.humanStates.set(humanId, { - callbacks: [], - actionQueue: [], - isCooldown: false - }); - } - - const humanState = stateRef.current.humanStates.get(humanId)!; - humanState.callbacks.push(callback); - humanState.actionQueue.push({ actionType, actionUuid, actionName: action.actionName }); - - stateRef.current.isMonitoring = true; + state.actionQueue.push({ + actionType: action.actionType, + actionUuid, + actionName: action.actionName, + maxLoadCount: action.loadCount ?? 0, + maxAssemblyCount: action.assemblyCount ?? 0, + count: 0, + isMonitored: true, + isCompleted: false, + callback + }); }; - const removeHumanFromMonitor = (humanId: string) => { - // stateRef.current.humanStates.delete(humanId); + const removeHumanFromMonitor = (humanId: string, actionUuid: string) => { + if (!humanEventManagerRef.current) return; + const state = humanEventManagerRef.current.humanStates.find(h => h.humanId === humanId); + if (!state) return; - if (stateRef.current.humanStates.size === 0) { - stateRef.current.isMonitoring = false; + const action = state.actionQueue.find(a => a.actionUuid === actionUuid); + if (action) { + action.callback = undefined; + action.isMonitored = false; } }; - const getCallbackCount = (humanId: string, actionUuid: string) => { - if (!stateRef.current.callbackCounts.has(humanId)) return 0; - return stateRef.current.callbackCounts.get(humanId)!.get(actionUuid) || 0; - }; - - const incrementCallbackCount = (humanId: string, actionUuid: string) => { - if (!stateRef.current.callbackCounts.has(humanId)) { - stateRef.current.callbackCounts.set(humanId, new Map()); - } - const actionCounts = stateRef.current.callbackCounts.get(humanId)!; - const currentCount = actionCounts.get(actionUuid) || 0; - actionCounts.set(actionUuid, currentCount + 1); - }; - useFrame(() => { - if (!stateRef.current.isMonitoring || !isPlaying || isPaused) return; + if (!humanEventManagerRef.current || humanEventManagerRef.current.humanStates.length === 0 || !isPlaying || isPaused) return; - stateRef.current.humanStates.forEach((humanState, humanId) => { - if (humanState.callbacks.length === 0 || humanState.isCooldown) return; + for (const humanState of humanEventManagerRef.current.humanStates) { + if (humanState.isCooldown) continue; - const actionQueue = humanState.actionQueue; - if (!actionQueue || actionQueue.length === 0) return; + const { humanId, actionQueue } = humanState; + if (!actionQueue || actionQueue.length === 0) continue; + + const action = actionQueue.find(a => !a.isCompleted); + if (!action || !action.isMonitored || !action.callback) continue; - const { actionType: expectedActionType, actionUuid } = actionQueue[0]; const human = getHumanById(humanId); const humanAsset = getAssetById(humanId); - const action = getActionByUuid(selectedProduct.productUuid, actionUuid) as HumanAction | undefined; + const currentAction = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '') as HumanAction | undefined; - if (!humanAsset || !human || !action || action.actionType !== expectedActionType) return; - - const currentCount = getCallbackCount(humanId, actionUuid); - - const currentAction = getActionByUuid(selectedProduct.productUuid, human.currentAction?.actionUuid || '') as HumanAction | undefined; + if (!human || !humanAsset || !currentAction) continue; + if (human.isActive || human.state !== "idle" || humanAsset.animationState?.current !== 'idle') continue; let conditionMet = false; - if (expectedActionType === "worker") { - if (currentAction && currentAction.actionType === 'worker' && currentCount >= currentAction.loadCount) { - humanState.callbacks.shift(); - actionQueue.shift(); - if (humanState.callbacks.length === 0) { - removeHumanFromMonitor(humanId); - } - return; + if (currentAction.actionType === 'worker') { + if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) { + conditionMet = true; + } else if (action.actionType === 'assembly') { + conditionMet = true; } - - if (currentAction && currentAction.actionType === 'worker') { - conditionMet = ( - !human.isActive && - human.state === "idle" && - humanAsset.animationState?.current === 'idle' && - human.currentLoad < currentAction.loadCapacity - ); - - if (conditionMet && actionUuid !== human.currentAction?.actionUuid) { - setCurrentPhase(human.modelUuid, 'init'); - } - } else { - conditionMet = ( - !human.isActive && - human.state === "idle" && - humanAsset.animationState?.current === 'idle' - ); - if (conditionMet && actionUuid !== human.currentAction?.actionUuid) { - setCurrentPhase(human.modelUuid, 'init'); - } - } - - } else if (expectedActionType === "assembly") { - if (currentAction && currentAction.actionType === 'worker') { - conditionMet = ( - !human.isActive && - human.state === "idle" && - humanAsset.animationState?.current === 'idle' && - human.currentLoad < currentAction.loadCapacity - ); - if (conditionMet && actionUuid !== human.currentAction?.actionUuid) { - setCurrentPhase(human.modelUuid, 'init'); - } - } else { - conditionMet = ( - !human.isActive && - human.state === "idle" && - humanAsset.animationState?.current === 'idle' - ) + } else if (currentAction.actionType === 'assembly') { + if (action.actionType === 'assembly') { + conditionMet = true; + } else if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) { + conditionMet = true; } } if (conditionMet) { - const callback = humanState.callbacks.shift(); - actionQueue.shift(); - - if (callback) { - callback(); - incrementCallbackCount(humanId, actionUuid); + if (action.actionUuid !== human.currentAction?.actionUuid) { + setCurrentPhase(human.modelUuid, 'init'); + removeCurrentAction(human.modelUuid); } - if (humanState.callbacks.length === 0) { - removeHumanFromMonitor(humanId); - } else { - humanState.isCooldown = true; - setTimeout(() => { - humanState.isCooldown = false; - }, 1000); + action.callback(); + action.count = (action.count ?? 0) + 1; + action.isMonitored = false; + if ((action.actionType === 'worker' && action.count >= action.maxLoadCount) || + (action.actionType === 'assembly' && action.count >= action.maxAssemblyCount)) { + action.isCompleted = true; } + humanState.isCooldown = true; + setTimeout(() => { + humanState.isCooldown = false; + }, 1000); + + removeHumanFromMonitor(human.modelUuid, action.actionUuid); } - }); - }); + } + }, 0); return { addHumanToMonitor, - removeHumanFromMonitor, + removeHumanFromMonitor }; } \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/animator/assemblerAnimator.tsx b/app/src/modules/simulation/human/instances/animator/assemblerAnimator.tsx new file mode 100644 index 0000000..d891d8f --- /dev/null +++ b/app/src/modules/simulation/human/instances/animator/assemblerAnimator.tsx @@ -0,0 +1,172 @@ +import { useEffect, useRef, useState } from 'react'; +import { useFrame, useThree } from '@react-three/fiber'; +import * as THREE from 'three'; +import { Line } from '@react-three/drei'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../../scene/sceneContext'; +import { useProductContext } from '../../../products/productContext'; + +interface AssemblerAnimatorProps { + path: [number, number, number][]; + handleCallBack: () => void; + reset: () => void; + human: HumanStatus; +} + +function AssemblerAnimator({ path, handleCallBack, human, reset }: Readonly) { + const { humanStore, assetStore, productStore } = useSceneContext(); + const { getActionByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { getHumanById } = humanStore(); + const { setCurrentAnimation } = assetStore(); + const { isPaused } = usePauseButtonStore(); + const { isPlaying } = usePlayButtonStore(); + const { speed } = useAnimationPlaySpeed(); + const { isReset, setReset } = useResetButtonStore(); + const progressRef = useRef(0); + const completedRef = useRef(false); + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); + const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>((action as HumanAction)?.assemblyPoint?.rotation || [0, 0, 0]); + const [restRotation, setRestingRotation] = useState(true); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + const { scene } = useThree(); + + useEffect(() => { + if (!human.currentAction?.actionUuid) return; + if (human.currentPhase === 'init-assembly' && path.length > 0) { + setCurrentPath(path); + setObjectRotation((action as HumanAction)?.assemblyPoint?.rotation ?? null); + } + }, [human.currentPhase, path, objectRotation, selectedProduct, human.currentAction?.actionUuid]); + + useEffect(() => { + completedRef.current = false; + }, [currentPath]); + + useEffect(() => { + if (isReset || !isPlaying) { + reset(); + setCurrentPath([]); + completedRef.current = false; + progressRef.current = 0; + setReset(false); + setRestingRotation(true); + const object = scene.getObjectByProperty('uuid', human.modelUuid); + const humanData = getHumanById(human.modelUuid); + if (object && humanData) { + object.position.set(humanData.position[0], humanData.position[1], humanData.position[2]); + object.rotation.set(humanData.rotation[0], humanData.rotation[1], humanData.rotation[2]); + } + } + }, [isReset, isPlaying]); + + const lastTimeRef = useRef(performance.now()); + + useFrame(() => { + const now = performance.now(); + const delta = (now - lastTimeRef.current) / 1000; + lastTimeRef.current = now; + + const object = scene.getObjectByProperty('uuid', human.modelUuid); + if (!object || currentPath.length < 2) return; + if (isPaused || !isPlaying) return; + + let totalDistance = 0; + const distances = []; + let accumulatedDistance = 0; + let index = 0; + const rotationSpeed = 1.5; + + for (let i = 0; i < currentPath.length - 1; i++) { + const start = new THREE.Vector3(...currentPath[i]); + const end = new THREE.Vector3(...currentPath[i + 1]); + const segmentDistance = start.distanceTo(end); + distances.push(segmentDistance); + totalDistance += segmentDistance; + } + + while (index < distances.length && progressRef.current > accumulatedDistance + distances[index]) { + accumulatedDistance += distances[index]; + index++; + } + + if (index < distances.length) { + const start = new THREE.Vector3(...currentPath[index]); + const end = new THREE.Vector3(...currentPath[index + 1]); + const segmentDistance = distances[index]; + + const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix( + new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0)) + ); + const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI); + targetQuaternion.multiply(y180); + + const angle = object.quaternion.angleTo(targetQuaternion); + if (angle < 0.01) { + object.quaternion.copy(targetQuaternion); + } else { + const step = rotationSpeed * delta * speed * human.speed; + object.quaternion.rotateTowards(targetQuaternion, step); + } + + const isAligned = angle < 0.01; + + if (isAligned) { + progressRef.current += delta * (speed * human.speed); + const t = (progressRef.current - accumulatedDistance) / segmentDistance; + const position = start.clone().lerp(end, t); + object.position.copy(position); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + } else { + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + } + } + + if (progressRef.current >= totalDistance) { + if (restRotation && objectRotation) { + const targetEuler = new THREE.Euler(0, objectRotation[1], 0); + const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler); + const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI); + const targetQuaternion = baseQuaternion.multiply(y180); + + const angle = object.quaternion.angleTo(targetQuaternion); + if (angle < 0.01) { + object.quaternion.copy(targetQuaternion); + setRestingRotation(false); + } else { + const step = rotationSpeed * delta * speed * human.speed; + object.quaternion.rotateTowards(targetQuaternion, step); + } + + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + return; + } + } + + if (progressRef.current >= totalDistance) { + setRestingRotation(true); + progressRef.current = 0; + setCurrentPath([]); + handleCallBack(); + } + }); + + return ( + <> + {currentPath.length > 0 && ( + + + {currentPath.map((point, index) => ( + + + + + ))} + + )} + + ); +} + +export default AssemblerAnimator; \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx index 9cf0154..178cc4e 100644 --- a/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx +++ b/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx @@ -50,7 +50,7 @@ const MaterialAnimator = ({ human }: { human: HumanStatus; }) => { return ( <> - {hasLoad && (action as HumanAction).actionType === 'worker' && human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup') && ( + {hasLoad && action && (action as HumanAction).actionType === 'worker' && human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup') && ( void; reset: () => void; @@ -14,7 +14,7 @@ interface HumanAnimatorProps { human: HumanStatus; } -function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProcess }: Readonly) { +function WorkerAnimator({ path, handleCallBack, human, reset, startUnloadingProcess }: Readonly) { const { humanStore, assetStore, productStore } = useSceneContext(); const { getActionByUuid } = productStore(); const { selectedProductStore } = useProductContext(); @@ -29,7 +29,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce const movingForward = useRef(true); const completedRef = useRef(false); const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); - const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>((action as HumanAction)?.pickUpPoint?.rotation || [0, 0, 0]) + const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>((action as HumanAction)?.pickUpPoint?.rotation || [0, 0, 0]); const [restRotation, setRestingRotation] = useState(true); const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); const { scene } = useThree(); @@ -39,15 +39,12 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); if (human.currentPhase === 'init-pickup' && path.length > 0) { setCurrentPath(path); - setObjectRotation((action as HumanAction).pickUpPoint?.rotation ?? null) - } else if (human.currentPhase === 'init-assembly' && path.length > 0) { - setObjectRotation((action as HumanAction)?.assemblyPoint?.rotation ?? null) - setCurrentPath(path); + setObjectRotation((action as HumanAction).pickUpPoint?.rotation ?? null); } else if (human.currentPhase === 'pickup-drop' && path.length > 0) { - setObjectRotation((action as HumanAction)?.dropPoint?.rotation ?? null) + setObjectRotation((action as HumanAction)?.dropPoint?.rotation ?? null); setCurrentPath(path); } else if (human.currentPhase === 'drop-pickup' && path.length > 0) { - setObjectRotation((action as HumanAction)?.pickUpPoint?.rotation ?? null) + setObjectRotation((action as HumanAction)?.pickUpPoint?.rotation ?? null); setCurrentPath(path); } }, [human.currentPhase, path, objectRotation, selectedProduct, human.currentAction?.actionUuid]); @@ -72,7 +69,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce object.rotation.set(humanData.rotation[0], humanData.rotation[1], humanData.rotation[2]); } } - }, [isReset, isPlaying]) + }, [isReset, isPlaying]); const lastTimeRef = useRef(performance.now()); @@ -109,7 +106,9 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce const end = new THREE.Vector3(...currentPath[index + 1]); const segmentDistance = distances[index]; - const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0))); + const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix( + new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0)) + ); const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI); targetQuaternion.multiply(y180); @@ -118,13 +117,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce object.quaternion.copy(targetQuaternion); } else { const step = rotationSpeed * delta * speed * human.speed; - const angle = object.quaternion.angleTo(targetQuaternion); - - if (angle < step) { - object.quaternion.copy(targetQuaternion); - } else { - object.quaternion.rotateTowards(targetQuaternion, step); - } + object.quaternion.rotateTowards(targetQuaternion, step); } const isAligned = angle < 0.01; @@ -134,13 +127,13 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce const t = (progressRef.current - accumulatedDistance) / segmentDistance; const position = start.clone().lerp(end, t); object.position.copy(position); - if (human.currentMaterials.length > 0 && action?.actionType === 'worker' && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) { + if (human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) { setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true); } else { setCurrentAnimation(human.modelUuid, 'walking', true, true, true); } } else { - if (human.currentMaterials.length > 0 && action?.actionType === 'worker' && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) { + if (human.currentMaterials.length > 0 && (human.currentPhase !== 'init-pickup' && human.currentPhase !== 'init-assembly' && human.currentPhase !== 'drop-pickup')) { setCurrentAnimation(human.modelUuid, 'idle_with_box', true, true, true); } else { setCurrentAnimation(human.modelUuid, 'idle', true, true, true); @@ -151,7 +144,6 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce if (progressRef.current >= totalDistance) { if (restRotation && objectRotation) { const targetEuler = new THREE.Euler(0, objectRotation[1], 0); - const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler); const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI); const targetQuaternion = baseQuaternion.multiply(y180); @@ -162,21 +154,14 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce setRestingRotation(false); } else { const step = rotationSpeed * delta * speed * human.speed; - const angle = object.quaternion.angleTo(targetQuaternion); - - if (angle < step) { - object.quaternion.copy(targetQuaternion); - } else { - object.quaternion.rotateTowards(targetQuaternion, step); - } - } - - if (human.currentMaterials.length > 0) { - setCurrentAnimation(human.modelUuid, 'idle_with_box', true, true, true); - } else { - setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + object.quaternion.rotateTowards(targetQuaternion, step); } + setCurrentAnimation( + human.modelUuid, + human.currentMaterials.length > 0 ? 'idle_with_box' : 'idle', + true, true, true + ); return; } } @@ -196,7 +181,6 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce return ( <> {currentPath.length > 0 && ( - // helper {currentPath.map((point, index) => ( @@ -208,7 +192,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce )} - ) + ); } -export default HumanAnimator \ No newline at end of file +export default WorkerAnimator; \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/actions/assemberInstance.tsx b/app/src/modules/simulation/human/instances/instance/actions/assemberInstance.tsx new file mode 100644 index 0000000..881a2ec --- /dev/null +++ b/app/src/modules/simulation/human/instances/instance/actions/assemberInstance.tsx @@ -0,0 +1,221 @@ +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 AssemblerAnimator from '../../animator/assemblerAnimator'; + +function AssemblerInstance({ human }: { human: HumanStatus }) { + const { navMesh } = useNavMesh(); + const { isPlaying } = usePlayButtonStore(); + const { scene } = useThree(); + const { assetStore, materialStore, humanStore, productStore } = useSceneContext(); + const { setMaterial } = materialStore(); + const { triggerPointActions } = useTriggerHandler(); + const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore(); + const { getActionByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { setHumanActive, setHumanState, decrementHumanLoad, removeLastMaterial, setCurrentPhase } = humanStore(); + + const [path, setPath] = useState<[number, number, number][]>([]); + const isPausedRef = useRef(false); + const isSpeedRef = useRef(0); + const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); + 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: [number, number, number], end: [number, number, number]) => { + try { + const navMeshQuery = new NavMeshQuery(navMesh); + let startPoint = new THREE.Vector3(start[0], start[1], start[2]); + let endPoint = new THREE.Vector3(end[0], end[1], end[2]); + const { path: segmentPath } = navMeshQuery.computePath(startPoint, endPoint); + if ( + segmentPath.length > 0 && + Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(endPoint.x) && + Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(endPoint.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(startPoint, startPoint); + 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(human.modelUuid, 'init'); + setHumanActive(human.modelUuid, false); + setHumanState(human.modelUuid, 'idle'); + resetAnimation(human.modelUuid); + setPath([]); + 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' && human.currentPhase === 'init') { + const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); + if (!humanMesh) return; + + const toPickupPath = computePath(humanMesh.position.toArray(), (action as HumanAction)?.assemblyPoint?.position || [0, 0, 0]); + setPath(toPickupPath); + setHumanState(human.modelUuid, 'idle'); + setCurrentPhase(human.modelUuid, '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' && human.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(human.modelUuid, '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 && human.currentPhase === 'assembling') { + setHumanState(human.modelUuid, 'idle'); + setCurrentPhase(human.modelUuid, 'waiting'); + setHumanActive(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() + } + }, [human, 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]); + + function handleCallBack() { + if (human.currentPhase === 'init-assembly') { + setCurrentPhase(human.modelUuid, '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([]); + } + } + + return ( + <> + + + ) +} + +export default AssemblerInstance; \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/actions/workerInstance.tsx b/app/src/modules/simulation/human/instances/instance/actions/workerInstance.tsx new file mode 100644 index 0000000..c5645af --- /dev/null +++ b/app/src/modules/simulation/human/instances/instance/actions/workerInstance.tsx @@ -0,0 +1,615 @@ +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 WorkerAnimator from '../../animator/workerAnimator'; + +function WorkerInstance({ 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, 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, setCurrentPhase } = humanStore(); + + 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); + + useEffect(() => { + isPausedRef.current = isPaused; + }, [isPaused]); + + useEffect(() => { + isSpeedRef.current = speed; + }, [speed]); + + const computePath = useCallback((start: [number, number, number], end: [number, number, number]) => { + try { + const navMeshQuery = new NavMeshQuery(navMesh); + let startPoint = new THREE.Vector3(start[0], start[1], start[2]); + let endPoint = new THREE.Vector3(end[0], end[1], end[2]); + const { path: segmentPath } = navMeshQuery.computePath(startPoint, endPoint); + if ( + segmentPath.length > 0 && + Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(endPoint.x) && + Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(endPoint.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(startPoint, startPoint); + 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(human.modelUuid, '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 + } + 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.actionType !== 'worker' || !action.pickUpPoint || !action.dropPoint) return; + + if (!human.isActive && human.state === 'idle' && human.currentPhase === 'init') { + const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); + if (!humanMesh) return; + + const toPickupPath = computePath(humanMesh.position.toArray(), action?.pickUpPoint?.position || [0, 0, 0]); + + setPath(toPickupPath); + setCurrentPhase(human.modelUuid, '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' && human.currentPhase === 'picking') { + if (humanAsset && human.currentLoad === action.loadCapacity && human.currentMaterials.length > 0 && human.currentLoad > 0 && humanAsset.animationState?.current === 'pickup' && humanAsset.animationState?.isCompleted) { + if (action.pickUpPoint && action.dropPoint) { + const toDrop = computePath(action.pickUpPoint.position || [0, 0, 0], action.dropPoint.position || [0, 0, 0]); + setPath(toDrop); + setCurrentPhase(human.modelUuid, '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.currentMaterials.length > 0 && human.currentLoad > 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' && human.currentPhase === 'dropping' && human.currentLoad === 0) { + if (action.pickUpPoint && action.dropPoint) { + const dropToPickup = computePath(action.dropPoint.position || [0, 0, 0], action.pickUpPoint.position || [0, 0, 0]); + setPath(dropToPickup); + setCurrentPhase(human.modelUuid, '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() + } + }, [human, human.currentAction, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); + + function handleCallBack() { + if (human.currentPhase === 'init-pickup') { + setCurrentPhase(human.modelUuid, '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([]); + } else if (human.currentPhase === 'pickup-drop') { + setCurrentPhase(human.modelUuid, '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 (human.currentPhase === 'drop-pickup') { + setCurrentPhase(human.modelUuid, '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 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 WorkerInstance; \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index 71e11ef..8d1c51f 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -1,36 +1,20 @@ -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 { useEffect, useRef } from 'react'; 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'; +import AssemblerInstance from './actions/assemberInstance'; +import WorkerInstance from './actions/workerInstance'; 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 { humanStore, productStore } = useSceneContext(); + const { getActionByUuid } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); - const { setHumanActive, setHumanState, clearCurrentMaterials, setHumanLoad, setHumanScheduled, decrementHumanLoad, removeLastMaterial, incrementIdleTime, incrementActiveTime, resetTime, setCurrentPhase } = humanStore(); + const { incrementIdleTime, incrementActiveTime } = humanStore(); - const [path, setPath] = useState<[number, number, number][]>([]); - const pauseTimeRef = useRef(null); const idleTimeRef = useRef(0); const activeTimeRef = useRef(0); const isPausedRef = useRef(false); @@ -39,14 +23,7 @@ function HumanInstance({ human }: { human: HumanStatus }) { 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); + const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); useEffect(() => { isPausedRef.current = isPaused; @@ -56,259 +33,6 @@ function HumanInstance({ human }: { human: HumanStatus }) { isSpeedRef.current = speed; }, [speed]); - const computePath = useCallback((start: [number, number, number], end: [number, number, number]) => { - try { - const navMeshQuery = new NavMeshQuery(navMesh); - let startPoint = new THREE.Vector3(start[0], start[1], start[2]); - let endPoint = new THREE.Vector3(end[0], end[1], end[2]); - const { path: segmentPath } = navMeshQuery.computePath(startPoint, endPoint); - if ( - segmentPath.length > 0 && - Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(endPoint.x) && - Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(endPoint.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(startPoint, startPoint); - 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(human.modelUuid, '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' && human.currentPhase === 'init') { - - const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); - if (!humanMesh) return; - - const toPickupPath = computePath(humanMesh.position.toArray(), (action as HumanAction)?.assemblyPoint?.position || [0, 0, 0]); - setPath(toPickupPath); - setHumanState(human.modelUuid, 'idle'); - setCurrentPhase(human.modelUuid, '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' && human.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(human.modelUuid, '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 && human.currentPhase === 'assembling') { - setHumanState(human.modelUuid, 'idle'); - setCurrentPhase(human.modelUuid, '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, 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' && human.currentPhase === 'init') { - - const humanMesh = scene.getObjectByProperty('uuid', human.modelUuid); - if (!humanMesh) return; - - const toPickupPath = computePath(humanMesh.position.toArray(), action?.pickUpPoint?.position || [0, 0, 0]); - setPath(toPickupPath); - setCurrentPhase(human.modelUuid, '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' && human.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(action.pickUpPoint.position || [0, 0, 0], action.dropPoint.position || [0, 0, 0]); - setPath(toDrop); - setCurrentPhase(human.modelUuid, '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.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' && human.currentPhase === 'dropping' && human.currentLoad === 0) { - if (action.pickUpPoint && action.dropPoint) { - const dropToPickup = computePath(action.dropPoint.position || [0, 0, 0], action.pickUpPoint.position || [0, 0, 0]); - setPath(dropToPickup); - setCurrentPhase(human.modelUuid, '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, human.currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); - - function handleCallBack() { - if (human.currentPhase === 'init-pickup') { - setCurrentPhase(human.modelUuid, '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 (human.currentPhase === 'init-assembly') { - setCurrentPhase(human.modelUuid, '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 (human.currentPhase === 'pickup-drop') { - setCurrentPhase(human.modelUuid, '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 (human.currentPhase === 'drop-pickup') { - setCurrentPhase(human.modelUuid, '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; @@ -353,438 +77,15 @@ function HumanInstance({ human }: { human: HumanStatus }) { }; }, [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 ( <> - + {action && action.actionType === 'worker' && + + } + {action && action.actionType === 'assembly' && + + } diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index 62582af..15ed931 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -325,48 +325,17 @@ export function useTriggerHandler() { if (model?.type === 'vehicle') { const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); if (human) { - if (human.isActive === false && human.state === 'idle') { - const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); - if (vehicle) { - if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { - // Handle current action from vehicle + setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (vehicle) { + addVehicleToMonitor(vehicle.modelUuid, () => { + addHumanToMonitor(human.modelUuid, () => { setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); + handleAction(action, materialId); - - } else { - - // Handle current action using Event Manager - setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); - - addVehicleToMonitor(vehicle.modelUuid, () => { - handleAction(action, materialId); - }) - } - } - } else { - setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); - addHumanToMonitor(human.modelUuid, () => { - const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (vehicle) { - if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { - // Handle current action from vehicle - setIsPaused(materialId, true); - handleAction(action, materialId); - - } else { - - // Handle current action using Event Manager - setIsPaused(materialId, true); - - addVehicleToMonitor(vehicle.modelUuid, () => { - handleAction(action, materialId); - }) - } - } - }, action.actionUuid) + }, action.actionUuid) + }) } } } else if (model?.type === 'transfer') { @@ -374,61 +343,30 @@ export function useTriggerHandler() { if (human) { setIsPaused(materialId, true); setHumanScheduled(human.modelUuid, true); - addHumanToMonitor(human.modelUuid, () => { - const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (conveyor) { - // Handle current action using Event Manager - setIsPaused(materialId, true); - - addConveyorToMonitor(conveyor.modelUuid, () => { - addHumanToMonitor(human.modelUuid, () => { - handleAction(action, materialId); - }, action.actionUuid) - }, [materialId]) - } - }, action.actionUuid) + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (conveyor) { + addConveyorToMonitor(conveyor.modelUuid, () => { + addHumanToMonitor(human.modelUuid, () => { + setIsPaused(materialId, true); + handleAction(action, materialId); + }, action.actionUuid) + }, [materialId]) + } } } else if (model?.type === 'machine') { const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); if (human) { - if (human.isActive === false && human.state === 'idle') { - const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); - if (machine) { - if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { + setIsPaused(materialId, true); + setHumanScheduled(human.modelUuid, true); + const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (machine) { + addMachineToMonitor(machine.modelUuid, () => { + addHumanToMonitor(human.modelUuid, () => { setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); + handleAction(action, materialId); - } else { - - // Handle current action using Event Manager - setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); - - addMachineToMonitor(machine.modelUuid, () => { - handleAction(action, materialId); - }) - } - } - } else { - setIsPaused(materialId, true); - setHumanScheduled(human.modelUuid, true); - addHumanToMonitor(human.modelUuid, () => { - const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (machine) { - if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { - setIsPaused(materialId, true); - handleAction(action, materialId); - } else { - - // Handle current action using Event Manager - setIsPaused(materialId, true); - - addMachineToMonitor(machine.modelUuid, () => { - handleAction(action, materialId); - }) - } - } - }, action.actionUuid); + }, action.actionUuid); + }) } } } else { @@ -465,7 +403,6 @@ export function useTriggerHandler() { addHumanToMonitor(human.modelUuid, () => { handleAction(action, materialId) }, action.actionUuid); - } } } diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index dcb9f49..01a1603 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -102,6 +102,7 @@ interface HumanAction { pickUpPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } dropPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } loadCount: number; + assemblyCount: number; loadCapacity: number; triggers: TriggerSchema[]; } @@ -261,6 +262,26 @@ interface HumanStatus extends HumanEventSchema { }; } +type HumanEventState = { + humanId: string; + actionQueue: { + actionType: 'worker' | 'assembly'; + actionUuid: string; + actionName: string; + maxLoadCount: number; + maxAssemblyCount: number; + count?: number; + isMonitored: boolean; + isCompleted: boolean; + callback?: () => void; + }[]; + isCooldown: boolean; +}; + +type HumanEventManagerState = { + humanStates: HumanEventState[]; +}; + // Materials interface MaterialSchema { materialId: string;