CARLA
Loading...
Searching...
No Matches
PropActorFactory.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
7
14
15#include <util/ue-header-guard-begin.h>
16#include "Json.h"
17#include "JsonUtilities.h"
18#include "Misc/FileHelper.h"
19#include "Misc/Paths.h"
20#include "Engine/StaticMeshActor.h"
21#include "Components/StaticMeshComponent.h"
22#include <util/ue-header-guard-end.h>
23
24TArray<FActorDefinition> APropActorFactory::GetDefinitions()
25{
26 // Rebuilt from scratch every episode, otherwise entries accumulate.
27 Definitions.Reset();
28 PropsParams.Reset();
29 LoadPropParametersArrayFromFile("PropParameters.json", PropsParams);
30 // Content packs: <Pack>/Content/Config/PropParameters.json, later packs
31 // override earlier entries with the same Name.
32 for (const FString &PackFile : UCarlaContentPackManager::FindPackCatalogFiles("PropParameters.json"))
33 {
34 TArray<FPropParameters> PackPropsParams;
35 LoadPropParametersArrayFromFile(PackFile, PackPropsParams);
37 [](const FPropParameters &P) { return P.Name; });
38 }
39 // Content packs may also ship the registry format
40 // (<Pack>/Content/Config/<Pack>.Package.json: {"props": [{name, path, size}]}).
41 {
42 TArray<FPropParameters> PackRegistryProps;
46 [](const FPropParameters &P) { return P.Name; });
47 }
48
49 MeshCacheByPath.Reset();
50 for (const FPropParameters& Params : PropsParams)
51 {
52 if (Params.Mesh.IsNull())
53 {
54 continue;
55 }
56 const FString MeshPath = Params.Mesh.ToSoftObjectPath().ToString();
57 UStaticMesh* Mesh = Params.Mesh.LoadSynchronous();
58 if (Mesh != nullptr)
59 {
60 MeshCacheByPath.Add(MeshPath, Mesh);
61 }
62 }
63
65 return Definitions;
66}
67
69 const FTransform &SpawnAtTransform,
70 const FActorDescription &ActorDescription)
71{
72 FActorSpawnResult SpawnResult;
73 if(!IsValid(ActorDescription.Class))
74 {
75 UE_LOG(LogCarla, Error, TEXT("Actor Description Class is null."));
76 SpawnResult.Status = EActorSpawnResultStatus::InvalidDescription;
77 return SpawnResult;
78 }
79
80 FActorSpawnParameters SpawnParameters;
81 SpawnParameters.SpawnCollisionHandlingOverride =
82 ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
83
84 AStaticMeshActor* StaticMeshActor = GetWorld()->SpawnActor<AStaticMeshActor>(
85 ActorDescription.Class, SpawnAtTransform, SpawnParameters);
86
87 SpawnResult.Actor = StaticMeshActor;
88
89 if(StaticMeshActor == nullptr)
90 {
91 SpawnResult.Status = EActorSpawnResultStatus::Collision;
92 return SpawnResult;
93 }
94
95 UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(
96 StaticMeshActor->GetRootComponent());
97
98 if (!StaticMeshComponent)
99 {
100 UE_LOG(LogCarla, Error, TEXT("Prop spawn failed: StaticMeshComponent is null for actor %s"),
101 *ActorDescription.Id);
102 StaticMeshActor->Destroy();
103 SpawnResult.Status = EActorSpawnResultStatus::UnknownError;
104 return SpawnResult;
105 }
106
107 if (!ActorDescription.Variations.Contains("mesh_path"))
108 {
109 UE_LOG(LogCarla, Error, TEXT("Prop spawn failed: No mesh_path variation found for actor %s"),
110 *ActorDescription.Id);
111 StaticMeshActor->Destroy();
112 SpawnResult.Status = EActorSpawnResultStatus::InvalidDescription;
113 return SpawnResult;
114 }
115
117 ActorDescription.Variations["mesh_path"], "");
118
119 if (MeshPath.IsEmpty())
120 {
121 UE_LOG(LogCarla, Error, TEXT("Prop spawn failed: mesh_path is empty for actor %s"),
122 *ActorDescription.Id);
123 StaticMeshActor->Destroy();
124 SpawnResult.Status = EActorSpawnResultStatus::InvalidDescription;
125 return SpawnResult;
126 }
127
128 UStaticMesh* Mesh = nullptr;
129 if (TObjectPtr<UStaticMesh>* Cached = MeshCacheByPath.Find(MeshPath))
130 {
131 Mesh = Cached->Get();
132 }
133 else
134 {
135 UE_LOG(LogCarla, Warning,
136 TEXT("PropActorFactory: mesh cache miss for '%s' (actor %s); falling back to synchronous LoadObject."),
137 *MeshPath, *ActorDescription.Id);
138 Mesh = LoadObject<UStaticMesh>(nullptr, *MeshPath);
139 if (Mesh != nullptr)
140 {
141 MeshCacheByPath.Add(MeshPath, Mesh);
142 }
143 }
144
145 if (Mesh == nullptr)
146 {
147 UE_LOG(LogCarla, Error, TEXT("Prop spawn failed: Failed to load mesh '%s' for actor %s"),
148 *MeshPath, *ActorDescription.Id);
149 StaticMeshActor->Destroy();
150 SpawnResult.Status = EActorSpawnResultStatus::UnknownError;
151 return SpawnResult;
152 }
153
154 StaticMeshComponent->SetMobility(EComponentMobility::Movable);
155 if (!StaticMeshComponent->SetStaticMesh(Mesh))
156 {
157 UE_LOG(LogCarla, Error, TEXT("Prop spawn failed: Failed to set mesh '%s' for actor %s"),
158 *MeshPath, *ActorDescription.Id);
159 StaticMeshActor->Destroy();
160 SpawnResult.Status = EActorSpawnResultStatus::UnknownError;
161 return SpawnResult;
162 }
163
164 PostProcessProp(StaticMeshActor, ActorDescription);
165 SpawnResult.Status = EActorSpawnResultStatus::Success;
166 return SpawnResult;
167}
168
169void APropActorFactory::ReleaseContentPack(const FString &MountPoint)
170{
171 Definitions.RemoveAll([&](const FActorDefinition &D) { return DefinitionReferencesPath(D, MountPoint); });
172 PropsParams.RemoveAll([&](const FPropParameters &P) {
173 return P.Mesh.ToSoftObjectPath().ToString().StartsWith(MountPoint);
174 });
175 for (auto It = MeshCacheByPath.CreateIterator(); It; ++It)
176 {
177 if (It.Key().StartsWith(MountPoint))
178 {
179 It.RemoveCurrent();
180 }
181 }
182}
183
184TSharedPtr<FJsonObject> APropActorFactory::FPropParametersToJsonObject(const FPropParameters& PropParams)
185{
186 TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
187
188 JsonObject->SetStringField(TEXT("Name"), PropParams.Name);
189 JsonObject->SetStringField(TEXT("Mesh"), PropParams.Mesh.ToSoftObjectPath().ToString());
190
191 FString PropSizeString;
192 switch(PropParams.Size)
193 {
194 case EPropSize::Tiny:
195 PropSizeString = FString("Tiny");
196 break;
197 case EPropSize::Small:
198 PropSizeString = FString("Small");
199 break;
200 case EPropSize::Medium:
201 PropSizeString = FString("Medium");
202 break;
203 case EPropSize::Big:
204 PropSizeString = FString("Big");
205 break;
206 case EPropSize::Huge:
207 PropSizeString = FString("Huge");
208 break;
209 default:
210 PropSizeString = FString("INVALID");
211 break;
212 }
213 JsonObject->SetStringField(TEXT("Size"), PropSizeString);
214
215 JsonObject->SetStringField(TEXT("BaseType"), PropParams.BaseType);
216 JsonObject->SetStringField(TEXT("SubType"), PropParams.SubType);
217 return JsonObject;
218}
219
220FString APropActorFactory::FPropParametersArrayToJson(const TArray<FPropParameters>& PropParamsArray)
221{
222 TArray<TSharedPtr<FJsonValue>> JsonArray;
223
224 for (const FPropParameters& PropParams : PropParamsArray)
225 {
226 // Convert each FPropParameters to a JSON object
227 TSharedPtr<FJsonObject> JsonObject = FPropParametersToJsonObject(PropParams);
228 JsonArray.Add(MakeShareable(new FJsonValueObject(JsonObject)));
229 }
230
231 // Convert the array of JSON objects into a single JSON object
232 TSharedRef<FJsonObject> RootObject = MakeShareable(new FJsonObject);
233 RootObject->SetArrayField(TEXT("Props"), JsonArray);
234
235 // Serialize the JSON object into an FString
236 FString OutputString;
237 TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
238 FJsonSerializer::Serialize(RootObject, Writer);
239
240 return OutputString;
241}
242
243void APropActorFactory::SavePropParametersArrayToFile(const TArray<FPropParameters>& PropParametersArray, const FString& FileName)
244{
245 FString FilePath = FPaths::ProjectContentDir() + TEXT("Carla/Config/") + FileName;
246 // Convert the array to an FString in JSON format
247 FString JsonContent = FPropParametersArrayToJson(PropParametersArray);
248
249 // Save the JSON to a file
250 if (FFileHelper::SaveStringToFile(JsonContent, *FilePath))
251 {
252 UE_LOG(LogCarla, Log, TEXT("JSON file successfully saved at: %s"), *FilePath);
253 }
254 else
255 {
256 UE_LOG(LogCarla, Error, TEXT("Failed to save JSON file at: %s"), *FilePath);
257 }
258}
259
260bool APropActorFactory::JsonToFPropParameters(const TSharedPtr<FJsonObject> JsonObject, FPropParameters& OutPropParams)
261{
262 if (!JsonObject.IsValid()) return false;
263
264 JsonObject->TryGetStringField(TEXT("Name"), OutPropParams.Name);
265
266 // Build the soft reference from the path string; the actual UStaticMesh
267 // load is deferred to MakePropDefinition / the factory cache seed so the
268 // JSON parse itself does not block on disk I/O.
269 FString MeshPath;
270 JsonObject->TryGetStringField(TEXT("Mesh"), MeshPath);
271 OutPropParams.Mesh = TSoftObjectPtr<UStaticMesh>(FSoftObjectPath(MeshPath));
272
273 FString PropSizeString;
274 JsonObject->TryGetStringField(TEXT("Size"), PropSizeString);
275
276 // Optional fields: catalogs written before the prop types keep the struct
277 // default (empty) when absent.
278 JsonObject->TryGetStringField(TEXT("BaseType"), OutPropParams.BaseType);
279 JsonObject->TryGetStringField(TEXT("SubType"), OutPropParams.SubType);
280
281 if(PropSizeString == FString("Tiny")){
282 OutPropParams.Size = EPropSize::Tiny;
283 }
284 else if(PropSizeString == FString("Small"))
285 {
286 OutPropParams.Size = EPropSize::Small;
287 }
288 else if(PropSizeString == FString("Medium"))
289 {
290 OutPropParams.Size = EPropSize::Medium;
291 }
292 else if(PropSizeString == FString("Big"))
293 {
294 OutPropParams.Size = EPropSize::Big;
295 }
296 else if(PropSizeString == FString("Huge"))
297 {
298 OutPropParams.Size = EPropSize::Huge;
299 }
300 else
301 {
302 OutPropParams.Size = EPropSize::INVALID;
303 }
304
305 return true;
306}
307
308bool APropActorFactory::JsonToFPropParametersArray(const FString& JsonString, TArray<FPropParameters>& OutPropParamsArray)
309{
310 TSharedPtr<FJsonObject> RootObject;
311 TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
312
313 if (!FJsonSerializer::Deserialize(Reader, RootObject) || !RootObject.IsValid())
314 {
315 UE_LOG(LogCarla, Error, TEXT("Failed to parse JSON."));
316 return false;
317 }
318
319 // Get the "Props" array from the JSON root object
320 const TArray<TSharedPtr<FJsonValue>>* PropsArray;
321 if (RootObject->TryGetArrayField(TEXT("Props"), PropsArray))
322 {
323 OutPropParamsArray.Empty();
324 for (const TSharedPtr<FJsonValue>& PropValue : *PropsArray)
325 {
326 TSharedPtr<FJsonObject> PropObject = PropValue->AsObject();
327 if (PropObject.IsValid())
328 {
329 FPropParameters PropParams;
330 if (JsonToFPropParameters(PropObject, PropParams))
331 {
332 OutPropParamsArray.Add(PropParams);
333 }
334 }
335 }
336 }
337
338 return true;
339}
340
341void APropActorFactory::LoadPropParametersArrayFromFile(const FString& FileName, TArray<FPropParameters>& OutPropParamsArray)
342{
343 FString JsonString;
344 // A relative name is resolved against the project catalog dir; an absolute
345 // path (content pack catalogs) is used as is.
346 FString FilePath = FPaths::IsRelative(FileName)
347 ? FPaths::ProjectContentDir() + TEXT("Carla/Config/") + FileName
348 : FileName;
349 FString JsonContent;
350
351 // Load the JSON file content into an FString
352 if (FFileHelper::LoadFileToString(JsonContent, *FilePath))
353 {
354 // Parse the JSON and populate the TArray<FPropParameters>
355 if (JsonToFPropParametersArray(JsonContent, OutPropParamsArray))
356 {
357 UE_LOG(LogCarla, Log, TEXT("Prop parameters loaded successfully from %s"), *FilePath);
358 }
359 else
360 {
361 UE_LOG(LogCarla, Error, TEXT("Failed to parse Prop parameters from %s"), *FilePath);
362 }
363 }
364 else
365 {
366 UE_LOG(LogCarla, Error, TEXT("Failed to load file: %s"), *FilePath);
367 }
368}
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.
virtual void ReleaseContentPack(const FString &MountPoint) override
Drop every cached definition, parameter and loaded asset that refers to content under MountPoint ("/<...
static FString FPropParametersArrayToJson(const TArray< FPropParameters > &PropParamsArray)
static void SavePropParametersArrayToFile(const TArray< FPropParameters > &PropParamsArray, const FString &FileName)
TArray< FActorDefinition > Definitions
TMap< FString, TObjectPtr< UStaticMesh > > MeshCacheByPath
Mesh cache keyed by mesh path (same string stored in ActorDescription.Variations["mesh_path"]).
bool PostProcessProp(AActor *SpawnedActor, const FActorDescription &PropParams)
virtual TArray< FActorDefinition > GetDefinitions() override
Retrieve the definitions of the static mesh actor.
TArray< FPropParameters > PropsParams
static bool JsonToFPropParameters(const TSharedPtr< FJsonObject > JsonObject, FPropParameters &OutPropParams)
static TSharedPtr< FJsonObject > FPropParametersToJsonObject(const FPropParameters &PropParams)
static bool JsonToFPropParametersArray(const FString &JsonString, TArray< FPropParameters > &OutPropParamsArray)
virtual FActorSpawnResult SpawnActor(const FTransform &SpawnAtTransform, const FActorDescription &ActorDescription) override
Spawn an actor based on ActorDescription and Transform.
static void LoadPropParametersArrayFromFile(const FString &FileName, TArray< FPropParameters > &OutPropParamsArray)
static void MakePropDefinitions(const TArray< FPropParameters > &ParameterArray, TArray< FActorDefinition > &Definitions)
static FString ActorAttributeToString(const FActorAttribute &ActorAttribute, const FString &Default)
static TArray< FString > FindContentPackPropFiles()
Every <Content>/Config/‍**/*.Package.json of every mounted content pack.
static void LoadPropDefinitionsFromFiles(const TArray< FString > &PropFileNames, TArray< FPropParameters > &PropParametersArray)
Parse the given *.Package.json files in order (later files override earlier entries with the same nam...
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
TSoftObjectPtr< UStaticMesh > Mesh