123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- /*
- ---------------------------------------------------------------------------
- Open Asset Import Library (assimp)
- ---------------------------------------------------------------------------
- Copyright (c) 2006-2025, 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.
- ---------------------------------------------------------------------------
- */
- #include "AbstractImportExportBase.h"
- #include "UnitTestPCH.h"
- #include <assimp/ColladaMetaData.h>
- #include <assimp/SceneCombiner.h>
- #include <assimp/commonMetaData.h>
- #include <assimp/postprocess.h>
- #include <assimp/scene.h>
- #include <assimp/Exporter.hpp>
- #include <assimp/Importer.hpp>
- using namespace Assimp;
- class utColladaImportExport : public AbstractImportExportBase {
- public:
- // Clones the scene in an exception-safe way
- struct SceneCloner {
- SceneCloner(const aiScene *scene) {
- sceneCopy = nullptr;
- SceneCombiner::CopyScene(&sceneCopy, scene);
- }
- ~SceneCloner() {
- delete sceneCopy;
- sceneCopy = nullptr;
- }
- aiScene *sceneCopy;
- };
- virtual bool importerTest() final {
- Assimp::Importer importer;
- {
- const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure);
- if (scene == nullptr)
- return false;
- // Expected number of items
- EXPECT_EQ(scene->mNumMeshes, 1u);
- EXPECT_EQ(scene->mNumMaterials, 1u);
- EXPECT_EQ(scene->mNumAnimations, 0u);
- EXPECT_EQ(scene->mNumTextures, 0u);
- EXPECT_EQ(scene->mNumLights, 1u);
- EXPECT_EQ(scene->mNumCameras, 1u);
- // Expected common metadata
- aiString value;
- EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT, value)) << "No importer format metadata";
- EXPECT_STREQ("Collada Importer", value.C_Str());
- EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT_VERSION, value)) << "No format version metadata";
- EXPECT_STREQ("1.4.1", value.C_Str());
- EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_GENERATOR, value)) << "No generator metadata";
- EXPECT_EQ(strncmp(value.C_Str(), "Maya 8.0", 8), 0) << "AI_METADATA_SOURCE_GENERATOR was: " << value.C_Str();
- EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_COPYRIGHT, value)) << "No copyright metadata";
- EXPECT_EQ(strncmp(value.C_Str(), "Copyright 2006", 14), 0) << "AI_METADATA_SOURCE_COPYRIGHT was: " << value.C_Str();
- }
- {
- const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/box_nested_animation.dae", aiProcess_ValidateDataStructure);
- if (scene == nullptr)
- return false;
- // Expect only one animation with the correct name
- EXPECT_EQ(scene->mNumAnimations, 1u);
- EXPECT_EQ(std::string(scene->mAnimations[0]->mName.C_Str()), std::string("Armature"));
- }
- return true;
- }
- typedef std::pair<std::string, std::string> IdNameString;
- typedef std::map<std::string, std::string> IdNameMap;
- template <typename T>
- static inline IdNameString GetColladaIdName(const T *item, size_t index) {
- std::ostringstream stream;
- stream << typeid(T).name() << "@" << index;
- if (item->mMetaData) {
- aiString aiStr;
- if (item->mMetaData->Get(AI_METADATA_COLLADA_ID, aiStr))
- return std::make_pair(std::string(aiStr.C_Str()), stream.str());
- }
- return std::make_pair(std::string(), stream.str());
- }
- template <typename T>
- static inline IdNameString GetItemIdName(const T *item, size_t index) {
- std::ostringstream stream;
- stream << typeid(T).name() << "@" << index;
- return std::make_pair(std::string(item->mName.C_Str()), stream.str());
- }
- // Specialisations
- static inline IdNameString GetItemIdName(aiMaterial *item, size_t index) {
- std::ostringstream stream;
- stream << typeid(aiMaterial).name() << "@" << index;
- return std::make_pair(std::string(item->GetName().C_Str()), stream.str());
- }
- static inline IdNameString GetItemIdName(aiTexture *item, size_t index) {
- std::ostringstream stream;
- stream << typeid(aiTexture).name() << "@" << index;
- return std::make_pair(std::string(item->mFilename.C_Str()), stream.str());
- }
- static inline void ReportDuplicate(IdNameMap &itemIdMap, const IdNameString &namePair, const char *typeNameStr) {
- const auto result = itemIdMap.insert(namePair);
- EXPECT_TRUE(result.second) << "Duplicate '" << typeNameStr << "' name: '" << namePair.first << "'. " << namePair.second << " == " << result.first->second;
- }
- template <typename T>
- static inline void CheckUniqueIds(IdNameMap &itemIdMap, unsigned int itemCount, T **itemArray) {
- for (size_t idx = 0; idx < itemCount; ++idx) {
- IdNameString namePair = GetItemIdName(itemArray[idx], idx);
- ReportDuplicate(itemIdMap, namePair, typeid(T).name());
- }
- }
- static inline void CheckUniqueIds(IdNameMap &itemIdMap, const aiNode *parent, size_t index) {
- IdNameString namePair = GetItemIdName(parent, index);
- ReportDuplicate(itemIdMap, namePair, typeid(aiNode).name());
- for (size_t idx = 0; idx < parent->mNumChildren; ++idx) {
- CheckUniqueIds(itemIdMap, parent->mChildren[idx], idx);
- }
- }
- static inline void CheckNodeIdNames(IdNameMap &nodeIdMap, IdNameMap &nodeNameMap, const aiNode *parent, size_t index) {
- IdNameString namePair = GetItemIdName(parent, index);
- IdNameString idPair = GetColladaIdName(parent, index);
- ReportDuplicate(nodeIdMap, idPair, typeid(aiNode).name());
- for (size_t idx = 0; idx < parent->mNumChildren; ++idx) {
- CheckNodeIdNames(nodeIdMap, nodeNameMap, parent->mChildren[idx], idx);
- }
- }
- static inline void SetAllNodeNames(const aiString &newName, aiNode *node) {
- node->mName = newName;
- for (size_t idx = 0; idx < node->mNumChildren; ++idx) {
- SetAllNodeNames(newName, node->mChildren[idx]);
- }
- }
- void ImportAndCheckIds(const char *file, const aiScene *origScene) {
- // Import the Collada using the 'default' where aiNode and aiMesh names are the Collada ids
- Assimp::Importer importer;
- const aiScene *scene = importer.ReadFile(file, aiProcess_ValidateDataStructure);
- ASSERT_TRUE(scene != nullptr) << "Fatal: could not re-import " << file;
- EXPECT_EQ(origScene->mNumMeshes, scene->mNumMeshes) << "in " << file;
- // Check the ids are unique
- IdNameMap itemIdMap;
- // Recurse the Nodes
- CheckUniqueIds(itemIdMap, scene->mRootNode, 0);
- // Check the lists
- CheckUniqueIds(itemIdMap, scene->mNumMeshes, scene->mMeshes);
- // The remaining will come in using the name, which may not be unique
- // Check we have the right number
- EXPECT_EQ(origScene->mNumAnimations, scene->mNumAnimations);
- EXPECT_EQ(origScene->mNumMaterials, scene->mNumMaterials);
- EXPECT_EQ(origScene->mNumTextures, scene->mNumTextures);
- EXPECT_EQ(origScene->mNumLights, scene->mNumLights);
- EXPECT_EQ(origScene->mNumCameras, scene->mNumCameras);
- }
- void ImportAsNames(const char *file, const aiScene *origScene) {
- // Import the Collada but using the user-visible names for aiNode and aiMesh
- // Note that this mode may not support bones or animations
- Assimp::Importer importer;
- importer.SetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES, 1);
- const aiScene *scene = importer.ReadFile(file, aiProcess_ValidateDataStructure);
- ASSERT_TRUE(scene != nullptr) << "Fatal: could not re-import " << file;
- EXPECT_EQ(origScene->mNumMeshes, scene->mNumMeshes) << "in " << file;
- // Check the node ids are unique but the node names are not
- IdNameMap nodeIdMap;
- IdNameMap nodeNameMap;
- // Recurse the Nodes
- CheckNodeIdNames(nodeIdMap, nodeNameMap, scene->mRootNode, 0);
- // nodeNameMap should have fewer than nodeIdMap
- EXPECT_LT(nodeNameMap.size(), nodeIdMap.size()) << "Some nodes should have the same names";
- // Check the counts haven't changed
- EXPECT_EQ(origScene->mNumAnimations, scene->mNumAnimations);
- EXPECT_EQ(origScene->mNumMaterials, scene->mNumMaterials);
- EXPECT_EQ(origScene->mNumTextures, scene->mNumTextures);
- EXPECT_EQ(origScene->mNumLights, scene->mNumLights);
- EXPECT_EQ(origScene->mNumCameras, scene->mNumCameras);
- }
- };
- TEST_F(utColladaImportExport, importDaeFromFileTest) {
- EXPECT_TRUE(importerTest());
- }
- unsigned int GetMeshUseCount(const aiNode *rootNode) {
- unsigned int result = rootNode->mNumMeshes;
- for (unsigned int i = 0; i < rootNode->mNumChildren; ++i) {
- result += GetMeshUseCount(rootNode->mChildren[i]);
- }
- return result;
- }
- #ifndef ASSIMP_BUILD_NO_EXPORT
- TEST_F(utColladaImportExport, exportRootNodeMeshTest) {
- Assimp::Importer importer;
- Assimp::Exporter exporter;
- const char *outFile = "exportRootNodeMeshTest_out.dae";
- const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure);
- ASSERT_TRUE(scene != nullptr) << "Fatal: could not import duck.dae!";
- ASSERT_EQ(0u, scene->mRootNode->mNumMeshes) << "Collada import should not give the root node a mesh";
- {
- SceneCloner clone(scene);
- ASSERT_TRUE(clone.sceneCopy != nullptr) << "Fatal: could not copy scene!";
- // Do this by moving the meshes from the first child that has some
- aiNode *rootNode = clone.sceneCopy->mRootNode;
- ASSERT_TRUE(rootNode->mNumChildren > 0) << "Fatal: root has no children";
- aiNode *meshNode = rootNode->mChildren[0];
- ASSERT_EQ(1u, meshNode->mNumMeshes) << "Fatal: First child node has no duck mesh";
- // Move the meshes to the parent
- rootNode->mNumMeshes = meshNode->mNumMeshes;
- rootNode->mMeshes = new unsigned int[rootNode->mNumMeshes];
- for (unsigned int i = 0; i < rootNode->mNumMeshes; ++i) {
- rootNode->mMeshes[i] = meshNode->mMeshes[i];
- }
- // Remove the meshes from the original node
- meshNode->mNumMeshes = 0;
- delete[] meshNode->mMeshes;
- meshNode->mMeshes = nullptr;
- ASSERT_EQ(AI_SUCCESS, exporter.Export(clone.sceneCopy, "collada", outFile)) << "Fatal: Could not export file";
- }
- // Reimport and look for meshes
- scene = importer.ReadFile(outFile, aiProcess_ValidateDataStructure);
- ASSERT_TRUE(scene != nullptr) << "Fatal: could not reimport!";
- // A Collada root node is not allowed to have a mesh
- ASSERT_EQ(0u, scene->mRootNode->mNumMeshes) << "Collada reimport should not give the root node a mesh";
- // Walk nodes and counts used meshes
- // Should be exactly one
- EXPECT_EQ(1u, GetMeshUseCount(scene->mRootNode)) << "Nodes had unexpected number of meshes in use";
- }
- 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 all the names
- for (size_t idx = 0; idx < scene->mNumMeshes; ++idx) {
- scene->mMeshes[idx]->mName.Clear();
- }
- for (size_t idx = 0; idx < scene->mNumMaterials; ++idx) {
- scene->mMaterials[idx]->RemoveProperty(AI_MATKEY_NAME);
- }
- for (size_t idx = 0; idx < scene->mNumAnimations; ++idx) {
- scene->mAnimations[idx]->mName.Clear();
- }
- // Can't clear texture names
- for (size_t idx = 0; idx < scene->mNumLights; ++idx) {
- scene->mLights[idx]->mName.Clear();
- }
- for (size_t idx = 0; idx < scene->mNumCameras; ++idx) {
- scene->mCameras[idx]->mName.Clear();
- }
- SetAllNodeNames(aiString(), scene->mRootNode);
- ASSERT_EQ(AI_SUCCESS, exporter.Export(scene, "collada", outFileEmpty)) << "Fatal: Could not export un-named meshes file";
- ImportAndCheckIds(outFileEmpty, scene);
- // Force everything to have the same non-empty name
- aiString testName("test_name");
- for (size_t idx = 0; idx < scene->mNumMeshes; ++idx) {
- scene->mMeshes[idx]->mName = testName;
- }
- for (size_t idx = 0; idx < scene->mNumMaterials; ++idx) {
- scene->mMaterials[idx]->AddProperty(&testName, AI_MATKEY_NAME);
- }
- for (size_t idx = 0; idx < scene->mNumAnimations; ++idx) {
- scene->mAnimations[idx]->mName = testName;
- }
- // Can't clear texture names
- for (size_t idx = 0; idx < scene->mNumLights; ++idx) {
- scene->mLights[idx]->mName = testName;
- }
- for (size_t idx = 0; idx < scene->mNumCameras; ++idx) {
- scene->mCameras[idx]->mName = testName;
- }
- SetAllNodeNames(testName, scene->mRootNode);
- ASSERT_EQ(AI_SUCCESS, exporter.Export(scene, "collada", outFileNamed)) << "Fatal: Could not export named meshes file";
- ImportAndCheckIds(outFileNamed, scene);
- ImportAsNames(outFileNamed, scene);
- }
- // This file is invalid, we just want to ensure that the importer is not crashing
- // This was reported as GH#4286. The "count" parameter in "Cube-mesh-positions-array" is too small.
- TEST_F(utColladaImportExport, parseInvalid4286) {
- Assimp::Importer importer;
- const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/invalid/box_nested_animation_4286.dae", 0);
- EXPECT_EQ(nullptr, scene);
- }
- #endif
- class utColladaZaeImportExport : public AbstractImportExportBase {
- public:
- virtual bool importerTest() final {
- {
- Assimp::Importer importer;
- const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.zae", aiProcess_ValidateDataStructure);
- if (scene == nullptr)
- return false;
- // Expected number of items
- EXPECT_EQ(scene->mNumMeshes, 1u);
- EXPECT_EQ(scene->mNumMaterials, 1u);
- EXPECT_EQ(scene->mNumAnimations, 0u);
- //EXPECT_EQ(scene->mNumTextures, 1u);
- EXPECT_EQ(scene->mNumLights, 1u);
- EXPECT_EQ(scene->mNumCameras, 1u);
- }
- {
- Assimp::Importer importer;
- const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck_nomanifest.zae", aiProcess_ValidateDataStructure);
- if (scene == nullptr)
- return false;
- // Expected number of items
- EXPECT_EQ(scene->mNumMeshes, 1u);
- EXPECT_EQ(scene->mNumMaterials, 1u);
- EXPECT_EQ(scene->mNumAnimations, 0u);
- //EXPECT_EQ(scene->mNumTextures, 1u);
- EXPECT_EQ(scene->mNumLights, 1u);
- EXPECT_EQ(scene->mNumCameras, 1u);
- }
- return true;
- }
- };
- TEST_F(utColladaZaeImportExport, importBlenFromFileTest) {
- EXPECT_TRUE(importerTest());
- }
- TEST_F(utColladaZaeImportExport, importMakeHumanTest) {
- Assimp::Importer importer;
- const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/human.zae", aiProcess_ValidateDataStructure);
- ASSERT_NE(nullptr, scene);
- // Expected number of items
- EXPECT_EQ(scene->mNumMeshes, 2u);
- EXPECT_EQ(scene->mNumMaterials, 2u);
- EXPECT_EQ(scene->mNumAnimations, 0u);
- EXPECT_EQ(scene->mNumTextures, 2u);
- EXPECT_EQ(scene->mNumLights, 0u);
- EXPECT_EQ(scene->mNumCameras, 0u);
- // Expected common metadata
- aiString value;
- EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT, value)) << "No importer format metadata";
- EXPECT_STREQ("Collada Importer", value.C_Str());
- EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT_VERSION, value)) << "No format version metadata";
- EXPECT_STREQ("1.4.1", value.C_Str());
- }
|