Files
UnrealEngineUWP/Engine/Source/Developer/NaniteBuilder/Private/Cluster.cpp
2022-10-11 12:15:30 -04:00

675 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Cluster.h"
#include "GraphPartitioner.h"
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, bool bInPreserveArea,
uint32 TriBegin, uint32 TriEnd, const FGraphPartitioner& Partitioner, const FAdjacency& Adjacency )
{
GUID = (uint64(TriBegin) << 32) | TriEnd;
NumTris = TriEnd - TriBegin;
//ensure(NumTriangles <= FCluster::ClusterSize);
bHasColors = bInHasColors;
bPreserveArea = bInPreserveArea;
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;
if( bHasColors )
{
GetColor( NewIndex ) = InVert.Color.ReinterpretAsLinear();
}
FVector2f* UVs = GetUVs( NewIndex );
for( uint32 UVIndex = 0; UVIndex < NumTexCoords; UVIndex++ )
{
UVs[ UVIndex ] = InVert.UVs[ UVIndex ];
}
}
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 ] );
}
SanitizeVertexData();
for( uint32 VertexIndex = 0; VertexIndex < NumVerts; VertexIndex++ )
{
float* Attributes = GetAttributes( VertexIndex );
// Make sure this vertex is valid from the start
if( bHasColors )
CorrectAttributesColor( Attributes );
else
CorrectAttributes( Attributes );
}
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;
bPreserveArea = SrcCluster.bPreserveArea;
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;
}
MaterialIndexes.Add( SrcCluster.MaterialIndexes[ TriIndex ] );
}
Bound();
}
// Merge
FCluster::FCluster( const TArray< const FCluster*, TInlineAllocator<32> >& MergeList )
{
NumTexCoords = MergeList[0]->NumTexCoords;
bHasColors = MergeList[0]->bHasColors;
bPreserveArea = MergeList[0]->bPreserveArea;
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 )
{
NumTris += Child->NumTris;
Bounds += Child->Bounds;
SurfaceArea += Child->SurfaceArea;
// Can jump multiple levels but guarantee it steps at least 1.
MipLevel = FMath::Max( MipLevel, Child->MipLevel + 1 );
LODError = FMath::Max( LODError, Child->LODError );
EdgeLength = FMath::Max( EdgeLength, Child->EdgeLength );
for( int32 i = 0; i < Child->Indexes.Num(); i++ )
{
uint32 NewIndex = AddVert( &Child->Verts[ Child->Indexes[i] * GetVertSize() ], VertHashTable );
Indexes.Add( NewIndex );
}
ExternalEdges.Append( Child->ExternalEdges );
MaterialIndexes.Append( Child->MaterialIndexes );
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;
}
ensure( NumTris == Indexes.Num() / 3 );
}
float FCluster::Simplify( uint32 TargetNumTris, float TargetError, uint32 LimitNumTris, bool bForNaniteFallback )
{
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
// When generating nanite fallback meshes, use the same weights as in FQuadricSimplifierMeshReduction::ReduceMeshDescription
// to ensure consistent LOD transition screen size.
float PositionScale = bForNaniteFallback ? 1.f : 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] = bForNaniteFallback ? 16.f : 1.0f;
AttributeWeights[1] = bForNaniteFallback ? 16.f : 1.0f;
AttributeWeights[2] = bForNaniteFallback ? 16.f : 1.0f;
if( bHasColors )
{
float* ColorWeights = AttributeWeights + 3;
ColorWeights[0] = bForNaniteFallback ? 0.1f : 0.0625f;
ColorWeights[1] = bForNaniteFallback ? 0.1f : 0.0625f;
ColorWeights[2] = bForNaniteFallback ? 0.1f : 0.0625f;
ColorWeights[3] = bForNaniteFallback ? 0.1f : 0.0625f;
}
uint32 TexCoordOffset = 3 + ( bHasColors ? 4 : 0 );
float* UVWeights = AttributeWeights + TexCoordOffset;
// Normalize UVWeights
for( uint32 UVIndex = 0; UVIndex < NumTexCoords; UVIndex++ )
{
if (bForNaniteFallback)
{
float MinUV = +FLT_MAX;
float MaxUV = -FLT_MAX;
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)[UVIndex];
FVector2f UV1 = GetUVs(Index1)[UVIndex];
FVector2f UV2 = GetUVs(Index2)[UVIndex];
MinUV = FMath::Min(MinUV, UV0.X);
MinUV = FMath::Min(MinUV, UV0.Y);
MinUV = FMath::Min(MinUV, UV1.X);
MinUV = FMath::Min(MinUV, UV1.Y);
MinUV = FMath::Min(MinUV, UV2.X);
MinUV = FMath::Min(MinUV, UV2.Y);
MaxUV = FMath::Max(MaxUV, UV0.X);
MaxUV = FMath::Max(MaxUV, UV0.Y);
MaxUV = FMath::Max(MaxUV, UV1.X);
MaxUV = FMath::Max(MaxUV, UV1.Y);
MaxUV = FMath::Max(MaxUV, UV2.X);
MaxUV = FMath::Max(MaxUV, UV2.Y);
}
UVWeights[2 * UVIndex + 0] = 0.5f / FMath::Max(1.f, MaxUV - MinUV);
UVWeights[2 * UVIndex + 1] = 0.5f / FMath::Max(1.f, MaxUV - MinUV);
}
else
{
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(bForNaniteFallback ? 512.f : 2.0f );
if (bForNaniteFallback)
{
Simplifier.SetLimitErrorToSurfaceArea(false);
Simplifier.DegreePenalty = 100.0f;
Simplifier.InversionPenalty = 1000000.0f;
}
float MaxErrorSqr = Simplifier.Simplify(
NumVerts, TargetNumTris, FMath::Square( TargetError ),
0, LimitNumTris, MAX_flt );
check( Simplifier.GetRemainingNumVerts() > 0 );
check( Simplifier.GetRemainingNumTris() > 0 );
if( bPreserveArea )
Simplifier.PreserveSurfaceArea();
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 uint32 VertSize = GetVertSize();
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 ) )
{
uint32 i;
for( i = 0; i < VertSize; i++ )
{
if( Vert[i] != Verts[ NewIndex * VertSize + i ] )
break;
}
if( i == VertSize )
break;
}
if( !HashTable.IsValid( NewIndex ) )
{
Verts.AddUninitialized( VertSize );
NewIndex = NumVerts++;
HashTable.Add( Hash, NewIndex );
FMemory::Memcpy( &GetPosition( NewIndex ), Vert, GetVertSize() * sizeof( float ) );
}
return NewIndex;
}
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 );
}
static void SanitizeFloat( float& X, float MinValue, float MaxValue, float DefaultValue )
{
if( X >= MinValue && X <= MaxValue )
;
else if( X < MinValue )
X = MinValue;
else if( X > MaxValue )
X = MaxValue;
else
X = DefaultValue;
}
void FCluster::SanitizeVertexData()
{
const float FltThreshold = 1e12f; // Fairly arbitrary threshold for sensible float values.
// Should be large enough for all practical purposes, while still leaving enough headroom
// so that overflows shouldn't be a concern.
// With a 1e12 threshold, even x^3 fits comfortable in float range.
for( uint32 VertexIndex = 0; VertexIndex < NumVerts; VertexIndex++ )
{
FVector3f& Position = GetPosition( VertexIndex );
SanitizeFloat( Position.X, -FltThreshold, FltThreshold, 0.0f );
SanitizeFloat( Position.Y, -FltThreshold, FltThreshold, 0.0f );
SanitizeFloat( Position.Z, -FltThreshold, FltThreshold, 0.0f );
FVector3f& Normal = GetNormal( VertexIndex );
if( !( Normal.X >= -FltThreshold && Normal.X <= FltThreshold &&
Normal.Y >= -FltThreshold && Normal.Y <= FltThreshold &&
Normal.Z >= -FltThreshold && Normal.Z <= FltThreshold ) ) // Don't flip condition. Intentionally written like this to be NaN-safe
{
Normal = FVector3f::UpVector;
}
if( bHasColors )
{
FLinearColor& Color = GetColor( VertexIndex );
SanitizeFloat( Color.R, 0.0f, 1.0f, 1.0f );
SanitizeFloat( Color.G, 0.0f, 1.0f, 1.0f );
SanitizeFloat( Color.B, 0.0f, 1.0f, 1.0f );
SanitizeFloat( Color.A, 0.0f, 1.0f, 1.0f );
}
FVector2f* UVs = GetUVs( VertexIndex );
for( uint32 UvIndex = 0; UvIndex < NumTexCoords; UvIndex++ )
{
SanitizeFloat( UVs[ UvIndex ].X, -FltThreshold, FltThreshold, 0.0f );
SanitizeFloat( UVs[ UvIndex ].Y, -FltThreshold, FltThreshold, 0.0f );
}
}
}
FArchive& operator<<(FArchive& Ar, FMaterialRange& Range)
{
Ar << Range.RangeStart;
Ar << Range.RangeLength;
Ar << Range.MaterialIndex;
Ar << Range.BatchTriCounts;
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;
}
} // namespace Nanite