From 32781a566db2cd6022c7020ff2ac591d753c11ab Mon Sep 17 00:00:00 2001 From: Fabrice Fontaine Date: Sun, 6 Oct 2019 15:42:11 +0200 Subject: [PATCH 01/33] Fix FBXConverter: use proper 64-bit constant Use proper 64-bit constant for CONVERT_FBX_TIME(time) conversion, fixes: code/FBXConverter.cpp:2025: error: integer constant is too large for 'long' type code/FBXConverter.cpp:2026: error: integer constant is too large for 'long' type code/FBXConverter.cpp:2794: error: integer constant is too large for 'long' type code/FBXConverter.cpp:2868: error: integer constant is too large for 'long' type code/FBXConverter.cpp:2878: error: integer constant is too large for 'long' type code/FBXConverter.cpp:2888: error: integer constant is too large for 'long' type Signed-off-by: Peter Seiderer [Retrieved from: https://git.buildroot.net/buildroot/tree/package/assimp/0001-Fix-FBXConverter-use-proper-64-bit-constant.patch] Signed-off-by: Fabrice Fontaine --- code/FBX/FBXConverter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/FBX/FBXConverter.cpp b/code/FBX/FBXConverter.cpp index 152be3277..b8601a2a5 100644 --- a/code/FBX/FBXConverter.cpp +++ b/code/FBX/FBXConverter.cpp @@ -77,7 +77,7 @@ namespace Assimp { #define MAGIC_NODE_TAG "_$AssimpFbx$" -#define CONVERT_FBX_TIME(time) static_cast(time) / 46186158000L +#define CONVERT_FBX_TIME(time) static_cast(time) / 46186158000LL FBXConverter::FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones ) : defaultMaterialIndex() From 81d125a2cca748ef3f79f1f0a6731d7e6d694ec1 Mon Sep 17 00:00:00 2001 From: Robikz Date: Sun, 6 Oct 2019 18:46:35 +0200 Subject: [PATCH 02/33] dllexport ASSIMP_API in all Windows compilers, not just MSVC This fixes a problem mentioned in issue #2685 where the libassimp.dll compiled with MinGW doesn't export any symbols. --- include/assimp/defs.h | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/include/assimp/defs.h b/include/assimp/defs.h index 05a5e3fd4..e32b95dde 100644 --- a/include/assimp/defs.h +++ b/include/assimp/defs.h @@ -126,16 +126,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * GENBOUNDINGBOXES */ ////////////////////////////////////////////////////////////////////////// -#ifdef _MSC_VER +#ifdef _WIN32 # undef ASSIMP_API - ////////////////////////////////////////////////////////////////////////// /* Define 'ASSIMP_BUILD_DLL_EXPORT' to build a DLL of the library */ ////////////////////////////////////////////////////////////////////////// # ifdef ASSIMP_BUILD_DLL_EXPORT # define ASSIMP_API __declspec(dllexport) # define ASSIMP_API_WINONLY __declspec(dllexport) -# pragma warning (disable : 4251) ////////////////////////////////////////////////////////////////////////// /* Define 'ASSIMP_DLL' before including Assimp to link to ASSIMP in @@ -148,7 +146,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # define ASSIMP_API # define ASSIMP_API_WINONLY # endif +#elif defined(SWIG) + /* Do nothing, the relevant defines are all in AssimpSwigPort.i */ + +#else +# define ASSIMP_API __attribute__ ((visibility("default"))) +# define ASSIMP_API_WINONLY +#endif + +#ifdef _MSC_VER +# ifdef ASSIMP_BUILD_DLL_EXPORT +# pragma warning (disable : 4251) +# endif /* Force the compiler to inline a function, if possible */ # define AI_FORCE_INLINE __forceinline @@ -156,17 +166,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /* Tells the compiler that a function never returns. Used in code analysis * to skip dead paths (e.g. after an assertion evaluated to false). */ # define AI_WONT_RETURN __declspec(noreturn) - #elif defined(SWIG) /* Do nothing, the relevant defines are all in AssimpSwigPort.i */ #else - # define AI_WONT_RETURN - -# define ASSIMP_API __attribute__ ((visibility("default"))) -# define ASSIMP_API_WINONLY # define AI_FORCE_INLINE inline #endif // (defined _MSC_VER) From 0761530e17cb7638be3e8267a9128bea7a2dae51 Mon Sep 17 00:00:00 2001 From: Robikz Date: Sun, 6 Oct 2019 18:46:47 +0200 Subject: [PATCH 03/33] Set proper sharedLibraryName for MinGW in installed assimpTargets-*.cmake The shared library name for MinGW is the .a library with which we need to link the built binary. This problem is described in issue #2685. --- assimpTargets-debug.cmake.in | 7 ++++++- assimpTargets-release.cmake.in | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/assimpTargets-debug.cmake.in b/assimpTargets-debug.cmake.in index 1ebe2a608..ed3935970 100644 --- a/assimpTargets-debug.cmake.in +++ b/assimpTargets-debug.cmake.in @@ -63,7 +63,12 @@ if(MSVC) else() set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" ) if(ASSIMP_BUILD_SHARED_LIBS) - set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") + if(WIN32) + # Handle MinGW compiler. + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@@CMAKE_STATIC_LIBRARY_SUFFIX@") + else() + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") + endif() set_target_properties(assimp::assimp PROPERTIES IMPORTED_SONAME_DEBUG "${sharedLibraryName}" IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/${sharedLibraryName}" diff --git a/assimpTargets-release.cmake.in b/assimpTargets-release.cmake.in index b09b881f7..f00710d85 100644 --- a/assimpTargets-release.cmake.in +++ b/assimpTargets-release.cmake.in @@ -63,7 +63,12 @@ if(MSVC) else() set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" ) if(ASSIMP_BUILD_SHARED_LIBS) - set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") + if(WIN32) + # Handle MinGW compiler. + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@@CMAKE_STATIC_LIBRARY_SUFFIX@") + else() + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") + endif() set_target_properties(assimp::assimp PROPERTIES IMPORTED_SONAME_RELEASE "${sharedLibraryName}" IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/${sharedLibraryName}" From 6ac8279977c3a54118551e549d77329497116f66 Mon Sep 17 00:00:00 2001 From: Robikz Date: Sun, 6 Oct 2019 18:46:51 +0200 Subject: [PATCH 04/33] assimpTargets.cmake: define add_library() with lib type directly Instead of using if(ON)/if(OFF) to determine which of the "hardcoded" add_library(... SHARED ...) or (... STATIC ...) should be used, specify a new BUILD_LIB_TYPE variable that is set directly to either SHARED or STATIC and substituted in the `add_library()` statement when assimpTargets.cmake.in is configured. This removes a CMP0012 collision with prior `cmake_policy(VERSION 2.6)` statement and makes the CMP0012 warning not appear in users' projects. This problem is mentioned in issue #2685. --- CMakeLists.txt | 5 +++++ assimpTargets.cmake.in | 6 +----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dcafb649f..5a716daa0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -391,6 +391,11 @@ IF(HUNTER_ENABLED) ) ELSE(HUNTER_ENABLED) # cmake configuration files + if(${BUILD_SHARED_LIBS}) + set(BUILD_LIB_TYPE SHARED) + else() + set(BUILD_LIB_TYPE STATIC) + endif() CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimp-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake" @ONLY IMMEDIATE) CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimpTargets.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets.cmake" @ONLY IMMEDIATE) IF (is_multi_config) diff --git a/assimpTargets.cmake.in b/assimpTargets.cmake.in index ab1a8d2c7..ea63ba2a5 100644 --- a/assimpTargets.cmake.in +++ b/assimpTargets.cmake.in @@ -51,11 +51,7 @@ if(_IMPORT_PREFIX STREQUAL "/") endif() # Create imported target assimp::assimp -if(@BUILD_SHARED_LIBS@) - add_library(assimp::assimp SHARED IMPORTED) -else() - add_library(assimp::assimp STATIC IMPORTED) -endif() +add_library(assimp::assimp @BUILD_LIB_TYPE@ IMPORTED) set_target_properties(assimp::assimp PROPERTIES COMPATIBLE_INTERFACE_STRING "assimp_MAJOR_VERSION" From 059ee0e091f1c658c20202a9123bdf90fc7fa307 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Fri, 11 Oct 2019 16:28:14 +0100 Subject: [PATCH 05/33] Update assimp legal and version Will now report the major and minor versions specified in cmakelists --- code/Common/Version.cpp | 14 +++++--------- test/unit/utVersion.cpp | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/code/Common/Version.cpp b/code/Common/Version.cpp index 868cfb06a..cf1da7d5b 100644 --- a/code/Common/Version.cpp +++ b/code/Common/Version.cpp @@ -46,8 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "ScenePrivate.h" -static const unsigned int MajorVersion = 5; -static const unsigned int MinorVersion = 0; +#include "revision.h" // -------------------------------------------------------------------------------- // Legal information string - don't remove this. @@ -56,9 +55,9 @@ static const char* LEGAL_INFORMATION = "Open Asset Import Library (Assimp).\n" "A free C/C++ library to import various 3D file formats into applications\n\n" -"(c) 2008-2017, assimp team\n" +"(c) 2006-2019, assimp team\n" "License under the terms and conditions of the 3-clause BSD license\n" -"http://assimp.sourceforge.net\n" +"http://assimp.org\n" ; // ------------------------------------------------------------------------------------------------ @@ -70,13 +69,13 @@ ASSIMP_API const char* aiGetLegalString () { // ------------------------------------------------------------------------------------------------ // Get Assimp minor version ASSIMP_API unsigned int aiGetVersionMinor () { - return MinorVersion; + return VER_MINOR; } // ------------------------------------------------------------------------------------------------ // Get Assimp major version ASSIMP_API unsigned int aiGetVersionMajor () { - return MajorVersion; + return VER_MAJOR; } // ------------------------------------------------------------------------------------------------ @@ -104,9 +103,6 @@ ASSIMP_API unsigned int aiGetCompileFlags () { return flags; } -// include current build revision, which is even updated from time to time -- :-) -#include "revision.h" - // ------------------------------------------------------------------------------------------------ ASSIMP_API unsigned int aiGetVersionRevision() { return GitVersion; diff --git a/test/unit/utVersion.cpp b/test/unit/utVersion.cpp index 233b2fb0b..66e832baa 100644 --- a/test/unit/utVersion.cpp +++ b/test/unit/utVersion.cpp @@ -48,7 +48,7 @@ TEST_F( utVersion, aiGetLegalStringTest ) { EXPECT_NE( lv, nullptr ); std::string text( lv ); - size_t pos( text.find( std::string( "2017" ) ) ); + size_t pos( text.find( std::string( "2019" ) ) ); EXPECT_NE( pos, std::string::npos ); } From ce5c71d2e7d993823c674aa71f05b33d281a4e00 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Fri, 11 Oct 2019 18:57:38 +0100 Subject: [PATCH 06/33] Collada ZAE import must convert manifest and image paths Moved ConvertPath into ColladaParser and use it when reading all filenames from the XML Added more EXPECTS to the Collada tests --- code/Collada/ColladaHelper.h | 2 +- code/Collada/ColladaLoader.cpp | 56 ++--------------------- code/Collada/ColladaLoader.h | 5 +-- code/Collada/ColladaParser.cpp | 69 +++++++++++++++++++++++++++-- code/Collada/ColladaParser.h | 5 ++- test/unit/utColladaImportExport.cpp | 27 ++++++++++- 6 files changed, 100 insertions(+), 64 deletions(-) diff --git a/code/Collada/ColladaHelper.h b/code/Collada/ColladaHelper.h index 66cff2d20..b7d47da15 100644 --- a/code/Collada/ColladaHelper.h +++ b/code/Collada/ColladaHelper.h @@ -583,7 +583,7 @@ struct Image /** Embedded image data */ std::vector mImageData; - /** File format hint ofembedded image data */ + /** File format hint of embedded image data */ std::string mEmbeddedFormat; }; diff --git a/code/Collada/ColladaLoader.cpp b/code/Collada/ColladaLoader.cpp index 0fbc7d599..8327142bf 100644 --- a/code/Collada/ColladaLoader.cpp +++ b/code/Collada/ColladaLoader.cpp @@ -1735,6 +1735,7 @@ void ColladaLoader::BuildMaterials(ColladaParser& pParser, aiScene* /*pScene*/) // ------------------------------------------------------------------------------------------------ // Resolves the texture name for the given effect texture entry +// and loads the texture data aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParser, const Collada::Effect& pEffect, const std::string& pName) { @@ -1762,7 +1763,7 @@ aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParse //set default texture file name result.Set(name + ".jpg"); - ConvertPath(result); + ColladaParser::ConvertPath(result); return result; } @@ -1781,7 +1782,7 @@ aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParse // setup format hint - if (imIt->second.mEmbeddedFormat.length() > 3) { + if (imIt->second.mEmbeddedFormat.length() >= HINTMAXTEXTURELEN) { ASSIMP_LOG_WARN("Collada: texture format hint is too long, truncating to 3 characters"); } strncpy(tex->achFormatHint, imIt->second.mEmbeddedFormat.c_str(), 3); @@ -1802,61 +1803,10 @@ aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParse } result.Set(imIt->second.mFileName); - ConvertPath(result); } return result; } -// ------------------------------------------------------------------------------------------------ -// Convert a path read from a collada file to the usual representation -void ColladaLoader::ConvertPath(aiString& ss) -{ - // TODO: collada spec, p 22. Handle URI correctly. - // For the moment we're just stripping the file:// away to make it work. - // Windows doesn't seem to be able to find stuff like - // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg' - if (0 == strncmp(ss.data, "file://", 7)) - { - ss.length -= 7; - memmove(ss.data, ss.data + 7, ss.length); - ss.data[ss.length] = '\0'; - } - - // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes... - // I need to filter it without destroying linux paths starting with "/somewhere" -#if defined( _MSC_VER ) - if (ss.data[0] == '/' && isalpha((unsigned char)ss.data[1]) && ss.data[2] == ':') { -#else - if (ss.data[0] == '/' && isalpha(ss.data[1]) && ss.data[2] == ':') { -#endif - --ss.length; - ::memmove(ss.data, ss.data + 1, ss.length); - ss.data[ss.length] = 0; - } - - // find and convert all %xy special chars - char* out = ss.data; - for (const char* it = ss.data; it != ss.data + ss.length; /**/) - { - if (*it == '%' && (it + 3) < ss.data + ss.length) - { - // separate the number to avoid dragging in chars from behind into the parsing - char mychar[3] = { it[1], it[2], 0 }; - size_t nbr = strtoul16(mychar); - it += 3; - *out++ = (char)(nbr & 0xFF); - } - else - { - *out++ = *it++; - } - } - - // adjust length and terminator of the shortened string - *out = 0; - ss.length = (ptrdiff_t)(out - ss.data); -} - // ------------------------------------------------------------------------------------------------ // Reads a float value from an accessor and its data array. ai_real ColladaLoader::ReadFloat(const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex, size_t pOffset) const diff --git a/code/Collada/ColladaLoader.h b/code/Collada/ColladaLoader.h index dce46e06f..d8d3f4f52 100644 --- a/code/Collada/ColladaLoader.h +++ b/code/Collada/ColladaLoader.h @@ -94,7 +94,7 @@ public: public: /** Returns whether the class can handle the format of the given file. * See BaseImporter::CanRead() for details. */ - bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override; + bool CanRead(const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override; protected: /** Return importer meta information. @@ -184,9 +184,6 @@ protected: aiString FindFilenameForEffectTexture( const ColladaParser& pParser, const Collada::Effect& pEffect, const std::string& pName); - /** Converts a path read from a collada file to the usual representation */ - void ConvertPath( aiString& ss); - /** Reads a float value from an accessor and its data array. * @param pAccessor The accessor to use for reading * @param pData The data array to read from diff --git a/code/Collada/ColladaParser.cpp b/code/Collada/ColladaParser.cpp index 646ec473d..f80ed1417 100644 --- a/code/Collada/ColladaParser.cpp +++ b/code/Collada/ColladaParser.cpp @@ -183,13 +183,66 @@ std::string ColladaParser::ReadZaeManifest(ZipArchiveIOSystem &zip_archive) { if (filepath == nullptr) return std::string(); - return std::string(filepath); + aiString ai_str(filepath); + ConvertPath(ai_str); + + return std::string(ai_str.C_Str()); } } } return std::string(); } +// ------------------------------------------------------------------------------------------------ +// Convert a path read from a collada file to the usual representation +void ColladaParser::ConvertPath(aiString& ss) +{ + // TODO: collada spec, p 22. Handle URI correctly. + // For the moment we're just stripping the file:// away to make it work. + // Windows doesn't seem to be able to find stuff like + // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg' + if (0 == strncmp(ss.data, "file://", 7)) + { + ss.length -= 7; + memmove(ss.data, ss.data + 7, ss.length); + ss.data[ss.length] = '\0'; + } + + // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes... + // I need to filter it without destroying linux paths starting with "/somewhere" +#if defined( _MSC_VER ) + if (ss.data[0] == '/' && isalpha((unsigned char)ss.data[1]) && ss.data[2] == ':') { +#else + if (ss.data[0] == '/' && isalpha(ss.data[1]) && ss.data[2] == ':') { +#endif + --ss.length; + ::memmove(ss.data, ss.data + 1, ss.length); + ss.data[ss.length] = 0; + } + + // find and convert all %xy special chars + char* out = ss.data; + for (const char* it = ss.data; it != ss.data + ss.length; /**/) + { + if (*it == '%' && (it + 3) < ss.data + ss.length) + { + // separate the number to avoid dragging in chars from behind into the parsing + char mychar[3] = { it[1], it[2], 0 }; + size_t nbr = strtoul16(mychar); + it += 3; + *out++ = (char)(nbr & 0xFF); + } + else + { + *out++ = *it++; + } + } + + // adjust length and terminator of the shortened string + *out = 0; + ss.length = (ptrdiff_t)(out - ss.data); +} + // ------------------------------------------------------------------------------------------------ // Read bool from text contents of current element bool ColladaParser::ReadBoolFromTextContent() @@ -1120,7 +1173,12 @@ void ColladaParser::ReadImage(Collada::Image& pImage) if (!mReader->isEmptyElement()) { // element content is filename - hopefully const char* sz = TestTextContent(); - if (sz)pImage.mFileName = sz; + if (sz) + { + aiString filepath(sz); + ConvertPath(filepath); + pImage.mFileName = filepath.C_Str(); + } TestClosing("init_from"); } if (!pImage.mFileName.length()) { @@ -1153,7 +1211,12 @@ void ColladaParser::ReadImage(Collada::Image& pImage) { // element content is filename - hopefully const char* sz = TestTextContent(); - if (sz)pImage.mFileName = sz; + if (sz) + { + aiString filepath(sz); + ConvertPath(filepath); + pImage.mFileName = filepath.C_Str(); + } TestClosing("ref"); } else if (IsElement("hex") && !pImage.mFileName.length()) diff --git a/code/Collada/ColladaParser.h b/code/Collada/ColladaParser.h index a2c9a4ff2..2c4adaead 100644 --- a/code/Collada/ColladaParser.h +++ b/code/Collada/ColladaParser.h @@ -66,12 +66,15 @@ namespace Assimp { friend class ColladaLoader; + /** Converts a path read from a collada file to the usual representation */ + static void ConvertPath(aiString& ss); + protected: /** Map for generic metadata as aiString */ typedef std::map StringMetaData; /** Constructor from XML file */ - ColladaParser( IOSystem* pIOHandler, const std::string& pFile); + ColladaParser(IOSystem* pIOHandler, const std::string& pFile); /** Destructor */ ~ColladaParser(); diff --git a/test/unit/utColladaImportExport.cpp b/test/unit/utColladaImportExport.cpp index 1a2a6bc9d..8a413d52b 100644 --- a/test/unit/utColladaImportExport.cpp +++ b/test/unit/utColladaImportExport.cpp @@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "AbstractImportExportBase.h" #include +#include #include using namespace Assimp; @@ -53,7 +54,18 @@ public: virtual bool importerTest() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure ); - return nullptr != scene; + if (scene == nullptr) + return false; + + // Expected number of items + EXPECT_EQ(scene->mNumMeshes, 1); + EXPECT_EQ(scene->mNumMaterials, 1); + EXPECT_EQ(scene->mNumAnimations, 0); + EXPECT_EQ(scene->mNumTextures, 0); + EXPECT_EQ(scene->mNumLights, 1); + EXPECT_EQ(scene->mNumCameras, 1); + + return true; } }; @@ -66,7 +78,18 @@ public: virtual bool importerTest() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.zae", aiProcess_ValidateDataStructure); - return nullptr != scene; + if (scene == nullptr) + return false; + + // Expected number of items + EXPECT_EQ(scene->mNumMeshes, 1); + EXPECT_EQ(scene->mNumMaterials, 1); + EXPECT_EQ(scene->mNumAnimations, 0); + EXPECT_EQ(scene->mNumTextures, 1); + EXPECT_EQ(scene->mNumLights, 1); + EXPECT_EQ(scene->mNumCameras, 1); + + return true; } }; From 6a6ccc0fb0d537f9bbafd841428afe908773767d Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Mon, 14 Oct 2019 11:27:34 +0100 Subject: [PATCH 07/33] Rename ConvertPath() to UriDecodePath() --- code/Collada/ColladaLoader.cpp | 2 +- code/Collada/ColladaParser.cpp | 8 ++++---- code/Collada/ColladaParser.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/code/Collada/ColladaLoader.cpp b/code/Collada/ColladaLoader.cpp index 8327142bf..37529bc98 100644 --- a/code/Collada/ColladaLoader.cpp +++ b/code/Collada/ColladaLoader.cpp @@ -1763,7 +1763,7 @@ aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParse //set default texture file name result.Set(name + ".jpg"); - ColladaParser::ConvertPath(result); + ColladaParser::UriDecodePath(result); return result; } diff --git a/code/Collada/ColladaParser.cpp b/code/Collada/ColladaParser.cpp index f80ed1417..4eece8327 100644 --- a/code/Collada/ColladaParser.cpp +++ b/code/Collada/ColladaParser.cpp @@ -184,7 +184,7 @@ std::string ColladaParser::ReadZaeManifest(ZipArchiveIOSystem &zip_archive) { return std::string(); aiString ai_str(filepath); - ConvertPath(ai_str); + UriDecodePath(ai_str); return std::string(ai_str.C_Str()); } @@ -195,7 +195,7 @@ std::string ColladaParser::ReadZaeManifest(ZipArchiveIOSystem &zip_archive) { // ------------------------------------------------------------------------------------------------ // Convert a path read from a collada file to the usual representation -void ColladaParser::ConvertPath(aiString& ss) +void ColladaParser::UriDecodePath(aiString& ss) { // TODO: collada spec, p 22. Handle URI correctly. // For the moment we're just stripping the file:// away to make it work. @@ -1176,7 +1176,7 @@ void ColladaParser::ReadImage(Collada::Image& pImage) if (sz) { aiString filepath(sz); - ConvertPath(filepath); + UriDecodePath(filepath); pImage.mFileName = filepath.C_Str(); } TestClosing("init_from"); @@ -1214,7 +1214,7 @@ void ColladaParser::ReadImage(Collada::Image& pImage) if (sz) { aiString filepath(sz); - ConvertPath(filepath); + UriDecodePath(filepath); pImage.mFileName = filepath.C_Str(); } TestClosing("ref"); diff --git a/code/Collada/ColladaParser.h b/code/Collada/ColladaParser.h index 2c4adaead..4e8c8fccf 100644 --- a/code/Collada/ColladaParser.h +++ b/code/Collada/ColladaParser.h @@ -67,7 +67,7 @@ namespace Assimp friend class ColladaLoader; /** Converts a path read from a collada file to the usual representation */ - static void ConvertPath(aiString& ss); + static void UriDecodePath(aiString& ss); protected: /** Map for generic metadata as aiString */ From d371a113fa4d9bb7815db347fcb4bfa70b019352 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Mon, 14 Oct 2019 12:10:47 +0100 Subject: [PATCH 08/33] /DEBUG:FULL is not a compiler flag --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dcafb649f..c4d8cf4e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -253,7 +253,7 @@ ELSEIF(MSVC) IF(MSVC12) ADD_COMPILE_OPTIONS(/wd4351) ENDIF() - SET(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Ob2 /DEBUG:FULL /Zi") + SET(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Ob2 /Zi") ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" ) IF(NOT HUNTER_ENABLED) SET(CMAKE_CXX_FLAGS "-fPIC -std=c++11 ${CMAKE_CXX_FLAGS}") From 198bc428fafff40cf3814d1a771173f8f19bcf91 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Mon, 14 Oct 2019 12:15:02 +0100 Subject: [PATCH 09/33] Fix some warnings in Collada import --- code/Collada/ColladaParser.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/Collada/ColladaParser.cpp b/code/Collada/ColladaParser.cpp index 4eece8327..1a7b96189 100644 --- a/code/Collada/ColladaParser.cpp +++ b/code/Collada/ColladaParser.cpp @@ -240,7 +240,8 @@ void ColladaParser::UriDecodePath(aiString& ss) // adjust length and terminator of the shortened string *out = 0; - ss.length = (ptrdiff_t)(out - ss.data); + ai_assert(out > ss.data); + ss.length = static_cast(out - ss.data); } // ------------------------------------------------------------------------------------------------ @@ -3119,7 +3120,7 @@ void ColladaParser::ReadMaterialVertexInputBinding(Collada::SemanticMappingTable } } -void Assimp::ColladaParser::ReadEmbeddedTextures(ZipArchiveIOSystem& zip_archive) +void ColladaParser::ReadEmbeddedTextures(ZipArchiveIOSystem& zip_archive) { // Attempt to load any undefined Collada::Image in ImageLibrary for (ImageLibrary::iterator it = mImageLibrary.begin(); it != mImageLibrary.end(); ++it) { From f4bd11279fbae18b1151dfc4b4c25c9cca9ddb04 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Mon, 14 Oct 2019 12:25:42 +0100 Subject: [PATCH 10/33] Update Collada ZAE tests to include encoded URIs --- test/models/Collada/duck.zae | Bin 132801 -> 132811 bytes test/unit/utColladaImportExport.cpp | 41 ++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/test/models/Collada/duck.zae b/test/models/Collada/duck.zae index 97823535522cba8aee8db7b75b0e99bcc39c3d73..7e6748cb0092e498298802d23d1d7608bb474b04 100644 GIT binary patch delta 100935 zcmV)6K*+zrjR?z)2!BvZ0|XQR000O8!(vWP_#8sCV}=0$kXHl%3;+NCWOZX}AbMeC zE@WY4w7qMuUB{88`Q3p3!-g6&m;ve>uDoYf$!fUmsu|DthLXFxs=o*{bz~7mkt&k1 zt;YQL$>)hY6y=Mo#`L(*O`UyNd#$`gyz#~xkstoYKYsDz_J8|_*KeM`dikRd(*5

fN^6#HK`NJRnaR2Ppix*EneR}`u_3xhK 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 wtseB-q9*h9l$0_RW_DhTTqTyJ|i8 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 delta 100924 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 diff --git a/test/unit/utColladaImportExport.cpp b/test/unit/utColladaImportExport.cpp index 8a413d52b..f07f9a274 100644 --- a/test/unit/utColladaImportExport.cpp +++ b/test/unit/utColladaImportExport.cpp @@ -53,7 +53,7 @@ class utColladaImportExport : public AbstractImportExportBase { public: virtual bool importerTest() { Assimp::Importer importer; - const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure ); + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure); if (scene == nullptr) return false; @@ -76,18 +76,35 @@ TEST_F(utColladaImportExport, importBlenFromFileTest) { class utColladaZaeImportExport : public AbstractImportExportBase { public: virtual bool importerTest() { - Assimp::Importer importer; - const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.zae", aiProcess_ValidateDataStructure); - if (scene == nullptr) - return false; + { + 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, 1); - EXPECT_EQ(scene->mNumMaterials, 1); - EXPECT_EQ(scene->mNumAnimations, 0); - EXPECT_EQ(scene->mNumTextures, 1); - EXPECT_EQ(scene->mNumLights, 1); - EXPECT_EQ(scene->mNumCameras, 1); + // Expected number of items + EXPECT_EQ(scene->mNumMeshes, 1); + EXPECT_EQ(scene->mNumMaterials, 1); + EXPECT_EQ(scene->mNumAnimations, 0); + EXPECT_EQ(scene->mNumTextures, 1); + EXPECT_EQ(scene->mNumLights, 1); + EXPECT_EQ(scene->mNumCameras, 1); + } + + { + 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, 1); + EXPECT_EQ(scene->mNumMaterials, 1); + EXPECT_EQ(scene->mNumAnimations, 0); + EXPECT_EQ(scene->mNumTextures, 1); + EXPECT_EQ(scene->mNumLights, 1); + EXPECT_EQ(scene->mNumCameras, 1); + } return true; } From 17eb292d73e817d49a51b3e98a5cedbfdd923fdc Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Mon, 14 Oct 2019 14:49:57 +0100 Subject: [PATCH 11/33] Tests: Fix signed/unsigned warnings --- test/unit/utColladaImportExport.cpp | 36 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/test/unit/utColladaImportExport.cpp b/test/unit/utColladaImportExport.cpp index f07f9a274..8bce3a3dd 100644 --- a/test/unit/utColladaImportExport.cpp +++ b/test/unit/utColladaImportExport.cpp @@ -58,12 +58,12 @@ public: return false; // Expected number of items - EXPECT_EQ(scene->mNumMeshes, 1); - EXPECT_EQ(scene->mNumMaterials, 1); - EXPECT_EQ(scene->mNumAnimations, 0); - EXPECT_EQ(scene->mNumTextures, 0); - EXPECT_EQ(scene->mNumLights, 1); - EXPECT_EQ(scene->mNumCameras, 1); + 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); return true; } @@ -83,12 +83,12 @@ public: return false; // Expected number of items - EXPECT_EQ(scene->mNumMeshes, 1); - EXPECT_EQ(scene->mNumMaterials, 1); - EXPECT_EQ(scene->mNumAnimations, 0); - EXPECT_EQ(scene->mNumTextures, 1); - EXPECT_EQ(scene->mNumLights, 1); - EXPECT_EQ(scene->mNumCameras, 1); + 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); } { @@ -98,12 +98,12 @@ public: return false; // Expected number of items - EXPECT_EQ(scene->mNumMeshes, 1); - EXPECT_EQ(scene->mNumMaterials, 1); - EXPECT_EQ(scene->mNumAnimations, 0); - EXPECT_EQ(scene->mNumTextures, 1); - EXPECT_EQ(scene->mNumLights, 1); - EXPECT_EQ(scene->mNumCameras, 1); + 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; From 9aa9238e7e97e702c7dd152598449e1518692d5b Mon Sep 17 00:00:00 2001 From: Paul Arden Date: Fri, 18 Oct 2019 14:40:41 +1100 Subject: [PATCH 12/33] Copy texture data before handing on to asset which then handles freeing of the memory. This prevents the memory being released twice which was throwing an error. Fixes issue #2714. --- code/glTF2/glTF2Exporter.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/glTF2/glTF2Exporter.cpp b/code/glTF2/glTF2Exporter.cpp index 4724c2ef4..dc0b76443 100644 --- a/code/glTF2/glTF2Exporter.cpp +++ b/code/glTF2/glTF2Exporter.cpp @@ -320,7 +320,9 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTe if (path[0] == '*') { // embedded aiTexture* tex = mScene->mTextures[atoi(&path[1])]; - uint8_t* data = reinterpret_cast(tex->pcData); + // copy data since lifetime control is handed over to the asset + uint8_t* data = new uint8_t[tex->mWidth]; + memcpy(data, tex->pcData, tex->mWidth); texture->source->SetData(data, tex->mWidth, *mAsset); if (tex->achFormatHint[0]) { From 31e885a47ae59c161e2d8faebb3568d9b4b98f79 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 22 Oct 2019 20:24:23 +0200 Subject: [PATCH 13/33] Update appveyor.yml Reenable VS2013 --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index df16431bd..39525f59d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,6 +14,7 @@ matrix: fast_finish: true image: + - Visual Studio 2013 - Visual Studio 2015 - Visual Studio 2017 - Visual Studio 2019 From 41ab019cbf14137f2105c46c47adf49011f9bb2b Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Wed, 23 Oct 2019 22:42:19 +0200 Subject: [PATCH 14/33] Update appveyor.yml Add missing cmake build target. --- appveyor.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 39525f59d..fa0a125f0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,11 +30,13 @@ install: - set PATH=C:\Ruby24-x64\bin;%PATH% - set CMAKE_DEFINES -DASSIMP_WERROR=ON - if [%COMPILER%]==[MinGW] set PATH=C:\MinGW\bin;%PATH% + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2013" set CMAKE_GENERATOR_NAME=Visual Studio 12 2013 - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015 - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" set CMAKE_GENERATOR_NAME=Visual Studio 15 2017 - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2019" set CMAKE_GENERATOR_NAME=Visual Studio 16 2019 - cmake %CMAKE_DEFINES% -G "%CMAKE_GENERATOR_NAME%" -A %platform% . - # Rename sh.exe as sh.exe in PATH interferes with MinGW + # Rename sh.exe as sh.exe in PATH interferes with MinGW - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015 + - rename "C:\Program Files\Git\usr\bin\sh.exe" "sh2.exe" - set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5" - ps: Invoke-WebRequest -Uri https://download.microsoft.com/download/5/7/b/57b2947c-7221-4f33-b35e-2fc78cb10df4/vc_redist.x64.exe -OutFile .\packaging\windows-innosetup\vc_redist.x64.exe From 822be33408533b4f861d69dbc9aa9abd6c237f45 Mon Sep 17 00:00:00 2001 From: Paul Arden Date: Fri, 25 Oct 2019 12:17:43 +1100 Subject: [PATCH 15/33] Move pbrSG glossinessFactor into the correct material extension object. Fixes issue #2724. --- code/glTF2/glTF2AssetWriter.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/glTF2/glTF2AssetWriter.inl b/code/glTF2/glTF2AssetWriter.inl index 5d1b22064..b3148f798 100644 --- a/code/glTF2/glTF2AssetWriter.inl +++ b/code/glTF2/glTF2AssetWriter.inl @@ -358,7 +358,7 @@ namespace glTF2 { WriteVec(pbrSpecularGlossiness, pbrSG.specularFactor, "specularFactor", defaultSpecularFactor, w.mAl); if (pbrSG.glossinessFactor != 1) { - WriteFloat(obj, pbrSG.glossinessFactor, "glossinessFactor", w.mAl); + WriteFloat(pbrSpecularGlossiness, pbrSG.glossinessFactor, "glossinessFactor", w.mAl); } WriteTex(pbrSpecularGlossiness, pbrSG.diffuseTexture, "diffuseTexture", w.mAl); From cbd4e8bc220b1c79bed6aa788c758a69853b0376 Mon Sep 17 00:00:00 2001 From: Paul Arden Date: Fri, 25 Oct 2019 12:19:28 +1100 Subject: [PATCH 16/33] Fix inconsistent indentation in previous commit. --- code/glTF2/glTF2AssetWriter.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/glTF2/glTF2AssetWriter.inl b/code/glTF2/glTF2AssetWriter.inl index b3148f798..44a1d3e73 100644 --- a/code/glTF2/glTF2AssetWriter.inl +++ b/code/glTF2/glTF2AssetWriter.inl @@ -358,7 +358,7 @@ namespace glTF2 { WriteVec(pbrSpecularGlossiness, pbrSG.specularFactor, "specularFactor", defaultSpecularFactor, w.mAl); if (pbrSG.glossinessFactor != 1) { - WriteFloat(pbrSpecularGlossiness, pbrSG.glossinessFactor, "glossinessFactor", w.mAl); + WriteFloat(pbrSpecularGlossiness, pbrSG.glossinessFactor, "glossinessFactor", w.mAl); } WriteTex(pbrSpecularGlossiness, pbrSG.diffuseTexture, "diffuseTexture", w.mAl); From 5f58ef82b93c7f6fd9c0c08c3f8d739db0a29047 Mon Sep 17 00:00:00 2001 From: Rem Date: Fri, 25 Oct 2019 10:18:27 +0300 Subject: [PATCH 17/33] prevent accidental lower casing material names in ReplaceDefaultMaterial --- code/3DS/3DSConverter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/3DS/3DSConverter.cpp b/code/3DS/3DSConverter.cpp index 2176b75fc..3c3da36a3 100644 --- a/code/3DS/3DSConverter.cpp +++ b/code/3DS/3DSConverter.cpp @@ -72,7 +72,7 @@ void Discreet3DSImporter::ReplaceDefaultMaterial() unsigned int idx( NotSet ); for (unsigned int i = 0; i < mScene->mMaterials.size();++i) { - std::string &s = mScene->mMaterials[i].mName; + std::string s = mScene->mMaterials[i].mName; for ( std::string::iterator it = s.begin(); it != s.end(); ++it ) { *it = static_cast< char >( ::tolower( *it ) ); } From 168ae22ad4dc3f079b4efff7d77be02b1f1b99c5 Mon Sep 17 00:00:00 2001 From: RevoluPowered Date: Sat, 26 Oct 2019 16:28:51 +0100 Subject: [PATCH 18/33] Implemented easy armature lookup This lets you directly retrieve the node a bone links to and informs you of the armature directly This also fixes a bug with bone name being made unique which causes them to become not 1:1 what the modeller has imported. --- code/FBX/FBXConverter.cpp | 458 +++++++++++++++++++++++++++++--------- code/FBX/FBXConverter.h | 69 ++++-- code/FBX/FBXImporter.cpp | 194 ++++++++-------- include/assimp/mesh.h | 19 +- 4 files changed, 515 insertions(+), 225 deletions(-) diff --git a/code/FBX/FBXConverter.cpp b/code/FBX/FBXConverter.cpp index b8601a2a5..f8225f2e5 100644 --- a/code/FBX/FBXConverter.cpp +++ b/code/FBX/FBXConverter.cpp @@ -68,7 +68,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include - +#include +#include namespace Assimp { namespace FBX { @@ -120,6 +121,46 @@ namespace Assimp { ConvertGlobalSettings(); TransferDataToScene(); + // Now convert all bone positions to the correct mOffsetMatrix + std::vector bones; + std::vector nodes; + std::map bone_stack; + BuildBoneList(out->mRootNode, out->mRootNode, out, bones); + BuildNodeList(out->mRootNode, nodes ); + + + BuildBoneStack(out->mRootNode, out->mRootNode, out, bones, bone_stack, nodes); + + std::cout << "Bone stack size: " << bone_stack.size() << std::endl; + + for( std::pair kvp : bone_stack ) + { + aiBone *bone = kvp.first; + aiNode *bone_node = kvp.second; + std::cout << "active node lookup: " << bone->mName.C_Str() << std::endl; + // lcl transform grab - done in generate_nodes :) + + //bone->mOffsetMatrix = bone_node->mTransformation; + aiNode * armature = GetArmatureRoot(bone_node, bones); + + ai_assert(armature); + + // set up bone armature id + bone->mArmature = armature; + + // set this bone node to be referenced properly + ai_assert(bone_node); + bone->mNode = bone_node; + + // apply full hierarchy to transform for basic offset + while( bone_node->mParent ) + { + bone->mRestMatrix = bone_node->mTransformation * bone->mRestMatrix; + bone_node = bone_node->mParent; + } + } + + // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE // to make sure the scene passes assimp's validation. FBX files // need not contain geometry (i.e. camera animations, raw armatures). @@ -138,6 +179,167 @@ namespace Assimp { std::for_each(textures.begin(), textures.end(), Util::delete_fun()); } + /* Returns the armature root node */ + /* This is required to be detected for a bone initially, it will recurse up until it cannot find another + * bone and return the node + * No known failure points. (yet) + */ + aiNode * FBXConverter::GetArmatureRoot(aiNode *bone_node, std::vector &bone_list) + { + while(bone_node) + { + if(!IsBoneNode(bone_node->mName, bone_list)) + { + std::cout << "Found valid armature: " << bone_node->mName.C_Str() << std::endl; + return bone_node; + } + + bone_node = bone_node->mParent; + } + + std::cout << "can't find armature! node: " << bone_node << std::endl; + + return NULL; + } + + /* Simple IsBoneNode check if this could be a bone */ + bool FBXConverter::IsBoneNode(const aiString &bone_name, std::vector& bones ) + { + for( aiBone *bone : bones) + { + if(bone->mName == bone_name) + { + return true; + } + } + + return false; + } + + + /* Pop this node by name from the stack if found */ + /* Used in multiple armature situations with duplicate node / bone names */ + /* Known flaw: cannot have nodes with bone names, will be fixed in later release */ + /* (serious to be fixed) Known flaw: nodes which have more than one bone could be prematurely dropped from stack */ + aiNode* FBXConverter::GetNodeFromStack(const aiString &node_name, std::vector &nodes) + { + std::vector::iterator iter; + aiNode *found = NULL; + for( iter = nodes.begin(); iter < nodes.end(); ++iter ) + { + aiNode *element = *iter; + ai_assert(element); + // node valid and node name matches + if(element->mName == node_name) + { + found = element; + break; + } + } + + if(found != NULL) { + // now pop the element from the node list + nodes.erase(iter); + + return found; + } + return NULL; + } + + /* Prepare flat node list which can be used for non recursive lookups later */ + void FBXConverter::BuildNodeList(aiNode *current_node, std::vector &nodes) + { + assert(current_node); + + for( unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) + { + aiNode *child = current_node->mChildren[nodeId]; + assert(child); + + nodes.push_back(child); + + BuildNodeList(child, nodes); + } + } + + /* Reprocess all nodes to calculate bone transforms properly based on the REAL mOffsetMatrix not the local. */ + /* Before this would use mesh transforms which is wrong for bone transforms */ + /* Before this would work for simple character skeletons but not complex meshes with multiple origins */ + /* Source: sketch fab log cutter fbx */ + void FBXConverter::BuildBoneList(aiNode *current_node, const aiNode * root_node, const aiScene *scene, std::vector &bones ) + { + assert(scene); + for( unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) + { + aiNode *child = current_node->mChildren[nodeId]; + assert(child); + + // check for bones + for( unsigned int meshId = 0; meshId < child->mNumMeshes; ++meshId) + { + assert(child->mMeshes); + unsigned int mesh_index = child->mMeshes[meshId]; + aiMesh *mesh = scene->mMeshes[ mesh_index ]; + assert(mesh); + + for( unsigned int boneId = 0; boneId < mesh->mNumBones; ++boneId) + { + aiBone *bone = mesh->mBones[boneId]; + ai_assert(bone); + + // duplicate meshes exist with the same bones sometimes :) + // so this must be detected + if( std::find(bones.begin(), bones.end(), bone) == bones.end() ) + { + // add the element once + bones.push_back(bone); + } + } + + // find mesh and get bones + // then do recursive lookup for bones in root node hierarchy + } + + BuildBoneList(child, root_node, scene, bones); + } + } + + /* A bone stack allows us to have multiple armatures, with the same bone names + * A bone stack allows us also to retrieve bones true transform even with duplicate names :) + */ + void FBXConverter::BuildBoneStack(aiNode *current_node, const aiNode *root_node, const aiScene *scene, + const std::vector &bones, + std::map &bone_stack, + std::vector &node_stack ) + { + ai_assert(scene); + ai_assert(root_node); + ai_assert(!node_stack.empty()); + + for( aiBone * bone : bones) + { + ai_assert(bone); + aiNode* node = GetNodeFromStack(bone->mName, node_stack); + if(node == NULL) + { + node_stack.clear(); + BuildNodeList(out->mRootNode, node_stack ); + std::cout << "Resetting bone stack: null element " << bone->mName.C_Str() << std::endl; + + node = GetNodeFromStack(bone->mName, node_stack); + + if(!node) { + std::cout << "serious import issue armature failed to be detected?" << std::endl; + continue; + } + } + + std::cout << "Successfully added bone to stack and have valid armature: " << bone->mName.C_Str() << std::endl; + + bone_stack.insert(std::pair(bone, node)); + } + } + void FBXConverter::ConvertRootNode() { out->mRootNode = new aiNode(); std::string unique_name; @@ -145,7 +347,7 @@ namespace Assimp { out->mRootNode->mName.Set(unique_name); // root has ID 0 - ConvertNodes(0L, *out->mRootNode); + ConvertNodes(0L, out->mRootNode, out->mRootNode); } static std::string getAncestorBaseName(const aiNode* node) @@ -179,8 +381,11 @@ namespace Assimp { GetUniqueName(original_name, unique_name); return unique_name; } - - void FBXConverter::ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform) { + /// todo: pre-build node hierarchy + /// todo: get bone from stack + /// todo: make map of aiBone* to aiNode* + /// then update convert clusters to the new format + void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) { const std::vector& conns = doc.GetConnectionsByDestinationSequenced(id, "Model"); std::vector nodes; @@ -191,62 +396,69 @@ namespace Assimp { try { for (const Connection* con : conns) { - // ignore object-property links if (con->PropertyName().length()) { - continue; + // really important we document why this is ignored. + FBXImporter::LogInfo("ignoring property link - no docs on why this is ignored"); + continue; //? } + // convert connection source object into Object base class const Object* const object = con->SourceObject(); if (nullptr == object) { - FBXImporter::LogWarn("failed to convert source object for Model link"); + FBXImporter::LogError("failed to convert source object for Model link"); continue; } + // FBX Model::Cube, Model::Bone001, etc elements + // This detects if we can cast the object into this model structure. const Model* const model = dynamic_cast(object); if (nullptr != model) { nodes_chain.clear(); post_nodes_chain.clear(); - aiMatrix4x4 new_abs_transform = parent_transform; - - std::string unique_name = MakeUniqueNodeName(model, parent); - + aiMatrix4x4 new_abs_transform = parent->mTransformation; + std::string node_name = FixNodeName(model->Name()); // even though there is only a single input node, the design of // assimp (or rather: the complicated transformation chain that // is employed by fbx) means that we may need multiple aiNode's // to represent a fbx node's transformation. - const bool need_additional_node = GenerateTransformationNodeChain(*model, unique_name, nodes_chain, post_nodes_chain); + + // generate node transforms - this includes pivot data + // if need_additional_node is true then you t + const bool need_additional_node = GenerateTransformationNodeChain(*model, node_name, nodes_chain, post_nodes_chain); + + // assert that for the current node we must have at least a single transform ai_assert(nodes_chain.size()); if (need_additional_node) { - nodes_chain.push_back(new aiNode(unique_name)); + nodes_chain.push_back(new aiNode(node_name)); } //setup metadata on newest node SetupNodeMetadata(*model, *nodes_chain.back()); // link all nodes in a row - aiNode* last_parent = &parent; - for (aiNode* prenode : nodes_chain) { - ai_assert(prenode); + aiNode* last_parent = parent; + for (aiNode* child : nodes_chain) { + ai_assert(child); - if (last_parent != &parent) { + if (last_parent != parent) { last_parent->mNumChildren = 1; last_parent->mChildren = new aiNode*[1]; - last_parent->mChildren[0] = prenode; + last_parent->mChildren[0] = child; } - prenode->mParent = last_parent; - last_parent = prenode; + child->mParent = last_parent; + last_parent = child; - new_abs_transform *= prenode->mTransformation; + new_abs_transform *= child->mTransformation; } // attach geometry - ConvertModel(*model, *nodes_chain.back(), new_abs_transform); + ConvertModel(*model, nodes_chain.back(), root_node, new_abs_transform); // check if there will be any child nodes const std::vector& child_conns @@ -258,7 +470,7 @@ namespace Assimp { for (aiNode* postnode : post_nodes_chain) { ai_assert(postnode); - if (last_parent != &parent) { + if (last_parent != parent) { last_parent->mNumChildren = 1; last_parent->mChildren = new aiNode*[1]; last_parent->mChildren[0] = postnode; @@ -280,15 +492,15 @@ namespace Assimp { ); } - // attach sub-nodes (if any) - ConvertNodes(model->ID(), *last_parent, new_abs_transform); + // recursion call - child nodes + ConvertNodes(model->ID(), last_parent, root_node); if (doc.Settings().readLights) { - ConvertLights(*model, unique_name); + ConvertLights(*model, node_name); } if (doc.Settings().readCameras) { - ConvertCameras(*model, unique_name); + ConvertCameras(*model, node_name); } nodes.push_back(nodes_chain.front()); @@ -297,10 +509,10 @@ namespace Assimp { } if (nodes.size()) { - parent.mChildren = new aiNode*[nodes.size()](); - parent.mNumChildren = static_cast(nodes.size()); + parent->mChildren = new aiNode*[nodes.size()](); + parent->mNumChildren = static_cast(nodes.size()); - std::swap_ranges(nodes.begin(), nodes.end(), parent.mChildren); + std::swap_ranges(nodes.begin(), nodes.end(), parent->mChildren); } } catch (std::exception&) { @@ -803,7 +1015,7 @@ namespace Assimp { // is_complex needs to be consistent with NeedsComplexTransformationChain() // or the interplay between this code and the animation converter would // not be guaranteed. - ai_assert(NeedsComplexTransformationChain(model) == ((chainBits & chainMaskComplex) != 0)); + //ai_assert(NeedsComplexTransformationChain(model) == ((chainBits & chainMaskComplex) != 0)); // now, if we have more than just Translation, Scaling and Rotation, // we need to generate a full node chain to accommodate for assimp's @@ -905,7 +1117,8 @@ namespace Assimp { } } - void FBXConverter::ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform) + void FBXConverter::ConvertModel(const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { const std::vector& geos = model.GetGeometry(); @@ -917,11 +1130,12 @@ namespace Assimp { const MeshGeometry* const mesh = dynamic_cast(geo); const LineGeometry* const line = dynamic_cast(geo); if (mesh) { - const std::vector& indices = ConvertMesh(*mesh, model, node_global_transform, nd); + const std::vector& indices = ConvertMesh(*mesh, model, parent, root_node, + absolute_transform); std::copy(indices.begin(), indices.end(), std::back_inserter(meshes)); } else if (line) { - const std::vector& indices = ConvertLine(*line, model, node_global_transform, nd); + const std::vector& indices = ConvertLine(*line, model, parent, root_node); std::copy(indices.begin(), indices.end(), std::back_inserter(meshes)); } else { @@ -930,15 +1144,16 @@ namespace Assimp { } if (meshes.size()) { - nd.mMeshes = new unsigned int[meshes.size()](); - nd.mNumMeshes = static_cast(meshes.size()); + parent->mMeshes = new unsigned int[meshes.size()](); + parent->mNumMeshes = static_cast(meshes.size()); - std::swap_ranges(meshes.begin(), meshes.end(), nd.mMeshes); + std::swap_ranges(meshes.begin(), meshes.end(), parent->mMeshes); } } - std::vector FBXConverter::ConvertMesh(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) + std::vector + FBXConverter::ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { std::vector temp; @@ -962,18 +1177,18 @@ namespace Assimp { const MatIndexArray::value_type base = mindices[0]; for (MatIndexArray::value_type index : mindices) { if (index != base) { - return ConvertMeshMultiMaterial(mesh, model, node_global_transform, nd); + return ConvertMeshMultiMaterial(mesh, model, parent, root_node, absolute_transform); } } } // faster code-path, just copy the data - temp.push_back(ConvertMeshSingleMaterial(mesh, model, node_global_transform, nd)); + temp.push_back(ConvertMeshSingleMaterial(mesh, model, absolute_transform, parent, root_node)); return temp; } std::vector FBXConverter::ConvertLine(const LineGeometry& line, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) + aiNode *parent, aiNode *root_node) { std::vector temp; @@ -984,7 +1199,7 @@ namespace Assimp { return temp; } - aiMesh* const out_mesh = SetupEmptyMesh(line, nd); + aiMesh* const out_mesh = SetupEmptyMesh(line, root_node); out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; // copy vertices @@ -1019,7 +1234,7 @@ namespace Assimp { return temp; } - aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode& nd) + aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode *parent) { aiMesh* const out_mesh = new aiMesh(); meshes.push_back(out_mesh); @@ -1036,17 +1251,18 @@ namespace Assimp { } else { - out_mesh->mName = nd.mName; + out_mesh->mName = parent->mName; } return out_mesh; } - unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) + unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model, + const aiMatrix4x4 &absolute_transform, aiNode *parent, + aiNode *root_node) { const MatIndexArray& mindices = mesh.GetMaterialIndices(); - aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd); + aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent); const std::vector& vertices = mesh.GetVertices(); const std::vector& faces = mesh.GetFaceIndexCounts(); @@ -1164,7 +1380,8 @@ namespace Assimp { } if (doc.Settings().readWeights && mesh.DeformerSkin() != NULL) { - ConvertWeights(out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION); + ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, NO_MATERIAL_SEPARATION, + nullptr); } std::vector animMeshes; @@ -1209,8 +1426,10 @@ namespace Assimp { return static_cast(meshes.size() - 1); } - std::vector FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) + std::vector + FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent, + aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { const MatIndexArray& mindices = mesh.GetMaterialIndices(); ai_assert(mindices.size()); @@ -1221,7 +1440,7 @@ namespace Assimp { for (MatIndexArray::value_type index : mindices) { if (had.find(index) == had.end()) { - indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, node_global_transform, nd)); + indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, parent, root_node, absolute_transform)); had.insert(index); } } @@ -1229,12 +1448,12 @@ namespace Assimp { return indices; } - unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, - MatIndexArray::value_type index, - const aiMatrix4x4& node_global_transform, - aiNode& nd) + unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, + MatIndexArray::value_type index, + aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { - aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd); + aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent); const MatIndexArray& mindices = mesh.GetMaterialIndices(); const std::vector& vertices = mesh.GetVertices(); @@ -1399,7 +1618,7 @@ namespace Assimp { ConvertMaterialForMesh(out_mesh, model, mesh, index); if (process_weights) { - ConvertWeights(out_mesh, model, mesh, node_global_transform, index, &reverseMapping); + ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, index, &reverseMapping); } std::vector animMeshes; @@ -1449,10 +1668,10 @@ namespace Assimp { return static_cast(meshes.size() - 1); } - void FBXConverter::ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo, - const aiMatrix4x4& node_global_transform, - unsigned int materialIndex, - std::vector* outputVertStartIndices) + void FBXConverter::ConvertWeights(aiMesh *out, const Model &model, const MeshGeometry &geo, + const aiMatrix4x4 &absolute_transform, + aiNode *parent, aiNode *root_node, unsigned int materialIndex, + std::vector *outputVertStartIndices) { ai_assert(geo.DeformerSkin()); @@ -1463,13 +1682,12 @@ namespace Assimp { const Skin& sk = *geo.DeformerSkin(); std::vector bones; - bones.reserve(sk.Clusters().size()); const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION; ai_assert(no_mat_check || outputVertStartIndices); try { - + // iterate over the sub deformers for (const Cluster* cluster : sk.Clusters()) { ai_assert(cluster); @@ -1483,6 +1701,7 @@ namespace Assimp { index_out_indices.clear(); out_indices.clear(); + // now check if *any* of these weights is contained in the output mesh, // taking notes so we don't need to do it twice. for (WeightIndexArray::value_type index : indices) { @@ -1520,68 +1739,107 @@ namespace Assimp { } } } - + // if we found at least one, generate the output bones // XXX this could be heavily simplified by collecting the bone // data in a single step. - ConvertCluster(bones, model, *cluster, out_indices, index_out_indices, - count_out_indices, node_global_transform); + ConvertCluster(bones, cluster, out_indices, index_out_indices, + count_out_indices, absolute_transform, parent, root_node); } + + bone_map.clear(); } - catch (std::exception&) { + catch (std::exception&e) { std::for_each(bones.begin(), bones.end(), Util::delete_fun()); throw; } if (bones.empty()) { + out->mBones = nullptr; + out->mNumBones = 0; return; + } else { + out->mBones = new aiBone *[bones.size()](); + out->mNumBones = static_cast(bones.size()); + + std::swap_ranges(bones.begin(), bones.end(), out->mBones); } - - out->mBones = new aiBone*[bones.size()](); - out->mNumBones = static_cast(bones.size()); - - std::swap_ranges(bones.begin(), bones.end(), out->mBones); } - void FBXConverter::ConvertCluster(std::vector& bones, const Model& /*model*/, const Cluster& cl, - std::vector& out_indices, - std::vector& index_out_indices, - std::vector& count_out_indices, - const aiMatrix4x4& node_global_transform) + const aiNode* FBXConverter::GetNodeByName( const aiString& name, aiNode *current_node ) { + aiNode * iter = current_node; + //printf("Child count: %d", iter->mNumChildren); + return iter; + } - aiBone* const bone = new aiBone(); - bones.push_back(bone); + void FBXConverter::ConvertCluster(std::vector &local_mesh_bones, const Cluster *cl, + std::vector &out_indices, std::vector &index_out_indices, + std::vector &count_out_indices, const aiMatrix4x4 &absolute_transform, + aiNode *parent, aiNode *root_node) { + assert(cl); // make sure cluster valid + std::string deformer_name = cl->TargetNode()->Name(); + aiString bone_name = aiString(FixNodeName(deformer_name)); - bone->mName = FixNodeName(cl.TargetNode()->Name()); + aiBone *bone = NULL; - bone->mOffsetMatrix = cl.TransformLink(); - bone->mOffsetMatrix.Inverse(); + if (bone_map.count(deformer_name)) { + std::cout << "retrieved bone from lookup " << bone_name.C_Str() << ". Deformer: " << deformer_name + << std::endl; + bone = bone_map[deformer_name]; + } else { + std::cout << "created new bone " << bone_name.C_Str() << ". Deformer: " << deformer_name << std::endl; + bone = new aiBone(); + bone->mName = bone_name; - bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform; + // store local transform link for post processing + bone->mOffsetMatrix = cl->TransformLink(); + bone->mOffsetMatrix.Inverse(); - bone->mNumWeights = static_cast(out_indices.size()); - aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[out_indices.size()]; + aiMatrix4x4 matrix = (aiMatrix4x4)absolute_transform; - const size_t no_index_sentinel = std::numeric_limits::max(); - const WeightArray& weights = cl.GetWeights(); + bone->mOffsetMatrix = bone->mOffsetMatrix * matrix; // * mesh_offset - const size_t c = index_out_indices.size(); - for (size_t i = 0; i < c; ++i) { - const size_t index_index = index_out_indices[i]; - if (index_index == no_index_sentinel) { - continue; + // + // Now calculate the aiVertexWeights + // + + aiVertexWeight *cursor = nullptr; + + bone->mNumWeights = static_cast(out_indices.size()); + cursor = bone->mWeights = new aiVertexWeight[out_indices.size()]; + + const size_t no_index_sentinel = std::numeric_limits::max(); + const WeightArray& weights = cl->GetWeights(); + + const size_t c = index_out_indices.size(); + for (size_t i = 0; i < c; ++i) { + const size_t index_index = index_out_indices[i]; + + if (index_index == no_index_sentinel) { + continue; + } + + const size_t cc = count_out_indices[i]; + for (size_t j = 0; j < cc; ++j) { + // cursor runs from first element relative to the start + // or relative to the start of the next indexes. + aiVertexWeight& out_weight = *cursor++; + + out_weight.mVertexId = static_cast(out_indices[index_index + j]); + out_weight.mWeight = weights[i]; + } } - const size_t cc = count_out_indices[i]; - for (size_t j = 0; j < cc; ++j) { - aiVertexWeight& out_weight = *cursor++; - - out_weight.mVertexId = static_cast(out_indices[index_index + j]); - out_weight.mWeight = weights[i]; - } + bone_map.insert(std::pair(deformer_name, bone)); } + + std::cout << "bone research: Indicies size: " << out_indices.size() << std::endl; + + // lookup must be populated in case something goes wrong + // this also allocates bones to mesh instance outside + local_mesh_bones.push_back(bone); } void FBXConverter::ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo, diff --git a/code/FBX/FBXConverter.h b/code/FBX/FBXConverter.h index 77ced1950..619da92c1 100644 --- a/code/FBX/FBXConverter.h +++ b/code/FBX/FBXConverter.h @@ -123,7 +123,7 @@ private: // ------------------------------------------------------------------------------------------------ // collect and assign child nodes - void ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4()); + void ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node); // ------------------------------------------------------------------------------------------------ void ConvertLights(const Model& model, const std::string &orig_name ); @@ -179,32 +179,35 @@ private: void SetupNodeMetadata(const Model& model, aiNode& nd); // ------------------------------------------------------------------------------------------------ - void ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform); + void ConvertModel(const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform); // ------------------------------------------------------------------------------------------------ // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed - std::vector ConvertMesh(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd); + std::vector + ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform); // ------------------------------------------------------------------------------------------------ std::vector ConvertLine(const LineGeometry& line, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd); + aiNode *parent, aiNode *root_node); // ------------------------------------------------------------------------------------------------ - aiMesh* SetupEmptyMesh(const Geometry& mesh, aiNode& nd); + aiMesh* SetupEmptyMesh(const Geometry& mesh, aiNode *parent); // ------------------------------------------------------------------------------------------------ - unsigned int ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd); + unsigned int ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model, + const aiMatrix4x4 &absolute_transform, aiNode *parent, + aiNode *root_node); // ------------------------------------------------------------------------------------------------ - std::vector ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd); + std::vector + ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform); // ------------------------------------------------------------------------------------------------ - unsigned int ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, - MatIndexArray::value_type index, - const aiMatrix4x4& node_global_transform, aiNode& nd); + unsigned int ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, MatIndexArray::value_type index, + aiNode *parent, aiNode *root_node, const aiMatrix4x4 &absolute_transform); // ------------------------------------------------------------------------------------------------ static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits::max() */ @@ -217,17 +220,17 @@ private: * - outputVertStartIndices is only used when a material index is specified, it gives for * each output vertex the DOM index it maps to. */ - void ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo, - const aiMatrix4x4& node_global_transform = aiMatrix4x4(), - unsigned int materialIndex = NO_MATERIAL_SEPARATION, - std::vector* outputVertStartIndices = NULL); - + void ConvertWeights(aiMesh *out, const Model &model, const MeshGeometry &geo, const aiMatrix4x4 &absolute_transform, + aiNode *parent = NULL, aiNode *root_node = NULL, + unsigned int materialIndex = NO_MATERIAL_SEPARATION, + std::vector *outputVertStartIndices = NULL); + // lookup + static const aiNode* GetNodeByName( const aiString& name, aiNode *current_node ); // ------------------------------------------------------------------------------------------------ - void ConvertCluster(std::vector& bones, const Model& /*model*/, const Cluster& cl, - std::vector& out_indices, - std::vector& index_out_indices, - std::vector& count_out_indices, - const aiMatrix4x4& node_global_transform); + void ConvertCluster(std::vector &local_mesh_bones, const Cluster *cl, + std::vector &out_indices, std::vector &index_out_indices, + std::vector &count_out_indices, const aiMatrix4x4 &absolute_transform, + aiNode *parent, aiNode *root_node); // ------------------------------------------------------------------------------------------------ void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo, @@ -452,10 +455,30 @@ private: using NodeNameCache = std::unordered_map; NodeNameCache mNodeNames; + // Deformer name is not the same as a bone name - it does contain the bone name though :) + // Deformer names in FBX are always unique in an FBX file. + std::map bone_map; + double anim_fps; aiScene* const out; const FBX::Document& doc; + + static void BuildBoneList(aiNode *current_node, const aiNode *root_node, const aiScene *scene, + std::vector& bones); + + void BuildBoneStack(aiNode *current_node, const aiNode *root_node, const aiScene *scene, + const std::vector &bones, + std::map &bone_stack, + std::vector &node_stack ); + + static void BuildNodeList(aiNode *current_node, std::vector &nodes); + + static aiNode *GetNodeFromStack(const aiString &node_name, std::vector &nodes); + + static aiNode *GetArmatureRoot(aiNode *bone_node, std::vector &bone_list); + + static bool IsBoneNode(const aiString &bone_name, std::vector &bones); }; } diff --git a/code/FBX/FBXImporter.cpp b/code/FBX/FBXImporter.cpp index c8c1a6853..afcc1ddc7 100644 --- a/code/FBX/FBXImporter.cpp +++ b/code/FBX/FBXImporter.cpp @@ -48,26 +48,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "FBXImporter.h" -#include "FBXTokenizer.h" -#include "FBXParser.h" -#include "FBXUtil.h" -#include "FBXDocument.h" #include "FBXConverter.h" +#include "FBXDocument.h" +#include "FBXParser.h" +#include "FBXTokenizer.h" +#include "FBXUtil.h" -#include #include -#include +#include #include +#include namespace Assimp { -template<> -const char* LogFunctions::Prefix() { - static auto prefix = "FBX: "; - return prefix; +template <> +const char *LogFunctions::Prefix() { + static auto prefix = "FBX: "; + return prefix; } -} +} // namespace Assimp using namespace Assimp; using namespace Assimp::Formatter; @@ -76,131 +76,123 @@ using namespace Assimp::FBX; namespace { static const aiImporterDesc desc = { - "Autodesk FBX Importer", - "", - "", - "", - aiImporterFlags_SupportTextFlavour, - 0, - 0, - 0, - 0, - "fbx" + "Autodesk FBX Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "fbx" }; } // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by #Importer -FBXImporter::FBXImporter() -{ +FBXImporter::FBXImporter() { } // ------------------------------------------------------------------------------------------------ // Destructor, private as well -FBXImporter::~FBXImporter() -{ +FBXImporter::~FBXImporter() { } // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. -bool FBXImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const -{ - const std::string& extension = GetExtension(pFile); - if (extension == std::string( desc.mFileExtensions ) ) { - return true; - } +bool FBXImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const { + const std::string &extension = GetExtension(pFile); + if (extension == std::string(desc.mFileExtensions)) { + return true; + } - else if ((!extension.length() || checkSig) && pIOHandler) { - // at least ASCII-FBX files usually have a 'FBX' somewhere in their head - const char* tokens[] = {"fbx"}; - return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1); - } - return false; + else if ((!extension.length() || checkSig) && pIOHandler) { + // at least ASCII-FBX files usually have a 'FBX' somewhere in their head + const char *tokens[] = { "fbx" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1); + } + return false; } // ------------------------------------------------------------------------------------------------ // List all extensions handled by this loader -const aiImporterDesc* FBXImporter::GetInfo () const -{ - return &desc; +const aiImporterDesc *FBXImporter::GetInfo() const { + return &desc; } // ------------------------------------------------------------------------------------------------ // Setup configuration properties for the loader -void FBXImporter::SetupProperties(const Importer* pImp) -{ - settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true); - settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false); - settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true); - settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true); - settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true); - settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true); - settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true); - settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false); - settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true); - settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true); - settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false); - settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true); - settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false); +void FBXImporter::SetupProperties(const Importer *pImp) { + settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true); + settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false); + settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true); + settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true); + settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true); + settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true); + settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true); + settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false); + settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true); + settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true); + settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false); + settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true); + settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false); } // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. -void FBXImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) -{ - std::unique_ptr stream(pIOHandler->Open(pFile,"rb")); - if (!stream) { - ThrowException("Could not open file for reading"); - } +void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr stream(pIOHandler->Open(pFile, "rb")); + if (!stream) { + ThrowException("Could not open file for reading"); + } - // read entire file into memory - no streaming for this, fbx - // files can grow large, but the assimp output data structure - // then becomes very large, too. Assimp doesn't support - // streaming for its output data structures so the net win with - // streaming input data would be very low. - std::vector contents; - contents.resize(stream->FileSize()+1); - stream->Read( &*contents.begin(), 1, contents.size()-1 ); - contents[ contents.size() - 1 ] = 0; - const char* const begin = &*contents.begin(); + // read entire file into memory - no streaming for this, fbx + // files can grow large, but the assimp output data structure + // then becomes very large, too. Assimp doesn't support + // streaming for its output data structures so the net win with + // streaming input data would be very low. + std::vector contents; + contents.resize(stream->FileSize() + 1); + stream->Read(&*contents.begin(), 1, contents.size() - 1); + contents[contents.size() - 1] = 0; + const char *const begin = &*contents.begin(); - // broadphase tokenizing pass in which we identify the core - // syntax elements of FBX (brackets, commas, key:value mappings) - TokenList tokens; - try { + // broadphase tokenizing pass in which we identify the core + // syntax elements of FBX (brackets, commas, key:value mappings) + TokenList tokens; + try { - bool is_binary = false; - if (!strncmp(begin,"Kaydara FBX Binary",18)) { - is_binary = true; - TokenizeBinary(tokens,begin,contents.size()); - } - else { - Tokenize(tokens,begin); - } + bool is_binary = false; + if (!strncmp(begin, "Kaydara FBX Binary", 18)) { + is_binary = true; + TokenizeBinary(tokens, begin, contents.size()); + } else { + Tokenize(tokens, begin); + } - // use this information to construct a very rudimentary - // parse-tree representing the FBX scope structure - Parser parser(tokens, is_binary); + // use this information to construct a very rudimentary + // parse-tree representing the FBX scope structure + Parser parser(tokens, is_binary); - // take the raw parse-tree and convert it to a FBX DOM - Document doc(parser,settings); + // take the raw parse-tree and convert it to a FBX DOM + Document doc(parser, settings); - // convert the FBX DOM to aiScene - ConvertToAssimpScene(pScene, doc, settings.removeEmptyBones); + // convert the FBX DOM to aiScene + ConvertToAssimpScene(pScene, doc, settings.removeEmptyBones); - // size relative to cm - float size_relative_to_cm = doc.GlobalSettings().UnitScaleFactor(); + // size relative to cm + float size_relative_to_cm = doc.GlobalSettings().UnitScaleFactor(); - // Set FBX file scale is relative to CM must be converted to M for - // assimp universal format (M) - SetFileScale( size_relative_to_cm * 0.01f); + // Set FBX file scale is relative to CM must be converted to M for + // assimp universal format (M) + SetFileScale(size_relative_to_cm * 0.01f); - std::for_each(tokens.begin(),tokens.end(),Util::delete_fun()); - } - catch(std::exception&) { - std::for_each(tokens.begin(),tokens.end(),Util::delete_fun()); - throw; - } + std::for_each(tokens.begin(), tokens.end(), Util::delete_fun()); + } catch (std::exception &) { + std::for_each(tokens.begin(), tokens.end(), Util::delete_fun()); + throw; + } } #endif // !ASSIMP_BUILD_NO_FBX_IMPORTER diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index eb30ad5df..e652a6927 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -252,6 +252,9 @@ struct aiVertexWeight { }; +// Forward declare aiNode (pointer use only) +struct aiNode; + // --------------------------------------------------------------------------- /** @brief A single bone of a mesh. * @@ -268,6 +271,12 @@ struct aiBone { //! The maximum value for this member is #AI_MAX_BONE_WEIGHTS. unsigned int mNumWeights; + // The bone armature node - used for skeleton conversion + C_STRUCT aiNode* mArmature; + + // The bone node in the scene - used for skeleton conversion + C_STRUCT aiNode* mNode; + //! The influence weights of this bone, by vertex index. C_STRUCT aiVertexWeight* mWeights; @@ -284,6 +293,11 @@ struct aiBone { */ C_STRUCT aiMatrix4x4 mOffsetMatrix; + /** Matrix used for the global rest transform + * This tells you directly the rest without extending as required in most game engine implementations + * */ + C_STRUCT aiMatrix4x4 mRestMatrix; + #ifdef __cplusplus //! Default constructor @@ -773,7 +787,10 @@ struct aiMesh // DO NOT REMOVE THIS ADDITIONAL CHECK if (mNumBones && mBones) { for( unsigned int a = 0; a < mNumBones; a++) { - delete mBones[a]; + if(mBones[a]) + { + delete mBones[a]; + } } delete [] mBones; } From 6ea97a1282835ef43e25044a5053882bbf285d7a Mon Sep 17 00:00:00 2001 From: RevoluPowered Date: Sat, 26 Oct 2019 17:27:07 +0100 Subject: [PATCH 19/33] Updated test cases to test import of names This now doesn't overwrite names anymore as this would cause nasty bugs application side. We can now support these by default without having to handle them as edge cases. --- test/unit/utFBXImporterExporter.cpp | 39 ++++++++--------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/test/unit/utFBXImporterExporter.cpp b/test/unit/utFBXImporterExporter.cpp index 1445e3c3e..e73733a0d 100644 --- a/test/unit/utFBXImporterExporter.cpp +++ b/test/unit/utFBXImporterExporter.cpp @@ -76,6 +76,7 @@ TEST_F( utFBXImporterExporter, importBareBoxWithoutColorsAndTextureCoords ) { EXPECT_EQ(mesh->mNumVertices, 36u); } + TEST_F(utFBXImporterExporter, importCubesWithNoNames) { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/cubes_nonames.fbx", aiProcess_ValidateDataStructure); @@ -86,26 +87,6 @@ TEST_F(utFBXImporterExporter, importCubesWithNoNames) { ASSERT_STREQ(root->mName.C_Str(), "RootNode"); ASSERT_TRUE(root->mChildren); ASSERT_EQ(root->mNumChildren, 2u); - - const auto child0 = root->mChildren[0]; - ASSERT_TRUE(child0); - ASSERT_STREQ(child0->mName.C_Str(), "RootNode001"); - ASSERT_TRUE(child0->mChildren); - ASSERT_EQ(child0->mNumChildren, 1u); - - const auto child00 = child0->mChildren[0]; - ASSERT_TRUE(child00); - ASSERT_STREQ(child00->mName.C_Str(), "RootNode001001"); - - const auto child1 = root->mChildren[1]; - ASSERT_TRUE(child1); - ASSERT_STREQ(child1->mName.C_Str(), "RootNode002"); - ASSERT_TRUE(child1->mChildren); - ASSERT_EQ(child1->mNumChildren, 1u); - - const auto child10 = child1->mChildren[0]; - ASSERT_TRUE(child10); - ASSERT_STREQ(child10->mName.C_Str(), "RootNode002001"); } TEST_F(utFBXImporterExporter, importCubesWithUnicodeDuplicatedNames) { @@ -137,7 +118,7 @@ TEST_F(utFBXImporterExporter, importCubesWithUnicodeDuplicatedNames) { const auto child10 = child1->mChildren[0]; ASSERT_TRUE(child10); - ASSERT_STREQ(child10->mName.C_Str(), "\xd0\x9a\xd1\x83\xd0\xb1\x31""001"); + ASSERT_STREQ(child10->mName.C_Str(), "\xd0\x9a\xd1\x83\xd0\xb1\x31"); } TEST_F(utFBXImporterExporter, importCubesComplexTransform) { @@ -168,14 +149,14 @@ TEST_F(utFBXImporterExporter, importCubesComplexTransform) { auto parent = child1; const size_t chain_length = 8u; const char* chainStr[chain_length] = { - "Cube1001_$AssimpFbx$_Translation", - "Cube1001_$AssimpFbx$_RotationPivot", - "Cube1001_$AssimpFbx$_RotationPivotInverse", - "Cube1001_$AssimpFbx$_ScalingOffset", - "Cube1001_$AssimpFbx$_ScalingPivot", - "Cube1001_$AssimpFbx$_Scaling", - "Cube1001_$AssimpFbx$_ScalingPivotInverse", - "Cube1001" + "Cube1_$AssimpFbx$_Translation", + "Cube1_$AssimpFbx$_RotationPivot", + "Cube1_$AssimpFbx$_RotationPivotInverse", + "Cube1_$AssimpFbx$_ScalingOffset", + "Cube1_$AssimpFbx$_ScalingPivot", + "Cube1_$AssimpFbx$_Scaling", + "Cube1_$AssimpFbx$_ScalingPivotInverse", + "Cube1" }; for (size_t i = 0; i < chain_length; ++i) { ASSERT_TRUE(parent->mChildren); From 93efe4197aff5f46358545dd6b890ca454e8bfcd Mon Sep 17 00:00:00 2001 From: RevoluPowered Date: Sat, 26 Oct 2019 18:09:26 +0100 Subject: [PATCH 20/33] Removed redundant rest matrix and fixed assert compile error --- code/FBX/FBXConverter.cpp | 21 +++++++-------------- include/assimp/mesh.h | 5 ----- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/code/FBX/FBXConverter.cpp b/code/FBX/FBXConverter.cpp index f8225f2e5..0d754a638 100644 --- a/code/FBX/FBXConverter.cpp +++ b/code/FBX/FBXConverter.cpp @@ -151,13 +151,6 @@ namespace Assimp { // set this bone node to be referenced properly ai_assert(bone_node); bone->mNode = bone_node; - - // apply full hierarchy to transform for basic offset - while( bone_node->mParent ) - { - bone->mRestMatrix = bone_node->mTransformation * bone->mRestMatrix; - bone_node = bone_node->mParent; - } } @@ -249,12 +242,12 @@ namespace Assimp { /* Prepare flat node list which can be used for non recursive lookups later */ void FBXConverter::BuildNodeList(aiNode *current_node, std::vector &nodes) { - assert(current_node); + ai_assert(current_node); for( unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) { aiNode *child = current_node->mChildren[nodeId]; - assert(child); + ai_assert(child); nodes.push_back(child); @@ -268,19 +261,19 @@ namespace Assimp { /* Source: sketch fab log cutter fbx */ void FBXConverter::BuildBoneList(aiNode *current_node, const aiNode * root_node, const aiScene *scene, std::vector &bones ) { - assert(scene); + ai_assert(scene); for( unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) { aiNode *child = current_node->mChildren[nodeId]; - assert(child); + ai_assert(child); // check for bones for( unsigned int meshId = 0; meshId < child->mNumMeshes; ++meshId) { - assert(child->mMeshes); + ai_assert(child->mMeshes); unsigned int mesh_index = child->mMeshes[meshId]; aiMesh *mesh = scene->mMeshes[ mesh_index ]; - assert(mesh); + ai_assert(mesh); for( unsigned int boneId = 0; boneId < mesh->mNumBones; ++boneId) { @@ -1777,7 +1770,7 @@ namespace Assimp { std::vector &out_indices, std::vector &index_out_indices, std::vector &count_out_indices, const aiMatrix4x4 &absolute_transform, aiNode *parent, aiNode *root_node) { - assert(cl); // make sure cluster valid + ai_assert(cl); // make sure cluster valid std::string deformer_name = cl->TargetNode()->Name(); aiString bone_name = aiString(FixNodeName(deformer_name)); diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index e652a6927..617835e51 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -293,11 +293,6 @@ struct aiBone { */ C_STRUCT aiMatrix4x4 mOffsetMatrix; - /** Matrix used for the global rest transform - * This tells you directly the rest without extending as required in most game engine implementations - * */ - C_STRUCT aiMatrix4x4 mRestMatrix; - #ifdef __cplusplus //! Default constructor From 46cdd81d754bb602dabd3a3a026607bc2759e37e Mon Sep 17 00:00:00 2001 From: RevoluPowered Date: Sat, 26 Oct 2019 18:47:48 +0100 Subject: [PATCH 21/33] Added ArmaturePopulate scale process for all formats --- code/CMakeLists.txt | 2 + code/FBX/FBXConverter.cpp | 194 ------------------ code/PostProcessing/ArmaturePopulate.cpp | 247 +++++++++++++++++++++++ code/PostProcessing/ArmaturePopulate.h | 111 ++++++++++ include/assimp/postprocess.h | 12 ++ 5 files changed, 372 insertions(+), 194 deletions(-) create mode 100644 code/PostProcessing/ArmaturePopulate.cpp create mode 100644 code/PostProcessing/ArmaturePopulate.h diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index eec805b54..2c030abbb 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -670,6 +670,8 @@ SET( PostProcessing_SRCS PostProcessing/MakeVerboseFormat.h PostProcessing/ScaleProcess.cpp PostProcessing/ScaleProcess.h + PostProcessing/ArmaturePopulate.cpp + PostProcessing/ArmaturePopulate.h PostProcessing/GenBoundingBoxesProcess.cpp PostProcessing/GenBoundingBoxesProcess.h ) diff --git a/code/FBX/FBXConverter.cpp b/code/FBX/FBXConverter.cpp index 0d754a638..88abd6721 100644 --- a/code/FBX/FBXConverter.cpp +++ b/code/FBX/FBXConverter.cpp @@ -121,39 +121,6 @@ namespace Assimp { ConvertGlobalSettings(); TransferDataToScene(); - // Now convert all bone positions to the correct mOffsetMatrix - std::vector bones; - std::vector nodes; - std::map bone_stack; - BuildBoneList(out->mRootNode, out->mRootNode, out, bones); - BuildNodeList(out->mRootNode, nodes ); - - - BuildBoneStack(out->mRootNode, out->mRootNode, out, bones, bone_stack, nodes); - - std::cout << "Bone stack size: " << bone_stack.size() << std::endl; - - for( std::pair kvp : bone_stack ) - { - aiBone *bone = kvp.first; - aiNode *bone_node = kvp.second; - std::cout << "active node lookup: " << bone->mName.C_Str() << std::endl; - // lcl transform grab - done in generate_nodes :) - - //bone->mOffsetMatrix = bone_node->mTransformation; - aiNode * armature = GetArmatureRoot(bone_node, bones); - - ai_assert(armature); - - // set up bone armature id - bone->mArmature = armature; - - // set this bone node to be referenced properly - ai_assert(bone_node); - bone->mNode = bone_node; - } - - // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE // to make sure the scene passes assimp's validation. FBX files // need not contain geometry (i.e. camera animations, raw armatures). @@ -172,167 +139,6 @@ namespace Assimp { std::for_each(textures.begin(), textures.end(), Util::delete_fun()); } - /* Returns the armature root node */ - /* This is required to be detected for a bone initially, it will recurse up until it cannot find another - * bone and return the node - * No known failure points. (yet) - */ - aiNode * FBXConverter::GetArmatureRoot(aiNode *bone_node, std::vector &bone_list) - { - while(bone_node) - { - if(!IsBoneNode(bone_node->mName, bone_list)) - { - std::cout << "Found valid armature: " << bone_node->mName.C_Str() << std::endl; - return bone_node; - } - - bone_node = bone_node->mParent; - } - - std::cout << "can't find armature! node: " << bone_node << std::endl; - - return NULL; - } - - /* Simple IsBoneNode check if this could be a bone */ - bool FBXConverter::IsBoneNode(const aiString &bone_name, std::vector& bones ) - { - for( aiBone *bone : bones) - { - if(bone->mName == bone_name) - { - return true; - } - } - - return false; - } - - - /* Pop this node by name from the stack if found */ - /* Used in multiple armature situations with duplicate node / bone names */ - /* Known flaw: cannot have nodes with bone names, will be fixed in later release */ - /* (serious to be fixed) Known flaw: nodes which have more than one bone could be prematurely dropped from stack */ - aiNode* FBXConverter::GetNodeFromStack(const aiString &node_name, std::vector &nodes) - { - std::vector::iterator iter; - aiNode *found = NULL; - for( iter = nodes.begin(); iter < nodes.end(); ++iter ) - { - aiNode *element = *iter; - ai_assert(element); - // node valid and node name matches - if(element->mName == node_name) - { - found = element; - break; - } - } - - if(found != NULL) { - // now pop the element from the node list - nodes.erase(iter); - - return found; - } - return NULL; - } - - /* Prepare flat node list which can be used for non recursive lookups later */ - void FBXConverter::BuildNodeList(aiNode *current_node, std::vector &nodes) - { - ai_assert(current_node); - - for( unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) - { - aiNode *child = current_node->mChildren[nodeId]; - ai_assert(child); - - nodes.push_back(child); - - BuildNodeList(child, nodes); - } - } - - /* Reprocess all nodes to calculate bone transforms properly based on the REAL mOffsetMatrix not the local. */ - /* Before this would use mesh transforms which is wrong for bone transforms */ - /* Before this would work for simple character skeletons but not complex meshes with multiple origins */ - /* Source: sketch fab log cutter fbx */ - void FBXConverter::BuildBoneList(aiNode *current_node, const aiNode * root_node, const aiScene *scene, std::vector &bones ) - { - ai_assert(scene); - for( unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) - { - aiNode *child = current_node->mChildren[nodeId]; - ai_assert(child); - - // check for bones - for( unsigned int meshId = 0; meshId < child->mNumMeshes; ++meshId) - { - ai_assert(child->mMeshes); - unsigned int mesh_index = child->mMeshes[meshId]; - aiMesh *mesh = scene->mMeshes[ mesh_index ]; - ai_assert(mesh); - - for( unsigned int boneId = 0; boneId < mesh->mNumBones; ++boneId) - { - aiBone *bone = mesh->mBones[boneId]; - ai_assert(bone); - - // duplicate meshes exist with the same bones sometimes :) - // so this must be detected - if( std::find(bones.begin(), bones.end(), bone) == bones.end() ) - { - // add the element once - bones.push_back(bone); - } - } - - // find mesh and get bones - // then do recursive lookup for bones in root node hierarchy - } - - BuildBoneList(child, root_node, scene, bones); - } - } - - /* A bone stack allows us to have multiple armatures, with the same bone names - * A bone stack allows us also to retrieve bones true transform even with duplicate names :) - */ - void FBXConverter::BuildBoneStack(aiNode *current_node, const aiNode *root_node, const aiScene *scene, - const std::vector &bones, - std::map &bone_stack, - std::vector &node_stack ) - { - ai_assert(scene); - ai_assert(root_node); - ai_assert(!node_stack.empty()); - - for( aiBone * bone : bones) - { - ai_assert(bone); - aiNode* node = GetNodeFromStack(bone->mName, node_stack); - if(node == NULL) - { - node_stack.clear(); - BuildNodeList(out->mRootNode, node_stack ); - std::cout << "Resetting bone stack: null element " << bone->mName.C_Str() << std::endl; - - node = GetNodeFromStack(bone->mName, node_stack); - - if(!node) { - std::cout << "serious import issue armature failed to be detected?" << std::endl; - continue; - } - } - - std::cout << "Successfully added bone to stack and have valid armature: " << bone->mName.C_Str() << std::endl; - - bone_stack.insert(std::pair(bone, node)); - } - } - void FBXConverter::ConvertRootNode() { out->mRootNode = new aiNode(); std::string unique_name; diff --git a/code/PostProcessing/ArmaturePopulate.cpp b/code/PostProcessing/ArmaturePopulate.cpp new file mode 100644 index 000000000..1892645a9 --- /dev/null +++ b/code/PostProcessing/ArmaturePopulate.cpp @@ -0,0 +1,247 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 "ArmaturePopulate.h" + +#include +#include +#include +#include + +namespace Assimp { + +bool ArmaturePopulate::IsActive(unsigned int pFlags) const { + return (pFlags & aiProcess_PopulateArmatureData) != 0; +} + +void ArmaturePopulate::SetupProperties(const Importer *pImp) { + // do nothing +} + +void ArmaturePopulate::Execute(aiScene *out) { + + // Now convert all bone positions to the correct mOffsetMatrix + std::vector bones; + std::vector nodes; + std::map bone_stack; + BuildBoneList(out->mRootNode, out->mRootNode, out, bones); + BuildNodeList(out->mRootNode, nodes); + + BuildBoneStack(out->mRootNode, out->mRootNode, out, bones, bone_stack, nodes); + + ASSIMP_LOG_DEBUG_F("Bone stack size: %ld\n", bone_stack.size()); + + for (std::pair kvp : bone_stack) { + aiBone *bone = kvp.first; + aiNode *bone_node = kvp.second; + ASSIMP_LOG_DEBUG_F("active node lookup: %s\n", bone->mName.C_Str()); + // lcl transform grab - done in generate_nodes :) + + // bone->mOffsetMatrix = bone_node->mTransformation; + aiNode *armature = GetArmatureRoot(bone_node, bones); + + ai_assert(armature); + + // set up bone armature id + bone->mArmature = armature; + + // set this bone node to be referenced properly + ai_assert(bone_node); + bone->mNode = bone_node; + } +} + +/* Returns the armature root node */ +/* This is required to be detected for a bone initially, it will recurse up + * until it cannot find another bone and return the node No known failure + * points. (yet) + */ +aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node, + std::vector &bone_list) { + while (bone_node) { + if (!IsBoneNode(bone_node->mName, bone_list)) { + ASSIMP_LOG_DEBUG_F("Found valid armature: %s\n", bone_node->mName.C_Str()); + return bone_node; + } + + bone_node = bone_node->mParent; + } + + ASSIMP_LOG_WARN_F("can't find armature! node: %s\n", bone_node->mName.C_Str()); + + return NULL; +} + +/* Simple IsBoneNode check if this could be a bone */ +bool ArmaturePopulate::IsBoneNode(const aiString &bone_name, + std::vector &bones) { + for (aiBone *bone : bones) { + if (bone->mName == bone_name) { + return true; + } + } + + return false; +} + +/* Pop this node by name from the stack if found */ +/* Used in multiple armature situations with duplicate node / bone names */ +/* Known flaw: cannot have nodes with bone names, will be fixed in later release + */ +/* (serious to be fixed) Known flaw: nodes which have more than one bone could + * be prematurely dropped from stack */ +aiNode *ArmaturePopulate::GetNodeFromStack(const aiString &node_name, + std::vector &nodes) { + std::vector::iterator iter; + aiNode *found = NULL; + for (iter = nodes.begin(); iter < nodes.end(); ++iter) { + aiNode *element = *iter; + ai_assert(element); + // node valid and node name matches + if (element->mName == node_name) { + found = element; + break; + } + } + + if (found != NULL) { + // now pop the element from the node list + nodes.erase(iter); + + return found; + } + return NULL; +} + +/* Prepare flat node list which can be used for non recursive lookups later */ +void ArmaturePopulate::BuildNodeList(const aiNode *current_node, + std::vector &nodes) { + ai_assert(current_node); + + for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) { + aiNode *child = current_node->mChildren[nodeId]; + ai_assert(child); + + nodes.push_back(child); + + BuildNodeList(child, nodes); + } +} + +/* Reprocess all nodes to calculate bone transforms properly based on the REAL + * mOffsetMatrix not the local. */ +/* Before this would use mesh transforms which is wrong for bone transforms */ +/* Before this would work for simple character skeletons but not complex meshes + * with multiple origins */ +/* Source: sketch fab log cutter fbx */ +void ArmaturePopulate::BuildBoneList(aiNode *current_node, + const aiNode *root_node, + const aiScene *scene, + std::vector &bones) { + ai_assert(scene); + for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) { + aiNode *child = current_node->mChildren[nodeId]; + ai_assert(child); + + // check for bones + for (unsigned int meshId = 0; meshId < child->mNumMeshes; ++meshId) { + ai_assert(child->mMeshes); + unsigned int mesh_index = child->mMeshes[meshId]; + aiMesh *mesh = scene->mMeshes[mesh_index]; + ai_assert(mesh); + + for (unsigned int boneId = 0; boneId < mesh->mNumBones; ++boneId) { + aiBone *bone = mesh->mBones[boneId]; + ai_assert(bone); + + // duplicate meshes exist with the same bones sometimes :) + // so this must be detected + if (std::find(bones.begin(), bones.end(), bone) == bones.end()) { + // add the element once + bones.push_back(bone); + } + } + + // find mesh and get bones + // then do recursive lookup for bones in root node hierarchy + } + + BuildBoneList(child, root_node, scene, bones); + } +} + +/* A bone stack allows us to have multiple armatures, with the same bone names + * A bone stack allows us also to retrieve bones true transform even with + * duplicate names :) + */ +void ArmaturePopulate::BuildBoneStack(aiNode *current_node, + const aiNode *root_node, + const aiScene *scene, + const std::vector &bones, + std::map &bone_stack, + std::vector &node_stack) { + ai_assert(scene); + ai_assert(root_node); + ai_assert(!node_stack.empty()); + + for (aiBone *bone : bones) { + ai_assert(bone); + aiNode *node = GetNodeFromStack(bone->mName, node_stack); + if (node == NULL) { + node_stack.clear(); + BuildNodeList(root_node, node_stack); + ASSIMP_LOG_DEBUG_F("Resetting bone stack: null element %s\n", bone->mName.C_Str()); + + node = GetNodeFromStack(bone->mName, node_stack); + + if (!node) { + ASSIMP_LOG_ERROR("serious import issue armature failed to be detected"); + continue; + } + } + + ASSIMP_LOG_DEBUG_F("Successfully added bone to stack and have valid armature: %s\n", bone->mName.C_Str()); + + bone_stack.insert(std::pair(bone, node)); + } +} + +} // Namespace Assimp diff --git a/code/PostProcessing/ArmaturePopulate.h b/code/PostProcessing/ArmaturePopulate.h new file mode 100644 index 000000000..d84fbe09b --- /dev/null +++ b/code/PostProcessing/ArmaturePopulate.h @@ -0,0 +1,111 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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. + +---------------------------------------------------------------------- +*/ +#ifndef SCALE_PROCESS_H_ +#define SCALE_PROCESS_H_ + +#include "Common/BaseProcess.h" +#include +#include + +struct aiNode; +struct aiBone; + +#if (!defined AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT) +# define AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT 1.0f +#endif // !! AI_DEBONE_THRESHOLD + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** ScaleProcess: Class to rescale the whole model. + * Now rescales animations, bones, and blend shapes properly. + * Please note this will not write to 'scale' transform it will rewrite mesh + * and matrixes so that your scale values + * from your model package are preserved, so this is completely intentional + * bugs should be reported as soon as they are found. +*/ +class ASSIMP_API ArmaturePopulate : public BaseProcess { +public: + /// The default class constructor. + ArmaturePopulate() : BaseProcess() + {} + + /// The class destructor. + virtual ~ArmaturePopulate() + {} + + /// Overwritten, @see BaseProcess + virtual bool IsActive( unsigned int pFlags ) const; + + /// Overwritten, @see BaseProcess + virtual void SetupProperties( const Importer* pImp ); + + /// Overwritten, @see BaseProcess + virtual void Execute( aiScene* pScene ); + + static aiNode *GetArmatureRoot(aiNode *bone_node, + std::vector &bone_list); + + static bool IsBoneNode(const aiString &bone_name, + std::vector &bones); + + static aiNode *GetNodeFromStack(const aiString &node_name, + std::vector &nodes); + + static void BuildNodeList(const aiNode *current_node, + std::vector &nodes); + + static void BuildBoneList(aiNode *current_node, const aiNode *root_node, + const aiScene *scene, + std::vector &bones); + + static void BuildBoneStack(aiNode *current_node, const aiNode *root_node, + const aiScene *scene, + const std::vector &bones, + std::map &bone_stack, + std::vector &node_stack); +}; + +} // Namespace Assimp + + +#endif // SCALE_PROCESS_H_ \ No newline at end of file diff --git a/include/assimp/postprocess.h b/include/assimp/postprocess.h index 77d387c7e..2af48aedd 100644 --- a/include/assimp/postprocess.h +++ b/include/assimp/postprocess.h @@ -537,6 +537,18 @@ enum aiPostProcessSteps */ aiProcess_Debone = 0x4000000, + + // ------------------------------------------------------------------------- + /** + * This step generically populates aiBone->mArmature and aiBone->mNode generically + * The point of these is it saves you later having to calculate these elements + * This is useful when handling rest information or skin information + * If you have multiple armatures on your models we strongly recommend enabling this + * Instead of writing your own multi-root, multi-armature lookups we have done the + * hard work for you :) + */ + aiProcess_PopulateArmatureData = 0x5000000, + // ------------------------------------------------------------------------- /**


This step will perform a global scale of the model. * From 514257f58700385c15d23b27c11a7240cd433550 Mon Sep 17 00:00:00 2001 From: RevoluPowered Date: Sat, 26 Oct 2019 19:48:34 +0100 Subject: [PATCH 22/33] Added unit tests for ArmaturePopulate when used (added huestos model to tests) Added clear documentation for this too to explain, you need to enable it to make it available Signed-off-by: RevoluPowered --- code/Common/PostStepRegistry.cpp | 7 ++ code/PostProcessing/ArmaturePopulate.cpp | 14 +++- code/PostProcessing/ArmaturePopulate.h | 16 ++--- include/assimp/mesh.h | 4 ++ test/CMakeLists.txt | 1 + test/models/FBX/huesitos.fbx | Bin 0 -> 111564 bytes test/unit/utArmaturePopulate.cpp | 82 +++++++++++++++++++++++ 7 files changed, 111 insertions(+), 13 deletions(-) create mode 100644 test/models/FBX/huesitos.fbx create mode 100644 test/unit/utArmaturePopulate.cpp diff --git a/code/Common/PostStepRegistry.cpp b/code/Common/PostStepRegistry.cpp index ef58f8ddf..8ff4af040 100644 --- a/code/Common/PostStepRegistry.cpp +++ b/code/Common/PostStepRegistry.cpp @@ -131,11 +131,15 @@ corresponding preprocessor flag to selectively disable steps. #if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS) # include "PostProcessing/ScaleProcess.h" #endif +#if (!defined ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS) +# include "PostProcessing/ArmaturePopulate.h" +#endif #if (!defined ASSIMP_BUILD_NO_GENBOUNDINGBOXES_PROCESS) # include "PostProcessing/GenBoundingBoxesProcess.h" #endif + namespace Assimp { // ------------------------------------------------------------------------------------------------ @@ -180,6 +184,9 @@ void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out) #if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS) out.push_back( new ScaleProcess()); #endif +#if (!defined ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS) + out.push_back( new ArmaturePopulate()); +#endif #if (!defined ASSIMP_BUILD_NO_PRETRANSFORMVERTICES_PROCESS) out.push_back( new PretransformVertices()); #endif diff --git a/code/PostProcessing/ArmaturePopulate.cpp b/code/PostProcessing/ArmaturePopulate.cpp index 1892645a9..11fffc399 100644 --- a/code/PostProcessing/ArmaturePopulate.cpp +++ b/code/PostProcessing/ArmaturePopulate.cpp @@ -48,6 +48,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { +/// The default class constructor. +ArmaturePopulate::ArmaturePopulate() : BaseProcess() +{} + +/// The class destructor. +ArmaturePopulate::~ArmaturePopulate() +{} + bool ArmaturePopulate::IsActive(unsigned int pFlags) const { return (pFlags & aiProcess_PopulateArmatureData) != 0; } @@ -104,9 +112,9 @@ aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node, bone_node = bone_node->mParent; } - - ASSIMP_LOG_WARN_F("can't find armature! node: %s\n", bone_node->mName.C_Str()); - + + ASSIMP_LOG_WARN("GetArmatureRoot() can't find armature!"); + return NULL; } diff --git a/code/PostProcessing/ArmaturePopulate.h b/code/PostProcessing/ArmaturePopulate.h index d84fbe09b..30bfc7509 100644 --- a/code/PostProcessing/ArmaturePopulate.h +++ b/code/PostProcessing/ArmaturePopulate.h @@ -39,20 +39,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -#ifndef SCALE_PROCESS_H_ -#define SCALE_PROCESS_H_ +#ifndef ARMATURE_POPULATE_H_ +#define ARMATURE_POPULATE_H_ #include "Common/BaseProcess.h" +#include #include #include + struct aiNode; struct aiBone; -#if (!defined AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT) -# define AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT 1.0f -#endif // !! AI_DEBONE_THRESHOLD - namespace Assimp { // --------------------------------------------------------------------------- @@ -66,12 +64,10 @@ namespace Assimp { class ASSIMP_API ArmaturePopulate : public BaseProcess { public: /// The default class constructor. - ArmaturePopulate() : BaseProcess() - {} + ArmaturePopulate(); /// The class destructor. - virtual ~ArmaturePopulate() - {} + virtual ~ArmaturePopulate(); /// Overwritten, @see BaseProcess virtual bool IsActive( unsigned int pFlags ) const; diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index 617835e51..bde69ac9b 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -271,12 +271,16 @@ struct aiBone { //! The maximum value for this member is #AI_MAX_BONE_WEIGHTS. unsigned int mNumWeights; +#ifndef ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS // The bone armature node - used for skeleton conversion + // you must enable aiProcess_PopulateArmatureData to populate this C_STRUCT aiNode* mArmature; // The bone node in the scene - used for skeleton conversion + // you must enable aiProcess_PopulateArmatureData to populate this C_STRUCT aiNode* mNode; +#endif //! The influence weights of this bone, by vertex index. C_STRUCT aiVertexWeight* mWeights; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 913813c3b..f59ce000a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -148,6 +148,7 @@ SET( POST_PROCESSES unit/utRemoveRedundantMaterials.cpp unit/utRemoveVCProcess.cpp unit/utScaleProcess.cpp + unit/utArmaturePopulate.cpp unit/utJoinVertices.cpp unit/utRemoveComments.cpp unit/utRemoveComponent.cpp diff --git a/test/models/FBX/huesitos.fbx b/test/models/FBX/huesitos.fbx new file mode 100644 index 0000000000000000000000000000000000000000..646271392e5fbd5c3a069498778fb70ba302ebef GIT binary patch literal 111564 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 BGFAWp literal 0 HcmV?d00001 diff --git a/test/unit/utArmaturePopulate.cpp b/test/unit/utArmaturePopulate.cpp new file mode 100644 index 000000000..4aaf8aa7c --- /dev/null +++ b/test/unit/utArmaturePopulate.cpp @@ -0,0 +1,82 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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 "UnitTestPCH.h" +#include "TestModelFactory.h" + + +#include "SceneDiffer.h" +#include "AbstractImportExportBase.h" + +#include +#include +#include +#include +#include + +#include "PostProcessing/ArmaturePopulate.h" + +namespace Assimp { +namespace UnitTest { + +class utArmaturePopulate : public ::testing::Test { + // empty +}; + +TEST_F( utArmaturePopulate, importCheckForArmatureTest) { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/FBX/huesitos.fbx", aiProcess_ValidateDataStructure | aiProcess_PopulateArmatureData); + EXPECT_NE( nullptr, scene ); + EXPECT_EQ(scene->mNumMeshes, 1u); + aiMesh* mesh = scene->mMeshes[0]; + EXPECT_EQ(mesh->mNumFaces, 68u); + EXPECT_EQ(mesh->mNumVertices, 256u); + EXPECT_GT(mesh->mNumBones, 0u); + + aiBone* exampleBone = mesh->mBones[0]; + EXPECT_NE(exampleBone, nullptr); + EXPECT_NE(exampleBone->mArmature, nullptr); + EXPECT_NE(exampleBone->mNode, nullptr); +} + +} // Namespace UnitTest +} // Namespace Assimp From a30936954ef49236397938305e9c05e57670203c Mon Sep 17 00:00:00 2001 From: RevoluPowered Date: Sun, 27 Oct 2019 11:21:23 +0000 Subject: [PATCH 23/33] Best to check the number of children before checking the actual array --- code/Common/scene.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/Common/scene.cpp b/code/Common/scene.cpp index 2acb348d8..315581504 100644 --- a/code/Common/scene.cpp +++ b/code/Common/scene.cpp @@ -68,7 +68,7 @@ aiNode::aiNode(const std::string& name) aiNode::~aiNode() { // delete all children recursively // to make sure we won't crash if the data is invalid ... - if (mChildren && mNumChildren) + if (mNumChildren && mChildren) { for (unsigned int a = 0; a < mNumChildren; a++) delete mChildren[a]; From 5d0c63391b729cd7c839a9d8088625d1716c5fa5 Mon Sep 17 00:00:00 2001 From: RevoluPowered Date: Sun, 27 Oct 2019 11:21:49 +0000 Subject: [PATCH 24/33] Explicitly set the size of the parent node if we have no children --- code/FBX/FBXConverter.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/code/FBX/FBXConverter.cpp b/code/FBX/FBXConverter.cpp index 88abd6721..8f916a25b 100644 --- a/code/FBX/FBXConverter.cpp +++ b/code/FBX/FBXConverter.cpp @@ -313,6 +313,12 @@ namespace Assimp { std::swap_ranges(nodes.begin(), nodes.end(), parent->mChildren); } + else + { + parent->mNumChildren = 0; + parent->mChildren = nullptr; + } + } catch (std::exception&) { Util::delete_fun deleter; From 9c8d8357046e4d422b1ba20ffcf3b2da072c7774 Mon Sep 17 00:00:00 2001 From: RevoluPowered Date: Sun, 27 Oct 2019 11:29:56 +0000 Subject: [PATCH 25/33] Explicitly use nullptr --- code/Common/scene.cpp | 16 ++++++++-------- code/FBX/FBXConverter.cpp | 20 ++++++++++---------- code/PostProcessing/ArmaturePopulate.cpp | 12 ++++++------ include/assimp/mesh.h | 18 +++++++++--------- include/assimp/scene.h | 22 +++++++++++----------- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/code/Common/scene.cpp b/code/Common/scene.cpp index 315581504..d15619acf 100644 --- a/code/Common/scene.cpp +++ b/code/Common/scene.cpp @@ -44,23 +44,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. aiNode::aiNode() : mName("") -, mParent(NULL) +, mParent(nullptr) , mNumChildren(0) -, mChildren(NULL) +, mChildren(nullptr) , mNumMeshes(0) -, mMeshes(NULL) -, mMetaData(NULL) { +, mMeshes(nullptr) +, mMetaData(nullptr) { // empty } aiNode::aiNode(const std::string& name) : mName(name) -, mParent(NULL) +, mParent(nullptr) , mNumChildren(0) -, mChildren(NULL) +, mChildren(nullptr) , mNumMeshes(0) -, mMeshes(NULL) -, mMetaData(NULL) { +, mMeshes(nullptr) +, mMetaData(nullptr) { // empty } diff --git a/code/FBX/FBXConverter.cpp b/code/FBX/FBXConverter.cpp index 8f916a25b..20344331c 100644 --- a/code/FBX/FBXConverter.cpp +++ b/code/FBX/FBXConverter.cpp @@ -1134,7 +1134,7 @@ namespace Assimp { binormals = &tempBinormals; } else { - binormals = NULL; + binormals = nullptr; } } @@ -1184,7 +1184,7 @@ namespace Assimp { ConvertMaterialForMesh(out_mesh, model, mesh, mindices[0]); } - if (doc.Settings().readWeights && mesh.DeformerSkin() != NULL) { + if (doc.Settings().readWeights && mesh.DeformerSkin() != nullptr) { ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, NO_MATERIAL_SEPARATION, nullptr); } @@ -1264,7 +1264,7 @@ namespace Assimp { const std::vector& vertices = mesh.GetVertices(); const std::vector& faces = mesh.GetFaceIndexCounts(); - const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != NULL; + const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != nullptr; unsigned int count_faces = 0; unsigned int count_vertices = 0; @@ -1324,7 +1324,7 @@ namespace Assimp { binormals = &tempBinormals; } else { - binormals = NULL; + binormals = nullptr; } } @@ -1513,9 +1513,9 @@ namespace Assimp { unsigned int count = 0; const unsigned int* const out_idx = geo.ToOutputVertexIndex(index, count); - // ToOutputVertexIndex only returns NULL if index is out of bounds + // ToOutputVertexIndex only returns nullptr if index is out of bounds // which should never happen - ai_assert(out_idx != NULL); + ai_assert(out_idx != nullptr); index_out_indices.push_back(no_index_sentinel); count_out_indices.push_back(0); @@ -1586,7 +1586,7 @@ namespace Assimp { std::string deformer_name = cl->TargetNode()->Name(); aiString bone_name = aiString(FixNodeName(deformer_name)); - aiBone *bone = NULL; + aiBone *bone = nullptr; if (bone_map.count(deformer_name)) { std::cout << "retrieved bone from lookup " << bone_name.C_Str() << ". Deformer: " << deformer_name @@ -2740,7 +2740,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa // sanity check whether the input is ok static void validateAnimCurveNodes(const std::vector& curves, bool strictMode) { - const Object* target(NULL); + const Object* target(nullptr); for (const AnimationCurveNode* node : curves) { if (!target) { target = node->Target(); @@ -2771,7 +2771,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa #ifdef ASSIMP_BUILD_DEBUG validateAnimCurveNodes(curves, doc.Settings().strictMode); #endif - const AnimationCurveNode* curve_node = NULL; + const AnimationCurveNode* curve_node = nullptr; for (const AnimationCurveNode* node : curves) { ai_assert(node); @@ -3619,7 +3619,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa ai_assert(!out->mMeshes); ai_assert(!out->mNumMeshes); - // note: the trailing () ensures initialization with NULL - not + // note: the trailing () ensures initialization with nullptr - not // many C++ users seem to know this, so pointing it out to avoid // confusion why this code works. diff --git a/code/PostProcessing/ArmaturePopulate.cpp b/code/PostProcessing/ArmaturePopulate.cpp index 11fffc399..c677b193a 100644 --- a/code/PostProcessing/ArmaturePopulate.cpp +++ b/code/PostProcessing/ArmaturePopulate.cpp @@ -115,7 +115,7 @@ aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node, ASSIMP_LOG_WARN("GetArmatureRoot() can't find armature!"); - return NULL; + return nullptr; } /* Simple IsBoneNode check if this could be a bone */ @@ -139,7 +139,7 @@ bool ArmaturePopulate::IsBoneNode(const aiString &bone_name, aiNode *ArmaturePopulate::GetNodeFromStack(const aiString &node_name, std::vector &nodes) { std::vector::iterator iter; - aiNode *found = NULL; + aiNode *found = nullptr; for (iter = nodes.begin(); iter < nodes.end(); ++iter) { aiNode *element = *iter; ai_assert(element); @@ -150,13 +150,13 @@ aiNode *ArmaturePopulate::GetNodeFromStack(const aiString &node_name, } } - if (found != NULL) { + if (found != nullptr) { // now pop the element from the node list nodes.erase(iter); return found; } - return NULL; + return nullptr; } /* Prepare flat node list which can be used for non recursive lookups later */ @@ -233,10 +233,10 @@ void ArmaturePopulate::BuildBoneStack(aiNode *current_node, for (aiBone *bone : bones) { ai_assert(bone); aiNode *node = GetNodeFromStack(bone->mName, node_stack); - if (node == NULL) { + if (node == nullptr) { node_stack.clear(); BuildNodeList(root_node, node_stack); - ASSIMP_LOG_DEBUG_F("Resetting bone stack: null element %s\n", bone->mName.C_Str()); + ASSIMP_LOG_DEBUG_F("Resetting bone stack: nullptr element %s\n", bone->mName.C_Str()); node = GetNodeFromStack(bone->mName, node_stack); diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index bde69ac9b..fbf2a857a 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -435,11 +435,11 @@ struct aiAnimMesh /**Anim Mesh name */ C_STRUCT aiString mName; - /** Replacement for aiMesh::mVertices. If this array is non-NULL, + /** Replacement for aiMesh::mVertices. If this array is non-nullptr, * it *must* contain mNumVertices entries. The corresponding - * array in the host mesh must be non-NULL as well - animation + * array in the host mesh must be non-nullptr as well - animation * meshes may neither add or nor remove vertex components (if - * a replacement array is NULL and the corresponding source + * a replacement array is nullptr and the corresponding source * array is not, the source data is taken instead)*/ C_STRUCT aiVector3D* mVertices; @@ -613,7 +613,7 @@ struct aiMesh C_STRUCT aiVector3D* mVertices; /** Vertex normals. - * The array contains normalized vectors, NULL if not present. + * The array contains normalized vectors, nullptr if not present. * The array is mNumVertices in size. Normals are undefined for * point and line primitives. A mesh consisting of points and * lines only may not have normal vectors. Meshes with mixed @@ -636,7 +636,7 @@ struct aiMesh /** Vertex tangents. * The tangent of a vertex points in the direction of the positive - * X texture axis. The array contains normalized vectors, NULL if + * X texture axis. The array contains normalized vectors, nullptr if * not present. The array is mNumVertices in size. A mesh consisting * of points and lines only may not have normal vectors. Meshes with * mixed primitive types (i.e. lines and triangles) may have @@ -650,7 +650,7 @@ struct aiMesh /** Vertex bitangents. * The bitangent of a vertex points in the direction of the positive - * Y texture axis. The array contains normalized vectors, NULL if not + * Y texture axis. The array contains normalized vectors, nullptr if not * present. The array is mNumVertices in size. * @note If the mesh contains tangents, it automatically also contains * bitangents. @@ -659,14 +659,14 @@ struct aiMesh /** Vertex color sets. * A mesh may contain 0 to #AI_MAX_NUMBER_OF_COLOR_SETS vertex - * colors per vertex. NULL if not present. Each array is + * colors per vertex. nullptr if not present. Each array is * mNumVertices in size if present. */ C_STRUCT aiColor4D* mColors[AI_MAX_NUMBER_OF_COLOR_SETS]; /** Vertex texture coords, also known as UV channels. * A mesh may contain 0 to AI_MAX_NUMBER_OF_TEXTURECOORDS per - * vertex. NULL if not present. The array is mNumVertices in size. + * vertex. nullptr if not present. The array is mNumVertices in size. */ C_STRUCT aiVector3D* mTextureCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; @@ -688,7 +688,7 @@ struct aiMesh C_STRUCT aiFace* mFaces; /** The number of bones this mesh contains. - * Can be 0, in which case the mBones array is NULL. + * Can be 0, in which case the mBones array is nullptr. */ unsigned int mNumBones; diff --git a/include/assimp/scene.h b/include/assimp/scene.h index e69c81803..b76709eb1 100644 --- a/include/assimp/scene.h +++ b/include/assimp/scene.h @@ -110,13 +110,13 @@ struct ASSIMP_API aiNode /** The transformation relative to the node's parent. */ C_STRUCT aiMatrix4x4 mTransformation; - /** Parent node. NULL if this node is the root node. */ + /** Parent node. nullptr if this node is the root node. */ C_STRUCT aiNode* mParent; /** The number of child nodes of this node. */ unsigned int mNumChildren; - /** The child nodes of this node. NULL if mNumChildren is 0. */ + /** The child nodes of this node. nullptr if mNumChildren is 0. */ C_STRUCT aiNode** mChildren; /** The number of meshes of this node. */ @@ -127,7 +127,7 @@ struct ASSIMP_API aiNode */ unsigned int* mMeshes; - /** Metadata associated with this node or NULL if there is no metadata. + /** Metadata associated with this node or nullptr if there is no metadata. * Whether any metadata is generated depends on the source file format. See the * @link importer_notes @endlink page for more information on every source file * format. Importers that don't document any metadata don't write any. @@ -149,7 +149,7 @@ struct ASSIMP_API aiNode * of the scene. * * @param name Name to search for - * @return NULL or a valid Node if the search was successful. + * @return nullptr or a valid Node if the search was successful. */ inline const aiNode* FindNode(const aiString& name) const { @@ -344,7 +344,7 @@ struct aiScene #ifdef __cplusplus - //! Default constructor - set everything to 0/NULL + //! Default constructor - set everything to 0/nullptr ASSIMP_API aiScene(); //! Destructor @@ -353,33 +353,33 @@ struct aiScene //! Check whether the scene contains meshes //! Unless no special scene flags are set this will always be true. inline bool HasMeshes() const { - return mMeshes != NULL && mNumMeshes > 0; + return mMeshes != nullptr && mNumMeshes > 0; } //! Check whether the scene contains materials //! Unless no special scene flags are set this will always be true. inline bool HasMaterials() const { - return mMaterials != NULL && mNumMaterials > 0; + return mMaterials != nullptr && mNumMaterials > 0; } //! Check whether the scene contains lights inline bool HasLights() const { - return mLights != NULL && mNumLights > 0; + return mLights != nullptr && mNumLights > 0; } //! Check whether the scene contains textures inline bool HasTextures() const { - return mTextures != NULL && mNumTextures > 0; + return mTextures != nullptr && mNumTextures > 0; } //! Check whether the scene contains cameras inline bool HasCameras() const { - return mCameras != NULL && mNumCameras > 0; + return mCameras != nullptr && mNumCameras > 0; } //! Check whether the scene contains animations inline bool HasAnimations() const { - return mAnimations != NULL && mNumAnimations > 0; + return mAnimations != nullptr && mNumAnimations > 0; } //! Returns a short filename from a full path From 212bcfe75c15bc21a14a715fa4e06886a41d944a Mon Sep 17 00:00:00 2001 From: RevoluPowered Date: Sun, 27 Oct 2019 12:27:54 +0000 Subject: [PATCH 26/33] Test disable cache --- code/PostProcessing/ArmaturePopulate.cpp | 3 ++- test/unit/utArmaturePopulate.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/code/PostProcessing/ArmaturePopulate.cpp b/code/PostProcessing/ArmaturePopulate.cpp index c677b193a..5de2a3870 100644 --- a/code/PostProcessing/ArmaturePopulate.cpp +++ b/code/PostProcessing/ArmaturePopulate.cpp @@ -45,6 +45,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include namespace Assimp { @@ -76,7 +77,7 @@ void ArmaturePopulate::Execute(aiScene *out) { BuildBoneStack(out->mRootNode, out->mRootNode, out, bones, bone_stack, nodes); ASSIMP_LOG_DEBUG_F("Bone stack size: %ld\n", bone_stack.size()); - + std::cout << "post process for armature population has run!" << std::endl; for (std::pair kvp : bone_stack) { aiBone *bone = kvp.first; aiNode *bone_node = kvp.second; diff --git a/test/unit/utArmaturePopulate.cpp b/test/unit/utArmaturePopulate.cpp index 4aaf8aa7c..835d3fdb9 100644 --- a/test/unit/utArmaturePopulate.cpp +++ b/test/unit/utArmaturePopulate.cpp @@ -64,7 +64,7 @@ class utArmaturePopulate : public ::testing::Test { TEST_F( utArmaturePopulate, importCheckForArmatureTest) { Assimp::Importer importer; - const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/FBX/huesitos.fbx", aiProcess_ValidateDataStructure | aiProcess_PopulateArmatureData); + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/FBX/huesitos.fbx", aiProcess_PopulateArmatureData); EXPECT_NE( nullptr, scene ); EXPECT_EQ(scene->mNumMeshes, 1u); aiMesh* mesh = scene->mMeshes[0]; From a9a0d4d29b8875f37da55b8e8d621732be24e471 Mon Sep 17 00:00:00 2001 From: RevoluPowered Date: Sun, 27 Oct 2019 13:53:11 +0000 Subject: [PATCH 27/33] Tidying order of function calls and fixed debug statements --- code/PostProcessing/ArmaturePopulate.cpp | 176 ++++++++++++----------- 1 file changed, 94 insertions(+), 82 deletions(-) diff --git a/code/PostProcessing/ArmaturePopulate.cpp b/code/PostProcessing/ArmaturePopulate.cpp index 5de2a3870..75daeb6b5 100644 --- a/code/PostProcessing/ArmaturePopulate.cpp +++ b/code/PostProcessing/ArmaturePopulate.cpp @@ -76,12 +76,12 @@ void ArmaturePopulate::Execute(aiScene *out) { BuildBoneStack(out->mRootNode, out->mRootNode, out, bones, bone_stack, nodes); - ASSIMP_LOG_DEBUG_F("Bone stack size: %ld\n", bone_stack.size()); - std::cout << "post process for armature population has run!" << std::endl; + ASSIMP_LOG_DEBUG_F("Bone stack size: ", bone_stack.size()); + for (std::pair kvp : bone_stack) { aiBone *bone = kvp.first; aiNode *bone_node = kvp.second; - ASSIMP_LOG_DEBUG_F("active node lookup: %s\n", bone->mName.C_Str()); + ASSIMP_LOG_DEBUG_F("active node lookup: ", bone->mName.C_Str()); // lcl transform grab - done in generate_nodes :) // bone->mOffsetMatrix = bone_node->mTransformation; @@ -98,82 +98,6 @@ void ArmaturePopulate::Execute(aiScene *out) { } } -/* Returns the armature root node */ -/* This is required to be detected for a bone initially, it will recurse up - * until it cannot find another bone and return the node No known failure - * points. (yet) - */ -aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node, - std::vector &bone_list) { - while (bone_node) { - if (!IsBoneNode(bone_node->mName, bone_list)) { - ASSIMP_LOG_DEBUG_F("Found valid armature: %s\n", bone_node->mName.C_Str()); - return bone_node; - } - - bone_node = bone_node->mParent; - } - - ASSIMP_LOG_WARN("GetArmatureRoot() can't find armature!"); - - return nullptr; -} - -/* Simple IsBoneNode check if this could be a bone */ -bool ArmaturePopulate::IsBoneNode(const aiString &bone_name, - std::vector &bones) { - for (aiBone *bone : bones) { - if (bone->mName == bone_name) { - return true; - } - } - - return false; -} - -/* Pop this node by name from the stack if found */ -/* Used in multiple armature situations with duplicate node / bone names */ -/* Known flaw: cannot have nodes with bone names, will be fixed in later release - */ -/* (serious to be fixed) Known flaw: nodes which have more than one bone could - * be prematurely dropped from stack */ -aiNode *ArmaturePopulate::GetNodeFromStack(const aiString &node_name, - std::vector &nodes) { - std::vector::iterator iter; - aiNode *found = nullptr; - for (iter = nodes.begin(); iter < nodes.end(); ++iter) { - aiNode *element = *iter; - ai_assert(element); - // node valid and node name matches - if (element->mName == node_name) { - found = element; - break; - } - } - - if (found != nullptr) { - // now pop the element from the node list - nodes.erase(iter); - - return found; - } - return nullptr; -} - -/* Prepare flat node list which can be used for non recursive lookups later */ -void ArmaturePopulate::BuildNodeList(const aiNode *current_node, - std::vector &nodes) { - ai_assert(current_node); - - for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) { - aiNode *child = current_node->mChildren[nodeId]; - ai_assert(child); - - nodes.push_back(child); - - BuildNodeList(child, nodes); - } -} /* Reprocess all nodes to calculate bone transforms properly based on the REAL * mOffsetMatrix not the local. */ @@ -217,6 +141,21 @@ void ArmaturePopulate::BuildBoneList(aiNode *current_node, } } +/* Prepare flat node list which can be used for non recursive lookups later */ +void ArmaturePopulate::BuildNodeList(const aiNode *current_node, + std::vector &nodes) { + ai_assert(current_node); + + for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) { + aiNode *child = current_node->mChildren[nodeId]; + ai_assert(child); + + nodes.push_back(child); + + BuildNodeList(child, nodes); + } +} + /* A bone stack allows us to have multiple armatures, with the same bone names * A bone stack allows us also to retrieve bones true transform even with * duplicate names :) @@ -237,20 +176,93 @@ void ArmaturePopulate::BuildBoneStack(aiNode *current_node, if (node == nullptr) { node_stack.clear(); BuildNodeList(root_node, node_stack); - ASSIMP_LOG_DEBUG_F("Resetting bone stack: nullptr element %s\n", bone->mName.C_Str()); + ASSIMP_LOG_DEBUG_F("Resetting bone stack: nullptr element ", bone->mName.C_Str()); node = GetNodeFromStack(bone->mName, node_stack); if (!node) { - ASSIMP_LOG_ERROR("serious import issue armature failed to be detected"); + ASSIMP_LOG_ERROR("serious import issue node for bone was not detected"); continue; } } - ASSIMP_LOG_DEBUG_F("Successfully added bone to stack and have valid armature: %s\n", bone->mName.C_Str()); + ASSIMP_LOG_DEBUG_F("Successfully added bone[", bone->mName.C_Str(), "] to stack and bone node is: ", node->mName.C_Str()); bone_stack.insert(std::pair(bone, node)); } } + +/* Returns the armature root node */ +/* This is required to be detected for a bone initially, it will recurse up + * until it cannot find another bone and return the node No known failure + * points. (yet) + */ +aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node, + std::vector &bone_list) { + while (bone_node) { + if (!IsBoneNode(bone_node->mName, bone_list)) { + ASSIMP_LOG_DEBUG_F("GetArmatureRoot() Found valid armature: ", bone_node->mName.C_Str()); + return bone_node; + } + + bone_node = bone_node->mParent; + } + + ASSIMP_LOG_ERROR("GetArmatureRoot() can't find armature!"); + + return nullptr; +} + + + +/* Simple IsBoneNode check if this could be a bone */ +bool ArmaturePopulate::IsBoneNode(const aiString &bone_name, + std::vector &bones) { + for (aiBone *bone : bones) { + if (bone->mName == bone_name) { + return true; + } + } + + return false; +} + +/* Pop this node by name from the stack if found */ +/* Used in multiple armature situations with duplicate node / bone names */ +/* Known flaw: cannot have nodes with bone names, will be fixed in later release + */ +/* (serious to be fixed) Known flaw: nodes which have more than one bone could + * be prematurely dropped from stack */ +aiNode *ArmaturePopulate::GetNodeFromStack(const aiString &node_name, + std::vector &nodes) { + std::vector::iterator iter; + aiNode *found = nullptr; + for (iter = nodes.begin(); iter < nodes.end(); ++iter) { + aiNode *element = *iter; + ai_assert(element); + // node valid and node name matches + if (element->mName == node_name) { + found = element; + break; + } + } + + if (found != nullptr) { + ASSIMP_LOG_INFO_F("Removed node from stack: ", found->mName.C_Str()); + // now pop the element from the node list + nodes.erase(iter); + + return found; + } + + // unique names can cause this problem + ASSIMP_LOG_ERROR("[Serious] GetNodeFromStack() can't find node from stack!"); + + return nullptr; +} + + + + } // Namespace Assimp From 5155efe888530daaee69ec098f9627f7a14a9e68 Mon Sep 17 00:00:00 2001 From: RevoluPowered Date: Sun, 27 Oct 2019 13:53:43 +0000 Subject: [PATCH 28/33] Fixed bitmask issue We are approaching the limit for the number of post processes --- include/assimp/postprocess.h | 23 +++++++++++++---------- test/unit/utArmaturePopulate.cpp | 3 ++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/include/assimp/postprocess.h b/include/assimp/postprocess.h index 2af48aedd..4b6732e80 100644 --- a/include/assimp/postprocess.h +++ b/include/assimp/postprocess.h @@ -320,6 +320,19 @@ enum aiPostProcessSteps */ aiProcess_FixInfacingNormals = 0x2000, + + + // ------------------------------------------------------------------------- + /** + * This step generically populates aiBone->mArmature and aiBone->mNode generically + * The point of these is it saves you later having to calculate these elements + * This is useful when handling rest information or skin information + * If you have multiple armatures on your models we strongly recommend enabling this + * Instead of writing your own multi-root, multi-armature lookups we have done the + * hard work for you :) + */ + aiProcess_PopulateArmatureData = 0x4000, + // ------------------------------------------------------------------------- /**
This step splits meshes with more than one primitive type in * homogeneous sub-meshes. @@ -538,16 +551,6 @@ enum aiPostProcessSteps aiProcess_Debone = 0x4000000, - // ------------------------------------------------------------------------- - /** - * This step generically populates aiBone->mArmature and aiBone->mNode generically - * The point of these is it saves you later having to calculate these elements - * This is useful when handling rest information or skin information - * If you have multiple armatures on your models we strongly recommend enabling this - * Instead of writing your own multi-root, multi-armature lookups we have done the - * hard work for you :) - */ - aiProcess_PopulateArmatureData = 0x5000000, // ------------------------------------------------------------------------- /**
This step will perform a global scale of the model. diff --git a/test/unit/utArmaturePopulate.cpp b/test/unit/utArmaturePopulate.cpp index 835d3fdb9..8eb577d61 100644 --- a/test/unit/utArmaturePopulate.cpp +++ b/test/unit/utArmaturePopulate.cpp @@ -64,7 +64,8 @@ class utArmaturePopulate : public ::testing::Test { TEST_F( utArmaturePopulate, importCheckForArmatureTest) { Assimp::Importer importer; - const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/FBX/huesitos.fbx", aiProcess_PopulateArmatureData); + unsigned int mask = aiProcess_PopulateArmatureData | aiProcess_ValidateDataStructure; + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/FBX/huesitos.fbx", mask); EXPECT_NE( nullptr, scene ); EXPECT_EQ(scene->mNumMeshes, 1u); aiMesh* mesh = scene->mMeshes[0]; From d7d79db0ac2a55f3138bcc1bc1799f00646f0b61 Mon Sep 17 00:00:00 2001 From: RevoluPowered Date: Sun, 27 Oct 2019 12:57:47 +0000 Subject: [PATCH 29/33] Tests should always debug log --- test/unit/Main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/Main.cpp b/test/unit/Main.cpp index 5ba5c487d..333fd655d 100644 --- a/test/unit/Main.cpp +++ b/test/unit/Main.cpp @@ -16,7 +16,7 @@ int main(int argc, char* argv[]) // create a logger from both CPP Assimp::DefaultLogger::create("AssimpLog_Cpp.txt",Assimp::Logger::VERBOSE, - aiDefaultLogStream_DEBUGGER | aiDefaultLogStream_FILE); + aiDefaultLogStream_STDOUT | aiDefaultLogStream_DEBUGGER | aiDefaultLogStream_FILE); // .. and C. They should smoothly work together aiEnableVerboseLogging(AI_TRUE); From 02a63f8b1070e71bc9ee67d611d18078966451aa Mon Sep 17 00:00:00 2001 From: RevoluPowered Date: Tue, 29 Oct 2019 20:21:16 +0000 Subject: [PATCH 30/33] Fixed template being used in file --- code/PostProcessing/ArmaturePopulate.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/code/PostProcessing/ArmaturePopulate.h b/code/PostProcessing/ArmaturePopulate.h index 30bfc7509..aa1ad7c80 100644 --- a/code/PostProcessing/ArmaturePopulate.h +++ b/code/PostProcessing/ArmaturePopulate.h @@ -54,12 +54,17 @@ struct aiBone; namespace Assimp { // --------------------------------------------------------------------------- -/** ScaleProcess: Class to rescale the whole model. - * Now rescales animations, bones, and blend shapes properly. - * Please note this will not write to 'scale' transform it will rewrite mesh - * and matrixes so that your scale values - * from your model package are preserved, so this is completely intentional - * bugs should be reported as soon as they are found. +/** Armature Populate: This is a post process designed + * To save you time when importing models into your game engines + * This was originally designed only for fbx but will work with other formats + * it is intended to auto populate aiBone data with armature and the aiNode + * This is very useful when dealing with skinned meshes + * or when dealing with many different skeletons + * It's off by default but recommend that you try it and use it + * It should reduce down any glue code you have in your + * importers + * You can contact RevoluPowered + * For more info about this */ class ASSIMP_API ArmaturePopulate : public BaseProcess { public: From 55c2a3edb9377bf325b2a5606b52f4ac8d094513 Mon Sep 17 00:00:00 2001 From: Mike Samsonov Date: Fri, 1 Nov 2019 12:29:54 +0000 Subject: [PATCH 31/33] FBX orphant embedded textures --- code/FBX/FBXCompileConfig.h | 8 + code/FBX/FBXConverter.cpp | 57 +- code/FBX/FBXConverter.h | 14 +- code/FBX/FBXDocument.h | 34 +- .../FBX/box_orphant_embedded_texture.fbx | 723 ++++++++++++++++++ test/unit/utFBXImporterExporter.cpp | 20 + 6 files changed, 845 insertions(+), 11 deletions(-) create mode 100644 test/models/FBX/box_orphant_embedded_texture.fbx diff --git a/code/FBX/FBXCompileConfig.h b/code/FBX/FBXCompileConfig.h index 3a3841fa5..03536a182 100644 --- a/code/FBX/FBXCompileConfig.h +++ b/code/FBX/FBXCompileConfig.h @@ -47,6 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define INCLUDED_AI_FBX_COMPILECONFIG_H #include +#include // #if _MSC_VER > 1500 || (defined __GNUC___) @@ -54,16 +55,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # else # define fbx_unordered_map map # define fbx_unordered_multimap multimap +# define fbx_unordered_set set +# define fbx_unordered_multiset multiset #endif #ifdef ASSIMP_FBX_USE_UNORDERED_MULTIMAP # include +# include # if _MSC_VER > 1600 # define fbx_unordered_map unordered_map # define fbx_unordered_multimap unordered_multimap +# define fbx_unordered_set unordered_set +# define fbx_unordered_multiset unordered_multiset # else # define fbx_unordered_map tr1::unordered_map # define fbx_unordered_multimap tr1::unordered_multimap +# define fbx_unordered_set tr1::unordered_set +# define fbx_unordered_multiset tr1::unordered_multiset # endif #endif diff --git a/code/FBX/FBXConverter.cpp b/code/FBX/FBXConverter.cpp index 20344331c..d8a22d9f7 100644 --- a/code/FBX/FBXConverter.cpp +++ b/code/FBX/FBXConverter.cpp @@ -97,6 +97,14 @@ namespace Assimp { // populate the node_anim_chain_bits map, which is needed // to determine which nodes need to be generated. ConvertAnimations(); + // Embedded textures in FBX could be connected to nothing but to itself, + // for instance Texture -> Video connection only but not to the main graph, + // The idea here is to traverse all objects to find these Textures and convert them, + // so later during material conversion it will find converted texture in the textures_converted array. + if (doc.Settings().readTextures) + { + ConvertOrphantEmbeddedTextures(); + } ConvertRootNode(); if (doc.Settings().readAllMaterials) { @@ -1774,7 +1782,7 @@ namespace Assimp { bool textureReady = false; //tells if our texture is ready (if it was loaded or if it was found) unsigned int index; - VideoMap::const_iterator it = textures_converted.find(media); + VideoMap::const_iterator it = textures_converted.find(*media); if (it != textures_converted.end()) { index = (*it).second; textureReady = true; @@ -1782,7 +1790,7 @@ namespace Assimp { else { if (media->ContentLength() > 0) { index = ConvertVideo(*media); - textures_converted[media] = index; + textures_converted[*media] = index; textureReady = true; } } @@ -2306,13 +2314,13 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa if (media != nullptr && media->ContentLength() > 0) { unsigned int index; - VideoMap::const_iterator it = textures_converted.find(media); + VideoMap::const_iterator it = textures_converted.find(*media); if (it != textures_converted.end()) { index = (*it).second; } else { index = ConvertVideo(*media); - textures_converted[media] = index; + textures_converted[*media] = index; } // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) @@ -3666,6 +3674,47 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa } } + void FBXConverter::ConvertOrphantEmbeddedTextures() + { + // in C++14 it could be: + // for (auto&& [id, object] : objects) + for (auto&& id_and_object : doc.Objects()) + { + auto&& id = std::get<0>(id_and_object); + auto&& object = std::get<1>(id_and_object); + // If an object doesn't have parent + if (doc.ConnectionsBySource().count(id) == 0) + { + const Texture* realTexture = nullptr; + try + { + const auto& element = object->GetElement(); + const Token& key = element.KeyToken(); + const char* obtype = key.begin(); + const size_t length = static_cast(key.end() - key.begin()); + if (strncmp(obtype, "Texture", length) == 0) + { + const Texture* texture = static_cast(object->Get()); + if (texture->Media() && texture->Media()->ContentLength() > 0) + { + realTexture = texture; + } + } + } + catch (...) + { + // do nothing + } + if (realTexture) + { + const Video* media = realTexture->Media(); + unsigned int index = ConvertVideo(*media); + textures_converted[*media] = index; + } + } + } + } + // ------------------------------------------------------------------------------------------------ void ConvertToAssimpScene(aiScene* out, const Document& doc, bool removeEmptyBones) { diff --git a/code/FBX/FBXConverter.h b/code/FBX/FBXConverter.h index 619da92c1..46693bdca 100644 --- a/code/FBX/FBXConverter.h +++ b/code/FBX/FBXConverter.h @@ -427,6 +427,10 @@ private: // copy generated meshes, animations, lights, cameras and textures to the output scene void TransferDataToScene(); + // ------------------------------------------------------------------------------------------------ + // FBX file could have embedded textures not connected to anything + void ConvertOrphantEmbeddedTextures(); + private: // 0: not assigned yet, others: index is value - 1 unsigned int defaultMaterialIndex; @@ -438,21 +442,21 @@ private: std::vector cameras; std::vector textures; - using MaterialMap = std::map; + using MaterialMap = std::fbx_unordered_map; MaterialMap materials_converted; - using VideoMap = std::map; + using VideoMap = std::fbx_unordered_map; VideoMap textures_converted; - using MeshMap = std::map >; + using MeshMap = std::fbx_unordered_map >; MeshMap meshes_converted; // fixed node name -> which trafo chain components have animations? - using NodeAnimBitMap = std::map ; + using NodeAnimBitMap = std::fbx_unordered_map ; NodeAnimBitMap node_anim_chain_bits; // number of nodes with the same name - using NodeNameCache = std::unordered_map; + using NodeNameCache = std::fbx_unordered_map; NodeNameCache mNodeNames; // Deformer name is not the same as a bone name - it does contain the bone name though :) diff --git a/code/FBX/FBXDocument.h b/code/FBX/FBXDocument.h index 18e5c38f1..51c98da49 100644 --- a/code/FBX/FBXDocument.h +++ b/code/FBX/FBXDocument.h @@ -637,6 +637,15 @@ public: return ptr; } + bool operator==(const Video& other) const + { + return ( + type == other.type + && relativeFileName == other.relativeFileName + && fileName == other.fileName + ); + } + private: std::string type; std::string relativeFileName; @@ -1005,10 +1014,10 @@ public: // during their entire lifetime (Document). FBX files have // up to many thousands of objects (most of which we never use), // so the memory overhead for them should be kept at a minimum. -typedef std::map ObjectMap; +typedef std::fbx_unordered_map ObjectMap; typedef std::fbx_unordered_map > PropertyTemplateMap; -typedef std::multimap ConnectionMap; +typedef std::fbx_unordered_multimap ConnectionMap; /** DOM class for global document settings, a single instance per document can * be accessed via Document.Globals(). */ @@ -1177,4 +1186,25 @@ private: } // Namespace FBX } // Namespace Assimp +namespace std +{ + template <> + struct hash + { + std::size_t operator()(const Assimp::FBX::Video& video) const + { + using std::size_t; + using std::hash; + using std::string; + + size_t res = 17; + res = res * 31 + hash()(video.Name()); + res = res * 31 + hash()(video.RelativeFilename()); + res = res * 31 + hash()(video.Type()); + + return res; + } + }; +} + #endif // INCLUDED_AI_FBX_DOCUMENT_H diff --git a/test/models/FBX/box_orphant_embedded_texture.fbx b/test/models/FBX/box_orphant_embedded_texture.fbx new file mode 100644 index 000000000..d874751d0 --- /dev/null +++ b/test/models/FBX/box_orphant_embedded_texture.fbx @@ -0,0 +1,723 @@ +; FBX 7.5.0 project file +; ---------------------------------------------------- + +FBXHeaderExtension: { + FBXHeaderVersion: 1003 + FBXVersion: 7500 + CreationTimeStamp: { + Version: 1000 + Year: 2019 + Month: 3 + Day: 1 + Hour: 12 + Minute: 46 + Second: 3 + Millisecond: 995 + } + Creator: "FBX SDK/FBX Plugins version 2018.1.1" + SceneInfo: "SceneInfo::GlobalInfo", "UserData" { + Type: "UserData" + Version: 100 + MetaData: { + Version: 100 + Title: "" + Subject: "" + Author: "" + Keywords: "" + Revision: "" + Comment: "" + } + Properties70: { + P: "DocumentUrl", "KString", "Url", "", "U:\Some\Absolute\Path\box_embedded_texture.fbx" + P: "SrcDocumentUrl", "KString", "Url", "", "U:\Some\Absolute\Path\box_embedded_texture.fbx" + P: "Original", "Compound", "", "" + P: "Original|ApplicationVendor", "KString", "", "", "Autodesk" + P: "Original|ApplicationName", "KString", "", "", "Maya" + P: "Original|ApplicationVersion", "KString", "", "", "201800" + P: "Original|DateTime_GMT", "DateTime", "", "", "01/03/2019 12:46:03.994" + P: "Original|FileName", "KString", "", "", "U:\Some\Absolute\Path\box_embedded_texture.fbx" + P: "LastSaved", "Compound", "", "" + P: "LastSaved|ApplicationVendor", "KString", "", "", "Autodesk" + P: "LastSaved|ApplicationName", "KString", "", "", "Maya" + P: "LastSaved|ApplicationVersion", "KString", "", "", "201800" + P: "LastSaved|DateTime_GMT", "DateTime", "", "", "01/03/2019 12:46:03.994" + P: "Original|ApplicationActiveProject", "KString", "", "", "U:\Some\Absolute\Path" + } + } +} +GlobalSettings: { + Version: 1000 + Properties70: { + P: "UpAxis", "int", "Integer", "",1 + P: "UpAxisSign", "int", "Integer", "",1 + P: "FrontAxis", "int", "Integer", "",2 + P: "FrontAxisSign", "int", "Integer", "",1 + P: "CoordAxis", "int", "Integer", "",0 + P: "CoordAxisSign", "int", "Integer", "",1 + P: "OriginalUpAxis", "int", "Integer", "",1 + P: "OriginalUpAxisSign", "int", "Integer", "",1 + P: "UnitScaleFactor", "double", "Number", "",100 + P: "OriginalUnitScaleFactor", "double", "Number", "",1 + P: "AmbientColor", "ColorRGB", "Color", "",0,0,0 + P: "DefaultCamera", "KString", "", "", "Producer Perspective" + P: "TimeMode", "enum", "", "",6 + P: "TimeProtocol", "enum", "", "",2 + P: "SnapOnFrameMode", "enum", "", "",0 + P: "TimeSpanStart", "KTime", "Time", "",0 + P: "TimeSpanStop", "KTime", "Time", "",153953860000 + P: "CustomFrameRate", "double", "Number", "",-1 + P: "TimeMarker", "Compound", "", "" + P: "CurrentTimeMarker", "int", "Integer", "",-1 + } +} + +; Documents Description +;------------------------------------------------------------------ + +Documents: { + Count: 1 + Document: 2957686739424, "", "Scene" { + Properties70: { + P: "SourceObject", "object", "", "" + P: "ActiveAnimStackName", "KString", "", "", "Take 001" + } + RootNode: 0 + } +} + +; Document References +;------------------------------------------------------------------ + +References: { +} + +; Object definitions +;------------------------------------------------------------------ + +Definitions: { + Version: 100 + Count: 17 + ObjectType: "GlobalSettings" { + Count: 1 + } + ObjectType: "AnimationStack" { + Count: 1 + PropertyTemplate: "FbxAnimStack" { + Properties70: { + P: "Description", "KString", "", "", "" + P: "LocalStart", "KTime", "Time", "",0 + P: "LocalStop", "KTime", "Time", "",0 + P: "ReferenceStart", "KTime", "Time", "",0 + P: "ReferenceStop", "KTime", "Time", "",0 + } + } + } + ObjectType: "AnimationLayer" { + Count: 1 + PropertyTemplate: "FbxAnimLayer" { + Properties70: { + P: "Weight", "Number", "", "A",100 + P: "Mute", "bool", "", "",0 + P: "Solo", "bool", "", "",0 + P: "Lock", "bool", "", "",0 + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "BlendMode", "enum", "", "",0 + P: "RotationAccumulationMode", "enum", "", "",0 + P: "ScaleAccumulationMode", "enum", "", "",0 + P: "BlendModeBypass", "ULongLong", "", "",0 + } + } + } + ObjectType: "Geometry" { + Count: 1 + PropertyTemplate: "FbxMesh" { + Properties70: { + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "BBoxMin", "Vector3D", "Vector", "",0,0,0 + P: "BBoxMax", "Vector3D", "Vector", "",0,0,0 + P: "Primary Visibility", "bool", "", "",1 + P: "Casts Shadows", "bool", "", "",1 + P: "Receive Shadows", "bool", "", "",1 + } + } + } + ObjectType: "Material" { + Count: 1 + PropertyTemplate: "FbxSurfacePhong" { + Properties70: { + P: "ShadingModel", "KString", "", "", "Phong" + P: "MultiLayer", "bool", "", "",0 + P: "EmissiveColor", "Color", "", "A",0,0,0 + P: "EmissiveFactor", "Number", "", "A",1 + P: "AmbientColor", "Color", "", "A",0.2,0.2,0.2 + P: "AmbientFactor", "Number", "", "A",1 + P: "DiffuseColor", "Color", "", "A",0.8,0.8,0.8 + P: "DiffuseFactor", "Number", "", "A",1 + P: "Bump", "Vector3D", "Vector", "",0,0,0 + P: "NormalMap", "Vector3D", "Vector", "",0,0,0 + P: "BumpFactor", "double", "Number", "",1 + P: "TransparentColor", "Color", "", "A",0,0,0 + P: "TransparencyFactor", "Number", "", "A",0 + P: "DisplacementColor", "ColorRGB", "Color", "",0,0,0 + P: "DisplacementFactor", "double", "Number", "",1 + P: "VectorDisplacementColor", "ColorRGB", "Color", "",0,0,0 + P: "VectorDisplacementFactor", "double", "Number", "",1 + P: "SpecularColor", "Color", "", "A",0.2,0.2,0.2 + P: "SpecularFactor", "Number", "", "A",1 + P: "ShininessExponent", "Number", "", "A",20 + P: "ReflectionColor", "Color", "", "A",0,0,0 + P: "ReflectionFactor", "Number", "", "A",1 + } + } + } + ObjectType: "Texture" { + Count: 1 + PropertyTemplate: "FbxFileTexture" { + Properties70: { + P: "TextureTypeUse", "enum", "", "",0 + P: "Texture alpha", "Number", "", "A",1 + P: "CurrentMappingType", "enum", "", "",0 + P: "WrapModeU", "enum", "", "",0 + P: "WrapModeV", "enum", "", "",0 + P: "UVSwap", "bool", "", "",0 + P: "PremultiplyAlpha", "bool", "", "",1 + P: "Translation", "Vector", "", "A",0,0,0 + P: "Rotation", "Vector", "", "A",0,0,0 + P: "Scaling", "Vector", "", "A",1,1,1 + P: "TextureRotationPivot", "Vector3D", "Vector", "",0,0,0 + P: "TextureScalingPivot", "Vector3D", "Vector", "",0,0,0 + P: "CurrentTextureBlendMode", "enum", "", "",1 + P: "UVSet", "KString", "", "", "default" + P: "UseMaterial", "bool", "", "",0 + P: "UseMipMap", "bool", "", "",0 + } + } + } + ObjectType: "Model" { + Count: 1 + PropertyTemplate: "FbxNode" { + Properties70: { + P: "QuaternionInterpolate", "enum", "", "",0 + P: "RotationOffset", "Vector3D", "Vector", "",0,0,0 + P: "RotationPivot", "Vector3D", "Vector", "",0,0,0 + P: "ScalingOffset", "Vector3D", "Vector", "",0,0,0 + P: "ScalingPivot", "Vector3D", "Vector", "",0,0,0 + P: "TranslationActive", "bool", "", "",0 + P: "TranslationMin", "Vector3D", "Vector", "",0,0,0 + P: "TranslationMax", "Vector3D", "Vector", "",0,0,0 + P: "TranslationMinX", "bool", "", "",0 + P: "TranslationMinY", "bool", "", "",0 + P: "TranslationMinZ", "bool", "", "",0 + P: "TranslationMaxX", "bool", "", "",0 + P: "TranslationMaxY", "bool", "", "",0 + P: "TranslationMaxZ", "bool", "", "",0 + P: "RotationOrder", "enum", "", "",0 + P: "RotationSpaceForLimitOnly", "bool", "", "",0 + P: "RotationStiffnessX", "double", "Number", "",0 + P: "RotationStiffnessY", "double", "Number", "",0 + P: "RotationStiffnessZ", "double", "Number", "",0 + P: "AxisLen", "double", "Number", "",10 + P: "PreRotation", "Vector3D", "Vector", "",0,0,0 + P: "PostRotation", "Vector3D", "Vector", "",0,0,0 + P: "RotationActive", "bool", "", "",0 + P: "RotationMin", "Vector3D", "Vector", "",0,0,0 + P: "RotationMax", "Vector3D", "Vector", "",0,0,0 + P: "RotationMinX", "bool", "", "",0 + P: "RotationMinY", "bool", "", "",0 + P: "RotationMinZ", "bool", "", "",0 + P: "RotationMaxX", "bool", "", "",0 + P: "RotationMaxY", "bool", "", "",0 + P: "RotationMaxZ", "bool", "", "",0 + P: "InheritType", "enum", "", "",0 + P: "ScalingActive", "bool", "", "",0 + P: "ScalingMin", "Vector3D", "Vector", "",0,0,0 + P: "ScalingMax", "Vector3D", "Vector", "",1,1,1 + P: "ScalingMinX", "bool", "", "",0 + P: "ScalingMinY", "bool", "", "",0 + P: "ScalingMinZ", "bool", "", "",0 + P: "ScalingMaxX", "bool", "", "",0 + P: "ScalingMaxY", "bool", "", "",0 + P: "ScalingMaxZ", "bool", "", "",0 + P: "GeometricTranslation", "Vector3D", "Vector", "",0,0,0 + P: "GeometricRotation", "Vector3D", "Vector", "",0,0,0 + P: "GeometricScaling", "Vector3D", "Vector", "",1,1,1 + P: "MinDampRangeX", "double", "Number", "",0 + P: "MinDampRangeY", "double", "Number", "",0 + P: "MinDampRangeZ", "double", "Number", "",0 + P: "MaxDampRangeX", "double", "Number", "",0 + P: "MaxDampRangeY", "double", "Number", "",0 + P: "MaxDampRangeZ", "double", "Number", "",0 + P: "MinDampStrengthX", "double", "Number", "",0 + P: "MinDampStrengthY", "double", "Number", "",0 + P: "MinDampStrengthZ", "double", "Number", "",0 + P: "MaxDampStrengthX", "double", "Number", "",0 + P: "MaxDampStrengthY", "double", "Number", "",0 + P: "MaxDampStrengthZ", "double", "Number", "",0 + P: "PreferedAngleX", "double", "Number", "",0 + P: "PreferedAngleY", "double", "Number", "",0 + P: "PreferedAngleZ", "double", "Number", "",0 + P: "LookAtProperty", "object", "", "" + P: "UpVectorProperty", "object", "", "" + P: "Show", "bool", "", "",1 + P: "NegativePercentShapeSupport", "bool", "", "",1 + P: "DefaultAttributeIndex", "int", "Integer", "",-1 + P: "Freeze", "bool", "", "",0 + P: "LODBox", "bool", "", "",0 + P: "Lcl Translation", "Lcl Translation", "", "A",0,0,0 + P: "Lcl Rotation", "Lcl Rotation", "", "A",0,0,0 + P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1 + P: "Visibility", "Visibility", "", "A",1 + P: "Visibility Inheritance", "Visibility Inheritance", "", "",1 + } + } + } + ObjectType: "AnimationCurveNode" { + Count: 8 + PropertyTemplate: "FbxAnimCurveNode" { + Properties70: { + P: "d", "Compound", "", "" + } + } + } + ObjectType: "CollectionExclusive" { + Count: 1 + PropertyTemplate: "FbxDisplayLayer" { + Properties70: { + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "Show", "bool", "", "",1 + P: "Freeze", "bool", "", "",0 + P: "LODBox", "bool", "", "",0 + } + } + } + ObjectType: "Video" { + Count: 1 + PropertyTemplate: "FbxVideo" { + Properties70: { + P: "Path", "KString", "XRefUrl", "", "" + P: "RelPath", "KString", "XRefUrl", "", "" + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "ClipIn", "KTime", "Time", "",0 + P: "ClipOut", "KTime", "Time", "",0 + P: "Offset", "KTime", "Time", "",0 + P: "PlaySpeed", "double", "Number", "",0 + P: "FreeRunning", "bool", "", "",0 + P: "Loop", "bool", "", "",0 + P: "Mute", "bool", "", "",0 + P: "AccessMode", "enum", "", "",0 + P: "ImageSequence", "bool", "", "",0 + P: "ImageSequenceOffset", "int", "Integer", "",0 + P: "FrameRate", "double", "Number", "",0 + P: "LastFrame", "int", "Integer", "",0 + P: "Width", "int", "Integer", "",0 + P: "Height", "int", "Integer", "",0 + P: "StartFrame", "int", "Integer", "",0 + P: "StopFrame", "int", "Integer", "",0 + P: "InterlaceMode", "enum", "", "",0 + } + } + } +} + +; Object properties +;------------------------------------------------------------------ + +Objects: { + Geometry: 2957764348544, "Geometry::", "Mesh" { + Vertices: *24 { + a: -0.5,-0.5,-0.5,0.5,-0.50000011920929,-0.5,-0.5,0.50000011920929,-0.5,0.5,0.50000011920929,-0.5,-0.5,-0.500000059604645,0.5,0.5,-0.500000059604645,0.5,-0.5,0.500000059604645,0.5,0.5,0.500000059604645,0.5 + } + PolygonVertexIndex: *24 { + a: 0,2,3,-2,4,5,7,-7,0,1,5,-5,1,3,7,-6,3,2,6,-8,2,0,4,-7 + } + Edges: *12 { + a: 0,1,2,3,4,5,6,7,9,11,13,17 + } + GeometryVersion: 124 + LayerElementNormal: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Normals: *72 { + a: 0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,-5.96046447753906e-08,-1,0,-5.96046447753906e-08,-1,0,-5.96046447753906e-08,-1,0,-5.96046447753906e-08,-1,0,1,0,0,1,0,0,1,0,0,1,0,0,0,1,5.96046447753906e-08,0,1,5.96046447753906e-08,0,1,5.96046447753906e-08,0,1,5.96046447753906e-08,-1,0,0,-1,0,0,-1,0,0,-1,0,0 + } + NormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementBinormal: 0 { + Version: 102 + Name: "UVChannel_1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Binormals: *72 { + a: 1.19209289550781e-07,1,0,5.96046447753906e-08,1,0,0,1,0,5.96046447753906e-08,1,0,0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,1,-0,-5.96046447753906e-08,1,-0,-5.96046447753906e-08,1,-0,-5.96046447753906e-08,1,-0,-5.96046447753906e-08,1,0,0,1,0,0,1,0,0,1,0,0,1 + } + BinormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + + } + LayerElementBinormal: 1 { + Version: 102 + Name: "UVChannel_3" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Binormals: *72 { + a: -1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-0,0,-1,-0,0,-1,0,-0,-1,-0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,5.96046447753906e-08,-1,0,5.96046447753906e-08,-1,0,5.96046447753906e-08,-1,0,5.96046447753906e-08,-1,-0,0,-1,-0,0,-1,-0,0,-1,-0,0,-1 + } + BinormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + + } + LayerElementTangent: 0 { + Version: 102 + Name: "UVChannel_1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Tangents: *72 { + a: -1,1.19209289550781e-07,0,-1,5.96046447753906e-08,0,-1,-0,0,-1,5.96046447753906e-08,0,1,-0,-0,1,-0,0,1,-0,0,1,-0,0,1,-5.96046447753906e-08,-0,1,-5.96046447753906e-08,0,1,-5.96046447753906e-08,0,1,-5.96046447753906e-08,0,-0,1,-0,0,1,-0,0,1,-0,0,1,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,0,-1,-0,0,-1,0,0,-1,0,0,-1,0 + } + TangentsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementTangent: 1 { + Version: 102 + Name: "UVChannel_3" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Tangents: *72 { + a: -0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,0,1,0,0,1,0,-0,1,-0,0,1,0,-1,5.96046447753906e-08,0,-1,5.96046447753906e-08,0,-1,5.96046447753906e-08,-0,-1,5.96046447753906e-08,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,0,1,-0,0,1,-0,0,1,-0,0,1,-0 + } + TangentsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementUV: 0 { + Version: 101 + Name: "UVChannel_1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "IndexToDirect" + UV: *48 { + a: 1,0,0,0,1,1,0,1,0,0,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,0,1,1,1 + } + UVIndex: *24 { + a: 0,2,3,1,4,5,7,6,8,9,11,10,12,13,15,14,16,17,19,18,20,21,23,22 + } + } + LayerElementUV: 1 { + Version: 101 + Name: "UVChannel_3" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "IndexToDirect" + UV: *48 { + a: 0.28125,0.28125,0,0.28125,0,0,0.28125,0,0.34375,1,0.34375,0.71875,0.625,0.71875,0.625,1,0.28125,0.65625,0,0.65625,0,0.375,0.28125,0.375,0.625,0.28125,0.34375,0.28125,0.34375,0,0.625,0,0.28125,1,0,1,0,0.71875,0.28125,0.71875,0.625,0.65625,0.34375,0.65625,0.34375,0.375,0.625,0.375 + } + UVIndex: *24 { + a: 0,1,2,3,4,5,6,7,20,21,22,23,8,9,10,11,16,17,18,19,12,13,14,15 + } + } + LayerElementSmoothing: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByEdge" + ReferenceInformationType: "Direct" + Smoothing: *12 { + a: 0,0,0,0,0,0,0,0,0,0,0,0 + } + } + LayerElementMaterial: 0 { + Version: 101 + Name: "" + MappingInformationType: "AllSame" + ReferenceInformationType: "IndexToDirect" + Materials: *1 { + a: 0 + } + } + Layer: 0 { + Version: 100 + LayerElement: { + Type: "LayerElementNormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementBinormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementTangent" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementMaterial" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementSmoothing" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementUV" + TypedIndex: 0 + } + } + Layer: 1 { + Version: 100 + LayerElement: { + Type: "LayerElementBinormal" + TypedIndex: 1 + } + LayerElement: { + Type: "LayerElementTangent" + TypedIndex: 1 + } + LayerElement: { + Type: "LayerElementUV" + TypedIndex: 1 + } + } + } + Model: 2957618625584, "Model::Box", "Mesh" { + Version: 232 + Properties70: { + P: "RotationActive", "bool", "", "",1 + P: "InheritType", "enum", "", "",1 + P: "ScalingMax", "Vector3D", "Vector", "",0,0,0 + P: "DefaultAttributeIndex", "int", "Integer", "",0 + P: "Lcl Translation", "Lcl Translation", "", "A",0,0.5,2.18556946492754e-06 + P: "Lcl Rotation", "Lcl Rotation", "", "A",-90,0,0 + P: "currentUVSet", "KString", "", "U", "UVChannel_1" + P: "mr displacement use global settings", "Bool", "", "A+U",1 + P: "mr displacement view dependent", "Bool", "", "A+U",1 + P: "mr displacement method", "Integer", "", "A+U",6,6,6 + P: "mr displacement smoothing on", "Bool", "", "A+U",1 + P: "mr displacement edge length", "Number", "", "A+U",2,2,2 + P: "mr displacement max displace", "Number", "", "A+U",20,20,20 + P: "mr displacement parametric subdivision level", "Integer", "", "A+U",5,5,5 + P: "MaxHandle", "Integer", "", "A+UH",1,0,0 + } + Shading: T + Culling: "CullingOff" + } + Material: 2957776713840, "Material::Default", "" { + Version: 102 + ShadingModel: "phong" + MultiLayer: 0 + Properties70: { + P: "AmbientColor", "Color", "", "A",0,0,0 + P: "DiffuseColor", "Color", "", "A",1,1,1 + P: "TransparencyFactor", "Number", "", "A",1 + P: "SpecularColor", "Color", "", "A",0,0,0 + P: "ShininessExponent", "Number", "", "A",2 + P: "Emissive", "Vector3D", "Vector", "",0,0,0 + P: "Ambient", "Vector3D", "Vector", "",0,0,0 + P: "Diffuse", "Vector3D", "Vector", "",1,1,1 + P: "Specular", "Vector3D", "Vector", "",0,0,0 + P: "Shininess", "double", "Number", "",2 + P: "Opacity", "double", "Number", "",1 + P: "Reflectivity", "double", "Number", "",0 + } + } + Video: 2957776707120, "Video::Map #2", "Clip" { + Type: "Clip" + Properties70: { + P: "Path", "KString", "XRefUrl", "", "U:/Some/Absolute/Primitives/GridGrey.tga" + P: "RelPath", "KString", "XRefUrl", "", "..\Primitives\GridGrey.tga" + } + UseMipMap: 0 + Filename: "U:/Some/Absolute/Primitives/GridGrey.tga" + RelativeFilename: "..\Primitives\GridGrey.tga" + } + Video: 2957776707121, "Video::Map #2", "Clip" { + Type: "Clip" + Properties70: { + P: "Path", "KString", "XRefUrl", "", "U:/Some/Absolute/Primitives/GridGrey.tga" + P: "RelPath", "KString", "XRefUrl", "", "..\Primitives\GridGrey.tga" + } + UseMipMap: 0 + Filename: "U:/Some/Absolute/Primitives/GridGrey.tga" + RelativeFilename: "..\Primitives\GridGrey.tga" + Content: , + "AAAKAAAAAAAAAAAAAAEAARgAh+rq6v+Pj4/wj4+Phurq6ofq6uq2dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+3dHR0hurq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6gCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+Pj/+Pj4//j4+P/4+Pj/+Pj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj4//j4+P/4+Pj/+Pj4//j4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+P/4+Pj/+Pj4//j4+P/4+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+Pj4Hq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6ofq6uq2dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+3dHR0hurq6ofq6ur/j4+P8I+Pj4bq6uo=" + } + Texture: 2957776714320, "Texture::Map #2", "" { + Type: "TextureVideoClip" + Version: 202 + TextureName: "Texture::Map #2" + Properties70: { + P: "CurrentTextureBlendMode", "enum", "", "",0 + P: "UVSet", "KString", "", "", "UVChannel_1" + P: "UseMaterial", "bool", "", "",1 + } + Media: "Video::Map #2" + FileName: "U:/Some/Absolute/Primitives/GridGrey.tga" + RelativeFilename: "..\Primitives\GridGrey.tga" + ModelUVTranslation: 0,0 + ModelUVScaling: 1,1 + Texture_Alpha_Source: "None" + Cropping: 0,0,0,0 + } + Texture: 2957776714321, "Texture::Map #2", "" { + Type: "TextureVideoClip" + Version: 202 + TextureName: "Texture::Map #2" + Properties70: { + P: "CurrentTextureBlendMode", "enum", "", "",0 + P: "UVSet", "KString", "", "", "UVChannel_1" + P: "UseMaterial", "bool", "", "",1 + } + Media: "Video::Map #2" + FileName: "U:/Some/Absolute/Primitives/GridGrey.tga" + RelativeFilename: "..\Primitives\GridGrey.tga" + ModelUVTranslation: 0,0 + ModelUVScaling: 1,1 + Texture_Alpha_Source: "None" + Cropping: 0,0,0,0 + } + AnimationStack: 2957627494560, "AnimStack::Take 001", "" { + Properties70: { + P: "LocalStop", "KTime", "Time", "",153953860000 + P: "ReferenceStop", "KTime", "Time", "",153953860000 + } + } + AnimationCurveNode: 2957627500176, "AnimCurveNode::mr displacement use global settings", "" { + Properties70: { + P: "d|mr displacement use global settings", "Bool", "", "A",1 + } + } + AnimationCurveNode: 2957627490192, "AnimCurveNode::mr displacement view dependent", "" { + Properties70: { + P: "d|mr displacement view dependent", "Bool", "", "A",1 + } + } + AnimationCurveNode: 2957627487904, "AnimCurveNode::mr displacement method", "" { + Properties70: { + P: "d|mr displacement method", "Integer", "", "A",6 + } + } + AnimationCurveNode: 2957627492688, "AnimCurveNode::mr displacement smoothing on", "" { + Properties70: { + P: "d|mr displacement smoothing on", "Bool", "", "A",1 + } + } + AnimationCurveNode: 2957627492064, "AnimCurveNode::mr displacement edge length", "" { + Properties70: { + P: "d|mr displacement edge length", "Number", "", "A",2 + } + } + AnimationCurveNode: 2957627492896, "AnimCurveNode::mr displacement max displace", "" { + Properties70: { + P: "d|mr displacement max displace", "Number", "", "A",20 + } + } + AnimationCurveNode: 2957627492272, "AnimCurveNode::mr displacement parametric subdivision level", "" { + Properties70: { + P: "d|mr displacement parametric subdivision level", "Integer", "", "A",5 + } + } + AnimationCurveNode: 2957627488320, "AnimCurveNode::MaxHandle", "" { + Properties70: { + P: "d|MaxHandle", "Integer", "", "A",1 + } + } + AnimationLayer: 2957566198176, "AnimLayer::BaseLayer", "" { + } + CollectionExclusive: 2959111896352, "DisplayLayer::Box", "DisplayLayer" { + Properties70: { + P: "Color", "ColorRGB", "Color", "",0.607999980449677,0,0.157000005245209 + } + } +} + +; Object connections +;------------------------------------------------------------------ + +Connections: { + + ;Model::Box, Model::RootNode + C: "OO",2957618625584,0 + + ;AnimLayer::BaseLayer, AnimStack::Take 001 + C: "OO",2957566198176,2957627494560 + + ;AnimCurveNode::mr displacement use global settings, AnimLayer::BaseLayer + C: "OO",2957627500176,2957566198176 + + ;AnimCurveNode::mr displacement view dependent, AnimLayer::BaseLayer + C: "OO",2957627490192,2957566198176 + + ;AnimCurveNode::mr displacement method, AnimLayer::BaseLayer + C: "OO",2957627487904,2957566198176 + + ;AnimCurveNode::mr displacement smoothing on, AnimLayer::BaseLayer + C: "OO",2957627492688,2957566198176 + + ;AnimCurveNode::mr displacement edge length, AnimLayer::BaseLayer + C: "OO",2957627492064,2957566198176 + + ;AnimCurveNode::mr displacement max displace, AnimLayer::BaseLayer + C: "OO",2957627492896,2957566198176 + + ;AnimCurveNode::mr displacement parametric subdivision level, AnimLayer::BaseLayer + C: "OO",2957627492272,2957566198176 + + ;AnimCurveNode::MaxHandle, AnimLayer::BaseLayer + C: "OO",2957627488320,2957566198176 + + ;Texture::Map #2, Material::Default + C: "OP",2957776714320,2957776713840, "DiffuseColor" + + ;Video::Map #2, Texture::Map #2 + C: "OO",2957776707120,2957776714320 + + ;Video::Map #2, Texture::Map #2 + C: "OO",2957776707121,2957776714321 + + ;Geometry::, Model::Box + C: "OO",2957764348544,2957618625584 + + ;Material::Default, Model::Box + C: "OO",2957776713840,2957618625584 + + ;AnimCurveNode::mr displacement use global settings, Model::Box + C: "OP",2957627500176,2957618625584, "mr displacement use global settings" + + ;AnimCurveNode::mr displacement view dependent, Model::Box + C: "OP",2957627490192,2957618625584, "mr displacement view dependent" + + ;AnimCurveNode::mr displacement method, Model::Box + C: "OP",2957627487904,2957618625584, "mr displacement method" + + ;AnimCurveNode::mr displacement smoothing on, Model::Box + C: "OP",2957627492688,2957618625584, "mr displacement smoothing on" + + ;AnimCurveNode::mr displacement edge length, Model::Box + C: "OP",2957627492064,2957618625584, "mr displacement edge length" + + ;AnimCurveNode::mr displacement max displace, Model::Box + C: "OP",2957627492896,2957618625584, "mr displacement max displace" + + ;AnimCurveNode::mr displacement parametric subdivision level, Model::Box + C: "OP",2957627492272,2957618625584, "mr displacement parametric subdivision level" + + ;AnimCurveNode::MaxHandle, Model::Box + C: "OP",2957627488320,2957618625584, "MaxHandle" + + ;Model::Box, DisplayLayer::Box + C: "OO",2957618625584,2959111896352 +} +;Takes section +;---------------------------------------------------- + +Takes: { + Current: "Take 001" + Take: "Take 001" { + FileName: "Take_001.tak" + LocalTime: 0,153953860000 + ReferenceTime: 0,153953860000 + } +} diff --git a/test/unit/utFBXImporterExporter.cpp b/test/unit/utFBXImporterExporter.cpp index e73733a0d..43dc40e88 100644 --- a/test/unit/utFBXImporterExporter.cpp +++ b/test/unit/utFBXImporterExporter.cpp @@ -263,3 +263,23 @@ TEST_F(utFBXImporterExporter, fbxTokenizeTestTest) { //const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/transparentTest2.fbx", aiProcess_ValidateDataStructure); //EXPECT_NE(nullptr, scene); } + +TEST_F(utFBXImporterExporter, importOrphantEmbeddedTextureTest) { + // see https://github.com/assimp/assimp/issues/1957 + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/box_orphant_embedded_texture.fbx", aiProcess_ValidateDataStructure); + EXPECT_NE(nullptr, scene); + + EXPECT_EQ(1u, scene->mNumMaterials); + aiMaterial *mat = scene->mMaterials[0]; + ASSERT_NE(nullptr, mat); + + aiString path; + aiTextureMapMode modes[2]; + ASSERT_EQ(aiReturn_SUCCESS, mat->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, nullptr, nullptr, modes)); + ASSERT_STREQ(path.C_Str(), "..\\Primitives\\GridGrey.tga"); + + ASSERT_EQ(1u, scene->mNumTextures); + ASSERT_TRUE(scene->mTextures[0]->pcData); + ASSERT_EQ(9026u, scene->mTextures[0]->mWidth) << "FBX ASCII base64 compression used for a texture."; +} From 536fea1c8d091f8763b13e807216323f1fdd632a Mon Sep 17 00:00:00 2001 From: Mike Samsonov Date: Fri, 1 Nov 2019 14:50:30 +0000 Subject: [PATCH 32/33] operator less for old compilers --- code/FBX/FBXDocument.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/code/FBX/FBXDocument.h b/code/FBX/FBXDocument.h index 51c98da49..a60d7d9ef 100644 --- a/code/FBX/FBXDocument.h +++ b/code/FBX/FBXDocument.h @@ -646,6 +646,11 @@ public: ); } + bool operator<(const Video& other) const + { + return std::tie(type, relativeFileName, fileName) < std::tie(other.type, other.relativeFileName, other.fileName); + } + private: std::string type; std::string relativeFileName; From 82f926971b5220e6662f8b3726c8736944854e99 Mon Sep 17 00:00:00 2001 From: tanolino Date: Fri, 1 Nov 2019 17:01:52 +0100 Subject: [PATCH 33/33] Update CXMLReaderImpl.h Issues with German locale number converter expecting German numbers. In Germany we have numbers like #,## instead of #.## . Thus a unit conversion in COLLADA of 0.01 (centimeter) turned out to be 0.00. --- contrib/irrXML/CXMLReaderImpl.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/contrib/irrXML/CXMLReaderImpl.h b/contrib/irrXML/CXMLReaderImpl.h index 6f3bec5fa..a125312a1 100644 --- a/contrib/irrXML/CXMLReaderImpl.h +++ b/contrib/irrXML/CXMLReaderImpl.h @@ -15,6 +15,9 @@ #include //using namespace Assimp; +// For locale independent number conversion +#include +#include #ifdef _DEBUG #define IRR_DEBUGPRINT(x) printf((x)); @@ -178,8 +181,11 @@ public: return 0; core::stringc c = attrvalue; - return static_cast(atof(c.c_str())); - //return fast_atof(c.c_str()); + std::istringstream sstr(c.c_str()); + sstr.imbue(std::locale("C")); // Locale free number convert + float fNum; + sstr >> fNum; + return fNum; }