From b701db74551f19c6d30b4ffd3c316ea7cd16c044 Mon Sep 17 00:00:00 2001 From: Poovizhi99 Date: Thu, 8 May 2025 10:47:21 +0530 Subject: [PATCH] added steering for vehicle --- .../simulation/ui/vehicle/vehicleUI.tsx | 544 +++++++++++------- .../instances/animator/vehicleAnimator.tsx | 38 +- .../modules/simulation/vehicle/vehicles.tsx | 29 +- 3 files changed, 395 insertions(+), 216 deletions(-) diff --git a/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx b/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx index 2a124cd..12a4b3a 100644 --- a/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx +++ b/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx @@ -1,232 +1,380 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from "react"; import * as Types from "../../../../types/world/worldTypes"; import startPoint from "../../../../assets/gltf-glb/arrow_green.glb"; -import * as THREE from "three"; import startEnd from "../../../../assets/gltf-glb/arrow_red.glb"; -import { useGLTF } from '@react-three/drei'; -import { useFrame, useThree } from '@react-three/fiber'; -import { useSelectedEventSphere, useIsDragging, useIsRotating } from '../../../../store/simulation/useSimulationStore'; -import { useVehicleStore } from '../../../../store/simulation/useVehicleStore'; -import { useProductStore } from '../../../../store/simulation/useProductStore'; -import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore'; -import { upsertProductOrEventApi } from '../../../../services/simulation/UpsertProductOrEventApi'; +import { useGLTF } from "@react-three/drei"; +import { useFrame, useThree } from "@react-three/fiber"; +import { + useSelectedEventSphere, + useIsDragging, + useIsRotating, +} from "../../../../store/simulation/useSimulationStore"; +import { useVehicleStore } from "../../../../store/simulation/useVehicleStore"; +import { useProductStore } from "../../../../store/simulation/useProductStore"; +import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore"; +import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; +import { Box3, DoubleSide, Euler, Group, Mesh, Plane, Quaternion, Vector3 } from "three"; +import { position } from "html2canvas/dist/types/css/property-descriptors/position"; 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 { selectedProduct } = useSelectedProduct(); - const { getVehicleById } = useVehicleStore(); - const { updateEvent } = useProductStore(); - 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 } = useIsDragging(); - const { isRotating, setIsRotating } = useIsRotating(); - 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; + 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 { selectedProduct } = useSelectedProduct(); + const { getVehicleById } = useVehicleStore(); + const { updateEvent } = useProductStore(); + const [startPosition, setStartPosition] = useState<[number, number, number]>([ + 0, 1, 0, + ]); - const email = localStorage.getItem('email') - const organization = (email!.split("@")[1]).split(".")[0]; - const updateBackend = ( - productName: string, - productId: string, - organization: string, - eventData: EventsSchema - ) => { - upsertProductOrEventApi({ - productName: productName, - productId: productId, - organization: organization, - eventDatas: eventData - }) - } + const [endPosition, setEndPosition] = useState<[number, number, number]>([ + 0, 1, 0, + ]); + const [startRotation, setStartRotation] = useState<[number, number, number]>([ + 0, 0, 0, + ]); - useEffect(() => { - if (!selectedEventSphere) return; - const selectedVehicle = getVehicleById(selectedEventSphere.userData.modelUuid); + const [endRotation, setEndRotation] = useState<[number, number, number]>([ + 0, 0, 0, + ]); + const [steeringRotation, setSteeringRotation] = useState< + [number, number, number] + >([0, 0, 0]); - if (selectedVehicle?.point?.action) { - const { pickUpPoint, unLoadPoint } = selectedVehicle.point.action; + const { isDragging, setIsDragging } = useIsDragging(); + const { isRotating, setIsRotating } = useIsRotating(); + const { raycaster } = useThree(); + const [point, setPoint] = useState< + [number, number, number] + >([0, 0, 0]); + const plane = useRef(new Plane(new Vector3(0, 1, 0), 0)); + const [tubeRotation, setTubeRotation] = useState(false); + const tubeRef = useRef(null); + const outerGroup = useRef(null); + const state: Types.ThreeState = useThree(); + const controls: any = state.controls; + const [selectedVehicleData, setSelectedVechicleData] = useState< + { position: [number, number, number], rotation: [number, number, number], } + >({ position: [0, 0, 0], rotation: [0, 0, 0], }); + const CIRCLE_RADIUS = 0.8; + const email = localStorage.getItem("email"); + const organization = email!.split("@")[1].split(".")[0]; - if (pickUpPoint) { - setStartPosition([pickUpPoint.position.x, 0, pickUpPoint.position.z]); - setStartRotation([pickUpPoint.rotation.x, pickUpPoint.rotation.y, pickUpPoint.rotation.z]); + const updateBackend = ( + productName: string, + productId: string, + organization: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productId: productId, + organization: organization, + eventDatas: eventData, + }); + }; + + useEffect(() => { + if (!selectedEventSphere) return; + const selectedVehicle = getVehicleById( + selectedEventSphere.userData.modelUuid + ); + + if (selectedVehicle) { + + + setSelectedVechicleData({ position: selectedVehicle.position, rotation: selectedVehicle.rotation }); + setPoint(selectedVehicle.point.position) + } + + + setTimeout(() => { + if (selectedVehicle?.point?.action) { + const { pickUpPoint, unLoadPoint, steeringAngle } = selectedVehicle.point.action; + + if (pickUpPoint && outerGroup.current) { + const worldPos = new Vector3( + pickUpPoint.position.x, + pickUpPoint.position.y, + pickUpPoint.position.z + ); + const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); + + + setStartPosition([ + localPosition.x, + selectedVehicle.point.position[1], + localPosition.z, + ]); + setStartRotation([pickUpPoint.rotation.x, pickUpPoint.rotation.y, pickUpPoint.rotation.z,]) } else { - const defaultLocal = new THREE.Vector3(0, 0, 1.5); - const defaultWorld = selectedEventSphere.localToWorld(defaultLocal); - setStartPosition([defaultWorld.x, 0, defaultWorld.z]); - setStartRotation([0, 0, 0]); + setStartPosition([0, selectedVehicle.point.position[1] + 0.1, 1.5]); + setStartRotation([0, 0, 0]); } + // end point + if (unLoadPoint && outerGroup.current) { + const worldPos = new Vector3(unLoadPoint.position.x, unLoadPoint.position.y, unLoadPoint.position.z); + const localPosition = outerGroup.current.worldToLocal(worldPos); - if (unLoadPoint) { - setEndPosition([unLoadPoint.position.x, 0, unLoadPoint.position.z]); - setEndRotation([unLoadPoint.rotation.x, unLoadPoint.rotation.y, unLoadPoint.rotation.z]); + setEndPosition([localPosition.x, selectedVehicle.point.position[1], localPosition.z]); + setEndRotation([ + unLoadPoint.rotation.x, + unLoadPoint.rotation.y, + unLoadPoint.rotation.z, + ]); } else { - const defaultLocal = new THREE.Vector3(0, 0, -1.5); - const defaultWorld = selectedEventSphere.localToWorld(defaultLocal); - setEndPosition([defaultWorld.x, 0, defaultWorld.z]); - setEndRotation([0, 0, 0]); + setEndPosition([0, selectedVehicle.point.position[1] + 0.1, -1.5]); + setEndRotation([0, 0, 0]); } - } - }, [selectedEventSphere]); - - useFrame(() => { - if (!isDragging) return; - const intersectPoint = new THREE.Vector3(); - const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint); - - if (intersects) { - 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; - if (isRotating === 'start') { - const y = startRotation[1] + deltaX * rotationSpeed; - setStartRotation([0, y, 0]); - } else { - const y = endRotation[1] + deltaX * rotationSpeed; - setEndRotation([0, y, 0]); - } - } - }); + setSteeringRotation([0, steeringAngle, 0]) + } + }, 10); - const handlePointerDown = (e: any, state: "start" | "end", rotation: "start" | "end") => { + }, [selectedEventSphere, outerGroup.current]); - 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); + 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; + } + }; - } else { - setIsDragging(state); - setIsRotating(null); - if (controls) controls.enabled = false; - } - }; + const handlePointerUp = () => { + controls.enabled = true; + setIsDragging(null); + setIsRotating(null); - const handlePointerUp = () => { - controls.enabled = true; - setIsDragging(null); - setIsRotating(null); + if (selectedEventSphere?.userData.modelUuid) { + const updatedVehicle = getVehicleById( + selectedEventSphere.userData.modelUuid + ); - if (selectedEventSphere?.userData.modelUuid) { - const updatedVehicle = getVehicleById(selectedEventSphere.userData.modelUuid); + let globalStartPosition = null; + let globalEndPosition = null; - if (updatedVehicle) { - const event = updateEvent(selectedProduct.productId, selectedEventSphere.userData.modelUuid, { - point: { - ...updatedVehicle.point, - action: { - ...updatedVehicle.point?.action, - pickUpPoint: { - position: { x: startPosition[0], y: startPosition[1], z: startPosition[2], }, - rotation: { x: 0, y: startRotation[1], z: 0, }, - }, - unLoadPoint: { - position: { x: endPosition[0], y: endPosition[1], z: endPosition[2], }, - rotation: { x: 0, y: endRotation[1], z: 0, }, - }, + if (outerGroup.current && startMarker.current && endMarker.current) { + const worldPosStart = new Vector3(...startPosition); + globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone()); + const worldPosEnd = new Vector3(...endPosition); + globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone()); + } + if (updatedVehicle && globalEndPosition && globalStartPosition) { + const event = updateEvent( + selectedProduct.productId, + selectedEventSphere.userData.modelUuid, + { + point: { + ...updatedVehicle.point, + action: { + ...updatedVehicle.point?.action, + pickUpPoint: { + position: { + x: globalStartPosition.x, + y: 0, + z: globalStartPosition.z, + }, + rotation: { x: 0, y: startRotation[1], z: 0 }, }, - }, - }) + unLoadPoint: { + position: { + x: globalEndPosition.x, + y: 0, + z: globalEndPosition.z, + }, + rotation: { x: 0, y: endRotation[1], z: 0 }, + }, + steeringAngle: steeringRotation[1] + }, + }, + } + ); - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productId, - organization, - event - ); - } + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); } - } - }; + } + } + }; - useEffect(() => { - const handleGlobalPointerUp = () => { - setIsDragging(null); - setIsRotating(null); - if (controls) controls.enabled = true; - handlePointerUp(); - }; + useFrame(() => { + if (!isDragging || !plane.current || !raycaster || !outerGroup.current) return; + const intersectPoint = new Vector3(); + const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint); + if (!intersects) return; + const localPoint = outerGroup?.current.worldToLocal(intersectPoint.clone()); + if (isDragging === "start") { + if (startMarker.current) { - if (isDragging || isRotating) { - window.addEventListener("pointerup", handleGlobalPointerUp); - } + } + setStartPosition([localPoint.x, point[1], localPoint.z]); + } else if (isDragging === "end") { + setEndPosition([localPoint.x, point[1], localPoint.z]); + } + }); - return () => { - window.removeEventListener("pointerup", handleGlobalPointerUp); - }; - }, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation]); + useEffect(() => { + const handleGlobalPointerUp = () => { + setIsDragging(null); + setIsRotating(null); + setTubeRotation(false); + if (controls) controls.enabled = true; + handlePointerUp(); - return ( - startPosition.length > 0 && endPosition.length > 0 ? ( - - { - e.stopPropagation(); - handlePointerDown(e, "start", "start"); - }} - onPointerMissed={() => { - controls.enabled = true; - setIsDragging(null); - setIsRotating(null); - }} - /> + }; + + if (isDragging || isRotating || tubeRotation) { + window.addEventListener("pointerup", handleGlobalPointerUp); + } + + return () => { + window.removeEventListener("pointerup", handleGlobalPointerUp); + }; + }, [ + isDragging, isRotating, startPosition, startRotation, endPosition, endRotation, tubeRotation, steeringRotation, outerGroup.current, tubeRef.current + ]); + + + const prevSteeringY = useRef(0); + useFrame((state) => { + if (tubeRotation) { + const currentPointerX = state.pointer.x; + const deltaX = currentPointerX - prevMousePos.current.x; + prevMousePos.current.x = currentPointerX; + + const marker = tubeRef.current; + if (marker) { + const rotationSpeed = 2; + marker.rotation.y += deltaX * rotationSpeed; + setSteeringRotation([marker.rotation.x, marker.rotation.y, marker.rotation.z]); + } + } else { + prevSteeringY.current = 0; + } + }); + 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, + ]); + } + } + }); + + return selectedVehicleData ? ( + + { + e.stopPropagation(); + setTubeRotation(true); + prevMousePos.current.x = e.pointer.x; + controls.enabled = false; + }} + onPointerMissed={() => { + controls.enabled = true; + setTubeRotation(false); + }} + onPointerUp={() => { + controls.enabled = true; + setTubeRotation(false); + }} + > + ( + + + - { - e.stopPropagation(); - handlePointerDown(e, "end", "end"); - }} - onPointerMissed={() => { - controls.enabled = true; - setIsDragging(null); - setIsRotating(null); - }} - /> - ) : null - ); -} + + + + + ) + + + {/* Start Marker */} + { + e.stopPropagation(); + handlePointerDown(e, "start", "start"); + }} + onPointerMissed={() => { + controls.enabled = true; + setIsDragging(null); + setIsRotating(null); + }} + /> + + {/* End Marker */} + { + 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 460997a..62f230e 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -33,6 +33,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai 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 } | undefined; + const [rotatingToSteering, setRotatingToSteering] = useState(false); useEffect(() => { if (currentPhase === 'stationed-pickup' && path.length > 0) { @@ -133,18 +134,39 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai } if (progressRef.current >= totalDistance) { - if (restRotation && objectRotation) { - 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) { - object.rotation.set(objectRotation.x, objectRotation.y, objectRotation.z); + const restQuaternion = objectRotation ? + new THREE.Quaternion().setFromEuler(new THREE.Euler(objectRotation.x, objectRotation.y, objectRotation.z)) : + object.quaternion.clone(); + + const steeringQuaternion = new THREE.Quaternion().setFromEuler( + new THREE.Euler(objectRotation ? objectRotation.x : object.rotation.x, + agvDetail.point.action.steeringAngle, + objectRotation ? objectRotation.z : object.rotation.z) + ); + + if (restRotation) { + object.quaternion.slerp(restQuaternion, delta * 2); + const restAngleDiff = object.quaternion.angleTo(restQuaternion); + if (restAngleDiff < 0.01) { setRestingRotation(false); + setRotatingToSteering(true); + } + return; + } + else if (rotatingToSteering) { + object.quaternion.slerp(steeringQuaternion, delta * 2); + const steeringAngleDiff = object.quaternion.angleTo(steeringQuaternion); + if (steeringAngleDiff < 0.01) { + object.rotation.set( + objectRotation ? objectRotation.x : object.rotation.x, + agvDetail.point.action.steeringAngle, + objectRotation ? objectRotation.z : object.rotation.z + ); + setRotatingToSteering(false); } return; } } - if (progressRef.current >= totalDistance) { setRestingRotation(true); progressRef.current = 0; @@ -199,7 +221,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai return ( <> {currentPath.length > 0 && ( - + {currentPath.map((point, index) => ( diff --git a/app/src/modules/simulation/vehicle/vehicles.tsx b/app/src/modules/simulation/vehicle/vehicles.tsx index 68f40cf..93a275d 100644 --- a/app/src/modules/simulation/vehicle/vehicles.tsx +++ b/app/src/modules/simulation/vehicle/vehicles.tsx @@ -1,31 +1,40 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import VehicleInstances from "./instances/vehicleInstances"; import { useVehicleStore } from "../../../store/simulation/useVehicleStore"; -import { useSelectedEventData, useSelectedEventSphere } from "../../../store/simulation/useSimulationStore"; +import { useSelectedEventSphere } from "../../../store/simulation/useSimulationStore"; import VehicleUI from "../ui/vehicle/vehicleUI"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; function Vehicles() { - const { vehicles } = useVehicleStore(); + const { vehicles, getVehicleById } = useVehicleStore(); const { selectedEventSphere } = useSelectedEventSphere(); - const { selectedEventData } = useSelectedEventData(); const { isPlaying } = usePlayButtonStore(); + const [isVehicleSelected, setIsVehicleSelected] = useState(false); + useEffect(() => { // console.log('vehicles: ', vehicles); }, [vehicles]) + useEffect(() => { + if (selectedEventSphere) { + const selectedVehicle = getVehicleById(selectedEventSphere.userData.modelUuid); + if (selectedVehicle) { + setIsVehicleSelected(true); + } else { + setIsVehicleSelected(false); + } + } + }, [selectedEventSphere]) + return ( <> - - - {selectedEventSphere && selectedEventData?.data.type === "vehicle" && !isPlaying && - < VehicleUI /> + {isVehicleSelected && selectedEventSphere && !isPlaying && + } - ); } -export default Vehicles; \ No newline at end of file +export default Vehicles;