Преглед изворни кода

[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 <[email protected]>
Steve M пре 1 година
родитељ
комит
0cb1693689
37 измењених фајлова са 2167 додато и 9 уклоњено
  1. 4 0
      .gitignore
  2. 13 6
      CMakeLists.txt
  3. 125 0
      code/AssetLib/USD/USDLoader.cpp
  4. 78 0
      code/AssetLib/USD/USDLoader.h
  5. 751 0
      code/AssetLib/USD/USDLoaderImplTinyusdz.cpp
  6. 154 0
      code/AssetLib/USD/USDLoaderImplTinyusdz.h
  7. 110 0
      code/AssetLib/USD/USDLoaderImplTinyusdzHelper.cpp
  8. 29 0
      code/AssetLib/USD/USDLoaderImplTinyusdzHelper.h
  9. 116 0
      code/AssetLib/USD/USDLoaderUtil.cpp
  10. 59 0
      code/AssetLib/USD/USDLoaderUtil.h
  11. 50 0
      code/AssetLib/USD/USDPreprocessor.h
  12. 130 0
      code/CMakeLists.txt
  13. 3 2
      code/Common/BaseImporter.cpp
  14. 6 0
      code/Common/ImporterRegistry.cpp
  15. 13 0
      contrib/tinyusdz/README.md
  16. 57 0
      contrib/tinyusdz/assimp_tinyusdz_logging.inc
  17. 40 0
      contrib/tinyusdz/patches/README.md
  18. 42 0
      contrib/tinyusdz/patches/tinyusdz.patch
  19. 1 0
      doc/Fileformats.md
  20. 2 1
      include/assimp/BaseImporter.h
  21. 1 0
      test/CMakeLists.txt
  22. 3 0
      test/models-nonbsd/USD/usda/README.md
  23. 154 0
      test/models-nonbsd/USD/usda/blendshape.usda
  24. 101 0
      test/models-nonbsd/USD/usda/texturedcube.usda
  25. BIN
      test/models-nonbsd/USD/usda/textures/01.jpg
  26. BIN
      test/models-nonbsd/USD/usda/textures/checkerboard.png
  27. BIN
      test/models-nonbsd/USD/usda/textures/texture-cat.jpg
  28. 55 0
      test/models-nonbsd/USD/usda/translated-cube.usda
  29. 4 0
      test/models-nonbsd/USD/usdc/README.md
  30. BIN
      test/models-nonbsd/USD/usdc/blendshape.usdc
  31. BIN
      test/models-nonbsd/USD/usdc/suzanne.usdc
  32. BIN
      test/models-nonbsd/USD/usdc/texturedcube.usdc
  33. BIN
      test/models-nonbsd/USD/usdc/textures/01.jpg
  34. BIN
      test/models-nonbsd/USD/usdc/textures/checkerboard.png
  35. BIN
      test/models-nonbsd/USD/usdc/textures/texture-cat.jpg
  36. BIN
      test/models-nonbsd/USD/usdc/translated-cube.usdc
  37. 66 0
      test/unit/utUSDImport.cpp

+ 4 - 0
.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

+ 13 - 6
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("$<$<COMPILE_LANGUAGE:CXX>:-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()

+ 125 - 0
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 <memory>
+
+// internal headers
+#include <assimp/ai_assert.h>
+#include <assimp/anim.h>
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/fast_atof.h>
+#include <assimp/Importer.hpp>
+#include <assimp/importerdesc.h>
+#include <assimp/IOStreamBuffer.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/scene.h>
+#include <assimp/StringUtils.h>
+#include <assimp/StreamReader.h>
+
+#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

+ 78 - 0
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 <assimp/BaseImporter.h>
+#include <assimp/types.h>
+#include <vector>
+#include <cstdint>
+
+#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

+ 751 - 0
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 <memory>
+#include <sstream>
+
+// internal headers
+#include <assimp/ai_assert.h>
+#include <assimp/anim.h>
+#include <assimp/CreateAnimMesh.h>
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/fast_atof.h>
+#include <assimp/Importer.hpp>
+#include <assimp/importerdesc.h>
+#include <assimp/IOStreamBuffer.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/StringUtils.h>
+#include <assimp/StreamReader.h>
+
+#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<size_t, tinyusdz::tydra::Node> 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<unsigned int>(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<unsigned int>(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<unsigned int>(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<const float *>(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<const float *>(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<float, 3> &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<const float *>(&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<uint8_t>(floatPtr[fpi]     * 255);
+            tex->pcData[i].g = static_cast<uint8_t>(floatPtr[fpi + 1] * 255);
+            tex->pcData[i].r = static_cast<uint8_t>(floatPtr[fpi + 2] * 255);
+            tex->pcData[i].a = static_cast<uint8_t>(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<long int>(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<size_t, tinyusdz::tydra::Node> &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<size_t, tinyusdz::tydra::Node> &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<size_t, tinyusdz::tydra::Node> &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<unsigned int>(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<unsigned int>(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

+ 154 - 0
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 <assimp/BaseImporter.h>
+#include <assimp/scene.h>
+#include <assimp/types.h>
+#include <vector>
+#include <cstdint>
+#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<size_t, tinyusdz::tydra::Node> &meshNodes,
+            const std::string &nameWExt
+            );
+
+    aiNode *nodes(
+            const tinyusdz::tydra::RenderScene &render_scene,
+            std::map<size_t, tinyusdz::tydra::Node> &meshNodes,
+            const std::string &nameWExt);
+
+    aiNode *nodesRecursive(
+            aiNode *pNodeParent,
+            const tinyusdz::tydra::Node &node,
+            std::map<size_t, tinyusdz::tydra::Node> &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

+ 110 - 0
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<float, 3> &scaleOrPosIn) {
+    return aiVector3D(scaleOrPosIn[0], scaleOrPosIn[1], scaleOrPosIn[2]);
+}
+
+aiQuaternion Assimp::tinyUsdzQuatToAiQuat(const std::array<float, 4> &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

+ 29 - 0
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 <assimp/BaseImporter.h>
+#include <assimp/scene.h>
+#include <assimp/types.h>
+#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<float, 3> &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<float, 4> &quatIn);
+
+} // namespace Assimp
+#endif // AI_USDLOADER_IMPL_TINYUSDZ_HELPER_H_INCLUDED

+ 116 - 0
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 <memory>
+
+// internal headers
+#include <assimp/ai_assert.h>
+#include <assimp/anim.h>
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/fast_atof.h>
+#include <assimp/Importer.hpp>
+#include <assimp/importerdesc.h>
+#include <assimp/IOStreamBuffer.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/scene.h>
+#include <assimp/StringUtils.h>
+#include <assimp/StreamReader.h>
+
+#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

+ 59 - 0
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 <assimp/BaseImporter.h>
+#include <assimp/types.h>
+#include <vector>
+#include <cstdint>
+
+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

+ 50 - 0
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

+ 130 - 0
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:

+ 3 - 2
code/Common/BaseImporter.cpp

@@ -250,9 +250,10 @@ void BaseImporter::GetExtensionList(std::set<std::string> &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<std::string> extensions;
-    for (const char* ext : {ext0, ext1, ext2}) {
+    for (const char* ext : {ext0, ext1, ext2, ext3}) {
         if (ext == nullptr) continue;
         extensions.emplace(ext);
     }

+ 6 - 0
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<BaseImporter *> &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

+ 13 - 0
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.

+ 57 - 0
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 <sstream>
+#include <android/log.h>
+
+#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)

+ 40 - 0
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)
+```
+

+ 42 - 0
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)

+ 1 - 0
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)

+ 2 - 1
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

+ 1 - 0
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

+ 3 - 0
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)

+ 154 - 0
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 = </root/materials/Material/preview/Principled_BSDF.outputs:surface>
+            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 = </root/materials/Material>
+            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 = </root/Cube/Cube/Key_1>
+            prepend rel skel:skeleton = </root/Cube/Skel>
+            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 = </root/Cube/Skel/Anim>
+
+            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
+        }
+    }
+}
+

+ 101 - 0
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 = </_materials/Material>
+        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 = </_materials/Material/preview/Principled_BSDF.outputs:surface>
+
+        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 = </_materials/Material/preview/Image_Texture.outputs:rgb>
+                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 = </_materials/Material/preview/uvmap.outputs:result>
+                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
+    }
+}
+

BIN
test/models-nonbsd/USD/usda/textures/01.jpg


BIN
test/models-nonbsd/USD/usda/textures/checkerboard.png


BIN
test/models-nonbsd/USD/usda/textures/texture-cat.jpg


+ 55 - 0
test/models-nonbsd/USD/usda/translated-cube.usda

@@ -0,0 +1,55 @@
+#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"
+    {
+        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]
+        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)]
+        uniform token subdivisionScheme = "none"
+    }
+}
+
+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
+    }
+}
+

+ 4 - 0
test/models-nonbsd/USD/usdc/README.md

@@ -0,0 +1,4 @@
+[blendshape.usdc](blendshape.usdc) copied from tinyusdz/models (No attribution/license cited in that project)
+[suzanne.usdc](suzanne.usdc) copied from tinyusdz/models (No attribution/license cited in that project)
+[texturedcube.usdc](texturedcube.usdc) copied from tinyusdz/models (No attribution/license cited in that project)
+[translated-cube.usdc](translated-cube.usdc) copied from tinyusdz/models (No attribution/license cited in that project)

BIN
test/models-nonbsd/USD/usdc/blendshape.usdc


BIN
test/models-nonbsd/USD/usdc/suzanne.usdc


BIN
test/models-nonbsd/USD/usdc/texturedcube.usdc


BIN
test/models-nonbsd/USD/usdc/textures/01.jpg


BIN
test/models-nonbsd/USD/usdc/textures/checkerboard.png


BIN
test/models-nonbsd/USD/usdc/textures/texture-cat.jpg


BIN
test/models-nonbsd/USD/usdc/translated-cube.usdc


+ 66 - 0
test/unit/utUSDImport.cpp

@@ -0,0 +1,66 @@
+/*
+---------------------------------------------------------------------------
+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.
+                ---------------------------------------------------------------------------
+                */
+#include "UnitTestPCH.h"
+
+#include "AbstractImportExportBase.h"
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <assimp/Exporter.hpp>
+#include <assimp/Importer.hpp>
+
+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);
+    }
+};