13#include <util/ue-header-guard-begin.h>
14#include "Dom/JsonObject.h"
15#include "Misc/FileHelper.h"
16#include "Serialization/JsonReader.h"
17#include "Serialization/JsonSerializer.h"
18#include "HAL/PlatformFileManager.h"
19#include "Kismet/GameplayStatics.h"
20#include "Engine/StaticMeshActor.h"
21#include "Components/StaticMeshComponent.h"
22#include "Materials/MaterialInstanceDynamic.h"
23#include "Engine/StaticMesh.h"
24#include <util/ue-header-guard-end.h>
32 if (!TrafficLightManager)
34 UE_LOG(LogCarla, Warning, TEXT(
"MapLogicParser: TrafficLightManager is null"));
38 if (XODRFilePath.IsEmpty())
40 UE_LOG(LogCarla, Warning, TEXT(
"MapLogicParser: XODR file path is empty"));
46 if (LogicData.Num() == 0)
48 UE_LOG(LogCarla, Log, TEXT(
"MapLogicParser: No map_logic.json found for %s, using default timing"), *XODRFilePath);
54 UE_LOG(LogCarla, Log, TEXT(
"MapLogicParser: Applied custom timing to %d traffic light controllers"), LogicData.Num());
60 TArray<FTrafficLightLogicData> Result;
63 FString JsonFilePath = FPaths::Combine(DirectoryPath, TEXT(
"map_logic.json"));
65 if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*JsonFilePath))
67 UE_LOG(LogCarla, Log, TEXT(
"MapLogicParser: map_logic.json not found at %s"), *JsonFilePath);
72 if (!FFileHelper::LoadFileToString(JsonString, *JsonFilePath))
74 UE_LOG(LogCarla, Error, TEXT(
"MapLogicParser: Failed to load map_logic.json from %s"), *JsonFilePath);
78 UE_LOG(LogCarla, Log, TEXT(
"MapLogicParser: Successfully loaded map_logic.json from %s"), *JsonFilePath);
79 TSharedPtr<FJsonObject> JsonObject;
80 TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
82 if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
84 UE_LOG(LogCarla, Error, TEXT(
"MapLogicParser: Failed to parse JSON from %s"), *JsonFilePath);
88 const TArray<TSharedPtr<FJsonValue>>* TrafficLightsArray;
89 if (!JsonObject->TryGetArrayField(TEXT(
"TrafficLights"), TrafficLightsArray))
91 UE_LOG(LogCarla, Error, TEXT(
"MapLogicParser: No 'TrafficLights' array found in %s"), *JsonFilePath);
95 for (
const auto& JsonValue : *TrafficLightsArray)
97 TSharedPtr<FJsonObject> TrafficLightJson = JsonValue->AsObject();
98 if (TrafficLightJson.IsValid())
101 Result.Add(LogicData);
105 UE_LOG(LogCarla, Log, TEXT(
"MapLogicParser: Loaded %d traffic light configurations from %s"), Result.Num(), *JsonFilePath);
112 int32 SuccessCount = 0;
113 int32 FailedCount = 0;
115 for (
const auto& Data : LogicData)
121 Controller->
SetRedTime(Data.Timing.RedDuration);
128 UE_LOG(LogCarla, Error, TEXT(
"MapLogicParser: Controller '%s' not found for traffic light '%s' (JunctionID=%d)"),
129 *Data.TrafficLightGroupID, *Data.ActorName, Data.JunctionID);
134 UE_LOG(LogCarla, Log, TEXT(
"MapLogicParser: Applied timing to %d/%d traffic lights (%d failed)"),
135 SuccessCount, LogicData.Num(), FailedCount);
142 if (!TrafficLightJson.IsValid())
147 TrafficLightJson->TryGetStringField(TEXT(
"ActorName"), Result.
ActorName);
148 TrafficLightJson->TryGetStringField(TEXT(
"SignalID"), Result.
SignalID);
149 TrafficLightJson->TryGetNumberField(TEXT(
"JunctionID"), Result.
JunctionID);
150 TrafficLightJson->TryGetStringField(TEXT(
"TrafficLightGroupID"), Result.
TrafficLightGroupID);
151 const TSharedPtr<FJsonObject>* TimingObject;
152 if (TrafficLightJson->TryGetObjectField(TEXT(
"Timing"), TimingObject))
157 const TArray<TSharedPtr<FJsonValue>>* ModulesArray;
158 if (TrafficLightJson->TryGetArrayField(TEXT(
"Modules"), ModulesArray))
160 for (
const auto& ModuleValue : *ModulesArray)
162 TSharedPtr<FJsonObject> ModuleObject = ModuleValue->AsObject();
163 if (ModuleObject.IsValid())
166 const TArray<TSharedPtr<FJsonValue>>* LaneIdsArray;
167 if (ModuleObject->TryGetArrayField(TEXT(
"LaneIds"), LaneIdsArray))
169 for (
const auto& LaneIdValue : *LaneIdsArray)
172 if (LaneIdValue->TryGetNumber(LaneId))
183 const TArray<TSharedPtr<FJsonValue>>* HeadsArray;
184 if (TrafficLightJson->TryGetArrayField(TEXT(
"Heads"), HeadsArray))
186 for (
const auto& HeadValue : *HeadsArray)
188 TSharedPtr<FJsonObject> HeadObject = HeadValue->AsObject();
189 if (!HeadObject.IsValid())
194 HeadObject->TryGetStringField(TEXT(
"ComponentPrefix"), Head.
ComponentPrefix);
195 HeadObject->TryGetStringField(TEXT(
"SignalID"), Head.
SignalID);
196 const TArray<TSharedPtr<FJsonValue>>* LaneIdsArray;
197 if (HeadObject->TryGetArrayField(TEXT(
"LaneIds"), LaneIdsArray))
199 for (
const auto& LaneIdValue : *LaneIdsArray)
202 if (LaneIdValue->TryGetNumber(LaneId))
210 Result.
Heads.Add(Head);
222 if (!TimingJson.IsValid())
229 if (TimingJson->TryGetNumberField(TEXT(
"RedDuration"), TempValue))
231 Result.
RedDuration =
static_cast<float>(TempValue);
234 if (TimingJson->TryGetNumberField(TEXT(
"GreenDuration"), TempValue))
239 if (TimingJson->TryGetNumberField(TEXT(
"AmberDuration"), TempValue))
244 if (TimingJson->TryGetNumberField(TEXT(
"AmberBlinkInterval"), TempValue))
254 FString Directory, Filename, Extension;
255 FPaths::Split(FilePath, Directory, Filename, Extension);
268 AActor* FindBakedActor(UWorld* World,
const FString& ActorName,
const FVector& SignalLocation,
269 const FString& SignalId,
bool& bOutFoundByName)
271 bOutFoundByName =
false;
272 TArray<AActor*> AllActors;
273 UGameplayStatics::GetAllActorsOfClass(World, AActor::StaticClass(), AllActors);
275 if (!ActorName.IsEmpty())
277 const FName Wanted(*ActorName);
278 for (
AActor* Actor : AllActors)
280 if (Actor && Actor->ActorHasTag(Wanted))
282 bOutFoundByName =
true;
287 for (
AActor* Actor : AllActors)
289 if (Actor &&
Actor->GetActorLabel() == ActorName)
291 bOutFoundByName =
true;
298 constexpr float MaxDistanceMatchSqr = 2500.0f;
299 AActor* ClosestActor =
nullptr;
300 float MinDistance = MaxDistanceMatchSqr;
301 for (
AActor* Actor : AllActors)
303 if (!Actor)
continue;
310 UTrafficLightComponent* ExistingComp = Existing->GetTrafficLightComponent();
311 if (ExistingComp && !ExistingComp->GetSignId().IsEmpty()
312 && ExistingComp->GetSignId() != SignalId)
318 const float Dist = FVector::DistSquared(
Actor->GetActorLocation(), SignalLocation);
319 if (Dist < MinDistance)
322 ClosestActor =
Actor;
331 const TArray<UStaticMeshComponent*>& Meshes,
332 const FString& SignalId)
335 ADigitalTwinsTrafficLight::StaticClass(), SpawnTransform,
nullptr,
nullptr,
336 ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
337 if (!NewTrafficLight)
339 UE_LOG(LogCarla, Error, TEXT(
"Failed to spawn ADigitalTwinsTrafficLight for '%s'"), *SignalId);
343 for (UStaticMeshComponent* SourceMesh : Meshes)
345 if (!SourceMesh)
continue;
347 UStaticMeshComponent* NewMesh = NewObject<UStaticMeshComponent>(NewTrafficLight);
348 NewMesh->SetStaticMesh(SourceMesh->GetStaticMesh());
349 NewMesh->SetRelativeTransform(SourceMesh->GetRelativeTransform());
350 for (int32 i = 0; i < SourceMesh->GetNumMaterials(); i++)
352 NewMesh->SetMaterial(i, SourceMesh->GetMaterial(i));
354 NewMesh->RegisterComponent();
355 NewMesh->AttachToComponent(NewTrafficLight->GetRootComponent(),
356 FAttachmentTransformRules::KeepRelativeTransform);
362 UE_LOG(LogCarla, Error, TEXT(
"ADigitalTwinsTrafficLight has no TrafficLightComponent"));
363 NewTrafficLight->Destroy();
367 Comp->SetSignId(SignalId);
368 NewTrafficLight->FinishSpawning(SpawnTransform);
369 return NewTrafficLight;
375 void GroupMeshesBySignal(
const TArray<UStaticMeshComponent*>& MeshComponents,
377 TMap<FString, TArray<UStaticMeshComponent*>>& OutBySignal,
378 TArray<FString>& OutOrder)
380 TArray<UStaticMeshComponent*> Structural;
381 for (UStaticMeshComponent* Mesh : MeshComponents)
384 const FString Name = Mesh->GetName();
388 if (Name.StartsWith(Head.ComponentPrefix)
389 && (!Best || Head.ComponentPrefix.Len() > Best->
ComponentPrefix.Len()))
396 Structural.Add(Mesh);
400 if (!OutBySignal.Contains(SignalId))
402 OutOrder.Add(SignalId);
404 OutBySignal.FindOrAdd(SignalId).Add(Mesh);
406 if (Structural.Num() == 0)
411 : (OutOrder.Num() > 0 ? OutOrder[0] : Data.
SignalID);
412 if (!OutBySignal.Contains(Anchor))
414 OutOrder.Add(Anchor);
416 OutBySignal.FindOrAdd(Anchor).Append(Structural);
422 if (!TrafficLightManager)
424 UE_LOG(LogCarla, Warning, TEXT(
"MapLogicParser::ApplyLaneIdsFromMapLogic: TrafficLightManager is null"));
428 const std::optional<carla::road::Map>& Map = TrafficLightManager->
GetMap();
429 if (!Map.has_value())
431 UE_LOG(LogCarla, Warning, TEXT(
"MapLogicParser::ApplyLaneIdsFromMapLogic: Map is not available"));
436 if (LogicData.Num() == 0)
438 UE_LOG(LogCarla, Log, TEXT(
"MapLogicParser::ApplyLaneIdsFromMapLogic: No map_logic.json found or no data"));
442 const auto& Signals = Map->GetSignals();
444 int32 SuccessCount = 0;
446 int32 ByProximity = 0;
452 UE_LOG(LogCarla, Warning, TEXT(
"Skipping traffic light with empty SignalID (ActorName='%s')"), *Data.
ActorName);
456 std::string SignalIdStr(TCHAR_TO_UTF8(*Data.
SignalID));
458 if (Signals.find(SignalIdStr) == Signals.end())
460 UE_LOG(LogCarla, Warning, TEXT(
"Signal '%s' not found in OpenDRIVE"), *Data.
SignalID);
464 const auto& Signal = Signals.at(SignalIdStr);
465 auto CarlaTransform = Signal->GetTransform();
466 FTransform UETransform(CarlaTransform);
467 FVector SignalLocation = UETransform.GetLocation();
469 bool bFoundByName =
false;
470 AActor* ClosestActor = FindBakedActor(TrafficLightManager->GetWorld(), Data.
ActorName,
471 SignalLocation, Data.
SignalID, bFoundByName);
474 UE_LOG(LogCarla, Error, TEXT(
"No baked actor named '%s' and none within 50cm of signal '%s'"),
481 UE_LOG(LogCarla, Warning,
482 TEXT(
"Signal '%s': no actor tagged/labelled '%s'; fell back to the nearest actor. "
483 "Re-place the lights so the binding is by name, not by position."),
490 auto ApplyTiming = [&](UTrafficLightComponent* Comp)
504 const std::string Id(TCHAR_TO_UTF8(*Comp->GetSignId()));
505 if (Signals.at(Id)->GetType() ==
"1000002")
508 Walk.
State = ETrafficLightState::Green;
511 Clearance.
State = ETrafficLightState::Red;
513 Controller->
SetStates({Walk, Clearance});
524 if (TrafficLightActor)
528 if (TrafficLightComp->GetSignId() != Data.
SignalID)
530 TrafficLightComp->SetSignId(Data.
SignalID);
538 if (!TrafficLightComp->GetController())
542 ApplyTiming(TrafficLightComp);
543 if (Signals.at(std::string(TCHAR_TO_UTF8(*TrafficLightComp->GetSignId())))->GetType() !=
"1000002")
545 TrafficLightComp->InitializeSign(Map.value());
551 TArray<UStaticMeshComponent*> MeshComponents;
552 ClosestActor->GetComponents<UStaticMeshComponent>(MeshComponents);
554 if (MeshComponents.Num() == 0)
556 UE_LOG(LogCarla, Error, TEXT(
"DigitalTwins actor has no StaticMeshComponents"));
564 TMap<FString, TArray<UStaticMeshComponent*>> BySignal;
565 TArray<FString> Order;
566 if (Data.
Heads.Num() > 0)
568 GroupMeshesBySignal(MeshComponents, Data, BySignal, Order);
570 if (BySignal.Num() == 0)
573 BySignal.Add(Data.
SignalID, MeshComponents);
575 if (BySignal.Num() > 1)
578 UE_LOG(LogCarla, Log, TEXT(
"Rig '%s' carries %d signals: %s"), *Data.
ActorName,
579 BySignal.Num(), *FString::Join(Order, TEXT(
", ")));
582 const FTransform SpawnTransform(ClosestActor->GetActorRotation(), ClosestActor->GetActorLocation());
583 int32 SpawnedHere = 0;
584 for (
const FString& HeadSignalId : Order)
586 const std::string HeadSignalIdStr(TCHAR_TO_UTF8(*HeadSignalId));
587 if (Signals.find(HeadSignalIdStr) == Signals.end())
589 UE_LOG(LogCarla, Error, TEXT(
"Head signal '%s' of rig '%s' is not in the OpenDRIVE"),
594 TrafficLightManager->GetWorld(), SpawnTransform, BySignal[HeadSignalId], HeadSignalId);
595 if (!NewTrafficLight)
601 ApplyTiming(TrafficLightComp);
602 if (Signals.at(std::string(TCHAR_TO_UTF8(*TrafficLightComp->GetSignId())))->GetType() !=
"1000002")
604 TrafficLightComp->InitializeSign(Map.value());
611 ClosestActor->Destroy();
615 if (SplitRigs > 0 || ByProximity > 0)
617 UE_LOG(LogCarla, Log,
618 TEXT(
"Map logic: %d rig(s) split across several signals, %d matched by position "
619 "instead of by name"), SplitRigs, ByProximity);
621 UE_LOG(LogCarla, Log, TEXT(
"Applied lane IDs to %d traffic lights"), SuccessCount);
Traffic light class specifically for DigitalTwins meshes.
UTrafficLightComponent * GetTrafficLightComponent()
Class In charge of creating and assigning traffic light groups, controllers and components.
const std::optional< carla::road::Map > & GetMap()
void RegisterLightComponentFromOpenDRIVE(UTrafficLightComponent *TrafficLight)
UTrafficLightController * GetController(FString ControllerId)
static bool ParseAndApplyMapLogic(const FString &XODRFilePath, ATrafficLightManager *TrafficLightManager)
Parse map_logic.json file and apply traffic light timing configuration to the TrafficLightManager.
static TArray< FTrafficLightLogicData > LoadMapLogicFromJSON(const FString &XODRFilePath)
Load and parse the map_logic.json file from the same directory as the XODR file.
static FString GetDirectoryPath(const FString &FilePath)
Get the directory path from a full file path.
static FTrafficLightLogicData ParseTrafficLightFromJSON(TSharedPtr< FJsonObject > TrafficLightJson)
Parse individual traffic light data from JSON object.
static void ApplyLaneIdsFromMapLogic(const FString &XODRFilePath, ATrafficLightManager *TrafficLightManager)
Apply lane IDs from map_logic.json to traffic light components.
static void ApplyTrafficLightLogic(const TArray< FTrafficLightLogicData > &LogicData, ATrafficLightManager *TrafficLightManager)
Apply traffic light timing configuration to the TrafficLightManager.
static FTrafficLightTiming ParseTimingFromJSON(TSharedPtr< FJsonObject > TimingJson)
Parse timing data from JSON object.
Maps a controller from OpenDrive.
void SetGreenTime(float NewTime)
void SetYellowTime(float NewTime)
void SetRedTime(float NewTime)
void SetStates(TArray< FTrafficLightStage > States)
carla::SharedPtr< cc::Actor > Actor
One head of a baked Traffic Light Tool rig, and the OpenDRIVE signal it shows.
Represents a traffic light configuration entry from map_logic.json.
TArray< FTrafficLightModule > Modules
FTrafficLightTiming Timing
TArray< FTrafficLightHead > Heads
One entry per head of the baked rig.
FString TrafficLightGroupID
Represents a traffic light module with its lane assignments.
Defines a stage of a semaphor with a State and the time this state lasts.
Represents timing configuration for a traffic light from map_logic.json.