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