Agrarsense
InstancedActorRenderer.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
7
11
12AInstancedActorRenderer::AInstancedActorRenderer(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
13{
14 PrimaryActorTick.bCanEverTick = false;
15}
16
18{
19 Super::BeginPlay();
20
21 UWorld* World = GetWorld();
22
24 if (Settings)
25 {
28 }
29
31 if (Weather)
32 {
35 }
36}
37
38void AInstancedActorRenderer::EndPlay(const EEndPlayReason::Type EndPlayReason)
39{
40 Super::EndPlay(EndPlayReason);
41
42 UWorld* World = GetWorld();
43
45 if (Settings)
46 {
48 }
49
51 if (Weather)
52 {
54 }
55}
56
58{
59 if (!InstancedActor)
60 {
61 return;
62 }
63
64 UStaticMeshComponent* StaticMeshComponent = InstancedActor->GetStaticMeshComponent();
65 UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
66 UClass* ClassPtr = InstancedActor->GetClass();
67
68 FString Name = IInteractable::Execute_GetInteractableName(InstancedActor).ToString();
69
70 if (Name.IsEmpty())
71 {
72 Name = ClassPtr->GetAuthoredName();
73 if (Name.EndsWith(TEXT("_C")))
74 {
75 Name = Name.LeftChop(2);
76 }
77 if (Name.StartsWith(TEXT("BP_")))
78 {
79 Name = Name.RightChop(3);
80 }
81 Name.ReplaceInline(TEXT("_"), TEXT(" "));
82 Name.ReplaceInline(TEXT("Instanced"), TEXT(" "));
83 }
84
85#if WITH_EDITOR
86 FString ActorName = Name;
87 ActorName.ReplaceInline(TEXT(","), TEXT(""));
88 ActorName.ReplaceInline(TEXT(" "), TEXT("_"));
89 ActorName += "_Instanced";
90 SetActorLabel(ActorName);
91#endif
92
93 // Generate a 4-character GUID
94 FString Guid = FGuid::NewGuid().ToString().Left(4);
95 Name = FString::Printf(TEXT("%s(_%s_)"), *Name, *Guid);
96
97 InstancedStaticMeshComponent = NewObject<UInstancedStaticMeshComponent>(this);
98 InstancedStaticMeshComponent->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
99 AddInstanceComponent(InstancedStaticMeshComponent);
100
101 // Add new instance.
102 const FTransform& Transform = InstancedActor->GetActorTransform();
104
105 InstancedStaticMeshComponent->Rename(*Name);
106 InstancedStaticMeshComponent->bEditableWhenInherited = false;
107 InstancedStaticMeshComponent->RegisterComponent();
108 InstancedStaticMeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
109 InstancedStaticMeshComponent->CreatePhysicsState(false);
110 InstancedStaticMeshComponent->SetSimulatePhysics(false);
111 InstancedStaticMeshComponent->SetStaticMesh(Mesh);
112 InstancedStaticMeshComponent->SetMobility(EComponentMobility::Static);
113 InstancedStaticMeshComponent->SetHiddenInGame(false);
114 InstancedStaticMeshComponent->SetVisibility(true);
115 InstancedStaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform);
116 InstancedStaticMeshComponent->InstanceStartCullDistance = InstancedActor->GetInstanceStartCullDistance();
117 InstancedStaticMeshComponent->InstanceEndCullDistance = InstancedActor->GetInstanceEndCullDistance();
118
119 bool AllowWorldPositionOffsetDisable = InstancedActor->GetAllowWorldPositionOffsetDisable();
120
121 // For whatever reason this only works when done a bit later..
122 FTimerHandle Handle;
123 GetWorld()->GetTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda([this, AllowWorldPositionOffsetDisable] {
124 if (InstancedStaticMeshComponent && AllowWorldPositionOffsetDisable && CurrentWeatherParameters.WindIntensity < 0.05f)
125 {
126 InstancedStaticMeshComponent->SetEvaluateWorldPositionOffset(false);
127 InstancedStaticMeshComponent->SetEvaluateWorldPositionOffsetInRayTracing(false);
128 }}), 0.350f, false);
129
130 // Thermal and Semantic camera use custom depth.
131 // RenderCustomDepth is set to false, if no Thermal or Semantic camera exists in the level,
132 // and is automatically set to true/false for all instances when they are spawned/destroyed.
133 // Doing this saves a lot rendering performance.
134 InstancedStaticMeshComponent->SetRenderCustomDepth(true);
135 InstancedStaticMeshComponent->SetCustomDepthStencilValue(StaticMeshComponent->CustomDepthStencilValue);
136
137 // Huge performance implication, recommend is Rigid (especially for Foliage with WPO).
138 // https://docs.unrealengine.com/5.3/en-US/PythonAPI/class/ShadowCacheInvalidationBehavior.html
139 InstancedStaticMeshComponent->ShadowCacheInvalidationBehavior = EShadowCacheInvalidationBehavior::Rigid;
140
141 const int32 NumberOfMaterials = StaticMeshComponent->GetNumMaterials();
142 TArray<UMaterialInterface*> MeshMaterials;
143 MeshMaterials.Reserve(NumberOfMaterials);
144 for (int32 i = 0; i < NumberOfMaterials; i++)
145 {
146 MeshMaterials.Add(StaticMeshComponent->GetMaterial(i));
147 }
148
151 InstancedActorParameters.Materials = MeshMaterials;
152 InstancedActorParameters.CustomDepthStencilValue = StaticMeshComponent->CustomDepthStencilValue;
153 InstancedActorParameters.AllowWorldPositionOffsetDisable = AllowWorldPositionOffsetDisable;
154 InstancedActorParameters.IsTree = InstancedActor->IsTreeActor();
155
156 // Set materials to newly created UInstancedStaticMeshComponent
157 const int32 MaterialCount = MeshMaterials.Num();
158 for (int32 MaterialIndex = 0; MaterialIndex < MaterialCount; ++MaterialIndex)
159 {
160 UMaterialInterface* MaterialInterface = InstancedActorParameters.Materials[MaterialIndex];
161
162 if (MaterialInterface)
163 {
164 InstancedStaticMeshComponent->SetMaterial(MaterialIndex, MaterialInterface);
165
166#if WITH_EDITOR
167 // Validate in Editor that material has bUsedWithInstancedStaticMeshes on
168 UMaterial* Material = Cast<UMaterial>(MaterialInterface);
169 if (Material)
170 {
171 if (!Material->bUsedWithInstancedStaticMeshes)
172 {
173 UE_LOG(LogTemp, Warning, TEXT("AInstancedRendererManager: Material '%s' bUsedWithInstancedStaticMeshes is not set to true!"), *Material->GetName());
174 }
175 }
176#endif
177 }
178 }
179
180 InstancedActor->InstanceAdded();
181 InstancedActor->Destroy();
182}
183
184bool AInstancedActorRenderer::SpawnInstanceBackToActor(UInstancedStaticMeshComponent* ISM, int32 Index, bool OnlyTree)
185{
186 if (!ISM || !InstancedStaticMeshComponent|| Index < 0 || Index >= ISM->GetInstanceCount())
187 {
188 return false;
189 }
190
192 {
193 return false;
194 }
195
196 UWorld* World = GetWorld();
197 if (!World)
198 {
199 return false;
200 }
201
202 if (OnlyTree && !InstancedActorParameters.IsTree)
203 {
204 return false;
205 }
206
207 UClass* ActorClass = InstancedActorParameters.ActorClass;
208 if (!ActorClass)
209 {
210 return false;
211 }
212
213 // Get the transform for the specified instance
214 FTransform InstanceTransform;
215 InstancedStaticMeshComponent->GetInstanceTransform(Index, InstanceTransform);
216
217 // Spawn the actor
218 AActor* SpawnedActor = World->SpawnActorDeferred<AActor>(ActorClass, InstanceTransform);
219 if (SpawnedActor)
220 {
221 AInstancedActor* InstancedActor = Cast<AInstancedActor>(SpawnedActor);
222 if (InstancedActor)
223 {
224 InstancedActor->AddToInstancedRenderer = false;
225 }
226
227 SpawnedActor->FinishSpawning(InstanceTransform);
228
229 // Remove this ISM instance.
230 return InstancedStaticMeshComponent->RemoveInstance(Index);
231 }
232
233 return true;
234}
235
237{
239 {
240 return InstancedStaticMeshComponent->GetStaticMesh();
241 }
242
243 return nullptr;
244}
245
247{
248 UWorld* World = GetWorld();
249
250 if (!World)
251 {
252 return false;
253 }
254
256 {
257 return false;
258 }
259
260 UClass* Class = InstancedActorParameters.ActorClass;
261 if (!Class)
262 {
263 UE_LOG(LogTemp, Warning, TEXT("AInstancedActorRenderer: No spawn class setup."));
264 return false;
265 }
266
267 InstancedStaticMeshComponent->SetVisibility(false);
268
269 // Extract transforms from ISM component into TArray
270 TArray<FTransform> Transforms;
271
272 const int32 InstanceCount = InstancedStaticMeshComponent->GetInstanceCount();
273
274 for (int32 i = 0; i < InstanceCount; i++)
275 {
276 FTransform Transform;
277 InstancedStaticMeshComponent->GetInstanceTransform(i, Transform);
278 Transforms.Add(Transform);
279 }
280
281 // Spawn actors to level
282 for (const FTransform& Transform : Transforms)
283 {
284 if (Class)
285 {
286 AActor* SpawnedActor = World->SpawnActorDeferred<AActor>(Class, Transform);
287 if (SpawnedActor)
288 {
289 SpawnedActor->FinishSpawning(Transform);
290 }
291 }
292 }
293
294 InstancedStaticMeshComponent->DestroyComponent();
295
296 return true;
297}
298
299bool AInstancedActorRenderer::Matches(UStaticMeshComponent* StaticMeshComponent, const bool CanHaveAlternativeMaterial) const
300{
301 if (!StaticMeshComponent)
302 {
303 return false;
304 }
305
306 UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
307 if (!StaticMesh)
308 {
309 return false;
310 }
311
312 if (!CanHaveAlternativeMaterial)
313 {
314 if (InstancedActorParameters.Mesh == StaticMesh)
315 {
316 return true;
317 }
318 }
319 else
320 {
321 const int32 NumberOfMaterials = StaticMeshComponent->GetNumMaterials();
322 TArray<UMaterialInterface*> MeshMaterials;
323 MeshMaterials.Reserve(NumberOfMaterials);
324 for (int32 i = 0; i < NumberOfMaterials; i++)
325 {
326 MeshMaterials.Add(StaticMeshComponent->GetMaterial(i));
327 }
328
329 if (InstancedActorParameters.Mesh == StaticMesh)
330 {
331 bool bMaterialsMatch = true;
332
333 for (int32 MaterialIndex = 0; MaterialIndex < InstancedActorParameters.Materials.Num(); ++MaterialIndex)
334 {
335 UMaterialInterface* UniqueMeshMaterial = InstancedActorParameters.Materials[MaterialIndex];
336 UMaterialInterface* MeshMaterial = MeshMaterials[MaterialIndex];
337 if (UniqueMeshMaterial != MeshMaterial)
338 {
339 bMaterialsMatch = false;
340 }
341 }
342 MeshMaterials.Empty();
343
344 if (bMaterialsMatch)
345 {
346 return true;
347 }
348 }
349 }
350
351 return false;
352}
353
354bool AInstancedActorRenderer::Matches(UInstancedStaticMeshComponent* ISM) const
355{
357 {
358 return false;
359 }
360
361 if (ISM->GetStaticMesh() == InstancedActorParameters.Mesh)
362 {
363 const int32 NumberOfMaterials = ISM->GetNumMaterials();
364 if (NumberOfMaterials != InstancedActorParameters.Materials.Num())
365 {
366 return false;
367 }
368
369 for (int32 i = 0; i < NumberOfMaterials; i++)
370 {
371 if (ISM->GetMaterial(i) != InstancedActorParameters.Materials[i])
372 {
373 return false;
374 }
375 }
376
377 return true;
378 }
379
380 return false;
381}
382
384{
385 if (WorldPositionOffsetDistance == NewWPODistance)
386 {
387 return;
388 }
389
390 WorldPositionOffsetDistance = NewWPODistance;
391
393 {
394 InstancedStaticMeshComponent->SetWorldPositionOffsetDisableDistance(WorldPositionOffsetDistance);
395 }
396}
397
398void AInstancedActorRenderer::SetShadowCacheBehaviour(EShadowCacheInvalidationBehavior ShadowCacheInvalidationBehaviour)
399{
401 {
402 InstancedStaticMeshComponent->ShadowCacheInvalidationBehavior = ShadowCacheInvalidationBehaviour;
403 }
404}
405
407{
409 {
410 InstancedStaticMeshComponent->SetEvaluateWorldPositionOffset(RenderWPO);
411 InstancedStaticMeshComponent->SetEvaluateWorldPositionOffsetInRayTracing(RenderWPO);
412 }
413}
414
416{
419}
420
422{
423 CurrentWeatherParameters = WeatherParameters;
424
425 bool IsWinter = WeatherParameters.IsWinterSeason();
426 bool IsWindy = WeatherParameters.WindIntensity > 0.05f;
427
428 // Should render tree wind (WPO)
429 bool ShouldRenderTreeWPO = IsWinter || IsWindy;
430
431 // Should render snow (WPO), like rocks etc.
432 bool ShouldRenderSnowWPO = WeatherParameters.SnowAmount != 0.0f;
433
434 bool ShouldRenderWPO = IsWinter || IsWindy;
435
436 // Only update if the rendering state has changed
437 if (RenderingWPO != ShouldRenderWPO)
438 {
439 RenderingWPO = ShouldRenderWPO;
440 SetRenderWorldPositionOffet(ShouldRenderWPO);
441 }
442}
AInstancedActorRenderer(const FObjectInitializer &ObjectInitializer)
void SetupFromInstancedActor(AInstancedActor *InstancedActor)
bool Matches(UStaticMeshComponent *StaticMeshComponent, const bool CanHaveAlternativeMaterial) const
void OnGraphicsSettingsChanged(FGlobalGraphicsSettings GraphicsSettings)
void OnWeatherParametersChanged(FWeatherParameters WeatherParameters)
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override
FWeatherParameters CurrentWeatherParameters
void SetShadowCacheBehaviour(EShadowCacheInvalidationBehavior ShadowCacheInvalidationBehaviour)
void SetRenderWorldPositionOffet(bool RenderWPO)
FInstancedActorParameters InstancedActorParameters
bool SpawnInstanceBackToActor(UInstancedStaticMeshComponent *ISM, int32 Index, bool OnlyTree)
void SetWorldPositionOffsetDistance(int32 NewWPODistance)
virtual void BeginPlay() override
UInstancedStaticMeshComponent * InstancedStaticMeshComponent
UStaticMesh * GetStaticMesh() const
int32 GetInstanceEndCullDistance() const
int32 GetInstanceStartCullDistance() const
UStaticMeshComponent * GetStaticMeshComponent() const
bool GetAllowWorldPositionOffsetDisable() const
bool IsTreeActor() const
const FWeatherParameters & GetCurrentWeather() const
Definition: Weather.h:76
FLevelEventDelegate_WeatherChanged OnWeatherChanged
Definition: Weather.h:91
int32 GetWorldPositionOffsetRenderDistance() const
FGraphicsSettingsDelegate OnGraphicsSettingsChanged
static UAgrarsenseSettings * GetAgrarsenseSettings()
static AWeather * GetWeatherActor(const UObject *WorldContextObject)
EShadowCacheInvalidationBehavior FoliageShadowCacheInvalidationBehaviour
TSubclassOf< AActor > ActorClass
TWeakObjectPtr< UStaticMesh > Mesh
TArray< UMaterialInterface * > Materials