UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
CachingMeshSDF.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3// Port of geometry3Sharp CachingMeshSDF
4
5#pragma once
6
7#include "MathUtil.h"
8#include "MeshQueries.h"
10#include "Spatial/DenseGrid3.h"
11#include "Async/ParallelFor.h"
12
14
15namespace UE
16{
17namespace Geometry
18{
19
20
33template <class TriangleMeshType>
35{
36public:
37
38 // INPUTS
39
42 float CellSize;
43
44 // Bounds of Grid will be expanded this much in positive and negative directions.
45 // Useful for if you want field to extend outwards.
47
48 // max distance away from surface that we might need to evaluate
50
51 // Most of this parallelizes very well, makes a huge speed difference
52 bool bUseParallel = true;
53
54 // should we try to compute signs? if not, Grid remains unsigned
55 bool bComputeSigns = true;
56
57 // If the number of cells in any dimension may exceed this, CellSize will be automatically increased to keep cell count reasonable
59
60 // What counts as "inside" the mesh. Crossing count does not use triangle
61 // Orientation, so inverted faces are fine, but overlapping shells or self intersections
62 // will be filled using even/odd rules (as seen along X axis...)
63 // Winding count is basically mesh winding number, handles overlap shells and
64 // self-intersections, but inverted shells are 'subtracted', and inverted faces are a disaster.
65 // Both modes handle internal cavities, neither handles open sheets.
72
73 // Implementation computes the triangle closest to each Grid cell, can
74 // return this Grid if desired (only reason not to is avoid hanging onto memory)
75 bool bWantClosestTriGrid = false;
76
77 // Grid of per-cell crossing or parity counts
79
81 TFunction<bool(void)> CancelF = []() { return false; };
82
83
84 // OUTPUTS
85
90
91 TCachingMeshSDF(float CellSizeIn = 10) : Mesh(nullptr), Spatial(nullptr), CellSize(CellSizeIn)
92 {
93 }
94
103
104protected:
105
106 // set by Initialize
107
110
111public:
112
113 bool Validate()
114 {
115 FAxisAlignedBox3d Bounds = Spatial->GetBoundingBox();
116
117 if (Bounds.IsEmpty() || !FMath::IsFinite(Bounds.MaxDim()))
118 {
119 return false;
120 }
121 if (CellSize <= 0 || !FMath::IsFinite(CellSize))
122 {
123 return false;
124 }
126 {
127 return false;
128 }
129 return true;
130 }
131
133 {
134 if (!ensure(Validate()))
135 {
136 return;
137 }
138
139 // figure out Origin & dimensions
140 FAxisAlignedBox3d Bounds = Spatial->GetBoundingBox();
141
142 double MaxDim = MaxElement(Bounds.Max - Bounds.Min + ExpandBounds * 2.0) + 4.0 * MaxOffsetDistance;
143 if (!ensureMsgf(MaxDim / CellSize <= ApproxMaxCellsPerDimension - 4, TEXT("SDF resolution clamped to avoid excessive memory use")))
144 {
145 CellSize = float (MaxDim / (ApproxMaxCellsPerDimension - 4));
146 if (!ensure(CellSize > 0 && FMath::IsFinite(CellSize)))
147 {
148 return;
149 }
150 }
151
152 float fBufferWidth = (float)FMath::Max(4 * CellSize, 2 * MaxOffsetDistance + 2 * CellSize);
155 int NI = (int)((max.X - GridOrigin.X) / CellSize) + 1;
156 int NJ = (int)((max.Y - GridOrigin.Y) / CellSize) + 1;
157 int NK = (int)((max.Z - GridOrigin.Z) / CellSize) + 1;
158
161
162 MaxDistQueryDist = MaxOffsetDistance + (2 * CellSize * FMathf::Sqrt2);
163
164 // closest triangle id for each Grid cell
166 {
169 }
170
171 if (bComputeSigns == true)
172 {
173 // IntersectionCount(I,J,K) is # of tri intersections in (I-1,I]x{J}x{K}
176 IntersectionCount.Assign(0);
177
178 compute_intersections(GridOrigin, CellSize, NI, NJ, NK, IntersectionCount);
179 if (CancelF())
180 {
182 return;
183 }
184
185 // then figure out signs (inside/outside) from intersection counts
186 compute_signs(NI, NJ, NK, Grid, IntersectionCount);
187 if (CancelF())
188 {
190 return;
191 }
192
194 }
195 }
196
198 {
200 {
201 IntersectionsGrid.Resize(0, 0, 0);
202 }
203 }
204
205
207 {
208 float f = Grid[Idx];
209 if (f == UpperBoundDistance || f == -UpperBoundDistance)
210 {
211 FVector3d p = (FVector3d)cell_center(Idx);
212
213 float sign = FMath::Sign(f);
214
215 double dsqr;
216 int near_tid = Spatial->FindNearestTriangle(p, dsqr, MaxDistQueryDist);
217 //int near_tid = Spatial->FindNearestTriangle(p, dsqr);
219 {
220 f += 0.0001f;
221 }
222 else
223 {
224 f = sign * (float)FMathd::Sqrt(dsqr);
225 }
226
227 Grid[Idx] = f;
229 {
231 }
232 }
233 return f;
234 }
235
236
237
238
243
244
245
247 {
248 return Grid.GetDimensions();
249 }
250
254 const FDenseGrid3f& GetGrid() const
255 {
256 return Grid;
257 }
258
263 {
264 return GridOrigin;
265 }
266
267
269 {
270 checkf(bWantClosestTriGrid == true, TEXT("Set bWantClosestTriGrid=true to return this value"));
271 return ClosestTriGrid;
272 }
273
275 {
276 checkf(bWantIntersectionsGrid == true, TEXT("Set bWantIntersectionsGrid=true to return this value"));
277 return IntersectionsGrid;
278 }
279
280 float At(int I, int J, int K) const
281 {
282 return Grid.At(I, J, K);
283 }
284
285 constexpr const float operator[](FVector3i Idx) const
286 {
287 return Grid[Idx];
288 }
289
290 FVector3f CellCenter(int I, int J, int K)
291 {
292 return cell_center(FVector3i(I, J, K));
293 }
294
295private:
296 FVector3f cell_center(const FVector3i& IJK)
297 {
298 return FVector3f((float)IJK.X * CellSize + GridOrigin[0],
299 (float)IJK.Y * CellSize + GridOrigin[1],
300 (float)IJK.Z * CellSize + GridOrigin[2]);
301 }
302
303 // fill the intersection Grid w/ number of intersections in each cell
304 void compute_intersections(FVector3f Origin, float DX, int32 NI, int32 NJ, int32 NK, FDenseGrid3i& IntersectionCount)
305 {
306 double ox = (double)Origin[0], oy = (double)Origin[1], oz = (double)Origin[2];
307 double invdx = 1.0 / DX;
308
309 bool cancelled = false;
310
311 // this is what we will do for each triangle. There are no Grid-reads, only Grid-writes,
312 // since we use atomic_increment, it is always thread-safe
313 ParallelFor(Mesh->MaxTriangleID(),
314 [this, &Origin, &DX, &NI, &NJ, &NK,
316 {
317 if (TID % 100 == 0 && CancelF() == true)
318 {
319 cancelled = true;
320 }
321 if (cancelled || !Mesh->IsTriangle(TID))
322 {
323 return;
324 }
325
327 Mesh->GetTriVertices(TID, xp, xq, xr);
328
329
330 bool neg_x = false;
332 {
334 neg_x = n.X > 0;
335 }
336
337 // real IJK coordinates of xp/xq/xr
338 double fip = (xp[0] - ox) * invdx, fjp = (xp[1] - oy) * invdx, fkp = (xp[2] - oz) * invdx;
339 double fiq = (xq[0] - ox) * invdx, fjq = (xq[1] - oy) * invdx, fkq = (xq[2] - oz) * invdx;
340 double fir = (xr[0] - ox) * invdx, fjr = (xr[1] - oy) * invdx, fkr = (xr[2] - oz) * invdx;
341
342 // recompute J/K integer bounds of triangle w/o exact band
343 int32 j0 = FMath::Clamp(FMath::CeilToInt32(FMath::Min3(fjp, fjq, fjr)), 0, NJ - 1);
344 int32 j1 = FMath::Clamp(FMath::FloorToInt32(FMath::Max3(fjp, fjq, fjr)), 0, NJ - 1);
345 int32 k0 = FMath::Clamp(FMath::CeilToInt32(FMath::Min3(fkp, fkq, fkr)), 0, NK - 1);
346 int32 k1 = FMath::Clamp(FMath::FloorToInt32(FMath::Max3(fkp, fkq, fkr)), 0, NK - 1);
347
348 // and do intersection counts
349 for (int K = k0; K <= k1; ++K)
350 {
351 for (int J = j0; J <= j1; ++J)
352 {
353 double A, B, C;
354 if (PointInTriangle2d(J, K, fjp, fkp, fjq, fkq, fjr, fkr, A, B, C))
355 {
356 double fi = A * fip + B * fiq + C * fir; // intersection I coordinate
357 int i_interval = FMath::CeilToInt32(fi); // intersection is in (i_interval-1,i_interval]
358 if (i_interval < 0)
359 {
361 }
362 else if (i_interval < NI)
363 {
365 }
366 else
367 {
368 // we ignore intersections that are beyond the +x side of the Grid
369 }
370 }
371 }
372 }
373 }, !bUseParallel);
374 }
375
376
377
378
379
380 // iterate through each x-row of Grid and set unsigned distances to be negative
381 // inside the mesh, based on the IntersectionCount
382 void compute_signs(int NI, int NJ, int NK, FDenseGrid3f& Distances, FDenseGrid3i& IntersectionCount)
383 {
384 if (bUseParallel)
385 {
386 // can process each x-row in parallel
387 ParallelFor(NJ*NK, [this, &NI, &NJ, &NK, &Distances, &IntersectionCount](int32 vi)
388 {
389 if (CancelF())
390 {
391 return;
392 }
393
394 int32 J = vi % NJ, K = vi / NJ;
395 int32 total_count = 0;
396 for (int I = 0; I < NI; ++I) {
397 total_count += IntersectionCount.At(I, J, K);
398 if (
399 (InsideMode == EInsideModes::WindingCount && total_count > 0) ||
400 (InsideMode == EInsideModes::CrossingCount && total_count % 2 == 1)
401 )
402 {
403 Distances.At(I, J, K) = -Distances.At(I, J, K); // we are inside the mesh
404 }
405 }
406 }, !bUseParallel);
407
408 }
409 else
410 {
411 for (int K = 0; K < NK; ++K)
412 {
413 if (CancelF())
414 {
415 return;
416 }
417
418 for (int J = 0; J < NJ; ++J)
419 {
420 int total_count = 0;
421 for (int I = 0; I < NI; ++I)
422 {
423 total_count += IntersectionCount.At(I, J, K);
424 if (
425 (InsideMode == EInsideModes::WindingCount && total_count > 0) ||
426 (InsideMode == EInsideModes::CrossingCount && total_count % 2 == 1)
427 )
428 {
429 Distances.At(I, J, K) = -Distances.At(I, J, K); // we are inside the mesh
430 }
431 }
432 }
433 }
434 }
435 }
436
437
438
439public:
440
441 // calculate twice signed area of triangle (0,0)-(X1,Y1)-(X2,Y2)
442 // return an SOS-determined sign (-1, +1, or 0 only if it's a truly degenerate triangle)
443 static int Orientation(double X1, double Y1, double X2, double Y2, double& TwiceSignedArea)
444 {
445 TwiceSignedArea = Y1 * X2 - X1 * Y2;
446 if (TwiceSignedArea > 0) return 1;
447 else if (TwiceSignedArea < 0) return -1;
448 else if (Y2 > Y1) return 1;
449 else if (Y2 < Y1) return -1;
450 else if (X1 > X2) return 1;
451 else if (X1 < X2) return -1;
452 else return 0; // only true when X1==X2 and Y1==Y2
453 }
454
455
456 // robust test of (X0,Y0) in the triangle (X1,Y1)-(X2,Y2)-(X3,Y3)
457 // if true is returned, the barycentric coordinates are set in A,B,C.
458 static bool PointInTriangle2d(double X0, double Y0,
459 double X1, double Y1, double X2, double Y2, double X3, double Y3,
460 double& A, double& B, double& C)
461 {
462 A = B = C = 0;
463 X1 -= X0; X2 -= X0; X3 -= X0;
464 Y1 -= Y0; Y2 -= Y0; Y3 -= Y0;
465 int signa = Orientation(X2, Y2, X3, Y3, A);
466 if (signa == 0) return false;
467 int signb = Orientation(X3, Y3, X1, Y1, B);
468 if (signb != signa) return false;
469 int signc = Orientation(X1, Y1, X2, Y2, C);
470 if (signc != signa) return false;
471 double sum = A + B + C;
472 // if the SOS signs match and are nonzero, there's no way all of A, B, and C are zero.
473 // TODO: is this mathematically impossible? can we just remove the check?
474 checkf(sum != 0, TEXT("TCachingMeshSDF::PointInTriangle2d: impossible config?"));
475 A /= sum;
476 B /= sum;
477 C /= sum;
478 return true;
479 }
480
481};
482
483
484} // end namespace UE::Geometry
485} // end namespace UE
#define ensureMsgf( InExpression, InFormat,...)
Definition AssertionMacros.h:465
#define ensure( InExpression)
Definition AssertionMacros.h:464
#define checkf(expr, format,...)
Definition AssertionMacros.h:315
void ParallelFor(int32 Num, TFunctionRef< void(int32)> Body, bool bForceSingleThread, bool bPumpRenderingThread=false)
Definition ParallelFor.h:481
#define TEXT(x)
Definition Platform.h:1272
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::Math::TVector< float > FVector3f
Definition MathFwd.h:73
UE::Math::TVector< double > FVector3d
Definition MathFwd.h:60
const bool
Definition NetworkReplayStreaming.h:178
USkinnedMeshComponent float
Definition SkinnedMeshComponent.h:60
if(Failed) console_printf("Failed.\n")
Definition AndroidPlatformMisc.h:14
static RealType Sqrt(const RealType Value)
Definition MathUtil.h:342
Definition CachingMeshSDF.h:35
float GetValue(FVector3i Idx)
Definition CachingMeshSDF.h:206
bool bWantIntersectionsGrid
Definition CachingMeshSDF.h:78
bool bWantClosestTriGrid
Definition CachingMeshSDF.h:75
float UpperBoundDistance
Definition CachingMeshSDF.h:108
TTriLinearGridInterpolant< TCachingMeshSDF > MakeInterpolant()
Definition CachingMeshSDF.h:239
FDenseGrid3i ClosestTriGrid
Definition CachingMeshSDF.h:88
TCachingMeshSDF(const TriangleMeshType *MeshIn, float CellSizeIn, const TMeshAABBTree3< TriangleMeshType > *SpatialIn, bool bAutoBuild)
Definition CachingMeshSDF.h:95
FDenseGrid3f Grid
Definition CachingMeshSDF.h:87
float At(int I, int J, int K) const
Definition CachingMeshSDF.h:280
FVector3f CellCenter(int I, int J, int K)
Definition CachingMeshSDF.h:290
const FVector3f & GetGridOrigin() const
Definition CachingMeshSDF.h:262
FVector3d ExpandBounds
Definition CachingMeshSDF.h:46
const TMeshAABBTree3< TriangleMeshType > * Spatial
Definition CachingMeshSDF.h:41
static int Orientation(double X1, double Y1, double X2, double Y2, double &TwiceSignedArea)
Definition CachingMeshSDF.h:443
EInsideModes InsideMode
Definition CachingMeshSDF.h:71
TFunction< bool(void)> CancelF
Definition CachingMeshSDF.h:81
bool Validate()
Definition CachingMeshSDF.h:113
void CleanupUnwanted()
Definition CachingMeshSDF.h:197
FVector3f GridOrigin
Definition CachingMeshSDF.h:86
float CellSize
Definition CachingMeshSDF.h:42
bool bComputeSigns
Definition CachingMeshSDF.h:55
const FDenseGrid3i & GetClosestTriGrid() const
Definition CachingMeshSDF.h:268
void Initialize()
Definition CachingMeshSDF.h:132
double MaxDistQueryDist
Definition CachingMeshSDF.h:109
const FDenseGrid3f & GetGrid() const
Definition CachingMeshSDF.h:254
float MaxOffsetDistance
Definition CachingMeshSDF.h:49
const TriangleMeshType * Mesh
Definition CachingMeshSDF.h:40
FVector3i Dimensions()
Definition CachingMeshSDF.h:246
EInsideModes
Definition CachingMeshSDF.h:67
@ WindingCount
Definition CachingMeshSDF.h:69
@ CrossingCount
Definition CachingMeshSDF.h:68
FDenseGrid3i IntersectionsGrid
Definition CachingMeshSDF.h:89
bool bUseParallel
Definition CachingMeshSDF.h:52
const FDenseGrid3i & GetIntersectionsGrid() const
Definition CachingMeshSDF.h:274
int ApproxMaxCellsPerDimension
Definition CachingMeshSDF.h:58
static bool PointInTriangle2d(double X0, double Y0, double X1, double Y1, double X2, double Y2, double X3, double Y3, double &A, double &B, double &C)
Definition CachingMeshSDF.h:458
constexpr const float operator[](FVector3i Idx) const
Definition CachingMeshSDF.h:285
TCachingMeshSDF(float CellSizeIn=10)
Definition CachingMeshSDF.h:91
const FVector3i & GetDimensions() const
Definition DenseGrid3.h:56
void Assign(ElemType Value)
Definition DenseGrid3.h:73
constexpr ElemType & At(int I, int J, int K)
Definition DenseGrid3.h:115
void Resize(int DimX, int DimY, int DimZ, EAllowShrinking AllowShrinking=EAllowShrinking::Default)
Definition DenseGrid3.h:61
Definition MeshAABBTree3.h:61
Definition GridInterpolant.h:29
constexpr int InvalidID
Definition IndexTypes.h:13
void AtomicIncDec(FDenseGrid2i &Grid, int32 i, int32 j, bool bDecrement=false)
Definition DenseGrid2.h:207
TVector< RealType > NormalDirection(const TVector< RealType > &V0, const TVector< RealType > &V1, const TVector< RealType > &V2)
Definition VectorUtil.h:83
constexpr T MaxElement(const UE::Math::TVector< T > &Vector)
Definition VectorTypes.h:298
TDenseGrid3< int > FDenseGrid3i
Definition DenseGrid3.h:235
TDenseGrid3< float > FDenseGrid3f
Definition DenseGrid3.h:233
Definition AdvancedWidgetsModule.cpp:13
static constexpr UE_FORCEINLINE_HINT T Clamp(const T X, const T MinValue, const T MaxValue)
Definition UnrealMathUtility.h:592
static constexpr UE_FORCEINLINE_HINT T Min3(const T A, const T B, const T C)
Definition UnrealMathUtility.h:558
static constexpr UE_FORCEINLINE_HINT T Max3(const T A, const T B, const T C)
Definition UnrealMathUtility.h:551
Definition IntVectorTypes.h:252
TVector< RealType > Max
Definition BoxTypes.h:249
bool IsEmpty() const
Definition BoxTypes.h:613
RealType MaxDim() const
Definition BoxTypes.h:598
TVector< RealType > Min
Definition BoxTypes.h:248
static TVector< float > One()
Definition Vector.h:115
T Z
Definition Vector.h:68
static TVector< double > Zero()
Definition Vector.h:112
T Y
Definition Vector.h:65
T X
Definition Vector.h:62