diff --git a/.github/workflows/sanitizer.yml b/.github/workflows/sanitizer.yml index b23f4520f..483ee8fc1 100644 --- a/.github/workflows/sanitizer.yml +++ b/.github/workflows/sanitizer.yml @@ -46,7 +46,7 @@ jobs: CC: clang - name: configure and build - uses: lukka/run-cmake@v2 + uses: lukka/run-cmake@v3 with: cmakeListsOrSettingsJson: CMakeListsTxtAdvanced cmakeListsTxtPath: '${{ github.workspace }}/CMakeLists.txt' diff --git a/CMakeLists.txt b/CMakeLists.txt index 224f6e48f..dfaa2b791 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -286,9 +286,9 @@ IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT MINGW) ELSEIF(MSVC) # enable multi-core compilation with MSVC IF(CMAKE_CXX_COMPILER_ID MATCHES "Clang" ) # clang-cl - ADD_COMPILE_OPTIONS(/bigobj /W4 /WX ) + ADD_COMPILE_OPTIONS(/bigobj) ELSE() # msvc - ADD_COMPILE_OPTIONS(/MP /bigobj /W4 /WX) + ADD_COMPILE_OPTIONS(/MP /bigobj) ENDIF() # disable "elements of array '' will be default initialized" warning on MSVC2013 diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index f7fa9cd42..d4568fa31 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -730,7 +730,7 @@ bool glTF2Exporter::GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlo bool glTF2Exporter::GetMatSpecular(const aiMaterial &mat, glTF2::MaterialSpecular &specular) { // Specular requires either/or, default factors of zero disables specular, so do not export - if (GetMatColor(mat, specular.specularColorFactor, AI_MATKEY_COLOR_SPECULAR) != AI_SUCCESS || mat.Get(AI_MATKEY_SPECULAR_FACTOR, specular.specularFactor) != AI_SUCCESS) { + if (GetMatColor(mat, specular.specularColorFactor, AI_MATKEY_COLOR_SPECULAR) != AI_SUCCESS && mat.Get(AI_MATKEY_SPECULAR_FACTOR, specular.specularFactor) != AI_SUCCESS) { return false; } // The spec states that the default is 1.0 and [1.0, 1.0, 1.0]. We if both are 0, which should disable specular. Otherwise, if one is 0, set to 1.0 diff --git a/code/Common/BaseImporter.cpp b/code/Common/BaseImporter.cpp index 8f8e41d39..d2ff4a9dd 100644 --- a/code/Common/BaseImporter.cpp +++ b/code/Common/BaseImporter.cpp @@ -59,6 +59,31 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +namespace { +// Checks whether the passed string is a gcs version. +bool IsGcsVersion(const std::string &s) { + if (s.empty()) return false; + return std::all_of(s.cbegin(), s.cend(), [](const char c) { + // gcs only permits numeric characters. + return std::isdigit(static_cast(c)); + }); +} + +// Removes a possible version hash from a filename, as found for example in +// gcs uris (e.g. `gs://bucket/model.glb#1234`), see also +// https://github.com/GoogleCloudPlatform/gsutil/blob/c80f329bc3c4011236c78ce8910988773b2606cb/gslib/storage_url.py#L39. +std::string StripVersionHash(const std::string &filename) { + const std::string::size_type pos = filename.find_last_of('#'); + // Only strip if the hash is behind a possible file extension and the part + // behind the hash is a version string. + if (pos != std::string::npos && pos > filename.find_last_of('.') && + IsGcsVersion(filename.substr(pos + 1))) { + return filename.substr(0, pos); + } + return filename; +} +} // namespace + using namespace Assimp; // ------------------------------------------------------------------------------------------------ @@ -230,33 +255,38 @@ void BaseImporter::GetExtensionList(std::set &extensions) { const char *ext0, const char *ext1, const char *ext2) { - std::string::size_type pos = pFile.find_last_of('.'); - - // no file extension - can't read - if (pos == std::string::npos) { - return false; + std::set extensions; + for (const char* ext : {ext0, ext1, ext2}) { + if (ext == nullptr) continue; + extensions.emplace(ext); } + return HasExtension(pFile, extensions); +} - const char *ext_real = &pFile[pos + 1]; - if (!ASSIMP_stricmp(ext_real, ext0)) { - return true; +// ------------------------------------------------------------------------------------------------ +// Check for file extension +/*static*/ bool BaseImporter::HasExtension(const std::string &pFile, const std::set &extensions) { + const std::string file = StripVersionHash(pFile); + // CAUTION: Do not just search for the extension! + // GetExtension() returns the part after the *last* dot, but some extensions + // have dots inside them, e.g. ogre.mesh.xml. Compare the entire end of the + // string. + for (const std::string& ext : extensions) { + // Yay for C++<20 not having std::string::ends_with() + const std::string dotExt = "." + ext; + if (dotExt.length() > file.length()) continue; + // Possible optimization: Fetch the lowercase filename! + if (0 == ASSIMP_stricmp(file.c_str() + file.length() - dotExt.length(), dotExt.c_str())) { + return true; + } } - - // check for other, optional, file extensions - if (ext1 && !ASSIMP_stricmp(ext_real, ext1)) { - return true; - } - - if (ext2 && !ASSIMP_stricmp(ext_real, ext2)) { - return true; - } - return false; } // ------------------------------------------------------------------------------------------------ // Get file extension from path -std::string BaseImporter::GetExtension(const std::string &file) { +std::string BaseImporter::GetExtension(const std::string &pFile) { + const std::string file = StripVersionHash(pFile); std::string::size_type pos = file.find_last_of('.'); // no file extension at all diff --git a/code/Common/Importer.cpp b/code/Common/Importer.cpp index b66059397..bdf64ac8f 100644 --- a/code/Common/Importer.cpp +++ b/code/Common/Importer.cpp @@ -637,24 +637,10 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags) { std::set extensions; pimpl->mImporter[a]->GetExtensionList(extensions); - // CAUTION: Do not just search for the extension! - // GetExtension() returns the part after the *last* dot, but some extensions have dots - // inside them, e.g. ogre.mesh.xml. Compare the entire end of the string. - for (std::set::const_iterator it = extensions.cbegin(); it != extensions.cend(); ++it) { - - // Yay for C++<20 not having std::string::ends_with() - std::string extension = "." + *it; - if (extension.length() <= pFile.length()) { - // Possible optimization: Fetch the lowercase filename! - if (0 == ASSIMP_stricmp(pFile.c_str() + pFile.length() - extension.length(), extension.c_str())) { - ImporterAndIndex candidate = { pimpl->mImporter[a], a }; - possibleImporters.push_back(candidate); - break; - } - } - + if (BaseImporter::HasExtension(pFile, extensions)) { + ImporterAndIndex candidate = { pimpl->mImporter[a], a }; + possibleImporters.push_back(candidate); } - } // If just one importer supports this extension, pick it and close the case. diff --git a/include/assimp/BaseImporter.h b/include/assimp/BaseImporter.h index 42b537b91..d0b173171 100644 --- a/include/assimp/BaseImporter.h +++ b/include/assimp/BaseImporter.h @@ -275,6 +275,16 @@ public: // static utilities const char *ext1 = nullptr, const char *ext2 = nullptr); + // ------------------------------------------------------------------- + /** @brief Check whether a file has one of the passed file extensions + * @param pFile Input file + * @param extensions Extensions to check for. Lowercase characters only, no dot! + * @note Case-insensitive + */ + static bool HasExtension( + const std::string &pFile, + const std::set &extensions); + // ------------------------------------------------------------------- /** @brief Extract file extension from a string * @param pFile Input file diff --git a/test/models/IRR/scenegraphAnim.irr b/test/models/IRR/scenegraphAnim.irr index 1c6fd0ef4..2300ea18f 100644 --- a/test/models/IRR/scenegraphAnim.irr +++ b/test/models/IRR/scenegraphAnim.irr @@ -126,7 +126,7 @@ - + diff --git a/test/models/IRR/scenegraphAnimMod.irr b/test/models/IRR/scenegraphAnimMod.irr new file mode 100644 index 000000000..21408b245 --- /dev/null +++ b/test/models/IRR/scenegraphAnimMod.irr @@ -0,0 +1,554 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/models/IRR/scenegraphAnimMod_UTF16LE.irr b/test/models/IRR/scenegraphAnimMod_UTF16LE.irr new file mode 100644 index 000000000..7384ae29a Binary files /dev/null and b/test/models/IRR/scenegraphAnimMod_UTF16LE.irr differ diff --git a/test/models/IRR/scenegraphAnim_UTF16LE.irr b/test/models/IRR/scenegraphAnim_UTF16LE.irr index b22aaf4b7..f6794351f 100644 Binary files a/test/models/IRR/scenegraphAnim_UTF16LE.irr and b/test/models/IRR/scenegraphAnim_UTF16LE.irr differ diff --git a/test/unit/utImporter.cpp b/test/unit/utImporter.cpp index 98080c526..5ecc45e34 100644 --- a/test/unit/utImporter.cpp +++ b/test/unit/utImporter.cpp @@ -361,3 +361,37 @@ TEST_F(ImporterTest, unexpectedException) { EXPECT_TRUE(false); } } + +// ------------------------------------------------------------------------------------------------ + +struct ExtensionTestCase { + std::string testName; + std::string filename; + std::string getExtensionResult; + std::string hasExtension; + bool hasExtensionResult; +}; + +using ExtensionTest = ::testing::TestWithParam; + +TEST_P(ExtensionTest, testGetAndHasExtension) { + const ExtensionTestCase& testCase = GetParam(); + EXPECT_EQ(testCase.getExtensionResult, BaseImporter::GetExtension(testCase.filename)); + EXPECT_EQ(testCase.hasExtensionResult, BaseImporter::HasExtension(testCase.filename, {testCase.hasExtension})); +} + +INSTANTIATE_TEST_SUITE_P( + ExtensionTests, ExtensionTest, + ::testing::ValuesIn({ + {"NoExtension", "name", "", "glb", false}, + {"NoExtensionAndEmptyVersion", "name#", "", "glb", false}, + {"WithExtensionAndEmptyVersion", "name.glb#", "glb#", "glb", false}, + {"WithExtensionAndVersion", "name.glb#1234", "glb", "glb", true}, + {"WithExtensionAndHashInStem", "name#1234.glb", "glb", "glb", true}, + {"WithExtensionAndInvalidVersion", "name.glb#_", "glb#_", "glb", false}, + {"WithExtensionAndDotAndHashInStem", "name.glb#.abc", "abc", "glb", false}, + {"WithTwoExtensions", "name.abc.def", "def", "abc.def", true}, + }), + [](const ::testing::TestParamInfo& info) { + return info.param.testName; + }); diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index 91ac87ee5..ba1c859ad 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -60,7 +60,7 @@ using namespace Assimp; class utglTF2ImportExport : public AbstractImportExportBase { public: - virtual bool importerMatTest(const char *file, bool spec_gloss, std::array exp_modes = { aiTextureMapMode_Wrap, aiTextureMapMode_Wrap }) { + virtual bool importerMatTest(const char *file, bool spec, bool gloss, std::array exp_modes = { aiTextureMapMode_Wrap, aiTextureMapMode_Wrap }) { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(file, aiProcess_ValidateDataStructure); EXPECT_NE(scene, nullptr); @@ -105,16 +105,19 @@ public: aiColor3D spec_color = { 0, 0, 0 }; ai_real glossiness = ai_real(0.5); - if (spec_gloss) { + if (spec) { EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_COLOR_SPECULAR, spec_color)); constexpr ai_real spec_val(0.20000000298023225); // From the file EXPECT_EQ(spec_val, spec_color.r); EXPECT_EQ(spec_val, spec_color.g); EXPECT_EQ(spec_val, spec_color.b); + } else { + EXPECT_EQ(aiReturn_FAILURE, material->Get(AI_MATKEY_COLOR_SPECULAR, spec_color)); + } + if (gloss) { EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_GLOSSINESS_FACTOR, glossiness)); EXPECT_EQ(ai_real(1.0), glossiness); } else { - EXPECT_EQ(aiReturn_FAILURE, material->Get(AI_MATKEY_COLOR_SPECULAR, spec_color)); EXPECT_EQ(aiReturn_FAILURE, material->Get(AI_MATKEY_GLOSSINESS_FACTOR, glossiness)); } @@ -143,7 +146,7 @@ public: }; TEST_F(utglTF2ImportExport, importglTF2FromFileTest) { - EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", false, {aiTextureMapMode_Mirror, aiTextureMapMode_Clamp})); + EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", false, false, {aiTextureMapMode_Mirror, aiTextureMapMode_Clamp})); } TEST_F(utglTF2ImportExport, importBinaryglTF2FromFileTest) { @@ -151,7 +154,7 @@ TEST_F(utglTF2ImportExport, importBinaryglTF2FromFileTest) { } TEST_F(utglTF2ImportExport, importglTF2_KHR_materials_pbrSpecularGlossiness) { - EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured.gltf", true)); + EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured.gltf", true, true)); } void VerifyClearCoatScene(const aiScene *scene) { @@ -223,13 +226,16 @@ TEST_F(utglTF2ImportExport, importglTF2AndExport_KHR_materials_pbrSpecularGlossi // Export with specular glossiness disabled EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured_out.glb")); + // And re-import + EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured_out.glb", true, false)); + // Export with specular glossiness enabled ExportProperties props; props.SetPropertyBool(AI_CONFIG_USE_GLTF_PBR_SPECULAR_GLOSSINESS, true); EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured_out.glb", 0, &props)); // And re-import - EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured_out.glb", true)); + EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured_out.glb", true, true)); } TEST_F(utglTF2ImportExport, importglTF2AndExportToOBJ) {