/* * 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 "StdAfx.h" #include "MeshCompiler.h" #include "TangentSpaceCalculation.h" #include "ForsythFaceReorderer.h" #include "PVRTTriStrip/PVRTTriStrip.h" #include // 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(CMesh::TEXCOORDS, streamIndex); if (texCoords && (res = memcmp(&texCoords[a], &texCoords[b], sizeof(texCoords[a])))) { return res < 0; } } if ((mesh.m_pNorms && (res = memcmp(&mesh.m_pNorms[a], &mesh.m_pNorms[b], sizeof(mesh.m_pNorms[a])))) || (mesh.m_pColor0 && (res = memcmp(&mesh.m_pColor0[a], &mesh.m_pColor0[b], sizeof(mesh.m_pColor0[a])))) || (mesh.m_pColor1 && (res = memcmp(&mesh.m_pColor1[a], &mesh.m_pColor1[b], sizeof(mesh.m_pColor1[a])))) || (mesh.m_pVertMats && (res = memcmp(&mesh.m_pVertMats[a], &mesh.m_pVertMats[b], sizeof(mesh.m_pVertMats[a])))) || (mesh.m_pTangents && (res = memcmp(&mesh.m_pTangents[a], &mesh.m_pTangents[b], sizeof(mesh.m_pTangents[a]))))) { 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(CMesh::TEXCOORDS, streamIndex); if (oldMeshTexCoords) { SMeshTexCoord* newMeshTexCoords = newMesh.GetStreamPtr(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& vertexOldToNew, std::vector& 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 void prepareUniqueIndices(std::vector& outIndices, std::vector& 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 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(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 m_posIndx; // indices of unique positions (in mesh.m_pPositions) for each corner of each triangle std::vector m_normIndx; // indices of unique normals (in mesh.m_pNorms) for each corner of each triangle std::vector m_texCoordIndx; // indices of unique texture coordinates normals (in mesh.m_pTexCoord) for each corner of each triangle std::vector 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); } } static void debugDumpMesh(CMesh& mesh, const char* filename) { FILE* f = nullptr; azfopen(&f, filename, "wb"); if (!f) { return; } fprintf(f, "[Pos]\n"); for (int i = 0; i < mesh.GetVertexCount(); ++i) { fprintf(f, "\t%g %g %g\n", mesh.m_pPositions[i].x, mesh.m_pPositions[i].y, mesh.m_pPositions[i].z); } fprintf(f, "[Nor]\n"); for (int i = 0; i < mesh.GetVertexCount(); ++i) { const Vec3 n = mesh.m_pNorms[i].GetN(); fprintf(f, "\t%g %g %g\n", n.x, n.y, n.z); } if (mesh.m_pTexCoord) { fprintf(f, "[TexCoord UV Set 0]\n"); for (int i = 0; i < mesh.GetTexCoordCount(); ++i) { const Vec2 uv = mesh.m_pTexCoord[i].GetUV(); fprintf(f, "\t%g %g\n", uv.x, uv.y); } } SMeshTexCoord* texCoords = mesh.GetStreamPtr(CMesh::TEXCOORDS, 1); if (texCoords) { fprintf(f, "[TexCoord UV Set 1]\n"); for (int i = 0; i < mesh.GetTexCoordCount(); ++i) { const Vec2 uv = texCoords[i].GetUV(); fprintf(f, "\t%g %g\n", uv.x, uv.y); } } fprintf(f, "[Faces]\n"); for (int i = 0; i < mesh.GetIndexCount(); i += 3) { fprintf(f, "\t%u %u %u\n", (uint)mesh.m_pIndices[i + 0], (uint)mesh.m_pIndices[i + 1], (uint)mesh.m_pIndices[i + 2]); } fclose(f); } ////////////////////////////////////////////////////////////////////////// // 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 bases; // m_thash_table[] contains a std::vector 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 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(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 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 buffer0; std::vector 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 buffer0; std::vector 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 vertexOldToNew; std::vector 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(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