UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
FastWinding.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "MathUtil.h"
6#include "MeshQueries.h"
8#include "Util/MeshCaches.h"
9#include "MatrixTypes.h"
11
16{
17 using namespace UE::Geometry;
20
29 template <class TriangleMeshType, class IterableTriangleIndices>
31 const IterableTriangleIndices& Triangles,
33 FVector3d& P, double& R,
36 {
37 P = FVector3d::Zero();
40 R = 0;
41
42 // compute area-weighted centroid of Triangles, we use this as the expansion point
44 double sum_area = 0;
45 for (int tid : Triangles)
46 {
47 double Area = TriCache.Areas[tid];
48 sum_area += Area;
49 P += Area * TriCache.Centroids[tid];
50 }
51 P /= sum_area;
52
53 // compute first and second-order coefficients of FWN taylor expansion, as well as
54 // 'radius' value R, which is max dist from any tri vertex to P
55 FVector3d n, c;
56 double a = 0;
57 double RSq = 0;
58 for (int tid : Triangles)
59 {
60 Mesh.GetTriVertices(tid, P0, P1, P2);
61 TriCache.GetTriInfo(tid, n, a, c);
62
63 Order1 += a * n;
64
65 FVector3d dcp = c - P;
66 Order2 += a * FMatrix3d(dcp, n);
67
68 // update max radius R (as squared value in loop)
70 RSq = FMath::Max(RSq, MaxDistSq);
71 }
72 R = FMath::Sqrt(RSq);
73 }
74
83 template <class TriangleMeshType>
87 FVector3d& P,
88 double& R,
91 int NumTasks = 16)
92 {
93 // If the data is small enough, don't bother trying to parallelize
94 constexpr int ParallelThreshold = 3000; // Benchmarking shows parallel starts to outperform serial at around 2500 triangles or so
96 {
98 return;
99 }
100
101 // First compute the area-weighted centroid
102
103 struct PData
104 {
105 FVector3d P;
106 double Area;
107 };
108
110 {
112
114 DataOut.Area = TriCache.Areas[TID];
115 DataOut.P = DataOut.Area * TriCache.Centroids[TID];
116 return DataOut;
117 };
118
119 auto CentroidReduce = [](const PData& A, const PData& B) -> PData
120 {
121 PData Out;
122 Out.P = A.P + B.P;
123 Out.Area = A.Area + B.Area;
124 return Out;
125 };
126
128
129 CentroidData.P /= CentroidData.Area;
130
131 // Now compute the expansions
132
133 struct OrderData
134 {
137 double RSq;
138 };
139
141 {
143 FVector3d P0, P1, P2;
144 Mesh.GetTriVertices(tid, P0, P1, P2);
145
146 FVector3d n, c;
147 double a;
148 TriCache.GetTriInfo(tid, n, a, c);
149
151 DataOut.Order1 = a * n;
152
153 FVector3d dcp = c - P;
154 DataOut.Order2 = a * FMatrix3d(dcp, n);
155
156 // this is just for return value...
158 DataOut.RSq = MaxDistSq;
159
160 return DataOut;
161 };
162
163 auto ExpansionReduce = [](const OrderData& A, const OrderData& B) -> OrderData
164 {
165 OrderData Out;
166 Out.Order1 = A.Order1 + B.Order1;
167 Out.Order2 = A.Order2 + B.Order2;
168 Out.RSq = FMath::Max(A.RSq, B.RSq);
169 return Out;
170 };
171
173 OrderData{ FVector3d::Zero(), FMatrix3d::Zero(), 0.0 },
176 NumTasks);
177
178 // Set out values
179
180 P = CentroidData.P;
181 R = FMath::Sqrt(Orders.RSq);
182 Order1 = Orders.Order1;
183 Order2 = Orders.Order2;
184 }
185
194 template <class TriangleMeshType>
195 UE_DEPRECATED(5.7, "Use the above version of ComputeCoeffs, which takes a TArray of triangle indices instead")
197 const TSet<int>& TriangleSet,
199 FVector3d& P,
200 double& R,
203 int NumTasks = 16)
204 {
205 // If the data is small enough, don't bother trying to parallelize
206 constexpr int ParallelThreshold = 3000; // Benchmarking shows parallel starts to outperform serial at around 2500 triangles or so
207 if (TriangleSet.Num() < ParallelThreshold)
208 {
209 ComputeCoeffsSerial(Mesh, TriangleSet, TriCache, P, R, Order1, Order2);
210 return;
211 }
212
213 TArray<int> TriangleArray = TriangleSet.Array();
215
216 }
217
218
222 inline double EvaluateOrder1Approx(const FVector3d& Center, const FVector3d& Order1Coeff, const FVector3d& Q)
223 {
224 FVector3d dpq = (Center - Q);
225 double len = dpq.Length();
226
227 return (1.0 / FMathd::FourPi) * Order1Coeff.Dot(dpq / (len * len * len));
228 }
229
234 {
235 FVector3d dpq = (Center - Q);
236 double len = dpq.Length();
237 double len3 = len * len * len;
238 double fourPi_len3 = 1.0 / (FMathd::FourPi * len3);
239
240 double Order1 = fourPi_len3 * Order1Coeff.Dot(dpq);
241
242 // second-order hessian \grad^2(G)
243 double c = -3.0 / (FMathd::FourPi * len3 * len * len);
244
245 // expanded-out version below avoids extra constructors
246 //FMatrix3d xqxq(dpq, dpq);
247 //FMatrix3d hessian(fourPi_len3, fourPi_len3, fourPi_len3) - c * xqxq;
249 fourPi_len3 + c * dpq.X * dpq.X, c * dpq.X * dpq.Y, c * dpq.X * dpq.Z,
250 c * dpq.Y * dpq.X, fourPi_len3 + c * dpq.Y * dpq.Y, c * dpq.Y * dpq.Z,
251 c * dpq.Z * dpq.X, c * dpq.Z * dpq.Y, fourPi_len3 + c * dpq.Z * dpq.Z);
252
253 double Order2 = Order2Coeff.InnerProduct(hessian);
254
255 return Order1 + Order2;
256 }
257
258 // triangle-winding-number first-order approximation.
259 // T is triangle, P is 'Center' of cluster of dipoles, Q is evaluation point
260 // (This is really just for testing)
261 inline double Order1Approx(const FTriangle3d& T, const FVector3d& P, const FVector3d& XN, double XA, const FVector3d& Q)
262 {
263 FVector3d at0 = XA * XN;
264
265 FVector3d dpq = (P - Q);
266 double len = dpq.Length();
267 double len3 = len * len * len;
268
269 return (1.0 / FMathd::FourPi) * at0.Dot(dpq / (len * len * len));
270 }
271
272 // triangle-winding-number second-order approximation
273 // T is triangle, P is 'Center' of cluster of dipoles, Q is evaluation point
274 // (This is really just for testing)
275 inline double Order2Approx(const FTriangle3d& T, const FVector3d& P, const FVector3d& XN, double XA, const FVector3d& Q)
276 {
277 FVector3d dpq = (P - Q);
278
279 double len = dpq.Length();
280 double len3 = len * len * len;
281
282 // first-order approximation - integrated_normal_area * \grad(G)
283 double Order1 = (XA / FMathd::FourPi) * XN.Dot(dpq / len3);
284
285 // second-order hessian \grad^2(G)
287 xqxq *= 3.0 / (FMathd::FourPi * len3 * len * len);
288 double diag = 1 / (FMathd::FourPi * len3);
290
291 // second-order LHS - integrated second-order area matrix (formula 26)
292 FVector3d centroid(
293 (T.V[0].X + T.V[1].X + T.V[2].X) / 3.0, (T.V[0].Y + T.V[1].Y + T.V[2].Y) / 3.0, (T.V[0].Z + T.V[1].Z + T.V[2].Z) / 3.0);
294 FVector3d dcp = centroid - P;
296 double Order2 = XA * o2_lhs.InnerProduct(hessian);
297
298 return Order1 + Order2;
299 }
300} // namespace FastTriWinding
301
302
303
304namespace UE
305{
306namespace Geometry
307{
308
314template <class TriangleMeshType>
316{
318
319 struct FWNInfo
320 {
321 FVector3d Center;
322 double R;
323 FVector3d Order1Vec;
324 FMatrix3d Order2Mat;
325 bool bValid = false;
326 };
327
328 TArray<FWNInfo> FastWindingCache;
329 int32 FastWindingCacheOffset = 0;
330 uint64 FastWindingCacheMeshChangeStamp = 0;
331
332public:
336 double FWNBeta = 2.0;
337
342
347
349 {
350 this->Tree = TreeToRef;
351 if (bAutoBuild)
352 {
353 Build(true);
354 }
355 }
356
358 {
359 return Tree;
360 }
361
362 void Build(bool bForceRebuild = true)
363 {
364 check(Tree);
365 if ( Tree->IsValid(false) == false )
366 {
367 Tree->Build();
368 }
369 if (bForceRebuild || FastWindingCacheMeshChangeStamp != Tree->MeshChangeStamp)
370 {
371 build_fast_winding_cache();
372 FastWindingCacheMeshChangeStamp = Tree->MeshChangeStamp;
373 }
374 }
375
376 bool IsBuilt() const
377 {
378 return Tree->IsValid(false) && FastWindingCacheMeshChangeStamp == Tree->MeshChangeStamp;
379 }
380
388 {
389 Build(false);
390 double sum = branch_fast_winding_num(Tree->RootIndex, P);
391 return sum;
392 }
393
397 double FastWindingNumber(const FVector3d& P) const
398 {
400 double sum = branch_fast_winding_num(Tree->RootIndex, P);
401 return sum;
402 }
403
407 bool IsInside(const FVector3d& P, double WindingIsoThreshold = 0.5) const
408 {
410 }
411
412private:
413 // evaluate winding number contribution for all Triangles below IBox
414 double branch_fast_winding_num(int IBox, FVector3d P) const
415 {
416 double branch_sum = 0;
417
418 int idx = Tree->BoxToIndex[IBox];
419 if (idx < Tree->TrianglesEnd)
420 { // triangle-list case, array is [N t1 t2 ... tN]
421 int num_tris = Tree->IndexList[idx];
422 for (int i = 1; i <= num_tris; ++i)
423 {
424 FVector3d a, b, c;
425 int ti = Tree->IndexList[idx + i];
426 Tree->Mesh->GetTriVertices(ti, a, b, c);
427 double angle = VectorUtil::TriSolidAngle(a, b, c, P);
428 branch_sum += angle;
429 }
430 branch_sum /= FMathd::FourPi;
431 }
432 else
433 { // internal node, either 1 or 2 child boxes
434 int iChild1 = Tree->IndexList[idx];
435 if (iChild1 < 0)
436 { // 1 child, descend if nearer than cur min-dist
437 iChild1 = (-iChild1) - 1;
438
439 // if we have winding cache, we can more efficiently compute contribution of all Triangles
440 // below this box. Otherwise, recursively descend Tree.
441 bool contained = Tree->box_contains(iChild1, P);
442 if (contained == false && can_use_fast_winding_cache(iChild1, P))
443 {
444 branch_sum += evaluate_box_fast_winding_cache(iChild1, P);
445 }
446 else
447 {
448 branch_sum += branch_fast_winding_num(iChild1, P);
449 }
450 }
451 else
452 { // 2 children, descend closest first
453 iChild1 = iChild1 - 1;
454 int iChild2 = Tree->IndexList[idx + 1] - 1;
455
456 bool contained1 = Tree->box_contains(iChild1, P);
457 if (contained1 == false && can_use_fast_winding_cache(iChild1, P))
458 {
459 branch_sum += evaluate_box_fast_winding_cache(iChild1, P);
460 }
461 else
462 {
463 branch_sum += branch_fast_winding_num(iChild1, P);
464 }
465
466 bool contained2 = Tree->box_contains(iChild2, P);
467 if (contained2 == false && can_use_fast_winding_cache(iChild2, P))
468 {
469 branch_sum += evaluate_box_fast_winding_cache(iChild2, P);
470 }
471 else
472 {
473 branch_sum += branch_fast_winding_num(iChild2, P);
474 }
475 }
476 }
477
478 return branch_sum;
479 }
480
481 void build_fast_winding_cache()
482 {
483 // set this to a larger number to ignore caches if number of Triangles is too small.
484 // (seems to be no benefit to doing this...is holdover from Tree-decomposition FWN code)
485 constexpr int WINDING_CACHE_THRESH = 1;
486
487 //FMeshTriInfoCache TriCache = null;
489
490 // Fast winding cache can only exist for non-leaf boxes, so find the offset where the cache-able boxes start
491 for (FastWindingCacheOffset = 0; FastWindingCacheOffset < Tree->BoxToIndex.Num(); ++FastWindingCacheOffset)
492 {
493 if (Tree->BoxToIndex[FastWindingCacheOffset] >= Tree->TrianglesEnd)
494 {
495 break;
496 }
497 }
498 int32 CacheSize = Tree->BoxToIndex.Num() - FastWindingCacheOffset;
499 FastWindingCache.Empty(CacheSize);
500 FastWindingCache.Init({ .bValid = false }, CacheSize);
502 build_fast_winding_cache(Tree->RootIndex, 0, WINDING_CACHE_THRESH, root_hash, TriCache);
503 }
504 int build_fast_winding_cache(int IBox, int Depth, int TriCountThresh, TOptional<TArray<int>>& TriArray, const FMeshTriInfoCache& TriCache)
505 {
506 TriArray.Reset();
507
508 int idx = Tree->BoxToIndex[IBox];
509 if (idx < Tree->TrianglesEnd)
510 { // triangle-list case, array is [N t1 t2 ... tN]
511 int num_tris = Tree->IndexList[idx];
512 return num_tris;
513 }
514 else
515 { // internal node, either 1 or 2 child boxes
516 int iChild1 = Tree->IndexList[idx];
517 if (iChild1 < 0)
518 { // 1 child, descend if nearer than cur min-dist
519 iChild1 = (-iChild1) - 1;
520 int num_child_tris = build_fast_winding_cache(iChild1, Depth + 1, TriCountThresh, TriArray, TriCache);
521
522 // if count in child is large enough, we already built a cache at lower node
523 return num_child_tris;
524 }
525 else
526 { // 2 children, descend closest first
527 iChild1 = iChild1 - 1;
528 int iChild2 = Tree->IndexList[idx + 1] - 1;
529
530 // let each child build its own cache if it wants. If so, it will return the
531 // list of its child tris
533 int num_tris_1 = build_fast_winding_cache(iChild1, Depth + 1, TriCountThresh, TriArray, TriCache);
534 int num_tris_2 = build_fast_winding_cache(iChild2, Depth + 1, TriCountThresh, child2_array, TriCache);
536
537 if (Depth == 0)
538 {
539 return num_tris_1 + num_tris_2; // cannot build cache at level 0...
540 }
541
542 // collect up the Triangles we need. there are various cases depending on what children already did
543 if (TriArray.IsSet() || child2_array.IsSet() || build_cache)
544 {
545 if (!TriArray.IsSet() && child2_array.IsSet())
546 {
547 collect_triangles(iChild1, child2_array.GetValue());
548 TriArray = MoveTemp(child2_array); // Note child2_array is not used after this
549 }
550 else
551 {
552 if (!TriArray.IsSet())
553 {
554 TriArray.Emplace();
555 collect_triangles(iChild1, TriArray.GetValue());
556 }
557 if (!child2_array.IsSet())
558 {
559 collect_triangles(iChild2, TriArray.GetValue());
560 }
561 else
562 {
563 TriArray->Append(child2_array.GetValue());
564 }
565 }
566 }
567 if (build_cache)
568 {
569 check(TriArray.IsSet());
570 make_box_fast_winding_cache(IBox, TriArray.GetValue(), TriCache);
571 }
572
573 return (num_tris_1 + num_tris_2);
574 }
575 }
576 }
577
578 // check if value is in cache and far enough away from Q that we can use cached value
579 bool can_use_fast_winding_cache(int IBox, const FVector3d& Q) const
580 {
581 int32 CacheIdx = IBox - FastWindingCacheOffset;
582 if (CacheIdx < 0)
583 {
584 return false;
585 }
586
587 const FWNInfo& CacheInfo = FastWindingCache[CacheIdx];
588 if (!CacheInfo.bValid)
589 {
590 return false;
591 }
592
593 double DistQP_Sq = DistanceSquared(CacheInfo.Center, Q);
594 double DistThreshold = FWNBeta * CacheInfo.R;
596 }
597
598 // compute FWN cache for all Triangles underneath this box
599 void make_box_fast_winding_cache(int IBox, const TArray<int>& Triangles, const FMeshTriInfoCache& TriCache)
600 {
601 check(!FastWindingCache[IBox - FastWindingCacheOffset].bValid);
602
603 // construct cache
604 FWNInfo cacheInfo;
605 FastTriWinding::ComputeCoeffs(*Tree->Mesh, Triangles, TriCache, cacheInfo.Center, cacheInfo.R, cacheInfo.Order1Vec, cacheInfo.Order2Mat);
606 cacheInfo.bValid = true;
607
608 FastWindingCache[IBox - FastWindingCacheOffset] = cacheInfo;
609 }
610
611 // evaluate the FWN cache for IBox
612 double evaluate_box_fast_winding_cache(int IBox, const FVector3d& Q) const
613 {
614 const FWNInfo& cacheInfo = FastWindingCache[IBox - FastWindingCacheOffset];
615
616 if (FWNApproxOrder == 2)
617 {
618 return FastTriWinding::EvaluateOrder2Approx(cacheInfo.Center, cacheInfo.Order1Vec, cacheInfo.Order2Mat, Q);
619 }
620 else
621 {
622 return FastTriWinding::EvaluateOrder1Approx(cacheInfo.Center, cacheInfo.Order1Vec, Q);
623 }
624 }
625
626 // collect all the Triangles below IBox
627 void collect_triangles(int IBox, TArray<int>& Triangles)
628 {
629 int idx = Tree->BoxToIndex[IBox];
630 if (idx < Tree->TrianglesEnd)
631 { // triangle-list case, array is [N t1 t2 ... tN]
632 int num_tris = Tree->IndexList[idx];
633 for (int i = 1; i <= num_tris; ++i)
634 {
635 Triangles.Add(Tree->IndexList[idx + i]);
636 }
637 }
638 else
639 {
640 int iChild1 = Tree->IndexList[idx];
641 if (iChild1 < 0)
642 { // 1 child, descend if nearer than cur min-dist
643 collect_triangles((-iChild1) - 1, Triangles);
644 }
645 else
646 { // 2 children, descend closest first
647 collect_triangles(iChild1 - 1, Triangles);
648 collect_triangles(Tree->IndexList[idx + 1] - 1, Triangles);
649 }
650 }
651 }
652};
653
654
655} // end namespace UE::Geometry
656} // end namespace UE
OODEFFUNC typedef void(OODLE_CALLBACK t_fp_OodleCore_Plugin_Free)(void *ptr)
#define checkSlow(expr)
Definition AssertionMacros.h:332
#define check(expr)
Definition AssertionMacros.h:314
#define UE_DEPRECATED(Version, Message)
Definition CoreMiscDefines.h:302
FPlatformTypes::int64 int64
A 64-bit signed integer.
Definition Platform.h:1127
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
FPlatformTypes::uint64 uint64
A 64-bit unsigned integer.
Definition Platform.h:1117
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
UE_INTRINSIC_CAST UE_REWRITE constexpr std::remove_reference_t< T > && MoveTemp(T &&Obj) noexcept
Definition UnrealTemplate.h:520
Definition Array.h:670
UE_NODEBUG UE_FORCEINLINE_HINT SizeType Add(ElementType &&Item)
Definition Array.h:2696
void Init(const ElementType &Element, SizeType Number)
Definition Array.h:3043
void Empty(SizeType Slack=0)
Definition Array.h:2273
Definition FastWinding.h:316
bool IsInside(const FVector3d &P, double WindingIsoThreshold=0.5) const
Definition FastWinding.h:407
TFastWindingTree(TMeshAABBTree3< TriangleMeshType > *TreeToRef, bool bAutoBuild=true)
Definition FastWinding.h:343
double FastWindingNumber(const FVector3d &P)
Definition FastWinding.h:387
void SetTree(TMeshAABBTree3< TriangleMeshType > *TreeToRef, bool bAutoBuild=true)
Definition FastWinding.h:348
TMeshAABBTree3< TriangleMeshType > * GetTree() const
Definition FastWinding.h:357
double FWNBeta
Definition FastWinding.h:336
double FastWindingNumber(const FVector3d &P) const
Definition FastWinding.h:397
int FWNApproxOrder
Definition FastWinding.h:341
bool IsBuilt() const
Definition FastWinding.h:376
void Build(bool bForceRebuild=true)
Definition FastWinding.h:362
Definition MeshAABBTree3.h:61
@ Tree
Definition ITypedTableView.h:40
Definition FastWinding.h:16
double Order1Approx(const FTriangle3d &T, const FVector3d &P, const FVector3d &XN, double XA, const FVector3d &Q)
Definition FastWinding.h:261
void ComputeCoeffs(const TriangleMeshType &Mesh, const TArray< int > &TriangleArray, const FMeshTriInfoCache &TriCache, FVector3d &P, double &R, FVector3d &Order1, FMatrix3d &Order2, int NumTasks=16)
Definition FastWinding.h:84
void ComputeCoeffsSerial(const TriangleMeshType &Mesh, const IterableTriangleIndices &Triangles, const FMeshTriInfoCache &TriCache, FVector3d &P, double &R, FVector3d &Order1, FMatrix3d &Order2)
Definition FastWinding.h:30
double Order2Approx(const FTriangle3d &T, const FVector3d &P, const FVector3d &XN, double XA, const FVector3d &Q)
Definition FastWinding.h:275
double EvaluateOrder1Approx(const FVector3d &Center, const FVector3d &Order1Coeff, const FVector3d &Q)
Definition FastWinding.h:222
double EvaluateOrder2Approx(const FVector3d &Center, const FVector3d &Order1Coeff, const FMatrix3d &Order2Coeff, const FVector3d &Q)
Definition FastWinding.h:233
RealType TriSolidAngle(TVector< RealType > A, TVector< RealType > B, TVector< RealType > C, const TVector< RealType > &P)
Definition VectorUtil.h:468
Definition ParametricSurfaceData.h:18
TMatrix3< double > FMatrix3d
Definition MatrixTypes.h:510
T DistanceSquared(const UE::Math::TVector2< T > &V1, const UE::Math::TVector2< T > &V2)
Definition VectorTypes.h:82
@ Area
Definition FitOrientedBox2.h:17
T ParallelTransformReduce(IntType Num, const T &Init, TransformFuncT Transform, ReduceFuncT Reduce, int64 InNumTasks)
Definition ParallelTransformReduce.h:17
TTriangle3< double > FTriangle3d
Definition TriangleTypes.h:325
Definition AdvancedWidgetsModule.cpp:13
static constexpr UE_FORCEINLINE_HINT T Max3(const T A, const T B, const T C)
Definition UnrealMathUtility.h:551
Definition Optional.h:131
Definition MeshCaches.h:19
static FMeshTriInfoCache BuildTriInfoCache(const TriangleMeshType &Mesh)
Definition MeshCaches.h:32
static TMatrix3< double > Zero()
Definition MatrixTypes.h:82
Definition TriangleTypes.h:263
TVector< RealType > V[3]
Definition TriangleTypes.h:264
T Z
Definition Vector.h:68
static TVector< double > Zero()
Definition Vector.h:112
T Y
Definition Vector.h:65
T Length() const
Definition Vector.h:1722
T X
Definition Vector.h:62