증상:

  1. 차량이 잘 달리다가 커브에서 갑자기 튕겨져 나감
  2. 튕긴 차량이 도로 밖으로 떨어진 후 도로 밑에서 계속 주행
  3. 자율주행은 여전히 정상 주행 중이라고 판단

 


 

1차 수정

// SplineFollowerComponent.cpp - ComputeSteeringErrors
const FVector ToLA = (LAPos - VehicleLocation).GetSafeNormal();
Errors.PositionError = FMath::FindDeltaAngleDegrees(
    VehicleYaw,
    FMath::Atan2(ToLA.Y, ToLA.X) * (180.f / PI));
    //          X, Y만 사용 → Z 무시

 

조향 계산이 X, Y 평면만 사용하고 있는 것?

Z(높이)를 무시하니까 차가 도로 밑에 있어도 평면 위치만 정상이면 괜찮다고 판단한다고 추측

// 차량 높이와 같은 평면으로 보정
FVector LAPosFlat = LAPos;
LAPosFlat.Z = VehicleLocation.Z;

 

 

이게 아닌 것 같음 

AI에게 물어보니 원래 조향은 xy만 보는 게 자연스럽다고 함

 

생각해보니까 진짜 그럼

 

핸들 회전축 = 수직 (Z축)
   ↓
핸들 각도 변화 = 수평 (X-Y 평면)
   ↓
조향 계산이 X-Y 평면만 보는 게 자연스러움

 


2차 수정

// 차량 위치를 도로 위 거리로 변환
const float InputKey = Spline->FindInputKeyClosestToWorldLocation(VehicleLocation);
CurrentDistance = Spline->GetDistanceAlongSplineAtSplineInputKey(InputKey);

 

이 코드는 차량과 가장 가까운 도로 위 점을 찾는데 문제는 차량이 도로 밑에 30m 떨어져 있어도 가장 가까운 점은 여전히 그 위 도로 점

 

도로 (옆에서 본 모습):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ← 도로 (지상)
            ↑
       30m
            ↓
            ●  ← 차량 (도로 밑 30m 깊은 곳)

 

여기서 함수가 가장 가까운 도로 위 점을 찾으면

도로의 모든 점 중에서:
- 차량 바로 위 점: 30m 떨어짐
- 다른 도로 점들: 더 멀리 떨어짐 (예: 50m, 100m...)

→ 차량 바로 위 점이 가장 가까움
→ 그 점의 거리값을 CurrentDistance에 저장
차량의 CurrentDistance = 234m
-> 234m 지점 방향으로 운전 명령

 

차량이 30m 아래에 있는지는 모르는 것

 

도로를 벗어났을 때 운전을 멈추게 하는 코드를 작성해야겠다고 생각함. 

그래서 조향 코드는 그대로 두고 별도의 이탈 감지 로직(페일세이프) 을 추가하기로 결정

 


 

페일세이프 추가

// SplineFollowerComponent.h
UPROPERTY(EditAnywhere, Category = "Autopilot|Path")
float MaxRoadDeviation = 1500.f;  // 15m
// SplineFollowerComponent.cpp - TickComponent
const FVector RoadHere = GetLocationAtDistance(CurrentDistance);
if (FVector::Dist(VehicleLoc, RoadHere) > MaxRoadDeviation)
{
    ApplySpeedCommand(0.f, VehicleSpeed);
    ApplySteeringCommand(0.f);
    return;
}

 

FVector::Dist(VehicleLoc, RoadHere)

이건 3D 거리를 측정하기 때문에 차량이 도로 밑으로 떨어지면 감지됨

차량 위치:      (1000, 500, -3000)  ← Z가 -3000
도로 점:        (1000, 500, 100)
3D 거리 = 3100 → MaxRoadDeviation(1500) 초과 → 페일세이프 작동

 

근데 이건 추후에 다른 팀원이랑 코드 병합하고 정확하게 수정해야할듯!


 

새로운 문제

 

내가 깐 spline에 도로 mesh가 붙어있는 게 아니고 이미 있는 도로 mesh 위에 spline을 깔다보니 차량이 붕 떠있고 주행이 부자연스러움! 

[월드 상태]
RoadActor (스플라인만)
   ↓
Terrain (지형) 위에 깔림
   ↓
차량이 지형 위에서 주행
   ↓
지형 굴곡 때문에 튕김 발생

 

RoadActor에 메시 시각화 추가

 

ASplineMeshActor 상속받으려고 했는데, 알아보니까 

USplineComponent USplineMeshComponent

ASplineMeshActor는 USplineMeshComponent를 가진 액터라서 이걸 상속받으면 내가 짜놓은 자율주행 코드가 깨짐.

(함수가 없으니까)

→ AActor 상속 유지 + USplineComponent + USplineMeshComponent들 동적 생성

// RoadActor.h
UPROPERTY(EditAnywhere, Category = "Road|Mesh")
TObjectPtr<UStaticMesh> RoadMesh;

UPROPERTY(EditAnywhere, Category = "Road|Mesh")
float SegmentLength = 500.f;

UPROPERTY()
TArray<TObjectPtr<USplineMeshComponent>> SplineMeshes;

 


또 문제 발생

도로 메시가 스플라인과 어긋난 위치에 생성됨

스플라인:     ────────────
                          
도로 메시:  ←────────────  (살짝 옆에 떨어짐)

 

 

호출 순서를 잘못 했던 거였음

// 잘못된 순서
SetupAttachment(RootComponent);
RegisterComponent();
SetMobility(Movable);
SetStaticMesh(RoadMesh);
SetStartAndEnd(...);
SetForwardAxis(ForwardAxis);

 

SetupAttachment와 RegisterComponent를 너무 일찍 호출해서 메시가 잘못된 위치로 등록됨

// 올바른 순서: 모든 설정 끝낸 후 부착/등록
USplineMeshComponent* SMC = NewObject<USplineMeshComponent>(...);
SMC->SetMobility(EComponentMobility::Movable);
SMC->AttachToComponent(SplineComponent, ...);
SMC->SetForwardAxis(ForwardAxis, false);     // 메시 적용 전!
SMC->SetStaticMesh(RoadMesh);
SMC->SetStartAndEnd(StartPos, StartTan, EndPos, EndTan, true);
SMC->RegisterComponent();                     // 마지막!

 

 

또 문제 발생

메시 위치는 맞춰졌는데 이번엔 곡선 구간에서 도로가 직선처럼 보임

 

 

영상 자료와 BP_Road, ai를 참고해서 발견한 것:

// 기존
SMC->SetStartAndEnd(StartPos, StartTan, EndPos, EndTan, true);

 

스플라인에서 가져온 탄젠트(StartTan, EndTan)의 길이가 segment 실제 길이와 안 맞아서 메시가 출렁이거나 직선이 됨.

// 탄젠트 길이를 segment 실제 길이에 맞춰야 곡선이 자연스러움
StartTan = StartTan.GetSafeNormal() * ActualSegLen;
EndTan   = EndTan.GetSafeNormal()   * ActualSegLen;

SMC->SetStartAndEnd(StartPos, StartTan, EndPos, EndTan, true);

 

ai 도움을 받아 탄젠트 길이를 segment 길이로 정규화 해서 곡선을 표현함


또!!!!! 문제 발견

메시가 누적됨

 

같은 위치에 메시가 여러 층으로 겹쳐서 생성되는 현상이 발생

// 기존 ClearSplineMeshes
void ARoadActor::ClearSplineMeshes()
{
    for (USplineMeshComponent* SMC : SplineMeshes)
    {
        SMC->DestroyComponent();
    }
    SplineMeshes.Reset();
}

 

언리얼 핫리로드 후 SplineMeshes 배열은 초기화되지만 액터에 실제 붙어있는 메시 컴포넌트는 그대로 남음

그래서 배열을 순회해도 아무것도 안 지워지고 새 메시만 계속 쌓인 것

void ARoadActor::ClearSplineMeshes()
{
    // 액터에 붙어있는 모든 SplineMeshComponent를 직접 검색
    TArray<USplineMeshComponent*> ExistingMeshes;
    GetComponents<USplineMeshComponent>(ExistingMeshes);

    for (USplineMeshComponent* SMC : ExistingMeshes)
    {
        if (SMC) SMC->DestroyComponent();
    }
    SplineMeshes.Reset();
}

 

코드 수정

GetComponents<USplineMeshComponent> 는 액터에 실제 붙어있는 모든 컴포넌트를 가져오기 때문에 누적 문제를 해결함


알게된 것

 

1. 호출 순서가 중요한 함수들

SetForwardAxis  →  SetStaticMesh  →  SetStartAndEnd  →  RegisterComponent

 

이 순서를 지키지 않으면 메시 위치/회전이 어긋남

모든 설정 끝낸 후 등록

 

2. 탄젠트 정규화의 중요성

스플라인 메시의 곡선 표현은 탄젠트 길이를 segment 길이에 맞추는 것이 핵심

StartTan = StartTan.GetSafeNormal() * ActualSegLen;
EndTan   = EndTan.GetSafeNormal()   * ActualSegLen;

 

 

3. 핫리로드와 컴포넌트 추적

배열에 추적된 컴포넌트만 정리하는 것이 항상 안전한 건 아님

액터에 실제 붙어있는 컴포넌트를 직접 검색(GetComponents<T>)하는 게 핫리로드 후에도 안정적

 

+ Recent posts