Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

2081 lines
62 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Cluster.h"
#include "GraphPartitioner.h"
#include "NaniteRayTracingScene.h"
#include "Rasterizer.h"
#include "VectorUtil.h"
#include "ClusterDAG.h"
#include "NaniteBuilder.h"
#include "MonteCarlo.h"
#include "MeshUtilitiesCommon.h"
namespace Nanite
{
template< bool bHasTangents, bool bHasColors >
void CorrectAttributes( float* Attributes )
{
float* AttributesPtr = Attributes;
FVector3f& Normal = *reinterpret_cast< FVector3f* >( AttributesPtr );
Normal.Normalize();
AttributesPtr += 3;
if( bHasTangents )
{
FVector3f& TangentX = *reinterpret_cast< FVector3f* >( AttributesPtr );
AttributesPtr += 3;
TangentX -= ( TangentX | Normal ) * Normal;
TangentX.Normalize();
float& TangentYSign = *AttributesPtr++;
TangentYSign = TangentYSign < 0.0f ? -1.0f : 1.0f;
}
if( bHasColors )
{
FLinearColor& Color = *reinterpret_cast< FLinearColor* >( AttributesPtr );
AttributesPtr += 3;
Color = Color.GetClamped();
}
}
typedef void (CorrectAttributesFunction)( float* Attributes );
static CorrectAttributesFunction* CorrectAttributesFunctions[ 2 ][ 2 ] = // [ bHasTangents ][ bHasColors ]
{
{ CorrectAttributes<false, false>, CorrectAttributes<false, true> },
{ CorrectAttributes<true, false>, CorrectAttributes<true, true> }
};
FCluster::FCluster(
const FConstMeshBuildVertexView& InVerts,
TArrayView< const uint32 > InIndexes,
TArrayView< const int32 > InMaterialIndexes,
const FVertexFormat& InFormat,
uint32 Begin, uint32 End,
TArrayView< const uint32 > SortedIndexes,
TArrayView< const uint32 > SortedTo,
const FAdjacency& Adjacency )
{
Verts.Format = InFormat;
Verts.InitFormat();
GUID = (uint64(Begin) << 32) | End;
NumTris = End - Begin;
Verts.Reserve( NumTris );
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 = Begin; i < End; i++ )
{
uint32 TriIndex = SortedIndexes[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 )
{
NewIndex = Verts.AddUninitialized();
OldToNewIndex.Add( OldIndex, NewIndex );
Verts.GetPosition( NewIndex ) = InVerts.Position[OldIndex];
Verts.GetNormal( NewIndex ) = InVerts.TangentZ[OldIndex];
if( Verts.Format.bHasTangents )
{
const float TangentYSign = ((InVerts.TangentZ[OldIndex] ^ InVerts.TangentX[OldIndex]) | InVerts.TangentY[OldIndex]);
Verts.GetTangentX( NewIndex ) = InVerts.TangentX[OldIndex];
Verts.GetTangentYSign( NewIndex ) = TangentYSign < 0.0f ? -1.0f : 1.0f;
}
if( Verts.Format.bHasColors )
{
Verts.GetColor( NewIndex ) = InVerts.Color[OldIndex].ReinterpretAsLinear();
}
if( Verts.Format.NumTexCoords > 0 )
{
FVector2f* UVs = Verts.GetUVs( NewIndex );
for( uint32 UVIndex = 0; UVIndex < Verts.Format.NumTexCoords; UVIndex++ )
{
UVs[UVIndex] = InVerts.UVs[UVIndex][OldIndex];
}
}
if( Verts.Format.NumBoneInfluences > 0 )
{
FVector2f* BoneInfluences = Verts.GetBoneInfluences(NewIndex);
for (uint32 Influence = 0; Influence < Verts.Format.NumBoneInfluences; Influence++)
{
BoneInfluences[Influence].X = InVerts.BoneIndices[Influence][OldIndex];
BoneInfluences[Influence].Y = InVerts.BoneWeights[Influence][OldIndex];
}
}
}
Indexes.Add( NewIndex );
int32 EdgeIndex = TriIndex * 3 + k;
int32 AdjCount = 0;
Adjacency.ForAll( EdgeIndex,
[ &AdjCount, Begin, End, &SortedTo ]( int32 EdgeIndex, int32 AdjIndex )
{
uint32 AdjTri = SortedTo[ AdjIndex / 3 ];
if( AdjTri < Begin || AdjTri >= End )
AdjCount++;
} );
ExternalEdges.Add( (int8)AdjCount );
NumExternalEdges += AdjCount != 0 ? 1 : 0;
}
MaterialIndexes.Add( InMaterialIndexes[ TriIndex ] );
}
Verts.Sanitize();
for( uint32 VertIndex = 0; VertIndex < Verts.Num(); VertIndex++ )
{
// Make sure this vertex is valid from the start
CorrectAttributesFunctions[ Verts.Format.bHasTangents ][ Verts.Format.bHasColors ]( Verts.GetAttributes( VertIndex ) );
}
Bound();
}
// Split
FCluster::FCluster(
FCluster& SrcCluster,
uint32 Begin, uint32 End,
TArrayView< const uint32 > SortedIndexes,
TArrayView< const uint32 > SortedTo,
const FAdjacency& Adjacency )
: MipLevel( SrcCluster.MipLevel )
{
GUID = Murmur64( { SrcCluster.GUID, (uint64)Begin, (uint64)End } );
uint32 NumElements = End - Begin;
check( NumElements <= ClusterSize );
Verts.Format = SrcCluster.Verts.Format;
Verts.InitFormat();
if( SrcCluster.NumTris )
{
NumTris = NumElements;
Verts.Reserve( NumElements );
Indexes.Reserve( 3 * NumElements );
MaterialIndexes.Reserve( NumElements );
ExternalEdges.Reserve( 3 * NumElements );
NumExternalEdges = 0;
TMap< uint32, uint32 > OldToNewIndex;
OldToNewIndex.Reserve( NumTris );
for( uint32 i = Begin; i < End; i++ )
{
uint32 TriIndex = SortedIndexes[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 )
{
NewIndex = Verts.Add( &SrcCluster.Verts.GetPosition( OldIndex ) );
OldToNewIndex.Add( OldIndex, NewIndex );
}
Indexes.Add( NewIndex );
int32 EdgeIndex = TriIndex * 3 + k;
int32 AdjCount = SrcCluster.ExternalEdges[ EdgeIndex ];
Adjacency.ForAll( EdgeIndex,
[ &AdjCount, Begin, End, &SortedTo ]( int32 EdgeIndex, int32 AdjIndex )
{
uint32 AdjTri = SortedTo[ AdjIndex / 3 ];
if( AdjTri < Begin || AdjTri >= End )
AdjCount++;
} );
ExternalEdges.Add( (int8)AdjCount );
NumExternalEdges += AdjCount != 0 ? 1 : 0;
}
MaterialIndexes.Add( SrcCluster.MaterialIndexes[ TriIndex ] );
}
}
else
{
Verts.Reserve( NumElements );
MaterialIndexes.Reserve( NumElements );
for( uint32 i = Begin; i < End; i++ )
{
uint32 BrickIndex = SortedIndexes[i];
FBrick Brick = SrcCluster.Bricks[ BrickIndex ];
uint32 NumVoxels = FMath::CountBits( Brick.VoxelMask );
uint32 OldIndex = Brick.VertOffset;
uint32 NewIndex = Brick.VertOffset = Verts.Add( &SrcCluster.Verts.GetPosition( OldIndex ), NumVoxels );
Bricks.Add( Brick );
MaterialIndexes.Add( SrcCluster.MaterialIndexes[ BrickIndex ] );
}
}
Bound();
check( MaterialIndexes.Num() > 0 );
}
// Merge triangles
FCluster::FCluster( const FClusterDAG& DAG, TArrayView< const FClusterRef > Children )
{
uint32 NumVertsGuess = 0;
for( FClusterRef ChildRef : Children )
{
const FCluster& Child = ChildRef.GetCluster( DAG );
const uint8 NumBoneInfluences = ChildRef.IsInstance() ?
uint8(DAG.AssemblyInstanceData[ ChildRef.InstanceIndex ].NumBoneInfluences) :
Child.Verts.Format.NumBoneInfluences;
Verts.Format.NumTexCoords = FMath::Max( Verts.Format.NumTexCoords, Child.Verts.Format.NumTexCoords );
Verts.Format.NumBoneInfluences = FMath::Max( Verts.Format.NumBoneInfluences, NumBoneInfluences );
Verts.Format.bHasTangents |= Child.Verts.Format.bHasTangents;
Verts.Format.bHasColors |= Child.Verts.Format.bHasColors;
if( Child.NumTris == 0 )
{
// VOXELTODO - improve representation of voxels
// currently: one triangle per voxel brick
NumVertsGuess += 3 * Child.Bricks.Num();
NumTris += Child.Bricks.Num();
if ( Child.Bricks.Num() > 0 )
{
bHasVoxelTriangles = true;
}
}
else
{
NumVertsGuess += Child.Verts.Num();
NumTris += Child.NumTris;
}
// Can jump multiple levels but guarantee it steps at least 1.
MipLevel = FMath::Max( MipLevel, Child.MipLevel + 1 );
GUID = Murmur64( { GUID, Child.GUID } );
}
if( NumTris == 0 )
return;
Verts.InitFormat();
Verts.Reserve( NumVertsGuess );
Indexes.Reserve( 3 * NumTris );
MaterialIndexes.Reserve( NumTris );
ExternalEdges.Reserve( 3 * NumTris );
VoxelTriangle.Init( false, NumTris );
uint32 TriangleIndex = 0;
FHashTable VertHashTable( 1 << FMath::FloorLog2( NumVertsGuess ), NumVertsGuess );
uint32 VoxelSeed = 0;
const bool bAllowVoxels = DAG.Settings.ShapePreservation == ENaniteShapePreservation::Voxelize;
for( FClusterRef ChildRef : Children )
{
const FCluster& Child = ChildRef.GetCluster( DAG );
// VOXELTODO - improve representation of voxels
// currently: one triangle per voxel brick
auto BrickToTriangle = [&]( uint32 BrickIndex )
{
const FBrick& Brick = Child.Bricks[ BrickIndex ];
const uint32 NumVoxels = FMath::CountBits( Brick.VoxelMask );
FVector3f AvgPosition( 0.0f );
for( uint32 VertIndex = Brick.VertOffset; VertIndex < Brick.VertOffset + NumVoxels; VertIndex++ )
{
AvgPosition += Child.Verts.GetPosition( VertIndex );
}
AvgPosition /= float(NumVoxels);
// pick random voxel from brick
const uint32 VertIndex = Brick.VertOffset + EvolveSobolSeed(VoxelSeed) % NumVoxels;
uint32 NewIndex = Verts.AddUninitialized(3);
Verts.GetPosition( NewIndex ) = Child.Verts.GetPosition( VertIndex );
CopyAttributes( NewIndex, VertIndex, Child );
FMemory::Memcpy( &Verts.GetPosition( NewIndex + 1 ), &Verts.GetPosition( NewIndex ), Verts.GetVertSize() * sizeof( float ) );
FMemory::Memcpy( &Verts.GetPosition( NewIndex + 2 ), &Verts.GetPosition( NewIndex ), Verts.GetVertSize() * sizeof( float ) );
FVector3f N = Verts.GetNormal( NewIndex );
if( DAG.Settings.bVoxelNDF )
{
uint32 NormalSeed = 0;
// It would nice to sample the NDF instead of VNDF but don't have a way for getting unweighted samples for that.
FVector3f V = UniformSampleSphere( SobolSampler( BrickIndex, NormalSeed ) );
float NDF = Verts.GetColor( NewIndex ).A;
FVector3f Alpha;
if( NDF > 0.5f )
Alpha = FVector3f( 1.0f, 1.0f, 2.0f - 2.0f * NDF );
else
Alpha = FVector3f( 2.0f * NDF, 2.0f * NDF, 1.0f );
FMatrix44f TangentToWorld = GetTangentBasisFrisvad( Verts.GetNormal( NewIndex ) );
FVector3f TangentV = TangentToWorld.GetTransposed().TransformVector( V );
FVector3f SphereV = ( TangentV * Alpha ).GetSafeNormal();
FVector3f SphereN = ( V + UniformSampleSphere( SobolSampler( BrickIndex, NormalSeed ) ) ).GetSafeNormal();
FVector3f TangentN = ( SphereN * Alpha ).GetSafeNormal();
N = TangentToWorld.GetTransposed().TransformVector( TangentN );
}
FMatrix44f TriBasis = GetTangentBasisFrisvad( N );
// Random spin
float E = ( EvolveSobolSeed( VoxelSeed ) >> 8 ) * 5.96046447754e-08f; // * 2^-24
float Theta = 2.0f * PI * E;
float SinTheta, CosTheta;
FMath::SinCos( &SinTheta, &CosTheta, Theta );
FVector3f X = TriBasis.TransformVector( FVector3f( CosTheta, SinTheta, 0.0f ) );
FVector3f Y = TriBasis.TransformVector( FVector3f( -SinTheta, CosTheta, 0.0f ) );
float TriArea = 4.0f * float(NumVoxels); // inverse average projected area
float TriScale = Child.LODError * FMath::Sqrt( TriArea );
X *= TriScale;
Y *= TriScale;
Verts.GetPosition( NewIndex + 0 ) = AvgPosition - (1.0f / 3.0f) * X - (1.0f / 3.0f) * Y;
Verts.GetPosition( NewIndex + 1 ) = AvgPosition - (1.0f / 3.0f) * X + (2.0f / 3.0f) * Y;
Verts.GetPosition( NewIndex + 2 ) = AvgPosition + (2.0f / 3.0f) * X - (1.0f / 3.0f) * Y;
Indexes.Add( NewIndex + 0 );
Indexes.Add( NewIndex + 1 );
Indexes.Add( NewIndex + 2 );
MaterialIndexes.Add( Child.MaterialIndexes[ BrickIndex ] );
VoxelTriangle[ TriangleIndex++ ] = true;
return NewIndex;
};
if( ChildRef.IsInstance() )
{
const FMatrix44f& Transform = ChildRef.GetTransform( DAG );
const FMatrix44f NormalTransform = Transform.GetMatrixWithoutScale();
// TODO Recalculate instead?
const float MaxScale = Transform.GetScaleVector().GetMax();
Bounds += Child.Bounds.TransformBy( Transform );
SurfaceArea += Child.SurfaceArea * FMath::Square(MaxScale);
auto TransformVert = [&]( uint32 VertIndex )
{
FVector3f& Position = Verts.GetPosition( VertIndex );
Position = Transform.TransformPosition( Position );
FVector3f& Normal = Verts.GetNormal( VertIndex );
Normal = NormalTransform.TransformVector( Normal );
if( Verts.Format.bHasTangents )
{
FVector3f& TangentX = Verts.GetTangentX( VertIndex );
TangentX = NormalTransform.TransformVector( TangentX );
// ASSEMBLYTODO GetTangentYSign needs to negate if transform determinent is <0
}
// Instanced clusters' verts receive their assembly part's influences when being transformed to local
const FAssemblyInstanceData& InstanceData = DAG.AssemblyInstanceData[ ChildRef.InstanceIndex ];
if (InstanceData.NumBoneInfluences > 0)
{
check( Verts.Format.NumBoneInfluences >= InstanceData.NumBoneInfluences );
FMemory::Memcpy(
Verts.GetBoneInfluences( VertIndex ),
&DAG.AssemblyBoneInfluences[ InstanceData.FirstBoneInfluence ],
InstanceData.NumBoneInfluences * sizeof( FVector2f ));
}
FMemory::Memzero(
Verts.GetBoneInfluences( VertIndex ) + InstanceData.NumBoneInfluences,
(Verts.Format.NumBoneInfluences - InstanceData.NumBoneInfluences) * sizeof( FVector2f ) );
CorrectAttributesFunctions[ Verts.Format.bHasTangents ][ Verts.Format.bHasColors ]( Verts.GetAttributes( VertIndex ) );
};
if( bAllowVoxels && Child.NumTris == 0 )
{
for( int32 BrickIndex = 0; BrickIndex < Child.Bricks.Num(); BrickIndex++ )
{
uint32 NewIndex = BrickToTriangle( BrickIndex );
TransformVert( NewIndex + 0 );
TransformVert( NewIndex + 1 );
TransformVert( NewIndex + 2 );
}
}
else
{
for( int32 i = 0; i < Child.Indexes.Num(); i++ )
{
uint32 NewIndex = AddVertMismatched( Child.Indexes[i], Child, VertHashTable, TransformVert );
Indexes.Add( NewIndex );
}
}
TriangleIndex += Child.NumTris;
}
else
{
Bounds += Child.Bounds;
SurfaceArea += Child.SurfaceArea;
if( bAllowVoxels && Child.NumTris == 0 )
{
for( int32 BrickIndex = 0; BrickIndex < Child.Bricks.Num(); BrickIndex++ )
{
BrickToTriangle( BrickIndex );
}
}
else if( Verts.Format.Matches( Child.Verts.Format ) )
{
for( int32 i = 0; i < Child.Indexes.Num(); i++ )
{
const float* ChildVert = (float*)&Child.Verts.GetPosition( Child.Indexes[i] );
uint32 NewIndex = Verts.FindOrAddHash( ChildVert, Verts.Num(), VertHashTable );
if( NewIndex == Verts.Num() )
{
Verts.Add( ChildVert );
}
Indexes.Add( NewIndex );
}
}
else
{
for( int32 i = 0; i < Child.Indexes.Num(); i++ )
{
uint32 NewIndex = AddVertMismatched( Child.Indexes[i], Child, VertHashTable, [&]( uint32 NewIndex ){} );
Indexes.Add( NewIndex );
}
}
TriangleIndex += Child.NumTris;
}
ExternalEdges.Append( Child.ExternalEdges );
if( Child.NumTris > 0 )
{
MaterialIndexes.Append( Child.MaterialIndexes );
}
}
// TODO Clear ExternalEdges when this cluster contains all triangles for this level.
FAdjacency Adjacency = BuildAdjacency();
int32 ChildIndex = 0;
int32 MinIndex = 0;
int32 MaxIndex = Children[0].GetCluster( DAG ).ExternalEdges.Num();
for( int32 EdgeIndex = 0; EdgeIndex < ExternalEdges.Num(); EdgeIndex++ )
{
if( EdgeIndex >= MaxIndex )
{
ChildIndex++;
MinIndex = MaxIndex;
MaxIndex += Children[ ChildIndex ].GetCluster( DAG ).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 simplification 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 ] = (int8)AdjCount;
NumExternalEdges += AdjCount != 0 ? 1 : 0;
}
ensure( NumTris == Indexes.Num() / 3 );
check( MaterialIndexes.Num() > 0 );
}
float FCluster::Simplify( const FClusterDAG& DAG, uint32 TargetNumTris, float TargetError, uint32 LimitNumTris, const FRayTracingFallbackBuildSettings* RayTracingFallbackBuildSettings )
{
if( ( TargetNumTris >= NumTris && TargetError == 0.0f ) || LimitNumTris >= NumTris )
{
return 0.0f;
}
float UVArea[ MAX_STATIC_TEXCOORDS ] = { 0.0f };
if( Verts.Format.NumTexCoords > 0 )
{
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 = Verts.GetUVs( Index0 );
FVector2f* UV1 = Verts.GetUVs( Index1 );
FVector2f* UV2 = Verts.GetUVs( Index2 );
for( uint32 UVIndex = 0; UVIndex < Verts.Format.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 / (float)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 < Verts.Num(); i++ )
{
Verts.GetPosition(i) *= PositionScale;
}
TargetError *= PositionScale;
uint32 NumAttributes = Verts.GetVertSize() - 3;
float* AttributeWeights = (float*)FMemory_Alloca( NumAttributes * sizeof( float ) );
float* WeightsPtr = AttributeWeights;
// Normal
*WeightsPtr++ = 1.0f;
*WeightsPtr++ = 1.0f;
*WeightsPtr++ = 1.0f;
if( Verts.Format.bHasTangents )
{
// Tangent X
*WeightsPtr++ = 0.0625f;
*WeightsPtr++ = 0.0625f;
*WeightsPtr++ = 0.0625f;
// Tangent Y Sign
*WeightsPtr++ = 0.5f;
}
if( Verts.Format.bHasColors )
{
*WeightsPtr++ = 0.0625f;
*WeightsPtr++ = 0.0625f;
*WeightsPtr++ = 0.0625f;
*WeightsPtr++ = 0.0625f;
}
// Normalize UVWeights
for( uint32 UVIndex = 0; UVIndex < Verts.Format.NumTexCoords; UVIndex++ )
{
float UVWeight = 0.0f;
if( DAG.Settings.bLerpUVs )
{
float TriangleUVSize = FMath::Sqrt( UVArea[UVIndex] / (float)NumTris );
TriangleUVSize = FMath::Max( TriangleUVSize, THRESH_UVS_ARE_SAME );
UVWeight = 1.0f / ( 128.0f * TriangleUVSize );
}
*WeightsPtr++ = UVWeight;
*WeightsPtr++ = UVWeight;
}
for (uint32 Influence = 0; Influence < Verts.Format.NumBoneInfluences; Influence++)
{
// Set all bone index/weight values to 0.0 so that the closest
// original vertex to the new position will copy its data wholesale.
// Similar to the !bLerpUV path, but always used for skinning data.
float InfluenceWeight = 0.0f;
*WeightsPtr++ = InfluenceWeight; // Bone index
*WeightsPtr++ = InfluenceWeight; // Bone weight
}
check( ( WeightsPtr - AttributeWeights ) == NumAttributes );
FMeshSimplifier Simplifier( Verts.Array.GetData(), Verts.Num(), 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 = Verts.GetPosition( VertIndex0 );
const FVector3f& Position1 = Verts.GetPosition( VertIndex1 );
Simplifier.LockPosition( Position0 );
Simplifier.LockPosition( Position1 );
LockedEdges.Add( MakeTuple( Position0, Position1 ), ExternalEdges[ EdgeIndex ] );
}
}
Simplifier.SetAttributeWeights( AttributeWeights );
Simplifier.SetCorrectAttributes( CorrectAttributesFunctions[ Verts.Format.bHasTangents ][ Verts.Format.bHasColors ] );
Simplifier.SetEdgeWeight( 2.0f );
Simplifier.SetMaxEdgeLengthFactor( DAG.Settings.MaxEdgeLengthFactor );
float MaxErrorSqr = Simplifier.Simplify(
Verts.Num(), TargetNumTris, FMath::Square( TargetError ),
0, LimitNumTris, MAX_flt );
check( Simplifier.GetRemainingNumVerts() > 0 );
check( Simplifier.GetRemainingNumTris() > 0 );
if ( RayTracingFallbackBuildSettings && RayTracingFallbackBuildSettings->FoliageOverOcclusionBias > 0.0f )
{
if ( bHasVoxelTriangles )
{
Simplifier.ShrinkVoxelTriangles( RayTracingFallbackBuildSettings->FoliageOverOcclusionBias, VoxelTriangle );
}
else
{
Simplifier.ShrinkTriGroupWithMostSurfaceAreaLoss( RayTracingFallbackBuildSettings->FoliageOverOcclusionBias );
}
}
else if( DAG.Settings.ShapePreservation == ENaniteShapePreservation::PreserveArea )
{
Simplifier.PreserveSurfaceArea();
}
Simplifier.Compact();
Verts.SetNum( Simplifier.GetRemainingNumVerts() );
Indexes.SetNum( Simplifier.GetRemainingNumTris() * 3 );
MaterialIndexes.SetNum( Simplifier.GetRemainingNumTris() );
ExternalEdges.Init( 0, Simplifier.GetRemainingNumTris() * 3 );
NumTris = Simplifier.GetRemainingNumTris();
NumExternalEdges = 0;
for( int32 EdgeIndex = 0; EdgeIndex < ExternalEdges.Num(); EdgeIndex++ )
{
auto Edge = MakeTuple(
Verts.GetPosition( Indexes[ EdgeIndex ] ),
Verts.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 < Verts.Num(); i++ )
{
Verts.GetPosition(i) *= InvScale;
Bounds += Verts.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 = Verts.GetPosition( Indexes[ TriIndex * 3 + 0 ] );
Center += Verts.GetPosition( Indexes[ TriIndex * 3 + 1 ] );
Center += Verts.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, 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 Verts.GetPosition( Indexes[ CornerIndex ] );
},
[&]( int32 EdgeIndex, int32 OtherEdgeIndex )
{
Adjacency.Link( EdgeIndex, OtherEdgeIndex );
} );
}
return Adjacency;
}
uint32 FVertexArray::FindOrAddHash( const float* Vert, uint32 VertIndex, 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 ) )
{
uint32 i;
for( i = 0; i < VertSize; i++ )
{
if( Vert[i] != Array[ NewIndex * VertSize + i ] )
break;
}
if( i == VertSize )
break;
}
if( !HashTable.IsValid( NewIndex ) )
{
NewIndex = VertIndex;
HashTable.Add( Hash, NewIndex );
}
return NewIndex;
}
template< typename TTransformFunc >
inline uint32 FCluster::AddVertMismatched(
uint32 SrcVertIndex,
const FCluster& SrcCluster,
FHashTable& HashTable,
TTransformFunc&& TransformFunc )
{
// Create a temporary new vertex that will hold copied and default-initialized data
const uint32 TempIndex = Verts.Num();
Verts.AddUninitialized();
Verts.GetPosition( TempIndex ) = SrcCluster.Verts.GetPosition( SrcVertIndex );
CopyAttributes( TempIndex, SrcVertIndex, SrcCluster );
TransformFunc( TempIndex );
uint32 NewIndex = Verts.FindOrAddHash( (float*)&Verts.GetPosition( TempIndex ), TempIndex, HashTable );
if( NewIndex != TempIndex )
{
// Already exists, remove the temporary
Verts.RemoveAt( TempIndex );
}
return NewIndex;
}
void FCluster::CopyAttributes(
uint32 DstVertIndex,
uint32 SrcVertIndex,
const FCluster& SrcCluster )
{
Verts.CopyAttributes( DstVertIndex, SrcVertIndex, SrcCluster.Verts );
}
void FCluster::LerpAttributes(
uint32 DstVertIndex,
uint32 SrcTriIndex,
const FCluster& SrcCluster,
const FVector3f& Barycentrics )
{
FUintVector3 SrcVertIndexes(
SrcCluster.Indexes[ SrcTriIndex * 3 + 0 ],
SrcCluster.Indexes[ SrcTriIndex * 3 + 1 ],
SrcCluster.Indexes[ SrcTriIndex * 3 + 2 ] );
Verts.LerpAttributes( DstVertIndex, SrcVertIndexes, SrcCluster.Verts, Barycentrics );
}
// TODO move
void FVertexArray::CopyAttributes(
uint32 DstVertIndex,
uint32 SrcVertIndex,
const FVertexArray& SrcVerts )
{
if( Format.Matches( SrcVerts.Format ) )
{
uint32 AttrSize = VertSize - 3;
FMemory::Memcpy( GetAttributes( DstVertIndex ), SrcVerts.GetAttributes( SrcVertIndex ), AttrSize * sizeof( float ) );
return;
}
GetNormal( DstVertIndex ) = SrcVerts.GetNormal( SrcVertIndex );
if( Format.bHasTangents )
{
GetTangentX( DstVertIndex ) = SrcVerts.Format.bHasTangents ? SrcVerts.GetTangentX( SrcVertIndex ) : FVector3f(0.0f);
GetTangentYSign( DstVertIndex ) = SrcVerts.Format.bHasTangents ? SrcVerts.GetTangentYSign( SrcVertIndex ) : 1.0f;
}
if( Format.bHasColors )
{
GetColor( DstVertIndex ) = SrcVerts.Format.bHasColors ? SrcVerts.GetColor( SrcVertIndex ) : FLinearColor::White;
}
const uint32 NumUVsToCopy = FMath::Min( Format.NumTexCoords, SrcVerts.Format.NumTexCoords );
FMemory::Memcpy( GetUVs( DstVertIndex ), SrcVerts.GetUVs( SrcVertIndex ), NumUVsToCopy * sizeof( FVector2f ) );
if( Format.NumTexCoords > NumUVsToCopy )
FMemory::Memzero( GetUVs( DstVertIndex ) + NumUVsToCopy, ( Format.NumTexCoords - NumUVsToCopy ) * sizeof( FVector2f ) );
const uint32 NumInfluencesToCopy = FMath::Min( Format.NumBoneInfluences, SrcVerts.Format.NumBoneInfluences );
if( NumInfluencesToCopy > 0 )
FMemory::Memcpy( GetBoneInfluences( DstVertIndex ), SrcVerts.GetBoneInfluences( SrcVertIndex ), NumInfluencesToCopy * sizeof( FVector2f ) );
if( Format.NumBoneInfluences > NumInfluencesToCopy )
FMemory::Memzero( GetBoneInfluences( DstVertIndex ) + NumInfluencesToCopy, ( Format.NumBoneInfluences - NumInfluencesToCopy ) * sizeof( FVector2f ) );
}
void FVertexArray::LerpAttributes(
uint32 DstVertIndex,
const FUintVector3& SrcVertIndexes,
const FVertexArray& SrcVerts,
const FVector3f& Barycentrics )
{
check( Format.NumTexCoords >= SrcVerts.Format.NumTexCoords );
check( Format.NumBoneInfluences >= SrcVerts.Format.NumBoneInfluences );
if( Format.Matches( SrcVerts.Format ) )
{
const float* SrcAttributes0 = SrcVerts.GetAttributes( SrcVertIndexes[0] );
const float* SrcAttributes1 = SrcVerts.GetAttributes( SrcVertIndexes[1] );
const float* SrcAttributes2 = SrcVerts.GetAttributes( SrcVertIndexes[2] );
float* DstAttributes = GetAttributes( DstVertIndex );
uint32 AttrSize = Format.GetBoneInfluenceOffset() - 3;
for( uint32 i = 0; i < AttrSize; i++ )
{
DstAttributes[i] =
SrcAttributes0[i] * Barycentrics[0] +
SrcAttributes1[i] * Barycentrics[1] +
SrcAttributes2[i] * Barycentrics[2];
}
}
else
{
GetNormal( DstVertIndex ) =
SrcVerts.GetNormal( SrcVertIndexes[0] ) * Barycentrics[0] +
SrcVerts.GetNormal( SrcVertIndexes[1] ) * Barycentrics[1] +
SrcVerts.GetNormal( SrcVertIndexes[2] ) * Barycentrics[2];
if( Format.bHasTangents )
{
if( SrcVerts.Format.bHasTangents )
{
GetTangentX( DstVertIndex ) =
SrcVerts.GetTangentX( SrcVertIndexes[0] ) * Barycentrics[0] +
SrcVerts.GetTangentX( SrcVertIndexes[1] ) * Barycentrics[1] +
SrcVerts.GetTangentX( SrcVertIndexes[2] ) * Barycentrics[2];
// Need to lerp?
GetTangentYSign( DstVertIndex ) =
SrcVerts.GetTangentYSign( SrcVertIndexes[0] ) * Barycentrics[0] +
SrcVerts.GetTangentYSign( SrcVertIndexes[1] ) * Barycentrics[1] +
SrcVerts.GetTangentYSign( SrcVertIndexes[2] ) * Barycentrics[2];
}
else
{
// TODO
GetTangentX( DstVertIndex ) = FVector3f(0.0f);
GetTangentYSign( DstVertIndex ) = 1.0f;
}
}
if( Format.bHasColors )
{
if( SrcVerts.Format.bHasColors )
{
GetColor( DstVertIndex ) =
SrcVerts.GetColor( SrcVertIndexes[0] ) * Barycentrics[0] +
SrcVerts.GetColor( SrcVertIndexes[1] ) * Barycentrics[1] +
SrcVerts.GetColor( SrcVertIndexes[2] ) * Barycentrics[2];
}
else
GetColor( DstVertIndex ) = FLinearColor::White;
}
for( uint32 UVIndex = 0; UVIndex < SrcVerts.Format.NumTexCoords; UVIndex++ )
{
GetUVs( DstVertIndex )[ UVIndex ] =
SrcVerts.GetUVs( SrcVertIndexes[0] )[ UVIndex ] * Barycentrics[0] +
SrcVerts.GetUVs( SrcVertIndexes[1] )[ UVIndex ] * Barycentrics[1] +
SrcVerts.GetUVs( SrcVertIndexes[2] )[ UVIndex ] * Barycentrics[2];
}
FMemory::Memzero( GetUVs( DstVertIndex ) + SrcVerts.Format.NumTexCoords, ( Format.NumTexCoords - SrcVerts.Format.NumTexCoords ) * sizeof( FVector2f ) );
}
if( SrcVerts.Format.NumBoneInfluences > 0 )
{
// Copy dominant skinning attributes instead of interpolating them
int32 DomCorner = FMath::Max3Index( Barycentrics[0], Barycentrics[1], Barycentrics[2] );
uint32 DomIndex = SrcVertIndexes[ DomCorner ];
FMemory::Memcpy( GetBoneInfluences( DstVertIndex ), SrcVerts.GetBoneInfluences( DomIndex ), SrcVerts.Format.NumBoneInfluences * sizeof( FVector2f ) );
FMemory::Memzero( GetBoneInfluences( DstVertIndex ) + SrcVerts.Format.NumBoneInfluences, ( Format.NumBoneInfluences - SrcVerts.Format.NumBoneInfluences ) * sizeof( FVector2f ) );
}
}
void FCluster::Bound()
{
Bounds = FBounds3f();
SurfaceArea = 0.0f;
TArray< FVector3f, TInlineAllocator<128> > Positions;
Positions.SetNum( Verts.Num(), EAllowShrinking::No );
for( uint32 i = 0; i < Verts.Num(); i++ )
{
Positions[i] = Verts.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] = Verts.GetPosition( Indexes[ i + 0 ] );
v[1] = Verts.GetPosition( Indexes[ i + 1 ] );
v[2] = Verts.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 );
}
#if RAY_TRACE_VOXELS
#if 0
// [Loubet and Neyret 2017, "Hybrid mesh-volume LoDs for all-scale pre-filtering of complex 3D assets"]
static void GenerateRay( uint32 SampleIndex, uint32& Seed, FVector3f VoxelCenter, float VoxelSize, FVector3f& Origin, FVector3f& Direction, FVector2f& Time )
{
Direction = UniformSampleSphere( SobolSampler( SampleIndex, Seed ) );
Origin = FVector3f( LatticeSampler( SampleIndex, Seed ) ) - 0.5f;
FVector2f Gaussian0 = GaussianSampleDisk( SobolSampler( SampleIndex, Seed ), 0.6f, 1.5f );
FVector2f Gaussian1 = GaussianSampleDisk( SobolSampler( SampleIndex, Seed ), 0.6f, 1.5f );
Origin += FVector3f( Gaussian0.X, Gaussian0.Y, Gaussian1.X );
Origin *= VoxelSize;
Origin += VoxelCenter;
Time[0] = 0.0f;
Time[1] = VoxelSize;
}
#elif 1
static void GenerateRay( uint32 SampleIndex, uint32& Seed, FVector3f VoxelCenter, float VoxelSize, FVector3f& Origin, FVector3f& Direction, FVector2f& Time )
{
do
{
Direction = UniformSampleSphere( SobolSampler( SampleIndex, Seed ) );
// [ Duff et al. 2017, "Building an Orthonormal Basis, Revisited" ]
const float Sign = Direction.Z >= 0.0f ? 1.0f : -1.0f;
const float a = -1.0f / ( Sign + Direction.Z );
const float b = Direction.X * Direction.Y * a;
FVector3f TangentX( 1.0f + Sign * a * FMath::Square( Direction.X ), Sign * b, -Sign * Direction.X );
FVector3f TangentY( b, Sign + a * FMath::Square( Direction.Y ), -Direction.Y );
FVector2f Disk = UniformSampleDisk( SobolSampler( SampleIndex, Seed ) );
Disk *= VoxelSize * 0.5f * UE_SQRT_3;
Origin = TangentX * Disk.X;
Origin += TangentY * Disk.Y;
// Reject sample if it doesn't hit voxel
const FVector3f InvDir = 1.0f / Direction;
const FVector3f Center = -Origin * InvDir;
const FVector3f Extent = InvDir.GetAbs() * ( VoxelSize * 0.5f );
const FVector3f MinIntersection = Center - Extent;
const FVector3f MaxIntersection = Center + Extent;
Time[0] = MinIntersection.GetMax();
Time[1] = MaxIntersection.GetMin();
} while( Time[0] >= Time[1] );
Origin += VoxelCenter;
// Force start to zero, negative isn't supported
Origin += Direction * Time[0];
Time[1] -= Time[0];
Time[0] = 0.0f;
}
#else
static void GenerateRay( uint32 SampleIndex, uint32& Seed, FVector3f VoxelCenter, float VoxelSize, FVector3f& Origin, FVector3f& Direction, FVector2f& Time )
{
//FVector4f Rand = LatticeSampler( SampleIndex, Seed );
Direction = UniformSampleSphere( SobolSampler( SampleIndex, Seed ) );
// [ Duff et al. 2017, "Building an Orthonormal Basis, Revisited" ]
const float Sign = Direction.Z >= 0.0f ? 1.0f : -1.0f;
const float a = -1.0f / ( Sign + Direction.Z );
const float b = Direction.X * Direction.Y * a;
FVector3f TangentX( 1.0f + Sign * a * FMath::Square( Direction.X ), Sign * b, -Sign * Direction.X );
FVector3f TangentY( b, Sign + a * FMath::Square( Direction.Y ), -Direction.Y );
//FVector2f Disk = UniformSampleDisk( FVector2f( Rand.Z, Rand.W ) ) * 0.5f;
FVector2f Disk = GaussianSampleDisk( SobolSampler( SampleIndex, Seed ), 0.5f, 1.0f );
Disk *= VoxelSize;
Origin = VoxelCenter;
Origin += TangentX * Disk.X;
Origin += TangentY * Disk.Y;
Time[0] = -0.5f * VoxelSize;
Time[1] = +0.5f * VoxelSize;
}
#endif
static void GenerateRayAligned( uint32 SampleIndex, uint32& Seed, FVector3f VoxelCenter, float VoxelSize, FVector3f& Origin, FVector3f& Direction, FVector2f& Time )
{
uint32 RandIndex = SampleIndex + EvolveSobolSeed( Seed );
uint32 Face = RandIndex % 6;
float Sign = (Face & 1) ? 1.0f : -1.0f;
const int32 SwizzleZ = Face >> 1;
const int32 SwizzleX = ( 1 << SwizzleZ ) & 3;
const int32 SwizzleY = ( 1 << SwizzleX ) & 3;
FVector2f Sobol = SobolSampler( SampleIndex, Seed );
Origin = VoxelCenter;
Origin[ SwizzleX ] += VoxelSize * ( Sobol.X - 1.0f );
Origin[ SwizzleY ] += VoxelSize * ( Sobol.Y - 1.0f );
Origin[ SwizzleZ ] -= VoxelSize * 0.5f * Sign;
Direction[ SwizzleX ] = 0.0f;
Direction[ SwizzleY ] = 0.0f;
Direction[ SwizzleZ ] = Sign;
Time[0] = 0.0f;
Time[1] = VoxelSize;
}
bool TestCrosshair( const FRayTracingScene& RayTracingScene, const FVector3f& VoxelCenter, float VoxelSize, uint32& HitInstanceIndex, uint32& HitClusterIndex, uint32& HitTriIndex, FVector3f& HitBarycentrics )
{
FVector2f Time( 0.0f, VoxelSize );
for( int j = 0; j < 3; j++ )
{
FVector3f Origin = VoxelCenter;
Origin[j] -= 0.5f * VoxelSize;
FVector3f Direction( 0.0f );
Direction[j] = 1.0f;
// TODO use Ray4
FRay1 Ray = {};
Ray.SetRay( Origin, Direction, Time );
RayTracingScene.Intersect1( Ray );
if( RayTracingScene.GetHit( Ray, HitInstanceIndex, HitClusterIndex, HitTriIndex, HitBarycentrics ) )
return true;
}
return false;
}
#endif
void FCluster::Voxelize( FClusterDAG& DAG, TArrayView< const FClusterRef > Children, float VoxelSize )
{
if( DAG.Settings.bVoxelNDF || ( DAG.Settings.bVoxelOpacity && DAG.Settings.NumRays > 1 ) )
Verts.Format.bHasColors = true;
for( FClusterRef ChildRef : Children )
{
const FCluster& Child = ChildRef.GetCluster( DAG );
const uint8 NumBoneInfluences = ChildRef.IsInstance() ?
uint8(DAG.AssemblyInstanceData[ ChildRef.InstanceIndex ].NumBoneInfluences) :
Child.Verts.Format.NumBoneInfluences;
Verts.Format.NumTexCoords = FMath::Max( Verts.Format.NumTexCoords, Child.Verts.Format.NumTexCoords );
Verts.Format.NumBoneInfluences = FMath::Max( Verts.Format.NumBoneInfluences, NumBoneInfluences );
Verts.Format.bHasTangents |= Child.Verts.Format.bHasTangents;
Verts.Format.bHasColors |= Child.Verts.Format.bHasColors;
// Can jump multiple levels but guarantee it steps at least 1.
MipLevel = FMath::Max( MipLevel, Child.MipLevel + 1 );
GUID = Murmur64( { GUID, Child.GUID } );
}
#if RAY_TRACE_VOXELS
// We have to take into account the worst-case vertex format of the whole scene
Verts.Format.NumTexCoords = FMath::Max( Verts.Format.NumTexCoords, DAG.MaxTexCoords );
Verts.Format.NumBoneInfluences = FMath::Max( Verts.Format.NumBoneInfluences, DAG.MaxBoneInfluences );
Verts.Format.bHasTangents |= DAG.bHasTangents;
Verts.Format.bHasColors |= DAG.bHasColors;
#endif
Verts.InitFormat();
TMap< FIntVector3, uint32 > VoxelMap;
check( VoxelSize > 0.0f );
const float RcpVoxelSize = 1.0f / VoxelSize;
for( FClusterRef ChildRef : Children )
{
const FCluster& Child = ChildRef.GetCluster( DAG );
FMatrix44f Transform = FMatrix44f::Identity;
bool bTransform = ChildRef.IsInstance();
if( bTransform )
{
Transform = ChildRef.GetTransform( DAG );
}
if( Child.NumTris )
{
for( uint32 TriIndex = 0; TriIndex < Child.NumTris; TriIndex++ )
{
FVector3f Triangle[3];
for( int k = 0; k < 3; k++ )
{
Triangle[k] = Child.Verts.GetPosition( Child.Indexes[ TriIndex * 3 + k ] );
if( bTransform )
Triangle[k] = Transform.TransformPosition( Triangle[k] );
Triangle[k] *= RcpVoxelSize;
}
VoxelizeTri26( Triangle,
[&]( const FIntVector3& Voxel, const FVector3f& Barycentrics )
{
VoxelMap.FindOrAdd( Voxel, VoxelMap.Num() );
} );
}
}
else
{
auto AddVoxels = [&]( const FVector3f& Center, float Extent )
{
FBounds3f VoxelBounds = { Center - Extent, Center + Extent };
if( bTransform )
VoxelBounds = VoxelBounds.TransformBy( Transform );
const FIntVector3 MinVoxel = FloorToInt( FVector3f( VoxelBounds.Min ) * RcpVoxelSize );
const FIntVector3 MaxVoxel = FloorToInt( FVector3f( VoxelBounds.Max ) * RcpVoxelSize );
for( int32 z = MinVoxel.Z; z <= MaxVoxel.Z; z++ )
{
for( int32 y = MinVoxel.Y; y <= MaxVoxel.Y; y++ )
{
for( int32 x = MinVoxel.X; x <= MaxVoxel.X; x++ )
{
VoxelMap.FindOrAdd( FIntVector3(x,y,z), VoxelMap.Num() );
}
}
}
};
for( int32 BrickIndex = 0; BrickIndex < Child.Bricks.Num(); BrickIndex++ )
{
int32 MaterialIndex = Child.MaterialIndexes[ BrickIndex ];
uint32 NumVoxels = FMath::CountBits( Child.Bricks[ BrickIndex ].VoxelMask );
for( uint32 i = 0; i < NumVoxels; i++ )
{
uint32 VertIndex = Child.Bricks[ BrickIndex ].VertOffset + i;
AddVoxels( Child.Verts.GetPosition( VertIndex ), Child.LODError * 0.5f );
}
}
#if RAY_TRACE_VOXELS
for( const FIntVector3& Voxel : Child.ExtraVoxels )
{
AddVoxels( ( Voxel + 0.5f ) * Child.LODError, Child.LODError * 0.5f );
}
#endif
}
}
#if RAY_TRACE_VOXELS
const FRayTracingScene& RayTracingScene = DAG.RayTracingScene;
check( ExtraVoxels.Num() == 0 );
const float RayBackUp = VoxelSize * DAG.Settings.RayBackUp;
const uint32 NumRayPackets = FMath::DivideAndRoundUp( DAG.Settings.NumRays, 16u );
const uint32 NumRayVariations = 64;
TArray< FRay16 > Rays;
if( DAG.Settings.NumRays > 1 )
{
Rays.AddZeroed( NumRayPackets * NumRayVariations );
for( int32 i = 0; i < Rays.Num(); i++ )
{
for( uint32 j = 0; j < 16; j++ )
{
uint32 SampleIndex = ReverseBits( 16 * i + j );
uint32 Seed = 0;
FVector3f Origin;
FVector3f Direction;
FVector2f Time;
if( DAG.Settings.bSeparable )
{
GenerateRayAligned( SampleIndex, Seed, FVector3f( 0.0f ), VoxelSize, Origin, Direction, Time );
Origin -= Direction * VoxelSize;
Time[1] += VoxelSize * 2.0f;
}
else
{
GenerateRay( SampleIndex, Seed, FVector3f( 0.0f ), VoxelSize, Origin, Direction, Time );
Origin -= Direction * RayBackUp;
Time[1] += RayBackUp;
}
Rays[i].SetRay( j, Origin, Direction, Time );
}
}
}
static_assert( NANITE_ASSEMBLY_TRANSFORM_INDEX_BITS <= 25 );
struct FSampledVoxel
{
float Coverage; //TEMP
TSGGX< float > NDF;
uint32 ClusterIndex;
uint32 InstanceIndex : NANITE_ASSEMBLY_TRANSFORM_INDEX_BITS;
uint32 TriIndex : 32 - NANITE_ASSEMBLY_TRANSFORM_INDEX_BITS;
uint16 Barycentrics[2];
};
TArray< FSampledVoxel > SampledVoxels;
SampledVoxels.AddZeroed( VoxelMap.Num() );
ParallelFor( TEXT("Nanite.VoxelizeTrace.PF"), VoxelMap.Num(), 4,
[&]( int32 VoxelIndex )
{
FIntVector3 Voxel = VoxelMap.Get( FSetElementId::FromInteger( VoxelIndex ) ).Key;
FVector3f VoxelCenter = ( Voxel + 0.5f ) * VoxelSize;
FSampledVoxel& SampledVoxel = SampledVoxels[ VoxelIndex ];
uint32 HitInstanceIndex = 0;
uint32 HitClusterIndex = 0;
uint32 HitTriIndex = 0;
FVector3f HitBarycentrics;
uint16 HitCount = 0;
uint16 RayCount = 0;
if( DAG.Settings.NumRays > 1 )
{
uint32 HitCountDim[3] = {};
uint32 RayCountDim[3] = {};
int32 VaritationIndex = VoxelIndex & ( NumRayVariations - 1 );
for( uint32 PacketIndex = 0; PacketIndex < NumRayPackets; PacketIndex++ )
{
FRay16 Ray16 = Rays[ PacketIndex + VaritationIndex * NumRayPackets ];
for( int j = 0; j < 16; j++ )
{
Ray16.ray.org_x[j] += VoxelCenter.X;
Ray16.ray.org_y[j] += VoxelCenter.Y;
Ray16.ray.org_z[j] += VoxelCenter.Z;
}
RayTracingScene.Intersect16( Ray16 );
RayCount += 16;
for( int j = 0; j < 16; j++ )
{
uint32 Dim = FMath::Max3Index( FMath::Abs( Ray16.ray.dir_x[j] ), FMath::Abs( Ray16.ray.dir_y[j] ), FMath::Abs( Ray16.ray.dir_z[j] ) );
RayCountDim[ Dim ]++;
if( RayTracingScene.GetHit( Ray16, j, HitInstanceIndex, HitClusterIndex, HitTriIndex, HitBarycentrics ) )
{
if( DAG.Settings.bSeparable )
{
if( Ray16.ray.tfar[j] < VoxelSize ||
Ray16.ray.tfar[j] > VoxelSize * 2.0f )
{
RayCount--;
RayCountDim[ Dim ]--;
continue;
}
}
else if( Ray16.ray.tfar[j] < RayBackUp )
{
RayCount--;
continue;
}
HitCount++;
HitCountDim[ Dim ]++;
// Sample attributes from hit triangle
FCluster& HitCluster = DAG.Clusters[ HitClusterIndex ];
FVector3f HitNormal =
HitCluster.Verts.GetNormal( HitCluster.Indexes[ HitTriIndex * 3 + 0 ] ) * HitBarycentrics[0] +
HitCluster.Verts.GetNormal( HitCluster.Indexes[ HitTriIndex * 3 + 1 ] ) * HitBarycentrics[1] +
HitCluster.Verts.GetNormal( HitCluster.Indexes[ HitTriIndex * 3 + 2 ] ) * HitBarycentrics[2];
if( HitInstanceIndex != ~0u )
{
const FMatrix44f& Transform = DAG.AssemblyInstanceData[ HitInstanceIndex ].Transform;
HitNormal = Transform.GetMatrixWithoutScale().TransformVector( HitNormal );
}
HitNormal.Normalize();
SampledVoxel.NDF += HitNormal;
}
}
}
if( DAG.Settings.bSeparable )
{
// Force covered if all rays along 1 axis hit something
if( ( RayCountDim[0] && RayCountDim[0] == HitCountDim[0] ) ||
( RayCountDim[1] && RayCountDim[1] == HitCountDim[1] ) ||
( RayCountDim[2] && RayCountDim[2] == HitCountDim[2] ) )
{
uint32 Dummy1, Dummy2, Dummy3;
FVector3f Dummy4;
if( TestCrosshair( RayTracingScene, VoxelCenter, VoxelSize, Dummy1, Dummy2, Dummy3, Dummy4 ) )
RayCount = HitCount;
}
}
}
else
{
if( DAG.Settings.bSeparable )
{
RayCount++;
if( TestCrosshair( RayTracingScene, VoxelCenter, VoxelSize, HitInstanceIndex, HitClusterIndex, HitTriIndex, HitBarycentrics ) )
HitCount++;
}
else
{
uint32 TileID;
TileID = FMath::MortonCode3( Voxel.X & 1023 );
TileID |= FMath::MortonCode3( Voxel.Y & 1023 ) << 1;
TileID |= FMath::MortonCode3( Voxel.Z & 1023 ) << 2;
FRay1 Ray = {};
{
uint32 SampleIndex = ReverseBits( TileID );
uint32 Seed = 0;
FVector3f Origin;
FVector3f Direction;
FVector2f Time;
GenerateRay( SampleIndex, Seed, VoxelCenter, VoxelSize, Origin, Direction, Time );
Ray.SetRay( Origin, Direction, Time );
}
RayTracingScene.Intersect1( Ray );
RayCount++;
if( RayTracingScene.GetHit( Ray, HitInstanceIndex, HitClusterIndex, HitTriIndex, HitBarycentrics ) )
HitCount++;
}
}
if( RayCount > 0 )
{
SampledVoxel.NDF /= RayCount;
SampledVoxel.Coverage = (float)HitCount / RayCount;
}
if( HitCount > 0 )
{
SampledVoxel.ClusterIndex = HitClusterIndex;
SampledVoxel.InstanceIndex = HitInstanceIndex;
SampledVoxel.TriIndex = HitTriIndex;
SampledVoxel.Barycentrics[0] = (uint16)FMath::RoundToInt32( HitBarycentrics[0] * 65535.0f );
SampledVoxel.Barycentrics[1] = (uint16)FMath::RoundToInt32( HitBarycentrics[1] * 65535.0f );
SampledVoxel.Barycentrics[1] = FMath::Min< uint16 >( SampledVoxel.Barycentrics[1], 65535 - SampledVoxel.Barycentrics[0] );
}
},
EParallelForFlags::Unbalanced );
uint32 Seed = 0;
if( DAG.Settings.NumRays > 1 && !DAG.Settings.bVoxelOpacity )
{
FBinaryHeap< float > CoverageHeap( VoxelMap.Num(), VoxelMap.GetMaxIndex() );
float CoverageSum = 0.0f;
for( auto It = VoxelMap.CreateIterator(); It; ++It )
{
FSampledVoxel& SampledVoxel = SampledVoxels[ It.Value() ];
if( SampledVoxel.Coverage > 0.0f )
{
CoverageHeap.Add( SampledVoxel.Coverage, It.GetId().AsInteger() );
CoverageSum += SampledVoxel.Coverage;
}
else
{
// Remember rejected voxels, so their volume still gets sampled at higher levels
if( DAG.Settings.NumRays < 32 )
ExtraVoxels.Add( It.Key() );
It.RemoveCurrent();
}
}
while( (float)CoverageHeap.Num() > CoverageSum )
{
uint32 VoxelIndex = CoverageHeap.Top();
float Coverage = CoverageHeap.GetKey( VoxelIndex );
CoverageHeap.Pop();
FSetElementId VoxelId = FSetElementId::FromInteger( VoxelIndex );
FIntVector3 Voxel = VoxelMap.Get( VoxelId ).Key;
VoxelMap.Remove( VoxelId );
// Remember rejected voxels, so their volume still gets sampled at higher levels
ExtraVoxels.Add( Voxel );
FSampledVoxel& SelfData = SampledVoxels[ VoxelIndex ];
float TotalWeight = 0.0f;
// Distribute coverage to neighbors
struct FNeighbor
{
FSetElementId Id;
float Weight;
};
TArray< FNeighbor, TFixedAllocator<27> > Neighbors;
for( int32 z = -1; z <= 1; z++ )
{
for( int32 y = -1; y <= 1; y++ )
{
for( int32 x = -1; x <= 1; x++ )
{
FSetElementId NeighborId = VoxelMap.FindId( Voxel + FIntVector3(x,y,z) );
if( NeighborId.IsValidId() )
{
FVector3f Direction( FIntVector3(x,y,z) );
Direction.Normalize();
FSampledVoxel& NeighborData = SampledVoxels[ VoxelMap.Get( NeighborId ).Value ];
float Weight = SelfData.NDF.ProjectedArea( Direction );
if (Weight > 0.0f)
{
Neighbors.Add( { NeighborId, Weight } );
TotalWeight += Weight;
}
}
}
}
}
for( FNeighbor& Neighbor : Neighbors )
{
uint32 AdjIndex = Neighbor.Id.AsInteger();
FSampledVoxel& NeighborData = SampledVoxels[ VoxelMap.Get( Neighbor.Id ).Value ];
float SrcCoverage = Coverage * Neighbor.Weight / TotalWeight;
float DstCoverage = CoverageHeap.GetKey( AdjIndex );
// Average of over and under case
float SrcBlend = 1.0f - 0.5f * DstCoverage;
float DstBlend = 1.0f - 0.5f * SrcCoverage;
// Stochastic choice of src or dst attributes
float Rand = ( EvolveSobolSeed( Seed ) >> 8 ) * 5.96046447754e-08f; // * 2^-24
if( SrcCoverage * SrcBlend > ( SrcCoverage * SrcBlend + DstCoverage * DstBlend ) * Rand )
{
NeighborData.ClusterIndex = SelfData.ClusterIndex;
NeighborData.InstanceIndex = SelfData.InstanceIndex;
NeighborData.TriIndex = SelfData.TriIndex;
NeighborData.Barycentrics[0] = SelfData.Barycentrics[0];
NeighborData.Barycentrics[1] = SelfData.Barycentrics[1];
}
SrcBlend *= Neighbor.Weight / TotalWeight;
NeighborData.NDF =
SrcBlend * SelfData.NDF +
DstBlend * NeighborData.NDF;
NeighborData.Coverage = SrcCoverage + DstCoverage - SrcCoverage * DstCoverage;
CoverageHeap.Update( NeighborData.Coverage, AdjIndex );
}
}
}
else
{
for( auto It = VoxelMap.CreateIterator(); It; ++It )
{
FSampledVoxel& SampledVoxel = SampledVoxels[ It.Value() ];
if( SampledVoxel.Coverage == 0.0f )
{
// Remember rejected voxels, so their volume still gets sampled at higher levels
if( DAG.Settings.NumRays < 32 )
ExtraVoxels.Add( It.Key() );
It.RemoveCurrent();
}
}
}
// Create Verts, lerp attributes
for( auto& Voxel : VoxelMap )
{
uint32 OldIndex = Voxel.Value;
uint32 NewIndex = Voxel.Value = Verts.AddUninitialized();
FSampledVoxel& SampledVoxel = SampledVoxels[ OldIndex ];
const FCluster& Cluster = DAG.Clusters[ SampledVoxel.ClusterIndex ];
MaterialIndexes.Add( Cluster.MaterialIndexes[ SampledVoxel.TriIndex ] );
Verts.GetPosition( NewIndex ) = ( Voxel.Key + 0.5f ) * VoxelSize;
FVector3f Barycentrics;
Barycentrics[0] = SampledVoxel.Barycentrics[0] / 65535.0f;
Barycentrics[1] = SampledVoxel.Barycentrics[1] / 65535.0f;
Barycentrics[2] = 1.0f - Barycentrics[0] - Barycentrics[1];
LerpAttributes( NewIndex, SampledVoxel.TriIndex, Cluster, Barycentrics );
if( SampledVoxel.InstanceIndex != NANITE_MAX_ASSEMBLY_TRANSFORMS )
{
const FAssemblyInstanceData& InstanceData = DAG.AssemblyInstanceData[ SampledVoxel.InstanceIndex ];
if( Verts.Format.bHasTangents )
{
FVector3f& TangentX = Verts.GetTangentX( NewIndex );
TangentX = InstanceData.Transform.GetMatrixWithoutScale().TransformVector( TangentX );
// ASSEMBLYTODO GetTangentYSign needs to negate if transform determinent is <0
}
// Instanced clusters' verts receive their assembly part's influences when being transformed to local
if( InstanceData.NumBoneInfluences > 0 )
{
check( Verts.Format.NumBoneInfluences >= InstanceData.NumBoneInfluences );
FMemory::Memcpy(
Verts.GetBoneInfluences( NewIndex ),
&DAG.AssemblyBoneInfluences[ InstanceData.FirstBoneInfluence ],
InstanceData.NumBoneInfluences * sizeof( FVector2f ) );
}
FMemory::Memzero(
Verts.GetBoneInfluences( NewIndex ) + InstanceData.NumBoneInfluences,
(Verts.Format.NumBoneInfluences - InstanceData.NumBoneInfluences) * sizeof( FVector2f ) );
}
if( DAG.Settings.bVoxelNDF )
{
FVector3f AvgNormal;
FVector2f Alpha;
SampledVoxel.NDF.FitIsotropic( AvgNormal, Alpha );
Verts.GetNormal( NewIndex ) = AvgNormal;
//GetColor( NewIndex ).A = (2.0f / PI) * FMath::Atan2( Alpha.X, Alpha.Y );
if( Alpha.X > Alpha.Y )
Verts.GetColor( NewIndex ).A = 1.0f - 0.5f * Alpha.Y / Alpha.X;
else
Verts.GetColor( NewIndex ).A = 0.5f * Alpha.X / Alpha.Y;
}
if( DAG.Settings.bVoxelOpacity )
{
Verts.GetColor( NewIndex ).B = SampledVoxel.Coverage;
}
CorrectAttributesFunctions[ Verts.Format.bHasTangents ][ Verts.Format.bHasColors ]( Verts.GetAttributes( NewIndex ) );
}
if( VoxelMap.Num() == 0 )
{
// VOXELTODO: Silly workaround for the case where no voxels are hit by rays.
// Solve this properly.
const FCluster& FirstChild = Children[0].GetCluster( DAG );
// ASSEMBLYTODO
const FVector3f Center = FirstChild.Verts.GetPosition( 0 ) * RcpVoxelSize;
const FIntVector3 Voxel = FloorToInt( Center );
VoxelMap.Add( Voxel, 0 );
Verts.AddUninitialized();
MaterialIndexes.Add( FirstChild.MaterialIndexes[ 0 ] );
Verts.GetPosition( 0 ) = ( Voxel + 0.5f ) * VoxelSize;
CopyAttributes( 0, 0, FirstChild );
}
#endif
check( MaterialIndexes.Num() > 0 );
VoxelsToBricks( VoxelMap );
}
void FCluster::VoxelsToBricks( TMap< FIntVector3, uint32 >& VoxelMap )
{
check( Bricks.IsEmpty() );
FVertexArray NewVerts( Verts.Format );
TArray< int32 > NewMaterialIndexes;
TSet< FIntVector4 > BrickSet;
for( auto& Voxel : VoxelMap )
BrickSet.FindOrAdd( FIntVector4( Voxel.Key & ~3, MaterialIndexes[ Voxel.Value ] ) );
TArray< FIntVector4 > SortedBricks = BrickSet.Array();
SortedBricks.Sort(
[]( const FIntVector4& A, const FIntVector4& B )
{
if( A.W != B.W )
return A.W < B.W;
else if( A.Z != B.Z )
return A.Z < B.Z;
else if( A.Y != B.Y )
return A.Y < B.Y;
else
return A.X < B.X;
} );
for( FIntVector4& Candidate : SortedBricks )
{
FBrick Brick;
Brick.VoxelMask = 0;
Brick.Position = FIntVector3( Candidate );
Brick.VertOffset = NewVerts.Num();
FIntVector3 BrickMin( MAX_int32 );
bool bBrickValid = false;
for( uint32 z = 0; z < 4; z++ )
{
for( uint32 y = 0; y < 4; y++ )
{
for( uint32 x = 0; x < 4; x++ )
{
FIntVector3 Voxel = Brick.Position + FIntVector3(x,y,z);
uint32* VertIndex = VoxelMap.Find( Voxel );
if( VertIndex && MaterialIndexes[ *VertIndex ] == Candidate.W )
{
BrickMin = BrickMin.ComponentMin( Voxel );
bBrickValid = true;
}
}
}
}
if( !bBrickValid )
continue; // No voxels left in brick. Skip it.
Brick.Position = BrickMin;
uint32 VoxelIndex = 0;
for( uint32 z = 0; z < 4; z++ )
{
for( uint32 y = 0; y < 4; y++ )
{
for( uint32 x = 0; x < 4; x++ )
{
FIntVector3 Voxel = Brick.Position + FIntVector3(x,y,z);
uint32* VertIndex = VoxelMap.Find( Voxel );
if( VertIndex && MaterialIndexes[ *VertIndex ] == Candidate.W )
{
Brick.VoxelMask |= 1ull << VoxelIndex;
VoxelMap.Remove( Voxel );
uint32 OldIndex = *VertIndex;
uint32 NewIndex = NewVerts.Add( &Verts.GetPosition( OldIndex ) );
}
VoxelIndex++;
}
}
}
Bricks.Add( Brick );
NewMaterialIndexes.Add( Candidate.W );
}
check( VoxelMap.IsEmpty() );
Swap( Verts, NewVerts );
Swap( MaterialIndexes, NewMaterialIndexes );
}
void FCluster::BuildMaterialRanges()
{
check( MaterialRanges.Num() == 0 );
check( NumTris * 3 == Indexes.Num() );
TArray< int32, TInlineAllocator<128> > MaterialElements;
TArray< int32, TInlineAllocator<64> > MaterialCounts;
MaterialElements.AddUninitialized( MaterialIndexes.Num() );
MaterialCounts.AddZeroed( NANITE_MAX_CLUSTER_MATERIALS );
// Tally up number per material index
for( int32 i = 0; i < MaterialIndexes.Num(); i++ )
{
MaterialElements[i] = i;
MaterialCounts[ MaterialIndexes[i] ]++;
}
// Sort by range count descending, and material index ascending.
// This groups the material ranges from largest to smallest, which is
// more efficient for evaluating the sequences on the GPU, and also makes
// the minus one encoding work (the first range must have more than 1 tri).
MaterialElements.Sort(
[&]( int32 A, int32 B )
{
int32 IndexA = MaterialIndexes[A];
int32 IndexB = MaterialIndexes[B];
int32 CountA = MaterialCounts[ IndexA ];
int32 CountB = MaterialCounts[ IndexB ];
if( CountA != CountB )
return CountA > CountB;
if( IndexA != IndexB )
return IndexA < IndexB;
return A < B;
} );
FMaterialRange CurrentRange;
CurrentRange.RangeStart = 0;
CurrentRange.RangeLength = 0;
CurrentRange.MaterialIndex = MaterialElements.Num() > 0 ? MaterialIndexes[ MaterialElements[0] ] : 0;
for( int32 i = 0; i < MaterialElements.Num(); i++ )
{
int32 MaterialIndex = MaterialIndexes[ MaterialElements[i] ];
// Material changed, so add current range and reset
if (CurrentRange.RangeLength > 0 && MaterialIndex != CurrentRange.MaterialIndex)
{
MaterialRanges.Add(CurrentRange);
CurrentRange.RangeStart = i;
CurrentRange.RangeLength = 1;
CurrentRange.MaterialIndex = MaterialIndex;
}
else
{
++CurrentRange.RangeLength;
}
}
// Add last triangle to range
if (CurrentRange.RangeLength > 0)
{
MaterialRanges.Add(CurrentRange);
}
if( NumTris )
{
TArray< uint32 > NewIndexes;
TArray< int32 > NewMaterialIndexes;
NewIndexes.AddUninitialized( Indexes.Num() );
NewMaterialIndexes.AddUninitialized( MaterialIndexes.Num() );
for( uint32 NewIndex = 0; NewIndex < NumTris; NewIndex++ )
{
uint32 OldIndex = MaterialElements[ NewIndex ];
NewIndexes[ NewIndex * 3 + 0 ] = Indexes[ OldIndex * 3 + 0 ];
NewIndexes[ NewIndex * 3 + 1 ] = Indexes[ OldIndex * 3 + 1 ];
NewIndexes[ NewIndex * 3 + 2 ] = Indexes[ OldIndex * 3 + 2 ];
NewMaterialIndexes[ NewIndex ] = MaterialIndexes[ OldIndex ];
}
Swap( Indexes, NewIndexes );
Swap( MaterialIndexes, NewMaterialIndexes );
}
else
{
FVertexArray NewVerts( Verts.Format );
TArray< int32 > NewMaterialIndexes;
TArray< FBrick > NewBricks;
NewVerts.Reserve( Verts.Num() );
NewMaterialIndexes.AddUninitialized( MaterialIndexes.Num() );
NewBricks.AddUninitialized( Bricks.Num() );
for( int32 NewIndex = 0; NewIndex < MaterialElements.Num(); NewIndex++ )
{
int32 OldIndex = MaterialElements[ NewIndex ];
NewMaterialIndexes[ NewIndex ] = MaterialIndexes[ OldIndex ];
FBrick& OldBrick = Bricks[ OldIndex ];
FBrick& NewBrick = NewBricks[ NewIndex ];
uint32 NumVoxels = FMath::CountBits( OldBrick.VoxelMask );
NewBrick = OldBrick;
NewBrick.VertOffset = NewVerts.Add( &Verts.GetPosition( OldBrick.VertOffset ), NumVoxels );
}
Swap( Verts, NewVerts );
Swap( MaterialIndexes, NewMaterialIndexes );
Swap( Bricks, NewBricks );
}
}
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;
}
static void SanitizeVector( FVector3f& V, float MaxValue, FVector3f DefaultValue )
{
if ( !( V.X >= -MaxValue && V.X <= MaxValue &&
V.Y >= -MaxValue && V.Y <= MaxValue &&
V.Z >= -MaxValue && V.Z <= MaxValue ) ) // Don't flip condition. This is intentionally written like this to be NaN-safe.
{
V = DefaultValue;
}
}
void FVertexArray::Sanitize()
{
const float FltThreshold = NANITE_MAX_COORDINATE_VALUE;
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 );
SanitizeVector( Normal, FltThreshold, FVector3f::UpVector );
if( Format.bHasTangents )
{
FVector3f& TangentX = GetTangentX( VertexIndex );
SanitizeVector( TangentX, FltThreshold, FVector3f::ForwardVector );
float& TangentYSign = GetTangentYSign( VertexIndex );
TangentYSign = TangentYSign < 0.0f ? -1.0f : 1.0f;
}
if( Format.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 );
}
if( Format.NumTexCoords > 0 )
{
FVector2f* UVs = GetUVs( VertexIndex );
for( uint32 UVIndex = 0; UVIndex < Format.NumTexCoords; UVIndex++ )
{
SanitizeFloat( UVs[ UVIndex ].X, -FltThreshold, FltThreshold, 0.0f );
SanitizeFloat( UVs[ UVIndex ].Y, -FltThreshold, FltThreshold, 0.0f );
}
}
if( Format.NumBoneInfluences > 0 )
{
FVector2f* BoneInfluences = GetBoneInfluences( VertexIndex );
for( uint32 Influence = 0; Influence < Format.NumBoneInfluences; Influence++ )
{
SanitizeFloat( BoneInfluences[Influence].X, 0.0f, FltThreshold, 0.0f );
SanitizeFloat( BoneInfluences[Influence].Y, 0.0f, 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;
}
FClusterAreaWeightedTriangleSampler::FClusterAreaWeightedTriangleSampler()
: Cluster(nullptr)
{
}
void FClusterAreaWeightedTriangleSampler::Init(const FCluster* InCluster)
{
Cluster = InCluster;
Initialize();
}
float FClusterAreaWeightedTriangleSampler::GetWeights(TArray<float>& OutWeights)
{
//If these hit, you're trying to get weights on a sampler that's not been initialized.
check(Cluster);
TConstArrayView<uint32> Indexes = Cluster->Indexes;
float Total = 0.0f;
OutWeights.Empty(Cluster->NumTris);
int32 First = 0;
int32 Last = Cluster->NumTris;
for (int32 i = First; i < Last; i += 3)
{
FVector3f V0 = Cluster->Verts.GetPosition(Indexes[i + 0]);
FVector3f V1 = Cluster->Verts.GetPosition(Indexes[i + 1]);
FVector3f V2 = Cluster->Verts.GetPosition(Indexes[i + 2]);
float Area = ((V1 - V0) ^ (V2 - V0)).Size() * 0.5f;
OutWeights.Add(Area);
Total += Area;
}
return Total;
}
} // namespace Nanite