CARLA
Loading...
Searching...
No Matches
OpenDriveActor.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
8#include "Carla.h"
10
12#include <carla/geom/Math.h>
15#include <carla/rpc/String.h>
17
18#include <util/ue-header-guard-begin.h>
19#include "UObject/ConstructorHelpers.h"
20#include "DrawDebugHelpers.h"
21#include "UObject/ConstructorHelpers.h"
22#include "Materials/MaterialExpressionTextureSample.h"
23#include "DrawDebugHelpers.h"
24#include <util/ue-header-guard-end.h>
25
26#include <algorithm>
27#include <unordered_set>
28
29AOpenDriveActor::AOpenDriveActor(const FObjectInitializer &ObjectInitializer)
30 : Super(ObjectInitializer)
31{
32 PrimaryActorTick.bCanEverTick = false;
33
34 // Structure to hold one-time initialization
35 static struct FConstructorStatics
36 {
37 // A helper class object we use to find target UTexture2D object in resource
38 // package
39 ConstructorHelpers::FObjectFinderOptional<UTexture2D> TextureObject;
40 FName Category;
41 FText Name;
42 FConstructorStatics()
43 // Use helper class object to find the texture resource path
44 : TextureObject(TEXT("/Carla/Icons/OpenDriveActorIcon")),
45 Category(TEXT("OpenDriveActor")),
46 Name(NSLOCTEXT("SpriteCategory", "OpenDriveActor", "OpenDriveActor"))
47 {}
48 } ConstructorStatics;
49
50 // We need a scene component to attach Icon sprite
51 USceneComponent *SceneComponent =
52 ObjectInitializer.CreateDefaultSubobject<USceneComponent>(this, TEXT("SceneComp"));
53 RootComponent = SceneComponent;
54 RootComponent->Mobility = EComponentMobility::Static;
55
56#if WITH_EDITORONLY_DATA
58 ObjectInitializer.CreateEditorOnlyDefaultSubobject<UBillboardComponent>(this, TEXT("Sprite"));
60 {
61 // Get the sprite texture from helper class object
62 SpriteComponent->Sprite = ConstructorStatics.TextureObject.Get();
63 // Assign sprite category name
64 SpriteComponent->SpriteInfo.Category = ConstructorStatics.Category;
65 // Assign sprite display name
66 SpriteComponent->SpriteInfo.DisplayName = ConstructorStatics.Name;
67 // Attach sprite to scene component
68 SpriteComponent->SetupAttachment(RootComponent);
69 SpriteComponent->Mobility = EComponentMobility::Static;
70 SpriteComponent->SetEditorScale(1.f);
71 }
72#endif // WITH_EDITORONLY_DATA
73}
74
75#if WITH_EDITOR
76void AOpenDriveActor::PostEditChangeProperty(struct FPropertyChangedEvent &Event)
77{
78 Super::PostEditChangeProperty(Event);
79
80 const FName PropertyName = (Event.Property != NULL ? Event.Property->GetFName() : NAME_None);
81 if (PropertyName == GET_MEMBER_NAME_CHECKED(AOpenDriveActor, bGenerateRoutes))
82 {
83 if (bGenerateRoutes)
84 {
85 bGenerateRoutes = false;
86
87 RemoveRoutes(); // Avoid OpenDrive overlapping
88 RemoveSpawners(); // Restart the spawners in case OpenDrive has changed
90
91 if (bAddSpawners)
92 {
94 }
95 if (bShowDebug)
96 {
98 }
99 }
100 }
101 if (PropertyName == GET_MEMBER_NAME_CHECKED(AOpenDriveActor, bRemoveRoutes))
102 {
103 if (bRemoveRoutes)
104 {
105 bRemoveRoutes = false;
106
109 RemoveRoutes();
110 }
111 }
112 if (PropertyName == GET_MEMBER_NAME_CHECKED(AOpenDriveActor, bShowDebug))
113 {
114 if (bShowDebug)
115 {
116 DebugRoutes();
117 }
118 else
119 {
121 }
122 }
123 if (PropertyName == GET_MEMBER_NAME_CHECKED(AOpenDriveActor, bRemoveCurrentSpawners))
124 {
125 if (bRemoveCurrentSpawners)
126 {
127 bRemoveCurrentSpawners = false;
128
130 }
131 }
132}
133#endif // WITH_EDITOR
134
136{
137 BuildRoutes(GetWorld()->GetMapName());
138}
139
140void AOpenDriveActor::BuildRoutes(FString MapName)
141{
142 using Waypoint = carla::road::element::Waypoint;
143
144 // As the OpenDrive file has the same name as level, build the path to the
145 // xodr file using the lavel name and the game content directory.
146 static FString XodrContent;
147 if (CustomPath.IsEmpty()){
148 XodrContent = UOpenDrive::LoadXODR(MapName);
149 } else {
151 }
152
153 auto map = carla::opendrive::OpenDriveParser::Load(carla::rpc::FromLongFString(XodrContent));
154
155 if (!map.has_value())
156 {
157 UE_LOG(LogCarla, Error, TEXT("Failed to parse OpenDrive file."));
158 return;
159 }
160
161 // List with waypoints, each one at the end of each lane of the map
162 const std::vector<Waypoint> LaneWaypoints =
163 map->GenerateWaypointsOnRoadEntries();
164
165 std::unordered_map<Waypoint, std::vector<Waypoint>> PredecessorMap;
166
167 for (auto &Wp : LaneWaypoints)
168 {
169 const auto PredecessorsList = map->GetPredecessors(Wp);
170 if (PredecessorsList.empty())
171 {
172 continue;
173 }
174 const auto MinRoadId = *std::min_element(
175 PredecessorsList.begin(),
176 PredecessorsList.end(),
177 [](const auto &WaypointA, const auto &WaypointB) {
178 return WaypointA.road_id < WaypointB.road_id;
179 });
180 PredecessorMap[MinRoadId].emplace_back(Wp);
181 }
182
183 for (auto &&PredecessorWp : PredecessorMap)
184 {
185 ARoutePlanner *RoutePlanner = nullptr;
186
187 for (auto &&Wp : PredecessorWp.second)
188 {
189 std::vector<Waypoint> Waypoints;
190 auto CurrentWp = Wp;
191
192 do
193 {
194 Waypoints.emplace_back(CurrentWp);
195 const auto Successors = map->GetNext(CurrentWp, RoadAccuracy);
196 if (Successors.empty())
197 {
198 break;
199 }
200 if (Successors.front().road_id != Wp.road_id)
201 {
202 break;
203 }
204 CurrentWp = Successors.front();
205 } while (CurrentWp.road_id == Wp.road_id);
206
207 // connect the last wp of the current road to the first wp of the following road
208 const auto FollowingWp = map->GetSuccessors(CurrentWp);
209 if (!FollowingWp.empty())
210 {
211 Waypoints.emplace_back(FollowingWp.front());
212 }
213
214 if (Waypoints.size() >= 2)
215 {
216 TArray<FVector> Positions;
217 Positions.Reserve(Waypoints.size());
218 for (int i = 0; i < Waypoints.size(); ++i)
219 {
220 // Add the trigger height because the z position of the points does not
221 // influence on the driver AI and is easy to visualize in the editor
222 Positions.Add(map->ComputeTransform(Waypoints[i]).location +
223 FVector(0.f, 0.f, TriggersHeight));
224 }
225
226 // If the route planner does not exist, create it
227 if (RoutePlanner == nullptr )
228 {
229 const auto WpTransform = map->ComputeTransform(Wp);
230 RoutePlanner = GetWorld()->SpawnActor<ARoutePlanner>();
231 RoutePlanner->bIsIntersection = map->IsJunction(Wp.road_id);
232 RoutePlanner->SetBoxExtent(FVector(70.f, 70.f, 50.f));
233 RoutePlanner->SetActorRotation(WpTransform.rotation);
234 RoutePlanner->SetActorLocation(WpTransform.location +
235 FVector(0.f, 0.f, TriggersHeight));
236 }
237
238 if (RoutePlanner != nullptr)
239 {
240 RoutePlanner->AddRoute(1.f, Positions);
241 RoutePlanners.Add(RoutePlanner);
242 }
243 }
244 }
245 }
246}
247
249{
250 const int rp_num = RoutePlanners.Num();
251 for (int i = 0; i < rp_num; i++)
252 {
253 if (RoutePlanners[i] != nullptr)
254 {
255 RoutePlanners[i]->Destroy();
256 }
257 }
258 RoutePlanners.Empty();
259}
260
262{
263 for (int i = 0; i < RoutePlanners.Num(); ++i)
264 {
265 if (RoutePlanners[i] != nullptr)
266 {
267 RoutePlanners[i]->DrawRoutes();
268 }
269 }
270}
271
273{
274#if WITH_EDITOR
275 FlushPersistentDebugLines(GetWorld());
276#endif // WITH_EDITOR
277}
278
280{
281 for (int i = 0; i < RoutePlanners.Num(); ++i)
282 {
283 if (RoutePlanners[i] != nullptr)
284 {
285 if (!bOnIntersections && RoutePlanners[i]->bIsIntersection)
286 {
287 continue;
288 }
289 else
290 {
291 FTransform Trans = RoutePlanners[i]->GetActorTransform();
292 AVehicleSpawnPoint *Spawner = GetWorld()->SpawnActor<AVehicleSpawnPoint>();
293 Spawner->SetActorRotation(Trans.GetRotation());
294 Spawner->SetActorLocation(Trans.GetTranslation() + FVector(0.f, 0.f, SpawnersHeight));
295 VehicleSpawners.Add(Spawner);
296 }
297 }
298 }
299}
300
302{
303 const int vs_num = VehicleSpawners.Num();
304 for (int i = 0; i < vs_num; i++)
305 {
306 if (VehicleSpawners[i] != nullptr)
307 {
308 VehicleSpawners[i]->Destroy();
309 }
310 }
311 VehicleSpawners.Empty();
312}
float TriggersHeight
Trigger elevantion.
float SpawnersHeight
Determine the height where the spawners will be placed, relative to each RoutePlanner.
AOpenDriveActor(const FObjectInitializer &ObjectInitializer)
bool bOnIntersections
If true, spawners will be placed on junctions too.
float RoadAccuracy
Distance between waypoints where the cars will drive.
bool bAddSpawners
If true, spawners will be placed when generating the routes.
void RemoveRoutes()
Remove all the existing ARoutePlanner and VehicleSpawners previously generated by this class to avoid...
UBillboardComponent * SpriteComponent
A UBillboardComponent to hold Icon sprite.
void DebugRoutes() const
FString CustomPath
Specify a custom path. Expected a full path to the xodr.
TArray< ARoutePlanner * > RoutePlanners
TArray< AVehicleSpawnPoint * > VehicleSpawners
void RemoveDebugRoutes() const
Assign a random route to every ACarlaWheeledVehicle entering the trigger volume.
void AddRoute(float probability, const TArray< FVector > &routePoints)
void SetBoxExtent(const FVector &Extent)
Base class for spawner locations for walkers.
static std::optional< road::Map > Load(const std::string &opendrive)