new human event mangaer

This commit is contained in:
2025-07-22 16:33:33 +05:30
parent 88361b1623
commit 708c8a4ba1
19 changed files with 1355 additions and 992 deletions

View File

@@ -1,5 +1,6 @@
import React from "react"; import React from "react";
import InputRange from "../../../../../ui/inputs/InputRange"; import InputRange from "../../../../../ui/inputs/InputRange";
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
import SwapAction from "./SwapAction"; import SwapAction from "./SwapAction";
interface AssemblyActionProps { interface AssemblyActionProps {
@@ -10,6 +11,15 @@ interface AssemblyActionProps {
disabled?: boolean, disabled?: boolean,
onChange: (value: number) => void; onChange: (value: number) => void;
}; };
assemblyCount: {
value: number;
min: number;
max: number;
step: number;
defaultValue: string,
disabled: false,
onChange: (value: number) => void;
}
swapOptions: string[]; swapOptions: string[];
swapDefaultOption: string; swapDefaultOption: string;
onSwapSelect: (value: string) => void; onSwapSelect: (value: string) => void;
@@ -18,6 +28,7 @@ interface AssemblyActionProps {
const AssemblyAction: React.FC<AssemblyActionProps> = ({ const AssemblyAction: React.FC<AssemblyActionProps> = ({
processTime, processTime,
assemblyCount,
swapOptions, swapOptions,
swapDefaultOption, swapDefaultOption,
onSwapSelect, onSwapSelect,
@@ -34,6 +45,21 @@ const AssemblyAction: React.FC<AssemblyActionProps> = ({
onClick={() => { }} onClick={() => { }}
onChange={processTime.onChange} onChange={processTime.onChange}
/> />
{assemblyCount && (
<InputWithDropDown
label="Assembly Count"
value={assemblyCount.value.toString()}
min={assemblyCount.min}
max={assemblyCount.max}
disabled={assemblyCount.disabled}
defaultValue={assemblyCount.defaultValue}
step={assemblyCount.step}
activeOption="unit"
onClick={() => { }}
onChange={(value) => assemblyCount.onChange(parseInt(value))}
/>
)}
<SwapAction <SwapAction
options={swapOptions} options={swapOptions}
defaultOption={swapDefaultOption} defaultOption={swapDefaultOption}

View File

@@ -11,7 +11,7 @@ interface WorkerActionProps {
disabled?: boolean; disabled?: boolean;
onChange: (value: string) => void; onChange: (value: string) => void;
}; };
loadCount?: { loadCount: {
value: number; value: number;
min: number; min: number;
max: number; max: number;

View File

@@ -20,6 +20,7 @@ function HumanMechanics() {
const [activeOption, setActiveOption] = useState<"worker" | "assembly">("worker"); const [activeOption, setActiveOption] = useState<"worker" | "assembly">("worker");
const [speed, setSpeed] = useState("0.5"); const [speed, setSpeed] = useState("0.5");
const [loadCount, setLoadCount] = useState(0); const [loadCount, setLoadCount] = useState(0);
const [assemblyCount, setAssemblyCount] = useState(0);
const [loadCapacity, setLoadCapacity] = useState("1"); const [loadCapacity, setLoadCapacity] = useState("1");
const [processTime, setProcessTime] = useState(10); const [processTime, setProcessTime] = useState(10);
const [swappedMaterial, setSwappedMaterial] = useState("Default material"); const [swappedMaterial, setSwappedMaterial] = useState("Default material");
@@ -56,6 +57,7 @@ function HumanMechanics() {
setLoadCapacity(firstAction.loadCapacity.toString()); setLoadCapacity(firstAction.loadCapacity.toString());
setActiveOption(firstAction.actionType); setActiveOption(firstAction.actionType);
setLoadCount(firstAction.loadCount || 0); setLoadCount(firstAction.loadCount || 0);
setAssemblyCount(firstAction.assemblyCount || 0);
setProcessTime(firstAction.processTime || 10); setProcessTime(firstAction.processTime || 10);
setSwappedMaterial(firstAction.swapMaterial || "Default material"); setSwappedMaterial(firstAction.swapMaterial || "Default material");
} }
@@ -84,6 +86,7 @@ function HumanMechanics() {
setActiveOption(newCurrentAction.actionType); setActiveOption(newCurrentAction.actionType);
setLoadCapacity(newCurrentAction.loadCapacity.toString()); setLoadCapacity(newCurrentAction.loadCapacity.toString());
setLoadCount(newCurrentAction.loadCount || 0); setLoadCount(newCurrentAction.loadCount || 0);
setAssemblyCount(newCurrentAction.assemblyCount || 0);
if (newCurrentAction.actionType === 'assembly') { if (newCurrentAction.actionType === 'assembly') {
setProcessTime(newCurrentAction.processTime || 10); setProcessTime(newCurrentAction.processTime || 10);
@@ -200,6 +203,28 @@ function HumanMechanics() {
setLoadCount(value); 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) => { const handleProcessTimeChange = (value: number) => {
if (!currentAction || !selectedPointData || !selectedAction.actionId) return; if (!currentAction || !selectedPointData || !selectedAction.actionId) return;
@@ -281,6 +306,7 @@ function HumanMechanics() {
actionName: `Action ${selectedPointData.actions.length + 1}`, actionName: `Action ${selectedPointData.actions.length + 1}`,
actionType: "worker", actionType: "worker",
loadCount: 1, loadCount: 1,
assemblyCount: 1,
loadCapacity: 1, loadCapacity: 1,
processTime: 10, processTime: 10,
triggers: [], triggers: [],
@@ -389,7 +415,7 @@ function HumanMechanics() {
}} }}
loadCount={{ loadCount={{
value: loadCount, value: loadCount,
min: 0, min: 1,
max: 20, max: 20,
step: 1, step: 1,
defaultValue: "1", defaultValue: "1",
@@ -407,6 +433,15 @@ function HumanMechanics() {
max: 60, max: 60,
onChange: handleProcessTimeChange, 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"]} swapOptions={["Default material", "Material 1", "Material 2", "Material 3"]}
swapDefaultOption={swappedMaterial} swapDefaultOption={swappedMaterial}
onSwapSelect={handleMaterialChange} onSwapSelect={handleMaterialChange}

View File

@@ -265,6 +265,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
actionName: "Action 1", actionName: "Action 1",
actionType: "worker", actionType: "worker",
loadCount: 1, loadCount: 1,
assemblyCount: 1,
loadCapacity: 1, loadCapacity: 1,
processTime: 10, processTime: 10,
triggers: [] triggers: []

View File

@@ -384,6 +384,7 @@ async function handleModelLoad(
actionName: "Action 1", actionName: "Action 1",
actionType: "worker", actionType: "worker",
loadCount: 1, loadCount: 1,
assemblyCount: 1,
loadCapacity: 1, loadCapacity: 1,
processTime: 10, processTime: 10,
triggers: [] triggers: []

View File

@@ -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<THREE.AnimationMixer>();
const actions = useRef<{ [name: string]: THREE.AnimationAction }>({});
const [previousAnimation, setPreviousAnimation] = useState<string | null>(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;
}

View File

@@ -404,6 +404,7 @@ const CopyPasteControls3D = ({
actionName: "Action 1", actionName: "Action 1",
actionType: "worker", actionType: "worker",
loadCapacity: 1, loadCapacity: 1,
assemblyCount: 1,
loadCount: 1, loadCount: 1,
processTime: 10, processTime: 10,
triggers: [] triggers: []

View File

@@ -405,6 +405,7 @@ const DuplicationControls3D = ({
actionName: "Action 1", actionName: "Action 1",
actionType: "worker", actionType: "worker",
loadCapacity: 1, loadCapacity: 1,
assemblyCount: 1,
loadCount: 1, loadCount: 1,
processTime: 10, processTime: 10,
triggers: [] triggers: []

View File

@@ -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 { createAssetStore, AssetStoreType } from '../../store/builder/useAssetStore';
import { createWallAssetStore, WallAssetStoreType } from '../../store/builder/useWallAssetStore'; import { createWallAssetStore, WallAssetStoreType } from '../../store/builder/useWallAssetStore';
@@ -38,6 +38,8 @@ type SceneContextValue = {
storageUnitStore: StorageUnitStoreType; storageUnitStore: StorageUnitStoreType;
humanStore: HumanStoreType; humanStore: HumanStoreType;
humanEventManagerRef: React.RefObject<HumanEventManagerState>;
clearStores: () => void; clearStores: () => void;
layout: 'Main Layout' | 'Comparison Layout'; layout: 'Main Layout' | 'Comparison Layout';
@@ -71,6 +73,8 @@ export function SceneProvider({
const storageUnitStore = useMemo(() => createStorageUnitStore(), []); const storageUnitStore = useMemo(() => createStorageUnitStore(), []);
const humanStore = useMemo(() => createHumanStore(), []); const humanStore = useMemo(() => createHumanStore(), []);
const humanEventManagerRef = useRef<HumanEventManagerState>({ humanStates: [] });
const clearStores = useMemo(() => () => { const clearStores = useMemo(() => () => {
assetStore.getState().clearAssets(); assetStore.getState().clearAssets();
wallAssetStore.getState().clearWallAssets(); wallAssetStore.getState().clearWallAssets();
@@ -87,6 +91,7 @@ export function SceneProvider({
vehicleStore.getState().clearVehicles(); vehicleStore.getState().clearVehicles();
storageUnitStore.getState().clearStorageUnits(); storageUnitStore.getState().clearStorageUnits();
humanStore.getState().clearHumans(); humanStore.getState().clearHumans();
humanEventManagerRef.current.humanStates = [];
}, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]); }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]);
const contextValue = useMemo(() => ( const contextValue = useMemo(() => (
@@ -106,6 +111,7 @@ export function SceneProvider({
vehicleStore, vehicleStore,
storageUnitStore, storageUnitStore,
humanStore, humanStore,
humanEventManagerRef,
clearStores, clearStores,
layout layout
} }

View File

@@ -1,24 +1,24 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useFrame } from '@react-three/fiber'
import { useSceneContext } from '../../../../scene/sceneContext'; import { useSceneContext } from '../../../../scene/sceneContext';
import { useProductContext } from '../../../products/productContext'; import { useProductContext } from '../../../products/productContext';
// import { findConveyorSubsequence } from '../../../simulator/functions/getConveyorSequencesInProduct'; // import { findConveyorSubsequence } from '../../../simulator/functions/getConveyorSequencesInProduct';
function ConveyorInstance({ conveyor }: { readonly conveyor: ConveyorStatus }) { function ConveyorInstance({ conveyor }: { readonly conveyor: ConveyorStatus }) {
const { materialStore, conveyorStore, productStore } = useSceneContext(); const { materialStore, conveyorStore, productStore } = useSceneContext();
const { getProductById } = productStore(); const { getProductById } = productStore();
const { selectedProductStore } = useProductContext(); const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore(); const { selectedProduct } = selectedProductStore();
const { getMaterialsByCurrentModelUuid, materials } = materialStore(); const { getMaterialsByCurrentModelUuid } = materialStore();
const { isReset } = useResetButtonStore();
const { setConveyorPaused } = conveyorStore(); const { setConveyorPaused } = conveyorStore();
useEffect(() => { useFrame(() => {
const product = getProductById(selectedProduct.productUuid); const product = getProductById(selectedProduct.productUuid);
if (!product) return; if (!product) return;
const conveyorMaterials = getMaterialsByCurrentModelUuid(conveyor.modelUuid); const conveyorMaterials = getMaterialsByCurrentModelUuid(conveyor.modelUuid);
if (conveyorMaterials && conveyorMaterials?.length > 0) { if (conveyorMaterials && conveyorMaterials.length > 0) {
const hasPausedMaterials = conveyorMaterials.some(material => material.isPaused); 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(() => { useEffect(() => {
// console.log('conveyor: ', conveyor); // console.log('conveyor: ', conveyor);
@@ -61,7 +61,7 @@ function ConveyorInstance({ conveyor }: { readonly conveyor: ConveyorStatus }) {
return ( return (
<> <>
</> </>
) );
}; }
export default ConveyorInstance export default ConveyorInstance

View File

@@ -1,195 +1,142 @@
import { useEffect, useRef } from 'react'; import { useEffect } from 'react';
import { useFrame } from '@react-three/fiber'; import { useFrame } from '@react-three/fiber';
import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../store/usePlayButtonStore'; import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../store/usePlayButtonStore';
import { useSceneContext } from '../../../scene/sceneContext'; import { useSceneContext } from '../../../scene/sceneContext';
import { useProductContext } from '../../products/productContext'; import { useProductContext } from '../../products/productContext';
export function useHumanEventManager() { export function useHumanEventManager() {
const { humanStore, productStore, assetStore } = useSceneContext(); const { humanStore, productStore, assetStore, humanEventManagerRef } = useSceneContext();
const { getHumanById, setCurrentPhase } = humanStore(); const { getHumanById, setCurrentPhase, removeCurrentAction } = humanStore();
const { getAssetById } = assetStore(); const { getAssetById } = assetStore();
const { getActionByUuid } = productStore(); const { getActionByUuid } = productStore();
const { selectedProductStore } = useProductContext(); const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore(); const { selectedProduct } = selectedProductStore();
const stateRef = useRef({
humanStates: new Map<string, {
callbacks: (() => void)[],
actionQueue: { actionType: "worker" | "assembly", actionUuid: string, actionName: string }[],
isCooldown: boolean
}>(),
callbackCounts: new Map<string, Map<string, number>>(),
isMonitoring: false
});
const { isPlaying } = usePlayButtonStore(); const { isPlaying } = usePlayButtonStore();
const { isPaused } = usePauseButtonStore(); const { isPaused } = usePauseButtonStore();
const { isReset } = useResetButtonStore(); const { isReset } = useResetButtonStore();
useEffect(() => { useEffect(() => {
if (isReset) { if ((isReset || !isPlaying) && humanEventManagerRef.current) {
stateRef.current.humanStates.clear(); humanEventManagerRef.current.humanStates = [];
stateRef.current.callbackCounts.clear();
stateRef.current.isMonitoring = false;
} }
}, [isReset]); }, [isReset, isPlaying]);
const addHumanToMonitor = (humanId: string, callback: () => void, actionUuid: string) => { 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; let state = humanEventManagerRef.current.humanStates.find(h => h.humanId === humanId);
if (!state) {
const actionType = action.actionType; state = { humanId, actionQueue: [], isCooldown: false };
if (actionType !== "worker" && actionType !== "assembly") return; humanEventManagerRef.current.humanStates.push(state);
if (!stateRef.current.callbackCounts.has(humanId)) {
stateRef.current.callbackCounts.set(humanId, new Map());
} }
const actionCounts = stateRef.current.callbackCounts.get(humanId)!; const existingAction = state.actionQueue.find(a => a.actionUuid === actionUuid);
if (!actionCounts.has(actionUuid)) { if (existingAction) {
actionCounts.set(actionUuid, 0); const currentCount = existingAction.count ?? 0;
} if (existingAction.actionType === 'worker') {
if (currentCount < existingAction.maxLoadCount) {
const currentCount = actionCounts.get(actionUuid)!; existingAction.callback = callback;
if (actionType === 'worker' && currentCount >= action.loadCount) { 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; return;
} }
if (!stateRef.current.humanStates.has(humanId)) { state.actionQueue.push({
stateRef.current.humanStates.set(humanId, { actionType: action.actionType,
callbacks: [], actionUuid,
actionQueue: [], actionName: action.actionName,
isCooldown: false maxLoadCount: action.loadCount ?? 0,
}); maxAssemblyCount: action.assemblyCount ?? 0,
} count: 0,
isMonitored: true,
const humanState = stateRef.current.humanStates.get(humanId)!; isCompleted: false,
humanState.callbacks.push(callback); callback
humanState.actionQueue.push({ actionType, actionUuid, actionName: action.actionName }); });
stateRef.current.isMonitoring = true;
}; };
const removeHumanFromMonitor = (humanId: string) => { const removeHumanFromMonitor = (humanId: string, actionUuid: string) => {
// stateRef.current.humanStates.delete(humanId); if (!humanEventManagerRef.current) return;
const state = humanEventManagerRef.current.humanStates.find(h => h.humanId === humanId);
if (!state) return;
if (stateRef.current.humanStates.size === 0) { const action = state.actionQueue.find(a => a.actionUuid === actionUuid);
stateRef.current.isMonitoring = false; 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(() => { 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) => { for (const humanState of humanEventManagerRef.current.humanStates) {
if (humanState.callbacks.length === 0 || humanState.isCooldown) return; if (humanState.isCooldown) continue;
const actionQueue = humanState.actionQueue; const { humanId, actionQueue } = humanState;
if (!actionQueue || actionQueue.length === 0) return; 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 human = getHumanById(humanId);
const humanAsset = getAssetById(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; if (!human || !humanAsset || !currentAction) continue;
if (human.isActive || human.state !== "idle" || humanAsset.animationState?.current !== 'idle') continue;
const currentCount = getCallbackCount(humanId, actionUuid);
const currentAction = getActionByUuid(selectedProduct.productUuid, human.currentAction?.actionUuid || '') as HumanAction | undefined;
let conditionMet = false; let conditionMet = false;
if (expectedActionType === "worker") { if (currentAction.actionType === 'worker') {
if (currentAction && currentAction.actionType === 'worker' && currentCount >= currentAction.loadCount) { if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) {
humanState.callbacks.shift(); conditionMet = true;
actionQueue.shift(); } else if (action.actionType === 'assembly') {
if (humanState.callbacks.length === 0) { conditionMet = true;
removeHumanFromMonitor(humanId);
}
return;
} }
} else if (currentAction.actionType === 'assembly') {
if (currentAction && currentAction.actionType === 'worker') { if (action.actionType === 'assembly') {
conditionMet = ( conditionMet = true;
!human.isActive && } else if (action.actionType === 'worker' && human.currentLoad < currentAction.loadCapacity) {
human.state === "idle" && conditionMet = true;
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'
)
} }
} }
if (conditionMet) { if (conditionMet) {
const callback = humanState.callbacks.shift(); if (action.actionUuid !== human.currentAction?.actionUuid) {
actionQueue.shift(); setCurrentPhase(human.modelUuid, 'init');
removeCurrentAction(human.modelUuid);
if (callback) {
callback();
incrementCallbackCount(humanId, actionUuid);
} }
if (humanState.callbacks.length === 0) { action.callback();
removeHumanFromMonitor(humanId); action.count = (action.count ?? 0) + 1;
} else { action.isMonitored = false;
humanState.isCooldown = true; if ((action.actionType === 'worker' && action.count >= action.maxLoadCount) ||
setTimeout(() => { (action.actionType === 'assembly' && action.count >= action.maxAssemblyCount)) {
humanState.isCooldown = false; action.isCompleted = true;
}, 1000);
} }
humanState.isCooldown = true;
setTimeout(() => {
humanState.isCooldown = false;
}, 1000);
removeHumanFromMonitor(human.modelUuid, action.actionUuid);
} }
}); }
}); }, 0);
return { return {
addHumanToMonitor, addHumanToMonitor,
removeHumanFromMonitor, removeHumanFromMonitor
}; };
} }

View File

@@ -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<AssemblerAnimatorProps>) {
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<number>(0);
const completedRef = useRef<boolean>(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<boolean>(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 && (
<group visible={false}>
<Line points={currentPath} color="blue" lineWidth={3} />
{currentPath.map((point, index) => (
<mesh key={index} position={point}>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="red" />
</mesh>
))}
</group>
)}
</>
);
}
export default AssemblerAnimator;

View File

@@ -50,7 +50,7 @@ const MaterialAnimator = ({ human }: { human: HumanStatus; }) => {
return ( 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') && (
<MaterialModel <MaterialModel
matRef={meshRef} matRef={meshRef}
materialId={human.currentMaterials[0].materialId || ''} materialId={human.currentMaterials[0].materialId || ''}

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react';
import { useFrame, useThree } from '@react-three/fiber'; import { useFrame, useThree } from '@react-three/fiber';
import * as THREE from 'three'; import * as THREE from 'three';
import { Line } from '@react-three/drei'; import { Line } from '@react-three/drei';
@@ -6,7 +6,7 @@ import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useRese
import { useSceneContext } from '../../../../scene/sceneContext'; import { useSceneContext } from '../../../../scene/sceneContext';
import { useProductContext } from '../../../products/productContext'; import { useProductContext } from '../../../products/productContext';
interface HumanAnimatorProps { interface WorkerAnimatorProps {
path: [number, number, number][]; path: [number, number, number][];
handleCallBack: () => void; handleCallBack: () => void;
reset: () => void; reset: () => void;
@@ -14,7 +14,7 @@ interface HumanAnimatorProps {
human: HumanStatus; human: HumanStatus;
} }
function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProcess }: Readonly<HumanAnimatorProps>) { function WorkerAnimator({ path, handleCallBack, human, reset, startUnloadingProcess }: Readonly<WorkerAnimatorProps>) {
const { humanStore, assetStore, productStore } = useSceneContext(); const { humanStore, assetStore, productStore } = useSceneContext();
const { getActionByUuid } = productStore(); const { getActionByUuid } = productStore();
const { selectedProductStore } = useProductContext(); const { selectedProductStore } = useProductContext();
@@ -29,7 +29,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
const movingForward = useRef<boolean>(true); const movingForward = useRef<boolean>(true);
const completedRef = useRef<boolean>(false); const completedRef = useRef<boolean>(false);
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); 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<boolean>(true); const [restRotation, setRestingRotation] = useState<boolean>(true);
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
const { scene } = useThree(); const { scene } = useThree();
@@ -39,15 +39,12 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || ''); const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
if (human.currentPhase === 'init-pickup' && path.length > 0) { if (human.currentPhase === 'init-pickup' && path.length > 0) {
setCurrentPath(path); setCurrentPath(path);
setObjectRotation((action as HumanAction).pickUpPoint?.rotation ?? null) 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);
} else if (human.currentPhase === 'pickup-drop' && path.length > 0) { } 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); setCurrentPath(path);
} else if (human.currentPhase === 'drop-pickup' && path.length > 0) { } 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); setCurrentPath(path);
} }
}, [human.currentPhase, path, objectRotation, selectedProduct, human.currentAction?.actionUuid]); }, [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]); object.rotation.set(humanData.rotation[0], humanData.rotation[1], humanData.rotation[2]);
} }
} }
}, [isReset, isPlaying]) }, [isReset, isPlaying]);
const lastTimeRef = useRef(performance.now()); 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 end = new THREE.Vector3(...currentPath[index + 1]);
const segmentDistance = distances[index]; 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); const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
targetQuaternion.multiply(y180); targetQuaternion.multiply(y180);
@@ -118,13 +117,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
object.quaternion.copy(targetQuaternion); object.quaternion.copy(targetQuaternion);
} else { } else {
const step = rotationSpeed * delta * speed * human.speed; const step = rotationSpeed * delta * speed * human.speed;
const angle = object.quaternion.angleTo(targetQuaternion); object.quaternion.rotateTowards(targetQuaternion, step);
if (angle < step) {
object.quaternion.copy(targetQuaternion);
} else {
object.quaternion.rotateTowards(targetQuaternion, step);
}
} }
const isAligned = angle < 0.01; const isAligned = angle < 0.01;
@@ -134,13 +127,13 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
const t = (progressRef.current - accumulatedDistance) / segmentDistance; const t = (progressRef.current - accumulatedDistance) / segmentDistance;
const position = start.clone().lerp(end, t); const position = start.clone().lerp(end, t);
object.position.copy(position); 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); setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true);
} else { } else {
setCurrentAnimation(human.modelUuid, 'walking', true, true, true); setCurrentAnimation(human.modelUuid, 'walking', true, true, true);
} }
} else { } 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); setCurrentAnimation(human.modelUuid, 'idle_with_box', true, true, true);
} else { } else {
setCurrentAnimation(human.modelUuid, 'idle', true, true, true); setCurrentAnimation(human.modelUuid, 'idle', true, true, true);
@@ -151,7 +144,6 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
if (progressRef.current >= totalDistance) { if (progressRef.current >= totalDistance) {
if (restRotation && objectRotation) { if (restRotation && objectRotation) {
const targetEuler = new THREE.Euler(0, objectRotation[1], 0); const targetEuler = new THREE.Euler(0, objectRotation[1], 0);
const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler); const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI); const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
const targetQuaternion = baseQuaternion.multiply(y180); const targetQuaternion = baseQuaternion.multiply(y180);
@@ -162,21 +154,14 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
setRestingRotation(false); setRestingRotation(false);
} else { } else {
const step = rotationSpeed * delta * speed * human.speed; const step = rotationSpeed * delta * speed * human.speed;
const angle = object.quaternion.angleTo(targetQuaternion); object.quaternion.rotateTowards(targetQuaternion, step);
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);
} }
setCurrentAnimation(
human.modelUuid,
human.currentMaterials.length > 0 ? 'idle_with_box' : 'idle',
true, true, true
);
return; return;
} }
} }
@@ -196,7 +181,6 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
return ( return (
<> <>
{currentPath.length > 0 && ( {currentPath.length > 0 && (
// helper
<group visible={false}> <group visible={false}>
<Line points={currentPath} color="blue" lineWidth={3} /> <Line points={currentPath} color="blue" lineWidth={3} />
{currentPath.map((point, index) => ( {currentPath.map((point, index) => (
@@ -208,7 +192,7 @@ function HumanAnimator({ path, handleCallBack, human, reset, startUnloadingProce
</group> </group>
)} )}
</> </>
) );
} }
export default HumanAnimator export default WorkerAnimator;

View File

@@ -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<boolean>(false);
const isSpeedRef = useRef<number>(0);
const { speed } = useAnimationPlaySpeed();
const { isPaused } = usePauseButtonStore();
const humanAsset = getAssetById(human.modelUuid);
const processStartTimeRef = useRef<number | null>(null);
const processTimeRef = useRef<number>(0);
const processAnimationIdRef = useRef<number | null>(null);
const accumulatedPausedTimeRef = useRef<number>(0);
const lastPauseTimeRef = useRef<number | null>(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 (
<>
<AssemblerAnimator
path={path}
handleCallBack={handleCallBack}
human={human}
reset={reset}
/>
</>
)
}
export default AssemblerInstance;

View File

@@ -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<number | null>(null);
const idleTimeRef = useRef<number>(0);
const activeTimeRef = useRef<number>(0);
const isPausedRef = useRef<boolean>(false);
const isSpeedRef = useRef<number>(0);
const { speed } = useAnimationPlaySpeed();
const { isPaused } = usePauseButtonStore();
const previousTimeRef = useRef<number | null>(null);
const animationFrameIdRef = useRef<number | null>(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 (
<>
<WorkerAnimator
path={path}
handleCallBack={handleCallBack}
human={human}
reset={reset}
startUnloadingProcess={startUnloadingProcess}
/>
</>
)
}
export default WorkerInstance;

View File

@@ -1,36 +1,20 @@
import { useCallback, useEffect, useRef, useState } from 'react'; import { useEffect, useRef } 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 { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler';
import { useSceneContext } from '../../../../scene/sceneContext'; import { useSceneContext } from '../../../../scene/sceneContext';
import { useProductContext } from '../../../products/productContext'; import { useProductContext } from '../../../products/productContext';
import HumanAnimator from '../animator/humanAnimator';
import MaterialAnimator from '../animator/materialAnimator'; import MaterialAnimator from '../animator/materialAnimator';
import AssemblerInstance from './actions/assemberInstance';
import WorkerInstance from './actions/workerInstance';
function HumanInstance({ human }: { human: HumanStatus }) { function HumanInstance({ human }: { human: HumanStatus }) {
const { navMesh } = useNavMesh();
const { isPlaying } = usePlayButtonStore(); const { isPlaying } = usePlayButtonStore();
const { scene } = useThree(); const { humanStore, productStore } = useSceneContext();
const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext(); const { getActionByUuid } = productStore();
const { removeMaterial, setEndTime, setMaterial, setIsVisible } = materialStore();
const { getStorageUnitById } = storageUnitStore();
const { getArmBotById } = armBotStore();
const { getConveyorById } = conveyorStore();
const { getVehicleById } = vehicleStore();
const { getMachineById } = machineStore();
const { triggerPointActions } = useTriggerHandler();
const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore();
const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore();
const { selectedProductStore } = useProductContext(); const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore(); 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<number | null>(null);
const idleTimeRef = useRef<number>(0); const idleTimeRef = useRef<number>(0);
const activeTimeRef = useRef<number>(0); const activeTimeRef = useRef<number>(0);
const isPausedRef = useRef<boolean>(false); const isPausedRef = useRef<boolean>(false);
@@ -39,14 +23,7 @@ function HumanInstance({ human }: { human: HumanStatus }) {
const { isPaused } = usePauseButtonStore(); const { isPaused } = usePauseButtonStore();
const previousTimeRef = useRef<number | null>(null); const previousTimeRef = useRef<number | null>(null);
const animationFrameIdRef = useRef<number | null>(null); const animationFrameIdRef = useRef<number | null>(null);
const humanAsset = getAssetById(human.modelUuid); const action = getActionByUuid(selectedProduct.productUuid, human?.currentAction?.actionUuid || '');
const processStartTimeRef = useRef<number | null>(null);
const processTimeRef = useRef<number>(0);
const processAnimationIdRef = useRef<number | null>(null);
const accumulatedPausedTimeRef = useRef<number>(0);
const lastPauseTimeRef = useRef<number | null>(null);
const hasLoggedHalfway = useRef(false);
const hasLoggedCompleted = useRef(false);
useEffect(() => { useEffect(() => {
isPausedRef.current = isPaused; isPausedRef.current = isPaused;
@@ -56,259 +33,6 @@ function HumanInstance({ human }: { human: HumanStatus }) {
isSpeedRef.current = speed; isSpeedRef.current = speed;
}, [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) { function animate(currentTime: number) {
if (previousTimeRef.current === null) { if (previousTimeRef.current === null) {
previousTimeRef.current = currentTime; previousTimeRef.current = currentTime;
@@ -353,438 +77,15 @@ function HumanInstance({ human }: { human: HumanStatus }) {
}; };
}, [human, isPlaying]); }, [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 ( return (
<> <>
<HumanAnimator {action && action.actionType === 'worker' &&
path={path} <WorkerInstance human={human} />
handleCallBack={handleCallBack} }
human={human} {action && action.actionType === 'assembly' &&
reset={reset} <AssemblerInstance human={human} />
startUnloadingProcess={startUnloadingProcess} }
/>
<MaterialAnimator human={human} /> <MaterialAnimator human={human} />
</> </>

View File

@@ -325,48 +325,17 @@ export function useTriggerHandler() {
if (model?.type === 'vehicle') { if (model?.type === 'vehicle') {
const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid);
if (human) { if (human) {
if (human.isActive === false && human.state === 'idle') { setIsPaused(materialId, true);
const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); setHumanScheduled(human.modelUuid, true);
if (vehicle) { const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { if (vehicle) {
// Handle current action from vehicle addVehicleToMonitor(vehicle.modelUuid, () => {
addHumanToMonitor(human.modelUuid, () => {
setIsPaused(materialId, true); setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
handleAction(action, materialId); handleAction(action, materialId);
}, action.actionUuid)
} 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)
} }
} }
} else if (model?.type === 'transfer') { } else if (model?.type === 'transfer') {
@@ -374,61 +343,30 @@ export function useTriggerHandler() {
if (human) { if (human) {
setIsPaused(materialId, true); setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true); setHumanScheduled(human.modelUuid, true);
addHumanToMonitor(human.modelUuid, () => { const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); if (conveyor) {
if (conveyor) { addConveyorToMonitor(conveyor.modelUuid, () => {
// Handle current action using Event Manager addHumanToMonitor(human.modelUuid, () => {
setIsPaused(materialId, true); setIsPaused(materialId, true);
handleAction(action, materialId);
addConveyorToMonitor(conveyor.modelUuid, () => { }, action.actionUuid)
addHumanToMonitor(human.modelUuid, () => { }, [materialId])
handleAction(action, materialId); }
}, action.actionUuid)
}, [materialId])
}
}, action.actionUuid)
} }
} else if (model?.type === 'machine') { } else if (model?.type === 'machine') {
const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid);
if (human) { if (human) {
if (human.isActive === false && human.state === 'idle') { setIsPaused(materialId, true);
const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); setHumanScheduled(human.modelUuid, true);
if (machine) { const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { if (machine) {
addMachineToMonitor(machine.modelUuid, () => {
addHumanToMonitor(human.modelUuid, () => {
setIsPaused(materialId, true); setIsPaused(materialId, true);
setHumanScheduled(human.modelUuid, true);
handleAction(action, materialId); handleAction(action, materialId);
} else { }, action.actionUuid);
})
// 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);
} }
} }
} else { } else {
@@ -465,7 +403,6 @@ export function useTriggerHandler() {
addHumanToMonitor(human.modelUuid, () => { addHumanToMonitor(human.modelUuid, () => {
handleAction(action, materialId) handleAction(action, materialId)
}, action.actionUuid); }, action.actionUuid);
} }
} }
} }

View File

@@ -102,6 +102,7 @@ interface HumanAction {
pickUpPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } pickUpPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; }
dropPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } dropPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; }
loadCount: number; loadCount: number;
assemblyCount: number;
loadCapacity: number; loadCapacity: number;
triggers: TriggerSchema[]; 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 // Materials
interface MaterialSchema { interface MaterialSchema {
materialId: string; materialId: string;