# GetNextNodeId() 로직 분석 및 검증 ## 🎯 검증 대상 사용자 요구사항: ``` 001 (65, 229) → 002 (206, 244) → Forward → 003 ✓ 001 (65, 229) → 002 (206, 244) → Backward → 001 ✓ 002 (206, 244) → 003 (278, 278) → Forward → 004 ✓ 002 (206, 244) → 003 (278, 278) → Backward → 002 ✓ ``` --- ## 📐 벡터 계산 논리 ### 기본 개념 ``` 이전 위치: prevPos = (x1, y1) 현재 위치: currentPos = (x2, y2) 이동 벡터: v_movement = (x2-x1, y2-y1) → 이 벡터의 방향이 "AGV가 이동한 방향" 다음 노드 위치: nextPos = (x3, y3) 다음 벡터: v_next = (x3-x2, y3-y2) → 이 벡터의 방향이 "다음 노드로 가는 방향" ``` ### 내적 (Dot Product) ``` dot = v_movement · v_next = v_m.x * v_n.x + v_m.y * v_n.y 의미: dot ≈ 1 : 거의 같은 방향 (0°) → Forward에 적합 dot ≈ 0 : 직각 (90°) → Left/Right dot ≈ -1 : 거의 반대 방향 (180°) → Backward에 적합 ``` ### 외적 (Cross Product) ``` cross = v_movement × v_next (Z 성분) = v_m.x * v_n.y - v_m.y * v_n.x 의미: cross > 0 : 반시계 방향 (좌측) cross < 0 : 시계 방향 (우측) ``` --- ## 🧪 실제 시나리오 계산 ### 시나리오 1: 001 → 002 → Forward → ? #### 초기 조건 ``` 001: (65, 229) 002: (206, 244) 003: (278, 278) 이동 벡터: v_m = (206-65, 244-229) = (141, 15) 정규화: n_m = (141/142.79, 15/142.79) ≈ (0.987, 0.105) ``` #### 002의 ConnectedNodes: [N001, N003] **후보 1: N001** ``` 다음 벡터: v_n = (65-206, 229-244) = (-141, -15) 정규화: n_n = (-141/142.79, -15/142.79) ≈ (-0.987, -0.105) 내적: dot = 0.987*(-0.987) + 0.105*(-0.105) = -0.974 - 0.011 ≈ -0.985 (매우 반대 방향) 외적: cross = 0.987*(-0.105) - 0.105*(-0.987) = -0.104 + 0.104 ≈ 0 Forward 모드에서: dotProduct < -0.5 → baseScore = 20.0 (낮은 점수) ``` **후보 2: N003** ⭐ ``` 다음 벡터: v_n = (278-206, 278-244) = (72, 34) 정규화: n_n = (72/79.88, 34/79.88) ≈ (0.901, 0.426) 내적: dot = 0.987*0.901 + 0.105*0.426 = 0.889 + 0.045 ≈ 0.934 (거의 같은 방향) 외적: cross = 0.987*0.426 - 0.105*0.901 = 0.421 - 0.095 ≈ 0.326 Forward 모드에서: dotProduct > 0.9 → baseScore = 100.0 ✓ (최고 점수!) ``` #### 결과: N003 선택 ✅ --- ### 시나리오 2: 001 → 002 → Backward → ? #### 초기 조건 ``` 001: (65, 229) 002: (206, 244) 이동 벡터: v_m = (141, 15) (같음) 정규화: n_m = (0.987, 0.105) (같음) ``` #### 002의 ConnectedNodes: [N001, N003] **후보 1: N001** ⭐ ``` 다음 벡터: v_n = (-141, -15) (같음) 정규화: n_n = (-0.987, -0.105) (같음) 내적: dot ≈ -0.985 (매우 반대 방향) Backward 모드에서: dotProduct < -0.9 → baseScore = 100.0 ✓ (최고 점수!) ``` **후보 2: N003** ``` 다음 벡터: v_n = (72, 34) (같음) 정규화: n_n = (0.901, 0.426) (같음) 내적: dot ≈ 0.934 (거의 같은 방향) Backward 모드에서: dotProduct > 0.5 → baseScore = 0 (점수 없음) ``` #### 결과: N001 선택 ✅ --- ### 시나리오 3: 002 → 003 → Forward → ? #### 초기 조건 ``` 002: (206, 244) 003: (278, 278) 004: (380, 340) 이동 벡터: v_m = (278-206, 278-244) = (72, 34) 정규화: n_m ≈ (0.901, 0.426) ``` #### 003의 ConnectedNodes: [N002, N004] **후보 1: N002** ``` 다음 벡터: v_n = (206-278, 244-278) = (-72, -34) 정규화: n_n ≈ (-0.901, -0.426) 내적: dot ≈ -0.934 (거의 반대) Forward 모드에서: dotProduct < 0 → baseScore ≤ 50.0 ``` **후보 2: N004** ⭐ ``` 다음 벡터: v_n = (380-278, 340-278) = (102, 62) 정규화: n_n = (102/119.54, 62/119.54) ≈ (0.853, 0.519) 내적: dot = 0.901*0.853 + 0.426*0.519 = 0.768 + 0.221 ≈ 0.989 (거의 같은 방향) Forward 모드에서: dotProduct > 0.9 → baseScore = 100.0 ✓ ``` #### 결과: N004 선택 ✅ --- ### 시나리오 4: 002 → 003 → Backward → ? #### 초기 조건 ``` 002: (206, 244) 003: (278, 278) 이동 벡터: v_m = (72, 34) 정규화: n_m = (0.901, 0.426) ``` #### 003의 ConnectedNodes: [N002, N004] **후보 1: N002** ⭐ ``` 다음 벡터: v_n = (-72, -34) 정규화: n_n = (-0.901, -0.426) 내적: dot ≈ -0.934 (거의 반대) Backward 모드에서: dotProduct < -0.9 → baseScore = 100.0 ✓ ``` **후보 2: N004** ``` 다음 벡터: v_n = (102, 62) 정규화: n_n = (0.853, 0.519) 내적: dot ≈ 0.989 (거의 같은) Backward 모드에서: dotProduct > 0 → baseScore = 0 (점수 없음) ``` #### 결과: N002 선택 ✅ --- ## ✅ 검증 결과 | 시나리오 | 이전→현재 | 방향 | 예상 | 계산 결과 | 검증 | |---------|----------|------|------|----------|------| | 1 | 001→002 | Forward | 003 | 003 (100.0) | ✅ | | 2 | 001→002 | Backward | 001 | 001 (100.0) | ✅ | | 3 | 002→003 | Forward | 004 | 004 (100.0) | ✅ | | 4 | 002→003 | Backward | 002 | 002 (100.0) | ✅ | --- ## 🔍 핵심 로직 검토 ### VirtualAGV.GetNextNodeId() - 라인 628-719 ```csharp public string GetNextNodeId(AgvDirection direction, List allNodes) { // 1️⃣ 히스토리 검증 if (_prevPosition == Point.Empty || _currentPosition == Point.Empty) return null; // ← 2개 위치 필수 // 2️⃣ 연결된 노드 필터링 var candidateNodes = allNodes.Where(n => _currentNode.ConnectedNodes.Contains(n.NodeId) ).ToList(); // 3️⃣ 이동 벡터 계산 var movementVector = new PointF( _currentPosition.X - _prevPosition.X, _currentPosition.Y - _prevPosition.Y ); // 4️⃣ 정규화 var normalizedMovement = new PointF( movementVector.X / movementLength, movementVector.Y / movementLength ); // 5️⃣ 각 후보에 대해 점수 계산 foreach (var candidate in candidateNodes) { float score = CalculateDirectionalScore( normalizedMovement, normalizedToNext, direction // ← Forward/Backward/Left/Right ); if (score > bestCandidate.score) bestCandidate = (candidate, score); } return bestCandidate.node?.NodeId; } ``` ### CalculateDirectionalScore() - 라인 721-821 ```csharp private float CalculateDirectionalScore( PointF movementDirection, // 정규화된 이동 벡터 PointF nextDirection, // 정규화된 다음 벡터 AgvDirection requestedDir) // 요청된 방향 { // 내적: 유사도 계산 float dotProduct = (movementDirection.X * nextDirection.X) + (movementDirection.Y * nextDirection.Y); // 외적: 좌우 판별 float crossProduct = (movementDirection.X * nextDirection.Y) - (movementDirection.Y * nextDirection.X); // 방향에 따라 점수 계산 switch (requestedDir) { case AgvDirection.Forward: // Forward: dotProduct > 0.9 → 100점 ✓ break; case AgvDirection.Backward: // Backward: dotProduct < -0.9 → 100점 ✓ break; case AgvDirection.Left: // Left: crossProduct 양수 선호 ✓ break; case AgvDirection.Right: // Right: crossProduct 음수 선호 ✓ break; } return baseScore; } ``` --- ## 📊 최종 결론 ### ✅ 로직이 정확함 모든 시나리오에서: - **Forward 이동**: 이동 벡터와 방향이 거의 같은 노드 선택 (dotProduct > 0.9) - **Backward 이동**: 이동 벡터와 반대 방향인 노드 선택 (dotProduct < -0.9) ### 🎯 동작 원리 1. **이동 벡터**: "AGV가 이동한 방향"을 나타냄 2. **Forward**: 같은 방향으로 계속 진행 3. **Backward**: 반대 방향으로 돌아감 ``` Forward 논리: 001→002 이동 벡터 방향으로 → 002에서 Forward 선택 → 같은 방향인 003 선택 ✓ Backward 논리: 001→002 이동 벡터 방향으로 → 002에서 Backward 선택 → 반대 방향인 001 선택 ✓ ``` --- ## 🧪 테스트 클래스 **파일**: `GetNextNodeIdTest.cs` 실행하면: 1. 각 시나리오별 벡터 계산 출력 2. 내적/외적 값 표시 3. 후보 노드별 점수 계산 4. 선택된 노드 및 검증 결과 --- **분석 완료**: 2025-10-23 **상태**: 🟢 로직 정확 검증 완료