Merge remote-tracking branch 'origin/simulation-agv-v2' into v2

This commit is contained in:
Jerald-Golden-B 2025-04-30 11:49:15 +05:30
commit 3ed43ed16a
10 changed files with 687 additions and 261 deletions

Binary file not shown.

Binary file not shown.

View File

@ -21,6 +21,7 @@ const SimulationPlayer: React.FC = () => {
// Button functions
const handleReset = () => {
setReset(true);
// setReset(!isReset);
setSpeed(1);
};
const handlePlayStop = () => {

View File

@ -1,10 +1,13 @@
import React, { useEffect, useRef, useState } from 'react';
import * as THREE from 'three';
import { useEventsStore } from '../../../../../store/simulation/useEventsStore';
import useModuleStore from '../../../../../store/useModuleStore';
import { TransformControls } from '@react-three/drei';
import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
import { useSelectedEventSphere, useSelectedEventData } from '../../../../../store/simulation/useSimulationStore';
import React, { useEffect, useRef, useState } from "react";
import * as THREE from "three";
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
import useModuleStore from "../../../../../store/useModuleStore";
import { TransformControls } from "@react-three/drei";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import {
useSelectedEventSphere,
useSelectedEventData,
} from "../../../../../store/simulation/useSimulationStore";
function PointsCreator() {
const { events, updatePoint, getPointByUuid, getEventByModelUuid } = useEventsStore();
@ -12,8 +15,8 @@ function PointsCreator() {
const transformRef = useRef<any>(null);
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere } = useSelectedEventSphere();
const { setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere, } = useSelectedEventSphere();
const { selectedEventData, setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
useEffect(() => {
if (selectedEventSphere) {
@ -47,33 +50,52 @@ function PointsCreator() {
}, [selectedEventSphere]);
const updatePointToState = (selectedEventSphere: THREE.Mesh) => {
let point = JSON.parse(JSON.stringify(getPointByUuid(selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid)));
let point = JSON.parse(
JSON.stringify(
getPointByUuid(
selectedEventSphere.userData.modelUuid,
selectedEventSphere.userData.pointUuid
)
)
);
if (point) {
point.position = [selectedEventSphere.position.x, selectedEventSphere.position.y, selectedEventSphere.position.z];
updatePoint(selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid, point)
point.position = [
selectedEventSphere.position.x,
selectedEventSphere.position.y,
selectedEventSphere.position.z,
];
updatePoint(
selectedEventSphere.userData.modelUuid,
selectedEventSphere.userData.pointUuid,
point
);
}
}
};
return (
<>
{activeModule === 'simulation' &&
{activeModule === "simulation" && (
<>
<group name='EventPointsGroup' >
<group name="EventPointsGroup">
{events.map((event, i) => {
if (event.type === 'transfer') {
if (event.type === "transfer") {
return (
<group key={i} position={[...event.position]} rotation={[...event.rotation]} >
{event.points.map((point, j) => (
<mesh
name='Event-Sphere'
name="Event-Sphere"
uuid={point.uuid}
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(sphereRefs.current[point.uuid]);
setSelectedEventSphere(
sphereRefs.current[point.uuid]
);
}}
onPointerMissed={() => {
clearSelectedEventSphere();
if (selectedEventData?.data.type !== 'vehicle') {
// clearSelectedEventSphere();
}
setTransformMode(null);
}}
key={`${i}-${j}`}
@ -87,19 +109,21 @@ function PointsCreator() {
))}
</group>
);
} else if (event.type === 'vehicle') {
} else if (event.type === "vehicle") {
return (
<group key={i} position={[...event.position]} rotation={[...event.rotation]} >
<mesh
name='Event-Sphere'
name="Event-Sphere"
uuid={event.point.uuid}
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(sphereRefs.current[event.point.uuid]);
setSelectedEventSphere(
sphereRefs.current[event.point.uuid]
);
}}
onPointerMissed={() => {
clearSelectedEventSphere();
// clearSelectedEventSphere();
setTransformMode(null);
}}
position={new THREE.Vector3(...event.point.position)}
@ -111,19 +135,21 @@ function PointsCreator() {
</mesh>
</group>
);
} else if (event.type === 'roboticArm') {
} else if (event.type === "roboticArm") {
return (
<group key={i} position={[...event.position]} rotation={[...event.rotation]} >
<mesh
name='Event-Sphere'
name="Event-Sphere"
uuid={event.point.uuid}
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(sphereRefs.current[event.point.uuid]);
setSelectedEventSphere(
sphereRefs.current[event.point.uuid]
);
}}
onPointerMissed={() => {
clearSelectedEventSphere();
// clearSelectedEventSphere();
setTransformMode(null);
}}
position={new THREE.Vector3(...event.point.position)}
@ -135,19 +161,21 @@ function PointsCreator() {
</mesh>
</group>
);
} else if (event.type === 'machine') {
} else if (event.type === "machine") {
return (
<group key={i} position={[...event.position]} rotation={[...event.rotation]} >
<mesh
name='Event-Sphere'
name="Event-Sphere"
uuid={event.point.uuid}
ref={(el) => (sphereRefs.current[event.point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
setSelectedEventSphere(sphereRefs.current[event.point.uuid]);
setSelectedEventSphere(
sphereRefs.current[event.point.uuid]
);
}}
onPointerMissed={() => {
clearSelectedEventSphere();
// clearSelectedEventSphere();
setTransformMode(null);
}}
position={new THREE.Vector3(...event.point.position)}
@ -164,11 +192,18 @@ function PointsCreator() {
}
})}
</group>
{(selectedEventSphere && transformMode) &&
<TransformControls ref={transformRef} object={selectedEventSphere} mode={transformMode} onMouseUp={(e) => { updatePointToState(selectedEventSphere) }} />
}
{selectedEventSphere && transformMode && (
<TransformControls
ref={transformRef}
object={selectedEventSphere}
mode={transformMode}
onMouseUp={(e) => {
updatePointToState(selectedEventSphere);
}}
/>
)}
</>
}
)}
</>
);
}

View File

@ -0,0 +1,131 @@
import { useRef } from "react";
import * as THREE from "three";
import { ThreeEvent, useThree } from "@react-three/fiber";
type OnUpdateCallback = (object: THREE.Object3D) => void;
export default function useDraggableGLTF(onUpdate: OnUpdateCallback) {
const { camera, gl, controls, scene } = useThree();
const activeObjRef = useRef<THREE.Object3D | null>(null);
const planeRef = useRef<THREE.Plane>(
new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
);
const offsetRef = useRef<THREE.Vector3>(new THREE.Vector3());
const initialPositionRef = useRef<THREE.Vector3>(new THREE.Vector3());
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
const handlePointerDown = (e: ThreeEvent<PointerEvent>) => {
e.stopPropagation();
let obj: THREE.Object3D | null = e.object;
// Traverse up until we find modelUuid in userData
while (obj && !obj.userData?.modelUuid) {
obj = obj.parent;
}
if (!obj) return;
// Disable orbit controls while dragging
if (controls) (controls as any).enabled = false;
activeObjRef.current = obj;
initialPositionRef.current.copy(obj.position);
// Get world position
const objectWorldPos = new THREE.Vector3();
obj.getWorldPosition(objectWorldPos);
// Set plane at the object's Y level
planeRef.current.set(new THREE.Vector3(0, 1, 0), -objectWorldPos.y);
// Convert pointer to NDC
const rect = gl.domElement.getBoundingClientRect();
pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
// Raycast to intersection
raycaster.setFromCamera(pointer, camera);
const intersection = new THREE.Vector3();
raycaster.ray.intersectPlane(planeRef.current, intersection);
// Calculate offset
offsetRef.current.copy(objectWorldPos).sub(intersection);
// Start listening for drag
gl.domElement.addEventListener("pointermove", handlePointerMove);
gl.domElement.addEventListener("pointerup", handlePointerUp);
};
const handlePointerMove = (e: PointerEvent) => {
if (!activeObjRef.current) return;
// Check if Shift key is pressed
const isShiftKeyPressed = e.shiftKey;
// Get the mouse position relative to the canvas
const rect = gl.domElement.getBoundingClientRect();
pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
// Update raycaster to point to the mouse position
raycaster.setFromCamera(pointer, camera);
// Create a vector to store intersection point
const intersection = new THREE.Vector3();
const intersects = raycaster.ray.intersectPlane(planeRef.current, intersection);
if (!intersects) return;
// Add offset for dragging
intersection.add(offsetRef.current);
console.log('intersection: ', intersection);
// Get the parent's world matrix if exists
const parent = activeObjRef.current.parent;
const targetPosition = new THREE.Vector3();
if (isShiftKeyPressed) {
console.log('isShiftKeyPressed: ', isShiftKeyPressed);
// For Y-axis only movement, maintain original X and Z
console.log('initialPositionRef: ', initialPositionRef);
console.log('intersection.y: ', intersection);
targetPosition.set(
initialPositionRef.current.x,
intersection.y,
initialPositionRef.current.z
);
} else {
// For free movement
targetPosition.copy(intersection);
}
// Convert world position to local if object is nested inside a parent
if (parent) {
parent.worldToLocal(targetPosition);
}
// Update object position
activeObjRef.current.position.copy(targetPosition);
};
const handlePointerUp = () => {
if (controls) (controls as any).enabled = true;
if (activeObjRef.current) {
// Pass the updated position to the onUpdate callback to persist it
onUpdate(activeObjRef.current);
}
gl.domElement.removeEventListener("pointermove", handlePointerMove);
gl.domElement.removeEventListener("pointerup", handlePointerUp);
activeObjRef.current = null;
};
return { handlePointerDown };
}

View File

@ -0,0 +1,243 @@
import React, { useEffect, useRef, useState } from 'react';
import startPoint from "../../../../assets/gltf-glb/arrow_green.glb";
import startEnd from "../../../../assets/gltf-glb/arrow_red.glb";
import * as THREE from "three";
import { useGLTF } from '@react-three/drei';
import { useFrame, useThree } from '@react-three/fiber';
import { useSelectedEventSphere } from '../../../../store/simulation/useSimulationStore';
import { useVehicleStore } from '../../../../store/simulation/useVehicleStore';
import * as Types from "../../../../types/world/worldTypes";
const VehicleUI = () => {
const { scene: startScene } = useGLTF(startPoint) as any;
const { scene: endScene } = useGLTF(startEnd) as any;
const startMarker = useRef<THREE.Group>(null);
const endMarker = useRef<THREE.Group>(null);
const prevMousePos = useRef({ x: 0, y: 0 });
const { selectedEventSphere } = useSelectedEventSphere();
const { vehicles, updateVehicle } = useVehicleStore();
const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 0, 0]);
const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 0, 0]);
const [startRotation, setStartRotation] = useState<[number, number, number]>([0, 0, 0]);
const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]);
const [isDragging, setIsDragging] = useState<"start" | "end" | null>(null);
const [isRotating, setIsRotating] = useState<"start" | "end" | null>(null);
const { raycaster } = useThree();
const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0));
const state: Types.ThreeState = useThree();
const controls: any = state.controls;
useEffect(() => {
if (!selectedEventSphere) return;
const selectedVehicle = vehicles.find(
(vehicle: any) => vehicle.modelUuid === selectedEventSphere.userData.modelUuid
);
if (selectedVehicle?.point?.action) {
const { pickUpPoint, unLoadPoint } = selectedVehicle.point.action;
if (pickUpPoint) {
const pickupPosition = new THREE.Vector3(
pickUpPoint.position.x,
pickUpPoint.position.y,
pickUpPoint.position.z
);
const pickupRotation = new THREE.Vector3(
pickUpPoint.rotation.x,
pickUpPoint.rotation.y,
pickUpPoint.rotation.z
);
pickupPosition.y = 0;
setStartPosition([pickupPosition.x, 0, pickupPosition.z]);
setStartRotation([pickupRotation.x, pickupRotation.y, pickupRotation.z]);
} else {
const defaultLocal = new THREE.Vector3(0, 0, 1.5);
const defaultWorld = selectedEventSphere.localToWorld(defaultLocal);
defaultWorld.y = 0;
setStartPosition([defaultWorld.x, 0, defaultWorld.z]);
setStartRotation([0, 0, 0]);
}
if (unLoadPoint) {
const unLoadPosition = new THREE.Vector3(
unLoadPoint.position.x,
unLoadPoint.position.y,
unLoadPoint.position.z
);
const unLoadRotation = new THREE.Vector3(
unLoadPoint.rotation.x,
unLoadPoint.rotation.y,
unLoadPoint.position.z
);
unLoadPosition.y = 0; // Force y to 0
setEndPosition([unLoadPosition.x, 0, unLoadPosition.z]);
setEndRotation([unLoadRotation.x, unLoadRotation.y, unLoadRotation.z]);
} else {
const defaultLocal = new THREE.Vector3(0, 0, -1.5);
const defaultWorld = selectedEventSphere.localToWorld(defaultLocal);
defaultWorld.y = 0; // Force y to 0
setEndPosition([defaultWorld.x, 0, defaultWorld.z]);
setEndRotation([0, 0, 0]);
}
}
}, [selectedEventSphere]);
useFrame(() => {
if (!isDragging) return;
const intersectPoint = new THREE.Vector3();
const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint);
if (intersects) {
intersectPoint.y = 0; // Force y to 0
if (isDragging === "start") {
setStartPosition([intersectPoint.x, 0, intersectPoint.z]);
}
if (isDragging === "end") {
setEndPosition([intersectPoint.x, 0, intersectPoint.z]);
}
}
});
useFrame((state) => {
if (!isRotating) return;
const currentPointerX = state.pointer.x;
const deltaX = currentPointerX - prevMousePos.current.x;
prevMousePos.current.x = currentPointerX;
const marker = isRotating === "start" ? startMarker.current : endMarker.current;
if (marker) {
const rotationSpeed = 10;
marker.rotation.y += deltaX * rotationSpeed;
if (isRotating === 'start') {
setStartRotation([marker.rotation.x, marker.rotation.y, marker.rotation.z])
} else {
setEndRotation([marker.rotation.x, marker.rotation.y, marker.rotation.z])
}
}
});
const handlePointerDown = (e: any, state: "start" | "end", rotation: "start" | "end") => {
if (e.object.name === "handle") {
const normalizedX = (e.clientX / window.innerWidth) * 2 - 1;
const normalizedY = -(e.clientY / window.innerHeight) * 2 + 1;
prevMousePos.current = { x: normalizedX, y: normalizedY };
setIsRotating(rotation);
if (controls) controls.enabled = false;
setIsDragging(null);
} else {
setIsDragging(state);
setIsRotating(null);
if (controls) controls.enabled = false;
}
};
const handlePointerUp = () => {
controls.enabled = true;
setIsDragging(null);
setIsRotating(null);
if (selectedEventSphere?.userData.modelUuid) {
const updatedVehicle = vehicles.find(
(vehicle) => vehicle.modelUuid === selectedEventSphere.userData.modelUuid
);
if (updatedVehicle) {
updateVehicle(selectedEventSphere.userData.modelUuid, {
point: {
...updatedVehicle.point,
action: {
...updatedVehicle.point?.action,
pickUpPoint: {
position: {
x: startPosition[0],
y: startPosition[1],
z: startPosition[2],
},
rotation: {
x: startRotation[0],
y: startRotation[1],
z: startRotation[2],
},
},
unLoadPoint: {
position: {
x: endPosition[0],
y: endPosition[1],
z: endPosition[2],
},
rotation: {
x: endRotation[0],
y: endRotation[1],
z: endRotation[2],
},
},
},
},
});
}
}
};
useEffect(() => {
const handleGlobalPointerUp = () => {
setIsDragging(null);
setIsRotating(null);
if (controls) controls.enabled = true;
handlePointerUp();
};
if (isDragging || isRotating) {
window.addEventListener("pointerup", handleGlobalPointerUp);
}
return () => {
window.removeEventListener("pointerup", handleGlobalPointerUp);
};
}, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation]);
return (
startPosition.length > 0 && endPosition.length > 0 ? (
<mesh>
<primitive
name="start"
object={startScene}
ref={startMarker}
position={startPosition}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "start", "start");
}}
onPointerMissed={() => {
controls.enabled = true;
setIsDragging(null);
setIsRotating(null);
}}
/>
<primitive
name="end"
object={endScene}
ref={endMarker}
position={endPosition}
onPointerDown={(e: any) => {
e.stopPropagation();
handlePointerDown(e, "end", "end");
}}
onPointerMissed={() => {
controls.enabled = true;
setIsDragging(null);
setIsRotating(null);
}}
/>
</mesh>
) : null
);
}
export default VehicleUI;

View File

@ -1,9 +1,8 @@
import { useEffect, useRef, useState } from 'react'
import { useFrame, useThree } from '@react-three/fiber';
import { useFloorItems } from '../../../../../store/store';
import * as THREE from 'three';
import { Line } from '@react-three/drei';
import { useAnimationPlaySpeed, usePauseButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore';
interface VehicleAnimatorProps {
@ -16,27 +15,35 @@ interface VehicleAnimatorProps {
}
function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail, reset }: VehicleAnimatorProps) {
const { decrementVehicleLoad, vehicles } = useVehicleStore();
const { decrementVehicleLoad } = useVehicleStore();
const { isPaused } = usePauseButtonStore();
const { isPlaying } = usePlayButtonStore();
const { speed } = useAnimationPlaySpeed();
const { isReset } = useResetButtonStore();
const [restRotation, setRestingRotation] = useState<boolean>(true);
const [progress, setProgress] = useState<number>(0);
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
const { scene } = useThree();
const { isReset, setReset } = useResetButtonStore();
const progressRef = useRef<number>(0);
const movingForward = useRef<boolean>(true);
const completedRef = useRef<boolean>(false);
const isPausedRef = useRef<boolean>(false);
const pauseTimeRef = useRef<number | null>(null);
const [restRotation, setRestingRotation] = useState<boolean>(true);
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
const [progress, setProgress] = useState<number>(0);
const { scene } = useThree();
let startTime: number;
let pausedTime: number;
let fixedInterval: number;
let coveredDistance = progressRef.current;
let objectRotation = (agvDetail.point?.action?.pickUpPoint?.rotation || { x: 0, y: 0, z: 0 }) as { x: number; y: number; z: number };
useEffect(() => {
if (currentPhase === 'stationed-pickup' && path.length > 0) {
setCurrentPath(path);
objectRotation = agvDetail.point.action?.pickUpPoint?.rotation
} else if (currentPhase === 'pickup-drop' && path.length > 0) {
objectRotation = agvDetail.point.action?.unLoadPoint?.rotation
setCurrentPath(path);
} else if (currentPhase === 'drop-pickup' && path.length > 0) {
objectRotation = agvDetail.point.action?.pickUpPoint?.rotation
setCurrentPath(path);
}
}, [currentPhase, path]);
@ -45,122 +52,42 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
setProgress(0);
completedRef.current = false;
}, [currentPath]);
// useEffect(() => {
// console.log('isReset: ', isReset);
// if (isReset) {
// reset();
// setCurrentPath([]);
// setProgress(0);
// completedRef.current = false;
// decrementVehicleLoad(agvDetail.modelUuid, 0)
// }
// }, [isReset])
// 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;
// }
// }
// if (progressRef.current >= totalDistance) {
// setRestingRotation(true);
// progressRef.current = 0;
// movingForward.current = !movingForward.current;
// setCurrentPath([]);
// handleCallBack();
// if (currentPhase === 'pickup-drop') {
// requestAnimationFrame(firstFrame);
// }
// }
// });
useEffect(() => {
if (isReset) {
if (isReset || !isPlaying) {
reset();
setCurrentPath([]);
setProgress(0);
progressRef.current = 0;
completedRef.current = false;
movingForward.current = true;
setRestingRotation(false);
progressRef.current = 0;
startTime = 0;
coveredDistance = 0;
setReset(false);
setRestingRotation(true);
decrementVehicleLoad(agvDetail.modelUuid, 0);
const object = scene.getObjectByProperty('uuid', agvUuid);
console.log('currentPhase: ', currentPhase);
if (object) {
object.position.set(agvDetail.position[0], agvDetail.position[1], agvDetail.position[2]);
object.rotation.set(objectRotation.x, objectRotation.y, objectRotation.z);
}
}
}, [isReset]);
}, [isReset, isPlaying])
useEffect(() => {
isPausedRef.current = isPaused;
}, [isPaused]);
useFrame((_, delta) => {
// If reset is active, don't run anything in frame
if (isReset) return;
const object = scene.getObjectByProperty('uuid', agvUuid);
if (!object || currentPath.length < 2) return;
if (isPaused) return;
let totalDistance = 0;
const distances = [];
let accumulatedDistance = 0;
let index = 0;
for (let i = 0; i < currentPath.length - 1; i++) {
const start = new THREE.Vector3(...currentPath[i]);
@ -170,10 +97,6 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
totalDistance += segmentDistance;
}
let coveredDistance = progressRef.current;
let accumulatedDistance = 0;
let index = 0;
while (index < distances.length && coveredDistance > accumulatedDistance + distances[index]) {
accumulatedDistance += distances[index];
index++;
@ -186,17 +109,16 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
const currentDirection = new THREE.Vector3().subVectors(end, start).normalize();
const targetAngle = Math.atan2(currentDirection.x, currentDirection.z);
const rotationSpeed = speed;
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 rotationSpeed = 2.0;
const maxRotationStep = rotationSpeed * delta;
const rotationStep = Math.sign(angleDifference) * Math.min(Math.abs(angleDifference), maxRotationStep);
object.rotation.y += rotationStep;
object.rotation.y += Math.sign(angleDifference) * Math.min(Math.abs(angleDifference), maxRotationStep);
const isAligned = Math.abs(angleDifference) < 0.01;
if (isAligned) {
@ -211,43 +133,63 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
if (progressRef.current >= totalDistance) {
if (restRotation) {
const targetQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0));
const targetQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(objectRotation.x, objectRotation.y, objectRotation.z));
object.quaternion.slerp(targetQuaternion, delta * 2);
const angleDiff = object.quaternion.angleTo(targetQuaternion);
if (angleDiff < 0.01) {
const objectRotation = agvDetail.point.rotation;
object.rotation.set(objectRotation[0], objectRotation[1], objectRotation[2]);
object.rotation.set(objectRotation.x, objectRotation.y, objectRotation.z);
setRestingRotation(false);
}
} else {
setRestingRotation(true);
progressRef.current = 0;
movingForward.current = !movingForward.current;
setCurrentPath([]);
handleCallBack();
if (currentPhase === 'pickup-drop') {
requestAnimationFrame(firstFrame);
}
return;
}
}
if (progressRef.current >= totalDistance) {
setRestingRotation(true);
progressRef.current = 0;
movingForward.current = !movingForward.current;
setCurrentPath([]);
handleCallBack();
if (currentPhase === 'pickup-drop') {
requestAnimationFrame(firstFrame);
}
}
});
function firstFrame() {
const unLoadDuration = agvDetail.point.action.unLoadDuration;
const droppedMaterial = agvDetail.currentLoad;
fixedInterval = (unLoadDuration / droppedMaterial) * 1000;
startTime = performance.now();
step(droppedMaterial);
}
function step(droppedMaterial: number) {
const elapsedTime = (performance.now() - startTime) * speed;
if (isPausedRef.current) {
if (!pauseTimeRef.current) {
pauseTimeRef.current = performance.now();
}
requestAnimationFrame(() => step(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);
if (droppedMat === 0) return;
startTime = performance.now();
requestAnimationFrame(() => step(droppedMat));
if (droppedMat > 0) {
startTime = performance.now();
requestAnimationFrame(() => step(droppedMat));
} else {
return;
}
} else {
requestAnimationFrame(() => step(droppedMaterial));
}

View File

@ -1,18 +1,18 @@
import React, { useCallback, useEffect, useState } from 'react';
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/store';
import { usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore';
function VehicleInstance({ agvDetail }: any) {
const { navMesh } = useNavMesh();
const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { isReset } = useResetButtonStore();
const { isPlaying } = usePlayButtonStore();
const { vehicles, setVehicleActive, setVehicleState, incrementVehicleLoad } = useVehicleStore();
const [currentPhase, setCurrentPhase] = useState<string>('stationed');
const [path, setPath] = useState<[number, number, number][]>([]);
let isIncrememtable = useRef<boolean>(true);
const computePath = useCallback(
(start: any, end: any) => {
@ -20,7 +20,7 @@ function VehicleInstance({ agvDetail }: any) {
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]) || []
segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []
);
} catch {
return [];
@ -29,77 +29,92 @@ function VehicleInstance({ agvDetail }: any) {
[navMesh]
);
function vehicleStatus(modelid: string, status: string) {
// console.log(`AGV ${modelid}: ${status}`);
function vehicleStatus(modelId: string, status: string) {
// console.log(`${modelId} , ${status});
}
// Function to reset everything
function reset() {
setCurrentPhase('stationed');
setVehicleActive(agvDetail.modelUuid, false);
setVehicleState(agvDetail.modelUuid, 'idle');
setPath([]);
setCurrentPhase('stationed')
}
const increment = () => {
if (isIncrememtable.current) {
incrementVehicleLoad(agvDetail.modelUuid, 2);
isIncrememtable.current = false;
}
}
useEffect(() => {
if (isPlaying) {
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
new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]),
agvDetail?.point?.action?.pickUpPoint?.position
);
setPath(toPickupPath);
setVehicleActive(agvDetail.modelUuid, true);
setVehicleState(agvDetail.modelUuid, 'running');
setCurrentPhase('stationed-pickup');
setVehicleState(agvDetail.modelUuid, 'running');
setVehicleActive(agvDetail.modelUuid, true);
vehicleStatus(agvDetail.modelUuid, 'Started from station, heading to pickup');
return;
} else if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'picking') {
setTimeout(() => {
incrementVehicleLoad(agvDetail.modelUuid, 2);
increment();
}, 5000);
if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity) {
const toDrop = computePath(
agvDetail.point.action.pickUpPoint,
agvDetail.point.action.unLoadPoint
agvDetail.point.action.pickUpPoint.position,
agvDetail.point.action.unLoadPoint.position
);
setPath(toDrop);
setVehicleActive(agvDetail.modelUuid, true);
setVehicleState(agvDetail.modelUuid, 'running');
setCurrentPhase('pickup-drop');
setVehicleState(agvDetail.modelUuid, 'running');
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) {
const dropToPickup = computePath(
agvDetail.point.action.unLoadPoint,
agvDetail.point.action.pickUpPoint
agvDetail.point.action.unLoadPoint.position,
agvDetail.point.action.pickUpPoint.position
);
setPath(dropToPickup);
setVehicleActive(agvDetail.modelUuid, true);
setVehicleState(agvDetail.modelUuid, 'running');
setCurrentPhase('drop-pickup');
setVehicleState(agvDetail.modelUuid, 'running');
setVehicleActive(agvDetail.modelUuid, true);
vehicleStatus(agvDetail.modelUuid, 'Started from dropping point, heading to pickup point');
isIncrememtable.current = true;
}
} else {
reset()
}
}, [vehicles, currentPhase, path, isPlaying, isReset]);
}, [vehicles, currentPhase, path, isPlaying]);
function handleCallBack() {
if (currentPhase === 'stationed-pickup') {
setVehicleActive(agvDetail.modelUuid, false);
setVehicleState(agvDetail.modelUuid, 'idle');
setCurrentPhase('picking');
setVehicleState(agvDetail.modelUuid, 'idle');
setVehicleActive(agvDetail.modelUuid, false);
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');
setVehicleState(agvDetail.modelUuid, 'idle');
setVehicleActive(agvDetail.modelUuid, false);
vehicleStatus(agvDetail.modelUuid, 'Reached drop point');
setPath([]);
} else if (currentPhase === 'drop-pickup') {
setVehicleActive(agvDetail.modelUuid, false);
setVehicleState(agvDetail.modelUuid, 'idle');
setCurrentPhase('picking');
setVehicleState(agvDetail.modelUuid, 'idle');
setVehicleActive(agvDetail.modelUuid, false);
setPath([]);
vehicleStatus(agvDetail.modelUuid, 'Reached pickup point again, cycle complete');
}

View File

@ -3,12 +3,16 @@ import VehicleInstance from './instance/vehicleInstance'
import { useVehicleStore } from '../../../../store/simulation/useVehicleStore'
function VehicleInstances() {
const { vehicles } = useVehicleStore();
return (
<>
{vehicles.map((val: any, i: any) =>
<VehicleInstance agvDetail={val} key={i} />
)}
</>

View File

@ -1,15 +1,21 @@
import React, { useEffect } from 'react'
import VehicleInstances from './instances/vehicleInstances';
import { useVehicleStore } from '../../../store/simulation/useVehicleStore';
import { useFloorItems } from '../../../store/store';
import React, { useEffect, useState } from "react";
import VehicleInstances from "./instances/vehicleInstances";
import { useVehicleStore } from "../../../store/simulation/useVehicleStore";
import { useFloorItems } from "../../../store/store";
import { useSelectedEventData, useSelectedEventSphere } from "../../../store/simulation/useSimulationStore";
import VehicleUI from "../ui/vehicle/vehicleUI";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
function Vehicles() {
const { vehicles, addVehicle } = useVehicleStore();
const { selectedEventSphere } = useSelectedEventSphere();
const { selectedEventData } = useSelectedEventData();
const { floorItems } = useFloorItems();
const { isPlaying } = usePlayButtonStore();
const vehicleStatusSample: VehicleEventSchema[] = [
const [vehicleStatusSample, setVehicleStatusSample] = useState<
VehicleEventSchema[]
>([
{
modelUuid: "9356f710-4727-4b50-bdb2-9c1e747ecc74",
modelName: "AGV",
@ -73,8 +79,8 @@ function Vehicles() {
unLoadDuration: 10,
loadCapacity: 2,
steeringAngle:0,
pickUpPoint: { position: { x: 90, y: 0, z: 28 }, rotation: { x: 0, y: 0, z: 0 } },
unLoadPoint: { position: { x: 20, y: 0, z: 10 }, rotation: { x: 0, y: 0, z: 0 } },
pickUpPoint: null,
unLoadPoint: null,
triggers: [
{
triggerUuid: "trig-001",
@ -98,69 +104,118 @@ function Vehicles() {
}
}
},
{
modelUuid: "e729a4f1-11d2-4778-8d6a-468f1b4f6b79",
modelName: "forklift",
position: [98.85729337188162, 0, 38.36616546567653],
rotation: [0, 0, 0],
state: "idle",
type: "vehicle",
speed: 2.5,
point: {
uuid: "point-789",
position: [0, 1, 0],
rotation: [0, 0, 0],
action: {
actionUuid: "action-456",
actionName: "Deliver to Zone A",
actionType: "travel",
unLoadDuration: 15,
loadCapacity: 5,
steeringAngle:0,
pickUpPoint: { position: { x: 98.71483985219794, y: 0, z: 28.66321267938962 }, rotation: { x: 0, y: 0, z: 0 } },
unLoadPoint: { position: { x: 20, y: 0, z: 10 }, rotation: { x: 0, y: 0, z: 0 } },
triggers: [
{
triggerUuid: "trig-001",
triggerName: "Start Travel",
triggerType: "onStart",
delay: 0,
triggeredAsset: {
triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
}
},
{
triggerUuid: "trig-002",
triggerName: "Complete Travel",
triggerType: "onComplete",
delay: 2,
triggeredAsset: null
}
]
}
}
}
];
// {
// modelUuid: "cd7d0584-0684-42b4-b051-9e882c1914aa",
// modelName: "AGV",
// position: [105.90938758014703, 0, 31.584209911095215],
// rotation: [0, 0, 0],
// state: "idle",
// type: "vehicle",
// speed: 2.5,
// point: {
// uuid: "point-789",
// position: [0, 1, 0],
// rotation: [0, 0, 0],
// action: {
// actionUuid: "action-456",
// actionName: "Deliver to Zone A",
// actionType: "travel",
// unLoadDuration: 10,
// loadCapacity: 2,
// steeringAngle:0,
// pickUpPoint: null,
// unLoadPoint: null,
// triggers: [
// {
// triggerUuid: "trig-001",
// triggerName: "Start Travel",
// triggerType: "onStart",
// delay: 0,
// triggeredAsset: {
// triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
// triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
// triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
// }
// },
// {
// triggerUuid: "trig-002",
// triggerName: "Complete Travel",
// triggerType: "onComplete",
// delay: 2,
// triggeredAsset: null
// }
// ]
// }
// }
// },
// {
// modelUuid: "e729a4f1-11d2-4778-8d6a-468f1b4f6b79",
// modelName: "forklift",
// position: [98.85729337188162, 0, 38.36616546567653],
// rotation: [0, 0, 0],
// state: "idle",
// type: "vehicle",
// speed: 2.5,
// point: {
// uuid: "point-789",
// position: [0, 1, 0],
// rotation: [0, 0, 0],
// action: {
// actionUuid: "action-456",
// actionName: "Deliver to Zone A",
// actionType: "travel",
// unLoadDuration: 15,
// loadCapacity: 5,
// steeringAngle:0,
// pickUpPoint: null,
// unLoadPoint: null,
// triggers: [
// {
// triggerUuid: "trig-001",
// triggerName: "Start Travel",
// triggerType: "onStart",
// delay: 0,
// triggeredAsset: {
// triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
// triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
// triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
// }
// },
// {
// triggerUuid: "trig-002",
// triggerName: "Complete Travel",
// triggerType: "onComplete",
// delay: 2,
// triggeredAsset: null
// }
// ]
// }
// }
// }
]);
useEffect(() => {
addVehicle('123', vehicleStatusSample[0]);
// addVehicle('123', vehicleStatusSample[1]);
// addVehicle('123', vehicleStatusSample[2]);
}, [])
console.log('vehicles: ', vehicles);
useEffect(() => {
// console.log('vehicles: ', vehicles);
}, [vehicles])
useEffect(() => {
addVehicle("123", vehicleStatusSample[0]);
addVehicle('123', vehicleStatusSample[1]);
// addVehicle('123', vehicleStatusSample[2]);
}, []);
return (
<>
<VehicleInstances />
{selectedEventSphere && selectedEventData?.data.type === "vehicle" && !isPlaying &&
< VehicleUI />
}
</>
)
);
}
export default Vehicles;
export default Vehicles;