using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using AGVNavigationCore.Models; namespace AGVNavigationCore.PathFinding.Planning { /// /// 방향 기반 경로 탐색기 /// 이전 위치 + 현재 위치 + 이동 방향을 기반으로 다음 노드를 결정 /// public class DirectionalPathfinder { /// /// 이동 방향별 가중치 /// public class DirectionWeights { public float ForwardWeight { get; set; } = 1.0f; // 직진 public float LeftWeight { get; set; } = 1.5f; // 좌측 public float RightWeight { get; set; } = 1.5f; // 우측 public float BackwardWeight { get; set; } = 2.0f; // 후진 } private readonly DirectionWeights _weights; public DirectionalPathfinder(DirectionWeights weights = null) { _weights = weights ?? new DirectionWeights(); } /// /// 이전 위치와 현재 위치, 그리고 이동 방향을 기반으로 다음 노드 ID를 반환 /// /// 이전 위치 (이전 RFID 감지 위치) /// 현재 노드 (현재 RFID 노드) /// 현재 위치 /// 이동 방향 (Forward/Backward/Left/Right) /// 맵의 모든 노드 /// 다음 노드 ID (또는 null) public string GetNextNodeId( Point previousPos, MapNode currentNode, Point currentPos, AgvDirection direction, List allNodes) { // 전제조건: 최소 2개 위치 히스토리 필요 if (previousPos == Point.Empty || currentPos == Point.Empty) { return null; } if (currentNode == null || allNodes == null || allNodes.Count == 0) { return null; } // 현재 노드에 연결된 노드들 가져오기 var connectedNodeIds = currentNode.ConnectedNodes; if (connectedNodeIds == null || connectedNodeIds.Count == 0) { return null; } // 연결된 노드 중 현재 노드가 아닌 것들만 필터링 var candidateNodes = allNodes.Where(n => connectedNodeIds.Contains(n.Id) && n.Id != currentNode.Id ).ToList(); if (candidateNodes.Count == 0) { return null; } // 이전→현재 벡터 계산 (진행 방향 벡터) var movementVector = new PointF( currentPos.X - previousPos.X, currentPos.Y - previousPos.Y ); // 벡터 정규화 var movementLength = (float)Math.Sqrt( movementVector.X * movementVector.X + movementVector.Y * movementVector.Y ); if (movementLength < 0.001f) // 거의 이동하지 않음 { return candidateNodes[0].Id; // 첫 번째 연결 노드 반환 } var normalizedMovement = new PointF( movementVector.X / movementLength, movementVector.Y / movementLength ); // 각 후보 노드에 대해 방향 점수 계산 var scoredCandidates = new List<(MapNode node, float score)>(); foreach (var candidate in candidateNodes) { var toNextVector = new PointF( candidate.Position.X - currentPos.X, candidate.Position.Y - currentPos.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 score = CalculateDirectionalScore( normalizedMovement, normalizedToNext, direction ); scoredCandidates.Add((candidate, score)); } if (scoredCandidates.Count == 0) { return null; } // 가장 높은 점수를 가진 노드 반환 var bestCandidate = scoredCandidates.OrderByDescending(x => x.score).First(); return bestCandidate.node.Id; } /// /// 이동 방향을 기반으로 방향 점수를 계산 /// 높은 점수 = 더 나은 선택지 /// private float CalculateDirectionalScore( PointF movementDirection, // 정규화된 이전→현재 벡터 PointF nextDirection, // 정규화된 현재→다음 벡터 AgvDirection requestedDir) // 요청된 이동 방향 { float baseScore = 0; // 벡터 간 각도 계산 (내적) float dotProduct = (movementDirection.X * nextDirection.X) + (movementDirection.Y * nextDirection.Y); // 외적으로 좌우 판별 (Z 성분) float crossProduct = (movementDirection.X * nextDirection.Y) - (movementDirection.Y * nextDirection.X); switch (requestedDir) { case AgvDirection.Forward: // Forward: 직진 방향 선호 (dotProduct ≈ 1) if (dotProduct > 0.9f) // 거의 같은 방향 { baseScore = 100.0f * _weights.ForwardWeight; } else if (dotProduct > 0.5f) // 비슷한 방향 { baseScore = 80.0f * _weights.ForwardWeight; } else if (dotProduct > 0.0f) // 약간 다른 방향 { baseScore = 50.0f * _weights.ForwardWeight; } else if (dotProduct > -0.5f) // 거의 반대 방향 아님 { baseScore = 20.0f * _weights.BackwardWeight; } else { baseScore = 0.0f; // 완전 반대 } break; case AgvDirection.Backward: // Backward: 역진 방향 선호 (dotProduct ≈ -1) if (dotProduct < -0.9f) // 거의 반대 방향 { baseScore = 100.0f * _weights.BackwardWeight; } else if (dotProduct < -0.5f) // 비슷하게 반대 { baseScore = 80.0f * _weights.BackwardWeight; } else if (dotProduct < 0.0f) // 약간 다른 방향 { baseScore = 50.0f * _weights.BackwardWeight; } else if (dotProduct < 0.5f) // 거의 같은 방향 아님 { baseScore = 20.0f * _weights.ForwardWeight; } else { baseScore = 0.0f; // 완전 같은 방향 } break; case AgvDirection.Left: // Left: 좌측 방향 선호 // Forward 상태에서: crossProduct > 0 = 좌측 // Backward 상태에서: crossProduct < 0 = 좌측 (반대) if (dotProduct > 0.0f) // Forward 상태 { // crossProduct > 0이면 좌측 if (crossProduct > 0.5f) { baseScore = 100.0f * _weights.LeftWeight; } else if (crossProduct > 0.0f) { baseScore = 70.0f * _weights.LeftWeight; } else if (crossProduct > -0.5f) { baseScore = 50.0f * _weights.ForwardWeight; } else { baseScore = 30.0f * _weights.RightWeight; } } else // Backward 상태 - 좌우 반전 { // Backward에서 좌측 = crossProduct < 0 if (crossProduct < -0.5f) { baseScore = 100.0f * _weights.LeftWeight; } else if (crossProduct < 0.0f) { baseScore = 70.0f * _weights.LeftWeight; } else if (crossProduct < 0.5f) { baseScore = 50.0f * _weights.BackwardWeight; } else { baseScore = 30.0f * _weights.RightWeight; } } break; case AgvDirection.Right: // Right: 우측 방향 선호 // Forward 상태에서: crossProduct < 0 = 우측 // Backward 상태에서: crossProduct > 0 = 우측 (반대) if (dotProduct > 0.0f) // Forward 상태 { // crossProduct < 0이면 우측 if (crossProduct < -0.5f) { baseScore = 100.0f * _weights.RightWeight; } else if (crossProduct < 0.0f) { baseScore = 70.0f * _weights.RightWeight; } else if (crossProduct < 0.5f) { baseScore = 50.0f * _weights.ForwardWeight; } else { baseScore = 30.0f * _weights.LeftWeight; } } else // Backward 상태 - 좌우 반전 { // Backward에서 우측 = crossProduct > 0 if (crossProduct > 0.5f) { baseScore = 100.0f * _weights.RightWeight; } else if (crossProduct > 0.0f) { baseScore = 70.0f * _weights.RightWeight; } else if (crossProduct > -0.5f) { baseScore = 50.0f * _weights.BackwardWeight; } else { baseScore = 30.0f * _weights.LeftWeight; } } break; } return baseScore; } /// /// 벡터 간 각도를 도 단위로 계산 /// private float CalculateAngle(PointF vector1, PointF vector2) { float dotProduct = (vector1.X * vector2.X) + (vector1.Y * vector2.Y); float magnitude1 = (float)Math.Sqrt(vector1.X * vector1.X + vector1.Y * vector1.Y); float magnitude2 = (float)Math.Sqrt(vector2.X * vector2.X + vector2.Y * vector2.Y); if (magnitude1 < 0.001f || magnitude2 < 0.001f) { return 0; } float cosAngle = dotProduct / (magnitude1 * magnitude2); cosAngle = Math.Max(-1.0f, Math.Min(1.0f, cosAngle)); // 범위 제한 return (float)(Math.Acos(cosAngle) * 180.0 / Math.PI); } } }