CARLA
Loading...
Searching...
No Matches
MapLogicParser.cpp
Go to the documentation of this file.
1// Copyright (c) 2024 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 "MapLogicParser.h"
12
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>
25
29
30bool UMapLogicParser::ParseAndApplyMapLogic(const FString& XODRFilePath, ATrafficLightManager* TrafficLightManager)
31{
32 if (!TrafficLightManager)
33 {
34 UE_LOG(LogCarla, Warning, TEXT("MapLogicParser: TrafficLightManager is null"));
35 return false;
36 }
37
38 if (XODRFilePath.IsEmpty())
39 {
40 UE_LOG(LogCarla, Warning, TEXT("MapLogicParser: XODR file path is empty"));
41 return false;
42 }
43
44 TArray<FTrafficLightLogicData> LogicData = LoadMapLogicFromJSON(XODRFilePath);
45
46 if (LogicData.Num() == 0)
47 {
48 UE_LOG(LogCarla, Log, TEXT("MapLogicParser: No map_logic.json found for %s, using default timing"), *XODRFilePath);
49 return false;
50 }
51
52 ApplyTrafficLightLogic(LogicData, TrafficLightManager);
53
54 UE_LOG(LogCarla, Log, TEXT("MapLogicParser: Applied custom timing to %d traffic light controllers"), LogicData.Num());
55 return true;
56}
57
58TArray<FTrafficLightLogicData> UMapLogicParser::LoadMapLogicFromJSON(const FString& XODRFilePath)
59{
60 TArray<FTrafficLightLogicData> Result;
61
62 FString DirectoryPath = GetDirectoryPath(XODRFilePath);
63 FString JsonFilePath = FPaths::Combine(DirectoryPath, TEXT("map_logic.json"));
64
65 if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*JsonFilePath))
66 {
67 UE_LOG(LogCarla, Log, TEXT("MapLogicParser: map_logic.json not found at %s"), *JsonFilePath);
68 return Result;
69 }
70
71 FString JsonString;
72 if (!FFileHelper::LoadFileToString(JsonString, *JsonFilePath))
73 {
74 UE_LOG(LogCarla, Error, TEXT("MapLogicParser: Failed to load map_logic.json from %s"), *JsonFilePath);
75 return Result;
76 }
77
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);
81
82 if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
83 {
84 UE_LOG(LogCarla, Error, TEXT("MapLogicParser: Failed to parse JSON from %s"), *JsonFilePath);
85 return Result;
86 }
87
88 const TArray<TSharedPtr<FJsonValue>>* TrafficLightsArray;
89 if (!JsonObject->TryGetArrayField(TEXT("TrafficLights"), TrafficLightsArray))
90 {
91 UE_LOG(LogCarla, Error, TEXT("MapLogicParser: No 'TrafficLights' array found in %s"), *JsonFilePath);
92 return Result;
93 }
94
95 for (const auto& JsonValue : *TrafficLightsArray)
96 {
97 TSharedPtr<FJsonObject> TrafficLightJson = JsonValue->AsObject();
98 if (TrafficLightJson.IsValid())
99 {
100 FTrafficLightLogicData LogicData = ParseTrafficLightFromJSON(TrafficLightJson);
101 Result.Add(LogicData);
102 }
103 }
104
105 UE_LOG(LogCarla, Log, TEXT("MapLogicParser: Loaded %d traffic light configurations from %s"), Result.Num(), *JsonFilePath);
106
107 return Result;
108}
109
110void UMapLogicParser::ApplyTrafficLightLogic(const TArray<FTrafficLightLogicData>& LogicData, ATrafficLightManager* TrafficLightManager)
111{
112 int32 SuccessCount = 0;
113 int32 FailedCount = 0;
114
115 for (const auto& Data : LogicData)
116 {
117 UTrafficLightController* Controller = TrafficLightManager->GetController(Data.TrafficLightGroupID);
118
119 if (Controller)
120 {
121 Controller->SetRedTime(Data.Timing.RedDuration);
122 Controller->SetGreenTime(Data.Timing.GreenDuration);
123 Controller->SetYellowTime(Data.Timing.AmberDuration);
124 SuccessCount++;
125 }
126 else
127 {
128 UE_LOG(LogCarla, Error, TEXT("MapLogicParser: Controller '%s' not found for traffic light '%s' (JunctionID=%d)"),
129 *Data.TrafficLightGroupID, *Data.ActorName, Data.JunctionID);
130 FailedCount++;
131 }
132 }
133
134 UE_LOG(LogCarla, Log, TEXT("MapLogicParser: Applied timing to %d/%d traffic lights (%d failed)"),
135 SuccessCount, LogicData.Num(), FailedCount);
136}
137
139{
141
142 if (!TrafficLightJson.IsValid())
143 {
144 return Result;
145 }
146
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))
153 {
154 Result.Timing = ParseTimingFromJSON(*TimingObject);
155 }
156
157 const TArray<TSharedPtr<FJsonValue>>* ModulesArray;
158 if (TrafficLightJson->TryGetArrayField(TEXT("Modules"), ModulesArray))
159 {
160 for (const auto& ModuleValue : *ModulesArray)
161 {
162 TSharedPtr<FJsonObject> ModuleObject = ModuleValue->AsObject();
163 if (ModuleObject.IsValid())
164 {
165 FTrafficLightModule Module;
166 const TArray<TSharedPtr<FJsonValue>>* LaneIdsArray;
167 if (ModuleObject->TryGetArrayField(TEXT("LaneIds"), LaneIdsArray))
168 {
169 for (const auto& LaneIdValue : *LaneIdsArray)
170 {
171 int32 LaneId;
172 if (LaneIdValue->TryGetNumber(LaneId))
173 {
174 Module.LaneIds.Add(LaneId);
175 }
176 }
177 }
178 Result.Modules.Add(Module);
179 }
180 }
181 }
182
183 const TArray<TSharedPtr<FJsonValue>>* HeadsArray;
184 if (TrafficLightJson->TryGetArrayField(TEXT("Heads"), HeadsArray))
185 {
186 for (const auto& HeadValue : *HeadsArray)
187 {
188 TSharedPtr<FJsonObject> HeadObject = HeadValue->AsObject();
189 if (!HeadObject.IsValid())
190 {
191 continue;
192 }
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))
198 {
199 for (const auto& LaneIdValue : *LaneIdsArray)
200 {
201 int32 LaneId;
202 if (LaneIdValue->TryGetNumber(LaneId))
203 {
204 Head.LaneIds.Add(LaneId);
205 }
206 }
207 }
208 if (!Head.ComponentPrefix.IsEmpty())
209 {
210 Result.Heads.Add(Head);
211 }
212 }
213 }
214
215 return Result;
216}
217
219{
220 FTrafficLightTiming Result;
221
222 if (!TimingJson.IsValid())
223 {
224 return Result;
225 }
226
227 double TempValue;
228
229 if (TimingJson->TryGetNumberField(TEXT("RedDuration"), TempValue))
230 {
231 Result.RedDuration = static_cast<float>(TempValue);
232 }
233
234 if (TimingJson->TryGetNumberField(TEXT("GreenDuration"), TempValue))
235 {
236 Result.GreenDuration = static_cast<float>(TempValue);
237 }
238
239 if (TimingJson->TryGetNumberField(TEXT("AmberDuration"), TempValue))
240 {
241 Result.AmberDuration = static_cast<float>(TempValue);
242 }
243
244 if (TimingJson->TryGetNumberField(TEXT("AmberBlinkInterval"), TempValue))
245 {
246 Result.AmberBlinkInterval = static_cast<float>(TempValue);
247 }
248
249 return Result;
250}
251
252FString UMapLogicParser::GetDirectoryPath(const FString& FilePath)
253{
254 FString Directory, Filename, Extension;
255 FPaths::Split(FilePath, Directory, Filename, Extension);
256 return Directory;
257}
258
259namespace
260{
261 /// The baked rig of a map_logic entry.
262 ///
263 /// By name first: the placer stamps the entry's ActorName on the baked actor as a tag (and
264 /// as its editor label), so the binding is explicit. The nearest-actor search is only a
265 /// fallback for a level baked before that, and it is a guess: two rigs of one junction sit
266 /// within the 50 cm radius by construction, and a gantry that serves two approaches has two
267 /// signals at one location.
268 AActor* FindBakedActor(UWorld* World, const FString& ActorName, const FVector& SignalLocation,
269 const FString& SignalId, bool& bOutFoundByName)
270 {
271 bOutFoundByName = false;
272 TArray<AActor*> AllActors;
273 UGameplayStatics::GetAllActorsOfClass(World, AActor::StaticClass(), AllActors);
274
275 if (!ActorName.IsEmpty())
276 {
277 const FName Wanted(*ActorName);
278 for (AActor* Actor : AllActors)
279 {
280 if (Actor && Actor->ActorHasTag(Wanted))
281 {
282 bOutFoundByName = true;
283 return Actor;
284 }
285 }
286#if WITH_EDITOR
287 for (AActor* Actor : AllActors)
288 {
289 if (Actor && Actor->GetActorLabel() == ActorName)
290 {
291 bOutFoundByName = true;
292 return Actor;
293 }
294 }
295#endif
296 }
297
298 constexpr float MaxDistanceMatchSqr = 2500.0f;
299 AActor* ClosestActor = nullptr;
300 float MinDistance = MaxDistanceMatchSqr;
301 for (AActor* Actor : AllActors)
302 {
303 if (!Actor) continue;
304
305 // Never match a light that another signal already claimed. Two rigs on one junction sit
306 // within the 50 cm match radius, and adopting a claimed one would overwrite its SignId
307 // -- the second signal would silently steal the first one's identity.
308 if (ATrafficLightBase* Existing = Cast<ATrafficLightBase>(Actor))
309 {
310 UTrafficLightComponent* ExistingComp = Existing->GetTrafficLightComponent();
311 if (ExistingComp && !ExistingComp->GetSignId().IsEmpty()
312 && ExistingComp->GetSignId() != SignalId)
313 {
314 continue;
315 }
316 }
317
318 const float Dist = FVector::DistSquared(Actor->GetActorLocation(), SignalLocation);
319 if (Dist < MinDistance)
320 {
321 MinDistance = Dist;
322 ClosestActor = Actor;
323 }
324 }
325 return ClosestActor;
326 }
327
328 /// One ADigitalTwinsTrafficLight carrying copies of ``Meshes``, spawned deferred so that
329 /// BeginPlay -- and with it the lamp scan -- runs after the meshes are in place.
330 ADigitalTwinsTrafficLight* SpawnDigitalTwinsLight(UWorld* World, const FTransform& SpawnTransform,
331 const TArray<UStaticMeshComponent*>& Meshes,
332 const FString& SignalId)
333 {
334 ADigitalTwinsTrafficLight* NewTrafficLight = World->SpawnActorDeferred<ADigitalTwinsTrafficLight>(
335 ADigitalTwinsTrafficLight::StaticClass(), SpawnTransform, nullptr, nullptr,
336 ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
337 if (!NewTrafficLight)
338 {
339 UE_LOG(LogCarla, Error, TEXT("Failed to spawn ADigitalTwinsTrafficLight for '%s'"), *SignalId);
340 return nullptr;
341 }
342
343 for (UStaticMeshComponent* SourceMesh : Meshes)
344 {
345 if (!SourceMesh) continue;
346
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++)
351 {
352 NewMesh->SetMaterial(i, SourceMesh->GetMaterial(i));
353 }
354 NewMesh->RegisterComponent();
355 NewMesh->AttachToComponent(NewTrafficLight->GetRootComponent(),
356 FAttachmentTransformRules::KeepRelativeTransform);
357 }
358
359 UTrafficLightComponent* Comp = NewTrafficLight->GetTrafficLightComponent();
360 if (!Comp)
361 {
362 UE_LOG(LogCarla, Error, TEXT("ADigitalTwinsTrafficLight has no TrafficLightComponent"));
363 NewTrafficLight->Destroy();
364 return nullptr;
365 }
366 // SignId before FinishSpawning so BeginPlay's lamp census can name the signal.
367 Comp->SetSignId(SignalId);
368 NewTrafficLight->FinishSpawning(SpawnTransform);
369 return NewTrafficLight;
370 }
371
372 /// The mesh components of one baked rig, grouped by the OpenDRIVE signal their head names.
373 /// Anything that matches no head (Pole_%02d_Base / _Extensible / _Cap, and every backplate
374 /// or mesh a future rig adds) is structure and goes with the anchor signal.
375 void GroupMeshesBySignal(const TArray<UStaticMeshComponent*>& MeshComponents,
376 const FTrafficLightLogicData& Data,
377 TMap<FString, TArray<UStaticMeshComponent*>>& OutBySignal,
378 TArray<FString>& OutOrder)
379 {
380 TArray<UStaticMeshComponent*> Structural;
381 for (UStaticMeshComponent* Mesh : MeshComponents)
382 {
383 if (!Mesh) continue;
384 const FString Name = Mesh->GetName();
385 const FTrafficLightHead* Best = nullptr;
386 for (const FTrafficLightHead& Head : Data.Heads)
387 {
388 if (Name.StartsWith(Head.ComponentPrefix)
389 && (!Best || Head.ComponentPrefix.Len() > Best->ComponentPrefix.Len()))
390 {
391 Best = &Head;
392 }
393 }
394 if (!Best)
395 {
396 Structural.Add(Mesh);
397 continue;
398 }
399 const FString SignalId = Best->SignalID.IsEmpty() ? Data.SignalID : Best->SignalID;
400 if (!OutBySignal.Contains(SignalId))
401 {
402 OutOrder.Add(SignalId);
403 }
404 OutBySignal.FindOrAdd(SignalId).Add(Mesh);
405 }
406 if (Structural.Num() == 0)
407 {
408 return;
409 }
410 const FString Anchor = OutBySignal.Contains(Data.SignalID) ? Data.SignalID
411 : (OutOrder.Num() > 0 ? OutOrder[0] : Data.SignalID);
412 if (!OutBySignal.Contains(Anchor))
413 {
414 OutOrder.Add(Anchor);
415 }
416 OutBySignal.FindOrAdd(Anchor).Append(Structural);
417 }
418}
419
420void UMapLogicParser::ApplyLaneIdsFromMapLogic(const FString& XODRFilePath, ATrafficLightManager* TrafficLightManager)
421{
422 if (!TrafficLightManager)
423 {
424 UE_LOG(LogCarla, Warning, TEXT("MapLogicParser::ApplyLaneIdsFromMapLogic: TrafficLightManager is null"));
425 return;
426 }
427
428 const std::optional<carla::road::Map>& Map = TrafficLightManager->GetMap();
429 if (!Map.has_value())
430 {
431 UE_LOG(LogCarla, Warning, TEXT("MapLogicParser::ApplyLaneIdsFromMapLogic: Map is not available"));
432 return;
433 }
434
435 TArray<FTrafficLightLogicData> LogicData = LoadMapLogicFromJSON(XODRFilePath);
436 if (LogicData.Num() == 0)
437 {
438 UE_LOG(LogCarla, Log, TEXT("MapLogicParser::ApplyLaneIdsFromMapLogic: No map_logic.json found or no data"));
439 return;
440 }
441
442 const auto& Signals = Map->GetSignals();
443
444 int32 SuccessCount = 0;
445 int32 SplitRigs = 0;
446 int32 ByProximity = 0;
447
448 for (const FTrafficLightLogicData& Data : LogicData)
449 {
450 if (Data.SignalID.IsEmpty())
451 {
452 UE_LOG(LogCarla, Warning, TEXT("Skipping traffic light with empty SignalID (ActorName='%s')"), *Data.ActorName);
453 continue;
454 }
455
456 std::string SignalIdStr(TCHAR_TO_UTF8(*Data.SignalID));
457
458 if (Signals.find(SignalIdStr) == Signals.end())
459 {
460 UE_LOG(LogCarla, Warning, TEXT("Signal '%s' not found in OpenDRIVE"), *Data.SignalID);
461 continue;
462 }
463
464 const auto& Signal = Signals.at(SignalIdStr);
465 auto CarlaTransform = Signal->GetTransform();
466 FTransform UETransform(CarlaTransform);
467 FVector SignalLocation = UETransform.GetLocation();
468
469 bool bFoundByName = false;
470 AActor* ClosestActor = FindBakedActor(TrafficLightManager->GetWorld(), Data.ActorName,
471 SignalLocation, Data.SignalID, bFoundByName);
472 if (!ClosestActor)
473 {
474 UE_LOG(LogCarla, Error, TEXT("No baked actor named '%s' and none within 50cm of signal '%s'"),
475 *Data.ActorName, *Data.SignalID);
476 continue;
477 }
478 if (!bFoundByName)
479 {
480 ++ByProximity;
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."),
484 *Data.SignalID, *Data.ActorName);
485 }
486
487 // Timing of a light's own stage: prefer the controller the component actually joined
488 // (RegisterLightComponentFromOpenDRIVE derives it from the signal), so a rig split across
489 // two signals times both of their stages, not just the one TrafficLightGroupID names.
490 auto ApplyTiming = [&](UTrafficLightComponent* Comp)
491 {
492 UTrafficLightController* Controller = Comp ? Comp->GetController() : nullptr;
493 if (!Controller)
494 {
495 Controller = TrafficLightManager->GetController(Data.TrafficLightGroupID);
496 }
497 if (Controller)
498 {
499 Controller->SetRedTime(Data.Timing.RedDuration);
500 Controller->SetGreenTime(Data.Timing.GreenDuration);
501 Controller->SetYellowTime(Data.Timing.AmberDuration);
502 // Pedestrian heads have no amber lamp. Use an explicit two-state cycle,
503 // including the red crossing-clearance tail before vehicles can start.
504 const std::string Id(TCHAR_TO_UTF8(*Comp->GetSignId()));
505 if (Signals.at(Id)->GetType() == "1000002")
506 {
508 Walk.State = ETrafficLightState::Green;
509 Walk.Time = Data.Timing.GreenDuration;
510 FTrafficLightStage Clearance;
511 Clearance.State = ETrafficLightState::Red;
512 Clearance.Time = Data.Timing.RedDuration;
513 Controller->SetStates({Walk, Clearance});
514 }
515 }
516 else
517 {
518 UE_LOG(LogCarla, Error, TEXT("Failed to get controller '%s'"), *Data.TrafficLightGroupID);
519 }
520 };
521
522 ATrafficLightBase* TrafficLightActor = Cast<ATrafficLightBase>(ClosestActor);
523
524 if (TrafficLightActor)
525 {
526 UTrafficLightComponent* TrafficLightComp = TrafficLightActor->GetTrafficLightComponent();
527
528 if (TrafficLightComp->GetSignId() != Data.SignalID)
529 {
530 TrafficLightComp->SetSignId(Data.SignalID);
531 }
532
533 // An adopted light used to be given a SignId and nothing else, so it joined no
534 // ATrafficLightGroup and no controller ever ticked it. Register it like a spawned one.
535 // GetController() is set by UTrafficLightController::AddTrafficLight, so it doubles as
536 // the "already registered" guard -- registering twice would put the component into the
537 // controller's list twice.
538 if (!TrafficLightComp->GetController())
539 {
540 TrafficLightManager->RegisterLightComponentFromOpenDRIVE(TrafficLightComp);
541 }
542 ApplyTiming(TrafficLightComp);
543 if (Signals.at(std::string(TCHAR_TO_UTF8(*TrafficLightComp->GetSignId())))->GetType() != "1000002")
544 {
545 TrafficLightComp->InitializeSign(Map.value());
546 }
547 ++SuccessCount;
548 continue;
549 }
550
551 TArray<UStaticMeshComponent*> MeshComponents;
552 ClosestActor->GetComponents<UStaticMeshComponent>(MeshComponents);
553
554 if (MeshComponents.Num() == 0)
555 {
556 UE_LOG(LogCarla, Error, TEXT("DigitalTwins actor has no StaticMeshComponents"));
557 continue;
558 }
559
560 // One ADigitalTwinsTrafficLight per distinct signal the rig's heads name. With no "Heads"
561 // array (or with every head on the same signal) that is exactly one, i.e. the old
562 // behaviour; with an arrow head beside a through head it is two, each with its own
563 // ETrafficLightState, its own trigger boxes and its own stage.
564 TMap<FString, TArray<UStaticMeshComponent*>> BySignal;
565 TArray<FString> Order;
566 if (Data.Heads.Num() > 0)
567 {
568 GroupMeshesBySignal(MeshComponents, Data, BySignal, Order);
569 }
570 if (BySignal.Num() == 0)
571 {
572 Order.Add(Data.SignalID);
573 BySignal.Add(Data.SignalID, MeshComponents);
574 }
575 if (BySignal.Num() > 1)
576 {
577 ++SplitRigs;
578 UE_LOG(LogCarla, Log, TEXT("Rig '%s' carries %d signals: %s"), *Data.ActorName,
579 BySignal.Num(), *FString::Join(Order, TEXT(", ")));
580 }
581
582 const FTransform SpawnTransform(ClosestActor->GetActorRotation(), ClosestActor->GetActorLocation());
583 int32 SpawnedHere = 0;
584 for (const FString& HeadSignalId : Order)
585 {
586 const std::string HeadSignalIdStr(TCHAR_TO_UTF8(*HeadSignalId));
587 if (Signals.find(HeadSignalIdStr) == Signals.end())
588 {
589 UE_LOG(LogCarla, Error, TEXT("Head signal '%s' of rig '%s' is not in the OpenDRIVE"),
590 *HeadSignalId, *Data.ActorName);
591 continue;
592 }
593 ADigitalTwinsTrafficLight* NewTrafficLight = SpawnDigitalTwinsLight(
594 TrafficLightManager->GetWorld(), SpawnTransform, BySignal[HeadSignalId], HeadSignalId);
595 if (!NewTrafficLight)
596 {
597 continue;
598 }
599 UTrafficLightComponent* TrafficLightComp = NewTrafficLight->GetTrafficLightComponent();
600 TrafficLightManager->RegisterLightComponentFromOpenDRIVE(TrafficLightComp);
601 ApplyTiming(TrafficLightComp);
602 if (Signals.at(std::string(TCHAR_TO_UTF8(*TrafficLightComp->GetSignId())))->GetType() != "1000002")
603 {
604 TrafficLightComp->InitializeSign(Map.value());
605 }
606 ++SpawnedHere;
607 ++SuccessCount;
608 }
609 if (SpawnedHere > 0)
610 {
611 ClosestActor->Destroy();
612 }
613 }
614
615 if (SplitRigs > 0 || ByProximity > 0)
616 {
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);
620 }
621 UE_LOG(LogCarla, Log, TEXT("Applied lane IDs to %d traffic lights"), SuccessCount);
622}
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 SetStates(TArray< FTrafficLightStage > States)
carla::SharedPtr< cc::Actor > Actor
One head of a baked Traffic Light Tool rig, and the OpenDRIVE signal it shows.
TArray< int32 > LaneIds
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.
Represents a traffic light module with its lane assignments.
TArray< int32 > LaneIds
Defines a stage of a semaphor with a State and the time this state lasts.
ETrafficLightState State
Represents timing configuration for a traffic light from map_logic.json.