finding shortest path

This commit is contained in:
2025-08-26 14:06:25 +05:30
parent 7b5486590a
commit 5117e48527
4 changed files with 275 additions and 41 deletions

View File

@@ -57,7 +57,7 @@ export default function PathCreator() {
const { activeSubTool } = useActiveSubTool(); const { activeSubTool } = useActiveSubTool();
const { activeTool } = useActiveTool(); const { activeTool } = useActiveTool();
const [pathPointsList, setPathPointsList] = useState<PointData[]>([]);
const POINT_SNAP_THRESHOLD = 0.5; const POINT_SNAP_THRESHOLD = 0.5;
const CAN_POINT_SNAP = true; const CAN_POINT_SNAP = true;
useEffect(() => {}, [paths]); useEffect(() => {}, [paths]);
@@ -275,7 +275,11 @@ export default function PathCreator() {
newPoint.position = point.position; newPoint.position = point.position;
} }
} }
setPathPointsList((prev) => {
if (!prev.find((p) => p.pointId === newPoint.pointId))
return [...prev, newPoint];
return prev;
});
if (draftPoints.length === 0) { if (draftPoints.length === 0) {
setDraftPoints([newPoint]); setDraftPoints([newPoint]);
} else { } else {
@@ -329,6 +333,9 @@ export default function PathCreator() {
return points; return points;
}, [paths]); }, [paths]);
useEffect(() => {
console.log("allPoints: ", allPoints);
}, [paths]);
return ( return (
<> <>
{/* Draft points (red) */} {/* Draft points (red) */}
@@ -340,8 +347,10 @@ export default function PathCreator() {
))} ))}
{/* Saved points */} {/* Saved points */}
{allPoints.map((point) => ( {allPoints.map((point: PointData, i: any) => (
<PointHandler <PointHandler
points={allPoints}
pointIndex={i}
key={point.pointId} key={point.pointId}
point={point} point={point}
setPaths={setPaths} setPaths={setPaths}

View File

@@ -12,6 +12,7 @@ type PointData = {
isCurved?: boolean; isCurved?: boolean;
handleA?: [number, number, number] | null; handleA?: [number, number, number] | null;
handleB?: [number, number, number] | null; handleB?: [number, number, number] | null;
neighbors?: string[];
}; };
interface PathDataInterface { interface PathDataInterface {
pathId: string; pathId: string;
@@ -28,7 +29,130 @@ type PointHandlerProps = {
paths: PathDataInterface[]; paths: PathDataInterface[];
setHoveredPoint: React.Dispatch<React.SetStateAction<PointData | null>>; setHoveredPoint: React.Dispatch<React.SetStateAction<PointData | null>>;
hoveredLine: PathDataInterface | null; hoveredLine: PathDataInterface | null;
pointIndex: any;
points: PointData[];
}; };
function dist(a: PointData, b: PointData): number {
return Math.sqrt(
(a.position[0] - b.position[0]) ** 2 +
(a.position[1] - b.position[1]) ** 2 +
(a.position[2] - b.position[2]) ** 2
);
}
/** --- A* Algorithm --- */
type AStarResult = {
pointIds: string[];
distance: number;
};
function aStarShortestPath(
startId: string,
goalId: string,
points: PointData[],
paths: PathData
): AStarResult | null {
const pointById = new Map(points.map((p) => [p.pointId, p]));
const start = pointById.get(startId);
const goal = pointById.get(goalId);
if (!start || !goal) return null;
const openSet = new Set<string>([startId]);
const cameFrom: Record<string, string | null> = {};
const gScore: Record<string, number> = {};
const fScore: Record<string, number> = {};
for (const p of points) {
cameFrom[p.pointId] = null;
gScore[p.pointId] = Infinity;
fScore[p.pointId] = Infinity;
}
gScore[startId] = 0;
fScore[startId] = dist(start, goal);
const neighborsOf = (id: string): { id: string; cost: number }[] => {
const me = pointById.get(id)!;
const out: { id: string; cost: number }[] = [];
for (const edge of paths) {
const [a, b] = edge.pathPoints;
if (a.pointId === id) out.push({ id: b.pointId, cost: dist(me, b) });
else if (b.pointId === id) out.push({ id: a.pointId, cost: dist(me, a) });
}
return out;
};
while (openSet.size > 0) {
let current: string = [...openSet].reduce((a, b) =>
fScore[a] < fScore[b] ? a : b
);
if (current === goalId) {
const ids: string[] = [];
let node: string | null = current;
while (node) {
ids.unshift(node);
node = cameFrom[node];
}
return { pointIds: ids, distance: gScore[goalId] };
}
openSet.delete(current);
for (const nb of neighborsOf(current)) {
const tentativeG = gScore[current] + nb.cost;
if (tentativeG < gScore[nb.id]) {
cameFrom[nb.id] = current;
gScore[nb.id] = tentativeG;
fScore[nb.id] = tentativeG + dist(pointById.get(nb.id)!, goal);
openSet.add(nb.id);
}
}
}
return null;
}
/** --- Convert node path to edges --- */
function nodePathToEdges(
pointIds: string[],
points: PointData[],
paths: PathData
): PathData {
const byId = new Map(points.map((p) => [p.pointId, p]));
const edges: PathData = [];
for (let i = 0; i < pointIds.length - 1; i++) {
const a = pointIds[i];
const b = pointIds[i + 1];
const edge = paths.find(
(p) =>
(p.pathPoints[0].pointId === a && p.pathPoints[1].pointId === b) ||
(p.pathPoints[0].pointId === b && p.pathPoints[1].pointId === a)
);
if (edge) {
const [p1, p2] = edge.pathPoints;
edges.push({
pathId: edge.pathId,
pathPoints:
p1.pointId === a
? ([p1, p2] as [PointData, PointData])
: ([p2, p1] as [PointData, PointData]),
});
} else {
const pa = byId.get(a)!;
const pb = byId.get(b)!;
edges.push({
pathId: `synthetic-${a}-${b}`,
pathPoints: [pa, pb],
});
}
}
return edges;
}
export default function PointHandler({ export default function PointHandler({
point, point,
setPaths, setPaths,
@@ -36,6 +160,8 @@ export default function PointHandler({
setHoveredPoint, setHoveredPoint,
hoveredLine, hoveredLine,
hoveredPoint, hoveredPoint,
pointIndex,
points,
}: PointHandlerProps) { }: PointHandlerProps) {
const { toolMode } = useToolMode(); const { toolMode } = useToolMode();
const { activeTool } = useActiveTool(); const { activeTool } = useActiveTool();
@@ -47,9 +173,11 @@ export default function PointHandler({
paths?: any; paths?: any;
}>({}); }>({});
const [selectedPoints, setSelectedPoints] = useState<PointData[]>([]); const [selectedPoints, setSelectedPoints] = useState<PointData[]>([]);
const [shortestEdges, setShortestEdges] = useState<PathData>([]);
const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters
const [selectedPointIndices, setSelectedPointIndices] = useState<number[]>(
[]
);
const CAN_POINT_SNAP = true; const CAN_POINT_SNAP = true;
const CAN_ANGLE_SNAP = true; const CAN_ANGLE_SNAP = true;
const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5; const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5;
@@ -70,6 +198,7 @@ export default function PointHandler({
return removedPaths; return removedPaths;
}; };
const getConnectedPoints = (uuid: string): PointData[] => { const getConnectedPoints = (uuid: string): PointData[] => {
const connected: PointData[] = []; const connected: PointData[] = [];
@@ -85,6 +214,7 @@ export default function PointHandler({
return connected; return connected;
}; };
const snapPathAngle = useCallback( const snapPathAngle = useCallback(
( (
newPosition: [number, number, number], newPosition: [number, number, number],
@@ -155,6 +285,7 @@ export default function PointHandler({
}, },
[] []
); );
const getAllOtherPathPoints = useCallback((): PointData[] => { const getAllOtherPathPoints = useCallback((): PointData[] => {
return ( return (
paths?.flatMap((path) => paths?.flatMap((path) =>
@@ -186,42 +317,103 @@ export default function PointHandler({
}, },
[getAllOtherPathPoints] [getAllOtherPathPoints]
); );
useEffect(() => { let isHandlingShiftClick = false;
console.log("selectedPoints: ", selectedPoints);
}, [selectedPoints]); // const handlePointClick = (e: any, point: PointData) => {
// const setPathPosition = useCallback( // if (toolMode === "3D-Delete") {
// ( // removePathByPoint(point.pointId);
// pointUuid: string, // }
// position: [number, number, number], // if (e.shiftKey) {
// setPaths: React.Dispatch<React.SetStateAction<PathData>> // setSelectedPointIndices((prev) => {
// ) => { // if (prev.length === 0) return [pointIndex];
// setPaths((prevPaths) => // if (prev.length === 1) {
// prevPaths.map((path) => { // // defer shortest path calculation
// if (path.pathPoints.some((p) => p.pointId === pointUuid)) { // setTimeout(() => {
// return { // console.log("points: ", points);
// ...path, // const p1 = points[prev[0]];
// pathPoints: path.pathPoints.map((p) => // console.log(' p1: ', p1);
// p.pointId === pointUuid ? { ...p, position } : p // const p2 = points[pointIndex];
// ) as [PointData, PointData], // 👈 force back to tuple // console.log('p2: ', p2);
// }; // const result = aStarShortestPath(
// p1.pointId,
// p2.pointId,
// points,
// paths
// );
// if (result) {
// const edges = nodePathToEdges(result.pointIds, points, paths);
// console.log("edges: ", edges);
// setShortestEdges(edges);
// } else {
// setShortestEdges([]);
// } // }
// return path; // }, 0);
// }) // return [prev[0], pointIndex];
// ); // }
// }, // return [pointIndex];
// [setPaths] // });
// ); // }
// };
// const getPathsByPointId = (pointId: any) => { const handlePointClick = (e: any, point: PointData) => {
// return paths.filter((a) => a.pathPoints.some((p) => p.pointId === pointId)); e.stopPropagation();
// };
const handlePointClick = (e: any, pointId: string) => {
if (toolMode === "3D-Delete") { if (toolMode === "3D-Delete") {
removePathByPoint(pointId); removePathByPoint(point.pointId);
} else if (e.ctrlKey) { return;
}
if (e.shiftKey) {
if (isHandlingShiftClick) return; // prevent double-handling
isHandlingShiftClick = true;
setTimeout(() => {
isHandlingShiftClick = false;
}, 100); // reset the flag after a short delay
setSelectedPointIndices((prev) => {
// console.log("Clicked point index:", pointIndex);
console.log("Previous selection:", prev);
if (prev.length === 0) {
console.log("first works");
return [pointIndex];
}
if (prev.length === 1) {
if (prev[0] === pointIndex) {
console.log("Same point selected twice — ignoring");
return prev;
}
const p1 = points[prev[0]];
const p2 = points[pointIndex];
console.log("Point 1:", p1);
console.log("Point 2:", p2);
if (p1 && p2) {
const result = aStarShortestPath(
p1.pointId,
p2.pointId,
points,
paths
);
if (result) {
const edges = nodePathToEdges(result.pointIds, points, paths);
setShortestEdges(edges);
} else {
setShortestEdges([]);
}
}
return [prev[0], pointIndex];
}
return [pointIndex];
});
} }
}; };
const handleDragStart = (point: PointData) => { const handleDragStart = (point: PointData) => {
if (activeTool !== "cursor") return; if (activeTool !== "cursor") return;
const intersectionPoint = new Vector3(); const intersectionPoint = new Vector3();
@@ -261,6 +453,11 @@ export default function PointHandler({
pathIntersection.forEach((update) => {}); pathIntersection.forEach((update) => {});
} }
}; };
useEffect(() => {
console.log("selectedPoints: ", selectedPoints);
}, [selectedPoints]);
return ( return (
<> <>
<DragControls <DragControls
@@ -276,8 +473,7 @@ export default function PointHandler({
name="Path-Point" name="Path-Point"
userData={point} userData={point}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); handlePointClick(e, point);
handlePointClick(e, point.pointId);
}} }}
onPointerOver={(e) => { onPointerOver={(e) => {
if (!hoveredPoint && e.buttons === 0 && !e.ctrlKey) { if (!hoveredPoint && e.buttons === 0 && !e.ctrlKey) {
@@ -303,3 +499,30 @@ export default function PointHandler({
</> </>
); );
} }
// const setPathPosition = useCallback(
// (
// pointUuid: string,
// position: [number, number, number],
// setPaths: React.Dispatch<React.SetStateAction<PathData>>
// ) => {
// setPaths((prevPaths) =>
// prevPaths.map((path) => {
// if (path.pathPoints.some((p) => p.pointId === pointUuid)) {
// return {
// ...path,
// pathPoints: path.pathPoints.map((p) =>
// p.pointId === pointUuid ? { ...p, position } : p
// ) as [PointData, PointData], // 👈 force back to tuple
// };
// }
// return path;
// })
// );
// },
// [setPaths]
// );
// const getPathsByPointId = (pointId: any) => {
// return paths.filter((a) => a.pathPoints.some((p) => p.pointId === pointId));
// };

View File

@@ -602,7 +602,7 @@ export default function PointHandlers({
const handleARef = useRef<THREE.Mesh>(null); const handleARef = useRef<THREE.Mesh>(null);
const handleBRef = useRef<THREE.Mesh>(null); const handleBRef = useRef<THREE.Mesh>(null);
const lineRef = useRef<any>(null); const lineRef = useRef<any>(null);
const { camera, gl, controls, raycaster } = useThree(); const { gl, controls, raycaster } = useThree();
const [dragging, setDragging] = useState< const [dragging, setDragging] = useState<
null | "main" | "handleA" | "handleB" null | "main" | "handleA" | "handleB"
@@ -620,6 +620,7 @@ export default function PointHandlers({
if (prev.length === 1) { if (prev.length === 1) {
// defer shortest path calculation // defer shortest path calculation
setTimeout(() => { setTimeout(() => {
console.log('points: ', points);
const p1 = points[prev[0]]; const p1 = points[prev[0]];
const p2 = points[pointIndex]; const p2 = points[pointIndex];
const result = aStarShortestPath( const result = aStarShortestPath(
@@ -630,6 +631,7 @@ export default function PointHandlers({
); );
if (result) { if (result) {
const edges = nodePathToEdges(result.pointIds, points, paths); const edges = nodePathToEdges(result.pointIds, points, paths);
console.log('edges: ', edges);
setShortestEdges(edges); setShortestEdges(edges);
setShortestPath(edges); setShortestPath(edges);
} else { } else {

View File

@@ -51,7 +51,7 @@ export default function StructuredPath() {
// --- State Variables --- // --- State Variables ---
const [pathPointsList, setPathPointsList] = useState<PointData[]>([]); const [pathPointsList, setPathPointsList] = useState<PointData[]>([]);
const [allPaths, setAllPaths] = useState<PathData>([]); const [allPaths, setAllPaths] = useState<PathData>([]);
console.log("allPaths: ", allPaths);
const [computedShortestPath, setComputedShortestPath] = useState<PathData>( const [computedShortestPath, setComputedShortestPath] = useState<PathData>(
[] []
); );