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