From 0cb169368956fba4508fbd3af361cf37c8060e8e Mon Sep 17 00:00:00 2001 From: Steve M Date: Fri, 12 Jul 2024 04:25:35 -0700 Subject: [PATCH] [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 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

-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); + } +};