/*
* 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.
#ifndef CRYINCLUDE_CRYCOMMONTOOLS_EXPORT_MESHUTILS_H
#define CRYINCLUDE_CRYCOMMONTOOLS_EXPORT_MESHUTILS_H
#pragma once
#include "BaseTypes.h" // uint8
#include "Cry_Vector3.h" // Vec3
#include "IIndexedMesh.h" // CMesh
namespace MeshUtils
{
struct Face
{
int vertexIndex[3];
};
struct Color
{
uint8 r;
uint8 g;
uint8 b;
};
// Stores linking of a vertex to bone(s)
class VertexLinks
{
public:
struct Link
{
int boneId;
float weight;
Vec3 offset;
Link()
: boneId(-1)
, weight(-1.0f)
, offset(0.0f, 0.0f, 0.0f)
{
}
};
enum ESort
{
eSort_ByWeight,
eSort_ByBoneId,
};
public:
std::vector links;
public:
// minWeightToDelete: links with weights <= minWeightToDelete will be deleted
const char* Normalize(ESort eSort, const float minWeightToDelete, const int maxLinkCount)
{
if (minWeightToDelete < 0 || minWeightToDelete >= 1)
{
return "Bad minWeightToDelete passed";
}
if (maxLinkCount <= 0)
{
return "Bad maxLinkCount passed";
}
// Merging links with matching bone ids
{
DeleteByWeight(0.0f);
if (links.empty())
{
return "All bone links of a vertex have zero weight";
}
std::sort(links.begin(), links.end(), CompareLinksByBoneId);
size_t dst = 0;
for (size_t i = 1; i < links.size(); ++i)
{
if (links[i].boneId == links[dst].boneId)
{
const float w0 = links[dst].weight;
const float w1 = links[i].weight;
const float a = w0 / (w0 + w1);
links[dst].offset = links[dst].offset * a + links[i].offset * (1 - a);
links[dst].weight = w0 + w1;
}
else
{
links[++dst] = links[i];
}
}
links.resize(dst + 1);
}
// Deleting links, normalizing link weights.
//
// Note: we produce meaningful results even in cases like this:
// input weights are { 0.03, 0.01 }, minWeightTodelete is 0.2.
// Output weights produced are { 0.75, 0.25 }.
{
std::sort(links.begin(), links.end(), CompareLinksByWeight);
if (links.size() > maxLinkCount)
{
links.resize(maxLinkCount);
}
NormalizeWeights();
const size_t oldSize = links.size();
DeleteByWeight(minWeightToDelete);
if (links.empty())
{
return "All bone links of a vertex are deleted (minWeightToDelete is too big)";
}
if (links.size() != oldSize)
{
NormalizeWeights();
}
}
switch (eSort)
{
case eSort_ByWeight:
// Do nothing because we already sorted links by weight (see above)
break;
case eSort_ByBoneId:
std::sort(links.begin(), links.end(), CompareLinksByBoneId);
break;
default:
assert(0);
break;
}
return 0;
}
private:
void DeleteByWeight(float minWeightToDelete)
{
for (size_t i = 0; i < links.size(); ++i)
{
if (links[i].weight <= minWeightToDelete)
{
if (i < links.size() - 1)
{
links[i] = links[links.size() - 1];
}
links.resize(links.size() - 1);
--i;
}
}
}
void NormalizeWeights()
{
assert(!links.empty() && links[0].weight > 0);
float w = 0;
for (size_t i = 0; i < links.size(); ++i)
{
w += links[i].weight;
}
w = 1 / w;
for (size_t i = 0; i < links.size(); ++i)
{
links[i].weight *= w;
}
}
static bool CompareLinksByBoneId(const Link& left, const Link& right)
{
if (left.boneId != right.boneId)
{
return left.boneId < right.boneId;
}
if (left.weight != right.weight)
{
return left.weight < right.weight;
}
return memcmp(&left.offset, &right.offset, sizeof(left.offset)) < 0;
}
static bool CompareLinksByWeight(const Link& left, const Link& right)
{
if (left.weight != right.weight)
{
return left.weight > right.weight;
}
if (left.boneId != right.boneId)
{
return left.boneId < right.boneId;
}
return memcmp(&left.offset, &right.offset, sizeof(left.offset)) < 0;
}
};
class Mesh
{
public:
// Vertex data
std::vector m_positions;
std::vector m_topologyIds;
std::vector m_normals;
std::vector> m_texCoords;
std::vector m_colors;
std::vector m_alphas;
std::vector m_links;
std::vector m_vertexMatIds;
size_t m_auxSizeof;
std::vector m_aux;
// Face data
std::vector m_faces;
std::vector m_faceMatIds;
// Mappings computed and filled by ComputeVertexRemapping()
std::vector m_vertexOldToNew;
std::vector m_vertexNewToOld;
public:
Mesh()
: m_auxSizeof(0)
{
}
int GetVertexCount() const
{
return m_positions.size();
}
int GetFaceCount() const
{
return m_faces.size();
}
//////////////////////////////////////////////////////////////////////////
// Setters
void Clear()
{
m_positions.clear();
m_topologyIds.clear();
m_normals.clear();
m_texCoords.clear();
m_colors.clear();
m_alphas.clear();
m_links.clear();
m_vertexMatIds.clear();
m_aux.clear();
m_faces.clear();
m_faceMatIds.clear();
m_vertexOldToNew.clear();
m_vertexNewToOld.clear();
}
const char* SetPositions(const float* pVec3, int count, int stride, const float scale)
{
if (count <= 0)
{
return "bad position count";
}
if (stride < 0 || (stride > 0 && stride < sizeof(Vec3)))
{
return "bad position stride";
}
m_positions.resize(count);
for (int i = 0; i < count; ++i)
{
const float* const p = (const float*)(((const char*)pVec3) + ((size_t)i * stride));
if (!_finite(p[0]) || !_finite(p[1]) || !_finite(p[2]))
{
m_positions.clear();
return "Illegal (NAN) vertex position. Fix the 3d Model.";
}
m_positions[i].x = p[0] * scale;
m_positions[i].y = p[1] * scale;
m_positions[i].z = p[2] * scale;
}
return 0;
}
const char* SetTopologyIds(const int* pTopo, int count, int stride)
{
if (count <= 0)
{
return "bad topologyId count";
}
if (stride < 0 || (stride > 0 && stride < sizeof(int)))
{
return "bad topologyId stride";
}
m_topologyIds.resize(count);
for (int i = 0; i < count; ++i)
{
const int* const p = (const int*)(((const char*)pTopo) + ((size_t)i * stride));
m_topologyIds[i] = p[0];
}
return 0;
}
const char* SetNormals(const float* pVec3, int count, int stride)
{
if (count <= 0)
{
return "bad normal count";
}
if (stride < 0 || (stride > 0 && stride < sizeof(Vec3)))
{
return "bad normal stride";
}
m_normals.resize(count);
for (int i = 0; i < count; ++i)
{
const float* const p = (const float*)(((const char*)pVec3) + ((size_t)i * stride));
if (!_finite(p[0]) || !_finite(p[1]) || !_finite(p[2]))
{
m_normals.clear();
return "Illegal (NAN) vertex normal. Fix the 3d Model.";
}
m_normals[i].x = p[0];
m_normals[i].y = p[1];
m_normals[i].z = p[2];
m_normals[i] = m_normals[i].GetNormalizedSafe(Vec3_OneZ);
}
return 0;
}
const char* SetTexCoords(const float* pVec2, int count, int stride, bool bFlipT, uint streamIndex)
{
if (count <= 0)
{
return "bad texCoord count";
}
if (stride < 0 || (stride > 0 && stride < sizeof(float) * 2))
{
return "bad texCoord stride";
}
if (m_texCoords.size() <= streamIndex)
{
m_texCoords.resize(streamIndex + 1);
}
m_texCoords[streamIndex].resize(count);
for (int i = 0; i < count; ++i)
{
const float* const p = (const float*)(((const char*)pVec2) + ((size_t)i * stride));
if (!_finite(p[0]) || !_finite(p[1]))
{
m_texCoords[streamIndex].clear();
return "Illegal (NAN) texture coordinate. Fix the 3d Model.";
}
m_texCoords[streamIndex][i].x = p[0];
m_texCoords[streamIndex][i].y = bFlipT ? 1 - p[1] : p[1];
}
return 0;
}
const char* SetColors(const uint8* pRgb, int count, int stride)
{
if (count <= 0)
{
return "bad color count";
}
if (stride < 0 || (stride > 0 && stride < 3))
{
return "bad color stride";
}
m_colors.resize(count);
for (int i = 0; i < count; ++i)
{
const uint8* const p = (((const uint8*)pRgb) + ((size_t)i * stride));
m_colors[i].r = p[0];
m_colors[i].g = p[1];
m_colors[i].b = p[2];
}
return 0;
}
const char* SetAlphas(const uint8* pAlpha, int count, int stride)
{
if (count <= 0)
{
return "bad alpha count";
}
if (stride < 0)
{
return "bad alpha stride";
}
m_alphas.resize(count);
for (int i = 0; i < count; ++i)
{
const uint8* const p = (((const uint8*)pAlpha) + ((size_t)i * stride));
m_alphas[i] = p[0];
}
return 0;
}
const char* SetFaces(const int* pVertIdx3, int count, int stride)
{
if (count <= 0)
{
return "bad face count";
}
if (stride < 0 || (stride > 0 && stride < 3 * sizeof(int)))
{
return "bad face stride";
}
m_faces.resize(count);
for (int i = 0; i < count; ++i)
{
const int* const p = (const int*)(((const char*)pVertIdx3) + ((size_t)i * stride));
for (int j = 0; j < 3; ++j)
{
if (p[j] < 0 || p[j] >= m_positions.size())
{
return "bad vertex index found in a face";
}
m_faces[i].vertexIndex[j] = p[j];
}
}
return 0;
}
const char* SetFaceMatIds(const int* pMatIds, int count, int stride, int maxMaterialId)
{
if (count <= 0)
{
return "bad face materialId count";
}
if (stride < 0 || (stride > 0 && stride < sizeof(int)))
{
return "bad face materialIdstride";
}
m_faceMatIds.resize(count);
for (int i = 0; i < count; ++i)
{
const int* const p = (const int*)(((const char*)pMatIds) + ((size_t)i * stride));
if (p[0] < 0)
{
return "negative material ID found in a face";
}
if (p[0] >= maxMaterialId)
{
return "material ID found in a face is outside of allowed ranges";
}
m_faceMatIds[i] = p[0];
}
return 0;
}
const char* SetAux(size_t auxSizeof, const void* pData, int count, int stride)
{
if (auxSizeof <= 0)
{
return "bad aux sizeof";
}
if (count <= 0)
{
return "bad aux count";
}
if (stride < 0 || (stride > 0 && stride < auxSizeof))
{
return "bad aux stride";
}
m_auxSizeof = auxSizeof;
m_aux.resize(count * m_auxSizeof);
for (int i = 0; i < count; ++i)
{
const uint8* const p = (((const uint8*)pData) + ((size_t)i * stride));
memcpy(&m_aux[i * m_auxSizeof], p, m_auxSizeof);
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
// Validation
// Returns 0 if ok, or pointer to the error text
const char* Validate() const
{
const int nVerts = (int)m_positions.size();
if (nVerts <= 0)
{
return "No vertices";
}
const int nFaces = (int)m_faces.size();
if (nFaces <= 0)
{
return "No faces";
}
if (!m_topologyIds.empty() && nVerts != (int)m_topologyIds.size())
{
return "Mismatch in the number of topology IDs";
}
if (!m_normals.empty() && nVerts != (int)m_normals.size())
{
return "Mismatch in the number of normals";
}
for (uint streamIndex = 0; streamIndex < m_texCoords.size(); ++streamIndex)
{
if (!m_texCoords[streamIndex].empty() && nVerts != (int)m_texCoords[streamIndex].size())
{
return "Mismatch in the number of texture coordinates";
}
}
if (!m_colors.empty() && nVerts != (int)m_colors.size())
{
return "Mismatch in the number of colors";
}
if (!m_alphas.empty() && nVerts != (int)m_alphas.size())
{
return "Mismatch in the number of alphas";
}
if (!m_links.empty() && nVerts != (int)m_links.size())
{
return "Mismatch in the number of vertex-bone links";
}
for (size_t i = 0; i < m_links.size(); ++i)
{
if (m_links[i].links.empty())
{
return "Found a vertex without bone linking";
}
}
if (!m_vertexMatIds.empty() && nVerts != (int)m_vertexMatIds.size())
{
return "Mismatch in the number of vertex materials";
}
if (!m_aux.empty() && nVerts != (int)(m_aux.size() / m_auxSizeof))
{
return "Mismatch in the number of auxiliary elements";
}
if (!m_faceMatIds.empty() && nFaces != (int)m_faceMatIds.size())
{
return "Mismatch in the number of face materials";
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
// Computation
void RemoveDegenerateFaces()
{
int writePos = 0;
for (int readPos = 0; readPos < (int)m_faces.size(); ++readPos)
{
const Face& face = m_faces[readPos];
if (face.vertexIndex[0] != face.vertexIndex[1] &&
face.vertexIndex[1] != face.vertexIndex[2] &&
face.vertexIndex[0] != face.vertexIndex[2])
{
m_faces[writePos] = m_faces[readPos];
if (!m_faceMatIds.empty())
{
m_faceMatIds[writePos] = m_faceMatIds[readPos];
}
++writePos;
}
}
m_faces.resize(writePos);
if (!m_faceMatIds.empty())
{
m_faceMatIds.resize(writePos);
}
}
int AddVertexCopy(int sourceVertexIndex)
{
if (sourceVertexIndex < 0 || sourceVertexIndex >= m_positions.size())
{
assert(0);
return -1;
}
m_positions.push_back(m_positions[sourceVertexIndex]);
if (!m_topologyIds.empty())
{
m_topologyIds.push_back(m_topologyIds[sourceVertexIndex]);
}
if (!m_normals.empty())
{
m_normals.push_back(m_normals[sourceVertexIndex]);
}
for (uint streamIndex = 0; streamIndex < m_texCoords.size(); ++streamIndex)
{
if (!m_texCoords[streamIndex].empty())
{
m_texCoords[streamIndex].push_back(m_texCoords[streamIndex][sourceVertexIndex]);
}
}
if (!m_colors.empty())
{
m_colors.push_back(m_colors[sourceVertexIndex]);
}
if (!m_alphas.empty())
{
m_alphas.push_back(m_alphas[sourceVertexIndex]);
}
if (!m_links.empty())
{
m_links.push_back(m_links[sourceVertexIndex]);
}
if (!m_vertexMatIds.empty())
{
m_vertexMatIds.push_back(m_vertexMatIds[sourceVertexIndex]);
}
if (!m_aux.empty())
{
m_aux.resize(m_aux.size() + m_auxSizeof);
memcpy(&m_aux[m_aux.size() - m_auxSizeof], &m_aux[sourceVertexIndex * m_auxSizeof], m_auxSizeof);
}
return (int)m_positions.size() - 1;
}
// Note: might create new vertices and modify vertex indices in faces
void SetVertexMaterialIdsFromFaceMaterialIds()
{
m_vertexMatIds.clear();
if (m_faceMatIds.empty())
{
return;
}
m_vertexMatIds.resize(m_positions.size(), -1);
for (size_t i = 0; i < m_faces.size(); ++i)
{
const int faceMatId = m_faceMatIds[i];
for (int j = 0; j < 3; ++j)
{
int v = m_faces[i].vertexIndex[j];
if (m_vertexMatIds[v] >= 0 && m_vertexMatIds[v] != faceMatId)
{
v = AddVertexCopy(v);
m_faces[i].vertexIndex[j] = v;
}
m_vertexMatIds[v] = faceMatId;
}
}
}
// Computes m_vertexOldToNew and m_vertexNewToOld by detecting duplicate vertices
void ComputeVertexRemapping()
{
const size_t nVerts = m_positions.size();
m_vertexNewToOld.resize(nVerts);
for (size_t i = 0; i < nVerts; ++i)
{
m_vertexNewToOld[i] = i;
}
VertexLess less(*this);
std::sort(m_vertexNewToOld.begin(), m_vertexNewToOld.end(), less);
m_vertexOldToNew.resize(nVerts);
int nVertsNew = 0;
for (size_t i = 0; i < nVerts; ++i)
{
if (i == 0 || less(m_vertexNewToOld[i - 1], m_vertexNewToOld[i]))
{
m_vertexNewToOld[nVertsNew++] = m_vertexNewToOld[i];
}
m_vertexOldToNew[m_vertexNewToOld[i]] = nVertsNew - 1;
}
m_vertexNewToOld.resize(nVertsNew);
}
// Changes order of vertices, number of vertices, vertex indices in faces
void RemoveVerticesByUsingComputedRemapping()
{
CompactVertices(m_positions, m_vertexNewToOld);
CompactVertices(m_topologyIds, m_vertexNewToOld);
CompactVertices(m_normals, m_vertexNewToOld);
for (uint streamIndex = 0; streamIndex < m_texCoords.size(); ++streamIndex)
{
CompactVertices(m_texCoords[streamIndex], m_vertexNewToOld);
}
CompactVertices(m_colors, m_vertexNewToOld);
CompactVertices(m_alphas, m_vertexNewToOld);
CompactVertices(m_links, m_vertexNewToOld);
CompactVertices(m_vertexMatIds, m_vertexNewToOld);
CompactVerticesRaw(m_aux, m_auxSizeof, m_vertexNewToOld);
for (size_t i = 0, count = m_faces.size(); i < count; ++i)
{
for (int j = 0; j < 3; ++j)
{
const int oldVertedIdx = m_faces[i].vertexIndex[j];
assert(oldVertedIdx >= 0 && (size_t)oldVertedIdx < m_vertexOldToNew.size());
const int newVertexIndex = m_vertexOldToNew[oldVertedIdx];
m_faces[i].vertexIndex[j] = newVertexIndex;
}
}
}
// Deleting degraded faces (faces with two or more vertices
// sharing same position in space)
void RemoveDegradedFaces()
{
size_t j = 0;
for (size_t i = 0, count = m_faces.size(); i < count; ++i)
{
const Vec3& p0 = m_positions[m_faces[i].vertexIndex[0]];
const Vec3& p1 = m_positions[m_faces[i].vertexIndex[1]];
const Vec3& p2 = m_positions[m_faces[i].vertexIndex[2]];
if (p0 != p1 && p1 != p2 && p2 != p0)
{
m_faces[j] = m_faces[i];
if (!m_faceMatIds.empty())
{
m_faceMatIds[j] = m_faceMatIds[i];
}
++j;
}
}
m_faces.resize(j);
if (!m_faceMatIds.empty())
{
m_faceMatIds.resize(j);
}
}
private:
//////////////////////////////////////////////////////////////////////////
// Internal helpers
template
static void CompactVertices(std::vector& arr, const std::vector& newToOld)
{
if (arr.empty())
{
return;
}
const size_t newCount = newToOld.size();
std::vector tmp;
tmp.reserve(newCount);
for (size_t i = 0; i < newCount; ++i)
{
tmp.push_back(arr[newToOld[i]]);
}
arr.swap(tmp);
}
static void CompactVerticesRaw(std::vector& arr, size_t elemSizeof, const std::vector& newToOld)
{
if (arr.empty())
{
return;
}
const size_t newCount = newToOld.size();
std::vector tmp;
tmp.resize(newCount * elemSizeof);
for (size_t i = 0; i < newCount; ++i)
{
memcpy(&tmp[i * elemSizeof], &arr[newToOld[i] * elemSizeof], elemSizeof);
}
arr.swap(tmp);
}
struct VertexLess
{
const Mesh& m;
VertexLess(const Mesh& mesh)
: m(mesh)
{
}
bool operator()(int a, int b) const
{
if (!m.m_topologyIds.empty())
{
const int res = m.m_topologyIds[a] - m.m_topologyIds[b];
if (res != 0)
{
return res < 0;
}
}
{
const int res = memcmp(&m.m_positions[a], &m.m_positions[b], sizeof(m.m_positions[0]));
if (res != 0)
{
return res < 0;
}
}
int res = 0;
if (res == 0 && !m.m_normals.empty())
{
res = memcmp(&m.m_normals[a], &m.m_normals[b], sizeof(m.m_normals[0]));
}
for (uint streamIndex = 0; streamIndex < m.m_texCoords.size(); ++streamIndex)
{
if (res == 0 && !m.m_texCoords[streamIndex].empty())
{
res = memcmp(&m.m_texCoords[streamIndex][a], &m.m_texCoords[streamIndex][b], sizeof(m.m_texCoords[streamIndex][0]));
}
}
if (res == 0 && !m.m_colors.empty())
{
res = memcmp(&m.m_colors[a], &m.m_colors[b], sizeof(m.m_colors[0]));
}
if (res == 0 && !m.m_alphas.empty())
{
res = (int)m.m_alphas[a] - (int)m.m_alphas[b];
}
if (res == 0 && !m.m_links.empty())
{
if (m.m_links[a].links.size() != m.m_links[b].links.size())
{
res = (m.m_links[a].links.size() < m.m_links[b].links.size()) ? -1 : +1;
}
else
{
res = memcmp(&m.m_links[a].links[0], &m.m_links[b].links[0], sizeof(m.m_links[a].links[0]) * m.m_links[a].links.size());
}
}
if (res == 0 && !m.m_vertexMatIds.empty())
{
res = m.m_vertexMatIds[a] - m.m_vertexMatIds[b];
}
if (res == 0 && !m.m_aux.empty())
{
res = memcmp(&m.m_aux[a * m.m_auxSizeof], &m.m_aux[b * m.m_auxSizeof], m.m_auxSizeof);
}
return res < 0;
}
};
};
} // namespace MeshUtils
#endif // CRYINCLUDE_CRYCOMMONTOOLS_EXPORT_MESHUTILS_H