Fix for blendshapes import when using the JoinIdenticalVertices optimization flag
parent
3d589d8fc8
commit
78d04c137d
|
@ -114,179 +114,58 @@ void JoinVerticesProcess::Execute( aiScene* pScene)
|
||||||
pScene->mFlags |= AI_SCENE_FLAGS_NON_VERBOSE_FORMAT;
|
pScene->mFlags |= AI_SCENE_FLAGS_NON_VERBOSE_FORMAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
namespace {
|
||||||
// Unites identical vertices in the given mesh
|
|
||||||
int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
|
bool areVerticesEqual(const Vertex &lhs, const Vertex &rhs, bool complex)
|
||||||
{
|
{
|
||||||
static_assert( AI_MAX_NUMBER_OF_COLOR_SETS == 8, "AI_MAX_NUMBER_OF_COLOR_SETS == 8");
|
|
||||||
static_assert( AI_MAX_NUMBER_OF_TEXTURECOORDS == 8, "AI_MAX_NUMBER_OF_TEXTURECOORDS == 8");
|
|
||||||
|
|
||||||
// Return early if we don't have any positions
|
|
||||||
if (!pMesh->HasPositions() || !pMesh->HasFaces()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We'll never have more vertices afterwards.
|
|
||||||
std::vector<Vertex> uniqueVertices;
|
|
||||||
uniqueVertices.reserve( pMesh->mNumVertices);
|
|
||||||
|
|
||||||
// For each vertex the index of the vertex it was replaced by.
|
|
||||||
// Since the maximal number of vertices is 2^31-1, the most significand bit can be used to mark
|
|
||||||
// whether a new vertex was created for the index (true) or if it was replaced by an existing
|
|
||||||
// unique vertex (false). This saves an additional std::vector<bool> and greatly enhances
|
|
||||||
// branching performance.
|
|
||||||
static_assert(AI_MAX_VERTICES == 0x7fffffff, "AI_MAX_VERTICES == 0x7fffffff");
|
|
||||||
std::vector<unsigned int> replaceIndex( pMesh->mNumVertices, 0xffffffff);
|
|
||||||
|
|
||||||
// A little helper to find locally close vertices faster.
|
// A little helper to find locally close vertices faster.
|
||||||
// Try to reuse the lookup table from the last step.
|
// Try to reuse the lookup table from the last step.
|
||||||
const static float epsilon = 1e-5f;
|
const static float epsilon = 1e-5f;
|
||||||
// float posEpsilonSqr;
|
|
||||||
SpatialSort* vertexFinder = NULL;
|
|
||||||
SpatialSort _vertexFinder;
|
|
||||||
|
|
||||||
typedef std::pair<SpatialSort,float> SpatPair;
|
|
||||||
if (shared) {
|
|
||||||
std::vector<SpatPair >* avf;
|
|
||||||
shared->GetProperty(AI_SPP_SPATIAL_SORT,avf);
|
|
||||||
if (avf) {
|
|
||||||
SpatPair& blubb = (*avf)[meshIndex];
|
|
||||||
vertexFinder = &blubb.first;
|
|
||||||
// posEpsilonSqr = blubb.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!vertexFinder) {
|
|
||||||
// bad, need to compute it.
|
|
||||||
_vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof( aiVector3D));
|
|
||||||
vertexFinder = &_vertexFinder;
|
|
||||||
// posEpsilonSqr = ComputePositionEpsilon(pMesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Squared because we check against squared length of the vector difference
|
// Squared because we check against squared length of the vector difference
|
||||||
static const float squareEpsilon = epsilon * epsilon;
|
static const float squareEpsilon = epsilon * epsilon;
|
||||||
|
|
||||||
// Again, better waste some bytes than a realloc ...
|
// Square compare is useful for animeshes vertexes compare
|
||||||
std::vector<unsigned int> verticesFound;
|
if ((lhs.position - rhs.position).SquareLength() > squareEpsilon) {
|
||||||
verticesFound.reserve(10);
|
return false;
|
||||||
|
}
|
||||||
// Run an optimized code path if we don't have multiple UVs or vertex colors.
|
|
||||||
// This should yield false in more than 99% of all imports ...
|
|
||||||
const bool complex = ( pMesh->GetNumColorChannels() > 0 || pMesh->GetNumUVChannels() > 1);
|
|
||||||
|
|
||||||
// Now check each vertex if it brings something new to the table
|
|
||||||
for( unsigned int a = 0; a < pMesh->mNumVertices; a++) {
|
|
||||||
// collect the vertex data
|
|
||||||
Vertex v(pMesh,a);
|
|
||||||
|
|
||||||
// collect all vertices that are close enough to the given position
|
|
||||||
vertexFinder->FindIdenticalPositions( v.position, verticesFound);
|
|
||||||
unsigned int matchIndex = 0xffffffff;
|
|
||||||
|
|
||||||
// check all unique vertices close to the position if this vertex is already present among them
|
|
||||||
for( unsigned int b = 0; b < verticesFound.size(); b++) {
|
|
||||||
|
|
||||||
const unsigned int vidx = verticesFound[b];
|
|
||||||
const unsigned int uidx = replaceIndex[ vidx];
|
|
||||||
if( uidx & 0x80000000)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const Vertex& uv = uniqueVertices[ uidx];
|
|
||||||
// Position mismatch is impossible - the vertex finder already discarded all non-matching positions
|
|
||||||
|
|
||||||
// We just test the other attributes even if they're not present in the mesh.
|
// We just test the other attributes even if they're not present in the mesh.
|
||||||
// In this case they're initialized to 0 so the comparison succeeds.
|
// In this case they're initialized to 0 so the comparison succeeds.
|
||||||
// By this method the non-present attributes are effectively ignored in the comparison.
|
// By this method the non-present attributes are effectively ignored in the comparison.
|
||||||
if( (uv.normal - v.normal).SquareLength() > squareEpsilon)
|
if ((lhs.normal - rhs.normal).SquareLength() > squareEpsilon) {
|
||||||
continue;
|
return false;
|
||||||
if( (uv.texcoords[0] - v.texcoords[0]).SquareLength() > squareEpsilon)
|
}
|
||||||
continue;
|
|
||||||
if( (uv.tangent - v.tangent).SquareLength() > squareEpsilon)
|
if ((lhs.texcoords[0] - rhs.texcoords[0]).SquareLength() > squareEpsilon) {
|
||||||
continue;
|
return false;
|
||||||
if( (uv.bitangent - v.bitangent).SquareLength() > squareEpsilon)
|
}
|
||||||
continue;
|
|
||||||
|
if ((lhs.tangent - rhs.tangent).SquareLength() > squareEpsilon) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((lhs.bitangent - rhs.bitangent).SquareLength() > squareEpsilon) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Usually we won't have vertex colors or multiple UVs, so we can skip from here
|
// Usually we won't have vertex colors or multiple UVs, so we can skip from here
|
||||||
// Actually this increases runtime performance slightly, at least if branch
|
// Actually this increases runtime performance slightly, at least if branch
|
||||||
// prediction is on our side.
|
// prediction is on our side.
|
||||||
if (complex) {
|
if (complex) {
|
||||||
// manually unrolled because continue wouldn't work as desired in an inner loop,
|
for (int i = 0; i < 8; i++) {
|
||||||
// also because some compilers seem to fail the task. Colors and UV coords
|
if (i > 0 && (lhs.texcoords[i] - rhs.texcoords[i]).SquareLength() > squareEpsilon) {
|
||||||
// are interleaved since the higher entries are most likely to be
|
return false;
|
||||||
// zero and thus useless. By interleaving the arrays, vertices are,
|
|
||||||
// on average, rejected earlier.
|
|
||||||
|
|
||||||
if( (uv.texcoords[1] - v.texcoords[1]).SquareLength() > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
if( GetColorDifference( uv.colors[0], v.colors[0]) > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if( (uv.texcoords[2] - v.texcoords[2]).SquareLength() > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
if( GetColorDifference( uv.colors[1], v.colors[1]) > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if( (uv.texcoords[3] - v.texcoords[3]).SquareLength() > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
if( GetColorDifference( uv.colors[2], v.colors[2]) > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if( (uv.texcoords[4] - v.texcoords[4]).SquareLength() > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
if( GetColorDifference( uv.colors[3], v.colors[3]) > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if( (uv.texcoords[5] - v.texcoords[5]).SquareLength() > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
if( GetColorDifference( uv.colors[4], v.colors[4]) > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if( (uv.texcoords[6] - v.texcoords[6]).SquareLength() > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
if( GetColorDifference( uv.colors[5], v.colors[5]) > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if( (uv.texcoords[7] - v.texcoords[7]).SquareLength() > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
if( GetColorDifference( uv.colors[6], v.colors[6]) > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if( GetColorDifference( uv.colors[7], v.colors[7]) > squareEpsilon)
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
if (GetColorDifference(lhs.colors[i], rhs.colors[i]) > squareEpsilon) {
|
||||||
// we're still here -> this vertex perfectly matches our given vertex
|
return false;
|
||||||
matchIndex = uidx;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// found a replacement vertex among the uniques?
|
|
||||||
if( matchIndex != 0xffffffff)
|
|
||||||
{
|
|
||||||
// store where to found the matching unique vertex
|
|
||||||
replaceIndex[a] = matchIndex | 0x80000000;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// no unique vertex matches it up to now -> so add it
|
|
||||||
replaceIndex[a] = (unsigned int)uniqueVertices.size();
|
|
||||||
uniqueVertices.push_back( v);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!DefaultLogger::isNullLogger() && DefaultLogger::get()->getLogSeverity() == Logger::VERBOSE) {
|
return true;
|
||||||
DefaultLogger::get()->debug((Formatter::format(),
|
|
||||||
"Mesh ",meshIndex,
|
|
||||||
" (",
|
|
||||||
(pMesh->mName.length ? pMesh->mName.data : "unnamed"),
|
|
||||||
") | Verts in: ",pMesh->mNumVertices,
|
|
||||||
" out: ",
|
|
||||||
uniqueVertices.size(),
|
|
||||||
" | ~",
|
|
||||||
((pMesh->mNumVertices - uniqueVertices.size()) / (float)pMesh->mNumVertices) * 100.f,
|
|
||||||
"%"
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class XMesh>
|
||||||
|
void updateXMeshVertices(XMesh *pMesh, std::vector<Vertex> &uniqueVertices) {
|
||||||
// replace vertex data with the unique data sets
|
// replace vertex data with the unique data sets
|
||||||
pMesh->mNumVertices = (unsigned int)uniqueVertices.size();
|
pMesh->mNumVertices = (unsigned int)uniqueVertices.size();
|
||||||
|
|
||||||
|
@ -296,11 +175,15 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
|
||||||
// dislikes branches, even if they're easily predictable.
|
// dislikes branches, even if they're easily predictable.
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Position
|
// Position, if present (check made for aiAnimMesh)
|
||||||
|
if (pMesh->mVertices)
|
||||||
|
{
|
||||||
delete [] pMesh->mVertices;
|
delete [] pMesh->mVertices;
|
||||||
pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
|
pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
|
||||||
for( unsigned int a = 0; a < pMesh->mNumVertices; a++)
|
for (unsigned int a = 0; a < pMesh->mNumVertices; a++) {
|
||||||
pMesh->mVertices[a] = uniqueVertices[a].position;
|
pMesh->mVertices[a] = uniqueVertices[a].position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Normals, if present
|
// Normals, if present
|
||||||
if (pMesh->mNormals)
|
if (pMesh->mNormals)
|
||||||
|
@ -347,6 +230,155 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
|
||||||
pMesh->mTextureCoords[a][b] = uniqueVertices[b].texcoords[a];
|
pMesh->mTextureCoords[a][b] = uniqueVertices[b].texcoords[a];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// Unites identical vertices in the given mesh
|
||||||
|
int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
|
||||||
|
{
|
||||||
|
static_assert( AI_MAX_NUMBER_OF_COLOR_SETS == 8, "AI_MAX_NUMBER_OF_COLOR_SETS == 8");
|
||||||
|
static_assert( AI_MAX_NUMBER_OF_TEXTURECOORDS == 8, "AI_MAX_NUMBER_OF_TEXTURECOORDS == 8");
|
||||||
|
|
||||||
|
// Return early if we don't have any positions
|
||||||
|
if (!pMesh->HasPositions() || !pMesh->HasFaces()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll never have more vertices afterwards.
|
||||||
|
std::vector<Vertex> uniqueVertices;
|
||||||
|
uniqueVertices.reserve( pMesh->mNumVertices);
|
||||||
|
|
||||||
|
// For each vertex the index of the vertex it was replaced by.
|
||||||
|
// Since the maximal number of vertices is 2^31-1, the most significand bit can be used to mark
|
||||||
|
// whether a new vertex was created for the index (true) or if it was replaced by an existing
|
||||||
|
// unique vertex (false). This saves an additional std::vector<bool> and greatly enhances
|
||||||
|
// branching performance.
|
||||||
|
static_assert(AI_MAX_VERTICES == 0x7fffffff, "AI_MAX_VERTICES == 0x7fffffff");
|
||||||
|
std::vector<unsigned int> replaceIndex( pMesh->mNumVertices, 0xffffffff);
|
||||||
|
|
||||||
|
// float posEpsilonSqr;
|
||||||
|
SpatialSort* vertexFinder = NULL;
|
||||||
|
SpatialSort _vertexFinder;
|
||||||
|
|
||||||
|
typedef std::pair<SpatialSort,float> SpatPair;
|
||||||
|
if (shared) {
|
||||||
|
std::vector<SpatPair >* avf;
|
||||||
|
shared->GetProperty(AI_SPP_SPATIAL_SORT,avf);
|
||||||
|
if (avf) {
|
||||||
|
SpatPair& blubb = (*avf)[meshIndex];
|
||||||
|
vertexFinder = &blubb.first;
|
||||||
|
// posEpsilonSqr = blubb.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!vertexFinder) {
|
||||||
|
// bad, need to compute it.
|
||||||
|
_vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof( aiVector3D));
|
||||||
|
vertexFinder = &_vertexFinder;
|
||||||
|
// posEpsilonSqr = ComputePositionEpsilon(pMesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Again, better waste some bytes than a realloc ...
|
||||||
|
std::vector<unsigned int> verticesFound;
|
||||||
|
verticesFound.reserve(10);
|
||||||
|
|
||||||
|
// Run an optimized code path if we don't have multiple UVs or vertex colors.
|
||||||
|
// This should yield false in more than 99% of all imports ...
|
||||||
|
const bool complex = ( pMesh->GetNumColorChannels() > 0 || pMesh->GetNumUVChannels() > 1);
|
||||||
|
const bool hasAnimMeshes = pMesh->mNumAnimMeshes > 0;
|
||||||
|
|
||||||
|
// We'll never have more vertices afterwards.
|
||||||
|
std::vector<Vertex> uniqueAnimatedVertices[pMesh->mNumAnimMeshes];
|
||||||
|
if (hasAnimMeshes) {
|
||||||
|
for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) {
|
||||||
|
uniqueAnimatedVertices[animMeshIndex].reserve(pMesh->mNumVertices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check each vertex if it brings something new to the table
|
||||||
|
for( unsigned int a = 0; a < pMesh->mNumVertices; a++) {
|
||||||
|
// collect the vertex data
|
||||||
|
Vertex v(pMesh,a);
|
||||||
|
|
||||||
|
// collect all vertices that are close enough to the given position
|
||||||
|
vertexFinder->FindIdenticalPositions( v.position, verticesFound);
|
||||||
|
unsigned int matchIndex = 0xffffffff;
|
||||||
|
|
||||||
|
// check all unique vertices close to the position if this vertex is already present among them
|
||||||
|
for( unsigned int b = 0; b < verticesFound.size(); b++) {
|
||||||
|
const unsigned int vidx = verticesFound[b];
|
||||||
|
const unsigned int uidx = replaceIndex[ vidx];
|
||||||
|
if( uidx & 0x80000000)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const Vertex& uv = uniqueVertices[ uidx];
|
||||||
|
|
||||||
|
if (!areVerticesEqual(v, uv, complex)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAnimMeshes) {
|
||||||
|
// If given vertex is animated, then it has to be preserver 1 to 1 (base mesh and animated mesh require same topology)
|
||||||
|
// NOTE: not doing this totaly breaks anim meshes as they don't have their own faces (they use pMesh->mFaces)
|
||||||
|
bool breaksAnimMesh = false;
|
||||||
|
for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) {
|
||||||
|
const Vertex& animatedUV = uniqueAnimatedVertices[animMeshIndex][ uidx];
|
||||||
|
Vertex aniMeshVertex(pMesh->mAnimMeshes[animMeshIndex], a);
|
||||||
|
if (!areVerticesEqual(aniMeshVertex, animatedUV, complex)) {
|
||||||
|
breaksAnimMesh = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (breaksAnimMesh) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're still here -> this vertex perfectly matches our given vertex
|
||||||
|
matchIndex = uidx;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// found a replacement vertex among the uniques?
|
||||||
|
if( matchIndex != 0xffffffff)
|
||||||
|
{
|
||||||
|
// store where to found the matching unique vertex
|
||||||
|
replaceIndex[a] = matchIndex | 0x80000000;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no unique vertex matches it up to now -> so add it
|
||||||
|
replaceIndex[a] = (unsigned int)uniqueVertices.size();
|
||||||
|
uniqueVertices.push_back( v);
|
||||||
|
if (hasAnimMeshes) {
|
||||||
|
for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) {
|
||||||
|
Vertex aniMeshVertex(pMesh->mAnimMeshes[animMeshIndex], a);
|
||||||
|
uniqueAnimatedVertices[animMeshIndex].push_back(aniMeshVertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DefaultLogger::isNullLogger() && DefaultLogger::get()->getLogSeverity() == Logger::VERBOSE) {
|
||||||
|
DefaultLogger::get()->debug((Formatter::format(),
|
||||||
|
"Mesh ",meshIndex,
|
||||||
|
" (",
|
||||||
|
(pMesh->mName.length ? pMesh->mName.data : "unnamed"),
|
||||||
|
") | Verts in: ",pMesh->mNumVertices,
|
||||||
|
" out: ",
|
||||||
|
uniqueVertices.size(),
|
||||||
|
" | ~",
|
||||||
|
((pMesh->mNumVertices - uniqueVertices.size()) / (float)pMesh->mNumVertices) * 100.f,
|
||||||
|
"%"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateXMeshVertices(pMesh, uniqueVertices);
|
||||||
|
if (hasAnimMeshes) {
|
||||||
|
for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) {
|
||||||
|
updateXMeshVertices(pMesh->mAnimMeshes[animMeshIndex], uniqueAnimatedVertices[animMeshIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// adjust the indices in all faces
|
// adjust the indices in all faces
|
||||||
for( unsigned int a = 0; a < pMesh->mNumFaces; a++)
|
for( unsigned int a = 0; a < pMesh->mNumFaces; a++)
|
||||||
|
|
|
@ -134,6 +134,30 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
/** Extract a particular vertex from a anim mesh and interleave all components */
|
||||||
|
explicit Vertex(const aiAnimMesh* msh, unsigned int idx) {
|
||||||
|
ai_assert(idx < msh->mNumVertices);
|
||||||
|
position = msh->mVertices[idx];
|
||||||
|
|
||||||
|
if (msh->HasNormals()) {
|
||||||
|
normal = msh->mNormals[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msh->HasTangentsAndBitangents()) {
|
||||||
|
tangent = msh->mTangents[idx];
|
||||||
|
bitangent = msh->mBitangents[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned int i = 0; msh->HasTextureCoords(i); ++i) {
|
||||||
|
texcoords[i] = msh->mTextureCoords[i][idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned int i = 0; msh->HasVertexColors(i); ++i) {
|
||||||
|
colors[i] = msh->mColors[i][idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Vertex& operator += (const Vertex& v) {
|
Vertex& operator += (const Vertex& v) {
|
||||||
|
|
Loading…
Reference in New Issue