UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
MeshWindingNumberGrid.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3// Port of geometry3Sharp MeshWindingNumberGrid
4
5#pragma once
6
7#include "MathUtil.h"
8#include "MeshQueries.h"
10#include "Spatial/FastWinding.h"
12#include "Util/MeshCaches.h"
13#include "MatrixTypes.h"
14
15
16namespace UE
17{
18namespace Geometry
19{
20
33template <class TriangleMeshType>
35{
36public:
39
40 // size of cubes in the grid
41 double CellSize;
42
43 // how many cells around border should we keep
44 int BufferCells = 1;
45
46 // Should we compute MWN at all grid cells (expensive!!) or only in narrow band.
47 // In narrow-band mode, we guess rest of MWN values by propagating along x-rows
49 {
51 NarrowBand = 1
52 };
54
55 // in narrow-band mode, if mesh is not closed, we will explore space around
56 // this MWN iso-value
57 float WindingIsoValue = 0.5f;
58
59 // in NarrowBand mode, we compute mesh SDF grid; if true then it can be accessed
60 // via SDFGrid function after Compute()
61 bool bWantMeshSDFGrid = true;
62
64 TFunction<bool()> CancelF = [](){ return false; };
65
66 // computed results
69
70 // SDF grid we compute in narrow-band mode
72
73
78
79
80 void Compute()
81 {
82 // figure out origin & dimensions
83 FAxisAlignedBox3d bounds = FastWinding->GetTree()->GetBoundingBox();
84
85 if (bounds.IsEmpty())
86 {
87 return;
88 }
89
90 float fBufferWidth = (float)BufferCells * (float)CellSize;
93 int ni = (int)((max.X - GridOrigin.X) / (float)CellSize) + 1;
94 int nj = (int)((max.Y - GridOrigin.Y) / (float)CellSize) + 1;
95 int nk = (int)((max.Z - GridOrigin.Z) / (float)CellSize) + 1;
96
97 if (ni >= 0 && nj >= 0 && nk >= 0)
98 {
100 {
101 make_grid_dense(GridOrigin, (float)CellSize, ni, nj, nk, WindingGrid);
102 }
103 else
104 {
105 make_grid(GridOrigin, (float)CellSize, ni, nj, nk, WindingGrid);
106 }
107 }
108
109 if (!bWantMeshSDFGrid)
110 {
111 MeshSDF.Empty();
112 }
113 }
114
115
116
118 {
119 return WindingGrid.GetDimensions();
120 }
121
122 constexpr const float& At(int I, int J, int K) const
123 {
124 return WindingGrid.At(I, J, K);
125 }
126
127 float GetValue(const FVector3i& IJK) const // TTriLinearGridInterpolant interface
128 {
129 return WindingGrid[IJK];
130 }
131
132 FVector3f CellCenter(int I, int J, int K)
133 {
134 return FVector3f((float)I * CellSize + GridOrigin.X,
135 (float)J * CellSize + GridOrigin.Y,
136 (float)K * CellSize + GridOrigin.Z);
137 }
138
139private:
140
141 void make_grid(FVector3f origin, float dx, int ni, int nj, int nk, FDenseGrid3f& winding)
142 {
143 winding.Resize(ni, nj, nk);
144 winding.Assign(FMathf::MaxReal); // sentinel
145
146 // seed MWN cache
147 FastWinding->Build(false);
148
149 // Ok, because the whole idea is that the surface might have holes, we are going to
150 // compute MWN along known triangles and then propagate the computed region outwards
151 // until any MWN iso-sign-change is surrounded.
152 // To seed propagation, we compute unsigned SDF and then compute MWN for any voxels
153 // containing surface (ie w/ distance smaller than cellsize)
154
155 // compute unsigned SDF
156 MeshSDF.bComputeSigns = false;
157 MeshSDF.CancelF = CancelF;
158 MeshSDF.Compute(FastWinding->GetTree()->GetBoundingBox());
159 if (CancelF())
160 {
161 return;
162 }
163
165
166 // compute MWN at surface voxels
167 double ox = (double)origin[0], oy = (double)origin[1], oz = (double)origin[2];
168 ParallelFor(nj*nk, [this, origin, dx, ni, nj, nk, &winding, &distances](int LinearJK)
169 {
170 if (CancelF())
171 {
172 return;
173 }
174 int J = LinearJK % nj, K = LinearJK / nj;
175 int StartIdx = ni * (J + nj * K);
176 for (int I = 0; I < ni; ++I)
177 {
178 int LinearIdx = StartIdx + I;
179 float dist = distances[LinearIdx];
180 // this could be tighter? but I don't think it matters...
181 if (dist < CellSize)
182 {
183 FVector3d gx((float)I * dx + origin[0], (float)J * dx + origin[1], (float)K * dx + origin[2]);
184 winding[LinearIdx] = (float)FastWinding->FastWindingNumber(gx);
185 }
186 }
187 });
188 if (CancelF())
189 {
190 return;
191 }
192
193 // Now propagate outwards from computed voxels.
194 // Current procedure is to check 26-neighbours around each 'front' voxel,
195 // and if there are any MWN sign changes, that neighbour is added to front.
196 // Front is initialized w/ all voxels we computed above
197
198 FAxisAlignedBox3i bounds = winding.Bounds();
200
201 // since we will be computing new MWN values as necessary, we cannot use
202 // winding grid to track whether a voxel is 'new' or not.
203 // So, using 3D bitmap instead - is updated at end of each pass.
204 TArray<bool> bits;
205 bits.SetNumZeroed(winding.Size());
206
208 for (int32 LinearIdx = 0; LinearIdx < bits.Num(); LinearIdx++)
209 {
210 if (winding[LinearIdx] != FMathf::MaxReal)
211 {
213 bits[LinearIdx] = true;
214 }
215 }
216 if (CancelF())
217 {
218 return;
219 }
220
221 // Unique set of 'new' voxels to compute in next iteration.
222 TSet<int> queue;
224
225 while (true)
226 {
227 if (CancelF())
228 {
229 return;
230 }
231
232 // can process front voxels in parallel
233 bool abort = false;
234 ParallelFor(cur_front.Num(), [this, origin, dx, ni, nj, nk, &winding, &distances, &bounds, &bits, &cur_front, &queue, &QueueSection, &abort](int FrontIdx)
235 {
236 if (FrontIdx % 100 == 0)
237 {
238 abort = CancelF();
239 }
240 if (abort)
241 {
242 return;
243 }
244
245 FVector3i ijk = winding.ToIndex(cur_front[FrontIdx]);
246 float val = winding[ijk];
247
248 // check 26-neighbours to see if we have a crossing in any direction
249 for (int K = 0; K < 26; ++K) {
250 FVector3i nijk = ijk + IndexUtil::GridOffsets26[K];
251 if (bounds.Contains(nijk) == false)
252 {
253 continue;
254 }
255 int LinearNIJK = winding.ToLinear(nijk);
256 float val2 = winding[LinearNIJK];
257 if (val2 == FMathf::MaxReal) {
258 FVector3d gx((float)nijk.X * dx + origin[0], (float)nijk.Y * dx + origin[1], (float)nijk.Z * dx + origin[2]);
259 val2 = (float)FastWinding->FastWindingNumber(gx);
261 }
262 if (bits[LinearNIJK] == false) {
263 // this is a 'new' voxel this round.
264 // If we have a MWN-iso-crossing, add it to the front next round
267 if (crossing)
268 {
269 FScopeLock QueueLock(&QueueSection);
270 queue.Add(LinearNIJK);
271 }
272 }
273 }
274 });
275 if (queue.Num() == 0)
276 {
277 break;
278 }
279
280 // update known-voxels list and create front for next iteration
281 for (int LinearIdx : queue)
282 {
283 bits[LinearIdx] = true;
284 }
286 for (int idx : queue)
287 {
288 cur_front.Add(idx);
289 }
290 queue.Reset();
291 }
292
293 if (CancelF())
294 {
295 return;
296 }
297
298 // fill in the rest of the grid by propagating know MWN values
299 fill_spans(ni, nj, nk, winding);
300 }
301
302
303
304 void make_grid_dense(FVector3f origin, float dx, int ni, int nj, int nk, FDenseGrid3f& winding)
305 {
306 winding.Resize(ni, nj, nk);
307
308 // seed MWN cache
309 FastWinding->Build(false);
310
311 bool abort = false;
312 ParallelFor(winding.Size(), [this, origin, dx, &winding, &abort](int LinearIdx)
313 {
314 if (LinearIdx % 100 == 0)
315 {
316 abort = CancelF();
317 }
318 if (abort)
319 {
320 return;
321 }
322 FVector3i ijk = winding.ToIndex(LinearIdx);
323 FVector3d gx((double)ijk.X * dx + origin[0], (double)ijk.Y * dx + origin[1], (double)ijk.Z * dx + origin[2]);
324 winding[LinearIdx] = (float)FastWinding->FastWindingNumber(gx);
325 });
326
327 }
328
329
330 void fill_spans(int ni, int nj, int nk, FDenseGrid3f& winding)
331 {
332 ParallelFor(nj*nk, [this, ni, nj, nk, &winding](int LinearJK)
333 {
334 int J = LinearJK % nj, K = LinearJK / nj;
335 int StartIdx = ni * (J + nj * K);
336 float last = winding[StartIdx];
337 if (last == FMathf::MaxReal)
338 {
339 last = 0;
340 }
341 for (int I = 0; I < ni; ++I) {
342 int LinearIdx = I + StartIdx;
343 if (winding[LinearIdx] == FMathf::MaxReal)
344 {
345 winding[LinearIdx] = last;
346 }
347 else
348 {
349 last = winding[LinearIdx];
350 if (last < WindingIsoValue) // propagate zeros on outside
351 {
352 last = 0;
353 }
354 }
355 }
356 });
357 }
358
359};
360
361} // end namespace UE::Geometry
362} // end namespace UE
void ParallelFor(int32 Num, TFunctionRef< void(int32)> Body, bool bForceSingleThread, bool bPumpRenderingThread=false)
Definition ParallelFor.h:481
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
UE::FPlatformRecursiveMutex FCriticalSection
Definition CriticalSection.h:53
UE::Math::TVector< float > FVector3f
Definition MathFwd.h:73
const bool
Definition NetworkReplayStreaming.h:178
USkinnedMeshComponent float
Definition SkinnedMeshComponent.h:60
if(Failed) console_printf("Failed.\n")
Definition ScopeLock.h:141
Definition Array.h:670
UE_REWRITE SizeType Num() const
Definition Array.h:1144
void SetNumZeroed(SizeType NewNum, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2340
void Reset(SizeType NewSize=0)
Definition Array.h:2246
UE_NODEBUG UE_FORCEINLINE_HINT SizeType Add(ElementType &&Item)
Definition Array.h:2696
Definition AndroidPlatformMisc.h:14
const FVector3i & GetDimensions() const
Definition DenseGrid3.h:56
constexpr ElemType & At(int I, int J, int K)
Definition DenseGrid3.h:115
FAxisAlignedBox3i Bounds() const
Definition DenseGrid3.h:206
Definition FastWinding.h:316
double FastWindingNumber(const FVector3d &P)
Definition FastWinding.h:387
void Build(bool bForceRebuild=true)
Definition FastWinding.h:362
Definition MeshWindingNumberGrid.h:35
TFunction< bool()> CancelF
Definition MeshWindingNumberGrid.h:64
int BufferCells
Definition MeshWindingNumberGrid.h:44
FVector3f CellCenter(int I, int J, int K)
Definition MeshWindingNumberGrid.h:132
FVector3i Dimensions() const
Definition MeshWindingNumberGrid.h:117
float WindingIsoValue
Definition MeshWindingNumberGrid.h:57
EComputeModes
Definition MeshWindingNumberGrid.h:49
@ FullGrid
Definition MeshWindingNumberGrid.h:50
@ NarrowBand
Definition MeshWindingNumberGrid.h:51
bool bWantMeshSDFGrid
Definition MeshWindingNumberGrid.h:61
FDenseGrid3f WindingGrid
Definition MeshWindingNumberGrid.h:68
TSweepingMeshSDF< TriangleMeshType > MeshSDF
Definition MeshWindingNumberGrid.h:71
TFastWindingTree< TriangleMeshType > * FastWinding
Definition MeshWindingNumberGrid.h:38
EComputeModes ComputeMode
Definition MeshWindingNumberGrid.h:53
TMeshWindingNumberGrid(const TriangleMeshType *Mesh, TFastWindingTree< TriangleMeshType > *FastWinding, double CellSize)
Definition MeshWindingNumberGrid.h:74
float GetValue(const FVector3i &IJK) const
Definition MeshWindingNumberGrid.h:127
FVector3f GridOrigin
Definition MeshWindingNumberGrid.h:67
void Compute()
Definition MeshWindingNumberGrid.h:80
const TriangleMeshType * Mesh
Definition MeshWindingNumberGrid.h:37
double CellSize
Definition MeshWindingNumberGrid.h:41
constexpr const float & At(int I, int J, int K) const
Definition MeshWindingNumberGrid.h:122
Definition SweepingMeshSDF.h:46
GEOMETRYCORE_API const FVector3i GridOffsets26[26]
Definition IndexUtil.cpp:32
TDenseGrid3< float > FDenseGrid3f
Definition DenseGrid3.h:233
Definition AdvancedWidgetsModule.cpp:13
FVector3i Max
Definition IntBoxTypes.h:186
Definition IntVectorTypes.h:252
static FVector3i One()
Definition IntVectorTypes.h:330
static TVector< float > One()
Definition Vector.h:115
T Z
Definition Vector.h:68
T Y
Definition Vector.h:65
static TVector< T > Max(const TVector< T > &A, const TVector< T > &B)
Definition Vector.h:2506
static TVector< T > Min(const TVector< T > &A, const TVector< T > &B)
Definition Vector.h:2496
T X
Definition Vector.h:62