IVSmoke 1.0
Loading...
Searching...
No Matches
IVSmokeCollisionComponent.cpp
1// Copyright (c) 2026, Team SDB. All rights reserved.
2
3#include "IVSmokeCollisionComponent.h"
4
5#include "IVSmoke.h"
6#include "IVSmokeGridLibrary.h"
7#include "PhysicsEngine/BodySetup.h"
8
9#if WITH_EDITOR
10#include "Editor.h"
11#endif
12
13DECLARE_CYCLE_STAT(TEXT("Update Collision"), STAT_IVSmoke_UpdateCollision, STATGROUP_IVSmoke)
14DECLARE_CYCLE_STAT(TEXT("Update Collision With Octree"), STAT_IVSmoke_UpdateCollisionWithOctree, STATGROUP_IVSmoke)
15DECLARE_CYCLE_STAT(TEXT("Rebuild Physics Geometry"), STAT_IVSmoke_RebuildPhysicsGeometry, STATGROUP_IVSmoke)
16
17//~==============================================================================
18// Component Lifecycle
19#pragma region Lifecycle
20
21UIVSmokeCollisionComponent::UIVSmokeCollisionComponent()
22{
23 PrimaryComponentTick.bCanEverTick = false;
24
25 SetGenerateOverlapEvents(false);
26
27 Mobility = EComponentMobility::Movable;
28
29 BodyInstance.SetCollisionProfileName(UCollisionProfile::CustomCollisionProfileName);
30
31 BodyInstance.SetCollisionEnabled(ECollisionEnabled::QueryOnly);
32
33 BodyInstance.SetObjectType(ECC_WorldDynamic);
34
35 BodyInstance.SetResponseToAllChannels(ECR_Ignore);
36
37 BodyInstance.SetResponseToChannel(ECC_Visibility, ECR_Block);
38}
39
40UBodySetup* UIVSmokeCollisionComponent::GetBodySetup()
41{
42#if WITH_EDITOR
43 // Prevent BodyInstanceCustomization crash when editor-world actor is selected during PIE
44 if (GEditor && GEditor->IsPlaySessionInProgress())
45 {
46 const UWorld* World = GetWorld();
47 if (World && World->WorldType == EWorldType::Editor)
48 {
49 return nullptr;
50 }
51 }
52#endif
53
54 if (!VoxelBodySetup)
55 {
56 VoxelBodySetup = NewObject<UBodySetup>(this, NAME_None, RF_Transient);
57 VoxelBodySetup->CollisionTraceFlag = CTF_UseSimpleAsComplex;
58 VoxelBodySetup->bNeverNeedsCookedCollisionData = true;
59 }
60 return VoxelBodySetup;
61}
62
63void UIVSmokeCollisionComponent::OnCreatePhysicsState()
64{
65 GetBodySetup();
66
67 Super::OnCreatePhysicsState();
68}
69
70void UIVSmokeCollisionComponent::TryUpdateCollision(const TArray<uint64>& VoxelBitArray, const FIntVector& GridResolution, float VoxelSize, int32 ActiveVoxelNum, float SyncTime, bool bForce)
71{
72 if (GetCollisionEnabled() == ECollisionEnabled::NoCollision)
73 {
74 if (VoxelBodySetup && VoxelBodySetup->AggGeom.BoxElems.Num() > 0)
75 {
77 }
78 return;
79 }
80
81 if (!bForce)
82 {
83 if (LastSyncTime > 0.0f && (SyncTime - LastSyncTime) < MinCollisionUpdateInterval)
84 {
85 return;
86 }
87
88 int32 Diff = FMath::Abs(ActiveVoxelNum - LastActiveVoxelNum);
89
91 {
92 return;
93 }
94 }
95
96 UpdateCollision(VoxelBitArray, GridResolution, VoxelSize);
97
98 LastSyncTime = SyncTime;
99 LastActiveVoxelNum = ActiveVoxelNum;
100}
101
102#pragma endregion
103
104//~==============================================================================
105// Collision Management
106#pragma region Collision
107
108void UIVSmokeCollisionComponent::UpdateCollision(const TArray<uint64>& VoxelBitArray, const FIntVector& GridResolution, float VoxelSize)
109{
110 SCOPE_CYCLE_COUNTER(STAT_IVSmoke_UpdateCollision);
111
112 TRACE_CPUPROFILER_EVENT_SCOPE_TEXT("IVSmoke::UIVSmokeCollisionComponent::UpdateCollision");
113
114 UBodySetup* BodySetup = GetBodySetup();
115 if (!BodySetup)
116 {
117 return;
118 }
119
120 BodySetup->AggGeom.EmptyElements();
121
122 TArray<uint64> TempVoxelBitArray = VoxelBitArray;
123
124 const int32 ResolutionY = GridResolution.Y;
125 const int32 ResolutionZ = GridResolution.Z;
126
127 const FIntVector CenterOffset = GridResolution / 2;
128
129 const float VoxelExtent = VoxelSize * 0.5f;
130
131 for (int32 Z = 0; Z < ResolutionZ; ++Z)
132 {
133 for (int32 Y = 0; Y < ResolutionY; ++Y)
134 {
135 const int32 Index = UIVSmokeGridLibrary::GridToVoxelBitIndex(Y, Z, ResolutionY);
136
137 uint64& CurrentRow = TempVoxelBitArray[Index];
138 while (CurrentRow)
139 {
140 const int32 BeginX = FMath::CountTrailingZeros64(CurrentRow);
141
142 const uint64 Shifted = CurrentRow >> BeginX;
143
144 const int32 Width = (Shifted == MAX_uint64) ? (64 - BeginX) : FMath::CountTrailingZeros64(~Shifted);
145
146 const uint64 Mask = (Width == 64) ? MAX_uint64 : ((1ULL << Width) - 1ULL) << BeginX;
147
148 int32 Height = 1;
149 for (int32 NextY = Y + 1; NextY < ResolutionY; ++NextY)
150 {
151 const int32 NextIndex = UIVSmokeGridLibrary::GridToVoxelBitIndex(NextY, Z, ResolutionY);
152
153 const uint64& NextRow = TempVoxelBitArray[NextIndex];
154 if ((NextRow & Mask) == Mask)
155 {
156 ++Height;
157 }
158 else
159 {
160 break;
161 }
162 }
163
164 int32 Depth = 1;
165 for (int32 NextZ = Z + 1; NextZ < ResolutionZ; ++NextZ)
166 {
167 bool bCanExpand = true;
168 for (int32 H = 0; H < Height; ++H)
169 {
170 const int32 NextIndex = UIVSmokeGridLibrary::GridToVoxelBitIndex(Y + H, NextZ, ResolutionY);
171
172 const uint64& NextRow = TempVoxelBitArray[NextIndex];
173 if ((NextRow & Mask) != Mask)
174 {
175 bCanExpand = false;
176 break;
177 }
178 }
179
180 if (bCanExpand)
181 {
182 ++Depth;
183 }
184 else
185 {
186 break;
187 }
188 }
189
190 for (int32 D = 0; D < Depth; ++D)
191 {
192 for (int32 H = 0; H < Height; ++H)
193 {
194 const int32 NextIndex = UIVSmokeGridLibrary::GridToVoxelBitIndex(Y + H, Z + D, ResolutionY);
195
196 TempVoxelBitArray[NextIndex] &= ~Mask;
197 }
198 }
199
200 FKBoxElem Box;
201
202 FIntVector BeginGridPos(BeginX, Y, Z);
203 FVector BeginVoxelCenter = UIVSmokeGridLibrary::GridToLocal(BeginGridPos, VoxelSize, CenterOffset);
204 FVector CenterShift((Width - 1) * VoxelExtent, (Height - 1) * VoxelExtent, (Depth - 1) * VoxelExtent);
205 Box.Center = BeginVoxelCenter + CenterShift;
206
207 Box.X = Width * VoxelSize;
208 Box.Y = Height * VoxelSize;
209 Box.Z = Depth * VoxelSize;
210 Box.Rotation = FRotator::ZeroRotator;
211
212 BodySetup->AggGeom.BoxElems.Add(Box);
213 }
214 }
215 }
216
217 FinalizePhysicsUpdate();
218}
219
221{
222 if (VoxelBodySetup)
223 {
224 VoxelBodySetup->AggGeom.EmptyElements();
225 VoxelBodySetup->InvalidatePhysicsData();
226 VoxelBodySetup->CreatePhysicsMeshes();
227 }
228
229 RecreatePhysicsState();
230}
231
232void UIVSmokeCollisionComponent::FinalizePhysicsUpdate()
233{
234 if (!VoxelBodySetup)
235 {
236 return;
237 }
238
239 VoxelBodySetup->InvalidatePhysicsData();
240 VoxelBodySetup->CreatePhysicsMeshes();
241
242 RecreatePhysicsState();
243}
244
246{
247#if WITH_EDITOR
248 if (!bDebugEnabled)
249 {
250 return;
251 }
252
253 UWorld* World = GetWorld();
254 if (!World)
255 {
256 return;
257 }
258
259 if (!VoxelBodySetup)
260 {
261 return;
262 }
263
264 FTransform ComponentTrans = GetComponentTransform();
265
266 const float GapScale = 0.95f;
267
268 for (const FKBoxElem& Box : VoxelBodySetup->AggGeom.BoxElems)
269 {
270 FVector WorldCenter = ComponentTrans.TransformPosition(Box.Center);
271
272 FVector Extent(Box.X * 0.5f * GapScale, Box.Y * 0.5f * GapScale, Box.Z * 0.5f * GapScale);
273
274 FQuat WorldRotation = ComponentTrans.GetRotation() * Box.Rotation.Quaternion();
275
276 uint32 Hash = GetTypeHash(Box.Center);
277 FRandomStream StableRNG(Hash);
278
279 float Hue = StableRNG.FRandRange(0.0f, 360.0f);
280 FLinearColor BoxColor = FLinearColor::MakeFromHSV8(Hue, 200, 255);
281
282 DrawDebugBox(
283 World,
284 WorldCenter,
285 Extent,
286 WorldRotation,
287 BoxColor.ToFColor(true),
288 false, -1.0f, 0, 1.5f
289 );
290 }
291
292 FVector TextPos = GetComponentLocation() + FVector(0, 0, 50.0f);
293 FString DebugMsg = FString::Printf(TEXT("Collision Boxes: %d"), VoxelBodySetup->AggGeom.BoxElems.Num());
294 DrawDebugString(World, TextPos, DebugMsg, nullptr, FColor::White, 0.0f, true);
295#endif
296}
297
298#pragma endregion
void TryUpdateCollision(const TArray< uint64 > &VoxelBitArray, const FIntVector &GridResolution, float VoxelSize, int32 ActiveVoxelNum, float SyncTime, bool bForce=false)
static FORCEINLINE FVector GridToLocal(const FIntVector &GridPos, float VoxelSize, const FIntVector &CenterOffset)
static FORCEINLINE int32 GridToVoxelBitIndex(const FIntVector &GridPos, const FIntVector &Resolution)