first commit
This commit is contained in:
221
app/src/modules/simulation/events/arrows/arrows.tsx
Normal file
221
app/src/modules/simulation/events/arrows/arrows.tsx
Normal file
@@ -0,0 +1,221 @@
|
||||
import * as THREE from "three";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { useDeleteTool } from "../../../../store/builder/store";
|
||||
|
||||
interface ConnectionLine {
|
||||
id: string;
|
||||
startPointUuid: string;
|
||||
endPointUuid: string;
|
||||
trigger: TriggerSchema;
|
||||
}
|
||||
|
||||
export function Arrows({ connections }: { readonly connections: ConnectionLine[] }) {
|
||||
const [hoveredLineKey, setHoveredLineKey] = useState<string | null>(null);
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const { scene } = useThree();
|
||||
const { deleteTool } = useDeleteTool();
|
||||
|
||||
const getWorldPositionFromScene = (uuid: string): THREE.Vector3 | null => {
|
||||
const obj = scene.getObjectByProperty("uuid", uuid);
|
||||
if (!obj) return null;
|
||||
const pos = new THREE.Vector3();
|
||||
obj.getWorldPosition(pos);
|
||||
return pos;
|
||||
};
|
||||
|
||||
const createArrow = (
|
||||
key: string,
|
||||
fullCurve: THREE.QuadraticBezierCurve3,
|
||||
centerT: number,
|
||||
segmentSize: number,
|
||||
scale: number,
|
||||
reverse = false
|
||||
) => {
|
||||
const t1 = Math.max(0, centerT - segmentSize / 2);
|
||||
const t2 = Math.min(1, centerT + segmentSize / 2);
|
||||
const subCurve = getSubCurve(fullCurve, t1, t2, reverse);
|
||||
|
||||
const shaftGeometry = new THREE.TubeGeometry(subCurve, 8, 0.01 * scale, 8, false);
|
||||
|
||||
const end = subCurve.getPoint(1);
|
||||
const tangent = subCurve.getTangent(1).normalize();
|
||||
|
||||
const arrowHeadLength = 0.15 * scale;
|
||||
const arrowRadius = 0.01 * scale;
|
||||
const arrowHeadRadius = arrowRadius * 2.5;
|
||||
|
||||
const headGeometry = new THREE.ConeGeometry(arrowHeadRadius, arrowHeadLength, 8);
|
||||
headGeometry.translate(0, arrowHeadLength / 2, 0);
|
||||
|
||||
const rotation = new THREE.Quaternion().setFromUnitVectors(
|
||||
new THREE.Vector3(0, 1, 0),
|
||||
tangent
|
||||
);
|
||||
|
||||
return (
|
||||
<group key={key}>
|
||||
<mesh
|
||||
geometry={shaftGeometry}
|
||||
onPointerOver={() => setHoveredLineKey(key)}
|
||||
onPointerOut={() => setHoveredLineKey(null)}
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={deleteTool && hoveredLineKey === key ? "red" : "#42a5f5"}
|
||||
/>
|
||||
</mesh>
|
||||
<mesh
|
||||
position={end}
|
||||
quaternion={rotation}
|
||||
geometry={headGeometry}
|
||||
onPointerOver={() => setHoveredLineKey(key)}
|
||||
onPointerOut={() => setHoveredLineKey(null)}
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={deleteTool && hoveredLineKey === key ? "red" : "#42a5f5"}
|
||||
/>
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
};
|
||||
|
||||
const getSubCurve = (
|
||||
curve: THREE.Curve<THREE.Vector3>,
|
||||
t1: number,
|
||||
t2: number,
|
||||
reverse = false
|
||||
) => {
|
||||
const divisions = 10;
|
||||
const subPoints = Array.from({ length: divisions + 1 }, (_, i) => {
|
||||
const t = THREE.MathUtils.lerp(t1, t2, i / divisions);
|
||||
return curve.getPoint(t);
|
||||
});
|
||||
|
||||
if (reverse) subPoints.reverse();
|
||||
|
||||
return new THREE.CatmullRomCurve3(subPoints);
|
||||
};
|
||||
|
||||
const arrowGroups = connections.flatMap((connection) => {
|
||||
const start = getWorldPositionFromScene(connection.startPointUuid);
|
||||
const end = getWorldPositionFromScene(connection.endPointUuid);
|
||||
if (!start || !end) return [];
|
||||
|
||||
const isBidirectional = connections.some(
|
||||
(other) =>
|
||||
other.startPointUuid === connection.endPointUuid &&
|
||||
other.endPointUuid === connection.startPointUuid
|
||||
);
|
||||
|
||||
if (isBidirectional && connection.startPointUuid < connection.endPointUuid) {
|
||||
const distance = start.distanceTo(end);
|
||||
const heightFactor = Math.max(0.5, distance * 0.2);
|
||||
const control = new THREE.Vector3(
|
||||
(start.x + end.x) / 2,
|
||||
Math.max(start.y, end.y) + heightFactor,
|
||||
(start.z + end.z) / 2
|
||||
);
|
||||
const curve = new THREE.QuadraticBezierCurve3(start, control, end);
|
||||
const scale = THREE.MathUtils.clamp(distance * 0.75, 0.5, 3);
|
||||
|
||||
return [
|
||||
createArrow(connection.id + "-fwd", curve, 0.33, 0.25, scale, true),
|
||||
createArrow(connection.id + "-bwd", curve, 0.66, 0.25, scale, false),
|
||||
];
|
||||
}
|
||||
|
||||
if (!isBidirectional) {
|
||||
const distance = start.distanceTo(end);
|
||||
const heightFactor = Math.max(0.5, distance * 0.2);
|
||||
const control = new THREE.Vector3(
|
||||
(start.x + end.x) / 2,
|
||||
Math.max(start.y, end.y) + heightFactor,
|
||||
(start.z + end.z) / 2
|
||||
);
|
||||
const curve = new THREE.QuadraticBezierCurve3(start, control, end);
|
||||
const scale = THREE.MathUtils.clamp(distance * 0.75, 0.5, 5);
|
||||
|
||||
return [
|
||||
createArrow(connection.id, curve, 0.5, 0.3, scale)
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
return <group ref={groupRef} name="connectionArrows">{arrowGroups}</group>;
|
||||
}
|
||||
|
||||
export function ArrowOnQuadraticBezier({
|
||||
start,
|
||||
mid,
|
||||
end,
|
||||
color = "#42a5f5",
|
||||
}: {
|
||||
start: number[];
|
||||
mid: number[];
|
||||
end: number[];
|
||||
color?: string;
|
||||
}) {
|
||||
const minScale = 0.5;
|
||||
const maxScale = 5;
|
||||
const segmentSize = 0.3;
|
||||
|
||||
const startVec = useMemo(() => new THREE.Vector3(...start), [start]);
|
||||
const midVec = useMemo(() => new THREE.Vector3(...mid), [mid]);
|
||||
const endVec = useMemo(() => new THREE.Vector3(...end), [end]);
|
||||
|
||||
const fullCurve = useMemo(
|
||||
() => new THREE.QuadraticBezierCurve3(startVec, midVec, endVec),
|
||||
[startVec, midVec, endVec]
|
||||
);
|
||||
|
||||
const distance = useMemo(() => startVec.distanceTo(endVec), [startVec, endVec]);
|
||||
const scale = useMemo(() => THREE.MathUtils.clamp(distance * 0.75, minScale, maxScale), [distance]);
|
||||
|
||||
const arrowHeadLength = 0.15 * scale;
|
||||
const arrowRadius = 0.01 * scale;
|
||||
const arrowHeadRadius = arrowRadius * 2.5;
|
||||
|
||||
const subCurve = useMemo(() => {
|
||||
const centerT = 0.5;
|
||||
const t1 = Math.max(0, centerT - segmentSize / 2);
|
||||
const t2 = Math.min(1, centerT + segmentSize / 2);
|
||||
|
||||
const divisions = 10;
|
||||
const subPoints = Array.from({ length: divisions + 1 }, (_, i) => {
|
||||
const t = THREE.MathUtils.lerp(t1, t2, i / divisions);
|
||||
return fullCurve.getPoint(t);
|
||||
});
|
||||
return new THREE.CatmullRomCurve3(subPoints);
|
||||
}, [fullCurve, segmentSize]);
|
||||
|
||||
const tubeGeometry = useMemo(
|
||||
() => new THREE.TubeGeometry(subCurve, 20, arrowRadius, 8, false),
|
||||
[subCurve, arrowRadius]
|
||||
);
|
||||
|
||||
const arrowPosition = useMemo(() => subCurve.getPoint(1), [subCurve]);
|
||||
const arrowTangent = useMemo(() => subCurve.getTangent(1).normalize(), [subCurve]);
|
||||
|
||||
const arrowRotation = useMemo(() => {
|
||||
return new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), arrowTangent);
|
||||
}, [arrowTangent]);
|
||||
|
||||
const coneGeometry = useMemo(() => {
|
||||
const geom = new THREE.ConeGeometry(arrowHeadRadius, arrowHeadLength, 8);
|
||||
geom.translate(0, arrowHeadLength / 2, 0);
|
||||
return geom;
|
||||
}, [arrowHeadRadius, arrowHeadLength]);
|
||||
|
||||
return (
|
||||
<group name="ArrowWithTube">
|
||||
<mesh name="ArrowWithTube" geometry={tubeGeometry}>
|
||||
<meshStandardMaterial color={color} />
|
||||
</mesh>
|
||||
<mesh name="ArrowWithTube" position={arrowPosition} quaternion={arrowRotation} geometry={coneGeometry}>
|
||||
<meshStandardMaterial color={color} />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import * as THREE from "three";
|
||||
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
|
||||
import { useProductStore } from "../../../../../store/simulation/useProductStore";
|
||||
import useModuleStore, { useSubModuleStore } from "../../../../../store/useModuleStore";
|
||||
import { TransformControls } from "@react-three/drei";
|
||||
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
|
||||
import { useSelectedEventSphere, useSelectedEventData, } from "../../../../../store/simulation/useSimulationStore";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { usePlayButtonStore } from "../../../../../store/usePlayButtonStore";
|
||||
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import { useProductContext } from "../../../products/productContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
function PointsCreator() {
|
||||
const { gl, raycaster, scene, pointer, camera } = useThree();
|
||||
const { subModule } = useSubModuleStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { events, updatePoint, getPointByUuid, getEventByModelUuid } = useEventsStore();
|
||||
const { getEventByModelUuid: getEventByModelUuidFromProduct, updatePoint: updatePointFromProduct, getEventByModelUuid: getEventByModelUuidFromProduct2, getPointByUuid: getPointByUuidFromProduct } = useProductStore();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { activeModule } = useModuleStore();
|
||||
const transformRef = useRef<any>(null);
|
||||
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
|
||||
const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({});
|
||||
const { selectedEventSphere, setSelectedEventSphere, clearSelectedEventSphere, } = useSelectedEventSphere();
|
||||
const { setSelectedEventData, clearSelectedEventData } = useSelectedEventData();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
|
||||
const { projectId } = useParams();
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEventSphere) {
|
||||
const eventData = getEventByModelUuid(
|
||||
selectedEventSphere.userData.modelUuid
|
||||
);
|
||||
|
||||
if (eventData) {
|
||||
setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid);
|
||||
} else {
|
||||
clearSelectedEventData();
|
||||
}
|
||||
} else {
|
||||
clearSelectedEventData();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedEventSphere]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const keyCombination = detectModifierKeys(e);
|
||||
if (!selectedEventSphere) return;
|
||||
if (keyCombination === "G") {
|
||||
setTransformMode((prev) => (prev === "translate" ? null : "translate"));
|
||||
}
|
||||
if (keyCombination === "R") {
|
||||
setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [selectedEventSphere]);
|
||||
|
||||
const updatePointToState = (selectedEventSphere: THREE.Mesh) => {
|
||||
let point: PointsScheme = JSON.parse(
|
||||
JSON.stringify(
|
||||
getPointByUuid(
|
||||
selectedEventSphere.userData.modelUuid,
|
||||
selectedEventSphere.userData.pointUuid
|
||||
)
|
||||
)
|
||||
);
|
||||
if (point) {
|
||||
point.position = [
|
||||
selectedEventSphere.position.x,
|
||||
selectedEventSphere.position.y,
|
||||
selectedEventSphere.position.z,
|
||||
];
|
||||
point.rotation = [
|
||||
selectedEventSphere.rotation.x,
|
||||
selectedEventSphere.rotation.y,
|
||||
selectedEventSphere.rotation.z,
|
||||
];
|
||||
|
||||
const event = getEventByModelUuidFromProduct(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid);
|
||||
|
||||
if (event && selectedProduct.productUuid !== '') {
|
||||
|
||||
const updatedPoint = JSON.parse(
|
||||
JSON.stringify(
|
||||
getPointByUuidFromProduct(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid, selectedEventSphere.userData.pointUuid)
|
||||
)
|
||||
);
|
||||
if (updatedPoint) {
|
||||
updatedPoint.position = point.position;
|
||||
updatedPoint.rotation = point.rotation;
|
||||
|
||||
const updatedEvent = updatePointFromProduct(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventSphere.userData.modelUuid,
|
||||
selectedEventSphere.userData.pointUuid,
|
||||
updatedPoint
|
||||
)
|
||||
if (updatedEvent) {
|
||||
updatePoint(
|
||||
selectedEventSphere.userData.modelUuid,
|
||||
selectedEventSphere.userData.pointUuid,
|
||||
point
|
||||
)
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
updatedEvent
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
|
||||
let drag = false;
|
||||
let isMouseDown = false;
|
||||
|
||||
const onMouseDown = () => {
|
||||
isMouseDown = true;
|
||||
drag = false;
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
if (selectedEventSphere && !drag) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster
|
||||
.intersectObjects(scene.children, true)
|
||||
.filter((intersect) => intersect.object.name === "Event-Sphere");
|
||||
if (intersects.length === 0) {
|
||||
clearSelectedEventSphere();
|
||||
setTransformMode(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseMove = () => {
|
||||
if (isMouseDown) {
|
||||
drag = true;
|
||||
}
|
||||
};
|
||||
|
||||
if (subModule === "mechanics") {
|
||||
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||
}
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [gl, subModule, selectedEventSphere]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{activeModule === "simulation" && (
|
||||
<>
|
||||
<group name="EventPointsGroup" visible={!isPlaying}>
|
||||
{events.map((event, index) => {
|
||||
const updatedEvent = selectedProduct.productUuid !== ''
|
||||
? getEventByModelUuidFromProduct2(selectedProduct.productUuid, event.modelUuid)
|
||||
: null;
|
||||
|
||||
const usedEvent = updatedEvent || event;
|
||||
|
||||
if (usedEvent.type === "transfer") {
|
||||
return (
|
||||
<group
|
||||
key={`${index}-${usedEvent.modelUuid}`}
|
||||
position={usedEvent.position}
|
||||
rotation={usedEvent.rotation}
|
||||
>
|
||||
{usedEvent.points.map((point, j) => (
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={point.uuid}
|
||||
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere(
|
||||
sphereRefs.current[point.uuid]
|
||||
);
|
||||
}}
|
||||
key={`${index}-${point.uuid}`}
|
||||
position={new THREE.Vector3(...point.position)}
|
||||
userData={{
|
||||
modelUuid: usedEvent.modelUuid,
|
||||
pointUuid: point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="orange" />
|
||||
</mesh>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
} else if (usedEvent.type === "vehicle") {
|
||||
const point = usedEvent.point;
|
||||
return (
|
||||
<group
|
||||
key={`${index}-${usedEvent.modelUuid}`}
|
||||
position={usedEvent.position}
|
||||
rotation={usedEvent.rotation}
|
||||
>
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={point.uuid}
|
||||
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere(
|
||||
sphereRefs.current[point.uuid]
|
||||
);
|
||||
}}
|
||||
position={new THREE.Vector3(...point.position)}
|
||||
userData={{
|
||||
modelUuid: usedEvent.modelUuid,
|
||||
pointUuid: point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="blue" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
} else if (usedEvent.type === "roboticArm") {
|
||||
const point = usedEvent.point;
|
||||
return (
|
||||
<group
|
||||
key={`${index}-${usedEvent.modelUuid}`}
|
||||
position={usedEvent.position}
|
||||
rotation={usedEvent.rotation}
|
||||
>
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={point.uuid}
|
||||
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere(
|
||||
sphereRefs.current[point.uuid]
|
||||
);
|
||||
}}
|
||||
position={new THREE.Vector3(...point.position)}
|
||||
userData={{
|
||||
modelUuid: usedEvent.modelUuid,
|
||||
pointUuid: point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="green" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
} else if (usedEvent.type === "machine") {
|
||||
const point = usedEvent.point;
|
||||
return (
|
||||
<group
|
||||
key={`${index}-${usedEvent.modelUuid}`}
|
||||
position={usedEvent.position}
|
||||
rotation={usedEvent.rotation}
|
||||
>
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={point.uuid}
|
||||
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere(
|
||||
sphereRefs.current[point.uuid]
|
||||
);
|
||||
}}
|
||||
position={new THREE.Vector3(...point.position)}
|
||||
userData={{
|
||||
modelUuid: usedEvent.modelUuid,
|
||||
pointUuid: point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="purple" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
} else if (usedEvent.type === "storageUnit") {
|
||||
const point = usedEvent.point;
|
||||
return (
|
||||
<group
|
||||
key={`${index}-${usedEvent.modelUuid}`}
|
||||
position={usedEvent.position}
|
||||
rotation={usedEvent.rotation}
|
||||
>
|
||||
<mesh
|
||||
name="Event-Sphere"
|
||||
uuid={point.uuid}
|
||||
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedEventSphere(
|
||||
sphereRefs.current[point.uuid]
|
||||
);
|
||||
}}
|
||||
position={new THREE.Vector3(...point.position)}
|
||||
userData={{
|
||||
modelUuid: usedEvent.modelUuid,
|
||||
pointUuid: point.uuid,
|
||||
}}
|
||||
>
|
||||
<sphereGeometry args={[0.1, 16, 16]} />
|
||||
<meshStandardMaterial color="red" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</group>
|
||||
|
||||
{selectedEventSphere && transformMode && (
|
||||
<TransformControls
|
||||
ref={transformRef}
|
||||
object={selectedEventSphere}
|
||||
mode={transformMode}
|
||||
onMouseUp={(e) => {
|
||||
updatePointToState(selectedEventSphere);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PointsCreator;
|
||||
@@ -0,0 +1,37 @@
|
||||
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
|
||||
interface HandleAddEventToProductParams {
|
||||
event: EventsSchema | undefined;
|
||||
addEvent: (productUuid: string, event: EventsSchema) => void;
|
||||
selectedProduct: {
|
||||
productUuid: string;
|
||||
productName: string;
|
||||
}
|
||||
clearSelectedAsset?: () => void;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export const handleAddEventToProduct = ({
|
||||
event,
|
||||
addEvent,
|
||||
selectedProduct,
|
||||
clearSelectedAsset,
|
||||
projectId
|
||||
}: HandleAddEventToProductParams) => {
|
||||
if (event && selectedProduct.productUuid) {
|
||||
addEvent(selectedProduct.productUuid, event);
|
||||
|
||||
upsertProductOrEventApi({
|
||||
productName: selectedProduct.productName,
|
||||
productUuid: selectedProduct.productUuid,
|
||||
projectId: projectId ||'',
|
||||
eventDatas: event
|
||||
}).then((data) => {
|
||||
// console.log(data);
|
||||
})
|
||||
|
||||
if (clearSelectedAsset) {
|
||||
clearSelectedAsset();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import * as THREE from 'three';
|
||||
import { Group } from '../../../../../types/world/worldTypes';
|
||||
|
||||
function PointsCalculator(
|
||||
type: string,
|
||||
model: Group,
|
||||
rotation: THREE.Vector3 = new THREE.Vector3()
|
||||
): { points?: THREE.Vector3[] } | null {
|
||||
if (!model) return null;
|
||||
|
||||
const box = new THREE.Box3().setFromObject(model);
|
||||
|
||||
const size = new THREE.Vector3();
|
||||
box.getSize(size);
|
||||
const center = new THREE.Vector3();
|
||||
box.getCenter(center);
|
||||
|
||||
const rotationMatrix = new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(rotation.x, rotation.y, rotation.z));
|
||||
|
||||
const localTopMiddle = new THREE.Vector3(0, size.y / 2, 0);
|
||||
const worldTopMiddle = localTopMiddle.clone().applyMatrix4(rotationMatrix).add(center);
|
||||
|
||||
if (type === 'Conveyor') {
|
||||
const isWidthLonger = size.x > size.z;
|
||||
const longerSize = isWidthLonger ? size.x : size.z;
|
||||
const shorterSize = isWidthLonger ? size.z : size.x;
|
||||
const halfLongerSize = longerSize / 2;
|
||||
const halfShorterSize = shorterSize / 2;
|
||||
|
||||
const localEndPoint1 = new THREE.Vector3(isWidthLonger ? -halfLongerSize + halfShorterSize : 0, size.y / 2, isWidthLonger ? 0 : -halfLongerSize + halfShorterSize);
|
||||
|
||||
const localEndPoint2 = new THREE.Vector3(isWidthLonger ? halfLongerSize - halfShorterSize : 0, size.y / 2, isWidthLonger ? 0 : halfLongerSize - halfShorterSize);
|
||||
|
||||
const worldEndPoint1 = localEndPoint1.applyMatrix4(rotationMatrix).add(center);
|
||||
const worldEndPoint2 = localEndPoint2.applyMatrix4(rotationMatrix).add(center);
|
||||
|
||||
return {
|
||||
points: [worldEndPoint1, worldTopMiddle, worldEndPoint2]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
points: [worldTopMiddle]
|
||||
};
|
||||
}
|
||||
|
||||
export default PointsCalculator;
|
||||
12
app/src/modules/simulation/events/points/points.tsx
Normal file
12
app/src/modules/simulation/events/points/points.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import PointsCreator from './creator/pointsCreator'
|
||||
|
||||
function Points() {
|
||||
return (
|
||||
<>
|
||||
<PointsCreator />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Points
|
||||
@@ -0,0 +1,526 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import * as THREE from "three";
|
||||
import { useSubModuleStore } from "../../../../store/useModuleStore";
|
||||
import { useSelectedAction, useSelectedAsset } from "../../../../store/simulation/useSimulationStore";
|
||||
import { useProductStore } from "../../../../store/simulation/useProductStore";
|
||||
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
|
||||
import { handleAddEventToProduct } from "../points/functions/handleAddEventToProduct";
|
||||
import { QuadraticBezierLine } from "@react-three/drei";
|
||||
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import { useDeleteTool } from "../../../../store/builder/store";
|
||||
import { usePlayButtonStore } from "../../../../store/usePlayButtonStore";
|
||||
import { ArrowOnQuadraticBezier, Arrows } from "../arrows/arrows";
|
||||
import { useProductContext } from "../../products/productContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
interface ConnectionLine {
|
||||
id: string;
|
||||
startPointUuid: string;
|
||||
endPointUuid: string;
|
||||
trigger: TriggerSchema;
|
||||
}
|
||||
|
||||
function TriggerConnector() {
|
||||
const { gl, raycaster, scene, pointer, camera } = useThree();
|
||||
const { subModule } = useSubModuleStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { products, getPointByUuid, getIsEventInProduct, getActionByUuid, addTrigger, removeTrigger, addEvent, getEventByModelUuid, getPointUuidByActionUuid, getProductById } = useProductStore();
|
||||
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const [hoveredLineKey, setHoveredLineKey] = useState<string | null>(null);
|
||||
const groupRefs = useRef<Record<string, any>>({});
|
||||
const [helperlineColor, setHelperLineColor] = useState<string>("red");
|
||||
const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3; end: THREE.Vector3; mid: THREE.Vector3; } | null>(null);
|
||||
const { deleteTool } = useDeleteTool();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { selectedAction } = useSelectedAction();
|
||||
const { projectId } = useParams();
|
||||
|
||||
const [firstSelectedPoint, setFirstSelectedPoint] = useState<{
|
||||
productUuid: string;
|
||||
modelUuid: string;
|
||||
pointUuid: string;
|
||||
actionUuid?: string;
|
||||
} | null>(null);
|
||||
|
||||
const [connections, setConnections] = useState<ConnectionLine[]>([]);
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const newConnections: ConnectionLine[] = [];
|
||||
|
||||
const product = getProductById(selectedProduct.productUuid);
|
||||
if (!product || products.length === 0) return;
|
||||
|
||||
product.eventDatas.forEach(event => {
|
||||
// Handle Conveyor points
|
||||
if (event.type === "transfer" && 'points' in event) {
|
||||
event.points.forEach(point => {
|
||||
if (point.action?.triggers) {
|
||||
point.action.triggers.forEach(trigger => {
|
||||
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
|
||||
newConnections.push({
|
||||
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
|
||||
startPointUuid: point.uuid,
|
||||
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||
trigger
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// Handle Vehicle point
|
||||
else if (event.type === "vehicle" && 'point' in event) {
|
||||
const point = event.point;
|
||||
if (point.action?.triggers) {
|
||||
point.action.triggers.forEach(trigger => {
|
||||
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
|
||||
newConnections.push({
|
||||
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
|
||||
startPointUuid: point.uuid,
|
||||
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||
trigger
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Handle Robotic Arm points
|
||||
else if (event.type === "roboticArm" && 'point' in event) {
|
||||
const point = event.point;
|
||||
point.actions?.forEach(action => {
|
||||
action.triggers?.forEach(trigger => {
|
||||
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
|
||||
newConnections.push({
|
||||
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
|
||||
startPointUuid: point.uuid,
|
||||
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||
trigger
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// Handle Machine point
|
||||
else if (event.type === "machine" && 'point' in event) {
|
||||
const point = event.point;
|
||||
if (point.action?.triggers) {
|
||||
point.action.triggers.forEach(trigger => {
|
||||
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
|
||||
newConnections.push({
|
||||
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
|
||||
startPointUuid: point.uuid,
|
||||
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||
trigger
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Handle StorageUnit point
|
||||
else if (event.type === "storageUnit" && 'point' in event) {
|
||||
const point = event.point;
|
||||
if (point.action?.triggers) {
|
||||
point.action.triggers.forEach(trigger => {
|
||||
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
|
||||
newConnections.push({
|
||||
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
|
||||
startPointUuid: point.uuid,
|
||||
endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||
trigger
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setConnections(newConnections);
|
||||
}, [products, selectedProduct.productUuid]);
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
|
||||
let drag = false;
|
||||
let isRightMouseDown = false;
|
||||
|
||||
const onMouseDown = (evt: MouseEvent) => {
|
||||
if (selectedAsset) {
|
||||
clearSelectedAsset();
|
||||
}
|
||||
if (evt.button === 2) {
|
||||
isRightMouseDown = true;
|
||||
drag = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseUp = (evt: MouseEvent) => {
|
||||
if (evt.button === 2) {
|
||||
isRightMouseDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
const onMouseMove = () => {
|
||||
if (isRightMouseDown) {
|
||||
drag = true;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRightClick = (evt: MouseEvent) => {
|
||||
if (drag) return;
|
||||
evt.preventDefault();
|
||||
|
||||
const intersects = raycaster
|
||||
.intersectObjects(scene.children, true)
|
||||
.filter(
|
||||
(intersect) =>
|
||||
intersect.object.name === ('Event-Sphere')
|
||||
);
|
||||
if (intersects.length === 0) {
|
||||
setFirstSelectedPoint(null);
|
||||
return;
|
||||
};
|
||||
|
||||
const currentObject = intersects[0].object;
|
||||
if (!currentObject || currentObject.name !== 'Event-Sphere') {
|
||||
setFirstSelectedPoint(null);
|
||||
return;
|
||||
};
|
||||
|
||||
const modelUuid = currentObject.userData.modelUuid;
|
||||
const pointUuid = currentObject.userData.pointUuid;
|
||||
|
||||
if (firstSelectedPoint && firstSelectedPoint.pointUuid === pointUuid) {
|
||||
setFirstSelectedPoint(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedProduct && getIsEventInProduct(selectedProduct.productUuid, modelUuid)) {
|
||||
|
||||
const point = getPointByUuid(
|
||||
selectedProduct.productUuid,
|
||||
modelUuid,
|
||||
pointUuid
|
||||
);
|
||||
|
||||
const event = getEventByModelUuid(selectedProduct.productUuid, modelUuid);
|
||||
const clickedPointUuid = getPointUuidByActionUuid(selectedProduct.productUuid, selectedAction.actionId || '');
|
||||
|
||||
if (!point || !event) {
|
||||
setFirstSelectedPoint(null);
|
||||
return;
|
||||
};
|
||||
|
||||
let actionUuid: string | undefined;
|
||||
if ('action' in point && point.action) {
|
||||
actionUuid = point.action.actionUuid;
|
||||
} else if ('actions' in point && point.actions.length === 1) {
|
||||
actionUuid = point.actions[0].actionUuid;
|
||||
}
|
||||
|
||||
if (!firstSelectedPoint) {
|
||||
if (point.uuid !== clickedPointUuid) return;
|
||||
setFirstSelectedPoint({
|
||||
productUuid: selectedProduct.productUuid,
|
||||
modelUuid,
|
||||
pointUuid,
|
||||
actionUuid: selectedAction.actionId || ''
|
||||
});
|
||||
} else {
|
||||
const trigger: TriggerSchema = {
|
||||
triggerUuid: THREE.MathUtils.generateUUID(),
|
||||
triggerName: `Trigger ${firstSelectedPoint.pointUuid.slice(0, 4)} → ${pointUuid.slice(0, 4)}`,
|
||||
triggerType: "onComplete",
|
||||
delay: 0,
|
||||
triggeredAsset: {
|
||||
triggeredModel: {
|
||||
modelName: event.modelName || 'Unknown',
|
||||
modelUuid: modelUuid
|
||||
},
|
||||
triggeredPoint: {
|
||||
pointName: 'Point',
|
||||
pointUuid: point.uuid
|
||||
},
|
||||
triggeredAction: actionUuid ? {
|
||||
actionName: getActionByUuid(selectedProduct.productUuid, actionUuid)?.actionName || 'Action',
|
||||
actionUuid: actionUuid
|
||||
} : null
|
||||
}
|
||||
};
|
||||
|
||||
if (firstSelectedPoint.actionUuid) {
|
||||
const event = addTrigger(selectedProduct.productUuid, firstSelectedPoint.actionUuid, trigger);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
setFirstSelectedPoint(null);
|
||||
}
|
||||
} else if (!getIsEventInProduct(selectedProduct.productUuid, modelUuid) && firstSelectedPoint) {
|
||||
handleAddEventToProduct({
|
||||
event: useEventsStore.getState().getEventByModelUuid(modelUuid),
|
||||
addEvent,
|
||||
selectedProduct,
|
||||
projectId: projectId || ''
|
||||
})
|
||||
|
||||
const point = getPointByUuid(
|
||||
selectedProduct.productUuid,
|
||||
modelUuid,
|
||||
pointUuid
|
||||
);
|
||||
|
||||
const event = getEventByModelUuid(selectedProduct.productUuid, modelUuid);
|
||||
|
||||
if (!point || !event) {
|
||||
setFirstSelectedPoint(null);
|
||||
return;
|
||||
};
|
||||
|
||||
let actionUuid: string | undefined;
|
||||
if ('action' in point && point.action) {
|
||||
actionUuid = point.action.actionUuid;
|
||||
} else if ('actions' in point && point.actions.length === 1) {
|
||||
actionUuid = point.actions[0].actionUuid;
|
||||
}
|
||||
|
||||
const trigger: TriggerSchema = {
|
||||
triggerUuid: THREE.MathUtils.generateUUID(),
|
||||
triggerName: `Trigger ${firstSelectedPoint.pointUuid.slice(0, 4)} → ${pointUuid.slice(0, 4)}`,
|
||||
triggerType: "onComplete",
|
||||
delay: 0,
|
||||
triggeredAsset: {
|
||||
triggeredModel: {
|
||||
modelName: event.modelName || 'Unknown',
|
||||
modelUuid: modelUuid
|
||||
},
|
||||
triggeredPoint: {
|
||||
pointName: 'Point',
|
||||
pointUuid: point.uuid
|
||||
},
|
||||
triggeredAction: actionUuid ? {
|
||||
actionName: getActionByUuid(selectedProduct.productUuid, actionUuid)?.actionName || 'Action',
|
||||
actionUuid: actionUuid
|
||||
} : null
|
||||
}
|
||||
};
|
||||
|
||||
if (firstSelectedPoint.actionUuid) {
|
||||
const event = addTrigger(selectedProduct.productUuid, firstSelectedPoint.actionUuid, trigger);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
setFirstSelectedPoint(null);
|
||||
} else if (firstSelectedPoint) {
|
||||
setFirstSelectedPoint(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (subModule === 'mechanics' && !deleteTool && selectedAction.actionId && selectedAction.actionName) {
|
||||
canvasElement.addEventListener("mousedown", onMouseDown);
|
||||
canvasElement.addEventListener("mouseup", onMouseUp);
|
||||
canvasElement.addEventListener("mousemove", onMouseMove);
|
||||
canvasElement.addEventListener('contextmenu', handleRightClick);
|
||||
} else {
|
||||
setFirstSelectedPoint(null);
|
||||
}
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener("mousedown", onMouseDown);
|
||||
canvasElement.removeEventListener("mouseup", onMouseUp);
|
||||
canvasElement.removeEventListener("mousemove", onMouseMove);
|
||||
canvasElement.removeEventListener('contextmenu', handleRightClick);
|
||||
};
|
||||
|
||||
}, [gl, subModule, selectedProduct, firstSelectedPoint, deleteTool, selectedAction]);
|
||||
|
||||
useFrame(() => {
|
||||
if (firstSelectedPoint) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster.intersectObjects(scene.children, true).filter(
|
||||
(intersect) =>
|
||||
!intersect.object.name.includes("Roof") &&
|
||||
!intersect.object.name.includes("MeasurementReference") &&
|
||||
!intersect.object.name.includes("agv-collider") &&
|
||||
!intersect.object.name.includes("zonePlane") &&
|
||||
!intersect.object.name.includes("SelectionGroup") &&
|
||||
!intersect.object.name.includes("selectionAssetGroup") &&
|
||||
!intersect.object.name.includes("SelectionGroupBoundingBoxLine") &&
|
||||
!intersect.object.name.includes("SelectionGroupBoundingBox") &&
|
||||
!intersect.object.name.includes("SelectionGroupBoundingLine") &&
|
||||
intersect.object.type !== "GridHelper" &&
|
||||
!intersect.object.name.includes("ArrowWithTube") &&
|
||||
!intersect.object.parent?.name.includes("Zone") &&
|
||||
intersect.object.type !== "Line2"
|
||||
);
|
||||
|
||||
let point: THREE.Vector3 | null = null;
|
||||
|
||||
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(scene.children, true).filter((intersect) => intersect.object.name === ('Event-Sphere'));
|
||||
|
||||
if (sphereIntersects.length > 0 && sphereIntersects[0].object.uuid === firstSelectedPoint.pointUuid) {
|
||||
setHelperLineColor('red');
|
||||
setCurrentLine(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const startPoint = getWorldPositionFromScene(firstSelectedPoint.pointUuid);
|
||||
|
||||
if (point && startPoint) {
|
||||
if (sphereIntersects.length > 0) {
|
||||
point = sphereIntersects[0].object.getWorldPosition(new THREE.Vector3());
|
||||
}
|
||||
const distance = startPoint.distanceTo(point);
|
||||
const heightFactor = Math.max(0.5, distance * 0.2);
|
||||
const midPoint = new THREE.Vector3(
|
||||
(startPoint.x + point.x) / 2,
|
||||
Math.max(startPoint.y, point.y) + heightFactor,
|
||||
(startPoint.z + point.z) / 2
|
||||
);
|
||||
|
||||
const endPoint = point;
|
||||
|
||||
setCurrentLine({
|
||||
start: startPoint,
|
||||
mid: midPoint,
|
||||
end: endPoint,
|
||||
});
|
||||
|
||||
setHelperLineColor(sphereIntersects.length > 0 ? "#6cf542" : "red");
|
||||
} else {
|
||||
setCurrentLine(null);
|
||||
}
|
||||
} else {
|
||||
setCurrentLine(null);
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
const getWorldPositionFromScene = (pointUuid: string): THREE.Vector3 | null => {
|
||||
const pointObj = scene.getObjectByProperty("uuid", pointUuid);
|
||||
if (!pointObj) return null;
|
||||
|
||||
const worldPosition = new THREE.Vector3();
|
||||
pointObj.getWorldPosition(worldPosition);
|
||||
return worldPosition;
|
||||
};
|
||||
|
||||
const removeConnection = (connection: ConnectionLine) => {
|
||||
if (connection.trigger.triggerUuid) {
|
||||
const event = removeTrigger(selectedProduct.productUuid, connection.trigger.triggerUuid);
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<group name="simulationConnectionGroup" visible={!isPlaying}>
|
||||
{connections.map((connection) => {
|
||||
const startPoint = getWorldPositionFromScene(connection.startPointUuid);
|
||||
const endPoint = getWorldPositionFromScene(connection.endPointUuid);
|
||||
|
||||
if (!startPoint || !endPoint) return null;
|
||||
|
||||
const distance = startPoint.distanceTo(endPoint);
|
||||
const heightFactor = Math.max(0.5, distance * 0.2);
|
||||
const midPoint = new THREE.Vector3(
|
||||
(startPoint.x + endPoint.x) / 2,
|
||||
Math.max(startPoint.y, endPoint.y) + heightFactor,
|
||||
(startPoint.z + endPoint.z) / 2
|
||||
);
|
||||
|
||||
return (
|
||||
<QuadraticBezierLine
|
||||
key={connection.id}
|
||||
ref={(el) => (groupRefs.current[connection.id] = el!)}
|
||||
start={startPoint.toArray()}
|
||||
end={endPoint.toArray()}
|
||||
mid={midPoint.toArray()}
|
||||
color={deleteTool && hoveredLineKey === connection.id ? "red" : "#42a5f5"}
|
||||
lineWidth={4}
|
||||
dashed={deleteTool && hoveredLineKey === connection.id ? false : true}
|
||||
dashSize={0.75}
|
||||
dashScale={20}
|
||||
onPointerOver={() => setHoveredLineKey(connection.id)}
|
||||
onPointerOut={() => setHoveredLineKey(null)}
|
||||
onClick={() => {
|
||||
if (deleteTool) {
|
||||
setHoveredLineKey(null);
|
||||
setCurrentLine(null);
|
||||
removeConnection(connection);
|
||||
}
|
||||
}}
|
||||
userData={connection.trigger}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<Arrows connections={connections} />
|
||||
|
||||
{currentLine && (
|
||||
<>
|
||||
<QuadraticBezierLine
|
||||
start={currentLine.start.toArray()}
|
||||
end={currentLine.end.toArray()}
|
||||
mid={currentLine.mid.toArray()}
|
||||
color={helperlineColor}
|
||||
lineWidth={4}
|
||||
dashed
|
||||
dashSize={1}
|
||||
dashScale={20}
|
||||
/>
|
||||
<ArrowOnQuadraticBezier
|
||||
start={currentLine.start.toArray()}
|
||||
mid={currentLine.mid.toArray()}
|
||||
end={currentLine.end.toArray()}
|
||||
color={helperlineColor}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default TriggerConnector;
|
||||
Reference in New Issue
Block a user