diff --git a/app/src/modules/simulation/events/arrows/arrows.tsx b/app/src/modules/simulation/events/arrows/arrows.tsx new file mode 100644 index 0000000..fdb0c18 --- /dev/null +++ b/app/src/modules/simulation/events/arrows/arrows.tsx @@ -0,0 +1,207 @@ +import * as THREE from "three"; +import { useMemo, useRef } from "react"; +import { useThree } from "@react-three/fiber"; + +interface ConnectionLine { + id: string; + startPointUuid: string; + endPointUuid: string; + trigger: TriggerSchema; +} + +export function Arrows({ connections }: { connections: ConnectionLine[] }) { + const groupRef = useRef(null); + const { scene } = useThree(); + + 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 ( + + + + + + + + + ); + }; + + const getSubCurve = ( + curve: THREE.Curve, + 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 {arrowGroups}; +} + +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 curveLength = useMemo(() => fullCurve.getLength(), [fullCurve]); + const arrowHeadTOffset = useMemo(() => Math.min(arrowHeadLength / curveLength, 0.5), [arrowHeadLength, curveLength]); + + const endT = 1 - arrowHeadTOffset; + + const subCurve = useMemo(() => { + const t1 = Math.max(0, endT - segmentSize / 2); + const t2 = Math.min(1, endT + 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, endT, segmentSize]); + + const tubeGeometry = useMemo( + () => new THREE.TubeGeometry(subCurve, 20, arrowRadius, 8, false), + [subCurve, arrowRadius] + ); + + const arrowPosition = useMemo(() => fullCurve.getPoint(1), [fullCurve]); + const arrowTangent = useMemo(() => fullCurve.getTangent(1).normalize(), [fullCurve]); + + 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 ( + + + + + + + + + ); +} \ No newline at end of file diff --git a/app/src/modules/simulation/events/temp.md b/app/src/modules/simulation/events/temp.md deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx similarity index 95% rename from app/src/modules/simulation/triggers/connector/triggerConnector.tsx rename to app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx index dc8fb11..5ca87a5 100644 --- a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx +++ b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx @@ -6,11 +6,12 @@ import { useSelectedAction, useSelectedAsset } from "../../../../store/simulatio import { useProductStore } from "../../../../store/simulation/useProductStore"; import { useEventsStore } from "../../../../store/simulation/useEventsStore"; import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore"; -import { handleAddEventToProduct } from "../../events/points/functions/handleAddEventToProduct"; +import { handleAddEventToProduct } from "../points/functions/handleAddEventToProduct"; import { QuadraticBezierLine } from "@react-three/drei"; import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; import { useDeleteTool } from "../../../../store/builder/store"; import { usePlayButtonStore } from "../../../../store/usePlayButtonStore"; +import { ArrowOnQuadraticBezier, Arrows } from "../arrows/arrows"; interface ConnectionLine { id: string; @@ -368,6 +369,7 @@ function TriggerConnector() { !intersect.object.name.includes("Roof") && !intersect.object.name.includes("agv-collider") && !intersect.object.name.includes("MeasurementReference") && + !intersect.object.name.includes("ArrowWithTube") && !intersect.object.parent?.name.includes("Zone") && !(intersect.object.type === "GridHelper") && !(intersect.object.type === "Line2") @@ -487,18 +489,29 @@ function TriggerConnector() { ); })} + + {currentLine && ( - + <> + + + )} + ); } diff --git a/app/src/modules/simulation/triggers/trigger.tsx b/app/src/modules/simulation/triggers/trigger.tsx index 5f11709..0af5881 100644 --- a/app/src/modules/simulation/triggers/trigger.tsx +++ b/app/src/modules/simulation/triggers/trigger.tsx @@ -1,4 +1,4 @@ -import TriggerConnector from './connector/triggerConnector' +import TriggerConnector from '../events/triggerConnections/triggerConnector' function Trigger() {