using System; using System.Collections.Generic; using System.Linq; using AGVNavigationCore.Models; namespace AGVNavigationCore.PathFinding { /// /// AGV 특화 경로 탐색기 (방향성 및 도킹 제약 고려) /// public class AGVPathfinder { private AStarPathfinder _pathfinder; private Dictionary _nodeMap; /// /// AGV 현재 방향 /// public AgvDirection CurrentDirection { get; set; } = AgvDirection.Forward; /// /// 회전 비용 가중치 (회전이 비싼 동작임을 반영) /// public float RotationCostWeight { get; set; } = 50.0f; /// /// 도킹 접근 거리 (픽셀 단위) /// public float DockingApproachDistance { get; set; } = 100.0f; /// /// 생성자 /// public AGVPathfinder() { _pathfinder = new AStarPathfinder(); _nodeMap = new Dictionary(); } /// /// 맵 노드 설정 /// /// 맵 노드 목록 public void SetMapNodes(List mapNodes) { _pathfinder.SetMapNodes(mapNodes); _nodeMap.Clear(); foreach (var node in mapNodes ?? new List()) { _nodeMap[node.NodeId] = node; } } /// /// AGV 경로 계산 (방향성 및 도킹 제약 고려) /// /// 시작 노드 ID /// 목적지 노드 ID /// 목적지 도착 방향 (null이면 자동 결정) /// AGV 경로 계산 결과 public AGVPathResult FindAGVPath(string startNodeId, string endNodeId, AgvDirection? targetDirection = null) { var stopwatch = System.Diagnostics.Stopwatch.StartNew(); try { if (!_nodeMap.ContainsKey(startNodeId)) { return AGVPathResult.CreateFailure($"시작 노드를 찾을 수 없습니다: {startNodeId}", stopwatch.ElapsedMilliseconds); } if (!_nodeMap.ContainsKey(endNodeId)) { return AGVPathResult.CreateFailure($"목적지 노드를 찾을 수 없습니다: {endNodeId}", stopwatch.ElapsedMilliseconds); } var endNode = _nodeMap[endNodeId]; if (IsSpecialNode(endNode)) { return FindPathToSpecialNode(startNodeId, endNode, targetDirection, stopwatch); } else { return FindNormalPath(startNodeId, endNodeId, targetDirection, stopwatch); } } catch (Exception ex) { return AGVPathResult.CreateFailure($"AGV 경로 계산 중 오류: {ex.Message}", stopwatch.ElapsedMilliseconds); } } /// /// 충전 스테이션으로의 경로 찾기 /// /// 시작 노드 ID /// AGV 경로 계산 결과 public AGVPathResult FindPathToChargingStation(string startNodeId) { var chargingStations = _nodeMap.Values .Where(n => n.Type == NodeType.Charging && n.IsActive) .Select(n => n.NodeId) .ToList(); if (chargingStations.Count == 0) { return AGVPathResult.CreateFailure("사용 가능한 충전 스테이션이 없습니다", 0); } var nearestResult = _pathfinder.FindNearestPath(startNodeId, chargingStations); if (!nearestResult.Success) { return AGVPathResult.CreateFailure("충전 스테이션으로의 경로를 찾을 수 없습니다", nearestResult.CalculationTimeMs); } var targetNodeId = nearestResult.Path.Last(); return FindAGVPath(startNodeId, targetNodeId, AgvDirection.Forward); } /// /// 특정 타입의 도킹 스테이션으로의 경로 찾기 /// /// 시작 노드 ID /// 장비 타입 /// AGV 경로 계산 결과 public AGVPathResult FindPathToDockingStation(string startNodeId, StationType stationType) { var dockingStations = _nodeMap.Values .Where(n => n.Type == NodeType.Docking && n.StationType == stationType && n.IsActive) .Select(n => n.NodeId) .ToList(); if (dockingStations.Count == 0) { return AGVPathResult.CreateFailure($"{stationType} 타입의 사용 가능한 도킹 스테이션이 없습니다", 0); } var nearestResult = _pathfinder.FindNearestPath(startNodeId, dockingStations); if (!nearestResult.Success) { return AGVPathResult.CreateFailure($"{stationType} 도킹 스테이션으로의 경로를 찾을 수 없습니다", nearestResult.CalculationTimeMs); } var targetNodeId = nearestResult.Path.Last(); return FindAGVPath(startNodeId, targetNodeId, AgvDirection.Backward); } /// /// 일반 노드로의 경로 계산 /// private AGVPathResult FindNormalPath(string startNodeId, string endNodeId, AgvDirection? targetDirection, System.Diagnostics.Stopwatch stopwatch) { var result = _pathfinder.FindPath(startNodeId, endNodeId); if (!result.Success) { return AGVPathResult.CreateFailure(result.ErrorMessage, stopwatch.ElapsedMilliseconds); } var agvCommands = GenerateAGVCommands(result.Path, targetDirection ?? AgvDirection.Forward); return AGVPathResult.CreateSuccess(result.Path, agvCommands, result.TotalDistance, stopwatch.ElapsedMilliseconds); } /// /// 특수 노드(도킹/충전)로의 경로 계산 /// private AGVPathResult FindPathToSpecialNode(string startNodeId, MapNode endNode, AgvDirection? targetDirection, System.Diagnostics.Stopwatch stopwatch) { var requiredDirection = GetRequiredDirectionForNode(endNode); var actualTargetDirection = targetDirection ?? requiredDirection; var result = _pathfinder.FindPath(startNodeId, endNode.NodeId); if (!result.Success) { return AGVPathResult.CreateFailure(result.ErrorMessage, stopwatch.ElapsedMilliseconds); } if (actualTargetDirection != requiredDirection) { return AGVPathResult.CreateFailure($"{endNode.NodeId}는 {requiredDirection} 방향으로만 접근 가능합니다", stopwatch.ElapsedMilliseconds); } var agvCommands = GenerateAGVCommands(result.Path, actualTargetDirection); return AGVPathResult.CreateSuccess(result.Path, agvCommands, result.TotalDistance, stopwatch.ElapsedMilliseconds); } /// /// 노드가 특수 노드(도킹/충전)인지 확인 /// private bool IsSpecialNode(MapNode node) { return node.Type == NodeType.Docking || node.Type == NodeType.Charging; } /// /// 노드에 필요한 접근 방향 반환 /// private AgvDirection GetRequiredDirectionForNode(MapNode node) { switch (node.Type) { case NodeType.Charging: return AgvDirection.Forward; case NodeType.Docking: return node.DockDirection == DockingDirection.Forward ? AgvDirection.Forward : AgvDirection.Backward; default: return AgvDirection.Forward; } } /// /// 경로에서 AGV 명령어 생성 /// private List GenerateAGVCommands(List path, AgvDirection targetDirection) { var commands = new List(); if (path.Count < 2) return commands; var currentDir = CurrentDirection; for (int i = 0; i < path.Count - 1; i++) { var currentNodeId = path[i]; var nextNodeId = path[i + 1]; if (_nodeMap.ContainsKey(currentNodeId) && _nodeMap.ContainsKey(nextNodeId)) { var currentNode = _nodeMap[currentNodeId]; var nextNode = _nodeMap[nextNodeId]; if (currentNode.CanRotate && ShouldRotate(currentDir, targetDirection)) { commands.Add(GetRotationCommand(currentDir, targetDirection)); currentDir = targetDirection; } commands.Add(currentDir); } } return commands; } /// /// 회전이 필요한지 판단 /// private bool ShouldRotate(AgvDirection current, AgvDirection target) { return current != target && (current == AgvDirection.Forward && target == AgvDirection.Backward || current == AgvDirection.Backward && target == AgvDirection.Forward); } /// /// 회전 명령어 반환 /// private AgvDirection GetRotationCommand(AgvDirection from, AgvDirection to) { if (from == AgvDirection.Forward && to == AgvDirection.Backward) return AgvDirection.Right; if (from == AgvDirection.Backward && to == AgvDirection.Forward) return AgvDirection.Right; return AgvDirection.Right; } /// /// 경로 유효성 검증 /// /// 검증할 경로 /// 유효성 검증 결과 public bool ValidatePath(List path) { if (path == null || path.Count < 2) return true; for (int i = 0; i < path.Count - 1; i++) { if (!_pathfinder.AreNodesConnected(path[i], path[i + 1])) { return false; } } return true; } } }