343 lines
11 KiB
C++
343 lines
11 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "CoreTypes.h"
|
|
#include "Components.h"
|
|
#include "StaticMeshResources.h"
|
|
#include "ThirdPartyBuildOptimizationHelper.h"
|
|
|
|
class BuildOptimizationHelper
|
|
{
|
|
public:
|
|
/** Helper struct for building acceleration structures. */
|
|
struct FIndexAndZ
|
|
{
|
|
float Z;
|
|
int32 Index;
|
|
|
|
/** Default constructor. */
|
|
FIndexAndZ() {}
|
|
|
|
/** Initialization constructor. */
|
|
FIndexAndZ(int32 InIndex, FVector V)
|
|
{
|
|
Z = 0.30f * V.X + 0.33f * V.Y + 0.37f * V.Z;
|
|
Index = InIndex;
|
|
}
|
|
};
|
|
|
|
/** Sorting function for vertex Z/index pairs. */
|
|
struct FCompareIndexAndZ
|
|
{
|
|
FORCEINLINE bool operator()(FIndexAndZ const& A, FIndexAndZ const& B) const { return A.Z < B.Z; }
|
|
};
|
|
|
|
struct FMeshEdge
|
|
{
|
|
int32 Vertices[2];
|
|
int32 Faces[2];
|
|
};
|
|
|
|
/**
|
|
* This helper class builds the edge list for a mesh. It uses a hash of vertex
|
|
* positions to edges sharing that vertex to remove the n^2 searching of all
|
|
* previously added edges.
|
|
*/
|
|
class FMeshEdgeBuilder
|
|
{
|
|
protected:
|
|
/**
|
|
* The list of indices to build the edge data from
|
|
*/
|
|
const TArray<uint32>& Indices;
|
|
/**
|
|
* The array of verts for vertex position comparison
|
|
*/
|
|
const FConstMeshBuildVertexView& Vertices;
|
|
/**
|
|
* The array of edges to create
|
|
*/
|
|
TArray<FMeshEdge>& Edges;
|
|
/**
|
|
* List of edges that start with a given vertex
|
|
*/
|
|
TMultiMap<FVector, FMeshEdge*> VertexToEdgeList;
|
|
|
|
/**
|
|
* Searches the list of edges to see if this one matches an existing and
|
|
* returns a pointer to it if it does
|
|
*
|
|
* @param Index1 the first index to check for
|
|
* @param Index2 the second index to check for
|
|
*
|
|
* @return nullptr if no edge was found, otherwise the edge that was found
|
|
*/
|
|
inline FMeshEdge* FindOppositeEdge(int32 Index1, int32 Index2)
|
|
{
|
|
FMeshEdge* Edge = nullptr;
|
|
TArray<FMeshEdge*> EdgeList;
|
|
|
|
// Search the hash for a corresponding vertex
|
|
VertexToEdgeList.MultiFind((FVector)Vertices.Position[Index2], EdgeList);
|
|
|
|
// Now search through the array for a match or not
|
|
for (int32 EdgeIndex = 0; EdgeIndex < EdgeList.Num() && Edge == nullptr; ++EdgeIndex)
|
|
{
|
|
FMeshEdge* OtherEdge = EdgeList[EdgeIndex];
|
|
// See if this edge matches the passed in edge
|
|
if (OtherEdge != nullptr && DoesEdgeMatch(Index1, Index2, OtherEdge))
|
|
{
|
|
// We have a match
|
|
Edge = OtherEdge;
|
|
}
|
|
}
|
|
return Edge;
|
|
}
|
|
|
|
/**
|
|
* Updates an existing edge if found or adds the new edge to the list
|
|
*
|
|
* @param Index1 the first index in the edge
|
|
* @param Index2 the second index in the edge
|
|
* @param Triangle the triangle that this edge was found in
|
|
*/
|
|
inline void AddEdge(int32 Index1, int32 Index2, int32 Triangle)
|
|
{
|
|
// If this edge matches another then just fill the other triangle
|
|
// otherwise add it
|
|
FMeshEdge* OtherEdge = FindOppositeEdge(Index1, Index2);
|
|
if (OtherEdge == nullptr)
|
|
{
|
|
// Add a new edge to the array
|
|
int32 EdgeIndex = Edges.AddZeroed();
|
|
Edges[EdgeIndex].Vertices[0] = Index1;
|
|
Edges[EdgeIndex].Vertices[1] = Index2;
|
|
Edges[EdgeIndex].Faces[0] = Triangle;
|
|
Edges[EdgeIndex].Faces[1] = INDEX_NONE;
|
|
// Also add this edge to the hash for faster searches
|
|
// NOTE: This relies on the array never being realloced!
|
|
VertexToEdgeList.Add((FVector)Vertices.Position[Index1], &Edges[EdgeIndex]);
|
|
}
|
|
else
|
|
{
|
|
OtherEdge->Faces[1] = Triangle;
|
|
}
|
|
}
|
|
|
|
public:
|
|
/**
|
|
* Initializes the values for the code that will build the mesh edge list
|
|
*/
|
|
FMeshEdgeBuilder(
|
|
const TArray<uint32>& InIndices,
|
|
const FConstMeshBuildVertexView& InVertices,
|
|
TArray<FMeshEdge>& OutEdges
|
|
) : Indices(InIndices), Vertices(InVertices), Edges(OutEdges)
|
|
{
|
|
// Presize the array so that there are no extra copies being done
|
|
// when adding edges to it
|
|
Edges.Empty(Indices.Num());
|
|
}
|
|
|
|
/**
|
|
* Virtual dtor
|
|
*/
|
|
virtual ~FMeshEdgeBuilder() {}
|
|
|
|
/**
|
|
* Uses a hash of indices to edge lists so that it can avoid the n^2 search
|
|
* through the full edge list
|
|
*/
|
|
void FindEdges(void)
|
|
{
|
|
// @todo Handle something other than trilists when building edges
|
|
int32 TriangleCount = Indices.Num() / 3;
|
|
int32 EdgeCount = 0;
|
|
// Work through all triangles building the edges
|
|
for (int32 Triangle = 0; Triangle < TriangleCount; Triangle++)
|
|
{
|
|
// Determine the starting index
|
|
int32 TriangleIndex = Triangle * 3;
|
|
// Get the indices for the triangle
|
|
int32 Index1 = Indices[TriangleIndex];
|
|
int32 Index2 = Indices[TriangleIndex + 1];
|
|
int32 Index3 = Indices[TriangleIndex + 2];
|
|
// Add the first to second edge
|
|
AddEdge(Index1, Index2, Triangle);
|
|
// Now add the second to third
|
|
AddEdge(Index2, Index3, Triangle);
|
|
// Add the third to first edge
|
|
AddEdge(Index3, Index1, Triangle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function determines whether a given edge matches or not for a static mesh
|
|
*
|
|
* @param Index1 The first index of the edge being checked
|
|
* @param Index2 The second index of the edge
|
|
* @param OtherEdge The edge to compare. Was found via the map
|
|
*
|
|
* @return true if the edge is a match, false otherwise
|
|
*/
|
|
inline bool DoesEdgeMatch(int32 Index1, int32 Index2, FMeshEdge* OtherEdge)
|
|
{
|
|
return Vertices.Position[OtherEdge->Vertices[1]] == Vertices.Position[Index1] &&
|
|
OtherEdge->Faces[1] == INDEX_NONE;
|
|
}
|
|
};
|
|
|
|
static void BuildDepthOnlyIndexBuffer(
|
|
TArray<uint32>& OutDepthIndices,
|
|
const FConstMeshBuildVertexView& InVertices,
|
|
const TArray<uint32>& InIndices,
|
|
const TArrayView<FStaticMeshSection>& InSections
|
|
)
|
|
{
|
|
int32 NumVertices = InVertices.Position.Num();
|
|
if (InIndices.Num() <= 0 || NumVertices <= 0)
|
|
{
|
|
OutDepthIndices.Empty();
|
|
return;
|
|
}
|
|
|
|
// Create a mapping of index -> first overlapping index to accelerate the construction of the shadow index buffer.
|
|
TArray<FIndexAndZ> VertIndexAndZ;
|
|
VertIndexAndZ.Empty(NumVertices);
|
|
for (int32 VertIndex = 0; VertIndex < NumVertices; VertIndex++)
|
|
{
|
|
VertIndexAndZ.Emplace(VertIndex, FVector(InVertices.Position[VertIndex]));
|
|
}
|
|
VertIndexAndZ.Sort(FCompareIndexAndZ());
|
|
|
|
// Setup the index map. 0xFFFFFFFF == not set.
|
|
TArray<uint32> IndexMap;
|
|
IndexMap.AddUninitialized(NumVertices);
|
|
FMemory::Memset(IndexMap.GetData(), 0xFF, NumVertices * sizeof(uint32));
|
|
|
|
// Search for duplicates, quickly!
|
|
for (int32 i = 0; i < VertIndexAndZ.Num(); i++)
|
|
{
|
|
uint32 SrcIndex = VertIndexAndZ[i].Index;
|
|
float Z = VertIndexAndZ[i].Z;
|
|
IndexMap[SrcIndex] = FMath::Min(IndexMap[SrcIndex], SrcIndex);
|
|
|
|
// Search forward since we add pairs both ways.
|
|
for (int32 j = i + 1; j < VertIndexAndZ.Num(); j++)
|
|
{
|
|
if (FMath::Abs(VertIndexAndZ[j].Z - Z) > THRESH_POINTS_ARE_SAME * 4.01f)
|
|
break; // can't be any more dups
|
|
|
|
uint32 OtherIndex = VertIndexAndZ[j].Index;
|
|
if (InVertices.Position[SrcIndex].Equals(InVertices.Position[OtherIndex], 0.0f))
|
|
{
|
|
IndexMap[SrcIndex] = FMath::Min(IndexMap[SrcIndex], OtherIndex);
|
|
IndexMap[OtherIndex] = FMath::Min(IndexMap[OtherIndex], SrcIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build the depth-only index buffer by remapping all indices to the first overlapping
|
|
// vertex in the vertex buffer.
|
|
OutDepthIndices.Empty();
|
|
for (int32 SectionIndex = 0; SectionIndex < InSections.Num(); ++SectionIndex)
|
|
{
|
|
const FStaticMeshSection& Section = InSections[SectionIndex];
|
|
int32 FirstIndex = Section.FirstIndex;
|
|
int32 LastIndex = FirstIndex + Section.NumTriangles * 3;
|
|
for (int32 SrcIndex = FirstIndex; SrcIndex < LastIndex; ++SrcIndex)
|
|
{
|
|
uint32 VertIndex = InIndices[SrcIndex];
|
|
OutDepthIndices.Add(IndexMap[VertIndex]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void CacheOptimizeVertexAndIndexBuffer(
|
|
FMeshBuildVertexData& Vertices,
|
|
TArray<TArray<uint32> >& PerSectionIndices,
|
|
TArray<int32>& WedgeMap
|
|
)
|
|
{
|
|
if (Vertices.Position.Num() <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Copy the vertices since we will be reordering them
|
|
FMeshBuildVertexData OriginalVertices;
|
|
OriginalVertices.Position = Vertices.Position;
|
|
OriginalVertices.TangentX = Vertices.TangentX;
|
|
OriginalVertices.TangentY = Vertices.TangentY;
|
|
OriginalVertices.TangentZ = Vertices.TangentZ;
|
|
OriginalVertices.Color = Vertices.Color;
|
|
OriginalVertices.UVs = Vertices.UVs;
|
|
|
|
// Initialize a cache that stores which indices have been assigned
|
|
TArray<int32> IndexCache;
|
|
IndexCache.AddUninitialized(Vertices.Position.Num());
|
|
FMemory::Memset(IndexCache.GetData(), INDEX_NONE, IndexCache.Num() * IndexCache.GetTypeSize());
|
|
int32 NextAvailableIndex = 0;
|
|
|
|
// Iterate through the section index buffers,
|
|
// Optimizing index order for the post transform cache (minimizes the number of vertices transformed),
|
|
// And vertex order for the pre transform cache (minimizes the amount of vertex data fetched by the GPU).
|
|
for (int32 SectionIndex = 0; SectionIndex < PerSectionIndices.Num(); SectionIndex++)
|
|
{
|
|
TArray<uint32>& Indices = PerSectionIndices[SectionIndex];
|
|
|
|
if (Indices.Num())
|
|
{
|
|
// Optimize the index buffer for the post transform cache with.
|
|
BuildOptimizationThirdParty::CacheOptimizeIndexBuffer(Indices);
|
|
|
|
// Copy the index buffer since we will be reordering it
|
|
TArray<uint32> OriginalIndices = Indices;
|
|
|
|
// Go through the indices and assign them new values that are coherent where possible
|
|
for (int32 Index = 0; Index < Indices.Num(); Index++)
|
|
{
|
|
const int32 CachedIndex = IndexCache[OriginalIndices[Index]];
|
|
|
|
if (CachedIndex == INDEX_NONE)
|
|
{
|
|
// No new index has been allocated for this existing index, assign a new one
|
|
Indices[Index] = NextAvailableIndex;
|
|
// Mark what this index has been assigned to
|
|
IndexCache[OriginalIndices[Index]] = NextAvailableIndex;
|
|
NextAvailableIndex++;
|
|
}
|
|
else
|
|
{
|
|
// Reuse an existing index assignment
|
|
Indices[Index] = CachedIndex;
|
|
}
|
|
|
|
// Reorder the vertices based on the new index assignment
|
|
Vertices.Position[Indices[Index]] = OriginalVertices.Position[OriginalIndices[Index]];
|
|
Vertices.TangentX[Indices[Index]] = OriginalVertices.TangentX[OriginalIndices[Index]];
|
|
Vertices.TangentY[Indices[Index]] = OriginalVertices.TangentY[OriginalIndices[Index]];
|
|
Vertices.TangentZ[Indices[Index]] = OriginalVertices.TangentZ[OriginalIndices[Index]];
|
|
if (Vertices.Color.Num() > 0)
|
|
{
|
|
Vertices.Color[Indices[Index]] = OriginalVertices.Color[OriginalIndices[Index]];
|
|
}
|
|
for (int32 TexCoord = 0; TexCoord < Vertices.UVs.Num(); ++TexCoord)
|
|
{
|
|
Vertices.UVs[TexCoord][Indices[Index]] = OriginalVertices.UVs[TexCoord][OriginalIndices[Index]];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int32 WedgeIndex = 0; WedgeIndex < WedgeMap.Num(); ++WedgeIndex)
|
|
{
|
|
const int32 MappedIndex = WedgeMap[WedgeIndex];
|
|
if (MappedIndex != INDEX_NONE)
|
|
{
|
|
WedgeMap[WedgeIndex] = IndexCache[MappedIndex];
|
|
}
|
|
}
|
|
}
|
|
}; |