diff --git a/app/src/assets/gltf-glb/arrow_green.glb b/app/src/assets/gltf-glb/arrow_green.glb new file mode 100644 index 0000000..8e02527 Binary files /dev/null and b/app/src/assets/gltf-glb/arrow_green.glb differ diff --git a/app/src/assets/gltf-glb/arrow_red.glb b/app/src/assets/gltf-glb/arrow_red.glb new file mode 100644 index 0000000..02bfea4 Binary files /dev/null and b/app/src/assets/gltf-glb/arrow_red.glb differ diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx index 528fbc2..fb59d3a 100644 --- a/app/src/components/ui/simulation/simulationPlayer.tsx +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -21,6 +21,7 @@ const SimulationPlayer: React.FC = () => { // Button functions const handleReset = () => { setReset(true); + // setReset(!isReset); setSpeed(1); }; const handlePlayStop = () => { diff --git a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx index 8e476d0..9d32063 100644 --- a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx +++ b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx @@ -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(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" && ( <> - + {events.map((event, i) => { - if (event.type === 'transfer') { + if (event.type === "transfer") { return ( {event.points.map((point, j) => ( (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() { ))} ); - } else if (event.type === 'vehicle') { + } else if (event.type === "vehicle") { return ( (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() { ); - } else if (event.type === 'roboticArm') { + } else if (event.type === "roboticArm") { return ( (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() { ); - } else if (event.type === 'machine') { + } else if (event.type === "machine") { return ( (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() { } })} - {(selectedEventSphere && transformMode) && - { updatePointToState(selectedEventSphere) }} /> - } + {selectedEventSphere && transformMode && ( + { + updatePointToState(selectedEventSphere); + }} + /> + )} - } + )} ); } diff --git a/app/src/modules/simulation/ui/vehicle/useDraggableGLTF.ts b/app/src/modules/simulation/ui/vehicle/useDraggableGLTF.ts new file mode 100644 index 0000000..b7e9272 --- /dev/null +++ b/app/src/modules/simulation/ui/vehicle/useDraggableGLTF.ts @@ -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(null); + const planeRef = useRef( + new THREE.Plane(new THREE.Vector3(0, 1, 0), 0) + ); + const offsetRef = useRef(new THREE.Vector3()); + const initialPositionRef = useRef(new THREE.Vector3()); + + const raycaster = new THREE.Raycaster(); + const pointer = new THREE.Vector2(); + + const handlePointerDown = (e: ThreeEvent) => { + 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 }; +} + + + diff --git a/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx b/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx new file mode 100644 index 0000000..5dec724 --- /dev/null +++ b/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx @@ -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(null); + const endMarker = useRef(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 ? ( + + { + e.stopPropagation(); + handlePointerDown(e, "start", "start"); + }} + onPointerMissed={() => { + controls.enabled = true; + setIsDragging(null); + setIsRotating(null); + }} + /> + + { + e.stopPropagation(); + handlePointerDown(e, "end", "end"); + }} + onPointerMissed={() => { + controls.enabled = true; + setIsDragging(null); + setIsRotating(null); + }} + /> + + ) : null + ); +} +export default VehicleUI; + + diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index a8cfc39..2f0b235 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -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(true); - const [progress, setProgress] = useState(0); - const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); - const { scene } = useThree(); + const { isReset, setReset } = useResetButtonStore(); const progressRef = useRef(0); const movingForward = useRef(true); const completedRef = useRef(false); + const isPausedRef = useRef(false); + const pauseTimeRef = useRef(null); + const [restRotation, setRestingRotation] = useState(true); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + const [progress, setProgress] = useState(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)); } diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index a7a41d3..6a81d3a 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -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('stationed'); const [path, setPath] = useState<[number, number, number][]>([]); + let isIncrememtable = useRef(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'); } diff --git a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx index 2a0070b..91111cf 100644 --- a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx +++ b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx @@ -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) => + + )} diff --git a/app/src/modules/simulation/vehicle/vehicles.tsx b/app/src/modules/simulation/vehicle/vehicles.tsx index c7fd4b6..d5798db 100644 --- a/app/src/modules/simulation/vehicle/vehicles.tsx +++ b/app/src/modules/simulation/vehicle/vehicles.tsx @@ -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 ( <> + {selectedEventSphere && selectedEventData?.data.type === "vehicle" && !isPlaying && + < VehicleUI /> + } - ) + ); } -export default Vehicles; \ No newline at end of file +export default Vehicles; + + +