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

8.2 KiB
Raw Blame History

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

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

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 상태: 🟢 로직 정확 검증 완료