346 lines
16 KiB
C#
346 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using AGVNavigationCore.Models;
|
|
using AGVNavigationCore.PathFinding.Core;
|
|
using AGVNavigationCore.PathFinding.Validation;
|
|
|
|
namespace AGVNavigationCore.Utils
|
|
{
|
|
/// <summary>
|
|
/// AGV 도킹 방향 검증 유틸리티
|
|
/// 경로 계산 후 마지막 도킹 방향이 올바른지 검증
|
|
/// </summary>
|
|
public static class DockingValidator
|
|
{
|
|
/// <summary>
|
|
/// 경로의 도킹 방향 검증
|
|
/// </summary>
|
|
/// <param name="pathResult">경로 계산 결과</param>
|
|
/// <param name="mapNodes">맵 노드 목록</param>
|
|
/// <param name="currentDirection">AGV 현재 방향</param>
|
|
/// <returns>도킹 검증 결과</returns>
|
|
public static DockingValidationResult ValidateDockingDirection(AGVPathResult pathResult, List<MapNode> 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<MapNode>)
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 도킹이 필요한 노드인지 확인 (도킹방향이 DontCare가 아닌 경우)
|
|
/// </summary>
|
|
private static bool IsDockingRequired(DockingDirection dockDirection)
|
|
{
|
|
return dockDirection != DockingDirection.DontCare;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 노드 도킹 방향에 따른 필요한 AGV 방향 반환
|
|
/// </summary>
|
|
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; // 기본값 (사실상 사용되지 않음)
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 경로 기반 최종 방향 계산
|
|
/// 개선된 구현: 경로 진행 방향과 목적지 노드 타입을 고려
|
|
/// </summary>
|
|
private static AgvDirection CalculateFinalDirection(List<MapNode> path, List<MapNode> 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<MapNode>)
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 방향을 텍스트로 변환
|
|
/// </summary>
|
|
private static string GetDirectionText(AgvDirection direction)
|
|
{
|
|
switch (direction)
|
|
{
|
|
case AgvDirection.Forward:
|
|
return "전진";
|
|
case AgvDirection.Backward:
|
|
return "후진";
|
|
default:
|
|
return direction.ToString();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 도킹 검증 결과를 문자열로 변환 (디버깅용)
|
|
/// </summary>
|
|
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}";
|
|
}
|
|
}
|
|
}
|
|
} |