CARLA
Loading...
Searching...
No Matches
RoutePlanner.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
7#include "RoutePlanner.h"
8#include "Carla.h"
12
13#include <util/ue-header-guard-begin.h>
14#include "Engine/CollisionProfile.h"
15#include "Engine/Engine.h"
16#include "DrawDebugHelpers.h"
17#include "EngineUtils.h"
18#include <util/ue-header-guard-end.h>
19
20static bool IsSplineValid(const USplineComponent *SplineComponent)
21{
22 return (SplineComponent != nullptr) &&
23 (SplineComponent->GetNumberOfSplinePoints() > 1);
24}
25
27{
28 auto *Vehicle = (IsValid(Actor) ? nullptr : Cast<ACarlaWheeledVehicle>(Actor));
29 return (Vehicle != nullptr ?
30 Cast<AWheeledVehicleAIController>(Vehicle->GetController()) :
31 nullptr);
32}
33
34static const USplineComponent *PickARoute(
35 URandomEngine &RandomEngine,
36 const TArray<USplineComponent *> &Routes,
37 const TArray<float> &Probabilities)
38{
39 check(Routes.Num() > 0);
40
41 if (Routes.Num() == 1)
42 {
43 return Routes[0];
44 }
45
46 auto Index = RandomEngine.GetIntWithWeight(Probabilities);
47 check((Index >= 0) && (Index < Routes.Num()));
48 return Routes[Index];
49}
50
51ARoutePlanner::ARoutePlanner(const FObjectInitializer &ObjectInitializer)
52 : Super(ObjectInitializer)
53{
54 RootComponent =
55 ObjectInitializer.CreateDefaultSubobject<USceneComponent>(this, TEXT("SceneRootComponent"));
56 RootComponent->SetMobility(EComponentMobility::Static);
57
58 TriggerVolume = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerVolume"));
59 TriggerVolume->SetupAttachment(RootComponent);
60 TriggerVolume->SetHiddenInGame(true);
61 TriggerVolume->SetMobility(EComponentMobility::Static);
62 TriggerVolume->SetCollisionProfileName(FName("OverlapAll"));
63 TriggerVolume->SetGenerateOverlapEvents(true);
64
65 // Do not change default value here, our autopilot depends on this.
66 TriggerVolume->SetBoxExtent(FVector{32.0f, 32.0f, 32.0f});
67}
68
70{
71 CleanRoute();
72 Super::BeginDestroy();
73}
74
75#if WITH_EDITOR
76void ARoutePlanner::PostEditChangeProperty(FPropertyChangedEvent &PropertyChangedEvent)
77{
78 Super::PostEditChangeProperty(PropertyChangedEvent);
79 const auto Size = Routes.Num();
80 if (PropertyChangedEvent.Property && (Size != Probabilities.Num()))
81 {
82 Probabilities.Reset(Size);
83 for (auto i = 0; i < Size; ++i)
84 {
85 Probabilities.Add(1.0f / static_cast<float>(Size));
86 if (Routes[i] == nullptr)
87 {
88 Routes[i] = NewObject<USplineComponent>(this);
89 Routes[i]->SetupAttachment(RootComponent);
90 Routes[i]->SetHiddenInGame(true);
91 Routes[i]->SetMobility(EComponentMobility::Static);
92 Routes[i]->RegisterComponent();
93 }
94 }
95 }
96}
97#endif // WITH_EDITOR
98
99void ARoutePlanner::AddRoute(float probability, const TArray<FVector> &routePoints)
100{
101 USplineComponent *NewSpline = NewObject<USplineComponent>(this);
102 NewSpline->bHiddenInGame = true;
103
104 #if WITH_EDITOR
105 NewSpline->EditorUnselectedSplineSegmentColor = FLinearColor(1.f, 0.15f, 0.15f);
106 #endif // WITH_EDITOR
107
108 NewSpline->SetLocationAtSplinePoint(0, routePoints[0], ESplineCoordinateSpace::World, true);
109 NewSpline->SetLocationAtSplinePoint(1, routePoints[1], ESplineCoordinateSpace::World, true);
110
111 for (int i = 2; i < routePoints.Num(); ++i)
112 {
113 NewSpline->AddSplinePoint(routePoints[i], ESplineCoordinateSpace::World, true);
114 }
115
116 Routes.Add(NewSpline);
117 Probabilities.Add(probability);
118}
119
121{
122 Routes.Empty();
123 Probabilities.Empty();
124}
125
127{
128 if (IsValid(&Controller) && (Controller.GetRandomEngine() != nullptr))
129 {
130 auto *RandomEngine = Controller.GetRandomEngine();
131 auto *Route = PickARoute(*RandomEngine, Routes, Probabilities);
132
133 TArray<FVector> WayPoints;
134 const auto Size = Route->GetNumberOfSplinePoints();
135 if (Size > 1)
136 {
137 WayPoints.Reserve(Size);
138 for (auto i = 1; i < Size; ++i)
139 {
140 WayPoints.Add(Route->GetLocationAtSplinePoint(i, ESplineCoordinateSpace::World));
141 }
142
143 Controller.SetFixedRoute(WayPoints);
144 }
145 else
146 {
147 UE_LOG(LogCarla, Error, TEXT("ARoutePlanner '%s' has a route with zero way-points."), *GetName());
148 }
149 }
150
151}
152
154 const UObject *WorldContextObject,
155 const FVector &Location,
156 float SearchRadius,
157 FRotator &OutRotation)
158{
159 const UWorld *World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
160 if (World == nullptr)
161 {
162 return false;
163 }
164
165 bool bFound = false;
166 float ClosestDistanceSq = FMath::Square(SearchRadius);
167 FVector ClosestDirection = FVector::ForwardVector;
168
169 for (TActorIterator<ARoutePlanner> It(World); It; ++It)
170 {
171 for (const USplineComponent *Route : It->Routes)
172 {
173 if (!IsSplineValid(Route))
174 {
175 continue;
176 }
177 const FVector ClosestPoint =
178 Route->FindLocationClosestToWorldLocation(Location, ESplineCoordinateSpace::World);
179 const float DistanceSq = FVector::DistSquared(Location, ClosestPoint);
180 if (DistanceSq <= ClosestDistanceSq)
181 {
182 bFound = true;
183 ClosestDistanceSq = DistanceSq;
184 ClosestDirection =
185 Route->FindDirectionClosestToWorldLocation(Location, ESplineCoordinateSpace::World);
186 }
187 }
188 }
189
190 if (bFound)
191 {
192 OutRotation = ClosestDirection.Rotation();
193 }
194 return bFound;
195}
196
198{
199 if (Routes.Num() < 1)
200 {
201 UE_LOG(LogCarla, Warning, TEXT("ARoutePlanner '%s' has no route assigned."), *GetName());
202 return;
203 }
204
205 for (auto &&Route : Routes)
206 {
207 if (!IsSplineValid(Route))
208 {
209 UE_LOG(LogCarla, Error, TEXT("ARoutePlanner '%s' has a route with zero way-points."), *GetName());
210 return;
211 }
212 }
213
214 // Register delegate on begin overlap.
215 if (!TriggerVolume->OnComponentBeginOverlap.IsAlreadyBound(this, &ARoutePlanner::OnTriggerBeginOverlap))
216 {
217 TriggerVolume->OnComponentBeginOverlap.AddDynamic(this, &ARoutePlanner::OnTriggerBeginOverlap);
218 }
219}
220
222{
223 Super::BeginPlay();
224 Init();
225}
226
227void ARoutePlanner::EndPlay(const EEndPlayReason::Type EndPlayReason)
228{
229 // Deregister the delegate.
230 if (TriggerVolume->OnComponentBeginOverlap.IsAlreadyBound(this, &ARoutePlanner::OnTriggerBeginOverlap))
231 {
232 TriggerVolume->OnComponentBeginOverlap.RemoveDynamic(this, &ARoutePlanner::OnTriggerBeginOverlap);
233 }
234
235 Super::EndPlay(EndPlayReason);
236}
237
239 UPrimitiveComponent * /*OverlappedComp*/,
240 AActor *OtherActor,
241 UPrimitiveComponent * /*OtherComp*/,
242 int32 /*OtherBodyIndex*/,
243 bool /*bFromSweep*/,
244 const FHitResult & /*SweepResult*/)
245{
246 auto *Controller = GetVehicleController(OtherActor);
247 if (Controller != nullptr)
248 {
249 AssignRandomRoute(*Controller);
250 }
251}
252
254{
255#if WITH_EDITOR
256 for (int i = 0, lenRoutes = Routes.Num(); i < lenRoutes; ++i)
257 {
258 for (int j = 0, lenNumPoints = Routes[i]->GetNumberOfSplinePoints() - 1; j < lenNumPoints; ++j)
259 {
260 const FVector p0 = Routes[i]->GetLocationAtSplinePoint(j + 0, ESplineCoordinateSpace::World);
261 const FVector p1 = Routes[i]->GetLocationAtSplinePoint(j + 1, ESplineCoordinateSpace::World);
262
263 static const float MinThickness = 3.f;
264 static const float MaxThickness = 15.f;
265
266 const float Dist = (float) j / (float) lenNumPoints;
267 const float OneMinusDist = 1.f - Dist;
268 const float Thickness = OneMinusDist * MaxThickness + MinThickness;
269
270 if (bIsIntersection)
271 {
272 // from blue to black
273 DrawDebugLine(
274 GetWorld(), p0, p1, FColor(0, 0, 255 * OneMinusDist),
275 true, -1.f, 0, Thickness);
276 }
277 else
278 {
279 // from green to black
280 DrawDebugLine(
281 GetWorld(), p0, p1, FColor(0, 255 * OneMinusDist, 0),
282 true, -1.f, 0, Thickness);
283 }
284 }
285 }
286#endif
287}
static AWheeledVehicleAIController * GetVehicleController(AActor *Actor)
static bool IsSplineValid(const USplineComponent *SplineComponent)
static const USplineComponent * PickARoute(URandomEngine &RandomEngine, const TArray< USplineComponent * > &Routes, const TArray< float > &Probabilities)
static bool IsValid(const ACarlaWheeledVehicle *Vehicle)
virtual void BeginPlay() override
TArray< TObjectPtr< USplineComponent > > Routes
virtual void BeginDestroy() override
void AddRoute(float probability, const TArray< FVector > &routePoints)
ARoutePlanner(const FObjectInitializer &ObjectInitializer)
void AssignRandomRoute(AWheeledVehicleAIController &Controller) const
void OnTriggerBeginOverlap(UPrimitiveComponent *OverlappedComp, AActor *OtherActor, UPrimitiveComponent *OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult)
TArray< float > Probabilities
static bool FindRoadFlowRotationAtLocation(const UObject *WorldContextObject, const FVector &Location, float SearchRadius, FRotator &OutRotation)
Searches every ARoutePlanner in the world for the route spline point closest to Location,...
TObjectPtr< UBoxComponent > TriggerVolume
virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override
Wheeled vehicle controller with optional AI.
void SetFixedRoute(const TArray< FVector > &Locations, bool bOverwriteCurrent=true)
Set a fixed route to follow if autopilot is enabled.
int32 GetIntWithWeight(const TArray< float > &Weights)