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