added predined path
This commit is contained in:
@@ -28,8 +28,7 @@ function AisleCreator() {
|
||||
const { activeLayer } = useActiveLayer();
|
||||
const { socket } = useSocketStore();
|
||||
const { aisleStore, undoRedo2DStore } = useSceneContext();
|
||||
const { aisles, addAisle, getAislePointById } = aisleStore();
|
||||
console.log("aisles: ", aisles);
|
||||
const { addAisle, getAislePointById } = aisleStore();
|
||||
const { push2D } = undoRedo2DStore();
|
||||
const drag = useRef(false);
|
||||
const isLeftMouseDown = useRef(false);
|
||||
|
||||
@@ -1,245 +1,20 @@
|
||||
// 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";
|
||||
|
||||
type PointData = {
|
||||
pointId: string;
|
||||
position: [number, number, number];
|
||||
isCurved: boolean;
|
||||
handleA: [number, number, number] | null;
|
||||
handleB: [number, number, number] | null;
|
||||
};
|
||||
interface PointProps {
|
||||
point: any;
|
||||
pointIndex: number;
|
||||
groupIndex: number;
|
||||
selected: number[];
|
||||
mainShapeOnly?: PointData[];
|
||||
setSelected: React.Dispatch<React.SetStateAction<number[]>>;
|
||||
pointsGroups: any[][];
|
||||
setPointsGroups: React.Dispatch<React.SetStateAction<any[][]>>;
|
||||
@@ -260,7 +35,6 @@ export default function PointHandle({
|
||||
shortestPath,
|
||||
setShortestPath,
|
||||
}: PointProps) {
|
||||
|
||||
const meshRef = useRef<THREE.Mesh>(null);
|
||||
const handleARef = useRef<THREE.Mesh>(null);
|
||||
const handleBRef = useRef<THREE.Mesh>(null);
|
||||
@@ -553,7 +327,7 @@ export default function PointHandle({
|
||||
// path.unshift(u);
|
||||
// u = previous[u];
|
||||
// }
|
||||
//
|
||||
//
|
||||
// return path;
|
||||
// };
|
||||
|
||||
|
||||
@@ -1,3 +1,90 @@
|
||||
// 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}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
import { Line } from "@react-three/drei";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
@@ -10,7 +97,7 @@ export default function LineSegment({
|
||||
insertPoint,
|
||||
}: {
|
||||
index: number;
|
||||
createdPoints: any[]; // Array of points with position, isCurved, handleA, handleB
|
||||
createdPoints: any[];
|
||||
updatePoints: (i0: number, p0: Vector3, i1: number, p1: Vector3) => void;
|
||||
insertPoint?: (index: number, point: Vector3) => void;
|
||||
}) {
|
||||
@@ -18,21 +105,25 @@ export default function LineSegment({
|
||||
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);
|
||||
// Force y = 0
|
||||
const starts = new Vector3(current.position[0], 0, current.position[2]);
|
||||
const ends = new Vector3(next.position[0], 0, next.position[2]);
|
||||
|
||||
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 hB = current.handleB
|
||||
? new Vector3(current.handleB[0], 0, current.handleB[2])
|
||||
: starts;
|
||||
const hA = next.handleA
|
||||
? new Vector3(next.handleA[0], 0, next.handleA[2])
|
||||
: ends;
|
||||
|
||||
const curve = useCurve
|
||||
? new CubicBezierCurve3(starts, hB, hA, ends)
|
||||
@@ -41,7 +132,6 @@ export default function LineSegment({
|
||||
return curve.getPoints(useCurve ? 100 : 2);
|
||||
}, [createdPoints, index]);
|
||||
|
||||
// ======== Events ========
|
||||
const onPointerUp = () => {
|
||||
dragStart.current = null;
|
||||
gl.domElement.style.cursor = "default";
|
||||
@@ -51,8 +141,17 @@ export default function LineSegment({
|
||||
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 start = new Vector3(
|
||||
createdPoints[index].position[0],
|
||||
0,
|
||||
createdPoints[index].position[2]
|
||||
);
|
||||
const end = new Vector3(
|
||||
createdPoints[index + 1].position[0],
|
||||
0,
|
||||
createdPoints[index + 1].position[2]
|
||||
);
|
||||
|
||||
const segLen = start.distanceTo(end);
|
||||
const distToStart = start.distanceTo(intersection);
|
||||
const distToEnd = end.distanceTo(intersection);
|
||||
@@ -74,7 +173,6 @@ export default function LineSegment({
|
||||
};
|
||||
}, []);
|
||||
|
||||
// ======== Render ========
|
||||
return (
|
||||
<Line
|
||||
points={curvePoints}
|
||||
|
||||
@@ -18,335 +18,6 @@ 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];
|
||||
@@ -354,25 +25,29 @@ type PointData = {
|
||||
handleA: [number, number, number] | null;
|
||||
handleB: [number, number, number] | null;
|
||||
};
|
||||
|
||||
interface PathDataInterface {
|
||||
pathId: string;
|
||||
pathPoints: [PointData, PointData];
|
||||
}
|
||||
|
||||
type PathData = PathDataInterface[];
|
||||
|
||||
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 };
|
||||
}, []);
|
||||
@@ -516,6 +191,7 @@ export default function PreDefinedPath() {
|
||||
}, [pointsGroups]);
|
||||
const [shortestPath, setShortestPath] = useState<number[]>([]);
|
||||
const [shortestDistance, setShortestDistance] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
const domElement = gl.domElement;
|
||||
domElement.addEventListener("contextmenu", (e) => e.preventDefault());
|
||||
@@ -537,6 +213,7 @@ export default function PreDefinedPath() {
|
||||
point={point}
|
||||
groupIndex={gIdx}
|
||||
pointIndex={idx}
|
||||
// mainShapeOnly={mainShapeOnly}
|
||||
setPointsGroups={setPointsGroups}
|
||||
pointsGroups={pointsGroups} // <-- pass the full groups
|
||||
selected={selected}
|
||||
@@ -570,52 +247,13 @@ export default function PreDefinedPath() {
|
||||
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) => {
|
||||
insertPoint={(index, pointVec) => {
|
||||
setPointsGroups((prev) => {
|
||||
const newGroups = [...prev];
|
||||
const group = [...newGroups[gIdx]];
|
||||
const groupToSplit = newGroups[gIdx];
|
||||
|
||||
// Create the new point
|
||||
const newPoint: PointData = {
|
||||
const newPoint = {
|
||||
pointId: crypto.randomUUID(),
|
||||
position: pointVec.toArray() as [
|
||||
number,
|
||||
@@ -627,9 +265,24 @@ export default function PreDefinedPath() {
|
||||
handleB: null,
|
||||
};
|
||||
|
||||
// Find best place to insert based on index (insert between points)
|
||||
group.splice(index, 0, newPoint); // insert at index
|
||||
newGroups[gIdx] = group;
|
||||
// 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;
|
||||
});
|
||||
}}
|
||||
@@ -643,75 +296,3 @@ export default function PreDefinedPath() {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// 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]
|
||||
// );
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
type PointData = {
|
||||
pointId: string;
|
||||
position: [number, number, number];
|
||||
isCurved: boolean;
|
||||
handleA: [number, number, number] | null;
|
||||
handleB: [number, number, number] | null;
|
||||
};
|
||||
|
||||
interface PathDataInterface {
|
||||
pathId: string;
|
||||
pathPoints: [PointData, PointData];
|
||||
}
|
||||
|
||||
type PathData = PathDataInterface[];
|
||||
|
||||
function distance(a: PointData, b: PointData): number {
|
||||
const dx = a.position[0] - b.position[0];
|
||||
const dy = a.position[1] - b.position[1];
|
||||
const dz = a.position[2] - b.position[2];
|
||||
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
|
||||
type AStarResult = {
|
||||
path: PointData[]; // ordered list of points along the path
|
||||
distance: number; // total distance
|
||||
};
|
||||
|
||||
export function aStarShortestPath(
|
||||
startId: string,
|
||||
goalId: string,
|
||||
points: PointData[],
|
||||
paths: PathData
|
||||
): AStarResult | null {
|
||||
const openSet = new Set<string>([startId]);
|
||||
const cameFrom: Record<string, string | null> = {};
|
||||
const gScore: Record<string, number> = {};
|
||||
const fScore: Record<string, number> = {};
|
||||
|
||||
points.forEach((p) => {
|
||||
gScore[p.pointId] = Infinity;
|
||||
fScore[p.pointId] = Infinity;
|
||||
cameFrom[p.pointId] = null;
|
||||
});
|
||||
|
||||
gScore[startId] = 0;
|
||||
fScore[startId] = 0;
|
||||
|
||||
while (openSet.size > 0) {
|
||||
// Pick node with lowest fScore
|
||||
let current = [...openSet].reduce((a, b) =>
|
||||
fScore[a] < fScore[b] ? a : b
|
||||
);
|
||||
|
||||
if (current === goalId) {
|
||||
// ✅ Reconstruct path
|
||||
const path: PointData[] = [];
|
||||
let node: string | null = current;
|
||||
while (node) {
|
||||
const pt = points.find((p) => p.pointId === node);
|
||||
if (pt) path.unshift(pt);
|
||||
node = cameFrom[node];
|
||||
}
|
||||
|
||||
return {
|
||||
path,
|
||||
distance: gScore[goalId],
|
||||
};
|
||||
}
|
||||
|
||||
openSet.delete(current);
|
||||
|
||||
// Find neighbors from paths
|
||||
const neighbors = paths.filter((p) =>
|
||||
p.pathPoints.some((pt) => pt.pointId === current)
|
||||
);
|
||||
|
||||
for (let n of neighbors) {
|
||||
const [p1, p2] = n.pathPoints;
|
||||
const neighbor = p1.pointId === current ? p2 : p1;
|
||||
|
||||
const tentativeG =
|
||||
gScore[current] +
|
||||
distance(points.find((pt) => pt.pointId === current)!, neighbor);
|
||||
|
||||
if (tentativeG < gScore[neighbor.pointId]) {
|
||||
cameFrom[neighbor.pointId] = current;
|
||||
gScore[neighbor.pointId] = tentativeG;
|
||||
fScore[neighbor.pointId] = tentativeG; // no heuristic for now
|
||||
openSet.add(neighbor.pointId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // no path found
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export function handleContextMenu(
|
||||
evt: MouseEvent,
|
||||
setCurrentTempPath: (val: any[]) => void
|
||||
) {
|
||||
evt.preventDefault();
|
||||
setCurrentTempPath([]);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import * as THREE from "three";
|
||||
type PointData = {
|
||||
pointId: string;
|
||||
position: [number, number, number];
|
||||
isCurved: boolean;
|
||||
handleA: [number, number, number] | null;
|
||||
handleB: [number, number, number] | null;
|
||||
};
|
||||
|
||||
interface PathDataInterface {
|
||||
pathId: string;
|
||||
pathPoints: [PointData, PointData]; // always two points
|
||||
}
|
||||
|
||||
type PathData = PathDataInterface[];
|
||||
export const POLYGON_CLOSE_THRESHOLD = 0.3;
|
||||
export const SNAP_POINT_THRESHOLD = 0.2;
|
||||
export const SNAP_LINE_THRESHOLD = 0.2;
|
||||
export function handleMouseClick({
|
||||
evt,
|
||||
isDragging,
|
||||
raycaster,
|
||||
plane,
|
||||
pointer,
|
||||
currentTempPath,
|
||||
setCurrentTempPath,
|
||||
pathPointsList,
|
||||
allPaths,
|
||||
setAllPaths,
|
||||
addPointToCurrentTemp,
|
||||
}: {
|
||||
evt: MouseEvent;
|
||||
isDragging: { current: boolean };
|
||||
raycaster: THREE.Raycaster;
|
||||
plane: THREE.Plane;
|
||||
pointer: { x: number; y: number };
|
||||
currentTempPath: any[];
|
||||
setCurrentTempPath: (val: any[]) => void;
|
||||
pathPointsList: any[];
|
||||
allPaths: any[];
|
||||
setAllPaths: React.Dispatch<React.SetStateAction<PathData>>;
|
||||
addPointToCurrentTemp: (point: any) => void;
|
||||
}) {
|
||||
if (isDragging.current) return;
|
||||
if (evt.ctrlKey || evt.shiftKey) return;
|
||||
|
||||
const intersectPoint = new THREE.Vector3();
|
||||
const pos = raycaster.ray.intersectPlane(plane, intersectPoint);
|
||||
if (!pos) return;
|
||||
|
||||
let clickedPoint = new THREE.Vector3(pos.x, pos.y, pos.z);
|
||||
|
||||
let snapPoint: any = null;
|
||||
for (let p of pathPointsList) {
|
||||
const pVec = new THREE.Vector3(...p.position);
|
||||
if (pVec.distanceTo(clickedPoint) < SNAP_POINT_THRESHOLD) {
|
||||
snapPoint = p;
|
||||
clickedPoint = pVec;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let newPoint = snapPoint ?? {
|
||||
pointId: THREE.MathUtils.generateUUID(),
|
||||
position: [clickedPoint.x, 0, clickedPoint.z],
|
||||
isCurved: false,
|
||||
handleA: null,
|
||||
handleB: null,
|
||||
};
|
||||
|
||||
if (currentTempPath.length > 2) {
|
||||
const firstVec = new THREE.Vector3(...currentTempPath[0].position);
|
||||
if (firstVec.distanceTo(clickedPoint) < POLYGON_CLOSE_THRESHOLD) {
|
||||
const closingPoint = { ...currentTempPath[0] };
|
||||
console.log("closingPoint: ", closingPoint);
|
||||
addPointToCurrentTemp(closingPoint);
|
||||
setCurrentTempPath([]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const getNearestPointOnLine = (
|
||||
a: THREE.Vector3,
|
||||
b: THREE.Vector3,
|
||||
p: THREE.Vector3
|
||||
) => {
|
||||
const ab = new THREE.Vector3().subVectors(b, a);
|
||||
const t = Math.max(
|
||||
0,
|
||||
Math.min(1, p.clone().sub(a).dot(ab) / ab.lengthSq())
|
||||
);
|
||||
return a.clone().add(ab.multiplyScalar(t));
|
||||
};
|
||||
|
||||
for (let path of allPaths) {
|
||||
const a = new THREE.Vector3(...path.pathPoints[0].position);
|
||||
const b = new THREE.Vector3(...path.pathPoints[1].position);
|
||||
const closest = getNearestPointOnLine(a, b, clickedPoint);
|
||||
|
||||
if (closest.distanceTo(clickedPoint) < SNAP_LINE_THRESHOLD) {
|
||||
const splitPoint = {
|
||||
pointId: THREE.MathUtils.generateUUID(),
|
||||
position: closest.toArray() as [number, number, number],
|
||||
isCurved: false,
|
||||
handleA: null,
|
||||
handleB: null,
|
||||
};
|
||||
|
||||
setAllPaths((prev: any) =>
|
||||
prev
|
||||
.filter((pa: any) => pa.pathId !== path.pathId)
|
||||
.concat([
|
||||
{
|
||||
pathId: THREE.MathUtils.generateUUID(),
|
||||
pathPoints: [path.pathPoints[0], splitPoint],
|
||||
},
|
||||
{
|
||||
pathId: THREE.MathUtils.generateUUID(),
|
||||
pathPoints: [splitPoint, path.pathPoints[1]],
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
addPointToCurrentTemp(splitPoint);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
addPointToCurrentTemp(newPoint);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export function handleMouseDown(
|
||||
evt: MouseEvent,
|
||||
isLeftClickDown: { current: boolean },
|
||||
isDragging: { current: boolean }
|
||||
) {
|
||||
if (evt.button === 0) {
|
||||
if (evt.ctrlKey || evt.shiftKey) return;
|
||||
isLeftClickDown.current = true;
|
||||
isDragging.current = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export function handleMouseMove(
|
||||
isLeftClickDown: { current: boolean },
|
||||
isDragging: { current: boolean }
|
||||
) {
|
||||
if (isLeftClickDown.current) isDragging.current = true;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export function handleMouseUp(
|
||||
evt: MouseEvent,
|
||||
isLeftClickDown: { current: boolean }
|
||||
) {
|
||||
if (evt.button === 0) isLeftClickDown.current = false;
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
import * as THREE from "three";
|
||||
|
||||
export const POLYGON_CLOSE_THRESHOLD = 0.3;
|
||||
export const SNAP_POINT_THRESHOLD = 0.2;
|
||||
export const SNAP_LINE_THRESHOLD = 0.2;
|
||||
type PointData = {
|
||||
pointId: string;
|
||||
position: [number, number, number];
|
||||
isCurved: boolean;
|
||||
handleA: [number, number, number] | null;
|
||||
handleB: [number, number, number] | null;
|
||||
};
|
||||
|
||||
interface PathDataInterface {
|
||||
pathId: string;
|
||||
pathPoints: [PointData, PointData]; // always two points
|
||||
}
|
||||
|
||||
type PathData = PathDataInterface[];
|
||||
|
||||
export function handleMouseDown(
|
||||
evt: MouseEvent,
|
||||
isLeftClickDown: React.MutableRefObject<boolean>,
|
||||
isDragging: React.MutableRefObject<boolean>
|
||||
) {
|
||||
if (evt.button === 0) {
|
||||
if (evt.ctrlKey || evt.shiftKey) return;
|
||||
isLeftClickDown.current = true;
|
||||
isDragging.current = false;
|
||||
}
|
||||
}
|
||||
|
||||
export function handleMouseUp(
|
||||
evt: MouseEvent,
|
||||
isLeftClickDown: React.MutableRefObject<boolean>
|
||||
) {
|
||||
if (evt.button === 0) isLeftClickDown.current = false;
|
||||
}
|
||||
|
||||
export function handleMouseMove(
|
||||
isLeftClickDown: React.MutableRefObject<boolean>,
|
||||
isDragging: React.MutableRefObject<boolean>
|
||||
) {
|
||||
if (isLeftClickDown.current) isDragging.current = true;
|
||||
}
|
||||
|
||||
export function handleMouseClick({
|
||||
evt,
|
||||
isDragging,
|
||||
raycaster,
|
||||
plane,
|
||||
pointer,
|
||||
currentTempPath,
|
||||
setCurrentTempPath,
|
||||
pathPointsList,
|
||||
allPaths,
|
||||
setAllPaths,
|
||||
addPointToCurrentTemp,
|
||||
}: {
|
||||
evt: MouseEvent;
|
||||
isDragging: React.MutableRefObject<boolean>;
|
||||
raycaster: THREE.Raycaster;
|
||||
plane: THREE.Plane;
|
||||
pointer: { x: number; y: number };
|
||||
currentTempPath: any[];
|
||||
setCurrentTempPath: (val: any[]) => void;
|
||||
pathPointsList: any[];
|
||||
allPaths: PathData;
|
||||
setAllPaths: React.Dispatch<React.SetStateAction<PathData>>;
|
||||
addPointToCurrentTemp: (point: any) => void;
|
||||
}) {
|
||||
if (isDragging.current) return;
|
||||
if (evt.ctrlKey || evt.shiftKey) return;
|
||||
|
||||
const intersectPoint = new THREE.Vector3();
|
||||
const pos = raycaster.ray.intersectPlane(plane, intersectPoint);
|
||||
if (!pos) return;
|
||||
|
||||
let clickedPoint = new THREE.Vector3(pos.x, 0, pos.z); // force y = 0
|
||||
|
||||
let snapPoint: any = null;
|
||||
for (let p of pathPointsList) {
|
||||
const pVec = new THREE.Vector3(p.position[0], 0, p.position[2]); // force y = 0
|
||||
if (pVec.distanceTo(clickedPoint) < SNAP_POINT_THRESHOLD) {
|
||||
snapPoint = {
|
||||
...p,
|
||||
position: [p.position[0], 0, p.position[2]], // force y = 0
|
||||
};
|
||||
clickedPoint = pVec;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let newPoint = snapPoint ?? {
|
||||
pointId: THREE.MathUtils.generateUUID(),
|
||||
position: [clickedPoint.x, 0, clickedPoint.z], // y = 0
|
||||
isCurved: false,
|
||||
handleA: null,
|
||||
handleB: null,
|
||||
};
|
||||
|
||||
if (currentTempPath.length > 2) {
|
||||
const firstVec = new THREE.Vector3(
|
||||
currentTempPath[0].position[0],
|
||||
0,
|
||||
currentTempPath[0].position[2]
|
||||
); // y = 0
|
||||
if (firstVec.distanceTo(clickedPoint) < POLYGON_CLOSE_THRESHOLD) {
|
||||
const closingPoint = {
|
||||
...currentTempPath[0],
|
||||
position: [
|
||||
currentTempPath[0].position[0],
|
||||
0,
|
||||
currentTempPath[0].position[2],
|
||||
], // y = 0
|
||||
};
|
||||
addPointToCurrentTemp(closingPoint);
|
||||
setCurrentTempPath([]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const getNearestPointOnLine = (
|
||||
a: THREE.Vector3,
|
||||
b: THREE.Vector3,
|
||||
p: THREE.Vector3
|
||||
) => {
|
||||
const ab = new THREE.Vector3().subVectors(b, a);
|
||||
const t = Math.max(
|
||||
0,
|
||||
Math.min(1, p.clone().sub(a).dot(ab) / ab.lengthSq())
|
||||
);
|
||||
return a.clone().add(ab.multiplyScalar(t));
|
||||
};
|
||||
|
||||
for (let path of allPaths) {
|
||||
const a = new THREE.Vector3(
|
||||
path.pathPoints[0].position[0],
|
||||
0,
|
||||
path.pathPoints[0].position[2]
|
||||
);
|
||||
const b = new THREE.Vector3(
|
||||
path.pathPoints[1].position[0],
|
||||
0,
|
||||
path.pathPoints[1].position[2]
|
||||
);
|
||||
const closest = getNearestPointOnLine(a, b, clickedPoint);
|
||||
closest.y = 0; // force y = 0
|
||||
|
||||
if (closest.distanceTo(clickedPoint) < SNAP_LINE_THRESHOLD) {
|
||||
const splitPoint = {
|
||||
pointId: THREE.MathUtils.generateUUID(),
|
||||
position: [closest.x, 0, closest.z], // y = 0
|
||||
isCurved: false,
|
||||
handleA: null,
|
||||
handleB: null,
|
||||
};
|
||||
|
||||
setAllPaths((prev) =>
|
||||
prev
|
||||
.filter((pa) => pa.pathId !== path.pathId)
|
||||
.concat([
|
||||
{
|
||||
pathId: THREE.MathUtils.generateUUID(),
|
||||
pathPoints: [
|
||||
{
|
||||
...path.pathPoints[0],
|
||||
position: [
|
||||
path.pathPoints[0].position[0],
|
||||
0,
|
||||
path.pathPoints[0].position[2],
|
||||
] as [number, number, number],
|
||||
},
|
||||
splitPoint,
|
||||
] as [PointData, PointData],
|
||||
},
|
||||
{
|
||||
pathId: THREE.MathUtils.generateUUID(),
|
||||
pathPoints: [
|
||||
splitPoint,
|
||||
{
|
||||
...path.pathPoints[1],
|
||||
position: [
|
||||
path.pathPoints[1].position[0],
|
||||
0,
|
||||
path.pathPoints[1].position[2],
|
||||
] as [number, number, number],
|
||||
},
|
||||
] as [PointData, PointData],
|
||||
},
|
||||
])
|
||||
);
|
||||
console.log("path.pathPoints[1]: ", path.pathPoints);
|
||||
|
||||
addPointToCurrentTemp(splitPoint);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
addPointToCurrentTemp(newPoint);
|
||||
}
|
||||
|
||||
export function handleContextMenu(
|
||||
evt: MouseEvent,
|
||||
setCurrentTempPath: (val: any[]) => void
|
||||
) {
|
||||
evt.preventDefault();
|
||||
setCurrentTempPath([]);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { useMemo, useRef } from "react";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import * as THREE from "three";
|
||||
import { Line } from "@react-three/drei";
|
||||
|
||||
type PointData = {
|
||||
pointId: string;
|
||||
position: [number, number, number];
|
||||
isCurved: boolean;
|
||||
handleA: [number, number, number] | null;
|
||||
handleB: [number, number, number] | null;
|
||||
};
|
||||
|
||||
interface PathDataInterface {
|
||||
pathId: string;
|
||||
pathPoints: [PointData, PointData];
|
||||
}
|
||||
|
||||
type PathData = PathDataInterface[];
|
||||
|
||||
interface LineSegmentProps {
|
||||
index: number;
|
||||
paths: PathDataInterface[];
|
||||
setPaths: React.Dispatch<React.SetStateAction<PathData>>;
|
||||
insertPoint?: (pathIndex: number, point: THREE.Vector3) => void;
|
||||
}
|
||||
|
||||
export default function LineSegment({
|
||||
index,
|
||||
paths,
|
||||
setPaths,
|
||||
insertPoint,
|
||||
}: LineSegmentProps) {
|
||||
const { gl, raycaster, camera, controls } = useThree();
|
||||
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
|
||||
|
||||
const segmentPoints = useMemo(() => {
|
||||
if (!paths[index]) return [];
|
||||
|
||||
const [startPoint, endPoint] = paths[index].pathPoints;
|
||||
|
||||
const start = new THREE.Vector3(...startPoint.position);
|
||||
const end = new THREE.Vector3(...endPoint.position);
|
||||
|
||||
const useCurve =
|
||||
(startPoint.isCurved && startPoint.handleB) ||
|
||||
(endPoint.isCurved && endPoint.handleA);
|
||||
|
||||
const hB = startPoint.handleB
|
||||
? new THREE.Vector3(...startPoint.handleB)
|
||||
: start;
|
||||
const hA = endPoint.handleA ? new THREE.Vector3(...endPoint.handleA) : end;
|
||||
|
||||
const curve = useCurve
|
||||
? new THREE.CubicBezierCurve3(start, hB, hA, end)
|
||||
: new THREE.LineCurve3(start, end);
|
||||
|
||||
return curve.getPoints(useCurve ? 100 : 2);
|
||||
}, [paths, index]);
|
||||
|
||||
const curvePoints = useMemo(() => {
|
||||
if (!paths || index >= paths.length) return [];
|
||||
|
||||
const path = paths[index];
|
||||
const [current, next] = path.pathPoints;
|
||||
|
||||
const start = new THREE.Vector3(...current.position);
|
||||
const end = new THREE.Vector3(...next.position);
|
||||
|
||||
const useCurve =
|
||||
(current.isCurved && current.handleB) || (next.isCurved && next.handleA);
|
||||
|
||||
const hB = current.handleB ? new THREE.Vector3(...current.handleB) : start;
|
||||
const hA = next.handleA ? new THREE.Vector3(...next.handleA) : end;
|
||||
|
||||
const curve = useCurve
|
||||
? new THREE.CubicBezierCurve3(start, hB, hA, end)
|
||||
: new THREE.LineCurve3(start, end);
|
||||
|
||||
return curve.getPoints(useCurve ? 100 : 2);
|
||||
}, [paths, index]);
|
||||
|
||||
return <Line points={curvePoints} color="purple" lineWidth={3} />;
|
||||
}
|
||||
@@ -0,0 +1,815 @@
|
||||
// import { Line } from "@react-three/drei";
|
||||
// import { useFrame, useThree } from "@react-three/fiber";
|
||||
// import React, { useRef, useState } from "react";
|
||||
// import * as THREE from "three";
|
||||
|
||||
// /** --- Types --- */
|
||||
// type PointData = {
|
||||
// pointId: string;
|
||||
// position: [number, number, number];
|
||||
// isCurved: boolean;
|
||||
// handleA: [number, number, number] | null;
|
||||
// handleB: [number, number, number] | null;
|
||||
// };
|
||||
|
||||
// interface PathDataInterface {
|
||||
// pathId: string;
|
||||
// pathPoints: [PointData, PointData]; // always two points
|
||||
// }
|
||||
|
||||
// type PathData = PathDataInterface[];
|
||||
|
||||
// interface PointHandleProps {
|
||||
// point: PointData;
|
||||
// pointIndex: number;
|
||||
// points: PointData[];
|
||||
// setPoints: React.Dispatch<React.SetStateAction<PointData[]>>;
|
||||
// setPaths: React.Dispatch<React.SetStateAction<PathData>>;
|
||||
// paths: PathData;
|
||||
// selected: number[];
|
||||
// setSelected: React.Dispatch<React.SetStateAction<number[]>>;
|
||||
// setShortestPath: React.Dispatch<React.SetStateAction<PathData>>;
|
||||
// }
|
||||
|
||||
// /** --- Math helpers --- */
|
||||
// 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) edges.push(edge);
|
||||
// // else {
|
||||
// // const pa = byId.get(a)!;
|
||||
// // const pb = byId.get(b)!;
|
||||
// // edges.push({
|
||||
// // pathId: `synthetic-${a}-${b}`,
|
||||
// // pathPoints: [pa, pb],
|
||||
// // });
|
||||
// // }
|
||||
// // }
|
||||
// //
|
||||
|
||||
// // return 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) {
|
||||
// // Ensure correct order in 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;
|
||||
// }
|
||||
|
||||
// /** --- React Component --- */
|
||||
// export default function PointHandlers({
|
||||
// point,
|
||||
// pointIndex,
|
||||
// points,
|
||||
// setPoints,
|
||||
// setPaths,
|
||||
// paths,
|
||||
// selected,
|
||||
// setSelected,
|
||||
// setShortestPath,
|
||||
// }: PointHandleProps) {
|
||||
// const meshRef = useRef<THREE.Mesh>(null);
|
||||
// const handleARef = useRef<THREE.Mesh>(null);
|
||||
// const handleBRef = useRef<THREE.Mesh>(null);
|
||||
// const lineRef = useRef<any>(null!);
|
||||
// const { camera, gl, controls } = useThree();
|
||||
|
||||
// const [dragging, setDragging] = useState<
|
||||
// null | "main" | "handleA" | "handleB"
|
||||
// >(null);
|
||||
// const dragOffset = useRef(new THREE.Vector3());
|
||||
// const [shortestEdges, setShortestEdges] = useState<PathData>([]);
|
||||
|
||||
// /** Click handling */
|
||||
// const onPointClick = (e: any) => {
|
||||
// e.stopPropagation();
|
||||
// if (e.shiftKey) {
|
||||
// setSelected((prev) => {
|
||||
// if (prev.length === 0) return [pointIndex];
|
||||
// else if (prev.length === 1) {
|
||||
// const p1 = points[prev[0]];
|
||||
// const p2 = points[pointIndex];
|
||||
// const result = aStarShortestPath(
|
||||
// p1.pointId,
|
||||
// p2.pointId,
|
||||
// points,
|
||||
// paths
|
||||
// );
|
||||
// if (result) {
|
||||
// const edges = nodePathToEdges(result.pointIds, points, paths);
|
||||
// setShortestEdges(edges);
|
||||
// setShortestPath(edges);
|
||||
// } else {
|
||||
// setShortestEdges([]);
|
||||
// }
|
||||
// return [prev[0], pointIndex];
|
||||
// } else {
|
||||
// setShortestEdges([]);
|
||||
// return [pointIndex];
|
||||
// }
|
||||
// });
|
||||
// } else if (e.ctrlKey) {
|
||||
// setPoints((prev) => {
|
||||
// const updated = [...prev];
|
||||
// const p = { ...updated[pointIndex] };
|
||||
|
||||
// if (!p.handleA && !p.handleB) {
|
||||
// p.handleA = [p.position[0] + 1, p.position[1], p.position[2]];
|
||||
// p.handleB = [p.position[0] - 1, p.position[1], p.position[2]];
|
||||
// p.isCurved = true;
|
||||
// } else {
|
||||
// p.handleA = null;
|
||||
// p.handleB = null;
|
||||
// p.isCurved = false;
|
||||
// }
|
||||
|
||||
// updated[pointIndex] = p;
|
||||
// return updated;
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
// /** 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)) return;
|
||||
|
||||
// const newPos = intersection.add(dragOffset.current);
|
||||
|
||||
// setPoints((prevPoints) => {
|
||||
// const updatedPoints = [...prevPoints];
|
||||
// const point = { ...updatedPoints[pointIndex] };
|
||||
|
||||
// if (dragging === "main") {
|
||||
// // Calculate delta movement
|
||||
// const delta = newPos
|
||||
// .clone()
|
||||
// .sub(new THREE.Vector3().fromArray(point.position));
|
||||
|
||||
// // Move main point
|
||||
// point.position = newPos.toArray() as [number, number, number];
|
||||
|
||||
// // Move handles with main point
|
||||
// if (point.handleA)
|
||||
// point.handleA = new THREE.Vector3()
|
||||
// .fromArray(point.handleA)
|
||||
// .add(delta)
|
||||
// .toArray() as [number, number, number];
|
||||
// if (point.handleB)
|
||||
// point.handleB = new THREE.Vector3()
|
||||
// .fromArray(point.handleB)
|
||||
// .add(delta)
|
||||
// .toArray() as [number, number, number];
|
||||
// } else {
|
||||
// // Dragging a handle
|
||||
// point[dragging] = newPos.toArray() as [number, number, number];
|
||||
|
||||
// if (point.isCurved) {
|
||||
// // Mirror the opposite handle
|
||||
// const mainPos = new THREE.Vector3().fromArray(point.position);
|
||||
// const thisHandle = new THREE.Vector3().fromArray(point[dragging]!);
|
||||
// const mirrorHandle = mainPos
|
||||
// .clone()
|
||||
// .sub(thisHandle.clone().sub(mainPos));
|
||||
|
||||
// if (dragging === "handleA")
|
||||
// point.handleB = mirrorHandle.toArray() as [number, number, number];
|
||||
// if (dragging === "handleB")
|
||||
// point.handleA = mirrorHandle.toArray() as [number, number, number];
|
||||
// }
|
||||
// }
|
||||
|
||||
// updatedPoints[pointIndex] = point;
|
||||
|
||||
// // Update all paths that include this point
|
||||
// setPaths((prevPaths: any) =>
|
||||
// prevPaths.map((path: any) => {
|
||||
// const updatedPathPoints = path.pathPoints.map((p: any) =>
|
||||
// p.pointId === point.pointId ? point : p
|
||||
// );
|
||||
// return { ...path, pathPoints: updatedPathPoints };
|
||||
// })
|
||||
// );
|
||||
|
||||
// return updatedPoints;
|
||||
// });
|
||||
// });
|
||||
|
||||
// /** Update line between handles */
|
||||
// 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>
|
||||
|
||||
// {/* Curve handles */}
|
||||
// {point.isCurved && point.handleA && point.handleB && (
|
||||
// <>
|
||||
// <Line
|
||||
// ref={lineRef}
|
||||
// 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>
|
||||
// </>
|
||||
// )}
|
||||
|
||||
// {/* Draw connected paths */}
|
||||
|
||||
// {/* Highlight shortest path */}
|
||||
// {shortestEdges.map((edge) => (
|
||||
// <Line
|
||||
// key={`sp-${edge.pathId}`}
|
||||
// points={edge.pathPoints.map((p) => p.position)}
|
||||
// color="yellow"
|
||||
// lineWidth={3}
|
||||
// />
|
||||
// ))}
|
||||
// </>
|
||||
// );
|
||||
// }
|
||||
|
||||
/** --- Types --- */
|
||||
import { Line } from "@react-three/drei";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import React, { useRef, useState } from "react";
|
||||
import * as THREE from "three";
|
||||
|
||||
/** --- Types --- */
|
||||
type PointData = {
|
||||
pointId: string;
|
||||
position: [number, number, number];
|
||||
isCurved: boolean;
|
||||
handleA: [number, number, number] | null;
|
||||
handleB: [number, number, number] | null;
|
||||
};
|
||||
|
||||
interface PathDataInterface {
|
||||
pathId: string;
|
||||
pathPoints: [PointData, PointData]; // always two points
|
||||
}
|
||||
|
||||
type PathData = PathDataInterface[];
|
||||
|
||||
interface PointHandleProps {
|
||||
point: PointData;
|
||||
pointIndex: number;
|
||||
points: PointData[];
|
||||
setPoints: React.Dispatch<React.SetStateAction<PointData[]>>;
|
||||
setPaths: React.Dispatch<React.SetStateAction<PathData>>;
|
||||
paths: PathData;
|
||||
selected: number[];
|
||||
setSelected: React.Dispatch<React.SetStateAction<number[]>>;
|
||||
setShortestPath: React.Dispatch<React.SetStateAction<PathData>>;
|
||||
}
|
||||
|
||||
/** --- Math helpers --- */
|
||||
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;
|
||||
}
|
||||
|
||||
/** --- React Component --- */
|
||||
export default function PointHandlersRenamed({
|
||||
point,
|
||||
pointIndex,
|
||||
points,
|
||||
setPoints,
|
||||
setPaths,
|
||||
paths,
|
||||
selected,
|
||||
setSelected,
|
||||
setShortestPath,
|
||||
}: PointHandleProps) {
|
||||
const meshRef = useRef<THREE.Mesh>(null);
|
||||
const handleARef = useRef<THREE.Mesh>(null);
|
||||
const handleBRef = useRef<THREE.Mesh>(null);
|
||||
const lineRef = useRef<any>(null);
|
||||
const { camera, gl, controls } = useThree();
|
||||
|
||||
const [dragging, setDragging] = useState<
|
||||
null | "main" | "handleA" | "handleB"
|
||||
>(null);
|
||||
const dragOffset = useRef(new THREE.Vector3());
|
||||
const [shortestEdges, setShortestEdges] = useState<PathData>([]);
|
||||
|
||||
/** Click handling */
|
||||
const onPointClick = (e: any) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (e.shiftKey) {
|
||||
setSelected((prev) => {
|
||||
if (prev.length === 0) return [pointIndex];
|
||||
if (prev.length === 1) {
|
||||
// defer shortest path calculation
|
||||
setTimeout(() => {
|
||||
const p1 = points[prev[0]];
|
||||
const p2 = points[pointIndex];
|
||||
const result = aStarShortestPath(
|
||||
p1.pointId,
|
||||
p2.pointId,
|
||||
points,
|
||||
paths
|
||||
);
|
||||
if (result) {
|
||||
const edges = nodePathToEdges(result.pointIds, points, paths);
|
||||
setShortestEdges(edges);
|
||||
setShortestPath(edges);
|
||||
} else {
|
||||
setShortestEdges([]);
|
||||
}
|
||||
}, 0);
|
||||
return [prev[0], pointIndex];
|
||||
}
|
||||
return [pointIndex];
|
||||
});
|
||||
} else if (e.ctrlKey) {
|
||||
setPoints((prev) => {
|
||||
const updated = [...prev];
|
||||
const p = { ...updated[pointIndex] };
|
||||
|
||||
if (!p.handleA && !p.handleB) {
|
||||
p.handleA = [p.position[0] + 1, 0, p.position[2]];
|
||||
p.handleB = [p.position[0] - 1, 0, p.position[2]];
|
||||
p.isCurved = true;
|
||||
} else {
|
||||
p.handleA = null;
|
||||
p.handleB = null;
|
||||
p.isCurved = false;
|
||||
}
|
||||
|
||||
updated[pointIndex] = p;
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/** 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;
|
||||
};
|
||||
|
||||
/** Update position in useFrame */
|
||||
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)) return;
|
||||
const newPos = intersection.add(dragOffset.current);
|
||||
|
||||
setPoints((prevPoints) => {
|
||||
const updatedPoints = [...prevPoints];
|
||||
const p = { ...updatedPoints[pointIndex] };
|
||||
|
||||
if (dragging === "main") {
|
||||
const delta = newPos
|
||||
.clone()
|
||||
.sub(new THREE.Vector3().fromArray(p.position));
|
||||
p.position = [newPos.x, 0, newPos.z];
|
||||
if (p.handleA)
|
||||
p.handleA = new THREE.Vector3()
|
||||
.fromArray(p.handleA)
|
||||
.add(delta)
|
||||
.toArray() as [number, number, number];
|
||||
if (p.handleB)
|
||||
p.handleB = new THREE.Vector3()
|
||||
.fromArray(p.handleB)
|
||||
.add(delta)
|
||||
.toArray() as [number, number, number];
|
||||
} else {
|
||||
p[dragging] = [newPos.x, 0, newPos.z];
|
||||
if (p.isCurved) {
|
||||
const mainPos = new THREE.Vector3().fromArray(p.position);
|
||||
const thisHandle = new THREE.Vector3().fromArray(p[dragging]!);
|
||||
const mirrorHandle = mainPos
|
||||
.clone()
|
||||
.sub(thisHandle.clone().sub(mainPos));
|
||||
if (dragging === "handleA")
|
||||
p.handleB = mirrorHandle.toArray() as [number, number, number];
|
||||
if (dragging === "handleB")
|
||||
p.handleA = mirrorHandle.toArray() as [number, number, number];
|
||||
}
|
||||
}
|
||||
|
||||
updatedPoints[pointIndex] = p;
|
||||
|
||||
setPaths((prevPaths: any) =>
|
||||
prevPaths.map((path: any) => ({
|
||||
...path,
|
||||
pathPoints: path.pathPoints.map((pp: any) =>
|
||||
pp.pointId === p.pointId ? p : pp
|
||||
),
|
||||
}))
|
||||
);
|
||||
|
||||
return updatedPoints;
|
||||
});
|
||||
});
|
||||
|
||||
/** Update line between handles */
|
||||
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>
|
||||
|
||||
{/* Curve handles */}
|
||||
{point.isCurved && point.handleA && point.handleB && (
|
||||
<>
|
||||
<Line
|
||||
ref={lineRef}
|
||||
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 */}
|
||||
{shortestEdges.map((edge) => (
|
||||
<Line
|
||||
key={`sp-${edge.pathId}`}
|
||||
points={edge.pathPoints.map((p) => p.position)}
|
||||
color="yellow"
|
||||
lineWidth={3}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,763 @@
|
||||
import * as THREE from "three";
|
||||
import { useRef, useState, useMemo, useEffect } from "react";
|
||||
import { useThree, useFrame } from "@react-three/fiber";
|
||||
import { Line } from "@react-three/drei";
|
||||
import { useSceneContext } from "../../../scene/sceneContext";
|
||||
import {
|
||||
useAnimationPlaySpeed,
|
||||
usePlayButtonStore,
|
||||
} from "../../../../store/usePlayButtonStore";
|
||||
import PointHandles from "./pointHandlers";
|
||||
import LineSegment from "./lineSegment";
|
||||
import {
|
||||
handleContextMenu,
|
||||
handleMouseClick,
|
||||
handleMouseDown,
|
||||
handleMouseMove,
|
||||
handleMouseUp,
|
||||
} from "./functions/pathMouseHandler";
|
||||
|
||||
type PointData = {
|
||||
pointId: string;
|
||||
position: [number, number, number];
|
||||
isCurved: boolean;
|
||||
handleA: [number, number, number] | null;
|
||||
handleB: [number, number, number] | null;
|
||||
};
|
||||
|
||||
interface PathDataInterface {
|
||||
pathId: string;
|
||||
pathPoints: [PointData, PointData];
|
||||
}
|
||||
|
||||
type PathData = PathDataInterface[];
|
||||
type SegmentPoint = {
|
||||
position: THREE.Vector3;
|
||||
originalPoint?: PointData;
|
||||
};
|
||||
// ///////////////////////////////////logic wise crct code (path and points)
|
||||
// export default function StructuredPath() {
|
||||
// const { scene, camera, raycaster, gl, pointer } = useThree();
|
||||
// const plane = useMemo(
|
||||
// () => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0),
|
||||
// []
|
||||
// );
|
||||
// const { speed } = useAnimationPlaySpeed();
|
||||
// const { assetStore } = useSceneContext();
|
||||
// const { assets } = assetStore();
|
||||
// const [points, setPoints] = useState<PointData[]>([]);
|
||||
// const [paths, setPaths] = useState<PathData>([]);
|
||||
// const [shortestPath, setShortestPath] = useState<PathData>([]);
|
||||
// useEffect(() => {
|
||||
// console.log("paths: ", paths);
|
||||
// }, [paths]);
|
||||
// const [tempPath, setTempPath] = useState<PointData[]>([]);
|
||||
// const [mousePos, setMousePos] = useState<[number, number, number] | null>(
|
||||
// null
|
||||
// );
|
||||
// const [selected, setSelected] = useState<number[]>([]);
|
||||
// const POLYGON_CLOSE_THRESHOLD = 0.3;
|
||||
// const isLeftMouseDown = useRef(false);
|
||||
// const drag = useRef(false);
|
||||
// const SNAP_POINT_THRESHOLD = 0.2;
|
||||
// const [assetUuid, setAssetUuid] = useState<any>();
|
||||
// const SNAP_LINE_THRESHOLD = 0.2;
|
||||
// const movementState = useRef<any>({});
|
||||
// const activeIndexRef = useRef(0);
|
||||
// const { isPlaying } = usePlayButtonStore();
|
||||
|
||||
// const pathSegments = useMemo(() => {
|
||||
// if (!shortestPath || shortestPath.length === 0) return [];
|
||||
|
||||
// const segments: SegmentPoint[] = [];
|
||||
|
||||
// shortestPath.forEach((path) => {
|
||||
// const [start, end] = path.pathPoints;
|
||||
|
||||
// if (start.isCurved && start.handleA && start.handleB) {
|
||||
// // Curved segment
|
||||
// const curve = new THREE.CubicBezierCurve3(
|
||||
// new THREE.Vector3(...start.position),
|
||||
// new THREE.Vector3(...start.handleA),
|
||||
// new THREE.Vector3(...start.handleB),
|
||||
// new THREE.Vector3(...end.position)
|
||||
// );
|
||||
// const points = curve
|
||||
// .getPoints(20)
|
||||
// .map((pos) => ({ position: pos, originalPoint: start }));
|
||||
// segments.push(...points);
|
||||
// } else {
|
||||
// // Straight segment
|
||||
// segments.push(
|
||||
// {
|
||||
// position: new THREE.Vector3(...start.position),
|
||||
// originalPoint: start,
|
||||
// },
|
||||
// { position: new THREE.Vector3(...end.position), originalPoint: end }
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
|
||||
// // Remove duplicates
|
||||
// return segments.filter(
|
||||
// (v, i, arr) => i === 0 || !v.position.equals(arr[i - 1].position)
|
||||
// );
|
||||
// }, [shortestPath]);
|
||||
|
||||
// useEffect(() => {
|
||||
// const findVehicle = assets
|
||||
// .filter((val) => val.eventData?.type === "Vehicle")
|
||||
// ?.map((val) => val.modelUuid);
|
||||
|
||||
// setAssetUuid(findVehicle);
|
||||
|
||||
// movementState.current = {};
|
||||
// findVehicle.forEach((uuid) => {
|
||||
// movementState.current[uuid] = {
|
||||
// index: 0,
|
||||
// progress: 0,
|
||||
// };
|
||||
// });
|
||||
// }, [assets]);
|
||||
|
||||
// useFrame((_, delta) => {
|
||||
// if (!isPlaying || pathSegments.length < 2) return;
|
||||
|
||||
// const object = scene.getObjectByProperty(
|
||||
// "uuid",
|
||||
// assetUuid[activeIndexRef.current]
|
||||
// );
|
||||
// if (!object) return;
|
||||
|
||||
// const state = movementState.current[assetUuid[activeIndexRef.current]];
|
||||
// if (!state) return;
|
||||
|
||||
// // Current start & end
|
||||
// const startSeg = pathSegments[state.index];
|
||||
// const endSeg = pathSegments[state.index + 1];
|
||||
|
||||
// const segmentDistance = startSeg.position.distanceTo(endSeg.position);
|
||||
// state.progress += (speed * delta) / segmentDistance;
|
||||
|
||||
// if (state.progress >= 1) {
|
||||
// state.progress = 0;
|
||||
// state.index++;
|
||||
// if (state.index >= pathSegments.length - 1) {
|
||||
// state.index = 0;
|
||||
// activeIndexRef.current =
|
||||
// (activeIndexRef.current + 1) % assetUuid.length;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Move object along sampled points
|
||||
// const newPos = startSeg.position
|
||||
// .clone()
|
||||
// .lerp(endSeg.position, state.progress);
|
||||
// object.position.copy(newPos);
|
||||
|
||||
// // Smooth rotation
|
||||
// const direction = endSeg.position
|
||||
// .clone()
|
||||
// .sub(startSeg.position)
|
||||
// .normalize();
|
||||
// const forward = new THREE.Vector3(0, 0, 1);
|
||||
// object.quaternion.setFromUnitVectors(forward, direction);
|
||||
|
||||
// // Access handles if needed
|
||||
// if (startSeg.originalPoint?.handleA) {
|
||||
// const handleA = startSeg.originalPoint.handleA;
|
||||
// const handleB = startSeg.originalPoint.handleB;
|
||||
// // do something with handles here
|
||||
// }
|
||||
// });
|
||||
|
||||
// useFrame(() => {
|
||||
// if (tempPath.length === 0) return;
|
||||
// raycaster.setFromCamera(pointer, camera);
|
||||
// const intersect = new THREE.Vector3();
|
||||
// if (raycaster.ray.intersectPlane(plane, intersect)) {
|
||||
// setMousePos([intersect.x, intersect.y, intersect.z]);
|
||||
// }
|
||||
// });
|
||||
|
||||
// const getClosestPointOnLine = (
|
||||
// a: THREE.Vector3,
|
||||
// b: THREE.Vector3,
|
||||
// p: THREE.Vector3
|
||||
// ) => {
|
||||
// const ab = new THREE.Vector3().subVectors(b, a);
|
||||
// const t = Math.max(
|
||||
// 0,
|
||||
// Math.min(1, p.clone().sub(a).dot(ab) / ab.lengthSq())
|
||||
// );
|
||||
// return a.clone().add(ab.multiplyScalar(t));
|
||||
// };
|
||||
|
||||
// const addPointToTemp = (newPoint: PointData) => {
|
||||
// // Add to tempPath
|
||||
// setTempPath((prev) => {
|
||||
// const updated = [...prev, newPoint];
|
||||
|
||||
// // Create new path from last point to this one
|
||||
// if (prev.length > 0) {
|
||||
// const lastPoint = prev[prev.length - 1];
|
||||
// const newPath: PathDataInterface = {
|
||||
// pathId: THREE.MathUtils.generateUUID(),
|
||||
// pathPoints: [lastPoint, newPoint],
|
||||
// };
|
||||
// setPaths((prevPaths) => [...prevPaths, newPath]);
|
||||
// }
|
||||
|
||||
// return updated;
|
||||
// });
|
||||
|
||||
// // Add to global points if not already exists
|
||||
// setPoints((prev) => {
|
||||
// if (!prev.find((p) => p.pointId === newPoint.pointId))
|
||||
// return [...prev, newPoint];
|
||||
// return prev;
|
||||
// });
|
||||
// };
|
||||
|
||||
// useEffect(() => {
|
||||
// const canvas = gl.domElement;
|
||||
|
||||
// const onMouseDown = (evt: MouseEvent) => {
|
||||
// if (evt.button === 0) {
|
||||
// if (evt.ctrlKey || evt.shiftKey) return;
|
||||
// isLeftMouseDown.current = true;
|
||||
// drag.current = false;
|
||||
// }
|
||||
// };
|
||||
|
||||
// const onMouseUp = (evt: MouseEvent) => {
|
||||
// if (evt.button === 0) isLeftMouseDown.current = false;
|
||||
// };
|
||||
|
||||
// const onMouseMove = () => {
|
||||
// if (isLeftMouseDown.current) drag.current = true;
|
||||
// };
|
||||
|
||||
// const onClick = (evt: MouseEvent) => {
|
||||
// if (drag.current) return;
|
||||
|
||||
// if (evt.ctrlKey || evt.shiftKey) return;
|
||||
// const intersectPoint = new THREE.Vector3();
|
||||
// const pointIntersects = raycaster
|
||||
// .intersectObjects(scene.children)
|
||||
// .find((intersect) => intersect.object.name === "path-line");
|
||||
|
||||
// const pos = raycaster.ray.intersectPlane(plane, intersectPoint);
|
||||
// if (!pos) return;
|
||||
|
||||
// let clickedPoint = new THREE.Vector3(pos.x, pos.y, pos.z);
|
||||
|
||||
// // Snap to existing point if close
|
||||
// let snapPoint: PointData | null = null;
|
||||
// for (let p of points) {
|
||||
// const pVec = new THREE.Vector3(...p.position);
|
||||
// if (pVec.distanceTo(clickedPoint) < SNAP_POINT_THRESHOLD) {
|
||||
// snapPoint = p;
|
||||
// clickedPoint = pVec;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// let newPoint: PointData = snapPoint ?? {
|
||||
// pointId: THREE.MathUtils.generateUUID(),
|
||||
// position: [clickedPoint.x, 0, clickedPoint.z],
|
||||
// isCurved: false,
|
||||
// handleA: null,
|
||||
// handleB: null,
|
||||
// };
|
||||
|
||||
// // Check if polygon can be closed
|
||||
// if (tempPath.length > 2) {
|
||||
// const firstVec = new THREE.Vector3(...tempPath[0].position);
|
||||
// if (firstVec.distanceTo(clickedPoint) < POLYGON_CLOSE_THRESHOLD) {
|
||||
// // Close polygon by connecting last point → first point
|
||||
// const closingPoint = { ...tempPath[0] };
|
||||
// addPointToTemp(closingPoint);
|
||||
// setTempPath([]); // Polygon finished
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Split existing line if clicked near it (same as before)
|
||||
// for (let path of paths) {
|
||||
// const a = new THREE.Vector3(...path.pathPoints[0].position);
|
||||
// const b = new THREE.Vector3(...path.pathPoints[1].position);
|
||||
// const closest = getClosestPointOnLine(a, b, clickedPoint);
|
||||
|
||||
// if (closest.distanceTo(clickedPoint) < SNAP_LINE_THRESHOLD) {
|
||||
// const splitPoint: PointData = {
|
||||
// pointId: THREE.MathUtils.generateUUID(),
|
||||
// position: closest.toArray() as [number, number, number],
|
||||
// isCurved: false,
|
||||
// handleA: null,
|
||||
// handleB: null,
|
||||
// };
|
||||
|
||||
// // Remove original path and replace with split paths
|
||||
// setPaths((prev) =>
|
||||
// prev
|
||||
// .filter((pa) => pa.pathId !== path.pathId)
|
||||
// .concat([
|
||||
// {
|
||||
// pathId: THREE.MathUtils.generateUUID(),
|
||||
// pathPoints: [path.pathPoints[0], splitPoint],
|
||||
// },
|
||||
// {
|
||||
// pathId: THREE.MathUtils.generateUUID(),
|
||||
// pathPoints: [splitPoint, path.pathPoints[1]],
|
||||
// },
|
||||
// ])
|
||||
// );
|
||||
|
||||
// addPointToTemp(splitPoint);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Normal add point
|
||||
// addPointToTemp(newPoint);
|
||||
// };
|
||||
|
||||
// const onContextMenu = (evt: MouseEvent) => {
|
||||
// evt.preventDefault();
|
||||
// setTempPath([]); // Cancel current polygon
|
||||
// };
|
||||
|
||||
// canvas.addEventListener("mousedown", onMouseDown);
|
||||
// canvas.addEventListener("mouseup", onMouseUp);
|
||||
// canvas.addEventListener("mousemove", onMouseMove);
|
||||
// canvas.addEventListener("click", onClick);
|
||||
// canvas.addEventListener("contextmenu", onContextMenu);
|
||||
|
||||
// return () => {
|
||||
// canvas.removeEventListener("mousedown", onMouseDown);
|
||||
// canvas.removeEventListener("mouseup", onMouseUp);
|
||||
// canvas.removeEventListener("mousemove", onMouseMove);
|
||||
// canvas.removeEventListener("click", onClick);
|
||||
// canvas.removeEventListener("contextmenu", onContextMenu);
|
||||
// };
|
||||
// }, [gl, camera, raycaster, pointer, tempPath, points, paths, plane]);
|
||||
|
||||
// return (
|
||||
// <>
|
||||
// {paths.map((path, pathIndex) => (
|
||||
// <LineSegment
|
||||
// key={path.pathId}
|
||||
// index={pathIndex}
|
||||
// paths={paths}
|
||||
// setPaths={setPaths}
|
||||
// />
|
||||
// ))}
|
||||
|
||||
// {/* Interactive PointHandles */}
|
||||
// {points.map((point, index) => (
|
||||
// <PointHandles
|
||||
// key={point.pointId}
|
||||
// point={point}
|
||||
// pointIndex={index}
|
||||
// points={points}
|
||||
// setPoints={setPoints}
|
||||
// paths={paths}
|
||||
// setPaths={setPaths}
|
||||
// setShortestPath={setShortestPath}
|
||||
// selected={selected}
|
||||
// setSelected={setSelected}
|
||||
// />
|
||||
// ))}
|
||||
|
||||
// {tempPath.length > 0 && mousePos && (
|
||||
// <Line
|
||||
// points={[
|
||||
// new THREE.Vector3(...tempPath[tempPath.length - 1].position),
|
||||
// new THREE.Vector3(...mousePos),
|
||||
// ]}
|
||||
// color="orange"
|
||||
// lineWidth={2}
|
||||
// dashed
|
||||
// />
|
||||
// )}
|
||||
// </>
|
||||
// );
|
||||
// }
|
||||
export default function StructuredPath() {
|
||||
const { scene, camera, raycaster, gl, pointer } = useThree();
|
||||
const plane = useMemo(
|
||||
() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0),
|
||||
[]
|
||||
);
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
const { assetStore } = useSceneContext();
|
||||
const { assets } = assetStore();
|
||||
|
||||
// --- State Variables ---
|
||||
const [pathPointsList, setPathPointsList] = useState<PointData[]>([]);
|
||||
const [allPaths, setAllPaths] = useState<PathData>([]);
|
||||
const [computedShortestPath, setComputedShortestPath] = useState<PathData>(
|
||||
[]
|
||||
);
|
||||
const [currentTempPath, setCurrentTempPath] = useState<PointData[]>([]);
|
||||
const [currentMousePos, setCurrentMousePos] = useState<
|
||||
[number, number, number] | null
|
||||
>(null);
|
||||
const [selectedPointIndices, setSelectedPointIndices] = useState<number[]>(
|
||||
[]
|
||||
);
|
||||
const [vehicleUuids, setVehicleUuids] = useState<any>();
|
||||
|
||||
// --- Constants & Refs ---
|
||||
const isLeftClickDown = useRef<boolean>(false);
|
||||
const isDragging = useRef<boolean>(false);
|
||||
const vehicleMovementState = useRef<any>({});
|
||||
const activeVehicleIndexRef = useRef(0);
|
||||
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
|
||||
// --- Computed Path Segments ---
|
||||
const pathSegments = useMemo(() => {
|
||||
if (!computedShortestPath || computedShortestPath.length === 0) return [];
|
||||
|
||||
const segments: SegmentPoint[] = [];
|
||||
|
||||
computedShortestPath.forEach((path) => {
|
||||
const [start, end] = path.pathPoints;
|
||||
|
||||
if (start.isCurved && start.handleA && start.handleB) {
|
||||
const curve = new THREE.CubicBezierCurve3(
|
||||
new THREE.Vector3(...start.position),
|
||||
new THREE.Vector3(...start.handleA),
|
||||
new THREE.Vector3(...start.handleB),
|
||||
new THREE.Vector3(...end.position)
|
||||
);
|
||||
const points = curve
|
||||
.getPoints(20)
|
||||
.map((pos) => ({ position: pos, originalPoint: start }));
|
||||
segments.push(...points);
|
||||
} else {
|
||||
segments.push(
|
||||
{
|
||||
position: new THREE.Vector3(...start.position),
|
||||
originalPoint: start,
|
||||
},
|
||||
{ position: new THREE.Vector3(...end.position), originalPoint: end }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return segments.filter(
|
||||
(v, i, arr) => i === 0 || !v.position.equals(arr[i - 1].position)
|
||||
);
|
||||
}, [computedShortestPath]);
|
||||
|
||||
// --- Initialize Vehicles ---
|
||||
useEffect(() => {
|
||||
const findVehicle = assets
|
||||
.filter((val) => val.eventData?.type === "Vehicle")
|
||||
?.map((val) => val.modelUuid);
|
||||
|
||||
setVehicleUuids(findVehicle);
|
||||
|
||||
vehicleMovementState.current = {};
|
||||
findVehicle.forEach((uuid) => {
|
||||
vehicleMovementState.current[uuid] = { index: 0, progress: 0 };
|
||||
});
|
||||
}, [assets]);
|
||||
|
||||
// --- Vehicle Movement ---
|
||||
useFrame((_, delta) => {
|
||||
if (!isPlaying || pathSegments.length < 2) return;
|
||||
|
||||
const object = scene.getObjectByProperty(
|
||||
"uuid",
|
||||
vehicleUuids[activeVehicleIndexRef.current]
|
||||
);
|
||||
if (!object) return;
|
||||
|
||||
const state =
|
||||
vehicleMovementState.current[vehicleUuids[activeVehicleIndexRef.current]];
|
||||
if (!state) return;
|
||||
|
||||
const startSeg = pathSegments[state.index];
|
||||
const endSeg = pathSegments[state.index + 1];
|
||||
|
||||
const segmentDistance = startSeg.position.distanceTo(endSeg.position);
|
||||
state.progress += (speed * delta) / segmentDistance;
|
||||
|
||||
if (state.progress >= 1) {
|
||||
state.progress = 0;
|
||||
state.index++;
|
||||
if (state.index >= pathSegments.length - 1) {
|
||||
state.index = 0;
|
||||
activeVehicleIndexRef.current =
|
||||
(activeVehicleIndexRef.current + 1) % vehicleUuids.length;
|
||||
}
|
||||
}
|
||||
|
||||
const newPos = startSeg.position
|
||||
.clone()
|
||||
.lerp(endSeg.position, state.progress);
|
||||
object.position.copy(newPos);
|
||||
|
||||
const direction = endSeg.position
|
||||
.clone()
|
||||
.sub(startSeg.position)
|
||||
.normalize();
|
||||
const forward = new THREE.Vector3(0, 0, 1);
|
||||
object.quaternion.setFromUnitVectors(forward, direction);
|
||||
});
|
||||
|
||||
// --- Update Mouse Position ---
|
||||
useFrame(() => {
|
||||
if (currentTempPath.length === 0) return;
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersect = new THREE.Vector3();
|
||||
if (raycaster.ray.intersectPlane(plane, intersect)) {
|
||||
setCurrentMousePos([intersect.x, intersect.y, intersect.z]);
|
||||
}
|
||||
});
|
||||
|
||||
const addPointToCurrentTemp = (newPoint: PointData) => {
|
||||
setCurrentTempPath((prev) => {
|
||||
const updated = [...prev, newPoint];
|
||||
|
||||
if (prev.length > 0) {
|
||||
const lastPoint = prev[prev.length - 1];
|
||||
const newPath: PathDataInterface = {
|
||||
pathId: THREE.MathUtils.generateUUID(),
|
||||
pathPoints: [lastPoint, newPoint],
|
||||
};
|
||||
setAllPaths((prevPaths) => [...prevPaths, newPath]);
|
||||
}
|
||||
|
||||
return updated;
|
||||
});
|
||||
|
||||
setPathPointsList((prev) => {
|
||||
if (!prev.find((p) => p.pointId === newPoint.pointId))
|
||||
return [...prev, newPoint];
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// --- Event Handlers ---
|
||||
// useEffect(() => {
|
||||
// const canvas = gl.domElement;
|
||||
|
||||
// const handleMouseDown = (evt: MouseEvent) => {
|
||||
// if (evt.button === 0) {
|
||||
// if (evt.ctrlKey || evt.shiftKey) return;
|
||||
// isLeftClickDown.current = true;
|
||||
// isDragging.current = false;
|
||||
// }
|
||||
// };
|
||||
|
||||
// const handleMouseUp = (evt: MouseEvent) => {
|
||||
// if (evt.button === 0) isLeftClickDown.current = false;
|
||||
// };
|
||||
|
||||
// const handleMouseMove = () => {
|
||||
// if (isLeftClickDown.current) isDragging.current = true;
|
||||
// };
|
||||
|
||||
// const handleMouseClick = (evt: MouseEvent) => {
|
||||
// if (isDragging.current) return;
|
||||
// if (evt.ctrlKey || evt.shiftKey) return;
|
||||
|
||||
// const intersectPoint = new THREE.Vector3();
|
||||
// const pointIntersects = raycaster
|
||||
// .intersectObjects(scene.children)
|
||||
// .find((intersect) => intersect.object.name === "path-line");
|
||||
|
||||
// const pos = raycaster.ray.intersectPlane(plane, intersectPoint);
|
||||
// if (!pos) return;
|
||||
|
||||
// let clickedPoint = new THREE.Vector3(pos.x, pos.y, pos.z);
|
||||
|
||||
// let snapPoint: PointData | null = null;
|
||||
// for (let p of pathPointsList) {
|
||||
// const pVec = new THREE.Vector3(...p.position);
|
||||
// if (pVec.distanceTo(clickedPoint) < SNAP_POINT_THRESHOLD) {
|
||||
// snapPoint = p;
|
||||
// clickedPoint = pVec;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// let newPoint: PointData = snapPoint ?? {
|
||||
// pointId: THREE.MathUtils.generateUUID(),
|
||||
// position: [clickedPoint.x, 0, clickedPoint.z],
|
||||
// isCurved: false,
|
||||
// handleA: null,
|
||||
// handleB: null,
|
||||
// };
|
||||
|
||||
// if (currentTempPath.length > 2) {
|
||||
// const firstVec = new THREE.Vector3(...currentTempPath[0].position);
|
||||
// if (firstVec.distanceTo(clickedPoint) < POLYGON_CLOSE_THRESHOLD) {
|
||||
// const closingPoint = { ...currentTempPath[0] };
|
||||
// addPointToCurrentTemp(closingPoint);
|
||||
// setCurrentTempPath([]);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// for (let path of allPaths) {
|
||||
// const a = new THREE.Vector3(...path.pathPoints[0].position);
|
||||
// const b = new THREE.Vector3(...path.pathPoints[1].position);
|
||||
// const closest = getNearestPointOnLine(a, b, clickedPoint);
|
||||
|
||||
// if (closest.distanceTo(clickedPoint) < SNAP_LINE_THRESHOLD) {
|
||||
// const splitPoint: PointData = {
|
||||
// pointId: THREE.MathUtils.generateUUID(),
|
||||
// position: closest.toArray() as [number, number, number],
|
||||
// isCurved: false,
|
||||
// handleA: null,
|
||||
// handleB: null,
|
||||
// };
|
||||
|
||||
// setAllPaths((prev) =>
|
||||
// prev
|
||||
// .filter((pa) => pa.pathId !== path.pathId)
|
||||
// .concat([
|
||||
// {
|
||||
// pathId: THREE.MathUtils.generateUUID(),
|
||||
// pathPoints: [path.pathPoints[0], splitPoint],
|
||||
// },
|
||||
// {
|
||||
// pathId: THREE.MathUtils.generateUUID(),
|
||||
// pathPoints: [splitPoint, path.pathPoints[1]],
|
||||
// },
|
||||
// ])
|
||||
// );
|
||||
|
||||
// addPointToCurrentTemp(splitPoint);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// addPointToCurrentTemp(newPoint);
|
||||
// };
|
||||
|
||||
// const handleContextMenu = (evt: MouseEvent) => {
|
||||
// evt.preventDefault();
|
||||
// setCurrentTempPath([]);
|
||||
// };
|
||||
|
||||
// canvas.addEventListener("mousedown", handleMouseDown);
|
||||
// canvas.addEventListener("mouseup", handleMouseUp);
|
||||
// canvas.addEventListener("mousemove", handleMouseMove);
|
||||
// canvas.addEventListener("click", handleMouseClick);
|
||||
// canvas.addEventListener("contextmenu", handleContextMenu);
|
||||
|
||||
// return () => {
|
||||
// canvas.removeEventListener("mousedown", handleMouseDown);
|
||||
// canvas.removeEventListener("mouseup", handleMouseUp);
|
||||
// canvas.removeEventListener("mousemove", handleMouseMove);
|
||||
// canvas.removeEventListener("click", handleMouseClick);
|
||||
// canvas.removeEventListener("contextmenu", handleContextMenu);
|
||||
// };
|
||||
// }, [
|
||||
// gl,
|
||||
// camera,
|
||||
// raycaster,
|
||||
// pointer,
|
||||
// currentTempPath,
|
||||
// pathPointsList,
|
||||
// allPaths,
|
||||
// plane,
|
||||
// ]);
|
||||
useEffect(() => {
|
||||
const canvas = gl.domElement;
|
||||
|
||||
const onMouseDown = (evt: MouseEvent) =>
|
||||
handleMouseDown(evt, isLeftClickDown, isDragging);
|
||||
const onMouseUp = (evt: MouseEvent) => handleMouseUp(evt, isLeftClickDown);
|
||||
const onMouseMove = () => handleMouseMove(isLeftClickDown, isDragging);
|
||||
const onClick = (evt: MouseEvent) =>
|
||||
handleMouseClick({
|
||||
evt,
|
||||
isDragging,
|
||||
raycaster,
|
||||
plane,
|
||||
pointer,
|
||||
currentTempPath,
|
||||
setCurrentTempPath,
|
||||
pathPointsList,
|
||||
allPaths,
|
||||
setAllPaths,
|
||||
addPointToCurrentTemp,
|
||||
});
|
||||
const onContextMenu = (evt: MouseEvent) =>
|
||||
handleContextMenu(evt, setCurrentTempPath);
|
||||
|
||||
canvas.addEventListener("mousedown", onMouseDown);
|
||||
canvas.addEventListener("mouseup", onMouseUp);
|
||||
canvas.addEventListener("mousemove", onMouseMove);
|
||||
canvas.addEventListener("click", onClick);
|
||||
canvas.addEventListener("contextmenu", onContextMenu);
|
||||
|
||||
return () => {
|
||||
canvas.removeEventListener("mousedown", onMouseDown);
|
||||
canvas.removeEventListener("mouseup", onMouseUp);
|
||||
canvas.removeEventListener("mousemove", onMouseMove);
|
||||
canvas.removeEventListener("click", onClick);
|
||||
canvas.removeEventListener("contextmenu", onContextMenu);
|
||||
};
|
||||
}, [
|
||||
gl,
|
||||
camera,
|
||||
raycaster,
|
||||
pointer,
|
||||
plane,
|
||||
currentTempPath,
|
||||
pathPointsList,
|
||||
allPaths,
|
||||
]);
|
||||
// --- Render ---
|
||||
return (
|
||||
<>
|
||||
{allPaths.map((path, pathIndex) => (
|
||||
<LineSegment
|
||||
key={path.pathId}
|
||||
index={pathIndex}
|
||||
paths={allPaths}
|
||||
setPaths={setAllPaths}
|
||||
/>
|
||||
))}
|
||||
|
||||
{pathPointsList.map((point, index) => (
|
||||
<PointHandles
|
||||
key={point.pointId}
|
||||
point={point}
|
||||
pointIndex={index}
|
||||
points={pathPointsList}
|
||||
setPoints={setPathPointsList}
|
||||
paths={allPaths}
|
||||
setPaths={setAllPaths}
|
||||
setShortestPath={setComputedShortestPath}
|
||||
selected={selectedPointIndices}
|
||||
setSelected={setSelectedPointIndices}
|
||||
/>
|
||||
))}
|
||||
|
||||
{currentTempPath.length > 0 && currentMousePos && (
|
||||
<Line
|
||||
points={[
|
||||
new THREE.Vector3(
|
||||
...currentTempPath[currentTempPath.length - 1].position
|
||||
),
|
||||
new THREE.Vector3(...currentMousePos),
|
||||
]}
|
||||
color="orange"
|
||||
lineWidth={2}
|
||||
dashed
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import VehicleInstances from "./instances/vehicleInstances";
|
||||
import VehicleUI from "../spatialUI/vehicle/vehicleUI";
|
||||
import { useSceneContext } from "../../scene/sceneContext";
|
||||
import PreDefinedPath from "./preDefinedPath/preDefinedPath";
|
||||
import StructuredPath from "./structuredPath/structuredPath";
|
||||
|
||||
function Vehicles() {
|
||||
const { vehicleStore } = useSceneContext();
|
||||
@@ -28,7 +29,8 @@ function Vehicles() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PreDefinedPath />
|
||||
<StructuredPath />
|
||||
{/* <PreDefinedPath /> */}
|
||||
{/* <VehicleInstances /> */}
|
||||
{isVehicleSelected && selectedEventSphere && !isPlaying && <VehicleUI />}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user