Agrarsense
InstancedRenderer.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 "InstancedRenderer.h"
10
11#include "TimerManager.h"
12#include "Materials/Material.h"
13#include "Kismet/GameplayStatics.h"
14#include "Engine/World.h"
15
17
18AInstancedRenderer::AInstancedRenderer(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
19{
20 RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
21 RootComponent->SetMobility(EComponentMobility::Static);
22 PrimaryActorTick.bCanEverTick = true;
23}
24
26{
27 Super::BeginPlay();
28
29 // Disable collision for this actor. The instanced actor's themselves have their own collisions
30 SetActorEnableCollision(false);
31
32 if (Instance == nullptr)
33 {
34 Instance = this;
35 }
36 else
37 {
38#if WITH_EDITOR
39 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Instance already exists. Destroying this actor.."));
40#endif
41 Destroy();
42 }
43
44 UWorld* World = GetWorld();
45
47 if (Settings)
48 {
51 }
52
54 if (Weather)
55 {
58 }
59
60#if WITH_EDITOR
61 FTimerHandle Handle;
62 World->GetTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda([this]
63 {
64 LogInfo();
65 }), 1.0f, false);
66#endif
67}
68
70{
71#if WITH_EDITOR
72 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Unique mesh entries: %d | Total Instance Count: %d"), InstanceEntries.Num(), TotalInstanceCount);
73#endif
74}
75
76void AInstancedRenderer::EndPlay(const EEndPlayReason::Type EndPlayReason)
77{
78 Super::EndPlay(EndPlayReason);
79
80 if (Instance == this)
81 {
82 Instance = nullptr;
83 }
84 else
85 {
86 return;
87 }
88
89 UWorld* World = GetWorld();
90
92 if (Settings)
93 {
95 }
96
98 if (Weather)
99 {
101 }
102
104
105 // Destroy all Instanced entries
106 for (FInstanceEntry& Entry : InstanceEntries)
107 {
108 if (Entry.InstancedStaticMeshComponent)
109 {
110 Entry.InstancedStaticMeshComponent->ClearInstances();
111 Entry.InstancedStaticMeshComponent->UnregisterComponent();
112 Entry.InstancedStaticMeshComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform);
113 Entry.InstancedStaticMeshComponent->DestroyComponent();
114 Entry.InstancedStaticMeshComponent = nullptr;
115 }
116
117 Entry.Materials.Empty();
118 Entry.ActorData.Empty();
119 Entry.Mesh.Reset();
120 }
121
122 InstanceEntries.Empty();
123}
124
125void AInstancedRenderer::Tick(float DeltaTime)
126{
128}
129
131{
132 int NumberOfActorsToUpdate = InstancedActorsToUpdate.size();
133 if (InstancedActorsToUpdate.size() == 0)
134 {
135 SetActorTickEnabled(false);
136 return;
137 }
138
139 for (int32 i = 0; i < NumberOfActorsToUpdate; i++)
140 {
142 AInstancedActor* ActorPtr = InstancedData.Actor.Get();
143 if (ActorPtr)
144 {
145 FTransform CurrentActorTransform = ActorPtr->GetActorTransform();
146 if (!AreTransformsEqual(ActorPtr->GetPreviousTransform(), CurrentActorTransform))
147 {
148 UpdateInstanceTransform(ActorPtr->GetComponentIndex(), ActorPtr->GetInstanceIndex(), CurrentActorTransform);
149 ActorPtr->UpdatePreviousTransform(CurrentActorTransform);
150 }
151 }
152 else
153 {
154 // Remove InstancedData from std::vector InstancedActorsToUpdate
156 NumberOfActorsToUpdate--;
157 i--;
158 }
159 }
160}
161
163{
164 if (InstancedActor == nullptr)
165 {
166#if WITH_EDITOR
167 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Actor is null!"));
168#endif
169 return false;
170 }
171
172 UStaticMeshComponent* StaticMeshComponent = InstancedActor->GetStaticMeshComponent();
173 if (StaticMeshComponent == nullptr)
174 {
175#if WITH_EDITOR
176 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: AInstancedActor %s UStaticMeshComponent is null!"), *InstancedActor->GetName());
177#endif
178 return false;
179 }
180
181 UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
182 if (Mesh == nullptr)
183 {
184#if WITH_EDITOR
185 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: AInstancedActor %s UStaticMeshComponent mesh is null!"), *InstancedActor->GetName());
186#endif
187 return false;
188 }
189
190 // Find or create InstancedStaticMeshComponents array index for this instance
191 int32 ComponentIndex = FindOrAddUniqueMesh(StaticMeshComponent, InstancedActor);
192
193 // Check index validity
194 if (!InstanceEntries.IsValidIndex(ComponentIndex))
195 {
196#if WITH_EDITOR
197 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: ComponentIndex is not valid!"));
198#endif
199 return false;
200 }
201
202 UInstancedStaticMeshComponent* InstancedStaticMeshComponent = InstanceEntries[ComponentIndex].InstancedStaticMeshComponent;
203 if (!InstancedStaticMeshComponent)
204 {
205#if WITH_EDITOR
206 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: UInstancedStaticMeshComponent is nullptr!"));
207#endif
208 return false;
209 }
210
211 // Add InstancedActor to TArray
212 FInstancedActorData InstancedData;
213 InstancedData.Actor = InstancedActor;
214 InstanceEntries[ComponentIndex].ActorData.Add(InstancedData);
215
216 // Get the StaticMeshComponent transform and add instance
217 FTransform ActorTransform = StaticMeshComponent->GetComponentTransform();
218 InstancedStaticMeshComponent->AddInstance(ActorTransform);
219
220 // If actor has bool UpdateTransformAutomatically true,
221 // add actor and its data to InstancedActorsToUpdate array
222 if (InstancedActor->UpdateTransformAutomatically())
223 {
224 InstancedStaticMeshComponent->SetMobility(EComponentMobility::Movable);
226 }
227
228 int32 InstanceIndex = InstancedStaticMeshComponent->GetNumRenderInstances() - 1;
229
230 // Notify the actor that instance has been added successfully.
231 InstancedActor->InstanceAdded(ComponentIndex, InstanceIndex);
232
234
235 return true;
236}
237
238int32 AInstancedRenderer::FindOrAddUniqueMesh(UStaticMeshComponent* StaticMeshComponent, AInstancedActor* InstancedActor)
239{
240 if (InstancedActor == nullptr || StaticMeshComponent == nullptr)
241 {
242#if WITH_EDITOR
243 UE_LOG(LogTemp, Warning, TEXT("StaticInstancedRenderer.cpp: StaticMeshComponent or InstancedActor is null!"));
244#endif
245 // return absurd number which should fail adding this instance to this renderer in above function.
246 return 99999999;
247 }
248
249 UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
250
251 if (!InstancedActor->HasAlternativeMaterial())
252 {
253 const int num = InstanceEntries.Num();
254 for (int32 Index = 0; Index < num; ++Index)
255 {
256 if (InstanceEntries[Index].Mesh == Mesh)
257 {
258 return Index;
259 }
260 }
261 }
262
263 const int count = StaticMeshComponent->GetNumMaterials();
264
265 TArray<UMaterialInterface*> MeshMaterials;
266 MeshMaterials.Reserve(count);
267
268 for (int32 i = 0; i < count; i++)
269 {
270 MeshMaterials.Add(StaticMeshComponent->GetMaterial(i));
271 }
272
273 const int Size = InstanceEntries.Num();
274 for (int32 Index = 0; Index < Size; ++Index)
275 {
276 if (InstanceEntries[Index].Mesh == Mesh)
277 {
278 bool bMaterialsMatch = true;
279
280 for (int32 MaterialIndex = 0; MaterialIndex < InstanceEntries[Index].Materials.Num(); ++MaterialIndex)
281 {
282 UMaterialInterface* UniqueMeshMaterial = InstanceEntries[Index].Materials[MaterialIndex];
283 UMaterialInterface* MeshMaterial = MeshMaterials[MaterialIndex];
284 if (UniqueMeshMaterial != MeshMaterial)
285 {
286 bMaterialsMatch = false;
287 }
288 }
289
290 if (bMaterialsMatch)
291 {
292 return Index;
293 }
294 }
295 }
296
297 // Else this is completely new mesh and/or mesh with alternative material,
298 // we need to create new FInstanceEntry and UInstancedStaticMeshComponent for it
299
300 // Create and setup new UInstancedStaticMeshComponent
301 UInstancedStaticMeshComponent* InstancedStaticMeshComponent = NewObject<UInstancedStaticMeshComponent>(this);
302 InstancedStaticMeshComponent->RegisterComponent();
303 InstancedStaticMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
304 InstancedStaticMeshComponent->CreatePhysicsState(false);
305 InstancedStaticMeshComponent->SetSimulatePhysics(false);
306 InstancedStaticMeshComponent->SetStaticMesh(Mesh);
307 InstancedStaticMeshComponent->SetMobility(EComponentMobility::Static);
308 InstancedStaticMeshComponent->SetHiddenInGame(false);
309 InstancedStaticMeshComponent->SetVisibility(true);
310 InstancedStaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform);
311 InstancedStaticMeshComponent->InstanceStartCullDistance = InstancedActor->GetInstanceStartCullDistance();
312 InstancedStaticMeshComponent->InstanceEndCullDistance = InstancedActor->GetInstanceEndCullDistance();
313
314 // Set WPO distance and evaluation
315 InstancedStaticMeshComponent->SetWorldPositionOffsetDisableDistance(WorldPositionOffsetDistance);
316 /*InstancedStaticMeshComponent->SetEvaluateWorldPositionOffset(true);
317 InstancedStaticMeshComponent->SetEvaluateWorldPositionOffsetInRayTracing(true);*/
318
319 bool AllowWorldPositionOffsetDisable = InstancedActor->GetAllowWorldPositionOffsetDisable();
320
321 // For whatever reason this only works when done a bit later..
322 FTimerHandle Handle;
323 GetWorld()->GetTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda([this, InstancedStaticMeshComponent, AllowWorldPositionOffsetDisable]
324 {
325 if (InstancedStaticMeshComponent && AllowWorldPositionOffsetDisable && CurrentWeatherParameters.WindIntensity < 0.05f)
326 {
327 InstancedStaticMeshComponent->SetEvaluateWorldPositionOffset(false);
328 InstancedStaticMeshComponent->SetEvaluateWorldPositionOffsetInRayTracing(false);
329 }
330 }), 0.350f, false);
331
332 // Thermal and Semantic camera use custom depth.
333 // RenderCustomDepth is set to false, if no Thermal or Semantic camera exists in the level,
334 // and is automatically set to true/false for all instances when they are spawned/destroyed.
335 // Doing this saves a lot rendering performance.
336 InstancedStaticMeshComponent->SetRenderCustomDepth(true);
337 InstancedStaticMeshComponent->SetCustomDepthStencilValue(StaticMeshComponent->CustomDepthStencilValue);
338
339 // Huge performance implication, recommend is Rigid (especially for Foliage with WPO).
340 // https://docs.unrealengine.com/5.3/en-US/PythonAPI/class/ShadowCacheInvalidationBehavior.html
341 InstancedStaticMeshComponent->ShadowCacheInvalidationBehavior = EShadowCacheInvalidationBehavior::Rigid;
342
343 // Create new FInstanceEntry
344 FInstanceEntry InstanceEntry;
345 InstanceEntry.Mesh = Mesh;
346 InstanceEntry.Materials = MeshMaterials;
347 InstanceEntry.InstanceEntryIndex = InstanceEntries.Num();
348 InstanceEntry.MeshName = Mesh->GetName();
349 InstanceEntry.InstancedStaticMeshComponent = InstancedStaticMeshComponent;
350 InstanceEntry.AllowWorldPositionOffsetDisable = AllowWorldPositionOffsetDisable;
351
352 // Set materials to newly created UInstancedStaticMeshComponent
353 const int32 MaterialCount = MeshMaterials.Num();
354 for (int32 MaterialIndex = 0; MaterialIndex < MaterialCount; ++MaterialIndex)
355 {
356 UMaterialInterface* MaterialInterface = InstanceEntry.Materials[MaterialIndex];
357
358 if (MaterialInterface)
359 {
360 InstancedStaticMeshComponent->SetMaterial(MaterialIndex, MaterialInterface);
361
362#if WITH_EDITOR
363 // Validate in Editor that material has bUsedWithInstancedStaticMeshes on
364 UMaterial* Material = Cast<UMaterial>(MaterialInterface);
365 if (Material)
366 {
367 if (!Material->bUsedWithInstancedStaticMeshes)
368 {
369 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Material '%s' bUsedWithInstancedStaticMeshes is not set to true!"), *Material->GetName());
370 }
371 }
372#endif
373 }
374 }
375
376 InstanceEntries.Add(InstanceEntry);
377
378 return InstanceEntries.Num() - 1;
379}
380
382{
383 if (!InstancedActor)
384 {
385 return;
386 }
387
388 if (Add)
389 {
390 FInstancedActorData InstancedData;
391 InstancedData.Actor = InstancedActor;
392 InstancedActor->UpdatePreviousTransform(InstancedActor->GetActorTransform());
393 InstancedActorsToUpdate.emplace_back(InstancedData);
394
395 // Set this Actor's tick to true.
396 SetActorTickEnabled(true);
397 }
398 else
399 {
400 auto it = std::remove_if(InstancedActorsToUpdate.begin(), InstancedActorsToUpdate.end(),
401 [InstancedActor](const FInstancedActorData& Data)
402 {
403 return Data.Actor == InstancedActor;
404 });
405
406 if (it != InstancedActorsToUpdate.end())
407 {
409 }
410 }
411}
412
413void AInstancedRenderer::UpdateInstanceTransform(int32 ComponentIndex, int32 InstanceNumber, const FTransform& NewTransform)
414{
415 if (!InstanceEntries.IsValidIndex(ComponentIndex))
416 {
417 return;
418 }
419
420 FInstanceEntry& Entry = InstanceEntries[ComponentIndex];
421
423 {
424 Entry.InstancedStaticMeshComponent->UpdateInstanceTransform(InstanceNumber, NewTransform, true, true);
425 }
426}
427
428void AInstancedRenderer::RemoveInstance(int32 ComponentIndex, int32 InstanceNumber)
429{
430 if (!InstanceEntries.IsValidIndex(ComponentIndex))
431 {
432 return;
433 }
434
435 FInstanceEntry& Entry = InstanceEntries[ComponentIndex];
436
438 {
439 Entry.InstancedStaticMeshComponent->RemoveInstance(InstanceNumber);
440 }
441
442 auto& InstancedActors = InstanceEntries[ComponentIndex].ActorData;
443
444 // Remove instance if within range
445 if (InstanceNumber >= 0 && InstanceNumber < InstancedActors.Num())
446 {
447 // Is it necessary here to check the Actor validity?
448 AInstancedActor* InstanceToRemove = InstancedActors[InstanceNumber].Actor.Get();
449 if (InstanceToRemove)
450 {
451 InstancedActors.RemoveAt(InstanceNumber);
452 }
453 }
454
455 for (int32 i = 0; i < InstancedActors.Num(); i++)
456 {
457 AInstancedActor* InstanceActor = InstancedActors[i].Actor.Get();
458 if (InstanceActor)
459 {
460 // Notify all AInstancedActor that are part of this InstanceEntry
461 // that one of the InstancedActors has been removed,
462 // this is because we need to adjust each AInstancedActor* InstanceNumber,
463 // unless the removed instance number is smaller
464 // than AInstancedActor* InstanceNumber (handled in the UpdateIndex() function).
465 InstanceActor->UpdateIndex(InstanceNumber);
466 }
467 }
468
470}
471
472bool AInstancedRenderer::AreTransformsEqual(const FTransform& TransformA, const FTransform& TransformB) const
473{
474 if (TransformA.GetTranslation() != TransformB.GetTranslation())
475 {
476 return false;
477 }
478
479 if (TransformA.GetRotation() != TransformB.GetRotation())
480 {
481 return false;
482 }
483
484 if (TransformA.GetScale3D() != TransformB.GetScale3D())
485 {
486 return false;
487 }
488
489 // else Transforms are equal
490 return true;
491}
492
494{
495 Rendering = ShouldRender;
496 for (const FInstanceEntry& Entry : InstanceEntries)
497 {
498 if (Entry.InstancedStaticMeshComponent)
499 {
500 Entry.InstancedStaticMeshComponent->SetVisibility(Rendering);
501 }
502 }
503
504#if WITH_EDITOR
505 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Rendering: %s"), (Rendering ? TEXT("true") : TEXT("false")));
506#endif
507}
508
510{
511 if (!InstanceEntries.IsValidIndex(Index))
512 {
513#if WITH_EDITOR
514 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Index %d is not valid!"), Index);
515#endif
516 return;
517 }
518
519 FInstanceEntry& Entry = InstanceEntries[Index];
520
522 {
523 Entry.InstancedStaticMeshComponent->SetVisibility(Visible);
524 }
525}
526
528{
531}
532
534{
535 // When weather changes, we check current wind intensity to
536 // enable or disable certain instances world position offset evaluation.
537 // Disabling WPO completely when wind is 0.0 saves a lot of performance with foliage.
539 {
540 CurrentWeatherParameters = WeatherParameters;
541 bool ShouldRenderWPO = CurrentWeatherParameters.WindIntensity > 0.05f;
542 SetRenderWorldPositionOffet(ShouldRenderWPO);
543 }
544}
545
547{
548 for (FInstanceEntry& Entry : InstanceEntries)
549 {
550 if (Entry.InstancedStaticMeshComponent)
551 {
552 Entry.InstancedStaticMeshComponent->SetRenderCustomDepth(Enabled);
553 }
554 }
555}
556
558{
559 if (WorldPositionOffsetDistance == NewWPODistance)
560 {
561 return;
562 }
563
564 WorldPositionOffsetDistance = NewWPODistance;
565
566 for (FInstanceEntry& Entry : InstanceEntries)
567 {
568 if (Entry.InstancedStaticMeshComponent)
569 {
570 Entry.InstancedStaticMeshComponent->SetWorldPositionOffsetDisableDistance(WorldPositionOffsetDistance);
571 }
572 }
573}
574
575void AInstancedRenderer::SetShadowCacheBehaviour(EShadowCacheInvalidationBehavior ShadowCacheInvalidationBehaviour)
576{
577 for (FInstanceEntry& Entry : InstanceEntries)
578 {
579 if (Entry.InstancedStaticMeshComponent)
580 {
581 Entry.InstancedStaticMeshComponent->ShadowCacheInvalidationBehavior = ShadowCacheInvalidationBehaviour;
582 }
583 }
584}
585
587{
588 for (FInstanceEntry& Entry : InstanceEntries)
589 {
590 if (Entry.AllowWorldPositionOffsetDisable && Entry.InstancedStaticMeshComponent)
591 {
592 Entry.InstancedStaticMeshComponent->SetEvaluateWorldPositionOffset(RenderWPO);
593 Entry.InstancedStaticMeshComponent->SetEvaluateWorldPositionOffsetInRayTracing(RenderWPO);
594 }
595 }
596}
597
598void AInstancedRenderer::DebugMaterialImpact(bool SetUnrealDefaultMaterial)
599{
600 UMaterialInterface* DefaultMaterial = nullptr;
601 if (SetUnrealDefaultMaterial)
602 {
603 DefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain(0));
604 }
605
606 for (FInstanceEntry& Entry : InstanceEntries)
607 {
608 if (Entry.AllowWorldPositionOffsetDisable && Entry.InstancedStaticMeshComponent)
609 {
610 if (SetUnrealDefaultMaterial)
611 {
612 for (int32 i = 0; i < Entry.InstancedStaticMeshComponent->GetNumMaterials(); ++i)
613 {
614 UMaterialInterface* CurrentMaterial = Entry.InstancedStaticMeshComponent->GetMaterial(i);
615 Entry.Materials.Add(CurrentMaterial);
616 }
617 for (int32 i = 0; i < Entry.InstancedStaticMeshComponent->GetNumMaterials(); ++i)
618 {
619 Entry.InstancedStaticMeshComponent->SetMaterial(i, DefaultMaterial);
620 }
621 }
622 else
623 {
624 if (Entry.Materials.Num() > 0)
625 {
626 for (int32 i = 0; i < Entry.Materials.Num(); ++i)
627 {
628 UMaterialInterface* OriginalMaterial = Entry.Materials[i];
629 Entry.InstancedStaticMeshComponent->SetMaterial(i, OriginalMaterial);
630 }
631 }
632 }
633 }
634 }
635}
int GetInstanceIndex() const
bool UpdateTransformAutomatically() const
void InstanceAdded(int32 CompIndex, int32 InstanceNum)
int GetInstanceEndCullDistance() const
FTransform GetPreviousTransform() const
int GetInstanceStartCullDistance() const
void UpdateIndex(int32 UpdatedIndex)
bool HasAlternativeMaterial() const
int GetComponentIndex() const
void UpdatePreviousTransform(const FTransform &NewTransform)
UStaticMeshComponent * GetStaticMeshComponent() const
bool GetAllowWorldPositionOffsetDisable() const
bool AddActorToInstancedRendering(AInstancedActor *InstancedActor)
void RemoveInstance(int32 ComponentIndex, int32 InstanceNumber)
void OnWeatherParametersChanged(FWeatherParameters WeatherParameters)
void SetShadowCacheBehaviour(EShadowCacheInvalidationBehavior ShadowCacheInvalidationBehaviour)
void EndPlay(const EEndPlayReason::Type EndPlayReason) override
void UpdateInstanceTransform(int32 ComponentIndex, int32 InstanceNumber, const FTransform &NewTransform)
void SetInstancedRendering(bool ShouldRender)
void AddOrRemoveActorFromAutomaticTransformUpdates(bool Add, AInstancedActor *InstancedActor)
void BeginPlay() override
bool AreTransformsEqual(const FTransform &TransformA, const FTransform &TransformB) const
void OnGraphicsSettingsChanged(FGlobalGraphicsSettings GraphicsSettings)
TArray< FInstanceEntry > InstanceEntries
void SetWorldPositionOffsetDistance(int32 NewWPODistance)
void SetRenderWorldPositionOffet(bool RenderWPO)
FWeatherParameters CurrentWeatherParameters
int32 FindOrAddUniqueMesh(UStaticMeshComponent *StaticMeshComponent, AInstancedActor *InstancedActor)
std::vector< FInstancedActorData > InstancedActorsToUpdate
void Tick(float DeltaTime) override
void SetRenderCustomDepth(bool Enabled)
AInstancedRenderer(const FObjectInitializer &ObjectInitializer)
void DebugMaterialImpact(bool SetUnrealDefaultMaterial)
void SetComponentIndexVisibility(int32 Index, bool Visible)
static AInstancedRenderer * Instance
const FWeatherParameters & GetCurrentWeather() const
Definition: Weather.h:76
FLevelEventDelegate_WeatherChanged OnWeatherChanged
Definition: Weather.h:85
int32 GetWorldPositionOffsetRenderDistance() const
FGraphicsSettingsDelegate OnGraphicsSettingsChanged
static UAgrarsenseSettings * GetAgrarsenseSettings()
static AWeather * GetWeatherActor(const UObject *WorldContextObject)
EShadowCacheInvalidationBehavior FoliageShadowCacheInvalidationBehaviour
TWeakObjectPtr< UStaticMesh > Mesh
TArray< UMaterialInterface * > Materials
bool AllowWorldPositionOffsetDisable
UInstancedStaticMeshComponent * InstancedStaticMeshComponent
TWeakObjectPtr< AInstancedActor > Actor