파일정리
This commit is contained in:
464
AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs
Normal file
464
AGVLogic/AGVNavigationCore/Utils/DirectionalHelper.cs
Normal file
@@ -0,0 +1,464 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using AGVNavigationCore.Models;
|
||||
using AGVNavigationCore.PathFinding.Analysis;
|
||||
using AGVNavigationCore.PathFinding.Planning;
|
||||
|
||||
namespace AGVNavigationCore.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// AGV 방향 계산 헬퍼 유틸리티
|
||||
/// 현재 위치에서 주어진 모터 방향과 마그넷 방향으로 이동할 때 다음 노드를 계산
|
||||
/// 이전 이동 방향과 마그넷 방향을 고려하여 더 정확한 경로 예측
|
||||
/// </summary>
|
||||
public static class DirectionalHelper
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// AGV방향과 일치하는지 확인한다. 단 원본위치에서 dock 위치가 Don't Care 라면 true가 반환 됩니다.
|
||||
/// </summary>
|
||||
/// <param name="dock"></param>
|
||||
/// <param name="agvdirection"></param>
|
||||
/// <returns></returns>
|
||||
public static bool MatchAGVDirection(this DockingDirection dock, AgvDirection agvdirection)
|
||||
{
|
||||
if (dock == DockingDirection.DontCare) return true;
|
||||
if (dock == DockingDirection.Forward && agvdirection == AgvDirection.Forward) return true;
|
||||
if (dock == DockingDirection.Backward && agvdirection == AgvDirection.Backward) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static JunctionAnalyzer _junctionAnalyzer;
|
||||
|
||||
/// <summary>
|
||||
/// JunctionAnalyzer 초기화 (첫 호출 시)
|
||||
/// </summary>
|
||||
private static void InitializeJunctionAnalyzer(List<MapNode> allNodes)
|
||||
{
|
||||
if (_junctionAnalyzer == null && allNodes != null)
|
||||
{
|
||||
_junctionAnalyzer = new JunctionAnalyzer(allNodes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 노드에서 주어진 모터 방향과 마그넷 방향으로 이동할 때 다음 노드를 반환
|
||||
/// 이전 모터 방향과 마그넷 방향을 고려하여 더 정확한 경로 예측
|
||||
/// </summary>
|
||||
/// <param name="currentNode">현재 노드</param>
|
||||
/// <param name="prevNode">이전 노드 (진행 방향 기준점)</param>
|
||||
/// <param name="prevDirection">이전 구간의 모터 방향</param>
|
||||
/// <param name="direction">현재 모터 방향 (Forward 또는 Backward)</param>
|
||||
/// <param name="magnetDirection">현재 마그넷 방향 (Straight/Left/Right)</param>
|
||||
/// <param name="allNodes">모든 맵 노드</param>
|
||||
/// <returns>다음 노드 (또는 null)</returns>
|
||||
public static MapNode GetNextNodeByDirection(
|
||||
MapNode currentNode,
|
||||
MapNode prevNode,
|
||||
AgvDirection prevDirection,
|
||||
AgvDirection direction,
|
||||
MagnetDirection magnetDirection,
|
||||
List<MapNode> allNodes)
|
||||
{
|
||||
if (currentNode == null || prevNode == null || allNodes == null)
|
||||
return null;
|
||||
|
||||
// JunctionAnalyzer 초기화
|
||||
InitializeJunctionAnalyzer(allNodes);
|
||||
|
||||
// 현재 노드에 연결된 노드들 중 이전 노드가 아닌 노드들만 필터링
|
||||
var connectedMapNodes = currentNode.ConnectedMapNodes;
|
||||
if (connectedMapNodes == null || connectedMapNodes.Count == 0)
|
||||
return null;
|
||||
|
||||
List<MapNode> candidateNodes = new List<MapNode>();
|
||||
if (prevDirection == direction)
|
||||
{
|
||||
candidateNodes = connectedMapNodes.Where(n => n.Id != prevNode.Id).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
candidateNodes = connectedMapNodes.ToList();
|
||||
}
|
||||
|
||||
if (candidateNodes.Count == 0)
|
||||
return null;
|
||||
|
||||
// 이전→현재 이동 벡터
|
||||
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;
|
||||
|
||||
Console.WriteLine(
|
||||
$"\n[GetNextNodeByDirection] ========== 다음 노드 선택 시작 ==========");
|
||||
Console.WriteLine(
|
||||
$" 현재노드: {currentNode.RfidId}[{currentNode.Id}]({currentNode.Position.X:F1}, {currentNode.Position.Y:F1})");
|
||||
Console.WriteLine(
|
||||
$" 이전노드: {prevNode.RfidId}[{prevNode.Id}]({prevNode.Position.X:F1}, {prevNode.Position.Y:F1})");
|
||||
Console.WriteLine(
|
||||
$" 이동벡터: ({movementVector.X:F2}, {movementVector.Y:F2}) → 정규화: ({normalizedMovement.X:F3}, {normalizedMovement.Y:F3})");
|
||||
Console.WriteLine(
|
||||
$" 현재방향: {direction}, 이전방향: {prevDirection}, 마그넷방향: {magnetDirection}");
|
||||
Console.WriteLine(
|
||||
$" 후보노드 개수: {candidateNodes.Count}");
|
||||
|
||||
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 == prevDirection)
|
||||
{
|
||||
// Forward: 진행 방향과 유사한 방향 선택 (높은 내적 = 좋음)
|
||||
score = dotProduct;
|
||||
}
|
||||
else // Backward
|
||||
{
|
||||
// Backward: 진행 방향과 반대인 방향 선택 (낮은 내적 = 좋음)
|
||||
score = -dotProduct;
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
$"\n [후보] {candidate.RfidId}[{candidate.Id}]({candidate.Position.X:F1}, {candidate.Position.Y:F1})");
|
||||
Console.WriteLine(
|
||||
$" 벡터: ({toNextVector.X:F2}, {toNextVector.Y:F2}), 길이: {toNextLength:F2}");
|
||||
Console.WriteLine(
|
||||
$" 정규화벡터: ({normalizedToNext.X:F3}, {normalizedToNext.Y:F3})");
|
||||
Console.WriteLine(
|
||||
$" 내적(dotProduct): {dotProduct:F4}");
|
||||
Console.WriteLine(
|
||||
$" 기본점수 ({(direction == prevDirection ? "방향유지" : "방향변경")}): {score:F4}");
|
||||
|
||||
// 이전 모터 방향이 제공된 경우: 방향 일관성 보너스 추가
|
||||
var scoreBeforeMotor = score;
|
||||
score = ApplyMotorDirectionConsistencyBonus(
|
||||
score,
|
||||
direction,
|
||||
prevDirection,
|
||||
dotProduct
|
||||
);
|
||||
Console.WriteLine(
|
||||
$" 모터방향 적용 후: {scoreBeforeMotor:F4} → {score:F4}");
|
||||
|
||||
// 마그넷 방향을 고려한 점수 조정
|
||||
var scoreBeforeMagnet = score;
|
||||
score = ApplyMagnetDirectionBonus(
|
||||
score,
|
||||
magnetDirection,
|
||||
normalizedMovement,
|
||||
normalizedToNext,
|
||||
currentNode,
|
||||
candidate,
|
||||
direction
|
||||
);
|
||||
Console.WriteLine(
|
||||
$" 마그넷방향 적용 후: {scoreBeforeMagnet:F4} → {score:F4}");
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestNode = candidate;
|
||||
Console.WriteLine(
|
||||
$" ⭐ 현재 최고점수 선택됨!");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
$"\n 최종선택: {bestNode?.RfidId ?? 0}[{bestNode?.Id ?? "null"}] (점수: {bestScore:F4})");
|
||||
Console.WriteLine(
|
||||
$"[GetNextNodeByDirection] ========== 다음 노드 선택 종료 ==========\n");
|
||||
|
||||
return bestNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모터 방향 일관성을 고려한 점수 보정
|
||||
/// 같은 방향으로 계속 이동하는 경우 보너스 점수 부여
|
||||
/// </summary>
|
||||
/// <param name="baseScore">기본 점수</param>
|
||||
/// <param name="currentDirection">현재 모터 방향</param>
|
||||
/// <param name="prevMotorDirection">이전 모터 방향</param>
|
||||
/// <param name="dotProduct">벡터 내적값</param>
|
||||
/// <returns>조정된 점수</returns>
|
||||
private static float ApplyMotorDirectionConsistencyBonus(
|
||||
float baseScore,
|
||||
AgvDirection currentDirection,
|
||||
AgvDirection prevMotorDirection,
|
||||
float dotProduct)
|
||||
{
|
||||
float adjustedScore = baseScore;
|
||||
|
||||
// 모터 방향이 변경되지 않은 경우: 일관성 보너스
|
||||
if (currentDirection == prevMotorDirection)
|
||||
{
|
||||
// Forward 지속: 직진 방향으로의 이동 선호
|
||||
// Backward 지속: 반대 방향으로의 이동 선호
|
||||
const float CONSISTENCY_BONUS = 0.2f;
|
||||
adjustedScore += CONSISTENCY_BONUS;
|
||||
|
||||
System.Diagnostics.Debug.WriteLine(
|
||||
$"[DirectionalHelper] 모터 방향 일관성 보너스: {currentDirection} → {currentDirection} " +
|
||||
$"(점수: {baseScore:F3} → {adjustedScore:F3})");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 모터 방향이 변경된 경우: 방향 변경 페널티
|
||||
const float DIRECTION_CHANGE_PENALTY = 0.15f;
|
||||
adjustedScore -= DIRECTION_CHANGE_PENALTY;
|
||||
|
||||
System.Diagnostics.Debug.WriteLine(
|
||||
$"[DirectionalHelper] 모터 방향 변경 페널티: {prevMotorDirection} → {currentDirection} " +
|
||||
$"(점수: {baseScore:F3} → {adjustedScore:F3})");
|
||||
}
|
||||
|
||||
return adjustedScore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마그넷 방향을 고려한 점수 보정
|
||||
/// Straight/Left/Right 마그넷 방향에 따라 후보 노드를 평가
|
||||
/// </summary>
|
||||
/// <param name="baseScore">기본 점수</param>
|
||||
/// <param name="magnetDirection">마그넷 방향 (Straight/Left/Right)</param>
|
||||
/// <param name="normalizedMovement">정규화된 이동 벡터</param>
|
||||
/// <param name="normalizedToNext">정규화된 다음 이동 벡터</param>
|
||||
/// <param name="currentNode">현재 노드</param>
|
||||
/// <param name="candidate">후보 노드</param>
|
||||
/// <returns>조정된 점수</returns>
|
||||
private static float ApplyMagnetDirectionBonus(
|
||||
float baseScore,
|
||||
MagnetDirection magnetDirection,
|
||||
PointF normalizedMovement,
|
||||
PointF normalizedToNext,
|
||||
MapNode currentNode,
|
||||
MapNode candidate,
|
||||
AgvDirection direction)
|
||||
{
|
||||
float adjustedScore = baseScore;
|
||||
|
||||
// Straight: 일직선 방향 (높은 내적 보너스)
|
||||
if (magnetDirection == MagnetDirection.Straight)
|
||||
{
|
||||
const float STRAIGHT_BONUS = 0.5f;
|
||||
adjustedScore += STRAIGHT_BONUS;
|
||||
|
||||
Console.WriteLine(
|
||||
$" [마그넷 판정] Straight 보너스 +0.5: {baseScore:F4} → {adjustedScore:F4}");
|
||||
}
|
||||
// Left 또는 Right: 모터 위치에 따른 회전 방향 판단
|
||||
else if (magnetDirection == MagnetDirection.Left || magnetDirection == MagnetDirection.Right)
|
||||
{
|
||||
// 2D 외적: movement × toNext = movement.X * toNext.Y - movement.Y * toNext.X
|
||||
float crossProduct = (normalizedMovement.X * normalizedToNext.Y) -
|
||||
(normalizedMovement.Y * normalizedToNext.X);
|
||||
|
||||
bool isLeftMotorMatch = false;
|
||||
bool isRightMotorMatch = false;
|
||||
|
||||
// ===== 정방향(Forward) 이동 =====
|
||||
if (direction == AgvDirection.Forward)
|
||||
{
|
||||
// Forward 이동 시 외적 판정:
|
||||
// - 외적 < 0 (음수) = 반시계 회전 = Left 모터 멈춤
|
||||
// - 외적 > 0 (양수) = 시계 회전 = Right 모터 멈춤
|
||||
//
|
||||
// 예: 004 → 012 → 016 (Left 모터)
|
||||
// 외적 = -0.9407 (음수) → 반시계 → Left 일치 ✅
|
||||
|
||||
isLeftMotorMatch = crossProduct < 0; // 음수 = 반시계 = Left 멈춤
|
||||
isRightMotorMatch = crossProduct > 0; // 양수 = 시계 = Right 멈춤
|
||||
}
|
||||
// ===== 역방향(Backward) 이동 =====
|
||||
else // Backward
|
||||
{
|
||||
// Backward 이동 시 외적 판정:
|
||||
// - 외적 < 0 (음수) = 시계 회전 = Left 모터 멈춤
|
||||
// - 외적 > 0 (양수) = 반시계 회전 = Right 모터 멈춤
|
||||
//
|
||||
// 예: 012 → 004 → 003 (Left 모터)
|
||||
// 외적 = 0.9334 (양수) → 반시계(역방향 기준 시계) → Left 일치 ✅
|
||||
|
||||
isLeftMotorMatch = crossProduct > 0; // 양수 = 시계(역) = Left 멈춤
|
||||
isRightMotorMatch = crossProduct < 0; // 음수 = 반시계(역) = Right 멈춤
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
$" [마그넷 판정] 외적(Cross): {crossProduct:F4}, Left모터일치: {isLeftMotorMatch}, Right모터일치: {isRightMotorMatch} [{direction}]");
|
||||
|
||||
// 외적의 절대값으로 회전 강도 판단 (0에 가까우면 약함, 1에 가까우면 강함)
|
||||
float rotationStrength = Math.Abs(crossProduct);
|
||||
|
||||
if ((magnetDirection == MagnetDirection.Left && isLeftMotorMatch) ||
|
||||
(magnetDirection == MagnetDirection.Right && isRightMotorMatch))
|
||||
{
|
||||
// 올바른 모터 방향: 회전 강도에 비례한 보너스
|
||||
// 강한 회전(|외적| ≈ 1): +2.0
|
||||
// 약한 회전(|외적| ≈ 0.2): +0.4
|
||||
float magnetBonus = rotationStrength * 2.0f;
|
||||
adjustedScore += magnetBonus;
|
||||
|
||||
Console.WriteLine(
|
||||
$" [마그넷 판정] ✅ {magnetDirection} 모터 일치 (회전강도: {rotationStrength:F4}, 보너스 +{magnetBonus:F4}): {baseScore:F4} → {adjustedScore:F4}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 잘못된 모터 방향: 회전 강도에 비례한 페널티
|
||||
// 강한 회전(|외적| ≈ 1): -2.0
|
||||
// 약한 회전(|외적| ≈ 0.2): -0.4
|
||||
float magnetPenalty = rotationStrength * 2.0f;
|
||||
adjustedScore -= magnetPenalty;
|
||||
|
||||
string actualMotor = crossProduct > 0 ? "Left" : "Right";
|
||||
|
||||
Console.WriteLine(
|
||||
$" [마그넷 판정] ❌ {magnetDirection} 모터 불일치 (실제: {actualMotor}, 회전강도: {rotationStrength:F4}, 페널티 -{magnetPenalty:F4}): {baseScore:F4} → {adjustedScore:F4}");
|
||||
}
|
||||
}
|
||||
|
||||
return adjustedScore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모터 방향을 고려한 다음 노드 선택 (디버깅/분석용)
|
||||
/// </summary>
|
||||
public static (MapNode node, float score, string reason) GetNextNodeByDirectionWithDetails(
|
||||
MapNode currentNode,
|
||||
MapNode prevNode,
|
||||
AgvDirection direction,
|
||||
List<MapNode> allNodes,
|
||||
AgvDirection? prevMotorDirection)
|
||||
{
|
||||
if (currentNode == null || prevNode == null || allNodes == null)
|
||||
return (null, 0, "입력 파라미터가 null입니다");
|
||||
|
||||
var connectedMapNodes = currentNode.ConnectedMapNodes;
|
||||
if (connectedMapNodes == null || connectedMapNodes.Count == 0)
|
||||
return (null, 0, "연결된 노드가 없습니다");
|
||||
|
||||
var candidateNodes = connectedMapNodes.ToList();
|
||||
|
||||
if (candidateNodes.Count == 0)
|
||||
return (null, 0, "후보 노드가 없습니다");
|
||||
|
||||
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], 1.0f, "움직임이 거의 없음");
|
||||
|
||||
var normalizedMovement = new PointF(
|
||||
movementVector.X / movementLength,
|
||||
movementVector.Y / movementLength
|
||||
);
|
||||
|
||||
MapNode bestNode = null;
|
||||
float bestScore = float.MinValue;
|
||||
string reason = "";
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
float dotProduct = (normalizedMovement.X * normalizedToNext.X) +
|
||||
(normalizedMovement.Y * normalizedToNext.Y);
|
||||
|
||||
float score = (direction == AgvDirection.Forward) ? dotProduct : -dotProduct;
|
||||
|
||||
if (prevMotorDirection.HasValue)
|
||||
{
|
||||
score = ApplyMotorDirectionConsistencyBonus(
|
||||
score,
|
||||
direction,
|
||||
prevMotorDirection.Value,
|
||||
dotProduct
|
||||
);
|
||||
}
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
bestNode = candidate;
|
||||
|
||||
// 선택 이유 생성
|
||||
if (prevMotorDirection.HasValue && direction == prevMotorDirection)
|
||||
{
|
||||
reason = $"모터 방향 일관성 유지 ({direction}) → {candidate.Id}";
|
||||
}
|
||||
else if (prevMotorDirection.HasValue)
|
||||
{
|
||||
reason = $"모터 방향 변경 ({prevMotorDirection} → {direction}) → {candidate.Id}";
|
||||
}
|
||||
else
|
||||
{
|
||||
reason = $"방향 기반 선택 ({direction}) → {candidate.Id}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (bestNode, bestScore, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user