Files
o3de/Code/CryEngine/Cry3DEngine/MeshCompiler/ForsythFaceReorderer.cpp
T
2021-03-08 14:30:57 -08:00

352 lines
10 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include <platform.h> // for assert
#include "ForsythFaceReorderer.h"
#include <cmath> // powf()
ForsythFaceReorderer::ForsythFaceReorderer()
{
computeValencyScoreTable();
}
bool ForsythFaceReorderer::reorderFaces(
const size_t cacheSize,
const uint verticesPerFace,
const size_t indexCount,
const uint32* const inVertexIndices,
uint32* const outVertexIndices,
uint32* const outFaceToOldFace)
{
clear();
if (verticesPerFace < sk_minVerticesPerFace || verticesPerFace > sk_maxVerticesPerFace)
{
return false;
}
if (indexCount <= 0)
{
return true;
}
if (indexCount % verticesPerFace != 0)
{
return false;
}
if (inVertexIndices == 0)
{
return false;
}
if (outVertexIndices == 0)
{
return false;
}
if ((cacheSize < verticesPerFace) || (cacheSize > sk_maxCacheSize))
{
return false;
}
m_cacheSize = (int)cacheSize;
m_cacheUsedSize = 0;
computeCacheScoreTable(verticesPerFace);
const size_t faceCount = indexCount / verticesPerFace;
if (indexCount / verticesPerFace >= (uint32) - 1)
{
// Face count is too high
return false;
}
size_t writtenFaceCount = 0;
uint32 vertexCount;
{
// TODO: use minVertexIndex also. It will allow to use less memory for ranged indices.
// For example indices in ranges [800;899] will use memory size 100, not 900.
uint32 maxVertexIndex = 0;
for (size_t i = 0; i < indexCount; ++i)
{
if (inVertexIndices[i] > maxVertexIndex)
{
maxVertexIndex = inVertexIndices[i];
}
}
if (((size_t)maxVertexIndex) + 1 >= (uint32) - 1)
{
// Vertex count is too high
return false;
}
vertexCount = maxVertexIndex + 1;
}
// Allocate and initialize arrays
{
{
Vertex initVertex;
initVertex.m_pFaceList = 0;
initVertex.m_aliveFaceCount = 0;
initVertex.m_posInCache = -1;
initVertex.m_score = 0;
m_vertices.resize(vertexCount, initVertex);
}
m_deadFacesBitArray.resize((faceCount + 7) / 8, 0);
m_faceScores.resize(faceCount, 0);
m_vertexFaceLists.resize(faceCount * verticesPerFace);
}
// Fill per-vertex face lists
{
for (size_t i = 0; i < indexCount; ++i)
{
const uint32 vertexIndex = inVertexIndices[i];
if (m_vertices[vertexIndex].m_aliveFaceCount >= sk_maxValency)
{
// Vertex valency is too high
return false;
}
++m_vertices[vertexIndex].m_aliveFaceCount;
}
uint32 pos = 0;
for (uint32 vi = 0; vi < vertexCount; ++vi)
{
Vertex& v = m_vertices[vi];
v.m_pFaceList = &m_vertexFaceLists[pos];
pos += v.m_aliveFaceCount;
v.m_aliveFaceCount = 0;
}
assert(pos == faceCount * verticesPerFace);
const uint32* pVertexIndex = &inVertexIndices[0];
for (uint32 fi = 0; fi < faceCount; ++fi, pVertexIndex += verticesPerFace)
{
for (uint j = 0; j < verticesPerFace; ++j)
{
Vertex& v = m_vertices[pVertexIndex[j]];
v.m_pFaceList[v.m_aliveFaceCount++] = fi;
}
}
}
// Compute vertex and face scores
{
for (uint32 vi = 0; vi < vertexCount; ++vi)
{
computeVertexScore(m_vertices[vi]);
}
const uint32* pVertexIndex = &inVertexIndices[0];
for (uint32 fi = 0; fi < faceCount; ++fi, pVertexIndex += verticesPerFace)
{
m_faceScores[fi] = 0;
for (uint j = 0; j < verticesPerFace; ++j)
{
const Vertex& v = m_vertices[pVertexIndex[j]];
m_faceScores[fi] += v.m_score;
}
}
}
// Add faces with highest scores to the output buffer, one by one.
uint32 faceSearchCursor = 0;
uint32 bestFaceToAdd;
for (;; )
{
// Find face with highest score
{
bestFaceToAdd = (uint32) - 1;
float highestScore = -1;
for (int i = 0; i < m_cacheUsedSize; ++i)
{
const Vertex& v = m_vertices[m_cache[i]];
const uint32* const pFaces = v.m_pFaceList;
for (valency_type j = 0; j < v.m_aliveFaceCount; ++j)
{
const uint32 faceIndex = pFaces[j];
if (highestScore < m_faceScores[faceIndex])
{
highestScore = m_faceScores[faceIndex];
bestFaceToAdd = faceIndex;
}
}
}
if (bestFaceToAdd == (uint32) - 1)
{
bestFaceToAdd = findBestFaceToAdd(faceSearchCursor);
assert(bestFaceToAdd != (uint32) - 1);
}
}
// Add the best face to the output buffer
{
size_t writtenIndexCount = writtenFaceCount * verticesPerFace;
for (uint j = 0; j < verticesPerFace; ++j)
{
outVertexIndices[writtenIndexCount + j] = inVertexIndices[(size_t)bestFaceToAdd * verticesPerFace + j];
}
if (outFaceToOldFace)
{
outFaceToOldFace[writtenFaceCount] = bestFaceToAdd;
}
if (++writtenFaceCount == faceCount)
{
// We're done.
return true;
}
}
// Make changes to the cache, vertex & cache scores, vertex face lists
{
m_deadFacesBitArray[bestFaceToAdd >> 3] |= 1 << (bestFaceToAdd & 7);
for (int j = verticesPerFace - 1; j >= 0; --j)
{
const uint32 vertexIndex = inVertexIndices[(size_t)bestFaceToAdd * verticesPerFace + j];
moveVertexToCacheTop(vertexIndex);
removeFaceFromVertex(vertexIndex, bestFaceToAdd);
}
for (int i = 0; i < m_cacheUsedSize; ++i)
{
Vertex& v = m_vertices[m_cache[i]];
if (i >= m_cacheSize)
{
v.m_posInCache = -1;
}
const float oldScore = v.m_score;
computeVertexScore(v);
const float differenceScore = v.m_score - oldScore;
const uint32* const pFaces = v.m_pFaceList;
for (valency_type j = 0; j < v.m_aliveFaceCount; ++j)
{
m_faceScores[pFaces[j]] += differenceScore;
}
}
if (m_cacheUsedSize > m_cacheSize)
{
m_cacheUsedSize = m_cacheSize;
}
}
}
}
void ForsythFaceReorderer::clear()
{
m_vertices.clear();
m_deadFacesBitArray.clear();
m_faceScores.clear();
m_vertexFaceLists.clear();
}
void ForsythFaceReorderer::computeCacheScoreTable(const int verticesPerFace)
{
static const float lastFaceScore = 0.75f;
static const float cacheDecayPower = 1.5f;
// Vertices of last added face should have *same* fixed score,
// because otherwise results will depend on the order of vertices
// in face (5,6,7 and 7,5,6 will produce different results).
for (int j = 0; j < verticesPerFace; ++j)
{
m_scoreTable_cachePosition[j] = lastFaceScore;
}
for (int i = verticesPerFace; i < m_cacheSize; ++i)
{
const float x = 1.0f - ((i - verticesPerFace) / (m_cacheSize - verticesPerFace));
m_scoreTable_cachePosition[i] = powf(x, cacheDecayPower);
}
}
void ForsythFaceReorderer::computeValencyScoreTable()
{
// Lower number of alive faces in the vertex produces higher score.
// It allows to get rid of lone vertices quickly.
static const float valencyPower = -0.5f;
static const float valencyScale = 2.0f;
m_scoreTable_valency[0] = 0;
for (valency_type i = 1; i < sk_valencyTableSize; ++i)
{
m_scoreTable_valency[i] = valencyScale * powf(i, valencyPower);
}
}
void ForsythFaceReorderer::computeVertexScore(ForsythFaceReorderer::Vertex& v)
{
if (v.m_aliveFaceCount > 0)
{
assert(v.m_posInCache < m_cacheSize);
const float valencyScore = (v.m_aliveFaceCount < sk_valencyTableSize) ? m_scoreTable_valency[v.m_aliveFaceCount] : 0;
// Preventing "SCA: warning C6385: Invalid data: accessing 'm_scoreTable_cachePosition', the readable size is '200' bytes, but '484' bytes might be read"
PREFAST_SUPPRESS_WARNING(6385) const float cacheScore = (v.m_posInCache >= 0) ? m_scoreTable_cachePosition[v.m_posInCache] : 0;
v.m_score = valencyScore + cacheScore;
}
}
void ForsythFaceReorderer::moveVertexToCacheTop(const uint32 vertexIndex)
{
const int oldPosInCache = m_vertices[vertexIndex].m_posInCache;
for (int dst = (oldPosInCache >= 0) ? oldPosInCache : m_cacheUsedSize; dst > 0; --dst)
{
const uint32 v = m_cache[dst - 1];
m_cache[dst] = v;
++m_vertices[v].m_posInCache;
}
m_cache[0] = vertexIndex;
m_vertices[vertexIndex].m_posInCache = 0;
if (oldPosInCache < 0)
{
++m_cacheUsedSize;
}
}
void ForsythFaceReorderer::removeFaceFromVertex(const uint32 vertexIndex, const uint32 faceIndex)
{
Vertex& v = m_vertices[vertexIndex];
assert(v.m_aliveFaceCount > 0);
uint32* const pFaces = v.m_pFaceList;
for (int j = 0;; ++j)
{
if (pFaces[j] == faceIndex)
{
pFaces[j] = pFaces[--v.m_aliveFaceCount];
return;
}
}
}
uint32 ForsythFaceReorderer::findBestFaceToAdd(uint32& faceSearchCursor) const
{
assert(!m_faceScores.empty());
assert(faceSearchCursor < m_faceScores.size());
while (m_deadFacesBitArray[faceSearchCursor >> 3] & (1 << (faceSearchCursor & 7)))
{
++faceSearchCursor;
}
return faceSearchCursor++;
}