USD model support in Scene3D 71/317371/26
authorRichard Huang <r.huang@samsung.com>
Mon, 9 Sep 2024 12:33:26 +0000 (13:33 +0100)
committerRichard Huang <r.huang@samsung.com>
Thu, 26 Sep 2024 10:24:42 +0000 (11:24 +0100)
Change-Id: I3df770423fdc2d16d480341eb30f327ecfd47c70

41 files changed:
automated-tests/build.sh
automated-tests/execute.sh
automated-tests/resources/usd/AntiqueCamera.usdz [new file with mode: 0644]
automated-tests/resources/usd/Avocado.usdz [new file with mode: 0644]
automated-tests/resources/usd/BarramundiFish.usdz [new file with mode: 0644]
automated-tests/resources/usd/BoomBox.usdz [new file with mode: 0644]
automated-tests/resources/usd/CesiumMan.usdz [new file with mode: 0644]
automated-tests/resources/usd/CesiumMilkTruck.usdz [new file with mode: 0644]
automated-tests/resources/usd/Corset.usdz [new file with mode: 0644]
automated-tests/resources/usd/DamagedHelmet.usdz [new file with mode: 0644]
automated-tests/resources/usd/Fox.usdz [new file with mode: 0644]
automated-tests/resources/usd/Lantern.usdz [new file with mode: 0644]
automated-tests/resources/usd/MetalRoughSpheresNoTextures.usdz [new file with mode: 0644]
automated-tests/resources/usd/WaterBottle.usdz [new file with mode: 0644]
automated-tests/src/dali-scene3d-internal/utc-Dali-Gltf2LoaderImpl.cpp
automated-tests/src/dali-scene3d/utc-Dali-ShaderManager.cpp
automated-tests/src/dali-usd-loader/CMakeLists.txt [new file with mode: 0644]
automated-tests/src/dali-usd-loader/tct-dali-usd-loader-core.cpp [new file with mode: 0644]
automated-tests/src/dali-usd-loader/utc-Dali-UsdLoader.cpp [new file with mode: 0644]
build/tizen/CMakeLists.txt
build/tizen/dali-usd-loader/CMakeLists.txt [new file with mode: 0644]
build/tizen/dali-usd-loader/dali2-usd-loader.pc.in [new file with mode: 0644]
dali-scene3d/internal/common/model-load-task.cpp
dali-scene3d/internal/common/model-load-task.h
dali-scene3d/internal/loader/dli-loader-impl.h
dali-scene3d/internal/loader/glb-loader-impl.h
dali-scene3d/internal/loader/gltf2-loader-impl.h
dali-scene3d/internal/loader/model-loader-impl.h [deleted file]
dali-scene3d/public-api/loader/material-definition.cpp
dali-scene3d/public-api/loader/material-definition.h
dali-scene3d/public-api/loader/model-loader-impl.h [new file with mode: 0644]
dali-scene3d/public-api/loader/model-loader.cpp
dali-scene3d/public-api/loader/model-loader.h
dali-scene3d/public-api/loader/shader-manager.cpp
dali-usd-loader.manifest [new file with mode: 0644]
dali-usd-loader.manifest-smack [new file with mode: 0644]
dali-usd-loader/internal/create-usd-loader.cpp [new file with mode: 0644]
dali-usd-loader/internal/file.list [new file with mode: 0644]
dali-usd-loader/internal/usd-loader-impl.cpp [new file with mode: 0644]
dali-usd-loader/internal/usd-loader-impl.h [new file with mode: 0644]
packaging/dali-toolkit.spec

index a56cd87e1c78e30d18b91880e90fcc131ef9248f..1fd56b56703a0b8edaa5aea6e75d33420567aea2 100755 (executable)
@@ -41,6 +41,13 @@ function build
     (cd build ; cmake .. -DMODULE=$1 -G "$BUILDSYSTEM" ; $BUILDCMD -j7 )
 }
 
+# Query main build to determine if we are enabling USD loader
+USD_LOADER_ENABLED=0
+(cd ../build/tizen ; cmake -LA -N 2>/dev/null | grep USD_LOADER_ENABLED | grep "\=ON")
+if [ $? -eq 0 ] ; then
+    USD_LOADER_ENABLED=1
+fi
+
 if [ -n "$1" ] ; then
   echo BUILDING ONLY $1
   build $1
@@ -48,9 +55,11 @@ else
   for mod in `ls -1 src/ | grep -v CMakeList `
   do
     if [ $mod != 'common' ] && [ $mod != 'manual' ]; then
-        echo BUILDING $mod
-        build $mod
-        if [ $? -ne 0 ]; then echo "Build failed" ; exit 1; fi
+        if [ $mod != 'dali-usd-loader' ] || [[ $mod == 'dali-usd-loader' && $USD_LOADER_ENABLED == 1 ]]; then
+            echo BUILDING $mod
+            build $mod
+            if [ $? -ne 0 ]; then echo "Build failed" ; exit 1; fi
+        fi
     fi
   done
 fi
index 152b0524e6e5412b34e7a153536a770340eff43f..bcc899a2ec395a4a634dde1752353861870ef494 100755 (executable)
@@ -97,6 +97,7 @@ function output_end
 EOF
 }
 
+modules=`ls -1 build/src | grep -v CMakeFiles | grep -v cmake_install.cmake | grep -v Makefile`
 
 if [ $opt_modules == 1 ] ; then
     modules= get_modules
@@ -116,7 +117,6 @@ find build \( -name "*.gcda" \) -exec rm '{}' \;
 ASCII_BOLD="\e[1m"
 ASCII_RESET="\e[0m"
 
-modules=`ls -1 src/ | grep -v CMakeList | grep -v common | grep -v manual`
 if [ -f summary.xml ] ; then unlink summary.xml ; fi
 
 if [ $opt_tct == 1 ] ; then
diff --git a/automated-tests/resources/usd/AntiqueCamera.usdz b/automated-tests/resources/usd/AntiqueCamera.usdz
new file mode 100644 (file)
index 0000000..32aa478
Binary files /dev/null and b/automated-tests/resources/usd/AntiqueCamera.usdz differ
diff --git a/automated-tests/resources/usd/Avocado.usdz b/automated-tests/resources/usd/Avocado.usdz
new file mode 100644 (file)
index 0000000..2142bf1
Binary files /dev/null and b/automated-tests/resources/usd/Avocado.usdz differ
diff --git a/automated-tests/resources/usd/BarramundiFish.usdz b/automated-tests/resources/usd/BarramundiFish.usdz
new file mode 100644 (file)
index 0000000..4f7b53f
Binary files /dev/null and b/automated-tests/resources/usd/BarramundiFish.usdz differ
diff --git a/automated-tests/resources/usd/BoomBox.usdz b/automated-tests/resources/usd/BoomBox.usdz
new file mode 100644 (file)
index 0000000..954588d
Binary files /dev/null and b/automated-tests/resources/usd/BoomBox.usdz differ
diff --git a/automated-tests/resources/usd/CesiumMan.usdz b/automated-tests/resources/usd/CesiumMan.usdz
new file mode 100644 (file)
index 0000000..9c6e54c
Binary files /dev/null and b/automated-tests/resources/usd/CesiumMan.usdz differ
diff --git a/automated-tests/resources/usd/CesiumMilkTruck.usdz b/automated-tests/resources/usd/CesiumMilkTruck.usdz
new file mode 100644 (file)
index 0000000..7023881
Binary files /dev/null and b/automated-tests/resources/usd/CesiumMilkTruck.usdz differ
diff --git a/automated-tests/resources/usd/Corset.usdz b/automated-tests/resources/usd/Corset.usdz
new file mode 100644 (file)
index 0000000..11cb11a
Binary files /dev/null and b/automated-tests/resources/usd/Corset.usdz differ
diff --git a/automated-tests/resources/usd/DamagedHelmet.usdz b/automated-tests/resources/usd/DamagedHelmet.usdz
new file mode 100644 (file)
index 0000000..fc9e14a
Binary files /dev/null and b/automated-tests/resources/usd/DamagedHelmet.usdz differ
diff --git a/automated-tests/resources/usd/Fox.usdz b/automated-tests/resources/usd/Fox.usdz
new file mode 100644 (file)
index 0000000..c785571
Binary files /dev/null and b/automated-tests/resources/usd/Fox.usdz differ
diff --git a/automated-tests/resources/usd/Lantern.usdz b/automated-tests/resources/usd/Lantern.usdz
new file mode 100644 (file)
index 0000000..adb100b
Binary files /dev/null and b/automated-tests/resources/usd/Lantern.usdz differ
diff --git a/automated-tests/resources/usd/MetalRoughSpheresNoTextures.usdz b/automated-tests/resources/usd/MetalRoughSpheresNoTextures.usdz
new file mode 100644 (file)
index 0000000..671aa65
Binary files /dev/null and b/automated-tests/resources/usd/MetalRoughSpheresNoTextures.usdz differ
diff --git a/automated-tests/resources/usd/WaterBottle.usdz b/automated-tests/resources/usd/WaterBottle.usdz
new file mode 100644 (file)
index 0000000..0d65e12
Binary files /dev/null and b/automated-tests/resources/usd/WaterBottle.usdz differ
index 069941c75c56dea93433193e66235586995e35e7..4f368829878740a1d3c5773ec0bb02854cc9efab 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -232,6 +232,8 @@ int UtcDaliGltfLoaderSuccess1(void)
      Vector3(0, 0, 1),
      true,
      false,
+     false,
+     false,
      true,
      false,
      Scene3D::Material::AlphaModeType::MASK,
@@ -314,6 +316,8 @@ int UtcDaliGltfLoaderSuccess1(void)
       Vector3::ONE,
       true,
       true,
+      false,
+      false,
       true,
       false,
       Scene3D::Material::AlphaModeType::OPAQUE,
@@ -392,6 +396,8 @@ int UtcDaliGltfLoaderSuccess1(void)
     DALI_TEST_EQUAL(md.mSpecularColorFactor, m.mSpecularColorFactor);
     DALI_TEST_EQUAL(md.mNeedAlbedoTexture, m.mNeedAlbedoTexture);
     DALI_TEST_EQUAL(md.mNeedMetallicRoughnessTexture, m.mNeedMetallicRoughnessTexture);
+    DALI_TEST_EQUAL(md.mNeedMetallicTexture, m.mNeedMetallicTexture);
+    DALI_TEST_EQUAL(md.mNeedRoughnessTexture, m.mNeedRoughnessTexture);
     DALI_TEST_EQUAL(md.mNeedNormalTexture, m.mNeedNormalTexture);
     DALI_TEST_EQUAL(md.mAlphaModeType, m.mAlphaModeType);
     DALI_TEST_EQUAL(md.mIsOpaque, m.mIsOpaque);
index f17dcb331afa16a3d503239801c12e284cecaeed..8736f8a5f9edf85c072b95cf7a21254fd7dd301c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -89,6 +89,7 @@ int UtcDaliShaderManagerProduceShader(void)
      {ShaderOption::Type::THREE_TEXTURE, ShaderOption::Type::BASE_COLOR_TEXTURE}},
     {//3
      [](ShaderParameters& p) {
+       p.materialDefinition.mFlags |= MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS;
        p.materialDefinition.mTextureStages.push_back({MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS, {}});
      },
      {ShaderOption::Type::THREE_TEXTURE, ShaderOption::Type::METALLIC_ROUGHNESS_TEXTURE}},
diff --git a/automated-tests/src/dali-usd-loader/CMakeLists.txt b/automated-tests/src/dali-usd-loader/CMakeLists.txt
new file mode 100644 (file)
index 0000000..fce3bd1
--- /dev/null
@@ -0,0 +1,99 @@
+SET(PKG_NAME "dali-usd-loader")
+
+SET(EXEC_NAME "tct-${PKG_NAME}-core")
+SET(RPM_NAME "core-${PKG_NAME}-tests")
+
+SET(CAPI_LIB "dali-usd-loader")
+
+# List of test case sources (Only these get parsed for test cases)
+SET(TC_SOURCES
+  utc-Dali-UsdLoader.cpp
+  )
+
+# List of test harness files (Won't get parsed for test cases)
+SET(TEST_HARNESS_DIR "../dali-toolkit/dali-toolkit-test-utils")
+
+SET(TEST_HARNESS_SOURCES
+  ${TEST_HARNESS_DIR}/toolkit-adaptor.cpp
+  ${TEST_HARNESS_DIR}/toolkit-application.cpp
+  ${TEST_HARNESS_DIR}/toolkit-async-task-manager.cpp
+  ${TEST_HARNESS_DIR}/toolkit-event-thread-callback.cpp
+  ${TEST_HARNESS_DIR}/toolkit-environment-variable.cpp
+  ${TEST_HARNESS_DIR}/toolkit-input-method-context.cpp
+  ${TEST_HARNESS_DIR}/toolkit-input-method-options.cpp
+  ${TEST_HARNESS_DIR}/toolkit-lifecycle-controller.cpp
+  ${TEST_HARNESS_DIR}/toolkit-orientation.cpp
+  ${TEST_HARNESS_DIR}/toolkit-style-monitor.cpp
+  ${TEST_HARNESS_DIR}/toolkit-test-application.cpp
+  ${TEST_HARNESS_DIR}/toolkit-timer.cpp
+  ${TEST_HARNESS_DIR}/toolkit-trigger-event-factory.cpp
+  ${TEST_HARNESS_DIR}/toolkit-window.cpp
+  ${TEST_HARNESS_DIR}/toolkit-scene-holder.cpp
+  ${TEST_HARNESS_DIR}/dali-test-suite-utils.cpp
+  ${TEST_HARNESS_DIR}/dali-toolkit-test-suite-utils.cpp
+  ${TEST_HARNESS_DIR}/dummy-control.cpp
+  ${TEST_HARNESS_DIR}/mesh-builder.cpp
+  ${TEST_HARNESS_DIR}/test-actor-utils.cpp
+  ${TEST_HARNESS_DIR}/test-animation-data.cpp
+  ${TEST_HARNESS_DIR}/test-application.cpp
+  ${TEST_HARNESS_DIR}/test-button.cpp
+  ${TEST_HARNESS_DIR}/test-harness.cpp
+  ${TEST_HARNESS_DIR}/test-gesture-generator.cpp
+  ${TEST_HARNESS_DIR}/test-gl-abstraction.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-sync-impl.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-sync-object.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-buffer.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-command-buffer.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-framebuffer.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-controller.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-texture.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-sampler.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-program.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-pipeline.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-shader.cpp
+  ${TEST_HARNESS_DIR}/test-graphics-reflection.cpp
+  ${TEST_HARNESS_DIR}/test-platform-abstraction.cpp
+  ${TEST_HARNESS_DIR}/test-render-controller.cpp
+  ${TEST_HARNESS_DIR}/test-render-surface.cpp
+  ${TEST_HARNESS_DIR}/test-trace-call-stack.cpp
+)
+
+PKG_CHECK_MODULES(${CAPI_LIB} REQUIRED
+  dali2-core
+  dali2-adaptor
+  dali2-toolkit
+  dali2-scene3d
+  dali2-usd-loader
+)
+
+ADD_COMPILE_OPTIONS( -O0 -ggdb --coverage -Wall -Werror -DDEBUG_ENABLED)
+ADD_COMPILE_OPTIONS( ${${CAPI_LIB}_CFLAGS_OTHER} )
+
+ADD_DEFINITIONS(-DTEST_RESOURCE_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../../resources\" )
+
+FOREACH(directory ${${CAPI_LIB}_LIBRARY_DIRS})
+    SET(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -L${directory}")
+ENDFOREACH(directory ${CAPI_LIB_LIBRARY_DIRS})
+
+INCLUDE_DIRECTORIES(
+    ../../../
+    ${${CAPI_LIB}_INCLUDE_DIRS}
+    ../dali-toolkit/dali-toolkit-test-utils
+)
+
+ADD_CUSTOM_COMMAND(
+  COMMAND ${SCRIPT_DIR}/tcheadgen.sh ${EXEC_NAME}.h ${TC_SOURCES}
+  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+  OUTPUT ${EXEC_NAME}.h
+  COMMENT "Generating test tables"
+  )
+
+ADD_EXECUTABLE(${EXEC_NAME} ${EXEC_NAME}.h ${EXEC_NAME}.cpp ${TC_SOURCES} ${TEST_HARNESS_SOURCES})
+TARGET_LINK_LIBRARIES(${EXEC_NAME}
+    ${${CAPI_LIB}_LIBRARIES}
+    -lpthread -ldl --coverage
+)
+
+INSTALL(PROGRAMS ${EXEC_NAME}
+    DESTINATION ${BIN_DIR}/${EXEC_NAME}
+)
diff --git a/automated-tests/src/dali-usd-loader/tct-dali-usd-loader-core.cpp b/automated-tests/src/dali-usd-loader/tct-dali-usd-loader-core.cpp
new file mode 100644 (file)
index 0000000..e4a7ee0
--- /dev/null
@@ -0,0 +1,9 @@
+#include <test-harness.h>
+
+// Must come second
+#include "tct-dali-usd-loader-core.h"
+
+int main(int argc, char* const argv[])
+{
+  return TestHarness::RunTests(argc, argv, tc_array);
+}
diff --git a/automated-tests/src/dali-usd-loader/utc-Dali-UsdLoader.cpp b/automated-tests/src/dali-usd-loader/utc-Dali-UsdLoader.cpp
new file mode 100644 (file)
index 0000000..f29fb80
--- /dev/null
@@ -0,0 +1,390 @@
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// Enable debug log for test coverage
+#define DEBUG_ENABLED 1
+
+#include <dali-scene3d/public-api/loader/load-result.h>
+#include <dali-scene3d/public-api/loader/model-loader.h>
+#include <dali-scene3d/public-api/loader/resource-bundle.h>
+#include <dali-scene3d/public-api/loader/scene-definition.h>
+#include <dali-scene3d/public-api/loader/shader-manager.h>
+#include <dali-test-suite-utils.h>
+#include <string_view>
+
+using namespace Dali;
+using namespace Dali::Scene3D::Loader;
+
+namespace
+{
+struct Context
+{
+  ResourceBundle::PathProvider pathProvider = [](ResourceType::Value type) {
+    return TEST_RESOURCE_DIR "/";
+  };
+
+  ResourceBundle  resources;
+  SceneDefinition scene;
+  SceneMetadata   metaData;
+
+  std::vector<AnimationDefinition>      animations;
+  std::vector<AnimationGroupDefinition> animationGroups;
+  std::vector<CameraParameters>         cameras;
+  std::vector<LightParameters>          lights;
+
+  LoadResult loadResult{
+    resources,
+    scene,
+    metaData,
+    animations,
+    animationGroups,
+    cameras,
+    lights};
+
+  Dali::Scene3D::Loader::ModelLoader* loader;
+};
+
+struct ExceptionMessageStartsWith
+{
+  const std::string_view expected;
+
+  bool operator()(const std::runtime_error& e)
+  {
+    const bool success = (0 == strncmp(e.what(), expected.data(), expected.size()));
+    if(!success)
+    {
+      printf("Expected: %s, got: %s.\n", expected.data(), e.what());
+    }
+    return success;
+  }
+};
+
+} // namespace
+
+int UtcDaliUsdLoaderFailedToLoad(void)
+{
+  Context ctx;
+
+  ctx.loader = new Dali::Scene3D::Loader::ModelLoader(TEST_RESOURCE_DIR "non-existent.usdz", ctx.pathProvider(ResourceType::Mesh) + "/", ctx.loadResult);
+  DALI_TEST_EQUAL(ctx.loader->LoadModel(ctx.pathProvider, true), false);
+
+  DALI_TEST_EQUAL(0, ctx.scene.GetRoots().size());
+  DALI_TEST_EQUAL(0, ctx.scene.GetNodeCount());
+
+  DALI_TEST_EQUAL(0, ctx.resources.mEnvironmentMaps.size());
+  DALI_TEST_EQUAL(0, ctx.resources.mMaterials.size());
+  DALI_TEST_EQUAL(0, ctx.resources.mMeshes.size());
+  DALI_TEST_EQUAL(0, ctx.resources.mShaders.size());
+  DALI_TEST_EQUAL(0, ctx.resources.mSkeletons.size());
+
+  DALI_TEST_EQUAL(0, ctx.cameras.size());
+  DALI_TEST_EQUAL(0, ctx.lights.size());
+  DALI_TEST_EQUAL(0, ctx.animations.size());
+  DALI_TEST_EQUAL(0, ctx.animationGroups.size());
+
+  delete ctx.loader;
+
+  END_TEST;
+}
+
+int UtcDaliUsdLoaderSuccess1(void)
+{
+  TestApplication app;
+
+  Context ctx;
+
+  /**
+   * Converted from the CesiumMan glTF file and its Assets
+   * Donated by Cesium for glTF testing
+   * Take from https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/CesiumMan
+   */
+  ctx.loader = new Dali::Scene3D::Loader::ModelLoader(TEST_RESOURCE_DIR "/usd/CesiumMan.usdz", ctx.pathProvider(ResourceType::Mesh) + "/", ctx.loadResult);
+  DALI_TEST_EQUAL(ctx.loader->LoadModel(ctx.pathProvider, true), true);
+
+  auto& resources = ctx.resources;
+  resources.GenerateResources();
+
+  auto& scene = ctx.scene;
+  auto& roots = scene.GetRoots();
+
+  DALI_TEST_EQUAL(1u, roots.size());
+  DALI_TEST_EQUAL(7u, scene.GetNodeCount());
+
+  // Default envmap is used
+  DALI_TEST_EQUAL(1u, resources.mEnvironmentMaps.size());
+
+  // Check meshes
+  auto& meshes = resources.mMeshes;
+  DALI_TEST_EQUAL(1u, meshes.size());
+  {
+    auto& md = meshes[0u].first;
+    DALI_TEST_EQUAL(md.mFlags, uint32_t(MeshDefinition::U32_INDICES));
+    DALI_TEST_EQUAL(md.mPrimitiveType, Geometry::TRIANGLES);
+    DALI_TEST_CHECK(md.mRawData);
+    DALI_TEST_EQUAL(md.mRawData->mIndices.size(), 28032u);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs.size(), 5u);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[0].mName, "aPosition");
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[0].mType, Property::VECTOR3);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[0].mNumElements, 14016u);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[0].mData.size(), 168192u);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[1].mName, "aNormal");
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[1].mType, Property::VECTOR3);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[1].mNumElements, 14016u);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[1].mData.size(), 168192u);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[2].mName, "aTexCoord");
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[2].mType, Property::VECTOR2);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[2].mNumElements, 14016u);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[2].mData.size(), 112128u);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[3].mName, "aTangent");
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[3].mType, Property::VECTOR3);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[3].mNumElements, 14016u);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[3].mData.size(), 168192u);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[4].mName, "aVertexColor");
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[4].mType, Property::VECTOR4);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[4].mNumElements, 14016u);
+    DALI_TEST_EQUAL(md.mRawData->mAttribs[4].mData.size(), 224256u);
+    DALI_TEST_CHECK(meshes[0u].second.geometry);
+  }
+
+  // Check materials
+  auto& materials = resources.mMaterials;
+  DALI_TEST_EQUAL(1u, materials.size());
+  {
+    auto& md = materials[0u].first;
+    DALI_TEST_EQUAL(md.mFlags, MaterialDefinition::ALBEDO | MaterialDefinition::GLTF_CHANNELS);
+    DALI_TEST_EQUAL(md.mEnvironmentIdx, 0);
+    DALI_TEST_EQUAL(md.mColor, Color::WHITE);
+    DALI_TEST_EQUAL(md.mMetallic, 1.0f);
+    DALI_TEST_EQUAL(md.mRoughness, 1.0f);
+    DALI_TEST_EQUAL(md.mBaseColorFactor, Vector4::ONE);
+    DALI_TEST_EQUAL(md.mNormalScale, 1.0f);
+    DALI_TEST_EQUAL(md.mOcclusionStrength, 1.0f);
+    DALI_TEST_EQUAL(md.mEmissiveFactor, Vector3::ZERO);
+    DALI_TEST_EQUAL(md.mIor, -1.0f);
+    DALI_TEST_EQUAL(md.mDielectricSpecular, 0.04f);
+    DALI_TEST_EQUAL(md.mSpecularFactor, 1.0f);
+    DALI_TEST_EQUAL(md.mSpecularColorFactor, Vector3::ONE);
+    DALI_TEST_EQUAL(md.mNeedAlbedoTexture, true);
+    DALI_TEST_EQUAL(md.mNeedMetallicRoughnessTexture, false);
+    DALI_TEST_EQUAL(md.mNeedMetallicTexture, false);
+    DALI_TEST_EQUAL(md.mNeedRoughnessTexture, false);
+    DALI_TEST_EQUAL(md.mNeedNormalTexture, false);
+    DALI_TEST_EQUAL(md.mAlphaModeType, Scene3D::Material::AlphaModeType::OPAQUE);
+    DALI_TEST_EQUAL(md.mIsOpaque, true);
+    DALI_TEST_EQUAL(md.mIsMask, false);
+
+    DALI_TEST_EQUAL(md.mTextureStages.size(), 1u);
+
+    auto iTexture = md.mTextureStages.begin();
+    DALI_TEST_EQUAL(iTexture->mSemantic, uint32_t(MaterialDefinition::ALBEDO));
+    DALI_TEST_EQUAL(iTexture->mTexture.mImageUri, "");
+    DALI_TEST_EQUAL(uint32_t(iTexture->mTexture.mSamplerFlags), uint32_t(SamplerFlags::DEFAULT)); // don't interpret it as a character
+    DALI_TEST_EQUAL(iTexture->mTexture.mMinImageDimensions, ImageDimensions());
+    DALI_TEST_EQUAL(iTexture->mTexture.mSamplingMode, SamplingMode::BOX_THEN_LINEAR);
+    DALI_TEST_EQUAL(iTexture->mTexture.mTextureBuffer.size(), 209908u);
+
+    auto& ts = materials[0u].second;
+    DALI_TEST_EQUAL(ts.GetTextureCount(), 5u);
+    DALI_TEST_EQUAL(ts.GetTexture(0).GetWidth(), 1024);
+    DALI_TEST_EQUAL(ts.GetTexture(0).GetHeight(), 1024);
+    DALI_TEST_EQUAL(ts.GetTexture(1).GetWidth(), 1);
+    DALI_TEST_EQUAL(ts.GetTexture(1).GetHeight(), 1);
+    DALI_TEST_EQUAL(ts.GetTexture(2).GetWidth(), 256);
+    DALI_TEST_EQUAL(ts.GetTexture(2).GetHeight(), 256);
+    DALI_TEST_EQUAL(ts.GetTexture(3).GetWidth(), 1);
+    DALI_TEST_EQUAL(ts.GetTexture(3).GetHeight(), 1);
+    DALI_TEST_EQUAL(ts.GetTexture(4).GetWidth(), 1);
+    DALI_TEST_EQUAL(ts.GetTexture(4).GetHeight(), 1);
+  }
+
+  DALI_TEST_EQUAL(0u, resources.mShaders.size());
+  DALI_TEST_EQUAL(0u, resources.mSkeletons.size());
+
+  Scene3D::Loader::ShaderManagerPtr shaderManager = new Scene3D::Loader::ShaderManager();
+  ViewProjection                    viewProjection;
+  Transforms                        xforms{
+    MatrixStack{},
+    viewProjection};
+  NodeDefinition::CreateParams nodeParams{
+    resources,
+    xforms,
+    shaderManager,
+  };
+
+  Customization::Choices choices;
+
+  // Create DALi actors
+  Actor root = Actor::New();
+  SetActorCentered(root);
+  for(auto iRoot : roots)
+  {
+    if(auto actor = scene.CreateNodes(iRoot, choices, nodeParams))
+    {
+      scene.ConfigureSkinningShaders(resources, actor, std::move(nodeParams.mSkinnables));
+      scene.ApplyConstraints(actor, std::move(nodeParams.mConstrainables));
+      root.Add(actor);
+    }
+  }
+
+  DALI_TEST_CHECK(root.FindChildByName("Z_UP"));
+  DALI_TEST_CHECK(root.FindChildByName("Armature"));
+
+  delete ctx.loader;
+
+  END_TEST;
+}
+
+int UtcDaliUsdLoaderSuccess2(void)
+{
+  TestApplication app;
+
+  Customization::Choices choices;
+  for(auto modelName : {
+        /**
+         * Converted from the AntiqueCamera glTF file and its Assets
+         * Donated by UX3D for glTF testing
+         * Take from https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/AntiqueCamera
+         */
+        "AntiqueCamera",
+        /**
+         * Converted from the Avocado glTF file and its Assets
+         * Donated by Microsoft for glTF testing
+         * Take from https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Avocado
+         */
+        "Avocado",
+        /**
+         * Converted from the BoomBox glTF file and its Assets
+         * Donated by Microsoft for glTF testing
+         * Take from https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/BoomBox
+         */
+        "BoomBox",
+        /**
+         * Converted from the BarramundiFish glTF file and its Assets
+         * Donated by Microsoft for glTF testing
+         * Take from https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/BarramundiFish
+         */
+        "BarramundiFish",
+        /**
+         * Converted from the CesiumMan glTF file and its Assets
+         * Donated by Cesium for glTF testing
+         * Take from https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/CesiumMan
+         */
+        "CesiumMan",
+        /**
+         * Converted from the CesiumMilkTruck glTF file and its Assets
+         * Donated by Cesium for glTF testing
+         * Take from https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/CesiumMilkTruck
+         */
+        "CesiumMilkTruck",
+        /**
+         * Converted from the DamagedHelmet glTF file and its Assets
+         * By theblueturtle, published under a Creative Commons Attribution-NonCommercial license
+         * Take from https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/DamagedHelmet
+         */
+        "DamagedHelmet",
+        /**
+         * Converted from the Fox glTF file and its Assets
+         * By PixelMannen, published under CC-BY 4.0 license
+         * Take from https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Fox
+         */
+        "Fox",
+        /**
+         * Converted from the Lantern glTF file and its Assets
+         * Donated by Microsoft for glTF testing
+         * Take from https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Lantern
+         */
+        "Lantern",
+        /**
+         * Converted from the MetalRoughSpheresNoTextures glTF file and its Assets
+         * Donated by Kirill Gavrilov for glTF testing
+         * Take from https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/MetalRoughSpheresNoTextures
+         */
+        "MetalRoughSpheresNoTextures",
+        /**
+         * Converted from the WaterBottle glTF file and its Assets
+         * Donated by Microsoft for glTF testing
+         * Take from https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/WaterBottle
+         */
+        "WaterBottle",
+      })
+  {
+    Context ctx;
+
+    auto& resources = ctx.resources;
+    resources.mEnvironmentMaps.push_back({});
+
+    const std::string resourcePath = TEST_RESOURCE_DIR "/usd/";
+
+    ctx.loader = new Dali::Scene3D::Loader::ModelLoader(resourcePath + modelName + ".usdz", ctx.pathProvider(ResourceType::Mesh) + "/", ctx.loadResult);
+    DALI_TEST_EQUAL(ctx.loader->LoadModel(ctx.pathProvider, true), true);
+
+    auto& scene = ctx.scene;
+    DALI_TEST_CHECK(scene.GetNodeCount() > 0);
+
+    resources.GenerateResources();
+
+    DALI_TEST_CHECK(resources.mMaterials.size() > 0);
+
+    for(auto iRoot : scene.GetRoots())
+    {
+      struct Visitor : NodeDefinition::IVisitor
+      {
+        struct ResourceReceiver : IResourceReceiver
+        {
+          std::vector<bool> mCounts;
+
+          void Register(ResourceType::Value type, Index id) override
+          {
+            if(type == ResourceType::Mesh)
+            {
+              mCounts[id] = true;
+            }
+          }
+        } receiver;
+
+        void Start(NodeDefinition& n) override
+        {
+          for(auto& renderable : n.mRenderables)
+          {
+            renderable->RegisterResources(receiver);
+          }
+        }
+
+        void Finish(NodeDefinition& n) override
+        {
+        }
+      } visitor;
+
+      visitor.receiver.mCounts.resize(resources.mMeshes.size(), false);
+
+      scene.Visit(iRoot, choices, visitor);
+
+      for(uint32_t i0 = 0, i1 = resources.mMeshes.size(); i0 < i1; ++i0)
+      {
+        if(visitor.receiver.mCounts[i0])
+        {
+          DALI_TEST_CHECK(!resources.mMeshes[i0].first.mRawData->mAttribs.empty());
+          DALI_TEST_CHECK(resources.mMeshes[i0].second.geometry);
+        }
+      }
+    }
+
+    delete ctx.loader;
+  }
+
+  END_TEST;
+}
index 9cd1e6b28f03d6126d3e1271bcd3a7d026ca9335..805d86f07eddb0416a9d9819d8bf144d99bfcf96 100644 (file)
@@ -37,6 +37,29 @@ OPTION(CONFIGURE_AUTOMATED_TESTS "Configure automated tests" ON)
 OPTION(USE_DEFAULT_RESOURCE_DIR  "Whether to use the default resource folders. Otherwise set environment variables for DALI_IMAGE_DIR, DALI_SOUND_DIR, DALI_STYLE_DIR, DALI_STYLE_IMAGE_DIR and DALI_DATA_READ_ONLY_DIR" ON)
 OPTION(BUILD_SCENE3D             "Whether to build dali-scene3d." ON)
 OPTION(BUILD_PHYSICS             "Whether to build dali-physics." ON)
+OPTION(BUILD_USD_LOADER          "Whether to build dali-usd-loader." OFF)
+
+# Search for OpenUSD headers and libraries
+find_path(OpenUSD_INCLUDE_DIR pxr/usd/usd/stage.h
+          PATHS ${CMAKE_INSTALL_PREFIX}/ /usr/local/USD /usr/USD
+          PATH_SUFFIXES include)
+
+find_library(OpenUSD_LIB usd_usd
+             PATHS ${CMAKE_INSTALL_PREFIX}/ /usr/local/USD /usr/USD
+             PATH_SUFFIXES lib)
+
+# Check if both include and library are found
+if(OpenUSD_INCLUDE_DIR AND OpenUSD_LIB)
+    SET( BUILD_USD_LOADER ON )
+    get_filename_component(USD_ROOT_DIR ${OpenUSD_INCLUDE_DIR} DIRECTORY)
+    message(STATUS "OpenUSD found at: ${USD_ROOT_DIR}")
+else()
+    message(WARNING "OpenUSD not found. Skipping the compilation of usd-loader files.")
+endif()
+
+if( BUILD_USD_LOADER )
+    OPTION(USD_LOADER_ENABLED "Whether dali-usd-loader is enabled." ON)
+endif()
 
 IF( ENABLE_PKG_CONFIGURE )
   FIND_PACKAGE( PkgConfig REQUIRED )
@@ -578,6 +601,10 @@ IF ( BUILD_PHYSICS )
   ADD_SUBDIRECTORY( ${CMAKE_CURRENT_SOURCE_DIR}/dali-physics )
 ENDIF()
 
+IF ( BUILD_USD_LOADER )
+  ADD_SUBDIRECTORY( ${CMAKE_CURRENT_SOURCE_DIR}/dali-usd-loader )
+ENDIF()
+
 # Build documentation if doxygen tool is available
 SET( doxygenEnabled OFF )
 IF( DOXYGEN_FOUND )
@@ -673,6 +700,7 @@ MESSAGE( STATUS "Enable link test:              " ${ENABLE_LINK_TEST} )
 MESSAGE( STATUS "Configure automated tests:     " ${CONFIGURE_AUTOMATED_TESTS} )
 MESSAGE( STATUS "Build Dali Scene3D:            " ${BUILD_SCENE3D} )
 MESSAGE( STATUS "Build Dali Physics:            " ${BUILD_PHYSICS} )
+MESSAGE( STATUS "Build Dali USD Loader:         " ${BUILD_USD_LOADER} )
 MESSAGE( STATUS "CXXFLAGS:                      " ${CMAKE_CXX_FLAGS} )
 MESSAGE( STATUS "LDFLAGS:                       " ${CMAKE_SHARED_LINKER_FLAGS_INIT}${CMAKE_SHARED_LINKER_FLAGS} )
 
diff --git a/build/tizen/dali-usd-loader/CMakeLists.txt b/build/tizen/dali-usd-loader/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e64535a
--- /dev/null
@@ -0,0 +1,138 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 3.8.2)
+set(name "dali2-usd-loader")
+
+project(${name} CXX)
+
+set(${name}_VERSION_MAJOR 2)
+set(${name}_VERSION_MINOR 0)
+set(${name}_VERSION_PATCH 0)
+set(${name}_VERSION ${${name}_VERSION_MAJOR}.${${name}_VERSION_MINOR}.${${name}_VERSION_PATCH} )
+
+SET(DALI_USD_LOADER_VERSION ${${name}_VERSION} )
+
+if(CMAKE_BUILD_TYPE MATCHES Debug)
+    add_definitions("-DDEBUG_ENABLED")
+endif()
+
+add_definitions("-DBUILDING_DALI_USD_LOADER")
+
+foreach(flag ${PKGS_CFLAGS})
+    set(extra_flags "${extra_flags} ${flag}")
+endforeach(flag)
+
+# The -fPIC option used here is to generate position-independent code (PIC) suitable for use in shared libraries.
+# Unlike the -fPIE option which generates position-independent executables (PIE) suitable for use in executable binaries.
+set(prj_cxx_std c++17)
+if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
+    set(extra_flags "${extra_flags} -fPIC -std=${prj_cxx_std}")
+elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+    set(extra_flags "${extra_flags} -fPIC -std=${prj_cxx_std}")
+elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
+    set(extra_flags "${extra_flags} /std:${prj_cxx_std} /vmg /D_USE_MATH_DEFINES /D_CRT_SECURE_NO_WARNINGS /MP /GS /Oi /GL /EHsc")
+endif()
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${extra_flags} -Wno-deprecated")
+
+set(prefix ${CMAKE_INSTALL_PREFIX})
+
+set(repo_root_dir "${CMAKE_CURRENT_LIST_DIR}/../../..")
+set(usd_loader_dir "${repo_root_dir}/dali-usd-loader")
+
+option(ENABLE_PKG_CONFIGURE "Use pkgconfig" ON)
+option(ENABLE_COVERAGE "Coverage" OFF)
+
+if (ENABLE_PKG_CONFIGURE)
+    find_package(PkgConfig REQUIRED)
+
+    pkg_check_modules(DALICORE REQUIRED dali2-core)
+    pkg_check_modules(DALIADAPTOR REQUIRED dali2-adaptor)
+
+    # Configure the pkg-config file
+    # Requires the following variables to be setup:
+    # @PREFIX@ @EXEC_PREFIX@ @DALI_VERSION@ @LIB_DIR@ @DEV_INCLUDE_PATH@
+    set( LIB_DIR $ENV{libdir} )
+    if( NOT LIB_DIR )
+        set( LIB_DIR ${CMAKE_INSTALL_LIBDIR} )
+    endif()
+    if( NOT LIB_DIR )
+        set( LIB_DIR ${prefix}/lib )
+    endif()
+
+    set(PREFIX ${prefix})
+    set(EXEC_PREFIX ${CMAKE_INSTALL_PREFIX})
+    set(DEV_INCLUDE_PATH ${INCLUDE_DIR})
+
+    set(core_pkg_cfg_file dali2-usd-loader.pc)
+    configure_file(${CMAKE_CURRENT_LIST_DIR}/${core_pkg_cfg_file}.in ${core_pkg_cfg_file} @ONLY)
+endif()
+
+set(usd_loader_src_files "")
+include(${usd_loader_dir}/internal/file.list)
+
+set(prefix_include_dir "${prefix}/include")
+
+include_directories(BEFORE
+  ${repo_root_dir}
+  ${USD_ROOT_DIR}/include
+)
+
+include_directories(AFTER "${prefix_include_dir}")
+
+MESSAGE(STATUS "USD Loader sources: ${usd_loader_src_files}")
+
+ADD_LIBRARY("${name}" SHARED ${usd_loader_src_files} )
+
+TARGET_LINK_LIBRARIES("${name}" ${DALICORE_LDFLAGS} "-L${USD_ROOT_DIR}/lib"
+  dali2-toolkit
+  dali2-scene3d
+  -lusd_usd -lusd_sdf -lusd_tf -lusd_usdGeom -lusd_usdShade -lusd_usdSkel -lusd_gf -lusd_vt
+  ${COVERAGE})
+TARGET_COMPILE_OPTIONS("${name}" PUBLIC "-I${repo_root_dir}/dali-usd-loader/include")
+
+IF (ENABLE_PKG_CONFIGURE)
+  INSTALL(FILES
+    ${CMAKE_CURRENT_BINARY_DIR}/${core_pkg_cfg_file}
+    DESTINATION ${LIB_DIR}/pkgconfig )
+ENDIF()
+
+IF( INSTALL_CMAKE_MODULES )
+  MESSAGE(STATUS "Installing cmake modules & libs")
+  SET_TARGET_PROPERTIES( ${name}
+    PROPERTIES
+    VERSION ${DALI_USD_LOADER_VERSION}
+    SOVERSION ${${name}_VERSION_MAJOR}
+    CLEAN_DIRECT_OUPUT 1
+    )
+
+  IF( ENABLE_DEBUG )
+    SET( BIN_DIR "${BIN_DIR}/debug" )
+    SET( LIB_DIR "${LIB_DIR}/debug" )
+  ENDIF()
+
+  # Install library
+  INSTALL( TARGETS ${name}
+    EXPORT ${name}-targets
+    LIBRARY DESTINATION ${LIB_DIR}
+    ARCHIVE DESTINATION ${LIB_DIR}
+    RUNTIME DESTINATION ${BIN_DIR}
+  )
+
+  # Install the cmake modules.
+  INSTALL(
+    EXPORT ${name}-targets
+    NAMESPACE ${name}::
+    FILE ${name}-targets.cmake
+    DESTINATION share/${name}
+  )
+
+  FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${name}-config.cmake "
+    include(CMakeFindDependencyMacro)
+    include(\${CMAKE_CURRENT_LIST_DIR}/${name}-targets.cmake)
+  ")
+  INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/${name}-config.cmake DESTINATION share/${name})
+
+ELSE()
+  MESSAGE(STATUS "Installing libs")
+  INSTALL( TARGETS ${name} DESTINATION ${LIB_DIR} )
+
+ENDIF()
diff --git a/build/tizen/dali-usd-loader/dali2-usd-loader.pc.in b/build/tizen/dali-usd-loader/dali2-usd-loader.pc.in
new file mode 100644 (file)
index 0000000..895736d
--- /dev/null
@@ -0,0 +1,12 @@
+prefix=@PREFIX@
+exec_prefix=@EXEC_PREFIX@
+apiversion=@DALI_USD_LOADER_VERSION@
+libdir=@LIB_DIR@
+includedir=@DEV_INCLUDE_PATH@
+
+Name: DALi Engine Usd Loader Library
+Description: Dali Usd Loader library
+Version: ${apiversion}
+Requires: dali2-scene3d
+Libs: -L${libdir} -ldali2-usd-loader
+Cflags: -I${includedir}
index fd634f85c1c123d008339d38cf712b08973883ff..75df573277e5d2d382468e293fccd03c6b94873d 100644 (file)
@@ -77,8 +77,7 @@ void ModelLoadTask::Process()
   if(gTraceFilter && gTraceFilter->IsTraceEnabled())
   {
     mStartTimeNanoSceonds = GetNanoseconds();
-    DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_MODEL_LOADING_TASK", [&](std::ostringstream& oss)
-                                            { oss << "[u:" << mModelUrl << ",dir:" << mResourceDirectoryUrl << "]"; });
+    DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_MODEL_LOADING_TASK", [&](std::ostringstream& oss) { oss << "[u:" << mModelUrl << ",dir:" << mResourceDirectoryUrl << "]"; });
   }
 #endif
 
@@ -88,12 +87,11 @@ void ModelLoadTask::Process()
     mResourceDirectoryUrl = std::string(modelUrl.parent_path()) + "/";
   }
 
-  Dali::Scene3D::Loader::ResourceBundle::PathProvider pathProvider = [&](Dali::Scene3D::Loader::ResourceType::Value type)
-  {
+  Dali::Scene3D::Loader::ResourceBundle::PathProvider pathProvider = [&](Dali::Scene3D::Loader::ResourceType::Value type) {
     return mResourceDirectoryUrl;
   };
 
-  mModelLoader = std::make_shared<Dali::Scene3D::Loader::ModelLoader>(mModelUrl, mResourceDirectoryUrl, mLoadResult);
+  mModelLoader = std::make_unique<Dali::Scene3D::Loader::ModelLoader>(mModelUrl, mResourceDirectoryUrl, mLoadResult);
 
   bool loadSucceeded = false;
 
@@ -135,8 +133,7 @@ void ModelLoadTask::Process()
   if(gTraceFilter && gTraceFilter->IsTraceEnabled())
   {
     mEndTimeNanoSceonds = GetNanoseconds();
-    DALI_TRACE_END_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_MODEL_LOADING_TASK", [&](std::ostringstream& oss)
-                                          {
+    DALI_TRACE_END_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_MODEL_LOADING_TASK", [&](std::ostringstream& oss) {
       oss << std::fixed << std::setprecision(3);
       oss << "[";
       oss << "d:" << static_cast<float>(mEndTimeNanoSceonds - mStartTimeNanoSceonds) / 1000000.0f << "ms ";
index c2baead5b95db7a8e61538162736c54bf27ed80e..eca66e5962cf622e1fb9fc035d80a6a26e81bda4 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_SCENE3D_MODEL_LOAD_TASK_H
 
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -118,12 +118,14 @@ private:
   // Undefined
   ModelLoadTask& operator=(const ModelLoadTask& task) = delete;
 
-  std::string                                         mModelUrl;
-  std::string                                         mResourceDirectoryUrl;
-  std::shared_ptr<Dali::Scene3D::Loader::ModelLoader> mModelLoader;
-  ModelCacheManager                                   mModelCacheManager;
-  Dali::Scene3D::Loader::LoadResult                   mLoadResult;
-  bool                                                mHasSucceeded;
+  using ModelLoaderUniquePtr = std::unique_ptr<Dali::Scene3D::Loader::ModelLoader>;
+
+  std::string                       mModelUrl;
+  std::string                       mResourceDirectoryUrl;
+  ModelLoaderUniquePtr              mModelLoader;
+  ModelCacheManager                 mModelCacheManager;
+  Dali::Scene3D::Loader::LoadResult mLoadResult;
+  bool                              mHasSucceeded;
 };
 
 } // namespace Internal
index ba6b1179a9dcfbab68beb36ebdea294b4cc827f6..d94dbbf6e9d79659eec2e28d8fd4324cfd0b4a54 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef DALI_SCENE3D_LOADER_DLI_LOADER_IMPL_H
 #define DALI_SCENE3D_LOADER_DLI_LOADER_IMPL_H
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <dali/public-api/common/vector-wrapper.h>
 
 // INTERNAL INCLUDES
-#include <dali-scene3d/internal/loader/model-loader-impl.h>
 #include <dali-scene3d/public-api/api.h>
 #include <dali-scene3d/public-api/loader/animation-definition.h>
 #include <dali-scene3d/public-api/loader/customization.h>
 #include <dali-scene3d/public-api/loader/dli-input-parameter.h>
 #include <dali-scene3d/public-api/loader/index.h>
+#include <dali-scene3d/public-api/loader/model-loader-impl.h>
 #include <dali-scene3d/public-api/loader/node-definition.h>
 #include <dali-scene3d/public-api/loader/string-callback.h>
 
@@ -56,7 +56,7 @@ public:
   void SetErrorCallback(StringCallback onError);
 
   /**
-   * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl::LoadMode()
+   * @copydoc Dali::Scene3D::Loader::ModelLoaderImpl::LoadMode()
    */
   bool LoadModel(const std::string& uri, Dali::Scene3D::Loader::LoadResult& result) override;
 
index 05c9d9c52a0471d14037064c75edfbe74703736b..b2610aaf7602cdf6a8150c68faed61baf989ed22 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef DALI_SCENE3D_LOADER_GLB_LOADER_IMPL_H
 #define DALI_SCENE3D_LOADER_GLB_LOADER_IMPL_H
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,8 +22,8 @@
 #include <string>
 
 // INTERNAL INCLUDES
-#include <dali-scene3d/internal/loader/model-loader-impl.h>
 #include <dali-scene3d/public-api/api.h>
+#include <dali-scene3d/public-api/loader/model-loader-impl.h>
 
 namespace Dali::Scene3D::Loader::Internal
 {
@@ -31,7 +31,7 @@ class GlbLoaderImpl : public ModelLoaderImpl
 {
 public:
   /**
-   * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl::LoadMode()
+   * @copydoc Dali::Scene3D::Loader::ModelLoaderImpl::LoadMode()
    */
   bool LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result) override;
 };
index a4dfc6e5c45fc2ecb957374a203da501d006f09c..47f46606a08362e50ff6e84696d716974cdf5843 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef DALI_SCENE3D_LOADER_GLTF2_LOADER_IMPL_H
 #define DALI_SCENE3D_LOADER_GLTF2_LOADER_IMPL_H
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,8 +22,8 @@
 #include <string>
 
 // INTERNAL INCLUDES
-#include <dali-scene3d/internal/loader/model-loader-impl.h>
 #include <dali-scene3d/public-api/api.h>
+#include <dali-scene3d/public-api/loader/model-loader-impl.h>
 
 namespace Dali::Scene3D::Loader::Internal
 {
@@ -31,7 +31,7 @@ class Gltf2LoaderImpl : public ModelLoaderImpl
 {
 public:
   /**
-   * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl::LoadMode()
+   * @copydoc Dali::Scene3D::Loader::odelLoaderImpl::LoadMode()
    */
   bool LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result) override;
 };
diff --git a/dali-scene3d/internal/loader/model-loader-impl.h b/dali-scene3d/internal/loader/model-loader-impl.h
deleted file mode 100644 (file)
index 619cacb..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-#ifndef DALI_SCENE3D_LOADER_MODEL_LOADER_IMPL_H
-#define DALI_SCENE3D_LOADER_MODEL_LOADER_IMPL_H
-/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-// INTERNAL INCLUDES
-#include <dali-scene3d/public-api/api.h>
-#include <dali-scene3d/public-api/loader/load-result.h>
-#include <dali-scene3d/public-api/loader/model-loader.h>
-#include <dali-scene3d/public-api/loader/resource-bundle.h>
-
-// EXTERNAL INCLUDES
-#include <string>
-
-namespace Dali
-{
-namespace Scene3D
-{
-namespace Loader
-{
-namespace Internal
-{
-class ModelLoaderImpl
-{
-public:
-  ModelLoaderImpl() = default;
-
-  /**
-   * @brief Set InputParameter.
-   * Thie method store only a pointer of InputParameter.
-   * The object of InputParameter should not be deleted until it is no longer used.
-   * @param[in] inputParameter Input parameters those can be used for model loading.
-   */
-  void SetInputParameter(Dali::Scene3D::Loader::ModelLoader::InputParameter& inputParameter)
-  {
-    mInputParameter = &inputParameter;
-  }
-
-  /**
-   * @brief Request to load model from url.
-   * @param[in] url model file url.
-   * @param[out] result loaded model data.
-   * @return True if model loading is successfully finished.
-   */
-  virtual bool LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result) = 0;
-
-protected:
-  Dali::Scene3D::Loader::ModelLoader::InputParameter* mInputParameter{nullptr};
-};
-} // namespace Internal
-} // namespace Loader
-} // namespace Scene3D
-} // namespace Dali
-
-#endif // DALI_SCENE3D_LOADER_MODEL_LOADER_IMPL_H
index 27a460b394f32bc8432f419d3960586bcf18f5b4..e97c7b6ee6b5e9f62358e4f5d7380ed5e28ceafe 100644 (file)
@@ -144,6 +144,58 @@ Dali::PixelData LoadImageResource(const std::string& resourcePath,
   }
   return pixelData;
 }
+
+uint32_t CombineMetallicRoughnessTextures(Dali::Devel::PixelBuffer& metallicTexture, Dali::Devel::PixelBuffer& roughnessTexture, Dali::Devel::PixelBuffer& metallicRoughnessTexture)
+{
+  if(metallicTexture.GetWidth() != roughnessTexture.GetWidth() || metallicTexture.GetHeight() != roughnessTexture.GetHeight())
+  {
+    // Resize the metallic texture to match the size of the roughness texture
+    metallicTexture.Resize(roughnessTexture.GetWidth(), roughnessTexture.GetHeight());
+  }
+
+  // Combine the two textures together
+  uint32_t           metallicRoughnessWidth         = roughnessTexture.GetWidth();
+  uint32_t           metallicRoughnessHeight        = roughnessTexture.GetHeight();
+  Pixel::Format      pixelFormatMetallicRoughness   = Pixel::RGBA8888;
+  const unsigned int bytesPerPixelMetallicRoughness = Pixel::GetBytesPerPixel(pixelFormatMetallicRoughness);
+  uint32_t           combinedBufferSize             = metallicRoughnessWidth * metallicRoughnessHeight * bytesPerPixelMetallicRoughness;
+
+  metallicRoughnessTexture = Dali::Devel::PixelBuffer::New(metallicRoughnessWidth,
+                                                           metallicRoughnessHeight,
+                                                           pixelFormatMetallicRoughness);
+
+  const uint8_t* metallicBufferPtr          = metallicTexture.GetBuffer();
+  const uint8_t* roughnessBufferPtr         = roughnessTexture.GetBuffer();
+  uint8_t*       metallicRoughnessBufferPtr = metallicRoughnessTexture.GetBuffer();
+
+  const uint32_t bytesPerPixelMetallic  = Pixel::GetBytesPerPixel(metallicTexture.GetPixelFormat());
+  const uint32_t bytesPerPixelRoughness = Pixel::GetBytesPerPixel(roughnessTexture.GetPixelFormat());
+
+  for(uint32_t y = 0; y < metallicRoughnessHeight; ++y)
+  {
+    for(uint32_t x = 0; x < metallicRoughnessWidth; ++x)
+    {
+      // Go through each pixel from the top to the bottom row by row
+      uint32_t pixelIndex = y * metallicRoughnessWidth + x;
+
+      uint32_t metallicBufferIndex          = pixelIndex * bytesPerPixelMetallic;
+      uint32_t roughnessBufferIndex         = pixelIndex * bytesPerPixelRoughness;
+      uint32_t metallicRoughnessBufferIndex = pixelIndex * bytesPerPixelMetallicRoughness;
+
+      uint8_t metallicValue  = metallicBufferPtr[metallicBufferIndex];
+      uint8_t roughnessValue = roughnessBufferPtr[roughnessBufferIndex];
+
+      // Fill the combined texture buffer
+      metallicRoughnessBufferPtr[metallicRoughnessBufferIndex + 0] = 0u;             // R channel
+      metallicRoughnessBufferPtr[metallicRoughnessBufferIndex + 1] = roughnessValue; // G channel
+      metallicRoughnessBufferPtr[metallicRoughnessBufferIndex + 2] = metallicValue;  // B channel
+      metallicRoughnessBufferPtr[metallicRoughnessBufferIndex + 3] = 0u;             // A channel
+    }
+  }
+
+  return combinedBufferSize;
+}
+
 } // namespace
 
 SamplerFlags::Type SamplerFlags::Encode(FilterMode::Type minFilter, FilterMode::Type magFilter, WrapMode::Type wrapS, WrapMode::Type wrapT)
@@ -287,6 +339,72 @@ MaterialDefinition::LoadRaw(const std::string& imagesPath)
       raw.mTextures.push_back({LoadImageResource(imagesPath, iTexture->mTexture, FittingMode::DEFAULT, true), iTexture->mTexture.mSamplerFlags});
       ++iTexture;
     }
+    else if(checkStage(METALLIC) || checkStage(ROUGHNESS))
+    {
+      // In some cases (e.g. USD model) it could have metallic texture and roughness texture separately,
+      // but what we want is a combined texture for both metallic and roughness.
+
+      Dali::Devel::PixelBuffer metallicTexture;
+      Dali::Devel::PixelBuffer roughnessTexture;
+      SamplerFlags::Type       mMetallicSamplerFlags  = SamplerFlags::DEFAULT;
+      SamplerFlags::Type       mRoughnessSamplerFlags = SamplerFlags::DEFAULT;
+
+      if(checkStage(METALLIC))
+      {
+        if(!iTexture->mTexture.mTextureBuffer.empty())
+        {
+          metallicTexture       = Dali::LoadImageFromBuffer(iTexture->mTexture.mTextureBuffer.data(), iTexture->mTexture.mTextureBuffer.size(), iTexture->mTexture.mMinImageDimensions, FittingMode::DEFAULT, iTexture->mTexture.mSamplingMode, true);
+          mMetallicSamplerFlags = iTexture->mTexture.mSamplingMode;
+        }
+
+        iTexture = mTextureStages.erase(iTexture);
+      }
+
+      if(checkStage(ROUGHNESS))
+      {
+        if(!iTexture->mTexture.mTextureBuffer.empty())
+        {
+          roughnessTexture       = Dali::LoadImageFromBuffer(iTexture->mTexture.mTextureBuffer.data(), iTexture->mTexture.mTextureBuffer.size(), iTexture->mTexture.mMinImageDimensions, FittingMode::DEFAULT, iTexture->mTexture.mSamplingMode, true);
+          mRoughnessSamplerFlags = iTexture->mTexture.mSamplingMode;
+        }
+
+        iTexture = mTextureStages.erase(iTexture);
+      }
+
+      if(metallicTexture && roughnessTexture)
+      {
+        // If we have both metallic texture and roughness texture, combine them together as one metallic-roughness texture
+        // with roughness value in G channel and metallic value in B channel (to match what we support in our PBR shader).
+
+        Dali::Devel::PixelBuffer metallicRoughnessTexture;
+        uint32_t                 combinedBufferSize = CombineMetallicRoughnessTextures(metallicTexture, roughnessTexture, metallicRoughnessTexture);
+
+        uint8_t* metallicRoughnessBufferPtr = metallicRoughnessTexture.GetBuffer();
+        iTexture                            = mTextureStages.insert(iTexture, {MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS, TextureDefinition{std::vector<uint8_t>(metallicRoughnessBufferPtr, metallicRoughnessBufferPtr + combinedBufferSize)}});
+        ++iTexture;
+
+        raw.mTextures.push_back({Devel::PixelBuffer::Convert(metallicRoughnessTexture), mRoughnessSamplerFlags});
+      }
+      else
+      {
+        if(metallicTexture)
+        {
+          const uint8_t* metallicBufferPtr = metallicTexture.GetBuffer();
+          iTexture                         = mTextureStages.insert(iTexture, {MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS, TextureDefinition{std::vector<uint8_t>(metallicBufferPtr, metallicBufferPtr + metallicTexture.GetWidth() * metallicTexture.GetHeight() * Pixel::GetBytesPerPixel(metallicTexture.GetPixelFormat()))}});
+          ++iTexture;
+
+          raw.mTextures.push_back({Devel::PixelBuffer::Convert(metallicTexture), mMetallicSamplerFlags});
+        }
+        else if(roughnessTexture)
+        {
+          const uint8_t* roughnessBufferPtr = roughnessTexture.GetBuffer();
+          iTexture                          = mTextureStages.insert(iTexture, {MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS, TextureDefinition{std::vector<uint8_t>(roughnessBufferPtr, roughnessBufferPtr + roughnessTexture.GetWidth() * roughnessTexture.GetHeight() * Pixel::GetBytesPerPixel(roughnessTexture.GetPixelFormat()))}});
+          ++iTexture;
+
+          raw.mTextures.push_back({Devel::PixelBuffer::Convert(roughnessTexture), mRoughnessSamplerFlags});
+        }
+      }
+    }
     else if(createMetallicRoughnessAndNormal && mNeedMetallicRoughnessTexture)
     {
       // NOTE: we want to set both metallic and roughness to 1.0; dli uses the R & A channels,
index fcd942d0f01177d58b201503cfc5ccc81f9434a7..121d835bec44c543c5b6e3462036f6f0705f7a3a 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef DALI_SCENE3D_LOADER_MATERIAL_DEFINITION_H
 #define DALI_SCENE3D_LOADER_MATERIAL_DEFINITION_H
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -151,8 +151,8 @@ struct DALI_SCENE3D_API MaterialDefinition
     METALLIC       = NthBit(1),
     ROUGHNESS      = NthBit(2),
     NORMAL         = NthBit(3),
-    EMISSIVE       = NthBit(4),
-    OCCLUSION      = NthBit(5),
+    OCCLUSION      = NthBit(4),
+    EMISSIVE       = NthBit(5),
     SPECULAR       = NthBit(6),
     SPECULAR_COLOR = NthBit(7),
     SUBSURFACE     = NthBit(8), // Note: dli-only
@@ -260,9 +260,11 @@ public: // DATA
   float   mSpecularFactor      = 1.0f;
   Vector3 mSpecularColorFactor = Vector3::ONE;
 
-  // For the glTF, each of albedo, metallicRoughness, normal textures are not essential.
+  // For the glTF or USD models, each of albedo, metallic, roughness, normal textures are not essential.
   bool mNeedAlbedoTexture            = true;
   bool mNeedMetallicRoughnessTexture = true;
+  bool mNeedMetallicTexture          = false;
+  bool mNeedRoughnessTexture         = false;
   bool mNeedNormalTexture            = true;
   bool mDoubleSided                  = false;
 
diff --git a/dali-scene3d/public-api/loader/model-loader-impl.h b/dali-scene3d/public-api/loader/model-loader-impl.h
new file mode 100644 (file)
index 0000000..ba55ff0
--- /dev/null
@@ -0,0 +1,68 @@
+#ifndef DALI_SCENE3D_LOADER_MODEL_LOADER_IMPL_H
+#define DALI_SCENE3D_LOADER_MODEL_LOADER_IMPL_H
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/public-api/api.h>
+#include <dali-scene3d/public-api/loader/load-result.h>
+#include <dali-scene3d/public-api/loader/model-loader.h>
+#include <dali-scene3d/public-api/loader/resource-bundle.h>
+
+// EXTERNAL INCLUDES
+#include <string>
+
+namespace Dali
+{
+namespace Scene3D
+{
+namespace Loader
+{
+class ModelLoaderImpl
+{
+public:
+  ModelLoaderImpl() = default;
+
+  virtual ~ModelLoaderImpl() = default;
+
+  /**
+   * @brief Set InputParameter.
+   * Thie method store only a pointer of InputParameter.
+   * The object of InputParameter should not be deleted until it is no longer used.
+   * @param[in] inputParameter Input parameters those can be used for model loading.
+   */
+  void SetInputParameter(Dali::Scene3D::Loader::ModelLoader::InputParameter& inputParameter)
+  {
+    mInputParameter = &inputParameter;
+  }
+
+  /**
+   * @brief Request to load model from url.
+   * @param[in] url model file url.
+   * @param[out] result loaded model data.
+   * @return True if model loading is successfully finished.
+   */
+  virtual bool LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result) = 0;
+
+protected:
+  Dali::Scene3D::Loader::ModelLoader::InputParameter* mInputParameter{nullptr};
+};
+} // namespace Loader
+} // namespace Scene3D
+} // namespace Dali
+
+#endif // DALI_SCENE3D_LOADER_MODEL_LOADER_IMPL_H
index 211b0e0ae0528651a69a8841a4d4548736fae2d3..595e1871c424eccee409f285c12e268b610475d3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  */
 
 // FILE HEADER
+#include <dali-scene3d/public-api/loader/model-loader-impl.h>
 #include <dali-scene3d/public-api/loader/model-loader.h>
 
 // EXTERNAL INCLUDES
 #include <dali/integration-api/debug.h>
+#include <dlfcn.h>
 #include <filesystem>
 #include <memory>
 
@@ -27,7 +29,6 @@
 #include <dali-scene3d/internal/loader/dli-loader-impl.h>
 #include <dali-scene3d/internal/loader/glb-loader-impl.h>
 #include <dali-scene3d/internal/loader/gltf2-loader-impl.h>
-#include <dali-scene3d/internal/loader/model-loader-impl.h>
 
 namespace Dali::Scene3D::Loader
 {
@@ -37,7 +38,15 @@ static constexpr std::string_view OBJ_EXTENSION      = ".obj";
 static constexpr std::string_view GLTF_EXTENSION     = ".gltf";
 static constexpr std::string_view GLB_EXTENSION      = ".glb";
 static constexpr std::string_view DLI_EXTENSION      = ".dli";
+static constexpr std::string_view USD_EXTENSION      = ".usd";
+static constexpr std::string_view USDZ_EXTENSION     = ".usdz";
+static constexpr std::string_view USDA_EXTENSION     = ".usda";
+static constexpr std::string_view USDC_EXTENSION     = ".usdc";
 static constexpr std::string_view METADATA_EXTENSION = "metadata";
+
+const char* USD_LOADER_SO("libdali2-usd-loader.so");
+const char* CREATE_USD_LOADER_SYMBOL("CreateUsdLoader");
+
 } // namespace
 
 ModelLoader::ModelLoader(const std::string& modelUrl, const std::string& resourceDirectoryUrl, Dali::Scene3D::Loader::LoadResult& loadResult)
@@ -48,6 +57,10 @@ ModelLoader::ModelLoader(const std::string& modelUrl, const std::string& resourc
   CreateModelLoader();
 }
 
+ModelLoader::~ModelLoader()
+{
+}
+
 bool ModelLoader::LoadModel(Dali::Scene3D::Loader::ResourceBundle::PathProvider& pathProvider, bool loadOnlyRawResource)
 {
   if(!mImpl)
@@ -110,15 +123,44 @@ void ModelLoader::CreateModelLoader()
 
   if(extension == DLI_EXTENSION)
   {
-    mImpl = std::make_shared<Dali::Scene3D::Loader::Internal::DliLoaderImpl>();
+    mImpl = std::make_unique<Dali::Scene3D::Loader::Internal::DliLoaderImpl>();
   }
   else if(extension == GLTF_EXTENSION)
   {
-    mImpl = std::make_shared<Dali::Scene3D::Loader::Internal::Gltf2LoaderImpl>();
+    mImpl = std::make_unique<Dali::Scene3D::Loader::Internal::Gltf2LoaderImpl>();
   }
   else if(extension == GLB_EXTENSION)
   {
-    mImpl = std::make_shared<Dali::Scene3D::Loader::Internal::GlbLoaderImpl>();
+    mImpl = std::make_unique<Dali::Scene3D::Loader::Internal::GlbLoaderImpl>();
+  }
+  else if(extension == USD_EXTENSION || extension == USDZ_EXTENSION || extension == USDA_EXTENSION || extension == USDC_EXTENSION)
+  {
+    // Attempt to load the USD loader library dynamically
+    // Once loaded we will keep it open so that any subsequent loading of USD models
+    // doesn't require loading the same library repeatedly.
+    void* handle(dlopen(USD_LOADER_SO, RTLD_LAZY));
+    if(!handle)
+    {
+      // The shared library failed to load
+      DALI_LOG_ERROR("ModelLoader::CreateModelLoader, dlopen error: %s\n", dlerror());
+      return;
+    }
+
+    // Dynamically link to the CreateUsdLoader function in the shared library at runtime.
+    using CreateUsdLoaderFunc           = Dali::Scene3D::Loader::ModelLoaderImpl* (*)();
+    CreateUsdLoaderFunc createUsdLoader = reinterpret_cast<CreateUsdLoaderFunc>(dlsym(handle, CREATE_USD_LOADER_SYMBOL));
+
+    if(!createUsdLoader)
+    {
+      // Close the shared library if the symbol couldn't be found
+      dlclose(handle);
+      DALI_LOG_ERROR("Cannot find CreateUsdLoader function: %s\n", dlerror());
+
+      return;
+    }
+
+    // Create an instance of USD loader
+    mImpl = ModelLoaderImplUniquePtr(createUsdLoader());
   }
   else
   {
index 8a660deec8699dbaa8296bd68ae7474ab6a0fec6..64aa8ff87ef9928fe7121f0ccd08f059f0c5db93 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef DALI_SCENE3D_LOADER_MODEL_LOADER_H
 #define DALI_SCENE3D_LOADER_MODEL_LOADER_H
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 namespace Dali::Scene3D::Loader
 {
-namespace Internal
-{
 class ModelLoaderImpl;
-}
 
 class DALI_SCENE3D_API ModelLoader
 {
@@ -48,6 +45,12 @@ public:
    */
   ModelLoader(const std::string& modelUrl, const std::string& resourceDirectoryUrl, Dali::Scene3D::Loader::LoadResult& loadResult);
 
+  /**
+   * @brief ModelLoader Destructor
+   * @SINCE_2_3.43
+   */
+  ~ModelLoader();
+
   /**
    * @brief Request to load model from model url.
    * @SINCE_2_2.17
@@ -124,7 +127,8 @@ private:
   Dali::Scene3D::Loader::LoadResult             mLoadResult;
   Dali::Scene3D::Loader::Customization::Choices mResourceChoices;
 
-  std::shared_ptr<Internal::ModelLoaderImpl> mImpl;
+  using ModelLoaderImplUniquePtr = std::unique_ptr<Dali::Scene3D::Loader::ModelLoaderImpl>;
+  ModelLoaderImplUniquePtr mImpl;
 };
 } // namespace Dali::Scene3D::Loader
 
index 4625e18b2e9675e658568000c201b281de50470b..57d112a851b4a3f79edc7934908eaa3b9d782a53 100644 (file)
@@ -69,7 +69,7 @@ ShaderOption MakeOption(const MaterialDefinition& materialDef, const MeshDefinit
       option.AddOption(ShaderOption::Type::BASE_COLOR_TEXTURE);
     }
 
-    if(materialDef.CheckTextures(MaterialDefinition::METALLIC | MaterialDefinition::ROUGHNESS))
+    if(MaskMatch(materialDef.mFlags, MaterialDefinition::METALLIC) || MaskMatch(materialDef.mFlags, MaterialDefinition::ROUGHNESS))
     {
       option.AddOption(ShaderOption::Type::METALLIC_ROUGHNESS_TEXTURE);
     }
diff --git a/dali-usd-loader.manifest b/dali-usd-loader.manifest
new file mode 100644 (file)
index 0000000..a76fdba
--- /dev/null
@@ -0,0 +1,5 @@
+<manifest>
+       <request>
+               <domain name="_" />
+       </request>
+</manifest>
diff --git a/dali-usd-loader.manifest-smack b/dali-usd-loader.manifest-smack
new file mode 100644 (file)
index 0000000..08561d6
--- /dev/null
@@ -0,0 +1,8 @@
+<manifest>
+       <assign>
+               <filesystem path="/usr/lib/*" label="_" />
+       </assign>
+       <request>
+               <domain name="dali"/>
+       </request>
+</manifest>
diff --git a/dali-usd-loader/internal/create-usd-loader.cpp b/dali-usd-loader/internal/create-usd-loader.cpp
new file mode 100644 (file)
index 0000000..c2da4dc
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <dali-toolkit/public-api/dali-toolkit-common.h>
+
+// INTERNAL INCLUDES
+#include <dali-usd-loader/internal/usd-loader-impl.h>
+
+namespace Dali::Scene3D::Loader
+{
+extern "C" DALI_TOOLKIT_API Dali::Scene3D::Loader::ModelLoaderImpl* CreateUsdLoader()
+{
+  return new UsdLoaderImpl();
+}
+} // namespace Dali::Scene3D::Loader
diff --git a/dali-usd-loader/internal/file.list b/dali-usd-loader/internal/file.list
new file mode 100644 (file)
index 0000000..7bd08c0
--- /dev/null
@@ -0,0 +1,6 @@
+set(usd_loader_internal_dir ${usd_loader_dir}/internal)
+
+set(usd_loader_src_files ${usd_loader_src_files}
+       ${usd_loader_internal_dir}/usd-loader-impl.cpp
+       ${usd_loader_internal_dir}/create-usd-loader.cpp
+)
diff --git a/dali-usd-loader/internal/usd-loader-impl.cpp b/dali-usd-loader/internal/usd-loader-impl.cpp
new file mode 100644 (file)
index 0000000..1645ffc
--- /dev/null
@@ -0,0 +1,2066 @@
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// FILE HEADER
+#include <dali-usd-loader/internal/usd-loader-impl.h>
+
+// EXTERNAL INCLUDES
+#include <dali/integration-api/debug.h>
+
+#include <pxr/usd/ar/asset.h>
+#include <pxr/usd/ar/resolvedPath.h>
+#include <pxr/usd/ar/resolver.h>
+#include <pxr/usd/usd/prim.h>
+#include <pxr/usd/usd/primRange.h>
+#include <pxr/usd/usd/stage.h>
+#include <pxr/usd/usdGeom/camera.h>
+#include <pxr/usd/usdGeom/mesh.h>
+#include <pxr/usd/usdGeom/primvarsAPI.h>
+#include <pxr/usd/usdGeom/xformCommonAPI.h>
+#include <pxr/usd/usdGeom/xformable.h>
+#include <pxr/usd/usdShade/material.h>
+#include <pxr/usd/usdShade/materialBindingAPI.h>
+#include <pxr/usd/usdSkel/animation.h>
+#include <pxr/usd/usdSkel/bindingAPI.h>
+#include <pxr/usd/usdSkel/root.h>
+#include <pxr/usd/usdSkel/skeleton.h>
+#include <pxr/usd/usdSkel/utils.h>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/public-api/loader/load-result.h>
+#include <dali-scene3d/public-api/loader/utils.h>
+
+using namespace Dali;
+using namespace pxr;
+using namespace Dali::Scene3D::Loader;
+
+namespace Dali::Scene3D::Loader
+{
+namespace
+{
+const Vector3 CAMERA_DEFAULT_POSITION(0.0f, 0.0f, 3.5f);
+
+#ifdef DEBUG_ENABLED
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_USD_LOADER");
+#endif
+
+// Utility function to print a specific number of indentation levels
+void PrintLevel(int level)
+{
+  for(int i = 0; i < level; i++)
+  {
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "  ");
+  }
+}
+
+// Convert a USD matrix to a DALi Matrix
+Matrix ConvertUsdMatrix(const GfMatrix4d& gfMat)
+{
+  std::vector<float> matData(gfMat.data(), gfMat.data() + 16);
+  return Matrix(matData.data());
+}
+
+// Template function to retrieve the value of a USD attribute, handling time samples if available
+template<typename T>
+T GetAttributeValue(UsdAttribute attribute, T& value)
+{
+  std::vector<double> times;
+  attribute.GetTimeSamples(&times);
+  if(times.size() > 0u)
+  {
+    attribute.Get<T>(&value, times[0]);
+  }
+  else
+  {
+    attribute.Get<T>(&value, UsdTimeCode::Default());
+  }
+
+  return value;
+}
+
+// Template function to retrieve the flattened value of a USD geometry primvar (e.g., color, normals)
+template<typename T>
+VtArray<T> GetFlattenedPrimvarValue(UsdGeomPrimvar primvar, VtArray<T>& value)
+{
+  std::vector<double> times;
+  primvar.GetAttr().GetTimeSamples(&times);
+
+  if(times.size() > 0u)
+  {
+    primvar.ComputeFlattened<T>(&value, times[0]);
+  }
+  else
+  {
+    primvar.ComputeFlattened<T>(&value, UsdTimeCode::Default());
+  }
+
+  return value;
+}
+
+// Recursively traverses connected shader inputs to collect all relevant shaders
+std::vector<UsdShadeShader> TraverseShaderInputs(const UsdShadeShader& shader)
+{
+  std::vector<UsdShadeShader> matches;
+
+  for(const auto& i : shader.GetInputs())
+  {
+    if(i.HasConnectedSource())
+    {
+      for(const auto& s : i.GetConnectedSources())
+      {
+        if(s)
+        {
+          matches.push_back(s.source);
+          auto nestedMatches = TraverseShaderInputs(s.source);
+          matches.insert(matches.end(), nestedMatches.begin(), nestedMatches.end());
+        }
+      }
+    }
+  }
+
+  return matches;
+}
+
+// Triangulates polygonal faces based on their vertex indices, converting them into triangles.
+//
+// USD can store mesh data in polygons with more than three sides (n-gons). When preparing for
+// rendering, these n-gons must be converted into triangles. This function takes an array of
+// vertex counts per face (e.g., quads, pentagons) and converts these faces into triangles by
+// generating new vertex indices that represent the triangulated mesh.
+//
+// The process of triangulation involves breaking down these polygons (which may have 4, 5,
+// or more vertices) into a set of triangles. Each n-sided polygon is split into n-2 triangles.
+// For example, a quad (4 vertices) is split into two triangles. Triangulation also considers
+// the coordinate system's handedness (left-handed or right-handed), which affects the winding
+// order of vertices in the triangles.
+template<typename T>
+VtArray<T> GetTriangulatedAttribute(const VtArray<int>& countArray, const VtArray<T>& indexArray, bool isLeftHanded)
+{
+  VtArray<T> returnArray;
+  int        j = 0;
+
+  // Iterate over each polygon in the count array
+  for(int count : countArray)
+  {
+    // Extract the indices for the current polygon
+    const VtArray<T> poly(indexArray.begin() + j, indexArray.begin() + j + count);
+
+    // Triangulate the polygon (assumes convex polygons)
+    for(int i = 0; i < count - 2; ++i)
+    {
+      // Append triangulated indices to the return array
+      if(isLeftHanded)
+      {
+        // Left-handed winding order
+        returnArray.push_back(poly[0]);
+        returnArray.push_back(poly[i + 2]);
+        returnArray.push_back(poly[i + 1]);
+      }
+      else
+      {
+        // Right-handed winding order
+        returnArray.push_back(poly[0]);
+        returnArray.push_back(poly[i + 1]);
+        returnArray.push_back(poly[i + 2]);
+      }
+    }
+
+    // Move to the next polygon
+    j += count;
+  }
+
+  return returnArray;
+}
+
+// Converts a USD image path to a standard path format
+std::string ConvertImagePath(const std::string& input)
+{
+  std::string result = input;
+
+  // Find the position of '[' and ']'
+  size_t startPos = result.find('[');
+  size_t endPos   = result.find(']');
+
+  if(startPos != std::string::npos && endPos != std::string::npos)
+  {
+    // Extract the substring between '[' and ']'
+    std::string extracted = result.substr(startPos + 1, endPos - startPos - 1);
+
+    // Find the last '/' in the extracted string between '[' and ']'
+    size_t lastSlashPosInExtracted = extracted.rfind('/', endPos);
+    if(lastSlashPosInExtracted != std::string::npos)
+    {
+      extracted.erase(0, lastSlashPosInExtracted + 1);
+    }
+
+    // Find the last '/' before '[' in the original path
+    size_t lastSlashPos = result.rfind('/', startPos);
+    if(lastSlashPos != std::string::npos)
+    {
+      result.erase(lastSlashPos + 1, endPos - lastSlashPos + 1);
+      result.insert(lastSlashPos + 1, extracted);
+    }
+  }
+
+  return result;
+}
+
+// Loads a USD asset file as a memory buffer (vector of uint8_t)
+std::vector<uint8_t> LoadAssetFileAsBuffer(const std::string resolvedAssetPath)
+{
+  std::shared_ptr<ArAsset> const asset = ArGetResolver().OpenAsset(ArResolvedPath(resolvedAssetPath));
+
+  if(asset)
+  {
+    std::shared_ptr<const char> const buffer = asset->GetBuffer();
+    if(buffer)
+    {
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "LoadAssetFileAsBuffer: %s, size: %lu", ArResolvedPath(resolvedAssetPath).GetPathString().c_str(), asset->GetSize());
+
+      // Convert the buffer to a vector of uint8_t
+      return std::vector<uint8_t>(buffer.get(), buffer.get() + asset->GetSize());
+    }
+  }
+
+  // Return an empty vector if loading fails
+  return std::vector<uint8_t>();
+}
+
+} // namespace
+
+struct UsdLoaderImpl::Impl
+{
+public:
+  /**
+   * @brief Traverses materials in the USD scene and populate the output.
+   * @param[in, out] output The load result.
+   */
+  void TraverseMaterials(LoadResult& output);
+
+  /**
+   * @brief Traverses prims in the USD scene and populate the output.
+   * @param[in, out] output The load result.
+   * @param[in] prim The current USD prim being traversed.
+   * @param[in] parentIndex The index of the parent node in the hierarchy.
+   * @param[in] level The level of nesting in the hierarchy.
+   */
+  void TraversePrims(LoadResult& output, const UsdPrim& prim, Index parentIndex, int level);
+
+private:
+  /**
+   * @brief Converts a mesh prim to the internal representation.
+   * @param[in, out] output The load result.
+   * @param[in] prim The USD prim representing the mesh.
+   * @param[in, out] nodeIndex The index of the current node.
+   * @param[in] parentIndex The index of the parent node.
+   */
+  void ConvertMesh(LoadResult& output, const UsdPrim& prim, Index& nodeIndex, Index parentIndex);
+
+  /**
+   * @brief Converts a node prim to the internal representation.
+   * @param[in, out] output The load result.
+   * @param[in] prim The USD prim representing the node.
+   * @param[in, out] nodeIndex The index of the current node.
+   * @param[in] parentIndex The index of the parent node.
+   */
+  void ConvertNode(LoadResult& output, const UsdPrim& prim, Index& nodeIndex, Index parentIndex);
+
+  /**
+   * @brief Converts a camera prim to the internal representation.
+   * @param[in, out] output The load result.
+   * @param[in] prim The USD prim representing the camera.
+   */
+  void ConvertCamera(LoadResult& output, const UsdPrim& prim);
+
+  /**
+   * @brief Converts a texture associated with a material to the internal representation.
+   * @param[in] usdMaterial The USD material.
+   * @param[in] usdUvTexture The USD UV texture.
+   * @param[in, out] materialDefinition The material definition.
+   * @param[in] semantic The semantic information of the texture.
+   * @return True if conversion successful, false otherwise.
+   */
+  bool ConvertTexture(const UsdShadeMaterial& usdMaterial, const UsdShadeShader& usdUvTexture, MaterialDefinition& materialDefinition, uint32_t semantic = 0u);
+
+  /**
+   * @brief Extracts the transformation (position, rotation, scale) of a given USD primitive.
+   *
+   * This function retrieves the local transformation matrix of a USD prim and decomposes it into
+   * position, rotation, and scale components.
+   *
+   * @param[in] prim The USD primitive from which to extract the transformation.
+   * @param[out] position The extracted position vector.
+   * @param[out] rotation The extracted rotation quaternion.
+   * @param[out] scale The extracted scale vector.
+   * @param[in] time The time at which to sample the transformation.
+   */
+  void GetXformableTransformation(const UsdPrim& prim, Vector3& position, Quaternion& rotation, Vector3& scale, const UsdTimeCode time = UsdTimeCode::Default());
+
+  /**
+   * @brief Adds a node to the scene graph and optionally sets its transformation.
+   *
+   * This function creates a new node based on a USD primitive and adds it to the scene graph.
+   * Optionally, it can set the transformation of the node (position, rotation, scale).
+   *
+   * @param[in,out] scene The scene definition to which the node will be added.
+   * @param[in] nodeName The name of the node to be added.
+   * @param[in] parentIndex The index of the parent node in the scene graph.
+   * @param[in] position The position of the node (if setting transformation).
+   * @param[in] rotation The rotation of the node (if setting transformation).
+   * @param[in] scale The scale of the node (if setting transformation).
+   * @param[in] setTransformation Whether to apply the transformation to the node.
+   * @return A pointer to the created node definition.
+   */
+  NodeDefinition* AddNodeToScene(SceneDefinition& scene, const std::string nodeName, const Index parentIndex, const Vector3& position, const Quaternion& rotation, const Vector3& scale, bool setTransformation);
+
+  /**
+   * @brief Retrieves geometric primitive variables from a USD prim.
+   *
+   * This function extracts texture coordinates (texcoords), vertex colors, and tangent attributes
+   * from a USD primitive and categorizes them into separate vectors.
+   *
+   * @param[in] prim The USD primitive from which to retrieve the primvars.
+   * @param[out] texcoords A vector to store the retrieved texture coordinate primvars.
+   * @param[out] colors A vector to store the retrieved color primvars.
+   * @param[out] tangents A vector to store the retrieved tangent primvars.
+   */
+  void RetrieveGeomPrimvars(const UsdPrim& prim, std::vector<UsdGeomPrimvar>& texcoords, std::vector<UsdGeomPrimvar>& colors, std::vector<UsdGeomPrimvar>& tangents);
+
+  /**
+   * @brief Processes and stores mesh indices in the mesh definition.
+   *
+   * This function processes the triangulated face indices of a mesh, including handling subset indices,
+   * and stores them in the mesh definition.
+   *
+   * @param[in,out] meshDefinition The mesh definition to store the processed indices.
+   * @param[in] indexMap A map of the original to triangulated indices.
+   * @param[in] subsetIdcs The indices belonging to the current subset.
+   * @param[in] triangulatedIndex The triangulated indices for the entire mesh.
+   * @param[out] subIndexArray A vector to store the processed subset indices.
+   * @param[out] flattenedSubTriangulatedIndices A vector to store the flattened triangulated indices.
+   */
+  void ProcessMeshIndices(MeshDefinition& meshDefinition, std::map<int, VtArray<int>>& indexMap, VtIntArray& subsetIdcs, VtArray<int>& triangulatedIndex, std::vector<uint32_t>& subIndexArray, std::vector<uint32_t>& flattenedSubTriangulatedIndices);
+
+  /**
+   * @brief Processes and stores vertex positions in the mesh definition.
+   *
+   * This function processes the vertex positions based on the subset indices and stores them
+   * in the mesh definition.
+   *
+   * @param[in,out] meshDefinition The mesh definition to store the processed vertex positions.
+   * @param[in] points The original vertex positions.
+   * @param[out] worldPosition A vector to store the processed world positions.
+   * @param[in] subIndexArray The subset indices used to extract the relevant positions.
+   */
+  void ProcessMeshPositions(MeshDefinition& meshDefinition, const VtArray<GfVec3f>& points, VtArray<GfVec3f>& worldPosition, std::vector<uint32_t>& subIndexArray);
+
+  /**
+   * @brief Processes and stores vertex normals in the mesh definition.
+   *
+   * This function processes the vertex normals, handling both face-varying and vertex-based normals,
+   * and stores them in the mesh definition.
+   *
+   * @param[in,out] meshDefinition The mesh definition to store the processed normals.
+   * @param[in] usdMesh The USD mesh primitive.
+   * @param[out] normals A vector to store the processed normals.
+   * @param[in] subIndexArray The subset indices used to extract the relevant normals.
+   * @param[in] flattenedSubTriangulatedIndices The flattened triangulated indices for face-varying normals.
+   * @param[in] faceVertexCounts The number of vertices per face.
+   * @param[in] isLeftHanded A flag indicating whether the coordinate system is left-handed.
+   */
+  void ProcessMeshNormals(MeshDefinition& meshDefinition, UsdGeomMesh& usdMesh, VtArray<GfVec3f>& normals, std::vector<uint32_t>& subIndexArray, std::vector<uint32_t>& flattenedSubTriangulatedIndices, VtArray<int>& faceVertexCounts, bool isLeftHanded);
+
+  /**
+   * @brief Generates normals for a mesh if none are provided.
+   *
+   * This function generates normals for a mesh by computing the cross product of adjacent
+   * edges for each face. The generated normals are then stored in the mesh definition.
+   *
+   * @param[in,out] meshDefinition The mesh definition where the generated normals will be stored.
+   */
+  void GenerateNormal(MeshDefinition& meshDefinition);
+
+  /**
+   * @brief Processes and stores texture coordinates (UVs) in the mesh definition.
+   *
+   * This function processes the texture coordinates, handling both face-varying and vertex-based UVs,
+   * and stores them in the mesh definition.
+   *
+   * @param[in,out] meshDefinition The mesh definition to store the processed texture coordinates.
+   * @param[in] texcoords A vector of texture coordinate primvars to process.
+   * @param[in] subIndexArray The subset indices used to extract the relevant UVs.
+   * @param[in] flattenedSubTriangulatedIndices The flattened triangulated indices for face-varying UVs.
+   * @param[in] faceVertexCounts The number of vertices per face.
+   * @param[in] isLeftHanded A flag indicating whether the coordinate system is left-handed.
+   */
+  void ProcessMeshTexcoords(MeshDefinition& meshDefinition, std::vector<UsdGeomPrimvar>& texcoords, std::vector<uint32_t>& subIndexArray, std::vector<uint32_t>& flattenedSubTriangulatedIndices, VtArray<int>& faceVertexCounts, bool isLeftHanded);
+
+  /**
+   * @brief Generates tangent vectors for a mesh.
+   *
+   * This function generates tangent vectors for a mesh based on its texture coordinates (UVs)
+   * and stores them in the mesh definition.
+   *
+   * @param[in,out] meshDefinition The mesh definition where the generated tangents will be stored.
+   * @param[in] texcoords A vector of texture coordinate primvars to assist in tangent generation.
+   */
+  void GenerateTangents(MeshDefinition& meshDefinition, std::vector<UsdGeomPrimvar>& texcoords);
+
+  /**
+   * @brief Processes and stores vertex colors in the mesh definition.
+   *
+   * This function processes the vertex colors, handling different interpolation types (constant, vertex, face-varying),
+   * and stores them in the mesh definition. If no colors are provided, a default white color is assigned.
+   *
+   * @param[in,out] meshDefinition The mesh definition to store the processed vertex colors.
+   * @param[in] colors A vector of color primvars to process.
+   * @param[in] worldPosition The vertex positions to match with colors.
+   * @param[in] subIndexArray The subset indices used to extract the relevant colors.
+   * @param[in] flattenedSubTriangulatedIndices The flattened triangulated indices for face-varying colors.
+   * @param[in] faceVertexCounts The number of vertices per face.
+   * @param[in] isLeftHanded A flag indicating whether the coordinate system is left-handed.
+   */
+  void ProcessMeshColors(MeshDefinition& meshDefinition, std::vector<UsdGeomPrimvar>& colors, VtArray<GfVec3f>& worldPosition, std::vector<uint32_t>& subIndexArray, std::vector<uint32_t>& flattenedSubTriangulatedIndices, VtArray<int>& faceVertexCounts, bool isLeftHanded);
+
+  /**
+   * @brief Processes and binds materials to a mesh subset within a USD prim.
+   *
+   * This function retrieves and assigns the appropriate material to a specific subset of a mesh
+   * within the USD primitive. It updates the material ID used by the mesh subset in the output data.
+   *
+   * @param[in,out] output The load result where the material binding will be stored.
+   * @param[in] prim The USD primitive containing the mesh and its subsets.
+   * @param[in] subsets A vector of geometric subsets (parts of the mesh) within the USD primitive.
+   * @param[in] subIndex The index of the subset within the mesh for which the material is being processed.
+   * @param[out] meshSubMaterialId The material ID that is associated with the subset. It is updated with the correct ID after processing.
+   */
+  void ProcessMaterialBinding(LoadResult& output, const UsdPrim& prim, std::vector<UsdGeomSubset>& subsets, size_t subIndex, int& meshSubMaterialId);
+
+public:
+  UsdStageRefPtr mUsdStage; ///< Pointer to the USD stage.
+
+  std::map<std::string, int> mMaterialMap; ///< Maps prim paths to material IDs.
+
+  Index mNodeIndex; ///< Index of the current node being processed.
+  int   mMeshCount; ///< Count of mesh objects encountered during traversal.
+
+  Index mDefaultMaterial; ///< Index of the default material.
+};
+
+UsdLoaderImpl::UsdLoaderImpl()
+: mImpl{new Impl}
+{
+}
+
+UsdLoaderImpl::~UsdLoaderImpl() = default;
+
+bool UsdLoaderImpl::LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result)
+{
+  // Open the stage of the USD scene from the specified URL
+  mImpl->mUsdStage = UsdStage::Open(url);
+
+  mImpl->mMeshCount       = 0;
+  mImpl->mNodeIndex       = INVALID_INDEX;
+  mImpl->mDefaultMaterial = INVALID_INDEX;
+
+  // Traverse materials in the USD scene and populate the result
+  mImpl->TraverseMaterials(result);
+
+  // Get the index of the root node in the result scene
+  Index rootIndex = result.mScene.GetNodeCount();
+
+  // Create a node definition for the scene root
+  std::unique_ptr<NodeDefinition> sceneRoot{new NodeDefinition()};
+  sceneRoot->mName = "USD_SCENE_ROOT_NODE";
+
+  // Add the scene root node to the result scene
+  result.mScene.AddNode(std::move(sceneRoot));
+  result.mScene.AddRootNode(rootIndex);
+
+  // Traverse prims in the USD scene and populate the result
+  UsdPrim rootPrim = mImpl->mUsdStage->GetPseudoRoot();
+  mImpl->TraversePrims(result, rootPrim, rootIndex, 0);
+
+  // Set default environment map
+  EnvironmentDefinition environmentDefinition;
+  environmentDefinition.mUseBrdfTexture = true;
+  environmentDefinition.mIblIntensity   = Scene3D::Loader::EnvironmentDefinition::GetDefaultIntensity();
+  result.mResources.mEnvironmentMaps.push_back({std::move(environmentDefinition), EnvironmentDefinition::Textures()});
+
+  return true;
+}
+
+void UsdLoaderImpl::Impl::TraverseMaterials(LoadResult& output)
+{
+  auto& outMaterials = output.mResources.mMaterials;
+
+  int materialId = 0; // Initialize material ID counter
+
+  // Traverse all prims (nodes) in the USD stage
+  UsdPrimRange prims = mUsdStage->Traverse();
+  for(auto prim : prims)
+  {
+    // Check if the current prim is a material
+    if(prim.IsA<UsdShadeMaterial>())
+    {
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, " => UsdShadeMaterial: %d: %s\n        ", materialId, prim.GetPrimPath().GetText());
+
+      UsdShadeMaterial material = UsdShadeMaterial(prim);
+      UsdShadeOutput   surf     = material.GetSurfaceOutput();
+
+      // If no valid connected sources are found, skip this material
+      if(surf.GetConnectedSources().size() == 0)
+      {
+        DALI_LOG_ERROR("No valid connected sources, ");
+        continue;
+      }
+
+      UsdShadeConnectableAPI     previewSurface = surf.GetConnectedSources()[0].source;
+      std::vector<UsdShadeInput> inputs         = previewSurface.GetInputs();
+
+      // Initialize the material definition with default values
+      MaterialDefinition materialDefinition;
+
+      materialDefinition.mFlags |= MaterialDefinition::GLTF_CHANNELS;
+      materialDefinition.mShadowAvailable = true;
+
+      materialDefinition.mBaseColorFactor     = Vector4::ONE;
+      materialDefinition.mEmissiveFactor      = Vector3::ZERO;
+      materialDefinition.mSpecularFactor      = 1.0f;
+      materialDefinition.mSpecularColorFactor = Dali::Vector3::ONE;
+
+      materialDefinition.mMetallic    = 1.0f;
+      materialDefinition.mRoughness   = 1.0f;
+      materialDefinition.mNormalScale = 1.0f;
+
+      materialDefinition.mShadowAvailable = true;
+      materialDefinition.mDoubleSided     = false;
+
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "mMaterialMap[%s] = %d, ", prim.GetPrimPath().GetAsString().c_str(), materialId);
+
+      // Map the material path to the material ID
+      mMaterialMap[prim.GetPrimPath().GetAsString()] = materialId;
+      materialId++;
+
+      // Flags to track different material properties
+      bool hasAlpha                     = false;
+      bool hasThreshold                 = false;
+      bool needMetallicRoughnessTexture = false;
+      bool needMetallicTexture          = false;
+      bool needRoughnessTexture         = false;
+      bool needNormalTexture            = false;
+      bool needAlbedoTexture            = false;
+
+      float opacityThreshold(0.0f);
+
+      std::map<uint32_t, UsdShadeInput> shaderInputMap; // Map of texture semantic and shader input
+
+      // Loop through all inputs of the surface shader and sort them to
+      // match with the order of texture loading in MaterialDefinition.
+      for(auto input : inputs)
+      {
+        std::string baseName = input.GetBaseName().GetString();
+
+        // Handle opacity input
+        if(baseName == "opacity")
+        {
+          UsdAttribute opacityAttr = input.GetAttr();
+          float        opacity(1.0f);
+          if(opacityAttr.HasAuthoredValue())
+          {
+            GetAttributeValue<float>(opacityAttr, opacity);
+          }
+
+          DALI_LOG_INFO(gLogFilter, Debug::Verbose, "opacity: %f, ", opacity);
+
+          // Set the alpha value in the base color factor
+          materialDefinition.mBaseColorFactor.a = opacity;
+
+          // Check if the material has transparency
+          if(opacity < 1.0f || input.HasConnectedSource())
+          {
+            hasAlpha = true;
+          }
+
+          DALI_LOG_INFO(gLogFilter, Debug::Verbose, "hasAlpha: %d, ", hasAlpha);
+        }
+        else if(baseName == "opacityThreshold")
+        {
+          // Handle opacity threshold input (for alpha masking)
+          UsdAttribute opacityThresholdAttr = input.GetAttr();
+          if(opacityThresholdAttr.HasAuthoredValue())
+          {
+            GetAttributeValue<float>(opacityThresholdAttr, opacityThreshold);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "opacityThreshold: %.7f, ", opacityThreshold);
+          }
+
+          if(opacityThreshold > 0.0f)
+          {
+            hasThreshold = true;
+          }
+        }
+        else if(baseName == "ior")
+        {
+          // Handle index of refraction (ior)
+          UsdAttribute iorAttr = input.GetAttr();
+          float        ior(1.5f);
+          if(iorAttr.HasAuthoredValue())
+          {
+            GetAttributeValue<float>(iorAttr, ior);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "ior: %.7f, ", ior);
+
+            materialDefinition.mIor                = ior;
+            materialDefinition.mDielectricSpecular = powf((materialDefinition.mIor - 1.0f) / (materialDefinition.mIor + 1.0f), 2.0f);
+          }
+        }
+        else if(baseName == "diffuseColor")
+        {
+          shaderInputMap[MaterialDefinition::ALBEDO] = input;
+        }
+        else if(baseName == "metallic")
+        {
+          shaderInputMap[MaterialDefinition::METALLIC] = input;
+        }
+        else if(baseName == "roughness")
+        {
+          shaderInputMap[MaterialDefinition::ROUGHNESS] = input;
+        }
+        else if(baseName == "normal")
+        {
+          shaderInputMap[MaterialDefinition::NORMAL] = input;
+        }
+        else if(baseName == "occlusion")
+        {
+          shaderInputMap[MaterialDefinition::OCCLUSION] = input;
+        }
+        else if(baseName == "emissiveColor")
+        {
+          shaderInputMap[MaterialDefinition::EMISSIVE] = input;
+        }
+        else if(baseName == "specularColor")
+        {
+          shaderInputMap[MaterialDefinition::SPECULAR_COLOR] = input;
+        }
+        else if(baseName == "useSpecularWorkflow")
+        {
+          UsdAttribute useSpecularWorkflowAttr = input.GetAttr();
+          int          useSpecularWorkflow(0);
+          if(useSpecularWorkflowAttr.HasAuthoredValue())
+          {
+            GetAttributeValue<int>(useSpecularWorkflowAttr, useSpecularWorkflow);
+          }
+          DALI_LOG_INFO(gLogFilter, Debug::Verbose, "useSpecularWorkflow: %d, ", useSpecularWorkflow);
+        }
+      }
+
+      // Process each mapped shader input
+      for(auto iter : shaderInputMap)
+      {
+        UsdShadeInput input = iter.second;
+
+        // Check if the input has a connected texture source
+        UsdShadeShader uvTexture;
+        if(input.HasConnectedSource())
+        {
+          uvTexture = UsdShadeShader(input.GetConnectedSources()[0].source);
+        }
+
+        std::string baseName = input.GetBaseName().GetString();
+
+        if(baseName == "diffuseColor")
+        {
+          // Process diffuse color (albedo)
+          UsdAttribute diffuseAttr = input.GetAttr();
+          if(input.HasConnectedSource())
+          {
+            TfToken id;
+            if(uvTexture.GetShaderId(&id) && id.GetString() == "UsdUVTexture")
+            {
+              // Convert the texture and associate it with the material
+              DALI_LOG_INFO(gLogFilter, Debug::Verbose, "diffuseColorTexture: ");
+              needAlbedoTexture = ConvertTexture(material, uvTexture, materialDefinition, MaterialDefinition::ALBEDO);
+              if(needAlbedoTexture)
+              {
+                DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TraverseMaterials: MaterialDefinition::ALBEDO, ");
+              }
+            }
+          }
+          else
+          {
+            GfVec3f diffuseColor(0.18f, 0.18f, 0.18f);
+            if(diffuseAttr.HasAuthoredValue())
+            {
+              GetAttributeValue<GfVec3f>(diffuseAttr, diffuseColor);
+              DALI_LOG_INFO(gLogFilter, Debug::Verbose, "diffuseColor: %.7f, %.7f, %.7f, ", diffuseColor[0], diffuseColor[1], diffuseColor[2]);
+            }
+
+            // Set the base color factor of the material
+            materialDefinition.mBaseColorFactor.r = diffuseColor[0];
+            materialDefinition.mBaseColorFactor.g = diffuseColor[1];
+            materialDefinition.mBaseColorFactor.b = diffuseColor[2];
+          }
+        }
+        else if(baseName == "metallic")
+        {
+          // Process metallic input
+          UsdAttribute metallicAttr = input.GetAttr();
+
+          if(input.HasConnectedSource())
+          {
+            TfToken id;
+            if(uvTexture.GetShaderId(&id) && id.GetString() == "UsdUVTexture")
+            {
+              // Convert the texture and associate it with the material
+              DALI_LOG_INFO(gLogFilter, Debug::Verbose, "metallicTexture: ");
+              needMetallicTexture = ConvertTexture(material, uvTexture, materialDefinition, MaterialDefinition::METALLIC);
+              if(needMetallicTexture)
+              {
+                DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TraverseMaterials: MaterialDefinition::METALLIC, ");
+              }
+            }
+          }
+          else if(metallicAttr.HasAuthoredValue())
+          {
+            float metallicFactor(0.0f);
+            GetAttributeValue<float>(metallicAttr, metallicFactor);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "metallicFactor: %.7f, ", metallicFactor);
+
+            // Set the metallic factor of the material
+            materialDefinition.mMetallic = metallicFactor;
+          }
+        }
+        else if(baseName == "roughness")
+        {
+          // Process roughness input
+          UsdAttribute roughnessAttr = input.GetAttr();
+          if(input.HasConnectedSource())
+          {
+            TfToken id;
+            if(uvTexture.GetShaderId(&id) && id.GetString() == "UsdUVTexture")
+            {
+              // Convert the texture and associate it with the material
+              DALI_LOG_INFO(gLogFilter, Debug::Verbose, "roughnessTexture: ");
+              needRoughnessTexture = ConvertTexture(material, uvTexture, materialDefinition, MaterialDefinition::ROUGHNESS);
+              if(needRoughnessTexture)
+              {
+                DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TraverseMaterials: MaterialDefinition::MaterialDefinition::ROUGHNESS, ");
+              }
+            }
+          }
+          else if(roughnessAttr.HasAuthoredValue())
+          {
+            float roughnessFactor(0.5f);
+            GetAttributeValue<float>(roughnessAttr, roughnessFactor);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "roughnessFactor: %.7f, ", roughnessFactor);
+
+            // Set the roughness factor of the material
+            materialDefinition.mRoughness = roughnessFactor;
+          }
+        }
+        else if(baseName == "normal")
+        {
+          // Process normal map input
+          UsdAttribute normalAttr = input.GetAttr();
+          if(input.HasConnectedSource())
+          {
+            TfToken id;
+            if(uvTexture.GetShaderId(&id) && id.GetString() == "UsdUVTexture")
+            {
+              // Convert the texture and associate it with the material
+              DALI_LOG_INFO(gLogFilter, Debug::Verbose, "normalTexture: ");
+              needNormalTexture = ConvertTexture(material, uvTexture, materialDefinition, MaterialDefinition::NORMAL);
+              if(needNormalTexture)
+              {
+                DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TraverseMaterials: MaterialDefinition::NORMAL, ");
+              }
+            }
+          }
+          else if(normalAttr.HasAuthoredValue())
+          {
+            GfVec3f normal;
+            GetAttributeValue<GfVec3f>(normalAttr, normal);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "normal: %.7f, %.7f, %.7f, ", normal[0], normal[1], normal[2]);
+          }
+        }
+        else if(baseName == "occlusion")
+        {
+          // Process occlusion map input
+          UsdAttribute occlusionAttr = input.GetAttr();
+          if(input.HasConnectedSource())
+          {
+            TfToken id;
+            if(uvTexture.GetShaderId(&id) && id.GetString() == "UsdUVTexture")
+            {
+              // Convert the texture and associate it with the material
+              DALI_LOG_INFO(gLogFilter, Debug::Verbose, "occlusionTexture: ");
+              if(ConvertTexture(material, uvTexture, materialDefinition, MaterialDefinition::OCCLUSION))
+              {
+                DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TraverseMaterials: MaterialDefinition::OCCLUSION, ");
+              }
+            }
+          }
+          else if(occlusionAttr.HasAuthoredValue())
+          {
+            float occlusion(1.0f);
+            GetAttributeValue<float>(occlusionAttr, occlusion);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "occlusion: %.7f, ", occlusion);
+          }
+        }
+        else if(baseName == "emissiveColor")
+        {
+          // Process emissive color input
+          UsdAttribute emissiveAttr = input.GetAttr();
+          if(input.HasConnectedSource())
+          {
+            TfToken id;
+            if(uvTexture.GetShaderId(&id) && id.GetString() == "UsdUVTexture")
+            {
+              // Convert the texture and associate it with the material
+              DALI_LOG_INFO(gLogFilter, Debug::Verbose, "emissiveColorTexture: ");
+              if(ConvertTexture(material, uvTexture, materialDefinition, MaterialDefinition::EMISSIVE))
+              {
+                DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TraverseMaterials: MaterialDefinition::EMISSIVE, ");
+                materialDefinition.mEmissiveFactor = Vector3::ONE;
+              }
+
+              // Handle emissive color scale
+              UsdShadeInput scaleInput = uvTexture.GetInput(TfToken("scale"));
+              if(scaleInput)
+              {
+                GfVec4d scale;
+                scaleInput.Get<GfVec4d>(&scale);
+                DALI_LOG_INFO(gLogFilter, Debug::Verbose, "emissiveColorScale: %.7f, %.7f, %.7f, %.7f, ", scale[0], scale[1], scale[2], scale[3]);
+              }
+            }
+          }
+
+          if(emissiveAttr.HasAuthoredValue())
+          {
+            GfVec3f emissiveFactor;
+            GetAttributeValue<GfVec3f>(emissiveAttr, emissiveFactor);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "emissiveFactor: %.7f, %.7f, %.7f, ", emissiveFactor[0], emissiveFactor[1], emissiveFactor[2]);
+
+            // Set the emissive factor of the material
+            materialDefinition.mEmissiveFactor = Vector3(emissiveFactor[0], emissiveFactor[1], emissiveFactor[2]);
+          }
+        }
+        else if(baseName == "specularColor")
+        {
+          // Process specular color input
+          UsdAttribute specularAttr = input.GetAttr();
+          if(input.HasConnectedSource())
+          {
+            TfToken id;
+            if(uvTexture.GetShaderId(&id) && id.GetString() == "UsdUVTexture")
+            {
+              // Convert the texture and associate it with the material
+              DALI_LOG_INFO(gLogFilter, Debug::Verbose, "specularColorTexture: ");
+              if(ConvertTexture(material, uvTexture, materialDefinition, MaterialDefinition::SPECULAR_COLOR))
+              {
+                DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TraverseMaterials: MaterialDefinition::SPECULAR_COLOR, ");
+              }
+            }
+          }
+          else if(specularAttr.HasAuthoredValue())
+          {
+            GfVec3f specularColor;
+            GetAttributeValue<GfVec3f>(specularAttr, specularColor);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "specularColor: %.7f, %.7f, %.7f, ", specularColor[0], specularColor[1], specularColor[2]);
+
+            // Set the specular color factor of the material
+            materialDefinition.mSpecularColorFactor = Vector3(specularColor[0], specularColor[1], specularColor[2]);
+          }
+        }
+      }
+
+      // Set alpha mode based on transparency and threshold values
+      if(hasAlpha)
+      {
+        if(hasThreshold)
+        {
+          materialDefinition.mAlphaModeType = Scene3D::Material::AlphaModeType::MASK;
+          materialDefinition.mIsMask        = true;
+          materialDefinition.SetAlphaCutoff(std::min(1.f, std::max(0.f, opacityThreshold)));
+        }
+        else
+        {
+          materialDefinition.mAlphaModeType = Scene3D::Material::AlphaModeType::BLEND;
+          materialDefinition.mIsOpaque      = false;
+          materialDefinition.mFlags |= MaterialDefinition::TRANSPARENCY;
+        }
+      }
+
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TraverseMaterials: materialDefinition.mFlags: %u. needAlbedoTexture: %d, needMetallicRoughnessTexture: %d, needNormalTexture: %d\n", materialDefinition.mFlags, needAlbedoTexture, needMetallicRoughnessTexture, needNormalTexture);
+
+      // Set texture needs in the material definition
+      materialDefinition.mNeedAlbedoTexture            = needAlbedoTexture;
+      materialDefinition.mNeedMetallicRoughnessTexture = needMetallicRoughnessTexture;
+      materialDefinition.mNeedMetallicTexture          = needMetallicTexture;
+      materialDefinition.mNeedRoughnessTexture         = needRoughnessTexture;
+      materialDefinition.mNeedNormalTexture            = needNormalTexture;
+
+      // Add the processed material to the output materials list
+      outMaterials.emplace_back(std::move(materialDefinition), TextureSet());
+    }
+  }
+}
+
+void UsdLoaderImpl::Impl::TraversePrims(LoadResult& output, const UsdPrim& prim, Index parentIndex, int level)
+{
+  PrintLevel(level);
+
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "%s\n", prim.GetName().GetText());
+
+  auto& scene = output.mScene;
+
+  Index nodeIndex = scene.GetNodeCount() - 1;
+
+  if(prim.IsA<UsdGeomMesh>())
+  {
+    ConvertMesh(output, prim, nodeIndex, parentIndex);
+  }
+  else if(prim.IsA<UsdGeomXformable>())
+  {
+    ConvertNode(output, prim, nodeIndex, parentIndex);
+  }
+  else if(prim.IsA<UsdGeomCamera>())
+  {
+    ConvertCamera(output, prim);
+  }
+  else if(prim.IsA<UsdSkelRoot>())
+  {
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, " => UsdSkelRoot\n");
+  }
+  else if(prim.IsA<UsdSkelSkeleton>())
+  {
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, " => UsdSkelSkeleton\n");
+  }
+  else
+  {
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "\n");
+  }
+
+  level++;
+
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "TraversePrims: nodeIndex: %d, ", nodeIndex);
+
+  // Recursively traverse child prims
+  for(const UsdPrim& child : prim.GetChildren())
+  {
+    TraversePrims(output, child, nodeIndex, level);
+  }
+
+  level--;
+}
+
+bool UsdLoaderImpl::Impl::ConvertTexture(const UsdShadeMaterial& usdMaterial, const UsdShadeShader& usdUvTexture, MaterialDefinition& materialDefinition, uint32_t semantic)
+{
+  bool    transformOffsetAuthored = false;
+  GfVec2f uvTransformOffset(0.0f, 0.0f);
+  float   uvTransformRotation = 0.0f;
+  GfVec2f uvTransformScale(0.0f, 0.0f);
+
+  // Get all inputs (primvar reader, transform, etc)
+  std::vector<UsdShadeShader> deps = TraverseShaderInputs(usdUvTexture);
+  deps.push_back(UsdShadeShader(usdUvTexture.GetPrim()));
+
+  for(const auto& d : deps)
+  {
+    TfToken tokenId;
+    d.GetShaderId(&tokenId);
+    std::string depsId = tokenId.GetString();
+
+    // Check if it is a primvar reader (for UV channels)
+    if(depsId == "UsdPrimvarReader_float2")
+    {
+      TfToken tokenVarName("varname");
+      // Handle UV channel
+      UsdShadeInput shaderInput = UsdShadeShader(d).GetInput(tokenVarName);
+      if(shaderInput)
+      {
+        std::string uvMapName;
+        shaderInput.Get<std::string>(&uvMapName);
+        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "uvMapName: %s, ", uvMapName.c_str());
+      }
+    }
+    else if(depsId == "UsdTransform2d") // Check if it is a 2D transform
+    {
+      // Extract transformation attributes (translation, scale, rotation)
+      for(auto input : UsdShadeShader(d).GetInputs())
+      {
+        transformOffsetAuthored = true;
+
+        std::string baseName = input.GetBaseName().GetString();
+        if(baseName == "translation")
+        {
+          TfToken       tokenTranslation(baseName);
+          UsdShadeInput translationInput = UsdShadeShader(d).GetInput(tokenTranslation);
+          if(translationInput)
+          {
+            translationInput.Get<GfVec2f>(&uvTransformOffset);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "uvTransformOffset: %.7f, %.7f, ", uvTransformOffset[0], uvTransformOffset[1]);
+          }
+        }
+        else if(baseName == "scale")
+        {
+          TfToken       tokenScale(baseName);
+          UsdShadeInput scaleInput = UsdShadeShader(d).GetInput(tokenScale);
+          if(scaleInput)
+          {
+            scaleInput.Get<GfVec2f>(&uvTransformScale);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "uvTransformScale: %.7f, %.7f, ", uvTransformScale[0], uvTransformScale[1]);
+          }
+        }
+        else if(baseName == "rotation")
+        {
+          TfToken       tokenRotation(baseName);
+          UsdShadeInput rotationInput = UsdShadeShader(d).GetInput(tokenRotation);
+          if(rotationInput)
+          {
+            rotationInput.Get<float>(&uvTransformRotation); // in Degree, need to convert it to Radian
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "uvTransformRotation: %.7f, ", uvTransformRotation);
+          }
+        }
+      }
+    }
+    else if(depsId == "UsdUVTexture")
+    {
+      // Handle UV texture
+
+      std::string          imagePath;
+      std::vector<uint8_t> imageBuffer;
+
+      // Extract various texture attributes (file, wrapS, wrapT, scale, bias, st, fallback)
+      std::vector<UsdShadeInput> inputs = usdUvTexture.GetInputs();
+      for(auto input : inputs)
+      {
+        std::string baseName = input.GetBaseName().GetString();
+        if(baseName == "file")
+        {
+          // Get the asset path of the texture
+          SdfAssetPath fileInput;
+          input.Get<SdfAssetPath>(&fileInput);
+
+          std::string resolvedAssetPath = fileInput.GetResolvedPath();
+          DALI_LOG_INFO(gLogFilter, Debug::Verbose, "File: %s, ", resolvedAssetPath.c_str());
+
+          imagePath = ConvertImagePath(resolvedAssetPath);
+          DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Converted File Path: %s, ", imagePath.c_str());
+
+          // Load the texture image data as a buffer
+          imageBuffer = LoadAssetFileAsBuffer(resolvedAssetPath);
+        }
+        else if(baseName == "wrapS")
+        {
+          // Handle texture wrapping in S direction
+          TfToken       tokenWrapS(baseName);
+          UsdShadeInput wrapSInput = UsdShadeShader(d).GetInput(tokenWrapS);
+          if(wrapSInput)
+          {
+            TfToken wrapS;
+            wrapSInput.Get<TfToken>(&wrapS);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "wrapS: %s, ", wrapS.GetText());
+          }
+        }
+        else if(baseName == "wrapT")
+        {
+          // Handle texture wrapping in T direction
+          TfToken       tokenWrapT(baseName);
+          UsdShadeInput wrapTInput = UsdShadeShader(d).GetInput(tokenWrapT);
+          if(wrapTInput)
+          {
+            TfToken wrapT;
+            wrapTInput.Get<TfToken>(&wrapT);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "wrapT: %s, ", wrapT.GetText());
+          }
+        }
+        else if(baseName == "scale")
+        {
+          // Handle texture scale
+          TfToken       tokenScale(baseName);
+          UsdShadeInput scaleInput = UsdShadeShader(d).GetInput(tokenScale);
+          if(scaleInput)
+          {
+            GfVec4f scale;
+            scaleInput.Get<GfVec4f>(&scale);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "scale: %.7f, %.7f, %.7f, %.7f, ", scale[0], scale[1], scale[2], scale[3]);
+          }
+        }
+        else if(baseName == "bias")
+        {
+          // Handle texture bias
+          TfToken       tokenBias(baseName);
+          UsdShadeInput biasInput = UsdShadeShader(d).GetInput(tokenBias);
+          if(biasInput)
+          {
+            GfVec4f bias;
+            biasInput.Get<GfVec4f>(&bias);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "bias: %.7f, %.7f, %.7f, %.7f, ", bias[0], bias[1], bias[2], bias[3]);
+          }
+        }
+        else if(baseName == "st")
+        {
+          // Handle texture ST (UV) coordinates
+          TfToken       tokenSt(baseName);
+          UsdShadeInput stInput = UsdShadeShader(d).GetInput(tokenSt);
+          if(stInput)
+          {
+            GfVec2f st;
+            stInput.Get<GfVec2f>(&st);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "st: %.7f, %.7f, ", st[0], st[1]);
+          }
+        }
+        else if(baseName == "fallback")
+        {
+          // Handle fallback color for the texture
+          TfToken       tokenFallback(baseName);
+          UsdShadeInput fallbackInput = UsdShadeShader(d).GetInput(tokenFallback);
+          if(fallbackInput)
+          {
+            GfVec4f fallback;
+            fallbackInput.Get<GfVec4f>(&fallback);
+            DALI_LOG_INFO(gLogFilter, Debug::Verbose, "fallback: %.7f, %.7f, %.7f, %.7f, ", fallback[0], fallback[1], fallback[2], fallback[3]);
+          }
+        }
+      }
+
+      if(imageBuffer.size() > 0)
+      {
+        // If the texture is loaded as an image buffer, add the buffer to the material definition
+        materialDefinition.mTextureStages.push_back({semantic, TextureDefinition{std::move(imageBuffer)}});
+        materialDefinition.mFlags |= semantic;
+        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "mTextureStages.push_back: semantic: %u, mFlags: %u, imageBuffer: %ld, ", semantic, materialDefinition.mFlags, imageBuffer.size());
+
+        return true;
+      }
+      else if(!imagePath.empty())
+      {
+        // Otherwise, add the image file path to the material definition
+        materialDefinition.mTextureStages.push_back({semantic, TextureDefinition{std::move(imagePath)}});
+        materialDefinition.mFlags |= semantic;
+        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "mTextureStages.push_back: semantic: %u, mFlags: %u, imagePath: %s, ", semantic, materialDefinition.mFlags, imagePath.c_str());
+
+        return true;
+      }
+    }
+  }
+
+  if(transformOffsetAuthored)
+  {
+    // @TODO: Process texture transform (similar to KHR_texture_transform) (future work)
+  }
+
+  return false; // Return false if texture conversion fails
+}
+
+void UsdLoaderImpl::Impl::GetXformableTransformation(const UsdPrim& prim, Vector3& position, Quaternion& rotation, Vector3& scale, const UsdTimeCode time)
+{
+  // Retrieve the local transformation matrix of the xformable prim
+  auto       xformable = UsdGeomXformable(prim);
+  GfMatrix4d result(1);
+  bool       resetsXformStack;
+  xformable.GetLocalTransformation(&result, &resetsXformStack, time);
+
+  // Decompose the matrix into position, rotation, and scale components
+  Matrix transformMatrix = ConvertUsdMatrix(result);
+  transformMatrix.GetTransformComponents(position, rotation, scale);
+
+  if(transformMatrix == Dali::Matrix::IDENTITY)
+  {
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "IDENTITY, ");
+  }
+  else
+  {
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Position: %.7f, %.7f, %.7f, ", position.x, position.y, position.z);
+
+    if(rotation == Quaternion::IDENTITY)
+    {
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Rotation: IDENTITY, ");
+    }
+    else
+    {
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Rotation: %.7f, %.7f, %.7f, %.7f, ", rotation.AsVector().x, rotation.AsVector().y, rotation.AsVector().z, rotation.AsVector().w);
+    }
+
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Scale: %.7f, %.7f, %.7f, ", scale.x, scale.y, scale.z);
+  }
+}
+
+NodeDefinition* UsdLoaderImpl::Impl::AddNodeToScene(SceneDefinition& scene, const std::string nodeName, const Index parentIndex, const Vector3& position, const Quaternion& rotation, const Vector3& scale, bool setTransformation)
+{
+  // Add the node to the scene graph
+  auto weakNode = scene.AddNode([&]() {
+        std::unique_ptr<NodeDefinition> nodeDefinition{new NodeDefinition()};
+
+        nodeDefinition->mParentIdx = parentIndex;
+        nodeDefinition->mName      = nodeName;
+        if(nodeDefinition->mName.empty())
+        {
+          nodeDefinition->mName = std::to_string(reinterpret_cast<uintptr_t>(nodeDefinition.get()));
+        }
+
+        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "scene.AddNode (ConvertNode): %s, parentIndex: %d\n", nodeDefinition->mName.c_str(), parentIndex);
+
+        if (setTransformation)
+        {
+          nodeDefinition->mPosition    = position;
+          nodeDefinition->mOrientation = rotation;
+          nodeDefinition->mScale       = scale;
+        }
+
+        return nodeDefinition; }());
+
+  if(!weakNode)
+  {
+    DALI_LOG_ERROR("Failed to create Node %s!\n", nodeName.c_str());
+  }
+
+  return weakNode;
+}
+
+void UsdLoaderImpl::Impl::RetrieveGeomPrimvars(const UsdPrim& prim, std::vector<UsdGeomPrimvar>& texcoords, std::vector<UsdGeomPrimvar>& colors, std::vector<UsdGeomPrimvar>& tangents)
+{
+  UsdGeomPrimvarsAPI          pvAPI(prim);
+  std::vector<UsdGeomPrimvar> primvars = pvAPI.GetPrimvars();
+
+  for(auto p : primvars)
+  {
+    if(p.HasAuthoredValue())
+    {
+      // Collect texture coordinates (UVs), assuming all UVs are stored in one of these primvar types
+      if(p.GetTypeName() == SdfValueTypeNames->TexCoord2hArray || p.GetTypeName() == SdfValueTypeNames->TexCoord2fArray || p.GetTypeName() == SdfValueTypeNames->TexCoord2dArray || (p.GetPrimvarName() == "st" && p.GetTypeName() == SdfValueTypeNames->Float2Array))
+      {
+        texcoords.push_back(p);
+      }
+      else if(p.GetTypeName().GetRole() == SdfValueRoleNames->Color)
+      {
+        // Collect color attributes
+        std::string colorName;
+        size_t      pos = p.GetName().GetString().find(":");
+        if(pos != std::string::npos)
+        {
+          colorName = p.GetName().GetString().substr(pos + 1);
+        }
+
+        if(colorName == "displayColor")
+        {
+          // Add "displayColor" at the front
+          colors.insert(colors.begin(), p);
+        }
+        else
+        {
+          colors.push_back(p);
+        }
+      }
+
+      // Collect tangent attributes
+      if(p.GetName().GetString().find("tangents") != std::string::npos)
+      {
+        tangents.push_back(p);
+      }
+    }
+  }
+
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "texcoords: %lu, colors: %lu, tangents: %lu, ", texcoords.size(), colors.size(), tangents.size());
+}
+
+void UsdLoaderImpl::Impl::ProcessMeshIndices(MeshDefinition& meshDefinition, std::map<int, VtArray<int>>& indexMap, VtIntArray& subsetIdcs, VtArray<int>& triangulatedIndex, std::vector<uint32_t>& subIndexArray, std::vector<uint32_t>& flattenedSubTriangulatedIndices)
+{
+  std::vector<VtArray<int>> subTriangulatedIndices;
+
+  // Get indices for each subset
+  for(int index : subsetIdcs)
+  {
+    subTriangulatedIndices.push_back(indexMap[index]);
+  }
+
+  // Flatten and store the triangulated indices for the current subset
+  for(auto sublist : subTriangulatedIndices)
+  {
+    for(auto item : sublist)
+    {
+      flattenedSubTriangulatedIndices.push_back(item);
+    }
+  }
+
+  for(int index : flattenedSubTriangulatedIndices)
+  {
+    subIndexArray.push_back(triangulatedIndex[index * 3]);
+    subIndexArray.push_back(triangulatedIndex[index * 3 + 1]);
+    subIndexArray.push_back(triangulatedIndex[index * 3 + 2]);
+  }
+
+  std::vector<uint32_t> indexArrayTriangulated;
+  for(size_t k = 0; k < subIndexArray.size(); ++k)
+  {
+    indexArrayTriangulated.push_back(k);
+  }
+
+  // To store the final triangulated indices, we need space for uint32_t.
+  meshDefinition.mRawData->mIndices.resize(indexArrayTriangulated.size() * 2);
+
+  auto indicesData = reinterpret_cast<uint32_t*>(meshDefinition.mRawData->mIndices.data());
+  for(size_t i = 0; i < indexArrayTriangulated.size(); i++)
+  {
+    indicesData[i] = indexArrayTriangulated[i];
+  }
+}
+
+void UsdLoaderImpl::Impl::ProcessMeshPositions(MeshDefinition& meshDefinition, const VtArray<GfVec3f>& points, VtArray<GfVec3f>& worldPosition, std::vector<uint32_t>& subIndexArray)
+{
+  // Process vertex positions
+  for(uint32_t index : subIndexArray)
+  {
+    worldPosition.push_back(points[index]);
+  }
+
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "subIndexArray: %lu, worldPosition: %lu, ", subIndexArray.size(), worldPosition.size());
+
+  // Add vertex positions into the mesh definition
+  std::vector<uint8_t> bufferPositions(worldPosition.size() * sizeof(GfVec3f));
+
+  std::copy(reinterpret_cast<const uint8_t*>(worldPosition.data()),
+            reinterpret_cast<const uint8_t*>(worldPosition.data() + worldPosition.size()),
+            bufferPositions.begin());
+
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "bufferPositions.size: %lu, ", bufferPositions.size());
+
+  meshDefinition.mRawData->mAttribs.push_back({"aPosition", Property::VECTOR3, static_cast<uint32_t>(worldPosition.size()), std::move(bufferPositions)});
+}
+
+void UsdLoaderImpl::Impl::ProcessMeshNormals(MeshDefinition& meshDefinition, UsdGeomMesh& usdMesh, VtArray<GfVec3f>& normals, std::vector<uint32_t>& subIndexArray, std::vector<uint32_t>& flattenedSubTriangulatedIndices, VtArray<int>& faceVertexCounts, bool isLeftHanded)
+{
+  UsdAttribute normalsAttr = usdMesh.GetNormalsAttr();
+  if(normalsAttr.HasValue())
+  {
+    VtVec3fArray rawNormals;
+    GetAttributeValue<VtVec3fArray>(normalsAttr, rawNormals);
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "rawNormals: %lu, ", rawNormals.size());
+
+    if(usdMesh.GetNormalsInterpolation().GetString() == "faceVarying")
+    {
+      // Handle face-varying normals (one normal per face vertex)
+      VtArray<GfVec3f> triangulatedNormal = GetTriangulatedAttribute<GfVec3f>(faceVertexCounts, rawNormals, isLeftHanded);
+
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "normals: faceVarying, triangulatedNormal: %lu, flattenedSubTriangulatedIndices: %lu, ", triangulatedNormal.size(), flattenedSubTriangulatedIndices.size());
+
+      for(int index : flattenedSubTriangulatedIndices)
+      {
+        normals.push_back(static_cast<GfVec3f>(triangulatedNormal[index * 3]));
+        normals.push_back(static_cast<GfVec3f>(triangulatedNormal[index * 3 + 1]));
+        normals.push_back(static_cast<GfVec3f>(triangulatedNormal[index * 3 + 2]));
+      }
+    }
+    else if(usdMesh.GetNormalsInterpolation().GetString() == "vertex")
+    {
+      // Handle vertex-based normals (one normal per vertex)
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "normals: vertex, subIndexArray: %lu, ", subIndexArray.size());
+      for(auto x : subIndexArray)
+      {
+        normals.push_back(static_cast<GfVec3f>(rawNormals[x]));
+      }
+    }
+
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "normals: size = %lu, value: ", normals.size());
+
+    if(normals.size() > 0)
+    {
+      std::vector<uint8_t> bufferNormals(normals.size() * sizeof(GfVec3f));
+
+      std::copy(reinterpret_cast<const uint8_t*>(normals.data()),
+                reinterpret_cast<const uint8_t*>(normals.data() + normals.size()),
+                bufferNormals.begin());
+
+      // Add normal attribute to the mesh definition
+      meshDefinition.mRawData->mAttribs.push_back({"aNormal", Property::VECTOR3, static_cast<uint32_t>(normals.size()), std::move(bufferNormals)});
+    }
+  }
+}
+
+void UsdLoaderImpl::Impl::GenerateNormal(MeshDefinition& meshDefinition)
+{
+  auto& attribs = meshDefinition.mRawData->mAttribs;
+
+  // Determine the number of indices. If indices are not defined, use the number of vertices in the position attribute.
+  const uint32_t numIndices = meshDefinition.mRawData->mIndices.empty() ? attribs[0].mNumElements : static_cast<uint32_t>(meshDefinition.mRawData->mIndices.size() / 2);
+
+  // Pointer to the vertex positions
+  auto* positions = reinterpret_cast<const Vector3*>(attribs[0].mData.data());
+
+  std::vector<uint8_t> buffer(attribs[0].mNumElements * sizeof(Vector3));
+  auto                 normals = reinterpret_cast<Vector3*>(buffer.data());
+
+  // Pointer to the index data
+  auto indicesData = reinterpret_cast<uint32_t*>(meshDefinition.mRawData->mIndices.data());
+
+  // Loop through each triangle (3 indices at a time)
+  for(uint32_t i = 0; i < numIndices; i += 3)
+  {
+    // Get the positions of the three vertices of the triangle
+    Vector3 pos[]{positions[indicesData[i]], positions[indicesData[i + 1]], positions[indicesData[i + 2]]};
+
+    // Compute the edge vectors of the triangle
+    Vector3 a = pos[1] - pos[0]; // Edge from vertex 0 to vertex 1
+    Vector3 b = pos[2] - pos[0]; // Edge from vertex 0 to vertex 2
+
+    // Compute the normal using the cross product of the two edge vectors
+    Vector3 normal(a.Cross(b));
+
+    // Accumulate the normal for each vertex of the triangle
+    normals[indicesData[i]] += normal;
+    normals[indicesData[i + 1]] += normal;
+    normals[indicesData[i + 2]] += normal;
+  }
+
+  // Normalize the accumulated normals to ensure they are unit vectors
+  auto iEnd = normals + attribs[0].mNumElements;
+  while(normals != iEnd)
+  {
+    normals->Normalize();
+    ++normals;
+  }
+
+  // Add generated normals to the mesh definition
+  attribs.push_back({"aNormal", Property::VECTOR3, attribs[0].mNumElements, std::move(buffer)});
+}
+
+void UsdLoaderImpl::Impl::ProcessMeshTexcoords(MeshDefinition& meshDefinition, std::vector<UsdGeomPrimvar>& texcoords, std::vector<uint32_t>& subIndexArray, std::vector<uint32_t>& flattenedSubTriangulatedIndices, VtArray<int>& faceVertexCounts, bool isLeftHanded)
+{
+  if(texcoords.size() > 0 && texcoords.size() <= 2)
+  {
+    // Support up to two texture coordinate sets
+    for(size_t i = 0; i < texcoords.size(); i++)
+    {
+      UsdGeomPrimvar stCoords            = texcoords[i];
+      std::string    stCoordsPrimvarName = stCoords.GetName().GetString();
+      std::string    txName              = stCoordsPrimvarName.substr(stCoordsPrimvarName.find(":") + 1, std::string::npos);
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "texcoords[%lu]: %s, %s, ", i, stCoordsPrimvarName.c_str(), txName.c_str());
+
+      if(stCoords.IsDefined())
+      {
+        VtVec2fArray rawUVs;
+        GetFlattenedPrimvarValue<GfVec2f>(stCoords, rawUVs);
+        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "rawUVs: %lu, value: ", rawUVs.size());
+
+        VtVec2fArray UVs;
+        TfToken      interpolation = stCoords.GetInterpolation();
+        if(interpolation.GetString() == "faceVarying")
+        {
+          // Handle face-varying UVs
+          VtVec2fArray triangulatedUV = GetTriangulatedAttribute<GfVec2f>(faceVertexCounts, rawUVs, isLeftHanded);
+
+          for(int index : flattenedSubTriangulatedIndices)
+          {
+            UVs.push_back(static_cast<GfVec2f>(triangulatedUV[index * 3]));
+            UVs.push_back(static_cast<GfVec2f>(triangulatedUV[index * 3 + 1]));
+            UVs.push_back(static_cast<GfVec2f>(triangulatedUV[index * 3 + 2]));
+          }
+        }
+        else if(interpolation.GetString() == "vertex")
+        {
+          // Handle vertex-based UVs
+          for(auto x : subIndexArray)
+          {
+            UVs.push_back(static_cast<GfVec2f>(rawUVs[x]));
+          }
+        }
+        else
+        {
+          DALI_LOG_ERROR("Unexpected interpolation type %s for UV, ", interpolation.GetString().c_str());
+          continue;
+        }
+
+        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "UVs: size = %lu, value: ", UVs.size());
+
+        // Flip UVs vertically to match the texture coordinate system in DALi
+        VtVec2fArray flipyUVs;
+        for(const auto& uv : UVs)
+        {
+          flipyUVs.push_back(GfVec2f(uv[0], 1.0f - uv[1]));
+        }
+
+        std::vector<uint8_t> bufferTexCoords(flipyUVs.size() * sizeof(GfVec2f));
+
+        std::copy(reinterpret_cast<const uint8_t*>(flipyUVs.data()),
+                  reinterpret_cast<const uint8_t*>(flipyUVs.data() + flipyUVs.size()),
+                  bufferTexCoords.begin());
+
+        // Add texcoord attribute to the mesh definition
+        meshDefinition.mRawData->mAttribs.push_back({"aTexCoord", Property::VECTOR2, static_cast<uint32_t>(flipyUVs.size()), std::move(bufferTexCoords)});
+      }
+    }
+  }
+}
+
+void UsdLoaderImpl::Impl::GenerateTangents(MeshDefinition& meshDefinition, std::vector<UsdGeomPrimvar>& texcoords)
+{
+  auto& attribs = meshDefinition.mRawData->mAttribs;
+
+  // Required positions, normals, uvs (if we have them).
+  std::vector<uint8_t> buffer(attribs[0].mNumElements * sizeof(Vector3));
+  auto                 tangentsData = reinterpret_cast<Vector3*>(buffer.data());
+
+  // Check if UVs are present. Tangents require UV coordinates for calculation.
+  bool hasUVs = texcoords.size() > 0 && attribs.size() == 3;
+
+  if(hasUVs)
+  {
+    // Number of indices (each triangle face has 3 indices).
+    const uint32_t numIndices = meshDefinition.mRawData->mIndices.empty() ? attribs[0].mNumElements : static_cast<uint32_t>(meshDefinition.mRawData->mIndices.size() / 2);
+
+    // Pointers to the vertex positions and UV coordinates.
+    auto* positions = reinterpret_cast<const Vector3*>(attribs[0].mData.data());
+    auto* uvs       = reinterpret_cast<const Vector2*>(attribs[2].mData.data());
+
+    // Pointer to the index data.
+    auto indicesData = reinterpret_cast<uint32_t*>(meshDefinition.mRawData->mIndices.data());
+
+    // Loop over each triangle (three indices at a time).
+    for(uint32_t i = 0; i < numIndices; i += 3)
+    {
+      // Get the positions of the triangle vertices.
+      Vector3 pos[]{positions[indicesData[i]], positions[indicesData[i + 1]], positions[indicesData[i + 2]]};
+
+      // Get the UV coordinates of the triangle vertices.
+      Vector2 uv[]{uvs[indicesData[i]], uvs[indicesData[i + 1]], uvs[indicesData[i + 2]]};
+
+      // Calculate the edge vectors in 3D space.
+      float x0 = pos[1].x - pos[0].x;
+      float y0 = pos[1].y - pos[0].y;
+      float z0 = pos[1].z - pos[0].z;
+
+      float x1 = pos[2].x - pos[0].x;
+      float y1 = pos[2].y - pos[0].y;
+      float z1 = pos[2].z - pos[0].z;
+
+      // Calculate the edge vectors in UV space.
+      float s0 = uv[1].x - uv[0].x;
+      float t0 = uv[1].y - uv[0].y;
+
+      float s1 = uv[2].x - uv[0].x;
+      float t1 = uv[2].y - uv[0].y;
+
+      // Calculate the determinant of the matrix formed by the UV edges.
+      float det = (s0 * t1 - t0 * s1);
+
+      // To avoid division by zero, check the determinant against a small epsilon value.
+      float r = 1.f / ((std::abs(det) < Dali::Epsilon<1000>::value) ? (Dali::Epsilon<1000>::value * (det > 0.0f ? 1.f : -1.f)) : det);
+
+      // Compute the tangent vector using the positions and UVs.
+      Vector3 tangent((x0 * t1 - t0 * x1) * r, (y0 * t1 - t0 * y1) * r, (z0 * t1 - t0 * z1) * r);
+
+      // Accumulate the tangent for each vertex of the triangle.
+      tangentsData[indicesData[i]] += Vector3(tangent);
+      tangentsData[indicesData[i + 1]] += Vector3(tangent);
+      tangentsData[indicesData[i + 2]] += Vector3(tangent);
+    }
+  }
+
+  // Normalize the accumulated tangents.
+  auto* normalsData = reinterpret_cast<const Vector3*>(attribs[1].mData.data());
+  auto  iEnd        = normalsData + attribs[1].mNumElements;
+  while(normalsData != iEnd)
+  {
+    Vector3 tangentVec3;
+    if(hasUVs)
+    {
+      // Tangent is calculated from the accumulated data.
+      tangentVec3 = Vector3((*tangentsData).x, (*tangentsData).y, (*tangentsData).z);
+    }
+    else
+    {
+      // Fallback: generate tangent using the cross product of the normal with the X or Y axis.
+      Vector3 t[]{normalsData->Cross(Vector3::XAXIS), normalsData->Cross(Vector3::YAXIS)};
+      tangentVec3 = t[t[1].LengthSquared() > t[0].LengthSquared()];
+    }
+
+    // Orthogonalize the tangent by subtracting the component in the direction of the normal.
+    tangentVec3 -= *normalsData * normalsData->Dot(tangentVec3);
+    tangentVec3.Normalize();
+
+    // Store the calculated tangent.
+    if(hasUVs)
+    {
+      *tangentsData = tangentVec3;
+    }
+    else
+    {
+      *tangentsData = Vector4(tangentVec3.x, tangentVec3.y, tangentVec3.z, 1.0f);
+    }
+
+    ++tangentsData;
+    ++normalsData;
+  }
+
+  // Add tangent attribute to the mesh definition
+  attribs.push_back({"aTangent", Property::VECTOR3, attribs[0].mNumElements, std::move(buffer)});
+}
+
+void UsdLoaderImpl::Impl::ProcessMeshColors(MeshDefinition& meshDefinition, std::vector<UsdGeomPrimvar>& colors, VtArray<GfVec3f>& worldPosition, std::vector<uint32_t>& subIndexArray, std::vector<uint32_t>& flattenedSubTriangulatedIndices, VtArray<int>& faceVertexCounts, bool isLeftHanded)
+{
+  if(colors.size() > 0)
+  {
+    // We only support up to one color attribute
+    UsdGeomPrimvar displayColor = colors[0];
+
+    std::string colorPrimvarName = displayColor.GetName().GetString();
+    std::string colorName        = colorPrimvarName.substr(colorPrimvarName.find(":") + 1, std::string::npos);
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "displayColor: %s, %s, ", colorPrimvarName.c_str(), colorName.c_str());
+
+    if(displayColor.IsDefined() && displayColor.HasAuthoredValue())
+    {
+      VtVec3fArray rawColors;
+      GetAttributeValue<VtVec3fArray>(displayColor.GetAttr(), rawColors);
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "rawColors: %lu, ", rawColors.size());
+
+      VtArray<GfVec3f> convertedColors;
+
+      TfToken interpolation = displayColor.GetInterpolation();
+      if(interpolation.GetString() == "constant")
+      {
+        // Handle constant color (same color for all vertices)
+        convertedColors = VtArray<GfVec3f>(subIndexArray.size(), rawColors[0]);
+      }
+      else if(interpolation.GetString() == "faceVarying")
+      {
+        // Handle face-varying colors
+        VtVec3fArray triangulatedColors = GetTriangulatedAttribute<GfVec3f>(faceVertexCounts, rawColors, isLeftHanded);
+
+        for(int index : flattenedSubTriangulatedIndices)
+        {
+          convertedColors.push_back(static_cast<GfVec3f>(triangulatedColors[index * 3]));
+          convertedColors.push_back(static_cast<GfVec3f>(triangulatedColors[index * 3 + 1]));
+          convertedColors.push_back(static_cast<GfVec3f>(triangulatedColors[index * 3 + 2]));
+        }
+      }
+      else if(interpolation.GetString() == "vertex")
+      {
+        // Handle vertex colors
+        for(auto x : subIndexArray)
+        {
+          convertedColors.push_back(static_cast<GfVec3f>(rawColors[x]));
+        }
+      }
+      else if(interpolation.GetString() == "uniform")
+      {
+        // Handle uniform colors
+        GetFlattenedPrimvarValue<GfVec3f>(displayColor, rawColors);
+        DALI_LOG_INFO(gLogFilter, Debug::Verbose, "rawColors (uniform): %lu, value: ", rawColors.size());
+
+        for(auto x : subIndexArray)
+        {
+          convertedColors.push_back(static_cast<GfVec3f>(rawColors[x]));
+        }
+      }
+
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "convertedColors: size = %lu, value: ", convertedColors.size());
+
+      // COLOR_0
+
+      std::vector<uint8_t> bufferColors(convertedColors.size() * sizeof(GfVec3f));
+
+      std::copy(reinterpret_cast<const uint8_t*>(convertedColors.data()),
+                reinterpret_cast<const uint8_t*>(convertedColors.data() + convertedColors.size()),
+                bufferColors.begin());
+
+      // Add color attribute to the mesh definition
+      meshDefinition.mRawData->mAttribs.push_back({"aVertexColor", Property::VECTOR3, static_cast<uint32_t>(convertedColors.size()), std::move(bufferColors)});
+    }
+  }
+  else if(worldPosition.size() > 0)
+  {
+    // If no colors are defined, use white color (Vector4::ONE)
+    std::vector<uint8_t> buffer(worldPosition.size() * sizeof(Vector4));
+    auto                 bufferColors = reinterpret_cast<Vector4*>(buffer.data());
+
+    for(uint32_t i = 0; i < worldPosition.size(); i++)
+    {
+      bufferColors[i] = Vector4::ONE;
+    }
+
+    // Add default white color attribute
+    meshDefinition.mRawData->mAttribs.push_back({"aVertexColor", Property::VECTOR4, static_cast<uint32_t>(worldPosition.size()), std::move(buffer)});
+  }
+}
+
+void UsdLoaderImpl::Impl::ProcessMaterialBinding(LoadResult& output, const UsdPrim& prim, std::vector<UsdGeomSubset>& subsets, size_t subIndex, int& meshSubMaterialId)
+{
+  auto& outMaterials = output.mResources.mMaterials;
+
+  int meshMaterialId = INVALID_INDEX;
+
+  UsdShadeMaterialBindingAPI materialAPI  = UsdShadeMaterialBindingAPI(prim);
+  std::string                materialPath = materialAPI.ComputeBoundMaterial().GetPrim().GetPath().GetString();
+
+  if(auto material = mMaterialMap.find(materialPath); material != mMaterialMap.end())
+  {
+    meshMaterialId = material->second;
+  }
+
+  bool doubleSided(false);
+  if(prim.HasAttribute(TfToken("doubleSided")) && meshMaterialId >= 0)
+  {
+    // Handle double sidedness
+    UsdAttribute doubleSidedAttr = UsdGeomMesh(prim).GetDoubleSidedAttr();
+    GetAttributeValue<bool>(doubleSidedAttr, doubleSided);
+
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "doubleSided: %d, ", doubleSided);
+
+    outMaterials[meshMaterialId].first.mDoubleSided = doubleSided;
+  }
+
+  // Set default mesh material if no material is bound
+  if(meshMaterialId >= 0)
+  {
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "meshMaterialId: %d, materialPath: %s, ", meshMaterialId, materialPath.c_str());
+  }
+  else
+  {
+    // The default material is used when a mesh does not specify a material
+    if(mDefaultMaterial == INVALID_INDEX)
+    {
+      mDefaultMaterial = outMaterials.size();
+
+      MaterialDefinition materialDefinition;
+      materialDefinition.mFlags |= MaterialDefinition::GLTF_CHANNELS;
+      materialDefinition.mShadowAvailable              = true;
+      materialDefinition.mNeedAlbedoTexture            = false;
+      materialDefinition.mNeedMetallicRoughnessTexture = false;
+      materialDefinition.mNeedNormalTexture            = false;
+
+      outMaterials.emplace_back(std::move(materialDefinition), TextureSet());
+    }
+
+    meshMaterialId = mDefaultMaterial;
+  }
+
+  meshSubMaterialId = meshMaterialId;
+
+  std::string subsetMaterialPath;
+
+  // Set material for the subset of the mesh
+  auto                       subset            = subsets[subIndex];
+  UsdShadeMaterialBindingAPI subsetMaterialAPI = UsdShadeMaterialBindingAPI(subset.GetPrim());
+  subsetMaterialPath                           = subsetMaterialAPI.ComputeBoundMaterial().GetPath().GetString();
+
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "subsetMaterialPath: %s, ", subsetMaterialPath.c_str());
+
+  if(auto subMaterial = mMaterialMap.find(subsetMaterialPath); subMaterial != mMaterialMap.end())
+  {
+    meshSubMaterialId = subMaterial->second;
+  }
+
+  if(meshSubMaterialId >= 0)
+  {
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "meshSubMaterialId: %d, subsetMaterialPath: %s, ", meshSubMaterialId, subsetMaterialPath.c_str());
+
+    // Set double-sided property if applicable
+    outMaterials[meshSubMaterialId].first.mDoubleSided = doubleSided;
+  }
+  else
+  {
+    meshSubMaterialId = mDefaultMaterial;
+  }
+}
+
+void UsdLoaderImpl::Impl::ConvertMesh(LoadResult& output, const UsdPrim& prim, Index& nodeIndex, Index parentIndex)
+{
+  auto& scene = output.mScene;
+
+  nodeIndex = scene.GetNodeCount();
+
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, " => UsdGeomMesh %d, nodeIndex: %d, parentIndex: %d, ", mMeshCount, nodeIndex, parentIndex);
+  mMeshCount++;
+
+  Vector3    position;
+  Quaternion rotation;
+  Vector3    scale;
+  Matrix     transformMatrix;
+
+  // Handle transformation for non-skeleton mesh nodes
+  bool isNonSkeletonMeshNode = prim.IsA<UsdGeomXformable>() && !prim.IsA<UsdSkelSkeleton>();
+  if(isNonSkeletonMeshNode)
+  {
+    GetXformableTransformation(prim, position, rotation, scale);
+  }
+
+  // Create a new node for the mesh in the scene graph
+  nodeIndex                = scene.GetNodeCount();
+  NodeDefinition* weakNode = AddNodeToScene(scene, prim.GetName().GetString(), parentIndex, position, rotation, scale, isNonSkeletonMeshNode);
+
+  // @TODO: Handle xform animation (future work)
+
+  // Start processing the mesh geometry
+  UsdGeomMesh usdMesh = UsdGeomMesh(prim);
+
+  // Retrieve the mesh's vertices (points)
+  UsdAttribute     pointsAttr = usdMesh.GetPointsAttr();
+  VtArray<GfVec3f> points;
+  GetAttributeValue<VtArray<GfVec3f>>(pointsAttr, points);
+
+  if(points.empty())
+  {
+    DALI_LOG_ERROR("No points in mesh, ");
+    mMeshCount--;
+  }
+  else
+  {
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "PointsCount: %lu, ", points.size());
+
+    auto& outMeshes = output.mResources.mMeshes;
+
+    // Get Face Vertex Counts (number of vertices per face)
+    VtArray<int> faceVertexCounts;
+    UsdAttribute facesAttr = usdMesh.GetFaceVertexCountsAttr();
+    GetAttributeValue<VtArray<int>>(facesAttr, faceVertexCounts);
+
+    // Get Face Vertex Indices (index of vertices for each face)
+    VtArray<int> faceVertexIndices;
+    UsdAttribute indicesAttr = usdMesh.GetFaceVertexIndicesAttr();
+    GetAttributeValue<VtArray<int>>(indicesAttr, faceVertexIndices);
+
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "FaceVertexCounts: %lu, FaceVertexIndices: %lu, ", faceVertexCounts.size(), faceVertexIndices.size());
+
+    std::vector<UsdGeomPrimvar> texcoords;
+    std::vector<UsdGeomPrimvar> colors;
+    std::vector<UsdGeomPrimvar> tangents;
+
+    // Check for UV, color, and tangent attributes
+    RetrieveGeomPrimvars(prim, texcoords, colors, tangents);
+
+    // Determine if the mesh uses left-handed or right-handed coordinates
+    TfToken      orientation;
+    UsdAttribute orientationAttr = usdMesh.GetOrientationAttr();
+    GetAttributeValue<TfToken>(orientationAttr, orientation);
+
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "orientation: %s, ", orientation.GetText());
+
+    bool isLeftHanded = orientation.GetString() != "rightHanded";
+
+    // Maps triangulated indices
+    std::map<int, VtArray<int>> indexMap;
+    int                         j = 0;
+    for(size_t i = 0; i < faceVertexCounts.size(); ++i)
+    {
+      VtArray<int> tmp;
+      for(int k = 0; k < faceVertexCounts[i] - 2; ++k)
+      {
+        tmp.push_back(j);
+        j++;
+      }
+      indexMap[i] = tmp;
+    }
+
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "indexMap: %lu, ", indexMap.size());
+
+    // Triangulate face indices
+    VtArray<int> triangulatedIndex = GetTriangulatedAttribute<int>(faceVertexCounts, faceVertexIndices, isLeftHanded);
+
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "triangulatedIndex: %lu, ", triangulatedIndex.size());
+
+    // Get mesh subsets (i.e. the group of faces sharing the same material)
+    std::vector<UsdGeomSubset> subsets          = UsdGeomSubset::GetAllGeomSubsets(usdMesh);
+    VtIntArray                 remainingIndices = UsdGeomSubset::GetUnassignedIndices(subsets, faceVertexCounts.size());
+    if(remainingIndices.size() > 0)
+    {
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "extra subset: remainingIndices: %lu, ", remainingIndices.size());
+
+      // Handle the case where a prim is an instance and therefore cannot be modified
+      UsdPrim p = prim;
+      while(p.IsInstance())
+      {
+        p = p.GetParent();
+      }
+
+      // Create a subset for unassigned faces
+      subsets.emplace_back(UsdGeomSubset::CreateGeomSubset(UsdGeomImageable(p), UsdGeomTokens->partition, UsdGeomTokens->face, remainingIndices));
+    }
+
+    size_t numSubsets = subsets.size();
+
+    // Reserve space for the mesh renderables
+    weakNode->mRenderables.reserve(numSubsets);
+
+    // Prepare subset indices
+    std::vector<VtIntArray> subsetIndices(numSubsets, VtIntArray());
+    for(size_t i = 0; i < numSubsets; ++i)
+    {
+      VtIntArray indices;
+      GetAttributeValue<VtIntArray>(subsets[i].GetIndicesAttr(), indices);
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "indices[%lu]: %lu, ", i, indices.size());
+
+      if(indices == remainingIndices)
+      {
+        subsetIndices[i] = remainingIndices;
+      }
+      else
+      {
+        subsetIndices[i] = indices;
+      }
+    }
+
+    // Process each subset of the mesh
+    for(size_t subIndex = 0; subIndex < numSubsets; ++subIndex)
+    {
+      // Initialize a mesh definition for each subset
+      MeshDefinition meshDefinition;
+      meshDefinition.mRawData = std::make_shared<MeshDefinition::RawData>();
+      meshDefinition.mFlags |= MeshDefinition::U32_INDICES;
+      meshDefinition.mSkeletonIdx = INVALID_INDEX;
+
+      // Process indices
+      auto&                 subsetIdcs = subsetIndices[subIndex];
+      std::vector<uint32_t> subIndexArray;
+      std::vector<uint32_t> flattenedSubTriangulatedIndices;
+
+      ProcessMeshIndices(meshDefinition, indexMap, subsetIdcs, triangulatedIndex, subIndexArray, flattenedSubTriangulatedIndices);
+
+      // Process vertex positions
+      VtArray<GfVec3f> worldPosition;
+      ProcessMeshPositions(meshDefinition, points, worldPosition, subIndexArray);
+
+      // Process normals
+      VtArray<GfVec3f> normals;
+      ProcessMeshNormals(meshDefinition, usdMesh, normals, subIndexArray, flattenedSubTriangulatedIndices, faceVertexCounts, isLeftHanded);
+
+      // Generate normals if not provided
+      if(normals.size() == 0 && meshDefinition.mRawData->mAttribs.size() > 0) // Check if normals are missing but positions are available
+      {
+        GenerateNormal(meshDefinition);
+      }
+
+      // Process texture coordinates (texcoords)
+      ProcessMeshTexcoords(meshDefinition, texcoords, subIndexArray, flattenedSubTriangulatedIndices, faceVertexCounts, isLeftHanded);
+
+      // Generate Tangents
+      GenerateTangents(meshDefinition, texcoords);
+
+      // Process vertex colors
+      ProcessMeshColors(meshDefinition, colors, worldPosition, subIndexArray, flattenedSubTriangulatedIndices, faceVertexCounts, isLeftHanded);
+
+      // Add the processed meshes to the output meshes list
+      outMeshes.emplace_back(std::move(meshDefinition), MeshGeometry{});
+
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "outMeshes: mIndices: %lu, mAttribs: %lu, ", outMeshes.back().first.mRawData->mIndices.size(), outMeshes.back().first.mRawData->mAttribs.size());
+
+      // Process material binding
+      int meshSubMaterialId;
+      ProcessMaterialBinding(output, prim, subsets, subIndex, meshSubMaterialId);
+
+      // Create a renderable object for the model and associate the mesh and material with the renderable
+      std::unique_ptr<NodeDefinition::Renderable> renderable;
+
+      auto modelRenderable      = new ModelRenderable();
+      modelRenderable->mMeshIdx = mMeshCount - 1 + subIndex;
+
+      modelRenderable->mMaterialIdx = meshSubMaterialId;
+
+      renderable.reset(modelRenderable);
+      weakNode->mRenderables.push_back(std::move(renderable));
+
+      DALI_LOG_INFO(gLogFilter, Debug::Verbose, "weakNode %s->mRenderables.push_back, ", weakNode->mName.c_str());
+    }
+  }
+
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "\n");
+}
+
+void UsdLoaderImpl::Impl::ConvertNode(LoadResult& output, const UsdPrim& prim, Index& nodeIndex, Index parentIndex)
+{
+  auto& scene = output.mScene;
+
+  nodeIndex = scene.GetNodeCount();
+
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, " => UsdGeomXformable %d: parentIndex: %d, ", nodeIndex, parentIndex);
+
+  // Retrieve the local transformation matrix for the node
+  Vector3    position;
+  Quaternion rotation;
+  Vector3    scale;
+  GetXformableTransformation(prim, position, rotation, scale);
+
+  // Create a new node for the prim in the scene graph
+  nodeIndex = scene.GetNodeCount();
+  AddNodeToScene(scene, prim.GetName().GetString(), parentIndex, position, rotation, scale, true);
+
+  // @TODO: Handle xform animation (future work)
+}
+
+void UsdLoaderImpl::Impl::ConvertCamera(LoadResult& output, const UsdPrim& prim)
+{
+  auto& cameraParameters = output.mCameraParameters;
+
+  // Initialize camera parameters with default values if not present
+  if(cameraParameters.empty())
+  {
+    cameraParameters.push_back(CameraParameters());
+    cameraParameters[0].matrix.SetTranslation(CAMERA_DEFAULT_POSITION);
+  }
+
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, " => UsdGeomCamera: ");
+
+  // Convert camera properties from USD to internal representation
+  UsdGeomCamera camera         = UsdGeomCamera(prim);
+  UsdAttribute  projectionAttr = camera.GetProjectionAttr();
+  if(projectionAttr.HasValue())
+  {
+    TfToken projection("");
+    GetAttributeValue<TfToken>(projectionAttr, projection);
+
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "projection: %s, ", projection.GetText());
+
+    if(projection.GetString() == "perspective")
+    {
+      cameraParameters[0].isPerspective = true;
+    }
+    else if(projection.GetString() == "orthographic")
+    {
+      cameraParameters[0].isPerspective = false;
+    }
+  }
+
+  GfCamera     gfCamera          = camera.GetCamera(UsdTimeCode::Default());
+  UsdAttribute clippingRangeAttr = camera.GetClippingRangeAttr();
+  if(clippingRangeAttr.HasValue())
+  {
+    GfVec2f clippingRange(0.0f, 0.0f);
+    GetAttributeValue<GfVec2f>(clippingRangeAttr, clippingRange);
+
+    DALI_LOG_INFO(gLogFilter, Debug::Verbose, "zNear: %.7f, zFar: %.7f, ", clippingRange[0], clippingRange[1]);
+
+    cameraParameters[0].zNear = clippingRange[0];
+    cameraParameters[0].zFar  = clippingRange[1];
+  }
+
+  Radian yFOV = Radian(gfCamera.GetFieldOfView(GfCamera::FOVVertical));
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "yFOV: %f, ", yFOV.radian);
+  cameraParameters[0].yFovDegree = Degree(gfCamera.GetFieldOfView(GfCamera::FOVVertical));
+
+  float aspectRatio = gfCamera.GetAspectRatio();
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "aspectRatio: %.7f, ", aspectRatio);
+  cameraParameters[0].aspectRatio = aspectRatio;
+
+  float apertureX = gfCamera.GetHorizontalAperture() / 10.0f;
+  float apertureY = gfCamera.GetVerticalAperture() / 10.0f;
+  DALI_LOG_INFO(gLogFilter, Debug::Verbose, "apertureX: %.7f, apertureY: %.7f\n", apertureX, apertureY);
+
+  // Apply the camera's transform matrix to the camera parameters
+  GfMatrix4d matrix          = gfCamera.GetTransform();
+  cameraParameters[0].matrix = ConvertUsdMatrix(matrix);
+}
+
+} // namespace Dali::Scene3D::Loader
diff --git a/dali-usd-loader/internal/usd-loader-impl.h b/dali-usd-loader/internal/usd-loader-impl.h
new file mode 100644 (file)
index 0000000..e53b714
--- /dev/null
@@ -0,0 +1,46 @@
+#ifndef DALI_SCENE3D_LOADER_USD_LOADER_IMPL_H
+#define DALI_SCENE3D_LOADER_USD_LOADER_IMPL_H
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <string>
+
+// INTERNAL INCLUDES
+#include <dali-scene3d/public-api/loader/model-loader-impl.h>
+
+namespace Dali::Scene3D::Loader
+{
+class UsdLoaderImpl : public ModelLoaderImpl
+{
+public:
+  UsdLoaderImpl();
+  ~UsdLoaderImpl();
+
+  /**
+   * @copydoc Dali::Scene3D::Loader::Internal::ModelLoaderImpl::LoadMode()
+   */
+  bool LoadModel(const std::string& url, Dali::Scene3D::Loader::LoadResult& result) override;
+
+private:
+  struct Impl;
+  const std::unique_ptr<Impl> mImpl;
+};
+
+} // namespace Dali::Scene3D::Loader
+
+#endif // DALI_SCENE3D_LOADER_USD_LOADER_IMPL_H
index 33c79471fa9cc7d6b2de6239dd514750e98a9119..364d709eb896977e205aa0ae8c55931e1c36b4a4 100644 (file)
@@ -1,6 +1,6 @@
 Name:       dali2-toolkit
 Summary:    Dali 3D engine Toolkit
-Version:    2.3.42
+Version:    2.3.43
 Release:    1
 Group:      System/Libraries
 License:    Apache-2.0 and BSD-3-Clause and MIT
@@ -21,6 +21,10 @@ BuildRequires:  pkgconfig(egl)
 BuildRequires:  gettext
 BuildRequires:  pkgconfig(libtzplatform-config)
 
+%if 0%{?enable_usd_loader}
+BuildRequires:  openusd-devel
+%endif
+
 #############################
 # profile setup
 #############################
@@ -160,6 +164,31 @@ Requires:   %{dali2_physics3d} = %{version}-%{release}
 %description -n %{dali2_physics3d}-devel
 Development components for dali2-physics-3d.
 
+%if 0%{?enable_usd_loader}
+##############################
+# dali-usd-loader
+##############################
+%define dali2_usdloader dali2-usd-loader
+%package -n %{dali2_usdloader}
+Summary:    USD model loading library
+Group:      System/Libraries
+License:    Apache-2.0
+Requires:   %{dali2_scene3d}
+Requires:   openusd
+
+%description -n %{dali2_usdloader}
+Provides functionality for loading and rendering USD models. See README.md for more details.
+
+%package -n %{dali2_usdloader}-devel
+Summary:    Development components for dali-usd-loader
+Group:      Development/Building
+Requires:   %{dali2_usdloader} = %{version}-%{release}
+Requires:   %{dali2_scene3d}-devel
+
+%description -n %{dali2_usdloader}-devel
+Development components for dali-usd-loader.
+%endif
+
 %define dali_data_rw_dir            %TZ_SYS_SHARE/dali/
 %define dali_data_ro_dir            %TZ_SYS_RO_SHARE/dali/
 
@@ -348,6 +377,12 @@ pushd %{dali_toolkit_style_files}/1920x1080_rpi
 for FILE in *; do mv ./"${FILE}" ../"${FILE}"; done
 popd
 
+%if 0%{?enable_usd_loader}
+%post -n %{dali2_usdloader}
+/sbin/ldconfig
+exit 0
+%endif
+
 ##############################
 # Pre Uninstall
 ##############################
@@ -459,6 +494,12 @@ case "$1" in
   ;;
 esac
 
+%if 0%{?enable_usd_loader}
+%postun -n %{dali2_usdloader}
+/sbin/ldconfig
+exit 0
+%endif
+
 ##############################
 # Files in Binary Packages
 ##############################
@@ -583,3 +624,19 @@ esac
 %{_includedir}/bullet/*
 %{_libdir}/pkgconfig/dali2-physics-3d.pc
 %{_libdir}/pkgconfig/bullet3.pc
+
+%if 0%{?enable_usd_loader}
+%files -n %{dali2_usdloader}
+%if 0%{?enable_dali_smack_rules}
+%manifest dali-usd-loader.manifest-smack
+%else
+%manifest dali-usd-loader.manifest
+%endif
+%defattr(-,root,root,-)
+%{_libdir}/lib%{dali2_usdloader}.so
+%license LICENSE
+
+%files -n %{dali2_usdloader}-devel
+%defattr(-,root,root,-)
+%{_libdir}/pkgconfig/dali2-usd-loader.pc
+%endif
\ No newline at end of file