From 70a99316ba3f4d1385f05f05fcd100bc1cb44c0e Mon Sep 17 00:00:00 2001 From: Poovizhi99 Date: Fri, 25 Apr 2025 17:57:38 +0530 Subject: [PATCH] added stationed-pickup and pickup to drop functionality added --- .../instances/animator/vehicleAnimator.tsx | 206 ++++++++++++++---- .../instances/instance/vehicleInstance.tsx | 139 ++++++++---- .../vehicle/instances/vehicleInstances.tsx | 6 +- .../modules/simulation/vehicle/vehicles.tsx | 68 +++--- 4 files changed, 288 insertions(+), 131 deletions(-) diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index 137d679..0d59f2b 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -1,61 +1,179 @@ import { useFrame, useThree } from '@react-three/fiber'; -import React, { useEffect, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react'; +import { useFloorItems } from '../../../../../store/store'; +import * as THREE from 'three'; +import { Line } from '@react-three/drei'; +import { useAnimationPlaySpeed, usePauseButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore'; interface VehicleAnimatorProps { - path: [number, number, number][]; - handleCallBack: () => void; - currentPhase: string; - agvUuid: number + path: [number, number, number][]; + handleCallBack: () => void; + currentPhase: string; + agvUuid: number; + agvDetail: any; } +function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail }: VehicleAnimatorProps) { + const { updateVehicleLoad, vehicles } = useVehicleStore(); + const { isPaused } = usePauseButtonStore(); + const { speed } = useAnimationPlaySpeed(); + const { isReset } = useResetButtonStore(); + const [restRotation, setRestingRotation] = useState(true); + const [progress, setProgress] = useState(0); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + const { scene } = useThree(); + const progressRef = useRef(0); + const movingForward = useRef(true); + const completedRef = useRef(false); + let startTime: number; + let pausedTime: number; + let fixedInterval: number; -function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid }: VehicleAnimatorProps) { - const [progress, setProgress] = useState(0) - const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); - const { scene } = useThree(); + useEffect(() => { + if (currentPhase === 'stationed-pickup' && path.length > 0) { + setCurrentPath(path); + } else if (currentPhase === 'pickup-drop' && path.length > 0) { + setCurrentPath(path); + } else if (currentPhase === 'drop-pickup' && path.length > 0) { + setCurrentPath(path); + } + }, [currentPhase, path]); - useEffect(() => { + useEffect(() => { + setProgress(0); + completedRef.current = false; + }, [currentPath]); - if (currentPhase === 'stationed-pickup' && path.length > 0) { - setCurrentPath(path); + useFrame((_, delta) => { + const object = scene.getObjectByProperty('uuid', agvUuid); + if (!object || currentPath.length < 2) return; + if (isPaused) return; + + let totalDistance = 0; + const distances = []; + + 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; + } + + let coveredDistance = progressRef.current; + let accumulatedDistance = 0; + let index = 0; + + while ( + index < distances.length && + coveredDistance > 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 currentDirection = new THREE.Vector3().subVectors(end, start).normalize(); + const targetAngle = Math.atan2(currentDirection.x, currentDirection.z); + const rotationSpeed = 2.0; + const currentAngle = object.rotation.y; + + let angleDifference = targetAngle - currentAngle; + if (angleDifference > Math.PI) angleDifference -= 2 * Math.PI; + if (angleDifference < -Math.PI) angleDifference += 2 * Math.PI; + + const maxRotationStep = rotationSpeed * delta; + object.rotation.y += Math.sign(angleDifference) * Math.min(Math.abs(angleDifference), maxRotationStep); + + const isAligned = Math.abs(angleDifference) < 0.01; + + if (isAligned) { + progressRef.current += delta * (speed * agvDetail.speed); + coveredDistance = progressRef.current; + + const t = (coveredDistance - accumulatedDistance) / segmentDistance; + const position = start.clone().lerp(end, t); + object.position.copy(position); + } + } + + if (progressRef.current >= totalDistance) { + if (restRotation) { + const targetQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0)); + object.quaternion.slerp(targetQuaternion, delta * 2); + const angleDiff = object.quaternion.angleTo(targetQuaternion); + if (angleDiff < 0.01) { + let objectRotation = agvDetail.point.rotation + object.rotation.set(objectRotation[0], objectRotation[1], objectRotation[2]); + setRestingRotation(false); } + return; + } + } - }, [currentPhase, path]) - useFrame((_, delta) => { - if (!path || path.length < 2) return; + if (progressRef.current >= totalDistance) { + setRestingRotation(true); + progressRef.current = 0; + movingForward.current = !movingForward.current; + setCurrentPath([]); + handleCallBack(); + if (currentPhase === 'pickup-drop') { + requestAnimationFrame(firstFrame); + } + } + }); - const object = scene.getObjectByProperty("uuid", agvUuid) - if (!object) return; + function firstFrame() { + const unLoadDuration = agvDetail.point.action.unLoadDuration; + const droppedMaterial = agvDetail.currentLoad; + fixedInterval = (unLoadDuration / droppedMaterial) * 1000; + if (!isPaused) { + step(droppedMaterial); + } else { + pausedTime = performance.now(); + step(droppedMaterial); + } + } - setProgress(prev => { - const next = prev + delta * 0.1; // speed - return next >= 1 ? 1 : next; - }); + function step(droppedMaterial: number) { + if (!isPaused) { + startTime = performance.now(); + const elapsedTime = performance.now() - startTime - pausedTime; + if (elapsedTime >= fixedInterval) { + let droppedMat = droppedMaterial - 1; + updateVehicleLoad(agvDetail.modelUuid, droppedMat); + if (droppedMat === 0) return; + startTime = performance.now(); + pausedTime = 0; + requestAnimationFrame(() => step(droppedMat)); + } else { + requestAnimationFrame(() => step(droppedMaterial)); + } + } else { + requestAnimationFrame(() => firstFrame); + } + } - const totalSegments = path.length - 1; - const segmentIndex = Math.floor(progress * totalSegments); - const t = progress * totalSegments - segmentIndex; - - const start = path[segmentIndex]; - const end = path[segmentIndex + 1] || start; - - // Directly set position without creating a new Vector3 - object.position.x = start[0] + (end[0] - start[0]) * t; - object.position.y = start[1] + (end[1] - start[1]) * t; - object.position.z = start[2] + (end[2] - start[2]) * t; - }); - // useFrame(() => { - // if (currentPath.length === 0) return; - // const object = scene.getObjectByProperty("uuid", agvUuid); - // if (!object) return; - - - - // }) - return ( + return ( + <> + {currentPath.length > 0 && ( <> + + {currentPath.map((point, index) => ( + + + + + ))} - ) + )} + + ); } -export default VehicleAnimator \ No newline at end of file +export default VehicleAnimator; \ No newline at end of file diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 646dabd..98d037c 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -1,67 +1,122 @@ -import React, { useCallback, useEffect, useState } from 'react' -import VehicleAnimator from '../animator/vehicleAnimator' -import * as THREE from "three"; +import React, { useCallback, useEffect, useState } from 'react'; +import VehicleAnimator from '../animator/vehicleAnimator'; +import * as THREE from 'three'; import { NavMeshQuery } from '@recast-navigation/core'; import { useNavMesh } from '../../../../../store/store'; import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore'; -function VehicleInstance({ agvDetails }: any) { +function VehicleInstance({ agvDetail }: any) { const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); - const { setVehicleActive, setVehicleState } = useVehicleStore(); - const [currentPhase, setCurrentPhase] = useState<(string)>("stationed"); + const { vehicles, setVehicleActive, setVehicleState, updateVehicleLoad } = useVehicleStore(); + const [currentPhase, setCurrentPhase] = useState('stationed'); const [path, setPath] = useState<[number, number, number][]>([]); - const computePath = useCallback((start: any, end: any) => { + 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, y + 0.1, z] as [number, number, number]) || [] + ); + } catch { + return []; + } + }, + [navMesh] + ); - - try { - const navMeshQuery = new NavMeshQuery(navMesh); - const { path: segmentPath } = navMeshQuery.computePath(start, end); - return ( - segmentPath?.map( - ({ x, y, z }) => [x, y + 0.1, z] as [number, number, number] - ) || [] - ); - } catch { - return []; - } - }, [navMesh]); + function vehicleStatus(modelid: string, status: string) { + // console.log(`AGV ${modelid}: ${status}`); + } useEffect(() => { - // const pickupToDropPath = computePath(pickup, drop); - // const dropToPickupPath = computePath(drop, pickup); - if (isPlaying) { - if (!agvDetails.isActive && agvDetails.state == "idle" && currentPhase == "stationed") { - const toPickupPath = computePath(new THREE.Vector3(agvDetails.position[0], agvDetails.position[1], agvDetails.position[2]), agvDetails.point.action.pickUpPoint); - setPath(toPickupPath) - setVehicleActive(agvDetails.modelUuid, true) - setVehicleState(agvDetails.modelUuid, "running") - setCurrentPhase("stationed-pickup") - // + 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 + ); + setPath(toPickupPath); + setVehicleActive(agvDetail.modelUuid, true); + setVehicleState(agvDetail.modelUuid, 'running'); + setCurrentPhase('stationed-pickup'); + vehicleStatus(agvDetail.modelUuid, 'Started from station, heading to pickup'); + return; + } else if ( + !agvDetail.isActive && + agvDetail.state === 'idle' && + currentPhase === 'picking' + ) { + setTimeout(() => { + updateVehicleLoad(agvDetail.modelUuid, 2); + }, 5000); + + if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity) { + const toDrop = computePath( + agvDetail.point.action.pickUpPoint, + agvDetail.point.action.unLoadPoint + ); + setPath(toDrop); + setVehicleActive(agvDetail.modelUuid, true); + setVehicleState(agvDetail.modelUuid, 'running'); + setCurrentPhase('pickup-drop'); + vehicleStatus(agvDetail.modelUuid, 'Started from pickup point, heading to drop point'); + } + } else if ( + !agvDetail.isActive && + agvDetail.state === 'idle' && + currentPhase === 'dropping' && + agvDetail.currentLoad === 0 + ) { + const dropToPickup = computePath( + agvDetail.point.action.unLoadPoint, + agvDetail.point.action.pickUpPoint + ); + setPath(dropToPickup); + setVehicleActive(agvDetail.modelUuid, true); + setVehicleState(agvDetail.modelUuid, 'running'); + setCurrentPhase('drop-pickup'); + vehicleStatus(agvDetail.modelUuid, 'Started from dropping point, heading to pickup point'); } } - }, [agvDetails, currentPhase, path, isPlaying]) + }, [vehicles, currentPhase, path, isPlaying]); function handleCallBack() { - if (currentPhase === "stationed-pickup") { - setVehicleActive(agvDetails.modelUuid, false) - setVehicleState(agvDetails.modelUuid, "idle") - setCurrentPhase("picking") - setPath([]) + if (currentPhase === 'stationed-pickup') { + setVehicleActive(agvDetail.modelUuid, false); + setVehicleState(agvDetail.modelUuid, 'idle'); + setCurrentPhase('picking'); + vehicleStatus(agvDetail.modelUuid, 'Reached pickup point, waiting for material'); + setPath([]); + } else if (currentPhase === 'pickup-drop') { + setVehicleActive(agvDetail.modelUuid, false); + setVehicleState(agvDetail.modelUuid, 'idle'); + setCurrentPhase('dropping'); + vehicleStatus(agvDetail.modelUuid, 'Reached drop point'); + setPath([]); + } else if (currentPhase === 'drop-pickup') { + setVehicleActive(agvDetail.modelUuid, false); + setVehicleState(agvDetail.modelUuid, 'idle'); + setCurrentPhase('picking'); + setPath([]); + vehicleStatus(agvDetail.modelUuid, 'Reached pickup point again, cycle complete'); } } - return ( <> - - - + - ) + ); } -export default VehicleInstance \ No newline at end of file +export default VehicleInstance; \ No newline at end of file diff --git a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx index f3f1435..2a0070b 100644 --- a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx +++ b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx @@ -1,12 +1,14 @@ import React from 'react' import VehicleInstance from './instance/vehicleInstance' +import { useVehicleStore } from '../../../../store/simulation/useVehicleStore' -function VehicleInstances({ vehicles }: any) { +function VehicleInstances() { + const { vehicles } = useVehicleStore(); return ( <> {vehicles.map((val: any, i: any) => - + )} diff --git a/app/src/modules/simulation/vehicle/vehicles.tsx b/app/src/modules/simulation/vehicle/vehicles.tsx index b9bc07b..6c5ea34 100644 --- a/app/src/modules/simulation/vehicle/vehicles.tsx +++ b/app/src/modules/simulation/vehicle/vehicles.tsx @@ -1,34 +1,36 @@ import React, { useEffect } from 'react' import VehicleInstances from './instances/vehicleInstances'; - import { useVehicleStore } from '../../../store/simulation/useVehicleStore'; +import { useFloorItems } from '../../../store/store'; function Vehicles() { const { vehicles, addVehicle } = useVehicleStore(); - const vehicleStatusSample: VehicleStatus[] = [ + const { floorItems } = useFloorItems(); + + const vehicleStatusSample: VehicleEventSchema[] = [ { - modelUuid: "2c01ed76-359a-485b-83d4-052cfcdb9d89", + modelUuid: "9356f710-4727-4b50-bdb2-9c1e747ecc74", modelName: "AGV", - position: [10, 0, 5], + position: [97.9252965204558, 0, 37.96138815638661], rotation: [0, 0, 0], state: "idle", type: "vehicle", speed: 2.5, point: { uuid: "point-789", - position: [93.42159216649789, 0, 23.790878603572857], + position: [0, 1, 0], rotation: [0, 0, 0], action: { actionUuid: "action-456", actionName: "Deliver to Zone A", actionType: "travel", material: "crate", - unLoadDuration: 15, - loadCapacity: 5, - pickUpPoint: { x: 5, y: 0, z: 3 }, - unLoadPoint: { x: 20, y: 0, z: 10 }, + unLoadDuration: 10, + loadCapacity: 2, + pickUpPoint: { x: 98.71483985219794, y: 0, z: 28.66321267938962 }, + unLoadPoint: { x: 105.71483985219794, y: 0, z: 28.66321267938962 }, triggers: [ { triggerUuid: "trig-001", @@ -50,18 +52,12 @@ function Vehicles() { } ] } - }, - productId: "prod-890", - isActive: false, - idleTime: 0, - activeTime: 0, - currentLoad: 0, - distanceTraveled: 0 + } }, { - modelUuid: "311130b9-4f2e-425a-b3b5-5039cb348806", + modelUuid: "b06960bb-3d2e-41f7-a646-335f389c68b4", modelName: "AGV", - position: [95.69567023145055, 0, 33.18042399595448], + position: [89.61609306554463, 0, 33.634136622267356], rotation: [0, 0, 0], state: "idle", type: "vehicle", @@ -75,9 +71,9 @@ function Vehicles() { actionName: "Deliver to Zone A", actionType: "travel", material: "crate", - unLoadDuration: 15, - loadCapacity: 5, - pickUpPoint: { x: 5, y: 0, z: 3 }, + unLoadDuration: 10, + loadCapacity: 2, + pickUpPoint: { x: 90, y: 0, z: 28 }, unLoadPoint: { x: 20, y: 0, z: 10 }, triggers: [ { @@ -100,17 +96,11 @@ function Vehicles() { } ] } - }, - productId: "prod-890", - isActive: false, - idleTime: 0, - activeTime: 0, - currentLoad: 0, - distanceTraveled: 0 + } }, { - modelUuid: "fa54132c-8333-4832-becb-5281f5e11549", - modelName: "AGV", - position: [102.71483985219794, 0, 23.66321267938962], + modelUuid: "e729a4f1-11d2-4778-8d6a-468f1b4f6b79", + modelName: "forklift", + position: [98.85729337188162, 0, 38.36616546567653], rotation: [0, 0, 0], state: "idle", type: "vehicle", @@ -126,7 +116,7 @@ function Vehicles() { material: "crate", unLoadDuration: 15, loadCapacity: 5, - pickUpPoint: { x: 5, y: 0, z: 3 }, + pickUpPoint: { x: 98.71483985219794, y: 0, z: 28.66321267938962 }, unLoadPoint: { x: 20, y: 0, z: 10 }, triggers: [ { @@ -149,13 +139,7 @@ function Vehicles() { } ] } - }, - productId: "prod-890", - isActive: false, - idleTime: 0, - activeTime: 0, - currentLoad: 0, - distanceTraveled: 0 + } } ]; @@ -163,7 +147,7 @@ function Vehicles() { useEffect(() => { addVehicle('123', vehicleStatusSample[0]); addVehicle('123', vehicleStatusSample[1]); - addVehicle('123', vehicleStatusSample[2]); + // addVehicle('123', vehicleStatusSample[2]); }, []) useEffect(() => { @@ -173,9 +157,7 @@ function Vehicles() { return ( <> - - - + ) }