CARLA
Loading...
Searching...
No Matches
VehicleActorFactory.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 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
12
13#include <util/ue-header-guard-begin.h>
14#include "Json.h"
15#include "JsonUtilities.h"
16#include "Misc/FileHelper.h"
17#include "Misc/Paths.h"
18#include "Components/StaticMeshComponent.h"
19#include "Materials/MaterialInstanceDynamic.h"
20#include <util/ue-header-guard-end.h>
21
22TArray<FActorDefinition> AVehicleActorFactory::GetDefinitions()
23{
24 // Rebuilt from scratch every episode, otherwise entries accumulate.
25 Definitions.Reset();
26 VehiclesParams.Reset();
27 MineVehiclesParams.Reset();
28 LoadVehicleParametersArrayFromFile("VehicleParameters.json", VehiclesParams);
29 FString UniqueVehicleParameters = GetWorld()->GetMapName().Mid(GetWorld()->StreamingLevelsPrefix.Len()) + "/Vehicles.json";
32 // Content packs: <Pack>/Content/Config/{VehicleParameters,Vehicles,<Map>/Vehicles}.json,
33 // later packs override earlier entries with the same make.model.
34 for (const FString &CatalogName : {FString("VehicleParameters.json"), FString("Vehicles.json"), UniqueVehicleParameters})
35 {
36 for (const FString &PackFile : UCarlaContentPackManager::FindPackCatalogFiles(CatalogName))
37 {
38 TArray<FVehicleParameters> PackVehiclesParams;
39 LoadVehicleParametersArrayFromFile(PackFile, PackVehiclesParams);
41 [](const FVehicleParameters &V) { return V.Make + TEXT(".") + V.Model; });
42 }
43 }
45 return Definitions;
46}
47
49 const FTransform &SpawnAtTransform,
50 const FActorDescription &ActorDescription)
51{
52 FActorSpawnResult SpawnResult;
53 if(!IsValid(ActorDescription.Class))
54 {
55 UE_LOG(LogCarla, Error, TEXT("Actor Description Class is null."));
56 SpawnResult.Status = EActorSpawnResultStatus::InvalidDescription;
57 return SpawnResult;
58 }
59
60 AActor* SpawnedActor = GetWorld()->SpawnActor<AActor>(ActorDescription.Class, SpawnAtTransform);
61 SpawnResult.Actor = SpawnedActor;
62
63 if(SpawnedActor == nullptr)
64 {
65 SpawnResult.Status = EActorSpawnResultStatus::Collision;
66 return SpawnResult;
67 }
68
69 if( PostProcessVehicle(SpawnedActor, ActorDescription) ){
70 // Modular vehicles keep painted panels on static-mesh components. The
71 // legacy Blueprint paint path only visits the main skeletal mesh.
72 if (SpawnedActor->ActorHasTag(TEXT("Carla.StaticBodyworkPaint")) &&
73 ActorDescription.Variations.Contains(TEXT("color")))
74 {
76 TEXT("color"), ActorDescription.Variations, FColor::White));
77 TArray<UStaticMeshComponent*> Panels;
78 SpawnedActor->GetComponents(Panels);
79 for (UStaticMeshComponent* Panel : Panels)
80 {
81 const TArray<FName> Slots = Panel->GetMaterialSlotNames();
82 for (int32 Index = 0; Index < Slots.Num(); ++Index)
83 {
84 if (Slots[Index] == TEXT("Bodywork_Mat"))
85 {
86 if (UMaterialInstanceDynamic* Paint = Panel->CreateDynamicMaterialInstance(Index))
87 {
88 Paint->SetVectorParameterValue(TEXT("Base Color"), Color);
89 }
90 }
91 }
92 }
93 }
94 SpawnResult.Status = EActorSpawnResultStatus::Success;
95 return SpawnResult;
96 }
97 SpawnResult.Status = EActorSpawnResultStatus::UnknownError;
98 return SpawnResult;
99}
100
101void AVehicleActorFactory::ReleaseContentPack(const FString &MountPoint)
102{
103 Definitions.RemoveAll([&](const FActorDefinition &D) { return DefinitionReferencesPath(D, MountPoint); });
104 auto UnderPack = [&](const FVehicleParameters &V) {
105 return V.Class.ToSoftObjectPath().ToString().StartsWith(MountPoint);
106 };
107 VehiclesParams.RemoveAll(UnderPack);
108 MineVehiclesParams.RemoveAll(UnderPack);
109}
110
112{
113 TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
114
115 JsonObject->SetStringField(TEXT("Make"), VehicleParams.Make);
116 JsonObject->SetStringField(TEXT("Model"), VehicleParams.Model);
117 JsonObject->SetStringField(TEXT("Class"), VehicleParams.Class.ToSoftObjectPath().ToString());
118 JsonObject->SetNumberField(TEXT("NumberOfWheels"), VehicleParams.NumberOfWheels);
119 JsonObject->SetNumberField(TEXT("Generation"), VehicleParams.Generation);
120 JsonObject->SetStringField(TEXT("ObjectType"), VehicleParams.ObjectType);
121 JsonObject->SetStringField(TEXT("BaseType"), VehicleParams.BaseType);
122 JsonObject->SetStringField(TEXT("SpecialType"), VehicleParams.SpecialType);
123 JsonObject->SetBoolField(TEXT("HasDynamicDoors"), VehicleParams.HasDynamicDoors);
124 JsonObject->SetBoolField(TEXT("HasLights"), VehicleParams.HasLights);
125
126 // Serialize RecommendedColors
127 TArray<TSharedPtr<FJsonValue>> ColorsArray;
128 for (const FColor& Color : VehicleParams.RecommendedColors)
129 {
130 TSharedPtr<FJsonObject> ColorObject = MakeShareable(new FJsonObject);
131 ColorObject->SetNumberField(TEXT("R"), Color.R);
132 ColorObject->SetNumberField(TEXT("G"), Color.G);
133 ColorObject->SetNumberField(TEXT("B"), Color.B);
134 ColorObject->SetNumberField(TEXT("A"), Color.A);
135 ColorsArray.Add(MakeShareable(new FJsonValueObject(ColorObject)));
136 }
137 JsonObject->SetArrayField(TEXT("RecommendedColors"), ColorsArray);
138
139 // Serialize SupportedDrivers
140 TArray<TSharedPtr<FJsonValue>> DriversArray;
141 for (int32 DriverID : VehicleParams.SupportedDrivers)
142 {
143 DriversArray.Add(MakeShareable(new FJsonValueNumber(DriverID)));
144 }
145 JsonObject->SetArrayField(TEXT("SupportedDrivers"), DriversArray);
146
147 return JsonObject;
148}
149
150FString AVehicleActorFactory::FVehicleParametersArrayToJson(const TArray<FVehicleParameters>& VehicleParamsArray)
151{
152 TArray<TSharedPtr<FJsonValue>> JsonArray;
153
154 for (const FVehicleParameters& VehicleParams : VehicleParamsArray)
155 {
156 // Convert each FVehicleParameters to a JSON object
157 TSharedPtr<FJsonObject> JsonObject = FVehicleParametersToJsonObject(VehicleParams);
158 JsonArray.Add(MakeShareable(new FJsonValueObject(JsonObject)));
159 }
160
161 // Convert the array of JSON objects into a single JSON object
162 TSharedRef<FJsonObject> RootObject = MakeShareable(new FJsonObject);
163 RootObject->SetArrayField(TEXT("Vehicles"), JsonArray);
164
165 // Serialize the JSON object into an FString
166 FString OutputString;
167 TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
168 FJsonSerializer::Serialize(RootObject, Writer);
169
170 return OutputString;
171}
172
173void AVehicleActorFactory::SaveVehicleParametersArrayToFile(const TArray<FVehicleParameters>& VehicleParamsArray, const FString& FileName)
174{
175 FString FilePath = FPaths::ProjectContentDir() + TEXT("Carla/Config/") + FileName;
176 // Convert the array to an FString in JSON format
177 FString JsonContent = FVehicleParametersArrayToJson(VehicleParamsArray);
178
179 // Save the JSON to a file
180 if (FFileHelper::SaveStringToFile(JsonContent, *FilePath))
181 {
182 UE_LOG(LogCarla, Log, TEXT("JSON file successfully saved at: %s"), *FilePath);
183 }
184 else
185 {
186 UE_LOG(LogCarla, Error, TEXT("Failed to save JSON file at: %s"), *FilePath);
187 }
188}
189
190bool AVehicleActorFactory::JsonToFVehicleParameters(const TSharedPtr<FJsonObject> JsonObject, FVehicleParameters& OutVehicleParams)
191{
192 if (!JsonObject.IsValid()) return false;
193
194 JsonObject->TryGetStringField(TEXT("Make"), OutVehicleParams.Make);
195 JsonObject->TryGetStringField(TEXT("Model"), OutVehicleParams.Model);
196 // Build the soft class reference from the path string; the actual UClass
197 // load is deferred to MakeVehicleDefinition so the JSON parse itself does
198 // not block on disk I/O.
199 FString ClassPath;
200 JsonObject->TryGetStringField(TEXT("Class"), ClassPath);
201 OutVehicleParams.Class = TSoftClassPtr<ACarlaWheeledVehicle>(FSoftObjectPath(ClassPath));
202
203 JsonObject->TryGetNumberField(TEXT("NumberOfWheels"), OutVehicleParams.NumberOfWheels);
204 JsonObject->TryGetNumberField(TEXT("Generation"), OutVehicleParams.Generation);
205 JsonObject->TryGetStringField(TEXT("ObjectType"), OutVehicleParams.ObjectType);
206 JsonObject->TryGetStringField(TEXT("BaseType"), OutVehicleParams.BaseType);
207 JsonObject->TryGetStringField(TEXT("SpecialType"), OutVehicleParams.SpecialType);
208 JsonObject->TryGetBoolField(TEXT("HasDynamicDoors"), OutVehicleParams.HasDynamicDoors);
209 JsonObject->TryGetBoolField(TEXT("HasLights"), OutVehicleParams.HasLights);
210
211 // Parse RecommendedColors
212 const TArray<TSharedPtr<FJsonValue>>* ColorsArray;
213 if (JsonObject->TryGetArrayField(TEXT("RecommendedColors"), ColorsArray))
214 {
215 OutVehicleParams.RecommendedColors.Empty();
216 for (const TSharedPtr<FJsonValue>& ColorValue : *ColorsArray)
217 {
218 TSharedPtr<FJsonObject> ColorObject = ColorValue->AsObject();
219 if (ColorObject.IsValid())
220 {
221 FColor Color;
222 Color.R = ColorObject->GetIntegerField(TEXT("R"));
223 Color.G = ColorObject->GetIntegerField(TEXT("G"));
224 Color.B = ColorObject->GetIntegerField(TEXT("B"));
225 Color.A = ColorObject->GetIntegerField(TEXT("A"));
226 OutVehicleParams.RecommendedColors.Add(Color);
227 }
228 }
229 }
230
231 // Parse SupportedDrivers
232 const TArray<TSharedPtr<FJsonValue>>* DriversArray;
233 if (JsonObject->TryGetArrayField(TEXT("SupportedDrivers"), DriversArray))
234 {
235 OutVehicleParams.SupportedDrivers.Empty();
236 for (const TSharedPtr<FJsonValue>& DriverValue : *DriversArray)
237 {
238 OutVehicleParams.SupportedDrivers.Add(DriverValue->AsNumber());
239 }
240 }
241
242 return true;
243}
244
245bool AVehicleActorFactory::JsonToFVehicleParametersArray(const FString& JsonString, TArray<FVehicleParameters>& OutVehicleParamsArray)
246{
247 TSharedPtr<FJsonObject> RootObject;
248 TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
249
250 if (!FJsonSerializer::Deserialize(Reader, RootObject) || !RootObject.IsValid())
251 {
252 UE_LOG(LogCarla, Error, TEXT("Failed to parse JSON."));
253 return false;
254 }
255
256 // Get the "Vehicles" array from the JSON root object
257 const TArray<TSharedPtr<FJsonValue>>* VehiclesArray;
258 if (RootObject->TryGetArrayField(TEXT("Vehicles"), VehiclesArray))
259 {
260 OutVehicleParamsArray.Empty();
261 for (const TSharedPtr<FJsonValue>& VehicleValue : *VehiclesArray)
262 {
263 TSharedPtr<FJsonObject> VehicleObject = VehicleValue->AsObject();
264 if (VehicleObject.IsValid())
265 {
266 FVehicleParameters VehicleParams;
267 if (JsonToFVehicleParameters(VehicleObject, VehicleParams))
268 {
269 OutVehicleParamsArray.Add(VehicleParams);
270 }
271 }
272 }
273 }
274
275 return true;
276}
277
278void AVehicleActorFactory::LoadVehicleParametersArrayFromFile(const FString& FileName, TArray<FVehicleParameters>& OutVehicleParamsArray)
279{
280 FString JsonString;
281 // A relative name is resolved against the project catalog dir; an absolute
282 // path (content pack catalogs) is used as is.
283 FString FilePath = FPaths::IsRelative(FileName)
284 ? FPaths::ProjectContentDir() + TEXT("Carla/Config/") + FileName
285 : FileName;
286 FString JsonContent;
287
288 // Load the JSON file content into an FString
289 if (FFileHelper::LoadFileToString(JsonContent, *FilePath))
290 {
291 // Parse the JSON and populate the TArray<FVehicleParameters>
292 if (JsonToFVehicleParametersArray(JsonContent, OutVehicleParamsArray))
293 {
294 UE_LOG(LogCarla, Log, TEXT("Vehicle parameters loaded successfully from %s"), *FilePath);
295 }
296 else
297 {
298 UE_LOG(LogCarla, Error, TEXT("Failed to parse vehicle parameters from %s"), *FilePath);
299 }
300 }
301 else if(FileName == "VehicleParameters.json")
302 {
303 UE_LOG(LogCarla, Error, TEXT("Failed to load file: %s"), *FilePath);
304 }
305 else
306 {
307 UE_LOG(LogCarla, Warning, TEXT("Additional file not found: %s"), *FilePath);
308 }
309}
static bool IsValid(const ACarlaWheeledVehicle *Vehicle)
static bool DefinitionReferencesPath(const FActorDefinition &Definition, const FString &MountPoint)
True when the definition's class or any attribute/variation value points at content under MountPoint.
static void SaveVehicleParametersArrayToFile(const TArray< FVehicleParameters > &VehicleParamsArray, const FString &FileName)
static bool JsonToFVehicleParametersArray(const FString &JsonString, TArray< FVehicleParameters > &OutVehicleParamsArray)
static bool JsonToFVehicleParameters(const TSharedPtr< FJsonObject > JsonObject, FVehicleParameters &OutVehicleParams)
static TSharedPtr< FJsonObject > FVehicleParametersToJsonObject(const FVehicleParameters &VehicleParams)
virtual FActorSpawnResult SpawnActor(const FTransform &SpawnAtTransform, const FActorDescription &ActorDescription) override
Spawn an actor based on ActorDescription and Transform.
bool PostProcessVehicle(AActor *SpawnedActor, const FActorDescription &VehicleParams)
virtual TArray< FActorDefinition > GetDefinitions() override
Retrieve the definitions of the static mesh actor.
static FString FVehicleParametersArrayToJson(const TArray< FVehicleParameters > &VehicleParamsArray)
static void LoadVehicleParametersArrayFromFile(const FString &FileName, TArray< FVehicleParameters > &OutVehicleParamsArray)
TArray< FVehicleParameters > VehiclesParams
TArray< FVehicleParameters > MineVehiclesParams
TArray< FActorDefinition > Definitions
virtual void ReleaseContentPack(const FString &MountPoint) override
Drop every cached definition, parameter and loaded asset that refers to content under MountPoint ("/<...
static FColor RetrieveActorAttributeToColor(const FString &Id, const TMap< FString, FActorAttribute > &Attributes, const FColor &Default)
static void MakeVehicleDefinitions(const TArray< FVehicleParameters > &ParameterArray, TArray< FActorDefinition > &Definitions)
static void MergeCatalog(TArray< T > &Into, const TArray< T > &From, KeyFn Key)
Merge catalog entries: an entry whose key already exists replaces it in place (later files override b...
static TArray< FString > FindPackCatalogFiles(const FString &RelativeName)
<ContentDir>/Config/<RelativeName> of every mounted pack where the file exists, in mount order (later...
A definition of a Carla Actor with all the variation and attributes.
A description of a Carla Actor with all its variation.
TMap< FString, FActorAttribute > Variations
User selected variations of the actor.
TSubclassOf< AActor > Class
Class of the actor to be spawned.
Result of an actor spawn function.
EActorSpawnResultStatus Status
TArray< FColor > RecommendedColors
TArray< int32 > SupportedDrivers
List of IDs of the drivers (pedestrians) supported by this vehicle, leave empty if no driver is suppo...
FString BaseType
(OPTIONAL) Use for a base classification of vehicles (car / truck / van / bycicle / motorcycle).
FString ObjectType
(OPTIONAL) Use for custom classification of vehicles.
TSoftClassPtr< ACarlaWheeledVehicle > Class
FString SpecialType
(OPTIONAL) Use for a more in-depth classification of vehicles (electric / emergency / taxi ....