You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Tools/RC/ResourceCompilerPC/RenderMeshBuilder.cpp

502 lines
16 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 "ResourceCompilerPC_precompiled.h"
#include "RenderMeshBuilder.h"
#include "StlUtils.h"
#include "NvTriStrip/NvTriStrip.h"
// constructs everything for the render mesh out of the given mesh
void CRenderMeshBuilder::build (const CryChunkedFile::MeshDesc* pMeshDesc)
{
clear();
m_pMeshDesc = pMeshDesc;
// build the tangent bases
MeshProxy Proxy;
Proxy.init(pMeshDesc);
m_TangBaseBuilder.CalculateTangentSpace (Proxy);
buildExtToIntMaps();
// create the m_arrMtlFaces: the faces are sorted and perhaps
// degraded faces are deleted
buildMtlFaces();
// create the indices and the array m_arrMaterials
buildIndexBuffer();
// optimize the final vertex buffer spacial locality
remapIndicesForVBCache();
//selfValidate();
}
// increases all indices of materials by the given offset
void CRenderMeshBuilder::addMaterialOffset (unsigned nOffset)
{
for (MaterialGroupArray::iterator it = m_arrPrimGroups.begin(); it != m_arrPrimGroups.end(); ++it)
{
it->nMaterial += nOffset;
}
}
// cleans up the object
void CRenderMeshBuilder::clear()
{
m_arrIndices.clear();
m_arrPrimGroups.clear();
m_arrExtTangMap.clear();
m_arrExtUVMap.clear();
m_arrExtTangents.clear();
m_arrMtlFaces.clear();
m_arrExtFaces.clear();
m_mapVUVP.clear();
m_arrExtToTBBMap.clear();
m_pMeshDesc = NULL;
}
// returns the number of vertices in the resulting vertex buffer
unsigned CRenderMeshBuilder::numVertices() const
{
// the number of external tangent bases determine this, because the
// tangent base calculation algorithm completely splits all the necessary
// vertices, so that the vertex buffer can be formed
return m_arrExtTangents.size();
}
// prepares the m_arrExtTangents, m_arrExtToTBBMap, m_arrExtTangMap and m_arrExtUVMap
void CRenderMeshBuilder::prepareExtToIntMapping ()
{
unsigned numTBBTangents = m_TangBaseBuilder.GetBaseCount();
unsigned numTBBTangents_Reserve = numTBBTangents * 9 / 7;
// prepare ext->TBB and the table of actual tangents
m_arrExtToTBBMap.reserve (numTBBTangents_Reserve);
//m_arrExtToTBBMap.resize (numTBBTangents);
m_arrExtTangents.reserve (numTBBTangents_Reserve);
//m_arrExtTangents.resize (numTBBTangents);
//for (i = 0; i < numTBBTangents; ++i)
//{
// m_arrExtToTBBMap[i] = i;
// TangData& rBase = m_arrExtTangents[i];
// m_TangBaseBuilder.GetBase(i, &rBase.tangent.x, &rBase.binormal.x, &rBase.tnormal.x);
//}
// create the indexation map new->old
m_arrExtTangMap.reserve(numTBBTangents_Reserve);
//m_arrExtTangMap.resize (numTBBTangents,-1);
if (m_pMeshDesc->numTexFaces())
{
m_arrExtUVMap.reserve(numTBBTangents_Reserve);
//m_arrExtUVMap.resize (numTBBTangents, -1);
}
m_arrExtFaces.reserve (m_pMeshDesc->numFaces());
}
// adds an entry to all required maps - m_arrExtTangents, m_arrExtToTBBMap, m_arrExtTangMap and m_arrExtUVMap
bool CRenderMeshBuilder::addExtToIntMapEntry (DWORD FaceExt[3], const CryFace& FaceInt, const CryTexFace& TexFaceInt)
{
unsigned numTBBTangents = m_TangBaseBuilder.GetBaseCount();
assert (m_arrExtTangMap.size() == m_arrExtUVMap.size() || m_arrExtUVMap.empty());
assert (m_arrExtTangMap.size() == m_arrExtTangents.size());
CryFace NewExtFace = FaceInt;
if (FaceExt[0] == FaceExt[1] || FaceExt[1] == FaceExt[2] || FaceExt[2] == FaceExt[0])
{
return false;
}
for (unsigned i = 0; i < 3; ++i)
{
VertexUVPair VUVPair((ushort)FaceInt[i], (ushort)TexFaceInt[i], (ushort)FaceExt[i]);
VertexUVPairMap::iterator itVUVPair = m_mapVUVP.find (VUVPair);
unsigned nExtEntry;
if (itVUVPair == m_mapVUVP.end())
{
// no such pair, add a new one.
nExtEntry = m_arrExtTangents.size();
TangData rBase;
m_TangBaseBuilder.GetBase (FaceExt[i], &rBase.tangent.x, &rBase.binormal.x, &rBase.tnormal.x);
AdjustBase(rBase);
m_arrExtTangents.push_back (rBase);
m_arrExtTangMap.push_back(FaceInt[i]);
m_arrExtToTBBMap.push_back((ushort)FaceExt[i]);
if (m_pMeshDesc->numTexFaces())
{
m_arrExtUVMap.push_back(TexFaceInt[i]);
}
m_mapVUVP.insert (VertexUVPairMap::value_type(VUVPair, nExtEntry));
}
else
{
// there's already such a pair, use it
nExtEntry = itVUVPair->second;
}
NewExtFace[i] = nExtEntry;
}
assert(!NewExtFace.isDegenerate());
m_arrExtFaces.push_back(NewExtFace);
return true;
}
// creates the mapping from the external to internal indices
void CRenderMeshBuilder::buildExtToIntMaps()
{
unsigned i, numTBBTangents = m_TangBaseBuilder.GetBaseCount();
prepareExtToIntMapping();
unsigned numDegenerate = 0;
CryTexFace TexFaceInt (0, 0, 0);
DWORD FaceExt[3];
for (i = 0; i < m_pMeshDesc->numFaces(); ++i)
{
// internal indexation face
const CryFace& FaceInt = m_pMeshDesc->pFaces[i];
// external indexation face
m_TangBaseBuilder.GetTriangleBaseIndices(i, FaceExt);
if (m_pMeshDesc->numTexFaces())
{
TexFaceInt = m_pMeshDesc->pTexFaces[i];
}
if (!addExtToIntMapEntry (FaceExt, FaceInt, TexFaceInt))
{
++numDegenerate;
continue; // degenerate face
}
#ifdef _DEBUG
CryFace& NewExtFace = m_arrExtFaces.back();
for (int j = 0; j < 3; ++j)
{
assert (m_arrExtUVMap[NewExtFace[j]] == TexFaceInt[j]);
assert (m_arrExtTangMap[NewExtFace[j]] == FaceInt[j]);
}
#endif
}
if (numDegenerate)
{
LogWarning("%u degenerate faces (skipped)", numDegenerate);
}
}
// calculate the number of elements == nEl
// create the indices and the array m_arrMaterials
// Create the m_arrMtlFaces
// degraded faces are deleted
void CRenderMeshBuilder::buildMtlFaces()
{
unsigned nFace, numExtFaces = m_arrExtFaces.size();
// pass 1: calculate each material's number of faces, and the number of materials
// SKIPPED NOW
const unsigned nMaxMatID = 0x400;
// pass 2: create the face groups and reserve space for the faces
m_arrMtlFaces.reserve (nMaxMatID / 4);
unsigned numSkippedFaces = 0;
// pass 3: create the faces in the face groups
for (nFace = 0; nFace < numExtFaces; ++nFace)
{
// external indexation face
const CryFace& rExtFace = m_arrExtFaces[nFace];
int nMatID = rExtFace.MatID;
// material id of the face to count
if (nMatID < 0 || nMatID > nMaxMatID)
{
++numSkippedFaces;
continue;
}
assert (!rExtFace.isDegenerate());
if (m_arrMtlFaces.size() <= (unsigned)nMatID)
{
m_arrMtlFaces.resize (nMatID + 1);
}
m_arrMtlFaces[nMatID].push_back (Face(rExtFace));
}
if (numSkippedFaces)
{
LogWarning ("%d faces skipped: no material or material id is out of range", numSkippedFaces);
}
}
//////////////////////////////////////////////////////////////////////////
// create the indices and the array m_arrMaterials out of m_arrMtlFaces
void CRenderMeshBuilder::buildIndexBuffer()
{
m_arrIndices.reserve (m_pMeshDesc->numFaces() * 3);
SetListsOnly (true);
for (unsigned nMaterial = 0; nMaterial < m_arrMtlFaces.size(); ++nMaterial)
{
const FaceArray& arrFaces = m_arrMtlFaces[nMaterial];
if (arrFaces.empty())
{
continue;
}
PrimitiveGroup* pGroup = NULL;
unsigned short numGroups = 0;
GenerateStrips((unsigned short*)&arrFaces[0], arrFaces.size() * 3, &pGroup, &numGroups);
for (unsigned nGroup = 0; nGroup < numGroups; ++nGroup)
{
appendNvidiaStrip (pGroup[nGroup], nMaterial);
}
delete[numGroups] pGroup;
}
}
//////////////////////////////////////////////////////////////////////////
// remaps (transposes, permutates) the indices to improve spatial locality of the vertex buffer
void CRenderMeshBuilder::remapIndicesForVBCache()
{
// this is the old->new indexation
std::vector<unsigned> arrVCache;
arrVCache.resize (m_arrExtTangents.size(), -1);
unsigned nNextVertex = 0;
for (unsigned i = 0; i < m_arrIndices.size(); ++i)
{
ushort nVertex = m_arrIndices[i];
if (arrVCache[nVertex] == -1)
{
// we've met this vertex for the first time
arrVCache[nVertex] = nNextVertex++;
}
}
remapExtIndices(&arrVCache[0], nNextVertex);
}
// permutate the contents of the array with a permutation old->new
template <class T>
void Permutate (std::vector<T>& arrOld, unsigned* pPermutation, unsigned newSize)
{
assert (newSize <= arrOld.size());
std::vector<T> arrNew;
arrNew.resize (newSize);
for (unsigned nEntry = 0; nEntry < arrOld.size(); ++nEntry)
{
if (pPermutation[nEntry] < arrNew.size())
{
arrNew[pPermutation[nEntry]] = arrOld[nEntry];
}
else
{
assert (pPermutation[nEntry] == -1);
}
}
arrOld.swap(arrNew);
}
// remaps external indices according to the given permutation old->new
void CRenderMeshBuilder::remapExtIndices (unsigned* pPermutation, unsigned numNewVertices)
{
unsigned numVertices = this->numVertices();
// remap the indices
for (unsigned nIndex = 0; nIndex < m_arrIndices.size(); ++nIndex)
{
assert (m_arrIndices[nIndex] < numVertices);
m_arrIndices[nIndex] = pPermutation[m_arrIndices[nIndex]];
assert (m_arrIndices[nIndex] < numNewVertices);
}
for (unsigned nFace = 0; nFace < m_arrExtFaces.size(); ++nFace)
{
m_arrExtFaces[nFace].v0 = pPermutation[m_arrExtFaces[nFace].v0];
m_arrExtFaces[nFace].v1 = pPermutation[m_arrExtFaces[nFace].v1];
m_arrExtFaces[nFace].v2 = pPermutation[m_arrExtFaces[nFace].v2];
}
// remap the ExtToInt mappings
assert (m_arrExtTangMap.size() == numVertices);
Permutate(m_arrExtTangMap, pPermutation, numNewVertices);
if (m_pMeshDesc->numTexFaces())
{
assert (m_arrExtUVMap.size() == numVertices);
Permutate(m_arrExtUVMap, pPermutation, numNewVertices);
}
assert (m_arrExtTangents.size() == numVertices);
// remap the tangent bases
Permutate(m_arrExtTangents, pPermutation, numNewVertices);
assert (m_arrExtTangents.size() == numNewVertices);
}
//////////////////////////////////////////////////////////////////////////
// add the primitive group(s) and indices (m_arrPrimGroups and m_arrIndices)
// from the given primitives generated by Nvidia Stripifier
void CRenderMeshBuilder::appendNvidiaStrip (const struct PrimitiveGroup& rGroup, unsigned nMaterial)
{
int j;
// in case we'll add this material group, collect info in it
MaterialGroup MatGroup;
MatGroup.nMaterial = nMaterial;
MatGroup.nIndexBase = m_arrIndices.size();
MatGroup.numIndices = 0;
for (int nIndex = 0; nIndex < (int)rGroup.numIndices - 2; )
{
int v[3];
unsigned short* src = rGroup.indices + nIndex;
switch (rGroup.type)
{
case PT_LIST:
v[0] = src[0];
v[1] = src[1];
v[2] = src[2];
nIndex += 3;
break;
case PT_STRIP:
if (nIndex & 1)
{
v[0] = src[1];
v[1] = src[0];
v[2] = src[2];
}
else
{
v[0] = src[0];
v[1] = src[1];
v[2] = src[2];
}
nIndex += 1;
break;
case PT_FAN:
v[0] = rGroup.indices[0];
v[1] = src[1];
v[2] = src[2];
break;
}
if (v[0] == v[1] || v[1] == v[2] || v[2] == v[0])
{
continue;
}
MatGroup.numIndices += 3;
for (j = 0; j < 3; ++j)
{
m_arrIndices.push_back(v[j]);
}
}
if (MatGroup.numIndices)
{
// there were some triangles - add a new group or append those triangles to the previous group
if (!m_arrPrimGroups.empty() && m_arrPrimGroups.back().nMaterial == nMaterial)
{
m_arrPrimGroups.back().numIndices += MatGroup.numIndices;
}
else
{
m_arrPrimGroups.push_back(MatGroup);
}
}
}
void CRenderMeshBuilder::selfValidate()
{
assert (m_arrExtFaces.size() <= m_pMeshDesc->numFaces());
unsigned numFaces = m_arrExtFaces.size();
for (unsigned nFace = 0; nFace < numFaces; ++nFace)
{
CryFace ExtFace = m_arrExtFaces[nFace];
CryFace IntFace = m_pMeshDesc->pFaces[nFace];
CryTexFace TexFace = m_pMeshDesc->pTexFaces[nFace];
for (int i = 0; i < 3; ++i)
{
// this is only applicable to a normal manifold mesh
assert (m_arrExtUVMap[ExtFace[i]] == TexFace[i]);
assert (m_arrExtTangMap[ExtFace[i]] == IntFace[i]);
}
}
}
// adjusts the base - converts from Martin's algorithm's requirements to the engine requirements
void CRenderMeshBuilder::AdjustBase(TangData& rBase)
{
/*
float fBinormal = rBase.binormal * rBase.tnormal;
float fTangent = rBase.tangent * rBase.tnormal;
assert (fabs(fBinormal) < 1e-2 && fabs(fTangent) < 1e-2);
/* // normalize the normal
float fEpsilon = 0.0005f;
float fSqrt1_2 = 0.70710678118654752440084436210485f; // square root of 1/2
float fNormalLen = rBase.tnormal.GetLength();
if (fNormalLen < fEpsilon)
rBase.tnormal /= fNormalLen;
rBase.tnormal /= fNormalLen;
// make the bisect that
Vec3 vBisect = rBase.binormal+rBase.tangent;
vBisect -= (vBisect * rBase.tnormal) * rBase.tnormal; // make it orthogonal to the normal
float fBisectLen = vBisect.GetLength();
if (fBisectLen < fEpsilon)
return;
vBisect /= fBisectLen;
Vec3 vBase = vBisect ^ rBase.tnormal;
if (rBase.binormal * vBase > rBase.tangent * vBase)
{
assert (rBase.binormal * vBase > 0 && rBase.tangent * vBase < 0);
rBase.binormal = (vBisect + vBase) * fSqrt1_2;
rBase.tangent = (vBisect - vBase) * fSqrt1_2;
}
else
{
assert (rBase.binormal * vBase < 0 && rBase.tangent * vBase > 0);
rBase.binormal = (vBisect - vBase) * fSqrt1_2;
rBase.tangent = (vBisect + vBase) * fSqrt1_2;
}
*/
//std::swap(rBase.binormal, rBase.tangent);
rBase.binormal = -rBase.binormal;
}
void CRenderMeshBuilder::MeshProxy::GetPos(const DWORD indwPos, float outfPos[3]) const
{
const Vec3 ptPos = m_pMeshDesc->pVertices[indwPos].p;
// unrotate the object
for (int i = 0; i < 3; ++i)
{
outfPos[i] = m_tm(0, i) * ptPos.x + m_tm(1, i) * ptPos.y + m_tm(2, i) * ptPos.z;
}
}