From d090b976b08e9185098a0408e41b37862d216b6f Mon Sep 17 00:00:00 2001 From: Poovizhi Date: Sat, 23 Aug 2025 10:12:37 +0530 Subject: [PATCH] updated vehicle path based on wall data --- .../vehicle/pathCreator/pathCreator.tsx | 1089 +++++++++++++++++ .../modules/simulation/vehicle/vehicles.tsx | 4 +- 2 files changed, 1092 insertions(+), 1 deletion(-) create mode 100644 app/src/modules/simulation/vehicle/pathCreator/pathCreator.tsx diff --git a/app/src/modules/simulation/vehicle/pathCreator/pathCreator.tsx b/app/src/modules/simulation/vehicle/pathCreator/pathCreator.tsx new file mode 100644 index 0000000..f3b7644 --- /dev/null +++ b/app/src/modules/simulation/vehicle/pathCreator/pathCreator.tsx @@ -0,0 +1,1089 @@ +// import { Line } from "@react-three/drei"; +// import { useThree } from "@react-three/fiber"; +// import React, { useEffect, useMemo, useState } from "react"; +// import { MathUtils, Plane, Vector3 } from "three"; +// import { Vector3Array } from "../../../../types/world/worldTypes"; + +// type PointData = { +// pointId: string; +// position: [number, number, number]; +// isCurved?: boolean; +// handleA?: [number, number, number] | null; +// handleB?: [number, number, number] | null; +// }; + +// interface PathDataInterface { +// pathId: string; +// isActive?: boolean; +// isCurved?: boolean; +// pathPoints: [PointData, PointData]; +// } + +// type PathData = PathDataInterface[]; + +// export default function PathCreator() { +// const [paths, setPaths] = useState([]); +// const [draftPoints, setDraftPoints] = useState([]); +// const [mousePos, setMousePos] = useState<[number, number, number] | null>( +// null +// ); // 👈 track mouse for dashed line + +// const plane = useMemo(() => new Plane(new Vector3(0, 1, 0), 0), []); +// const { scene, raycaster, gl } = useThree(); +// const [snappedPosition, setSnappedPosition] = useState(); +// // --- Helpers --- +// function getClosestIntersection( +// intersects: Vector3Array, +// point: Vector3 +// ): Vector3 { +// let closestNewPoint: Vector3 = point; +// let minDistance = Infinity; +// for (const intersect of intersects) { +// const distance = point.distanceTo(intersect); +// if (distance < minDistance) { +// minDistance = distance; +// closestNewPoint = intersect; +// } +// } +// return closestNewPoint; +// } + +// const handleClick = () => { +// const intersectionPoint = new Vector3(); +// let position = raycaster.ray.intersectPlane(plane, intersectionPoint); +// if (!position) return; + +// const pointIntersect = raycaster +// .intersectObjects(scene.children) +// .find((intersect) => intersect.object.name === "Path-Point"); + +// const pathIntersect = raycaster +// .intersectObjects(scene.children) +// .find((intersect) => intersect.object.name === "Path-Line"); + +// // ✅ Case 1: Split an existing path +// if (!pointIntersect && pathIntersect) { +// const hitLine = pathIntersect.object; +// const hitPathId = hitLine.userData.pathId; + +// const hitPath = paths.find((p) => p.pathId === hitPathId); +// if (!hitPath) return; + +// const [p1, p2] = hitPath.pathPoints; + +// const point1Vec = new Vector3(...p1.position); +// const point2Vec = new Vector3(...p2.position); + +// const lineDir = new Vector3() +// .subVectors(point2Vec, point1Vec) +// .normalize(); +// const point1ToClick = new Vector3().subVectors( +// pathIntersect.point, +// point1Vec +// ); +// const dot = point1ToClick.dot(lineDir); + +// const projection = new Vector3() +// .copy(lineDir) +// .multiplyScalar(dot) +// .add(point1Vec); + +// const lineLength = point1Vec.distanceTo(point2Vec); +// let t = point1Vec.distanceTo(projection) / lineLength; +// t = Math.max(0, Math.min(1, t)); + +// const closestPoint = new Vector3().lerpVectors(point1Vec, point2Vec, t); + +// const newPoint: PointData = { +// pointId: MathUtils.generateUUID(), +// position: closestPoint.toArray() as [number, number, number], +// }; + +// const newPath1: PathDataInterface = { +// pathId: MathUtils.generateUUID(), +// pathPoints: [p1, newPoint], +// }; +// const newPath2: PathDataInterface = { +// pathId: MathUtils.generateUUID(), +// pathPoints: [newPoint, p2], +// }; + +// setPaths((prev) => +// prev.filter((p) => p.pathId !== hitPathId).concat([newPath1, newPath2]) +// ); + +// setDraftPoints([newPoint]); +// return; +// } +// // 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 (pointIntersects && !snappedPoint) { +// // const point = getWallPointById(pointIntersects.object.uuid); +// // if (point) { +// // newPoint.pointUuid = point.pointUuid; +// // newPoint.position = point.position; +// // newPoint.layer = point.layer; +// // } +// // } +// // ✅ Case 2: Normal new path creation +// const newPoint: PointData = { +// pointId: MathUtils.generateUUID(), +// position: [position.x, position.y, position.z], +// }; + +// if (draftPoints.length === 0) { +// setDraftPoints([newPoint]); +// } else { +// const newPath: PathDataInterface = { +// pathId: MathUtils.generateUUID(), +// pathPoints: [draftPoints[0], newPoint], +// }; +// setPaths((prev) => [...prev, newPath]); +// setDraftPoints([newPoint]); +// } +// }; + +// const handleContextMenu = (event: any) => { +// event.preventDefault(); +// setDraftPoints([]); +// }; + +// // 👇 track mouse move for preview line +// const handleMouseMove = () => { +// const intersectionPoint = new Vector3(); +// let pos = raycaster.ray.intersectPlane(plane, intersectionPoint); +// if (pos) { +// setMousePos([pos.x, pos.y, pos.z]); +// } +// }; + +// useEffect(() => { +// const canvasElement = gl.domElement; + +// canvasElement.addEventListener("click", handleClick); +// canvasElement.addEventListener("mousemove", handleMouseMove); +// canvasElement.addEventListener("contextmenu", handleContextMenu); + +// return () => { +// canvasElement.removeEventListener("click", handleClick); +// canvasElement.removeEventListener("mousemove", handleMouseMove); +// canvasElement.removeEventListener("contextmenu", handleContextMenu); +// }; +// }, [gl, draftPoints, paths]); + +// const allPoints = useMemo(() => { +// const points: PointData[] = []; +// const seenUuids = new Set(); +// paths?.forEach((wall) => { +// wall.pathPoints.forEach((point) => { +// if (!seenUuids.has(point.pointId)) { +// seenUuids.add(point.pointId); +// points.push(point); +// } +// }); +// }); +// return points; +// }, [paths]); + +// return ( +// <> +// {/* Draft points (red) */} +// {draftPoints.map((point) => ( +// +// +// +// +// ))} + +// {/* All saved points */} +// {allPoints.map((point) => ( +// +// +// +// +// ))} + +// {/* 👇 Dashed preview line while drawing */} +// {draftPoints.length > 0 && mousePos && ( +// +// )} + +// {/* Permanent paths */} +// {paths.map((points) => ( +// +// ))} +// +// ); +// } +import { Line } from "@react-three/drei"; +import { useFrame, useThree } from "@react-three/fiber"; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { LineCurve3, MathUtils, Plane, Vector3 } from "three"; +import { Vector3Array } from "../../../../types/world/worldTypes"; + +type PointData = { + pointId: string; + position: [number, number, number]; + isCurved?: boolean; + handleA?: [number, number, number] | null; + handleB?: [number, number, number] | null; +}; + +interface PathDataInterface { + pathId: string; + isActive?: boolean; + isCurved?: boolean; + pathPoints: [PointData, PointData]; +} + +type PathData = PathDataInterface[]; + +export default function PathCreator() { + const [paths, setPaths] = useState([]); + + const [draftPoints, setDraftPoints] = useState([]); + const [mousePos, setMousePos] = useState<[number, number, number] | null>( + null + ); + + const plane = useMemo(() => new Plane(new Vector3(0, 1, 0), 0), []); + const { scene, raycaster, gl } = useThree(); + const [snappedPosition, setSnappedPosition] = useState< + [number, number, number] | null + >(null); + const [snappedPoint, setSnappedPoint] = useState(null); + const finalPosition = useRef<[number, number, number] | null>(null); + const [tempPath, setTempPath] = useState(); + + const POINT_SNAP_THRESHOLD = 0.5; + const CAN_POINT_SNAP = true; + + const getAllOtherPathPoints = useCallback((): PointData[] => { + if (draftPoints.length === 0) return []; + return ( + paths?.flatMap((path) => + path.pathPoints.filter((pt) => pt.pointId !== draftPoints[0].pointId) + ) ?? [] + ); + }, [paths, draftPoints]); + + const snapPathPoint = useCallback( + (position: [number, number, number]) => { + if (draftPoints.length === 0 || !CAN_POINT_SNAP) { + return { + position, + isSnapped: false, + snappedPoint: null as PointData | null, + }; + } + + const otherPoints = getAllOtherPathPoints(); + const currentVec = new Vector3(...position); + + for (const point of otherPoints) { + const pointVec = new Vector3(...point.position); + const distance = currentVec.distanceTo(pointVec); + + if (distance <= POINT_SNAP_THRESHOLD) { + return { + position: point.position, + isSnapped: true, + snappedPoint: point, + }; + } + } + + return { position, isSnapped: false, snappedPoint: null }; + }, + [draftPoints, getAllOtherPathPoints] + ); + + // ---- RAYCAST ---- + useFrame(() => { + const intersectionPoint = new Vector3(); + raycaster.ray.intersectPlane(plane, intersectionPoint); + if (!intersectionPoint) return; + + const snapped = snapPathPoint([ + intersectionPoint.x, + intersectionPoint.y, + intersectionPoint.z, + ]); + + if (snapped.isSnapped && snapped.snappedPoint) { + finalPosition.current = snapped.position; + setSnappedPosition(snapped.position); + setSnappedPoint(snapped.snappedPoint); + } else { + finalPosition.current = [ + intersectionPoint.x, + intersectionPoint.y, + intersectionPoint.z, + ]; + setSnappedPosition(null); + setSnappedPoint(null); + } + if (!finalPosition.current) return; + const paths: [PointData, PointData] = [ + draftPoints[0], + { pointId: "temp-point", position: finalPosition.current }, + ]; + setTempPath({ + pathId: "temp-path", + pathPoints: paths, + }); + }); + + // ✅ Click handler + // const handleClick = () => { + // const intersectionPoint = new Vector3(); + // const pos = raycaster.ray.intersectPlane(plane, intersectionPoint); + // if (!pos) return; + + // const pointIntersect = raycaster + // .intersectObjects(scene.children) + // .find((intersect) => intersect.object.name === "Path-Point"); + + // const pathIntersect = raycaster + // .intersectObjects(scene.children) + // .find((intersect) => intersect.object.name === "Path-Line"); + + // // Case 1: Split path + // if (!pointIntersect && pathIntersect) { + // const hitLine = pathIntersect.object; + // const hitPathId = hitLine.userData.pathId; + // const hitPath = paths.find((p) => p.pathId === hitPathId); + // if (!hitPath) return; + + // const [p1, p2] = hitPath.pathPoints; + // const point1Vec = new Vector3(...p1.position); + // const point2Vec = new Vector3(...p2.position); + + // const lineDir = new Vector3() + // .subVectors(point2Vec, point1Vec) + // .normalize(); + // const point1ToClick = new Vector3().subVectors( + // pathIntersect.point, + // point1Vec + // ); + // const dot = point1ToClick.dot(lineDir); + + // const projection = new Vector3() + // .copy(lineDir) + // .multiplyScalar(dot) + // .add(point1Vec); + + // const lineLength = point1Vec.distanceTo(point2Vec); + // let t = point1Vec.distanceTo(projection) / lineLength; + // t = Math.max(0, Math.min(1, t)); + + // const closestPoint = new Vector3().lerpVectors(point1Vec, point2Vec, t); + + // const newPoint: PointData = { + // pointId: MathUtils.generateUUID(), + // position: closestPoint.toArray() as [number, number, number], + // }; + + // const newPath1: PathDataInterface = { + // pathId: MathUtils.generateUUID(), + // pathPoints: [p1, newPoint], + // }; + // const newPath2: PathDataInterface = { + // pathId: MathUtils.generateUUID(), + // pathPoints: [newPoint, p2], + // }; + + // setPaths((prev) => + // prev.filter((p) => p.pathId !== hitPathId).concat([newPath1, newPath2]) + // ); + + // setDraftPoints([newPoint]); + // return; + // } + + // // Case 2: Normal path creation + // const newPoint: PointData = { + // pointId: MathUtils.generateUUID(), + // position: [pos.x, pos.y, pos.z], + // }; + + // if (snappedPosition) { + // newPoint.position = snappedPosition; + // } + + // if (snappedPoint && draftPoints[0]?.pointId === snappedPoint.pointId) { + // return; // prevent connecting point to itself + // } + + // if (draftPoints.length === 0) { + // setDraftPoints([newPoint]); + // } else { + // const newPath: PathDataInterface = { + // pathId: MathUtils.generateUUID(), + // pathPoints: [draftPoints[0], newPoint], + // }; + // setPaths((prev) => [...prev, newPath]); + // setDraftPoints([newPoint]); + // } + // }; + // ✅ Click handler + + useEffect(() => { + console.log("paths", paths); + }, [paths]); + const getPathPointById = (uuid: any) => { + for (const path of paths) { + const point = path.pathPoints.find((p) => p.pointId === uuid); + console.log( + "point: ", + path.pathPoints.map((val) => val) + ); + if (point) return point; + } + return undefined; + }; + const getPathPointByPoints = (point: any) => { + for (const path of paths) { + if ( + (path.pathPoints[0].pointId === point[0].pointId || + path.pathPoints[1].pointId === point[0].pointId) && + (path.pathPoints[0].pointId === point[1].pointId || + path.pathPoints[1].pointId === point[1].pointId) + ) { + return path; + } + } + return undefined; + }; + useEffect(() => { + console.log("draftPoints", draftPoints); + }, [draftPoints]); + const handleClick = () => { + const intersectionPoint = new Vector3(); + const pos = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (!pos) return; + const pointIntersect = raycaster + .intersectObjects(scene.children) + .find((intersect) => intersect.object.name === "Path-Point"); + + const pathIntersect = raycaster + .intersectObjects(scene.children) + .find((intersect) => intersect.object.name === "Path-Line"); + + // --- Case 1: Split path --- + if (!pointIntersect && pathIntersect) { + const hitLine = pathIntersect.object; + const clickedPath = getPathPointByPoints(hitLine.userData.pathPoints); + if (clickedPath) { + const hitPath = paths.find((p) => p.pathId === clickedPath.pathId); + if (!hitPath) return; + + const [p1, p2] = clickedPath.pathPoints; + const point1Vec = new Vector3(...p1.position); + const point2Vec = new Vector3(...p2.position); + + // Project clicked point onto line + const lineDir = new Vector3() + .subVectors(point2Vec, point1Vec) + .normalize(); + const point1ToClick = new Vector3().subVectors( + pathIntersect.point, + point1Vec + ); + const dot = point1ToClick.dot(lineDir); + const projection = new Vector3() + .copy(lineDir) + .multiplyScalar(dot) + .add(point1Vec); + + const lineLength = point1Vec.distanceTo(point2Vec); + let t = point1Vec.distanceTo(projection) / lineLength; + t = Math.max(0, Math.min(1, t)); + + const closestPoint = new Vector3().lerpVectors(point1Vec, point2Vec, t); + setPaths((prev) => prev.filter((p) => p.pathId !== clickedPath.pathId)); + + const point1: PointData = { + pointId: clickedPath?.pathPoints[0].pointId, + position: clickedPath?.pathPoints[0].position, + }; + const point2: PointData = { + pointId: clickedPath?.pathPoints[1].pointId, + position: clickedPath?.pathPoints[1].position, + }; + const splitPoint: PointData = { + pointId: MathUtils.generateUUID(), + position: closestPoint.toArray() as [number, number, number], + }; + + if (draftPoints.length === 0) { + const path1: PathDataInterface = { + pathId: MathUtils.generateUUID(), + pathPoints: [point1, splitPoint], + }; + const path2: PathDataInterface = { + pathId: MathUtils.generateUUID(), + pathPoints: [point2, splitPoint], + }; + setDraftPoints([splitPoint]); + setPaths((prev) => [...prev, path1, path2]); + console.log("draftPoints:if ", path1, path2, draftPoints); + } else { + const newPath: PathDataInterface = { + pathId: MathUtils.generateUUID(), + pathPoints: [draftPoints[0], splitPoint], + }; + const firstPath: PathDataInterface = { + pathId: MathUtils.generateUUID(), + pathPoints: [point1, splitPoint], + }; + const secondPath: PathDataInterface = { + pathId: MathUtils.generateUUID(), + pathPoints: [point2, splitPoint], + }; + + console.log("else ", newPath, firstPath, secondPath, draftPoints); + setPaths((prev) => [...prev, newPath, firstPath, secondPath]); + setDraftPoints([splitPoint]); + } + + return; + } + } + const newPoint: PointData = { + pointId: MathUtils.generateUUID(), + position: [pos.x, pos.y, pos.z], + }; + // --- Case 2: Normal path creation --- + let clickedPoint: PointData | null = null; + for (const pt of allPoints) { + if (new Vector3(...pt.position).distanceTo(pos) <= POINT_SNAP_THRESHOLD) { + clickedPoint = pt; + break; + } + } + if (snappedPosition && snappedPoint) { + newPoint.pointId = snappedPoint.pointId; + newPoint.position = snappedPosition; + } + if (snappedPoint && snappedPoint.pointId == draftPoints[0]?.pointId) { + return; + } + if (snappedPosition && !snappedPoint) { + newPoint.position = snappedPosition; + } + if (pointIntersect && !snappedPoint) { + const point = getPathPointById(pointIntersect.object.userData.pointId); + console.log("point: ", point); + + if (point) { + console.log("newPoint: ", newPoint); + newPoint.pointId = point.pointId; + newPoint.position = point.position; + } + } + + if (draftPoints.length === 0) { + setDraftPoints([newPoint]); + } else { + const newPath: PathDataInterface = { + pathId: MathUtils.generateUUID(), + pathPoints: [draftPoints[0], newPoint], + }; + + setPaths((prev) => [...prev, newPath]); + setDraftPoints([newPoint]); + } + }; + + const handleContextMenu = (event: any) => { + event.preventDefault(); + setDraftPoints([]); + }; + + const handleMouseMove = () => { + const intersectionPoint = new Vector3(); + const pos = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (pos) { + setMousePos([pos.x, pos.y, pos.z]); + } + }; + + useEffect(() => { + const canvasElement = gl.domElement; + canvasElement.addEventListener("click", handleClick); + canvasElement.addEventListener("mousemove", handleMouseMove); + canvasElement.addEventListener("contextmenu", handleContextMenu); + + return () => { + canvasElement.removeEventListener("click", handleClick); + canvasElement.removeEventListener("mousemove", handleMouseMove); + canvasElement.removeEventListener("contextmenu", handleContextMenu); + }; + }, [gl, draftPoints, paths]); + + const allPoints = useMemo(() => { + const points: PointData[] = []; + const seen = new Set(); + paths.forEach((path) => { + path.pathPoints.forEach((p) => { + if (!seen.has(p.pointId)) { + seen.add(p.pointId); + points.push(p); + } + }); + }); + return points; + }, [paths]); + + useEffect(() => {}, [paths]); + return ( + <> + {/* Draft points (red) */} + {draftPoints.map((point) => ( + + + + + ))} + + {/* Saved points */} + {allPoints.map((point) => ( + + + + + ))} + {/* {tempPath && draftPoints.length > 0 && ( + + )} */} + {/* Preview line */} + {draftPoints.length > 0 && mousePos && ( + + )} + + {/* Permanent paths */} + {paths.map((path) => ( + + ))} + + ); +} + +function ReferenceLine({ points }: any) { + const path = useMemo(() => { + const [start, end] = points?.map((p: any) => new Vector3(...p.position)); + return new LineCurve3(start, end); + }, [points]); + + const linePoints = useMemo(() => path.getPoints(1), [path]); + + return ; +} + +// import { Line } from "@react-three/drei"; +// import { useFrame, useThree } from "@react-three/fiber"; +// import React, { +// useCallback, +// useEffect, +// useMemo, +// useRef, +// useState, +// } from "react"; +// import { MathUtils, Plane, Vector3 } from "three"; +// import { Vector3Array } from "../../../../types/world/worldTypes"; + +// type PointData = { +// pointId: string; +// position: [number, number, number]; +// isCurved?: boolean; +// handleA?: [number, number, number] | null; +// handleB?: [number, number, number] | null; +// }; + +// interface PathDataInterface { +// pathId: string; +// isActive?: boolean; +// isCurved?: boolean; +// pathPoints: [PointData, PointData]; +// } + +// type PathData = PathDataInterface[]; + +// type SnapObject = { +// pointId: string; +// position: [number, number, number]; +// }; + +// export default function PathCreator() { +// const [paths, setPaths] = useState([]); +// +// const [draftPoints, setDraftPoints] = useState([]); +// const [mousePos, setMousePos] = useState<[number, number, number] | null>( +// null +// ); // 👈 track mouse for dashed line + +// const plane = useMemo(() => new Plane(new Vector3(0, 1, 0), 0), []); +// const { scene, raycaster, gl } = useThree(); +// const [snappedPosition, setSnappedPosition] = useState< +// [number, number, number] | null +// >(null); +// const [snappedPoint, setSnappedPoint] = useState(null); +// const [tempWall, setTempWall] = useState(null); +// const finalPosition = useRef<[number, number, number] | null>(null); + +// const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters + +// const CAN_POINT_SNAP = true; // Whether snapping is enabled or not + +// const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5; // Distance threshold for snapping in meters + +// const CAN_ANGLE_SNAP = true; // Whether snapping is enabled or not +// // --- Helpers --- +// function getClosestIntersection( +// intersects: Vector3Array, +// point: Vector3 +// ): Vector3 { +// let closestNewPoint: Vector3 = point; +// let minDistance = Infinity; +// for (const intersect of intersects) { +// const distance = point.distanceTo(intersect); +// if (distance < minDistance) { +// minDistance = distance; +// closestNewPoint = intersect; +// } +// } +// return closestNewPoint; +// } +// const getAllOtherPathPoints = useCallback((): PointData[] => { +// if (draftPoints.length === 0) return []; +// return ( +// paths?.flatMap((path) => +// path.pathPoints.filter((pt) => pt.pointId !== draftPoints[0].pointId) +// ) ?? [] +// ); +// }, [paths, draftPoints]); + +// // Snapping +// const snapPathPoint = useCallback( +// (position: [number, number, number], tempPoint?: PointData) => { +// if (draftPoints.length === 0 || !CAN_POINT_SNAP) { +// return { +// position, +// isSnapped: false, +// snappedPoint: null as PointData | null, +// }; +// } + +// const otherPoints = [...getAllOtherPathPoints()]; +// if (tempPoint) { +// otherPoints.push(tempPoint); +// } + +// const currentVec = new Vector3(...position); + +// for (const point of otherPoints) { +// const pointVec = new Vector3(...point.position); +// const distance = currentVec.distanceTo(pointVec); + +// if (distance <= POINT_SNAP_THRESHOLD) { +// return { +// position: point.position, +// isSnapped: true, +// snappedPoint: point, +// }; +// } +// } + +// return { position, isSnapped: false, snappedPoint: null }; +// }, +// [draftPoints, getAllOtherPathPoints] +// ); + +// // Frame loop +// useFrame(() => { +// const intersectionPoint = new Vector3(); +// raycaster.ray.intersectPlane(plane, intersectionPoint); + +// if (!intersectionPoint) return; + +// const snapped = snapPathPoint( +// [intersectionPoint.x, intersectionPoint.y, intersectionPoint.z], +// draftPoints[0] // ✅ use one point, not array +// ); + +// if (snapped.isSnapped && snapped.snappedPoint) { +// finalPosition.current = snapped.position; +// setSnappedPosition(snapped.position); // ✅ correct type +// setSnappedPoint(snapped.snappedPoint); +// } else { +// finalPosition.current = [ +// intersectionPoint.x, +// intersectionPoint.y, +// intersectionPoint.z, +// ]; +// setSnappedPosition(null); +// setSnappedPoint(null); +// } +// }); +// const getWallPointById: (uuid) => { +// for (const wall of get().paths) { +// const point = wall.pathPoints.find(p => p.pointUuid === uuid); +// if (point) return point; +// } +// return undefined; +// } +// const handleClick = () => { +// const intersectionPoint = new Vector3(); +// let position = raycaster.ray.intersectPlane(plane, intersectionPoint); +// if (!position) return; + +// const pointIntersect = raycaster +// .intersectObjects(scene.children) +// .find((intersect) => intersect.object.name === "Path-Point"); + +// const pathIntersect = raycaster +// .intersectObjects(scene.children) +// .find((intersect) => intersect.object.name === "Path-Line"); + +// // ✅ Case 1: Split an existing path +// if (!pointIntersect && pathIntersect) { +// const hitLine = pathIntersect.object; +// const hitPathId = hitLine.userData.pathId; + +// const hitPath = paths.find((p) => p.pathId === hitPathId); +// if (!hitPath) return; + +// const [p1, p2] = hitPath.pathPoints; + +// const point1Vec = new Vector3(...p1.position); +// const point2Vec = new Vector3(...p2.position); + +// const lineDir = new Vector3() +// .subVectors(point2Vec, point1Vec) +// .normalize(); +// const point1ToClick = new Vector3().subVectors( +// pathIntersect.point, +// point1Vec +// ); +// const dot = point1ToClick.dot(lineDir); + +// const projection = new Vector3() +// .copy(lineDir) +// .multiplyScalar(dot) +// .add(point1Vec); + +// const lineLength = point1Vec.distanceTo(point2Vec); +// let t = point1Vec.distanceTo(projection) / lineLength; +// t = Math.max(0, Math.min(1, t)); + +// const closestPoint = new Vector3().lerpVectors(point1Vec, point2Vec, t); + +// const newPoint: PointData = { +// pointId: MathUtils.generateUUID(), +// position: closestPoint.toArray() as [number, number, number], +// }; + +// const newPath1: PathDataInterface = { +// pathId: MathUtils.generateUUID(), +// pathPoints: [p1, newPoint], +// }; +// const newPath2: PathDataInterface = { +// pathId: MathUtils.generateUUID(), +// pathPoints: [newPoint, p2], +// }; + +// setPaths((prev) => +// prev.filter((p) => p.pathId !== hitPathId).concat([newPath1, newPath2]) +// ); + +// setDraftPoints([newPoint]); +// return; +// } + +// const newPoint: PointData = { +// pointId: MathUtils.generateUUID(), +// position: [position.x, position.y, position.z], +// }; +// if (snappedPosition && snappedPoint) { +// newPoint.pointId = snappedPoint.pointId; +// newPoint.position = snappedPosition; +// } + +// if (snappedPoint && snappedPoint.pointId === draftPoints[0].pointId) { +// return; +// } + +// if (snappedPosition && !snappedPoint) { +// newPoint.position = snappedPosition; +// } + +// if (pointIntersect && !snappedPoint) { +// const point = getWallPointById(pointIntersects.object.uuid); +// if (point) { +// newPoint.pointUuid = point.pointUuid; +// newPoint.position = point.position; +// newPoint.layer = point.layer; +// } +// } +// // ✅ Case 2: Normal new path creation + +// if (draftPoints.length === 0) { +// setDraftPoints([newPoint]); +// } else { +// const newPath: PathDataInterface = { +// pathId: MathUtils.generateUUID(), +// pathPoints: [draftPoints[0], newPoint], +// }; +// setPaths((prev) => [...prev, newPath]); +// setDraftPoints([newPoint]); +// } +// }; + +// const handleContextMenu = (event: any) => { +// event.preventDefault(); +// setDraftPoints([]); +// }; + +// // 👇 track mouse move for preview line +// const handleMouseMove = () => { +// const intersectionPoint = new Vector3(); +// let pos = raycaster.ray.intersectPlane(plane, intersectionPoint); +// if (pos) { +// setMousePos([pos.x, pos.y, pos.z]); +// } +// }; + +// useEffect(() => { +// const canvasElement = gl.domElement; + +// canvasElement.addEventListener("click", handleClick); +// canvasElement.addEventListener("mousemove", handleMouseMove); +// canvasElement.addEventListener("contextmenu", handleContextMenu); + +// return () => { +// canvasElement.removeEventListener("click", handleClick); +// canvasElement.removeEventListener("mousemove", handleMouseMove); +// canvasElement.removeEventListener("contextmenu", handleContextMenu); +// }; +// }, [gl, draftPoints, paths]); + +// const allPoints = useMemo(() => { +// const points: PointData[] = []; +// const seenUuids = new Set(); +// paths?.forEach((wall) => { +// wall.pathPoints.forEach((point) => { +// if (!seenUuids.has(point.pointId)) { +// seenUuids.add(point.pointId); +// points.push(point); +// } +// }); +// }); +// return points; +// }, [paths]); + +// return ( +// <> +// {/* Draft points (red) */} +// {draftPoints.map((point) => ( +// +// +// +// +// ))} + +// {/* All saved points */} +// {allPoints.map((point) => ( +// +// +// +// +// ))} + +// {/* 👇 Dashed preview line while drawing */} +// {draftPoints.length > 0 && mousePos && ( +// +// )} + +// {/* Permanent paths */} +// {paths.map((points) => ( +// +// ))} +// +// ); +// } diff --git a/app/src/modules/simulation/vehicle/vehicles.tsx b/app/src/modules/simulation/vehicle/vehicles.tsx index abcd659..95bef00 100644 --- a/app/src/modules/simulation/vehicle/vehicles.tsx +++ b/app/src/modules/simulation/vehicle/vehicles.tsx @@ -6,6 +6,7 @@ import VehicleUI from "../spatialUI/vehicle/vehicleUI"; import { useSceneContext } from "../../scene/sceneContext"; import PreDefinedPath from "./preDefinedPath/preDefinedPath"; import StructuredPath from "./structuredPath/structuredPath"; +import PathCreator from "./pathCreator/pathCreator"; function Vehicles() { const { vehicleStore } = useSceneContext(); @@ -29,7 +30,8 @@ function Vehicles() { return ( <> - + + {/* */} {/* */} {/* */} {isVehicleSelected && selectedEventSphere && !isPlaying && }