using System; using System.Collections.Generic; using System.Linq; using AGVNavigationCore.Models; using AGVNavigationCore.PathFinding.Core; using AGVNavigationCore.PathFinding.Validation; namespace AGVNavigationCore.Utils { /// /// AGV 도킹 방향 검증 유틸리티 /// 경로 계산 후 마지막 도킹 방향이 올바른지 검증 /// public static class DockingValidator { /// /// 경로의 도킹 방향 검증 /// /// 경로 계산 결과 /// 맵 노드 목록 /// AGV 현재 방향 /// 도킹 검증 결과 public static DockingValidationResult ValidateDockingDirection(AGVPathResult pathResult, List mapNodes) { // 경로가 없거나 실패한 경우 if (pathResult == null || !pathResult.Success || pathResult.Path == null || pathResult.Path.Count == 0) { System.Diagnostics.Debug.WriteLine($"[DockingValidator] 도킹 검증 불필요: 경로 없음"); return DockingValidationResult.CreateNotRequired(); } if (pathResult.DetailedPath.Any() == false && pathResult.Path.Any() && pathResult.Path.Count == 2 && pathResult.Path[0].Id == pathResult.Path[1].Id) { System.Diagnostics.Debug.WriteLine($"[DockingValidator] 도킹 검증 불필요: 동일포인트"); return DockingValidationResult.CreateNotRequired(); } // 목적지 노드 가져오기 (Path는 이제 List) var LastNode = pathResult.Path[pathResult.Path.Count - 1]; if (LastNode == null) { System.Diagnostics.Debug.WriteLine($"[DockingValidator] 목적지 노드가 null입니다"); return DockingValidationResult.CreateNotRequired(); } System.Diagnostics.Debug.WriteLine($"[DockingValidator] 목적지 노드: {LastNode.Id} 타입:{LastNode.Type} ({(int)LastNode.Type})"); ////detail 경로 이동 예측 검증 //for (int i = 0; i < pathResult.DetailedPath.Count - 1; i++) //{ // var curNodeId = pathResult.DetailedPath[i].NodeId; // var nextNodeId = pathResult.DetailedPath[i + 1].NodeId; // var curNode = mapNodes?.FirstOrDefault(n => n.Id == curNodeId); // var nextNode = mapNodes?.FirstOrDefault(n => n.Id == nextNodeId); // if (curNode != null && nextNode != null) // { // MapNode prevNode = null; // AgvDirection prevDir = AgvDirection.Stop; // if (i == 0) // { // prevNode = pathResult.PrevNode; // prevDir = pathResult.PrevDirection; // } // else // { // var prevNodeId = pathResult.DetailedPath[i - 1].NodeId; // prevNode = mapNodes?.FirstOrDefault(n => n.Id == prevNodeId); // prevDir = pathResult.DetailedPath[i - 1].MotorDirection; // } // if (prevNode != null) // { // // DirectionalHelper를 사용하여 예상되는 다음 노드 확인 // Console.WriteLine( // $"\n[ValidateDockingDirection] 경로 검증 단계 {i}:"); // Console.WriteLine( // $" 이전→현재→다음: {prevNode.Id}({prevNode.RfidId}) → {curNode.Id}({curNode.RfidId}) → {nextNode.Id}({nextNode.RfidId})"); // Console.WriteLine( // $" 현재 노드 위치: ({curNode.Position.X:F1}, {curNode.Position.Y:F1})"); // Console.WriteLine( // $" 이전 모터방향: {prevDir}, 현재 모터방향: {pathResult.DetailedPath[i].MotorDirection}"); // Console.WriteLine( // $" 마그넷방향: {pathResult.DetailedPath[i].MagnetDirection}"); // var expectedNextNode = DirectionalHelper.GetNextNodeByDirection( // curNode, // prevNode, // prevDir, // pathResult.DetailedPath[i].MotorDirection, // pathResult.DetailedPath[i].MagnetDirection, // mapNodes // ); // var expectedNextNodeL = DirectionalHelper.GetNextNodeByDirection( // curNode, // prevNode, // prevDir, // pathResult.DetailedPath[i].MotorDirection, // PathFinding.Planning.MagnetDirection.Left, // mapNodes // ); // var expectedNextNodeR = DirectionalHelper.GetNextNodeByDirection( // curNode, // prevNode, // prevDir, // pathResult.DetailedPath[i].MotorDirection, // PathFinding.Planning.MagnetDirection.Right, // mapNodes // ); // var expectedNextNodeS = DirectionalHelper.GetNextNodeByDirection( // curNode, // prevNode, // prevDir, // pathResult.DetailedPath[i].MotorDirection, // PathFinding.Planning.MagnetDirection.Straight, // mapNodes // ); // Console.WriteLine( // $" [예상] GetNextNodeByDirection 결과: {expectedNextNode?.Id ?? "null"}"); // Console.WriteLine( // $" [실제] DetailedPath 다음 노드: {nextNode.RfidId}[{nextNode.Id}]"); // if (expectedNextNode != null && !expectedNextNode.Id.Equals(nextNode.Id)) // { // string error = // $"[DockingValidator] ⚠️ 경로 방향 불일치" + // $"\n현재={curNode.RfidId}[{curNodeId}] 이전={prevNode.RfidId}[{(prevNode?.Id ?? string.Empty)}] " + // $"\n예상다음={expectedNextNode.RfidId}[{expectedNextNode.Id}] 실제다음={nextNode.RfidId}[{nextNodeId}]"; // Console.WriteLine( // $"[ValidateDockingDirection] ❌ 경로 방향 불일치 검출!"); // Console.WriteLine( // $" 이동 벡터:"); // Console.WriteLine( // $" 이전→현재: ({(curNode.Position.X - prevNode.Position.X):F2}, {(curNode.Position.Y - prevNode.Position.Y):F2})"); // Console.WriteLine( // $" 현재→예상: ({(expectedNextNode.Position.X - curNode.Position.X):F2}, {(expectedNextNode.Position.Y - curNode.Position.Y):F2})"); // Console.WriteLine( // $" 현재→실제: ({(nextNode.Position.X - curNode.Position.X):F2}, {(nextNode.Position.Y - curNode.Position.Y):F2})"); // Console.WriteLine($"[ValidateDockingDirection] 에러메시지: {error}"); // return DockingValidationResult.CreateInvalid( // LastNode.Id, // LastNode.Type, // pathResult.DetailedPath[i].MotorDirection, // pathResult.DetailedPath[i].MotorDirection, // error); // } // else // { // Console.WriteLine( // $" ✅ 경로 방향 일치!"); // } // } // } //} // 도킹이 필요한 노드인지 확인 (DockDirection이 DontCare가 아닌 경우) if (LastNode.DockDirection == DockingDirection.DontCare) { System.Diagnostics.Debug.WriteLine($"[DockingValidator] 도킹 불필요: {LastNode.DockDirection}"); return DockingValidationResult.CreateNotRequired(); } // 필요한 도킹 방향 확인 var requiredDirection = GetRequiredDockingDirection(LastNode.DockDirection); System.Diagnostics.Debug.WriteLine($"[DockingValidator] 필요한 도킹 방향: {requiredDirection}"); var LastNodeInfo = pathResult.DetailedPath.Last(); if (LastNodeInfo.NodeId != LastNode.Id) { string error = $"마지막 노드의 도킹방향과 경로정보의 노드ID 불일치: 필요={LastNode.Id}, 계산됨={LastNodeInfo.NodeId }"; System.Diagnostics.Debug.WriteLine($"[DockingValidator] ❌ 도킹 검증 실패: {error}"); return DockingValidationResult.CreateInvalid( LastNode.Id, LastNode.Type, requiredDirection, LastNodeInfo.MotorDirection, error); } // 검증 수행 if (LastNodeInfo.MotorDirection == requiredDirection && pathResult.DetailedPath[pathResult.DetailedPath.Count - 1].MotorDirection == requiredDirection) { System.Diagnostics.Debug.WriteLine($"[DockingValidator] ✅ 도킹 검증 성공"); return DockingValidationResult.CreateValid( LastNode.Id, LastNode.Type, requiredDirection, LastNodeInfo.MotorDirection); } else { string error = $"도킹 방향 불일치: 필요={GetDirectionText(requiredDirection)}, 계산됨={GetDirectionText(LastNodeInfo.MotorDirection)}"; System.Diagnostics.Debug.WriteLine($"[DockingValidator] ❌ 도킹 검증 실패: {error}"); return DockingValidationResult.CreateInvalid( LastNode.Id, LastNode.Type, requiredDirection, LastNodeInfo.MotorDirection, error); } } /// /// 도킹이 필요한 노드인지 확인 (도킹방향이 DontCare가 아닌 경우) /// private static bool IsDockingRequired(DockingDirection dockDirection) { return dockDirection != DockingDirection.DontCare; } /// /// 노드 도킹 방향에 따른 필요한 AGV 방향 반환 /// private static AgvDirection GetRequiredDockingDirection(DockingDirection dockDirection) { switch (dockDirection) { case DockingDirection.Forward: return AgvDirection.Forward; // 전진 도킹 case DockingDirection.Backward: return AgvDirection.Backward; // 후진 도킹 case DockingDirection.DontCare: default: return AgvDirection.Forward; // 기본값 (사실상 사용되지 않음) } } /// /// 경로 기반 최종 방향 계산 /// 개선된 구현: 경로 진행 방향과 목적지 노드 타입을 고려 /// private static AgvDirection CalculateFinalDirection(List path, List mapNodes, AgvDirection currentDirection) { System.Diagnostics.Debug.WriteLine($"[CalculateFinalDirection] 입력 - 경로 수: {path?.Count}, 현재 방향: {currentDirection}"); // 경로가 1개 이하면 현재 방향 유지 if (path.Count < 2) { System.Diagnostics.Debug.WriteLine($"[CalculateFinalDirection] 경로가 짧음, 현재 방향 유지: {currentDirection}"); return currentDirection; } // 목적지 노드 확인 (Path는 이제 List) var lastNode = path[path.Count - 1]; if (lastNode == null) { System.Diagnostics.Debug.WriteLine($"[CalculateFinalDirection] 목적지 노드가 null입니다"); return currentDirection; } // 도킹 노드인 경우, 필요한 도킹 방향으로 설정 if (IsDockingRequired(lastNode.DockDirection)) { var requiredDockingDirection = GetRequiredDockingDirection(lastNode.DockDirection); System.Diagnostics.Debug.WriteLine($"[CalculateFinalDirection] 도킹 노드(DockDirection={lastNode.DockDirection}) 감지, 필요 방향: {requiredDockingDirection}"); // 현재 방향이 필요한 도킹 방향과 다르면 경고 로그 if (currentDirection != requiredDockingDirection) { System.Diagnostics.Debug.WriteLine($"[CalculateFinalDirection] ⚠️ 현재 방향({currentDirection})과 필요 도킹 방향({requiredDockingDirection}) 불일치"); } // 도킹 노드의 경우 항상 필요한 도킹 방향 반환 return requiredDockingDirection; } // 일반 노드인 경우 마지막 구간의 이동 방향 분석 var secondLastNode = path[path.Count - 2]; if (secondLastNode == null) { System.Diagnostics.Debug.WriteLine($"[CalculateFinalDirection] 이전 노드가 null입니다"); return currentDirection; } // 마지막 구간의 이동 벡터 계산 var deltaX = lastNode.Position.X - secondLastNode.Position.X; var deltaY = lastNode.Position.Y - secondLastNode.Position.Y; var distance = Math.Sqrt(deltaX * deltaX + deltaY * deltaY); System.Diagnostics.Debug.WriteLine($"[CalculateFinalDirection] 마지막 구간: {secondLastNode.Id} → {lastNode.Id}, 벡터: ({deltaX}, {deltaY}), 거리: {distance:F2}"); // 이동 거리가 매우 작으면 현재 방향 유지 if (distance < 1.0) { System.Diagnostics.Debug.WriteLine($"[CalculateFinalDirection] 이동 거리 너무 짧음, 현재 방향 유지: {currentDirection}"); return currentDirection; } // 일반 노드의 경우 현재 방향 유지 (방향 전환은 회전 노드에서만 발생) System.Diagnostics.Debug.WriteLine($"[CalculateFinalDirection] 일반 노드, 현재 방향 유지: {currentDirection}"); return currentDirection; } /// /// 방향을 텍스트로 변환 /// private static string GetDirectionText(AgvDirection direction) { switch (direction) { case AgvDirection.Forward: return "전진"; case AgvDirection.Backward: return "후진"; default: return direction.ToString(); } } /// /// 도킹 검증 결과를 문자열로 변환 (디버깅용) /// public static string GetValidationSummary(DockingValidationResult validation) { if (validation == null) return "검증 결과 없음"; if (!validation.IsValidationRequired) return "도킹 검증 불필요"; if (validation.IsValid) { return $"도킹 검증 통과: {validation.TargetNodeId}({validation.TargetNodeType}) - {GetDirectionText(validation.RequiredDockingDirection)} 도킹"; } else { return $"도킹 검증 실패: {validation.TargetNodeId}({validation.TargetNodeType}) - {validation.ValidationError}"; } } } }