added path curves

This commit is contained in:
2025-08-22 09:10:37 +05:30
parent 3f808f167d
commit b623a92b9c
4 changed files with 270 additions and 533 deletions

View File

@@ -1,6 +1,6 @@
import * as THREE from "three";
export const POLYGON_CLOSE_THRESHOLD = 0.3;
export const POLYGON_CLOSE_THRESHOLD = 0.1;
export const SNAP_POINT_THRESHOLD = 0.2;
export const SNAP_LINE_THRESHOLD = 0.2;
type PointData = {

View File

@@ -1,4 +1,4 @@
import { useMemo, useRef } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";
import { Line } from "@react-three/drei";
@@ -21,6 +21,7 @@ type PathData = PathDataInterface[];
interface LineSegmentProps {
index: number;
paths: PathDataInterface[];
pathIndex: number;
setPaths: React.Dispatch<React.SetStateAction<PathData>>;
insertPoint?: (pathIndex: number, point: THREE.Vector3) => void;
}
@@ -30,33 +31,12 @@ export default function LineSegment({
paths,
setPaths,
insertPoint,
pathIndex,
}: 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 [curve, setCurve] = useState<any>();
const [curveState, setCurveState] = useState<string>("");
const curvePoints = useMemo(() => {
if (!paths || index >= paths.length) return [];
@@ -67,18 +47,210 @@ export default function LineSegment({
const start = new THREE.Vector3(...current.position);
const end = new THREE.Vector3(...next.position);
// 1⃣ Case 1: use predefined handles
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;
if (useCurve) {
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);
const curve = new THREE.CubicBezierCurve3(start, hB, hA, end);
return curve.getPoints(100);
}
return curve.getPoints(useCurve ? 100 : 2);
}, [paths, index]);
// 2⃣ Case 2: use curveState-generated curve
if (curveState) {
const direction = new THREE.Vector3().subVectors(end, start).normalize();
return <Line points={curvePoints} color="purple" lineWidth={3} />;
const up = new THREE.Vector3(0, 1, 0);
const perpendicular = new THREE.Vector3()
.crossVectors(direction, up)
.normalize();
const distance = start.distanceTo(end);
const controlDistance = distance / 6;
let controlPoint1, controlPoint2;
// if (curveState === "arc") {
// const direction = new THREE.Vector3()
// .subVectors(end, start)
// .normalize();
// const perpendicular = new THREE.Vector3(
// -direction.z,
// 0,
// direction.x
// ).normalize();
// controlPoint1 = new THREE.Vector3().addVectors(
// start,
// perpendicular.clone().multiplyScalar(-controlDistance) // negative fixes to "C"
// );
// controlPoint2 = new THREE.Vector3().addVectors(
// end,
// perpendicular.clone().multiplyScalar(-controlDistance)
// );
// }
if (curveState === "arc") {
const direction = new THREE.Vector3()
.subVectors(end, start)
.normalize();
// Perpendicular direction in XZ plane
const perpendicular = new THREE.Vector3(-direction.z, 0, direction.x);
const distance = start.distanceTo(end);
const controlDistance = distance / 4;
const controlPoint1 = new THREE.Vector3()
.addVectors(start, direction.clone().multiplyScalar(distance / 3))
.add(perpendicular.clone().multiplyScalar(-controlDistance)); // ← flipped
const controlPoint2 = new THREE.Vector3()
.addVectors(end, direction.clone().multiplyScalar(-distance / 3))
.add(perpendicular.clone().multiplyScalar(-controlDistance)); // ← flipped
const curve = new THREE.CubicBezierCurve3(
start,
controlPoint1,
controlPoint2,
end
);
return curve.getPoints(64);
}
// if (curveState === "arc") {
// const direction = new THREE.Vector3()
// .subVectors(end, start)
// .normalize();
// // XZ-plane perpendicular
// const perpendicular = new THREE.Vector3(-direction.z, 0, direction.x);
// const distance = start.distanceTo(end);
// const controlDistance = distance / 6; // ← increase this for more curvature
// const controlPoint1 = new THREE.Vector3().addVectors(
// start,
// perpendicular.clone().multiplyScalar(-controlDistance)
// );
// const controlPoint2 = new THREE.Vector3().addVectors(
// end,
// perpendicular.clone().multiplyScalar(-controlDistance)
// );
// const curve = new THREE.CubicBezierCurve3(
// start,
// controlPoint1,
// controlPoint2,
// end
// );
// return curve.getPoints(64);
// }
const curve = new THREE.CubicBezierCurve3(
start,
controlPoint1,
controlPoint2,
end
);
return curve.getPoints(32);
}
// 3⃣ Case 3: fallback straight line
const line = new THREE.LineCurve3(start, end);
return line.getPoints(2);
}, [paths, index, curveState]);
// 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);
// // 1⃣ Case 1: Use predefined curve handles if present
// const useCurve =
// (current.isCurved && current.handleB) || (next.isCurved && next.handleA);
// if (useCurve) {
// const handleB = current.handleB
// ? new THREE.Vector3(...current.handleB)
// : start;
// const handleA = next.handleA ? new THREE.Vector3(...next.handleA) : end;
// const curve = new THREE.CubicBezierCurve3(start, handleB, handleA, end);
// return curve.getPoints(100);
// }
// // 2⃣ Case 2: Use curveState-generated arc (gentle C-shaped)
// // if (curveState === "arc") {
// // const direction = new THREE.Vector3().subVectors(end, start).normalize();
// // const distance = start.distanceTo(end);
// // // Get perpendicular in XZ plane
// // const perpendicular = new THREE.Vector3(-direction.z, 0, direction.x);
// // const controlOffset = perpendicular.multiplyScalar(distance / 8);
// // // Create gentle symmetric control points for a "C" arc
// // const controlPoint1 = start.clone().add(controlOffset.clone().negate());
// // const controlPoint2 = end.clone().add(controlOffset.clone().negate());
// // const curve = new THREE.CubicBezierCurve3(
// // start,
// // controlPoint1,
// // controlPoint2,
// // end
// // );
// // return curve.getPoints(64); // 64 for smoother shape
// // }
// if (curveState === "arc") {
// const direction = new THREE.Vector3().subVectors(end, start).normalize();
// const distance = start.distanceTo(end);
// // const curveHeight = distance * 0.25; // 25% of distance
// // 🔺 Control height: Raise control points on Y-axis
// const curveHeight = distance / 4; // adjust 4 → higher = taller arc
// // Control points directly above the midpoint
// const mid = start.clone().add(end).multiplyScalar(0.5);
// const controlPoint = mid
// .clone()
// .add(new THREE.Vector3(0, curveHeight, 0));
// // Use Quadratic Bezier for simple arc
// const curve = new THREE.QuadraticBezierCurve3(start, controlPoint, end);
// return curve.getPoints(64);
// }
// // 3⃣ Case 3: Fallback to straight line
// const line = new THREE.LineCurve3(start, end);
// return line.getPoints(2);
// }, [paths, index, curveState]);
const handleClick = (evt: any) => {
if (evt.ctrlKey) {
setCurveState("arc");
}
};
// const bendFactor = 1 / 10; // tweak this dynamically
// const controlOffset = perpendicular.multiplyScalar(distance * bendFactor);
return (
<Line
points={curvePoints}
color="purple"
lineWidth={3.5}
onClick={handleClick}
/>
);
}

View File

@@ -587,7 +587,7 @@ function nodePathToEdges(
}
/** --- React Component --- */
export default function PointHandlersRenamed({
export default function PointHandlers({
point,
pointIndex,
points,
@@ -602,7 +602,7 @@ export default function PointHandlersRenamed({
const handleARef = useRef<THREE.Mesh>(null);
const handleBRef = useRef<THREE.Mesh>(null);
const lineRef = useRef<any>(null);
const { camera, gl, controls } = useThree();
const { camera, gl, controls, raycaster } = useThree();
const [dragging, setDragging] = useState<
null | "main" | "handleA" | "handleB"
@@ -671,7 +671,12 @@ export default function PointHandlersRenamed({
: target === "handleA"
? handleARef.current
: handleBRef.current;
if (targetRef) dragOffset.current.copy(targetRef.position).sub(e.point);
if (targetRef && targetRef.position) {
dragOffset.current
.copy(new THREE.Vector3(targetRef.position.x, 0, targetRef.position.z))
.sub(e.point);
}
if (controls) (controls as any).enabled = false;
gl.domElement.style.cursor = "grabbing";
};
@@ -683,10 +688,10 @@ export default function PointHandlersRenamed({
};
/** Update position in useFrame */
useFrame(({ raycaster, mouse }) => {
useFrame(({ 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);
@@ -700,16 +705,20 @@ export default function PointHandlersRenamed({
.clone()
.sub(new THREE.Vector3().fromArray(p.position));
p.position = [newPos.x, 0, newPos.z];
if (p.handleA)
if (p.handleA) {
p.handleA = new THREE.Vector3()
.fromArray(p.handleA)
.add(delta)
.add(new THREE.Vector3(delta.x, 0, delta.z))
.toArray() as [number, number, number];
if (p.handleB)
}
if (p.handleB) {
p.handleB = new THREE.Vector3()
.fromArray(p.handleB)
.add(delta)
.add(new THREE.Vector3(delta.x, 0, delta.z))
.toArray() as [number, number, number];
}
} else {
p[dragging] = [newPos.x, 0, newPos.z];
if (p.isCurved) {
@@ -718,6 +727,7 @@ export default function PointHandlersRenamed({
const mirrorHandle = mainPos
.clone()
.sub(thisHandle.clone().sub(mainPos));
console.log("mirrorHandle: ", mirrorHandle);
if (dragging === "handleA")
p.handleB = mirrorHandle.toArray() as [number, number, number];
if (dragging === "handleB")
@@ -765,7 +775,7 @@ export default function PointHandlersRenamed({
onPointerDown={(e) => startDrag("main", e)}
onPointerUp={stopDrag}
>
<sphereGeometry args={[0.3, 16, 16]} />
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial
color={selected.includes(pointIndex) ? "red" : "pink"}
/>

View File

@@ -27,6 +27,8 @@ type PointData = {
interface PathDataInterface {
pathId: string;
isActive?: boolean;
isCurved?: boolean;
pathPoints: [PointData, PointData];
}
@@ -35,355 +37,7 @@ 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(
@@ -397,6 +51,7 @@ export default function StructuredPath() {
// --- State Variables ---
const [pathPointsList, setPathPointsList] = useState<PointData[]>([]);
const [allPaths, setAllPaths] = useState<PathData>([]);
console.log("allPaths: ", allPaths);
const [computedShortestPath, setComputedShortestPath] = useState<PathData>(
[]
);
@@ -426,28 +81,55 @@ export default function StructuredPath() {
computedShortestPath.forEach((path) => {
const [start, end] = path.pathPoints;
const startPos = new THREE.Vector3(...start.position);
const endPos = new THREE.Vector3(...end.position);
// Start point has curve handles
if (start.isCurved && start.handleA && start.handleB) {
const handleA = new THREE.Vector3(...start.handleA);
const handleB = new THREE.Vector3(...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)
startPos,
handleA,
handleB,
endPos
);
const points = curve
.getPoints(20)
.map((pos) => ({ position: pos, originalPoint: start }));
const points = curve.getPoints(20).map((pos) => ({
position: pos,
originalPoint: start,
}));
segments.push(...points);
} else {
}
// End point has curve handles
else if (end.isCurved && end.handleA && end.handleB) {
const handleA = new THREE.Vector3(...end.handleA);
const handleB = new THREE.Vector3(...end.handleB);
const curve = new THREE.CubicBezierCurve3(
startPos,
handleA,
handleB,
endPos
);
const points = curve.getPoints(20).map((pos) => ({
position: pos,
originalPoint: end,
}));
segments.push(...points);
}
// No curves — just straight line
else {
segments.push(
{
position: new THREE.Vector3(...start.position),
originalPoint: start,
},
{ position: new THREE.Vector3(...end.position), originalPoint: end }
{ position: startPos, originalPoint: start },
{ position: endPos, originalPoint: end }
);
}
});
// Filter out duplicate consecutive points
return segments.filter(
(v, i, arr) => i === 0 || !v.position.equals(arr[i - 1].position)
);
@@ -543,134 +225,6 @@ export default function StructuredPath() {
});
};
// --- 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;
@@ -725,6 +279,7 @@ export default function StructuredPath() {
<LineSegment
key={path.pathId}
index={pathIndex}
pathIndex={pathIndex}
paths={allPaths}
setPaths={setAllPaths}
/>