Agrarsense
Tagger.cpp
Go to the documentation of this file.
1// Copyright (c) 2023 FrostBit Software Lab at the Lapland University of Applied Sciences
2//
3// This work is licensed under the terms of the MIT license.
4// For a copy, see <https://opensource.org/licenses/MIT>.
5
6#include "Tagger.h"
12
14#include "GeoReferencingSystem.h"
15#include "AssetRegistry/AssetRegistryModule.h"
16#include "Components/SkeletalMeshComponent.h"
17#include "Components/StaticMeshComponent.h"
18#include "Engine/SkeletalMesh.h"
19#include "Engine/StaticMesh.h"
20#include "Landscape.h"
21#include "LandscapeComponent.h"
22#include "Engine/World.h"
23#include "Kismet/GameplayStatics.h"
24#include "TimerManager.h"
25
27{
28 PrimaryActorTick.bCanEverTick = false;
29}
30
32{
33 Super::BeginPlay();
34
36 {
37 return;
38 }
39
40 UWorld* World = GetWorld();
41
42 // Create TMap<FString, ELabels>
43 LabelMap = UEnumUtilities::CreateStringEnumMap<ELabels>("/Script/Agrarsense.ELabels");
44
45 ActorSpawnedDelegateHandle = World->AddOnActorSpawnedHandler(FOnActorSpawned::FDelegate::CreateUObject(this, &ATagger::OnActorSpawned));
46
47 // Tag all existing Actors in the World after small delay
48 FTimerHandle Handle;
49 World->GetTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda([this]
50 {
52 }), 0.200f, false);
53}
54
55void ATagger::EndPlay(const EEndPlayReason::Type EndPlayReason)
56{
57 Super::EndPlay(EndPlayReason);
58
59 TaggedActors.Empty();
61}
62
64{
65 TArray<AActor*> Actors;
66 UGameplayStatics::GetAllActorsOfClass(GetWorld(), AActor::StaticClass(), Actors);
67
68 TaggedActors.Reserve(Actors.Num());
69
70 // Sort actors based on distance to the origin (0,0,0)
71 // This way we can ensure that the actors are tagged in a consistent order each run
72 const FVector Origin(0.0f, 0.0f, 0.0f);
73 Actors.Sort([&](const AActor& A, const AActor& B)
74 {
75 return FVector::DistSquared(A.GetActorLocation(), Origin) < FVector::DistSquared(B.GetActorLocation(), Origin);
76 });
77
78 for (AActor* Actor : Actors)
79 {
80 if (Actor)
81 {
82 TagActor(*Actor);
83 }
84 }
85}
86
87void ATagger::OnActorSpawned(AActor* Actor)
88{
89 if (Actor)
90 {
91 TagActor(*Actor);
92 }
93}
94
95template <typename T>
96static auto CastEnum(T label)
97{
98 return static_cast<typename std::underlying_type<T>::type>(label);
99}
100
101void ATagger::TagActor(AActor& Actor)
102{
103//#ifndef InstanceSegmentationPass_EXISTS
104// AInstancedActor* InstancedActor = Cast<AInstancedActor>(&Actor);
105// if (InstancedActor)
106// {
107// if (InstancedActor->IsCustomDepthSetup())
108// {
109// // Custom depth has been setup beforehand. Return
110// return;
111// }
112// }
113//
114// AWalker* Walker = Cast<AWalker>(&Actor);
115// if (Walker)
116// {
117// // If the actor is AWalker, return. These are tagged in editor.
118// return;
119// }
120//#endif
121
122 // Check if this is a Landscape actor
123 // if so, apply same ID and label to all landscape and its components.
124 ALandscape* Landscape = Cast<ALandscape>(&Actor);
125 if (Landscape)
126 {
127 TagLandscape(Landscape, Actor);
128 return;
129 }
130
131 // Iterate all static meshes
132 TArray<UStaticMeshComponent*> StaticMeshComponents;
133 Actor.GetComponents<UStaticMeshComponent>(StaticMeshComponents);
134 for (UStaticMeshComponent* Component : StaticMeshComponents)
135 {
136 if (Component)
137 {
138 ELabels Label = GetLabelFromStaticComponent(Component);
139 float InstanceSegmentationID = SetStencilValue(*Component, Actor, Label);
140
141 FTaggedActorData ActorData;
142 ActorData.Actor = &Actor;
143 ActorData.InstanceID = InstanceSegmentationID;
144 ActorData.StaticMeshComponent = Component;
145 ActorData.Label = Label;
146 TaggedActors.Add(ActorData);
147 }
148 }
149
150 // Iterate all skeletal meshes
151 TArray<USkeletalMeshComponent*> SkeletalMeshComponents;
152 Actor.GetComponents<USkeletalMeshComponent>(SkeletalMeshComponents);
153 for (USkeletalMeshComponent* Component : SkeletalMeshComponents)
154 {
155 if (Component)
156 {
158 float InstanceSegmentationID = SetStencilValue(*Component, Actor, Label);
159
160 FTaggedActorData ActorData;
161 ActorData.Actor = &Actor;
162 ActorData.InstanceID = InstanceSegmentationID;
163 ActorData.SkeletalMeshComponent = Component;
164 ActorData.Label = Label;
165 TaggedActors.Add(ActorData);
166 }
167 }
168}
169
170float ATagger::SetStencilValue(UPrimitiveComponent& Component, AActor& Actor, const ELabels& Label)
171{
172 Component.SetRenderCustomDepth(true);
173 Component.SetCustomDepthStencilValue(static_cast<int>(Label) + 1);
174
175#ifdef InstanceSegmentationPass_EXISTS
176 float CurrentID = ID;
177
178 // Set custom primitive data, used for instance segmentation.
179 Component.SetCustomPrimitiveDataVector4(4,
180 FVector4(CurrentID,
181 (float)CastEnum(Label),
182 0.0f,
183 0.0f));
184
185 // Increase ID counter
186 ID += 1.0f;
187#endif
188
189 return CurrentID;
190}
191
193{
194 if (TaggedActors.IsEmpty())
195 {
196 return;
197 }
198
200 if (!CSVFile)
201 {
202 return;
203 }
204
205 for (const FTaggedActorData& TaggedActorData : TaggedActors)
206 {
207 AActor* Actor = TaggedActorData.Actor;
208 if (!Actor)
209 {
210 continue;
211 }
212
213 WriteComponentToCSV(TaggedActorData);
214 }
215
216 CSVFile->Close();
217}
218
219AActor* ATagger::GetActorByInstanceID(float InstanceID)
220{
221 for (const FTaggedActorData& TaggedActorData : TaggedActors)
222 {
223 if (TaggedActorData.InstanceID == InstanceID)
224 {
225 return TaggedActorData.Actor;
226 }
227 }
228
229 return nullptr;
230}
231
233{
234 GeoReferencingSystem = AGeoReferencingSystem::GetGeoReferencingSystem(GetWorld());
235
236 // CSV file settings
237 FCSVFileSettings Settings;
239 Settings.Append = false;
240 Settings.CreateUnique = true;
242
243 FString MapName = GetWorld()->GetMapName();
244 FString FileName = MapName + TEXT("_ObjectLocations");
245
246 if (MapName.Contains("dev"))
247 {
248 Settings.QueueSize = 65536;
249 }
250
251 CSVFile = UCSVFile::CreateCSVFile(FileName, Settings);
252
253 TArray<FString> Header = { TEXT("Name"), TEXT("InstanceID"), TEXT("LocationX"), TEXT("LocationY"),
254 TEXT("LocationZ"), TEXT("Yaw"), TEXT("Pitch"), TEXT("Roll"), TEXT("Bounds")};
256 {
257 Header.Append({ TEXT("Latitude"), TEXT("Longitude"), TEXT("Altitude") });
258 }
259 CSVFile->WriteRow(Header);
260}
261
263{
264 AActor* Actor = Data.Actor;
265
266 if (!Actor)
267 {
268 return;
269 }
270
271 FString AssetPath;
272 FString MeshName;
273 FVector Location = Actor->GetActorLocation();
274 FRotator Rotation = Actor->GetActorRotation();
275 FBoxSphereBounds Bounds;
276
277 // Try StaticMeshComponent first
278 if (Data.StaticMeshComponent && Data.StaticMeshComponent->GetStaticMesh())
279 {
280 UStaticMesh* StaticMesh = Data.StaticMeshComponent->GetStaticMesh();
281 AssetPath = StaticMesh->GetPathName();
282 MeshName = StaticMesh->GetName();
283 Bounds = StaticMesh->GetBounds();
284 }
285 // If not valid, try SkeletalMeshComponent
286 else if (Data.SkeletalMeshComponent && Data.SkeletalMeshComponent->GetSkeletalMeshAsset())
287 {
288 USkeletalMesh* SkeletalMesh = Data.SkeletalMeshComponent->GetSkeletalMeshAsset();
289 AssetPath = SkeletalMesh->GetPathName();
290 MeshName = SkeletalMesh->GetName();
291 Bounds = SkeletalMesh->GetBounds();
292 }
293 else
294 {
295 return;
296 }
297
298 // Check asset path
299 if (!AssetPath.StartsWith(TEXT("/Game/Agrarsense/Models")))
300 {
301 return;
302 }
303
304 TArray<FString> Row;
305 Row.Reserve(GeoReferencingSystem ? 12 : 9);
306
307 Row.Add(MeshName);
308 Row.Add(FString::Printf(TEXT("%.0f"), Data.InstanceID));
309
310 // TODO add semantic label to the CSV
311 Row.Add(FString::Printf(TEXT("%.2f"), Location.X));
312 Row.Add(FString::Printf(TEXT("%.2f"), Location.Y));
313 Row.Add(FString::Printf(TEXT("%.2f"), Location.Z));
314 Row.Add(FString::Printf(TEXT("%.2f"), Rotation.Yaw));
315 Row.Add(FString::Printf(TEXT("%.2f"), Rotation.Pitch));
316 Row.Add(FString::Printf(TEXT("%.2f"), Rotation.Roll));
317
318 // Bounds
319 Row.Add(FString::Printf(TEXT("Origin=(%.2f,%.2f,%.2f),BoxExtent=(%.2f,%.2f,%.2f),SphereRadius=%.2f"),
320 Bounds.Origin.X, Bounds.Origin.Y, Bounds.Origin.Z,
321 Bounds.BoxExtent.X, Bounds.BoxExtent.Y, Bounds.BoxExtent.Z,
322 Bounds.SphereRadius));
323
325 {
326 const FGeographicCoordinates GeoCoords = UCoordinateConversionUtilities::UnrealToGeographicCoordinates(GeoReferencingSystem, Location);
327 Row.Add(FString::Printf(TEXT("%.8f"), GeoCoords.Latitude));
328 Row.Add(FString::Printf(TEXT("%.8f"), GeoCoords.Longitude));
329 Row.Add(FString::Printf(TEXT("%.8f"), GeoCoords.Altitude));
330 }
331
332 if (CSVFile)
333 {
334 CSVFile->WriteRow(Row);
335 }
336}
337
338void ATagger::TagLandscape(ALandscape* Landscape, AActor& Actor)
339{
340 if (!Landscape)
341 {
342 return;
343 }
344
345 constexpr float LANDSCAPE_ID = 0.0f;
346
347 // For some reason we needed to loop UStaticMeshComponents instead of ULandscapeComponents
348 TArray<UStaticMeshComponent*> StaticMeshComponents;
349 Actor.GetComponents<UStaticMeshComponent>(StaticMeshComponents);
350 for (UStaticMeshComponent* Component : StaticMeshComponents)
351 {
352 if (Component)
353 {
354 Component->SetRenderCustomDepth(true);
355 Component->SetCustomDepthStencilValue(static_cast<int>(ELabels::Terrain) + 1);
356
357#ifdef InstanceSegmentationPass_EXISTS
358 // Set custom primitive data, used for instance segmentation.
359 Component->SetCustomPrimitiveDataVector4(4,
360 FVector4(LANDSCAPE_ID,
362 0.0f,
363 0.0f));
364#endif
365 }
366 }
367}
368
370{
371 const ELabels* FoundLabel = LabelMap.Find(String);
372 if (FoundLabel)
373 {
374 return *FoundLabel;
375 }
376 else
377 {
378#if WITH_EDITOR
379 UE_LOG(LogTemp, Warning, TEXT("Tagger.cpp: Missing Label: %s"), *String);
380#endif
381 return ELabels::None;
382 }
383}
384
385ELabels ATagger::GetLabelFromStaticComponent(UStaticMeshComponent* StaticMeshComponentPtr)
386{
387 ELabels Label = ELabels::None;
388
389 if (!StaticMeshComponentPtr)
390 {
391 return Label;
392 }
393
394 Label = GetLabelFromTag(StaticMeshComponentPtr);
395 if (Label != ELabels::None)
396 {
397 return Label;
398 }
399
400 UStaticMesh* MeshPtr = StaticMeshComponentPtr->GetStaticMesh();
401 if (MeshPtr)
402 {
403 FString Name;
404 Label = GetLabelFromPath(MeshPtr->GetPathName(), Name);
405
406 if (Label != ELabels::None)
407 {
408 StaticMeshComponentPtr->ComponentTags.Insert(FName(Name), 0);
409 }
410 }
411
412 return Label;
413}
414
415ELabels ATagger::GetLabelFromSkeletalMeshComponent(USkeletalMeshComponent* USkeletalMeshComponentPtr)
416{
417 ELabels Label = ELabels::None;
418
419 if (!USkeletalMeshComponentPtr)
420 {
421 return Label;
422 }
423
424 Label = GetLabelFromTag(USkeletalMeshComponentPtr);
425 if (Label != ELabels::None)
426 {
427 return Label;
428 }
429
430 USkeletalMesh* PhysicsAsset = USkeletalMeshComponentPtr->GetSkeletalMeshAsset();
431 if (PhysicsAsset)
432 {
433 FString Name;
434 Label = GetLabelFromPath(PhysicsAsset->GetPathName(), Name);
435
436 if (Label != ELabels::None)
437 {
438 USkeletalMeshComponentPtr->ComponentTags.Insert(FName(Name), 0);
439 }
440 }
441
442 return Label;
443}
444
445ELabels ATagger::GetLabelFromPath(const FString& Path, FString& FolderName)
446{
447 ELabels Label = ELabels::None;
448
449 // If the Path doesn't contain Models,
450 // the asset is not under /Content/Agrarsense/Models directory
451 // meaing we ignore it.
452 if (!Path.Contains("Models"))
453 {
454 return Label;
455 }
456
457 TArray<FString> StringArray;
458 Path.ParseIntoArray(StringArray, TEXT("/"), false);
459
460 FolderName = StringArray[StringArray.Num() - 2];
461
462 Label = GetLabelFromString(FolderName);
463
464 return Label;
465}
ELabels
Definition: ObjectLabel.h:14
static auto CastEnum(T label)
Definition: Tagger.cpp:96
void ExportObjectLocationsToCSV()
Definition: Tagger.cpp:192
virtual void BeginPlay() override
Definition: Tagger.cpp:31
void CreateCSVFile()
Definition: Tagger.cpp:232
TMap< FString, ELabels > LabelMap
Definition: Tagger.h:115
void WriteComponentToCSV(const FTaggedActorData Data)
Definition: Tagger.cpp:262
ELabels GetLabelFromPath(const FString &Path, FString &FolderName)
Definition: Tagger.cpp:445
ELabels GetLabelFromStaticComponent(UStaticMeshComponent *StaticMeshComponentPtr)
Definition: Tagger.cpp:385
void OnActorSpawned(AActor *Actor)
Definition: Tagger.cpp:87
ELabels GetLabelFromTag(const T *Object)
Definition: Tagger.h:95
void TagExistingActors()
Definition: Tagger.cpp:63
float ID
Definition: Tagger.h:123
AGeoReferencingSystem * GeoReferencingSystem
Definition: Tagger.h:107
TArray< FTaggedActorData > TaggedActors
Definition: Tagger.h:111
ATagger()
Definition: Tagger.cpp:26
void TagActor(AActor &Actor)
Definition: Tagger.cpp:101
UCSVFile * CSVFile
Definition: Tagger.h:109
FDelegateHandle ActorSpawnedDelegateHandle
Definition: Tagger.h:113
ELabels GetLabelFromSkeletalMeshComponent(USkeletalMeshComponent *USkeletalMeshComponentPtr)
Definition: Tagger.cpp:415
AActor * GetActorByInstanceID(float InstanceID)
Definition: Tagger.cpp:219
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override
Definition: Tagger.cpp:55
float SetStencilValue(UPrimitiveComponent &Component, AActor &Actor, const ELabels &Label)
Definition: Tagger.cpp:170
ELabels GetLabelFromString(const FString &String)
Definition: Tagger.cpp:369
void TagLandscape(ALandscape *Landscape, AActor &Actor)
Definition: Tagger.cpp:338
static bool IsPlayingInMainMenu()
static UCSVFile * CreateCSVFile(const FString &FileNameWithoutExtension, const FCSVFileSettings &Settings)
Definition: CSVFile.cpp:14
void Close()
Definition: CSVFile.cpp:115
void WriteRow(const TArray< FString > &Cells)
Definition: CSVFile.cpp:77
static FGeographicCoordinates UnrealToGeographicCoordinates(AGeoReferencingSystem *GeoReferencingSystem, const FVector &Position)
bool CreateUnique
Definition: CSVFile.h:42
ECSVDelimiter Delimiter
Definition: CSVFile.h:36
int32 QueueSize
Definition: CSVFile.h:48
FCSVFileWriteOptions FileWriteOption
Definition: CSVFile.h:45
float InstanceID
Definition: Tagger.h:37
ELabels Label
Definition: Tagger.h:40
AActor * Actor
Definition: Tagger.h:28
UStaticMeshComponent * StaticMeshComponent
Definition: Tagger.h:31
USkeletalMeshComponent * SkeletalMeshComponent
Definition: Tagger.h:34