From 5e025224d6cb797cdd99bd9e8f9068c3e2d2c80a Mon Sep 17 00:00:00 2001 From: Poovizhi Date: Wed, 13 Aug 2025 15:24:32 +0530 Subject: [PATCH] added pre defined paths in rough --- app/src/functions/findShortestPath.ts | 0 .../aisle/aisleCreator/aisleCreator.tsx | 1169 +++++++++-------- .../instances/animator/vehicleAnimator.tsx | 2 +- .../vehicle/preDefinedPath/PointHandle.tsx | 586 +++++++++ .../functions/findShortestPath.ts | 22 + .../vehicle/preDefinedPath/lineSegment.tsx | 87 ++ .../vehicle/preDefinedPath/preDefinedPath.tsx | 717 ++++++++++ .../modules/simulation/vehicle/vehicles.tsx | 51 +- 8 files changed, 2039 insertions(+), 595 deletions(-) create mode 100644 app/src/functions/findShortestPath.ts create mode 100644 app/src/modules/simulation/vehicle/preDefinedPath/PointHandle.tsx create mode 100644 app/src/modules/simulation/vehicle/preDefinedPath/functions/findShortestPath.ts create mode 100644 app/src/modules/simulation/vehicle/preDefinedPath/lineSegment.tsx create mode 100644 app/src/modules/simulation/vehicle/preDefinedPath/preDefinedPath.tsx diff --git a/app/src/functions/findShortestPath.ts b/app/src/functions/findShortestPath.ts new file mode 100644 index 0000000..e69de29 diff --git a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx index aea7cd2..2bd8656 100644 --- a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx +++ b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx @@ -1,580 +1,613 @@ -import * as THREE from 'three' -import { useEffect, useMemo, useRef, useState } from 'react' -import { useThree } from '@react-three/fiber'; -import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store'; -import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; -import { useParams } from 'react-router-dom'; -import { useVersionContext } from '../../version/versionContext'; -import { useSceneContext } from '../../../scene/sceneContext'; -import ReferenceAisle from './referenceAisle'; -import ReferencePoint from '../../point/reference/referencePoint'; -import { getUserData } from '../../../../functions/getUserData'; +import * as THREE from "three"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { useThree } from "@react-three/fiber"; +import { + useActiveLayer, + useSocketStore, + useToggleView, + useToolMode, +} from "../../../../store/builder/store"; +import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; +import { useParams } from "react-router-dom"; +import { useVersionContext } from "../../version/versionContext"; +import { useSceneContext } from "../../../scene/sceneContext"; +import ReferenceAisle from "./referenceAisle"; +import ReferencePoint from "../../point/reference/referencePoint"; +import { getUserData } from "../../../../functions/getUserData"; // import { upsertAisleApi } from '../../../../services/factoryBuilder/aisle/upsertAisleApi'; function AisleCreator() { - const { scene, camera, raycaster, gl, pointer } = useThree(); - const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); - const { toggleView } = useToggleView(); - const { toolMode } = useToolMode(); - const { activeLayer } = useActiveLayer(); - const { socket } = useSocketStore(); - const { aisleStore, undoRedo2DStore } = useSceneContext(); - const { addAisle, getAislePointById } = aisleStore(); - const { push2D } = undoRedo2DStore(); - const drag = useRef(false); - const isLeftMouseDown = useRef(false); - const { selectedVersionStore } = useVersionContext(); - const { selectedVersion } = selectedVersionStore(); - const { userId, organization } = getUserData(); - const { projectId } = useParams(); - - const [tempPoints, setTempPoints] = useState([]); - const [isCreating, setIsCreating] = useState(false); - const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, isFlipped, snappedPosition, snappedPoint, setSnappedPosition, setSnappedPoint } = useBuilderStore(); - - useEffect(() => { - const canvasElement = gl.domElement; - - const onMouseDown = (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown.current = true; - drag.current = false; - } - }; - - const onMouseUp = (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown.current = false; - } - }; - - const onMouseMove = () => { - if (isLeftMouseDown) { - drag.current = true; - } - }; - - const onMouseClick = () => { - if (drag.current || !toggleView) return; - - raycaster.setFromCamera(pointer, camera); - const intersectionPoint = new THREE.Vector3(); - let position = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (!position) return; - - const intersects = raycaster.intersectObjects(scene.children).find((intersect) => intersect.object.name === 'Aisle-Point'); - - const newPoint: Point = { - pointUuid: THREE.MathUtils.generateUUID(), - pointType: 'Aisle', - position: [position.x, position.y, position.z], - layer: activeLayer - }; - - if (snappedPosition && snappedPoint) { - newPoint.pointUuid = snappedPoint.pointUuid; - newPoint.position = snappedPosition; - newPoint.layer = snappedPoint.layer; - } - - if (snappedPoint && snappedPoint.pointUuid === tempPoints[0]?.pointUuid) { return } - - if (snappedPosition && !snappedPoint) { - newPoint.position = snappedPosition; - } - - if (intersects && !snappedPoint) { - const point = getAislePointById(intersects.object.uuid); - if (point) { - newPoint.pointUuid = point.pointUuid; - newPoint.position = point.position; - newPoint.layer = point.layer; - } - } - - if (aisleType === 'solid-aisle') { - - if (tempPoints.length === 0) { - setTempPoints([newPoint]); - setIsCreating(true); - } else { - const aisle: Aisle = { - aisleUuid: THREE.MathUtils.generateUUID(), - points: [tempPoints[0], newPoint], - type: { - aisleType: 'solid-aisle', - aisleColor: aisleColor, - aisleWidth: aisleWidth - } - }; - - addAisle(aisle); - - push2D({ - type: 'Draw', - actions: [ - { - actionType: 'Line-Create', - point: { - type: 'Aisle', - lineData: aisle, - timeStamp: new Date().toISOString(), - } - } - ], - }) - - if (projectId) { - - // API - - // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') - - // SOCKET - - socket.emit('v1:model-aisle:add', { - projectId: projectId, - versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization, - aisleUuid: aisle.aisleUuid, - points: aisle.points, - type: aisle.type - }) - } - setTempPoints([newPoint]); - } - } else if (aisleType === 'dashed-aisle') { - - if (tempPoints.length === 0) { - setTempPoints([newPoint]); - setIsCreating(true); - } else { - const aisle: Aisle = { - aisleUuid: THREE.MathUtils.generateUUID(), - points: [tempPoints[0], newPoint], - type: { - aisleType: 'dashed-aisle', - aisleColor: aisleColor, - aisleWidth: aisleWidth, - dashLength: dashLength, - gapLength: gapLength - } - }; - - addAisle(aisle); - - push2D({ - type: 'Draw', - actions: [ - { - actionType: 'Line-Create', - point: { - type: 'Aisle', - lineData: aisle, - timeStamp: new Date().toISOString(), - } - } - ], - }) - - if (projectId) { - - // API - - // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') - - // SOCKET - - socket.emit('v1:model-aisle:add', { - projectId: projectId, - versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization, - aisleUuid: aisle.aisleUuid, - points: aisle.points, - type: aisle.type - }) - } - setTempPoints([newPoint]); - } - } else if (aisleType === 'dotted-aisle') { - - if (tempPoints.length === 0) { - setTempPoints([newPoint]); - setIsCreating(true); - } else { - const aisle: Aisle = { - aisleUuid: THREE.MathUtils.generateUUID(), - points: [tempPoints[0], newPoint], - type: { - aisleType: 'dotted-aisle', - aisleColor: aisleColor, - dotRadius: dotRadius, - gapLength: gapLength - } - }; - - addAisle(aisle); - - push2D({ - type: 'Draw', - actions: [ - { - actionType: 'Line-Create', - point: { - type: 'Aisle', - lineData: aisle, - timeStamp: new Date().toISOString(), - } - } - ], - }) - - if (projectId) { - - // API - - // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') - - // SOCKET - - socket.emit('v1:model-aisle:add', { - projectId: projectId, - versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization, - aisleUuid: aisle.aisleUuid, - points: aisle.points, - type: aisle.type - }) - } - setTempPoints([newPoint]); - } - } else if (aisleType === 'arrow-aisle') { - - if (tempPoints.length === 0) { - setTempPoints([newPoint]); - setIsCreating(true); - } else { - const aisle: Aisle = { - aisleUuid: THREE.MathUtils.generateUUID(), - points: [tempPoints[0], newPoint], - type: { - aisleType: 'arrow-aisle', - aisleColor: aisleColor, - aisleWidth: aisleWidth - } - }; - - addAisle(aisle); - - push2D({ - type: 'Draw', - actions: [ - { - actionType: 'Line-Create', - point: { - type: 'Aisle', - lineData: aisle, - timeStamp: new Date().toISOString(), - } - } - ], - }) - - if (projectId) { - - // API - - // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') - - // SOCKET - - socket.emit('v1:model-aisle:add', { - projectId: projectId, - versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization, - aisleUuid: aisle.aisleUuid, - points: aisle.points, - type: aisle.type - }) - } - setTempPoints([newPoint]); - } - } else if (aisleType === 'arrows-aisle') { - - if (tempPoints.length === 0) { - setTempPoints([newPoint]); - setIsCreating(true); - } else { - const aisle: Aisle = { - aisleUuid: THREE.MathUtils.generateUUID(), - points: [tempPoints[0], newPoint], - type: { - aisleType: 'arrows-aisle', - aisleColor: aisleColor, - aisleWidth: aisleWidth, - aisleLength: aisleLength, - gapLength: gapLength - } - }; - - addAisle(aisle); - - push2D({ - type: 'Draw', - actions: [ - { - actionType: 'Line-Create', - point: { - type: 'Aisle', - lineData: aisle, - timeStamp: new Date().toISOString(), - } - } - ], - }) - - if (projectId) { - - // API - - // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') - - // SOCKET - - socket.emit('v1:model-aisle:add', { - projectId: projectId, - versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization, - aisleUuid: aisle.aisleUuid, - points: aisle.points, - type: aisle.type - }) - } - setTempPoints([newPoint]); - } - } else if (aisleType === 'arc-aisle') { - - if (tempPoints.length === 0) { - setTempPoints([newPoint]); - setIsCreating(true); - } else { - const aisle: Aisle = { - aisleUuid: THREE.MathUtils.generateUUID(), - points: [tempPoints[0], newPoint], - type: { - aisleType: 'arc-aisle', - aisleColor: aisleColor, - aisleWidth: aisleWidth, - isFlipped: isFlipped - } - }; - - addAisle(aisle); - - push2D({ - type: 'Draw', - actions: [ - { - actionType: 'Line-Create', - point: { - type: 'Aisle', - lineData: aisle, - timeStamp: new Date().toISOString(), - } - } - ], - }) - - if (projectId) { - - // API - - // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') - - // SOCKET - - socket.emit('v1:model-aisle:add', { - projectId: projectId, - versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization, - aisleUuid: aisle.aisleUuid, - points: aisle.points, - type: aisle.type - }) - } - setTempPoints([newPoint]); - } - } else if (aisleType === 'circle-aisle') { - - if (tempPoints.length === 0) { - setTempPoints([newPoint]); - setIsCreating(true); - } else { - const aisle: Aisle = { - aisleUuid: THREE.MathUtils.generateUUID(), - points: [tempPoints[0], newPoint], - type: { - aisleType: 'circle-aisle', - aisleColor: aisleColor, - aisleWidth: aisleWidth - } - }; - - addAisle(aisle); - - push2D({ - type: 'Draw', - actions: [ - { - actionType: 'Line-Create', - point: { - type: 'Aisle', - lineData: aisle, - timeStamp: new Date().toISOString(), - } - } - ], - }) - - if (projectId) { - - // API - - // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') - - // SOCKET - - socket.emit('v1:model-aisle:add', { - projectId: projectId, - versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization, - aisleUuid: aisle.aisleUuid, - points: aisle.points, - type: aisle.type - }) - } - setTempPoints([newPoint]); - } - } else if (aisleType === 'junction-aisle') { - - if (tempPoints.length === 0) { - setTempPoints([newPoint]); - setIsCreating(true); - } else { - const aisle: Aisle = { - aisleUuid: THREE.MathUtils.generateUUID(), - points: [tempPoints[0], newPoint], - type: { - aisleType: 'junction-aisle', - aisleColor: aisleColor, - aisleWidth: aisleWidth, - isFlipped: isFlipped - } - }; - - addAisle(aisle); - - push2D({ - type: 'Draw', - actions: [ - { - actionType: 'Line-Create', - point: { - type: 'Aisle', - lineData: aisle, - timeStamp: new Date().toISOString(), - } - } - ], - }) - - if (projectId) { - - // API - - // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') - - // SOCKET - - socket.emit('v1:model-aisle:add', { - projectId: projectId, - versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization, - aisleUuid: aisle.aisleUuid, - points: aisle.points, - type: aisle.type - }) - } - setTempPoints([newPoint]); - } - } - }; - - const onContext = (event: any) => { - event.preventDefault(); - if (isCreating) { - setTempPoints([]); - setIsCreating(false); - } - }; - - if (toolMode === "Aisle" && toggleView) { - if (tempPoints.length === 0) { - setSnappedPosition(null); - setSnappedPoint(null); - } - canvasElement.addEventListener("mousedown", onMouseDown); - canvasElement.addEventListener("mouseup", onMouseUp); - canvasElement.addEventListener("mousemove", onMouseMove); - canvasElement.addEventListener("click", onMouseClick); - canvasElement.addEventListener("contextmenu", onContext); - } else { - if (tempPoints.length > 0 || isCreating) { - setTempPoints([]); - setIsCreating(false); - } - canvasElement.removeEventListener("mousedown", onMouseDown); - canvasElement.removeEventListener("mouseup", onMouseUp); - canvasElement.removeEventListener("mousemove", onMouseMove); - canvasElement.removeEventListener("click", onMouseClick); - canvasElement.removeEventListener("contextmenu", onContext); + const { scene, camera, raycaster, gl, pointer } = useThree(); + const plane = useMemo( + () => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), + [] + ); + const { toggleView } = useToggleView(); + const { toolMode } = useToolMode(); + const { activeLayer } = useActiveLayer(); + const { socket } = useSocketStore(); + const { aisleStore, undoRedo2DStore } = useSceneContext(); + const { aisles, addAisle, getAislePointById } = aisleStore(); + console.log("aisles: ", aisles); + const { push2D } = undoRedo2DStore(); + const drag = useRef(false); + const isLeftMouseDown = useRef(false); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); + + const [tempPoints, setTempPoints] = useState([]); + const [isCreating, setIsCreating] = useState(false); + const { + aisleType, + aisleWidth, + aisleColor, + dashLength, + gapLength, + dotRadius, + aisleLength, + isFlipped, + snappedPosition, + snappedPoint, + setSnappedPosition, + setSnappedPoint, + } = useBuilderStore(); + + useEffect(() => { + const canvasElement = gl.domElement; + + const onMouseDown = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = true; + drag.current = false; + } + }; + + const onMouseUp = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = false; + } + }; + + const onMouseMove = () => { + if (isLeftMouseDown) { + drag.current = true; + } + }; + + const onMouseClick = () => { + if (drag.current || !toggleView) return; + + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + let position = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (!position) return; + + const intersects = raycaster + .intersectObjects(scene.children) + .find((intersect) => intersect.object.name === "Aisle-Point"); + + const newPoint: Point = { + pointUuid: THREE.MathUtils.generateUUID(), + pointType: "Aisle", + position: [position.x, position.y, position.z], + layer: activeLayer, + }; + + if (snappedPosition && snappedPoint) { + newPoint.pointUuid = snappedPoint.pointUuid; + newPoint.position = snappedPosition; + newPoint.layer = snappedPoint.layer; + } + + if (snappedPoint && snappedPoint.pointUuid === tempPoints[0]?.pointUuid) { + return; + } + + if (snappedPosition && !snappedPoint) { + newPoint.position = snappedPosition; + } + + if (intersects && !snappedPoint) { + const point = getAislePointById(intersects.object.uuid); + if (point) { + newPoint.pointUuid = point.pointUuid; + newPoint.position = point.position; + newPoint.layer = point.layer; } + } - return () => { - canvasElement.removeEventListener("mousedown", onMouseDown); - canvasElement.removeEventListener("mouseup", onMouseUp); - canvasElement.removeEventListener("mousemove", onMouseMove); - canvasElement.removeEventListener("click", onMouseClick); - canvasElement.removeEventListener("contextmenu", onContext); - }; - }, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addAisle, getAislePointById, aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, snappedPosition, snappedPoint, selectedVersion?.versionId]); + if (aisleType === "solid-aisle") { + if (tempPoints.length === 0) { + setTempPoints([newPoint]); + setIsCreating(true); + } else { + const aisle: Aisle = { + aisleUuid: THREE.MathUtils.generateUUID(), + points: [tempPoints[0], newPoint], + type: { + aisleType: "solid-aisle", + aisleColor: aisleColor, + aisleWidth: aisleWidth, + }, + }; - return ( + addAisle(aisle); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Line-Create", + point: { + type: "Aisle", + lineData: aisle, + timeStamp: new Date().toISOString(), + }, + }, + ], + }); + + if (projectId) { + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit("v1:model-aisle:add", { + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type, + }); + } + setTempPoints([newPoint]); + } + } else if (aisleType === "dashed-aisle") { + if (tempPoints.length === 0) { + setTempPoints([newPoint]); + setIsCreating(true); + } else { + const aisle: Aisle = { + aisleUuid: THREE.MathUtils.generateUUID(), + points: [tempPoints[0], newPoint], + type: { + aisleType: "dashed-aisle", + aisleColor: aisleColor, + aisleWidth: aisleWidth, + dashLength: dashLength, + gapLength: gapLength, + }, + }; + + addAisle(aisle); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Line-Create", + point: { + type: "Aisle", + lineData: aisle, + timeStamp: new Date().toISOString(), + }, + }, + ], + }); + + if (projectId) { + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit("v1:model-aisle:add", { + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type, + }); + } + setTempPoints([newPoint]); + } + } else if (aisleType === "dotted-aisle") { + if (tempPoints.length === 0) { + setTempPoints([newPoint]); + setIsCreating(true); + } else { + const aisle: Aisle = { + aisleUuid: THREE.MathUtils.generateUUID(), + points: [tempPoints[0], newPoint], + type: { + aisleType: "dotted-aisle", + aisleColor: aisleColor, + dotRadius: dotRadius, + gapLength: gapLength, + }, + }; + + addAisle(aisle); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Line-Create", + point: { + type: "Aisle", + lineData: aisle, + timeStamp: new Date().toISOString(), + }, + }, + ], + }); + + if (projectId) { + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit("v1:model-aisle:add", { + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type, + }); + } + setTempPoints([newPoint]); + } + } else if (aisleType === "arrow-aisle") { + if (tempPoints.length === 0) { + setTempPoints([newPoint]); + setIsCreating(true); + } else { + const aisle: Aisle = { + aisleUuid: THREE.MathUtils.generateUUID(), + points: [tempPoints[0], newPoint], + type: { + aisleType: "arrow-aisle", + aisleColor: aisleColor, + aisleWidth: aisleWidth, + }, + }; + + addAisle(aisle); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Line-Create", + point: { + type: "Aisle", + lineData: aisle, + timeStamp: new Date().toISOString(), + }, + }, + ], + }); + + if (projectId) { + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit("v1:model-aisle:add", { + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type, + }); + } + setTempPoints([newPoint]); + } + } else if (aisleType === "arrows-aisle") { + if (tempPoints.length === 0) { + setTempPoints([newPoint]); + setIsCreating(true); + } else { + const aisle: Aisle = { + aisleUuid: THREE.MathUtils.generateUUID(), + points: [tempPoints[0], newPoint], + type: { + aisleType: "arrows-aisle", + aisleColor: aisleColor, + aisleWidth: aisleWidth, + aisleLength: aisleLength, + gapLength: gapLength, + }, + }; + + addAisle(aisle); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Line-Create", + point: { + type: "Aisle", + lineData: aisle, + timeStamp: new Date().toISOString(), + }, + }, + ], + }); + + if (projectId) { + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit("v1:model-aisle:add", { + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type, + }); + } + setTempPoints([newPoint]); + } + } else if (aisleType === "arc-aisle") { + if (tempPoints.length === 0) { + setTempPoints([newPoint]); + setIsCreating(true); + } else { + const aisle: Aisle = { + aisleUuid: THREE.MathUtils.generateUUID(), + points: [tempPoints[0], newPoint], + type: { + aisleType: "arc-aisle", + aisleColor: aisleColor, + aisleWidth: aisleWidth, + isFlipped: isFlipped, + }, + }; + + addAisle(aisle); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Line-Create", + point: { + type: "Aisle", + lineData: aisle, + timeStamp: new Date().toISOString(), + }, + }, + ], + }); + + if (projectId) { + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit("v1:model-aisle:add", { + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type, + }); + } + setTempPoints([newPoint]); + } + } else if (aisleType === "circle-aisle") { + if (tempPoints.length === 0) { + setTempPoints([newPoint]); + setIsCreating(true); + } else { + const aisle: Aisle = { + aisleUuid: THREE.MathUtils.generateUUID(), + points: [tempPoints[0], newPoint], + type: { + aisleType: "circle-aisle", + aisleColor: aisleColor, + aisleWidth: aisleWidth, + }, + }; + + addAisle(aisle); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Line-Create", + point: { + type: "Aisle", + lineData: aisle, + timeStamp: new Date().toISOString(), + }, + }, + ], + }); + + if (projectId) { + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit("v1:model-aisle:add", { + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type, + }); + } + setTempPoints([newPoint]); + } + } else if (aisleType === "junction-aisle") { + if (tempPoints.length === 0) { + setTempPoints([newPoint]); + setIsCreating(true); + } else { + const aisle: Aisle = { + aisleUuid: THREE.MathUtils.generateUUID(), + points: [tempPoints[0], newPoint], + type: { + aisleType: "junction-aisle", + aisleColor: aisleColor, + aisleWidth: aisleWidth, + isFlipped: isFlipped, + }, + }; + + addAisle(aisle); + + push2D({ + type: "Draw", + actions: [ + { + actionType: "Line-Create", + point: { + type: "Aisle", + lineData: aisle, + timeStamp: new Date().toISOString(), + }, + }, + ], + }); + + if (projectId) { + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit("v1:model-aisle:add", { + projectId: projectId, + versionId: selectedVersion?.versionId || "", + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type, + }); + } + setTempPoints([newPoint]); + } + } + }; + + const onContext = (event: any) => { + event.preventDefault(); + if (isCreating) { + setTempPoints([]); + setIsCreating(false); + } + }; + + if (toolMode === "Aisle" && toggleView) { + if (tempPoints.length === 0) { + setSnappedPosition(null); + setSnappedPoint(null); + } + canvasElement.addEventListener("mousedown", onMouseDown); + canvasElement.addEventListener("mouseup", onMouseUp); + canvasElement.addEventListener("mousemove", onMouseMove); + canvasElement.addEventListener("click", onMouseClick); + canvasElement.addEventListener("contextmenu", onContext); + } else { + if (tempPoints.length > 0 || isCreating) { + setTempPoints([]); + setIsCreating(false); + } + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener("click", onMouseClick); + canvasElement.removeEventListener("contextmenu", onContext); + } + + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener("click", onMouseClick); + canvasElement.removeEventListener("contextmenu", onContext); + }; + }, [ + gl, + camera, + scene, + raycaster, + pointer, + plane, + toggleView, + toolMode, + activeLayer, + socket, + tempPoints, + isCreating, + addAisle, + getAislePointById, + aisleType, + aisleWidth, + aisleColor, + dashLength, + gapLength, + dotRadius, + aisleLength, + snappedPosition, + snappedPoint, + selectedVersion?.versionId, + ]); + + return ( + <> + {toggleView && ( <> - {toggleView && - <> - - {tempPoints.map((point) => ( - - ))} - + + {tempPoints.map((point) => ( + + ))} + - {tempPoints.length > 0 && - - } - - } + {tempPoints.length > 0 && } - ); + )} + + ); } -export default AisleCreator; \ No newline at end of file +export default AisleCreator; diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index 2edfc58..482908f 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -156,7 +156,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai } else { object.quaternion.rotateTowards(targetQuaternion, step); } - } + } return; } } diff --git a/app/src/modules/simulation/vehicle/preDefinedPath/PointHandle.tsx b/app/src/modules/simulation/vehicle/preDefinedPath/PointHandle.tsx new file mode 100644 index 0000000..5d22ff9 --- /dev/null +++ b/app/src/modules/simulation/vehicle/preDefinedPath/PointHandle.tsx @@ -0,0 +1,586 @@ +// import React, { useRef, useState } from "react"; +// import { useThree, useFrame } from "@react-three/fiber"; +// import * as THREE from "three"; +// import { Line } from "@react-three/drei"; + +// interface PointProps { +// point: any; +// pointIndex: number; +// groupIndex: number; +// selected: number[]; +// setPointsGroups: React.Dispatch>; +// } + +// export default function EditablePoint({ +// point, +// pointIndex, +// groupIndex, +// selected, +// setPointsGroups, +// }: PointProps) { +// const meshRef = useRef(null); +// const handleARef = useRef(null); +// const handleBRef = useRef(null); +// const lineRef = useRef(null!); + +// const { camera, gl, controls } = useThree(); +// const [dragging, setDragging] = useState< +// null | "main" | "handleA" | "handleB" +// >(null); +// const dragOffset = useRef(new THREE.Vector3()); + +// /** Handle clicking the point */ +// const onPointClick = (e: any) => { +// e.stopPropagation(); + +// if (e.ctrlKey) { +// // Toggle curve handles +// setPointsGroups((prev) => { +// const newGroups = [...prev]; +// const group = [...newGroups[groupIndex]]; +// const idx = group.findIndex((p) => p.pointId === point.pointId); +// const updated = { ...group[idx] }; + +// if (!updated.handleA && !updated.handleB) { +// updated.handleA = [ +// updated.position[0] + 1, +// updated.position[1], +// updated.position[2], +// ]; +// updated.handleB = [ +// updated.position[0] - 1, +// updated.position[1], +// updated.position[2], +// ]; +// updated.isCurved = true; +// } else { +// updated.handleA = null; +// updated.handleB = null; +// updated.isCurved = false; +// } + +// group[idx] = updated; +// newGroups[groupIndex] = group; +// return newGroups; +// }); +// } +// }; + +// /** Pointer down for dragging */ +// const startDrag = (target: "main" | "handleA" | "handleB", e: any) => { +// e.stopPropagation(); +// setDragging(target); +// const targetRef = +// target === "main" +// ? meshRef.current +// : target === "handleA" +// ? handleARef.current +// : handleBRef.current; +// if (targetRef) { +// dragOffset.current.copy(targetRef.position).sub(e.point); +// } +// if (controls) (controls as any).enabled = false; +// gl.domElement.style.cursor = "grabbing"; +// }; + +// /** Pointer up stops dragging */ +// const stopDrag = () => { +// setDragging(null); +// gl.domElement.style.cursor = "auto"; +// if (controls) (controls as any).enabled = true; +// }; + +// /** Handle dragging logic */ +// useFrame(({ raycaster, mouse }) => { +// if (!dragging) return; + +// const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); +// raycaster.setFromCamera(mouse, camera); +// const intersection = new THREE.Vector3(); + +// if (raycaster.ray.intersectPlane(plane, intersection)) { +// const newPos = intersection.add(dragOffset.current); + +// setPointsGroups((prev) => { +// const newGroups = [...prev]; +// const group = [...newGroups[groupIndex]]; +// const idx = group.findIndex((p) => p.pointId === point.pointId); +// const updated = { ...group[idx] }; + +// if (dragging === "main") { +// const delta = new THREE.Vector3() +// .fromArray(newPos.toArray()) +// .sub(new THREE.Vector3().fromArray(updated.position)); +// updated.position = newPos.toArray() as [number, number, number]; + +// // Move handles along with the main point +// if (updated.handleA) { +// updated.handleA = new THREE.Vector3() +// .fromArray(updated.handleA) +// .add(delta) +// .toArray() as [number, number, number]; +// } +// if (updated.handleB) { +// updated.handleB = new THREE.Vector3() +// .fromArray(updated.handleB) +// .add(delta) +// .toArray() as [number, number, number]; +// } +// } else { +// updated[dragging] = newPos.toArray() as [number, number, number]; + +// // Mirror opposite handle +// if (updated.isCurved) { +// const mainPos = new THREE.Vector3().fromArray(updated.position); +// const thisHandle = new THREE.Vector3().fromArray( +// updated[dragging]! +// ); +// const mirrorHandle = mainPos +// .clone() +// .sub(thisHandle.clone().sub(mainPos)); + +// if (dragging === "handleA") +// updated.handleB = mirrorHandle.toArray() as [ +// number, +// number, +// number +// ]; +// if (dragging === "handleB") +// updated.handleA = mirrorHandle.toArray() as [ +// number, +// number, +// number +// ]; +// } +// } + +// group[idx] = updated; +// newGroups[groupIndex] = group; +// return newGroups; +// }); +// } +// }); + +// /** Update line between handles each frame */ +// useFrame(() => { +// if (lineRef.current && point.handleA && point.handleB) { +// const positions = lineRef.current.geometry.attributes.position +// .array as Float32Array; +// positions[0] = point.handleA[0]; +// positions[1] = point.handleA[1]; +// positions[2] = point.handleA[2]; +// positions[3] = point.handleB[0]; +// positions[4] = point.handleB[1]; +// positions[5] = point.handleB[2]; +// lineRef.current.geometry.attributes.position.needsUpdate = true; +// } +// }); + +// return ( +// <> +// {/* Main point */} +// startDrag("main", e)} +// onPointerUp={stopDrag} +// > +// +// +// + +// {/* Handles + line */} +// {point.isCurved && point.handleA && point.handleB && ( +// <> +// {/* Line between handles */} +// {point.handleA && point.handleB && ( +// +// )} + +// {/* Handle A */} +// startDrag("handleA", e)} +// onPointerUp={stopDrag} +// > +// +// +// + +// {/* Handle B */} +// startDrag("handleB", e)} +// onPointerUp={stopDrag} +// > +// +// +// +// +// )} +// +// ); +// } +import React, { useRef, useState, useEffect } from "react"; +import { useThree, useFrame } from "@react-three/fiber"; +import * as THREE from "three"; +import { Line } from "@react-three/drei"; + +interface PointProps { + point: any; + pointIndex: number; + groupIndex: number; + selected: number[]; + setSelected: React.Dispatch>; + pointsGroups: any[][]; + setPointsGroups: React.Dispatch>; + shortestPath: number[]; // <- add this + setShortestPath: React.Dispatch>; // <- add this + setShortestDistance?: React.Dispatch>; // optional +} + +export default function PointHandle({ + point, + pointIndex, + groupIndex, + selected, + setSelected, + pointsGroups, + setPointsGroups, + setShortestDistance, + shortestPath, + setShortestPath, +}: PointProps) { + + const meshRef = useRef(null); + const handleARef = useRef(null); + const handleBRef = useRef(null); + const lineRef = useRef(null!); + // const pathLineRef = useRef(null!); + + const { camera, gl, controls } = useThree(); + const [dragging, setDragging] = useState< + null | "main" | "handleA" | "handleB" + >(null); + const dragOffset = useRef(new THREE.Vector3()); + // const [shortestPath, setShortestPath] = useState([]); + + /** Shift-click or ctrl-click handling */ + const onPointClick = (e: any) => { + e.stopPropagation(); + + if (e.ctrlKey) { + // Toggle handles + setPointsGroups((prev) => { + const newGroups = [...prev]; + const group = [...newGroups[groupIndex]]; + const idx = group.findIndex((p) => p.pointId === point.pointId); + const updated = { ...group[idx] }; + + if (!updated.handleA && !updated.handleB) { + updated.handleA = [ + updated.position[0] + 1, + updated.position[1], + updated.position[2], + ]; + updated.handleB = [ + updated.position[0] - 1, + updated.position[1], + updated.position[2], + ]; + updated.isCurved = true; + } else { + updated.handleA = null; + updated.handleB = null; + updated.isCurved = false; + } + + group[idx] = updated; + newGroups[groupIndex] = group; + return newGroups; + }); + } else if (e.shiftKey) { + // Shift-click for multi-select + setSelected((prev) => { + if (prev.includes(pointIndex)) return prev; // keep selection + const newSelection = [...prev, pointIndex]; + return newSelection.slice(-2); // keep only 2 points + }); + } else { + // Single selection + setSelected([pointIndex]); + } + }; + + /** Dragging logic */ + const startDrag = (target: "main" | "handleA" | "handleB", e: any) => { + e.stopPropagation(); + setDragging(target); + const targetRef = + target === "main" + ? meshRef.current + : target === "handleA" + ? handleARef.current + : handleBRef.current; + if (targetRef) dragOffset.current.copy(targetRef.position).sub(e.point); + if (controls) (controls as any).enabled = false; + gl.domElement.style.cursor = "grabbing"; + }; + + const stopDrag = () => { + setDragging(null); + gl.domElement.style.cursor = "auto"; + if (controls) (controls as any).enabled = true; + }; + + useFrame(({ raycaster, mouse }) => { + if (!dragging) return; + + const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); + raycaster.setFromCamera(mouse, camera); + const intersection = new THREE.Vector3(); + + if (raycaster.ray.intersectPlane(plane, intersection)) { + const newPos = intersection.add(dragOffset.current); + + setPointsGroups((prev) => { + const newGroups = [...prev]; + const group = [...newGroups[groupIndex]]; + const idx = group.findIndex((p) => p.pointId === point.pointId); + const updated = { ...group[idx] }; + + if (dragging === "main") { + const delta = new THREE.Vector3() + .fromArray(newPos.toArray()) + .sub(new THREE.Vector3().fromArray(updated.position)); + updated.position = newPos.toArray() as [number, number, number]; + + if (updated.handleA) { + updated.handleA = new THREE.Vector3() + .fromArray(updated.handleA) + .add(delta) + .toArray() as [number, number, number]; + } + if (updated.handleB) { + updated.handleB = new THREE.Vector3() + .fromArray(updated.handleB) + .add(delta) + .toArray() as [number, number, number]; + } + } else { + updated[dragging] = newPos.toArray() as [number, number, number]; + if (updated.isCurved) { + const mainPos = new THREE.Vector3().fromArray(updated.position); + const thisHandle = new THREE.Vector3().fromArray( + updated[dragging]! + ); + const mirrorHandle = mainPos + .clone() + .sub(thisHandle.clone().sub(mainPos)); + + if (dragging === "handleA") + updated.handleB = mirrorHandle.toArray() as [ + number, + number, + number + ]; + if (dragging === "handleB") + updated.handleA = mirrorHandle.toArray() as [ + number, + number, + number + ]; + } + } + + group[idx] = updated; + newGroups[groupIndex] = group; + return newGroups; + }); + } + }); + + /** Update handle lines */ + useFrame(() => { + if (lineRef.current && point.handleA && point.handleB) { + const positions = lineRef.current.geometry.attributes.position + .array as Float32Array; + positions[0] = point.handleA[0]; + positions[1] = point.handleA[1]; + positions[2] = point.handleA[2]; + positions[3] = point.handleB[0]; + positions[4] = point.handleB[1]; + positions[5] = point.handleB[2]; + lineRef.current.geometry.attributes.position.needsUpdate = true; + } + }); + + useEffect(() => { + if (selected.length === 2) { + const groupPoints = pointsGroups[groupIndex]; + if (!groupPoints) return; + + const pathPoints = selected + .map((i) => groupPoints[i]) + .filter((p) => p !== undefined) + .map((p) => p.position); + + setShortestPath(pathPoints); + + // compute distance + let totalDistance = 0; + for (let i = 0; i < pathPoints.length - 1; i++) { + const p1 = new THREE.Vector3().fromArray(pathPoints[i]); + const p2 = new THREE.Vector3().fromArray(pathPoints[i + 1]); + totalDistance += p1.distanceTo(p2); + } + setShortestDistance?.(totalDistance); + } else { + setShortestPath([]); + setShortestDistance?.(0); + } + }, [selected, pointsGroups]); + + return ( + <> + {/* Main point */} + startDrag("main", e)} + onPointerUp={stopDrag} + > + + + + + {/* Handles + line */} + {point.isCurved && point.handleA && point.handleB && ( + <> + + startDrag("handleA", e)} + onPointerUp={stopDrag} + > + + + + startDrag("handleB", e)} + onPointerUp={stopDrag} + > + + + + + )} + + {/* Highlight shortest path */} + {shortestPath.length > 1 && ( + + )} + + ); +} + +/** Build adjacency list for shortest path */ +// const buildGraph = (points: any[]) => { +// const graph: Record = {}; +// points.forEach((p, idx) => { +// graph[idx] = []; +// points.forEach((q, j) => { +// if (idx !== j) { +// const d = new THREE.Vector3() +// .fromArray(p.position) +// .distanceTo(new THREE.Vector3().fromArray(q.position)); +// graph[idx].push({ neighbor: j, distance: d }); +// } +// }); +// }); +// return graph; +// }; + +// /** Dijkstra shortest path */ +// const findShortestPath = (graph: any, startIdx: number, endIdx: number) => { +// const distances: number[] = Array(Object.keys(graph).length).fill(Infinity); +// const previous: (number | null)[] = Array(distances.length).fill(null); +// distances[startIdx] = 0; +// const queue = new Set(Object.keys(graph).map(Number)); + +// while (queue.size) { +// let current = [...queue].reduce((a, b) => +// distances[a] < distances[b] ? a : b +// ); +// if (current === endIdx) break; +// queue.delete(current); + +// for (const { neighbor, distance } of graph[current]) { +// const alt = distances[current] + distance; +// if (alt < distances[neighbor]) { +// distances[neighbor] = alt; +// previous[neighbor] = current; +// } +// } +// } + +// const path: number[] = []; +// let u: number | null = endIdx; +// while (u !== null) { +// path.unshift(u); +// u = previous[u]; +// } +// +// return path; +// }; + +// /** Calculate shortest path when 2 points are selected */ +// useEffect(() => { +// if (selected.length === 2) { +// const groupPoints = pointsGroups[groupIndex]; +// const graph = buildGraph(groupPoints); +// const path = findShortestPath(graph, selected[0], selected[1]); +// setShortestPath(path); + +// // Calculate distance +// if (setShortestDistance) { +// let totalDistance = 0; +// for (let i = 0; i < path.length - 1; i++) { +// const p1 = new THREE.Vector3().fromArray( +// groupPoints[path[i]].position +// ); +// const p2 = new THREE.Vector3().fromArray( +// groupPoints[path[i + 1]].position +// ); +// totalDistance += p1.distanceTo(p2); +// } +// setShortestDistance?.(totalDistance); +// } +// } else { +// setShortestPath([]); +// if (setShortestDistance) setShortestDistance(0); +// } +// }, [selected, pointsGroups]); diff --git a/app/src/modules/simulation/vehicle/preDefinedPath/functions/findShortestPath.ts b/app/src/modules/simulation/vehicle/preDefinedPath/functions/findShortestPath.ts new file mode 100644 index 0000000..84df838 --- /dev/null +++ b/app/src/modules/simulation/vehicle/preDefinedPath/functions/findShortestPath.ts @@ -0,0 +1,22 @@ +export function findShortestPath( + startIndex: number, + endIndex: number, + adjacency: number[][] +) { + const queue = [[startIndex]]; + const visited = new Set(); + + while (queue.length > 0) { + const path = queue.shift()!; + const node = path[path.length - 1]; + if (node === endIndex) return path; + + if (!visited.has(node)) { + visited.add(node); + for (const neighbor of adjacency[node]) { + queue.push([...path, neighbor]); + } + } + } + return []; +} diff --git a/app/src/modules/simulation/vehicle/preDefinedPath/lineSegment.tsx b/app/src/modules/simulation/vehicle/preDefinedPath/lineSegment.tsx new file mode 100644 index 0000000..d57e37e --- /dev/null +++ b/app/src/modules/simulation/vehicle/preDefinedPath/lineSegment.tsx @@ -0,0 +1,87 @@ +import { Line } from "@react-three/drei"; +import { useThree } from "@react-three/fiber"; +import { useEffect, useMemo, useRef } from "react"; +import { CubicBezierCurve3, LineCurve3, Plane, Vector3 } from "three"; + +export default function LineSegment({ + index, + createdPoints, + updatePoints, + insertPoint, +}: { + index: number; + createdPoints: any[]; // Array of points with position, isCurved, handleA, handleB + updatePoints: (i0: number, p0: Vector3, i1: number, p1: Vector3) => void; + insertPoint?: (index: number, point: Vector3) => void; +}) { + const { gl, raycaster, camera, controls } = useThree(); + const plane = new Plane(new Vector3(0, 1, 0), 0); + const dragStart = useRef(null); + + // ======== Curve or Line Points ======== + const curvePoints = useMemo(() => { + if (!createdPoints || index + 1 >= createdPoints.length) return []; + + const current = createdPoints[index]; + const next = createdPoints[index + 1]; + + const starts = new Vector3(...current.position); + const ends = new Vector3(...next.position); + + const useCurve = + (current.isCurved && current.handleB) || (next.isCurved && next.handleA); + + const hB = current.handleB ? new Vector3(...current.handleB) : starts; + const hA = next.handleA ? new Vector3(...next.handleA) : ends; + + const curve = useCurve + ? new CubicBezierCurve3(starts, hB, hA, ends) + : new LineCurve3(starts, ends); + + return curve.getPoints(useCurve ? 100 : 2); + }, [createdPoints, index]); + + // ======== Events ======== + const onPointerUp = () => { + dragStart.current = null; + gl.domElement.style.cursor = "default"; + if (controls) (controls as any).enabled = true; + }; + + const onClickLine = () => { + const intersection = new Vector3(); + if (raycaster.ray.intersectPlane(plane, intersection)) { + const start = new Vector3(...createdPoints[index].position); + const end = new Vector3(...createdPoints[index + 1].position); + const segLen = start.distanceTo(end); + const distToStart = start.distanceTo(intersection); + const distToEnd = end.distanceTo(intersection); + + if ( + distToStart > 0.01 && + distToEnd > 0.01 && + distToStart + distToEnd <= segLen + 0.01 + ) { + insertPoint?.(index + 1, intersection); + } + } + }; + + useEffect(() => { + gl.domElement.addEventListener("pointerup", onPointerUp); + return () => { + gl.domElement.removeEventListener("pointerup", onPointerUp); + }; + }, []); + + // ======== Render ======== + return ( + + ); +} diff --git a/app/src/modules/simulation/vehicle/preDefinedPath/preDefinedPath.tsx b/app/src/modules/simulation/vehicle/preDefinedPath/preDefinedPath.tsx new file mode 100644 index 0000000..bee15f7 --- /dev/null +++ b/app/src/modules/simulation/vehicle/preDefinedPath/preDefinedPath.tsx @@ -0,0 +1,717 @@ +import { Line, Plane } from "@react-three/drei"; +import { ThreeEvent, useFrame, useThree } from "@react-three/fiber"; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { Matrix4, Mesh, Quaternion, Vector3 } from "three"; +import { + useAnimationPlaySpeed, + usePlayButtonStore, +} from "../../../../store/usePlayButtonStore"; +import { useSceneContext } from "../../../scene/sceneContext"; +import { findShortestPath } from "./functions/findShortestPath"; +import * as THREE from "three"; +import PointHandle from "./PointHandle"; +import LineSegment from "./lineSegment"; + +// export default function PreDefinedPath() { +// const { gl, raycaster } = useThree(); +// const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)); +// const [points, setPoints] = useState<[number, number, number][]>([]); +// const [selected, setSelected] = useState([]); +// const downPosition = useRef<{ x: number; y: number } | null>(null); +// const hasClicked = useRef(false); + +// const handleMouseDown = useCallback((e: MouseEvent) => { +// hasClicked.current = false; +// downPosition.current = { x: e.clientX, y: e.clientY }; +// }, []); + +// const handleClick = useCallback( +// (e: MouseEvent) => { +// if (hasClicked.current) return; +// hasClicked.current = true; + +// if ( +// !downPosition.current || +// Math.abs(downPosition.current.x - e.clientX) > 2 || +// Math.abs(downPosition.current.y - e.clientY) > 2 +// ) { +// return; +// } + +// const intersection = new THREE.Vector3(); +// if (raycaster.ray.intersectPlane(plane.current, intersection)) { +// const pointArray = intersection.toArray() as [number, number, number]; +// const alreadyExists = points.some( +// (p) => +// Math.abs(p[0] - pointArray[0]) < 0.01 && +// Math.abs(p[1] - pointArray[1]) < 0.01 && +// Math.abs(p[2] - pointArray[2]) < 0.01 +// ); + +// if (!alreadyExists) { +// setPoints((prev) => [...prev, pointArray]); +// } +// } +// }, +// [raycaster, points] +// ); + +// useEffect(() => { +// const domElement = gl.domElement; +// domElement.addEventListener("mousedown", handleMouseDown); +// domElement.addEventListener("mouseup", handleClick); +// return () => { +// domElement.removeEventListener("mousedown", handleMouseDown); +// domElement.removeEventListener("mouseup", handleClick); +// }; +// }, [handleClick, handleMouseDown]); + +// // Update two existing points +// const updatePoints = ( +// i0: number, +// p0: THREE.Vector3, +// i1: number, +// p1: THREE.Vector3 +// ) => { +// const updated = [...points]; +// updated[i0] = p0.toArray() as [number, number, number]; +// updated[i1] = p1.toArray() as [number, number, number]; +// setPoints(updated); +// }; + +// const insertPoint = (index: number, point: THREE.Vector3) => { +// const updated = [...points]; +// updated.splice(index, 0, point.toArray() as [number, number, number]); +// setPoints(updated); +// }; + +// return ( +// <> +// {points.map((pos, idx) => ( +// { +// setSelected((prev) => (prev.length === 2 ? [idx] : [...prev, idx])); +// }} +// > +// +// +// +// ))} + +// {points.map((pos, i) => { +// if (i < points.length - 1) { +// return ( +// +// ); +// } +// return null; +// })} +// +// ); +// } + +///crcted +// export default function PreDefinedPath() { +// const { gl, raycaster } = useThree(); +// const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)); + +// const [pointsGroups, setPointsGroups] = useState< +// [number, number, number][][] +// >([[]]); +// +// const [selected, setSelected] = useState([]); +// const downPosition = useRef<{ x: number; y: number } | null>(null); +// const hasClicked = useRef(false); + +// const handleMouseDown = useCallback((e: MouseEvent) => { +// hasClicked.current = false; +// downPosition.current = { x: e.clientX, y: e.clientY }; +// }, []); + +// const handleClick = useCallback( +// (e: MouseEvent) => { +// // Right click → start new group +// if (e.button === 2) { +// setPointsGroups((prev) => [...prev, []]); +// setSelected([]); +// return; +// } + +// // Left click only +// if (e.button !== 0) return; +// if (hasClicked.current) return; +// hasClicked.current = true; + +// if ( +// !downPosition.current || +// Math.abs(downPosition.current.x - e.clientX) > 2 || +// Math.abs(downPosition.current.y - e.clientY) > 2 +// ) { +// return; +// } + +// const intersection = new THREE.Vector3(); +// if (raycaster.ray.intersectPlane(plane.current, intersection)) { +// const pointArray = intersection.toArray() as [number, number, number]; +// setPointsGroups((prev) => { +// const newGroups = [...prev]; +// const currentGroup = [...newGroups[newGroups.length - 1]]; + +// const alreadyExists = currentGroup.some( +// (p) => +// Math.abs(p[0] - pointArray[0]) < 0.01 && +// Math.abs(p[1] - pointArray[1]) < 0.01 && +// Math.abs(p[2] - pointArray[2]) < 0.01 +// ); +// if (alreadyExists) return prev; + +// if (selected.length === 2) { +// const [startIdx, endIdx] = selected; +// const insertIndex = startIdx < endIdx ? startIdx + 1 : endIdx + 1; + +// currentGroup.splice(insertIndex, 0, pointArray); +// newGroups[newGroups.length - 1] = currentGroup; +// return newGroups; +// } else { +// currentGroup.push(pointArray); +// newGroups[newGroups.length - 1] = currentGroup; +// return newGroups; +// } +// }); +// } +// }, +// [raycaster, selected] +// ); + +// useEffect(() => { +// const domElement = gl.domElement; +// domElement.addEventListener("contextmenu", (e) => e.preventDefault()); // disable browser menu +// domElement.addEventListener("mousedown", handleMouseDown); +// domElement.addEventListener("mouseup", handleClick); +// return () => { +// domElement.removeEventListener("mousedown", handleMouseDown); +// domElement.removeEventListener("mouseup", handleClick); +// }; +// }, [handleClick, handleMouseDown]); + +// const updatePoints = ( +// groupIndex: number, +// i0: number, +// p0: THREE.Vector3, +// i1: number, +// p1: THREE.Vector3 +// ) => { +// setPointsGroups((prev) => { +// const newGroups = [...prev]; +// const group = [...newGroups[groupIndex]]; +// group[i0] = p0.toArray() as [number, number, number]; +// group[i1] = p1.toArray() as [number, number, number]; +// newGroups[groupIndex] = group; +// return newGroups; +// }); +// }; + +// const insertPoint = ( +// groupIndex: number, +// index: number, +// point: THREE.Vector3 +// ) => { +// setPointsGroups((prev) => { +// const newGroups = [...prev]; +// const group = [...newGroups[groupIndex]]; +// group.splice(index, 0, point.toArray() as [number, number, number]); +// newGroups[groupIndex] = group; +// return newGroups; +// }); +// }; + +// return ( +// <> +// {pointsGroups.map((group, gIdx) => ( +// +// {group.map((pos, idx) => ( +// { +// e.stopPropagation(); +// setSelected((prev) => +// prev.length === 2 ? [idx] : [...prev, idx] +// ); +// }} +// > +// +// +// +// ))} + +// {group.map((pos, i) => { +// if (i < group.length - 1) { +// return ( +// +// updatePoints(gIdx, i0, p0, i1, p1) +// } +// insertPoint={(index, point) => +// insertPoint(gIdx, index, point) +// } +// /> +// ); +// } +// return null; +// })} +// +// ))} +// +// ); +// } +// const handleClick = useCallback( +// (e: any) => { +// if (e.ctrlKey) return; +// if (e.button === 2) { +// setPointsGroups((prev) => [...prev, []]); +// setSelected([]); +// return; +// } +// if (e.button !== 0) return; +// if (hasClicked.current) return; +// hasClicked.current = true; + +// if ( +// !downPosition.current || +// Math.abs(downPosition.current.x - e.clientX) > 2 || +// Math.abs(downPosition.current.y - e.clientY) > 2 +// ) +// return; + +// const intersection = new THREE.Vector3(); +// if (raycaster.ray.intersectPlane(plane.current, intersection)) { +// const pointArray = intersection.toArray() as [number, number, number]; + +// setPointsGroups((prev) => { +// const newGroups = [...prev]; +// const currentGroup = [...newGroups[newGroups.length - 1]]; + +// const alreadyExists = currentGroup.some( +// (p) => +// Math.abs(p.position[0] - pointArray[0]) < 0.01 && +// Math.abs(p.position[1] - pointArray[1]) < 0.01 && +// Math.abs(p.position[2] - pointArray[2]) < 0.01 +// ); +// if (alreadyExists) return prev; + +// const newPoint: PointData = { +// pointId: crypto.randomUUID(), +// position: pointArray, +// isCurved: false, +// handleA: null, +// handleB: null, +// }; + +// if (selected.length === 2) { +// const [startIdx, endIdx] = selected; +// const insertIndex = startIdx < endIdx ? startIdx + 1 : endIdx + 1; +// currentGroup.splice(insertIndex, 0, newPoint); +// } else { +// currentGroup.push(newPoint); +// } + +// newGroups[newGroups.length - 1] = currentGroup; +// return newGroups; +// }); +// } +// }, +// [raycaster, selected] +// ); + +type PointData = { + pointId: string; + position: [number, number, number]; + isCurved: boolean; + handleA: [number, number, number] | null; + handleB: [number, number, number] | null; +}; +export default function PreDefinedPath() { + const { gl, raycaster } = useThree(); + const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)); + + const [mainShapeOnly, setMainShapeOnly] = useState([]); + console.log("mainShapeOnly: ", mainShapeOnly); + const [pointsGroups, setPointsGroups] = useState([[]]); + console.log("pointsGroups: ", pointsGroups); + const [definedPath, setDefinedPath] = useState( + [] + ); + console.log("definedPath: ", definedPath); + const [selected, setSelected] = useState([]); + + const downPosition = useRef<{ x: number; y: number } | null>(null); + const hasClicked = useRef(false); + + const handleMouseDown = useCallback((e: any) => { + console.log("e.ctrlKey: ", e.ctrlKey); + hasClicked.current = false; + downPosition.current = { x: e.clientX, y: e.clientY }; + }, []); + + const SNAP_DISTANCE = 0.5; + + const handleClick = useCallback( + (e: any) => { + console.log("e.ctrlKey: ", e.ctrlKey); + if (e.ctrlKey) return; + if (e.button === 2) { + setPointsGroups((prev) => [...prev, []]); + setSelected([]); + return; + } + if (e.button !== 0) return; + if (hasClicked.current) return; + hasClicked.current = true; + + if ( + !downPosition.current || + Math.abs(downPosition.current.x - e.clientX) > 2 || + Math.abs(downPosition.current.y - e.clientY) > 2 + ) + return; + + const intersection = new THREE.Vector3(); + if (raycaster.ray.intersectPlane(plane.current, intersection)) { + const pointArray = intersection.toArray() as [number, number, number]; + + setPointsGroups((prev) => { + const newGroups = [...prev]; + const currentGroup = [...newGroups[newGroups.length - 1]]; + + // 1️⃣ Find nearest existing point + let nearestPos: [number, number, number] | null = null; + newGroups.forEach((group) => { + group.forEach((p) => { + const dist = Math.sqrt( + Math.pow(p.position[0] - pointArray[0], 2) + + Math.pow(p.position[1] - pointArray[1], 2) + + Math.pow(p.position[2] - pointArray[2], 2) + ); + if (dist <= SNAP_DISTANCE && !nearestPos) { + nearestPos = p.position; // take only the position + } + }); + }); + + if (nearestPos) { + // 2️⃣ Reuse the position, but create NEW pointId + const snapPoint: PointData = { + pointId: crypto.randomUUID(), + position: nearestPos, + isCurved: false, + handleA: null, + handleB: null, + }; + currentGroup.push(snapPoint); + newGroups[newGroups.length - 1] = currentGroup; + return newGroups; + } + + // 3️⃣ Otherwise, create brand new point + const newPoint: PointData = { + pointId: crypto.randomUUID(), + position: pointArray, + isCurved: false, + handleA: null, + handleB: null, + }; + currentGroup.push(newPoint); + newGroups[newGroups.length - 1] = currentGroup; + return newGroups; + }); + } + }, + [raycaster] + ); + + function findConnectedComponents(groups: PointData[][]) { + const visited = new Set(); + const components: PointData[][] = []; + + const arePointsEqual = (p1: PointData, p2: PointData) => + Math.abs(p1.position[0] - p2.position[0]) < 0.001 && + Math.abs(p1.position[1] - p2.position[1]) < 0.001 && + Math.abs(p1.position[2] - p2.position[2]) < 0.001; + + const dfs = (point: PointData, component: PointData[][]) => { + if (visited.has(point.pointId)) return; + visited.add(point.pointId); + + for (const group of groups) { + if (group.some((gp) => arePointsEqual(gp, point))) { + if (!component.includes(group)) { + component.push(group); + for (const gp of group) dfs(gp, component); + } + } + } + }; + + for (const group of groups) { + for (const point of group) { + if (!visited.has(point.pointId)) { + const newComponent: PointData[][] = []; + dfs(point, newComponent); + if (newComponent.length > 0) components.push(newComponent.flat()); + } + } + } + return components; + } + + useEffect(() => { + const newDefinedPath = pointsGroups.filter((g) => g.length > 0); + setDefinedPath(newDefinedPath); + + const connected = findConnectedComponents(newDefinedPath); + if (connected.length > 0) { + let mainShape = [...connected[0]]; + const isolatedPoints = connected + .slice(1) + .filter((arr) => arr.length === 1); + const updatedMainShapeOnly = [mainShape, ...isolatedPoints]; + setMainShapeOnly(updatedMainShapeOnly); + } else { + setMainShapeOnly([]); + } + }, [pointsGroups]); + + useEffect(() => { + setDefinedPath(() => { + if (pointsGroups.length === 1) { + return [...pointsGroups[0]]; + } else { + return pointsGroups.filter((group) => group.length > 0); + } + }); + }, [pointsGroups]); + const [shortestPath, setShortestPath] = useState([]); + const [shortestDistance, setShortestDistance] = useState(0); + useEffect(() => { + const domElement = gl.domElement; + domElement.addEventListener("contextmenu", (e) => e.preventDefault()); + domElement.addEventListener("mousedown", handleMouseDown); + domElement.addEventListener("mouseup", handleClick); + return () => { + domElement.removeEventListener("mousedown", handleMouseDown); + domElement.removeEventListener("mouseup", handleClick); + }; + }, [handleClick, handleMouseDown]); + + return ( + <> + {pointsGroups.map((group, gIdx) => ( + + {group.map((point, idx) => ( + + ))} + + {group.map((point, i) => { + if (i < group.length - 1) { + return ( + { + setPointsGroups((prev) => { + const newGroups = [...prev]; + const newGroup = [...newGroups[gIdx]]; + newGroup[i0] = { + ...newGroup[i0], + position: p0.toArray() as [number, number, number], + }; + newGroup[i1] = { + ...newGroup[i1], + position: p1.toArray() as [number, number, number], + }; + newGroups[gIdx] = newGroup; + return newGroups; + }); + }} + // insertPoint={(index, pointVec) => { + // setPointsGroups((prev) => { + // const newGroups = [...prev]; + // const groupToSplit = newGroups[gIdx]; + + // // Create the new point + // const newPoint = { + // pointId: crypto.randomUUID(), + // position: pointVec.toArray() as [ + // number, + // number, + // number + // ], + // isCurved: false, + // handleA: null, + // handleB: null, + // }; + + // // First half: everything from start to clicked segment + // const firstHalf = [ + // ...groupToSplit.slice(0, index), + // newPoint, + // ]; + + // // Second half: new point + everything after clicked segment + // const secondHalf = [ + // newPoint, + // ...groupToSplit.slice(index), + // ]; + + // // Replace the original group with the first half + // newGroups[gIdx] = firstHalf; + + // // Insert the second half as a new group right after + // newGroups.splice(gIdx + 1, 0, secondHalf); + + // return newGroups; + // }); + // }} + insertPoint={(index: number, pointVec: THREE.Vector3) => { + setPointsGroups((prev) => { + const newGroups = [...prev]; + const group = [...newGroups[gIdx]]; + + // Create the new point + const newPoint: PointData = { + pointId: crypto.randomUUID(), + position: pointVec.toArray() as [ + number, + number, + number + ], + isCurved: false, + handleA: null, + handleB: null, + }; + + // Find best place to insert based on index (insert between points) + group.splice(index, 0, newPoint); // insert at index + newGroups[gIdx] = group; + return newGroups; + }); + }} + /> + ); + } + return null; + })} + + ))} + + ); +} + +// const handleClick = useCallback( +// (e: MouseEvent) => { +// if (e.ctrlKey) return; +// if (e.button === 2) { +// setPointsGroups((prev) => [...prev, []]); +// setSelected([]); +// return; +// } +// if (e.button !== 0) return; + +// // Check small movement +// if ( +// !downPosition.current || +// Math.abs(downPosition.current.x - e.clientX) > 2 || +// Math.abs(downPosition.current.y - e.clientY) > 2 +// ) { +// return; +// } + +// const intersection = new THREE.Vector3(); +// if (raycaster.ray.intersectPlane(plane.current, intersection)) { +// const pointArray = intersection.toArray() as [number, number, number]; + +// setPointsGroups((prev) => { +// const newGroups = [...prev]; +// const currentGroup = [...newGroups[newGroups.length - 1]]; + +// // Search for existing point in ALL groups +// let existingPoint: PointData | null = null; +// for (const group of prev) { +// for (const p of group) { +// if ( +// Math.abs(p.position[0] - pointArray[0]) < 0.01 && +// Math.abs(p.position[1] - pointArray[1]) < 0.01 && +// Math.abs(p.position[2] - pointArray[2]) < 0.01 +// ) { +// existingPoint = p; +// break; +// } +// } +// if (existingPoint) break; +// } + +// if (existingPoint) { +// // Just connect to existing point without duplicating +// if ( +// currentGroup.length === 0 || +// currentGroup[currentGroup.length - 1].pointId !== +// existingPoint.pointId +// ) { +// currentGroup.push(existingPoint); +// } +// } else { +// // Create new point +// const newPoint: PointData = { +// pointId: crypto.randomUUID(), +// position: pointArray, +// isCurved: false, +// handleA: null, +// handleB: null, +// }; +// currentGroup.push(newPoint); +// } + +// newGroups[newGroups.length - 1] = currentGroup; +// return newGroups; +// }); +// } +// }, +// [raycaster] +// ); diff --git a/app/src/modules/simulation/vehicle/vehicles.tsx b/app/src/modules/simulation/vehicle/vehicles.tsx index 9e594a5..1b9ee4f 100644 --- a/app/src/modules/simulation/vehicle/vehicles.tsx +++ b/app/src/modules/simulation/vehicle/vehicles.tsx @@ -4,36 +4,35 @@ import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; import VehicleInstances from "./instances/vehicleInstances"; import VehicleUI from "../spatialUI/vehicle/vehicleUI"; import { useSceneContext } from "../../scene/sceneContext"; +import PreDefinedPath from "./preDefinedPath/preDefinedPath"; function Vehicles() { - const { vehicleStore } = useSceneContext(); - const { getVehicleById } = vehicleStore(); - const { selectedEventSphere } = useSelectedEventSphere(); - const { isPlaying } = usePlayButtonStore(); - const [isVehicleSelected, setIsVehicleSelected] = useState(false); + const { vehicleStore } = useSceneContext(); + const { getVehicleById } = vehicleStore(); + const { selectedEventSphere } = useSelectedEventSphere(); + const { isPlaying } = usePlayButtonStore(); + const [isVehicleSelected, setIsVehicleSelected] = useState(false); - useEffect(() => { - if (selectedEventSphere) { - const selectedVehicle = getVehicleById(selectedEventSphere.userData.modelUuid); - if (selectedVehicle) { - setIsVehicleSelected(true); - } else { - setIsVehicleSelected(false); - } - } - }, [getVehicleById, selectedEventSphere]) + useEffect(() => { + if (selectedEventSphere) { + const selectedVehicle = getVehicleById( + selectedEventSphere.userData.modelUuid + ); + if (selectedVehicle) { + setIsVehicleSelected(true); + } else { + setIsVehicleSelected(false); + } + } + }, [getVehicleById, selectedEventSphere]); - return ( - <> - - - - {isVehicleSelected && selectedEventSphere && !isPlaying && - - } - - - ); + return ( + <> + + {/* */} + {isVehicleSelected && selectedEventSphere && !isPlaying && } + + ); } export default Vehicles;