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#ifdef InstanceSegmentationPass_EXISTS
38 // if using our fork of Unreal Engine, user can use instance segmentation sensor,
39 // but only in Vindeln_Dev or RovaniemiForest_Dev maps where
40 // there are no ISM instances (expect for landscape vegetation)
41 FString MapName = GetWorld()->GetMapName().ToLower();
42
43 if (MapName.Contains(TEXT("vindeln")) && !MapName.Contains(TEXT("dev")))
44 {
45 AllowISMCreation = true;
46 }
47
48 if (MapName.Contains(TEXT("rovaniemiforest")) && !MapName.Contains(TEXT("dev")))
49 {
50 AllowISMCreation = true;
51 }
52#endif
53}
54
55void AInstancedRendererManager::EndPlay(const EEndPlayReason::Type EndPlayReason)
56{
57 Super::EndPlay(EndPlayReason);
58
59 if (Instance == this)
60 {
61 Instance = nullptr;
62 }
63}
64
66{
68 {
69 return false;
70 }
71
72 if (!InstancedActor)
73 {
74#if WITH_EDITOR
75 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Actor is null!"));
76#endif
77 return false;
78 }
79
80 if (InstancedActor->UpdateTransformAutomatically())
81 {
82#if WITH_EDITOR
83 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: AInstancedActor %s is set to update transform automatically."), *InstancedActor->GetName());
84#endif
85 return false;
86 }
87
88 UStaticMeshComponent* StaticMeshComponent = InstancedActor->GetStaticMeshComponent();
89 if (!StaticMeshComponent)
90 {
91#if WITH_EDITOR
92 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: AInstancedActor %s UStaticMeshComponent is null!"), *InstancedActor->GetName());
93#endif
94 return false;
95 }
96
97 UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
98 if (!Mesh)
99 {
100#if WITH_EDITOR
101 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: AInstancedActor %s UStaticMeshComponent mesh is null!"), *InstancedActor->GetName());
102#endif
103 return false;
104 }
105
106 // Loop throigh InstancedRenderers and check if we already have this type of instance in the world.
108 {
109 if (Renderer && Renderer->Matches(StaticMeshComponent, InstancedActor->AlternativeMaterial))
110 {
111 UInstancedStaticMeshComponent* Comp = Renderer->GetInstancedStaticMeshComponent();
112 if (Comp)
113 {
114 Comp->AddInstance(InstancedActor->GetActorTransform());
115 InstancedActor->InstanceAdded();
116 InstancedActor->Destroy();
117 return true;
118 }
119 }
120 }
121
122 // Else this is new instance, create new AInstancedActorRenderer Actor.
123 FTransform SpawnTransform = FTransform::Identity;
124 FActorSpawnParameters SpawnParams;
125
126 AInstancedActorRenderer* NewRenderer = GetWorld()->SpawnActor<AInstancedActorRenderer>(
127 AInstancedActorRenderer::StaticClass(),
128 SpawnTransform,
129 SpawnParams
130 );
131
132 InstancedRenderers.AddUnique(NewRenderer);
133 NewRenderer->SetupFromInstancedActor(InstancedActor);
134
135 return true;
136}
137
138bool AInstancedRendererManager::AppendISM(UInstancedStaticMeshComponent* ISM)
139{
140 if (!ISM)
141 {
142 return false;
143 }
144
146 {
147 if (!Renderer || !Renderer->Matches(ISM))
148 {
149 continue;
150 }
151
152 UInstancedStaticMeshComponent* Comp = Renderer->GetInstancedStaticMeshComponent();
153 if (!Comp)
154 {
155 continue;
156 }
157
158 const int32 InstanceCount = ISM->GetInstanceCount();
159 for (int32 i = 0; i < InstanceCount; ++i)
160 {
161 FTransform InstanceTransform;
162 if (ISM->GetInstanceTransform(i, InstanceTransform, true))
163 {
164 Comp->AddInstance(InstanceTransform);
165 }
166 }
167
168 return true;
169 }
170
171 return false;
172}
173
174bool AInstancedRendererManager::AppendOrCreateISM(UInstancedStaticMeshComponent* ISM)
175{
176 if (!ISM)
177 {
178 return false;
179 }
180
181 if (!AppendISM(ISM))
182 {
183 FTransform SpawnTransform = FTransform::Identity;
184 FActorSpawnParameters SpawnParams;
185
186 AInstancedActorRenderer* NewRenderer = GetWorld()->SpawnActor<AInstancedActorRenderer>(
187 AInstancedActorRenderer::StaticClass(),
188 SpawnTransform,
189 SpawnParams
190 );
191
192 if (!NewRenderer)
193 {
194 return false;
195 }
196
197 InstancedRenderers.AddUnique(NewRenderer);
198
199 UInstancedStaticMeshComponent* Comp = NewRenderer->GetInstancedStaticMeshComponent();
200
201 if (Comp)
202 {
203 Comp->SetStaticMesh(ISM->GetStaticMesh());
204 const int32 NumberOfMaterials = ISM->GetNumMaterials();
205 for (int32 i = 0; i < NumberOfMaterials; i++)
206 {
207 Comp->SetMaterial(i, ISM->GetMaterial(i));
208 }
209
210 TArray<FTransform> InstanceTransforms;
211 for (int32 i = 0; i < ISM->GetInstanceCount(); i++)
212 {
213 FTransform InstanceTransform;
214 if (ISM->GetInstanceTransform(i, InstanceTransform, true))
215 {
216 InstanceTransforms.Add(InstanceTransform);
217 }
218 }
219
220 for (const FTransform& Transform : InstanceTransforms)
221 {
222 Comp->AddInstance(Transform);
223 }
224 }
225
226 return true;
227 }
228
229 return true;
230}
231
233{
234 for (int32 i = InstancedRenderers.Num() - 1; i >= 0; --i)
235 {
237 if (Renderer && Renderer->SpawnInstancesBackActors())
238 {
239 Renderer->Destroy();
240 InstancedRenderers.RemoveAt(i);
241 }
242 }
243}
244
246{
247 UWorld* World = GetWorld();
248 if (World && !World->IsGameWorld())
249 {
250 TArray<AActor*> FoundActors;
251 UGameplayStatics::GetAllActorsOfClass(World, AInstancedActorRenderer::StaticClass(), FoundActors);
252
253 TArray<AInstancedActorRenderer*> RendererActors;
254 for (AActor* Actor : FoundActors)
255 {
256 if (AInstancedActorRenderer* Renderer = Cast<AInstancedActorRenderer>(Actor))
257 {
258 RendererActors.AddUnique(Renderer);
259 }
260 }
261 return RendererActors;
262 }
263
264 // If running at runtime (including PIE), return InstancedRenderers
265 return InstancedRenderers;
266}
267
269{
270 int32 total = 0;
272 {
273 if (Renderer)
274 {
275 UInstancedStaticMeshComponent* Comp = Renderer->GetInstancedStaticMeshComponent();
276 if (Comp)
277 {
278 total += Comp->GetInstanceCount();
279 }
280 }
281 }
282
283 TotalInstanceCount = total;
284
285 return TotalInstanceCount;
286}
287
288bool AInstancedRendererManager::SpawnInstanceBackToActor(UInstancedStaticMeshComponent* ISM, int32 Index, bool OnlyTree)
289{
291 {
292 if (Renderer && Renderer->SpawnInstanceBackToActor(ISM, Index, OnlyTree))
293 {
294 return true;
295 }
296 }
297
298 // Didn't find instance or spawning instance back to actor failed.
299 return false;
300}
301
302void AInstancedRendererManager::DestroyOverlappingInstancesBox(UInstancedStaticMeshComponent* ISM, FBox AreaBox, bool OnlyTrees)
303{
304 if (!ISM)
305 {
306 return;
307 }
308
310 {
311 if (!Renderer)
312 {
313 continue;
314 }
315
316 FInstancedActorParameters params = Renderer->GetInstancedActorParameters();
317 if (!params.IsTree && OnlyTrees)
318 {
319 continue;
320 }
321
322 UInstancedStaticMeshComponent* Comp = Renderer->GetInstancedStaticMeshComponent();
323 if (Comp && Comp == ISM)
324 {
325 TArray<int32> OverlappingInstances = Comp->GetInstancesOverlappingBox(AreaBox);
326
327 // Sort indices in descending order to avoid shifting issues
328 OverlappingInstances.Sort([](int32 A, int32 B)
329 {
330 return B < A;
331 });
332
333 for (int32 InstanceIndex : OverlappingInstances)
334 {
335 Comp->RemoveInstance(InstanceIndex);
336 }
337 }
338 }
339}
340
342{
344 {
345 if (Renderer)
346 {
347 UInstancedStaticMeshComponent* Comp = Renderer->GetInstancedStaticMeshComponent();
348 if (Comp)
349 {
350 Comp->SetVisibility(Visible);
351 }
352 }
353 }
354}
355
357{
358 AGeoReferencingSystem* GeoReferencingSystem = AGeoReferencingSystem::GetGeoReferencingSystem(GetWorld());
359 TArray<TArray<FString>> Rows;
360
362 {
363 if (Renderer && Renderer->GetInstancedActorParameters().IsTree)
364 {
365 UInstancedStaticMeshComponent* ISM = Renderer->GetInstancedStaticMeshComponent();
366 if (!ISM) continue;
367
368 const int32 InstanceCount = ISM->GetInstanceCount();
369 const FString MeshName = Renderer->GetStaticMesh() ? Renderer->GetStaticMesh()->GetName() : TEXT("Unknown");
370
371 for (int32 i = 0; i < InstanceCount; ++i)
372 {
373 FTransform Transform;
374 if (!ISM->GetInstanceTransform(i, Transform, true)) continue;
375
376 const FVector Location = Transform.GetLocation();
377 const FRotator Rotation = Transform.Rotator();
378
379 FString LatitudeStr = TEXT(""), LongitudeStr = TEXT(""), AltitudeStr = TEXT("");
380
381 if (GeoReferencingSystem)
382 {
383 const FGeographicCoordinates GeographicCoordinates = UCoordinateConversionUtilities::UnrealToGeographicCoordinates(GeoReferencingSystem, Location);
384 LatitudeStr = FString::Printf(TEXT("%.8f"), GeographicCoordinates.Latitude);
385 LongitudeStr = FString::Printf(TEXT("%.8f"), GeographicCoordinates.Longitude);
386 AltitudeStr = FString::Printf(TEXT("%.8f"), GeographicCoordinates.Altitude);
387 }
388
389 TArray<FString> Row;
390 Row.Add(MeshName);
391 Row.Add(FString::Printf(TEXT("%.2f"), Location.X));
392 Row.Add(FString::Printf(TEXT("%.2f"), Location.Y));
393 Row.Add(FString::Printf(TEXT("%.2f"), Location.Z));
394
395 if (GeoReferencingSystem)
396 {
397 Row.Add(LatitudeStr);
398 Row.Add(LongitudeStr);
399 Row.Add(AltitudeStr);
400 }
401
402 Rows.Add(Row);
403 }
404 }
405 }
406
407 if (Rows.IsEmpty())
408 {
409 return;
410 }
411
412 // CSV file settings
413 FCSVFileSettings Settings;
415 Settings.Append = false;
416 Settings.CreateUnique = true;
418
419 // Filename
420 FString MapName = GetWorld()->GetMapName();
421 FString FileName = MapName + TEXT("_TreeLocations");
422
423 // Create CSV file
424 UCSVFile* CSV = UCSVFile::CreateCSVFile(FileName, Settings);
425
426 // Create CSV Header row
427 TArray<FString> Header = { TEXT("name"), TEXT("x"), TEXT("y"), TEXT("z") };
428 if (GeoReferencingSystem)
429 {
430 Header.Append({ TEXT("latitude"), TEXT("longitude"), TEXT("altitude") });
431 }
432 CSV->WriteRow(Header);
433
434 // Write data rows
435 for (const TArray<FString>& Row : Rows)
436 {
437 CSV->WriteRow(Row);
438 }
439
440 // Finish writing and close the file
441 CSV->Destroy();
442}
void SetupFromInstancedActor(AInstancedActor *InstancedActor)
UInstancedStaticMeshComponent * GetInstancedStaticMeshComponent() const
bool UpdateTransformAutomatically() const
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:14
void WriteRow(const TArray< FString > &Cells)
Definition: CSVFile.cpp:77
void Destroy()
Definition: CSVFile.cpp:133
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