From ff9f3b86084c3b502d9dd4fefe3d87275f4df57d Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Wed, 29 Apr 2020 17:17:46 +0100 Subject: [PATCH] Collada: Ensure has unique id Use the "id" for mesh names by default. Set option AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES to use the mesh "name" instead --- code/CMakeLists.txt | 1 + code/Collada/ColladaExporter.cpp | 104 +++++++++++++++++++++------- code/Collada/ColladaExporter.h | 12 ++-- code/Collada/ColladaHelper.h | 4 +- code/Collada/ColladaLoader.cpp | 18 ++++- code/Collada/ColladaParser.cpp | 101 ++++++++++++++------------- code/Collada/ColladaParser.h | 16 ++--- include/assimp/ColladaMetaData.h | 53 ++++++++++++++ include/assimp/config.h.in | 6 +- test/unit/utColladaImportExport.cpp | 53 +++++++++++++- 10 files changed, 274 insertions(+), 94 deletions(-) create mode 100644 include/assimp/ColladaMetaData.h diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 6afed40f9..f626a51e3 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -66,6 +66,7 @@ SET( PUBLIC_HEADERS ${HEADER_PATH}/color4.h ${HEADER_PATH}/color4.inl ${CMAKE_CURRENT_BINARY_DIR}/../include/assimp/config.h + ${HEADER_PATH}/ColladaMetaData.h ${HEADER_PATH}/commonMetaData.h ${HEADER_PATH}/defs.h ${HEADER_PATH}/Defines.h diff --git a/code/Collada/ColladaExporter.cpp b/code/Collada/ColladaExporter.cpp index 0026fda26..a502e728c 100644 --- a/code/Collada/ColladaExporter.cpp +++ b/code/Collada/ColladaExporter.cpp @@ -45,6 +45,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ColladaExporter.h" #include +#include #include #include #include @@ -115,7 +116,7 @@ static const std::string XMLIDEncode(const std::string &name) { if (strchr(XML_ID_CHARS, *it) != nullptr) { idEncoded << *it; } else { - // Select placeholder character based on invalid character to prevent name collisions + // Select placeholder character based on invalid character to reduce ID collisions idEncoded << XML_ID_CHARS[(*it) % XML_ID_CHARS_COUNT]; } } @@ -854,8 +855,8 @@ void ColladaExporter::WriteControllerLibrary() { // Writes a skin controller of the given mesh void ColladaExporter::WriteController(size_t pIndex) { const aiMesh *mesh = mScene->mMeshes[pIndex]; - const std::string idstr = mesh->mName.length == 0 ? GetMeshId(pIndex) : mesh->mName.C_Str(); - const std::string idstrEscaped = XMLIDEncode(idstr); + const std::string idstr = GetMeshUniqueId(pIndex); + const std::string namestr = GetMeshName(pIndex); if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0) return; @@ -863,11 +864,11 @@ void ColladaExporter::WriteController(size_t pIndex) { if (mesh->mNumBones == 0) return; - mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); // bind pose matrix @@ -884,10 +885,10 @@ void ColladaExporter::WriteController(size_t pIndex) { PopTag(); mOutput << startstr << "" << endstr; - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "mNumBones << "\">"; + mOutput << startstr << "mNumBones << "\">"; for (size_t i = 0; i < mesh->mNumBones; ++i) mOutput << XMLIDEncode(mesh->mBones[i]->mName.C_Str()) << " "; @@ -897,7 +898,7 @@ void ColladaExporter::WriteController(size_t pIndex) { mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "mNumBones << "\" stride=\"" << 1 << "\">" << endstr; + mOutput << startstr << "mNumBones << "\" stride=\"" << 1 << "\">" << endstr; PushTag(); mOutput << startstr << "" << endstr; @@ -934,8 +935,8 @@ void ColladaExporter::WriteController(size_t pIndex) { mOutput << startstr << "" << endstr; PushTag(); - mOutput << startstr << "" << endstr; - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PopTag(); mOutput << startstr << "" << endstr; @@ -943,8 +944,8 @@ void ColladaExporter::WriteController(size_t pIndex) { mOutput << startstr << "mNumVertices << "\">" << endstr; PushTag(); - mOutput << startstr << "" << endstr; - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; mOutput << startstr << ""; @@ -1019,9 +1020,8 @@ void ColladaExporter::WriteGeometryLibrary() { // Writes the given mesh void ColladaExporter::WriteGeometry(size_t pIndex) { const aiMesh *mesh = mScene->mMeshes[pIndex]; - const std::string idstr = mesh->mName.length == 0 ? GetMeshId(pIndex) : mesh->mName.C_Str(); - const std::string geometryName = XMLEscape(idstr); - const std::string geometryId = XMLIDEncode(idstr); + const std::string geometryName = GetMeshName(pIndex); + const std::string geometryId = GetMeshUniqueId(pIndex); if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0) return; @@ -1034,15 +1034,15 @@ void ColladaExporter::WriteGeometry(size_t pIndex) { PushTag(); // Positions - WriteFloatArray(idstr + "-positions", FloatType_Vector, (ai_real *)mesh->mVertices, mesh->mNumVertices); + WriteFloatArray(geometryId + "-positions", FloatType_Vector, (ai_real *)mesh->mVertices, mesh->mNumVertices); // Normals, if any if (mesh->HasNormals()) - WriteFloatArray(idstr + "-normals", FloatType_Vector, (ai_real *)mesh->mNormals, mesh->mNumVertices); + WriteFloatArray(geometryId + "-normals", FloatType_Vector, (ai_real *)mesh->mNormals, mesh->mNumVertices); // texture coords for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { if (mesh->HasTextureCoords(static_cast(a))) { - WriteFloatArray(idstr + "-tex" + to_string(a), mesh->mNumUVComponents[a] == 3 ? FloatType_TexCoord3 : FloatType_TexCoord2, + WriteFloatArray(geometryId + "-tex" + to_string(a), mesh->mNumUVComponents[a] == 3 ? FloatType_TexCoord3 : FloatType_TexCoord2, (ai_real *)mesh->mTextureCoords[a], mesh->mNumVertices); } } @@ -1050,7 +1050,7 @@ void ColladaExporter::WriteGeometry(size_t pIndex) { // vertex colors for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { if (mesh->HasVertexColors(static_cast(a))) - WriteFloatArray(idstr + "-color" + to_string(a), FloatType_Color, (ai_real *)mesh->mColors[a], mesh->mNumVertices); + WriteFloatArray(geometryId + "-color" + to_string(a), FloatType_Color, (ai_real *)mesh->mColors[a], mesh->mNumVertices); } // assemble vertex structure @@ -1530,13 +1530,13 @@ void ColladaExporter::WriteNode(const aiScene *pScene, aiNode *pNode) { const std::string node_name = XMLEscape(pNode->mName.data); mOutput << startstr << "" << endstr; PushTag(); @@ -1595,14 +1595,14 @@ void ColladaExporter::WriteNode(const aiScene *pScene, aiNode *pNode) { if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0) continue; - const std::string meshName = mesh->mName.length == 0 ? GetMeshId(pNode->mMeshes[a]) : mesh->mName.C_Str(); + const std::string meshId = GetMeshUniqueId(pNode->mMeshes[a]); if (mesh->mNumBones == 0) { - mOutput << startstr << "" << endstr; + mOutput << startstr << "" << endstr; PushTag(); } else { mOutput << startstr - << "" + << "" << endstr; PushTag(); @@ -1649,5 +1649,59 @@ void ColladaExporter::WriteNode(const aiScene *pScene, aiNode *pNode) { mOutput << startstr << "" << endstr; } +/// Get or Create a unique mesh ID string for the given mesh index +std::string Assimp::ColladaExporter::GetMeshUniqueId(size_t pIndex) { + auto meshId = mMeshIdMap.find(pIndex); + if (meshId != mMeshIdMap.cend()) + return meshId->second; + + // Not seen this mesh before, create and add + return AddMeshIndexToMaps(pIndex, true); +} + +std::string Assimp::ColladaExporter::GetMeshName(size_t pIndex) { + auto meshName = mMeshNameMap.find(pIndex); + if (meshName != mMeshNameMap.cend()) + return meshName->second; + + // Not seen this mesh before, create and add + return AddMeshIndexToMaps(pIndex, false); +} + +inline bool ValueIsUnique(const std::map &map, const std::string &value) { + for (const auto &map_val : map) { + if (value == map_val.second) + return false; + } + return true; +} + +// Add the mesh index to both Id and Name maps and return either Id or Name +std::string Assimp::ColladaExporter::AddMeshIndexToMaps(size_t pIndex, bool meshId) { + const aiMesh *mesh = mScene->mMeshes[pIndex]; + std::string idStr = mesh->mName.length == 0 ? std::string("meshId_") + to_string(pIndex) : XMLIDEncode(mesh->mName.C_Str()); + // Ensure is unique. Relatively slow but will only happen once per mesh + if (!ValueIsUnique(mMeshIdMap, idStr)) { + // Select a number to append + size_t postfix = 1; + idStr.append("_"); + while (!ValueIsUnique(mMeshIdMap, idStr + to_string(postfix))) { + ++postfix; + } + idStr = idStr + to_string(postfix); + } + // Add to map + mMeshIdMap.insert(std::make_pair(pIndex, idStr)); + + // Add name to map + const std::string nameStr = mesh->mName.length == 0 ? idStr : XMLEscape(mesh->mName.C_Str()); + mMeshNameMap.insert(std::make_pair(pIndex, nameStr)); + + if (meshId) + return idStr; + else + return nameStr; +} + #endif #endif diff --git a/code/Collada/ColladaExporter.h b/code/Collada/ColladaExporter.h index fa7e6ee80..d75d2d355 100644 --- a/code/Collada/ColladaExporter.h +++ b/code/Collada/ColladaExporter.h @@ -145,10 +145,14 @@ protected: startstr.erase(startstr.length() - 2); } - /// Creates a mesh ID for the given mesh - std::string GetMeshId(size_t pIndex) const { - return std::string("meshId") + to_string(pIndex); - } + /// Get or Create a unique mesh ID string for the given mesh index + std::string GetMeshUniqueId(size_t pIndex); + std::string GetMeshName(size_t pIndex); + +private: + std::string AddMeshIndexToMaps(size_t pIndex, bool meshId); + mutable std::map mMeshIdMap; // Cache of encoded unique IDs + mutable std::map mMeshNameMap; // Cache of encoded mesh names public: /// Stringstream to write all output into diff --git a/code/Collada/ColladaHelper.h b/code/Collada/ColladaHelper.h index 3eb073cd9..f41691606 100644 --- a/code/Collada/ColladaHelper.h +++ b/code/Collada/ColladaHelper.h @@ -339,11 +339,13 @@ struct SubMesh { /** Contains data for a single mesh */ struct Mesh { - Mesh() { + Mesh(const std::string &id) : + mId(id) { for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) mNumUVComponents[i] = 2; } + const std::string mId; std::string mName; // just to check if there's some sophisticated addressing involved... diff --git a/code/Collada/ColladaLoader.cpp b/code/Collada/ColladaLoader.cpp index 44d65e40d..c76954fdf 100644 --- a/code/Collada/ColladaLoader.cpp +++ b/code/Collada/ColladaLoader.cpp @@ -46,6 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ColladaLoader.h" #include "ColladaParser.h" +#include #include #include #include @@ -265,6 +266,13 @@ aiNode *ColladaLoader::BuildHierarchy(const ColladaParser &pParser, const Collad // find a name for the new node. It's more complicated than you might think node->mName.Set(FindNameForNode(pNode)); + // if we're not using the unique IDs, hold onto them for reference and export + if (useColladaName) { + if (!pNode->mID.empty()) + node->mMetaData->Add(AI_METADATA_COLLADA_ID, aiString(pNode->mID)); + if (!pNode->mSID.empty()) + node->mMetaData->Add(AI_METADATA_COLLADA_SID, aiString(pNode->mSID)); + } // calculate the transformation matrix for it node->mTransformation = pParser.CalculateResultTransform(pNode->mTransforms); @@ -603,7 +611,11 @@ aiMesh *ColladaLoader::CreateMesh(const ColladaParser &pParser, const Collada::M const Collada::Controller *pSrcController, size_t pStartVertex, size_t pStartFace) { std::unique_ptr dstMesh(new aiMesh); - dstMesh->mName = pSrcMesh->mName; + if (useColladaName) { + dstMesh->mName = pSrcMesh->mName; + } else { + dstMesh->mName = pSrcMesh->mId; + } // count the vertices addressed by its faces const size_t numVertices = std::accumulate(pSrcMesh->mFaceSize.begin() + pStartFace, @@ -700,10 +712,10 @@ aiMesh *ColladaLoader::CreateMesh(const ColladaParser &pParser, const Collada::M for (unsigned int i = 0; i < targetData.mStrings.size(); ++i) { const Collada::Mesh *targetMesh = pParser.ResolveLibraryReference(pParser.mMeshLibrary, targetData.mStrings.at(i)); - aiMesh *aimesh = findMesh(targetMesh->mName); + aiMesh *aimesh = findMesh(useColladaName ? targetMesh->mName : targetMesh->mId); if (!aimesh) { if (targetMesh->mSubMeshes.size() > 1) { - throw DeadlyImportError("Morhing target mesh must be a single"); + throw DeadlyImportError("Morphing target mesh must be a single"); } aimesh = CreateMesh(pParser, targetMesh, targetMesh->mSubMeshes.at(0), NULL, 0, 0); mTargetMeshes.push_back(aimesh); diff --git a/code/Collada/ColladaParser.cpp b/code/Collada/ColladaParser.cpp index 5080094c1..d83980929 100644 --- a/code/Collada/ColladaParser.cpp +++ b/code/Collada/ColladaParser.cpp @@ -1716,18 +1716,25 @@ void ColladaParser::ReadGeometryLibrary() { // TODO: (thom) support SIDs // ai_assert( TestAttribute( "sid") == -1); - // create a mesh and store it in the library under its ID - Mesh *mesh = new Mesh; - mMeshLibrary[id] = mesh; + // create a mesh and store it in the library under its (resolved) ID + // Skip and warn if ID is not unique + if (mMeshLibrary.find(id) == mMeshLibrary.cend()) { + std::unique_ptr mesh(new Mesh(id)); - // read the mesh name if it exists - const int nameIndex = TestAttribute("name"); - if (nameIndex != -1) { - mesh->mName = mReader->getAttributeValue(nameIndex); + // read the mesh name if it exists + const int nameIndex = TestAttribute("name"); + if (nameIndex != -1) { + mesh->mName = mReader->getAttributeValue(nameIndex); + } + + // read on from there + ReadGeometry(*mesh); + // Read successfully, add to library + mMeshLibrary.insert({ id, mesh.release() }); + } else { + ASSIMP_LOG_ERROR_F("Collada: Skipped duplicate geometry id: \"", id, "\""); + SkipElement(); } - - // read on from there - ReadGeometry(mesh); } else { // ignore the rest SkipElement(); @@ -1743,7 +1750,7 @@ void ColladaParser::ReadGeometryLibrary() { // ------------------------------------------------------------------------------------------------ // Reads a geometry from the geometry library. -void ColladaParser::ReadGeometry(Collada::Mesh *pMesh) { +void ColladaParser::ReadGeometry(Collada::Mesh &pMesh) { if (mReader->isEmptyElement()) return; @@ -1767,7 +1774,7 @@ void ColladaParser::ReadGeometry(Collada::Mesh *pMesh) { // ------------------------------------------------------------------------------------------------ // Reads a mesh from the geometry library -void ColladaParser::ReadMesh(Mesh *pMesh) { +void ColladaParser::ReadMesh(Mesh &pMesh) { if (mReader->isEmptyElement()) return; @@ -1997,16 +2004,16 @@ void ColladaParser::ReadAccessor(const std::string &pID) { // ------------------------------------------------------------------------------------------------ // Reads input declarations of per-vertex mesh data into the given mesh -void ColladaParser::ReadVertexData(Mesh *pMesh) { +void ColladaParser::ReadVertexData(Mesh &pMesh) { // extract the ID of the element. Not that we care, but to catch strange referencing schemes we should warn about int attrID = GetAttribute("id"); - pMesh->mVertexID = mReader->getAttributeValue(attrID); + pMesh.mVertexID = mReader->getAttributeValue(attrID); // a number of elements while (mReader->read()) { if (mReader->getNodeType() == irr::io::EXN_ELEMENT) { if (IsElement("input")) { - ReadInputChannel(pMesh->mPerVertexData); + ReadInputChannel(pMesh.mPerVertexData); } else { ThrowException(format() << "Unexpected sub element <" << mReader->getNodeName() << "> in tag "); } @@ -2021,7 +2028,7 @@ void ColladaParser::ReadVertexData(Mesh *pMesh) { // ------------------------------------------------------------------------------------------------ // Reads input declarations of per-index mesh data into the given mesh -void ColladaParser::ReadIndexData(Mesh *pMesh) { +void ColladaParser::ReadIndexData(Mesh &pMesh) { std::vector vcount; std::vector perIndexData; @@ -2111,7 +2118,7 @@ void ColladaParser::ReadIndexData(Mesh *pMesh) { // only when we're done reading all

tags (and thus know the final vertex count) can we commit the submesh subgroup.mNumFaces = actualPrimitives; - pMesh->mSubMeshes.push_back(subgroup); + pMesh.mSubMeshes.push_back(subgroup); } // ------------------------------------------------------------------------------------------------ @@ -2158,7 +2165,7 @@ void ColladaParser::ReadInputChannel(std::vector &poChannels) { // ------------------------------------------------------------------------------------------------ // Reads a

primitive index list and assembles the mesh data into the given mesh -size_t ColladaParser::ReadPrimitives(Mesh *pMesh, std::vector &pPerIndexChannels, +size_t ColladaParser::ReadPrimitives(Mesh &pMesh, std::vector &pPerIndexChannels, size_t pNumPrimitives, const std::vector &pVCount, PrimitiveType pPrimType) { // determine number of indices coming per vertex // find the offset index for all per-vertex channels @@ -2220,7 +2227,7 @@ size_t ColladaParser::ReadPrimitives(Mesh *pMesh, std::vector &pPe ThrowException("Expected different index count in

element."); // find the data for all sources - for (std::vector::iterator it = pMesh->mPerVertexData.begin(); it != pMesh->mPerVertexData.end(); ++it) { + for (std::vector::iterator it = pMesh.mPerVertexData.begin(); it != pMesh.mPerVertexData.end(); ++it) { InputChannel &input = *it; if (input.mResolved) continue; @@ -2241,7 +2248,7 @@ size_t ColladaParser::ReadPrimitives(Mesh *pMesh, std::vector &pPe // ignore vertex pointer, it doesn't refer to an accessor if (input.mType == IT_Vertex) { // warn if the vertex channel does not refer to the element in the same mesh - if (input.mAccessor != pMesh->mVertexID) + if (input.mAccessor != pMesh.mVertexID) ThrowException("Unsupported vertex referencing scheme."); continue; } @@ -2268,8 +2275,8 @@ size_t ColladaParser::ReadPrimitives(Mesh *pMesh, std::vector &pPe numPrimitives = numberOfVertices - 1; } - pMesh->mFaceSize.reserve(numPrimitives); - pMesh->mFacePosIndices.reserve(indices.size() / numOffsets); + pMesh.mFaceSize.reserve(numPrimitives); + pMesh.mFacePosIndices.reserve(indices.size() / numOffsets); size_t polylistStartVertex = 0; for (size_t currentPrimitive = 0; currentPrimitive < numPrimitives; currentPrimitive++) { @@ -2314,7 +2321,7 @@ size_t ColladaParser::ReadPrimitives(Mesh *pMesh, std::vector &pPe } // store the face size to later reconstruct the face from - pMesh->mFaceSize.push_back(numPoints); + pMesh.mFaceSize.push_back(numPoints); } // if I ever get my hands on that guy who invented this steaming pile of indirection... @@ -2325,7 +2332,7 @@ size_t ColladaParser::ReadPrimitives(Mesh *pMesh, std::vector &pPe ///@note This function willn't work correctly if both PerIndex and PerVertex channels have same channels. ///For example if TEXCOORD present in both and tags this function will create wrong uv coordinates. ///It's not clear from COLLADA documentation is this allowed or not. For now only exporter fixed to avoid such behavior -void ColladaParser::CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset, Mesh *pMesh, std::vector &pPerIndexChannels, size_t currentPrimitive, const std::vector &indices) { +void ColladaParser::CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset, Mesh &pMesh, std::vector &pPerIndexChannels, size_t currentPrimitive, const std::vector &indices) { // calculate the base offset of the vertex whose attributes we ant to copy size_t baseOffset = currentPrimitive * numOffsets * numPoints + currentVertex * numOffsets; @@ -2333,17 +2340,17 @@ void ColladaParser::CopyVertex(size_t currentVertex, size_t numOffsets, size_t n ai_assert((baseOffset + numOffsets - 1) < indices.size()); // extract per-vertex channels using the global per-vertex offset - for (std::vector::iterator it = pMesh->mPerVertexData.begin(); it != pMesh->mPerVertexData.end(); ++it) + for (std::vector::iterator it = pMesh.mPerVertexData.begin(); it != pMesh.mPerVertexData.end(); ++it) ExtractDataObjectFromChannel(*it, indices[baseOffset + perVertexOffset], pMesh); // and extract per-index channels using there specified offset for (std::vector::iterator it = pPerIndexChannels.begin(); it != pPerIndexChannels.end(); ++it) ExtractDataObjectFromChannel(*it, indices[baseOffset + it->mOffset], pMesh); // store the vertex-data index for later assignment of bone vertex weights - pMesh->mFacePosIndices.push_back(indices[baseOffset + perVertexOffset]); + pMesh.mFacePosIndices.push_back(indices[baseOffset + perVertexOffset]); } -void ColladaParser::ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Mesh *pMesh, std::vector &pPerIndexChannels, size_t currentPrimitive, const std::vector &indices) { +void ColladaParser::ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Mesh &pMesh, std::vector &pPerIndexChannels, size_t currentPrimitive, const std::vector &indices) { if (currentPrimitive % 2 != 0) { //odd tristrip triangles need their indices mangled, to preserve winding direction CopyVertex(1, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices); @@ -2358,7 +2365,7 @@ void ColladaParser::ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, // ------------------------------------------------------------------------------------------------ // Extracts a single object from an input channel and stores it in the appropriate mesh data array -void ColladaParser::ExtractDataObjectFromChannel(const InputChannel &pInput, size_t pLocalIndex, Mesh *pMesh) { +void ColladaParser::ExtractDataObjectFromChannel(const InputChannel &pInput, size_t pLocalIndex, Mesh &pMesh) { // ignore vertex referrer - we handle them that separate if (pInput.mType == IT_Vertex) return; @@ -2380,40 +2387,40 @@ void ColladaParser::ExtractDataObjectFromChannel(const InputChannel &pInput, siz switch (pInput.mType) { case IT_Position: // ignore all position streams except 0 - there can be only one position if (pInput.mIndex == 0) - pMesh->mPositions.push_back(aiVector3D(obj[0], obj[1], obj[2])); + pMesh.mPositions.push_back(aiVector3D(obj[0], obj[1], obj[2])); else ASSIMP_LOG_ERROR("Collada: just one vertex position stream supported"); break; case IT_Normal: // pad to current vertex count if necessary - if (pMesh->mNormals.size() < pMesh->mPositions.size() - 1) - pMesh->mNormals.insert(pMesh->mNormals.end(), pMesh->mPositions.size() - pMesh->mNormals.size() - 1, aiVector3D(0, 1, 0)); + if (pMesh.mNormals.size() < pMesh.mPositions.size() - 1) + pMesh.mNormals.insert(pMesh.mNormals.end(), pMesh.mPositions.size() - pMesh.mNormals.size() - 1, aiVector3D(0, 1, 0)); // ignore all normal streams except 0 - there can be only one normal if (pInput.mIndex == 0) - pMesh->mNormals.push_back(aiVector3D(obj[0], obj[1], obj[2])); + pMesh.mNormals.push_back(aiVector3D(obj[0], obj[1], obj[2])); else ASSIMP_LOG_ERROR("Collada: just one vertex normal stream supported"); break; case IT_Tangent: // pad to current vertex count if necessary - if (pMesh->mTangents.size() < pMesh->mPositions.size() - 1) - pMesh->mTangents.insert(pMesh->mTangents.end(), pMesh->mPositions.size() - pMesh->mTangents.size() - 1, aiVector3D(1, 0, 0)); + if (pMesh.mTangents.size() < pMesh.mPositions.size() - 1) + pMesh.mTangents.insert(pMesh.mTangents.end(), pMesh.mPositions.size() - pMesh.mTangents.size() - 1, aiVector3D(1, 0, 0)); // ignore all tangent streams except 0 - there can be only one tangent if (pInput.mIndex == 0) - pMesh->mTangents.push_back(aiVector3D(obj[0], obj[1], obj[2])); + pMesh.mTangents.push_back(aiVector3D(obj[0], obj[1], obj[2])); else ASSIMP_LOG_ERROR("Collada: just one vertex tangent stream supported"); break; case IT_Bitangent: // pad to current vertex count if necessary - if (pMesh->mBitangents.size() < pMesh->mPositions.size() - 1) - pMesh->mBitangents.insert(pMesh->mBitangents.end(), pMesh->mPositions.size() - pMesh->mBitangents.size() - 1, aiVector3D(0, 0, 1)); + if (pMesh.mBitangents.size() < pMesh.mPositions.size() - 1) + pMesh.mBitangents.insert(pMesh.mBitangents.end(), pMesh.mPositions.size() - pMesh.mBitangents.size() - 1, aiVector3D(0, 0, 1)); // ignore all bitangent streams except 0 - there can be only one bitangent if (pInput.mIndex == 0) - pMesh->mBitangents.push_back(aiVector3D(obj[0], obj[1], obj[2])); + pMesh.mBitangents.push_back(aiVector3D(obj[0], obj[1], obj[2])); else ASSIMP_LOG_ERROR("Collada: just one vertex bitangent stream supported"); break; @@ -2421,13 +2428,13 @@ void ColladaParser::ExtractDataObjectFromChannel(const InputChannel &pInput, siz // up to 4 texture coord sets are fine, ignore the others if (pInput.mIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS) { // pad to current vertex count if necessary - if (pMesh->mTexCoords[pInput.mIndex].size() < pMesh->mPositions.size() - 1) - pMesh->mTexCoords[pInput.mIndex].insert(pMesh->mTexCoords[pInput.mIndex].end(), - pMesh->mPositions.size() - pMesh->mTexCoords[pInput.mIndex].size() - 1, aiVector3D(0, 0, 0)); + if (pMesh.mTexCoords[pInput.mIndex].size() < pMesh.mPositions.size() - 1) + pMesh.mTexCoords[pInput.mIndex].insert(pMesh.mTexCoords[pInput.mIndex].end(), + pMesh.mPositions.size() - pMesh.mTexCoords[pInput.mIndex].size() - 1, aiVector3D(0, 0, 0)); - pMesh->mTexCoords[pInput.mIndex].push_back(aiVector3D(obj[0], obj[1], obj[2])); + pMesh.mTexCoords[pInput.mIndex].push_back(aiVector3D(obj[0], obj[1], obj[2])); if (0 != acc.mSubOffset[2] || 0 != acc.mSubOffset[3]) /* hack ... consider cleaner solution */ - pMesh->mNumUVComponents[pInput.mIndex] = 3; + pMesh.mNumUVComponents[pInput.mIndex] = 3; } else { ASSIMP_LOG_ERROR("Collada: too many texture coordinate sets. Skipping."); } @@ -2436,15 +2443,15 @@ void ColladaParser::ExtractDataObjectFromChannel(const InputChannel &pInput, siz // up to 4 color sets are fine, ignore the others if (pInput.mIndex < AI_MAX_NUMBER_OF_COLOR_SETS) { // pad to current vertex count if necessary - if (pMesh->mColors[pInput.mIndex].size() < pMesh->mPositions.size() - 1) - pMesh->mColors[pInput.mIndex].insert(pMesh->mColors[pInput.mIndex].end(), - pMesh->mPositions.size() - pMesh->mColors[pInput.mIndex].size() - 1, aiColor4D(0, 0, 0, 1)); + if (pMesh.mColors[pInput.mIndex].size() < pMesh.mPositions.size() - 1) + pMesh.mColors[pInput.mIndex].insert(pMesh.mColors[pInput.mIndex].end(), + pMesh.mPositions.size() - pMesh.mColors[pInput.mIndex].size() - 1, aiColor4D(0, 0, 0, 1)); aiColor4D result(0, 0, 0, 1); for (size_t i = 0; i < pInput.mResolved->mSize; ++i) { result[static_cast(i)] = obj[pInput.mResolved->mSubOffset[i]]; } - pMesh->mColors[pInput.mIndex].push_back(result); + pMesh.mColors[pInput.mIndex].push_back(result); } else { ASSIMP_LOG_ERROR("Collada: too many vertex color sets. Skipping."); } diff --git a/code/Collada/ColladaParser.h b/code/Collada/ColladaParser.h index 861a65256..a84a59354 100644 --- a/code/Collada/ColladaParser.h +++ b/code/Collada/ColladaParser.h @@ -174,10 +174,10 @@ protected: void ReadGeometryLibrary(); /** Reads a geometry from the geometry library. */ - void ReadGeometry(Collada::Mesh *pMesh); + void ReadGeometry(Collada::Mesh &pMesh); /** Reads a mesh from the geometry library */ - void ReadMesh(Collada::Mesh *pMesh); + void ReadMesh(Collada::Mesh &pMesh); /** Reads a source element - a combination of raw data and an accessor defining * things that should not be redefinable. Yes, that's another rant. @@ -195,29 +195,29 @@ protected: void ReadAccessor(const std::string &pID); /** Reads input declarations of per-vertex mesh data into the given mesh */ - void ReadVertexData(Collada::Mesh *pMesh); + void ReadVertexData(Collada::Mesh &pMesh); /** Reads input declarations of per-index mesh data into the given mesh */ - void ReadIndexData(Collada::Mesh *pMesh); + void ReadIndexData(Collada::Mesh &pMesh); /** Reads a single input channel element and stores it in the given array, if valid */ void ReadInputChannel(std::vector &poChannels); /** Reads a

primitive index list and assembles the mesh data into the given mesh */ - size_t ReadPrimitives(Collada::Mesh *pMesh, std::vector &pPerIndexChannels, + size_t ReadPrimitives(Collada::Mesh &pMesh, std::vector &pPerIndexChannels, size_t pNumPrimitives, const std::vector &pVCount, Collada::PrimitiveType pPrimType); /** Copies the data for a single primitive into the mesh, based on the InputChannels */ void CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset, - Collada::Mesh *pMesh, std::vector &pPerIndexChannels, + Collada::Mesh &pMesh, std::vector &pPerIndexChannels, size_t currentPrimitive, const std::vector &indices); /** Reads one triangle of a tristrip into the mesh */ - void ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Collada::Mesh *pMesh, + void ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Collada::Mesh &pMesh, std::vector &pPerIndexChannels, size_t currentPrimitive, const std::vector &indices); /** Extracts a single object from an input channel and stores it in the appropriate mesh data array */ - void ExtractDataObjectFromChannel(const Collada::InputChannel &pInput, size_t pLocalIndex, Collada::Mesh *pMesh); + void ExtractDataObjectFromChannel(const Collada::InputChannel &pInput, size_t pLocalIndex, Collada::Mesh &pMesh); /** Reads the library of node hierarchies and scene parts */ void ReadSceneLibrary(); diff --git a/include/assimp/ColladaMetaData.h b/include/assimp/ColladaMetaData.h new file mode 100644 index 000000000..4288692c6 --- /dev/null +++ b/include/assimp/ColladaMetaData.h @@ -0,0 +1,53 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2020, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file ColladaMetaData.h + * Declares common metadata constants used by Collada files + */ +#pragma once +#ifndef AI_COLLADAMETADATA_H_INC +#define AI_COLLADAMETADATA_H_INC + +#define AI_METADATA_COLLADA_ID "COLLADA_ID" +#define AI_METADATA_COLLADA_SID "COLLADA_SID" + +#endif diff --git a/include/assimp/config.h.in b/include/assimp/config.h.in index e2f2a3888..c26dcc77f 100644 --- a/include/assimp/config.h.in +++ b/include/assimp/config.h.in @@ -1030,10 +1030,10 @@ enum aiComponent #define AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION "IMPORT_COLLADA_IGNORE_UP_DIRECTION" // --------------------------------------------------------------------------- -/** @brief Specifies whether the Collada loader should use Collada names as node names. +/** @brief Specifies whether the Collada loader should use Collada names. * - * If this property is set to true, the Collada names will be used as the - * node name. The default is to use the id tag (resp. sid tag, if no id tag is present) + * If this property is set to true, the Collada names will be used as the node and + * mesh names. The default is to use the id tag (resp. sid tag, if no id tag is present) * instead. * Property type: Bool. Default value: false. */ diff --git a/test/unit/utColladaImportExport.cpp b/test/unit/utColladaImportExport.cpp index 549ff68fb..5c2f801db 100644 --- a/test/unit/utColladaImportExport.cpp +++ b/test/unit/utColladaImportExport.cpp @@ -44,13 +44,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include using namespace Assimp; class utColladaImportExport : public AbstractImportExportBase { public: - virtual bool importerTest() { + virtual bool importerTest() final { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure); if (scene == nullptr) @@ -80,15 +81,61 @@ public: return true; } + + void ImportAndCheckIds(const char *file, size_t meshCount) { + // Import the Collada using the 'default' where mesh names are the ids + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(file, aiProcess_ValidateDataStructure); + ASSERT_TRUE(scene != nullptr) << "Fatal: could not re-import " << file; + EXPECT_EQ(meshCount, scene->mNumMeshes) << "in " << file; + + // Check the mesh ids are unique + std::map meshNameMap; + for (size_t idx = 0; idx < scene->mNumMeshes; ++idx) { + std::string meshName(scene->mMeshes[idx]->mName.C_Str()); + const auto result = meshNameMap.insert(std::make_pair(meshName, idx)); + EXPECT_TRUE(result.second) << "Duplicate name: " << meshName << " index " << result.first->second; + } + } }; -TEST_F(utColladaImportExport, importBlenFromFileTest) { +TEST_F(utColladaImportExport, importDaeFromFileTest) { EXPECT_TRUE(importerTest()); } +TEST_F(utColladaImportExport, exporterUniqueIdsTest) { + Assimp::Importer importer; + Assimp::Exporter exporter; + const char *outFileEmpty = "exportMeshIdTest_empty_out.dae"; + const char *outFileNamed = "exportMeshIdTest_named_out.dae"; + + // Load a sample file containing multiple meshes + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/teapots.DAE", aiProcess_ValidateDataStructure); + + ASSERT_TRUE(scene != nullptr) << "Fatal: could not import teapots.DAE!"; + ASSERT_EQ(3u, scene->mNumMeshes) << "Fatal: teapots.DAE initial load failed"; + + // Clear the mesh names + for (size_t idx = 0; idx < scene->mNumMeshes; ++idx) { + scene->mMeshes[idx]->mName.Clear(); + } + ASSERT_EQ(AI_SUCCESS, exporter.Export(scene, "collada", outFileEmpty)) << "Fatal: Could not export un-named meshes file"; + + ImportAndCheckIds(outFileEmpty, 3); + + // Force the meshes to have the same non-empty name + aiString testName("test_mesh"); + for (size_t idx = 0; idx < scene->mNumMeshes; ++idx) { + scene->mMeshes[idx]->mName = testName; + } + ASSERT_EQ(AI_SUCCESS, exporter.Export(scene, "collada", outFileNamed)) << "Fatal: Could not export named meshes file"; + + ImportAndCheckIds(outFileNamed, 3); +} + class utColladaZaeImportExport : public AbstractImportExportBase { public: - virtual bool importerTest() { + virtual bool importerTest() final { { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.zae", aiProcess_ValidateDataStructure);