From 43634b8b27cbbe5f4f7611dc729355a4c758c14a Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Fri, 31 May 2024 09:36:14 +0200 Subject: [PATCH 01/39] Postprocessing: Fix endless loop (#5605) - closes https://github.com/assimp/assimp/issues/5603 --- code/PostProcessing/TextureTransform.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/PostProcessing/TextureTransform.cpp b/code/PostProcessing/TextureTransform.cpp index 53dba3860..228e97e42 100644 --- a/code/PostProcessing/TextureTransform.cpp +++ b/code/PostProcessing/TextureTransform.cpp @@ -390,8 +390,8 @@ void TextureTransformStep::Execute( aiScene* pScene) { cnt = 0; for (it = trafo.begin();it != trafo.end(); ++it,++cnt) { if ((*it).lockedPos != AI_TT_UV_IDX_LOCK_NONE && (*it).lockedPos != cnt) { - it2 = trafo.begin();unsigned int t = 0; - while (t != (*it).lockedPos) + it2 = trafo.begin(); + while ((*it2).lockedPos != (*it).lockedPos) ++it2; if ((*it2).lockedPos != AI_TT_UV_IDX_LOCK_NONE) { From 0d546b3d2edb5ae737c11971b26233f5a5316a43 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Fri, 31 May 2024 13:08:51 +0200 Subject: [PATCH 02/39] Build: Fix compilation for VS-2022 debug mode - warning (#5606) - closes https://github.com/assimp/assimp/issues/5601 --- contrib/zip/src/miniz.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/zip/src/miniz.h b/contrib/zip/src/miniz.h index f3b3456bd..ad5850ce1 100644 --- a/contrib/zip/src/miniz.h +++ b/contrib/zip/src/miniz.h @@ -5267,7 +5267,7 @@ struct mz_zip_internal_state_tag { (array_ptr)->m_element_size = element_size #if defined(DEBUG) || defined(_DEBUG) -static MZ_FORCEINLINE mz_uint +static MZ_FORCEINLINE size_t mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) { MZ_ASSERT(index < pArray->m_size); return index; From 8231d99a8547574bd9bc984c7c15702d71cf77e4 Mon Sep 17 00:00:00 2001 From: Bradly Landucci <98300047+BradlyLanducci@users.noreply.github.com> Date: Mon, 10 Jun 2024 02:19:57 -0700 Subject: [PATCH 03/39] Converted a size_t to mz_uint that was being treated as an error (#5600) Co-authored-by: Kim Kulling --- contrib/zip/src/zip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/zip/src/zip.c b/contrib/zip/src/zip.c index 5b8955dba..ac520a265 100644 --- a/contrib/zip/src/zip.c +++ b/contrib/zip/src/zip.c @@ -1239,7 +1239,7 @@ int zip_entry_openbyindex(struct zip_t *zip, size_t index) { if (!(pHeader = &MZ_ZIP_ARRAY_ELEMENT( &pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, - mz_uint32, index)))) { + mz_uint32, (mz_uint)index)))) { // cannot find header in central directory return ZIP_ENOHDR; } From e963a863cea52c48b550633e6a3ae77c67165d22 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Mon, 10 Jun 2024 22:25:28 +0200 Subject: [PATCH 04/39] Add trim to xml string parsing (#5611) --- include/assimp/XmlParser.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/assimp/XmlParser.h b/include/assimp/XmlParser.h index 4c19098a4..d7038e358 100644 --- a/include/assimp/XmlParser.h +++ b/include/assimp/XmlParser.h @@ -43,6 +43,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define INCLUDED_AI_IRRXML_WRAPPER #include +#include #include #include "BaseImporter.h" @@ -447,6 +448,7 @@ inline bool TXmlParser::getValueAsString(XmlNode &node, std::string & } text = node.text().as_string(); + text = ai_trim(text); return true; } From 75a10fedd092239a75752184bda2408b88c2d34e Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 11 Jun 2024 16:30:49 +0200 Subject: [PATCH 05/39] Replace duplicated trim (#5613) --- code/AssetLib/Obj/ObjFileMtlImporter.cpp | 2 +- code/AssetLib/Obj/ObjFileParser.cpp | 2 +- code/AssetLib/Obj/ObjTools.h | 16 ---------------- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/code/AssetLib/Obj/ObjFileMtlImporter.cpp b/code/AssetLib/Obj/ObjFileMtlImporter.cpp index f8e3e1cde..effdf627f 100644 --- a/code/AssetLib/Obj/ObjFileMtlImporter.cpp +++ b/code/AssetLib/Obj/ObjFileMtlImporter.cpp @@ -343,7 +343,7 @@ void ObjFileMtlImporter::createMaterial() { } } - name = trim_whitespaces(name); + name = ai_trim(name); std::map::iterator it = m_pModel->mMaterialMap.find(name); if (m_pModel->mMaterialMap.end() == it) { diff --git a/code/AssetLib/Obj/ObjFileParser.cpp b/code/AssetLib/Obj/ObjFileParser.cpp index ed41f2273..fec1fe87b 100644 --- a/code/AssetLib/Obj/ObjFileParser.cpp +++ b/code/AssetLib/Obj/ObjFileParser.cpp @@ -577,7 +577,7 @@ void ObjFileParser::getMaterialDesc() { // Get name std::string strName(pStart, &(*m_DataIt)); - strName = trim_whitespaces(strName); + strName = ai_trim(strName); if (strName.empty()) { skip = true; } diff --git a/code/AssetLib/Obj/ObjTools.h b/code/AssetLib/Obj/ObjTools.h index 664402ee3..ac5e119f2 100644 --- a/code/AssetLib/Obj/ObjTools.h +++ b/code/AssetLib/Obj/ObjTools.h @@ -247,22 +247,6 @@ inline char_t getFloat(char_t it, char_t end, ai_real &value) { return it; } -/** - * @brief Will remove white-spaces for a string. - * @param[in] str The string to clean - * @return The trimmed string. - */ -template -inline string_type trim_whitespaces(string_type str) { - while (!str.empty() && IsSpace(str[0])) { - str.erase(0); - } - while (!str.empty() && IsSpace(str[str.length() - 1])) { - str.erase(str.length() - 1); - } - return str; -} - /** * @brief Checks for a line-end. * @param[in] it Current iterator in string. From a722e33027962bb56f6caadf55b052ec7d19bfb0 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 11 Jun 2024 21:19:52 +0200 Subject: [PATCH 06/39] Move aiScene constructor (#5614) --- code/Common/Version.cpp | 80 ---------------------------------------- code/Common/scene.cpp | 81 +++++++++++++++++++++++++++++++++++++++++ include/assimp/scene.h | 22 ++++++----- 3 files changed, 93 insertions(+), 90 deletions(-) diff --git a/code/Common/Version.cpp b/code/Common/Version.cpp index 10ccf58e7..ef8bbc9f5 100644 --- a/code/Common/Version.cpp +++ b/code/Common/Version.cpp @@ -118,83 +118,3 @@ ASSIMP_API const char *aiGetBranchName() { return GitBranch; } -// ------------------------------------------------------------------------------------------------ -ASSIMP_API aiScene::aiScene() : - mFlags(0), - mRootNode(nullptr), - mNumMeshes(0), - mMeshes(nullptr), - mNumMaterials(0), - mMaterials(nullptr), - mNumAnimations(0), - mAnimations(nullptr), - mNumTextures(0), - mTextures(nullptr), - mNumLights(0), - mLights(nullptr), - mNumCameras(0), - mCameras(nullptr), - mMetaData(nullptr), - mName(), - mNumSkeletons(0), - mSkeletons(nullptr), - mPrivate(new Assimp::ScenePrivateData()) { - // empty -} - -// ------------------------------------------------------------------------------------------------ -ASSIMP_API aiScene::~aiScene() { - // delete all sub-objects recursively - delete mRootNode; - - // To make sure we won't crash if the data is invalid it's - // much better to check whether both mNumXXX and mXXX are - // valid instead of relying on just one of them. - if (mNumMeshes && mMeshes) { - for (unsigned int a = 0; a < mNumMeshes; ++a) { - delete mMeshes[a]; - } - } - delete[] mMeshes; - - if (mNumMaterials && mMaterials) { - for (unsigned int a = 0; a < mNumMaterials; ++a) { - delete mMaterials[a]; - } - } - delete[] mMaterials; - - if (mNumAnimations && mAnimations) { - for (unsigned int a = 0; a < mNumAnimations; ++a) { - delete mAnimations[a]; - } - } - delete[] mAnimations; - - if (mNumTextures && mTextures) { - for (unsigned int a = 0; a < mNumTextures; ++a) { - delete mTextures[a]; - } - } - delete[] mTextures; - - if (mNumLights && mLights) { - for (unsigned int a = 0; a < mNumLights; ++a) { - delete mLights[a]; - } - } - delete[] mLights; - - if (mNumCameras && mCameras) { - for (unsigned int a = 0; a < mNumCameras; ++a) { - delete mCameras[a]; - } - } - delete[] mCameras; - - aiMetadata::Dealloc(mMetaData); - - delete[] mSkeletons; - - delete static_cast(mPrivate); -} diff --git a/code/Common/scene.cpp b/code/Common/scene.cpp index e42f4b2e6..5c2e370d2 100644 --- a/code/Common/scene.cpp +++ b/code/Common/scene.cpp @@ -40,6 +40,87 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include +#include "ScenePrivate.h" + +aiScene::aiScene() : + mFlags(0), + mRootNode(nullptr), + mNumMeshes(0), + mMeshes(nullptr), + mNumMaterials(0), + mMaterials(nullptr), + mNumAnimations(0), + mAnimations(nullptr), + mNumTextures(0), + mTextures(nullptr), + mNumLights(0), + mLights(nullptr), + mNumCameras(0), + mCameras(nullptr), + mMetaData(nullptr), + mName(), + mNumSkeletons(0), + mSkeletons(nullptr), + mPrivate(new Assimp::ScenePrivateData()) { + // empty +} + +aiScene::~aiScene() { + // delete all sub-objects recursively + delete mRootNode; + + // To make sure we won't crash if the data is invalid it's + // much better to check whether both mNumXXX and mXXX are + // valid instead of relying on just one of them. + if (mNumMeshes && mMeshes) { + for (unsigned int a = 0; a < mNumMeshes; ++a) { + delete mMeshes[a]; + } + } + delete[] mMeshes; + + if (mNumMaterials && mMaterials) { + for (unsigned int a = 0; a < mNumMaterials; ++a) { + delete mMaterials[a]; + } + } + delete[] mMaterials; + + if (mNumAnimations && mAnimations) { + for (unsigned int a = 0; a < mNumAnimations; ++a) { + delete mAnimations[a]; + } + } + delete[] mAnimations; + + if (mNumTextures && mTextures) { + for (unsigned int a = 0; a < mNumTextures; ++a) { + delete mTextures[a]; + } + } + delete[] mTextures; + + if (mNumLights && mLights) { + for (unsigned int a = 0; a < mNumLights; ++a) { + delete mLights[a]; + } + } + delete[] mLights; + + if (mNumCameras && mCameras) { + for (unsigned int a = 0; a < mNumCameras; ++a) { + delete mCameras[a]; + } + } + delete[] mCameras; + + aiMetadata::Dealloc(mMetaData); + + delete[] mSkeletons; + + delete static_cast(mPrivate); +} + aiNode::aiNode() : mName(""), mParent(nullptr), diff --git a/include/assimp/scene.h b/include/assimp/scene.h index 86ab9bf60..9137a856c 100644 --- a/include/assimp/scene.h +++ b/include/assimp/scene.h @@ -141,25 +141,28 @@ struct ASSIMP_API aiNode { /** Destructor */ ~aiNode(); - /** Searches for a node with a specific name, beginning at this + /** + * @brief Searches for a node with a specific name, beginning at this * nodes. Normally you will call this method on the root node * of the scene. * * @param name Name to search for * @return nullptr or a valid Node if the search was successful. */ - inline - const aiNode* FindNode(const aiString& name) const { + inline const aiNode* FindNode(const aiString& name) const { return FindNode(name.data); } - inline - aiNode* FindNode(const aiString& name) { + inline aiNode* FindNode(const aiString& name) { return FindNode(name.data); } + /** + * @brief Will search for a node described by its name. + * @param[in] name The name for the node to look for. + * @return Pointer showing to the node or nullptr if not found. + */ const aiNode* FindNode(const char* name) const; - aiNode* FindNode(const char* name); /** @@ -240,8 +243,7 @@ struct ASSIMP_API aiNode { * delete a given scene on your own. */ // ------------------------------------------------------------------------------- -struct aiScene -{ +struct ASSIMP_API aiScene { /** Any combination of the AI_SCENE_FLAGS_XXX flags. By default * this value is 0, no flags are set. Most applications will * want to reject all scenes with the AI_SCENE_FLAGS_INCOMPLETE @@ -355,10 +357,10 @@ struct aiScene #ifdef __cplusplus //! Default constructor - set everything to 0/nullptr - ASSIMP_API aiScene(); + aiScene(); //! Destructor - ASSIMP_API ~aiScene(); + ~aiScene(); //! Check whether the scene contains meshes //! Unless no special scene flags are set this will always be true. From 97cbecb859375625fae43059db696a439b159a0c Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Wed, 12 Jun 2024 21:01:03 +0200 Subject: [PATCH 07/39] Move revision.h and revision.h.in to include folder (#5615) --- CMakeLists.txt | 4 ++-- code/Common/Version.cpp | 3 +-- code/res/assimp.rc | 2 +- revision.h.in => include/assimp/revision.h.in | 0 tools/assimp_cmd/assimp_cmd.rc | 2 +- tools/assimp_view/assimp_view.rc | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) rename revision.h.in => include/assimp/revision.h.in (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f1e2d2d3e..718a251e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -761,8 +761,8 @@ IF ( ASSIMP_INSTALL ) ENDIF() CONFIGURE_FILE( - ${CMAKE_CURRENT_LIST_DIR}/revision.h.in - ${CMAKE_CURRENT_BINARY_DIR}/revision.h + ${CMAKE_CURRENT_LIST_DIR}/include/assimp/revision.h.in + ${CMAKE_CURRENT_BINARY_DIR}/include/assimp/revision.h ) CONFIGURE_FILE( diff --git a/code/Common/Version.cpp b/code/Common/Version.cpp index ef8bbc9f5..2fca44824 100644 --- a/code/Common/Version.cpp +++ b/code/Common/Version.cpp @@ -44,8 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ScenePrivate.h" #include #include - -#include "revision.h" +#include // -------------------------------------------------------------------------------- // Legal information string - don't remove this. diff --git a/code/res/assimp.rc b/code/res/assimp.rc index fd10935f6..6c9356276 100644 --- a/code/res/assimp.rc +++ b/code/res/assimp.rc @@ -1,4 +1,4 @@ -#include "revision.h" +#include #ifdef __GNUC__ #include "winresrc.h" #else diff --git a/revision.h.in b/include/assimp/revision.h.in similarity index 100% rename from revision.h.in rename to include/assimp/revision.h.in diff --git a/tools/assimp_cmd/assimp_cmd.rc b/tools/assimp_cmd/assimp_cmd.rc index 2ead81bcb..63478d356 100644 --- a/tools/assimp_cmd/assimp_cmd.rc +++ b/tools/assimp_cmd/assimp_cmd.rc @@ -1,4 +1,4 @@ -#include "revision.h" +#include #if defined(__GNUC__) && defined(_WIN32) #include "winresrc.h" #else diff --git a/tools/assimp_view/assimp_view.rc b/tools/assimp_view/assimp_view.rc index 1dc4f7f52..10b762059 100644 --- a/tools/assimp_view/assimp_view.rc +++ b/tools/assimp_view/assimp_view.rc @@ -1,7 +1,7 @@ // Microsoft Visual C++ generated resource script. // #include "resource.h" -#include "revision.h" +#include #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// From 81858f9e6230d9e8b53aee5494454a503f92a10b Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Fri, 14 Jun 2024 12:51:47 +0200 Subject: [PATCH 08/39] Update MDLMaterialLoader.cpp (#5620) * Update MDLMaterialLoader.cpp - closes https://github.com/assimp/assimp/issues/5239 * Add missing declaration --- code/AssetLib/MDL/MDLMaterialLoader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/AssetLib/MDL/MDLMaterialLoader.cpp b/code/AssetLib/MDL/MDLMaterialLoader.cpp index 38c42c1a5..a966f5920 100644 --- a/code/AssetLib/MDL/MDLMaterialLoader.cpp +++ b/code/AssetLib/MDL/MDLMaterialLoader.cpp @@ -730,7 +730,8 @@ void MDLImporter::SkipSkinLump_3DGS_MDL7( // if an ASCII effect description (HLSL?) is contained in the file, // we can simply ignore it ... if (iType & AI_MDL7_SKINTYPE_MATERIAL_ASCDEF) { - int32_t iMe = *((int32_t *)szCurrent); + int32_t iMe = 0; + ::memcpy(&iMe, szCurrent, sizeof(int32_t)); AI_SWAP4(iMe); szCurrent += sizeof(char) * iMe + sizeof(int32_t); } From 907da122e9d099ecdd2a392c58f819d2a9b622df Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Sat, 15 Jun 2024 12:41:43 +0200 Subject: [PATCH 09/39] Create inno_setup (#5621) --- .github/workflows/inno_setup | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/inno_setup diff --git a/.github/workflows/inno_setup b/.github/workflows/inno_setup new file mode 100644 index 000000000..0bfef0cd3 --- /dev/null +++ b/.github/workflows/inno_setup @@ -0,0 +1,51 @@ +name: Build Windows Installer +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + build: + name: Build the Inno Setup Installer + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: lukka/get-cmake@latest + - uses: ilammy/msvc-dev-cmd@v1 + + + - name: Cache DX SDK + id: dxcache + uses: actions/cache@v4 + with: + path: '${{ github.workspace }}/DX_SDK' + key: ${{ runner.os }}-DX_SDK + restore-keys: | + ${{ runner.os }}-DX_SDK + + - name: Download DXSetup + run: | + curl -s -o DXSDK_Jun10.exe --location https://download.microsoft.com/download/A/E/7/AE743F1F-632B-4809-87A9-AA1BB3458E31/DXSDK_Jun10.exe + cmd.exe /c start /wait .\DXSDK_Jun10.exe /U /O /F /S /P "${{ github.workspace }}\DX_SDK" + + - name: Set Windows specific CMake arguments + id: windows_extra_cmake_args + run: echo "::set-output name=args::-DASSIMP_BUILD_ASSIMP_TOOLS=1 -DASSIMP_BUILD_ASSIMP_VIEW=1 -DASSIMP_BUILD_ZLIB=1" + + - name: configure and build + uses: lukka/run-cmake@v3 + env: + DXSDK_DIR: '${{ github.workspace }}/DX_SDK' + + with: + cmakeListsOrSettingsJson: CMakeListsTxtAdvanced + cmakeListsTxtPath: '${{ github.workspace }}/CMakeLists.txt' + cmakeAppendedArgs: '-GNinja -DCMAKE_BUILD_TYPE=Release ${{ steps.windows_extra_cmake_args.outputs.args }} ${{ steps.hunter_extra_cmake_args.outputs.args }}' + buildWithCMakeArgs: '--parallel 24 -v' + buildDirectory: '${{ github.workspace }}/build/' + + - name: Compile .ISS to .EXE Installer + uses: Minionguyjpro/Inno-Setup-Action@v1.2.2 + with: + path: packaging/windows-innosetup/script_x64.iss + options: /O+ From 75091d45294465f997b637c930b4ecb8a8ead747 Mon Sep 17 00:00:00 2001 From: mosfet80 Date: Mon, 17 Jun 2024 11:50:42 +0200 Subject: [PATCH 10/39] clean HunterGate.cmake (#5619) removing cmake version control check. The cmake version is always greater than 3.22 Co-authored-by: Kim Kulling --- cmake-modules/HunterGate.cmake | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/cmake-modules/HunterGate.cmake b/cmake-modules/HunterGate.cmake index 6d9cc2401..fe02e3614 100644 --- a/cmake-modules/HunterGate.cmake +++ b/cmake-modules/HunterGate.cmake @@ -22,38 +22,9 @@ # 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. -# This is a gate file to Hunter package manager. -# Include this file using `include` command and add package you need, example: -# -# cmake_minimum_required(VERSION 3.2) -# -# include("cmake/HunterGate.cmake") -# HunterGate( -# URL "https://github.com/path/to/hunter/archive.tar.gz" -# SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d" -# ) -# -# project(MyProject) -# -# hunter_add_package(Foo) -# hunter_add_package(Boo COMPONENTS Bar Baz) -# -# Projects: -# * https://github.com/hunter-packages/gate/ -# * https://github.com/ruslo/hunter option(HUNTER_ENABLED "Enable Hunter package manager support" ON) -if(HUNTER_ENABLED) - if(CMAKE_VERSION VERSION_LESS "3.2") - message( - FATAL_ERROR - "At least CMake version 3.2 required for Hunter dependency management." - " Update CMake or set HUNTER_ENABLED to OFF." - ) - endif() -endif() - include(CMakeParseArguments) # cmake_parse_arguments option(HUNTER_STATUS_PRINT "Print working status" ON) From a51500ba2be28592c49f601b79a7030143232c11 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Mon, 17 Jun 2024 13:12:54 +0200 Subject: [PATCH 11/39] Draft: Update init of aiString (#5623) * Draft: Update init of aiString - closes https://github.com/assimp/assimp/issues/5622 * Update types.h * Fix typo * Fix another typo * Adapt usage of AI_MAXLEN * Fix compare operator * Add missing renames --- code/AssetLib/3DS/3DSConverter.cpp | 2 +- code/AssetLib/AC/ACLoader.cpp | 10 ++-- code/AssetLib/Blender/BlenderLoader.cpp | 4 +- code/AssetLib/FBX/FBXConverter.cpp | 6 +- code/AssetLib/FBX/FBXParser.h | 2 +- code/AssetLib/Irr/IRRLoader.cpp | 4 +- code/AssetLib/LWS/LWSLoader.cpp | 8 +-- code/AssetLib/MD5/MD5Loader.cpp | 2 +- code/AssetLib/MDL/MDLLoader.cpp | 4 +- code/AssetLib/MDL/MDLMaterialLoader.cpp | 2 +- code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp | 4 +- code/AssetLib/Q3D/Q3DLoader.cpp | 2 +- code/AssetLib/SMD/SMDLoader.cpp | 4 +- code/AssetLib/STL/STLLoader.cpp | 2 +- code/AssetLib/Unreal/UnrealLoader.cpp | 4 +- code/AssetLib/glTF/glTFImporter.cpp | 4 +- code/AssetLib/glTF2/glTF2Importer.cpp | 4 +- code/Common/SceneCombiner.cpp | 4 +- code/Material/MaterialSystem.cpp | 2 +- code/PostProcessing/OptimizeGraph.cpp | 2 +- code/PostProcessing/PretransformVertices.cpp | 4 +- .../RemoveRedundantMaterials.cpp | 2 +- code/PostProcessing/ValidateDataStructure.cpp | 6 +- include/assimp/StringUtils.h | 22 +++++-- include/assimp/types.h | 58 ++++++++++--------- test/unit/utPretransformVertices.cpp | 2 +- tools/assimp_view/Display.cpp | 19 +++--- tools/assimp_view/Material.cpp | 6 +- tools/assimp_view/MessageProc.cpp | 2 +- 29 files changed, 104 insertions(+), 93 deletions(-) diff --git a/code/AssetLib/3DS/3DSConverter.cpp b/code/AssetLib/3DS/3DSConverter.cpp index 7ae756668..7d1c24cd6 100644 --- a/code/AssetLib/3DS/3DSConverter.cpp +++ b/code/AssetLib/3DS/3DSConverter.cpp @@ -709,7 +709,7 @@ void Discreet3DSImporter::GenerateNodeGraph(aiScene *pcOut) { pcNode->mNumMeshes = 1; // Build a name for the node - pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "3DSMesh_%u", i); + pcNode->mName.length = ai_snprintf(pcNode->mName.data, AI_MAXLEN, "3DSMesh_%u", i); } // Build dummy nodes for all cameras diff --git a/code/AssetLib/AC/ACLoader.cpp b/code/AssetLib/AC/ACLoader.cpp index dbcb39b30..242364150 100644 --- a/code/AssetLib/AC/ACLoader.cpp +++ b/code/AssetLib/AC/ACLoader.cpp @@ -193,7 +193,7 @@ bool AC3DImporter::LoadObjectSection(std::vector &objects) { // Generate a default name for both the light source and the node // FIXME - what's the right way to print a size_t? Is 'zu' universally available? stick with the safe version. - light->mName.length = ::ai_snprintf(light->mName.data, MAXLEN, "ACLight_%i", static_cast(mLights->size()) - 1); + light->mName.length = ::ai_snprintf(light->mName.data, AI_MAXLEN, "ACLight_%i", static_cast(mLights->size()) - 1); obj.name = std::string(light->mName.data); ASSIMP_LOG_VERBOSE_DEBUG("AC3D: Light source encountered"); @@ -696,18 +696,18 @@ aiNode *AC3DImporter::ConvertObjectSection(Object &object, // generate a name depending on the type of the node switch (object.type) { case Object::Group: - node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACGroup_%i", mGroupsCounter++); + node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACGroup_%i", mGroupsCounter++); break; case Object::Poly: - node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACPoly_%i", mPolysCounter++); + node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACPoly_%i", mPolysCounter++); break; case Object::Light: - node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACLight_%i", mLightsCounter++); + node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACLight_%i", mLightsCounter++); break; // there shouldn't be more than one world, but we don't care case Object::World: - node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACWorld_%i", mWorldsCounter++); + node->mName.length = ::ai_snprintf(node->mName.data, AI_MAXLEN, "ACWorld_%i", mWorldsCounter++); break; } } diff --git a/code/AssetLib/Blender/BlenderLoader.cpp b/code/AssetLib/Blender/BlenderLoader.cpp index 1a40a2fe5..923eb5959 100644 --- a/code/AssetLib/Blender/BlenderLoader.cpp +++ b/code/AssetLib/Blender/BlenderLoader.cpp @@ -359,7 +359,7 @@ void BlenderImporter::ResolveImage(aiMaterial *out, const Material *mat, const M // check if the file contents are bundled with the BLEND file if (img->packedfile) { name.data[0] = '*'; - name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(MAXLEN - 1), static_cast(conv_data.textures->size())); + name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(AI_MAXLEN - 1), static_cast(conv_data.textures->size())); conv_data.textures->push_back(new aiTexture()); aiTexture *curTex = conv_data.textures->back(); @@ -433,7 +433,7 @@ void BlenderImporter::AddSentinelTexture(aiMaterial *out, const Material *mat, c (void)conv_data; aiString name; - name.length = ai_snprintf(name.data, MAXLEN, "Procedural,num=%i,type=%s", conv_data.sentinel_cnt++, + name.length = ai_snprintf(name.data, AI_MAXLEN, "Procedural,num=%i,type=%s", conv_data.sentinel_cnt++, GetTextureTypeDisplayString(tex->tex->type)); out->AddProperty(&name, AI_MATKEY_TEXTURE_DIFFUSE( conv_data.next_texture[aiTextureType_DIFFUSE]++)); diff --git a/code/AssetLib/FBX/FBXConverter.cpp b/code/AssetLib/FBX/FBXConverter.cpp index cc73756fb..234931cbe 100644 --- a/code/AssetLib/FBX/FBXConverter.cpp +++ b/code/AssetLib/FBX/FBXConverter.cpp @@ -1860,7 +1860,7 @@ aiString FBXConverter::GetTexturePath(const Texture *tex) { // We need to load all textures before referencing them, as FBX file format order may reference a texture before loading it // This may occur on this case too, it has to be studied path.data[0] = '*'; - path.length = 1 + ASSIMP_itoa10(path.data + 1, MAXLEN - 1, index); + path.length = 1 + ASSIMP_itoa10(path.data + 1, AI_MAXLEN - 1, index); } } } @@ -2440,7 +2440,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial *out_mat, const PropertyTa // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) path.data[0] = '*'; - path.length = 1 + ASSIMP_itoa10(path.data + 1, MAXLEN - 1, index); + path.length = 1 + ASSIMP_itoa10(path.data + 1, AI_MAXLEN - 1, index); } out_mat->AddProperty(&path, (name + "|file").c_str(), aiTextureType_UNKNOWN, 0); @@ -2806,7 +2806,7 @@ void FBXConverter::ProcessMorphAnimDatas(std::map auto geoIt = std::find(model->GetGeometry().begin(), model->GetGeometry().end(), geo); auto geoIndex = static_cast(std::distance(model->GetGeometry().begin(), geoIt)); auto name = aiString(FixNodeName(model->Name() + "*")); - name.length = 1 + ASSIMP_itoa10(name.data + name.length, MAXLEN - 1, geoIndex); + name.length = 1 + ASSIMP_itoa10(name.data + name.length, AI_MAXLEN - 1, geoIndex); morphAnimData *animData; auto animIt = morphAnimDatas->find(name.C_Str()); if (animIt == morphAnimDatas->end()) { diff --git a/code/AssetLib/FBX/FBXParser.h b/code/AssetLib/FBX/FBXParser.h index 97f74b914..2ca216d8c 100644 --- a/code/AssetLib/FBX/FBXParser.h +++ b/code/AssetLib/FBX/FBXParser.h @@ -134,7 +134,7 @@ public: const char* elementNameCStr = elementName.c_str(); for (auto element = elements.begin(); element != elements.end(); ++element) { - if (!ASSIMP_strincmp(element->first.c_str(), elementNameCStr, MAXLEN)) { + if (!ASSIMP_strincmp(element->first.c_str(), elementNameCStr, AI_MAXLEN)) { return element->second; } } diff --git a/code/AssetLib/Irr/IRRLoader.cpp b/code/AssetLib/Irr/IRRLoader.cpp index f41a2543d..12ce86260 100644 --- a/code/AssetLib/Irr/IRRLoader.cpp +++ b/code/AssetLib/Irr/IRRLoader.cpp @@ -168,7 +168,7 @@ void IRRImporter::BuildSkybox(std::vector &meshes, std::vectorAddProperty(&s, AI_MATKEY_NAME); int shading = aiShadingMode_NoShading; @@ -316,7 +316,7 @@ void IRRImporter::ComputeAnimations(Node *root, aiNode *real, std::vectormNodeName.length = ::ai_snprintf(anim->mNodeName.data, MAXLEN, + anim->mNodeName.length = ::ai_snprintf(anim->mNodeName.data, AI_MAXLEN, "$INST_DUMMY_%i_%s", total - 1, (root->name.length() ? root->name.c_str() : "")); diff --git a/code/AssetLib/LWS/LWSLoader.cpp b/code/AssetLib/LWS/LWSLoader.cpp index 4c3a44785..047ab0cc9 100644 --- a/code/AssetLib/LWS/LWSLoader.cpp +++ b/code/AssetLib/LWS/LWSLoader.cpp @@ -305,14 +305,14 @@ void LWSImporter::SetupNodeName(aiNode *nd, LWS::NodeDesc &src) { } std::string::size_type t = src.path.substr(s).find_last_of('.'); - nd->mName.length = ::ai_snprintf(nd->mName.data, MAXLEN, "%s_(%08X)", src.path.substr(s).substr(0, t).c_str(), combined); - if (nd->mName.length > MAXLEN) { - nd->mName.length = MAXLEN; + nd->mName.length = ::ai_snprintf(nd->mName.data, AI_MAXLEN, "%s_(%08X)", src.path.substr(s).substr(0, t).c_str(), combined); + if (nd->mName.length > AI_MAXLEN) { + nd->mName.length = AI_MAXLEN; } return; } } - nd->mName.length = ::ai_snprintf(nd->mName.data, MAXLEN, "%s_(%08X)", src.name, combined); + nd->mName.length = ::ai_snprintf(nd->mName.data, AI_MAXLEN, "%s_(%08X)", src.name, combined); } // ------------------------------------------------------------------------------------------------ diff --git a/code/AssetLib/MD5/MD5Loader.cpp b/code/AssetLib/MD5/MD5Loader.cpp index 0976484a4..02f08d3ea 100644 --- a/code/AssetLib/MD5/MD5Loader.cpp +++ b/code/AssetLib/MD5/MD5Loader.cpp @@ -707,7 +707,7 @@ void MD5Importer::LoadMD5CameraFile() { for (std::vector::const_iterator it = cuts.begin(); it != cuts.end() - 1; ++it) { aiAnimation *anim = *tmp++ = new aiAnimation(); - anim->mName.length = ::ai_snprintf(anim->mName.data, MAXLEN, "anim%u_from_%u_to_%u", (unsigned int)(it - cuts.begin()), (*it), *(it + 1)); + anim->mName.length = ::ai_snprintf(anim->mName.data, AI_MAXLEN, "anim%u_from_%u_to_%u", (unsigned int)(it - cuts.begin()), (*it), *(it + 1)); anim->mTicksPerSecond = cameraParser.fFrameRate; anim->mChannels = new aiNodeAnim *[anim->mNumChannels = 1]; diff --git a/code/AssetLib/MDL/MDLLoader.cpp b/code/AssetLib/MDL/MDLLoader.cpp index 99b0145af..498e40b34 100644 --- a/code/AssetLib/MDL/MDLLoader.cpp +++ b/code/AssetLib/MDL/MDLLoader.cpp @@ -962,7 +962,7 @@ void MDLImporter::CalcAbsBoneMatrices_3DGS_MDL7(MDL::IntBone_MDL7 **apcOutBones) if (AI_MDL7_BONE_STRUCT_SIZE__NAME_IS_NOT_THERE == pcHeader->bone_stc_size) { // no real name for our poor bone is specified :-( - pcOutBone->mName.length = ai_snprintf(pcOutBone->mName.data, MAXLEN, + pcOutBone->mName.length = ai_snprintf(pcOutBone->mName.data, AI_MAXLEN, "UnnamedBone_%i", iBone); } else { // Make sure we won't run over the buffer's end if there is no @@ -1567,7 +1567,7 @@ void MDLImporter::InternReadFile_3DGS_MDL7() { } else { pcNode->mName.length = (ai_uint32)::strlen(szBuffer); } - ::strncpy(pcNode->mName.data, szBuffer, MAXLEN - 1); + ::strncpy(pcNode->mName.data, szBuffer, AI_MAXLEN - 1); ++p; } } diff --git a/code/AssetLib/MDL/MDLMaterialLoader.cpp b/code/AssetLib/MDL/MDLMaterialLoader.cpp index a966f5920..f68f8e23e 100644 --- a/code/AssetLib/MDL/MDLMaterialLoader.cpp +++ b/code/AssetLib/MDL/MDLMaterialLoader.cpp @@ -494,7 +494,7 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7( aiString szFile; const size_t iLen = strlen((const char *)szCurrent); - size_t iLen2 = iLen > (MAXLEN - 1) ? (MAXLEN - 1) : iLen; + size_t iLen2 = iLen > (AI_MAXLEN - 1) ? (AI_MAXLEN - 1) : iLen; memcpy(szFile.data, (const char *)szCurrent, iLen2); szFile.data[iLen2] = '\0'; szFile.length = static_cast(iLen2); diff --git a/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp b/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp index d2ab59eb4..920279fce 100644 --- a/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp +++ b/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp @@ -588,7 +588,7 @@ bool Q3BSPFileImporter::importTextureFromArchive(const Q3BSP::Q3BSPModel *model, aiString name; name.data[0] = '*'; - name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(MAXLEN - 1), static_cast(mTextures.size())); + name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(AI_MAXLEN - 1), static_cast(mTextures.size())); archive->Close(pTextureStream); @@ -641,7 +641,7 @@ bool Q3BSPFileImporter::importLightmap(const Q3BSP::Q3BSPModel *pModel, aiScene aiString name; name.data[0] = '*'; - name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(MAXLEN - 1), static_cast(mTextures.size())); + name.length = 1 + ASSIMP_itoa10(name.data + 1, static_cast(AI_MAXLEN - 1), static_cast(mTextures.size())); pMatHelper->AddProperty(&name, AI_MATKEY_TEXTURE_LIGHTMAP(1)); mTextures.push_back(pTexture); diff --git a/code/AssetLib/Q3D/Q3DLoader.cpp b/code/AssetLib/Q3D/Q3DLoader.cpp index ac6b17ec6..84a508979 100644 --- a/code/AssetLib/Q3D/Q3DLoader.cpp +++ b/code/AssetLib/Q3D/Q3DLoader.cpp @@ -250,7 +250,7 @@ void Q3DImporter::InternReadFile(const std::string &pFile, c = stream.GetI1(); while (c) { mat.name.data[mat.name.length++] = c; - if (mat.name.length == MAXLEN) { + if (mat.name.length == AI_MAXLEN) { ASSIMP_LOG_ERROR("String ouverflow detected, skipped material name parsing."); break; } diff --git a/code/AssetLib/SMD/SMDLoader.cpp b/code/AssetLib/SMD/SMDLoader.cpp index df598e5c7..1eac5d934 100644 --- a/code/AssetLib/SMD/SMDLoader.cpp +++ b/code/AssetLib/SMD/SMDLoader.cpp @@ -589,12 +589,12 @@ void SMDImporter::CreateOutputMaterials() { pScene->mMaterials[iMat] = pcMat; aiString szName; - szName.length = static_cast(ai_snprintf(szName.data,MAXLEN,"Texture_%u",iMat)); + szName.length = static_cast(ai_snprintf(szName.data, AI_MAXLEN, "Texture_%u", iMat)); pcMat->AddProperty(&szName,AI_MATKEY_NAME); if (aszTextures[iMat].length()) { - ::strncpy(szName.data, aszTextures[iMat].c_str(),MAXLEN-1); + ::strncpy(szName.data, aszTextures[iMat].c_str(), AI_MAXLEN - 1); szName.length = static_cast( aszTextures[iMat].length() ); pcMat->AddProperty(&szName,AI_MATKEY_TEXTURE_DIFFUSE(0)); } diff --git a/code/AssetLib/STL/STLLoader.cpp b/code/AssetLib/STL/STLLoader.cpp index 38cd2a0b8..3a9fc1b12 100644 --- a/code/AssetLib/STL/STLLoader.cpp +++ b/code/AssetLib/STL/STLLoader.cpp @@ -257,7 +257,7 @@ void STLImporter::LoadASCIIFile(aiNode *root) { size_t temp = (size_t)(sz - szMe); // setup the name of the node if (temp) { - if (temp >= MAXLEN) { + if (temp >= AI_MAXLEN) { throw DeadlyImportError("STL: Node name too long"); } std::string name(szMe, temp); diff --git a/code/AssetLib/Unreal/UnrealLoader.cpp b/code/AssetLib/Unreal/UnrealLoader.cpp index 3810e5bf4..85b68b508 100644 --- a/code/AssetLib/Unreal/UnrealLoader.cpp +++ b/code/AssetLib/Unreal/UnrealLoader.cpp @@ -452,7 +452,7 @@ void UnrealImporter::InternReadFile(const std::string &pFile, aiColor3D color(1.f, 1.f, 1.f); aiString s; - ::ai_snprintf(s.data, MAXLEN, "mat%u_tx%u_", i, materials[i].tex); + ::ai_snprintf(s.data, AI_MAXLEN, "mat%u_tx%u_", i, materials[i].tex); // set the two-sided flag if (materials[i].type == Unreal::MF_NORMAL_TS) { @@ -472,7 +472,7 @@ void UnrealImporter::InternReadFile(const std::string &pFile, // a special name for the weapon attachment point if (materials[i].type == Unreal::MF_WEAPON_PLACEHOLDER) { - s.length = ::ai_snprintf(s.data, MAXLEN, "$WeaponTag$"); + s.length = ::ai_snprintf(s.data, AI_MAXLEN, "$WeaponTag$"); color = aiColor3D(0.f, 0.f, 0.f); } diff --git a/code/AssetLib/glTF/glTFImporter.cpp b/code/AssetLib/glTF/glTFImporter.cpp index 1d718fd20..2443205f3 100644 --- a/code/AssetLib/glTF/glTFImporter.cpp +++ b/code/AssetLib/glTF/glTFImporter.cpp @@ -109,7 +109,7 @@ inline void SetMaterialColorProperty(std::vector &embeddedTexIdxs, Asset & if (texIdx != -1) { // embedded // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) uri.data[0] = '*'; - uri.length = 1 + ASSIMP_itoa10(uri.data + 1, MAXLEN - 1, texIdx); + uri.length = 1 + ASSIMP_itoa10(uri.data + 1, AI_MAXLEN - 1, texIdx); } mat->AddProperty(&uri, _AI_MATKEY_TEXTURE_BASE, texType, 0); @@ -242,7 +242,7 @@ void glTFImporter::ImportMeshes(glTF::Asset &r) { if (mesh.primitives.size() > 1) { ai_uint32 &len = aim->mName.length; aim->mName.data[len] = '-'; - len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p); + len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(AI_MAXLEN - len - 1), p); } switch (prim.mode) { diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index 50421d2a0..eb158ce4f 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -161,7 +161,7 @@ static void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset if (texIdx != -1) { // embedded // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) uri.data[0] = '*'; - uri.length = 1 + ASSIMP_itoa10(uri.data + 1, MAXLEN - 1, texIdx); + uri.length = 1 + ASSIMP_itoa10(uri.data + 1, AI_MAXLEN - 1, texIdx); } mat->AddProperty(&uri, AI_MATKEY_TEXTURE(texType, texSlot)); @@ -539,7 +539,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { if (mesh.primitives.size() > 1) { ai_uint32 &len = aim->mName.length; aim->mName.data[len] = '-'; - len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p); + len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(AI_MAXLEN - len - 1), p); } switch (prim.mode) { diff --git a/code/Common/SceneCombiner.cpp b/code/Common/SceneCombiner.cpp index 44897a40b..7954d4d01 100644 --- a/code/Common/SceneCombiner.cpp +++ b/code/Common/SceneCombiner.cpp @@ -78,7 +78,7 @@ inline void PrefixString(aiString &string, const char *prefix, unsigned int len) if (string.length >= 1 && string.data[0] == '$') return; - if (len + string.length >= MAXLEN - 1) { + if (len + string.length >= AI_MAXLEN - 1) { ASSIMP_LOG_VERBOSE_DEBUG("Can't add an unique prefix because the string is too long"); ai_assert(false); return; @@ -408,7 +408,7 @@ void SceneCombiner::MergeScenes(aiScene **_dest, aiScene *master, std::vectormData. The size of mData is not guaranteed to be - // MAXLEN in size. + // AI_MAXLEN in size. aiString s(*(aiString *)prop->mData); if ('*' == s.data[0]) { // Offset the index and write it back .. diff --git a/code/Material/MaterialSystem.cpp b/code/Material/MaterialSystem.cpp index dbae4b7e4..7e2fb51b9 100644 --- a/code/Material/MaterialSystem.cpp +++ b/code/Material/MaterialSystem.cpp @@ -486,7 +486,7 @@ aiReturn aiMaterial::AddBinaryProperty(const void *pInput, memcpy(pcNew->mData, pInput, pSizeInBytes); pcNew->mKey.length = static_cast(::strlen(pKey)); - ai_assert(MAXLEN > pcNew->mKey.length); + ai_assert(AI_MAXLEN > pcNew->mKey.length); strcpy(pcNew->mKey.data, pKey); if (UINT_MAX != iOutIndex) { diff --git a/code/PostProcessing/OptimizeGraph.cpp b/code/PostProcessing/OptimizeGraph.cpp index 3be5ff514..01f6fca14 100644 --- a/code/PostProcessing/OptimizeGraph.cpp +++ b/code/PostProcessing/OptimizeGraph.cpp @@ -164,7 +164,7 @@ void OptimizeGraphProcess::CollectNewChildren(aiNode *nd, std::list &n ++it; } if (join_master && !join.empty()) { - join_master->mName.length = ::ai_snprintf(join_master->mName.data, MAXLEN, "$MergedNode_%u", count_merged++); + join_master->mName.length = ::ai_snprintf(join_master->mName.data, AI_MAXLEN, "$MergedNode_%u", count_merged++); unsigned int out_meshes = 0; for (std::list::const_iterator it = join.cbegin(); it != join.cend(); ++it) { diff --git a/code/PostProcessing/PretransformVertices.cpp b/code/PostProcessing/PretransformVertices.cpp index 37727c968..0203ac211 100644 --- a/code/PostProcessing/PretransformVertices.cpp +++ b/code/PostProcessing/PretransformVertices.cpp @@ -635,7 +635,7 @@ void PretransformVertices::Execute(aiScene *pScene) { aiNode *pcNode = new aiNode(); *nodes = pcNode; pcNode->mParent = pScene->mRootNode; - pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "light_%u", i); + pcNode->mName.length = ai_snprintf(pcNode->mName.data, AI_MAXLEN, "light_%u", i); pScene->mLights[i]->mName = pcNode->mName; } // generate camera nodes @@ -643,7 +643,7 @@ void PretransformVertices::Execute(aiScene *pScene) { aiNode *pcNode = new aiNode(); *nodes = pcNode; pcNode->mParent = pScene->mRootNode; - pcNode->mName.length = ::ai_snprintf(pcNode->mName.data, MAXLEN, "cam_%u", i); + pcNode->mName.length = ::ai_snprintf(pcNode->mName.data, AI_MAXLEN, "cam_%u", i); pScene->mCameras[i]->mName = pcNode->mName; } } diff --git a/code/PostProcessing/RemoveRedundantMaterials.cpp b/code/PostProcessing/RemoveRedundantMaterials.cpp index 828fcd0ac..111c233b1 100644 --- a/code/PostProcessing/RemoveRedundantMaterials.cpp +++ b/code/PostProcessing/RemoveRedundantMaterials.cpp @@ -176,7 +176,7 @@ void RemoveRedundantMatsProcess::Execute( aiScene* pScene) { if (ppcMaterials[idx]) { aiString sz; if( ppcMaterials[idx]->Get(AI_MATKEY_NAME, sz) != AI_SUCCESS ) { - sz.length = ::ai_snprintf(sz.data,MAXLEN,"JoinedMaterial_#%u",p); + sz.length = ::ai_snprintf(sz.data, AI_MAXLEN,"JoinedMaterial_#%u",p); ((aiMaterial*)ppcMaterials[idx])->AddProperty(&sz,AI_MATKEY_NAME); } } else { diff --git a/code/PostProcessing/ValidateDataStructure.cpp b/code/PostProcessing/ValidateDataStructure.cpp index a0a73e200..1dd5d436a 100644 --- a/code/PostProcessing/ValidateDataStructure.cpp +++ b/code/PostProcessing/ValidateDataStructure.cpp @@ -909,9 +909,9 @@ void ValidateDSProcess::Validate(const aiNode *pNode) { // ------------------------------------------------------------------------------------------------ void ValidateDSProcess::Validate(const aiString *pString) { - if (pString->length > MAXLEN) { + if (pString->length > AI_MAXLEN) { ReportError("aiString::length is too large (%u, maximum is %lu)", - pString->length, MAXLEN); + pString->length, AI_MAXLEN); } const char *sz = pString->data; while (true) { @@ -920,7 +920,7 @@ void ValidateDSProcess::Validate(const aiString *pString) { ReportError("aiString::data is invalid: the terminal zero is at a wrong offset"); } break; - } else if (sz >= &pString->data[MAXLEN]) { + } else if (sz >= &pString->data[AI_MAXLEN]) { ReportError("aiString::data is invalid. There is no terminal character"); } ++sz; diff --git a/include/assimp/StringUtils.h b/include/assimp/StringUtils.h index 0af923902..4002d2daf 100644 --- a/include/assimp/StringUtils.h +++ b/include/assimp/StringUtils.h @@ -57,9 +57,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #if defined(_MSC_VER) && !defined(__clang__) -#define AI_SIZEFMT "%Iu" +# define AI_SIZEFMT "%Iu" #else -#define AI_SIZEFMT "%zu" +# define AI_SIZEFMT "%zu" #endif // --------------------------------------------------------------------------------- @@ -99,9 +99,9 @@ inline int ai_snprintf(char *outBuf, size_t size, const char *format, ...) { } #elif defined(__MINGW32__) -#define ai_snprintf __mingw_snprintf +# define ai_snprintf __mingw_snprintf #else -#define ai_snprintf snprintf +# define ai_snprintf snprintf #endif // --------------------------------------------------------------------------------- @@ -185,6 +185,7 @@ AI_FORCE_INLINE std::string ai_rgba2hex(int r, int g, int b, int a, bool with_he // --------------------------------------------------------------------------------- /// @brief Performs a trim from start (in place) /// @param s string to trim. +// --------------------------------------------------------------------------------- AI_FORCE_INLINE void ai_trim_left(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); @@ -195,7 +196,6 @@ AI_FORCE_INLINE void ai_trim_left(std::string &s) { /// @brief Performs a trim from end (in place). /// @param s string to trim. // --------------------------------------------------------------------------------- -// --------------------------------------------------------------------------------- AI_FORCE_INLINE void ai_trim_right(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); @@ -214,6 +214,10 @@ AI_FORCE_INLINE std::string ai_trim(std::string &s) { return out; } +// --------------------------------------------------------------------------------- +/// @brief Performs a to lower operation onto on single character. +/// @param in The character +/// @return the character as lower-case. // --------------------------------------------------------------------------------- template AI_FORCE_INLINE char_t ai_tolower(char_t in) { @@ -233,6 +237,10 @@ AI_FORCE_INLINE std::string ai_tolower(const std::string &in) { return out; } +// --------------------------------------------------------------------------------- +/// @brief Performs a to upper operation onto on single character. +/// @param in The character +/// @return the character as upper-case. // --------------------------------------------------------------------------------- template AI_FORCE_INLINE char_t ai_toupper(char_t in) { @@ -243,6 +251,7 @@ AI_FORCE_INLINE char_t ai_toupper(char_t in) { /// @brief Performs a ToLower-operation and return the upper-case string. /// @param in The incoming string. /// @return The string as uppercase. +// --------------------------------------------------------------------------------- AI_FORCE_INLINE std::string ai_str_toupper(const std::string &in) { std::string out(in); std::transform(out.begin(), out.end(), out.begin(), [](char c) { return ai_toupper(c); }); @@ -255,6 +264,7 @@ AI_FORCE_INLINE std::string ai_str_toupper(const std::string &in) { /// @param in The incoming string. /// @param placeholder Placeholder character, default is a question mark. /// @return The string, with all non-printable characters replaced. +// --------------------------------------------------------------------------------- AI_FORCE_INLINE std::string ai_str_toprintable(const std::string &in, char placeholder = '?') { std::string out(in); std::transform(out.begin(), out.end(), out.begin(), [placeholder] (unsigned char c) { @@ -271,9 +281,9 @@ AI_FORCE_INLINE std::string ai_str_toprintable(const std::string &in, char place /// @param placeholder Placeholder character, default is a question mark. /// @return The string, with all non-printable characters replaced. Will return an /// empty string if in is null or len is <= 0. +// --------------------------------------------------------------------------------- AI_FORCE_INLINE std::string ai_str_toprintable(const char *in, int len, char placeholder = '?') { return (in && len > 0) ? ai_str_toprintable(std::string(in, len), placeholder) : std::string(); } - #endif // INCLUDED_AI_STRINGUTILS_H diff --git a/include/assimp/types.h b/include/assimp/types.h index ea72dd996..84981d483 100644 --- a/include/assimp/types.h +++ b/include/assimp/types.h @@ -118,9 +118,9 @@ extern "C" { /** Maximum dimension for strings, ASSIMP strings are zero terminated. */ #ifdef __cplusplus -static const size_t MAXLEN = 1024; +static const size_t AI_MAXLEN = 1024; #else -#define MAXLEN 1024 +#define AI_MAXLEN 1024 #endif // ---------------------------------------------------------------------------------- @@ -243,7 +243,8 @@ struct aiColor3D { }; // !struct aiColor3D // ---------------------------------------------------------------------------------- -/** Represents an UTF-8 string, zero byte terminated. +/** + * @brief Represents an UTF-8 string, zero byte terminated. * * The character set of an aiString is explicitly defined to be UTF-8. This Unicode * transformation was chosen in the belief that most strings in 3d files are limited @@ -260,42 +261,40 @@ struct aiColor3D { * UTF-8 strings to their working character set (i.e. MBCS, WideChar). * * We use this representation instead of std::string to be C-compatible. The - * (binary) length of such a string is limited to MAXLEN characters (including the + * (binary) length of such a string is limited to AI_MAXLEN characters (including the * the terminating zero). -*/ + */ struct aiString { #ifdef __cplusplus /** Default constructor, the string is set to have zero length */ - aiString() AI_NO_EXCEPT - : length(0) { - data[0] = '\0'; - + aiString() AI_NO_EXCEPT : + length(0), data{'\0'} { #ifdef ASSIMP_BUILD_DEBUG // Debug build: overwrite the string on its full length with ESC (27) - memset(data + 1, 27, MAXLEN - 1); + memset(data + 1, 27, AI_MAXLEN - 1); #endif } /** Copy constructor */ aiString(const aiString &rOther) : - length(rOther.length) { + length(rOther.length), data{'\0'} { // Crop the string to the maximum length - length = length >= MAXLEN ? MAXLEN - 1 : length; + length = length >= AI_MAXLEN ? AI_MAXLEN - 1 : length; memcpy(data, rOther.data, length); data[length] = '\0'; } - + /** Constructor from std::string */ explicit aiString(const std::string &pString) : - length((ai_uint32)pString.length()) { - length = length >= MAXLEN ? MAXLEN - 1 : length; + length((ai_uint32)pString.length()), data{'\0'} { + length = length >= AI_MAXLEN ? AI_MAXLEN - 1 : length; memcpy(data, pString.c_str(), length); data[length] = '\0'; } /** Copy a std::string to the aiString */ void Set(const std::string &pString) { - if (pString.length() > MAXLEN - 1) { + if (pString.length() > AI_MAXLEN - 1) { return; } length = (ai_uint32)pString.length(); @@ -306,8 +305,8 @@ struct aiString { /** Copy a const char* to the aiString */ void Set(const char *sz) { ai_int32 len = (ai_uint32)::strlen(sz); - if (len > (ai_int32)MAXLEN - 1) { - len = (ai_int32) MAXLEN - 1; + if (len > static_cast(AI_MAXLEN - 1)) { + len = static_cast(AI_MAXLEN - 1); } length = len; memcpy(data, sz, len); @@ -321,8 +320,8 @@ struct aiString { } length = rOther.length; - if (length >(MAXLEN - 1)) { - length = (ai_int32) MAXLEN - 1; + if (length > (AI_MAXLEN - 1)) { + length = static_cast(AI_MAXLEN - 1); } memcpy(data, rOther.data, length); @@ -344,21 +343,24 @@ struct aiString { /** Comparison operator */ bool operator==(const aiString &other) const { - return (length == other.length && 0 == memcmp(data, other.data, length)); + if (length == other.length) { + return memcmp(data, other.data, length) == 0; + } + return false; } /** Inverse comparison operator */ bool operator!=(const aiString &other) const { - return (length != other.length || 0 != memcmp(data, other.data, length)); + return !(*this == other); } /** Append a string to the string */ void Append(const char *app) { - const ai_uint32 len = (ai_uint32)::strlen(app); + const ai_uint32 len = static_cast(::strlen(app)); if (!len) { return; } - if (length + len >= MAXLEN) { + if (length + len >= AI_MAXLEN) { return; } @@ -373,7 +375,7 @@ struct aiString { #ifdef ASSIMP_BUILD_DEBUG // Debug build: overwrite the string on its full length with ESC (27) - memset(data + 1, 27, MAXLEN - 1); + memset(data + 1, 27, AI_MAXLEN - 1); #endif } @@ -389,8 +391,8 @@ struct aiString { * the number of bytes from the beginning of the string to its end.*/ ai_uint32 length; - /** String buffer. Size limit is MAXLEN */ - char data[MAXLEN]; + /** String buffer. Size limit is AI_MAXLEN */ + char data[AI_MAXLEN]; }; // !struct aiString // ---------------------------------------------------------------------------------- @@ -528,7 +530,7 @@ struct aiMemoryInfo { */ struct aiBuffer { const char *data; ///< Begin poiner - const char *end; ///< End pointer + const char *end; ///< End pointer #ifdef __cplusplus /// @brief The class constructor. diff --git a/test/unit/utPretransformVertices.cpp b/test/unit/utPretransformVertices.cpp index 9652ec611..a7b21c893 100644 --- a/test/unit/utPretransformVertices.cpp +++ b/test/unit/utPretransformVertices.cpp @@ -68,7 +68,7 @@ void AddNodes(unsigned int num, aiNode *father, unsigned int depth) { for (unsigned int i = 0; i < 5; ++i) { aiNode *nd = father->mChildren[i] = new aiNode(); - nd->mName.length = snprintf(nd->mName.data, MAXLEN, "%i%i", depth, i); + nd->mName.length = snprintf(nd->mName.data, AI_MAXLEN, "%i%i", depth, i); // spawn two meshes nd->mMeshes = new unsigned int[nd->mNumMeshes = 2]; diff --git a/tools/assimp_view/Display.cpp b/tools/assimp_view/Display.cpp index 7e740549c..a542cddac 100644 --- a/tools/assimp_view/Display.cpp +++ b/tools/assimp_view/Display.cpp @@ -174,7 +174,7 @@ int CDisplay::AddNodeToDisplayList( ai_assert(nullptr != pcNode); ai_assert(nullptr != hRoot); - char chTemp[MAXLEN]; + char chTemp[AI_MAXLEN]; if(0 == pcNode->mName.length) { if (iIndex >= 100) { @@ -186,12 +186,12 @@ int CDisplay::AddNodeToDisplayList( } else iIndex += iDepth * 10; - ai_snprintf(chTemp, MAXLEN,"Node %u",iIndex); + ai_snprintf(chTemp,AI_MAXLEN,"Node %u",iIndex); } else { - ai_snprintf(chTemp, MAXLEN,"%s",pcNode->mName.data); + ai_snprintf(chTemp, AI_MAXLEN, "%s", pcNode->mName.data); } - ai_snprintf(chTemp+strlen(chTemp), MAXLEN- strlen(chTemp), iIndex ? " (%i)" : " (%i meshes)",pcNode->mNumMeshes); + ai_snprintf(chTemp + strlen(chTemp), AI_MAXLEN - strlen(chTemp), iIndex ? " (%i)" : " (%i meshes)", pcNode->mNumMeshes); TVITEMEXW tvi; TVINSERTSTRUCTW sNew; @@ -236,15 +236,15 @@ int CDisplay::AddMeshToDisplayList(unsigned int iIndex, HTREEITEM hRoot) { aiMesh* pcMesh = g_pcAsset->pcScene->mMeshes[iIndex]; - char chTemp[MAXLEN]; + char chTemp[AI_MAXLEN]; if(0 == pcMesh->mName.length) { - ai_snprintf(chTemp,MAXLEN,"Mesh %u",iIndex); + ai_snprintf(chTemp, AI_MAXLEN, "Mesh %u", iIndex); } else { - ai_snprintf(chTemp,MAXLEN,"%s",pcMesh->mName.data); + ai_snprintf(chTemp, AI_MAXLEN, "%s", pcMesh->mName.data); } - ai_snprintf(chTemp+strlen(chTemp),MAXLEN-strlen(chTemp), iIndex ? " (%i)" : " (%i faces)",pcMesh->mNumFaces); + ai_snprintf(chTemp + strlen(chTemp), AI_MAXLEN - strlen(chTemp), iIndex ? " (%i)" : " (%i faces)", pcMesh->mNumFaces); TVITEMEXW tvi; TVINSERTSTRUCTW sNew; @@ -280,8 +280,7 @@ int CDisplay::AddMeshToDisplayList(unsigned int iIndex, HTREEITEM hRoot) //------------------------------------------------------------------------------- // Replace the currently selected texture by another one -int CDisplay::ReplaceCurrentTexture(const char* szPath) -{ +int CDisplay::ReplaceCurrentTexture(const char* szPath) { ai_assert(nullptr != szPath); // well ... try to load it diff --git a/tools/assimp_view/Material.cpp b/tools/assimp_view/Material.cpp index 2337f13fa..f5cfce8c6 100644 --- a/tools/assimp_view/Material.cpp +++ b/tools/assimp_view/Material.cpp @@ -281,7 +281,7 @@ bool CMaterialManager::TryLongerPath(char* szTemp,aiString* p_szString) // copy the result string back to the aiString const size_t iLen = strlen(szTempB); size_t iLen2 = iLen+1; - iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2; + iLen2 = iLen2 > AI_MAXLEN ? AI_MAXLEN : iLen2; memcpy(p_szString->data,szTempB,iLen2); p_szString->length = static_cast(iLen); return true; @@ -295,7 +295,7 @@ bool CMaterialManager::TryLongerPath(char* szTemp,aiString* p_szString) // copy the result string back to the aiString const size_t iLen = strlen(szTempB); size_t iLen2 = iLen+1; - iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2; + iLen2 = iLen2 > AI_MAXLEN ? AI_MAXLEN : iLen2; memcpy(p_szString->data,szTempB,iLen2); p_szString->length = static_cast(iLen); return true; @@ -402,7 +402,7 @@ int CMaterialManager::FindValidPath(aiString* p_szString) // copy the result string back to the aiStr const size_t len = strlen(szTemp); size_t len2 = len+1; - len2 = len2 > MAXLEN ? MAXLEN : len2; + len2 = len2 > AI_MAXLEN ? AI_MAXLEN : len2; memcpy(p_szString->data, szTemp, len2); p_szString->length = static_cast(len); } diff --git a/tools/assimp_view/MessageProc.cpp b/tools/assimp_view/MessageProc.cpp index 230d295fc..a21b559dd 100644 --- a/tools/assimp_view/MessageProc.cpp +++ b/tools/assimp_view/MessageProc.cpp @@ -842,7 +842,7 @@ void OpenAsset() { aiString sz; aiGetExtensionList(&sz); - char szList[MAXLEN + 100]; + char szList[AI_MAXLEN + 100]; strcpy(szList,"ASSIMP assets"); char* szCur = szList + 14; strcpy(szCur,sz.data); From 07ab05cc418f2b2fde1615be24c9718cb40b73ec Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Mon, 17 Jun 2024 23:52:23 +0200 Subject: [PATCH 12/39] Fix init aistring issue 5622 inpython module (#5625) * Draft: Update init of aiString - closes https://github.com/assimp/assimp/issues/5622 * Update types.h * Fix typo * Fix another typo * Adapt usage of AI_MAXLEN * Fix compare operator * Add missing renames * Add missing renames --- port/PyAssimp/pyassimp/structs.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/port/PyAssimp/pyassimp/structs.py b/port/PyAssimp/pyassimp/structs.py index 932998025..4364e2793 100644 --- a/port/PyAssimp/pyassimp/structs.py +++ b/port/PyAssimp/pyassimp/structs.py @@ -70,7 +70,7 @@ class String(Structure): See 'types.h' for details. """ - MAXLEN = 1024 + AI_MAXLEN = 1024 _fields_ = [ # Binary length of the string excluding the terminal 0. This is NOT the @@ -79,7 +79,7 @@ class String(Structure): ("length", c_uint32), # String buffer. Size limit is MAXLEN - ("data", c_char*MAXLEN), + ("data", c_char*AI_MAXLEN), ] class MaterialPropertyString(Structure): @@ -90,7 +90,7 @@ class MaterialPropertyString(Structure): material property (see MaterialSystem.cpp aiMaterial::AddProperty() for details). """ - MAXLEN = 1024 + AI_MAXLEN = 1024 _fields_ = [ # Binary length of the string excluding the terminal 0. This is NOT the @@ -98,8 +98,8 @@ class MaterialPropertyString(Structure): # the number of bytes from the beginning of the string to its end. ("length", c_uint32), - # String buffer. Size limit is MAXLEN - ("data", c_char*MAXLEN), + # String buffer. Size limit is AI_MAXLEN + ("data", c_char*AI_MAXLEN), ] class MemoryInfo(Structure): From 0cef794abdf33c2c5a9ebed05b7cbbd5fcb73c9b Mon Sep 17 00:00:00 2001 From: mosfet80 Date: Tue, 18 Jun 2024 09:30:35 +0200 Subject: [PATCH 13/39] Update Readme.md (#5618) link https://bitbucket.org/Starnick/assimpnet/src/master/ is deleted. reverto to old link Co-authored-by: Kim Kulling --- port/AssimpNET/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/port/AssimpNET/Readme.md b/port/AssimpNET/Readme.md index 814cab3e0..dc3be5882 100644 --- a/port/AssimpNET/Readme.md +++ b/port/AssimpNET/Readme.md @@ -1 +1 @@ -Please check the following git-repo for the source: https://github.com/kebby/assimp-net +Please check the following git-repo for the source: https://bitbucket.org/Starnick/assimpnet/ From 5c2a33f23019260000d3bbfb2a0db0e606a11770 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 18 Jun 2024 14:22:12 +0200 Subject: [PATCH 14/39] Make stepfile schema validation more robust. (#5627) * Make stepfile schema validation more robust. * Update STEPFile.h --- code/AssetLib/IFC/IFCLoader.cpp | 9 ++++++++- code/AssetLib/Step/STEPFile.h | 19 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/code/AssetLib/IFC/IFCLoader.cpp b/code/AssetLib/IFC/IFCLoader.cpp index 9414697df..13ea2d429 100644 --- a/code/AssetLib/IFC/IFCLoader.cpp +++ b/code/AssetLib/IFC/IFCLoader.cpp @@ -220,7 +220,7 @@ void IFCImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy std::unique_ptr db(STEP::ReadFileHeader(std::move(stream))); const STEP::HeaderInfo &head = static_cast(*db).GetHeader(); - if (!head.fileSchema.size() || head.fileSchema.substr(0, 3) != "IFC") { + if (!head.fileSchema.size() || head.fileSchema.substr(0, 4) != "IFC2") { ThrowException("Unrecognized file schema: " + head.fileSchema); } @@ -260,6 +260,8 @@ void IFCImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy ThrowException("missing IfcProject entity"); } + + ConversionData conv(*db, proj->To(), pScene, settings); SetUnits(conv); SetCoordinateSpace(conv); @@ -352,6 +354,11 @@ void ConvertUnit(const ::Assimp::STEP::EXPRESS::DataType &dt, ConversionData &co // ------------------------------------------------------------------------------------------------ void SetUnits(ConversionData &conv) { + if (conv.proj.UnitsInContext == nullptr) { + IFCImporter::LogError("Skipping conversion data, nullptr."); + return; + } + // see if we can determine the coordinate space used to express. for (size_t i = 0; i < conv.proj.UnitsInContext->Units.size(); ++i) { ConvertUnit(*conv.proj.UnitsInContext->Units[i], conv); diff --git a/code/AssetLib/Step/STEPFile.h b/code/AssetLib/Step/STEPFile.h index d8bc0ac49..1fd24b329 100644 --- a/code/AssetLib/Step/STEPFile.h +++ b/code/AssetLib/Step/STEPFile.h @@ -82,8 +82,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // this is intended as stress test - by default, entities are evaluated // lazily and therefore not unless needed. -//#define ASSIMP_IFC_TEST - namespace Assimp { // ******************************************************************************** @@ -531,6 +529,7 @@ public: template const T &To() const { + return dynamic_cast(**this); } @@ -581,12 +580,12 @@ private: }; template -inline bool operator==(const std::shared_ptr &lo, T whatever) { +inline bool operator == (const std::shared_ptr &lo, T whatever) { return *lo == whatever; // XXX use std::forward if we have 0x } template -inline bool operator==(const std::pair> &lo, T whatever) { +inline bool operator == (const std::pair> &lo, T whatever) { return *(lo.second) == whatever; // XXX use std::forward if we have 0x } @@ -599,18 +598,30 @@ struct Lazy { Lazy(const LazyObject *obj = nullptr) : obj(obj) {} operator const T *() const { + if (obj == nullptr) { + throw TypeError("Obj type is nullptr."); + } return obj->ToPtr(); } operator const T &() const { + if (obj == nullptr) { + throw TypeError("Obj type is nullptr."); + } return obj->To(); } const T &operator*() const { + if (obj == nullptr) { + throw TypeError("Obj type is nullptr."); + } return obj->To(); } const T *operator->() const { + if (obj == nullptr) { + throw TypeError("Obj type is nullptr."); + } return &obj->To(); } From 77f7706a97f478c059a88a4384ec49779d7321e9 Mon Sep 17 00:00:00 2001 From: michaelsctts <83245396+michaelsctts@users.noreply.github.com> Date: Wed, 19 Jun 2024 09:45:15 -0400 Subject: [PATCH 15/39] fix PLY binary export color from float to uchar (#5608) --- code/AssetLib/Ply/PlyExporter.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/code/AssetLib/Ply/PlyExporter.cpp b/code/AssetLib/Ply/PlyExporter.cpp index 2dc3311ff..9c4b281b8 100644 --- a/code/AssetLib/Ply/PlyExporter.cpp +++ b/code/AssetLib/Ply/PlyExporter.cpp @@ -346,10 +346,22 @@ void PlyExporter::WriteMeshVertsBinary(const aiMesh* m, unsigned int components) for (unsigned int n = PLY_EXPORT_HAS_COLORS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_COLOR_SETS; n <<= 1, ++c) { if (m->HasVertexColors(c)) { - mOutput.write(reinterpret_cast(&m->mColors[c][i].r), 16); + unsigned char rgba[4] = { + static_cast(m->mColors[c][i].r * 255), + static_cast(m->mColors[c][i].g * 255), + static_cast(m->mColors[c][i].b * 255), + static_cast(m->mColors[c][i].a * 255) + }; + mOutput.write(reinterpret_cast(&rgba), 4); } else { - mOutput.write(reinterpret_cast(&defaultColor.r), 16); + unsigned char rgba[4] = { + static_cast(defaultColor.r * 255), + static_cast(defaultColor.g * 255), + static_cast(defaultColor.b * 255), + static_cast(defaultColor.a * 255) + }; + mOutput.write(reinterpret_cast(&rgba), 4); } } From 0afd366dcb789de062b1f2bcbc2ea8e6413b936e Mon Sep 17 00:00:00 2001 From: ycn2022 <124049705+ycn2022@users.noreply.github.com> Date: Fri, 21 Jun 2024 03:26:04 +0800 Subject: [PATCH 16/39] Update FBXMeshGeometry.cpp (#5624) Some FBXs do not have "Materials" information, which can cause parsing errors Co-authored-by: Kim Kulling --- code/AssetLib/FBX/FBXMeshGeometry.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/code/AssetLib/FBX/FBXMeshGeometry.cpp b/code/AssetLib/FBX/FBXMeshGeometry.cpp index 3b706727a..67488f53a 100644 --- a/code/AssetLib/FBX/FBXMeshGeometry.cpp +++ b/code/AssetLib/FBX/FBXMeshGeometry.cpp @@ -644,10 +644,12 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector& materials_out, cons return; } - // materials are handled separately. First of all, they are assigned per-face - // and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect - // has a slightly different meaning for materials. - ParseVectorDataArray(materials_out,GetRequiredElement(source,"Materials")); + if (source["Materials"]) { + // materials are handled separately. First of all, they are assigned per-face + // and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect + // has a slightly different meaning for materials. + ParseVectorDataArray(materials_out, GetRequiredElement(source, "Materials")); + } if (MappingInformationType == "AllSame") { // easy - same material for all faces From adff2f388a23f2192738470b094bb3197feb8ada Mon Sep 17 00:00:00 2001 From: Stepan Hrbek Date: Fri, 21 Jun 2024 13:49:19 +0200 Subject: [PATCH 17/39] Fix collada uv channels - temporary was stored and then updated. So all information on uv channels was ignored. (#5630) Co-authored-by: Kim Kulling --- code/AssetLib/Collada/ColladaParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/Collada/ColladaParser.cpp b/code/AssetLib/Collada/ColladaParser.cpp index a563f6f9c..e7f91b5fe 100644 --- a/code/AssetLib/Collada/ColladaParser.cpp +++ b/code/AssetLib/Collada/ColladaParser.cpp @@ -2293,9 +2293,9 @@ void ColladaParser::ReadNodeGeometry(XmlNode &node, Node *pNode) { urlMat++; s.mMatName = urlMat; + ReadMaterialVertexInputBinding(instanceMatNode, s); // store the association instance.mMaterials[group] = s; - ReadMaterialVertexInputBinding(instanceMatNode, s); } } } From dd10fb6ee4194083dc2f20eb40fbda3c4c5d987d Mon Sep 17 00:00:00 2001 From: Garux Date: Tue, 25 Jun 2024 00:23:59 +0500 Subject: [PATCH 18/39] remove ASE parsing break added in c1968823adfb8c997f98671becca51fc54614da7 : original intent was to keep parsing (#5634) crash case (iMaterialCount = 0) is handled by 47dbabadcd683724be26109c757ad86f4645d280 --- code/AssetLib/ASE/ASEParser.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/code/AssetLib/ASE/ASEParser.cpp b/code/AssetLib/ASE/ASEParser.cpp index 7efab0da3..c071ae900 100644 --- a/code/AssetLib/ASE/ASEParser.cpp +++ b/code/AssetLib/ASE/ASEParser.cpp @@ -513,7 +513,6 @@ void Parser::ParseLV1MaterialListBlock() { if (iIndex >= iMaterialCount) { LogWarning("Out of range: material index is too large"); iIndex = iMaterialCount - 1; - return; } // get a reference to the material From cdf8394ccc4adac94aae9ac395d09eb03608ef2d Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 25 Jun 2024 00:05:31 +0200 Subject: [PATCH 19/39] Fix nullptr dereferencing (#5638) - closes https://github.com/assimp/assimp/issues/5617 --- code/AssetLib/FBX/FBXExporter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/FBX/FBXExporter.cpp b/code/AssetLib/FBX/FBXExporter.cpp index ae210eb1a..50ce3bc2e 100644 --- a/code/AssetLib/FBX/FBXExporter.cpp +++ b/code/AssetLib/FBX/FBXExporter.cpp @@ -1051,7 +1051,7 @@ aiNode* get_node_for_mesh(unsigned int meshIndex, aiNode* node) aiMatrix4x4 get_world_transform(const aiNode* node, const aiScene* scene) { std::vector node_chain; - while (node != scene->mRootNode) { + while (node != scene->mRootNode && node != nullptr) { node_chain.push_back(node); node = node->mParent; } From d0703a5a3af4625bf1bdc865e970abd80a63deec Mon Sep 17 00:00:00 2001 From: Julian Knodt Date: Fri, 28 Jun 2024 03:58:09 -0700 Subject: [PATCH 20/39] Fix exporting incorrect bone order (#5435) Co-authored-by: Kim Kulling --- code/AssetLib/FBX/FBXExporter.cpp | 33 ++++++++++++------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/code/AssetLib/FBX/FBXExporter.cpp b/code/AssetLib/FBX/FBXExporter.cpp index 50ce3bc2e..3b1dd335e 100644 --- a/code/AssetLib/FBX/FBXExporter.cpp +++ b/code/AssetLib/FBX/FBXExporter.cpp @@ -1868,33 +1868,26 @@ void FBXExporter::WriteObjects () // one sticky point is that the number of vertices may not match, // because assimp splits vertices by normal, uv, etc. - // functor for aiNode sorting - struct SortNodeByName - { - bool operator()(const aiNode *lhs, const aiNode *rhs) const - { - return strcmp(lhs->mName.C_Str(), rhs->mName.C_Str()) < 0; - } - }; // first we should mark the skeleton for each mesh. // the skeleton must include not only the aiBones, // but also all their parent nodes. // anything that affects the position of any bone node must be included. - // Use SorNodeByName to make sure the exported result will be the same across all systems - // Otherwise the aiNodes of the skeleton would be sorted based on the pointer address, which isn't consistent - std::vector> skeleton_by_mesh(mScene->mNumMeshes); + + // note that we want to preserve input order as much as possible here. + // previously, sorting by name lead to consistent output across systems, but was not + // suitable for downstream consumption by some applications. + std::vector> skeleton_by_mesh(mScene->mNumMeshes); // at the same time we can build a list of all the skeleton nodes, // which will be used later to mark them as type "limbNode". std::unordered_set limbnodes; //actual bone nodes in fbx, without parenting-up - std::unordered_set setAllBoneNamesInScene; - for(unsigned int m = 0; m < mScene->mNumMeshes; ++ m) - { + std::vector allBoneNames; + for(unsigned int m = 0; m < mScene->mNumMeshes; ++ m) { aiMesh* pMesh = mScene->mMeshes[m]; for(unsigned int b = 0; b < pMesh->mNumBones; ++ b) - setAllBoneNamesInScene.insert(pMesh->mBones[b]->mName.data); + allBoneNames.push_back(pMesh->mBones[b]->mName.data); } aiMatrix4x4 mxTransIdentity; @@ -1902,7 +1895,7 @@ void FBXExporter::WriteObjects () std::map node_by_bone; for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) { const aiMesh* m = mScene->mMeshes[mi]; - std::set skeleton; + std::vector skeleton; for (size_t bi =0; bi < m->mNumBones; ++bi) { const aiBone* b = m->mBones[bi]; const std::string name(b->mName.C_Str()); @@ -1921,7 +1914,7 @@ void FBXExporter::WriteObjects () node_by_bone[name] = n; limbnodes.insert(n); } - skeleton.insert(n); + skeleton.push_back(n); // mark all parent nodes as skeleton as well, // up until we find the root node, // or else the node containing the mesh, @@ -1932,7 +1925,7 @@ void FBXExporter::WriteObjects () parent = parent->mParent ) { // if we've already done this node we can skip it all - if (skeleton.count(parent)) { + if (std::find(skeleton.begin(), skeleton.end(), parent) != skeleton.end()) { break; } // ignore fbx transform nodes as these will be collapsed later @@ -1942,7 +1935,7 @@ void FBXExporter::WriteObjects () continue; } //not a bone in scene && no effect in transform - if(setAllBoneNamesInScene.find(node_name)==setAllBoneNamesInScene.end() + if (std::find(allBoneNames.begin(), allBoneNames.end(), node_name) == allBoneNames.end() && parent->mTransformation == mxTransIdentity) { continue; } @@ -2027,7 +2020,7 @@ void FBXExporter::WriteObjects () aiMatrix4x4 mesh_xform = get_world_transform(mesh_node, mScene); // now make a subdeformer for each bone in the skeleton - const std::set skeleton= skeleton_by_mesh[mi]; + const auto & skeleton= skeleton_by_mesh[mi]; for (const aiNode* bone_node : skeleton) { // if there's a bone for this node, find it const aiBone* b = nullptr; From 0b19b7d73b8c6e40925114e4fcc936f48ee4323d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20M=C3=B6ller?= Date: Sat, 29 Jun 2024 19:20:10 +0200 Subject: [PATCH 21/39] fixes potential memory leak on malformed obj file (#5645) --- code/AssetLib/Obj/ObjFileImporter.cpp | 16 +++++++--------- code/AssetLib/Obj/ObjFileImporter.h | 5 +++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/code/AssetLib/Obj/ObjFileImporter.cpp b/code/AssetLib/Obj/ObjFileImporter.cpp index e956f83d4..09aa84c22 100644 --- a/code/AssetLib/Obj/ObjFileImporter.cpp +++ b/code/AssetLib/Obj/ObjFileImporter.cpp @@ -193,7 +193,7 @@ void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene pScene->mRootNode->mChildren = new aiNode *[childCount]; // Create nodes for the whole scene - std::vector MeshArray; + std::vector> MeshArray; MeshArray.reserve(meshCount); for (size_t index = 0; index < pModel->mObjects.size(); ++index) { createNodes(pModel, pModel->mObjects[index], pScene->mRootNode, pScene, MeshArray); @@ -205,7 +205,7 @@ void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene if (pScene->mNumMeshes > 0) { pScene->mMeshes = new aiMesh *[MeshArray.size()]; for (size_t index = 0; index < MeshArray.size(); ++index) { - pScene->mMeshes[index] = MeshArray[index]; + pScene->mMeshes[index] = MeshArray[index].release(); } } @@ -257,7 +257,7 @@ void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene // Creates all nodes of the model aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile::Object *pObject, aiNode *pParent, aiScene *pScene, - std::vector &MeshArray) { + std::vector> &MeshArray) { ai_assert(nullptr != pModel); if (nullptr == pObject) { return nullptr; @@ -275,12 +275,10 @@ aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile for (size_t i = 0; i < pObject->m_Meshes.size(); ++i) { unsigned int meshId = pObject->m_Meshes[i]; - aiMesh *pMesh = createTopology(pModel, pObject, meshId); + std::unique_ptr pMesh = createTopology(pModel, pObject, meshId); if (pMesh != nullptr) { if (pMesh->mNumFaces > 0) { - MeshArray.push_back(pMesh); - } else { - delete pMesh; + MeshArray.push_back(std::move(pMesh)); } } } @@ -312,7 +310,7 @@ aiNode *ObjFileImporter::createNodes(const ObjFile::Model *pModel, const ObjFile // ------------------------------------------------------------------------------------------------ // Create topology data -aiMesh *ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, unsigned int meshIndex) { +std::unique_ptr ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, unsigned int meshIndex) { // Checking preconditions ai_assert(nullptr != pModel); @@ -394,7 +392,7 @@ aiMesh *ObjFileImporter::createTopology(const ObjFile::Model *pModel, const ObjF // Create mesh vertices createVertexArray(pModel, pData, meshIndex, pMesh.get(), uiIdxCount); - return pMesh.release(); + return pMesh; } // ------------------------------------------------------------------------------------------------ diff --git a/code/AssetLib/Obj/ObjFileImporter.h b/code/AssetLib/Obj/ObjFileImporter.h index e76c27950..6768013e4 100644 --- a/code/AssetLib/Obj/ObjFileImporter.h +++ b/code/AssetLib/Obj/ObjFileImporter.h @@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include struct aiMesh; @@ -84,10 +85,10 @@ protected: //! \brief Creates all nodes stored in imported content. aiNode *createNodes(const ObjFile::Model *pModel, const ObjFile::Object *pData, - aiNode *pParent, aiScene *pScene, std::vector &MeshArray); + aiNode *pParent, aiScene *pScene, std::vector> &MeshArray); //! \brief Creates topology data like faces and meshes for the geometry. - aiMesh *createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, + std::unique_ptr createTopology(const ObjFile::Model *pModel, const ObjFile::Object *pData, unsigned int uiMeshIndex); //! \brief Creates vertices from model. From dd1474e2801c6e0261e8a2d88fbe189789f76d14 Mon Sep 17 00:00:00 2001 From: ThatOSDev <100424906+ThatOSDev@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:12:58 -0600 Subject: [PATCH 22/39] Update zip.c (#5639) Now works with GCC without failing the compilation. Co-authored-by: Kim Kulling --- contrib/zip/src/zip.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/contrib/zip/src/zip.c b/contrib/zip/src/zip.c index ac520a265..deef56178 100644 --- a/contrib/zip/src/zip.c +++ b/contrib/zip/src/zip.c @@ -13,10 +13,14 @@ #include #include +#if defined(_MSC_VER) +/* For Visual Studio only, NOT MinGW (GCC) -- ThatOSDev */ +#pragma warning( disable : 4706 ) +#endif + #if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ defined(__MINGW32__) -/* Win32, DOS, MSVC, MSVS */ -#pragma warning( disable : 4706 ) +/* Win32, DOS, MSVC, MSVS, MinGW(GCC for windows) */ #include #define STRCLONE(STR) ((STR) ? _strdup(STR) : NULL) From 35976a4eb460632c4565234ee5d7105fc1ee2086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20M=C3=B6ller?= Date: Tue, 2 Jul 2024 21:54:18 +0200 Subject: [PATCH 23/39] fixes some uninit bool loads (#5644) This commit fixes some bool loads which are not initialized. With ubsan and the "option -fsanitize=bool", this results in a runtime error during test execution. Co-authored-by: Kim Kulling --- .../LimitBoneWeightsProcess.cpp | 3 ++- include/assimp/metadata.h | 12 +++++------ test/unit/utMetadata.cpp | 20 +++++++++++++++++-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/code/PostProcessing/LimitBoneWeightsProcess.cpp b/code/PostProcessing/LimitBoneWeightsProcess.cpp index 816914ada..71b6f9ec6 100644 --- a/code/PostProcessing/LimitBoneWeightsProcess.cpp +++ b/code/PostProcessing/LimitBoneWeightsProcess.cpp @@ -53,7 +53,8 @@ namespace Assimp { // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -LimitBoneWeightsProcess::LimitBoneWeightsProcess() : mMaxWeights(AI_LMW_MAX_WEIGHTS) { +LimitBoneWeightsProcess::LimitBoneWeightsProcess() : + mMaxWeights(AI_LMW_MAX_WEIGHTS), mRemoveEmptyBones(true) { // empty } diff --git a/include/assimp/metadata.h b/include/assimp/metadata.h index 8d387ca00..4fd4adf7e 100644 --- a/include/assimp/metadata.h +++ b/include/assimp/metadata.h @@ -113,19 +113,19 @@ struct aiMetadata; */ // ------------------------------------------------------------------------------- -inline aiMetadataType GetAiType(bool) { +inline aiMetadataType GetAiType(const bool &) { return AI_BOOL; } inline aiMetadataType GetAiType(int32_t) { return AI_INT32; } -inline aiMetadataType GetAiType(uint64_t) { +inline aiMetadataType GetAiType(const uint64_t &) { return AI_UINT64; } -inline aiMetadataType GetAiType(float) { +inline aiMetadataType GetAiType(const float &) { return AI_FLOAT; } -inline aiMetadataType GetAiType(double) { +inline aiMetadataType GetAiType(const double &) { return AI_DOUBLE; } inline aiMetadataType GetAiType(const aiString &) { @@ -137,10 +137,10 @@ inline aiMetadataType GetAiType(const aiVector3D &) { inline aiMetadataType GetAiType(const aiMetadata &) { return AI_AIMETADATA; } -inline aiMetadataType GetAiType(int64_t) { +inline aiMetadataType GetAiType(const int64_t &) { return AI_INT64; } -inline aiMetadataType GetAiType(uint32_t) { +inline aiMetadataType GetAiType(const uint32_t &) { return AI_UINT32; } diff --git a/test/unit/utMetadata.cpp b/test/unit/utMetadata.cpp index e7cd239aa..76afd52ef 100644 --- a/test/unit/utMetadata.cpp +++ b/test/unit/utMetadata.cpp @@ -242,6 +242,22 @@ TEST_F( utMetadata, copy_test ) { EXPECT_EQ( i32v, v ); } + // uint32_t test + { + uint32_t v = 0; + bool ok = copy.Get("uint32_t", v); + EXPECT_TRUE(ok); + EXPECT_EQ( ui32, v ); + } + + // int64_t test + { + int64_t v = -1; + bool ok = copy.Get("int64_t", v); + EXPECT_TRUE(ok); + EXPECT_EQ( i64, v ); + } + // uint64_t test { uint64_t v = 255; @@ -264,14 +280,14 @@ TEST_F( utMetadata, copy_test ) { EXPECT_EQ( dv, v ); } - // bool test + // string test { aiString v; EXPECT_TRUE( copy.Get( "aiString", v ) ); EXPECT_EQ( strVal, v ); } - // bool test + // vector test { aiVector3D v; EXPECT_TRUE( copy.Get( "aiVector3D", v ) ); From 7e053cc64104605898bb5c48f6b245ca3e1edab3 Mon Sep 17 00:00:00 2001 From: Markus Prettner Date: Tue, 2 Jul 2024 22:11:52 +0200 Subject: [PATCH 24/39] Fix names of enum values in docstring of aiProcess_FindDegenerates (#5640) Co-authored-by: Kim Kulling --- include/assimp/postprocess.h | 2 +- port/PyAssimp/pyassimp/postprocess.py | 2 +- port/dAssimp/assimp/postprocess.d | 2 +- port/jassimp/jassimp/src/jassimp/AiPostProcessSteps.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/assimp/postprocess.h b/include/assimp/postprocess.h index 962d500f8..4fcbbea37 100644 --- a/include/assimp/postprocess.h +++ b/include/assimp/postprocess.h @@ -379,7 +379,7 @@ enum aiPostProcessSteps * point primitives to separate meshes. * *
  • Set the #AI_CONFIG_PP_SBP_REMOVE importer property to - * @code aiPrimitiveType_POINTS | aiPrimitiveType_LINES + * @code aiPrimitiveType_POINT | aiPrimitiveType_LINE * @endcode to cause SortByPType to reject point * and line meshes from the scene. *
  • diff --git a/port/PyAssimp/pyassimp/postprocess.py b/port/PyAssimp/pyassimp/postprocess.py index 0c55d6798..f5a4ac414 100644 --- a/port/PyAssimp/pyassimp/postprocess.py +++ b/port/PyAssimp/pyassimp/postprocess.py @@ -270,7 +270,7 @@ aiProcess_SortByPType = 0x8000 # point primitives to separate meshes. #
  • #
  • Set the AI_CONFIG_PP_SBP_REMOVE option to -# @code aiPrimitiveType_POINTS | aiPrimitiveType_LINES +# @code aiPrimitiveType_POINT | aiPrimitiveType_LINE # @endcode to cause SortByPType to reject point # and line meshes from the scene. #
  • diff --git a/port/dAssimp/assimp/postprocess.d b/port/dAssimp/assimp/postprocess.d index 343bb36dd..ec0d82dbd 100644 --- a/port/dAssimp/assimp/postprocess.d +++ b/port/dAssimp/assimp/postprocess.d @@ -348,7 +348,7 @@ extern ( C ) { *
  • Specify the SortByPType flag. This moves line and * point primitives to separate meshes.
  • *
  • Set the AI_CONFIG_PP_SBP_REMOVE option to - * aiPrimitiveType_POINTS | aiPrimitiveType_LINES + * aiPrimitiveType_POINT | aiPrimitiveType_LINE * to cause SortByPType to reject point and line meshes from the * scene.
  • * diff --git a/port/jassimp/jassimp/src/jassimp/AiPostProcessSteps.java b/port/jassimp/jassimp/src/jassimp/AiPostProcessSteps.java index 7bb617b2c..44d400f58 100644 --- a/port/jassimp/jassimp/src/jassimp/AiPostProcessSteps.java +++ b/port/jassimp/jassimp/src/jassimp/AiPostProcessSteps.java @@ -349,7 +349,7 @@ public enum AiPostProcessSteps { *
  • Specify the #SortByPType flag. This moves line and point * primitives to separate meshes. *
  • Set the AI_CONFIG_PP_SBP_REMOVE option to - * aiPrimitiveType_POINTS | aiPrimitiveType_LINES + * aiPrimitiveType_POINT | aiPrimitiveType_LINE * to cause SortByPType to reject point and line meshes from the * scene. * From 625528d02c17505e752ae63d8d23132782046807 Mon Sep 17 00:00:00 2001 From: Brad D <59133880+bedwardly-down@users.noreply.github.com> Date: Wed, 3 Jul 2024 03:05:58 -0500 Subject: [PATCH 25/39] Fix: StackAllocator Undefined Reference fix (#5650) Co-authored-by: Kim Kulling --- code/Common/StackAllocator.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/code/Common/StackAllocator.h b/code/Common/StackAllocator.h index a5d97c22d..fcb72a09e 100644 --- a/code/Common/StackAllocator.h +++ b/code/Common/StackAllocator.h @@ -88,6 +88,11 @@ private: } // namespace Assimp +/// @brief Fixes an undefined reference error when linking in certain build environments. +// May throw warnings about needing stdc++17, but should compile without issues on modern compilers. +inline const size_t Assimp::StackAllocator::g_maxBytesPerBlock; +inline const size_t Assimp::StackAllocator::g_startBytesPerBlock; + #include "StackAllocator.inl" #endif // include guard From ddb74c2bbdee1565dda667e85f0c82a0588c8053 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Wed, 3 Jul 2024 21:37:24 +0200 Subject: [PATCH 26/39] Fix out of bound access (#5651) --- code/AssetLib/Ply/PlyLoader.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/AssetLib/Ply/PlyLoader.cpp b/code/AssetLib/Ply/PlyLoader.cpp index 3e92339fb..0c2463f24 100644 --- a/code/AssetLib/Ply/PlyLoader.cpp +++ b/code/AssetLib/Ply/PlyLoader.cpp @@ -564,6 +564,10 @@ void PLYImporter::LoadFace(const PLY::Element *pcElement, const PLY::ElementInst if (mGeneratedMesh->mFaces == nullptr) { mGeneratedMesh->mNumFaces = pcElement->NumOccur; mGeneratedMesh->mFaces = new aiFace[mGeneratedMesh->mNumFaces]; + } else { + if (mGeneratedMesh->mNumFaces < pcElement->NumOccur) { + throw DeadlyImportError("Invalid .ply file: Too many faces"); + } } if (!bIsTriStrip) { From fe6e25080be4efc44a4a4df833b99019e963305a Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Fri, 5 Jul 2024 13:43:50 +0200 Subject: [PATCH 27/39] Docker: Fix security finding (#5655) - Use --no-install-recommends to avoid getting more deps than needed --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index eb5715d0f..716e8b5d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM ubuntu:22.04 -RUN apt-get update && apt-get install -y ninja-build \ +RUN apt-get update && apt-get install --no-install-recommends -y ninja-build \ git cmake build-essential software-properties-common RUN add-apt-repository ppa:ubuntu-toolchain-r/test && apt-get update From d5cb1fe01f3e38d611ba30348ab3f2851fdb3402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20M=C3=B6ller?= Date: Sat, 6 Jul 2024 08:27:05 +0200 Subject: [PATCH 28/39] Fix potential heapbuffer overflow in md5 parsing (#5652) --- code/AssetLib/MD5/MD5Parser.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/code/AssetLib/MD5/MD5Parser.cpp b/code/AssetLib/MD5/MD5Parser.cpp index 24882af7e..2de8d5033 100644 --- a/code/AssetLib/MD5/MD5Parser.cpp +++ b/code/AssetLib/MD5/MD5Parser.cpp @@ -234,8 +234,12 @@ inline void AI_MD5_READ_TRIPLE(aiVector3D &vec, const char **sz, const char *buf AI_MD5_SKIP_SPACES(sz, bufferEnd, linenumber); if ('(' != **sz) { MD5Parser::ReportWarning("Unexpected token: ( was expected", linenumber); + if (*sz == bufferEnd) + return; ++*sz; } + if (*sz == bufferEnd) + return; ++*sz; AI_MD5_SKIP_SPACES(sz, bufferEnd, linenumber); *sz = fast_atoreal_move(*sz, (float &)vec.x); @@ -247,6 +251,8 @@ inline void AI_MD5_READ_TRIPLE(aiVector3D &vec, const char **sz, const char *buf if (')' != **sz) { MD5Parser::ReportWarning("Unexpected token: ) was expected", linenumber); } + if (*sz == bufferEnd) + return; ++*sz; } From bff00b15f13df3f0afa77ef23b60685dadfbfd55 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Sat, 6 Jul 2024 13:59:02 +0200 Subject: [PATCH 29/39] Replace raw pointers by std::string (#5656) * Replace raw pointers by std::string --- code/AssetLib/3MF/XmlSerializer.cpp | 10 +- code/AssetLib/AMF/AMFImporter.cpp | 27 +- code/AssetLib/AMF/AMFImporter.hpp | 1 - code/AssetLib/AMF/AMFImporter_Node.hpp | 15 - code/AssetLib/AMF/AMFImporter_Postprocess.cpp | 5 +- code/AssetLib/ASE/ASELoader.cpp | 29 +- code/AssetLib/ASE/ASELoader.h | 7 +- code/AssetLib/ASE/ASEParser.cpp | 553 +++++++++--------- code/AssetLib/ASE/ASEParser.h | 11 +- test/unit/utB3DImportExport.cpp | 2 +- 10 files changed, 312 insertions(+), 348 deletions(-) diff --git a/code/AssetLib/3MF/XmlSerializer.cpp b/code/AssetLib/3MF/XmlSerializer.cpp index 354ed19bb..fdc9f5a3d 100644 --- a/code/AssetLib/3MF/XmlSerializer.cpp +++ b/code/AssetLib/3MF/XmlSerializer.cpp @@ -57,8 +57,8 @@ static constexpr size_t ColRGBA_Len = 9; static constexpr size_t ColRGB_Len = 7; // format of the color string: #RRGGBBAA or #RRGGBB (3MF Core chapter 5.1.1) -bool validateColorString(const char *color) { - const size_t len = strlen(color); +bool validateColorString(const std::string color) { + const size_t len = color.size(); if (ColRGBA_Len != len && ColRGB_Len != len) { return false; } @@ -157,8 +157,8 @@ aiMatrix4x4 parseTransformMatrix(const std::string& matrixStr) { return transformMatrix; } -bool parseColor(const char *color, aiColor4D &diffuse) { - if (nullptr == color) { +bool parseColor(const std::string &color, aiColor4D &diffuse) { + if (color.empty()) { return false; } @@ -178,7 +178,7 @@ bool parseColor(const char *color, aiColor4D &diffuse) { char b[3] = { color[5], color[6], '\0' }; diffuse.b = static_cast(strtol(b, nullptr, 16)) / ai_real(255.0); - const size_t len = strlen(color); + const size_t len = color.size(); if (ColRGB_Len == len) { return true; } diff --git a/code/AssetLib/AMF/AMFImporter.cpp b/code/AssetLib/AMF/AMFImporter.cpp index 7c0d3b4e9..42f9664be 100644 --- a/code/AssetLib/AMF/AMFImporter.cpp +++ b/code/AssetLib/AMF/AMFImporter.cpp @@ -178,28 +178,6 @@ bool AMFImporter::XML_SearchNode(const std::string &nodeName) { return nullptr != mXmlParser->findNode(nodeName); } -void AMFImporter::ParseHelper_FixTruncatedFloatString(const char *pInStr, std::string &pOutString) { - size_t instr_len; - - pOutString.clear(); - instr_len = strlen(pInStr); - if (!instr_len) return; - - pOutString.reserve(instr_len * 3 / 2); - // check and correct floats in format ".x". Must be "x.y". - if (pInStr[0] == '.') pOutString.push_back('0'); - - pOutString.push_back(pInStr[0]); - for (size_t ci = 1; ci < instr_len; ci++) { - if ((pInStr[ci] == '.') && ((pInStr[ci - 1] == ' ') || (pInStr[ci - 1] == '-') || (pInStr[ci - 1] == '+') || (pInStr[ci - 1] == '\t'))) { - pOutString.push_back('0'); - pOutString.push_back('.'); - } else { - pOutString.push_back(pInStr[ci]); - } - } -} - static bool ParseHelper_Decode_Base64_IsBase64(const char pChar) { return (isalnum((unsigned char)pChar) || (pChar == '+') || (pChar == '/')); } @@ -213,7 +191,10 @@ void AMFImporter::ParseHelper_Decode_Base64(const std::string &pInputBase64, std uint8_t arr4[4], arr3[3]; // check input data - if (pInputBase64.size() % 4) throw DeadlyImportError("Base64-encoded data must have size multiply of four."); + if (pInputBase64.size() % 4) { + throw DeadlyImportError("Base64-encoded data must have size multiply of four."); + } + // prepare output place pOutputData.clear(); pOutputData.reserve(pInputBase64.size() / 4 * 3); diff --git a/code/AssetLib/AMF/AMFImporter.hpp b/code/AssetLib/AMF/AMFImporter.hpp index 50be465ce..97e0a7118 100644 --- a/code/AssetLib/AMF/AMFImporter.hpp +++ b/code/AssetLib/AMF/AMFImporter.hpp @@ -168,7 +168,6 @@ public: AI_WONT_RETURN void Throw_ID_NotFound(const std::string &pID) const AI_WONT_RETURN_SUFFIX; void XML_CheckNode_MustHaveChildren(pugi::xml_node &node); bool XML_SearchNode(const std::string &nodeName); - void ParseHelper_FixTruncatedFloatString(const char *pInStr, std::string &pOutString); AMFImporter(const AMFImporter &pScene) = delete; AMFImporter &operator=(const AMFImporter &pScene) = delete; diff --git a/code/AssetLib/AMF/AMFImporter_Node.hpp b/code/AssetLib/AMF/AMFImporter_Node.hpp index 21068a9ba..2b4f6717d 100644 --- a/code/AssetLib/AMF/AMFImporter_Node.hpp +++ b/code/AssetLib/AMF/AMFImporter_Node.hpp @@ -56,7 +56,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -/// \class CAMFImporter_NodeElement /// Base class for elements of nodes. class AMFNodeElementBase { public: @@ -106,7 +105,6 @@ protected: } }; // class IAMFImporter_NodeElement -/// \struct CAMFImporter_NodeElement_Constellation /// A collection of objects or constellations with specific relative locations. struct AMFConstellation : public AMFNodeElementBase { /// Constructor. @@ -116,7 +114,6 @@ struct AMFConstellation : public AMFNodeElementBase { }; // struct CAMFImporter_NodeElement_Constellation -/// \struct CAMFImporter_NodeElement_Instance /// Part of constellation. struct AMFInstance : public AMFNodeElementBase { @@ -135,7 +132,6 @@ struct AMFInstance : public AMFNodeElementBase { AMFNodeElementBase(ENET_Instance, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Metadata /// Structure that define metadata node. struct AMFMetadata : public AMFNodeElementBase { @@ -148,7 +144,6 @@ struct AMFMetadata : public AMFNodeElementBase { AMFNodeElementBase(ENET_Metadata, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Root /// Structure that define root node. struct AMFRoot : public AMFNodeElementBase { @@ -161,7 +156,6 @@ struct AMFRoot : public AMFNodeElementBase { AMFNodeElementBase(ENET_Root, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Color /// Structure that define object node. struct AMFColor : public AMFNodeElementBase { bool Composed; ///< Type of color stored: if true then look for formula in \ref Color_Composed[4], else - in \ref Color. @@ -177,7 +171,6 @@ struct AMFColor : public AMFNodeElementBase { } }; -/// \struct CAMFImporter_NodeElement_Material /// Structure that define material node. struct AMFMaterial : public AMFNodeElementBase { @@ -187,7 +180,6 @@ struct AMFMaterial : public AMFNodeElementBase { AMFNodeElementBase(ENET_Material, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Object /// Structure that define object node. struct AMFObject : public AMFNodeElementBase { @@ -206,7 +198,6 @@ struct AMFMesh : public AMFNodeElementBase { AMFNodeElementBase(ENET_Mesh, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Vertex /// Structure that define vertex node. struct AMFVertex : public AMFNodeElementBase { /// Constructor. @@ -215,7 +206,6 @@ struct AMFVertex : public AMFNodeElementBase { AMFNodeElementBase(ENET_Vertex, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Edge /// Structure that define edge node. struct AMFEdge : public AMFNodeElementBase { /// Constructor. @@ -224,7 +214,6 @@ struct AMFEdge : public AMFNodeElementBase { AMFNodeElementBase(ENET_Edge, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Vertices /// Structure that define vertices node. struct AMFVertices : public AMFNodeElementBase { /// Constructor. @@ -233,7 +222,6 @@ struct AMFVertices : public AMFNodeElementBase { AMFNodeElementBase(ENET_Vertices, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Volume /// Structure that define volume node. struct AMFVolume : public AMFNodeElementBase { std::string MaterialID; ///< Which material to use. @@ -245,7 +233,6 @@ struct AMFVolume : public AMFNodeElementBase { AMFNodeElementBase(ENET_Volume, pParent) {} }; -/// \struct CAMFImporter_NodeElement_Coordinates /// Structure that define coordinates node. struct AMFCoordinates : public AMFNodeElementBase { aiVector3D Coordinate; ///< Coordinate. @@ -256,7 +243,6 @@ struct AMFCoordinates : public AMFNodeElementBase { AMFNodeElementBase(ENET_Coordinates, pParent) {} }; -/// \struct CAMFImporter_NodeElement_TexMap /// Structure that define texture coordinates node. struct AMFTexMap : public AMFNodeElementBase { aiVector3D TextureCoordinate[3]; ///< Texture coordinates. @@ -273,7 +259,6 @@ struct AMFTexMap : public AMFNodeElementBase { } }; -/// \struct CAMFImporter_NodeElement_Triangle /// Structure that define triangle node. struct AMFTriangle : public AMFNodeElementBase { size_t V[3]; ///< Triangle vertices. diff --git a/code/AssetLib/AMF/AMFImporter_Postprocess.cpp b/code/AssetLib/AMF/AMFImporter_Postprocess.cpp index 969c64bd2..bc6fb42a8 100644 --- a/code/AssetLib/AMF/AMFImporter_Postprocess.cpp +++ b/code/AssetLib/AMF/AMFImporter_Postprocess.cpp @@ -224,7 +224,8 @@ size_t AMFImporter::PostprocessHelper_GetTextureID_Or_Create(const std::string & } // Create format hint. - strcpy(converted_texture.FormatHint, "rgba0000"); // copy initial string. + constexpr char templateColor[] = "rgba0000"; + memcpy(converted_texture.FormatHint, templateColor, 8); if (!r.empty()) converted_texture.FormatHint[4] = '8'; if (!g.empty()) converted_texture.FormatHint[5] = '8'; if (!b.empty()) converted_texture.FormatHint[6] = '8'; @@ -867,7 +868,7 @@ nl_clean_loop: pScene->mTextures[idx]->mHeight = static_cast(tex_convd.Height); pScene->mTextures[idx]->pcData = (aiTexel *)tex_convd.Data; // texture format description. - strcpy(pScene->mTextures[idx]->achFormatHint, tex_convd.FormatHint); + strncpy(pScene->mTextures[idx]->achFormatHint, tex_convd.FormatHint, HINTMAXTEXTURELEN); idx++; } // for(const SPP_Texture& tex_convd: mTexture_Converted) diff --git a/code/AssetLib/ASE/ASELoader.cpp b/code/AssetLib/ASE/ASELoader.cpp index 7e411fc03..c5f2eba32 100644 --- a/code/AssetLib/ASE/ASELoader.cpp +++ b/code/AssetLib/ASE/ASELoader.cpp @@ -124,7 +124,7 @@ void ASEImporter::InternReadFile(const std::string &pFile, // Allocate storage and copy the contents of the file to a memory buffer std::vector mBuffer2; TextFileToBuffer(file.get(), mBuffer2); - + const size_t fileSize = mBuffer2.size(); this->mBuffer = &mBuffer2[0]; this->pcScene = pScene; @@ -146,7 +146,7 @@ void ASEImporter::InternReadFile(const std::string &pFile, }; // Construct an ASE parser and parse the file - ASE::Parser parser(mBuffer, defaultFormat); + ASE::Parser parser(mBuffer, fileSize, defaultFormat); mParser = &parser; mParser->Parse(); @@ -446,10 +446,9 @@ void ASEImporter::BuildLights() { } // ------------------------------------------------------------------------------------------------ -void ASEImporter::AddNodes(const std::vector &nodes, - aiNode *pcParent, const char *szName) { +void ASEImporter::AddNodes(const std::vector &nodes, aiNode *pcParent, const std::string &name) { aiMatrix4x4 m; - AddNodes(nodes, pcParent, szName, m); + AddNodes(nodes, pcParent, name, m); } // ------------------------------------------------------------------------------------------------ @@ -506,10 +505,9 @@ void ASEImporter::AddMeshes(const ASE::BaseNode *snode, aiNode *node) { // ------------------------------------------------------------------------------------------------ // Add child nodes to a given parent node -void ASEImporter::AddNodes(const std::vector &nodes, - aiNode *pcParent, const char *szName, +void ASEImporter::AddNodes(const std::vector &nodes, aiNode *pcParent, const std::string &name, const aiMatrix4x4 &mat) { - const size_t len = szName ? ::strlen(szName) : 0; + const size_t len = name.size(); ai_assert(4 <= AI_MAX_NUMBER_OF_COLOR_SETS); // Receives child nodes for the pcParent node @@ -519,16 +517,18 @@ void ASEImporter::AddNodes(const std::vector &nodes, // which has *us* as parent. for (std::vector::const_iterator it = nodes.begin(), end = nodes.end(); it != end; ++it) { const BaseNode *snode = *it; - if (szName) { - if (len != snode->mParent.length() || ::strcmp(szName, snode->mParent.c_str())) + if (!name.empty()) { + if (len != snode->mParent.length() || name != snode->mParent.c_str()) { continue; - } else if (snode->mParent.length()) + } + } else if (snode->mParent.length()) { continue; + } (*it)->mProcessed = true; // Allocate a new node and add it to the output data structure - apcNodes.push_back(new aiNode()); + apcNodes.push_back(new aiNode); aiNode *node = apcNodes.back(); node->mName.Set((snode->mName.length() ? snode->mName.c_str() : "Unnamed_Node")); @@ -541,7 +541,7 @@ void ASEImporter::AddNodes(const std::vector &nodes, // Add sub nodes - prevent stack overflow due to recursive parenting if (node->mName != node->mParent->mName && node->mName != node->mParent->mParent->mName) { - AddNodes(nodes, node, node->mName.data, snode->mTransform); + AddNodes(nodes, node, node->mName.C_Str(), snode->mTransform); } // Further processing depends on the type of the node @@ -619,7 +619,8 @@ void ASEImporter::BuildNodes(std::vector &nodes) { } // add all nodes - AddNodes(nodes, ch, nullptr); + static const std::string none = ""; + AddNodes(nodes, ch, none); // now iterate through al nodes and find those that have not yet // been added to the nodegraph (= their parent could not be recognized) diff --git a/code/AssetLib/ASE/ASELoader.h b/code/AssetLib/ASE/ASELoader.h index 5654fa630..99d5119ed 100644 --- a/code/AssetLib/ASE/ASELoader.h +++ b/code/AssetLib/ASE/ASELoader.h @@ -153,13 +153,13 @@ private: * \param matrix Current transform */ void AddNodes(const std::vector& nodes, - aiNode* pcParent,const char* szName); + aiNode* pcParent, const std::string &name); void AddNodes(const std::vector& nodes, - aiNode* pcParent,const char* szName, + aiNode* pcParent, const std::string &name, const aiMatrix4x4& matrix); - void AddMeshes(const ASE::BaseNode* snode,aiNode* node); + void AddMeshes(const ASE::BaseNode* snode, aiNode* node); // ------------------------------------------------------------------- /** Generate a default material and add it to the parser's list @@ -188,5 +188,4 @@ protected: } // end of namespace Assimp - #endif // AI_3DSIMPORTER_H_INC diff --git a/code/AssetLib/ASE/ASEParser.cpp b/code/AssetLib/ASE/ASEParser.cpp index c071ae900..02d21f41b 100644 --- a/code/AssetLib/ASE/ASEParser.cpp +++ b/code/AssetLib/ASE/ASEParser.cpp @@ -66,23 +66,24 @@ using namespace Assimp::ASE; // Handle a "top-level" section in the file. EOF is no error in this case. #define AI_ASE_HANDLE_TOP_LEVEL_SECTION() \ - else if ('{' == *filePtr) iDepth++; \ - else if ('}' == *filePtr) { \ + else if ('{' == *mFilePtr) \ + ++iDepth; \ + else if ('}' == *mFilePtr) { \ if (0 == --iDepth) { \ - ++filePtr; \ + ++mFilePtr; \ SkipToNextToken(); \ return; \ } \ } \ - if ('\0' == *filePtr) { \ + if ('\0' == *mFilePtr) { \ return; \ } \ - if (IsLineEnd(*filePtr) && !bLastWasEndLine) { \ + if (IsLineEnd(*mFilePtr) && !bLastWasEndLine) {\ ++iLineNumber; \ bLastWasEndLine = true; \ } else \ bLastWasEndLine = false; \ - ++filePtr; + ++mFilePtr; // ------------------------------------------------------------------------------------------------ // Handle a nested section in the file. EOF is an error in this case @@ -90,32 +91,32 @@ using namespace Assimp::ASE; // @param msg Full name of the section (including the asterisk) #define AI_ASE_HANDLE_SECTION(level, msg) \ - if ('{' == *filePtr) \ + if ('{' == *mFilePtr) \ iDepth++; \ - else if ('}' == *filePtr) { \ + else if ('}' == *mFilePtr) { \ if (0 == --iDepth) { \ - ++filePtr; \ + ++mFilePtr; \ SkipToNextToken(); \ return; \ } \ - } else if ('\0' == *filePtr) { \ + } else if ('\0' == *mFilePtr) { \ LogError("Encountered unexpected EOL while parsing a " msg \ " chunk (Level " level ")"); \ } \ - if (IsLineEnd(*filePtr) && !bLastWasEndLine) { \ + if (IsLineEnd(*mFilePtr) && !bLastWasEndLine) { \ ++iLineNumber; \ bLastWasEndLine = true; \ } else \ bLastWasEndLine = false; \ - ++filePtr; + ++mFilePtr; // ------------------------------------------------------------------------------------------------ -Parser::Parser(const char *szFile, unsigned int fileFormatDefault) : - filePtr(nullptr), mEnd (nullptr) { - ai_assert(nullptr != szFile); +Parser::Parser(const char *file, size_t fileLen, unsigned int fileFormatDefault) : + mFilePtr(nullptr), mEnd (nullptr) { + ai_assert(file != nullptr); - filePtr = szFile; - mEnd = filePtr + std::strlen(filePtr); + mFilePtr = file; + mEnd = mFilePtr + fileLen; iFileFormat = fileFormatDefault; // make sure that the color values are invalid @@ -179,9 +180,9 @@ AI_WONT_RETURN void Parser::LogError(const char *szWarn) { // ------------------------------------------------------------------------------------------------ bool Parser::SkipToNextToken() { while (true) { - char me = *filePtr; + char me = *mFilePtr; - if (filePtr == mEnd) { + if (mFilePtr == mEnd) { return false; } @@ -198,7 +199,7 @@ bool Parser::SkipToNextToken() { return false; } - ++filePtr; + ++mFilePtr; } } @@ -207,22 +208,22 @@ bool Parser::SkipSection() { // must handle subsections ... int iCnt = 0; while (true) { - if ('}' == *filePtr) { + if ('}' == *mFilePtr) { --iCnt; if (0 == iCnt) { // go to the next valid token ... - ++filePtr; + ++mFilePtr; SkipToNextToken(); return true; } - } else if ('{' == *filePtr) { + } else if ('{' == *mFilePtr) { ++iCnt; - } else if ('\0' == *filePtr) { + } else if ('\0' == *mFilePtr) { LogWarning("Unable to parse block: Unexpected EOF, closing bracket \'}\' was expected [#1]"); return false; - } else if (IsLineEnd(*filePtr)) + } else if (IsLineEnd(*mFilePtr)) ++iLineNumber; - ++filePtr; + ++mFilePtr; } } @@ -230,11 +231,11 @@ bool Parser::SkipSection() { void Parser::Parse() { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Version should be 200. Validate this ... - if (TokenMatch(filePtr, "3DSMAX_ASCIIEXPORT", 18)) { + if (TokenMatch(mFilePtr, "3DSMAX_ASCIIEXPORT", 18)) { unsigned int fmt; ParseLV4MeshLong(fmt); @@ -255,23 +256,23 @@ void Parser::Parse() { continue; } // main scene information - if (TokenMatch(filePtr, "SCENE", 5)) { + if (TokenMatch(mFilePtr, "SCENE", 5)) { ParseLV1SceneBlock(); continue; } // "group" - no implementation yet, in facte // we're just ignoring them for the moment - if (TokenMatch(filePtr, "GROUP", 5)) { + if (TokenMatch(mFilePtr, "GROUP", 5)) { Parse(); continue; } // material list - if (TokenMatch(filePtr, "MATERIAL_LIST", 13)) { + if (TokenMatch(mFilePtr, "MATERIAL_LIST", 13)) { ParseLV1MaterialListBlock(); continue; } // geometric object (mesh) - if (TokenMatch(filePtr, "GEOMOBJECT", 10)) + if (TokenMatch(mFilePtr, "GEOMOBJECT", 10)) { m_vMeshes.emplace_back("UNNAMED"); @@ -279,7 +280,7 @@ void Parser::Parse() { continue; } // helper object = dummy in the hierarchy - if (TokenMatch(filePtr, "HELPEROBJECT", 12)) + if (TokenMatch(mFilePtr, "HELPEROBJECT", 12)) { m_vDummies.emplace_back(); @@ -287,7 +288,7 @@ void Parser::Parse() { continue; } // light object - if (TokenMatch(filePtr, "LIGHTOBJECT", 11)) + if (TokenMatch(mFilePtr, "LIGHTOBJECT", 11)) { m_vLights.emplace_back("UNNAMED"); @@ -295,20 +296,20 @@ void Parser::Parse() { continue; } // camera object - if (TokenMatch(filePtr, "CAMERAOBJECT", 12)) { + if (TokenMatch(mFilePtr, "CAMERAOBJECT", 12)) { m_vCameras.emplace_back("UNNAMED"); ParseLV1ObjectBlock(m_vCameras.back()); continue; } // comment - print it on the console - if (TokenMatch(filePtr, "COMMENT", 7)) { + if (TokenMatch(mFilePtr, "COMMENT", 7)) { std::string out = ""; ParseString(out, "*COMMENT"); LogInfo(("Comment: " + out).c_str()); continue; } // ASC bone weights - if (AI_ASE_IS_OLD_FILE_FORMAT() && TokenMatch(filePtr, "MESH_SOFTSKINVERTS", 18)) { + if (AI_ASE_IS_OLD_FILE_FORMAT() && TokenMatch(mFilePtr, "MESH_SOFTSKINVERTS", 18)) { ParseLV1SoftSkinBlock(); } } @@ -340,25 +341,25 @@ void Parser::ParseLV1SoftSkinBlock() { */ // ************************************************************** while (true) { - if (*filePtr == '}') { - ++filePtr; + if (*mFilePtr == '}') { + ++mFilePtr; return; - } else if (*filePtr == '\0') + } else if (*mFilePtr == '\0') return; - else if (*filePtr == '{') - ++filePtr; + else if (*mFilePtr == '{') + ++mFilePtr; else // if (!IsSpace(*filePtr) && !IsLineEnd(*filePtr)) { ASE::Mesh *curMesh = nullptr; unsigned int numVerts = 0; - const char *sz = filePtr; - while (!IsSpaceOrNewLine(*filePtr)) { - ++filePtr; + const char *sz = mFilePtr; + while (!IsSpaceOrNewLine(*mFilePtr)) { + ++mFilePtr; } - const unsigned int diff = (unsigned int)(filePtr - sz); + const unsigned int diff = (unsigned int)(mFilePtr - sz); if (diff) { std::string name = std::string(sz, diff); for (std::vector::iterator it = m_vMeshes.begin(); @@ -374,24 +375,24 @@ void Parser::ParseLV1SoftSkinBlock() { // Skip the mesh data - until we find a new mesh // or the end of the *MESH_SOFTSKINVERTS section while (true) { - SkipSpacesAndLineEnd(&filePtr, mEnd); - if (*filePtr == '}') { - ++filePtr; + SkipSpacesAndLineEnd(&mFilePtr, mEnd); + if (*mFilePtr == '}') { + ++mFilePtr; return; - } else if (!IsNumeric(*filePtr)) + } else if (!IsNumeric(*mFilePtr)) break; - SkipLine(&filePtr, mEnd); + SkipLine(&mFilePtr, mEnd); } } else { - SkipSpacesAndLineEnd(&filePtr, mEnd); + SkipSpacesAndLineEnd(&mFilePtr, mEnd); ParseLV4MeshLong(numVerts); // Reserve enough storage curMesh->mBoneVertices.reserve(numVerts); for (unsigned int i = 0; i < numVerts; ++i) { - SkipSpacesAndLineEnd(&filePtr, mEnd); + SkipSpacesAndLineEnd(&mFilePtr, mEnd); unsigned int numWeights; ParseLV4MeshLong(numWeights); @@ -430,10 +431,10 @@ void Parser::ParseLV1SoftSkinBlock() { } } } - if (*filePtr == '\0') + if (*mFilePtr == '\0') return; - ++filePtr; - SkipSpacesAndLineEnd(&filePtr, mEnd); + ++mFilePtr; + SkipSpacesAndLineEnd(&mFilePtr, mEnd); } } @@ -441,35 +442,35 @@ void Parser::ParseLV1SoftSkinBlock() { void Parser::ParseLV1SceneBlock() { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (TokenMatch(filePtr, "SCENE_BACKGROUND_STATIC", 23)) + if ('*' == *mFilePtr) { + ++mFilePtr; + if (TokenMatch(mFilePtr, "SCENE_BACKGROUND_STATIC", 23)) { // parse a color triple and assume it is really the bg color ParseLV4MeshFloatTriple(&m_clrBackground.r); continue; } - if (TokenMatch(filePtr, "SCENE_AMBIENT_STATIC", 20)) + if (TokenMatch(mFilePtr, "SCENE_AMBIENT_STATIC", 20)) { // parse a color triple and assume it is really the bg color ParseLV4MeshFloatTriple(&m_clrAmbient.r); continue; } - if (TokenMatch(filePtr, "SCENE_FIRSTFRAME", 16)) { + if (TokenMatch(mFilePtr, "SCENE_FIRSTFRAME", 16)) { ParseLV4MeshLong(iFirstFrame); continue; } - if (TokenMatch(filePtr, "SCENE_LASTFRAME", 15)) { + if (TokenMatch(mFilePtr, "SCENE_LASTFRAME", 15)) { ParseLV4MeshLong(iLastFrame); continue; } - if (TokenMatch(filePtr, "SCENE_FRAMESPEED", 16)) { + if (TokenMatch(mFilePtr, "SCENE_FRAMESPEED", 16)) { ParseLV4MeshLong(iFrameSpeed); continue; } - if (TokenMatch(filePtr, "SCENE_TICKSPERFRAME", 19)) { + if (TokenMatch(mFilePtr, "SCENE_TICKSPERFRAME", 19)) { ParseLV4MeshLong(iTicksPerFrame); continue; } @@ -485,9 +486,9 @@ void Parser::ParseLV1MaterialListBlock() { unsigned int iMaterialCount = 0; unsigned int iOldMaterialCount = (unsigned int)m_vMaterials.size(); while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (TokenMatch(filePtr, "MATERIAL_COUNT", 14)) { + if ('*' == *mFilePtr) { + ++mFilePtr; + if (TokenMatch(mFilePtr, "MATERIAL_COUNT", 14)) { ParseLV4MeshLong(iMaterialCount); if (UINT_MAX - iOldMaterialCount < iMaterialCount) { @@ -499,7 +500,7 @@ void Parser::ParseLV1MaterialListBlock() { m_vMaterials.resize(iOldMaterialCount + iMaterialCount, Material("INVALID")); continue; } - if (TokenMatch(filePtr, "MATERIAL", 8)) { + if (TokenMatch(mFilePtr, "MATERIAL", 8)) { // ensure we have at least one material allocated if (iMaterialCount == 0) { LogWarning("*MATERIAL_COUNT unspecified or 0"); @@ -524,7 +525,7 @@ void Parser::ParseLV1MaterialListBlock() { if( iDepth == 1 ){ // CRUDE HACK: support missing brace after "Ascii Scene Exporter v2.51" LogWarning("Missing closing brace in material list"); - --filePtr; + --mFilePtr; return; } } @@ -538,37 +539,37 @@ void Parser::ParseLV2MaterialBlock(ASE::Material &mat) { unsigned int iNumSubMaterials = 0; while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (TokenMatch(filePtr, "MATERIAL_NAME", 13)) { + if ('*' == *mFilePtr) { + ++mFilePtr; + if (TokenMatch(mFilePtr, "MATERIAL_NAME", 13)) { if (!ParseString(mat.mName, "*MATERIAL_NAME")) SkipToNextToken(); continue; } // ambient material color - if (TokenMatch(filePtr, "MATERIAL_AMBIENT", 16)) { + if (TokenMatch(mFilePtr, "MATERIAL_AMBIENT", 16)) { ParseLV4MeshFloatTriple(&mat.mAmbient.r); continue; } // diffuse material color - if (TokenMatch(filePtr, "MATERIAL_DIFFUSE", 16)) { + if (TokenMatch(mFilePtr, "MATERIAL_DIFFUSE", 16)) { ParseLV4MeshFloatTriple(&mat.mDiffuse.r); continue; } // specular material color - if (TokenMatch(filePtr, "MATERIAL_SPECULAR", 17)) { + if (TokenMatch(mFilePtr, "MATERIAL_SPECULAR", 17)) { ParseLV4MeshFloatTriple(&mat.mSpecular.r); continue; } // material shading type - if (TokenMatch(filePtr, "MATERIAL_SHADING", 16)) { - if (TokenMatch(filePtr, "Blinn", 5)) { + if (TokenMatch(mFilePtr, "MATERIAL_SHADING", 16)) { + if (TokenMatch(mFilePtr, "Blinn", 5)) { mat.mShading = Discreet3DS::Blinn; - } else if (TokenMatch(filePtr, "Phong", 5)) { + } else if (TokenMatch(mFilePtr, "Phong", 5)) { mat.mShading = Discreet3DS::Phong; - } else if (TokenMatch(filePtr, "Flat", 4)) { + } else if (TokenMatch(mFilePtr, "Flat", 4)) { mat.mShading = Discreet3DS::Flat; - } else if (TokenMatch(filePtr, "Wire", 4)) { + } else if (TokenMatch(mFilePtr, "Wire", 4)) { mat.mShading = Discreet3DS::Wire; } else { // assume gouraud shading @@ -578,13 +579,13 @@ void Parser::ParseLV2MaterialBlock(ASE::Material &mat) { continue; } // material transparency - if (TokenMatch(filePtr, "MATERIAL_TRANSPARENCY", 21)) { + if (TokenMatch(mFilePtr, "MATERIAL_TRANSPARENCY", 21)) { ParseLV4MeshFloat(mat.mTransparency); mat.mTransparency = ai_real(1.0) - mat.mTransparency; continue; } // material self illumination - if (TokenMatch(filePtr, "MATERIAL_SELFILLUM", 18)) { + if (TokenMatch(mFilePtr, "MATERIAL_SELFILLUM", 18)) { ai_real f = 0.0; ParseLV4MeshFloat(f); @@ -594,71 +595,71 @@ void Parser::ParseLV2MaterialBlock(ASE::Material &mat) { continue; } // material shininess - if (TokenMatch(filePtr, "MATERIAL_SHINE", 14)) { + if (TokenMatch(mFilePtr, "MATERIAL_SHINE", 14)) { ParseLV4MeshFloat(mat.mSpecularExponent); mat.mSpecularExponent *= 15; continue; } // two-sided material - if (TokenMatch(filePtr, "MATERIAL_TWOSIDED", 17)) { + if (TokenMatch(mFilePtr, "MATERIAL_TWOSIDED", 17)) { mat.mTwoSided = true; continue; } // material shininess strength - if (TokenMatch(filePtr, "MATERIAL_SHINESTRENGTH", 22)) { + if (TokenMatch(mFilePtr, "MATERIAL_SHINESTRENGTH", 22)) { ParseLV4MeshFloat(mat.mShininessStrength); continue; } // diffuse color map - if (TokenMatch(filePtr, "MAP_DIFFUSE", 11)) { + if (TokenMatch(mFilePtr, "MAP_DIFFUSE", 11)) { // parse the texture block ParseLV3MapBlock(mat.sTexDiffuse); continue; } // ambient color map - if (TokenMatch(filePtr, "MAP_AMBIENT", 11)) { + if (TokenMatch(mFilePtr, "MAP_AMBIENT", 11)) { // parse the texture block ParseLV3MapBlock(mat.sTexAmbient); continue; } // specular color map - if (TokenMatch(filePtr, "MAP_SPECULAR", 12)) { + if (TokenMatch(mFilePtr, "MAP_SPECULAR", 12)) { // parse the texture block ParseLV3MapBlock(mat.sTexSpecular); continue; } // opacity map - if (TokenMatch(filePtr, "MAP_OPACITY", 11)) { + if (TokenMatch(mFilePtr, "MAP_OPACITY", 11)) { // parse the texture block ParseLV3MapBlock(mat.sTexOpacity); continue; } // emissive map - if (TokenMatch(filePtr, "MAP_SELFILLUM", 13)) { + if (TokenMatch(mFilePtr, "MAP_SELFILLUM", 13)) { // parse the texture block ParseLV3MapBlock(mat.sTexEmissive); continue; } // bump map - if (TokenMatch(filePtr, "MAP_BUMP", 8)) { + if (TokenMatch(mFilePtr, "MAP_BUMP", 8)) { // parse the texture block ParseLV3MapBlock(mat.sTexBump); } // specular/shininess map - if (TokenMatch(filePtr, "MAP_SHINESTRENGTH", 17)) { + if (TokenMatch(mFilePtr, "MAP_SHINESTRENGTH", 17)) { // parse the texture block ParseLV3MapBlock(mat.sTexShininess); continue; } // number of submaterials - if (TokenMatch(filePtr, "NUMSUBMTLS", 10)) { + if (TokenMatch(mFilePtr, "NUMSUBMTLS", 10)) { ParseLV4MeshLong(iNumSubMaterials); // allocate enough storage mat.avSubMaterials.resize(iNumSubMaterials, Material("INVALID SUBMATERIAL")); } // submaterial chunks - if (TokenMatch(filePtr, "SUBMATERIAL", 11)) { + if (TokenMatch(mFilePtr, "SUBMATERIAL", 11)) { // ensure we have at least one material allocated if (iNumSubMaterials == 0) { LogWarning("*NUMSUBMTLS unspecified or 0"); @@ -701,10 +702,10 @@ void Parser::ParseLV3MapBlock(Texture &map) { bool parsePath = true; std::string temp; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // type of map - if (TokenMatch(filePtr, "MAP_CLASS", 9)) { + if (TokenMatch(mFilePtr, "MAP_CLASS", 9)) { temp.clear(); if (!ParseString(temp, "*MAP_CLASS")) SkipToNextToken(); @@ -715,7 +716,7 @@ void Parser::ParseLV3MapBlock(Texture &map) { continue; } // path to the texture - if (parsePath && TokenMatch(filePtr, "BITMAP", 6)) { + if (parsePath && TokenMatch(mFilePtr, "BITMAP", 6)) { if (!ParseString(map.mMapName, "*BITMAP")) SkipToNextToken(); @@ -729,32 +730,32 @@ void Parser::ParseLV3MapBlock(Texture &map) { continue; } // offset on the u axis - if (TokenMatch(filePtr, "UVW_U_OFFSET", 12)) { + if (TokenMatch(mFilePtr, "UVW_U_OFFSET", 12)) { ParseLV4MeshFloat(map.mOffsetU); continue; } // offset on the v axis - if (TokenMatch(filePtr, "UVW_V_OFFSET", 12)) { + if (TokenMatch(mFilePtr, "UVW_V_OFFSET", 12)) { ParseLV4MeshFloat(map.mOffsetV); continue; } // tiling on the u axis - if (TokenMatch(filePtr, "UVW_U_TILING", 12)) { + if (TokenMatch(mFilePtr, "UVW_U_TILING", 12)) { ParseLV4MeshFloat(map.mScaleU); continue; } // tiling on the v axis - if (TokenMatch(filePtr, "UVW_V_TILING", 12)) { + if (TokenMatch(mFilePtr, "UVW_V_TILING", 12)) { ParseLV4MeshFloat(map.mScaleV); continue; } // rotation around the z-axis - if (TokenMatch(filePtr, "UVW_ANGLE", 9)) { + if (TokenMatch(mFilePtr, "UVW_ANGLE", 9)) { ParseLV4MeshFloat(map.mRotation); continue; } // map blending factor - if (TokenMatch(filePtr, "MAP_AMOUNT", 10)) { + if (TokenMatch(mFilePtr, "MAP_AMOUNT", 10)) { ParseLV4MeshFloat(map.mTextureBlend); continue; } @@ -766,14 +767,14 @@ void Parser::ParseLV3MapBlock(Texture &map) { // ------------------------------------------------------------------------------------------------ bool Parser::ParseString(std::string &out, const char *szName) { char szBuffer[1024]; - if (!SkipSpaces(&filePtr, mEnd)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { ai_snprintf(szBuffer, 1024, "Unable to parse %s block: Unexpected EOL", szName); LogWarning(szBuffer); return false; } // there must be '"' - if ('\"' != *filePtr) { + if ('\"' != *mFilePtr) { ai_snprintf(szBuffer, 1024, "Unable to parse %s block: Strings are expected " "to be enclosed in double quotation marks", @@ -781,8 +782,8 @@ bool Parser::ParseString(std::string &out, const char *szName) { LogWarning(szBuffer); return false; } - ++filePtr; - const char *sz = filePtr; + ++mFilePtr; + const char *sz = mFilePtr; while (true) { if ('\"' == *sz) break; @@ -796,8 +797,8 @@ bool Parser::ParseString(std::string &out, const char *szName) { } sz++; } - out = std::string(filePtr, (uintptr_t)sz - (uintptr_t)filePtr); - filePtr = sz + 1; + out = std::string(mFilePtr, (uintptr_t)sz - (uintptr_t)mFilePtr); + mFilePtr = sz + 1; return true; } @@ -805,48 +806,48 @@ bool Parser::ParseString(std::string &out, const char *szName) { void Parser::ParseLV1ObjectBlock(ASE::BaseNode &node) { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // first process common tokens such as node name and transform // name of the mesh/node - if (TokenMatch(filePtr, "NODE_NAME", 9)) { + if (TokenMatch(mFilePtr, "NODE_NAME", 9)) { if (!ParseString(node.mName, "*NODE_NAME")) SkipToNextToken(); continue; } // name of the parent of the node - if (TokenMatch(filePtr, "NODE_PARENT", 11)) { + if (TokenMatch(mFilePtr, "NODE_PARENT", 11)) { if (!ParseString(node.mParent, "*NODE_PARENT")) SkipToNextToken(); continue; } // transformation matrix of the node - if (TokenMatch(filePtr, "NODE_TM", 7)) { + if (TokenMatch(mFilePtr, "NODE_TM", 7)) { ParseLV2NodeTransformBlock(node); continue; } // animation data of the node - if (TokenMatch(filePtr, "TM_ANIMATION", 12)) { + if (TokenMatch(mFilePtr, "TM_ANIMATION", 12)) { ParseLV2AnimationBlock(node); continue; } if (node.mType == BaseNode::Light) { // light settings - if (TokenMatch(filePtr, "LIGHT_SETTINGS", 14)) { + if (TokenMatch(mFilePtr, "LIGHT_SETTINGS", 14)) { ParseLV2LightSettingsBlock((ASE::Light &)node); continue; } // type of the light source - if (TokenMatch(filePtr, "LIGHT_TYPE", 10)) { - if (!ASSIMP_strincmp("omni", filePtr, 4)) { + if (TokenMatch(mFilePtr, "LIGHT_TYPE", 10)) { + if (!ASSIMP_strincmp("omni", mFilePtr, 4)) { ((ASE::Light &)node).mLightType = ASE::Light::OMNI; - } else if (!ASSIMP_strincmp("target", filePtr, 6)) { + } else if (!ASSIMP_strincmp("target", mFilePtr, 6)) { ((ASE::Light &)node).mLightType = ASE::Light::TARGET; - } else if (!ASSIMP_strincmp("free", filePtr, 4)) { + } else if (!ASSIMP_strincmp("free", mFilePtr, 4)) { ((ASE::Light &)node).mLightType = ASE::Light::FREE; - } else if (!ASSIMP_strincmp("directional", filePtr, 11)) { + } else if (!ASSIMP_strincmp("directional", mFilePtr, 11)) { ((ASE::Light &)node).mLightType = ASE::Light::DIRECTIONAL; } else { LogWarning("Unknown kind of light source"); @@ -855,13 +856,13 @@ void Parser::ParseLV1ObjectBlock(ASE::BaseNode &node) { } } else if (node.mType == BaseNode::Camera) { // Camera settings - if (TokenMatch(filePtr, "CAMERA_SETTINGS", 15)) { + if (TokenMatch(mFilePtr, "CAMERA_SETTINGS", 15)) { ParseLV2CameraSettingsBlock((ASE::Camera &)node); continue; - } else if (TokenMatch(filePtr, "CAMERA_TYPE", 11)) { - if (!ASSIMP_strincmp("target", filePtr, 6)) { + } else if (TokenMatch(mFilePtr, "CAMERA_TYPE", 11)) { + if (!ASSIMP_strincmp("target", mFilePtr, 6)) { ((ASE::Camera &)node).mCameraType = ASE::Camera::TARGET; - } else if (!ASSIMP_strincmp("free", filePtr, 4)) { + } else if (!ASSIMP_strincmp("free", mFilePtr, 4)) { ((ASE::Camera &)node).mCameraType = ASE::Camera::FREE; } else { LogWarning("Unknown kind of camera"); @@ -871,13 +872,13 @@ void Parser::ParseLV1ObjectBlock(ASE::BaseNode &node) { } else if (node.mType == BaseNode::Mesh) { // mesh data // FIX: Older files use MESH_SOFTSKIN - if (TokenMatch(filePtr, "MESH", 4) || - TokenMatch(filePtr, "MESH_SOFTSKIN", 13)) { + if (TokenMatch(mFilePtr, "MESH", 4) || + TokenMatch(mFilePtr, "MESH_SOFTSKIN", 13)) { ParseLV2MeshBlock((ASE::Mesh &)node); continue; } // mesh material index - if (TokenMatch(filePtr, "MATERIAL_REF", 12)) { + if (TokenMatch(mFilePtr, "MATERIAL_REF", 12)) { ParseLV4MeshLong(((ASE::Mesh &)node).iMaterialIndex); continue; } @@ -891,17 +892,17 @@ void Parser::ParseLV1ObjectBlock(ASE::BaseNode &node) { void Parser::ParseLV2CameraSettingsBlock(ASE::Camera &camera) { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (TokenMatch(filePtr, "CAMERA_NEAR", 11)) { + if ('*' == *mFilePtr) { + ++mFilePtr; + if (TokenMatch(mFilePtr, "CAMERA_NEAR", 11)) { ParseLV4MeshFloat(camera.mNear); continue; } - if (TokenMatch(filePtr, "CAMERA_FAR", 10)) { + if (TokenMatch(mFilePtr, "CAMERA_FAR", 10)) { ParseLV4MeshFloat(camera.mFar); continue; } - if (TokenMatch(filePtr, "CAMERA_FOV", 10)) { + if (TokenMatch(mFilePtr, "CAMERA_FOV", 10)) { ParseLV4MeshFloat(camera.mFOV); continue; } @@ -914,21 +915,21 @@ void Parser::ParseLV2CameraSettingsBlock(ASE::Camera &camera) { void Parser::ParseLV2LightSettingsBlock(ASE::Light &light) { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (TokenMatch(filePtr, "LIGHT_COLOR", 11)) { + if ('*' == *mFilePtr) { + ++mFilePtr; + if (TokenMatch(mFilePtr, "LIGHT_COLOR", 11)) { ParseLV4MeshFloatTriple(&light.mColor.r); continue; } - if (TokenMatch(filePtr, "LIGHT_INTENS", 12)) { + if (TokenMatch(mFilePtr, "LIGHT_INTENS", 12)) { ParseLV4MeshFloat(light.mIntensity); continue; } - if (TokenMatch(filePtr, "LIGHT_HOTSPOT", 13)) { + if (TokenMatch(mFilePtr, "LIGHT_HOTSPOT", 13)) { ParseLV4MeshFloat(light.mAngle); continue; } - if (TokenMatch(filePtr, "LIGHT_FALLOFF", 13)) { + if (TokenMatch(mFilePtr, "LIGHT_FALLOFF", 13)) { ParseLV4MeshFloat(light.mFalloff); continue; } @@ -943,9 +944,9 @@ void Parser::ParseLV2AnimationBlock(ASE::BaseNode &mesh) { ASE::Animation *anim = &mesh.mAnim; while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (TokenMatch(filePtr, "NODE_NAME", 9)) { + if ('*' == *mFilePtr) { + ++mFilePtr; + if (TokenMatch(mFilePtr, "NODE_NAME", 9)) { std::string temp; if (!ParseString(temp, "*NODE_NAME")) SkipToNextToken(); @@ -967,9 +968,9 @@ void Parser::ParseLV2AnimationBlock(ASE::BaseNode &mesh) { } // position keyframes - if (TokenMatch(filePtr, "CONTROL_POS_TRACK", 17) || - TokenMatch(filePtr, "CONTROL_POS_BEZIER", 18) || - TokenMatch(filePtr, "CONTROL_POS_TCB", 15)) { + if (TokenMatch(mFilePtr, "CONTROL_POS_TRACK", 17) || + TokenMatch(mFilePtr, "CONTROL_POS_BEZIER", 18) || + TokenMatch(mFilePtr, "CONTROL_POS_TCB", 15)) { if (!anim) SkipSection(); else @@ -977,9 +978,9 @@ void Parser::ParseLV2AnimationBlock(ASE::BaseNode &mesh) { continue; } // scaling keyframes - if (TokenMatch(filePtr, "CONTROL_SCALE_TRACK", 19) || - TokenMatch(filePtr, "CONTROL_SCALE_BEZIER", 20) || - TokenMatch(filePtr, "CONTROL_SCALE_TCB", 17)) { + if (TokenMatch(mFilePtr, "CONTROL_SCALE_TRACK", 19) || + TokenMatch(mFilePtr, "CONTROL_SCALE_BEZIER", 20) || + TokenMatch(mFilePtr, "CONTROL_SCALE_TCB", 17)) { if (!anim || anim == &mesh.mTargetAnim) { // Target animation channels may have no rotation channels ASSIMP_LOG_ERROR("ASE: Ignoring scaling channel in target animation"); @@ -989,9 +990,9 @@ void Parser::ParseLV2AnimationBlock(ASE::BaseNode &mesh) { continue; } // rotation keyframes - if (TokenMatch(filePtr, "CONTROL_ROT_TRACK", 17) || - TokenMatch(filePtr, "CONTROL_ROT_BEZIER", 18) || - TokenMatch(filePtr, "CONTROL_ROT_TCB", 15)) { + if (TokenMatch(mFilePtr, "CONTROL_ROT_TRACK", 17) || + TokenMatch(mFilePtr, "CONTROL_ROT_BEZIER", 18) || + TokenMatch(mFilePtr, "CONTROL_ROT_TCB", 15)) { if (!anim || anim == &mesh.mTargetAnim) { // Target animation channels may have no rotation channels ASSIMP_LOG_ERROR("ASE: Ignoring rotation channel in target animation"); @@ -1010,8 +1011,8 @@ void Parser::ParseLV3ScaleAnimationBlock(ASE::Animation &anim) { unsigned int iIndex; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; bool b = false; @@ -1019,18 +1020,18 @@ void Parser::ParseLV3ScaleAnimationBlock(ASE::Animation &anim) { // we ignore the additional information for bezier's and TCBs // simple scaling keyframe - if (TokenMatch(filePtr, "CONTROL_SCALE_SAMPLE", 20)) { + if (TokenMatch(mFilePtr, "CONTROL_SCALE_SAMPLE", 20)) { b = true; anim.mScalingType = ASE::Animation::TRACK; } // Bezier scaling keyframe - if (TokenMatch(filePtr, "CONTROL_BEZIER_SCALE_KEY", 24)) { + if (TokenMatch(mFilePtr, "CONTROL_BEZIER_SCALE_KEY", 24)) { b = true; anim.mScalingType = ASE::Animation::BEZIER; } // TCB scaling keyframe - if (TokenMatch(filePtr, "CONTROL_TCB_SCALE_KEY", 21)) { + if (TokenMatch(mFilePtr, "CONTROL_TCB_SCALE_KEY", 21)) { b = true; anim.mScalingType = ASE::Animation::TCB; } @@ -1049,8 +1050,8 @@ void Parser::ParseLV3PosAnimationBlock(ASE::Animation &anim) { AI_ASE_PARSER_INIT(); unsigned int iIndex; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; bool b = false; @@ -1058,18 +1059,18 @@ void Parser::ParseLV3PosAnimationBlock(ASE::Animation &anim) { // we ignore the additional information for bezier's and TCBs // simple scaling keyframe - if (TokenMatch(filePtr, "CONTROL_POS_SAMPLE", 18)) { + if (TokenMatch(mFilePtr, "CONTROL_POS_SAMPLE", 18)) { b = true; anim.mPositionType = ASE::Animation::TRACK; } // Bezier scaling keyframe - if (TokenMatch(filePtr, "CONTROL_BEZIER_POS_KEY", 22)) { + if (TokenMatch(mFilePtr, "CONTROL_BEZIER_POS_KEY", 22)) { b = true; anim.mPositionType = ASE::Animation::BEZIER; } // TCB scaling keyframe - if (TokenMatch(filePtr, "CONTROL_TCB_POS_KEY", 19)) { + if (TokenMatch(mFilePtr, "CONTROL_TCB_POS_KEY", 19)) { b = true; anim.mPositionType = ASE::Animation::TCB; } @@ -1088,8 +1089,8 @@ void Parser::ParseLV3RotAnimationBlock(ASE::Animation &anim) { AI_ASE_PARSER_INIT(); unsigned int iIndex; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; bool b = false; @@ -1097,18 +1098,18 @@ void Parser::ParseLV3RotAnimationBlock(ASE::Animation &anim) { // we ignore the additional information for bezier's and TCBs // simple scaling keyframe - if (TokenMatch(filePtr, "CONTROL_ROT_SAMPLE", 18)) { + if (TokenMatch(mFilePtr, "CONTROL_ROT_SAMPLE", 18)) { b = true; anim.mRotationType = ASE::Animation::TRACK; } // Bezier scaling keyframe - if (TokenMatch(filePtr, "CONTROL_BEZIER_ROT_KEY", 22)) { + if (TokenMatch(mFilePtr, "CONTROL_BEZIER_ROT_KEY", 22)) { b = true; anim.mRotationType = ASE::Animation::BEZIER; } // TCB scaling keyframe - if (TokenMatch(filePtr, "CONTROL_TCB_ROT_KEY", 19)) { + if (TokenMatch(mFilePtr, "CONTROL_TCB_ROT_KEY", 19)) { b = true; anim.mRotationType = ASE::Animation::TCB; } @@ -1131,10 +1132,10 @@ void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode &mesh) { AI_ASE_PARSER_INIT(); int mode = 0; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // name of the node - if (TokenMatch(filePtr, "NODE_NAME", 9)) { + if (TokenMatch(mFilePtr, "NODE_NAME", 9)) { std::string temp; if (!ParseString(temp, "*NODE_NAME")) SkipToNextToken(); @@ -1161,28 +1162,28 @@ void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode &mesh) { if (mode) { // fourth row of the transformation matrix - and also the // only information here that is interesting for targets - if (TokenMatch(filePtr, "TM_ROW3", 7)) { + if (TokenMatch(mFilePtr, "TM_ROW3", 7)) { ParseLV4MeshFloatTriple((mode == 1 ? mesh.mTransform[3] : &mesh.mTargetPosition.x)); continue; } if (mode == 1) { // first row of the transformation matrix - if (TokenMatch(filePtr, "TM_ROW0", 7)) { + if (TokenMatch(mFilePtr, "TM_ROW0", 7)) { ParseLV4MeshFloatTriple(mesh.mTransform[0]); continue; } // second row of the transformation matrix - if (TokenMatch(filePtr, "TM_ROW1", 7)) { + if (TokenMatch(mFilePtr, "TM_ROW1", 7)) { ParseLV4MeshFloatTriple(mesh.mTransform[1]); continue; } // third row of the transformation matrix - if (TokenMatch(filePtr, "TM_ROW2", 7)) { + if (TokenMatch(mFilePtr, "TM_ROW2", 7)) { ParseLV4MeshFloatTriple(mesh.mTransform[2]); continue; } // inherited position axes - if (TokenMatch(filePtr, "INHERIT_POS", 11)) { + if (TokenMatch(mFilePtr, "INHERIT_POS", 11)) { unsigned int aiVal[3]; ParseLV4MeshLongTriple(aiVal); @@ -1191,7 +1192,7 @@ void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode &mesh) { continue; } // inherited rotation axes - if (TokenMatch(filePtr, "INHERIT_ROT", 11)) { + if (TokenMatch(mFilePtr, "INHERIT_ROT", 11)) { unsigned int aiVal[3]; ParseLV4MeshLongTriple(aiVal); @@ -1200,7 +1201,7 @@ void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode &mesh) { continue; } // inherited scaling axes - if (TokenMatch(filePtr, "INHERIT_SCL", 11)) { + if (TokenMatch(mFilePtr, "INHERIT_SCL", 11)) { unsigned int aiVal[3]; ParseLV4MeshLongTriple(aiVal); @@ -1225,75 +1226,75 @@ void Parser::ParseLV2MeshBlock(ASE::Mesh &mesh) { unsigned int iNumCVertices = 0; unsigned int iNumCFaces = 0; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Number of vertices in the mesh - if (TokenMatch(filePtr, "MESH_NUMVERTEX", 14)) { + if (TokenMatch(mFilePtr, "MESH_NUMVERTEX", 14)) { ParseLV4MeshLong(iNumVertices); continue; } // Number of texture coordinates in the mesh - if (TokenMatch(filePtr, "MESH_NUMTVERTEX", 15)) { + if (TokenMatch(mFilePtr, "MESH_NUMTVERTEX", 15)) { ParseLV4MeshLong(iNumTVertices); continue; } // Number of vertex colors in the mesh - if (TokenMatch(filePtr, "MESH_NUMCVERTEX", 15)) { + if (TokenMatch(mFilePtr, "MESH_NUMCVERTEX", 15)) { ParseLV4MeshLong(iNumCVertices); continue; } // Number of regular faces in the mesh - if (TokenMatch(filePtr, "MESH_NUMFACES", 13)) { + if (TokenMatch(mFilePtr, "MESH_NUMFACES", 13)) { ParseLV4MeshLong(iNumFaces); continue; } // Number of UVWed faces in the mesh - if (TokenMatch(filePtr, "MESH_NUMTVFACES", 15)) { + if (TokenMatch(mFilePtr, "MESH_NUMTVFACES", 15)) { ParseLV4MeshLong(iNumTFaces); continue; } // Number of colored faces in the mesh - if (TokenMatch(filePtr, "MESH_NUMCVFACES", 15)) { + if (TokenMatch(mFilePtr, "MESH_NUMCVFACES", 15)) { ParseLV4MeshLong(iNumCFaces); continue; } // mesh vertex list block - if (TokenMatch(filePtr, "MESH_VERTEX_LIST", 16)) { + if (TokenMatch(mFilePtr, "MESH_VERTEX_LIST", 16)) { ParseLV3MeshVertexListBlock(iNumVertices, mesh); continue; } // mesh face list block - if (TokenMatch(filePtr, "MESH_FACE_LIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_FACE_LIST", 14)) { ParseLV3MeshFaceListBlock(iNumFaces, mesh); continue; } // mesh texture vertex list block - if (TokenMatch(filePtr, "MESH_TVERTLIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_TVERTLIST", 14)) { ParseLV3MeshTListBlock(iNumTVertices, mesh); continue; } // mesh texture face block - if (TokenMatch(filePtr, "MESH_TFACELIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_TFACELIST", 14)) { ParseLV3MeshTFaceListBlock(iNumTFaces, mesh); continue; } // mesh color vertex list block - if (TokenMatch(filePtr, "MESH_CVERTLIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_CVERTLIST", 14)) { ParseLV3MeshCListBlock(iNumCVertices, mesh); continue; } // mesh color face block - if (TokenMatch(filePtr, "MESH_CFACELIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_CFACELIST", 14)) { ParseLV3MeshCFaceListBlock(iNumCFaces, mesh); continue; } // mesh normals - if (TokenMatch(filePtr, "MESH_NORMALS", 12)) { + if (TokenMatch(mFilePtr, "MESH_NORMALS", 12)) { ParseLV3MeshNormalListBlock(mesh); continue; } // another mesh UV channel ... - if (TokenMatch(filePtr, "MESH_MAPPINGCHANNEL", 19)) { + if (TokenMatch(mFilePtr, "MESH_MAPPINGCHANNEL", 19)) { unsigned int iIndex(0); ParseLV4MeshLong(iIndex); if (0 == iIndex) { @@ -1318,7 +1319,7 @@ void Parser::ParseLV2MeshBlock(ASE::Mesh &mesh) { } } // mesh animation keyframe. Not supported - if (TokenMatch(filePtr, "MESH_ANIMATION", 14)) { + if (TokenMatch(mFilePtr, "MESH_ANIMATION", 14)) { LogWarning("Found *MESH_ANIMATION element in ASE/ASK file. " "Keyframe animation is not supported by Assimp, this element " @@ -1326,7 +1327,7 @@ void Parser::ParseLV2MeshBlock(ASE::Mesh &mesh) { //SkipSection(); continue; } - if (TokenMatch(filePtr, "MESH_WEIGHTS", 12)) { + if (TokenMatch(mFilePtr, "MESH_WEIGHTS", 12)) { ParseLV3MeshWeightsBlock(mesh); continue; } @@ -1340,26 +1341,26 @@ void Parser::ParseLV3MeshWeightsBlock(ASE::Mesh &mesh) { unsigned int iNumVertices = 0, iNumBones = 0; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Number of bone vertices ... - if (TokenMatch(filePtr, "MESH_NUMVERTEX", 14)) { + if (TokenMatch(mFilePtr, "MESH_NUMVERTEX", 14)) { ParseLV4MeshLong(iNumVertices); continue; } // Number of bones - if (TokenMatch(filePtr, "MESH_NUMBONE", 12)) { + if (TokenMatch(mFilePtr, "MESH_NUMBONE", 12)) { ParseLV4MeshLong(iNumBones); continue; } // parse the list of bones - if (TokenMatch(filePtr, "MESH_BONE_LIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_BONE_LIST", 14)) { ParseLV4MeshBones(iNumBones, mesh); continue; } // parse the list of bones vertices - if (TokenMatch(filePtr, "MESH_BONE_VERTEX_LIST", 21)) { + if (TokenMatch(mFilePtr, "MESH_BONE_VERTEX_LIST", 21)) { ParseLV4MeshBonesVertices(iNumVertices, mesh); continue; } @@ -1372,14 +1373,14 @@ void Parser::ParseLV4MeshBones(unsigned int iNumBones, ASE::Mesh &mesh) { AI_ASE_PARSER_INIT(); mesh.mBones.resize(iNumBones, Bone("UNNAMED")); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Mesh bone with name ... - if (TokenMatch(filePtr, "MESH_BONE_NAME", 14)) { + if (TokenMatch(mFilePtr, "MESH_BONE_NAME", 14)) { // parse an index ... - if (SkipSpaces(&filePtr, mEnd)) { - unsigned int iIndex = strtoul10(filePtr, &filePtr); + if (SkipSpaces(&mFilePtr, mEnd)) { + unsigned int iIndex = strtoul10(mFilePtr, &mFilePtr); if (iIndex >= iNumBones) { LogWarning("Bone index is out of bounds"); continue; @@ -1398,13 +1399,13 @@ void Parser::ParseLV4MeshBonesVertices(unsigned int iNumVertices, ASE::Mesh &mes AI_ASE_PARSER_INIT(); mesh.mBoneVertices.resize(iNumVertices); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Mesh bone vertex - if (TokenMatch(filePtr, "MESH_BONE_VERTEX", 16)) { + if (TokenMatch(mFilePtr, "MESH_BONE_VERTEX", 16)) { // read the vertex index - unsigned int iIndex = strtoul10(filePtr, &filePtr); + unsigned int iIndex = strtoul10(mFilePtr, &mFilePtr); if (iIndex >= mesh.mPositions.size()) { iIndex = (unsigned int)mesh.mPositions.size() - 1; LogWarning("Bone vertex index is out of bounds. Using the largest valid " @@ -1418,12 +1419,12 @@ void Parser::ParseLV4MeshBonesVertices(unsigned int iNumVertices, ASE::Mesh &mes std::pair pairOut; while (true) { // first parse the bone index ... - if (!SkipSpaces(&filePtr, mEnd)) break; - pairOut.first = strtoul10(filePtr, &filePtr); + if (!SkipSpaces(&mFilePtr, mEnd)) break; + pairOut.first = strtoul10(mFilePtr, &mFilePtr); // then parse the vertex weight - if (!SkipSpaces(&filePtr, mEnd)) break; - filePtr = fast_atoreal_move(filePtr, pairOut.second); + if (!SkipSpaces(&mFilePtr, mEnd)) break; + mFilePtr = fast_atoreal_move(mFilePtr, pairOut.second); // -1 marks unused entries if (-1 != pairOut.first) { @@ -1444,11 +1445,11 @@ void Parser::ParseLV3MeshVertexListBlock( // allocate enough storage in the array mesh.mPositions.resize(iNumVertices); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Vertex entry - if (TokenMatch(filePtr, "MESH_VERTEX", 11)) { + if (TokenMatch(mFilePtr, "MESH_VERTEX", 11)) { aiVector3D vTemp; unsigned int iIndex; @@ -1471,11 +1472,11 @@ void Parser::ParseLV3MeshFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh) // allocate enough storage in the face array mesh.mFaces.resize(iNumFaces); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Face entry - if (TokenMatch(filePtr, "MESH_FACE", 9)) { + if (TokenMatch(mFilePtr, "MESH_FACE", 9)) { ASE::Face mFace; ParseLV4MeshFace(mFace); @@ -1498,11 +1499,11 @@ void Parser::ParseLV3MeshTListBlock(unsigned int iNumVertices, // allocate enough storage in the array mesh.amTexCoords[iChannel].resize(iNumVertices); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Vertex entry - if (TokenMatch(filePtr, "MESH_TVERT", 10)) { + if (TokenMatch(mFilePtr, "MESH_TVERT", 10)) { aiVector3D vTemp; unsigned int iIndex; ParseLV4MeshFloatTriple(&vTemp.x, iIndex); @@ -1527,11 +1528,11 @@ void Parser::ParseLV3MeshTFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh, unsigned int iChannel) { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Face entry - if (TokenMatch(filePtr, "MESH_TFACE", 10)) { + if (TokenMatch(mFilePtr, "MESH_TFACE", 10)) { unsigned int aiValues[3]; unsigned int iIndex = 0; @@ -1557,26 +1558,26 @@ void Parser::ParseLV3MappingChannel(unsigned int iChannel, ASE::Mesh &mesh) { unsigned int iNumTVertices = 0; unsigned int iNumTFaces = 0; while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Number of texture coordinates in the mesh - if (TokenMatch(filePtr, "MESH_NUMTVERTEX", 15)) { + if (TokenMatch(mFilePtr, "MESH_NUMTVERTEX", 15)) { ParseLV4MeshLong(iNumTVertices); continue; } // Number of UVWed faces in the mesh - if (TokenMatch(filePtr, "MESH_NUMTVFACES", 15)) { + if (TokenMatch(mFilePtr, "MESH_NUMTVFACES", 15)) { ParseLV4MeshLong(iNumTFaces); continue; } // mesh texture vertex list block - if (TokenMatch(filePtr, "MESH_TVERTLIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_TVERTLIST", 14)) { ParseLV3MeshTListBlock(iNumTVertices, mesh, iChannel); continue; } // mesh texture face block - if (TokenMatch(filePtr, "MESH_TFACELIST", 14)) { + if (TokenMatch(mFilePtr, "MESH_TFACELIST", 14)) { ParseLV3MeshTFaceListBlock(iNumTFaces, mesh, iChannel); continue; } @@ -1591,11 +1592,11 @@ void Parser::ParseLV3MeshCListBlock(unsigned int iNumVertices, ASE::Mesh &mesh) // allocate enough storage in the array mesh.mVertexColors.resize(iNumVertices); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Vertex entry - if (TokenMatch(filePtr, "MESH_VERTCOL", 12)) { + if (TokenMatch(mFilePtr, "MESH_VERTCOL", 12)) { aiColor4D vTemp; vTemp.a = 1.0f; unsigned int iIndex; @@ -1615,11 +1616,11 @@ void Parser::ParseLV3MeshCListBlock(unsigned int iNumVertices, ASE::Mesh &mesh) void Parser::ParseLV3MeshCFaceListBlock(unsigned int iNumFaces, ASE::Mesh &mesh) { AI_ASE_PARSER_INIT(); while (true) { - if ('*' == *filePtr) { - ++filePtr; + if ('*' == *mFilePtr) { + ++mFilePtr; // Face entry - if (TokenMatch(filePtr, "MESH_CFACE", 10)) { + if (TokenMatch(mFilePtr, "MESH_CFACE", 10)) { unsigned int aiValues[3]; unsigned int iIndex = 0; @@ -1652,9 +1653,9 @@ void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) { // Smooth the vertex and face normals together. The result // will be edgy then, but otherwise everything would be soft ... while (true) { - if ('*' == *filePtr) { - ++filePtr; - if (faceIdx != UINT_MAX && TokenMatch(filePtr, "MESH_VERTEXNORMAL", 17)) { + if ('*' == *mFilePtr) { + ++mFilePtr; + if (faceIdx != UINT_MAX && TokenMatch(mFilePtr, "MESH_VERTEXNORMAL", 17)) { aiVector3D vNormal; ParseLV4MeshFloatTriple(&vNormal.x, index); if (faceIdx >= sMesh.mFaces.size()) @@ -1676,7 +1677,7 @@ void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) { sMesh.mNormals[faceIdx * 3 + index] += vNormal; continue; } - if (TokenMatch(filePtr, "MESH_FACENORMAL", 15)) { + if (TokenMatch(mFilePtr, "MESH_FACENORMAL", 15)) { aiVector3D vNormal; ParseLV4MeshFloatTriple(&vNormal.x, faceIdx); @@ -1698,34 +1699,34 @@ void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) { // ------------------------------------------------------------------------------------------------ void Parser::ParseLV4MeshFace(ASE::Face &out) { // skip spaces and tabs - if (!SkipSpaces(&filePtr, mEnd)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL [#1]"); SkipToNextToken(); return; } // parse the face index - out.iFace = strtoul10(filePtr, &filePtr); + out.iFace = strtoul10(mFilePtr, &mFilePtr); // next character should be ':' - if (!SkipSpaces(&filePtr, mEnd)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { // FIX: there are some ASE files which haven't got : here .... LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL. \':\' expected [#2]"); SkipToNextToken(); return; } // FIX: There are some ASE files which haven't got ':' here - if (':' == *filePtr) ++filePtr; + if (':' == *mFilePtr) ++mFilePtr; // Parse all mesh indices for (unsigned int i = 0; i < 3; ++i) { unsigned int iIndex = 0; - if (!SkipSpaces(&filePtr, mEnd)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL"); SkipToNextToken(); return; } - switch (*filePtr) { + switch (*mFilePtr) { case 'A': case 'a': break; @@ -1743,39 +1744,39 @@ void Parser::ParseLV4MeshFace(ASE::Face &out) { SkipToNextToken(); return; }; - ++filePtr; + ++mFilePtr; // next character should be ':' - if (!SkipSpaces(&filePtr, mEnd) || ':' != *filePtr) { + if (!SkipSpaces(&mFilePtr, mEnd) || ':' != *mFilePtr) { LogWarning("Unable to parse *MESH_FACE Element: " "Unexpected EOL. \':\' expected [#2]"); SkipToNextToken(); return; } - ++filePtr; - if (!SkipSpaces(&filePtr, mEnd)) { + ++mFilePtr; + if (!SkipSpaces(&mFilePtr, mEnd)) { LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL. " "Vertex index expected [#4]"); SkipToNextToken(); return; } - out.mIndices[iIndex] = strtoul10(filePtr, &filePtr); + out.mIndices[iIndex] = strtoul10(mFilePtr, &mFilePtr); } // now we need to skip the AB, BC, CA blocks. while (true) { - if ('*' == *filePtr) break; - if (IsLineEnd(*filePtr)) { + if ('*' == *mFilePtr) break; + if (IsLineEnd(*mFilePtr)) { //iLineNumber++; return; } - filePtr++; + mFilePtr++; } // parse the smoothing group of the face - if (TokenMatch(filePtr, "*MESH_SMOOTHING", 15)) { - if (!SkipSpaces(&filePtr, mEnd)) { + if (TokenMatch(mFilePtr, "*MESH_SMOOTHING", 15)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { LogWarning("Unable to parse *MESH_SMOOTHING Element: " "Unexpected EOL. Smoothing group(s) expected [#5]"); SkipToNextToken(); @@ -1785,43 +1786,43 @@ void Parser::ParseLV4MeshFace(ASE::Face &out) { // Parse smoothing groups until we don't anymore see commas // FIX: There needn't always be a value, sad but true while (true) { - if (*filePtr < '9' && *filePtr >= '0') { - uint32_t value = strtoul10(filePtr, &filePtr); + if (*mFilePtr < '9' && *mFilePtr >= '0') { + uint32_t value = strtoul10(mFilePtr, &mFilePtr); if (value < 32) { - out.iSmoothGroup |= (1 << strtoul10(filePtr, &filePtr)); + out.iSmoothGroup |= (1 << strtoul10(mFilePtr, &mFilePtr)); } else { const std::string message = std::string("Unable to set smooth group, value with ") + ai_to_string(value) + std::string(" out of range"); LogWarning(message.c_str()); } } - SkipSpaces(&filePtr, mEnd); - if (',' != *filePtr) { + SkipSpaces(&mFilePtr, mEnd); + if (',' != *mFilePtr) { break; } - ++filePtr; - SkipSpaces(&filePtr, mEnd); + ++mFilePtr; + SkipSpaces(&mFilePtr, mEnd); } } // *MESH_MTLID is optional, too while (true) { - if ('*' == *filePtr) { + if ('*' == *mFilePtr) { break; } - if (IsLineEnd(*filePtr)) { + if (IsLineEnd(*mFilePtr)) { return; } - filePtr++; + mFilePtr++; } - if (TokenMatch(filePtr, "*MESH_MTLID", 11)) { - if (!SkipSpaces(&filePtr, mEnd)) { + if (TokenMatch(mFilePtr, "*MESH_MTLID", 11)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { LogWarning("Unable to parse *MESH_MTLID Element: Unexpected EOL. " "Material index expected [#6]"); SkipToNextToken(); return; } - out.iMaterial = strtoul10(filePtr, &filePtr); + out.iMaterial = strtoul10(mFilePtr, &mFilePtr); } return; } @@ -1863,7 +1864,7 @@ void Parser::ParseLV4MeshFloatTriple(ai_real *apOut) { // ------------------------------------------------------------------------------------------------ void Parser::ParseLV4MeshFloat(ai_real &fOut) { // skip spaces and tabs - if (!SkipSpaces(&filePtr, mEnd)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { // LOG LogWarning("Unable to parse float: unexpected EOL [#1]"); fOut = 0.0; @@ -1871,12 +1872,12 @@ void Parser::ParseLV4MeshFloat(ai_real &fOut) { return; } // parse the first float - filePtr = fast_atoreal_move(filePtr, fOut); + mFilePtr = fast_atoreal_move(mFilePtr, fOut); } // ------------------------------------------------------------------------------------------------ void Parser::ParseLV4MeshLong(unsigned int &iOut) { // Skip spaces and tabs - if (!SkipSpaces(&filePtr, mEnd)) { + if (!SkipSpaces(&mFilePtr, mEnd)) { // LOG LogWarning("Unable to parse long: unexpected EOL [#1]"); iOut = 0; @@ -1884,7 +1885,7 @@ void Parser::ParseLV4MeshLong(unsigned int &iOut) { return; } // parse the value - iOut = strtoul10(filePtr, &filePtr); + iOut = strtoul10(mFilePtr, &mFilePtr); } } diff --git a/code/AssetLib/ASE/ASEParser.h b/code/AssetLib/ASE/ASEParser.h index 263b5ca73..9c5cd39b1 100644 --- a/code/AssetLib/ASE/ASEParser.h +++ b/code/AssetLib/ASE/ASEParser.h @@ -391,11 +391,11 @@ public: // ------------------------------------------------------------------- //! Construct a parser from a given input file which is //! guaranteed to be terminated with zero. - //! @param szFile Input file + //! @param file The name of the input file. //! @param fileFormatDefault Assumed file format version. If the //! file format is specified in the file the new value replaces //! the default value. - Parser(const char *szFile, unsigned int fileFormatDefault); + Parser(const char *file, size_t fileLen, unsigned int fileFormatDefault); // ------------------------------------------------------------------- //! Parses the file into the parsers internal representation @@ -617,11 +617,8 @@ private: bool ParseString(std::string &out, const char *szName); public: - //! Pointer to current data - const char *filePtr; - - /// The end pointer of the file data - const char *mEnd; + const char *mFilePtr; ////< Pointer to current data + const char *mEnd; ///< The end pointer of the file data //! background color to be passed to the viewer //! QNAN if none was found diff --git a/test/unit/utB3DImportExport.cpp b/test/unit/utB3DImportExport.cpp index 76017f66c..e84313ad8 100644 --- a/test/unit/utB3DImportExport.cpp +++ b/test/unit/utB3DImportExport.cpp @@ -56,6 +56,6 @@ public: } }; -TEST_F(utB3DImportExport, importACFromFileTest) { +TEST_F(utB3DImportExport, importB3DFromFileTest) { EXPECT_TRUE(importerTest()); } From 193f2c5d87ee4e62f64942be33e72ccd59168632 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Mon, 8 Jul 2024 17:09:17 +0200 Subject: [PATCH 30/39] Fix compile warning (#5657) --- code/AssetLib/glTF2/glTF2Asset.inl | 2 +- code/AssetLib/glTF2/glTF2AssetWriter.inl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index 457fee8fa..3d309049e 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -1440,7 +1440,7 @@ inline void MaterialSheen::SetDefaults() { inline void MaterialVolume::SetDefaults() { //KHR_materials_volume properties thicknessFactor = 0.f; - attenuationDistance = INFINITY; + attenuationDistance = std::numeric_limits::infinity(); SetVector(attenuationColor, defaultAttenuationColor); } diff --git a/code/AssetLib/glTF2/glTF2AssetWriter.inl b/code/AssetLib/glTF2/glTF2AssetWriter.inl index 17e29d8e7..3e42ffe57 100644 --- a/code/AssetLib/glTF2/glTF2AssetWriter.inl +++ b/code/AssetLib/glTF2/glTF2AssetWriter.inl @@ -507,7 +507,7 @@ namespace glTF2 { WriteTex(materialVolume, volume.thicknessTexture, "thicknessTexture", w.mAl); - if (volume.attenuationDistance != INFINITY) { + if (volume.attenuationDistance != std::numeric_limits::infinity()) { WriteFloat(materialVolume, volume.attenuationDistance, "attenuationDistance", w.mAl); } From 206839d4f23162fb515010d5d93a21e1bbde5c50 Mon Sep 17 00:00:00 2001 From: Stepan Hrbek Date: Wed, 10 Jul 2024 09:01:53 +0200 Subject: [PATCH 31/39] Allow empty slots in mTextureCoords (#5636) * Allow empty slots in aiMesh::mTextureCoords. 1.Explicitly say in documentation that empty slots are allowed (it was unclear). 2.Change GetNumUVChannels() implementation to allow empty slots. 3.Revert fraction of 2da2835b29355a0b8f077fee466aba7a4148c1e1 where empty slots are detected and error logged. * Fix #5632 by reverting fraction of d6aacefa1ef1219b85028b4038ef7e861c2e9462 where Collada texcoords are renumbered to avoid empty slots. --------- Co-authored-by: Kim Kulling --- code/AssetLib/Collada/ColladaLoader.cpp | 10 ++++------ code/PostProcessing/ValidateDataStructure.cpp | 15 +-------------- include/assimp/mesh.h | 11 +++++++---- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/code/AssetLib/Collada/ColladaLoader.cpp b/code/AssetLib/Collada/ColladaLoader.cpp index 2574fd2ea..6d7085b35 100644 --- a/code/AssetLib/Collada/ColladaLoader.cpp +++ b/code/AssetLib/Collada/ColladaLoader.cpp @@ -625,16 +625,14 @@ aiMesh *ColladaLoader::CreateMesh(const ColladaParser &pParser, const Mesh *pSrc } // same for texture coords, as many as we have - // empty slots are not allowed, need to pack and adjust UV indexes accordingly - for (size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { + for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { if (pSrcMesh->mTexCoords[a].size() >= pStartVertex + numVertices) { - dstMesh->mTextureCoords[real] = new aiVector3D[numVertices]; + dstMesh->mTextureCoords[a] = new aiVector3D[numVertices]; for (size_t b = 0; b < numVertices; ++b) { - dstMesh->mTextureCoords[real][b] = pSrcMesh->mTexCoords[a][pStartVertex + b]; + dstMesh->mTextureCoords[a][b] = pSrcMesh->mTexCoords[a][pStartVertex + b]; } - dstMesh->mNumUVComponents[real] = pSrcMesh->mNumUVComponents[a]; - ++real; + dstMesh->mNumUVComponents[a] = pSrcMesh->mNumUVComponents[a]; } } diff --git a/code/PostProcessing/ValidateDataStructure.cpp b/code/PostProcessing/ValidateDataStructure.cpp index 1dd5d436a..8441b48be 100644 --- a/code/PostProcessing/ValidateDataStructure.cpp +++ b/code/PostProcessing/ValidateDataStructure.cpp @@ -371,20 +371,7 @@ void ValidateDSProcess::Validate(const aiMesh *pMesh) { ReportWarning("There are unreferenced vertices"); } - // texture channel 2 may not be set if channel 1 is zero ... - { - unsigned int i = 0; - for (; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) { - if (!pMesh->HasTextureCoords(i)) break; - } - for (; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) - if (pMesh->HasTextureCoords(i)) { - ReportError("Texture coordinate channel %i exists " - "although the previous channel was nullptr.", - i); - } - } - // the same for the vertex colors + // vertex color channel 2 may not be set if channel 1 is zero ... { unsigned int i = 0; for (; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) { diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index ed9a8faf9..da81cc24c 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -729,8 +729,9 @@ struct aiMesh { /** * @brief Vertex texture coordinates, also known as UV channels. * - * A mesh may contain 0 to AI_MAX_NUMBER_OF_TEXTURECOORDS per - * vertex. nullptr if not present. The array is mNumVertices in size. + * A mesh may contain 0 to AI_MAX_NUMBER_OF_TEXTURECOORDS channels per + * vertex. Used and unused (nullptr) channels may go in any order. + * The array is mNumVertices in size. */ C_STRUCT aiVector3D *mTextureCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; @@ -950,8 +951,10 @@ struct aiMesh { //! @return the number of stored uv-channels. unsigned int GetNumUVChannels() const { unsigned int n(0); - while (n < AI_MAX_NUMBER_OF_TEXTURECOORDS && mTextureCoords[n]) { - ++n; + for (unsigned i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; i++) { + if (mTextureCoords[i]) { + ++n; + } } return n; From b0aae04801753c6c3b9d45f309c802036d192e86 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Thu, 11 Jul 2024 10:30:42 +0200 Subject: [PATCH 32/39] Update Readme.md (#5663) - Add a discord invitation --- Readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index eae0cf00a..fbc316548 100644 --- a/Readme.md +++ b/Readme.md @@ -26,12 +26,11 @@ Clone [our model database](https://github.com/assimp/assimp-mdb). ### Communities ### - Ask questions at [the Assimp Discussion Board](https://github.com/assimp/assimp/discussions). +- Find us on [Discord](https://discord.gg/s9KJfaem) - Ask [the Assimp community on Reddit](https://www.reddit.com/r/Assimp/). - Ask on [StackOverflow with the assimp-tag](http://stackoverflow.com/questions/tagged/assimp?sort=newest). - Nothing has worked? File a question or an issue-report at [The Assimp-Issue Tracker](https://github.com/assimp/assimp/issues) -And we also have a Gitter-channel:Gitter [![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
    - #### Supported file formats #### See [the complete list of supported formats](https://github.com/assimp/assimp/blob/master/doc/Fileformats.md). From d3bec4499087810c36217c2a792836c8f8d36d26 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Thu, 11 Jul 2024 14:19:09 +0200 Subject: [PATCH 33/39] Update Readme.md (#5665) --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index fbc316548..920a309a2 100644 --- a/Readme.md +++ b/Readme.md @@ -26,7 +26,7 @@ Clone [our model database](https://github.com/assimp/assimp-mdb). ### Communities ### - Ask questions at [the Assimp Discussion Board](https://github.com/assimp/assimp/discussions). -- Find us on [Discord](https://discord.gg/s9KJfaem) +- Find us on [https://discord.gg/s9KJfaem](https://discord.gg/kKazXMXDy2) - Ask [the Assimp community on Reddit](https://www.reddit.com/r/Assimp/). - Ask on [StackOverflow with the assimp-tag](http://stackoverflow.com/questions/tagged/assimp?sort=newest). - Nothing has worked? File a question or an issue-report at [The Assimp-Issue Tracker](https://github.com/assimp/assimp/issues) From 0cb169368956fba4508fbd3af361cf37c8060e8e Mon Sep 17 00:00:00 2001 From: Steve M Date: Fri, 12 Jul 2024 04:25:35 -0700 Subject: [PATCH 34/39] [USD] Integrate "tinyusdz" project (#5628) * Squash development commits for PR * Fix failing build on armeabi-v7a via android NDK * Update with blendshape support * Migrate to auto-cloning and patching tinyusdz (instead of manually copying files) * Update to latest rendermesh-refactor branch commit * Remove tracked file * Update to use recent commit to "dev" branch "rendermesh-refactor" was merged to "dev" around 9 May 2024 but merge was not obvious from commit messages * Add UNUSED() macro (cherry picked from commit d89fe8f034c353cc5cc5b3ac78cd8845e006de38) * Update tinyusdz branch * Prevent per-ABI (x86, x86_64 etc) clone on android * Add verbose logging cmake option * Fix macro and patch * Address compiler warnings * Address compiler warnings * Address compiler warnings * Attempt prevent re-clone/re-patch once downloaded by any ABI build * Disable tinyusdz clone/build by default assimp github PR auto-CI checks clone/build the tinyusdz code, and reject PR due to compiler warnings in the 3rd party external tinyusdz project --------- Co-authored-by: Steve M --- .gitignore | 4 + CMakeLists.txt | 19 +- code/AssetLib/USD/USDLoader.cpp | 125 +++ code/AssetLib/USD/USDLoader.h | 78 ++ code/AssetLib/USD/USDLoaderImplTinyusdz.cpp | 751 ++++++++++++++++++ code/AssetLib/USD/USDLoaderImplTinyusdz.h | 154 ++++ .../USD/USDLoaderImplTinyusdzHelper.cpp | 110 +++ .../USD/USDLoaderImplTinyusdzHelper.h | 29 + code/AssetLib/USD/USDLoaderUtil.cpp | 116 +++ code/AssetLib/USD/USDLoaderUtil.h | 59 ++ code/AssetLib/USD/USDPreprocessor.h | 50 ++ code/CMakeLists.txt | 130 +++ code/Common/BaseImporter.cpp | 5 +- code/Common/ImporterRegistry.cpp | 6 + contrib/tinyusdz/README.md | 13 + contrib/tinyusdz/assimp_tinyusdz_logging.inc | 57 ++ contrib/tinyusdz/patches/README.md | 40 + contrib/tinyusdz/patches/tinyusdz.patch | 42 + doc/Fileformats.md | 1 + include/assimp/BaseImporter.h | 3 +- test/CMakeLists.txt | 1 + test/models-nonbsd/USD/usda/README.md | 3 + test/models-nonbsd/USD/usda/blendshape.usda | 154 ++++ test/models-nonbsd/USD/usda/texturedcube.usda | 101 +++ test/models-nonbsd/USD/usda/textures/01.jpg | Bin 0 -> 10290 bytes .../USD/usda/textures/checkerboard.png | Bin 0 -> 7688 bytes .../USD/usda/textures/texture-cat.jpg | Bin 0 -> 50234 bytes .../USD/usda/translated-cube.usda | 55 ++ test/models-nonbsd/USD/usdc/README.md | 4 + test/models-nonbsd/USD/usdc/blendshape.usdc | Bin 0 -> 4862 bytes test/models-nonbsd/USD/usdc/suzanne.usdc | Bin 0 -> 48768 bytes test/models-nonbsd/USD/usdc/texturedcube.usdc | Bin 0 -> 3551 bytes test/models-nonbsd/USD/usdc/textures/01.jpg | Bin 0 -> 10290 bytes .../USD/usdc/textures/checkerboard.png | Bin 0 -> 7688 bytes .../USD/usdc/textures/texture-cat.jpg | Bin 0 -> 50234 bytes .../USD/usdc/translated-cube.usdc | Bin 0 -> 2527 bytes test/unit/utUSDImport.cpp | 66 ++ 37 files changed, 2167 insertions(+), 9 deletions(-) create mode 100644 code/AssetLib/USD/USDLoader.cpp create mode 100644 code/AssetLib/USD/USDLoader.h create mode 100644 code/AssetLib/USD/USDLoaderImplTinyusdz.cpp create mode 100644 code/AssetLib/USD/USDLoaderImplTinyusdz.h create mode 100644 code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp create mode 100644 code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h create mode 100644 code/AssetLib/USD/USDLoaderUtil.cpp create mode 100644 code/AssetLib/USD/USDLoaderUtil.h create mode 100644 code/AssetLib/USD/USDPreprocessor.h create mode 100644 contrib/tinyusdz/README.md create mode 100644 contrib/tinyusdz/assimp_tinyusdz_logging.inc create mode 100644 contrib/tinyusdz/patches/README.md create mode 100644 contrib/tinyusdz/patches/tinyusdz.patch create mode 100644 test/models-nonbsd/USD/usda/README.md create mode 100644 test/models-nonbsd/USD/usda/blendshape.usda create mode 100644 test/models-nonbsd/USD/usda/texturedcube.usda create mode 100644 test/models-nonbsd/USD/usda/textures/01.jpg create mode 100644 test/models-nonbsd/USD/usda/textures/checkerboard.png create mode 100644 test/models-nonbsd/USD/usda/textures/texture-cat.jpg create mode 100644 test/models-nonbsd/USD/usda/translated-cube.usda create mode 100644 test/models-nonbsd/USD/usdc/README.md create mode 100644 test/models-nonbsd/USD/usdc/blendshape.usdc create mode 100644 test/models-nonbsd/USD/usdc/suzanne.usdc create mode 100644 test/models-nonbsd/USD/usdc/texturedcube.usdc create mode 100644 test/models-nonbsd/USD/usdc/textures/01.jpg create mode 100644 test/models-nonbsd/USD/usdc/textures/checkerboard.png create mode 100644 test/models-nonbsd/USD/usdc/textures/texture-cat.jpg create mode 100644 test/models-nonbsd/USD/usdc/translated-cube.usdc create mode 100644 test/unit/utUSDImport.cpp diff --git a/.gitignore b/.gitignore index 09d631ee8..c8fa089db 100644 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,7 @@ tools/assimp_qt_viewer/ui_mainwindow.h #Generated directory generated/* + +# 3rd party cloned repos/tarballs etc +# tinyusdz repo, automatically cloned via CMake +contrib/tinyusdz/autoclone diff --git a/CMakeLists.txt b/CMakeLists.txt index 718a251e4..619cd0ce3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,13 @@ SET(CMAKE_POLICY_DEFAULT_CMP0092 NEW) CMAKE_MINIMUM_REQUIRED( VERSION 3.22 ) +# Experimental USD importer: disabled, need to opt-in +# Note: assimp github PR automatic checks will fail the PR due to compiler warnings in +# the external, 3rd party tinyusdz code which isn't technically part of the PR since it's +# auto-cloned during build; so MUST disable the feature or the PR will be rejected +option(ASSIMP_BUILD_USD_IMPORTER "Enable USD file import" off) +option(ASSIMP_BUILD_USD_VERBOSE_LOGS "Enable verbose USD import debug logging" off) + # Disabled importers: m3d for 5.1 or later ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_IMPORTER) ADD_DEFINITIONS( -DASSIMP_BUILD_NO_M3D_EXPORTER) @@ -262,7 +269,7 @@ IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT MINGW AND NOT HAIKU) IF(NOT ASSIMP_HUNTER_ENABLED) SET(CMAKE_POSITION_INDEPENDENT_CODE ON) ENDIF() - + IF(CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 13) MESSAGE(STATUS "GCC13 detected disabling \"-Wdangling-reference\" in Cpp files as it appears to be a false positive") ADD_COMPILE_OPTIONS("$<$:-Wno-dangling-reference>") @@ -284,13 +291,13 @@ ELSEIF(MSVC) ELSE() # msvc ADD_COMPILE_OPTIONS(/MP /bigobj) ENDIF() - + # disable "elements of array '' will be default initialized" warning on MSVC2013 IF(MSVC12) - ADD_COMPILE_OPTIONS(/wd4351) + ADD_COMPILE_OPTIONS(/wd4351) ENDIF() # supress warning for double to float conversion if Double precision is activated - ADD_COMPILE_OPTIONS(/wd4244) + ADD_COMPILE_OPTIONS(/wd4244) SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG /Zi /Od") SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG:FULL /PDBALTPATH:%_PDB% /OPT:REF /OPT:ICF") @@ -330,7 +337,7 @@ ENDIF() IF (ASSIMP_COVERALLS) MESSAGE(STATUS "Coveralls enabled") - + INCLUDE(Coveralls) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") @@ -338,7 +345,7 @@ ENDIF() IF (ASSIMP_ASAN) MESSAGE(STATUS "AddressSanitizer enabled") - + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address") ENDIF() diff --git a/code/AssetLib/USD/USDLoader.cpp b/code/AssetLib/USD/USDLoader.cpp new file mode 100644 index 000000000..c4669f57f --- /dev/null +++ b/code/AssetLib/USD/USDLoader.cpp @@ -0,0 +1,125 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + --------------------------------------------------------------------------- + */ + +/** @file USDLoader.cpp + * @brief Implementation of the USD importer class + */ + +#ifndef ASSIMP_BUILD_NO_USD_IMPORTER +#include + +// internal headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "USDLoader.h" +#include "USDLoaderUtil.h" +#include "USDPreprocessor.h" + +static constexpr aiImporterDesc desc = { + "USD Object Importer", + "", + "", + "https://en.wikipedia.org/wiki/Universal_Scene_Description/", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "usd usda usdc usdz" +}; + +namespace Assimp { +using namespace std; + +// Constructor to be privately used by Importer +USDImporter::USDImporter() : + impl(USDImporterImplTinyusdz()) { +} + +// ------------------------------------------------------------------------------------------------ + +bool USDImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const { + UNUSED(checkSig); + // Based on token + static const uint32_t usdcTokens[] = { AI_MAKE_MAGIC("PXR-USDC") }; + bool canRead = CheckMagicToken(pIOHandler, pFile, usdcTokens, AI_COUNT_OF(usdcTokens)); + if (canRead) { + return canRead; + } + + // Based on extension + // TODO: confirm OK to replace this w/SimpleExtensionCheck() below + canRead = isUsd(pFile) || isUsda(pFile) || isUsdc(pFile) || isUsdz(pFile); + if (canRead) { + return canRead; + } + canRead = SimpleExtensionCheck(pFile, "usd", "usda", "usdc", "usdz"); + return canRead; +} + +const aiImporterDesc *USDImporter::GetInfo() const { + return &desc; +} + +void USDImporter::InternReadFile( + const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler) { + impl.InternReadFile( + pFile, + pScene, + pIOHandler); +} + +} // namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_USD_IMPORTER diff --git a/code/AssetLib/USD/USDLoader.h b/code/AssetLib/USD/USDLoader.h new file mode 100644 index 000000000..3d6b58db7 --- /dev/null +++ b/code/AssetLib/USD/USDLoader.h @@ -0,0 +1,78 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the + following conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------- + */ + +/** @file USDLoader.h + * @brief Declaration of the USD importer class. + */ +#pragma once +#ifndef AI_USDLOADER_H_INCLUDED +#define AI_USDLOADER_H_INCLUDED + +#include +#include +#include +#include + +#include "USDLoaderImplTinyusdz.h" + +namespace Assimp { +class USDImporter : public BaseImporter { +public: + USDImporter(); + ~USDImporter() override = default; + + /// \brief Returns whether the class can handle the format of the given file. + /// \remark See BaseImporter::CanRead() for details. + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + +protected: + //! \brief Appends the supported extension. + const aiImporterDesc *GetInfo() const override; + + void InternReadFile( + const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler) override; +private: + USDImporterImplTinyusdz impl; +}; + +} // namespace Assimp +#endif // AI_USDLOADER_H_INCLUDED diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp b/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp new file mode 100644 index 000000000..0f293c818 --- /dev/null +++ b/code/AssetLib/USD/USDLoaderImplTinyusdz.cpp @@ -0,0 +1,751 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + --------------------------------------------------------------------------- + */ + +/** @file USDLoader.cpp + * @brief Implementation of the USD importer class + */ + +#ifndef ASSIMP_BUILD_NO_USD_IMPORTER +#include +#include + +// internal headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "io-util.hh" // namespace tinyusdz::io +#include "tydra/scene-access.hh" +#include "tydra/shader-network.hh" +#include "USDLoaderImplTinyusdzHelper.h" +#include "USDLoaderImplTinyusdz.h" +#include "USDLoaderUtil.h" +#include "USDPreprocessor.h" + +#include "../../../contrib/tinyusdz/assimp_tinyusdz_logging.inc" + +namespace { + const char *const TAG = "tinyusdz loader"; +} + +namespace Assimp { +using namespace std; + +void USDImporterImplTinyusdz::InternReadFile( + const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler) { + UNUSED(pIOHandler); + UNUSED(TAG); // Ignore unused variable when -Werror enabled + // Grab filename for logging purposes + size_t pos = pFile.find_last_of('/'); + string basePath = pFile.substr(0, pos); + string nameWExt = pFile.substr(pos + 1); + stringstream ss; + ss.str(""); + ss << "InternReadFile(): model" << nameWExt; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + + bool ret{ false }; + tinyusdz::USDLoadOptions options; + tinyusdz::Stage stage; + std::string warn, err; + bool is_usdz{ false }; + if (isUsdc(pFile)) { + ret = LoadUSDCFromFile(pFile, &stage, &warn, &err, options); + ss.str(""); + ss << "InternReadFile(): LoadUSDCFromFile() result: " << ret; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } else if (isUsda(pFile)) { + ret = LoadUSDAFromFile(pFile, &stage, &warn, &err, options); + ss.str(""); + ss << "InternReadFile(): LoadUSDAFromFile() result: " << ret; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } else if (isUsdz(pFile)) { + ret = LoadUSDZFromFile(pFile, &stage, &warn, &err, options); + is_usdz = true; + ss.str(""); + ss << "InternReadFile(): LoadUSDZFromFile() result: " << ret; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } else if (isUsd(pFile)) { + ret = LoadUSDFromFile(pFile, &stage, &warn, &err, options); + ss.str(""); + ss << "InternReadFile(): LoadUSDFromFile() result: " << ret; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } + if (warn.empty() && err.empty()) { + ss.str(""); + ss << "InternReadFile(): load free of warnings/errors"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } else { + if (!warn.empty()) { + ss.str(""); + ss << "InternReadFile(): WARNING reported: " << warn; + TINYUSDZLOGW(TAG, "%s", ss.str().c_str()); + } + if (!err.empty()) { + ss.str(""); + ss << "InternReadFile(): ERROR reported: " << err; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + } + } + if (!ret) { + ss.str(""); + ss << "InternReadFile(): ERROR: load failed! ret: " << ret; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + return; + } + tinyusdz::tydra::RenderScene render_scene; + tinyusdz::tydra::RenderSceneConverter converter; + tinyusdz::tydra::RenderSceneConverterEnv env(stage); + std::string usd_basedir = tinyusdz::io::GetBaseDir(pFile); + env.set_search_paths({ usd_basedir }); // {} needed to convert to vector of char + + // NOTE: Pointer address of usdz_asset must be valid until the call of RenderSceneConverter::ConvertToRenderScene. + tinyusdz::USDZAsset usdz_asset; + if (is_usdz) { + if (!tinyusdz::ReadUSDZAssetInfoFromFile(pFile, &usdz_asset, &warn, &err)) { + if (!warn.empty()) { + ss.str(""); + ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: WARNING reported: " << warn; + TINYUSDZLOGW(TAG, "%s", ss.str().c_str()); + } + if (!err.empty()) { + ss.str(""); + ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: ERROR reported: " << err; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + } + ss.str(""); + ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: ERROR!"; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + } else { + ss.str(""); + ss << "InternReadFile(): ReadUSDZAssetInfoFromFile: OK"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } + + tinyusdz::AssetResolutionResolver arr; + if (!tinyusdz::SetupUSDZAssetResolution(arr, &usdz_asset)) { + ss.str(""); + ss << "InternReadFile(): SetupUSDZAssetResolution: ERROR: load failed! ret: " << ret; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + } else { + ss.str(""); + ss << "InternReadFile(): SetupUSDZAssetResolution: OK"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + env.asset_resolver = arr; + } + } + + ret = converter.ConvertToRenderScene(env, &render_scene); + if (!ret) { + ss.str(""); + ss << "InternReadFile(): ConvertToRenderScene() failed!"; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + return; + } + +// sanityCheckNodesRecursive(pScene->mRootNode); + meshes(render_scene, pScene, nameWExt); + materials(render_scene, pScene, nameWExt); + textures(render_scene, pScene, nameWExt); + textureImages(render_scene, pScene, nameWExt); + buffers(render_scene, pScene, nameWExt); + + std::map meshNodes; + setupNodes(render_scene, pScene, meshNodes, nameWExt); + + setupBlendShapes(render_scene, pScene, nameWExt); +} + +void USDImporterImplTinyusdz::meshes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + stringstream ss; + pScene->mNumMeshes = static_cast(render_scene.meshes.size()); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes](); + ss.str(""); + ss << "meshes(): pScene->mNumMeshes: " << pScene->mNumMeshes; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + + // Export meshes + for (size_t meshIdx = 0; meshIdx < pScene->mNumMeshes; meshIdx++) { + pScene->mMeshes[meshIdx] = new aiMesh(); + pScene->mMeshes[meshIdx]->mName.Set(render_scene.meshes[meshIdx].prim_name); + ss.str(""); + ss << " mesh[" << meshIdx << "]: " << + render_scene.meshes[meshIdx].joint_and_weights.jointIndices.size() << " jointIndices, " << + render_scene.meshes[meshIdx].joint_and_weights.jointWeights.size() << " jointWeights, elementSize: " << + render_scene.meshes[meshIdx].joint_and_weights.elementSize; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + ss.str(""); + ss << " skel_id: " << render_scene.meshes[meshIdx].skel_id; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + if (render_scene.meshes[meshIdx].material_id > -1) { + pScene->mMeshes[meshIdx]->mMaterialIndex = render_scene.meshes[meshIdx].material_id; + } + verticesForMesh(render_scene, pScene, meshIdx, nameWExt); + facesForMesh(render_scene, pScene, meshIdx, nameWExt); + // Some models infer normals from faces, but others need them e.g. + // - apple "toy car" canopy normals will be wrong + // - human "untitled" model (tinyusdz issue #115) will be "splotchy" + normalsForMesh(render_scene, pScene, meshIdx, nameWExt); + materialsForMesh(render_scene, pScene, meshIdx, nameWExt); + uvsForMesh(render_scene, pScene, meshIdx, nameWExt); + } +} + +void USDImporterImplTinyusdz::verticesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + UNUSED(nameWExt); + pScene->mMeshes[meshIdx]->mNumVertices = static_cast(render_scene.meshes[meshIdx].points.size()); + pScene->mMeshes[meshIdx]->mVertices = new aiVector3D[pScene->mMeshes[meshIdx]->mNumVertices]; + for (size_t j = 0; j < pScene->mMeshes[meshIdx]->mNumVertices; ++j) { + pScene->mMeshes[meshIdx]->mVertices[j].x = render_scene.meshes[meshIdx].points[j][0]; + pScene->mMeshes[meshIdx]->mVertices[j].y = render_scene.meshes[meshIdx].points[j][1]; + pScene->mMeshes[meshIdx]->mVertices[j].z = render_scene.meshes[meshIdx].points[j][2]; + } +} + +void USDImporterImplTinyusdz::facesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + UNUSED(nameWExt); + pScene->mMeshes[meshIdx]->mNumFaces = static_cast(render_scene.meshes[meshIdx].faceVertexCounts().size()); + pScene->mMeshes[meshIdx]->mFaces = new aiFace[pScene->mMeshes[meshIdx]->mNumFaces](); + size_t faceVertIdxOffset = 0; + for (size_t faceIdx = 0; faceIdx < pScene->mMeshes[meshIdx]->mNumFaces; ++faceIdx) { + pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices = render_scene.meshes[meshIdx].faceVertexCounts()[faceIdx]; + pScene->mMeshes[meshIdx]->mFaces[faceIdx].mIndices = new unsigned int[pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices]; + for (size_t j = 0; j < pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices; ++j) { + pScene->mMeshes[meshIdx]->mFaces[faceIdx].mIndices[j] = + render_scene.meshes[meshIdx].faceVertexIndices()[j + faceVertIdxOffset]; + } + faceVertIdxOffset += pScene->mMeshes[meshIdx]->mFaces[faceIdx].mNumIndices; + } +} + +void USDImporterImplTinyusdz::normalsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + UNUSED(nameWExt); + pScene->mMeshes[meshIdx]->mNormals = new aiVector3D[pScene->mMeshes[meshIdx]->mNumVertices]; + const float *floatPtr = reinterpret_cast(render_scene.meshes[meshIdx].normals.get_data().data()); + for (size_t vertIdx = 0, fpj = 0; vertIdx < pScene->mMeshes[meshIdx]->mNumVertices; ++vertIdx, fpj += 3) { + pScene->mMeshes[meshIdx]->mNormals[vertIdx].x = floatPtr[fpj]; + pScene->mMeshes[meshIdx]->mNormals[vertIdx].y = floatPtr[fpj + 1]; + pScene->mMeshes[meshIdx]->mNormals[vertIdx].z = floatPtr[fpj + 2]; + } +} + +void USDImporterImplTinyusdz::materialsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + UNUSED(render_scene); UNUSED(pScene); UNUSED(meshIdx); UNUSED(nameWExt); +} + +void USDImporterImplTinyusdz::uvsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + UNUSED(nameWExt); + const size_t uvSlotsCount = render_scene.meshes[meshIdx].texcoords.size(); + if (uvSlotsCount < 1) { + return; + } + pScene->mMeshes[meshIdx]->mTextureCoords[0] = new aiVector3D[pScene->mMeshes[meshIdx]->mNumVertices]; + pScene->mMeshes[meshIdx]->mNumUVComponents[0] = 2; // U and V stored in "x", "y" of aiVector3D. + for (unsigned int uvSlotIdx = 0; uvSlotIdx < uvSlotsCount; ++uvSlotIdx) { + const auto uvsForSlot = render_scene.meshes[meshIdx].texcoords.at(uvSlotIdx); + if (uvsForSlot.get_data().size() == 0) { + continue; + } + const float *floatPtr = reinterpret_cast(uvsForSlot.get_data().data()); + for (size_t vertIdx = 0, fpj = 0; vertIdx < pScene->mMeshes[meshIdx]->mNumVertices; ++vertIdx, fpj += 2) { + pScene->mMeshes[meshIdx]->mTextureCoords[uvSlotIdx][vertIdx].x = floatPtr[fpj]; + pScene->mMeshes[meshIdx]->mTextureCoords[uvSlotIdx][vertIdx].y = floatPtr[fpj + 1]; + } + } +} + +static aiColor3D *ownedColorPtrFor(const std::array &color) { + aiColor3D *colorPtr = new aiColor3D(); + colorPtr->r = color[0]; + colorPtr->g = color[1]; + colorPtr->b = color[2]; + return colorPtr; +} + +static std::string nameForTextureWithId( + const tinyusdz::tydra::RenderScene &render_scene, + const int targetId) { + stringstream ss; + std::string texName; + for (const auto &image : render_scene.images) { + if (image.buffer_id == targetId) { + texName = image.asset_identifier; + ss.str(""); + ss << "nameForTextureWithId(): found texture " << texName << " with target id " << targetId; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + break; + } + } + ss.str(""); + ss << "nameForTextureWithId(): ERROR! Failed to find texture with target id " << targetId; + TINYUSDZLOGE(TAG, "%s", ss.str().c_str()); + return texName; +} + +static void assignTexture( + const tinyusdz::tydra::RenderScene &render_scene, + const tinyusdz::tydra::RenderMaterial &material, + aiMaterial *mat, + const int textureId, + const int aiTextureType) { + UNUSED(material); + std::string name = nameForTextureWithId(render_scene, textureId); + aiString *texName = new aiString(); + texName->Set(name); + stringstream ss; + ss.str(""); + ss << "assignTexture(): name: " << name; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + // TODO: verify hard-coded '0' index is correct + mat->AddProperty(texName, _AI_MATKEY_TEXTURE_BASE, aiTextureType, 0); +} + +void USDImporterImplTinyusdz::materials( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + const size_t numMaterials{render_scene.materials.size()}; + (void) numMaterials; // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "materials(): model" << nameWExt << ", numMaterials: " << numMaterials; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + pScene->mNumMaterials = 0; + if (render_scene.materials.empty()) { + return; + } + pScene->mMaterials = new aiMaterial *[render_scene.materials.size()]; + for (const auto &material : render_scene.materials) { + ss.str(""); + ss << " material[" << pScene->mNumMaterials << "]: name: |" << material.name << "|, disp name: |" << material.display_name << "|"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + aiMaterial *mat = new aiMaterial; + + aiString *materialName = new aiString(); + materialName->Set(material.name); + mat->AddProperty(materialName, AI_MATKEY_NAME); + + mat->AddProperty( + ownedColorPtrFor(material.surfaceShader.diffuseColor.value), + 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty( + ownedColorPtrFor(material.surfaceShader.specularColor.value), + 1, AI_MATKEY_COLOR_SPECULAR); + mat->AddProperty( + ownedColorPtrFor(material.surfaceShader.emissiveColor.value), + 1, AI_MATKEY_COLOR_EMISSIVE); + + ss.str(""); + if (material.surfaceShader.diffuseColor.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.diffuseColor.texture_id, aiTextureType_DIFFUSE); + ss << " material[" << pScene->mNumMaterials << "]: diff tex id " << material.surfaceShader.diffuseColor.texture_id << "\n"; + } + if (material.surfaceShader.specularColor.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.specularColor.texture_id, aiTextureType_SPECULAR); + ss << " material[" << pScene->mNumMaterials << "]: spec tex id " << material.surfaceShader.specularColor.texture_id << "\n"; + } + if (material.surfaceShader.normal.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.normal.texture_id, aiTextureType_NORMALS); + ss << " material[" << pScene->mNumMaterials << "]: normal tex id " << material.surfaceShader.normal.texture_id << "\n"; + } + if (material.surfaceShader.emissiveColor.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.emissiveColor.texture_id, aiTextureType_EMISSIVE); + ss << " material[" << pScene->mNumMaterials << "]: emissive tex id " << material.surfaceShader.emissiveColor.texture_id << "\n"; + } + if (material.surfaceShader.occlusion.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.occlusion.texture_id, aiTextureType_LIGHTMAP); + ss << " material[" << pScene->mNumMaterials << "]: lightmap (occlusion) tex id " << material.surfaceShader.occlusion.texture_id << "\n"; + } + if (material.surfaceShader.metallic.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.metallic.texture_id, aiTextureType_METALNESS); + ss << " material[" << pScene->mNumMaterials << "]: metallic tex id " << material.surfaceShader.metallic.texture_id << "\n"; + } + if (material.surfaceShader.roughness.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.roughness.texture_id, aiTextureType_DIFFUSE_ROUGHNESS); + ss << " material[" << pScene->mNumMaterials << "]: roughness tex id " << material.surfaceShader.roughness.texture_id << "\n"; + } + if (material.surfaceShader.clearcoat.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.clearcoat.texture_id, aiTextureType_CLEARCOAT); + ss << " material[" << pScene->mNumMaterials << "]: clearcoat tex id " << material.surfaceShader.clearcoat.texture_id << "\n"; + } + if (material.surfaceShader.opacity.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.opacity.texture_id, aiTextureType_OPACITY); + ss << " material[" << pScene->mNumMaterials << "]: opacity tex id " << material.surfaceShader.opacity.texture_id << "\n"; + } + if (material.surfaceShader.displacement.is_texture()) { + assignTexture(render_scene, material, mat, material.surfaceShader.displacement.texture_id, aiTextureType_DISPLACEMENT); + ss << " material[" << pScene->mNumMaterials << "]: displacement tex id " << material.surfaceShader.displacement.texture_id << "\n"; + } + if (material.surfaceShader.clearcoatRoughness.is_texture()) { + ss << " material[" << pScene->mNumMaterials << "]: clearcoatRoughness tex id " << material.surfaceShader.clearcoatRoughness.texture_id << "\n"; + } + if (material.surfaceShader.opacityThreshold.is_texture()) { + ss << " material[" << pScene->mNumMaterials << "]: opacityThreshold tex id " << material.surfaceShader.opacityThreshold.texture_id << "\n"; + } + if (material.surfaceShader.ior.is_texture()) { + ss << " material[" << pScene->mNumMaterials << "]: ior tex id " << material.surfaceShader.ior.texture_id << "\n"; + } + if (!ss.str().empty()) { + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } + + pScene->mMaterials[pScene->mNumMaterials] = mat; + ++pScene->mNumMaterials; + } +} + +void USDImporterImplTinyusdz::textures( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + UNUSED(pScene); + const size_t numTextures{render_scene.textures.size()}; + UNUSED(numTextures); // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "textures(): model" << nameWExt << ", numTextures: " << numTextures; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + size_t i{0}; + UNUSED(i); + for (const auto &texture : render_scene.textures) { + UNUSED(texture); + ss.str(""); + ss << " texture[" << i << "]: id: " << texture.texture_image_id << ", disp name: |" << texture.display_name << "|, varname_uv: " << + texture.varname_uv << ", prim_name: |" << texture.prim_name << "|, abs_path: |" << texture.abs_path << "|"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + ++i; + } +} + +/** + * "owned" as in, used "new" to allocate and aiScene now responsible for "delete" + * + * @param render_scene renderScene object + * @param image textureImage object + * @param nameWExt filename w/ext (use to extract file type hint) + * @return aiTexture ptr + */ +static aiTexture *ownedEmbeddedTextureFor( + const tinyusdz::tydra::RenderScene &render_scene, + const tinyusdz::tydra::TextureImage &image, + const std::string &nameWExt) { + UNUSED(nameWExt); + stringstream ss; + aiTexture *tex = new aiTexture(); + size_t pos = image.asset_identifier.find_last_of('/'); + string embTexName{image.asset_identifier.substr(pos + 1)}; + tex->mFilename.Set(image.asset_identifier.c_str()); + tex->mHeight = image.height; +// const size_t imageBytesCount{render_scene.buffers[image.buffer_id].data.size() / image.channels}; + tex->mWidth = image.width; + if (tex->mHeight == 0) { + pos = embTexName.find_last_of('.'); + strncpy(tex->achFormatHint, embTexName.substr(pos + 1).c_str(), 3); + const size_t imageBytesCount{render_scene.buffers[image.buffer_id].data.size()}; + tex->pcData = (aiTexel *) new char[imageBytesCount]; + memcpy(tex->pcData, &render_scene.buffers[image.buffer_id].data[0], imageBytesCount); + } else { + string formatHint{"rgba8888"}; + strncpy(tex->achFormatHint, formatHint.c_str(), 8); + const size_t imageTexelsCount{tex->mWidth * tex->mHeight}; + tex->pcData = (aiTexel *) new char[imageTexelsCount * image.channels]; + const float *floatPtr = reinterpret_cast(&render_scene.buffers[image.buffer_id].data[0]); + ss.str(""); + ss << "ownedEmbeddedTextureFor(): manual fill..."; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + for (size_t i = 0, fpi = 0; i < imageTexelsCount; ++i, fpi += 4) { + tex->pcData[i].b = static_cast(floatPtr[fpi] * 255); + tex->pcData[i].g = static_cast(floatPtr[fpi + 1] * 255); + tex->pcData[i].r = static_cast(floatPtr[fpi + 2] * 255); + tex->pcData[i].a = static_cast(floatPtr[fpi + 3] * 255); + } + ss.str(""); + ss << "ownedEmbeddedTextureFor(): imageTexelsCount: " << imageTexelsCount << ", channels: " << image.channels; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + } + return tex; +} + +void USDImporterImplTinyusdz::textureImages( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + stringstream ss; + const size_t numTextureImages{render_scene.images.size()}; + UNUSED(numTextureImages); // Ignore unused variable when -Werror enabled + ss.str(""); + ss << "textureImages(): model" << nameWExt << ", numTextureImages: " << numTextureImages; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + pScene->mTextures = nullptr; // Need to iterate over images before knowing if valid textures available + pScene->mNumTextures = 0; + for (const auto &image : render_scene.images) { + ss.str(""); + ss << " image[" << pScene->mNumTextures << "]: |" << image.asset_identifier << "| w: " << image.width << ", h: " << image.height << + ", channels: " << image.channels << ", miplevel: " << image.miplevel << ", buffer id: " << image.buffer_id << "\n" << + " buffers.size(): " << render_scene.buffers.size() << ", data empty? " << render_scene.buffers[image.buffer_id].data.empty(); + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + if (image.buffer_id > -1 && + image.buffer_id < static_cast(render_scene.buffers.size()) && + !render_scene.buffers[image.buffer_id].data.empty()) { + aiTexture *tex = ownedEmbeddedTextureFor( + render_scene, + image, + nameWExt); + if (pScene->mTextures == nullptr) { + ss.str(""); + ss << " Init pScene->mTextures[" << render_scene.images.size() << "]"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + pScene->mTextures = new aiTexture *[render_scene.images.size()]; + } + ss.str(""); + ss << " pScene->mTextures[" << pScene->mNumTextures << "] name: |" << tex->mFilename.C_Str() << + "|, w: " << tex->mWidth << ", h: " << tex->mHeight << ", hint: " << tex->achFormatHint; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + pScene->mTextures[pScene->mNumTextures++] = tex; + } + } +} + +void USDImporterImplTinyusdz::buffers( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + const size_t numBuffers{render_scene.buffers.size()}; + UNUSED(pScene); UNUSED(numBuffers); // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "buffers(): model" << nameWExt << ", numBuffers: " << numBuffers; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + size_t i = 0; + for (const auto &buffer : render_scene.buffers) { + ss.str(""); + ss << " buffer[" << i << "]: count: " << buffer.data.size() << ", type: " << to_string(buffer.componentType); + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + ++i; + } +} + +void USDImporterImplTinyusdz::setupNodes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + std::map &meshNodes, + const std::string &nameWExt) { + stringstream ss; + + pScene->mRootNode = nodes(render_scene, meshNodes, nameWExt); + pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; + pScene->mRootNode->mMeshes = new unsigned int[pScene->mRootNode->mNumMeshes]; + ss.str(""); + ss << "setupNodes(): pScene->mNumMeshes: " << pScene->mNumMeshes; + if (pScene->mRootNode != nullptr) { + ss << ", mRootNode->mNumMeshes: " << pScene->mRootNode->mNumMeshes; + } + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + + for (unsigned int meshIdx = 0; meshIdx < pScene->mNumMeshes; meshIdx++) { + pScene->mRootNode->mMeshes[meshIdx] = meshIdx; + } + +} + +aiNode *USDImporterImplTinyusdz::nodes( + const tinyusdz::tydra::RenderScene &render_scene, + std::map &meshNodes, + const std::string &nameWExt) { + const size_t numNodes{render_scene.nodes.size()}; + (void) numNodes; // Ignore unused variable when -Werror enabled + stringstream ss; + ss.str(""); + ss << "nodes(): model" << nameWExt << ", numNodes: " << numNodes; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + return nodesRecursive(nullptr, render_scene.nodes[0], meshNodes); +} + +using Assimp::tinyusdzNodeTypeFor; +using Assimp::tinyUsdzMat4ToAiMat4; +using tinyusdz::tydra::NodeType; +aiNode *USDImporterImplTinyusdz::nodesRecursive( + aiNode *pNodeParent, + const tinyusdz::tydra::Node &node, + std::map &meshNodes) { + stringstream ss; + aiNode *cNode = new aiNode(); + cNode->mParent = pNodeParent; + cNode->mName.Set(node.prim_name); + cNode->mTransformation = tinyUsdzMat4ToAiMat4(node.local_matrix.m); + ss.str(""); + ss << "nodesRecursive(): node " << cNode->mName.C_Str() << + " type: |" << tinyusdzNodeTypeFor(node.nodeType) << + "|, disp " << node.display_name << ", abs " << node.abs_path; + if (cNode->mParent != nullptr) { + ss << " (parent " << cNode->mParent->mName.C_Str() << ")"; + } + ss << " has " << node.children.size() << " children"; + if (node.id > -1) { + ss << "\n node mesh id: " << node.id << " (node type: " << tinyusdzNodeTypeFor(node.nodeType) << ")"; + meshNodes[node.id] = node; + } + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + if (!node.children.empty()) { + cNode->mNumChildren = static_cast(node.children.size()); + cNode->mChildren = new aiNode *[cNode->mNumChildren]; + } + + size_t i{0}; + for (const auto &childNode: node.children) { + cNode->mChildren[i] = nodesRecursive(cNode, childNode, meshNodes); + ++i; + } + return cNode; +} + +void USDImporterImplTinyusdz::sanityCheckNodesRecursive( + aiNode *cNode) { + stringstream ss; + ss.str(""); + ss << "sanityCheckNodesRecursive(): node " << cNode->mName.C_Str(); + if (cNode->mParent != nullptr) { + ss << " (parent " << cNode->mParent->mName.C_Str() << ")"; + } + ss << " has " << cNode->mNumChildren << " children"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + for (size_t i = 0; i < cNode->mNumChildren; ++i) { + sanityCheckNodesRecursive(cNode->mChildren[i]); + } +} + +void USDImporterImplTinyusdz::setupBlendShapes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt) { + stringstream ss; + ss.str(""); + ss << "setupBlendShapes(): iterating over " << pScene->mNumMeshes << " meshes for model" << nameWExt; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + for (size_t meshIdx = 0; meshIdx < pScene->mNumMeshes; meshIdx++) { + blendShapesForMesh(render_scene, pScene, meshIdx, nameWExt); + } +} + +void USDImporterImplTinyusdz::blendShapesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt) { + UNUSED(nameWExt); + stringstream ss; + const unsigned int numBlendShapeTargets{static_cast(render_scene.meshes[meshIdx].targets.size())}; + UNUSED(numBlendShapeTargets); // Ignore unused variable when -Werror enabled + ss.str(""); + ss << " blendShapesForMesh(): mesh[" << meshIdx << "], numBlendShapeTargets: " << numBlendShapeTargets; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + if (numBlendShapeTargets > 0) { + pScene->mMeshes[meshIdx]->mNumAnimMeshes = numBlendShapeTargets; + pScene->mMeshes[meshIdx]->mAnimMeshes = new aiAnimMesh *[pScene->mMeshes[meshIdx]->mNumAnimMeshes]; + } + auto mapIter = render_scene.meshes[meshIdx].targets.begin(); + size_t animMeshIdx{0}; + for (; mapIter != render_scene.meshes[meshIdx].targets.end(); ++mapIter) { + const std::string name{mapIter->first}; + const tinyusdz::tydra::ShapeTarget shapeTarget{mapIter->second}; + pScene->mMeshes[meshIdx]->mAnimMeshes[animMeshIdx] = aiCreateAnimMesh(pScene->mMeshes[meshIdx]); + ss.str(""); + ss << " mAnimMeshes[" << animMeshIdx << "]: mNumVertices: " << pScene->mMeshes[meshIdx]->mAnimMeshes[animMeshIdx]->mNumVertices << + ", target: " << shapeTarget.pointIndices.size() << " pointIndices, " << shapeTarget.pointOffsets.size() << + " pointOffsets, " << shapeTarget.normalOffsets.size() << " normalOffsets"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + for (size_t iVert = 0; iVert < shapeTarget.pointOffsets.size(); ++iVert) { + pScene->mMeshes[meshIdx]->mAnimMeshes[animMeshIdx]->mVertices[shapeTarget.pointIndices[iVert]] += + tinyUsdzScaleOrPosToAssimp(shapeTarget.pointOffsets[iVert]); + } + for (size_t iVert = 0; iVert < shapeTarget.normalOffsets.size(); ++iVert) { + pScene->mMeshes[meshIdx]->mAnimMeshes[animMeshIdx]->mNormals[shapeTarget.pointIndices[iVert]] += + tinyUsdzScaleOrPosToAssimp(shapeTarget.normalOffsets[iVert]); + } + ss.str(""); + ss << " target[" << animMeshIdx << "]: name: " << name << ", prim_name: " << + shapeTarget.prim_name << ", abs_path: " << shapeTarget.abs_path << + ", display_name: " << shapeTarget.display_name << ", " << shapeTarget.pointIndices.size() << + " pointIndices, " << shapeTarget.pointOffsets.size() << " pointOffsets, " << + shapeTarget.normalOffsets.size() << " normalOffsets, " << shapeTarget.inbetweens.size() << + " inbetweens"; + TINYUSDZLOGD(TAG, "%s", ss.str().c_str()); + ++animMeshIdx; + } +} + +} // namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_USD_IMPORTER diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdz.h b/code/AssetLib/USD/USDLoaderImplTinyusdz.h new file mode 100644 index 000000000..637c58686 --- /dev/null +++ b/code/AssetLib/USD/USDLoaderImplTinyusdz.h @@ -0,0 +1,154 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the + following conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------- + */ + +/** @file USDLoader.h + * @brief Declaration of the USD importer class. + */ +#pragma once +#ifndef AI_USDLOADER_IMPL_TINYUSDZ_H_INCLUDED +#define AI_USDLOADER_IMPL_TINYUSDZ_H_INCLUDED + +#include +#include +#include +#include +#include +#include "tinyusdz.hh" +#include "tydra/render-data.hh" + +namespace Assimp { +class USDImporterImplTinyusdz { +public: + USDImporterImplTinyusdz() = default; + ~USDImporterImplTinyusdz() = default; + + void InternReadFile( + const std::string &pFile, + aiScene *pScene, + IOSystem *pIOHandler); + + void meshes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void verticesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void facesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void normalsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void materialsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void uvsForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); + + void materials( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void textures( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void textureImages( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void buffers( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void setupNodes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + std::map &meshNodes, + const std::string &nameWExt + ); + + aiNode *nodes( + const tinyusdz::tydra::RenderScene &render_scene, + std::map &meshNodes, + const std::string &nameWExt); + + aiNode *nodesRecursive( + aiNode *pNodeParent, + const tinyusdz::tydra::Node &node, + std::map &meshNodes); + + void sanityCheckNodesRecursive( + aiNode *pNode); + + void setupBlendShapes( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + const std::string &nameWExt); + + void blendShapesForMesh( + const tinyusdz::tydra::RenderScene &render_scene, + aiScene *pScene, + size_t meshIdx, + const std::string &nameWExt); +}; +} // namespace Assimp +#endif // AI_USDLOADER_IMPL_TINYUSDZ_H_INCLUDED diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp b/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp new file mode 100644 index 000000000..2f10db32e --- /dev/null +++ b/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp @@ -0,0 +1,110 @@ +#ifndef ASSIMP_BUILD_NO_USD_IMPORTER +#include "USDLoaderImplTinyusdzHelper.h" + +#include "../../../contrib/tinyusdz/assimp_tinyusdz_logging.inc" + +namespace { +//const char *const TAG = "tinyusdz helper"; +} + +using ChannelType = tinyusdz::tydra::AnimationChannel::ChannelType; +std::string Assimp::tinyusdzAnimChannelTypeFor(ChannelType animChannel) { + switch (animChannel) { + case ChannelType::Transform: { + return "Transform"; + } + case ChannelType::Translation: { + return "Translation"; + } + case ChannelType::Rotation: { + return "Rotation"; + } + case ChannelType::Scale: { + return "Scale"; + } + case ChannelType::Weight: { + return "Weight"; + } + default: + return "Invalid"; + } +} + +using tinyusdz::tydra::NodeType; +std::string Assimp::tinyusdzNodeTypeFor(NodeType type) { + switch (type) { + case NodeType::Xform: { + return "Xform"; + } + case NodeType::Mesh: { + return "Mesh"; + } + case NodeType::Camera: { + return "Camera"; + } + case NodeType::Skeleton: { + return "Skeleton"; + } + case NodeType::PointLight: { + return "PointLight"; + } + case NodeType::DirectionalLight: { + return "DirectionalLight"; + } + case NodeType::EnvmapLight: { + return "EnvmapLight"; + } + default: + return "Invalid"; + } +} + +aiMatrix4x4 Assimp::tinyUsdzMat4ToAiMat4(const double matIn[4][4]) { + aiMatrix4x4 matOut; + matOut.a1 = matIn[0][0]; + matOut.a2 = matIn[0][1]; + matOut.a3 = matIn[0][2]; + matOut.a4 = matIn[0][3]; + matOut.b1 = matIn[1][0]; + matOut.b2 = matIn[1][1]; + matOut.b3 = matIn[1][2]; + matOut.b4 = matIn[1][3]; + matOut.c1 = matIn[2][0]; + matOut.c2 = matIn[2][1]; + matOut.c3 = matIn[2][2]; + matOut.c4 = matIn[2][3]; + matOut.d1 = matIn[3][0]; + matOut.d2 = matIn[3][1]; + matOut.d3 = matIn[3][2]; + matOut.d4 = matIn[3][3]; +// matOut.a1 = matIn[0][0]; +// matOut.a2 = matIn[1][0]; +// matOut.a3 = matIn[2][0]; +// matOut.a4 = matIn[3][0]; +// matOut.b1 = matIn[0][1]; +// matOut.b2 = matIn[1][1]; +// matOut.b3 = matIn[2][1]; +// matOut.b4 = matIn[3][1]; +// matOut.c1 = matIn[0][2]; +// matOut.c2 = matIn[1][2]; +// matOut.c3 = matIn[2][2]; +// matOut.c4 = matIn[3][2]; +// matOut.d1 = matIn[0][3]; +// matOut.d2 = matIn[1][3]; +// matOut.d3 = matIn[2][3]; +// matOut.d4 = matIn[3][3]; + return matOut; +} + +aiVector3D Assimp::tinyUsdzScaleOrPosToAssimp(const std::array &scaleOrPosIn) { + return aiVector3D(scaleOrPosIn[0], scaleOrPosIn[1], scaleOrPosIn[2]); +} + +aiQuaternion Assimp::tinyUsdzQuatToAiQuat(const std::array &quatIn) { + // tinyusdz "quat" is x,y,z,w + // aiQuaternion is w,x,y,z + return aiQuaternion( + quatIn[3], quatIn[0], quatIn[1], quatIn[2]); +} + +#endif // !! ASSIMP_BUILD_NO_USD_IMPORTER diff --git a/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h b/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h new file mode 100644 index 000000000..ee6d96a06 --- /dev/null +++ b/code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h @@ -0,0 +1,29 @@ +#pragma once +#ifndef AI_USDLOADER_IMPL_TINYUSDZ_HELPER_H_INCLUDED +#define AI_USDLOADER_IMPL_TINYUSDZ_HELPER_H_INCLUDED + +#include +#include +#include +#include "tinyusdz.hh" +#include "tydra/render-data.hh" + +namespace Assimp { + +std::string tinyusdzAnimChannelTypeFor( + tinyusdz::tydra::AnimationChannel::ChannelType animChannel); +std::string tinyusdzNodeTypeFor(tinyusdz::tydra::NodeType type); +aiMatrix4x4 tinyUsdzMat4ToAiMat4(const double matIn[4][4]); + +aiVector3D tinyUsdzScaleOrPosToAssimp(const std::array &scaleOrPosIn); + +/** + * Convert quaternion from tinyusdz "quat" to assimp "aiQuaternion" type + * + * @param quatIn tinyusdz float[4] in x,y,z,w order + * @return assimp aiQuaternion converted from input + */ +aiQuaternion tinyUsdzQuatToAiQuat(const std::array &quatIn); + +} // namespace Assimp +#endif // AI_USDLOADER_IMPL_TINYUSDZ_HELPER_H_INCLUDED diff --git a/code/AssetLib/USD/USDLoaderUtil.cpp b/code/AssetLib/USD/USDLoaderUtil.cpp new file mode 100644 index 000000000..16f0ba8ae --- /dev/null +++ b/code/AssetLib/USD/USDLoaderUtil.cpp @@ -0,0 +1,116 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the following + conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + --------------------------------------------------------------------------- + */ + +/** @file USDLoader.cpp + * @brief Implementation of the USD importer class + */ + +#ifndef ASSIMP_BUILD_NO_USD_IMPORTER +#include + +// internal headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "USDLoaderUtil.h" + +namespace Assimp { +using namespace std; +bool isUsda(const std::string &pFile) { + size_t pos = pFile.find_last_of('.'); + if (pos == string::npos) { + return false; + } + string ext = pFile.substr(pos + 1); + if (ext.size() != 4) { + return false; + } + return (ext[0] == 'u' || ext[0] == 'U') && (ext[1] == 's' || ext[1] == 'S') && (ext[2] == 'd' || ext[2] == 'D') && (ext[3] == 'a' || ext[3] == 'A'); +} + +bool isUsdc(const std::string &pFile) { + size_t pos = pFile.find_last_of('.'); + if (pos == string::npos) { + return false; + } + string ext = pFile.substr(pos + 1); + if (ext.size() != 4) { + return false; + } + return (ext[0] == 'u' || ext[0] == 'U') && (ext[1] == 's' || ext[1] == 'S') && (ext[2] == 'd' || ext[2] == 'D') && (ext[3] == 'c' || ext[3] == 'C'); +} + +bool isUsdz(const std::string &pFile) { + size_t pos = pFile.find_last_of('.'); + if (pos == string::npos) { + return false; + } + string ext = pFile.substr(pos + 1); + if (ext.size() != 4) { + return false; + } + return (ext[0] == 'u' || ext[0] == 'U') && (ext[1] == 's' || ext[1] == 'S') && (ext[2] == 'd' || ext[2] == 'D') && (ext[3] == 'z' || ext[3] == 'Z'); +} + +bool isUsd(const std::string &pFile) { + size_t pos = pFile.find_last_of('.'); + if (pos == string::npos) { + return false; + } + string ext = pFile.substr(pos + 1); + if (ext.size() != 3) { + return false; + } + return (ext[0] == 'u' || ext[0] == 'U') && (ext[1] == 's' || ext[1] == 'S') && (ext[2] == 'd' || ext[2] == 'D'); +} +} // namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_USD_IMPORTER diff --git a/code/AssetLib/USD/USDLoaderUtil.h b/code/AssetLib/USD/USDLoaderUtil.h new file mode 100644 index 000000000..b39cd254e --- /dev/null +++ b/code/AssetLib/USD/USDLoaderUtil.h @@ -0,0 +1,59 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the + following conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------- + */ + +/** @file USDLoader.h + * @brief Declaration of the USD importer class. + */ +#pragma once +#ifndef AI_USDLOADER_UTIL_H_INCLUDED +#define AI_USDLOADER_UTIL_H_INCLUDED + +#include +#include +#include +#include + +namespace Assimp { +bool isUsda(const std::string &pFile); +bool isUsdc(const std::string &pFile); +bool isUsdz(const std::string &pFile); +bool isUsd(const std::string &pFile); +} // namespace Assimp +#endif // AI_USDLOADER_UTIL_H_INCLUDED diff --git a/code/AssetLib/USD/USDPreprocessor.h b/code/AssetLib/USD/USDPreprocessor.h new file mode 100644 index 000000000..dec37ea27 --- /dev/null +++ b/code/AssetLib/USD/USDPreprocessor.h @@ -0,0 +1,50 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2024, assimp team + + All rights reserved. + + Redistribution and use of this software in source and binary forms, + with or without modification, are permitted provided that the + following conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------- + */ + +/** @file USDLoader.h + * @brief Declaration of the USD importer class. + */ +#pragma once +#ifndef AI_USDPREPROCESSOR_H_INCLUDED +#define AI_USDPREPROCESSOR_H_INCLUDED + +#define UNUSED(x) (void) x + +#endif // AI_USDPREPROCESSOR_H_INCLUDED diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index f4c293672..e4e67e682 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -828,6 +828,17 @@ ADD_ASSIMP_IMPORTER( 3D AssetLib/Unreal/UnrealLoader.h ) +ADD_ASSIMP_IMPORTER( USD + AssetLib/USD/USDLoader.cpp + AssetLib/USD/USDLoader.h + AssetLib/USD/USDLoaderImplTinyusdz.cpp + AssetLib/USD/USDLoaderImplTinyusdz.h + AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp + AssetLib/USD/USDLoaderImplTinyusdzHelper.h + AssetLib/USD/USDLoaderUtil.cpp + AssetLib/USD/USDLoaderUtil.h +) + ADD_ASSIMP_IMPORTER( X AssetLib/X/XFileHelper.h AssetLib/X/XFileImporter.cpp @@ -921,6 +932,123 @@ SET( Extra_SRCS ) SOURCE_GROUP( Extra FILES ${Extra_SRCS}) +# USD/USDA/USDC/USDZ support +# tinyusdz +IF (ASSIMP_BUILD_USD_IMPORTER) + if (ASSIMP_BUILD_USD_VERBOSE_LOGS) + ADD_DEFINITIONS( -DASSIMP_USD_VERBOSE_LOGS) + endif () + # Use CMAKE_CURRENT_SOURCE_DIR which provides assimp-local path (CMAKE_SOURCE_DIR is + # relative to top-level/main project) + set(Tinyusdz_BASE_ABSPATH "${CMAKE_CURRENT_SOURCE_DIR}/../contrib/tinyusdz") + set(Tinyusdz_REPO_ABSPATH "${Tinyusdz_BASE_ABSPATH}/autoclone") + + # Note: ALWAYS specify a git commit hash (or tag) instead of a branch name; using a branch + # name can lead to non-deterministic (unpredictable) results since the code is potentially + # in flux + # "dev" branch, 9 Jul 2024 + set(TINYUSDZ_GIT_TAG "bd2a1edbbf69f352a6c40730114db9918c384848") + message("****") + message("\n\n**** Cloning tinyusdz repo, git tag ${TINYUSDZ_GIT_TAG}\n\n") + + # Patch required to build arm32 on android + set(TINYUSDZ_PATCH_CMD git apply ${Tinyusdz_BASE_ABSPATH}/patches/tinyusdz.patch) + + # Note: CMake's "FetchContent" (which executes at configure time) is much better for this use case + # than "ExternalProject" (which executes at build time); we just want to clone a repo and + # block (wait) as long as necessary until cloning is complete, so we immediately have full + # access to the cloned source files + include(FetchContent) + # Only want to clone once (on Android, using SOURCE_DIR will clone per-ABI (x86, x86_64 etc)) + set(FETCHCONTENT_BASE_DIR ${Tinyusdz_REPO_ABSPATH}) + set(FETCHCONTENT_QUIET on) # Turn off to troubleshoot repo clone problems + set(FETCHCONTENT_UPDATES_DISCONNECTED on) # Prevent other ABIs from re-cloning/re-patching etc + FetchContent_Declare( + tinyusdz_repo + GIT_REPOSITORY "https://github.com/lighttransport/tinyusdz" + GIT_TAG ${TINYUSDZ_GIT_TAG} + PATCH_COMMAND ${TINYUSDZ_PATCH_CMD} + ) + FetchContent_MakeAvailable(tinyusdz_repo) + message("**** Finished cloning tinyusdz repo") + message("****") + + set(Tinyusdz_SRC_ABSPATH "${Tinyusdz_REPO_ABSPATH}/tinyusdz_repo-src/src") + set(Tinyusdz_SRCS + ${Tinyusdz_SRC_ABSPATH}/ascii-parser.cc + ${Tinyusdz_SRC_ABSPATH}/ascii-parser-basetype.cc + ${Tinyusdz_SRC_ABSPATH}/ascii-parser-timesamples.cc + ${Tinyusdz_SRC_ABSPATH}/ascii-parser-timesamples-array.cc + ${Tinyusdz_SRC_ABSPATH}/asset-resolution.cc + ${Tinyusdz_SRC_ABSPATH}/composition.cc + ${Tinyusdz_SRC_ABSPATH}/crate-format.cc + ${Tinyusdz_SRC_ABSPATH}/crate-pprint.cc + ${Tinyusdz_SRC_ABSPATH}/crate-reader.cc + ${Tinyusdz_SRC_ABSPATH}/image-loader.cc + ${Tinyusdz_SRC_ABSPATH}/image-util.cc + ${Tinyusdz_SRC_ABSPATH}/image-writer.cc + ${Tinyusdz_SRC_ABSPATH}/io-util.cc + ${Tinyusdz_SRC_ABSPATH}/linear-algebra.cc + ${Tinyusdz_SRC_ABSPATH}/path-util.cc + ${Tinyusdz_SRC_ABSPATH}/pprinter.cc + ${Tinyusdz_SRC_ABSPATH}/prim-composition.cc + ${Tinyusdz_SRC_ABSPATH}/prim-reconstruct.cc + ${Tinyusdz_SRC_ABSPATH}/prim-types.cc + ${Tinyusdz_SRC_ABSPATH}/primvar.cc + ${Tinyusdz_SRC_ABSPATH}/stage.cc + ${Tinyusdz_SRC_ABSPATH}/str-util.cc + ${Tinyusdz_SRC_ABSPATH}/tiny-format.cc + ${Tinyusdz_SRC_ABSPATH}/tinyusdz.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/attribute-eval.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/attribute-eval-typed.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/attribute-eval-typed-animatable.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/attribute-eval-typed-animatable-fallback.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/attribute-eval-typed-fallback.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/facial.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/prim-apply.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/render-data.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/scene-access.cc + ${Tinyusdz_SRC_ABSPATH}/tydra/shader-network.cc + ${Tinyusdz_SRC_ABSPATH}/usda-reader.cc + ${Tinyusdz_SRC_ABSPATH}/usda-writer.cc + ${Tinyusdz_SRC_ABSPATH}/usdc-reader.cc + ${Tinyusdz_SRC_ABSPATH}/usdc-writer.cc + ${Tinyusdz_SRC_ABSPATH}/usdGeom.cc + ${Tinyusdz_SRC_ABSPATH}/usdLux.cc + ${Tinyusdz_SRC_ABSPATH}/usdMtlx.cc + ${Tinyusdz_SRC_ABSPATH}/usdShade.cc + ${Tinyusdz_SRC_ABSPATH}/usdSkel.cc + ${Tinyusdz_SRC_ABSPATH}/value-pprint.cc + ${Tinyusdz_SRC_ABSPATH}/value-types.cc + ${Tinyusdz_SRC_ABSPATH}/xform.cc + ) + + set(Tinyusdz_DEP_SOURCES + ${Tinyusdz_SRC_ABSPATH}/external/fpng.cpp + #${Tinyusdz_SRC_ABSPATH}/external/staticstruct.cc + #${Tinyusdz_SRC_ABSPATH}/external/string_id/database.cpp + #${Tinyusdz_SRC_ABSPATH}/external/string_id/error.cpp + #${Tinyusdz_SRC_ABSPATH}/external/string_id/string_id.cpp + #${Tinyusdz_SRC_ABSPATH}/external/tinyxml2/tinyxml2.cpp + ${Tinyusdz_SRC_ABSPATH}/integerCoding.cpp + ${Tinyusdz_SRC_ABSPATH}/lz4-compression.cc + ${Tinyusdz_SRC_ABSPATH}/lz4/lz4.c + ) + + set(tinyusdz_INCLUDE_DIRS "${Tinyusdz_SRC_ABSPATH}") + INCLUDE_DIRECTORIES(${tinyusdz_INCLUDE_DIRS}) + SOURCE_GROUP( Contrib\\Tinyusdz + FILES + ${Tinyusdz_SRCS} + ${Tinyusdz_DEP_SOURCES} + ) + MESSAGE(STATUS "tinyusdz enabled") +ELSE() # IF (ASSIMP_BUILD_USD_IMPORTER) + set(Tinyusdz_SRCS "") + set(Tinyusdz_DEP_SOURCES "") + MESSAGE(STATUS "tinyusdz disabled") +ENDIF() # IF (ASSIMP_BUILD_USD_IMPORTER) + # pugixml IF(ASSIMP_HUNTER_ENABLED) hunter_add_package(pugixml) @@ -1171,6 +1299,8 @@ SET( assimp_src ${openddl_parser_SRCS} ${open3dgc_SRCS} ${ziplib_SRCS} + ${Tinyusdz_SRCS} + ${Tinyusdz_DEP_SOURCES} ${Pugixml_SRCS} ${stb_SRCS} # Necessary to show the headers in the project when using the VC++ generator: diff --git a/code/Common/BaseImporter.cpp b/code/Common/BaseImporter.cpp index 3a4c7c329..5c70cc27e 100644 --- a/code/Common/BaseImporter.cpp +++ b/code/Common/BaseImporter.cpp @@ -250,9 +250,10 @@ void BaseImporter::GetExtensionList(std::set &extensions) { /*static*/ bool BaseImporter::SimpleExtensionCheck(const std::string &pFile, const char *ext0, const char *ext1, - const char *ext2) { + const char *ext2, + const char *ext3) { std::set extensions; - for (const char* ext : {ext0, ext1, ext2}) { + for (const char* ext : {ext0, ext1, ext2, ext3}) { if (ext == nullptr) continue; extensions.emplace(ext); } diff --git a/code/Common/ImporterRegistry.cpp b/code/Common/ImporterRegistry.cpp index 92a04e692..276f20974 100644 --- a/code/Common/ImporterRegistry.cpp +++ b/code/Common/ImporterRegistry.cpp @@ -55,6 +55,9 @@ corresponding preprocessor flag to selectively disable formats. // Importers // (include_new_importers_here) // ------------------------------------------------------------------------------------------------ +#if !defined(ASSIMP_BUILD_NO_USD_IMPORTER) +#include "AssetLib/USD/USDLoader.h" +#endif #ifndef ASSIMP_BUILD_NO_X_IMPORTER #include "AssetLib/X/XFileImporter.h" #endif @@ -230,6 +233,9 @@ void GetImporterInstanceList(std::vector &out) { // (register_new_importers_here) // ---------------------------------------------------------------------------- out.reserve(64); +#if !defined(ASSIMP_BUILD_NO_USD_IMPORTER) + out.push_back(new USDImporter()); +#endif #if (!defined ASSIMP_BUILD_NO_X_IMPORTER) out.push_back(new XFileImporter()); #endif diff --git a/contrib/tinyusdz/README.md b/contrib/tinyusdz/README.md new file mode 100644 index 000000000..b32feca6f --- /dev/null +++ b/contrib/tinyusdz/README.md @@ -0,0 +1,13 @@ +# tinyusdz +"tinyusdz" C++ project provides USD/USDA/UDSC/UDSZ 3D model file format suport + +## Automatic repo clone +tinyusdz repo is automatically cloned. Users who haven't opted-in to USD support +won't be burdened with the extra download volume. + +To update te git commit hash pulled down, modify `TINYUSDZ_GIT_TAG` in file + `code/CMakeLists.txt` + +## Notes +Couldn't leverage tinyusdz CMakeLists.txt. Fell back to compiling source files specified in +"android" example. diff --git a/contrib/tinyusdz/assimp_tinyusdz_logging.inc b/contrib/tinyusdz/assimp_tinyusdz_logging.inc new file mode 100644 index 000000000..d1e8dd2b6 --- /dev/null +++ b/contrib/tinyusdz/assimp_tinyusdz_logging.inc @@ -0,0 +1,57 @@ +/** + * Usage + * Add line below all other #include statements: + * #include "../../../assimp_tinyusdz_logging.inc" + * to files: + * - contrib/tinyusdz/tinyusdz_repo/src/tydra/render-data.cc + * - contrib/tinyusdz/tinyusdz_repo/src/tydra/scene-access.cc + */ +#pragma once + +#if defined(__ANDROID__) +#include +#include + +#define TINYUSDZLOGT(tag, ...) ((void)__android_log_print(ANDROID_LOG_DEBUG, tag, __VA_ARGS__)) +#define TINYUSDZLOG0(tag, ...) ((void)__android_log_print(ANDROID_LOG_DEFAULT, tag, __VA_ARGS__)) +#define TINYUSDZLOGD(tag, ...) ((void)__android_log_print(ANDROID_LOG_DEBUG, tag, __VA_ARGS__)) +#define TINYUSDZLOGI(tag, ...) ((void)__android_log_print(ANDROID_LOG_INFO, tag, __VA_ARGS__)) +#define TINYUSDZLOGW(tag, ...) ((void)__android_log_print(ANDROID_LOG_WARN, tag, __VA_ARGS__)) +#define TINYUSDZLOGE(tag, ...) ((void)__android_log_print(ANDROID_LOG_ERROR, tag, __VA_ARGS__)) +#else +#define TINYUSDZLOGT(tag, ...) +#define TINYUSDZLOG0(tag, ...) +#define TINYUSDZLOGD(tag, ...) +#define TINYUSDZLOGI(tag, ...) +#define TINYUSDZLOGW(tag, ...) +#define TINYUSDZLOGE(tag, ...) +#endif // #if defined(__ANDROID__) + +#if defined(TINYUSDZ_LOCAL_DEBUG_PRINT) +#if defined(__ANDROID__) +#if defined(ASSIMP_USD_VERBOSE_LOGS) +// Works well but _extremely_ verbose +#define DCOUT(x) \ + do { \ + std::stringstream ss; \ + ss << __FILE__ << ":" << __func__ << ":" \ + << std::to_string(__LINE__) << " " << x << "\n"; \ + TINYUSDZLOGE("tinyusdz", "%s", ss.str().c_str()); \ + } while (false) +#else // defined(ASSIMP_USD_VERBOSE_LOGS) +// Silent version +#define DCOUT(x) \ + do { \ + std::stringstream ss; \ + ss << __FILE__ << ":" << __func__ << ":" \ + << std::to_string(__LINE__) << " " << x << "\n"; \ + } while (false) +#endif // defined(ASSIMP_USD_VERBOSE_LOGS) +#else // defined(__ANDROID__) +#define DCOUT(x) \ + do { \ + std::cout << __FILE__ << ":" << __func__ << ":" \ + << std::to_string(__LINE__) << " " << x << "\n"; \ + } while (false) +#endif // #if defined(__ANDROID__) +#endif // #if defined(TINYUSDZ_LOCAL_DEBUG_PRINT) \ No newline at end of file diff --git a/contrib/tinyusdz/patches/README.md b/contrib/tinyusdz/patches/README.md new file mode 100644 index 000000000..a6b2040c3 --- /dev/null +++ b/contrib/tinyusdz/patches/README.md @@ -0,0 +1,40 @@ +# Tinyusdz patch files + +Pending acceptance of proposed changes upstream, need to resort to patching to keep things moving + +## Tinyusdz files needing patches + +### `tinyusdz_repo/src/external/stb_image_resize2.h` +Without patch, build will fail for armeabi-v7a ABI via android NDK + +Add `#elif` block as indicated below around line `2407` +``` +#elif defined(STBIR_WASM) || (defined(STBIR_NEON) && defined(_MSC_VER) && defined(_M_ARM)) // WASM or 32-bit ARM on MSVC/clang +... +#elif defined(STBIR_NEON) && (defined(__ANDROID__) && defined(__arm__)) // 32-bit ARM on android NDK + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + // TODO: this stub is just to allow build on armeabi-v7a via android NDK + } + + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + // TODO: this stub is just to allow build on armeabi-v7a via android NDK + } + + static stbir__inline float stbir__half_to_float( stbir__FP16 h ) + { + // TODO: this stub is just to allow build on armeabi-v7a via android NDK + return 0; + } + + static stbir__inline stbir__FP16 stbir__float_to_half( float f ) + { + // TODO: this stub is just to allow build on armeabi-v7a via android NDK + return 0; + } + +#elif defined(STBIR_NEON) && defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__) // 64-bit ARM on MSVC (not clang) +``` + diff --git a/contrib/tinyusdz/patches/tinyusdz.patch b/contrib/tinyusdz/patches/tinyusdz.patch new file mode 100644 index 000000000..e84e9f8fe --- /dev/null +++ b/contrib/tinyusdz/patches/tinyusdz.patch @@ -0,0 +1,42 @@ +diff -rupN -x .git autoclone/tinyusdz_repo-src/src/external/stb_image_resize2.h tinyusdz_repo_patch/src/external/stb_image_resize2.h +--- autoclone/tinyusdz_repo-src/src/external/stb_image_resize2.h 2024-07-09 21:29:48.556969900 -0700 ++++ tinyusdz_repo_patch/src/external/stb_image_resize2.h 2024-07-09 23:03:47.379316700 -0700 +@@ -2404,6 +2404,38 @@ static stbir__inline stbir_uint8 stbir__ + } + } + ++#elif defined(STBIR_NEON) && (defined(__ANDROID__) && defined(__arm__)) // 32-bit ARM on android NDK ++ ++ // TODO As of Apr 2024, tinyusdz doesn't support building on armeabi-v7a (32 bit arm) for android ++ // (falls through to arm64 block and build fails) ++ // ++ // For assimp integration, the image processing utilities aren't used at all, so it's safe to ++ // essentially replace the functions with dummy no-ops. ++ // ++ // This will need to be done manually whenever the tinyusdz source files are updated in assimp, ++ // as it seems unlikely this will be fixed in the tinyusdz project ++ static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) ++ { ++ // TODO: this stub is just to allow build on armeabi-v7a via android NDK ++ } ++ ++ static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) ++ { ++ // TODO: this stub is just to allow build on armeabi-v7a via android NDK ++ } ++ ++ static stbir__inline float stbir__half_to_float( stbir__FP16 h ) ++ { ++ // TODO: this stub is just to allow build on armeabi-v7a via android NDK ++ return 0; ++ } ++ ++ static stbir__inline stbir__FP16 stbir__float_to_half( float f ) ++ { ++ // TODO: this stub is just to allow build on armeabi-v7a via android NDK ++ return 0; ++ } ++ + #elif defined(STBIR_NEON) && defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__) // 64-bit ARM on MSVC (not clang) + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) diff --git a/doc/Fileformats.md b/doc/Fileformats.md index 6fc0fe34c..50d2d5e69 100644 --- a/doc/Fileformats.md +++ b/doc/Fileformats.md @@ -60,6 +60,7 @@ __Importers__: - [STL](https://en.wikipedia.org/wiki/STL_(file_format)) - TER - UC +- [USD](https://en.wikipedia.org/wiki/Universal_Scene_Description) - VTA - X - [X3D](https://en.wikipedia.org/wiki/X3D) diff --git a/include/assimp/BaseImporter.h b/include/assimp/BaseImporter.h index 447784e75..167b254d6 100644 --- a/include/assimp/BaseImporter.h +++ b/include/assimp/BaseImporter.h @@ -277,7 +277,8 @@ public: // static utilities const std::string &pFile, const char *ext0, const char *ext1 = nullptr, - const char *ext2 = nullptr); + const char *ext2 = nullptr, + const char *ext3 = nullptr); // ------------------------------------------------------------------- /** @brief Check whether a file has one of the passed file extensions diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a59cafbbc..7b7fd850a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -148,6 +148,7 @@ SET( IMPORTERS #unit/utM3DImportExport.cpp unit/utMDCImportExport.cpp unit/utAssbinImportExport.cpp + unit/utUSDImport.cpp unit/ImportExport/utAssjsonImportExport.cpp unit/ImportExport/utCOBImportExport.cpp unit/ImportExport/utOgreImportExport.cpp diff --git a/test/models-nonbsd/USD/usda/README.md b/test/models-nonbsd/USD/usda/README.md new file mode 100644 index 000000000..e860175fd --- /dev/null +++ b/test/models-nonbsd/USD/usda/README.md @@ -0,0 +1,3 @@ +[blendshape.usda](blendshape.usda) copied from tinyusdz/models (No attribution/license cited in that project) +[texturedcube.usda](texturedcube.usda) copied from tinyusdz/models (No attribution/license cited in that project) +[translated-cube.usda](translated-cube.usda) copied from tinyusdz/models (No attribution/license cited in that project) diff --git a/test/models-nonbsd/USD/usda/blendshape.usda b/test/models-nonbsd/USD/usda/blendshape.usda new file mode 100644 index 000000000..06fc33063 --- /dev/null +++ b/test/models-nonbsd/USD/usda/blendshape.usda @@ -0,0 +1,154 @@ +#usda 1.0 +( + defaultPrim = "root" + doc = "Blender v3.4.0 Alpha" + metersPerUnit = 0.01 + upAxis = "Z" +) + +def Xform "root" +{ + float3 xformOp:scale = (100, 100, 100) + uniform token[] xformOpOrder = ["xformOp:scale"] + + def Scope "lights" + { + def DomeLight "environment" + { + custom color3f color = (0.05087609, 0.05087609, 0.05087609) + color3f inputs:color = (0.05087609, 0.05087609, 0.05087609) + float inputs:intensity = 683.0135 + custom float intensity = 683.0135 + } + } + + def Scope "materials" + { + def Material "Material" + { + token outputs:surface.connect = + custom string userProperties:blenderName:data = "Material" + + def Scope "preview" + { + def Shader "Principled_BSDF" + { + uniform token info:id = "UsdPreviewSurface" + float inputs:clearcoat = 0 + float inputs:clearcoatRoughness = 0.03 + color3f inputs:diffuseColor = (0.8, 0.8, 0.8) + color3f inputs:emissiveColor = (0, 0, 0) + float inputs:ior = 1.45 + float inputs:metallic = 0 + float inputs:opacity = 1 + float inputs:roughness = 0.5 + float inputs:specular = 0.5 + token outputs:surface + } + } + } + } + + def SkelRoot "Cube" + { + custom string userProperties:blenderName:object = "Cube" + + def Mesh "Cube" ( + active = true + prepend apiSchemas = ["SkelBindingAPI"] + ) + { + uniform bool doubleSided = 1 + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] + int[] faceVertexIndices = [0, 4, 6, 2, 3, 2, 6, 7, 7, 6, 4, 5, 5, 1, 3, 7, 1, 0, 2, 3, 5, 4, 0, 1] + rel material:binding = + normal3f[] normals = [(-2.3880695e-8, 0, 1), (-2.3880695e-8, 0, 1), (-2.3880695e-8, 0, 1), (-2.3880695e-8, 0, 1), (-0.23399627, -0.9436586, -0.2339963), (-0.23399627, -0.9436586, -0.2339963), (-0.23399627, -0.9436586, -0.2339963), (-0.23399627, -0.9436586, -0.2339963), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)] ( + interpolation = "faceVarying" + ) + point3f[] points = [(1, 1, 1), (1, 1, -1), (1, -1.9918684, 1), (1, -1, -1), (-1, 1, 1), (-1, 1, -1), (-1, -1, 1), (-1, -1, -1)] + int[] primvars:skel:jointIndices = [0, 0, 0, 0, 0, 0, 0, 0] ( + elementSize = 1 + interpolation = "vertex" + ) + float[] primvars:skel:jointWeights = [1, 1, 1, 1, 1, 1, 1, 1] ( + elementSize = 1 + interpolation = "vertex" + ) + texCoord2f[] primvars:st = [(0.625, 0.5), (0.875, 0.5), (0.875, 0.75), (0.625, 0.75), (0.375, 0.75), (0.625, 0.75), (0.625, 1), (0.375, 1), (0.375, 0), (0.625, 0), (0.625, 0.25), (0.375, 0.25), (0.125, 0.5), (0.375, 0.5), (0.375, 0.75), (0.125, 0.75), (0.375, 0.5), (0.625, 0.5), (0.625, 0.75), (0.375, 0.75), (0.375, 0.25), (0.625, 0.25), (0.625, 0.5), (0.375, 0.5)] ( + interpolation = "faceVarying" + ) + uniform token[] skel:blendShapes = ["Key_1"] + rel skel:blendShapeTargets = + prepend rel skel:skeleton = + uniform token subdivisionScheme = "none" + custom string userProperties:blenderName:data = "Cube" + custom string userProperties:blenderName:data:st = "UVMap" + + def BlendShape "Key_1" + { + uniform vector3f[] offsets = [(0, 0, 0.98508406), (0, 0, 0), (0, 0.892874, 0.98508406), (0, 0, 0), (0, 0, 0.98508406), (0, 0, 0), (0, 0, 0.98508406), (0, 0, 0)] + uniform int[] pointIndices = [0, 1, 2, 3, 4, 5, 6, 7] + } + } + + def Skeleton "Skel" + { + uniform matrix4d[] bindTransforms = [( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )] + uniform token[] joints = ["joint1"] + uniform matrix4d[] restTransforms = [( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1) )] + prepend rel skel:animationSource = + + def SkelAnimation "Anim" + { + uniform token[] blendShapes = ["Key_1"] + float[] blendShapeWeights = [0] + } + } + } + + def Xform "Light" + { + custom string userProperties:blenderName:object = "Light" + float3 xformOp:rotateXYZ = (37.26105, 3.163703, 106.93632) + float3 xformOp:scale = (1, 0.99999994, 1) + double3 xformOp:translate = (4.076245307922363, 1.0054539442062378, 5.903861999511719) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"] + + def SphereLight "Light" + { + custom color3f color = (1, 1, 1) + color3f inputs:color = (1, 1, 1) + float inputs:intensity = 5435247 + float inputs:radius = 0.10000002 + float inputs:specular = 1 + custom float intensity = 5435247 + custom float radius = 0.10000002 + custom float specular = 1 + custom string userProperties:blenderName:data = "Light" + } + } + + def Xform "Camera" + { + custom string userProperties:blenderName:object = "Camera" + float3 xformOp:rotateXYZ = (63.559303, -0.0000026647115, 46.691948) + float3 xformOp:scale = (1, 1, 1) + double3 xformOp:translate = (7.358891487121582, -6.925790786743164, 4.958309173583984) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"] + + def Camera "Camera" + { + float2 clippingRange = (10, 10000) + float focalLength = 50 + float horizontalAperture = 36 + float horizontalApertureOffset = 0 + token projection = "perspective" + double shutter:close = 0.25 + double shutter:open = -0.25 + custom string userProperties:blenderName:data = "Camera" + float verticalAperture = 24 + float verticalApertureOffset = 0 + } + } +} + diff --git a/test/models-nonbsd/USD/usda/texturedcube.usda b/test/models-nonbsd/USD/usda/texturedcube.usda new file mode 100644 index 000000000..980fa323c --- /dev/null +++ b/test/models-nonbsd/USD/usda/texturedcube.usda @@ -0,0 +1,101 @@ +#usda 1.0 +( + doc = "Blender v3.1.0" + metersPerUnit = 1 + upAxis = "Z" +) + +def Xform "Camera" +{ + matrix4d xformOp:transform = ( (0.6859206557273865, 0.7276763319969177, 0, 0), (-0.32401347160339355, 0.305420845746994, 0.8953956365585327, 0), (0.6515582203865051, -0.6141703724861145, 0.44527140259742737, 0), (7.358891487121582, -6.925790786743164, 4.958309173583984, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + + def Camera "Camera" + { + float2 clippingRange = (0.1, 100) + float focalLength = 50 + float horizontalAperture = 36 + float horizontalApertureOffset = 0 + token projection = "perspective" + float verticalAperture = 20.25 + float verticalApertureOffset = 0 + } +} + +def Xform "Cube" +{ + matrix4d xformOp:transform = ( (1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (-1.1853550672531128, 0, 1.9550952911376953, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + + def Mesh "Cube" + { + uniform bool doubleSided = 1 + int[] faceVertexCounts = [4, 4, 4, 4, 4, 4] + int[] faceVertexIndices = [0, 4, 6, 2, 3, 2, 6, 7, 7, 6, 4, 5, 5, 1, 3, 7, 1, 0, 2, 3, 5, 4, 0, 1] + rel material:binding = + normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (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, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)] ( + interpolation = "faceVarying" + ) + point3f[] points = [(1, 1, 1), (1, 1, -1), (1, -1, 1), (1, -1, -1), (-1, 1, 1), (-1, 1, -1), (-1, -1, 1), (-1, -1, -1)] + texCoord2f[] primvars:UVMap = [(0.625, 0.5), (0.875, 0.5), (0.875, 0.75), (0.625, 0.75), (0.375, 0.75), (0.625, 0.75), (0.625, 1), (0.375, 1), (0.375, 0), (0.625, 0), (0.625, 0.25), (0.375, 0.25), (0.125, 0.5), (0.375, 0.5), (0.375, 0.75), (0.125, 0.75), (0.375, 0.5), (0.625, 0.5), (0.625, 0.75), (0.375, 0.75), (0.375, 0.25), (0.625, 0.25), (0.625, 0.5), (0.375, 0.5)] ( + interpolation = "faceVarying" + ) + uniform token subdivisionScheme = "none" + } +} + +def "_materials" +{ + def Material "Material" + { + token outputs:surface.connect = + + def Scope "preview" + { + def Shader "Principled_BSDF" + { + uniform token info:id = "UsdPreviewSurface" + float inputs:clearcoat = 0 + float inputs:clearcoatRoughness = 0.03 + float3 inputs:diffuseColor.connect = + float inputs:ior = 1.45 + float inputs:metallic = 0 + float inputs:opacity = 1 + float inputs:roughness = 0.4 + float inputs:specular = 0.5 + token outputs:surface + } + + def Shader "Image_Texture" + { + uniform token info:id = "UsdUVTexture" + asset inputs:file = @.\textures\checkerboard.png@ + token inputs:sourceColorSpace = "sRGB" + float2 inputs:st.connect = + float3 outputs:rgb + } + + def Shader "uvmap" + { + uniform token info:id = "UsdPrimvarReader_float2" + token inputs:varname = "UVMap" + float2 outputs:result + } + } + } +} + +def Xform "Light" +{ + matrix4d xformOp:transform = ( (-0.29086464643478394, 0.9551711678504944, -0.05518905818462372, 0), (-0.7711008191108704, -0.1998833566904068, 0.6045247316360474, 0), (0.5663931965827942, 0.21839119493961334, 0.7946722507476807, 0), (4.076245307922363, 1.0054539442062378, 5.903861999511719, 1) ) + uniform token[] xformOpOrder = ["xformOp:transform"] + + def SphereLight "Light" + { + color3f inputs:color = (1, 1, 1) + float inputs:intensity = 10 + float inputs:radius = 0.1 + float inputs:specular = 1 + } +} + diff --git a/test/models-nonbsd/USD/usda/textures/01.jpg b/test/models-nonbsd/USD/usda/textures/01.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b24c7c708eb53fcffceee78207c2b01bcd2dfb38 GIT binary patch literal 10290 zcmeI0O-jR16ov0=n!iWUgod<6rNvTBi-qo>1EHV`aO_gtgmcHP!hs`K;LpUJ#E2cF zJzS(E1S}_+ymtsWFJI2RuW9jCd`Wb(yk3eiBBng0_>?R0+OFreJ>wWe9 zmGShkGN}&3+v=<`Zaq~dYr}`Twdvmzngb(Z2P6y}AQ6@tGEo#Z834m314#Z`lW8Q? zstMp*33UdG<{D+ti5-yaP|pk;P}Bql4#+TYfJFb5%2ESZjg*vX0?Szmb*7r;8fDOl z9gytMOc*$%s3;5^f>HltQFku}4k;?i{s*p+C)7<8?sc|Nnro0zud$gBJL1(8Vh1wY z?_LZXKvV?=4$$mP;2L?istIsi33cM7xkeduVh1GZ=a0UDFmOOo6Bsxk!@vO&mKq?@ JKY16$`!{4&2?ziH literal 0 HcmV?d00001 diff --git a/test/models-nonbsd/USD/usda/textures/checkerboard.png b/test/models-nonbsd/USD/usda/textures/checkerboard.png new file mode 100644 index 0000000000000000000000000000000000000000..fde967aca605b21710638f3049ffb43924cb094f GIT binary patch literal 7688 zcmeAS@N?(olHy`uVBq!ia0y~yUi(1B2{d zPZ!6KiaBp?9u#CU;9++3`ET2x;LIN{m5Yw8Dj{w_cBFZf%m zJkT75{|XI^3=AFu2Y_r9jszen#9{y>IhiCF7#dg@c^DWRni$v^7!({Cm>C!*j4B@u znbAZrnmtBKfNFtGfL<}Zo_GB+q30CIIWK;12FCg5zzfhJJwj0%s2$!LNY%?(5r1&oPDbidtQ Tws0n}6k+gm^>bP0l+XkKh(xEg literal 0 HcmV?d00001 diff --git a/test/models-nonbsd/USD/usda/textures/texture-cat.jpg b/test/models-nonbsd/USD/usda/textures/texture-cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..22870c622e0871e051f47977a51dab3d9bec3c22 GIT binary patch literal 50234 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%0A*%-6<9 zb4VkqS?;y*pH-zg#T{EiW{HyHHx_b2T3s%%+McJtlldhdx&tFUQ6K>0ny9RJH?q;{ zIvr$8FJxj?a&%{iEWK-A8;Fn)M^R*Akx^E2Vvv(`^-0M{JytbvZjB!3E$)vWDC1*}9f3j&D+p7w2G2L3zx3@g1>TotH z)|;AcoZ6@9+m4o25H%-GFfJ8@BV^G!W+Ae2AeV`^O~ezFW(*^=C`J*QFqji)L<%hk zffi%fn280ADVW+-A4k!cigp~NqY227Er+7zblhnuPRYoK!pUkHVquAPBe15G9YLCk z)FzZxrtK<_I9?@qE)+^rQ2YlheNG-$zPs=ovK3zWRL%6zqtG6V^arW!em|f;e^jiy zSf(W0;VmNPb;9R#)i&uub7b8zrV-^@)9jl*>Q< zEX`>0gec|OJ%{YAsg0NwO(q6+R**XuT^upZ`Y)rcC^cuuQ5Xpq{Zwijc5J;I$BW7C zs~#@)g!`G~Z=zl7`_|tDSnz=xO0F+XZd`W=J)Ac-`KUzh83!z!Ow^~LeYd6o1S_~ldhrg7$tPU|tl znoehVj>(E=d0YjQYojL%zU&{}ayd&*F@*qc8Oj~PUd>|CI;R5Bb)nL0K()`+V*}0A zwG59ICh4Y=)?ZN4>e%aKE7+=8!ZH`jX)E6qtmSV8vi6LW{gd2%1IKCRxL((ESlBhJ zi=6tW8LYGscYRQ;+elan_ktBwCZ@?PVxJXkwn|G4DBcKO5d=TAHruX>hLL-zY8$b9 zE~;&hsu-AB>)trMuBBskVXXiui)@rLj;qb69a7LfETv?lXB^5SX9X>R*rx?}jBJKv z2V<4NASqk1qd6@*I|yW0=aTmqMcG^~4^$5-D~Dwg`zShH&^)fY0SLS_s7WXZS!S|Z zRLV8);PW)9N~)urt0|=8X&1Vou4!wJbscHZoR*$qNy<)1E=}+9Q8bkTOH8s(g!1nzot^MoVfG#Msuv}?2@Q|%@arj(vfj%rTly- z&n8l`NG*e6iR2ygn};;*-FViQ03^-1s`tEu%|_L zZ4SKs>~iVyo@_j9zoKH=tAo0J9!bePpQvnRY8fJ|hkhuH&%|z(WKNCTFKJ_j;ENE9 zi0YrG->S5x7#G5-roX@|YHS;kxagWWw^p>nY#zI8tm%f=3c8eJ+^b4qk$uxStyxon zi*r^Gjg68piz^rcXi91!^G!w&A_=I%Xag`KG$FASsFX&jg%+s?vj=u%?yIsAYu}rs;N+R+P~VL4jN< zAg>abg{yxB-isku@40KM58S)aW9rY|IX*z6$^+3Ja)kYoZEioMCEuJ=b3bdvX4gv9Q=KOx%9Sxa0DL$>Z{_=<>Cise69# zVOU$p)U>U%qq1(p!wAYvW6%-|D*A`m;N?F%>Lktmbx2{i+_gc-)$`a z0A_0(PrA$8j^Qbyvd#`S?PU-35~;NQ%u2#X6s~Mwd~Sb5K3az!d(?b>rNPsZ>s+Z* zn>3ziQaEvU{ShN@;Uf08RpjgI=&?N)nBCb$rFFR*o@i5Gn_}y<&Km%uT@hgmyo6t5 zB5X*&vYb1Ra-k^mUdc|JHYHlqk1l#D`e@Hft2mfITe8?IUR%u=N+yG2h@^+$eedZZ80M%TGOW(2uyCHQy+@X>BiD_ zM`b;+Yp zn-yD8Np_zqX}I1BXQ_#}K!--)D!rKQ(C(9q3N2=D%gD-Oi&0%+ykGfXVu z$g6r@U40VKK^(2)rgg_Qozsvt&A|rgxxD}@Z7B6bakPCGs)w9ksvN%JeBSCjynRAdQf@AjVB&R& z2Po6-cHaw>t>Sp+otI4b@%eLmUuhB9pyR#Q&%e~YX|V zL5yRkJSM>oBbiB57LlM^_bI|a0@nB-K*4Q|#u0E$zIi|dh;b3?vUZ*vAyYFRsn#0{ zbX%~oGSCLdK-}$hC%X2{lvv!|20`Nc1=huJvN($8zchA7VZS68hk=r8hv42pZmQ76 z04gy<#O=OmEfO-t<-(M3aDsV-VCXZo)Px>Wgtm&U&AB+wH6X*!Df3O8ukcfCI!juR zYYl>!ZHT{4Y4J~uth_1DM4f?H+hPb);V#JCHQ;vjL}Uk;OWSl=j5tnH$B?eJO+!!FI}{cJ2{h>A!B$iDOtJ*qB1UXV8m_!#n_xt21EO$7yTG~i zT^Y$w2wKG>RZ1_ixN{JsU~_D_^`+)ndWv9ga1}*e5E})psfTT|MUq*6m zw>3vm32pGV6|sYGLeAGyfJx-7ji+$S&ke6+o7#FSnyAmBu92O$SI(2USIFM>za%14 zlCy4UXEX!osVH=bHy0VY49&RejbrgckYJ+SD`yD_J}SbZ+>#P*s|iCyY8snUr%Xp| zT{j6B*(Ac5jNaKz5Y$<~;4^=!w4{)=*#gR%7i167SW~{lTPl&uxYTLY4q*j^kBZV# zRI+A{I|J2XDW$O&=gD2sR1iVG)fY95WJ1ZI_qpfsgJxDz+9Ht7+d&}%FRDoO#Qw>Sm@B68>6{u}$D_XZ?y@=9| z(t^N@z_)4YQJ7I$0;$#Q(%)3l`^ zed~8l(v^NPaxSSh3I71ftLYBPzaThIdU%`Q z?)zpvwW^x6!fBjcbBmpoZkzZ+eA?;%0BP;u0kzIj8h&CIwYJ%L+_$~p?$!;{Zz#%g~_<|!V`S9`6FCg&?R2t3F*}(K2Jv{E?XyQ z-Eab@r*+8rD>`Ff4gO^%RxH?T!ES4@pFTp2WI3kfuHMA{mP$P=Vv@t)r{jH6F}RV0 zPzlAmtQ{F^%ac%xr6OW}oD5$G0jT5nuDn=aDKXf+u%hod0Bz9#cn&OOHv|?NWp_R@_~S zL-63iTA)ZMt^oqYAy{!J^4@&+Px8z@5}BDjd(W9!YcTWYQJiC!<(~=wj1csRhH<2j z@I&elFxJi2LhyRKUXlRL6UiM`^;gXFReUAtO=ui@eC((=rGcmfYjO1R*rg+3<%ELd z1C2_2^EA(4JIdT#D^z)Tq>_D_R{j11AuLlCU$gIG`vs9 zQ&}qUzLZ0##3tqKWdz9?VTt26n5|!Zg5Z^c;|J^A_}}Ns?OBum%BUbVu32x_a~Yi0 zv>g9QU^euQBzRRv)l!>P+N4W-EEQV0qAIPEVOHv7A7&C7xn9V1hL!S_8*STnx{YcI zmb8@TZhyEcS66zTEbBb|A`KoDf$584Xkg2rCd$4eEE#cPofUJomzLj$zAf77G<+i^ z$HVcmL3HOs!SvJ~ikF@LNHK-REBxJ0=w5w%7TfQFGO^thHi=4r7W{uPbUGjNBVmTY z41I1RHtyM^+CosdE{GG}hxW$?|8Qu?<(JjM9^hfzP4w-6{u>xYN;a$A&|g@LMxLSxlyJQbg)!?gN{h^2G9*U1V!3ypbY z7Ai0Z`=T?U?ieqeA>T-b&8**(l_is*n!ac=FfI8_#X*&f5T&Zc$AHe9~?y7a=RUV(&I$D}qK_RKmazc=Cfx(a-%IB)$Br4TPRvwz<~ z2?Bu?gj$Y9mT@oV{MgvBZGO@NmoV@clw62u?gNXB?A;^AeAVRK^l1zcymH))CB~6! zT!7~!OAMKx>{we>mQfma<@^zV#!9E^k0MR+Ny#FosuEZ>bn^?+jq;Ow?xVCA1C3{h7Ezw8{#)ot~aW+#zJDFzE4h@U>D1jLitB~up{s>PjqiaME zHzagm_D>wOTC?kmhSrqL*#gga)(mbVB`oUr44W4OIQ<_vNU)weiOWs#<+V$rh}sZh z+qDcYjWJU^j`(vU%kWaplv{)dejL)K{T;QEdgJnOVeKG$-RAd&=1P(JKUMe6p2pfO zUlC)JDwi_I9lwK0lN0`!zeZ)EbXdtq4w^gdZ;ayzoZS)YJZZ73s(V@7XwJ8=1+dSH z%e@|G+Jo4?{U(yD0%*C>$qiZjxb}8-T!hvp07BkFF0$vN3N=L&s#g>`9OctF%aEa$ z!y|!}>6!I;?AyA=Eb}hzqMaH;+h-EmO=zWPCMsKNa)4*mj;G|)PjdAZS#EGgor>aj zYSQo2Mp2cgt2MEio2`57=#|}a+W6ln{eAuob}yF-oib5`9E{(nh~GDT#Wv-p&MLoL z6tu;i$js&HDva?I zUe`maSQrEg;FW1bMn;+B)S<7WvfRC`0su|D^{${WH_eHklD8-uE z_sc}Pn2(K#Q#zmaUHx!iVi@hz-^JP{c@7+lm)P3SIXg|??SEXCdPYh6wbi?nozGtI zAXxInE8gBefPikD|@mK*>`YG=F@r3VZQ~BDcc1(^|J*{Po({Wu{=guv zwQsM37L7}#_DNI|X3i%t(b!!z&+l}aj4kR?e_R2DT7`SyHafL5Y@BRWi`%O^(w_C=zT>a!e~cN3SJm!uWOHDf5@;;pNkL*hy;dBHZ^7?Nc2JNLHkffRX# zu$X~|%rr9t*Aml^ORl8$`hcGlkE3O~qN^HC>-=k`Hp7<0#tC>(QpV1oxy9CSYPXT^ zaSTHDE?t{OG}UcvpeO>p%?||Tn|dKghmCb+0|^+A%z!pcp-zfAt+CTkLBBEmZ>tSy zV{UxeM+w#~UwCr*ksaSaM7|wge=bRmqdp8zjR8<>mVBoMcUnKEop-X8)`P;DT6PSR zP(iW7`NbJ*?3RFIF3)uZk?OAL*Y{r(X}ME}*RHf?3R1PuiKU(l{{iPf{@T0~$g)%z z)NQOptm=)#8&x2`TLXtMuvMO|gv|TOOiAko7T%7e`m>r(Q`}*vX;tU#Srq$+X1o_y z!A|b3&#VsdA@jkgXR1tCGP?2c=Psu|7aM$hZG`p;|t;N+Q#^H#nN z0a7^K71d~Z5OcrPJ=Y%mov^Wdz3zkI=EV;i_3y5~)c1Zxp}t)j3M~VU)nBYv<}bTGCj+8YJ+(iqP?tOAeI1t~>i9s~1HWk4FPnOPf+L&! znuT2SVm_d1I>jg30bv#GD^G~QOHs>{mabI90h+PXkj(s7JnNgciaCXsUc1K=Z4p4$ z&YJ5XzPcEZc@%T~Y2{GZyGrc(gOz`}TNP(2?Vch&l)&-UECt>s4MokF#6tRD;9D;T`FY z>Z_YOnf+vEU&FDwmq-2mZP>;~kE{(|DNxfdMw1;SWeRh}CW?YaeO>*uRGyqfJbcW6 zbfFP^!Mb0w+|kxljUDI6_p}0uTl+~W&Q{9gzFP7GqtT9pf=9qVCYuM{dR@<7DIv56 z2$u8{xom8Y-g8g$B*zke?$MtbKxk1mK^w#H5Q3v_Ut`I(`93|9P*t!{;%i`U`571K za%NJnocfkcdci4>8*sA9t;v%=h4m&L=t`4ONmHbL(iE=9?9y>e2t|G_C{2y4UYQn& zaHp8up60#-`4}Yi6J~toFtIOG#f@cIPbn+>a!SF z#R1$Z$9YV_C4F2|GcjbK53)syFr@kQtR28D6)l99HxrDTksxqTq{BLxCwligkdT)} z7wysYPQt(|iF83RhKuf*jFT{KoSFQuo+7WI}dWb4f*D??D#KqwWfuui|>gZX@4tgI+c>gZZ zSxCi23U?+*HJ8stL=5k}hTnu+3%Z#8?TKpk>MIHr!PSK6+(Qv?aN3zT3q~Rh*>BLZ z;mDOh$kgGf&l-~p8sP81n)AJHK|e(ol7K3jnfAJ0BAYh(gK1N9JLRU9p}CFoL-P9e zFK7vkstof?<^YjI2xY9E7P?4Yzvz|1*%wfCe=W8#4 z6dhlkOQUwu$whv7GPdQt>)f7O`AUG1?_O*mP|3l*iVY#nb!!7Hx?sf?cb0b z!B+6qU&mnLv*f@0n`Y-JuVSV|0dV}mgnJ#ufB_n&f5fZ*t)m-?^=3y9<5@%<`*S#) z4(Fhzbf|ijP4d{o+V5Sk%v_1^&4y%M^Ga^3aQj;@pS}z2E=QAHUJtBkH6y%a1?_^ zT8RknLUssPSF_&ipc-TdM4ouR>O0C|YL=THQCh=4@@H7XC4G$u2K5^=EQt{kI0#C- zSFvs)%#5O13NF>JMEioxQ3N+%v$cnw#bMINK=-5;aZ(?> zJ0);m4g~(m@RWv^l-bsqGBZ=L@bdCyr+H;$_TQU(FDl^Nlg@qbUbVF*Mo!TL(atnm5&F|DYN)v3ngn48AA4mk?6*`>G?4>v8+@0fNE<|(cJj(NM3$Ia#C7gf z|HB{(xv%Y}rVB7ha@4*z+slQ|9tDUtpD^s1 z7*vsa-TetnD3xWK*$y7`dJ%~&Cx%xo6k$qTw`7*>o;!E}cYEhOgY-!MieBu1ZLg=kFmZdIXDmYGtb3G+JHa3oQVsVN zJ83+G?r=$SMvWO4AIJ{c;GoH&7GQw#gBi7r@N?4yHu(;BB7?WmsSD3`OyF<%esI0% zW>uA#el4MFgK=K7>_jez|ME9csIV4P;$@cl9_Ci4NUC*hBZ5p2bGx8=Q3a#h1Exlu z-z;fvD@ z5QnkmQ7Y1w-^#YD)&%TF5~WR@{be!BOc)wU5&YAXlMyzDm(gtS9?p9;PA-mgc zf6|=1xSs3SYLzg3f;)fyU0UlvKsUsJA4pK+OG}k%ot^;f5R=O+g*8B`e!>e|ki_%M zFW)50yLz>VM}81N*L>=&JsMI7cUi^;>CCrKU#RqQ7NlK>k|MApNV*G6=L5TqFB#Yq zE--8_To3;`7WVZChol`H@76;O zTtC(?ow^=_Bc5)+_YL{4Z{1RIw+q#L+tuhD`ZoRcn+0o`jb6hSoEx+B1dTXi->Q=) z20_jyR#&^u2-tBL@7YR9(ZzEWrIh3d$&9HceJ~i(viq z5=H(s**oC%Z^zpI@?&k$I_j6QW#O=EM%E{mU%^Kn0{t5DlYuY&KZLo$FIC8#l|j-Bbswj z?_c|8akUuENObs+qG&hUrsxcqkPS*xA@4>zZOENP%7L+M>v@vm@o0IY%AB_Ij#5RC zB}Ykp#Uk7^@R^fSJG{2Ah7F(w={~8khpcmmPYtBapQgvR=Iy-G)$i3KnL{^k>Xn1b zjI<3yoZ3oCOYq8=^GHIkI~QBw>e|Ga-}l%xYc@FCDm}-0qqWMuD3BdCCoiPMRTztX zZq5MFN-WF5TZ^OD8ApqSA~R@M7FbLNC>>-=qJEgm3|Q6CERTP{OP_mx!T$a$_(&dS zQ~rG$uAS1YnRjEhl^0oA1CmbJEv4XEe}4b2AnGno>#(*;6fPIwqK5aWNld2;{0Xm* zp1L@_cb&G1X~e;7yAV)yE1=7L_J#A@8!ZW>5{5I#+rlhEsnEA@JvY#~eipq)?7m80-u zbJqz}E~G54{_|mL$JvqcXvRVZ$L#w}_P=B3hY-DMSu~emKLtm$J993wk1JV!a8$H5 zPEbN`iX#-oh>Zvb^>t}eb<^kU1Yb;(&El#t=EhfW3l6lV$Twy=Dq^FenJ*#>7No91 zE+UK2ZyKLjpEwG7vc&31dQgzGK1lfW1lb%-Y7s-c1gx=?3NG7w{U(xf#9;Gv-9gWe zJvzM32EoS*Q3u`*RM#M(pqhX-kdQpf>tGkiE;%MZsQJFqo}M{>9kM-t-Ca(@BP{39 z^AAb8m9-zXg{pM8#tVH+Z5FaKd3Kg|!OGF+{meOu&`S7?^z<~qu3 z=A{rtO~=R=x?HjF@i`H0U1|L5A<$6Q$+L3+2P**)H_FfwKj2HLd0x!-7eVXpz(Ln# zM4#fTA3rDo|5VhEgPzU%ee^{)Q7yJGM(3Mu*x6{xsdi#5A_cTH7VZ}%XQ$(+U--=C zch_4uuYW(NqU17rc4a`MBS4AtB3?VVeYIQoPy6l3y)Ugem)Xk|!4d85>2tlv(M;G1 zz-h5`V1{d^nEOQpPr~z@p@$dSNi^4hDBy;&$-#OIN1?mj_msh?h_c>MKC>2Xb&hX9 zgZbxr=Jt;!>e2?ZPrs$Uz1DsEGTb+h`!-X=?RmC~a=*9;Fcv4ZLQh=SFPDi4 zUbebRQcf~NkJ!+J@!MmklZ+Hpwu~L4}+$NRcIw)(+NY{(QhhUAm z#tLvmOH?D4Q!l4l|I}x3z?}*??L_H4vg(s~KZ>E|}4 zofQSE2zp+w&xf%Jaazu^WH&L@t*vf7uc~o6S6t*o-N4JLt2TvUiB)RMS1;EARC`ju zpFMYFkVKT=U)Q9@V#E=pA)4lrZ|dV{;dU`^O1DMC*`b8oN<5~BchzM z0$Hx+DuuPZQ>*qRK$RME$H41?7ktsZHN&uzEjXf;_6fpUh-zwH-LCd;r>;6w z-(xKh_AsQ@bw|Eq&NEj~{a6a=^=bj{E&&{!##BWBe(E0XqXr9;&O7)=BEyI~ANJO7sVZ1hE7$^5QYKy+V3U9`$T0;fFG_xPEPC0cztY z`h(Wb?|DMcLVKSUA{P}F@rZEKch~Q25V^_@a-tv`R0T}n{=#7tiL-Yu`0_$2?E zZJFvwC-#@(fEIh5-ucAQeM7uhde`aB<_18y*d zcF{@4RiN+A7g~4l0B2Y2>?hGqMOWEfy}Q}V*QjKIX1e$GqOxyHM(#dw^EK2QR4x!X>cU)> zCB8IUdH&4$3j#ya}yQ2l? zu}CKXu-~X+h7yH)`>aUgkHbU>G`5)o0pbRN)-b>7B_z{e5(;Q4qTxIk4%o6yT6I}!UvgK&Ka7{OV+emb6#&>P{!#*QE zZXDWJW*2Qvv{bQ?xBwaw7h1HpA_rW%q{rLw&ssIi_=@qsVWnOL87pOMB1l5gJ&u2I zL_y4KZfcD=Ox)+?-8GwX22U}L)NyQO?&YcHA|*R(a{X>tXg1;J=C#=K!hhY{NK)fS z*MqB^z(DF9)*oNNKgOhSh&LP7Ma{A&a`lkppn=zTd|R=t`{mg zZ3~i%?xdrS^e9vs`LFphWeE<57hj060kJ3^uY6)R{j^kpz2Y&9$rXVa;UdQ%bz_*Z zyYEY{IlU!9lWMhYl^ea6`mU&wQ9AnAm-inFlQ4B@%Ta2Ve^rBs6v?S5s^Z2b6%W~q zT$Ev&{ZowQjLuu{13Mxk;?`3sibgJPH!@WhcVibHNVt6in1pWoKy>r_i&Vmo>4OC` z-LfA>S6=xd=}2!Zz}v5H;PQj(?C#0&*QSjJ4$EmC@$k}k2tB<7k~jnF{!urVAop0h zegf+jhB)xCo{n$G&|e_>qiWJasz>>I$_rbr?bYz z`iSp!)TaG|g=vKNng%3zLMESP%+m@MFhO&w&XuS{^zu`qP%S7iQQ~+*C^qHukXn6k zH&1^WTgOS~;ljpr4DDa_P87eIG{8^Q1(V}A17Yb7ENeD7Y#!?and=Jg`;z<8+9`Q? zlvmC@W0(Lb+U^Us<`W|I#XUk2rPuXmDzVqsi1IMheM|PHKpg0RX?(^uol_ddnrZ6lc51f*T1#+`r&%B{%nc zs$Qhky}^~YniA*C^$x9aSP!kQE(q=QbF3`|RGp{AjCRDPQ97l@0aMZ&#uGdgox^0I zH~KDJyt5A$+Nr-1pS@{-*1PmfQ}dycWOoc)l9`~U92LLg5nZL5Yw8ALXNoS6>So3^ zi~ZZIQ3r%F=)H)K5h8_}H<)R2&6$`39a9*X^-26pe|1;4tLUA7@*%pfM@o+P(qqo$ zQ7SmRs&fj+JR%?L+ITtQl;Y%=*3T+g-P)!UWQYMRgG0rqVtyq$y`V`q)7`&dhi~Us zf&qvhK|e;EKEmkss~m{s-iESyJ&+fB|>|S0(O)DE=O)=h(EyzV+ZFoU1k|7chsxNYoKm3PLLR~o&QsukG7LSk% z9v1eQ(aMup`aY`TE8p~2^x*iy+*P+jR21Y^xEB=29@7rzkL*9lu!AgW&c(aaxi&&W zq_N;GkPQ(`X`gx4rj^4$i~7Zu=R4LcMjjoU+vz4hqm%f%E3ZSz<2rq9QjPW&X%iH| zH(0-Peh+YccjL+OZ5$=+eiN0xJI*emx$fvJ%p(ZlA%9`JW8=>RST?{K;>w)E-0}dX zm4HVVGzXZPn@|~rR;C%RjdxPTfUufTSYdT@@)fr2xn_{~lpQ9}U^2vM1yLFLx0tQ9 z;mJ~YcEuq~63uust$Jg_-YjWU1I~xQXVGmFUtV#b+7$EWEO>wcj1PXxu#zCsbS%(P zIVX#V3{+*z2I+Zl;0gK*=~#Uy9kdPks^{s|;RN+6b#A9N8W-xXugVHy7}bYq1^iTB z?)|W!8Uz{EGP%E0f@B3Ffs@>c1o}JN_YUAkjlqpw6Ao81v9`@g$X$(7oYujJ(DRi~ zSs-KQq+qYbsj@lwW^nwKUk`k&SzlP%ZqJEg{6_BHWvhJfib7SDuXA$TVAMEhZv%`C zGKvtV>vh<*s+_2*a2B&mQ=@J+FlUR!)eENdMYWX63y2c+rbC2Kn18}3Elo$ zkuSbw!EZ{PhUOdO9@WD|_dSB9L8N(p9{4%Uz_qw)5$|#szyXw&8Gg-)tv|91(4F~J@De zzkOxpfvb#;ZOV&53THd#T(4f)(tF^3?u&fTy%fY|`I+xwo14)^>u~K_;$eKJCmtK{ z37g(Lqt2j}Y=GsH5JyF-y7o_PU-~2=D*H|mgJoKygyZd|iv^Fge83DUg!E@GkQG*+ z_sivTyH>q(DY=)MTCyqZ&Mohl)C6I>ZqDdW@coV6TISX-uX^>4GW8+%QC=x$)%9r(RHN9A*TH&pBRJw^ceBwc zR7tHjl9NTt1Ly+QR>z)8ug#!DGTxm=P^f4woXI3#OsF6YwONB0B|FO3`t^6o*M52@ zEWV5YP$ntT<5x1ueFEI>LFgk4yBV8b*?U@?X~7wpkScOpqlEHwXKKmG7ZyZhvRWt6 z76}r{(FKbpx~5uzjKVpi+}~0=@gvJP#hp^QQd}iqZb>vexDvl-5N(1mt=fN%CcCf& zOTBB$<*Gd!YNpy#(3wi9$MS};?id#VflaiOVZr3>#VA>O`$3&_XR094A30cM)l2+_ z$z_UGAF_N#)69Q$yLrcT0=bgP9)*X4dk<#Wch8mQw$RB4k-64Vmf+*Hm8qeX+wU2< zi)erkJsU9CD%I0xGHH0MYc72AE<4*t!DRqa_J$3T=M>a1{ZMEacGa9%l~DCeCpIm< zx~=TWbiAA5xQbTSR8PG2!if-QFh^H)*B&mn{9Pj5U_P-%9Fb(k*yfct-u;$*A)ZmV zox1j(RZ5MMh5c^oZ`_$VQpJ!C60f5B_FU^QVBv3ElSyE;aN`wl+*9%-e)vuIp}*o7 zN6cEam259eB z*W|l}a4eUdC=2NWXO7#qJAHE#%gfR}W;(yV;g~S->;A)zD{{goR&_~UeZ~5D;ucyS zi-wTkcD1Aa9YC*WHZB<^OND~@mxf=0yeWgwc$jFiT!(SV?1q znG#NpLS)Qcjqia~%c{jKe1ArEyj_%BE34tQ!OpWMT0!Udeti#)Hk_vGFNubk>^Maq2DsMSGcNFboSMGbO z(6p#er@09{@^`BGZYiviH3zQKd0+hYW0CK={+2$(1r!OmCA@p6*bi6mJKy*$1d|VF zC356`Nk?BI5Z#{JYx#h)*oS-iAI4-shvD2%>iUlPTT|DQ4Lwge3s-3qsY1W=M`G%u zttR`8&$2LQ#qQ!ax!&$45wo#u^{&!3^v&lj8|9?8T*uUJuE-DS+F{*~V&8*0KsmqV z4t>t61>u=%H+Re&g8%YY-&|FL7uJzD0KqnTIC;moW6$u2{j3v@+w2zw`i z-xMlj37knGaraoi+t^g{zG!Rb{WMVU582o|h}CZIcR|rJ-_I0U;S}dDWzuDC=KsT> z=ka!m-m}FX&{Ku|%e#Ely#0!B$_DEJ;EqPGubf2hqLMOUg(2v2Jg(uCJKl;eS^Hp! z%nw9(e|f#o#i;XcpH2D+MaWM!KEs(WUz_hddLD>N&#Tc48qRw!w7$rdKM37tHkAB_ zf$i|nb#3uPG$l1J@uqNH;)(opN5t%jGLW~pOO<{M6?M$@37&=6wUiRKY4XkE{emY& z{&0}+WLc8*ci~dLSxcDk*7(T`fF9>*XVY?59HbRmJ0&~P!FC|%m4`fd#F$ah5hndng;eaBZy zE(GLF2;*17ifb4c{p!8L|6xqj@`+w>V5NyYzNvN?PhOo~9t}lMj zDUnvMTff9KbSTUclrU+lb-5LJ zqdXt~0<_87BkcNh(_eKfa7`hR(ogPd>yW3|8722=*{1D3tm6lC8os*eTNNYYl@wT` znTkv1@6q5r&F$pq`X4)EjQ#j!>)Gx6MDF_RL>^!5N7DZ=E=Urys+3&WS+`%<-ZN65 zb350jlg$$Z8RMmtL%NETk{NmLXr*8Mjt0xkv%LYeMS~UWUhUUWQ|=PKwC1(X2LYFf zr-^h4vJJk5mt%Og_ zmvB@8_@*!dJ-$Yp9OGs_xWk-mtK{Q`KT*5QgB%e-tQ_&Q)*rVr+Htp)+i!moW2K2` ziQ}fmqP*IZ(d7vb??0zdQTD)Vr_SbuUX)WH@!>qcQ)W|10}r5Q3!}$qBFB@Ne|O#VqyN;vvKDW08QDZK$;1 zZTPR05T~2V|2j!M2Z%-qLpPWJ-W*@E!0q<=_HH~KW+LmUhJ&|NIs7DdZ>`oT&9*3E zhJz=an>z% z`;4kFDa{KmfUO`ND5x{V`mK%|cYP#BjI;fAF}wrkhN;sO9BTya>}8f4F~>@E-=Q zq$^5bo@a3>kg0WZ~IpOQsWY(xz;{{6&-_f2)G*HR>NIeU? zNkF>4&;Jyxc#8IK=A=Yn*j^Hn_mY0we?*vrA4x+BtJQuBvh4i4X_@L=tW~5pcJ8FmFC(4itxk zOW()f^wCVmBvrukzcJc_7tIldiyNhQt^!U)Kp$Q}wpA0A6#J9&GVAW1$u> zOzu{7^4&>fx2D^Ul$Y~i208m%@oP~%Py>N9%<+aL#d*TiApzNq0-t>7X&G&o9}Oif zuGJA`;`T!*RxeJQ|Kn55nhe-}k{@~&Sby6y zZ&d_4O`5CB|CVC2dBr*890)n3@#Y-zoEN!_+P}*RuF4Tnp}MaUFWpZwOAgki@?K@9 z;!i=V0vl-KL+d5-^+t^+>Y!1Nr-(&%mK~XEbyzkj$u6!ClP88$7(WB>B=C98Z2yOm zB?s=|*Q?KXJ>&N){UnLcc5HUL6y1KFsV1)Fy!!b`#Wx^)JZl@hM}#bljuS(Jr_S9m z)9i-45=XuJ#z9a8C3sSrKQ}ecNL^MX3bMkHUEt38A7{nQ+oY%9Qe6hVcrjF3U}=9xL3LxmaKX#;6~XU77#h3svyzMpww(4>*LT=p~ivw>;2%DA|}dL>Ehx_6Y$$4dKQVdGTeQ z>uZU&+#80BxK*!nG>-Qvk_sL1f_s6%AAir?^h_1#XbV{!#H_AE_hR6W=!HxYReh9w zzrv_D!aE6OF^a$l7bL#0Yl?_cz$i!{pocyhmV%ZBo{9Pc!;|H%-BZ#&jYfger* zH|nD@+nQ8(IsA5yZq(~P46T~{p!F*wjoTcg!YIsaGze_AbtzwbG#P`I!JR3}gJ@y& z3r|ht{azbMg?i)wE8;dnoNnAd+iVLBc7(eZ6Kb#@oZxwB#*1S~it4cI`$gkdYg zCp}w`G~G7C?$77#$+z6WA(7t0Cy@BVsABHvZt|$lS<=(%A+a^X6NiN|3X%!@ZTHGP zr!5yCIuW#yLFjBYJMOjo%!I_&|3HF2Lrb^ZGy1;&u1iq%d7-<`Q8R>su}&Pgjq^hV z9^czd()WG_Ip%o(QUyC^Bqj9*)Kj@})jU{4djCRlPu&*rKN{j!(~|{LbX&@0?$5H} z7b%jmfr~S|2cM!%0@5rP|Au&^lYlwb7+~t98u^6!B+LrMVD$*}*J>j1TTjiin^ZJm zF((Df|F$Q0&7-cqT^a?2xwv(+?FGHtb&Raa@jE8pg><_)4>_mgm3Ijan!ZguTzBXN z8Rv<<^B(Fqg#~1{-YYF|Mtx7^sgbleEEExJSIRM_x`4&xCkNbsHeKqe0*klZGFgKX zo^Yi_^1TuFzH#<9;m#-7Z*2SrqK z1VWrw{blB@gf`dzblCpf&EOr`HH=$u8>iwcv0Y)|-t<9u51tUa{XT2(&r!RLi_!=g z!L~Pha4X#etLKOlmDrvnX(H-{NFM%`$A_V$*?eFG;}kpINi(C`WdC@m$oGRpHhCy+ z;QGH#I_%^t&e0MTw!oztRZvxq1j3#CT+|Y&JgAz3Ei}N06iI1j`LSPOyYuBJo!>p< z1^4kZJ<)}z-C-m>1weoYZROmXYoG9!P=@#m>#5p8fnZx?jsR(aZc&Gx&zD4(JzT0+7q6%uD z(xYSin?d+)|38d4B-)mgf0>=>oQo(wxZPTiT&nozpg*J>Mn&jf95i%-j$7!$=6>2F zhq@5BJWtoKRXE4*l zl2h2>e!t*1G3zi5DZjxF$owuPwHAzsVzhJbXfR)6aq{-`>z6i4M9G&MwT(`1ID; z6eomW?o4I@@SEuLGm7{)u(Z9e}?S+1&3m@5w7VaDM z9=udV8?GXZkST=me+ON9HvQ1^UIKb;Cu#8orUN&`FPJ_fWKuG--S8e%qY^+Wj3mkK zx{UCn^{(;1IomU%TmCm9#bYG>OrQt87o8LxkavcjEo0;vuFTFP>Ne@@;|t{W;*cD+ zSGxR`;9&XRi*bo#QCbJ`Y?J6|6Fm>Rz}<>B!2rD{Lod5*fg>NoPY5 zIlzcbvf(AoazWV#+j~n$ZYY{^M)*#B5pOE4S3O_l_FHn~VdRJ+a*-!VcKco6h8GoQ zmXaKjXE3frq4Mqi7w}{9I$D`5FBbjbv@z&E?=(7{5PI_#nWQYKcuEWy>KXcGGd)QP z+edFdyv?`doA#Gdf(B30w@^L9NVmbg*ly;Nv@MG*c3;*9%ha2=b2ZPxnpFp1RQPLhnFb*_byX64UBpLY`O`)SbPOpGONu>(GUC?fVARQJ5F*g}2N-2OCZekNYt z(FXhLr-;_Gr1Q^%5SBGYo&BKq!;vR~1U!eUDqyLk;m9>XjFXz5NOVYy$L2Y^Q2$1I zSAP~fwZEn$nkApQG1jdD7EqG!s{dBr@vr{u22M+uQYd{`eBdUPCBJ~a=&b(RgI@7r zw*%UM!tWbAw^%rb$NduA@T<^E4-*#|A#-LB&m zvW;_YdYf%+x5xd@3hJpydKvfLO26B680kdYOBQq^LvdGHM~R2$$hVt z6Czx_bBP}o^B<2Ey5WCPfy7jJk?{{9_|!}GLM~k5D|8-17pU=XVuf|<^GBfa5WD8M zT!(e*{MP6^lj84%?j$>|U#dMfkeBYc#G!J%b-HfTy5|*?F1>Vm4$`{i)Nj#x>vSEZ lb<3!)6nbvmzs+!!#llwTJ9TpFi^QTGx}r+)T)NHM|JfS}_hz^A}DzgQxVe;=OJnl(-G$*W*~fsI)vGl z_G89F{a%3ZnaWuc3}>TUiJ;Vgn1h&$n1{FsF&}X;!jA|bsFMYVMnn*?5U~hhI$MJB zcM&{-(j|yX5n;q-2s4Hzl+88~l$$5$Xp-y*O09@C#8SjE#BxMC;&Q|l2pW&s=Sq}K zztoo6(00_`wCR{2M^QFo=tOy9UtL&sASlHVR4*dB5j2J?5vvhbA+AQOL0p3 z&GV2SgHe`0ooCtn=&MIoG?}1H%y`zL%$Wtc0E%SxAFO}%=|WYmX)^M&JJjcQY};^P zKk~`Q0J#IE&&kQA^<>-SCXD%HWZM5^>(7o6 zUGEc}(y^m!heObbLN_!zc}fsS=S*q5-vH)5*L3H|*f=lhscfQYLLHUOddk06KIf<4 z_rE>Q*?l7x+TU52^ABe@)&XMO1TLUV>nT5oyV=Attuwsa0T?MBhRx>QJ)&Q75uQbwsv3~dz? z6B(3sXjVu%Ni^R;`XorIiEKYf8$DbxPIx!PuA~tE7GcgS6O@3F!$*ses|B9UA(;2-FPG44LSl$i01r4*e2# zCrgSBE!iFdE2cmeTCf96YKPklO-k#EWUPb{CZwqt4Ox8^2Cfq=+70!gp4VWAdJCyN z>q+7(aH=*KE~62rRP?)oqAGe)okaUcv>l|3982_(eo2Qb$j0jekWu9{zI94UkZw!_8EjFrF1WcJTO^3S z8OnVj{Ss!K+&>^`dKn05%;KZWQ+l>1DG$gxzS<`6kUui`?svm5hvSs(QAkWqk|gLD zQpf_$#8A@}z&y+-u|6q7bQf%hOIj~6^xv}VM!Eo;>;DY=Ua)Ql%X#3tk}P7mqbze3 z9@^?}m@2)J;qj``%`iU%cnq9zk%fJ1uMQf~jY?ot;O>Knl$QJL+*2_%t0f3Tu(X-M zn+7u#?^j2`S_b@f$iy;zL?bI6gwFxwbS7))Ax%okSsnK_&I8*q86>UC#>QC?PpFEj zl|zP3sjMP7f&UJ$KCn!MKgAL_BtX&wvZkgFa4pcP_78%4Ws*CDQ;H184cn7Nb2OgzCEayq4k!Z!3F|g+{?zIlF_F-IA3FGEkGZ)PSQ5w05bLz_I5TyxnU2Ix+}EA-kE*n7oO=Zcb3OjTdDrx<1_t~Mz?a~a+98xc1WED8nv&Fm+saA?gQuC3USTMl?|LX;J4Ty za5yX(;JsX3sqivJTq^8^dMMos85Hq|kJuRZrX}E-5v5t1YXe_0*~K+_g~jOR#@XO% zL8mpa20aYgV91fXogVh!$eD+K(7YZTuci`H9c^v1cmzb77>~!rsMr~edn|1t7DbWA zWdNCoD2ixc1spNf3Vu|$VfaJ%%6ihujaaRpTW!Nu$0t_jKdn{&uzEhWa_*0;-VdzP z-m})eW1YsoZJqH~>onV2)(hU8`}+K2{!z=(1+NAV2iaGaoG5=e^iqg@G5kW9Jrw2+ zMn0MHeAxQj!ov&MR~AAtYz*>Kt3aQU8a#qWZ4`~IN4bf16J_sqPy z-K(DL{M)Q2B2~L02ST6C-YGsFn*Qkg2KLu$M#>(J)IJonm+!gu!E4!}c?*9zZ_xwu z7T+JVm+g&wvSddvaBncc-V+RPcLxLfFMvpj&fz{(qMmiM zmO6^I6&zTQb;UiRxRel&$Q;ktzhVEfr2a@revChL#yWQKUmBh($$!OKPUP*UxQdbZ zQ2v@O=LEuXo;!2(W$W_(4eZrAG!aHjKT6))i6G6r-~&rUG(-hP6B-i9={o3 zreG&+@|nGFr2@Tn@^)a$piYRF!VqAVN>0(A zd49w@u4r2oGaCWy7+*&C!C$)^lo@QdUr$DxL%d teOuf#sl?S!sg`)tM1XdSFIBjH{|@E*Pc7(`mSD0uIqokFXopg_jBjnNe)WmlUr9yY7^C1a+N-Q zc4hyw|Ns7Ju^R|m2RnPxz94Nb_G2&Fd(-AaTM^oPY4f8k*PhIYKYrrHpB87yk(|iH zNh<6h6)5FhQYzp?k@8ZkmzU(#LKrwZNR~h;$y;)=SsbKzi;e!b#a^{M`(C;s3KX3b zos{IA*Vv9Xk6DLsEtuhpsRX0J<~pVU5Nk>5D!xw~J|s?;8SNg<0;D2fm%I||h&KKzks2W> zB{fW{<(H83tJHZ`()@(L#IG#g-p?hAm&K9F=pdyhMO%`RoGnW&mgSPSx6{W~>MS`p zNVf7x@pR3^w(}Nu?ecl`vz0HY)m7WY3V*7V)k`j_>iGVHyZYYswd;FVRaI>+uRYZF zs%wB$MeX{!km~rt{iW)oIzBD*>~)apc-!6Qj#@@_ydHGpb#>M8tb3VrYG|JGiR!<< zj!+%i zJZXKBZM`KgX?U&@3LAOh(w<*;*w{)$iw@dk)%5R+avY>Nzt=~ddRz3ga3ETNJ z=MS^1HF@Xs!A1RGxlmEPVyk$uqUv}>b(LP&`hW0KpZPrcqoV4abEWRZ>NeGKu1fyo zp*D4@)lzrb*T>X@akbSmHBWx1r=AS`Ew6$4TiCDp*`p6P{HC#`_SqVfhM%c6SsGT@ z_I%FHrH34>O0lu@!U&&rMfzc_ErnfpO!Z+vcL zpV4P(?os{!j#eG_sL^|?JgRZl??UtlvT{08GcO~lvP+)L>All1TT%_D{4?t(OY3ci z=S#h94LYn=JE)GU+O1RDsg7&gY*O3UHd^8(i-+x%TIi*#?ValJ+R>J;y1wsiTPRs< zy=)64N7Yr$bGE#*IjNSs3u+zJ@%U$xntWFD(eJ;^kowzZNVb7Cb+)uUdy4e5$Y5LJ zI+G?!w&6Ba)NQO}8*Ni3N+W1DRvjyip%W9;(c=c&rl`Xw4YbWr2T!e2-zopd@qq_U zrm2pzYuBIibT;{<*?j2#KF`#r6)sh<{h?OAl2ys!lRr3R{w z_lrJKi>i(feV!e4DE(5s=&}$j&P(yOi!No=?UL%)+_pt(VN-Vz#@e<^aW-`?VU%r`6m3)Y5k}bdN{wvlA;NIm zKFMlRj}nI24oUTG>IuS7+fk{GO+8Im({@5CU@7AM=CQ5RO$&|xTehHEmXc2Sw{2ye zY?q`m*F$W!50V3I?_Hg3>Kn<$_D0HcE?`q%Np7}RlFh@WzLWA@Xl}jFcXG*dexK*^ z%7y0G`}c0Pca-ERlv5^9Q;wC?5jK}$l;kzSHc`s)p#C3an=E}J*~Zw0+B__|s)yxl zP1_l%`YW|y&Ks%3>Cf)E+3rYEO*KdLw)`Gsle{eIg{sd~idA|1#Mk${leh1C*T<^Y z8Q-@B9;=1?Y&nu!0oz;YZDE_`mK3HQuY1HI)lvUe{Vm5z*rZ~XKfG+ur3w#J%VjCb zwpWd{eQoJtv#B&~Gb}k34o1*G+{X@9@O8?6{0IBX%a)`1K5=vT@K*JD?qthT-L}Q% zIqyRst6m);J3=Mr;UEoIsLzM9Q;PO5H`&Pi2m*Q7$BCts*h$HU&c z9#un6#^kDwp1Z?-WA8m*xg3bPR!()is+P-+lpCw^--&YL4Ro!lYUNAk6EC=wvs`_D zp}J~0ThOh6>Uj8V#C~?PzIwpzL*)Cz4PE5oC0st-Rm<%UyK5UVQLTUle}&p{V+_$iokT>ce99 z^F2LZskwo(MyihE)sYioiU!|$xO?O3IwsF$t!G3ESM}kJ^h)_P+QC|W9iPAH<~*6 zokdmemGS>uy&it=Y0ztr+dhv1ZTHonJ4I~|)xf`eY){pqj|$mds6NjghCTK>TC~u; z-0<9jhdj$&DQCN?R=QBhc3G`@rmF3{TJvO0+i5lQw@}*&HSC${uR1>PJ!!FcNH2=r z@pO0j@H)4|rC?g*Z~Fh`?&b0!=XLNs-yGG^>+w5Zmj?mw9~btweD3r9wTDX)%kz*A zCGJ}S@9tCws*W?#=BQ#$_g6`kIY4<0zrUYxumOU0TgCRx_$4Y^aZU2I16SmkMq zgJP0po!*cq6^x$o!*eU_KZ?0e_l}e zI%o|u$y}#5hy-Z>eoH2&+tg) z%CG|5B+ELzAzN(c*vn(@hI}zTUa=N=%F-tnN$>>v|H{^-yn)3q--mw?kx5*}1(diBO>6sI3v~O>vYR56z|G~0O^RN7{!uKp< zXn&>V;@a|mu&mSkD>wOWzqQ7w;mVzB4dwsfY@Oy`*}un_Y}%Bl>JRq*2U~TTf90mV z$7Iu{%(;9%{6E;L)BG!Mx_{hS;*a4b`Bz@sZwV_|XMpn8j1cx8EbBD? z%5`s_V1X5TE3{evgJqrOU%7j+JS%PT(77EIlbo&78}c)2XLh~cU-IiOzEDiEtkWBE z-D1V~>aCTPeeWO0CRx$x4Y~fsD1NzeS*2aAR@o+5(diA@e?b%OxjIPM9=^+Jk`j!3%xlV7$ zeZMWLh;2~SLfj;O)Yl*69s7 zXVJ>+XUEzp$HUwdlWf)L4Y|jiAG2NdawVx%CB-CLb$UZyU#p>2TGU$c_Vrdwa<)!y z$RRUku&Mj{E5Q}p%O+XY=?yuk!9f=1&`YV5-9R?UvQBTvEAOvoO_!!9@7m3gO|q=h z8}h7```DAseU;jG2U|_DtkWBE$Y6K5Y^_1c-2`aF1}3o5#JF?g>g+}le|}_H{_TFbF5AG)K!kPt)-ac zY@ObakDg-eQvc>k`*A*sNtShbLms+f1zUTvmy*AJifoc)o!*caom9WGLbfK_)oEuK6f7;?)reJnLPk^@4j5#Uv{_y&?B6kj_)acVK;M z^i*OXcO3pP5PKI=vyE?s17NuFy?x z@mUweB+ELzAxD*(!?uJ(DN&`0&}NEdo!*e^ji{)!9QIJI?H9;RGS}%1xx_b@tS9aU zD((fEa+92`(;M>9Vv+pI7JkZ?E#@(kOj=EE$ftvku%*d(wQZ^)e=-;r03>7oq2RZcd^Os6;Gv4g&p zX;a1yx*_&vnCbL}yxw}9wdLKF_2ryolPv4>h8&Z(i1q(HUHKxuh1Dd>I=vx3e%^}r zJ7SaflwQnCvZB))a-45xK4R_}dE4*fm`PT2dPANNF@P^!y;RhWw&gN!d{zq9l%NE}LYg(;M=Jhg0Q^e+*I_Zl1|D$xNp=OqWeE*Xa#8UpXYt59q6m-%%yoBr~1fkSE3^ z$;WCBSNux{SWPn1=?!^9x{nffwX!1qj)qC*I=vycsN*TGkjp7ee~VB|GSle|`EHvt zY|en{%FABG6_YIM^oG3lr4LV)>L^QxoRdwmqSG64Pd9J=a8!bFJLzz?Nmg`vLmq4K z;adXYm9TZsttMH~=?!`H)7LCUs;b<+UznR@S*JH-@5Bi#pgC83$CuzHS=Q+dx%S

    -2^k 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 literal 0 HcmV?d00001 diff --git a/test/models-nonbsd/USD/usdc/texturedcube.usdc b/test/models-nonbsd/USD/usdc/texturedcube.usdc new file mode 100644 index 0000000000000000000000000000000000000000..899b005e3252ab30862cb407ea073fd30fcb9889 GIT binary patch literal 3551 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 JKY16$`!{4&2?ziH literal 0 HcmV?d00001 diff --git a/test/models-nonbsd/USD/usdc/textures/checkerboard.png b/test/models-nonbsd/USD/usdc/textures/checkerboard.png new file mode 100644 index 0000000000000000000000000000000000000000..fde967aca605b21710638f3049ffb43924cb094f GIT binary patch literal 7688 zcmeAS@N?(olHy`uVBq!ia0y~yUi(1B2{d zPZ!6KiaBp?9u#CU;9++3`ET2x;LIN{m5Yw8Dj{w_cBFZf%m zJkT75{|XI^3=AFu2Y_r9jszen#9{y>IhiCF7#dg@c^DWRni$v^7!({Cm>C!*j4B@u znbAZrnmtBKfNFtGfL<}Zo_GB+q30CIIWK;12FCg5zzfhJJwj0%s2$!LNY%?(5r1&oPDbidtQ Tws0n}6k+gm^>bP0l+XkKh(xEg literal 0 HcmV?d00001 diff --git a/test/models-nonbsd/USD/usdc/textures/texture-cat.jpg b/test/models-nonbsd/USD/usdc/textures/texture-cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..22870c622e0871e051f47977a51dab3d9bec3c22 GIT binary patch literal 50234 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%0A*%-6<9 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 +#include +#include +#include + +using namespace ::Assimp; + +class utUSDImport : public AbstractImportExportBase { +public: + virtual bool importerTest() { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/../models-nonbsd/USD/suzanne.usdc", aiProcess_ValidateDataStructure); + EXPECT_EQ(1u, scene->mNumMeshes); + EXPECT_NE(nullptr, scene->mMeshes[0]); + if (nullptr == scene->mMeshes[0]) { + return false; + } + EXPECT_EQ(507u, scene->mMeshes[0]->mNumVertices); + EXPECT_EQ(968u, scene->mMeshes[0]->mNumFaces); + + return (nullptr != scene); + } +}; From 35e4f1bf6492e11485336c1518485413c599e00e Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Fri, 12 Jul 2024 14:37:49 +0200 Subject: [PATCH 35/39] Kimkulling/fix double precision tests (#5660) * Make color single precision * Fix the unittests for double precision * Fix merge issues * Fix issues with Vertex + Color4 * Fix vertex operator, some tests are still red. --- code/AssetLib/3DS/3DSHelper.h | 5 +- code/AssetLib/AMF/AMFImporter.cpp | 12 +-- code/AssetLib/AMF/AMFImporter_Geometry.cpp | 6 +- code/AssetLib/AMF/AMFImporter_Material.cpp | 13 ++- code/AssetLib/ASE/ASEParser.cpp | 97 ++++++++++++++-------- code/AssetLib/ASE/ASEParser.h | 9 +- code/AssetLib/Collada/ColladaParser.cpp | 54 ++++++------ code/AssetLib/MDL/MDLMaterialLoader.cpp | 2 +- code/AssetLib/OFF/OFFLoader.cpp | 2 +- code/AssetLib/Obj/ObjFileData.h | 4 +- code/AssetLib/STL/STLLoader.cpp | 4 +- code/Common/Assimp.cpp | 16 ++-- code/Material/MaterialSystem.cpp | 89 ++++++++++++++++++++ include/assimp/Vertex.h | 13 +-- include/assimp/XmlParser.h | 32 +++++-- include/assimp/cimport.h | 16 ++-- include/assimp/color4.h | 4 +- include/assimp/defs.h | 11 ++- include/assimp/types.h | 12 +-- test/unit/AssimpAPITest_aiMatrix3x3.cpp | 15 +++- test/unit/AssimpAPITest_aiMatrix4x4.cpp | 6 +- test/unit/utIssues.cpp | 14 ++-- 22 files changed, 291 insertions(+), 145 deletions(-) diff --git a/code/AssetLib/3DS/3DSHelper.h b/code/AssetLib/3DS/3DSHelper.h index 8f85f2e4d..271a2cc7b 100644 --- a/code/AssetLib/3DS/3DSHelper.h +++ b/code/AssetLib/3DS/3DSHelper.h @@ -365,14 +365,13 @@ struct Texture { #ifdef _MSC_VER #pragma warning(pop) #endif // _MSC_VER - // --------------------------------------------------------------------------- /** Helper structure representing a 3ds material */ struct Material { //! Default constructor has been deleted Material() : mName(), - mDiffuse(ai_real(0.6), ai_real(0.6), ai_real(0.6)), + mDiffuse(0.6f, 0.6f, 0.6f), mSpecularExponent(ai_real(0.0)), mShininessStrength(ai_real(1.0)), mShading(Discreet3DS::Gouraud), @@ -385,7 +384,7 @@ struct Material { //! Constructor with explicit name explicit Material(const std::string &name) : mName(name), - mDiffuse(ai_real(0.6), ai_real(0.6), ai_real(0.6)), + mDiffuse(0.6f, 0.6f, 0.6f), mSpecularExponent(ai_real(0.0)), mShininessStrength(ai_real(1.0)), mShading(Discreet3DS::Gouraud), diff --git a/code/AssetLib/AMF/AMFImporter.cpp b/code/AssetLib/AMF/AMFImporter.cpp index 42f9664be..7861c592e 100644 --- a/code/AssetLib/AMF/AMFImporter.cpp +++ b/code/AssetLib/AMF/AMFImporter.cpp @@ -384,17 +384,17 @@ void AMFImporter::ParseNode_Instance(XmlNode &node) { for (auto ¤tNode : node.children()) { const std::string ¤tName = currentNode.name(); if (currentName == "deltax") { - XmlParser::getValueAsFloat(currentNode, als.Delta.x); + XmlParser::getValueAsReal(currentNode, als.Delta.x); } else if (currentName == "deltay") { - XmlParser::getValueAsFloat(currentNode, als.Delta.y); + XmlParser::getValueAsReal(currentNode, als.Delta.y); } else if (currentName == "deltaz") { - XmlParser::getValueAsFloat(currentNode, als.Delta.z); + XmlParser::getValueAsReal(currentNode, als.Delta.z); } else if (currentName == "rx") { - XmlParser::getValueAsFloat(currentNode, als.Delta.x); + XmlParser::getValueAsReal(currentNode, als.Delta.x); } else if (currentName == "ry") { - XmlParser::getValueAsFloat(currentNode, als.Delta.y); + XmlParser::getValueAsReal(currentNode, als.Delta.y); } else if (currentName == "rz") { - XmlParser::getValueAsFloat(currentNode, als.Delta.z); + XmlParser::getValueAsReal(currentNode, als.Delta.z); } } ParseHelper_Node_Exit(); diff --git a/code/AssetLib/AMF/AMFImporter_Geometry.cpp b/code/AssetLib/AMF/AMFImporter_Geometry.cpp index db262dfbd..b1d87eb2e 100644 --- a/code/AssetLib/AMF/AMFImporter_Geometry.cpp +++ b/code/AssetLib/AMF/AMFImporter_Geometry.cpp @@ -167,11 +167,11 @@ void AMFImporter::ParseNode_Coordinates(XmlNode &node) { AMFCoordinates &als = *((AMFCoordinates *)ne); // alias for convenience const std::string ¤tName = ai_tolower(currentNode.name()); if (currentName == "x") { - XmlParser::getValueAsFloat(currentNode, als.Coordinate.x); + XmlParser::getValueAsReal(currentNode, als.Coordinate.x); } else if (currentName == "y") { - XmlParser::getValueAsFloat(currentNode, als.Coordinate.y); + XmlParser::getValueAsReal(currentNode, als.Coordinate.y); } else if (currentName == "z") { - XmlParser::getValueAsFloat(currentNode, als.Coordinate.z); + XmlParser::getValueAsReal(currentNode, als.Coordinate.z); } } ParseHelper_Node_Exit(); diff --git a/code/AssetLib/AMF/AMFImporter_Material.cpp b/code/AssetLib/AMF/AMFImporter_Material.cpp index ae27f5d37..74fe2a1b6 100644 --- a/code/AssetLib/AMF/AMFImporter_Material.cpp +++ b/code/AssetLib/AMF/AMFImporter_Material.cpp @@ -263,26 +263,25 @@ void AMFImporter::ParseNode_TexMap(XmlNode &node, const bool pUseOldName) { const std::string &name = currentNode.name(); if (name == "utex1") { read_flag[0] = true; - XmlParser::getValueAsFloat(node, als.TextureCoordinate[0].x); + XmlParser::getValueAsReal(node, als.TextureCoordinate[0].x); } else if (name == "utex2") { read_flag[1] = true; - XmlParser::getValueAsFloat(node, als.TextureCoordinate[1].x); + XmlParser::getValueAsReal(node, als.TextureCoordinate[1].x); } else if (name == "utex3") { read_flag[2] = true; - XmlParser::getValueAsFloat(node, als.TextureCoordinate[2].x); + XmlParser::getValueAsReal(node, als.TextureCoordinate[2].x); } else if (name == "vtex1") { read_flag[3] = true; - XmlParser::getValueAsFloat(node, als.TextureCoordinate[0].y); + XmlParser::getValueAsReal(node, als.TextureCoordinate[0].y); } else if (name == "vtex2") { read_flag[4] = true; - XmlParser::getValueAsFloat(node, als.TextureCoordinate[1].y); + XmlParser::getValueAsReal(node, als.TextureCoordinate[1].y); } else if (name == "vtex3") { read_flag[5] = true; - XmlParser::getValueAsFloat(node, als.TextureCoordinate[2].y); + XmlParser::getValueAsReal(node, als.TextureCoordinate[2].y); } } ParseHelper_Node_Exit(); - } else { for (pugi::xml_attribute &attr : node.attributes()) { const std::string name = attr.name(); diff --git a/code/AssetLib/ASE/ASEParser.cpp b/code/AssetLib/ASE/ASEParser.cpp index 02d21f41b..c9bbe3ca6 100644 --- a/code/AssetLib/ASE/ASEParser.cpp +++ b/code/AssetLib/ASE/ASEParser.cpp @@ -422,7 +422,7 @@ void Parser::ParseLV1SoftSkinBlock() { me.first = static_cast(curMesh->mBones.size()); curMesh->mBones.emplace_back(bone); } - ParseLV4MeshFloat(me.second); + ParseLV4MeshReal(me.second); // Add the new bone weight to list vert.mBoneWeights.push_back(me); @@ -580,14 +580,14 @@ void Parser::ParseLV2MaterialBlock(ASE::Material &mat) { } // material transparency if (TokenMatch(mFilePtr, "MATERIAL_TRANSPARENCY", 21)) { - ParseLV4MeshFloat(mat.mTransparency); + ParseLV4MeshReal(mat.mTransparency); mat.mTransparency = ai_real(1.0) - mat.mTransparency; continue; } // material self illumination if (TokenMatch(mFilePtr, "MATERIAL_SELFILLUM", 18)) { ai_real f = 0.0; - ParseLV4MeshFloat(f); + ParseLV4MeshReal(f); mat.mEmissive.r = f; mat.mEmissive.g = f; @@ -596,7 +596,7 @@ void Parser::ParseLV2MaterialBlock(ASE::Material &mat) { } // material shininess if (TokenMatch(mFilePtr, "MATERIAL_SHINE", 14)) { - ParseLV4MeshFloat(mat.mSpecularExponent); + ParseLV4MeshReal(mat.mSpecularExponent); mat.mSpecularExponent *= 15; continue; } @@ -607,7 +607,7 @@ void Parser::ParseLV2MaterialBlock(ASE::Material &mat) { } // material shininess strength if (TokenMatch(mFilePtr, "MATERIAL_SHINESTRENGTH", 22)) { - ParseLV4MeshFloat(mat.mShininessStrength); + ParseLV4MeshReal(mat.mShininessStrength); continue; } // diffuse color map @@ -731,32 +731,32 @@ void Parser::ParseLV3MapBlock(Texture &map) { } // offset on the u axis if (TokenMatch(mFilePtr, "UVW_U_OFFSET", 12)) { - ParseLV4MeshFloat(map.mOffsetU); + ParseLV4MeshReal(map.mOffsetU); continue; } // offset on the v axis if (TokenMatch(mFilePtr, "UVW_V_OFFSET", 12)) { - ParseLV4MeshFloat(map.mOffsetV); + ParseLV4MeshReal(map.mOffsetV); continue; } // tiling on the u axis if (TokenMatch(mFilePtr, "UVW_U_TILING", 12)) { - ParseLV4MeshFloat(map.mScaleU); + ParseLV4MeshReal(map.mScaleU); continue; } // tiling on the v axis if (TokenMatch(mFilePtr, "UVW_V_TILING", 12)) { - ParseLV4MeshFloat(map.mScaleV); + ParseLV4MeshReal(map.mScaleV); continue; } // rotation around the z-axis if (TokenMatch(mFilePtr, "UVW_ANGLE", 9)) { - ParseLV4MeshFloat(map.mRotation); + ParseLV4MeshReal(map.mRotation); continue; } // map blending factor if (TokenMatch(mFilePtr, "MAP_AMOUNT", 10)) { - ParseLV4MeshFloat(map.mTextureBlend); + ParseLV4MeshReal(map.mTextureBlend); continue; } } @@ -895,15 +895,15 @@ void Parser::ParseLV2CameraSettingsBlock(ASE::Camera &camera) { if ('*' == *mFilePtr) { ++mFilePtr; if (TokenMatch(mFilePtr, "CAMERA_NEAR", 11)) { - ParseLV4MeshFloat(camera.mNear); + ParseLV4MeshReal(camera.mNear); continue; } if (TokenMatch(mFilePtr, "CAMERA_FAR", 10)) { - ParseLV4MeshFloat(camera.mFar); + ParseLV4MeshReal(camera.mFar); continue; } if (TokenMatch(mFilePtr, "CAMERA_FOV", 10)) { - ParseLV4MeshFloat(camera.mFOV); + ParseLV4MeshReal(camera.mFOV); continue; } } @@ -922,15 +922,15 @@ void Parser::ParseLV2LightSettingsBlock(ASE::Light &light) { continue; } if (TokenMatch(mFilePtr, "LIGHT_INTENS", 12)) { - ParseLV4MeshFloat(light.mIntensity); + ParseLV4MeshReal(light.mIntensity); continue; } if (TokenMatch(mFilePtr, "LIGHT_HOTSPOT", 13)) { - ParseLV4MeshFloat(light.mAngle); + ParseLV4MeshReal(light.mAngle); continue; } if (TokenMatch(mFilePtr, "LIGHT_FALLOFF", 13)) { - ParseLV4MeshFloat(light.mFalloff); + ParseLV4MeshReal(light.mFalloff); continue; } } @@ -1038,7 +1038,7 @@ void Parser::ParseLV3ScaleAnimationBlock(ASE::Animation &anim) { if (b) { anim.akeyScaling.emplace_back(); aiVectorKey &key = anim.akeyScaling.back(); - ParseLV4MeshFloatTriple(&key.mValue.x, iIndex); + ParseLV4MeshRealTriple(&key.mValue.x, iIndex); key.mTime = (double)iIndex; } } @@ -1077,7 +1077,7 @@ void Parser::ParseLV3PosAnimationBlock(ASE::Animation &anim) { if (b) { anim.akeyPositions.emplace_back(); aiVectorKey &key = anim.akeyPositions.back(); - ParseLV4MeshFloatTriple(&key.mValue.x, iIndex); + ParseLV4MeshRealTriple(&key.mValue.x, iIndex); key.mTime = (double)iIndex; } } @@ -1118,8 +1118,8 @@ void Parser::ParseLV3RotAnimationBlock(ASE::Animation &anim) { aiQuatKey &key = anim.akeyRotations.back(); aiVector3D v; ai_real f; - ParseLV4MeshFloatTriple(&v.x, iIndex); - ParseLV4MeshFloat(f); + ParseLV4MeshRealTriple(&v.x, iIndex); + ParseLV4MeshReal(f); key.mTime = (double)iIndex; key.mValue = aiQuaternion(v, f); } @@ -1163,23 +1163,23 @@ void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode &mesh) { // fourth row of the transformation matrix - and also the // only information here that is interesting for targets if (TokenMatch(mFilePtr, "TM_ROW3", 7)) { - ParseLV4MeshFloatTriple((mode == 1 ? mesh.mTransform[3] : &mesh.mTargetPosition.x)); + ParseLV4MeshRealTriple((mode == 1 ? mesh.mTransform[3] : &mesh.mTargetPosition.x)); continue; } if (mode == 1) { // first row of the transformation matrix if (TokenMatch(mFilePtr, "TM_ROW0", 7)) { - ParseLV4MeshFloatTriple(mesh.mTransform[0]); + ParseLV4MeshRealTriple(mesh.mTransform[0]); continue; } // second row of the transformation matrix if (TokenMatch(mFilePtr, "TM_ROW1", 7)) { - ParseLV4MeshFloatTriple(mesh.mTransform[1]); + ParseLV4MeshRealTriple(mesh.mTransform[1]); continue; } // third row of the transformation matrix if (TokenMatch(mFilePtr, "TM_ROW2", 7)) { - ParseLV4MeshFloatTriple(mesh.mTransform[2]); + ParseLV4MeshRealTriple(mesh.mTransform[2]); continue; } // inherited position axes @@ -1414,7 +1414,7 @@ void Parser::ParseLV4MeshBonesVertices(unsigned int iNumVertices, ASE::Mesh &mes // --- ignored ai_real afVert[3]; - ParseLV4MeshFloatTriple(afVert); + ParseLV4MeshRealTriple(afVert); std::pair pairOut; while (true) { @@ -1453,7 +1453,7 @@ void Parser::ParseLV3MeshVertexListBlock( aiVector3D vTemp; unsigned int iIndex; - ParseLV4MeshFloatTriple(&vTemp.x, iIndex); + ParseLV4MeshRealTriple(&vTemp.x, iIndex); if (iIndex >= iNumVertices) { LogWarning("Invalid vertex index. It will be ignored"); @@ -1506,7 +1506,7 @@ void Parser::ParseLV3MeshTListBlock(unsigned int iNumVertices, if (TokenMatch(mFilePtr, "MESH_TVERT", 10)) { aiVector3D vTemp; unsigned int iIndex; - ParseLV4MeshFloatTriple(&vTemp.x, iIndex); + ParseLV4MeshRealTriple(&vTemp.x, iIndex); if (iIndex >= iNumVertices) { LogWarning("Tvertex has an invalid index. It will be ignored"); @@ -1657,7 +1657,7 @@ void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) { ++mFilePtr; if (faceIdx != UINT_MAX && TokenMatch(mFilePtr, "MESH_VERTEXNORMAL", 17)) { aiVector3D vNormal; - ParseLV4MeshFloatTriple(&vNormal.x, index); + ParseLV4MeshRealTriple(&vNormal.x, index); if (faceIdx >= sMesh.mFaces.size()) continue; @@ -1679,7 +1679,7 @@ void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh &sMesh) { } if (TokenMatch(mFilePtr, "MESH_FACENORMAL", 15)) { aiVector3D vNormal; - ParseLV4MeshFloatTriple(&vNormal.x, faceIdx); + ParseLV4MeshRealTriple(&vNormal.x, faceIdx); if (faceIdx >= sMesh.mFaces.size()) { ASSIMP_LOG_ERROR("ASE: Invalid vertex index in MESH_FACENORMAL section"); @@ -1844,7 +1844,17 @@ void Parser::ParseLV4MeshLongTriple(unsigned int *apOut, unsigned int &rIndexOut ParseLV4MeshLongTriple(apOut); } // ------------------------------------------------------------------------------------------------ -void Parser::ParseLV4MeshFloatTriple(ai_real *apOut, unsigned int &rIndexOut) { +void Parser::ParseLV4MeshRealTriple(ai_real *apOut, unsigned int &rIndexOut) { + ai_assert(nullptr != apOut); + + // parse the index + ParseLV4MeshLong(rIndexOut); + + // parse the three others + ParseLV4MeshRealTriple(apOut); +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFloatTriple(float* apOut, unsigned int& rIndexOut) { ai_assert(nullptr != apOut); // parse the index @@ -1854,7 +1864,15 @@ void Parser::ParseLV4MeshFloatTriple(ai_real *apOut, unsigned int &rIndexOut) { ParseLV4MeshFloatTriple(apOut); } // ------------------------------------------------------------------------------------------------ -void Parser::ParseLV4MeshFloatTriple(ai_real *apOut) { +void Parser::ParseLV4MeshRealTriple(ai_real *apOut) { + ai_assert(nullptr != apOut); + + for (unsigned int i = 0; i < 3; ++i) { + ParseLV4MeshReal(apOut[i]); + } +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFloatTriple(float* apOut) { ai_assert(nullptr != apOut); for (unsigned int i = 0; i < 3; ++i) { @@ -1862,7 +1880,7 @@ void Parser::ParseLV4MeshFloatTriple(ai_real *apOut) { } } // ------------------------------------------------------------------------------------------------ -void Parser::ParseLV4MeshFloat(ai_real &fOut) { +void Parser::ParseLV4MeshReal(ai_real &fOut) { // skip spaces and tabs if (!SkipSpaces(&mFilePtr, mEnd)) { // LOG @@ -1875,6 +1893,19 @@ void Parser::ParseLV4MeshFloat(ai_real &fOut) { mFilePtr = fast_atoreal_move(mFilePtr, fOut); } // ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFloat(float &fOut) { + // skip spaces and tabs + if (!SkipSpaces(&mFilePtr, mEnd)) { + // LOG + LogWarning("Unable to parse float: unexpected EOL [#1]"); + fOut = 0.0; + ++iLineNumber; + return; + } + // parse the first float + mFilePtr = fast_atoreal_move(mFilePtr, fOut); +} +// ------------------------------------------------------------------------------------------------ void Parser::ParseLV4MeshLong(unsigned int &iOut) { // Skip spaces and tabs if (!SkipSpaces(&mFilePtr, mEnd)) { diff --git a/code/AssetLib/ASE/ASEParser.h b/code/AssetLib/ASE/ASEParser.h index 9c5cd39b1..916605790 100644 --- a/code/AssetLib/ASE/ASEParser.h +++ b/code/AssetLib/ASE/ASEParser.h @@ -553,13 +553,15 @@ private: //! (also works for MESH_TVERT, MESH_CFACE, MESH_VERTCOL ...) //! \param apOut Output buffer (3 floats) //! \param rIndexOut Output index - void ParseLV4MeshFloatTriple(ai_real *apOut, unsigned int &rIndexOut); + void ParseLV4MeshRealTriple(ai_real *apOut, unsigned int &rIndexOut); + void ParseLV4MeshFloatTriple(float *apOut, unsigned int &rIndexOut); // ------------------------------------------------------------------- //! Parse a *MESH_VERT block in a file //! (also works for MESH_TVERT, MESH_CFACE, MESH_VERTCOL ...) //! \param apOut Output buffer (3 floats) - void ParseLV4MeshFloatTriple(ai_real *apOut); + void ParseLV4MeshRealTriple(ai_real *apOut); + void ParseLV4MeshFloatTriple(float *apOut); // ------------------------------------------------------------------- //! Parse a *MESH_TFACE block in a file @@ -577,7 +579,8 @@ private: // ------------------------------------------------------------------- //! Parse a single float element //! \param fOut Output float - void ParseLV4MeshFloat(ai_real &fOut); + void ParseLV4MeshReal(ai_real &fOut); + void ParseLV4MeshFloat(float &fOut); // ------------------------------------------------------------------- //! Parse a single int element diff --git a/code/AssetLib/Collada/ColladaParser.cpp b/code/AssetLib/Collada/ColladaParser.cpp index e7f91b5fe..0741b3c73 100644 --- a/code/AssetLib/Collada/ColladaParser.cpp +++ b/code/AssetLib/Collada/ColladaParser.cpp @@ -968,34 +968,34 @@ void ColladaParser::ReadLight(XmlNode &node, Collada::Light &pLight) { content = fast_atoreal_move(content, (ai_real &)pLight.mColor.b); SkipSpacesAndLineEnd(&content, end); } else if (currentName == "constant_attenuation") { - XmlParser::getValueAsFloat(currentNode, pLight.mAttConstant); + XmlParser::getValueAsReal(currentNode, pLight.mAttConstant); } else if (currentName == "linear_attenuation") { - XmlParser::getValueAsFloat(currentNode, pLight.mAttLinear); + XmlParser::getValueAsReal(currentNode, pLight.mAttLinear); } else if (currentName == "quadratic_attenuation") { - XmlParser::getValueAsFloat(currentNode, pLight.mAttQuadratic); + XmlParser::getValueAsReal(currentNode, pLight.mAttQuadratic); } else if (currentName == "falloff_angle") { - XmlParser::getValueAsFloat(currentNode, pLight.mFalloffAngle); + XmlParser::getValueAsReal(currentNode, pLight.mFalloffAngle); } else if (currentName == "falloff_exponent") { - XmlParser::getValueAsFloat(currentNode, pLight.mFalloffExponent); + XmlParser::getValueAsReal(currentNode, pLight.mFalloffExponent); } // FCOLLADA extensions // ------------------------------------------------------- else if (currentName == "outer_cone") { - XmlParser::getValueAsFloat(currentNode, pLight.mOuterAngle); + XmlParser::getValueAsReal(currentNode, pLight.mOuterAngle); } else if (currentName == "penumbra_angle") { // this one is deprecated, now calculated using outer_cone - XmlParser::getValueAsFloat(currentNode, pLight.mPenumbraAngle); + XmlParser::getValueAsReal(currentNode, pLight.mPenumbraAngle); } else if (currentName == "intensity") { - XmlParser::getValueAsFloat(currentNode, pLight.mIntensity); + XmlParser::getValueAsReal(currentNode, pLight.mIntensity); } else if (currentName == "falloff") { - XmlParser::getValueAsFloat(currentNode, pLight.mOuterAngle); + XmlParser::getValueAsReal(currentNode, pLight.mOuterAngle); } else if (currentName == "hotspot_beam") { - XmlParser::getValueAsFloat(currentNode, pLight.mFalloffAngle); + XmlParser::getValueAsReal(currentNode, pLight.mFalloffAngle); } // OpenCOLLADA extensions // ------------------------------------------------------- else if (currentName == "decay_falloff") { - XmlParser::getValueAsFloat(currentNode, pLight.mOuterAngle); + XmlParser::getValueAsReal(currentNode, pLight.mOuterAngle); } } } @@ -1010,15 +1010,15 @@ void ColladaParser::ReadCamera(XmlNode &node, Collada::Camera &camera) { if (currentName == "orthographic") { camera.mOrtho = true; } else if (currentName == "xfov" || currentName == "xmag") { - XmlParser::getValueAsFloat(currentNode, camera.mHorFov); + XmlParser::getValueAsReal(currentNode, camera.mHorFov); } else if (currentName == "yfov" || currentName == "ymag") { - XmlParser::getValueAsFloat(currentNode, camera.mVerFov); + XmlParser::getValueAsReal(currentNode, camera.mVerFov); } else if (currentName == "aspect_ratio") { - XmlParser::getValueAsFloat(currentNode, camera.mAspect); + XmlParser::getValueAsReal(currentNode, camera.mAspect); } else if (currentName == "znear") { - XmlParser::getValueAsFloat(currentNode, camera.mZNear); + XmlParser::getValueAsReal(currentNode, camera.mZNear); } else if (currentName == "zfar") { - XmlParser::getValueAsFloat(currentNode, camera.mZFar); + XmlParser::getValueAsReal(currentNode, camera.mZFar); } } } @@ -1170,15 +1170,15 @@ void ColladaParser::ReadSamplerProperties(XmlNode &node, Sampler &out) { } else if (currentName == "mirrorV") { XmlParser::getValueAsBool(currentNode, out.mMirrorV); } else if (currentName == "repeatU") { - XmlParser::getValueAsFloat(currentNode, out.mTransform.mScaling.x); + XmlParser::getValueAsReal(currentNode, out.mTransform.mScaling.x); } else if (currentName == "repeatV") { - XmlParser::getValueAsFloat(currentNode, out.mTransform.mScaling.y); + XmlParser::getValueAsReal(currentNode, out.mTransform.mScaling.y); } else if (currentName == "offsetU") { - XmlParser::getValueAsFloat(currentNode, out.mTransform.mTranslation.x); + XmlParser::getValueAsReal(currentNode, out.mTransform.mTranslation.x); } else if (currentName == "offsetV") { - XmlParser::getValueAsFloat(currentNode, out.mTransform.mTranslation.y); + XmlParser::getValueAsReal(currentNode, out.mTransform.mTranslation.y); } else if (currentName == "rotateUV") { - XmlParser::getValueAsFloat(currentNode, out.mTransform.mRotation); + XmlParser::getValueAsReal(currentNode, out.mTransform.mRotation); } else if (currentName == "blend_mode") { std::string v; XmlParser::getValueAsString(currentNode, v); @@ -1198,14 +1198,14 @@ void ColladaParser::ReadSamplerProperties(XmlNode &node, Sampler &out) { // OKINO extensions // ------------------------------------------------------- else if (currentName == "weighting") { - XmlParser::getValueAsFloat(currentNode, out.mWeighting); + XmlParser::getValueAsReal(currentNode, out.mWeighting); } else if (currentName == "mix_with_previous_layer") { - XmlParser::getValueAsFloat(currentNode, out.mMixWithPrevious); + XmlParser::getValueAsReal(currentNode, out.mMixWithPrevious); } // MAX3D extensions // ------------------------------------------------------- else if (currentName == "amount") { - XmlParser::getValueAsFloat(currentNode, out.mWeighting); + XmlParser::getValueAsReal(currentNode, out.mWeighting); } } } @@ -1265,13 +1265,13 @@ void ColladaParser::ReadEffectColor(XmlNode &node, aiColor4D &pColor, Sampler &p // ------------------------------------------------------------------------------------------------ // Reads an effect entry containing a float -void ColladaParser::ReadEffectFloat(XmlNode &node, ai_real &pFloat) { - pFloat = 0.f; +void ColladaParser::ReadEffectFloat(XmlNode &node, ai_real &pReal) { + pReal = 0.f; XmlNode floatNode = node.child("float"); if (floatNode.empty()) { return; } - XmlParser::getValueAsFloat(floatNode, pFloat); + XmlParser::getValueAsReal(floatNode, pReal); } // ------------------------------------------------------------------------------------------------ diff --git a/code/AssetLib/MDL/MDLMaterialLoader.cpp b/code/AssetLib/MDL/MDLMaterialLoader.cpp index f68f8e23e..7adb76d94 100644 --- a/code/AssetLib/MDL/MDLMaterialLoader.cpp +++ b/code/AssetLib/MDL/MDLMaterialLoader.cpp @@ -610,7 +610,7 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7( if (is_not_qnan(clrTexture.r)) { clrTemp.r *= clrTexture.a; } - pcMatOut->AddProperty(&clrTemp.r, 1, AI_MATKEY_OPACITY); + pcMatOut->AddProperty(&clrTemp.r, 1, AI_MATKEY_OPACITY); // read phong power int iShadingMode = (int)aiShadingMode_Gouraud; diff --git a/code/AssetLib/OFF/OFFLoader.cpp b/code/AssetLib/OFF/OFFLoader.cpp index 566e3212e..a3feaa53c 100644 --- a/code/AssetLib/OFF/OFFLoader.cpp +++ b/code/AssetLib/OFF/OFFLoader.cpp @@ -316,7 +316,7 @@ void OFFImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; aiMaterial *pcMat = new aiMaterial(); - aiColor4D clr(ai_real(0.6), ai_real(0.6), ai_real(0.6), ai_real(1.0)); + aiColor4D clr(0.6f, 0.6f, 0.6f, 1.0f); pcMat->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); pScene->mMaterials[0] = pcMat; diff --git a/code/AssetLib/Obj/ObjFileData.h b/code/AssetLib/Obj/ObjFileData.h index 3dc20c799..205c855e5 100644 --- a/code/AssetLib/Obj/ObjFileData.h +++ b/code/AssetLib/Obj/ObjFileData.h @@ -199,12 +199,12 @@ struct Material { //! Constructor Material() : - diffuse(ai_real(0.6), ai_real(0.6), ai_real(0.6)), + diffuse(0.6f, 0.6f, 0.6f), alpha(ai_real(1.0)), shineness(ai_real(0.0)), illumination_model(1), ior(ai_real(1.0)), - transparent(ai_real(1.0), ai_real(1.0), ai_real(1.0)), + transparent(1.0f, 1.0, 1.0), roughness(), metallic(), sheen(), diff --git a/code/AssetLib/STL/STLLoader.cpp b/code/AssetLib/STL/STLLoader.cpp index 3a9fc1b12..90c504d0d 100644 --- a/code/AssetLib/STL/STLLoader.cpp +++ b/code/AssetLib/STL/STLLoader.cpp @@ -181,7 +181,7 @@ void STLImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy mBuffer = &buffer2[0]; // the default vertex color is light gray. - mClrColorDefault.r = mClrColorDefault.g = mClrColorDefault.b = mClrColorDefault.a = (ai_real)0.6; + mClrColorDefault.r = mClrColorDefault.g = mClrColorDefault.b = mClrColorDefault.a = 0.6f; // allocate a single node mScene->mRootNode = new aiNode(); @@ -209,7 +209,7 @@ void STLImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy } pcMat->AddProperty(&clrDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); pcMat->AddProperty(&clrDiffuse, 1, AI_MATKEY_COLOR_SPECULAR); - clrDiffuse = aiColor4D(ai_real(0.05), ai_real(0.05), ai_real(0.05), ai_real(1.0)); + clrDiffuse = aiColor4D(0.05f, 0.05f, 0.05f, 1.0f); pcMat->AddProperty(&clrDiffuse, 1, AI_MATKEY_COLOR_AMBIENT); mScene->mNumMaterials = 1; diff --git a/code/Common/Assimp.cpp b/code/Common/Assimp.cpp index bd1d6aedd..9abac7ee2 100644 --- a/code/Common/Assimp.cpp +++ b/code/Common/Assimp.cpp @@ -743,14 +743,14 @@ ASSIMP_API void aiVector2DivideByVector( } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiVector2Length( +ASSIMP_API ai_real aiVector2Length( const C_STRUCT aiVector2D *v) { ai_assert(nullptr != v); return v->Length(); } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiVector2SquareLength( +ASSIMP_API ai_real aiVector2SquareLength( const C_STRUCT aiVector2D *v) { ai_assert(nullptr != v); return v->SquareLength(); @@ -764,7 +764,7 @@ ASSIMP_API void aiVector2Negate( } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiVector2DotProduct( +ASSIMP_API ai_real aiVector2DotProduct( const C_STRUCT aiVector2D *a, const C_STRUCT aiVector2D *b) { ai_assert(nullptr != a); @@ -859,14 +859,14 @@ ASSIMP_API void aiVector3DivideByVector( } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiVector3Length( +ASSIMP_API ai_real aiVector3Length( const C_STRUCT aiVector3D *v) { ai_assert(nullptr != v); return v->Length(); } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiVector3SquareLength( +ASSIMP_API ai_real aiVector3SquareLength( const C_STRUCT aiVector3D *v) { ai_assert(nullptr != v); return v->SquareLength(); @@ -880,7 +880,7 @@ ASSIMP_API void aiVector3Negate( } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiVector3DotProduct( +ASSIMP_API ai_real aiVector3DotProduct( const C_STRUCT aiVector3D *a, const C_STRUCT aiVector3D *b) { ai_assert(nullptr != a); @@ -966,7 +966,7 @@ ASSIMP_API void aiMatrix3Inverse(C_STRUCT aiMatrix3x3 *mat) { } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiMatrix3Determinant(const C_STRUCT aiMatrix3x3 *mat) { +ASSIMP_API ai_real aiMatrix3Determinant(const C_STRUCT aiMatrix3x3 *mat) { ai_assert(nullptr != mat); return mat->Determinant(); } @@ -1066,7 +1066,7 @@ ASSIMP_API void aiMatrix4Inverse(C_STRUCT aiMatrix4x4 *mat) { } // ------------------------------------------------------------------------------------------------ -ASSIMP_API float aiMatrix4Determinant(const C_STRUCT aiMatrix4x4 *mat) { +ASSIMP_API ai_real aiMatrix4Determinant(const C_STRUCT aiMatrix4x4 *mat) { ai_assert(nullptr != mat); return mat->Determinant(); } diff --git a/code/Material/MaterialSystem.cpp b/code/Material/MaterialSystem.cpp index 7e2fb51b9..917f69105 100644 --- a/code/Material/MaterialSystem.cpp +++ b/code/Material/MaterialSystem.cpp @@ -174,6 +174,95 @@ aiReturn aiGetMaterialFloatArray(const aiMaterial *pMat, return AI_SUCCESS; } +// ------------------------------------------------------------------------------------------------ +// Get an array of floating-point values from the material. +aiReturn aiGetMaterialDoubleArray(const aiMaterial *pMat, + const char *pKey, + unsigned int type, + unsigned int index, + double *pOut, + unsigned int *pMax) { + ai_assert(pOut != nullptr); + ai_assert(pMat != nullptr); + + const aiMaterialProperty *prop; + aiGetMaterialProperty(pMat, pKey, type, index, (const aiMaterialProperty **)&prop); + if (nullptr == prop) { + return AI_FAILURE; + } + + // data is given in floats, convert to ai_real + unsigned int iWrite = 0; + if (aiPTI_Float == prop->mType || aiPTI_Buffer == prop->mType) { + iWrite = prop->mDataLength / sizeof(float); + if (pMax) { + iWrite = std::min(*pMax, iWrite); + ; + } + + for (unsigned int a = 0; a < iWrite; ++a) { + pOut[a] = static_cast(reinterpret_cast(prop->mData)[a]); + } + + if (pMax) { + *pMax = iWrite; + } + } + // data is given in doubles, convert to float + else if (aiPTI_Double == prop->mType) { + iWrite = prop->mDataLength / sizeof(double); + if (pMax) { + iWrite = std::min(*pMax, iWrite); + ; + } + for (unsigned int a = 0; a < iWrite; ++a) { + pOut[a] = static_cast(reinterpret_cast(prop->mData)[a]); + } + if (pMax) { + *pMax = iWrite; + } + } + // data is given in ints, convert to float + else if (aiPTI_Integer == prop->mType) { + iWrite = prop->mDataLength / sizeof(int32_t); + if (pMax) { + iWrite = std::min(*pMax, iWrite); + } + for (unsigned int a = 0; a < iWrite; ++a) { + pOut[a] = static_cast(reinterpret_cast(prop->mData)[a]); + } + if (pMax) { + *pMax = iWrite; + } + } + // a string ... read floats separated by spaces + else { + if (pMax) { + iWrite = *pMax; + } + // strings are zero-terminated with a 32 bit length prefix, so this is safe + const char *cur = prop->mData + 4; + ai_assert(prop->mDataLength >= 5); + ai_assert(!prop->mData[prop->mDataLength - 1]); + for (unsigned int a = 0;; ++a) { + cur = fast_atoreal_move(cur, pOut[a]); + if (a == iWrite - 1) { + break; + } + if (!IsSpace(*cur)) { + ASSIMP_LOG_ERROR("Material property", pKey, + " is a string; failed to parse a float array out of it."); + return AI_FAILURE; + } + } + + if (pMax) { + *pMax = iWrite; + } + } + return AI_SUCCESS; +} + // ------------------------------------------------------------------------------------------------ // Get an array if integers from the material aiReturn aiGetMaterialIntegerArray(const aiMaterial *pMat, diff --git a/include/assimp/Vertex.h b/include/assimp/Vertex.h index 62a531ce2..c0a6a836a 100644 --- a/include/assimp/Vertex.h +++ b/include/assimp/Vertex.h @@ -4,7 +4,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2024, assimp team - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -61,7 +60,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -namespace Assimp { +namespace Assimp { /////////////////////////////////////////////////////////////////////////// // std::plus-family operates on operands with identical types - we need to @@ -231,7 +230,8 @@ private: // ---------------------------------------------------------------------------- /// This time binary arithmetic of v0 with a floating-point number - template