IVSmoke 1.0
Loading...
Searching...
No Matches
IVSmokeHoleGeneratorComponent.cpp
1// Copyright (c) 2026, Team SDB. All rights reserved.
2
3#include "IVSmokeHoleGeneratorComponent.h"
4
5#include "Engine/TextureRenderTargetVolume.h"
6#include "Engine/Texture2D.h"
7#include "Engine/World.h"
8#include "GameFramework/GameStateBase.h"
9#include "GlobalShader.h"
10#include "IVSmoke.h"
11#include "IVSmokeHoleShaders.h"
12#include "IVSmokeHolePreset.h"
13#include "IVSmokePostProcessPass.h"
14#include "IVSmokeVoxelVolume.h"
15#include "Net/UnrealNetwork.h"
16#include "RHICommandList.h"
17#include "RHIStaticStates.h"
18#include "RenderGraphBuilder.h"
19#include "RenderGraphUtils.h"
20#include "RenderingThread.h"
21#include "TextureResource.h"
22
23UIVSmokeHoleGeneratorComponent::UIVSmokeHoleGeneratorComponent()
24 : bHoleTextureDirty(false)
25{
26 PrimaryComponentTick.bCanEverTick = true;
27 SetIsReplicatedByDefault(true);
28 SetGenerateOverlapEvents(true);
29}
30
31void UIVSmokeHoleGeneratorComponent::PostInitProperties()
32{
33 Super::PostInitProperties();
34
35 // Prevent projectiles from being blocked by this box
36 SetCollisionEnabled(ECollisionEnabled::QueryOnly);
37 SetCollisionResponseToAllChannels(ECR_Overlap);
38}
39
40void UIVSmokeHoleGeneratorComponent::BeginPlay()
41{
42 Super::BeginPlay();
43
44 // 1. Setup Fast TArray owner for replication callbacks
45 ActiveHoles.OwnerComponent = this;
46 ActiveHoles.Reserve(MaxHoles);
47
48 // Join process
49 if (ActiveHoles.Num() > 0)
50 {
52 }
53
54#if !UE_SERVER
55 Local_InitializeHoleTexture();
56#endif
57}
58
59void UIVSmokeHoleGeneratorComponent::TickComponent(const float DeltaTime, const ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
60{
61 Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
62
63 // 1. Server cleans up expired holes and updates dynamic objects
64 if (GetOwner()->HasAuthority())
65 {
66 Authority_CleanupExpiredHoles();
67 Authority_UpdateDynamicSubjectList();
68 }
69
70 // 2. All host update voxel volume area
72
73 // 3. If any holes exist, then must be updated
74 if (ActiveHoles.Num() > 0)
75 {
77 }
78
79 // 4. Client & Standalone rebuild texture
80#if !UE_SERVER
81 if (bHoleTextureDirty)
82 {
83 if (ActiveHoles.Num() > 0)
84 {
85 Local_RebuildHoleTexture();
86 }
87 else
88 {
89 Local_ClearHoleTexture();
90 }
92 }
93#endif
94}
95
96void UIVSmokeHoleGeneratorComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
97{
98 Super::EndPlay(EndPlayReason);
99}
100
101void UIVSmokeHoleGeneratorComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
102{
103 Super::GetLifetimeReplicatedProps(OutLifetimeProps);
104 DOREPLIFETIME(UIVSmokeHoleGeneratorComponent, ActiveHoles);
105}
106
107//~============================================================================
108// Public API (Blueprint & C++)
109#pragma region API
111{
112 // 1. Clear all active holes
113 ActiveHoles.Empty();
114
115 // 2. Clear all dynamic subjects
116 DynamicSubjectList.Empty();
117
118 // 3. Clear hole texture
119#if !UE_SERVER
120 Local_ClearHoleTexture();
121#endif
122
124}
125#pragma endregion
126
127//~============================================================================
128// Public API Called by UIVSmokeHoleRequestComponent
129#pragma region API
130void UIVSmokeHoleGeneratorComponent::CreatePenetrationHole(const FVector3f& InOrigin, const FVector3f& InDirection, const uint8 PresetID)
131{
132 const TObjectPtr<UIVSmokeHolePreset> Preset = UIVSmokeHolePreset::FindByID(PresetID);
133 if (!Preset)
134 {
135 UE_LOG(LogIVSmoke, Warning, TEXT("[CreatePenetrationHole] Invalid PresetID: %d"), PresetID);
136 return;
137 }
138
139 if (Preset->Duration <= 0.0f)
140 {
141 UE_LOG(LogIVSmoke, Warning, TEXT("[CreatePenetrationHole] Invalid Lifetime: %f"), Preset->Duration);
142 return;
143 }
144
145 if (Preset->HoleType != EIVSmokeHoleType::Penetration)
146 {
147 UE_LOG(LogIVSmoke, Warning, TEXT("[CreatePenetrationHole] Preset is not Penetration type"));
148 return;
149 }
150
151 // 1. Check whether it passes through the smoke volume
152 FVector3f EntryPoint, ExitPoint;
153 if (!Authority_CalculatePenetrationPoints(InOrigin, InDirection, Preset->BulletThickness, EntryPoint, ExitPoint))
154 {
155 return;
156 }
157
158 // 2. Create Hole
159 FIVSmokeHoleData HoleData;
160 HoleData.Position = EntryPoint;
161 HoleData.EndPosition = ExitPoint;
162 HoleData.PresetID = PresetID;
163 HoleData.ExpirationServerTime = GetSyncedTime() + Preset->Duration;
164 Authority_CreateHole(HoleData);
165}
166
167void UIVSmokeHoleGeneratorComponent::CreateExplosionHole(const FVector3f& Origin, const uint8 PresetID)
168{
169 const TObjectPtr<UIVSmokeHolePreset> Preset = UIVSmokeHolePreset::FindByID(PresetID);
170 if (!Preset)
171 {
172 UE_LOG(LogIVSmoke, Warning, TEXT("[CreateExplosionHole] Invalid PresetID: %d"), PresetID);
173 return;
174 }
175
176 if (Preset->Duration <= 0.0f)
177 {
178 UE_LOG(LogIVSmoke, Warning, TEXT("[CreateExplosionHole] Invalid Lifetime: %f"), Preset->Duration);
179 return;
180 }
181
182 if (Preset->HoleType != EIVSmokeHoleType::Explosion)
183 {
184 UE_LOG(LogIVSmoke, Warning, TEXT("[CreateExplosionHole] Preset is not Explosion type"));
185 return;
186 }
187
188 // 1. Check whether the smoke volume intersects with the explosion
189 const FBox3f VolumeBox = FBox3f(Bounds.GetBox());
190 const FVector3f ExpandedMin = VolumeBox.Min - FVector3f(Preset->Radius);
191 const FVector3f ExpandedMax = VolumeBox.Max + FVector3f(Preset->Radius);
192 if (const FBox3f ExpandedBox(ExpandedMin, ExpandedMax); !ExpandedBox.IsInside(Origin))
193 {
194 return;
195 }
196
197 // 2. Create Hole
198 FIVSmokeHoleData HoleData;
199 HoleData.Position = Origin;
200 HoleData.EndPosition = Origin;
201 HoleData.PresetID = PresetID;
202 HoleData.ExpirationServerTime = GetSyncedTime() + Preset->Duration;
203 Authority_CreateHole(HoleData);
204}
205
206void UIVSmokeHoleGeneratorComponent::RegisterTrackDynamicHole(AActor* TargetActor, const uint8 PresetID)
207{
208 const TObjectPtr<UIVSmokeHolePreset> Preset = UIVSmokeHolePreset::FindByID(PresetID);
209 if (!Preset)
210 {
211 UE_LOG(LogIVSmoke, Warning, TEXT("[RegisterTrackDynamicHole] Invalid PresetID: %d"), PresetID);
212 return;
213 }
214
215 if (Preset->Duration <= 0.0f)
216 {
217 UE_LOG(LogIVSmoke, Warning, TEXT("[RegisterTrackDynamicHole] Invalid Duration: %f"), Preset->Duration);
218 return;
219 }
220
221 if (Preset->HoleType != EIVSmokeHoleType::Dynamic)
222 {
223 UE_LOG(LogIVSmoke, Warning, TEXT("[RegisterTrackDynamicHole] Preset is not Dynamic type"));
224 return;
225 }
226
227 // 1. Check if already registered
228 for (const FIVSmokeHoleDynamicSubject& Tracker : DynamicSubjectList)
229 {
230 if (Tracker.TargetActor == TargetActor)
231 {
232 UE_LOG(LogIVSmoke, Warning, TEXT("[RegisterTrackDynamicHole] Actor already registered"));
233 return;
234 }
235 }
236
237 // 2. Register new subject
238 FIVSmokeHoleDynamicSubject NewDynamicSubject;
239 NewDynamicSubject.TargetActor = TargetActor;
240 NewDynamicSubject.PresetID = PresetID;
241 NewDynamicSubject.LastWorldPosition = FVector3f(TargetActor->GetActorLocation());
242 NewDynamicSubject.LastWorldRotation = TargetActor->GetActorQuat();
243 DynamicSubjectList.Add(NewDynamicSubject);
244}
245#pragma endregion
246
247//~============================================================================
248// Authority Only
249#pragma region Authority Only
250void UIVSmokeHoleGeneratorComponent::Authority_CreateHole(const FIVSmokeHoleData& HoleData)
251{
252 if (ActiveHoles.Num() < MaxHoles)
253 {
254 ActiveHoles.AddHole(HoleData);
255 }
256 else
257 {
258 int32 OldestIndex = 0;
259 float MinTime = FLT_MAX;
260
261 for (int32 i = 0; i < ActiveHoles.Num(); ++i)
262 {
263 if (ActiveHoles[i].ExpirationServerTime < MinTime)
264 {
265 MinTime = ActiveHoles[i].ExpirationServerTime;
266 OldestIndex = i;
267 }
268 }
269
270 FIVSmokeHoleData& Target = ActiveHoles[OldestIndex];
271 Target.Position = HoleData.Position;
272 Target.EndPosition = HoleData.EndPosition;
274 Target.PresetID = HoleData.PresetID;
275 ActiveHoles.MarkItemDirty(Target);
276 }
277
279}
280
281void UIVSmokeHoleGeneratorComponent::Authority_CleanupExpiredHoles()
282{
283 const float CurrentServerTime = GetSyncedTime();
284
285 for (int32 i = ActiveHoles.Num() - 1; i >= 0; --i)
286 {
287 if (ActiveHoles[i].IsExpired(CurrentServerTime))
288 {
289 ActiveHoles.RemoveAtSwap(i);
291 }
292 }
293}
294
295bool UIVSmokeHoleGeneratorComponent::Authority_CalculatePenetrationPoints(
296 const FVector3f& Origin, const FVector3f& Direction, const float BulletThickness, FVector3f& OutEntry, FVector3f& OutExit)
297{
298 FVector3f NormalizedDirection = Direction.GetSafeNormal();
299 if (NormalizedDirection.IsNearlyZero())
300 {
301 UE_LOG(LogIVSmoke, Warning, TEXT("[CalculatePenetrationPoints] Direction is zero"));
302 return false;
303 }
304
305 const float DistToCenter = FVector3f::Dist(Origin, FVector3f(GetComponentLocation()));
306 const float DiagonalLength = GetScaledBoxExtent().Size() * 2.0f;
307 const float MaxDistance = DistToCenter + DiagonalLength;
308
309 const FVector3f RayEnd = Origin + NormalizedDirection * MaxDistance;
310
311 FHitResult HitEntry, HitExit;
312 FCollisionQueryParams QueryParams;
313 QueryParams.bTraceComplex = false;
314
315 // 1. Forward trace (Origin -> RayEnd) to find Entry point
316 if (!LineTraceComponent(HitEntry, FVector(Origin), FVector(RayEnd), QueryParams))
317 {
318 return false;
319 }
320
321 // 2. Reverse trace (RayEnd -> Origin) to find Exit point
322 if (!LineTraceComponent(HitExit, FVector(RayEnd), FVector(Origin), QueryParams))
323 {
324 OutExit = FVector3f(HitEntry.Location);
325 }
326 else
327 {
328 OutExit = FVector3f(HitExit.Location);
329 }
330
331 OutEntry = FVector3f(HitEntry.Location);
332
333 // 3. Obstacle detection using SphereTrace between Entry and Exit
334 if (ObstacleObjectTypes.Num() > 0)
335 {
336 TArray<FHitResult> HitResults;
337 FCollisionQueryParams WorldParams;
338 WorldParams.AddIgnoredComponent(this);
339 const FCollisionShape SweepShape = FCollisionShape::MakeSphere(BulletThickness);
340 const FCollisionObjectQueryParams ObjectParams(ObstacleObjectTypes);
341
342 if (GetWorld()->SweepMultiByObjectType(
343 HitResults, FVector(OutEntry), FVector(OutExit), FQuat::Identity,
344 ObjectParams, SweepShape, WorldParams))
345 {
346 for (const FHitResult& Hit : HitResults)
347 {
348 if (AActor* HitActor = Hit.GetActor())
349 {
350 if (!HitActor->ActorHasTag(IVSmokeVoxelVolumeTag))
351 {
352 OutExit = FVector3f(Hit.Location);
353 break;
354 }
355 }
356 }
357 }
358 }
359
360 return true;
361}
362
363void UIVSmokeHoleGeneratorComponent::Authority_UpdateDynamicSubjectList()
364{
365 const float CurrentTime = GetSyncedTime();
366 const FBox3f SmokeVolume = FBox3f(Bounds.GetBox());
367
368 for (int32 i = DynamicSubjectList.Num() - 1; i >= 0; --i)
369 {
370 FIVSmokeHoleDynamicSubject& DynamicSubject = DynamicSubjectList[i];
371
372 // 0. Delete if object is not alive
373 if (!DynamicSubject.TargetActor.IsValid())
374 {
375 DynamicSubjectList.RemoveAtSwap(i);
376 continue;
377 }
378
379 const TObjectPtr<AActor> Actor = DynamicSubject.TargetActor.Get();
380 const TObjectPtr<UIVSmokeHolePreset> Preset = UIVSmokeHolePreset::FindByID(DynamicSubject.PresetID);
381
382 // 0. Delete if preset is not valid
383 if (!Preset)
384 {
385 DynamicSubjectList.RemoveAtSwap(i);
386 continue;
387 }
388
389 const FVector3f CurrentPos = FVector3f(Actor->GetActorLocation());
390 const FVector3f LastPos = FVector3f(DynamicSubject.LastWorldPosition);
391
392 if (!SmokeVolume.IsInside(CurrentPos))
393 {
394 continue;
395 }
396
397 // 1. Ignore if object moves a little bit
398 if (Preset->DistanceThreshold > FVector3f::Dist(CurrentPos, LastPos))
399 {
400 continue;
401 }
402
403 // 2. Create Hole
404 FIVSmokeHoleData HoleData;
405 HoleData.Position = LastPos;
406 HoleData.EndPosition = CurrentPos;
407 HoleData.PresetID = DynamicSubject.PresetID;
408 HoleData.ExpirationServerTime = CurrentTime + Preset->Duration;
409 Authority_CreateHole(HoleData);
410
411 // 3. Update registered object
412 DynamicSubject.LastWorldPosition = CurrentPos;
413 DynamicSubject.LastWorldRotation = Actor->GetActorQuat();
414 }
415}
416#pragma endregion
417
418//~============================================================================
419// Local Only
420#pragma region Local Only
421#if !UE_SERVER
422void UIVSmokeHoleGeneratorComponent::Local_InitializeHoleTexture()
423{
424 if (VoxelResolution.X <= 0 || VoxelResolution.Y <= 0 || VoxelResolution.Z <= 0)
425 {
426 return;
427 }
428
429 // Create UTextureRenderTargetVolume
430 HoleTexture = NewObject<UTextureRenderTargetVolume>(this, TEXT("HoleTexture"));
431 HoleTexture->bCanCreateUAV = true;
432 HoleTexture->Init(VoxelResolution.X, VoxelResolution.Y, VoxelResolution.Z, PF_FloatRGBA);
433 HoleTexture->ClearColor = FLinearColor::White;
434 HoleTexture->SRGB = false;
435 HoleTexture->UpdateResourceImmediate(true);
436}
437
438void UIVSmokeHoleGeneratorComponent::Local_ClearHoleTexture()
439{
440 if (!HoleTexture)
441 {
442 return;
443 }
444
445 FTextureRenderTargetResource* RenderTargetResource = HoleTexture->GameThread_GetRenderTargetResource();
446 if (!RenderTargetResource)
447 {
448 return;
449 }
450
451 FTextureRHIRef Texture = RenderTargetResource->GetRenderTargetTexture();
452 if (!Texture.IsValid())
453 {
454 return;
455 }
456
457 ENQUEUE_RENDER_COMMAND(IVSmokeHoleClear)(
458 [Texture](FRHICommandListImmediate& RHICmdList)
459 {
460 FRDGBuilder GraphBuilder(RHICmdList);
461
462 const FRDGTextureRef RDGTexture = GraphBuilder.RegisterExternalTexture(
463 CreateRenderTarget(Texture, TEXT("IVSmokeHoleTextureClear"))
464 );
465
466 // Clear to white (1,1,1,1) = no holes = full smoke density
467 AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(RDGTexture), FVector4f(1.0f, 1.0f, 1.0f, 1.0f));
468
469 GraphBuilder.Execute();
470 }
471 );
472}
473
474void UIVSmokeHoleGeneratorComponent::Local_RebuildHoleTexture()
475{
476 if (IsRunningCommandlet())
477 {
478 return;
479 }
480
481 if (!HoleTexture)
482 {
483 return;
484 }
485
486 const float SyncedTime = GetSyncedTime();
487 if (SyncedTime == 0.0f)
488 {
489 return;
490 }
491
492 if (HoleTexture->SizeX != VoxelResolution.X ||
493 HoleTexture->SizeY != VoxelResolution.Y ||
494 HoleTexture->SizeZ != VoxelResolution.Z)
495 {
496 Local_InitializeHoleTexture();
497 return;
498 }
499
500 const FTextureRenderTargetResource* RenderTargetResource = HoleTexture->GameThread_GetRenderTargetResource();
501 if (!RenderTargetResource)
502 {
503 return;
504 }
505
506 TArray<FIVSmokeHoleGPU> GPUHoles = ActiveHoles.GetHoleGPUData(SyncedTime);
507
508 const TObjectPtr<AIVSmokeVoxelVolume> VoxelVolume = Cast<AIVSmokeVoxelVolume>(GetOwner());
509 if (VoxelVolume == nullptr)
510 {
511 return;
512 }
513
514 FTextureRHIRef Texture = RenderTargetResource->GetRenderTargetTexture();
515 if (!Texture.IsValid())
516 {
517 return;
518 }
519
520 const FVector3f WorldVolumeMin = FVector3f(VoxelVolume->GetVoxelWorldAABBMin());
521 const FVector3f WorldVolumeMax = FVector3f(VoxelVolume->GetVoxelWorldAABBMax());
522 const FIntVector Resolution = VoxelResolution;
523 const int32 NumHoles = ActiveHoles.Num();
524 const int32 CapturedBlurStep = BlurStep;
525
526 // Capture noise settings for render thread
527 FTextureRHIRef PenetrationNoiseTextureRHI = PenetrationNoise.Texture && PenetrationNoise.Texture->GetResource()
528 ? PenetrationNoise.Texture->GetResource()->TextureRHI : nullptr;
529 FTextureRHIRef ExplosionNoiseTextureRHI = ExplosionNoise.Texture && ExplosionNoise.Texture->GetResource()
530 ? ExplosionNoise.Texture->GetResource()->TextureRHI : nullptr;
531 FTextureRHIRef DynamicNoiseTextureRHI = DynamicNoise.Texture && DynamicNoise.Texture->GetResource()
532 ? DynamicNoise.Texture->GetResource()->TextureRHI : nullptr;
533
534 const float CapturedPenetrationNoiseStrength = PenetrationNoise.Strength;
535 const float CapturedPenetrationNoiseScale = PenetrationNoise.Scale;
536 const float CapturedExplosionNoiseStrength = ExplosionNoise.Strength;
537 const float CapturedExplosionNoiseScale = ExplosionNoise.Scale;
538 const float CapturedDynamicNoiseStrength = DynamicNoise.Strength;
539 const float CapturedDynamicNoiseScale = DynamicNoise.Scale;
540
541 ENQUEUE_RENDER_COMMAND(IVSmokeHoleCarveFullRebuild)(
542 [Texture, GPUHoles = MoveTemp(GPUHoles), WorldVolumeMin, WorldVolumeMax, Resolution, NumHoles, CapturedBlurStep,
543 PenetrationNoiseTextureRHI, ExplosionNoiseTextureRHI, DynamicNoiseTextureRHI,
544 CapturedPenetrationNoiseStrength, CapturedPenetrationNoiseScale,
545 CapturedExplosionNoiseStrength, CapturedExplosionNoiseScale,
546 CapturedDynamicNoiseStrength, CapturedDynamicNoiseScale]
547 (FRHICommandListImmediate& RHICmdList)
548 {
549 FRDGBuilder GraphBuilder(RHICmdList);
550
551 const FRDGTextureRef RDGTexture = GraphBuilder.RegisterExternalTexture(
552 CreateRenderTarget(Texture, TEXT("IVSmokeHoleTexture"))
553 );
554
555 const FRDGBufferRef HoleBuffer = CreateStructuredBuffer(
556 GraphBuilder,
557 TEXT("IVSmokeHoleBuffer"),
558 sizeof(FIVSmokeHoleGPU),
559 GPUHoles.Num(),
560 GPUHoles.GetData(),
561 sizeof(FIVSmokeHoleGPU) * GPUHoles.Num()
562 );
563
564 // ============================================================================
565 // Pass 1: Hole Carve
566 // ============================================================================
567 FIVSmokeHoleCarveCS::FParameters* CarveParameters = GraphBuilder.AllocParameters<FIVSmokeHoleCarveCS::FParameters>();
568 CarveParameters->VolumeTexture = GraphBuilder.CreateUAV(RDGTexture);
569 CarveParameters->HoleBuffer = GraphBuilder.CreateSRV(HoleBuffer);
570 CarveParameters->VolumeMin = WorldVolumeMin;
571 CarveParameters->VolumeMax = WorldVolumeMax;
572 CarveParameters->Resolution = Resolution;
573 CarveParameters->NumHoles = NumHoles;
574
575 // Noise textures (use GWhiteTexture as fallback for null textures)
576 CarveParameters->PenetrationNoiseTexture = PenetrationNoiseTextureRHI ? PenetrationNoiseTextureRHI : GWhiteTexture->TextureRHI;
577 CarveParameters->ExplosionNoiseTexture = ExplosionNoiseTextureRHI ? ExplosionNoiseTextureRHI : GWhiteTexture->TextureRHI;
578 CarveParameters->DynamicNoiseTexture = DynamicNoiseTextureRHI ? DynamicNoiseTextureRHI : GWhiteTexture->TextureRHI;
579 CarveParameters->NoiseSampler = TStaticSamplerState<SF_Bilinear, AM_Wrap, AM_Wrap, AM_Wrap>::GetRHI();
580
581 // Noise parameters
582 CarveParameters->PenetrationNoiseStrength = CapturedPenetrationNoiseStrength;
583 CarveParameters->PenetrationNoiseScale = CapturedPenetrationNoiseScale;
584 CarveParameters->ExplosionNoiseStrength = CapturedExplosionNoiseStrength;
585 CarveParameters->ExplosionNoiseScale = CapturedExplosionNoiseScale;
586 CarveParameters->DynamicNoiseStrength = CapturedDynamicNoiseStrength;
587 CarveParameters->DynamicNoiseScale = CapturedDynamicNoiseScale;
588
589 const TShaderMapRef<FIVSmokeHoleCarveCS> CarveShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
590 FIVSmokePostProcessPass::AddComputeShaderPass<FIVSmokeHoleCarveCS>(GraphBuilder, GetGlobalShaderMap(GMaxRHIFeatureLevel), CarveShader, CarveParameters, Resolution);
591
592 // ============================================================================
593 // Pass 2-4: Separable Gaussian Blur (X, Y, Z)
594 // ============================================================================
595 if (CapturedBlurStep > 0)
596 {
597 // Create ping-pong texture for blur
598 const FRDGTextureDesc BlurTexDesc = FRDGTextureDesc::Create3D(
599 FIntVector(Resolution.X, Resolution.Y, Resolution.Z),
600 PF_FloatRGBA,
601 FClearValueBinding::Black,
602 TexCreate_ShaderResource | TexCreate_UAV
603 );
604 const FRDGTextureRef BlurTempTexture = GraphBuilder.CreateTexture(BlurTexDesc, TEXT("IVSmokeHoleBlurTemp"));
605
606 const TShaderMapRef<FIVSmokeHoleBlurCS> BlurShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
607 FRHISamplerState* LinearClampSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
608
609 // Blur directions
610 const FIntVector BlurDirections[3] = {
611 FIntVector(1, 0, 0), // X
612 FIntVector(0, 1, 0), // Y
613 FIntVector(0, 0, 1) // Z
614 };
615
616 const FRDGTextureRef PingPong[2] = { RDGTexture, BlurTempTexture };
617 int32 CurrentInput = 0;
618
619 for (int32 i = 0; i < 3; ++i)
620 {
621 FIVSmokeHoleBlurCS::FParameters* BlurParameters = GraphBuilder.AllocParameters<FIVSmokeHoleBlurCS::FParameters>();
622 BlurParameters->InputTexture = GraphBuilder.CreateSRV(PingPong[CurrentInput]);
623 BlurParameters->InputSampler = LinearClampSampler;
624 BlurParameters->OutputTexture = GraphBuilder.CreateUAV(PingPong[1 - CurrentInput]);
625 BlurParameters->Resolution = Resolution;
626 BlurParameters->BlurDirection = BlurDirections[i];
627 BlurParameters->BlurStep = CapturedBlurStep;
628
630 GetGlobalShaderMap(GMaxRHIFeatureLevel), BlurShader, BlurParameters, Resolution);
631
632 CurrentInput = 1 - CurrentInput;
633 }
634
635 // If final result is in BlurTempTexture (CurrentInput == 1), copy back to RDGTexture
636 if (CurrentInput == 1)
637 {
638 AddCopyTexturePass(GraphBuilder, BlurTempTexture, RDGTexture, FRHICopyTextureInfo());
639 }
640 }
641
642 GraphBuilder.Execute();
643 }
644 );
645}
646#endif
647#pragma endregion
648
649//~============================================================================
650// Common
651#pragma region Common
653{
654 const UWorld* World = GetWorld();
655 if (!World)
656 {
657 return 0.0f;
658 }
659
660 if (World->GetNetMode() == NM_Client) // Only Client
661 {
662 if (const AGameStateBase* GameState = World->GetGameState())
663 {
664 return GameState->GetServerWorldTimeSeconds();
665 }
666 }
667
668 return World->GetTimeSeconds(); // Server & StandAlone
669}
670
671#if !UE_SERVER
673{
674 if (const TObjectPtr<AIVSmokeVoxelVolume> VoxelVolume = Cast<AIVSmokeVoxelVolume>(GetOwner()))
675 {
676 const FVector WorldVoxelAABBMin = VoxelVolume->GetVoxelWorldAABBMin();
677 const FVector WorldVoxelAABBMax = VoxelVolume->GetVoxelWorldAABBMax();
678 const FVector Extent = (WorldVoxelAABBMax - WorldVoxelAABBMin) * 0.5f;
679 const FVector WorldVoxelCenter = (WorldVoxelAABBMax + WorldVoxelAABBMin) * 0.5f;
680
681 SetWorldLocation(WorldVoxelCenter);
682 SetBoxExtent(Extent, false);
683 }
684}
685
687{
688 if (HoleTexture)
689 {
690 if (const FTextureRenderTargetResource* Resource = HoleTexture->GameThread_GetRenderTargetResource())
691 {
692 return Resource->GetRenderTargetTexture();
693 }
694 }
695
696 return nullptr;
697}
698#endif
699
700#pragma endregion
static void AddComputeShaderPass(FRDGBuilder &GraphBuilder, FGlobalShaderMap *ShaderMap, TShaderMapRef< TShaderClass > ComputeShader, typename TShaderClass::FParameters *Parameters, const FIntVector &TotalThreadSize)
Component that generates hole texture for volumetric smoke. Provides public API for penetration and e...
void CreateExplosionHole(const FVector3f &Origin, const uint8 PresetID)
TArray< TEnumAsByte< EObjectTypeQuery > > ObstacleObjectTypes
void CreatePenetrationHole(const FVector3f &Origin, const FVector3f &Direction, const uint8 PresetID)
FORCEINLINE void MarkHoleTextureDirty(const bool bIsDirty=true)
void RegisterTrackDynamicHole(AActor *TargetActor, const uint8 PresetID)
static TObjectPtr< UIVSmokeHolePreset > FindByID(const uint8 InPresetID)
void RemoveAtSwap(const int32 Index)
void AddHole(const FIVSmokeHoleData &NewHole)
FORCEINLINE void Reserve(const int32 Number)
TArray< FIVSmokeHoleGPU > GetHoleGPUData(const float CurrentServerTime) const
TObjectPtr< UIVSmokeHoleGeneratorComponent > OwnerComponent
FORCEINLINE int32 Num() const
Network-optimized hole data structure.
Dynamic hole generated type data structure.
TWeakObjectPtr< AActor > TargetActor
Built from FIVSmokeHoleData + UIVSmokeHolePreset at render time.
TObjectPtr< UTexture2D > Texture