Files
o3de/Code/CryEngine/Cry3DEngine/MeshCompiler/MeshCompiler.cpp
T

1548 lines
54 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 <Cry_Geo.h>
#include "MeshCompiler.h"
#include "TangentSpaceCalculation.h"
#include "ForsythFaceReorderer.h"
#include "PVRTTriStrip/PVRTTriStrip.h"
#include <cstring> // memset()
namespace mesh_compiler
{
//////////////////////////////////////////////////////////////////////////
CMeshCompiler::CMeshCompiler()
: m_pVertexMap(0)
, m_pIndexMap(0)
{
}
//////////////////////////////////////////////////////////////////////////
CMeshCompiler::~CMeshCompiler()
{
}
namespace
{
struct VertexLess
{
const CMesh& mesh;
VertexLess(const CMesh& a_mesh)
: mesh(a_mesh)
{
assert(mesh.m_pPositionsF16 == 0);
}
bool operator()(int a, int b) const
{
if (mesh.m_pTopologyIds && mesh.m_pTopologyIds[a] != mesh.m_pTopologyIds[b])
{
return mesh.m_pTopologyIds[a] < mesh.m_pTopologyIds[b];
}
int res = memcmp(&mesh.m_pPositions[a], &mesh.m_pPositions[b], sizeof(mesh.m_pPositions[a]));
if (res)
{
return res < 0;
}
for (uint streamIndex = 0; streamIndex < CMesh::maxStreamsPerType; ++streamIndex)
{
SMeshTexCoord* texCoords = mesh.GetStreamPtr<SMeshTexCoord>(CMesh::TEXCOORDS, streamIndex);
if (texCoords)
{
res = memcmp(&texCoords[a], &texCoords[b], sizeof(texCoords[a]));
if (res)
{
return res < 0;
}
}
}
if (mesh.m_pNorms)
{
res = memcmp(&mesh.m_pNorms[a], &mesh.m_pNorms[b], sizeof(mesh.m_pNorms[a]));
if (!res)
{
return res < 0;
}
}
if (mesh.m_pColor0)
{
res = memcmp(&mesh.m_pColor0[a], &mesh.m_pColor0[b], sizeof(mesh.m_pColor0[a]));
if (!res)
{
return res < 0;
}
}
if (mesh.m_pColor1)
{
res = memcmp(&mesh.m_pColor1[a], &mesh.m_pColor1[b], sizeof(mesh.m_pColor1[a]));
if (!res)
{
return res < 0;
}
}
if (mesh.m_pVertMats)
{
res = memcmp(&mesh.m_pVertMats[a], &mesh.m_pVertMats[b], sizeof(mesh.m_pVertMats[a]));
if (!res)
{
return res < 0;
}
}
if (mesh.m_pTangents)
{
res = memcmp(&mesh.m_pTangents[a], &mesh.m_pTangents[b], sizeof(mesh.m_pTangents[a]));
if (!res)
{
return res < 0;
}
}
return false;
}
};
// Copies a vertex from old to new mesh
inline void CopyMeshVertex(CMesh& newMesh, int newVertex, const CMesh& oldMesh, int oldVertex)
{
assert(newVertex < newMesh.GetVertexCount());
assert(newMesh.m_pPositionsF16 == 0);
assert(oldMesh.m_pPositionsF16 == 0);
newMesh.m_pPositions[newVertex] = oldMesh.m_pPositions[oldVertex];
if (oldMesh.m_pNorms)
{
newMesh.m_pNorms[newVertex] = oldMesh.m_pNorms[oldVertex];
}
if (oldMesh.m_pTopologyIds)
{
newMesh.m_pTopologyIds[newVertex] = oldMesh.m_pTopologyIds[oldVertex];
}
for (uint streamIndex = 0; streamIndex < CMesh::maxStreamsPerType; ++streamIndex)
{
SMeshTexCoord* oldMeshTexCoords = oldMesh.GetStreamPtr<SMeshTexCoord>(CMesh::TEXCOORDS, streamIndex);
if (oldMeshTexCoords)
{
SMeshTexCoord* newMeshTexCoords = newMesh.GetStreamPtr<SMeshTexCoord>(CMesh::TEXCOORDS, streamIndex);
newMeshTexCoords[newVertex] = oldMeshTexCoords[oldVertex];
}
}
if (oldMesh.m_pColor0)
{
newMesh.m_pColor0[newVertex] = oldMesh.m_pColor0[oldVertex];
}
if (oldMesh.m_pColor1)
{
newMesh.m_pColor1[newVertex] = oldMesh.m_pColor1[oldVertex];
}
if (oldMesh.m_pVertMats)
{
newMesh.m_pVertMats[newVertex] = oldMesh.m_pVertMats[oldVertex];
}
if (oldMesh.m_pTangents)
{
newMesh.m_pTangents[newVertex] = oldMesh.m_pTangents[oldVertex];
}
//New since Touch Bending Gem. A Mesh can have boneMappings.
if (oldMesh.m_pBoneMapping)
{
newMesh.m_pBoneMapping[newVertex] = oldMesh.m_pBoneMapping[oldVertex];
}
}
// Modified version of MeshUtils::Mesh::ComputeVertexRemapping()
// Computes vertexOldToNew and vertexNewToOld by detecting duplicate vertices
void ComputeVertexRemapping(const CMesh& mesh, std::vector<int>& vertexOldToNew, std::vector<int>& vertexNewToOld)
{
const size_t nVerts = mesh.GetVertexCount();
vertexNewToOld.resize(nVerts);
for (size_t i = 0; i < nVerts; ++i)
{
vertexNewToOld[i] = i;
}
VertexLess less(mesh);
std::sort(vertexNewToOld.begin(), vertexNewToOld.end(), less);
vertexOldToNew.resize(nVerts);
int nVertsNew = 0;
for (size_t i = 0; i < nVerts; ++i)
{
if (i == 0 || less(vertexNewToOld[i - 1], vertexNewToOld[i]))
{
vertexNewToOld[nVertsNew++] = vertexNewToOld[i];
}
vertexOldToNew[vertexNewToOld[i]] = nVertsNew - 1;
}
vertexNewToOld.resize(nVertsNew);
}
} // namespace
//////////////////////////////////////////////////////////////////////////
namespace
{
class CMeshInputProxy
: public ITriangleInputProxy
{
struct Index
{
int index;
int origPos;
};
template <class TComparator>
void prepareUniqueIndices(std::vector<int>& outIndices, std::vector<Index>& tmp, const TComparator& comparator)
{
const int faceCount = m_mesh.GetFaceCount();
tmp.resize(faceCount * 3);
outIndices.resize(faceCount * 3, -1);
for (int i = 0; i < faceCount; ++i)
{
for (int j = 0; j < 3; ++j)
{
tmp[i * 3 + j].index = m_mesh.m_pFaces[i].v[j];
tmp[i * 3 + j].origPos = i * 3 + j;
}
}
std::sort(tmp.begin(), tmp.end(), comparator);
int curIndex = -1;
for (int i = 0, n = faceCount * 3; i < n; ++i)
{
if (curIndex < 0 || comparator(tmp[i - 1], tmp[i]))
{
curIndex = tmp[i].index;
}
outIndices[tmp[i].origPos] = curIndex;
}
}
const char* ValidateMesh() const
{
if (m_mesh.m_pPositionsF16)
{
return "the mesh has 16-bit positions";
}
if (!m_mesh.m_pFaces)
{
return "the mesh has no stream with faces";
}
if (!m_mesh.m_pPositions)
{
return "the mesh has no stream with positions";
}
if (!m_mesh.m_pNorms)
{
return "the mesh has no stream with normals";
}
if (!m_mesh.m_pTexCoord)
{
return "the mesh has no stream with texture coordinates";
}
const int faceCount = m_mesh.GetFaceCount();
const int vertexCount = m_mesh.GetVertexCount();
const int texCoordCount = m_mesh.GetTexCoordCount();
if (faceCount <= 0)
{
return "face count in the mesh is 0";
}
if (vertexCount <= 0)
{
return "vertex count in the mesh is 0";
}
if (texCoordCount <= 0)
{
return "texture coordinate count in the mesh is 0";
}
if (vertexCount != texCoordCount)
{
return "mismatch in number of positions and texture coordinates in the mesh";
}
for (int i = 0; i < faceCount; ++i)
{
for (int j = 0; j < 3; ++j)
{
const int vIdx = m_mesh.m_pFaces[i].v[j];
if (vIdx < 0 || vIdx >= vertexCount)
{
return "a face in the mesh has vertex index that is out of range";
}
}
}
// Trying to trigger a crash if a stream size is not correct
{
Vec3 v(0.0f, 0.0f, 0.0f);
SMeshNormal n(v);
SMeshTexCoord uv(0, 0);
v = m_mesh.m_pPositions[0];
v = m_mesh.m_pPositions[vertexCount - 1];
n = m_mesh.m_pNorms[0];
n = m_mesh.m_pNorms[vertexCount - 1];
uv = m_mesh.m_pTexCoord[0];
uv = m_mesh.m_pTexCoord[vertexCount - 1];
}
return 0;
}
public:
CMeshInputProxy(const CMesh& inMesh)
: m_mesh(inMesh)
{
m_pErrorText = ValidateMesh();
if (m_pErrorText)
{
return;
}
assert(m_mesh.m_pPositionsF16 == 0);
struct PositionComparator
{
const Vec3* const pPositions;
const int* const pTopologyIds;
PositionComparator(const Vec3* const a_pPositions, const int* const a_pTopologyIds)
: pPositions(a_pPositions)
, pTopologyIds(a_pTopologyIds)
{
}
bool operator()(const Index& v0, const Index& v1) const
{
if (pTopologyIds)
{
const int a = pTopologyIds[v0.index];
const int b = pTopologyIds[v1.index];
if (a != b)
{
return a < b;
}
}
const Vec3& a = pPositions[v0.index];
const Vec3& b = pPositions[v1.index];
if (a.x != b.x)
{
return a.x < b.x;
}
if (a.y != b.y)
{
return a.y < b.y;
}
return a.z < b.z;
}
};
struct NormalComparator
{
const SMeshNormal* const pNormals;
NormalComparator(const SMeshNormal* const a_pNormals)
: pNormals(a_pNormals)
{
}
bool operator()(const Index& v0, const Index& v1) const
{
const SMeshNormal& a = pNormals[v0.index];
const SMeshNormal& b = pNormals[v1.index];
return a < b;
}
};
struct TexCoordComparator
{
const SMeshTexCoord* const pTexCoords;
TexCoordComparator(const SMeshTexCoord* const a_pTexCoords)
: pTexCoords(a_pTexCoords)
{
}
bool operator()(const Index& v0, const Index& v1) const
{
const SMeshTexCoord& a = pTexCoords[v0.index];
const SMeshTexCoord& b = pTexCoords[v1.index];
return a < b;
}
};
std::vector<Index> tmp;
prepareUniqueIndices(m_posIndx, tmp, PositionComparator(m_mesh.m_pPositions, m_mesh.m_pTopologyIds));
prepareUniqueIndices(m_normIndx, tmp, NormalComparator(m_mesh.m_pNorms));
prepareUniqueIndices(m_texCoordIndx, tmp, TexCoordComparator(m_mesh.m_pTexCoord));
SMeshTexCoord* texCoords = m_mesh.GetStreamPtr<SMeshTexCoord>(CMesh::TEXCOORDS, 1);
if (texCoords)
{
prepareUniqueIndices(m_texCoord2Indx, tmp, TexCoordComparator(texCoords));
}
}
const char* GetErrorText() const
{
return m_pErrorText;
}
// interface ITriangleInputProxy ----------------------------------------------
//! /return 0..
uint32 GetTriangleCount() const
{
return m_mesh.GetFaceCount();
}
//! /param indwTriNo 0..
//! /param outdwPos
//! /param outdwNorm
//! /param outdwUV
void GetTriangleIndices(const uint32 indwTriNo, uint32 outdwPos[3], uint32 outdwNorm[3], uint32 outdwUV[3]) const
{
const int* const pPosInds = &m_posIndx[indwTriNo * 3];
const int* const pNormInds = &m_normIndx[indwTriNo * 3];
const int* const pTexCoordInds = &m_texCoordIndx[indwTriNo * 3];
for (int j = 0; j < 3; ++j)
{
outdwPos[j] = pPosInds[j];
outdwUV[j] = pTexCoordInds[j];
outdwNorm[j] = pNormInds[j];
}
}
//! /param indwPos 0..
//! /param outfPos
void GetPos(const uint32 indwPos, Vec3& outfPos) const
{
assert(!m_pErrorText);
assert((int)indwPos < m_mesh.GetVertexCount());
outfPos = m_mesh.m_pPositions[indwPos];
}
//! /param indwPos 0..
//! /param outfUV
void GetUV(const uint32 indwPos, Vec2& outfUV) const
{
assert(!m_pErrorText);
assert((int)indwPos < m_mesh.GetTexCoordCount());
outfUV = m_mesh.m_pTexCoord[indwPos].GetUV();
}
//! /param indwTriNo 0..
//! /param indwVertNo 0..
//! /param outfNorm
void GetNorm(const uint32 indwTriNo, const uint32 indwVertNo, Vec3& outfNorm) const
{
assert(!m_pErrorText);
assert((int)indwTriNo < m_mesh.GetFaceCount());
assert((int)indwVertNo < 3);
const int vIdx = m_mesh.m_pFaces[indwTriNo].v[indwVertNo];
assert(vIdx < m_mesh.GetVertexCount());
outfNorm = m_mesh.m_pNorms[vIdx].GetN();
}
//-----------------------------------------------------------------------------
private:
const CMesh& m_mesh;
const char* m_pErrorText;
std::vector<int> m_posIndx; // indices of unique positions (in mesh.m_pPositions) for each corner of each triangle
std::vector<int> m_normIndx; // indices of unique normals (in mesh.m_pNorms) for each corner of each triangle
std::vector<int> m_texCoordIndx; // indices of unique texture coordinates normals (in mesh.m_pTexCoord) for each corner of each triangle
std::vector<int> m_texCoord2Indx; // indices of unique texture coordinates normals (in mesh.m_pTexCoord[1]) for each corner of each triangle for the 2nd uv set
};
}
//do not use vec3 lib to keep it the fallback as it was
inline static Vec3 CrossProd(const Vec3& a, const Vec3& b)
{
Vec3 ret;
ret.x = a.y * b.z - a.z * b.y;
ret.y = a.z * b.x - a.x * b.z;
ret.z = a.x * b.y - a.y * b.x;
return ret;
}
inline static void GetOtherBaseVec(const Vec3& s, Vec3& a, Vec3& b)
{
if (fabsf(s.z) > 0.5f)
{
a.x = s.z;
a.y = s.y;
a.z = -s.x;
}
else
{
a.x = s.y;
a.y = -s.x;
a.z = s.z;
}
b = CrossProd(s, a).normalize();
a = CrossProd(b, s).normalize();
}
//check packed tangent space and ensure some useful values, fix always according to normal
static void VerifyTangentSpace(SMeshTangents& rTangents, const SMeshNormal& rNormal)
{
Vec3 normal = rNormal.GetN();
if (normal.GetLengthSquared() < 0.1f)
{
normal = Vec3(0, 0, 1);
}
else if (normal.GetLengthSquared() < 0.9f)
{
normal.Normalize();
}
//unpack first(necessary since the quantization can introduce errors whereas the original float data were different)
Vec3 tangent, bitangent;
rTangents.GetTB(tangent, bitangent);
//check if they are equal
const bool cIsEqual = (tangent == bitangent);
//check if they are zero
const bool cTangentIsZero = (tangent.GetLengthSquared() < 0.01f);
const bool cBitangentIsZero = (bitangent.GetLengthSquared() < 0.01f);
const bool cbHasBeenChanged = (cIsEqual || cTangentIsZero || cBitangentIsZero);
if (cIsEqual)
{
//fix case where both vec's are equal
GetOtherBaseVec(normal, tangent, bitangent);
}
else
if (cTangentIsZero)
{
//fix case where tangent is zero
bitangent.Normalize();//just to make sure
if (abs(bitangent * normal) > 0.9f)//if angle between both vecs is to low, calc new one for both
{
GetOtherBaseVec(normal, tangent, bitangent);
}
else
{
tangent = CrossProd(normal, bitangent);
}
}
else
if (cBitangentIsZero)
{
//fix case where bitangent is zero
tangent.Normalize();//just to make sure
if (abs(tangent * normal) > 0.9f)//if angle between both vecs is to low, calc new one for both
{
GetOtherBaseVec(normal, tangent, bitangent);
}
else
{
bitangent = CrossProd(tangent, normal);
}
}
//pack altered tangent vecs
if (cbHasBeenChanged)
{
rTangents = SMeshTangents(tangent, bitangent, normal);
}
}
//////////////////////////////////////////////////////////////////////////
// Optimizes CMesh.
// IMPLEMENTATION:
// . Sort|Group faces by materials
// . Create vertex buffer with sequence of (possibly non-unique) vertices, 3 verts per face
// . For each (non-unique) vertex calculate the tangent base
// . Index the mesh (Compact Vertices): detect and delete duplicate vertices
// . Remove degenerated triangles in the generated mesh (GetIndices())
// . Sort vertices and indices for GPU cache
bool CMeshCompiler::Compile(CMesh& mesh, int flags)
{
assert(mesh.m_pPositionsF16 == 0);
if (mesh.GetFaceCount() == 0)
{
// the mesh is either empty or already compiled
const int cVertexCount = mesh.GetVertexCount();
if (cVertexCount == 0)
{
// the mesh is empty, nothing to do
return true;
}
// the mesh is already compiled, likely to have a refresh here: just verify and correct tangent space
if (mesh.m_pTangents && mesh.m_pNorms)
{
for (int i = 0; i < cVertexCount; ++i)
{
VerifyTangentSpace(mesh.m_pTangents[i], mesh.m_pNorms[i]);
}
}
// Confetti begin: David Srour
// A CGF is already compiled as soon as a mesh is imported in the editor.
// Thus, the following code path branch will only get hit if:
// - RC job is being done outside the editor (eg. when compiling mobile resources via XML file)
// - Job input is an already compiled CGF file
// - "Refresh" was specific to force recompile
// - "OptimizedPrimitiveType = 1" to specify PowerVR stripify algorithm
if (flags & MESH_COMPILE_PVR_STRIPIFY)
{
const bool bOk = StripifyMesh_PVRTriStripList(mesh);
if (!bOk)
{
m_LastError.Format("Mesh compilation failed - stripifier failed. Contact an RC programmer.");
return false;
}
FindVertexRanges(mesh);
}
return true;
}
// the mesh has faces - it means that it's a non-compiled mesh. let's compile it.
// Check input data
{
if (mesh.GetIndexCount() > 0)
{
m_LastError.Format(
"Mesh compilation failed - input mesh has both indices and faces. Contact an RC programmer.");
return false;
}
const int vertexCount = mesh.GetVertexCount();
const int faceCount = mesh.GetFaceCount();
const int subSetCount = mesh.GetSubSetCount();
if (subSetCount >= MAX_SUB_MATERIALS)
{
m_LastError.Format(
"Mesh compilation failed - Number of subsets (%d) exceeds the maximum amount of sub-materials (%d).",
subSetCount, MAX_SUB_MATERIALS);
return false;
}
for (int i = 0; i < faceCount; ++i)
{
const SMeshFace& face = mesh.m_pFaces[i];
if (face.nSubset < 0 || face.nSubset >= subSetCount)
{
m_LastError.Format(
"Mesh compilation failed - face %d has bad subset index %d (allowed range is [0;%d]). Contact an RC programmer.",
i, (int)face.nSubset, subSetCount - 1);
return false;
}
for (int j = 0; j < 3; ++j)
{
const int vIdx = mesh.m_pFaces[i].v[j];
if (vIdx < 0 || vIdx >= vertexCount)
{
m_LastError.Format(
"Mesh compilation failed - face %d has bad vertex index %d (allowed range is [0;%d]). Contact an RC programmer.",
i, vIdx, vertexCount - 1);
return false;
}
}
}
}
//////////////////////////////////////////////////////////////////////////
// Calculate Tangent Space.
// Results will be stored in bases[] and m_thash_table[]
//////////////////////////////////////////////////////////////////////////
std::vector<SMeshTangents> bases;
// m_thash_table[] contains a std::vector<SBasisFace> per subset.
// Vector contains faces belonging to the subset.
// Face contains three indices of elements in bases[].
COMPILE_TIME_ASSERT(sizeof(m_thash_table) / sizeof(m_thash_table[0]) == MAX_SUB_MATERIALS);
for (int i = 0; i < MAX_SUB_MATERIALS; ++i)
{
m_thash_table[i].clear();
}
if (flags & MESH_COMPILE_TANGENTS)
{
// Generate tangent basis vectors before indexing per-material
CMeshInputProxy Input(mesh);
if (Input.GetErrorText())
{
m_LastError.Format("Mesh compilation failed - %s. Contact an RC or Editor programmer.", Input.GetErrorText());
return false;
}
CTangentSpaceCalculation tangents;
string errorMessage;
// calculate the base matrices
const bool bUseCustomNormals = (flags & MESH_COMPILE_USECUSTOMNORMALS) ? true : false;
const eCalculateTangentSpaceErrorCode nErrorCode = tangents.CalculateTangentSpace(Input, bUseCustomNormals, errorMessage);
if (nErrorCode != CALCULATE_TANGENT_SPACE_NO_ERRORS)
{
const char* errorCodeMessage;
switch (nErrorCode)
{
case VERTICES_SHARING_COORDINATES:
errorCodeMessage = "Asset contains non-manifold geometry.\nPlease fix the model in your DCC tool to solve this issue.\n";
break;
case ALL_VERTICES_ON_THE_SAME_VECTOR:
errorCodeMessage = "Asset contains non-manifold geometry.\nPlease fix the model in your DCC tool to solve this issue.\n";
break;
case BROKEN_TEXTURE_COORDINATES:
errorCodeMessage = "Texture UV coordinates are not valid.\nCheck that the UV's have space on the UV map in your DCC tool to solve this issue.\n";
break;
case MEMORY_ALLOCATION_FAILED:
errorCodeMessage = "Mesh compiler failed to allocate memory for compilation.\nYou can reduce the size of your mesh to attempt to solve this issue.\n";
break;
default:
AZ_Assert(false, "Unknown error code. Please implement a failure message.");
errorCodeMessage = "Unknown error code encountered.\nThis happens when a programmer has not implemented a message for an error code.\n";
break;
}
m_LastError.Format("\n%s%sCalculateTangentSpace() failed - error code: %d", errorCodeMessage, errorMessage.c_str(), nErrorCode);
return false;
}
const uint32 dwCnt = tangents.GetBaseCount();
const uint32 dwTris = Input.GetTriangleCount();
bases.resize(dwCnt);
std::vector<int> basisIndices;
basisIndices.resize(dwTris * 3);
for (uint32 dwTri = 0; dwTri < dwTris; dwTri++)
{
uint32 dwBaseIndx[3];
tangents.GetTriangleBaseIndices(dwTri, dwBaseIndx);
// for every corner of the triangle
for (uint32 i = 0; i < 3; i++)
{
assert(dwBaseIndx[i] < dwCnt);
basisIndices[dwTri * 3 + i] = dwBaseIndx[i]; // set the base vector
}
}
for (uint32 i = 0; i < dwCnt; i++)
{
Vec3 Tangent, Bitangent, Normal;
tangents.GetBase(i, (float*)&Tangent, (float*)&Bitangent, (float*)&Normal);
bases[i] = SMeshTangents(Tangent, Bitangent, Normal);
VerifyTangentSpace(bases[i], SMeshNormal(Normal));
}
const int faceCount = mesh.GetFaceCount();
for (int i = 0; i < faceCount; i++)
{
SBasisFace fc;
fc.v[0] = basisIndices[i * 3 + 0];
fc.v[1] = basisIndices[i * 3 + 1];
fc.v[2] = basisIndices[i * 3 + 2];
const SMeshFace& face = mesh.m_pFaces[i];
m_thash_table[face.nSubset].push_back(fc);
}
}
//////////////////////////////////////////////////////////////////////////
// Create new mesh that will store non-unique vertices, 3 vertices per face
const int max_vert_num = mesh.GetFaceCount() * 3;
CMesh outMesh;
outMesh.Copy(mesh);
outMesh.SetVertexCount(max_vert_num);
outMesh.ReallocStream(CMesh::VERT_MATS, 0, max_vert_num);
if (mesh.m_pTopologyIds)
{
outMesh.ReallocStream(CMesh::TOPOLOGY_IDS, 0, max_vert_num);
}
if (mesh.m_pTexCoord)
{
outMesh.ReallocStream(CMesh::TEXCOORDS, 0, max_vert_num);
}
if (mesh.GetStreamPtr<SMeshTexCoord>(CMesh::TEXCOORDS, 1))
{
outMesh.ReallocStream(CMesh::TEXCOORDS, 1, max_vert_num);
}
if (flags & MESH_COMPILE_TANGENTS)
{
outMesh.ReallocStream(CMesh::TANGENTS, 0, max_vert_num);
}
if (mesh.m_pColor0)
{
outMesh.ReallocStream(CMesh::COLORS, 0, max_vert_num);
}
if (mesh.m_pColor1)
{
outMesh.ReallocStream(CMesh::COLORS, 1, max_vert_num);
}
//New Since Touch Bending Gem. A Touch Bendable Mesh has bone mappings.
if (mesh.m_pBoneMapping)
{
outMesh.ReallocStream(CMesh::BONEMAPPING, 0, max_vert_num);
}
// temporarily store original subset index in subset's nNumVerts
{
const uint32 nSubsets = outMesh.GetSubSetCount();
for (uint32 i = 0; i < nSubsets; i++)
{
outMesh.m_subsets[i].nNumVerts = i;
}
}
// Sort subsets depending on their physicalization type (don't do it for character meshes (with mapping)).
if (!m_pVertexMap)
{
// move normal physicalize subsets to the beginning (needed for breakable objects)
for (uint32 i = 0; i < (uint32)outMesh.m_subsets.size(); i++)
{
const SMeshSubset& outSubset = outMesh.m_subsets[i];
if (outSubset.nPhysicalizeType == PHYS_GEOM_TYPE_DEFAULT)
{
const SMeshSubset tmp = outSubset;
outMesh.m_subsets.erase(outMesh.m_subsets.begin() + i);
outMesh.m_subsets.insert(outMesh.m_subsets.begin(), tmp);
}
}
// move physicalize proxy subsets to the end
for (int nSubset = (int)outMesh.m_subsets.size() - 1; nSubset >= 0; --nSubset)
{
const SMeshSubset& outSubset = outMesh.m_subsets[nSubset];
if (outSubset.nPhysicalizeType != PHYS_GEOM_TYPE_NONE && outSubset.nPhysicalizeType != PHYS_GEOM_TYPE_DEFAULT)
{
const SMeshSubset tmp = outSubset;
outMesh.m_subsets.erase(outMesh.m_subsets.begin() + nSubset);
outMesh.m_subsets.push_back(tmp);
}
}
}
// m_vhash_table[] contains a std::vector<SMeshFace> per subset.
// Vector contains faces belonging to the subset.
// Face contains three indices of elements in mesh.m_pVertices[].
COMPILE_TIME_ASSERT(sizeof(m_vhash_table) / sizeof(m_vhash_table[0]) == MAX_SUB_MATERIALS);
for (int i = 0; i < MAX_SUB_MATERIALS; ++i)
{
m_vhash_table[i].clear();
}
for (int i = 0, n = mesh.GetFaceCount(); i < n; ++i)
{
const SMeshFace& face = mesh.m_pFaces[i];
m_vhash_table[face.nSubset].push_back(&face);
}
// Fill the new mesh with vertices
{
int buff_vert_count = 0;
for (int t = 0; t < outMesh.GetSubSetCount(); t++)
{
SMeshSubset& subset = outMesh.m_subsets[t];
// memorize the starting index of this material's face range
subset.nFirstIndexId = buff_vert_count;
// scan through all the faces using the shader #t.
// note: subset's nNumVerts contains original subset index
const size_t nNumFacesInSubset = m_vhash_table[subset.nNumVerts].size();
for (size_t i = 0; i < nNumFacesInSubset; ++i)
{
const SMeshFace* const pFace = m_vhash_table[subset.nNumVerts][i];
for (int v = 0; v < 3; ++v)
{
CopyMeshVertex(outMesh, buff_vert_count, mesh, pFace->v[v]);
if (!bases.empty())
{
const SBasisFace& tFace = m_thash_table[subset.nNumVerts][i];
outMesh.m_pTangents[buff_vert_count] = bases[tFace.v[v]];
}
// store subset id to prevent vertex sharing between materials during re-compacting
outMesh.m_pVertMats[buff_vert_count] = pFace->nSubset;
++buff_vert_count;
}
}
subset.nNumIndices = buff_vert_count - subset.nFirstIndexId;
}
if (buff_vert_count != max_vert_num)
{
m_LastError.Format("Mesh compilation failed - internal error inf handling vertices. Contact an RC programmer.");
return false;
}
}
if (!CreateIndicesAndDeleteDuplicateVertices(outMesh))
{
return false;
}
if (flags & MESH_COMPILE_VALIDATE_FAIL_ON_DEGENERATE_FACES)
{
if(CheckForDegenerateFaces(outMesh))
{
m_LastError.Format("Mesh contains degenerate faces.");
return false;
}
}
if (flags & MESH_COMPILE_OPTIMIZE)
{
const bool bOk = StripifyMesh_Forsyth(outMesh);
if (!bOk)
{
m_LastError.Format("Mesh compilation failed - stripifier failed. Contact an RC programmer.");
return false;
}
}
else
{
if (m_pIndexMap || m_pVertexMap)
{
m_LastError.Format("Mesh compilation failed - face and/or index maps cannot be requested without OPTIMIZE. Contact an RC programmer.");
return false;
}
}
FindVertexRanges(outMesh);
// Copy modified mesh back to original one.
mesh.Copy(outMesh);
// Calculate bounding box.
mesh.m_bbox.Reset();
for (int i = 0, n = mesh.GetVertexCount(); i < n; ++i)
{
mesh.m_bbox.Add(mesh.m_pPositions[i]);
}
if (flags & MESH_COMPILE_VALIDATE)
{
const char* pErrorDescription = 0;
if (!mesh.Validate(&pErrorDescription))
{
m_LastError.Format("Internal error in mesh compiling (%s). Contact an RC programmer.", pErrorDescription);
return false;
}
}
return true;
}
bool CMeshCompiler::StripifyMesh_Forsyth(CMesh& mesh)
{
if (mesh.GetFaceCount() > 0)
{
// We don't support stripifying of meshes with explicit faces, we support meshes with index array only
return false;
}
enum
{
kCACHESIZE_GEFORCE3 = 24
};
const size_t cacheSize = kCACHESIZE_GEFORCE3;
enum
{
kVerticesPerFace = 3
};
// Prepare mapping buffers
if (m_pIndexMap)
{
const int n = mesh.GetIndexCount();
m_pIndexMap->resize(n);
for (int i = 0; i < n; ++i)
{
(*m_pIndexMap)[i] = mesh.m_pIndices[i];
}
}
if (m_pVertexMap)
{
const int n = mesh.GetVertexCount();
m_pVertexMap->resize(n, -1);
}
CMesh newMesh;
newMesh.Copy(mesh);
// TODO: make those variables members of CMeshCompiler so we don't need to allocate memory every time
ForsythFaceReorderer ffr;
std::vector<uint32> buffer0;
std::vector<uint32> buffer1;
// Reserve space
//
// We will use buffer0 for both subset's indices and for mapping from old vertex indices
// to new vertex indices (number of vertices decreases in case some vertices are not
// referenced from indices). In the latter case having size of buffer0 equal to number of
// indices is not enough if indices refer vertices in a spare fashion. Unfortunately,
// to compute range of referenced vertices we need to scan all indices in all subsets
// which is not fast.
{
int maxIndexCountInSubset = 0;
int maxVertexCountInSubset = 0;
for (int i = 0; i < newMesh.GetSubSetCount(); i++)
{
const SMeshSubset& subset = mesh.m_subsets[i];
if (subset.nNumIndices == 0)
{
continue;
}
if (subset.nNumIndices < 0)
{
assert(0);
return false;
}
if (subset.nNumIndices % kVerticesPerFace != 0)
{
assert(0);
return false;
}
if (subset.nFirstIndexId % kVerticesPerFace != 0)
{
assert(0);
return false;
}
if (maxIndexCountInSubset < subset.nNumIndices)
{
maxIndexCountInSubset = subset.nNumIndices;
}
int subsetMinIndex = mesh.m_pIndices[subset.nFirstIndexId];
int subsetMaxIndex = subsetMinIndex;
for (int j = 1; j < subset.nNumIndices; ++j)
{
const int idx = mesh.m_pIndices[subset.nFirstIndexId + j];
if (idx < subsetMinIndex)
{
subsetMinIndex = idx;
}
else if (idx > subsetMaxIndex)
{
subsetMaxIndex = idx;
}
}
if (maxVertexCountInSubset < subsetMaxIndex - subsetMinIndex + 1)
{
maxVertexCountInSubset = subsetMaxIndex - subsetMinIndex + 1;
}
}
buffer0.resize(max(maxIndexCountInSubset, maxVertexCountInSubset));
buffer1.resize(maxIndexCountInSubset);
}
int newVertexCount = 0;
for (int i = 0; i < newMesh.GetSubSetCount(); i++)
{
const SMeshSubset& subset = mesh.m_subsets[i];
if (subset.nNumIndices == 0)
{
continue;
}
int subsetMinIndex = mesh.m_pIndices[subset.nFirstIndexId];
int subsetMaxIndex = subsetMinIndex;
for (int j = 1; j < subset.nNumIndices; ++j)
{
const int idx = mesh.m_pIndices[subset.nFirstIndexId + j];
if (idx < subsetMinIndex)
{
subsetMinIndex = idx;
}
else if (idx > subsetMaxIndex)
{
subsetMaxIndex = idx;
}
}
for (int j = 0; j < subset.nNumIndices; ++j)
{
buffer0[j] = mesh.m_pIndices[subset.nFirstIndexId + j] - subsetMinIndex;
}
const bool bOk = ffr.reorderFaces(
cacheSize,
kVerticesPerFace,
subset.nNumIndices,
&buffer0[0], // inVertexIndices
&buffer1[0], // outVertexIndices
0); // faceToOldFace[] - we don't need it
if (!bOk)
{
return false;
}
// Reorder vertices
SMeshSubset& newSubset = newMesh.m_subsets[i];
newSubset.nFirstVertId = newVertexCount;
newSubset.nNumVerts = 0;
newSubset.nFirstIndexId = subset.nFirstIndexId;
newSubset.nNumIndices = subset.nNumIndices;
const int oldSubsetVertexCount = (int)subsetMaxIndex - (int)subsetMinIndex + 1;
assert(buffer0.size() >= (size_t)oldSubsetVertexCount);
memset(&buffer0[0], -1, sizeof(buffer0[0]) * oldSubsetVertexCount);
for (int j = 0; j < subset.nNumIndices; ++j)
{
const uint32 idx = buffer1[j];
const int oldVertexIndex = subsetMinIndex + idx;
if (buffer0[idx] == -1)
{
if (m_pVertexMap)
{
(*m_pVertexMap)[oldVertexIndex] = newVertexCount;
}
buffer0[idx] = newVertexCount;
//copy from old -> new vertex buffer
CopyMeshVertex(newMesh, newVertexCount, mesh, oldVertexIndex);
++newVertexCount;
++newSubset.nNumVerts;
}
newMesh.m_pIndices[subset.nFirstIndexId + j] = buffer0[idx];
}
}
newMesh.SetVertexCount(newVertexCount);
mesh.Copy(newMesh);
return true;
}
// Confetti Begin: Nicholas Baldwin
bool CMeshCompiler::StripifyMesh_PVRTriStripList(CMesh& mesh)
{
if (mesh.GetFaceCount() > 0)
{
// We don't support stripifying of meshes with explicit faces, we support meshes with index array only
return false;
}
enum
{
kCACHESIZE_GEFORCE3 = 24
};
const size_t cacheSize = kCACHESIZE_GEFORCE3;
enum
{
kVerticesPerFace = 3
};
// Prepare mapping buffers
if (m_pIndexMap)
{
const int n = mesh.GetIndexCount();
m_pIndexMap->resize(n);
for (int i = 0; i < n; ++i)
{
(*m_pIndexMap)[i] = mesh.m_pIndices[i];
}
}
if (m_pVertexMap)
{
const int n = mesh.GetVertexCount();
m_pVertexMap->resize(n, -1);
}
CMesh newMesh;
newMesh.Copy(mesh);
// TODO: make those variables members of CMeshCompiler so we don't need to allocate memory every time
std::vector<uint32> buffer0;
std::vector<uint32> buffer1;
// Reserve space
//
// We will use buffer0 for both subset's indices and for mapping from old vertex indices
// to new vertex indices (number of vertices decreases in case some vertices are not
// referenced from indices). In the latter case having size of buffer0 equal to number of
// indices is not enough if indices refer vertices in a spare fashion. Unfortunately,
// to compute range of referenced vertices we need to scan all indices in all subsets
// which is not fast.
{
int maxIndexCountInSubset = 0;
int maxVertexCountInSubset = 0;
for (int i = 0; i < newMesh.GetSubSetCount(); i++)
{
const SMeshSubset& subset = mesh.m_subsets[i];
if (subset.nNumIndices == 0)
{
continue;
}
if (subset.nNumIndices < 0)
{
assert(0);
return false;
}
if (subset.nNumIndices % kVerticesPerFace != 0)
{
assert(0);
return false;
}
if (subset.nFirstIndexId % kVerticesPerFace != 0)
{
assert(0);
return false;
}
if (maxIndexCountInSubset < subset.nNumIndices)
{
maxIndexCountInSubset = subset.nNumIndices;
}
int subsetMinIndex = mesh.m_pIndices[subset.nFirstIndexId];
int subsetMaxIndex = subsetMinIndex;
for (int j = 1; j < subset.nNumIndices; ++j)
{
const int idx = mesh.m_pIndices[subset.nFirstIndexId + j];
if (idx < subsetMinIndex)
{
subsetMinIndex = idx;
}
else if (idx > subsetMaxIndex)
{
subsetMaxIndex = idx;
}
}
if (maxVertexCountInSubset < subsetMaxIndex - subsetMinIndex + 1)
{
maxVertexCountInSubset = subsetMaxIndex - subsetMinIndex + 1;
}
}
buffer0.resize(max(maxIndexCountInSubset, maxVertexCountInSubset));
}
int newVertexCount = 0;
for (int i = 0; i < newMesh.GetSubSetCount(); i++)
{
const SMeshSubset& subset = mesh.m_subsets[i];
if (subset.nNumIndices == 0)
{
continue;
}
int subsetMinIndex = mesh.m_pIndices[subset.nFirstIndexId];
int subsetMaxIndex = subsetMinIndex;
for (int j = 1; j < subset.nNumIndices; ++j)
{
const int idx = mesh.m_pIndices[subset.nFirstIndexId + j];
if (idx < subsetMinIndex)
{
subsetMinIndex = idx;
}
else if (idx > subsetMaxIndex)
{
subsetMaxIndex = idx;
}
}
buffer1.clear();
buffer1.resize(subset.nNumIndices);
for (int j = 0; j < subset.nNumIndices; ++j)
{
buffer0[j] = mesh.m_pIndices[subset.nFirstIndexId + j] - subsetMinIndex;
buffer1[j] = mesh.m_pIndices[subset.nFirstIndexId + j] - subsetMinIndex;
}
PVRTTriStripList(&buffer1[0], buffer1.size() / kVerticesPerFace);
// Reorder vertices
SMeshSubset& newSubset = newMesh.m_subsets[i];
newSubset.nFirstVertId = newVertexCount;
newSubset.nNumVerts = 0;
newSubset.nFirstIndexId = subset.nFirstIndexId;
newSubset.nNumIndices = subset.nNumIndices;
const int oldSubsetVertexCount = (int)subsetMaxIndex - (int)subsetMinIndex + 1;
assert(buffer0.size() >= (size_t)oldSubsetVertexCount);
memset(&buffer0[0], -1, sizeof(buffer0[0]) * oldSubsetVertexCount);
for (int j = 0; j < subset.nNumIndices; ++j)
{
const uint32 idx = buffer1[j];
const int oldVertexIndex = subsetMinIndex + idx;
if (buffer0[idx] == -1)
{
if (m_pVertexMap)
{
(*m_pVertexMap)[oldVertexIndex] = newVertexCount;
}
buffer0[idx] = newVertexCount;
//copy from old -> new vertex buffer
CopyMeshVertex(newMesh, newVertexCount, mesh, oldVertexIndex);
++newVertexCount;
++newSubset.nNumVerts;
}
newMesh.m_pIndices[subset.nFirstIndexId + j] = buffer0[idx];
}
}
newMesh.SetVertexCount(newVertexCount);
mesh.Copy(newMesh);
return true;
}
//////////////////////////////////////////////////////////////////////////
//
// Input:
// mesh contains mesh.GetVertexCount() vertices (vertex data are stored in
// m_pPositions[] m_pNorms[] and in other data streams).
// Face and index streams are ignored.
// Output:
// 1) mesh contains unique vertices only.
// 2) data stream mesh.m_pIndices has "inputMesh.GetVertexCount()"
// indices (one output index per each input vertex).
// note that an output index points to an *unique* vertex in the
// output mesh.
// 3) data stream mesh.m_pFaces is empty.
//
// For example vertices [A, B, B, C, A, D] will be transformed to
// [A, B, C, D], and index array created will be [0, 1, 1, 2, 0, 3].
//
// Note that mesh.subsets[] is neither used nor changed.
//
bool CMeshCompiler::CreateIndicesAndDeleteDuplicateVertices(CMesh& mesh)
{
assert(mesh.m_pPositionsF16 == 0);
const int oldVertexCount = mesh.GetVertexCount();
if (oldVertexCount <= 0)
{
return true;
}
CMesh oldMesh;
oldMesh.Copy(mesh);
std::vector<int> vertexOldToNew;
std::vector<int> vertexNewToOld;
ComputeVertexRemapping(oldMesh, vertexOldToNew, vertexNewToOld);
const int newVertexCount = (int)vertexNewToOld.size();
assert(vertexOldToNew.size() == oldVertexCount);
const uint maxVertexCount = (sizeof(vtx_idx) == 2 ? 0xffff : 0x7fffffff);
if (newVertexCount > maxVertexCount)
{
m_LastError.Format("Too many vertices in mesh after compilation: %u (limit is %u).", (uint)newVertexCount, (uint)maxVertexCount);
return false;
}
for (int i = 0; i < newVertexCount; ++i)
{
CopyMeshVertex(mesh, i, oldMesh, vertexNewToOld[i]);
}
mesh.SetVertexCount(newVertexCount);
if (mesh.m_pNorms)
{
mesh.ReallocStream(CMesh::NORMALS, 0, newVertexCount);
}
if (mesh.m_pTexCoord)
{
mesh.ReallocStream(CMesh::TEXCOORDS, 0, newVertexCount);
}
if (mesh.GetStreamPtr<SMeshTexCoord>(CMesh::TEXCOORDS, 1))
{
mesh.ReallocStream(CMesh::TEXCOORDS, 1, newVertexCount);
}
if (mesh.m_pColor0)
{
mesh.ReallocStream(CMesh::COLORS, 0, newVertexCount);
}
if (mesh.m_pColor1)
{
mesh.ReallocStream(CMesh::COLORS, 1, newVertexCount);
}
if (mesh.m_pTangents)
{
mesh.ReallocStream(CMesh::TANGENTS, 0, newVertexCount);
}
//New since Touch Bending Gem. a Tocuh Bendable Mesh has bone mappings.
if (mesh.m_pBoneMapping)
{
mesh.ReallocStream(CMesh::BONEMAPPING, 0, newVertexCount);
}
mesh.ReallocStream(CMesh::TOPOLOGY_IDS, 0, 0);
mesh.ReallocStream(CMesh::VERT_MATS, 0, 0);
mesh.SetFaceCount(0);
mesh.SetIndexCount(oldVertexCount);
for (int i = 0; i < oldVertexCount; ++i)
{
mesh.m_pIndices[i] = vertexOldToNew[i];
}
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CMeshCompiler::CheckForDegenerateFaces(const CMesh& mesh)
{
for (int i = 0; i < mesh.GetSubSetCount(); i++)
{
const SMeshSubset& subset = mesh.m_subsets[i];
for (int j = subset.nFirstIndexId; j < subset.nFirstIndexId + subset.nNumIndices; j += 3)
{
if (mesh.m_pIndices[j + 0] == mesh.m_pIndices[j + 1] ||
mesh.m_pIndices[j + 1] == mesh.m_pIndices[j + 2] ||
mesh.m_pIndices[j + 2] == mesh.m_pIndices[j + 0])
{
return true;
}
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
void CMeshCompiler::FindVertexRanges(CMesh& mesh)
{
assert(mesh.m_pPositionsF16 == 0);
const int nNumIndices = mesh.GetIndexCount();
// Find vertex range (both index and spacial ranges) for each material (needed for rendering)
for (int i = 0; i < mesh.GetSubSetCount(); i++)
{
SMeshSubset& subset = mesh.m_subsets[i];
if (subset.nNumIndices == 0)
{
subset.nNumVerts = 0;
continue;
}
if (subset.nNumIndices + subset.nFirstIndexId > nNumIndices)
{
assert(0);
continue;
}
int nMin = INT_MAX;
int nMax = INT_MIN;
Vec3 vMin = SetMaxBB();
Vec3 vMax = SetMinBB();
for (int j = subset.nFirstIndexId; j < subset.nNumIndices + subset.nFirstIndexId; j++)
{
int index = mesh.m_pIndices[j];
Vec3 v = mesh.m_pPositions[index];
vMin.CheckMin(v);
vMax.CheckMax(v);
nMin = min(nMin, index);
nMax = max(nMax, index);
}
subset.vCenter = (vMin + vMax) * 0.5f;
subset.fRadius = (vMin - subset.vCenter).GetLength();
subset.nFirstVertId = nMin;
subset.nNumVerts = nMax - nMin + 1;
}
}
//////////////////////////////////////////////////////////////////////////
bool CMeshCompiler::CompareMeshes(const CMesh& mesh1, const CMesh& mesh2)
{
if (mesh1.m_subsets.size() != mesh2.m_subsets.size())
{
return false;
}
if (mesh1.GetFaceCount() != mesh2.GetFaceCount())
{
return false;
}
if (mesh1.GetVertexCount() != mesh2.GetVertexCount())
{
return false;
}
if (mesh1.GetTexCoordCount() != mesh2.GetTexCoordCount())
{
return false;
}
if (mesh1.GetIndexCount() != mesh2.GetIndexCount())
{
return false;
}
if (!mesh1.CompareStreams(mesh2))
{
return false;
}
return true;
}
} // namespace mesh_compiler