import { useMemo, useRef } from 'react'; import * as THREE from 'three'; import { useFrame } from '@react-three/fiber'; import { Line, QuadraticBezierLine } from '@react-three/drei'; import type { ComputedEdge } from '@/core/store'; import type { Position3D } from '@/schema/types'; /** * Edge style type */ export type EdgeStyle = 'line' | 'tube' | 'arrow' | 'bezier'; /** * Props for edge components */ export interface EdgeProps { edge: ComputedEdge; style?: EdgeStyle; animated?: boolean; flowSpeed?: number; } /** * Calculate control points for bezier curves */ function getBezierControlPoints( start: Position3D, end: Position3D ): { mid1: Position3D; mid2: Position3D } { const midY = (start.y + end.y) / 2; const offsetZ = Math.abs(end.y - start.y) * 0.3; return { mid1: { x: start.x, y: midY, z: start.z + offsetZ }, mid2: { x: end.x, y: midY, z: end.z + offsetZ }, }; } /** * Simple line edge */ export function LineEdge({ edge }: EdgeProps) { const points = useMemo(() => [ new THREE.Vector3(edge.sourcePosition.x, edge.sourcePosition.y, edge.sourcePosition.z), new THREE.Vector3(edge.targetPosition.x, edge.targetPosition.y, edge.targetPosition.z), ], [edge.sourcePosition, edge.targetPosition]); const color = edge.highlighted ? '#ffffff' : edge.color; const lineWidth = edge.highlighted ? 3 : 1.5; return ( ); } /** * Bezier curve edge for smoother connections */ export function BezierEdge({ edge }: EdgeProps) { const start = useMemo(() => new THREE.Vector3(edge.sourcePosition.x, edge.sourcePosition.y, edge.sourcePosition.z), [edge.sourcePosition] ); const end = useMemo(() => new THREE.Vector3(edge.targetPosition.x, edge.targetPosition.y, edge.targetPosition.z), [edge.targetPosition] ); const control = useMemo(() => { const { mid1 } = getBezierControlPoints(edge.sourcePosition, edge.targetPosition); return new THREE.Vector3(mid1.x, mid1.y, mid1.z); }, [edge.sourcePosition, edge.targetPosition]); const color = edge.highlighted ? '#ffffff' : edge.color; const lineWidth = edge.highlighted ? 3 : 1.5; return ( ); } /** * Tube edge for 3D pipe-like connections */ export function TubeEdge({ edge, animated = false, flowSpeed = 1 }: EdgeProps) { const tubeRef = useRef(null); // Create curve for tube const curve = useMemo(() => { const start = new THREE.Vector3( edge.sourcePosition.x, edge.sourcePosition.y, edge.sourcePosition.z ); const end = new THREE.Vector3( edge.targetPosition.x, edge.targetPosition.y, edge.targetPosition.z ); const { mid1, mid2 } = getBezierControlPoints(edge.sourcePosition, edge.targetPosition); const control1 = new THREE.Vector3(mid1.x, mid1.y, mid1.z); const control2 = new THREE.Vector3(mid2.x, mid2.y, mid2.z); return new THREE.CubicBezierCurve3(start, control1, control2, end); }, [edge.sourcePosition, edge.targetPosition]); // Tube geometry const geometry = useMemo(() => { const radius = edge.highlighted ? 0.06 : 0.03; return new THREE.TubeGeometry(curve, 32, radius, 8, false); }, [curve, edge.highlighted]); // Animate flow effect useFrame(({ clock }) => { if (animated && tubeRef.current) { const material = tubeRef.current.material as THREE.MeshStandardMaterial; if (material.map) { material.map.offset.y = clock.getElapsedTime() * flowSpeed; } } }); const color = edge.highlighted ? '#ffffff' : edge.color; return ( ); } /** * Arrow edge with direction indicator */ export function ArrowEdge({ edge }: EdgeProps) { const start = new THREE.Vector3( edge.sourcePosition.x, edge.sourcePosition.y, edge.sourcePosition.z ); const end = new THREE.Vector3( edge.targetPosition.x, edge.targetPosition.y, edge.targetPosition.z ); const direction = end.clone().sub(start).normalize(); const arrowPosition = end.clone().sub(direction.clone().multiplyScalar(0.3)); const color = edge.highlighted ? '#ffffff' : edge.color; // Calculate arrow rotation const quaternion = new THREE.Quaternion(); quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction); return ( {/* Arrow head */} ); } /** * Factory function to get edge component by style */ export function getEdgeComponent(style: EdgeStyle): React.ComponentType { switch (style) { case 'line': return LineEdge; case 'bezier': return BezierEdge; case 'tube': return TubeEdge; case 'arrow': return ArrowEdge; default: return TubeEdge; } }