From dfd70b5c10cd45ef9d80c96ee08fe4e7393520a9 Mon Sep 17 00:00:00 2001 From: Florian Born Date: Thu, 9 Mar 2023 19:06:58 +0100 Subject: [PATCH] GLTF Importer: Build a list of the actual vertices so it works well with shared attribute lists --- code/AssetLib/glTF2/glTF2Asset.h | 2 +- code/AssetLib/glTF2/glTF2Asset.inl | 19 ++--- code/AssetLib/glTF2/glTF2Importer.cpp | 100 +++++++++++++++++--------- 3 files changed, 78 insertions(+), 43 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index 1d7cff325..cf9c8f11c 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -565,7 +565,7 @@ struct Accessor : public Object { inline size_t GetMaxByteSize(); template - void ExtractData(T *&outData); + size_t ExtractData(T *&outData, const std::vector *remappingIndices = nullptr); void WriteData(size_t count, const void *src_buffer, size_t src_stride); void WriteSparseValues(size_t count, const void *src_data, size_t src_dataStride); diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index 0e2998357..4e4497e1f 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -962,14 +962,15 @@ inline size_t Accessor::GetMaxByteSize() { } template -void Accessor::ExtractData(T *&outData) { +size_t Accessor::ExtractData(T *&outData, const std::vector *remappingIndices) { uint8_t *data = GetPointer(); if (!data) { throw DeadlyImportError("GLTF2: data is null when extracting data from ", getContextForErrorMessages(id, name)); } + const size_t usedCount = (remappingIndices != nullptr) ? remappingIndices->size() : count; const size_t elemSize = GetElementSize(); - const size_t totalSize = elemSize * count; + const size_t totalSize = elemSize * usedCount; const size_t stride = GetStride(); @@ -980,18 +981,20 @@ void Accessor::ExtractData(T *&outData) { } const size_t maxSize = GetMaxByteSize(); - if (count * stride > maxSize) { - throw DeadlyImportError("GLTF: count*stride ", (count * stride), " > maxSize ", maxSize, " in ", getContextForErrorMessages(id, name)); + if (usedCount * stride > maxSize) { + throw DeadlyImportError("GLTF: count*stride ", (usedCount * stride), " > maxSize ", maxSize, " in ", getContextForErrorMessages(id, name)); } - outData = new T[count]; - if (stride == elemSize && targetElemSize == elemSize) { + outData = new T[usedCount]; + if (remappingIndices == nullptr && stride == elemSize && targetElemSize == elemSize) { memcpy(outData, data, totalSize); } else { - for (size_t i = 0; i < count; ++i) { - memcpy(outData + i, data + i * stride, elemSize); + for (size_t i = 0; i < usedCount; ++i) { + size_t srcIdx = remappingIndices != nullptr ? static_cast((*remappingIndices)[i]) : i; + memcpy(outData + i, data + srcIdx * stride, elemSize); } } + return usedCount; } inline void Accessor::WriteData(size_t _count, const void *src_buffer, size_t src_stride) { diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index d30556806..f0dc284c3 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -453,6 +453,12 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { unsigned int k = 0; meshOffsets.clear(); + + const unsigned int unusedIndex = ~0; + std::vector usedVertexIndices; + std::vector reverseMappingIndices; + std::vector indexBuffer; + for (unsigned int m = 0; m < r.meshes.Size(); ++m) { Mesh &mesh = r.meshes[m]; @@ -462,6 +468,36 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { for (unsigned int p = 0; p < mesh.primitives.size(); ++p) { Mesh::Primitive &prim = mesh.primitives[p]; + // extract used vertices: + bool useIndexBuffer = prim.indices; + std::vector* usedVertexIndicesPtr = nullptr; + if (useIndexBuffer) { + size_t count = prim.indices->count; + indexBuffer.resize(count); + usedVertexIndices.clear(); + reverseMappingIndices.clear(); + usedVertexIndices.reserve(count / 3); // this is a very rough heuristic to reduce re-allocations + usedVertexIndicesPtr = &usedVertexIndices; + Accessor::Indexer data = prim.indices->GetIndexer(); + if (!data.IsValid()) { + throw DeadlyImportError("GLTF: Invalid accessor without data in mesh ", getContextForErrorMessages(mesh.id, mesh.name)); + } + + // Build the vertex remapping table and the modified index buffer (used later instead of the original one) + // In case no index buffer is used, the original vertex arrays are being used so no remapping is required in the first place. + for (unsigned int i = 0; i < count; ++i) { + unsigned int index = data.GetUInt(i); + if (index >= reverseMappingIndices.size()) { + reverseMappingIndices.resize(index + 1, unusedIndex); + } + if (reverseMappingIndices[index] == unusedIndex) { + reverseMappingIndices[index] = static_cast(usedVertexIndices.size()); + usedVertexIndices.push_back(index); + } + indexBuffer[i] = reverseMappingIndices[index]; + } + } + aiMesh *aim = new aiMesh(); meshes.push_back(std::unique_ptr(aim)); @@ -493,26 +529,27 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { Mesh::Primitive::Attributes &attr = prim.attributes; + size_t numAllVertices = 0; if (!attr.position.empty() && attr.position[0]) { - aim->mNumVertices = static_cast(attr.position[0]->count); - attr.position[0]->ExtractData(aim->mVertices); + numAllVertices = attr.position[0]->count; + aim->mNumVertices = static_cast(attr.position[0]->ExtractData(aim->mVertices, usedVertexIndicesPtr)); } if (!attr.normal.empty() && attr.normal[0]) { - if (attr.normal[0]->count != aim->mNumVertices) { + if (attr.normal[0]->count != numAllVertices) { DefaultLogger::get()->warn("Normal count in mesh \"", mesh.name, "\" does not match the vertex count, normals ignored."); } else { - attr.normal[0]->ExtractData(aim->mNormals); + attr.normal[0]->ExtractData(aim->mNormals, usedVertexIndicesPtr); // only extract tangents if normals are present if (!attr.tangent.empty() && attr.tangent[0]) { - if (attr.tangent[0]->count != aim->mNumVertices) { + if (attr.tangent[0]->count != numAllVertices) { DefaultLogger::get()->warn("Tangent count in mesh \"", mesh.name, "\" does not match the vertex count, tangents ignored."); } else { // generate bitangents from normals and tangents according to spec Tangent *tangents = nullptr; - attr.tangent[0]->ExtractData(tangents); + attr.tangent[0]->ExtractData(tangents, usedVertexIndicesPtr); aim->mTangents = new aiVector3D[aim->mNumVertices]; aim->mBitangents = new aiVector3D[aim->mNumVertices]; @@ -529,7 +566,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { } for (size_t c = 0; c < attr.color.size() && c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) { - if (attr.color[c]->count != aim->mNumVertices) { + if (attr.color[c]->count != numAllVertices) { DefaultLogger::get()->warn("Color stream size in mesh \"", mesh.name, "\" does not match the vertex count"); continue; @@ -537,7 +574,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { auto componentType = attr.color[c]->componentType; if (componentType == glTF2::ComponentType_FLOAT) { - attr.color[c]->ExtractData(aim->mColors[c]); + attr.color[c]->ExtractData(aim->mColors[c], usedVertexIndicesPtr); } else { if (componentType == glTF2::ComponentType_UNSIGNED_BYTE) { aim->mColors[c] = GetVertexColorsForType(attr.color[c]); @@ -552,13 +589,13 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { continue; } - if (attr.texcoord[tc]->count != aim->mNumVertices) { + if (attr.texcoord[tc]->count != numAllVertices) { DefaultLogger::get()->warn("Texcoord stream size in mesh \"", mesh.name, "\" does not match the vertex count"); continue; } - attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]); + attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc], usedVertexIndicesPtr); aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents(); aiVector3D *values = aim->mTextureCoords[tc]; @@ -583,11 +620,11 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { Mesh::Primitive::Target &target = targets[i]; if (needPositions) { - if (target.position[0]->count != aim->mNumVertices) { + if (target.position[0]->count != numAllVertices) { ASSIMP_LOG_WARN("Positions of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count"); } else { aiVector3D *positionDiff = nullptr; - target.position[0]->ExtractData(positionDiff); + target.position[0]->ExtractData(positionDiff, usedVertexIndicesPtr); for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId]; } @@ -595,11 +632,11 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { } } if (needNormals) { - if (target.normal[0]->count != aim->mNumVertices) { + if (target.normal[0]->count != numAllVertices) { ASSIMP_LOG_WARN("Normals of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count"); } else { aiVector3D *normalDiff = nullptr; - target.normal[0]->ExtractData(normalDiff); + target.normal[0]->ExtractData(normalDiff, usedVertexIndicesPtr); for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId]; } @@ -610,14 +647,14 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { if (!aiAnimMesh.HasNormals()) { // prevent nullptr access to aiAnimMesh.mNormals below when no normals are available ASSIMP_LOG_WARN("Bitangents of target ", i, " in mesh \"", mesh.name, "\" can't be computed, because mesh has no normals."); - } else if (target.tangent[0]->count != aim->mNumVertices) { + } else if (target.tangent[0]->count != numAllVertices) { ASSIMP_LOG_WARN("Tangents of target ", i, " in mesh \"", mesh.name, "\" does not match the vertex count"); } else { Tangent *tangent = nullptr; - attr.tangent[0]->ExtractData(tangent); + attr.tangent[0]->ExtractData(tangent, usedVertexIndicesPtr); aiVector3D *tangentDiff = nullptr; - target.tangent[0]->ExtractData(tangentDiff); + target.tangent[0]->ExtractData(tangentDiff, usedVertexIndicesPtr); for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) { tangent[vertexId].xyz += tangentDiff[vertexId]; @@ -641,20 +678,15 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { aiFace *facePtr = nullptr; size_t nFaces = 0; - if (prim.indices) { - size_t count = prim.indices->count; - - Accessor::Indexer data = prim.indices->GetIndexer(); - if (!data.IsValid()) { - throw DeadlyImportError("GLTF: Invalid accessor without data in mesh ", getContextForErrorMessages(mesh.id, mesh.name)); - } + if (useIndexBuffer) { + size_t count = indexBuffer.size(); switch (prim.mode) { case PrimitiveMode_POINTS: { nFaces = count; facePtr = faces = new aiFace[nFaces]; for (unsigned int i = 0; i < count; ++i) { - SetFaceAndAdvance1(facePtr, aim->mNumVertices, data.GetUInt(i)); + SetFaceAndAdvance1(facePtr, aim->mNumVertices, indexBuffer[i]); } break; } @@ -667,7 +699,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { } facePtr = faces = new aiFace[nFaces]; for (unsigned int i = 0; i < count; i += 2) { - SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(i), data.GetUInt(i + 1)); + SetFaceAndAdvance2(facePtr, aim->mNumVertices, indexBuffer[i], indexBuffer[i + 1]); } break; } @@ -676,12 +708,12 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { case PrimitiveMode_LINE_STRIP: { nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); facePtr = faces = new aiFace[nFaces]; - SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(0), data.GetUInt(1)); + SetFaceAndAdvance2(facePtr, aim->mNumVertices, indexBuffer[0], indexBuffer[1]); for (unsigned int i = 2; i < count; ++i) { - SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(i - 1), data.GetUInt(i)); + SetFaceAndAdvance2(facePtr, aim->mNumVertices, indexBuffer[i - 1], indexBuffer[i]); } if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop - SetFaceAndAdvance2(facePtr, aim->mNumVertices, data.GetUInt(static_cast(count) - 1), faces[0].mIndices[0]); + SetFaceAndAdvance2(facePtr, aim->mNumVertices, indexBuffer[static_cast(count) - 1], faces[0].mIndices[0]); } break; } @@ -694,7 +726,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { } facePtr = faces = new aiFace[nFaces]; for (unsigned int i = 0; i < count; i += 3) { - SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); + SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[i], indexBuffer[i + 1], indexBuffer[i + 2]); } break; } @@ -705,10 +737,10 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { // The ordering is to ensure that the triangles are all drawn with the same orientation if ((i + 1) % 2 == 0) { // For even n, vertices n + 1, n, and n + 2 define triangle n - SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(i + 1), data.GetUInt(i), data.GetUInt(i + 2)); + SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[i + 1], indexBuffer[i], indexBuffer[i + 2]); } else { // For odd n, vertices n, n+1, and n+2 define triangle n - SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); + SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[i], indexBuffer[i + 1], indexBuffer[i + 2]); } } break; @@ -716,9 +748,9 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { case PrimitiveMode_TRIANGLE_FAN: nFaces = count - 2; facePtr = faces = new aiFace[nFaces]; - SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(0), data.GetUInt(1), data.GetUInt(2)); + SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[0], indexBuffer[1], indexBuffer[2]); for (unsigned int i = 1; i < nFaces; ++i) { - SetFaceAndAdvance3(facePtr, aim->mNumVertices, data.GetUInt(0), data.GetUInt(i + 1), data.GetUInt(i + 2)); + SetFaceAndAdvance3(facePtr, aim->mNumVertices, indexBuffer[0], indexBuffer[i + 1], indexBuffer[i + 2]); } break; }