14#include "Materials/MaterialInterface.h"
15#include "PCGComponent.h"
16#include "Components/BoxComponent.h"
17#include "Components/InstancedStaticMeshComponent.h"
18#include "EngineUtils.h"
19#include "TimerManager.h"
21#include "Misc/FileHelper.h"
22#include "Misc/Paths.h"
23#include "UObject/UObjectIterator.h"
24#include "HAL/IConsoleManager.h"
31 TEXT(
"carla.OpenDrive.StreetFurniture"), 1,
32 TEXT(
"1 (default): scatter street furniture (lamps, vegetation, signage) on generated OpenDRIVE worlds. 0: bare road only."),
56 void TagGeneratedComponent(UPrimitiveComponent &Component, uint32 ActorId,
crp::CityObjectLabel Label)
66 const int32 VertexOffset = Dst.
Vertices.Num();
71 for (
const int32 Index : Src.Triangles)
89 bool bSourceHasPerVertexUVs)
95 const auto &UVs = Mesh.
GetUVs();
102 TMap<size_t, int32> GlobalToLocal;
108 for (int32 k = 0; k < 3; ++k)
110 const size_t GlobalIdx = Indexes[i + k];
111 if (
const int32 *Found = GlobalToLocal.Find(GlobalIdx))
113 LocalIdx[k] = *Found;
118 const size_t VertexIdx = GlobalIdx - 1;
119 const auto &V = Vertices[VertexIdx];
120 const int32 NewLocalIdx = Result.
Vertices.Add(
121 FVector{1e2f * V.x, 1e2f * V.y, 1e2f * V.z});
122 if (bSourceHasPerVertexUVs && VertexIdx < UVs.size())
124 Result.
UV0.SetNum(NewLocalIdx + 1);
125 Result.
UV0[NewLocalIdx] = FVector2D{UVs[VertexIdx].x, UVs[VertexIdx].y};
127 GlobalToLocal.Add(GlobalIdx, NewLocalIdx);
128 LocalIdx[k] = NewLocalIdx;
143 TArray<FVector> Accum;
144 Accum.Init(FVector::ZeroVector, Result.
Vertices.Num());
145 for (int32 i = 0; i < Result.
Triangles.Num(); i += 3)
150 const FVector U = V1 - V0;
151 const FVector V = V2 - V0;
155 Normal.X = (U.Y * V.Z) - (U.Z * V.Y);
156 Normal.Y = (U.Z * V.X) - (U.X * V.Z);
157 Normal.Z = (U.X * V.Y) - (U.Y * V.X);
166 Accum[Result.
Triangles[i + 1]] += Normal;
167 Accum[Result.
Triangles[i + 2]] += Normal;
170 for (int32 i = 0; i < Accum.Num(); ++i)
172 const FVector Normal = Accum[i].GetSafeNormal(.0001f);
173 if (Normal != FVector::ZeroVector)
189 struct FGroundBSplineLattice
191 float OriginX = 0.0f;
192 float OriginY = 0.0f;
193 float Spacing = 1.0f;
196 TArray<float> Control;
198 static void Basis(
float T,
float B[4])
200 const float T2 = T * T;
201 const float T3 = T2 * T;
202 B[0] = (1.0f - T) * (1.0f - T) * (1.0f - T) / 6.0f;
203 B[1] = (3.0f * T3 - 6.0f * T2 + 4.0f) / 6.0f;
204 B[2] = (-3.0f * T3 + 3.0f * T2 + 3.0f * T + 1.0f) / 6.0f;
208 void LocalCoords(
float X,
float Y, int32 &I, int32 &J,
float &S,
float &T)
const
210 const float FX = FMath::Clamp((X - OriginX) / Spacing, 0.0f,
static_cast<float>(CellsX) - KINDA_SMALL_NUMBER);
211 const float FY = FMath::Clamp((Y - OriginY) / Spacing, 0.0f,
static_cast<float>(CellsY) - KINDA_SMALL_NUMBER);
212 I = FMath::Clamp(FMath::FloorToInt(FX), 0, CellsX - 1);
213 J = FMath::Clamp(FMath::FloorToInt(FY), 0, CellsY - 1);
214 S = FX -
static_cast<float>(I);
215 T = FY -
static_cast<float>(J);
218 float Eval(
float X,
float Y)
const
221 float S, T, BS[4], BT[4];
222 LocalCoords(X, Y, I, J, S, T);
225 const int32 NumCtrlX = CellsX + 3;
227 for (int32 L = 0; L < 4; ++L)
229 for (int32 K = 0; K < 4; ++K)
231 Z += BS[K] * BT[L] * Control[(J + L) * NumCtrlX + (I + K)];
237 void Fit(
const TArray<FVector> &Points)
239 const int32 NumCtrlX = CellsX + 3;
240 const int32 NumCtrlY = CellsY + 3;
241 TArray<float> Num, Den;
242 Num.Init(0.0f, NumCtrlX * NumCtrlY);
243 Den.Init(0.0f, NumCtrlX * NumCtrlY);
244 for (
const FVector &P : Points)
247 float S, T, BS[4], BT[4];
248 LocalCoords(P.X, P.Y, I, J, S, T);
252 for (int32 L = 0; L < 4; ++L)
254 for (int32 K = 0; K < 4; ++K)
256 SumW2 += FMath::Square(BS[K] * BT[L]);
259 if (SumW2 <= KINDA_SMALL_NUMBER)
263 for (int32 L = 0; L < 4; ++L)
265 for (int32 K = 0; K < 4; ++K)
267 const float W = BS[K] * BT[L];
268 const float Phi = W *
static_cast<float>(P.Z) / SumW2;
269 const int32 Idx = (J + L) * NumCtrlX + (I + K);
270 Num[Idx] += W * W * Phi;
275 Control.Init(0.0f, NumCtrlX * NumCtrlY);
276 for (int32 Idx = 0; Idx < Control.Num(); ++Idx)
278 if (Den[Idx] > KINDA_SMALL_NUMBER)
280 Control[Idx] = Num[Idx] / Den[Idx];
290 CellSize = FMath::Max(InCellSize, 50.0f);
293 Width = FMath::Max(1, FMath::CeilToInt((Bounds.Max.X - Bounds.Min.X) /
CellSize)) + 1;
294 Height = FMath::Max(1, FMath::CeilToInt((Bounds.Max.Y - Bounds.Min.Y) /
CellSize)) + 1;
300 const FVector &A,
const FVector &B,
const FVector &C,
bool bIsDrive)
302 const float MinX = FMath::Min3(A.X, B.X, C.X);
303 const float MaxX = FMath::Max3(A.X, B.X, C.X);
304 const float MinY = FMath::Min3(A.Y, B.Y, C.Y);
305 const float MaxY = FMath::Max3(A.Y, B.Y, C.Y);
306 const int32 X0 = FMath::Clamp(FMath::FloorToInt((MinX - OriginX) / CellSize), 0, Width - 1);
307 const int32 X1 = FMath::Clamp(FMath::CeilToInt((MaxX - OriginX) / CellSize), 0, Width - 1);
308 const int32 Y0 = FMath::Clamp(FMath::FloorToInt((MinY - OriginY) / CellSize), 0, Height - 1);
309 const int32 Y1 = FMath::Clamp(FMath::CeilToInt((MaxY - OriginY) / CellSize), 0, Height - 1);
311 const float Denom = (B.Y - C.Y) * (A.X - C.X) + (C.X - B.X) * (A.Y - C.Y);
312 if (FMath::Abs(Denom) < KINDA_SMALL_NUMBER)
317 for (int32 CY = Y0; CY <= Y1; ++CY)
319 for (int32 CX = X0; CX <= X1; ++CX)
321 const float PX = OriginX + (
static_cast<float>(CX) + 0.5f) * CellSize;
322 const float PY = OriginY + (
static_cast<float>(CY) + 0.5f) * CellSize;
323 float W0 = ((B.Y - C.Y) * (PX - C.X) + (C.X - B.X) * (PY - C.Y)) / Denom;
324 float W1 = ((C.Y - A.Y) * (PX - C.X) + (A.X - C.X) * (PY - C.Y)) / Denom;
325 float W2 = 1.0f - W0 - W1;
329 constexpr float Tolerance = -0.05f;
330 if (W0 < Tolerance || W1 < Tolerance || W2 < Tolerance)
336 const bool bInside = (W0 >= 0.0f && W1 >= 0.0f && W2 >= 0.0f);
337 W0 = FMath::Max(W0, 0.0f);
338 W1 = FMath::Max(W1, 0.0f);
339 W2 = FMath::Max(W2, 0.0f);
340 const float WSum = W0 + W1 + W2;
341 const float Z = (W0 * A.Z + W1 * B.Z + W2 * C.Z) / FMath::Max(WSum, KINDA_SMALL_NUMBER);
343 FCell &Cell = Cells[CY * Width + CX];
382 const float Straight = CellSize;
383 const float Diagonal = CellSize * 1.41421356f;
384 const float Infinite = 1e12f;
386 const auto Chamfer = [&](
auto GetSeed,
auto GetDist,
auto SetDist,
auto GetZ,
auto SetZ)
388 for (int32 Idx = 0; Idx < Cells.Num(); ++Idx)
390 if (GetSeed(Cells[Idx]))
392 SetDist(Cells[Idx], 0.0f);
393 SetZ(Cells[Idx], Cells[Idx].PavedMinZ);
397 SetDist(Cells[Idx], Infinite);
400 const auto Relax = [&](
FCell &Cell, int32 NX, int32 NY,
float Cost)
402 if (NX < 0 || NX >= Width || NY < 0 || NY >= Height)
406 const FCell &N = Cells[NY * Width + NX];
407 if (GetDist(N) + Cost < GetDist(Cell))
409 SetDist(Cell, GetDist(N) + Cost);
413 for (int32 CY = 0; CY < Height; ++CY)
415 for (int32 CX = 0; CX < Width; ++CX)
417 FCell &Cell = Cells[CY * Width + CX];
418 Relax(Cell, CX - 1, CY, Straight);
419 Relax(Cell, CX, CY - 1, Straight);
420 Relax(Cell, CX - 1, CY - 1, Diagonal);
421 Relax(Cell, CX + 1, CY - 1, Diagonal);
424 for (int32 CY = Height - 1; CY >= 0; --CY)
426 for (int32 CX = Width - 1; CX >= 0; --CX)
428 FCell &Cell = Cells[CY * Width + CX];
429 Relax(Cell, CX + 1, CY, Straight);
430 Relax(Cell, CX, CY + 1, Straight);
431 Relax(Cell, CX + 1, CY + 1, Diagonal);
432 Relax(Cell, CX - 1, CY + 1, Diagonal);
437 float ScratchZ = 0.0f;
442 [&ScratchZ](
const FCell &) {
return ScratchZ; },
443 [](
FCell &, float) {});
456 UE_LOG(LogCarla, Warning, TEXT(
"DumpRasterAt: raster not built"));
461 for (int32 DY = -1; DY <= 1; ++DY)
463 for (int32 DX = -1; DX <= 1; ++DX)
465 const int32 CX = CX0 + DX;
466 const int32 CY = CY0 + DY;
472 UE_LOG(LogCarla, Log,
473 TEXT(
"cell(%d,%d) ctr(%.1f,%.1f) drive=%d min=%.1f max=%.1f"),
477 Cell.bDrive, Cell.DriveMinZ, Cell.DriveMaxZ);
489 const int32 CX = FMath::FloorToInt((X - OriginX) / CellSize);
490 const int32 CY = FMath::FloorToInt((Y - OriginY) / CellSize);
491 if (CX < 0 || CX >= Width || CY < 0 || CY >= Height)
495 return &Cells[CY * Width + CX];
501 TEXT(
"carla.DumpRasterAt"),
502 TEXT(
"Dump road-raster chunk info around x y (metres)"),
503 FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(
504 [](
const TArray<FString> &Args, UWorld *World)
508 UE_LOG(LogCarla, Warning, TEXT(
"usage: carla.DumpRasterAt <x> <y>"));
511 const float CX = FCString::Atof(*Args[0]) * 100.0f;
512 const float CY = FCString::Atof(*Args[1]) * 100.0f;
513 for (TActorIterator<AOpenDriveGenerator> It(World); It; ++It)
515 It->DumpRasterAt(CX, CY);
523 TEXT(
"carla.MapGenQA"),
524 TEXT(
"Run the generated-map QA checks (road support, terrain invasion, objects on roadway)"),
525 FConsoleCommandWithWorldDelegate::CreateStatic([](UWorld *World)
527 for (TActorIterator<AOpenDriveGenerator> It(World); It; ++It)
529 It->RunGenerationQA();
532 UE_LOG(LogCarla, Warning, TEXT(
"carla.MapGenQA: no AOpenDriveGenerator in world"));
537 PrimaryActorTick.bCanEverTick =
false;
538 MeshComponent = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT(
"RootComponent"));
539 RootComponent = MeshComponent;
544 PrimaryActorTick.bCanEverTick =
false;
545 RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT(
"RootComponent"));
549 : Super(ObjectInitializer)
551 PrimaryActorTick.bCanEverTick =
false;
552 RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT(
"SceneComponent"));
553 SetRootComponent(RootComponent);
554 RootComponent->Mobility = EComponentMobility::Static;
561 PCGComponent = CreateDefaultSubobject<UPCGComponent>(TEXT(
"PCGComponent"));
562 PCGBoundsComponent = CreateDefaultSubobject<UBoxComponent>(TEXT(
"PCGBoundsComponent"));
572 if (OpenDrive.IsEmpty())
574 UE_LOG(LogCarla, Error, TEXT(
"The OpenDrive is empty"));
596 UE_LOG(LogCarla, Error, TEXT(
"The OpenDrive has not been loaded"));
612 const auto Meshes = CarlaMap->GenerateChunkedMesh(Parameters);
614 UMaterialInterface* ResolvedRoadMaterial =
RoadMaterial.LoadSynchronous();
615 UMaterialInterface* ResolvedSidewalkMaterial =
SidewalkMaterial.LoadSynchronous();
624 TArray<TPair<FProceduralCustomMesh, bool>> PendingSections;
626 for (
const auto &Mesh : Meshes) {
644 const bool bHasPerVertexUVs = Mesh->
GetUVs().size() == Mesh->
GetVertices().size();
646 TArray<FString> SectionOrder;
647 TMap<FString, FProceduralCustomMesh> SectionsByMaterial;
649 if (MaterialRanges.empty())
654 SectionOrder.Add(TEXT(
"road"));
655 SectionsByMaterial.Add(TEXT(
"road"), *Mesh);
659 for (
const auto &MatRange : MaterialRanges)
666 const FString MatName(MatRange.name.c_str());
669 AppendProceduralMesh(*Existing, Extracted);
673 SectionOrder.Add(MatName);
674 SectionsByMaterial.Add(MatName, MoveTemp(Extracted));
679 for (
const FString &MatName : SectionOrder)
682 const bool bIsSidewalk = (MatName == TEXT(
"sidewalk"));
683 for (
const FVector &Vertex : SectionMesh.
Vertices)
687 PendingSections.Emplace(MoveTemp(SectionMesh), bIsSidewalk);
701 for (
const auto &Section : PendingSections)
704 const bool bIsDrive = !Section.Value;
705 for (int32 T = 0; T + 2 < SectionMesh.
Triangles.Num(); T += 3)
717 for (
const auto &Section : PendingSections)
720 const bool bIsSidewalk = Section.Value;
723 UProceduralMeshComponent *TempPMC = TempActor->
MeshComponent;
724 TempPMC->bUseAsyncCooking =
true;
725 TempPMC->bUseComplexAsSimpleCollision =
true;
726 TempPMC->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
728 TempPMC->CreateMeshSection_LinearColor(
734 TArray<FLinearColor>(),
735 TArray<FProcMeshTangent>(),
738 UMaterialInterface* SectionMaterial = bIsSidewalk ? ResolvedSidewalkMaterial : ResolvedRoadMaterial;
741 TempPMC->SetMaterial(0, SectionMaterial);
744 TagGeneratedComponent(
746 TempActor->GetUniqueID(),
747 bIsSidewalk ? crp::CityObjectLabel::Sidewalks : crp::CityObjectLabel::Roads);
756 actor->SetActorHiddenInGame(
true);
781 UE_LOG(LogCarla, Error, TEXT(
"The OpenDrive has not been loaded"));
789 UE_LOG(LogCarla, Warning, TEXT(
"AOpenDriveGenerator: RoadMeshBounds is empty, skipping ground plane"));
810 const float MinX = PaddedBounds.Min.X;
811 const float MaxX = PaddedBounds.Max.X;
812 const float MinY = PaddedBounds.Min.Y;
813 const float MaxY = PaddedBounds.Max.Y;
822 constexpr int32 MaxGridVerticesPerAxis = 1024;
832 const float StepY = StepX;
838 UE_LOG(LogCarla, Warning, TEXT(
"AOpenDriveGenerator: road raster is empty, skipping ground plane"));
844 TArray<FVector> FitPoints;
852 FitPoints.Add(FVector(
869 TArray<FGroundBSplineLattice> SplineLevels;
870 const float ExtentX = MaxX - MinX;
871 const float ExtentY = MaxY - MinY;
872 float ControlSpacing = FMath::Max(ExtentX, ExtentY) / 4.0f;
873 const float FinestControlSpacing = FMath::Max(4.0f * SampleCellSize, StepX);
874 constexpr int32 MaxSplineLevels = 12;
875 TArray<FVector> Residuals = FitPoints;
876 while (SplineLevels.Num() < MaxSplineLevels)
878 FGroundBSplineLattice Level;
879 Level.OriginX = MinX;
880 Level.OriginY = MinY;
881 Level.Spacing = ControlSpacing;
882 Level.CellsX = FMath::Max(1, FMath::CeilToInt(ExtentX / ControlSpacing));
883 Level.CellsY = FMath::Max(1, FMath::CeilToInt(ExtentY / ControlSpacing));
884 Level.Fit(Residuals);
885 for (FVector &P : Residuals)
887 P.Z -= Level.Eval(P.X, P.Y);
889 SplineLevels.Add(MoveTemp(Level));
890 if (ControlSpacing <= FinestControlSpacing)
894 ControlSpacing *= 0.5f;
897 const auto EvalSpline = [&SplineLevels](
float X,
float Y) ->
float
900 for (
const FGroundBSplineLattice &Level : SplineLevels)
902 Z += Level.Eval(X, Y);
918 TArray<float> Heights;
919 Heights.SetNumUninitialized(NumX * NumY);
920 for (int32 IY = 0; IY < NumY; ++IY)
922 const float Y = Vertex0Y +
static_cast<float>(IY) * StepY;
923 for (int32 IX = 0; IX < NumX; ++IX)
925 const float X = Vertex0X +
static_cast<float>(IX) * StepX;
937 float GapWeight = 0.0f;
938 float GapZSum = 0.0f;
939 bool bGapZone =
false;
942 for (int32 DY = -1; DY <= 1 && !bGapZone; ++DY)
944 for (int32 DX = -1; DX <= 1; ++DX)
946 const int32 NX = VCellX + DX;
947 const int32 NY = VCellY + DY;
948 if (NX >= 0 && NX < RoadRaster.Width && NY >= 0 && NY <
RoadRaster.
Height &&
958 for (int32 DY = -3; DY <= 3; ++DY)
960 for (int32 DX = -3; DX <= 3; ++DX)
962 const int32 NX = VCellX + DX;
963 const int32 NY = VCellY + DY;
969 if (!N.bDriveCovered)
975 const float D2 = (FMath::Square(X - CtrX) + FMath::Square(Y - CtrY))
977 const float Weight = 1.0f / (D2 * D2 + 0.01f);
979 GapZSum += Weight * N.DriveMinZ;
993 const float HoldDist = 300.0f;
994 bool bGapHandled =
false;
995 if ((bGapZone || (Cell && Cell->bPaved)) && CarlaMap.has_value())
1005 const FVector Probe(X, Y, Cell ? Cell->NearestPavedMinZ : 0.0f);
1009 const FTransform LaneTransform = CarlaMap->ComputeTransform(*ClosestWp);
1010 const float LaneHalfWidth = 0.5f * 1e2f *
static_cast<float>(CarlaMap->GetLaneWidth(*ClosestWp));
1011 const FVector LaneLoc = LaneTransform.GetLocation();
1012 const float Dist = FVector::Dist2D(Probe, LaneLoc);
1013 if (Dist < LaneHalfWidth + 60.0f)
1018 GroundZ = ((Cell && Cell->bPaved) ? Cell->PavedMinZ
1020 - NearPavementClearance;
1031 const float EdgeDistA = FMath::Max(Dist - LaneHalfWidth, 1.0f);
1032 float ZA = LaneLoc.Z;
1034 FVector2D Away(X - LaneLoc.X, Y - LaneLoc.Y);
1035 if (Away.Normalize())
1037 const FVector ProbeB(
1038 X + Away.X * 250.0f, Y + Away.Y * 250.0f, Probe.Z);
1042 const FTransform LaneB = CarlaMap->ComputeTransform(*WpB);
1043 const float HalfB = 0.5f * 1e2f *
static_cast<float>(CarlaMap->GetLaneWidth(*WpB));
1044 const float EdgeDistB = FMath::Max(
1045 FVector::Dist2D(Probe, LaneB.GetLocation()) - HalfB, 1.0f);
1046 const float Alpha = EdgeDistA / (EdgeDistA + EdgeDistB);
1047 GapZ = FMath::Lerp(ZA, LaneB.GetLocation().Z, Alpha);
1059 else if (GapWeight > 0.0f)
1065 else if (Cell && Cell->bPaved)
1067 GroundZ = Cell->PavedMinZ - NearPavementClearance;
1069 else if (Cell && Cell->DistToPaved >= 0.0f && Cell->DistToPaved < SkirtWidth)
1071 const float Alpha = FMath::Clamp(
1072 (Cell->DistToPaved - HoldDist) / FMath::Max(SkirtWidth - HoldDist, 1.0f),
1074 GroundZ = FMath::Lerp(
1075 Cell->NearestPavedMinZ - NearPavementClearance,
1091 constexpr float ContinuousDropTolerance = 50.0f;
1092 const float OwnPavedZ = (Cell && Cell->bPaved) ? Cell->PavedMinZ : FLT_MAX;
1095 for (int32 DY = -1; DY <= 1; ++DY)
1097 for (int32 DX = -1; DX <= 1; ++DX)
1099 const int32 NX = CellX + DX;
1100 const int32 NY = CellY + DY;
1106 if (!Neighbour.bPaved)
1110 const bool bContinuous =
1111 OwnPavedZ != FLT_MAX &&
1112 (OwnPavedZ - Neighbour.PavedMinZ) <= ContinuousDropTolerance;
1119 const bool bGenuinelyLowerDeck =
1120 Neighbour.PavedMinZ < GroundZ - ContinuousDropTolerance;
1121 if (!bContinuous && (!bGapHandled || bGenuinelyLowerDeck))
1127 Heights[IY * NumX + IX] = GroundZ;
1141 MeshData.
Vertices.Reserve(NumX * NumY);
1142 MeshData.
UV0.Reserve(NumX * NumY);
1144 for (int32 IY = 0; IY < NumY; ++IY)
1146 const float Y = Vertex0Y +
static_cast<float>(IY) * StepY;
1147 for (int32 IX = 0; IX < NumX; ++IX)
1149 const float X = Vertex0X +
static_cast<float>(IX) * StepX;
1150 MeshData.
Vertices.Add(FVector(X, Y, Heights[IY * NumX + IX]));
1153 MeshData.
UV0.Add(FVector2D((X - MinX) / UVTile, (Y - MinY) / UVTile));
1160 MeshData.
Normals.Reserve(NumX * NumY);
1161 const auto VertexZ = [&](int32 IX, int32 IY) ->
float
1163 return MeshData.
Vertices[IY * NumX + IX].Z;
1165 for (int32 IY = 0; IY < NumY; ++IY)
1167 for (int32 IX = 0; IX < NumX; ++IX)
1169 const int32 XPrev = FMath::Max(IX - 1, 0);
1170 const int32 XNext = FMath::Min(IX + 1, NumX - 1);
1171 const int32 YPrev = FMath::Max(IY - 1, 0);
1172 const int32 YNext = FMath::Min(IY + 1, NumY - 1);
1173 const float DZDX = (VertexZ(XNext, IY) - VertexZ(XPrev, IY)) / (
static_cast<float>(XNext - XPrev) * StepX);
1174 const float DZDY = (VertexZ(IX, YNext) - VertexZ(IX, YPrev)) / (
static_cast<float>(YNext - YPrev) * StepY);
1175 MeshData.
Normals.Add(FVector(-DZDX, -DZDY, 1.0f).GetSafeNormal());
1183 MeshData.
Triangles.Reserve((NumX - 1) * (NumY - 1) * 6);
1184 for (int32 IY = 0; IY + 1 < NumY; ++IY)
1186 for (int32 IX = 0; IX + 1 < NumX; ++IX)
1188 const int32 C0 = (IY + 1) * NumX + IX;
1189 const int32 C1 = (IY + 1) * NumX + IX + 1;
1190 const int32 C2 = IY * NumX + IX + 1;
1191 const int32 C3 = IY * NumX + IX;
1192 MeshData.
Triangles.Append({ C0, C1, C3, C1, C2, C3 });
1197 UProceduralMeshComponent *TempPMC = TempActor->
MeshComponent;
1198 TempPMC->bUseAsyncCooking =
true;
1199 TempPMC->bUseComplexAsSimpleCollision =
true;
1200 TempPMC->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
1202 TempPMC->CreateMeshSection_LinearColor(
1208 TArray<FLinearColor>(),
1209 TArray<FProcMeshTangent>(),
1212 if (UMaterialInterface* ResolvedGroundMaterial =
GroundMaterial.LoadSynchronous())
1214 TempPMC->SetMaterial(0, ResolvedGroundMaterial);
1217 TagGeneratedComponent(*TempPMC, TempActor->GetUniqueID(), crp::CityObjectLabel::Terrain);
1221 TempActor->SetActorHiddenInGame(
true);
1243 Mask.SetNumZeroed(W * H);
1244 for (int32 Idx = 0; Idx < W * H; ++Idx)
1251 const auto Morph = [&](uint8 Grow)
1253 TArray<uint8> Out = Mask;
1254 for (int32 CY = 0; CY < H; ++CY)
1256 for (int32 CX = 0; CX < W; ++CX)
1258 if (Mask[CY * W + CX] == Grow)
1262 for (int32 DY = -1; DY <= 1; ++DY)
1264 for (int32 DX = -1; DX <= 1; ++DX)
1266 const int32 NX = CX + DX;
1267 const int32 NY = CY + DY;
1268 if (NX >= 0 && NX < W && NY >= 0 && NY < H && Mask[NY * W + NX] == Grow)
1270 Out[CY * W + CX] = Grow;
1278 Mask = MoveTemp(Out);
1280 for (int32 I = 0; I < K; ++I) { Morph(1); }
1281 for (int32 I = 0; I < K; ++I) { Morph(0); }
1290 for (int32 Idx = 0; Idx < W * H; ++Idx)
1292 Mask[Idx] = (Mask[Idx] && !
RoadRaster.
Cells[Idx].bDriveCovered) ? 1 : 0;
1300 TMap<int32, float> NodeZCache;
1301 const auto NodeZ = [&](int32 NX, int32 NY) ->
float
1303 const int32 Key = NY * (W + 1) + NX;
1304 if (
const float *Found = NodeZCache.Find(Key))
1310 float Result = FLT_MAX;
1311 float TouchMin = FLT_MAX;
1312 for (int32 DY = -1; DY <= 0; ++DY)
1314 for (int32 DX = -1; DX <= 0; ++DX)
1316 const int32 CX = NX + DX;
1317 const int32 CY = NY + DY;
1318 if (CX < 0 || CX >= W || CY < 0 || CY >= H)
1323 if (N.bDriveCovered)
1325 TouchMin = FMath::Min(TouchMin, N.DriveMinZ);
1329 if (TouchMin != FLT_MAX)
1337 float WeightSum = 0.0f;
1339 for (int32 DY = -3; DY <= 2; ++DY)
1341 for (int32 DX = -3; DX <= 2; ++DX)
1343 const int32 CX = NX + DX;
1344 const int32 CY = NY + DY;
1345 if (CX < 0 || CX >= W || CY < 0 || CY >= H)
1350 if (!N.bDriveCovered)
1354 const float DCX =
static_cast<float>(DX) + 0.5f;
1355 const float DCY =
static_cast<float>(DY) + 0.5f;
1356 const float Weight = 1.0f / (DCX * DCX + DCY * DCY);
1357 WeightSum += Weight;
1358 ZSum += Weight * N.DriveMinZ;
1361 if (WeightSum > 0.0f)
1363 Result = ZSum / WeightSum;
1366 NodeZCache.Add(Key, Result);
1371 for (int32 CY = 0; CY < H; ++CY)
1373 for (int32 CX = 0; CX < W; ++CX)
1375 if (!Mask[CY * W + CX])
1384 bool bOverpass =
false;
1385 for (int32 DY = -K - 1; DY <= K + 1 && !bOverpass; ++DY)
1387 for (int32 DX = -K - 1; DX <= K + 1; ++DX)
1389 const int32 NX = CX + DX;
1390 const int32 NY = CY + DY;
1391 if (NX < 0 || NX >= W || NY < 0 || NY >= H)
1413 const float Z00 = NodeZ(CX, CY);
1414 const float Z10 = NodeZ(CX + 1, CY);
1415 const float Z11 = NodeZ(CX + 1, CY + 1);
1416 const float Z01 = NodeZ(CX, CY + 1);
1417 if (Z00 == FLT_MAX || Z10 == FLT_MAX || Z11 == FLT_MAX || Z01 == FLT_MAX)
1422 0.25f * (Z00 + Z10 + Z11 + Z01);
1426 UE_LOG(LogCarla, Log,
1427 TEXT(
"AOpenDriveGenerator: %d median gap cells lofted for the terrain"), GapCells);
1434 UE_LOG(LogCarla, Error, TEXT(
"The OpenDrive has not been loaded"));
1453 const std::vector<carla::geom::Location> Zones = CarlaMap->GetAllCrosswalkZones();
1459 constexpr float BarWidth = 0.4f;
1460 constexpr float BarGap = 0.4f;
1461 constexpr float EdgeMargin = 0.1f;
1465 auto AddZebraPolygon = [&MeshData](
const std::vector<carla::geom::Location> &Poly,
float ZOffsetCm)
1467 if (Poly.size() < 3)
1474 float LongestLen2 = -1.0f;
1475 for (
size_t k = 0; k < Poly.size(); ++k)
1477 const auto &P = Poly[k];
1478 const auto &Q = Poly[(k + 1) % Poly.size()];
1479 const float DX = Q.x - P.x, DY = Q.y - P.y;
1480 const float Len2 = DX * DX + DY * DY;
1481 if (Len2 > LongestLen2)
1487 if (LongestLen2 < 1e-4f)
1491 const float InvLen = 1.0f / FMath::Sqrt(LongestLen2);
1492 const FVector2D U((Poly[(Longest + 1) % Poly.size()].x - Poly[Longest].x) * InvLen,
1493 (Poly[(Longest + 1) % Poly.size()].y - Poly[Longest].y) * InvLen);
1494 const FVector2D V(-U.Y, U.X);
1496 struct FLocalPt {
float A, B, Z; };
1497 TArray<FLocalPt> Ring;
1498 float AMin = FLT_MAX, AMax = -FLT_MAX, BMin = FLT_MAX, BMax = -FLT_MAX;
1499 for (
const auto &P : Poly)
1501 const float A = P.x * U.X + P.y * U.Y;
1502 const float B = P.x * V.X + P.y * V.Y;
1503 Ring.Add({A, B, P.z});
1504 AMin = FMath::Min(AMin, A); AMax = FMath::Max(AMax, A);
1505 BMin = FMath::Min(BMin, B); BMax = FMath::Max(BMax, B);
1511 auto SurfaceZ = [&Ring](
float A,
float B) ->
float
1513 for (int32 K = 1; K + 1 < Ring.Num(); ++K)
1515 const FLocalPt &P0 = Ring[0], &P1 = Ring[K], &P2 = Ring[K + 1];
1516 const float Denom = (P1.B - P2.B) * (P0.A - P2.A) + (P2.A - P1.A) * (P0.B - P2.B);
1517 if (FMath::Abs(Denom) < 1e-9f)
1521 const float W0 = ((P1.B - P2.B) * (A - P2.A) + (P2.A - P1.A) * (B - P2.B)) / Denom;
1522 const float W1 = ((P2.B - P0.B) * (A - P2.A) + (P0.A - P2.A) * (B - P2.B)) / Denom;
1523 const float W2 = 1.0f - W0 - W1;
1524 if (W0 >= -1e-3f && W1 >= -1e-3f && W2 >= -1e-3f)
1526 return W0 * P0.Z + W1 * P1.Z + W2 * P2.Z;
1529 float Best = FLT_MAX, Z = Ring[0].Z;
1530 for (
const FLocalPt &P : Ring)
1532 const float D = FMath::Square(P.A - A) + FMath::Square(P.B - B);
1533 if (D < Best) { Best = D; Z = P.Z; }
1540 auto ClipBar = [&Ring](TArray<FVector2D> Subject) -> TArray<FVector2D>
1542 for (int32 K = 0; K < Ring.Num(); ++K)
1544 const FVector2D E0(Ring[K].A, Ring[K].B);
1545 const FVector2D E1(Ring[(K + 1) % Ring.Num()].A, Ring[(K + 1) % Ring.Num()].B);
1546 const FVector2D Edge = E1 - E0;
1549 FVector2D Centroid(0.0f, 0.0f);
1550 for (
const FLocalPt &P : Ring) { Centroid += FVector2D(P.A, P.B); }
1551 Centroid /=
static_cast<float>(Ring.Num());
1552 const float CentroidSide = Edge.X * (Centroid.Y - E0.Y) - Edge.Y * (Centroid.X - E0.X);
1553 const float Sign = CentroidSide >= 0.0f ? 1.0f : -1.0f;
1554 TArray<FVector2D> Out;
1555 for (int32 I = 0; I < Subject.Num(); ++I)
1557 const FVector2D &P = Subject[I];
1558 const FVector2D &Q = Subject[(I + 1) % Subject.Num()];
1559 const float SideP = Sign * (Edge.X * (P.Y - E0.Y) - Edge.Y * (P.X - E0.X));
1560 const float SideQ = Sign * (Edge.X * (Q.Y - E0.Y) - Edge.Y * (Q.X - E0.X));
1565 if ((SideP >= 0.0f) != (SideQ >= 0.0f))
1567 const float T = SideP / (SideP - SideQ);
1568 Out.Add(P + T * (Q - P));
1571 Subject = MoveTemp(Out);
1572 if (Subject.Num() < 3)
1580 const float Extent = AMax - AMin;
1581 const int32 NumBars = FMath::Max(1, FMath::FloorToInt((Extent - BarWidth) / (BarWidth + BarGap)) + 1);
1582 const float Start = AMin + 0.5f * (Extent - (NumBars * (BarWidth + BarGap) - BarGap));
1583 for (int32 Bar = 0; Bar < NumBars; ++Bar)
1585 const float A0 = Start + Bar * (BarWidth + BarGap);
1586 const float A1 = A0 + BarWidth;
1587 TArray<FVector2D> Quad;
1588 Quad.Add(FVector2D(A0, BMin + EdgeMargin));
1589 Quad.Add(FVector2D(A1, BMin + EdgeMargin));
1590 Quad.Add(FVector2D(A1, BMax - EdgeMargin));
1591 Quad.Add(FVector2D(A0, BMax - EdgeMargin));
1592 TArray<FVector2D> Clipped = ClipBar(MoveTemp(Quad));
1593 if (Clipped.Num() < 3)
1597 const int32 First = MeshData.
Vertices.Num();
1598 for (
const FVector2D &L : Clipped)
1600 const float X = L.X * U.X + L.Y * V.X;
1601 const float Y = L.X * U.Y + L.Y * V.Y;
1602 const float Z = SurfaceZ(L.X, L.Y);
1603 MeshData.
Vertices.Add(FVector(1e2f * X, 1e2f * Y, 1e2f * Z + ZOffsetCm));
1604 MeshData.
Normals.Add(FVector::UpVector);
1605 MeshData.
UV0.Add(FVector2D((L.X - A0) / BarWidth, (L.Y - BMin) / FMath::Max(BMax - BMin, 0.1f)));
1607 for (int32 K = 1; K + 1 < Clipped.Num(); ++K)
1620 std::vector<carla::geom::Location> Current;
1621 size_t StartIdx = 0;
1622 for (
size_t i = 0; i < Zones.size(); ++i)
1624 if (i != StartIdx && Zones[StartIdx] == Zones[i])
1628 if (i >= Zones.size() - 1)
1635 Current.push_back(Zones[i]);
1644 UProceduralMeshComponent *TempPMC = TempActor->
MeshComponent;
1645 TempPMC->bUseAsyncCooking =
true;
1646 TempPMC->bUseComplexAsSimpleCollision =
true;
1649 TempPMC->SetCollisionEnabled(ECollisionEnabled::NoCollision);
1651 TempPMC->CreateMeshSection_LinearColor(
1657 TArray<FLinearColor>(),
1658 TArray<FProcMeshTangent>(),
1661 if (UMaterialInterface* ResolvedCrosswalkMaterial =
CrosswalkMaterial.LoadSynchronous())
1663 TempPMC->SetMaterial(0, ResolvedCrosswalkMaterial);
1668 TagGeneratedComponent(*TempPMC, TempActor->GetUniqueID(), crp::CityObjectLabel::RoadLines);
1672 TempActor->SetActorHiddenInGame(
true);
1682 UE_LOG(LogCarla, Error, TEXT(
"The OpenDrive has not been loaded"));
1704 constexpr float BoundsExtentMeters = 1e6f;
1708 std::vector<std::string> MarkInfo;
1709 const auto Markings = CarlaMap->GenerateLineMarkings(Parameters, MinPos, MaxPos, MarkInfo);
1715 for (
const auto &Mark : Markings)
1722 const bool bIsYellow = (Index < MarkInfo.size()) &&
1723 (MarkInfo[Index].find(
"yellow") != std::string::npos);
1726 if (!Mark || !Mark->GetVertices().size())
1732 UProceduralMeshComponent *TempPMC = TempActor->
MeshComponent;
1733 TempPMC->SetCollisionEnabled(ECollisionEnabled::NoCollision);
1736 for (FVector &Vertex : MeshData.
Vertices)
1742 TempPMC->CreateMeshSection_LinearColor(
1748 TArray<FLinearColor>(),
1749 TArray<FProcMeshTangent>(),
1752 UMaterialInterface* SectionMaterial = bIsYellow ? ResolvedYellow : ResolvedWhite;
1753 if (SectionMaterial)
1755 TempPMC->SetMaterial(0, SectionMaterial);
1758 TagGeneratedComponent(*TempPMC, TempActor->GetUniqueID(), crp::CityObjectLabel::RoadLines);
1762 TempActor->SetActorHiddenInGame(
true);
1771 const float FX = FMath::Clamp(
1773 0.0f,
static_cast<float>(
GroundGridNumX - 1) - KINDA_SMALL_NUMBER);
1774 const float FY = FMath::Clamp(
1776 0.0f,
static_cast<float>(
GroundGridNumY - 1) - KINDA_SMALL_NUMBER);
1777 const int32 IX = FMath::Min(FMath::FloorToInt(FX),
GroundGridNumX - 2);
1778 const int32 IY = FMath::Min(FMath::FloorToInt(FY),
GroundGridNumY - 2);
1779 const float S = FX -
static_cast<float>(IX);
1780 const float T = FY -
static_cast<float>(IY);
1785 return FMath::Lerp(FMath::Lerp(Z00, Z10, S), FMath::Lerp(Z01, Z11, S), T);
1789 const FName &Tag,
float Spacing,
float Offset,
float RoadClearance,
1790 bool bMeasureFromCurb,
bool bKeepOnSidewalk,
float SOffset)
1799 constexpr float BoundsExtentMeters = 1e6f;
1818 const auto Anchors = CarlaMap->GetTreesTransform(
1819 MinPos, MaxPos, Spacing, Offset, SOffset, bMeasureFromCurb, bKeepOnSidewalk);
1821 int32 NumSpawned = 0;
1822 int32 NumFilteredOnRoad = 0;
1824 for (
const auto &AnchorPair : Anchors)
1826 const FTransform AnchorTransform = AnchorPair.first;
1827 FVector Location = AnchorTransform.GetLocation();
1844 if (!bMeasureFromCurb && Cell && Cell->DistToDrive >= 0.0f && Cell->DistToDrive < RoadClearance)
1846 ++NumFilteredOnRoad;
1855 const FTransform LaneTransform = CarlaMap->ComputeTransform(*ClosestWp);
1856 const float LaneHalfWidth = 0.5f * 1e2f *
static_cast<float>(CarlaMap->GetLaneWidth(*ClosestWp));
1857 if (FVector::Dist2D(Location, LaneTransform.GetLocation()) < LaneHalfWidth + RoadClearance)
1859 ++NumFilteredOnRoad;
1870 if (Cell && Cell->bPaved)
1872 Location.Z = Cell->PavedMinZ;
1880 Location, AnchorTransform.Rotator());
1881 Anchor->Tags.Add(Tag);
1887 if (NumFilteredOnRoad > 0)
1889 UE_LOG(LogCarla, Log, TEXT(
"AOpenDriveGenerator: %s: discarded %d/%d anchors too close to a driving lane"),
1890 *Tag.ToString(), NumFilteredOnRoad,
static_cast<int32
>(Anchors.size()));
1900 UE_LOG(LogCarla, Error, TEXT(
"The OpenDrive has not been loaded"));
1906 UE_LOG(LogCarla, Log, TEXT(
"AOpenDriveGenerator: carla.OpenDrive.StreetFurniture=0, skipping street furniture"));
1920 const int32 TotalAnchors = NumLampAnchors + NumVegetationAnchors + NumSignageAnchors;
1921 UE_LOG(LogCarla, Log, TEXT(
"AOpenDriveGenerator: furniture anchors spawned: %d lamp, %d vegetation, %d signage"),
1922 NumLampAnchors, NumVegetationAnchors, NumSignageAnchors);
1924 if (TotalAnchors == 0)
1952 UE_LOG(LogCarla, Warning, TEXT(
"AOpenDriveGenerator: StreetFurnitureGraph is not ")
1953 TEXT(
"set, skipping PCG street-furniture scatter (%d lamp/vegetation/signage ")
1954 TEXT(
"anchors were still spawned and are ready to sample once a graph is assigned)."),
1968 UE_LOG(LogCarla, Error, TEXT(
"The OpenDrive has not been loaded"));
1972 const auto Waypoints = CarlaMap->GenerateWaypointsOnRoadEntries();
1973 for (
const auto &Wp : Waypoints)
1975 const FTransform Trans = CarlaMap->ComputeTransform(Wp);
1977 Spawner->SetActorRotation(Trans.GetRotation());
1978 Spawner->SetActorLocation(Trans.GetTranslation() + FVector(0.f, 0.f,
SpawnersHeight));
1995 FTimerHandle QATimerHandle;
1996 GetWorldTimerManager().SetTimer(
2004 UE_LOG(LogCarla, Warning, TEXT(
"MapGenQA: no generated map data to check"));
2008 constexpr int32 MaxReportedLocations = 50;
2009 const auto AppendLoc = [](FString &Out, int32 &Count,
float X,
float Y,
float Z,
const TCHAR *Extra =
nullptr)
2011 if (Count >= MaxReportedLocations)
2020 Out += FString::Printf(TEXT(
"{\"x\":%.1f,\"y\":%.1f,\"z\":%.1f%s%s}"),
2021 X / 100.0f, Y / 100.0f, Z / 100.0f,
2022 Extra ? TEXT(
",") : TEXT(
""), Extra ? Extra : TEXT(
""));
2027 int32 PavedCells = 0;
2028 int32 FloatingCells = 0;
2029 int32 InvadedCells = 0;
2030 int32 StackedCells = 0;
2031 int32 BridgeEdgeCells = 0;
2032 float WorstGap = 0.0f;
2033 float WorstInvasion = 0.0f;
2034 FString FloatingLocs, InvadedLocs;
2035 int32 FloatingLocCount = 0, InvadedLocCount = 0;
2055 bool bBridgeEdge =
false;
2056 bool bBoundary =
false;
2057 for (int32 DY = -1; DY <= 1; ++DY)
2059 for (int32 DX = -1; DX <= 1; ++DX)
2061 const int32 NX = CX + DX;
2062 const int32 NY = CY + DY;
2069 if (!Neighbour.bPaved)
2073 else if ((Cell.PavedMinZ - Neighbour.PavedMinZ) > 50.0f)
2095 WorstGap = FMath::Max(WorstGap, Gap);
2096 AppendLoc(FloatingLocs, FloatingLocCount, X, Y, Cell.PavedMinZ);
2099 else if (Gap < -1.0f)
2102 WorstInvasion = FMath::Max(WorstInvasion, -Gap);
2103 AppendLoc(InvadedLocs, InvadedLocCount, X, Y, Cell.PavedMinZ);
2109 int32 ObjectsChecked = 0;
2110 int32 ObjectViolations = 0;
2112 int32 ObjectLocCount = 0;
2120 const auto PivotBlocksRoad = [&](
const FVector &Pivot) ->
bool
2127 const bool bOnFootprint =
2128 Cell->
bDrive || (Cell->DistToDrive >= 0.0f && Cell->DistToDrive < 100.0f);
2131 return bOnFootprint && FMath::Abs(Pivot.Z - Cell->NearestPavedMinZ) < 500.0f;
2133 const auto IsExemptOwner = [](
const AActor *Owner) ->
bool
2139 return Owner ==
nullptr
2142 || Owner->IsA<
APawn>();
2144 for (TObjectIterator<UStaticMeshComponent> It; It; ++It)
2146 UStaticMeshComponent *Comp = *It;
2147 if (!
IsValid(Comp) || Comp->GetWorld() != GetWorld() || !Comp->GetStaticMesh())
2151 if (IsExemptOwner(Comp->GetOwner()))
2155 const auto CheckOnePivot = [&](
const FVector &Pivot)
2158 if (PivotBlocksRoad(Pivot))
2161 const FString Extra = FString::Printf(TEXT(
"\"class\":\"%s\""),
2162 *Comp->GetOwner()->GetClass()->GetName());
2163 AppendLoc(ObjectLocs, ObjectLocCount, Pivot.X, Pivot.Y, Pivot.Z, *Extra);
2166 if (
const UInstancedStaticMeshComponent *ISM = Cast<UInstancedStaticMeshComponent>(Comp))
2168 for (int32 Inst = 0; Inst < ISM->GetInstanceCount(); ++Inst)
2170 FTransform InstTransform;
2171 if (ISM->GetInstanceTransform(Inst, InstTransform,
true))
2173 CheckOnePivot(InstTransform.GetLocation());
2180 if (Comp->Bounds.GetBox().GetExtent().X > 50000.0f ||
2181 Comp->Bounds.GetBox().GetExtent().Y > 50000.0f)
2185 CheckOnePivot(Comp->GetComponentLocation());
2189 UE_LOG(LogCarla, Log,
2190 TEXT(
"MapGenQA: paved cells %d (stacked %d, bridge-edge %d) | floating %d (worst gap %.0fcm) | ")
2191 TEXT(
"terrain invasion %d (worst %.0fcm) | objects checked %d, on roadway %d"),
2192 PavedCells, StackedCells, BridgeEdgeCells, FloatingCells, WorstGap,
2193 InvadedCells, WorstInvasion, ObjectsChecked, ObjectViolations);
2195 const FString Report = FString::Printf(
2197 TEXT(
" \"support\": {\"paved_cells\": %d, \"stacked_cells\": %d, \"bridge_edge_cells\": %d, ")
2198 TEXT(
"\"floating_cells\": %d, \"worst_gap_cm\": %.1f, \"floating_locations\": [%s]},\n")
2199 TEXT(
" \"invasion\": {\"cells\": %d, \"worst_cm\": %.1f, \"locations\": [%s]},\n")
2200 TEXT(
" \"objects\": {\"checked\": %d, \"on_roadway\": %d, \"items\": [%s]}\n")
2202 PavedCells, StackedCells, BridgeEdgeCells, FloatingCells, WorstGap, *FloatingLocs,
2203 InvadedCells, WorstInvasion, *InvadedLocs,
2204 ObjectsChecked, ObjectViolations, *ObjectLocs);
2205 const FString ReportPath = FPaths::ProjectSavedDir() / TEXT(
"mapgen_qa.json");
2206 FFileHelper::SaveStringToFile(Report, *ReportPath);
2207 UE_LOG(LogCarla, Log, TEXT(
"MapGenQA: report written to %s"), *ReportPath);
2219 auto World = GetWorld();
2220 check(World !=
nullptr);
2223 AActor* TrafficLightManagerActor = UGameplayStatics::GetActorOfClass(World, ATrafficLightManager::StaticClass());
2224 if(TrafficLightManagerActor ==
nullptr) {
static TAutoConsoleVariable< int32 > CVarOpenDriveStreetFurniture(TEXT("carla.OpenDrive.StreetFurniture"), 1, TEXT("1 (default): scatter street furniture (lamps, vegetation, signage) on generated OpenDRIVE worlds. 0: bare road only."), ECVF_Default)
static FAutoConsoleCommandWithWorld GMapGenQACommand(TEXT("carla.MapGenQA"), TEXT("Run the generated-map QA checks (road support, terrain invasion, objects on roadway)"), FConsoleCommandWithWorldDelegate::CreateStatic([](UWorld *World) { for(TActorIterator< AOpenDriveGenerator > It(World);It;++It) { It->RunGenerationQA();return;} UE_LOG(LogCarla, Warning, TEXT("carla.MapGenQA: no AOpenDriveGenerator in world"));}))
Re-run the generation QA on the placed generator from the console: carla.MapGenQA (also runs automati...
static FAutoConsoleCommandWithWorldAndArgs GDumpRasterAtCommand(TEXT("carla.DumpRasterAt"), TEXT("Dump road-raster chunk info around x y (metres)"), FConsoleCommandWithWorldAndArgsDelegate::CreateStatic([](const TArray< FString > &Args, UWorld *World) { if(Args.Num()< 2) { UE_LOG(LogCarla, Warning, TEXT("usage: carla.DumpRasterAt <x> <y>"));return;} const float CX=FCString::Atof(*Args[0]) *100.0f;const float CY=FCString::Atof(*Args[1]) *100.0f;for(TActorIterator< AOpenDriveGenerator > It(World);It;++It) { It->DumpRasterAt(CX, CY);return;} }))
Dump the road-raster chunk table around a point (client metres): carla.DumpRasterAt <x> <y> – debuggi...
static bool IsValid(const ACarlaWheeledVehicle *Vehicle)
const std::optional< carla::road::Map > & GetMap() const
ACarlaProceduralMeshActor()
TObjectPtr< UProceduralMeshComponent > MeshComponent
bool LoadOpenDrive(const FString &OpenDrive)
Set the OpenDRIVE information as string and generates the queryable map structure.
TSoftObjectPtr< UMaterialInterface > LaneMarkingWhiteMaterial
Material applied to white lane-marking meshes.
FBox RoadMeshBounds
World-space bounding box of every road/sidewalk vertex generated by GenerateRoadMesh,...
const FString & GetOpenDrive() const
Get the OpenDRIVE information as string.
void RunGenerationQA()
Systematic post-generation validation, run automatically shortly after GenerateAll (delayed so the as...
float MedianFillMaxHeightDelta
Max height split (cm) of the driving surface samples WITHIN a single raster cell near a gap cell for ...
AOpenDriveGenerator(const FObjectInitializer &ObjectInitializer)
float SignageRoadClearance
float GroundUVTileSize
World-space size (cm) of one UV tile on the ground plane, i.e.
void GenerateGroundPlane()
Generates a terrain-following heightfield beneath the generated road network so gaps in the road/side...
void GeneratePoles()
Scatters street furniture (lamp poles, roadside vegetation, signage) along the generated road network...
float VegetationRoadClearance
FBox FurnitureAnchorBounds
Accumulated bounding box of every furniture anchor spawned for the current generation,...
TArray< float > GroundGridHeights
Evaluated ground heightfield (final terrain surface z per grid vertex, row-major, GroundGridNumY rows...
FRoadSurfaceRaster RoadRaster
TSoftObjectPtr< UMaterialInterface > GroundMaterial
Material applied to the ground plane (see GenerateGroundPlane).
void DumpRasterAt(float X, float Y) const
Debug: log the raster's weld/height record in a 3x3 cell block around (X, Y) in UE cm.
FName LampAnchorTag
Tag used to mark lamp-pole anchor points for the PCG graph to sample.
void GenerateRoadMesh()
Generates the road and sidewalk mesh based on the OpenDRIVE information.
TObjectPtr< UBoxComponent > PCGBoundsComponent
Provides the PCG component's generation bounds: PCG derives its grid bounds from the owner actor's co...
float SignageAnchorOffset
Lateral offset (m) of signage anchors past the curb line, clamped onto the sidewalk like the lamps (s...
float GroundSkirtWidth
Width (cm) of the blend band past the road footprint over which the terrain transitions from exactly ...
float LampAnchorSpacing
Spacing (m) between lamp anchors along each road.
float LampAnchorOffset
Lateral offset (m) of lamp anchors past the curb line (the outer edge of the last roadway lane: parki...
FName VegetationAnchorTag
Tag used to mark roadside-vegetation anchor points.
TSoftObjectPtr< UMaterialInterface > SidewalkMaterial
Material applied to sidewalk mesh sections.
void GenerateLaneMarkings()
Generates the lane-marking meshes (white/yellow) based on the OpenDRIVE information.
float SampleGroundGridHeight(float X, float Y) const
Bilinear interpolation of GroundGridHeights at a world XY position.
TObjectPtr< UPCGComponent > PCGComponent
Drives the runtime PCG scatter (see GeneratePoles).
float DecalZOffset
Vertical offset (cm) applied to crosswalk and lane-marking meshes above the road surface,...
float VegetationAnchorSpacing
Spacing (m) between vegetation anchors along each road.
int32 GenerateFurnitureAnchors(const FName &Tag, float Spacing, float Offset, float RoadClearance, bool bMeasureFromCurb, bool bKeepOnSidewalk, float SOffset)
Spawns one AProceduralFurnitureAnchor per transform returned by Map::GetTreesTransform for the given ...
int32 StreetFurnitureSeed
PCG seed of the furniture scatter; with it, every server and run places the same furniture and banner...
void GenerateCrosswalkMesh()
Generates the crosswalk mesh based on the OpenDRIVE information.
TSoftObjectPtr< UPCGGraphInterface > StreetFurnitureGraph
PCG graph asset that samples the tagged anchors below (via a "Get Actor Data" node per category,...
FVector2D GroundGridOrigin
float LampRoadClearance
Per-category clearance (cm) beyond the lane half-width that street furniture must keep from the neare...
virtual void BeginPlay() override
float QAStackedRoadGap
Min z spread (cm) within one sample cell that marks it as stacked roads (bridge over road) rather tha...
FName SignageAnchorTag
Tag used to mark decorative-signage anchor points.
void GenerateMedianFill()
Annotates the narrow unmapped strips between parallel carriageway roads so the terrain lofts flush ac...
float MedianFillMaxWidth
Widest gap (cm) between opposing driving surfaces that GenerateMedianFill closes with road surface; w...
TArray< TObjectPtr< AVehicleSpawnPoint > > VehicleSpawners
float QASupportGapMax
Max allowed gap (cm) between a road cell's lowest surface and the terrain below it before the cell co...
float SpawnersHeight
Determine the height where the spawners will be placed, relative to each RoutePlanner.
TSoftObjectPtr< UMaterialInterface > RoadMaterial
Material applied to driving-lane mesh sections.
TSoftObjectPtr< UMaterialInterface > CrosswalkMaterial
Material applied to the zebra-bar crosswalk meshes.
float GroundPlaneZOffset
Vertical offset (cm) applied below the road surface height where the terrain runs under road geometry...
bool IsStreetFurnitureGenerating() const
True while the street-furniture PCG scatter is still spawning; it finishes over the next frames.
float GroundHeightSampleCellSize
XY cell size (cm) of RoadRaster.
void GenerateSpawnPoints()
Generates spawn points along the road.
bool IsOpenDriveValid() const
Checks if the OpenDrive has been loaded and it's valid.
float GroundPlanePadding
How far (cm) the ground plane extends past the generated road/sidewalk bounding box on each side,...
float VegetationAnchorOffset
Lateral offset (m) of vegetation anchors – set past the lamp row so poles and trees don't overlap.
float SignageAnchorSpacing
Spacing (m) between signage anchors along each road.
TSoftObjectPtr< UMaterialInterface > LaneMarkingYellowMaterial
Material applied to yellow lane-marking meshes (centre line).
TArray< TObjectPtr< AActor > > ActorMeshList
Invisible marker placed along a generated road to anchor PCG-scattered street furniture (lamps,...
AProceduralFurnitureAnchor()
static FString GetTagAsString(crp::CityObjectLabel Tag)
Transform a CityObjectLabel into a string.
static void SetStencilValue(UPrimitiveComponent &Component, const uint32 ActorID, const crp::CityObjectLabel &Label, const bool bSetRenderCustomDepth)
Class In charge of creating and assigning traffic light groups, controllers and components.
Base class for spawner locations for walkers.
The game instance contains elements that must be kept alive in between levels.
const carla::rpc::OpendriveGenerationParameters & GetOpendriveGenerationParameters() const
static UCarlaGameInstance * GetGameInstance(const UObject *WorldContextObject)
static ACarlaGameModeBase * GetGameMode(const UObject *WorldContextObject)
static FString GetXODR(const UWorld *World)
Return the OpenDrive XML associated to MapName, or empty if the file is not found.
Mesh data container, validator and exporter.
const std::vector< uv_type > & GetUVs() const
const std::vector< index_type > & GetIndexes() const
const std::vector< material_type > & GetMaterials() const
const std::vector< vertex_type > & GetVertices() const
static void log_warning(Args &&... args)
uint8 bDriveCovered
Like bDrive but WITHOUT the rasterizer's negative barycentric tolerance: set only when the cell cente...
const FCell * CellAtWorld(float X, float Y) const
void Initialize(const FBox &Bounds, float InCellSize)
void BuildDistanceFields()
Two-pass chamfer distance transform filling DistToDrive/DistToPaved and NearestPavedMinZ.
void RasterizeTriangle(const FVector &A, const FVector &B, const FVector &C, bool bIsDrive)
Splats one triangle of road ("drive") or sidewalk surface into the grid: conservative 2D rasterizatio...
A definition of a Carla Mesh.
TArray< int32 > Triangles
TArray< FVector > Vertices
TArray< FVector > Normals
Material that references the vertex index start and end of a mesh where it is affecting.
Seting for map generation from opendrive without additional geometry.
bool enable_mesh_visibility