From 292ea0dbc2e053d2b4b8d9a74d07ffdeb4fd76aa Mon Sep 17 00:00:00 2001
From: Jerald-Golden-B <jerald@hexrfactory.com>
Date: Tue, 20 May 2025 17:46:22 +0530
Subject: [PATCH] feat: refactor TriggerConnector to include Arrows component
 for visualizing connections and update import paths refactor: remove unused
 triggerConnector file and update trigger.tsx import path

---
 .../simulation/events/arrows/arrows.tsx       | 207 ++++++++++++++++++
 app/src/modules/simulation/events/temp.md     |   0
 .../triggerConnections}/triggerConnector.tsx  |  35 ++-
 .../modules/simulation/triggers/trigger.tsx   |   2 +-
 4 files changed, 232 insertions(+), 12 deletions(-)
 create mode 100644 app/src/modules/simulation/events/arrows/arrows.tsx
 delete mode 100644 app/src/modules/simulation/events/temp.md
 rename app/src/modules/simulation/{triggers/connector => events/triggerConnections}/triggerConnector.tsx (95%)

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<THREE.Group>(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 (
+            <group key={key}>
+                <mesh geometry={shaftGeometry}>
+                    <meshStandardMaterial color="#42a5f5" />
+                </mesh>
+                <mesh position={end} quaternion={rotation} geometry={headGeometry}>
+                    <meshStandardMaterial color="#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 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 (
+        <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>
+    );
+}
\ 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() {
                 );
             })}
 
+            <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}
-                />
+                <>
+                    <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>
     );
 }
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() {