..
This commit is contained in:
114
Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs
Normal file
114
Cs_HMI/AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using AGVNavigationCore.Models;
|
||||
|
||||
namespace AGVNavigationCore.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// AGV 방향 계산 헬퍼 유틸리티
|
||||
/// 현재 위치에서 주어진 모터 방향으로 이동할 때 다음 노드를 계산
|
||||
/// </summary>
|
||||
public static class DirectionalHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 현재 노드에서 주어진 방향(Forward/Backward)으로 이동할 때 다음 노드를 반환
|
||||
/// </summary>
|
||||
/// <param name="currentNode">현재 노드</param>
|
||||
/// <param name="prevNode">이전 노드 (진행 방향 기준점)</param>
|
||||
/// <param name="direction">이동 방향 (Forward 또는 Backward)</param>
|
||||
/// <param name="allNodes">모든 맵 노드</param>
|
||||
/// <returns>다음 노드 (또는 null)</returns>
|
||||
public static MapNode GetNextNodeByDirection(
|
||||
MapNode currentNode,
|
||||
MapNode prevNode,
|
||||
AgvDirection direction,
|
||||
List<MapNode> allNodes)
|
||||
{
|
||||
if (currentNode == null || prevNode == null || allNodes == null)
|
||||
return null;
|
||||
|
||||
// 현재 노드에 연결된 노드들 중 이전 노드가 아닌 노드들만 필터링
|
||||
var connectedNodeIds = currentNode.ConnectedNodes;
|
||||
if (connectedNodeIds == null || connectedNodeIds.Count == 0)
|
||||
return null;
|
||||
|
||||
var candidateNodes = allNodes.Where(n =>
|
||||
connectedNodeIds.Contains(n.NodeId)
|
||||
).ToList();
|
||||
|
||||
if (candidateNodes.Count == 0)
|
||||
return null;
|
||||
|
||||
// Forward인 경우: 이전→현재 방향으로 계속 직진하는 노드 우선
|
||||
// Backward인 경우: 이전→현재 방향의 반대로 이동하는 노드 우선
|
||||
var movementVector = new PointF(
|
||||
currentNode.Position.X - prevNode.Position.X,
|
||||
currentNode.Position.Y - prevNode.Position.Y
|
||||
);
|
||||
|
||||
var movementLength = (float)Math.Sqrt(
|
||||
movementVector.X * movementVector.X +
|
||||
movementVector.Y * movementVector.Y
|
||||
);
|
||||
|
||||
if (movementLength < 0.001f)
|
||||
return candidateNodes[0];
|
||||
|
||||
var normalizedMovement = new PointF(
|
||||
movementVector.X / movementLength,
|
||||
movementVector.Y / movementLength
|
||||
);
|
||||
|
||||
// 각 후보 노드에 대해 점수 계산
|
||||
MapNode bestNode = null;
|
||||
float bestScore = float.MinValue;
|
||||
|
||||
foreach (var candidate in candidateNodes)
|
||||
{
|
||||
var toNextVector = new PointF(
|
||||
candidate.Position.X - currentNode.Position.X,
|
||||
candidate.Position.Y - currentNode.Position.Y
|
||||
);
|
||||
|
||||
var toNextLength = (float)Math.Sqrt(
|
||||
toNextVector.X * toNextVector.X +
|
||||
toNextVector.Y * toNextVector.Y
|
||||
);
|
||||
|
||||
if (toNextLength < 0.001f)
|
||||
continue;
|
||||
|
||||
var normalizedToNext = new PointF(
|
||||
toNextVector.X / toNextLength,
|
||||
toNextVector.Y / toNextLength
|
||||
);
|
||||
|
||||
// 내적 계산 (유사도: -1 ~ 1)
|
||||
float dotProduct = (normalizedMovement.X * normalizedToNext.X) +
|
||||
(normalizedMovement.Y * normalizedToNext.Y);
|
||||
|
||||
float score;
|
||||
if (direction == AgvDirection.Forward)
|
||||
{
|
||||
// Forward: 진행 방향과 유사한 방향 선택 (높은 내적 = 좋음)
|
||||
score = dotProduct;
|
||||
}
|
||||
else // Backward
|
||||
{
|
||||
// Backward: 진행 방향과 반대인 방향 선택 (낮은 내적 = 좋음)
|
||||
score = -dotProduct;
|
||||
}
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestNode = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return bestNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,59 @@ namespace AGVNavigationCore.Utils
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[DockingValidator] 목적지 노드: {targetNodeId} 타입:{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.NodeId == curNodeId);
|
||||
var nextNode = mapNodes?.FirstOrDefault(n => n.NodeId == nextNodeId);
|
||||
|
||||
if (curNode != null && nextNode != null)
|
||||
{
|
||||
MapNode prevNode = null;
|
||||
if (i == 0) prevNode = pathResult.PrevNode;
|
||||
else
|
||||
{
|
||||
var prevNodeId = pathResult.DetailedPath[i - 1].NodeId;
|
||||
prevNode = mapNodes?.FirstOrDefault(n => n.NodeId == prevNodeId);
|
||||
}
|
||||
|
||||
|
||||
if (prevNode != null)
|
||||
{
|
||||
// DirectionalHelper를 사용하여 예상되는 다음 노드 확인
|
||||
var expectedNextNode = DirectionalHelper.GetNextNodeByDirection(
|
||||
curNode,
|
||||
prevNode,
|
||||
pathResult.DetailedPath[i].MotorDirection,
|
||||
mapNodes
|
||||
);
|
||||
|
||||
if (expectedNextNode != null && !expectedNextNode.NodeId.Equals(nextNode.NodeId))
|
||||
{
|
||||
|
||||
|
||||
string error =
|
||||
$"[DockingValidator] ⚠️ 경로 방향 불일치: " +
|
||||
$"현재={curNode.RfidId}[{curNodeId}] 이전={prevNode.RfidId}[{(prevNode?.NodeId ?? string.Empty)}] " +
|
||||
$"예상다음={expectedNextNode.RfidId}[{expectedNextNode.NodeId}] 실제다음={nextNode.RfidId}[{nextNodeId}]";
|
||||
System.Diagnostics.Debug.WriteLine($"[DockingValidator] ❌ 도킹 검증 실패: {error}");
|
||||
return DockingValidationResult.CreateInvalid(
|
||||
targetNodeId,
|
||||
LastNode.Type,
|
||||
pathResult.DetailedPath[i].MotorDirection,
|
||||
pathResult.DetailedPath[i].MotorDirection,
|
||||
error);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 도킹이 필요한 노드인지 확인 (DockDirection이 DontCare가 아닌 경우)
|
||||
if (LastNode.DockDirection == DockingDirection.DontCare)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user