CARLA
Loading...
Searching...
No Matches
CityMapGenerator.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 "CityMapGenerator.h"
8#include "Carla.h"
10#include "MapGen/RoadMap.h"
11#include "Game/Tagger.h"
12
13#include <util/ue-header-guard-begin.h>
14#include "Components/InstancedStaticMeshComponent.h"
15#include "Engine/World.h"
16#include "Misc/Paths.h"
17#include <util/ue-header-guard-end.h>
18
19#include <algorithm>
20#include <unordered_set>
21
22#ifdef CARLA_ROAD_GENERATOR_EXTRA_LOG
23#include <sstream>
24#endif // CARLA_ROAD_GENERATOR_EXTRA_LOG
25
26namespace crp = carla::rpc;
27
28// =============================================================================
29// -- Private types ------------------------------------------------------------
30// =============================================================================
31
33public:
34
36
37 bool Insert(const HalfEdge &InHalfEdge)
38 {
39 return Set.insert(&InHalfEdge).second &&
40 Set.insert(&MapGen::DoublyConnectedEdgeList::GetPair(InHalfEdge)).second;
41 }
42
43private:
44
45 std::unordered_set<const HalfEdge *> Set;
46};
47
48// =============================================================================
49// -- Constructor and destructor -----------------------------------------------
50// =============================================================================
51
52ACityMapGenerator::ACityMapGenerator(const FObjectInitializer& ObjectInitializer)
53 : Super(ObjectInitializer)
54{
55 RoadMap = ObjectInitializer.CreateDefaultSubobject<URoadMap>(this, TEXT("RoadMap"));
56}
57
59
60// =============================================================================
61// -- Overriden from UObject ---------------------------------------------------
62// =============================================================================
63
64void ACityMapGenerator::PreSave(FObjectPreSaveContext ObjectSaveContext)
65{
66#if WITH_EDITOR
68 // Generate road map only if we are not cooking.
69 FCoreUObjectDelegates::OnObjectPreSave.Broadcast(this, ObjectSaveContext);
70 if (!GIsCookerLoadingPackage) {
71 check(RoadMap != nullptr);
73 }
74 }
75#endif // WITH_EDITOR
76
77 Super::PreSave(ObjectSaveContext);
78}
79
80// =============================================================================
81// -- Overriden from ACityMapMeshHolder ----------------------------------------
82// =============================================================================
83
96
97// =============================================================================
98// -- Map construction and update related methods ------------------------------
99// =============================================================================
100
102{
103 if (!bUseFixedSeed) {
104 FRandomStream randomStream;
105 randomStream.GenerateNewSeed();
106 Seed = randomStream.GetCurrentSeed();
107 }
108}
109
111{
112 if ((MapSizeX < 5u) || (MapSizeY < 5u)) {
113 MapSizeX = 5u;
114 MapSizeY = 5u;
115 UE_LOG(LogCarla, Warning, TEXT("Map size changed, was too small"));
116 }
117#ifdef CARLA_ROAD_GENERATOR_EXTRA_LOG
118 // Delete the dcel before the new one is created so indices are restored.
119 Dcel.Reset(nullptr);
120#endif // CARLA_ROAD_GENERATOR_EXTRA_LOG
122 UE_LOG(LogCarla, Log,
123 TEXT("Generated DCEL with: { %d vertices, %d half-edges, %d faces }"),
124 Dcel->CountNodes(),
125 Dcel->CountHalfEdges(),
126 Dcel->CountFaces());
127 DcelParser = MakeUnique<MapGen::GraphParser>(*Dcel);
128#ifdef CARLA_ROAD_GENERATOR_EXTRA_LOG
129 { // print the results of the parser.
130 std::wstringstream sout;
131 sout << "\nGenerated " << DcelParser->CityAreaCount() << " city areas: ";
132 for (auto i = 0u; i < DcelParser->CityAreaCount(); ++i) {
133 sout << "{ ";
134 auto &cityArea = DcelParser->GetCityAreaAt(i);
135 for (size_t j = 0u; j < cityArea.NodeCount(); ++j) {
136 sout << cityArea.GetNodeAt(j) << " ";
137 }
138 sout << "} ";
139 }
140 sout << "\nGenerated " << DcelParser->RoadSegmentCount() << " road segments: ";
141 for (auto i = 0u; i < DcelParser->RoadSegmentCount(); ++i) {
142 sout << "{ ";
143 auto &roadSegment = DcelParser->GetRoadSegmentAt(i);
144 for (size_t j = 0u; j < roadSegment.Size(); ++j) {
145 sout << roadSegment[j] << " ";
146 }
147 sout << "} ";
148 }
149 UE_LOG(LogCarla, Log, TEXT("\n%s"), sout.str().c_str());
150 }
151#endif // CARLA_ROAD_GENERATOR_EXTRA_LOG
152}
153
155{
156 check(Dcel != nullptr);
158 const Graph &graph = *Dcel;
159
160 const uint32 margin = CityMapMeshTag::GetRoadIntersectionSize() / 2u;
161
162 FHalfEdgeCounter HalfEdgeCounter;
163
164 // For each edge add road segment.
165 for (auto &edge : graph.GetHalfEdges()) {
166 if (HalfEdgeCounter.Insert(edge)) {
167 auto source = Graph::GetSource(edge).GetPosition();
168 auto target = Graph::GetTarget(edge).GetPosition();
169
170 if (source.x == target.x) {
171 // vertical
172 auto y = 1u + margin + std::min(source.y, target.y);
173 auto end = std::max(source.y, target.y) - margin;
174 for (; y < end; ++y) {
175 AddInstance(ECityMapMeshTag::RoadTwoLanes_LaneLeft, source.x, y, HALF_PI);
176 AddInstance(ECityMapMeshTag::RoadTwoLanes_LaneRight, source.x, y, HALF_PI);
177 AddInstance(ECityMapMeshTag::RoadTwoLanes_SidewalkLeft, source.x, y, HALF_PI);
178 AddInstance(ECityMapMeshTag::RoadTwoLanes_SidewalkRight, source.x, y, HALF_PI);
179 AddInstance(ECityMapMeshTag::RoadTwoLanes_LaneMarkingBroken, source.x, y, HALF_PI);
180 }
181 } else if (source.y == target.y) {
182 // horizontal
183 auto x = 1u + margin + std::min(source.x, target.x);
184 auto end = std::max(source.x, target.x) - margin;
185 for (; x < end; ++x) {
186 AddInstance(ECityMapMeshTag::RoadTwoLanes_LaneLeft, x, source.y);
187 AddInstance(ECityMapMeshTag::RoadTwoLanes_LaneRight, x, source.y);
188 AddInstance(ECityMapMeshTag::RoadTwoLanes_SidewalkLeft, x, source.y);
189 AddInstance(ECityMapMeshTag::RoadTwoLanes_SidewalkRight, x, source.y);
190 AddInstance(ECityMapMeshTag::RoadTwoLanes_LaneMarkingBroken, x, source.y);
191 }
192 } else {
193 UE_LOG(LogCarla, Warning, TEXT("Diagonal edge ignored"));
194 }
195 }
196 }
197
198#define ADD_INTERSECTION(tag, x, y, angle) \
199 AddInstance(tag ##_Lane0, x, y, angle); \
200 AddInstance(tag ##_Lane1, x, y, angle); \
201 AddInstance(tag ##_Lane2, x, y, angle); \
202 AddInstance(tag ##_Lane3, x, y, angle); \
203 AddInstance(tag ##_Lane4, x, y, angle); \
204 AddInstance(tag ##_Lane5, x, y, angle); \
205 AddInstance(tag ##_Lane6, x, y, angle); \
206 AddInstance(tag ##_Lane7, x, y, angle); \
207 AddInstance(tag ##_Lane8, x, y, angle); \
208 AddInstance(tag ##_Lane9, x, y, angle); \
209 AddInstance(tag ##_Sidewalk0, x, y, angle); \
210 AddInstance(tag ##_Sidewalk1, x, y, angle); \
211 AddInstance(tag ##_Sidewalk2, x, y, angle); \
212 AddInstance(tag ##_Sidewalk3, x, y, angle); \
213 AddInstance(tag ##_LaneMarking, x, y, angle);
214
215 // For each node add the intersection.
216 for (auto &node : graph.GetNodes()) {
217 const auto coords = node.GetPosition();
218 switch (node.IntersectionType) {
220 ADD_INTERSECTION(ECityMapMeshTag::Road90DegTurn, coords.x, coords.y, node.Rotation);
221 break;
223 ADD_INTERSECTION(ECityMapMeshTag::RoadTIntersection, coords.x, coords.y, node.Rotation);
224 break;
226 ADD_INTERSECTION(ECityMapMeshTag::RoadXIntersection, coords.x, coords.y, node.Rotation);
227 break;
228 default:
229 UE_LOG(LogCarla, Warning, TEXT("Intersection type not implemented"));
230 }
231 }
232
233#undef ADD_INTERSECTION
234}
235
236// Find first component of type road.
237static bool LineTrace(
238 UWorld *World,
239 const FVector &Start,
240 const FVector &End,
241 FHitResult &HitResult)
242{
243 TArray <FHitResult> OutHits;
244 static FName TraceTag = FName(TEXT("RoadTrace"));
245 const bool Success = World->LineTraceMultiByObjectType(
246 OutHits,
247 Start,
248 End,
249 FCollisionObjectQueryParams(ECollisionChannel::ECC_WorldStatic),
250 FCollisionQueryParams(TraceTag, true));
251
252 if (Success) {
253 for (FHitResult &Item : OutHits) {
254 if (ATagger::MatchComponent(*Item.Component, crp::CityObjectLabel::Roads)) {
255 HitResult = Item;
256 return true;
257 }
258 }
259 }
260 return false;
261}
262
264{
265 UE_LOG(LogCarla, Log, TEXT("Generating road map..."));
266
267 auto World = GetWorld();
268 check(GetWorld() != nullptr);
269 check(RoadMap != nullptr);
270
271 ATagger::TagActorsInLevel(*GetWorld(), bTagForSemanticSegmentation); // We need the tags.
272
273 const float IntersectionSize = CityMapMeshTag::GetRoadIntersectionSize();
274 const uint32 Margin = IntersectionSize / 2u;
275 const float Offset = GetMapScale() * Margin;
276
277 const float CmPerPixel = GetMapScale() / static_cast<float>(PixelsPerMapUnit);
278
279 const uint32 SizeX = PixelsPerMapUnit * (MapSizeX + 2u * Margin);
280 const uint32 SizeY = PixelsPerMapUnit * (MapSizeY + 2u * Margin);
281
282 const FTransform &ActorTransform = GetActorTransform();
283
284 const FVector MapOffset(-Offset, -Offset, 0.0f);
285 RoadMap->Reset(SizeX, SizeY, 1.0f / CmPerPixel, ActorTransform.Inverse(), MapOffset);
286
287 for (uint32 PixelY = 0u; PixelY < SizeY; ++PixelY) {
288 for (uint32 PixelX = 0u; PixelX < SizeX; ++PixelX) {
289 const float X = static_cast<float>(PixelX) * CmPerPixel - Offset;
290 const float Y = static_cast<float>(PixelY) * CmPerPixel - Offset;
291 const FVector Start = ActorTransform.TransformPosition(FVector(X, Y, 50.0f));
292 const FVector End = ActorTransform.TransformPosition(FVector(X, Y, -50.0f));
293
294 // Do the ray tracing.
295 FHitResult Hit;
296 if (LineTrace(World, Start, End, Hit)) {
297 auto StaticMeshComponent = Cast<UStaticMeshComponent>(Hit.Component.Get());
298 if (StaticMeshComponent == nullptr) {
299 UE_LOG(LogCarla, Error, TEXT("Road component is not UInstancedStaticMeshComponent"));
300 } else {
302 PixelX,
303 PixelY,
304 GetTag(*StaticMeshComponent->GetStaticMesh()),
305 StaticMeshComponent->GetOwner()->GetTransform(),
307 }
308 }
309 }
310 }
311
312#if WITH_EDITOR
313 RoadMap->Log();
314#endif // WITH_EDITOR
315
316 if (bSaveRoadMapToDisk) {
317 RoadMap->SaveAsPNG(FPaths::ProjectSavedDir(), World->GetMapName());
318 }
319
320#if WITH_EDITOR
321 RoadMap->DrawDebugPixelsToLevel(GetWorld(), !bDrawDebugPixelsToLevel);
322#endif // WITH_EDITOR
323}
float y
float x
static bool LineTrace(UWorld *World, const FVector &Start, const FVector &End, FHitResult &HitResult)
#define ADD_INTERSECTION(tag, x, y, angle)
long long node
Definition LaneGraph.cpp:43
bool bGenerateRoadMapOnSave
The road map is re-computed on save so we always store an up-to-date version.
void GenerateGraph()
Regenerate the DCEL.
bool bGenerateRoads
If false, no mesh is added, only the internal representation of road is generated.
uint32 MapSizeY
Size Y of the map in map units.
int32 Seed
Seed of the random map generated.
uint32 PixelsPerMapUnit
The resolution in pixels per map unit of the road map.
virtual void UpdateMap() override
Here does nothing, implement in derived classes.
TUniquePtr< MapGen::DoublyConnectedEdgeList > Dcel
bool bDrawDebugPixelsToLevel
If true, a debug point is drawn in the level for each pixel of the road map.
void GenerateRoads()
Add the road meshes to the scene based on the current DCEL.
bool bLeftHandTraffic
Whether the road map should be generated based on left-hand traffic.
bool bUseFixedSeed
If false, a random seed is generated each time.
void GenerateRoadMap()
Generate the road map image and save to disk if requested.
void UpdateSeeds()
Update the random seeds. Generate random if no fixed seed is used.
bool bTriggerRoadMapGeneration
Trigger the generation a the road map image of the current layout (used for off-road and opposite lan...
ACityMapGenerator(const FObjectInitializer &ObjectInitializer)
TUniquePtr< MapGen::GraphParser > DcelParser
virtual void PreSave(FObjectPreSaveContext ObjectSaveContext) override
uint32 MapSizeX
Size X of the map in map units.
bool bSaveRoadMapToDisk
If true, the road map encoded as an image is saved to disk.
bool bTagForSemanticSegmentation
If true, activate the custom depth pass of each tagged actor in the level.
void AddInstance(ECityMapMeshTag Tag, uint32 X, uint32 Y)
Add an instance of a mesh with a given tile location.
float GetMapScale() const
ECityMapMeshTag GetTag(const UStaticMesh &StaticMesh) const
Return the tag corresponding to StaticMesh.
static bool MatchComponent(const UPrimitiveComponent &Component, crp::CityObjectLabel Tag)
Return true if Component has been tagged with the given Tag.
Definition Tagger.h:61
static void TagActorsInLevel(UWorld &World, bool bTagForSemanticSegmentation)
Set the tag of every actor in level.
Definition Tagger.cpp:177
static uint32 GetRoadIntersectionSize()
Get the size in tiles of a road intersection side.
std::unordered_set< const HalfEdge * > Set
bool Insert(const HalfEdge &InHalfEdge)
Simple doubly-connected edge list structure.
static HalfEdge & GetPair(HalfEdge &halfEdge)
static TUniquePtr< DoublyConnectedEdgeList > Generate(uint32 SizeX, uint32 SizeY, int32 Seed)
Create a squared DoublyConnectedEdgeList of size SizeX times SizeY and generate random connections in...
Road map of the level.
Definition RoadMap.h:95
void Reset(uint32 Width, uint32 Height, float PixelsPerCentimeter, const FTransform &WorldToMap, const FVector &MapOffset)
Resets current map an initializes an empty map of the given size.
Definition RoadMap.cpp:83
void SetPixelAt(uint32 PixelX, uint32 PixelY, ECityMapMeshTag Tag, const FTransform &Transform, bool bInvertDirection=false)
Definition RoadMap.cpp:98
bool SaveAsPNG(const FString &Folder, const FString &MapName) const
Save the current map as PNG with the pixel data encoded as color.
Definition RoadMap.cpp:251