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';
|
2025-03-28 13:40:49 +00:00
|
|
|
import * as Types from '../../../types/world/worldTypes';
|
2025-03-25 12:04:20 +00:00
|
|
|
import { QuadraticBezierLine } from '@react-three/drei';
|
2025-03-27 09:04:36 +00:00
|
|
|
import { useIsConnecting, useSimulationPaths } from '../../../store/store';
|
2025-03-25 12:04:20 +00:00
|
|
|
import useModuleStore from '../../../store/useModuleStore';
|
2025-04-04 11:27:18 +00:00
|
|
|
import { usePlayButtonStore } from '../../../store/usePlayButtonStore';
|
2025-03-25 12:04:20 +00:00
|
|
|
|
|
|
|
function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject<THREE.Group> }) {
|
2025-03-27 06:54:15 +00:00
|
|
|
const { activeModule } = useModuleStore();
|
2025-03-25 12:04:20 +00:00
|
|
|
const { gl, raycaster, scene, pointer, camera } = useThree();
|
2025-03-27 09:04:36 +00:00
|
|
|
const { setIsConnecting } = useIsConnecting();
|
2025-03-25 12:04:20 +00:00
|
|
|
const { simulationPaths, setSimulationPaths } = useSimulationPaths();
|
2025-04-04 11:27:18 +00:00
|
|
|
const { isPlaying } = usePlayButtonStore();
|
2025-03-25 12:04:20 +00:00
|
|
|
|
2025-03-27 09:04:36 +00:00
|
|
|
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');
|
|
|
|
|
2025-03-27 09:04:36 +00:00
|
|
|
const updatePathConnections = (
|
|
|
|
fromPathUUID: string,
|
|
|
|
fromPointUUID: string,
|
|
|
|
toPathUUID: string,
|
|
|
|
toPointUUID: string
|
|
|
|
) => {
|
|
|
|
const updatedPaths = simulationPaths.map(path => {
|
2025-03-28 13:40:49 +00:00
|
|
|
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
|
2025-03-27 09:04:36 +00:00
|
|
|
};
|
2025-03-28 13:40:49 +00:00
|
|
|
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]
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2025-03-27 09:04:36 +00:00
|
|
|
}
|
2025-03-28 13:40:49 +00:00
|
|
|
return point;
|
|
|
|
})
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else if (path.modeluuid === toPathUUID) {
|
|
|
|
return {
|
|
|
|
...path,
|
|
|
|
points: path.points.map(point => {
|
|
|
|
if (point.uuid === toPointUUID) {
|
|
|
|
const reverseTarget = {
|
|
|
|
pathUUID: fromPathUUID,
|
|
|
|
pointUUID: fromPointUUID
|
2025-03-27 09:04:36 +00:00
|
|
|
};
|
2025-03-28 13:40:49 +00:00
|
|
|
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]
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2025-03-27 09:04:36 +00:00
|
|
|
}
|
2025-03-28 13:40:49 +00:00
|
|
|
return point;
|
|
|
|
})
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2025-03-29 12:44:29 +00:00
|
|
|
// In the updatePathConnections function, modify the Vehicle handling section:
|
2025-03-28 13:40:49 +00:00
|
|
|
else if (path.type === 'Vehicle') {
|
|
|
|
// Handle outgoing connections from Vehicle
|
2025-04-04 11:27:18 +00:00
|
|
|
if (path.modeluuid === fromPathUUID && path.points.uuid === fromPointUUID) {
|
2025-03-28 13:40:49 +00:00
|
|
|
const newTarget = {
|
|
|
|
pathUUID: toPathUUID,
|
|
|
|
pointUUID: toPointUUID
|
|
|
|
};
|
2025-04-04 11:27:18 +00:00
|
|
|
const existingTargets = path.points.connections.targets || [];
|
2025-03-28 13:40:49 +00:00
|
|
|
|
2025-03-31 08:58:24 +00:00
|
|
|
// Check if target is a Conveyor
|
2025-03-29 12:44:29 +00:00
|
|
|
const toPath = simulationPaths.find(p => p.modeluuid === toPathUUID);
|
2025-03-31 08:58:24 +00:00
|
|
|
if (toPath?.type !== 'Conveyor') {
|
|
|
|
console.log("Vehicle can only connect to Conveyors");
|
2025-03-29 12:44:29 +00:00
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2025-03-31 08:58:24 +00:00
|
|
|
// Check if already has a connection
|
|
|
|
if (existingTargets.length >= 1) {
|
|
|
|
console.log("Vehicle can have only one connection");
|
2025-03-29 12:44:29 +00:00
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2025-03-28 13:40:49 +00:00
|
|
|
if (!existingTargets.some(target =>
|
|
|
|
target.pathUUID === newTarget.pathUUID &&
|
|
|
|
target.pointUUID === newTarget.pointUUID
|
|
|
|
)) {
|
|
|
|
return {
|
|
|
|
...path,
|
2025-04-04 11:27:18 +00:00
|
|
|
points: {
|
|
|
|
...path.points,
|
2025-03-28 13:40:49 +00:00
|
|
|
connections: {
|
2025-04-04 11:27:18 +00:00
|
|
|
...path.points.connections,
|
2025-03-28 13:40:49 +00:00
|
|
|
targets: [...existingTargets, newTarget]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Handle incoming connections to Vehicle
|
2025-04-04 11:27:18 +00:00
|
|
|
else if (path.modeluuid === toPathUUID && path.points.uuid === toPointUUID) {
|
2025-03-28 13:40:49 +00:00
|
|
|
const reverseTarget = {
|
|
|
|
pathUUID: fromPathUUID,
|
|
|
|
pointUUID: fromPointUUID
|
|
|
|
};
|
2025-04-04 11:27:18 +00:00
|
|
|
const existingTargets = path.points.connections.targets || [];
|
2025-03-28 13:40:49 +00:00
|
|
|
|
2025-03-31 08:58:24 +00:00
|
|
|
// Check if source is a Conveyor
|
2025-03-29 12:44:29 +00:00
|
|
|
const fromPath = simulationPaths.find(p => p.modeluuid === fromPathUUID);
|
2025-03-31 08:58:24 +00:00
|
|
|
if (fromPath?.type !== 'Conveyor') {
|
|
|
|
console.log("Vehicle can only connect to Conveyors");
|
2025-03-29 12:44:29 +00:00
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2025-03-31 08:58:24 +00:00
|
|
|
// Check if already has a connection
|
|
|
|
if (existingTargets.length >= 1) {
|
|
|
|
console.log("Vehicle can have only one connection");
|
2025-03-29 12:44:29 +00:00
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2025-03-28 13:40:49 +00:00
|
|
|
if (!existingTargets.some(target =>
|
|
|
|
target.pathUUID === reverseTarget.pathUUID &&
|
|
|
|
target.pointUUID === reverseTarget.pointUUID
|
|
|
|
)) {
|
|
|
|
return {
|
|
|
|
...path,
|
2025-04-04 11:27:18 +00:00
|
|
|
points: {
|
|
|
|
...path.points,
|
2025-03-28 13:40:49 +00:00
|
|
|
connections: {
|
2025-04-04 11:27:18 +00:00
|
|
|
...path.points.connections,
|
2025-03-28 13:40:49 +00:00
|
|
|
targets: [...existingTargets, reverseTarget]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2025-03-29 12:44:29 +00:00
|
|
|
return path;
|
2025-03-27 09:04:36 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
2025-03-31 08:58:24 +00:00
|
|
|
|
2025-03-25 12:04:20 +00:00
|
|
|
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;
|
|
|
|
|
2025-03-28 13:40:49 +00:00
|
|
|
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);
|
|
|
|
|
2025-03-28 13:40:49 +00:00
|
|
|
let isStartOrEnd = false;
|
2025-03-25 12:04:20 +00:00
|
|
|
|
2025-04-04 11:27:18 +00:00
|
|
|
if (intersected.userData.path.points && intersected.userData.path.points.length > 1) {
|
2025-03-28 13:40:49 +00:00
|
|
|
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
|
|
|
);
|
2025-04-04 11:27:18 +00:00
|
|
|
} else if (intersected.userData.path.points) {
|
|
|
|
isStartOrEnd = sphereUUID === intersected.userData.path.points.uuid;
|
2025-03-28 13:40:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pathUUID) {
|
|
|
|
const firstPath = simulationPaths.find(p => p.modeluuid === firstSelected?.pathUUID);
|
|
|
|
const secondPath = simulationPaths.find(p => p.modeluuid === pathUUID);
|
|
|
|
|
2025-03-29 12:44:29 +00:00
|
|
|
// Prevent vehicle-to-vehicle connections
|
2025-03-28 13:40:49 +00:00
|
|
|
if (firstPath && secondPath && firstPath.type === 'Vehicle' && secondPath.type === 'Vehicle') {
|
|
|
|
console.log("Cannot connect two vehicle paths together");
|
|
|
|
return;
|
|
|
|
}
|
2025-03-25 12:04:20 +00:00
|
|
|
|
2025-03-29 12:44:29 +00:00
|
|
|
// Prevent conveyor middle point to conveyor connections
|
|
|
|
if (firstPath && secondPath &&
|
|
|
|
firstPath.type === 'Conveyor' &&
|
|
|
|
secondPath.type === 'Conveyor' &&
|
|
|
|
!firstSelected?.isCorner) {
|
|
|
|
console.log("Conveyor middle points can only connect to non-conveyor paths");
|
2025-03-25 12:04:20 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-29 12:44:29 +00:00
|
|
|
// Check if this specific connection already exists
|
|
|
|
const isDuplicateConnection = firstSelected
|
|
|
|
? simulationPaths.some(path => {
|
|
|
|
if (path.modeluuid === firstSelected.pathUUID) {
|
|
|
|
if (path.type === 'Conveyor') {
|
|
|
|
const point = path.points.find(p => p.uuid === firstSelected.sphereUUID);
|
|
|
|
return point?.connections.targets.some(t =>
|
|
|
|
t.pathUUID === pathUUID && t.pointUUID === sphereUUID
|
|
|
|
);
|
|
|
|
} else if (path.type === 'Vehicle') {
|
2025-04-04 11:27:18 +00:00
|
|
|
return path.points.connections.targets.some(t =>
|
2025-03-29 12:44:29 +00:00
|
|
|
t.pathUUID === pathUUID && t.pointUUID === sphereUUID
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
})
|
|
|
|
: false;
|
|
|
|
|
|
|
|
if (isDuplicateConnection) {
|
|
|
|
console.log("These points are already connected. Ignoring.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-31 08:58:24 +00:00
|
|
|
// For Vehicles, check if they're already connected to anything
|
|
|
|
if (intersected.userData.path.type === 'Vehicle') {
|
2025-04-04 11:27:18 +00:00
|
|
|
console.log('intersected: ', intersected);
|
|
|
|
const vehicleConnections = intersected.userData.path.points.connections.targets.length;
|
2025-03-31 08:58:24 +00:00
|
|
|
if (vehicleConnections >= 1) {
|
|
|
|
console.log("Vehicle can only have one connection");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// For non-Vehicle paths, check if already connected
|
2025-03-29 12:44:29 +00:00
|
|
|
if (intersected.userData.path.type !== 'Vehicle') {
|
|
|
|
const isAlreadyConnected = simulationPaths.some(path => {
|
|
|
|
if (path.type === 'Conveyor') {
|
|
|
|
return path.points.some(point =>
|
|
|
|
point.uuid === sphereUUID &&
|
|
|
|
point.connections.targets.length > 0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return false;
|
2025-03-25 12:04:20 +00:00
|
|
|
});
|
|
|
|
|
2025-03-29 12:44:29 +00:00
|
|
|
if (isAlreadyConnected) {
|
|
|
|
console.log("Conveyor point is already connected. Ignoring.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstSelected) {
|
2025-03-31 08:58:24 +00:00
|
|
|
// Check if trying to connect Vehicle to non-Conveyor
|
|
|
|
if ((firstPath?.type === 'Vehicle' && secondPath?.type !== 'Conveyor') ||
|
|
|
|
(secondPath?.type === 'Vehicle' && firstPath?.type !== 'Conveyor')) {
|
|
|
|
console.log("Vehicle can only connect to Conveyors");
|
2025-03-29 12:44:29 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prevent same-path connections
|
2025-03-25 12:04:20 +00:00
|
|
|
if (firstSelected.pathUUID === pathUUID) {
|
|
|
|
console.log("Cannot connect spheres on the same path.");
|
|
|
|
return;
|
|
|
|
}
|
2025-03-29 12:44:29 +00:00
|
|
|
|
|
|
|
// At least one must be start/end point
|
2025-03-25 12:04:20 +00:00
|
|
|
if (!firstSelected.isCorner && !isStartOrEnd) {
|
|
|
|
console.log("At least one of the selected spheres must be a start or end point.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-29 12:44:29 +00:00
|
|
|
// All checks passed - make the connection
|
2025-03-27 09:04:36 +00:00
|
|
|
handleAddConnection(
|
|
|
|
firstSelected.pathUUID,
|
|
|
|
firstSelected.sphereUUID,
|
|
|
|
pathUUID,
|
|
|
|
sphereUUID
|
|
|
|
);
|
2025-03-29 12:44:29 +00:00
|
|
|
} else {
|
|
|
|
// First selection - just store it
|
|
|
|
setFirstSelected({
|
|
|
|
pathUUID,
|
|
|
|
sphereUUID,
|
|
|
|
position: worldPosition,
|
|
|
|
isCorner: isStartOrEnd
|
|
|
|
});
|
|
|
|
setIsConnecting(true);
|
2025-03-25 12:04:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2025-03-29 12:44:29 +00:00
|
|
|
// Clicked outside - cancel connection
|
2025-03-25 12:04:20 +00:00
|
|
|
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);
|
|
|
|
};
|
2025-03-27 09:04:36 +00:00
|
|
|
}, [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") &&
|
2025-03-28 13:40:49 +00:00
|
|
|
!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) =>
|
2025-03-28 13:40:49 +00:00
|
|
|
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);
|
2025-03-28 13:40:49 +00:00
|
|
|
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';
|
|
|
|
|
2025-03-29 12:44:29 +00:00
|
|
|
// Inside the useFrame hook, where we check for snapped spheres:
|
2025-03-28 13:40:49 +00:00
|
|
|
const isConnectable = (pathData.type === 'Vehicle' ||
|
|
|
|
(pathData.points.length > 0 && (
|
|
|
|
sphereUUID === pathData.points[0].uuid ||
|
|
|
|
sphereUUID === pathData.points[pathData.points.length - 1].uuid
|
2025-03-29 12:44:29 +00:00
|
|
|
))) &&
|
|
|
|
!isVehicleToVehicle &&
|
|
|
|
!(firstPath?.type === 'Conveyor' &&
|
|
|
|
pathData.type === 'Conveyor' &&
|
|
|
|
!firstSelected.isCorner);
|
|
|
|
|
|
|
|
// Check for duplicate connection (regardless of path type)
|
|
|
|
const isDuplicateConnection = simulationPaths.some(path => {
|
|
|
|
if (path.modeluuid === firstSelected.pathUUID) {
|
|
|
|
if (path.type === 'Conveyor') {
|
|
|
|
const point = path.points.find(p => p.uuid === firstSelected.sphereUUID);
|
|
|
|
return point?.connections.targets.some(t =>
|
|
|
|
t.pathUUID === pathUUID && t.pointUUID === sphereUUID
|
|
|
|
);
|
|
|
|
} else if (path.type === 'Vehicle') {
|
2025-04-04 11:27:18 +00:00
|
|
|
return path.points.connections.targets.some(t =>
|
2025-03-29 12:44:29 +00:00
|
|
|
t.pathUUID === pathUUID && t.pointUUID === sphereUUID
|
|
|
|
);
|
|
|
|
}
|
2025-03-28 13:40:49 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
2025-03-25 12:04:20 +00:00
|
|
|
|
2025-03-29 12:44:29 +00:00
|
|
|
// For non-Vehicle paths, check if already connected
|
|
|
|
const isNonVehicleAlreadyConnected = pathData.type !== 'Vehicle' &&
|
|
|
|
simulationPaths.some(path => {
|
|
|
|
if (path.type === 'Conveyor') {
|
|
|
|
return path.points.some(point =>
|
|
|
|
point.uuid === sphereUUID &&
|
|
|
|
point.connections.targets.length > 0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
2025-03-31 08:58:24 +00:00
|
|
|
// Check vehicle connection rules
|
2025-03-29 12:44:29 +00:00
|
|
|
const isVehicleAtMaxConnections = pathData.type === 'Vehicle' &&
|
2025-04-04 11:27:18 +00:00
|
|
|
pathData.points.connections.targets.length >= 1;
|
2025-03-31 08:58:24 +00:00
|
|
|
const isVehicleConnectingToNonConveyor =
|
|
|
|
(firstPath?.type === 'Vehicle' && secondPath?.type !== 'Conveyor') ||
|
|
|
|
(secondPath?.type === 'Vehicle' && firstPath?.type !== 'Conveyor');
|
2025-03-29 12:44:29 +00:00
|
|
|
|
2025-03-25 12:04:20 +00:00
|
|
|
if (
|
2025-03-29 12:44:29 +00:00
|
|
|
!isDuplicateConnection &&
|
2025-03-28 13:40:49 +00:00
|
|
|
!isVehicleToVehicle &&
|
2025-03-29 12:44:29 +00:00
|
|
|
!isNonVehicleAlreadyConnected &&
|
|
|
|
!isVehicleAtMaxConnections &&
|
2025-03-31 08:58:24 +00:00
|
|
|
!isVehicleConnectingToNonConveyor &&
|
2025-03-25 12:04:20 +00:00
|
|
|
firstSelected.sphereUUID !== sphereUUID &&
|
|
|
|
firstSelected.pathUUID !== pathUUID &&
|
2025-03-28 13:40:49 +00:00
|
|
|
(firstSelected.isCorner || isConnectable)
|
2025-03-25 12:04:20 +00:00
|
|
|
) {
|
2025-03-28 13:40:49 +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,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (sphereIntersects.length > 0) {
|
|
|
|
setHelperLineColor(isInvalidConnection ? 'red' : '#6cf542');
|
|
|
|
} else {
|
|
|
|
setHelperLineColor('yellow');
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
setCurrentLine(null);
|
|
|
|
setIsConnecting(false);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
setCurrentLine(null);
|
|
|
|
setIsConnecting(false);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
2025-04-04 11:27:18 +00:00
|
|
|
<group name='simulationConnectionGroup' visible={!isPlaying} >
|
2025-03-28 13:40:49 +00:00
|
|
|
{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') {
|
2025-04-04 11:27:18 +00:00
|
|
|
return path.points.connections.targets.map((target, index) => {
|
|
|
|
const fromSphere = pathsGroupRef.current?.getObjectByProperty('uuid', path.points.uuid);
|
2025-03-27 09:04:36 +00:00
|
|
|
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
|
2025-04-04 11:27:18 +00:00
|
|
|
key={`${path.points.uuid}-${target.pointUUID}-${index}`}
|
2025-03-27 09:04:36 +00:00
|
|
|
start={fromWorldPosition.toArray()}
|
|
|
|
end={toWorldPosition.toArray()}
|
|
|
|
mid={midPoint.toArray()}
|
2025-03-28 13:40:49 +00:00
|
|
|
color="orange"
|
2025-03-27 09:04:36 +00:00
|
|
|
lineWidth={4}
|
|
|
|
dashed
|
2025-03-28 13:40:49 +00:00
|
|
|
dashSize={0.75}
|
2025-03-27 09:04:36 +00:00
|
|
|
dashScale={20}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return null;
|
2025-03-28 13:40:49 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
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}
|
|
|
|
/>
|
|
|
|
)}
|
2025-04-04 11:27:18 +00:00
|
|
|
</group>
|
2025-03-25 12:04:20 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-03-25 08:30:03 +00:00
|
|
|
export default PathConnector;
|