Files
ENIG/Cs_HMI/AGVLogic/GETNEXTNODEID_LOGIC_ANALYSIS.md
backuppc d932b8d332 fix: Add motor direction parameter to magnet direction calculation in pathfinding
- Fixed critical issue in ConvertToDetailedPath where motor direction was not passed to GetRequiredMagnetDirection
- Motor direction is essential for backward movement as Left/Right directions must be inverted
- Modified AGVPathfinder.cs line 280 to pass currentDirection parameter
- Ensures backward motor direction properly inverts magnet sensor directions

feat: Add waypoint support to pathfinding system

- Added FindPath overload with params string[] waypointNodeIds in AStarPathfinder
- Supports sequential traversal through multiple intermediate nodes
- Validates waypoints and prevents duplicates in sequence
- Returns combined path result with aggregated metrics

feat: Implement path result merging with DetailedPath preservation

- Added CombineResults method in AStarPathfinder for intelligent path merging
- Automatically deduplicates nodes when last of previous path equals first of current
- Preserves DetailedPath information including motor and magnet directions
- Essential for multi-segment path operations

feat: Integrate magnet direction with motor direction awareness

- Modified JunctionAnalyzer.GetRequiredMagnetDirection to accept AgvDirection parameter
- Inverts Left/Right magnet directions when moving Backward
- Properly handles motor direction context throughout pathfinding

feat: Add automatic start node selection in simulator

- Added SetStartNodeToCombo method to SimulatorForm
- Automatically selects start node combo box when AGV position is set via RFID
- Improves UI usability and workflow efficiency

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 15:46:16 +09:00

368 lines
8.2 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<MapNode> 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
**상태**: 🟢 로직 정확 검증 완료