1500 lines
52 KiB
TypeScript
1500 lines
52 KiB
TypeScript
import { useFrame, useThree } from "@react-three/fiber";
|
|
import React, { useEffect, useRef, useState } from "react";
|
|
import * as THREE from "three";
|
|
import * as Types from "../../../types/world/worldTypes";
|
|
import * as SimulationTypes from "../../../types/simulation";
|
|
import { QuadraticBezierLine } from "@react-three/drei";
|
|
import {
|
|
useDeleteTool,
|
|
useIsConnecting,
|
|
useRenderDistance,
|
|
useSimulationStates,
|
|
useSocketStore,
|
|
} from "../../../store/store";
|
|
import useModuleStore from "../../../store/useModuleStore";
|
|
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
|
import { setEventApi } from "../../../services/factoryBuilder/assest/floorAsset/setEventsApt";
|
|
|
|
function PathConnector({
|
|
pathsGroupRef,
|
|
}: {
|
|
pathsGroupRef: React.MutableRefObject<THREE.Group>;
|
|
}) {
|
|
const { activeModule } = useModuleStore();
|
|
const { gl, raycaster, scene, pointer, camera } = useThree();
|
|
const { deleteTool } = useDeleteTool();
|
|
const { renderDistance } = useRenderDistance();
|
|
const { setIsConnecting } = useIsConnecting();
|
|
const { simulationStates, setSimulationStates } = useSimulationStates();
|
|
const { isPlaying } = usePlayButtonStore();
|
|
const { socket } = useSocketStore();
|
|
const groupRefs = useRef<{ [key: string]: any }>({});
|
|
|
|
const [firstSelected, setFirstSelected] = useState<{
|
|
modelUUID: string;
|
|
sphereUUID: string;
|
|
position: THREE.Vector3;
|
|
isCorner: boolean;
|
|
} | null>(null);
|
|
const [currentLine, setCurrentLine] = useState<{
|
|
start: THREE.Vector3;
|
|
end: THREE.Vector3;
|
|
mid: THREE.Vector3;
|
|
} | null>(null);
|
|
const [helperlineColor, setHelperLineColor] = useState<string>("red");
|
|
const [hoveredLineKey, setHoveredLineKey] = useState<string | null>(null);
|
|
|
|
const updatePathConnections = (
|
|
fromModelUUID: string,
|
|
fromPointUUID: string,
|
|
toModelUUID: string,
|
|
toPointUUID: string
|
|
) => {
|
|
const updatedPaths = simulationStates.map((path) => {
|
|
if (path.type === "Conveyor") {
|
|
// Handle outgoing connections from Conveyor
|
|
if (path.modeluuid === fromModelUUID) {
|
|
return {
|
|
...path,
|
|
points: path.points.map((point) => {
|
|
if (point.uuid === fromPointUUID) {
|
|
const newTarget = {
|
|
modelUUID: toModelUUID,
|
|
pointUUID: toPointUUID,
|
|
};
|
|
const existingTargets = point.connections.targets || [];
|
|
|
|
if (
|
|
!existingTargets.some(
|
|
(target) =>
|
|
target.modelUUID === newTarget.modelUUID &&
|
|
target.pointUUID === newTarget.pointUUID
|
|
)
|
|
) {
|
|
return {
|
|
...point,
|
|
connections: {
|
|
...point.connections,
|
|
targets: [...existingTargets, newTarget],
|
|
},
|
|
};
|
|
}
|
|
}
|
|
return point;
|
|
}),
|
|
};
|
|
}
|
|
// Handle incoming connections to Conveyor
|
|
else if (path.modeluuid === toModelUUID) {
|
|
return {
|
|
...path,
|
|
points: path.points.map((point) => {
|
|
if (point.uuid === toPointUUID) {
|
|
const reverseTarget = {
|
|
modelUUID: fromModelUUID,
|
|
pointUUID: fromPointUUID,
|
|
};
|
|
const existingTargets = point.connections.targets || [];
|
|
|
|
if (
|
|
!existingTargets.some(
|
|
(target) =>
|
|
target.modelUUID === reverseTarget.modelUUID &&
|
|
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 === fromModelUUID &&
|
|
path.points.uuid === fromPointUUID
|
|
) {
|
|
const newTarget = {
|
|
modelUUID: toModelUUID,
|
|
pointUUID: toPointUUID,
|
|
};
|
|
const existingTargets = path.points.connections.targets || [];
|
|
|
|
// Check if target is a Conveyor
|
|
const toPath = simulationStates.find(
|
|
(p) => p.modeluuid === toModelUUID
|
|
);
|
|
if (toPath?.type !== "Conveyor") {
|
|
console.log("Vehicle can only connect to Conveyors");
|
|
return path;
|
|
}
|
|
|
|
// Check if already has a connection
|
|
if (existingTargets.length >= 1) {
|
|
console.log("Vehicle can have only one connection");
|
|
return path;
|
|
}
|
|
|
|
if (
|
|
!existingTargets.some(
|
|
(target) =>
|
|
target.modelUUID === newTarget.modelUUID &&
|
|
target.pointUUID === newTarget.pointUUID
|
|
)
|
|
) {
|
|
return {
|
|
...path,
|
|
points: {
|
|
...path.points,
|
|
connections: {
|
|
...path.points.connections,
|
|
targets: [...existingTargets, newTarget],
|
|
},
|
|
},
|
|
};
|
|
}
|
|
}
|
|
// Handle incoming connections to Vehicle
|
|
else if (
|
|
path.modeluuid === toModelUUID &&
|
|
path.points.uuid === toPointUUID
|
|
) {
|
|
const reverseTarget = {
|
|
modelUUID: fromModelUUID,
|
|
pointUUID: fromPointUUID,
|
|
};
|
|
const existingTargets = path.points.connections.targets || [];
|
|
|
|
// Check if source is a Conveyor
|
|
const fromPath = simulationStates.find(
|
|
(p) => p.modeluuid === fromModelUUID
|
|
);
|
|
if (fromPath?.type !== "Conveyor") {
|
|
console.log("Vehicle can only connect to Conveyors");
|
|
return path;
|
|
}
|
|
|
|
// Check if already has a connection
|
|
if (existingTargets.length >= 1) {
|
|
console.log("Vehicle can have only one connection");
|
|
return path;
|
|
}
|
|
|
|
if (
|
|
!existingTargets.some(
|
|
(target) =>
|
|
target.modelUUID === reverseTarget.modelUUID &&
|
|
target.pointUUID === reverseTarget.pointUUID
|
|
)
|
|
) {
|
|
return {
|
|
...path,
|
|
points: {
|
|
...path.points,
|
|
connections: {
|
|
...path.points.connections,
|
|
targets: [...existingTargets, reverseTarget],
|
|
},
|
|
},
|
|
};
|
|
}
|
|
}
|
|
return path;
|
|
} else if (path.type === "StaticMachine") {
|
|
// Handle outgoing connections from StaticMachine
|
|
if (
|
|
path.modeluuid === fromModelUUID &&
|
|
path.points.uuid === fromPointUUID
|
|
) {
|
|
const newTarget = {
|
|
modelUUID: toModelUUID,
|
|
pointUUID: toPointUUID,
|
|
};
|
|
|
|
// Ensure target is an ArmBot
|
|
const toPath = simulationStates.find(
|
|
(p) => p.modeluuid === toModelUUID
|
|
);
|
|
if (toPath?.type !== "ArmBot") {
|
|
console.log("StaticMachine can only connect to ArmBot");
|
|
return path;
|
|
}
|
|
|
|
const existingTargets = path.points.connections.targets || [];
|
|
|
|
// Allow only one connection
|
|
if (existingTargets.length >= 1) {
|
|
console.log("StaticMachine can only have one connection");
|
|
return path;
|
|
}
|
|
|
|
if (
|
|
!existingTargets.some(
|
|
(target) =>
|
|
target.modelUUID === newTarget.modelUUID &&
|
|
target.pointUUID === newTarget.pointUUID
|
|
)
|
|
) {
|
|
return {
|
|
...path,
|
|
points: {
|
|
...path.points,
|
|
connections: {
|
|
...path.points.connections,
|
|
targets: [...existingTargets, newTarget],
|
|
},
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
// Handle incoming connections to StaticMachine
|
|
else if (
|
|
path.modeluuid === toModelUUID &&
|
|
path.points.uuid === toPointUUID
|
|
) {
|
|
const reverseTarget = {
|
|
modelUUID: fromModelUUID,
|
|
pointUUID: fromPointUUID,
|
|
};
|
|
|
|
const fromPath = simulationStates.find(
|
|
(p) => p.modeluuid === fromModelUUID
|
|
);
|
|
if (fromPath?.type !== "ArmBot") {
|
|
console.log("StaticMachine can only be connected from ArmBot");
|
|
return path;
|
|
}
|
|
|
|
const existingTargets = path.points.connections.targets || [];
|
|
|
|
if (existingTargets.length >= 1) {
|
|
console.log("StaticMachine can only have one connection");
|
|
return path;
|
|
}
|
|
|
|
if (
|
|
!existingTargets.some(
|
|
(target) =>
|
|
target.modelUUID === reverseTarget.modelUUID &&
|
|
target.pointUUID === reverseTarget.pointUUID
|
|
)
|
|
) {
|
|
return {
|
|
...path,
|
|
points: {
|
|
...path.points,
|
|
connections: {
|
|
...path.points.connections,
|
|
targets: [...existingTargets, reverseTarget],
|
|
},
|
|
},
|
|
};
|
|
}
|
|
}
|
|
return path;
|
|
} else if (path.type === "ArmBot") {
|
|
// Handle outgoing connections from ArmBot
|
|
if (
|
|
path.modeluuid === fromModelUUID &&
|
|
path.points.uuid === fromPointUUID
|
|
) {
|
|
const newTarget = {
|
|
modelUUID: toModelUUID,
|
|
pointUUID: toPointUUID,
|
|
};
|
|
|
|
const toPath = simulationStates.find(
|
|
(p) => p.modeluuid === toModelUUID
|
|
);
|
|
if (!toPath) return path;
|
|
|
|
const existingTargets = path.points.connections.targets || [];
|
|
|
|
// Check if connecting to a StaticMachine and already connected to one
|
|
const alreadyConnectedToStatic = existingTargets.some((target) => {
|
|
const targetPath = simulationStates.find(
|
|
(p) => p.modeluuid === target.modelUUID
|
|
);
|
|
return targetPath?.type === "StaticMachine";
|
|
});
|
|
|
|
if (toPath.type === "StaticMachine") {
|
|
if (alreadyConnectedToStatic) {
|
|
console.log("ArmBot can only connect to one StaticMachine");
|
|
return path;
|
|
}
|
|
}
|
|
|
|
if (
|
|
!existingTargets.some(
|
|
(target) =>
|
|
target.modelUUID === newTarget.modelUUID &&
|
|
target.pointUUID === newTarget.pointUUID
|
|
)
|
|
) {
|
|
return {
|
|
...path,
|
|
points: {
|
|
...path.points,
|
|
connections: {
|
|
...path.points.connections,
|
|
targets: [...existingTargets, newTarget],
|
|
},
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
// Handle incoming connections to ArmBot
|
|
else if (
|
|
path.modeluuid === toModelUUID &&
|
|
path.points.uuid === toPointUUID
|
|
) {
|
|
const reverseTarget = {
|
|
modelUUID: fromModelUUID,
|
|
pointUUID: fromPointUUID,
|
|
};
|
|
|
|
const fromPath = simulationStates.find(
|
|
(p) => p.modeluuid === fromModelUUID
|
|
);
|
|
if (!fromPath) return path;
|
|
|
|
const existingTargets = path.points.connections.targets || [];
|
|
|
|
const alreadyConnectedFromStatic = existingTargets.some((target) => {
|
|
const targetPath = simulationStates.find(
|
|
(p) => p.modeluuid === target.modelUUID
|
|
);
|
|
return targetPath?.type === "StaticMachine";
|
|
});
|
|
|
|
if (fromPath.type === "StaticMachine") {
|
|
if (alreadyConnectedFromStatic) {
|
|
console.log(
|
|
"ArmBot can only be connected from one StaticMachine"
|
|
);
|
|
return path;
|
|
}
|
|
}
|
|
|
|
if (
|
|
!existingTargets.some(
|
|
(target) =>
|
|
target.modelUUID === reverseTarget.modelUUID &&
|
|
target.pointUUID === reverseTarget.pointUUID
|
|
)
|
|
) {
|
|
return {
|
|
...path,
|
|
points: {
|
|
...path.points,
|
|
connections: {
|
|
...path.points.connections,
|
|
targets: [...existingTargets, reverseTarget],
|
|
},
|
|
},
|
|
};
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
|
|
return path;
|
|
});
|
|
|
|
setSimulationStates(updatedPaths);
|
|
|
|
const updatedPathDetails = updatedPaths.filter(
|
|
(path) =>
|
|
path.modeluuid === fromModelUUID || path.modeluuid === toModelUUID
|
|
);
|
|
|
|
updateBackend(updatedPathDetails);
|
|
};
|
|
|
|
const updateBackend = async (
|
|
updatedPaths: (
|
|
| SimulationTypes.ConveyorEventsSchema
|
|
| SimulationTypes.VehicleEventsSchema
|
|
| SimulationTypes.StaticMachineEventsSchema
|
|
| SimulationTypes.ArmBotEventsSchema
|
|
)[]
|
|
) => {
|
|
if (updatedPaths.length === 0) return;
|
|
const email = localStorage.getItem("email");
|
|
const organization = email ? email.split("@")[1].split(".")[0] : "";
|
|
|
|
updatedPaths.forEach(async (updatedPath) => {
|
|
if (updatedPath.type === "Conveyor") {
|
|
// await setEventApi(
|
|
// organization,
|
|
// updatedPath.modeluuid,
|
|
// { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed }
|
|
// );
|
|
|
|
const data = {
|
|
organization: organization,
|
|
modeluuid: updatedPath.modeluuid,
|
|
eventData: {
|
|
type: "Conveyor",
|
|
points: updatedPath.points,
|
|
speed: updatedPath.speed,
|
|
},
|
|
};
|
|
|
|
socket.emit("v2:model-asset:updateEventData", data);
|
|
} else if (updatedPath.type === "Vehicle") {
|
|
// await setEventApi(
|
|
// organization,
|
|
// updatedPath.modeluuid,
|
|
// { type: "Vehicle", points: updatedPath.points }
|
|
// );
|
|
|
|
const data = {
|
|
organization: organization,
|
|
modeluuid: updatedPath.modeluuid,
|
|
eventData: { type: "Vehicle", points: updatedPath.points },
|
|
};
|
|
|
|
socket.emit("v2:model-asset:updateEventData", data);
|
|
} else if (updatedPath.type === "StaticMachine") {
|
|
// await setEventApi(
|
|
// organization,
|
|
// updatedPath.modeluuid,
|
|
// { type: "StaticMachine", points: updatedPath.points }
|
|
// );
|
|
|
|
const data = {
|
|
organization: organization,
|
|
modeluuid: updatedPath.modeluuid,
|
|
eventData: { type: "StaticMachine", points: updatedPath.points },
|
|
};
|
|
|
|
socket.emit("v2:model-asset:updateEventData", data);
|
|
} else if (updatedPath.type === "ArmBot") {
|
|
// await setEventApi(
|
|
// organization,
|
|
// updatedPath.modeluuid,
|
|
// { type: "ArmBot", points: updatedPath.points }
|
|
// );
|
|
|
|
const data = {
|
|
organization: organization,
|
|
modeluuid: updatedPath.modeluuid,
|
|
eventData: { type: "ArmBot", points: updatedPath.points },
|
|
};
|
|
|
|
socket.emit("v2:model-asset:updateEventData", data);
|
|
}
|
|
});
|
|
};
|
|
|
|
const handleAddConnection = (
|
|
fromModelUUID: string,
|
|
fromUUID: string,
|
|
toModelUUID: string,
|
|
toUUID: string
|
|
) => {
|
|
updatePathConnections(fromModelUUID, fromUUID, toModelUUID, toUUID);
|
|
setFirstSelected(null);
|
|
setCurrentLine(null);
|
|
setIsConnecting(false);
|
|
};
|
|
|
|
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")) {
|
|
const modelUUID = intersected.userData.path.modeluuid;
|
|
const sphereUUID = intersected.uuid;
|
|
const worldPosition = new THREE.Vector3();
|
|
intersected.getWorldPosition(worldPosition);
|
|
|
|
let isStartOrEnd = false;
|
|
|
|
if (
|
|
intersected.userData.path.points &&
|
|
intersected.userData.path.points.length > 1
|
|
) {
|
|
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);
|
|
} else if (intersected.userData.path.points) {
|
|
isStartOrEnd = sphereUUID === intersected.userData.path.points.uuid;
|
|
}
|
|
|
|
if (modelUUID) {
|
|
const firstPath = simulationStates.find(
|
|
(p) => p.modeluuid === firstSelected?.modelUUID
|
|
);
|
|
const secondPath = simulationStates.find(
|
|
(p) => p.modeluuid === modelUUID
|
|
);
|
|
|
|
// Prevent vehicle-to-vehicle connections
|
|
if (
|
|
firstPath &&
|
|
secondPath &&
|
|
firstPath.type === "Vehicle" &&
|
|
secondPath.type === "Vehicle"
|
|
) {
|
|
console.log("Cannot connect two vehicle paths together");
|
|
return;
|
|
}
|
|
|
|
// Prevent conveyor middle point to conveyor connections
|
|
if (
|
|
firstPath &&
|
|
secondPath &&
|
|
firstPath.type === "Conveyor" &&
|
|
secondPath.type === "Conveyor" &&
|
|
(!firstSelected?.isCorner || !isStartOrEnd)
|
|
) {
|
|
console.log(
|
|
"Conveyor connections must be between start/end points"
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Check if this specific connection already exists
|
|
const isDuplicateConnection = firstSelected
|
|
? simulationStates.some((path) => {
|
|
if (path.modeluuid === firstSelected.modelUUID) {
|
|
if (path.type === "Conveyor") {
|
|
const point = path.points.find(
|
|
(p) => p.uuid === firstSelected.sphereUUID
|
|
);
|
|
return point?.connections.targets.some(
|
|
(t) =>
|
|
t.modelUUID === modelUUID &&
|
|
t.pointUUID === sphereUUID
|
|
);
|
|
} else if (path.type === "Vehicle") {
|
|
return path.points.connections.targets.some(
|
|
(t) =>
|
|
t.modelUUID === modelUUID &&
|
|
t.pointUUID === sphereUUID
|
|
);
|
|
}
|
|
}
|
|
return false;
|
|
})
|
|
: false;
|
|
|
|
if (isDuplicateConnection) {
|
|
console.log("These points are already connected. Ignoring.");
|
|
return;
|
|
}
|
|
|
|
// For Vehicles, check if they're already connected to anything
|
|
if (intersected.userData.path.type === "Vehicle") {
|
|
const vehicleConnections =
|
|
intersected.userData.path.points.connections.targets.length;
|
|
if (vehicleConnections >= 1) {
|
|
console.log("Vehicle can only have one connection");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// For non-Vehicle paths, check if already connected
|
|
if (intersected.userData.path.type !== "Vehicle") {
|
|
const isAlreadyConnected = simulationStates.some((path) => {
|
|
if (path.type === "Conveyor") {
|
|
return path.points.some(
|
|
(point) =>
|
|
point.uuid === sphereUUID &&
|
|
point.connections.targets.length > 0
|
|
);
|
|
}
|
|
return false;
|
|
});
|
|
|
|
if (isAlreadyConnected) {
|
|
console.log("Conveyor point is already connected. Ignoring.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (firstSelected) {
|
|
// 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");
|
|
return;
|
|
}
|
|
|
|
// Prevent same-path connections
|
|
if (firstSelected.modelUUID === modelUUID) {
|
|
console.log("Cannot connect spheres on the same path.");
|
|
return;
|
|
}
|
|
|
|
// Check if StaticMachine is involved in the connection
|
|
if (
|
|
(firstPath?.type === "StaticMachine" &&
|
|
secondPath?.type !== "ArmBot") ||
|
|
(secondPath?.type === "StaticMachine" &&
|
|
firstPath?.type !== "ArmBot")
|
|
) {
|
|
console.log("StaticMachine can only connect to ArmBot");
|
|
return;
|
|
}
|
|
|
|
// Check if StaticMachine already has a connection
|
|
if (firstPath?.type === "StaticMachine") {
|
|
const staticConnections =
|
|
firstPath.points.connections.targets.length;
|
|
if (staticConnections >= 1) {
|
|
console.log("StaticMachine can only have one connection");
|
|
return;
|
|
}
|
|
}
|
|
if (secondPath?.type === "StaticMachine") {
|
|
const staticConnections =
|
|
secondPath.points.connections.targets.length;
|
|
if (staticConnections >= 1) {
|
|
console.log("StaticMachine can only have one connection");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check if ArmBot is involved
|
|
if (
|
|
(firstPath?.type === "ArmBot" &&
|
|
secondPath?.type === "StaticMachine") ||
|
|
(secondPath?.type === "ArmBot" &&
|
|
firstPath?.type === "StaticMachine")
|
|
) {
|
|
const armBotPath =
|
|
firstPath?.type === "ArmBot" ? firstPath : secondPath;
|
|
const staticPath =
|
|
firstPath?.type === "StaticMachine" ? firstPath : secondPath;
|
|
|
|
const armBotConnections =
|
|
armBotPath.points.connections.targets || [];
|
|
const alreadyConnectedToStatic = armBotConnections.some(
|
|
(target) => {
|
|
const targetPath = simulationStates.find(
|
|
(p) => p.modeluuid === target.modelUUID
|
|
);
|
|
return targetPath?.type === "StaticMachine";
|
|
}
|
|
);
|
|
|
|
if (alreadyConnectedToStatic) {
|
|
console.log("ArmBot can only connect to one StaticMachine");
|
|
return;
|
|
}
|
|
|
|
const staticConnections =
|
|
staticPath.points.connections.targets.length;
|
|
if (staticConnections >= 1) {
|
|
console.log("StaticMachine can only have one connection");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Prevent ArmBot ↔ ArmBot
|
|
if (
|
|
firstPath?.type === "ArmBot" &&
|
|
secondPath?.type === "ArmBot"
|
|
) {
|
|
console.log("Cannot connect two ArmBots together");
|
|
return;
|
|
}
|
|
|
|
// If one is ArmBot, ensure the other is StaticMachine or Conveyor
|
|
if (
|
|
firstPath?.type === "ArmBot" ||
|
|
secondPath?.type === "ArmBot"
|
|
) {
|
|
const otherType =
|
|
firstPath?.type === "ArmBot"
|
|
? secondPath?.type
|
|
: firstPath?.type;
|
|
if (otherType !== "StaticMachine" && otherType !== "Conveyor") {
|
|
console.log(
|
|
"ArmBot can only connect to Conveyors or one StaticMachine"
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// At least one must be start/end point
|
|
if (!firstSelected.isCorner && !isStartOrEnd) {
|
|
console.log(
|
|
"At least one of the selected spheres must be a start or end point."
|
|
);
|
|
return;
|
|
}
|
|
|
|
// All checks passed - make the connection
|
|
handleAddConnection(
|
|
firstSelected.modelUUID,
|
|
firstSelected.sphereUUID,
|
|
modelUUID,
|
|
sphereUUID
|
|
);
|
|
} else {
|
|
// First selection - just store it
|
|
setFirstSelected({
|
|
modelUUID,
|
|
sphereUUID,
|
|
position: worldPosition,
|
|
isCorner: isStartOrEnd,
|
|
});
|
|
setIsConnecting(true);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Clicked outside - cancel connection
|
|
setFirstSelected(null);
|
|
setCurrentLine(null);
|
|
setIsConnecting(false);
|
|
}
|
|
};
|
|
|
|
if (activeModule === "simulation" && !deleteTool) {
|
|
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, simulationStates, deleteTool]);
|
|
|
|
useFrame(() => {
|
|
Object.values(groupRefs.current).forEach((group) => {
|
|
if (group) {
|
|
const distance = new THREE.Vector3(
|
|
...group.position.toArray()
|
|
).distanceTo(camera.position);
|
|
group.visible = distance <= renderDistance && !isPlaying;
|
|
}
|
|
});
|
|
});
|
|
|
|
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") &&
|
|
!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;
|
|
modelUUID: 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"));
|
|
|
|
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 modelUUID = pathData.modeluuid;
|
|
|
|
const firstPath = simulationStates.find(
|
|
(p) => p.modeluuid === firstSelected.modelUUID
|
|
);
|
|
const secondPath = simulationStates.find(
|
|
(p) => p.modeluuid === modelUUID
|
|
);
|
|
const isVehicleToVehicle =
|
|
firstPath?.type === "Vehicle" && secondPath?.type === "Vehicle";
|
|
|
|
// Inside the useFrame hook, where we check for snapped spheres:
|
|
const isConnectable =
|
|
(pathData.type === "Vehicle" ||
|
|
pathData.type === "ArmBot" ||
|
|
(pathData.points.length > 0 &&
|
|
(sphereUUID === pathData.points[0].uuid ||
|
|
sphereUUID ===
|
|
pathData.points[pathData.points.length - 1].uuid ||
|
|
(pathData.type === "Conveyor" &&
|
|
firstPath?.type === "ArmBot")))) && // Allow ArmBot to connect to middle points
|
|
!isVehicleToVehicle &&
|
|
!(
|
|
firstPath?.type === "Conveyor" &&
|
|
pathData.type === "Conveyor" &&
|
|
!firstSelected.isCorner
|
|
);
|
|
|
|
// Check for duplicate connection (regardless of path type)
|
|
const isDuplicateConnection = simulationStates.some((path) => {
|
|
if (path.modeluuid === firstSelected.modelUUID) {
|
|
if (path.type === "Conveyor") {
|
|
const point = path.points.find(
|
|
(p) => p.uuid === firstSelected.sphereUUID
|
|
);
|
|
return point?.connections.targets.some(
|
|
(t) => t.modelUUID === modelUUID && t.pointUUID === sphereUUID
|
|
);
|
|
} else if (path.type === "Vehicle") {
|
|
return path.points.connections.targets.some(
|
|
(t) => t.modelUUID === modelUUID && t.pointUUID === sphereUUID
|
|
);
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
|
|
// For non-Vehicle paths, check if already connected
|
|
const isNonVehicleAlreadyConnected =
|
|
pathData.type !== "Vehicle" &&
|
|
simulationStates.some((path) => {
|
|
if (path.type === "Conveyor") {
|
|
return path.points.some(
|
|
(point) =>
|
|
point.uuid === sphereUUID &&
|
|
point.connections.targets.length > 0
|
|
);
|
|
}
|
|
return false;
|
|
});
|
|
|
|
// Check vehicle connection rules
|
|
const isVehicleAtMaxConnections =
|
|
pathData.type === "Vehicle" &&
|
|
pathData.points.connections.targets.length >= 1;
|
|
const isVehicleConnectingToNonConveyor =
|
|
(firstPath?.type === "Vehicle" && secondPath?.type !== "Conveyor") ||
|
|
(secondPath?.type === "Vehicle" && firstPath?.type !== "Conveyor");
|
|
|
|
// Check if StaticMachine is connecting to non-ArmBot
|
|
const isStaticMachineToNonArmBot =
|
|
(firstPath?.type === "StaticMachine" &&
|
|
secondPath?.type !== "ArmBot") ||
|
|
(secondPath?.type === "StaticMachine" &&
|
|
firstPath?.type !== "ArmBot");
|
|
|
|
// Check if StaticMachine already has a connection
|
|
const isStaticMachineAtMaxConnections =
|
|
(firstPath?.type === "StaticMachine" &&
|
|
firstPath.points.connections.targets.length >= 1) ||
|
|
(secondPath?.type === "StaticMachine" &&
|
|
secondPath.points.connections.targets.length >= 1);
|
|
|
|
// Check if ArmBot is connecting to StaticMachine
|
|
const isArmBotToStaticMachine =
|
|
(firstPath?.type === "ArmBot" &&
|
|
secondPath?.type === "StaticMachine") ||
|
|
(secondPath?.type === "ArmBot" &&
|
|
firstPath?.type === "StaticMachine");
|
|
|
|
// Prevent multiple StaticMachine connections to ArmBot
|
|
let isArmBotAlreadyConnectedToStatic = false;
|
|
if (isArmBotToStaticMachine) {
|
|
const armBotPath =
|
|
firstPath?.type === "ArmBot" ? firstPath : secondPath;
|
|
isArmBotAlreadyConnectedToStatic =
|
|
armBotPath.points.connections.targets.some((target) => {
|
|
const targetPath = simulationStates.find(
|
|
(p) => p.modeluuid === target.modelUUID
|
|
);
|
|
return targetPath?.type === "StaticMachine";
|
|
});
|
|
}
|
|
|
|
// Prevent ArmBot to ArmBot
|
|
const isArmBotToArmBot =
|
|
firstPath?.type === "ArmBot" && secondPath?.type === "ArmBot";
|
|
|
|
// If ArmBot is involved, other must be Conveyor or StaticMachine
|
|
const isArmBotToInvalidType =
|
|
(firstPath?.type === "ArmBot" || secondPath?.type === "ArmBot") &&
|
|
!(
|
|
firstPath?.type === "Conveyor" ||
|
|
firstPath?.type === "StaticMachine" ||
|
|
secondPath?.type === "Conveyor" ||
|
|
secondPath?.type === "StaticMachine"
|
|
);
|
|
|
|
if (
|
|
!isDuplicateConnection &&
|
|
!isVehicleToVehicle &&
|
|
!isNonVehicleAlreadyConnected &&
|
|
!isVehicleAtMaxConnections &&
|
|
!isVehicleConnectingToNonConveyor &&
|
|
!isStaticMachineToNonArmBot &&
|
|
!isStaticMachineAtMaxConnections &&
|
|
!isArmBotToArmBot &&
|
|
!isArmBotToInvalidType &&
|
|
!isArmBotAlreadyConnectedToStatic &&
|
|
firstSelected.sphereUUID !== sphereUUID &&
|
|
firstSelected.modelUUID !== modelUUID &&
|
|
(firstSelected.isCorner || isConnectable) &&
|
|
!(
|
|
firstPath?.type === "Conveyor" &&
|
|
pathData.type === "Conveyor" &&
|
|
!(firstSelected.isCorner && isConnectable)
|
|
)
|
|
) {
|
|
snappedSphere = {
|
|
sphereUUID,
|
|
position: spherePosition,
|
|
modelUUID,
|
|
isCorner: isConnectable,
|
|
};
|
|
} 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);
|
|
}
|
|
});
|
|
|
|
const removeConnections = (
|
|
connection1: { model: string; point: string },
|
|
connection2: { model: string; point: string }
|
|
) => {
|
|
const updatedStates = simulationStates.map((state) => {
|
|
// Handle Conveyor (which has multiple points)
|
|
if (state.type === "Conveyor") {
|
|
const updatedConveyor: SimulationTypes.ConveyorEventsSchema = {
|
|
...state,
|
|
points: state.points.map((point) => {
|
|
// Check if this point is either connection1 or connection2
|
|
if (
|
|
(state.modeluuid === connection1.model &&
|
|
point.uuid === connection1.point) ||
|
|
(state.modeluuid === connection2.model &&
|
|
point.uuid === connection2.point)
|
|
) {
|
|
return {
|
|
...point,
|
|
connections: {
|
|
...point.connections,
|
|
targets: point.connections.targets.filter((target) => {
|
|
// Remove the target that matches the other connection
|
|
return !(
|
|
(target.modelUUID === connection1.model &&
|
|
target.pointUUID === connection1.point) ||
|
|
(target.modelUUID === connection2.model &&
|
|
target.pointUUID === connection2.point)
|
|
);
|
|
}),
|
|
},
|
|
};
|
|
}
|
|
return point;
|
|
}),
|
|
};
|
|
return updatedConveyor;
|
|
}
|
|
// Handle Vehicle
|
|
else if (state.type === "Vehicle") {
|
|
if (
|
|
(state.modeluuid === connection1.model &&
|
|
state.points.uuid === connection1.point) ||
|
|
(state.modeluuid === connection2.model &&
|
|
state.points.uuid === connection2.point)
|
|
) {
|
|
const updatedVehicle: SimulationTypes.VehicleEventsSchema = {
|
|
...state,
|
|
points: {
|
|
...state.points,
|
|
connections: {
|
|
...state.points.connections,
|
|
targets: state.points.connections.targets.filter((target) => {
|
|
return !(
|
|
(target.modelUUID === connection1.model &&
|
|
target.pointUUID === connection1.point) ||
|
|
(target.modelUUID === connection2.model &&
|
|
target.pointUUID === connection2.point)
|
|
);
|
|
}),
|
|
},
|
|
// Ensure all required Vehicle point properties are included
|
|
speed: state.points.speed,
|
|
actions: state.points.actions,
|
|
},
|
|
};
|
|
return updatedVehicle;
|
|
}
|
|
}
|
|
// Handle StaticMachine
|
|
else if (state.type === "StaticMachine") {
|
|
if (
|
|
(state.modeluuid === connection1.model &&
|
|
state.points.uuid === connection1.point) ||
|
|
(state.modeluuid === connection2.model &&
|
|
state.points.uuid === connection2.point)
|
|
) {
|
|
const updatedStaticMachine: SimulationTypes.StaticMachineEventsSchema = {
|
|
...state,
|
|
points: {
|
|
...state.points,
|
|
connections: {
|
|
...state.points.connections,
|
|
targets: state.points.connections.targets.filter((target) => {
|
|
return !(
|
|
(target.modelUUID === connection1.model &&
|
|
target.pointUUID === connection1.point) ||
|
|
(target.modelUUID === connection2.model &&
|
|
target.pointUUID === connection2.point)
|
|
);
|
|
}),
|
|
},
|
|
// Ensure all required StaticMachine point properties are included
|
|
actions: state.points.actions,
|
|
triggers: state.points.triggers,
|
|
},
|
|
};
|
|
return updatedStaticMachine;
|
|
}
|
|
}
|
|
// Handle ArmBot
|
|
else if (state.type === "ArmBot") {
|
|
if (
|
|
(state.modeluuid === connection1.model &&
|
|
state.points.uuid === connection1.point) ||
|
|
(state.modeluuid === connection2.model &&
|
|
state.points.uuid === connection2.point)
|
|
) {
|
|
const updatedArmBot: SimulationTypes.ArmBotEventsSchema = {
|
|
...state,
|
|
points: {
|
|
...state.points,
|
|
connections: {
|
|
...state.points.connections,
|
|
targets: state.points.connections.targets.filter((target) => {
|
|
return !(
|
|
(target.modelUUID === connection1.model &&
|
|
target.pointUUID === connection1.point) ||
|
|
(target.modelUUID === connection2.model &&
|
|
target.pointUUID === connection2.point)
|
|
);
|
|
}),
|
|
},
|
|
actions: {
|
|
...state.points.actions,
|
|
processes:
|
|
state.points.actions.processes?.filter((process) => {
|
|
return !(
|
|
process.startPoint === connection1.point ||
|
|
process.endPoint === connection1.point ||
|
|
process.startPoint === connection2.point ||
|
|
process.endPoint === connection2.point
|
|
);
|
|
}) || [],
|
|
},
|
|
triggers: state.points.triggers,
|
|
},
|
|
};
|
|
return updatedArmBot;
|
|
}
|
|
}
|
|
return state;
|
|
});
|
|
|
|
const updatedPaths = updatedStates.filter(
|
|
(state) =>
|
|
state.modeluuid === connection1.model ||
|
|
state.modeluuid === connection2.model
|
|
);
|
|
|
|
console.log("updatedPaths: ", updatedPaths);
|
|
updateBackend(updatedPaths);
|
|
|
|
setSimulationStates(updatedStates);
|
|
};
|
|
|
|
return (
|
|
<group name="simulationConnectionGroup" visible={!isPlaying}>
|
|
{simulationStates.flatMap((path) => {
|
|
if (path.type === "Conveyor") {
|
|
return path.points.flatMap((point) =>
|
|
point.connections.targets.map((target, index) => {
|
|
const targetPath = simulationStates.find(
|
|
(p) => p.modeluuid === target.modelUUID
|
|
);
|
|
if (
|
|
targetPath?.type !== "Conveyor" &&
|
|
targetPath?.type !== "ArmBot"
|
|
)
|
|
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}`}
|
|
ref={(el) =>
|
|
(groupRefs.current[
|
|
`${point.uuid}-${target.pointUUID}-${index}`
|
|
] = el!)
|
|
}
|
|
start={fromWorldPosition.toArray()}
|
|
end={toWorldPosition.toArray()}
|
|
mid={midPoint.toArray()}
|
|
color={
|
|
deleteTool &&
|
|
hoveredLineKey ===
|
|
`${point.uuid}-${target.pointUUID}-${index}`
|
|
? "red"
|
|
: targetPath?.type === "ArmBot"
|
|
? "#42a5f5"
|
|
: "white"
|
|
}
|
|
lineWidth={4}
|
|
dashed={
|
|
deleteTool &&
|
|
hoveredLineKey ===
|
|
`${point.uuid}-${target.pointUUID}-${index}`
|
|
? false
|
|
: true
|
|
}
|
|
dashSize={0.75}
|
|
dashScale={20}
|
|
onPointerOver={() =>
|
|
setHoveredLineKey(
|
|
`${point.uuid}-${target.pointUUID}-${index}`
|
|
)
|
|
}
|
|
onPointerOut={() => setHoveredLineKey(null)}
|
|
onClick={() => {
|
|
if (deleteTool) {
|
|
const connection1 = {
|
|
model: path.modeluuid,
|
|
point: point.uuid,
|
|
};
|
|
const connection2 = {
|
|
model: target.modelUUID,
|
|
point: target.pointUUID,
|
|
};
|
|
|
|
removeConnections(connection1, connection2);
|
|
}
|
|
}}
|
|
userData={target}
|
|
/>
|
|
);
|
|
}
|
|
return null;
|
|
})
|
|
);
|
|
}
|
|
|
|
if (path.type === "Vehicle") {
|
|
return path.points.connections.targets.map((target, index) => {
|
|
const fromSphere = pathsGroupRef.current?.getObjectByProperty(
|
|
"uuid",
|
|
path.points.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.points.uuid}-${target.pointUUID}-${index}`}
|
|
ref={(el) =>
|
|
(groupRefs.current[
|
|
`${path.points.uuid}-${target.pointUUID}-${index}`
|
|
] = el!)
|
|
}
|
|
start={fromWorldPosition.toArray()}
|
|
end={toWorldPosition.toArray()}
|
|
mid={midPoint.toArray()}
|
|
color={
|
|
deleteTool &&
|
|
hoveredLineKey ===
|
|
`${path.points.uuid}-${target.pointUUID}-${index}`
|
|
? "red"
|
|
: "orange"
|
|
}
|
|
lineWidth={4}
|
|
dashed={
|
|
deleteTool &&
|
|
hoveredLineKey ===
|
|
`${path.points.uuid}-${target.pointUUID}-${index}`
|
|
? false
|
|
: true
|
|
}
|
|
dashSize={0.75}
|
|
dashScale={20}
|
|
onPointerOver={() =>
|
|
setHoveredLineKey(
|
|
`${path.points.uuid}-${target.pointUUID}-${index}`
|
|
)
|
|
}
|
|
onPointerOut={() => setHoveredLineKey(null)}
|
|
onClick={() => {
|
|
if (deleteTool) {
|
|
const connection1 = {
|
|
model: path.modeluuid,
|
|
point: path.points.uuid,
|
|
};
|
|
const connection2 = {
|
|
model: target.modelUUID,
|
|
point: target.pointUUID,
|
|
};
|
|
|
|
removeConnections(connection1, connection2);
|
|
}
|
|
}}
|
|
userData={target}
|
|
/>
|
|
);
|
|
}
|
|
return null;
|
|
});
|
|
}
|
|
|
|
if (path.type === "StaticMachine") {
|
|
return path.points.connections.targets.map((target, index) => {
|
|
const targetPath = simulationStates.find(
|
|
(p) => p.modeluuid === target.modelUUID
|
|
);
|
|
if (targetPath?.type !== "ArmBot") return null;
|
|
|
|
const fromSphere = pathsGroupRef.current?.getObjectByProperty(
|
|
"uuid",
|
|
path.points.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.points.uuid}-${target.pointUUID}-${index}`}
|
|
ref={(el) =>
|
|
(groupRefs.current[
|
|
`${path.points.uuid}-${target.pointUUID}-${index}`
|
|
] = el!)
|
|
}
|
|
start={fromWorldPosition.toArray()}
|
|
end={toWorldPosition.toArray()}
|
|
mid={midPoint.toArray()}
|
|
color={
|
|
deleteTool &&
|
|
hoveredLineKey ===
|
|
`${path.points.uuid}-${target.pointUUID}-${index}`
|
|
? "red"
|
|
: "#42a5f5"
|
|
}
|
|
lineWidth={4}
|
|
dashed={
|
|
deleteTool &&
|
|
hoveredLineKey ===
|
|
`${path.points.uuid}-${target.pointUUID}-${index}`
|
|
? false
|
|
: true
|
|
}
|
|
dashSize={0.75}
|
|
dashScale={20}
|
|
onPointerOver={() =>
|
|
setHoveredLineKey(
|
|
`${path.points.uuid}-${target.pointUUID}-${index}`
|
|
)
|
|
}
|
|
onPointerOut={() => setHoveredLineKey(null)}
|
|
onClick={() => {
|
|
if (deleteTool) {
|
|
const connection1 = {
|
|
model: path.modeluuid,
|
|
point: path.points.uuid,
|
|
};
|
|
const connection2 = {
|
|
model: target.modelUUID,
|
|
point: target.pointUUID,
|
|
};
|
|
|
|
removeConnections(connection1, connection2);
|
|
}
|
|
}}
|
|
userData={target}
|
|
/>
|
|
);
|
|
}
|
|
return null;
|
|
});
|
|
}
|
|
|
|
return [];
|
|
})}
|
|
|
|
{currentLine && (
|
|
<QuadraticBezierLine
|
|
start={currentLine.start.toArray()}
|
|
end={currentLine.end.toArray()}
|
|
mid={currentLine.mid.toArray()}
|
|
color={helperlineColor}
|
|
lineWidth={4}
|
|
dashed
|
|
dashSize={1}
|
|
dashScale={20}
|
|
/>
|
|
)}
|
|
</group>
|
|
);
|
|
}
|
|
|
|
export default PathConnector;
|