added pre defined paths in rough
This commit is contained in:
0
app/src/functions/findShortestPath.ts
Normal file
0
app/src/functions/findShortestPath.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -156,7 +156,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai
|
||||
} else {
|
||||
object.quaternion.rotateTowards(targetQuaternion, step);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
@@ -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 [];
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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]
|
||||
// );
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user