added path curves
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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"}
|
||||
/>
|
||||
|
||||
@@ -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 }));
|
||||
segments.push(...points);
|
||||
} else {
|
||||
segments.push(
|
||||
{
|
||||
position: new THREE.Vector3(...start.position),
|
||||
const points = curve.getPoints(20).map((pos) => ({
|
||||
position: pos,
|
||||
originalPoint: start,
|
||||
},
|
||||
{ position: new THREE.Vector3(...end.position), originalPoint: end }
|
||||
}));
|
||||
segments.push(...points);
|
||||
}
|
||||
|
||||
// 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: 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}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user