import { useRef, useMemo } from 'react'; import { useFrame } from '@react-three/fiber'; import { Text, Billboard, QuadraticBezierLine } from '@react-three/drei'; import * as THREE from 'three'; import { useBlueprintStore } from '../../store/blueprintStore'; const NODE_COLOR = '#aaaaaa'; const ROOT_COLOR = '#ffffff'; function splitCamelCase(str: string): string[] { if (str === str.toUpperCase()) { return [str]; } const result: string[] = []; let current = ''; for (let i = 0; i < str.length; i++) { const char = str[i]; if (char >= 'A' && char <= 'Z' && current.length > 0) { result.push(current); current = char; } else { current += char; } } if (current) result.push(current); return result; } function SystemNode({ id, name, position, isRoot = false, size = 0.8 }: { id: string; name: string; position: [number, number, number]; isRoot?: boolean; size?: number; }) { const meshRef = useRef(null); const { selectedNode, setSelectedNode } = useBlueprintStore(); const isSelected = selectedNode === id; const color = isRoot ? ROOT_COLOR : NODE_COLOR; useFrame(() => { if (meshRef.current) { meshRef.current.rotation.y += 0.005; } }); return ( { e.stopPropagation(); setSelectedNode(id); }} onPointerOver={() => document.body.style.cursor = 'pointer'} onPointerOut={() => document.body.style.cursor = 'auto'} > {(() => { const parts = splitCamelCase(name); const lineHeight = 0.45; const startY = size + 0.5 + (parts.length - 1) * lineHeight / 2; return ( <> {parts.map((part, i) => ( {part} ))} ); })()} {isSelected && ( )} ); } function fibonacciSpherePoint(i: number, n: number, radius: number): [number, number, number] { const phi = Math.acos(1 - 2 * (i + 0.5) / n); const theta = Math.PI * (1 + Math.sqrt(5)) * i; return [ radius * Math.sin(phi) * Math.cos(theta), radius * Math.cos(phi), radius * Math.sin(phi) * Math.sin(theta), ]; } function ConnectionLine({ start, end, color = '#4a5568', lineWidth = 1.5 }: { start: [number, number, number]; end: [number, number, number]; color?: string; lineWidth?: number; }) { const dx = end[0] - start[0]; const dy = end[1] - start[1]; const dz = end[2] - start[2]; const dist = Math.sqrt(dx * dx + dy * dy + dz * dz); const midX = (start[0] + end[0]) / 2; const midY = (start[1] + end[1]) / 2; const midZ = (start[2] + end[2]) / 2; const controlOffset = dist * 0.25; const yDirection = dy > 0 ? 1 : -1; return ( ); } export default function SystemStructure() { const { blueprint, selectedNode } = useBlueprintStore(); const { subsystems, modules } = blueprint; const rootPosition: [number, number, number] = [0, 0, 0]; const subsystemPositions = useMemo(() => { const radius = 8; const count = subsystems.length; return subsystems.map((sub, i) => { const position = fibonacciSpherePoint(i, count, radius); position[1] *= 0.6; return { id: sub.id, position: position as [number, number, number], dependCount: sub.depends_on.length, }; }); }, [subsystems]); const getSubsystemSize = (dependCount: number) => { const baseSize = 0.6; const maxAdd = 0.4; return baseSize + Math.min(dependCount * 0.15, maxAdd); }; const modulePositions = useMemo(() => { const positions: { id: string; name: string; position: [number, number, number]; parentId: string }[] = []; const moduleRadius = 2.5; subsystems.forEach((sub) => { const subModules = modules.filter(m => m.parent_subsystem === sub.id); const subPos = subsystemPositions.find(p => p.id === sub.id); if (!subPos) return; const count = subModules.length || 1; subModules.forEach((mod, i) => { const offset = fibonacciSpherePoint(i, count, moduleRadius); const parentY = subPos.position[1]; const yOffset = parentY > 0 ? 1.2 : -1.2; positions.push({ id: mod.id, name: mod.name, position: [ subPos.position[0] + offset[0], parentY + yOffset + offset[1] * 0.5, subPos.position[2] + offset[2], ] as [number, number, number], parentId: sub.id, }); }); }); return positions; }, [subsystems, modules, subsystemPositions]); const dependencies = useMemo(() => { const lines: { from: [number, number, number]; to: [number, number, number]; fromId: string; toId: string }[] = []; subsystems.forEach(sub => { const fromPos = subsystemPositions.find(p => p.id === sub.id); if (!fromPos) return; sub.depends_on.forEach(depId => { const toPos = subsystemPositions.find(p => p.id === depId); if (toPos) { lines.push({ from: fromPos.position, to: toPos.position, fromId: sub.id, toId: depId, }); } }); }); return lines; }, [subsystems, subsystemPositions]); return ( {subsystemPositions.map((sub) => ( ))} {modulePositions.map((mod) => ( ))} {dependencies.map((dep, i) => { const isHighlighted = selectedNode && (dep.fromId === selectedNode || dep.toId === selectedNode); return ( ); })} {subsystemPositions.map((sub) => { const isHighlighted = selectedNode === sub.id || selectedNode === 'root'; return ( ); })} {modulePositions.map((mod) => { const parent = subsystemPositions.find(p => p.id === mod.parentId); if (!parent) return null; const isHighlighted = selectedNode === mod.id || selectedNode === mod.parentId; return ( ); })} ); }