added pre defined paths in rough

This commit is contained in:
2025-08-13 15:24:32 +05:30
parent e835de5424
commit 5e025224d6
8 changed files with 2039 additions and 595 deletions

View File

File diff suppressed because it is too large Load Diff

View File

@@ -156,7 +156,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
} else {
object.quaternion.rotateTowards(targetQuaternion, step);
}
}
}
return;
}
}

View File

@@ -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<React.SetStateAction<any[][]>>;
// }
// export default function EditablePoint({
// point,
// pointIndex,
// groupIndex,
// selected,
// setPointsGroups,
// }: PointProps) {
// const meshRef = useRef<THREE.Mesh>(null);
// const handleARef = useRef<THREE.Mesh>(null);
// const handleBRef = useRef<THREE.Mesh>(null);
// const lineRef = useRef<THREE.Line>(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 */}
// <mesh
// ref={meshRef}
// position={point.position}
// onClick={onPointClick}
// onPointerDown={(e) => startDrag("main", e)}
// onPointerUp={stopDrag}
// >
// <sphereGeometry args={[0.3, 16, 16]} />
// <meshStandardMaterial
// color={selected.includes(pointIndex) ? "red" : "pink"}
// />
// </mesh>
// {/* Handles + line */}
// {point.isCurved && point.handleA && point.handleB && (
// <>
// {/* Line between handles */}
// {point.handleA && point.handleB && (
// <Line
// points={[point.handleA, point.handleB]}
// color="gray"
// lineWidth={1} // optional, works in WebGL2
// />
// )}
// {/* Handle A */}
// <mesh
// ref={handleARef}
// position={point.handleA}
// onPointerDown={(e) => startDrag("handleA", e)}
// onPointerUp={stopDrag}
// >
// <sphereGeometry args={[0.15, 8, 8]} />
// <meshStandardMaterial color="orange" />
// </mesh>
// {/* Handle B */}
// <mesh
// ref={handleBRef}
// position={point.handleB}
// onPointerDown={(e) => startDrag("handleB", e)}
// onPointerUp={stopDrag}
// >
// <sphereGeometry args={[0.15, 8, 8]} />
// <meshStandardMaterial color="green" />
// </mesh>
// </>
// )}
// </>
// );
// }
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<React.SetStateAction<number[]>>;
pointsGroups: any[][];
setPointsGroups: React.Dispatch<React.SetStateAction<any[][]>>;
shortestPath: number[]; // <- add this
setShortestPath: React.Dispatch<React.SetStateAction<number[]>>; // <- add this
setShortestDistance?: React.Dispatch<React.SetStateAction<number>>; // optional
}
export default function PointHandle({
point,
pointIndex,
groupIndex,
selected,
setSelected,
pointsGroups,
setPointsGroups,
setShortestDistance,
shortestPath,
setShortestPath,
}: PointProps) {
const meshRef = useRef<THREE.Mesh>(null);
const handleARef = useRef<THREE.Mesh>(null);
const handleBRef = useRef<THREE.Mesh>(null);
const lineRef = useRef<THREE.Line>(null!);
// const pathLineRef = useRef<THREE.Line>(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<number[]>([]);
/** 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 */}
<mesh
ref={meshRef}
position={point.position}
onClick={onPointClick}
onPointerDown={(e) => startDrag("main", e)}
onPointerUp={stopDrag}
>
<sphereGeometry args={[0.3, 16, 16]} />
<meshStandardMaterial
color={selected.includes(pointIndex) ? "red" : "pink"}
/>
</mesh>
{/* Handles + line */}
{point.isCurved && point.handleA && point.handleB && (
<>
<Line
points={[point.handleA, point.handleB]}
color="gray"
lineWidth={1}
/>
<mesh
ref={handleARef}
position={point.handleA}
onPointerDown={(e) => startDrag("handleA", e)}
onPointerUp={stopDrag}
>
<sphereGeometry args={[0.15, 8, 8]} />
<meshStandardMaterial color="orange" />
</mesh>
<mesh
ref={handleBRef}
position={point.handleB}
onPointerDown={(e) => startDrag("handleB", e)}
onPointerUp={stopDrag}
>
<sphereGeometry args={[0.15, 8, 8]} />
<meshStandardMaterial color="green" />
</mesh>
</>
)}
{/* Highlight shortest path */}
{shortestPath.length > 1 && (
<Line
points={shortestPath} // <- just use the positions array
color="blue"
lineWidth={2}
/>
)}
</>
);
}
/** Build adjacency list for shortest path */
// const buildGraph = (points: any[]) => {
// const graph: Record<number, { neighbor: number; distance: number }[]> = {};
// 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]);

View File

@@ -0,0 +1,22 @@
export function findShortestPath(
startIndex: number,
endIndex: number,
adjacency: number[][]
) {
const queue = [[startIndex]];
const visited = new Set<number>();
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 [];
}

View File

@@ -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<Vector3 | null>(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 (
<Line
points={curvePoints}
color="purple"
lineWidth={2}
onPointerDown={onClickLine}
onPointerUp={onPointerUp}
/>
);
}

View File

@@ -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<number[]>([]);
// 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) => (
// <mesh
// key={idx}
// position={pos}
// onClick={() => {
// setSelected((prev) => (prev.length === 2 ? [idx] : [...prev, idx]));
// }}
// >
// <sphereGeometry args={[0.3, 16, 16]} />
// <meshStandardMaterial
// color={selected.includes(idx) ? "red" : "pink"}
// />
// </mesh>
// ))}
// {points.map((pos, i) => {
// if (i < points.length - 1) {
// return (
// <DraggableLineSegment
// key={i}
// index={i}
// start={new THREE.Vector3(...points[i])}
// end={new THREE.Vector3(...points[i + 1])}
// updatePoints={updatePoints}
// insertPoint={insertPoint}
// />
// );
// }
// 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<number[]>([]);
// 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) => (
// <React.Fragment key={gIdx}>
// {group.map((pos, idx) => (
// <mesh
// key={idx}
// position={pos}
// onClick={(e) => {
// e.stopPropagation();
// setSelected((prev) =>
// prev.length === 2 ? [idx] : [...prev, idx]
// );
// }}
// >
// <sphereGeometry args={[0.3, 16, 16]} />
// <meshStandardMaterial
// color={selected.includes(idx) ? "red" : "pink"}
// />
// </mesh>
// ))}
// {group.map((pos, i) => {
// if (i < group.length - 1) {
// return (
// <DraggableLineSegment
// key={i}
// index={i}
// start={new THREE.Vector3(...group[i])}
// end={new THREE.Vector3(...group[i + 1])}
// updatePoints={(i0, p0, i1, p1) =>
// updatePoints(gIdx, i0, p0, i1, p1)
// }
// insertPoint={(index, point) =>
// insertPoint(gIdx, index, point)
// }
// />
// );
// }
// return null;
// })}
// </React.Fragment>
// ))}
// </>
// );
// }
// 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<PointData[][]>([]);
console.log("mainShapeOnly: ", mainShapeOnly);
const [pointsGroups, setPointsGroups] = useState<PointData[][]>([[]]);
console.log("pointsGroups: ", pointsGroups);
const [definedPath, setDefinedPath] = useState<PointData[][] | PointData[]>(
[]
);
console.log("definedPath: ", definedPath);
const [selected, setSelected] = useState<number[]>([]);
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<string>();
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<number[]>([]);
const [shortestDistance, setShortestDistance] = useState<number>(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) => (
<React.Fragment key={gIdx}>
{group.map((point, idx) => (
<PointHandle
key={point.pointId}
point={point}
groupIndex={gIdx}
pointIndex={idx}
setPointsGroups={setPointsGroups}
pointsGroups={pointsGroups} // <-- pass the full groups
selected={selected}
setSelected={setSelected} // <-- pass setter for multi-selection
shortestPath={shortestPath}
setShortestPath={setShortestPath}
setShortestDistance={setShortestDistance}
/>
))}
{group.map((point, i) => {
if (i < group.length - 1) {
return (
<LineSegment
key={i}
index={i}
createdPoints={group} // pass the whole group here
updatePoints={(i0, p0, i1, p1) => {
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;
})}
</React.Fragment>
))}
</>
);
}
// 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]
// );

View File

@@ -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 (
<>
<VehicleInstances />
{isVehicleSelected && selectedEventSphere && !isPlaying &&
<VehicleUI />
}
</>
);
return (
<>
<PreDefinedPath />
{/* <VehicleInstances /> */}
{isVehicleSelected && selectedEventSphere && !isPlaying && <VehicleUI />}
</>
);
}
export default Vehicles;