Agrarsense
InstancedRendererManager.cpp
Go to the documentation of this file.
1// Copyright (c) 2025 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
9
10#include "Engine/World.h"
11#include "GeoReferencingSystem.h"
12#include "Kismet/GameplayStatics.h"
13
15
16AInstancedRendererManager::AInstancedRendererManager(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
17{
18 PrimaryActorTick.bCanEverTick = false;
19}
20
22{
23 Super::BeginPlay();
24
25 if (!Instance)
26 {
27 Instance = this;
28 }
29 else
30 {
31#if WITH_EDITOR
32 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Instance already exists. Destroying this actor.."));
33#endif
34 Destroy();
35 }
36
37 UWorld* World = GetWorld();
38
39
40 // Ensure we don't create ISM's for these maps.
41 // Instance Segmentation Camera sensor doesn't support ISM/HISM components (or non-nanite meshes).
42 FString MapName = World->GetMapName().ToLower();
43 if (MapName.Contains(TEXT("vindeln_dev")) || MapName.Contains(TEXT("rovaniemiforest_dev")))
44 {
45 AllowISMCreation = false;
46 }
47
49 {
50 // Get all existing AInstancedActor in the world and try to add them to instanced rendering.
51 TArray<AActor*> Actors;
52 UGameplayStatics::GetAllActorsOfClass(World, AInstancedActor::StaticClass(), Actors);
53
54 for (AActor* Actor : Actors)
55 {
56 AInstancedActor* InstancedActor = Cast<AInstancedActor>(Actor);
57 if (InstancedActor && InstancedActor->CanAddToInstancedRenderer())
58 {
59 AddActorToInstancedRendering(InstancedActor);
60 }
61 }
62
63 // Subscribe to OnActorSpawned event to catch any new AInstancedActor spawned in the world to add them to instanced rendering.
64 ActorSpawnedDelegateHandle = World->AddOnActorSpawnedHandler(FOnActorSpawned::FDelegate::CreateUObject(this, &AInstancedRendererManager::OnActorSpawned));
65 }
66}
67
69{
70 if (AInstancedActor* InstancedActor = Cast<AInstancedActor>(Actor))
71 {
72 // Try to add the newly spawned AInstancedActor to instanced rendering.
73 if (InstancedActor->CanAddToInstancedRenderer())
74 {
75 AddActorToInstancedRendering(InstancedActor);
76 }
77 }
78}
79
80void AInstancedRendererManager::EndPlay(const EEndPlayReason::Type EndPlayReason)
81{
82 Super::EndPlay(EndPlayReason);
83
85
86 if (Instance == this)
87 {
88 Instance = nullptr;
89 }
90}
91
93{
95 {
96 return false;
97 }
98
99 if (!InstancedActor)
100 {
101#if WITH_EDITOR
102 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Actor is null!"));
103#endif
104 return false;
105 }
106
107 if (InstancedActor->UpdateTransformAutomatically())
108 {
109#if WITH_EDITOR
110 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: AInstancedActor %s is set to update transform automatically."), *InstancedActor->GetName());
111#endif
112 return false;
113 }
114
115 UStaticMeshComponent* StaticMeshComponent = InstancedActor->GetStaticMeshComponent();
116 if (!StaticMeshComponent)
117 {
118#if WITH_EDITOR
119 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: AInstancedActor %s UStaticMeshComponent is null!"), *InstancedActor->GetName());
120#endif
121 return false;
122 }
123
124 UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
125 if (!Mesh)
126 {
127#if WITH_EDITOR
128 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: AInstancedActor %s UStaticMeshComponent mesh is null!"), *InstancedActor->GetName());
129#endif
130 return false;
131 }
132
133 // Loop throigh InstancedRenderers and check if we already have this type of instance in the world.
135 {
136 if (Renderer && Renderer->Matches(StaticMeshComponent, InstancedActor->AlternativeMaterial))
137 {
138 UInstancedStaticMeshComponent* Comp = Renderer->GetInstancedStaticMeshComponent();
139 if (Comp)
140 {
141 Comp->AddInstance(InstancedActor->GetActorTransform());
142 InstancedActor->InstanceAdded();
143 InstancedActor->Destroy();
144 return true;
145 }
146 }
147 }
148
149 // Else this is new instance, create new AInstancedActorRenderer Actor.
150 FTransform SpawnTransform = FTransform::Identity;
151 FActorSpawnParameters SpawnParams;
152
153 AInstancedActorRenderer* NewRenderer = GetWorld()->SpawnActor<AInstancedActorRenderer>(
154 AInstancedActorRenderer::StaticClass(),
155 SpawnTransform,
156 SpawnParams
157 );
158
159 InstancedRenderers.AddUnique(NewRenderer);
160 NewRenderer->SetupFromInstancedActor(InstancedActor);
161
162 return true;
163}
164
165bool AInstancedRendererManager::AppendISM(UInstancedStaticMeshComponent* ISM)
166{
167 if (!ISM)
168 {
169 return false;
170 }
171
173 {
174 if (!Renderer || !Renderer->Matches(ISM))
175 {
176 continue;
177 }
178
179 UInstancedStaticMeshComponent* Comp = Renderer->GetInstancedStaticMeshComponent();
180 if (!Comp)
181 {
182 continue;
183 }
184
185 const int32 InstanceCount = ISM->GetInstanceCount();
186 for (int32 i = 0; i < InstanceCount; ++i)
187 {
188 FTransform InstanceTransform;
189 if (ISM->GetInstanceTransform(i, InstanceTransform, true))
190 {
191 Comp->AddInstance(InstanceTransform);
192 }
193 }
194
195 return true;
196 }
197
198 return false;
199}
200
201bool AInstancedRendererManager::AppendOrCreateISM(UInstancedStaticMeshComponent* ISM)
202{
203 if (!ISM)
204 {
205 return false;
206 }
207
208 if (!AppendISM(ISM))
209 {
210 FTransform SpawnTransform = FTransform::Identity;
211 FActorSpawnParameters SpawnParams;
212
213 AInstancedActorRenderer* NewRenderer = GetWorld()->SpawnActor<AInstancedActorRenderer>(
214 AInstancedActorRenderer::StaticClass(),
215 SpawnTransform,
216 SpawnParams
217 );
218
219 if (!NewRenderer)
220 {
221 return false;
222 }
223
224 InstancedRenderers.AddUnique(NewRenderer);
225
226 UInstancedStaticMeshComponent* Comp = NewRenderer->GetInstancedStaticMeshComponent();
227
228 if (Comp)
229 {
230 Comp->SetStaticMesh(ISM->GetStaticMesh());
231 const int32 NumberOfMaterials = ISM->GetNumMaterials();
232 for (int32 i = 0; i < NumberOfMaterials; i++)
233 {
234 Comp->SetMaterial(i, ISM->GetMaterial(i));
235 }
236
237 TArray<FTransform> InstanceTransforms;
238 for (int32 i = 0; i < ISM->GetInstanceCount(); i++)
239 {
240 FTransform InstanceTransform;
241 if (ISM->GetInstanceTransform(i, InstanceTransform, true))
242 {
243 InstanceTransforms.Add(InstanceTransform);
244 }
245 }
246
247 for (const FTransform& Transform : InstanceTransforms)
248 {
249 Comp->AddInstance(Transform);
250 }
251 }
252
253 return true;
254 }
255
256 return true;
257}
258
260{
261 for (int32 i = InstancedRenderers.Num() - 1; i >= 0; --i)
262 {
264 if (Renderer && Renderer->SpawnInstancesBackActors())
265 {
266 Renderer->Destroy();
267 InstancedRenderers.RemoveAt(i);
268 }
269 }
270}
271
273{
274 UWorld* World = GetWorld();
275 if (World && !World->IsGameWorld())
276 {
277 TArray<AActor*> FoundActors;
278 UGameplayStatics::GetAllActorsOfClass(World, AInstancedActorRenderer::StaticClass(), FoundActors);
279
280 TArray<AInstancedActorRenderer*> RendererActors;
281 for (AActor* Actor : FoundActors)
282 {
283 if (AInstancedActorRenderer* Renderer = Cast<AInstancedActorRenderer>(Actor))
284 {
285 RendererActors.AddUnique(Renderer);
286 }
287 }
288 return RendererActors;
289 }
290
291 // If running at runtime (including PIE), return InstancedRenderers
292 return InstancedRenderers;
293}
294
296{
297 int32 total = 0;
299 {
300 if (Renderer)
301 {
302 UInstancedStaticMeshComponent* Comp = Renderer->GetInstancedStaticMeshComponent();
303 if (Comp)
304 {
305 total += Comp->GetInstanceCount();
306 }
307 }
308 }
309
310 TotalInstanceCount = total;
311
312 return TotalInstanceCount;
313}
314
315bool AInstancedRendererManager::SpawnInstanceBackToActor(UInstancedStaticMeshComponent* ISM, int32 Index, bool OnlyTree)
316{
318 {
319 if (Renderer && Renderer->SpawnInstanceBackToActor(ISM, Index, OnlyTree))
320 {
321 return true;
322 }
323 }
324
325 // Didn't find instance or spawning instance back to actor failed.
326 return false;
327}
328
329void AInstancedRendererManager::DestroyOverlappingInstancesBox(UInstancedStaticMeshComponent* ISM, FBox AreaBox, bool OnlyTrees)
330{
331 if (!ISM)
332 {
333 return;
334 }
335
337 {
338 if (!Renderer)
339 {
340 continue;
341 }
342
343 FInstancedActorParameters params = Renderer->GetInstancedActorParameters();
344 if (!params.IsTree && OnlyTrees)
345 {
346 continue;
347 }
348
349 UInstancedStaticMeshComponent* Comp = Renderer->GetInstancedStaticMeshComponent();
350 if (Comp && Comp == ISM)
351 {
352 TArray<int32> OverlappingInstances = Comp->GetInstancesOverlappingBox(AreaBox);
353
354 // Sort indices in descending order to avoid shifting issues
355 OverlappingInstances.Sort([](int32 A, int32 B)
356 {
357 return B < A;
358 });
359
360 for (int32 InstanceIndex : OverlappingInstances)
361 {
362 Comp->RemoveInstance(InstanceIndex);
363 }
364 }
365 }
366}
367
369{
371 {
372 if (Renderer)
373 {
374 UInstancedStaticMeshComponent* Comp = Renderer->GetInstancedStaticMeshComponent();
375 if (Comp)
376 {
377 Comp->SetVisibility(Visible);
378 }
379 }
380 }
381}
382
384{
385 AGeoReferencingSystem* GeoReferencingSystem = AGeoReferencingSystem::GetGeoReferencingSystem(GetWorld());
386 TArray<TArray<FString>> Rows;
387
389 {
390 if (Renderer && Renderer->GetInstancedActorParameters().IsTree)
391 {
392 UInstancedStaticMeshComponent* ISM = Renderer->GetInstancedStaticMeshComponent();
393 if (!ISM) continue;
394
395 const int32 InstanceCount = ISM->GetInstanceCount();
396 const FString MeshName = Renderer->GetStaticMesh() ? Renderer->GetStaticMesh()->GetName() : TEXT("Unknown");
397
398 for (int32 i = 0; i < InstanceCount; ++i)
399 {
400 FTransform Transform;
401 if (!ISM->GetInstanceTransform(i, Transform, true)) continue;
402
403 const FVector Location = Transform.GetLocation();
404 const FRotator Rotation = Transform.Rotator();
405
406 FString LatitudeStr = TEXT(""), LongitudeStr = TEXT(""), AltitudeStr = TEXT("");
407
408 if (GeoReferencingSystem)
409 {
410 const FGeographicCoordinates GeographicCoordinates = UCoordinateConversionUtilities::UnrealToGeographicCoordinates(GeoReferencingSystem, Location);
411 LatitudeStr = FString::Printf(TEXT("%.8f"), GeographicCoordinates.Latitude);
412 LongitudeStr = FString::Printf(TEXT("%.8f"), GeographicCoordinates.Longitude);
413 AltitudeStr = FString::Printf(TEXT("%.8f"), GeographicCoordinates.Altitude);
414 }
415
416 TArray<FString> Row;
417 Row.Add(MeshName);
418 Row.Add(FString::Printf(TEXT("%.2f"), Location.X));
419 Row.Add(FString::Printf(TEXT("%.2f"), Location.Y));
420 Row.Add(FString::Printf(TEXT("%.2f"), Location.Z));
421
422 if (GeoReferencingSystem)
423 {
424 Row.Add(LatitudeStr);
425 Row.Add(LongitudeStr);
426 Row.Add(AltitudeStr);
427 }
428
429 Rows.Add(Row);
430 }
431 }
432 }
433
434 if (Rows.IsEmpty())
435 {
436 return;
437 }
438
439 // CSV file settings
440 FCSVFileSettings Settings;
442 Settings.Append = false;
443 Settings.CreateUnique = true;
445
446 // Filename
447 FString MapName = GetWorld()->GetMapName();
448 FString FileName = MapName + TEXT("_TreeLocations");
449
450 // Create CSV file
451 UCSVFile* CSV = UCSVFile::CreateCSVFile(FileName, Settings);
452
453 // Create CSV Header row
454 TArray<FString> Header = { TEXT("name"), TEXT("x"), TEXT("y"), TEXT("z") };
455 if (GeoReferencingSystem)
456 {
457 Header.Append({ TEXT("latitude"), TEXT("longitude"), TEXT("altitude") });
458 }
459 CSV->WriteRow(Header);
460
461 // Write data rows
462 for (const TArray<FString>& Row : Rows)
463 {
464 CSV->WriteRow(Row);
465 }
466
467 // Finish writing and close the file
468 CSV->Destroy();
469}
void SetupFromInstancedActor(AInstancedActor *InstancedActor)
UInstancedStaticMeshComponent * GetInstancedStaticMeshComponent() const
bool UpdateTransformAutomatically() const
bool CanAddToInstancedRenderer()
UStaticMeshComponent * GetStaticMeshComponent() const
TArray< AInstancedActorRenderer * > InstancedRenderers
bool SpawnInstanceBackToActor(UInstancedStaticMeshComponent *ISM, int32 Index, bool OnlyTree)
bool AppendOrCreateISM(UInstancedStaticMeshComponent *ISM)
bool AppendISM(UInstancedStaticMeshComponent *ISM)
TArray< AInstancedActorRenderer * > GetAllInstancedActorRendererActors() const
void DestroyOverlappingInstancesBox(UInstancedStaticMeshComponent *ISM, FBox AreaBox, bool OnlyTrees)
static AInstancedRendererManager * Instance
bool AddActorToInstancedRendering(AInstancedActor *InstancedActor)
virtual void BeginPlay() override
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override
AInstancedRendererManager(const FObjectInitializer &ObjectInitializer)
static UCSVFile * CreateCSVFile(const FString &FileNameWithoutExtension, const FCSVFileSettings &Settings)
Definition: CSVFile.cpp:15
void WriteRow(const TArray< FString > &Cells)
Definition: CSVFile.cpp:78
void Destroy()
Definition: CSVFile.cpp:134
static FGeographicCoordinates UnrealToGeographicCoordinates(AGeoReferencingSystem *GeoReferencingSystem, const FVector &Position)
bool CreateUnique
Definition: CSVFile.h:42
ECSVDelimiter Delimiter
Definition: CSVFile.h:36
FCSVFileWriteOptions FileWriteOption
Definition: CSVFile.h:45