Dwinzo_dev/app/src/modules/simulation/path/pathConnector.tsx

497 lines
22 KiB
TypeScript
Raw Normal View History

2025-03-25 12:04:20 +00:00
import { useFrame, useThree } from '@react-three/fiber';
import React, { useEffect, useState } from 'react';
import * as THREE from 'three';
import * as Types from '../../../types/world/worldTypes';
2025-03-25 12:04:20 +00:00
import { QuadraticBezierLine } from '@react-three/drei';
import { useIsConnecting, useSimulationPaths } from '../../../store/store';
2025-03-25 12:04:20 +00:00
import useModuleStore from '../../../store/useModuleStore';
function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject<THREE.Group> }) {
const { activeModule } = useModuleStore();
2025-03-25 12:04:20 +00:00
const { gl, raycaster, scene, pointer, camera } = useThree();
const { setIsConnecting } = useIsConnecting();
2025-03-25 12:04:20 +00:00
const { simulationPaths, setSimulationPaths } = useSimulationPaths();
const [firstSelected, setFirstSelected] = useState<{
pathUUID: string;
sphereUUID: string;
position: THREE.Vector3;
isCorner: boolean;
} | null>(null);
2025-03-25 12:04:20 +00:00
const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3, end: THREE.Vector3, mid: THREE.Vector3 } | null>(null);
const [helperlineColor, setHelperLineColor] = useState<string>('red');
const updatePathConnections = (
fromPathUUID: string,
fromPointUUID: string,
toPathUUID: string,
toPointUUID: string
) => {
const updatedPaths = simulationPaths.map(path => {
if (path.type === 'Conveyor') {
if (path.modeluuid === fromPathUUID) {
return {
...path,
points: path.points.map(point => {
if (point.uuid === fromPointUUID) {
const newTarget = {
pathUUID: toPathUUID,
pointUUID: toPointUUID
};
const existingTargets = point.connections.targets || [];
if (!existingTargets.some(target =>
target.pathUUID === newTarget.pathUUID &&
target.pointUUID === newTarget.pointUUID
)) {
return {
...point,
connections: {
...point.connections,
targets: [...existingTargets, newTarget]
}
};
}
}
return point;
})
};
}
else if (path.modeluuid === toPathUUID) {
return {
...path,
points: path.points.map(point => {
if (point.uuid === toPointUUID) {
const reverseTarget = {
pathUUID: fromPathUUID,
pointUUID: fromPointUUID
};
const existingTargets = point.connections.targets || [];
if (!existingTargets.some(target =>
target.pathUUID === reverseTarget.pathUUID &&
target.pointUUID === reverseTarget.pointUUID
)) {
return {
...point,
connections: {
...point.connections,
targets: [...existingTargets, reverseTarget]
}
};
}
}
return point;
})
};
}
}
else if (path.type === 'Vehicle') {
// Handle outgoing connections from Vehicle
if (path.modeluuid === fromPathUUID && path.point.uuid === fromPointUUID) {
const newTarget = {
pathUUID: toPathUUID,
pointUUID: toPointUUID
};
const existingTargets = path.point.connections.targets || [];
if (!existingTargets.some(target =>
target.pathUUID === newTarget.pathUUID &&
target.pointUUID === newTarget.pointUUID
)) {
return {
...path,
point: {
...path.point,
connections: {
...path.point.connections,
targets: [...existingTargets, newTarget]
}
}
};
}
}
// Handle incoming connections to Vehicle
else if (path.modeluuid === toPathUUID && path.point.uuid === toPointUUID) {
const reverseTarget = {
pathUUID: fromPathUUID,
pointUUID: fromPointUUID
};
const existingTargets = path.point.connections.targets || [];
if (!existingTargets.some(target =>
target.pathUUID === reverseTarget.pathUUID &&
target.pointUUID === reverseTarget.pointUUID
)) {
return {
...path,
point: {
...path.point,
connections: {
...path.point.connections,
targets: [...existingTargets, reverseTarget]
}
}
};
}
}
}
return path;
});
setSimulationPaths(updatedPaths);
};
const handleAddConnection = (fromPathUUID: string, fromUUID: string, toPathUUID: string, toUUID: string) => {
updatePathConnections(fromPathUUID, fromUUID, toPathUUID, toUUID);
setFirstSelected(null);
setCurrentLine(null);
setIsConnecting(false);
};
2025-03-25 12:04:20 +00:00
useEffect(() => {
const canvasElement = gl.domElement;
let drag = false;
let MouseDown = false;
const onMouseDown = () => {
MouseDown = true;
drag = false;
};
const onMouseUp = () => {
MouseDown = false;
};
const onMouseMove = () => {
if (MouseDown) {
drag = true;
}
};
const onContextMenu = (evt: MouseEvent) => {
evt.preventDefault();
if (drag || evt.button === 0) return;
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(pathsGroupRef.current.children, true);
if (intersects.length > 0) {
const intersected = intersects[0].object;
if (intersected.name.includes("events-sphere")) {
2025-03-25 12:04:20 +00:00
const pathUUID = intersected.userData.path.modeluuid;
const sphereUUID = intersected.uuid;
const worldPosition = new THREE.Vector3();
intersected.getWorldPosition(worldPosition);
let isStartOrEnd = false;
2025-03-25 12:04:20 +00:00
if (intersected.userData.path.points) {
isStartOrEnd = intersected.userData.path.points.length > 0 && (
sphereUUID === intersected.userData.path.points[0].uuid ||
sphereUUID === intersected.userData.path.points[intersected.userData.path.points.length - 1].uuid
2025-03-25 12:04:20 +00:00
);
} else if (intersected.userData.path.point) {
isStartOrEnd = sphereUUID === intersected.userData.path.point.uuid;
}
if (pathUUID) {
const firstPath = simulationPaths.find(p => p.modeluuid === firstSelected?.pathUUID);
const secondPath = simulationPaths.find(p => p.modeluuid === pathUUID);
if (firstPath && secondPath && firstPath.type === 'Vehicle' && secondPath.type === 'Vehicle') {
console.log("Cannot connect two vehicle paths together");
return;
}
const isAlreadyConnected = simulationPaths.some(path => {
if (path.type === 'Conveyor') {
return path.points.some(point =>
point.uuid === sphereUUID &&
point.connections.targets.length > 0
);
} else if (path.type === 'Vehicle') {
return path.point.uuid === sphereUUID &&
path.point.connections.targets.length > 0;
}
return false;
});
2025-03-25 12:04:20 +00:00
if (isAlreadyConnected) {
console.log("Sphere is already connected. Ignoring.");
return;
}
if (!firstSelected) {
setFirstSelected({
pathUUID,
sphereUUID,
position: worldPosition,
isCorner: isStartOrEnd
});
setIsConnecting(true);
} else {
if (firstSelected.sphereUUID === sphereUUID) return;
if (firstSelected.pathUUID === pathUUID) {
console.log("Cannot connect spheres on the same path.");
return;
}
if (!firstSelected.isCorner && !isStartOrEnd) {
console.log("At least one of the selected spheres must be a start or end point.");
return;
}
handleAddConnection(
firstSelected.pathUUID,
firstSelected.sphereUUID,
pathUUID,
sphereUUID
);
2025-03-25 12:04:20 +00:00
}
}
}
} else {
setFirstSelected(null);
setCurrentLine(null);
setIsConnecting(false);
}
};
if (activeModule === 'simulation') {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
canvasElement.addEventListener("contextmenu", onContextMenu);
} else {
setFirstSelected(null);
setCurrentLine(null);
setIsConnecting(false);
}
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
canvasElement.removeEventListener("contextmenu", onContextMenu);
};
}, [camera, scene, raycaster, firstSelected, simulationPaths]);
2025-03-25 12:04:20 +00:00
useFrame(() => {
if (firstSelected) {
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, true).filter((intersect) =>
!intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("agv-collider") &&
2025-03-25 12:04:20 +00:00
!intersect.object.name.includes("MeasurementReference") &&
!intersect.object.userData.isPathObject &&
!(intersect.object.type === "GridHelper")
);
let point: THREE.Vector3 | null = null;
let snappedSphere: { sphereUUID: string, position: THREE.Vector3, pathUUID: string, isCorner: boolean } | null = null;
let isInvalidConnection = false;
if (intersects.length > 0) {
point = intersects[0].point;
if (point.y < 0.05) {
point = new THREE.Vector3(point.x, 0.05, point.z);
}
}
const sphereIntersects = raycaster.intersectObjects(pathsGroupRef.current.children, true).filter((obj) =>
obj.object.name.includes("events-sphere")
2025-03-25 12:04:20 +00:00
);
if (sphereIntersects.length > 0) {
const sphere = sphereIntersects[0].object;
const sphereUUID = sphere.uuid;
const spherePosition = new THREE.Vector3();
sphere.getWorldPosition(spherePosition);
const pathData = sphere.userData.path;
const pathUUID = pathData.modeluuid;
const firstPath = simulationPaths.find(p => p.modeluuid === firstSelected.pathUUID);
const secondPath = simulationPaths.find(p => p.modeluuid === pathUUID);
const isVehicleToVehicle = firstPath?.type === 'Vehicle' && secondPath?.type === 'Vehicle';
const isConnectable = (pathData.type === 'Vehicle' ||
(pathData.points.length > 0 && (
sphereUUID === pathData.points[0].uuid ||
sphereUUID === pathData.points[pathData.points.length - 1].uuid
))) && !isVehicleToVehicle;
const isAlreadyConnected = simulationPaths.some(path => {
if (path.type === 'Conveyor') {
return path.points.some(point =>
point.uuid === sphereUUID &&
point.connections.targets.length > 0
);
} else if (path.type === 'Vehicle') {
return path.point.uuid === sphereUUID &&
path.point.connections.targets.length > 0;
}
return false;
});
2025-03-25 12:04:20 +00:00
if (
!isAlreadyConnected &&
!isVehicleToVehicle &&
2025-03-25 12:04:20 +00:00
firstSelected.sphereUUID !== sphereUUID &&
firstSelected.pathUUID !== pathUUID &&
(firstSelected.isCorner || isConnectable)
2025-03-25 12:04:20 +00:00
) {
snappedSphere = {
sphereUUID,
position: spherePosition,
pathUUID,
isCorner: isConnectable
};
2025-03-25 12:04:20 +00:00
} else {
isInvalidConnection = true;
}
}
if (snappedSphere) {
point = snappedSphere.position;
}
if (point) {
const distance = firstSelected.position.distanceTo(point);
const heightFactor = Math.max(0.5, distance * 0.2);
const midPoint = new THREE.Vector3(
(firstSelected.position.x + point.x) / 2,
Math.max(firstSelected.position.y, point.y) + heightFactor,
(firstSelected.position.z + point.z) / 2
);
setCurrentLine({
start: firstSelected.position,
end: point,
mid: midPoint,
});
console.log({
start: firstSelected.position,
end: point,
mid: midPoint,
});
2025-03-25 12:04:20 +00:00
// setIsConnecting(true);
2025-03-25 12:04:20 +00:00
if (sphereIntersects.length > 0) {
setHelperLineColor(isInvalidConnection ? 'red' : '#6cf542');
} else {
setHelperLineColor('yellow');
}
} else {
setCurrentLine(null);
setIsConnecting(false);
}
} else {
setCurrentLine(null);
setIsConnecting(false);
}
});
return (
<>
{simulationPaths.flatMap(path => {
if (path.type === 'Conveyor') {
return path.points.flatMap(point =>
point.connections.targets.map((target, index) => {
const targetPath = simulationPaths.find(p => p.modeluuid === target.pathUUID);
if (targetPath?.type === 'Vehicle') return null;
const fromSphere = pathsGroupRef.current?.getObjectByProperty('uuid', point.uuid);
const toSphere = pathsGroupRef.current?.getObjectByProperty('uuid', target.pointUUID);
if (fromSphere && toSphere) {
const fromWorldPosition = new THREE.Vector3();
const toWorldPosition = new THREE.Vector3();
fromSphere.getWorldPosition(fromWorldPosition);
toSphere.getWorldPosition(toWorldPosition);
const distance = fromWorldPosition.distanceTo(toWorldPosition);
const heightFactor = Math.max(0.5, distance * 0.2);
const midPoint = new THREE.Vector3(
(fromWorldPosition.x + toWorldPosition.x) / 2,
Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor,
(fromWorldPosition.z + toWorldPosition.z) / 2
);
return (
<QuadraticBezierLine
key={`${point.uuid}-${target.pointUUID}-${index}`}
start={fromWorldPosition.toArray()}
end={toWorldPosition.toArray()}
mid={midPoint.toArray()}
color="white"
lineWidth={4}
dashed
dashSize={0.75}
dashScale={20}
/>
);
}
return null;
})
);
} else if (path.type === 'Vehicle') {
return path.point.connections.targets.map((target, index) => {
const fromSphere = pathsGroupRef.current?.getObjectByProperty('uuid', path.point.uuid);
const toSphere = pathsGroupRef.current?.getObjectByProperty('uuid', target.pointUUID);
if (fromSphere && toSphere) {
const fromWorldPosition = new THREE.Vector3();
const toWorldPosition = new THREE.Vector3();
fromSphere.getWorldPosition(fromWorldPosition);
toSphere.getWorldPosition(toWorldPosition);
const distance = fromWorldPosition.distanceTo(toWorldPosition);
const heightFactor = Math.max(0.5, distance * 0.2);
const midPoint = new THREE.Vector3(
(fromWorldPosition.x + toWorldPosition.x) / 2,
Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor,
(fromWorldPosition.z + toWorldPosition.z) / 2
);
return (
<QuadraticBezierLine
key={`${path.point.uuid}-${target.pointUUID}-${index}`}
start={fromWorldPosition.toArray()}
end={toWorldPosition.toArray()}
mid={midPoint.toArray()}
color="orange"
lineWidth={4}
dashed
dashSize={0.75}
dashScale={20}
/>
);
}
return null;
});
}
return [];
})}
2025-03-25 12:04:20 +00:00
{currentLine && (
<QuadraticBezierLine
start={currentLine.start.toArray()}
end={currentLine.end.toArray()}
mid={currentLine.mid.toArray()}
color={helperlineColor}
lineWidth={4}
dashed
dashSize={1}
dashScale={20}
/>
)}
</>
);
}
export default PathConnector;