540 lines
22 KiB
TypeScript
540 lines
22 KiB
TypeScript
import { 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 { useProductStore } from '../../../../../store/simulation/useProductStore';
|
|
import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler';
|
|
import MaterialAnimator from '../animator/materialAnimator';
|
|
import { useSceneContext } from '../../../../scene/sceneContext';
|
|
import { useProductContext } from '../../../products/productContext';
|
|
|
|
function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) {
|
|
const { navMesh } = useNavMesh();
|
|
const { isPlaying } = usePlayButtonStore();
|
|
const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore } = useSceneContext();
|
|
const { removeMaterial, setEndTime } = materialStore();
|
|
const { getStorageUnitById } = storageUnitStore();
|
|
const { getArmBotById } = armBotStore();
|
|
const { getConveyorById } = conveyorStore();
|
|
const { triggerPointActions } = useTriggerHandler();
|
|
const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = useProductStore();
|
|
const { selectedProductStore } = useProductContext();
|
|
const { selectedProduct } = selectedProductStore();
|
|
const { vehicles, setVehicleActive, setVehicleState, setVehiclePicking, clearCurrentMaterials, setVehicleLoad, decrementVehicleLoad, removeLastMaterial, getLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = vehicleStore();
|
|
|
|
const [currentPhase, setCurrentPhase] = useState<string>('stationed');
|
|
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);
|
|
let startTime: number;
|
|
let fixedInterval: number;
|
|
const { speed } = useAnimationPlaySpeed();
|
|
const { isPaused } = usePauseButtonStore();
|
|
const previousTimeRef = useRef<number | null>(null);
|
|
const animationFrameIdRef = useRef<number | null>(null);
|
|
|
|
useEffect(() => {
|
|
isPausedRef.current = isPaused;
|
|
}, [isPaused]);
|
|
|
|
useEffect(() => {
|
|
isSpeedRef.current = speed;
|
|
}, [speed]);
|
|
|
|
const computePath = useCallback(
|
|
(start: any, end: any) => {
|
|
console.log('end: ', end);
|
|
try {
|
|
const navMeshQuery = new NavMeshQuery(navMesh);
|
|
const { path: segmentPath } = navMeshQuery.computePath(start, end);
|
|
if (
|
|
segmentPath.length > 0 &&
|
|
Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(end.x) &&
|
|
Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(end.z)
|
|
) {
|
|
console.log('if ', segmentPath);
|
|
return segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || [];
|
|
} else {
|
|
console.log("There is no path here...Choose valid path")
|
|
const { path: segmentPaths } = navMeshQuery.computePath(start, start);
|
|
return segmentPaths.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || [];
|
|
}
|
|
} catch {
|
|
console.error("Failed to compute path");
|
|
return [];
|
|
}
|
|
},
|
|
[navMesh]
|
|
);
|
|
|
|
function vehicleStatus(modelId: string, status: string) {
|
|
// console.log(`${modelId} , ${status}`);
|
|
}
|
|
|
|
// 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;
|
|
resetTime(agvDetail.modelUuid)
|
|
activeTimeRef.current = 0
|
|
idleTimeRef.current = 0
|
|
previousTimeRef.current = null
|
|
if (animationFrameIdRef.current !== null) {
|
|
cancelAnimationFrame(animationFrameIdRef.current)
|
|
animationFrameIdRef.current = null
|
|
}
|
|
}
|
|
|
|
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
|
|
);
|
|
// const toPickupPath = computePath(
|
|
// new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]),
|
|
// new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2])
|
|
// );
|
|
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 animate(currentTime: number) {
|
|
if (previousTimeRef.current === null) {
|
|
previousTimeRef.current = currentTime;
|
|
}
|
|
|
|
const deltaTime = (currentTime - previousTimeRef.current) / 1000;
|
|
previousTimeRef.current = currentTime;
|
|
|
|
if (agvDetail.isActive) {
|
|
if (!isPausedRef.current) {
|
|
activeTimeRef.current += deltaTime * isSpeedRef.current;
|
|
}
|
|
} else {
|
|
if (!isPausedRef.current) {
|
|
idleTimeRef.current += deltaTime * isSpeedRef.current; // Scale idle time by speed
|
|
}
|
|
}
|
|
animationFrameIdRef.current = requestAnimationFrame(animate);
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!isPlaying) return
|
|
if (!agvDetail.isActive) {
|
|
const roundedActiveTime = Math.round(activeTimeRef.current);
|
|
// console.log('Final Active Time:', roundedActiveTime, 'seconds');
|
|
incrementActiveTime(agvDetail.modelUuid, roundedActiveTime);
|
|
activeTimeRef.current = 0;
|
|
} else {
|
|
const roundedIdleTime = Math.round(idleTimeRef.current);
|
|
// console.log('Final Idle Time:', roundedIdleTime, 'seconds');
|
|
incrementIdleTime(agvDetail.modelUuid, roundedIdleTime);
|
|
idleTimeRef.current = 0;
|
|
}
|
|
|
|
if (animationFrameIdRef.current === null) {
|
|
animationFrameIdRef.current = requestAnimationFrame(animate);
|
|
}
|
|
|
|
return () => {
|
|
if (animationFrameIdRef.current !== null) {
|
|
cancelAnimationFrame(animationFrameIdRef.current);
|
|
animationFrameIdRef.current = null;
|
|
}
|
|
};
|
|
}, [agvDetail, 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.productUuid, agvDetail.point.action.triggers[0]?.triggerUuid);
|
|
const model = getEventByModelUuid(selectedProduct.productUuid, trigger?.triggeredAsset?.triggeredModel?.modelUuid || '');
|
|
|
|
if (trigger && model) {
|
|
if (model.type === 'transfer') {
|
|
const action = getActionByUuid(selectedProduct.productUuid, agvDetail.point.action.actionUuid);
|
|
if (action) {
|
|
handleMaterialDropToConveyor(model);
|
|
}
|
|
} else if (model.type === 'machine') {
|
|
//
|
|
} else if (model.type === 'roboticArm') {
|
|
const action = getActionByUuid(selectedProduct.productUuid, agvDetail.point.action.actionUuid);
|
|
if (action) {
|
|
handleMaterialDropToArmBot(model);
|
|
}
|
|
} else if (model.type === 'storageUnit') {
|
|
const action = getActionByUuid(selectedProduct.productUuid, agvDetail.point.action.actionUuid);
|
|
if (action) {
|
|
handleMaterialDropToStorageUnit(model);
|
|
}
|
|
}
|
|
} else {
|
|
const droppedMaterial = agvDetail.currentLoad;
|
|
startTime = performance.now();
|
|
handleMaterialDropByDefault(droppedMaterial);
|
|
}
|
|
} else {
|
|
const droppedMaterial = agvDetail.currentLoad;
|
|
startTime = performance.now();
|
|
handleMaterialDropByDefault(droppedMaterial);
|
|
}
|
|
}
|
|
|
|
function handleMaterialDropToStorageUnit(model: StorageEventSchema) {
|
|
if (model) {
|
|
if (model.point.action.actionType === 'store') {
|
|
loopMaterialDropToStorage(
|
|
agvDetail.modelUuid,
|
|
agvDetail.currentLoad,
|
|
agvDetail.point.action.unLoadDuration,
|
|
model.modelUuid,
|
|
model.point.action.storageCapacity,
|
|
agvDetail.point.action
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function loopMaterialDropToStorage(
|
|
vehicleId: string,
|
|
vehicleCurrentLoad: number,
|
|
unLoadDuration: number,
|
|
storageUnitId: string,
|
|
storageMaxCapacity: number,
|
|
action: VehicleAction
|
|
) {
|
|
startTime = performance.now();
|
|
const fixedInterval = ((unLoadDuration / vehicleCurrentLoad) * (1000 / isSpeedRef.current));
|
|
|
|
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(model: ConveyorEventSchema) {
|
|
const conveyor = getConveyorById(model.modelUuid);
|
|
if (conveyor) {
|
|
loopMaterialDropToConveyor(
|
|
agvDetail.modelUuid,
|
|
agvDetail.currentLoad,
|
|
conveyor.modelUuid,
|
|
agvDetail.point.action.unLoadDuration,
|
|
agvDetail.point.action
|
|
);
|
|
}
|
|
}
|
|
|
|
function loopMaterialDropToConveyor(
|
|
vehicleId: string,
|
|
vehicleCurrentLoad: number,
|
|
conveyorId: string,
|
|
unLoadDuration: number,
|
|
action: VehicleAction
|
|
) {
|
|
let lastIncrementTime = performance.now();
|
|
let pauseStartTime: number | null = null;
|
|
let totalPausedDuration = 0;
|
|
const fixedInterval = (unLoadDuration * 1000) / speed;
|
|
|
|
const dropLoop = (currentTime: number) => {
|
|
const conveyor = getConveyorById(conveyorId);
|
|
|
|
if (isPausedRef.current || (conveyor && conveyor.isPaused)) {
|
|
if (pauseStartTime === null) {
|
|
pauseStartTime = currentTime;
|
|
}
|
|
requestAnimationFrame(dropLoop);
|
|
return;
|
|
}
|
|
|
|
// If we were paused but now resumed
|
|
if (pauseStartTime !== null) {
|
|
totalPausedDuration += currentTime - pauseStartTime;
|
|
pauseStartTime = null;
|
|
}
|
|
|
|
// Adjust for paused time
|
|
const adjustedCurrentTime = currentTime - totalPausedDuration;
|
|
const elapsedSinceLastIncrement = adjustedCurrentTime - lastIncrementTime;
|
|
|
|
if (elapsedSinceLastIncrement >= fixedInterval) {
|
|
if (conveyor && vehicleCurrentLoad > 0) {
|
|
decrementVehicleLoad(vehicleId, 1);
|
|
vehicleCurrentLoad -= 1;
|
|
|
|
const material = removeLastMaterial(vehicleId);
|
|
if (material) {
|
|
triggerPointActions(action, material.materialId);
|
|
}
|
|
|
|
// Update the last increment time (using adjusted time)
|
|
lastIncrementTime = adjustedCurrentTime;
|
|
}
|
|
}
|
|
|
|
// Continue the loop if there's more load to drop
|
|
if (vehicleCurrentLoad > 0) {
|
|
requestAnimationFrame(dropLoop);
|
|
}
|
|
};
|
|
|
|
requestAnimationFrame(dropLoop);
|
|
}
|
|
|
|
function handleMaterialDropToArmBot(model: RoboticArmEventSchema) {
|
|
const armBot = getArmBotById(model.modelUuid);
|
|
if (armBot && armBot.state === 'idle' && !armBot.isActive) {
|
|
loopMaterialDropToArmBot(
|
|
agvDetail.modelUuid,
|
|
agvDetail.currentLoad,
|
|
agvDetail.point.action.unLoadDuration,
|
|
model.modelUuid,
|
|
agvDetail.point.action
|
|
);
|
|
}
|
|
}
|
|
|
|
function loopMaterialDropToArmBot(
|
|
vehicleId: string,
|
|
vehicleCurrentLoad: number,
|
|
unLoadDuration: number,
|
|
armBotId: string,
|
|
action: VehicleAction
|
|
) {
|
|
startTime = performance.now();
|
|
const armBot = getArmBotById(armBotId);
|
|
|
|
if (!armBot || armBot.state !== 'idle' || armBot.isActive || vehicleCurrentLoad <= 0) {
|
|
return;
|
|
}
|
|
|
|
const checkIdleDuration = () => {
|
|
if (isPausedRef.current) {
|
|
pauseTimeRef.current ??= performance.now();
|
|
requestAnimationFrame(checkIdleDuration);
|
|
return;
|
|
}
|
|
|
|
if (pauseTimeRef.current) {
|
|
const pauseDuration = performance.now() - pauseTimeRef.current;
|
|
startTime += pauseDuration;
|
|
pauseTimeRef.current = null;
|
|
}
|
|
|
|
const elapsedTime = performance.now() - startTime;
|
|
|
|
if (elapsedTime >= unLoadDuration * (1000 / speed)) {
|
|
const material = getLastMaterial(vehicleId);
|
|
if (material) {
|
|
vehicleCurrentLoad -= 1;
|
|
|
|
triggerPointActions(action, material.materialId);
|
|
|
|
if (vehicleCurrentLoad > 0) {
|
|
setTimeout(() => {
|
|
const waitForNextTransfer = () => {
|
|
const currentArmBot = getArmBotById(armBotId);
|
|
if (currentArmBot && currentArmBot.state === 'idle' && !currentArmBot.isActive) {
|
|
startTime = performance.now();
|
|
loopMaterialDropToArmBot(vehicleId, vehicleCurrentLoad, unLoadDuration, armBotId, action);
|
|
} else {
|
|
requestAnimationFrame(waitForNextTransfer);
|
|
}
|
|
};
|
|
waitForNextTransfer();
|
|
}, 0)
|
|
}
|
|
}
|
|
} else {
|
|
requestAnimationFrame(checkIdleDuration);
|
|
}
|
|
};
|
|
|
|
checkIdleDuration();
|
|
}
|
|
|
|
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 / isSpeedRef.current));
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
|
|
|