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 namespace crp = carla::rpc;
26 
27 // =============================================================================
28 // -- Private types ------------------------------------------------------------
29 // =============================================================================
30 
32 public:
33 
35 
36  bool Insert(const HalfEdge &InHalfEdge)
37  {
38  return Set.insert(&InHalfEdge).second &&
39  Set.insert(&MapGen::DoublyConnectedEdgeList::GetPair(InHalfEdge)).second;
40  }
41 
42 private:
43 
44  std::unordered_set<const HalfEdge *> Set;
45 };
46 
47 // =============================================================================
48 // -- Constructor and destructor -----------------------------------------------
49 // =============================================================================
50 
51 ACityMapGenerator::ACityMapGenerator(const FObjectInitializer& ObjectInitializer)
52  : Super(ObjectInitializer)
53 {
54  RoadMap = ObjectInitializer.CreateDefaultSubobject<URoadMap>(this, TEXT("RoadMap"));
55 }
56 
58 
59 // =============================================================================
60 // -- Overriden from UObject ---------------------------------------------------
61 // =============================================================================
62 
63 void ACityMapGenerator::PreSave(const ITargetPlatform *TargetPlatform)
64 {
65 #if WITH_EDITOR
67  // Generate road map only if we are not cooking.
68  FCoreUObjectDelegates::OnObjectSaved.Broadcast(this);
69  if (!GIsCookerLoadingPackage) {
70  check(RoadMap != nullptr);
72  }
73  }
74 #endif // WITH_EDITOR
75 
76  Super::PreSave(TargetPlatform);
77 }
78 
79 // =============================================================================
80 // -- Overriden from ACityMapMeshHolder ----------------------------------------
81 // =============================================================================
82 
84 {
85  UpdateSeeds();
86  GenerateGraph();
87  if (bGenerateRoads) {
88  GenerateRoads();
89  }
93  }
94 }
95 
96 // =============================================================================
97 // -- Map construction and update related methods ------------------------------
98 // =============================================================================
99 
101 {
102  if (!bUseFixedSeed) {
103  FRandomStream randomStream;
104  randomStream.GenerateNewSeed();
105  Seed = randomStream.GetCurrentSeed();
106  }
107 }
108 
110 {
111  if ((MapSizeX < 5u) || (MapSizeY < 5u)) {
112  MapSizeX = 5u;
113  MapSizeY = 5u;
114  UE_LOG(LogCarla, Warning, TEXT("Map size changed, was too small"));
115  }
116 #ifdef CARLA_ROAD_GENERATOR_EXTRA_LOG
117  // Delete the dcel before the new one is created so indices are restored.
118  Dcel.Reset(nullptr);
119 #endif // CARLA_ROAD_GENERATOR_EXTRA_LOG
121  UE_LOG(LogCarla, Log,
122  TEXT("Generated DCEL with: { %d vertices, %d half-edges, %d faces }"),
123  Dcel->CountNodes(),
124  Dcel->CountHalfEdges(),
125  Dcel->CountFaces());
126  DcelParser = MakeUnique<MapGen::GraphParser>(*Dcel);
127 #ifdef CARLA_ROAD_GENERATOR_EXTRA_LOG
128  { // print the results of the parser.
129  std::wstringstream sout;
130  sout << "\nGenerated " << DcelParser->CityAreaCount() << " city areas: ";
131  for (auto i = 0u; i < DcelParser->CityAreaCount(); ++i) {
132  sout << "{ ";
133  auto &cityArea = DcelParser->GetCityAreaAt(i);
134  for (size_t j = 0u; j < cityArea.NodeCount(); ++j) {
135  sout << cityArea.GetNodeAt(j) << " ";
136  }
137  sout << "} ";
138  }
139  sout << "\nGenerated " << DcelParser->RoadSegmentCount() << " road segments: ";
140  for (auto i = 0u; i < DcelParser->RoadSegmentCount(); ++i) {
141  sout << "{ ";
142  auto &roadSegment = DcelParser->GetRoadSegmentAt(i);
143  for (size_t j = 0u; j < roadSegment.Size(); ++j) {
144  sout << roadSegment[j] << " ";
145  }
146  sout << "} ";
147  }
148  UE_LOG(LogCarla, Log, TEXT("\n%s"), sout.str().c_str());
149  }
150 #endif // CARLA_ROAD_GENERATOR_EXTRA_LOG
151 }
152 
154 {
155  check(Dcel != nullptr);
157  const Graph &graph = *Dcel;
158 
159  const uint32 margin = CityMapMeshTag::GetRoadIntersectionSize() / 2u;
160 
161  FHalfEdgeCounter HalfEdgeCounter;
162 
163  // For each edge add road segment.
164  for (auto &edge : graph.GetHalfEdges()) {
165  if (HalfEdgeCounter.Insert(edge)) {
166  auto source = Graph::GetSource(edge).GetPosition();
167  auto target = Graph::GetTarget(edge).GetPosition();
168 
169  if (source.x == target.x) {
170  // vertical
171  auto y = 1u + margin + std::min(source.y, target.y);
172  auto end = std::max(source.y, target.y) - margin;
173  for (; y < end; ++y) {
174  AddInstance(ECityMapMeshTag::RoadTwoLanes_LaneLeft, source.x, y, HALF_PI);
175  AddInstance(ECityMapMeshTag::RoadTwoLanes_LaneRight, source.x, y, HALF_PI);
176  AddInstance(ECityMapMeshTag::RoadTwoLanes_SidewalkLeft, source.x, y, HALF_PI);
177  AddInstance(ECityMapMeshTag::RoadTwoLanes_SidewalkRight, source.x, y, HALF_PI);
178  AddInstance(ECityMapMeshTag::RoadTwoLanes_LaneMarkingBroken, source.x, y, HALF_PI);
179  }
180  } else if (source.y == target.y) {
181  // horizontal
182  auto x = 1u + margin + std::min(source.x, target.x);
183  auto end = std::max(source.x, target.x) - margin;
184  for (; x < end; ++x) {
185  AddInstance(ECityMapMeshTag::RoadTwoLanes_LaneLeft, x, source.y);
186  AddInstance(ECityMapMeshTag::RoadTwoLanes_LaneRight, x, source.y);
187  AddInstance(ECityMapMeshTag::RoadTwoLanes_SidewalkLeft, x, source.y);
188  AddInstance(ECityMapMeshTag::RoadTwoLanes_SidewalkRight, x, source.y);
189  AddInstance(ECityMapMeshTag::RoadTwoLanes_LaneMarkingBroken, x, source.y);
190  }
191  } else {
192  UE_LOG(LogCarla, Warning, TEXT("Diagonal edge ignored"));
193  }
194  }
195  }
196 
197 #define ADD_INTERSECTION(tag, x, y, angle) \
198  AddInstance(tag ##_Lane0, x, y, angle); \
199  AddInstance(tag ##_Lane1, x, y, angle); \
200  AddInstance(tag ##_Lane2, x, y, angle); \
201  AddInstance(tag ##_Lane3, x, y, angle); \
202  AddInstance(tag ##_Lane4, x, y, angle); \
203  AddInstance(tag ##_Lane5, x, y, angle); \
204  AddInstance(tag ##_Lane6, x, y, angle); \
205  AddInstance(tag ##_Lane7, x, y, angle); \
206  AddInstance(tag ##_Lane8, x, y, angle); \
207  AddInstance(tag ##_Lane9, x, y, angle); \
208  AddInstance(tag ##_Sidewalk0, x, y, angle); \
209  AddInstance(tag ##_Sidewalk1, x, y, angle); \
210  AddInstance(tag ##_Sidewalk2, x, y, angle); \
211  AddInstance(tag ##_Sidewalk3, x, y, angle); \
212  AddInstance(tag ##_LaneMarking, x, y, angle);
213 
214  // For each node add the intersection.
215  for (auto &node : graph.GetNodes()) {
216  const auto coords = node.GetPosition();
217  switch (node.IntersectionType) {
219  ADD_INTERSECTION(ECityMapMeshTag::Road90DegTurn, coords.x, coords.y, node.Rotation);
220  break;
222  ADD_INTERSECTION(ECityMapMeshTag::RoadTIntersection, coords.x, coords.y, node.Rotation);
223  break;
225  ADD_INTERSECTION(ECityMapMeshTag::RoadXIntersection, coords.x, coords.y, node.Rotation);
226  break;
227  default:
228  UE_LOG(LogCarla, Warning, TEXT("Intersection type not implemented"));
229  }
230  }
231 
232 #undef ADD_INTERSECTION
233 }
234 
235 // Find first component of type road.
236 static bool LineTrace(
237  UWorld *World,
238  const FVector &Start,
239  const FVector &End,
240  FHitResult &HitResult)
241 {
242  TArray <FHitResult> OutHits;
243  static FName TraceTag = FName(TEXT("RoadTrace"));
244  const bool Success = World->LineTraceMultiByObjectType(
245  OutHits,
246  Start,
247  End,
248  FCollisionObjectQueryParams(ECollisionChannel::ECC_WorldStatic),
249  FCollisionQueryParams(TraceTag, true));
250 
251  if (Success) {
252  for (FHitResult &Item : OutHits) {
253  if (ATagger::MatchComponent(*Item.Component, crp::CityObjectLabel::Roads)) {
254  HitResult = Item;
255  return true;
256  }
257  }
258  }
259  return false;
260 }
261 
263 {
264  UE_LOG(LogCarla, Log, TEXT("Generating road map..."));
265 
266  auto World = GetWorld();
267  check(GetWorld() != nullptr);
268  check(RoadMap != nullptr);
269 
270  ATagger::TagActorsInLevel(*GetWorld(), bTagForSemanticSegmentation); // We need the tags.
271 
272  const float IntersectionSize = CityMapMeshTag::GetRoadIntersectionSize();
273  const uint32 Margin = IntersectionSize / 2u;
274  const float Offset = GetMapScale() * Margin;
275 
276  const float CmPerPixel = GetMapScale() / static_cast<float>(PixelsPerMapUnit);
277 
278  const uint32 SizeX = PixelsPerMapUnit * (MapSizeX + 2u * Margin);
279  const uint32 SizeY = PixelsPerMapUnit * (MapSizeY + 2u * Margin);
280 
281  const FTransform &ActorTransform = GetActorTransform();
282 
283  const FVector MapOffset(-Offset, -Offset, 0.0f);
284  RoadMap->Reset(SizeX, SizeY, 1.0f / CmPerPixel, ActorTransform.Inverse(), MapOffset);
285 
286  for (uint32 PixelY = 0u; PixelY < SizeY; ++PixelY) {
287  for (uint32 PixelX = 0u; PixelX < SizeX; ++PixelX) {
288  const float X = static_cast<float>(PixelX) * CmPerPixel - Offset;
289  const float Y = static_cast<float>(PixelY) * CmPerPixel - Offset;
290  const FVector Start = ActorTransform.TransformPosition(FVector(X, Y, 50.0f));
291  const FVector End = ActorTransform.TransformPosition(FVector(X, Y, -50.0f));
292 
293  // Do the ray tracing.
294  FHitResult Hit;
295  if (LineTrace(World, Start, End, Hit)) {
296  auto StaticMeshComponent = Cast<UStaticMeshComponent>(Hit.Component.Get());
297  if (StaticMeshComponent == nullptr) {
298  UE_LOG(LogCarla, Error, TEXT("Road component is not UInstancedStaticMeshComponent"));
299  } else {
301  PixelX,
302  PixelY,
303  GetTag(*StaticMeshComponent->GetStaticMesh()),
304  StaticMeshComponent->GetOwner()->GetTransform(),
306  }
307  }
308  }
309  }
310 
311 #if WITH_EDITOR
312  RoadMap->Log();
313 #endif // WITH_EDITOR
314 
315  if (bSaveRoadMapToDisk) {
316  RoadMap->SaveAsPNG(FPaths::ProjectSavedDir(), World->GetMapName());
317  }
318 
319 #if WITH_EDITOR
320  RoadMap->DrawDebugPixelsToLevel(GetWorld(), !bDrawDebugPixelsToLevel);
321 #endif // WITH_EDITOR
322 }
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.
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:222
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.
double min(double v1, double v2)
Definition: Simplify.h:294
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 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.
float GetMapScale() const
ECityMapMeshTag GetTag(const UStaticMesh &StaticMesh) const
Return the tag corresponding to StaticMesh.
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 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)
static bool MatchComponent(const UPrimitiveComponent &Component, crp::CityObjectLabel Tag)
Return true if Component has been tagged with the given Tag.
Definition: Tagger.h:62
void GenerateGraph()
Regenerate the DCEL.