// Copyright Epic Games, Inc. All Rights Reserved. #include "Cluster.h" #include "GraphPartitioner.h" template< typename T > FORCEINLINE uint32 Min3Index( const T A, const T B, const T C ) { return ( A < B ) ? ( ( A < C ) ? 0 : 2 ) : ( ( B < C ) ? 1 : 2 ); } template< typename T > FORCEINLINE uint32 Max3Index( const T A, const T B, const T C ) { return ( A > B ) ? ( ( A > C ) ? 0 : 2 ) : ( ( B > C ) ? 1 : 2 ); } namespace Nanite { void CorrectAttributes( float* Attributes ) { FVector3f& Normal = *reinterpret_cast< FVector3f* >( Attributes ); Normal.Normalize(); } void CorrectAttributesColor( float* Attributes ) { CorrectAttributes( Attributes ); FLinearColor& Color = *reinterpret_cast< FLinearColor* >( Attributes + 3 ); Color = Color.GetClamped(); } FCluster::FCluster( const TArray< FStaticMeshBuildVertex >& InVerts, const TArrayView< const uint32 >& InIndexes, const TArrayView< const int32 >& InMaterialIndexes, uint32 InNumTexCoords, bool bInHasColors, uint32 TriBegin, uint32 TriEnd, const FGraphPartitioner& Partitioner, const FAdjacency& Adjacency ) { GUID = (uint64(TriBegin) << 32) | TriEnd; NumTris = TriEnd - TriBegin; //ensure(NumTriangles <= FCluster::ClusterSize); bHasColors = bInHasColors; NumTexCoords = InNumTexCoords; Verts.Reserve( NumTris * GetVertSize() ); Indexes.Reserve( 3 * NumTris ); MaterialIndexes.Reserve( NumTris ); ExternalEdges.Reserve( 3 * NumTris ); NumExternalEdges = 0; check(InMaterialIndexes.Num() * 3 == InIndexes.Num()); TMap< uint32, uint32 > OldToNewIndex; OldToNewIndex.Reserve( NumTris ); for( uint32 i = TriBegin; i < TriEnd; i++ ) { uint32 TriIndex = Partitioner.Indexes[i]; for( uint32 k = 0; k < 3; k++ ) { uint32 OldIndex = InIndexes[ TriIndex * 3 + k ]; uint32* NewIndexPtr = OldToNewIndex.Find( OldIndex ); uint32 NewIndex = NewIndexPtr ? *NewIndexPtr : ~0u; if( NewIndex == ~0u ) { Verts.AddUninitialized( GetVertSize() ); NewIndex = NumVerts++; OldToNewIndex.Add( OldIndex, NewIndex ); const FStaticMeshBuildVertex& InVert = InVerts[ OldIndex ]; GetPosition( NewIndex ) = InVert.Position; GetNormal( NewIndex ) = InVert.TangentZ.ContainsNaN() ? FVector3f::UpVector : InVert.TangentZ; if( bHasColors ) { GetColor( NewIndex ) = InVert.Color.ReinterpretAsLinear(); } FVector2f* UVs = GetUVs( NewIndex ); for( uint32 UVIndex = 0; UVIndex < NumTexCoords; UVIndex++ ) { UVs[ UVIndex ] = InVert.UVs[ UVIndex ].ContainsNaN() ? FVector2f::ZeroVector : InVert.UVs[ UVIndex ]; } float* Attributes = GetAttributes( NewIndex ); // Make sure this vertex is valid from the start if( bHasColors ) CorrectAttributesColor( Attributes ); else CorrectAttributes( Attributes ); } Indexes.Add( NewIndex ); int32 EdgeIndex = TriIndex * 3 + k; int32 AdjCount = 0; Adjacency.ForAll( EdgeIndex, [ &AdjCount, TriBegin, TriEnd, &Partitioner ]( int32 EdgeIndex, int32 AdjIndex ) { uint32 AdjTri = Partitioner.SortedTo[ AdjIndex / 3 ]; if( AdjTri < TriBegin || AdjTri >= TriEnd ) AdjCount++; } ); ExternalEdges.Add( AdjCount ); NumExternalEdges += AdjCount != 0 ? 1 : 0; } MaterialIndexes.Add( InMaterialIndexes[ TriIndex ] ); } Bound(); } // Split FCluster::FCluster( FCluster& SrcCluster, uint32 TriBegin, uint32 TriEnd, const FGraphPartitioner& Partitioner, const FAdjacency& Adjacency ) : MipLevel( SrcCluster.MipLevel ) { GUID = MurmurFinalize64(SrcCluster.GUID) ^ ((uint64(TriBegin) << 32) | TriEnd); NumTexCoords = SrcCluster.NumTexCoords; bHasColors = SrcCluster.bHasColors; NumTris = TriEnd - TriBegin; Verts.Reserve( NumTris * GetVertSize() ); Indexes.Reserve( 3 * NumTris ); MaterialIndexes.Reserve( NumTris ); ExternalEdges.Reserve( 3 * NumTris ); NumExternalEdges = 0; TMap< uint32, uint32 > OldToNewIndex; OldToNewIndex.Reserve( NumTris ); for( uint32 i = TriBegin; i < TriEnd; i++ ) { uint32 TriIndex = Partitioner.Indexes[i]; for( uint32 k = 0; k < 3; k++ ) { uint32 OldIndex = SrcCluster.Indexes[ TriIndex * 3 + k ]; uint32* NewIndexPtr = OldToNewIndex.Find( OldIndex ); uint32 NewIndex = NewIndexPtr ? *NewIndexPtr : ~0u; if( NewIndex == ~0u ) { Verts.AddUninitialized( GetVertSize() ); NewIndex = NumVerts++; OldToNewIndex.Add( OldIndex, NewIndex ); FMemory::Memcpy( &GetPosition( NewIndex ), &SrcCluster.GetPosition( OldIndex ), GetVertSize() * sizeof( float ) ); } Indexes.Add( NewIndex ); int32 EdgeIndex = TriIndex * 3 + k; int32 AdjCount = SrcCluster.ExternalEdges[ EdgeIndex ]; Adjacency.ForAll( EdgeIndex, [ &AdjCount, TriBegin, TriEnd, &Partitioner ]( int32 EdgeIndex, int32 AdjIndex ) { uint32 AdjTri = Partitioner.SortedTo[ AdjIndex / 3 ]; if( AdjTri < TriBegin || AdjTri >= TriEnd ) AdjCount++; } ); ExternalEdges.Add( AdjCount ); NumExternalEdges += AdjCount != 0 ? 1 : 0; } const int32 MaterialIndex = SrcCluster.MaterialIndexes[ TriIndex ]; MaterialIndexes.Add( MaterialIndex ); } Bound(); } // Merge FCluster::FCluster( const TArray< const FCluster*, TInlineAllocator<32> >& MergeList ) { NumTexCoords = MergeList[0]->NumTexCoords; bHasColors = MergeList[0]->bHasColors; const uint32 NumTrisGuess = ClusterSize * MergeList.Num(); Verts.Reserve( NumTrisGuess * GetVertSize() ); Indexes.Reserve( 3 * NumTrisGuess ); MaterialIndexes.Reserve( NumTrisGuess ); ExternalEdges.Reserve( 3 * NumTrisGuess ); NumExternalEdges = 0; FHashTable VertHashTable( 1 << FMath::FloorLog2( NumTrisGuess ), NumTrisGuess ); for( const FCluster* Child : MergeList ) { Bounds += Child->Bounds; SurfaceArea += Child->SurfaceArea; // Can jump multiple levels but guarantee it steps at least 1. MipLevel = FMath::Max( MipLevel, Child->MipLevel + 1 ); for( int32 i = 0; i < Child->Indexes.Num(); i++ ) { uint32 NewIndex = AddVert( &Child->Verts[ Child->Indexes[i] * GetVertSize() ], VertHashTable ); Indexes.Add( NewIndex ); ExternalEdges.Add( Child->ExternalEdges[i] ); } for( int32 i = 0; i < Child->MaterialIndexes.Num(); i++ ) { const int32 MaterialIndex = Child->MaterialIndexes[i]; MaterialIndexes.Add( MaterialIndex ); } GUID = MurmurFinalize64(GUID) ^ Child->GUID; } FAdjacency Adjacency = BuildAdjacency(); int32 ChildIndex = 0; int32 MinIndex = 0; int32 MaxIndex = MergeList[0]->ExternalEdges.Num(); for( int32 EdgeIndex = 0; EdgeIndex < ExternalEdges.Num(); EdgeIndex++ ) { if( EdgeIndex >= MaxIndex ) { ChildIndex++; MinIndex = MaxIndex; MaxIndex += MergeList[ ChildIndex ]->ExternalEdges.Num(); } int32 AdjCount = ExternalEdges[ EdgeIndex ]; Adjacency.ForAll( EdgeIndex, [ &AdjCount, MinIndex, MaxIndex ]( int32 EdgeIndex, int32 AdjIndex ) { if( AdjIndex < MinIndex || AdjIndex >= MaxIndex ) AdjCount--; } ); // This seems like a sloppy workaround for a bug elsewhere but it is possible an interior edge is moved during simplifiation to // match another cluster and it isn't reflected in this count. Sounds unlikely but any hole closing could do this. // The only way to catch it would be to rebuild full adjacency after every pass which isn't practical. AdjCount = FMath::Max( AdjCount, 0 ); ExternalEdges[ EdgeIndex ] = AdjCount; NumExternalEdges += AdjCount != 0 ? 1 : 0; } NumTris = Indexes.Num() / 3; } float FCluster::Simplify( uint32 TargetNumTris, float TargetError, uint32 LimitNumTris ) { if( ( TargetNumTris >= NumTris && TargetError == 0.0f ) || LimitNumTris >= NumTris ) { return 0.0f; } float UVArea[ MAX_STATIC_TEXCOORDS ] = { 0.0f }; for( uint32 TriIndex = 0; TriIndex < NumTris; TriIndex++ ) { uint32 Index0 = Indexes[ TriIndex * 3 + 0 ]; uint32 Index1 = Indexes[ TriIndex * 3 + 1 ]; uint32 Index2 = Indexes[ TriIndex * 3 + 2 ]; FVector2f* UV0 = GetUVs( Index0 ); FVector2f* UV1 = GetUVs( Index1 ); FVector2f* UV2 = GetUVs( Index2 ); for( uint32 UVIndex = 0; UVIndex < NumTexCoords; UVIndex++ ) { FVector2f EdgeUV1 = UV1[ UVIndex ] - UV0[ UVIndex ]; FVector2f EdgeUV2 = UV2[ UVIndex ] - UV0[ UVIndex ]; float SignedArea = 0.5f * ( EdgeUV1 ^ EdgeUV2 ); UVArea[ UVIndex ] += FMath::Abs( SignedArea ); // Force an attribute discontinuity for UV mirroring edges. // Quadric could account for this but requires much larger UV weights which raises error on meshes which have no visible issues otherwise. MaterialIndexes[ TriIndex ] |= ( SignedArea >= 0.0f ? 1 : 0 ) << ( UVIndex + 24 ); } } float TriangleSize = FMath::Sqrt( SurfaceArea / NumTris ); FFloat32 CurrentSize( FMath::Max( TriangleSize, THRESH_POINTS_ARE_SAME ) ); FFloat32 DesiredSize( 0.25f ); FFloat32 FloatScale( 1.0f ); // Lossless scaling by only changing the float exponent. int32 Exponent = FMath::Clamp( (int)DesiredSize.Components.Exponent - (int)CurrentSize.Components.Exponent, -126, 127 ); FloatScale.Components.Exponent = Exponent + 127; //ExpBias // Scale ~= DesiredSize / CurrentSize float PositionScale = FloatScale.FloatValue; for( uint32 i = 0; i < NumVerts; i++ ) { GetPosition(i) *= PositionScale; } TargetError *= PositionScale; uint32 NumAttributes = GetVertSize() - 3; float* AttributeWeights = (float*)FMemory_Alloca( NumAttributes * sizeof( float ) ); // Normal AttributeWeights[0] = 1.0f; AttributeWeights[1] = 1.0f; AttributeWeights[2] = 1.0f; if( bHasColors ) { float* ColorWeights = AttributeWeights + 3; ColorWeights[0] = 0.0625f; ColorWeights[1] = 0.0625f; ColorWeights[2] = 0.0625f; ColorWeights[3] = 0.0625f; } uint32 TexCoordOffset = 3 + ( bHasColors ? 4 : 0 ); float* UVWeights = AttributeWeights + TexCoordOffset; // Normalize UVWeights for( uint32 UVIndex = 0; UVIndex < NumTexCoords; UVIndex++ ) { float TriangleUVSize = FMath::Sqrt( UVArea[ UVIndex ] / NumTris ); TriangleUVSize = FMath::Max( TriangleUVSize, THRESH_UVS_ARE_SAME ); UVWeights[ 2 * UVIndex + 0 ] = 1.0f / ( 128.0f * TriangleUVSize ); UVWeights[ 2 * UVIndex + 1 ] = 1.0f / ( 128.0f * TriangleUVSize ); } FMeshSimplifier Simplifier( Verts.GetData(), NumVerts, Indexes.GetData(), Indexes.Num(), MaterialIndexes.GetData(), NumAttributes ); TMap< TTuple< FVector3f, FVector3f >, int8 > LockedEdges; for( int32 EdgeIndex = 0; EdgeIndex < ExternalEdges.Num(); EdgeIndex++ ) { if( ExternalEdges[ EdgeIndex ] ) { uint32 VertIndex0 = Indexes[ EdgeIndex ]; uint32 VertIndex1 = Indexes[ Cycle3( EdgeIndex ) ]; const FVector3f& Position0 = GetPosition( VertIndex0 ); const FVector3f& Position1 = GetPosition( VertIndex1 ); Simplifier.LockPosition( Position0 ); Simplifier.LockPosition( Position1 ); LockedEdges.Add( MakeTuple( Position0, Position1 ), ExternalEdges[ EdgeIndex ] ); } } Simplifier.SetAttributeWeights( AttributeWeights ); Simplifier.SetCorrectAttributes( bHasColors ? CorrectAttributesColor : CorrectAttributes ); Simplifier.SetEdgeWeight( 2.0f ); float MaxErrorSqr = Simplifier.Simplify( NumVerts, TargetNumTris, FMath::Square( TargetError ), 0, LimitNumTris, MAX_flt ); check( Simplifier.GetRemainingNumVerts() > 0 ); check( Simplifier.GetRemainingNumTris() > 0 ); Simplifier.Compact(); Verts.SetNum( Simplifier.GetRemainingNumVerts() * GetVertSize() ); Indexes.SetNum( Simplifier.GetRemainingNumTris() * 3 ); MaterialIndexes.SetNum( Simplifier.GetRemainingNumTris() ); ExternalEdges.Init( 0, Simplifier.GetRemainingNumTris() * 3 ); NumVerts = Simplifier.GetRemainingNumVerts(); NumTris = Simplifier.GetRemainingNumTris(); NumExternalEdges = 0; for( int32 EdgeIndex = 0; EdgeIndex < ExternalEdges.Num(); EdgeIndex++ ) { auto Edge = MakeTuple( GetPosition( Indexes[ EdgeIndex ] ), GetPosition( Indexes[ Cycle3( EdgeIndex ) ] ) ); int8* AdjCount = LockedEdges.Find( Edge ); if( AdjCount ) { ExternalEdges[ EdgeIndex ] = *AdjCount; NumExternalEdges++; } } float InvScale = 1.0f / PositionScale; for( uint32 i = 0; i < NumVerts; i++ ) { GetPosition(i) *= InvScale; Bounds += GetPosition(i); } for( uint32 TriIndex = 0; TriIndex < NumTris; TriIndex++ ) { // Remove UV mirroring bits MaterialIndexes[ TriIndex ] &= 0xffffff; } return FMath::Sqrt( MaxErrorSqr ) * InvScale; } void FCluster::Split( FGraphPartitioner& Partitioner, const FAdjacency& Adjacency ) const { FDisjointSet DisjointSet( NumTris ); for( int32 EdgeIndex = 0; EdgeIndex < Indexes.Num(); EdgeIndex++ ) { Adjacency.ForAll( EdgeIndex, [ &DisjointSet ]( int32 EdgeIndex0, int32 EdgeIndex1 ) { if( EdgeIndex0 > EdgeIndex1 ) DisjointSet.UnionSequential( EdgeIndex0 / 3, EdgeIndex1 / 3 ); } ); } auto GetCenter = [ this ]( uint32 TriIndex ) { FVector3f Center; Center = GetPosition( Indexes[ TriIndex * 3 + 0 ] ); Center += GetPosition( Indexes[ TriIndex * 3 + 1 ] ); Center += GetPosition( Indexes[ TriIndex * 3 + 2 ] ); return Center * (1.0f / 3.0f); }; Partitioner.BuildLocalityLinks( DisjointSet, Bounds, MaterialIndexes, GetCenter ); auto* RESTRICT Graph = Partitioner.NewGraph( NumTris * 3 ); for( uint32 i = 0; i < NumTris; i++ ) { Graph->AdjacencyOffset[i] = Graph->Adjacency.Num(); uint32 TriIndex = Partitioner.Indexes[i]; // Add shared edges for( int k = 0; k < 3; k++ ) { Adjacency.ForAll( 3 * TriIndex + k, [ &Partitioner, Graph ]( int32 EdgeIndex, int32 AdjIndex ) { Partitioner.AddAdjacency( Graph, AdjIndex / 3, 4 * 65 ); } ); } Partitioner.AddLocalityLinks( Graph, TriIndex, 1 ); } Graph->AdjacencyOffset[ NumTris ] = Graph->Adjacency.Num(); Partitioner.PartitionStrict( Graph, ClusterSize - 4, ClusterSize, false ); } FAdjacency FCluster::BuildAdjacency() const { FAdjacency Adjacency( Indexes.Num() ); FEdgeHash EdgeHash( Indexes.Num() ); for( int32 EdgeIndex = 0; EdgeIndex < Indexes.Num(); EdgeIndex++ ) { Adjacency.Direct[ EdgeIndex ] = -1; EdgeHash.ForAllMatching( EdgeIndex, true, [ this ]( int32 CornerIndex ) { return GetPosition( Indexes[ CornerIndex ] ); }, [&]( int32 EdgeIndex, int32 OtherEdgeIndex ) { Adjacency.Link( EdgeIndex, OtherEdgeIndex ); } ); } return Adjacency; } uint32 FCluster::AddVert( const float* Vert, FHashTable& HashTable ) { const FVector3f& Position = *reinterpret_cast< const FVector3f* >( Vert ); uint32 Hash = HashPosition( Position ); uint32 NewIndex; for( NewIndex = HashTable.First( Hash ); HashTable.IsValid( NewIndex ); NewIndex = HashTable.Next( NewIndex ) ) { if( 0 == FMemory::Memcmp( &GetPosition( NewIndex ), Vert, GetVertSize() * sizeof( float ) ) ) { break; } } if( !HashTable.IsValid( NewIndex ) ) { Verts.AddUninitialized( GetVertSize() ); NewIndex = NumVerts++; HashTable.Add( Hash, NewIndex ); FMemory::Memcpy( &GetPosition( NewIndex ), Vert, GetVertSize() * sizeof( float ) ); } return NewIndex; } struct FNormalCone { FVector3f Axis; float CosAngle; FNormalCone() {} FNormalCone( const FVector3f& InAxis ) : Axis( InAxis ) , CosAngle( 1.0f ) { if( !Axis.Normalize() ) { Axis = FVector3f( 0.0f, 0.0f, 1.0f ); } } }; FORCEINLINE FMatrix44f OrthonormalBasis( const FVector3f& Vec ) { float Sign = Vec.Z >= 0.0f ? 1.0f : -1.0f; float a = -1.0f / ( Sign + Vec.Z ); float b = Vec.X * Vec.Y * a; return FMatrix44f( { 1.0f + Sign * a * FMath::Square( Vec.X ), Sign * b, -Vec.X * Sign }, { b, Sign + a * FMath::Square( Vec.Y ), -Vec.Y }, Vec, FVector3f::ZeroVector ); } FMatrix44f CovarianceToBasis( const FMatrix44f& Covariance ) { #if 0 FMatrix44f Eigenvectors; FVector3f Eigenvalues; diagonalizeSymmetricMatrix( Covariance, Eigenvectors, Eigenvalues ); //Eigenvectors = Eigenvectors.GetTransposed(); uint32 i0 = Max3Index( Eigenvalues[0], Eigenvalues[1], Eigenvalues[2] ); uint32 i1 = (1 << i0) & 3; uint32 i2 = (1 << i1) & 3; i1 = Eigenvalues[ i1 ] > Eigenvalues[ i2 ] ? i1 : i2; FVector3f Eigenvector0 = Eigenvectors.GetColumn( i0 ); FVector3f Eigenvector1 = Eigenvectors.GetColumn( i1 ); Eigenvector0.Normalize(); Eigenvector1 -= ( Eigenvector0 | Eigenvector1 ) * Eigenvector1; Eigenvector1.Normalize(); return FMatrix44f( Eigenvector0, Eigenvector1, Eigenvector0 ^ Eigenvector1, FVector3f::ZeroVector ); #else // Start with highest variance cardinal direction uint32 HighestVarianceDim = Max3Index( Covariance.M[0][0], Covariance.M[1][1], Covariance.M[2][2] ); FVector3f Eigenvector0 = FMatrix44f::Identity.GetColumn( HighestVarianceDim ); // Compute dominant eigenvector using power method for( int i = 0; i < 32; i++ ) { Eigenvector0 = Covariance.TransformVector( Eigenvector0 ); Eigenvector0.Normalize(); } if( !Eigenvector0.IsNormalized() ) { Eigenvector0 = FVector3f( 0.0f, 0.0f, 1.0f ); } // Rotate matrix so that Z is Eigenvector0. This allows us to ignore Z dimension and turn this into a 2D problem. FMatrix44f ZSpace = OrthonormalBasis( Eigenvector0 ); FMatrix44f ZLocalCovariance = Covariance * ZSpace; // Compute eigenvalues in XY plane. Solve for 2x2. float Det = ZLocalCovariance.M[0][0] * ZLocalCovariance.M[1][1] - ZLocalCovariance.M[0][1] * ZLocalCovariance.M[1][0]; float Trace = ZLocalCovariance.M[0][0] + ZLocalCovariance.M[1][1]; float Sqr = Trace * Trace - 4.0f * Det; if( Sqr < 0.0f ) { return ZSpace; } float Sqrt = FMath::Sqrt( Sqr ); float Eigenvalue1 = 0.5f * ( Trace + Sqrt ); float Eigenvalue2 = 0.5f * ( Trace - Sqrt ); float MinEigenvalue = FMath::Min( Eigenvalue1, Eigenvalue2 ); float MaxEigenvalue = FMath::Max( Eigenvalue1, Eigenvalue2 ); // Solve ( Eigenvalue * I - M ) * Eigenvector = 0 FVector3f Eigenvector1; if( FMath::Abs( ZLocalCovariance.M[0][1] ) > FMath::Abs( ZLocalCovariance.M[1][0] ) ) { Eigenvector1 = FVector3f( ZLocalCovariance.M[0][1], MaxEigenvalue - ZLocalCovariance.M[0][0], 0.0f ); } else { Eigenvector1 = FVector3f( MaxEigenvalue - ZLocalCovariance.M[1][1], ZLocalCovariance.M[1][0], 0.0f ); } Eigenvector1 = ZSpace.TransformVector( Eigenvector1 ); //Eigenvector1 -= ( Eigenvector0 | Eigenvector1 ) * Eigenvector1; Eigenvector1.Normalize(); return FMatrix44f( Eigenvector0, Eigenvector1, Eigenvector0 ^ Eigenvector1, FVector3f::ZeroVector ); #endif } void FCluster::Bound() { Bounds = FBounds3f(); SurfaceArea = 0.0f; TArray< FVector3f, TInlineAllocator<128> > Positions; Positions.SetNum( NumVerts, false ); for( uint32 i = 0; i < NumVerts; i++ ) { Positions[i] = GetPosition(i); Bounds += Positions[i]; } SphereBounds = FSphere3f( Positions.GetData(), Positions.Num() ); LODBounds = SphereBounds; float MaxEdgeLength2 = 0.0f; for( int i = 0; i < Indexes.Num(); i += 3 ) { FVector3f v[3]; v[0] = GetPosition( Indexes[ i + 0 ] ); v[1] = GetPosition( Indexes[ i + 1 ] ); v[2] = GetPosition( Indexes[ i + 2 ] ); FVector3f Edge01 = v[1] - v[0]; FVector3f Edge12 = v[2] - v[1]; FVector3f Edge20 = v[0] - v[2]; MaxEdgeLength2 = FMath::Max( MaxEdgeLength2, Edge01.SizeSquared() ); MaxEdgeLength2 = FMath::Max( MaxEdgeLength2, Edge12.SizeSquared() ); MaxEdgeLength2 = FMath::Max( MaxEdgeLength2, Edge20.SizeSquared() ); float TriArea = 0.5f * ( Edge01 ^ Edge20 ).Size(); SurfaceArea += TriArea; } EdgeLength = FMath::Sqrt( MaxEdgeLength2 ); } FArchive& operator<<(FArchive& Ar, FMaterialRange& Range) { Ar << Range.RangeStart; Ar << Range.RangeLength; Ar << Range.MaterialIndex; return Ar; } FArchive& operator<<(FArchive& Ar, FStripDesc& Desc) { for (uint32 i = 0; i < 4; i++) { for (uint32 j = 0; j < 3; j++) { Ar << Desc.Bitmasks[i][j]; } } Ar << Desc.NumPrevRefVerticesBeforeDwords; Ar << Desc.NumPrevNewVerticesBeforeDwords; return Ar; } /* FArchive& operator<<(FArchive& Ar, FCluster& Cluster) { Ar << Cluster.NumVerts; Ar << Cluster.NumTris; Ar << Cluster.NumTexCoords; Ar << Cluster.bHasColors; Ar << Cluster.Verts; Ar << Cluster.Indexes; Ar << Cluster.MaterialIndexes; Ar << Cluster.BoundaryEdges; Ar << Cluster.ExternalEdges; Ar << Cluster.NumExternalEdges; Ar << Cluster.AdjacentClusters; Ar << Cluster.Bounds; Ar << Cluster.GUID; Ar << Cluster.MipLevel; Ar << Cluster.QuantizedPosStart; Ar << Cluster.QuantizedPosShift; Ar << Cluster.MeshBoundsMin; Ar << Cluster.MeshBoundsDelta; Ar << Cluster.EdgeLength; Ar << Cluster.LODError; Ar << Cluster.SphereBounds; Ar << Cluster.LODBounds; Ar << Cluster.GroupIndex; Ar << Cluster.GroupPartIndex; Ar << Cluster.GeneratingGroupIndex; Ar << Cluster.MaterialRanges; Ar << Cluster.QuantizedPositions; Ar << Cluster.StripDesc; Ar << Cluster.StripIndexData; return Ar; } */ } // namespace Nanite