UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
AdaptiveTessellator.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 "Math/Vector.h"
7#include "Math/Vector2D.h"
8
9#include "Async/ParallelFor.h"
10#include "Templates/Function.h"
11#include "Templates/Tuple.h"
13#include "IndexTypes.h"
15
16#include "Containers/HashTable.h"
17
18// GeometryCore/VectorUtil.h clashes with NaniteUtilities/VectorUtil.h. Workaround: TriangleTypes.h includes VectorUtil.h
19#include "TriangleTypes.h"
20
21#include <limits>
22#include <atomic>
23
24namespace UE {
25namespace Geometry {
26
27// Info struct returned by edge split operation
29{
31 FIndex2i OldTriIndex; //< triangle indices before split
32 FIndex2i NewTriIndex; //< newly added triangles indices NewTriIndex[i] will be on the same side as OldTriIndex[i] for i=0,1, or IndexConstants::InvalidID on failure
33 FIndex2i NewVertIndex; //< indices of newly introduced vertices (NewVertIndex[i] is the shared vertex of OldTriIndex[i] and NewTriIndex[i])
34};
35
37{
39 FIndex3i NewTriIndex; //< indices of the newly created triangles, or IndexConstants::InvalidID on failure
40 FIndex3i EdgeIndex; //< for each of the new triangles the edge index corresponding to the original triangle edge
41 int32 NewVertIndex; //< index of newly created vertex
42};
43
45{
47 FIndex2i Triangles; //< triangle indices corresponding to the newly flipped triangles, or IndexConstants::InvalidID on failure
48 FIndex2i SharedEdge; //< local edge index [0..2] of the new shared edge, with respect to triangle indices above
49};
50
51// Configured by MeshT and DisplacementPolicyT, which provide mesh topology functionality and
52// evaluation/bounds of the displacement function.
53//
54// See MinimalMeshTopology for an example topology implementation.
55//
56template <typename MeshT, typename DisplacementPolicyT>
58{
59private:
60
61 inline static uint32 HashPosition(const FVector3f& Position)
62 {
63 union { float f; uint32 i; } x;
64 union { float f; uint32 i; } y;
65 union { float f; uint32 i; } z;
66
67 x.f = Position.X;
68 y.f = Position.Y;
69 z.f = Position.Z;
70
71 return Murmur32({
72 Position.X == 0.0f ? 0u : x.i,
73 Position.Y == 0.0f ? 0u : y.i,
74 Position.Z == 0.0f ? 0u : z.i
75 });
76 }
77
78 inline static uint32 HashPosition(const FVector3d& Position)
79 {
80 union { double f; uint64 i; } x;
81 union { double f; uint64 i; } y;
82 union { double f; uint64 i; } z;
83
84 x.f = Position.X;
85 y.f = Position.Y;
86 z.f = Position.Z;
87
88 return Murmur64({
89 Position.X == 0.0 ? 0u : x.i,
90 Position.Y == 0.0 ? 0u : y.i,
91 Position.Z == 0.0 ? 0u : z.i
92 });
93 }
94
95
96 template <typename T>
98 {
99 /*
100 Vert order:
101 1 0__1
102 |\ \ |
103 | \ \ | <= flip triangle
104 |__\ \|
105 0 2 2
106 */
107
108 uint32 VertXY[3][2] =
109 {
110 { TriX, TriY },
111 { TriX, TriY + 1},
112 { TriX + 1, TriY },
113 };
114 VertXY[0][1] += FlipTri;
115 VertXY[1][0] += FlipTri;
116
117 for (int Corner = 0; Corner < 3; Corner++)
118 {
119 Barycentrics[Corner][0] = static_cast<T>(VertXY[Corner][0]);
120 Barycentrics[Corner][1] = static_cast<T>(VertXY[Corner][1]);
121 Barycentrics[Corner][2] = static_cast<T>(NumSubdivisions - VertXY[Corner][0] - VertXY[Corner][1]);
122 Barycentrics[Corner] /= static_cast<T>(NumSubdivisions);
123 }
124 }
125
126 template <typename T>
127 inline static constexpr T EquilateralArea( T EdgeLength )
128 {
129 constexpr T sqrt3_4 = T(0.4330127018922193);
130 return sqrt3_4 * FMath::Square( EdgeLength * EdgeLength );
131 }
132
133 template <typename T>
135 {
136 // Area * Determinant using triple product
137 return TriangleArea * FMath::Abs( Barycentrics0 | ( Barycentrics1 ^ Barycentrics2 ) );
138 }
139
140 template <typename T>
141 // https://math.stackexchange.com/questions/3748903/closest-point-to-triangle-edge-with-barycentric-coordinates
142 inline static constexpr T DistanceToEdge( T Barycentric, T EdgeLength, T TriangleArea )
143 {
144 return T(2.) * Barycentric * TriangleArea / EdgeLength;
145 }
146
149
150 inline static constexpr int32 NextEdge(int32 e) { return (e+1)%3; }
151 inline static constexpr int32 PrevEdge(int32 e) { return (e+2)%3; }
152
153 public:
154
156 {
158
159 Custom
160 };
161
162 using RealType = typename MeshT::RealType; // the field related to the vector-space
163 using VecType = typename MeshT::VecType; // for positions and normals
164
165 struct FOptions
166 {
167 // target error to achieve (with respect to error defined by displacement policy)
169
170 // one-dimensional rate (edge-length) at which features are sampled. triangles with edg e-length smaller than
171 // this value will not be refined
173
174 // vertices with same positions are considered to be identical and stay at the same position after displacement
175 bool bCrackFree { true };
176
177 // apply displacement to the tessellated mesh
178 bool bFinalDisplace { true };
179
180 // upper bound on the number of triangles of the tessellated mesh
181 uint32 MaxTriangles { 10'000'000u };
182
184
185 bool bSingleThreaded { false };
186 };
187
188 // Initialize and run adaptive tessellation
190 MeshT& InMesh, //< mesh interface
191 DisplacementPolicyT& InDisplacementPolicy, //< displacement and error interface
192 const FOptions& InOptions)
193 : Mesh(InMesh)
194 , DisplacementPolicy(InDisplacementPolicy)
195 , Options(InOptions)
196 {
198
199 TriSplitRecords.AddUninitialized( Mesh.MaxTriID() );
200 Displacements.Init( VecType( std::numeric_limits<RealType>::signaling_NaN(),
201 std::numeric_limits<RealType>::signaling_NaN(),
202 std::numeric_limits<RealType>::signaling_NaN() ), Mesh.MaxVertexID() );
203
204 for( int32 TriIndex = 0; TriIndex < Mesh.MaxTriID(); ++TriIndex )
205 {
206 if (!Mesh.IsValidTri(TriIndex))
207 {
208 continue;
209 }
210 for( int k = 0; k < 3; k++ )
211 {
212 const uint32 VID = Mesh.GetVertexIndex(TriIndex, k);
213 check(Mesh.IsValidVertex(VID));
214 if (!VectorUtil::IsFinite(Displacements[VID]))
215 {
216 Displacements[VID] = DisplacementPolicy.GetVertexDisplacement(VID, TriIndex);
217 if (!ensure(UE::Geometry::VectorUtil::IsFinite(Displacements[VID])))
218 {
219 Displacements[VID] = VecType(0., 0., 0.);
220 }
221 }
222 }
223 }
224
225 SplitRequests.SetNum( TriSplitRecords.Num() );
226 NumSplits = 0;
227
228 NumNewTriangles.store(0);
229
230 if ( static_cast<uint32>(Mesh.MaxTriID()) >= Options.MaxTriangles )
231 {
232 return;
233 }
234
235 ParallelFor( TEXT("TAdaptiveTessellator.FindSplitBVH.PF"), TriSplitRecords.Num(), 32,
236 [&]( uint32 TriIndex )
237 {
238 CheckSplitAndEnqueue( TriIndex, 0 );
239 }, ParallelForFlags );
240
241 int32 Iter = 0;
242 while( NumSplits )
243 {
244 // Size to atomic count and sort for deterministic order
245 {
246 TRACE_CPUPROFILER_EVENT_SCOPE_TEXT("TAdaptiveTessellator.SortSplits.SF");
247 SplitRequests.SetNum( NumSplits, EAllowShrinking::No );
248 SplitRequests.Sort();
249 }
250
251 for( int32 i = 0; i < SplitRequests.Num(); i++ )
252 {
253 TriSplitRecords[ SplitRequests[i] ].RequestIndex = i;
254 }
255
256 {
257 TRACE_CPUPROFILER_EVENT_SCOPE_TEXT("TAdaptiveTessellator.SplitTriangles.SF");
258 while( SplitRequests.Num() )
259 {
260 SplitTriangle( SplitRequests.Pop() );
261 }
262 }
263
264 if ( static_cast<uint32>(Mesh.MaxTriID()) >= Options.MaxTriangles)
265 {
266 break;
267 }
268
269 SplitRequests.SetNum( TriSplitRecords.Num() );
270 NumSplits = 0;
271
272 NumNewTriangles.store(0);
273
274 ParallelFor( TEXT("TAdaptiveTessellator.FindSplitBVH.PF"), FindRequests.Num(), 32,
275 [&]( uint32 i )
276 {
277 CheckSplitAndEnqueue( FindRequests[i], Iter );
279
280 FindRequests.Reset();
281
282 Iter++;
283 }
284
285 if( Options.bCrackFree && Mesh.IsTriangleSoup())
286 {
288 OldDisplacements.AddUninitialized( Displacements.Num() );
289 Swap( Displacements, OldDisplacements );
290
291 FHashTable HashTable( 1 << FMath::FloorLog2( Mesh.MaxVertexID() ), Mesh.MaxVertexID() );
292 ParallelFor( TEXT("TAdaptiveTessellator.HashVerts.PF"), Mesh.MaxVertexID(), 4096,
293 [&]( int32 i )
294 {
295 if (Mesh.IsValidVertex(i))
296 {
297 // HashTable expects 32bit
298 HashTable.Add_Concurrent( static_cast<uint32>(HashPosition(Mesh.GetVertexPosition(i))), i );
299 }
300 }, ParallelForFlags );
301
302 ParallelFor( TEXT("TAdaptiveTessellator.HashVerts.PF"), Mesh.MaxVertexID(), 4096,
303 [&]( int32 VID )
304 {
305 if (!Mesh.IsValidVertex(VID))
306 {
307 return;
308 }
309
310 VecType Average( 0.0 );
311 int32 Count = 0;
312
313 uint32 Hash = static_cast<uint32>(HashPosition( Mesh.GetVertexPosition(VID) ));
314 for( uint32 OtherIndex = HashTable.First( Hash ); HashTable.IsValid( OtherIndex ); OtherIndex = HashTable.Next( OtherIndex ) )
315 {
316 if( Mesh.GetVertexPosition(VID) == Mesh.GetVertexPosition(OtherIndex) )
317 {
319 Count++;
320 }
321 }
322
323 Displacements[VID] = Average / Count;
324 }, ParallelForFlags );
325 }
326
327 if (Options.bFinalDisplace)
328 {
329 ParallelFor(TEXT("TAdaptiveTessellator.Displace.PF"), Mesh.MaxVertexID(), 4096,
330 [&](int32 VID)
331 {
332 if (Mesh.IsValidVertex(VID))
333 {
334 if (ensure(VectorUtil::IsFinite(Displacements[VID])))
335 {
336 Mesh.SetVertexPosition(VID, Mesh.GetVertexPosition(VID) + Displacements[VID]);
337 }
338 }
340 }
341
342 // Free memory
343 Displacements.Empty();
344 check(Displacements.Max() == 0);
345
346 TriSplitRecords.Empty();
347 FindRequests.Empty();
348 SplitRequests.Empty();
349 }
350
351private:
352 inline bool CouldFlipEdge( int32 TriIndex, int32 TriEdgeIndex ) const
353 {
354 // Don't flip boundary edges
355 const int32 AdjTriIndex = Mesh.GetAdjTriangle(TriIndex, TriEdgeIndex);
356 if (AdjTriIndex < 0)
357 {
358 return false;
359 }
360
361 if (!Mesh.EdgeManifoldCheck(TriIndex, TriEdgeIndex))
362 {
363 return false;
364 }
365
366 if (!Mesh.AllowEdgeFlip(TriIndex, TriEdgeIndex, AdjTriIndex))
367 {
368 return false;
369 }
370
371 const VecType TriNormal = Mesh.GetTriangleNormal( TriIndex );
372 const VecType AdjNormal = Mesh.GetTriangleNormal( AdjTriIndex );
373
374 return ( TriNormal | AdjNormal ) > RealType(0.999);
375 }
376
377 void FindSplitBVH( uint32 TriIndex );
378
379 void SplitTriangle(uint32 TriIndex);
380
381 inline void SetDisplacementAt(int32 VertexIndex, VecType Displacement)
382 {
383 if (VertexIndex >= Displacements.Num())
384 {
385 // this should be progressive growth behavior
386 Displacements.AddUninitialized(1 + VertexIndex - Displacements.Num());
387 }
388 Displacements[VertexIndex] = Displacement;
389 }
390
391 inline void GrowTriRecords(int32 Num)
392 {
393 if (Num > TriSplitRecords.Num())
394 {
395 TriSplitRecords.AddDefaulted(Num - TriSplitRecords.Num());
396 }
397 }
398
399 struct FTriSplit
400 {
401 VecType SplitBarycentrics;
402 int32 RequestIndex = -1;
403 };
404
405 void AddFindRequest(int32 TriIndex);
406
407 void CheckSplitAndEnqueue( int32 TriIndex, int32 Level ); // thread-safe
408
409 void EnqueueSplitRequest( int32 TriIndex, VecType Barycentrics ); // thread-safe
410
411 void RemoveSplitRequest(int32 TriIndex);
412
413 void TryDelaunayFlip( int32 TriIndex, int TriEdgeIndex );
414
415 MeshT& Mesh;
416 DisplacementPolicyT& DisplacementPolicy;
417 const FOptions& Options;
418
419 TArray<VecType> Displacements;
420 TArray<FTriSplit> TriSplitRecords;
421 TArray<uint32> FindRequests;
422 TArray<uint32> SplitRequests; // triangle indices
423
424 std::atomic<uint32> NumSplits;
425 std::atomic<uint32> NumNewTriangles;
426};
427
428template <typename MeshT, typename DisplacementPolicyT>
429inline void TAdaptiveTessellator<MeshT, DisplacementPolicyT>::FindSplitBVH( uint32 TriIndex )
430{
431 const VecType P0 = Mesh.GetVertexPosition(TriIndex, 0);
432 const VecType P1 = Mesh.GetVertexPosition(TriIndex, 1);
433 const VecType P2 = Mesh.GetVertexPosition(TriIndex, 2);
434
435 const VecType Edge01 = P1 - P0;
436 const VecType Edge12 = P2 - P1;
437 const VecType Edge20 = P0 - P2;
438
439 const VecType EdgeLengths( Edge01.Length(), Edge12.Length(), Edge20.Length() );
440
441 if( EdgeLengths[0] < Options.SampleRate &&
442 EdgeLengths[1] < Options.SampleRate &&
443 EdgeLengths[2] < Options.SampleRate )
444 {
445 return;
446 }
447
448 const float TriArea = 0.5f * ( Edge01 ^ Edge20 ).Size();
449
450 // area of equilateral triangle with edgelength = SampleRate. We want to have 1 sample per triangle of this size
451 const float SampleArea = EquilateralArea( Options.SampleRate );
452
453 const FIndex3i Triangle = Mesh.GetTriangle(TriIndex);
454
455 const VecType& Displacement0 = Displacements[ Triangle.A ];
456 const VecType& Displacement1 = Displacements[ Triangle.B ];
457 const VecType& Displacement2 = Displacements[ Triangle.C ];
458
459 bool bCouldFlipEdge[3];
460 for( uint32 EdgeIndex = 0; EdgeIndex < 3; EdgeIndex++ )
461 bCouldFlipEdge[ EdgeIndex ] = CouldFlipEdge( TriIndex, EdgeIndex );
462
463 VecType BestSplit = VecType::ZeroVector;
464 RealType BestError = -1.0f;
465
466 struct FNode
467 {
468 RealType ErrorMin;
469 RealType ErrorMax;
470 uint32 TriX : 13;
471 uint32 TriY : 13;
472 uint32 FlipTri : 1;
473 uint32 Level : 4;
474 };
475
476 TArray<FNode, TInlineAllocator<256>> Candidates; // heap, element with largest maxerror at the root
477
478 FNode Node;
479 Node.ErrorMin = 0.0f;
480 Node.ErrorMax = MAX_flt;
481 Node.TriX = 0;
482 Node.TriY = 0;
483 Node.FlipTri = 0;
484 Node.Level = 0;
485
486 {
487 VecType Barycentrics[3] =
488 {
489 { 1.0f, 0.0f, 0.0f },
490 { 0.0f, 1.0f, 0.0f },
491 { 0.0f, 0.0f, 1.0f },
492 };
493
494 const UE::Math::TVector2<RealType> ErrorBounds = DisplacementPolicy.GetErrorBounds(
499 TriIndex );
500
501 Node.ErrorMin = ErrorBounds.X;
502 Node.ErrorMax = ErrorBounds.Y;
503 }
504
505 RealType ErrorMinimum = Options.TargetError;
506
507 while( Node.ErrorMax >= ErrorMinimum &&
508 Node.ErrorMax > BestError * 1.25f + Options.TargetError )
509 {
510 for( uint32 ChildIndex = 0; ChildIndex < 4; ChildIndex++ )
511 {
512 FNode ChildNode;
513 ChildNode.Level = Node.Level + 1;
514 ChildNode.FlipTri = Node.FlipTri;
515
516 /*
517 |\ \|\|
518 |\|\ \|
519 */
520
521 ChildNode.TriX = Node.TriX * 2 + ( ChildIndex & 1 );
522 ChildNode.TriY = Node.TriY * 2 + ( ChildIndex >> 1 );
523
524 if( Node.FlipTri )
525 {
526 ChildNode.TriX ^= 1;
527 ChildNode.TriY ^= 1;
528 }
529
530 if( ChildIndex == 3 )
531 {
532 ChildNode.TriX ^= 1;
533 ChildNode.TriY ^= 1;
534 ChildNode.FlipTri ^= 1;
535 }
536
537 VecType Barycentrics[3];
539
540 // Get the error bounds of the child triangle
541 {
542 UE::Math::TVector2<RealType> ErrorBounds = DisplacementPolicy.GetErrorBounds(
547 TriIndex );
548
549 ChildNode.ErrorMin = ErrorBounds.X;
550 ChildNode.ErrorMax = ErrorBounds.Y;
551
552 // Clamp bounds just in case float precision results in them not perfectly nesting
553 ChildNode.ErrorMin = FMath::Max( ChildNode.ErrorMin, Node.ErrorMin );
554 ChildNode.ErrorMax = FMath::Min( ChildNode.ErrorMax, Node.ErrorMax );
555 }
556
557 // ErrorMinimum is the minimum of TargetError and the smallest error achieved so far by any created children
558 if( ErrorMinimum > ChildNode.ErrorMax )
559 continue;
560
561 if( ErrorMinimum < ChildNode.ErrorMin )
562 ErrorMinimum = ChildNode.ErrorMin;
563
564 if( ChildNode.ErrorMax > BestError * 1.25f + Options.TargetError )
565 {
566 int32 NumSamples = 64;
567
568 bool bStopTraversal = ChildNode.Level == 12 || ChildNode.ErrorMax - ChildNode.ErrorMin < Options.TargetError;
569 if( !bStopTraversal )
570 {
572
573 const bool bSmallEnough = ChildArea < SampleArea * 16.0f;
574 NumSamples = FMath::Min( NumSamples, FMath::CeilToInt( ChildArea / SampleArea ) );
575
577 }
578
579 if( !bStopTraversal )
580 {
581 NumSamples = FMath::Min( NumSamples, DisplacementPolicy.GetNumSamples(Barycentrics, Triangle, TriIndex));
582 bStopTraversal = NumSamples <= 16;
583 }
584
585 if( bStopTraversal )
586 {
587 for( int32 i = 0; i < NumSamples; i++ )
588 {
589 // Hammersley sequence
591 Reverse = Reverse ^ (Reverse >> 1);
592
594
595 // todo see UniformSampleTrianglePoint
596 Random.X = RealType( i + 1 ) / RealType( NumSamples + 1 );
597 Random.Y = RealType( Reverse >> 8 ) * 0x1p-24f;
598
599 // Square to triangle
600 if( Random.X < Random.Y )
601 {
602 Random.X *= 0.5f;
603 Random.Y -= Random.X;
604 }
605 else
606 {
607 Random.Y *= 0.5f;
608 Random.X -= Random.Y;
609 }
610
611 VecType Split;
612 Split.X = Random.X;
613 Split.Y = Random.Y;
614 Split.Z = 1.0f - Split.X - Split.Y;
615
616 RealType DistToEdge[3];
620
621 uint32 e0 = FMath::Min3Index( DistToEdge[0], DistToEdge[1], DistToEdge[2] ); // index of barycentric coordinate that is closest to any edge
622 uint32 e1 = (1 << e0) & 3;
623 uint32 e2 = (1 << e1) & 3;
624
625 CA_ASSUME(e1 <= 2);
626 CA_ASSUME(e2 <= 2);
627
628 bool bTooCloseToEdge = DistToEdge[ e0 ] < 0.5f * Options.SampleRate;
630 {
631 Split[ e0 ] = Split[ e0 ] / ( Split[ e0 ] + Split[ e1 ] );
632 Split[ e1 ] = 1.0f - Split[ e0 ];
633 Split[ e2 ] = 0.0f;
634
635 bool bTooCloseToEdge1 = !bCouldFlipEdge[ e1 ] && DistanceToEdge<RealType>( Split[ e0 ], EdgeLengths[ e1 ], TriArea ) < 0.5f * Options.SampleRate;
636 bool bTooCloseToEdge2 = !bCouldFlipEdge[ e2 ] && DistanceToEdge<RealType>( Split[ e1 ], EdgeLengths[ e2 ], TriArea ) < 0.5f * Options.SampleRate;
638 }
639
640 if( !bTooCloseToEdge )
641 {
642 const VecType NewDisplacement = DisplacementPolicy.GetDisplacement(Split, TriIndex);
643
644 VecType LerpedDisplacement;
648
649 const RealType Error = ( NewDisplacement - LerpedDisplacement ).SizeSquared();
650
651 if( BestError < Error )
652 {
655 }
656 }
657 }
658 }
659 else
660 {
661 // bStopTraversal == false, push the node on the traversal heap (with the largest error at the root)
662 Candidates.HeapPush( ChildNode,
663 [&]( const FNode& Node0, const FNode& Node1 )
664 {
665 return Node0.ErrorMax > Node1.ErrorMax;
666 } );
667 }
668 }
669 }
670
671 if( Candidates.IsEmpty() )
672 break;
673
674 Candidates.HeapPop( Node,
675 [&]( const FNode& Node0, const FNode& Node1 )
676 {
677 return Node0.ErrorMax > Node1.ErrorMax;
679 }
680
681 if( BestError > Options.TargetError )
682 {
683 EnqueueSplitRequest( TriIndex, BestSplit );
684 }
685}
686
687template <typename MeshT, typename DisplacementPolicyT>
688inline void TAdaptiveTessellator<MeshT, DisplacementPolicyT>::SplitTriangle( uint32 TriIndex )
689{
690 const VecType Barycentrics = TriSplitRecords[ TriIndex ].SplitBarycentrics;
691 TriSplitRecords[ TriIndex ].RequestIndex = -1;
692
693 // first check whether we are splitting along an edge
694 for( uint32 EdgeIndex = 0; EdgeIndex < 3; EdgeIndex++ )
695 {
696 if( Barycentrics[ ( EdgeIndex + 2 ) % 3 ] == RealType(0) )
697 {
698 if (!Mesh.AllowEdgeSplit(TriIndex, EdgeIndex))
699 {
700 return;
701 }
702
703 const FEdgeSplitInfo SplitInfo = Mesh.SplitEdge( TriIndex, EdgeIndex, Barycentrics[EdgeIndex] );
704
705 if (SplitInfo.NewTriIndex[0] == IndexConstants::InvalidID)
706 {
707 return;
708 }
709
710 int NumNewTris = 0;
711 // evaluate the displacement at the newly created vertices, possibly material seam
712 // requires to evaluate displacement twice
713 for( int32 j = 0; j < 2; j++ )
714 {
715 if (SplitInfo.OldTriIndex[j] >= 0)
716 {
717 const VecType NewDisplacement = DisplacementPolicy.GetVertexDisplacement(SplitInfo.NewVertIndex[j], SplitInfo.OldTriIndex[j]);
718
719 // handle cases where split edge introduces 1 or 2 vertices at the edge. if 1, average the results
720 if (j == 1 && SplitInfo.NewVertIndex[1] == SplitInfo.NewVertIndex[0])
721 {
722 VecType& D = Displacements[SplitInfo.NewVertIndex[1]];
723 D = 0.5f * (D + NewDisplacement);
724 }
725 else
726 {
727 SetDisplacementAt(SplitInfo.NewVertIndex[j], NewDisplacement);
728 }
729
730 ++NumNewTris;
731 }
732 }
733
734 GrowTriRecords(FMath::Max(SplitInfo.NewTriIndex[0], SplitInfo.NewTriIndex[1])+1);
735 check(TriSplitRecords.Num() <= Mesh.MaxTriID());
736
737 for( int32 j = 0; j < 2; j++ )
738 {
739 if (SplitInfo.OldTriIndex[j] >= 0)
740 {
741 RemoveSplitRequest( SplitInfo.OldTriIndex[j] );
742
743 AddFindRequest( SplitInfo.OldTriIndex[j] );
744 AddFindRequest( SplitInfo.NewTriIndex[j] );
745
746 TryDelaunayFlip( SplitInfo.OldTriIndex[j], 1 );
747 TryDelaunayFlip( SplitInfo.NewTriIndex[j], 2 );
748 }
749 }
750
751 // we have performed an edge-split and are done here
752 return;
753 }
754 }
755
756 // regular interior point split (poke)
757 const VecType NewDisplacement = DisplacementPolicy.GetDisplacement(Barycentrics, TriIndex);
758 const FPokeInfo pokeInfo = Mesh.PokeTriangle(TriIndex, Barycentrics);
759
760 if (pokeInfo.NewTriIndex[0] != IndexConstants::InvalidID)
761 {
762 check(pokeInfo.NewTriIndex[0] == TriIndex);
763
764 GrowTriRecords(FMath::Max(pokeInfo.NewTriIndex[1], pokeInfo.NewTriIndex[2]) + 1);
765 check(pokeInfo.NewTriIndex[1] < Mesh.MaxTriID());
766 check(pokeInfo.NewTriIndex[2] < Mesh.MaxTriID());
767
768 SetDisplacementAt(pokeInfo.NewVertIndex, NewDisplacement);
769
771 {
772 AddFindRequest(pokeInfo.NewTriIndex[LocalTriIndex]);
773 TryDelaunayFlip(pokeInfo.NewTriIndex[LocalTriIndex], pokeInfo.EdgeIndex[LocalTriIndex]);
774 }
775 }
776}
777
778template <typename MeshT, typename DisplacementPolicyT>
779inline void TAdaptiveTessellator<MeshT, DisplacementPolicyT>::AddFindRequest( int32 TriIndex )
780{
781 check(TriIndex < TriSplitRecords.Num());
782 int32& RequestIndex = TriSplitRecords[ TriIndex ].RequestIndex;
783 if( RequestIndex != -2 )
784 {
785 check( RequestIndex == -1 );
786 FindRequests.Add( TriIndex );
787 RequestIndex = -2;
788 }
789}
790
791template <typename MeshT, typename DisplacementPolicyT>
792inline void TAdaptiveTessellator<MeshT, DisplacementPolicyT>::CheckSplitAndEnqueue( const int32 TriIndex, const int32 Level )
793{
794 TriSplitRecords[ TriIndex ].RequestIndex = -1;
795
796 if ( Options.RefinementMethod == ERefinementMethod::HierarchicalSplit )
797 {
798 FindSplitBVH( TriIndex );
799 }
800 else
801 {
802 VecType SplitVertexBary;
803 if (DisplacementPolicy.ShouldRefine( TriIndex, Displacements, SplitVertexBary, Level ))
804 {
805 EnqueueSplitRequest( TriIndex, SplitVertexBary );
806 }
807 }
808}
809
810template <typename MeshT, typename DisplacementPolicyT>
811inline void TAdaptiveTessellator<MeshT, DisplacementPolicyT>::EnqueueSplitRequest( int32 TriIndex, VecType Barycentrics )
812{
814 if (Barycentrics[0] == RealType(0) || Barycentrics[1] == RealType(0) || Barycentrics[2] == RealType(0) )
815 {
817 }
818
819 if ( NumNewTriangles.fetch_add(AddTrianglesPerRefine) < Options.MaxTriangles - Mesh.MaxTriID() )
820 {
821 TriSplitRecords[ TriIndex ].SplitBarycentrics = Barycentrics;
822 TriSplitRecords[ TriIndex ].RequestIndex = NumSplits++;
823 SplitRequests[ TriSplitRecords[ TriIndex ].RequestIndex ] = TriIndex;
824 }
825}
826
827template <typename MeshT, typename DisplacementPolicyT>
828inline void TAdaptiveTessellator<MeshT, DisplacementPolicyT>::RemoveSplitRequest( int32 TriIndex )
829{
830 int32& RequestIndex = TriSplitRecords[ TriIndex ].RequestIndex;
831 if( RequestIndex >= 0 )
832 {
833 TriSplitRecords[ SplitRequests.Last() ].RequestIndex = RequestIndex;
834 SplitRequests.RemoveAtSwap( RequestIndex, EAllowShrinking::No );
835 RequestIndex = -1;
836 }
837}
838
839template <typename MeshT, typename DisplacementPolicyT>
840inline void TAdaptiveTessellator<MeshT, DisplacementPolicyT>::TryDelaunayFlip( int32 TriIndex, int32 TriEdgeIndex )
841{
842 if( !CouldFlipEdge( TriIndex, TriEdgeIndex ) )
843 return;
844
845 auto ComputeCotangent = [this]( uint32 TriIndex, int TriEdgeIndex ) -> RealType
846 {
847 const uint32 e0 = TriEdgeIndex;
848 const uint32 e1 = NextEdge(TriEdgeIndex);
849 const uint32 e2 = PrevEdge(TriEdgeIndex);
850
851 const VecType P0 = Mesh.GetVertexPosition(TriIndex, e0);
852 const VecType P1 = Mesh.GetVertexPosition(TriIndex, e1);
853 const VecType P2 = Mesh.GetVertexPosition(TriIndex, e2);
854
855 const VecType Edge01 = P1 - P0;
856 const VecType Edge12 = P2 - P1;
857 const VecType Edge20 = P0 - P2;
858
859 VecType EdgeLengthsSqr(
860 Edge01.SizeSquared(),
861 Edge12.SizeSquared(),
862 Edge20.SizeSquared() );
863
864 const RealType TriArea = RealType(0.5) * ( Edge01 ^ Edge20 ).Size();
865 return RealType(0.25) * ( -EdgeLengthsSqr.X + EdgeLengthsSqr.Y + EdgeLengthsSqr.Z ) / TriArea;
866 };
867
868 const TPair<int32, int32> OppositeEdge = Mesh.GetAdjEdge(TriIndex, TriEdgeIndex);
869
870 const RealType LaplacianWeight = RealType(0.5) * (ComputeCotangent( TriIndex, TriEdgeIndex ) +
871 ComputeCotangent( OppositeEdge.Get<0>(), OppositeEdge.Get<1>() ));
872
873 const bool bFlipEdge = LaplacianWeight < RealType(-1e-6);
874 if( bFlipEdge )
875 {
876 const FFlipEdgeInfo FlipEdgeInfo = Mesh.FlipEdge(TriIndex, TriEdgeIndex);
877
878 if (FlipEdgeInfo.Triangles[0] != IndexConstants::InvalidID)
879 {
881
882 const int32 AdjTriIndex = OppositeEdge.Get<0>();
883 const int32 AdjTriEdgeIndex = OppositeEdge.Get<1>();
884
885 RemoveSplitRequest(TriIndex);
886 RemoveSplitRequest(AdjTriIndex);
887
888 AddFindRequest(TriIndex);
889 AddFindRequest(AdjTriIndex);
890
891 TryDelaunayFlip(FlipEdgeInfo.Triangles[0], NextEdge(FlipEdgeInfo.SharedEdge[0]));
892 TryDelaunayFlip(FlipEdgeInfo.Triangles[0], PrevEdge(FlipEdgeInfo.SharedEdge[0]));
893
894 TryDelaunayFlip(FlipEdgeInfo.Triangles[1], PrevEdge(FlipEdgeInfo.SharedEdge[1]));
895 TryDelaunayFlip(FlipEdgeInfo.Triangles[1], NextEdge(FlipEdgeInfo.SharedEdge[1]));
896 }
897 }
898}
899
900} // namespace Geometry
901} // namespace UE
#define check(expr)
Definition AssertionMacros.h:314
#define ensure( InExpression)
Definition AssertionMacros.h:464
#define CA_ASSUME(Expr)
Definition CoreMiscDefines.h:126
EParallelForFlags
Definition ParallelFor.h:47
void ParallelFor(int32 Num, TFunctionRef< void(int32)> Body, bool bForceSingleThread, bool bPumpRenderingThread=false)
Definition ParallelFor.h:481
uint64 Murmur64(std::initializer_list< uint64 > InitList)
Definition HashTable.h:60
uint32 Murmur32(std::initializer_list< uint32 > InitList)
Definition HashTable.h:43
#define TEXT(x)
Definition Platform.h:1272
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
#define TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(Name)
Definition CpuProfilerTrace.h:532
@ Num
Definition MetalRHIPrivate.h:234
#define MAX_flt
Definition NumericLimits.h:29
#define Split(a, ahi, alo)
Definition Predicates.inl:204
uint32 Size
Definition VulkanMemory.cpp:4034
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition HashTable.h:210
Definition Array.h:670
UE_FORCEINLINE_HINT SizeType AddUninitialized()
Definition Array.h:1664
UE_REWRITE SizeType Num() const
Definition Array.h:1144
void Reset(SizeType NewSize=0)
Definition Array.h:2246
SizeType HeapPush(ElementType &&InItem, const PREDICATE_CLASS &Predicate)
Definition Array.h:3671
UE_REWRITE bool IsEmpty() const
Definition Array.h:1133
void HeapPop(ElementType &OutItem, const PREDICATE_CLASS &Predicate, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:3748
void SetNum(SizeType NewNum, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2308
void Init(const ElementType &Element, SizeType Number)
Definition Array.h:3043
ElementType Pop(EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:1196
UE_NODEBUG void Sort()
Definition Array.h:3418
Definition AdaptiveTessellator.h:58
typename MeshT::VecType VecType
Definition AdaptiveTessellator.h:163
typename MeshT::RealType RealType
Definition AdaptiveTessellator.h:162
TAdaptiveTessellator(MeshT &InMesh, DisplacementPolicyT &InDisplacementPolicy, const FOptions &InOptions)
Definition AdaptiveTessellator.h:189
ERefinementMethod
Definition AdaptiveTessellator.h:156
ECompressionLevel Level
Definition OodleDataCompression.cpp:70
constexpr int InvalidID
Definition IndexTypes.h:13
const FName EdgeIndex("EdgeIndex")
Definition MeshAttributes.h:40
const FName VertexIndex("VertexIndex")
Definition MeshAttributes.h:28
double SizeSquared(const T &Value)
Definition SplineMath.h:172
bool IsFinite(const TVector2< RealType > &V)
Definition VectorUtil.h:42
Definition AdvancedWidgetsModule.cpp:13
static constexpr UE_FORCEINLINE_HINT T Square(const T A)
Definition UnrealMathUtility.h:578
static constexpr UE_FORCEINLINE_HINT int32 Min3Index(const T A, const T B, const T C)
Definition UnrealMathUtility.h:571
Definition Tuple.h:652
Definition AdaptiveTessellator.h:29
FIndex2i OldTriIndex
Definition AdaptiveTessellator.h:31
FIndex2i NewVertIndex
Definition AdaptiveTessellator.h:33
FIndex2i NewTriIndex
Definition AdaptiveTessellator.h:32
Definition AdaptiveTessellator.h:45
FIndex2i SharedEdge
Definition AdaptiveTessellator.h:48
FIndex2i Triangles
Definition AdaptiveTessellator.h:47
Definition IndexTypes.h:27
Definition IndexTypes.h:158
Definition AdaptiveTessellator.h:37
FIndex3i NewTriIndex
Definition AdaptiveTessellator.h:39
int32 NewVertIndex
Definition AdaptiveTessellator.h:41
FIndex3i EdgeIndex
Definition AdaptiveTessellator.h:40
Definition AdaptiveTessellator.h:166
RealType SampleRate
Definition AdaptiveTessellator.h:172
bool bSingleThreaded
Definition AdaptiveTessellator.h:185
bool bFinalDisplace
Definition AdaptiveTessellator.h:178
ERefinementMethod RefinementMethod
Definition AdaptiveTessellator.h:183
bool bCrackFree
Definition AdaptiveTessellator.h:175
uint32 MaxTriangles
Definition AdaptiveTessellator.h:181
RealType TargetError
Definition AdaptiveTessellator.h:168
Definition Vector2D.h:38
T X
Definition Vector2D.h:49