CARLA
Loading...
Searching...
No Matches
SensorSpawnerActor.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB). This work is licensed under the terms of the MIT license. For a copy, see <https://opensource.org/licenses/MIT>.
2
3
11
12#include <util/ue-header-guard-begin.h>
15#include "Sensor/Sensor.h"
16#include "Kismet/GameplayStatics.h"
17#include <util/ue-header-guard-end.h>
18
19DEFINE_LOG_CATEGORY_STATIC(LogSensorSpawnerActor, Verbose, All);
20
22{
23 PrimaryActorTick.bCanEverTick = true;
24 PrimaryActorTick.bStartWithTickEnabled = true;
25
26 SceneComp = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComp"));
27 RootComponent = SceneComp;
28 SaveImagePath = FPaths::ProjectSavedDir() + "SensorSpawnerCaptures/";
29
30 SensorClassToCapture = nullptr;
31}
32
34{
35 Super::BeginPlay();
36
37 // Wait for the CarlaEpisode initialisation. It is done on CarlaGameMode BeginPlay().
38 if(ACarlaGameModeBase* CarlaGameMode = Cast<ACarlaGameModeBase>(UGameplayStatics::GetGameMode(GetWorld())))
39 {
40 CarlaGameMode->OnEpisodeInitialisedDelegate.AddDynamic(this, &ASensorSpawnerActor::OnEpisodeInitialised);
41 }
42
43 SaveImagePath = SaveImagePath + FString::Printf(TEXT("%lld"), FDateTime::Now().ToUnixTimestamp());
45}
46
48{
49 if(IsValid(InitialisedEpisode))
50 {
51 CarlaEpisode = InitialisedEpisode;
52
53 // Spawn cameras with initial delay if set.
54 GetWorldTimerManager().SetTimer(InitialDelaySpawnTimerHandle, this, &ASensorSpawnerActor::SpawnSensors, InitialDelay);
55 }
56}
57
59{
60 // Check if we are doing a delayed spawn. If so, don't do nothing.
61 if(!SensorsToSpawnCopy.IsEmpty())
62 {
63 UE_LOG(LogSensorSpawnerActor, Warning, TEXT("Warning: ASensorSpawnerActor::SpawnSensors - Delayed spawn already in progress, wait until it ends"));
64 return;
65 }
66
67 if(DelayBetweenSpawns > 0.f)
68 {
71 return;
72 }
73
74 for(const auto& SensorStruct : SensorsToSpawn)
75 {
76 if(const FActorDefinition* SensorDefinition = GetActorDefinitionByClass(SensorStruct.SensorClass))
77 {
78 FActorDescription SensorDescription;
79 GenerateSensorActorDescription(SensorDefinition, SensorDescription);
80
81 for(int i = 0; i < SensorStruct.Amount; i++)
82 {
83 SpawnSensorActor(SensorDescription, SensorStruct.bAttachToActor);
84 }
85 }
86 }
87}
88
90{
91 bRecordingData = true;
92
93 for(ASensor* CurrentSensor : SpawnedSensorsArray)
94 {
95 if(CurrentSensor)
96 {
97 CurrentSensor->SetSavingDataToDisk(bRecordingData);
98 }
99 }
100}
101
103{
104 bRecordingData = false;
105
106 for(ASensor* CurrentSensor : SpawnedSensorsArray)
107 {
108 if(CurrentSensor)
109 {
110 CurrentSensor->SetSavingDataToDisk(bRecordingData);
111 }
112 }
113}
114
119
120const FActorDefinition* ASensorSpawnerActor::GetActorDefinitionByClass(const TSubclassOf<AActor> ActorClass) const
121{
122 if(!ActorClass || !IsValid(CarlaEpisode))
123 {
124 return nullptr;
125 }
126
127 const TArray<FActorDefinition>& ActorDefinitions = CarlaEpisode->GetActorDefinitions();
128 // Checks that the class is exactly the same. If we want to allow also child classes use: ActorDef.Class->IsChildOf(ActorClass)
129 const FActorDefinition* ActorDefinition = ActorDefinitions.FindByPredicate([&](const FActorDefinition& ActorDef){ return ActorDef.Class == ActorClass; });
130
131 return ActorDefinition;
132}
133
134void ASensorSpawnerActor::SpawnSensorActor(const FActorDescription& SensorDescription, bool bAttachToActor)
135{
137 {
138 FTransform Transform;
139 GetRandomTransform(Transform);
140
141 const TPair<EActorSpawnResultStatus, FCarlaActor*> SpawnResult = CarlaEpisode->SpawnActorWithInfo(Transform, SensorDescription);
142
143 // After spawn handle other logic if spawn was success.
144 if(SpawnResult.Value)
145 {
146 AddSensorToSaveDataArray(SpawnResult.Value->GetActor());
147
148 if(bAttachToActor)
149 {
150 AttachSensorToActor(SpawnResult.Value->GetActor());
151 }
152 }
153 }
154}
155
157{
158 SensorDescription.UId = Definition->UId;
159 SensorDescription.Id = Definition->Id;
160 SensorDescription.Class = Definition->Class;
161 SensorDescription.Variations.Reserve(Definition->Variations.Num());
162
163 FActorAttribute CreatedAttribute;
164 for(const FActorVariation& Variation : Definition->Variations)
165 {
166 if(Variation.RecommendedValues.IsValidIndex(0))
167 {
168 CreatedAttribute.Id = Variation.Id;
169 CreatedAttribute.Type = Variation.Type;
170 CreatedAttribute.Value = Variation.RecommendedValues[0];
171 SensorDescription.Variations.Emplace(CreatedAttribute.Id, CreatedAttribute);
172 }
173 }
174}
175
176void ASensorSpawnerActor::GetRandomTransform(FTransform &Transform) const
177{
178 Transform = FTransform::Identity;
179 const float PosX = FMath::FRandRange(MinSpawnLocation.X, MaxSpawnLocation.X);
180 const float PosY = FMath::FRandRange(MinSpawnLocation.Y, MaxSpawnLocation.Y);
181 const float PosZ = FMath::FRandRange(MinSpawnLocation.Z, MaxSpawnLocation.Z);
182 Transform.SetLocation(FVector(PosX, PosY, PosZ));
183}
184
186{
187 if(SensorsToSpawnCopy.IsEmpty())
188 {
189 GetWorldTimerManager().ClearTimer(SpawnSensorsDelayedTimerHandle);
190 return;
191 }
192
193 if(const FActorDefinition* SensorDefinition = GetActorDefinitionByClass(SensorsToSpawnCopy[0].SensorClass))
194 {
195 FActorDescription SensorDescription;
196 GenerateSensorActorDescription(SensorDefinition, SensorDescription);
197 SpawnSensorActor(SensorDescription, SensorsToSpawnCopy[0].bAttachToActor);
198 }
199
200 SensorsToSpawnCopy[0].Amount--;
201
202 if(SensorsToSpawnCopy[0].Amount <= 0)
203 {
204 SensorsToSpawnCopy.RemoveAt(0);
205 }
206}
207
209{
210 if(ASensor* CaptureSensor = Cast<ASensor>(Actor))
211 {
212 if(SensorClassToCapture == CaptureSensor->GetClass() || SensorClassToCapture == nullptr)
213 {
214 SpawnedSensorsArray.Add(CaptureSensor);
215 CaptureSensor->SetSavingDataToDisk(bRecordingData);
216 }
217 }
218}
219
220void ASensorSpawnerActor::Tick(float DeltaSeconds)
221{
222 TRACE_CPUPROFILER_EVENT_SCOPE(ASensorSpawnerActor::Tick);
223 Super::Tick(DeltaSeconds);
224
226 {
227 SaveSensorData(DeltaSeconds);
228 }
230 {
232 }
233}
234
236{
237 const FString FrameNumber = FString::Printf(TEXT("%lld"), FCarlaEngine::GetFrameCounter());
238 for(ASensor* CurrentSensor : SpawnedSensorsArray)
239 {
240 if(ASceneCaptureSensor* CaptureSensor = Cast<ASceneCaptureSensor>(CurrentSensor))
241 {
242 const FString FinalPath = FPaths::Combine(SaveImagePath, CaptureSensor->GetName(), FString::Printf(TEXT("%lld"), FDateTime::Now().ToUnixTimestamp()) + "-Frame_" + FrameNumber + ".png");
243 CaptureSensor->SaveCaptureToDisk(FinalPath);
244 continue;
245 }
246
247 if(const AInertialMeasurementUnit* IMUSensor = Cast<AInertialMeasurementUnit>(CurrentSensor))
248 {
249 const FVector Accelerometer = IMUSensor->GetAccelerometerValue().ToFVector();
250 const FVector Gyroscope = IMUSensor->GetGyroscopeValue().ToFVector();
251 float Compass = IMUSensor->GetCompassValue();
252 Compass = FMath::RadiansToDegrees(Compass);
253
254 const FString FilePath = FPaths::Combine(SaveImagePath, IMUSensor->GetName(), IMUSensor->GetName() + ".json");
255 UJsonFileManagerLibrary::SaveIMUDataToJson(FilePath, Accelerometer, Gyroscope, Compass, FrameNumber);
256 continue;
257 }
258
259 if(const AGnssSensor* GnssSensor = Cast<AGnssSensor>(CurrentSensor))
260 {
261 const FString FilePath = FPaths::Combine(SaveImagePath, GnssSensor->GetName(), GnssSensor->GetName() + ".json");
262 UJsonFileManagerLibrary::SaveGnssDataToJson(FilePath, GnssSensor->GetAltitudeValue(), GnssSensor->GetLatitudeValue(), GnssSensor->GetLongitudeValue(), FrameNumber);
263 }
264
265 if(const ARayCastLidar* LidarSensor = Cast<ARayCastLidar>(CurrentSensor))
266 {
267 const FString FilePath = FPaths::Combine(SaveImagePath, LidarSensor->GetName(), FString::Printf(TEXT("%lld"), FDateTime::Now().ToUnixTimestamp()) + "-Frame_" + FrameNumber + ".ply");
268 UJsonFileManagerLibrary::SaveLidarDataToPly(FilePath, LidarSensor->GetTestPointCloud(), 4);
269 }
270
271 if(const ARadar* RadarSensor = Cast<ARadar>(CurrentSensor))
272 {
273 const FString FilePath = FPaths::Combine(SaveImagePath, RadarSensor->GetName(), FString::Printf(TEXT("%lld"), FDateTime::Now().ToUnixTimestamp()) + "-Frame_" + FrameNumber + ".json");
274 UJsonFileManagerLibrary::SaveRadarDataToJson(FilePath, RadarSensor->GetRadarData(), FrameNumber);
275 }
276 }
277}
278
280{
281 const UWorld* World = GetWorld();
282 if(SensorActor && GetWorld() && AttachActorClass)
283 {
284 AActor* SensorAttachParent = UGameplayStatics::GetActorOfClass(World, AttachActorClass);
285 if(SensorAttachParent)
286 {
287 SensorActor->AttachToActor(SensorAttachParent, FAttachmentTransformRules::KeepWorldTransform);
288 SensorActor->SetOwner(SensorAttachParent);
289 SensorActor->SetActorRelativeTransform(FTransform::Identity);
290 }
291 }
292}
293
295{
296 float velocity_range = VelocityRange; // m/s
297 for(const ASensor* CurrentSensor : SpawnedSensorsArray)
298 {
299 if(const ARadar* RadarSensor = Cast<ARadar>(CurrentSensor); CurrentSensor)
300 {
301 FRotator CurrentRot = RadarSensor->GetActorRotation();
302 for( auto& detect : RadarSensor->GetRadarData().GetDetections() )
303 {
304 float Azi = FMath::RadiansToDegrees(detect.azimuth);
305 float Alt = FMath::RadiansToDegrees(detect.altitude);
306
307 FVector FWVector( (detect.depth - 0.25f) * 100.0f, 0.f, 0.f);
308 FTransform SensorTransform(FRotator(CurrentRot.Pitch + Alt,
309 CurrentRot.Yaw + Azi,
310 CurrentRot.Roll));
311
312 FWVector = SensorTransform.GetRotation().RotateVector(FWVector);
313
314 float Norm_velocity = detect.velocity / velocity_range;
315 FColor ColorPoint;
316 ColorPoint.R = FMath::Clamp(Norm_velocity, 0.0f, 1.0f) * 255;
317 ColorPoint.G = FMath::Clamp( 1.0f - abs(Norm_velocity), 0.0f, 1.0f) * 255;
318 ColorPoint.B = FMath::Clamp(abs(- 1.0f - Norm_velocity), 0.0f, 1.0f) * 255;
319
320 DrawDebugPoint(GetWorld(), RadarSensor->GetActorLocation() + FWVector , PointSize, ColorPoint, false, 0.06);
321 }
322 }
323 }
324}
DEFINE_LOG_CATEGORY_STATIC(LogSensorSpawnerActor, Verbose, All)
static bool IsValid(const ACarlaWheeledVehicle *Vehicle)
Base class for the CARLA Game Mode.
Gnss sensor representation The actual position calculation is done one server side.
Definition GnssSensor.h:24
A ray-cast based Radar sensor.
Definition Radar.h:21
A ray-cast based Lidar sensor.
Base class for sensors using a USceneCaptureComponent2D for rendering the scene.
void GetRandomTransform(FTransform &Transform) const
TObjectPtr< USceneComponent > SceneComp
virtual void BeginPlay() override
void AttachSensorToActor(AActor *SensorActor)
FTimerHandle SpawnSensorsDelayedTimerHandle
FTimerHandle InitialDelaySpawnTimerHandle
TArray< TObjectPtr< ASensor > > SpawnedSensorsArray
TSubclassOf< class ASensor > SensorClassToCapture
void SpawnSensorActor(const FActorDescription &SensorDescription, bool bAttachToActor)
void OnEpisodeInitialised(UCarlaEpisode *InitialisedEpisode)
void SaveSensorData(float DeltaSeconds)
TSubclassOf< AActor > AttachActorClass
TObjectPtr< UCarlaEpisode > CarlaEpisode
const FActorDefinition * GetActorDefinitionByClass(const TSubclassOf< AActor > ActorClass) const
TArray< FSensorTuple > SensorsToSpawn
virtual void Tick(float DeltaSeconds) override
TArray< FSensorTuple > SensorsToSpawnCopy
void AddSensorToSaveDataArray(AActor *Actor)
void GenerateSensorActorDescription(const FActorDefinition *Definition, FActorDescription &SensorDescription) const
static uint64_t GetFrameCounter()
Definition CarlaEngine.h:73
A simulation episode.
static bool SaveGnssDataToJson(const FString &JsonFilePath, double Altitude, double Latitude, double Longitude, const FString &FrameNumber)
static bool SaveLidarDataToPly(const FString &FilePath, const TArray< float > &PointArray, int ArrayElementSize)
static bool SaveRadarDataToJson(const FString &JsonFilePath, const FRadarData &Rays, const FString &FrameNumber)
static bool SaveIMUDataToJson(const FString &JsonFilePath, const FVector &Accelerometer, const FVector &Gyroscope, float Compass, const FString &FrameNumber)
An actor attribute, may be an intrinsic (non-modifiable) attribute of the actor or an user-defined ac...
A definition of a Carla Actor with all the variation and attributes.
uint32 UId
Uniquely identifies the definition (no need to fill it).
TArray< FActorVariation > Variations
Variations represent variables the user can modify to generate variations of the actor.
TSubclassOf< AActor > Class
Class of the actor to be spawned (Optional).
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.
uint32 UId
UId of the definition in which this description was based.